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,
cell: ({ row }) => {
const MySwal = withReactContent(Swal);
const levelNumber = Number(getCookiesDecrypt("ulne")); // 1 = Mabes, 2 = Polda
const calendarOwnerLevel = Number(row.original.assignedToLevel); // dari API
const calendarOwner = row.original.assignedTo; // ID unit Polda/Mabes dari API
const myUnit = getCookiesDecrypt("unitId"); // misal ID unit Polda login
const levelNumber = Number(getCookiesDecrypt("ulne"));
const userId = Number(getCookiesDecrypt("uie"));
async function doDelete(id: any) {
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 (
<DropdownMenu>
<DropdownMenuTrigger asChild>
@ -178,7 +153,7 @@ const useTableColumns = () => {
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent className="p-0" align="end">
{canView && (
<Link
href={`/contributor/schedule/calendar-polri/detail/${row.original.id}`}
>
@ -187,9 +162,9 @@ const useTableColumns = () => {
Detail
</DropdownMenuItem>
</Link>
)}
{canEdit && (
{row.original.createdById === userId && (
<Link
href={`/contributor/schedule/calendar-polri/update/${row.original.id}`}
>
@ -200,7 +175,7 @@ const useTableColumns = () => {
</Link>
)}
{canDelete && (
{row.original.createdById === userId && (
<DropdownMenuItem
onClick={() => handleDeleteCalendars(row.original.id)}
className="p-2 border-b text-destructive bg-destructive/30 focus:bg-destructive focus:text-destructive-foreground rounded-none"
@ -214,7 +189,7 @@ const useTableColumns = () => {
);
},
},
// {
// id: "actions",
// accessorKey: "action",

View File

@ -2,6 +2,7 @@
import { Reveal } from "@/components/landing-page/Reveal";
import React, { useState } from "react";
import { useTranslations } from "next-intl";
import Image from "next/image";
interface FAQItem {
question: string;
@ -41,19 +42,41 @@ const FAQS: React.FC = () => {
{/* Header */}
<Reveal>
<div className="flex items-center justify-center mb-6">
<img src="/assets/icons-faqs.png" alt="Faqs" />
<h2 className="ml-4 text-lg lg:text-2xl font-bold text-gray-800 dark:text-white">Frequently Asked Questions</h2>
<Image
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>
{/* FAQS Items */}
<div className="space-y-4">
{faqs?.map((faq, index) => (
<div key={index} className="border-b border-gray-300 pb-2 cursor-pointer">
<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
key={index}
className="border-b border-gray-300 pb-2 cursor-pointer"
>
<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>
{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>

View File

@ -27,7 +27,7 @@ import {
saveMediaBlastCampaign,
} from "@/service/broadcast/broadcast";
import { error } from "@/config/swal";
import { useRouter } from "@/i18n/routing";
import { Link, useRouter } from "@/i18n/routing";
import { useEffect, useRef, useState } from "react";
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 }) {
const editor = useRef(null);
const id = useParams()?.id;
@ -85,7 +90,7 @@ export default function ContentBlast(props: { type: string }) {
const router = useRouter();
const { type } = props;
const [dataSelectCampaign, setDataSelectCampaign] = useState([]);
const [dataSelectCampaign, setDataSelectCampaign] = useState<Campaign[]>([]);
const [openModal, setOpenModal] = useState(false);
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!
</div>
<DialogFooter className="flex justify-center">
<Button type="button" color="success">
Menu "Sent"
</Button>
<Link
href={`/admin/broadcast/campaign-list/detail/${
form.getValues("selected")[0]?.id
}`}
>
<Button type="button" color="success">
Menu "Sent"
</Button>
</Link>
<Button
type="button"
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
const handleFileUnitChange = (
fileIndex: number,
@ -219,6 +225,30 @@ export default function FormAudioDetail() {
currentSelection.polda = value;
currentSelection.polres = 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 {
// Validasi khusus untuk POLRES - harus ada POLDA yang ter-checklist
if (key === "polres" && value) {
@ -480,10 +510,28 @@ export default function FormAudioDetail() {
const setupPlacementCheck = (length: number) => {
const temp = [];
const unitSelections = [];
const checkedLevelsArray = [];
for (let i = 0; i < 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);
};
useEffect(() => {
@ -587,8 +635,24 @@ export default function FormAudioDetail() {
const temp = [];
for (let i = 0; i < filePlacements?.length; i++) {
if (filePlacements[i]?.length !== 0) {
const now = filePlacements[i]?.filter((a) => a !== "all");
const data = { mediaFileId: files[i]?.id, placements: now.join(",") };
// const now = filePlacements[i]?.filter((a) => a !== "all");
// 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);
}
}
@ -596,13 +660,19 @@ export default function FormAudioDetail() {
};
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 = {
mediaUploadId: id,
statusId: status,
message: description,
// files: [],
files: isUserMabesApprover ? getPlacement() : [],
customLocationPlacement: Array.from(checkedLevels).join(","),
};
setModalOpen(false);
loading();
@ -638,37 +708,129 @@ export default function FormAudioDetail() {
setRejectedFiles(rejects);
}
// const setupPlacement = (
// index: number,
// placement: string,
// checked: boolean
// ) => {
// let temp = [...filePlacements];
// if (checked) {
// if (placement === "all") {
// temp[index] = ["all", "mabes", "polda", "international"];
// } else {
// const now = temp[index];
// now?.push(placement);
// if (now?.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);
// temp[index] = now;
// if (now?.length === 3 && now?.includes("all")) {
// const newData = now.filter((b) => b !== "all");
// temp[index] = newData;
// }
// }
// }
// setFilePlacements(temp);
// };
const setupPlacement = (
index: number,
placement: string,
checked: boolean
) => {
let temp = [...filePlacements];
if (checked) {
if (placement === "all") {
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") {
// 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 = (
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 = (
fileIndex: number,
@ -992,6 +1099,15 @@ export default function FormAudioDetail() {
currentSelection.polres = checkedPolresCount > 0;
currentSelection.satker = Boolean(isSatkerChecked);
// Update checkbox "semua" berdasarkan semua checkbox yang aktif
currentSelection.semua =
currentSelection.nasional &&
currentSelection.wilayah &&
currentSelection.international &&
currentSelection.polda &&
currentSelection.polres &&
currentSelection.satker;
newSelections[fileIndex] = currentSelection;
return newSelections;
});
@ -1303,6 +1419,13 @@ export default function FormAudioDetail() {
<p className="text-sm text-gray-600 break-all">
{file.fileName}
</p>
{isUserMabesApprover ? (
""
) : (
<p className="status text-success text-sm mb-0">
Selesai
</p>
)}
</div>
</div>
<button
@ -1320,84 +1443,36 @@ export default function FormAudioDetail() {
</div>
{/* Section Pengaturan Distribusi */}
<div className="bg-white rounded-md p-4 border">
<h5 className="font-medium text-gray-900 mb-4 flex items-center gap-2">
<Icon
icon="material-symbols:settings-outline"
width={18}
height={18}
/>
Pengaturan Distribusi
</h5>
{isUserMabesApprover ? (
<div className="bg-white rounded-md p-4 border">
<h5 className="font-medium text-gray-900 mb-4 flex items-center gap-2">
<Icon
icon="material-symbols:settings-outline"
width={18}
height={18}
/>
Pengaturan Distribusi
</h5>
{/* Checkbox Tingkat Utama */}
<div className="space-y-4">
<div>
<p className="text-sm font-medium text-gray-700 mb-3">
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:
{/* Checkbox Tingkat Utama */}
<div className="space-y-4">
<div>
<p className="text-sm font-medium text-gray-700 mb-3">
Tingkat Distribusi:
</p>
{/* Checkbox Sub-kategori dengan tombol Kustom sejajar */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-3">
<div className="grid grid-cols-2 md:grid-cols-4 gap-3">
{[
{ key: "polda", label: "POLDA" },
{ key: "polres", label: "POLRES" },
{ key: "satker", label: "SATKER" },
{ 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-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
id={`${item.key}-${index}`}
@ -1427,199 +1502,260 @@ export default function FormAudioDetail() {
</Label>
</div>
))}
</div>
</div>
{/* Tombol Kustom sejajar dengan checkbox */}
<div className="flex items-center justify-center p-3">
<Dialog>
<DialogTrigger asChild>
<Button
variant="outline"
size="sm"
className="gap-2"
{/* 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>
{/* 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
icon="material-symbols:tune"
width={16}
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>
{item.label}
</Label>
</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[
index
]?.has(
Number(sub.id)
)
);
return (
<Button
size="sm"
variant="outline"
className="text-xs h-6 px-2"
onClick={() =>
handleSelectAllSubItems(
index,
polda
)
}
>
{allSubItemsChecked ? (
<>
<Icon
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>
{/* Tombol Kustom sejajar dengan checkbox */}
<div className="flex items-center justify-center p-3">
<Dialog>
<DialogTrigger asChild>
<Button
variant="outline"
size="sm"
className="gap-2"
>
<Icon
icon="material-symbols:tune"
width={16}
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
);
})()}
</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={
}}
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 */}
{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[
index
]?.has(
Number(
sub.id
)
) || false
}
onCheckedChange={() =>
handleFileCheckboxChangePlacement(
)
);
return (
<Button
size="sm"
variant="outline"
className="text-xs h-6 px-2"
onClick={() =>
handleSelectAllSubItems(
index,
Number(
sub.id
)
polda
)
}
/>
<span className="text-gray-700">
{sub.name}
</span>
</Label>
)
)}
>
{allSubItemsChecked ? (
<>
<Icon
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 className="flex justify-end gap-3 border-t border-gray-200 pt-4">
<DialogClose asChild>
<Button variant="outline">
{t("cancel", {
defaultValue: "Batal",
})}
</Button>
</DialogClose>
<DialogClose asChild>
<Button>
{t("save", {
defaultValue: "Simpan",
})}
</Button>
</DialogClose>
</div>
</DialogContent>
</Dialog>
)}
</div>
))}
</div>
<div className="flex justify-end gap-3 border-t border-gray-200 pt-4">
<DialogClose asChild>
<Button variant="outline">
{t("cancel", {
defaultValue: "Batal",
})}
</Button>
</DialogClose>
<DialogClose asChild>
<Button>
{/* {t("save", {
defaultValue: "Simpan",
})} */}
Simpan
</Button>
</DialogClose>
</div>
</DialogContent>
</Dialog>
</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
const handleFileUnitChange = (
fileIndex: number,
@ -208,6 +214,30 @@ export default function FormTeksDetail() {
currentSelection.polda = value;
currentSelection.polres = 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 {
// Validasi khusus untuk POLRES - harus ada POLDA yang ter-checklist
if (key === "polres" && value) {
@ -440,10 +470,28 @@ export default function FormTeksDetail() {
const setupPlacementCheck = (length: number) => {
const temp = [];
const unitSelections = [];
const checkedLevelsArray = [];
for (let i = 0; i < 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);
};
useEffect(() => {
@ -460,6 +508,7 @@ export default function FormTeksDetail() {
names: details?.files[0]?.fileName,
format: details?.files[0]?.format,
});
setupPlacementCheck(details?.files?.length);
if (details?.assignedToLevel) {
const levels = new Set(
@ -530,12 +579,27 @@ export default function FormTeksDetail() {
};
const getPlacement = () => {
// console.log("getPlaa", filePlacements);
const temp = [];
for (let i = 0; i < filePlacements?.length; i++) {
if (filePlacements[i]?.length !== 0) {
const now = filePlacements[i]?.filter((a) => a !== "all");
const data = { mediaFileId: files[i]?.id, placements: now?.join(",") };
// const now = filePlacements[i]?.filter((a) => a !== "all");
// 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);
}
}
@ -543,12 +607,19 @@ export default function FormTeksDetail() {
};
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 = {
mediaUploadId: id,
statusId: status,
message: description,
files: isUserMabesApprover ? getPlacement() : [],
customLocationPlacement: Array.from(checkedLevels).join(","),
};
setModalOpen(false);
@ -653,6 +724,43 @@ export default function FormTeksDetail() {
if (checked) {
if (placement === "all") {
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") {
// Ketika satker di-checklist, HANYA tambahkan satker saja
// JANGAN otomatis checklist polres
@ -679,6 +787,36 @@ export default function FormTeksDetail() {
} 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);
@ -930,6 +1068,15 @@ export default function FormTeksDetail() {
currentSelection.polres = checkedPolresCount > 0;
currentSelection.satker = Boolean(isSatkerChecked);
// Update checkbox "semua" berdasarkan semua checkbox yang aktif
currentSelection.semua =
currentSelection.nasional &&
currentSelection.wilayah &&
currentSelection.international &&
currentSelection.polda &&
currentSelection.polres &&
currentSelection.satker;
newSelections[fileIndex] = currentSelection;
return newSelections;
});
@ -1264,6 +1411,13 @@ export default function FormTeksDetail() {
<p className="text-sm text-gray-600 break-all">
{file.fileName}
</p>
{isUserMabesApprover ? (
""
) : (
<p className="status text-success text-sm mb-0">
Selesai
</p>
)}
</div>
</div>
<button
@ -1281,84 +1435,36 @@ export default function FormTeksDetail() {
</div>
{/* Section Pengaturan Distribusi */}
<div className="bg-white rounded-md p-4 border">
<h5 className="font-medium text-gray-900 mb-4 flex items-center gap-2">
<Icon
icon="material-symbols:settings-outline"
width={18}
height={18}
/>
Pengaturan Distribusi
</h5>
{isUserMabesApprover ? (
<div className="bg-white rounded-md p-4 border">
<h5 className="font-medium text-gray-900 mb-4 flex items-center gap-2">
<Icon
icon="material-symbols:settings-outline"
width={18}
height={18}
/>
Pengaturan Distribusi
</h5>
{/* Checkbox Tingkat Utama */}
<div className="space-y-4">
<div>
<p className="text-sm font-medium text-gray-700 mb-3">
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:
{/* Checkbox Tingkat Utama */}
<div className="space-y-4">
<div>
<p className="text-sm font-medium text-gray-700 mb-3">
Tingkat Distribusi:
</p>
{/* Checkbox Sub-kategori dengan tombol Kustom sejajar */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-3">
<div className="grid grid-cols-2 md:grid-cols-4 gap-3">
{[
{ key: "polda", label: "POLDA" },
{ key: "polres", label: "POLRES" },
{ key: "satker", label: "SATKER" },
{ 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-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
id={`${item.key}-${index}`}
@ -1388,199 +1494,260 @@ export default function FormTeksDetail() {
</Label>
</div>
))}
</div>
</div>
{/* Tombol Kustom sejajar dengan checkbox */}
<div className="flex items-center justify-center p-3">
<Dialog>
<DialogTrigger asChild>
<Button
variant="outline"
size="sm"
className="gap-2"
{/* 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>
{/* 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
icon="material-symbols:tune"
width={16}
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>
{item.label}
</Label>
</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[
index
]?.has(
Number(sub.id)
)
);
return (
<Button
size="sm"
variant="outline"
className="text-xs h-6 px-2"
onClick={() =>
handleSelectAllSubItems(
index,
polda
)
}
>
{allSubItemsChecked ? (
<>
<Icon
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>
{/* Tombol Kustom sejajar dengan checkbox */}
<div className="flex items-center justify-center p-3">
<Dialog>
<DialogTrigger asChild>
<Button
variant="outline"
size="sm"
className="gap-2"
>
<Icon
icon="material-symbols:tune"
width={16}
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
);
})()}
</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={
}}
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 */}
{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[
index
]?.has(
Number(
sub.id
)
) || false
}
onCheckedChange={() =>
handleFileCheckboxChangePlacement(
)
);
return (
<Button
size="sm"
variant="outline"
className="text-xs h-6 px-2"
onClick={() =>
handleSelectAllSubItems(
index,
Number(
sub.id
)
polda
)
}
/>
<span className="text-gray-700">
{sub.name}
</span>
</Label>
)
)}
>
{allSubItemsChecked ? (
<>
<Icon
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 className="flex justify-end gap-3 border-t border-gray-200 pt-4">
<DialogClose asChild>
<Button variant="outline">
{t("cancel", {
defaultValue: "Batal",
})}
</Button>
</DialogClose>
<DialogClose asChild>
<Button>
{t("save", {
defaultValue: "Simpan",
})}
</Button>
</DialogClose>
</div>
</DialogContent>
</Dialog>
)}
</div>
))}
</div>
<div className="flex justify-end gap-3 border-t border-gray-200 pt-4">
<DialogClose asChild>
<Button variant="outline">
{t("cancel", {
defaultValue: "Batal",
})}
</Button>
</DialogClose>
<DialogClose asChild>
<Button>
{/* {t("save", {
defaultValue: "Simpan",
})} */}
Simpan
</Button>
</DialogClose>
</div>
</DialogContent>
</Dialog>
</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
const handleFileUnitChange = (
fileIndex: number,
@ -211,6 +217,30 @@ export default function FormVideoDetail() {
currentSelection.polda = value;
currentSelection.polres = 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 {
// Validasi khusus untuk POLRES - harus ada POLDA yang ter-checklist
if (key === "polres" && value) {
@ -479,6 +509,31 @@ export default function FormVideoDetail() {
);
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);
setApproval(approvals?.data?.data);
}
@ -523,12 +578,19 @@ export default function FormVideoDetail() {
};
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 = {
mediaUploadId: id,
statusId: status,
message: description,
files: isUserMabesApprover ? getPlacement() : [],
customLocationPlacement: Array.from(checkedLevels).join(","),
};
setModalOpen(false);
loading();
@ -557,12 +619,28 @@ export default function FormVideoDetail() {
}
const getPlacement = () => {
// console.log("getPlaa", filePlacements);
const temp = [];
for (let i = 0; i < filePlacements?.length; i++) {
if (filePlacements[i].length !== 0) {
const now = filePlacements[i].filter((a) => a !== "all");
const data = { mediaFileId: files[i].id, placements: now.join(",") };
// const now = filePlacements[i].filter((a) => a !== "all");
// 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);
}
}
@ -611,6 +689,43 @@ export default function FormVideoDetail() {
if (checked) {
if (placement === "all") {
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") {
// Ketika satker di-checklist, HANYA tambahkan satker saja
// JANGAN otomatis checklist polres
@ -637,6 +752,36 @@ export default function FormVideoDetail() {
} 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);
@ -925,6 +1070,15 @@ export default function FormVideoDetail() {
currentSelection.polres = checkedPolresCount > 0;
currentSelection.satker = Boolean(isSatkerChecked);
// Update checkbox "semua" berdasarkan semua checkbox yang aktif
currentSelection.semua =
currentSelection.nasional &&
currentSelection.wilayah &&
currentSelection.international &&
currentSelection.polda &&
currentSelection.polres &&
currentSelection.satker;
newSelections[fileIndex] = currentSelection;
return newSelections;
});
@ -1285,84 +1439,36 @@ export default function FormVideoDetail() {
</div>
{/* Section Pengaturan Distribusi */}
<div className="bg-white rounded-md p-4 border">
<h5 className="font-medium text-gray-900 mb-4 flex items-center gap-2">
<Icon
icon="material-symbols:settings-outline"
width={18}
height={18}
/>
Pengaturan Distribusi
</h5>
{isUserMabesApprover ? (
<div className="bg-white rounded-md p-4 border">
<h5 className="font-medium text-gray-900 mb-4 flex items-center gap-2">
<Icon
icon="material-symbols:settings-outline"
width={18}
height={18}
/>
Pengaturan Distribusi
</h5>
{/* Checkbox Tingkat Utama */}
<div className="space-y-4">
<div>
<p className="text-sm font-medium text-gray-700 mb-3">
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:
{/* Checkbox Tingkat Utama */}
<div className="space-y-4">
<div>
<p className="text-sm font-medium text-gray-700 mb-3">
Tingkat Distribusi:
</p>
{/* Checkbox Sub-kategori dengan tombol Kustom sejajar */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-3">
<div className="grid grid-cols-2 md:grid-cols-4 gap-3">
{[
{ key: "polda", label: "POLDA" },
{ key: "polres", label: "POLRES" },
{ key: "satker", label: "SATKER" },
{ 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-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
id={`${item.key}-${index}`}
@ -1392,199 +1498,260 @@ export default function FormVideoDetail() {
</Label>
</div>
))}
</div>
</div>
{/* Tombol Kustom sejajar dengan checkbox */}
<div className="flex items-center justify-center p-3">
<Dialog>
<DialogTrigger asChild>
<Button
variant="outline"
size="sm"
className="gap-2"
{/* 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>
{/* 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
icon="material-symbols:tune"
width={16}
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>
{item.label}
</Label>
</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[
index
]?.has(
Number(sub.id)
)
);
return (
<Button
size="sm"
variant="outline"
className="text-xs h-6 px-2"
onClick={() =>
handleSelectAllSubItems(
index,
polda
)
}
>
{allSubItemsChecked ? (
<>
<Icon
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>
{/* Tombol Kustom sejajar dengan checkbox */}
<div className="flex items-center justify-center p-3">
<Dialog>
<DialogTrigger asChild>
<Button
variant="outline"
size="sm"
className="gap-2"
>
<Icon
icon="material-symbols:tune"
width={16}
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
);
})()}
</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={
}}
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 */}
{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[
index
]?.has(
Number(
sub.id
)
) || false
}
onCheckedChange={() =>
handleFileCheckboxChangePlacement(
)
);
return (
<Button
size="sm"
variant="outline"
className="text-xs h-6 px-2"
onClick={() =>
handleSelectAllSubItems(
index,
Number(
sub.id
)
polda
)
}
/>
<span className="text-gray-700">
{sub.name}
</span>
</Label>
)
)}
>
{allSubItemsChecked ? (
<>
<Icon
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 className="flex justify-end gap-3 border-t border-gray-200 pt-4">
<DialogClose asChild>
<Button variant="outline">
{t("cancel", {
defaultValue: "Batal",
})}
</Button>
</DialogClose>
<DialogClose asChild>
<Button>
{t("save", {
defaultValue: "Simpan",
})}
</Button>
</DialogClose>
</div>
</DialogContent>
</Dialog>
)}
</div>
))}
</div>
<div className="flex justify-end gap-3 border-t border-gray-200 pt-4">
<DialogClose asChild>
<Button variant="outline">
{t("cancel", {
defaultValue: "Batal",
})}
</Button>
</DialogClose>
<DialogClose asChild>
<Button>
{/* {t("save", {
defaultValue: "Simpan",
})} */}
Simpan
</Button>
</DialogClose>
</div>
</DialogContent>
</Dialog>
</div>
</div>
</div>
</div>
)}
)}
</div>
</div>
</div>
) : (
""
)}
</div>
))}
</div>

View File

@ -95,11 +95,19 @@ export function CalendarPolriAddDetail() {
const details = response?.data?.data;
setDetail(details);
if (details) {
// if (details) {
// setDate({
// from: parseISO(details.startDate),
// to: parseISO(details.endDate),
// });
// }
if (details?.startDate && details?.endDate) {
setDate({
from: parseISO(details.startDate),
to: parseISO(details.endDate),
});
} else {
// atau biarkan kosong sesuai kebutuhan
}
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 { useEffect, useState } from "react";
import * as React from "react";
import { Dialog, DialogContent, DialogTrigger } from "@/components/ui/dialog";
interface Advertisement {
id: string;
placements: string;
imageUrl: string;
contentFileUrl?: 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]);
}, [props.data, props.loading, props.placement]);
return (
<div
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>}
{ads?.map(
(ad) =>
ad && (
<img
key={ad.id}
src={ad.contentFileUrl}
alt={`Banner ${ad.id}`}
className="w-full"
/>
<Dialog key={ad.id}>
<DialogTrigger asChild>
<img
src={ad.contentFileUrl || ad.imageUrl}
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>

View File

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

View File

@ -557,9 +557,9 @@
"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.",
"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?",
"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?",
"answer4": "MediaHub is a platform that provides various informative and educational content. Key features include search, download, and personalization of content.",
"welcome": "Welcome To",

View File

@ -560,9 +560,9 @@
"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.",
"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?",
"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?",
"answer4": "MediaHub adalah platform yang menyediakan berbagai konten informatif dan edukatif. Fitur utama meliputi pencarian, pengunduhan, dan personalisasi konten.",
"welcome": "Selamat Datang di",