643 lines
19 KiB
JavaScript
643 lines
19 KiB
JavaScript
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 (
|
||
<>
|
||
<HeaderSwitch />
|
||
<Box
|
||
sx={{
|
||
p: '1.5rem',
|
||
maxWidth: '1600px',
|
||
mx: 'auto',
|
||
height: '100vh',
|
||
display: 'flex',
|
||
flexDirection: 'column',
|
||
}}>
|
||
<Box sx={{ display: 'flex', justifyContent: 'space-between', mb: 1, }}>
|
||
|
||
{/* Компонент фильтров */}
|
||
<TableFilters
|
||
selectedBranch={selectedBranch}
|
||
onBranchChange={handleBranchChange}
|
||
searchQuery={searchQuery}
|
||
onSearchChange={handleSearchChange}
|
||
/>
|
||
<PrimaryButton onClick={() => setCreateModalOpen(true)} startIcon={<WhitePlus />} sx={{ height: '2.5rem' }}>
|
||
Создать проект
|
||
</PrimaryButton>
|
||
</Box>
|
||
|
||
{/* Первая таблица */}
|
||
<Paper
|
||
sx={{
|
||
mb: '2rem',
|
||
overflow: 'hidden',
|
||
flex: '0 0 45vh',
|
||
borderRadius: '1rem',
|
||
boxShadow: '0px 4px 12px rgba(0, 0, 0, 0.1)',
|
||
}}>
|
||
<MaterialReactTable table={projectsTable} />
|
||
</Paper>
|
||
|
||
{/* Вторая таблица */}
|
||
<Paper
|
||
sx={{
|
||
overflow: 'hidden',
|
||
flex: '0 0 35vh',
|
||
borderRadius: '1rem',
|
||
boxShadow: '0px 4px 12px rgba(0, 0, 0, 0.1)',
|
||
}}>
|
||
<Typography
|
||
variant='subtitle1'
|
||
sx={{
|
||
p: '0.75rem 1.5rem',
|
||
backgroundColor: 'white',
|
||
borderBottom: '1px solid #e0e0e0',
|
||
fontWeight: 'bold',
|
||
flexShrink: 0,
|
||
fontSize: '1.25rem',
|
||
}}>
|
||
{getSelectedProjectTitle()}
|
||
</Typography>
|
||
{selectedRow ? (
|
||
<Box sx={{ height: 'calc(35vh - 3rem)' }}>
|
||
{isSummaryLoading && !summaryData.length ? (
|
||
<Box sx={{ height: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
|
||
<CircularProgress size={32} />
|
||
</Box>
|
||
) : <MaterialReactTable table={summaryProjectTable} />}
|
||
</Box>
|
||
) : (
|
||
<Box
|
||
sx={{
|
||
height: '100%',
|
||
display: 'flex',
|
||
alignItems: 'center',
|
||
justifyContent: 'center',
|
||
color: 'rgb(152, 162, 179)',
|
||
fontSize: '1rem',
|
||
fontWeight: 600,
|
||
}}>
|
||
Разверните или выберите проект в верхней таблице
|
||
</Box>
|
||
)}
|
||
</Paper>
|
||
|
||
{/* Модалка редактирования */}
|
||
<EditModal open={editModalOpen} onClose={() => setEditModalOpen(false)} rowData={selectedRowData} onSave={onSaveEdit} isSaving={isSaving} orgUnitNames={orgUnitNames} />
|
||
<ModalCreateProject
|
||
open={createModalOpen}
|
||
onClose={() => setCreateModalOpen(false)}
|
||
onCreate={handleCreateProject}
|
||
config={createProjectConfig}
|
||
/>
|
||
<Modal
|
||
open={Boolean(projectToDelete)}
|
||
onClose={() => !isDeleting && setProjectToDelete(null)}
|
||
title='Удалить проект?'
|
||
size='small'
|
||
actions={
|
||
<>
|
||
<PrimaryButton onClick={() => setProjectToDelete(null)} disabled={isDeleting}>
|
||
Отмена
|
||
</PrimaryButton>
|
||
<DangerOutlinedButton onClick={handleDeleteProject} disabled={isDeleting}>
|
||
{isDeleting ? 'Удаление...' : 'Удалить'}
|
||
</DangerOutlinedButton>
|
||
</>
|
||
}>
|
||
<Typography>
|
||
Проект «{projectToDelete?.name}» будет удалён без возможности восстановления.
|
||
</Typography>
|
||
</Modal>
|
||
</Box>
|
||
</>
|
||
|
||
);
|
||
};
|
||
|
||
export default SummaryPage;
|