/* global React, Icon, Avatar */ const { useState } = React; function Calendar({ goto }) { const [view, setView] = useState('week'); const [selectedEvent, setSelectedEvent] = useState(null); const events = window.CALENDAR_EVENTS; const days = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']; const dates = ['27', '28', '29', '30', '01', '02', '03']; const hours = Array.from({ length: 11 }, (_, i) => i + 8); // 8am-6pm const eventColor = { discovery: '#f97316', strategy: '#eab308', internal: '#3b82f6', other: '#6b7280', }; return (
April · Week 18

Calendar

{view === 'week' ? (
{days.map((d, i) => (
{d}
{dates[i]}
))} {hours.map(h => (
{String(h).padStart(2,'0')}:00
{days.map((d, dayIdx) => { const dayEvents = events.filter(e => e.day === dayIdx && Math.floor(e.start) === h); return (
{dayEvents.map(e => (
setSelectedEvent(e)} style={{ background: `${eventColor[e.type]}22`, border: '1px solid ' + eventColor[e.type], borderLeft: '2px solid ' + eventColor[e.type], padding: 6, fontSize: 10, cursor: 'pointer', height: `${(e.end - e.start) * 56 - 8}px`, minHeight: 28, }}>
{e.title}
{String(Math.floor(e.start)).padStart(2,'0')}:{String(Math.round((e.start%1)*60)).padStart(2,'0')}
))}
); })}
))}
) : (
)} {selectedEvent && ( <>
setSelectedEvent(null)}>
Event

{selectedEvent.title}

{days[selectedEvent.day]} · {String(Math.floor(selectedEvent.start)).padStart(2,'0')}:{String(Math.round((selectedEvent.start%1)*60)).padStart(2,'0')} – {String(Math.floor(selectedEvent.end)).padStart(2,'0')}:{String(Math.round((selectedEvent.end%1)*60)).padStart(2,'0')}
{selectedEvent.contactId && (() => { const c = window.CONTACTS.find(x => x.id === selectedEvent.contactId); return c && (
Contact
goto('contact', c.id)} style={{ padding: 12, background: 'var(--raised)', border: '1px solid var(--divider)', display: 'flex', alignItems: 'center', gap: 10, cursor: 'pointer' }}>
{c.name}
{c.title} · {c.company}
); })()} {selectedEvent.invite && } {selectedEvent.reminders && }
Prep notes
{selectedEvent.notes}
)}
); } function MonthView({ events, eventColor, onSelect }) { const days = ['Mon','Tue','Wed','Thu','Fri','Sat','Sun']; // Build a 5x7 grid for April. Today is Apr 28 (col idx 1, row idx 4 if Mon=0) const cells = []; for (let i = 0; i < 35; i++) { const dayNum = i - 1; // April starts on Wed = idx 2 let label = ''; if (i >= 2 && i < 32) label = String(i - 1); cells.push({ idx: i, label, isToday: label === '28' }); } return (
{days.map((d, i) => (
{d}
))}
{cells.map((c, i) => { const colIdx = i % 7; const rowIdx = Math.floor(i / 7); // place this week's events on row 4 (week of Apr 27) const eventsHere = (rowIdx === 4 && c.label) ? events.filter(e => e.day === colIdx) : []; return (
{c.label || ''}
{eventsHere.map(e => (
onSelect(e)} style={{ padding: '3px 6px', marginBottom: 3, background: `${eventColor[e.type]}22`, borderLeft: '2px solid ' + eventColor[e.type], fontSize: 10, cursor: 'pointer', overflow:'hidden', textOverflow:'ellipsis', whiteSpace:'nowrap' }}> {String(Math.floor(e.start)).padStart(2,'0')}:{String(Math.round((e.start%1)*60)).padStart(2,'0')}{' '} {e.title.split(' · ')[0]}
))}
); })}
); } Object.assign(window, { Calendar }); /* ============================================================ INVITE STATUS — did the contact accept the Google Calendar invite? ============================================================ */ function InviteStatus({ invite }) { const cfg = { accepted: { label: 'Accepted', color: '#22c55e', icon: 'check', bg: 'rgba(34,197,94,0.10)' }, tentative: { label: 'Maybe', color: '#eab308', icon: 'clock', bg: 'rgba(234,179,8,0.10)' }, declined: { label: 'Declined', color: '#ef4444', icon: 'x', bg: 'rgba(239,68,68,0.10)' }, pending: { label: 'No response', color: 'var(--text-dim)', icon: 'mail', bg: 'var(--raised)' }, }[invite.status] || { label: invite.status, color: 'var(--text-secondary)', icon: 'mail', bg: 'var(--raised)' }; return (
Google Calendar Invite
{cfg.label}
{invite.status === 'pending' ? 'Awaiting response from contact' : `Responded ${invite.respondedAt}${invite.note ? ' · ' + invite.note : ''}`}
); } /* ============================================================ REMINDER SEQUENCE — 24h, 8h, 1h, 10m before ============================================================ */ const REM_OFFSET_LABEL = { '24h': '24 hours before', '8h': '8 hours before', '1h': '1 hour before', '10m': '10 minutes before', }; const REM_STATUS = { scheduled: { label: 'Scheduled', color: 'var(--text-dim)', icon: 'clock' }, sent: { label: 'Sent', color: 'var(--text-secondary)', icon: 'check' }, delivered: { label: 'Delivered', color: 'var(--text-secondary)', icon: 'check' }, opened: { label: 'Opened', color: '#22c55e', icon: 'eye' }, read: { label: 'Read', color: '#22c55e', icon: 'eye' }, cancelled: { label: 'Cancelled', color: '#ef4444', icon: 'x' }, failed: { label: 'Failed', color: '#ef4444', icon: 'x' }, }; function ReminderSequence({ event }) { const reminders = event.reminders || []; const sentCount = reminders.filter(r => ['sent','delivered','opened','read'].includes(r.status)).length; const openedCount = reminders.filter(r => ['opened','read'].includes(r.status)).length; return (
Reminder Sequence
{sentCount}/{reminders.length} sent · {openedCount} opened
{reminders.map((r, i) => { const s = REM_STATUS[r.status] || REM_STATUS.scheduled; const channelColor = r.channel === 'whatsapp' ? '#22c55e' : '#3b82f6'; return (
{/* Channel badge */}
{/* Offset + detail */}
{REM_OFFSET_LABEL[r.offset] || r.offset} · {r.channel}
{r.detail || (r.sentAt ? r.sentAt : '—')}
{/* Status pill */}
{s.label}
); })}
); } Object.assign(window, { InviteStatus, ReminderSequence });