From e78b2bea5021985356270a6889401399bbfa795f Mon Sep 17 00:00:00 2001 From: kyy Date: Fri, 8 May 2026 15:04:40 +0900 Subject: [PATCH] =?UTF-8?q?dev=20=EB=B3=91=ED=95=A9=20code-check=20?= =?UTF-8?q?=EC=98=A4=EB=A5=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/features/users/UserDetailPage.tsx | 23 ++++++++ .../src/features/users/UserListPage.tsx | 58 +++++++++++++------ adminfront/src/locales/template.toml | 17 ++++++ devfront/src/features/clients/ClientsPage.tsx | 8 +-- .../clients/components/ClientLogo.tsx | 11 +++- locales/en.toml | 17 ++++++ locales/ko.toml | 17 ++++++ locales/template.toml | 21 +++++++ 8 files changed, 144 insertions(+), 28 deletions(-) diff --git a/adminfront/src/features/users/UserDetailPage.tsx b/adminfront/src/features/users/UserDetailPage.tsx index 0e7b67ef..9d8cb79c 100644 --- a/adminfront/src/features/users/UserDetailPage.tsx +++ b/adminfront/src/features/users/UserDetailPage.tsx @@ -123,6 +123,7 @@ function createEmptyAppointment(): AppointmentDraft { tenantId: "", tenantName: "", tenantSlug: "", + isPrimary: false, isOwner: false, jobTitle: "", position: "", @@ -554,6 +555,15 @@ function UserDetailPage() { ); }; + const setPrimaryAppointment = (targetIndex: number) => { + setAdditionalAppointments((current) => + current.map((appointment, index) => ({ + ...appointment, + isPrimary: index === targetIndex, + })), + ); + }; + const handleUserTypeChange = (value: string) => { const nextType = value as UserType; setUserType(nextType); @@ -645,6 +655,9 @@ function UserDetailPage() { Array.isArray(rawAppointments) ? (rawAppointments as UserAppointment[]).map((appointment) => ({ ...appointment, + isPrimary: + appointment.isPrimary === true || + appointment.tenantId === primaryFromMetadata?.id, draftId: createDraftId(), })) : isUserHanmacFamily @@ -654,6 +667,7 @@ function UserDetailPage() { tenantId: tenant.id, tenantName: tenant.name, tenantSlug: tenant.slug, + isPrimary: tenant.id === fallbackAppointment?.id, isOwner: metadata.primaryTenantIsOwner === true && tenant.id === fallbackAppointment?.id, @@ -667,6 +681,7 @@ function UserDetailPage() { tenantId: fallbackAppointment.id, tenantName: fallbackAppointment.name, tenantSlug: fallbackAppointment.slug, + isPrimary: true, isOwner: metadata.primaryTenantIsOwner === true, jobTitle: user.jobTitle, position: user.position, @@ -781,7 +796,15 @@ function UserDetailPage() { payload.metadata = { ...metadata, additionalAppointments: appointments, + primaryTenantId: primaryAppointment?.tenantId, + primaryTenantName: primaryAppointment?.tenantName, + primaryTenantSlug: primaryAppointment?.tenantSlug, + primaryTenantIsOwner: primaryAppointment?.isOwner ?? false, }; + payload.tenantSlug = primaryAppointment?.tenantSlug; + payload.primaryTenantId = primaryAppointment?.tenantId; + payload.primaryTenantName = primaryAppointment?.tenantName; + payload.primaryTenantIsOwner = primaryAppointment?.isOwner ?? false; } mutation.mutate(payload); diff --git a/adminfront/src/features/users/UserListPage.tsx b/adminfront/src/features/users/UserListPage.tsx index 7b064b2e..2123d30a 100644 --- a/adminfront/src/features/users/UserListPage.tsx +++ b/adminfront/src/features/users/UserListPage.tsx @@ -33,7 +33,13 @@ import { DialogTrigger, } from "../../components/ui/dialog"; import { Input } from "../../components/ui/input"; -import { Switch } from "../../components/ui/switch"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "../../components/ui/select"; import { Table, TableBody, @@ -547,7 +553,7 @@ function UserListPage() { /> requestSort("id")} >
@@ -556,7 +562,7 @@ function UserListPage() {
requestSort("name_email")} >
@@ -568,7 +574,7 @@ function UserListPage() {
requestSort("status")} >
@@ -577,7 +583,7 @@ function UserListPage() {
requestSort("tenant_dept")} >
@@ -594,7 +600,7 @@ function UserListPage() { visibleColumns[field.key] !== false && ( requestSort(field.key)} >
@@ -605,7 +611,7 @@ function UserListPage() { ), )} requestSort("createdAt")} >
@@ -693,28 +699,42 @@ function UserListPage() { )}
- {" "} +
- + {t(`ui.common.status.${user.status}`, user.status)} diff --git a/adminfront/src/locales/template.toml b/adminfront/src/locales/template.toml index 79844871..d2b06aea 100644 --- a/adminfront/src/locales/template.toml +++ b/adminfront/src/locales/template.toml @@ -131,12 +131,17 @@ empty = "" import_error = "" import_success = "" loading = "" +no_results = "" subtitle = "" [msg.admin.groups.members] +add_modal_desc = "" add_success = "" +all_added = "" count = "" empty = "" +move_modal_desc = "" +move_success = "" remove_confirm = "" remove_success = "" title = "" @@ -234,6 +239,9 @@ subtitle = "" desc = "" empty = "" limit_notice = "" +remove_confirm = "" +remove_error = "" +remove_success = "" [msg.admin.tenants.registry] count = "" @@ -250,6 +258,7 @@ empty = "" subtitle = "" [msg.admin.users] +confirm_remove_org = "" export_error = "" status_error = "" @@ -827,8 +836,11 @@ unit_level_placeholder = "" title = "" [ui.admin.groups.members] +add_modal_title = "" +move_modal_title = "" [ui.admin.groups.members.table] +actions = "" email = "" name = "" remove = "" @@ -985,8 +997,12 @@ search_placeholder = "" select_placeholder = "" [ui.admin.tenants.members] +add_existing = "" +create_new = "" descendants = "" direct = "" +remove = "" +view_profile = "" [msg.admin.apikeys.registry] count = "" @@ -1013,6 +1029,7 @@ total = "" total_label = "" [ui.admin.tenants.members.table] +actions = "" email = "" name = "" role = "" diff --git a/devfront/src/features/clients/ClientsPage.tsx b/devfront/src/features/clients/ClientsPage.tsx index bb0ebbe6..5657fb42 100644 --- a/devfront/src/features/clients/ClientsPage.tsx +++ b/devfront/src/features/clients/ClientsPage.tsx @@ -1,12 +1,6 @@ import { useMutation, useQuery } from "@tanstack/react-query"; import type { AxiosError } from "axios"; -import { - BookOpenText, - Filter, - Plus, - Search, - X, -} from "lucide-react"; +import { BookOpenText, Filter, Plus, Search, X } from "lucide-react"; import { useEffect, useState } from "react"; import { useAuth } from "react-oidc-context"; import { Link, useNavigate } from "react-router-dom"; diff --git a/devfront/src/features/clients/components/ClientLogo.tsx b/devfront/src/features/clients/components/ClientLogo.tsx index 0c7b0a56..397a1d99 100644 --- a/devfront/src/features/clients/components/ClientLogo.tsx +++ b/devfront/src/features/clients/components/ClientLogo.tsx @@ -1,6 +1,10 @@ import { ServerCog, ShieldHalf } from "lucide-react"; import { useMemo, useState } from "react"; -import { Avatar, AvatarFallback, AvatarImage } from "../../../components/ui/avatar"; +import { + Avatar, + AvatarFallback, + AvatarImage, +} from "../../../components/ui/avatar"; import type { ClientSummary, ClientType } from "../../../lib/devApi"; import { t } from "../../../lib/i18n"; @@ -28,7 +32,10 @@ function TypeFallbackIcon({ type }: { type: ClientType }) { export function ClientLogo({ client }: ClientLogoProps) { const [didImageFail, setDidImageFail] = useState(false); - const logoUrl = useMemo(() => readLogoUrl(client.metadata), [client.metadata]); + const logoUrl = useMemo( + () => readLogoUrl(client.metadata), + [client.metadata], + ); const showImage = Boolean(logoUrl) && !didImageFail; const clientName = client.name || t("ui.dev.clients.untitled", "Untitled"); diff --git a/locales/en.toml b/locales/en.toml index 2da95a9f..785df7d0 100644 --- a/locales/en.toml +++ b/locales/en.toml @@ -131,12 +131,17 @@ empty = "No organization units have been registered yet." import_error = "Import Error" import_success = "Import Success" loading = "Loading..." +no_results = "No groups found." subtitle = "Manage departments and teams under the current tenant." [msg.admin.groups.members] +add_modal_desc = "Search and select members to add from users in this tenant." add_success = "Member added successfully." +all_added = "All tenant members are already in this group." count = "{{count}} members loaded." empty = "No members are assigned to this organization unit." +move_modal_desc = "Select the target group to move the selected member." +move_success = "Member moved successfully." remove_confirm = "Are you sure you want to remove this member?" remove_success = "Member removed successfully." title = "Member Management" @@ -229,6 +234,9 @@ subtitle = "Set the basic tenant profile information." desc = "View the list of users belonging to this organization." empty = "No members found." limit_notice = "Showing members from the first 10 descendant organizations due to size limits." +remove_confirm = "Are you sure you want to exclude '{{name}}' from this organization?" +remove_error = "An error occurred while excluding from organization." +remove_success = "Successfully excluded from organization." [msg.admin.tenants.owners] add_success = "Owner added successfully." @@ -255,6 +263,7 @@ empty = "No child tenants are connected." subtitle = "Review and manage child tenants linked under this tenant." [msg.admin.users] +confirm_remove_org = "Do you want to remove this user from the organization?" export_error = "Failed to export users." status_error = "Failed to update user status." @@ -1026,8 +1035,11 @@ unit_level_placeholder = "Unit Level Placeholder" title = "User Groups" [ui.admin.groups.members] +add_modal_title = "Add Member to Group" +move_modal_title = "Move Department" [ui.admin.groups.members.table] +actions = "ACTIONS" email = "Email" name = "Name" remove = "Remove" @@ -1217,13 +1229,17 @@ self_delete_blocked = "You cannot delete your own account." title = "API Key Registry" [ui.admin.tenants.members] +add_existing = "Assign Existing Member" +create_new = "Create New Member" delete_selected = "Delete Selected" +remove = "Exclude from Organization" view_org_chart = "View Full Org Chart" direct_label = "Direct" list_title = "Member Management" title = "Tenant Members ({{count}})" total = "Total" total_label = "Total" +view_profile = "View Profile" [ui.admin.tenants.import_preview] candidates = "Candidates" @@ -1235,6 +1251,7 @@ no_candidates = "No matching tenants found." title = "Import Preview" [ui.admin.tenants.members.table] +actions = "ACTIONS" email = "EMAIL" name = "NAME" role = "ROLE" diff --git a/locales/ko.toml b/locales/ko.toml index fe5e2203..466eebcf 100644 --- a/locales/ko.toml +++ b/locales/ko.toml @@ -612,12 +612,17 @@ empty = "테넌트에 등록된 조직 단위가 없습니다." import_error = "가져오기 실패" import_success = "조직도가 임포트되었습니다." loading = "로딩 중..." +no_results = "그룹이 없습니다." subtitle = "이 테넌트에 정의된 사용자 그룹 목록입니다." [msg.admin.groups.members] +add_modal_desc = "이 테넌트에 속한 사용자 중 추가할 멤버를 검색하여 선택하세요." add_success = "구성원이 추가되었습니다." +all_added = "모든 테넌트 멤버가 이미 이 그룹에 속해 있습니다." count = "{{count}} 명" empty = "멤버가 없습니다." +move_modal_desc = "선택한 멤버를 이동할 대상 그룹을 선택하세요." +move_success = "멤버가 이동되었습니다." remove_confirm = "제거하시겠습니까?" remove_success = "구성원이 제외되었습니다." title = "[{{name}}] 멤버 관리" @@ -710,6 +715,9 @@ subtitle = "필수 정보만 입력해도 생성 가능합니다. Slug는 없으 desc = "조직에 소속된 사용자 목록을 확인합니다." empty = "소속된 사용자가 없습니다." limit_notice = "하위 조직이 많아 상위 10개 조직의 멤버만 표시됩니다." +remove_confirm = "'{{name}}'님을 이 조직에서 제외하시겠습니까?" +remove_error = "조직에서 제외하는 중 오류가 발생했습니다." +remove_success = "조직에서 제외되었습니다." [msg.admin.tenants.owners] add_success = "소유자가 추가되었습니다." @@ -736,6 +744,7 @@ empty = "하위 테넌트가 없습니다." subtitle = "현재 테넌트 하위에 생성된 조직입니다." [msg.admin.users] +confirm_remove_org = "이 조직에서 사용자를 제외하시겠습니까?" export_error = "사용자 내보내기에 실패했습니다." status_error = "사용자 상태 변경에 실패했습니다." @@ -1505,8 +1514,11 @@ unit_level_placeholder = "예: 본부, 팀" title = "User Groups" [ui.admin.groups.members] +add_modal_title = "그룹에 멤버 추가" +move_modal_title = "부서 이동" [ui.admin.groups.members.table] +actions = "ACTIONS" email = "이메일" name = "이름" remove = "제거" @@ -1679,13 +1691,17 @@ status_error = "사용자 상태 변경에 실패했습니다: {{error}}" title = "API Key Registry" [ui.admin.tenants.members] +add_existing = "기존 멤버 배정" +create_new = "신규 멤버 생성" delete_selected = "선택 삭제" +remove = "조직에서 제외" view_org_chart = "전체 조직도 보기" direct_label = "직속" list_title = "구성원 관리" title = "테넌트 구성원 ({{count}})" total = "전체" total_label = "전체" +view_profile = "상세 정보" [ui.admin.tenants.import_preview] candidates = "후보" @@ -1697,6 +1713,7 @@ no_candidates = "매칭 가능한 테넌트가 없습니다." title = "임포트 미리보기" [ui.admin.tenants.members.table] +actions = "ACTIONS" email = "EMAIL" name = "NAME" role = "ROLE" diff --git a/locales/template.toml b/locales/template.toml index 0e24f3ec..5627cf0d 100644 --- a/locales/template.toml +++ b/locales/template.toml @@ -481,12 +481,17 @@ empty = "" import_error = "" import_success = "" loading = "" +no_results = "" subtitle = "" [msg.admin.groups.members] +add_modal_desc = "" add_success = "" +all_added = "" count = "" empty = "" +move_modal_desc = "" +move_success = "" remove_confirm = "" remove_success = "" title = "" @@ -579,6 +584,9 @@ subtitle = "" desc = "" empty = "" limit_notice = "" +remove_confirm = "" +remove_error = "" +remove_success = "" [msg.admin.tenants.owners] add_success = "" @@ -605,6 +613,7 @@ empty = "" subtitle = "" [msg.admin.users] +confirm_remove_org = "" export_error = "" status_error = "" @@ -1374,8 +1383,11 @@ unit_level_placeholder = "" title = "" [ui.admin.groups.members] +add_modal_title = "" +move_modal_title = "" [ui.admin.groups.members.table] +actions = "" email = "" name = "" remove = "" @@ -1521,13 +1533,17 @@ search_placeholder = "" select_placeholder = "" [ui.admin.tenants.members] +add_existing = "" +create_new = "" descendants = "" direct = "" direct_label = "" list_title = "" +remove = "" title = "" total = "" total_label = "" +view_profile = "" [msg.admin.apikeys.registry] count = "" @@ -1554,15 +1570,20 @@ status_error = "" title = "" [ui.admin.tenants.members] +add_existing = "" +create_new = "" delete_selected = "" +remove = "" view_org_chart = "" direct_label = "" list_title = "" title = "" total = "" total_label = "" +view_profile = "" [ui.admin.tenants.members.table] +actions = "" email = "" name = "" role = ""