// ============================================================ // JuFlow — Agenda (Daily / Weekly / Monthly views) // ============================================================ const { useState: useStA, useMemo: useMemoA, useCallback: useCbA, useRef: useRefA, useEffect: useEfA } = React; const UA = window.JFUtils; const START_HOUR = 7; const END_HOUR = 21; const PX_PER_MIN = 1.8; // pixels per minute → 60min=108px, 30min=54px const timeToY = (date) => { const d = new Date(date); const mins = (d.getHours() - START_HOUR) * 60 + d.getMinutes(); return Math.max(0, mins * PX_PER_MIN); }; const TOTAL_HEIGHT = (END_HOUR - START_HOUR) * 60 * PX_PER_MIN; // Generate time labels (30-min increments) const TIME_LABELS = []; for (let h = START_HOUR; h <= END_HOUR; h++) { TIME_LABELS.push({ label: `${String(h).padStart(2,'0')}:00`, mins: (h - START_HOUR) * 60 }); if (h < END_HOUR) TIME_LABELS.push({ label: `${String(h).padStart(2,'0')}:30`, mins: (h - START_HOUR) * 60 + 30 }); } // ─── Appointment card for grid views ────────────────────── const ApptCard = ({ appt, onOpen, compact = false }) => { const [hov, setHov] = useState(false); const client = UA.getClient(appt.clientId); const service = UA.getService(appt.serviceId); const prof = UA.getProfessional(appt.professionalId); const color = prof?.color || '#FF6B35'; const endTime = UA.addMinutes(appt.datetime, appt.duration); const isCancelled = appt.status === 'cancelled' || appt.status === 'no_show'; return (