diff --git a/app/[locale]/(protected)/admin/add-experts/component/column.tsx b/app/[locale]/(protected)/admin/add-experts/component/column.tsx index 641f4a22..4309457c 100644 --- a/app/[locale]/(protected)/admin/add-experts/component/column.tsx +++ b/app/[locale]/(protected)/admin/add-experts/component/column.tsx @@ -18,7 +18,7 @@ import { useRouter } from "next/navigation"; import { deleteUser } from "@/service/management-user/management-user"; import { stringify } from "querystring"; -const columns: ColumnDef[] = [ +const getColumns = ({ onRefresh }: { onRefresh: () => void }): ColumnDef[] => [ { accessorKey: "no", header: "No", @@ -30,11 +30,13 @@ const columns: ColumnDef[] = [ header: "Nama", cell: ({ row }) => {row.getValue("fullname")}, }, + { accessorKey: "address", header: "Wilayah", - cell: ({ row }) => MABES, + cell: () => MABES, }, + { accessorKey: "userRolePlacements", header: "Posisi", @@ -52,6 +54,7 @@ const columns: ColumnDef[] = [ return {posisi}; }, }, + { accessorKey: "role.name", header: "Bidang Keahlian", @@ -81,29 +84,24 @@ const columns: ColumnDef[] = [ { id: "actions", - accessorKey: "action", header: "Actions", - enableHiding: false, cell: ({ row }) => { const { toast } = useToast(); const MySwal = withReactContent(Swal); - const router = useRouter(); + const doDelete = async (id: number) => { - // Tampilkan loading Swal.fire({ title: "Menghapus user...", - text: "Mohon tunggu sebentar", + text: "Mohon tunggu", allowOutsideClick: false, - didOpen: () => { - Swal.showLoading(); - }, + didOpen: () => Swal.showLoading(), }); const response = await deleteUser(id); - if (response?.error) { - Swal.close(); + Swal.close(); + if (response?.error) { toast({ title: stringify(response?.message), variant: "destructive", @@ -111,88 +109,52 @@ const columns: ColumnDef[] = [ return; } - Swal.close(); + toast({ title: "Berhasil menghapus user" }); - toast({ - title: "Berhasil menghapus user", - }); - - router.push("?dataChange=true"); + // ⬅️ INI YANG PENTING → REFRESH TABLE TANPA RELOAD + onRefresh(); }; const handleDelete = (id: number) => { MySwal.fire({ - title: "Apakah anda ingin menghapus data user?", + title: "Hapus user ini?", showCancelButton: true, confirmButtonColor: "#dc3545", confirmButtonText: "Iya", cancelButtonText: "Tidak", - }).then((result) => { - if (result.isConfirmed) { - doDelete(id); - } + }).then((res) => { + if (res.isConfirmed) doDelete(id); }); }; - // const doDelete = async (id: number) => { - // const response = await deleteUser(id); - // if (response?.error) { - // toast({ - // title: stringify(response?.message), - // variant: "destructive", - // }); - // } - // toast({ - // title: "Success delete", - // }); - - // router.push("?dataChange=true"); - // }; - - // const handleDelete = (id: number) => { - // MySwal.fire({ - // title: "Apakah anda ingin menghapus data user?", - // showCancelButton: true, - // confirmButtonColor: "#dc3545", - // confirmButtonText: "Iya", - // cancelButtonText: "Tidak", - // }).then((result) => { - // if (result.isConfirmed) { - // doDelete(id); - // } - // }); - // }; return ( - - - - - - View + + + + + View - - - - Edit + + + Edit + handleDelete(row.original.userKeycloakId)} - className="p-2 border-b text-destructive bg-destructive/30 focus:bg-destructive focus:text-destructive-foreground rounded-none" + className="text-red-600 cursor-pointer hover:bg-red-300" > - - Delete + Delete + ); @@ -200,4 +162,4 @@ const columns: ColumnDef[] = [ }, ]; -export default columns; +export default getColumns; diff --git a/app/[locale]/(protected)/admin/add-experts/component/table.tsx b/app/[locale]/(protected)/admin/add-experts/component/table.tsx index 57be718c..b4ff2295 100644 --- a/app/[locale]/(protected)/admin/add-experts/component/table.tsx +++ b/app/[locale]/(protected)/admin/add-experts/component/table.tsx @@ -2,7 +2,6 @@ import * as React from "react"; import { - ColumnDef, ColumnFiltersState, PaginationState, SortingState, @@ -15,7 +14,6 @@ import { useReactTable, } from "@tanstack/react-table"; import { Button } from "@/components/ui/button"; - import { Table, TableBody, @@ -25,7 +23,6 @@ import { TableRow, } from "@/components/ui/table"; import { UserIcon } from "lucide-react"; -import { cn } from "@/lib/utils"; import { DropdownMenu, DropdownMenuContent, @@ -35,43 +32,14 @@ import { DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; import { Input } from "@/components/ui/input"; -import { InputGroup, InputGroupText } from "@/components/ui/input-group"; -import { paginationBlog } from "@/service/blog/blog"; -import { ticketingPagination } from "@/service/ticketing/ticketing"; -import { Badge } from "@/components/ui/badge"; import { useRouter, useSearchParams } from "next/navigation"; import TablePagination from "@/components/table/table-pagination"; -import columns from "./column"; -import { getPlanningPagination } from "@/service/agenda-setting/agenda-setting"; -import { - Popover, - PopoverContent, - PopoverTrigger, -} from "@/components/ui/popover"; -import { listDataMedia } from "@/service/broadcast/broadcast"; +// import columns from "./column"; +import getColumns from "./column"; import { listEnableCategory } from "@/service/content/content"; -import { Checkbox } from "@/components/ui/checkbox"; -import { close, loading } from "@/config/swal"; import { Link } from "@/i18n/routing"; import { listDataExperts } from "@/service/experts/experts"; -const dummyData = [ - { - id: 1, - name: "Prof. Dr. Ravi", - region: "Nasional", - skills: "Komunikasi", - experience: "Akademisi", - }, - { - id: 2, - name: "Prof. Dr. Novan", - region: "DKI Jakarta", - skills: "Hukum", - experience: "Akademisi + Praktisi", - }, -]; - const AddExpertTable = () => { const router = useRouter(); const searchParams = useSearchParams(); @@ -97,7 +65,8 @@ const AddExpertTable = () => { const [limit, setLimit] = React.useState(10); const table = useReactTable({ data: dataTable, - columns, + // columns, + columns: getColumns({ onRefresh: fetchData }), onSortingChange: setSorting, onColumnFiltersChange: setColumnFilters, getCoreRowModel: getCoreRowModel(), @@ -283,7 +252,11 @@ const AddExpertTable = () => { )) ) : ( - + No results. diff --git a/app/[locale]/(protected)/admin/add-experts/create/page.tsx b/app/[locale]/(protected)/admin/add-experts/create/page.tsx index d3a7fd90..5aedfd7a 100644 --- a/app/[locale]/(protected)/admin/add-experts/create/page.tsx +++ b/app/[locale]/(protected)/admin/add-experts/create/page.tsx @@ -62,29 +62,34 @@ import { Eye, EyeOff } from "lucide-react"; // }), // }); -const FormSchema = z - .object({ - name: z.string({ required_error: "Required" }), - username: z.string({ required_error: "Required" }), - password: z - .string({ required_error: "Required" }) - .min(8, "Minimal 8 karakter") - .regex(/[A-Z]/, "Harus mengandung huruf besar (A-Z)") - .regex(/[0-9]/, "Harus mengandung angka (0-9)") - .regex(/[^A-Za-z0-9]/, "Harus mengandung karakter spesial (!@#$%^&*)"), +const FormSchema = z.object({ + name: z.string({ required_error: "Required" }), + username: z + .string({ required_error: "Required" }) + .refine((val) => !/\s/.test(val), { + message: "Username tidak boleh mengandung spasi", + }), + // .transform((val) => val.toLowerCase()), - // confirmPassword: z.string({ required_error: "Required" }), + password: z + .string({ required_error: "Required" }) + .min(8, "Minimal 8 karakter") + .regex(/[A-Z]/, "Harus mengandung huruf besar (A-Z)") + .regex(/[0-9]/, "Harus mengandung angka (0-9)") + .regex(/[^A-Za-z0-9]/, "Harus mengandung karakter spesial (!@#$%^&*)"), - phoneNumber: z.string({ required_error: "Required" }), - email: z.string({ required_error: "Required" }), - skills: z.string({ required_error: "Required" }), - experiences: z.string({ required_error: "Required" }), - company: z.string({ required_error: "Required" }), - }) - // .refine((data) => data.password === data.confirmPassword, { - // path: ["confirmPassword"], - // message: "Konfirmasi password tidak sama", - // }); + // confirmPassword: z.string({ required_error: "Required" }), + + phoneNumber: z.string({ required_error: "Required" }), + email: z.string({ required_error: "Required" }), + skills: z.string({ required_error: "Required" }), + experiences: z.string({ required_error: "Required" }), + company: z.string({ required_error: "Required" }), +}); +// .refine((data) => data.password === data.confirmPassword, { +// path: ["confirmPassword"], +// message: "Konfirmasi password tidak sama", +// }); export type Placements = { index: number; @@ -326,6 +331,39 @@ export default function AddExpertForm() { )} /> ( + + Username (huruf kecil, tanpa spasi) + + { + let value = e.target.value; + + // Hapus spasi otomatis + value = value.replace(/\s+/g, ""); + + // Jadikan lowercase otomatis + value = value.toLowerCase(); + + field.onChange(value); + }} + /> + + {/* Info tambahan */} +

+ Username otomatis menjadi huruf kecil tanpa spasi. +

+ + +
+ )} + /> + {/* ( @@ -341,7 +379,7 @@ export default function AddExpertForm() { )} - /> + /> */} : } - Password harus memiliki minimal 8 karakter, special karakter, angka dan huruf kapital + + Password harus memiliki minimal 8 karakter, special karakter, + angka dan huruf kapital + {/* Strength meter */} {field.value && ( diff --git a/app/[locale]/(protected)/admin/broadcast/campaign-list/account-list/component/table.tsx b/app/[locale]/(protected)/admin/broadcast/campaign-list/account-list/component/table.tsx index 2ed79724..8d6ca03b 100644 --- a/app/[locale]/(protected)/admin/broadcast/campaign-list/account-list/component/table.tsx +++ b/app/[locale]/(protected)/admin/broadcast/campaign-list/account-list/component/table.tsx @@ -59,8 +59,12 @@ import { deleteMediaBlastCampaignAccount, saveMediaBlastCampaignAccountBulk, } from "@/service/broadcast/broadcast"; -import { AdministrationUserList, getUserListAll } from "@/service/management-user/management-user"; +import { + AdministrationUserList, + getUserListAll, +} from "@/service/management-user/management-user"; import { close, loading, error, success, successCallback } from "@/config/swal"; +import { Link } from "@/i18n/routing"; // Mock data for available accounts - replace with actual API call const availableAccounts = [ @@ -98,7 +102,8 @@ const AccountListTable = () => { const [accountCategory, setAccountCategory] = React.useState(""); const [selectedAccount, setSelectedAccount] = React.useState([]); const [selectedCategory, setSelectedCategory] = React.useState(""); - const [availableAccountsList, setAvailableAccountsList] = React.useState(availableAccounts); + const [availableAccountsList, setAvailableAccountsList] = + React.useState(availableAccounts); const [usersList, setUsersList] = React.useState([]); const table = useReactTable({ @@ -171,7 +176,7 @@ const AccountListTable = () => { async function saveCampaignAccount() { try { loading(); - + if (accountCategory === "all-account") { // Handle all accounts - send only campaignId and category "all" const request = { @@ -202,7 +207,7 @@ const AccountListTable = () => { default: roleId = "5"; } - + const request = { mediaBlastCampaignId: campaignId, mediaBlastAccountCategory: `role-${roleId}`, @@ -216,7 +221,7 @@ const AccountListTable = () => { // Handle custom selection - send campaignId and selected user IDs const request = { mediaBlastCampaignId: campaignId, - mediaBlastAccountIds: selectedAccount.map(acc => acc.id), + mediaBlastAccountIds: selectedAccount.map((acc) => acc.id), }; const response = await saveMediaBlastCampaignAccountBulk(request); if (response?.error) { @@ -224,7 +229,7 @@ const AccountListTable = () => { return; } } - + close(); successCallback("Akun berhasil ditambahkan ke campaign!"); resetDialogState(); @@ -247,7 +252,7 @@ const AccountListTable = () => { try { loading(); const response = await getUserListAll(); - + if (response?.data?.data?.content) { setUsersList(response.data.data.content); } @@ -265,15 +270,15 @@ const AccountListTable = () => { setFiltered(temp); }; - - const removeSelectedAccount = (accountId: string) => { - setSelectedAccount(selectedAccount.filter(acc => acc.id !== accountId)); + setSelectedAccount(selectedAccount.filter((acc) => acc.id !== accountId)); }; const getFilteredAccounts = () => { if (accountCategory === "kategori" && selectedCategory) { - return availableAccountsList.filter(acc => acc.category === selectedCategory); + return availableAccountsList.filter( + (acc) => acc.category === selectedCategory + ); } return availableAccountsList; }; @@ -291,7 +296,10 @@ const AccountListTable = () => { Pilih Akun - + Pilih Akun Untuk Campaign Ini @@ -350,15 +358,17 @@ const AccountListTable = () => { options={usersList.map((user: any) => ({ value: user.id, label: `${user.fullname} (${user.role?.name})`, - user: user + user: user, }))} value={selectedAccount.map((acc: any) => ({ value: acc.id, label: `${acc.fullname} (${acc.role?.name})`, - user: acc + user: acc, }))} onChange={(selectedOptions: any) => { - const selectedUsers = selectedOptions ? selectedOptions.map((option: any) => option.user) : []; + const selectedUsers = selectedOptions + ? selectedOptions.map((option: any) => option.user) + : []; setSelectedAccount(selectedUsers); }} placeholder="Cari dan pilih user..." @@ -369,14 +379,17 @@ const AccountListTable = () => { className="react-select" classNamePrefix="select" /> - + {/* Selected Accounts Display */} {selectedAccount.length > 0 && (
{selectedAccount.map((acc) => ( - + {acc.fullname} { {accountCategory === "kategori" && selectedCategory && (

- Semua akun dengan role "{selectedCategory.toUpperCase()}" akan ditambahkan. + Semua akun dengan role "{selectedCategory.toUpperCase()}" + akan ditambahkan.

)} @@ -412,7 +426,8 @@ const AccountListTable = () => { {accountCategory === "custom" && (

- {selectedAccount.length} user terpilih akan ditambahkan ke campaign ini. + {selectedAccount.length} user terpilih akan ditambahkan ke + campaign ini.

)} @@ -423,7 +438,8 @@ const AccountListTable = () => { onClick={saveCampaignAccount} disabled={ !accountCategory || - (accountCategory === "custom" && selectedAccount.length < 1) || + (accountCategory === "custom" && + selectedAccount.length < 1) || (accountCategory === "kategori" && !selectedCategory) } > @@ -441,7 +457,47 @@ const AccountListTable = () => {
{/* === Filter Akun === */} -
+
+ {/*
+ + + + +
*/} + -
@@ -304,3 +282,380 @@ export default function CreateAccountForBroadcast() {
); } + + +// "use client"; +// import SiteBreadcrumb from "@/components/site-breadcrumb"; +// import { z } from "zod"; +// import { useForm } from "react-hook-form"; +// import { zodResolver } from "@hookform/resolvers/zod"; +// import { +// Form, +// FormControl, +// FormField, +// FormItem, +// FormLabel, +// FormMessage, +// } from "@/components/ui/form"; +// import withReactContent from "sweetalert2-react-content"; +// import Swal from "sweetalert2"; +// import { Input } from "@/components/ui/input"; +// import { Button } from "@/components/ui/button"; +// import { +// getMediaBlastCampaignPage, +// saveMediaBlastAccount, +// } from "@/service/broadcast/broadcast"; +// import { error } from "@/config/swal"; +// import { useRouter } from "@/i18n/routing"; +// import { Checkbox } from "@/components/ui/checkbox"; +// import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"; +// import { useEffect, useState } from "react"; + +// // const FormSchema = z.object({ +// // name: z.string({ +// // required_error: "Required", +// // }), +// // accountType: z +// // .array(z.string()) +// // .refine((value) => value.some((item) => item), { +// // message: "Required", +// // }), +// // accountCategory: z.enum(["polri", "jurnalis", "umum", "ksp"], { +// // required_error: "Required", +// // }), +// // email: z.string({ +// // required_error: "Required", +// // }), +// // whatsapp: z.string({ +// // required_error: "Required", +// // }), +// // campaignId: z.string({ required_error: "Required" }), +// // }); +// const FormSchema = z +// .object({ +// name: z.string().min(1, "Required"), + +// accountType: z.array(z.string()).refine((value) => value.length > 0, { +// message: "Pilih minimal satu tipe akun", +// }), + +// accountCategory: z.enum(["polri", "jurnalis", "umum", "ksp"], { +// required_error: "Required", +// }), + +// email: z.string().optional(), +// whatsapp: z.string().optional(), + +// campaignId: z.string().min(1, "Required"), +// }) +// .refine( +// (data) => { +// if (data.accountType.includes("email")) { +// return !!data.email && data.email.trim() !== ""; +// } +// return true; +// }, +// { path: ["email"], message: "Email wajib diisi" } +// ) +// .refine( +// (data) => { +// if (data.accountType.includes("wa")) { +// return !!data.whatsapp && data.whatsapp.trim() !== ""; +// } +// return true; +// }, +// { path: ["whatsapp"], message: "Whatsapp wajib diisi" } +// ); + +// export default function CreateAccountForBroadcast() { +// const MySwal = withReactContent(Swal); +// const router = useRouter(); +// const form = useForm>({ +// resolver: zodResolver(FormSchema), +// defaultValues: { accountType: [] }, +// }); +// const selectedTypes = form.watch("accountType"); +// const [campaigns, setCampaigns] = useState([]); + +// useEffect(() => { +// fetchCampaignList(); +// }, []); + +// async function fetchCampaignList() { +// try { +// const res = await getMediaBlastCampaignPage(0); +// setCampaigns(res?.data?.data?.content ?? []); +// } catch (e) { +// console.log("Error fetch campaign:", e); +// } +// } + +// const onSubmit = async (data: z.infer) => { +// MySwal.fire({ +// title: "Simpan Data", +// text: "Apakah Anda yakin ingin menyimpan data ini?", +// icon: "warning", +// showCancelButton: true, +// cancelButtonColor: "#d33", +// confirmButtonColor: "#3085d6", +// confirmButtonText: "Simpan", +// }).then((result) => { +// if (result.isConfirmed) { +// save(data); +// } +// }); +// }; + +// function successSubmit() { +// MySwal.fire({ +// title: "Sukses", +// icon: "success", +// confirmButtonColor: "#3085d6", +// confirmButtonText: "OK", +// }).then((result) => { +// if (result.isConfirmed) { +// router.push("/admin/broadcast/campaign-list/account-list"); +// } +// }); +// } + +// const save = async (data: z.infer) => { +// const reqData = { +// accountName: data.name, +// accountType: data.accountType.join(","), +// accountCategory: data.accountCategory, +// emailAddress: data.email ?? "", +// whatsappNumber: data.whatsapp ?? "", +// campaignId: data.campaignId, +// }; +// console.log("data", data); + +// const response = await saveMediaBlastAccount(reqData); +// if (response?.error) { +// error(response.message); +// return false; +// } + +// successSubmit(); +// }; +// return ( +//
+// +//
+// +//

Account

+// ( +// +// Nama +// + +// +// +// )} +// /> +// ( +// +// Tipe Akun +//
+// {" "} +// { +// return ( +// +// +// { +// return checked +// ? field.onChange([...field.value, "wa"]) +// : field.onChange( +// field.value?.filter( +// (value) => value !== "wa" +// ) +// ); +// }} +// /> +// +// +// Whatsapp +// +// +// ); +// }} +// /> +// { +// return ( +// +// +// { +// return checked +// ? field.onChange([...field.value, "email"]) +// : field.onChange( +// field.value?.filter( +// (value) => value !== "email" +// ) +// ); +// }} +// /> +// +// Email +// +// ); +// }} +// /> +//
+ +// +//
+// )} +// /> +// {/* ( +// +// Kategori +// +// +// +// +// +// +// POLRI +// +// +// +// +// +// JURNALIS +// +// +// +// +// +// UMUM +// +// +// +// +// +// KSP +// +// +// +// +// +// )} +// /> */} +// ( +// +// Email +// + +// +// +// )} +// /> +// ( +// +// Nama +// + +// +// +// )} +// /> +// ( +// +// Campaign +// +// +// +// +// +// )} +// /> +//
+// +// +//
+// +// +//
+// ); +// } diff --git a/app/[locale]/(protected)/admin/broadcast/email/[id]/page.tsx b/app/[locale]/(protected)/admin/broadcast/create/[id]/page.tsx similarity index 100% rename from app/[locale]/(protected)/admin/broadcast/email/[id]/page.tsx rename to app/[locale]/(protected)/admin/broadcast/create/[id]/page.tsx diff --git a/app/[locale]/(protected)/admin/broadcast/email/component/column.tsx b/app/[locale]/(protected)/admin/broadcast/create/component/column.tsx similarity index 93% rename from app/[locale]/(protected)/admin/broadcast/email/component/column.tsx rename to app/[locale]/(protected)/admin/broadcast/create/component/column.tsx index 5e5c1abf..3414f44e 100644 --- a/app/[locale]/(protected)/admin/broadcast/email/component/column.tsx +++ b/app/[locale]/(protected)/admin/broadcast/create/component/column.tsx @@ -90,16 +90,16 @@ const columns: ColumnDef[] = [ Detail - + - Email Blast + Email & Whatsapp Blast - + {/* Whatsapp Blast - + */} ); diff --git a/app/[locale]/(protected)/admin/broadcast/email/component/table.tsx b/app/[locale]/(protected)/admin/broadcast/create/component/table.tsx similarity index 100% rename from app/[locale]/(protected)/admin/broadcast/email/component/table.tsx rename to app/[locale]/(protected)/admin/broadcast/create/component/table.tsx diff --git a/app/[locale]/(protected)/admin/broadcast/page.tsx b/app/[locale]/(protected)/admin/broadcast/page.tsx index d1ced467..7f2c8c8d 100644 --- a/app/[locale]/(protected)/admin/broadcast/page.tsx +++ b/app/[locale]/(protected)/admin/broadcast/page.tsx @@ -1,6 +1,6 @@ "use client"; import SiteBreadcrumb from "@/components/site-breadcrumb"; -import BroadcastTable from "./email/component/table"; +import BroadcastTable from "./create/component/table"; import { PlusIcon } from "lucide-react"; import EscalationTable from "../../shared/communication/escalation/components/escalation-table"; @@ -8,7 +8,7 @@ import InternalTable from "../../shared/communication/internal/components/intern import { useState } from "react"; import { Link } from "@/i18n/routing"; import { Button } from "@/components/ui/button"; -import BroadcastEmailTable from "./email/component/table"; +import BroadcastEmailTable from "./create/component/table"; import BroadcastWhatsAppTable from "./whatsapp/component/table"; export default function AdminBroadcast() { diff --git a/app/[locale]/(protected)/contributor/agenda-setting/event-modal.tsx b/app/[locale]/(protected)/contributor/agenda-setting/event-modal.tsx index 57445c81..7382f25c 100644 --- a/app/[locale]/(protected)/contributor/agenda-setting/event-modal.tsx +++ b/app/[locale]/(protected)/contributor/agenda-setting/event-modal.tsx @@ -109,7 +109,9 @@ const EventModal = ({ const pathname = usePathname(); const [isLoading, setIsLoading] = useState(false); const [checkedLevels, setCheckedLevels] = useState>(new Set()); - const [expandedPolda, setExpandedPolda] = useState>({}); + const [expandedPolda, setExpandedPolda] = useState>( + {} + ); const [audioFile, setAudioFile] = useState(null); const [isRecording, setIsRecording] = useState(false); const [timer, setTimer] = useState(120); @@ -153,9 +155,11 @@ const EventModal = ({ }); // State untuk melacak apakah perubahan berasal dari checkbox Jenis Agenda - const [isUpdatingFromJenisAgenda, setIsUpdatingFromJenisAgenda] = useState(false); + const [isUpdatingFromJenisAgenda, setIsUpdatingFromJenisAgenda] = + useState(false); // State untuk melacak jenis perubahan spesifik - const [jenisAgendaChangeType, setJenisAgendaChangeType] = useState(""); + const [jenisAgendaChangeType, setJenisAgendaChangeType] = + useState(""); const levelNumber = Number(getCookiesDecrypt("ulne")) || 0; const userLevelId = getCookiesDecrypt("ulie"); @@ -261,7 +265,11 @@ const EventModal = ({ // useEffect untuk sinkronisasi checkbox modal dengan Jenis Agenda useEffect(() => { - if (listDest.length > 0 && isUpdatingFromJenisAgenda && jenisAgendaChangeType) { + if ( + listDest.length > 0 && + isUpdatingFromJenisAgenda && + jenisAgendaChangeType + ) { syncModalWithJenisAgenda(); } }, [isUpdatingFromJenisAgenda, jenisAgendaChangeType]); @@ -273,44 +281,55 @@ const EventModal = ({ } }, [checkedLevels, isUpdatingFromJenisAgenda]); - // Fungsi untuk update wilayahPublish berdasarkan checkbox modal + // 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)) + 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 + + 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; - + + 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)) + .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) { @@ -321,7 +340,7 @@ const EventModal = ({ }); } }); - + const newSelectedSatker: string[] = []; if (satkerItem) { if (checkedLevels.has(Number(satkerItem.id))) { @@ -335,51 +354,56 @@ const EventModal = ({ }); } } - + // Update state arrays setSelectedPolda(newSelectedPolda); setSelectedPolres(newSelectedPolres); setSelectedSatker(newSelectedSatker); - + // Update wilayahPublish berdasarkan yang dipilih di modal - setWilayahPublish(prev => { + 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; + 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 + // 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 @@ -391,7 +415,11 @@ const EventModal = ({ } 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.levelNumber === 2 && + item.name !== "SATKER POLRI" && + newCheckedLevels.has(Number(item.id)) + ) { if (item.subDestination) { item.subDestination.forEach((polres: any) => { newCheckedLevels.add(Number(polres.id)); @@ -401,7 +429,9 @@ const EventModal = ({ }); } else if (jenisAgendaChangeType === "satker_checked") { // Checklist satker - const satkerItem: any = listDest.find((item: any) => item.name === "SATKER POLRI"); + const satkerItem: any = listDest.find( + (item: any) => item.name === "SATKER POLRI" + ); if (satkerItem) { newCheckedLevels.add(Number(satkerItem.id)); if (satkerItem.subDestination) { @@ -434,10 +464,12 @@ const EventModal = ({ } } }); - setWilayahPublish(prev => ({ ...prev, polres: false })); + setWilayahPublish((prev) => ({ ...prev, polres: false })); } else if (jenisAgendaChangeType === "satker_unchecked") { // Clear satker dari checkedLevels - const satkerItem: any = listDest.find((item: any) => item.name === "SATKER POLRI"); + const satkerItem: any = listDest.find( + (item: any) => item.name === "SATKER POLRI" + ); if (satkerItem) { newCheckedLevels.delete(Number(satkerItem.id)); if (satkerItem.subDestination) { @@ -447,9 +479,9 @@ const EventModal = ({ } } } - + setCheckedLevels(newCheckedLevels); - + // Reset flag setelah sinkronisasi selesai setIsUpdatingFromJenisAgenda(false); setJenisAgendaChangeType(""); @@ -484,12 +516,14 @@ const EventModal = ({ 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; + 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)); @@ -514,7 +548,12 @@ const EventModal = ({ 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")); + setJenisAgendaChangeType( + key + + (wilayahPublish[key as keyof typeof wilayahPublish] + ? "_unchecked" + : "_checked") + ); setWilayahPublish((prev: any) => { let newState = { ...prev }; @@ -551,18 +590,21 @@ const EventModal = ({ return newState; } - // Validasi khusus untuk POLRES + // 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)) + 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."); + alert( + "Harap pilih POLDA di Modal terlebih dahulu sebelum mengaktifkan checkbox POLRES." + ); // Reset flag karena perubahan dibatalkan setIsUpdatingFromJenisAgenda(false); setJenisAgendaChangeType(""); @@ -598,7 +640,9 @@ const EventModal = ({ }); } else if (key === "satker") { // Clear satker - const satkerItem: any = listDest.find((item: any) => item.name === "SATKER POLRI"); + const satkerItem: any = listDest.find( + (item: any) => item.name === "SATKER POLRI" + ); if (satkerItem) { newCheckedLevels.delete(Number(satkerItem.id)); if (satkerItem.subDestination) { @@ -613,9 +657,14 @@ const EventModal = ({ // 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 + // Untuk level 2: semua, polres harus aktif if (levelNumber === 1) { - newState.semua = newState.nasional && newState.polda && newState.polres && newState.satker && newState.international; + newState.semua = + newState.nasional && + newState.polda && + newState.polres && + newState.satker && + newState.international; } else if (levelNumber === 2) { newState.semua = newState.polres; } else { @@ -770,7 +819,7 @@ const EventModal = ({ const onDeleteEventAction = async () => { try { - } catch (error) { } + } catch (error) {} }; const handleOpenDeleteModal = (eventId: string) => { @@ -950,7 +999,7 @@ const EventModal = ({ ); }; - const handleRemoveFile = (id: number) => { }; + const handleRemoveFile = (id: number) => {}; async function doDelete(id: any) { loading(); @@ -1106,40 +1155,42 @@ const EventModal = ({
- -
- toggleWilayah("semua")} - /> - -
- - {levelNumber === 1 && ( - <> -
- toggleWilayah("nasional")} - /> - -
toggleWilayah("polda")} + id="semua" + checked={wilayahPublish.semua} + onCheckedChange={() => toggleWilayah("semua")} /> -
+ + {levelNumber === 1 && ( + <> +
+ toggleWilayah("nasional")} + /> + +
+
+ toggleWilayah("polda")} + /> + +
)} @@ -1151,39 +1202,41 @@ const EventModal = ({ onCheckedChange={() => toggleWilayah("polres")} />
)} {levelNumber === 1 && ( <> -
- toggleWilayah("satker")} - /> - -
-
- toggleWilayah("international")} - /> - -
+
+ toggleWilayah("satker")} + /> + +
+
+ + toggleWilayah("international") + } + /> + +
)} - +
@@ -1202,7 +1255,9 @@ const EventModal = ({
- {polda?.subDestination?.map((polres: any) => ( - - ))} + {polda?.subDestination?.map( + (polres: any) => ( + + ) + )}
)}
@@ -1304,8 +1372,7 @@ const EventModal = ({
- { - return import("@/components/editor/custom-editor"); - }, + () => import("@/components/editor/custom-editor"), { ssr: false } ); const animatedComponent = makeAnimated(); +// ------------------------- +// ZOD SCHEMA (baru) +// ------------------------- const FormSchema = z.object({ - title: z.string({ - required_error: "Required", - }), - url: z.string({ - required_error: "Required", - }), - thumbnail: z.string({ - required_error: "Required", - }), - detail: z.string({ - required_error: "Required", - }), selected: z .array( z.object({ @@ -73,9 +62,16 @@ const FormSchema = z.object({ value: z.string(), }) ) - .refine((value) => value.length > 0, { - message: "Required", - }), + .min(1, "Pilih minimal satu campaign"), + + title: z.string().min(1, "Required"), + url: z.string().min(1, "Required"), + thumbnail: z.string().min(1, "Required"), + detail: z.string().min(1, "Required"), + + messageType: z + .array(z.string()) + .min(1, "Pilih minimal satu tipe pesan (WA / Email)"), }); interface Campaign { @@ -88,66 +84,77 @@ export default function ContentBlast(props: { type: string }) { const id = useParams()?.id; const MySwal = withReactContent(Swal); const router = useRouter(); - const { type } = props; const [dataSelectCampaign, setDataSelectCampaign] = useState([]); const [openModal, setOpenModal] = useState(false); const form = useForm>({ resolver: zodResolver(FormSchema), - defaultValues: { selected: [], detail: "" }, + defaultValues: { + selected: [], + detail: "", + messageType: [], // checkbox + }, }); + const selectedTypes = form.watch("messageType"); + const onSubmit = async (data: z.infer) => { - if (form.getValues("detail") == "") { - form.setError("detail", { - type: "manual", - message: "Required", - }); - } else { - MySwal.fire({ - title: "Simpan Data", - text: "Apakah Anda yakin ingin menyimpan data ini?", - icon: "warning", - showCancelButton: true, - cancelButtonColor: "#d33", - confirmButtonColor: "#3085d6", - confirmButtonText: "Simpan", - }).then((result) => { - if (result.isConfirmed) { - save(data); - } - }); - } + MySwal.fire({ + title: "Kirim Broadcast?", + text: "Pesan akan dikirim ke semua campaign yang dipilih", + icon: "warning", + showCancelButton: true, + cancelButtonColor: "#d33", + confirmButtonColor: "#3085d6", + confirmButtonText: "Kirim", + }).then((result) => { + if (result.isConfirmed) save(data); + }); }; + // ------------------------- + // SAVE LOGIC (baru) + // ------------------------- const save = async (data: z.infer) => { - const selectedCampaign = form.getValues("selected"); + const selectedCampaign = data.selected; for (let i = 0; i < selectedCampaign.length; i++) { - const reqData = { - mediaUploadId: id, - mediaBlastCampaignId: selectedCampaign[i].id, - subject: type == "wa" ? `*${data.title}*` : data.title, - body: data.detail?.replace(/\n/g, ""), - type: type, - isScheduled: false, - thumbnail: data?.thumbnail, - sendDate: getLocaleTimestamp(new Date()), - sendTime: getLocaleTime(new Date()), - contentUrl: data.url, - }; + const campaignId = selectedCampaign[i].id; - console.log("req =>", reqData); - const response = await saveMediaBlastBroadcast(reqData); + // Loop WA / Email + for (let mt of data.messageType) { + const payload = { + mediaUploadId: id, + mediaBlastCampaignId: campaignId, + subject: mt === "wa" ? `*${data.title}*` : data.title, + body: data.detail.replace(/\n/g, ""), + type: mt, // <-- WA / email + isScheduled: false, + thumbnail: data.thumbnail, + sendDate: getLocaleTimestamp(new Date()), + sendTime: getLocaleTime(new Date()), + contentUrl: data.url, + }; + + console.log("REQ =>", payload); + await saveMediaBlastBroadcast(payload); + } } + setOpenModal(true); }; useEffect(() => { - async function initState() { + async function init() { const response = await detailMediaSummary(String(id)); const details = response?.data?.data; + + if (!details) return; + + form.setValue("thumbnail", details.smallThumbnailLink); + form.setValue("title", details.title); + form.setValue("url", details.pageUrl); let pageUrl = details?.pageUrl || ""; if (pageUrl.includes("mediahub.polri.go.id")) { pageUrl = pageUrl.replace( @@ -157,59 +164,43 @@ export default function ContentBlast(props: { type: string }) { } ); } - if (details != undefined) { - form.setValue("thumbnail", details.smallThumbnailLink); - let body = `

Berita hari ini !!!

-
-
- -
- ${pageUrl} -

${details?.title}

-

- ${textEllipsis(details?.description, 150)} -

-
-
`; - form.setValue("title", `${details?.title}`); - form.setValue( - "url", - details?.pageUrl || "https://mediahub.polri.go.id" - ); - if (type == "wa") { - body = `${textEllipsis(details?.description, 150)}`; - form.setValue("detail", body); - } else { - form.setValue("detail", body); - } - } + + const body = `

Berita hari ini !!!

+
+
+ +
+ ${pageUrl} + +

${details.title}

+

+ ${textEllipsis(details.description, 150)} +

+
+
`; + + form.setValue("detail", body); } - async function getCampaign() { + async function loadCampaign() { const response = await getMediaBlastCampaignList(); const campaign = response?.data?.data?.content; handleLabelCampaign(campaign); - console.log(campaign); } - initState(); - getCampaign(); + init(); + loadCampaign(); }, [id]); function handleLabelCampaign(data: any) { - const optionArr: any = []; - - data.map((option: any) => { - optionArr.push({ - id: option.id, - label: option.title, - value: option.title, - }); - }); - console.log("option arr", optionArr); - setDataSelectCampaign(optionArr); + const arr = data.map((item: any) => ({ + id: item.id, + label: item.title, + value: item.title, + })); + setDataSelectCampaign(arr); } return ( @@ -218,104 +209,127 @@ export default function ContentBlast(props: { type: string }) { onSubmit={form.handleSubmit(onSubmit)} className="space-y-3 bg-white rounded-sm p-4" > -

Broadcast

+

Broadcast

+ + {/* SELECT CAMPAIGN */} ( - Subject + Pilih Campaign - + Judul + )} /> + + {/* DETAIL */} ( - Detail Perencanaan - {type === "wa" ? ( -