forked from baron/baron-sso
150 lines
5.4 KiB
TypeScript
150 lines
5.4 KiB
TypeScript
import { useQuery } from "@tanstack/react-query";
|
|
import { Building2, Plus, Users } from "lucide-react";
|
|
import { Link } from "react-router-dom";
|
|
import { commonStickyTableHeaderClass } from "../../../../../common/ui/table";
|
|
import { Badge } from "../../../components/ui/badge";
|
|
import { Button } from "../../../components/ui/button";
|
|
import {
|
|
Card,
|
|
CardContent,
|
|
CardDescription,
|
|
CardHeader,
|
|
CardTitle,
|
|
} from "../../../components/ui/card";
|
|
import {
|
|
Table,
|
|
TableBody,
|
|
TableCell,
|
|
TableHead,
|
|
TableHeader,
|
|
TableRow,
|
|
} from "../../../components/ui/table";
|
|
import {
|
|
fetchAllTenants,
|
|
fetchGroups,
|
|
type TenantSummary,
|
|
} from "../../../lib/adminApi";
|
|
|
|
export default function GlobalUserGroupListPage() {
|
|
const { data: tenantList, isLoading: isTenantsLoading } = useQuery({
|
|
queryKey: ["admin-tenants"],
|
|
queryFn: () => fetchAllTenants(),
|
|
});
|
|
|
|
if (isTenantsLoading)
|
|
return <div className="p-8">Loading tenants and groups...</div>;
|
|
|
|
return (
|
|
<div className="space-y-6 flex flex-col h-[calc(100vh-theme(spacing.32))]">
|
|
<header className="flex items-start justify-between flex-shrink-0 sticky top-[-2.5rem] z-20 bg-background/95 backdrop-blur pt-4 pb-2 -mt-4">
|
|
<div className="space-y-1">
|
|
<h2 className="text-3xl font-bold tracking-tight">User Groups</h2>
|
|
<p className="text-muted-foreground">
|
|
모든 테넌트의 유저 그룹을 관리합니다. 권한 상속의 주체가 되는 그룹을
|
|
설정하세요.
|
|
</p>
|
|
</div>
|
|
</header>
|
|
|
|
<div className="grid gap-6 flex-1 overflow-auto p-1">
|
|
{tenantList?.items.map((tenant) => (
|
|
<TenantGroupCard key={tenant.id} tenant={tenant} />
|
|
))}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function TenantGroupCard({ tenant }: { tenant: TenantSummary }) {
|
|
const { data: groups, isLoading } = useQuery({
|
|
queryKey: ["tenant-user-groups", tenant.id],
|
|
queryFn: () => fetchGroups(tenant.id),
|
|
});
|
|
|
|
return (
|
|
<Card className="flex flex-col min-h-0 bg-[var(--color-panel)] overflow-hidden">
|
|
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2 flex-shrink-0">
|
|
<div className="space-y-1">
|
|
<CardTitle className="text-xl flex items-center gap-2">
|
|
<Building2 size={20} className="text-muted-foreground" />
|
|
{tenant.name}
|
|
<Badge variant="outline" className="ml-2">
|
|
{tenant.slug}
|
|
</Badge>
|
|
</CardTitle>
|
|
<CardDescription>
|
|
이 테넌트에 정의된 유저 그룹 목록입니다.
|
|
</CardDescription>
|
|
</div>
|
|
<Button size="sm" variant="outline" asChild>
|
|
<Link to={`/tenants/${tenant.id}/user-groups`}>
|
|
<Plus size={16} className="mr-2" />
|
|
그룹 관리
|
|
</Link>
|
|
</Button>
|
|
</CardHeader>
|
|
<CardContent className="flex-1 flex flex-col min-h-0 pt-0">
|
|
<div className="flex-1 rounded-md border overflow-hidden flex flex-col">
|
|
<div className="flex-1 overflow-auto relative custom-scrollbar">
|
|
<Table>
|
|
<TableHeader className={commonStickyTableHeaderClass}>
|
|
<TableRow>
|
|
<TableHead className="w-[250px]">그룹명</TableHead>
|
|
<TableHead>설명</TableHead>
|
|
<TableHead className="w-[100px]">멤버 수</TableHead>
|
|
<TableHead className="text-right">작업</TableHead>
|
|
</TableRow>
|
|
</TableHeader>
|
|
<TableBody>
|
|
{isLoading ? (
|
|
<TableRow>
|
|
<TableCell colSpan={4} className="text-center">
|
|
Loading...
|
|
</TableCell>
|
|
</TableRow>
|
|
) : groups?.length === 0 ? (
|
|
<TableRow>
|
|
<TableCell
|
|
colSpan={4}
|
|
className="text-center text-muted-foreground py-4"
|
|
>
|
|
등록된 유저 그룹이 없습니다.
|
|
</TableCell>
|
|
</TableRow>
|
|
) : (
|
|
groups?.map((group) => (
|
|
<TableRow key={group.id}>
|
|
<TableCell className="font-medium">
|
|
<div className="flex items-center gap-2">
|
|
<Users size={14} className="text-primary" />
|
|
<Link
|
|
to={`/tenants/${tenant.id}/user-groups/${group.id}`}
|
|
className="hover:underline"
|
|
>
|
|
{group.name}
|
|
</Link>
|
|
</div>
|
|
</TableCell>
|
|
<TableCell>{group.description || "-"}</TableCell>
|
|
<TableCell>{group.members?.length || 0} 명</TableCell>
|
|
<TableCell className="text-right">
|
|
<Button variant="ghost" size="sm" asChild>
|
|
<Link
|
|
to={`/tenants/${tenant.id}/user-groups/${group.id}`}
|
|
>
|
|
상세보기
|
|
</Link>
|
|
</Button>
|
|
</TableCell>
|
|
</TableRow>
|
|
))
|
|
)}
|
|
</TableBody>
|
|
</Table>
|
|
</div>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
);
|
|
}
|