{t(
"msg.admin.tenants.schema.empty",
- 'No custom fields defined. Click "Add Field" to begin.',
+ '정의된 커스텀 필드가 없습니다. "필드 추가"를 눌러 시작하세요.',
)}
-
-
- {t("ui.admin.tenants.schema.field.label", "Display Label")}
+
+ {t("ui.admin.tenants.schema.field.label", "표시 라벨")}
-
-
{t("ui.admin.tenants.schema.field.type", "Type")}
+
+
+ {t("ui.admin.tenants.schema.field.type", "유형")}
+
))}
-
+
diff --git a/adminfront/src/features/user-groups/routes/GlobalUserGroupListPage.tsx b/adminfront/src/features/user-groups/routes/GlobalUserGroupListPage.tsx
deleted file mode 100644
index 193578ee..00000000
--- a/adminfront/src/features/user-groups/routes/GlobalUserGroupListPage.tsx
+++ /dev/null
@@ -1,141 +0,0 @@
-import { useQuery } from "@tanstack/react-query";
-import { Building2, Plus, Users } from "lucide-react";
-import { useState } from "react";
-import { Link } from "react-router-dom";
-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 { fetchGroups, fetchTenants } from "../../../lib/adminApi";
-
-export default function GlobalUserGroupListPage() {
- const { data: tenantList, isLoading: isTenantsLoading } = useQuery({
- queryKey: ["admin-tenants"],
- queryFn: () => fetchTenants(100, 0),
- });
-
- if (isTenantsLoading)
- return
Loading tenants and groups...
;
-
- return (
-
-
-
-
- {tenantList?.items.map((tenant) => (
-
- ))}
-
-
- );
-}
-
-function TenantGroupCard({ tenant }: { tenant: any }) {
- const { data: groups, isLoading } = useQuery({
- queryKey: ["tenant-user-groups", tenant.id],
- queryFn: () => fetchGroups(tenant.id),
- });
-
- return (
-
-
-
-
-
- {tenant.name}
-
- {tenant.slug}
-
-
-
- 이 테넌트에 정의된 유저 그룹 목록입니다.
-
-
-
-
-
-
-
-
- 그룹명
- 설명
- 멤버 수
- 작업
-
-
-
- {isLoading ? (
-
-
- Loading...
-
-
- ) : groups?.length === 0 ? (
-
-
- 등록된 유저 그룹이 없습니다.
-
-
- ) : (
- groups?.map((group) => (
-
-
-
-
-
- {group.name}
-
-
-
- {group.description || "-"}
- {group.members?.length || 0} 명
-
-
-
-
- ))
- )}
-
-
-
-
- );
-}
diff --git a/adminfront/src/features/user-groups/routes/TenantUserGroupsTab.tsx b/adminfront/src/features/user-groups/routes/TenantUserGroupsTab.tsx
index 2f980a25..68aaec1c 100644
--- a/adminfront/src/features/user-groups/routes/TenantUserGroupsTab.tsx
+++ b/adminfront/src/features/user-groups/routes/TenantUserGroupsTab.tsx
@@ -1,7 +1,9 @@
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
-import { Plus, Trash2, Users } from "lucide-react";
-import { useState } from "react";
+import { Plus, Trash2, Upload, Users } from "lucide-react";
+import { useRef, useState } from "react";
import { Link, useParams } from "react-router-dom";
+import { toast } from "sonner";
+import { Badge } from "../../../components/ui/badge";
import { Button } from "../../../components/ui/button";
import {
Card,
@@ -29,14 +31,23 @@ import {
TableHeader,
TableRow,
} from "../../../components/ui/table";
-import { createGroup, deleteGroup, fetchGroups } from "../../../lib/adminApi";
+import {
+ createGroup,
+ deleteGroup,
+ fetchGroups,
+ importOrgChart,
+} from "../../../lib/adminApi";
+import { t } from "../../../lib/i18n";
export function TenantUserGroupsTab() {
const { tenantId } = useParams<{ tenantId: string }>();
const queryClient = useQueryClient();
+ const fileInputRef = useRef
(null);
const [isCreateOpen, setIsCreateOpen] = useState(false);
const [newGroupName, setNewGroupName] = useState("");
const [newGroupDesc, setNewGroupDesc] = useState("");
+ const [newParentId, setNewParentId] = useState("");
+ const [newUnitType, setNewUnitType] = useState("");
const { data: groups, isLoading } = useQuery({
queryKey: ["tenant-user-groups", tenantId],
@@ -46,7 +57,12 @@ export function TenantUserGroupsTab() {
const createMutation = useMutation({
mutationFn: () =>
- createGroup(tenantId!, { name: newGroupName, description: newGroupDesc }),
+ createGroup(tenantId!, {
+ name: newGroupName,
+ description: newGroupDesc,
+ parentId: newParentId || undefined,
+ unitType: newUnitType || undefined,
+ }),
onSuccess: () => {
queryClient.invalidateQueries({
queryKey: ["tenant-user-groups", tenantId],
@@ -54,10 +70,25 @@ export function TenantUserGroupsTab() {
setIsCreateOpen(false);
setNewGroupName("");
setNewGroupDesc("");
- alert("User group created successfully");
+ setNewParentId("");
+ setNewUnitType("");
+ toast.success(t("msg.admin.groups.list.create_success", "조직 단위가 생성되었습니다."));
},
onError: (error: any) => {
- alert(error.message || "Failed to create user group");
+ toast.error(t("msg.admin.groups.list.create_error", { error: error.message }));
+ },
+ });
+
+ const importMutation = useMutation({
+ mutationFn: (file: File) => importOrgChart(tenantId!, file),
+ onSuccess: () => {
+ queryClient.invalidateQueries({
+ queryKey: ["tenant-user-groups", tenantId],
+ });
+ toast.success(t("msg.admin.groups.list.import_success", "조직도가 임포트되었습니다."));
+ },
+ onError: (error: any) => {
+ toast.error(t("msg.admin.groups.list.import_error", { error: error.message }));
},
});
@@ -67,137 +98,204 @@ export function TenantUserGroupsTab() {
queryClient.invalidateQueries({
queryKey: ["tenant-user-groups", tenantId],
});
- alert("User group deleted successfully");
+ toast.success(t("msg.admin.groups.list.delete_success", "조직 단위가 삭제되었습니다."));
},
});
- if (isLoading) return Loading user groups...
;
+ const handleFileChange = (e: React.ChangeEvent) => {
+ const file = e.target.files?.[0];
+ if (file) {
+ importMutation.mutate(file);
+ }
+ };
+
+ if (isLoading) return {t("msg.admin.groups.list.loading", "로딩 중...")}
;
return (
-
-
+
+
-
-
User Groups
+
+
+ {t("ui.admin.groups.list.title", "조직 관리")}
+
- Manage user groups within this tenant for collective permission
- assignment.
+ {t("msg.admin.groups.list.subtitle", "이 테넌트에 정의된 조직 단위들을 관리합니다.")}
-
diff --git a/adminfront/src/features/user-groups/routes/UserGroupDetailPage.tsx b/adminfront/src/features/user-groups/routes/UserGroupDetailPage.tsx
index 0ecc7b72..a68ea73a 100644
--- a/adminfront/src/features/user-groups/routes/UserGroupDetailPage.tsx
+++ b/adminfront/src/features/user-groups/routes/UserGroupDetailPage.tsx
@@ -2,6 +2,7 @@ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { ArrowLeft, Plus, Shield, Trash2, UserPlus, Users } from "lucide-react";
import { useState } from "react";
import { Link, useParams } from "react-router-dom";
+import { toast } from "sonner";
import { Badge } from "../../../components/ui/badge";
import { Button } from "../../../components/ui/button";
import {
@@ -47,6 +48,7 @@ import {
removeGroupMember,
removeGroupRole,
} from "../../../lib/adminApi";
+import { t } from "../../../lib/i18n";
export function UserGroupDetailPage() {
const { tenantId, id } = useParams<{ tenantId: string; id: string }>();
@@ -99,10 +101,10 @@ export function UserGroupDetailPage() {
queryClient.invalidateQueries({ queryKey: ["user-group-detail", id] });
setIsAddMemberOpen(false);
setSelectedUserId("");
- alert("Member added successfully");
+ toast.success(t("msg.admin.groups.members.add_success", "구성원이 추가되었습니다."));
},
onError: (error: any) => {
- alert(error.message || "Failed to add member");
+ toast.error(error.message || t("err.common.unknown", "오류가 발생했습니다."));
},
});
@@ -110,7 +112,7 @@ export function UserGroupDetailPage() {
mutationFn: (userId: string) => removeGroupMember(tenantId!, id!, userId),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["user-group-detail", id] });
- alert("Member removed successfully");
+ toast.success(t("msg.admin.groups.members.remove_success", "구성원이 제외되었습니다."));
},
});
@@ -120,10 +122,10 @@ export function UserGroupDetailPage() {
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["user-group-roles", id] });
setIsAddRoleOpen(false);
- alert(`Role '${selectedRelation}' assigned successfully`);
+ toast.success(t("msg.admin.groups.roles.assign_success", "역할이 할당되었습니다."));
},
onError: (error: any) => {
- alert(error.message || "Failed to assign role");
+ toast.error(error.message || t("err.common.unknown", "오류가 발생했습니다."));
},
});
@@ -132,7 +134,7 @@ export function UserGroupDetailPage() {
removeGroupRole(tenantId!, id!, role.targetTenantId, role.relation),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["user-group-roles", id] });
- alert("Role removed successfully");
+ toast.success(t("msg.admin.groups.roles.remove_success", "역할이 회수되었습니다."));
},
});
@@ -141,7 +143,7 @@ export function UserGroupDetailPage() {
- Loading group details...
+ {t("msg.common.loading", "로딩 중...")}
);
@@ -150,32 +152,25 @@ export function UserGroupDetailPage() {
return (
- Could not load group
+ 조직 단위를 불러올 수 없습니다
-
+
Error:{" "}
{(error as any)?.response?.data?.error ||
(error as any)?.message ||
"Not found"}
-
- Path: /admin/tenants/{tenantId}/user-groups/{id}
-
-
- The group ID might be invalid or you don't have sufficient
- permissions.
-
window.location.reload()}>
- Retry
+ {t("ui.common.retry", "다시 시도")}
- Return to Group List
+ {t("ui.admin.groups.detail.breadcrumb_org", "조직 관리 목록으로 돌아가기")}
@@ -185,72 +180,84 @@ export function UserGroupDetailPage() {
-
+
- Tenant Detail
+ {t("ui.admin.groups.detail.breadcrumb_tenant", "테넌트 상세")}
/
-
User Group
+
+ {t("ui.admin.groups.detail.breadcrumb_org", "조직 관리")}
+
+
/
+
{t("ui.admin.groups.detail.breadcrumb_unit", "조직 단위")}
{currentGroup.name}
+ {currentGroup.unitType && (
+
+ {currentGroup.unitType}
+
+ )}
-
- {currentGroup.description || "No description provided."}
+
+ {currentGroup.description || t("msg.common.no_description", "설명이 없습니다.")}
- User Group
- Tenant: {tenantId?.split("-")[0]}...
+ {t("ui.admin.groups.detail.breadcrumb_unit", "조직 단위")}
+ ID: {id?.split("-")[0]}...
{/* Members Management */}
-
+
- Members
- Manage users in this group.
+ {t("ui.admin.groups.detail.members_title", "구성원 관리")}
+ {t("ui.admin.groups.detail.members_subtitle", "이 조직에 소속된 사용자를 관리합니다.")}
+
+
{userSchema.length > 0 && (
diff --git a/adminfront/src/features/users/UserDetailPage.tsx b/adminfront/src/features/users/UserDetailPage.tsx
index e192b7e0..d660dbd1 100644
--- a/adminfront/src/features/users/UserDetailPage.tsx
+++ b/adminfront/src/features/users/UserDetailPage.tsx
@@ -70,6 +70,8 @@ function UserDetailPage() {
status: "active",
companyCode: "",
department: "",
+ position: "",
+ jobTitle: "",
password: "",
metadata: {},
},
@@ -104,6 +106,8 @@ function UserDetailPage() {
status: user.status,
companyCode: user.companyCode || "",
department: user.department || "",
+ position: user.position || "",
+ jobTitle: user.jobTitle || "",
password: "",
metadata: user.metadata || {},
});
@@ -337,6 +341,38 @@ function UserDetailPage() {
+
+
{userSchema.length > 0 && (
diff --git a/adminfront/src/features/users/UserListPage.tsx b/adminfront/src/features/users/UserListPage.tsx
index 2db0b794..6c06bf2e 100644
--- a/adminfront/src/features/users/UserListPage.tsx
+++ b/adminfront/src/features/users/UserListPage.tsx
@@ -199,6 +199,9 @@ function UserListPage() {
"TENANT / DEPT",
)}
+
+ {t("ui.admin.users.list.table.position_job", "POSITION / JOB")}
+
{t("ui.admin.users.list.table.created", "CREATED")}
@@ -272,6 +275,14 @@ function UserListPage() {
+
+
+ {user.position || "-"}
+
+ {user.jobTitle || "-"}
+
+
+
{new Date(user.createdAt).toLocaleDateString()}
diff --git a/adminfront/src/lib/adminApi.ts b/adminfront/src/lib/adminApi.ts
index f9e0135c..a1df652f 100644
--- a/adminfront/src/lib/adminApi.ts
+++ b/adminfront/src/lib/adminApi.ts
@@ -21,6 +21,7 @@ export type AuditLogListResponse = {
export type TenantSummary = {
id: string;
+ type: string; // PERSONAL, COMPANY, COMPANY_GROUP, USER_GROUP
name: string;
slug: string;
description: string;
@@ -33,6 +34,7 @@ export type TenantSummary = {
export type TenantCreateRequest = {
name: string;
+ type?: string;
slug?: string;
description?: string;
status?: string;
@@ -49,6 +51,7 @@ export type TenantListResponse = {
export type TenantUpdateRequest = {
name?: string;
+ type?: string;
slug?: string;
description?: string;
status?: string;
@@ -170,8 +173,10 @@ export type GroupMember = {
export type GroupSummary = {
id: string;
tenantId: string;
+ parentId?: string;
name: string;
description?: string;
+ unitType?: string;
members?: GroupMember[];
createdAt?: string;
updatedAt?: string;
@@ -179,19 +184,21 @@ export type GroupSummary = {
export type GroupCreateRequest = {
name: string;
+ parentId?: string;
description?: string;
+ unitType?: string;
};
export async function fetchGroups(tenantId: string) {
const { data } = await apiClient.get(
- `/v1/admin/tenants/${tenantId}/user-groups`,
+ `/v1/admin/tenants/${tenantId}/organization`,
);
return data;
}
export async function fetchGroup(tenantId: string, groupId: string) {
const { data } = await apiClient.get(
- `/v1/admin/tenants/${tenantId}/user-groups/${groupId}`,
+ `/v1/admin/tenants/${tenantId}/organization/${groupId}`,
);
return data;
}
@@ -201,7 +208,7 @@ export async function createGroup(
payload: GroupCreateRequest,
) {
const { data } = await apiClient.post(
- `/v1/admin/tenants/${tenantId}/user-groups`,
+ `/v1/admin/tenants/${tenantId}/organization`,
payload,
);
return data;
@@ -209,7 +216,7 @@ export async function createGroup(
export async function deleteGroup(tenantId: string, groupId: string) {
await apiClient.delete(
- `/v1/admin/tenants/${tenantId}/user-groups/${groupId}`,
+ `/v1/admin/tenants/${tenantId}/organization/${groupId}`,
);
}
@@ -219,7 +226,7 @@ export async function addGroupMember(
userId: string,
) {
await apiClient.post(
- `/v1/admin/tenants/${tenantId}/user-groups/${groupId}/members`,
+ `/v1/admin/tenants/${tenantId}/organization/${groupId}/members`,
{ userId },
);
}
@@ -230,7 +237,7 @@ export async function removeGroupMember(
userId: string,
) {
await apiClient.delete(
- `/v1/admin/tenants/${tenantId}/user-groups/${groupId}/members/${userId}`,
+ `/v1/admin/tenants/${tenantId}/organization/${groupId}/members/${userId}`,
);
}
@@ -242,7 +249,7 @@ export type GroupRole = {
export async function fetchGroupRoles(tenantId: string, groupId: string) {
const { data } = await apiClient.get(
- `/v1/admin/tenants/${tenantId}/user-groups/${groupId}/roles`,
+ `/v1/admin/tenants/${tenantId}/organization/${groupId}/roles`,
);
return data;
}
@@ -254,7 +261,7 @@ export async function assignGroupRole(
relation: string,
) {
await apiClient.post(
- `/v1/admin/tenants/${tenantId}/user-groups/${groupId}/roles`,
+ `/v1/admin/tenants/${tenantId}/organization/${groupId}/roles`,
{ tenantId: targetTenantId, relation },
);
}
@@ -266,10 +273,25 @@ export async function removeGroupRole(
relation: string,
) {
await apiClient.delete(
- `/v1/admin/tenants/${tenantId}/user-groups/${groupId}/roles/${targetTenantId}/${relation}`,
+ `/v1/admin/tenants/${tenantId}/organization/${groupId}/roles/${targetTenantId}/${relation}`,
);
}
+export async function importOrgChart(tenantId: string, file: File) {
+ const formData = new FormData();
+ formData.append("file", file);
+ const { data } = await apiClient.post(
+ `/v1/admin/tenants/${tenantId}/organization/import`,
+ formData,
+ {
+ headers: {
+ "Content-Type": "multipart/form-data",
+ },
+ },
+ );
+ return data;
+}
+
// API Key Management (M2M)
export type ApiKeyCreateRequest = {
name: string;
@@ -315,6 +337,8 @@ export type UserSummary = {
tenant?: TenantSummary;
metadata?: Record;
department?: string;
+ position?: string;
+ jobTitle?: string;
createdAt: string;
updatedAt: string;
};
@@ -334,6 +358,8 @@ export type UserCreateRequest = {
role?: string;
companyCode?: string;
department?: string;
+ position?: string;
+ jobTitle?: string;
};
export type UserCreateResponse = UserSummary & {
@@ -348,6 +374,8 @@ export type UserUpdateRequest = {
status?: string;
companyCode?: string;
department?: string;
+ position?: string;
+ jobTitle?: string;
};
export async function fetchUsers(limit = 50, offset = 0, search?: string) {
diff --git a/adminfront/src/locales/en.toml b/adminfront/src/locales/en.toml
index 26d7f7ae..a12529d1 100644
--- a/adminfront/src/locales/en.toml
+++ b/adminfront/src/locales/en.toml
@@ -1338,6 +1338,6 @@ logout = "Logout"
overview = "Overview"
relying_parties = "Apps (RP)"
tenant_dashboard = "Tenant Dashboard"
-user_groups = "User Groups"
+user_groups = "Organization"
tenants = "Tenants"
users = "Users"
diff --git a/adminfront/src/locales/ko.toml b/adminfront/src/locales/ko.toml
index 73958bf0..7119000d 100644
--- a/adminfront/src/locales/ko.toml
+++ b/adminfront/src/locales/ko.toml
@@ -5,13 +5,11 @@
affiliate = "가족사 임직원"
general = "일반 사용자"
-[domain.company]
-baron = "바론"
-halla = "한라"
-hanmac = "한맥"
-jangheon = "장헌"
-ptc = "PTC"
-saman = "삼안"
+[domain.tenant_type]
+company = "COMPANY (일반 기업)"
+company_group = "COMPANY_GROUP (그룹사/지주사)"
+personal = "PERSONAL (개인 워크스페이스)"
+user_group = "USER_GROUP (내부 부서/팀)"
[err]
@@ -90,13 +88,34 @@ count = "로드된 로그 {{count}}건"
[msg.admin.groups]
[msg.admin.groups.list]
-subtitle = "이 테넌트에 정의된 사용자 그룹 목록입니다."
+create_success = "조직 단위가 성공적으로 생성되었습니다."
+create_error = "조직 단위 생성에 실패했습니다: {{error}}"
+delete_confirm = "정말로 이 조직 단위를 삭제하시겠습니까?"
+delete_success = "조직 단위가 삭제되었습니다."
+import_success = "조직도가 성공적으로 임포트되었습니다."
+import_error = "조직도 임포트에 실패했습니다: {{error}}"
+loading = "조직 단위를 불러오는 중..."
+subtitle = "이 테넌트에 정의된 조직 단위 목록입니다."
+title = "조직 관리"
[msg.admin.groups.members]
count = "{{count}} 명"
empty = "멤버가 없습니다."
title = "[{{name}}] 멤버 관리"
+[msg.admin.groups.members]
+add_success = "구성원이 추가되었습니다."
+empty = "구성원이 없습니다."
+remove_confirm = "{{name}} 님을 이 조직에서 제외하시겠습니까?"
+remove_success = "구성원이 제외되었습니다."
+
+[msg.admin.groups.roles]
+assign_success = "역할이 성공적으로 할당되었습니다."
+description = "이 조직의 구성원들이 대상 테넌트에서 상속받을 역할을 선택하세요."
+empty = "할당된 역할이 없습니다."
+remove_confirm = "할당된 역할을 회수하시겠습니까?"
+remove_success = "역할이 회수되었습니다."
+
[msg.admin.groups.prompt]
user_id = "추가할 사용자의 UUID를 입력하세요:"
@@ -123,11 +142,37 @@ tenant_title = "Tenant isolation"
description = "주요 운영 화면으로 바로 이동합니다."
[msg.admin.tenants]
+approve_confirm = "이 테넌트를 승인하시겠습니까?"
+approve_success = "테넌트가 승인되었습니다."
delete_confirm = "테넌트 \\\"{{name}}\\\"를 삭제할까요?"
+delete_success = "테넌트가 삭제되었습니다."
empty = "아직 등록된 테넌트가 없습니다."
fetch_error = "테넌트 목록 조회에 실패했습니다."
+missing_id = "테넌트 ID가 없습니다."
subtitle = "현재 등록된 테넌트를 확인하고 상태를 관리합니다."
+[msg.admin.tenants.admins]
+add_success = "관리자가 성공적으로 추가되었습니다."
+empty = "등록된 관리자가 없습니다."
+remove_confirm = "{{name}} 사용자의 관리자 권한을 회수할까요?"
+remove_success = "관리자 권한이 회수되었습니다."
+subtitle = "이 테넌트의 자원을 관리할 수 있는 권한을 가진 사용자들입니다."
+title = "테넌트 관리자 설정"
+
+[ui.admin.tenants.admins]
+add_button = "관리자 추가"
+already_admin = "이미 관리자"
+dialog_description = "이름 또는 이메일로 사용자를 검색하여 관리 권한을 부여하세요."
+dialog_no_results = "검색 결과가 없습니다."
+dialog_search_hint = "검색어를 입력해 주세요."
+dialog_search_placeholder = "사용자 검색 (최소 2자)..."
+dialog_title = "새 관리자 추가"
+remove_title = "관리자 권한 회수"
+table_actions = "액션"
+table_email = "이메일"
+table_name = "이름"
+title = "테넌트 관리자"
+
[msg.admin.tenants.create]
subtitle = "글로벌 운영 기준의 신규 테넌트를 등록합니다."
@@ -148,11 +193,11 @@ empty = "소속된 사용자가 없습니다."
count = "총 {{count}}개 테넌트"
[msg.admin.tenants.schema]
-empty = "No custom fields defined. Click \\\"Add Field\\\" to begin."
-missing_id = "Tenant ID missing"
-subtitle = "Define custom attributes for users in this tenant."
-update_error = "Failed to update schema"
-update_success = "Schema updated successfully"
+empty = "정의된 커스텀 필드가 없습니다. \\\"필드 추가\\\"를 눌러 시작하세요."
+missing_id = "테넌트 ID가 없습니다."
+subtitle = "이 테넌트 사용자를 위한 커스텀 속성을 정의합니다."
+update_error = "스키마 업데이트에 실패했습니다."
+update_success = "스키마가 성공적으로 업데이트되었습니다."
[msg.admin.tenants.sub]
empty = "하위 테넌트가 없습니다."
@@ -655,19 +700,38 @@ status = "STATUS"
time = "TIME"
[ui.admin.groups]
+add_unit = "조직 추가"
+import_csv = "CSV 임포트"
[ui.admin.groups.create]
-title = "새 그룹 생성"
+description = "부서나 팀과 같은 새로운 조직 단위를 추가합니다."
+title = "새 조직 단위 생성"
+
+[ui.admin.groups.detail]
+breadcrumb_org = "조직 관리"
+breadcrumb_tenant = "테넌트 상세"
+breadcrumb_unit = "조직 단위"
+members_title = "구성원 관리"
+members_subtitle = "이 조직 단위에 소속된 사용자들을 관리합니다."
+permissions_title = "권한 관리"
+permissions_subtitle = "이 조직 단위가 다른 테넌트에 대해 가지는 역할을 관리합니다."
+subtitle = "조직 단위의 구성원 및 권한을 관리합니다."
+title = "조직 단위 상세"
[ui.admin.groups.form]
desc_label = "설명"
-desc_placeholder = "그룹 용도 설명"
-name_label = "그룹 이름"
+desc_placeholder = "조직 단위 용도 설명"
+name_label = "조직명"
name_placeholder = "예: 개발팀, 인사팀"
+parent_label = "상위 조직"
+parent_none = "없음 (최상위)"
submit = "생성하기"
+unit_level_label = "조직 레벨"
+unit_level_placeholder = "예: 본부, 실, 팀, 셀"
[ui.admin.groups.list]
-title = "User Groups"
+subtitle = "이 테넌트에 정의된 조직 단위(부서, 팀 등) 목록입니다."
+title = "조직 관리"
[ui.admin.groups.members]
@@ -677,19 +741,21 @@ name = "이름"
remove = "제거"
[ui.admin.groups.table]
-actions = "ACTIONS"
-members = "MEMBERS"
-name = "NAME"
+actions = "액션"
+created_at = "생성일"
+level = "레벨"
+members = "멤버"
+name = "이름"
[ui.admin.header]
plane = "Admin Plane"
[ui.admin.overview]
-kicker = "Global Overview"
-title = "Tenant-independent control plane"
+kicker = "글로벌 개요"
+title = "테넌트 통합 관리 평면"
[ui.admin.overview.playbook]
-title = "Admin playbook"
+title = "운영 플레이북"
[ui.admin.overview.quick_links]
add_tenant = "테넌트 추가"
@@ -697,6 +763,12 @@ tenant_dashboard = "테넌트 대시보드"
title = "빠른 이동"
view_audit_logs = "감사 로그 보기"
+[ui.admin.overview.summary]
+audit_events_24h = "감사 이벤트 (24h)"
+oidc_clients = "OIDC 클라이언트"
+policy_gate = "정책 게이트"
+total_tenants = "전체 테넌트"
+
[ui.admin.role]
rp_admin = "RP ADMIN"
super_admin = "SUPER ADMIN"
@@ -714,24 +786,44 @@ section = "Tenants"
[ui.admin.tenants.create]
title = "테넌트 추가"
+[ui.admin.tenants.detail]
+breadcrumb_list = "테넌트 목록"
+header_subtitle = "테넌트 정보를 수정하거나 연동 설정을 관리합니다."
+loading = "테넌트 정보를 불러오는 중..."
+tab_admins = "관리자 설정"
+tab_federation = "외부 연동"
+tab_organization = "조직 관리"
+tab_profile = "프로필"
+tab_schema = "사용자 스키마"
+title = "테넌트 상세"
+
[ui.admin.tenants.create.breadcrumb]
action = "Create"
section = "Tenants"
[ui.admin.tenants.create.form]
-description = "Description"
-domains_label = "Allowed Domains (Comma separated)"
+description = "설명"
+domains_label = "허용된 도메인 (콤마로 구분)"
domains_placeholder = "example.com, example.kr"
-name = "Tenant name"
-slug = "Slug"
+name = "테넌트 이름"
+slug = "슬러그 (Slug)"
slug_placeholder = "tenant-slug"
-status = "Status"
+status = "상태"
+type = "테넌트 유형"
[ui.admin.tenants.create.memo]
title = "정책 메모"
-[ui.admin.tenants.create.profile]
-title = "Tenant Profile"
+[ui.admin.tenants.profile]
+allowed_domains = "허용된 도메인 (콤마로 구분)"
+allowed_domains_help = "이 도메인을 가진 이메일로 가입한 사용자는 자동으로 이 테넌트에 배정됩니다."
+description = "설명"
+name = "테넌트 이름"
+slug = "슬러그 (Slug)"
+status = "상태"
+subtitle = "슬러그 및 상태 변경은 즉시 적용됩니다."
+title = "테넌트 프로필"
+type = "테넌트 유형"
[ui.admin.tenants.members]
title = "Tenant Members ({{count}})"
@@ -742,23 +834,35 @@ name = "NAME"
role = "ROLE"
status = "STATUS"
+[ui.admin.tenants.profile]
+allowed_domains = "허용된 도메인 (콤마로 구분)"
+allowed_domains_help = "이 도메인을 가진 이메일로 가입한 사용자는 자동으로 이 테넌트에 배정됩니다."
+approve_button = "테넌트 승인"
+description = "설명"
+name = "테넌트 이름"
+slug = "슬러그 (Slug)"
+status = "상태"
+subtitle = "슬러그 및 상태 변경은 즉시 적용됩니다."
+title = "테넌트 프로필"
+type = "테넌트 유형"
+
[ui.admin.tenants.registry]
title = "Tenant registry"
[ui.admin.tenants.schema]
-add_field = "Add Field"
-save = "Save Schema Changes"
-title = "User Schema Extension"
+add_field = "필드 추가"
+save = "스키마 변경사항 저장"
+title = "사용자 스키마 확장"
[ui.admin.tenants.schema.field]
-key = "Field Key (ID)"
-key_placeholder = "e.g. employee_id"
-label = "Display Label"
-label_placeholder = "e.g. 사번"
-type = "Type"
-type_boolean = "Boolean"
-type_number = "Number"
-type_text = "Text"
+key = "필드 키 (ID)"
+key_placeholder = "예: employee_id"
+label = "표시 라벨"
+label_placeholder = "예: 사번"
+type = "유형"
+type_boolean = "불리언 (Boolean)"
+type_number = "숫자 (Number)"
+type_text = "텍스트 (Text)"
[ui.admin.tenants.sub]
add = "하위 테넌트 추가"
@@ -790,8 +894,8 @@ title = "사용자 추가"
title = "계정 정보"
[ui.admin.users.create.breadcrumb]
-new = "New"
-section = "Users"
+new = "신규"
+section = "사용자 관리"
[ui.admin.users.create.custom_fields]
title = "테넌트 확장 정보 (Custom Fields)"
@@ -802,12 +906,16 @@ department = "부서"
department_placeholder = "개발팀"
email = "이메일"
email_placeholder = "user@example.com"
+job_title = "직무"
+job_title_placeholder = "프론트엔드 개발"
name = "이름"
name_placeholder = "홍길동"
password = "비밀번호"
password_placeholder = "********"
phone = "전화번호"
phone_placeholder = "010-1234-5678"
+position = "직급"
+position_placeholder = "수석/책임/선임"
role = "역할 (Role)"
tenant = "테넌트 (Tenant)"
tenant_global = "시스템 전역 (소속 없음)"
@@ -821,7 +929,7 @@ edit_title = "정보 수정"
title = "사용자 상세"
[ui.admin.users.detail.breadcrumb]
-section = "Users"
+section = "사용자 관리"
[ui.admin.users.detail.custom_fields]
title = "테넌트 확장 정보 (Custom Fields)"
@@ -829,10 +937,14 @@ title = "테넌트 확장 정보 (Custom Fields)"
[ui.admin.users.detail.form]
department = "부서"
department_placeholder = "개발팀"
+job_title = "직무"
+job_title_placeholder = "프론트엔드 개발"
name = "이름"
name_placeholder = "홍길동"
phone = "전화번호"
phone_placeholder = "010-1234-5678"
+position = "직급"
+position_placeholder = "수석/책임/선임"
role = "역할 (Role)"
status = "상태"
tenant = "테넌트 (Tenant)"
@@ -852,19 +964,20 @@ tenant_slug = "Slug: {{slug}}"
title = "사용자 관리"
[ui.admin.users.list.breadcrumb]
-list = "List"
-section = "Users"
+list = "목록"
+section = "사용자 관리"
[ui.admin.users.list.registry]
-title = "User Registry"
+title = "사용자 레지스트리"
[ui.admin.users.list.table]
-actions = "ACTIONS"
-created = "CREATED"
-name_email = "NAME / EMAIL"
-role = "ROLE"
-status = "STATUS"
-tenant_dept = "TENANT / DEPT"
+actions = "액션"
+created = "생성일"
+name_email = "이름 / 이메일"
+position_job = "직급 / 직무"
+role = "역할"
+status = "상태"
+tenant_dept = "테넌트 / 부서"
[ui.common]
@@ -882,10 +995,10 @@ edit = "편집"
hyphen = "-"
na = "N/A"
never = "Never"
-next = "Next"
-page_of = "Page {{page}} of {{total}}"
+next = "다음"
+page_of = "{{page}} / {{total}} 페이지"
prev = "이전"
-previous = "Previous"
+previous = "이전"
qr = "QR"
read_only = "읽기 전용"
refresh = "새로고침"
@@ -1330,6 +1443,18 @@ verify = "본인인증"
[ui.userfront.signup.success]
action = "로그인하기"
+[msg.admin]
+header_subtitle = "테넌트 격리 및 최소 권한 원칙 기본 적용"
+idp_env_prod = "IDP 환경: 운영(Prod)"
+logout_confirm = "로그아웃 하시겠습니까?"
+scope_admin = "/admin 네임스페이스 한정"
+session_ttl = "세션 유효기간: 15분"
+tenant_headers = "테넌트 식별 헤더 적용"
+
+[ui.admin]
+brand = "Baron 로그인"
+title = "운영 도구"
+
[ui.admin.nav]
api_keys = "API 키"
audit_logs = "감사 로그"
@@ -1338,6 +1463,6 @@ logout = "로그아웃"
overview = "개요"
relying_parties = "애플리케이션(RP)"
tenant_dashboard = "테넌트 대시보드"
-user_groups = "유저 그룹"
+user_groups = "조직 관리"
tenants = "테넌트"
users = "사용자"