front-projects #25
@ -14,7 +14,7 @@ export const ProjectsApi = {
|
||||
},
|
||||
|
||||
update: (id, data) => {
|
||||
return api.put(`/projects/${id}`, data).then((r) => r.data);
|
||||
return api.patch(`/projects/${id}`, data).then((r) => r.data);
|
||||
},
|
||||
|
||||
delete: (id) => {
|
||||
|
||||
@ -41,6 +41,7 @@ export const AppRoutes = () => {
|
||||
<Route path="/projects" element={<ProjectsPage />}/>
|
||||
<Route path="/forms" element={<FormsPage />} />
|
||||
<Route path="/task/:taskId" element={<TaskPage />} />
|
||||
<Route path="/project/:projectId" element={<TaskPage />} />
|
||||
<Route path="/table/:tableId" element={<TablePage />} />
|
||||
<Route path="/table-temp/:tableId" element={<TableTempPage />} />
|
||||
<Route path="/admin_panel/users" element={<UsersPage />} />
|
||||
|
||||
@ -4,7 +4,7 @@ import { NavLink } from 'react-router-dom';
|
||||
import styled from '@emotion/styled';
|
||||
import { TasksSvg, AdminPanelSvg, DictSvg } from '../common/icons/icons';
|
||||
import { useAuth } from '../../app/context/AuthProvider';
|
||||
import { ROLES_NAME_ID } from '../../constants';
|
||||
import { ROLES_NAME_ID } from '../../constants/constants';
|
||||
|
||||
const Switch = styled.div`
|
||||
margin-left: 1rem;
|
||||
|
||||
@ -25,7 +25,7 @@ import { StageStatusChip } from './StageStatusChip';
|
||||
import { formatDateForApi } from './utils';
|
||||
import CollapsibleTree from '../common/CollapsibleTree';
|
||||
import { collectLeafColumns } from '../RealtimeTable/utils/columnUtils';
|
||||
import { STAGE_ROLE_RUSSIAN_NAME } from '../../constants';
|
||||
import { STAGE_ROLE_RUSSIAN_NAME } from '../../constants/constants';
|
||||
|
||||
export const AddStagesModal = ({
|
||||
isOpen,
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { Box, Divider, Stack } from '@mui/material';
|
||||
import { UnLockedSvg } from '../common/icons/icons';
|
||||
import { ROLES_ID_RUSSIAN_NAME, EDIT_ACCESS_RUSSIAN } from '../../constants';
|
||||
import { ROLES_ID_RUSSIAN_NAME, EDIT_ACCESS_RUSSIAN } from '../../constants/constants';
|
||||
|
||||
export const StageInfoForTooltip = ({ stageData = [] }) => {
|
||||
const accessData = stageData[0] || [];
|
||||
|
||||
@ -20,7 +20,7 @@ import {
|
||||
import { getStageStatus, getStatusColors } from './utils';
|
||||
import { formatDate } from '../../../utils/formatDate';
|
||||
import { StageStatusChip } from '../StageStatusChip';
|
||||
import { ROLES_NAME_ID, SHEET_NAME, STAGE_ROLE_RUSSIAN_NAME } from '../../../constants';
|
||||
import { ROLES_NAME_ID, SHEET_NAME, STAGE_ROLE_RUSSIAN_NAME } from '../../../constants/constants';
|
||||
import { useAuth } from '../../../app/context/AuthProvider';
|
||||
|
||||
export const StagesTable = ({ stages, onEdit, onDelete }) => {
|
||||
|
||||
@ -56,7 +56,7 @@ export function PaginatedList({
|
||||
extraActions = null,
|
||||
enableExport = true,
|
||||
limitOptions = [10, 20, 50, 100, 200],
|
||||
maxHeight = 'calc(110vh - 300px)',
|
||||
maxHeight = 'calc(100vh - 300px)',
|
||||
enableScroll = true,
|
||||
}) {
|
||||
const [exportMode, setExportMode] = useState(false);
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { useEffect, useState, useRef } from 'react';
|
||||
import { EDIT_ACCESS_RUSSIAN, ROLES_ID_RUSSIAN_NAME } from '../../../constants';
|
||||
import { EDIT_ACCESS_RUSSIAN, ROLES_ID_RUSSIAN_NAME } from '../../../constants/constants';
|
||||
import {
|
||||
SelectWrapper,
|
||||
SelectContainer,
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { useAuth } from '../../../app/context/AuthProvider';
|
||||
import { ROLES_NAME_ID } from '../../../constants';
|
||||
import { ROLES_NAME_ID } from '../../../constants/constants';
|
||||
import { Switch, Buttons, StyledNavLink } from './SwitchFormTask.style';
|
||||
|
||||
export function SwitchFormTask() {
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { DIRECTION_TRANSLATE, FORM_TYPE_OPTIONS } from '../../../constants';
|
||||
import { DIRECTION_TRANSLATE, FORM_TYPE_OPTIONS } from '../../../constants/constants';
|
||||
import {
|
||||
Section,
|
||||
SectionStartPart,
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import React, { useMemo } from 'react';
|
||||
import { Box, Stack, Typography } from '@mui/material';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import TableCard from '../TableCard/TableCard';
|
||||
@ -12,6 +12,9 @@ const TablesList = ({
|
||||
formInfo
|
||||
}) => {
|
||||
const navigate = useNavigate();
|
||||
const isProject = useMemo(() => {
|
||||
return !!projects;
|
||||
}, [projects])
|
||||
|
||||
const handleNavigate = (path) => {
|
||||
navigate(path);
|
||||
@ -22,7 +25,7 @@ const TablesList = ({
|
||||
if (!formInfo){
|
||||
return;
|
||||
}
|
||||
const path = `${basePath}/form/${formInfo.id}/form-type/${formInfo.form_type_code}/${table.sheet}/${table?.direction}`;
|
||||
const path = `${basePath}/form/${formInfo.id}/form-type/${isProject ? 'project' : formInfo.form_type_code}/${table.sheet}/${table?.direction}`;
|
||||
|
||||
return (
|
||||
<TableCard
|
||||
|
||||
@ -26,7 +26,7 @@ import { formatDate } from '../../utils/formatDate';
|
||||
import { IconWithContent } from './IconWithContent';
|
||||
import { Chip } from '../styles/StyledChip';
|
||||
import { ExportButton } from '../common/Buttons/ButtonsActions';
|
||||
import { FORM_TYPE_TRANSLATE } from '../../constants';
|
||||
import { FORM_TYPE_TRANSLATE } from '../../constants/constants';
|
||||
|
||||
const TaskCard = styled.div`
|
||||
width: 26.8rem;
|
||||
|
||||
90
web/src/constants/projectConfig.jsx
Normal file
90
web/src/constants/projectConfig.jsx
Normal file
@ -0,0 +1,90 @@
|
||||
export const DEFAULT_PROJECT_CONFIG = {
|
||||
'ССП/РФ': {
|
||||
type: 'multiselect',
|
||||
options: [],
|
||||
placeholder: 'Выберите ССП/РФ',
|
||||
defaultValue: null,
|
||||
required_field: true,
|
||||
},
|
||||
'Год': {
|
||||
type: 'number',
|
||||
placeholder: 'Введите год',
|
||||
defaultValue: null,
|
||||
min: 0,
|
||||
step: 1,
|
||||
required_field: true,
|
||||
},
|
||||
'Тип проекта развития': {
|
||||
type: 'multiselect',
|
||||
options: [
|
||||
'Открытие ВСП',
|
||||
'Закрытие ВСП',
|
||||
'Переезд ВСП',
|
||||
'Реновация РФ',
|
||||
'Открытие УРМ',
|
||||
],
|
||||
placeholder: 'Выберите тип проекта',
|
||||
defaultValue: null,
|
||||
required_field: false,
|
||||
|
||||
},
|
||||
'Название проекта': {
|
||||
type: 'text',
|
||||
placeholder: 'Введите название проекта',
|
||||
defaultValue: null,
|
||||
required_field: true,
|
||||
},
|
||||
'Формат ВСП': {
|
||||
type: 'multiselect',
|
||||
options: [
|
||||
'Фланговый',
|
||||
'Типовой',
|
||||
'Розничный',
|
||||
'МСБ',
|
||||
'Лёгкий',
|
||||
'Мини',
|
||||
'Розничный-киоск',
|
||||
'МБО',
|
||||
'Офис самообслуживания',
|
||||
'Другое',
|
||||
],
|
||||
placeholder: 'Выберите формат ВСП',
|
||||
defaultValue: null,
|
||||
required_field: false,
|
||||
|
||||
},
|
||||
'Адрес объекта': {
|
||||
type: 'text',
|
||||
placeholder: 'Введите адрес объекта',
|
||||
defaultValue: '',
|
||||
required_field: false,
|
||||
|
||||
},
|
||||
'Целевая численность, чел.': {
|
||||
type: 'number',
|
||||
placeholder: 'Введите число',
|
||||
defaultValue: 0,
|
||||
min: 0,
|
||||
step: 1,
|
||||
required_field: false,
|
||||
|
||||
},
|
||||
'Тип размещения': {
|
||||
type: 'multiselect',
|
||||
options: ['Собственность', 'Аренда', 'Субаренда'],
|
||||
placeholder: 'Выберите тип размещения',
|
||||
defaultValue: null,
|
||||
required_field: false,
|
||||
|
||||
},
|
||||
'Площадь размещения, м2': {
|
||||
type: 'number',
|
||||
placeholder: 'Введите число',
|
||||
defaultValue: 0,
|
||||
min: 0,
|
||||
step: 1,
|
||||
required_field: false,
|
||||
|
||||
},
|
||||
|
||||
};
|
||||
@ -2,7 +2,7 @@ import { useCallback, useEffect, useState } from 'react';
|
||||
import { PageContainer } from '../../StyledForPage';
|
||||
import { HeaderSwitch } from '../../../components/HeaderMenu/HeaderSwitch.jsx';
|
||||
import { SwitchUserSsp } from '../../../components/AdminPanel/SwitchUserSsp';
|
||||
import { ROLES_NAME_ID } from '../../../constants';
|
||||
import { ROLES_NAME_ID } from '../../../constants/constants';
|
||||
import { useAuth } from '../../../app/context/AuthProvider';
|
||||
import { Box } from '@mui/material';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { useMemo, useState } from 'react';
|
||||
import { Box, Typography, Link, Chip } from '@mui/material';
|
||||
import { ROLES_ID_RUSSIAN_NAME } from '../../../../constants';
|
||||
import { ROLES_ID_RUSSIAN_NAME } from '../../../../constants/constants';
|
||||
import {
|
||||
ENTITY_RUSSIAN_NAMES,
|
||||
getRussianNamesForType,
|
||||
|
||||
@ -4,7 +4,7 @@ import { FormsApi } from '../../../../api/forms';
|
||||
import { SspApi } from '../../../../api/ssp';
|
||||
import { TasksApi } from '../../../../api/tasks';
|
||||
import { UsersApi } from '../../../../api/users';
|
||||
import { DIRECTION_TRANSLATE, FORM_TYPE_TRANSLATE, ROLES_ID_RUSSIAN_NAME, SHEET_NAME, STAGE_ROLE_RUSSIAN_NAME } from '../../../../constants';
|
||||
import { DIRECTION_TRANSLATE, FORM_TYPE_TRANSLATE, ROLES_ID_RUSSIAN_NAME, SHEET_NAME, STAGE_ROLE_RUSSIAN_NAME } from '../../../../constants/constants';
|
||||
|
||||
export const VALUE_TRANSFORMERS = {
|
||||
Роль: transformRole,
|
||||
|
||||
@ -2,7 +2,7 @@ import { useState, useEffect, useCallback } from 'react';
|
||||
import { PageContainer } from '../../StyledForPage';
|
||||
import { HeaderSwitch } from '../../../components/HeaderMenu/HeaderSwitch';
|
||||
import { SwitchUserSsp } from '../../../components/AdminPanel/SwitchUserSsp';
|
||||
import { ROLES_NAME_ID } from '../../../constants';
|
||||
import { ROLES_NAME_ID } from '../../../constants/constants';
|
||||
import { useAuth } from '../../../app/context/AuthProvider';
|
||||
import {
|
||||
Box,
|
||||
|
||||
@ -2,7 +2,7 @@ import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import Modal from '../../../components/common/Modal/Modal';
|
||||
import { Box, Button, Typography } from '@mui/material';
|
||||
import SelectWithOptions from '../components/SelectWithOptions';
|
||||
import { ROLES_NAME_ID } from '../../../constants';
|
||||
import { ROLES_NAME_ID } from '../../../constants/constants';
|
||||
import { AutocompleteForChangeOptions } from '../components/AutocompleteForChangeOptions';
|
||||
|
||||
export const ModalEditUserSsp = ({
|
||||
|
||||
@ -15,7 +15,7 @@ import EditOutlinedIcon from '@mui/icons-material/EditOutlined';
|
||||
import {
|
||||
ROLES_ID_RUSSIAN_NAME_ARRAY as ROLES,
|
||||
ROLES_ID_RUSSIAN_NAME,
|
||||
} from '../../../../constants';
|
||||
} from '../../../../constants/constants';
|
||||
import SelectWithOptions from '../../components/SelectWithOptions';
|
||||
import useAbbreviation from './hooks/useAbbreviation';
|
||||
import {
|
||||
|
||||
@ -5,12 +5,12 @@ import {
|
||||
CheckedIcon,
|
||||
IndeterminateIcon,
|
||||
} from '../../IconsForTable/icons';
|
||||
import { ROLES_NAME_ID } from '../../../../constants';
|
||||
import { ROLES_NAME_ID } from '../../../../constants/constants';
|
||||
import { createFilterData } from '../../utils/filters';
|
||||
import {
|
||||
ROLES_ID_RUSSIAN_NAME_ARRAY as ROLES,
|
||||
ROLES_ID_NAME,
|
||||
} from '../../../../constants';
|
||||
} from '../../../../constants/constants';
|
||||
import SelectWithOptions from '../../components/SelectWithOptions';
|
||||
import RowActionsMenu from './RowActionMenu';
|
||||
import DepartmentCell from './DepartmentCell';
|
||||
|
||||
@ -12,7 +12,7 @@ import {
|
||||
} from '@mui/material';
|
||||
import { CheckIcon, CheckedIcon } from '../IconsForTable/icons';
|
||||
import { TrashSvg } from '../../../components/common/icons/icons';
|
||||
import { ROLES_NAME_ID } from '../../../constants';
|
||||
import { ROLES_NAME_ID } from '../../../constants/constants';
|
||||
|
||||
const StyleForChecked = {
|
||||
display: 'flex',
|
||||
|
||||
@ -6,7 +6,7 @@ import { Button, Stack } from '@mui/material';
|
||||
import Select from '@mui/material/Select';
|
||||
import Box from '@mui/material/Box';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import { ROLES_ID_RUSSIAN_NAME } from '../../../constants';
|
||||
import { ROLES_ID_RUSSIAN_NAME } from '../../../constants/constants';
|
||||
import { UserIcon, TrashSvg } from '../../../components/common/icons/icons';
|
||||
import {
|
||||
DangerOutlinedButton,
|
||||
|
||||
@ -2,7 +2,7 @@ import { useState, useEffect } from 'react';
|
||||
import { PageContainer } from '../../StyledForPage';
|
||||
import { HeaderSwitch } from '../../../components/HeaderMenu/HeaderSwitch';
|
||||
import { SwitchUserSsp } from '../../../components/AdminPanel/SwitchUserSsp';
|
||||
import { ROLES_ID_RUSSIAN_NAME_ARRAY, ROLES_NAME_ID } from '../../../constants';
|
||||
import { ROLES_ID_RUSSIAN_NAME_ARRAY, ROLES_NAME_ID } from '../../../constants/constants';
|
||||
import { useAuth } from '../../../app/context/AuthProvider';
|
||||
import { Box, TextField, Autocomplete, FormLabel } from '@mui/material';
|
||||
import SearchComponent from '../../../components/common/SearchComponent';
|
||||
@ -10,7 +10,7 @@ import { toast } from 'react-toastify';
|
||||
import { SspApi } from '../../../api/ssp';
|
||||
import { UsersApi } from '../../../api/users';
|
||||
import UserTable from './UserTable/UserTable';
|
||||
import { ROLES_ID_RUSSIAN_NAME } from '../../../constants';
|
||||
import { ROLES_ID_RUSSIAN_NAME } from '../../../constants/constants';
|
||||
import { ModalEditUserSsp } from './ModalEditUserSsp';
|
||||
import { ModalAddUsers } from './ModalAddUsers';
|
||||
import UsersActionBar from './UsersActionBar';
|
||||
|
||||
@ -13,7 +13,7 @@ import TableDictVsp from './components/TableDictVsp';
|
||||
import { PageContainer } from '../StyledForPage';
|
||||
import { SspApi } from '../../api/ssp';
|
||||
import { useAuth } from '../../app/context/AuthProvider';
|
||||
import { ROLES_NAME_ID } from '../../constants';
|
||||
import { ROLES_NAME_ID } from '../../constants/constants';
|
||||
import DictVspFilters from './components/DictVspFilters';
|
||||
|
||||
const DictVspInfo = () => {
|
||||
|
||||
@ -14,7 +14,7 @@ import {
|
||||
PrimaryButton,
|
||||
} from '../../../../components/common/Buttons/Buttons';
|
||||
import { useAuth } from '../../../../app/context/AuthProvider';
|
||||
import { ROLES_NAME_ID } from '../../../../constants';
|
||||
import { ROLES_NAME_ID } from '../../../../constants/constants';
|
||||
import {
|
||||
ModalContainer,
|
||||
FieldContainer,
|
||||
|
||||
@ -11,7 +11,7 @@ import {
|
||||
PrimaryOutlinedButton,
|
||||
} from '../../../components/common/Buttons/Buttons';
|
||||
import { useAuth } from '../../../app/context/AuthProvider';
|
||||
import { ROLES_NAME_ID } from '../../../constants';
|
||||
import { ROLES_NAME_ID } from '../../../constants/constants';
|
||||
import ModalExport from './Modals/ModalExport';
|
||||
|
||||
const TableDictVsp = ({
|
||||
|
||||
@ -8,7 +8,7 @@ import { useParams } from 'react-router-dom';
|
||||
import { RealtimeProvider } from '../components/RealtimeTable/contexts/RealtimeContext';
|
||||
import { useAuth } from '../app/context/AuthProvider';
|
||||
import { BackButton } from '../components/common/Buttons/BackButton';
|
||||
import { SHEET_NAME } from '../constants';
|
||||
import { SHEET_NAME } from '../constants/constants';
|
||||
|
||||
const TableInfo = ({ formId, sheetName }) => {
|
||||
const portalContent = (
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { useState } from 'react';
|
||||
// src/components/common/ProjectCardComponent/ModalCreateProject.jsx
|
||||
import { useState, useEffect } from 'react';
|
||||
import { Box, Button, Divider, TextField, Typography } from '@mui/material';
|
||||
import SelectWithOptions from '../AdminPanel/components/SelectWithOptions';
|
||||
import Modal from '../../components/common/Modal/Modal';
|
||||
@ -9,25 +10,103 @@ export const ModalCreateProject = ({
|
||||
onClose,
|
||||
onCreate,
|
||||
config = {},
|
||||
initialData = null,
|
||||
isEdit = false,
|
||||
projectId = null,
|
||||
}) => {
|
||||
const [projectData, setProjectData] = useState(() => {
|
||||
const initialData = {};
|
||||
// Если есть initialData, используем её, иначе создаем из конфига
|
||||
if (initialData) {
|
||||
return { ...initialData };
|
||||
}
|
||||
const initial = {};
|
||||
Object.keys(config).forEach((key) => {
|
||||
initialData[key] = config[key].defaultValue;
|
||||
initial[key] = config[key].defaultValue;
|
||||
});
|
||||
return initialData;
|
||||
return initial;
|
||||
});
|
||||
|
||||
const [errors, setErrors] = useState({});
|
||||
|
||||
// Обновляем данные при изменении initialData
|
||||
useEffect(() => {
|
||||
if (initialData) {
|
||||
setProjectData({ ...initialData });
|
||||
} else {
|
||||
const initial = {};
|
||||
Object.keys(config).forEach((key) => {
|
||||
initial[key] = config[key].defaultValue;
|
||||
});
|
||||
setProjectData(initial);
|
||||
}
|
||||
setErrors({});
|
||||
}, [initialData, config]);
|
||||
|
||||
const handleChange = (field, value) => {
|
||||
setProjectData((prev) => ({
|
||||
...prev,
|
||||
[field]: value,
|
||||
}));
|
||||
// Очищаем ошибку для поля при изменении
|
||||
if (errors[field]) {
|
||||
setErrors((prev) => ({
|
||||
...prev,
|
||||
[field]: undefined,
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
const validateField = (fieldName, config) => {
|
||||
if (config.required_field) {
|
||||
const value = projectData[fieldName];
|
||||
|
||||
// Проверка для разных типов полей
|
||||
if (config.type === 'multiselect') {
|
||||
if (!value || (Array.isArray(value) && value.length === 0) || value === '') {
|
||||
return `Поле "${fieldName}" обязательно для заполнения`;
|
||||
}
|
||||
} else if (config.type === 'text') {
|
||||
if (!value || value.trim() === '') {
|
||||
return `Поле "${fieldName}" обязательно для заполнения`;
|
||||
}
|
||||
} else if (config.type === 'number') {
|
||||
if (value === null || value === undefined || value === '') {
|
||||
return `Поле "${fieldName}" обязательно для заполнения`;
|
||||
}
|
||||
// Проверка на минимальное значение
|
||||
if (config.min !== undefined && Number(value) < config.min) {
|
||||
return `Значение должно быть не меньше ${config.min}`;
|
||||
}
|
||||
// Проверка на максимальное значение
|
||||
if (config.max !== undefined && Number(value) > config.max) {
|
||||
return `Значение должно быть не больше ${config.max}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const validateAllFields = () => {
|
||||
const newErrors = {};
|
||||
let hasErrors = false;
|
||||
|
||||
Object.entries(config).forEach(([fieldName, fieldConfig]) => {
|
||||
const error = validateField(fieldName, fieldConfig);
|
||||
if (error) {
|
||||
newErrors[fieldName] = error;
|
||||
hasErrors = true;
|
||||
}
|
||||
});
|
||||
|
||||
setErrors(newErrors);
|
||||
return !hasErrors;
|
||||
};
|
||||
|
||||
const handleClickCreate = () => {
|
||||
onCreate(projectData);
|
||||
onClose();
|
||||
if (validateAllFields()) {
|
||||
onCreate(projectData);
|
||||
// Не закрываем модалку здесь, пусть это делает родительский компонент
|
||||
}
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
@ -36,10 +115,14 @@ export const ModalCreateProject = ({
|
||||
resetData[key] = config[key].defaultValue;
|
||||
});
|
||||
setProjectData(resetData);
|
||||
setErrors({});
|
||||
onClose();
|
||||
};
|
||||
|
||||
const renderField = (fieldName, config) => {
|
||||
const hasError = !!errors[fieldName];
|
||||
const errorText = errors[fieldName];
|
||||
|
||||
switch (config.type) {
|
||||
case 'multiselect':
|
||||
return (
|
||||
@ -50,9 +133,13 @@ export const ModalCreateProject = ({
|
||||
fontWeight: '400',
|
||||
fontSize: '0.75rem',
|
||||
paddingBottom: '.25rem',
|
||||
color: hasError ? 'var(--error-color, #d32f2f)' : 'var(--grey-dark)',
|
||||
}}
|
||||
>
|
||||
{fieldName}
|
||||
{config.required_field && (
|
||||
<span style={{ color: 'var(--error-color, #d32f2f)', marginLeft: '4px' }}>*</span>
|
||||
)}
|
||||
</Typography>
|
||||
<SelectWithOptions
|
||||
className="sameSize"
|
||||
@ -61,7 +148,22 @@ export const ModalCreateProject = ({
|
||||
placeholder={config.placeholder}
|
||||
onChange={(value) => handleChange(fieldName, value)}
|
||||
width="28.25rem"
|
||||
style={{
|
||||
borderColor: hasError ? 'var(--error-color, #d32f2f)' : undefined,
|
||||
borderWidth: hasError ? '2px' : undefined,
|
||||
}}
|
||||
/>
|
||||
{hasError && (
|
||||
<Typography
|
||||
sx={{
|
||||
color: 'var(--error-color, #d32f2f)',
|
||||
fontSize: '0.75rem',
|
||||
marginTop: '4px',
|
||||
}}
|
||||
>
|
||||
{errorText}
|
||||
</Typography>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
|
||||
@ -73,11 +175,14 @@ export const ModalCreateProject = ({
|
||||
sx={{
|
||||
fontWeight: '400',
|
||||
fontSize: '0.75rem',
|
||||
color: 'var(--grey-dark)',
|
||||
color: hasError ? 'var(--error-color, #d32f2f)' : 'var(--grey-dark)',
|
||||
paddingBottom: '.25rem',
|
||||
}}
|
||||
>
|
||||
{fieldName}
|
||||
{config.required_field && (
|
||||
<span style={{ color: 'var(--error-color, #d32f2f)', marginLeft: '4px' }}>*</span>
|
||||
)}
|
||||
</Typography>
|
||||
<TextField
|
||||
className="sameSize"
|
||||
@ -85,6 +190,14 @@ export const ModalCreateProject = ({
|
||||
onChange={(e) => handleChange(fieldName, e.target.value)}
|
||||
placeholder={config.placeholder}
|
||||
size="small"
|
||||
error={hasError}
|
||||
helperText={hasError ? errorText : ''}
|
||||
FormHelperTextProps={{
|
||||
sx: {
|
||||
marginLeft: 0,
|
||||
fontSize: '0.75rem',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
@ -97,11 +210,14 @@ export const ModalCreateProject = ({
|
||||
sx={{
|
||||
fontWeight: '400',
|
||||
fontSize: '0.75rem',
|
||||
color: 'var(--grey-dark)',
|
||||
color: hasError ? 'var(--error-color, #d32f2f)' : 'var(--grey-dark)',
|
||||
paddingBottom: '.25rem',
|
||||
}}
|
||||
>
|
||||
{fieldName}
|
||||
{config.required_field && (
|
||||
<span style={{ color: 'var(--error-color, #d32f2f)', marginLeft: '4px' }}>*</span>
|
||||
)}
|
||||
</Typography>
|
||||
<TextField
|
||||
className="sameSize"
|
||||
@ -118,6 +234,8 @@ export const ModalCreateProject = ({
|
||||
}}
|
||||
placeholder={config.placeholder}
|
||||
size="small"
|
||||
error={hasError}
|
||||
helperText={hasError ? errorText : ''}
|
||||
slotProps={{
|
||||
htmlInput: {
|
||||
min: config.min,
|
||||
@ -125,6 +243,12 @@ export const ModalCreateProject = ({
|
||||
step: config.step || 1,
|
||||
},
|
||||
}}
|
||||
FormHelperTextProps={{
|
||||
sx: {
|
||||
marginLeft: 0,
|
||||
fontSize: '0.75rem',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
@ -136,7 +260,7 @@ export const ModalCreateProject = ({
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title={'Создание проекта'}
|
||||
title={isEdit ? 'Редактирование проекта' : 'Создание проекта'}
|
||||
open={open}
|
||||
onClose={handleClose}
|
||||
customSize={'43.75'}
|
||||
@ -154,7 +278,7 @@ export const ModalCreateProject = ({
|
||||
onClick={handleClickCreate}
|
||||
style={{ height: '2.5rem' }}
|
||||
>
|
||||
Создать
|
||||
{isEdit ? 'Сохранить' : 'Создать'}
|
||||
</PrimaryButton>
|
||||
</>
|
||||
}
|
||||
@ -180,4 +304,4 @@ export const ModalCreateProject = ({
|
||||
<Divider />
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
};
|
||||
@ -25,6 +25,7 @@ import {
|
||||
} from '../../components/common/icons/icons';
|
||||
import { IconWithContent } from '../../components/common/IconWithContent';
|
||||
import { ExportButton } from '../../components/common/Buttons/ButtonsActions';
|
||||
import { ModalCreateProject } from './ModalCreateProject';
|
||||
|
||||
const ProjectCard = styled.div`
|
||||
width: 26.8rem;
|
||||
@ -137,17 +138,9 @@ const getStatusLabel = (status) => {
|
||||
return statusMap[status] || status || 'Не указан';
|
||||
};
|
||||
|
||||
const getLevelLabel = (level) => {
|
||||
const levelMap = {
|
||||
project: 'Проект',
|
||||
subproject: 'Подпроект',
|
||||
task: 'Задача',
|
||||
};
|
||||
return levelMap[level] || level || 'Не указан';
|
||||
};
|
||||
|
||||
export const ProjectCardComponent = ({
|
||||
project,
|
||||
projectDataInit,
|
||||
exportMode = false,
|
||||
isLoadingFile = false,
|
||||
onAddForExport,
|
||||
@ -172,22 +165,41 @@ export const ProjectCardComponent = ({
|
||||
} = project;
|
||||
|
||||
const [isEditModalOpen, setEditModalOpen] = useState(false);
|
||||
const [editedName, setEditedName] = useState('');
|
||||
const [currentName, setCurrentName] = useState(name || '');
|
||||
const [isChecked, setIsChecked] = useState(false);
|
||||
const [editFormData, setEditFormData] = useState({});
|
||||
const [projectData, setProjectData] = useState(projectDataInit);
|
||||
|
||||
useEffect(() => {
|
||||
setIsChecked(false);
|
||||
}, [exportMode]);
|
||||
|
||||
|
||||
|
||||
// Функция для подготовки данных проекта к редактированию
|
||||
const prepareProjectDataForEdit = () => {
|
||||
return {
|
||||
'ССП/РФ': project.org_unit_id || null,
|
||||
'Год': year || null,
|
||||
'Тип проекта развития': project_type || null,
|
||||
'Название проекта': currentName,
|
||||
'Формат ВСП': vsp_format || null,
|
||||
'Адрес объекта': object_address || '',
|
||||
'Целевая численность, чел.': staff_count || 0,
|
||||
'Тип размещения': placement_type || null,
|
||||
'Площадь размещения, м2': total_area || 0,
|
||||
};
|
||||
};
|
||||
|
||||
const handleOpenEdit = (event) => {
|
||||
event.stopPropagation();
|
||||
setEditedName(currentName);
|
||||
setEditFormData(prepareProjectDataForEdit());
|
||||
setEditModalOpen(true);
|
||||
};
|
||||
|
||||
const handleCloseEdit = () => {
|
||||
setEditModalOpen(false);
|
||||
setEditFormData({});
|
||||
};
|
||||
|
||||
const handleCheckboxClick = (event) => {
|
||||
@ -211,30 +223,39 @@ export const ProjectCardComponent = ({
|
||||
}
|
||||
};
|
||||
|
||||
const updateProject = async (nextName) => {
|
||||
// Обновленная функция обновления проекта
|
||||
const updateProject = async (formData) => {
|
||||
try {
|
||||
await ProjectsApi.update(id, { name: nextName });
|
||||
setCurrentName(nextName);
|
||||
const projectData = {
|
||||
name: formData['Название проекта'] || currentName,
|
||||
project_type: formData['Тип проекта развития'] || null,
|
||||
vsp_format: formData['Формат ВСП'] || null,
|
||||
object_address: formData['Адрес объекта'] || '',
|
||||
staff_count: Number(formData['Целевая численность, чел.']) || 0,
|
||||
placement_type: formData['Тип размещения'] || null,
|
||||
total_area: Number(formData['Площадь размещения, м2']) || 0,
|
||||
year: formData['Год'] ? Number(formData['Год']) : null,
|
||||
branch_id: formData['ССП/РФ'] || null,
|
||||
};
|
||||
|
||||
await ProjectsApi.update(id, projectData);
|
||||
|
||||
// Обновляем локальные данные
|
||||
setCurrentName(projectData.name);
|
||||
setEditModalOpen(false);
|
||||
toast.success('Название проекта обновлено');
|
||||
toast.success('Данные проекта успешно обновлены');
|
||||
|
||||
// Можно добавить рефреш данных или перезагрузку страницы
|
||||
window.location.reload(); // или используйте callback для обновления списка
|
||||
} catch (error) {
|
||||
toast.error(
|
||||
error?.response?.data?.detail || 'Не удалось обновить название проекта',
|
||||
error?.response?.data?.detail || 'Не удалось обновить данные проекта',
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSaveName = async () => {
|
||||
const nextName = editedName.trim();
|
||||
if (!nextName) {
|
||||
toast.error('Название не может быть пустым');
|
||||
return;
|
||||
}
|
||||
if (nextName === currentName) {
|
||||
setEditModalOpen(false);
|
||||
return;
|
||||
}
|
||||
await updateProject(nextName);
|
||||
const handleSaveProject = async (formData) => {
|
||||
await updateProject(formData);
|
||||
};
|
||||
|
||||
const getInitials = (name) => {
|
||||
@ -252,18 +273,21 @@ export const ProjectCardComponent = ({
|
||||
<Box sx={{ width: '100%' }}>
|
||||
<CardHeader>
|
||||
<ProjectId>ID: {id}</ProjectId>
|
||||
<Stack direction={'row'} spacing={'.5rem'} alignItems="center">
|
||||
<Stack direction={'row'} spacing={'.5rem'} sx={{
|
||||
|
||||
}} >
|
||||
{!exportMode && (
|
||||
<>
|
||||
{status && (
|
||||
<StatusChip
|
||||
status={status}
|
||||
label={getStatusLabel(status)}
|
||||
<StatusChip
|
||||
status={status}
|
||||
label={getStatusLabel(status)}
|
||||
size="small"
|
||||
/>
|
||||
)}
|
||||
<ExportButton onClick={handleExportProject} />
|
||||
<Edit onClick={handleOpenEdit} />
|
||||
{/* {Пока нет редактирование + экспорт} */}
|
||||
{/* <ExportButton onClick={handleExportProject} />
|
||||
<Edit onClick={handleOpenEdit} /> */}
|
||||
</>
|
||||
)}
|
||||
</Stack>
|
||||
@ -272,18 +296,13 @@ export const ProjectCardComponent = ({
|
||||
<ProjectTitle>{currentName}</ProjectTitle>
|
||||
|
||||
<ProjectInfoGrid>
|
||||
{level && (
|
||||
<LevelChip label={getLevelLabel(level)} size="small" />
|
||||
)}
|
||||
{project_type && (
|
||||
<InfoChip label={`Тип: ${project_type}`} size="small" />
|
||||
)}
|
||||
{report_count !== undefined && report_count !== null && (
|
||||
<InfoChip label={`Отчетов: ${report_count}`} size="small" />
|
||||
)}
|
||||
{parent_id && (
|
||||
<InfoChip label={`Родитель: ${parent_id}`} size="small" />
|
||||
)}
|
||||
|
||||
</ProjectInfoGrid>
|
||||
</Box>
|
||||
|
||||
@ -302,18 +321,6 @@ export const ProjectCardComponent = ({
|
||||
<IconWithContent icon={<UserIcon />}>
|
||||
{org_unit_name || 'Организация не указана'}
|
||||
</IconWithContent>
|
||||
{object_address && (
|
||||
<IconWithContent >
|
||||
{object_address}
|
||||
</IconWithContent>
|
||||
)}
|
||||
{(staff_count || total_area) && (
|
||||
<IconWithContent >
|
||||
{staff_count && `👥 ${staff_count}`}
|
||||
{staff_count && total_area && ' | '}
|
||||
{total_area && `📐 ${total_area} м²`}
|
||||
</IconWithContent>
|
||||
)}
|
||||
</CardBody>
|
||||
|
||||
{exportMode && (
|
||||
@ -327,6 +334,16 @@ export const ProjectCardComponent = ({
|
||||
)}
|
||||
</Stack>
|
||||
</ProjectCard>
|
||||
|
||||
<ModalCreateProject
|
||||
open={isEditModalOpen}
|
||||
onClose={handleCloseEdit}
|
||||
onCreate={handleSaveProject}
|
||||
config={projectData}
|
||||
initialData={editFormData}
|
||||
isEdit={true}
|
||||
projectId={id}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@ -1,4 +1,3 @@
|
||||
// src/pages/ProjectsPage/ProjectsPage.jsx
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { ProjectsApi } from '../../api/projects';
|
||||
import { CardsSkeleton } from '../../components/common/Skeleton';
|
||||
@ -18,12 +17,14 @@ import {
|
||||
Typography,
|
||||
IconButton,
|
||||
Tooltip,
|
||||
TextField,
|
||||
Autocomplete,
|
||||
} from '@mui/material';
|
||||
import { toast } from 'react-toastify';
|
||||
import { ModalCreateProject } from './ModalCreateProject';
|
||||
import {
|
||||
ROLES_NAME_ID,
|
||||
} from '../../constants';
|
||||
} from '../../constants/constants';
|
||||
import { useAuth } from '../../app/context/AuthProvider';
|
||||
import { PrimaryButton } from '../../components/common/Buttons/Buttons';
|
||||
import {
|
||||
@ -34,79 +35,20 @@ import { exportBulkForms, exportSingleForm } from '../../utils/exportForm';
|
||||
import { WhitePlus } from '../../components/common/icons/icons';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { PaginatedList } from '../../components/common/PaginatedList';
|
||||
import { DEFAULT_PROJECT_CONFIG } from '../../constants/projectConfig';
|
||||
import { SspApi } from '../../api/ssp';
|
||||
|
||||
|
||||
// Конфигурация для создания проекта
|
||||
const defaultProjectConfig = {
|
||||
'Тип проекта развития': {
|
||||
type: 'multiselect',
|
||||
options: [
|
||||
'Открытие ВСП',
|
||||
'Закрытие ВСП',
|
||||
'Переезд ВСП',
|
||||
'Реновация РФ',
|
||||
'Открытие УРМ',
|
||||
],
|
||||
placeholder: 'Выберите тип проекта',
|
||||
defaultValue: null,
|
||||
},
|
||||
'Название проекта': {
|
||||
type: 'text',
|
||||
placeholder: 'Введите название проекта',
|
||||
defaultValue: '',
|
||||
},
|
||||
'Формат ВСП': {
|
||||
type: 'multiselect',
|
||||
options: [
|
||||
'Фланговый',
|
||||
'Типовой',
|
||||
'Розничный',
|
||||
'МСБ',
|
||||
'Лёгкий',
|
||||
'Мини',
|
||||
'Розничный-киоск',
|
||||
'МБО',
|
||||
'Офис самообслуживания',
|
||||
'Другое',
|
||||
],
|
||||
placeholder: 'Выберите формат ВСП',
|
||||
defaultValue: null,
|
||||
},
|
||||
'Адрес объекта': {
|
||||
type: 'text',
|
||||
placeholder: 'Введите адрес объекта',
|
||||
defaultValue: '',
|
||||
},
|
||||
'Целевая численность, чел.': {
|
||||
type: 'number',
|
||||
placeholder: 'Введите число',
|
||||
defaultValue: 0,
|
||||
min: 0,
|
||||
step: 1,
|
||||
},
|
||||
'Тип размещения': {
|
||||
type: 'multiselect',
|
||||
options: ['Собственность', 'Аренда', 'Субаренда'],
|
||||
placeholder: 'Выберите тип размещения',
|
||||
defaultValue: null,
|
||||
},
|
||||
'Площадь размещения, м2': {
|
||||
type: 'number',
|
||||
placeholder: 'Введите число',
|
||||
defaultValue: 0,
|
||||
min: 0,
|
||||
step: 1,
|
||||
},
|
||||
};
|
||||
export { DEFAULT_PROJECT_CONFIG } from '../../constants/projectConfig';
|
||||
|
||||
export default function ProjectsPage() {
|
||||
const navigate = useNavigate();
|
||||
const [items, setItems] = useState([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [modalOpen, setModalOpen] = useState(false);
|
||||
const [query, setQuery] = useState('');
|
||||
const [statusFilter, setStatusFilter] = useState('');
|
||||
const [levelFilter, setLevelFilter] = useState('');
|
||||
|
||||
// Новые фильтры
|
||||
const [yearFilter, setYearFilter] = useState('');
|
||||
const [branchFilter, setBranchFilter] = useState('');
|
||||
|
||||
// Pagination state
|
||||
const [page, setPage] = useState(1);
|
||||
@ -118,11 +60,47 @@ export default function ProjectsPage() {
|
||||
const [exportMode, setExportMode] = useState(false);
|
||||
const [exportList, setExportList] = useState([]);
|
||||
|
||||
const [projectData, setProjectData] = useState(DEFAULT_PROJECT_CONFIG);
|
||||
|
||||
// Получаем список годов для фильтра (можно динамически или задать диапазон)
|
||||
const yearOptions = useMemo(() => {
|
||||
const currentYear = new Date().getFullYear();
|
||||
const years = [];
|
||||
for (let year = currentYear - 5; year <= currentYear + 2; year++) {
|
||||
years.push(year);
|
||||
}
|
||||
return years;
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!user) return;
|
||||
if (user.role_id == ROLES_NAME_ID.admin) {
|
||||
SspApi.getAll().then(data => {
|
||||
if (data.success) {
|
||||
setProjectData(prev => ({
|
||||
...prev, 'ССП/РФ': {
|
||||
...prev['ССП/РФ'],
|
||||
options: data.result
|
||||
}
|
||||
}))
|
||||
return;
|
||||
}
|
||||
toast.error('Ошибка при получении ССП/РФ');
|
||||
});
|
||||
return;
|
||||
}
|
||||
setProjectData(prev => ({
|
||||
...prev, 'ССП/РФ': {
|
||||
...prev['ССП/РФ'],
|
||||
options: user.org_units,
|
||||
}
|
||||
}))
|
||||
}, [user])
|
||||
|
||||
const openModal = () => {
|
||||
setModalOpen(true);
|
||||
};
|
||||
|
||||
// Маппинг полей из модалки в API
|
||||
const mapFormDataToApi = (formData) => {
|
||||
const apiData = {
|
||||
name: formData['Название проекта'] || '',
|
||||
@ -132,12 +110,9 @@ export default function ProjectsPage() {
|
||||
staff_count: formData['Целевая численность, чел.'] || 0,
|
||||
placement_type: formData['Тип размещения'] || null,
|
||||
total_area: formData['Площадь размещения, м2'] || 0,
|
||||
// Добавляем значения по умолчанию для обязательных полей
|
||||
year: new Date().getFullYear(),
|
||||
org_unit_id: user?.org_unit_id || null,
|
||||
form_type_code: 'project', // или другое значение по умолчанию
|
||||
status: 'active',
|
||||
level: 'project',
|
||||
year: formData['Год'] || null,
|
||||
branch_id: formData['ССП/РФ'] || null,
|
||||
|
||||
};
|
||||
return apiData;
|
||||
};
|
||||
@ -169,6 +144,9 @@ export default function ProjectsPage() {
|
||||
const params = {
|
||||
offset: skip,
|
||||
limit: currentLimit,
|
||||
// Добавляем фильтры в параметры запроса
|
||||
...(yearFilter && { year: parseInt(yearFilter) }),
|
||||
...(branchFilter && { branch_id: parseInt(branchFilter.id) }),
|
||||
};
|
||||
|
||||
const res = await ProjectsApi.list(params);
|
||||
@ -184,10 +162,11 @@ export default function ProjectsPage() {
|
||||
}
|
||||
};
|
||||
|
||||
// Сбрасываем страницу при изменении любых фильтров
|
||||
useEffect(() => {
|
||||
setPage(1);
|
||||
loadProjects(1, limit);
|
||||
}, [query, statusFilter, levelFilter]);
|
||||
}, [yearFilter, branchFilter]);
|
||||
|
||||
const handlePageChange = (event, value) => {
|
||||
setPage(value);
|
||||
@ -201,6 +180,13 @@ export default function ProjectsPage() {
|
||||
loadProjects(1, newLimit);
|
||||
};
|
||||
|
||||
// Сброс всех фильтров
|
||||
const handleClearFilters = () => {
|
||||
setYearFilter('');
|
||||
setBranchFilter('');
|
||||
setPage(1);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
loadProjects(page, limit);
|
||||
}, []);
|
||||
@ -253,6 +239,68 @@ export default function ProjectsPage() {
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
{/* Фильтры */}
|
||||
<Box sx={{ mb: 3, display: 'flex', gap: 2, flexWrap: 'wrap', alignItems: 'center' }}>
|
||||
|
||||
{/* Фильтр по году */}
|
||||
<FormControl size="small" sx={{ minWidth: 150, height:'2.5rem' }}>
|
||||
<Select
|
||||
displayEmpty
|
||||
value={yearFilter}
|
||||
onChange={(e) => setYearFilter(e.target.value)}
|
||||
>
|
||||
<MenuItem value="">Все годы</MenuItem>
|
||||
{yearOptions.map((year) => (
|
||||
<MenuItem key={year} value={year}>
|
||||
{year}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
|
||||
{/* Фильтр по ССП/РФ */}
|
||||
<FormControl size="small" sx={{ minWidth: '10rem' }}>
|
||||
<Autocomplete
|
||||
size="small"
|
||||
options={projectData['ССП/РФ']?.options ?? []}
|
||||
noOptionsText="Не найдено"
|
||||
getOptionLabel={(option) => option.title || option.name || '-'}
|
||||
value={
|
||||
branchFilter
|
||||
}
|
||||
onChange={(event, newValue) => {
|
||||
setBranchFilter(newValue);
|
||||
}}
|
||||
sx={{ height: '2.5rem', minWidth: '21.25rem' }}
|
||||
renderInput={(params) => (
|
||||
<TextField
|
||||
{...params}
|
||||
placeholder="Все"
|
||||
sx={{
|
||||
height: '2.5rem',
|
||||
width: '21.25rem',
|
||||
'& .MuiOutlinedInput-root': {
|
||||
height: '2.5rem',
|
||||
'& fieldset': {
|
||||
borderRadius: '0.5rem',
|
||||
borderColor: 'rgba(0,0,0,0.1)',
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</FormControl>
|
||||
<Button
|
||||
variant="outlined"
|
||||
size="small"
|
||||
onClick={handleClearFilters}
|
||||
sx={{ ml: 'auto' }}
|
||||
>
|
||||
Сбросить фильтры
|
||||
</Button>
|
||||
</Box>
|
||||
|
||||
<PaginatedList
|
||||
items={items}
|
||||
loading={loading}
|
||||
@ -267,6 +315,7 @@ export default function ProjectsPage() {
|
||||
<ProjectCardComponent
|
||||
key={project.id}
|
||||
project={project}
|
||||
projectDataInit={projectData}
|
||||
{...exportProps}
|
||||
/>
|
||||
)}
|
||||
@ -286,7 +335,7 @@ export default function ProjectsPage() {
|
||||
open={modalOpen}
|
||||
onClose={() => setModalOpen(false)}
|
||||
onCreate={handleCreateProject}
|
||||
config={defaultProjectConfig}
|
||||
config={projectData}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
||||
@ -57,7 +57,7 @@ export default function TablePage() {
|
||||
|
||||
const connection = connectToTable(tableId, user?.id, {
|
||||
onError: () => {
|
||||
toast.error('Ошибка подключения к серверу реального времени');
|
||||
toast.error('Ошибка подключения к серверу');
|
||||
},
|
||||
onClose: (event) => {
|
||||
if (isUnmountingRef.current) {
|
||||
|
||||
@ -3,6 +3,7 @@ import { createPortal } from 'react-dom';
|
||||
import { useParams, useNavigate } from 'react-router-dom';
|
||||
import { TasksApi } from '../../api/tasks';
|
||||
import { TablesApi } from '../../api/tables';
|
||||
import { ProjectsApi } from '../../api/projects'; // Добавьте импорт API для проектов
|
||||
import {
|
||||
TaskInfoContainer,
|
||||
NameTask,
|
||||
@ -21,13 +22,13 @@ import { FormsApi } from '../../api/forms';
|
||||
import { ExportWithTextButton } from '../../components/common/Buttons/ButtonsActions';
|
||||
import { exportSingleForm } from '../../utils/exportForm';
|
||||
import TablesList from '../../components/common/TableList/TableList';
|
||||
import { DIRECTION_TRANSLATE, FORM_TYPE_TRANSLATE, SHEET_NAME } from '../../constants';
|
||||
import { DIRECTION_TRANSLATE, FORM_TYPE_TRANSLATE, SHEET_NAME } from '../../constants/constants';
|
||||
|
||||
const TaskInfo = ({ task }) => {
|
||||
const TaskInfo = ({ task, isProject }) => {
|
||||
const portalContent = (
|
||||
<TaskInfoContainer>
|
||||
<BackButton to="/tasks" />
|
||||
<NameTask>{FORM_TYPE_TRANSLATE[task.form_type_code] || ''}</NameTask>
|
||||
<BackButton to={isProject ? "/projects" : "/tasks"} />
|
||||
<NameTask>{isProject ? 'Проект' : FORM_TYPE_TRANSLATE[task.form_type_code] || ''}</NameTask>
|
||||
</TaskInfoContainer>
|
||||
);
|
||||
|
||||
@ -39,9 +40,13 @@ const TaskInfo = ({ task }) => {
|
||||
};
|
||||
|
||||
export default function TaskPage() {
|
||||
const { taskId } = useParams();
|
||||
const { taskId, projectId } = useParams();
|
||||
const navigate = useNavigate();
|
||||
const id = Number(taskId);
|
||||
|
||||
// Определяем, что у нас за ID и какой тип
|
||||
const isProject = !!projectId;
|
||||
const id = isProject ? Number(projectId) : Number(taskId);
|
||||
|
||||
const [task, setTask] = useState(null);
|
||||
const [tables, setTables] = useState([]);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
@ -50,23 +55,42 @@ export default function TaskPage() {
|
||||
const [modalProjectOpen, setModalProjectOpen] = useState(false);
|
||||
const [tempProjects, setTempProjects] = useState([]);
|
||||
const [projects, setProjects] = useState([]);
|
||||
const [isProjectData, setIsProjectData] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const getSheets = async () => {
|
||||
const getData = async () => {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
const data = await TasksApi.getSheets(id);
|
||||
// Преобразуем данные с учетом direction
|
||||
const mappedTables = data.result.all_sheets.map(sheet => ({
|
||||
name: SHEET_NAME[sheet.sheet_name] || sheet.sheet_name,
|
||||
sheet: sheet.sheet_name,
|
||||
direction: sheet.direction,
|
||||
}));
|
||||
//пока скрыт
|
||||
setTables(mappedTables.filter(t => t.sheet !== "OTCH9F",));
|
||||
if (isProject) {
|
||||
// Получаем информацию о проекте
|
||||
const projectData = await ProjectsApi.get(id);
|
||||
setTask(projectData.result);
|
||||
setIsProjectData(true);
|
||||
console.log(projectData.result.reports);
|
||||
|
||||
const mappedTables = projectData.result.reports.map(sheet => ({
|
||||
name: SHEET_NAME[sheet.report_type] || sheet.report_type,
|
||||
sheet: sheet.report_type,
|
||||
}));
|
||||
setTables(mappedTables);
|
||||
} else {
|
||||
// Получаем информацию о задаче
|
||||
const taskData = await TasksApi.getById(id);
|
||||
setTask(taskData.result);
|
||||
setIsProjectData(false);
|
||||
|
||||
// Получаем таблицы задачи
|
||||
const tablesData = await TasksApi.getSheets(id);
|
||||
const mappedTables = tablesData.result.all_sheets.map(sheet => ({
|
||||
name: SHEET_NAME[sheet.sheet_name] || sheet.sheet_name,
|
||||
sheet: sheet.sheet_name,
|
||||
direction: sheet.direction,
|
||||
}));
|
||||
setTables(mappedTables.filter(t => t.sheet !== "OTCH9F"));
|
||||
}
|
||||
} catch (error) {
|
||||
toast.error(
|
||||
error?.response?.data?.detail || 'Ошибка получения данных задачи',
|
||||
error?.response?.data?.detail || `Ошибка получения данных ${isProject ? 'проекта' : 'задачи'}`,
|
||||
);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
@ -74,27 +98,11 @@ export default function TaskPage() {
|
||||
};
|
||||
|
||||
if (Number.isFinite(id)) {
|
||||
getSheets();
|
||||
getData();
|
||||
}
|
||||
}, [id]);
|
||||
}, [id, isProject]);
|
||||
|
||||
useEffect(() => {
|
||||
const getFormInfo = async () => {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
const data = await TasksApi.getById(id);
|
||||
setTask(data.result);
|
||||
} catch (error) {
|
||||
// обработка ошибки
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
if (Number.isFinite(id)) {
|
||||
getFormInfo();
|
||||
}
|
||||
}, [id]);
|
||||
// Объединили два useEffect в один
|
||||
|
||||
const handleCreateProject = (projectData) => {
|
||||
// логика создания проекта
|
||||
@ -106,6 +114,14 @@ export default function TaskPage() {
|
||||
return searchString.includes(query.toLowerCase());
|
||||
});
|
||||
|
||||
// Функция для экспорта
|
||||
const handleExport = () => {
|
||||
const fileName = isProject
|
||||
? `project_${task?.title || id}`
|
||||
: `form_${task?.title || taskId}`;
|
||||
exportSingleForm(id, fileName);
|
||||
};
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="flex justify-center pt-12">
|
||||
@ -116,7 +132,7 @@ export default function TaskPage() {
|
||||
|
||||
return (
|
||||
<>
|
||||
{task && <TaskInfo task={task} />}
|
||||
{task && <TaskInfo task={task} isProject={isProject} />}
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
@ -142,9 +158,7 @@ export default function TaskPage() {
|
||||
onChange={setQuery}
|
||||
/>
|
||||
<Stack sx={{ flexDirection: 'row', spacing: '.5rem', justifyContent: 'end', gap: '.5rem' }}>
|
||||
<ExportWithTextButton
|
||||
onClick={() => exportSingleForm(taskId, task?.title ?? 'form')}
|
||||
/>
|
||||
<ExportWithTextButton onClick={handleExport} />
|
||||
<PrimaryOutlinedButton
|
||||
variant="outlined"
|
||||
onClick={() => setModalStageOpen(true)}
|
||||
@ -155,9 +169,10 @@ export default function TaskPage() {
|
||||
<SettingStageModal
|
||||
isOpen={modalStageOpen}
|
||||
onClose={() => setModalStageOpen(false)}
|
||||
taskId={taskId}
|
||||
mode="task"
|
||||
formTypeCode={task?.form_type_code || ''}
|
||||
taskId={isProject ? undefined : taskId}
|
||||
projectId={isProject ? projectId : undefined}
|
||||
mode={isProject ? "project" : "task"}
|
||||
formTypeCode={ task?.form_type_code || ''}
|
||||
sheets={tables}
|
||||
/>
|
||||
|
||||
@ -168,8 +183,9 @@ export default function TaskPage() {
|
||||
query={query}
|
||||
projects={projects}
|
||||
filteredTables={filteredTables}
|
||||
basePath="/table"
|
||||
basePath={"/table"}
|
||||
formInfo={task}
|
||||
isProject={isProject}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
@ -27,7 +27,7 @@ import {
|
||||
ORG_UNIT_TYPE_OPTIONS,
|
||||
ROLES_NAME_ID,
|
||||
FORM_TYPE_TRANSLATE
|
||||
} from '../../constants';
|
||||
} from '../../constants/constants';
|
||||
import { useAuth } from '../../app/context/AuthProvider';
|
||||
import { SspApi } from '../../api/ssp';
|
||||
import { PrimaryButton } from '../../components/common/Buttons/Buttons';
|
||||
@ -256,7 +256,6 @@ export default function TasksPage() {
|
||||
<>
|
||||
<PageContainer>
|
||||
<HeaderSwitch />
|
||||
<HeaderSwitch />
|
||||
|
||||
<PaginatedList
|
||||
items={items}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user