import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import type { AxiosError } from "axios"; import { Plus, Save, Trash2 } from "lucide-react"; import { useEffect, useState } from "react"; import { 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 { toast } from "../../../components/ui/use-toast"; import { fetchMe, fetchTenant, updateTenant } from "../../../lib/adminApi"; import { t } from "../../../lib/i18n"; import { normalizeAdminRole } from "../../../lib/roles"; import { type SchemaField, createSchemaField, isSchemaFieldType, normalizeSchemaField, } from "./tenantSchemaFields"; export function TenantSchemaPage() { const { tenantId } = useParams<{ tenantId: string }>(); const queryClient = useQueryClient(); const { data: profile, isLoading: isProfileLoading } = useQuery({ queryKey: ["me"], queryFn: fetchMe, }); const profileRole = normalizeAdminRole(profile?.role); const canAccess = profileRole === "super_admin" || profileRole === "tenant_admin"; const tenantQuery = useQuery({ queryKey: ["tenant", tenantId], queryFn: () => { if (!tenantId) throw new Error("Tenant ID is required"); return fetchTenant(tenantId); }, enabled: !!tenantId && canAccess, }); const [fields, setFields] = useState([]); useEffect(() => { const rawSchema = tenantQuery.data?.config?.userSchema; if (Array.isArray(rawSchema)) { setFields(rawSchema.map(normalizeSchemaField)); } }, [tenantQuery.data]); const updateMutation = useMutation({ mutationFn: (newFields: SchemaField[]) => { if (!tenantId) throw new Error("Tenant ID is required"); // Remove legacy loginIdField, keep isLoginId natively in userSchema const newConfig = { ...tenantQuery.data?.config }; newConfig.loginIdField = undefined; newConfig.userSchema = newFields; return updateTenant(tenantId, { config: newConfig, }); }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["tenant", tenantId] }); toast.success( t( "msg.admin.tenants.schema.update_success", "스키마가 저장되었습니다.", ), ); }, onError: (err: AxiosError<{ error?: string }>) => { toast.error( err.response?.data?.error || t("msg.admin.tenants.schema.update_error", "저장에 실패했습니다."), ); }, }); if (isProfileLoading) { return (
{t("msg.common.loading", "로딩 중...")}
); } if (!canAccess) { return (

{t("msg.common.forbidden", "접근 권한이 없습니다.")}

{t( "msg.admin.tenants.schema.forbidden_desc", "사용자 스키마 설정은 관리자만 접근할 수 있습니다.", )}

); } if (!tenantId) { return (
{t("msg.admin.tenants.schema.missing_id", "테넌트 ID가 없습니다.")}
); } const addField = () => { setFields([...fields, createSchemaField()]); }; const removeField = (index: number) => { setFields(fields.filter((_, i) => i !== index)); }; const updateField = (index: number, updates: Partial) => { const newFields = [...fields]; newFields[index] = { ...newFields[index], ...updates }; setFields(newFields); }; return (
{t("ui.admin.tenants.schema.title", "사용자 스키마 확장")} {t( "msg.admin.tenants.schema.subtitle", "이 테넌트 사용자를 위한 커스텀 속성을 정의합니다.", )}
{fields.length === 0 && (
{t( "msg.admin.tenants.schema.empty", '정의된 커스텀 필드가 없습니다. "필드 추가"를 눌러 시작하세요.', )}
)} {fields.map((field, index) => (
updateField(index, { key: e.target.value }) } placeholder={t( "ui.admin.tenants.schema.field.key_placeholder", "예: employee_id", )} className="h-10" />
updateField(index, { label: e.target.value }) } placeholder={t( "ui.admin.tenants.schema.field.label_placeholder", "예: 사번", )} className="h-10" />
{(field.type === "number" || field.type === "float") && ( )}
updateField(index, { validation: e.target.value }) } placeholder={t( "ui.admin.tenants.schema.field.validation_placeholder", "정규식 (예: ^[0-9]+$)", )} className="h-9 text-xs font-mono" />
))}
); }