forked from baron/baron-sso
feat: 구현: 유저 그룹 중심 권한 통합 및 미들웨어 정책 고도화
This commit is contained in:
@@ -0,0 +1,205 @@
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { Plus, Trash2, Users } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
import { Link, useParams } from "react-router-dom";
|
||||
import { Button } from "../../../components/ui/button";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "../../../components/ui/card";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "../../../components/ui/dialog";
|
||||
import { Input } from "../../../components/ui/input";
|
||||
import { Label } from "../../../components/ui/label";
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "../../../components/ui/table";
|
||||
import { createGroup, deleteGroup, fetchGroups } from "../../../lib/adminApi";
|
||||
|
||||
export function TenantUserGroupsTab() {
|
||||
const { tenantId } = useParams<{ tenantId: string }>();
|
||||
const queryClient = useQueryClient();
|
||||
const [isCreateOpen, setIsCreateOpen] = useState(false);
|
||||
const [newGroupName, setNewGroupName] = useState("");
|
||||
const [newGroupDesc, setNewGroupDesc] = useState("");
|
||||
|
||||
const { data: groups, isLoading } = useQuery({
|
||||
queryKey: ["tenant-user-groups", tenantId],
|
||||
queryFn: () => fetchGroups(tenantId!),
|
||||
enabled: !!tenantId,
|
||||
});
|
||||
|
||||
const createMutation = useMutation({
|
||||
mutationFn: () =>
|
||||
createGroup(tenantId!, { name: newGroupName, description: newGroupDesc }),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: ["tenant-user-groups", tenantId],
|
||||
});
|
||||
setIsCreateOpen(false);
|
||||
setNewGroupName("");
|
||||
setNewGroupDesc("");
|
||||
alert("User group created successfully");
|
||||
},
|
||||
onError: (error: any) => {
|
||||
alert(error.message || "Failed to create user group");
|
||||
},
|
||||
});
|
||||
|
||||
const deleteMutation = useMutation({
|
||||
mutationFn: (groupId: string) => deleteGroup(tenantId!, groupId),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: ["tenant-user-groups", tenantId],
|
||||
});
|
||||
alert("User group deleted successfully");
|
||||
},
|
||||
});
|
||||
|
||||
if (isLoading) return <div>Loading user groups...</div>;
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<Card>
|
||||
<CardHeader className="flex flex-row items-center justify-between">
|
||||
<div>
|
||||
<CardTitle>User Groups</CardTitle>
|
||||
<CardDescription>
|
||||
Manage user groups within this tenant for collective permission
|
||||
assignment.
|
||||
</CardDescription>
|
||||
</div>
|
||||
<Dialog open={isCreateOpen} onOpenChange={setIsCreateOpen}>
|
||||
<DialogTrigger asChild>
|
||||
<Button size="sm">
|
||||
<Plus size={16} className="mr-2" />
|
||||
Create Group
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Create User Group</DialogTitle>
|
||||
<DialogDescription>
|
||||
Create a new group to manage users collectively.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="space-y-4 py-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="name">Group Name</Label>
|
||||
<Input
|
||||
id="name"
|
||||
placeholder="e.g. Developers, Project A Managers"
|
||||
value={newGroupName}
|
||||
onChange={(e) => setNewGroupName(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="description">Description</Label>
|
||||
<Input
|
||||
id="description"
|
||||
placeholder="Brief description of the group"
|
||||
value={newGroupDesc}
|
||||
onChange={(e) => setNewGroupDesc(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => setIsCreateOpen(false)}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => createMutation.mutate()}
|
||||
disabled={!newGroupName || createMutation.isPending}
|
||||
>
|
||||
{createMutation.isPending ? "Creating..." : "Create Group"}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>Name</TableHead>
|
||||
<TableHead>Description</TableHead>
|
||||
<TableHead>Created At</TableHead>
|
||||
<TableHead className="text-right">Actions</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{groups?.length === 0 ? (
|
||||
<TableRow>
|
||||
<TableCell
|
||||
colSpan={4}
|
||||
className="text-center py-8 text-muted-foreground"
|
||||
>
|
||||
No user groups found for this tenant.
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
) : (
|
||||
groups?.map((group) => (
|
||||
<TableRow key={group.id}>
|
||||
<TableCell className="font-medium">
|
||||
<div className="flex items-center gap-2">
|
||||
<Users size={16} className="text-muted-foreground" />
|
||||
<Link
|
||||
to={`/tenants/${tenantId}/user-groups/${group.id}`}
|
||||
className="hover:underline text-primary"
|
||||
>
|
||||
{group.name}
|
||||
</Link>
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell>{group.description || "-"}</TableCell>
|
||||
<TableCell>
|
||||
{group.createdAt
|
||||
? new Date(group.createdAt).toLocaleDateString()
|
||||
: "-"}
|
||||
</TableCell>
|
||||
<TableCell className="text-right">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="text-destructive hover:text-destructive hover:bg-destructive/10"
|
||||
onClick={() => {
|
||||
if (
|
||||
confirm(
|
||||
"Are you sure you want to delete this group?",
|
||||
)
|
||||
) {
|
||||
deleteMutation.mutate(group.id);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Trash2 size={16} />
|
||||
</Button>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user