[AURORA-1057] - Ошибка экспорта задач

This commit is contained in:
Veronika Tsygankova 2026-06-04 17:16:05 +03:00
parent e47200733e
commit 9fc054acd4
6 changed files with 242 additions and 41 deletions

View File

@ -33,13 +33,15 @@ export const TasksApi = {
createProjectTable(taskId, payload) { createProjectTable(taskId, payload) {
return api.post(`/tasks/${taskId}/projects`, payload).then((r) => r.data); return api.post(`/tasks/${taskId}/projects`, payload).then((r) => r.data);
}, },
exportTask(taskId) { exportTask(taskId, params = {}) {
return api.get(`export/task/${taskId}`, { return api.get(`export/form/${taskId}`, {
params,
responseType: "blob", responseType: "blob",
}); });
}, },
exportTasks(payload) { exportTasks(payload, params = {}) {
return api.post(`export/bulk`, payload, { return api.post(`export/bulk`, payload, {
params,
responseType: "blob", responseType: "blob",
}); });
}, },

View File

@ -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 (
<Modal
title="Экспорт формы"
open={open}
onClose={handleClose}
customSize={31.25}
actions={
<>
<Button
variant="grey"
onClick={handleClose}
disabled={loading}
style={{ height: '2.5rem' }}
>
Отменить
</Button>
<Button
variant="contained"
onClick={handleExport}
disabled={loading}
style={{ height: '2.5rem' }}
>
{loading ? 'Экспорт...' : 'Экспортировать'}
</Button>
</>
}
>
<Box sx={{ display: 'flex', flexDirection: 'column', gap: '0.5rem' }}>
<FormLabel
sx={{
fontWeight: 400,
fontSize: '0.75rem',
color: '#4a5565',
}}
>
direction
</FormLabel>
<FormControl fullWidth disabled={loading}>
<Select
value={direction}
displayEmpty
onChange={(e) => setDirection(e.target.value)}
renderValue={(selected) =>
selected ? (
DIRECTION_OPTIONS.find((o) => o.value === selected)?.label
) : (
<Box component="span" sx={{ color: 'rgba(0,0,0,0.4)' }}>
Выберите direction
</Box>
)
}
MenuProps={{
style: { zIndex: 1301 },
PaperProps: { sx: { zIndex: 1301 } },
}}
sx={{
height: '2rem',
fontSize: '0.875rem',
'& .MuiOutlinedInput-notchedOutline': {
borderRadius: '0.5rem',
borderColor: 'rgba(0, 0, 0, 0.1)',
},
}}
>
{DIRECTION_OPTIONS.map((option) => (
<MenuItem key={option.value} value={option.value}>
{option.label}
</MenuItem>
))}
</Select>
</FormControl>
</Box>
</Modal>
);
};

View File

@ -26,7 +26,6 @@ import { formatDate } from '../../utils/formatDate';
import { IconWithContent } from './IconWithContent'; import { IconWithContent } from './IconWithContent';
import { Chip } from '../styles/StyledChip'; import { Chip } from '../styles/StyledChip';
import { ExportButton } from '../common/Buttons/ButtonsActions'; import { ExportButton } from '../common/Buttons/ButtonsActions';
import { downloadFile } from '../../utils/downloadFile';
const TaskCard = styled.div` const TaskCard = styled.div`
width: 26.8rem; width: 26.8rem;
@ -93,6 +92,7 @@ export const TaskCardComponent = ({
exportMode = false, exportMode = false,
isLoadingFile = false, isLoadingFile = false,
onAddForExport, onAddForExport,
onExportClick,
}) => { }) => {
const navigate = useNavigate(); const navigate = useNavigate();
const { id, title, name, start_date, end_date, tables_count, org_unit, form_type_code } = taskForm; 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(); event.stopPropagation();
try { onExportClick?.(id, title);
const response = await TasksApi.exportTask(id);
await downloadFile(response, title, 'xlsx');
} catch (error) {
toast.error('Ошибка при скачивании файла');
}
}; };
const updateTask = async (nextTitle) => { const updateTask = async (nextTitle) => {

View File

@ -19,8 +19,8 @@ import { SettingModal as SettingStageModal } from '../../components/Stages/Setti
import { PrimaryOutlinedButton } from '../../components/common/Buttons/Buttons'; import { PrimaryOutlinedButton } from '../../components/common/Buttons/Buttons';
import { FormsApi } from '../../api/forms'; import { FormsApi } from '../../api/forms';
import { ModalCreateProject } from './ModalCreateProject'; import { ModalCreateProject } from './ModalCreateProject';
import { downloadFile } from '../../utils/downloadFile';
import { ExportWithTextButton } from '../../components/common/Buttons/ButtonsActions'; import { ExportWithTextButton } from '../../components/common/Buttons/ButtonsActions';
import { ExportFormModal } from '../../components/common/ExportFormModal';
import TablesList from '../../components/common/TableList/TableList'; import TablesList from '../../components/common/TableList/TableList';
import { SHEET_NAME } from '../../constants'; import { SHEET_NAME } from '../../constants';
@ -55,6 +55,7 @@ export default function TaskPage() {
const [query, setQuery] = useState(''); const [query, setQuery] = useState('');
const [modalStageOpen, setModalStageOpen] = useState(false); const [modalStageOpen, setModalStageOpen] = useState(false);
const [modalProjectOpen, setModalProjectOpen] = useState(false); const [modalProjectOpen, setModalProjectOpen] = useState(false);
const [exportModalOpen, setExportModalOpen] = useState(false);
const [tempProjects, setTempProjects] = useState([]); const [tempProjects, setTempProjects] = useState([]);
const [projects, setProjects] = 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 ( return (
<> <>
<Box <Box
@ -156,7 +148,7 @@ export default function TaskPage() {
onChange={setQuery} onChange={setQuery}
/> />
<Stack sx={{ flexDirection: 'row', spacing: '.5rem', justifyContent: 'end' }}> <Stack sx={{ flexDirection: 'row', spacing: '.5rem', justifyContent: 'end' }}>
<ExportWithTextButton onClick={handleExport} /> <ExportWithTextButton onClick={() => setExportModalOpen(true)} />
<PrimaryOutlinedButton <PrimaryOutlinedButton
variant="outlined" variant="outlined"
onClick={() => setModalStageOpen(true)} onClick={() => setModalStageOpen(true)}
@ -181,6 +173,12 @@ export default function TaskPage() {
/> />
</Box> </Box>
</Box> </Box>
<ExportFormModal
open={exportModalOpen}
onClose={() => setExportModalOpen(false)}
taskId={taskId}
taskTitle={task?.title ?? 'form'}
/>
</> </>
); );
} }

View File

@ -27,11 +27,11 @@ import { ROLES_NAME_ID } from '../constants';
import { useAuth } from '../app/context/AuthProvider'; import { useAuth } from '../app/context/AuthProvider';
import { SspApi } from '../api/ssp'; import { SspApi } from '../api/ssp';
import { PrimaryButton } from '../components/common/Buttons/Buttons'; import { PrimaryButton } from '../components/common/Buttons/Buttons';
import { downloadFile } from '../utils/downloadFile';
import { import {
ExportExitButton, ExportExitButton,
ExportWithTextButton, ExportWithTextButton,
} from '../components/common/Buttons/ButtonsActions'; } from '../components/common/Buttons/ButtonsActions';
import { ExportFormModal } from '../components/common/ExportFormModal';
import { WhitePlus } from '../components/common/icons/icons'; import { WhitePlus } from '../components/common/icons/icons';
export default function TasksPage() { export default function TasksPage() {
@ -60,7 +60,12 @@ export default function TasksPage() {
const [exportMode, setExportMode] = useState(false); const [exportMode, setExportMode] = useState(false);
const [exportList, setExportList] = useState([]); const [exportList, setExportList] = useState([]);
const [isLoadingFile, setIsLoadingFile] = useState(false); const [exportModal, setExportModal] = useState({
open: false,
taskId: null,
taskTitle: null,
formIds: null,
});
const openModal = () => { const openModal = () => {
setModalOpen(true); setModalOpen(true);
@ -185,16 +190,31 @@ export default function TasksPage() {
} }
}; };
const handleExport = async () => { const openBulkExportModal = () => {
setIsLoadingFile(true); setExportModal({
try { open: true,
const response = await TasksApi.exportTasks({ task_ids: exportList }); taskId: null,
await downloadFile(response, 'Задачи', 'xlsx'); taskTitle: null,
setExportMode(false); formIds: exportList,
} catch (error) { });
toast.error('Ошибка при скачивании файла'); };
}
setIsLoadingFile(false); 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 // Calculate total pages
@ -236,13 +256,10 @@ export default function TasksPage() {
<> <>
<ExportWithTextButton <ExportWithTextButton
text={'Экспортировать'} text={'Экспортировать'}
onClick={handleExport} onClick={openBulkExportModal}
disabled={isLoadingFile} disabled={exportList.length === 0}
/>
<ExportExitButton
onClick={() => setExportMode(false)}
disabled={isLoadingFile}
/> />
<ExportExitButton onClick={() => setExportMode(false)} />
</> </>
)} )}
</Stack> </Stack>
@ -258,12 +275,12 @@ export default function TasksPage() {
{items.map((t) => ( {items.map((t) => (
<TaskCardComponent <TaskCardComponent
exportMode={exportMode} exportMode={exportMode}
isLoadingFile={exportMode ? isLoadingFile : false}
taskForm={t} taskForm={t}
key={t.id + '-task-id'} key={t.id + '-task-id'}
onAddForExport={(checked) => { onAddForExport={(checked) => {
handleChangeToExportList(t.id, checked); handleChangeToExportList(t.id, checked);
}} }}
onExportClick={openSingleExportModal}
/> />
))} ))}
</> </>
@ -444,6 +461,19 @@ export default function TasksPage() {
</FormControl> </FormControl>
</div> </div>
</Modal> </Modal>
<ExportFormModal
open={exportModal.open}
onClose={closeExportModal}
onSuccess={() => {
if (exportModal.formIds?.length) {
setExportMode(false);
}
closeExportModal();
}}
taskId={exportModal.taskId}
taskTitle={exportModal.taskTitle ?? 'form'}
formIds={exportModal.formIds}
/>
</> </>
); );
} }

View File

@ -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;
};