diff --git a/client/src/api/Timetable.js b/client/src/api/Timetable.js
new file mode 100644
index 00000000..259b3a4b
--- /dev/null
+++ b/client/src/api/Timetable.js
@@ -0,0 +1,17 @@
+import axios from 'axios';
+import serverRoot from './server';
+
+const API = axios.create({
+ baseURL: `${serverRoot}/api/examslots`,
+ withCredentials: true,
+});
+
+export const fetchExamSlot = async (course, branch, semester) => {
+ const { data } = await API.get(`/${course}/${branch}/${semester}`);
+ return data;
+}
+
+export const createOrUpdateExamSlot = async (examSlotData) => {
+ const { data } = await API.post('/update', examSlotData);
+ return data;
+}
\ No newline at end of file
diff --git a/client/src/screens/contributions/index.jsx b/client/src/screens/contributions/index.jsx
index c38969aa..3fca2808 100644
--- a/client/src/screens/contributions/index.jsx
+++ b/client/src/screens/contributions/index.jsx
@@ -108,7 +108,7 @@ const Contributions = () => {
? "Upload files to this folder"
: "Your files will be added to this folder"}
-
{isUploading ? "UPLOADING..." : "SUBMIT"}
diff --git a/client/src/screens/contributions/styles.scss b/client/src/screens/contributions/styles.scss
index 8468a9fc..b52b989f 100644
--- a/client/src/screens/contributions/styles.scss
+++ b/client/src/screens/contributions/styles.scss
@@ -174,4 +174,30 @@
.filepond--root {
max-height: 20vh;
+}
+
+.file_pond {
+ margin-top: 1rem;
+
+ .large {
+ width: 100%;
+ height: 100%;
+ }
+
+ &.large {
+ .filepond--root {
+ height: 00px; // make it taller
+ max-width: 100%; // stretch width
+ font-size: 1.1rem; // slightly bigger text
+ }
+
+ .filepond--drop-label {
+ height: 100%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 1.2rem;
+ padding: 2rem;
+ }
+ }
}
\ No newline at end of file
diff --git a/client/src/screens/dashboard/components/timetable/timetable.jsx b/client/src/screens/dashboard/components/timetable/timetable.jsx
new file mode 100644
index 00000000..75b12bfd
--- /dev/null
+++ b/client/src/screens/dashboard/components/timetable/timetable.jsx
@@ -0,0 +1,682 @@
+import React, { useState } from 'react';
+import './timetable.scss';
+import { createOrUpdateExamSlot, fetchExamSlot } from '../../../../api/Timetable'; // Adjust path as needed
+import { useEffect } from 'react';
+import { toast } from 'react-toastify';
+
+const Timetable = ({ user }) => {
+ const [showForm, setShowForm] = useState(false);
+ const [examSlots, setExamSlots] = useState({});
+ const [formData, setFormData] = useState({
+ A: '', B: '', C: '', D: '', E: '', F: '', G: '',
+ A1: '', B1: '', C1: '', D1: '', E1: '', F1: '', G1: '',
+ ML1: '', ML2: '', ML3: '', ML4: '', ML5: '',
+ AL1: '', AL2: '', AL3: '', AL4: '', AL5: ''
+ });
+ const [overlaps, setOverlaps] = useState([]);
+
+ console.log(examSlots);
+
+ const timeSlots = [
+ '8:00 AM',
+ '9:00 AM',
+ '10:00 AM',
+ '11:00 AM',
+ '12:00 PM',
+ '1:00 PM',
+ '2:00 PM',
+ '3:00 PM',
+ '4:00 PM',
+ '5:00 PM'
+ ];
+
+ const days = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday'];
+
+ // Define slot schedules for overlap detection
+ const slotSchedules = {
+ 'Monday': {
+ '8:00 AM': ['A'],
+ '9:00 AM': ['B', 'ML1'],
+ '10:00 AM': ['C', 'ML1'],
+ '11:00 AM': ['D', 'ML1'],
+ '12:00 PM': ['F'],
+ '1:00 PM': ['F1'],
+ '2:00 PM': ['D1', 'AL1'],
+ '3:00 PM': ['C1', 'AL1'],
+ '4:00 PM': ['B1', 'AL1'],
+ '5:00 PM': ['A1']
+ },
+ 'Tuesday': {
+ '8:00 AM': ['E'],
+ '9:00 AM': ['A', 'ML2'],
+ '10:00 AM': ['B', 'ML2'],
+ '11:00 AM': ['C', 'ML2'],
+ '12:00 PM': ['F'],
+ '1:00 PM': ['F1'],
+ '2:00 PM': ['C1', 'AL2'],
+ '3:00 PM': ['B1', 'AL2'],
+ '4:00 PM': ['A1', 'AL2'],
+ '5:00 PM': ['E1']
+ },
+ 'Wednesday': {
+ '8:00 AM': ['D'],
+ '9:00 AM': ['E', 'ML3'],
+ '10:00 AM': ['A', 'ML3'],
+ '11:00 AM': ['B', 'ML3'],
+ '12:00 PM': ['G'],
+ '1:00 PM': ['G1'],
+ '2:00 PM': ['B1', 'AL3'],
+ '3:00 PM': ['A1', 'AL3'],
+ '4:00 PM': ['E1', 'AL3'],
+ '5:00 PM': ['D1']
+ },
+ 'Thursday': {
+ '8:00 AM': ['C'],
+ '9:00 AM': ['D', 'ML4'],
+ '10:00 AM': ['E', 'ML4'],
+ '11:00 AM': ['A', 'ML4'],
+ '12:00 PM': ['G'],
+ '1:00 PM': ['G1'],
+ '2:00 PM': ['A1', 'AL4'],
+ '3:00 PM': ['E1', 'AL4'],
+ '4:00 PM': ['D1', 'AL4'],
+ '5:00 PM': ['C1']
+ },
+ 'Friday': {
+ '8:00 AM': ['B'],
+ '9:00 AM': ['C', 'ML5'],
+ '10:00 AM': ['D', 'ML5'],
+ '11:00 AM': ['F', 'ML5'],
+ '12:00 PM': ['G'],
+ '1:00 PM': ['G1'],
+ '2:00 PM': ['F1', 'AL5'],
+ '3:00 PM': ['D1', 'AL5'],
+ '4:00 PM': ['C1', 'AL5'],
+ '5:00 PM': ['B1']
+ }
+ };
+
+ // Function to detect overlaps
+ const detectOverlaps = (currentFormData) => {
+ const foundOverlaps = [];
+
+ Object.entries(slotSchedules).forEach(([day, daySchedule]) => {
+ Object.entries(daySchedule).forEach(([time, slots]) => {
+ // Check if multiple slots have courses assigned for the same time
+ const activeCourses = slots.filter(slot => {
+ const course = currentFormData[slot];
+ return course && course.trim() !== '';
+ });
+
+ if (activeCourses.length > 1) {
+ const courseDetails = activeCourses.map(slot => ({
+ slot,
+ course: currentFormData[slot]
+ }));
+
+ foundOverlaps.push({
+ day,
+ time,
+ conflicts: courseDetails
+ });
+ }
+ });
+ });
+
+ return foundOverlaps;
+ };
+
+ // Generate timetable data based on examSlots
+ const generateTimetableData = () => {
+ const daySchedule = {
+ 'Monday': {
+ '8:00 AM': 'A',
+ '9:00 AM': examSlots.ML1 ? 'ML1' : 'B',
+ '10:00 AM': examSlots.ML1 ? 'ML1' : 'C',
+ '11:00 AM': examSlots.ML1 ? 'ML1' : 'D',
+ '12:00 PM': 'F',
+ '1:00 PM': 'F1',
+ '2:00 PM': examSlots.AL1 ? 'AL1' : 'D1',
+ '3:00 PM': examSlots.AL1 ? 'AL1' : 'C1',
+ '4:00 PM': examSlots.AL1 ? 'AL1' : 'B1',
+ '5:00 PM': 'A1'
+ },
+ 'Tuesday': {
+ '8:00 AM': 'E',
+ '9:00 AM': examSlots.ML2 ? 'ML2' : 'A',
+ '10:00 AM': examSlots.ML2 ? 'ML2' : 'B',
+ '11:00 AM': examSlots.ML2 ? 'ML2' : 'C',
+ '12:00 PM': 'F',
+ '1:00 PM': 'F1',
+ '2:00 PM': examSlots.AL2 ? 'AL2' : 'C1',
+ '3:00 PM': examSlots.AL2 ? 'AL2' : 'B1',
+ '4:00 PM': examSlots.AL2 ? 'AL2' : 'A1',
+ '5:00 PM': 'E1'
+ },
+ 'Wednesday': {
+ '8:00 AM': 'D',
+ '9:00 AM': examSlots.ML3 ? 'ML3' : 'E',
+ '10:00 AM': examSlots.ML3 ? 'ML3' : 'A',
+ '11:00 AM': examSlots.ML3 ? 'ML3' : 'B',
+ '12:00 PM': 'G',
+ '1:00 PM': 'G1',
+ '2:00 PM': examSlots.AL3 ? 'AL3' : 'B1',
+ '3:00 PM': examSlots.AL3 ? 'AL3' : 'A1',
+ '4:00 PM': examSlots.AL3 ? 'AL3' : 'E1',
+ '5:00 PM': 'D1'
+ },
+ 'Thursday': {
+ '8:00 AM': 'C',
+ '9:00 AM': examSlots.ML4 ? 'ML4' : 'D',
+ '10:00 AM': examSlots.ML4 ? 'ML4' : 'E',
+ '11:00 AM': examSlots.ML4 ? 'ML4' : 'A',
+ '12:00 PM': 'G',
+ '1:00 PM': 'G1',
+ '2:00 PM': examSlots.AL4 ? 'AL4' : 'A1',
+ '3:00 PM': examSlots.AL4 ? 'AL4' : 'E1',
+ '4:00 PM': examSlots.AL4 ? 'AL4' : 'D1',
+ '5:00 PM': 'C1'
+ },
+ 'Friday': {
+ '8:00 AM': 'B',
+ '9:00 AM': examSlots.ML5 ? 'ML5' : 'C',
+ '10:00 AM': examSlots.ML5 ? 'ML5' : 'D',
+ '11:00 AM': examSlots.ML5 ? 'ML5' : 'F',
+ '12:00 PM': 'G',
+ '1:00 PM': 'G1',
+ '2:00 PM': examSlots.AL5 ? 'AL5' : 'F1',
+ '3:00 PM': examSlots.AL5 ? 'AL5' : 'D1',
+ '4:00 PM': examSlots.AL5 ? 'AL5' : 'C1',
+ '5:00 PM': 'B1'
+ }
+ };
+
+ const timetableData = {};
+
+ days.forEach(day => {
+ timetableData[day] = {};
+ Object.entries(daySchedule[day]).forEach(([time, slot]) => {
+ const courseCode = examSlots[slot];
+ if (courseCode && courseCode.trim() !== '') {
+ timetableData[day][time] = {
+ subject: courseCode,
+ room: `Slot ${slot}`,
+ type: slot.includes('L') ? 'lab' : 'lecture'
+ };
+ }
+ });
+ });
+
+ return timetableData;
+ };
+
+ const timetableData = generateTimetableData();
+
+ const handleInputChange = (slot, value) => {
+ const newFormData = {
+ ...formData,
+ [slot]: value
+ };
+
+ setFormData(newFormData);
+
+ // Detect overlaps in real-time
+ const currentOverlaps = detectOverlaps(newFormData);
+ setOverlaps(currentOverlaps);
+ };
+
+ const handleFormSubmit = async () => {
+ // Check for overlaps before submitting
+ const currentOverlaps = detectOverlaps(formData);
+
+ if (currentOverlaps.length > 0) {
+ toast.error('Please resolve all schedule conflicts before saving!');
+ return;
+ }
+
+ try {
+ // Prepare the data for API request
+ const examSlotData = {
+ branch: user?.user?.department || user?.department,
+ semester: user?.user?.semester || user?.semester,
+ course: user?.user?.degree || user?.degree,
+
+ // Regular slots
+ A: formData.A || '',
+ B: formData.B || '',
+ C: formData.C || '',
+ D: formData.D || '',
+ E: formData.E || '',
+ F: formData.F || '',
+ G: formData.G || '',
+
+ A1: formData.A1 || '',
+ B1: formData.B1 || '',
+ C1: formData.C1 || '',
+ D1: formData.D1 || '',
+ E1: formData.E1 || '',
+ F1: formData.F1 || '',
+ G1: formData.G1 || '',
+
+ // AL slots
+ AL1: formData.AL1 || '',
+ AL2: formData.AL2 || '',
+ AL3: formData.AL3 || '',
+ AL4: formData.AL4 || '',
+ AL5: formData.AL5 || '',
+
+ // ML slots
+ ML1: formData.ML1 || '',
+ ML2: formData.ML2 || '',
+ ML3: formData.ML3 || '',
+ ML4: formData.ML4 || '',
+ ML5: formData.ML5 || ''
+ };
+
+ console.log('Submitting timetable data:', examSlotData);
+
+ // Make API request
+ const response = await createOrUpdateExamSlot(examSlotData);
+
+ console.log('Timetable updated successfully:', response);
+ toast.success('Timetable updated successfully!');
+
+ // Update examSlots with new data
+ setExamSlots(formData);
+
+ // Clear overlaps and close form
+ setOverlaps([]);
+ setShowForm(false);
+
+ } catch (error) {
+ console.error('Error updating timetable:', error);
+ toast.error('Failed to update timetable. Please try again.');
+ }
+ };
+
+ const handleFormCancel = () => {
+ setShowForm(false);
+ setOverlaps([]);
+ // Reset form data to current examSlots values
+ setFormData({
+ A: examSlots.A || '',
+ B: examSlots.B || '',
+ C: examSlots.C || '',
+ D: examSlots.D || '',
+ E: examSlots.E || '',
+ F: examSlots.F || '',
+ G: examSlots.G || '',
+ A1: examSlots.A1 || '',
+ B1: examSlots.B1 || '',
+ C1: examSlots.C1 || '',
+ D1: examSlots.D1 || '',
+ E1: examSlots.E1 || '',
+ F1: examSlots.F1 || '',
+ G1: examSlots.G1 || '',
+ ML1: examSlots.ML1 || '',
+ ML2: examSlots.ML2 || '',
+ ML3: examSlots.ML3 || '',
+ ML4: examSlots.ML4 || '',
+ ML5: examSlots.ML5 || '',
+ AL1: examSlots.AL1 || '',
+ AL2: examSlots.AL2 || '',
+ AL3: examSlots.AL3 || '',
+ AL4: examSlots.AL4 || '',
+ AL5: examSlots.AL5 || ''
+ });
+ };
+
+ const getSlotColor = (slot) => {
+ const slotColors = {
+ // Regular slots - vibrant colors matching the course cards
+ 'A': '#C8A8E9', // light purple
+ 'B': '#FFB3D1', // light pink
+ 'C': '#87CEEB', // light blue
+ 'D': '#DDA0DD', // plum
+ 'E': '#F0E68C', // khaki
+ 'F': '#FFE4B5', // moccasin
+ 'G': '#98FB98', // pale green
+
+ // Lab slots - slightly darker versions
+ 'A1': '#B19CD9', // darker purple
+ 'B1': '#FF9AC1', // darker pink
+ 'C1': '#6BB6DB', // darker blue
+ 'D1': '#CD88CD', // darker plum
+ 'E1': '#E6D87C', // darker khaki
+ 'F1': '#FFDAB9', // darker moccasin
+ 'G1': '#90EE90', // darker pale green
+
+ // Morning lab slots - rich colors
+ 'ML1': '#9370DB', // medium slate blue
+ 'ML2': '#FF69B4', // hot pink
+ 'ML3': '#4682B4', // steel blue
+ 'ML4': '#DA70D6', // orchid
+ 'ML5': '#FFD700', // gold
+
+ // Afternoon lab slots - warm colors
+ 'AL1': '#87CEEB', // light blue
+ 'AL2': '#DDA0DD', // plum
+ 'AL3': '#F0E68C', // khaki
+ 'AL4': '#FFE4B5', // moccasin
+ 'AL5': '#98FB98', // pale green
+ };
+ return slotColors[slot] || '#f8f9fa';
+ };
+
+ // Check if user is BR (you can adjust this condition based on your user object structure)
+ const isBR = user?.user?.isBR || user?.isBR;
+
+ useEffect(() => {
+ const fetchData = async () => {
+ console.log(user);
+ console.log(user?.user?.degree, user?.user?.department,user?.user?.semester);
+
+ var branch = user?.user?.department;
+ // replace spaces of branch with -
+ branch = branch.replace(/ /g, '-');
+
+ console.log(branch);
+
+ const response = await fetchExamSlot(user?.user?.degree, branch,user?.user?.semester);
+
+ setExamSlots(response);
+
+ // Update formData with fetched exam slots
+ const newFormData = {
+ A: response.A || '',
+ B: response.B || '',
+ C: response.C || '',
+ D: response.D || '',
+ E: response.E || '',
+ F: response.F || '',
+ G: response.G || '',
+ A1: response.A1 || '',
+ B1: response.B1 || '',
+ C1: response.C1 || '',
+ D1: response.D1 || '',
+ E1: response.E1 || '',
+ F1: response.F1 || '',
+ G1: response.G1 || '',
+ ML1: response.ML1 || '',
+ ML2: response.ML2 || '',
+ ML3: response.ML3 || '',
+ ML4: response.ML4 || '',
+ ML5: response.ML5 || '',
+ AL1: response.AL1 || '',
+ AL2: response.AL2 || '',
+ AL3: response.AL3 || '',
+ AL4: response.AL4 || '',
+ AL5: response.AL5 || ''
+ };
+
+ setFormData(newFormData);
+
+ // Check for existing overlaps
+ const initialOverlaps = detectOverlaps(newFormData);
+ setOverlaps(initialOverlaps);
+
+ console.log(response);
+ };
+
+ fetchData();
+ }, [user]);
+
+ const getInputStyle = (slot) => {
+ // Check if this slot is part of any overlap
+ const isInOverlap = overlaps.some(overlap =>
+ overlap.conflicts.some(conflict => conflict.slot === slot)
+ );
+
+ return {
+ width: '100%',
+ padding: '8px',
+ border: isInOverlap ? '2px solid #dc3545' : '1px solid #000000ff',
+ borderRadius: '4px',
+ fontSize: '14px',
+ backgroundColor: isInOverlap ? '#fff5f5' : 'white'
+ };
+ };
+
+ return (
+
+ {/* BR Form Button and Modal */}
+ {isBR && (
+
+
+
+ )}
+
+ {/* Form Modal */}
+ {showForm && (
+
+
+
Update Timetable Slots
+
+ {/* Overlap Warning */}
+ {overlaps.length > 0 && (
+
+
⚠️ Schedule Conflicts Detected:
+ {overlaps.map((overlap, index) => (
+
+
{overlap.day} at {overlap.time}:
+
+ {overlap.conflicts.map((conflict, i) => (
+ - Slot {conflict.slot}: {conflict.course}
+ ))}
+
+
+ ))}
+
+ Please remove courses from conflicting slots or clear one of the overlapping entries.
+
+
+ )}
+
+
+
+ {/* Regular slots A-G */}
+
+
Regular Slots
+ {['A', 'B', 'C', 'D', 'E', 'F', 'G'].map(slot => (
+
+
+ Slot {slot}:
+
+
handleInputChange(slot, e.target.value)}
+ placeholder={`Enter course for slot ${slot}`}
+ style={getInputStyle(slot)}
+ />
+
+ ))}
+
+
+ {/* Lab slots A1-G1 */}
+
+
Lab Slots
+ {['A1', 'B1', 'C1', 'D1', 'E1', 'F1', 'G1'].map(slot => (
+
+
+ Slot {slot}:
+
+
handleInputChange(slot, e.target.value)}
+ placeholder={`Enter course for slot ${slot}`}
+ style={getInputStyle(slot)}
+ />
+
+ ))}
+
+
+ {/* ML slots ML1-ML5 */}
+
+
Morning Lab Slots
+ {['ML1', 'ML2', 'ML3', 'ML4', 'ML5'].map(slot => (
+
+
+ {slot} (9-12):
+
+
handleInputChange(slot, e.target.value)}
+ placeholder={`Enter course for ${slot}`}
+ style={getInputStyle(slot)}
+ />
+
+ ))}
+
+
+ {/* AL slots AL1-AL5 */}
+
+
Afternoon Lab Slots
+ {['AL1', 'AL2', 'AL3', 'AL4', 'AL5'].map(slot => (
+
+
+ {slot} (2-5):
+
+
handleInputChange(slot, e.target.value)}
+ placeholder={`Enter course for ${slot}`}
+ style={getInputStyle(slot)}
+ />
+
+ ))}
+
+
+
+
+
+
+
+
+
+
+ )}
+
+ {/* Original Timetable */}
+
+ {/* Header row with time slots */}
+
+ {timeSlots.map((time) => (
+
+ {time}
+
+ ))}
+
+ {/* Day rows */}
+ {days.map((day) => (
+
+ {day}
+ {timeSlots.map((time) => {
+ const classData = timetableData[day]?.[time];
+ return (
+
+ {classData ? (
+
+
+ {classData.subject}
+
+
+ {classData.room}
+
+
+ ) : null}
+
+ );
+ })}
+
+ ))}
+
+
+ );
+};
+
+export default Timetable;
\ No newline at end of file
diff --git a/client/src/screens/dashboard/components/timetable/timetable.scss b/client/src/screens/dashboard/components/timetable/timetable.scss
new file mode 100644
index 00000000..7b3cdaa2
--- /dev/null
+++ b/client/src/screens/dashboard/components/timetable/timetable.scss
@@ -0,0 +1,154 @@
+
+
+// timetable.scss
+.timetable-container {
+ width: 100%;
+ overflow-x: auto;
+ padding: 20px 0;
+
+ .timetable-grid {
+ display: grid;
+ grid-template-columns: 120px repeat(10, 1fr);
+ // grid-gap: 1px;
+ background-color: #e0e0e0;
+ border: 1px solid #000000;
+ border-radius: 8px;
+ overflow: hidden;
+ min-width: 800px;
+
+ .time-header {
+ background-color: #f5f5f5;
+ padding: 12px 8px;
+ font-weight: bold;
+ text-align: center;
+ // border-right: 1px solid #000000;
+ }
+
+ .time-slot-header {
+ background-color: #f5f5f5;
+ padding: 12px 8px;
+ font-weight: bold;
+ text-align: center;
+ font-size: 0.9rem;
+ color: #333;
+ }
+
+ .day-header {
+ background-color: #f0f0f0;
+ padding: 12px 8px;
+ font-weight: bold;
+ text-align: center;
+ color: #333;
+ // border-right: 1px solid #000000;
+ }
+
+ .timetable-cell {
+ background-color: white;
+ border: 0.02rem solid #000000 !important;
+ padding: 8px;
+ min-height: 60px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ transition: all 0.2s ease;
+
+ &.empty {
+ background-color: #fafafa;
+
+ &:hover {
+ background-color: #f0f0f0;
+ }
+ }
+
+ &.occupied {
+ cursor: pointer;
+
+ &:hover {
+ transform: scale(1.02);
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+ }
+
+ .class-info {
+ text-align: center;
+ width: 100%;
+
+ .subject-name {
+ font-weight: bold;
+ font-size: 0.85rem;
+ margin-bottom: 4px;
+ color: rgb(0, 0, 0);
+ }
+
+ .room-info {
+ font-size: 0.7rem;
+ opacity: 0.9;
+ color: rgb(0, 0, 0);
+ }
+ }
+
+ // Different colors for different class types
+ &.lecture {
+ background: linear-gradient(135deg, #4CAF50, #45a049);
+ }
+
+ &.lab {
+ background: linear-gradient(135deg, #2196F3, #1976D2);
+ }
+
+ &.practical {
+ background: linear-gradient(135deg, #FF9800, #F57C00);
+ }
+
+ &.tutorial {
+ background: linear-gradient(135deg, #9C27B0, #7B1FA2);
+ }
+
+ &.project {
+ background: linear-gradient(135deg, #E91E63, #C2185B);
+ }
+
+ &.seminar {
+ background: linear-gradient(135deg, #607D8B, #455A64);
+ }
+
+ &.study {
+ background: linear-gradient(135deg, #795548, #5D4037);
+ }
+
+ &.default {
+ background: linear-gradient(135deg, #757575, #616161);
+ }
+ }
+ }
+ }
+}
+
+// Mobile responsiveness
+@media (max-width: 768px) {
+ .timetable-container {
+ .timetable-grid {
+ grid-template-columns: 80px repeat(10, minmax(80px, 1fr));
+
+ .time-slot-header,
+ .day-header {
+ font-size: 0.8rem;
+ padding: 8px 4px;
+ }
+
+ .timetable-cell {
+ min-height: 50px;
+ padding: 4px;
+
+ &.occupied .class-info {
+ .subject-name {
+ font-size: 0.75rem;
+ }
+
+ .room-info {
+ font-size: 0.65rem;
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/client/src/screens/dashboard/index.jsx b/client/src/screens/dashboard/index.jsx
index b9b8f431..ab5ee7a0 100644
--- a/client/src/screens/dashboard/index.jsx
+++ b/client/src/screens/dashboard/index.jsx
@@ -9,7 +9,7 @@ import CourseCard from "./components/coursecard";
import ContributionBanner from "./components/contributionbanner";
import Footer from "../../components/footer";
import FavouriteCard from "./components/favouritecard";
-
+import Timetable from "./components/timetable/timetable.jsx";
import { ChangeCurrentCourse, ResetFileBrowserState } from "../../actions/filebrowser_actions";
import { useDispatch, useSelector } from "react-redux";
import { useNavigate } from "react-router-dom";
@@ -238,12 +238,13 @@ const Dashboard = () => {
text={
showPrevious
? "▼ HIDE PREVIOUS COURSES"
- : "▶ SHOW PREVIOUS COURSES"
+ : "▶️ SHOW PREVIOUS COURSES"
}
color={"light"}
type={"bold"}
/>
+
{showPrevious && (
<>
@@ -268,12 +269,19 @@ const Dashboard = () => {
>
)}
+
>
)}
@@ -306,4 +314,4 @@ const Dashboard = () => {
);
};
-export default Dashboard;
+export default Dashboard;
\ No newline at end of file
diff --git a/client/yarn.lock b/client/yarn.lock
index bbc033e4..a4ff28eb 100644
--- a/client/yarn.lock
+++ b/client/yarn.lock
@@ -492,6 +492,11 @@ electron-to-chromium@^1.4.251:
resolved "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.284.tgz"
integrity sha512-M8WEXFuKXMYMVr45fo8mq0wUrrJHheiKZf6BArTKk9ZBYCKJEOU5H8cdWgDT+qCVZf7Na4lVUaZsA+h6uA9+PA==
+esbuild-darwin-arm64@0.15.16:
+ version "0.15.16"
+ resolved "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.15.16.tgz"
+ integrity sha512-fMXaUr5ou0M4WnewBKsspMtX++C1yIa3nJ5R2LSbLCfJT3uFdcRoU/NZjoM4kOMKyOD9Sa/2vlgN8G07K3SJnw==
+
esbuild@^0.15.9:
version "0.15.16"
resolved "https://registry.npmjs.org/esbuild/-/esbuild-0.15.16.tgz"
@@ -561,6 +566,11 @@ form-data@^4.0.0:
combined-stream "^1.0.8"
mime-types "^2.1.12"
+fsevents@~2.3.2:
+ version "2.3.2"
+ resolved "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz"
+ integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==
+
function-bind@^1.1.1:
version "1.1.1"
resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz"
diff --git a/server/index.js b/server/index.js
index e0d8c9eb..cdf978b4 100644
--- a/server/index.js
+++ b/server/index.js
@@ -31,6 +31,7 @@ import brRoutes from "./modules/br/br.routes.js";
import fileRoutes from "./modules/file/file.routes.js";
import folderRoutes from "./modules/folder/folder.routes.js";
import yearRoutes from "./modules/year/year.routes.js";
+import examSlotsRoutes from "./modules/exam-slots/exam-slots.routes.js";
import links from "./links.js";
const app = express();
@@ -75,6 +76,7 @@ app.use("/api/br", brRoutes);
app.use("/api/files", fileRoutes);
app.use("/api/folder", folderRoutes);
app.use("/api/year", yearRoutes);
+app.use("/api/examslots", examSlotsRoutes);
app.use(
"/homepage",
diff --git a/server/modules/exam-slots/exam-slots.controller.js b/server/modules/exam-slots/exam-slots.controller.js
new file mode 100644
index 00000000..44550b8b
--- /dev/null
+++ b/server/modules/exam-slots/exam-slots.controller.js
@@ -0,0 +1,48 @@
+import express from 'express';
+import ExamSlotModel from './exam-slots.model.js';
+
+//check if exam slot exists, if it does edit it, if not create it
+export const createOrUpdateExamSlot = async (req, res) => {
+ try {
+ const {course} = req.body;
+ const { branch } = req.body;
+ const {semester} = req.body;
+ let examSlot = await ExamSlotModel.findOne({course, branch, semester });
+ if (examSlot) {
+ //update
+ examSlot = await ExamSlotModel.findOneAndUpdate(
+ {course, branch, semester },
+ { $set: req.body });
+ return res.status(200).json({ message: 'Exam slot updated successfully', examSlot });
+ } else {
+ //create
+ examSlot = new ExamSlotModel(req.body);
+ await examSlot.save();
+ return res.status(201).json({ message: 'Exam slot created successfully', examSlot });
+ }
+ } catch (error) {
+ console.error('Error in createOrUpdateExamSlot:', error);
+ return res.status(500).json({ message: 'Server error', error: error.message });
+ }
+};
+
+export const getExamSlot = async (req, res) => {
+ try {
+ const {course, branch, semester } = req.params;
+
+ const branchFixed = branch.replace(/-/g, ' ');
+ const examSlot = await ExamSlotModel.findOne({course, branchFixed, semester });
+ if (!examSlot) {
+ return res.status(404).json({ message: 'Exam slot not found' });
+ }
+ return res.status(200).json(examSlot);
+ } catch (error) {
+ console.error('Error in getExamSlot:', error);
+ return res.status(500).json({ message: 'Server error', error: error.message });
+ }
+};
+
+export default {
+ createOrUpdateExamSlot,
+ getExamSlot
+}
\ No newline at end of file
diff --git a/server/modules/exam-slots/exam-slots.model.js b/server/modules/exam-slots/exam-slots.model.js
new file mode 100644
index 00000000..f8654cca
--- /dev/null
+++ b/server/modules/exam-slots/exam-slots.model.js
@@ -0,0 +1,52 @@
+import mongoose from 'mongoose';
+const { Schema, model } = mongoose;
+
+const courseSlotSchema = {
+ course: {
+ type: String,
+ required: true, // still required (probably needed for uniqueness)
+ },
+ branch: {
+ type: String,
+ required: true, // still required
+ },
+ semester: {
+ type: Number,
+ required: true, // still required
+ },
+
+ // Regular slots
+ A: { type: String, required: false },
+ A1: { type: String, required: false },
+ B: { type: String, required: false },
+ B1: { type: String, required: false },
+ C: { type: String, required: false },
+ C1: { type: String, required: false },
+ D: { type: String, required: false },
+ D1: { type: String, required: false },
+ E: { type: String, required: false },
+ E1: { type: String, required: false },
+ F: { type: String, required: false },
+ F1: { type: String, required: false },
+ G: { type: String, required: false },
+ G1: { type: String, required: false },
+
+ // ML slots
+ ML1: { type: String, required: false },
+ ML2: { type: String, required: false },
+ ML3: { type: String, required: false },
+ ML4: { type: String, required: false },
+ ML5: { type: String, required: false },
+
+ // AL slots
+ AL1: { type: String, required: false },
+ AL2: { type: String, required: false },
+ AL3: { type: String, required: false },
+ AL4: { type: String, required: false },
+ AL5: { type: String, required: false },
+};
+
+const courseSlotModel = new Schema(courseSlotSchema, { timestamps: true });
+
+const ExamSlotModel = model('ExamSlot', courseSlotModel);
+export default ExamSlotModel;
\ No newline at end of file
diff --git a/server/modules/exam-slots/exam-slots.routes.js b/server/modules/exam-slots/exam-slots.routes.js
new file mode 100644
index 00000000..ff3fc3d2
--- /dev/null
+++ b/server/modules/exam-slots/exam-slots.routes.js
@@ -0,0 +1,9 @@
+import express from 'express';
+import { createOrUpdateExamSlot, getExamSlot } from './exam-slots.controller.js';
+
+const router = express.Router();
+
+router.post('/update', createOrUpdateExamSlot);
+router.get('/:course/:branch/:semester', getExamSlot);
+
+export default router;
diff --git a/server/yarn.lock b/server/yarn.lock
index 586fa22f..4f24dbbb 100644
--- a/server/yarn.lock
+++ b/server/yarn.lock
@@ -2040,6 +2040,11 @@ fs.realpath@^1.0.0:
resolved "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz"
integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==
+fsevents@~2.3.2:
+ version "2.3.2"
+ resolved "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz"
+ integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==
+
function-bind@^1.1.1, function-bind@^1.1.2:
version "1.1.2"
resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz"