This commit is contained in:
Sabda Yagra 2025-08-23 22:04:54 +07:00
parent e5a2fc2bf1
commit 285bcad1d8
5 changed files with 1561 additions and 1215 deletions

View File

@ -198,6 +198,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,
@ -1298,6 +1304,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
@ -1315,84 +1328,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}`}
@ -1422,199 +1387,259 @@ 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", })}
})} </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

@ -191,6 +191,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,
@ -1266,6 +1272,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
@ -1283,84 +1296,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}`}
@ -1390,199 +1355,259 @@ 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", })}
})} </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

@ -190,6 +190,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,
@ -1283,84 +1289,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}`}
@ -1390,199 +1348,259 @@ 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", })}
})} </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

@ -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>