forked from baron/baron-sso
박스 타이틀 description 추가 및 로고 추가
This commit is contained in:
@@ -38,6 +38,7 @@ import {
|
||||
sortItems,
|
||||
toggleSort,
|
||||
} from "../../../../../common/core/utils";
|
||||
import { PageHeader } from "../../../../../common/core/components/page";
|
||||
import {
|
||||
commonStickyTableHeaderClass,
|
||||
commonTableShellClass,
|
||||
@@ -745,182 +746,132 @@ function TenantListPage() {
|
||||
|
||||
return (
|
||||
<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 flex-shrink-0 sticky top-[-2.5rem] z-20 bg-background/95 backdrop-blur pt-4 pb-2 -mt-4">
|
||||
<div className="space-y-2">
|
||||
<h2 className="text-3xl font-semibold">
|
||||
{t("ui.admin.tenants.title", "테넌트 목록")}
|
||||
</h2>
|
||||
<p className="text-sm text-[var(--color-muted)]">
|
||||
{t(
|
||||
"msg.admin.tenants.subtitle",
|
||||
"시스템에 등록된 모든 테넌트를 평면 목록으로 확인하고 관리합니다.",
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="relative w-64 mr-2">
|
||||
<Search className="absolute left-2.5 top-2.5 h-4 w-4 text-muted-foreground" />
|
||||
<Input
|
||||
placeholder={t(
|
||||
"ui.admin.tenants.list.search_placeholder",
|
||||
"테넌트 이름, 슬러그, UUID 검색...",
|
||||
)}
|
||||
className="pl-9 h-9"
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className="flex rounded-md border bg-background p-0.5"
|
||||
data-testid="tenant-view-mode-toggle"
|
||||
>
|
||||
<Button
|
||||
type="button"
|
||||
variant={viewMode === "tree" ? "default" : "ghost"}
|
||||
size="sm"
|
||||
className="h-8 gap-1.5"
|
||||
onClick={() => setViewMode("tree")}
|
||||
data-testid="tenant-view-tree-btn"
|
||||
>
|
||||
<Network size={14} />
|
||||
{t("ui.admin.tenants.view.tree", "트리")}
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
variant={viewMode === "table" ? "default" : "ghost"}
|
||||
size="sm"
|
||||
className="h-8 gap-1.5"
|
||||
onClick={() => setViewMode("table")}
|
||||
data-testid="tenant-view-table-btn"
|
||||
>
|
||||
<List size={14} />
|
||||
{t("ui.admin.tenants.view.table", "평면")}
|
||||
</Button>
|
||||
</div>
|
||||
<Button
|
||||
type="button"
|
||||
variant={scopeTenantId ? "default" : "outline"}
|
||||
size="sm"
|
||||
className="h-9 gap-2"
|
||||
onClick={() => setScopePickerOpen(true)}
|
||||
data-testid="tenant-scope-picker-btn"
|
||||
>
|
||||
<Network size={16} />
|
||||
{selectedScopeTenant
|
||||
? t("ui.admin.tenants.scope.active", "{{name}} 하위", {
|
||||
name: selectedScopeTenant.name,
|
||||
})
|
||||
: t("ui.admin.tenants.scope.pick", "상위 범위 선택")}
|
||||
</Button>
|
||||
{scopeTenantId && (
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="h-9"
|
||||
onClick={() => setScopeTenantId("")}
|
||||
data-testid="tenant-scope-clear-btn"
|
||||
>
|
||||
{t("ui.common.clear", "초기화")}
|
||||
</Button>
|
||||
)}
|
||||
|
||||
<RoleGuard roles={["super_admin"]}>
|
||||
<input
|
||||
ref={fileInputRef}
|
||||
type="file"
|
||||
accept=".csv,text/csv"
|
||||
className="hidden"
|
||||
data-testid="tenant-import-input"
|
||||
onChange={handleImportFile}
|
||||
/>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
data-testid="tenant-data-mgmt-btn"
|
||||
className="gap-2"
|
||||
>
|
||||
<LayoutDashboard size={16} />
|
||||
{t("ui.admin.tenants.data_mgmt", "데이터 관리")}
|
||||
<ChevronDown size={14} className="opacity-50" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end" className="w-56">
|
||||
<DropdownMenuItem
|
||||
onClick={handleTemplateDownload}
|
||||
data-testid="tenant-template-menu-item"
|
||||
className="cursor-pointer"
|
||||
>
|
||||
<FileSpreadsheet size={16} className="mr-2 opacity-50" />
|
||||
{t("ui.admin.tenants.csv_template", "템플릿 다운로드")}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem
|
||||
onClick={() => fileInputRef.current?.click()}
|
||||
disabled={importMutation.isPending}
|
||||
data-testid="tenant-import-menu-item"
|
||||
className="cursor-pointer"
|
||||
>
|
||||
<Upload size={16} className="mr-2 opacity-50" />
|
||||
{t("ui.admin.tenants.import", "CSV 가져오기")}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem
|
||||
onClick={() => exportMutation.mutate(false)}
|
||||
disabled={exportMutation.isPending}
|
||||
data-testid="tenant-export-menu-item"
|
||||
className="cursor-pointer"
|
||||
>
|
||||
<Download size={16} className="mr-2 opacity-50" />
|
||||
{t(
|
||||
"ui.admin.tenants.export_without_ids",
|
||||
"UUID 제외 내보내기",
|
||||
)}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
onClick={() => exportMutation.mutate(true)}
|
||||
disabled={exportMutation.isPending}
|
||||
data-testid="tenant-export-with-ids-menu-item"
|
||||
className="cursor-pointer"
|
||||
>
|
||||
<Download size={16} className="mr-2 opacity-50" />
|
||||
{t("ui.admin.tenants.export_with_ids", "UUID 포함 내보내기")}
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</RoleGuard>
|
||||
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => query.refetch()}
|
||||
disabled={query.isFetching}
|
||||
className="w-9 px-0"
|
||||
title={t("ui.common.refresh", "새로고침")}
|
||||
>
|
||||
<RefreshCw size={16} />
|
||||
<span className="sr-only">
|
||||
{t("ui.common.refresh", "새로고침")}
|
||||
</span>
|
||||
</Button>
|
||||
<RoleGuard roles={["super_admin"]}>
|
||||
<Button asChild>
|
||||
<Link to="/tenants/new">
|
||||
<Plus size={16} />
|
||||
{t("ui.admin.tenants.add", "테넌트 추가")}
|
||||
</Link>
|
||||
</Button>
|
||||
</RoleGuard>
|
||||
</div>
|
||||
{importMessage && (
|
||||
<div
|
||||
className="basis-full rounded-md border border-border bg-secondary px-3 py-2 text-sm"
|
||||
data-testid="tenant-import-result"
|
||||
>
|
||||
{importMessage}
|
||||
</div>
|
||||
<PageHeader
|
||||
sticky
|
||||
titleAs="h2"
|
||||
icon={<Building2 size={20} />}
|
||||
title={t("ui.admin.tenants.title", "테넌트 목록")}
|
||||
description={t(
|
||||
"msg.admin.tenants.subtitle",
|
||||
"시스템에 등록된 모든 테넌트를 평면 목록으로 확인하고 관리합니다.",
|
||||
)}
|
||||
</header>
|
||||
actions={
|
||||
<>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="relative mr-2 w-64">
|
||||
<Search className="absolute left-2.5 top-2.5 h-4 w-4 text-muted-foreground" />
|
||||
<Input
|
||||
placeholder={t(
|
||||
"ui.admin.tenants.list.search_placeholder",
|
||||
"테넌트 이름 또는 슬러그 검색...",
|
||||
)}
|
||||
className="h-9 pl-9"
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<RoleGuard roles={["super_admin"]}>
|
||||
<input
|
||||
ref={fileInputRef}
|
||||
type="file"
|
||||
accept=".csv,text/csv"
|
||||
className="hidden"
|
||||
data-testid="tenant-import-input"
|
||||
onChange={handleImportFile}
|
||||
/>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
data-testid="tenant-data-mgmt-btn"
|
||||
className="gap-2"
|
||||
>
|
||||
<LayoutDashboard size={16} />
|
||||
{t("ui.admin.tenants.data_mgmt", "데이터 관리")}
|
||||
<ChevronDown size={14} className="opacity-50" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end" className="w-56">
|
||||
<DropdownMenuItem
|
||||
onClick={handleTemplateDownload}
|
||||
data-testid="tenant-template-menu-item"
|
||||
className="cursor-pointer"
|
||||
>
|
||||
<FileSpreadsheet size={16} className="mr-2 opacity-50" />
|
||||
{t("ui.admin.tenants.csv_template", "템플릿 다운로드")}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem
|
||||
onClick={() => fileInputRef.current?.click()}
|
||||
disabled={importMutation.isPending}
|
||||
data-testid="tenant-import-menu-item"
|
||||
className="cursor-pointer"
|
||||
>
|
||||
<Upload size={16} className="mr-2 opacity-50" />
|
||||
{t("ui.admin.tenants.import", "CSV 가져오기")}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem
|
||||
onClick={() => exportMutation.mutate(false)}
|
||||
disabled={exportMutation.isPending}
|
||||
data-testid="tenant-export-menu-item"
|
||||
className="cursor-pointer"
|
||||
>
|
||||
<Download size={16} className="mr-2 opacity-50" />
|
||||
{t(
|
||||
"ui.admin.tenants.export_without_ids",
|
||||
"UUID 제외 내보내기",
|
||||
)}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
onClick={() => exportMutation.mutate(true)}
|
||||
disabled={exportMutation.isPending}
|
||||
data-testid="tenant-export-with-ids-menu-item"
|
||||
className="cursor-pointer"
|
||||
>
|
||||
<Download size={16} className="mr-2 opacity-50" />
|
||||
{t(
|
||||
"ui.admin.tenants.export_with_ids",
|
||||
"UUID 포함 내보내기",
|
||||
)}
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</RoleGuard>
|
||||
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => query.refetch()}
|
||||
disabled={query.isFetching}
|
||||
className="w-9 px-0"
|
||||
title={t("ui.common.refresh", "새로고침")}
|
||||
>
|
||||
<RefreshCw size={16} />
|
||||
<span className="sr-only">
|
||||
{t("ui.common.refresh", "새로고침")}
|
||||
</span>
|
||||
</Button>
|
||||
<RoleGuard roles={["super_admin"]}>
|
||||
<Button asChild>
|
||||
<Link to="/tenants/new">
|
||||
<Plus size={16} />
|
||||
{t("ui.admin.tenants.add", "테넌트 추가")}
|
||||
</Link>
|
||||
</Button>
|
||||
</RoleGuard>
|
||||
</div>
|
||||
{importMessage ? (
|
||||
<div
|
||||
className="rounded-md border border-border bg-secondary px-3 py-2 text-sm"
|
||||
data-testid="tenant-import-result"
|
||||
>
|
||||
{importMessage}
|
||||
</div>
|
||||
) : null}
|
||||
</>
|
||||
}
|
||||
/>
|
||||
|
||||
<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 flex-shrink-0">
|
||||
|
||||
Reference in New Issue
Block a user