diff --git a/app/[locale]/(protected)/supervisor/ticketing/components/table.tsx b/app/[locale]/(protected)/supervisor/ticketing/components/table.tsx index 14969f62..a8ceead5 100644 --- a/app/[locale]/(protected)/supervisor/ticketing/components/table.tsx +++ b/app/[locale]/(protected)/supervisor/ticketing/components/table.tsx @@ -43,12 +43,9 @@ type Issue = { }; export default function TicketingLayout() { - // data & pagination const [issues, setIssues] = React.useState([]); const [totalElements, setTotalElements] = React.useState(0); const [totalPages, setTotalPages] = React.useState(1); - - // controls const [isSidebarOpen, setIsSidebarOpen] = React.useState(true); const [selectedIssue, setSelectedIssue] = React.useState(null); const [search, setSearch] = React.useState(""); @@ -60,8 +57,6 @@ export default function TicketingLayout() { const [selectedTicketId, setSelectedTicketId] = React.useState( null ); - - // selection const [selectedMap, setSelectedMap] = React.useState>( {} ); @@ -70,28 +65,22 @@ export default function TicketingLayout() { return issues.every((i) => selectedMap[String(i.id)]); }, [issues, selectedMap]); - // search debounce React.useEffect(() => { const t = setTimeout(() => { fetchData(); }, 450); return () => clearTimeout(t); - // eslint-disable-next-line react-hooks/exhaustive-deps }, [search, page, pageSize, sortOrder]); React.useEffect(() => { - // initial fetch fetchData(); - // eslint-disable-next-line react-hooks/exhaustive-deps }, []); async function fetchData() { try { - // note: existing API used earlier: ticketingPagination(search, Number(showData), page - 1) const res = await ticketingPagination(search, pageSize, page - 1); const data = res?.data?.data; const content = data?.content || []; - // map fields to Issue type (adjust if necessary) const mapped: Issue[] = content.map((it: any, idx: number) => ({ id: it.id ?? idx, title: it.title ?? it.subject ?? "No Title", @@ -103,8 +92,6 @@ export default function TicketingLayout() { setIssues(mapped); setTotalElements(data?.totalElements ?? 0); setTotalPages(data?.totalPages ?? 1); - - // keep selection map bounded to current items const newMap: Record = {}; mapped.forEach((m) => { newMap[String(m.id)] = Boolean(selectedMap[String(m.id)]); @@ -115,7 +102,6 @@ export default function TicketingLayout() { } } - // select all toggle function toggleSelectAll() { const next: Record = {}; if (!allSelected) { @@ -148,19 +134,16 @@ export default function TicketingLayout() { return { label: source, short: source.slice(0, 2).toUpperCase() }; } - // Chat actions const [chatMessages, setChatMessages] = React.useState< { id: string; from: "user" | "agent"; text: string; time?: string }[] >([]); const [replyText, setReplyText] = React.useState(""); React.useEffect(() => { - // clear chatMessages when selecting new issue and optionally load messages for issue if (!selectedIssue) { setChatMessages([]); return; } - // For demo: populate sample messages (replace by real API call) setChatMessages([ { id: "m1", @@ -194,7 +177,6 @@ export default function TicketingLayout() { ]); setReplyText(""); if (closeAfter) { - // emulate resolve + close bubble setTimeout(() => { setSelectedIssue(null); }, 300); @@ -202,9 +184,7 @@ export default function TicketingLayout() { } function handleTranslate() { - // placeholder: you can call your translate API here if (!replyText.trim()) return; - // naive "translation" demo: append "(translated)" setReplyText((t) => t + " (translated)"); } @@ -235,7 +215,19 @@ export default function TicketingLayout() { !isSidebarOpen && "hidden" )} > - Filters +
+ + All New Issues + + + {totalElements} + +
+ {levelNumber !== "2" && levelNumber !== "3" && ( + + )}
diff --git a/components/form/content/image-form.tsx b/components/form/content/image-form.tsx index 64cebfa8..814327a3 100644 --- a/components/form/content/image-form.tsx +++ b/components/form/content/image-form.tsx @@ -88,6 +88,8 @@ export default function FormImage() { const MySwal = withReactContent(Swal); const router = useRouter(); const editor = useRef(null); + const levelNumber = getCookiesDecrypt("ulne"); + const roleId = getCookiesDecrypt("urie"); type ImageSchema = z.infer; const params = useParams(); const locale = params?.locale; @@ -96,7 +98,6 @@ export default function FormImage() { 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 [selectedCategory, setSelectedCategory] = useState(); @@ -1233,37 +1234,39 @@ export default function FormImage() { {t("description", { defaultValue: "Description" })} - + }} + className="px-3 py-2 bg-blue-500 text-white rounded-md hover:bg-blue-600" + > + {isLoadingTranslate + ? "Translating..." + : "Translate to English"} + + )}
{/* Editor */} @@ -1273,9 +1276,9 @@ export default function FormImage() { render={({ field: { onChange, value } }) => ( { - onChange(val); - setLocalContent(val); - setEditorContent(val); + onChange(val); + setLocalContent(val); + setEditorContent(val); }} initialData={localContent || value} /> @@ -1616,9 +1619,14 @@ export default function FormImage() { {/* button submit */}
- + */} + {levelNumber !== "2" && levelNumber !== "3" && ( + + )}
diff --git a/components/form/content/image-update-form.tsx b/components/form/content/image-update-form.tsx index b056e3cd..b22603b7 100644 --- a/components/form/content/image-update-form.tsx +++ b/components/form/content/image-update-form.tsx @@ -50,6 +50,16 @@ import { Upload } from "tus-js-client"; import { useTranslations } from "next-intl"; import { Link } from "@/i18n/routing"; import { htmlToString } from "@/utils/globals"; +import { getUserLevelForAssignments } from "@/service/task"; +import { + Dialog, + DialogClose, + DialogContent, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@/components/ui/dialog"; +import { v4 as uuidv4 } from "uuid"; const imageSchema = z.object({ title: z.string().min(1, { message: "Judul diperlukan" }), @@ -112,9 +122,21 @@ const CustomEditor = dynamic( ); interface FileWithPreview extends File { + id: string; preview: string; } +interface Destination { + id: string; + name: string; + subDestination?: SubDestination[]; +} + +interface SubDestination { + id: string; + name: string; +} + export default function FormImageUpdate() { const MySwal = withReactContent(Swal); const router = useRouter(); @@ -155,6 +177,525 @@ export default function FormImageUpdate() { setThumbnailFile(file); } }; + const [fileUnitSelections, setFileUnitSelections] = useState< + Array<{ + semua: boolean; + nasional: boolean; + wilayah: boolean; + international: boolean; + polda: boolean; + polres: boolean; + satker: boolean; + }> + >([]); + // State global untuk kompatibilitas (akan dihapus nanti) + const [unitSelection, setUnitSelection] = useState({ + semua: false, + nasional: false, + wilayah: false, + international: false, + polda: false, + polres: false, + satker: false, + }); + const [checkedLevels, setCheckedLevels] = useState>(new Set()); + const [listDest, setListDest] = useState([]); + const [isLoading, setIsLoading] = useState(false); + const [expandedPolda, setExpandedPolda] = useState>( + {} + ); + const [fileCheckedLevels, setFileCheckedLevels] = useState< + Array> + >([]); + const [isUpdatingFromMainCheckbox, setIsUpdatingFromMainCheckbox] = + useState(false); + const [mainCheckboxChangeType, setMainCheckboxChangeType] = + useState(""); + + useEffect(() => { + async function fetchPoldaPolres() { + setIsLoading(true); + try { + const response = await getUserLevelForAssignments(); + setListDest(response?.data?.data.list); + const initialExpandedState = response?.data?.data.list.reduce( + (acc: any, polda: any) => { + acc[polda.id] = false; + return acc; + }, + {} + ); + setExpandedPolda(initialExpandedState); + console.log("polres", initialExpandedState); + } catch (error) { + console.error("Error fetching Polda/Polres data:", error); + } finally { + setIsLoading(false); + } + } + fetchPoldaPolres(); + }, []); + + // useEffect untuk sinkronisasi checkbox modal dengan checkbox utama + useEffect(() => { + if ( + listDest.length > 0 && + isUpdatingFromMainCheckbox && + mainCheckboxChangeType + ) { + syncModalWithMainCheckbox(); + } + }, [isUpdatingFromMainCheckbox, mainCheckboxChangeType]); + + // useEffect untuk update checkbox utama ketika pilihan modal berubah + useEffect(() => { + if (!isUpdatingFromMainCheckbox && listDest.length > 0) { + updateMainCheckboxFromModalLegacy(); + } + }, [checkedLevels, isUpdatingFromMainCheckbox]); + + // Fungsi untuk update checkbox utama berdasarkan checkbox modal (global/legacy) + const updateMainCheckboxFromModalLegacy = () => { + if (!isUpdatingFromMainCheckbox && listDest.length > 0) { + // Hitung item yang dipilih berdasarkan checkedLevels + const checkedPoldaCount = listDest.filter( + (item: any) => + item.levelNumber === 2 && + item.name !== "SATKER POLRI" && + checkedLevels.has(Number(item.id)) + ).length; + + const checkedPolresCount = listDest.reduce((total: number, item: any) => { + if (item.subDestination && item.name !== "SATKER POLRI") { + // Hanya hitung sub-item dari POLDA (bukan dari SATKER POLRI) + return ( + total + + item.subDestination.filter((sub: any) => + checkedLevels.has(Number(sub.id)) + ).length + ); + } + return total; + }, 0); + + const satkerItem: any = listDest.find( + (item: any) => item.name === "SATKER POLRI" + ); + const checkedSatkerCount = satkerItem + ? (checkedLevels.has(Number(satkerItem.id)) ? 1 : 0) + + (satkerItem.subDestination?.filter((sub: any) => + checkedLevels.has(Number(sub.id)) + ).length || 0) + : 0; + + // Checkbox aktif jika ADA item yang dipilih dalam kategori tersebut + const hasSelectedPolda = checkedPoldaCount > 0; + const hasSelectedPolres = checkedPolresCount > 0; + const hasSelectedSatker = checkedSatkerCount > 0; + + // Update unitSelection berdasarkan yang dipilih di modal + setUnitSelection((prev) => { + const newState = { ...prev }; + + // Update individual checkboxes + newState.polda = hasSelectedPolda; + newState.polres = hasSelectedPolres; + newState.satker = hasSelectedSatker; + + // Update checkbox "semua" berdasarkan semua checkbox yang aktif + newState.semua = + newState.nasional && + newState.wilayah && + newState.international && + hasSelectedPolda && + hasSelectedPolres && + hasSelectedSatker; + + return newState; + }); + } + }; + + // Fungsi untuk sinkronisasi checkbox modal dengan checkbox utama + const syncModalWithMainCheckbox = () => { + if (isUpdatingFromMainCheckbox) { + const newCheckedLevels = new Set(checkedLevels); + + // Handle checklist actions - menambahkan semua item ke modal + if (mainCheckboxChangeType === "polda_checked") { + // Checklist semua polda + listDest.forEach((item: any) => { + if (item.levelNumber === 2 && item.name !== "SATKER POLRI") { + newCheckedLevels.add(Number(item.id)); + } + }); + } else if (mainCheckboxChangeType === "polres_checked") { + // Checklist semua polres, tapi hanya yang poldanya sudah di-checklist + // Jangan checklist sub-item SATKER POLRI + listDest.forEach((item: any) => { + if ( + item.levelNumber === 2 && + item.name !== "SATKER POLRI" && + newCheckedLevels.has(Number(item.id)) + ) { + if (item.subDestination) { + item.subDestination.forEach((polres: any) => { + newCheckedLevels.add(Number(polres.id)); + }); + } + } + }); + + // Tidak perlu menghapus SATKER ketika POLRES di-checklist + // Biarkan keduanya bisa aktif bersamaan + // SATKER dan POLRES adalah konsep yang berbeda: + // - SATKER: unit-unit seperti ITWASUM, BAINTELKAM, dll. + // - POLRES: unit-unit seperti POLRES METRO JAKARTA PUSAT, dll. + } else if (mainCheckboxChangeType === "satker_checked") { + // Checklist satker + const satkerItem: any = listDest.find( + (item: any) => item.name === "SATKER POLRI" + ); + if (satkerItem) { + newCheckedLevels.add(Number(satkerItem.id)); + // Checklist semua sub-item yang ada di bawah SATKER (bukan POLRES) + if (satkerItem.subDestination) { + satkerItem.subDestination.forEach((sub: any) => { + newCheckedLevels.add(Number(sub.id)); + }); + } + } + } else if (mainCheckboxChangeType === "semua_checked") { + // Checklist semua item + listDest.forEach((item: any) => { + newCheckedLevels.add(Number(item.id)); + // Checklist semua sub-item di bawah setiap item + if (item.subDestination) { + item.subDestination.forEach((sub: any) => { + newCheckedLevels.add(Number(sub.id)); + }); + } + }); + } + // Handle unchecklist actions - menghapus item dari modal + else if (mainCheckboxChangeType === "polres_unchecked") { + // Clear polres dari checkedLevels, tapi jangan hapus sub-item SATKER POLRI + listDest.forEach((item: any) => { + if (item.subDestination && item.name !== "SATKER POLRI") { + item.subDestination.forEach((polres: any) => { + newCheckedLevels.delete(Number(polres.id)); + }); + } + }); + } else if (mainCheckboxChangeType === "polda_unchecked") { + // Clear polda dan polres dari checkedLevels, tapi jangan hapus SATKER POLRI + listDest.forEach((item: any) => { + if (item.levelNumber === 2 && item.name !== "SATKER POLRI") { + newCheckedLevels.delete(Number(item.id)); + if (item.subDestination) { + item.subDestination.forEach((polres: any) => { + newCheckedLevels.delete(Number(polres.id)); + }); + } + } + }); + } else if (mainCheckboxChangeType === "satker_unchecked") { + // Clear satker dan semua sub-item di bawahnya dari checkedLevels + const satkerItem: any = listDest.find( + (item: any) => item.name === "SATKER POLRI" + ); + if (satkerItem) { + newCheckedLevels.delete(Number(satkerItem.id)); + if (satkerItem.subDestination) { + satkerItem.subDestination.forEach((sub: any) => { + newCheckedLevels.delete(Number(sub.id)); + }); + } + } + } else if (mainCheckboxChangeType === "semua_unchecked") { + // Clear semua + newCheckedLevels.clear(); + } + + setCheckedLevels(newCheckedLevels); + + // Reset flag setelah sinkronisasi selesai + setIsUpdatingFromMainCheckbox(false); + setMainCheckboxChangeType(""); + } + }; + + // Fungsi untuk mengupdate state individual file + const handleFileUnitChange = ( + fileIndex: number, + key: keyof typeof unitSelection, + value: boolean + ) => { + setFileUnitSelections((prev) => { + const newSelections = [...prev]; + const currentSelection = { ...newSelections[fileIndex] }; + + if (key === "semua") { + // Jika klik Semua, set semua value ke true/false + currentSelection.semua = value; + currentSelection.nasional = value; + currentSelection.wilayah = value; + currentSelection.international = value; + currentSelection.polda = value; + currentSelection.polres = value; + currentSelection.satker = value; + + // Update fileCheckedLevels untuk sinkronisasi dengan modal + setFileCheckedLevels((prevLevels) => { + const newArray = [...prevLevels]; + const currentFileLevels = new Set( + newArray[fileIndex] || new Set() + ); + + if (value) { + // Checklist semua item di modal + listDest.forEach((item: any) => { + currentFileLevels.add(Number(item.id)); + if (item.subDestination) { + item.subDestination.forEach((sub: any) => { + currentFileLevels.add(Number(sub.id)); + }); + } + }); + } else { + // Unchecklist semua item di modal + currentFileLevels.clear(); + } + + newArray[fileIndex] = currentFileLevels; + return newArray; + }); + } else { + // Validasi khusus untuk POLRES - harus ada POLDA yang ter-checklist + if (key === "polres" && value) { + const currentFileCheckedLevels = fileCheckedLevels[fileIndex]; + const hasSelectedPolda = + currentFileCheckedLevels && + listDest.some( + (item: any) => + item.levelNumber === 2 && + item.name !== "SATKER POLRI" && + currentFileCheckedLevels.has(Number(item.id)) + ); + + if (!hasSelectedPolda) { + alert( + "Harap pilih POLDA di Modal terlebih dahulu sebelum mengaktifkan checkbox POLRES." + ); + return prev; // Batalkan perubahan + } + } + + // Update salah satu saja + currentSelection[key] = value; + + // Cek apakah semua selain "semua" sudah dicentang + const allChecked = [ + "nasional", + "wilayah", + "international", + "polda", + "polres", + "satker", + ].every((k) => currentSelection[k as keyof typeof unitSelection]); + + currentSelection.semua = allChecked; + } + + newSelections[fileIndex] = currentSelection; + return newSelections; + }); + }; + + // Fungsi untuk mengupdate checklist levels untuk file tertentu + const handleFileCheckboxChangePlacement = ( + fileIndex: number, + levelId: number + ) => { + setFileCheckedLevels((prev) => { + const newArray = [...prev]; + const currentFileLevels = new Set(newArray[fileIndex]); + const isCurrentlyChecked = currentFileLevels.has(levelId); + + if (isCurrentlyChecked) { + currentFileLevels.delete(levelId); + + // Jika ini adalah POLDA yang di-unchecklist, unchecklist juga semua polres di bawahnya + const poldaItem = listDest.find( + (item: any) => Number(item.id) === levelId + ) as any; + if ( + poldaItem && + poldaItem.subDestination && + poldaItem.name !== "SATKER POLRI" + ) { + poldaItem.subDestination.forEach((polres: any) => { + currentFileLevels.delete(Number(polres.id)); + }); + } + + // Jika ini adalah SATKER POLRI yang di-unchecklist, unchecklist juga semua sub-item di bawahnya + if (poldaItem && poldaItem.name === "SATKER POLRI") { + poldaItem.subDestination?.forEach((subItem: any) => { + currentFileLevels.delete(Number(subItem.id)); + }); + } + } else { + currentFileLevels.add(levelId); + + // Jika ini adalah SATKER POLRI yang di-checklist, checklist juga semua sub-item di bawahnya + const satkerItem = listDest.find( + (item: any) => Number(item.id) === levelId + ) as any; + if (satkerItem && satkerItem.name === "SATKER POLRI") { + // Checklist semua sub-item di bawah SATKER POLRI (bukan POLRES) + satkerItem.subDestination?.forEach((subItem: any) => { + currentFileLevels.add(Number(subItem.id)); + }); + } + } + + newArray[fileIndex] = currentFileLevels; + + // Update checkbox utama berdasarkan perubahan di modal + // Pindahkan ke sini agar state sudah ter-update + setTimeout(() => updateMainCheckboxFromModal(fileIndex), 0); + + return newArray; + }); + }; + + // Fungsi untuk mengupdate checkbox utama berdasarkan checklist di modal + const updateMainCheckboxFromModal = (fileIndex: number) => { + setFileUnitSelections((prev) => { + const newSelections = [...prev]; + const currentSelection = { ...newSelections[fileIndex] }; + const currentFileLevels = fileCheckedLevels[fileIndex]; + + if (!currentFileLevels) return prev; + + // Hitung total POLDA yang ada (bukan SATKER POLRI) + const totalPoldaCount = listDest.filter( + (item: any) => item.levelNumber === 2 && item.name !== "SATKER POLRI" + ).length; + + // Hitung berapa banyak POLDA yang ter-checklist (bukan SATKER POLRI) + const checkedPoldaCount = listDest.reduce((total: number, item: any) => { + if ( + item.levelNumber === 2 && + item.name !== "SATKER POLRI" && + currentFileLevels.has(Number(item.id)) + ) { + return total + 1; + } + return total; + }, 0); + + // Hitung total POLRES yang ada dari POLDA yang ter-checklist + const totalPolresFromCheckedPolda = listDest.reduce( + (total: number, item: any) => { + if ( + item.subDestination && + item.name !== "SATKER POLRI" && + currentFileLevels.has(Number(item.id)) + ) { + return total + item.subDestination.length; + } + return total; + }, + 0 + ); + + // Hitung berapa banyak POLRES yang ter-checklist + const checkedPolresCount = listDest.reduce((total: number, item: any) => { + if (item.subDestination && item.name !== "SATKER POLRI") { + // Hanya hitung sub-item dari POLDA (bukan dari SATKER POLRI) + return ( + total + + item.subDestination.filter((sub: any) => + currentFileLevels.has(Number(sub.id)) + ).length + ); + } + return total; + }, 0); + + // Cek apakah SATKER POLRI ter-checklist + const satkerItem = listDest.find( + (item: any) => item.name === "SATKER POLRI" + ); + const isSatkerChecked = + satkerItem && currentFileLevels.has(Number(satkerItem.id)); + + // Update checkbox berdasarkan kondisi + // POLDA aktif jika ada minimal 1 POLDA ter-checklist + currentSelection.polda = checkedPoldaCount > 0; + // POLRES aktif jika ada minimal 1 POLRES ter-checklist + currentSelection.polres = checkedPolresCount > 0; + currentSelection.satker = Boolean(isSatkerChecked); + + // Update checkbox "semua" berdasarkan semua checkbox yang aktif + currentSelection.semua = + currentSelection.nasional && + currentSelection.wilayah && + currentSelection.international && + currentSelection.polda && + currentSelection.polres && + currentSelection.satker; + + newSelections[fileIndex] = currentSelection; + return newSelections; + }); + }; + + const toggleExpand = (id: number) => { + setExpandedPolda((prev) => ({ + ...prev, + [id]: !prev[id], + })); + }; + + // Fungsi untuk menangani "Pilih Semua" sub-items di bawah POLDA + const handleSelectAllSubItems = (fileIndex: number, polda: any) => { + setFileCheckedLevels((prev) => { + const newArray = [...prev]; + const currentFileLevels = new Set(newArray[fileIndex]); + + // Cek apakah semua sub-items sudah ter-checklist + const allSubItemsChecked = polda.subDestination?.every((sub: any) => + currentFileLevels.has(Number(sub.id)) + ); + + if (allSubItemsChecked) { + // Jika semua sudah ter-checklist, unchecklist semuanya + polda.subDestination?.forEach((sub: any) => { + currentFileLevels.delete(Number(sub.id)); + }); + } else { + // Jika belum semua ter-checklist, checklist semuanya + // Checklist POLDA juga jika belum ter-checklist + if (!currentFileLevels.has(Number(polda.id))) { + currentFileLevels.add(Number(polda.id)); + } + // Checklist semua sub-items + polda.subDestination?.forEach((sub: any) => { + currentFileLevels.add(Number(sub.id)); + }); + } + + newArray[fileIndex] = currentFileLevels; + + // Update checkbox utama berdasarkan perubahan di modal + setTimeout(() => updateMainCheckboxFromModal(fileIndex), 0); + + return newArray; + }); + }; const options: Option[] = [ { id: "all", name: "SEMUA" }, @@ -167,22 +708,22 @@ export default function FormImageUpdate() { const [selectedTarget, setSelectedTarget] = useState( detail?.category.id ); - const [unitSelection, setUnitSelection] = useState({ - allUnit: false, - mabes: false, - polda: false, - polres: false, - }); + // const [unitSelection, setUnitSelection] = useState({ + // allUnit: false, + // mabes: false, + // polda: false, + // polres: false, + // }); let fileTypeId = "1"; const { getRootProps, getInputProps } = useDropzone({ onDrop: (acceptedFiles) => { - // setFiles(acceptedFiles.map((file) => Object.assign(file))); setFiles((prevFiles) => [ ...prevFiles, ...acceptedFiles.map((file) => Object.assign(file, { + id: uuidv4(), // generate unique id preview: URL.createObjectURL(file), }) ), @@ -193,6 +734,23 @@ export default function FormImageUpdate() { }, }); + // const { getRootProps, getInputProps } = useDropzone({ + // onDrop: (acceptedFiles) => { + // // setFiles(acceptedFiles.map((file) => Object.assign(file))); + // setFiles((prevFiles) => [ + // ...prevFiles, + // ...acceptedFiles.map((file) => + // Object.assign(file, { + // preview: URL.createObjectURL(file), + // }) + // ), + // ]); + // }, + // accept: { + // "image/*": [], + // }, + // }); + const { control, handleSubmit, @@ -357,6 +915,33 @@ export default function FormImageUpdate() { } }; + const [filePlacements, setFilePlacements] = useState([]); + + const getPlacement = () => { + const temp = []; + for (let i = 0; i < filePlacements?.length; i++) { + if (filePlacements[i]?.length !== 0) { + const now = filePlacements[i]; + let nowArr = now?.join(",")?.replaceAll("wilayah", "polda"); + nowArr = nowArr?.replaceAll("nasional", "mabes"); + nowArr = nowArr?.replaceAll("semua", "all"); + + // Dapatkan checked levels untuk file ini + const currentFileCheckedLevels = fileCheckedLevels[i] + ? Array.from(fileCheckedLevels[i]) + : []; + + const data = { + mediaFileId: files[i]?.id, + placements: nowArr, + customLocationPlacements: currentFileCheckedLevels.join(","), + }; + temp.push(data); + } + } + return temp; + }; + const save = async (data: ImageSchema) => { loading(); const finalTags = tags.join(", "); @@ -376,37 +961,36 @@ export default function FormImageUpdate() { tags: finalTags, isYoutube: false, isInternationalMedia: false, + files: getPlacement(), }; + console.log("Form Data Submitted:", requestData); const response = await createMedia(requestData); - // console.log("Form Data Submitted:", requestData); if (response?.error) { error(response?.message); return false; } + // Upload thumbnail const formMedia = new FormData(); const thumbnail = thumbnailFile || files[0]; formMedia.append("file", thumbnail); - const responseThumbnail = await uploadThumbnail(id, formMedia); - if (responseThumbnail?.error == true) { + if (responseThumbnail?.error) { error(responseThumbnail?.message); return false; } - const progressInfoArr = []; - for (const item of files) { - progressInfoArr.push({ percentage: 0, fileName: item.name }); - } - progressInfo = progressInfoArr; + // Upload files (progress) + const progressInfoArr = files.map((item) => ({ + percentage: 0, + fileName: item.name, + })); setIsStartUpload(true); setProgressList(progressInfoArr); close(); - // showProgress(); - // console.log("files:", files); files.map(async (item: any, index: number) => { await uploadResumableFile( index, @@ -668,65 +1252,282 @@ export default function FormImageUpdate() { } const [tempFile, setTempFile] = useState([]); - const [filePlacements, setFilePlacements] = useState([]); + const setupPlacement = ( + index: number, + placement: string, + checked: boolean + ) => { + let temp = [...filePlacements]; + if (checked) { + if (placement === "all") { + temp[index] = ["all", "mabes", "polda", "international"]; - const setupPlacement = (id: number | string, placement: PlacementType) => { - console.log(`FileDestination.leng:: ${id}_${placement}`); - const arrayFile: FilePlacement[] = []; + // Update fileCheckedLevels untuk sinkronisasi dengan modal ketika "all" diklik + setFileCheckedLevels((prevLevels) => { + const newArray = [...prevLevels]; + const currentFileLevels = new Set( + newArray[index] || new Set() + ); - for (let i = 0; i < tempFile?.length; i++) { - const element = tempFile[i]; - - if (element.id == id) { - const findPlacementIdx = filePlacements.findIndex( - (o) => Number(o.mediaFileId) === Number(id) - ); - - if (findPlacementIdx > -1) { - const findPlacement = filePlacements[findPlacementIdx]; - - if (findPlacement?.placements?.includes(placement)) { - if (placement === "all") { - findPlacement.placements = undefined; - } else { - findPlacement.placements = findPlacement.placements.filter( - (val) => val !== placement - ); - if (findPlacement.placements?.includes("all")) { - findPlacement.placements = findPlacement.placements.filter( - (val) => val !== "all" - ); - } + // Checklist semua item di modal + listDest.forEach((item: any) => { + currentFileLevels.add(Number(item.id)); + if (item.subDestination) { + item.subDestination.forEach((sub: any) => { + currentFileLevels.add(Number(sub.id)); + }); } - } else if (placement === "all") { - findPlacement.placements = [ - "all", - "mabes", - "polda", - "international", - ]; - } else if (findPlacement.placements) { - findPlacement.placements = [...findPlacement.placements, placement]; - } else { - findPlacement.placements = [placement]; - } - } else { - const file: FilePlacement = { - mediaFileId: Number(element.id), - placements: [placement], - }; + }); - arrayFile.push(file); + newArray[index] = currentFileLevels; + return newArray; + }); + + // Update fileUnitSelections untuk checkbox tingkat utama + setFileUnitSelections((prevSelections) => { + const newSelections = [...prevSelections]; + const currentSelection = { ...newSelections[index] }; + + // Set semua checkbox tingkat utama ke true + currentSelection.nasional = true; + currentSelection.wilayah = true; + currentSelection.international = true; + currentSelection.polda = true; + currentSelection.polres = true; + currentSelection.satker = true; + currentSelection.semua = true; + + newSelections[index] = currentSelection; + return newSelections; + }); + } else if (placement === "satker") { + // Ketika satker di-checklist, HANYA tambahkan satker saja + // JANGAN otomatis checklist polres + const now = temp[index] || []; + if (!now.includes("satker")) { + now.push("satker"); + } + temp[index] = now; + } else { + const now = temp[index] || []; + if (!now.includes(placement)) { + now.push(placement); + } + // Hanya auto-checklist "all" jika polda, polres, dan mabes ter-checklist + // JANGAN include satker dalam perhitungan auto "all" + const nonSatkerItems = now.filter( + (item) => item !== "satker" && item !== "all" + ); + if (nonSatkerItems.length === 3 && !now.includes("all")) { + now.push("all"); + } + temp[index] = now; + } + } else { + if (placement === "all") { + temp[index] = []; + + // Update fileCheckedLevels untuk sinkronisasi dengan modal ketika "all" di-unchecklist + setFileCheckedLevels((prevLevels) => { + const newArray = [...prevLevels]; + const currentFileLevels = new Set( + newArray[index] || new Set() + ); + + // Unchecklist semua item di modal + currentFileLevels.clear(); + + newArray[index] = currentFileLevels; + return newArray; + }); + + // Update fileUnitSelections untuk checkbox tingkat utama + setFileUnitSelections((prevSelections) => { + const newSelections = [...prevSelections]; + const currentSelection = { ...newSelections[index] }; + + // Set semua checkbox tingkat utama ke false + currentSelection.nasional = false; + currentSelection.wilayah = false; + currentSelection.international = false; + currentSelection.polda = false; + currentSelection.polres = false; + currentSelection.satker = false; + currentSelection.semua = false; + + newSelections[index] = currentSelection; + return newSelections; + }); + } else { + const now = temp[index]?.filter((a) => a !== placement); + console.log("now", now); + temp[index] = now; + // Hapus "all" jika tidak semua item ter-checklist + if (now.includes("all")) { + const nonSatkerItems = now.filter( + (item) => item !== "satker" && item !== "all" + ); + if (nonSatkerItems.length < 3) { + const newData = now.filter((b) => b !== "all"); + temp[index] = newData; + } } } } + setFilePlacements(temp); - const finalPlacements = [...filePlacements, ...arrayFile]; - setFilePlacements(finalPlacements); - console.log("FileDestination.leng::", finalPlacements); + // Update checklist levels di modal berdasarkan placement yang diubah + updateModalChecklistLevels(index, placement, checked); }; + // Fungsi untuk mengupdate checklist levels di modal berdasarkan placement + const updateModalChecklistLevels = ( + fileIndex: number, + placement: string, + checked: boolean + ) => { + if (!listDest || listDest.length === 0) return; + + setFileCheckedLevels((prev) => { + const newArray = [...prev]; + const currentFileLevels = new Set(newArray[fileIndex]); + + if (checked) { + if (placement === "polda") { + // Checklist semua POLDA (bukan SATKER POLRI) + listDest.forEach((item: any) => { + if (item.levelNumber === 2 && item.name !== "SATKER POLRI") { + currentFileLevels.add(Number(item.id)); + } + }); + } else if (placement === "polres") { + // Checklist POLRES hanya dari POLDA yang sudah ter-checklist + listDest.forEach((item: any) => { + if (item.levelNumber === 2 && item.name !== "SATKER POLRI") { + // Hanya checklist POLRES jika POLDA-nya sudah ter-checklist + if (currentFileLevels.has(Number(item.id))) { + if (item.subDestination) { + item.subDestination.forEach((polres: any) => { + currentFileLevels.add(Number(polres.id)); + }); + } + } + } + }); + } else if (placement === "satker") { + // Checklist SATKER POLRI dan semua sub-item di bawahnya + const satkerItem: any = listDest.find( + (item: any) => item.name === "SATKER POLRI" + ); + if (satkerItem) { + currentFileLevels.add(Number(satkerItem.id)); + if (satkerItem.subDestination) { + satkerItem.subDestination.forEach((sub: any) => { + currentFileLevels.add(Number(sub.id)); + }); + } + } + } + } else { + if (placement === "polda") { + // Unchecklist semua POLDA dan POLRES di bawahnya + listDest.forEach((item: any) => { + if (item.levelNumber === 2 && item.name !== "SATKER POLRI") { + currentFileLevels.delete(Number(item.id)); + if (item.subDestination) { + item.subDestination.forEach((polres: any) => { + currentFileLevels.delete(Number(polres.id)); + }); + } + } + }); + } else if (placement === "polres") { + // Unchecklist semua POLRES + listDest.forEach((item: any) => { + if (item.subDestination && item.name !== "SATKER POLRI") { + item.subDestination.forEach((polres: any) => { + currentFileLevels.delete(Number(polres.id)); + }); + } + }); + } else if (placement === "satker") { + // Unchecklist SATKER POLRI dan semua sub-item di bawahnya + const satkerItem: any = listDest.find( + (item: any) => item.name === "SATKER POLRI" + ); + if (satkerItem) { + currentFileLevels.delete(Number(satkerItem.id)); + if (satkerItem.subDestination) { + satkerItem.subDestination.forEach((sub: any) => { + currentFileLevels.delete(Number(sub.id)); + }); + } + } + } + } + + newArray[fileIndex] = currentFileLevels; + return newArray; + }); + }; + + // const setupPlacement = (id: number | string, placement: PlacementType) => { + // console.log(`FileDestination.leng:: ${id}_${placement}`); + // const arrayFile: FilePlacement[] = []; + + // for (let i = 0; i < tempFile?.length; i++) { + // const element = tempFile[i]; + + // if (element.id == id) { + // const findPlacementIdx = filePlacements.findIndex( + // (o) => Number(o.mediaFileId) === Number(id) + // ); + + // if (findPlacementIdx > -1) { + // const findPlacement = filePlacements[findPlacementIdx]; + + // if (findPlacement?.placements?.includes(placement)) { + // if (placement === "all") { + // findPlacement.placements = undefined; + // } else { + // findPlacement.placements = findPlacement.placements.filter( + // (val) => val !== placement + // ); + // if (findPlacement.placements?.includes("all")) { + // findPlacement.placements = findPlacement.placements.filter( + // (val) => val !== "all" + // ); + // } + // } + // } else if (placement === "all") { + // findPlacement.placements = [ + // "all", + // "mabes", + // "polda", + // "international", + // ]; + // } else if (findPlacement.placements) { + // findPlacement.placements = [...findPlacement.placements, placement]; + // } else { + // findPlacement.placements = [placement]; + // } + // } else { + // const file: FilePlacement = { + // mediaFileId: Number(element.id), + // placements: [placement], + // }; + + // arrayFile.push(file); + // } + // } + // } + + // const finalPlacements = [...filePlacements, ...arrayFile]; + // setFilePlacements(finalPlacements); + // console.log("FileDestination.leng::", finalPlacements); + // }; + const handleCheckboxChangeImage = (fileId: number, value: string) => { setSelectedOptions((prev: any) => { const currentSelections = prev[fileId] || []; @@ -948,7 +1749,7 @@ export default function FormImageUpdate() { {t("file-media", { defaultValue: "File Media" })}
- {files.map((file: any) => ( + {files.map((file: any, index: any) => (
-
-
diff --git a/components/form/content/teks-form.tsx b/components/form/content/teks-form.tsx index 53a877f4..6c4083f2 100644 --- a/components/form/content/teks-form.tsx +++ b/components/form/content/teks-form.tsx @@ -83,6 +83,7 @@ export default function FormTeks() { const MySwal = withReactContent(Swal); const router = useRouter(); const editor = useRef(null); + const levelNumber = getCookiesDecrypt("ulne"); type TeksSchema = z.infer; const params = useParams(); const locale = params?.locale; @@ -1488,9 +1489,14 @@ export default function FormTeks() {
- + */} + {levelNumber !== "2" && levelNumber !== "3" && ( + + )}
diff --git a/components/form/content/video-form.tsx b/components/form/content/video-form.tsx index 71a2ff59..4b1cc91f 100644 --- a/components/form/content/video-form.tsx +++ b/components/form/content/video-form.tsx @@ -83,6 +83,7 @@ export default function FormVideo() { const MySwal = withReactContent(Swal); const router = useRouter(); const editor = useRef(null); + const levelNumber = getCookiesDecrypt("ulne"); type VideoSchema = z.infer; const params = useParams(); const locale = params?.locale; @@ -1511,9 +1512,14 @@ export default function FormVideo() {
- + */} + {levelNumber !== "2" && levelNumber !== "3" && ( + + )}
diff --git a/components/form/ticketing/ticketing-detail-form.tsx b/components/form/ticketing/ticketing-detail-form.tsx index cfdc9ec9..ef31dbf5 100644 --- a/components/form/ticketing/ticketing-detail-form.tsx +++ b/components/form/ticketing/ticketing-detail-form.tsx @@ -637,9 +637,8 @@ // } "use client"; -import React, { useEffect, useState } from "react"; +import React, { useEffect, useRef, useState } from "react"; import { Button } from "@/components/ui/button"; -import { useMediaQuery } from "react-responsive"; import { getTicketingDetail, getTicketingReply, @@ -664,32 +663,39 @@ export default function FormDetailTicketing({ id }: Props) { const [detail, setDetail] = useState(null); const [ticketReply, setTicketReply] = useState([]); const [replyText, setReplyText] = useState(""); + const chatEndRef = useRef(null); useEffect(() => { async function init() { const resDetail = await getTicketingDetail(id); setDetail(resDetail?.data?.data); - await fetchReplies(); } init(); }, [id]); + useEffect(() => { + chatEndRef.current?.scrollIntoView({ behavior: "smooth" }); + }, [ticketReply]); + async function fetchReplies() { const res = await getTicketingReply(id); if (res?.data !== null) { - setTicketReply(res?.data?.data); + const sortedReplies = res?.data.data.sort( + (a: replyDetail, b: replyDetail) => + new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime() + ); + setTicketReply(sortedReplies); } } const sendReply = async (resolve: boolean) => { if (!replyText.trim()) return; - try { await saveTicketReply({ ticketId: id, comment: replyText, - resolve, // tambahan param kalau API butuh + resolve, }); setReplyText(""); await fetchReplies(); @@ -699,35 +705,26 @@ export default function FormDetailTicketing({ id }: Props) { }; const handleTranslate = () => { - // nanti bisa dihubungkan ke API translate MySwal.fire("Info", "Fitur translate belum dihubungkan", "info"); }; return (
{/* Header */} -
-
-
- {detail ? ( -
-
- {(detail?.title ?? "").split(" ").slice(0, 25).join(" ") + - ((detail?.title ?? "").split(" ").length > 25 ? "..." : "")} -
-
- • {detail?.source ?? "Ticket"} -
-
- ) : ( -
Chat
- )} +
+
+
+ {(detail?.title ?? "").split(" ").slice(0, 25).join(" ") + + ((detail?.title ?? "").split(" ").length > 25 ? "..." : "")} +
+
+ • {detail?.source ?? "Ticket"}
{/* Chat Messages */} -
+
{!detail ? (
@@ -748,74 +745,72 @@ export default function FormDetailTicketing({ id }: Props) {
) : ( -
- {ticketReply.map((m) => ( + ticketReply.map((m) => { + const isUser = m.user.fullname !== "Agent"; + return (
-
- {m.comments} -
-
- {m.user.fullname} - - +
+
+ {m.comments} +
+
{new Date(m.createdAt).toLocaleTimeString("id-ID", { hour: "2-digit", minute: "2-digit", })} - +
- ))} -
+ ); + }) )} +
{/* Input Box */} -
-
-
-