[AURORA-1043] - создание задачи
This commit is contained in:
parent
4308e11bc9
commit
c3a4c9436a
@ -14,7 +14,7 @@ export const TasksApi = {
|
||||
return api.get(`/form/${formId}`).then((r) => r.data);
|
||||
},
|
||||
create(payload) {
|
||||
return api.put("/tasks/", payload).then((r) => r.data);
|
||||
return api.post("/form/", payload).then((r) => r.data);
|
||||
},
|
||||
editTask(taskId, payload = {}) {
|
||||
return api.patch(`/tasks/${taskId}`, payload).then((r) => r.data);
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { Navigate, Outlet, Route, Routes } from 'react-router-dom';
|
||||
import { useAuth } from './context/AuthProvider';
|
||||
import { Layout } from './layout/Layout';
|
||||
import TasksPage from '../pages/TasksPage';
|
||||
import TasksPage from '../pages/TasksPage/TasksPage';
|
||||
import FormsPage from '../pages/FormsPage';
|
||||
import TaskPage from '../pages/TaskPage/TaskPage';
|
||||
import FormPage from '../pages/FormPage';
|
||||
@ -39,7 +39,6 @@ export const AppRoutes = () => {
|
||||
<Route path="/tasks" element={<TasksPage />} />
|
||||
<Route path="/forms" element={<FormsPage />} />
|
||||
<Route path="/task/:taskId" element={<TaskPage />} />
|
||||
<Route path="/form/:formId" element={<FormPage />} />
|
||||
<Route path="/table/:tableId" element={<TablePage />} />
|
||||
<Route path="/table-temp/:tableId" element={<TableTempPage />} />
|
||||
<Route path="/admin_panel/users" element={<UsersPage />} />
|
||||
|
||||
@ -75,7 +75,7 @@ export function HeaderSwitch() {
|
||||
}
|
||||
></TasksSvg>
|
||||
</div>
|
||||
<span>Задачи и формы</span>
|
||||
<span>Задачи</span>
|
||||
</StyledNavLink>
|
||||
{user.role_id === ROLES_NAME_ID.admin && (
|
||||
<StyledNavLink
|
||||
|
||||
@ -35,6 +35,13 @@ export const EDIT_ACCESS_RUSSIAN = {
|
||||
3: 'Просмотр и редактирование',
|
||||
};
|
||||
|
||||
export const FORM_TYPE_OPTIONS = ['FORM_1', 'FORM_2', 'FORM_3', 'FORM_4'];
|
||||
|
||||
export const ORG_UNIT_TYPE_OPTIONS = [
|
||||
{ value: 'ssp', label: 'ССП' },
|
||||
{ value: 'rf', label: 'Региональный филиал' },
|
||||
];
|
||||
|
||||
export const SHEET_NAME = {
|
||||
"AHR": "АХР",
|
||||
"CAP": "КВП",
|
||||
|
||||
46
web/src/pages/TasksPage/OrgUnitsAutocompletePaper.jsx
Normal file
46
web/src/pages/TasksPage/OrgUnitsAutocompletePaper.jsx
Normal file
@ -0,0 +1,46 @@
|
||||
import { forwardRef } from 'react';
|
||||
import { Box, Paper, Typography } from '@mui/material';
|
||||
import { CustomCheckbox } from '../../components/common/StyledCheckbox';
|
||||
|
||||
export const OrgUnitsAutocompletePaper = forwardRef(
|
||||
function OrgUnitsAutocompletePaper(
|
||||
{
|
||||
children,
|
||||
onSelectAll,
|
||||
isAllSelected,
|
||||
isIndeterminate,
|
||||
showSelectAll,
|
||||
...other
|
||||
},
|
||||
ref,
|
||||
) {
|
||||
return (
|
||||
<Paper ref={ref} {...other}>
|
||||
{showSelectAll && (
|
||||
<Box
|
||||
onMouseDown={(event) => event.preventDefault()}
|
||||
sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: 0.5,
|
||||
px: 2,
|
||||
py: 1,
|
||||
borderBottom: '1px solid',
|
||||
borderColor: 'divider',
|
||||
}}
|
||||
>
|
||||
<CustomCheckbox
|
||||
checked={isAllSelected}
|
||||
indeterminate={isIndeterminate}
|
||||
onChange={(event) => onSelectAll(event.target.checked)}
|
||||
/>
|
||||
<Typography variant="body2" sx={{ fontSize: '0.875rem' }}>
|
||||
Выделить все
|
||||
</Typography>
|
||||
</Box>
|
||||
)}
|
||||
{children}
|
||||
</Paper>
|
||||
);
|
||||
},
|
||||
);
|
||||
@ -1,11 +1,11 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { TasksApi } from '../api/tasks';
|
||||
import { CardsSkeleton } from '../components/common/Skeleton';
|
||||
import { SwitchFormTask } from '../components/common/SwitchFormTask/SwitchFormTask';
|
||||
import SearchComponent from '../components/common/SearchComponent';
|
||||
import { TaskCardComponent } from '../components/common/TaskCardComponent';
|
||||
import { HeaderSwitch } from '../components/HeaderMenu/HeaderSwitch';
|
||||
import { PageContainer, TasksContainer } from './StyledForPage';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { TasksApi } from '../../api/tasks';
|
||||
import { CardsSkeleton } from '../../components/common/Skeleton';
|
||||
import { SwitchFormTask } from '../../components/common/SwitchFormTask/SwitchFormTask';
|
||||
import SearchComponent from '../../components/common/SearchComponent';
|
||||
import { TaskCardComponent } from '../../components/common/TaskCardComponent';
|
||||
import { HeaderSwitch } from '../../components/HeaderMenu/HeaderSwitch';
|
||||
import { PageContainer, TasksContainer } from '../StyledForPage';
|
||||
import {
|
||||
Box,
|
||||
FormControl,
|
||||
@ -21,18 +21,34 @@ import {
|
||||
Typography,
|
||||
} from '@mui/material';
|
||||
import { toast } from 'react-toastify';
|
||||
import Modal from '../components/common/Modal/Modal';
|
||||
import { FormsApi } from '../api/forms';
|
||||
import { ROLES_NAME_ID } from '../constants';
|
||||
import { useAuth } from '../app/context/AuthProvider';
|
||||
import { SspApi } from '../api/ssp';
|
||||
import { PrimaryButton } from '../components/common/Buttons/Buttons';
|
||||
import Modal from '../../components/common/Modal/Modal';
|
||||
import {
|
||||
FORM_TYPE_OPTIONS,
|
||||
ORG_UNIT_TYPE_OPTIONS,
|
||||
ROLES_NAME_ID,
|
||||
} from '../../constants';
|
||||
import { useAuth } from '../../app/context/AuthProvider';
|
||||
import { SspApi } from '../../api/ssp';
|
||||
import { PrimaryButton } from '../../components/common/Buttons/Buttons';
|
||||
import {
|
||||
ExportExitButton,
|
||||
ExportWithTextButton,
|
||||
} from '../components/common/Buttons/ButtonsActions';
|
||||
import { exportBulkForms, exportSingleForm } from '../utils/exportForm';
|
||||
import { WhitePlus } from '../components/common/icons/icons';
|
||||
} from '../../components/common/Buttons/ButtonsActions';
|
||||
import { exportBulkForms, exportSingleForm } from '../../utils/exportForm';
|
||||
import { WhitePlus } from '../../components/common/icons/icons';
|
||||
import { OrgUnitsAutocompletePaper } from './OrgUnitsAutocompletePaper';
|
||||
import { renderOrgUnitValue } from './renderOrgUnitValue';
|
||||
|
||||
const modalSelectMenuProps = {
|
||||
sx: { zIndex: 1301 },
|
||||
style: { zIndex: 1301 },
|
||||
};
|
||||
|
||||
const modalSelectSx = {
|
||||
'& .MuiOutlinedInput-notchedOutline': {
|
||||
borderColor: 'rgba(0,0,0,0.1)',
|
||||
},
|
||||
};
|
||||
|
||||
export default function TasksPage() {
|
||||
const [items, setItems] = useState([]);
|
||||
@ -48,14 +64,30 @@ export default function TasksPage() {
|
||||
const [isLoadAll, setisLoadAll] = useState(false);
|
||||
|
||||
const [sspList, setSspList] = useState([]);
|
||||
const [forms, setForms] = useState([]);
|
||||
const [formData, setFormData] = useState({
|
||||
title: '',
|
||||
year: '',
|
||||
sspIds: [],
|
||||
formId: '',
|
||||
orgUnitType: '',
|
||||
org_unit_id: [],
|
||||
formTypeCode: '',
|
||||
});
|
||||
|
||||
const filteredOrgUnits = useMemo(() => {
|
||||
if (!formData.orgUnitType) return [];
|
||||
const isSsp = formData.orgUnitType === 'ssp';
|
||||
return sspList.filter((unit) => unit.is_ssp === isSsp);
|
||||
}, [sspList, formData.orgUnitType]);
|
||||
|
||||
const isAllOrgUnitsSelected = useMemo(() => {
|
||||
return (
|
||||
filteredOrgUnits.length > 0 &&
|
||||
filteredOrgUnits.every((unit) => formData.org_unit_id.includes(unit.id))
|
||||
);
|
||||
}, [filteredOrgUnits, formData.org_unit_id]);
|
||||
|
||||
const isOrgUnitsIndeterminate = useMemo(() => {
|
||||
return formData.org_unit_id.length > 0 && !isAllOrgUnitsSelected;
|
||||
}, [formData.org_unit_id, isAllOrgUnitsSelected]);
|
||||
|
||||
const { user } = useAuth();
|
||||
|
||||
const [exportMode, setExportMode] = useState(false);
|
||||
@ -66,34 +98,44 @@ export default function TasksPage() {
|
||||
|
||||
const handleSubmit = async () => {
|
||||
if (
|
||||
!formData.title ||
|
||||
!formData.year ||
|
||||
formData.sspIds.length === 0 ||
|
||||
!formData.formId
|
||||
!formData.orgUnitType ||
|
||||
formData.org_unit_id.length === 0 ||
|
||||
!formData.formTypeCode
|
||||
) {
|
||||
alert('Пожалуйста, заполните все поля');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const taskData = {
|
||||
title: formData.title,
|
||||
ssp_ids: formData.sspIds,
|
||||
form_id: formData.formId,
|
||||
start_year: Number(formData.year),
|
||||
};
|
||||
setLoadingUpdate(true);
|
||||
|
||||
await TasksApi.create(taskData);
|
||||
await Promise.all(
|
||||
formData.org_unit_id.map((orgUnitId) =>
|
||||
TasksApi.create({
|
||||
year: Number(formData.year),
|
||||
form_type_code: formData.formTypeCode,
|
||||
org_unit_id: orgUnitId,
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
// Reset to first page after creating a new task
|
||||
setPage(1);
|
||||
loadTasks(1, limit);
|
||||
|
||||
setModalOpen(false);
|
||||
setFormData({ title: '', year: '', sspIds: [], formId: '' });
|
||||
setFormData({
|
||||
year: '',
|
||||
orgUnitType: '',
|
||||
org_unit_id: [],
|
||||
formTypeCode: '',
|
||||
});
|
||||
} catch (error) {
|
||||
toast.error(error.response.data.detail || 'Ошибка при создании задачи');
|
||||
toast.error(
|
||||
error.response?.data?.detail ||
|
||||
error.response?.data?.message ||
|
||||
'Ошибка при создании задачи',
|
||||
);
|
||||
} finally {
|
||||
setLoadingUpdate(false);
|
||||
}
|
||||
@ -101,7 +143,12 @@ export default function TasksPage() {
|
||||
|
||||
const handleCancel = () => {
|
||||
setModalOpen(false);
|
||||
setFormData({ title: '', year: '', sspIds: [], formId: '' });
|
||||
setFormData({
|
||||
year: '',
|
||||
orgUnitType: '',
|
||||
org_unit_id: [],
|
||||
formTypeCode: '',
|
||||
});
|
||||
};
|
||||
|
||||
const handleFormChange = (field, value) => {
|
||||
@ -111,6 +158,21 @@ export default function TasksPage() {
|
||||
}));
|
||||
};
|
||||
|
||||
const handleOrgUnitTypeChange = (value) => {
|
||||
setFormData((prev) => ({
|
||||
...prev,
|
||||
orgUnitType: value,
|
||||
org_unit_id: [],
|
||||
}));
|
||||
};
|
||||
|
||||
const handleSelectAllOrgUnits = (checked) => {
|
||||
handleFormChange(
|
||||
'org_unit_id',
|
||||
checked ? filteredOrgUnits.map((unit) => unit.id) : [],
|
||||
);
|
||||
};
|
||||
|
||||
const loadTasks = async (currentPage = page, currentLimit = limit) => {
|
||||
if (isLoadAll) return;
|
||||
setLoading(true);
|
||||
@ -158,11 +220,6 @@ export default function TasksPage() {
|
||||
setSspList(data.result);
|
||||
}
|
||||
});
|
||||
FormsApi.formsList().then((data) => {
|
||||
if (data) {
|
||||
setForms(data);
|
||||
}
|
||||
});
|
||||
}
|
||||
}, [modalOpen]);
|
||||
|
||||
@ -350,41 +407,30 @@ export default function TasksPage() {
|
||||
}
|
||||
>
|
||||
<div className="space-y-4">
|
||||
<FormControl fullWidth>
|
||||
<FormLabel required>Название задачи</FormLabel>
|
||||
{/* <TextField
|
||||
size="small"
|
||||
placeholder="Укажите название задачи"
|
||||
value={formData.title}
|
||||
onChange={(e) => handleFormChange('title', e.target.value)}
|
||||
/> */}
|
||||
</FormControl>
|
||||
|
||||
<FormControl fullWidth size="small">
|
||||
<FormLabel required>Форма</FormLabel>
|
||||
<Autocomplete
|
||||
<Select
|
||||
size="small"
|
||||
options={forms}
|
||||
noOptionsText="Не найдено"
|
||||
getOptionLabel={(option) => option.title || option.name || option.form_type_code || ''}
|
||||
value={forms.find((f) => f.id === formData.formId) || null}
|
||||
onChange={(event, newValue) => {
|
||||
handleFormChange('formId', newValue ? newValue.id : '');
|
||||
}}
|
||||
renderInput={(params) => (
|
||||
<TextField
|
||||
{...params}
|
||||
placeholder="Не выбрана"
|
||||
sx={{
|
||||
'& .MuiOutlinedInput-root': {
|
||||
'& fieldset': {
|
||||
borderColor: 'rgba(0,0,0,0.1)',
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
displayEmpty
|
||||
fullWidth
|
||||
value={formData.formTypeCode}
|
||||
onChange={(e) => handleFormChange('formTypeCode', e.target.value)}
|
||||
renderValue={(selected) =>
|
||||
selected ? (
|
||||
selected
|
||||
) : (
|
||||
<span style={{ color: 'rgba(0,0,0,0.6)' }}>Не выбрана</span>
|
||||
)
|
||||
}
|
||||
MenuProps={modalSelectMenuProps}
|
||||
sx={modalSelectSx}
|
||||
>
|
||||
{FORM_TYPE_OPTIONS.map((formType) => (
|
||||
<MenuItem key={formType} value={formType}>
|
||||
{formType}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
|
||||
<FormControl fullWidth>
|
||||
@ -398,26 +444,60 @@ export default function TasksPage() {
|
||||
/>
|
||||
</FormControl>
|
||||
|
||||
<FormControl fullWidth size="small">
|
||||
<FormLabel required>Тип</FormLabel>
|
||||
<Select
|
||||
size="small"
|
||||
displayEmpty
|
||||
fullWidth
|
||||
value={formData.orgUnitType}
|
||||
onChange={(e) => handleOrgUnitTypeChange(e.target.value)}
|
||||
renderValue={(selected) => {
|
||||
const option = ORG_UNIT_TYPE_OPTIONS.find(
|
||||
(item) => item.value === selected,
|
||||
);
|
||||
return option ? (
|
||||
option.label
|
||||
) : (
|
||||
<span style={{ color: 'rgba(0,0,0,0.6)' }}>Не выбран</span>
|
||||
);
|
||||
}}
|
||||
MenuProps={modalSelectMenuProps}
|
||||
sx={modalSelectSx}
|
||||
>
|
||||
{ORG_UNIT_TYPE_OPTIONS.map((option) => (
|
||||
<MenuItem key={option.value} value={option.value}>
|
||||
{option.label}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
|
||||
<FormControl fullWidth size="small">
|
||||
<FormLabel required>ССП/Региональный филиал</FormLabel>
|
||||
{/* <Autocomplete
|
||||
<Autocomplete
|
||||
multiple
|
||||
size="small"
|
||||
options={sspList}
|
||||
disabled={!formData.orgUnitType}
|
||||
options={filteredOrgUnits}
|
||||
disableCloseOnSelect
|
||||
noOptionsText="Не найдено"
|
||||
getOptionLabel={(option) => option.title || ''}
|
||||
value={sspList.filter((ssp) => formData.sspIds.includes(ssp.id))}
|
||||
isOptionEqualToValue={(option, value) => option.id === value.id}
|
||||
renderValue={renderOrgUnitValue}
|
||||
value={filteredOrgUnits.filter((ssp) =>
|
||||
formData.org_unit_id.includes(ssp.id),
|
||||
)}
|
||||
onChange={(event, newValue) => {
|
||||
handleFormChange(
|
||||
'sspIds',
|
||||
'org_unit_id',
|
||||
newValue.map((ssp) => ssp.id),
|
||||
);
|
||||
}}
|
||||
renderInput={(params) => (
|
||||
<TextField
|
||||
{...params}
|
||||
placeholder={formData.sspIds.length === 0 ? 'Не выбрано' : ''}
|
||||
placeholder={formData.org_unit_id.length === 0 ? 'Не выбрано' : ''}
|
||||
sx={{
|
||||
'& .MuiOutlinedInput-root': {
|
||||
'& fieldset': {
|
||||
@ -427,12 +507,21 @@ export default function TasksPage() {
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
slots={{
|
||||
paper: OrgUnitsAutocompletePaper,
|
||||
}}
|
||||
slotProps={{
|
||||
chip: {
|
||||
size: 'small',
|
||||
paper: {
|
||||
onSelectAll: handleSelectAllOrgUnits,
|
||||
isAllSelected: isAllOrgUnitsSelected,
|
||||
isIndeterminate: isOrgUnitsIndeterminate,
|
||||
showSelectAll: filteredOrgUnits.length > 0,
|
||||
},
|
||||
popper: {
|
||||
style: { zIndex: 1301 },
|
||||
},
|
||||
}}
|
||||
/> */}
|
||||
/>
|
||||
</FormControl>
|
||||
</div>
|
||||
</Modal>
|
||||
38
web/src/pages/TasksPage/renderOrgUnitValue.jsx
Normal file
38
web/src/pages/TasksPage/renderOrgUnitValue.jsx
Normal file
@ -0,0 +1,38 @@
|
||||
import { Chip, Tooltip } from '@mui/material';
|
||||
|
||||
export const ORG_UNIT_TAGS_LIMIT = 2;
|
||||
|
||||
export const renderOrgUnitValue = (value, getItemProps) => {
|
||||
const visible = value.slice(0, ORG_UNIT_TAGS_LIMIT);
|
||||
const hidden = value.slice(ORG_UNIT_TAGS_LIMIT);
|
||||
|
||||
const tags = visible.map((option, index) => {
|
||||
const { key, ...itemProps } = getItemProps({ index });
|
||||
return (
|
||||
<Chip
|
||||
key={key}
|
||||
label={option.title || ''}
|
||||
size="small"
|
||||
{...itemProps}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
if (hidden.length > 0) {
|
||||
tags.push(
|
||||
<Tooltip
|
||||
key="org-unit-more-tags"
|
||||
title={hidden.map((option) => option.title).join(', ')}
|
||||
arrow
|
||||
>
|
||||
<Chip
|
||||
label={`+${hidden.length}`}
|
||||
size="small"
|
||||
className="MuiAutocomplete-tag"
|
||||
/>
|
||||
</Tooltip>,
|
||||
);
|
||||
}
|
||||
|
||||
return tags;
|
||||
};
|
||||
Loading…
x
Reference in New Issue
Block a user