сделал просмотр задачи

This commit is contained in:
Андрей Дувакин 2025-11-29 10:52:51 +05:00
parent 23ba0edf17
commit 1fb12bc7e4
3 changed files with 239 additions and 0 deletions

View File

@ -0,0 +1,114 @@
import {Avatar, Button, Col, Divider, Modal, Popconfirm, Row, Space, Spin, Typography} from "antd";
import {CloseOutlined, UserOutlined} from "@ant-design/icons";
import useViewTaskModal from "./useTaskLessonModal.js";
const {Title, Text, Paragraph} = Typography;
const ViewTaskModal = () => {
const {
selectedTaskToView,
modalIsOpen,
handleClose,
currentTaskFiles,
isCurrentTaskFilesLoading,
isCurrentTaskFilesError,
downloadFile,
downloadingFiles
} = useViewTaskModal();
return (
<Modal
open={modalIsOpen}
onCancel={handleClose}
footer={null}
width={1000}
closeIcon={<CloseOutlined style={{fontSize: 20}}/>}
title={null}
centered
destroyOnHidden
>
<Space align="start" style={{marginBottom: 16}}>
<Col>
<Title level={2} style={{margin: 0, flex: 1}}>
{selectedTaskToView?.title}
</Title>
<Avatar
size="small"
icon={<UserOutlined/>}
style={{backgroundColor: "#1890ff"}}
>
{selectedTaskToView?.creator?.first_name?.[0] || "У"}
</Avatar>
<Text type="secondary">
Создал: <strong>{selectedTaskToView?.creator?.first_name} {selectedTaskToView?.creator?.last_name}</strong>
{selectedTaskToView?.creator?.patronymic && ` ${selectedTaskToView?.creator.patronymic}`}
</Text>
</Col>
</Space>
{selectedTaskToView?.description && (
<>
<Title level={4} style={{margin: "16px 0 8px"}}>
Описание
</Title>
<Paragraph type="secondary" style={{fontSize: 16, marginBottom: 24}}>
{selectedTaskToView?.description}
</Paragraph>
<Divider style={{margin: "24px 0"}}/>
</>
)}
{selectedTaskToView?.text ? (
<div
className="Task-content"
dangerouslySetInnerHTML={{__html: selectedTaskToView?.text}}
style={{
fontSize: "16px",
lineHeight: "1.7",
color: "#333",
}}
/>
) : (
<Paragraph italic type="secondary">
Текстовый материал отсутствует
</Paragraph>
)}
<Divider/>
<Title level={3}>Прикрепленные файлы</Title>
{isCurrentTaskFilesLoading ? (
<Spin/>
) : currentTaskFiles.length > 0 ? (
currentTaskFiles.map((file) => (
<Row key={file.id} align="middle" justify="space-between">
<span>{file.filename || "Не указан"}</span>
<div>
<Button
onClick={() => downloadFile(file.id, file.filename)}
loading={downloadingFiles[file.id] || false}
disabled={downloadingFiles[file.id] || false}
type={"dashed"}
style={{marginRight: 8}}
>
{downloadingFiles[file.id] ? "Загрузка..." : "Скачать"}
</Button>
</div>
<Divider/>
</Row>
))
) : (
<p>Файлы отсутствуют</p>
)}
<div style={{textAlign: "right"}}>
<Button onClick={handleClose}>
Закрыть
</Button>
</div>
</Modal>
);
};
export default ViewTaskModal;

View File

@ -0,0 +1,121 @@
import {useDispatch, useSelector} from "react-redux";
import {setSelectedTaskToView} from "../../../../../Redux/Slices/tasksSlice.js";
import {notification} from "antd";
import CONFIG from "../../../../../Core/сonfig.js";
import {useState} from "react";
import {useGetTaskFilesListQuery} from "../../../../../Api/tasksApi.js";
const useViewTaskModal = () => {
const dispatch = useDispatch();
const {
selectedTaskToView
} = useSelector((state) => state.tasks);
const modalIsOpen = selectedTaskToView !== null;
const handleClose = () => {
dispatch(setSelectedTaskToView(null));
};
const {
data: currentTaskFiles = [],
isLoading: isCurrentTaskFilesLoading,
isError: isCurrentTaskFilesError
} = useGetTaskFilesListQuery(selectedTaskToView?.id, {
skip: !selectedTaskToView?.id
});
const [downloadingFiles, setDownloadingFiles] = useState({});
const downloadFile = async (fileId, fileName) => {
try {
setDownloadingFiles((prev) => ({...prev, [fileId]: true}));
const token = localStorage.getItem('access_token');
if (!token) {
notification.error({
title: "Ошибка",
description: "Токен не найден",
placement: "topRight",
});
return;
}
const response = await fetch(`${CONFIG.BASE_URL}/tasks/file/${fileId}/`, {
method: 'GET',
headers: {
'Authorization': `Bearer ${token}`,
},
});
if (!response.ok) {
const errorText = await response.text();
notification.error({
title: "Ошибка",
description: errorText || "Не удалось скачать файл",
placement: "topRight",
});
return;
}
const contentType = response.headers.get('content-type');
if (!contentType || contentType.includes('text/html')) {
const errorText = await response.text();
notification.error({
title: "Ошибка",
description: errorText || "Не удалось скачать файл",
placement: "topRight",
});
return;
}
let safeFileName = fileName || "file";
if (!safeFileName.match(/\.[a-zA-Z0-9]+$/)) {
if (contentType.includes('application/pdf')) {
safeFileName += '.pdf';
} else if (contentType.includes('image/jpeg')) {
safeFileName += '.jpg';
} else if (contentType.includes('image/png')) {
safeFileName += '.png';
} else {
safeFileName += '.bin';
}
}
const blob = await response.blob();
const downloadUrl = window.URL.createObjectURL(blob);
const link = document.createElement("a");
link.href = downloadUrl;
link.setAttribute("download", safeFileName);
document.body.appendChild(link);
link.click();
link.remove();
window.URL.revokeObjectURL(downloadUrl);
} catch (error) {
notification.error({
title: "Ошибка",
description: error.message || "Не удалось скачать файл",
placement: "topRight",
});
} finally {
setDownloadingFiles((prev) => ({...prev, [fileId]: false}));
}
};
return {
selectedTaskToView,
modalIsOpen,
handleClose,
currentTaskFiles,
isCurrentTaskFilesLoading,
isCurrentTaskFilesError,
downloadFile,
downloadingFiles
}
};
export default useViewTaskModal;

View File

@ -31,6 +31,7 @@ import ViewLessonModal from "./Components/ViewLessonModalForm/ViewLessonModal.js
import UpdateLessonModalForm from "./Components/UpdateLessonModalForm/UpdateLessonModalForm.jsx";
import CreateTaskModalForm from "./Components/CreateTaskModalForm/CreateTaskModalForm.jsx";
import UpdateTaskModalForm from "./Components/UpdateTaskModalForm/UpdateTaskModalForm.jsx";
import ViewTaskModal from "./Components/ViewTaskModalForm/ViewTaskModal.jsx";
const {Title, Text} = Typography;
@ -218,6 +219,9 @@ const CourseDetailPage = () => {
<CreateTaskModalForm
courseId={courseId}
/>
<ViewTaskModal
courseId={courseId}
/>
<UpdateTaskModalForm/>
{[CONFIG.ROOT_ROLE_NAME, ROLES.TEACHER].includes(userData.role.title) && (
<FloatButton.Group