From 8b1801bd9c481d96998e62dd2201ae985f8352d0 Mon Sep 17 00:00:00 2001 From: VPS 4 Builder Date: Fri, 3 Jul 2026 02:32:54 +0200 Subject: [PATCH] =?UTF-8?q?feat(agenda):=20layout=20em=202=20colunas=20no?= =?UTF-8?q?=20desktop=20+=20sidebar=20recolh=C3=ADvel?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - AgendaView: controles migrados do header horizontal para uma coluna lateral esquerda (AGENDAR, ícones de ação em faixa horizontal de tamanho igual, presença, legenda de dentistas); calendário em flex-1 ocupando a altura toda - Mobile revisto: cabeçalho compacto (título + AGENDAR) e calendário flex-1 - Sidebar do sistema recolhível no desktop (App: estado persistido + botão flutuante para reexibir); toggle fica ao lado do título "AGENDA CLÍNICA" - Toolbar do FullCalendar compactada (título/botões/cabeçalhos menores) - Título da agenda responsivo (não gera scroll horizontal na coluna estreita) Co-Authored-By: Claude Opus 4.8 --- frontend/App.tsx | 26 ++++++- frontend/views/AgendaView.tsx | 129 +++++++++++++++++++++------------- 2 files changed, 102 insertions(+), 53 deletions(-) diff --git a/frontend/App.tsx b/frontend/App.tsx index 3380c1e..e2bf915 100644 --- a/frontend/App.tsx +++ b/frontend/App.tsx @@ -4,7 +4,7 @@ import { HybridBackend } from './services/backend.ts'; import { Sidebar } from './components/Sidebar.tsx'; import { Menu, LayoutDashboard, Users, Calendar as CalendarIcon, - DollarSign, Activity, ClipboardList, Bell, Settings, MoreHorizontal, Eye, X + DollarSign, Activity, ClipboardList, Bell, Settings, MoreHorizontal, Eye, X, PanelLeftOpen } from 'lucide-react'; import { ToastContainer } from './components/Toast.tsx'; @@ -315,6 +315,14 @@ const MobileBottomNav: React.FC<{ const App: React.FC = () => { const ver = useAppVersion(); const [isSidebarOpen, setSidebarOpen] = useState(false); + // Recolher a sidebar no DESKTOP (persistente) — dá largura ao conteúdo (ex.: agenda). + const [isSidebarCollapsed, setSidebarCollapsed] = useState(() => { + try { return localStorage.getItem('scoreodonto:sidebar-collapsed') === '1'; } catch { return false; } + }); + const toggleSidebarCollapsed = (v: boolean) => { + setSidebarCollapsed(v); + try { localStorage.setItem('scoreodonto:sidebar-collapsed', v ? '1' : '0'); } catch { /* ignore */ } + }; const [, forcePwRerender] = useState(0); // re-avalia o modal de troca obrigatória de senha const currentRole = HybridBackend.getCurrentRole(); const activeWorkspace = HybridBackend.getActiveWorkspace(); @@ -500,7 +508,7 @@ const App: React.FC = () => { case 'leads': return ; case 'public': return ; case 'pacientes': return ; - case 'agenda': return ; + case 'agenda': return toggleSidebarCollapsed(!isSidebarCollapsed)} />; case 'ortodontia': return ; case 'financeiro': return ; case 'dentistas': return ; @@ -591,7 +599,7 @@ const App: React.FC = () => { > )} -
+
handleNavigate(t as ViewKey)} @@ -600,6 +608,18 @@ const App: React.FC = () => { )} + {/* Botão flutuante para reexibir a sidebar (desktop, quando recolhida). + Na agenda NÃO aparece — ela já tem o próprio toggle ao lado do título. */} + {!isStandaloneView && isSidebarCollapsed && currentView !== 'agenda' && ( + + )} +
{HybridBackend.isPreviewMode() && (
diff --git a/frontend/views/AgendaView.tsx b/frontend/views/AgendaView.tsx index e54a85f..70811df 100644 --- a/frontend/views/AgendaView.tsx +++ b/frontend/views/AgendaView.tsx @@ -3,7 +3,7 @@ import FullCalendar from '@fullcalendar/react'; import dayGridPlugin from '@fullcalendar/daygrid'; import timeGridPlugin from '@fullcalendar/timegrid'; import interactionPlugin from '@fullcalendar/interaction'; -import { Calendar as CalendarIcon, Plus, X, Loader2, GripVertical, Settings as GearIcon, Edit, Trash2, CalendarRange, Clock, User, Stethoscope, AlertCircle, CheckCircle2, Bell, CalendarClock, History, Download } from 'lucide-react'; +import { Calendar as CalendarIcon, Plus, X, Loader2, GripVertical, Settings as GearIcon, Edit, Trash2, CalendarRange, Clock, User, Stethoscope, AlertCircle, CheckCircle2, Bell, CalendarClock, History, Download, PanelLeftClose, PanelLeftOpen } from 'lucide-react'; import { AgendaSettingsModal } from '../components/AgendaSettingsModal.tsx'; import { AgendaDetailModal, ScoreBadge, FamiliaChips } from '../components/AgendaDetailModal.tsx'; import { WhatsChatDrawer } from './newwhats/WhatsChatDrawer.tsx'; @@ -15,7 +15,6 @@ import { Dentista, Agendamento, Paciente, DentistaHorario } from '../types.ts'; import { useGTOStore } from './LancarGTO.tsx'; import { useHybridBackend } from '../hooks/useHybridBackend.ts'; import { useToast } from '../contexts/ToastContext.tsx'; -import { PageHeader } from '../components/PageHeader.tsx'; // --- Reusable Debounce Hook --- function useDebounce(value: string, delay: number) { @@ -344,7 +343,7 @@ const AppointmentModal: React.FC<{ ); }; -export const AgendaView: React.FC<{ onNavigate?: (view: string) => void }> = ({ onNavigate }) => { +export const AgendaView: React.FC<{ onNavigate?: (view: string) => void; sidebarCollapsed?: boolean; onToggleSidebar?: () => void }> = ({ onNavigate, sidebarCollapsed, onToggleSidebar }) => { const { data: agendamentos, refresh } = useHybridBackend(HybridBackend.getAgendamentos); const { data: dentists, refresh: refreshDentists } = useHybridBackend(HybridBackend.getDentistas); const { data: googleEvents, refresh: refreshGoogle } = useHybridBackend(HybridBackend.getGoogleEvents); @@ -600,13 +599,78 @@ export const AgendaView: React.FC<{ onNavigate?: (view: string) => void }> = ({ }); return ( -
- -
+
+ {/* Coluna de controles (desktop: lateral esquerda vertical; mobile: topo compacto) */} + +
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 — toolbar/controles compactos (todas as telas) */ + .fc .fc-toolbar.fc-header-toolbar { margin-bottom: .6rem; } + .fc .fc-toolbar-title { font-size: 1.05rem; font-weight: 700; } + .fc .fc-button { padding: .3rem .65rem; font-size: .8rem; box-shadow: none !important; } + .fc .fc-button-group { gap: 0; } + .fc .fc-col-header-cell-cushion { font-size: .72rem; padding: 4px 2px; } /* FullCalendar — responsividade mobile */ @media (max-width: 639px) { .fc .fc-toolbar.fc-header-toolbar { margin-bottom: .5rem; flex-wrap: wrap; gap: .4rem; }