diff --git a/app/[locale]/(admin)/admin/content/video/detail/[id]/page.tsx b/app/[locale]/(admin)/admin/content/video/detail/[id]/page.tsx index b59a36b..84ad3d3 100644 --- a/app/[locale]/(admin)/admin/content/video/detail/[id]/page.tsx +++ b/app/[locale]/(admin)/admin/content/video/detail/[id]/page.tsx @@ -5,7 +5,7 @@ const VideoDetailPage = async () => { return (
-
+
diff --git a/app/[locale]/(admin)/admin/content/video/update/[id]/page.tsx b/app/[locale]/(admin)/admin/content/video/update/[id]/page.tsx index e136c95..6a37af7 100644 --- a/app/[locale]/(admin)/admin/content/video/update/[id]/page.tsx +++ b/app/[locale]/(admin)/admin/content/video/update/[id]/page.tsx @@ -1,10 +1,11 @@ import FormVideoUpdate from "@/components/form/content/audio-visual/video-update-form"; +import SiteBreadcrumb from "@/components/site-breadcrumb"; const VideoUpdatePage = async () => { return (
- {/* */} -
+ +
diff --git a/components/form/content/audio-visual/video-detail-form.tsx b/components/form/content/audio-visual/video-detail-form.tsx index b2d4074..e868c0c 100644 --- a/components/form/content/audio-visual/video-detail-form.tsx +++ b/components/form/content/audio-visual/video-detail-form.tsx @@ -173,6 +173,7 @@ export default function FormVideoDetail() { htmlDescription: details?.htmlDescription, uploadedById: details?.createdById, files: details?.files || [], + thumbnailUrl: details?.thumbnailUrl || details?.thumbnail || "", }; setDetail(mappedDetail); setFiles(details?.files || []); diff --git a/components/form/content/audio-visual/video-form.tsx b/components/form/content/audio-visual/video-form.tsx index a510b85..e9404c3 100644 --- a/components/form/content/audio-visual/video-form.tsx +++ b/components/form/content/audio-visual/video-form.tsx @@ -1,298 +1,3 @@ -"use client"; -import React, { - ChangeEvent, - useEffect, - useRef, - Fragment, - useState, -} from "react"; -import { useForm, Controller } from "react-hook-form"; -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 { useDropzone } from "react-dropzone"; -import Swal from "sweetalert2"; -import withReactContent from "sweetalert2-react-content"; -import { useParams, useRouter } from "next/navigation"; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from "@/components/ui/select"; -import { Checkbox } from "@/components/ui/checkbox"; -import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"; -import { Switch } from "@/components/ui/switch"; -import Cookies from "js-cookie"; -import Image from "next/image"; -import dynamic from "next/dynamic"; -import { CloudUpload } from "lucide-react"; -import { toast } from "sonner"; -import { htmlToString } from "@/utils/globals"; -import { getCookiesDecrypt } from "@/lib/utils"; -import { getCsrfToken } from "@/service/auth"; -import { - createArticle, - uploadArticleFiles, - uploadArticleThumbnail, - listArticleCategories, - listEnableCategory, - getTagsBySubCategoryId, - CreateArticleData, -} from "@/service/content/content"; -import { - generateDataArticle, - generateDataRewrite, - getGenerateKeywords, - getGenerateTitle, - getDetailArticle, -} from "@/service/content/ai"; - -const CustomEditor = dynamic( - () => import("@/components/editor/custom-editor"), - { ssr: false } -); - -interface FileWithPreview extends File { - preview: string; -} - -export default function FormVideo() { - const MySwal = withReactContent(Swal); - const router = useRouter(); - const params = useParams(); - const roleId = getCookiesDecrypt("urie"); - const userId = Cookies.get("userId"); - const [files, setFiles] = useState([]); - const [fileError, setFileError] = useState(null); - const [publishedFor, setPublishedFor] = useState([]); - const [title, setTitle] = useState(""); - const [categories, setCategories] = useState([]); - const [selectedCategory, setSelectedCategory] = useState(); - const [preview, setPreview] = useState(null); - const [thumbnail, setThumbnail] = useState(null); - const [isSwitchOn, setIsSwitchOn] = useState(false); - const [selectedFileType, setSelectedFileType] = useState("original"); - const [editorContent, setEditorContent] = useState(""); - const [rewriteEditorContent, setRewriteEditorContent] = useState(""); - const [isLoading, setIsLoading] = useState(false); - const [articleBody, setArticleBody] = useState(""); - const [showRewriteEditor, setShowRewriteEditor] = useState(false); - const [isGeneratedArticle, setIsGeneratedArticle] = useState(false); - const [articleIds, setArticleIds] = useState([]); - const [selectedArticleId, setSelectedArticleId] = useState( - null - ); - const [isLoadingData, setIsLoadingData] = useState(false); - const [tags, setTags] = useState([]); - const inputRef = useRef(null); - - // --- SCHEMA & FORM SETUP --- - const videoSchema = z.object({ - title: z.string().min(1, { message: "Judul wajib diisi." }), - description: z.string().optional(), - descriptionOri: z.string().optional(), - rewriteDescription: z.string().optional(), - creatorName: z.string().min(1, { message: "Nama pembuat wajib diisi." }), - files: z - .array(z.any()) - .min(1, { message: "Minimal 1 file harus diunggah." }), - categoryId: z.string().min(1, { message: "Kategori wajib dipilih." }), - tags: z - .array(z.string()) - .min(1, { message: "Minimal 1 tag harus ditambahkan." }), - publishedFor: z - .array(z.string()) - .min(1, { message: "Minimal 1 target publish harus dipilih." }), - }); - - const { - control, - handleSubmit, - getValues, - setValue, - formState: { errors }, - } = useForm>({ - resolver: zodResolver(videoSchema), - defaultValues: { - title: "", - description: "", - descriptionOri: "", - rewriteDescription: "", - creatorName: "", - files: [], - categoryId: "", - tags: [], - publishedFor: [], - }, - }); - - // --- FIX ① setValue hanya jalan saat publishedFor berubah setelah form siap --- - useEffect(() => { - if (publishedFor && publishedFor.length > 0) { - setValue("publishedFor", publishedFor); - } - }, [publishedFor, setValue]); - - // --- FIX ② pindahkan setValue("files") ke useEffect --- - useEffect(() => { - if (files.length > 0) { - setValue("files", files, { shouldValidate: true }); - } - }, [files, setValue]); - - // --- FIX ③ setValue("title") hanya jalan setelah form mount --- - useEffect(() => { - const formReady = getValues("title") !== undefined; - if (formReady && !getValues("title") && title) { - setValue("title", title); - } - }, [title, getValues, setValue]); - - // --- DROPZONE HANDLER aman --- - const { getRootProps, getInputProps } = useDropzone({ - accept: { "video/mp4": [".mp4"], "video/quicktime": [".mov"] }, - maxSize: 500 * 1024 * 1024, - multiple: true, - onDrop: (acceptedFiles, fileRejections) => { - setFileError(null); - if (fileRejections.length > 0) { - const messages = fileRejections - .map((rej) => rej.errors.map((e) => e.message).join(", ")) - .join(", "); - setFileError(messages || "File tidak valid"); - return; - } - if (acceptedFiles.length === 0) { - setFileError("Wajib upload minimal 1 file video"); - return; - } - const filesWithPreview = acceptedFiles.map((file) => - Object.assign(file, { preview: URL.createObjectURL(file) }) - ); - setFiles((prev) => [...prev, ...filesWithPreview]); // ⛔ Tidak lagi ada setValue di sini - }, - }); - - // --- FETCH CATEGORY AMAN --- - useEffect(() => { - const getCategories = async () => { - try { - const category = await listArticleCategories(1, 100); - if (!category?.error) { - const mapped = - category?.data?.data?.map((item: any) => ({ - id: item.id, - name: item.title, - })) || []; - setCategories(mapped); - } else { - const fallback = await listEnableCategory("2"); - setCategories(fallback?.data?.data?.content || []); - } - } catch (err) { - console.error("Error fetching category:", err); - } - }; - getCategories(); - }, []); - - // --- THUMBNAIL PREVIEW CLEANUP --- - useEffect(() => { - return () => { - if (preview) URL.revokeObjectURL(preview); - }; - }, [preview]); - - // --- FORM SUBMIT --- - const onSubmit = (data: z.infer) => { - MySwal.fire({ - title: "Simpan Data", - text: "Apakah Anda yakin ingin menyimpan data ini?", - icon: "warning", - showCancelButton: true, - confirmButtonText: "Simpan", - cancelButtonText: "Batal", - }).then((result) => { - if (result.isConfirmed) save(data); - }); - }; - - // --- SIMULASI SAVE --- - async function save(data: z.infer) { - console.log("✅ Data tersimpan:", data); - toast.success("Data berhasil disimpan!"); - } - - // --- RENDER --- - return ( -
- - - ( - - )} - /> - {errors.title && ( -

{errors.title.message}

- )} - -
- -
- - -

Drag & Drop file video di sini

-
- {fileError && ( -

{fileError}

- )} -
- - {files.length > 0 && ( -
- {files.map((f, i) => ( -
- {f.name} - -
- ))} -
- )} - -
- -
-
-
- ); -} - // "use client"; // import React, { // ChangeEvent, @@ -308,10 +13,10 @@ export default function FormVideo() { // import { Card } from "@/components/ui/card"; // import { zodResolver } from "@hookform/resolvers/zod"; // import * as z from "zod"; -// import { Upload } from "tus-js-client"; +// import { useDropzone } from "react-dropzone"; // import Swal from "sweetalert2"; // import withReactContent from "sweetalert2-react-content"; -// import { redirect, useParams, useRouter } from "next/navigation"; +// import { useParams, useRouter } from "next/navigation"; // import { // Select, // SelectContent, @@ -321,47 +26,34 @@ export default function FormVideo() { // } from "@/components/ui/select"; // import { Checkbox } from "@/components/ui/checkbox"; // import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"; - // import { Switch } from "@/components/ui/switch"; // import Cookies from "js-cookie"; - -// import { Textarea } from "@/components/ui/textarea"; -// import { getCookiesDecrypt } from "@/lib/utils"; -// import { useDropzone } from "react-dropzone"; -// import { Icon } from "@iconify/react"; -// import { CloudUpload } from "lucide-react"; // import Image from "next/image"; -// import { error, loading } from "@/config/swal"; -// import { Item } from "@radix-ui/react-dropdown-menu"; // import dynamic from "next/dynamic"; -// import { getCsrfToken } from "@/service/auth"; -// import { -// createMedia, -// createArticle, -// getTagsBySubCategoryId, -// listEnableCategory, -// listArticleCategories, -// uploadThumbnail, -// uploadArticleFiles, -// uploadArticleThumbnail, -// CreateArticleData, -// } from "@/service/content/content"; -// import { request } from "http"; +// import { CloudUpload } from "lucide-react"; // import { toast } from "sonner"; // import { htmlToString } from "@/utils/globals"; +// import { getCookiesDecrypt } from "@/lib/utils"; +// import { getCsrfToken } from "@/service/auth"; +// import { +// createArticle, +// uploadArticleFiles, +// uploadArticleThumbnail, +// listArticleCategories, +// listEnableCategory, +// getTagsBySubCategoryId, +// CreateArticleData, +// } from "@/service/content/content"; // import { // generateDataArticle, // generateDataRewrite, -// getDetailArticle, // getGenerateKeywords, // getGenerateTitle, +// getDetailArticle, // } from "@/service/content/ai"; -// import Link from "next/link"; // const CustomEditor = dynamic( -// () => { -// return import("@/components/editor/custom-editor"); -// }, +// () => import("@/components/editor/custom-editor"), // { ssr: false } // ); @@ -369,147 +61,46 @@ export default function FormVideo() { // preview: string; // } -// type Category = { -// id: string; -// name: string; -// }; - -// type Option = { -// id: string; -// label: string; -// }; - // export default function FormVideo() { // const MySwal = withReactContent(Swal); // const router = useRouter(); -// const editor = useRef(null); -// type VideoSchema = z.infer; // const params = useParams(); -// const locale = params?.locale; -// const [selectedFiles, setSelectedFiles] = useState([]); -// const taskId = Cookies.get("taskId"); -// const scheduleId = Cookies.get("scheduleId"); -// const scheduleType = Cookies.get("scheduleType"); // const roleId = getCookiesDecrypt("urie"); -// const [selectedFileType, setSelectedFileType] = useState("original"); -// const [categories, setCategories] = useState([]); +// const userId = Cookies.get("userId"); +// const [files, setFiles] = useState([]); +// const [fileError, setFileError] = useState(null); +// const [publishedFor, setPublishedFor] = useState([]); +// const [title, setTitle] = useState(""); +// const [categories, setCategories] = useState([]); // const [selectedCategory, setSelectedCategory] = useState(); -// const [tags, setTags] = useState([]); -// const [thumbnail, setThumbnail] = useState(null); // const [preview, setPreview] = useState(null); -// const [selectedLanguage, setSelectedLanguage] = useState(""); -// const [selectedWritingStyle, setSelectedWritingStyle] = -// useState("professional"); +// const [thumbnail, setThumbnail] = useState(null); +// const [isSwitchOn, setIsSwitchOn] = useState(false); +// const [selectedFileType, setSelectedFileType] = useState("original"); // const [editorContent, setEditorContent] = useState(""); // const [rewriteEditorContent, setRewriteEditorContent] = useState(""); -// const [selectedSEO, setSelectedSEO] = useState(""); -// const [title, setTitle] = useState(""); -// const [selectedAdvConfig, setSelectedAdvConfig] = useState(""); -// const [editingArticleId, setEditingArticleId] = useState(null); -// const [isLoading, setIsLoading] = useState(false); -// const [isLoadingData, setIsLoadingData] = useState(false); -// const [articleIds, setArticleIds] = useState([]); +// const [isLoading, setIsLoading] = useState(false); +// const [articleBody, setArticleBody] = useState(""); +// const [showRewriteEditor, setShowRewriteEditor] = useState(false); // const [isGeneratedArticle, setIsGeneratedArticle] = useState(false); -// const [articleBody, setArticleBody] = useState(""); +// const [articleIds, setArticleIds] = useState([]); // const [selectedArticleId, setSelectedArticleId] = useState( // null // ); -// const [selectedMainKeyword, setSelectedMainKeyword] = useState(""); -// const [publishedForError, setPublishedForError] = useState( -// null -// ); -// const userId = Cookies.get("userId"); -// const [selectedSize, setSelectedSize] = useState(""); -// const [detailData, setDetailData] = useState(null); -// const [articleImages, setArticleImages] = useState([]); -// const [isSwitchOn, setIsSwitchOn] = useState(false); +// const [isLoadingData, setIsLoadingData] = useState(false); +// const [tags, setTags] = useState([]); // const inputRef = useRef(null); -// const [fileError, setFileError] = useState(null); -// const [showRewriteEditor, setShowRewriteEditor] = useState(false); -// const [isContentRewriteClicked, setIsContentRewriteClicked] = useState(false); -// const [selectedTarget, setSelectedTarget] = useState(""); -// const [unitSelection, setUnitSelection] = useState({ -// allUnit: false, -// mabes: false, -// polda: false, -// polres: false, -// }); - -// let fileTypeId = "2"; -// let progressInfo: any = []; -// let counterUpdateProgress = 0; -// const [progressList, setProgressList] = useState([]); -// let uploadPersen = 0; -// const [isStartUpload, setIsStartUpload] = useState(false); -// const [counterProgress, setCounterProgress] = useState(0); -// const [publishedFor, setPublishedFor] = useState([]); -// const [files, setFiles] = useState([]); - -// const options: Option[] = [ -// { id: "all", label: "SEMUA" }, -// { id: "4", label: "UMUM" }, -// { id: "5", label: "JOURNALIS" }, -// ]; - -// const MAX_FILE_SIZE = 100 * 1024 * 1024; -// const ACCEPTED_FILE_TYPES = ["video/mp4", "video/quicktime"]; - -// const { getRootProps, getInputProps } = useDropzone({ -// accept: { -// "video/mp4": [".mp4"], -// "video/quicktime": [".mov"], -// }, -// maxSize: 500 * 1024 * 1024, -// multiple: true, -// onDrop: (acceptedFiles, fileRejections) => { -// setFileError(null); - -// if (fileRejections.length > 0) { -// const messages = fileRejections -// .map((rej) => rej.errors.map((e) => e.message).join(", ")) -// .join(", "); -// setFileError(messages || "File tidak valid"); -// return; -// } - -// if (acceptedFiles.length === 0) { -// setFileError("Wajib upload minimal 1 file video"); -// 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; -// }); -// }, -// }); +// // --- SCHEMA & FORM SETUP --- // const videoSchema = z.object({ -// title: z.string().min(1, { message: "titleRequired" }), +// title: z.string().min(1, { message: "Judul wajib diisi." }), // description: z.string().optional(), // descriptionOri: z.string().optional(), // rewriteDescription: z.string().optional(), -// creatorName: z.string().min(1, { message: "creatorRequired" }), +// creatorName: z.string().min(1, { message: "Nama pembuat wajib diisi." }), // files: z // .array(z.any()) -// .min(1, { message: "Minimal 1 file harus diunggah." }) -// .refine( -// (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.", -// } -// ), +// .min(1, { message: "Minimal 1 file harus diunggah." }), // categoryId: z.string().min(1, { message: "Kategori wajib dipilih." }), // tags: z // .array(z.string()) @@ -540,1385 +131,1790 @@ export default function FormVideo() { // }, // }); +// // --- FIX ① setValue hanya jalan saat publishedFor berubah setelah form siap --- // useEffect(() => { -// setValue("publishedFor", publishedFor); +// if (publishedFor && publishedFor.length > 0) { +// setValue("publishedFor", publishedFor); +// } // }, [publishedFor, setValue]); -// const doGenerateMainKeyword = async () => { -// console.log(selectedMainKeyword); -// if (selectedMainKeyword?.length > 1) { -// try { -// setIsLoading(true); -// const titleData = { -// keyword: selectedMainKeyword, -// style: selectedWritingStyle, -// website: "0", -// connectToWeb: true, -// lang: selectedLanguage, -// pointOfView: "None", -// clientId: "", -// }; -// console.log("Sending request for title with data:", titleData); -// const titleRes = await getGenerateTitle(titleData); -// setTitle(titleRes?.data?.data || ""); -// console.log("Generated title:", titleRes?.data?.data); - -// const keywordsData = { -// keyword: selectedMainKeyword, -// style: selectedWritingStyle, -// website: "0", -// connectToWeb: true, -// lang: selectedLanguage, -// pointOfView: "None", -// clientId: "", -// }; -// console.log("Sending request for keywords with data:", keywordsData); -// const keywordsRes = await getGenerateKeywords(keywordsData); -// setSelectedSEO(keywordsRes?.data?.data || []); -// console.log("Generated keywords:", keywordsRes?.data?.data); -// } catch (error) { -// console.error("Error during generation process:", error); -// } finally { -// setIsLoading(false); -// } -// } else { -// Swal.fire({ -// icon: "warning", -// title: "WARNING", -// text: "Please provide a valid main keyword.", -// }); -// console.error("Please provide a valid main keyword."); -// } -// }; - -// const doGenerateTitle = async () => { -// if (selectedMainKeyword?.length > 1) { -// try { -// setIsLoading(true); -// const titleData = { -// keyword: selectedMainKeyword, -// style: selectedWritingStyle, -// website: "0", -// connectToWeb: true, -// lang: selectedLanguage, -// pointOfView: "None", -// clientId: "", -// }; -// console.log("Sending request for title with data:", titleData); -// const titleRes = await getGenerateTitle(titleData); -// setTitle(titleRes?.data?.data || ""); -// console.log("Generated title:", titleRes?.data?.data); -// } catch (error) { -// console.error("Error generating title:", error); -// } finally { -// setIsLoading(false); -// } -// } else { -// Swal.fire({ -// icon: "warning", -// title: "WARNING", -// text: "Please provide a valid title.", -// }); -// console.error("Please provide a valid main keyword."); -// } -// }; - -// const doGenerateKeyword = async () => { -// if (selectedMainKeyword?.length > 1) { -// try { -// setIsLoading(true); -// const keywordsData = { -// keyword: selectedMainKeyword, -// style: selectedWritingStyle, -// website: "0", -// connectToWeb: true, -// lang: selectedLanguage, -// pointOfView: "None", -// clientId: "", -// }; -// console.log("Sending request for keywords with data:", keywordsData); -// const keywordsRes = await getGenerateKeywords(keywordsData); -// setSelectedSEO(keywordsRes?.data?.data || []); -// console.log("Generated keywords:", keywordsRes?.data?.data); -// } catch (error) { -// console.error("Error generating keywords:", error); -// } finally { -// setIsLoading(false); -// } -// } else { -// Swal.fire({ -// icon: "warning", -// title: "WARNING", -// text: "Please provide a valid keyword.", -// }); -// console.error("Please provide a valid main keyword."); -// } -// }; - -// const handleGenerateArtikel = async () => { -// const request = { -// advConfig: selectedAdvConfig, -// style: selectedWritingStyle, -// website: "None", -// connectToWeb: true, -// lang: selectedLanguage, -// pointOfView: "None", -// title: title, -// imageSource: "Web", -// mainKeyword: selectedMainKeyword, -// additionalKeywords: selectedSEO, -// targetCountry: null, -// articleSize: selectedSize, -// projectId: 2, -// createdBy: roleId, -// clientId: "ngDLPPiorplznw2jTqVe3YFCz5xqKfUJ", -// }; - -// const res = await generateDataArticle(request); -// close(); - -// if (res?.error) { -// console.error(res.message); -// return false; -// } - -// 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)); -// }; - -// 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."); -// }; -// const articleData = await waitForStatusUpdate(); -// const cleanArticleBody = articleData?.articleBody?.replace( -// /]*>/g, -// "" -// ); -// const articleImagesData = articleData?.imagesUrl?.split(","); -// setArticleBody(cleanArticleBody || ""); -// setDetailData(articleData); -// setSelectedArticleId(id); -// setArticleImages(articleImagesData || []); -// } catch (error) { -// console.error("Error fetching article details:", error); -// } finally { -// setIsLoadingData(false); -// } -// }; - -// 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((prevTags) => [...prevTags, newTag]); -// if (inputRef.current) { -// inputRef.current.value = ""; -// } -// } -// } -// }; - -// const handleRemoveTag = (index: number) => { -// setTags((prevTags) => prevTags.filter((_, i) => i !== index)); -// }; - -// const handleRemoveImage = (index: number) => { -// setSelectedFiles((prevImages) => prevImages.filter((_, i) => i !== index)); -// }; - +// // --- FIX ② pindahkan setValue("files") ke useEffect --- // useEffect(() => { -// async function initState() { -// getCategories(); -// // setVideoActive(fileTypeId == '2'); -// // getRoles(); +// if (files.length > 0) { +// setValue("files", files, { shouldValidate: true }); // } +// }, [files, setValue]); -// initState(); -// }, []); +// // --- FIX ③ setValue("title") hanya jalan setelah form mount --- +// useEffect(() => { +// const formReady = getValues("title") !== undefined; +// if (formReady && !getValues("title") && title) { +// setValue("title", title); +// } +// }, [title, getValues, setValue]); -// const getCategories = async () => { -// try { -// // Use new Article Categories API -// const category = await listArticleCategories(1, 100); -// console.log("Article categories response:", category); - -// if (category?.error) { -// console.error("Failed to fetch article categories:", category.message); -// // Fallback to old API if new one fails -// const fallbackCategory = await listEnableCategory(fileTypeId); -// const resCategory: Category[] = -// fallbackCategory?.data.data.content || []; -// setCategories(resCategory); +// // --- DROPZONE HANDLER aman --- +// const { getRootProps, getInputProps } = useDropzone({ +// accept: { "video/mp4": [".mp4"], "video/quicktime": [".mov"] }, +// maxSize: 500 * 1024 * 1024, +// multiple: true, +// onDrop: (acceptedFiles, fileRejections) => { +// setFileError(null); +// if (fileRejections.length > 0) { +// const messages = fileRejections +// .map((rej) => rej.errors.map((e) => e.message).join(", ")) +// .join(", "); +// setFileError(messages || "File tidak valid"); // return; // } - -// // Handle new API response structure -// const resCategory: Category[] = -// category?.data?.data?.map((item: any) => ({ -// id: item.id, -// name: item.title, // map title to name for backward compatibility -// title: item.title, -// description: item.description, -// ...item, -// })) || []; - -// setCategories(resCategory); -// console.log("Article categories loaded:", resCategory); - -// if (scheduleId && scheduleType === "3") { -// const findCategory = resCategory.find((o) => -// o.name.toLowerCase().includes("pers rilis") -// ); - -// if (findCategory) { -// setSelectedCategory(findCategory.id); -// const response = await getTagsBySubCategoryId(findCategory.id); -// setTags(response?.data?.data); -// } +// if (acceptedFiles.length === 0) { +// setFileError("Wajib upload minimal 1 file video"); +// return; // } -// } catch (error) { -// console.error("Failed to fetch categories:", error); -// // Fallback to old API if error occurs -// try { -// const fallbackCategory = await listEnableCategory(fileTypeId); -// const resCategory: Category[] = -// fallbackCategory?.data.data.content || []; -// setCategories(resCategory); -// } catch (fallbackError) { -// console.error("Fallback category fetch also failed:", fallbackError); -// } -// } -// }; - -// 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); -// } -// } -// }; - -// useEffect(() => { -// if (articleBody) { -// setValue("description", articleBody); -// setValue("rewriteDescription", articleBody); -// } -// }, [articleBody, setValue]); - -// const save = async (data: VideoSchema) => { -// if (publishedFor.length === 0) { -// setPublishedForError("Minimal 1 target publish harus dipilih."); -// return; -// } else { -// setPublishedForError(null); -// } -// loading(); -// const finalTags = data.tags.join(", "); -// const finalTitle = isSwitchOn ? title : data.title; -// // const finalDescription = articleBody || data.description; -// const finalDescription = isSwitchOn -// ? data.description -// : selectedFileType === "rewrite" -// ? data.rewriteDescription -// : data.descriptionOri; - -// if (!finalDescription?.trim()) { -// MySwal.fire("Error", "Deskripsi tidak boleh kosong.", "error"); -// return; -// } - -// function formatDateForBackend(date: Date) { -// const pad = (n: number) => (n < 10 ? "0" + n : n); -// return ( -// date.getFullYear() + -// "-" + -// pad(date.getMonth() + 1) + -// "-" + -// pad(date.getDate()) + -// " " + -// pad(date.getHours()) + -// ":" + -// pad(date.getMinutes()) + -// ":" + -// pad(date.getSeconds()) +// const filesWithPreview = acceptedFiles.map((file) => +// Object.assign(file, { preview: URL.createObjectURL(file) }) // ); -// } - -// let requestData: { -// title: string; -// description: string; -// htmlDescription: string; -// fileTypeId: string; -// categoryId: any; -// subCategoryId: any; -// uploadedBy: string; -// statusId: string; -// publishedFor: string; -// creatorName: string; -// tags: string; -// isYoutube: boolean; -// isInternationalMedia: boolean; -// attachFromScheduleId?: number; -// } = { -// ...data, -// title: finalTitle, -// description: htmlToString(finalDescription), -// htmlDescription: finalDescription, -// fileTypeId, -// categoryId: selectedCategory, -// subCategoryId: selectedCategory, -// uploadedBy: "2b7c8d83-d298-4b19-9f74-b07924506b58", -// statusId: "1", -// publishedFor: publishedFor.join(","), -// creatorName: data.creatorName, -// tags: finalTags, -// isYoutube: false, -// isInternationalMedia: false, -// }; - -// let id = Cookies.get("idCreate"); - -// if (scheduleId !== undefined) { -// requestData.attachFromScheduleId = Number(scheduleId); -// } - -// if (id == undefined) { -// // New Articles API request data structure -// const articleData: CreateArticleData = { -// aiArticleId: 0, // default 0 -// categoryIds: selectedCategory.toString(), -// createdAt: formatDateForBackend(new Date()), // ✅ format sesuai backend -// createdById: Number(userId), // isi dengan userId valid -// description: htmlToString(finalDescription), -// htmlDescription: finalDescription, -// isDraft: true, -// isPublish: false, -// oldId: 0, -// slug: finalTitle -// .toLowerCase() -// .replace(/\s+/g, "-") -// .replace(/[^a-z0-9-]/g, ""), -// tags: finalTags, -// title: finalTitle, -// typeId: 2, -// }; - -// // Use new Articles API -// const response = await createArticle(articleData); -// console.log("Article Data Submitted:", articleData); -// console.log("Article API Response:", response); - -// if (response?.error) { -// MySwal.fire( -// "Error", -// response.message || "Failed to create article", -// "error" -// ); -// return false; -// } - -// // Get the article ID from the new API response -// const articleId = response?.data?.data?.id; -// Cookies.set("idCreate", articleId, { expires: 1 }); -// id = articleId; - -// // Upload files using new article-files API -// const formData = new FormData(); - -// // Add all files to FormData -// files.forEach((file, index) => { -// formData.append("files", file); -// }); - -// console.log("Uploading files to article:", articleId); -// console.log("Files to upload:", files.length); +// setFiles((prev) => [...prev, ...filesWithPreview]); // ⛔ Tidak lagi ada setValue di sini +// }, +// }); +// // --- FETCH CATEGORY AMAN --- +// useEffect(() => { +// const getCategories = async () => { // try { -// const uploadResponse = await uploadArticleFiles(articleId, formData); - -// if (uploadResponse?.error) { -// MySwal.fire( -// "Error", -// uploadResponse.message || "Failed to upload files", -// "error" -// ); -// return false; +// const category = await listArticleCategories(1, 100); +// if (!category?.error) { +// const mapped = +// category?.data?.data?.map((item: any) => ({ +// id: item.id, +// name: item.title, +// })) || []; +// setCategories(mapped); +// } else { +// const fallback = await listEnableCategory("2"); +// setCategories(fallback?.data?.data?.content || []); // } - -// console.log("Files uploaded successfully:", uploadResponse); - -// // Upload thumbnail using first file as thumbnail -// if (files.length > 0) { -// const thumbnailFormData = new FormData(); -// thumbnailFormData.append("files", files[0]); // Use first file as thumbnail - -// console.log("Uploading thumbnail for article:", articleId); - -// try { -// const thumbnailResponse = await uploadArticleThumbnail( -// articleId, -// thumbnailFormData -// ); - -// if (thumbnailResponse?.error) { -// console.warn( -// "Thumbnail upload failed:", -// thumbnailResponse.message -// ); -// // Don't fail the whole process if thumbnail upload fails -// } else { -// console.log( -// "Thumbnail uploaded successfully:", -// thumbnailResponse -// ); -// } -// } catch (thumbnailError) { -// console.warn("Thumbnail upload error:", thumbnailError); -// // Don't fail the whole process if thumbnail upload fails -// } -// } -// } catch (uploadError) { -// console.error("Upload error:", uploadError); -// MySwal.fire( -// "Error", -// "Failed to upload files. Please try again.", -// "error" -// ); -// return false; +// } catch (err) { +// console.error("Error fetching category:", err); // } +// }; +// getCategories(); +// }, []); -// // Show success message -// MySwal.fire({ -// title: "Sukses", -// text: "Article dan files berhasil disimpan.", -// icon: "success", -// confirmButtonColor: "#3085d6", -// confirmButtonText: "OK", -// }).then(() => { -// router.push("/admin/content/video"); -// }); - -// Cookies.remove("idCreate"); -// return; -// } - -// Cookies.remove("idCreate"); -// }; - +// // --- THUMBNAIL PREVIEW CLEANUP --- // useEffect(() => { // return () => { -// if (preview) { -// URL.revokeObjectURL(preview); -// } +// if (preview) URL.revokeObjectURL(preview); // }; // }, [preview]); -// const onSubmit = (data: VideoSchema) => { +// // --- FORM SUBMIT --- +// const onSubmit = (data: z.infer) => { // MySwal.fire({ // title: "Simpan Data", // text: "Apakah Anda yakin ingin menyimpan data ini?", // icon: "warning", // showCancelButton: true, -// cancelButtonColor: "#d33", -// confirmButtonColor: "#3085d6", // confirmButtonText: "Simpan", +// cancelButtonText: "Batal", // }).then((result) => { -// if (result.isConfirmed) { -// save(data); -// } +// if (result.isConfirmed) save(data); // }); // }; -// async function uploadResumableFile( -// idx: number, -// id: string, -// file: any, -// duration: string -// ) { -// console.log(idx, id, file, duration); - -// // const placements = getPlacement(file.placements); -// // console.log("Placementttt: : ", placements); -// const resCsrf = await getCsrfToken(); -// const csrfToken = resCsrf?.data?.token; -// console.log("CSRF TOKEN : ", csrfToken); -// const headers = { -// "X-XSRF-TOKEN": csrfToken, -// }; - -// const upload = new Upload(file, { -// endpoint: `${process.env.NEXT_PUBLIC_API}/media/file/upload`, -// headers: headers, -// retryDelays: [0, 3000, 6000, 12_000, 24_000], -// chunkSize: 20_000, -// metadata: { -// mediaid: id, -// filename: file.name, -// filetype: file.type, -// duration, -// isWatermark: "true", // hardcode -// }, -// onBeforeRequest: function (req) { -// var xhr = req.getUnderlyingObject(); -// xhr.withCredentials = true; -// }, -// onError: async (e: any) => { -// console.log("Error upload :", e); -// error(e); -// }, -// onChunkComplete: ( -// chunkSize: any, -// bytesAccepted: any, -// bytesTotal: any -// ) => { -// const uploadPersen = Math.floor((bytesAccepted / bytesTotal) * 100); -// progressInfo[idx].percentage = uploadPersen; -// counterUpdateProgress++; -// console.log(counterUpdateProgress); -// setProgressList(progressInfo); -// setCounterProgress(counterUpdateProgress); -// }, -// onSuccess: async () => { -// uploadPersen = 100; -// progressInfo[idx].percentage = 100; -// counterUpdateProgress++; -// setCounterProgress(counterUpdateProgress); -// successTodo(); -// }, -// }); - -// upload.start(); +// // --- SIMULASI SAVE --- +// async function save(data: z.infer) { +// console.log("✅ Data tersimpan:", data); +// toast.success("Data berhasil disimpan!"); // } -// const successSubmit = (redirect: string) => { -// MySwal.fire({ -// title: "Sukses", -// text: "Data berhasil disimpan.", -// icon: "success", -// confirmButtonColor: "#3085d6", -// confirmButtonText: "OK", -// }).then(() => { -// router.push(redirect); -// }); -// }; - -// function successTodo() { -// let counter = 0; -// for (const element of progressInfo) { -// if (element.percentage == 100) { -// counter++; -// } -// } -// if (counter == progressInfo.length) { -// setIsStartUpload(false); -// // hideProgress(); -// Cookies.remove("idCreate"); -// successSubmit("/in/contributor/content/video"); -// } -// } - -// const handleImageChange = (e: React.ChangeEvent) => { -// const file = e.target.files?.[0]; -// if (file) { -// setThumbnail(file); // Simpan file asli tanpa dimodifikasi -// setPreview(URL.createObjectURL(file)); // Simpan preview string terpisah -// console.log("Selected Thumbnail:", file); -// } -// }; - -// const renderFilePreview = (file: FileWithPreview) => { -// if (file.type.startsWith("image")) { -// return ( -// {file.name} -// ); -// } else { -// return ; -// } -// }; - -// 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 handleRemoveAllFiles = () => { -// setFiles([]); -// }; - -// useEffect(() => { -// // Jika input title kosong, isi dengan hasil generate title -// if (!getValues("title") && title) { -// setValue("title", title); -// } -// }, [title, getValues, setValue]); - -// const handleRewriteClick = async () => { -// setIsContentRewriteClicked(true); - -// const request = { -// style: selectedWritingStyle, -// lang: "id", -// contextType: "text", -// urlContext: null, -// context: editorContent, // Ambil isi editor original -// createdBy: roleId, -// sentiment: "Humorous", -// clientId: "7QTW8cMojyayt6qnhqTOeJaBI70W4EaQ", -// }; - -// const res = await generateDataRewrite(request); -// close(); - -// if (res?.error) { -// console.error(res.message); -// return false; -// } - -// 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); -// }; - +// // --- RENDER --- // return ( //
-//
-// -//
-//

Form Video

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

{errors.title.message}

-// )} -//
- -//
-//
-// -// ( -//
-// - -// {errors.categoryId && ( -//

-// {errors.categoryId.message} -//

-// )} -//
-// )} -// /> -//
-//
-//
-// -//
-// -// setIsSwitchOn(checked) -// } -// /> -//
-//
-// {isSwitchOn && ( -//
-//
-//
-// -// -//
-//
-// -// -//
-//
-// -// -//
-//
-//
-//
-// -// -//
-//
-// setSelectedMainKeyword(e.target.value)} -// placeholder="Enter Main Keyword" -// /> -// {/* )} -// /> */} -//
-//
-//
-//
-// -// -//
-//
-// setTitle(e.target.value)} -// placeholder="Generated Title" -// /> -//
-//
-//
-//
-// -// -//
-//

-// Keywords To Include In The Text", -//

-//

Title Key

-//
-//