Here is a comprehensive prompt you can use. This is designed to be pasted into an AI coding assistant (like Gemini, ChatGPT, or Claude) to generate a working software prototype (MVP).
f21cbdd verified Download script.js from edensheiko/shiftmaster-pro-scheduler: direct link, hf CLI and curl.
- Browser
- Download file 14 kB
-
https://huggingface.co/spaces/edensheiko/shiftmaster-pro-scheduler/resolve/main/script.js
- Command line
-
hf download hf://spaces/edensheiko/shiftmaster-pro-scheduler/script.js
-
curl -L -o script.js https://huggingface.co/spaces/edensheiko/shiftmaster-pro-scheduler/resolve/main/script.js
14 kB
| 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 = '<p class="text-gray-500 italic">No workers added yet</p>'; | |
| 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 = ` | |
| <div class="flex justify-between items-start"> | |
| <div> | |
| <h3 class="font-medium text-gray-800">${worker.name}</h3> | |
| <p class="text-sm text-gray-600 mt-1"><span class="font-medium">Available:</span> ${days}</p> | |
| <p class="text-sm text-gray-600"><span class="font-medium">Prefers:</span> ${preference}</p> | |
| </div> | |
| <button class="delete-worker text-red-500 hover:text-red-700" data-id="${worker.id}"> | |
| <i data-feather="trash-2" class="w-4 h-4"></i> | |
| </button> | |
| </div> | |
| `; | |
| 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 = '<p class="text-gray-500 italic">No shifts created yet</p>'; | |
| 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 = ` | |
| <div class="flex justify-between items-start"> | |
| <div> | |
| <h3 class="font-medium text-gray-800">${formattedDate}</h3> | |
| <p class="text-sm text-gray-600 mt-1">${shift.start} - ${shift.end}</p> | |
| <p class="text-sm text-gray-600"><span class="font-medium">Role:</span> ${shift.role}</p> | |
| ${shift.assignedWorker ? `<p class="text-sm text-green-600 mt-1"><span class="font-medium">Assigned:</span> ${getWorkerName(shift.assignedWorker)}</p>` : ''} | |
| </div> | |
| <div class="flex space-x-2"> | |
| <button class="edit-shift text-blue-500 hover:text-blue-700" data-id="${shift.id}"> | |
| <i data-feather="edit" class="w-4 h-4"></i> | |
| </button> | |
| <button class="delete-shift text-red-500 hover:text-red-700" data-id="${shift.id}"> | |
| <i data-feather="trash-2" class="w-4 h-4"></i> | |
| </button> | |
| </div> | |
| </div> | |
| `; | |
| 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 = ` | |
| <td class="px-4 py-3 whitespace-nowrap text-sm text-gray-700">${formattedDate}</td> | |
| <td class="px-4 py-3 whitespace-nowrap text-sm text-gray-700">${shift.start} - ${shift.end}</td> | |
| <td class="px-4 py-3 whitespace-nowrap text-sm text-gray-700">${shift.role}</td> | |
| <td class="px-4 py-3 whitespace-nowrap text-sm ${shift.assignedWorker ? 'text-green-600 font-medium' : 'text-red-500'}"> | |
| ${shift.assignedWorker ? getWorkerName(shift.assignedWorker) : 'Unassigned'} | |
| </td> | |
| `; | |
| scheduleTableBody.appendChild(row); | |
| }); | |
| }); | |
| } | |
| // Initial render | |
| renderWorkers(); | |
| renderShifts(); | |
| }); |