diff --git a/src/app/globals.css b/src/app/globals.css index b6b7ebf..8b6441d 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -69,12 +69,24 @@ body { left: 0; bottom: 0; z-index: 100; - transition: transform 0.3s ease; + transition: width 0.3s cubic-bezier(0.16, 1, 0.3, 1), transform 0.3s ease; +} + +.sidebar.collapsed { + width: 76px; } .sidebar-header { - padding: 24px 20px; + padding: 20px 16px; border-bottom: 1px solid var(--border-color); + display: flex; + align-items: center; + justify-content: space-between; +} + +.sidebar.collapsed .sidebar-header { + justify-content: center; + padding: 20px 10px; } .sidebar-logo { @@ -84,77 +96,41 @@ body { text-decoration: none; } -.logo-icon { - width: 40px; - height: 40px; - background: var(--gradient-primary); - border-radius: var(--radius-md); - display: flex; - align-items: center; - justify-content: center; - font-size: 20px; - color: white; - box-shadow: var(--shadow-glow); -} - -.logo-text { - font-size: 20px; - font-weight: 800; - background: var(--gradient-primary); - -webkit-background-clip: text; - -webkit-text-fill-color: transparent; - letter-spacing: -0.5px; -} - -.sidebar-nav { - flex: 1; - padding: 16px 12px; - overflow-y: auto; -} - -.nav-section-title { - font-size: 11px; - font-weight: 600; - color: var(--text-muted); - text-transform: uppercase; - letter-spacing: 1.2px; - padding: 16px 12px 8px; -} - -.nav-item { - display: flex; - align-items: center; - gap: 12px; - padding: 10px 12px; - border-radius: var(--radius-sm); +.desktop-collapse-btn { color: var(--text-secondary); - text-decoration: none; - font-size: 14px; - font-weight: 500; - cursor: pointer; - transition: all 0.2s ease; - border: none; - background: none; - width: 100%; - text-align: left; } -.nav-item:hover { - background: var(--bg-card); +.desktop-collapse-btn:hover { color: var(--text-primary); + background: rgba(255, 255, 255, 0.1); } -.nav-item.active { - background: var(--accent-glow); - color: var(--accent); - box-shadow: inset 3px 0 0 var(--accent); +.mobile-close-btn { + display: none; } -.nav-item svg { width: 20px; height: 20px; flex-shrink: 0; } +.nav-section-divider { + height: 1px; + background: var(--border-color); + margin: 16px 12px; +} -.sidebar-footer { - padding: 16px; - border-top: 1px solid var(--border-color); +.sidebar.collapsed .nav-item { + justify-content: center; + padding: 12px; +} + +.sidebar.collapsed .nav-item svg { + margin: 0; +} + +.sidebar.collapsed .plan-badge { + justify-content: center; + padding: 10px; +} + +.sidebar.collapsed .sidebar-footer { + padding: 16px 10px; } .plan-badge { @@ -174,6 +150,11 @@ body { flex: 1; margin-left: 260px; min-height: 100vh; + transition: margin-left 0.3s cubic-bezier(0.16, 1, 0.3, 1); +} + +.main-content.collapsed { + margin-left: 76px; } .topbar { @@ -997,3 +978,41 @@ tr:hover td { background: var(--bg-card-hover); } transform: translateY(0) scale(1); } } + +/* === VIEW MODE SELECTOR === */ +.view-mode-selector { + display: inline-flex; + background: var(--bg-secondary); + border: 1px solid var(--border-color); + border-radius: var(--radius-sm); + padding: 3px; + gap: 3px; +} + +.view-mode-btn { + display: flex; + align-items: center; + gap: 6px; + padding: 6px 14px; + border-radius: 4px; + border: none; + background: transparent; + color: var(--text-secondary); + font-size: 13px; + font-weight: 500; + cursor: pointer; + transition: all 0.2s ease; + font-family: inherit; +} + +.view-mode-btn:hover { + color: var(--text-primary); + background: rgba(255, 255, 255, 0.06); +} + +.view-mode-btn.active { + background: var(--accent); + color: white; + font-weight: 600; + box-shadow: 0 2px 10px rgba(108, 99, 255, 0.4); +} diff --git a/src/app/page.tsx b/src/app/page.tsx index f776a62..e9739ae 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -36,8 +36,24 @@ export default function Home() { const [currentPlan, setCurrentPlan] = useState('Professional'); const [isLocked, setIsLocked] = useState(false); const [loadingSubscription, setLoadingSubscription] = useState(true); + const [isSidebarCollapsed, setIsSidebarCollapsed] = useState(false); const router = useRouter(); + useEffect(() => { + const savedCollapse = localStorage.getItem('agendapro_sidebar_collapsed'); + if (savedCollapse === 'true') { + setIsSidebarCollapsed(true); + } + }, []); + + const toggleSidebarCollapse = () => { + setIsSidebarCollapsed(prev => { + const next = !prev; + localStorage.setItem('agendapro_sidebar_collapsed', String(next)); + return next; + }); + }; + const checkSubscriptionStatus = async () => { const slug = localStorage.getItem('agendapro_tenant_slug') || 'barbearia-premium'; @@ -130,7 +146,7 @@ export default function Home() { } return ( -
+
setSidebarOpen(false)} currentPlan={currentPlan} isLocked={isLocked} + isCollapsed={isSidebarCollapsed} + onToggleCollapse={toggleSidebarCollapse} /> -
+
i + 8); @@ -16,15 +16,24 @@ const statusBorders: Record = { completed: 'var(--success)', cancelled: 'var(--danger)', }; +const statusLabels: Record = { + confirmed: 'Confirmado', + pending: 'Pendente', + completed: 'Concluído', + cancelled: 'Cancelado', +}; export default function Agenda() { const [showModal, setShowModal] = useState(false); const today = new Date(); const [currentDate, setCurrentDate] = useState(today); + const [viewMode, setViewMode] = useState<'day' | 'week' | 'list'>('day'); const [appointments, setAppointments] = useState([]); const [professionals, setProfessionals] = useState([]); const [services, setServices] = useState([]); const [loading, setLoading] = useState(true); + const [filterProf, setFilterProf] = useState('all'); + const [filterStatus, setFilterStatus] = useState('all'); const fetchAgenda = async () => { try { @@ -89,18 +98,71 @@ export default function Agenda() { setNewDate(todayObj.toISOString().split('T')[0]); }; + const getWeekDays = (date: Date) => { + const start = new Date(date); + const dayOfWeek = start.getDay(); + start.setDate(start.getDate() - dayOfWeek); + const days = []; + for (let i = 0; i < 7; i++) { + const d = new Date(start); + d.setDate(d.getDate() + i); + days.push(d); + } + return days; + }; + + const weekDays = getWeekDays(currentDate); + if (loading && professionals.length === 0) { return
Carregando agenda...
; } return (
-
+ {/* BARRA SUPERIOR DE NAVEGAÇÃO E SELETOR DE MODO */} +
- -

{dateStr}

- + +

+ {viewMode === 'week' + ? `Semana de ${weekDays[0].getDate()}/${weekDays[0].getMonth()+1} a ${weekDays[6].getDate()}/${weekDays[6].getMonth()+1}` + : dateStr} +

+
+ + {/* SELETOR DE MODO DE VISÃO */} +
+ + + +
+
) : ( -
-
-
- {professionals.map(p => ( -
-
-
- {p.name} -
-
- ))} -
- -
-
- {hours.map(h => ( -
- {String(h).padStart(2, '0')}:00 -
- ))} -
- - {professionals.map(prof => { - const visibleAppointments = appointments.filter(a => a.professionalId === prof.id); - - return ( -
- {hours.map(h => ( -
- ))} - {visibleAppointments.map(apt => { - const scale = 2; // 2px per minute - const top = (apt.startHour - 8) * 60 * scale + (apt.startMin * scale); - const height = apt.duration * scale; - const isSmall = apt.duration <= 20; - - return ( -
{ (e.currentTarget as HTMLElement).style.transform = 'scale(1.02)'; (e.currentTarget as HTMLElement).style.zIndex = '10'; }} - onMouseLeave={e => { (e.currentTarget as HTMLElement).style.transform = 'scale(1)'; (e.currentTarget as HTMLElement).style.zIndex = '2'; }} - > -
- {apt.client} + <> + {/* MODO 1: GRADE DIÁRIA POR HORA (MANTIDA ORIGINAL) */} + {viewMode === 'day' && ( +
+
+
+ {professionals.map(p => ( +
+
+
+ {p.name}
-
- {isSmall ? `- ${apt.service}` : `${apt.service} • R$ ${apt.price}`} -
- {!isSmall && height >= 60 && ( -
- {String(apt.startHour).padStart(2, '0')}:{String(apt.startMin).padStart(2, '0')} - {apt.duration}min -
- )}
- ); - })} + ))} +
+ +
+
+ {hours.map(h => ( +
+ {String(h).padStart(2, '0')}:00 +
+ ))} +
+ + {professionals.map(prof => { + const visibleAppointments = appointments.filter(a => a.professionalId === prof.id); + + return ( +
+ {hours.map(h => ( +
+ ))} + {visibleAppointments.map(apt => { + const scale = 2; + const top = (apt.startHour - 8) * 60 * scale + (apt.startMin * scale); + const height = apt.duration * scale; + const isSmall = apt.duration <= 20; + + return ( +
{ (e.currentTarget as HTMLElement).style.transform = 'scale(1.02)'; (e.currentTarget as HTMLElement).style.zIndex = '10'; }} + onMouseLeave={e => { (e.currentTarget as HTMLElement).style.transform = 'scale(1)'; (e.currentTarget as HTMLElement).style.zIndex = '2'; }} + > +
+ {apt.client} +
+
+ {isSmall ? `- ${apt.service}` : `${apt.service} • R$ ${apt.price}`} +
+ {!isSmall && height >= 60 && ( +
+ {String(apt.startHour).padStart(2, '0')}:{String(apt.startMin).padStart(2, '0')} - {apt.duration}min +
+ )} +
+ ); + })} +
+ ); + })} +
- ); - })} -
-
+ )} + + {/* MODO 2: VISÃO SEMANAL DE 7 DIAS */} + {viewMode === 'week' && ( +
+
+ {weekDays.map(day => { + const isTodayDay = day.toDateString() === today.toDateString(); + const isSelectedDay = day.toDateString() === currentDate.toDateString(); + const dayName = day.toLocaleDateString('pt-BR', { weekday: 'short' }); + const dayNum = day.getDate(); + const dayMonth = day.toLocaleDateString('pt-BR', { month: 'short' }); + + return ( +
setCurrentDate(day)} + style={{ + padding: '14px 8px', textAlign: 'center', borderRight: '1px solid var(--border-color)', + background: isSelectedDay ? 'var(--accent-glow)' : 'var(--bg-secondary)', cursor: 'pointer', + transition: 'all 0.2s' + }} + > +
{dayName}
+
+ {dayNum} {dayMonth} +
+
+ ); + })} +
+ +
+ {weekDays.map(day => { + const dayStr = day.toISOString().split('T')[0]; + // Para homologação estática, os agendamentos do dia ativo usam a data selecionada + const isSelected = day.toDateString() === currentDate.toDateString(); + const dayApts = isSelected ? appointments : []; + + return ( +
+ {dayApts.length === 0 ? ( +
+ Sem horários +
+ ) : ( +
+ {dayApts.map(apt => { + const prof = professionals.find(p => p.id === apt.professionalId); + return ( +
+
{String(apt.startHour).padStart(2, '0')}:{String(apt.startMin).padStart(2, '0')}
+
{apt.client}
+
{apt.service}
+ {prof && ( +
+ ● {prof.name} +
+ )} +
+ ); + })} +
+ )} +
+ ); + })} +
+
+ )} + + {/* MODO 3: VISÃO EM LISTA DETALHADA */} + {viewMode === 'list' && ( +
+ {/* Filtros da Lista */} +
+ Filtrar Lista: + + +
+ + {/* Feed de Cards da Lista */} +
+ {appointments + .filter(a => filterProf === 'all' || a.professionalId === filterProf) + .filter(a => filterStatus === 'all' || a.status === filterStatus) + .map(apt => { + const prof = professionals.find(p => p.id === apt.professionalId); + return ( +
+
+ + {statusLabels[apt.status] || apt.status} + + + {String(apt.startHour).padStart(2, '0')}:{String(apt.startMin).padStart(2, '0')} ({apt.duration} min) + +
+

{apt.client}

+
+ {apt.service} • R$ {apt.price} +
+
+ {prof ? ( + +
{prof.name} + + ) : } + {apt.phone && ( + + WhatsApp + + )} +
+
+ ); + })} +
+
+ )} + )} {showModal && ( diff --git a/src/components/Sidebar.tsx b/src/components/Sidebar.tsx index 38d39bf..43e2637 100644 --- a/src/components/Sidebar.tsx +++ b/src/components/Sidebar.tsx @@ -1,5 +1,5 @@ import { useState, useEffect } from 'react'; -import { Calendar, Users, Scissors, LayoutDashboard, Settings, CreditCard, UserCheck, X, Crown, LogOut } from 'lucide-react'; +import { Calendar, Users, Scissors, LayoutDashboard, Settings, CreditCard, UserCheck, X, Crown, LogOut, PanelLeftClose, PanelLeftOpen } from 'lucide-react'; import { useRouter } from 'next/navigation'; interface SidebarProps { @@ -9,6 +9,8 @@ interface SidebarProps { onClose: () => void; currentPlan?: string; isLocked?: boolean; + isCollapsed?: boolean; + onToggleCollapse?: () => void; } const navItems = [ @@ -24,7 +26,7 @@ const navItems = [ { id: 'subscription', label: 'Assinatura', icon: CreditCard }, ]; -export default function Sidebar({ currentPage, onNavigate, isOpen, onClose, currentPlan, isLocked = false }: SidebarProps) { +export default function Sidebar({ currentPage, onNavigate, isOpen, onClose, currentPlan, isLocked = false, isCollapsed = false, onToggleCollapse }: SidebarProps) { const router = useRouter(); const [tenantName, setTenantName] = useState('AgendaPRO'); @@ -39,7 +41,6 @@ export default function Sidebar({ currentPage, onNavigate, isOpen, onClose, curr localStorage.removeItem('agendapro_token'); localStorage.removeItem('agendapro_tenant_name'); localStorage.removeItem('agendapro_plan'); - // Limpar cookies de sessão document.cookie = "agendapro_new_tenant=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;"; document.cookie = "agendapro_plan=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;"; router.push('/login'); @@ -48,13 +49,23 @@ export default function Sidebar({ currentPage, onNavigate, isOpen, onClose, curr return ( <> {isOpen &&
} -