diff --git a/src/components/PermissionTree.tsx b/src/components/PermissionTree.tsx index f73222c..d1147a7 100644 --- a/src/components/PermissionTree.tsx +++ b/src/components/PermissionTree.tsx @@ -16,112 +16,148 @@ export default function PermissionTree({ selected: string[]; onChange: (val: string[]) => void; }) { - const [open, setOpen] = useState>({}); + // Ambil semua child dari node + const [openNodes, setOpenNodes] = useState>({}); - const toggle = (key: string) => { - setOpen((prev) => ({ ...prev, [key]: !prev[key] })); - }; + function toggleNode(label: string) { + setOpenNodes(prev => ({ ...prev, [label]: !prev[label] })); + } - - - // Ambil semua key dari node termasuk semua keturunannya - const collectKeys = (n: Node): string[] => { - if (!n.children) return [n.key]; - return [n.key, ...n.children.flatMap(collectKeys)]; - }; - - const checkState = (node: Node): { all: boolean; some: boolean } => { - const children = node.children || []; - // Jika tidak ada anak → nilai hanya berdasarkan dirinya sendiri - if (children.length === 0) { - const checked = selected.includes(node.key); - return { all: checked, some: checked }; + function getAllChildKeys(node: Node): string[] { + let result: string[] = []; + if (node.children) { + node.children.forEach((c) => { + result.push(c.key); + result = [...result, ...getAllChildKeys(c)]; + }); } - // Rekursif ke anak - let all = selected.includes(node.key); - let some = selected.includes(node.key); - for (const c of children) { - const childState = checkState(c); - if (!childState.all) all = false; - if (childState.some) some = true; + return result; + } + + // Dapatkan parentKey, jika ada + function getParentKey(key: string) { + const split = key.split("."); + if (split.length <= 1) return null; + split.pop(); + return split.join("."); + } + + + // Update parent ke atas secara rekursif + function updateParent(next: string[], parentKey: string | null): string[] { + if (!parentKey) return next; + + const allChildKeys = findAllChildKeysFromKey(parentKey); + + const selectedChild = allChildKeys.filter((c) => next.includes(c)); + + if (selectedChild.length === 0) { + // Semua child uncheck → parent uncheck + next = next.filter((x) => x !== parentKey); + } else if (selectedChild.length === allChildKeys.length) { + // Semua child check → parent check + if (!next.includes(parentKey)) { + next.push(parentKey); + } + } else { + // Sebagian child check → parent intermediate (checked = true, rendered sebagai indeterminate) + if (!next.includes(parentKey)) { + next.push(parentKey); + } } - return { all, some }; - }; - // Untuk ordering sesuai urutan JSON - const getOrderedKeys = (nodes: Node[]): string[] => - nodes.flatMap((n) => [n.key, ...getOrderedKeys(n.children || [])]); + // Rekursif naik ke atas + return updateParent(next, getParentKey(parentKey)); + } + // dapatkan child dari string key + function findAllChildKeysFromKey(parentKey: string) { + const list: string[] = []; - const RenderNode = ({ node }: { node: Node }) => { - const children = node.children || []; + function traverse(nodes: Node[]) { + nodes.forEach((n) => { + if (n.key.startsWith(parentKey + ".") && n.key !== parentKey) { + list.push(n.key); + } + if (n.children) traverse(n.children); + }); + } - const state = checkState(node); // ← gunakan recursive evaluator + traverse(permissionConfig.menus); + return list; + } - const isChecked = state.all; - const isIndeterminate = !state.all && state.some; + const RenderMenu = ({ menu }: { menu: Node }) => { + const hasChild = menu.children && menu.children.length > 0; + const open = openNodes[menu.label] ?? false; + const childKeys = getAllChildKeys(menu); + const isChecked = selected.includes(menu.key); + const isIndeterminate = + !isChecked && + selected.some( + (x) => + typeof x === "string" && + x.startsWith(menu.key + ".") + ); - const showChildren = open[node.key] ?? false; + function handleCheck() { + let next = [...selected]; - // Ambil semua key anak + parent - const collectKeys = (n: Node): string[] => { - if (!n.children) return [n.key]; - return [n.key, ...n.children.flatMap(collectKeys)]; - }; + if (childKeys.length > 0) { + // klik parent + if (!isChecked) { + next = [...new Set([...next, menu.key, ...childKeys])]; + } else { + next = next.filter((x) => x !== menu.key && !childKeys.includes(x)); + } - const allKeys = collectKeys(node); - - const toggleCheck = (checked: boolean) => { - let updated = new Set(selected); - - if (checked) { - // parent + semua child - allKeys.forEach((k) => updated.add(k)); - } else { - // hilangkan parent + semua child - allKeys.forEach((k) => updated.delete(k)); + next = updateParent(next, getParentKey(menu.key)); + onChange(next); + return; } - // ⬇⬇⬇ PERBAIKAN PENTING ⬇⬇⬇ - // - // Jika node indeterminate → parent harus tetap ada di selected - // - if (isIndeterminate) { - updated.add(node.key); - } - - // Jika semua child tercentang → parent harus checked + // klik child if (isChecked) { - updated.add(node.key); + next = next.filter((x) => x !== menu.key); + } else { + next.push(menu.key); } - onChange([...updated]); - }; + next = updateParent(next, getParentKey(menu.key)); + onChange(next); + } return ( - - - {children.length > 0 ? ( - toggle(node.key)}> - {showChildren ? : } + + + {menu.children && menu.children.length > 0 ? ( + toggleNode(menu.label)} + > + {openNodes[menu.label] ? ( + + ) : ( + + )} ) : ( -
+
)} toggleCheck(e.target.checked)} + onChange={handleCheck} /> - {children.length > 0 && ( - + {menu.children && ( + - {children.map((c) => ( - + {menu.children.map((child) => ( + ))} @@ -130,14 +166,11 @@ export default function PermissionTree({ ); }; - - return ( Hak Akses - {permissionConfig.menus.map((menu: Node) => ( - + ))} ); diff --git a/src/components/UserRoleSetting.tsx b/src/components/UserRoleSetting.tsx index b565285..f4a7020 100644 --- a/src/components/UserRoleSetting.tsx +++ b/src/components/UserRoleSetting.tsx @@ -72,13 +72,13 @@ export default function UserRoleSetting({ permissions }: { permissions: JsonValu }); notification({ title: "Success", - message: "Your user have been saved", + message: "Your role have been saved", type: "success", }); } else { notification({ title: "Error", - message: "Failed to create user ", + message: "Failed to create role", type: "error", }); } @@ -86,7 +86,7 @@ export default function UserRoleSetting({ permissions }: { permissions: JsonValu console.error(error); notification({ title: "Error", - message: "Failed to create user", + message: "Failed to create role", type: "error", }); } finally { @@ -97,19 +97,19 @@ export default function UserRoleSetting({ permissions }: { permissions: JsonValu async function handleEdit() { try { setBtnLoading(true); - const res = await apiFetch.api.pengaduan.category.update.post(dataEdit); + const res = await apiFetch.api.user["role-update"].post(dataEdit as any); if (res.status === 200) { mutate(); close(); notification({ title: "Success", - message: "Your category have been saved", + message: "Your role have been saved", type: "success", }); } else { notification({ title: "Error", - message: "Failed to edit category", + message: "Failed to edit role", type: "error", }); } @@ -117,7 +117,7 @@ export default function UserRoleSetting({ permissions }: { permissions: JsonValu console.error(error); notification({ title: "Error", - message: "Failed to edit category", + message: "Failed to edit role", type: "error", }); } finally { @@ -156,16 +156,10 @@ export default function UserRoleSetting({ permissions }: { permissions: JsonValu } } - function chooseEdit({ - data, - }: { - data: { - id: string; - name: string; - permissions: []; - }; - }) { - setDataEdit(data); + function chooseEdit({ data }: { data: { id: string; name: string; permissions: []; }; }) { + setDataEdit({ + id: data.id, name: data.name, permissions: data.permissions ? data.permissions : [] + }); open(); } @@ -185,7 +179,6 @@ export default function UserRoleSetting({ permissions }: { permissions: JsonValu } } - console.log("dataTambah", dataTambah); useShallowEffect(() => { if (dataEdit.name.length > 0) { @@ -200,8 +193,8 @@ export default function UserRoleSetting({ permissions }: { permissions: JsonValu opened={opened} onClose={close} title={"Edit"} - centered overlayProps={{ backgroundOpacity: 0.55, blur: 3 }} + size={"lg"} > @@ -216,6 +209,12 @@ export default function UserRoleSetting({ permissions }: { permissions: JsonValu } /> + { + setDataEdit({ ...dataEdit, permissions: permissions as never[] }); + }} + />