diff --git a/components/form/content/audio-update-form.tsx b/components/form/content/audio-update-form.tsx index 0d441faa..d83fa363 100644 --- a/components/form/content/audio-update-form.tsx +++ b/components/form/content/audio-update-form.tsx @@ -1,5 +1,11 @@ "use client"; -import React, { ChangeEvent, useEffect, useRef, useState } from "react"; +import React, { + ChangeEvent, + Fragment, + useEffect, + useRef, + useState, +} from "react"; import { useForm, Controller } from "react-hook-form"; import { Input } from "@/components/ui/input"; import { Button } from "@/components/ui/button"; @@ -27,15 +33,22 @@ import { createMedia, getTagsBySubCategoryId, listEnableCategory, + uploadThumbnail, } from "@/service/content/content"; import { detailMedia } from "@/service/curated-content/curated-content"; import { Badge } from "@/components/ui/badge"; -import { MailIcon } from "lucide-react"; +import { CloudUpload, MailIcon } from "lucide-react"; import { Swiper, SwiperSlide } from "swiper/react"; import ReactAudioPlayer from "react-audio-player"; import { FreeMode, Navigation, Thumbs } from "swiper/modules"; +import { useDropzone } from "react-dropzone"; +import Image from "next/image"; +import { Icon } from "@iconify/react/dist/iconify.js"; +import { error } from "@/lib/swal"; +import { getCsrfToken } from "@/service/auth"; +import { Upload } from "tus-js-client"; -const imageSchema = z.object({ +const audioSchema = z.object({ title: z.string().min(1, { message: "Judul diperlukan" }), description: z .string() @@ -64,6 +77,10 @@ type Detail = { tags: string; }; +interface FileWithPreview extends File { + preview: string; +} + export default function FormAudioUpdate() { const MySwal = withReactContent(Swal); const router = useRouter(); @@ -71,7 +88,14 @@ export default function FormAudioUpdate() { const { id } = useParams() as { id: string }; console.log(id); const editor = useRef(null); - type ImageSchema = z.infer; + type AudioSchema = z.infer; + + let progressInfo: any = []; + let counterUpdateProgress = 0; + const [progressList, setProgressList] = useState([]); + let uploadPersen = 0; + const [isStartUpload, setIsStartUpload] = useState(false); + const [counterProgress, setCounterProgress] = useState(0); const [selectedFiles, setSelectedFiles] = useState([]); const taskId = Cookies.get("taskId"); @@ -87,22 +111,32 @@ export default function FormAudioUpdate() { const [detailThumb, setDetailThumb] = useState([]); const [thumbsSwiper, setThumbsSwiper] = useState(null); const [selectedTarget, setSelectedTarget] = useState(""); + const inputRef = useRef(null); + const [selectedOptions, setSelectedOptions] = useState<{ + [fileId: number]: string; + }>({}); const [unitSelection, setUnitSelection] = useState({ allUnit: false, mabes: false, polda: false, polres: false, }); + const [files, setFiles] = useState([]); let fileTypeId = "4"; + const { getRootProps, getInputProps } = useDropzone({ + onDrop: (acceptedFiles) => { + setFiles(acceptedFiles.map((file) => Object.assign(file))); + }, + }); const { control, handleSubmit, setValue, formState: { errors }, - } = useForm({ - resolver: zodResolver(imageSchema), + } = useForm({ + resolver: zodResolver(audioSchema), }); // const handleKeyDown = (e: any) => { @@ -179,6 +213,10 @@ export default function FormAudioUpdate() { setDetail(details); + if (details?.files) { + setFiles(details.files); + } + if (details.publishedForObject) { const publisherIds = details.publishedForObject.map( (obj: any) => obj.id @@ -206,7 +244,7 @@ export default function FormAudioUpdate() { initState(); }, [refresh, setValue]); - const save = async (data: ImageSchema) => { + const save = async (data: AudioSchema) => { const requestData = { ...data, id: detail?.id, @@ -228,6 +266,34 @@ export default function FormAudioUpdate() { const response = await createMedia(requestData); console.log("Form Data Submitted:", requestData); + const formMedia = new FormData(); + const thumbnail = files[0]; + formMedia.append("file", thumbnail); + const responseThumbnail = await uploadThumbnail(id, formMedia); + if (responseThumbnail?.error == true) { + error(responseThumbnail?.message); + return false; + } + + const progressInfoArr = []; + for (const item of files) { + progressInfoArr.push({ percentage: 0, fileName: item.name }); + } + progressInfo = progressInfoArr; + setIsStartUpload(true); + setProgressList(progressInfoArr); + + close(); + // showProgress(); + files.map(async (item: any, index: number) => { + await uploadResumableFile( + index, + String(id), + item, + fileTypeId == "2" || fileTypeId == "4" ? item.duration : "0" + ); + }); + MySwal.fire({ title: "Sukses", text: "Data berhasil disimpan.", @@ -239,7 +305,69 @@ export default function FormAudioUpdate() { }); }; - const onSubmit = (data: ImageSchema) => { + 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: "false", // 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(); + } + + const onSubmit = (data: AudioSchema) => { MySwal.fire({ title: "Simpan Data", text: "Apakah Anda yakin ingin menyimpan data ini?", @@ -255,6 +383,123 @@ export default function FormAudioUpdate() { }); }; + 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/audio"); + } + } + + const handleRemoveAllFiles = () => { + setFiles([]); + }; + + 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 handleCheckboxChangeImage = (fileId: number, value: string) => { + setSelectedOptions((prev: any) => { + const currentSelections = prev[fileId] || []; + if (value === "all") { + // If "all" is clicked, toggle all options + if (currentSelections.includes("all")) { + return { ...prev, [fileId]: [] }; // Deselect all + } + return { + ...prev, + [fileId]: ["all", "nasional", "wilayah", "internasional"], + }; // Select all + } else { + // If any other checkbox is clicked, toggle that checkbox + const updatedSelections = currentSelections.includes(value) + ? currentSelections.filter((option: any) => option !== value) + : [...currentSelections, value]; + + // If all individual options are selected, include "all" automatically + const isAllSelected = ["nasional", "wilayah", "internasional"].every( + (opt) => updatedSelections.includes(opt) + ); + return { + ...prev, + [fileId]: isAllSelected + ? ["all", ...updatedSelections] + : updatedSelections.filter((opt: any) => opt !== "all"), + }; + } + }); + }; + return (
{detail !== undefined ? ( @@ -329,62 +574,152 @@ export default function FormAudioUpdate() {

)} - -
- - {detailThumb?.map((data: any) => { - return ( - -
- - {/* */} -
-
- ); - })} -
- - {/*
- - {detailThumb?.map((data: any) => { - return ( - -
- - +
+ + {/* */} + +
+ +
+ +

+ {/* Drop files here or click to upload. */} + Tarik file disini atau klik untuk upload. +

+
+ ( Upload file dengan format .jpg, .jpeg, atau .png. + Ukuran maksimal 100mb.) +
+
+
+ {files.length ? ( + +
{fileList}
+
+
+ +
+
- - ); - })} - -
*/} +
+ +
+ + ) : null} + {files.length > 0 && ( +
+ +
+ {files.map((file: any) => ( +
+ {file.fileName} +
+
+

{file.fileName}

+ + Lihat File + +
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+ ))} +
+
+ )} +
diff --git a/components/form/content/image-form.tsx b/components/form/content/image-form.tsx index a429a018..9920e898 100644 --- a/components/form/content/image-form.tsx +++ b/components/form/content/image-form.tsx @@ -538,12 +538,12 @@ export default function FormImage() { // 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 + "X-XSRF-TOKEN": csrfToken, }; const upload = new Upload(file, { @@ -559,8 +559,8 @@ export default function FormImage() { isWatermark: "true", // hardcode }, onBeforeRequest: function (req) { - var xhr = req.getUnderlyingObject() - xhr.withCredentials = true + var xhr = req.getUnderlyingObject(); + xhr.withCredentials = true; }, onError: async (e: any) => { console.log("Error upload :", e); diff --git a/components/form/content/image-update-form.tsx b/components/form/content/image-update-form.tsx index ae938971..cf412bbe 100644 --- a/components/form/content/image-update-form.tsx +++ b/components/form/content/image-update-form.tsx @@ -1,5 +1,11 @@ "use client"; -import React, { ChangeEvent, useEffect, useRef, useState } from "react"; +import React, { + ChangeEvent, + Fragment, + useEffect, + useRef, + useState, +} from "react"; import { useForm, Controller } from "react-hook-form"; import { Input } from "@/components/ui/input"; import { Button } from "@/components/ui/button"; @@ -27,11 +33,18 @@ import { createMedia, getTagsBySubCategoryId, listEnableCategory, + uploadThumbnail, } from "@/service/content/content"; import { detailMedia } from "@/service/curated-content/curated-content"; import { Badge } from "@/components/ui/badge"; -import { MailIcon } from "lucide-react"; +import { CloudUpload, MailIcon } from "lucide-react"; import dynamic from "next/dynamic"; +import { useDropzone } from "react-dropzone"; +import { Icon } from "@iconify/react/dist/iconify.js"; +import Image from "next/image"; +import { error } from "@/lib/swal"; +import { getCsrfToken } from "@/service/auth"; +import { Upload } from "tus-js-client"; const imageSchema = z.object({ title: z.string().min(1, { message: "Judul diperlukan" }), @@ -69,6 +82,10 @@ const CustomEditor = dynamic( { ssr: false } ); +interface FileWithPreview extends File { + preview: string; +} + export default function FormImageUpdate() { const MySwal = withReactContent(Swal); const router = useRouter(); @@ -78,6 +95,13 @@ export default function FormImageUpdate() { const editor = useRef(null); type ImageSchema = z.infer; + let progressInfo: any = []; + let counterUpdateProgress = 0; + const [progressList, setProgressList] = useState([]); + let uploadPersen = 0; + const [isStartUpload, setIsStartUpload] = useState(false); + const [counterProgress, setCounterProgress] = useState(0); + const [selectedFiles, setSelectedFiles] = useState([]); const taskId = Cookies.get("taskId"); const scheduleId = Cookies.get("scheduleId"); @@ -90,6 +114,13 @@ export default function FormImageUpdate() { const [refresh, setRefresh] = useState(false); const [selectedPublishers, setSelectedPublishers] = useState([]); const [articleBody, setArticleBody] = useState(""); + const [files, setFiles] = useState([]); + const [filesTemp, setFilesTemp] = useState([]); + const [publishedFor, setPublishedFor] = useState([]); + const inputRef = useRef(null); + const [selectedOptions, setSelectedOptions] = useState<{ + [fileId: number]: string; + }>({}); const [selectedTarget, setSelectedTarget] = useState(""); const [unitSelection, setUnitSelection] = useState({ @@ -101,6 +132,12 @@ export default function FormImageUpdate() { let fileTypeId = "1"; + const { getRootProps, getInputProps } = useDropzone({ + onDrop: (acceptedFiles) => { + setFiles(acceptedFiles.map((file) => Object.assign(file))); + }, + }); + const { control, handleSubmit, @@ -151,6 +188,19 @@ export default function FormImageUpdate() { initState(); }, []); + 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]); // Add new tag + if (inputRef.current) { + inputRef.current.value = ""; // Clear input field + } + } + } + }; + const getCategories = async () => { try { const category = await listEnableCategory(fileTypeId); @@ -184,6 +234,10 @@ export default function FormImageUpdate() { setDetail(details); + if (details?.files) { + setFiles(details.files); + } + if (details.publishedForObject) { const publisherIds = details.publishedForObject.map( (obj: any) => obj.id @@ -227,6 +281,34 @@ export default function FormImageUpdate() { const response = await createMedia(requestData); console.log("Form Data Submitted:", requestData); + const formMedia = new FormData(); + const thumbnail = files[0]; + formMedia.append("file", thumbnail); + const responseThumbnail = await uploadThumbnail(id, formMedia); + if (responseThumbnail?.error == true) { + error(responseThumbnail?.message); + return false; + } + + const progressInfoArr = []; + for (const item of files) { + progressInfoArr.push({ percentage: 0, fileName: item.name }); + } + progressInfo = progressInfoArr; + setIsStartUpload(true); + setProgressList(progressInfoArr); + + close(); + // showProgress(); + files.map(async (item: any, index: number) => { + await uploadResumableFile( + index, + String(id), + item, + fileTypeId == "2" || fileTypeId == "4" ? item.duration : "0" + ); + }); + MySwal.fire({ title: "Sukses", text: "Data berhasil disimpan.", @@ -238,6 +320,68 @@ export default function FormImageUpdate() { }); }; + 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(); + } + const onSubmit = (data: ImageSchema) => { MySwal.fire({ title: "Simpan Data", @@ -254,6 +398,123 @@ export default function FormImageUpdate() { }); }; + 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/image/"); + } + } + + const handleRemoveAllFiles = () => { + setFiles([]); + }; + + 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 handleCheckboxChangeImage = (fileId: number, value: string) => { + setSelectedOptions((prev: any) => { + const currentSelections = prev[fileId] || []; + if (value === "all") { + // If "all" is clicked, toggle all options + if (currentSelections.includes("all")) { + return { ...prev, [fileId]: [] }; // Deselect all + } + return { + ...prev, + [fileId]: ["all", "nasional", "wilayah", "internasional"], + }; // Select all + } else { + // If any other checkbox is clicked, toggle that checkbox + const updatedSelections = currentSelections.includes(value) + ? currentSelections.filter((option: any) => option !== value) + : [...currentSelections, value]; + + // If all individual options are selected, include "all" automatically + const isAllSelected = ["nasional", "wilayah", "internasional"].every( + (opt) => updatedSelections.includes(opt) + ); + return { + ...prev, + [fileId]: isAllSelected + ? ["all", ...updatedSelections] + : updatedSelections.filter((opt: any) => opt !== "all"), + }; + } + }); + }; + return ( {detail !== undefined ? ( @@ -326,6 +587,153 @@ export default function FormImageUpdate() {

)} +
+ + {/* */} + +
+ +
+ +

+ {/* Drop files here or click to upload. */} + Tarik file disini atau klik untuk upload. +

+
+ ( Upload file dengan format .jpg, .jpeg, atau .png. + Ukuran maksimal 100mb.) +
+
+
+ {files.length ? ( + +
{fileList}
+
+
+ +
+ +
+
+ +
+
+ ) : null} + {files.length > 0 && ( +
+ +
+ {files.map((file: any) => ( +
+ {file.fileName} +
+
+

{file.fileName}

+ + Lihat File + +
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+ ))} +
+
+ )} +
+
@@ -367,6 +775,30 @@ export default function FormImageUpdate() {
+ +
+ {tags.map((tag, index) => ( + + {tag}{" "} + + + ))} +
{detail?.tags?.split(",").map((tag, index) => ( ; + type TeksSchema = z.infer; + + let progressInfo: any = []; + let counterUpdateProgress = 0; + const [progressList, setProgressList] = useState([]); + let uploadPersen = 0; + const [isStartUpload, setIsStartUpload] = useState(false); + const [counterProgress, setCounterProgress] = useState(0); const [selectedFiles, setSelectedFiles] = useState([]); const taskId = Cookies.get("taskId"); @@ -82,6 +115,10 @@ export default function FormTeksUpdate() { const [refresh, setRefresh] = useState(false); const [selectedPublishers, setSelectedPublishers] = useState([]); + const [files, setFiles] = useState([]); + const [selectedOptions, setSelectedOptions] = useState<{ + [fileId: number]: string; + }>({}); const [selectedTarget, setSelectedTarget] = useState(""); const [unitSelection, setUnitSelection] = useState({ allUnit: false, @@ -89,16 +126,31 @@ export default function FormTeksUpdate() { polda: false, polres: false, }); + const [publishedFor, setPublishedFor] = useState([]); let fileTypeId = "3"; + const { getRootProps, getInputProps } = useDropzone({ + onDrop: (acceptedFiles) => { + setFiles(acceptedFiles.map((file) => Object.assign(file))); + }, + }); + + const options: Option[] = [ + { id: "all", label: "SEMUA" }, + { id: "5", label: "UMUM" }, + { id: "6", label: "JOURNALIS" }, + { id: "7", label: "POLRI" }, + { id: "8", label: "KSP" }, + ]; + const { control, handleSubmit, setValue, formState: { errors }, - } = useForm({ - resolver: zodResolver(imageSchema), + } = useForm({ + resolver: zodResolver(teksSchema), }); // const handleKeyDown = (e: any) => { @@ -128,11 +180,11 @@ export default function FormTeksUpdate() { setSelectedFiles((prevImages) => prevImages.filter((_, i) => i !== index)); }; - const handleCheckboxChange = (id: number) => { - setSelectedPublishers((prev) => - prev.includes(id) ? prev.filter((item) => item !== id) : [...prev, id] - ); - }; + // const handleCheckboxChange = (id: number) => { + // setSelectedPublishers((prev) => + // prev.includes(id) ? prev.filter((item) => item !== id) : [...prev, id] + // ); + // }; useEffect(() => { async function initState() { @@ -175,6 +227,10 @@ export default function FormTeksUpdate() { setDetail(details); + if (details?.files) { + setFiles(details.files); + } + if (details.publishedForObject) { const publisherIds = details.publishedForObject.map( (obj: any) => obj.id @@ -196,7 +252,34 @@ export default function FormTeksUpdate() { initState(); }, [refresh, setValue]); - const save = async (data: ImageSchema) => { + const handleCheckboxChange = (id: string): void => { + if (id === "all") { + if (publishedFor.includes("all")) { + // Uncheck all checkboxes + setPublishedFor([]); + } else { + // Select all checkboxes + 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]; + + // Remove "all" if any checkbox is unchecked + if (publishedFor.includes("all") && id !== "all") { + setPublishedFor(updatedPublishedFor.filter((item) => item !== "all")); + } else { + setPublishedFor(updatedPublishedFor); + } + } + }; + + const save = async (data: TeksSchema) => { const requestData = { ...data, id: detail?.id, @@ -208,7 +291,7 @@ export default function FormTeksUpdate() { subCategoryId: selectedTarget, uploadedBy: "2b7c8d83-d298-4b19-9f74-b07924506b58", statusId: "1", - publishedFor: "6", + publishedFor: publishedFor.join(","), creatorName: data.creatorName, tags: "siap", isYoutube: false, @@ -218,6 +301,34 @@ export default function FormTeksUpdate() { const response = await createMedia(requestData); console.log("Form Data Submitted:", requestData); + const formMedia = new FormData(); + const thumbnail = files[0]; + formMedia.append("file", thumbnail); + const responseThumbnail = await uploadThumbnail(id, formMedia); + if (responseThumbnail?.error == true) { + error(responseThumbnail?.message); + return false; + } + + const progressInfoArr = []; + for (const item of files) { + progressInfoArr.push({ percentage: 0, fileName: item.name }); + } + progressInfo = progressInfoArr; + setIsStartUpload(true); + setProgressList(progressInfoArr); + + close(); + // showProgress(); + files.map(async (item: any, index: number) => { + await uploadResumableFile( + index, + String(id), + item, + fileTypeId == "2" || fileTypeId == "4" ? item.duration : "0" + ); + }); + MySwal.fire({ title: "Sukses", text: "Data berhasil disimpan.", @@ -229,7 +340,69 @@ export default function FormTeksUpdate() { }); }; - const onSubmit = (data: ImageSchema) => { + 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(); + } + + const onSubmit = (data: TeksSchema) => { MySwal.fire({ title: "Simpan Data", text: "Apakah Anda yakin ingin menyimpan data ini?", @@ -245,6 +418,123 @@ export default function FormTeksUpdate() { }); }; + 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/teks"); + } + } + + const handleRemoveAllFiles = () => { + setFiles([]); + }; + + 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 handleCheckboxChangeImage = (fileId: number, value: string) => { + setSelectedOptions((prev: any) => { + const currentSelections = prev[fileId] || []; + if (value === "all") { + // If "all" is clicked, toggle all options + if (currentSelections.includes("all")) { + return { ...prev, [fileId]: [] }; // Deselect all + } + return { + ...prev, + [fileId]: ["all", "nasional", "wilayah", "internasional"], + }; // Select all + } else { + // If any other checkbox is clicked, toggle that checkbox + const updatedSelections = currentSelections.includes(value) + ? currentSelections.filter((option: any) => option !== value) + : [...currentSelections, value]; + + // If all individual options are selected, include "all" automatically + const isAllSelected = ["nasional", "wilayah", "internasional"].every( + (opt) => updatedSelections.includes(opt) + ); + return { + ...prev, + [fileId]: isAllSelected + ? ["all", ...updatedSelections] + : updatedSelections.filter((opt: any) => opt !== "all"), + }; + } + }); + }; + return ( {detail !== undefined ? ( @@ -300,7 +590,6 @@ export default function FormTeksUpdate() {
-
)}
+
+ + {/* */} + +
+ +
+ +

+ {/* Drop files here or click to upload. */} + Tarik file disini atau klik untuk upload. +

+
+ ( Upload file dengan format .doc, .docx, .pdf, .ppt, + atau .pptx Ukuran maksimal 100mb.) +
+
+
+ {files.length ? ( + +
{fileList}
+
+
+ +
+ +
+
+ +
+
+ ) : null} + {files.length > 0 && ( +
+ +
+ {files.map((file: any) => ( +
+ {file.fileName} +
+
+

{file.fileName}

+ + Lihat File + +
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+ ))} +
+
+ )} +
+
@@ -377,38 +813,23 @@ export default function FormTeksUpdate() {
-
- handleCheckboxChange(5)} - /> - -
-
- handleCheckboxChange(6)} - /> - -
-
- handleCheckboxChange(7)} - /> - -
-
- handleCheckboxChange(8)} - /> - -
+ {options.map((option) => ( +
+ opt.id !== "all") + .length + : publishedFor.includes(option.id) + } + onCheckedChange={() => handleCheckboxChange(option.id)} + /> + +
+ ))}
diff --git a/components/form/content/video-update-form.tsx b/components/form/content/video-update-form.tsx index 4ec5a31a..d242b28e 100644 --- a/components/form/content/video-update-form.tsx +++ b/components/form/content/video-update-form.tsx @@ -1,5 +1,11 @@ "use client"; -import React, { ChangeEvent, useEffect, useRef, useState } from "react"; +import React, { + ChangeEvent, + Fragment, + useEffect, + useRef, + useState, +} from "react"; import { useForm, Controller } from "react-hook-form"; import { Input } from "@/components/ui/input"; import { Button } from "@/components/ui/button"; @@ -27,10 +33,11 @@ import { createMedia, getTagsBySubCategoryId, listEnableCategory, + uploadThumbnail, } from "@/service/content/content"; import { detailMedia } from "@/service/curated-content/curated-content"; import { Badge } from "@/components/ui/badge"; -import { MailIcon } from "lucide-react"; +import { CloudUpload, MailIcon } from "lucide-react"; import { Swiper, SwiperSlide } from "swiper/react"; import "swiper/css"; import "swiper/css/free-mode"; @@ -40,8 +47,15 @@ import "swiper/css/thumbs"; import "swiper/css"; import "swiper/css/navigation"; import { FreeMode, Navigation, Pagination, Thumbs } from "swiper/modules"; +import { files } from "@/app/[locale]/(protected)/app/projects/[id]/data"; +import { useDropzone } from "react-dropzone"; +import Image from "next/image"; +import { Icon } from "@iconify/react/dist/iconify.js"; +import { Upload } from "tus-js-client"; +import { getCsrfToken } from "@/service/auth"; +import { error } from "@/lib/swal"; -const imageSchema = z.object({ +const videoSchema = z.object({ title: z.string().min(1, { message: "Judul diperlukan" }), description: z .string() @@ -70,6 +84,10 @@ type Detail = { tags: string; }; +interface FileWithPreview extends File { + preview: string; +} + export default function FormVideoUpdate() { const MySwal = withReactContent(Swal); const router = useRouter(); @@ -77,7 +95,14 @@ export default function FormVideoUpdate() { const { id } = useParams() as { id: string }; console.log(id); const editor = useRef(null); - type ImageSchema = z.infer; + type VideoSchema = z.infer; + + let progressInfo: any = []; + let counterUpdateProgress = 0; + const [progressList, setProgressList] = useState([]); + let uploadPersen = 0; + const [isStartUpload, setIsStartUpload] = useState(false); + const [counterProgress, setCounterProgress] = useState(0); const [selectedFiles, setSelectedFiles] = useState([]); const taskId = Cookies.get("taskId"); @@ -93,6 +118,7 @@ export default function FormVideoUpdate() { const [detailVideo, setDetailVideo] = useState([]); const [thumbsSwiper, setThumbsSwiper] = useState(null); + const [files, setFiles] = useState([]); const [selectedTarget, setSelectedTarget] = useState(""); const [unitSelection, setUnitSelection] = useState({ allUnit: false, @@ -100,16 +126,26 @@ export default function FormVideoUpdate() { polda: false, polres: false, }); + const inputRef = useRef(null); + const [selectedOptions, setSelectedOptions] = useState<{ + [fileId: number]: string; + }>({}); let fileTypeId = "2"; + const { getRootProps, getInputProps } = useDropzone({ + onDrop: (acceptedFiles) => { + setFiles(acceptedFiles.map((file) => Object.assign(file))); + }, + }); + const { control, handleSubmit, setValue, formState: { errors }, - } = useForm({ - resolver: zodResolver(imageSchema), + } = useForm({ + resolver: zodResolver(videoSchema), }); // const handleKeyDown = (e: any) => { @@ -186,6 +222,10 @@ export default function FormVideoUpdate() { setDetail(details); + if (details?.files) { + setFiles(details.files); + } + if (details.publishedForObject) { const publisherIds = details.publishedForObject.map( (obj: any) => obj.id @@ -213,7 +253,7 @@ export default function FormVideoUpdate() { initState(); }, [refresh, setValue]); - const save = async (data: ImageSchema) => { + const save = async (data: VideoSchema) => { const requestData = { ...data, id: detail?.id, @@ -235,6 +275,44 @@ export default function FormVideoUpdate() { const response = await createMedia(requestData); console.log("Form Data Submitted:", requestData); + const formMedia = new FormData(); + const thumbnail = files[0]; + formMedia.append("file", thumbnail); + const responseThumbnail = await uploadThumbnail(id, formMedia); + if (responseThumbnail?.error == true) { + error(responseThumbnail?.message); + return false; + } + + const progressInfoArr = []; + for (const item of files) { + progressInfoArr.push({ percentage: 0, fileName: item.name }); + } + progressInfo = progressInfoArr; + setIsStartUpload(true); + setProgressList(progressInfoArr); + + close(); + // showProgress(); + files.map(async (item: any, index: number) => { + await uploadResumableFile( + index, + String(id), + item, + fileTypeId == "2" || fileTypeId == "4" ? item.duration : "0" + ); + }); + + MySwal.fire({ + title: "Sukses", + text: "Data berhasil disimpan.", + icon: "success", + confirmButtonColor: "#3085d6", + confirmButtonText: "OK", + }).then(() => { + router.push("/en/contributor/content/image"); + }); + MySwal.fire({ title: "Sukses", text: "Data berhasil disimpan.", @@ -246,7 +324,68 @@ export default function FormVideoUpdate() { }); }; - const onSubmit = (data: ImageSchema) => { + 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: "false", // 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(); + } + + const onSubmit = (data: VideoSchema) => { MySwal.fire({ title: "Simpan Data", text: "Apakah Anda yakin ingin menyimpan data ini?", @@ -262,6 +401,123 @@ export default function FormVideoUpdate() { }); }; + 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 handleRemoveAllFiles = () => { + setFiles([]); + }; + + 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 handleCheckboxChangeImage = (fileId: number, value: string) => { + setSelectedOptions((prev: any) => { + const currentSelections = prev[fileId] || []; + if (value === "all") { + // If "all" is clicked, toggle all options + if (currentSelections.includes("all")) { + return { ...prev, [fileId]: [] }; // Deselect all + } + return { + ...prev, + [fileId]: ["all", "nasional", "wilayah", "internasional"], + }; // Select all + } else { + // If any other checkbox is clicked, toggle that checkbox + const updatedSelections = currentSelections.includes(value) + ? currentSelections.filter((option: any) => option !== value) + : [...currentSelections, value]; + + // If all individual options are selected, include "all" automatically + const isAllSelected = ["nasional", "wilayah", "internasional"].every( + (opt) => updatedSelections.includes(opt) + ); + return { + ...prev, + [fileId]: isAllSelected + ? ["all", ...updatedSelections] + : updatedSelections.filter((opt: any) => opt !== "all"), + }; + } + }); + }; + return ( {detail !== undefined ? ( @@ -338,48 +594,152 @@ export default function FormVideoUpdate() {

)}
- -
- - {detailVideo?.map((data: any) => ( - - - ))} - -
- - {detailVideo?.map((data: any) => ( - - - ))} - -
+
+ + {/* */} + +
+ +
+ +

+ {/* Drop files here or click to upload. */} + Tarik file disini atau klik untuk upload. +

+
+ ( Upload file dengan format .jpg, .jpeg, atau .png. + Ukuran maksimal 100mb.) +
+
+
+ {files.length ? ( + +
{fileList}
+
+
+ +
+ +
+
+ +
+
+ ) : null} + {files.length > 0 && ( +
+ +
+ {files.map((file: any) => ( +
+ {file.fileName} +
+
+

{file.fileName}

+ + Lihat File + +
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+ ))} +
+
+ )} +
diff --git a/components/form/task/task-detail-form.tsx b/components/form/task/task-detail-form.tsx index 9ab91157..5383a378 100644 --- a/components/form/task/task-detail-form.tsx +++ b/components/form/task/task-detail-form.tsx @@ -48,6 +48,8 @@ import { close, error, loading } from "@/lib/swal"; import { getCookiesDecrypt } from "@/lib/utils"; import { Avatar, AvatarImage } from "@/components/ui/avatar"; import { successCallback } from "@/config/swal"; +import FileUploader from "../shared/file-uploader"; +import { AudioRecorder } from "react-audio-voice-recorder"; const taskSchema = z.object({ uniqueCode: z.string().min(1, { message: "Judul diperlukan" }), @@ -145,6 +147,10 @@ interface AcceptanceData { }; } +interface FileWithPreview extends File { + preview: string; +} + export default function FormTaskDetail() { const MySwal = withReactContent(Swal); const router = useRouter(); @@ -187,11 +193,24 @@ export default function FormTaskDetail() { const [acceptAcceptance, setAcceptAcceptance] = useState( [] ); + + const [imageFiles, setImageFiles] = useState([]); + const [videoFiles, setVideoFiles] = useState([]); + const [textFiles, setTextFiles] = useState([]); + const [audioFiles, setAudioFiles] = useState([]); + const [isImageUploadFinish, setIsImageUploadFinish] = useState(false); + const [isVideoUploadFinish, setIsVideoUploadFinish] = useState(false); + const [isTextUploadFinish, setIsTextUploadFinish] = useState(false); + const [isAudioUploadFinish, setIsAudioUploadFinish] = useState(false); + const [voiceNoteLink, setVoiceNoteLink] = useState(""); const [statusAcceptance, setStatusAcceptance] = useState(); const [modalType, setModalType] = useState<"terkirim" | "diterima" | "">(""); const [Isloading, setLoading] = useState(false); const [refreshAcceptance, setRefreshAcceptance] = useState(false); const [replyingTo, setReplyingTo] = useState(null); + const [audioFile, setAudioFile] = useState(null); + const [isRecording, setIsRecording] = useState(false); + const [timer, setTimer] = useState(120); const [platformTypeVisible, setPlatformTypeVisible] = useState(false); const [unitSelection, setUnitSelection] = useState({ @@ -583,7 +602,7 @@ export default function FormTaskDetail() { }; const getModalContent = (type: "terkirim" | "diterima") => ( -
+
{Isloading ? (

Loading...

) : ( @@ -674,6 +693,49 @@ export default function FormTaskDetail() { }); } + const addAudioElement = (blob: Blob) => { + const url = URL.createObjectURL(blob); + const audio = document.createElement("audio"); + audio.src = url; + audio.controls = true; + document.body.appendChild(audio); + + // Convert Blob to File + const file = new File([blob], "voiceNote.webm", { type: "audio/webm" }); + setAudioFile(file); + }; + const handleDeleteAudio = () => { + // Remove the audio file by setting state to null + setAudioFile(null); + const audioElements = document.querySelectorAll("audio"); + audioElements.forEach((audio) => audio.remove()); + }; + + const onRecordingStart = () => { + setIsRecording(true); + + const countdown = setInterval(() => { + setTimer((prevTimer) => { + if (prevTimer <= 1) { + clearInterval(countdown); + return 0; + } + return prevTimer - 1; + }); + }, 1000); + + setTimeout(() => { + if (isRecording) { + handleStopRecording(); + } + }, 120000); + }; + + const handleStopRecording = () => { + setIsRecording(false); + setTimer(120); // Reset the timer to 2 minutes for the next recording + }; + return ( {detail !== undefined ? ( @@ -711,7 +773,7 @@ export default function FormTaskDetail() { - + Detail Status Penugasan @@ -991,6 +1053,92 @@ export default function FormTaskDetail() {

)}
+
+ +
+
+ + setImageFiles(files)} + /> +
+
+ + setImageFiles(files)} + /> +
+
+ + setTextFiles(files)} + /> +
+
+ + + setAudioFiles(files)} + className="mt-2" + /> +
+ {audioFile && ( +
+

Voice note ready to submit: {audioFile.name}

+ +
+ )} + {isRecording &&

Recording... {timer} seconds remaining

}{" "} + {/* Display remaining time */} +
+ + setVoiceNoteLink(e.target.value)} + /> +
+
+
diff --git a/components/form/task/task-edit-form.tsx b/components/form/task/task-edit-form.tsx index c2d81103..e8631463 100644 --- a/components/form/task/task-edit-form.tsx +++ b/components/form/task/task-edit-form.tsx @@ -33,6 +33,8 @@ import { DialogTrigger, } from "@/components/ui/dialog"; import { ChevronDown, ChevronUp } from "lucide-react"; +import FileUploader from "../shared/file-uploader"; +import { AudioRecorder } from "react-audio-voice-recorder"; const taskSchema = z.object({ // uniqueCode: z.string().min(1, { message: "Judul diperlukan" }), @@ -63,6 +65,10 @@ export type taskDetail = { is_active: string; }; +interface FileWithPreview extends File { + preview: string; +} + export default function FormTaskEdit() { const MySwal = withReactContent(Swal); const router = useRouter(); @@ -80,8 +86,15 @@ export default function FormTaskEdit() { text: false, }); - // const [assignmentType, setAssignmentType] = useState("mediahub"); - // const [assignmentCategory, setAssignmentCategory] = useState("publication"); + const [imageFiles, setImageFiles] = useState([]); + const [videoFiles, setVideoFiles] = useState([]); + const [textFiles, setTextFiles] = useState([]); + const [audioFiles, setAudioFiles] = useState([]); + const [isImageUploadFinish, setIsImageUploadFinish] = useState(false); + const [isVideoUploadFinish, setIsVideoUploadFinish] = useState(false); + const [isTextUploadFinish, setIsTextUploadFinish] = useState(false); + const [isAudioUploadFinish, setIsAudioUploadFinish] = useState(false); + const [voiceNoteLink, setVoiceNoteLink] = useState(""); const [mainType, setMainType] = useState("1"); const [taskType, setTaskType] = useState("atensi-khusus"); const [broadcastType, setBroadcastType] = useState(""); // untuk Tipe Penugasan @@ -93,6 +106,9 @@ export default function FormTaskEdit() { const [checkedLevels, setCheckedLevels] = useState(new Set()); const [expandedPolda, setExpandedPolda] = useState([{}]); const [isLoading, setIsLoading] = useState(false); + const [audioFile, setAudioFile] = useState(null); + const [isRecording, setIsRecording] = useState(false); + const [timer, setTimer] = useState(120); const [platformTypeVisible, setPlatformTypeVisible] = useState(false); const [unitSelection, setUnitSelection] = useState({ @@ -306,6 +322,49 @@ export default function FormTaskEdit() { })); }; + const addAudioElement = (blob: Blob) => { + const url = URL.createObjectURL(blob); + const audio = document.createElement("audio"); + audio.src = url; + audio.controls = true; + document.body.appendChild(audio); + + // Convert Blob to File + const file = new File([blob], "voiceNote.webm", { type: "audio/webm" }); + setAudioFile(file); + }; + const handleDeleteAudio = () => { + // Remove the audio file by setting state to null + setAudioFile(null); + const audioElements = document.querySelectorAll("audio"); + audioElements.forEach((audio) => audio.remove()); + }; + + const onRecordingStart = () => { + setIsRecording(true); + + const countdown = setInterval(() => { + setTimer((prevTimer) => { + if (prevTimer <= 1) { + clearInterval(countdown); + return 0; + } + return prevTimer - 1; + }); + }, 1000); + + setTimeout(() => { + if (isRecording) { + handleStopRecording(); + } + }, 120000); + }; + + const handleStopRecording = () => { + setIsRecording(false); + setTimer(120); // Reset the timer to 2 minutes for the next recording + }; + return (
@@ -537,27 +596,6 @@ export default function FormTaskEdit() { ))}
-
- - setBroadcastType(value)} // Mengatur nilai saat radio berubah - className="flex flex-wrap gap-3" - > -
- - -
-
- - -
-
- - -
-
-
)}
+
+ +
+
+ + setImageFiles(files)} + /> +
+
+ + setImageFiles(files)} + /> +
+
+ + setTextFiles(files)} + /> +
+
+ + + setAudioFiles(files)} + className="mt-2" + /> +
+ {audioFile && ( +
+

Voice note ready to submit: {audioFile.name}

+ +
+ )} + {isRecording &&

Recording... {timer} seconds remaining

}{" "} + {/* Display remaining time */} +
+ + setVoiceNoteLink(e.target.value)} + /> +
+
+
{/* Submit Button */} diff --git a/components/form/task/task-form.tsx b/components/form/task/task-form.tsx index 88b8d826..c340735a 100644 --- a/components/form/task/task-form.tsx +++ b/components/form/task/task-form.tsx @@ -306,23 +306,21 @@ export default function FormTask() { const onRecordingStart = () => { setIsRecording(true); - // Start a timer that stops the recording after 2 minutes (120 seconds) const countdown = setInterval(() => { setTimer((prevTimer) => { if (prevTimer <= 1) { - clearInterval(countdown); // Stop the timer when it reaches 0 + clearInterval(countdown); return 0; } return prevTimer - 1; }); - }, 1000); // Update every second + }, 1000); - // Automatically stop recording after 2 minutes setTimeout(() => { if (isRecording) { handleStopRecording(); } - }, 120000); // Stop after 2 minutes (120,000 ms) + }, 120000); }; const handleStopRecording = () => { diff --git a/components/landing-page/navbar.tsx b/components/landing-page/navbar.tsx index 0504f2aa..8106dde2 100644 --- a/components/landing-page/navbar.tsx +++ b/components/landing-page/navbar.tsx @@ -6,8 +6,23 @@ import { FiFile, FiImage, FiMusic, FiYoutube } from "react-icons/fi"; import { useParams, usePathname } from "next/navigation"; import { generateLocalizedPath } from "@/utils/globals"; import { Link } from "@/i18n/routing"; -import { NavigationMenu, NavigationMenuContent, NavigationMenuItem, NavigationMenuLink, NavigationMenuList, NavigationMenuTrigger, navigationMenuTriggerStyle } from "@/components/ui/navigation-menu"; -import { DropdownMenu, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger } from "../ui/dropdown-menu"; +import { + NavigationMenu, + NavigationMenuContent, + NavigationMenuItem, + NavigationMenuLink, + NavigationMenuList, + NavigationMenuTrigger, + navigationMenuTriggerStyle, +} from "@/components/ui/navigation-menu"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuGroup, + DropdownMenuItem, + DropdownMenuSeparator, + DropdownMenuTrigger, +} from "../ui/dropdown-menu"; import Image from "next/image"; import { Icon } from "../ui/icon"; import { getCookiesDecrypt } from "@/lib/utils"; @@ -17,7 +32,16 @@ import { useTranslations } from "next-intl"; import { useRouter } from "@/i18n/routing"; import { Button } from "@/components/ui/button"; import LocalSwitcher from "../partials/header/locale-switcher"; -import { Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog"; +import { + Dialog, + DialogClose, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@/components/ui/dialog"; import { listRole } from "@/service/landing/landing"; type Detail = { @@ -54,7 +78,11 @@ const Navbar = () => { const [menuActive, setMenuActive] = useState(); const [category, setCategory] = useState(); - let prefixPath = poldaName ? `/polda/${poldaName}` : satkerName ? `/satker/${satkerName}` : "/"; + let prefixPath = poldaName + ? `/polda/${poldaName}` + : satkerName + ? `/satker/${satkerName}` + : "/"; let active = ""; let menu = ""; @@ -141,18 +169,41 @@ const Navbar = () => {
{/* Logo */} - Media Hub Logo + Media Hub Logo {/* Mobile Menu Toggle */} - @@ -165,7 +216,14 @@ const Navbar = () => { - + { - router.push(prefixPath+"/image/filter")} className="flex place-items-start gap-1.5 p-2"> + router.push(prefixPath + "/image/filter")} + className="flex place-items-start gap-1.5 p-2" + >

{t("image")}

- router.push(prefixPath+"/video/filter")} className="flex items-start gap-1.5 p-2 "> + router.push(prefixPath + "/video/filter")} + className="flex items-start gap-1.5 p-2 " + > {pathname?.split("/")[1] == "in" ? ( <>

@@ -202,13 +266,19 @@ const Navbar = () => { {t("video")}

*/}
- router.push(prefixPath+"/document/filter")} className="flex place-items-start gap-1.5 p-2"> + router.push(prefixPath + "/document/filter")} + className="flex place-items-start gap-1.5 p-2" + >

{t("text")}

- router.push(prefixPath+"/audio/filter")} className="flex place-items-start gap-1.5 p-2 "> + router.push(prefixPath + "/audio/filter")} + className="flex place-items-start gap-1.5 p-2 " + >

{t("audio")}{" "} @@ -217,10 +287,17 @@ const Navbar = () => { - + - + { - + - + {

@@ -279,7 +366,12 @@ const Navbar = () => { className="pl-8 pr-4 py-1 w-28 text-[13px] border rounded-full focus:outline-none dark:text-white" /> - + { )}
*/} - {roleId === "5" || roleId === "6" || roleId === "7" || roleId === "8" ? ( + {roleId === "5" || + roleId === "6" || + roleId === "7" || + roleId === "8" ? ( {detail !== undefined ? (
- {"Image"} + {"Image"}
-
{detail?.fullname}
-

({detail?.fullname})

+
+ {detail?.fullname} +
+

+ ({detail?.fullname}) +

@@ -367,7 +472,11 @@ const Navbar = () => { href: "/content-management/galery", }, ].map((item, index) => ( - + {item.name} @@ -379,7 +488,11 @@ const Navbar = () => {
- @@ -388,16 +501,33 @@ const Navbar = () => { - ) : roleId === "2" || roleId === "3" || roleId === "4" || roleId === "9" || roleId === "10" || roleId === "11" || roleId === "12" || roleId === "13" ? ( + ) : roleId === "2" || + roleId === "3" || + roleId === "4" || + roleId === "9" || + roleId === "10" || + roleId === "11" || + roleId === "12" || + roleId === "13" ? ( // Dropdown menu for roleId === 3 {detail !== undefined ? (
- {"Image"} + {"Image"}
-
{detail?.fullname}
-

({detail?.fullname})

+
+ {detail?.fullname} +
+

+ ({detail?.fullname}) +

@@ -421,7 +551,11 @@ const Navbar = () => { href: "/dashboard", }, ].map((item, index) => ( - + {item.name} @@ -433,7 +567,11 @@ const Navbar = () => {
- @@ -445,22 +583,39 @@ const Navbar = () => { ) : ( // Masuk and Daftar buttons for roleId === null
- + {t("logIn")} - +
-

Kategori Registrasi

-

Silahkan pilih salah satu

+

+ Kategori Registrasi +

+

+ Silahkan pilih salah satu +

{role?.map((row: any) => (
- setCategory(event.target.value)} /> + setCategory(event.target.value)} + /> @@ -469,7 +624,11 @@ const Navbar = () => {
- + Selanjutnya{" "} @@ -488,7 +647,14 @@ const Navbar = () => { - + { - router.push(generateLocalizedPath("/video/filter", String(locale)))} className="flex items-start gap-1.5 p-2 hover:bg-white"> + + router.push( + generateLocalizedPath("/video/filter", String(locale)) + ) + } + className="flex items-start gap-1.5 p-2 hover:bg-white" + >

{t("video")}

- router.push(generateLocalizedPath("/audio/filter", String(locale)))} className="flex place-items-start gap-1.5 p-2 hover:bg-white"> + + router.push( + generateLocalizedPath("/audio/filter", String(locale)) + ) + } + className="flex place-items-start gap-1.5 p-2 hover:bg-white" + >

{t("audio")}

- router.push(generateLocalizedPath("/image/filter", String(locale)))} className="flex place-items-start gap-1.5 p-2 hover:bg-white"> + + router.push( + generateLocalizedPath("/image/filter", String(locale)) + ) + } + className="flex place-items-start gap-1.5 p-2 hover:bg-white" + >

{t("image")}

- router.push(generateLocalizedPath("/document/filter", String(locale)))} className="flex place-items-start gap-1.5 p-2 hover:bg-white"> + + router.push( + generateLocalizedPath( + "/document/filter", + String(locale) + ) + ) + } + className="flex place-items-start gap-1.5 p-2 hover:bg-white" + >

{t("text")} @@ -528,7 +725,14 @@ const Navbar = () => { - + { - + {

@@ -604,14 +818,22 @@ const Navbar = () => {
- +
{fullName ? ( <> - avatar-profile + avatar-profile

{fullName}

{`(${roleName})`}

@@ -619,20 +841,33 @@ const Navbar = () => {
- + {t("profile")} - + {t("contentManagement")} - - @@ -643,22 +878,41 @@ const Navbar = () => { ) : ( <> - + {t("logIn")} - +
-

Kategori Registrasi

-

Silahkan pilih salah satu

+

+ Kategori Registrasi +

+

+ Silahkan pilih salah satu +

{role?.map((row: any) => (
- setCategory(event.target.value)} /> + + setCategory(event.target.value) + } + /> @@ -667,7 +921,11 @@ const Navbar = () => {
- + Selanjutnya{" "} diff --git a/components/logo.tsx b/components/logo.tsx index ae258f42..2b940df4 100644 --- a/components/logo.tsx +++ b/components/logo.tsx @@ -30,7 +30,12 @@ const Logo = () => { {(!config?.collapsed || hovered) && (

D

)} */} - logo + logo ); }; diff --git a/components/partials/header/header-logo.tsx b/components/partials/header/header-logo.tsx index 067001fa..5cb63e2a 100644 --- a/components/partials/header/header-logo.tsx +++ b/components/partials/header/header-logo.tsx @@ -11,19 +11,23 @@ const HeaderLogo = () => { const isDesktop = useMediaQuery("(min-width: 1280px)"); return config.layout === "horizontal" ? ( - - -

- DashCode -

+ + logo ) : ( !isDesktop && ( - - -

- DashCode -

+ + logo ) ); diff --git a/components/partials/sidebar/menu/icon-nav.tsx b/components/partials/sidebar/menu/icon-nav.tsx index 620b4fe9..1644dd59 100644 --- a/components/partials/sidebar/menu/icon-nav.tsx +++ b/components/partials/sidebar/menu/icon-nav.tsx @@ -1,98 +1,112 @@ -'use client' -import React from 'react' +"use client"; +import React from "react"; import { ScrollArea } from "@/components/ui/scroll-area"; -import DashCodeLogo from '@/components/dascode-logo'; -import { Group, Submenu } from '@/lib/menus'; -import { Button } from '@/components/ui/button'; -import { Icon } from '@/components/ui/icon'; +import DashCodeLogo from "@/components/dascode-logo"; +import { Group, Submenu } from "@/lib/menus"; +import { Button } from "@/components/ui/button"; +import { Icon } from "@/components/ui/icon"; import { - Tooltip, - TooltipContent, - TooltipProvider, - TooltipTrigger, -} from "@/components/ui/tooltip" -import { Link } from '@/i18n/routing'; -import { cn } from '@/lib/utils'; -import { useConfig } from '@/hooks/use-config'; + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "@/components/ui/tooltip"; +import { Link } from "@/i18n/routing"; +import { cn } from "@/lib/utils"; +import { useConfig } from "@/hooks/use-config"; interface IconNavProps { - menuList: Group[] - + menuList: Group[]; } const IconNav = ({ menuList }: IconNavProps) => { + const [config, setConfig] = useConfig(); - const [config, setConfig] = useConfig(); + return ( +
+
+ +
+ + + +
+ ); +}; - return ( -
-
- -
- - - - -
- - ) -} - -export default IconNav \ No newline at end of file +export default IconNav; diff --git a/components/partials/sidebar/menu/sheet-menu.tsx b/components/partials/sidebar/menu/sheet-menu.tsx index a6947343..fe893380 100644 --- a/components/partials/sidebar/menu/sheet-menu.tsx +++ b/components/partials/sidebar/menu/sheet-menu.tsx @@ -1,14 +1,14 @@ -'use client' -import { Link } from '@/i18n/routing'; +"use client"; +import { Link } from "@/i18n/routing"; import { MenuIcon, PanelsTopLeft } from "lucide-react"; import { Icon } from "@/components/ui/icon"; import { Button } from "@/components/ui/button"; import { Menu } from "@/components/partials/sidebar/menu"; import { - Sheet, - SheetHeader, - SheetContent, - SheetTrigger, + Sheet, + SheetHeader, + SheetContent, + SheetTrigger, } from "@/components/ui/sheet"; import { MenuClassic } from "./menu-classic"; import DashCodeLogo from "@/components/dascode-logo"; @@ -17,33 +17,45 @@ import { useMediaQuery } from "@/hooks/use-media-query"; import { useConfig } from "@/hooks/use-config"; export function SheetMenu() { - const [mobileMenuConfig, setMobileMenuConfig] = useMobileMenuConfig(); - const [config, setConfig] = useConfig() - const { isOpen } = mobileMenuConfig; + const [mobileMenuConfig, setMobileMenuConfig] = useMobileMenuConfig(); + const [config, setConfig] = useConfig(); + const { isOpen } = mobileMenuConfig; - const isDesktop = useMediaQuery("(min-width: 1280px)"); - if (isDesktop) return null; - return ( - setMobileMenuConfig({ isOpen: !isOpen })}> - - - - - - - -

- DashCode -

- -
- -
-
- ); + const isDesktop = useMediaQuery("(min-width: 1280px)"); + if (isDesktop) return null; + return ( + setMobileMenuConfig({ isOpen: !isOpen })} + > + + + + + + + logo + + + + + + ); }