"use client"; import React, { useEffect, useRef, useState } from "react"; import { useForm, Controller } from "react-hook-form"; import { z } from "zod"; import { zodResolver } from "@hookform/resolvers/zod"; import { deleteArticle, deleteArticleFile, getArticleDetail, listEnableCategory, updateArticle, uploadThumbnail, } from "@/service/content/content"; 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 { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { Badge } from "@/components/ui/badge"; import { Checkbox } from "@/components/ui/checkbox"; import { useParams, useRouter } from "next/navigation"; import Swal from "sweetalert2"; import withReactContent from "sweetalert2-react-content"; import dynamic from "next/dynamic"; import { error, loading, close, successAutoClose } from "@/lib/swal"; import { htmlToString } from "@/utils/globals"; import { listArticleCategories, uploadArticleFiles } from "@/service/content"; import { Swiper, SwiperSlide } from "swiper/react"; import "swiper/css"; import "swiper/css/navigation"; import "swiper/css/pagination"; import "swiper/css/thumbs"; import { FreeMode, Navigation, Pagination, Thumbs } from "swiper/modules"; import { useDropzone } from "react-dropzone"; import { CloudUpload } from "lucide-react"; import { TimeIcon, TimesIcon } from "@/components/icons"; const CustomEditor = dynamic( () => import("@/components/editor/custom-editor"), { ssr: false } ); const videoSchema = z.object({ title: z.string().min(1, "Judul diperlukan"), description: z.string().min(2, "Deskripsi diperlukan"), creatorName: z.string().min(1, "Creator diperlukan"), files: z .array(z.any()) .optional() .refine( (files) => !files || files.every( (file: File) => ["video/mp4", "video/mov", "video/avi"].includes(file.type) && file.size <= 100 * 1024 * 1024 ), { message: "Hanya file .mp4, .mov, .avi, maksimal 100MB yang diperbolehkan.", } ), publishedFor: z .array(z.string()) .min(1, { message: "Minimal 1 target publish harus dipilih." }), }); type VideoSchema = z.infer; type Category = { id: number; title: string; }; interface FileWithPreview extends File { preview: string; } type Option = { id: string; label: string; }; interface DetailFile { id: number; fileUrl: string; fileName: string; } const options: Option[] = [ { id: "all", label: "SEMUA" }, { id: "4", label: "UMUM" }, { id: "5", label: "JOURNALIS" }, ]; export default function FormVideoUpdate() { const MySwal = withReactContent(Swal); const { id } = useParams() as { id: string }; const router = useRouter(); const [detail, setDetail] = useState(null); const [categories, setCategories] = useState([]); const [tags, setTags] = useState([]); const [selectedCategory, setSelectedCategory] = useState(""); const [selectedFile, setSelectedFile] = useState(null); const [publishedFor, setPublishedFor] = useState([]); const inputRef = useRef(null); const [files, setFiles] = useState([]); const [thumbsSwiper, setThumbsSwiper] = useState(null); const [detailFiles, setDetailFiles] = useState([]); const { getRootProps, getInputProps } = useDropzone({ accept: { "video/mp4": [".mp4"], "video/quicktime": [".mov"], }, maxSize: 500 * 1024 * 1024, multiple: true, onDrop: (acceptedFiles, fileRejections) => { if (fileRejections.length > 0) { const messages = fileRejections .map((rej) => rej.errors.map((e) => e.message).join(", ")) .join(", "); return; } const filesWithPreview = acceptedFiles.map((file) => Object.assign(file, { preview: URL.createObjectURL(file) }) ); setFiles((prev) => { const updatedFiles = [...prev, ...filesWithPreview]; setValue("files", updatedFiles, { shouldValidate: true }); return updatedFiles; }); }, }); const { control, handleSubmit, setValue, formState: { errors }, } = useForm({ resolver: zodResolver(videoSchema), defaultValues: { publishedFor: [] }, }); // 🧩 Fetch data detail + category useEffect(() => { loadData(); }, [id, setValue]); async function loadData() { try { loading(); const [detailRes, categoryRes] = await Promise.all([ getArticleDetail(Number(id)), listArticleCategories(1, 100), ]); close(); const detailData = detailRes?.data?.data; setDetail(detailData); const catData = categoryRes?.data?.data || []; setCategories(catData); // Prefill form setDetailFiles(detailData.files); setValue("title", detailData.title); setValue("description", detailData.htmlDescription); setValue("creatorName", detailData.createdByName ?? ""); setSelectedCategory(String(detailData.categories[0].id)); setTags(detailData.tags?.split(",").map((t: string) => t.trim()) || []); setPublishedFor(detailData.publishedFor?.split(",") || []); } catch (err) { close(); console.error("❌ Error loading detail:", err); } } const handleAddTag = (e: React.KeyboardEvent) => { if (e.key === "Enter" && e.currentTarget.value.trim()) { e.preventDefault(); const newTag = e.currentTarget.value.trim(); if (!tags.includes(newTag)) { setTags([...tags, newTag]); } e.currentTarget.value = ""; } }; const handleRemoveTag = (index: number) => { setTags(tags.filter((_, i) => i !== index)); }; const handleCheckboxChange = (value: string) => { setPublishedFor((prev) => prev.includes(value) ? prev.filter((v) => v !== value) : [...prev, value] ); }; const onSubmit = async (data: VideoSchema) => { try { loading(); const payload = { aiArticleId: detail?.aiArticleId ?? "", categoryIds: selectedCategory ?? "", createdById: detail?.createdById ?? "", description: htmlToString(data.description), htmlDescription: data.description, isDraft: false, isPublish: true, slug: detail?.slug ?? data.title.toLowerCase().replace(/\s+/g, "-"), statusId: detail?.statusId ?? 1, tags: tags.join(","), title: data.title, typeId: detail?.typeId ?? 2, }; console.log("📤 Payload Update:", payload); const res = await updateArticle(Number(id), payload); if (res?.error) { error(res.message || "Gagal memperbarui data"); return; } // if (selectedFile) { // const form = new FormData(); // form.append("file", selectedFile); // await uploadThumbnail(id, form); // } if (files.length > 0) { const formData = new FormData(); // Add all files to FormData files.forEach((file, index) => { formData.append("files", file); }); const uploadResponse = await uploadArticleFiles(id, formData); } close(); successAutoClose("Video berhasil diperbarui!"); router.push("/admin/content/video"); } catch (err) { close(); console.error("❌ Update error:", err); error("Terjadi kesalahan saat menyimpan data."); } }; const handleRemoveFile = (file: FileWithPreview) => { const uploadedFiles = files; const filtered = uploadedFiles.filter((i) => i.name !== file.name); setFiles([...filtered]); }; const handleDeleteFile = async (id: number) => { const res = await deleteArticleFile(id); loadData(); }; if (!detail) return

Memuat data...

; return (
{/* Left Side */}

Update Video

{/* Title */}
( )} /> {errors.title && (

{errors.title.message}

)}
{/* Category */}
{/* Description */}
( )} /> {errors.description && (

{errors.description.message}

)}

{/* Drop files here or click to upload. */} Drag File

Upload File Video Max
{detailFiles.map((file) => ( ))} {files?.map((file) => ( ))}
{/* Right Side */}
} />
e.target.files && setSelectedFile(e.target.files[0]) } />
{selectedFile ? ( Preview ) : ( Current Thumbnail )}
{tags.map((tag, index) => ( {tag} ))}
(
{options.map((option) => { const isAllChecked = field.value.length === options.filter((opt: any) => opt.id !== "all").length; const isChecked = option.id === "all" ? isAllChecked : field.value.includes(option.id); const handleChange = () => { let updated: string[] = []; if (option.id === "all") { updated = isAllChecked ? [] : options .filter((opt: any) => opt.id !== "all") .map((opt: any) => opt.id); } else { updated = isChecked ? field.value.filter((val) => val !== option.id) : [...field.value, option.id]; if (isAllChecked && option.id !== "all") { updated = updated.filter((val) => val !== "all"); } } field.onChange(updated); setPublishedFor(updated); }; return (
); })} {errors.publishedFor && (

{errors.publishedFor.message}

)}
)} />
); }