import * as React from 'react'; import { ApprovalRouteApi } from '../../services/api'; import { ApiClientError } from '../../services/httpClient'; import { PortalDictionaryClient } from '../../services/portalDictionary'; import { Company, Department, RequestType, routeTypeLabels, RouteType, TemplateRoute } from '../../models'; import { ActionsCell, Alert, Badge, Breadcrumbs, Button, Checkbox, ConfirmDialog, DataTable, Field, FormGrid, IconButton, Modal, PageHeader, PrimaryCell, SearchableSelect, Select } from '../ui'; export interface IRoutesScreenProps { api: ApprovalRouteApi; dictionary?: PortalDictionaryClient; onOpenSteps: (route: TemplateRoute) => void; } interface IFormState { requestTypeId: string; routeType: RouteType; companyId: string; departmentId: string; byProject: boolean; requiresControl: boolean; } const EMPTY_FORM: IFormState = { requestTypeId: '', routeType: RouteType.Sequential, companyId: '', departmentId: '', byProject: false, requiresControl: false }; const ROUTE_TYPE_OPTIONS = Object.values(RouteType) .filter(v => v !== RouteType.None) .map(v => ({ value: v, label: routeTypeLabels[v] })); export const RoutesScreen: React.FC = ({ api, dictionary, onOpenSteps }) => { const [requestTypes, setRequestTypes] = React.useState([]); const [companies, setCompanies] = React.useState([]); const [departments, setDepartments] = React.useState([]); const [dictLoading, setDictLoading] = React.useState(true); const [dictError, setDictError] = React.useState(); const [items, setItems] = React.useState([]); const [filterRequestTypeId, setFilterRequestTypeId] = React.useState(''); const [loading, setLoading] = React.useState(true); const [loadError, setLoadError] = React.useState(); const [editing, setEditing] = React.useState(); const [showForm, setShowForm] = React.useState(false); const [form, setForm] = React.useState(EMPTY_FORM); const [formError, setFormError] = React.useState(); const [saving, setSaving] = React.useState(false); const [deleting, setDeleting] = React.useState(); const [deleteError, setDeleteError] = React.useState(); const [deleteBusy, setDeleteBusy] = React.useState(false); React.useEffect(() => { api.requestTypes.list().then(setRequestTypes).catch(() => { /* показывается в общей ошибке ниже */ }); }, [api]); // Справочники 1С грузим один раз на экран: они статичны, а отделов ~700 — // поиск делаем на клиенте, без запроса на каждый символ. React.useEffect(() => { if (!dictionary) return; setDictLoading(true); setDictError(undefined); Promise.all([dictionary.companies(), dictionary.departments()]) .then(([loadedCompanies, loadedDepartments]) => { setCompanies(loadedCompanies); setDepartments(loadedDepartments); }) .catch((e: ApiClientError) => setDictError(e.message)) .finally(() => setDictLoading(false)); }, [dictionary]); const load = React.useCallback(() => { setLoading(true); setLoadError(undefined); const requestTypeId = filterRequestTypeId ? Number(filterRequestTypeId) : undefined; api.templateRoutes .list(requestTypeId) .then(setItems) .catch((e: ApiClientError) => setLoadError(e.message)) .finally(() => setLoading(false)); }, [api, filterRequestTypeId]); React.useEffect(() => { load(); }, [load]); const requestTypeName = (id: number): string => requestTypes.find(rt => rt.id === id)?.name ?? `#${id}`; // Пока справочник не загрузился (или маршрут ссылается на удалённую запись), // показываем усечённый Guid — чтобы строка таблицы не выглядела пустой. const companyName = (id: string): string => companies.find(c => c.id === id)?.name ?? shortGuid(id); const departmentName = (id: string): string => departments.find(d => d.id === id)?.name ?? shortGuid(id); // Отдел принадлежит компании, поэтому список сужаем под выбранную: // иначе пользователь листает ~700 позиций из всех юрлиц сразу. const departmentsForSelectedCompany = React.useMemo( () => (form.companyId ? departments.filter(d => d.companyId === form.companyId) : []), [departments, form.companyId] ); const openCreate = (): void => { setEditing(undefined); setForm({ ...EMPTY_FORM, requestTypeId: filterRequestTypeId }); setFormError(undefined); setShowForm(true); }; const openEdit = (item: TemplateRoute): void => { setEditing(item); setForm({ requestTypeId: String(item.requestTypeId), routeType: item.routeType, companyId: item.companyId, departmentId: item.departmentId ?? '', byProject: item.byProject, requiresControl: item.requiresControl }); setFormError(undefined); setShowForm(true); }; const submit = (): void => { if (!editing && !form.requestTypeId) { setFormError('Выберите тип заявки.'); return; } if (!form.companyId.trim()) { setFormError('Компания обязательна (Guid из справочника api.tnsu.kz).'); return; } setSaving(true); setFormError(undefined); const departmentId = form.departmentId.trim() ? form.departmentId.trim() : null; const request = editing ? api.templateRoutes.update(editing.id, { routeType: form.routeType, companyId: form.companyId.trim(), departmentId, byProject: form.byProject, requiresControl: form.requiresControl }) : api.templateRoutes.create({ routeType: form.routeType, requestTypeId: Number(form.requestTypeId), companyId: form.companyId.trim(), departmentId, byProject: form.byProject, requiresControl: form.requiresControl }); request .then(() => { setShowForm(false); load(); }) .catch((e: ApiClientError) => setFormError(e.message)) .finally(() => setSaving(false)); }; const confirmDelete = (): void => { if (!deleting) return; setDeleteBusy(true); setDeleteError(undefined); api.templateRoutes .remove(deleting.id) .then(() => { setDeleting(undefined); load(); }) .catch((e: ApiClientError) => setDeleteError(e.message)) .finally(() => setDeleteBusy(false)); }; return (
Добавить маршрут} />
({ value: String(rt.id), label: rt.name }))} placeholder="Выберите тип заявки" value={form.requestTypeId} disabled={!!editing} onChange={e => setForm({ ...form, requestTypeId: e.target.value })} />