From 978c8b364fd2f0651b3427357d93b3d8b6132417 Mon Sep 17 00:00:00 2001 From: Sabda Yagra Date: Sat, 19 Jul 2025 01:06:24 +0700 Subject: [PATCH] fixing --- components/audio-player.tsx | 4 +- components/form/content/audio-detail-form.tsx | 34 +- components/form/content/audio-form.tsx | 133 ++-- components/form/content/image-form.tsx | 582 +++++++----------- components/form/content/teks-form.tsx | 229 ++++--- components/form/content/video-form.tsx | 109 +++- 6 files changed, 583 insertions(+), 508 deletions(-) diff --git a/components/audio-player.tsx b/components/audio-player.tsx index 7d615000..133e9676 100644 --- a/components/audio-player.tsx +++ b/components/audio-player.tsx @@ -48,12 +48,12 @@ const AudioPlayer: React.FC = ({ urlAudio, fileName }) => {

{fileName}

-
-
- - {options.map((option) => ( -
- ( +
+
+ + + {options.map((option) => { + const isAllChecked = + field.value.length === + options.filter((opt: any) => opt.id !== "all").length; + + const isChecked = option.id === "all" - ? publishedFor.length === - options.filter((opt: any) => opt.id !== "all") - .length - : publishedFor.includes(option.id) - } - onCheckedChange={() => handleCheckboxChange(option.id)} - /> - + ? 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} +

+ )}
- ))} -
-
+
+ )} + />
diff --git a/components/form/content/image-form.tsx b/components/form/content/image-form.tsx index c54595f7..6e6b11ac 100644 --- a/components/form/content/image-form.tsx +++ b/components/form/content/image-form.tsx @@ -59,6 +59,7 @@ import { getCsrfToken } from "@/service/auth"; import { Link } from "@/i18n/routing"; import { request } from "http"; import { useLocale, useTranslations } from "next-intl"; +import { toast } from "sonner"; interface FileWithPreview extends File { preview: string; @@ -153,27 +154,77 @@ export default function FormImage() { { id: "8", label: "KSP" }, ]; + type FileWithPreview = File & { + preview: string; + }; + + const MAX_FILE_SIZE = 100 * 1024 * 1024; + const { getRootProps, getInputProps } = useDropzone({ - onDrop: (acceptedFiles) => { - setFiles(acceptedFiles.map((file) => Object.assign(file))); - }, accept: { - "image/*": [], + "image/jpeg": [], + "image/png": [], + "image/jpg": [], + }, + onDrop: (acceptedFiles) => { + const validFiles = acceptedFiles + .filter( + (file) => + ["image/jpeg", "image/png", "image/jpg"].includes(file.type) && + file.size <= MAX_FILE_SIZE + ) + .map((file) => + Object.assign(file, { + preview: URL.createObjectURL(file), + }) + ); + + if (validFiles.length === 0) { + toast.error( + "File tidak valid. Hanya .jpg, .jpeg, .png maksimal 100MB yang diperbolehkan." + ); + return; + } + + setFiles(validFiles); + setValue("files", validFiles); }, }); + useEffect(() => { + return () => { + files.forEach((file) => URL.revokeObjectURL(file.preview)); + }; + }, [files]); + const imageSchema = z.object({ title: z.string().min(1, { message: t("titleRequired") }), description: z.string().optional(), descriptionOri: z.string().optional(), rewriteDescription: z.string().optional(), creatorName: z.string().min(1, { message: t("creatorRequired") }), - categoryId: z.string().min(1, { message: "Kategori diperlukan" }), - tags: z.array(z.string()).min(1, { message: "Minimal 1 tag diperlukan" }), + files: z + .array(z.any()) + .min(1, { message: "Minimal 1 file harus diunggah." }) + .refine( + (files) => + files.every( + (file: File) => + ["image/jpeg", "image/png", "image/jpg"].includes(file.type) && + file.size <= 100 * 1024 * 1024 + ), + { + message: + "Hanya file .jpg, .jpeg, .png, maksimal 100MB yang diperbolehkan.", + } + ), + 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: "Pilih target publish" }), - files: z.array(z.any()).min(1, { message: "File harus diupload" }), + .min(1, { message: "Minimal 1 target publish harus dipilih." }), }); const { @@ -186,15 +237,15 @@ export default function FormImage() { } = useForm({ resolver: zodResolver(imageSchema), defaultValues: { + title: "", description: "", descriptionOri: "", rewriteDescription: "", - title: "", creatorName: "", + files: [], categoryId: "", tags: [], publishedFor: [], - files: [], }, }); @@ -395,7 +446,9 @@ export default function FormImage() { e.preventDefault(); const newTag = e.currentTarget.value.trim(); if (!tags.includes(newTag)) { - setTags((prevTags) => [...prevTags, newTag]); + const updatedTags = [...tags, newTag]; + setTags(updatedTags); + setValue("tags", updatedTags); if (inputRef.current) { inputRef.current.value = ""; } @@ -404,20 +457,15 @@ export default function FormImage() { }; const handleRemoveTag = (index: number) => { - setTags((prevTags) => prevTags.filter((_, i) => i !== index)); - }; - - const handleRemoveImage = (index: number) => { - setSelectedFiles((prevImages) => prevImages.filter((_, i) => i !== index)); + const updatedTags = tags.filter((_, i) => i !== index); + setTags(updatedTags); + setValue("tags", updatedTags); }; useEffect(() => { async function initState() { getCategories(); - // setVideoActive(fileTypeId == '2'); - // getRoles(); } - initState(); }, []); @@ -478,6 +526,12 @@ export default function FormImage() { const save = async (data: ImageSchema) => { loading(); + + if (files.length === 0) { + MySwal.fire("Error", "Minimal 1 file harus diunggah.", "error"); + return; + } + const finalTags = tags.join(", "); const finalTitle = isSwitchOn ? title : data.title; // const finalDescription = articleBody || data.description; @@ -817,10 +871,10 @@ export default function FormImage() { )}
-
+ {/*
- {/* */} - ( - - )} - /> - {errors.categoryId?.message && ( -

- {errors.categoryId?.message} -

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

+ {errors.categoryId.message} +

+ )} +
+ )} + /> +
+ {/*
+ - ( - <> - { - if (e.key === "Enter" && e.currentTarget.value.trim()) { - e.preventDefault(); - field.onChange([ - ...field.value, - e.currentTarget.value.trim(), - ]); - e.currentTarget.value = ""; - } - }} - /> - -
- {field.value.map((tag: string, index: number) => ( - - {tag}{" "} - - - ))} -
- - )} + - {/* Tampilkan error */} - {errors.tags?.message && ( -

{errors.tags.message}

+ {errors.tags && ( +

+ {errors.tags.message} +

)} + +
+ {tags.map((tag, index) => ( + + {tag}{" "} + + + ))} +
{/*
@@ -1577,63 +1458,76 @@ export default function FormImage() { ))}
*/} -
-
- + ( +
+
+ - ( - <> - {options.map((option) => ( + {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 (
{ - let updated: string[] = [...field.value]; - if (checked) { - updated.push(option.id); - } else { - updated = updated.filter( - (id) => id !== option.id - ); - } - - if (option.id === "all") { - if (checked) { - updated = options - .filter((opt) => opt.id !== "all") - .map((opt) => opt.id); - } else { - updated = []; - } - } - field.onChange(updated); - }} + checked={isChecked} + onCheckedChange={handleChange} />
- ))} + ); + })} - {/* Tampilkan error */} - {errors.publishedFor?.message && ( -

- {errors.publishedFor.message} -

- )} - - )} - /> -
-
+ {errors.publishedFor && ( +

+ {errors.publishedFor.message} +

+ )} +
+
+ )} + /> + + {/* button submit */}
- - ))} -
- - )} + + - {errors.tags?.message && ( -

{errors.tags.message}

+ + {errors.tags && ( +

+ {errors.tags.message} +

)} + +
+ {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} +

+ )} +
+
+ )} + />
diff --git a/components/form/content/video-form.tsx b/components/form/content/video-form.tsx index a0bfce39..c7786b45 100644 --- a/components/form/content/video-form.tsx +++ b/components/form/content/video-form.tsx @@ -85,7 +85,6 @@ export default function FormVideo() { 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"); @@ -116,7 +115,9 @@ export default function FormVideo() { null ); const [selectedMainKeyword, setSelectedMainKeyword] = useState(""); - // const [selectedWritingStyle, setSelectedWritingStyle] = useState(""); + const [publishedForError, setPublishedForError] = useState( + null + ); const [selectedSize, setSelectedSize] = useState(""); const [detailData, setDetailData] = useState(null); const [articleImages, setArticleImages] = useState([]); @@ -159,7 +160,7 @@ export default function FormVideo() { "video/mp4": [".mp4"], "video/quicktime": [".mov"], }, - maxSize: 500 * 1024 * 1024, + maxSize: 500 * 1024 * 1024, multiple: true, onDrop: (acceptedFiles, fileRejections) => { setFileError(null); @@ -211,6 +212,9 @@ export default function FormVideo() { (files) => files.every((file: File) => file.size <= MAX_FILE_SIZE), { message: "Ukuran file maksimal 100 MB" } ), + publishedFor: z + .array(z.string()) + .min(1, { message: "Minimal 1 target publish harus dipilih." }), }); const { @@ -228,9 +232,14 @@ export default function FormVideo() { category: "", files: [], tags: [], + publishedFor: [], }, }); + useEffect(() => { + setValue("publishedFor", publishedFor); + }, [publishedFor, setValue]); + const doGenerateMainKeyword = async () => { console.log(selectedMainKeyword); if (selectedMainKeyword?.length > 1) { @@ -511,6 +520,12 @@ export default function FormVideo() { }, [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; @@ -600,8 +615,6 @@ export default function FormVideo() { }); Cookies.remove("idCreate"); - - // MySwal.fire("Sukses", "Data berhasil disimpan.", "success"); }; useEffect(() => { @@ -1427,29 +1440,73 @@ export default function FormVideo() {

{errors.tags.message}

)}
-
-
- - {options.map((option) => ( -
- ( +
+
+ + + {options.map((option) => { + const isAllChecked = + field.value.length === + options.filter((opt: any) => opt.id !== "all").length; + + const isChecked = option.id === "all" - ? publishedFor.length === - options.filter((opt: any) => opt.id !== "all") - .length - : publishedFor.includes(option.id) - } - onCheckedChange={() => handleCheckboxChange(option.id)} - /> - + ? 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} +

+ )}
- ))} -
-
+
+ )} + />