// Calendar / Agenda screen — week view + day timeline
// Helper: formata Date pra ISO YYYY-MM-DD (sem timezone confusion)
function fmtDateISO(d) {
const yy = d.getFullYear();
const mm = String(d.getMonth() + 1).padStart(2, '0');
const dd = String(d.getDate()).padStart(2, '0');
return `${yy}-${mm}-${dd}`;
}
const CalendarScreen = ({ state, setState, navigate, showToast }) => {
const [view, setView] = React.useState('dia');
// Âncora da semana visível (Date) — começa em hoje
const [anchor, setAnchor] = React.useState(() => { const t = new Date(); t.setHours(0,0,0,0); return t; });
// Dia selecionado dentro da semana (Date)
const [selectedDate, setSelectedDate] = React.useState(() => { const t = new Date(); t.setHours(0,0,0,0); return t; });
const [showNew, setShowNew] = React.useState(false);
const [editingEvent, setEditingEvent] = React.useState(null);
const events = (state && state.events) || [];
const addEvent = (ev) => {
setState && setState(s => ({ ...s, events: [...(s.events || []), { ...ev, id: 'ev' + Date.now() }] }));
setShowNew(false);
showToast && showToast('Evento criado');
};
const updateEvent = (id, patch) => {
setState && setState(s => ({
...s,
events: (s.events || []).map(e => e.id === id ? { ...e, ...patch } : e),
}));
setEditingEvent(null);
showToast && showToast('Evento atualizado');
};
const deleteEvent = (id) => {
if (window.confirm && !window.confirm('Excluir este evento?')) return;
setState && setState(s => ({ ...s, events: (s.events || []).filter(e => e.id !== id) }));
setEditingEvent(null);
showToast && showToast('Evento excluído');
};
// Calcula a semana (Dom..Sáb) a partir do âncora
const today = React.useMemo(() => { const t = new Date(); t.setHours(0,0,0,0); return t; }, []);
const weekStart = (() => {
const w = new Date(anchor); w.setDate(anchor.getDate() - anchor.getDay()); return w;
})();
const WEEKDAY_LABEL = ['Dom','Seg','Ter','Qua','Qui','Sex','Sáb'];
const weekDays = Array.from({ length: 7 }, (_, i) => {
const date = new Date(weekStart); date.setDate(weekStart.getDate() + i);
return {
date,
d: WEEKDAY_LABEL[i],
n: date.getDate(),
today: date.getTime() === today.getTime(),
selected: date.getTime() === selectedDate.getTime(),
};
});
const shiftWeek = (dir) => {
const next = new Date(anchor); next.setDate(anchor.getDate() + 7 * dir);
setAnchor(next);
};
const monthLabel = anchor.toLocaleDateString('pt-BR', { month: 'long', year: 'numeric' }).toUpperCase();
// Eventos do dia selecionado
const selectedISO = fmtDateISO(selectedDate);
const eventsOfDay = events.filter(e => !e.date || e.date === selectedISO);
return (
navigate('dashboard')} right={
} />
{/* View switcher + week nav */}
{[{ id: 'dia', label: 'Dia' }, { id: 'semana', label: 'Semana' }, { id: 'mes', label: 'Mês' }].map(v => (
))}
{/* Week nav arrows */}
{/* Week strip */}
{weekDays.map((d, i) => {
const active = d.selected;
return (
);
})}
{view === 'dia' && }
{view === 'semana' && }
{view === 'mes' && }
setShowNew(false)} title="Novo evento">
setEditingEvent(null)} title="Editar evento">
{editingEvent && (
updateEvent(editingEvent.id, patch)}
onDelete={() => deleteEvent(editingEvent.id)}
/>
)}
);
};
const NewEventForm = ({ onSubmit, defaultDate }) => {
const [title, setTitle] = React.useState('');
const defaultISO = defaultDate ? fmtDateISO(defaultDate) : fmtDateISO(new Date());
const [dateStr, setDateStr] = React.useState(defaultISO);
const [start, setStart] = React.useState('14');
const [end, setEnd] = React.useState('15');
const [location, setLocation] = React.useState('');
const [notes, setNotes] = React.useState('');
const colors = [CDV.brand, CDV.mint, CDV.coral, CDV.amber, CDV.sky];
const [color, setColor] = React.useState(colors[0]);
const canSave = title.trim() && dateStr && Number(start) < Number(end);
return (
setTitle(e.target.value)} placeholder="Título do evento" autoFocus
style={{
width: '100%', height: 54, borderRadius: 16, border: `1px solid ${CDV.stroke}`,
background: CDV.surfaceHi, color: CDV.text, padding: '0 16px', fontSize: 15,
outline: 'none', fontFamily: 'inherit', marginBottom: 12,
}} />
Data
setDateStr(e.target.value)}
style={{
width: '100%', height: 50, borderRadius: 14, border: `1px solid ${CDV.stroke}`,
background: CDV.surfaceHi, color: CDV.text, padding: '0 14px', fontSize: 14,
outline: 'none', fontFamily: 'inherit', marginBottom: 12, colorScheme: 'dark',
}} />
setLocation(e.target.value)} placeholder="Local (opcional)"
style={{
width: '100%', height: 50, borderRadius: 14, border: `1px solid ${CDV.stroke}`,
background: CDV.surfaceHi, color: CDV.text, padding: '0 16px', fontSize: 14,
outline: 'none', fontFamily: 'inherit', marginBottom: 12,
}} />
);
};
const DayView = ({ events, onEventClick }) => {
const hours = Array.from({ length: 14 }, (_, i) => i + 7); // 7 to 20
const HOUR_HEIGHT = 56;
const nowHour = 10.4;
if (events.length === 0) {
return (
);
}
return (
{hours.map(h => (
{String(h).padStart(2, '0')}:00
))}
{/* Events */}
{events.map(ev => {
const top = (ev.start - hours[0]) * HOUR_HEIGHT + 14;
const height = (ev.end - ev.start) * HOUR_HEIGHT - 4;
return (
onEventClick && onEventClick(ev)} style={{
position: 'absolute', top, left: 60, right: 8,
height, padding: '8px 12px', borderRadius: 12,
background: `linear-gradient(135deg, ${ev.color}28, ${ev.color}12)`,
borderLeft: `3px solid ${ev.color}`,
cursor: 'pointer',
}}>
{ev.title}
{fmtHour(ev.start)} – {fmtHour(ev.end)} · {ev.location}
);
})}
{/* Now indicator */}
);
};
const fmtHour = (h) => {
const hh = Math.floor(h);
const mm = Math.round((h - hh) * 60);
return `${String(hh).padStart(2, '0')}:${String(mm).padStart(2, '0')}`;
};
const WeekView = ({ events }) => (
Semana de 16-22 Jun
{[...Array(7)].map((_, i) => (
{['S','T','Q','Q','S','S','D'][i]}
{16 + i}
{Array.from({ length: Math.floor(Math.random() * 4) + 1 }, (_, k) => (
))}
))}
{events.slice(0, 4).map(ev => (
{ev.title}
{fmtHour(ev.start)} - {fmtHour(ev.end)} · {ev.location}
))}
);
const MonthView = () => {
const dots = { 3: 4, 7: 2, 10: 3, 14: 1, 16: 2, 19: 5, 22: 2, 25: 3, 28: 1 };
return (
{['D','S','T','Q','Q','S','S'].map(d => (
{d}
))}
{[...Array(35)].map((_, i) => {
const day = i - 2;
if (day < 1 || day > 30) return
;
const today = day === 19;
return (
{day}
{Array.from({ length: dots[day] || 0 }, (_, k) => (
))}
);
})}
);
};
// Formulário de edição de evento existente (com botão Excluir)
const EditEventForm = ({ event, onSubmit, onDelete }) => {
const [title, setTitle] = React.useState(event.title || '');
const [dateStr, setDateStr] = React.useState(event.date || fmtDateISO(new Date()));
const [start, setStart] = React.useState(String(Math.floor(event.start || 14)));
const [end, setEnd] = React.useState(String(Math.floor(event.end || 15)));
const [location, setLocation] = React.useState(event.location && event.location !== '—' ? event.location : '');
const [notes, setNotes] = React.useState(event.notes || '');
const colors = [CDV.brand, CDV.mint, CDV.coral, CDV.amber, CDV.sky];
const [color, setColor] = React.useState(event.color || colors[0]);
const canSave = title.trim() && dateStr && Number(start) < Number(end);
return (
setTitle(e.target.value)} placeholder="Título do evento" autoFocus
style={{
width: '100%', height: 54, borderRadius: 16, border: `1px solid ${CDV.stroke}`,
background: CDV.surfaceHi, color: CDV.text, padding: '0 16px', fontSize: 15,
outline: 'none', fontFamily: 'inherit', marginBottom: 12,
}} />
Data
setDateStr(e.target.value)}
style={{
width: '100%', height: 50, borderRadius: 14, border: `1px solid ${CDV.stroke}`,
background: CDV.surfaceHi, color: CDV.text, padding: '0 14px', fontSize: 14,
outline: 'none', fontFamily: 'inherit', marginBottom: 12, colorScheme: 'dark',
}} />
setLocation(e.target.value)} placeholder="Local (opcional)"
style={{
width: '100%', height: 50, borderRadius: 14, border: `1px solid ${CDV.stroke}`,
background: CDV.surfaceHi, color: CDV.text, padding: '0 16px', fontSize: 14,
outline: 'none', fontFamily: 'inherit', marginBottom: 12,
}} />
);
};
Object.assign(window, { CalendarScreen });