diff --git a/src/components/ActivityLog/CommentSection.jsx b/src/components/ActivityLog/CommentSection.jsx new file mode 100644 index 0000000000..2a4ad03b75 --- /dev/null +++ b/src/components/ActivityLog/CommentSection.jsx @@ -0,0 +1,78 @@ +import PropTypes from 'prop-types'; +import { useState } from 'react'; +import { MessageSquare } from 'lucide-react'; +import styles from './styles/CommentSection.module.css'; +import { IconByRole, Tag } from './icons'; +import { useSelector } from 'react-redux'; + +const CommentSection = ({ logId, comments, userRole, handleCommentSubmit }) => { + const [newComment, setNewComment] = useState(''); + const darkMode = useSelector(state => state.theme.darkMode); + + const onSubmit = () => { + if (newComment.trim()) { + handleCommentSubmit(logId, newComment); + setNewComment(''); + } + }; + + return ( +
+
+

Interaction / Comments

+ +
+ {comments.length === 0 && ( +

No comments yet. Start the conversation!

+ )} + {comments.map(comment => ( +
+
+ + {comment.author} + {comment.role} +
+

{comment.text}

+
+ ))} +
+ +
+ + +
+
+
+ ); +}; + +CommentSection.propTypes = { + logId: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired, + comments: PropTypes.arrayOf( + PropTypes.shape({ + id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired, + role: PropTypes.string.isRequired, + author: PropTypes.string.isRequired, + text: PropTypes.string.isRequired, + }), + ).isRequired, + userRole: PropTypes.string.isRequired, + handleCommentSubmit: PropTypes.func.isRequired, +}; + +export default CommentSection; diff --git a/src/components/ActivityLog/CreateLogForm.jsx b/src/components/ActivityLog/CreateLogForm.jsx new file mode 100644 index 0000000000..576bc987a5 --- /dev/null +++ b/src/components/ActivityLog/CreateLogForm.jsx @@ -0,0 +1,153 @@ +import PropTypes from 'prop-types'; +import { useState } from 'react'; +import { CheckCircle, Send } from 'lucide-react'; +import styles from './styles/CreateLogForm.module.css'; +import { MOCK_USERS } from './MockLogs'; +import { useSelector } from 'react-redux'; + +const CreateLogForm = ({ userRole, currentUserName, setViewMode, handleAddLog }) => { + const isSupport = userRole === 'Support'; + const roleName = isSupport ? 'Support Member' : 'Student'; + + const darkMode = useSelector(state => state.theme.darkMode); + const [formData, setFormData] = useState({ + logContent: '', + course: 'Course A', + notesToTeacher: '', + }); + const [isSubmitted, setIsSubmitted] = useState(false); + const [error, setError] = useState(null); + + const handleChange = e => { + const { id, value } = e.target; + setFormData(prev => ({ ...prev, [id]: value })); + setError(null); + }; + + const handleSubmit = e => { + e.preventDefault(); + + if (!formData.logContent.trim()) { + setError('Log Content is required to submit a log entry.'); + return; + } + + const newLog = { + id: Date.now(), + submittedBy: currentUserName, + role: userRole, + timestamp: new Date().toISOString(), + logContent: formData.logContent, + course: formData.course, + notesToTeacher: formData.notesToTeacher || 'N/A', + assistedBy: isSupport ? { name: currentUserName, role: 'Support' } : null, + studentName: isSupport ? 'Young Learner Placeholder' : currentUserName, + studentRole: isSupport ? 'Unknown Grade' : MOCK_USERS.student.role, + teacher: isSupport ? 'Educator to be assigned' : MOCK_USERS.educator.name, + teacherFeedback: null, + comments: [], + }; + + handleAddLog(newLog); + + setIsSubmitted(true); + setFormData({ logContent: '', course: 'Course A', notesToTeacher: '' }); + + setTimeout(() => { + setIsSubmitted(false); + if (setViewMode) setViewMode('viewer'); + }, 2000); + }; + + return ( +
+
+

Create Daily Log

+

+ Add your log details below. Currently acting as a{' '} + {roleName}. +

+ + {isSubmitted && ( +
+ + Success! Log entry submitted successfully. + Switching back to Viewer... +
+ )} + + {error && ( +
+ Error: {error} +
+ )} + +
+
+ + +
+ +
+
+ + +
+ +
+ + +
+ + +
+
+
+
+ ); +}; + +CreateLogForm.propTypes = { + userRole: PropTypes.string.isRequired, + currentUserName: PropTypes.string.isRequired, + setViewMode: PropTypes.func, + handleAddLog: PropTypes.func.isRequired, +}; + +export default CreateLogForm; diff --git a/src/components/ActivityLog/DailyLogPage.jsx b/src/components/ActivityLog/DailyLogPage.jsx new file mode 100644 index 0000000000..65a24f8af4 --- /dev/null +++ b/src/components/ActivityLog/DailyLogPage.jsx @@ -0,0 +1,204 @@ +import React, { useState, useMemo, useEffect } from 'react'; +import { MOCK_USERS, INITIAL_MOCK_LOGS } from './MockLogs'; +import { useSelector } from 'react-redux'; +import styles from './styles/DailyLogPage.module.css'; +import CreateLogForm from './CreateLogForm'; +import { User, BookOpen, PlusSquare } from 'lucide-react'; +import LogEntryView from './LogEntryView'; +const ActivityLogs = () => { + const darkMode = useSelector(state => state.theme.darkMode); + const [currentUserRole, setCurrentUserRole] = useState('Educator'); + const [logs, setLogs] = useState(INITIAL_MOCK_LOGS); + const [logToDisplayId, setLogToDisplayId] = useState(1); + const [viewMode, setViewMode] = useState('viewer'); + + const currentUserName = MOCK_USERS[currentUserRole.toLowerCase()]?.name || 'Unknown User'; + const isEducator = currentUserRole === 'Educator'; + const isCreator = currentUserRole === 'Student' || currentUserRole === 'Support'; + + const logToDisplay = useMemo(() => logs.find(log => log.id === logToDisplayId), [ + logs, + logToDisplayId, + ]); + + const handleAddLog = newLog => { + setLogs(prevLogs => [newLog, ...prevLogs]); + setLogToDisplayId(newLog.id); + }; + + const toggleAssistedStatus = logId => { + const supportUser = MOCK_USERS.support; + setLogs(prevLogs => + prevLogs.map(log => { + if (log.id === logId) { + const isCurrentlyAssisted = !!log.assistedBy; + return { + ...log, + assistedBy: isCurrentlyAssisted + ? null + : { name: supportUser.name, role: supportUser.role }, + }; + } + return log; + }), + ); + }; + + const handleFeedbackSubmit = (logId, feedbackContent) => { + if (!isEducator) return; + setLogs(prevLogs => + prevLogs.map(log => { + if (log.id === logId) { + return { + ...log, + teacherFeedback: { + teacherName: currentUserName, + feedback: feedbackContent, + timestamp: new Date().toISOString(), + }, + }; + } + return log; + }), + ); + }; + + const handleCommentSubmit = (logId, commentText) => { + if (!commentText.trim()) return; + + const newComment = { + id: Date.now(), + author: currentUserName, + role: currentUserRole, + text: commentText, + timestamp: new Date().toISOString(), + }; + + setLogs(prevLogs => + prevLogs.map(log => { + if (log.id === logId) { + return { ...log, comments: [...log.comments, newComment] }; + } + return log; + }), + ); + }; + + const roleOptions = Object.keys(MOCK_USERS); + + useEffect(() => { + if (isEducator) setViewMode('viewer'); + if (!logToDisplay && logs.length > 0) setLogToDisplayId(logs[0].id); + }, [currentUserRole, logs, logToDisplay]); + + let PageContent; + if (viewMode === 'viewer' && logToDisplay) { + PageContent = ( + + ); + } else if (viewMode === 'creator' && isCreator) { + PageContent = ( + + ); + } else { + PageContent = ( +
+ No logs are available to display or review. Try submitting one as a Student/Support user + first! +
+ ); + } + + return ( +
+
+
+

Daily Log System

+
+ {isCreator && ( + + )} + + {logs.length > 0 && viewMode === 'viewer' && ( +
+ + View Log: + +
+ )} + +
+ + User Role: + +
+
+
+ +
{PageContent}
+ +
+ Current User: {currentUserName} ({currentUserRole}). +
+ Test: Switch to Student or Support, + create a new log, and then switch to Educator to confirm the new log + appears in the Log Switcher. +
+
+
+ ); +}; + +export default ActivityLogs; diff --git a/src/components/ActivityLog/EducatorFeedbackForm.jsx b/src/components/ActivityLog/EducatorFeedbackForm.jsx new file mode 100644 index 0000000000..d233959742 --- /dev/null +++ b/src/components/ActivityLog/EducatorFeedbackForm.jsx @@ -0,0 +1,73 @@ +import PropTypes from 'prop-types'; +import { useState } from 'react'; +import { CheckCircle, Send } from 'lucide-react'; +import styles from './styles/EducatorFeedback.module.css'; +import { useSelector } from 'react-redux'; + +const EducatorFeedbackForm = ({ + logId, + currentTeacherName, + logHasFeedback, + handleFeedbackSubmit, +}) => { + const [feedback, setFeedback] = useState(''); + const [isSubmitted, setIsSubmitted] = useState(false); + + const labelText = logHasFeedback ? 'Update Teacher Feedback' : 'Submit Teacher Feedback'; + const darkMode = useSelector(state => state.theme.darkMode); + + const handleSubmit = e => { + e.preventDefault(); + if (feedback.trim()) { + handleFeedbackSubmit(logId, feedback); + setFeedback(''); + setIsSubmitted(true); + setTimeout(() => setIsSubmitted(false), 2000); + } + }; + + return ( +
+
+

{labelText}

+ + {isSubmitted && ( +
+ + Success! Feedback submitted for the + student. +
+ )} + +
+ + +
+
+
+ ); +}; + +EducatorFeedbackForm.propTypes = { + logId: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired, + currentTeacherName: PropTypes.string.isRequired, + logHasFeedback: PropTypes.bool.isRequired, + handleFeedbackSubmit: PropTypes.func.isRequired, +}; + +export default EducatorFeedbackForm; diff --git a/src/components/ActivityLog/LogEntryView.jsx b/src/components/ActivityLog/LogEntryView.jsx new file mode 100644 index 0000000000..b914db50ba --- /dev/null +++ b/src/components/ActivityLog/LogEntryView.jsx @@ -0,0 +1,128 @@ +import PropTypes from 'prop-types'; +import { User, BookOpen } from 'lucide-react'; +import styles from './styles/LogEntryView.module.css'; +import { Tag } from './icons'; +import { useSelector } from 'react-redux'; +import TeacherFeedback from './TeacherFeedback'; +import EducatorFeedbackForm from './EducatorFeedbackForm'; +import CommentSection from './CommentSection'; + +const LogEntryView = ({ + log, + currentUserRole, + currentUserName, + toggleAssistedStatus, + handleCommentSubmit, + handleFeedbackSubmit, +}) => { + const isEducator = currentUserRole === 'Educator'; + const showAssistedTag = log.assistedBy; + + const darkMode = useSelector(state => state.theme.darkMode); + return ( +
+
+ {/* Student/Log Header Card */} +
+
+
+ +
+
+

+ {log.studentName} + {showAssistedTag && ( + + + Assisted by: {log.assistedBy.name} + + )} +

+

+ {log.studentRole} | Teacher: {log.teacher} +

+

Log for {log.course}

+
+
+ + {/* Educator Assisted Tag Toggle */} +
+ {isEducator && ( + + )} +
+
+ + {/* Log Content & Notes */} +
+
+

Log Content

+

{log.logContent}

+
+
+

Notes to Teacher

+

+ {log.notesToTeacher || 'No optional notes provided.'} +

+
+
+ + + + {isEducator && ( + + )} + + +
+
+ ); +}; + +LogEntryView.propTypes = { + log: PropTypes.shape({ + id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired, + studentName: PropTypes.string.isRequired, + studentRole: PropTypes.string.isRequired, + teacher: PropTypes.string.isRequired, + course: PropTypes.string.isRequired, + logContent: PropTypes.string.isRequired, + notesToTeacher: PropTypes.string, + teacherFeedback: PropTypes.any, + assistedBy: PropTypes.shape({ + name: PropTypes.string.isRequired, + role: PropTypes.string, + }), + comments: PropTypes.arrayOf( + PropTypes.shape({ + id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired, + role: PropTypes.string.isRequired, + author: PropTypes.string.isRequired, + text: PropTypes.string.isRequired, + }), + ), + }).isRequired, + currentUserRole: PropTypes.string.isRequired, + currentUserName: PropTypes.string.isRequired, + toggleAssistedStatus: PropTypes.func.isRequired, + handleCommentSubmit: PropTypes.func.isRequired, + handleFeedbackSubmit: PropTypes.func.isRequired, +}; + +export default LogEntryView; diff --git a/src/components/ActivityLog/MockLogs.js b/src/components/ActivityLog/MockLogs.js new file mode 100644 index 0000000000..2fbad305e1 --- /dev/null +++ b/src/components/ActivityLog/MockLogs.js @@ -0,0 +1,58 @@ +const INITIAL_MOCK_LOGS = [ + { + id: 1, + studentName: 'John Doe', + studentRole: 'Grade A', + teacher: 'Mr. Smith', + course: 'Course A', + logContent: 'Completed chapter 3 exercises on geometric proofs.', + notesToTeacher: 'I found the last proof very challenging.', + teacherFeedback: { + teacherName: 'Mr. Smith', + feedback: 'Great work! Well done. Focus on the deductive reasoning steps.', + }, + assistedBy: null, // Initial state: Log created by student + comments: [ + { + id: 101, + author: 'Mr. Smith', + role: 'Educator', + text: 'Please review the feedback before starting chapter 4.', + }, + { + id: 102, + author: 'John Doe', + role: 'Student', + text: 'Will do, thank you for the clarity on deductive reasoning.', + }, + ], + }, + { + id: 2, + studentName: 'Jane Doe', + studentRole: 'Grade B', + teacher: 'Ms. Johnson', + course: 'General Log', + logContent: 'Had difficulty logging into the learning platform today. Submitted a ticket.', + notesToTeacher: 'N/A', + teacherFeedback: null, + assistedBy: { name: 'Sarah Connor', role: 'Support' }, // Log created by support on behalf of student + comments: [ + { + id: 201, + author: 'Jane Doe', + role: 'Student', + text: 'Thanks Sarah for helping me report this.', + }, + { id: 202, author: 'Ms. Johnson', role: 'Educator', text: "Issue noted. I've contacted IT." }, + ], + }, +]; + +const MOCK_USERS = { + educator: { name: 'Mr. Smith', role: 'Educator' }, + student: { name: 'John Doe', role: 'Student' }, + support: { name: 'Sarah Connor', role: 'Support' }, +}; + +export { INITIAL_MOCK_LOGS, MOCK_USERS }; diff --git a/src/components/ActivityLog/TeacherFeedback.jsx b/src/components/ActivityLog/TeacherFeedback.jsx new file mode 100644 index 0000000000..9cc02848e2 --- /dev/null +++ b/src/components/ActivityLog/TeacherFeedback.jsx @@ -0,0 +1,33 @@ +import PropTypes from 'prop-types'; +import styles from './styles/TeacherFeedbackForm.module.css'; +import { useSelector } from 'react-redux'; + +const TeacherFeedback = ({ feedbackData }) => { + const darkMode = useSelector(state => state.theme.darkMode); + + return ( +
+
+

Teacher Feedback

+ + {feedbackData ? ( +
+

{feedbackData.teacherName}

+

{feedbackData.feedback}

+
+ ) : ( +
No feedback available yet.
+ )} +
+
+ ); +}; + +TeacherFeedback.propTypes = { + feedbackData: PropTypes.shape({ + teacherName: PropTypes.string.isRequired, + feedback: PropTypes.string.isRequired, + }), +}; + +export default TeacherFeedback; diff --git a/src/components/ActivityLog/icons.jsx b/src/components/ActivityLog/icons.jsx new file mode 100644 index 0000000000..c5ecfe591d --- /dev/null +++ b/src/components/ActivityLog/icons.jsx @@ -0,0 +1,40 @@ +import PropTypes from 'prop-types'; +import icons from './styles/icons.module.css'; +import { Briefcase, GraduationCap, User } from 'lucide-react'; + +const IconByRole = ({ role, className = icons.icon }) => { + switch (role) { + case 'Educator': + return ; + case 'Student': + return ; + case 'Support': + return ; + default: + return ; + } +}; + +IconByRole.propTypes = { + role: PropTypes.string.isRequired, + className: PropTypes.string, +}; + +const Tag = ({ children, color, darkMode }) => { + const darkColor = darkMode ? `${color}Dark` : undefined; + return ( + + {children} + + ); +}; + +Tag.propTypes = { + children: PropTypes.node.isRequired, + color: PropTypes.string, + darkMode: PropTypes.bool, +}; + +export { IconByRole, Tag }; diff --git a/src/components/ActivityLog/styles/CommentSection.module.css b/src/components/ActivityLog/styles/CommentSection.module.css new file mode 100644 index 0000000000..bbba6edce7 --- /dev/null +++ b/src/components/ActivityLog/styles/CommentSection.module.css @@ -0,0 +1,166 @@ +/* Comment Section */ +.commentSectionContainer { + margin-top: 32px; +} + +.commentSectionTitle { + font-size: 1.5rem; + font-weight: 800; + color: #111827; + margin-bottom: 16px; +} + +.commentList { + display: flex; + flex-direction: column; + gap: 16px; + max-height: 16rem; + overflow-y: auto; + padding-right: 8px; + margin-bottom: 16px; +} + +.noComments { + text-align: center; + color: #6b7280; + font-style: italic; +} + +.commentCard { + padding: 12px; + background-color: #ffffff; + border-radius: 12px; + border: 1px solid #e5e7eb; + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); +} + +.commentHeader { + display: flex; + align-items: center; + gap: 8px; + margin-bottom: 4px; +} + +.commentIcon { + width: 12px; + height: 12px; + color: #6366f1; +} + +.commentAuthor { + font-weight: 600; + font-size: 0.875rem; + color: #111827; +} + +.commentRoleTag { + background-color: #e0e7ff; + color: #4338ca; + padding: 0 6px; + border-radius: 6px; + font-size: 0.75rem; +} + +.commentText { + color: #374151; + font-size: 0.875rem; +} + +/* New Comment Input */ +.commentInputWrapper { + display: flex; + align-items: flex-start; + gap: 12px; +} + +.commentTextarea { + flex-grow: 1; + padding: 12px; + border-radius: 12px; + border: 1px solid #d1d5db; + background-color: #f9fafb; + color: #111827; + font-size: 0.875rem; + transition: all 0.15s ease-in-out; +} + +.commentTextarea:focus { + outline: none; + border-color: #6366f1; +} + +.commentSubmitBtn { + display: flex; + align-items: center; + justify-content: center; + gap: 8px; + font-weight: 700; + padding: 12px 20px; + border-radius: 12px; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + background-color: #16a34a; + color: #ffffff; + cursor: pointer; + transition: background-color 0.2s ease-in-out; +} + +.commentSubmitBtn:hover { + background-color: #15803d; +} + +.commentSubmitDisabled { + background-color: #9ca3af; + color: #374151; + cursor: not-allowed; +} + +.submitIcon { + width: 20px; + height: 20px; +} + +/* Dark Mode Overrides */ +.darkMode .commentSectionTitle { + color: #f3f4f6; +} + +.darkMode .commentCard { + background-color: #1f2937; + border-color: #374151; +} + +.darkMode .commentAuthor { + color: #f3f4f6; +} + +.darkMode .commentText { + color: #d1d5db; +} + +.darkMode .noComments { + color: #d1d5db; +} + +.darkMode .commentTextarea { + background-color: #374151; + border-color: #4b5563; + color: #f3f4f6; +} + +.darkMode .commentSubmitBtn { + background-color: #16a34a; + color: #f3f4f6; +} + +.darkMode .commentSubmitBtn:hover { + background-color: #15803d; +} + +.darkMode .commentSubmitDisabled { + background-color: #4b5563; + color: #d1d5db; +} + +.darkMode .submitIcon { + color: #f3f4f6; +} diff --git a/src/components/ActivityLog/styles/CreateLogForm.module.css b/src/components/ActivityLog/styles/CreateLogForm.module.css new file mode 100644 index 0000000000..1824f7429d --- /dev/null +++ b/src/components/ActivityLog/styles/CreateLogForm.module.css @@ -0,0 +1,192 @@ +.formContainer { + padding: 24px; + background-color: #ffffff; + border-radius: 12px; + border: 1px solid #e5e7eb; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.05); +} + +.title { + font-size: 1.5rem; + font-weight: 800; + color: #111827; + margin-bottom: 8px; +} + +.description { + font-size: 0.875rem; + color: #6b7280; + margin-bottom: 24px; +} + +.roleName { + font-weight: 600; + color: #6366f1; +} + +.successMessage { + display: flex; + align-items: center; + gap: 8px; + padding: 16px; + margin-bottom: 16px; + background-color: #dcfce7; + color: #166534; + border-radius: 12px; + font-size: 0.875rem; + transition: opacity 0.3s; +} + +.errorMessage { + display: flex; + align-items: center; + gap: 8px; + padding: 16px; + margin-bottom: 16px; + background-color: #fef2f2; + color: #991b1b; + border-radius: 12px; + font-size: 0.875rem; + transition: opacity 0.3s; +} + +.bold { + font-weight: 600; +} + +.icon { + width: 20px; + height: 20px; +} + +.formGrid { + display: grid; + grid-template-columns: 1fr; + gap: 24px; +} + +.rightColumn { + display: flex; + flex-direction: column; + gap: 16px; +} + +.label { + display: block; + font-size: 0.875rem; + font-weight: 500; + color: #374151; + margin-bottom: 4px; +} + +.textarea { + width: 100%; + padding: 12px; + border-radius: 12px; + border: 1px solid #d1d5db; + background-color: #f9fafb; + color: #111827; + font-size: 0.875rem; + transition: all 0.15s ease-in-out; +} + +.textarea:focus { + outline: none; + border-color: #6366f1; +} + +.input { + width: 100%; + padding: 12px; + border-radius: 12px; + border: 1px solid #d1d5db; + background-color: #f9fafb; + color: #111827; + font-size: 0.875rem; + transition: all 0.15s ease-in-out; +} + +.select { + width: 100%; + padding: 12px; + border-radius: 12px; + border: 1px solid #d1d5db; + background-color: #f9fafb; + color: #111827; + font-size: 0.875rem; + transition: all 0.15s ease-in-out; +} + +.submitBtn { + display: flex; + align-items: center; + justify-content: center; + gap: 8px; + font-weight: 700; + padding: 12px; + border-radius: 12px; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + background-color: #4f46e5; + color: #ffffff; + cursor: pointer; + transition: background-color 0.2s ease-in-out; + margin-top: 16px; +} + +.submitBtn:hover { + background-color: #4338ca; +} + +.disabledBtn { + background-color: #9ca3af; + color: #374151; + cursor: not-allowed; +} + +/* Dark Mode Overrides */ +.darkMode .formContainer { + background-color: #1f2937; + border-color: #374151; +} + +.darkMode .title { + color: #f3f4f6; +} + +.darkMode .description { + color: #d1d5db; +} + +.darkMode .label { + color: #e5e7eb; +} + +.darkMode .textarea, +.darkMode .input, +.darkMode .select { + background-color: #374151; + border-color: #4b5563; + color: #f3f4f6; +} + +.darkMode .submitBtn { + background-color: #4f46e5; + color: #f3f4f6; +} + +.darkMode .submitBtn:hover { + background-color: #4338ca; +} + +.darkMode .disabledBtn { + background-color: #4b5563; + color: #d1d5db; +} + +.darkMode .icon { + color: #f3f4f6; +} + +.darkMode .roleName { + color: #818cf8; +} diff --git a/src/components/ActivityLog/styles/DailyLogPage.module.css b/src/components/ActivityLog/styles/DailyLogPage.module.css new file mode 100644 index 0000000000..f3ff939629 --- /dev/null +++ b/src/components/ActivityLog/styles/DailyLogPage.module.css @@ -0,0 +1,182 @@ +/* Light Mode Styles */ +.container { + min-height: 100vh; + padding: 16px; + font-family: sans-serif; + background-color: #f9fafb; + color: #111827; +} + +.header { + display: flex; + flex-direction: column; + justify-content: space-between; + align-items: center; + margin-bottom: 40px; + padding: 16px; + background-color: #fff; + border-radius: 16px; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05); + border-bottom: 1px solid #f3f4f6; +} + +.title { + font-size: 2rem; + font-weight: 900; + color: #4f46e5; + margin-bottom: 16px; +} + +.controls { + display: flex; + flex-wrap: wrap; + gap: 16px; + align-items: center; +} + +.createButton { + display: flex; + align-items: center; + gap: 8px; + font-weight: bold; + padding: 8px 16px; + border-radius: 12px; + background-color: #4f46e5; + color: #fff; + cursor: pointer; + transition: background-color 0.2s ease-in-out; +} + +.createButton:hover { + background-color: #4338ca; +} + +.viewButton { + display: flex; + align-items: center; + gap: 8px; + font-weight: bold; + padding: 8px 16px; + border-radius: 12px; + background-color: #9ca3af; + color: #1f2937; + cursor: pointer; + transition: background-color 0.2s ease-in-out; +} + +.viewButton:hover { + background-color: #6b7280; +} + +.icon { + width: 20px; + height: 20px; +} + +.iconSmall { + width: 16px; + height: 16px; +} + +.logSwitcher, +.roleSelector { + display: flex; + align-items: center; + gap: 8px; + padding: 8px; + background-color: #f3f4f6; + border-radius: 12px; +} + +.select { + background-color: #fff; + border: 1px solid #d1d5db; + border-radius: 8px; + padding: 4px 8px; + font-size: 0.875rem; + color: #111827; +} + +.main { + max-width: 1024px; + margin: 0 auto; +} + +.footer { + margin-top: 40px; + padding-top: 16px; + border-top: 1px solid #e5e7eb; + text-align: center; + font-size: 0.875rem; + color: #6b7280; +} + +.noLogs { + text-align: center; + padding: 40px; + background-color: #fff; + border-radius: 16px; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05); + color: #6b7280; +} + +/* Dark Mode Overrides */ +.darkMode .container { + background-color: #1f2937; + color: #f3f4f6; +} + +.darkMode .header { + background-color: #374151; + border-bottom: 1px solid #4b5563; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.5); +} + +.darkMode .title { + color: #818cf8; +} + +.darkMode .createButton { + background-color: #6366f1; + color: #f9fafb; +} + +.darkMode .createButton:hover { + background-color: #4f46e5; +} + +.darkMode .viewButton { + background-color: #6b7280; + color: #f3f4f6; +} + +.darkMode .viewButton:hover { + background-color: #4b5563; +} + +.darkMode .logSwitcher, +.darkMode .roleSelector { + background-color: #4b5563; +} + +.darkMode .select { + background-color: #374151; + border: 1px solid #6b7280; + color: #f3f4f6; +} + +.darkMode .main { + max-width: 1024px; + margin: 0 auto; +} + +.darkMode .footer { + border-top: 1px solid #6b7280; + color: #d1d5db; +} + +.darkMode .noLogs { + background-color: #374151; + color: #d1d5db; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.5); +} diff --git a/src/components/ActivityLog/styles/EducatorFeedback.module.css b/src/components/ActivityLog/styles/EducatorFeedback.module.css new file mode 100644 index 0000000000..3611a81635 --- /dev/null +++ b/src/components/ActivityLog/styles/EducatorFeedback.module.css @@ -0,0 +1,125 @@ +/* Educator Feedback Form */ +.feedbackFormContainer { + margin-top: 32px; + padding: 24px; + background-color: #fef3c7; + border-radius: 16px; + border: 1px solid #fde68a; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05); +} + +.feedbackFormTitle { + font-size: 1.25rem; + font-weight: 700; + color: #b45309; + margin-bottom: 16px; +} + +.successAlert { + display: flex; + align-items: center; + padding: 12px; + margin-bottom: 16px; + font-size: 0.875rem; + color: #065f46; + background-color: #d1fae5; + border-radius: 12px; + transition: opacity 0.3s; +} + +.alertIcon { + width: 20px; + height: 20px; + margin-right: 12px; +} + +.alertText { + font-weight: 600; +} + +.feedbackTextarea { + width: 100%; + padding: 12px; + border-radius: 12px; + border: 1px solid #fcd34d; + background-color: #ffffff; + color: #111827; + font-size: 0.875rem; + transition: all 0.15s ease-in-out; +} + +.feedbackTextarea:focus { + outline: none; + border-color: #fbbf24; +} + +.feedbackSubmitBtn { + width: 100%; + display: flex; + align-items: center; + justify-content: center; + gap: 8px; + font-weight: 700; + padding: 12px; + border-radius: 12px; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + margin-top: 12px; + background-color: #d97706; + color: #fff; + cursor: pointer; + transition: background-color 0.2s ease-in-out; +} + +.feedbackSubmitBtn:hover { + background-color: #b45309; +} + +.feedbackSubmitDisabled { + background-color: #9ca3af; + color: #374151; + cursor: not-allowed; +} + +.submitIcon { + width: 20px; + height: 20px; +} + +/* Dark Mode */ +.darkMode .feedbackFormContainer { + background-color: rgba(202, 138, 4, 0.2); /* yellow-900/30 */ + border-color: #78350f; /* dark yellow border */ +} + +.darkMode .feedbackFormTitle { + color: #facc15; /* yellow-300 */ +} + +.darkMode .successAlert { + color: #86efac; + background-color: rgba(22, 163, 74, 0.2); +} + +.darkMode .feedbackTextarea { + background-color: #374151; + border-color: #ca8a04; + color: #f3f4f6; +} + +.darkMode .feedbackSubmitBtn { + background-color: #d97706; + color: #f3f4f6; +} + +.darkMode .feedbackSubmitBtn:hover { + background-color: #b45309; +} + +.darkMode .feedbackSubmitDisabled { + background-color: #4b5563; + color: #d1d5db; +} + +.darkMode .submitIcon { + color: #f3f4f6; +} diff --git a/src/components/ActivityLog/styles/LogEntryView.module.css b/src/components/ActivityLog/styles/LogEntryView.module.css new file mode 100644 index 0000000000..558301e352 --- /dev/null +++ b/src/components/ActivityLog/styles/LogEntryView.module.css @@ -0,0 +1,206 @@ +.container { + display: flex; + flex-direction: column; + gap: 32px; +} + +/* Header Card */ +.headerCard { + display: flex; + flex-direction: column; + justify-content: space-between; + align-items: flex-start; + padding: 24px; + background-color: #fff; + border-radius: 16px; + box-shadow: 0 4px 6px rgb(0 0 0 / 5%); + border: 1px solid #f3f4f6; +} + +.headerLeft { + display: flex; + gap: 16px; + align-items: flex-start; +} + +.avatar { + flex-shrink: 0; + width: 64px; + height: 64px; + background-color: #e0e7ff; + border-radius: 50%; + display: flex; + justify-content: center; + align-items: center; + color: #4f46e5; +} + +.avatarIcon { + width: 32px; + height: 32px; +} + +.studentName { + display: flex; + align-items: center; + gap: 8px; + font-size: 1.25rem; + font-weight: 700; + color: #111827; +} + +.assistedTag { + display: inline-flex; + align-items: center; + padding: 4px 8px; + gap: 18px; + background-color: #991b1b; + color: #ffe5e5; + border-radius: 6px; + font-size: 0.75rem; + font-weight: 500; + line-height: 1; +} + +.assistedIcon { + width: 14px; + height: 14px; + flex-shrink: 0; + color: inherit; +} + +.studentRole { + font-size: 0.875rem; + color: #6b7280; +} + +.courseInfo { + font-size: 1rem; + font-weight: 500; + color: #4f46e5; + margin-top: 4px; +} + +/* Header Right Buttons */ +.headerRight { + display: flex; + gap: 8px; + margin-top: 16px; +} + +.markAssistedBtn { + background-color: #16a34a; + color: #fff; + font-weight: 600; + padding: 8px 16px; + border-radius: 12px; + box-shadow: 0 2px 4px rgb(0 0 0 / 10%); + transition: background-color 0.2s; +} + +.markAssistedBtn:hover { + background-color: #15803d; +} + +.removeAssistedBtn { + background-color: #dc2626; + color: #fff; + font-weight: 600; + padding: 8px 16px; + border-radius: 12px; + box-shadow: 0 2px 4px rgb(0 0 0 / 10%); + transition: background-color 0.2s; +} + +.removeAssistedBtn:hover { + background-color: #b91c1c; +} + +/* Grid for Log Content & Notes */ +.grid { + display: grid; + grid-template-columns: 1fr; + gap: 24px; +} + +@media (width >= 768px) { + .grid { + grid-template-columns: repeat(2, 1fr); + } +} + +.gridItem { + padding: 24px; + background-color: #fff; + border-radius: 16px; + box-shadow: 0 4px 6px rgb(0 0 0 / 5%); + border: 1px solid #f3f4f6; +} + +.gridTitle { + font-size: 1.125rem; + font-weight: 600; + color: #111827; + margin-bottom: 8px; +} + +.gridContent { + color: #374151; + white-space: pre-wrap; +} + +.gridContentItalic { + color: #374151; + font-style: italic; +} + +/* Dark Mode Overrides */ +.darkMode .headerCard, +.darkMode .gridItem { + background-color: #1f2937; + border-color: #4b5563; + box-shadow: 0 4px 6px rgb(0 0 0 / 50%); +} + +.darkMode .studentName, +.darkMode .gridTitle { + color: #f3f4f6; +} + +.darkMode .studentRole, +.darkMode .gridContent, +.darkMode .gridContentItalic { + color: #d1d5db; +} + +.darkMode .courseInfo { + color: #818cf8; +} + +.darkMode .markAssistedBtn { + background-color: #16a34a; + color: #f3f4f6; +} + +.darkMode .markAssistedBtn:hover { + background-color: #15803d; +} + +.darkMode .removeAssistedBtn { + background-color: #dc2626; + color: #f3f4f6; +} + +.darkMode .removeAssistedBtn:hover { + background-color: #b91c1c; +} + +.darkMode .avatar { + background-color: #4338ca; + color: #c7d2fe; +} + +.darkMode .assistedTag { + background-color: #991b1b; + color: #fca5a5; +} diff --git a/src/components/ActivityLog/styles/TeacherFeedbackForm.module.css b/src/components/ActivityLog/styles/TeacherFeedbackForm.module.css new file mode 100644 index 0000000000..4aa65fcbf7 --- /dev/null +++ b/src/components/ActivityLog/styles/TeacherFeedbackForm.module.css @@ -0,0 +1,64 @@ +/* Teacher Feedback Styles */ +.feedbackContainer { + margin-top: 32px; +} + +.feedbackTitle { + font-size: 1.5rem; + font-weight: 800; + color: #111827; + margin-bottom: 16px; +} + +.feedbackCard { + padding: 24px; + background-color: #f3f4f6; + border-radius: 16px; + border: 1px solid #e5e7eb; +} + +.feedbackTeacherName { + font-size: 0.875rem; + font-weight: 600; + color: #111827; + margin-bottom: 8px; +} + +.feedbackText { + color: #374151; + font-style: italic; +} + +/* No Feedback Card */ +.noFeedbackCard { + padding: 24px; + text-align: center; + color: #6b7280; + background-color: #f9fafb; + border-radius: 16px; + border: 1px dashed #d1d5db; +} + +/* Dark Mode Overrides */ +.darkMode .feedbackTitle { + color: #f3f4f6; +} + +.darkMode .feedbackCard { + background-color: #374151; + border-color: #4b5563; +} + +.darkMode .feedbackTeacherName { + color: #f3f4f6; +} + +.darkMode .feedbackText { + color: #d1d5db; +} + +.darkMode .noFeedbackCard { + background-color: #1f2937; + color: #d1d5db; + border-color: #4b5563; +} diff --git a/src/components/ActivityLog/styles/icons.module.css b/src/components/ActivityLog/styles/icons.module.css new file mode 100644 index 0000000000..1fb548c5b3 --- /dev/null +++ b/src/components/ActivityLog/styles/icons.module.css @@ -0,0 +1,43 @@ +.icon { + width: 1rem; + height: 1rem; +} + +/* Base tag styling */ +.tag { + display: inline-flex; + align-items: center; + gap: 4px; + border-radius: 9999px; + padding: 0.125rem 0.5rem; + font-size: 0.75rem; + font-weight: 500; +} + +/* Color variants */ +.tagEducator { + background-color: #e0f2fe; + color: #0369a1; +} + +.tagStudent { + background-color: #dcfce7; + color: #166534; +} + +.tagSupport { + background-color: #fef9c3; + color: #854d0e; +} + +/* Fallback neutral tag */ +.tagDefault { + background-color: #f3f4f6; + color: #374151; +} + +/* Dark mode variants */ +.tagSupportDark { + background-color: #fef9c3; + color: #854d0e; +} diff --git a/src/routes.jsx b/src/routes.jsx index e233e5d23a..45437d39a8 100644 --- a/src/routes.jsx +++ b/src/routes.jsx @@ -183,6 +183,7 @@ import TaskDetails from './components/EductionPortal/StudentTasks/TaskDetails'; import PRReviewTeamAnalytics from './components/HGNPRDashboard/PRReviewTeamAnalytics'; import PRPromotionsPage from './components/PRPromotions/PRPromotionsPage'; import SimpleToolChart from './components/BMDashboard/Tools/SimpleToolChart'; +import ActivityLogs from './components/ActivityLog/DailyLogPage'; import ProtectedRoute from './components/common/ProtectedRoute'; import IntermediateTaskList from './components/EductionPortal/IntermediateTasks/IntermediateTaskList'; import WriteTaskUpload from './components/EductionPortal/Tasks/WriteTaskUpload'; @@ -785,6 +786,7 @@ export default ( fallback allowedRoles={[UserRole.Owner]} /> +