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;