feat(agenda+ui): responsividade mobile da agenda
- PageHeader empilha no mobile, titulo menor, acoes com flex-wrap - AgendaView: toolbar de acoes com wrap; botao NOVO vira so icone no mobile - FullCalendar: view de DIA no mobile (timeGridDay), toolbar enxuta + footer com Dia/Mes, altura por viewport (100dvh), CSS responsivo da toolbar/celulas, nowIndicator Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -9,12 +9,12 @@ interface PageHeaderProps {
|
||||
|
||||
export const PageHeader: React.FC<PageHeaderProps> = ({ title, description, children }) => {
|
||||
return (
|
||||
<div className="flex justify-between items-center mb-6">
|
||||
<div className="flex flex-col gap-3 sm:flex-row sm:justify-between sm:items-center mb-4 sm:mb-6">
|
||||
<div>
|
||||
<h2 className="text-2xl font-bold text-gray-900 uppercase">{title}</h2>
|
||||
<h2 className="text-xl sm:text-2xl font-bold text-gray-900 uppercase">{title}</h2>
|
||||
{description && <p className="text-gray-500 text-xs uppercase font-bold mt-1">{description}</p>}
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="flex items-center gap-2 sm:gap-3 flex-wrap">
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -369,6 +369,12 @@ export const AgendaView: React.FC<{ onNavigate?: (view: string) => void }> = ({
|
||||
const loadAtividade = useCallback(async () => {
|
||||
try { setAtividade(await HybridBackend.getAtividadeAgenda()); } catch { /* ignore */ }
|
||||
}, []);
|
||||
const [isMobile, setIsMobile] = useState(typeof window !== 'undefined' && window.innerWidth < 640);
|
||||
useEffect(() => {
|
||||
const onResize = () => setIsMobile(window.innerWidth < 640);
|
||||
window.addEventListener('resize', onResize);
|
||||
return () => window.removeEventListener('resize', onResize);
|
||||
}, []);
|
||||
const [importando, setImportando] = useState(false);
|
||||
const handleImportarGoogle = async () => {
|
||||
if (!confirm('Importar os agendamentos do Google (última semana + próximos) para o sistema? Eles passam a ser editáveis aqui.')) return;
|
||||
@@ -553,7 +559,7 @@ export const AgendaView: React.FC<{ onNavigate?: (view: string) => void }> = ({
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<PageHeader title="AGENDA CLÍNICA">
|
||||
<div className="flex gap-4 items-center">
|
||||
<div className="flex gap-2 sm:gap-3 items-center flex-wrap justify-end">
|
||||
<DragDropContext onDragEnd={onDragEnd}>
|
||||
<Droppable droppableId="dentists-legend" direction="horizontal">
|
||||
{(provided) => (
|
||||
@@ -616,17 +622,21 @@ export const AgendaView: React.FC<{ onNavigate?: (view: string) => void }> = ({
|
||||
</button>
|
||||
)}
|
||||
{(currentRole !== 'paciente') && (
|
||||
<button onClick={() => { setReagendarPatient(null); setSelectedAppointment(null); setSelectedDateInfo({ dateStr: new Date().toISOString() }); setIsModalOpen(true); }} className="bg-blue-600 text-white px-4 py-2 rounded-lg text-sm font-bold uppercase flex items-center gap-2 hover:bg-blue-700 shadow-sm transition-colors">
|
||||
<Plus size={16} /> NOVO AGENDAMENTO
|
||||
<button onClick={() => { setReagendarPatient(null); setSelectedAppointment(null); setSelectedDateInfo({ dateStr: new Date().toISOString() }); setIsModalOpen(true); }} title="Novo agendamento" className="bg-blue-600 text-white px-3 sm:px-4 py-2 rounded-lg text-sm font-bold uppercase flex items-center gap-2 hover:bg-blue-700 shadow-sm transition-colors">
|
||||
<Plus size={18} /> <span className="hidden sm:inline">NOVO AGENDAMENTO</span>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</PageHeader>
|
||||
<div className="bg-white rounded-xl shadow-sm border border-gray-200 p-4 relative" style={{ height: '750px' }}>
|
||||
<div className="bg-white rounded-xl shadow-sm border border-gray-200 p-2 sm:p-4 relative h-[calc(100dvh-220px)] sm:h-[750px] min-h-[420px]">
|
||||
<FullCalendar
|
||||
key={isMobile ? 'mobile' : 'desktop'}
|
||||
plugins={[dayGridPlugin, timeGridPlugin, interactionPlugin]}
|
||||
initialView="timeGridWeek"
|
||||
headerToolbar={{ left: 'prev,next today', center: 'title', right: 'dayGridMonth,timeGridWeek,timeGridDay' }}
|
||||
initialView={isMobile ? 'timeGridDay' : 'timeGridWeek'}
|
||||
headerToolbar={isMobile
|
||||
? { left: 'prev,next', center: 'title', right: 'today' }
|
||||
: { left: 'prev,next today', center: 'title', right: 'dayGridMonth,timeGridWeek,timeGridDay' }}
|
||||
footerToolbar={isMobile ? { center: 'timeGridDay,dayGridMonth' } : undefined}
|
||||
locale="pt-br"
|
||||
buttonText={{ today: 'HOJE', month: 'MÊS', week: 'SEMANA', day: 'DIA' }}
|
||||
slotMinTime="08:00:00"
|
||||
@@ -639,6 +649,7 @@ export const AgendaView: React.FC<{ onNavigate?: (view: string) => void }> = ({
|
||||
dateClick={handleDateClick}
|
||||
eventClick={handleEventClick}
|
||||
height="100%"
|
||||
nowIndicator={true}
|
||||
slotLabelFormat={{ hour: '2-digit', minute: '2-digit', hour12: false }}
|
||||
eventContent={(eventInfo) => {
|
||||
const isGoogle = eventInfo.event.extendedProps.isGoogle;
|
||||
@@ -745,6 +756,17 @@ export const AgendaView: React.FC<{ onNavigate?: (view: string) => void }> = ({
|
||||
.custom-scrollbar::-webkit-scrollbar { width: 4px; }
|
||||
.custom-scrollbar::-webkit-scrollbar-track { background: #f1f1f1; }
|
||||
.custom-scrollbar::-webkit-scrollbar-thumb { background: #cbd5e1; border-radius: 10px; }
|
||||
/* FullCalendar — responsividade mobile */
|
||||
@media (max-width: 639px) {
|
||||
.fc .fc-toolbar.fc-header-toolbar { margin-bottom: .5rem; flex-wrap: wrap; gap: .4rem; }
|
||||
.fc .fc-toolbar-title { font-size: .95rem; }
|
||||
.fc .fc-button { padding: .25rem .55rem; font-size: .72rem; }
|
||||
.fc .fc-toolbar-chunk { display: flex; align-items: center; }
|
||||
.fc .fc-col-header-cell-cushion { font-size: .65rem; padding: 2px; }
|
||||
.fc .fc-timegrid-slot-label-cushion, .fc .fc-timegrid-axis-cushion { font-size: .6rem; }
|
||||
.fc .fc-timegrid-event .fc-event-main { padding: 1px 2px; }
|
||||
.fc-footer-toolbar.fc-toolbar { margin-top: .5rem; }
|
||||
}
|
||||
`}</style>
|
||||
<AppointmentModal
|
||||
isOpen={isModalOpen}
|
||||
|
||||
Reference in New Issue
Block a user