mediahub-fe/components/form/content/image-update-form.tsx

2343 lines
91 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client";
import React, {
ChangeEvent,
Fragment,
useEffect,
useRef,
useState,
} from "react";
import { useForm, Controller } from "react-hook-form";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
import { Label } from "@/components/ui/label";
import { Card } from "@/components/ui/card";
import { zodResolver } from "@hookform/resolvers/zod";
import * as z from "zod";
import Swal from "sweetalert2";
import withReactContent from "sweetalert2-react-content";
import { useParams, useRouter } from "next/navigation";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { Checkbox } from "@/components/ui/checkbox";
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
import { register } from "module";
import { Switch } from "@/components/ui/switch";
import Cookies from "js-cookie";
import {
createMedia,
deleteFile,
deleteMedia,
getTagsBySubCategoryId,
listEnableCategory,
updateFilePlacements,
uploadThumbnail,
} from "@/service/content/content";
import { detailMedia } from "@/service/curated-content/curated-content";
import { Badge } from "@/components/ui/badge";
import { CloudUpload, MailIcon } from "lucide-react";
import dynamic from "next/dynamic";
import { useDropzone } from "react-dropzone";
import { Icon } from "@iconify/react/dist/iconify.js";
import Image from "next/image";
import { error, loading } from "@/lib/swal";
import { getCsrfToken } from "@/service/auth";
import { Upload } from "tus-js-client";
import { useTranslations } from "next-intl";
import { htmlToString } from "@/utils/globals";
import { getUserLevelForAssignments } from "@/service/task";
import {
Dialog,
DialogClose,
DialogContent,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import { v4 as uuidv4 } from "uuid";
import { getCookiesDecrypt } from "@/lib/utils";
import { translateText } from "@/service/content/ai";
import { close } from "@/config/swal";
const imageSchema = z.object({
title: z.string().min(1, { message: "Judul diperlukan" }),
description: z
.string()
.min(2, { message: "Narasi Penugasan harus lebih dari 2 karakter." }),
creatorName: z.string().min(1, { message: "Creator diperlukan" }),
// tags: z.string().min(1, { message: "Judul diperlukan" }),
});
type Category = {
id: string;
name: string;
};
type PlacementType = "all" | "mabes" | "polda" | "international" | string;
interface FilePlacement {
mediaFileId: number;
placements?: PlacementType[];
}
interface TempFileItem {
id: number | string;
}
type Detail = {
id: string;
title: string;
description: string;
htmldescription: string;
slug: string;
categoryId: number;
category: {
id: string;
name: string;
};
publishedFor: string;
publishedForObject: {
id: number;
name: string;
};
creatorName: string;
categoryName: string;
thumbnailLink: string;
tags: string;
};
type Option = {
id: string;
name: string;
};
interface Destination {
id: string;
name: string;
subDestination?: SubDestination[];
}
interface SubDestination {
id: string;
name: string;
}
const CustomEditor = dynamic(
() => {
return import("@/components/editor/custom-editor");
},
{ ssr: false }
);
interface FileWithPreview extends File {
id: string;
preview: string;
}
export default function FormImageUpdate() {
const MySwal = withReactContent(Swal);
const router = useRouter();
const { id } = useParams() as { id: string };
const editor = useRef(null);
const roleId = getCookiesDecrypt("urie");
type ImageSchema = z.infer<typeof imageSchema>;
let progressInfo: any = [];
let counterUpdateProgress = 0;
const [progressList, setProgressList] = useState<any>([]);
let uploadPersen = 0;
const isDetailOfRegionShowed = false;
const [isStartUpload, setIsStartUpload] = useState(false);
const [counterProgress, setCounterProgress] = useState(0);
const t = useTranslations("Form");
const [selectedFiles, setSelectedFiles] = useState<File[]>([]);
const taskId = Cookies.get("taskId");
const scheduleId = Cookies.get("scheduleId");
const scheduleType = Cookies.get("scheduleType");
const [categories, setCategories] = useState<Category[]>([]);
const [selectedCategory, setSelectedCategory] = useState<any>();
const [tags, setTags] = useState<any[]>([]);
const [detail, setDetail] = useState<Detail>();
const [refresh, setRefresh] = useState(false);
const [selectedPublishers, setSelectedPublishers] = useState<number[]>([]);
const [articleBody, setArticleBody] = useState<string>("");
const [files, setFiles] = useState<FileWithPreview[]>([]);
const [filesTemp, setFilesTemp] = useState<File[]>([]);
const [publishedFor, setPublishedFor] = useState<string[]>([]);
const [thumbnailFile, setThumbnailFile] = useState<File | null>(null);
const inputRef = useRef<HTMLInputElement>(null);
const [isLoadingTranslate, setIsLoadingTranslate] = useState(false);
const [translatedContent, setTranslatedContent] = React.useState("");
const [selectedLang, setSelectedLang] = React.useState<"id" | "en">("id");
const handleThumbnailChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];
if (file) {
setThumbnailFile(file);
}
};
const [fileUnitSelections, setFileUnitSelections] = useState<
Array<{
semua: boolean;
nasional: boolean;
wilayah: boolean;
international: boolean;
polda: boolean;
satker: boolean;
}>
>([]);
// State global untuk kompatibilitas (akan dihapus nanti)
const [unitSelection, setUnitSelection] = useState({
semua: false,
nasional: false,
wilayah: false,
international: false,
polda: false,
satker: false,
});
const [checkedLevels, setCheckedLevels] = useState<Set<number>>(new Set());
const [listDest, setListDest] = useState<Destination[]>([]);
const [isLoading, setIsLoading] = useState(false);
const [expandedPolda, setExpandedPolda] = useState<Record<number, boolean>>(
{}
);
const [fileCheckedLevels, setFileCheckedLevels] = useState<
Array<Set<number>>
>([]);
const [isUpdatingFromMainCheckbox, setIsUpdatingFromMainCheckbox] =
useState(false);
const [mainCheckboxChangeType, setMainCheckboxChangeType] =
useState<string>("");
useEffect(() => {
async function fetchPoldaPolres() {
setIsLoading(true);
try {
const response = await getUserLevelForAssignments();
setListDest(response?.data?.data.list);
const initialExpandedState = response?.data?.data.list.reduce(
(acc: any, polda: any) => {
acc[polda.id] = false;
return acc;
},
{}
);
setExpandedPolda(initialExpandedState);
console.log("polres", initialExpandedState);
} catch (error) {
console.error("Error fetching Polda/Polres data:", error);
} finally {
setIsLoading(false);
}
}
fetchPoldaPolres();
}, []);
// useEffect untuk sinkronisasi checkbox modal dengan checkbox utama
useEffect(() => {
if (
listDest.length > 0 &&
isUpdatingFromMainCheckbox &&
mainCheckboxChangeType
) {
syncModalWithMainCheckbox();
}
}, [isUpdatingFromMainCheckbox, mainCheckboxChangeType]);
// useEffect untuk update checkbox utama ketika pilihan modal berubah
useEffect(() => {
if (!isUpdatingFromMainCheckbox && listDest.length > 0) {
updateMainCheckboxFromModalLegacy();
}
}, [checkedLevels, isUpdatingFromMainCheckbox]);
// Fungsi untuk update checkbox utama berdasarkan checkbox modal (global/legacy)
const updateMainCheckboxFromModalLegacy = () => {
if (!isUpdatingFromMainCheckbox && listDest.length > 0) {
// Hitung item yang dipilih berdasarkan checkedLevels
const checkedPoldaCount = listDest.filter(
(item: any) =>
item.levelNumber === 2 &&
item.name !== "SATKER POLRI" &&
checkedLevels.has(Number(item.id))
).length;
const satkerItem: any = listDest.find(
(item: any) => item.name === "SATKER POLRI"
);
const checkedSatkerCount = satkerItem
? (checkedLevels.has(Number(satkerItem.id)) ? 1 : 0) +
(satkerItem.subDestination?.filter((sub: any) =>
checkedLevels.has(Number(sub.id))
).length || 0)
: 0;
// Checkbox aktif jika ADA item yang dipilih dalam kategori tersebut
const hasSelectedPolda = checkedPoldaCount > 0;
const hasSelectedSatker = checkedSatkerCount > 0;
// Update unitSelection berdasarkan yang dipilih di modal
setUnitSelection((prev) => {
const newState = { ...prev };
// Update individual checkboxes
newState.polda = hasSelectedPolda;
newState.satker = hasSelectedSatker;
// Update checkbox "semua" berdasarkan semua checkbox yang aktif
newState.semua =
newState.nasional &&
newState.wilayah &&
newState.international &&
hasSelectedPolda &&
hasSelectedSatker;
return newState;
});
}
};
// Fungsi untuk sinkronisasi checkbox modal dengan checkbox utama
const syncModalWithMainCheckbox = () => {
if (isUpdatingFromMainCheckbox) {
const newCheckedLevels = new Set(checkedLevels);
// Handle checklist actions - menambahkan semua item ke modal
if (mainCheckboxChangeType === "polda_checked") {
// Checklist semua polda
listDest.forEach((item: any) => {
if (item.levelNumber === 2 && item.name !== "SATKER POLRI") {
newCheckedLevels.add(Number(item.id));
}
});
} else if (mainCheckboxChangeType === "satker_checked") {
// Checklist satker
const satkerItem: any = listDest.find(
(item: any) => item.name === "SATKER POLRI"
);
if (satkerItem) {
newCheckedLevels.add(Number(satkerItem.id));
// Checklist semua sub-item yang ada di bawah SATKER (bukan POLRES)
if (satkerItem.subDestination) {
satkerItem.subDestination.forEach((sub: any) => {
newCheckedLevels.add(Number(sub.id));
});
}
}
} else if (mainCheckboxChangeType === "semua_checked") {
// Checklist semua item
listDest.forEach((item: any) => {
newCheckedLevels.add(Number(item.id));
// Checklist semua sub-item di bawah setiap item
if (item.subDestination) {
item.subDestination.forEach((sub: any) => {
newCheckedLevels.add(Number(sub.id));
});
}
});
} else if (mainCheckboxChangeType === "polda_unchecked") {
// Clear polda dari checkedLevels, tapi jangan hapus SATKER POLRI
listDest.forEach((item: any) => {
if (item.levelNumber === 2 && item.name !== "SATKER POLRI") {
newCheckedLevels.delete(Number(item.id));
}
});
} else if (mainCheckboxChangeType === "satker_unchecked") {
// Clear satker dan semua sub-item di bawahnya dari checkedLevels
const satkerItem: any = listDest.find(
(item: any) => item.name === "SATKER POLRI"
);
if (satkerItem) {
newCheckedLevels.delete(Number(satkerItem.id));
if (satkerItem.subDestination) {
satkerItem.subDestination.forEach((sub: any) => {
newCheckedLevels.delete(Number(sub.id));
});
}
}
} else if (mainCheckboxChangeType === "semua_unchecked") {
// Clear semua
newCheckedLevels.clear();
}
setCheckedLevels(newCheckedLevels);
// Reset flag setelah sinkronisasi selesai
setIsUpdatingFromMainCheckbox(false);
setMainCheckboxChangeType("");
}
};
// Fungsi untuk mengupdate state individual file
const handleFileUnitChange = (
fileIndex: number,
key: keyof typeof unitSelection,
value: boolean
) => {
setFileUnitSelections((prev) => {
const newSelections = [...prev];
const currentSelection = { ...newSelections[fileIndex] };
if (key === "semua") {
// Jika klik Semua, set semua value ke true/false
currentSelection.semua = value;
currentSelection.nasional = value;
currentSelection.wilayah = value;
currentSelection.international = value;
currentSelection.polda = 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 {
// Jika wilayah dicentang, auto centang POLDA, SATKER
if (key === "wilayah") {
currentSelection.wilayah = value;
if (value) {
// Ketika wilayah dicentang, auto centang POLDA, SATKER
currentSelection.polda = true;
currentSelection.satker = true;
// Update fileCheckedLevels untuk mengisi semua POLDA dan SATKER POLRI ketika wilayah dicentang
setFileCheckedLevels((prevLevels) => {
const newArray = [...prevLevels];
const currentFileLevels = new Set<number>(
newArray[fileIndex] || new Set()
);
// Checklist semua POLDA di modal
listDest.forEach((item: any) => {
if (item.levelNumber === 2 && item.name !== "SATKER POLRI") {
currentFileLevels.add(Number(item.id));
}
});
// Checklist SATKER POLRI dan semua sub-itemsnya
const satkerItem = listDest.find(
(item: any) => item.name === "SATKER POLRI"
);
if (satkerItem) {
currentFileLevels.add(Number(satkerItem.id));
// Checklist semua sub-items di bawah SATKER POLRI
if (satkerItem.subDestination) {
satkerItem.subDestination.forEach((sub: any) => {
currentFileLevels.add(Number(sub.id));
});
}
}
newArray[fileIndex] = currentFileLevels;
return newArray;
});
} else {
// Ketika wilayah di-uncheck, uncheck POLDA, SATKER dan hapus data POLDA dari fileCheckedLevels
currentSelection.polda = false;
currentSelection.satker = false;
// Update fileCheckedLevels untuk menghapus semua POLDA dan SATKER POLRI ketika wilayah di-uncheck
setFileCheckedLevels((prevLevels) => {
const newArray = [...prevLevels];
const currentFileLevels = new Set<number>(
newArray[fileIndex] || new Set()
);
// Hapus semua POLDA dari modal
listDest.forEach((item: any) => {
if (item.levelNumber === 2 && item.name !== "SATKER POLRI") {
currentFileLevels.delete(Number(item.id));
}
});
// Hapus SATKER POLRI dan semua sub-itemsnya
const satkerItem = listDest.find(
(item: any) => item.name === "SATKER POLRI"
);
if (satkerItem) {
currentFileLevels.delete(Number(satkerItem.id));
// Hapus semua sub-items di bawah SATKER POLRI
if (satkerItem.subDestination) {
satkerItem.subDestination.forEach((sub: any) => {
currentFileLevels.delete(Number(sub.id));
});
}
}
newArray[fileIndex] = currentFileLevels;
return newArray;
});
}
} else {
// Update salah satu saja
currentSelection[key] = value;
}
// Cek apakah semua selain "semua" sudah dicentang
const allChecked = [
"nasional",
"wilayah",
"international",
"polda",
"satker",
].every((k) => currentSelection[k as keyof typeof unitSelection]);
currentSelection.semua = allChecked;
}
newSelections[fileIndex] = currentSelection;
return newSelections;
});
};
// Fungsi untuk mengupdate checklist levels untuk file tertentu
const handleFileCheckboxChangePlacement = (
fileIndex: number,
levelId: number
) => {
setFileCheckedLevels((prev) => {
const newArray = [...prev];
const currentFileLevels = new Set<number>(newArray[fileIndex]);
const isCurrentlyChecked = currentFileLevels.has(levelId);
if (isCurrentlyChecked) {
currentFileLevels.delete(levelId);
// Jika ini adalah POLDA yang di-unchecklist, unchecklist juga semua polres di bawahnya
const poldaItem = listDest.find(
(item: any) => Number(item.id) === levelId
) as any;
if (
poldaItem &&
poldaItem.subDestination &&
poldaItem.name !== "SATKER POLRI"
) {
poldaItem.subDestination.forEach((polres: any) => {
currentFileLevels.delete(Number(polres.id));
});
}
// Jika ini adalah SATKER POLRI yang di-unchecklist, unchecklist juga semua sub-item di bawahnya
if (poldaItem && poldaItem.name === "SATKER POLRI") {
poldaItem.subDestination?.forEach((subItem: any) => {
currentFileLevels.delete(Number(subItem.id));
});
}
} else {
currentFileLevels.add(levelId);
// Jika ini adalah SATKER POLRI yang di-checklist, checklist juga semua sub-item di bawahnya
const satkerItem = listDest.find(
(item: any) => Number(item.id) === levelId
) as any;
if (satkerItem && satkerItem.name === "SATKER POLRI") {
// Checklist semua sub-item di bawah SATKER POLRI (bukan POLRES)
satkerItem.subDestination?.forEach((subItem: any) => {
currentFileLevels.add(Number(subItem.id));
});
}
}
newArray[fileIndex] = currentFileLevels;
// Update checkbox utama berdasarkan perubahan di modal
// Pindahkan ke sini agar state sudah ter-update
setTimeout(() => updateMainCheckboxFromModal(fileIndex), 0);
return newArray;
});
};
// Fungsi untuk mengupdate checkbox utama berdasarkan checklist di modal
const updateMainCheckboxFromModal = (fileIndex: number) => {
setFileUnitSelections((prev) => {
const newSelections = [...prev];
const currentSelection = { ...newSelections[fileIndex] };
const currentFileLevels = fileCheckedLevels[fileIndex];
if (!currentFileLevels) return prev;
// Hitung total POLDA yang ada (bukan SATKER POLRI)
const totalPoldaCount = listDest.filter(
(item: any) => item.levelNumber === 2 && item.name !== "SATKER POLRI"
).length;
// Hitung berapa banyak POLDA yang ter-checklist (bukan SATKER POLRI)
const checkedPoldaCount = listDest.reduce((total: number, item: any) => {
if (
item.levelNumber === 2 &&
item.name !== "SATKER POLRI" &&
currentFileLevels.has(Number(item.id))
) {
return total + 1;
}
return total;
}, 0);
// Cek apakah SATKER POLRI ter-checklist
const satkerItem = listDest.find(
(item: any) => item.name === "SATKER POLRI"
);
const isSatkerChecked =
satkerItem && currentFileLevels.has(Number(satkerItem.id));
// Update checkbox berdasarkan kondisi
// POLDA aktif jika ada minimal 1 POLDA ter-checklist
currentSelection.polda = checkedPoldaCount > 0;
currentSelection.satker = Boolean(isSatkerChecked);
// Update checkbox "semua" berdasarkan semua checkbox yang aktif
currentSelection.semua =
currentSelection.nasional &&
currentSelection.wilayah &&
currentSelection.international &&
currentSelection.polda &&
currentSelection.satker;
newSelections[fileIndex] = currentSelection;
return newSelections;
});
};
const toggleExpand = (id: number) => {
setExpandedPolda((prev) => ({
...prev,
[id]: !prev[id],
}));
};
// Fungsi untuk menangani "Pilih Semua" sub-items di bawah SATKER POLRI
const handleSelectAllSubItems = (fileIndex: number, polda: any) => {
setFileCheckedLevels((prev) => {
const newArray = [...prev];
const currentFileLevels = new Set<number>(newArray[fileIndex]);
// Cek apakah semua sub-items sudah ter-checklist
const allSubItemsChecked = polda.subDestination?.every((sub: any) =>
currentFileLevels.has(Number(sub.id))
);
if (allSubItemsChecked) {
// Jika semua sudah ter-checklist, unchecklist semuanya
polda.subDestination?.forEach((sub: any) => {
currentFileLevels.delete(Number(sub.id));
});
} else {
// Jika belum semua ter-checklist, checklist semuanya
// Checklist SATKER POLRI juga jika belum ter-checklist
if (!currentFileLevels.has(Number(polda.id))) {
currentFileLevels.add(Number(polda.id));
}
// Checklist semua sub-items
polda.subDestination?.forEach((sub: any) => {
currentFileLevels.add(Number(sub.id));
});
}
newArray[fileIndex] = currentFileLevels;
// Update checkbox utama berdasarkan perubahan di modal
setTimeout(() => updateMainCheckboxFromModal(fileIndex), 0);
return newArray;
});
};
const options: Option[] = [
{ id: "all", name: "SEMUA" },
{ id: "5", name: "UMUM" },
{ id: "6", name: "JOURNALIS" },
{ id: "7", name: "POLRI" },
{ id: "8", name: "KSP" },
];
const [selectedTarget, setSelectedTarget] = useState<string | undefined>(
detail?.category.id
);
// const [unitSelection, setUnitSelection] = useState({
// allUnit: false,
// mabes: false,
// polda: false,
// polres: false,
// });
let fileTypeId = "1";
// const { getRootProps, getInputProps } = useDropzone({
// onDrop: (acceptedFiles) => {
// // setFiles(acceptedFiles.map((file) => Object.assign(file)));
// setFiles((prevFiles) => [
// ...prevFiles,
// ...acceptedFiles.map((file) =>
// Object.assign(file, {
// preview: URL.createObjectURL(file),
// })
// ),
// ]);
// },
// accept: {
// "image/*": [],
// },
// });
const { getRootProps, getInputProps } = useDropzone({
onDrop: (acceptedFiles) => {
setFiles((prevFiles) => [
...prevFiles,
...acceptedFiles.map((file) =>
Object.assign(file, {
id: uuidv4(), // generate unique id
preview: URL.createObjectURL(file),
})
),
]);
},
accept: {
"image/*": [],
},
});
const {
control,
handleSubmit,
setValue,
getValues,
formState: { errors },
} = useForm<ImageSchema>({
resolver: zodResolver(imageSchema),
});
useEffect(() => {
async function initState() {
getCategories();
}
initState();
}, []);
const handleAddTag = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === "Enter" && e.currentTarget.value.trim()) {
e.preventDefault();
const newTag = e.currentTarget.value.trim();
if (!tags.includes(newTag)) {
setTags((prevTags) => [...prevTags, newTag]);
if (inputRef.current) {
inputRef.current.value = "";
}
}
}
};
const handleRemoveTag = (index: number) => {
setTags((prevTags) => prevTags.filter((_, i) => i !== index));
};
const handleEditTag = (index: number, newValue: string) => {
setTags((prevTags) =>
prevTags.map((tag, i) => (i === index ? newValue : tag))
);
};
const getCategories = async () => {
try {
const category = await listEnableCategory(fileTypeId);
const resCategory: Category[] = category?.data?.data?.content;
setCategories(resCategory);
console.log("data category", resCategory);
if (scheduleId && scheduleType === "3") {
const findCategory = resCategory.find((o) =>
o.name.toLowerCase().includes("pers rilis")
);
if (findCategory) {
// setValue("categoryId", findCategory.id);
setSelectedCategory(findCategory.id);
const response = await getTagsBySubCategoryId(findCategory.id);
setTags(response?.data?.data);
}
}
} catch (error) {
console.error("Failed to fetch categories:", error);
}
};
useEffect(() => {
async function initState() {
if (id) {
const response = await detailMedia(id);
const details = response?.data?.data;
setDetail(details);
setSelectedTarget(String(details.category.id));
setTempFile(details?.files);
setValue("title", details.title);
setValue("description", details.htmlDescription);
setValue("creatorName", details.creatorName);
setTimeout(() => {
setValue("title", details.title);
setValue("description", details.htmlDescription);
setValue("creatorName", details.creatorName);
}, 500);
if (details?.files) {
const formattedFiles = details.files.map((file: any) => ({
...file,
id: file.id,
fileName: file.fileName,
size: file.size,
thumbnailFileUrl: file.thumbnailFileUrl,
url: file.url,
}));
setFiles(formattedFiles);
// Inisialisasi filePlacements dari detail
const initialFilePlacements = details.files.map((file: any) => {
if (file.placements) {
// Map dari format backend ke format internal
const mappedPlacements = file.placements
.split(",")
.map((p: string) => {
const trimmed = p.trim();
switch (trimmed) {
case "all":
return "all";
case "mabes":
return "nasional";
case "polda":
return "wilayah";
case "satker":
return "satker";
case "international":
return "international";
default:
return trimmed;
}
});
return mappedPlacements;
}
return [];
});
setFilePlacements(initialFilePlacements);
// Inisialisasi fileCheckedLevels dari detail
const initialFileCheckedLevels = details.files.map((file: any) => {
if (file.customLocationPlacements) {
const levelIds = file.customLocationPlacements
.split(",")
.map((id: string) => Number(id.trim()))
.filter((id: number) => !isNaN(id));
return new Set(levelIds);
}
return new Set<number>();
});
setFileCheckedLevels(initialFileCheckedLevels);
// Inisialisasi fileUnitSelections dari detail
const initialFileUnitSelections = details.files.map((file: any) => {
const selection = {
semua: false,
nasional: false,
wilayah: false,
international: false,
polda: false,
satker: false,
};
if (file.placements) {
const placements = file.placements
.split(",")
.map((p: string) => p.trim());
// Map dari format backend ke checkbox
if (placements.includes("all")) {
selection.semua = true;
selection.nasional = true;
selection.wilayah = true;
selection.international = true;
selection.polda = true;
selection.satker = true;
} else {
if (placements.includes("mabes")) {
selection.nasional = true;
}
if (placements.includes("wilayah")) {
selection.wilayah = true;
}
if (placements.includes("polda")) {
selection.polda = true;
selection.wilayah = true; // Auto-check wilayah when polda is present
}
if (placements.includes("satker")) {
selection.satker = true;
selection.wilayah = true; // Auto-check wilayah when satker is present
}
if (placements.includes("international")) {
selection.international = true;
}
}
}
return selection;
});
setFileUnitSelections(initialFileUnitSelections);
}
if (details?.publishedFor) {
// Split string "7" to an array ["7"] if needed
setPublishedFor(details.publishedFor.split(","));
}
if (details?.tags) {
setTags(details.tags.split(",").map((tag: string) => tag.trim()));
}
}
}
initState();
}, [id, setValue]);
const handleCheckboxChange = (id: string) => {
if (id === "all") {
const allOptions = options
.filter((opt) => opt.id !== "all")
.map((opt) => opt.id);
setPublishedFor(
publishedFor.length === allOptions.length ? [] : allOptions
);
} else {
setPublishedFor((prev) =>
prev.includes(id) ? prev.filter((item) => item !== id) : [...prev, id]
);
}
};
const [filePlacements, setFilePlacements] = useState<string[][]>([]);
const getPlacement = () => {
const temp = [];
for (let i = 0; i < filePlacements?.length; i++) {
const file = files[i] as any;
if (
file.id &&
filePlacements[file.id] &&
filePlacements[file.id].length > 0
) {
const now = filePlacements[file.id];
const normalizedNow = now.map((item): PlacementType => {
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
const currentFileCheckedLevels = fileCheckedLevels[file.id]
? Array.from(fileCheckedLevels[file.id])
: [];
const data = {
mediaFileId: file.id,
placements: nowArr,
customLocationPlacements: currentFileCheckedLevels.join(","),
};
temp.push(data);
}
}
return temp;
};
const save = async (data: ImageSchema) => {
loading();
const finalTags = tags.join(", ");
// ✅ tentukan isi description sesuai pilihan bahasa
const descFinal =
selectedLang === "en" && translatedContent
? translatedContent
: data.description;
const requestData = {
...data,
id: detail?.id,
title: data.title,
description: htmlToString(descFinal), // versi plain text
htmlDescription: descFinal, // versi HTML
fileTypeId,
categoryId: selectedTarget,
subCategoryId: selectedTarget,
uploadedBy: "2b7c8d83-d298-4b19-9f74-b07924506b58",
statusId: "1",
publishedFor: publishedFor.join(","),
creatorName: data.creatorName,
tags: finalTags,
isYoutube: false,
isInternationalMedia: false,
};
console.log("Form Data Submitted:", requestData);
console.log("getPlacement(): ", getPlacement());
const response = await createMedia(requestData);
if (response?.error) {
error(response?.message);
return false;
}
const formMedia = new FormData();
const thumbnail = thumbnailFile || files[0];
formMedia.append("file", thumbnail);
const responseThumbnail = await uploadThumbnail(id, formMedia);
if (responseThumbnail?.error) {
error(responseThumbnail?.message);
return false;
}
const progressInfoArr = [];
for (const item of files) {
progressInfoArr.push({ percentage: 0, fileName: item.name });
}
progressInfo = progressInfoArr;
setIsStartUpload(true);
setProgressList(progressInfoArr);
// Update file placements
const responseFilePlacements = await updateFilePlacements(getPlacement());
if (responseFilePlacements?.error) {
error(responseFilePlacements?.message);
return false;
}
close();
// showProgress();
console.log("files:", files);
files.map(async (item: any, index: number) => {
await uploadResumableFile(
index,
String(id),
item,
fileTypeId == "2" || fileTypeId == "4" ? item.duration : "0"
);
});
MySwal.fire({
title: "Sukses",
text: "Data berhasil disimpan.",
icon: "success",
confirmButtonColor: "#3085d6",
confirmButtonText: "OK",
}).then(() => {
router.push("/in/contributor/content/image");
});
};
async function uploadResumableFile(
idx: number,
id: string,
file: any,
duration: string
) {
console.log(idx, id, file, duration);
// const placements = getPlacement(file.placements);
// console.log("Placementttt: : ", placements);
const resCsrf = await getCsrfToken();
const csrfToken = resCsrf?.data?.token;
console.log("CSRF TOKEN : ", csrfToken);
const headers = {
"X-XSRF-TOKEN": csrfToken,
};
if (!file.secondaryUrl || file.secondaryUrl == "") {
const upload = new Upload(file, {
endpoint: `${process.env.NEXT_PUBLIC_API}/media/file/upload`,
headers: headers,
retryDelays: [0, 3000, 6000, 12_000, 24_000],
chunkSize: 20_000,
metadata: {
mediaid: id,
filename: file.name,
filetype: file.type,
duration,
isWatermark: "true",
},
onBeforeRequest: function (req) {
var xhr = req.getUnderlyingObject();
xhr.withCredentials = true;
},
onError: async (e: any) => {
console.log("Error upload :", e);
error(e);
},
onChunkComplete: (
chunkSize: any,
bytesAccepted: any,
bytesTotal: any
) => {
const uploadPersen = Math.floor((bytesAccepted / bytesTotal) * 100);
progressInfo[idx].percentage = uploadPersen;
counterUpdateProgress++;
console.log(counterUpdateProgress);
setProgressList(progressInfo);
setCounterProgress(counterUpdateProgress);
},
onSuccess: async () => {
uploadPersen = 100;
progressInfo[idx].percentage = 100;
counterUpdateProgress++;
setCounterProgress(counterUpdateProgress);
successTodo();
},
});
upload.start();
}
}
const onSubmit = (data: ImageSchema) => {
MySwal.fire({
title: "Simpan Data",
text: "Apakah Anda yakin ingin menyimpan data ini?",
icon: "warning",
showCancelButton: true,
cancelButtonColor: "#d33",
confirmButtonColor: "#3085d6",
confirmButtonText: "Simpan",
}).then((result) => {
if (result.isConfirmed) {
save(data);
}
});
};
const successSubmit = (redirect: string) => {
MySwal.fire({
title: "Sukses",
text: "Data berhasil disimpan.",
icon: "success",
confirmButtonColor: "#3085d6",
confirmButtonText: "OK",
}).then(() => {
router.push(redirect);
});
};
function successTodo() {
let counter = 0;
for (const element of progressInfo) {
if (element.percentage == 100) {
counter++;
}
}
if (counter == progressInfo.length) {
setIsStartUpload(false);
// hideProgress();
Cookies.remove("idCreate");
successSubmit("/in/contributor/content/image/");
}
}
const handleRemoveAllFiles = () => {
setFiles([]);
};
const renderFilePreview = (file: FileWithPreview | any) => {
if (file?.preview || file instanceof File) {
return (
<Image
width={48}
height={48}
alt={file.name}
src={file.preview || URL.createObjectURL(file)}
className="rounded border p-0.5"
/>
);
} else if (file.thumbnailFileUrl) {
return (
<Image
width={48}
height={48}
alt={file.fileName}
src={file.thumbnailFileUrl}
className="rounded border p-0.5"
/>
);
} else {
return <Icon icon="tabler:file-description" />;
}
};
const handleRemoveFile = (file: FileWithPreview) => {
const uploadedFiles = files;
const filtered = uploadedFiles.filter((i) => i.name !== file.name);
setFiles([...filtered]);
};
const fileList = files.map((file: any) => (
<div
key={file.id || file.name}
className="flex justify-between border px-3.5 py-3 my-6 rounded-md"
>
<div className="flex gap-3 items-center">
<div className="file-preview">{renderFilePreview(file)}</div>
<div>
<div className="text-sm text-card-foreground">
{file.fileName || file.name}
</div>
<div className="text-xs font-light text-muted-foreground">
{Math.round(file.size / 100) / 10 > 1000 ? (
<>{(Math.round(file.size / 100) / 10000).toFixed(1)}</>
) : (
<>{(Math.round(file.size / 100) / 10).toFixed(1)}</>
)}
{" kb"}
</div>
</div>
</div>
<Button
type="button"
size="icon"
color="destructive"
variant="outline"
className="border-none rounded-full"
onClick={() => handleDeleteFile(file.id)}
>
<Icon icon="tabler:x" className="h-5 w-5" />
</Button>
</div>
));
type PlacementType = "all" | "mabes" | "polda" | "international" | string;
interface FilePlacement {
mediaFileId: number;
placements?: PlacementType[];
}
interface TempFileItem {
id: number | string;
// tambahkan properti lain kalau ada
}
const [tempFile, setTempFile] = useState<TempFileItem[]>([]);
const setupPlacement = (
index: number,
placement: string,
checked: boolean
) => {
let temp = [...filePlacements];
if (checked) {
if (placement === "all") {
temp[index] = ["all", "mabes", "polda", "international"];
setFileCheckedLevels((prevLevels) => {
const newArray = [...prevLevels];
const currentFileLevels = new Set<number>(
newArray[index] || new Set()
);
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;
});
setFileUnitSelections((prevSelections) => {
const newSelections = [...prevSelections];
const currentSelection = { ...newSelections[index] };
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") {
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);
}
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] = [];
setFileCheckedLevels((prevLevels) => {
const newArray = [...prevLevels];
const currentFileLevels = new Set<number>(
newArray[index] || new Set()
);
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;
if (placement === "wilayah") {
// Ketika wilayah di-uncheck, hapus wilayah, polda, dan satker
const now = temp[index]?.filter(
(a) => a !== "wilayah" && a !== "polda" && a !== "satker"
);
temp[index] = now;
} else if (placement === "polda") {
// Ketika polda di-uncheck, hapus polda dari filePlacements
const now = temp[index]?.filter((a) => a !== "polda");
temp[index] = now;
} else if (placement === "satker") {
// Ketika satker di-uncheck, hapus satker dari filePlacements
const now = temp[index]?.filter((a) => a !== "satker");
temp[index] = now;
} else {
const now = temp[index]?.filter((a) => a !== placement);
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);
};
// Fungsi untuk mengupdate checklist levels di modal berdasarkan placement
const updateModalChecklistLevels = (
fileIndex: number,
placement: string,
checked: boolean
) => {
if (!listDest || listDest.length === 0) return;
setFileCheckedLevels((prev) => {
const newArray = [...prev];
const currentFileLevels = new Set<number>(newArray[fileIndex]);
if (checked) {
if (placement === "polda") {
// Checklist semua POLDA (bukan SATKER POLRI)
listDest.forEach((item: any) => {
if (item.levelNumber === 2 && item.name !== "SATKER POLRI") {
currentFileLevels.add(Number(item.id));
}
});
} else if (placement === "satker") {
// Checklist SATKER POLRI dan semua sub-item di bawahnya
const satkerItem: any = listDest.find(
(item: any) => item.name === "SATKER POLRI"
);
if (satkerItem) {
currentFileLevels.add(Number(satkerItem.id));
if (satkerItem.subDestination) {
satkerItem.subDestination.forEach((sub: any) => {
currentFileLevels.add(Number(sub.id));
});
}
}
}
} else {
if (placement === "polda") {
// Unchecklist semua POLDA
listDest.forEach((item: any) => {
if (item.levelNumber === 2 && item.name !== "SATKER POLRI") {
currentFileLevels.delete(Number(item.id));
}
});
} else if (placement === "satker") {
// Unchecklist SATKER POLRI dan semua sub-item di bawahnya
const satkerItem: any = listDest.find(
(item: any) => item.name === "SATKER POLRI"
);
if (satkerItem) {
currentFileLevels.delete(Number(satkerItem.id));
if (satkerItem.subDestination) {
satkerItem.subDestination.forEach((sub: any) => {
currentFileLevels.delete(Number(sub.id));
});
}
}
}
}
newArray[fileIndex] = currentFileLevels;
return newArray;
});
};
// const setupPlacement = (id: number | string, placement: PlacementType) => {
// console.log(`FileDestination.leng:: ${id}_${placement}`);
// const arrayFile: FilePlacement[] = [];
// for (let i = 0; i < tempFile?.length; i++) {
// const element = tempFile[i];
// if (element.id == id) {
// const findPlacementIdx = filePlacements.findIndex(
// (o) => Number(o.mediaFileId) === Number(id)
// );
// if (findPlacementIdx > -1) {
// const findPlacement = filePlacements[findPlacementIdx];
// if (findPlacement?.placements?.includes(placement)) {
// if (placement === "all") {
// findPlacement.placements = undefined;
// } else {
// findPlacement.placements = findPlacement.placements.filter(
// (val) => val !== placement
// );
// if (findPlacement.placements?.includes("all")) {
// findPlacement.placements = findPlacement.placements.filter(
// (val) => val !== "all"
// );
// }
// }
// } else if (placement === "all") {
// findPlacement.placements = [
// "all",
// "mabes",
// "polda",
// "international",
// ];
// } else if (findPlacement.placements) {
// findPlacement.placements = [...findPlacement.placements, placement];
// } else {
// findPlacement.placements = [placement];
// }
// } else {
// const file: FilePlacement = {
// mediaFileId: Number(element.id),
// placements: [placement],
// };
// arrayFile.push(file);
// }
// }
// }
// const finalPlacements = [...filePlacements, ...arrayFile];
// setFilePlacements(finalPlacements);
// console.log("FileDestination.leng::", finalPlacements);
// };
function success() {
MySwal.fire({
title: "Sukses",
icon: "success",
confirmButtonColor: "#3085d6",
confirmButtonText: "OK",
}).then((result) => {
if (result.isConfirmed) {
// window.location.reload();
}
});
}
const handleDeleteFile = (id: number) => {
MySwal.fire({
title: "Hapus file",
text: "Apakah Anda yakin ingin menghapus file ini?",
icon: "warning",
showCancelButton: true,
cancelButtonColor: "#3085d6",
confirmButtonColor: "#d33",
confirmButtonText: "Hapus",
}).then((result) => {
if (result.isConfirmed) {
doDelete(id);
}
});
};
async function doDelete(id: number) {
const data = { id };
try {
const response = await deleteFile(data);
if (response?.error) {
error(response.message);
return;
}
setFiles((prevFiles: any) =>
prevFiles.filter((file: any) => file.id !== id)
);
success();
} catch (err) {
error("Terjadi kesalahan saat menghapus file");
}
}
return (
<form onSubmit={handleSubmit(onSubmit)}>
{detail !== undefined ? (
<div className="flex flex-col lg:flex-row gap-10">
<Card className="w-full lg:w-8/12">
<div className="px-6 py-6">
<p className="text-lg font-semibold mb-3">
{t("form-image", { defaultValue: "Form Image" })}
</p>
<div className="gap-5 mb-5">
{/* Input Title */}
<div className="space-y-2 py-3">
<Label>{t("title", { defaultValue: "Title" })}</Label>
<Controller
control={control}
name="title"
render={({ field }) => (
<Input
size="md"
type="text"
value={field?.value}
onChange={field.onChange}
placeholder="Enter Title"
/>
)}
/>
{errors.title?.message && (
<p className="text-red-400 text-sm">
{errors.title.message}
</p>
)}
</div>
<div className="flex items-center">
<div className="py-3 w-full space-y-2">
<Label>{t("category", { defaultValue: "Category" })}</Label>
<Select
value={selectedTarget}
onValueChange={(id) => {
console.log("Selected Category ID:", id);
setSelectedTarget(id);
}}
>
<SelectTrigger size="md">
<SelectValue placeholder="Pilih" />
</SelectTrigger>
<SelectContent>
{/* Show the category from details if it doesn't exist in categories list */}
{detail &&
!categories.find(
(cat) =>
String(cat.id) === String(detail.category.id)
) && (
<SelectItem
key={String(detail.category.id)}
value={String(detail.category.id)}
>
{detail.category.name}
</SelectItem>
)}
{categories.map((category) => (
<SelectItem
key={String(category.id)}
value={String(category.id)}
>
{category.name}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
</div>
<div className="py-3 space-y-2">
<div className="flex justify-between items-center">
<Label>
{t("description", { defaultValue: "Description" })}
</Label>
{roleId === "14" && (
<button
type="button"
onClick={async () => {
try {
loading();
setIsLoadingTranslate(true);
const res = await translateText({
text: getValues("description"),
sourceLang: "ID",
targetLang: "EN",
});
if (!res.error) {
const resultText =
res?.data?.data?.translations?.[0]?.text || "";
setTranslatedContent(resultText);
}
} catch (err) {
close();
console.error("Translate gagal:", err);
} finally {
close();
setIsLoadingTranslate(false);
}
}}
className="px-3 py-2 bg-blue-500 text-white rounded-md hover:bg-blue-600"
>
{isLoadingTranslate
? "Translating..."
: "Translate to English"}
</button>
)}
</div>
{/* Pilihan bahasa untuk posting */}
{roleId === "14" && (
<div className="flex items-center gap-4 mb-2">
<label className="flex items-center gap-2">
<input
type="radio"
value="id"
checked={selectedLang === "id"}
onChange={() => setSelectedLang("id")}
/>
<span>Gunakan Bahasa Indonesia</span>
</label>
</div>
)}
{/* Editor Bahasa Indonesia */}
<Controller
control={control}
name="description"
render={({ field }) => (
<CustomEditor
onChange={field.onChange}
initialData={field.value}
/>
)}
/>
{/* Editor Bahasa Inggris */}
{translatedContent && (
<div className="mt-4">
<div className="flex flex-col">
<Label className="text-[15px]">English Version</Label>
<label className="flex items-center gap-2">
<input
type="radio"
value="en"
checked={selectedLang === "en"}
onChange={() => setSelectedLang("en")}
disabled={!translatedContent}
/>
<span>Gunakan Bahasa Inggris</span>
</label>
</div>
<CustomEditor
onChange={(val: any) => setTranslatedContent(val)}
initialData={translatedContent}
/>
</div>
)}
{errors.description?.message && (
<p className="text-red-400 text-sm">
{errors.description.message}
</p>
)}
</div>
{/* <div className="py-3 space-y-2">
<Label>
{t("description", { defaultValue: "Description" })}
</Label>
<Controller
control={control}
name="description"
render={({ field }) => (
<CustomEditor
onChange={field.onChange}
initialData={field.value}
/>
)}
/>
{errors.description?.message && (
<p className="text-red-400 text-sm">
{errors.description.message}
</p>
)}
</div> */}
<div className="py-3 space-y-2">
<Label>
{t("select-file", { defaultValue: "Select File" })}
</Label>
{/* <Input
id="fileInput"
type="file"
onChange={handleImageChange}
/> */}
<Fragment>
<div {...getRootProps({ className: "dropzone" })}>
<input {...getInputProps()} />
<div className=" w-full text-center border-dashed border border-default-200 dark:border-default-300 rounded-md py-[52px] flex items-center flex-col">
<CloudUpload className="text-default-300 w-10 h-10" />
<h4 className=" text-2xl font-medium mb-1 mt-3 text-card-foreground/80">
{/* Drop files here or click to upload. */}
{t("drag-file", { defaultValue: "Drag File" })}
</h4>
<div className=" text-xs text-muted-foreground">
{t("upload-file-max", {
defaultValue: "Upload File Max",
})}
</div>
</div>
</div>
{files.length ? (
<Fragment>
<div>{fileList}</div>
<div className=" flex justify-between gap-2">
<div className="flex flex-row items-center gap-3 py-3">
<Label>
{t("watermark", { defaultValue: "Watermark" })}
</Label>
<div className="flex items-center gap-3">
<Switch defaultChecked color="primary" id="c2" />
</div>
</div>
{/* <Button
color="destructive"
onClick={() => handleDeleteFile(id)}
>
Remove file
</Button> */}
</div>
</Fragment>
) : null}
{files.length > 0 && (
<div className="mt-4">
<Label className="text-md font-semibold">
{" "}
{t("file-media", { defaultValue: "File Media" })}
</Label>
<div className="grid gap-4">
{files.map((file: any, index: any) => (
<div
key={file.id}
className="flex items-center border p-2 rounded-md"
>
<img
src={file.thumbnailFileUrl}
alt={file.fileName}
className="w-16 h-16 object-cover rounded-md mr-4"
/>
<div className="flex flex-wrap gap-3 items-center ">
<div className="flex-grow">
<p className="font-medium">{file.fileName}</p>
<a
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">
{/* <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 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>
{/* 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
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 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>
)}
</Fragment>
</div>
</div>
</div>
</Card>
<div className="w-full lg:w-4/12">
<Card className="h-[900px] md:h-[1100px] lg:h-fit">
<div className="px-3 py-3">
<div className="space-y-2">
<Label>{t("creator", { defaultValue: "Creator" })}</Label>
<Controller
control={control}
name="creatorName"
render={({ field }) => (
<Input
size="md"
type="text"
value={field?.value}
onChange={field.onChange}
placeholder="Enter Title"
/>
)}
/>
{errors.creatorName?.message && (
<p className="text-red-400 text-sm">
{errors.creatorName.message}
</p>
)}
</div>
</div>
{/* <div className="mt-3 px-3 space-y-2">
<Label>{t("preview", { defaultValue: "Preview" })}</Label>
<Card className="mt-2">
<img
src={detail.thumbnailLink}
alt="Thumbnail Gambar Utama"
className="w-full h-auto rounded"
/>
</Card>
</div> */}
<div className="mt-3 px-3 space-y-2">
<Label>{t("preview", { defaultValue: "Preview" })}</Label>
<Input
type="file"
accept="image/*"
onChange={(e) => {
const file = e.target.files?.[0];
if (file) {
setThumbnailFile(file);
}
}}
className="dark:border dark:border-gray-500 dark:rounded-lg"
/>
<Card className="mt-2">
<img
src={
thumbnailFile
? URL.createObjectURL(thumbnailFile)
: detail?.thumbnailLink
? `${detail.thumbnailLink}?v=${Date.now()}`
: ""
}
alt="Thumbnail Preview"
className="w-full h-auto rounded"
/>
</Card>
</div>
<div className="px-3 py-3">
<div className="space-y-2">
<Label>{t("tags", { defaultValue: "Tags" })}</Label>
<Input
type="text"
id="tags"
placeholder="Add a tag and press Enter"
onKeyDown={handleAddTag}
ref={inputRef}
/>
<div className="mt-3 flex flex-wrap gap-2">
{tags.map((tag, index) => (
<span
key={index}
className="flex items-center gap-2 px-2 py-1 rounded-lg bg-black text-white text-sm"
>
{/* Hidden span untuk ukur lebar teks */}
<span
className="absolute opacity-0 whitespace-pre"
id={`tag-span-${index}`}
>
{tag || " "}
</span>
<input
type="text"
value={tag}
onChange={(e) => handleEditTag(index, e.target.value)}
style={{
width: `${Math.max(tag.length, 1)}ch`,
}}
className="bg-black text-white border-none focus:outline-none"
/>
<button
type="button"
onClick={() => handleRemoveTag(index)}
className="remove-tag-button text-white"
>
×
</button>
</span>
))}
</div>
{/* <div className="flex flex-wrap gap-2">
{detail?.tags?.split(",").map((tag, index) => (
<Badge
key={index}
className="border rounded-md px-2 py-2"
>
{tag.trim()}
</Badge>
))}
</div> */}
</div>
</div>
<div className="px-3 py-3">
<div className="flex flex-col gap-3 space-y-2">
<Label>
{t("publish-target", { defaultValue: "Publish Target" })}
</Label>
{options.map((option: Option) => (
<div key={option.id} className="flex gap-2 items-center">
<Checkbox
id={option.id}
checked={
option.id === "all"
? publishedFor.length ===
options.filter((opt) => opt.id !== "all").length
: publishedFor.includes(option.id)
}
onCheckedChange={() => handleCheckboxChange(option.id)}
/>
<Label htmlFor={option.id}>{option.name}</Label>
</div>
))}
</div>
</div>
<div className="px-3 py-3 flex flex-row items-center text-blue-500 gap-2 text-sm">
<MailIcon />
<p className="">
{t("suggestion-box", { defaultValue: "Suggestion Box" })} (0)
</p>
</div>
<div className="px-3 py-3">
<p>{t("information", { defaultValue: "Information" })}:</p>
{/* <p>{detail?.status}</p> */}
</div>
</Card>
<div className="flex flex-row justify-end gap-3">
<div className="mt-4">
<Button type="submit" color="primary">
{t("submit", { defaultValue: "Submit" })}
</Button>
</div>
<div className="mt-4">
<Button type="submit" color="primary" variant="outline">
{t("cancel", { defaultValue: "Cancel" })}
</Button>
</div>
</div>
</div>
</div>
) : (
""
)}
</form>
);
}