import { Box, CircularProgress, Paper, Typography } from '@mui/material';
import { MaterialReactTable, useMaterialReactTable } from 'material-react-table';
import { useEffect, useMemo, useState, useCallback } from 'react';
import { useNavigate } from 'react-router-dom';
import { toast } from 'react-toastify';
import { ProjectsApi } from '../../api/projects';
import { SspApi } from '../../api/ssp';
import { HeaderSwitch } from '../../components/HeaderMenu/HeaderSwitch';
import { DangerOutlinedButton, PrimaryButton } from '../../components/common/Buttons/Buttons';
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 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 navigate = useNavigate();
const [selectedRow, setSelectedRow] = useState(null);
const [editModalOpen, setEditModalOpen] = useState(false);
const [selectedRowData, setSelectedRowData] = useState(null);
const [tableData, setTableData] = useState([]);
const [summaryData, setSummaryData] = useState([]);
const [isProjectsLoading, setIsProjectsLoading] = useState(true);
const [isSummaryLoading, setIsSummaryLoading] = useState(false);
const [orgUnitNames, setOrgUnitNames] = useState({});
const [orgUnits, setOrgUnits] = useState([]);
const [isSaving, setIsSaving] = useState(false);
const [projectsReloadKey, setProjectsReloadKey] = useState(0);
const [createModalOpen, setCreateModalOpen] = useState(false);
const [projectToDelete, setProjectToDelete] = useState(null);
const [isDeleting, setIsDeleting] = useState(false);
const [isLoading, setIsLoading] = useState(true);
const { user } = useAuth();
// Состояния для фильтров
const [selectedBranch, setSelectedBranch] = useState('');
const [searchQuery, setSearchQuery] = useState('');
const loadData = useCallback(async () => {
if (!user) return;
setIsLoading(true);
try {
const isAdmin = user.role_id === ROLES_NAME_ID.admin;
if (isAdmin) {
const sspResponse = await (
SspApi.getAll({ is_active: true })
);
if (!sspResponse.success) {
toast.error('Ошибка загрузки списка ССП');
return;
}
setOrgUnits(sspResponse.result);
setOrgUnitNames(
Object.fromEntries((sspResponse.result || []).map((orgUnit) => [orgUnit.id, orgUnit.title])),
);
} else {
setOrgUnits(user.org_units ?? []);
setOrgUnitNames(
Object.fromEntries((user.org_units || []).map((orgUnit) => [orgUnit.id, orgUnit.title])),
);
}
} catch (error) {
console.error('Error loading data:', error);
toast.error(error.response?.data?.message || 'Ошибка загрузки данных');
} finally {
setIsLoading(false);
}
}, [user]);
useEffect(() => {
loadData();
}, [loadData]);
const createProjectConfig = useMemo(() => ({
...DEFAULT_PROJECT_CONFIG,
'ССП/РФ': {
...DEFAULT_PROJECT_CONFIG['ССП/РФ'],
options: orgUnits,
},
}), [orgUnits]);
useEffect(() => {
let active = true;
const loadProjects = async () => {
setIsProjectsLoading(true);
try {
const response = await ProjectsApi.listWithReports({
limit: 1000,
...(selectedBranch?.id ? { branch_id: selectedBranch.id } : {}),
});
if (active) setTableData(response.result || []);
} catch (requestError) {
console.error('Error loading projects with reports:', requestError);
if (active) toast.error('Не удалось загрузить проекты');
} finally {
if (active) setIsProjectsLoading(false);
}
};
loadProjects();
return () => {
active = false;
};
}, [selectedBranch?.id, projectsReloadKey]);
useEffect(() => {
let active = true;
if (!selectedRow?.original?.id) {
setSummaryData([]);
return () => {
active = false;
};
}
const loadSummary = async () => {
setIsSummaryLoading(true);
try {
const response = await ProjectsApi.getSummary(selectedRow.original.id);
if (active) setSummaryData(response.result.filter(v => v.row_type !== 'PROJECT') || []);
} catch (requestError) {
console.error('Error loading project summary:', requestError);
if (active) {
setSummaryData([]);
toast.error('Не удалось загрузить сводку проекта');
}
} finally {
if (active) setIsSummaryLoading(false);
}
};
loadSummary();
return () => {
active = false;
};
}, [selectedRow?.original?.id]);
const getFilteredData = () => {
if (!selectedBranch && !searchQuery) {
return tableData;
}
return tableData
.filter((item) => {
// Фильтр по филиалу (только для корневых)
if (selectedBranch) {
return item.org_unit_id === selectedBranch.id;
}
return true;
})
.map((item) => {
if (!searchQuery) {
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)) || [];
// Возвращаем элемент с отфильтрованными дочерними элементами
return {
...item,
sub_rows: filteredSubRows,
};
})
.filter((item) => {
if (!searchQuery) {
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();
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 isSmeta = row.depth > 0;
const projectId = isSmeta ? row.getParentRow()?.original?.id : row.original.id;
setSelectedRowData({
...row.original,
_rowType: isSmeta ? 'smeta' : 'project',
_projectId: projectId,
});
setEditModalOpen(true);
};
const onNavigate = (row) => {
handleNavigateClick(row, navigate);
};
const handleDeleteProject = async () => {
if (!projectToDelete?.id) return;
setIsDeleting(true);
try {
await ProjectsApi.delete(projectToDelete.id);
if (selectedRow?.original?.id === projectToDelete.id) {
setSelectedRow(null);
setSummaryData([]);
}
setProjectToDelete(null);
setProjectsReloadKey((key) => key + 1);
toast.success('Проект успешно удалён');
} catch (requestError) {
console.error('Error deleting project:', requestError);
toast.error(requestError.response?.data?.detail || requestError.response?.data?.message || 'Не удалось удалить проект');
} finally {
setIsDeleting(false);
}
};
const onSaveEdit = async (formData) => {
setIsSaving(true);
try {
if (formData._rowType === 'smeta') {
const { is_in_plan, is_in_plan_q2, is_in_plan_q3, is_in_plan_q4, development_block, reserve_to_prrs_ahr, reserve_to_prrs_kv } = formData;
await ProjectsApi.updateSmeta(formData._projectId, formData.year, {
is_in_plan,
is_in_plan_q2,
is_in_plan_q3,
is_in_plan_q4,
development_block: development_block || null,
reserve_to_prrs_ahr: reserve_to_prrs_ahr === '' ? null : reserve_to_prrs_ahr,
reserve_to_prrs_kv: reserve_to_prrs_kv === '' ? null : reserve_to_prrs_kv,
});
} else {
const { name, status, technical_number, project_type, vsp_format, placement_type, object_address, staff_count, total_area, org_unit_id, krf_decision_date, fk_decision_date, board_decision_date, open_relocate_close_date, funding_by_ko_decision } = formData;
await ProjectsApi.update(formData._projectId, {
name,
status: status || null,
technical_number: technical_number || null,
project_type: project_type || null,
vsp_format: vsp_format || null,
placement_type: placement_type || null,
object_address: object_address || null,
staff_count: staff_count === '' ? null : staff_count,
total_area: total_area === '' ? null : total_area,
org_unit_id: org_unit_id === '' ? null : org_unit_id,
krf_decision_date: krf_decision_date || null,
fk_decision_date: fk_decision_date || null,
board_decision_date: board_decision_date || null,
open_relocate_close_date: open_relocate_close_date || null,
funding_by_ko_decision: funding_by_ko_decision || null,
});
}
setEditModalOpen(false);
setProjectsReloadKey((key) => key + 1);
} catch (requestError) {
console.error('Error updating project summary row:', requestError);
toast.error(requestError.response?.data?.detail || requestError.response?.data?.message || 'Не удалось сохранить изменения');
} finally {
setIsSaving(false);
}
};
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: false,
enableSorting: true,
enableBottomToolbar: false,
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: '45vh',
display: 'flex',
flexDirection: 'column',
},
},
muiTableContainerProps: {
sx: {
flex: 1,
overflow: '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: '35vh',
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,
initialState: {
columnPinning: { right: ['actions'], left: ['mrt-row-expand', 'project'] },
},
state: { isLoading: isProjectsLoading },
});
const summaryProjectTable = useMaterialReactTable({
columns: secondColumns,
data: summaryData,
...summaryProjectTableOptions,
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);
setSummaryData([]);
setSelectedBranch(value);
};
const handleSearchChange = (value) => {
setSearchQuery(value);
};
const handleCreateProject = async (formData) => {
try {
await ProjectsApi.create({
name: formData['Название проекта'] || '',
project_type: formData['Тип проекта развития'] || null,
vsp_format: formData['Формат ВСП'] || null,
object_address: formData['Адрес объекта'] || '',
staff_count: formData['Целевая численность, чел.'] || 0,
placement_type: formData['Тип размещения'] || null,
total_area: formData['Площадь размещения, м2'] || 0,
year: formData.Год || null,
branch_id: formData['ССП/РФ'] || null,
});
setProjectsReloadKey((key) => key + 1);
toast.success('Проект успешно создан');
} catch (requestError) {
console.error('Error creating project:', requestError);
toast.error(requestError.response?.data?.detail || requestError.response?.data?.message || 'Ошибка при создании проекта');
}
};
return (
<>
{/* Компонент фильтров */}
setCreateModalOpen(true)} startIcon={} sx={{ height: '2.5rem' }}>
Создать проект
{/* Первая таблица */}
{/* Вторая таблица */}
{getSelectedProjectTitle()}
{selectedRow ? (
{isSummaryLoading && !summaryData.length ? (
) : }
) : (
Разверните или выберите проект в верхней таблице
)}
{/* Модалка редактирования */}
setEditModalOpen(false)} rowData={selectedRowData} onSave={onSaveEdit} isSaving={isSaving} orgUnitNames={orgUnitNames} />
setCreateModalOpen(false)}
onCreate={handleCreateProject}
config={createProjectConfig}
/>
!isDeleting && setProjectToDelete(null)}
title='Удалить проект?'
size='small'
actions={
<>
setProjectToDelete(null)} disabled={isDeleting}>
Отмена
{isDeleting ? 'Удаление...' : 'Удалить'}
>
}>
Проект «{projectToDelete?.name}» будет удалён без возможности восстановления.
>
);
};
export default SummaryPage;