kontenhumas-fe/app/[locale]/(admin)/admin/agenda-setting/unit-mapping.tsx

252 lines
8.2 KiB
TypeScript

"use client";
import { Button } from "@/components/ui/button";
import {
Form,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import {
Dialog,
DialogContent,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import { Checkbox } from "@/components/ui/checkbox";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { zodResolver } from "@hookform/resolvers/zod";
import { useForm } from "react-hook-form";
import { z } from "zod";
import { useEffect, useState } from "react";
import { getUserLevelForAssignments } from "@/service/task";
const FormSchema = z.object({
items: z.array(z.string()).refine((value) => value.some((item) => item), {
message: "Required",
}),
});
interface UnitType {
id: number;
name: string;
subDestination: { id: number; name: string }[] | null;
}
export function UnitMapping(props: {
unit: "Polda" | "Satker" | "Polres";
sendDataToParent: (data: string[]) => void;
isDetail: boolean;
initData?: string[];
}) {
const { unit, sendDataToParent, isDetail } = props;
const [unitList, setUnitList] = useState<UnitType[]>([]);
const [satkerList, setSatkerList] = useState<{ id: number; name: string }[]>(
[]
);
const [polresList, setPolresList] = useState<{ id: number; name: string }[]>(
[]
);
const [isOpen, setIsOpen] = useState(false);
const form = useForm<z.infer<typeof FormSchema>>({
resolver: zodResolver(FormSchema),
defaultValues: {
items: props.initData ? props.initData : [],
},
});
useEffect(() => {
async function initState() {
const response = await getUserLevelForAssignments();
const list: UnitType[] = response?.data?.data?.list ?? [];
setupUnit(list);
// console.log("list", list);
}
initState();
}, []);
// Selalu punya array
const unitType = form.watch("items") ?? [];
// ---- FIX: case-insensitive + flatMap semua node ----
const setupUnit = (data: UnitType[] = []) => {
const norm = (v?: string) => (v ?? "").toUpperCase();
const poldaNodes = data.filter((a) => norm(a.name).includes("POLDA"));
const satkerNodes = data.filter((a) => norm(a.name).includes("SATKER"));
// POLDA level (UnitType)
setUnitList(poldaNodes);
// SATKER adalah gabungan semua subDestination dari node SATKER
const allSatker = satkerNodes.flatMap((n) => n.subDestination ?? []) ?? [];
setSatkerList(allSatker);
// POLRES adalah gabungan semua subDestination dari node POLDA
const allPolres = poldaNodes.flatMap((n) => n.subDestination ?? []);
setPolresList(allPolres);
};
// ---- FIX: jangan true saat list kosong ----
const isAllUnitChecked =
unitList.length > 0 &&
unitList.every((item) => unitType.includes(String(item.id)));
const isAllSatkerChecked =
satkerList.length > 0 &&
satkerList.every((item) => unitType.includes(String(item.id)));
const isAllPolresChecked =
polresList.length > 0 &&
polresList.every((item) => unitType.includes(String(item.id)));
useEffect(() => {
sendDataToParent(form.getValues("items"));
}, [unitType]);
// helper untuk ambil id-ids per kategori aktif
const currentIds = () => {
if (unit === "Polda") return unitList.map((i) => String(i.id));
if (unit === "Satker") return satkerList.map((i) => String(i.id));
return polresList.map((i) => String(i.id));
};
const allCheckedByUnit =
unit === "Polda"
? isAllUnitChecked
: unit === "Satker"
? isAllSatkerChecked
: isAllPolresChecked;
return (
<Dialog open={isOpen} onOpenChange={setIsOpen}>
<DialogTrigger asChild>
<a
onClick={() => setIsOpen(true)}
className="text-primary cursor-pointer text-xs mr-3"
>
Pilih {unit}
</a>
</DialogTrigger>
<DialogContent size="md" className="h-[500px] overflow-y-auto">
<DialogHeader>
<DialogTitle>{unit}</DialogTitle>
</DialogHeader>
<Form {...form}>
<form className="flex flex-col gap-2">
<div className="flex items-center gap-3">
<Checkbox
id={`all-${unit}`}
checked={allCheckedByUnit}
onCheckedChange={(checked) => {
const ids = currentIds();
if (checked) {
// gabungkan supaya kategori lain tidak hilang
const merged = Array.from(new Set([...unitType, ...ids]));
form.setValue("items", merged, {
shouldDirty: true,
shouldTouch: true,
shouldValidate: true,
});
} else {
// hapus hanya id kategori aktif
const filtered = unitType.filter((v) => !ids.includes(v));
form.setValue("items", filtered, {
shouldDirty: true,
shouldTouch: true,
shouldValidate: true,
});
}
}}
/>
<label
htmlFor={`all-${unit}`}
className="text-sm text-black uppercase"
>
SEMUA {unit}
</label>
</div>
<FormField
control={form.control}
name="items"
render={() => (
<FormItem
className={`grid ${
unit === "Polda"
? "grid-cols-2"
: unit === "Satker"
? "grid-cols-3"
: "grid-cols-4"
} gap-2`}
>
{(unit === "Polda"
? unitList
: unit === "Satker"
? satkerList
: polresList
)?.map((item: any) => (
<FormField
key={item.id}
control={form.control}
name="items"
render={({ field }) => (
<FormItem className="flex flex-row items-center space-x-3 space-y-0">
<FormControl>
<Checkbox
checked={field.value?.includes(String(item.id))}
onCheckedChange={(checked) => {
if (checked) {
form.setValue(
"items",
Array.from(
new Set([
...(field.value ?? []),
String(item.id),
])
),
{
shouldDirty: true,
shouldTouch: true,
shouldValidate: true,
}
);
} else {
form.setValue(
"items",
(field.value ?? []).filter(
(v: string) => v !== String(item.id)
),
{
shouldDirty: true,
shouldTouch: true,
shouldValidate: true,
}
);
}
}}
/>
</FormControl>
<p className="text-sm text-black">{item.name}</p>
</FormItem>
)}
/>
))}
<FormMessage />
</FormItem>
)}
/>
</form>
</Form>
</DialogContent>
</Dialog>
);
}