Rework menu for different statuses of equip

This commit is contained in:
Первов Артем
2026-04-30 04:40:42 +03:00
parent fea773bb15
commit 376b4a6a57
7 changed files with 77 additions and 5 deletions

View File

@@ -15,6 +15,7 @@ import { EquipmentStatusChangeCreate } from './resources/equipment-status-change
import { EquipmentStatusChangeEdit } from './resources/equipment-status-change/EquipmentStatusChangeEdit'; import { EquipmentStatusChangeEdit } from './resources/equipment-status-change/EquipmentStatusChangeEdit';
import { EquipmentStatusChangeList } from './resources/equipment-status-change/EquipmentStatusChangeList'; import { EquipmentStatusChangeList } from './resources/equipment-status-change/EquipmentStatusChangeList';
import { EquipmentStatusChangeShow } from './resources/equipment-status-change/EquipmentStatusChangeShow'; import { EquipmentStatusChangeShow } from './resources/equipment-status-change/EquipmentStatusChangeShow';
import { ToirLayout } from './layout/ToirLayout';
function ToirAdmin() { function ToirAdmin() {
const paletteMode = useEmbeddedParentTheme(); const paletteMode = useEmbeddedParentTheme();
@@ -32,6 +33,7 @@ function ToirAdmin() {
return ( return (
<Admin <Admin
layout={ToirLayout}
dataProvider={dataProvider} dataProvider={dataProvider}
authProvider={authProvider} authProvider={authProvider}
theme={theme} theme={theme}
@@ -47,7 +49,7 @@ function ToirAdmin() {
/> />
<Resource <Resource
name="status-changes" name="status-changes"
options={{ label: 'Акты' }} options={{ label: 'Журнал актов' }}
list={EquipmentStatusChangeList} list={EquipmentStatusChangeList}
create={EquipmentStatusChangeCreate} create={EquipmentStatusChangeCreate}
edit={EquipmentStatusChangeEdit} edit={EquipmentStatusChangeEdit}

View File

@@ -14,7 +14,7 @@ const customRu = {
}, },
}, },
'status-changes': { 'status-changes': {
name: 'Акт |||| Акты', name: 'Акт |||| Журнал актов',
fields: { fields: {
id: 'ID', id: 'ID',
equipmentId: 'Оборудование', equipmentId: 'Оборудование',

View File

@@ -0,0 +1,7 @@
import type { ComponentProps, ReactElement } from 'react';
import { Layout } from 'react-admin';
import { ToirMenu } from './ToirMenu';
export function ToirLayout(props: ComponentProps<typeof Layout>): ReactElement {
return <Layout {...props} menu={ToirMenu} />;
}

View File

@@ -0,0 +1,23 @@
import { Menu, MenuItemLink } from 'react-admin';
import {
EQUIPMENT_SIDEBAR_ARCHIVE_FILTER,
EQUIPMENT_SIDEBAR_IN_WORK_FILTER,
equipmentListSearch,
} from './toirMenuLinks';
export function ToirMenu() {
return (
<Menu>
<MenuItemLink to="/equipment" primaryText="Оборудование" />
<MenuItemLink
to={`/equipment?${equipmentListSearch(EQUIPMENT_SIDEBAR_IN_WORK_FILTER)}`}
primaryText="В работе"
/>
<MenuItemLink
to={`/equipment?${equipmentListSearch(EQUIPMENT_SIDEBAR_ARCHIVE_FILTER)}`}
primaryText="Архив"
/>
<MenuItemLink to="/status-changes" primaryText="Журнал актов" />
</Menu>
);
}

View File

@@ -0,0 +1,13 @@
/** Параметры списка оборудования в формате react-admin (как при сохранённом фильтре в URL). */
export function equipmentListSearch(filter: Record<string, unknown>): string {
const params = new URLSearchParams();
params.set('filter', JSON.stringify(filter));
params.set('displayedFilters', JSON.stringify({ status: true }));
return params.toString();
}
/** «В работе» → статус Active (в эксплуатации). См. `equipmentStatusChoices`. */
export const EQUIPMENT_SIDEBAR_IN_WORK_FILTER = { status: ['Active'] };
/** «Архив» → списанное оборудование (WriteOff). */
export const EQUIPMENT_SIDEBAR_ARCHIVE_FILTER = { status: ['WriteOff'] };

View File

@@ -1,3 +1,5 @@
import { useMemo } from 'react';
import { useLocation } from 'react-router-dom';
import { import {
CreateButton, CreateButton,
Datagrid, Datagrid,
@@ -5,13 +7,26 @@ import {
FilterButton, FilterButton,
List, List,
SelectArrayInput, SelectArrayInput,
SelectField,
TextField, TextField,
TextInput, TextInput,
TopToolbar, TopToolbar,
} from 'react-admin'; } from 'react-admin';
import { equipmentStatusChoices } from './shared'; import { equipmentStatusChoices } from './shared';
function parseListFilterFromSearch(search: string): Record<string, unknown> | undefined {
const params = new URLSearchParams(search);
const raw = params.get('filter');
if (!raw) {
return undefined;
}
try {
const parsed = JSON.parse(raw) as Record<string, unknown>;
return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : undefined;
} catch {
return undefined;
}
}
const equipmentFilters = [ const equipmentFilters = [
<TextInput key="q" source="q" label="Поиск" alwaysOn />, <TextInput key="q" source="q" label="Поиск" alwaysOn />,
<SelectArrayInput key="status" source="status" label="Статус" choices={equipmentStatusChoices} />, <SelectArrayInput key="status" source="status" label="Статус" choices={equipmentStatusChoices} />,
@@ -25,14 +40,23 @@ const ListActions = () => (
); );
export function EquipmentList() { export function EquipmentList() {
const location = useLocation();
const filterDefaultValues = useMemo(() => parseListFilterFromSearch(location.search), [location.search]);
const listKey = `${location.pathname}${location.search}`;
return ( return (
<List filters={equipmentFilters} actions={<ListActions />} sort={{ field: 'name', order: 'ASC' }}> <List
key={listKey}
filters={equipmentFilters}
actions={<ListActions />}
sort={{ field: 'name', order: 'ASC' }}
filterDefaultValues={filterDefaultValues}
>
<Datagrid rowClick="show"> <Datagrid rowClick="show">
<TextField source="name" /> <TextField source="name" />
<TextField source="serialNumber" /> <TextField source="serialNumber" />
<DateField source="dateOfInspection" /> <DateField source="dateOfInspection" />
<DateField source="commissionedAt" /> <DateField source="commissionedAt" />
<SelectField source="status" choices={equipmentStatusChoices} />
</Datagrid> </Datagrid>
</List> </List>
); );

View File

@@ -82,9 +82,12 @@ export function buildToirMuiTheme(mode: Mode): Theme {
styleOverrides: { styleOverrides: {
head: { head: {
fontWeight: 700, fontWeight: 700,
fontSize: '0.9rem',
borderBottom: `1px solid ${t.border}`, borderBottom: `1px solid ${t.border}`,
}, },
root: { root: {
fontSize: '0.95rem',
lineHeight: 1.35,
borderBottom: `1px solid ${t.divider}`, borderBottom: `1px solid ${t.divider}`,
}, },
}, },