Spaces:
Running
Running
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>Timetable Creator</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> | |
.timetable-cell { | |
min-height: 80px; | |
transition: all 0.2s ease; | |
} | |
.timetable-cell:hover { | |
background-color: #f3f4f6; | |
} | |
.draggable-item { | |
cursor: grab; | |
user-select: none; | |
} | |
.draggable-item:active { | |
cursor: grabbing; | |
} | |
.dropzone { | |
transition: background-color 0.2s ease; | |
} | |
.dropzone.drag-over { | |
background-color: #e5e7eb; | |
} | |
.modal { | |
transition: opacity 0.2s ease, transform 0.2s ease; | |
} | |
.modal-hidden { | |
opacity: 0; | |
transform: translateY(20px); | |
pointer-events: none; | |
} | |
</style> | |
</head> | |
<body class="bg-gray-50 min-h-screen"> | |
<div class="container mx-auto px-4 py-8"> | |
<header class="mb-8"> | |
<h1 class="text-3xl font-bold text-indigo-700 mb-2">Timetable Creator</h1> | |
<p class="text-gray-600">Drag and drop subjects to create your perfect schedule</p> | |
</header> | |
<div class="grid grid-cols-1 lg:grid-cols-4 gap-6"> | |
<!-- Subjects Panel --> | |
<div class="bg-white rounded-lg shadow p-4 lg:col-span-1"> | |
<div class="flex justify-between items-center mb-4"> | |
<h2 class="text-lg font-semibold text-gray-800">Subjects</h2> | |
<button id="addSubjectBtn" class="bg-indigo-100 text-indigo-700 p-2 rounded-full hover:bg-indigo-200 transition"> | |
<i class="fas fa-plus"></i> | |
</button> | |
</div> | |
<div id="subjectsList" class="space-y-2"> | |
<!-- Subjects will be added here --> | |
</div> | |
</div> | |
<!-- Timetable --> | |
<div class="bg-white rounded-lg shadow p-4 lg:col-span-3"> | |
<div class="flex justify-between items-center mb-4"> | |
<h2 class="text-lg font-semibold text-gray-800">Weekly Schedule</h2> | |
<div class="flex space-x-2"> | |
<button id="saveTimetableBtn" class="bg-green-100 text-green-700 px-3 py-1 rounded hover:bg-green-200 transition"> | |
<i class="fas fa-save mr-1"></i> Save | |
</button> | |
<button id="clearTimetableBtn" class="bg-red-100 text-red-700 px-3 py-1 rounded hover:bg-red-200 transition"> | |
<i class="fas fa-trash mr-1"></i> Clear | |
</button> | |
</div> | |
</div> | |
<div class="overflow-x-auto"> | |
<table class="w-full border-collapse"> | |
<thead> | |
<tr class="bg-gray-100"> | |
<th class="p-2 w-24 font-medium text-gray-600">Time</th> | |
<th class="p-2 font-medium text-gray-600">Monday</th> | |
<th class="p-2 font-medium text-gray-600">Tuesday</th> | |
<th class="p-2 font-medium text-gray-600">Wednesday</th> | |
<th class="p-2 font-medium text-gray-600">Thursday</th> | |
<th class="p-2 font-medium text-gray-600">Friday</th> | |
</tr> | |
</thead> | |
<tbody id="timetableBody"> | |
<!-- Timetable rows will be generated here --> | |
</tbody> | |
</table> | |
</div> | |
</div> | |
</div> | |
<!-- Saved Timetables Section --> | |
<div class="mt-8 bg-white rounded-lg shadow p-4"> | |
<h2 class="text-lg font-semibold text-gray-800 mb-4">Saved Timetables</h2> | |
<div id="savedTimetables" class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4"> | |
<!-- Saved timetables will appear here --> | |
</div> | |
</div> | |
</div> | |
<!-- Add Subject Modal --> | |
<div id="subjectModal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center p-4 z-50 modal modal-hidden"> | |
<div class="bg-white rounded-lg shadow-xl w-full max-w-md"> | |
<div class="p-6"> | |
<h3 class="text-xl font-semibold text-gray-800 mb-4">Add New Subject</h3> | |
<div class="mb-4"> | |
<label for="subjectName" class="block text-sm font-medium text-gray-700 mb-1">Subject Name</label> | |
<input type="text" id="subjectName" class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-indigo-500"> | |
</div> | |
<div class="mb-4"> | |
<label for="subjectColor" class="block text-sm font-medium text-gray-700 mb-1">Color</label> | |
<select id="subjectColor" class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-indigo-500"> | |
<option value="bg-red-100 text-red-800">Red</option> | |
<option value="bg-blue-100 text-blue-800">Blue</option> | |
<option value="bg-green-100 text-green-800">Green</option> | |
<option value="bg-yellow-100 text-yellow-800">Yellow</option> | |
<option value="bg-purple-100 text-purple-800">Purple</option> | |
<option value="bg-pink-100 text-pink-800">Pink</option> | |
<option value="bg-indigo-100 text-indigo-800">Indigo</option> | |
</select> | |
</div> | |
<div class="flex justify-end space-x-3"> | |
<button id="cancelSubjectBtn" class="px-4 py-2 text-gray-600 hover:text-gray-800 transition">Cancel</button> | |
<button id="saveSubjectBtn" class="px-4 py-2 bg-indigo-600 text-white rounded-md hover:bg-indigo-700 transition">Add Subject</button> | |
</div> | |
</div> | |
</div> | |
</div> | |
<!-- Save Timetable Modal --> | |
<div id="saveModal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center p-4 z-50 modal modal-hidden"> | |
<div class="bg-white rounded-lg shadow-xl w-full max-w-md"> | |
<div class="p-6"> | |
<h3 class="text-xl font-semibold text-gray-800 mb-4">Save Timetable</h3> | |
<div class="mb-4"> | |
<label for="timetableName" class="block text-sm font-medium text-gray-700 mb-1">Timetable Name</label> | |
<input type="text" id="timetableName" class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-indigo-500" placeholder="e.g. Fall Semester 2023"> | |
</div> | |
<div class="flex justify-end space-x-3"> | |
<button id="cancelSaveBtn" class="px-4 py-2 text-gray-600 hover:text-gray-800 transition">Cancel</button> | |
<button id="confirmSaveBtn" class="px-4 py-2 bg-indigo-600 text-white rounded-md hover:bg-indigo-700 transition">Save</button> | |
</div> | |
</div> | |
</div> | |
</div> | |
<script> | |
document.addEventListener('DOMContentLoaded', function() { | |
// DOM Elements | |
const subjectsList = document.getElementById('subjectsList'); | |
const timetableBody = document.getElementById('timetableBody'); | |
const savedTimetables = document.getElementById('savedTimetables'); | |
const addSubjectBtn = document.getElementById('addSubjectBtn'); | |
const saveTimetableBtn = document.getElementById('saveTimetableBtn'); | |
const clearTimetableBtn = document.getElementById('clearTimetableBtn'); | |
const subjectModal = document.getElementById('subjectModal'); | |
const saveModal = document.getElementById('saveModal'); | |
const cancelSubjectBtn = document.getElementById('cancelSubjectBtn'); | |
const saveSubjectBtn = document.getElementById('saveSubjectBtn'); | |
const cancelSaveBtn = document.getElementById('cancelSaveBtn'); | |
const confirmSaveBtn = document.getElementById('confirmSaveBtn'); | |
const subjectNameInput = document.getElementById('subjectName'); | |
const subjectColorSelect = document.getElementById('subjectColor'); | |
const timetableNameInput = document.getElementById('timetableName'); | |
// State | |
let subjects = []; | |
let timetable = {}; | |
let draggedItem = null; | |
let currentCell = null; | |
// Initialize timetable | |
initTimetable(); | |
// Event Listeners | |
addSubjectBtn.addEventListener('click', showSubjectModal); | |
cancelSubjectBtn.addEventListener('click', hideSubjectModal); | |
saveSubjectBtn.addEventListener('click', addSubject); | |
saveTimetableBtn.addEventListener('click', showSaveModal); | |
cancelSaveBtn.addEventListener('click', hideSaveModal); | |
confirmSaveBtn.addEventListener('click', saveTimetable); | |
clearTimetableBtn.addEventListener('click', clearTimetable); | |
// Initialize with some sample subjects | |
addSampleSubjects(); | |
// Functions | |
function initTimetable() { | |
const timeSlots = [ | |
'8:00 - 9:00', '9:00 - 10:00', '10:00 - 11:00', | |
'11:00 - 12:00', '12:00 - 13:00', '13:00 - 14:00', | |
'14:00 - 15:00', '15:00 - 16:00', '16:00 - 17:00' | |
]; | |
timetableBody.innerHTML = ''; | |
timeSlots.forEach(time => { | |
const row = document.createElement('tr'); | |
// Time cell | |
const timeCell = document.createElement('td'); | |
timeCell.className = 'p-2 border border-gray-200 bg-gray-50 text-center text-sm text-gray-600'; | |
timeCell.textContent = time; | |
row.appendChild(timeCell); | |
// Day cells | |
for (let i = 0; i < 5; i++) { | |
const dayCell = document.createElement('td'); | |
dayCell.className = 'p-1 border border-gray-200 timetable-cell dropzone'; | |
dayCell.dataset.day = i; | |
dayCell.dataset.time = time; | |
// Drag and drop events | |
dayCell.addEventListener('dragover', handleDragOver); | |
dayCell.addEventListener('dragleave', handleDragLeave); | |
dayCell.addEventListener('drop', handleDrop); | |
dayCell.addEventListener('dragenter', handleDragEnter); | |
row.appendChild(dayCell); | |
} | |
timetableBody.appendChild(row); | |
}); | |
} | |
function addSampleSubjects() { | |
const sampleSubjects = [ | |
{ name: 'Mathematics', color: 'bg-blue-100 text-blue-800' }, | |
{ name: 'Physics', color: 'bg-red-100 text-red-800' }, | |
{ name: 'Chemistry', color: 'bg-green-100 text-green-800' }, | |
{ name: 'Biology', color: 'bg-purple-100 text-purple-800' }, | |
{ name: 'History', color: 'bg-yellow-100 text-yellow-800' }, | |
{ name: 'English', color: 'bg-pink-100 text-pink-800' } | |
]; | |
sampleSubjects.forEach(subject => { | |
addSubjectToDOM(subject); | |
subjects.push(subject); | |
}); | |
} | |
function showSubjectModal() { | |
subjectNameInput.value = ''; | |
subjectModal.classList.remove('modal-hidden'); | |
} | |
function hideSubjectModal() { | |
subjectModal.classList.add('modal-hidden'); | |
} | |
function showSaveModal() { | |
timetableNameInput.value = ''; | |
saveModal.classList.remove('modal-hidden'); | |
} | |
function hideSaveModal() { | |
saveModal.classList.add('modal-hidden'); | |
} | |
function addSubject() { | |
const name = subjectNameInput.value.trim(); | |
const color = subjectColorSelect.value; | |
if (name) { | |
const subject = { name, color }; | |
addSubjectToDOM(subject); | |
subjects.push(subject); | |
hideSubjectModal(); | |
} | |
} | |
function addSubjectToDOM(subject) { | |
const subjectElement = document.createElement('div'); | |
subjectElement.className = `p-3 rounded-md draggable-item ${subject.color} flex justify-between items-center`; | |
subjectElement.textContent = subject.name; | |
subjectElement.draggable = true; | |
subjectElement.dataset.subject = subject.name; | |
// Add drag icon | |
const dragIcon = document.createElement('span'); | |
dragIcon.className = 'text-gray-500 ml-2'; | |
dragIcon.innerHTML = '<i class="fas fa-grip-vertical"></i>'; | |
subjectElement.appendChild(dragIcon); | |
// Drag events | |
subjectElement.addEventListener('dragstart', handleDragStart); | |
subjectElement.addEventListener('dragend', handleDragEnd); | |
subjectsList.appendChild(subjectElement); | |
} | |
function handleDragStart(e) { | |
draggedItem = e.target; | |
e.target.classList.add('opacity-50'); | |
e.dataTransfer.setData('text/plain', e.target.dataset.subject); | |
} | |
function handleDragEnd(e) { | |
e.target.classList.remove('opacity-50'); | |
} | |
function handleDragOver(e) { | |
e.preventDefault(); | |
currentCell = e.target; | |
e.target.classList.add('drag-over'); | |
} | |
function handleDragEnter(e) { | |
e.preventDefault(); | |
} | |
function handleDragLeave(e) { | |
e.target.classList.remove('drag-over'); | |
} | |
function handleDrop(e) { | |
e.preventDefault(); | |
e.target.classList.remove('drag-over'); | |
const subjectName = e.dataTransfer.getData('text/plain'); | |
const subject = subjects.find(s => s.name === subjectName); | |
if (subject && currentCell) { | |
// Clear cell first | |
currentCell.innerHTML = ''; | |
// Add subject to cell | |
const subjectElement = document.createElement('div'); | |
subjectElement.className = `p-2 rounded ${subject.color} text-sm font-medium`; | |
subjectElement.textContent = subject.name; | |
// Add remove button | |
const removeBtn = document.createElement('button'); | |
removeBtn.className = 'float-right text-gray-500 hover:text-gray-700 ml-1'; | |
removeBtn.innerHTML = '<i class="fas fa-times"></i>'; | |
removeBtn.addEventListener('click', function() { | |
currentCell.innerHTML = ''; | |
}); | |
subjectElement.appendChild(removeBtn); | |
currentCell.appendChild(subjectElement); | |
// Save to timetable object | |
const day = currentCell.dataset.day; | |
const time = currentCell.dataset.time; | |
if (!timetable[day]) timetable[day] = {}; | |
timetable[day][time] = subject.name; | |
} | |
} | |
function saveTimetable() { | |
const name = timetableNameInput.value.trim(); | |
if (name) { | |
const timetableData = { | |
name, | |
data: timetable, | |
createdAt: new Date().toISOString() | |
}; | |
// Save to localStorage | |
let saved = JSON.parse(localStorage.getItem('timetables') || '[]'); | |
saved.push(timetableData); | |
localStorage.setItem('timetables', JSON.stringify(saved)); | |
// Update UI | |
displaySavedTimetables(); | |
hideSaveModal(); | |
// Show success message | |
alert('Timetable saved successfully!'); | |
} | |
} | |
function displaySavedTimetables() { | |
const saved = JSON.parse(localStorage.getItem('timetables') || '[]'); | |
savedTimetables.innerHTML = ''; | |
if (saved.length === 0) { | |
savedTimetables.innerHTML = '<p class="text-gray-500">No saved timetables yet.</p>'; | |
return; | |
} | |
saved.forEach((timetable, index) => { | |
const card = document.createElement('div'); | |
card.className = 'bg-white border border-gray-200 rounded-lg p-4 hover:shadow-md transition'; | |
const name = document.createElement('h3'); | |
name.className = 'font-medium text-lg text-gray-800 mb-2'; | |
name.textContent = timetable.name; | |
const date = document.createElement('p'); | |
date.className = 'text-sm text-gray-500 mb-3'; | |
date.textContent = new Date(timetable.createdAt).toLocaleString(); | |
const actions = document.createElement('div'); | |
actions.className = 'flex justify-between'; | |
const loadBtn = document.createElement('button'); | |
loadBtn.className = 'bg-indigo-100 text-indigo-700 px-3 py-1 rounded text-sm hover:bg-indigo-200 transition'; | |
loadBtn.innerHTML = '<i class="fas fa-upload mr-1"></i> Load'; | |
loadBtn.addEventListener('click', () => loadTimetable(index)); | |
const deleteBtn = document.createElement('button'); | |
deleteBtn.className = 'bg-red-100 text-red-700 px-3 py-1 rounded text-sm hover:bg-red-200 transition'; | |
deleteBtn.innerHTML = '<i class="fas fa-trash mr-1"></i> Delete'; | |
deleteBtn.addEventListener('click', () => deleteTimetable(index)); | |
actions.appendChild(loadBtn); | |
actions.appendChild(deleteBtn); | |
card.appendChild(name); | |
card.appendChild(date); | |
card.appendChild(actions); | |
savedTimetables.appendChild(card); | |
}); | |
} | |
function loadTimetable(index) { | |
const saved = JSON.parse(localStorage.getItem('timetables') || '[]'); | |
if (saved[index]) { | |
// Clear current timetable | |
clearTimetable(); | |
// Load the saved timetable | |
const loadedTimetable = saved[index].data; | |
timetable = loadedTimetable; | |
// Update the UI | |
for (const day in loadedTimetable) { | |
for (const time in loadedTimetable[day]) { | |
const subjectName = loadedTimetable[day][time]; | |
const subject = subjects.find(s => s.name === subjectName); | |
if (subject) { | |
const cells = document.querySelectorAll(`[data-day="${day}"][data-time="${time}"]`); | |
if (cells.length > 0) { | |
const cell = cells[0]; | |
cell.innerHTML = ''; | |
const subjectElement = document.createElement('div'); | |
subjectElement.className = `p-2 rounded ${subject.color} text-sm font-medium`; | |
subjectElement.textContent = subject.name; | |
// Add remove button | |
const removeBtn = document.createElement('button'); | |
removeBtn.className = 'float-right text-gray-500 hover:text-gray-700 ml-1'; | |
removeBtn.innerHTML = '<i class="fas fa-times"></i>'; | |
removeBtn.addEventListener('click', function() { | |
cell.innerHTML = ''; | |
delete timetable[day][time]; | |
}); | |
subjectElement.appendChild(removeBtn); | |
cell.appendChild(subjectElement); | |
} | |
} | |
} | |
} | |
alert('Timetable loaded successfully!'); | |
} | |
} | |
function deleteTimetable(index) { | |
if (confirm('Are you sure you want to delete this timetable?')) { | |
const saved = JSON.parse(localStorage.getItem('timetables') || '[]'); | |
saved.splice(index, 1); | |
localStorage.setItem('timetables', JSON.stringify(saved)); | |
displaySavedTimetables(); | |
} | |
} | |
function clearTimetable() { | |
if (confirm('Are you sure you want to clear the current timetable?')) { | |
// Clear UI | |
const cells = document.querySelectorAll('.timetable-cell'); | |
cells.forEach(cell => { | |
cell.innerHTML = ''; | |
}); | |
// Clear data | |
timetable = {}; | |
} | |
} | |
// Initial display of saved timetables | |
displaySavedTimetables(); | |
}); | |
</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=theharby/timetable-project" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
</html> |