diff --git a/web/src/pages/Dashboard.tsx b/web/src/pages/Dashboard.tsx index e2cfec2..35b7824 100644 --- a/web/src/pages/Dashboard.tsx +++ b/web/src/pages/Dashboard.tsx @@ -118,11 +118,7 @@ function DashboardInner({ shop }: { shop: BarberShop }) { const [activeTab, setActiveTab] = useState('overview'); const [period, setPeriod] = useState('semana'); - const [appointmentView, setAppointmentView] = useState<'list' | 'calendar'>('list'); const [currentWeek, setCurrentWeek] = useState(new Date()); - const [searchQuery, setSearchQuery] = useState(''); - const [includeCancelled, setIncludeCancelled] = useState(false); - const [selectedDate, setSelectedDate] = useState(new Date()); // Form states const [svcName, setSvcName] = useState(''); @@ -148,67 +144,7 @@ function DashboardInner({ shop }: { shop: BarberShop }) { const allShopAppointments = appointments.filter((a) => a.shopId === shop.id && periodMatch(parseDate(a.date))); // Agendamentos ativos (não concluídos e não cancelados) - const shopAppointments = allShopAppointments.filter((a) => a.status !== 'concluido'); - - // Agendamentos concluídos (histórico passado) - const completedAppointments = allShopAppointments.filter((a) => a.status === 'concluido'); - - // Estatísticas para lista de marcações (do dia selecionado) - const selectedDateAppointments = appointments.filter((a) => { - if (a.shopId !== shop.id) return false; - const aptDate = new Date(a.date.replace(' ', 'T')); - return ( - aptDate.getDate() === selectedDate.getDate() && - aptDate.getMonth() === selectedDate.getMonth() && - aptDate.getFullYear() === selectedDate.getFullYear() - ); - }); - - const totalBookingsToday = selectedDateAppointments.filter((a) => includeCancelled || a.status !== 'cancelado').length; - - const newClientsToday = useMemo(() => { - const clientIds = new Set(selectedDateAppointments.map((a) => a.customerId)); - return clientIds.size; - }, [selectedDateAppointments]); - - const onlineBookingsToday = selectedDateAppointments.filter((a) => a.status !== 'cancelado').length; - - const occupancyRate = useMemo(() => { - // Calcular ocupação baseada em slots disponíveis (8h-18h = 20 slots de 30min) - const totalSlots = 20; - const bookedSlots = selectedDateAppointments.filter((a) => a.status !== 'cancelado').length; - return Math.round((bookedSlots / totalSlots) * 100); - }, [selectedDateAppointments]); - - // Comparação com semana passada (simplificado - sempre 0% por enquanto) - const comparisonPercent = 0; - - // Filtrar agendamentos para lista - const filteredAppointments = useMemo(() => { - let filtered = selectedDateAppointments; - - if (!includeCancelled) { - filtered = filtered.filter((a) => a.status !== 'cancelado'); - } - - if (searchQuery) { - filtered = filtered.filter((a) => { - const service = shop.services.find((s) => s.id === a.serviceId); - const barber = shop.barbers.find((b) => b.id === a.barberId); - const customer = users.find((u) => u.id === a.customerId); - const searchLower = searchQuery.toLowerCase(); - return ( - service?.name.toLowerCase().includes(searchLower) || - barber?.name.toLowerCase().includes(searchLower) || - customer?.name.toLowerCase().includes(searchLower) || - customer?.email.toLowerCase().includes(searchLower) || - a.date.toLowerCase().includes(searchLower) - ); - }); - } - - return filtered; - }, [selectedDateAppointments, includeCancelled, searchQuery, shop.services, shop.barbers, users]); + const shopAppointments = allShopAppointments.filter((a) => a.status !== 'concluido' && a.status !== 'cancelado'); // Pedidos apenas com produtos (não serviços) const shopOrders = orders.filter( @@ -454,66 +390,32 @@ function DashboardInner({ shop }: { shop: BarberShop }) {
{/* Coluna Principal - Esquerda */}
- {/* Reservas de Hoje */} + {/* Calendário de Visão Geral */} -
-
- -
-
-

Reservas de hoje

-

Verá aqui as reservas de hoje assim que chegarem

-
-
- {(() => { - const today = new Date(); - const todayAppts = allShopAppointments.filter(a => { - const aptDate = new Date(a.date.replace(' ', 'T')); - return aptDate.toDateString() === today.toDateString(); - }); - - if (todayAppts.length === 0) { - return ( -
- -

Sem reservas hoje

- -
- ); - } - - return ( -
- {todayAppts.slice(0, 3).map(a => { - const svc = shop.services.find(s => s.id === a.serviceId); - const barber = shop.barbers.find(b => b.id === a.barberId); - const customer = users.find(u => u.id === a.customerId); - const aptDate = new Date(a.date.replace(' ', 'T')); - const timeStr = aptDate.toLocaleTimeString('pt-PT', { hour: '2-digit', minute: '2-digit' }); - - return ( -
-
-

{customer?.name || 'Cliente'}

-

{timeStr} · {svc?.name || 'Serviço'}

-
- - {a.status} - -
- ); - })} - {todayAppts.length > 3 && ( - - )} +
+
+
+
- ); - })()} +
+

Calendário de Reservas

+

Visão panorâmica da semana atual

+
+
+ +
+ + {/* Reutiliza o Componente de Calendário Inteiro na aba Overview */} +
@@ -635,311 +537,190 @@ function DashboardInner({ shop }: { shop: BarberShop }) { )} {activeTab === 'appointments' && ( -
- {/* View Toggle Buttons */} -
-
- - +
+
+
+

Caixa de Pedidos

+

Aprove ou recuse os pedidos pendentes e conclua os serviços de hoje.

- {shopAppointments.length} no período + {pendingAppts} Novos Pedidos
- {/* List View */} - {appointmentView === 'list' && ( -
- {/* Cards de Estatísticas */} -
- -
-
- -
- = 0 ? 'text-green-600' : 'text-red-600'}`}> - {comparisonPercent >= 0 ? '+' : ''}{comparisonPercent}% - -
-

Total de marcações

-

{totalBookingsToday}

-

- Comparado com {totalBookingsToday} no mesmo dia da semana passada -

-
- - -
-
- -
- = 0 ? 'text-green-600' : 'text-red-600'}`}> - {comparisonPercent >= 0 ? '+' : ''}{comparisonPercent}% - -
-

Novos clientes

-

{newClientsToday}

-

- Comparado com {newClientsToday} no mesmo dia da semana passada -

-
- - -
-
- -
- = 0 ? 'text-green-600' : 'text-red-600'}`}> - {comparisonPercent >= 0 ? '+' : ''}{comparisonPercent}% - -
-

Marcações online

-

{onlineBookingsToday}

-

- Comparado com {onlineBookingsToday} no mesmo dia da semana passada -

-
- - -
-
- -
- = 0 ? 'text-green-600' : 'text-red-600'}`}> - {comparisonPercent >= 0 ? '+' : ''}{comparisonPercent}% - -
-

Ocupação

-

{occupancyRate}%

-

- Comparado com {occupancyRate}% no mesmo dia da semana passada -

-
-
- - {/* Barra de Pesquisa e Filtros */} -
-
-
- - setSearchQuery(e.target.value)} - className="w-full rounded-lg border border-slate-300 bg-white px-10 py-2.5 text-sm text-slate-900 placeholder:text-slate-400 focus:border-indigo-500 focus:outline-none focus:ring-2 focus:ring-indigo-500/30 transition-all" - /> -
-
-
- - -
-
- - {/* Navegação de Data */} -
- -
- -
- {selectedDate.toLocaleDateString('pt-PT', { - weekday: 'long', - day: 'numeric', - month: 'numeric', - year: 'numeric' - })} -
- -
-
- - {/* Lista de Agendamentos */} - - {filteredAppointments.length > 0 ? ( -
- {filteredAppointments.map((a) => { - const svc = shop.services.find((s) => s.id === a.serviceId); - const barber = shop.barbers.find((b) => b.id === a.barberId); - const customer = users.find((u) => u.id === a.customerId); - const aptDate = new Date(a.date.replace(' ', 'T')); - const timeStr = aptDate.toLocaleTimeString('pt-PT', { hour: '2-digit', minute: '2-digit' }); - const dateStr = aptDate.toLocaleDateString('pt-PT', { day: 'numeric', month: 'long', year: 'numeric' }); - - return ( -
-
-
-

Cliente

-

{customer?.name || 'Cliente'}

-

{customer?.email || ''}

-
-
-

Serviço

-

{svc?.name ?? 'Serviço'}

-

{barber?.name ?? 'Barbeiro'}

-
-
-

Data e Hora

-

{dateStr}

-

{timeStr}

-
-
-

Status

-
- - {a.status === 'pendente' - ? 'Pendente' - : a.status === 'confirmado' - ? 'Confirmado' - : a.status === 'concluido' - ? 'Concluído' - : 'Cancelado'} - -

{currency(a.total)}

-
-
-
-
- -
-
- ); - })} -
- ) : ( -
- -

Sem reservas

-

- Ambas as suas reservas online e manuais aparecerão aqui -

-
- )} -
+ {/* Secção de Pedidos Pendentes */} + +
+ +

Aguardam Aprovação

+ {pendingAppts}
- )} + {shopAppointments.filter(a => a.status === 'pendente').length > 0 ? ( +
+ {shopAppointments.filter(a => a.status === 'pendente').map((a) => { + const svc = shop.services.find((s) => s.id === a.serviceId); + const barber = shop.barbers.find((b) => b.id === a.barberId); + const customer = users.find((u) => u.id === a.customerId); + const aptDate = new Date(a.date.replace(' ', 'T')); - {/* Calendar View */} - {appointmentView === 'calendar' && ( - - - - )} + return ( +
+
+
+

Cliente

+

{customer?.name || 'Cliente'}

+
+
+

Serviço

+

{svc?.name ?? 'Serviço'}

+

com {barber?.name ?? 'Barbeiro'}

+
+
+

Data / Hora

+

+ {aptDate.toLocaleDateString('pt-PT', { day: 'numeric', month: 'short' })} às {aptDate.toLocaleTimeString('pt-PT', { hour: '2-digit', minute: '2-digit' })} +

+
+
+
+ + +
+
+ ); + })} +
+ ) : ( +
+ +

Não há pedidos pendentes no momento.

+
+ )} +
+ + {/* Secção de Pedidos Confirmados */} + +
+ +

Agendamentos Aprovados

+ {confirmedAppts} +
+ {shopAppointments.filter(a => a.status === 'confirmado').length > 0 ? ( +
+ {shopAppointments.filter(a => a.status === 'confirmado').map((a) => { + const svc = shop.services.find((s) => s.id === a.serviceId); + const barber = shop.barbers.find((b) => b.id === a.barberId); + const customer = users.find((u) => u.id === a.customerId); + const aptDate = new Date(a.date.replace(' ', 'T')); + + return ( +
+
+
+

Cliente

+

{customer?.name || 'Cliente'}

+
+
+

Serviço

+

{svc?.name ?? 'Serviço'}

+

com {barber?.name ?? 'Barbeiro'}

+
+
+

Data / Hora

+

+ {aptDate.toLocaleDateString('pt-PT', { day: 'numeric', month: 'short' })} às {aptDate.toLocaleTimeString('pt-PT', { hour: '2-digit', minute: '2-digit' })} +

+
+
+
+ + +
+
+ ); + })} +
+ ) : ( +
+ +

Próximos serviços irão aparecer aqui.

+
+ )} +
)} - {activeTab === 'history' && ( - -
-

Histórico de Agendamentos

- {completedAppointments.length} concluídos -
-
- {completedAppointments.length > 0 ? ( - completedAppointments.map((a) => { - const svc = shop.services.find((s) => s.id === a.serviceId); - const barber = shop.barbers.find((b) => b.id === a.barberId); - return ( -
-
-
-

{svc?.name ?? 'Serviço'}

- Concluído + {activeTab === 'history' && (() => { + // O histórico agora compreende marcacões permanentemente finalizadas (Cancelados e Concluídos) + const historyAppointments = allShopAppointments.filter(a => a.status === 'concluido' || a.status === 'cancelado'); + + return ( + +
+

Histórico de Agendamentos

+ {historyAppointments.length} registos +
+
+ {historyAppointments.length > 0 ? ( + historyAppointments.map((a) => { + const svc = shop.services.find((s) => s.id === a.serviceId); + const barber = shop.barbers.find((b) => b.id === a.barberId); + const customer = users.find((u) => u.id === a.customerId); + const aptDate = new Date(a.date.replace(' ', 'T')); + + return ( +
+
+
+

{svc?.name ?? 'Serviço'}

+ + {a.status === 'concluido' ? 'Concluído' : 'Cancelado'} + +
+

+ {customer?.name || 'Cliente'} c/ {barber?.name ?? 'Barbeiro'} · + {aptDate.toLocaleDateString('pt-PT', { day: 'numeric', month: 'short' })} +

+

{currency(a.total)}

-

{barber?.name ?? 'Barbeiro'} · {a.date}

-

{currency(a.total)}

-
- ); - }) - ) : ( -
- -

Nenhum agendamento concluído no período

-

Os agendamentos concluídos aparecerão aqui

-
- )} -
- - )} + ); + }) + ) : ( +
+ +

Nenhum registo no período selecionado

+

Os agendamentos cancelados ou concluídos aparecerão aqui.

+
+ )} +
+ + ); + })()} {activeTab === 'orders' && (