TTMT.ManageWebGUI/src/routes/_auth/role/create/index.tsx

274 lines
9.6 KiB
TypeScript

import { createFileRoute, useNavigate } from "@tanstack/react-router";
import { useGetPermissionList, useCreateRole } from "@/hooks/queries";
import { useState, useMemo } from "react";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Checkbox } from "@/components/ui/checkbox";
import { ScrollArea } from "@/components/ui/scroll-area";
import { Shield, ArrowLeft, Save } from "lucide-react";
import { toast } from "sonner";
import type { Permission } from "@/types/permission";
export const Route = createFileRoute("/_auth/role/create/")({
component: CreateRoleComponent,
loader: async ({ context }) => {
context.breadcrumbs = [
{ title: "Quản lý role", path: "/role" },
{ title: "Tạo role mới", path: "/role/create" },
];
},
});
function CreateRoleComponent() {
const navigate = useNavigate();
const { data: permissions = [], isLoading: permissionsLoading } = useGetPermissionList();
const createMutation = useCreateRole();
const [roleName, setRoleName] = useState("");
const [priority, setPriority] = useState(0);
const [selectedPermissions, setSelectedPermissions] = useState<number[]>([]);
// Helper to get unique identifier for permission
const getPermValue = (perm: Permission) => perm.value ?? perm.id ?? 0;
// Group permissions by parent (category)
const groupedPermissions = useMemo(() => {
const groups: Record<string, Permission[]> = {};
const permissionList = Array.isArray(permissions) ? permissions : [];
// First pass: identify all parent categories
const parentPermissions: Permission[] = [];
const childPermissions: Permission[] = [];
permissionList.forEach((perm: Permission) => {
const permValue = perm.value ?? perm.enum ?? 0;
const isParent = permValue % 10 === 0;
if (isParent) {
parentPermissions.push(perm);
groups[perm.name] = [];
} else {
childPermissions.push(perm);
}
});
// Second pass: assign children to parent categories
childPermissions.forEach((perm: Permission) => {
const permValue = perm.value ?? perm.enum ?? 0;
const parentEnum = Math.floor(permValue / 10) * 10;
const parent = parentPermissions.find((p: Permission) => (p.value ?? p.enum) === parentEnum);
const parentName = parent?.name || "Khác";
if (!groups[parentName]) {
groups[parentName] = [];
}
groups[parentName].push(perm);
});
// Third pass: add parent permissions that have no children as selectable items
// (like ALLOW_ALL which is value 0 with no children)
parentPermissions.forEach((parent) => {
const parentValue = parent.value ?? parent.enum ?? 0;
// Check if this parent has any children
const hasChildren = childPermissions.some((child) => {
const childValue = child.value ?? child.enum ?? 0;
return Math.floor(childValue / 10) * 10 === parentValue;
});
// If no children, add the parent itself as a selectable item
if (!hasChildren) {
groups[parent.name].push(parent);
}
});
// Remove empty groups
Object.keys(groups).forEach((key) => {
if (groups[key].length === 0) {
delete groups[key];
}
});
return groups;
}, [permissions]);
const handleTogglePermission = (permissionValue: number) => {
setSelectedPermissions((prev) =>
prev.includes(permissionValue)
? prev.filter((v) => v !== permissionValue)
: [...prev, permissionValue]
);
};
const handleSelectAll = (categoryPermissions: Permission[]) => {
const allValues = categoryPermissions.map((p) => getPermValue(p));
const allSelected = allValues.every((v) => selectedPermissions.includes(v));
if (allSelected) {
setSelectedPermissions((prev) =>
prev.filter((v) => !allValues.includes(v))
);
} else {
setSelectedPermissions((prev) => [...new Set([...prev, ...allValues])]);
}
};
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!roleName.trim()) {
toast.error("Vui lòng nhập tên role!");
return;
}
try {
await createMutation.mutateAsync({
RoleName: roleName,
Priority: priority,
PermissionIds: selectedPermissions,
});
toast.success("Tạo role thành công!");
navigate({ to: "/role" });
} catch (error) {
toast.error("Tạo role thất bại!");
}
};
return (
<div className="w-full px-6 space-y-4">
{/* Header */}
<div className="flex items-center justify-between">
<div>
<h1 className="text-3xl font-bold">Tạo Role mới</h1>
<p className="text-muted-foreground mt-2">
Tạo vai trò mới gán quyền hạn
</p>
</div>
<Button variant="outline" onClick={() => navigate({ to: "/role" })}>
<ArrowLeft className="h-4 w-4 mr-2" />
Quay lại
</Button>
</div>
<form onSubmit={handleSubmit} className="space-y-6">
{/* Role Info */}
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Shield className="h-5 w-5" /> Thông tin Role
</CardTitle>
<CardDescription>
Nhập thông tin bản của role
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="grid grid-cols-2 gap-4">
<div className="space-y-2">
<Label htmlFor="roleName">Tên Role *</Label>
<Input
id="roleName"
value={roleName}
onChange={(e) => setRoleName(e.target.value)}
placeholder="Nhập tên role..."
required
/>
</div>
<div className="space-y-2">
<Label htmlFor="priority">Đ ưu tiên</Label>
<Input
id="priority"
type="number"
value={priority}
onChange={(e) => setPriority(Number(e.target.value))}
placeholder="0"
/>
</div>
</div>
</CardContent>
</Card>
{/* Permissions Selection */}
<Card>
<CardHeader>
<CardTitle>Chọn quyền hạn</CardTitle>
<CardDescription>
Chọn các quyền role này đưc phép thực hiện ({selectedPermissions.length} đã chọn)
</CardDescription>
</CardHeader>
<CardContent>
{permissionsLoading ? (
<div className="text-center py-4">Đang tải danh sách quyền...</div>
) : (
<ScrollArea className="h-[400px] pr-4">
<div className="space-y-6">
{Object.entries(groupedPermissions).map(([category, perms]) => (
<div key={category} className="space-y-2">
<div className="flex items-center justify-between">
<h4 className="font-semibold text-sm text-muted-foreground uppercase">
{category}
</h4>
<Button
type="button"
variant="ghost"
size="sm"
onClick={() => handleSelectAll(perms)}
>
{perms.every((p) => selectedPermissions.includes(getPermValue(p)))
? "Bỏ tất cả"
: "Chọn tất cả"}
</Button>
</div>
<div className="grid grid-cols-2 md:grid-cols-3 gap-2">
{perms.map((perm) => {
const permValue = getPermValue(perm);
return (
<div
key={permValue}
className="flex items-center space-x-2 p-2 rounded border hover:bg-muted/50"
>
<Checkbox
id={`perm-${permValue}`}
checked={selectedPermissions.includes(permValue)}
onCheckedChange={() => handleTogglePermission(permValue)}
/>
<Label
htmlFor={`perm-${permValue}`}
className="text-sm cursor-pointer flex-1"
>
{perm.name}
</Label>
</div>
);
})}
</div>
</div>
))}
</div>
</ScrollArea>
)}
</CardContent>
</Card>
{/* Submit */}
<div className="flex justify-end gap-2">
<Button
type="button"
variant="outline"
onClick={() => navigate({ to: "/role" })}
>
Hủy
</Button>
<Button type="submit" disabled={createMutation.isPending}>
<Save className="h-4 w-4 mr-2" />
{createMutation.isPending ? "Đang tạo..." : "Tạo Role"}
</Button>
</div>
</form>
</div>
);
}