diff --git a/web/src/api/tasks.js b/web/src/api/tasks.js
index f35713d..af20cd9 100644
--- a/web/src/api/tasks.js
+++ b/web/src/api/tasks.js
@@ -33,13 +33,15 @@ export const TasksApi = {
createProjectTable(taskId, payload) {
return api.post(`/tasks/${taskId}/projects`, payload).then((r) => r.data);
},
- exportTask(taskId) {
- return api.get(`export/task/${taskId}`, {
+ exportTask(taskId, params = {}) {
+ return api.get(`export/form/${taskId}`, {
+ params,
responseType: "blob",
});
},
- exportTasks(payload) {
+ exportTasks(payload, params = {}) {
return api.post(`export/bulk`, payload, {
+ params,
responseType: "blob",
});
},
diff --git a/web/src/components/common/ExportFormModal.jsx b/web/src/components/common/ExportFormModal.jsx
new file mode 100644
index 0000000..ab5ea79
--- /dev/null
+++ b/web/src/components/common/ExportFormModal.jsx
@@ -0,0 +1,150 @@
+import { useEffect, useState } from 'react';
+import {
+ Box,
+ Button,
+ FormControl,
+ FormLabel,
+ MenuItem,
+ Select,
+} from '@mui/material';
+import Modal from './Modal/Modal';
+import { TasksApi } from '../../api/tasks';
+import { downloadFile } from '../../utils/downloadFile';
+import { getApiErrorMessage } from '../../utils/getApiErrorMessage';
+import { toast } from 'react-toastify';
+
+const DIRECTION_OPTIONS = [
+ { value: 'Development', label: 'Development' },
+ { value: 'Support', label: 'Support' },
+];
+
+export const ExportFormModal = ({
+ open,
+ onClose,
+ onSuccess,
+ taskId,
+ taskTitle = 'form',
+ formIds,
+ fileName = 'Задачи',
+}) => {
+ const isBulk = Boolean(formIds?.length);
+ const [direction, setDirection] = useState('');
+ const [loading, setLoading] = useState(false);
+
+ useEffect(() => {
+ if (open) {
+ setDirection('');
+ setLoading(false);
+ }
+ }, [open]);
+
+ const handleClose = () => {
+ if (!loading) {
+ onClose();
+ }
+ };
+
+ const handleExport = async () => {
+ setLoading(true);
+ try {
+ const params = direction ? { direction } : {};
+
+ if (isBulk) {
+ const response = await TasksApi.exportTasks(
+ { form_ids: formIds },
+ params,
+ );
+ await downloadFile(response, fileName, 'xlsx');
+ } else if (taskId) {
+ const response = await TasksApi.exportTask(taskId, params);
+ await downloadFile(response, taskTitle, 'xlsx');
+ }
+
+ onSuccess?.();
+ onClose();
+ } catch (error) {
+ const message = await getApiErrorMessage(
+ error,
+ 'Ошибка при скачивании файла',
+ );
+ toast.error(message);
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ return (
+
+
+
+ >
+ }
+ >
+
+
+ direction
+
+
+
+
+
+
+ );
+};
diff --git a/web/src/components/common/TaskCardComponent.jsx b/web/src/components/common/TaskCardComponent.jsx
index 4d83467..1dbc160 100644
--- a/web/src/components/common/TaskCardComponent.jsx
+++ b/web/src/components/common/TaskCardComponent.jsx
@@ -26,7 +26,6 @@ import { formatDate } from '../../utils/formatDate';
import { IconWithContent } from './IconWithContent';
import { Chip } from '../styles/StyledChip';
import { ExportButton } from '../common/Buttons/ButtonsActions';
-import { downloadFile } from '../../utils/downloadFile';
const TaskCard = styled.div`
width: 26.8rem;
@@ -93,6 +92,7 @@ export const TaskCardComponent = ({
exportMode = false,
isLoadingFile = false,
onAddForExport,
+ onExportClick,
}) => {
const navigate = useNavigate();
const { id, title, name, start_date, end_date, tables_count, org_unit, form_type_code } = taskForm;
@@ -132,14 +132,9 @@ export const TaskCardComponent = ({
}
};
- const handleExportTask = async (event) => {
+ const handleExportTask = (event) => {
event.stopPropagation();
- try {
- const response = await TasksApi.exportTask(id);
- await downloadFile(response, title, 'xlsx');
- } catch (error) {
- toast.error('Ошибка при скачивании файла');
- }
+ onExportClick?.(id, title);
};
const updateTask = async (nextTitle) => {
diff --git a/web/src/pages/TaskPage/TaskPage.jsx b/web/src/pages/TaskPage/TaskPage.jsx
index 5b08735..7e740bc 100644
--- a/web/src/pages/TaskPage/TaskPage.jsx
+++ b/web/src/pages/TaskPage/TaskPage.jsx
@@ -19,8 +19,8 @@ import { SettingModal as SettingStageModal } from '../../components/Stages/Setti
import { PrimaryOutlinedButton } from '../../components/common/Buttons/Buttons';
import { FormsApi } from '../../api/forms';
import { ModalCreateProject } from './ModalCreateProject';
-import { downloadFile } from '../../utils/downloadFile';
import { ExportWithTextButton } from '../../components/common/Buttons/ButtonsActions';
+import { ExportFormModal } from '../../components/common/ExportFormModal';
import TablesList from '../../components/common/TableList/TableList';
import { SHEET_NAME } from '../../constants';
@@ -55,6 +55,7 @@ export default function TaskPage() {
const [query, setQuery] = useState('');
const [modalStageOpen, setModalStageOpen] = useState(false);
const [modalProjectOpen, setModalProjectOpen] = useState(false);
+ const [exportModalOpen, setExportModalOpen] = useState(false);
const [tempProjects, setTempProjects] = useState([]);
const [projects, setProjects] = useState([]);
@@ -120,15 +121,6 @@ export default function TaskPage() {
);
}
- const handleExport = async () => {
- try {
- const response = await TasksApi.exportTask(taskId);
- await downloadFile(response, task.title, 'xlsx');
- } catch (error) {
- toast.error('Ошибка при скачивании файла');
- }
- };
-
return (
<>
-
+ setExportModalOpen(true)} />
setModalStageOpen(true)}
@@ -181,6 +173,12 @@ export default function TaskPage() {
/>
+ setExportModalOpen(false)}
+ taskId={taskId}
+ taskTitle={task?.title ?? 'form'}
+ />
>
);
}
\ No newline at end of file
diff --git a/web/src/pages/TasksPage.jsx b/web/src/pages/TasksPage.jsx
index 62453a5..8f5a579 100644
--- a/web/src/pages/TasksPage.jsx
+++ b/web/src/pages/TasksPage.jsx
@@ -27,11 +27,11 @@ 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 { downloadFile } from '../utils/downloadFile';
import {
ExportExitButton,
ExportWithTextButton,
} from '../components/common/Buttons/ButtonsActions';
+import { ExportFormModal } from '../components/common/ExportFormModal';
import { WhitePlus } from '../components/common/icons/icons';
export default function TasksPage() {
@@ -60,7 +60,12 @@ export default function TasksPage() {
const [exportMode, setExportMode] = useState(false);
const [exportList, setExportList] = useState([]);
- const [isLoadingFile, setIsLoadingFile] = useState(false);
+ const [exportModal, setExportModal] = useState({
+ open: false,
+ taskId: null,
+ taskTitle: null,
+ formIds: null,
+ });
const openModal = () => {
setModalOpen(true);
@@ -185,16 +190,31 @@ export default function TasksPage() {
}
};
- const handleExport = async () => {
- setIsLoadingFile(true);
- try {
- const response = await TasksApi.exportTasks({ task_ids: exportList });
- await downloadFile(response, 'Задачи', 'xlsx');
- setExportMode(false);
- } catch (error) {
- toast.error('Ошибка при скачивании файла');
- }
- setIsLoadingFile(false);
+ const openBulkExportModal = () => {
+ setExportModal({
+ open: true,
+ taskId: null,
+ taskTitle: null,
+ formIds: exportList,
+ });
+ };
+
+ const openSingleExportModal = (taskId, taskTitle) => {
+ setExportModal({
+ open: true,
+ taskId,
+ taskTitle,
+ formIds: null,
+ });
+ };
+
+ const closeExportModal = () => {
+ setExportModal({
+ open: false,
+ taskId: null,
+ taskTitle: null,
+ formIds: null,
+ });
};
// Calculate total pages
@@ -236,13 +256,10 @@ export default function TasksPage() {
<>
- setExportMode(false)}
- disabled={isLoadingFile}
+ onClick={openBulkExportModal}
+ disabled={exportList.length === 0}
/>
+ setExportMode(false)} />
>
)}
@@ -258,12 +275,12 @@ export default function TasksPage() {
{items.map((t) => (
{
handleChangeToExportList(t.id, checked);
}}
+ onExportClick={openSingleExportModal}
/>
))}
>
@@ -444,6 +461,19 @@ export default function TasksPage() {
+ {
+ if (exportModal.formIds?.length) {
+ setExportMode(false);
+ }
+ closeExportModal();
+ }}
+ taskId={exportModal.taskId}
+ taskTitle={exportModal.taskTitle ?? 'form'}
+ formIds={exportModal.formIds}
+ />
>
);
}
diff --git a/web/src/utils/getApiErrorMessage.js b/web/src/utils/getApiErrorMessage.js
new file mode 100644
index 0000000..fd932fe
--- /dev/null
+++ b/web/src/utils/getApiErrorMessage.js
@@ -0,0 +1,26 @@
+export const getApiErrorMessage = async (
+ error,
+ fallback = 'Произошла ошибка',
+) => {
+ const data = error?.response?.data;
+
+ if (!data) {
+ return error?.message || fallback;
+ }
+
+ if (data instanceof Blob) {
+ try {
+ const text = await data.text();
+ const json = JSON.parse(text);
+ return json.message || json.detail || text || fallback;
+ } catch {
+ return fallback;
+ }
+ }
+
+ if (typeof data === 'string') {
+ return data || fallback;
+ }
+
+ return data.message || data.detail || fallback;
+};