From 4d3aaab4e293f8b6c5612094cddc585762c93131 Mon Sep 17 00:00:00 2001 From: hanif salafi Date: Sun, 22 Jun 2025 09:01:48 +0700 Subject: [PATCH] feat : update spit convert and setting banner & popup --- .../admin/settings/banner/component/table.tsx | 4 +- .../admin/settings/popup/component/table.tsx | 4 +- components/form/content/spit-convert-form.tsx | 1892 ++++++++--------- 3 files changed, 945 insertions(+), 955 deletions(-) diff --git a/app/[locale]/(protected)/admin/settings/banner/component/table.tsx b/app/[locale]/(protected)/admin/settings/banner/component/table.tsx index 44d98ec7..8ce6c17f 100644 --- a/app/[locale]/(protected)/admin/settings/banner/component/table.tsx +++ b/app/[locale]/(protected)/admin/settings/banner/component/table.tsx @@ -74,7 +74,7 @@ import CustomPagination from "@/components/table/custom-pagination"; const ContentListBanner = () => { const router = useRouter(); const searchParams = useSearchParams(); - const [showData, setShowData] = React.useState("10"); + const [showData, setShowData] = React.useState("9"); const [categories, setCategories] = React.useState(); const [data, setData] = React.useState([]); const [totalData, setTotalData] = React.useState(1); @@ -389,7 +389,7 @@ const ContentListBanner = () => { /> {item.title} diff --git a/app/[locale]/(protected)/admin/settings/popup/component/table.tsx b/app/[locale]/(protected)/admin/settings/popup/component/table.tsx index 5361510a..b4563aa2 100644 --- a/app/[locale]/(protected)/admin/settings/popup/component/table.tsx +++ b/app/[locale]/(protected)/admin/settings/popup/component/table.tsx @@ -74,7 +74,7 @@ import CustomPagination from "@/components/table/custom-pagination"; const ContentListPopUp = () => { const router = useRouter(); const searchParams = useSearchParams(); - const [showData, setShowData] = React.useState("10"); + const [showData, setShowData] = React.useState("9"); const [categories, setCategories] = React.useState(); const [data, setData] = React.useState([]); const [totalData, setTotalData] = React.useState(1); @@ -389,7 +389,7 @@ const ContentListPopUp = () => { /> {item.title} diff --git a/components/form/content/spit-convert-form.tsx b/components/form/content/spit-convert-form.tsx index ecd0a7bb..51643adf 100644 --- a/components/form/content/spit-convert-form.tsx +++ b/components/form/content/spit-convert-form.tsx @@ -1,15 +1,22 @@ "use client"; import React, { ChangeEvent, useEffect, useRef, useState } from "react"; import { useForm, Controller } from "react-hook-form"; +import { zodResolver } from "@hookform/resolvers/zod"; +import * as z from "zod"; +import { useParams, useRouter } from "next/navigation"; +import { useTranslations } from "next-intl"; +import dynamic from "next/dynamic"; +import Cookies from "js-cookie"; + +// UI Components import { Input } from "@/components/ui/input"; import { Button } from "@/components/ui/button"; import { Label } from "@/components/ui/label"; -import { Card } from "@/components/ui/card"; -import { zodResolver } from "@hookform/resolvers/zod"; -import * as z from "zod"; -import Swal from "sweetalert2"; -import withReactContent from "sweetalert2-react-content"; -import { useParams, useRouter } from "next/navigation"; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { Badge } from "@/components/ui/badge"; +import { Alert, AlertDescription } from "@/components/ui/alert"; +import { Separator } from "@/components/ui/separator"; +import { ScrollArea } from "@/components/ui/scroll-area"; import { Select, SelectContent, @@ -19,50 +26,60 @@ import { } from "@/components/ui/select"; import { Checkbox } from "@/components/ui/checkbox"; import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"; -import JoditEditor from "jodit-react"; -import { register } from "module"; -import { Switch } from "@/components/ui/switch"; -import Cookies from "js-cookie"; -import { - convertSPIT, - createMedia, - deleteSPIT, - detailSPIT, - getTagsBySubCategoryId, - listCategory, - listEnableCategory, -} from "@/service/content/content"; -import { detailMedia } from "@/service/curated-content/curated-content"; -import { Badge } from "@/components/ui/badge"; -import { MailIcon } from "lucide-react"; + +// Icons +import { + AlertCircle, + FileText, + Image, + Loader2, + Save, + Trash2, + Edit3, + Globe, + Users, + Tag, + Eye, + Settings, + CheckCircle, + XCircle +} from "lucide-react"; + +// Swiper import { Swiper, SwiperSlide } from "swiper/react"; import "swiper/css"; import "swiper/css/free-mode"; import "swiper/css/navigation"; import "swiper/css/pagination"; import "swiper/css/thumbs"; -import "swiper/css"; -import "swiper/css/navigation"; import { FreeMode, Navigation, Pagination, Thumbs } from "swiper/modules"; -import { request } from "http"; + +// Services +import { + convertSPIT, + deleteSPIT, + detailSPIT, + getTagsBySubCategoryId, + listEnableCategory, +} from "@/service/content/content"; import { - generateDataArticle, generateDataRewrite, getDetailArticle, } from "@/service/content/ai"; -import { getCookiesDecrypt } from "@/lib/utils"; -import dynamic from "next/dynamic"; -import { error } from "@/lib/swal"; -import { useTranslations } from "next-intl"; -import { contextType } from "cleave.js/react"; -import { Form } from "@/components/ui/form"; -type Category = { +// Utils +import { getCookiesDecrypt } from "@/lib/utils"; +import { error } from "@/lib/swal"; +import Swal from "sweetalert2"; +import withReactContent from "sweetalert2-react-content"; + +// Types +interface Category { id: number; name: string; -}; +} -type Detail = { +interface Detail { id: string; contentTitle: string; contentDescription: string; @@ -75,17 +92,20 @@ type Detail = { creatorName: string; contentThumbnail: string; contentTag: string; -}; + categoryId?: number; + contentList?: FileType[]; +} -type Option = { +interface Option { id: string; label: string; -}; +} -interface FileData { +interface FileType { contentId: number; - placement?: string[]; - [key: string]: any; + contentFile: string; + thumbnailFileUrl: string; + fileName: string; } interface PlacementData { @@ -93,1023 +113,993 @@ interface PlacementData { placements: string; } -type FileType = { - contentId: number; - contentFile: string; - thumbnailFileUrl: string; - fileName: string; -}; +// Schema +const formSchema = z.object({ + contentTitle: z.string().min(1, { message: "Judul diperlukan" }), + contentDescription: z.string().optional(), + contentRewriteDescription: z.string().optional(), + contentCreator: z.string().min(1, { message: "Creator diperlukan" }), +}); +type FormData = z.infer; + +// Constants +const PUBLISH_OPTIONS: Option[] = [ + { id: "all", label: "SEMUA" }, + { id: "5", label: "UMUM" }, + { id: "6", label: "JOURNALIS" }, + { id: "7", label: "POLRI" }, + { id: "8", label: "KSP" }, +]; + +const WRITING_STYLES = [ + { value: "friendly", label: "Friendly" }, + { value: "professional", label: "Profesional" }, + { value: "informational", label: "Informational" }, + { value: "neutral", label: "Neutral" }, + { value: "witty", label: "Witty" }, +]; + +const PLACEMENT_OPTIONS = [ + { value: "all", label: "Semua" }, + { value: "mabes", label: "Nasional" }, + { value: "polda", label: "Wilayah" }, + { value: "international", label: "Internasional" }, +]; + +// Dynamic imports const CustomEditor = dynamic( - () => { - return import("@/components/editor/custom-editor"); - }, - { ssr: false } + () => import("@/components/editor/custom-editor"), + { + ssr: false, + loading: () => ( +
+ + Loading editor... +
+ ) + } ); export default function FormConvertSPIT() { const MySwal = withReactContent(Swal); const router = useRouter(); - - const { id } = useParams() as { id: string }; - console.log(id); - const editor = useRef(null); - type ImageSchema = z.infer; - - const [selectedFiles, setSelectedFiles] = useState([]); - const taskId = Cookies.get("taskId"); - const scheduleId = Cookies.get("scheduleId"); - const scheduleType = Cookies.get("scheduleType"); - - const [categories, setCategories] = useState([]); - const [selectedCategoryId, setSelectedCategoryId] = useState( - null - ); - const [detail, setDetail] = useState(); - const [refresh, setRefresh] = useState(false); - const [selectedPublishers, setSelectedPublishers] = useState([]); - const [detailThumb, setDetailThumb] = useState([]); - const [thumbsSwiper, setThumbsSwiper] = useState(null); - const [selectedAdvConfig, setSelectedAdvConfig] = useState(""); - const [title, setTitle] = useState(""); - const [articleIds, setArticleIds] = useState([]); - const [isGeneratedArticle, setIsGeneratedArticle] = useState(false); - const [articleBody, setArticleBody] = useState(""); - const [selectedArticleId, setSelectedArticleId] = useState( - null - ); - const [isContentRewriteClicked, setIsContentRewriteClicked] = useState(false); const t = useTranslations("Form"); - const [detailData, setDetailData] = useState(null); - const [selectedFileType, setSelectedFileType] = useState("original"); - const [isLoadingData, setIsLoadingData] = useState(false); - const userLevelId = getCookiesDecrypt("ulie"); - const userLevelNumber = getCookiesDecrypt("ulne"); - const roleId = getCookiesDecrypt("urie"); - const [isMabesApprover, setIsMabesApprover] = useState(false); - const [selectedTarget, setSelectedTarget] = useState(""); - const [unitSelection, setUnitSelection] = useState({ - allUnit: false, - mabes: false, - polda: false, - polres: false, - }); - const [publishedFor, setPublishedFor] = useState([]); - const [filePlacements, setFilePlacements] = useState([]); - const [isUserMabesApprover, setIsUserMabesApprover] = useState(false); - const [files, setFiles] = useState([]); - const [tags, setTags] = useState([]); - const inputRef = useRef(null); - const [selectedWritingStyle, setSelectedWritingStyle] = - useState("Professional"); - - const imageSchema = z.object({ - contentTitle: z.string().min(1, { message: "Judul diperlukan" }), - contentDescription: z.string().optional(), - contentRewriteDescription: z.string().optional(), - contentCreator: z.string().min(1, { message: "Creator diperlukan" }), - }); - - const options: Option[] = [ - { id: "all", label: "SEMUA" }, - { id: "5", label: "UMUM" }, - { id: "6", label: "JOURNALIS" }, - { id: "7", label: "POLRI" }, - { id: "8", label: "KSP" }, - ]; - - let fileTypeId = "1"; + const { id } = useParams() as { id: string }; + // Form state const { control, handleSubmit, setValue, - formState: { errors }, - } = useForm({ - resolver: zodResolver(imageSchema), + formState: { errors, isSubmitting }, + watch, + } = useForm({ + resolver: zodResolver(formSchema), defaultValues: { - contentTitle: detail?.contentTitle || "", - contentDescription: detail?.contentDescription || "", - contentCreator: detail?.contentCreator || "", - contentRewriteDescription: detail?.contentRewriteDescription || "", - // dll + contentTitle: "", + contentDescription: "", + contentCreator: "", + contentRewriteDescription: "", }, }); - // const form = useForm>({ - // resolver: zodResolver(imageSchema), - // defaultValues: { - // contentTitle: detail?.contentTitle || "", - // contentDescription: detail?.contentDescription || "", - // contentCreator: detail?.contentCreator || "", + // Component state + const [isLoading, setIsLoading] = useState(true); + const [isSaving, setIsSaving] = useState(false); + const [isDeleting, setIsDeleting] = useState(false); + const [detail, setDetail] = useState(null); + const [categories, setCategories] = useState([]); + const [selectedCategoryId, setSelectedCategoryId] = useState(null); + const [selectedFileType, setSelectedFileType] = useState<"original" | "rewrite">("original"); + const [selectedWritingStyle, setSelectedWritingStyle] = useState("professional"); + const [showRewriteEditor, setShowRewriteEditor] = useState(false); + const [isGeneratingRewrite, setIsGeneratingRewrite] = useState(false); + const [isLoadingRewrite, setIsLoadingRewrite] = useState(false); + + // Media state + const [detailThumb, setDetailThumb] = useState([]); + const [thumbsSwiper, setThumbsSwiper] = useState(null); + const [files, setFiles] = useState([]); + const [filePlacements, setFilePlacements] = useState([]); + + // Content rewrite state + const [articleIds, setArticleIds] = useState([]); + const [selectedArticleId, setSelectedArticleId] = useState(null); + const [articleBody, setArticleBody] = useState(""); + + // Form data state + const [tags, setTags] = useState([]); + const [publishedFor, setPublishedFor] = useState([]); + const [inputRef] = useState(useRef(null)); - // // dll - // }, - // }); + // User permissions + const userLevelId = getCookiesDecrypt("ulie"); + const roleId = getCookiesDecrypt("urie"); + const [isUserMabesApprover, setIsUserMabesApprover] = useState(false); + // Initialize component useEffect(() => { - if (articleBody) { - setValue("contentRewriteDescription", articleBody); - } - }, [articleBody, setValue]); - - const handleRemoveTag = (index: any) => { - setTags((prevTags) => prevTags.filter((_, i) => i !== index)); - }; - - const handleImageChange = (event: ChangeEvent) => { - if (event.target.files) { - const files = Array.from(event.target.files); - setSelectedFiles((prevImages: any) => [...prevImages, ...files]); - console.log("DATAFILE::", selectedFiles); - } - }; - - const handleRemoveImage = (index: number) => { - setSelectedFiles((prevImages) => prevImages.filter((_, i) => i !== index)); - }; - - // const handleDirectSave = () => { - // const values = form.getValues(); - // onSubmit(values); - // }; - - useEffect(() => { - async function initState() { - getCategories(); - } - - initState(); + initializeComponent(); }, []); useEffect(() => { - if ( - userLevelId != undefined && - roleId != undefined && - userLevelId == "216" && - roleId == "3" - ) { + checkUserPermissions(); + }, [userLevelId, roleId]); + + const initializeComponent = async () => { + try { + setIsLoading(true); + await Promise.all([ + loadCategories(), + id ? loadDetail() : Promise.resolve(), + ]); + } catch (error) { + console.error("Failed to initialize component:", error); + MySwal.fire({ + title: "Error", + text: "Failed to load data. Please try again.", + icon: "error", + confirmButtonColor: "#3085d6", + }); + } finally { + setIsLoading(false); + } + }; + + const checkUserPermissions = () => { + if (userLevelId === "216" && roleId === "3") { setIsUserMabesApprover(true); } - }, [userLevelId, roleId]); + }; - useEffect(() => { - if ( - userLevelId != undefined && - roleId != undefined && - userLevelId == "216" && - roleId == "3" - ) { - setIsMabesApprover(true); - } - }, [userLevelId, roleId]); - - const getCategories = async () => { + const loadCategories = async () => { try { - const category = await listEnableCategory(fileTypeId); - const resCategory: Category[] = category?.data?.data?.content; - - setCategories(resCategory); - console.log("data category", resCategory); + const response = await listEnableCategory("1"); + const categories = response?.data?.data?.content || []; + setCategories(categories); + // Auto-select "Pers Rilis" category if schedule type is 3 + const scheduleId = Cookies.get("scheduleId"); + const scheduleType = Cookies.get("scheduleType"); + if (scheduleId && scheduleType === "3") { - const findCategory = resCategory.find((o) => - o.name.toLowerCase().includes("pers rilis") + const persRilisCategory = categories.find((cat: Category) => + cat.name.toLowerCase().includes("pers rilis") ); - - if (findCategory) { - setSelectedCategoryId(findCategory.id); - const response = await getTagsBySubCategoryId(findCategory.id); - setTags(response?.data?.data); + if (persRilisCategory) { + setSelectedCategoryId(persRilisCategory.id); + await loadTags(persRilisCategory.id); } } } catch (error) { - console.error("Failed to fetch categories:", error); + console.error("Failed to load categories:", error); } }; - const setupPlacement = ( - index: number, - placement: string, - checked: boolean - ) => { - let temp = [...filePlacements]; - if (checked) { - if (placement === "all") { - temp[index] = ["all", "mabes", "polda", "international"]; - } else { - const now = temp[index]; - now.push(placement); - if (now.length === 3 && !now.includes("all")) { - now.push("all"); - } - temp[index] = now; - } - } else { - if (placement === "all") { - temp[index] = []; - } else { - const now = temp[index].filter((a) => a !== placement); - console.log("now", now); - temp[index] = now; - if (now.length === 3 && now.includes("all")) { - const newData = now.filter((b) => b !== "all"); - temp[index] = newData; - } - } - } - setFilePlacements(temp); - }; - - const setupPlacementCheck = (length: number) => { - const temp = []; - for (let i = 0; i < length; i++) { - temp.push([]); - } - setFilePlacements(temp); - }; - - useEffect(() => { - async function initState() { - if (id) { - const response = await detailSPIT(id); - const details = response?.data?.data; - - setDetail(details); - setFiles(details?.contentList); - setupPlacementCheck(details?.contentList?.length); - setValue("contentTitle", details?.contentTitle || ""); - setValue("contentDescription", details?.contentDescription || ""); - setValue("contentCreator", details?.contentCreator || ""); - setValue( - "contentRewriteDescription", - details?.contentRewriteDescription || "" - ); - - const filesData = details.contentList || []; - const fileUrls = filesData.map((file: { contentFile: string }) => - file.contentFile ? file.contentFile : "default-image.jpg" - ); - setDetailThumb(fileUrls); - - const matchingCategory = categories.find( - (category) => category.id === details.categoryId - ); - - if (matchingCategory) { - setSelectedTarget(matchingCategory.name); - } - - if (details?.contentTag) { - const initialTags = details.contentTag - .split(",") - .map((tag: string) => tag.trim()); - setTags(initialTags); - } - - // setSelectedTarget(details.categoryId); // Untuk dropdown - } - } - initState(); - }, [refresh]); - - const [tempFile, setTempFile] = useState( - detailThumb.map((data: any) => ({ - contentId: data.id, - placement: [], - })) - ); - - const handleCheckboxChangeFile = (contentId: number, value: string) => { - setTempFile((prevTempFile: any) => { - return prevTempFile.map((file: any) => { - if (file.contentId === contentId) { - const isChecked = file.placement?.includes(value); - return { - ...file, - placement: isChecked - ? file.placement.filter((v: any) => v !== value) - : [...(file.placement || []), value], - }; - } - return file; - }); - }); - }; - - const handleCheckboxChange = (id: string): void => { - if (id === "all") { - if (publishedFor.includes("all")) { - setPublishedFor([]); - } else { - setPublishedFor( - options - .filter((opt: any) => opt.id !== "all") - .map((opt: any) => opt.id) - ); - } - } else { - const updatedPublishedFor = publishedFor.includes(id) - ? publishedFor.filter((item) => item !== id) - : [...publishedFor, id]; - - if (publishedFor.includes("all") && id !== "all") { - setPublishedFor(updatedPublishedFor.filter((item) => item !== "all")); - } else { - setPublishedFor(updatedPublishedFor); + const loadTags = async (categoryId: number) => { + try { + const response = await getTagsBySubCategoryId(categoryId); + if (response?.data?.data?.length > 0) { + const tagsMerge = [...tags, response?.data?.data] + setTags(tagsMerge); } + } catch (error) { + console.error("Failed to load tags:", error); } }; - const getPlacement = () => { - console.log("getPlaa", filePlacements); - const temp = []; - for (let i = 0; i < filePlacements?.length; i++) { - if (filePlacements[i].length !== 0) { - const now = filePlacements[i].filter((a) => a !== "all"); - const data = { - mediaFileId: files[i].contentId, - placement: now.join(","), - }; - temp.push(data); + const loadDetail = async () => { + try { + const response = await detailSPIT(id); + const details = response?.data?.data; + + if (!details) { + throw new Error("Detail not found"); } - } - return temp; - }; - // const setupPlacement = ( - // index: number, - // category: string, - // isChecked: boolean - // ) => { - // setFilePlacements((prev) => - // prev.map((placement, i) => - // i === index - // ? isChecked - // ? [...new Set([...placement, category])] // Tambahkan kategori jika belum ada - // : placement.filter((item) => item !== category) // Hapus kategori jika ada - // : placement - // ) - // ); - // }; + setDetail(details); + setFiles(details.contentList || []); + setDetailThumb( + (details.contentList || []).map((file: FileType) => + file.contentFile || "default-image.jpg" + ) + ); + + // Initialize file placements + const fileCount = details.contentList?.length || 0; + setFilePlacements(Array(fileCount).fill([])); - const handleSelectAll = (category: string, isChecked: boolean) => { - setFilePlacements((prev: string[][]) => - prev.map((placement: string[]) => - isChecked - ? Array.from(new Set([...placement, category])) - : placement.filter((item: string) => item !== category) - ) - ); - }; + // Set form values + setValue("contentTitle", details.contentTitle || ""); + setValue("contentDescription", details.contentDescription || ""); + setValue("contentCreator", details.contentCreator || ""); + setValue("contentRewriteDescription", details.contentRewriteDescription || ""); - const save = async (data: ImageSchema) => { - const temp = []; - for (const element of detail.contentList) { - temp.push([]); - } - const description = - selectedFileType === "original" - ? data.contentDescription - : data.contentRewriteDescription; - const finalTags = tags.join(", "); - - const requestData = { - spitId: id, - title: data.contentTitle, - description, - htmlDescription: description, - tags: finalTags, - categoryId: selectedCategoryId, - publishedFor: publishedFor.join(","), - creator: data.contentCreator, - files: isUserMabesApprover ? getPlacement() : [], - }; - - const response = await convertSPIT(requestData); - console.log("Form Data Submitted:", response); - setFilePlacements(temp); - setFiles(detail.files); - MySwal.fire({ - title: "Sukses", - text: "Data berhasil disimpan.", - icon: "success", - confirmButtonColor: "#3085d6", - confirmButtonText: "OK", - }).then(() => { - router.push("/en/contributor/content/spit"); - }); - }; - - const onSubmit = (data: ImageSchema) => { - MySwal.fire({ - title: "Simpan Data", - text: "Apakah Anda yakin ingin menyimpan data ini?", - icon: "warning", - showCancelButton: true, - cancelButtonColor: "#d33", - confirmButtonColor: "#3085d6", - confirmButtonText: "Simpan", - }).then((result) => { - if (result.isConfirmed) { - save(data); + // Set category + if (details.categoryId) { + setSelectedCategoryId(details.categoryId); + await loadTags(details.categoryId); } - }); + + // Set tags + if (details.contentTag) { + const initialTags = details.contentTag.split(",").map((tag: string) => tag.trim()); + setTags(initialTags); + } + } catch (error) { + console.error("Failed to load detail:", error); + throw error; + } }; - const [showRewriteEditor, setShowRewriteEditor] = useState(false); - - // const handleRewriteClick = () => { - // setShowRewriteEditor(true); - // }; + // Event handlers + const handleCategoryChange = async (categoryId: string) => { + const id = Number(categoryId); + setSelectedCategoryId(id); + await loadTags(id); + }; const handleRewriteClick = async () => { - setIsContentRewriteClicked(true); - const request = { - style: selectedWritingStyle, - lang: "id", - contextType: "text", - urlContext: null, - context: detail?.contentDescription, - createdBy: roleId, - sentiment: "Humorous", - clientId: "7QTW8cMojyayt6qnhqTOeJaBI70W4EaQ", - }; - - const res = await generateDataRewrite(request); - close(); - - if (res?.error) { - console.error(res.message); - return false; + if (!detail?.contentDescription) { + MySwal.fire({ + title: "Warning", + text: "Please add content description first", + icon: "warning", + confirmButtonColor: "#3085d6", + }); + return; } - const newArticleId = res?.data?.data?.id; - setIsGeneratedArticle(true); - - setArticleIds((prevIds: string[]) => { - if (prevIds.length < 3) { - return [...prevIds, newArticleId]; - } else { - const updatedIds = [...prevIds]; - updatedIds[2] = newArticleId; - return updatedIds; - } - }); - - Cookies.set("nulisAIArticleIdTemp", JSON.stringify(articleIds)); - setShowRewriteEditor(true); - }; - - const handleArticleIdClick = async (id: string) => { - setIsLoadingData(true); - let retryCount = 0; - const maxRetries = 20; - try { - const waitForStatusUpdate = async () => { - while (retryCount < maxRetries) { - const res = await getDetailArticle(id); - const articleData = res?.data?.data; - - if (articleData?.status === 2) { - return articleData; - } - - retryCount++; - await new Promise((resolve) => setTimeout(resolve, 5000)); - } - - throw new Error("Timeout: Artikel belum selesai diproses."); + setIsGeneratingRewrite(true); + const request = { + style: selectedWritingStyle, + lang: "id", + contextType: "text", + urlContext: null, + context: detail.contentDescription, + createdBy: roleId, + sentiment: "Humorous", + clientId: "7QTW8cMojyayt6qnhqTOeJaBI70W4EaQ", }; - const articleData = await waitForStatusUpdate(); - const cleanArticleBody = articleData?.articleBody?.replace( - /]*>/g, - "" - ); - const articleImagesData = articleData?.imagesUrl?.split(","); - setArticleBody(cleanArticleBody || ""); - setDetailData(articleData); - setSelectedArticleId(id); - // setArticleImages(articleImagesData || []); + const response = await generateDataRewrite(request); + + if (response?.error) { + throw new Error(response.message); + } + + const newArticleId = response?.data?.data?.id; + setArticleIds(prev => { + const updated = [...prev]; + if (updated.length < 3) { + updated.push(newArticleId); + } else { + updated[2] = newArticleId; + } + return updated; + }); + + setShowRewriteEditor(true); + MySwal.fire({ + title: "Success", + text: "Content rewrite generated successfully", + icon: "success", + confirmButtonColor: "#3085d6", + }); } catch (error) { - console.error("Error fetching article details:", error); + console.error("Failed to generate rewrite:", error); + MySwal.fire({ + title: "Error", + text: "Failed to generate content rewrite", + icon: "error", + confirmButtonColor: "#3085d6", + }); } finally { - setIsLoadingData(false); + setIsGeneratingRewrite(false); } }; - function deleteSpitContent() { - MySwal.fire({ - title: "Apakah anda ingin menghapus konten?", - showCancelButton: true, - confirmButtonColor: "#dc3545", - confirmButtonText: "Iya", - cancelButtonText: "Tidak", - }).then((result) => { - if (result.isConfirmed) { - doDeleteSPIT(); - } - }); - } + const handleArticleSelect = async (articleId: string) => { + try { + setIsLoadingRewrite(true); + setSelectedArticleId(articleId); + + let retryCount = 0; + const maxRetries = 20; + + while (retryCount < maxRetries) { + const response = await getDetailArticle(articleId); + const articleData = response?.data?.data; - async function doDeleteSPIT() { - const response = await deleteSPIT(id); - if (response?.error) { - error(response.message); - return false; - } - - successBack(); - } - - function successBack() { - MySwal?.fire({ - title: "Sukses", - icon: "success", - confirmButtonColor: "#3085d6", - confirmButtonText: "OK", - }).then((result) => { - if (result.isConfirmed) { - if (window.history.state && window.history.state.idx > 0) { - console.log("backkkkk"); - console.log(window.history.state); - router.back(); - } else { - router.push("/in/contributor/content/spit"); + if (articleData?.status === 2) { + const cleanArticleBody = articleData.articleBody?.replace(/]*>/g, ""); + setArticleBody(cleanArticleBody || ""); + setValue("contentRewriteDescription", cleanArticleBody || ""); + break; } + + retryCount++; + await new Promise(resolve => setTimeout(resolve, 5000)); } - }); - } + + if (retryCount >= maxRetries) { + throw new Error("Timeout: Article processing took too long"); + } + } catch (error) { + console.error("Failed to load article:", error); + MySwal.fire({ + title: "Error", + text: "Failed to load article content", + icon: "error", + confirmButtonColor: "#3085d6", + }); + } finally { + setIsLoadingRewrite(false); + } + }; const handleAddTag = (e: React.KeyboardEvent) => { if (e.key === "Enter" && inputRef.current?.value.trim()) { e.preventDefault(); const newTag = inputRef.current.value.trim(); - + if (!tags.includes(newTag)) { - setTags((prevTags) => [...prevTags, newTag]); + setTags(prev => [...prev, newTag]); } - + inputRef.current.value = ""; } }; + const handleRemoveTag = (index: number) => { + setTags(prev => prev.filter((_, i) => i !== index)); + }; + + const handlePublishTargetChange = (optionId: string) => { + if (optionId === "all") { + setPublishedFor(prev => + prev.length === PUBLISH_OPTIONS.filter(opt => opt.id !== "all").length + ? [] + : PUBLISH_OPTIONS.filter(opt => opt.id !== "all").map(opt => opt.id) + ); + } else { + setPublishedFor(prev => + prev.includes(optionId) + ? prev.filter(id => id !== optionId && id !== "all") + : [...prev.filter(id => id !== "all"), optionId] + ); + } + }; + + const handleFilePlacementChange = (fileIndex: number, placement: string, checked: boolean) => { + setFilePlacements(prev => { + const updated = [...prev]; + const currentPlacements = updated[fileIndex] || []; + + if (checked) { + if (placement === "all") { + updated[fileIndex] = ["all", "mabes", "polda", "international"]; + } else { + const newPlacements = [...currentPlacements, placement]; + if (newPlacements.length === 3 && !newPlacements.includes("all")) { + newPlacements.push("all"); + } + updated[fileIndex] = newPlacements; + } + } else { + if (placement === "all") { + updated[fileIndex] = []; + } else { + const newPlacements = currentPlacements.filter(p => p !== placement); + if (newPlacements.length === 3 && newPlacements.includes("all")) { + updated[fileIndex] = newPlacements.filter(p => p !== "all"); + } else { + updated[fileIndex] = newPlacements; + } + } + } + + return updated; + }); + }; + + const handleSelectAllPlacements = (placement: string, checked: boolean) => { + setFilePlacements(prev => + prev.map(filePlacements => + checked + ? Array.from(new Set([...filePlacements, placement])) + : filePlacements.filter(p => p !== placement) + ) + ); + }; + + const getPlacementData = () => { + const placementData = []; + console.log("filePlacements : ", filePlacements); + for (let i = 0; i < filePlacements.length; i++) { + if (filePlacements[i].length > 0) { + const placements = filePlacements[i]; + placementData.push({ + mediaFileId: files[i].contentId, + placements: placements.join(","), + }); + } + } + return placementData; + }; + + // Form submission + const onSubmit = async (data: FormData) => { + try { + setIsSaving(true); + + const description = selectedFileType === "original" + ? data.contentDescription + : data.contentRewriteDescription; + + const requestData = { + spitId: id, + title: data.contentTitle, + description, + htmlDescription: description, + tags: tags.join(", "), + categoryId: selectedCategoryId, + publishedFor: publishedFor.join(","), + creator: data.contentCreator, + files: isUserMabesApprover ? getPlacementData() : [], + }; + + await convertSPIT(requestData); + + MySwal.fire({ + title: "Success", + text: "Data saved successfully", + icon: "success", + confirmButtonColor: "#3085d6", + }).then(() => { + router.push("/in/contributor/content/spit"); + }); + } catch (error) { + console.error("Failed to save:", error); + MySwal.fire({ + title: "Error", + text: "Failed to save data", + icon: "error", + confirmButtonColor: "#3085d6", + }); + } finally { + setIsSaving(false); + } + }; + + const handleDelete = async () => { + const result = await MySwal.fire({ + title: "Delete Content", + text: "Are you sure you want to delete this content?", + icon: "warning", + showCancelButton: true, + confirmButtonColor: "#dc3545", + cancelButtonColor: "#6c757d", + confirmButtonText: "Delete", + cancelButtonText: "Cancel", + }); + + if (result.isConfirmed) { + try { + setIsDeleting(true); + await deleteSPIT(id); + + MySwal.fire({ + title: "Success", + text: "Content deleted successfully", + icon: "success", + confirmButtonColor: "#3085d6", + }).then(() => { + router.back(); + }); + } catch (error) { + console.error("Failed to delete:", error); + MySwal.fire({ + title: "Error", + text: "Failed to delete content", + icon: "error", + confirmButtonColor: "#3085d6", + }); + } finally { + setIsDeleting(false); + } + } + }; + + // Loading state + if (isLoading) { + return ( +
+
+ +

Loading form data...

+
+
+ ); + } + return ( - <> -
- {detail !== undefined ? ( -
- -
-

{t("form-spit")}

-
- {/* Input Title */} -
- +
+ {/* Header */} +
+
+

SPIT Convert Form

+

+ Convert and manage your SPIT content +

+
+
+ + +
+
+ + +
+ {/* Main Content */} +
+ {/* Basic Information */} + + + + + Basic Information + + + + {/* Title */} +
+ + ( + + )} + /> + {errors.contentTitle && ( + + + + {errors.contentTitle.message} + + + )} +
+ + {/* Category */} +
+ + +
+
+
+ + {/* Content Editor */} + + + + + Content Editor + + + + setSelectedFileType(value)} + className="grid grid-cols-2 gap-4" + > +
+ + +
+
+ + +
+
+ + {/* Original Content */} + {selectedFileType === "original" && ( +
+ ( - )} />
-
-
- - -
-
-
- setSelectedFileType(value)} - value={selectedFileType} - className=" grid-cols-1" - > -
- - -
-
- - ( - - )} - /> -
-

Content Rewrite

-
- + )} + + {/* Content Rewrite */} + {selectedFileType === "rewrite" && ( +
+
+
+
-
- -
- {showRewriteEditor && ( -
- {isGeneratedArticle && ( -
- {articleIds.map((id: string, index: number) => ( - - ))} -
- )} -
- - -
-
- - - isLoadingData ? ( -
-

- Loading Proses Data... -

-
- ) : ( - - ) - } - /> -
-
- )} - -
-
- -
- - {detailThumb?.map((data: any) => ( - - {` - - ))} - -
- - {detailThumb?.map((data: any) => ( - - {` - - ))} - -
+ {isGeneratingRewrite ? ( + + ) : ( + + )} + Generate Rewrite +
-
-
- -
- {files?.length > 1 && ( -
-
- - handleSelectAll("all", Boolean(e)) - } - /> - -
-
- - handleSelectAll("mabes", Boolean(e)) - } - /> - -
-
- - handleSelectAll("polda", Boolean(e)) - } - /> - -
-
- - handleSelectAll("international", Boolean(e)) - } - /> - -
-
- )} - {files?.map((file, index) => ( -
- -
-
- {file.fileName} -
-
-
- - setupPlacement(index, "all", Boolean(e)) - } - /> - -
-
- - setupPlacement(index, "mabes", Boolean(e)) - } - /> - -
-
- - setupPlacement(index, "polda", Boolean(e)) - } - /> - -
-
- - setupPlacement( - index, - "international", - Boolean(e) - ) - } - /> - + {showRewriteEditor && ( +
+ {articleIds.length > 0 && ( +
+ {articleIds.map((articleId, index) => ( + + ))}
+ )} + +
+ + ( + + )} + />
-
- ))} -
-
- -
- -
-
- - ( - - )} - /> + )}
-
-
- - - Thumbnail Gambar Utama - -
-
-
- - -
- {tags.map((tag, index) => ( - - {tag} - + )} + + + + {/* Media Files */} + {detailThumb.length > 0 && ( + + + + + Media Files + + + +
+ + {detailThumb.map((image, index) => ( + + {`Media + + ))} + + + + {detailThumb.map((image, index) => ( + + {`Thumbnail + + ))} + +
+
+
+ )} + + {/* File Placement */} + {files.length > 0 && isUserMabesApprover && ( + + + + + File Placement + + + + {files.length > 1 && ( +
+ {PLACEMENT_OPTIONS.map((option) => ( +
+ + handleSelectAllPlacements(option.value, Boolean(checked)) + } + /> + +
))}
-
-
-
-
- - {options.map((option) => ( -
- opt.id !== "all") - .length - : publishedFor.includes(option.id) - } - onCheckedChange={() => - handleCheckboxChange(option.id) - } + )} + +
+ {files.map((file, index) => ( +
+ {file.fileName} - +
+

{file.fileName}

+
+ {PLACEMENT_OPTIONS.map((option) => ( +
+ + handleFilePlacementChange(index, option.value, Boolean(checked)) + } + /> + +
+ ))} +
+
))}
-
+ -
-
- -
-
- -
-
-
+ )}
- ) : ( - "" - )} + + {/* Sidebar */} +
+ {/* Creator Information */} + + + + + Creator Information + + + +
+ + ( + + )} + /> + {errors.contentCreator && ( + + + + {errors.contentCreator.message} + + + )} +
+
+
+ + {/* Preview */} + {detail?.contentThumbnail && ( + + + + + Preview + + + + Content thumbnail + + + )} + + {/* Tags */} + + + + + Tags + + + +
+ + +
+ {tags.length > 0 && ( +
+ {tags.map((tag, index) => ( + handleRemoveTag(index)} + > + {tag} + + + ))} +
+ )} +
+
+ + {/* Publish Targets */} + + + + + Publish Targets + + + + {PUBLISH_OPTIONS.map((option) => ( +
+ opt.id !== "all").length + : publishedFor.includes(option.id) + } + onCheckedChange={() => handlePublishTargetChange(option.id)} + /> + +
+ ))} +
+
+ + {/* Submit Button */} + + + + + +
+
- +
); }