import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import type { AxiosError } from "axios"; import { ArrowLeft, BadgeCheck, Building2, Loader2, Save, Users } from "lucide-react"; import * as React from "react"; import { useForm } from "react-hook-form"; import { Link, useNavigate, useParams } from "react-router-dom"; import { Button } from "../../components/ui/button"; import { Card, CardContent, CardDescription, CardHeader, CardTitle, } from "../../components/ui/card"; import { Input } from "../../components/ui/input"; import { Label } from "../../components/ui/label"; import { type UserUpdateRequest, fetchMe, fetchTenant, fetchTenants, fetchUser, updateUser, } from "../../lib/adminApi"; import { t } from "../../lib/i18n"; type UserSchemaField = { key: string; label?: string; type?: "text" | "number" | "boolean" | "date"; required?: boolean; adminOnly?: boolean; validation?: string; }; type UserFormValues = UserUpdateRequest & { metadata: Record }; // [New] Component for per-tenant profile/schema management function TenantProfileCard({ tenant, register, errors, isAdmin }: { tenant: any, register: any, errors: any, isAdmin: boolean }) { const { data: detail, isLoading } = useQuery({ queryKey: ["tenant", tenant.id], queryFn: () => fetchTenant(tenant.id), }); const schema: UserSchemaField[] = Array.isArray(detail?.config?.userSchema) ? (detail?.config?.userSchema as UserSchemaField[]) : []; if (isLoading) return
Loading schema...
; if (schema.length === 0) return null; return (
{tenant.name}
{tenant.slug}
{schema.map((field) => (
{errors.metadata?.[tenant.id]?.[field.key] && (

{errors.metadata[tenant.id][field.key].message}

)}
))}
); } function UserDetailPage() { const params = useParams<{ id: string }>(); const userId = params.id ?? ""; const navigate = useNavigate(); const queryClient = useQueryClient(); const [error, setError] = React.useState(null); const [successMsg, setSuccessMsg] = React.useState(null); const { data: profile } = useQuery({ queryKey: ["me"], queryFn: fetchMe, }); const { data: user, isLoading, isError, } = useQuery({ queryKey: ["user", userId], queryFn: () => fetchUser(userId), enabled: userId.length > 0, }); const { data: tenantsData } = useQuery({ queryKey: ["tenants", { limit: 100 }], queryFn: () => fetchTenants(100, 0), }); const tenants = tenantsData?.items ?? []; const { register, handleSubmit, reset, watch, formState: { errors }, } = useForm({ defaultValues: { name: "", phone: "", role: "user", status: "active", companyCode: "", department: "", position: "", jobTitle: "", password: "", metadata: {}, }, }); const isAdmin = profile?.role === "super_admin" || profile?.role === "tenant_admin"; React.useEffect(() => { if (user) { reset({ name: user.name, phone: user.phone || "", role: user.role, status: user.status, companyCode: user.companyCode || "", department: user.department || "", position: user.position || "", jobTitle: user.jobTitle || "", password: "", metadata: user.metadata || {}, }); } }, [user, reset]); const mutation = useMutation({ mutationFn: (data: UserUpdateRequest) => updateUser(userId, data), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["users"] }); queryClient.invalidateQueries({ queryKey: ["user", userId] }); setSuccessMsg( t( "msg.admin.users.detail.update_success", "사용자 정보가 수정되었습니다.", ), ); setError(null); }, onError: (err: AxiosError<{ error?: string }>) => { setError( err.response?.data?.error || t( "msg.admin.users.detail.update_error", "사용자 수정에 실패했습니다.", ), ); setSuccessMsg(null); }, }); const onSubmit = (data: UserFormValues) => { const payload: UserUpdateRequest = { ...data }; if (!payload.password) { payload.password = undefined; } mutation.mutate(payload); }; if (isLoading) { return (
{t("msg.common.loading", "Loading...")}
); } if (isError || !user) { return (
{t("msg.admin.users.detail.not_found", "사용자를 찾을 수 없습니다.")}
); } // Combined affiliated tenants const userAffiliatedTenants = [...(user.joinedTenants || [])]; if (user.tenant && !userAffiliatedTenants.find(t => t.id === user.tenant?.id)) { userAffiliatedTenants.push(user.tenant); } return (
{t("ui.admin.users.detail.breadcrumb.section", "Users")} / {user.name}

{t("ui.admin.users.detail.title", "사용자 상세")}

{t("ui.admin.users.detail.edit_title", "정보 수정")} {t( "msg.admin.users.detail.edit_subtitle", "{{email}} 계정의 정보를 수정합니다.", { email: user.email }, )}
{error && (
{error}
)} {successMsg && (
{successMsg}
)} {/* Tenant Affiliation Section (Enhanced) */}

{t("ui.admin.users.detail.tenants_section.title", "소속 및 조직 정보")}

{/* Select box to specify representative tenant from joined ones */} {userAffiliatedTenants.length > 0 ? (
) : (
{t("ui.admin.users.detail.form.tenant_global", "시스템 전역 (소속 없음)")}
)}

* 사용자의 주된 정체성을 결정하는 대표 조직을 선택합니다.

{userAffiliatedTenants.length > 1 && (
{userAffiliatedTenants.map(jt => ( t.slug === watch("companyCode"))?.id ? "bg-primary/10 border-primary/30 text-primary font-bold" : "bg-background border-border text-muted-foreground hover:border-primary/50" }`} > {jt.name} ))}
)}
{errors.name && (

{errors.name.message}

)}
{/* Tenant-specific Profiles (Namespaced Metadata) */}

{t("ui.admin.users.detail.custom_fields.multi_title", "테넌트별 프로필 관리")}

사용자가 소속된 각 테넌트별 맞춤 정보를 관리합니다.

{userAffiliatedTenants.map((tenant) => ( ))}

{t("ui.admin.users.detail.security.title", "보안 설정")}

{t( "msg.admin.users.detail.security.password_hint", "비밀번호를 변경하려면 입력하세요. 비워두면 현재 비밀번호가 유지됩니다.", )}

); } export default UserDetailPage;