document.addEventListener('DOMContentLoaded', function() { // Initialize app state if (!localStorage.getItem('shiftMasterData')) { localStorage.setItem('shiftMasterData', JSON.stringify({ workers: [], shifts: [], schedule: [] })); } // Load data from localStorage let appData = JSON.parse(localStorage.getItem('shiftMasterData')); // DOM Elements const addWorkerBtn = document.getElementById('addWorkerBtn'); const workerForm = document.getElementById('workerForm'); const saveWorker = document.getElementById('saveWorker'); const cancelWorker = document.getElementById('cancelWorker'); const workersList = document.getElementById('workersList'); const addShiftBtn = document.getElementById('addShiftBtn'); const shiftForm = document.getElementById('shiftForm'); const saveShift = document.getElementById('saveShift'); const cancelShift = document.getElementById('cancelShift'); const shiftsList = document.getElementById('shiftsList'); const generateSchedule = document.getElementById('generateSchedule'); const scheduleResults = document.getElementById('scheduleResults'); const scheduleTableBody = document.getElementById('scheduleTableBody'); const saveSchedule = document.getElementById('saveSchedule'); // Worker Management addWorkerBtn.addEventListener('click', () => { workerForm.classList.toggle('hidden'); }); cancelWorker.addEventListener('click', () => { workerForm.classList.add('hidden'); document.getElementById('workerName').value = ''; document.querySelectorAll('#workerForm input[type="checkbox"]').forEach(checkbox => { checkbox.checked = false; }); document.getElementById('shiftPreference').value = 'any'; }); saveWorker.addEventListener('click', () => { const name = document.getElementById('workerName').value.trim(); const daysAvailable = Array.from(document.querySelectorAll('#workerForm input[type="checkbox"]:checked')) .map(checkbox => checkbox.id); const shiftPreference = document.getElementById('shiftPreference').value; if (!name) { alert('Please enter a worker name'); return; } const newWorker = { id: Date.now().toString(), name, daysAvailable, shiftPreference }; appData.workers.push(newWorker); saveData(); renderWorkers(); workerForm.classList.add('hidden'); document.getElementById('workerName').value = ''; document.querySelectorAll('#workerForm input[type="checkbox"]').forEach(checkbox => { checkbox.checked = false; }); document.getElementById('shiftPreference').value = 'any'; }); // Shift Management addShiftBtn.addEventListener('click', () => { shiftForm.classList.toggle('hidden'); }); cancelShift.addEventListener('click', () => { shiftForm.classList.add('hidden'); document.getElementById('shiftDate').value = ''; document.getElementById('shiftStart').value = ''; document.getElementById('shiftEnd').value = ''; document.getElementById('shiftRole').value = ''; }); saveShift.addEventListener('click', () => { const date = document.getElementById('shiftDate').value; const start = document.getElementById('shiftStart').value; const end = document.getElementById('shiftEnd').value; const role = document.getElementById('shiftRole').value.trim(); if (!date || !start || !end || !role) { alert('Please fill all shift details'); return; } const newShift = { id: Date.now().toString(), date, start, end, role, assignedWorker: null }; appData.shifts.push(newShift); saveData(); renderShifts(); shiftForm.classList.add('hidden'); document.getElementById('shiftDate').value = ''; document.getElementById('shiftStart').value = ''; document.getElementById('shiftEnd').value = ''; document.getElementById('shiftRole').value = ''; }); // Schedule Generation generateSchedule.addEventListener('click', () => { if (appData.workers.length === 0 || appData.shifts.length === 0) { alert('Please add workers and shifts first'); return; } // Show loading state const spinner = generateSchedule.querySelector('i'); spinner.classList.remove('hidden'); generateSchedule.disabled = true; // Simulate processing time setTimeout(() => { generateScheduleAlgorithm(); spinner.classList.add('hidden'); generateSchedule.disabled = false; scheduleResults.classList.remove('hidden'); }, 1000); }); saveSchedule.addEventListener('click', () => { saveData(); alert('Schedule saved successfully!'); }); // Helper Functions function saveData() { localStorage.setItem('shiftMasterData', JSON.stringify(appData)); } function renderWorkers() { if (appData.workers.length === 0) { workersList.innerHTML = '

No workers added yet

'; return; } workersList.innerHTML = ''; appData.workers.forEach(worker => { const days = worker.daysAvailable.length > 0 ? worker.daysAvailable.join(', ') : 'Not specified'; const preference = worker.shiftPreference === 'any' ? 'Any shift' : worker.shiftPreference.charAt(0).toUpperCase() + worker.shiftPreference.slice(1); const workerElement = document.createElement('div'); workerElement.className = 'worker-card bg-gray-50 p-3 rounded-lg border border-gray-200'; workerElement.innerHTML = `

${worker.name}

Available: ${days}

Prefers: ${preference}

`; workersList.appendChild(workerElement); }); // Add event listeners to delete buttons document.querySelectorAll('.delete-worker').forEach(button => { button.addEventListener('click', function() { const workerId = this.getAttribute('data-id'); appData.workers = appData.workers.filter(worker => worker.id !== workerId); saveData(); renderWorkers(); }); }); feather.replace(); } function renderShifts() { if (appData.shifts.length === 0) { shiftsList.innerHTML = '

No shifts created yet

'; return; } shiftsList.innerHTML = ''; appData.shifts.forEach(shift => { const dateObj = new Date(shift.date); const formattedDate = dateObj.toLocaleDateString('en-US', { weekday: 'short', month: 'short', day: 'numeric' }); const shiftElement = document.createElement('div'); shiftElement.className = 'shift-card bg-gray-50 p-3 rounded-lg border border-gray-200'; shiftElement.innerHTML = `

${formattedDate}

${shift.start} - ${shift.end}

Role: ${shift.role}

${shift.assignedWorker ? `

Assigned: ${getWorkerName(shift.assignedWorker)}

` : ''}
`; shiftsList.appendChild(shiftElement); }); // Add event listeners to delete buttons document.querySelectorAll('.delete-shift').forEach(button => { button.addEventListener('click', function() { const shiftId = this.getAttribute('data-id'); appData.shifts = appData.shifts.filter(shift => shift.id !== shiftId); saveData(); renderShifts(); }); }); feather.replace(); } function getWorkerName(workerId) { const worker = appData.workers.find(w => w.id === workerId); return worker ? worker.name : 'Unknown'; } function generateScheduleAlgorithm() { // Reset all assignments appData.shifts.forEach(shift => { shift.assignedWorker = null; }); // Sort shifts by date and time const sortedShifts = [...appData.shifts].sort((a, b) => { const dateCompare = new Date(a.date) - new Date(b.date); if (dateCompare !== 0) return dateCompare; return a.start.localeCompare(b.start); }); // Track assigned workers to prevent double-booking const assignedWorkers = new Set(); // Assign workers to shifts sortedShifts.forEach(shift => { // Get the day of the week for this shift const shiftDate = new Date(shift.date); const dayOfWeek = ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday'][shiftDate.getDay()]; // Determine shift type (morning, afternoon, night) const startHour = parseInt(shift.start.split(':')[0]); let shiftType = 'afternoon'; if (startHour < 12) shiftType = 'morning'; else if (startHour >= 22 || startHour < 6) shiftType = 'night'; // Find available workers who: // 1. Are available on this day // 2. Match the shift preference (or have "any") // 3. Haven't been assigned to another shift in this schedule const availableWorkers = appData.workers.filter(worker => { return worker.daysAvailable.includes(dayOfWeek) && (worker.shiftPreference === 'any' || worker.shiftPreference === shiftType) && !assignedWorkers.has(worker.id); }); // If we found available workers, assign the first one if (availableWorkers.length > 0) { const assignedWorker = availableWorkers[0]; shift.assignedWorker = assignedWorker.id; assignedWorkers.add(assignedWorker.id); } }); // Render the schedule renderSchedule(); saveData(); } function renderSchedule() { scheduleTableBody.innerHTML = ''; // Group shifts by date const shiftsByDate = {}; appData.shifts.forEach(shift => { const date = shift.date; if (!shiftsByDate[date]) { shiftsByDate[date] = []; } shiftsByDate[date].push(shift); }); // Sort dates chronologically const sortedDates = Object.keys(shiftsByDate).sort((a, b) => new Date(a) - new Date(b)); // Create table rows sortedDates.forEach(date => { const dateObj = new Date(date); const formattedDate = dateObj.toLocaleDateString('en-US', { weekday: 'long', month: 'long', day: 'numeric' }); shiftsByDate[date].forEach(shift => { const row = document.createElement('tr'); row.innerHTML = ` ${formattedDate} ${shift.start} - ${shift.end} ${shift.role} ${shift.assignedWorker ? getWorkerName(shift.assignedWorker) : 'Unassigned'} `; scheduleTableBody.appendChild(row); }); }); } // Initial render renderWorkers(); renderShifts(); });