From cee2aaf71341948aacde606946ca3c506ddb3dc7 Mon Sep 17 00:00:00 2001 From: "Mahmoud M. Abdalla" Date: Mon, 28 Jul 2025 01:16:12 +0300 Subject: [PATCH] Version 1.1.1 - Enhanced Course Content Management Interface - Created dedicated CourseContent page for content management - Separated content management from course editing - Added prominent 'Manage Content' button on course detail pages - Enhanced UI with better visual hierarchy and styling - Improved content management workflow with CRUD operations - Added content type icons and status indicators - Enhanced modal interfaces for content creation/editing - Improved navigation and role-based access control --- frontend/src/App.js | 6 + frontend/src/pages/CourseContent.js | 745 ++++++++++++++++++++++++++++ frontend/src/pages/CourseDetail.js | 22 +- frontend/src/pages/CourseEdit.js | 12 + version.txt | 41 +- 5 files changed, 820 insertions(+), 6 deletions(-) create mode 100644 frontend/src/pages/CourseContent.js diff --git a/frontend/src/App.js b/frontend/src/App.js index b8376f8..cbb81a8 100644 --- a/frontend/src/App.js +++ b/frontend/src/App.js @@ -12,6 +12,7 @@ import Profile from './pages/Profile'; import Layout from './components/Layout'; import LoadingSpinner from './components/LoadingSpinner'; import CourseEdit from './pages/CourseEdit'; +import CourseContent from './pages/CourseContent'; import Home from './pages/Home'; const PrivateRoute = ({ children, allowedRoles = [] }) => { @@ -58,6 +59,11 @@ const AppRoutes = () => { } /> + + + + } /> diff --git a/frontend/src/pages/CourseContent.js b/frontend/src/pages/CourseContent.js new file mode 100644 index 0000000..eaf0720 --- /dev/null +++ b/frontend/src/pages/CourseContent.js @@ -0,0 +1,745 @@ +import React, { useState } from 'react'; +import { useNavigate, useParams } from 'react-router-dom'; +import { useQuery, useMutation, useQueryClient } from 'react-query'; +import { useAuth } from '../contexts/AuthContext'; +import { coursesAPI, courseContentAPI } from '../services/api'; +import { + PlusIcon, + DocumentIcon, + PhotoIcon, + VideoCameraIcon, + DocumentTextIcon, + QuestionMarkCircleIcon, + AcademicCapIcon as CertificateIcon, + TrashIcon, + PencilIcon, + EyeIcon, + EyeSlashIcon, + ArrowLeftIcon, + AcademicCapIcon, +} from '@heroicons/react/24/outline'; +import LoadingSpinner from '../components/LoadingSpinner'; +import toast from 'react-hot-toast'; + +const CourseContent = () => { + const { id } = useParams(); + const navigate = useNavigate(); + const { isTrainer, isSuperAdmin } = useAuth(); + const queryClient = useQueryClient(); + + // Content management state + const [showAddContentModal, setShowAddContentModal] = useState(false); + const [showEditContentModal, setShowEditContentModal] = useState(false); + const [editingContent, setEditingContent] = useState(null); + const [contentForm, setContentForm] = useState({ + title: '', + description: '', + type: 'document', + order: 0, + points: 0, + isRequired: true, + isPublished: true, + articleContent: '', + }); + const [uploadingFile, setUploadingFile] = useState(false); + const [selectedFile, setSelectedFile] = useState(null); + + // Get course details + const { data: courseData, isLoading: courseLoading } = useQuery( + ['course', id], + () => coursesAPI.getById(id), + { enabled: !!id } + ); + + // Get course content + const { data: contentData, isLoading: contentLoading } = useQuery( + ['course-content', id], + () => courseContentAPI.getAll(id), + { enabled: !!id } + ); + + // Content management mutations + const createContentMutation = useMutation( + (data) => courseContentAPI.create(id, data), + { + onSuccess: () => { + queryClient.invalidateQueries(['course-content', id]); + toast.success('Content added successfully!'); + setShowAddContentModal(false); + resetContentForm(); + }, + onError: (error) => { + toast.error(error.response?.data?.error || 'Failed to add content'); + }, + } + ); + + const updateContentMutation = useMutation( + (data) => courseContentAPI.update(id, editingContent.id, data), + { + onSuccess: () => { + queryClient.invalidateQueries(['course-content', id]); + toast.success('Content updated successfully!'); + setShowEditContentModal(false); + setEditingContent(null); + resetContentForm(); + }, + onError: (error) => { + toast.error(error.response?.data?.error || 'Failed to update content'); + }, + } + ); + + const deleteContentMutation = useMutation( + (contentId) => courseContentAPI.delete(id, contentId), + { + onSuccess: () => { + queryClient.invalidateQueries(['course-content', id]); + toast.success('Content deleted successfully!'); + }, + onError: (error) => { + toast.error(error.response?.data?.error || 'Failed to delete content'); + }, + } + ); + + const uploadFileMutation = useMutation( + ({ contentType, file, contentId }) => courseContentAPI.uploadFile(id, contentType, file, contentId), + { + onSuccess: (data) => { + toast.success('File uploaded successfully!'); + setSelectedFile(null); + setUploadingFile(false); + }, + onError: (error) => { + toast.error(error.response?.data?.error || 'Failed to upload file'); + setUploadingFile(false); + }, + } + ); + + // Content management handlers + const handleContentFormChange = (e) => { + const { name, value, type, checked } = e.target; + setContentForm(prev => ({ + ...prev, + [name]: type === 'checkbox' ? checked : value + })); + }; + + const resetContentForm = () => { + setContentForm({ + title: '', + description: '', + type: 'document', + order: 0, + points: 0, + isRequired: true, + isPublished: true, + articleContent: '', + }); + }; + + const handleAddContent = async (e) => { + e.preventDefault(); + createContentMutation.mutate(contentForm); + }; + + const handleEditContent = async (e) => { + e.preventDefault(); + updateContentMutation.mutate(contentForm); + }; + + const handleDeleteContent = (contentId) => { + if (window.confirm('Are you sure you want to delete this content?')) { + deleteContentMutation.mutate(contentId); + } + }; + + const handleEditContentClick = (content) => { + setEditingContent(content); + setContentForm({ + title: content.title || '', + description: content.description || '', + type: content.type || 'document', + order: content.order || 0, + points: content.points || 0, + isRequired: content.isRequired !== undefined ? content.isRequired : true, + isPublished: content.isPublished !== undefined ? content.isPublished : true, + articleContent: content.articleContent || '', + }); + setShowEditContentModal(true); + }; + + const handleFileUpload = async () => { + if (!selectedFile) return; + setUploadingFile(true); + uploadFileMutation.mutate({ + contentType: contentForm.type, + file: selectedFile, + contentId: editingContent?.id || null + }); + }; + + const handleFileChange = (e) => { + const file = e.target.files[0]; + setSelectedFile(file); + }; + + const getContentTypeIcon = (type) => { + const icons = { + document: DocumentIcon, + image: PhotoIcon, + video: VideoCameraIcon, + article: DocumentTextIcon, + quiz: QuestionMarkCircleIcon, + certificate: CertificateIcon, + }; + return icons[type] || DocumentIcon; + }; + + const getContentTypeLabel = (type) => { + const labels = { + document: 'Document', + image: 'Image', + video: 'Video', + article: 'Article', + quiz: 'Quiz', + certificate: 'Certificate', + }; + return labels[type] || 'Document'; + }; + + if (!isTrainer && !isSuperAdmin) { + return ( +
+

Access Denied

+

You don't have permission to manage course content.

+
+ ); + } + + if (courseLoading || contentLoading) { + return ; + } + + return ( +
+ {/* Header */} +
+
+
+ +
+

Course Content

+

+ {courseData?.course?.title} - Manage course materials +

+
+
+ +
+
+ + {/* Content List */} +
+
+

Course Materials

+
+ {contentData?.contents?.length || 0} items +
+
+ + {contentData?.contents?.length > 0 ? ( +
+ {contentData.contents.map((content) => { + const IconComponent = getContentTypeIcon(content.type); + return ( +
+
+
+ +
+

{content.title}

+

{getContentTypeLabel(content.type)}

+ {content.description && ( +

{content.description}

+ )} + {content.fileUrl && ( +

File attached

+ )} +
+
+
+
+ + Order: {content.order} + + + Points: {content.points} + + {content.isRequired && ( + + Required + + )} + {content.isPublished ? ( + + ) : ( + + )} +
+
+ + +
+
+
+
+ ); + })} +
+ ) : ( +
+ +

No content yet

+

Start building your course by adding content

+
+ )} +
+ + {/* Add Content Modal */} + {showAddContentModal && ( +
+
+
+

Add Course Content

+ +
+ +
+
+
+ + +
+ +
+ +