161 lines
6.2 KiB
TypeScript
161 lines
6.2 KiB
TypeScript
import * as React from 'react';
|
|
import styles from './RoutesScreen.module.scss';
|
|
import { RouteFormModal } from './RouteFormModal';
|
|
import { ApprovalRouteApi } from '../../services/api';
|
|
import { PortalDictionaryClient } from '../../services/portalDictionary';
|
|
import { RouteType, TemplateRoute } from '../../models';
|
|
import { routeTypeLabels, texts } from '../../texts';
|
|
import { shortGuid } from '../../utils/guid';
|
|
import { useRequestTypes } from '../../hooks/useRequestTypes';
|
|
import { useTemplateRoutes } from '../../hooks/useTemplateRoutes';
|
|
import { usePortalDictionaries } from '../../hooks/usePortalDictionaries';
|
|
import { useDeleteTarget } from '../../hooks/useDeleteTarget';
|
|
import {
|
|
ActionsCell,
|
|
Alert,
|
|
Badge,
|
|
Breadcrumbs,
|
|
Button,
|
|
ConfirmDialog,
|
|
DataTable,
|
|
Field,
|
|
IconButton,
|
|
PageHeader,
|
|
PrimaryCell,
|
|
Select
|
|
} from '../ui';
|
|
|
|
export interface IRoutesScreenProps {
|
|
api: ApprovalRouteApi;
|
|
dictionary?: PortalDictionaryClient;
|
|
onOpenSteps: (route: TemplateRoute) => void;
|
|
}
|
|
|
|
export const RoutesScreen: React.FC<IRoutesScreenProps> = ({ api, dictionary, onOpenSteps }) => {
|
|
const requestTypes = useRequestTypes(api);
|
|
const portalDictionaries = usePortalDictionaries(dictionary);
|
|
|
|
const [filterRequestTypeId, setFilterRequestTypeId] = React.useState('');
|
|
const routes = useTemplateRoutes(api, filterRequestTypeId ? Number(filterRequestTypeId) : undefined);
|
|
|
|
const [editing, setEditing] = React.useState<TemplateRoute | undefined>();
|
|
const [showForm, setShowForm] = React.useState(false);
|
|
|
|
const deleteTarget = useDeleteTarget<TemplateRoute>(item => api.templateRoutes.remove(item.id), routes.reload);
|
|
|
|
const requestTypeName = (id: number): string => requestTypes.items.find(rt => rt.id === id)?.name ?? `#${id}`;
|
|
|
|
// Пока справочник не загрузился (или маршрут ссылается на удалённую запись),
|
|
// показываем усечённый Guid — чтобы строка таблицы не выглядела пустой.
|
|
const companyName = (id: string): string => portalDictionaries.companies.find(c => c.id === id)?.name ?? shortGuid(id);
|
|
const departmentName = (id: string): string => portalDictionaries.departments.find(d => d.id === id)?.name ?? shortGuid(id);
|
|
|
|
const openCreate = (): void => {
|
|
setEditing(undefined);
|
|
setShowForm(true);
|
|
};
|
|
|
|
const openEdit = (item: TemplateRoute): void => {
|
|
setEditing(item);
|
|
setShowForm(true);
|
|
};
|
|
|
|
return (
|
|
<div>
|
|
<Breadcrumbs items={[{ label: texts.common.home }, { label: texts.routes.breadcrumb }]} />
|
|
<PageHeader
|
|
icon="route"
|
|
subtitle={texts.routes.subtitle}
|
|
title={texts.routes.title}
|
|
actions={<Button variant="accent" icon="plus" onClick={openCreate}>{texts.routes.add}</Button>}
|
|
/>
|
|
|
|
<div className={styles.filter}>
|
|
<Field label={texts.routes.filterLabel}>
|
|
<Select
|
|
options={requestTypes.items.map(rt => ({ value: String(rt.id), label: rt.name }))}
|
|
placeholder={texts.routes.filterAll}
|
|
value={filterRequestTypeId}
|
|
onChange={e => setFilterRequestTypeId(e.target.value)}
|
|
/>
|
|
</Field>
|
|
</div>
|
|
|
|
{portalDictionaries.error && <Alert kind="warn">{texts.routes.dictionaryFailed(portalDictionaries.error)}</Alert>}
|
|
{routes.error && <Alert kind="err">{routes.error}</Alert>}
|
|
{deleteTarget.error && <Alert kind="err">{deleteTarget.error}</Alert>}
|
|
|
|
<DataTable
|
|
columns={[
|
|
{
|
|
key: 'requestType',
|
|
header: texts.routes.columnRequestType,
|
|
render: r => <PrimaryCell>{requestTypeName(r.requestTypeId)}</PrimaryCell>
|
|
},
|
|
{
|
|
key: 'routeType',
|
|
header: texts.routes.columnRouteType,
|
|
render: r => <Badge variant={r.routeType === RouteType.Parallel ? 'blue' : 'gold'}>{routeTypeLabels[r.routeType]}</Badge>
|
|
},
|
|
{ key: 'company', header: texts.routes.columnCompany, render: r => companyName(r.companyId) },
|
|
{ key: 'department', header: texts.routes.columnDepartment, render: r => (r.departmentId ? departmentName(r.departmentId) : texts.common.empty) },
|
|
{
|
|
key: 'byProject',
|
|
header: texts.routes.columnByProject,
|
|
align: 'center',
|
|
render: r => (r.byProject ? <Badge variant="ok">{texts.common.yes}</Badge> : <Badge variant="neutral">{texts.common.no}</Badge>)
|
|
},
|
|
{
|
|
key: 'control',
|
|
header: texts.routes.columnControl,
|
|
align: 'center',
|
|
render: r => (r.requiresControl ? <Badge variant="gold">{texts.routes.controlRequired}</Badge> : <Badge variant="neutral">{texts.common.no}</Badge>)
|
|
},
|
|
{
|
|
key: 'actions',
|
|
header: '',
|
|
width: '160px',
|
|
align: 'right',
|
|
sticky: true,
|
|
render: r => (
|
|
<ActionsCell>
|
|
<Button size="sm" variant="secondary" icon="clipboard" onClick={() => onOpenSteps(r)}>{texts.routes.openSteps}</Button>
|
|
<IconButton icon="edit" label={texts.common.edit} onClick={() => openEdit(r)} />
|
|
<IconButton icon="trash" label={texts.common.remove} danger onClick={() => deleteTarget.ask(r)} />
|
|
</ActionsCell>
|
|
)
|
|
}
|
|
]}
|
|
rows={routes.items}
|
|
keyOf={r => r.id}
|
|
emptyMessage={routes.isLoading ? texts.common.loading : texts.routes.emptyList}
|
|
/>
|
|
|
|
{showForm && (
|
|
<RouteFormModal
|
|
api={api}
|
|
editing={editing}
|
|
initialRequestTypeId={filterRequestTypeId}
|
|
requestTypes={requestTypes.items}
|
|
companies={portalDictionaries.companies}
|
|
departments={portalDictionaries.departments}
|
|
dictionaryLoading={portalDictionaries.isLoading}
|
|
onSaved={routes.reload}
|
|
onClose={() => setShowForm(false)}
|
|
/>
|
|
)}
|
|
|
|
{deleteTarget.target && (
|
|
<ConfirmDialog
|
|
title={texts.routes.deleteTitle}
|
|
text={texts.routes.deleteText(deleteTarget.target.id)}
|
|
confirmLabel={texts.common.remove}
|
|
busy={deleteTarget.isBusy}
|
|
onCancel={deleteTarget.cancel}
|
|
onConfirm={deleteTarget.confirm}
|
|
/>
|
|
)}
|
|
</div>
|
|
);
|
|
};
|