diff --git a/web/src/app/Routes.jsx b/web/src/app/Routes.jsx index f86c37a..464298c 100644 --- a/web/src/app/Routes.jsx +++ b/web/src/app/Routes.jsx @@ -9,7 +9,7 @@ import FormsPage from '../pages/FormsPage'; import LoginPage from '../pages/LoginPage'; import NewFormTablePage from '../pages/NewFormTablePage.jsx'; import NewTablePage from '../pages/NewTablePage.jsx'; -import SummaryPage from '../pages/ProjectPages/SummaryPage.jsx'; +import NavigationProjectPage from '../pages/ProjectPages/NavigationProjectPage.jsx'; import ProjectsPage from '../pages/ProjectsPage/ProjectsPage.jsx'; import SvodPage from '../pages/SvodPage/SvodPage.jsx'; import TablePage from '../pages/TablePage'; @@ -39,7 +39,7 @@ export const AppRoutes = () => { }> } /> } /> - } /> + } /> } /> } /> } /> @@ -54,7 +54,7 @@ export const AppRoutes = () => { } /> } /> } /> - } /> + } /> } /> diff --git a/web/src/pages/ProjectPages/SummaryPage.jsx b/web/src/pages/ProjectPages/NavigationProjectPage.jsx similarity index 65% rename from web/src/pages/ProjectPages/SummaryPage.jsx rename to web/src/pages/ProjectPages/NavigationProjectPage.jsx index 572fac1..64c1817 100644 --- a/web/src/pages/ProjectPages/SummaryPage.jsx +++ b/web/src/pages/ProjectPages/NavigationProjectPage.jsx @@ -1,5 +1,4 @@ -import { Box, CircularProgress, Paper, Typography } from '@mui/material'; -import { MaterialReactTable, useMaterialReactTable } from 'material-react-table'; +import { Box, Typography } from '@mui/material'; import { useEffect, useMemo, useState, useCallback } from 'react'; import { useNavigate } from 'react-router-dom'; import { toast } from 'react-toastify'; @@ -11,14 +10,15 @@ import { WhitePlus } from '../../components/common/icons/icons'; import Modal from '../../components/common/Modal/Modal'; import { DEFAULT_PROJECT_CONFIG } from '../../constants/projectConfig'; import { ModalCreateProject } from '../ProjectsPage/ModalCreateProject'; -import { createFirstColumns, secondColumns } from './columns'; import EditModal from './components/EditModal/EditModal'; +import ProjectsTable from './components/ProjectsTable/ProjectsTable'; +import ProjectSummaryTable from './components/ProjectSummaryTable/ProjectSummaryTable'; import TableFilters from './components/TableFilters/TableFilters'; // Импортируем компонент фильтров import { handleNavigateClick } from './utils/tableHandlers'; import { useAuth } from '../../app/context/AuthProvider'; import { ROLES_NAME_ID } from '../../constants/constants'; -const SummaryPage = () => { +const NavigationProjectPage = () => { const navigate = useNavigate(); const [selectedRow, setSelectedRow] = useState(null); @@ -216,7 +216,7 @@ const SummaryPage = () => { }; }, [selectedRow?.original?.id]); - const getFilteredData = () => { + const filteredData = useMemo(() => { if (isArchiveMode) { return archivedData; } @@ -225,6 +225,8 @@ const SummaryPage = () => { return tableData; } + const query = searchQuery.toLowerCase().trim(); + return tableData .filter((item) => { if (selectedStatus && item.status !== selectedStatus) { @@ -237,15 +239,10 @@ const SummaryPage = () => { return true; }) .map((item) => { - if (!searchQuery) { + if (!query) { return item; } - const query = searchQuery.toLowerCase().trim(); - - // Проверяем, совпадает ли родитель - const parentMatches = (item.name || '').toLowerCase().includes(query); - // Фильтруем дочерние элементы - оставляем только те, что совпадают с поиском const filteredSubRows = item.sub_rows?.filter((subItem) => (subItem.project || '').toLowerCase().includes(query)) || []; @@ -256,32 +253,18 @@ const SummaryPage = () => { }; }) .filter((item) => { - if (!searchQuery) { + if (!query) { return true; } - const query = searchQuery.toLowerCase().trim(); const parentMatches = (item.name || '').toLowerCase().includes(query); const hasMatchingChildren = item.sub_rows && item.sub_rows.length > 0; return parentMatches || hasMatchingChildren; }); - }; - const filteredData = getFilteredData(); + }, [archivedData, isArchiveMode, searchQuery, selectedBranch, selectedStatus, tableData]); - const handleRowClick = (row) => { - if (row.depth === 0 && row) { - setSelectedRow(row); - } - }; - - const isChildOfSelectedRow = (row) => { - if (!selectedRow) return false; - const parentRowId = row.parentId; - return parentRowId === selectedRow.id; - }; - - const onEdit = (row) => { + const onEdit = useCallback((row) => { const isSmeta = row.depth > 0; const parentProject = isSmeta ? row.getParentRow()?.original : row.original; const projectId = parentProject?.id; @@ -292,11 +275,11 @@ const SummaryPage = () => { _projectFundingByKoDecision: parentProject?.funding_by_ko_decision, }); setEditModalOpen(true); - }; + }, []); - const onNavigate = (row) => { + const onNavigate = useCallback((row) => { handleNavigateClick(row, navigate); - }; + }, [navigate]); const handleDeleteProject = async () => { if (!projectToDelete?.id) return; @@ -362,243 +345,6 @@ const SummaryPage = () => { } }; - const firstColumns = useMemo( - () => createFirstColumns({ onDelete: setProjectToDelete, onEdit, onNavigate, orgUnitNames }), - [orgUnitNames], - ); - - const tableHeadCellStyles = { - fontWeight: 700, - fontSize: '12px', - lineHeight: 1.15, - textAlign: 'left', - backgroundColor: 'rgb(248, 249, 250)', - borderRight: '1px solid #e0e0e0', - '&:last-child': { - borderRight: 'none', - }, - whiteSpace: 'nowrap', - position: 'sticky', - top: 0, - boxShadow: 'none', - padding: '0.5rem 1rem', - '& .MuiTableSortLabel-icon': { - fontSize: '0.875rem', - }, - '& .MuiTableSortLabel-iconDirectionDesc': { - fontSize: '0.875rem', - }, - '& .MuiTableSortLabel-iconDirectionAsc': { - fontSize: '0.875rem', - }, - }; - - const projectsTableOptions = { - enableColumnActions: false, - enableColumnFilters: false, - enablePagination: isArchiveMode, - manualPagination: isArchiveMode, - enableSorting: true, - enableBottomToolbar: isArchiveMode, - enableTopToolbar: false, - enableExpanding: true, - getSubRows: (row) => row.sub_rows, - muiTableHeadCellProps: { - sx: tableHeadCellStyles, - }, - muiExpandButtonProps: { - sx: { - width: '1.5rem', - height: '1.5rem', - minWidth: '1.5rem', - '& .MuiSvgIcon-root': { - fontSize: '0.875rem', - }, - }, - }, - muiTableBodyRowProps: ({ row }) => { - const isSelected = selectedRow && row.id === selectedRow.id; - const isChild = isChildOfSelectedRow(row); - - return { - sx: { - backgroundColor: '#ffffff', - '&:hover': { - backgroundColor: 'rgb(248, 250, 252)', - }, - '&:hover td::after': { - backgroundColor: 'transparent !important', - }, - ...(row.depth > 0 && { - backgroundColor: 'rgb(252, 252, 253)', - }), - ...((isSelected || isChild) && { - backgroundColor: 'rgb(234, 247, 236)', - '&:hover': { - backgroundColor: 'rgb(225, 242, 229) !important', - }, - }), - }, - onClick: () => handleRowClick(row), - }; - }, - muiTableBodyCellProps: ({ row, column }) => ({ - sx: { - borderRight: '1px solid #e0e0e0', - '&:last-child': { - borderRight: 'none', - }, - '&:hover td::after': { - backgroundColor: 'transparent !important', - }, - textAlign: 'left', - fontSize: '0.875rem', - padding: '0.5rem 1rem', - ...(column.id === 'mrt-row-expand' && { - width: '1.5rem', - maxWidth: '1.5rem', - minWidth: '1.5rem', - padding: '0.5rem 0.25rem', - }), - ...(column.id === 'project' && - row.depth > 0 && { - pl: `${2 + row.depth * 0.5}rem`, - }), - }, - }), - muiTablePaperProps: { - elevation: 0, - sx: { - border: '1px solid #e0e0e0', - borderRadius: '1rem', - overflow: 'hidden', - height: isArchiveMode ? 'auto' : '45vh', - display: 'flex', - flexDirection: 'column', - }, - }, - muiTableContainerProps: { - sx: { - flex: 1, - overflowX: 'auto', - overflowY: isArchiveMode ? 'visible' : 'auto', - '& thead': { - position: 'sticky', - top: 0, - zIndex: 10, - }, - }, - }, - }; - - const summaryProjectTableOptions = { - enableColumnActions: false, - enableColumnFilters: false, - enablePagination: false, - enableSorting: true, - enableBottomToolbar: false, - enableTopToolbar: false, - enableExpanding: false, - muiTableBodyRowProps: ({ row }) => ({ - sx: { - backgroundColor: '#ffffff', - '&:hover': { - backgroundColor: 'rgb(248, 250, 252)', - }, - '&:hover td::after': { - backgroundColor: 'transparent !important', - }, - boxShadow: 'none', - }, - }), - muiTableHeadCellProps: { - sx: tableHeadCellStyles, - }, - muiTableBodyCellProps: ({ column }) => ({ - sx: { - borderRight: '1px solid #e0e0e0', - '&:last-child': { - borderRight: 'none', - }, - textAlign: 'left', - fontSize: '0.875rem', - padding: '0.5rem 1rem', - }, - }), - muiTablePaperProps: { - elevation: 0, - sx: { - border: '1px solid #e0e0e0', - borderRadius: '0 0 1rem 1rem', - overflow: 'hidden', - height: '100%', - minHeight: 0, - display: 'flex', - flexDirection: 'column', - }, - }, - muiTableContainerProps: { - sx: { - flex: 1, - overflow: 'auto', - '& thead': { - position: 'sticky', - top: 0, - zIndex: 10, - }, - }, - }, - initialState: { - columnPinning: { left: ['project'] }, - }, - }; - - const projectsTable = useMaterialReactTable({ - columns: firstColumns, - data: filteredData, - ...projectsTableOptions, - localization: { - expand: 'Раскрыть', - expandAll: 'Раскрыть все', - collapse: 'Свернуть', - collapseAll: 'Свернуть все', - noRecordsToDisplay: 'Нет данных для отображения', - }, - initialState: { - columnPinning: { right: ['actions'], left: ['mrt-row-expand', 'project'] }, - }, - ...(isArchiveMode ? { - rowCount: archivedTotalCount, - onPaginationChange: setArchivedPagination, - } : {}), - state: { - isLoading: isLoading || isProjectsLoading, - ...(isArchiveMode ? { pagination: archivedPagination } : {}), - }, - }); - - const summaryProjectTable = useMaterialReactTable({ - columns: secondColumns, - data: summaryData, - ...summaryProjectTableOptions, - localization: { - noRecordsToDisplay: 'Нет данных для отображения', - rowsPerPage: 'Строк на странице', - of: 'из', - }, - initialState: { - columnPinning: { left: ['data.header.name'] }, - }, - state: { isLoading: isSummaryLoading }, - }); - - const getSelectedProjectTitle = () => { - if (selectedRow?.original.name) { - return selectedRow.original.name; - } - return 'Проект не выбран'; - }; - // Обработчики фильтров const handleBranchChange = (value) => { setSelectedRow(null); @@ -668,61 +414,26 @@ const SummaryPage = () => { - {/* Первая таблица */} - - - + - {/* Вторая таблица */} - - - {getSelectedProjectTitle()} - - {selectedRow ? ( - - {isSummaryLoading && !summaryData.length ? ( - - - - ) : } - - ) : ( - - Разверните или выберите проект в верхней таблице - - )} - + {/* Модалка редактирования */} setEditModalOpen(false)} rowData={selectedRowData} onSave={onSaveEdit} isSaving={isSaving} orgUnitNames={orgUnitNames} /> @@ -757,4 +468,4 @@ const SummaryPage = () => { ); }; -export default SummaryPage; +export default NavigationProjectPage; diff --git a/web/src/pages/ProjectPages/components/ProjectSummaryTable/ProjectSummaryTable.jsx b/web/src/pages/ProjectPages/components/ProjectSummaryTable/ProjectSummaryTable.jsx new file mode 100644 index 0000000..1280031 --- /dev/null +++ b/web/src/pages/ProjectPages/components/ProjectSummaryTable/ProjectSummaryTable.jsx @@ -0,0 +1,148 @@ +import { Box, CircularProgress, Paper, Typography } from '@mui/material'; +import { MaterialReactTable, useMaterialReactTable } from 'material-react-table'; +import { secondColumns } from '../../columns'; + +const tableHeadCellStyles = { + fontWeight: 700, + fontSize: '12px', + lineHeight: 1.15, + textAlign: 'left', + backgroundColor: 'rgb(248, 249, 250)', + borderRight: '1px solid #e0e0e0', + '&:last-child': { + borderRight: 'none', + }, + whiteSpace: 'nowrap', + position: 'sticky', + top: 0, + boxShadow: 'none', + padding: '0.5rem 1rem', + '& .MuiTableSortLabel-icon': { + fontSize: '0.875rem', + }, + '& .MuiTableSortLabel-iconDirectionDesc': { + fontSize: '0.875rem', + }, + '& .MuiTableSortLabel-iconDirectionAsc': { + fontSize: '0.875rem', + }, +}; + +const ProjectSummaryTable = ({ data, isLoading, selectedRow }) => { + const table = useMaterialReactTable({ + columns: secondColumns, + data, + enableColumnActions: false, + enableColumnFilters: false, + enablePagination: false, + enableSorting: true, + enableBottomToolbar: false, + enableTopToolbar: false, + enableExpanding: false, + muiTableBodyRowProps: { + sx: { + backgroundColor: '#ffffff', + '&:hover': { + backgroundColor: 'rgb(248, 250, 252)', + }, + '&:hover td::after': { + backgroundColor: 'transparent !important', + }, + boxShadow: 'none', + }, + }, + muiTableHeadCellProps: { + sx: tableHeadCellStyles, + }, + muiTableBodyCellProps: { + sx: { + borderRight: '1px solid #e0e0e0', + '&:last-child': { + borderRight: 'none', + }, + textAlign: 'left', + fontSize: '0.875rem', + padding: '0.5rem 1rem', + }, + }, + muiTablePaperProps: { + elevation: 0, + sx: { + border: '1px solid #e0e0e0', + borderRadius: '0 0 1rem 1rem', + overflow: 'hidden', + height: '100%', + minHeight: 0, + display: 'flex', + flexDirection: 'column', + }, + }, + muiTableContainerProps: { + sx: { + flex: 1, + overflow: 'auto', + '& thead': { + position: 'sticky', + top: 0, + zIndex: 10, + }, + }, + }, + localization: { + noRecordsToDisplay: 'Нет данных для отображения', + }, + initialState: { + columnPinning: { left: ['data.header.name'] }, + }, + state: { isLoading }, + }); + + return ( + + + {selectedRow?.original.name || 'Проект не выбран'} + + {selectedRow ? ( + + {isLoading && !data.length ? ( + + + + ) : ( + + )} + + ) : ( + + Разверните или выберите проект в верхней таблице + + )} + + ); +}; + +export default ProjectSummaryTable; diff --git a/web/src/pages/ProjectPages/components/ProjectsTable/ProjectsTable.jsx b/web/src/pages/ProjectPages/components/ProjectsTable/ProjectsTable.jsx new file mode 100644 index 0000000..a56ae40 --- /dev/null +++ b/web/src/pages/ProjectPages/components/ProjectsTable/ProjectsTable.jsx @@ -0,0 +1,189 @@ +import { Paper } from '@mui/material'; +import { MaterialReactTable, useMaterialReactTable } from 'material-react-table'; +import { useMemo } from 'react'; +import { createFirstColumns } from '../../columns'; + +const tableHeadCellStyles = { + fontWeight: 700, + fontSize: '12px', + lineHeight: 1.15, + textAlign: 'left', + backgroundColor: 'rgb(248, 249, 250)', + borderRight: '1px solid #e0e0e0', + '&:last-child': { + borderRight: 'none', + }, + whiteSpace: 'nowrap', + position: 'sticky', + top: 0, + boxShadow: 'none', + padding: '0.5rem 1rem', + '& .MuiTableSortLabel-icon': { + fontSize: '0.875rem', + }, + '& .MuiTableSortLabel-iconDirectionDesc': { + fontSize: '0.875rem', + }, + '& .MuiTableSortLabel-iconDirectionAsc': { + fontSize: '0.875rem', + }, +}; + +const ProjectsTable = ({ + data, + isLoading, + isArchiveMode, + archivedTotalCount, + archivedPagination, + onPaginationChange, + selectedRow, + onRowSelect, + onDelete, + onEdit, + onNavigate, + orgUnitNames, +}) => { + const columns = useMemo( + () => createFirstColumns({ onDelete, onEdit, onNavigate, orgUnitNames }), + [onDelete, onEdit, onNavigate, orgUnitNames], + ); + + const table = useMaterialReactTable({ + columns, + data, + enableColumnActions: false, + enableColumnFilters: false, + enablePagination: isArchiveMode, + manualPagination: isArchiveMode, + enableSorting: true, + enableBottomToolbar: isArchiveMode, + enableTopToolbar: false, + enableExpanding: true, + getSubRows: (row) => row.sub_rows, + muiTableHeadCellProps: { + sx: tableHeadCellStyles, + }, + muiExpandButtonProps: { + sx: { + width: '1.5rem', + height: '1.5rem', + minWidth: '1.5rem', + '& .MuiSvgIcon-root': { + fontSize: '0.875rem', + }, + }, + }, + muiTableBodyRowProps: ({ row }) => { + const isSelected = selectedRow && row.id === selectedRow.id; + const isChild = selectedRow && row.parentId === selectedRow.id; + + return { + sx: { + backgroundColor: '#ffffff', + '&:hover': { + backgroundColor: 'rgb(248, 250, 252)', + }, + '&:hover td::after': { + backgroundColor: 'transparent !important', + }, + ...(row.depth > 0 && { + backgroundColor: 'rgb(252, 252, 253)', + }), + ...((isSelected || isChild) && { + backgroundColor: 'rgb(234, 247, 236)', + '&:hover': { + backgroundColor: 'rgb(225, 242, 229) !important', + }, + }), + }, + onClick: () => { + if (row.depth === 0) onRowSelect(row); + }, + }; + }, + muiTableBodyCellProps: ({ row, column }) => ({ + sx: { + borderRight: '1px solid #e0e0e0', + '&:last-child': { + borderRight: 'none', + }, + '&:hover td::after': { + backgroundColor: 'transparent !important', + }, + textAlign: 'left', + fontSize: '0.875rem', + padding: '0.5rem 1rem', + ...(column.id === 'mrt-row-expand' && { + width: '1.5rem', + maxWidth: '1.5rem', + minWidth: '1.5rem', + padding: '0.5rem 0.25rem', + }), + ...(column.id === 'project' && + row.depth > 0 && { + pl: `${2 + row.depth * 0.5}rem`, + }), + }, + }), + muiTablePaperProps: { + elevation: 0, + sx: { + border: '1px solid #e0e0e0', + borderRadius: '1rem', + overflow: 'hidden', + height: isArchiveMode ? 'auto' : '45vh', + display: 'flex', + flexDirection: 'column', + }, + }, + muiTableContainerProps: { + sx: { + flex: 1, + overflowX: 'auto', + overflowY: isArchiveMode ? 'visible' : 'auto', + '& thead': { + position: 'sticky', + top: 0, + zIndex: 10, + }, + }, + }, + localization: { + expand: 'Раскрыть', + expandAll: 'Раскрыть все', + collapse: 'Свернуть', + collapseAll: 'Свернуть все', + noRecordsToDisplay: 'Нет данных для отображения', + rowsPerPage: 'Строк на странице', + of: 'из', + }, + initialState: { + columnPinning: { right: ['actions'], left: ['mrt-row-expand', 'project'] }, + }, + ...(isArchiveMode + ? { + rowCount: archivedTotalCount, + onPaginationChange, + } + : {}), + state: { + isLoading, + ...(isArchiveMode ? { pagination: archivedPagination } : {}), + }, + }); + + return ( + + + + ); +}; + +export default ProjectsTable;