Renaissance Faire Performance Schedule Builder

Renaissance Faire Performance Schedule Builder

Renaissance Faire Schedule Builder

Define all performers and stages before creating the schedule.

Add Performer / Troupe

Current Performers

    Add Stage / Venue

    Current Stages

      Add performances below. Ensure start times do not overlap on the same stage.

      Schedule Performance

      Full Schedule Overview

      Time End Time Stage Performer Duration (min) Actions

      No performances scheduled yet.

      No performances scheduled yet.

      `; return; } // Check for conflicts and mark rows const scheduleWithConflicts = sortedSchedule.map(slot => { const slotStart = timeToMinutes(slot.time); const slotEnd = slotStart + slot.duration; const isConflict = sortedSchedule.some(other => { if (slot.id === other.id || slot.stageId !== other.stageId) return false; const otherStart = timeToMinutes(other.time); const otherEnd = otherStart + other.duration; // Check for overlap: [Start1 < End2] AND [End1 > Start2] return (slotStart < otherEnd && slotEnd > otherStart); }); return { ...slot, isConflict }; }); scheduleWithConflicts.forEach(slot => { const tr = document.createElement('tr'); tr.dataset.id = slot.id; tr.classList.toggle('bg-red-100', slot.isConflict); tr.classList.toggle('border-red-500', slot.isConflict); const endTime = calculateEndTime(slot.time, slot.duration); const stageName = findName(slot.stageId, stages); const performerName = findName(slot.performerId, performers); tr.innerHTML = ` ${slot.time} ${endTime} ${stageName} ${performerName} ${slot.duration} ${slot.isConflict ? `` : ''} `; scheduleTableBody.appendChild(tr); }); // Update button state (for edit mode) scheduleSaveBtn.classList.toggle('rfsb-btn-primary', !editIdInput.value); scheduleSaveBtn.classList.toggle('rfsb-btn-secondary', !!editIdInput.value); scheduleSaveBtn.innerHTML = editIdInput.value ? ' Update Slot' : ' Add Slot'; } /** Adds/Updates a performance slot */ window.rfsbAddPerformance = () => { if (!scheduleForm || !scheduleTime || !scheduleDuration || !scheduleStageSelect || !schedulePerformerSelect) return; const id = editIdInput.value; const time = scheduleTime.value; const duration = parseInt(scheduleDuration.value); const stageId = scheduleStageSelect.value; const performerId = schedulePerformerSelect.value; if (duration <= 0 || !stageId || !performerId) { showMessage("Invalid input. Check duration and selections.", true); return; } const newSlot = { id: id || 'sch' + Date.now(), time, duration, stageId, performerId }; // Conflict Check (Mandatory before adding) const newSlotStart = timeToMinutes(time); const newSlotEnd = newSlotStart + duration; const existingConflict = schedule.some(slot => { // Ignore self if editing if (slot.id === id) return false; // Only check slots on the same stage if (slot.stageId !== stageId) return false; const otherStart = timeToMinutes(slot.time); const otherEnd = otherStart + slot.duration; // Check for overlap: [Start1 < End2] AND [End1 > Start2] return (newSlotStart < otherEnd && newSlotEnd > otherStart); }); if (existingConflict) { showMessage("Conflict detected! This performance overlaps with an existing schedule slot on the same stage.", true); return; } if (id) { // Update existing const index = schedule.findIndex(slot => slot.id === id); if (index !== -1) schedule[index] = newSlot; showMessage("Schedule slot updated successfully.", false); } else { // Add new schedule.push(newSlot); showMessage("Performance added to schedule.", false); } scheduleForm.reset(); editIdInput.value = ''; // Clear edit mode renderScheduleTable(); }; /** Populates the form for editing */ window.rfsbEditSlot = (id) => { const slot = schedule.find(s => s.id === id); if (!slot) return; editIdInput.value = slot.id; scheduleTime.value = slot.time; scheduleDuration.value = slot.duration; scheduleStageSelect.value = slot.stageId; schedulePerformerSelect.value = slot.performerId; scheduleSaveBtn.classList.replace('rfsb-btn-primary', 'rfsb-btn-secondary'); // Visual hint of edit mode scheduleSaveBtn.innerHTML = ' Update Slot'; showMessage("Editing existing slot.", false); }; /** Deletes a performance slot */ window.rfsbDeleteSlot = (id) => { if (!confirm('Delete this schedule slot?')) return; schedule = schedule.filter(slot => slot.id !== id); if (editIdInput.value === id) pscClearForm(); // Clear form if slot being edited was deleted renderScheduleTable(); }; /** Clears all schedule slots */ window.clearSchedule = () => { if (schedule.length === 0) { showMessage("Schedule is already empty.", false); return; } if (!confirm('Are you sure you want to clear the entire schedule?')) return; schedule = []; renderScheduleTable(); pscClearForm(); }; // --- TAB AND NAV LOGIC --- /** Switches tabs */ window.rfsbShowTab = (tabId, element) => { if (!container || !tabLinks) return; container.querySelectorAll('.rfsb-tab-content').forEach(tab => tab.classList.remove('rfsb-active')); container.querySelectorAll('.rfsb-tab-link').forEach(link => link.classList.remove('rfsb-active')); const tabToShow = container.querySelector('#' + tabId); if (tabToShow) tabToShow.classList.add('rfsb-active'); if (element) element.classList.add('rfsb-active'); currentTabId = tabId; updateNavButtons(); // Re-render table/selects when switching views if (tabId === 'rfsb-tab-setup') { renderSetupLists(); } else if (tabId === 'rfsb-tab-scheduler') { renderScheduleTable(); } }; /** Handles nav buttons */ window.rfsbNavigateTabs = (isNext) => { if (!container || !tabLinks) return; // Simple validation check before proceeding from Setup if (currentTabId === 'rfsb-tab-setup' && isNext && (performers.length === 0 || stages.length === 0)) { showMessage("Please add at least one Performer and one Stage before scheduling.", true); return; } const targetTabId = isNext ? 'rfsb-tab-scheduler' : 'rfsb-tab-setup'; const targetTabLink = container.querySelector(`.rfsb-tab-link[onclick*="'${targetTabId}'"]`); if(targetTabLink) targetTabLink.click(); }; /** Updates nav button states */ function updateNavButtons() { if (!prevBtn || !nextBtn) return; prevBtn.disabled = currentTabId === 'rfsb-tab-setup'; nextBtn.disabled = currentTabId === 'rfsb-tab-scheduler'; } // --- PDF EXPORT --- window.rfsbDownloadPDF = () => { const jsPDF = window.jsPDF; if (typeof jsPDF === 'undefined' || !jsPDF.autoTable) { showMessage("PDF library not loaded.", true); return; } if (schedule.length === 0) { showMessage("Schedule is empty. Add slots before downloading.", true); return; } const sortedSchedule = [...schedule].sort((a, b) => a.time.localeCompare(b.time)); try { const doc = new jsPDF({ orientation: 'l', // Landscape for better schedule display unit: 'pt', format: 'a4' }); const pageMargin = 40; const pageWidth = doc.internal.pageSize.getWidth(); let y = pageMargin; // --- PDF Colors --- const PRIMARY_COLOR_HEX = '#0d9488'; // Teal const ACCENT_COLOR_HEX = '#fbbf24'; // Gold const DARK_GRAY_HEX = '#4b5563'; // 1. Title Block doc.setFontSize(22); doc.setFont(undefined, 'bold'); doc.setTextColor(PRIMARY_COLOR_HEX); doc.text("Renaissance Faire Master Schedule", pageWidth / 2, y, { align: 'center' }); y += 30; doc.setFontSize(10); doc.setFont(undefined, 'normal'); doc.setTextColor(DARK_GRAY_HEX); doc.text(`Schedule Date: ${new Date().toLocaleDateString('en-US')}`, pageMargin, y); doc.text(`Total Slots: ${schedule.length}`, pageWidth - pageMargin, y, { align: 'right' }); y += 20; // 2. Schedule Table const tableHead = [["Time", "End Time", "Stage", "Performer / Troupe", "Duration (min)"]]; const tableBody = sortedSchedule.map(slot => { const endTime = calculateEndTime(slot.time, slot.duration); return [ slot.time, endTime, findName(slot.stageId, stages), findName(slot.performerId, performers), slot.duration.toString() ]; }); doc.autoTable({ startY: y, head: tableHead, body: tableBody, theme: 'striped', styles: { fontSize: 9, cellPadding: 4, valign: 'middle' }, headStyles: { fillColor: PRIMARY_COLOR_HEX, textColor: 255 }, columnStyles: { 0: { cellWidth: 50, fontStyle: 'bold' }, // Time 1: { cellWidth: 50 }, // End Time 4: { cellWidth: 60, halign: 'center' } // Duration } }); doc.save(`RenFaire_Schedule_${new Date().toLocaleDateString('en-US').replace(/\//g, '-')}.pdf`); showMessage("PDF Master Schedule downloaded successfully!", false); } catch (e) { console.error("Error generating PDF:", e); showMessage("An error occurred while generating the PDF. Check console for details.", true); } }; // --- Initial Load --- renderSetupLists(); renderScheduleTable(); updateNavButtons(); });
      Scroll to Top