This commit is contained in:
hanif salafi 2025-08-27 08:52:09 +07:00
commit ca5a150015
12 changed files with 2346 additions and 1407 deletions

View File

@ -101,10 +101,8 @@ const useTableColumns = () => {
enableHiding: false, enableHiding: false,
cell: ({ row }) => { cell: ({ row }) => {
const MySwal = withReactContent(Swal); const MySwal = withReactContent(Swal);
const levelNumber = Number(getCookiesDecrypt("ulne")); // 1 = Mabes, 2 = Polda const levelNumber = Number(getCookiesDecrypt("ulne"));
const calendarOwnerLevel = Number(row.original.assignedToLevel); // dari API const userId = Number(getCookiesDecrypt("uie"));
const calendarOwner = row.original.assignedTo; // ID unit Polda/Mabes dari API
const myUnit = getCookiesDecrypt("unitId"); // misal ID unit Polda login
async function doDelete(id: any) { async function doDelete(id: any) {
const response = await deleteCalendar(id); const response = await deleteCalendar(id);
@ -143,29 +141,6 @@ const useTableColumns = () => {
} }
}); });
}; };
// === RULE AKSI ===
let canEdit = false;
let canDelete = false;
const canView = true;
if (levelNumber === 1) {
// Mabes -> bebas
canEdit = true;
canDelete = true;
} else if (levelNumber === 2) {
// Polda
if (calendarOwnerLevel === 1) {
// kalender Mabes -> hanya view
canEdit = false;
canDelete = false;
} else if (calendarOwnerLevel === 2 && calendarOwner === myUnit) {
// kalender polda sendiri -> bisa edit/delete
canEdit = true;
canDelete = true;
}
}
return ( return (
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger asChild> <DropdownMenuTrigger asChild>
@ -178,7 +153,7 @@ const useTableColumns = () => {
</Button> </Button>
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent className="p-0" align="end"> <DropdownMenuContent className="p-0" align="end">
{canView && (
<Link <Link
href={`/contributor/schedule/calendar-polri/detail/${row.original.id}`} href={`/contributor/schedule/calendar-polri/detail/${row.original.id}`}
> >
@ -187,9 +162,9 @@ const useTableColumns = () => {
Detail Detail
</DropdownMenuItem> </DropdownMenuItem>
</Link> </Link>
)}
{canEdit && (
{row.original.createdById === userId && (
<Link <Link
href={`/contributor/schedule/calendar-polri/update/${row.original.id}`} href={`/contributor/schedule/calendar-polri/update/${row.original.id}`}
> >
@ -200,7 +175,7 @@ const useTableColumns = () => {
</Link> </Link>
)} )}
{canDelete && ( {row.original.createdById === userId && (
<DropdownMenuItem <DropdownMenuItem
onClick={() => handleDeleteCalendars(row.original.id)} onClick={() => handleDeleteCalendars(row.original.id)}
className="p-2 border-b text-destructive bg-destructive/30 focus:bg-destructive focus:text-destructive-foreground rounded-none" className="p-2 border-b text-destructive bg-destructive/30 focus:bg-destructive focus:text-destructive-foreground rounded-none"

View File

@ -2,6 +2,7 @@
import { Reveal } from "@/components/landing-page/Reveal"; import { Reveal } from "@/components/landing-page/Reveal";
import React, { useState } from "react"; import React, { useState } from "react";
import { useTranslations } from "next-intl"; import { useTranslations } from "next-intl";
import Image from "next/image";
interface FAQItem { interface FAQItem {
question: string; question: string;
@ -41,19 +42,41 @@ const FAQS: React.FC = () => {
{/* Header */} {/* Header */}
<Reveal> <Reveal>
<div className="flex items-center justify-center mb-6"> <div className="flex items-center justify-center mb-6">
<img src="/assets/icons-faqs.png" alt="Faqs" /> <Image
<h2 className="ml-4 text-lg lg:text-2xl font-bold text-gray-800 dark:text-white">Frequently Asked Questions</h2> width={1920}
height={1080}
src="/assets/icons-faqs.png"
alt="Faqs"
className="h-12 w-12"
/>
<h2 className="ml-4 text-lg lg:text-2xl font-bold text-gray-800 dark:text-white">
Frequently Asked Questions
</h2>
</div> </div>
{/* FAQS Items */} {/* FAQS Items */}
<div className="space-y-4"> <div className="space-y-4">
{faqs?.map((faq, index) => ( {faqs?.map((faq, index) => (
<div key={index} className="border-b border-gray-300 pb-2 cursor-pointer"> <div
<div className="flex justify-between items-center" onClick={() => toggleFAQ(index)}> key={index}
<h3 className="text-sm font-semibold text-gray-800 dark:text-white">{faq.question}</h3> className="border-b border-gray-300 pb-2 cursor-pointer"
<span className="text-gray-500 dark:text-white text-xl">{openIndex === index ? "" : "+"}</span> >
<div
className="flex justify-between items-center"
onClick={() => toggleFAQ(index)}
>
<h3 className="text-sm font-semibold text-gray-800 dark:text-white">
{faq.question}
</h3>
<span className="text-gray-500 dark:text-white text-xl">
{openIndex === index ? "" : "+"}
</span>
</div> </div>
{openIndex === index && <p className="text-gray-600 dark:text-white mt-2 text-sm">{faq.answer}</p>} {openIndex === index && (
<p className="text-gray-600 dark:text-white mt-2 text-sm">
{faq.answer}
</p>
)}
</div> </div>
))} ))}
</div> </div>

View File

@ -27,7 +27,7 @@ import {
saveMediaBlastCampaign, saveMediaBlastCampaign,
} from "@/service/broadcast/broadcast"; } from "@/service/broadcast/broadcast";
import { error } from "@/config/swal"; import { error } from "@/config/swal";
import { useRouter } from "@/i18n/routing"; import { Link, useRouter } from "@/i18n/routing";
import { useEffect, useRef, useState } from "react"; import { useEffect, useRef, useState } from "react";
import { useParams } from "next/navigation"; import { useParams } from "next/navigation";
@ -78,6 +78,11 @@ const FormSchema = z.object({
}), }),
}); });
interface Campaign {
id: string;
name: string;
}
export default function ContentBlast(props: { type: string }) { export default function ContentBlast(props: { type: string }) {
const editor = useRef(null); const editor = useRef(null);
const id = useParams()?.id; const id = useParams()?.id;
@ -85,7 +90,7 @@ export default function ContentBlast(props: { type: string }) {
const router = useRouter(); const router = useRouter();
const { type } = props; const { type } = props;
const [dataSelectCampaign, setDataSelectCampaign] = useState([]); const [dataSelectCampaign, setDataSelectCampaign] = useState<Campaign[]>([]);
const [openModal, setOpenModal] = useState(false); const [openModal, setOpenModal] = useState(false);
const form = useForm<z.infer<typeof FormSchema>>({ const form = useForm<z.infer<typeof FormSchema>>({
@ -295,9 +300,16 @@ export default function ContentBlast(props: { type: string }) {
Untuk melihat Email Terkirim silahkan cek menu Sent! Untuk melihat Email Terkirim silahkan cek menu Sent!
</div> </div>
<DialogFooter className="flex justify-center"> <DialogFooter className="flex justify-center">
<Button type="button" color="success"> <Link
Menu "Sent" href={`/admin/broadcast/campaign-list/detail/${
</Button> form.getValues("selected")[0]?.id
}`}
>
<Button type="button" color="success">
Menu "Sent"
</Button>
</Link>
<Button <Button
type="button" type="button"
onClick={() => router.push("/admin/broadcast")} onClick={() => router.push("/admin/broadcast")}

View File

@ -200,6 +200,12 @@ export default function FormAudioDetail() {
}> }>
>([]); >([]);
useEffect(() => {
if (Number(userLevelId) === 216 && Number(roleId) === 3) {
setIsUserMabesApprover(true);
}
}, [userLevelId, roleId]);
// Fungsi untuk mengupdate state individual file // Fungsi untuk mengupdate state individual file
const handleFileUnitChange = ( const handleFileUnitChange = (
fileIndex: number, fileIndex: number,
@ -219,6 +225,30 @@ export default function FormAudioDetail() {
currentSelection.polda = value; currentSelection.polda = value;
currentSelection.polres = value; currentSelection.polres = value;
currentSelection.satker = value; currentSelection.satker = value;
// Update fileCheckedLevels untuk sinkronisasi dengan modal
setFileCheckedLevels((prevLevels) => {
const newArray = [...prevLevels];
const currentFileLevels = new Set<number>(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 { } else {
// Validasi khusus untuk POLRES - harus ada POLDA yang ter-checklist // Validasi khusus untuk POLRES - harus ada POLDA yang ter-checklist
if (key === "polres" && value) { if (key === "polres" && value) {
@ -480,10 +510,28 @@ export default function FormAudioDetail() {
const setupPlacementCheck = (length: number) => { const setupPlacementCheck = (length: number) => {
const temp = []; const temp = [];
const unitSelections = [];
const checkedLevelsArray = [];
for (let i = 0; i < length; i++) { for (let i = 0; i < length; i++) {
temp.push([]); temp.push([]);
// Inisialisasi state untuk setiap file
unitSelections.push({
semua: false,
nasional: false,
wilayah: false,
international: false,
polda: false,
polres: false,
satker: false,
});
// Inisialisasi checkedLevels untuk setiap file
checkedLevelsArray.push(new Set<number>());
} }
setFilePlacements(temp); setFilePlacements(temp);
setFileUnitSelections(unitSelections);
setFileCheckedLevels(checkedLevelsArray);
}; };
useEffect(() => { useEffect(() => {
@ -587,8 +635,24 @@ export default function FormAudioDetail() {
const temp = []; const temp = [];
for (let i = 0; i < filePlacements?.length; i++) { for (let i = 0; i < filePlacements?.length; i++) {
if (filePlacements[i]?.length !== 0) { if (filePlacements[i]?.length !== 0) {
const now = filePlacements[i]?.filter((a) => a !== "all"); // const now = filePlacements[i]?.filter((a) => a !== "all");
const data = { mediaFileId: files[i]?.id, placements: now.join(",") }; // const data = { mediaFileId: files[i]?.id, placements: now.join(",") };
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); temp.push(data);
} }
} }
@ -596,13 +660,19 @@ export default function FormAudioDetail() {
}; };
async function save() { async function save() {
// Gabungkan semua checkedLevels dari semua file
const allCheckedLevels = new Set<number>();
fileCheckedLevels.forEach((fileLevels) => {
fileLevels.forEach((levelId) => {
allCheckedLevels.add(levelId);
});
});
const data = { const data = {
mediaUploadId: id, mediaUploadId: id,
statusId: status, statusId: status,
message: description, message: description,
// files: [],
files: isUserMabesApprover ? getPlacement() : [], files: isUserMabesApprover ? getPlacement() : [],
customLocationPlacement: Array.from(checkedLevels).join(","),
}; };
setModalOpen(false); setModalOpen(false);
loading(); loading();
@ -638,37 +708,129 @@ export default function FormAudioDetail() {
setRejectedFiles(rejects); setRejectedFiles(rejects);
} }
// const setupPlacement = ( const setupPlacement = (
// index: number, index: number,
// placement: string, placement: string,
// checked: boolean checked: boolean
// ) => { ) => {
// let temp = [...filePlacements]; let temp = [...filePlacements];
// if (checked) { if (checked) {
// if (placement === "all") { if (placement === "all") {
// temp[index] = ["all", "mabes", "polda", "international"]; temp[index] = ["all", "mabes", "polda", "international"];
// } else {
// const now = temp[index]; // Update fileCheckedLevels untuk sinkronisasi dengan modal ketika "all" diklik
// now?.push(placement); setFileCheckedLevels((prevLevels) => {
// if (now?.length === 3 && !now?.includes("all")) { const newArray = [...prevLevels];
// now?.push("all"); const currentFileLevels = new Set<number>(newArray[index] || new Set());
// }
// temp[index] = now; // Checklist semua item di modal
// } listDest.forEach((item: any) => {
// } else { currentFileLevels.add(Number(item.id));
// if (placement === "all") { if (item.subDestination) {
// temp[index] = []; item.subDestination.forEach((sub: any) => {
// } else { currentFileLevels.add(Number(sub.id));
// const now = temp[index].filter((a) => a !== placement); });
// temp[index] = now; }
// if (now?.length === 3 && now?.includes("all")) { });
// const newData = now.filter((b) => b !== "all");
// temp[index] = newData; newArray[index] = currentFileLevels;
// } return newArray;
// } });
// }
// setFilePlacements(temp); // 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<number>(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);
// Update checklist levels di modal berdasarkan placement yang diubah
updateModalChecklistLevels(index, placement, checked);
};
const handleMain = ( const handleMain = (
type: string, type: string,
@ -706,62 +868,7 @@ export default function FormAudioDetail() {
}); });
}; };
const setupPlacement = (
index: number,
placement: string,
checked: boolean
) => {
let temp = [...filePlacements];
if (checked) {
if (placement === "all") {
temp[index] = ["all", "mabes", "polda", "international"];
} 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] = [];
} 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);
// Update checklist levels di modal berdasarkan placement yang diubah
updateModalChecklistLevels(index, placement, checked);
};
const updateModalChecklistLevels = ( const updateModalChecklistLevels = (
fileIndex: number, fileIndex: number,
@ -992,6 +1099,15 @@ export default function FormAudioDetail() {
currentSelection.polres = checkedPolresCount > 0; currentSelection.polres = checkedPolresCount > 0;
currentSelection.satker = Boolean(isSatkerChecked); 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; newSelections[fileIndex] = currentSelection;
return newSelections; return newSelections;
}); });
@ -1303,6 +1419,13 @@ export default function FormAudioDetail() {
<p className="text-sm text-gray-600 break-all"> <p className="text-sm text-gray-600 break-all">
{file.fileName} {file.fileName}
</p> </p>
{isUserMabesApprover ? (
""
) : (
<p className="status text-success text-sm mb-0">
Selesai
</p>
)}
</div> </div>
</div> </div>
<button <button
@ -1320,84 +1443,36 @@ export default function FormAudioDetail() {
</div> </div>
{/* Section Pengaturan Distribusi */} {/* Section Pengaturan Distribusi */}
<div className="bg-white rounded-md p-4 border"> {isUserMabesApprover ? (
<h5 className="font-medium text-gray-900 mb-4 flex items-center gap-2"> <div className="bg-white rounded-md p-4 border">
<Icon <h5 className="font-medium text-gray-900 mb-4 flex items-center gap-2">
icon="material-symbols:settings-outline" <Icon
width={18} icon="material-symbols:settings-outline"
height={18} width={18}
/> height={18}
Pengaturan Distribusi />
</h5> Pengaturan Distribusi
</h5>
{/* Checkbox Tingkat Utama */} {/* Checkbox Tingkat Utama */}
<div className="space-y-4"> <div className="space-y-4">
<div> <div>
<p className="text-sm font-medium text-gray-700 mb-3"> <p className="text-sm font-medium text-gray-700 mb-3">
Tingkat Distribusi: Tingkat Distribusi:
</p>
<div className="grid grid-cols-2 md:grid-cols-4 gap-3">
{[
{ key: "semua", label: "Semua" },
{ key: "nasional", label: "Nasional" },
{ key: "wilayah", label: "Wilayah" },
{
key: "international",
label: "Internasional",
},
].map((item, idx) => (
<div
key={item.key}
className="flex items-center gap-2 p-2 border border-gray-200 rounded-md hover:bg-gray-50"
>
<Checkbox
id={`${item.key}-${index}`}
checked={
fileUnitSelections[index]?.[
item.key as keyof typeof unitSelection
] || false
}
onCheckedChange={(value) => {
handleFileUnitChange(
index,
item.key as keyof typeof unitSelection,
value as boolean
);
setupPlacement(
index,
item.key,
Boolean(value)
);
}}
/>
<Label
htmlFor={`${item.key}-${index}`}
className="text-sm font-medium cursor-pointer"
>
{item.label}
</Label>
</div>
))}
</div>
</div>
{/* Detail Wilayah */}
{fileUnitSelections[index]?.wilayah && (
<div className="border-t border-gray-200 pt-2">
<p className="text-sm font-medium text-gray-700 mb-2">
Detail Wilayah:
</p> </p>
<div className="grid grid-cols-2 md:grid-cols-4 gap-3">
{/* Checkbox Sub-kategori dengan tombol Kustom sejajar */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-3">
{[ {[
{ key: "polda", label: "POLDA" }, { key: "semua", label: "Semua" },
{ key: "polres", label: "POLRES" }, { key: "nasional", label: "Nasional" },
{ key: "satker", label: "SATKER" }, { key: "wilayah", label: "Wilayah" },
{
key: "international",
label: "Internasional",
},
].map((item, idx) => ( ].map((item, idx) => (
<div <div
key={item.key} key={item.key}
className="flex items-center gap-2 p-3 border border-gray-200 rounded-md hover:bg-gray-50" className="flex items-center gap-2 p-2 border border-gray-200 rounded-md hover:bg-gray-50"
> >
<Checkbox <Checkbox
id={`${item.key}-${index}`} id={`${item.key}-${index}`}
@ -1427,199 +1502,260 @@ export default function FormAudioDetail() {
</Label> </Label>
</div> </div>
))} ))}
</div>
</div>
{/* Tombol Kustom sejajar dengan checkbox */} {/* Detail Wilayah */}
<div className="flex items-center justify-center p-3"> {fileUnitSelections[index]?.wilayah && (
<Dialog> <div className="border-t border-gray-200 pt-2">
<DialogTrigger asChild> <p className="text-sm font-medium text-gray-700 mb-2">
<Button Detail Wilayah:
variant="outline" </p>
size="sm"
className="gap-2" {/* Checkbox Sub-kategori dengan tombol Kustom sejajar */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-3">
{[
{ key: "polda", label: "POLDA" },
{ key: "polres", label: "POLRES" },
{ key: "satker", label: "SATKER" },
].map((item, idx) => (
<div
key={item.key}
className="flex items-center gap-2 p-3 border border-gray-200 rounded-md hover:bg-gray-50"
>
<Checkbox
id={`${item.key}-${index}`}
checked={
fileUnitSelections[index]?.[
item.key as keyof typeof unitSelection
] || false
}
onCheckedChange={(value) => {
handleFileUnitChange(
index,
item.key as keyof typeof unitSelection,
value as boolean
);
setupPlacement(
index,
item.key,
Boolean(value)
);
}}
/>
<Label
htmlFor={`${item.key}-${index}`}
className="text-sm font-medium cursor-pointer"
> >
<Icon {item.label}
icon="material-symbols:tune" </Label>
width={16} </div>
height={16} ))}
/>
{t("custom", {
defaultValue: "Kustom",
})}
</Button>
</DialogTrigger>
<DialogContent className="max-w-[95vw] lg:max-w-[1400px] max-h-[90vh]">
<DialogHeader className="border-b border-gray-200 pb-4">
<DialogTitle className="text-lg font-semibold">
Daftar Wilayah POLDA dan POLRES
</DialogTitle>
</DialogHeader>
<div className="grid grid-cols-1 lg:grid-cols-2 xl:grid-cols-3 gap-4 max-h-[70vh] overflow-y-auto p-1">
{listDest.map((polda: any) => (
<div
key={polda.id}
className="border border-gray-200 rounded-lg p-2 bg-white hover:shadow-sm transition-shadow"
>
{/* Header POLDA */}
<div className="flex items-center justify-between">
<Label className="flex items-center gap-3 flex-1 cursor-pointer">
<Checkbox
checked={
fileCheckedLevels[
index
]?.has(
Number(polda.id)
) || false
}
onCheckedChange={() =>
handleFileCheckboxChangePlacement(
index,
Number(polda.id)
)
}
/>
<span className="font-semibold text-gray-900 text-sm">
{polda.name}
</span>
</Label>
{polda.subDestination && (
<button
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
toggleExpand(polda.id);
}}
className="p-1 hover:bg-gray-100 rounded-md transition-colors"
>
<Icon
icon={
expandedPolda[
polda.id
]
? "mdi:chevron-up"
: "mdi:chevron-down"
}
width={16}
height={16}
/>
</button>
)}
</div>
{/* Sub-items */} {/* Tombol Kustom sejajar dengan checkbox */}
{polda.subDestination && <div className="flex items-center justify-center p-3">
expandedPolda[polda.id] && ( <Dialog>
<div className="max-h-[200px] overflow-y-auto border-t border-gray-100 pt-2"> <DialogTrigger asChild>
{/* Tombol Pilih Semua untuk sub-items */} <Button
<div className="mb-2 flex justify-start"> variant="outline"
{(() => { size="sm"
const allSubItemsChecked = className="gap-2"
polda.subDestination?.every( >
(sub: any) => <Icon
fileCheckedLevels[ icon="material-symbols:tune"
index width={16}
]?.has( height={16}
Number(sub.id) />
) {t("custom", {
); defaultValue: "Kustom",
return ( })}
<Button </Button>
size="sm" </DialogTrigger>
variant="outline" <DialogContent className="max-w-[95vw] lg:max-w-[1400px] max-h-[90vh]">
className="text-xs h-6 px-2" <DialogHeader className="border-b border-gray-200 pb-4">
onClick={() => <DialogTitle className="text-lg font-semibold">
handleSelectAllSubItems( Daftar Wilayah POLDA dan POLRES
index, </DialogTitle>
polda </DialogHeader>
) <div className="grid grid-cols-1 lg:grid-cols-2 xl:grid-cols-3 gap-4 max-h-[70vh] overflow-y-auto p-1">
} {listDest.map((polda: any) => (
> <div
{allSubItemsChecked ? ( key={polda.id}
<> className="border border-gray-200 rounded-lg p-2 bg-white hover:shadow-sm transition-shadow"
<Icon >
icon="material-symbols:check-indeterminate-small" {/* Header POLDA */}
width={12} <div className="flex items-center justify-between">
height={12} <Label className="flex items-center gap-3 flex-1 cursor-pointer">
className="mr-1" <Checkbox
/> checked={
Batal Semua fileCheckedLevels[
</> index
) : ( ]?.has(
<> Number(polda.id)
<Icon ) || false
icon="material-symbols:check-all" }
width={12} onCheckedChange={() =>
height={12} handleFileCheckboxChangePlacement(
className="mr-1" index,
/> Number(polda.id)
Pilih Semua )
</> }
)} />
</Button> <span className="font-semibold text-gray-900 text-sm">
{polda.name}
</span>
</Label>
{polda.subDestination && (
<button
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
toggleExpand(
polda.id
); );
})()} }}
</div> className="p-1 hover:bg-gray-100 rounded-md transition-colors"
<div className="space-y-1"> >
{polda.subDestination.map( <Icon
(sub: any) => ( icon={
<Label expandedPolda[
key={sub.id} polda.id
className="flex items-center gap-2 p-2 rounded-md hover:bg-gray-50 transition-colors cursor-pointer text-xs" ]
> ? "mdi:chevron-up"
<Checkbox : "mdi:chevron-down"
checked={ }
width={16}
height={16}
/>
</button>
)}
</div>
{/* Sub-items */}
{polda.subDestination &&
expandedPolda[polda.id] && (
<div className="max-h-[200px] overflow-y-auto border-t border-gray-100 pt-2">
{/* Tombol Pilih Semua untuk sub-items */}
<div className="mb-2 flex justify-start">
{(() => {
const allSubItemsChecked =
polda.subDestination?.every(
(sub: any) =>
fileCheckedLevels[ fileCheckedLevels[
index index
]?.has( ]?.has(
Number( Number(
sub.id sub.id
) )
) || false )
} );
onCheckedChange={() => return (
handleFileCheckboxChangePlacement( <Button
size="sm"
variant="outline"
className="text-xs h-6 px-2"
onClick={() =>
handleSelectAllSubItems(
index, index,
Number( polda
sub.id
)
) )
} }
/> >
<span className="text-gray-700"> {allSubItemsChecked ? (
{sub.name} <>
</span> <Icon
</Label> icon="material-symbols:check-indeterminate-small"
) width={12}
)} height={
12
}
className="mr-1"
/>
Batal Semua
</>
) : (
<>
<Icon
icon="material-symbols:check-all"
width={12}
height={
12
}
className="mr-1"
/>
Pilih Semua
</>
)}
</Button>
);
})()}
</div>
<div className="space-y-1">
{polda.subDestination.map(
(sub: any) => (
<Label
key={sub.id}
className="flex items-center gap-2 p-2 rounded-md hover:bg-gray-50 transition-colors cursor-pointer text-xs"
>
<Checkbox
checked={
fileCheckedLevels[
index
]?.has(
Number(
sub.id
)
) || false
}
onCheckedChange={() =>
handleFileCheckboxChangePlacement(
index,
Number(
sub.id
)
)
}
/>
<span className="text-gray-700">
{sub.name}
</span>
</Label>
)
)}
</div>
</div> </div>
</div> )}
)} </div>
</div> ))}
))} </div>
</div> <div className="flex justify-end gap-3 border-t border-gray-200 pt-4">
<div className="flex justify-end gap-3 border-t border-gray-200 pt-4"> <DialogClose asChild>
<DialogClose asChild> <Button variant="outline">
<Button variant="outline"> {t("cancel", {
{t("cancel", { defaultValue: "Batal",
defaultValue: "Batal", })}
})} </Button>
</Button> </DialogClose>
</DialogClose> <DialogClose asChild>
<DialogClose asChild> <Button>
<Button> {/* {t("save", {
{t("save", { defaultValue: "Simpan",
defaultValue: "Simpan", })} */}
})} Simpan
</Button> </Button>
</DialogClose> </DialogClose>
</div> </div>
</DialogContent> </DialogContent>
</Dialog> </Dialog>
</div>
</div> </div>
</div> </div>
</div> )}
)} </div>
</div> </div>
</div> ) : (
""
)}
</div> </div>
))} ))}
</div> </div>

File diff suppressed because it is too large Load Diff

View File

@ -189,6 +189,12 @@ export default function FormTeksDetail() {
}> }>
>([]); >([]);
useEffect(() => {
if (Number(userLevelId) === 216 && Number(roleId) === 3) {
setIsUserMabesApprover(true);
}
}, [userLevelId, roleId]);
// Fungsi untuk mengupdate state individual file // Fungsi untuk mengupdate state individual file
const handleFileUnitChange = ( const handleFileUnitChange = (
fileIndex: number, fileIndex: number,
@ -208,6 +214,30 @@ export default function FormTeksDetail() {
currentSelection.polda = value; currentSelection.polda = value;
currentSelection.polres = value; currentSelection.polres = value;
currentSelection.satker = value; currentSelection.satker = value;
// Update fileCheckedLevels untuk sinkronisasi dengan modal
setFileCheckedLevels((prevLevels) => {
const newArray = [...prevLevels];
const currentFileLevels = new Set<number>(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 { } else {
// Validasi khusus untuk POLRES - harus ada POLDA yang ter-checklist // Validasi khusus untuk POLRES - harus ada POLDA yang ter-checklist
if (key === "polres" && value) { if (key === "polres" && value) {
@ -440,10 +470,28 @@ export default function FormTeksDetail() {
const setupPlacementCheck = (length: number) => { const setupPlacementCheck = (length: number) => {
const temp = []; const temp = [];
const unitSelections = [];
const checkedLevelsArray = [];
for (let i = 0; i < length; i++) { for (let i = 0; i < length; i++) {
temp.push([]); temp.push([]);
// Inisialisasi state untuk setiap file
unitSelections.push({
semua: false,
nasional: false,
wilayah: false,
international: false,
polda: false,
polres: false,
satker: false,
});
// Inisialisasi checkedLevels untuk setiap file
checkedLevelsArray.push(new Set<number>());
} }
setFilePlacements(temp); setFilePlacements(temp);
setFileUnitSelections(unitSelections);
setFileCheckedLevels(checkedLevelsArray);
}; };
useEffect(() => { useEffect(() => {
@ -460,6 +508,7 @@ export default function FormTeksDetail() {
names: details?.files[0]?.fileName, names: details?.files[0]?.fileName,
format: details?.files[0]?.format, format: details?.files[0]?.format,
}); });
setupPlacementCheck(details?.files?.length);
if (details?.assignedToLevel) { if (details?.assignedToLevel) {
const levels = new Set( const levels = new Set(
@ -530,12 +579,27 @@ export default function FormTeksDetail() {
}; };
const getPlacement = () => { const getPlacement = () => {
// console.log("getPlaa", filePlacements);
const temp = []; const temp = [];
for (let i = 0; i < filePlacements?.length; i++) { for (let i = 0; i < filePlacements?.length; i++) {
if (filePlacements[i]?.length !== 0) { if (filePlacements[i]?.length !== 0) {
const now = filePlacements[i]?.filter((a) => a !== "all"); // const now = filePlacements[i]?.filter((a) => a !== "all");
const data = { mediaFileId: files[i]?.id, placements: now?.join(",") }; // const data = { mediaFileId: files[i]?.id, placements: now?.join(",") };
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); temp.push(data);
} }
} }
@ -543,12 +607,19 @@ export default function FormTeksDetail() {
}; };
async function save() { async function save() {
// Gabungkan semua checkedLevels dari semua file
const allCheckedLevels = new Set<number>();
fileCheckedLevels.forEach((fileLevels) => {
fileLevels.forEach((levelId) => {
allCheckedLevels.add(levelId);
});
});
const data = { const data = {
mediaUploadId: id, mediaUploadId: id,
statusId: status, statusId: status,
message: description, message: description,
files: isUserMabesApprover ? getPlacement() : [], files: isUserMabesApprover ? getPlacement() : [],
customLocationPlacement: Array.from(checkedLevels).join(","),
}; };
setModalOpen(false); setModalOpen(false);
@ -653,6 +724,43 @@ export default function FormTeksDetail() {
if (checked) { if (checked) {
if (placement === "all") { if (placement === "all") {
temp[index] = ["all", "mabes", "polda", "international"]; temp[index] = ["all", "mabes", "polda", "international"];
// Update fileCheckedLevels untuk sinkronisasi dengan modal ketika "all" diklik
setFileCheckedLevels((prevLevels) => {
const newArray = [...prevLevels];
const currentFileLevels = new Set<number>(newArray[index] || new Set());
// 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));
});
}
});
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") { } else if (placement === "satker") {
// Ketika satker di-checklist, HANYA tambahkan satker saja // Ketika satker di-checklist, HANYA tambahkan satker saja
// JANGAN otomatis checklist polres // JANGAN otomatis checklist polres
@ -679,6 +787,36 @@ export default function FormTeksDetail() {
} else { } else {
if (placement === "all") { if (placement === "all") {
temp[index] = []; temp[index] = [];
// Update fileCheckedLevels untuk sinkronisasi dengan modal ketika "all" di-unchecklist
setFileCheckedLevels((prevLevels) => {
const newArray = [...prevLevels];
const currentFileLevels = new Set<number>(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 { } else {
const now = temp[index]?.filter((a) => a !== placement); const now = temp[index]?.filter((a) => a !== placement);
console.log("now", now); console.log("now", now);
@ -930,6 +1068,15 @@ export default function FormTeksDetail() {
currentSelection.polres = checkedPolresCount > 0; currentSelection.polres = checkedPolresCount > 0;
currentSelection.satker = Boolean(isSatkerChecked); 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; newSelections[fileIndex] = currentSelection;
return newSelections; return newSelections;
}); });
@ -1264,6 +1411,13 @@ export default function FormTeksDetail() {
<p className="text-sm text-gray-600 break-all"> <p className="text-sm text-gray-600 break-all">
{file.fileName} {file.fileName}
</p> </p>
{isUserMabesApprover ? (
""
) : (
<p className="status text-success text-sm mb-0">
Selesai
</p>
)}
</div> </div>
</div> </div>
<button <button
@ -1281,84 +1435,36 @@ export default function FormTeksDetail() {
</div> </div>
{/* Section Pengaturan Distribusi */} {/* Section Pengaturan Distribusi */}
<div className="bg-white rounded-md p-4 border"> {isUserMabesApprover ? (
<h5 className="font-medium text-gray-900 mb-4 flex items-center gap-2"> <div className="bg-white rounded-md p-4 border">
<Icon <h5 className="font-medium text-gray-900 mb-4 flex items-center gap-2">
icon="material-symbols:settings-outline" <Icon
width={18} icon="material-symbols:settings-outline"
height={18} width={18}
/> height={18}
Pengaturan Distribusi />
</h5> Pengaturan Distribusi
</h5>
{/* Checkbox Tingkat Utama */} {/* Checkbox Tingkat Utama */}
<div className="space-y-4"> <div className="space-y-4">
<div> <div>
<p className="text-sm font-medium text-gray-700 mb-3"> <p className="text-sm font-medium text-gray-700 mb-3">
Tingkat Distribusi: Tingkat Distribusi:
</p>
<div className="grid grid-cols-2 md:grid-cols-4 gap-3">
{[
{ key: "semua", label: "Semua" },
{ key: "nasional", label: "Nasional" },
{ key: "wilayah", label: "Wilayah" },
{
key: "international",
label: "Internasional",
},
].map((item, idx) => (
<div
key={item.key}
className="flex items-center gap-2 p-2 border border-gray-200 rounded-md hover:bg-gray-50"
>
<Checkbox
id={`${item.key}-${index}`}
checked={
fileUnitSelections[index]?.[
item.key as keyof typeof unitSelection
] || false
}
onCheckedChange={(value) => {
handleFileUnitChange(
index,
item.key as keyof typeof unitSelection,
value as boolean
);
setupPlacement(
index,
item.key,
Boolean(value)
);
}}
/>
<Label
htmlFor={`${item.key}-${index}`}
className="text-sm font-medium cursor-pointer"
>
{item.label}
</Label>
</div>
))}
</div>
</div>
{/* Detail Wilayah */}
{fileUnitSelections[index]?.wilayah && (
<div className="border-t border-gray-200 pt-2">
<p className="text-sm font-medium text-gray-700 mb-2">
Detail Wilayah:
</p> </p>
<div className="grid grid-cols-2 md:grid-cols-4 gap-3">
{/* Checkbox Sub-kategori dengan tombol Kustom sejajar */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-3">
{[ {[
{ key: "polda", label: "POLDA" }, { key: "semua", label: "Semua" },
{ key: "polres", label: "POLRES" }, { key: "nasional", label: "Nasional" },
{ key: "satker", label: "SATKER" }, { key: "wilayah", label: "Wilayah" },
{
key: "international",
label: "Internasional",
},
].map((item, idx) => ( ].map((item, idx) => (
<div <div
key={item.key} key={item.key}
className="flex items-center gap-2 p-3 border border-gray-200 rounded-md hover:bg-gray-50" className="flex items-center gap-2 p-2 border border-gray-200 rounded-md hover:bg-gray-50"
> >
<Checkbox <Checkbox
id={`${item.key}-${index}`} id={`${item.key}-${index}`}
@ -1388,199 +1494,260 @@ export default function FormTeksDetail() {
</Label> </Label>
</div> </div>
))} ))}
</div>
</div>
{/* Tombol Kustom sejajar dengan checkbox */} {/* Detail Wilayah */}
<div className="flex items-center justify-center p-3"> {fileUnitSelections[index]?.wilayah && (
<Dialog> <div className="border-t border-gray-200 pt-2">
<DialogTrigger asChild> <p className="text-sm font-medium text-gray-700 mb-2">
<Button Detail Wilayah:
variant="outline" </p>
size="sm"
className="gap-2" {/* Checkbox Sub-kategori dengan tombol Kustom sejajar */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-3">
{[
{ key: "polda", label: "POLDA" },
{ key: "polres", label: "POLRES" },
{ key: "satker", label: "SATKER" },
].map((item, idx) => (
<div
key={item.key}
className="flex items-center gap-2 p-3 border border-gray-200 rounded-md hover:bg-gray-50"
>
<Checkbox
id={`${item.key}-${index}`}
checked={
fileUnitSelections[index]?.[
item.key as keyof typeof unitSelection
] || false
}
onCheckedChange={(value) => {
handleFileUnitChange(
index,
item.key as keyof typeof unitSelection,
value as boolean
);
setupPlacement(
index,
item.key,
Boolean(value)
);
}}
/>
<Label
htmlFor={`${item.key}-${index}`}
className="text-sm font-medium cursor-pointer"
> >
<Icon {item.label}
icon="material-symbols:tune" </Label>
width={16} </div>
height={16} ))}
/>
{t("custom", {
defaultValue: "Kustom",
})}
</Button>
</DialogTrigger>
<DialogContent className="max-w-[95vw] lg:max-w-[1400px] max-h-[90vh]">
<DialogHeader className="border-b border-gray-200 pb-4">
<DialogTitle className="text-lg font-semibold">
Daftar Wilayah POLDA dan POLRES
</DialogTitle>
</DialogHeader>
<div className="grid grid-cols-1 lg:grid-cols-2 xl:grid-cols-3 gap-4 max-h-[70vh] overflow-y-auto p-1">
{listDest.map((polda: any) => (
<div
key={polda.id}
className="border border-gray-200 rounded-lg p-2 bg-white hover:shadow-sm transition-shadow"
>
{/* Header POLDA */}
<div className="flex items-center justify-between">
<Label className="flex items-center gap-3 flex-1 cursor-pointer">
<Checkbox
checked={
fileCheckedLevels[
index
]?.has(
Number(polda.id)
) || false
}
onCheckedChange={() =>
handleFileCheckboxChangePlacement(
index,
Number(polda.id)
)
}
/>
<span className="font-semibold text-gray-900 text-sm">
{polda.name}
</span>
</Label>
{polda.subDestination && (
<button
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
toggleExpand(polda.id);
}}
className="p-1 hover:bg-gray-100 rounded-md transition-colors"
>
<Icon
icon={
expandedPolda[
polda.id
]
? "mdi:chevron-up"
: "mdi:chevron-down"
}
width={16}
height={16}
/>
</button>
)}
</div>
{/* Sub-items */} {/* Tombol Kustom sejajar dengan checkbox */}
{polda.subDestination && <div className="flex items-center justify-center p-3">
expandedPolda[polda.id] && ( <Dialog>
<div className="max-h-[200px] overflow-y-auto border-t border-gray-100 pt-2"> <DialogTrigger asChild>
{/* Tombol Pilih Semua untuk sub-items */} <Button
<div className="mb-2 flex justify-start"> variant="outline"
{(() => { size="sm"
const allSubItemsChecked = className="gap-2"
polda.subDestination?.every( >
(sub: any) => <Icon
fileCheckedLevels[ icon="material-symbols:tune"
index width={16}
]?.has( height={16}
Number(sub.id) />
) {t("custom", {
); defaultValue: "Kustom",
return ( })}
<Button </Button>
size="sm" </DialogTrigger>
variant="outline" <DialogContent className="max-w-[95vw] lg:max-w-[1400px] max-h-[90vh]">
className="text-xs h-6 px-2" <DialogHeader className="border-b border-gray-200 pb-4">
onClick={() => <DialogTitle className="text-lg font-semibold">
handleSelectAllSubItems( Daftar Wilayah POLDA dan POLRES
index, </DialogTitle>
polda </DialogHeader>
) <div className="grid grid-cols-1 lg:grid-cols-2 xl:grid-cols-3 gap-4 max-h-[70vh] overflow-y-auto p-1">
} {listDest.map((polda: any) => (
> <div
{allSubItemsChecked ? ( key={polda.id}
<> className="border border-gray-200 rounded-lg p-2 bg-white hover:shadow-sm transition-shadow"
<Icon >
icon="material-symbols:check-indeterminate-small" {/* Header POLDA */}
width={12} <div className="flex items-center justify-between">
height={12} <Label className="flex items-center gap-3 flex-1 cursor-pointer">
className="mr-1" <Checkbox
/> checked={
Batal Semua fileCheckedLevels[
</> index
) : ( ]?.has(
<> Number(polda.id)
<Icon ) || false
icon="material-symbols:check-all" }
width={12} onCheckedChange={() =>
height={12} handleFileCheckboxChangePlacement(
className="mr-1" index,
/> Number(polda.id)
Pilih Semua )
</> }
)} />
</Button> <span className="font-semibold text-gray-900 text-sm">
{polda.name}
</span>
</Label>
{polda.subDestination && (
<button
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
toggleExpand(
polda.id
); );
})()} }}
</div> className="p-1 hover:bg-gray-100 rounded-md transition-colors"
<div className="space-y-1"> >
{polda.subDestination.map( <Icon
(sub: any) => ( icon={
<Label expandedPolda[
key={sub.id} polda.id
className="flex items-center gap-2 p-2 rounded-md hover:bg-gray-50 transition-colors cursor-pointer text-xs" ]
> ? "mdi:chevron-up"
<Checkbox : "mdi:chevron-down"
checked={ }
width={16}
height={16}
/>
</button>
)}
</div>
{/* Sub-items */}
{polda.subDestination &&
expandedPolda[polda.id] && (
<div className="max-h-[200px] overflow-y-auto border-t border-gray-100 pt-2">
{/* Tombol Pilih Semua untuk sub-items */}
<div className="mb-2 flex justify-start">
{(() => {
const allSubItemsChecked =
polda.subDestination?.every(
(sub: any) =>
fileCheckedLevels[ fileCheckedLevels[
index index
]?.has( ]?.has(
Number( Number(
sub.id sub.id
) )
) || false )
} );
onCheckedChange={() => return (
handleFileCheckboxChangePlacement( <Button
size="sm"
variant="outline"
className="text-xs h-6 px-2"
onClick={() =>
handleSelectAllSubItems(
index, index,
Number( polda
sub.id
)
) )
} }
/> >
<span className="text-gray-700"> {allSubItemsChecked ? (
{sub.name} <>
</span> <Icon
</Label> icon="material-symbols:check-indeterminate-small"
) width={12}
)} height={
12
}
className="mr-1"
/>
Batal Semua
</>
) : (
<>
<Icon
icon="material-symbols:check-all"
width={12}
height={
12
}
className="mr-1"
/>
Pilih Semua
</>
)}
</Button>
);
})()}
</div>
<div className="space-y-1">
{polda.subDestination.map(
(sub: any) => (
<Label
key={sub.id}
className="flex items-center gap-2 p-2 rounded-md hover:bg-gray-50 transition-colors cursor-pointer text-xs"
>
<Checkbox
checked={
fileCheckedLevels[
index
]?.has(
Number(
sub.id
)
) || false
}
onCheckedChange={() =>
handleFileCheckboxChangePlacement(
index,
Number(
sub.id
)
)
}
/>
<span className="text-gray-700">
{sub.name}
</span>
</Label>
)
)}
</div>
</div> </div>
</div> )}
)} </div>
</div> ))}
))} </div>
</div> <div className="flex justify-end gap-3 border-t border-gray-200 pt-4">
<div className="flex justify-end gap-3 border-t border-gray-200 pt-4"> <DialogClose asChild>
<DialogClose asChild> <Button variant="outline">
<Button variant="outline"> {t("cancel", {
{t("cancel", { defaultValue: "Batal",
defaultValue: "Batal", })}
})} </Button>
</Button> </DialogClose>
</DialogClose> <DialogClose asChild>
<DialogClose asChild> <Button>
<Button> {/* {t("save", {
{t("save", { defaultValue: "Simpan",
defaultValue: "Simpan", })} */}
})} Simpan
</Button> </Button>
</DialogClose> </DialogClose>
</div> </div>
</DialogContent> </DialogContent>
</Dialog> </Dialog>
</div>
</div> </div>
</div> </div>
</div> )}
)} </div>
</div> </div>
</div> ) : (
""
)}
</div> </div>
))} ))}
</div> </div>

View File

@ -192,6 +192,12 @@ export default function FormVideoDetail() {
}> }>
>([]); >([]);
useEffect(() => {
if (Number(userLevelId) === 216 && Number(roleId) === 3) {
setIsUserMabesApprover(true);
}
}, [userLevelId, roleId]);
// Fungsi untuk mengupdate state individual file // Fungsi untuk mengupdate state individual file
const handleFileUnitChange = ( const handleFileUnitChange = (
fileIndex: number, fileIndex: number,
@ -211,6 +217,30 @@ export default function FormVideoDetail() {
currentSelection.polda = value; currentSelection.polda = value;
currentSelection.polres = value; currentSelection.polres = value;
currentSelection.satker = value; currentSelection.satker = value;
// Update fileCheckedLevels untuk sinkronisasi dengan modal
setFileCheckedLevels((prevLevels) => {
const newArray = [...prevLevels];
const currentFileLevels = new Set<number>(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 { } else {
// Validasi khusus untuk POLRES - harus ada POLDA yang ter-checklist // Validasi khusus untuk POLRES - harus ada POLDA yang ter-checklist
if (key === "polres" && value) { if (key === "polres" && value) {
@ -479,6 +509,31 @@ export default function FormVideoDetail() {
); );
setDetailVideo(fileUrls); setDetailVideo(fileUrls);
// Setup placement check untuk setiap file
const temp = [];
const unitSelections = [];
const checkedLevelsArray = [];
for (let i = 0; i < details?.files?.length; i++) {
temp.push([]);
// Inisialisasi state untuk setiap file
unitSelections.push({
semua: false,
nasional: false,
wilayah: false,
international: false,
polda: false,
polres: false,
satker: false,
});
// Inisialisasi checkedLevels untuk setiap file
checkedLevelsArray.push(new Set<number>());
}
setFilePlacements(temp);
setFileUnitSelections(unitSelections);
setFileCheckedLevels(checkedLevelsArray);
const approvals = await getDataApprovalByMediaUpload(details?.id); const approvals = await getDataApprovalByMediaUpload(details?.id);
setApproval(approvals?.data?.data); setApproval(approvals?.data?.data);
} }
@ -523,12 +578,19 @@ export default function FormVideoDetail() {
}; };
async function save() { async function save() {
// Gabungkan semua checkedLevels dari semua file
const allCheckedLevels = new Set<number>();
fileCheckedLevels.forEach((fileLevels) => {
fileLevels.forEach((levelId) => {
allCheckedLevels.add(levelId);
});
});
const data = { const data = {
mediaUploadId: id, mediaUploadId: id,
statusId: status, statusId: status,
message: description, message: description,
files: isUserMabesApprover ? getPlacement() : [], files: isUserMabesApprover ? getPlacement() : [],
customLocationPlacement: Array.from(checkedLevels).join(","),
}; };
setModalOpen(false); setModalOpen(false);
loading(); loading();
@ -557,12 +619,28 @@ export default function FormVideoDetail() {
} }
const getPlacement = () => { const getPlacement = () => {
// console.log("getPlaa", filePlacements);
const temp = []; const temp = [];
for (let i = 0; i < filePlacements?.length; i++) { for (let i = 0; i < filePlacements?.length; i++) {
if (filePlacements[i].length !== 0) { if (filePlacements[i].length !== 0) {
const now = filePlacements[i].filter((a) => a !== "all"); // const now = filePlacements[i].filter((a) => a !== "all");
const data = { mediaFileId: files[i].id, placements: now.join(",") }; // const data = { mediaFileId: files[i].id, placements: now.join(",") };
// const now = filePlacements[i]?.filter((a) => a !== "semua");
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); temp.push(data);
} }
} }
@ -611,6 +689,43 @@ export default function FormVideoDetail() {
if (checked) { if (checked) {
if (placement === "all") { if (placement === "all") {
temp[index] = ["all", "mabes", "polda", "international"]; temp[index] = ["all", "mabes", "polda", "international"];
// Update fileCheckedLevels untuk sinkronisasi dengan modal ketika "all" diklik
setFileCheckedLevels((prevLevels) => {
const newArray = [...prevLevels];
const currentFileLevels = new Set<number>(newArray[index] || new Set());
// 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));
});
}
});
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") { } else if (placement === "satker") {
// Ketika satker di-checklist, HANYA tambahkan satker saja // Ketika satker di-checklist, HANYA tambahkan satker saja
// JANGAN otomatis checklist polres // JANGAN otomatis checklist polres
@ -637,6 +752,36 @@ export default function FormVideoDetail() {
} else { } else {
if (placement === "all") { if (placement === "all") {
temp[index] = []; temp[index] = [];
// Update fileCheckedLevels untuk sinkronisasi dengan modal ketika "all" di-unchecklist
setFileCheckedLevels((prevLevels) => {
const newArray = [...prevLevels];
const currentFileLevels = new Set<number>(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 { } else {
const now = temp[index].filter((a) => a !== placement); const now = temp[index].filter((a) => a !== placement);
// console.log("now", now); // console.log("now", now);
@ -925,6 +1070,15 @@ export default function FormVideoDetail() {
currentSelection.polres = checkedPolresCount > 0; currentSelection.polres = checkedPolresCount > 0;
currentSelection.satker = Boolean(isSatkerChecked); 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; newSelections[fileIndex] = currentSelection;
return newSelections; return newSelections;
}); });
@ -1285,84 +1439,36 @@ export default function FormVideoDetail() {
</div> </div>
{/* Section Pengaturan Distribusi */} {/* Section Pengaturan Distribusi */}
<div className="bg-white rounded-md p-4 border"> {isUserMabesApprover ? (
<h5 className="font-medium text-gray-900 mb-4 flex items-center gap-2"> <div className="bg-white rounded-md p-4 border">
<Icon <h5 className="font-medium text-gray-900 mb-4 flex items-center gap-2">
icon="material-symbols:settings-outline" <Icon
width={18} icon="material-symbols:settings-outline"
height={18} width={18}
/> height={18}
Pengaturan Distribusi />
</h5> Pengaturan Distribusi
</h5>
{/* Checkbox Tingkat Utama */} {/* Checkbox Tingkat Utama */}
<div className="space-y-4"> <div className="space-y-4">
<div> <div>
<p className="text-sm font-medium text-gray-700 mb-3"> <p className="text-sm font-medium text-gray-700 mb-3">
Tingkat Distribusi: Tingkat Distribusi:
</p>
<div className="grid grid-cols-2 md:grid-cols-4 gap-3">
{[
{ key: "semua", label: "Semua" },
{ key: "nasional", label: "Nasional" },
{ key: "wilayah", label: "Wilayah" },
{
key: "international",
label: "Internasional",
},
].map((item, idx) => (
<div
key={item.key}
className="flex items-center gap-2 p-2 border border-gray-200 rounded-md hover:bg-gray-50"
>
<Checkbox
id={`${item.key}-${index}`}
checked={
fileUnitSelections[index]?.[
item.key as keyof typeof unitSelection
] || false
}
onCheckedChange={(value) => {
handleFileUnitChange(
index,
item.key as keyof typeof unitSelection,
value as boolean
);
setupPlacement(
index,
item.key,
Boolean(value)
);
}}
/>
<Label
htmlFor={`${item.key}-${index}`}
className="text-sm font-medium cursor-pointer"
>
{item.label}
</Label>
</div>
))}
</div>
</div>
{/* Detail Wilayah */}
{fileUnitSelections[index]?.wilayah && (
<div className="border-t border-gray-200 pt-2">
<p className="text-sm font-medium text-gray-700 mb-2">
Detail Wilayah:
</p> </p>
<div className="grid grid-cols-2 md:grid-cols-4 gap-3">
{/* Checkbox Sub-kategori dengan tombol Kustom sejajar */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-3">
{[ {[
{ key: "polda", label: "POLDA" }, { key: "semua", label: "Semua" },
{ key: "polres", label: "POLRES" }, { key: "nasional", label: "Nasional" },
{ key: "satker", label: "SATKER" }, { key: "wilayah", label: "Wilayah" },
{
key: "international",
label: "Internasional",
},
].map((item, idx) => ( ].map((item, idx) => (
<div <div
key={item.key} key={item.key}
className="flex items-center gap-2 p-3 border border-gray-200 rounded-md hover:bg-gray-50" className="flex items-center gap-2 p-2 border border-gray-200 rounded-md hover:bg-gray-50"
> >
<Checkbox <Checkbox
id={`${item.key}-${index}`} id={`${item.key}-${index}`}
@ -1392,199 +1498,260 @@ export default function FormVideoDetail() {
</Label> </Label>
</div> </div>
))} ))}
</div>
</div>
{/* Tombol Kustom sejajar dengan checkbox */} {/* Detail Wilayah */}
<div className="flex items-center justify-center p-3"> {fileUnitSelections[index]?.wilayah && (
<Dialog> <div className="border-t border-gray-200 pt-2">
<DialogTrigger asChild> <p className="text-sm font-medium text-gray-700 mb-2">
<Button Detail Wilayah:
variant="outline" </p>
size="sm"
className="gap-2" {/* Checkbox Sub-kategori dengan tombol Kustom sejajar */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-3">
{[
{ key: "polda", label: "POLDA" },
{ key: "polres", label: "POLRES" },
{ key: "satker", label: "SATKER" },
].map((item, idx) => (
<div
key={item.key}
className="flex items-center gap-2 p-3 border border-gray-200 rounded-md hover:bg-gray-50"
>
<Checkbox
id={`${item.key}-${index}`}
checked={
fileUnitSelections[index]?.[
item.key as keyof typeof unitSelection
] || false
}
onCheckedChange={(value) => {
handleFileUnitChange(
index,
item.key as keyof typeof unitSelection,
value as boolean
);
setupPlacement(
index,
item.key,
Boolean(value)
);
}}
/>
<Label
htmlFor={`${item.key}-${index}`}
className="text-sm font-medium cursor-pointer"
> >
<Icon {item.label}
icon="material-symbols:tune" </Label>
width={16} </div>
height={16} ))}
/>
{t("custom", {
defaultValue: "Kustom",
})}
</Button>
</DialogTrigger>
<DialogContent className="max-w-[95vw] lg:max-w-[1400px] max-h-[90vh]">
<DialogHeader className="border-b border-gray-200 pb-4">
<DialogTitle className="text-lg font-semibold">
Daftar Wilayah POLDA dan POLRES
</DialogTitle>
</DialogHeader>
<div className="grid grid-cols-1 lg:grid-cols-2 xl:grid-cols-3 gap-4 max-h-[70vh] overflow-y-auto p-1">
{listDest.map((polda: any) => (
<div
key={polda.id}
className="border border-gray-200 rounded-lg p-2 bg-white hover:shadow-sm transition-shadow"
>
{/* Header POLDA */}
<div className="flex items-center justify-between">
<Label className="flex items-center gap-3 flex-1 cursor-pointer">
<Checkbox
checked={
fileCheckedLevels[
index
]?.has(
Number(polda.id)
) || false
}
onCheckedChange={() =>
handleFileCheckboxChangePlacement(
index,
Number(polda.id)
)
}
/>
<span className="font-semibold text-gray-900 text-sm">
{polda.name}
</span>
</Label>
{polda.subDestination && (
<button
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
toggleExpand(polda.id);
}}
className="p-1 hover:bg-gray-100 rounded-md transition-colors"
>
<Icon
icon={
expandedPolda[
polda.id
]
? "mdi:chevron-up"
: "mdi:chevron-down"
}
width={16}
height={16}
/>
</button>
)}
</div>
{/* Sub-items */} {/* Tombol Kustom sejajar dengan checkbox */}
{polda.subDestination && <div className="flex items-center justify-center p-3">
expandedPolda[polda.id] && ( <Dialog>
<div className="max-h-[200px] overflow-y-auto border-t border-gray-100 pt-2"> <DialogTrigger asChild>
{/* Tombol Pilih Semua untuk sub-items */} <Button
<div className="mb-2 flex justify-start"> variant="outline"
{(() => { size="sm"
const allSubItemsChecked = className="gap-2"
polda.subDestination?.every( >
(sub: any) => <Icon
fileCheckedLevels[ icon="material-symbols:tune"
index width={16}
]?.has( height={16}
Number(sub.id) />
) {t("custom", {
); defaultValue: "Kustom",
return ( })}
<Button </Button>
size="sm" </DialogTrigger>
variant="outline" <DialogContent className="max-w-[95vw] lg:max-w-[1400px] max-h-[90vh]">
className="text-xs h-6 px-2" <DialogHeader className="border-b border-gray-200 pb-4">
onClick={() => <DialogTitle className="text-lg font-semibold">
handleSelectAllSubItems( Daftar Wilayah POLDA dan POLRES
index, </DialogTitle>
polda </DialogHeader>
) <div className="grid grid-cols-1 lg:grid-cols-2 xl:grid-cols-3 gap-4 max-h-[70vh] overflow-y-auto p-1">
} {listDest.map((polda: any) => (
> <div
{allSubItemsChecked ? ( key={polda.id}
<> className="border border-gray-200 rounded-lg p-2 bg-white hover:shadow-sm transition-shadow"
<Icon >
icon="material-symbols:check-indeterminate-small" {/* Header POLDA */}
width={12} <div className="flex items-center justify-between">
height={12} <Label className="flex items-center gap-3 flex-1 cursor-pointer">
className="mr-1" <Checkbox
/> checked={
Batal Semua fileCheckedLevels[
</> index
) : ( ]?.has(
<> Number(polda.id)
<Icon ) || false
icon="material-symbols:check-all" }
width={12} onCheckedChange={() =>
height={12} handleFileCheckboxChangePlacement(
className="mr-1" index,
/> Number(polda.id)
Pilih Semua )
</> }
)} />
</Button> <span className="font-semibold text-gray-900 text-sm">
{polda.name}
</span>
</Label>
{polda.subDestination && (
<button
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
toggleExpand(
polda.id
); );
})()} }}
</div> className="p-1 hover:bg-gray-100 rounded-md transition-colors"
<div className="space-y-1"> >
{polda.subDestination.map( <Icon
(sub: any) => ( icon={
<Label expandedPolda[
key={sub.id} polda.id
className="flex items-center gap-2 p-2 rounded-md hover:bg-gray-50 transition-colors cursor-pointer text-xs" ]
> ? "mdi:chevron-up"
<Checkbox : "mdi:chevron-down"
checked={ }
width={16}
height={16}
/>
</button>
)}
</div>
{/* Sub-items */}
{polda.subDestination &&
expandedPolda[polda.id] && (
<div className="max-h-[200px] overflow-y-auto border-t border-gray-100 pt-2">
{/* Tombol Pilih Semua untuk sub-items */}
<div className="mb-2 flex justify-start">
{(() => {
const allSubItemsChecked =
polda.subDestination?.every(
(sub: any) =>
fileCheckedLevels[ fileCheckedLevels[
index index
]?.has( ]?.has(
Number( Number(
sub.id sub.id
) )
) || false )
} );
onCheckedChange={() => return (
handleFileCheckboxChangePlacement( <Button
size="sm"
variant="outline"
className="text-xs h-6 px-2"
onClick={() =>
handleSelectAllSubItems(
index, index,
Number( polda
sub.id
)
) )
} }
/> >
<span className="text-gray-700"> {allSubItemsChecked ? (
{sub.name} <>
</span> <Icon
</Label> icon="material-symbols:check-indeterminate-small"
) width={12}
)} height={
12
}
className="mr-1"
/>
Batal Semua
</>
) : (
<>
<Icon
icon="material-symbols:check-all"
width={12}
height={
12
}
className="mr-1"
/>
Pilih Semua
</>
)}
</Button>
);
})()}
</div>
<div className="space-y-1">
{polda.subDestination.map(
(sub: any) => (
<Label
key={sub.id}
className="flex items-center gap-2 p-2 rounded-md hover:bg-gray-50 transition-colors cursor-pointer text-xs"
>
<Checkbox
checked={
fileCheckedLevels[
index
]?.has(
Number(
sub.id
)
) || false
}
onCheckedChange={() =>
handleFileCheckboxChangePlacement(
index,
Number(
sub.id
)
)
}
/>
<span className="text-gray-700">
{sub.name}
</span>
</Label>
)
)}
</div>
</div> </div>
</div> )}
)} </div>
</div> ))}
))} </div>
</div> <div className="flex justify-end gap-3 border-t border-gray-200 pt-4">
<div className="flex justify-end gap-3 border-t border-gray-200 pt-4"> <DialogClose asChild>
<DialogClose asChild> <Button variant="outline">
<Button variant="outline"> {t("cancel", {
{t("cancel", { defaultValue: "Batal",
defaultValue: "Batal", })}
})} </Button>
</Button> </DialogClose>
</DialogClose> <DialogClose asChild>
<DialogClose asChild> <Button>
<Button> {/* {t("save", {
{t("save", { defaultValue: "Simpan",
defaultValue: "Simpan", })} */}
})} Simpan
</Button> </Button>
</DialogClose> </DialogClose>
</div> </div>
</DialogContent> </DialogContent>
</Dialog> </Dialog>
</div>
</div> </div>
</div> </div>
</div> )}
)} </div>
</div> </div>
</div> ) : (
""
)}
</div> </div>
))} ))}
</div> </div>

View File

@ -95,11 +95,19 @@ export function CalendarPolriAddDetail() {
const details = response?.data?.data; const details = response?.data?.data;
setDetail(details); setDetail(details);
if (details) { // if (details) {
// setDate({
// from: parseISO(details.startDate),
// to: parseISO(details.endDate),
// });
// }
if (details?.startDate && details?.endDate) {
setDate({ setDate({
from: parseISO(details.startDate), from: parseISO(details.startDate),
to: parseISO(details.endDate), to: parseISO(details.endDate),
}); });
} else {
// atau biarkan kosong sesuai kebutuhan
} }
if (details?.assignedTo) { if (details?.assignedTo) {

View File

@ -1,78 +1,145 @@
// import { listDataAdvertisements } from "@/service/broadcast/broadcast";
// import { useEffect, useState } from "react";
// import * as React from "react";
// interface Advertisement {
// id: string;
// placements: string;
// imageUrl: string;
// [key: string]: any;
// }
// // // Simulasi fungsi API (replace dengan yang asli)
// // async function listDataAdvertisements(
// // page: number,
// // size: number,
// // search: string,
// // category: string,
// // status: string
// // ) {
// // // contoh struktur response dummy
// // return {
// // data: {
// // data: {
// // content: [
// // { id: "1", imageUrl: "/images/all-img/kiri1.png" },
// // { id: "2", imageUrl: "/images/all-img/kiri2.png" },
// // ],
// // totalElements: 2,
// // totalPages: 1,
// // },
// // },
// // };
// // }
// const AdvertisementPlacements = (props: {
// placement: string;
// data: Advertisement[];
// loading: boolean;
// }) => {
// const [ads, setAds] = useState<Advertisement[] | undefined[]>([]);
// useEffect(() => {
// if (!props.loading && props.data.length > 0) {
// console.log(
// "RRRRRR",
// props.data[0].placements.includes(props.placement),
// props.placement
// );
// const filtered = props.data.filter((a) =>
// a.placements.includes(props.placement)
// );
// let temps: Advertisement[] | undefined[] = [];
// temps[0] = filtered.find((b) => b.placements.includes("top"));
// temps[1] = filtered.find((b) => b.placements.includes("bottom"));
// setAds(temps);
// console.log("PPPPPP", filtered);
// }
// }, [props.data, props.loading]);
// return (
// <div
// className={`sticky top-0 space-y-4 ${
// props.placement == "left" ? "ml-14" : "mr-14"
// }`}
// >
// {props.loading && <p className="text-sm text-gray-500">Loading...</p>}
// {ads?.map(
// (ad) =>
// ad && (
// <img
// key={ad.id}
// src={ad.contentFileUrl}
// alt={`Banner ${ad.id}`}
// className="w-full"
// />
// )
// )}
// </div>
// );
// };
// export default AdvertisementPlacements;
import { listDataAdvertisements } from "@/service/broadcast/broadcast"; import { listDataAdvertisements } from "@/service/broadcast/broadcast";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import * as React from "react"; import * as React from "react";
import { Dialog, DialogContent, DialogTrigger } from "@/components/ui/dialog";
interface Advertisement { interface Advertisement {
id: string; id: string;
placements: string; placements: string;
imageUrl: string; imageUrl: string;
contentFileUrl?: string;
[key: string]: any; [key: string]: any;
} }
// // Simulasi fungsi API (replace dengan yang asli)
// async function listDataAdvertisements(
// page: number,
// size: number,
// search: string,
// category: string,
// status: string
// ) {
// // contoh struktur response dummy
// return {
// data: {
// data: {
// content: [
// { id: "1", imageUrl: "/images/all-img/kiri1.png" },
// { id: "2", imageUrl: "/images/all-img/kiri2.png" },
// ],
// totalElements: 2,
// totalPages: 1,
// },
// },
// };
// }
const AdvertisementPlacements = (props: { const AdvertisementPlacements = (props: {
placement: string; placement: string;
data: Advertisement[]; data: Advertisement[];
loading: boolean; loading: boolean;
}) => { }) => {
const [ads, setAds] = useState<Advertisement[] | undefined[]>([]); const [ads, setAds] = useState<Advertisement[] | undefined[]>([]);
useEffect(() => { useEffect(() => {
if (!props.loading && props.data.length > 0) { if (!props.loading && props.data.length > 0) {
console.log(
"RRRRRR",
props.data[0].placements.includes(props.placement),
props.placement
);
const filtered = props.data.filter((a) => const filtered = props.data.filter((a) =>
a.placements.includes(props.placement) a.placements.includes(props.placement)
); );
let temps: Advertisement[] | undefined[] = []; let temps: Advertisement[] | undefined[] = [];
temps[0] = filtered.find((b) => b.placements.includes("top")); temps[0] = filtered.find((b) => b.placements.includes("top"));
temps[1] = filtered.find((b) => b.placements.includes("bottom")); temps[1] = filtered.find((b) => b.placements.includes("bottom"));
setAds(temps); setAds(temps);
console.log("PPPPPP", filtered);
} }
}, [props.data, props.loading]); }, [props.data, props.loading, props.placement]);
return ( return (
<div <div
className={`sticky top-0 space-y-4 ${ className={`sticky top-0 space-y-4 ${
props.placement == "left" ? "ml-14" : "mr-14" props.placement === "left" ? "ml-14" : "mr-14"
}`} }`}
> >
{props.loading && <p className="text-sm text-gray-500">Loading...</p>} {props.loading && <p className="text-sm text-gray-500">Loading...</p>}
{ads?.map( {ads?.map(
(ad) => (ad) =>
ad && ( ad && (
<img <Dialog key={ad.id}>
key={ad.id} <DialogTrigger asChild>
src={ad.contentFileUrl} <img
alt={`Banner ${ad.id}`} src={ad.contentFileUrl || ad.imageUrl}
className="w-full" alt={`Banner ${ad.id}`}
/> className="w-full cursor-pointer rounded-lg shadow-md hover:opacity-90 transition"
/>
</DialogTrigger>
<DialogContent className="max-w-4xl p-0 bg-transparent border-0 shadow-none">
<img
src={ad.contentFileUrl || ad.imageUrl}
alt={`Banner Besar ${ad.id}`}
className="w-full h-auto rounded-lg"
/>
</DialogContent>
</Dialog>
) )
)} )}
</div> </div>

View File

@ -533,76 +533,87 @@ const HeroNew = (props: { group?: string }) => {
<div className="relative w-full"> <div className="relative w-full">
{content && content.length > 0 && ( {content && content.length > 0 && (
<Swiper <>
modules={[Autoplay, Navigation]} <Swiper
autoplay={{ modules={[Autoplay, Navigation]}
delay: 10000, autoplay={{
disableOnInteraction: false, delay: 10000,
}} disableOnInteraction: false,
loop={true} }}
navigation={{ loop
nextEl: ".swiper-button-next", navigation={{
prevEl: ".swiper-button-prev", nextEl: ".hero-next",
}} prevEl: ".hero-prev",
className="w-full" }}
> className="w-full"
{content.map((list: any) => ( >
<SwiperSlide key={list?.id}> {content.map((list: any) => (
<div className="relative h-[310px] lg:h-[700px]"> <SwiperSlide key={list?.id}>
{/* Gambar */} <div className="relative h-[310px] lg:h-[700px]">
<ImageBlurry {/* Gambar */}
priority <ImageBlurry
src={list?.smallThumbnailLink} priority
alt="gambar" src={list?.smallThumbnailLink}
style={{ alt="gambar"
objectFit: "contain", style={{
width: "100%", objectFit: "contain",
height: "100%", width: "100%",
}} height: "100%",
/> }}
/>
{/* Overlay */} {/* Overlay */}
<div className="absolute inset-0 bg-black/40 z-10" /> <div className="absolute inset-0 bg-black/40 z-10" />
{/* Judul & Link */} {/* Judul & Link */}
<Link <Link
href={ href={
Number(list?.fileTypeId) === 1 Number(list?.fileTypeId) === 1
? `${prefixPath}/image/detail/${list?.slug}` ? `${prefixPath}/image/detail/${list?.slug}`
: Number(list?.fileTypeId) === 2 : Number(list?.fileTypeId) === 2
? `${prefixPath}/video/detail/${list?.slug}` ? `${prefixPath}/video/detail/${list?.slug}`
: Number(list?.fileTypeId) === 3 : Number(list?.fileTypeId) === 3
? `${prefixPath}/document/detail/${list?.slug}` ? `${prefixPath}/document/detail/${list?.slug}`
: `${prefixPath}/audio/detail/${list?.slug}` : `${prefixPath}/audio/detail/${list?.slug}`
} }
className="absolute bottom-10 lg:bottom-20 left-8 lg:left-32 z-20 text-white w-[85%] lg:w-[45%] cursor-pointer" className="absolute bottom-10 lg:bottom-20 left-8 lg:left-32 z-20 text-white w-[85%] lg:w-[45%] cursor-pointer"
> >
<span className="text-red-600 text-[10px] lg:text-lg font-bold uppercase"> <span className="text-red-600 text-[10px] lg:text-lg font-bold uppercase">
{list?.categoryName} {list?.categoryName}
</span> </span>
<h2 className="text-[14px] lg:text-xl font-bold">{list?.title}</h2> <h2 className="text-[14px] lg:text-xl font-bold">
<p className="text-[9px] lg:text-sm mt-2"> {list?.title}
{formatDateToIndonesian(new Date(list?.createdAt))}{" "} </h2>
{list?.timezone || "WIB"} | 👁 {list?.clickCount} <p className="text-[9px] lg:text-sm mt-2">
</p> {formatDateToIndonesian(new Date(list?.createdAt))}{" "}
</Link> {list?.timezone || "WIB"} | 👁 {list?.clickCount}
</p>
<div className="absolute left-2 top-[45%] z-30 -translate-y-1/2 hover:bg-black/70 text-white p-2 rounded-full"> </Link>
<Icon
icon="mdi:chevron-left"
className="w-5 lg:w-10 h-5 lg:h-10"
/>{" "}
</div> </div>
<div className="absolute right-2 top-[45%] z-30 -translate-y-1/2 hover:bg-black/70 text-white p-2 rounded-full"> </SwiperSlide>
<Icon ))}
icon="mdi:chevron-right" </Swiper>
className="w-5 lg:w-10 h-5 lg:h-10"
/>{" "} {/* Tombol navigasi — render SEKALI dan beri selector yang di-bind */}
</div> <button
</div> className="hero-prev absolute left-2 top-1/2 z-30 -translate-y-1/2 hover:bg-black/70 text-white p-2 rounded-full"
</SwiperSlide> aria-label="Previous"
))} >
</Swiper> <Icon
icon="mdi:chevron-left"
className="w-5 lg:w-10 h-5 lg:h-10"
/>
</button>
<button
className="hero-next absolute right-2 top-1/2 z-30 -translate-y-1/2 hover:bg-black/70 text-white p-2 rounded-full"
aria-label="Next"
>
<Icon
icon="mdi:chevron-right"
className="w-5 lg:w-10 h-5 lg:h-10"
/>
</button>
</>
)} )}
</div> </div>

View File

@ -557,9 +557,9 @@
"question1": "WHAT CONTENT IS ON MEDIAHUB AND WHAT CATEGORIES ARE THERE IN IT?", "question1": "WHAT CONTENT IS ON MEDIAHUB AND WHAT CATEGORIES ARE THERE IN IT?",
"answer1": "MediaHub has a variety of content such as news, videos, and documents categorized into topics such as education, entertainment, and current affairs.", "answer1": "MediaHub has a variety of content such as news, videos, and documents categorized into topics such as education, entertainment, and current affairs.",
"question2": "HOW CAN MEDIAHUB CONTENT BE DOWNLOADED?", "question2": "HOW CAN MEDIAHUB CONTENT BE DOWNLOADED?",
"answer2": "You can download content by clicking the download button available on each content on the MediaHub page.", "answer2": "A=) Login first to be able to download B=) Select the photo size you want C=) Click the download button available on each content on the MediaHub page.",
"question3": "WHO CAN REGISTER AS A MEDIAHUB USER?", "question3": "WHO CAN REGISTER AS A MEDIAHUB USER?",
"answer3": "Anyone who has an interest in the content on MediaHub can register as a user, either for personal or professional purposes.", "answer3": "Anyone interested in MediaHub content can register as a user, including individuals and professionals within the General Public, Police, Journalists, and Presidential Staff Office (KSP) categories.",
"question4": "WHAT IS MEDIAHUB? AND WHAT ARE THE FEATURES IN IT?", "question4": "WHAT IS MEDIAHUB? AND WHAT ARE THE FEATURES IN IT?",
"answer4": "MediaHub is a platform that provides various informative and educational content. Key features include search, download, and personalization of content.", "answer4": "MediaHub is a platform that provides various informative and educational content. Key features include search, download, and personalization of content.",
"welcome": "Welcome To", "welcome": "Welcome To",

View File

@ -560,9 +560,9 @@
"question1": "APA SAJA KONTEN-KONTEN YANG ADA DI MEDIAHUB DAN KATEGORI DI DALAMNYA?", "question1": "APA SAJA KONTEN-KONTEN YANG ADA DI MEDIAHUB DAN KATEGORI DI DALAMNYA?",
"answer1": "MediaHub memiliki beragam konten seperti berita, video, dan dokumen yang dikategorikan dalam topik seperti edukasi, hiburan, dan informasi terkini.", "answer1": "MediaHub memiliki beragam konten seperti berita, video, dan dokumen yang dikategorikan dalam topik seperti edukasi, hiburan, dan informasi terkini.",
"question2": "BAGAIMANA KONTEN DARI MEDIAHUB DAPAT DIUNDUH?", "question2": "BAGAIMANA KONTEN DARI MEDIAHUB DAPAT DIUNDUH?",
"answer2": "Anda dapat mengunduh konten dengan klik tombol unduh yang tersedia pada setiap konten di halaman MediaHub.", "answer2": "A=) Login terlebih dahulu untuk dapat mengunduh. B=) Pilih size foto yang anda inginkan. C=) Klik tombol unduh yang tersedia pada setiap konten di halaman MediaHub.",
"question3": "SIAPA SAJA YANG DAPAT MENDAFTARKAN DIRI SEBAGAI PENGGUNA MEDIAHUB?", "question3": "SIAPA SAJA YANG DAPAT MENDAFTARKAN DIRI SEBAGAI PENGGUNA MEDIAHUB?",
"answer3": "Semua orang yang memiliki minat terhadap konten di MediaHub dapat mendaftar sebagai pengguna, baik untuk personal maupun profesional.", "answer3": "Semua orang yang memiliki minat terhadap konten di MediaHub dapat mendaftar sebagai pengguna, baik untuk personal maupun profesional yang tergabung dalam kategpri Masyarakat Umum. Anggota Polri, Jurnalis serta KSP",
"question4": "APA ITU MEDIAHUB? DAN APA SAJA FITUR YANG ADA DI DALAMNYA?", "question4": "APA ITU MEDIAHUB? DAN APA SAJA FITUR YANG ADA DI DALAMNYA?",
"answer4": "MediaHub adalah platform yang menyediakan berbagai konten informatif dan edukatif. Fitur utama meliputi pencarian, pengunduhan, dan personalisasi konten.", "answer4": "MediaHub adalah platform yang menyediakan berbagai konten informatif dan edukatif. Fitur utama meliputi pencarian, pengunduhan, dan personalisasi konten.",
"welcome": "Selamat Datang di", "welcome": "Selamat Datang di",