diff --git a/web/src/Components/Pages/CourseDetailPage/Components/ViewTaskModalForm/ViewTaskModal.jsx b/web/src/Components/Pages/CourseDetailPage/Components/ViewTaskModalForm/ViewTaskModal.jsx new file mode 100644 index 0000000..db0c940 --- /dev/null +++ b/web/src/Components/Pages/CourseDetailPage/Components/ViewTaskModalForm/ViewTaskModal.jsx @@ -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 ( + } + title={null} + centered + destroyOnHidden + > + + + + {selectedTaskToView?.title} + + } + style={{backgroundColor: "#1890ff"}} + > + {selectedTaskToView?.creator?.first_name?.[0] || "У"} + + + Создал: {selectedTaskToView?.creator?.first_name} {selectedTaskToView?.creator?.last_name} + {selectedTaskToView?.creator?.patronymic && ` ${selectedTaskToView?.creator.patronymic}`} + + + + + + {selectedTaskToView?.description && ( + <> + + Описание + + + {selectedTaskToView?.description} + + + + )} + + {selectedTaskToView?.text ? ( +
+ ) : ( + + Текстовый материал отсутствует + + )} + + + Прикрепленные файлы + {isCurrentTaskFilesLoading ? ( + + ) : currentTaskFiles.length > 0 ? ( + currentTaskFiles.map((file) => ( + + {file.filename || "Не указан"} +
+ +
+ +
+ )) + ) : ( +

Файлы отсутствуют

+ )} + +
+ +
+ + ); +}; + +export default ViewTaskModal; \ No newline at end of file diff --git a/web/src/Components/Pages/CourseDetailPage/Components/ViewTaskModalForm/useTaskLessonModal.js b/web/src/Components/Pages/CourseDetailPage/Components/ViewTaskModalForm/useTaskLessonModal.js new file mode 100644 index 0000000..354169a --- /dev/null +++ b/web/src/Components/Pages/CourseDetailPage/Components/ViewTaskModalForm/useTaskLessonModal.js @@ -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; \ No newline at end of file diff --git a/web/src/Components/Pages/CourseDetailPage/CourseDetailPage.jsx b/web/src/Components/Pages/CourseDetailPage/CourseDetailPage.jsx index 24ce198..5bb7a7d 100644 --- a/web/src/Components/Pages/CourseDetailPage/CourseDetailPage.jsx +++ b/web/src/Components/Pages/CourseDetailPage/CourseDetailPage.jsx @@ -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 = () => { + {[CONFIG.ROOT_ROLE_NAME, ROLES.TEACHER].includes(userData.role.title) && (