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) && (