forked from baron/baron-sso
feat: apply sticky header and inner scroll pattern to all table pages
This commit is contained in:
@@ -63,8 +63,8 @@ function ApiKeyListPage() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-8">
|
<div className="space-y-6 flex flex-col h-[calc(100vh-theme(spacing.32))]">
|
||||||
<header className="flex flex-wrap items-start justify-between gap-4">
|
<header className="flex flex-wrap items-start justify-between gap-4 flex-shrink-0">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<div className="flex items-center gap-2 text-sm text-[var(--color-muted)]">
|
<div className="flex items-center gap-2 text-sm text-[var(--color-muted)]">
|
||||||
<span>
|
<span>
|
||||||
@@ -103,8 +103,8 @@ function ApiKeyListPage() {
|
|||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<Card className="bg-[var(--color-panel)]">
|
<Card className="bg-[var(--color-panel)] flex-1 flex flex-col min-h-0 overflow-hidden">
|
||||||
<CardHeader className="flex flex-row items-center justify-between">
|
<CardHeader className="flex flex-row items-center justify-between flex-shrink-0">
|
||||||
<div>
|
<div>
|
||||||
<CardTitle>
|
<CardTitle>
|
||||||
{t("ui.admin.api_keys.list.registry.title", "API Key Registry")}
|
{t("ui.admin.api_keys.list.registry.title", "API Key Registry")}
|
||||||
@@ -119,15 +119,17 @@ function ApiKeyListPage() {
|
|||||||
</div>
|
</div>
|
||||||
<Badge variant="muted">{t("ui.common.badge.system", "System")}</Badge>
|
<Badge variant="muted">{t("ui.common.badge.system", "System")}</Badge>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent className="flex-1 flex flex-col min-h-0 pt-0">
|
||||||
{(errorMsg || fallbackError) && (
|
{(errorMsg || fallbackError) && (
|
||||||
<div className="mb-4 rounded-lg border border-destructive/40 bg-destructive/10 px-3 py-2 text-sm text-destructive">
|
<div className="mb-4 rounded-lg border border-destructive/40 bg-destructive/10 px-3 py-2 text-sm text-destructive">
|
||||||
{errorMsg ?? fallbackError}
|
{errorMsg ?? fallbackError}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
<div className="flex-1 rounded-md border overflow-hidden flex flex-col">
|
||||||
|
<div className="flex-1 overflow-auto relative custom-scrollbar">
|
||||||
<Table>
|
<Table>
|
||||||
<TableHeader>
|
<TableHeader className="sticky top-0 bg-muted/90 backdrop-blur z-10 shadow-sm">
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableHead>
|
<TableHead>
|
||||||
{t("ui.admin.api_keys.list.table.name", "NAME")}
|
{t("ui.admin.api_keys.list.table.name", "NAME")}
|
||||||
@@ -168,7 +170,10 @@ function ApiKeyListPage() {
|
|||||||
<TableRow key={key.id}>
|
<TableRow key={key.id}>
|
||||||
<TableCell className="font-semibold">
|
<TableCell className="font-semibold">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Key size={14} className="text-[var(--color-muted)]" />
|
<Key
|
||||||
|
size={14}
|
||||||
|
className="text-[var(--color-muted)]"
|
||||||
|
/>
|
||||||
{key.name}
|
{key.name}
|
||||||
</div>
|
</div>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
@@ -208,6 +213,8 @@ function ApiKeyListPage() {
|
|||||||
))}
|
))}
|
||||||
</TableBody>
|
</TableBody>
|
||||||
</Table>
|
</Table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -207,10 +207,11 @@ export function TenantAdminsAndOwnersTab() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-8 mt-6">
|
<div className="space-y-8 mt-6 flex flex-col h-[calc(100vh-theme(spacing.32))]">
|
||||||
|
<div className="flex-1 flex flex-col lg:flex-row gap-8 min-h-0">
|
||||||
{/* Owners Card */}
|
{/* Owners Card */}
|
||||||
<Card className="border-none shadow-sm bg-[var(--color-panel)]">
|
<Card className="flex-1 flex flex-col min-h-0 border-none shadow-sm bg-[var(--color-panel)]">
|
||||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-7">
|
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-7 flex-shrink-0">
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<CardTitle className="text-2xl font-bold flex items-center gap-2">
|
<CardTitle className="text-2xl font-bold flex items-center gap-2">
|
||||||
<Crown className="h-6 w-6 text-yellow-500" />
|
<Crown className="h-6 w-6 text-yellow-500" />
|
||||||
@@ -231,10 +232,11 @@ export function TenantAdminsAndOwnersTab() {
|
|||||||
{t("ui.admin.tenants.owners.add_button", "소유자 추가")}
|
{t("ui.admin.tenants.owners.add_button", "소유자 추가")}
|
||||||
</Button>
|
</Button>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent className="flex-1 flex flex-col min-h-0 pt-0">
|
||||||
<div className="rounded-xl border border-border overflow-hidden">
|
<div className="flex-1 rounded-md border overflow-hidden flex flex-col">
|
||||||
|
<div className="flex-1 overflow-auto relative custom-scrollbar">
|
||||||
<Table>
|
<Table>
|
||||||
<TableHeader className="bg-muted/30">
|
<TableHeader className="sticky top-0 bg-muted/90 backdrop-blur z-10 shadow-sm">
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableHead className="w-[250px] font-bold">
|
<TableHead className="w-[250px] font-bold">
|
||||||
{t("ui.admin.tenants.owners.table_name", "이름")}
|
{t("ui.admin.tenants.owners.table_name", "이름")}
|
||||||
@@ -332,12 +334,13 @@ export function TenantAdminsAndOwnersTab() {
|
|||||||
</TableBody>
|
</TableBody>
|
||||||
</Table>
|
</Table>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
{/* Admins Card */}
|
{/* Admins Card */}
|
||||||
<Card className="border-none shadow-sm bg-[var(--color-panel)]">
|
<Card className="flex-1 flex flex-col min-h-0 border-none shadow-sm bg-[var(--color-panel)]">
|
||||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-7">
|
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-7 flex-shrink-0">
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<CardTitle className="text-2xl font-bold flex items-center gap-2">
|
<CardTitle className="text-2xl font-bold flex items-center gap-2">
|
||||||
<ShieldCheck className="h-6 w-6 text-primary" />
|
<ShieldCheck className="h-6 w-6 text-primary" />
|
||||||
@@ -358,10 +361,11 @@ export function TenantAdminsAndOwnersTab() {
|
|||||||
{t("ui.admin.tenants.admins.add_button", "관리자 추가")}
|
{t("ui.admin.tenants.admins.add_button", "관리자 추가")}
|
||||||
</Button>
|
</Button>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent className="flex-1 flex flex-col min-h-0 pt-0">
|
||||||
<div className="rounded-xl border border-border overflow-hidden">
|
<div className="flex-1 rounded-md border overflow-hidden flex flex-col">
|
||||||
|
<div className="flex-1 overflow-auto relative custom-scrollbar">
|
||||||
<Table>
|
<Table>
|
||||||
<TableHeader className="bg-muted/30">
|
<TableHeader className="sticky top-0 bg-muted/90 backdrop-blur z-10 shadow-sm">
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableHead className="w-[250px] font-bold">
|
<TableHead className="w-[250px] font-bold">
|
||||||
{t("ui.admin.tenants.admins.table_name", "이름")}
|
{t("ui.admin.tenants.admins.table_name", "이름")}
|
||||||
@@ -459,8 +463,10 @@ export function TenantAdminsAndOwnersTab() {
|
|||||||
</TableBody>
|
</TableBody>
|
||||||
</Table>
|
</Table>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Common Dialog for adding users */}
|
{/* Common Dialog for adding users */}
|
||||||
<Dialog
|
<Dialog
|
||||||
|
|||||||
@@ -343,11 +343,11 @@ function TenantGroupsPage() {
|
|||||||
const currentGroup = groupsQuery.data?.find((g) => g.id === selectedGroupId);
|
const currentGroup = groupsQuery.data?.find((g) => g.id === selectedGroupId);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6 mt-6">
|
<div className="space-y-6 mt-6 flex flex-col h-[calc(100vh-theme(spacing.32))]">
|
||||||
<div className="grid gap-6 md:grid-cols-3">
|
<div className="grid gap-6 md:grid-cols-3 flex-1 min-h-0">
|
||||||
{/* 그룹 생성 폼 */}
|
{/* 그룹 생성 폼 */}
|
||||||
<Card className="bg-[var(--color-panel)] md:col-span-1 border-primary/20">
|
<Card className="flex flex-col min-h-0 bg-[var(--color-panel)] md:col-span-1 border-primary/20">
|
||||||
<CardHeader>
|
<CardHeader className="flex-shrink-0">
|
||||||
<CardTitle className="text-sm flex items-center gap-2">
|
<CardTitle className="text-sm flex items-center gap-2">
|
||||||
<Plus size={16} />{" "}
|
<Plus size={16} />{" "}
|
||||||
{t("ui.admin.groups.create.title", "새 그룹 생성")}
|
{t("ui.admin.groups.create.title", "새 그룹 생성")}
|
||||||
@@ -359,7 +359,7 @@ function TenantGroupsPage() {
|
|||||||
)}
|
)}
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="space-y-4">
|
<CardContent className="space-y-4 flex-1 overflow-auto">
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<Label htmlFor="name">
|
<Label htmlFor="name">
|
||||||
{t("ui.admin.groups.form.name_label", "그룹 이름")}
|
{t("ui.admin.groups.form.name_label", "그룹 이름")}
|
||||||
@@ -431,8 +431,8 @@ function TenantGroupsPage() {
|
|||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
{/* 그룹 목록 (트리 뷰) */}
|
{/* 그룹 목록 (트리 뷰) */}
|
||||||
<Card className="bg-[var(--color-panel)] md:col-span-2">
|
<Card className="flex flex-col min-h-0 bg-[var(--color-panel)] md:col-span-2">
|
||||||
<CardHeader className="flex flex-row items-center justify-between">
|
<CardHeader className="flex flex-row items-center justify-between flex-shrink-0">
|
||||||
<div>
|
<div>
|
||||||
<CardTitle>
|
<CardTitle>
|
||||||
{t("ui.admin.groups.list.title", "User Groups")}
|
{t("ui.admin.groups.list.title", "User Groups")}
|
||||||
@@ -458,9 +458,11 @@ function TenantGroupsPage() {
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<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>
|
<Table>
|
||||||
<TableHeader>
|
<TableHeader className="sticky top-0 bg-muted/90 backdrop-blur z-10 shadow-sm">
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableHead>
|
<TableHead>
|
||||||
{t("ui.admin.groups.table.name", "NAME")}
|
{t("ui.admin.groups.table.name", "NAME")}
|
||||||
@@ -520,14 +522,16 @@ function TenantGroupsPage() {
|
|||||||
))}
|
))}
|
||||||
</TableBody>
|
</TableBody>
|
||||||
</Table>
|
</Table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 멤버 관리 섹션 (선택된 그룹이 있을 때) */}
|
{/* 멤버 관리 섹션 (선택된 그룹이 있을 때) */}
|
||||||
{currentGroup && (
|
{currentGroup && (
|
||||||
<Card className="bg-[var(--color-panel)] border-t-4 border-t-primary">
|
<Card className="flex flex-col min-h-0 flex-1 bg-[var(--color-panel)] border-t-4 border-t-primary">
|
||||||
<CardHeader>
|
<CardHeader className="flex-shrink-0">
|
||||||
<CardTitle className="flex items-center gap-2">
|
<CardTitle className="flex items-center gap-2">
|
||||||
<Shield size={18} className="text-primary" />
|
<Shield size={18} className="text-primary" />
|
||||||
{t("msg.admin.groups.members.title", "[{{name}}] 멤버 관리", {
|
{t("msg.admin.groups.members.title", "[{{name}}] 멤버 관리", {
|
||||||
@@ -541,8 +545,8 @@ function TenantGroupsPage() {
|
|||||||
)}
|
)}
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent className="flex-1 flex flex-col min-h-0 pt-0">
|
||||||
<div className="flex justify-end mb-4">
|
<div className="flex justify-end mb-4 flex-shrink-0">
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={() => handleAddMember(currentGroup.id)}
|
onClick={() => handleAddMember(currentGroup.id)}
|
||||||
@@ -552,8 +556,10 @@ function TenantGroupsPage() {
|
|||||||
{t("ui.common.add", "멤버 추가")}
|
{t("ui.common.add", "멤버 추가")}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="flex-1 rounded-md border overflow-hidden flex flex-col">
|
||||||
|
<div className="flex-1 overflow-auto relative custom-scrollbar">
|
||||||
<Table>
|
<Table>
|
||||||
<TableHeader>
|
<TableHeader className="sticky top-0 bg-muted/90 backdrop-blur z-10 shadow-sm">
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableHead>
|
<TableHead>
|
||||||
{t("ui.admin.groups.members.table.name", "이름")}
|
{t("ui.admin.groups.members.table.name", "이름")}
|
||||||
@@ -573,13 +579,18 @@ function TenantGroupsPage() {
|
|||||||
colSpan={3}
|
colSpan={3}
|
||||||
className="text-center py-4 text-muted-foreground"
|
className="text-center py-4 text-muted-foreground"
|
||||||
>
|
>
|
||||||
{t("msg.admin.groups.members.empty", "멤버가 없습니다.")}
|
{t(
|
||||||
|
"msg.admin.groups.members.empty",
|
||||||
|
"멤버가 없습니다.",
|
||||||
|
)}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
)}
|
)}
|
||||||
{currentGroup.members?.map((user) => (
|
{currentGroup.members?.map((user) => (
|
||||||
<TableRow key={user.id}>
|
<TableRow key={user.id}>
|
||||||
<TableCell className="font-medium">{user.name}</TableCell>
|
<TableCell className="font-medium">
|
||||||
|
{user.name}
|
||||||
|
</TableCell>
|
||||||
<TableCell className="text-muted-foreground">
|
<TableCell className="text-muted-foreground">
|
||||||
{user.email}
|
{user.email}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
@@ -602,6 +613,8 @@ function TenantGroupsPage() {
|
|||||||
))}
|
))}
|
||||||
</TableBody>
|
</TableBody>
|
||||||
</Table>
|
</Table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -116,8 +116,8 @@ function TenantListPage() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-8">
|
<div className="space-y-6 flex flex-col h-[calc(100vh-theme(spacing.32))]">
|
||||||
<header className="flex flex-wrap items-start justify-between gap-4">
|
<header className="flex flex-wrap items-start justify-between gap-4 flex-shrink-0">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<div className="flex items-center gap-2 text-sm text-[var(--color-muted)]">
|
<div className="flex items-center gap-2 text-sm text-[var(--color-muted)]">
|
||||||
<span>{t("ui.admin.tenants.breadcrumb.section", "Tenants")}</span>
|
<span>{t("ui.admin.tenants.breadcrumb.section", "Tenants")}</span>
|
||||||
@@ -156,8 +156,8 @@ function TenantListPage() {
|
|||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<Card className="bg-[var(--color-panel)]">
|
<Card className="bg-[var(--color-panel)] flex-1 flex flex-col min-h-0 overflow-hidden">
|
||||||
<CardHeader className="flex flex-row items-center justify-between">
|
<CardHeader className="flex flex-row items-center justify-between flex-shrink-0">
|
||||||
<div>
|
<div>
|
||||||
<CardTitle>
|
<CardTitle>
|
||||||
{t("ui.admin.tenants.registry.title", "Tenant Registry")}
|
{t("ui.admin.tenants.registry.title", "Tenant Registry")}
|
||||||
@@ -172,15 +172,17 @@ function TenantListPage() {
|
|||||||
{t("ui.common.badge.admin_only", "Admin only")}
|
{t("ui.common.badge.admin_only", "Admin only")}
|
||||||
</Badge>
|
</Badge>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent className="flex-1 flex flex-col min-h-0 pt-0">
|
||||||
{(errorMsg || fallbackError) && (
|
{(errorMsg || fallbackError) && (
|
||||||
<div className="mb-4 rounded-lg border border-destructive/40 bg-destructive/10 px-3 py-2 text-sm text-destructive">
|
<div className="mb-4 rounded-lg border border-destructive/40 bg-destructive/10 px-3 py-2 text-sm text-destructive">
|
||||||
{errorMsg ?? fallbackError}
|
{errorMsg ?? fallbackError}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
<div className="flex-1 rounded-md border overflow-hidden flex flex-col">
|
||||||
|
<div className="flex-1 overflow-auto relative custom-scrollbar">
|
||||||
<Table>
|
<Table>
|
||||||
<TableHeader>
|
<TableHeader className="sticky top-0 bg-muted/90 backdrop-blur z-10 shadow-sm">
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableHead>
|
<TableHead>
|
||||||
{t("ui.admin.tenants.table.name", "NAME")}
|
{t("ui.admin.tenants.table.name", "NAME")}
|
||||||
@@ -228,9 +230,14 @@ function TenantListPage() {
|
|||||||
)}
|
)}
|
||||||
{tenants.map((tenant) => (
|
{tenants.map((tenant) => (
|
||||||
<TableRow key={tenant.id}>
|
<TableRow key={tenant.id}>
|
||||||
<TableCell className="font-semibold">{tenant.name}</TableCell>
|
<TableCell className="font-semibold">
|
||||||
|
{tenant.name}
|
||||||
|
</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<Badge variant="outline" className="text-[10px] font-mono">
|
<Badge
|
||||||
|
variant="outline"
|
||||||
|
className="text-[10px] font-mono"
|
||||||
|
>
|
||||||
{t(
|
{t(
|
||||||
`domain.tenant_type.${tenant.type?.toLowerCase()}`,
|
`domain.tenant_type.${tenant.type?.toLowerCase()}`,
|
||||||
tenant.type,
|
tenant.type,
|
||||||
@@ -250,7 +257,10 @@ function TenantListPage() {
|
|||||||
: "muted"
|
: "muted"
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{t(`ui.common.status.${tenant.status}`, tenant.status)}
|
{t(
|
||||||
|
`ui.common.status.${tenant.status}`,
|
||||||
|
tenant.status,
|
||||||
|
)}
|
||||||
</Badge>
|
</Badge>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell className="font-medium">
|
<TableCell className="font-medium">
|
||||||
@@ -286,6 +296,8 @@ function TenantListPage() {
|
|||||||
))}
|
))}
|
||||||
</TableBody>
|
</TableBody>
|
||||||
</Table>
|
</Table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -34,8 +34,8 @@ function TenantSubTenantsPage() {
|
|||||||
const subTenants = data?.items ?? [];
|
const subTenants = data?.items ?? [];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card className="mt-6 bg-[var(--color-panel)]">
|
<Card className="mt-6 bg-[var(--color-panel)] flex-1 flex flex-col min-h-0 overflow-hidden">
|
||||||
<CardHeader className="flex flex-row items-center justify-between">
|
<CardHeader className="flex flex-row items-center justify-between flex-shrink-0">
|
||||||
<div>
|
<div>
|
||||||
<CardTitle className="flex items-center gap-2">
|
<CardTitle className="flex items-center gap-2">
|
||||||
<Building2 size={18} className="text-primary" />
|
<Building2 size={18} className="text-primary" />
|
||||||
@@ -57,9 +57,11 @@ function TenantSubTenantsPage() {
|
|||||||
</Link>
|
</Link>
|
||||||
</Button>
|
</Button>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<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>
|
<Table>
|
||||||
<TableHeader>
|
<TableHeader className="sticky top-0 bg-muted/90 backdrop-blur z-10 shadow-sm">
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableHead>
|
<TableHead>
|
||||||
{t("ui.admin.tenants.sub.table.name", "NAME")}
|
{t("ui.admin.tenants.sub.table.name", "NAME")}
|
||||||
@@ -82,13 +84,18 @@ function TenantSubTenantsPage() {
|
|||||||
colSpan={4}
|
colSpan={4}
|
||||||
className="text-center py-8 text-muted-foreground"
|
className="text-center py-8 text-muted-foreground"
|
||||||
>
|
>
|
||||||
{t("msg.admin.tenants.sub.empty", "하위 테넌트가 없습니다.")}
|
{t(
|
||||||
|
"msg.admin.tenants.sub.empty",
|
||||||
|
"하위 테넌트가 없습니다.",
|
||||||
|
)}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
)}
|
)}
|
||||||
{subTenants.map((tenant) => (
|
{subTenants.map((tenant) => (
|
||||||
<TableRow key={tenant.id}>
|
<TableRow key={tenant.id}>
|
||||||
<TableCell className="font-semibold">{tenant.name}</TableCell>
|
<TableCell className="font-semibold">
|
||||||
|
{tenant.name}
|
||||||
|
</TableCell>
|
||||||
<TableCell className="text-xs font-mono">
|
<TableCell className="text-xs font-mono">
|
||||||
{tenant.slug}
|
{tenant.slug}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
@@ -115,6 +122,8 @@ function TenantSubTenantsPage() {
|
|||||||
))}
|
))}
|
||||||
</TableBody>
|
</TableBody>
|
||||||
</Table>
|
</Table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -42,8 +42,8 @@ function TenantUsersPage() {
|
|||||||
const users = usersQuery.data?.items ?? [];
|
const users = usersQuery.data?.items ?? [];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card className="mt-6 bg-[var(--color-panel)]">
|
<Card className="mt-6 bg-[var(--color-panel)] flex-1 flex flex-col min-h-0 overflow-hidden">
|
||||||
<CardHeader>
|
<CardHeader className="flex-shrink-0">
|
||||||
<CardTitle className="flex items-center gap-2">
|
<CardTitle className="flex items-center gap-2">
|
||||||
<User size={18} className="text-primary" />
|
<User size={18} className="text-primary" />
|
||||||
{t("ui.admin.tenants.members.title", "Tenant Members ({{count}})", {
|
{t("ui.admin.tenants.members.title", "Tenant Members ({{count}})", {
|
||||||
@@ -51,9 +51,11 @@ function TenantUsersPage() {
|
|||||||
})}
|
})}
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<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>
|
<Table>
|
||||||
<TableHeader>
|
<TableHeader className="sticky top-0 bg-muted/90 backdrop-blur z-10 shadow-sm">
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableHead>
|
<TableHead>
|
||||||
{t("ui.admin.tenants.members.table.name", "NAME")}
|
{t("ui.admin.tenants.members.table.name", "NAME")}
|
||||||
@@ -111,6 +113,8 @@ function TenantUsersPage() {
|
|||||||
))}
|
))}
|
||||||
</TableBody>
|
</TableBody>
|
||||||
</Table>
|
</Table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -35,8 +35,8 @@ export default function GlobalUserGroupListPage() {
|
|||||||
return <div className="p-8">Loading tenants and groups...</div>;
|
return <div className="p-8">Loading tenants and groups...</div>;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-8">
|
<div className="space-y-6 flex flex-col h-[calc(100vh-theme(spacing.32))]">
|
||||||
<header className="flex items-start justify-between">
|
<header className="flex items-start justify-between flex-shrink-0">
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<h2 className="text-3xl font-bold tracking-tight">User Groups</h2>
|
<h2 className="text-3xl font-bold tracking-tight">User Groups</h2>
|
||||||
<p className="text-muted-foreground">
|
<p className="text-muted-foreground">
|
||||||
@@ -46,7 +46,7 @@ export default function GlobalUserGroupListPage() {
|
|||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div className="grid gap-6">
|
<div className="grid gap-6 flex-1 overflow-auto p-1">
|
||||||
{tenantList?.items.map((tenant) => (
|
{tenantList?.items.map((tenant) => (
|
||||||
<TenantGroupCard key={tenant.id} tenant={tenant} />
|
<TenantGroupCard key={tenant.id} tenant={tenant} />
|
||||||
))}
|
))}
|
||||||
@@ -62,8 +62,8 @@ function TenantGroupCard({ tenant }: { tenant: TenantSummary }) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card>
|
<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">
|
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2 flex-shrink-0">
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<CardTitle className="text-xl flex items-center gap-2">
|
<CardTitle className="text-xl flex items-center gap-2">
|
||||||
<Building2 size={20} className="text-muted-foreground" />
|
<Building2 size={20} className="text-muted-foreground" />
|
||||||
@@ -83,9 +83,11 @@ function TenantGroupCard({ tenant }: { tenant: TenantSummary }) {
|
|||||||
</Link>
|
</Link>
|
||||||
</Button>
|
</Button>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<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 max-h-[400px]">
|
||||||
<Table>
|
<Table>
|
||||||
<TableHeader>
|
<TableHeader className="sticky top-0 bg-muted/90 backdrop-blur z-10 shadow-sm">
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableHead className="w-[250px]">그룹명</TableHead>
|
<TableHead className="w-[250px]">그룹명</TableHead>
|
||||||
<TableHead>설명</TableHead>
|
<TableHead>설명</TableHead>
|
||||||
@@ -139,6 +141,8 @@ function TenantGroupCard({ tenant }: { tenant: TenantSummary }) {
|
|||||||
)}
|
)}
|
||||||
</TableBody>
|
</TableBody>
|
||||||
</Table>
|
</Table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -929,9 +929,9 @@ function TenantUserGroupsTab() {
|
|||||||
const BaseIcon = getTenantIcon(currentBase.type);
|
const BaseIcon = getTenantIcon(currentBase.type);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6 mt-6">
|
<div className="space-y-6 mt-6 flex flex-col h-[calc(100vh-theme(spacing.32))]">
|
||||||
<Card className="bg-[var(--color-panel)] border-none shadow-sm overflow-hidden">
|
<Card className="flex-1 flex flex-col min-h-0 bg-[var(--color-panel)] border-none shadow-sm overflow-hidden">
|
||||||
<CardHeader className="flex flex-row items-center justify-between border-b bg-muted/5 py-4">
|
<CardHeader className="flex flex-row items-center justify-between border-b bg-muted/5 py-4 flex-shrink-0">
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<CardTitle className="text-xl font-bold flex items-center gap-2">
|
<CardTitle className="text-xl font-bold flex items-center gap-2">
|
||||||
<BaseIcon size={20} className="text-primary" />
|
<BaseIcon size={20} className="text-primary" />
|
||||||
@@ -1078,7 +1078,7 @@ function TenantUserGroupsTab() {
|
|||||||
</Dialog>
|
</Dialog>
|
||||||
</div>
|
</div>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<div className="px-6 py-3 bg-muted/5 border-b flex items-center gap-4">
|
<div className="px-6 py-3 bg-muted/5 border-b flex items-center gap-4 flex-shrink-0">
|
||||||
<div className="relative flex-1 max-w-sm">
|
<div className="relative flex-1 max-w-sm">
|
||||||
<Search className="absolute left-2.5 top-2.5 h-4 w-4 text-muted-foreground" />
|
<Search className="absolute left-2.5 top-2.5 h-4 w-4 text-muted-foreground" />
|
||||||
<Input
|
<Input
|
||||||
@@ -1102,9 +1102,11 @@ function TenantUserGroupsTab() {
|
|||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<CardContent className="p-0">
|
<CardContent className="flex-1 flex flex-col min-h-0 p-0">
|
||||||
|
<div className="flex-1 rounded-md border-0 overflow-hidden flex flex-col">
|
||||||
|
<div className="flex-1 overflow-auto relative custom-scrollbar">
|
||||||
<Table>
|
<Table>
|
||||||
<TableHeader className="bg-muted/5">
|
<TableHeader className="sticky top-0 bg-muted/90 backdrop-blur z-10 shadow-sm">
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableHead className="pl-6 w-[40%]">
|
<TableHead className="pl-6 w-[40%]">
|
||||||
{t("ui.admin.tenants.table.name", "NAME")}
|
{t("ui.admin.tenants.table.name", "NAME")}
|
||||||
@@ -1135,6 +1137,8 @@ function TenantUserGroupsTab() {
|
|||||||
/>
|
/>
|
||||||
</TableBody>
|
</TableBody>
|
||||||
</Table>
|
</Table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -211,8 +211,8 @@ export function UserGroupDetailPage() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-8">
|
<div className="space-y-8 flex flex-col h-[calc(100vh-theme(spacing.32))]">
|
||||||
<header className="flex flex-wrap items-start justify-between gap-4">
|
<header className="flex flex-wrap items-start justify-between gap-4 flex-shrink-0">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<div className="flex items-center gap-2 text-sm text-muted-foreground">
|
<div className="flex items-center gap-2 text-sm text-muted-foreground">
|
||||||
<Link
|
<Link
|
||||||
@@ -260,10 +260,10 @@ export function UserGroupDetailPage() {
|
|||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8">
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8 flex-1 min-h-0">
|
||||||
{/* Members Management */}
|
{/* Members Management */}
|
||||||
<Card className="border-none shadow-sm bg-[var(--color-panel)]">
|
<Card className="flex flex-col min-h-0 border-none shadow-sm bg-[var(--color-panel)] overflow-hidden">
|
||||||
<CardHeader className="flex flex-row items-center justify-between">
|
<CardHeader className="flex flex-row items-center justify-between flex-shrink-0">
|
||||||
<div>
|
<div>
|
||||||
<CardTitle>
|
<CardTitle>
|
||||||
{t("ui.admin.groups.detail.members_title", "구성원 관리")}
|
{t("ui.admin.groups.detail.members_title", "구성원 관리")}
|
||||||
@@ -347,10 +347,11 @@ export function UserGroupDetailPage() {
|
|||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent className="flex-1 flex flex-col min-h-0 pt-0">
|
||||||
<div className="rounded-md border border-border overflow-hidden">
|
<div className="flex-1 rounded-md border overflow-hidden flex flex-col">
|
||||||
|
<div className="flex-1 overflow-auto relative custom-scrollbar">
|
||||||
<Table>
|
<Table>
|
||||||
<TableHeader className="bg-muted/30">
|
<TableHeader className="sticky top-0 bg-muted/90 backdrop-blur z-10 shadow-sm">
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableHead className="font-bold">
|
<TableHead className="font-bold">
|
||||||
{t("ui.admin.users.list.table.name_email", "사용자")}
|
{t("ui.admin.users.list.table.name_email", "사용자")}
|
||||||
@@ -423,12 +424,13 @@ export function UserGroupDetailPage() {
|
|||||||
</TableBody>
|
</TableBody>
|
||||||
</Table>
|
</Table>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
{/* Roles/Permissions Management (Keto Based) */}
|
{/* Roles/Permissions Management (Keto Based) */}
|
||||||
<Card className="border-none shadow-sm bg-[var(--color-panel)]">
|
<Card className="flex flex-col min-h-0 border-none shadow-sm bg-[var(--color-panel)] overflow-hidden">
|
||||||
<CardHeader className="flex flex-row items-center justify-between">
|
<CardHeader className="flex flex-row items-center justify-between flex-shrink-0">
|
||||||
<div>
|
<div>
|
||||||
<CardTitle>
|
<CardTitle>
|
||||||
{t("ui.admin.groups.detail.permissions_title", "권한 관리")}
|
{t("ui.admin.groups.detail.permissions_title", "권한 관리")}
|
||||||
@@ -530,10 +532,11 @@ export function UserGroupDetailPage() {
|
|||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent className="flex-1 flex flex-col min-h-0 pt-0">
|
||||||
<div className="rounded-md border border-border overflow-hidden">
|
<div className="flex-1 rounded-md border overflow-hidden flex flex-col">
|
||||||
|
<div className="flex-1 overflow-auto relative custom-scrollbar">
|
||||||
<Table>
|
<Table>
|
||||||
<TableHeader className="bg-muted/30">
|
<TableHeader className="sticky top-0 bg-muted/90 backdrop-blur z-10 shadow-sm">
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableHead className="font-bold">
|
<TableHead className="font-bold">
|
||||||
{t("ui.admin.users.detail.form.tenant", "대상 테넌트")}
|
{t("ui.admin.users.detail.form.tenant", "대상 테넌트")}
|
||||||
@@ -611,6 +614,7 @@ export function UserGroupDetailPage() {
|
|||||||
</TableBody>
|
</TableBody>
|
||||||
</Table>
|
</Table>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user