edensheiko's picture
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
Raw History Blame Contribute Delete
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();
});