"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, 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, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { Checkbox } from "@/components/ui/checkbox"; import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"; // 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 { FreeMode, Navigation, Pagination, Thumbs } from "swiper/modules"; // Services import { convertSPIT, deleteSPIT, detailSPIT, getTagsBySubCategoryId, listEnableCategory, } from "@/service/content/content"; import { generateDataRewrite, getDetailArticle } from "@/service/content/ai"; // 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; } interface Detail { id: string; contentTitle: string; contentDescription: string; slug: string; content: { id: number; name: string; }; contentCreator: string; creatorName: string; contentThumbnail: string; contentTag: string; categoryId?: number; contentList?: FileType[]; } interface Option { id: string; label: string; } interface FileType { contentId: number; contentFile: string; thumbnailFileUrl: string; fileName: string; } interface PlacementData { mediaFileId: number; placements: 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( () => import("@/components/editor/custom-editor"), { ssr: false, loading: () => (
Loading editor...
), } ); export default function FormConvertSPIT() { const MySwal = withReactContent(Swal); const router = useRouter(); const t = useTranslations("Form"); const { id } = useParams() as { id: string }; // Form state const { control, handleSubmit, setValue, formState: { errors, isSubmitting }, watch, } = useForm({ resolver: zodResolver(formSchema), defaultValues: { contentTitle: "", contentDescription: "", contentCreator: "", contentRewriteDescription: "", }, }); // 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)); // User permissions const userLevelId = getCookiesDecrypt("ulie"); const roleId = getCookiesDecrypt("urie"); const [isUserMabesApprover, setIsUserMabesApprover] = useState(false); // Initialize component useEffect(() => { initializeComponent(); }, []); useEffect(() => { 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); } }; const loadCategories = async () => { try { 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 persRilisCategory = categories.find((cat: Category) => cat.name.toLowerCase().includes("pers rilis") ); if (persRilisCategory) { setSelectedCategoryId(persRilisCategory.id); await loadTags(persRilisCategory.id); } } } catch (error) { console.error("Failed to load categories:", error); } }; const loadTags = async (categoryId: number) => { try { const response = await getTagsBySubCategoryId(categoryId); if (response?.data?.data?.length > 0) { // Extract tag names from the response objects const apiTags = response?.data?.data?.map((tag: any) => tag.tagName); // Merge with existing tags, ensuring uniqueness const tagsMerge = Array.from(new Set([...tags, ...apiTags])); setTags(tagsMerge); } } catch (error) { console.error("Failed to load tags:", error); } }; const loadDetail = async () => { try { const response = await detailSPIT(id); const details = response?.data?.data; if (!details) { throw new Error("Detail not found"); } 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([])); // Set form values setValue("contentTitle", details.contentTitle || ""); setValue("contentDescription", details.contentDescription || ""); setValue("contentCreator", details.contentCreator || ""); setValue( "contentRewriteDescription", details.contentRewriteDescription || "" ); // Set category and load category tags if (details.categoryId) { setSelectedCategoryId(details.categoryId); await loadTags(details.categoryId); } // Set content tags and merge with category tags if (details.contentTag) { const contentTags = details.contentTag.split(",").map((tag: string) => tag.trim()); setTags(prev => Array.from(new Set([...prev, ...contentTags]))); } } catch (error) { console.error("Failed to load detail:", error); throw error; } }; // Event handlers const handleCategoryChange = async (categoryId: string) => { const id = Number(categoryId); setSelectedCategoryId(id); await loadTags(id); }; const handleRewriteClick = async () => { if (!detail?.contentDescription) { MySwal.fire({ title: "Warning", text: "Please add content description first", icon: "warning", confirmButtonColor: "#3085d6", }); return; } try { setIsGeneratingRewrite(true); const request = { style: selectedWritingStyle, lang: "id", contextType: "text", urlContext: null, context: detail.contentDescription, createdBy: roleId, sentiment: "Humorous", clientId: "7QTW8cMojyayt6qnhqTOeJaBI70W4EaQ", }; 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("Failed to generate rewrite:", error); MySwal.fire({ title: "Error", text: "Failed to generate content rewrite", icon: "error", confirmButtonColor: "#3085d6", }); } finally { setIsGeneratingRewrite(false); } }; 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; 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((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; }; const checkPlacement = (data: any) => { let temp = true; for (const element of data) { if (element.length < 1) { temp = false; break; } } return temp; }; // Form submission const onSubmit = async (data: FormData) => { if (!checkPlacement(filePlacements)) { error("Select File Placement"); return false; } if (!selectedCategoryId) { error("Select a category"); return false; } if (publishedFor.length < 1) { error("Select Publish Target"); return false; } 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"); router.replace(`${window.location.pathname}?id=${id}`); }); } 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 (
{/* 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" && (
( )} />
)} {/* Content Rewrite */} {selectedFileType === "rewrite" && (
{showRewriteEditor && (
{articleIds.length > 0 && (
{articleIds.map((articleId, index) => ( ))}
)}
( )} />
)}
)}
{/* 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) ) } />
))}
)}
{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 */}
); }