import { format, isToday } from 'date-fns'
import { ptBR } from 'date-fns/locale'
import { sortAppointmentsByTime } from '../../utils/agendaDate.js'
const DAY_START = '07:00'
const DAY_END = '19:00'
const SLOT_MINUTES = 30
export function AgendaDailyView({ baseDate, appointments, canCreateAppointment = true, onAppointmentClick, onSlotCreate }) {
const dailyAppointments = sortAppointmentsByTime(appointments)
const appointmentsByTime = groupAppointmentsByTime(dailyAppointments)
const slots = mergeSlotsWithAppointmentTimes(generateSlots(DAY_START, DAY_END, SLOT_MINUTES), dailyAppointments)
return (
Grade de horários do dia
{format(baseDate, "EEEE, dd 'de' MMMM", { locale: ptBR })}
{dailyAppointments.length} {dailyAppointments.length === 1 ? 'agendamento' : 'agendamentos'}
Livre
Agendado
{isToday(baseDate) && (
Hoje
)}
{slots.map((time) => {
const slotAppointments = appointmentsByTime.get(time) || []
const primaryAppointment = slotAppointments[0]
const isBooked = Boolean(primaryAppointment)
return (
{time}
{isBooked ? 'Agendado' : 'Disponível'}
{isBooked ? (
{primaryAppointment.type} com {primaryAppointment.professional}
{primaryAppointment.room ? {primaryAppointment.room} : null}
{primaryAppointment.mode ? {primaryAppointment.mode} : null}
{slotAppointments.length > 1 ? +{slotAppointments.length - 1} : null}
) : (
Horário disponível para novo agendamento.
)}
{isBooked ? primaryAppointment.status : 'Livre'}
{canCreateAppointment ? (
) : null}
)
})}
)
}
function getDailyToneClass(status) {
switch (status) {
case 'Confirmada':
return 'agenda-slot-confirmed'
case 'Em triagem':
return 'agenda-slot-triage'
case 'Cancelada':
return 'agenda-slot-cancelled'
case 'Bloqueado':
return 'agenda-slot-blocked'
case 'Aguardando':
default:
return 'agenda-slot-waiting'
}
}
function generateSlots(start, end, intervalMinutes) {
const [startHour, startMinute] = start.split(':').map(Number)
const [endHour, endMinute] = end.split(':').map(Number)
const slots = []
let cursor = startHour * 60 + startMinute
const last = endHour * 60 + endMinute
while (cursor < last) {
slots.push(formatMinutes(cursor))
cursor += intervalMinutes
}
return slots
}
function groupAppointmentsByTime(appointments) {
return appointments.reduce((map, appointment) => {
const time = normalizeTime(appointment.time)
if (!time) return map
map.set(time, [...(map.get(time) || []), appointment])
return map
}, new Map())
}
function mergeSlotsWithAppointmentTimes(slots, appointments) {
return [...new Set([...slots, ...appointments.map((appointment) => normalizeTime(appointment.time)).filter(Boolean)])]
.sort((first, second) => minutesFromTime(first) - minutesFromTime(second))
}
function normalizeTime(value) {
const match = String(value || '').match(/^(\d{1,2}):(\d{2})/)
if (!match) return ''
return `${match[1].padStart(2, '0')}:${match[2]}`
}
function minutesFromTime(value) {
const [hours, minutes] = normalizeTime(value).split(':').map(Number)
return hours * 60 + minutes
}
function formatMinutes(totalMinutes) {
const hours = String(Math.floor(totalMinutes / 60)).padStart(2, '0')
const minutes = String(totalMinutes % 60).padStart(2, '0')
return `${hours}:${minutes}`
}