Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Academic Calendar - Dark Mode</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
| <style> | |
| @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap'); | |
| body { | |
| font-family: 'Inter', sans-serif; | |
| background-color: #0f172a; | |
| color: #e2e8f0; | |
| } | |
| .calendar-day { | |
| transition: all 0.2s ease; | |
| } | |
| .calendar-day:hover { | |
| background-color: #1e293b; | |
| } | |
| .calendar-day.today { | |
| border: 2px solid #3b82f6; | |
| } | |
| .calendar-day.has-events::after { | |
| content: ''; | |
| position: absolute; | |
| bottom: 8px; | |
| left: 50%; | |
| transform: translateX(-50%); | |
| width: 6px; | |
| height: 6px; | |
| border-radius: 50%; | |
| background-color: #3b82f6; | |
| } | |
| .assignment-card { | |
| transition: all 0.2s ease; | |
| border-left: 4px solid; | |
| } | |
| .assignment-card:hover { | |
| transform: translateY(-2px); | |
| box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.3); | |
| } | |
| .assignment-card.urgent { | |
| border-left-color: #ef4444; | |
| } | |
| .assignment-card.upcoming { | |
| border-left-color: #f59e0b; | |
| } | |
| .assignment-card.completed { | |
| opacity: 0.7; | |
| border-left-color: #10b981; | |
| } | |
| .checkbox-container input:checked ~ .checkmark { | |
| background-color: #3b82f6; | |
| border-color: #3b82f6; | |
| } | |
| .checkbox-container input:checked ~ .checkmark:after { | |
| display: block; | |
| } | |
| .checkbox-container .checkmark:after { | |
| left: 6px; | |
| top: 2px; | |
| width: 5px; | |
| height: 10px; | |
| border: solid white; | |
| border-width: 0 2px 2px 0; | |
| transform: rotate(45deg); | |
| } | |
| .flatpickr-input { | |
| background-color: #1e293b ; | |
| color: #e2e8f0 ; | |
| border-color: #334155 ; | |
| } | |
| .flatpickr-day.selected { | |
| background-color: #3b82f6 ; | |
| border-color: #3b82f6 ; | |
| } | |
| .flatpickr-calendar { | |
| background-color: #1e293b ; | |
| border-color: #334155 ; | |
| } | |
| .flatpickr-day { | |
| color: #e2e8f0 ; | |
| } | |
| .flatpickr-day:hover { | |
| background-color: #334155 ; | |
| } | |
| .flatpickr-day.today { | |
| border-color: #3b82f6 ; | |
| } | |
| .flatpickr-time input { | |
| color: #e2e8f0 ; | |
| } | |
| .color-option { | |
| transition: transform 0.2s ease; | |
| } | |
| .color-option:hover { | |
| transform: scale(1.1); | |
| } | |
| .color-option.selected { | |
| transform: scale(1.1); | |
| box-shadow: 0 0 0 2px #3b82f6; | |
| } | |
| </style> | |
| </head> | |
| <body class="min-h-screen"> | |
| <div class="container mx-auto px-4 py-8"> | |
| <div class="flex flex-col lg:flex-row gap-6"> | |
| <!-- Sidebar --> | |
| <div class="w-full lg:w-64 bg-slate-800 rounded-xl p-4 h-fit"> | |
| <h1 class="text-2xl font-bold mb-6 flex items-center"> | |
| <i class="fas fa-calendar-days mr-2 text-blue-400"></i> | |
| Academic Calendar | |
| </h1> | |
| <div class="mb-6"> | |
| <button id="add-assignment-btn" class="w-full flex items-center justify-center py-2 px-4 bg-blue-600 hover:bg-blue-700 text-white rounded-lg transition"> | |
| <i class="fas fa-plus mr-2"></i> Add Assignment | |
| </button> | |
| </div> | |
| <div class="mb-6"> | |
| <h3 class="font-medium text-slate-300 mb-2">Classes</h3> | |
| <div id="classes-list" class="space-y-2"> | |
| <!-- Classes will be added here --> | |
| </div> | |
| <button id="add-class-btn" class="mt-3 text-sm text-blue-400 hover:text-blue-300 flex items-center"> | |
| <i class="fas fa-plus mr-1"></i> Add Class | |
| </button> | |
| </div> | |
| <div class="mb-6"> | |
| <h3 class="font-medium text-slate-300 mb-2">Filters</h3> | |
| <div class="space-y-1"> | |
| <button id="filter-all" class="w-full text-left px-3 py-1 text-sm bg-blue-600 text-white rounded-lg">All Assignments</button> | |
| <button id="filter-upcoming" class="w-full text-left px-3 py-1 text-sm bg-slate-700 hover:bg-slate-600 text-slate-200 rounded-lg">Upcoming</button> | |
| <button id="filter-urgent" class="w-full text-left px-3 py-1 text-sm bg-slate-700 hover:bg-slate-600 text-slate-200 rounded-lg">Due Soon</button> | |
| <button id="filter-completed" class="w-full text-left px-3 py-1 text-sm bg-slate-700 hover:bg-slate-600 text-slate-200 rounded-lg">Completed</button> | |
| </div> | |
| </div> | |
| <div class="pt-4 border-t border-slate-700"> | |
| <div class="flex items-center space-x-3"> | |
| <div class="w-8 h-8 rounded-full bg-blue-900 flex items-center justify-center text-blue-400"> | |
| <i class="fas fa-user"></i> | |
| </div> | |
| <div> | |
| <div class="font-medium" id="username">Student</div> | |
| <div class="text-xs text-slate-400">Viewing Calendar</div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Main Content --> | |
| <div class="flex-1"> | |
| <!-- Calendar Header --> | |
| <div class="bg-slate-800 rounded-xl p-4 mb-6 flex flex-col sm:flex-row justify-between items-center"> | |
| <div class="flex items-center mb-4 sm:mb-0"> | |
| <h2 id="current-month-year" class="text-xl font-semibold mr-4">Month Year</h2> | |
| <div class="flex space-x-2"> | |
| <button id="prev-month" class="p-2 rounded-lg bg-slate-700 hover:bg-slate-600"> | |
| <i class="fas fa-chevron-left"></i> | |
| </button> | |
| <button id="today" class="px-3 py-2 rounded-lg bg-slate-700 hover:bg-slate-600 text-sm"> | |
| Today | |
| </button> | |
| <button id="next-month" class="p-2 rounded-lg bg-slate-700 hover:bg-slate-600"> | |
| <i class="fas fa-chevron-right"></i> | |
| </button> | |
| </div> | |
| </div> | |
| <div class="flex items-center space-x-2"> | |
| <div class="relative"> | |
| <input type="text" id="search-assignments" placeholder="Search assignments..." class="pl-10 pr-4 py-2 bg-slate-700 border border-slate-600 rounded-lg focus:ring-blue-500 focus:border-blue-500 w-full sm:w-64"> | |
| <i class="fas fa-search absolute left-3 top-3 text-slate-400"></i> | |
| </div> | |
| <button id="view-toggle" class="p-2 rounded-lg bg-slate-700 hover:bg-slate-600"> | |
| <i class="fas fa-list" id="view-icon"></i> | |
| </button> | |
| </div> | |
| </div> | |
| <!-- Calendar View --> | |
| <div id="calendar-view"> | |
| <!-- Weekday Headers --> | |
| <div class="grid grid-cols-7 gap-1 mb-2"> | |
| <div class="text-center font-medium text-slate-400 py-2">Sun</div> | |
| <div class="text-center font-medium text-slate-400 py-2">Mon</div> | |
| <div class="text-center font-medium text-slate-400 py-2">Tue</div> | |
| <div class="text-center font-medium text-slate-400 py-2">Wed</div> | |
| <div class="text-center font-medium text-slate-400 py-2">Thu</div> | |
| <div class="text-center font-medium text-slate-400 py-2">Fri</div> | |
| <div class="text-center font-medium text-slate-400 py-2">Sat</div> | |
| </div> | |
| <!-- Calendar Grid --> | |
| <div id="calendar-grid" class="grid grid-cols-7 gap-1"> | |
| <!-- Days will be populated by JavaScript --> | |
| </div> | |
| </div> | |
| <!-- List View (Hidden by default) --> | |
| <div id="list-view" class="hidden"> | |
| <div class="bg-slate-800 rounded-xl p-4 mb-4"> | |
| <h3 class="text-lg font-semibold mb-4">Assignments</h3> | |
| <div id="assignments-container" class="space-y-3"> | |
| <!-- Assignments will be added here --> | |
| </div> | |
| <div id="no-assignments" class="text-center py-12"> | |
| <i class="fas fa-tasks text-4xl text-slate-600 mb-3"></i> | |
| <h4 class="text-lg font-medium text-slate-400">No assignments yet</h4> | |
| <p class="text-slate-500 mt-1">Add your first assignment by clicking the "Add Assignment" button</p> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Day View (Hidden by default) --> | |
| <div id="day-view" class="hidden"> | |
| <div class="bg-slate-800 rounded-xl p-4"> | |
| <div class="flex justify-between items-center mb-4"> | |
| <h3 id="day-view-date" class="text-lg font-semibold">Day, Month Date</h3> | |
| <button id="close-day-view" class="p-2 rounded-lg bg-slate-700 hover:bg-slate-600"> | |
| <i class="fas fa-times"></i> | |
| </button> | |
| </div> | |
| <div id="day-assignments" class="space-y-3"> | |
| <!-- Day-specific assignments will be added here --> | |
| </div> | |
| <div id="no-day-assignments" class="text-center py-12"> | |
| <i class="fas fa-calendar-day text-4xl text-slate-600 mb-3"></i> | |
| <h4 class="text-lg font-medium text-slate-400">No assignments this day</h4> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Add Class Modal --> | |
| <div id="add-class-modal" class="fixed inset-0 bg-black bg-opacity-70 flex items-center justify-center z-50 hidden"> | |
| <div class="bg-slate-800 rounded-xl shadow-xl w-full max-w-md"> | |
| <div class="p-4 border-b border-slate-700 flex justify-between items-center"> | |
| <h3 class="text-lg font-semibold">Add New Class</h3> | |
| <button id="close-class-modal" class="text-slate-400 hover:text-slate-300"> | |
| <i class="fas fa-times"></i> | |
| </button> | |
| </div> | |
| <div class="p-6"> | |
| <div class="mb-4"> | |
| <label for="class-name" class="block text-sm font-medium text-slate-300 mb-1">Class Name</label> | |
| <input type="text" id="class-name" class="w-full px-3 py-2 bg-slate-700 border border-slate-600 rounded-lg focus:ring-blue-500 focus:border-blue-500" placeholder="e.g. Math 101"> | |
| </div> | |
| <div class="mb-4"> | |
| <label for="class-color" class="block text-sm font-medium text-slate-300 mb-2">Color Theme</label> | |
| <div class="flex flex-wrap gap-3"> | |
| <div class="color-option w-8 h-8 rounded-full bg-red-500 cursor-pointer" data-color="red"></div> | |
| <div class="color-option w-8 h-8 rounded-full bg-blue-500 cursor-pointer" data-color="blue"></div> | |
| <div class="color-option w-8 h-8 rounded-full bg-green-500 cursor-pointer" data-color="green"></div> | |
| <div class="color-option w-8 h-8 rounded-full bg-yellow-500 cursor-pointer" data-color="yellow"></div> | |
| <div class="color-option w-8 h-8 rounded-full bg-purple-500 cursor-pointer" data-color="purple"></div> | |
| <div class="color-option w-8 h-8 rounded-full bg-pink-500 cursor-pointer" data-color="pink"></div> | |
| <div class="color-option w-8 h-8 rounded-full bg-indigo-500 cursor-pointer" data-color="indigo"></div> | |
| <div class="color-option w-8 h-8 rounded-full bg-teal-500 cursor-pointer" data-color="teal"></div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="p-4 border-t border-slate-700 flex justify-end space-x-3"> | |
| <button id="cancel-class" class="px-4 py-2 border border-slate-600 text-slate-300 rounded-lg hover:bg-slate-700">Cancel</button> | |
| <button id="save-class" class="px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg">Save Class</button> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Add Assignment Modal --> | |
| <div id="add-assignment-modal" class="fixed inset-0 bg-black bg-opacity-70 flex items-center justify-center z-50 hidden"> | |
| <div class="bg-slate-800 rounded-xl shadow-xl w-full max-w-md"> | |
| <div class="p-4 border-b border-slate-700 flex justify-between items-center"> | |
| <h3 class="text-lg font-semibold">Add New Assignment</h3> | |
| <button id="close-assignment-modal" class="text-slate-400 hover:text-slate-300"> | |
| <i class="fas fa-times"></i> | |
| </button> | |
| </div> | |
| <div class="p-6"> | |
| <div class="mb-4"> | |
| <label for="assignment-title" class="block text-sm font-medium text-slate-300 mb-1">Title</label> | |
| <input type="text" id="assignment-title" class="w-full px-3 py-2 bg-slate-700 border border-slate-600 rounded-lg focus:ring-blue-500 focus:border-blue-500" placeholder="e.g. Chapter 5 Homework"> | |
| </div> | |
| <div class="mb-4"> | |
| <label for="assignment-class" class="block text-sm font-medium text-slate-300 mb-1">Class</label> | |
| <select id="assignment-class" class="w-full px-3 py-2 bg-slate-700 border border-slate-600 rounded-lg focus:ring-blue-500 focus:border-blue-500"> | |
| <!-- Classes will be populated here --> | |
| </select> | |
| </div> | |
| <div class="mb-4"> | |
| <label for="assignment-due-date" class="block text-sm font-medium text-slate-300 mb-1">Due Date</label> | |
| <input type="text" id="assignment-due-date" class="w-full px-3 py-2 bg-slate-700 border border-slate-600 rounded-lg focus:ring-blue-500 focus:border-blue-500 flatpickr-input" placeholder="Select date"> | |
| </div> | |
| <div class="mb-4"> | |
| <label for="assignment-description" class="block text-sm font-medium text-slate-300 mb-1">Description</label> | |
| <textarea id="assignment-description" rows="3" class="w-full px-3 py-2 bg-slate-700 border border-slate-600 rounded-lg focus:ring-blue-500 focus:border-blue-500" placeholder="Add details about the assignment..."></textarea> | |
| </div> | |
| </div> | |
| <div class="p-4 border-t border-slate-700 flex justify-end space-x-3"> | |
| <button id="cancel-assignment" class="px-4 py-2 border border-slate-600 text-slate-300 rounded-lg hover:bg-slate-700">Cancel</button> | |
| <button id="save-assignment" class="px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg">Save Assignment</button> | |
| </div> | |
| </div> | |
| </div> | |
| <script src="https://cdn.jsdelivr.net/npm/flatpickr"></script> | |
| <script> | |
| // Data storage | |
| let classes = []; | |
| let assignments = []; | |
| let currentDate = new Date(); | |
| let currentView = 'calendar'; // 'calendar', 'list', or 'day' | |
| let selectedDay = null; | |
| let selectedColor = 'blue'; | |
| let currentFilter = 'all'; | |
| // DOM elements | |
| const classesList = document.getElementById('classes-list'); | |
| const currentMonthYear = document.getElementById('current-month-year'); | |
| const calendarGrid = document.getElementById('calendar-grid'); | |
| const assignmentsContainer = document.getElementById('assignments-container'); | |
| const noAssignments = document.getElementById('no-assignments'); | |
| const dayAssignments = document.getElementById('day-assignments'); | |
| const noDayAssignments = document.getElementById('no-day-assignments'); | |
| const dayViewDate = document.getElementById('day-view-date'); | |
| const addClassBtn = document.getElementById('add-class-btn'); | |
| const addAssignmentBtn = document.getElementById('add-assignment-btn'); | |
| const prevMonthBtn = document.getElementById('prev-month'); | |
| const nextMonthBtn = document.getElementById('next-month'); | |
| const todayBtn = document.getElementById('today'); | |
| const viewToggleBtn = document.getElementById('view-toggle'); | |
| const viewIcon = document.getElementById('view-icon'); | |
| const calendarView = document.getElementById('calendar-view'); | |
| const listView = document.getElementById('list-view'); | |
| const dayView = document.getElementById('day-view'); | |
| const closeDayViewBtn = document.getElementById('close-day-view'); | |
| const searchAssignments = document.getElementById('search-assignments'); | |
| const filterAll = document.getElementById('filter-all'); | |
| const filterUpcoming = document.getElementById('filter-upcoming'); | |
| const filterUrgent = document.getElementById('filter-urgent'); | |
| const filterCompleted = document.getElementById('filter-completed'); | |
| const addClassModal = document.getElementById('add-class-modal'); | |
| const addAssignmentModal = document.getElementById('add-assignment-modal'); | |
| const closeClassModal = document.getElementById('close-class-modal'); | |
| const closeAssignmentModal = document.getElementById('close-assignment-modal'); | |
| const cancelClass = document.getElementById('cancel-class'); | |
| const cancelAssignment = document.getElementById('cancel-assignment'); | |
| const saveClass = document.getElementById('save-class'); | |
| const saveAssignment = document.getElementById('save-assignment'); | |
| const classNameInput = document.getElementById('class-name'); | |
| const assignmentTitleInput = document.getElementById('assignment-title'); | |
| const assignmentClassInput = document.getElementById('assignment-class'); | |
| const assignmentDueDateInput = document.getElementById('assignment-due-date'); | |
| const assignmentDescriptionInput = document.getElementById('assignment-description'); | |
| const colorOptions = document.querySelectorAll('.color-option'); | |
| // Initialize date picker | |
| flatpickr("#assignment-due-date", { | |
| enableTime: false, | |
| dateFormat: "Y-m-d", | |
| minDate: "today" | |
| }); | |
| // Event listeners | |
| document.addEventListener('DOMContentLoaded', () => { | |
| loadFromLocalStorage(); | |
| renderCalendar(); | |
| renderClasses(); | |
| updateView(); | |
| // Set default color selection | |
| document.querySelector('.color-option[data-color="blue"]').classList.add('selected'); | |
| }); | |
| addClassBtn.addEventListener('click', () => { | |
| addClassModal.classList.remove('hidden'); | |
| classNameInput.focus(); | |
| }); | |
| addAssignmentBtn.addEventListener('click', () => { | |
| if (classes.length === 0) { | |
| alert('Please add at least one class first.'); | |
| addClassModal.classList.remove('hidden'); | |
| return; | |
| } | |
| addAssignmentModal.classList.remove('hidden'); | |
| assignmentTitleInput.focus(); | |
| populateClassDropdown(); | |
| }); | |
| prevMonthBtn.addEventListener('click', () => { | |
| currentDate.setMonth(currentDate.getMonth() - 1); | |
| renderCalendar(); | |
| }); | |
| nextMonthBtn.addEventListener('click', () => { | |
| currentDate.setMonth(currentDate.getMonth() + 1); | |
| renderCalendar(); | |
| }); | |
| todayBtn.addEventListener('click', () => { | |
| currentDate = new Date(); | |
| renderCalendar(); | |
| }); | |
| viewToggleBtn.addEventListener('click', () => { | |
| if (currentView === 'calendar') { | |
| currentView = 'list'; | |
| viewIcon.classList.remove('fa-list'); | |
| viewIcon.classList.add('fa-calendar'); | |
| } else { | |
| currentView = 'calendar'; | |
| viewIcon.classList.remove('fa-calendar'); | |
| viewIcon.classList.add('fa-list'); | |
| } | |
| updateView(); | |
| }); | |
| closeDayViewBtn.addEventListener('click', () => { | |
| currentView = 'calendar'; | |
| updateView(); | |
| }); | |
| closeClassModal.addEventListener('click', () => { | |
| addClassModal.classList.add('hidden'); | |
| }); | |
| closeAssignmentModal.addEventListener('click', () => { | |
| addAssignmentModal.classList.add('hidden'); | |
| }); | |
| cancelClass.addEventListener('click', () => { | |
| addClassModal.classList.add('hidden'); | |
| }); | |
| cancelAssignment.addEventListener('click', () => { | |
| addAssignmentModal.classList.add('hidden'); | |
| }); | |
| saveClass.addEventListener('click', () => { | |
| const name = classNameInput.value.trim(); | |
| if (name) { | |
| addClass(name, selectedColor); | |
| classNameInput.value = ''; | |
| addClassModal.classList.add('hidden'); | |
| } | |
| }); | |
| saveAssignment.addEventListener('click', () => { | |
| const title = assignmentTitleInput.value.trim(); | |
| const classId = assignmentClassInput.value; | |
| const dueDate = assignmentDueDateInput.value; | |
| const description = assignmentDescriptionInput.value.trim(); | |
| if (title && dueDate && classId) { | |
| addAssignment(classId, title, dueDate, description); | |
| assignmentTitleInput.value = ''; | |
| assignmentDueDateInput.value = ''; | |
| assignmentDescriptionInput.value = ''; | |
| addAssignmentModal.classList.add('hidden'); | |
| // If we're in day view and the assignment is for a different day, stay in calendar view | |
| if (currentView === 'day') { | |
| const assignmentDate = new Date(dueDate); | |
| if (assignmentDate.toDateString() !== selectedDay.toDateString()) { | |
| currentView = 'calendar'; | |
| updateView(); | |
| } | |
| } | |
| renderCalendar(); | |
| } | |
| }); | |
| colorOptions.forEach(option => { | |
| option.addEventListener('click', () => { | |
| colorOptions.forEach(opt => opt.classList.remove('selected')); | |
| option.classList.add('selected'); | |
| selectedColor = option.dataset.color; | |
| }); | |
| }); | |
| searchAssignments.addEventListener('input', () => { | |
| if (currentView === 'list') { | |
| renderAssignments(); | |
| } else if (currentView === 'day') { | |
| renderDayAssignments(selectedDay); | |
| } | |
| }); | |
| filterAll.addEventListener('click', () => { | |
| currentFilter = 'all'; | |
| updateFilterButtons(); | |
| if (currentView === 'list') { | |
| renderAssignments(); | |
| } else if (currentView === 'day') { | |
| renderDayAssignments(selectedDay); | |
| } | |
| }); | |
| filterUpcoming.addEventListener('click', () => { | |
| currentFilter = 'upcoming'; | |
| updateFilterButtons(); | |
| if (currentView === 'list') { | |
| renderAssignments(); | |
| } else if (currentView === 'day') { | |
| renderDayAssignments(selectedDay); | |
| } | |
| }); | |
| filterUrgent.addEventListener('click', () => { | |
| currentFilter = 'urgent'; | |
| updateFilterButtons(); | |
| if (currentView === 'list') { | |
| renderAssignments(); | |
| } else if (currentView === 'day') { | |
| renderDayAssignments(selectedDay); | |
| } | |
| }); | |
| filterCompleted.addEventListener('click', () => { | |
| currentFilter = 'completed'; | |
| updateFilterButtons(); | |
| if (currentView === 'list') { | |
| renderAssignments(); | |
| } else if (currentView === 'day') { | |
| renderDayAssignments(selectedDay); | |
| } | |
| }); | |
| // Functions | |
| function addClass(name, color) { | |
| const id = Date.now().toString(); | |
| const newClass = { id, name, color }; | |
| classes.push(newClass); | |
| saveToLocalStorage(); | |
| renderClasses(); | |
| } | |
| function addAssignment(classId, title, dueDate, description) { | |
| const id = Date.now().toString(); | |
| const newAssignment = { | |
| id, | |
| classId, | |
| title, | |
| dueDate, | |
| description, | |
| completed: false, | |
| createdAt: new Date().toISOString() | |
| }; | |
| assignments.push(newAssignment); | |
| saveToLocalStorage(); | |
| if (currentView === 'list') { | |
| renderAssignments(); | |
| } else if (currentView === 'day') { | |
| renderDayAssignments(selectedDay); | |
| } | |
| renderCalendar(); | |
| } | |
| function toggleAssignmentCompletion(assignmentId) { | |
| const assignment = assignments.find(a => a.id === assignmentId); | |
| if (assignment) { | |
| assignment.completed = !assignment.completed; | |
| saveToLocalStorage(); | |
| if (currentView === 'list') { | |
| renderAssignments(); | |
| } else if (currentView === 'day') { | |
| renderDayAssignments(selectedDay); | |
| } | |
| renderCalendar(); | |
| } | |
| } | |
| function deleteAssignment(assignmentId) { | |
| if (confirm('Are you sure you want to delete this assignment?')) { | |
| assignments = assignments.filter(a => a.id !== assignmentId); | |
| saveToLocalStorage(); | |
| if (currentView === 'list') { | |
| renderAssignments(); | |
| } else if (currentView === 'day') { | |
| renderDayAssignments(selectedDay); | |
| } | |
| renderCalendar(); | |
| } | |
| } | |
| function renderCalendar() { | |
| const year = currentDate.getFullYear(); | |
| const month = currentDate.getMonth(); | |
| currentMonthYear.textContent = `${getMonthName(month)} ${year}`; | |
| // Get first day of month and last date of month | |
| const firstDay = new Date(year, month, 1); | |
| const lastDay = new Date(year, month + 1, 0); | |
| // Get days from previous month to show | |
| const prevMonthDays = firstDay.getDay(); // 0 = Sunday, 6 = Saturday | |
| // Get days from next month to show | |
| const nextMonthDays = 6 - lastDay.getDay(); | |
| // Calculate total days to show (always 6 weeks) | |
| const totalDays = prevMonthDays + lastDay.getDate() + nextMonthDays; | |
| calendarGrid.innerHTML = ''; | |
| // Previous month days | |
| const prevMonthLastDay = new Date(year, month, 0).getDate(); | |
| for (let i = prevMonthDays - 1; i >= 0; i--) { | |
| const day = prevMonthLastDay - i; | |
| const date = new Date(year, month - 1, day); | |
| renderCalendarDay(date, true); | |
| } | |
| // Current month days | |
| for (let day = 1; day <= lastDay.getDate(); day++) { | |
| const date = new Date(year, month, day); | |
| renderCalendarDay(date, false); | |
| } | |
| // Next month days | |
| for (let day = 1; day <= nextMonthDays; day++) { | |
| const date = new Date(year, month + 1, day); | |
| renderCalendarDay(date, true); | |
| } | |
| } | |
| function renderCalendarDay(date, isOtherMonth) { | |
| const day = date.getDate(); | |
| const month = date.getMonth(); | |
| const year = date.getFullYear(); | |
| const today = new Date(); | |
| const dayElement = document.createElement('div'); | |
| dayElement.className = `calendar-day relative bg-slate-800 rounded-lg p-2 h-24 overflow-y-auto ${isOtherMonth ? 'opacity-50' : ''} ${date.toDateString() === today.toDateString() ? 'today' : ''}`; | |
| // Check if this day has any assignments | |
| const dayAssignments = assignments.filter(a => { | |
| const dueDate = new Date(a.dueDate); | |
| return dueDate.getDate() === day && | |
| dueDate.getMonth() === month && | |
| dueDate.getFullYear() === year; | |
| }); | |
| if (dayAssignments.length > 0) { | |
| dayElement.classList.add('has-events'); | |
| // Add color indicators for each class with assignments | |
| const classColors = {}; | |
| dayAssignments.forEach(a => { | |
| const cls = classes.find(c => c.id === a.classId); | |
| if (cls) { | |
| classColors[cls.color] = true; | |
| } | |
| }); | |
| const colorsContainer = document.createElement('div'); | |
| colorsContainer.className = 'flex space-x-1 absolute top-2 right-2'; | |
| Object.keys(classColors).forEach(color => { | |
| const colorDot = document.createElement('div'); | |
| colorDot.className = `w-2 h-2 rounded-full bg-${color}-500`; | |
| colorsContainer.appendChild(colorDot); | |
| }); | |
| dayElement.appendChild(colorsContainer); | |
| } | |
| dayElement.innerHTML += ` | |
| <div class="text-right font-medium mb-1">${day}</div> | |
| `; | |
| // Add click event to show day view | |
| dayElement.addEventListener('click', () => { | |
| selectedDay = date; | |
| currentView = 'day'; | |
| updateView(); | |
| renderDayAssignments(date); | |
| }); | |
| calendarGrid.appendChild(dayElement); | |
| } | |
| function renderClasses() { | |
| classesList.innerHTML = ''; | |
| if (classes.length === 0) { | |
| classesList.innerHTML = ` | |
| <div class="text-center py-2 text-slate-400 text-sm"> | |
| <p>No classes yet</p> | |
| </div> | |
| `; | |
| return; | |
| } | |
| classes.forEach(cls => { | |
| const classItem = document.createElement('div'); | |
| classItem.className = 'flex items-center p-2 rounded-lg hover:bg-slate-700 cursor-pointer'; | |
| classItem.innerHTML = ` | |
| <div class="w-3 h-3 rounded-full bg-${cls.color}-500 mr-2"></div> | |
| <div class="font-medium text-slate-200 truncate flex-1">${cls.name}</div> | |
| <div class="text-xs text-slate-400">${getAssignmentCount(cls.id)}</div> | |
| `; | |
| classesList.appendChild(classItem); | |
| }); | |
| } | |
| function renderAssignments() { | |
| const searchTerm = searchAssignments.value.toLowerCase(); | |
| let filteredAssignments = [...assignments]; | |
| // Apply search filter | |
| if (searchTerm) { | |
| filteredAssignments = filteredAssignments.filter(a => | |
| a.title.toLowerCase().includes(searchTerm) || | |
| (a.description && a.description.toLowerCase().includes(searchTerm)) | |
| ); | |
| } | |
| // Apply status filter | |
| if (currentFilter === 'upcoming') { | |
| filteredAssignments = filteredAssignments.filter(a => !a.completed && !isDueSoon(a.dueDate)); | |
| } else if (currentFilter === 'urgent') { | |
| filteredAssignments = filteredAssignments.filter(a => !a.completed && isDueSoon(a.dueDate)); | |
| } else if (currentFilter === 'completed') { | |
| filteredAssignments = filteredAssignments.filter(a => a.completed); | |
| } | |
| // Sort by due date (soonest first) and then by creation date (newest first) | |
| filteredAssignments.sort((a, b) => { | |
| if (a.completed !== b.completed) { | |
| return a.completed ? 1 : -1; | |
| } | |
| const dateA = new Date(a.dueDate); | |
| const dateB = new Date(b.dueDate); | |
| if (dateA < dateB) return -1; | |
| if (dateA > dateB) return 1; | |
| const createdA = new Date(a.createdAt); | |
| const createdB = new Date(b.createdAt); | |
| return createdB - createdA; | |
| }); | |
| assignmentsContainer.innerHTML = ''; | |
| if (filteredAssignments.length === 0) { | |
| noAssignments.classList.remove('hidden'); | |
| return; | |
| } | |
| noAssignments.classList.add('hidden'); | |
| filteredAssignments.forEach(assignment => { | |
| const dueDate = new Date(assignment.dueDate); | |
| const today = new Date(); | |
| const timeDiff = dueDate.getTime() - today.getTime(); | |
| const daysDiff = Math.ceil(timeDiff / (1000 * 3600 * 24)); | |
| let statusClass = ''; | |
| let statusText = ''; | |
| if (assignment.completed) { | |
| statusClass = 'completed'; | |
| statusText = 'Completed'; | |
| } else if (daysDiff <= 3) { | |
| statusClass = 'urgent'; | |
| statusText = `Due in ${daysDiff} day${daysDiff !== 1 ? 's' : ''}`; | |
| } else { | |
| statusClass = 'upcoming'; | |
| statusText = `Due in ${daysDiff} day${daysDiff !== 1 ? 's' : ''}`; | |
| } | |
| const classInfo = classes.find(c => c.id === assignment.classId); | |
| const classColor = classInfo ? classInfo.color : 'blue'; | |
| const assignmentCard = document.createElement('div'); | |
| assignmentCard.className = `assignment-card ${statusClass} bg-slate-700 p-4 rounded-lg shadow-sm border-l-${classColor}-500`; | |
| assignmentCard.innerHTML = ` | |
| <div class="flex items-start justify-between"> | |
| <div class="flex items-start space-x-3"> | |
| <label class="checkbox-container relative block cursor-pointer"> | |
| <input type="checkbox" ${assignment.completed ? 'checked' : ''} class="absolute opacity-0 h-0 w-0"> | |
| <span class="checkmark absolute top-0 left-0 h-5 w-5 border border-slate-500 rounded"></span> | |
| </label> | |
| <div class="flex-1"> | |
| <div class="flex items-center"> | |
| <div class="w-3 h-3 rounded-full bg-${classColor}-500 mr-2"></div> | |
| <h4 class="font-medium ${assignment.completed ? 'line-through text-slate-400' : 'text-slate-200'}">${assignment.title}</h4> | |
| </div> | |
| ${assignment.description ? `<p class="text-sm text-slate-400 mt-1">${assignment.description}</p>` : ''} | |
| <div class="flex items-center mt-2 text-sm"> | |
| <i class="far fa-calendar-alt text-slate-500 mr-1"></i> | |
| <span class="text-slate-400">${formatDate(assignment.dueDate)}</span> | |
| <span class="ml-3 px-2 py-0.5 text-xs rounded-full ${statusClass === 'urgent' ? 'bg-red-900 text-red-300' : statusClass === 'completed' ? 'bg-green-900 text-green-300' : 'bg-blue-900 text-blue-300'}">${statusText}</span> | |
| </div> | |
| </div> | |
| </div> | |
| <button class="delete-assignment text-slate-500 hover:text-red-400"> | |
| <i class="far fa-trash-alt"></i> | |
| </button> | |
| </div> | |
| `; | |
| const checkbox = assignmentCard.querySelector('input[type="checkbox"]'); | |
| checkbox.addEventListener('change', () => toggleAssignmentCompletion(assignment.id)); | |
| const deleteBtn = assignmentCard.querySelector('.delete-assignment'); | |
| deleteBtn.addEventListener('click', (e) => { | |
| e.stopPropagation(); | |
| deleteAssignment(assignment.id); | |
| }); | |
| assignmentsContainer.appendChild(assignmentCard); | |
| }); | |
| } | |
| function renderDayAssignments(date) { | |
| const day = date.getDate(); | |
| const month = date.getMonth(); | |
| const year = date.getFullYear(); | |
| dayViewDate.textContent = date.toLocaleDateString(undefined, { weekday: 'long', month: 'long', day: 'numeric' }); | |
| let dayAssignmentsList = assignments.filter(a => { | |
| const dueDate = new Date(a.dueDate); | |
| return dueDate.getDate() === day && | |
| dueDate.getMonth() === month && | |
| dueDate.getFullYear() === year; | |
| }); | |
| // Apply search filter | |
| const searchTerm = searchAssignments.value.toLowerCase(); | |
| if (searchTerm) { | |
| dayAssignmentsList = dayAssignmentsList.filter(a => | |
| a.title.toLowerCase().includes(searchTerm) || | |
| (a.description && a.description.toLowerCase().includes(searchTerm)) | |
| ); | |
| } | |
| // Apply status filter | |
| if (currentFilter === 'upcoming') { | |
| dayAssignmentsList = dayAssignmentsList.filter(a => !a.completed && !isDueSoon(a.dueDate)); | |
| } else if (currentFilter === 'urgent') { | |
| dayAssignmentsList = dayAssignmentsList.filter(a => !a.completed && isDueSoon(a.dueDate)); | |
| } else if (currentFilter === 'completed') { | |
| dayAssignmentsList = dayAssignmentsList.filter(a => a.completed); | |
| } | |
| // Sort by completion status and then by creation date | |
| dayAssignmentsList.sort((a, b) => { | |
| if (a.completed !== b.completed) { | |
| return a.completed ? 1 : -1; | |
| } | |
| const createdA = new Date(a.createdAt); | |
| const createdB = new Date(b.createdAt); | |
| return createdA - createdB; | |
| }); | |
| dayAssignments.innerHTML = ''; | |
| if (dayAssignmentsList.length === 0) { | |
| noDayAssignments.classList.remove('hidden'); | |
| return; | |
| } | |
| noDayAssignments.classList.add('hidden'); | |
| dayAssignmentsList.forEach(assignment => { | |
| const classInfo = classes.find(c => c.id === assignment.classId); | |
| const classColor = classInfo ? classInfo.color : 'blue'; | |
| const className = classInfo ? classInfo.name : 'Unknown Class'; | |
| const assignmentCard = document.createElement('div'); | |
| assignmentCard.className = `assignment-card ${assignment.completed ? 'completed' : 'upcoming'} bg-slate-700 p-4 rounded-lg shadow-sm border-l-${classColor}-500 mb-3`; | |
| assignmentCard.innerHTML = ` | |
| <div class="flex items-start justify-between"> | |
| <div class="flex items-start space-x-3"> | |
| <label class="checkbox-container relative block cursor-pointer"> | |
| <input type="checkbox" ${assignment.completed ? 'checked' : ''} class="absolute opacity-0 h-0 w-0"> | |
| <span class="checkmark absolute top-0 left-0 h-5 w-5 border border-slate-500 rounded"></span> | |
| </label> | |
| <div class="flex-1"> | |
| <div class="flex items-center"> | |
| <div class="w-3 h-3 rounded-full bg-${classColor}-500 mr-2"></div> | |
| <h4 class="font-medium ${assignment.completed ? 'line-through text-slate-400' : 'text-slate-200'}">${assignment.title}</h4> | |
| </div> | |
| <div class="text-xs text-slate-400 mt-1">${className}</div> | |
| ${assignment.description ? `<p class="text-sm text-slate-400 mt-2">${assignment.description}</p>` : ''} | |
| <div class="flex items-center mt-3 text-sm"> | |
| <i class="far fa-clock text-slate-500 mr-1"></i> | |
| <span class="text-slate-400">Due: ${formatTime(new Date(assignment.dueDate))}</span> | |
| </div> | |
| </div> | |
| </div> | |
| <button class="delete-assignment text-slate-500 hover:text-red-400"> | |
| <i class="far fa-trash-alt"></i> | |
| </button> | |
| </div> | |
| `; | |
| const checkbox = assignmentCard.querySelector('input[type="checkbox"]'); | |
| checkbox.addEventListener('change', () => toggleAssignmentCompletion(assignment.id)); | |
| const deleteBtn = assignmentCard.querySelector('.delete-assignment'); | |
| deleteBtn.addEventListener('click', (e) => { | |
| e.stopPropagation(); | |
| deleteAssignment(assignment.id); | |
| }); | |
| dayAssignments.appendChild(assignmentCard); | |
| }); | |
| } | |
| function populateClassDropdown() { | |
| assignmentClassInput.innerHTML = ''; | |
| if (classes.length === 0) { | |
| assignmentClassInput.innerHTML = '<option value="">No classes available</option>'; | |
| return; | |
| } | |
| classes.forEach(cls => { | |
| const option = document.createElement('option'); | |
| option.value = cls.id; | |
| option.textContent = cls.name; | |
| assignmentClassInput.appendChild(option); | |
| }); | |
| } | |
| function updateView() { | |
| if (currentView === 'calendar') { | |
| calendarView.classList.remove('hidden'); | |
| listView.classList.add('hidden'); | |
| dayView.classList.add('hidden'); | |
| viewIcon.classList.remove('fa-calendar'); | |
| viewIcon.classList.add('fa-list'); | |
| } else if (currentView === 'list') { | |
| calendarView.classList.add('hidden'); | |
| listView.classList.remove('hidden'); | |
| dayView.classList.add('hidden'); | |
| viewIcon.classList.remove('fa-list'); | |
| viewIcon.classList.add('fa-calendar'); | |
| renderAssignments(); | |
| } else if (currentView === 'day') { | |
| calendarView.classList.add('hidden'); | |
| listView.classList.add('hidden'); | |
| dayView.classList.remove('hidden'); | |
| viewIcon.classList.remove('fa-list'); | |
| viewIcon.classList.add('fa-calendar'); | |
| } | |
| } | |
| function updateFilterButtons() { | |
| filterAll.classList.remove('bg-blue-600', 'text-white'); | |
| filterUpcoming.classList.remove('bg-blue-600', 'text-white'); | |
| filterUrgent.classList.remove('bg-blue-600', 'text-white'); | |
| filterCompleted.classList.remove('bg-blue-600', 'text-white'); | |
| filterAll.classList.add('bg-slate-700', 'text-slate-200'); | |
| filterUpcoming.classList.add('bg-slate-700', 'text-slate-200'); | |
| filterUrgent.classList.add('bg-slate-700', 'text-slate-200'); | |
| filterCompleted.classList.add('bg-slate-700', 'text-slate-200'); | |
| if (currentFilter === 'all') { | |
| filterAll.classList.remove('bg-slate-700', 'text-slate-200'); | |
| filterAll.classList.add('bg-blue-600', 'text-white'); | |
| } else if (currentFilter === 'upcoming') { | |
| filterUpcoming.classList.remove('bg-slate-700', 'text-slate-200'); | |
| filterUpcoming.classList.add('bg-blue-600', 'text-white'); | |
| } else if (currentFilter === 'urgent') { | |
| filterUrgent.classList.remove('bg-slate-700', 'text-slate-200'); | |
| filterUrgent.classList.add('bg-blue-600', 'text-white'); | |
| } else if (currentFilter === 'completed') { | |
| filterCompleted.classList.remove('bg-slate-700', 'text-slate-200'); | |
| filterCompleted.classList.add('bg-blue-600', 'text-white'); | |
| } | |
| } | |
| function getAssignmentCount(classId) { | |
| return assignments.filter(a => a.classId === classId).length; | |
| } | |
| function isDueSoon(dueDate) { | |
| const date = new Date(dueDate); | |
| const today = new Date(); | |
| const timeDiff = date.getTime() - today.getTime(); | |
| const daysDiff = Math.ceil(timeDiff / (1000 * 3600 * 24)); | |
| return daysDiff <= 3; | |
| } | |
| function formatDate(dateString) { | |
| const options = { year: 'numeric', month: 'short', day: 'numeric' }; | |
| return new Date(dateString).toLocaleDateString(undefined, options); | |
| } | |
| function formatTime(date) { | |
| return date.toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit' }); | |
| } | |
| function getMonthName(monthIndex) { | |
| const months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']; | |
| return months[monthIndex]; | |
| } | |
| function saveToLocalStorage() { | |
| localStorage.setItem('classes', JSON.stringify(classes)); | |
| localStorage.setItem('assignments', JSON.stringify(assignments)); | |
| } | |
| function loadFromLocalStorage() { | |
| const savedClasses = localStorage.getItem('classes'); | |
| const savedAssignments = localStorage.getItem('assignments'); | |
| if (savedClasses) { | |
| classes = JSON.parse(savedClasses); | |
| } | |
| if (savedAssignments) { | |
| assignments = JSON.parse(savedAssignments); | |
| } | |
| } | |
| </script> | |
| <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=tsbetterworkpmo/project" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
| </html> |