"use client"; import { FormEvent, Fragment, useCallback, useEffect, useRef, useState, } from "react"; import { Controller, useForm } from "react-hook-form"; import * as z from "zod"; import { zodResolver } from "@hookform/resolvers/zod"; import Swal from "sweetalert2"; import withReactContent from "sweetalert2-react-content"; import { Input, Textarea } from "@heroui/input"; import dynamic from "next/dynamic"; import JoditEditor from "jodit-react"; import { useDropzone } from "react-dropzone"; import { Button } from "@heroui/button"; import { CloudUploadIcon, TimesIcon } from "@/components/icons"; import Image from "next/image"; import { Switch } from "@heroui/switch"; import { createArticle, createArticleSchedule, getArticleByCategory, uploadArticleFile, uploadArticleThumbnail, } from "@/services/article"; import ReactSelect from "react-select"; import makeAnimated from "react-select/animated"; import { Calendar, Checkbox, Chip, Modal, ModalBody, ModalContent, ModalFooter, ModalHeader, Popover, PopoverContent, PopoverTrigger, Select, SelectItem, SelectSection, useDisclosure, } from "@heroui/react"; import GenerateSingleArticleForm from "./generate-ai-single-form"; import { convertDateFormatNoTime, htmlToString } from "@/utils/global"; import { close, error, loading, successToast } from "@/config/swal"; import { useRouter } from "next/navigation"; import Link from "next/link"; import { getCategoryById } from "@/services/master-categories"; import { saveManualContext, updateManualArticle, } from "@/services/generate-article"; import GenerateContentRewriteForm from "./generate-ai-content-rewrite-form"; import Datepicker from "react-tailwindcss-datepicker"; import Cookies from "js-cookie"; import { getUserLevels } from "@/services/user-levels/user-levels-service"; const CustomEditor = dynamic( () => { return import("@/components/editor/custom-editor"); }, { ssr: false } ); interface FileWithPreview extends File { preview: string; } interface CategoryType { id: number; label: string; value: number; } const categorySchema = z.object({ id: z.number(), label: z.string(), value: z.number(), }); interface DiseData { id: number; articleBody: string; title: string; metaTitle: string; description: string; metaDescription: string; mainKeyword: string; additionalKeywords: string; } const createArticleSchema = z.object({ title: z.string().min(2, { message: "Judul harus diisi", }), slug: z.string().min(2, { message: "Slug harus diisi", }), description: z.string().min(2, { message: "Deskripsi harus diisi", }), category: z.array(categorySchema).nonempty({ message: "Kategori harus memiliki setidaknya satu item", }), tags: z.array(z.string()).nonempty({ message: "Minimal 1 tag", }), }); export default function CreateArticleForm() { const { isOpen, onOpen, onOpenChange } = useDisclosure(); const userLevel = Cookies.get("ulne"); const animatedComponents = makeAnimated(); const MySwal = withReactContent(Swal); const router = useRouter(); const editor = useRef(null); const [files, setFiles] = useState([]); const [useAi, setUseAI] = useState(false); const [listCategory, setListCategory] = useState([]); const [tag, setTag] = useState(""); const [thumbnailImg, setThumbnailImg] = useState([]); const [selectedMainImage, setSelectedMainImage] = useState( null ); const [thumbnailValidation, setThumbnailValidation] = useState(""); const [filesValidation, setFileValidation] = useState(""); const [diseData, setDiseData] = useState(); const [selectedWritingType, setSelectedWritingType] = useState("single"); const [status, setStatus] = useState<"publish" | "draft" | "scheduled">( "publish" ); const [isScheduled, setIsScheduled] = useState(false); const [timeValue, setTimeValue] = useState(""); const [startDateValue, setStartDateValue] = useState(null); const { getRootProps, getInputProps } = useDropzone({ onDrop: (acceptedFiles) => { setFiles((prevFiles) => [ ...prevFiles, ...acceptedFiles.map((file) => Object.assign(file)), ]); }, multiple: true, accept: { "image/*": [], }, }); const formOptions = { resolver: zodResolver(createArticleSchema), defaultValues: { title: "", description: "", category: [], tags: [] }, }; type UserSettingSchema = z.infer; const { register, control, handleSubmit, formState: { errors, isValid }, setValue, getValues, watch, setError, clearErrors, } = useForm(formOptions); useEffect(() => { fetchCategory(); }, []); const fetchCategory = async () => { const res = await getArticleByCategory(); if (res?.data?.data) { setupCategory(res?.data?.data); } }; const setupCategory = (data: any) => { const temp = []; for (const element of data) { temp.push({ id: element.id, label: element.title, value: element.id, }); } setListCategory(temp); }; const onSubmit = async (values: z.infer) => { if ((thumbnailImg.length < 1 && !selectedMainImage) || files.length < 1) { if (files.length < 1) { setFileValidation("Required"); } else { setFileValidation(""); } if (thumbnailImg.length < 1 && !selectedMainImage) { setThumbnailValidation("Required"); } else { setThumbnailValidation(""); } } else { setThumbnailValidation(""); setFileValidation(""); MySwal.fire({ title: "Simpan Data", text: "", icon: "warning", showCancelButton: true, cancelButtonColor: "#d33", confirmButtonColor: "#3085d6", confirmButtonText: "Simpan", }).then((result) => { if (result.isConfirmed) { save(values); } }); } }; useEffect(() => { if (useAi === false) { setValue("description", ""); } }, [useAi]); function removeImgTags(htmlString: string) { const parser = new DOMParser(); const doc = parser.parseFromString(String(htmlString), "text/html"); const images = doc.querySelectorAll("img"); images.forEach((img) => img.remove()); return doc.body.innerHTML; } const saveArticleToDise = async ( values: z.infer ) => { if (useAi) { const request = { id: diseData?.id, title: values.title, articleBody: removeImgTags(values.description), metaDescription: diseData?.metaDescription, metaTitle: diseData?.metaTitle, mainKeyword: diseData?.mainKeyword, additionalKeywords: diseData?.additionalKeywords, createdBy: "345", style: "Informational", projectId: 2, clientId: "humasClientIdtest", lang: "id", }; const res = await updateManualArticle(request); if (res.error) { error(res.message); return false; } return diseData?.id; } else { const request = { title: values.title, articleBody: removeImgTags(values.description), metaDescription: values.title, metaTitle: values.title, mainKeyword: values.title, additionalKeywords: values.title, createdBy: "345", style: "Informational", projectId: 2, clientId: "humasClientIdtest", lang: "id", }; const res = await saveManualContext(request); if (res.error) { res.message; return 0; } return res?.data?.data?.id; } }; const getUserLevelApprovalStatus = async () => { const res = await getUserLevels(String(userLevel)); return res?.data?.data?.isApprovalActive; }; const save = async (values: z.infer) => { loading(); const userLevelStatus = await getUserLevelApprovalStatus(); const formData = { title: values.title, typeId: 1, slug: values.slug, categoryIds: values.category.map((a) => a.id).join(","), tags: values.tags.join(","), description: htmlToString(removeImgTags(values.description)), htmlDescription: removeImgTags(values.description), aiArticleId: await saveArticleToDise(values), // isDraft: userLevelStatus ? true : status === "draft", // isPublish: userLevelStatus ? false : status === "publish", isDraft: status === "draft", isPublish: status === "publish", }; const response = await createArticle(formData); if (response?.error) { error(response.message); return false; } const articleId = response?.data?.data?.id; if (files?.length > 0) { const formFiles = new FormData(); for (const element of files) { formFiles.append("file", element); const resFile = await uploadArticleFile(articleId, formFiles); } } if (thumbnailImg?.length > 0 || files?.length > 0) { if (thumbnailImg?.length > 0) { const formFiles = new FormData(); formFiles.append("files", thumbnailImg[0]); const resFile = await uploadArticleThumbnail(articleId, formFiles); } else { const formFiles = new FormData(); if (selectedMainImage) { formFiles.append("files", files[selectedMainImage - 1]); const resFile = await uploadArticleThumbnail(articleId, formFiles); } } } if (status === "scheduled") { const request = { id: articleId, date: `${startDateValue?.year}-${startDateValue?.month}-${startDateValue?.day}`, }; const res = await createArticleSchedule(request); } close(); successSubmit("/admin/article", articleId, values.slug); }; function successSubmit(redirect: string, id: number, slug: string) { const url = `${window.location.protocol}//${window.location.host}` + "/news/detail/" + `${id}-${slug}`; MySwal.fire({ title: "Sukses", icon: "success", confirmButtonColor: "#3085d6", confirmButtonText: "OK", }).then((result) => { if (result.isConfirmed) { router.push(redirect); successToast("Article Url", url); } else { router.push(redirect); successToast("Article Url", url); } }); } const watchTitle = watch("title"); const generateSlug = (title: string) => { return title .toLowerCase() .trim() .replace(/[^\w\s-]/g, "") .replace(/\s+/g, "-"); }; useEffect(() => { setValue("slug", generateSlug(watchTitle)); }, [watchTitle]); const renderFilePreview = (file: FileWithPreview) => { if (file.type.startsWith("image")) { return ( {file.name} ); } else { return "Not Found"; } }; const handleRemoveFile = (file: FileWithPreview) => { const uploadedFiles = files; const filtered = uploadedFiles.filter((i) => i.name !== file.name); setFiles([...filtered]); }; const fileList = files.map((file, index) => (
{renderFilePreview(file)}
{file.name}
{Math.round(file.size / 100) / 10 > 1000 ? ( <>{(Math.round(file.size / 100) / 10000).toFixed(1)} ) : ( <>{(Math.round(file.size / 100) / 10).toFixed(1)} )} {" kb"}
setSelectedMainImage(index + 1)} >

Jadikan Thumbnail

)); const handleFileChange = (event: React.ChangeEvent) => { const selectedFiles = event.target.files; if (selectedFiles) { setThumbnailImg(Array.from(selectedFiles)); } }; const selectedCategory = watch("category"); useEffect(() => { getDetailCategory(); }, [selectedCategory]); const getDetailCategory = async () => { let temp = getValues("tags"); for (const element of selectedCategory) { const res = await getCategoryById(element?.id); const tagList = res?.data?.data?.tags; if (tagList) { temp = [...temp, ...res?.data?.data?.tags]; } } const uniqueArray = temp.filter( (item, index) => temp.indexOf(item) === index ); setValue("tags", uniqueArray as [string, ...string[]]); }; return (

Judul

( )} /> {errors?.title && (

{errors.title?.message}

)}

Slug

( )} /> {errors?.slug && (

{errors.slug?.message}

)}

Bantuan AI

{useAi && (
{selectedWritingType === "single" ? ( { setDiseData(data); setValue( "description", data?.articleBody ? data?.articleBody : "" ); }} /> ) : ( { setDiseData(data); setValue( "description", data?.articleBody ? data?.articleBody : "" ); }} /> )}
)}

Deskripsi

( )} /> {errors?.description && (

{errors.description?.message}

)}

File Media

Tarik file disini atau klik untuk upload.

( Upload file dengan format .jpg, .jpeg, atau .png. Ukuran maksimal 100mb.)
{files.length ? (
{fileList}
) : null}
{filesValidation !== "" && files.length < 1 && (

Upload File Media

)}

Thubmnail

{selectedMainImage && files.length >= selectedMainImage ? (
thumbnail
) : thumbnailImg.length > 0 ? (
thumbnail
) : ( <> {/* {" "} */} {thumbnailValidation !== "" && (

Upload thumbnail atau pilih dari File Media

)} )}

Kategori

( "!rounded-lg bg-white !border-1 !border-gray-200 dark:!border-stone-500", }} classNamePrefix="select" onChange={onChange} closeMenuOnSelect={false} components={animatedComponents} isClearable={true} isSearchable={true} isMulti={true} placeholder="Kategori..." name="sub-module" options={listCategory} /> )} /> {errors?.category && (

{errors.category?.message}

)}

Tags

(