From 59f9ebdafe1783081d03f1b87160680b9c496c46 Mon Sep 17 00:00:00 2001 From: Andrey Epifancev Date: Wed, 11 Nov 2026 17:10:17 +0400 Subject: [PATCH] =?UTF-8?q?feat(stages):=20=D1=84=D0=B8=D0=BB=D1=8C=D1=82?= =?UTF-8?q?=D1=80=D1=8B=20=D0=B8=20=D1=81=D0=BE=D1=80=D1=82=D0=B8=D1=80?= =?UTF-8?q?=D0=BE=D0=B2=D0=BA=D0=B0=20=D0=B2=20=D0=BE=D0=BA=D0=BD=D0=B5=20?= =?UTF-8?q?=C2=AB=D0=AD=D1=82=D0=B0=D0=BF=D1=8B=C2=BB=20(AURORA-1881)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- web/src/components/Stages/SettingModal.jsx | 46 +++++++++++++-- web/src/components/Stages/StagesFilters.jsx | 56 +++++++++++++++++++ .../Stages/StagesTable/StagesTable.jsx | 4 +- web/src/components/Stages/stageFilters.js | 6 +- 4 files changed, 103 insertions(+), 9 deletions(-) create mode 100644 web/src/components/Stages/StagesFilters.jsx diff --git a/web/src/components/Stages/SettingModal.jsx b/web/src/components/Stages/SettingModal.jsx index ad213bd..1f7d058 100644 --- a/web/src/components/Stages/SettingModal.jsx +++ b/web/src/components/Stages/SettingModal.jsx @@ -1,5 +1,5 @@ import { Box, CircularProgress, Stack } from '@mui/material'; -import { useEffect, useState } from 'react'; +import { useEffect, useMemo, useState } from 'react'; import { useAuth } from '../../app/context/AuthProvider'; import { useTaskStages } from '../../hooks/useTaskStages'; import { useProjectStages } from '../../hooks/useProjectStages'; @@ -10,7 +10,9 @@ import { EditContainer } from '../common/Modal/ModalStyled'; import { WhitePlus } from '../common/icons/icons'; import { AddStagesModal } from './AddStagesModal'; import { DeleteStageModal } from './DeleteStageModal'; +import { StagesFilters } from './StagesFilters'; import { StagesTable } from './StagesTable/StagesTable'; +import { EMPTY_STAGE_FILTERS, filterStages, sortStages } from './stageFilters'; export const SettingModal = ({ isOpen = true, @@ -50,6 +52,24 @@ export const SettingModal = ({ const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false); const [stageToDelete, setStageToDelete] = useState(null); const [sheetOptions, setSheetOptions] = useState([]); + const [filters, setFilters] = useState(EMPTY_STAGE_FILTERS); + + useEffect(() => { + if (!isOpen) setFilters(EMPTY_STAGE_FILTERS); + }, [isOpen]); + + const allStages = stagesInfo.stages || []; + const visibleStages = useMemo( + () => + filterStages( + sortStages( + allStages, + (sheets || []).map((s) => s.sheet), + ), + filters, + ), + [allStages, sheets, filters], + ); const handleAddStage = () => { setStageForEdit(null); @@ -119,11 +139,25 @@ export const SettingModal = ({ ) : ( - + <> + + 0 && visibleStages.length === 0 + ? 'По выбранным фильтрам этапы не найдены' + : undefined + } + onEdit={handleEditStage} + onDelete={handleDeleteStage} + /> + )} {isAdmin && ( { + const title = SHEET_NAME[sheet] || name || sheet; + return direction ? `${title} — ${DIRECTION_TRANSLATE[direction] ?? direction}` : title; +}; + +export const StagesFilters = ({ filters, onChange, sheets = [], showSheet = true, showRole = false }) => { + const changeFilter = (key, value) => onChange({ ...filters, [key]: value }); + const sheetOptions = []; + for (const item of sheets) { + const value = getSheetKey(item); + if (!sheetOptions.some((o) => o.value === value)) sheetOptions.push({ value, label: getSheetLabel(item) }); + } + + const selects = [ + showSheet && ['sheetKey', 'Лист', sheetOptions], + showRole && ['role', 'Роль', Object.entries(STAGE_ROLE_RUSSIAN_NAME).map(([value, label]) => ({ value, label }))], + ['status', 'Статус', STATUS_OPTIONS.map((value) => ({ value, label: value }))], + ].filter(Boolean); + + return ( + + changeFilter('search', e.target.value)} + sx={{ minWidth: 220, flex: 1 }} + /> + {selects.map(([key, label, options]) => ( + changeFilter(key, e.target.value)} + sx={{ minWidth: 170 }}> + Все + {options.map(({ value, label: text }) => ( + + {text} + + ))} + + ))} + onChange({ ...EMPTY_STAGE_FILTERS })}>Сбросить + + ); +}; diff --git a/web/src/components/Stages/StagesTable/StagesTable.jsx b/web/src/components/Stages/StagesTable/StagesTable.jsx index 04bd411..1e68c5d 100644 --- a/web/src/components/Stages/StagesTable/StagesTable.jsx +++ b/web/src/components/Stages/StagesTable/StagesTable.jsx @@ -9,7 +9,7 @@ import { getStageKey } from '../utils'; import { StyledTable, StyledTableContainer, StyledTableHead, StyledTableHeadRow, StyledTableRow } from './StagesTable.style'; import { getStageStatus, getStatusColors } from './utils'; -export const StagesTable = ({ stages, onEdit, onDelete }) => { +export const StagesTable = ({ stages, onEdit, onDelete, emptyText = 'Нет данных для отображения' }) => { const [anchorEl, setAnchorEl] = useState(null); const [selectedStage, setSelectedStage] = useState(null); const { user } = useAuth(); @@ -57,7 +57,7 @@ export const StagesTable = ({ stages, onEdit, onDelete }) => { {!stages || stages.length === 0 ? ( - Нет данных для отображения + {emptyText} ) : ( diff --git a/web/src/components/Stages/stageFilters.js b/web/src/components/Stages/stageFilters.js index 4fdb979..823df24 100644 --- a/web/src/components/Stages/stageFilters.js +++ b/web/src/components/Stages/stageFilters.js @@ -6,7 +6,11 @@ export const DIRECTION_ORDER = ['Support', 'Development']; export const getSheetKey = ({ sheet, direction }) => `${sheet}|${direction ?? ''}`; -const normalize = (value) => String(value ?? '').trim().toLowerCase().replaceAll('ё', 'е'); +const normalize = (value) => + String(value ?? '') + .trim() + .toLowerCase() + .replaceAll('ё', 'е'); export const filterStages = (stages, filters = EMPTY_STAGE_FILTERS, now = new Date()) => { const { search = '', sheetKey = '', role = '', status = '' } = filters ?? {};