forked from baron/baron-sso
tenants 레지스트리 가독성/로케일 적용
This commit is contained in:
@@ -69,7 +69,6 @@ import {
|
|||||||
SelectTrigger,
|
SelectTrigger,
|
||||||
SelectValue,
|
SelectValue,
|
||||||
} from "../../../components/ui/select";
|
} from "../../../components/ui/select";
|
||||||
import { Switch } from "../../../components/ui/switch";
|
|
||||||
import {
|
import {
|
||||||
Table,
|
Table,
|
||||||
TableBody,
|
TableBody,
|
||||||
@@ -80,7 +79,6 @@ import {
|
|||||||
} from "../../../components/ui/table";
|
} from "../../../components/ui/table";
|
||||||
import { Tabs, TabsList, TabsTrigger } from "../../../components/ui/tabs";
|
import { Tabs, TabsList, TabsTrigger } from "../../../components/ui/tabs";
|
||||||
import { toast } from "../../../components/ui/use-toast";
|
import { toast } from "../../../components/ui/use-toast";
|
||||||
import type { UserProfileResponse } from "../../../lib/adminApi";
|
|
||||||
import {
|
import {
|
||||||
deleteTenantsBulk,
|
deleteTenantsBulk,
|
||||||
exportTenantsCSV,
|
exportTenantsCSV,
|
||||||
@@ -142,6 +140,51 @@ const getTenantIcon = (type?: string) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function getTenantTypeLabel(type?: string) {
|
||||||
|
if (!type) return "-";
|
||||||
|
return t(`domain.tenant_type.${type.toLowerCase()}`, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTenantTypeTextClass(type?: string) {
|
||||||
|
switch (type?.toUpperCase()) {
|
||||||
|
case "COMPANY_GROUP":
|
||||||
|
return "text-sky-700";
|
||||||
|
case "COMPANY":
|
||||||
|
return "text-violet-700";
|
||||||
|
case "ORGANIZATION":
|
||||||
|
return "text-emerald-700";
|
||||||
|
case "USER_GROUP":
|
||||||
|
return "text-amber-700";
|
||||||
|
case "PERSONAL":
|
||||||
|
return "text-slate-700";
|
||||||
|
default:
|
||||||
|
return "text-muted-foreground";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildTenantParentPathMap(tenants: TenantSummary[]) {
|
||||||
|
const tenantById = new Map(tenants.map((tenant) => [tenant.id, tenant]));
|
||||||
|
const pathMap = new Map<string, string[]>();
|
||||||
|
|
||||||
|
for (const tenant of tenants) {
|
||||||
|
const names: string[] = [];
|
||||||
|
const visited = new Set<string>();
|
||||||
|
let currentParentId = tenant.parentId;
|
||||||
|
|
||||||
|
while (currentParentId && !visited.has(currentParentId)) {
|
||||||
|
visited.add(currentParentId);
|
||||||
|
const parent = tenantById.get(currentParentId);
|
||||||
|
if (!parent) break;
|
||||||
|
names.unshift(parent.name);
|
||||||
|
currentParentId = parent.parentId;
|
||||||
|
}
|
||||||
|
|
||||||
|
pathMap.set(tenant.id, names);
|
||||||
|
}
|
||||||
|
|
||||||
|
return pathMap;
|
||||||
|
}
|
||||||
|
|
||||||
const noImportParentRef = "__none__";
|
const noImportParentRef = "__none__";
|
||||||
|
|
||||||
function tenantParentRef(tenantId: string) {
|
function tenantParentRef(tenantId: string) {
|
||||||
@@ -339,19 +382,6 @@ function TenantListPage() {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const statusMutation = useMutation({
|
|
||||||
mutationFn: ({ tenantId, status }: { tenantId: string; status: string }) =>
|
|
||||||
updateTenant(tenantId, { status }),
|
|
||||||
onSuccess: () => {
|
|
||||||
query.refetch();
|
|
||||||
},
|
|
||||||
onError: () => {
|
|
||||||
toast.error(
|
|
||||||
t("msg.admin.tenants.status_error", "테넌트 상태 변경에 실패했습니다."),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const bulkUpdateStatusMutation = useMutation({
|
const bulkUpdateStatusMutation = useMutation({
|
||||||
mutationFn: async ({
|
mutationFn: async ({
|
||||||
tenantIds,
|
tenantIds,
|
||||||
@@ -935,8 +965,6 @@ function TenantListPage() {
|
|||||||
onSelectAll={handleSelectAll}
|
onSelectAll={handleSelectAll}
|
||||||
search={search}
|
search={search}
|
||||||
deletableTenants={deletableTenants}
|
deletableTenants={deletableTenants}
|
||||||
statusMutation={statusMutation}
|
|
||||||
profile={profile}
|
|
||||||
sortConfig={sortConfig}
|
sortConfig={sortConfig}
|
||||||
requestSort={requestSort}
|
requestSort={requestSort}
|
||||||
getSortIcon={getSortIcon}
|
getSortIcon={getSortIcon}
|
||||||
@@ -1513,13 +1541,6 @@ const TenantHierarchyView: React.FC<{
|
|||||||
onSelectAll: (checked: boolean) => void;
|
onSelectAll: (checked: boolean) => void;
|
||||||
search: string;
|
search: string;
|
||||||
deletableTenants: TenantSummary[];
|
deletableTenants: TenantSummary[];
|
||||||
statusMutation: UseMutationResult<
|
|
||||||
TenantSummary,
|
|
||||||
Error,
|
|
||||||
{ tenantId: string; status: string },
|
|
||||||
unknown
|
|
||||||
>;
|
|
||||||
profile: UserProfileResponse | undefined;
|
|
||||||
sortConfig: SortConfig<TenantSortKey> | null;
|
sortConfig: SortConfig<TenantSortKey> | null;
|
||||||
requestSort: (key: TenantSortKey) => void;
|
requestSort: (key: TenantSortKey) => void;
|
||||||
getSortIcon: (key: TenantSortKey) => React.ReactNode;
|
getSortIcon: (key: TenantSortKey) => React.ReactNode;
|
||||||
@@ -1536,8 +1557,6 @@ const TenantHierarchyView: React.FC<{
|
|||||||
onSelectAll,
|
onSelectAll,
|
||||||
search,
|
search,
|
||||||
deletableTenants,
|
deletableTenants,
|
||||||
statusMutation,
|
|
||||||
profile,
|
|
||||||
sortConfig,
|
sortConfig,
|
||||||
requestSort,
|
requestSort,
|
||||||
getSortIcon,
|
getSortIcon,
|
||||||
@@ -1558,6 +1577,10 @@ const TenantHierarchyView: React.FC<{
|
|||||||
() => buildTenantFullTree(tenants, scopeTenantId || undefined, !!search),
|
() => buildTenantFullTree(tenants, scopeTenantId || undefined, !!search),
|
||||||
[scopeTenantId, tenants, search],
|
[scopeTenantId, tenants, search],
|
||||||
);
|
);
|
||||||
|
const tenantParentPathMap = React.useMemo(
|
||||||
|
() => buildTenantParentPathMap(tenants),
|
||||||
|
[tenants],
|
||||||
|
);
|
||||||
|
|
||||||
// Initial expanded state: everything open
|
// Initial expanded state: everything open
|
||||||
const [expandedIds, setExpandedIds] = React.useState<Set<string>>(() => {
|
const [expandedIds, setExpandedIds] = React.useState<Set<string>>(() => {
|
||||||
@@ -1682,6 +1705,22 @@ const TenantHierarchyView: React.FC<{
|
|||||||
const visibleSelectedCount = selectedIds.filter((id) =>
|
const visibleSelectedCount = selectedIds.filter((id) =>
|
||||||
visibleSelectableIds.has(id),
|
visibleSelectableIds.has(id),
|
||||||
).length;
|
).length;
|
||||||
|
const normalizedSearch = search.trim();
|
||||||
|
const emptyMessage = React.useMemo(() => {
|
||||||
|
if (normalizedSearch) {
|
||||||
|
return t(
|
||||||
|
"msg.admin.tenants.empty_search",
|
||||||
|
"검색 조건에 맞는 테넌트가 없습니다.",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (scopeTenantId) {
|
||||||
|
return t(
|
||||||
|
"msg.admin.tenants.empty_scope",
|
||||||
|
"선택한 범위에 표시할 하위 테넌트가 없습니다.",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return t("msg.admin.tenants.empty", "아직 등록된 테넌트가 없습니다.");
|
||||||
|
}, [normalizedSearch, scopeTenantId]);
|
||||||
|
|
||||||
const renderRow = (
|
const renderRow = (
|
||||||
node: TenantViewRow,
|
node: TenantViewRow,
|
||||||
@@ -1923,10 +1962,7 @@ const TenantHierarchyView: React.FC<{
|
|||||||
colSpan={8}
|
colSpan={8}
|
||||||
className="py-8 text-center text-muted-foreground"
|
className="py-8 text-center text-muted-foreground"
|
||||||
>
|
>
|
||||||
{t(
|
{emptyMessage}
|
||||||
"msg.admin.tenants.empty",
|
|
||||||
"아직 등록된 테넌트가 없습니다.",
|
|
||||||
)}
|
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1071,6 +1071,7 @@ user = "General User (Tenant Member)"
|
|||||||
[ui.admin.tenants]
|
[ui.admin.tenants]
|
||||||
add = "Add Tenant"
|
add = "Add Tenant"
|
||||||
csv_template = "Template"
|
csv_template = "Template"
|
||||||
|
data_mgmt = "Data Management"
|
||||||
delete_selected = "Delete Selected"
|
delete_selected = "Delete Selected"
|
||||||
export_with_ids = "Include UUIDs"
|
export_with_ids = "Include UUIDs"
|
||||||
export_without_ids = "Export without UUIDs"
|
export_without_ids = "Export without UUIDs"
|
||||||
@@ -1267,6 +1268,15 @@ name = "NAME"
|
|||||||
slug = "SLUG"
|
slug = "SLUG"
|
||||||
status = "STATUS"
|
status = "STATUS"
|
||||||
|
|
||||||
|
[ui.admin.tenants.view]
|
||||||
|
list = "List"
|
||||||
|
table = "Table"
|
||||||
|
tree = "Tree"
|
||||||
|
|
||||||
|
[ui.admin.tenants.scope]
|
||||||
|
active = "{{name}} descendants"
|
||||||
|
pick = "Select parent scope"
|
||||||
|
|
||||||
[ui.admin.tenants.table]
|
[ui.admin.tenants.table]
|
||||||
actions = "ACTIONS"
|
actions = "ACTIONS"
|
||||||
id = "ID"
|
id = "ID"
|
||||||
|
|||||||
@@ -1074,6 +1074,7 @@ user = "일반 사용자 (Tenant Member)"
|
|||||||
[ui.admin.tenants]
|
[ui.admin.tenants]
|
||||||
add = "테넌트 추가"
|
add = "테넌트 추가"
|
||||||
csv_template = "템플릿"
|
csv_template = "템플릿"
|
||||||
|
data_mgmt = "데이터 관리"
|
||||||
delete_selected = "선택 삭제"
|
delete_selected = "선택 삭제"
|
||||||
export_with_ids = "UUID 포함"
|
export_with_ids = "UUID 포함"
|
||||||
export_without_ids = "UUID 제외 내보내기"
|
export_without_ids = "UUID 제외 내보내기"
|
||||||
@@ -1270,15 +1271,24 @@ name = "NAME"
|
|||||||
slug = "SLUG"
|
slug = "SLUG"
|
||||||
status = "STATUS"
|
status = "STATUS"
|
||||||
|
|
||||||
|
[ui.admin.tenants.view]
|
||||||
|
list = "평면 목록"
|
||||||
|
table = "평면"
|
||||||
|
tree = "트리"
|
||||||
|
|
||||||
|
[ui.admin.tenants.scope]
|
||||||
|
active = "{{name}} 하위"
|
||||||
|
pick = "상위 범위 선택"
|
||||||
|
|
||||||
[ui.admin.tenants.table]
|
[ui.admin.tenants.table]
|
||||||
actions = "ACTIONS"
|
actions = "ACTIONS"
|
||||||
id = "ID"
|
id = "ID"
|
||||||
members = "멤버수"
|
members = "멤버수"
|
||||||
name = "NAME"
|
name = "이름"
|
||||||
slug = "SLUG"
|
slug = "슬러그"
|
||||||
status = "STATUS"
|
status = "상태"
|
||||||
type = "유형"
|
type = "유형"
|
||||||
updated = "UPDATED"
|
updated = "수정일"
|
||||||
|
|
||||||
[ui.admin.users]
|
[ui.admin.users]
|
||||||
csv_template = "템플릿 다운로드"
|
csv_template = "템플릿 다운로드"
|
||||||
|
|||||||
@@ -264,6 +264,15 @@ subtitle = "List of owners with top-level permissions for this tenant."
|
|||||||
|
|
||||||
[msg.admin.tenants.registry]
|
[msg.admin.tenants.registry]
|
||||||
count = "{{count}} tenants loaded."
|
count = "{{count}} tenants loaded."
|
||||||
|
scope_results = "{{count}} tenants under {{name}}"
|
||||||
|
scope_search_results = "{{count}} search results under {{name}}"
|
||||||
|
search_results = "{{count}} search results"
|
||||||
|
table_hint = "Compare IDs, status, and size quickly in the sortable flat list."
|
||||||
|
tree_hint = "Review parent-child relationships and subtree coverage in the hierarchy."
|
||||||
|
|
||||||
|
[msg.admin.tenants]
|
||||||
|
empty_scope = "There are no child tenants to display in the selected scope."
|
||||||
|
empty_search = "No tenants match the current search."
|
||||||
|
|
||||||
[msg.admin.tenants.schema]
|
[msg.admin.tenants.schema]
|
||||||
empty = "No custom fields defined. Click \\\\\\\"Add Field\\\\\\\" to begin."
|
empty = "No custom fields defined. Click \\\\\\\"Add Field\\\\\\\" to begin."
|
||||||
@@ -1157,6 +1166,7 @@ user = "TENANT MEMBER"
|
|||||||
[ui.admin.tenants]
|
[ui.admin.tenants]
|
||||||
add = "Add Tenant"
|
add = "Add Tenant"
|
||||||
csv_template = "Template"
|
csv_template = "Template"
|
||||||
|
data_mgmt = "Data Management"
|
||||||
delete_selected = "Delete Selected"
|
delete_selected = "Delete Selected"
|
||||||
export_with_ids = "Include UUIDs"
|
export_with_ids = "Include UUIDs"
|
||||||
export_without_ids = "Export without UUIDs"
|
export_without_ids = "Export without UUIDs"
|
||||||
@@ -1441,9 +1451,14 @@ status = "STATUS"
|
|||||||
|
|
||||||
[ui.admin.tenants.table]
|
[ui.admin.tenants.table]
|
||||||
actions = "ACTIONS"
|
actions = "ACTIONS"
|
||||||
|
context = "Parent Path"
|
||||||
id = "ID"
|
id = "ID"
|
||||||
|
id_copy = "Copy ID"
|
||||||
members = "Members"
|
members = "Members"
|
||||||
|
members_count = "{{count}} members"
|
||||||
|
members_recursive = "including descendants"
|
||||||
name = "NAME"
|
name = "NAME"
|
||||||
|
root = "Top Level"
|
||||||
slug = "SLUG"
|
slug = "SLUG"
|
||||||
status = "STATUS"
|
status = "STATUS"
|
||||||
type = "TYPE"
|
type = "TYPE"
|
||||||
|
|||||||
@@ -765,6 +765,15 @@ subtitle = "이 테넌트의 최상위 권한을 가진 소유자(조직장) 목
|
|||||||
|
|
||||||
[msg.admin.tenants.registry]
|
[msg.admin.tenants.registry]
|
||||||
count = "총 {{count}}개 테넌트"
|
count = "총 {{count}}개 테넌트"
|
||||||
|
scope_results = "{{name}} 하위 {{count}}개"
|
||||||
|
scope_search_results = "{{name}} 하위 검색 결과 {{count}}개"
|
||||||
|
search_results = "검색 결과 {{count}}개"
|
||||||
|
table_hint = "정렬 가능한 평면 목록에서 ID, 상태, 규모를 빠르게 비교합니다."
|
||||||
|
tree_hint = "계층 구조를 따라 부모-자식 관계와 하위 범위를 함께 확인합니다."
|
||||||
|
|
||||||
|
[msg.admin.tenants]
|
||||||
|
empty_scope = "선택한 범위에 표시할 하위 테넌트가 없습니다."
|
||||||
|
empty_search = "검색 조건에 맞는 테넌트가 없습니다."
|
||||||
|
|
||||||
[msg.admin.tenants.schema]
|
[msg.admin.tenants.schema]
|
||||||
empty = "등록된 커스텀 필드가 없습니다. 필드 추가를 눌러 시작하세요."
|
empty = "등록된 커스텀 필드가 없습니다. 필드 추가를 눌러 시작하세요."
|
||||||
@@ -1652,6 +1661,7 @@ user = "TENANT MEMBER"
|
|||||||
|
|
||||||
[ui.admin.tenants]
|
[ui.admin.tenants]
|
||||||
add = "테넌트 추가"
|
add = "테넌트 추가"
|
||||||
|
data_mgmt = "데이터 관리"
|
||||||
delete_selected = "선택 삭제"
|
delete_selected = "선택 삭제"
|
||||||
seed_badge = "초기 설정"
|
seed_badge = "초기 설정"
|
||||||
title = "테넌트 목록"
|
title = "테넌트 목록"
|
||||||
@@ -1904,13 +1914,18 @@ status = "STATUS"
|
|||||||
|
|
||||||
[ui.admin.tenants.table]
|
[ui.admin.tenants.table]
|
||||||
actions = "ACTIONS"
|
actions = "ACTIONS"
|
||||||
|
context = "상위 경로"
|
||||||
id = "ID"
|
id = "ID"
|
||||||
|
id_copy = "ID 복사"
|
||||||
members = "멤버수"
|
members = "멤버수"
|
||||||
name = "NAME"
|
members_count = "{{count}}명"
|
||||||
slug = "SLUG"
|
members_recursive = "하위 포함"
|
||||||
status = "STATUS"
|
name = "이름"
|
||||||
|
root = "최상위"
|
||||||
|
slug = "슬러그"
|
||||||
|
status = "상태"
|
||||||
type = "유형"
|
type = "유형"
|
||||||
updated = "UPDATED"
|
updated = "수정일"
|
||||||
created = "CREATED"
|
created = "CREATED"
|
||||||
created = "CREATED"
|
created = "CREATED"
|
||||||
|
|
||||||
|
|||||||
@@ -622,6 +622,15 @@ subtitle = ""
|
|||||||
|
|
||||||
[msg.admin.tenants.registry]
|
[msg.admin.tenants.registry]
|
||||||
count = ""
|
count = ""
|
||||||
|
scope_results = ""
|
||||||
|
scope_search_results = ""
|
||||||
|
search_results = ""
|
||||||
|
table_hint = ""
|
||||||
|
tree_hint = ""
|
||||||
|
|
||||||
|
[msg.admin.tenants]
|
||||||
|
empty_scope = ""
|
||||||
|
empty_search = ""
|
||||||
|
|
||||||
[msg.admin.tenants.schema]
|
[msg.admin.tenants.schema]
|
||||||
empty = ""
|
empty = ""
|
||||||
@@ -1781,9 +1790,14 @@ status = ""
|
|||||||
|
|
||||||
[ui.admin.tenants.table]
|
[ui.admin.tenants.table]
|
||||||
actions = ""
|
actions = ""
|
||||||
|
context = ""
|
||||||
id = ""
|
id = ""
|
||||||
|
id_copy = ""
|
||||||
members = ""
|
members = ""
|
||||||
|
members_count = ""
|
||||||
|
members_recursive = ""
|
||||||
name = ""
|
name = ""
|
||||||
|
root = ""
|
||||||
slug = ""
|
slug = ""
|
||||||
status = ""
|
status = ""
|
||||||
type = ""
|
type = ""
|
||||||
|
|||||||
Reference in New Issue
Block a user