"use client"; import { Fragment, 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 dynamic from "next/dynamic"; import { useDropzone } from "react-dropzone"; import { CloudUploadIcon, TimesIcon } from "@/components/icons"; import Image from "next/image"; import ReactSelect from "react-select"; import makeAnimated from "react-select/animated"; import GenerateSingleArticleForm from "./generate-ai-single-form"; import { htmlToString } from "@/utils/global"; import { close, error, loading } from "@/config/swal"; import { useParams, useRouter } from "next/navigation"; import GetSeoScore from "./get-seo-score-form"; import Link from "next/link"; import Cookies from "js-cookie"; import { createArticleSchedule, deleteArticleFiles, getArticleByCategory, getArticleById, getArticleFiles, submitApproval, unPublishArticle, updateArticle, uploadArticleFile, uploadArticleThumbnail, } from "@/service/article"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Textarea } from "@/components/ui/textarea"; import { Badge } from "@/components/ui/badge"; import { Mail, X } from "lucide-react"; import { format } from "date-fns"; import { Popover, PopoverTrigger, PopoverContent, } from "@/components/ui/popover"; import { Calendar } from "@/components/ui/calendar"; import { Dialog, DialogTrigger, DialogContent, DialogHeader, DialogFooter, DialogTitle, DialogClose, } from "@/components/ui/dialog"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import DatePicker from "react-datepicker"; import { Switch } from "@/components/ui/switch"; const ViewEditor = dynamic( () => { return import("@/components/editor/view-editor"); }, { ssr: false }, ); 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(), }); const createArticleSchema = z.object({ title: z.string().min(2, { message: "Judul harus diisi", }), customCreatorName: 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", }), source: z.enum(["internal", "external"]).optional(), }); interface DiseData { id: number; articleBody: string; title: string; metaTitle: string; description: string; metaDescription: string; mainKeyword: string; additionalKeywords: string; } export default function EditArticleForm(props: { isDetail: boolean }) { const { isDetail } = props; const params = useParams(); const id = params?.id; const username = Cookies.get("username"); const userId = Cookies.get("uie"); 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 [detailfiles, setDetailFiles] = useState([]); const [mainImage, setMainImage] = useState(0); const [thumbnail, setThumbnail] = useState(""); const [diseId, setDiseId] = useState(0); const [thumbnailImg, setThumbnailImg] = useState([]); const [selectedMainImage, setSelectedMainImage] = useState( null, ); const [thumbnailValidation, setThumbnailValidation] = useState(""); // const { isOpen, onOpen, onOpenChange } = useDisclosure(); const [isOpen, setIsOpen] = useState(false); const onOpen = () => setIsOpen(true); const onOpenChange = () => setIsOpen((prev) => !prev); const [approvalStatus, setApprovalStatus] = useState(2); const [approvalMessage, setApprovalMessage] = useState(""); const [detailData, setDetailData] = useState(); // const [startDateValue, setStartDateValue] = useState(null); // const [timeValue, setTimeValue] = useState("00:00"); const [status, setStatus] = useState<"publish" | "draft" | "scheduled">( "publish", ); const [isScheduled, setIsScheduled] = useState(false); const [startDateValue, setStartDateValue] = useState(); const [startTimeValue, setStartTimeValue] = useState(""); const [levelId, setLevelId] = useState(); useEffect(() => { const ulne = Cookies.get("ulne"); setLevelId(ulne); }, []); 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 }, setValue, getValues, watch, setError, clearErrors, } = useForm(formOptions); useEffect(() => { initState(); }, [listCategory]); async function initState() { loading(); try { // 1️⃣ Ambil ARTICLE const articleRes = await getArticleById(id); const articleData = articleRes.data?.data; if (!articleData) return; // ===== ARTICLE DATA ===== setDetailData(articleData); setValue("title", articleData.title); setValue("customCreatorName", articleData.customCreatorName); setValue("slug", articleData.slug); setValue("source", articleData.source); const cleanDescription = articleData.htmlDescription ? articleData.htmlDescription .replace(/\\"/g, '"') .replace(/\\n/g, "\n") .trim() : ""; setValue("description", cleanDescription); setValue("tags", articleData.tags ? articleData.tags.split(",") : []); setThumbnail(articleData.thumbnailUrl); setDiseId(articleData.aiArticleId); setupInitCategory(articleData.categories); const filesRes = await getArticleFiles(); const allFiles = filesRes.data?.data ?? []; const filteredFiles = allFiles.filter( (file: any) => file.articleId === articleData.id, ); setDetailFiles(filteredFiles); } catch (error) { console.error("Init state error:", error); } finally { close(); } } const setupInitCategory = (data: any) => { const temp: CategoryType[] = []; for (let i = 0; i < data?.length; i++) { const datas = listCategory.filter((a) => a.id == data[i].id); if (datas[0]) { temp.push(datas[0]); } } setValue("category", temp as [CategoryType, ...CategoryType[]]); }; 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) => { MySwal.fire({ title: "Simpan Data", text: "", icon: "warning", showCancelButton: true, cancelButtonColor: "#d33", confirmButtonColor: "#3085d6", confirmButtonText: "Simpan", }).then((result) => { if (result.isConfirmed) { save(values); } }); }; const doPublish = async () => { MySwal.fire({ title: isScheduled ? "Jadwalkan Publikasi?" : "Publish Artikel Sekarang?", text: isScheduled ? "Artikel akan dipublish otomatis sesuai tanggal dan waktu yang kamu pilih." : "", icon: "warning", showCancelButton: true, cancelButtonColor: "#d33", confirmButtonColor: "#3085d6", confirmButtonText: isScheduled ? "Jadwalkan" : "Publish", }).then((result) => { if (result.isConfirmed) { if (isScheduled) { setStatus("scheduled"); publishScheduled(); } else { publishNow(); } } }); }; const publishNow = async () => { const response = await updateArticle(String(id), { id: Number(id), isPublish: true, title: detailData?.title, typeId: 1, slug: detailData?.slug, categoryIds: getValues("category") .map((val) => val.id) .join(","), tags: getValues("tags").join(","), description: htmlToString(getValues("description")), htmlDescription: getValues("description"), }); if (response?.error) { error(response.message); return; } successSubmit("/admin/news-article/image"); }; const publishScheduled = async () => { if (!startDateValue) { error("Tanggal belum dipilih!"); return; } const [hours, minutes] = startTimeValue ? startTimeValue.split(":").map(Number) : [0, 0]; const combinedDate = new Date(startDateValue); combinedDate.setHours(hours, minutes, 0, 0); const formattedDateTime = `${combinedDate.getFullYear()}-${String( combinedDate.getMonth() + 1, ).padStart(2, "0")}-${String(combinedDate.getDate()).padStart( 2, "0", )} ${String(combinedDate.getHours()).padStart(2, "0")}:${String( combinedDate.getMinutes(), ).padStart(2, "0")}:00`; const response = await updateArticle(String(id), { id: Number(id), isPublish: false, title: detailData?.title, typeId: 1, slug: detailData?.slug, categoryIds: getValues("category") .map((val) => val.id) .join(","), tags: getValues("tags").join(","), description: htmlToString(getValues("description")), htmlDescription: getValues("description"), }); if (response?.error) { error(response.message); return; } const articleId = response?.data?.data?.id ?? id; const scheduleReq = { id: articleId, date: formattedDateTime, }; console.log("📅 Mengirim jadwal publish:", scheduleReq); const res = await createArticleSchedule(scheduleReq); if (res?.error) { error("Gagal membuat jadwal publikasi."); return; } successSubmit("/admin/news-article/image"); }; const save = async (values: z.infer) => { loading(); const formData: any = { id: Number(id), title: values.title, typeId: 1, slug: values.slug, categoryIds: values.category.map((val) => val.id).join(","), tags: values.tags.join(","), description: htmlToString(values.description), htmlDescription: values.description, // createdAt: `${startDateValue} ${timeValue}:00`, }; // if (startDateValue && timeValue) { // formData.createdAt = `${startDateValue} ${timeValue}:00`; // } const response = await updateArticle(String(id), formData); if (response?.error) { error(response.message); return false; } const articleId = response?.data?.data?.id; const formFiles = new FormData(); if (files?.length > 0) { for (const element of files) { formFiles.append("file", element); const resFile = await uploadArticleFile(String(id), formFiles); } } if (thumbnailImg?.length > 0) { const formFiles = new FormData(); formFiles.append("files", thumbnailImg[0]); const resFile = await uploadArticleThumbnail(String(id), formFiles); } if (status === "scheduled" && startDateValue) { // ambil waktu, default 00:00 jika belum diisi const [hours, minutes] = startTimeValue ? startTimeValue.split(":").map(Number) : [0, 0]; // gabungkan tanggal + waktu const combinedDate = new Date(startDateValue); combinedDate.setHours(hours, minutes, 0, 0); // format: 2025-10-08 14:30:00 const formattedDateTime = `${combinedDate.getFullYear()}-${String( combinedDate.getMonth() + 1, ).padStart(2, "0")}-${String(combinedDate.getDate()).padStart( 2, "0", )} ${String(combinedDate.getHours()).padStart(2, "0")}:${String( combinedDate.getMinutes(), ).padStart(2, "0")}:00`; const request = { id: articleId, date: formattedDateTime, }; console.log("📤 Sending schedule request:", request); const res = await createArticleSchedule(request); console.log("✅ Schedule response:", res); } close(); successSubmitData(); }; function successSubmit(redirect: string) { MySwal.fire({ title: "Sukses", icon: "success", confirmButtonColor: "#3085d6", confirmButtonText: "OK", }).then((result) => { if (result.isConfirmed) { router.push(redirect); } }); } function successSubmitData() { MySwal.fire({ title: "Berhasil disimpan!", icon: "success", confirmButtonColor: "#3085d6", confirmButtonText: "OK", }); } const doUnpublish = async () => { MySwal.fire({ title: "Unpublish Artikel?", text: "Artikel akan dihapus dari publik dan tidak tampil lagi.", icon: "warning", showCancelButton: true, cancelButtonColor: "#d33", confirmButtonColor: "#3085d6", confirmButtonText: "Ya, Unpublish", }).then(async (result) => { if (result.isConfirmed) { loading(); const response = await unPublishArticle(String(id), { id: Number(id), isPublish: false, title: detailData?.title, typeId: 1, slug: detailData?.slug, categoryIds: getValues("category") .map((val) => val.id) .join(","), tags: getValues("tags").join(","), description: htmlToString(getValues("description")), htmlDescription: getValues("description"), }); if (response?.error) { error(response.message); return; } successSubmit("/admin/news-article/image"); } }); }; 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) => (
{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"}
)); const handleDeleteFile = (id: number) => { MySwal.fire({ title: "Hapus File", text: "", icon: "warning", showCancelButton: true, cancelButtonColor: "#d33", confirmButtonColor: "#3085d6", confirmButtonText: "Hapus", }).then((result) => { if (result.isConfirmed) { deleteFile(id); } }); }; const deleteFile = async (id: number) => { loading(); const res = await deleteArticleFiles(id); if (res?.error) { error(res.message); return false; } close(); initState(); MySwal.fire({ title: "Sukses", icon: "success", confirmButtonColor: "#3085d6", confirmButtonText: "OK", }).then((result) => { if (result.isConfirmed) { } }); }; const handleFileChange = (event: React.ChangeEvent) => { const selectedFiles = event.target.files; if (selectedFiles) { setThumbnailImg(Array.from(selectedFiles)); } }; const approval = async () => { loading(); const req = { articleId: Number(id), message: approvalMessage, statusId: approvalStatus, }; const res = await submitApproval(req); if (res?.error) { error(res.message); return false; } close(); initState(); MySwal.fire({ title: "Sukses", icon: "success", confirmButtonColor: "#3085d6", confirmButtonText: "OK", }).then((result) => { if (result.isConfirmed) { } }); }; const doApproval = () => { MySwal.fire({ title: "Submit Data?", text: "", icon: "warning", showCancelButton: true, cancelButtonColor: "#d33", confirmButtonColor: "#3085d6", confirmButtonText: "Submit", }).then((result) => { if (result.isConfirmed) { approval(); } }); }; if (isDetail) { return (
{/* ================= LEFT SIDE ================= */}
{/* Title */}

Title

{detailData?.title}
{/* Category */}

Category

{detailData?.categories ?.map((cat: any) => cat.title) .join(", ")}
{/* Description */}

Description

{/* Media File */}

Media File

{detailfiles?.length > 0 ? ( <> media
{detailfiles.map((file: any, index: number) => ( preview ))}
) : (

Belum ada file

)}
{/* ================= RIGHT SIDE ================= */}
{/* Creator & Thumbnail Card */}
{/* Creator */}

Creator

{detailData?.customCreatorName || "-"}
{/* Thumbnail */}

Thumbnail Image

thumbnail
{/* Tag */}

Tag

{detailData?.tags ?.split(",") .map((tag: string, i: number) => ( {tag} ))}
{/* Notes */}

Notes

-

Suggestion Box (0)

Description :

{detailData?.isPublish === true ? ( Approved ) : ( Pending )}

Comment

View Approver History

{/* ================= ACTION BUTTON ================= */}
{levelId === "2" && !detailData?.isPublish && ( <> )} {/* 🔥 Jika levelId 3 → hanya tampilkan Cancel */} {levelId === "3" && ( )}
); } }