Merge pull request 'delete project' (#96) from 3-form-project into test
Reviewed-on: #96
This commit is contained in:
commit
afff0eb128
@ -24,6 +24,10 @@ export const ProjectsApi = {
|
||||
return api.patch(`/projects/${projectId}/smeta/${year}`, data).then((r) => r.data);
|
||||
},
|
||||
|
||||
addYear: (projectId, year) => {
|
||||
return api.post(`/projects/${projectId}/year`, { year }).then((r) => r.data);
|
||||
},
|
||||
|
||||
delete: (id) => {
|
||||
return api.delete(`/projects/${id}`).then((r) => r.data);
|
||||
},
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { Box } from '@mui/material';
|
||||
import { Box, Typography } from '@mui/material';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import TableCard from '../TableCard/TableCard';
|
||||
|
||||
@ -16,9 +16,19 @@ const TablesList = ({ filteredTables, query, basePath = '/table', formInfo, isPr
|
||||
}
|
||||
const path = `${basePath}/form/${formInfo.id}/form-type/${isProject ? 'PROJECT' : formInfo.form_type_code}/${table.sheet}/${table?.direction}/${table?.year}`;
|
||||
|
||||
return <TableCard key={table.sheet + '_' + table?.direction} onNavigate={() => handleNavigate(path)} table={table} />;
|
||||
return <TableCard key={`${table.sheet}_${table?.direction}_${table?.year}`} onNavigate={() => handleNavigate(path)} table={table} />;
|
||||
};
|
||||
|
||||
const projectTablesByYear = isProject
|
||||
? Object.entries(
|
||||
filteredTables.reduce((groups, table) => {
|
||||
const year = table.year ?? 'Без года';
|
||||
groups[year] = [...(groups[year] || []), table];
|
||||
return groups;
|
||||
}, {}),
|
||||
).sort(([firstYear], [secondYear]) => Number(secondYear) - Number(firstYear))
|
||||
: [];
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
@ -38,7 +48,28 @@ const TablesList = ({ filteredTables, query, basePath = '/table', formInfo, isPr
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{filteredTables.map((table) => renderTableCard(table, formInfo))}
|
||||
{isProject
|
||||
? projectTablesByYear.map(([year, tables]) => (
|
||||
<Box key={year} sx={{ display: 'flex', flexDirection: 'column', gap: '0.75rem' }}>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: 1,
|
||||
py: 0.5,
|
||||
borderBottom: '1px solid',
|
||||
borderColor: 'divider',
|
||||
}}>
|
||||
<Typography variant='subtitle3' sx={{ fontWeight: 400 }}>
|
||||
{year === 'Без года' ? year : `${year} год`}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: '0.75rem' }}>
|
||||
{tables.map((table) => renderTableCard(table, formInfo))}
|
||||
</Box>
|
||||
</Box>
|
||||
))
|
||||
: filteredTables.map((table) => renderTableCard(table, formInfo))}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
@ -6,8 +6,9 @@ import { toast } from 'react-toastify';
|
||||
import { ProjectsApi } from '../../api/projects';
|
||||
import { SspApi } from '../../api/ssp';
|
||||
import { HeaderSwitch } from '../../components/HeaderMenu/HeaderSwitch';
|
||||
import { PrimaryButton } from '../../components/common/Buttons/Buttons';
|
||||
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';
|
||||
@ -30,6 +31,8 @@ const SummaryPage = () => {
|
||||
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 [selectedBranch, setSelectedBranch] = useState('');
|
||||
@ -191,6 +194,26 @@ const SummaryPage = () => {
|
||||
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 {
|
||||
@ -236,7 +259,7 @@ const SummaryPage = () => {
|
||||
};
|
||||
|
||||
const firstColumns = useMemo(
|
||||
() => createFirstColumns({ onEdit, onNavigate, orgUnitNames }),
|
||||
() => createFirstColumns({ onDelete: setProjectToDelete, onEdit, onNavigate, orgUnitNames }),
|
||||
[orgUnitNames],
|
||||
);
|
||||
|
||||
@ -266,7 +289,7 @@ const SummaryPage = () => {
|
||||
},
|
||||
};
|
||||
|
||||
const summaryProjectsTableOptions = {
|
||||
const projectsTableOptions = {
|
||||
enableColumnActions: false,
|
||||
enableColumnFilters: false,
|
||||
enablePagination: false,
|
||||
@ -362,7 +385,7 @@ const SummaryPage = () => {
|
||||
},
|
||||
};
|
||||
|
||||
const projectTableOptions = {
|
||||
const summaryProjectTableOptions = {
|
||||
enableColumnActions: false,
|
||||
enableColumnFilters: false,
|
||||
enablePagination: false,
|
||||
@ -423,20 +446,23 @@ const SummaryPage = () => {
|
||||
},
|
||||
};
|
||||
|
||||
const summaryProjectsTable = useMaterialReactTable({
|
||||
const projectsTable = useMaterialReactTable({
|
||||
columns: firstColumns,
|
||||
data: filteredData,
|
||||
...summaryProjectsTableOptions,
|
||||
...projectsTableOptions,
|
||||
initialState: {
|
||||
columnPinning: { right: ['actions'], left: ['mrt-row-expand', 'project'] },
|
||||
},
|
||||
state: { isLoading: isProjectsLoading },
|
||||
});
|
||||
|
||||
const projectTable = useMaterialReactTable({
|
||||
const summaryProjectTable = useMaterialReactTable({
|
||||
columns: secondColumns,
|
||||
data: summaryData,
|
||||
...projectTableOptions,
|
||||
...summaryProjectTableOptions,
|
||||
initialState: {
|
||||
columnPinning: { left: ['data.header.name'] },
|
||||
},
|
||||
state: { isLoading: isSummaryLoading },
|
||||
});
|
||||
|
||||
@ -491,7 +517,7 @@ const SummaryPage = () => {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
}}>
|
||||
<Box sx={{ display: 'flex', justifyContent: 'space-between', mb: 1, }}>
|
||||
<Box sx={{ display: 'flex', justifyContent: 'space-between', mb: 1, }}>
|
||||
|
||||
{/* Компонент фильтров */}
|
||||
<TableFilters
|
||||
@ -514,7 +540,7 @@ const SummaryPage = () => {
|
||||
borderRadius: '1rem',
|
||||
boxShadow: '0px 4px 12px rgba(0, 0, 0, 0.1)',
|
||||
}}>
|
||||
<MaterialReactTable table={summaryProjectsTable} />
|
||||
<MaterialReactTable table={projectsTable} />
|
||||
</Paper>
|
||||
|
||||
{/* Вторая таблица */}
|
||||
@ -543,7 +569,7 @@ const SummaryPage = () => {
|
||||
<Box sx={{ height: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
|
||||
<CircularProgress size={32} />
|
||||
</Box>
|
||||
) : <MaterialReactTable table={projectTable} />}
|
||||
) : <MaterialReactTable table={summaryProjectTable} />}
|
||||
</Box>
|
||||
) : (
|
||||
<Box
|
||||
@ -569,6 +595,25 @@ const SummaryPage = () => {
|
||||
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>
|
||||
</>
|
||||
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
import { Box, IconButton, Tooltip } from '@mui/material';
|
||||
import { Back, EditPenSvg } from '../../components/common/icons/icons';
|
||||
import { Back, EditPenSvg, TrashSvg } from '../../components/common/icons/icons';
|
||||
import StatusBadge from './components/StatusBadge/StatusBadge';
|
||||
import { DEVELOMENT_BLOCK } from './constants';
|
||||
|
||||
export const createFirstColumns = (handlers) => {
|
||||
const { onEdit, onNavigate, orgUnitNames = {} } = handlers;
|
||||
const { onDelete, onEdit, onNavigate, orgUnitNames = {} } = handlers;
|
||||
return [
|
||||
{
|
||||
id: 'project',
|
||||
@ -46,7 +46,7 @@ export const createFirstColumns = (handlers) => {
|
||||
{
|
||||
accessorKey: 'krf_decision_date',
|
||||
header: 'Дата решения КРФ',
|
||||
size: 120,
|
||||
size: 160,
|
||||
},
|
||||
{
|
||||
accessorKey: 'fk_decision_date',
|
||||
@ -140,16 +140,29 @@ export const createFirstColumns = (handlers) => {
|
||||
</Tooltip>
|
||||
|
||||
{row.depth == 0 && (
|
||||
<Tooltip title='Перейти'>
|
||||
<IconButton
|
||||
variant='outlined'
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onNavigate(row);
|
||||
}}>
|
||||
<Back style={{ transform: 'scaleX(-1)' }} />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<>
|
||||
<Tooltip title='Перейти'>
|
||||
<IconButton
|
||||
variant='outlined'
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onNavigate(row);
|
||||
}}>
|
||||
<Back style={{ transform: 'scaleX(-1)' }} />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<Tooltip title='Удалить проект'>
|
||||
<IconButton
|
||||
variant='outlined'
|
||||
color='error'
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onDelete(row.original);
|
||||
}}>
|
||||
<TrashSvg fill='currentColor' />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</>
|
||||
)}
|
||||
</Box>
|
||||
),
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { Box, CircularProgress, Stack } from '@mui/material';
|
||||
import { Box, CircularProgress, FormControl, FormLabel, Stack, TextField } from '@mui/material';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
@ -7,10 +7,11 @@ import { ProjectsApi } from '../../api/projects'; // Добавьте импор
|
||||
import { TasksApi } from '../../api/tasks';
|
||||
import { SettingModal as SettingStageModal } from '../../components/Stages/SettingModal';
|
||||
import { BackButton } from '../../components/common/Buttons/BackButton';
|
||||
import { PrimaryOutlinedButton } from '../../components/common/Buttons/Buttons';
|
||||
import { DangerOutlinedButton, PrimaryButton, PrimaryOutlinedButton } from '../../components/common/Buttons/Buttons';
|
||||
import { ExportWithTextButton } from '../../components/common/Buttons/ButtonsActions';
|
||||
import { IconWithContent } from '../../components/common/IconWithContent';
|
||||
import SearchComponent from '../../components/common/SearchComponent';
|
||||
import Modal from '../../components/common/Modal/Modal';
|
||||
import { NameTask, TaskInfoContainer } from '../../components/common/SwitchFormTask/SwitchFormTask.style';
|
||||
import TablesList from '../../components/common/TableList/TableList';
|
||||
import { TableIcon } from '../../components/common/icons/icons';
|
||||
@ -50,6 +51,10 @@ export default function TaskPage() {
|
||||
const [tempProjects, setTempProjects] = useState([]);
|
||||
const [projects, setProjects] = useState([]);
|
||||
const [isProjectData, setIsProjectData] = useState(false);
|
||||
const [addYearModalOpen, setAddYearModalOpen] = useState(false);
|
||||
const [newProjectYear, setNewProjectYear] = useState('');
|
||||
const [isAddingYear, setIsAddingYear] = useState(false);
|
||||
const [dataReloadKey, setDataReloadKey] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
const getData = async () => {
|
||||
@ -92,7 +97,7 @@ export default function TaskPage() {
|
||||
if (Number.isFinite(id)) {
|
||||
getData();
|
||||
}
|
||||
}, [id, isProject]);
|
||||
}, [id, isProject, dataReloadKey]);
|
||||
|
||||
// Объединили два useEffect в один
|
||||
|
||||
@ -112,6 +117,33 @@ export default function TaskPage() {
|
||||
exportSingleForm(id, fileName);
|
||||
};
|
||||
|
||||
const handleCloseAddYearModal = () => {
|
||||
if (isAddingYear) return;
|
||||
setAddYearModalOpen(false);
|
||||
setNewProjectYear('');
|
||||
};
|
||||
|
||||
const handleAddProjectYear = async () => {
|
||||
const year = Number(newProjectYear);
|
||||
if (!Number.isInteger(year) || year < 1) {
|
||||
toast.error('Введите корректный год');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
setIsAddingYear(true);
|
||||
await ProjectsApi.addYear(id, year);
|
||||
toast.success(`Год ${year} успешно добавлен`);
|
||||
setAddYearModalOpen(false);
|
||||
setNewProjectYear('');
|
||||
setDataReloadKey((key) => key + 1);
|
||||
} catch (error) {
|
||||
toast.error(error?.response?.data?.detail || error?.response?.data?.message || 'Не удалось добавить год');
|
||||
} finally {
|
||||
setIsAddingYear(false);
|
||||
}
|
||||
};
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className='flex justify-center pt-12'>
|
||||
@ -147,6 +179,11 @@ export default function TaskPage() {
|
||||
<PrimaryOutlinedButton variant='outlined' onClick={() => setModalStageOpen(true)}>
|
||||
<span>Этапы</span>
|
||||
</PrimaryOutlinedButton>
|
||||
{isProject && (
|
||||
<PrimaryButton onClick={() => setAddYearModalOpen(true)}>
|
||||
Добавить год
|
||||
</PrimaryButton>
|
||||
)}
|
||||
|
||||
<SettingStageModal
|
||||
isOpen={modalStageOpen}
|
||||
@ -170,6 +207,40 @@ export default function TaskPage() {
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
{isProject && (
|
||||
<Modal
|
||||
open={addYearModalOpen}
|
||||
onClose={handleCloseAddYearModal}
|
||||
title='Добавить год'
|
||||
size='small'
|
||||
actions={
|
||||
<>
|
||||
<DangerOutlinedButton onClick={handleCloseAddYearModal} disabled={isAddingYear}>
|
||||
Отмена
|
||||
</DangerOutlinedButton>
|
||||
<PrimaryButton onClick={handleAddProjectYear} disabled={isAddingYear}>
|
||||
{isAddingYear ? 'Добавление...' : 'Добавить'}
|
||||
</PrimaryButton>
|
||||
</>
|
||||
}>
|
||||
<FormControl fullWidth>
|
||||
<FormLabel required>Год</FormLabel>
|
||||
<TextField
|
||||
autoFocus
|
||||
size='small'
|
||||
type='number'
|
||||
placeholder='Введите год'
|
||||
value={newProjectYear}
|
||||
disabled={isAddingYear}
|
||||
onChange={(event) => setNewProjectYear(event.target.value)}
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === 'Enter') handleAddProjectYear();
|
||||
}}
|
||||
/>
|
||||
</FormControl>
|
||||
</Modal>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user