diff --git a/app/[locale]/(admin)/admin/agenda-setting/calender-view.tsx b/app/[locale]/(admin)/admin/agenda-setting/calender-view.tsx new file mode 100644 index 0000000..9c86ebf --- /dev/null +++ b/app/[locale]/(admin)/admin/agenda-setting/calender-view.tsx @@ -0,0 +1,697 @@ +"use client"; +import React, { useState, useEffect } from "react"; +import FullCalendar from "@fullcalendar/react"; +import dayGridPlugin from "@fullcalendar/daygrid"; +import timeGridPlugin from "@fullcalendar/timegrid"; +import interactionPlugin, { Draggable } from "@fullcalendar/interaction"; +import listPlugin from "@fullcalendar/list"; +import { Button } from "@/components/ui/button"; +import { Label } from "@/components/ui/label"; +import { Calendar } from "@/components/ui/calendar"; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { Book, CheckCheck, Plus, Timer } from "lucide-react"; +import { Checkbox } from "@/components/ui/checkbox"; +import { EventContentArg } from "@fullcalendar/core"; +import EventModal from "./event-modal"; +import { useTranslations } from "next-intl"; +import dayjs from "dayjs"; +import { getCookiesDecrypt } from "@/lib/utils"; +import { CalendarCategory } from "./data"; +import { error } from "@/config/swal"; +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "@/components/ui/popover"; +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@/components/ui/dialog"; +import { getAgendaSettingsList } from "@/service/agenda-settings/agenda-setting"; + +const wait = () => new Promise((resolve) => setTimeout(resolve, 1000)); + +interface CalendarViewProps { + categories: CalendarCategory[]; +} + +export type CalendarEvent = { + id: string; + title: string; + start: Date; + end: Date; + createBy: string; + createdByName: string; + isPublish: boolean | null; + allDay: boolean; + extendedProps: { + calendar: string; + description: string; + isPublish?: boolean; + createdByName?: string; + }; +}; + +export interface AgendaSettingsAPIResponse { + id: number; + title: string; + createdByName: string; + description: string; + agendaType: string; + startDate: string; + endDate: string; + isActive: boolean; + isPublish: boolean; + createdAt: string; + updatedAt: string; + createdById: number | null; +} + +interface YearlyData { + january?: AgendaSettingsAPIResponse[]; + february?: AgendaSettingsAPIResponse[]; + march?: AgendaSettingsAPIResponse[]; + april?: AgendaSettingsAPIResponse[]; + may?: AgendaSettingsAPIResponse[]; + june?: AgendaSettingsAPIResponse[]; + july?: AgendaSettingsAPIResponse[]; + august?: AgendaSettingsAPIResponse[]; + september?: AgendaSettingsAPIResponse[]; + october?: AgendaSettingsAPIResponse[]; + november?: AgendaSettingsAPIResponse[]; + december?: AgendaSettingsAPIResponse[]; +} + +interface MonthCardProps { + monthId: keyof YearlyData; + label: string; +} + +interface ListItemProps { + item: AgendaSettingsAPIResponse; + text: string; + createdBy: string; + isPublish: boolean | null; + bgColor: string; + colorList: string[] | null; +} + +interface APIResponse { + success: boolean; + message: string; + data: YearlyData | null; + errorCode: string | null; +} + +type EventType = "0" | "1" | "2" | "3" | "4" | "5"; + +const INITIAL_YEAR = dayjs().format("YYYY"); +const INITIAL_MONTH = dayjs().format("M"); + +const CalendarView = ({ categories }: CalendarViewProps) => { + const [selectedCategory, setSelectedCategory] = useState([]); + const [selectedEventDate, setSelectedEventDate] = useState(null); + const roleId = Number(getCookiesDecrypt("urie")) || 0; + const levelNumber = Number(getCookiesDecrypt("ulne")) || 0; + const userLevelId = Number(getCookiesDecrypt("ulie")) || 0; + const [calendarEvents, setCalendarEvents] = useState([]); + const [isLoading, setIsLoading] = useState(false); + const t = useTranslations("CalendarApp"); + const [sheetOpen, setSheetOpen] = useState(false); + const [date, setDate] = useState(new Date()); + const [activeView, setActiveView] = useState("listYear"); + const [yearlyData, setYearlyData] = useState({}); + const [open, setOpen] = useState(false); + const [selectedEventData, setSelectedEventData] = useState(null); + + const [selectedMonth, setSelectedMonth] = useState( + dayjs(new Date(Number(INITIAL_YEAR), Number(INITIAL_MONTH) - 1, 1)) + ); + + useEffect(() => { + if (activeView === "listYear") { + getYearlyEvents(); + } else { + getCalendarEvents(); + } + }, [activeView, selectedMonth]); + + useEffect(() => { + if (categories?.length > 0) { + setSelectedCategory(categories.map((c) => c.value)); + } + }, [categories]); + + const getCalendarEvents = async () => { + setIsLoading(true); + try { + const res = await getAgendaSettingsList( + selectedMonth?.format("YYYY") || INITIAL_YEAR, + selectedMonth.format("M") || INITIAL_MONTH, + "" + ); + + if (res?.error) { + error(res?.message || "Failed to fetch events"); + return; + } + + const monthData = res?.data?.data; + if (monthData) { + const allEvents: CalendarEvent[] = []; + const events = monthData?.map((event: any) => ({ + id: event.id.toString(), + title: event.title, + createBy: event.createdById, + createdByName: event.createdByName, + start: new Date(event.startDate), + end: new Date(event.endDate), + allDay: true, + extendedProps: { + isPublish: event.isPublish, + calendar: event.agendaType, + description: event.description, + createdByName: event.createdByName, + }, + })); + + console.log("Event Data : ", events); + setCalendarEvents(events); + } + } catch (err) { + console.error("Failed to fetch calendar events:", err); + error("Failed to fetch calendar events"); + } finally { + setIsLoading(false); + } + }; + + const getYearlyEvents = async () => { + setIsLoading(true); + try { + const res = await getAgendaSettingsList( + selectedMonth?.format("YYYY") || INITIAL_YEAR, + "", + "" + ); + + if (res?.error) { + error(res?.message || "Failed to fetch yearly events"); + return; + } + + const yearlyData = res?.data?.data; + if (yearlyData) { + setYearlyData(yearlyData); + } + } catch (err) { + console.error("Failed to fetch yearly events:", err); + error("Failed to fetch yearly events"); + } finally { + setIsLoading(false); + } + }; + + const filteredEvents = calendarEvents.filter((event) => { + if (!selectedCategory.length) return false; + console.log("Event category : ", selectedCategory); + + const eventCategories = event.extendedProps.calendar + ?.split(",") + .map((val: string) => val.trim()); + + const allCategoryId = ["1", "2", "3", "4", "5"]; + const hasAllCategories = allCategoryId.every((categoryId) => + selectedCategory.includes(categoryId) + ); + + return eventCategories?.some( + (cat: string) => + selectedCategory.includes(cat) || (hasAllCategories && cat == "0") + ); + }); + + const handleEventClick = (arg: any) => { + setSelectedEventDate(null); + setSelectedEventData(arg); + setSheetOpen(true); + wait().then(() => (document.body.style.pointerEvents = "auto")); + }; + + const handleCloseModal = () => { + setSheetOpen(false); + setSelectedEventData(null); + setSelectedEventDate(null); + }; + + const handleDateClick = (arg: any) => { + setSheetOpen(true); + setSelectedEventDate(arg); + setSelectedEventData(null); + wait().then(() => (document.body.style.pointerEvents = "auto")); + }; + + const handleCategorySelection = (category: string) => { + setSelectedCategory((prev) => { + if (prev.includes(category)) { + return prev.filter((c) => c !== category); + } else { + return [...prev, category]; + } + }); + }; + + const handleDateChange = (startDate: string, endDate: string) => { + const startDateOfDate = new Date(startDate); + setSelectedMonth(dayjs(startDateOfDate)); + }; + + const handleViewChange = (viewType: string) => { + setActiveView(viewType); + }; + + const handleClickListItem = (item: AgendaSettingsAPIResponse) => { + const formattedEvent: CalendarEvent = { + id: item.id.toString(), + title: item.title, + start: new Date(item.startDate), + end: new Date(item.endDate), + createBy: item.createdById?.toString() || "", + createdByName: item.createdByName, + isPublish: item.isPublish, + allDay: true, + extendedProps: { + calendar: item.agendaType, + description: item.description, + isPublish: item.isPublish, + createdByName: item.createdByName, + }, + }; + + const eventArg = { event: formattedEvent }; + setSelectedEventDate(null); + setSelectedEventData(eventArg); + setSheetOpen(true); + wait().then(() => (document.body.style.pointerEvents = "auto")); + }; + + const getEventColor = (type: EventType): string => { + const typeSplit = type.split(","); + const firstType = typeSplit[0] as EventType; + + const colors: Record = { + "0": "bg-gray-500", + "1": "bg-yellow-500", + "2": "bg-blue-400", + "3": "bg-slate-400", + "4": "bg-orange-500", + "5": "bg-green-400", + }; + + return colors[firstType] || "bg-gray-400"; + }; + + const getEventColorList = (type: EventType): string[] | null => { + const typeSplit = type.split(","); + const typeList = typeSplit.slice(1); + + if (typeList.length === 0) return null; + + const colors: Record = { + "0": "bg-black", + "1": "bg-yellow-500", + "2": "bg-blue-400", + "3": "bg-slate-400", + "4": "bg-orange-500", + "5": "bg-green-400", + }; + + return typeList.map((item) => colors[item as EventType] || "bg-gray-200"); + }; + + const renderEventContent = (eventInfo: any) => { + const { title } = eventInfo.event; + const { createdByName, isPublish, calendar } = + eventInfo.event.extendedProps; + const bgColor = getEventColor(calendar); + const colorList = getEventColorList(calendar); + + return ( +
+
+
+ {isPublish === true ? ( + + ) : ( + + )} +

{title}

+
+

+ Created By: {createdByName} +

+
+ {colorList && colorList.length > 0 && ( +
+ {colorList.map((color: string, index: number) => ( +
+ ))} +
+ )} +
+ ); + }; + + const handleClassName = (arg: EventContentArg) => { + const eventColor = getEventColor(arg.event.extendedProps.calendar); + return eventColor || "primary"; + }; + + // Components + const ListItem: React.FC = ({ + item, + text, + createdBy, + isPublish, + bgColor, + colorList, + }) => ( +
handleClickListItem(item)} + > +
+
+ {isPublish ? : } +

{text}

+
+

Created By: {createdBy}

+
+ {colorList && colorList.length > 0 && ( +
+ {colorList.map((color: string, index: number) => ( +
+ ))} +
+ )} +
+ ); + + const MonthCard: React.FC = ({ monthId, label }) => { + const events = yearlyData[monthId] || []; + const displayedEvents = events.slice(0, 3); + const hasMoreEvents = events.length > 3; + + return ( +
+
+

{label}

+
+
+ {events.length === 0 ? ( +
+

+ {t("no-data-yet", { defaultValue: "No Data Yet" })} +

+
+ ) : ( + <> + {displayedEvents.map((event, index) => ( + + ))} + {hasMoreEvents && ( + + + + + + + + + {label} + + + +
+ {events.map((event, index) => ( + + ))} +
+
+
+
+
+ )} + + )} +
+
+ ); + }; + + const months: Array<{ id: keyof YearlyData; label: string }> = [ + { id: "january", label: "Januari" }, + { id: "february", label: "Februari" }, + { id: "march", label: "Maret" }, + { id: "april", label: "April" }, + { id: "may", label: "Mei" }, + { id: "june", label: "Juni" }, + { id: "july", label: "Juli" }, + { id: "august", label: "Agustus" }, + { id: "september", label: "September" }, + { id: "october", label: "Oktober" }, + { id: "november", label: "November" }, + { id: "december", label: "Desember" }, + ]; + + const getModalContent = () => ( +
+ {isLoading ? ( +

Loading...

+ ) : ( + + + + + + + + + + + + + + + + + + + +
NoTicket NumberDate and TimeTitleStatus
1MIA - 00123/01/2025 13:00Daily Issue 25 Januari 2025Completed
+ )} +
+ ); + + return ( + <> +
+ + + + {/* {[3, 11, 2, 12].includes(roleId) && ( + + )} */} + {/* {[3, 11, 2, 12].includes(roleId) && levelNumber !== 3 && ( */} + + {/* )} */} + + {/* {roleId === 3 && userLevelId === 216 && ( */} + + + + + + + + {t("monitoring-results", { + defaultValue: "Monitoring Results", + })} + + + {getModalContent()} + + + {/* )} */} + + +
+ { + if (selectedDate) { + setDate(selectedDate); + handleDateClick(selectedDate); + } + }} + className="rounded-md border w-full p-0 border-none border-black" + /> +
+ +
+ {t("filter", { defaultValue: "Filter" })} +
+
    +
  • + { + if (selectedCategory.length === categories.length) { + setSelectedCategory([]); + } else { + setSelectedCategory(categories.map((c) => c.value)); + } + }} + /> + +
  • + {categories.map((category) => ( +
  • + + handleCategorySelection(category.value) + } + /> + +
  • + ))} +
+
+
+ + + + handleViewChange(info.view.type)} + datesSet={(info: any) => { + handleDateChange(info.view.currentStart, info.view.currentEnd); + handleViewChange(info.view.type); + }} + viewClassNames={ + activeView === "listYear" ? "hide-calendar-grid" : "" + } + /> + + {activeView === "listYear" && ( +
+ {[0, 3, 6, 9].map((startIndex) => ( +
+ {months.slice(startIndex, startIndex + 3).map((month) => ( + + ))} +
+ ))} +
+ )} +
+
+
+ + + + ); +}; + +export default CalendarView; diff --git a/app/[locale]/(admin)/admin/agenda-setting/data.ts b/app/[locale]/(admin)/admin/agenda-setting/data.ts new file mode 100644 index 0000000..bfd236e --- /dev/null +++ b/app/[locale]/(admin)/admin/agenda-setting/data.ts @@ -0,0 +1,151 @@ +import dayjs from "dayjs"; + +const date = new Date(); +const prevDay = new Date().getDate() - 1; +const nextDay = new Date(new Date().getTime() + 24 * 60 * 60 * 1000); +const INITIAL_YEAR = dayjs().format("YYYY"); +const INITIAL_MONTH = dayjs().format("M"); + +// prettier-ignore +const nextMonth = date.getMonth() === 11 ? new Date(date.getFullYear() + 1, 0, 1) : new Date(date.getFullYear(), date.getMonth() + 1, 1) +// prettier-ignore +const prevMonth = date.getMonth() === 11 ? new Date(date.getFullYear() - 1, 0, 1) : new Date(date.getFullYear(), date.getMonth() - 1, 1) + +export const calendarEvents = [ + { + id: "event-001-calendar-all-day", + title: "aaaAll Day Event", + start: date, + end: nextDay, + allDay: false, + //className: "warning", + extendedProps: { + calendar: "2", + }, + }, + { + id: "event-002-meeting-client", + title: "Meeting With Client", + start: new Date(date.getFullYear(), date.getMonth() + 1, -11), + end: new Date(date.getFullYear(), date.getMonth() + 1, -10), + allDay: true, + //className: "success", + extendedProps: { + calendar: "1", + }, + }, + { + id: "event-003-lunch", + title: "Lunch", + allDay: true, + start: new Date(date.getFullYear(), date.getMonth() + 1, -9), + end: new Date(date.getFullYear(), date.getMonth() + 1, -7), + // className: "info", + extendedProps: { + calendar: "3", + }, + }, + { + id: "event-004-birthday-party", + title: "Birthday Party", + start: new Date(date.getFullYear(), date.getMonth() + 1, -11), + end: new Date(date.getFullYear(), date.getMonth() + 1, -10), + allDay: true, + //className: "primary", + extendedProps: { + calendar: "3", + }, + }, + { + id: "event-005-birthday-party-2", + title: "Birthday Party", + start: new Date(date.getFullYear(), date.getMonth() + 1, -13), + end: new Date(date.getFullYear(), date.getMonth() + 1, -12), + allDay: true, + // className: "danger", + extendedProps: { + calendar: "2", + }, + }, + { + id: "event-006-monthly-meeting", + title: "Monthly Meeting", + start: nextMonth, + end: nextMonth, + allDay: true, + //className: "primary", + extendedProps: { + calendar: "5", + }, + }, +]; + +export const calendarCategories = [ + { + label: "Nasional", + value: "1", + activeClass: "ring-primary-500 bg-primary-500", + className: "group-hover:border-blue-500", + }, + { + label: "Polda", + value: "2", + activeClass: "ring-success-500 bg-success-500", + className: " group-hover:border-green-500", + }, + { + label: "Polres", + value: "3", + activeClass: "ring-danger-500 bg-danger-500", + className: " group-hover:border-red-500", + }, + { + label: "Satker", + value: "4", + activeClass: "ring-yellow-500 bg-yellow-500", + className: " group-hover:border-red-500", + }, + { + label: "Internasional", + value: "5", + activeClass: "ring-info-500 bg-info-500", + className: " group-hover:border-cyan-500", + }, +]; + +export const categories = [ + { + label: "Nasional", + value: "1", + className: + "data-[state=checked]:bg-yellow-500 data-[state=checked]:ring-yellow-500", + }, + { + label: "Polda", + value: "2", + className: + "data-[state=checked]:bg-blue-400 data-[state=checked]:ring-blue-400", + }, + { + label: "Polres", + value: "3", + className: + "data-[state=checked]:bg-slate-400 data-[state=checked]:ring-slate-400 ", + }, + { + label: "Satker", + value: "4", + className: + "data-[state=checked]:bg-warning data-[state=checked]:ring-warning ", + }, + { + label: "Internasional", + value: "5", + className: + "data-[state=checked]:bg-green-500 data-[state=checked]:ring-green-500 ", + }, +]; + +export type CalendarEvent = (typeof calendarEvents)[number]; +export type CalendarCategory = (typeof calendarCategories)[number]; +export type Category = (typeof categories)[number]; diff --git a/app/[locale]/(admin)/admin/agenda-setting/dragging-events.tsx b/app/[locale]/(admin)/admin/agenda-setting/dragging-events.tsx new file mode 100644 index 0000000..56683ea --- /dev/null +++ b/app/[locale]/(admin)/admin/agenda-setting/dragging-events.tsx @@ -0,0 +1,23 @@ +import { cn } from "@/lib/utils"; +const ExternalDraggingevent = ({ event }: any) => { + const { title, id, tag } = event; + + return ( +
+ + {title} +
+ ); +}; + +export default ExternalDraggingevent; diff --git a/app/[locale]/(admin)/admin/agenda-setting/event-modal.tsx b/app/[locale]/(admin)/admin/agenda-setting/event-modal.tsx new file mode 100644 index 0000000..412de50 --- /dev/null +++ b/app/[locale]/(admin)/admin/agenda-setting/event-modal.tsx @@ -0,0 +1,1616 @@ +"use client"; +import React, { useState, useEffect, useRef, Fragment } from "react"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { useForm, Controller } from "react-hook-form"; +import { cn, getCookiesDecrypt } from "@/lib/utils"; +import { format, isDate } from "date-fns"; +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "@/components/ui/popover"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { Calendar } from "@/components/ui/calendar"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { z } from "zod"; +import { + Loader2, + CalendarIcon, + ChevronUp, + ChevronDown, + Music, +} from "lucide-react"; +import { DateRange } from "react-day-picker"; +import DeleteConfirmationDialog from "@/components/delete-confirmation-dialog"; +import { CalendarCategory } from "./data"; +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@/components/ui/dialog"; +import { Textarea } from "@/components/ui/textarea"; +import { error, loading, success } from "@/lib/swal"; +import Cookies from "js-cookie"; +import Swal from "sweetalert2"; +import withReactContent from "sweetalert2-react-content"; + +import { Checkbox } from "@/components/ui/checkbox"; +import { getUserLevelForAssignments } from "@/service/task"; +import { AudioRecorder } from "react-audio-voice-recorder"; +import { Upload } from "tus-js-client"; +import { getCsrfToken } from "@/service/auth"; +import { Icon } from "@iconify/react"; +import Image from "next/image"; +import { UnitMapping } from "./unit-mapping"; +import { useToast } from "@/components/ui/use-toast"; +import { usePathname, useRouter } from "next/navigation"; +import { Card } from "@/components/ui/card"; +import WavesurferPlayer from "@wavesurfer/react"; +import WaveSurfer from "wavesurfer.js"; +import { deleteAgendaSettings, getAgendaSettingsById, publishAgendaSettings, saveAgendaSettings } from "@/service/service/agenda-setting/agenda-setting"; +import FileUploader from "@/components/form/shared/file-uploader"; + +const schema = z.object({ + title: z.string().min(3, { message: "Required" }), + description: z + .string() + .min(2, { message: "Narasi Penugasan harus lebih dari 2 karakter." }), +}); + +interface FileWithPreview extends File { + preview: string; +} + +interface FileUploaded { + id: number; + url: string; +} + +const EventModal = ({ + open, + onClose, + categories, + event, + selectedDate, +}: { + open: boolean; + onClose: () => void; + categories: any; + event: any; + selectedDate: any; +}) => { + const roleId = Number(getCookiesDecrypt("urie")) || 0; + const [detail, setDetail] = useState(); + const [date, setDate] = React.useState({ + from: new Date(), + to: new Date(), + }); + const [isPending, startTransition] = React.useTransition(); + const [listDest, setListDest] = useState([]); + const [deleteModalOpen, setDeleteModalOpen] = useState(false); + const [eventIdToDelete, setEventIdToDelete] = useState(null); + const MySwal = withReactContent(Swal); + const router = useRouter(); + const pathname = usePathname(); + const [isLoading, setIsLoading] = useState(false); + const [checkedLevels, setCheckedLevels] = useState>(new Set()); + const [expandedPolda, setExpandedPolda] = useState>({}); + const [audioFile, setAudioFile] = useState(null); + const [isRecording, setIsRecording] = useState(false); + const [timer, setTimer] = useState(120); + const [detailData, setDetailData] = useState(); + const [imageFiles, setImageFiles] = useState([]); + const [videoFiles, setVideoFiles] = useState([]); + const [textFiles, setTextFiles] = useState([]); + const [audioFiles, setAudioFiles] = useState([]); + const [imageUploadedFiles, setImageUploadedFiles] = useState( + [] + ); + const [videoUploadedFiles, setVideoUploadedFiles] = useState( + [] + ); + const [textUploadedFiles, setTextUploadedFiles] = useState( + [] + ); + const [audioUploadedFiles, setAudioUploadedFiles] = useState( + [] + ); + const { toast } = useToast(); + const [isImageUploadFinish, setIsImageUploadFinish] = useState(false); + const [isVideoUploadFinish, setIsVideoUploadFinish] = useState(false); + const [isTextUploadFinish, setIsTextUploadFinish] = useState(false); + const [isAudioUploadFinish, setIsAudioUploadFinish] = useState(false); + const [unitData, setUnitData] = useState([]); + const [satkerData, setSatkerData] = useState([]); + let progressInfo: any = []; + let counterUpdateProgress = 0; + const [progressList, setProgressList] = useState([]); + let uploadPersen = 0; + const [isStartUpload, setIsStartUpload] = useState(false); + const [counterProgress, setCounterProgress] = useState(0); + const [wilayahPublish, setWilayahPublish] = React.useState({ + semua: false, + nasional: false, + polda: false, + polres: false, + satker: false, + international: false, + }); + + // State untuk melacak apakah perubahan berasal dari checkbox Jenis Agenda + const [isUpdatingFromJenisAgenda, setIsUpdatingFromJenisAgenda] = useState(false); + // State untuk melacak jenis perubahan spesifik + const [jenisAgendaChangeType, setJenisAgendaChangeType] = useState(""); + + const levelNumber = Number(getCookiesDecrypt("ulne")) || 0; + const userLevelId = getCookiesDecrypt("ulie"); + const poldaState = Cookies.get("state"); + const [agendaType, setAgendaType] = React.useState(""); + const [selectedPolda, setSelectedPolda] = useState([]); + const [selectedPolres, setSelectedPolres] = useState([]); + const [selectedSatker, setSelectedSatker] = useState([]); + const isDetailMode = true; + const [wavesurfer, setWavesurfer] = useState(); + const [isPlaying, setIsPlaying] = useState(false); + const [isPublishing, setIsPublishing] = useState(false); + const [isDatePickerOpen, setIsDatePickerOpen] = useState(false); + + const { + register, + control, + reset, + setValue, + formState: { errors }, + handleSubmit, + } = useForm({ + resolver: zodResolver(schema), + mode: "all", + }); + + useEffect(() => { + async function fetchDetailData() { + const res = await getAgendaSettingsById(event?.event?.id); + const detail = res?.data?.data; + setDetailData(detail); + + const description = detail?.description; + if (description) { + setValue("description", description); + } + + const attachments = detail?.attachments; + setImageUploadedFiles( + attachments?.filter((file: any) => file.fileTypeId == 1) + ); + setVideoUploadedFiles( + attachments?.filter((file: any) => file.fileTypeId == 2) + ); + setTextUploadedFiles( + attachments?.filter((file: any) => file.fileTypeId == 3) + ); + setAudioUploadedFiles( + attachments?.filter((file: any) => file.fileTypeId == 4) + ); + + const rawAgendaTypes = detail?.agendaType?.split(",") || []; // ["0","1","2","3","4","5"] + const assignedToLevel = detail?.assignedToLevel?.split(",") || []; + + const wilayahState = { + semua: false, + nasional: false, + polda: false, + polres: false, + satker: false, + international: false, + }; + + rawAgendaTypes.forEach((type: any) => { + switch (type) { + case "0": + wilayahState.semua = true; + break; + case "1": + wilayahState.nasional = true; + break; + case "2": + wilayahState.polda = true; + break; + case "3": + wilayahState.polres = true; + break; + case "4": + wilayahState.satker = true; + break; + case "5": + wilayahState.international = true; + break; + default: + break; + } + }); + + setWilayahPublish(wilayahState); + + if (rawAgendaTypes.includes("2")) { + setSelectedPolda(assignedToLevel); + } + if (rawAgendaTypes.includes("3")) { + setSelectedPolres(assignedToLevel); + } + if (rawAgendaTypes.includes("4")) { + setSelectedSatker(assignedToLevel); + } + } + fetchDetailData(); + }, [event, setValue]); + + // useEffect untuk sinkronisasi checkbox modal dengan Jenis Agenda + useEffect(() => { + if (listDest.length > 0 && isUpdatingFromJenisAgenda && jenisAgendaChangeType) { + syncModalWithJenisAgenda(); + } + }, [isUpdatingFromJenisAgenda, jenisAgendaChangeType]); + + // useEffect untuk update wilayahPublish ketika pilihan modal berubah + useEffect(() => { + if (!isUpdatingFromJenisAgenda && listDest.length > 0) { + updateWilayahPublishFromModal(); + } + }, [checkedLevels, isUpdatingFromJenisAgenda]); + + // Fungsi untuk update wilayahPublish berdasarkan checkbox modal + const updateWilayahPublishFromModal = () => { + // Hanya update jika tidak sedang dalam proses update dari Jenis Agenda + if (!isUpdatingFromJenisAgenda && 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) { + 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 arrays untuk backend + const newSelectedPolda = listDest + .filter((item: any) => + item.levelNumber === 2 && + item.name !== "SATKER POLRI" && + checkedLevels.has(Number(item.id)) + ) + .map((item: any) => String(item.id)); + + const newSelectedPolres: string[] = []; + listDest.forEach((item: any) => { + if (item.subDestination) { + item.subDestination.forEach((sub: any) => { + if (checkedLevels.has(Number(sub.id))) { + newSelectedPolres.push(String(sub.id)); + } + }); + } + }); + + const newSelectedSatker: string[] = []; + if (satkerItem) { + if (checkedLevels.has(Number(satkerItem.id))) { + newSelectedSatker.push(String(satkerItem.id)); + } + if (satkerItem.subDestination) { + satkerItem.subDestination.forEach((sub: any) => { + if (checkedLevels.has(Number(sub.id))) { + newSelectedSatker.push(String(sub.id)); + } + }); + } + } + + // Update state arrays + setSelectedPolda(newSelectedPolda); + setSelectedPolres(newSelectedPolres); + setSelectedSatker(newSelectedSatker); + + // Update wilayahPublish berdasarkan yang dipilih di modal + setWilayahPublish(prev => { + const newState = { ...prev }; + + // Update individual checkboxes + newState.polda = hasSelectedPolda; + newState.polres = hasSelectedPolres; + newState.satker = hasSelectedSatker; + + // Update checkbox "semua" berdasarkan level user + if (levelNumber === 1) { + // Level 1: semua checkbox harus aktif (nasional, polda, polres, satker, international) + newState.semua = newState.nasional && hasSelectedPolda && hasSelectedPolres && hasSelectedSatker && newState.international; + } else if (levelNumber === 2) { + // Level 2: hanya polres yang perlu aktif + newState.semua = hasSelectedPolres; + } else { + newState.semua = false; + } + + return newState; + }); + + // Update agendaType berdasarkan checkbox yang aktif + const selectedKeys = []; + if (hasSelectedPolda) selectedKeys.push(wilayahValueMap.polda); + if (hasSelectedPolres) selectedKeys.push(wilayahValueMap.polres); + if (hasSelectedSatker) selectedKeys.push(wilayahValueMap.satker); + + setAgendaType(selectedKeys.join(",")); + } + }; + + // Fungsi untuk sinkronisasi checkbox modal dengan Jenis Agenda + const syncModalWithJenisAgenda = () => { + // Hanya jalankan sinkronisasi jika perubahan berasal dari checkbox Jenis Agenda + if (isUpdatingFromJenisAgenda) { + const newCheckedLevels = new Set(checkedLevels); + + // Handle checklist actions - menambahkan semua item ke modal + if (jenisAgendaChangeType === "polda_checked") { + // Checklist semua polda + listDest.forEach((item: any) => { + if (item.levelNumber === 2 && item.name !== "SATKER POLRI") { + newCheckedLevels.add(Number(item.id)); + } + }); + } else if (jenisAgendaChangeType === "polres_checked") { + // Checklist semua polres, tapi hanya yang poldanya sudah di-checklist + 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)); + }); + } + } + }); + } else if (jenisAgendaChangeType === "satker_checked") { + // Checklist satker + const satkerItem: any = listDest.find((item: any) => item.name === "SATKER POLRI"); + if (satkerItem) { + newCheckedLevels.add(Number(satkerItem.id)); + if (satkerItem.subDestination) { + satkerItem.subDestination.forEach((sub: any) => { + newCheckedLevels.add(Number(sub.id)); + }); + } + } + } + else if (jenisAgendaChangeType === "polres_unchecked") { + listDest.forEach((item: any) => { + if (item.subDestination) { + item.subDestination.forEach((polres: any) => { + newCheckedLevels.delete(Number(polres.id)); + }); + } + }); + } else if (jenisAgendaChangeType === "polda_unchecked") { + 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)); + }); + } + } + }); + setWilayahPublish(prev => ({ ...prev, polres: false })); + } else if (jenisAgendaChangeType === "satker_unchecked") { + 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)); + }); + } + } + } + + setCheckedLevels(newCheckedLevels); + + // Reset flag setelah sinkronisasi selesai + setIsUpdatingFromJenisAgenda(false); + setJenisAgendaChangeType(""); + } + }; + + useEffect(() => { + setIsDatePickerOpen(false); + }, [onClose]); + + useEffect(() => { + async function fetchPoldaPolres() { + 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); + } catch (error) { + console.error("Error fetching Polda/Polres data:", error); + } + } + fetchPoldaPolres(); + }, []); + + const handleCheckboxChange = (levelId: number) => { + setCheckedLevels((prev) => { + const updatedLevels = new Set(prev); + const isCurrentlyChecked = updatedLevels.has(levelId); + + if (isCurrentlyChecked) { + updatedLevels.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.subDestination.forEach((polres: any) => { + updatedLevels.delete(Number(polres.id)); + }); + } + } else { + updatedLevels.add(levelId); + } + return updatedLevels; + }); + }; + + const wilayahValueMap: Record = { + semua: "0", + nasional: "1", + polda: "2", + polres: "3", + satker: "4", + international: "5", + }; + + const toggleWilayah = (key: string) => { + // Set flag bahwa perubahan berasal dari checkbox Jenis Agenda + setIsUpdatingFromJenisAgenda(true); + setJenisAgendaChangeType(key + (wilayahPublish[key as keyof typeof wilayahPublish] ? "_unchecked" : "_checked")); + + setWilayahPublish((prev: any) => { + let newState = { ...prev }; + if (key === "semua") { + const newChecked = !prev.semua; + newState = { + semua: newChecked, + nasional: newChecked, + polda: newChecked, + polres: newChecked, + satker: newChecked, + international: newChecked, + }; + + if (newChecked) { + setAgendaType("0,1,2,3,4,5"); + // Checklist semua item di modal ketika "semua" di-checklist + const allCheckedLevels = new Set(); + listDest.forEach((item: any) => { + allCheckedLevels.add(Number(item.id)); + if (item.subDestination) { + item.subDestination.forEach((sub: any) => { + allCheckedLevels.add(Number(sub.id)); + }); + } + }); + setCheckedLevels(allCheckedLevels); + } else { + setAgendaType(""); + // Clear semua pilihan modal ketika "semua" di-unchecklist + setCheckedLevels(new Set()); + } + + return newState; + } + + // Validasi khusus untuk POLRES + if (key === "polres" && !prev[key]) { + // Cek apakah ada POLDA yang sudah dipilih di modal + const hasSelectedPolda = listDest.some((item: any) => + item.levelNumber === 2 && + item.name !== "SATKER POLRI" && + checkedLevels.has(Number(item.id)) + ); + + if (!hasSelectedPolda) { + // Jika tidak ada POLDA yang dipilih, tampilkan peringatan dan batalkan + alert("Harap pilih POLDA di Modal terlebih dahulu sebelum mengaktifkan checkbox POLRES."); + // Reset flag karena perubahan dibatalkan + setIsUpdatingFromJenisAgenda(false); + setJenisAgendaChangeType(""); + return prev; // Batalkan perubahan + } + } + + newState[key] = !prev[key]; + + // Jika checkbox di-unchecklist, clear pilihan modal yang sesuai + if (prev[key]) { + const newCheckedLevels = new Set(checkedLevels); + if (key === "polda") { + // Clear polda dan polres + 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 (key === "polres") { + // Clear polres + listDest.forEach((item: any) => { + if (item.subDestination) { + item.subDestination.forEach((polres: any) => { + newCheckedLevels.delete(Number(polres.id)); + }); + } + }); + } else if (key === "satker") { + // Clear satker + 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)); + }); + } + } + } + setCheckedLevels(newCheckedLevels); + } + + // Update checkbox "semua" berdasarkan status semua checkbox lainnya + // Untuk level 1: semua, nasional, polda, polres, satker, international harus aktif + // Untuk level 2: semua, polres harus aktif + if (levelNumber === 1) { + newState.semua = newState.nasional && newState.polda && newState.polres && newState.satker && newState.international; + } else if (levelNumber === 2) { + newState.semua = newState.polres; + } else { + newState.semua = false; + } + + const selectedKeys = Object.entries(newState) + .filter(([k, v]) => v && k !== "semua") + .map(([k]) => wilayahValueMap[k]); + + setAgendaType(selectedKeys.join(",")); + + return newState; + }); + }; + + const save = async (data: any, publish = false) => { + const agendaTypeList: string[] = []; + const assignedToLevelList: string[] = []; + + // // Mapping dari checkbox wilayah ke agendaType + // Object.keys(wilayahPublish).forEach((key) => { + // if (wilayahPublish[key as keyof typeof wilayahPublish]) { + // agendaTypeList.push(wilayahValueMap[key]); + // } + // }); + + if (wilayahPublish.semua) { + agendaTypeList.push("0", "1", "2", "3", "4", "5"); + } else { + Object.keys(wilayahPublish).forEach((key) => { + if ( + wilayahPublish[key as keyof typeof wilayahPublish] && + key !== "semua" + ) { + agendaTypeList.push(wilayahValueMap[key]); + } + }); + } + + // Unit-unit berdasarkan wilayah yang aktif + if (wilayahPublish.polda && selectedPolda.length > 0) { + assignedToLevelList.push(...selectedPolda); + } + if (wilayahPublish.polres && selectedPolres.length > 0) { + assignedToLevelList.push(...selectedPolres); + } + if (wilayahPublish.satker && selectedSatker.length > 0) { + assignedToLevelList.push(...selectedSatker); + } + + const reqData = { + id: detailData?.id, + title: data.title, + description: data.description, + agendaType: agendaTypeList.join(","), + assignedToLevel: assignedToLevelList.join(","), + startDate: date?.from ? format(date.from, "yyyy-MM-dd") : null, + endDate: date?.to ? format(date.to, "yyyy-MM-dd") : null, + }; + + console.log("Submitted Data:", reqData); + + const response = await saveAgendaSettings(reqData); + if (response?.error) { + error(response?.message); + return false; + } + + const id = response?.data?.data?.id; + + loading(); + if (imageFiles?.length === 0) { + setIsImageUploadFinish(true); + } + imageFiles?.map(async (item: any, index: number) => { + await uploadResumableFile(index, String(id), item, "1", "0"); + }); + + if (videoFiles?.length === 0) { + setIsVideoUploadFinish(true); + } + videoFiles?.map(async (item: any, index: number) => { + await uploadResumableFile(index, String(id), item, "2", "0"); + }); + + if (textFiles?.length === 0) { + setIsTextUploadFinish(true); + } + textFiles?.map(async (item: any, index: number) => { + await uploadResumableFile(index, String(id), item, "3", "0"); + }); + + if (audioFiles?.length == 0) { + setIsAudioUploadFinish(true); + } + audioFiles.map(async (item: FileWithPreview, index: number) => { + await uploadResumableFile(index, String(id), item, "4", "0"); + }); + if (publish) { + setIsPublishing(true); + const publishResponse = await publishAgendaSettings(id); + setIsPublishing(false); + + if (publishResponse?.error) { + error(publishResponse?.message); + return false; + } + + success("Agenda berhasil dipublikasikan!"); + } + }; + + const onSubmit = (data: any) => { + if ( + (wilayahPublish.polda && selectedPolda.length === 0) || + (wilayahPublish.satker && selectedSatker.length === 0) || + (wilayahPublish.polres && selectedPolres.length === 0) + ) { + toast({ + title: "Pilih ID untuk Polda/Satker", + variant: "destructive", + }); + return; + } + save(data); + }; + + useEffect(() => { + console.log("Event data:", event); + console.log("Selected date:", selectedDate); + + if (selectedDate) { + setDate({ + from: selectedDate.date, + to: selectedDate.date, + }); + } + if (event) { + setDate({ + from: event?.event?.start, + to: event?.event?.end, + }); + const eventCalendar = event?.event?.extendedProps?.calendar; + setAgendaType( + eventCalendar || (categories?.length > 0 && categories[0].value) + ); + } + setValue("title", event?.event?.title || ""); + setValue("description", event?.event?.description || ""); + }, [event, selectedDate, open, categories, setValue]); + + const onDeleteEventAction = async () => { + try { + } catch (error) { } + }; + + const handleOpenDeleteModal = (eventId: string) => { + setEventIdToDelete(eventId); + setDeleteModalOpen(true); + onClose(); + }; + + const toggleExpand = (poldaId: any) => { + console.log("Toogle : ", expandedPolda); + setExpandedPolda((prev: any) => ({ + ...prev, + [poldaId]: !prev[poldaId], + })); + }; + + 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); + }; + + const addAudioElement = (blob: Blob) => { + const url = URL.createObjectURL(blob); + const audio = document.createElement("audio"); + audio.src = url; + audio.controls = true; + document.body.appendChild(audio); + + const fileWithPreview: FileWithPreview = Object.assign( + new File([blob], "voiceNote.webm", { type: "audio/webm" }), + { preview: url } + ); + + setAudioFile(fileWithPreview); + setAudioFiles((prev) => [...prev, fileWithPreview]); + }; + + const handleDeleteAudio = () => { + setAudioFile(null); + const audioElements = document.querySelectorAll("audio"); + audioElements.forEach((audio) => audio.remove()); + }; + + async function uploadResumableFile( + idx: number, + id: string, + file: any, + fileTypeId: string, + duration: string + ) { + console.log(idx, id, file, fileTypeId, duration); + + 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}/agenda-settings/file/upload`, + headers: headers, + retryDelays: [0, 3000, 6000, 12_000, 24_000], + chunkSize: 20_000, + metadata: { + agendaSettingId: id, + filename: file.name, + contentType: file.type, + fileTypeId: fileTypeId, + duration, + }, + 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(); + if (fileTypeId == "1") { + setIsImageUploadFinish(true); + } else if (fileTypeId == "2") { + setIsVideoUploadFinish(true); + } + if (fileTypeId == "3") { + setIsTextUploadFinish(true); + } + if (fileTypeId == "4") { + setIsAudioUploadFinish(true); + } + }, + }); + + upload.start(); + } + + useEffect(() => { + successTodo(); + }, [ + isImageUploadFinish, + isVideoUploadFinish, + isAudioUploadFinish, + isTextUploadFinish, + ]); + + function successTodo() { + if ( + isImageUploadFinish && + isVideoUploadFinish && + isAudioUploadFinish && + isTextUploadFinish + ) { + successSubmit("/in/contributor/agenda-setting"); + } + } + + const successSubmit = (redirect: string) => { + MySwal.fire({ + title: "Sukses", + text: "Data berhasil disimpan.", + icon: "success", + confirmButtonColor: "#3085d6", + confirmButtonText: "OK", + }).then(() => { + router.push(redirect); + window.location.reload(); + }); + }; + + const renderFilePreview = (url: string) => { + return ( + {"file + ); + }; + + const handleRemoveFile = (id: number) => { }; + + async function doDelete(id: any) { + loading(); + const resDelete = await deleteAgendaSettings(id); + if (resDelete?.error) { + error(resDelete.message); + return false; + } + close(); + successSubmitDelete("/in/contributor/agenda-setting"); + window.location.reload(); + } + + const handleDelete = (id: any) => { + MySwal.fire({ + title: "Hapus Data?", + text: "", + icon: "warning", + showCancelButton: true, + cancelButtonColor: "#3085d6", + confirmButtonColor: "#d33", + confirmButtonText: "Hapus", + }).then((result) => { + if (result.isConfirmed) { + doDelete(id); + } + }); + }; + + function successSubmitDelete(redirect: string) { + MySwal.fire({ + title: "Sukses", + icon: "success", + confirmButtonColor: "#3085d6", + confirmButtonText: "OK", + }).then(() => { + if (redirect === window.location.pathname) { + fetch(redirect, { method: "GET", cache: "reload" }).then(() => { + console.log("Data diperbarui."); + }); + } else { + window.location.href = redirect; + } + }); + } + + const onReady = (ws: any) => { + setWavesurfer(ws); + setIsPlaying(false); + }; + + const onPlayPause = () => { + wavesurfer && wavesurfer.playPause(); + }; + + return ( + <> + setDeleteModalOpen(false)} + onConfirm={onDeleteEventAction} + defaultToast={false} + /> + + + + + {event?.length > 1 + ? "Edit Agenda Setting" + : "Create Agenda Setting"}{" "} + {event?.title} + + + +
+
+
+
+ + ( + + )} + /> + {errors?.title?.message && ( +
+ {typeof errors?.title?.message === "string" + ? errors?.title?.message + : JSON.stringify(errors?.title?.message)} +
+ )} +
+
+ + setIsDatePickerOpen(true)} + > + + + + + { + console.log("Date selected:", newDate); + setDate(newDate); + if (newDate?.from && newDate?.to) { + setIsDatePickerOpen(false); + } + }} + numberOfMonths={1} + className="rounded-md border" + /> + + +
+
+ +
+ +
+ toggleWilayah("semua")} + /> + +
+ + {levelNumber === 1 && ( + <> +
+ toggleWilayah("nasional")} + /> + +
+
+ toggleWilayah("polda")} + /> + +
+ + )} + + {(levelNumber === 1 || levelNumber === 2) && ( +
+ toggleWilayah("polres")} + /> + +
+ )} + + {levelNumber === 1 && ( + <> +
+ toggleWilayah("satker")} + /> + +
+
+ toggleWilayah("international")} + /> + +
+ + )} + +
+ + + + + + + + Daftar Wilayah Polda dan Polres + + +
+ {listDest?.map((polda: any) => ( +
+ + {expandedPolda[polda.id] && ( +
+ + {polda?.subDestination?.map((polres: any) => ( + + ))} +
+ )} +
+ ))} +
+
+
+
+
+
+ +
+ + ( +