From a4f283e4e6b75631d0996a4baaf163291f7570c0 Mon Sep 17 00:00:00 2001 From: chan Date: Tue, 24 Mar 2026 14:22:05 +0900 Subject: [PATCH] #445 #430 #426 #427 --- adminfront/src/components/ui/use-toast.ts | 20 ++-- .../components/OrgChartUploadModal.tsx | 2 +- .../routes/TenantAdminsAndOwnersTab.tsx | 102 +++++++++--------- .../tenants/routes/TenantGroupsPage.tsx | 2 +- .../tenants/routes/TenantProfilePage.tsx | 39 ++++++- .../tenants/routes/TenantSchemaPage.tsx | 2 +- .../tenants/routes/TenantUsersPage.tsx | 2 +- .../routes/TenantUserGroupsTab.tsx | 10 +- .../routes/UserGroupDetailPage.tsx | 2 +- .../src/features/users/UserCreatePage.tsx | 2 +- .../src/features/users/UserDetailPage.tsx | 2 +- .../src/features/users/UserListPage.tsx | 7 +- .../components/UserBulkMoveGroupModal.tsx | 2 +- adminfront/src/lib/adminApi.ts | 2 +- adminfront/src/main.tsx | 2 +- backend/cmd/server/main.go | 2 +- backend/internal/handler/tenant_handler.go | 18 ++++ backend/internal/handler/user_handler.go | 72 +++++++++---- 18 files changed, 197 insertions(+), 93 deletions(-) diff --git a/adminfront/src/components/ui/use-toast.ts b/adminfront/src/components/ui/use-toast.ts index adbc7f38..402ed87c 100644 --- a/adminfront/src/components/ui/use-toast.ts +++ b/adminfront/src/components/ui/use-toast.ts @@ -30,20 +30,26 @@ const toastBase = (message: string, type: ToastType = "success") => { export const toast = Object.assign(toastBase, { success: (message: string, options?: { description?: string }) => { - const finalMessage = options?.description ? `${message} -${options.description}` : message; + const finalMessage = options?.description + ? `${message} +${options.description}` + : message; toastBase(finalMessage, "success"); }, error: (message: string, options?: { description?: string }) => { - const finalMessage = options?.description ? `${message} -${options.description}` : message; + const finalMessage = options?.description + ? `${message} +${options.description}` + : message; toastBase(finalMessage, "error"); }, info: (message: string, options?: { description?: string }) => { - const finalMessage = options?.description ? `${message} -${options.description}` : message; + const finalMessage = options?.description + ? `${message} +${options.description}` + : message; toastBase(finalMessage, "info"); - } + }, }); export const useToastState = () => { diff --git a/adminfront/src/features/tenants/components/OrgChartUploadModal.tsx b/adminfront/src/features/tenants/components/OrgChartUploadModal.tsx index 1a993c18..551c13dc 100644 --- a/adminfront/src/features/tenants/components/OrgChartUploadModal.tsx +++ b/adminfront/src/features/tenants/components/OrgChartUploadModal.tsx @@ -2,7 +2,6 @@ import { useMutation } from "@tanstack/react-query"; import type { AxiosError } from "axios"; import { Download, FileText, Loader2, Upload } from "lucide-react"; import * as React from "react"; -import { toast } from "../../../components/ui/use-toast"; import { Button } from "../../../components/ui/button"; import { Dialog, @@ -13,6 +12,7 @@ import { DialogTitle, DialogTrigger, } from "../../../components/ui/dialog"; +import { toast } from "../../../components/ui/use-toast"; import { importOrgChart } from "../../../lib/adminApi"; import { t } from "../../../lib/i18n"; diff --git a/adminfront/src/features/tenants/routes/TenantAdminsAndOwnersTab.tsx b/adminfront/src/features/tenants/routes/TenantAdminsAndOwnersTab.tsx index 5186823c..4031f590 100644 --- a/adminfront/src/features/tenants/routes/TenantAdminsAndOwnersTab.tsx +++ b/adminfront/src/features/tenants/routes/TenantAdminsAndOwnersTab.tsx @@ -12,7 +12,6 @@ import { import { useState } from "react"; import { useAuth } from "react-oidc-context"; import { useParams } from "react-router-dom"; -import { toast } from "../../../components/ui/use-toast"; import { Badge } from "../../../components/ui/badge"; import { Button } from "../../../components/ui/button"; import { @@ -39,6 +38,7 @@ import { TableHeader, TableRow, } from "../../../components/ui/table"; +import { toast } from "../../../components/ui/use-toast"; import { addTenantAdmin, addTenantOwner, @@ -291,25 +291,29 @@ export function TenantAdminsAndOwnersTab() { {owner.email} - + + {owner.id === currentUserId ? t( "msg.admin.tenants.owners.remove_self", "본인의 권한은 회수할 수 없습니다.", @@ -322,11 +326,9 @@ export function TenantAdminsAndOwnersTab() { : t( "ui.admin.tenants.owners.remove_title", "소유자 권한 회수", - ) - } - > - - + )} + + )) @@ -420,25 +422,29 @@ export function TenantAdminsAndOwnersTab() { {admin.email} - + + {admin.id === currentUserId ? t( "msg.admin.tenants.admins.remove_self", "본인의 권한은 회수할 수 없습니다.", @@ -451,11 +457,9 @@ export function TenantAdminsAndOwnersTab() { : t( "ui.admin.tenants.admins.remove_title", "관리자 권한 회수", - ) - } - > - - + )} + + )) diff --git a/adminfront/src/features/tenants/routes/TenantGroupsPage.tsx b/adminfront/src/features/tenants/routes/TenantGroupsPage.tsx index 6d56c737..2778c8e8 100644 --- a/adminfront/src/features/tenants/routes/TenantGroupsPage.tsx +++ b/adminfront/src/features/tenants/routes/TenantGroupsPage.tsx @@ -18,7 +18,6 @@ import { import type React from "react"; import { useState } from "react"; import { useParams } from "react-router-dom"; -import { toast } from "../../../components/ui/use-toast"; import { Badge } from "../../../components/ui/badge"; import { Button } from "../../../components/ui/button"; import { @@ -38,6 +37,7 @@ import { TableHeader, TableRow, } from "../../../components/ui/table"; +import { toast } from "../../../components/ui/use-toast"; import { type GroupSummary, addGroupMember, diff --git a/adminfront/src/features/tenants/routes/TenantProfilePage.tsx b/adminfront/src/features/tenants/routes/TenantProfilePage.tsx index 17b787c3..6d186c62 100644 --- a/adminfront/src/features/tenants/routes/TenantProfilePage.tsx +++ b/adminfront/src/features/tenants/routes/TenantProfilePage.tsx @@ -3,7 +3,6 @@ import type { AxiosError } from "axios"; import { Save, Trash2 } from "lucide-react"; import { useEffect, useState } from "react"; import { useNavigate, useParams } from "react-router-dom"; -import { toast } from "../../../components/ui/use-toast"; import { Button } from "../../../components/ui/button"; import { Card, @@ -15,10 +14,12 @@ import { import { Input } from "../../../components/ui/input"; import { Label } from "../../../components/ui/label"; import { Textarea } from "../../../components/ui/textarea"; +import { toast } from "../../../components/ui/use-toast"; import { approveTenant, deleteTenant, fetchTenant, + fetchTenants, updateTenant, } from "../../../lib/adminApi"; import { t } from "../../../lib/i18n"; @@ -39,12 +40,21 @@ export function TenantProfilePage() { queryFn: () => fetchTenant(tenantId), }); + const parentQuery = useQuery({ + queryKey: ["tenants", "list-all"], + queryFn: () => fetchTenants(1000, 0), + }); + + const availableParents = + parentQuery.data?.items?.filter((t) => t.id !== tenantId) || []; + const [name, setName] = useState(""); const [type, setType] = useState("COMPANY"); const [slug, setSlug] = useState(""); const [description, setDescription] = useState(""); const [status, setStatus] = useState("active"); const [domains, setDomains] = useState(""); + const [parentId, setParentId] = useState(""); useEffect(() => { if (tenantQuery.data) { @@ -54,6 +64,7 @@ export function TenantProfilePage() { setDescription(tenantQuery.data.description ?? ""); setStatus(tenantQuery.data.status); setDomains(tenantQuery.data.domains?.join(", ") ?? ""); + setParentId(tenantQuery.data.parentId ?? ""); } }, [tenantQuery.data]); @@ -65,6 +76,7 @@ export function TenantProfilePage() { slug, description: description || undefined, status, + parentId: parentId || undefined, domains: domains .split(",") .map((d) => d.trim()) @@ -197,6 +209,31 @@ export function TenantProfilePage() { +
+ + +

+ {t( + "ui.admin.tenants.profile.form.parent_help", + "가족사 테넌트나 하위 조직을 종속시킬 경우 상위 테넌트를 선택해주세요.", + )} +

+