1
0
forked from baron/baron-sso

feat: apply sticky header and inner scroll pattern to all table pages

This commit is contained in:
2026-03-19 13:13:27 +09:00
parent 83991b13ca
commit f072d37362
9 changed files with 1007 additions and 944 deletions

View File

@@ -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>

View File

@@ -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

View File

@@ -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>
)} )}

View File

@@ -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>

View File

@@ -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>
); );

View File

@@ -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>
); );

View File

@@ -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>
); );

View File

@@ -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>

View File

@@ -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>