diff --git a/web/src/api/tasks.js b/web/src/api/tasks.js
index db7291d..8d1016a 100644
--- a/web/src/api/tasks.js
+++ b/web/src/api/tasks.js
@@ -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);
diff --git a/web/src/app/Routes.jsx b/web/src/app/Routes.jsx
index 4c26684..2069477 100644
--- a/web/src/app/Routes.jsx
+++ b/web/src/app/Routes.jsx
@@ -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 = () => {
} />
} />
} />
- } />
} />
} />
} />
diff --git a/web/src/components/HeaderMenu/HeaderSwitch.jsx b/web/src/components/HeaderMenu/HeaderSwitch.jsx
index d26d3fa..dcd49f1 100644
--- a/web/src/components/HeaderMenu/HeaderSwitch.jsx
+++ b/web/src/components/HeaderMenu/HeaderSwitch.jsx
@@ -75,7 +75,7 @@ export function HeaderSwitch() {
}
>
- Задачи и формы
+ Задачи
{user.role_id === ROLES_NAME_ID.admin && (
+ {showSelectAll && (
+ event.preventDefault()}
+ sx={{
+ display: 'flex',
+ alignItems: 'center',
+ gap: 0.5,
+ px: 2,
+ py: 1,
+ borderBottom: '1px solid',
+ borderColor: 'divider',
+ }}
+ >
+ onSelectAll(event.target.checked)}
+ />
+
+ Выделить все
+
+
+ )}
+ {children}
+
+ );
+ },
+);
diff --git a/web/src/pages/TasksPage.jsx b/web/src/pages/TasksPage/TasksPage.jsx
similarity index 64%
rename from web/src/pages/TasksPage.jsx
rename to web/src/pages/TasksPage/TasksPage.jsx
index f007b84..fb18a99 100644
--- a/web/src/pages/TasksPage.jsx
+++ b/web/src/pages/TasksPage/TasksPage.jsx
@@ -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,40 @@ 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 TasksApi.create({
+ year: Number(formData.year),
+ form_type_code: formData.formTypeCode,
+ org_unit_ids: formData.org_unit_id,
+ });
- // 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 +139,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 +154,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 +216,6 @@ export default function TasksPage() {
setSspList(data.result);
}
});
- FormsApi.formsList().then((data) => {
- if (data) {
- setForms(data);
- }
- });
}
}, [modalOpen]);
@@ -350,41 +403,30 @@ export default function TasksPage() {
}
>
-
- Название задачи
- {/* handleFormChange('title', e.target.value)}
- /> */}
-
-
Форма
- 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) => (
-
- )}
- />
+ displayEmpty
+ fullWidth
+ value={formData.formTypeCode}
+ onChange={(e) => handleFormChange('formTypeCode', e.target.value)}
+ renderValue={(selected) =>
+ selected ? (
+ selected
+ ) : (
+ Не выбрана
+ )
+ }
+ MenuProps={modalSelectMenuProps}
+ sx={modalSelectSx}
+ >
+ {FORM_TYPE_OPTIONS.map((formType) => (
+
+ ))}
+
@@ -398,26 +440,60 @@ export default function TasksPage() {
/>
+
+ Тип
+
+
+
ССП/Региональный филиал
- {/* 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) => (
)}
+ slots={{
+ paper: OrgUnitsAutocompletePaper,
+ }}
slotProps={{
- chip: {
- size: 'small',
+ paper: {
+ onSelectAll: handleSelectAllOrgUnits,
+ isAllSelected: isAllOrgUnitsSelected,
+ isIndeterminate: isOrgUnitsIndeterminate,
+ showSelectAll: filteredOrgUnits.length > 0,
+ },
+ popper: {
+ style: { zIndex: 1301 },
},
}}
- /> */}
+ />
diff --git a/web/src/pages/TasksPage/renderOrgUnitValue.jsx b/web/src/pages/TasksPage/renderOrgUnitValue.jsx
new file mode 100644
index 0000000..998f472
--- /dev/null
+++ b/web/src/pages/TasksPage/renderOrgUnitValue.jsx
@@ -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 (
+
+ );
+ });
+
+ if (hidden.length > 0) {
+ tags.push(
+ option.title).join(', ')}
+ arrow
+ >
+
+ ,
+ );
+ }
+
+ return tags;
+};