121 lines
3.4 KiB
TypeScript
121 lines
3.4 KiB
TypeScript
import React, { useState, useCallback } from 'react';
|
|
import { Calendar } from '@fullcalendar/react';
|
|
import dayGridPlugin from '@fullcalendar/daygrid';
|
|
import timeGridPlugin from '@fullcalendar/timegrid';
|
|
import interactionPlugin from '@fullcalendar/interaction';
|
|
import ptBrLocale from '@fullcalendar/core/locales/pt-br';
|
|
import { format } from 'date-fns';
|
|
import { api } from '../services/api';
|
|
import { toast } from 'react-toastify';
|
|
|
|
interface Agendamento {
|
|
id: string;
|
|
data: string;
|
|
observacoes?: string;
|
|
status: string;
|
|
cliente: {
|
|
id: string;
|
|
nome: string;
|
|
};
|
|
}
|
|
|
|
interface CalendarEvent {
|
|
id: string;
|
|
title: string;
|
|
start: string;
|
|
end: string;
|
|
backgroundColor?: string;
|
|
borderColor?: string;
|
|
extendedProps: {
|
|
agendamento: Agendamento;
|
|
};
|
|
}
|
|
|
|
interface AgendamentoCalendarioProps {
|
|
onEventClick?: (event: any) => void;
|
|
onDateSelect?: (selectInfo: any) => void;
|
|
}
|
|
|
|
export const AgendamentoCalendario: React.FC<AgendamentoCalendarioProps> = ({
|
|
onEventClick,
|
|
onDateSelect
|
|
}) => {
|
|
const [eventos, setEventos] = useState<CalendarEvent[]>([]);
|
|
const [loading, setLoading] = useState(false);
|
|
const [error, setError] = useState<string | null>(null);
|
|
|
|
const formatEventos = useCallback((agendamentos: Agendamento[]): CalendarEvent[] => {
|
|
return agendamentos.map(ag => ({
|
|
id: ag.id,
|
|
title: `${ag.cliente.nome} ${ag.observacoes ? `- ${ag.observacoes}` : ''}`,
|
|
start: ag.data,
|
|
end: ag.data,
|
|
backgroundColor: ag.status === 'CANCELADO' ? '#FEE2E2' : '#E0F2FE',
|
|
borderColor: ag.status === 'CANCELADO' ? '#EF4444' : '#0EA5E9',
|
|
extendedProps: {
|
|
agendamento: ag
|
|
}
|
|
}));
|
|
}, []);
|
|
|
|
const loadAgendamentos = useCallback(async ({ start, end }: { start: Date, end: Date }) => {
|
|
try {
|
|
setLoading(true);
|
|
setError(null);
|
|
|
|
const response = await api.get('/atendimentos/agendamentos', {
|
|
params: {
|
|
inicio: format(start, 'yyyy-MM-dd'),
|
|
fim: format(end, 'yyyy-MM-dd')
|
|
}
|
|
});
|
|
|
|
setEventos(formatEventos(response.data));
|
|
} catch (err: any) {
|
|
const mensagem = err.response?.data?.error || 'Erro ao carregar agendamentos';
|
|
setError(mensagem);
|
|
toast.error(mensagem);
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
}, [formatEventos]);
|
|
|
|
return (
|
|
<div className="relative h-full">
|
|
{loading && (
|
|
<div className="absolute inset-0 bg-white/50 flex items-center justify-center z-10">
|
|
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-500" />
|
|
</div>
|
|
)}
|
|
|
|
{error && (
|
|
<div className="absolute top-0 left-0 right-0 p-4 bg-red-100 text-red-700">
|
|
{error}
|
|
</div>
|
|
)}
|
|
|
|
<Calendar
|
|
plugins={[dayGridPlugin, timeGridPlugin, interactionPlugin]}
|
|
initialView="dayGridMonth"
|
|
locale={ptBrLocale}
|
|
events={eventos}
|
|
editable={false}
|
|
selectable={true}
|
|
selectMirror={true}
|
|
dayMaxEvents={3}
|
|
weekends={true}
|
|
datesSet={({ start, end }) => loadAgendamentos({ start, end })}
|
|
eventClick={({ event }) => onEventClick?.(event)}
|
|
select={(selectInfo) => onDateSelect?.(selectInfo)}
|
|
headerToolbar={{
|
|
left: 'prev,next today',
|
|
center: 'title',
|
|
right: 'dayGridMonth,timeGridWeek,timeGridDay'
|
|
}}
|
|
slotMinTime="08:00:00"
|
|
slotMaxTime="18:00:00"
|
|
/>
|
|
</div>
|
|
);
|
|
};
|