feat: update placement on media

This commit is contained in:
hanif salafi 2025-09-12 14:45:42 +07:00
parent 82e8dfafc9
commit 6991c6d9b9
4 changed files with 1107 additions and 432 deletions

View File

@ -762,19 +762,26 @@ export default function FormAudioUpdate() {
const getPlacement = () => { const getPlacement = () => {
const temp = []; const temp = [];
for (let i = 0; i < filePlacements?.length; i++) { for (let i = 0; i < filePlacements?.length; i++) {
if (filePlacements[i]?.length !== 0) { const file = files[i] as any;
const now = filePlacements[i]; if (file.id && filePlacements[file.id] && filePlacements[file.id].length > 0) {
let nowArr = now?.join(",")?.replaceAll("nasional", "mabes"); const now = filePlacements[file.id];
nowArr = nowArr?.replaceAll("wilayah", "polda"); const normalizedNow = now.map((item): PlacementType => {
nowArr = nowArr?.replaceAll("semua", "all"); const value = String(item);
if (value === "nasional") return "mabes";
if (value === "wilayah") return "polda";
if (value === "semua") return "all";
return item as PlacementType;
});
const uniqueNow = Array.from(new Set(normalizedNow));
const nowArr = uniqueNow.join(",");
// Dapatkan checked levels untuk file ini // Dapatkan checked levels untuk file ini
const currentFileCheckedLevels = fileCheckedLevels[i] const currentFileCheckedLevels = fileCheckedLevels[file.id]
? Array.from(fileCheckedLevels[i]) ? Array.from(fileCheckedLevels[file.id])
: []; : [];
const data = { const data = {
mediaFileId: files[i]?.id, mediaFileId: file.id,
placements: nowArr, placements: nowArr,
customLocationPlacements: currentFileCheckedLevels.join(","), customLocationPlacements: currentFileCheckedLevels.join(","),
}; };
@ -859,12 +866,20 @@ export default function FormAudioUpdate() {
} }
temp[index] = now; temp[index] = now;
} else { } else {
// Handle mapping dari UI key ke backend value
let placementToAdd = placement;
if (placement === "nasional") {
placementToAdd = "mabes";
} else if (placement === "semua") {
placementToAdd = "all";
}
const now = temp[index] || []; const now = temp[index] || [];
if (!now.includes(placement)) { if (!now.includes(placementToAdd)) {
now.push(placement); now.push(placementToAdd);
} }
// Auto-checklist "all" jika nasional, wilayah, dan international ter-checklist // Auto-checklist "all" jika nasional, wilayah, dan international ter-checklist
const requiredItems = ["nasional", "wilayah", "international"]; const requiredItems = ["mabes", "wilayah", "international"];
const hasAllRequired = requiredItems.every(item => now.includes(item)); const hasAllRequired = requiredItems.every(item => now.includes(item));
if (hasAllRequired && !now.includes("all")) { if (hasAllRequired && !now.includes("all")) {
now.push("all"); now.push("all");
@ -921,14 +936,22 @@ export default function FormAudioUpdate() {
const now = temp[index]?.filter((a) => a !== "satker"); const now = temp[index]?.filter((a) => a !== "satker");
temp[index] = now; temp[index] = now;
} else { } else {
const now = temp[index]?.filter((a) => a !== placement); // Handle mapping dari UI key ke backend value
let placementToRemove = placement;
if (placement === "nasional") {
placementToRemove = "mabes";
} else if (placement === "semua") {
placementToRemove = "all";
}
const now = temp[index]?.filter((a) => a !== placementToRemove);
temp[index] = now; temp[index] = now;
} }
// Hapus "all" jika tidak semua item ter-checklist // Hapus "all" jika tidak semua item ter-checklist
const currentNow = temp[index] || []; const currentNow = temp[index] || [];
if (currentNow.includes("all")) { if (currentNow.includes("all")) {
const requiredItems = ["nasional", "wilayah", "international"]; const requiredItems = ["mabes", "wilayah", "international"];
const hasAllRequired = requiredItems.every(item => currentNow.includes(item)); const hasAllRequired = requiredItems.every(item => currentNow.includes(item));
if (!hasAllRequired) { if (!hasAllRequired) {
const newData = currentNow.filter((b) => b !== "all"); const newData = currentNow.filter((b) => b !== "all");
@ -1108,9 +1131,11 @@ export default function FormAudioUpdate() {
} }
if (placements.includes("polda")) { if (placements.includes("polda")) {
selection.polda = true; selection.polda = true;
selection.wilayah = true; // Auto-check wilayah when polda is present
} }
if (placements.includes("satker")) { if (placements.includes("satker")) {
selection.satker = true; selection.satker = true;
selection.wilayah = true; // Auto-check wilayah when satker is present
} }
if (placements.includes("international")) { if (placements.includes("international")) {
selection.international = true; selection.international = true;
@ -1609,14 +1634,16 @@ export default function FormAudioUpdate() {
<div className="grid gap-4"> <div className="grid gap-4">
{files.map((file: any, index: any) => ( {files.map((file: any, index: any) => (
<div <div
key={file.id} // Gunakan ID file sebagai key key={file.id}
className="flex justify-between border px-3.5 py-3 my-6 rounded-md" className="flex items-center border p-2 rounded-md"
> >
<div className="flex gap-3 items-center"> <div className="flex flex-wrap gap-3 items-center ">
<div className="flex-grow">
<div>
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
width="48" width="80"
height="48" height="80"
viewBox="0 0 20 20" viewBox="0 0 20 20"
> >
<path <path
@ -1624,38 +1651,332 @@ export default function FormAudioUpdate() {
d="M14.702 2.226A1 1 0 0 1 16 3.18v6.027a5.5 5.5 0 0 0-1-.184V6.18L8 8.368V15.5a2.5 2.5 0 1 1-1-2V5.368a1 1 0 0 1 .702-.955zM8 7.32l7-2.187V3.18L8 5.368zM5.5 14a1.5 1.5 0 1 0 0 3a1.5 1.5 0 0 0 0-3m13.5.5a4.5 4.5 0 1 1-9 0a4.5 4.5 0 0 1 9 0m-2.265-.436l-2.994-1.65a.5.5 0 0 0-.741.438v3.3a.5.5 0 0 0 .741.438l2.994-1.65a.5.5 0 0 0 0-.876" d="M14.702 2.226A1 1 0 0 1 16 3.18v6.027a5.5 5.5 0 0 0-1-.184V6.18L8 8.368V15.5a2.5 2.5 0 1 1-1-2V5.368a1 1 0 0 1 .702-.955zM8 7.32l7-2.187V3.18L8 5.368zM5.5 14a1.5 1.5 0 1 0 0 3a1.5 1.5 0 0 0 0-3m13.5.5a4.5 4.5 0 1 1-9 0a4.5 4.5 0 0 1 9 0m-2.265-.436l-2.994-1.65a.5.5 0 0 0-.741.438v3.3a.5.5 0 0 0 .741.438l2.994-1.65a.5.5 0 0 0 0-.876"
/> />
</svg>{" "} </svg>{" "}
<div> <p className="font-medium">{file.fileName}</p>
<div className="text-sm text-card-foreground">
{file.fileName || file.name}
</div> </div>
<div className="text-xs font-light text-muted-foreground"> <a
{Math.round(file.size / 100) / 10 > 1000 ? ( href={file.url}
target="_blank"
rel="noopener noreferrer"
className="text-blue-500 text-sm"
>
{t("view-file", {
defaultValue: "View File",
})}
</a>
</div>
<div className="bg-white rounded-md p-4 border">
{/* Checkbox Tingkat Utama */}
<div className="space-y-4">
<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
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>
{/* Detail Wilayah */}
{fileUnitSelections[index]?.wilayah && isDetailOfRegionShowed && (
<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: "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"
>
{item.label}
</Label>
</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"
>
<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 SATKER
</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>
{/* Tombol expand hanya untuk SATKER POLRI */}
{polda.name === "SATKER POLRI" && 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 hanya untuk SATKER POLRI */}
{polda.name === "SATKER POLRI" && 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
Math.round(file.size / 100) / 10000 icon="material-symbols:check-indeterminate-small"
).toFixed(1)} width={
12
}
height={
12
}
className="mr-1"
/>
Batal
Semua
</> </>
) : ( ) : (
<> <>
{(Math.round(file.size / 100) / 10).toFixed( <Icon
1 icon="material-symbols:check-all"
)} width={
12
}
height={
12
}
className="mr-1"
/>
Pilih
Semua
</> </>
)} )}
{" kb"}
</div>
</div>
</div>
<Button
size="icon"
color="destructive"
variant="outline"
className="border-none rounded-full"
onClick={() => handleDeleteFile(file.id)} // Kirim ID spesifik
>
<Icon icon="tabler:x" className="h-5 w-5" />
</Button> </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 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>Simpan</Button>
</DialogClose>
</div>
</DialogContent>
</Dialog>
</div>
</div>
</div>
)}
</div>
</div>
</div>
</div> </div>
))} ))}
</div> </div>

View File

@ -885,9 +885,11 @@ export default function FormImageUpdate() {
} }
if (placements.includes("polda")) { if (placements.includes("polda")) {
selection.polda = true; selection.polda = true;
selection.wilayah = true; // Auto-check wilayah when polda is present
} }
if (placements.includes("satker")) { if (placements.includes("satker")) {
selection.satker = true; selection.satker = true;
selection.wilayah = true; // Auto-check wilayah when satker is present
} }
if (placements.includes("international")) { if (placements.includes("international")) {
selection.international = true; selection.international = true;
@ -934,19 +936,26 @@ export default function FormImageUpdate() {
const getPlacement = () => { const getPlacement = () => {
const temp = []; const temp = [];
for (let i = 0; i < filePlacements?.length; i++) { for (let i = 0; i < filePlacements?.length; i++) {
if (filePlacements[i]?.length !== 0) { const file = files[i] as any;
const now = filePlacements[i]; if (file.id && filePlacements[file.id] && filePlacements[file.id].length > 0) {
let nowArr = now?.join(",")?.replaceAll("nasional", "mabes"); const now = filePlacements[file.id];
nowArr = nowArr?.replaceAll("wilayah", "polda"); const normalizedNow = now.map((item): PlacementType => {
nowArr = nowArr?.replaceAll("semua", "all"); const value = String(item);
if (value === "nasional") return "mabes";
if (value === "wilayah") return "polda";
if (value === "semua") return "all";
return item as PlacementType;
});
const uniqueNow = Array.from(new Set(normalizedNow));
const nowArr = uniqueNow.join(",");
// Dapatkan checked levels untuk file ini // Dapatkan checked levels untuk file ini
const currentFileCheckedLevels = fileCheckedLevels[i] const currentFileCheckedLevels = fileCheckedLevels[file.id]
? Array.from(fileCheckedLevels[i]) ? Array.from(fileCheckedLevels[file.id])
: []; : [];
const data = { const data = {
mediaFileId: files[i]?.id, mediaFileId: file.id,
placements: nowArr, placements: nowArr,
customLocationPlacements: currentFileCheckedLevels.join(","), customLocationPlacements: currentFileCheckedLevels.join(","),
}; };

View File

@ -804,25 +804,31 @@ export default function FormTeksUpdate() {
}; };
const getPlacement = () => { const getPlacement = () => {
const temp = []; const temp: Array<{ mediaFileId: number | string; placements: string; customLocationPlacements: string }> = [];
for (let i = 0; i < filePlacements?.length; i++) { for (let i = 0; i < files.length; i++) {
if (filePlacements[i]?.length !== 0) { const file = files[i] as any;
const now = filePlacements[i]; const now = filePlacements[i];
let nowArr = now?.join(",")?.replaceAll("nasional", "mabes"); if (file?.id && Array.isArray(now) && now.length > 0) {
nowArr = nowArr?.replaceAll("wilayah", "polda"); const normalizedNow = now.map((item): PlacementType => {
nowArr = nowArr?.replaceAll("semua", "all"); const value = String(item);
if (value === "nasional") return "mabes";
if (value === "wilayah") return "polda";
if (value === "semua") return "all";
return item as PlacementType;
});
const uniqueNow = Array.from(new Set(normalizedNow));
const nowArr = uniqueNow.join(",");
// Dapatkan checked levels untuk file ini // Dapatkan checked levels untuk file ini (berbasis index)
const currentFileCheckedLevels = fileCheckedLevels[i] const currentFileCheckedLevels = fileCheckedLevels[i]
? Array.from(fileCheckedLevels[i]) ? Array.from(fileCheckedLevels[i])
: []; : [];
const data = { temp.push({
mediaFileId: files[i]?.id, mediaFileId: file.id,
placements: nowArr, placements: nowArr,
customLocationPlacements: currentFileCheckedLevels.join(","), customLocationPlacements: currentFileCheckedLevels.join(","),
}; });
temp.push(data);
} }
} }
return temp; return temp;
@ -903,12 +909,20 @@ export default function FormTeksUpdate() {
} }
temp[index] = now; temp[index] = now;
} else { } else {
// Handle mapping dari UI key ke backend value
let placementToAdd = placement;
if (placement === "nasional") {
placementToAdd = "mabes";
} else if (placement === "semua") {
placementToAdd = "all";
}
const now = temp[index] || []; const now = temp[index] || [];
if (!now.includes(placement)) { if (!now.includes(placementToAdd)) {
now.push(placement); now.push(placementToAdd);
} }
// Auto-checklist "all" jika nasional, wilayah, dan international ter-checklist // Auto-checklist "all" jika nasional, wilayah, dan international ter-checklist
const requiredItems = ["nasional", "wilayah", "international"]; const requiredItems = ["mabes", "wilayah", "international"];
const hasAllRequired = requiredItems.every(item => now.includes(item)); const hasAllRequired = requiredItems.every(item => now.includes(item));
if (hasAllRequired && !now.includes("all")) { if (hasAllRequired && !now.includes("all")) {
now.push("all"); now.push("all");
@ -965,14 +979,22 @@ export default function FormTeksUpdate() {
const now = temp[index]?.filter((a) => a !== "satker"); const now = temp[index]?.filter((a) => a !== "satker");
temp[index] = now; temp[index] = now;
} else { } else {
const now = temp[index]?.filter((a) => a !== placement); // Handle mapping dari UI key ke backend value
let placementToRemove = placement;
if (placement === "nasional") {
placementToRemove = "mabes";
} else if (placement === "semua") {
placementToRemove = "all";
}
const now = temp[index]?.filter((a) => a !== placementToRemove);
temp[index] = now; temp[index] = now;
} }
// Hapus "all" jika tidak semua item ter-checklist // Hapus "all" jika tidak semua item ter-checklist
const currentNow = temp[index] || []; const currentNow = temp[index] || [];
if (currentNow.includes("all")) { if (currentNow.includes("all")) {
const requiredItems = ["nasional", "wilayah", "international"]; const requiredItems = ["mabes", "wilayah", "international"];
const hasAllRequired = requiredItems.every(item => currentNow.includes(item)); const hasAllRequired = requiredItems.every(item => currentNow.includes(item));
if (!hasAllRequired) { if (!hasAllRequired) {
const newData = currentNow.filter((b) => b !== "all"); const newData = currentNow.filter((b) => b !== "all");
@ -1084,11 +1106,10 @@ export default function FormTeksUpdate() {
})); }));
setFiles(formattedFiles); setFiles(formattedFiles);
// Inisialisasi filePlacements dari detail // Inisialisasi filePlacements dari detail (biarkan format backend, normalisasi dilakukan saat submit)
const initialFilePlacements: string[][] = details.files.map((file: any) => { const initialFilePlacements: string[][] = details.files.map((file: any) => {
if (file.placements) { if (file.placements) {
const placements = file.placements.split(",").map((p: string) => p.trim()); return file.placements.split(",").map((p: string) => p.trim());
return placements;
} }
return []; return [];
}); });
@ -1123,7 +1144,6 @@ export default function FormTeksUpdate() {
if (file.placements) { if (file.placements) {
const placements = file.placements.split(",").map((p: string) => p.trim()); const placements = file.placements.split(",").map((p: string) => p.trim());
// Map dari format backend ke checkbox
if (placements.includes("all")) { if (placements.includes("all")) {
selection.semua = true; selection.semua = true;
selection.nasional = true; selection.nasional = true;
@ -1132,23 +1152,21 @@ export default function FormTeksUpdate() {
selection.polda = true; selection.polda = true;
selection.satker = true; selection.satker = true;
} else { } else {
if (placements.includes("mabes")) { if (placements.includes("mabes")) selection.nasional = true;
selection.nasional = true; if (placements.includes("international")) selection.international = true;
} if (placements.includes("polda")) selection.polda = true;
if (placements.includes("wilayah")) { if (placements.includes("satker")) selection.satker = true;
// Wilayah aktif jika ada "wilayah" ATAU ada polda/satker
if (
placements.includes("wilayah") ||
placements.includes("polda") ||
placements.includes("satker")
) {
selection.wilayah = true; selection.wilayah = true;
} }
if (placements.includes("polda")) {
selection.polda = true;
}
if (placements.includes("satker")) {
selection.satker = true;
}
if (placements.includes("international")) {
selection.international = true;
}
} }
} }
return selection; return selection;
}); });
setFileUnitSelections(initialFileUnitSelections); setFileUnitSelections(initialFileUnitSelections);
@ -1240,6 +1258,16 @@ export default function FormTeksUpdate() {
setIsStartUpload(true); setIsStartUpload(true);
setProgressList(progressInfoArr); setProgressList(progressInfoArr);
// Update file placements terlebih dahulu (sebelum upload)
const placementData = getPlacement();
if (placementData.length > 0) {
const responseFilePlacements = await updateFilePlacements(placementData);
if (responseFilePlacements?.error) {
error(responseFilePlacements?.message);
return false;
}
}
close(); close();
// showProgress(); // showProgress();
files.map(async (item: any, index: number) => { files.map(async (item: any, index: number) => {
@ -1251,12 +1279,6 @@ export default function FormTeksUpdate() {
); );
}); });
// Update file placements
const placementData = getPlacement();
if (placementData.length > 0) {
await updateFilePlacements(placementData);
}
MySwal.fire({ MySwal.fire({
title: "Sukses", title: "Sukses",
text: "Data berhasil disimpan.", text: "Data berhasil disimpan.",
@ -1594,13 +1616,13 @@ export default function FormTeksUpdate() {
</Fragment> </Fragment>
) : null} ) : null}
{files.length > 0 && ( {files.length > 0 && (
<div className="mt-4 space-y-2"> <div className="mt-4">
<Label className="text-lg font-semibold"> <Label className="text-md font-semibold">
{" "} {" "}
{t("file-media", { defaultValue: "File Media" })} {t("file-media", { defaultValue: "File Media" })}
</Label> </Label>
<div className="grid gap-4"> <div className="grid gap-4">
{files.map((file: any) => ( {files.map((file: any, index: any) => (
<div <div
key={file.id} key={file.id}
className="flex items-center border p-2 rounded-md" className="flex items-center border p-2 rounded-md"
@ -1624,196 +1646,317 @@ export default function FormTeksUpdate() {
})} })}
</a> </a>
</div> </div>
<div> <div className="bg-white rounded-md p-4 border">
<Label className="flex items-center space-x-2"> {/* Checkbox Tingkat Utama */}
<input <div className="space-y-4">
type="checkbox" <div className="grid grid-cols-2 md:grid-cols-4 gap-3">
checked={fileUnitSelections[files.indexOf(file)]?.semua || false} {[
onChange={(e) => { 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[files.indexOf(file)]?.[
item.key as keyof typeof unitSelection
] || false
}
onCheckedChange={(value) => {
handleFileUnitChange( handleFileUnitChange(
files.indexOf(file), files.indexOf(file),
"semua", item.key as keyof typeof unitSelection,
e.target.checked value as boolean
) );
} setupPlacement(
className="form-checkbox" files.indexOf(file),
item.key as keyof typeof unitSelection,
Boolean(value)
);
}}
/> />
<span> <Label
{t("all", { defaultValue: "All" })} htmlFor={`${item.key}-${index}`}
</span> className="text-sm font-medium cursor-pointer"
>
{item.label}
</Label> </Label>
</div> </div>
<div> ))}
<Label className="flex items-center space-x-2"> </div>
<input
type="checkbox" {/* Detail Wilayah */}
checked={fileUnitSelections[files.indexOf(file)]?.nasional || false} {fileUnitSelections[files.indexOf(file)]?.wilayah && isDetailOfRegionShowed && (
onChange={(e) => <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: "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[files.indexOf(file)]?.[
item.key as keyof typeof unitSelection
] || false
}
onCheckedChange={(value) => {
handleFileUnitChange( handleFileUnitChange(
files.indexOf(file), files.indexOf(file),
"nasional", item.key as keyof typeof unitSelection,
e.target.checked value as boolean
) );
} setupPlacement(
className="form-checkbox"
/>
<span>Nasional</span>
</Label>
</div>
<div>
<Label className="flex items-center space-x-2">
<input
type="checkbox"
checked={fileUnitSelections[files.indexOf(file)]?.wilayah || false}
onChange={(e) =>
handleFileUnitChange(
files.indexOf(file), files.indexOf(file),
"wilayah", item.key as keyof typeof unitSelection,
e.target.checked Boolean(value)
) );
} }}
className="form-checkbox"
/> />
<span>Wilayah</span> <Label
htmlFor={`${item.key}-${index}`}
className="text-sm font-medium cursor-pointer"
>
{item.label}
</Label> </Label>
</div> </div>
<div> ))}
<Label className="flex items-center space-x-2">
<input {/* Tombol Kustom sejajar dengan checkbox */}
type="checkbox" <div className="flex items-center justify-center p-3">
checked={fileUnitSelections[files.indexOf(file)]?.international || false}
onChange={(e) =>
handleFileUnitChange(
files.indexOf(file),
"international",
e.target.checked
)
}
className="form-checkbox"
/>
<span>Internasional</span>
</Label>
</div>
<div>
<Label className="flex items-center space-x-2">
<input
type="checkbox"
checked={fileUnitSelections[files.indexOf(file)]?.polda || false}
onChange={(e) =>
handleFileUnitChange(
files.indexOf(file),
"polda",
e.target.checked
)
}
className="form-checkbox"
/>
<span>POLDA</span>
</Label>
</div>
<div>
<Label className="flex items-center space-x-2">
<input
type="checkbox"
checked={fileUnitSelections[files.indexOf(file)]?.satker || false}
onChange={(e) =>
handleFileUnitChange(
files.indexOf(file),
"satker",
e.target.checked
)
}
className="form-checkbox"
/>
<span>SATKER</span>
</Label>
</div>
<div>
<Dialog> <Dialog>
<DialogTrigger asChild> <DialogTrigger asChild>
<Button variant="outline" size="sm"> <Button
Detail Wilayah variant="outline"
size="sm"
className="gap-2"
>
<Icon
icon="material-symbols:tune"
width={16}
height={16}
/>
{t("custom", {
defaultValue: "Kustom",
})}
</Button> </Button>
</DialogTrigger> </DialogTrigger>
<DialogContent className="max-w-2xl"> <DialogContent className="max-w-[95vw] lg:max-w-[1400px] max-h-[90vh]">
<DialogHeader> <DialogHeader className="border-b border-gray-200 pb-4">
<DialogTitle>Daftar Wilayah</DialogTitle> <DialogTitle className="text-lg font-semibold">
Daftar Wilayah POLDA dan SATKER
</DialogTitle>
</DialogHeader> </DialogHeader>
<div className="max-h-96 overflow-y-auto"> <div className="grid grid-cols-1 lg:grid-cols-2 xl:grid-cols-3 gap-4 max-h-[70vh] overflow-y-auto p-1">
{isLoading ? ( {listDest.map(
<div className="flex justify-center items-center h-32"> (polda: any) => (
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-gray-900"></div> <div
</div> key={polda.id}
) : ( className="border border-gray-200 rounded-lg p-2 bg-white hover:shadow-sm transition-shadow"
<div className="space-y-2"> >
{listDest.map((polda: any) => ( {/* Header POLDA */}
<div key={polda.id} className="space-y-1"> <div className="flex items-center justify-between">
<div className="flex items-center space-x-2"> <Label className="flex items-center gap-3 flex-1 cursor-pointer">
<input <Checkbox
type="checkbox" checked={
checked={fileCheckedLevels[files.indexOf(file)]?.has(Number(polda.id)) || false} fileCheckedLevels[
onChange={(e) => files.indexOf(file)
]?.has(
Number(
polda.id
)
) || false
}
onCheckedChange={() =>
handleFileCheckboxChangePlacement( handleFileCheckboxChangePlacement(
files.indexOf(file), files.indexOf(file),
Number(polda.id) Number(polda.id)
) )
} }
className="form-checkbox"
/> />
<span className="font-medium"> <span className="font-semibold text-gray-900 text-sm">
{polda.name} {polda.name}
</span> </span>
</Label>
{/* Tombol expand hanya untuk SATKER POLRI */}
{polda.name === "SATKER POLRI" && polda.subDestination && ( {polda.name === "SATKER POLRI" && polda.subDestination && (
<Button <button
variant="outline" onClick={(e) => {
size="sm" e.preventDefault();
onClick={() => toggleExpand(Number(polda.id))} e.stopPropagation();
toggleExpand(
Number(polda.id)
);
}}
className="p-1 hover:bg-gray-100 rounded-md transition-colors"
> >
{expandedPolda[Number(polda.id)] ? "Tutup" : "Buka"} <Icon
</Button> icon={
expandedPolda[Number(polda.id)]
? "mdi:chevron-up"
: "mdi:chevron-down"
}
width={16}
height={16}
/>
</button>
)} )}
</div> </div>
{polda.name === "SATKER POLRI" && polda.subDestination && expandedPolda[Number(polda.id)] && (
<div className="ml-6 space-y-1"> {/* Sub-items hanya untuk SATKER POLRI */}
<div className="flex items-center space-x-2"> {polda.name === "SATKER POLRI" && polda.subDestination &&
expandedPolda[Number(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[
files.indexOf(file)
]?.has(
Number(
sub.id
)
)
);
return (
<Button <Button
variant="outline"
size="sm" size="sm"
onClick={() => handleSelectAllSubItems(files.indexOf(file), polda)} variant="outline"
className="text-xs h-6 px-2"
onClick={() =>
handleSelectAllSubItems(
files.indexOf(file),
polda
)
}
> >
{polda.subDestination.every((sub: any) => {allSubItemsChecked ? (
fileCheckedLevels[files.indexOf(file)]?.has(Number(sub.id)) <>
) ? "Batal Pilih Semua" : "Pilih Semua"} <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> </Button>
);
})()}
</div> </div>
{polda.subDestination.map((sub: any) => ( <div className="space-y-1">
<div key={sub.id} className="flex items-center space-x-2 ml-4"> {polda.subDestination.map(
<input (
type="checkbox" sub: any
checked={fileCheckedLevels[files.indexOf(file)]?.has(Number(sub.id)) || false} ) => (
onChange={(e) => <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[
files.indexOf(file)
]?.has(
Number(
sub.id
)
) ||
false
}
onCheckedChange={() =>
handleFileCheckboxChangePlacement( handleFileCheckboxChangePlacement(
files.indexOf(file), files.indexOf(file),
Number(sub.id) Number(sub.id)
) )
} }
className="form-checkbox"
/> />
<span className="text-sm">{sub.name}</span> <span className="text-gray-700">
</div> {
))} sub.name
</div> }
</span>
</Label>
)
)} )}
</div> </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>Simpan</Button>
</DialogClose>
</div> </div>
</DialogContent> </DialogContent>
</Dialog> </Dialog>
</div> </div>
</div> </div>
</div> </div>
)}
</div>
</div>
</div>
</div>
))} ))}
</div> </div>
</div> </div>

View File

@ -25,7 +25,7 @@ import {
} from "@/components/ui/select"; } from "@/components/ui/select";
import { Checkbox } from "@/components/ui/checkbox"; import { Checkbox } from "@/components/ui/checkbox";
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"; import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog"; import { Dialog, DialogClose, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog";
import { register } from "module"; import { register } from "module";
import { Switch } from "@/components/ui/switch"; import { Switch } from "@/components/ui/switch";
@ -93,6 +93,7 @@ type Detail = {
}; };
interface FileWithPreview extends File { interface FileWithPreview extends File {
id: string;
preview: string; preview: string;
} }
@ -146,6 +147,9 @@ export default function FormVideoUpdate() {
type VideoSchema = z.infer<typeof videoSchema>; type VideoSchema = z.infer<typeof videoSchema>;
let progressInfo: any = []; let progressInfo: any = [];
let counterUpdateProgress = 0; let counterUpdateProgress = 0;
const isDetailOfRegionShowed = false;
const [progressList, setProgressList] = useState<any>([]); const [progressList, setProgressList] = useState<any>([]);
let uploadPersen = 0; let uploadPersen = 0;
const [isStartUpload, setIsStartUpload] = useState(false); const [isStartUpload, setIsStartUpload] = useState(false);
@ -764,19 +768,36 @@ export default function FormVideoUpdate() {
}); });
}; };
const getPlacement = (fileId?: string): string => { const getPlacement = () => {
if (fileId && filePlacements[fileId]) { const temp = [];
const placements = filePlacements[fileId]; for (let i = 0; i < files.length; i++) {
let nowArr = placements.join(","); const file = files[i] as any;
nowArr = nowArr?.replaceAll("all", "all"); if (file.id && filePlacements[file.id] && filePlacements[file.id].length > 0) {
nowArr = nowArr?.replaceAll("mabes", "mabes"); const now = filePlacements[file.id];
nowArr = nowArr?.replaceAll("wilayah", "polda"); const normalizedNow = now.map((item): PlacementType => {
nowArr = nowArr?.replaceAll("polda", "polda"); const value = String(item);
nowArr = nowArr?.replaceAll("satker", "satker"); if (value === "nasional") return "mabes";
nowArr = nowArr?.replaceAll("international", "international"); if (value === "wilayah") return "polda";
return nowArr; if (value === "semua") return "all";
return item as PlacementType;
});
const uniqueNow = Array.from(new Set(normalizedNow));
const nowArr = uniqueNow.join(",");
// Dapatkan checked levels untuk file ini
const currentFileCheckedLevels = fileCheckedLevels[file.id]
? Array.from(fileCheckedLevels[file.id])
: [];
const data = {
mediaFileId: file.id,
placements: nowArr,
customLocationPlacements: currentFileCheckedLevels.join(","),
};
temp.push(data);
} }
return ""; }
return temp;
}; };
const setupPlacement = (fileId: string, placement: string, isChecked: boolean) => { const setupPlacement = (fileId: string, placement: string, isChecked: boolean) => {
@ -926,12 +947,10 @@ export default function FormVideoUpdate() {
} }
// Update file placements // Update file placements
if (files.length > 0) { const responseFilePlacements = await updateFilePlacements(getPlacement());
files.forEach((file: any) => { if (responseFilePlacements?.error) {
if (file.id) { error(responseFilePlacements?.message);
updateFilePlacements(getPlacement(file.id)); return false;
}
});
} }
if (selectedFiles.length > 0) { if (selectedFiles.length > 0) {
@ -1317,13 +1336,13 @@ export default function FormVideoUpdate() {
</Fragment> </Fragment>
) : null} ) : null}
{files.length > 0 && ( {files.length > 0 && (
<div className="mt-4 space-y-2"> <div className="mt-4">
<Label className="text-lg font-semibold"> <Label className="text-md font-semibold">
{" "} {" "}
{t("file-media", { defaultValue: "File Media" })} {t("file-media", { defaultValue: "File Media" })}
</Label> </Label>
<div className="grid gap-4"> <div className="grid gap-4">
{files.map((file: any) => ( {files.map((file: any, index: any) => (
<div <div
key={file.id} key={file.id}
className="flex items-center border p-2 rounded-md" className="flex items-center border p-2 rounded-md"
@ -1347,139 +1366,322 @@ export default function FormVideoUpdate() {
})} })}
</a> </a>
</div> </div>
<div className="flex flex-wrap gap-3 items-center"> <div className="bg-white rounded-md p-4 border">
<div> {/* Checkbox Tingkat Utama */}
<Label className="flex items-center space-x-2"> <div className="space-y-4">
<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 <Checkbox
checked={fileUnitSelections[file.id]?.semua || false} // id={`${item.key}-${index}`}
onCheckedChange={(checked) => checked={
handleFileUnitChange(file.id, "semua", checked as boolean) fileUnitSelections[file.id]?.[
item.key as keyof typeof unitSelection
] || false
} }
onCheckedChange={(value) => {
handleFileUnitChange(
file.id,
item.key,
value as boolean
);
setupPlacement(
file.id,
item.key,
Boolean(value)
);
}}
/> />
<span> <Label
{t("all", { defaultValue: "All" })} htmlFor={`${item.key}-${index}`}
</span> className="text-sm font-medium cursor-pointer"
>
{item.label}
</Label> </Label>
</div> </div>
<div> ))}
<Label className="flex items-center space-x-2"> </div>
{/* Detail Wilayah */}
{fileUnitSelections[file.id]?.wilayah && isDetailOfRegionShowed && (
<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: "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 <Checkbox
checked={fileUnitSelections[file.id]?.nasional || false} id={`${item.key}-${index}`}
onCheckedChange={(checked) => checked={
handleFileUnitChange(file.id, "nasional", checked as boolean) fileUnitSelections[file.id]?.[
item.key as keyof typeof unitSelection
] || false
} }
onCheckedChange={(value) => {
handleFileUnitChange(
file.id,
item.key,
value as boolean
);
setupPlacement(
file.id,
item.key,
Boolean(value)
);
}}
/> />
<span>Nasional</span> <Label
htmlFor={`${item.key}-${index}`}
className="text-sm font-medium cursor-pointer"
>
{item.label}
</Label> </Label>
</div> </div>
<div> ))}
<Label className="flex items-center space-x-2">
<Checkbox {/* Tombol Kustom sejajar dengan checkbox */}
checked={fileUnitSelections[file.id]?.wilayah || false} <div className="flex items-center justify-center p-3">
onCheckedChange={(checked) =>
handleFileUnitChange(file.id, "wilayah", checked as boolean)
}
/>
<span>Wilayah</span>
</Label>
</div>
<div>
<Label className="flex items-center space-x-2">
<Checkbox
checked={fileUnitSelections[file.id]?.international || false}
onCheckedChange={(checked) =>
handleFileUnitChange(file.id, "international", checked as boolean)
}
/>
<span>Internasional</span>
</Label>
</div>
<div>
<Dialog> <Dialog>
<DialogTrigger asChild> <DialogTrigger asChild>
<Button <Button
type="button"
variant="outline" variant="outline"
size="sm" size="sm"
onClick={() => setTempFile(file)} className="gap-2"
> >
Detail Wilayah <Icon
icon="material-symbols:tune"
width={16}
height={16}
/>
{t("custom", {
defaultValue: "Kustom",
})}
</Button> </Button>
</DialogTrigger> </DialogTrigger>
<DialogContent className="max-w-2xl"> <DialogContent className="max-w-[95vw] lg:max-w-[1400px] max-h-[90vh]">
<DialogHeader> <DialogHeader className="border-b border-gray-200 pb-4">
<DialogTitle>Daftar Wilayah POLDA dan SATKER</DialogTitle> <DialogTitle className="text-lg font-semibold">
Daftar Wilayah POLDA dan SATKER
</DialogTitle>
</DialogHeader> </DialogHeader>
<div className="max-h-96 overflow-y-auto"> <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?.filter((item) => item.levelNumber === 2) {listDest.map(
.map((poldaItem) => ( (polda: any) => (
<div key={poldaItem.id} className="mb-4"> <div
<div className="flex items-center space-x-2 mb-2"> 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 <Checkbox
checked={fileCheckedLevels[file.id]?.has(poldaItem.id) || false} checked={
onCheckedChange={(checked) => fileCheckedLevels[
handleFileCheckboxChangePlacement(file.id, poldaItem, checked as boolean) file.id
]?.has(
Number(
polda.id
)
) || false
}
onCheckedChange={() =>
handleFileCheckboxChangePlacement(
file.id,
polda,
!fileCheckedLevels[file.id]?.has(Number(polda.id))
)
} }
/> />
<Label className="font-medium"> <span className="font-semibold text-gray-900 text-sm">
{poldaItem.name} {polda.name}
</span>
</Label> </Label>
{poldaItem.name === "SATKER POLRI" && poldaItem.subDestination.length > 0 && ( {/* Tombol expand hanya untuk SATKER POLRI */}
<Button {polda.name === "SATKER POLRI" && polda.subDestination && (
type="button" <button
variant="ghost" onClick={(e) => {
size="sm" e.preventDefault();
onClick={() => toggleExpand(poldaItem.id)} e.stopPropagation();
> toggleExpand(
{expandedPolda.has(poldaItem.id) ? "Tutup" : "Buka"} polda.id
</Button> );
)}
</div>
{poldaItem.name === "SATKER POLRI" && expandedPolda.has(poldaItem.id) && (
<div className="ml-6 space-y-2">
<div className="flex items-center space-x-2">
<Button
type="button"
variant="outline"
size="sm"
onClick={() => handleSelectAllSubItems(file.id, poldaItem)}
>
Pilih Semua
</Button>
</div>
{poldaItem.subDestination.map((satkerItem) => (
<div key={satkerItem.id} className="flex items-center space-x-2 ml-4">
<Checkbox
checked={fileCheckedLevels[file.id]?.has(satkerItem.id) || false}
onCheckedChange={(checked) => {
setFileCheckedLevels(prev => {
const currentFileLevels = prev[file.id] || new Set<number>();
const newFileLevels = new Set(currentFileLevels);
if (checked) {
newFileLevels.add(satkerItem.id);
} else {
newFileLevels.delete(satkerItem.id);
}
return { ...prev, [file.id]: newFileLevels };
});
updateMainCheckboxFromModal(file.id);
}} }}
className="p-1 hover:bg-gray-100 rounded-md transition-colors"
>
<Icon
icon={
expandedPolda.has(
polda.id
)
? "mdi:chevron-up"
: "mdi:chevron-down"
}
width={16}
height={16}
/> />
<Label className="text-sm"> </button>
{satkerItem.name}
</Label>
</div>
))}
</div>
)} )}
</div> </div>
))}
{/* Sub-items hanya untuk SATKER POLRI */}
{polda.name === "SATKER POLRI" && polda.subDestination &&
expandedPolda.has(
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[
file.id
]?.has(
Number(
sub.id
)
)
);
return (
<Button
size="sm"
variant="outline"
className="text-xs h-6 px-2"
onClick={() =>
handleSelectAllSubItems(
file.id,
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>
);
})()}
</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[
file.id
]?.has(
Number(
sub.id
)
) ||
false
}
onCheckedChange={() =>
handleFileCheckboxChangePlacement(
file.id,
polda,
!fileCheckedLevels[file.id]?.has(Number(sub.id))
)
}
/>
<span className="text-gray-700">
{
sub.name
}
</span>
</Label>
)
)}
</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>Simpan</Button>
</DialogClose>
</div> </div>
</DialogContent> </DialogContent>
</Dialog> </Dialog>
</div> </div>
</div> </div>
</div> </div>
)}
</div>
</div>
</div>
</div> </div>
))} ))}
</div> </div>