From 182baaff4e3d5f6f7aba76c3778f05d36d996b74 Mon Sep 17 00:00:00 2001 From: PotapovaA Date: Fri, 14 Aug 2026 16:10:44 +0300 Subject: [PATCH] delete project --- web/src/api/projects.js | 4 + .../components/common/TableList/TableList.jsx | 37 ++++++++- web/src/pages/ProjectPages/SummaryPage.jsx | 67 +++++++++++++--- web/src/pages/ProjectPages/columns.js | 39 ++++++---- web/src/pages/TaskPage/TaskPage.jsx | 77 ++++++++++++++++++- 5 files changed, 194 insertions(+), 30 deletions(-) diff --git a/web/src/api/projects.js b/web/src/api/projects.js index c71f1f4..8f26d7e 100644 --- a/web/src/api/projects.js +++ b/web/src/api/projects.js @@ -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); }, diff --git a/web/src/components/common/TableList/TableList.jsx b/web/src/components/common/TableList/TableList.jsx index 5e0a398..5cf91ab 100644 --- a/web/src/components/common/TableList/TableList.jsx +++ b/web/src/components/common/TableList/TableList.jsx @@ -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 handleNavigate(path)} table={table} />; + return 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 ( )} - {filteredTables.map((table) => renderTableCard(table, formInfo))} + {isProject + ? projectTablesByYear.map(([year, tables]) => ( + + + + {year === 'Без года' ? year : `${year} год`} + + + + {tables.map((table) => renderTableCard(table, formInfo))} + + + )) + : filteredTables.map((table) => renderTableCard(table, formInfo))} ); }; diff --git a/web/src/pages/ProjectPages/SummaryPage.jsx b/web/src/pages/ProjectPages/SummaryPage.jsx index df039d0..e437b3b 100644 --- a/web/src/pages/ProjectPages/SummaryPage.jsx +++ b/web/src/pages/ProjectPages/SummaryPage.jsx @@ -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', }}> - + {/* Компонент фильтров */} { borderRadius: '1rem', boxShadow: '0px 4px 12px rgba(0, 0, 0, 0.1)', }}> - + {/* Вторая таблица */} @@ -543,7 +569,7 @@ const SummaryPage = () => { - ) : } + ) : } ) : ( { onCreate={handleCreateProject} config={createProjectConfig} /> + !isDeleting && setProjectToDelete(null)} + title='Удалить проект?' + size='small' + actions={ + <> + setProjectToDelete(null)} disabled={isDeleting}> + Отмена + + + {isDeleting ? 'Удаление...' : 'Удалить'} + + + }> + + Проект «{projectToDelete?.name}» будет удалён без возможности восстановления. + + diff --git a/web/src/pages/ProjectPages/columns.js b/web/src/pages/ProjectPages/columns.js index 97231ad..d65bbcd 100644 --- a/web/src/pages/ProjectPages/columns.js +++ b/web/src/pages/ProjectPages/columns.js @@ -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) => { {row.depth == 0 && ( - - { - e.stopPropagation(); - onNavigate(row); - }}> - - - + <> + + { + e.stopPropagation(); + onNavigate(row); + }}> + + + + + { + e.stopPropagation(); + onDelete(row.original); + }}> + + + + )} ), diff --git a/web/src/pages/TaskPage/TaskPage.jsx b/web/src/pages/TaskPage/TaskPage.jsx index 4fafa04..5d08bba 100644 --- a/web/src/pages/TaskPage/TaskPage.jsx +++ b/web/src/pages/TaskPage/TaskPage.jsx @@ -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 (
@@ -147,6 +179,11 @@ export default function TaskPage() { setModalStageOpen(true)}> Этапы + {isProject && ( + setAddYearModalOpen(true)}> + Добавить год + + )} + + {isProject && ( + + + Отмена + + + {isAddingYear ? 'Добавление...' : 'Добавить'} + + + }> + + Год + setNewProjectYear(event.target.value)} + onKeyDown={(event) => { + if (event.key === 'Enter') handleAddProjectYear(); + }} + /> + + + )} ); }