import React, { useState } from 'react'; const EventScheduleGenerator = () => { const supervisors = ['Белер Гаел', 'Георги Русков', 'Добромир Попов']; const participants = [ 'Белер Гаел', 'Георги Русков', 'Добромир Попов', 'Джуанна Рускова', 'Краси Трендафилова', 'Анджелика Трендафилова', 'Роксана Мирзоева', 'Абигейл Мустафова', 'Цветелина Нихтянова', 'Клеманс Белер', 'Златка Рускова', 'Сара Ръслер', 'Таня Иванова', 'Естера Биела', 'Радина Ейер' ]; const timeSlots = [ '08:00-10:40', '10:40-12:15', '12:15-14:50', '14:50-16:30', '16:30-19:00' ]; const days = ['Петък', 'Събота', 'Неделя']; // availabilities: 0 = not set, 1 = available (green), 2 = unavailable (red) const [availabilities, setAvailabilities] = useState(() => { const initial = {}; participants.forEach(participant => { initial[participant] = {}; days.forEach(day => { initial[participant][day] = {}; timeSlots.forEach(slot => { initial[participant][day][slot] = 0; }); }); }); return initial; }); const [peoplePerShift, setPeoplePerShift] = useState(1); const [scheduleOptions, setScheduleOptions] = useState([]); const cycleAvailability = (participant, day, slot) => { setAvailabilities(prev => ({ ...prev, [participant]: { ...prev[participant], [day]: { ...prev[participant][day], [slot]: (prev[participant][day][slot] + 1) % 3 } } })); }; const setAllAvailable = () => { const newAvailabilities = {}; participants.forEach(participant => { newAvailabilities[participant] = {}; days.forEach(day => { newAvailabilities[participant][day] = {}; timeSlots.forEach(slot => { newAvailabilities[participant][day][slot] = 1; }); }); }); setAvailabilities(newAvailabilities); }; const setAllUnavailable = () => { const newAvailabilities = {}; participants.forEach(participant => { newAvailabilities[participant] = {}; days.forEach(day => { newAvailabilities[participant][day] = {}; timeSlots.forEach(slot => { newAvailabilities[participant][day][slot] = 2; }); }); }); setAvailabilities(newAvailabilities); }; const clearAll = () => { const newAvailabilities = {}; participants.forEach(participant => { newAvailabilities[participant] = {}; days.forEach(day => { newAvailabilities[participant][day] = {}; timeSlots.forEach(slot => { newAvailabilities[participant][day][slot] = 0; }); }); }); setAvailabilities(newAvailabilities); }; const setPersonAllAvailable = (participant) => { setAvailabilities(prev => ({ ...prev, [participant]: { ...prev[participant], ...days.reduce((dayAcc, day) => ({ ...dayAcc, [day]: { ...prev[participant][day], ...timeSlots.reduce((slotAcc, slot) => ({ ...slotAcc, [slot]: 1 }), {}) } }), {}) } })); }; const setPersonAllUnavailable = (participant) => { setAvailabilities(prev => ({ ...prev, [participant]: { ...prev[participant], ...days.reduce((dayAcc, day) => ({ ...dayAcc, [day]: { ...prev[participant][day], ...timeSlots.reduce((slotAcc, slot) => ({ ...slotAcc, [slot]: 2 }), {}) } }), {}) } })); }; const setDayAllAvailable = (day) => { setAvailabilities(prev => { const newAvailabilities = { ...prev }; participants.forEach(participant => { timeSlots.forEach(slot => { newAvailabilities[participant][day][slot] = 1; }); }); return newAvailabilities; }); }; const getCellColor = (availability) => { switch(availability) { case 1: return 'bg-green-500 hover:bg-green-600'; case 2: return 'bg-red-500 hover:bg-red-600'; default: return 'bg-gray-200 hover:bg-gray-300'; } }; const getCellText = (availability) => { switch(availability) { case 1: return '✓'; case 2: return '✗'; default: return ''; } }; const generateSchedules = () => { const options = []; for (let optionNum = 0; optionNum < 3; optionNum++) { const schedule = {}; days.forEach(day => { schedule[day] = {}; timeSlots.forEach(slot => { schedule[day][slot] = []; }); }); const strategies = [ 'balanced', 'supervisor_priority', 'random' ]; const strategy = strategies[optionNum]; const assignmentCount = {}; participants.forEach(p => assignmentCount[p] = 0); // Create all possible assignments const allSlots = []; days.forEach(day => { timeSlots.forEach(slot => { allSlots.push({ day, slot }); }); }); if (strategy === 'supervisor_priority') { // First ensure each slot has at least one supervisor if possible allSlots.forEach(({ day, slot }) => { const availableSupervisors = supervisors.filter(sup => availabilities[sup]?.[day]?.[slot] === 1 ); if (availableSupervisors.length > 0 && schedule[day][slot].length < peoplePerShift) { // Choose supervisor with least assignments const chosenSupervisor = availableSupervisors.reduce((min, sup) => assignmentCount[sup] < assignmentCount[min] ? sup : min ); schedule[day][slot].push(chosenSupervisor); assignmentCount[chosenSupervisor]++; } }); } // Fill remaining slots if (strategy === 'balanced') { // Sort slots by current assignment count, fill least filled first allSlots.sort((a, b) => schedule[a.day][a.slot].length - schedule[b.day][b.slot].length); allSlots.forEach(({ day, slot }) => { while (schedule[day][slot].length < peoplePerShift) { const availableParticipants = participants.filter(p => availabilities[p]?.[day]?.[slot] === 1 && !schedule[day][slot].includes(p) ); if (availableParticipants.length === 0) break; // Sort by assignment count (least assigned first) availableParticipants.sort((a, b) => assignmentCount[a] - assignmentCount[b]); const chosen = availableParticipants[0]; schedule[day][slot].push(chosen); assignmentCount[chosen]++; } }); } else if (strategy === 'supervisor_priority') { // Continue filling with other participants allSlots.forEach(({ day, slot }) => { while (schedule[day][slot].length < peoplePerShift) { const availableParticipants = participants.filter(p => availabilities[p]?.[day]?.[slot] === 1 && !schedule[day][slot].includes(p) ); if (availableParticipants.length === 0) break; // Prioritize supervisors, then by assignment count availableParticipants.sort((a, b) => { const aIsSupervisor = supervisors.includes(a) ? 0 : 1; const bIsSupervisor = supervisors.includes(b) ? 0 : 1; if (aIsSupervisor !== bIsSupervisor) return aIsSupervisor - bIsSupervisor; return assignmentCount[a] - assignmentCount[b]; }); const chosen = availableParticipants[0]; schedule[day][slot].push(chosen); assignmentCount[chosen]++; } }); } else { // Random strategy allSlots.sort(() => Math.random() - 0.5); allSlots.forEach(({ day, slot }) => { while (schedule[day][slot].length < peoplePerShift) { const availableParticipants = participants.filter(p => availabilities[p]?.[day]?.[slot] === 1 && !schedule[day][slot].includes(p) ); if (availableParticipants.length === 0) break; const chosen = availableParticipants[Math.floor(Math.random() * availableParticipants.length)]; schedule[day][slot].push(chosen); assignmentCount[chosen]++; } }); } options.push({ id: optionNum + 1, strategy: strategy, schedule: schedule, stats: assignmentCount }); } setScheduleOptions(options); }; const getStrategyName = (strategy) => { switch(strategy) { case 'balanced': return 'Балансиран'; case 'supervisor_priority': return 'Приоритет супервайзори'; case 'random': return 'Случаен'; default: return strategy; } }; const ScheduleDisplay = ({ option }) => (

Вариант {option.id} ({getStrategyName(option.strategy)})

{days.map(day => ( ))} {timeSlots.map(slot => ( {days.map(day => ( ))} ))}
Време{day}
{slot} {option.schedule[day][slot].map(person => (
{person}
))} {option.schedule[day][slot].length === 0 && (
Няма назначени
)}
{option.schedule[day][slot].length}/{peoplePerShift}
Статистика за назначения:
{Object.entries(option.stats).map(([person, count]) => (
{person}: {count}
))}
); return (

Генератор на График за Събитие

{/* People per shift control */}

Настройки на графика:

(По подразбиране: 1 човек на смяна)

Наличности на участниците

{/* Global Controls */}

Бързи команди:

Маркирай цял ден като наличен:

{days.map(day => ( ))}

Инструкции: Кликнете върху клетките, за да зададете наличност:

Не е зададено
Наличен (зелено)
Неналичен (червено)
{days.map(day => ( ))} {days.map(day => timeSlots.map(slot => ( )) )} {participants.map(participant => ( {days.map(day => timeSlots.map(slot => ( )) )} ))}
Участник {day} Действия
{slot.split('-')[0]}
{participant} {supervisors.includes(participant) &&
(Супервайзор)
}
{scheduleOptions.length > 0 && (

Предложени Варианти

Легенда: Супервайзорите са маркирани в синьо с удебелен шрифт и рамка. Останалите участници са в зелено. Показва се {peoplePerShift} {peoplePerShift === 1 ? 'човек' : 'души'} на смяна. Числото под всяка клетка показва колко души са назначени от максимума.

{scheduleOptions.map(option => ( ))}
)}
); }; export default EventScheduleGenerator;