import { Form, Input, Modal, Select } from 'antd'; import { useState } from 'react'; import { ApiError, api } from '../../api/client'; import type { AdminUser, BusinessSegment, RegionalOffice, Role, UserCreate } from '../../api/types'; import { useMountedRef, useNotifyError } from './hooks'; type Props = { open: boolean; roles: Role[]; offices: RegionalOffice[]; onClose: () => void; onCreated: (user: AdminUser) => void; }; /** Значения формы; пустой сегмент (`undefined`) уходит на сервер как `null` — «не задан». */ type FormValues = { email: string; full_name: string; employee_no?: string; regional_office_id: string; business_segment?: BusinessSegment; roles?: string[]; }; // Коды сегмента — как enum `BusinessSegment` бэкенда; «не задан» — очищенное поле const SEGMENT_OPTIONS: { value: BusinessSegment; label: string }[] = [ { value: 'DKB', label: 'DKB' }, { value: 'DMSB', label: 'DMSB' }, { value: 'DM', label: 'DM' }, ]; /** * Окно создания пользователя (`POST /admin/users`). Проверки формы — на клиенте (формат email, обязательные поля), * окончательная — на сервере: занятый email (409 `EMAIL_EXISTS`) показывается под полем email, прочие ошибки — * уведомлением. После закрытия поля очищаются. */ export function CreateUserModal({ open, roles, offices, onClose, onCreated }: Props) { const [form] = Form.useForm(); const [submitting, setSubmitting] = useState(false); const notifyError = useNotifyError(); const mounted = useMountedRef(); const submit = async (values: FormValues) => { setSubmitting(true); const body: UserCreate = { email: values.email.trim(), full_name: values.full_name.trim(), employee_no: values.employee_no?.trim() || null, regional_office_id: values.regional_office_id, business_segment: values.business_segment ?? null, roles: values.roles ?? [], }; try { const user = await api.post('/admin/users', body); if (mounted.current) { onCreated(user); } } catch (e: unknown) { if (!mounted.current) { return; } if (e instanceof ApiError && e.code === 'EMAIL_EXISTS') { form.setFields([{ name: 'email', errors: [e.message] }]); } else { notifyError(e); } } finally { if (mounted.current) { setSubmitting(false); } } }; // Пока запрос идёт, окно не закрывается: иначе ответ придёт в уже закрытую форму const cancel = () => { if (!submitting) { onClose(); } }; return ( form.submit()} onCancel={cancel} confirmLoading={submitting} cancelButtonProps={{ disabled: submitting }} mask={{ closable: !submitting }} // форма смонтирована всегда (forceRender), поэтому её можно очистить после закрытия окна forceRender afterClose={() => form.resetFields()}> form={form} layout='vertical' onFinish={submit} disabled={submitting}>