From f73095e3d4261f46c581bdf8c0bb446176eb0c8b Mon Sep 17 00:00:00 2001 From: yagostn Date: Fri, 5 Dec 2025 10:43:48 -0300 Subject: [PATCH] feat: mudancas de layout --- frontend/App.tsx | 4 - frontend/components/EventFiltersBar.tsx | 225 +++++++++++ frontend/components/EventTable.tsx | 174 +++++++++ frontend/components/Navbar.tsx | 1 - frontend/contexts/DataContext.tsx | 485 +++++++++++++++++++++++- frontend/pages/Calendar.tsx | 396 ++++++++++--------- frontend/pages/Dashboard.tsx | 328 +++++++++------- frontend/pages/Login.tsx | 100 ++--- frontend/pages/Register.tsx | 50 +-- 9 files changed, 1297 insertions(+), 466 deletions(-) create mode 100644 frontend/components/EventFiltersBar.tsx create mode 100644 frontend/components/EventTable.tsx diff --git a/frontend/App.tsx b/frontend/App.tsx index 46adf23..cd94801 100644 --- a/frontend/App.tsx +++ b/frontend/App.tsx @@ -4,7 +4,6 @@ import { Home } from "./pages/Home"; import { Dashboard } from "./pages/Dashboard"; import { Login } from "./pages/Login"; import { Register } from "./pages/Register"; -import { CalendarPage } from "./pages/Calendar"; import { TeamPage } from "./pages/Team"; import { FinancePage } from "./pages/Finance"; import { SettingsPage } from "./pages/Settings"; @@ -54,9 +53,6 @@ const AppContent: React.FC = () => { case "inspiration": return ; - case "calendar": - return ; - case "team": return ; diff --git a/frontend/components/EventFiltersBar.tsx b/frontend/components/EventFiltersBar.tsx new file mode 100644 index 0000000..1b85106 --- /dev/null +++ b/frontend/components/EventFiltersBar.tsx @@ -0,0 +1,225 @@ +import React from 'react'; +import { Calendar, MapPin, Clock, Filter, X } from 'lucide-react'; + +export interface EventFilters { + date: string; + city: string; + state: string; + timeRange: string; + type: string; +} + +interface EventFiltersBarProps { + filters: EventFilters; + onFilterChange: (filters: EventFilters) => void; + availableCities: string[]; + availableStates: string[]; + availableTypes: string[]; +} + +export const EventFiltersBar: React.FC = ({ + filters, + onFilterChange, + availableCities, + availableStates, + availableTypes, +}) => { + const handleReset = () => { + onFilterChange({ + date: '', + city: '', + state: '', + timeRange: '', + type: '', + }); + }; + + const hasActiveFilters = Object.values(filters).some(value => value !== ''); + + const timeRanges = [ + { value: '', label: 'Todos os horários' }, + { value: 'morning', label: 'Manhã (06:00 - 12:00)' }, + { value: 'afternoon', label: 'Tarde (12:00 - 18:00)' }, + { value: 'evening', label: 'Noite (18:00 - 23:59)' }, + ]; + + return ( +
+
+
+ +

Filtros Avançados

+
+ {hasActiveFilters && ( + + )} +
+ +
+ {/* Filtro por Data */} +
+ + onFilterChange({ ...filters, date: e.target.value })} + className="px-3 py-2 border border-gray-300 rounded text-sm focus:outline-none focus:border-brand-gold transition-colors" + /> +
+ + {/* Filtro por Estado */} +
+ + +
+ + {/* Filtro por Cidade */} +
+ + +
+ + {/* Filtro por Horário */} +
+ + +
+ + {/* Filtro por Tipo */} +
+ + +
+
+ + {/* Active Filters Display */} + {hasActiveFilters && ( +
+
+ Filtros ativos: + {filters.date && ( + + Data: {new Date(filters.date + 'T00:00:00').toLocaleDateString('pt-BR')} + + + )} + {filters.state && ( + + Estado: {filters.state} + + + )} + {filters.city && ( + + Cidade: {filters.city} + + + )} + {filters.timeRange && ( + + {timeRanges.find(r => r.value === filters.timeRange)?.label} + + + )} + {filters.type && ( + + Tipo: {filters.type} + + + )} +
+
+ )} +
+ ); +}; diff --git a/frontend/components/EventTable.tsx b/frontend/components/EventTable.tsx new file mode 100644 index 0000000..917eeca --- /dev/null +++ b/frontend/components/EventTable.tsx @@ -0,0 +1,174 @@ +import React from 'react'; +import { EventData, EventStatus, UserRole } from '../types'; +import { Calendar, MapPin, Users, CheckCircle, Clock } from 'lucide-react'; +import { STATUS_COLORS } from '../constants'; + +interface EventTableProps { + events: EventData[]; + onEventClick: (event: EventData) => void; + onApprove?: (e: React.MouseEvent, eventId: string) => void; + userRole: UserRole; +} + +export const EventTable: React.FC = ({ + events, + onEventClick, + onApprove, + userRole +}) => { + const canApprove = (userRole === UserRole.BUSINESS_OWNER || userRole === UserRole.SUPERADMIN); + + const formatDate = (date: string) => { + const eventDate = new Date(date + 'T00:00:00'); + return eventDate.toLocaleDateString('pt-BR', { + day: '2-digit', + month: '2-digit', + year: 'numeric' + }); + }; + + const getStatusDisplay = (status: EventStatus) => { + const statusLabels: Record = { + [EventStatus.PENDING_APPROVAL]: 'Pendente', + [EventStatus.CONFIRMED]: 'Confirmado', + [EventStatus.PLANNING]: 'Planejamento', + [EventStatus.IN_PROGRESS]: 'Em Andamento', + [EventStatus.COMPLETED]: 'Concluído', + [EventStatus.ARCHIVED]: 'Arquivado', + }; + return statusLabels[status] || status; + }; + + return ( +
+
+ + + + {canApprove && ( + + )} + + + + + + + + + + + + {events.map((event) => ( + onEventClick(event)} + className="hover:bg-gray-50 cursor-pointer transition-colors" + > + {canApprove && ( + + )} + + + + + + + + + + ))} + +
+ Ações + + Nome do Evento + + Tipo + + Data + + Horário + + Local + + Contatos + + Equipe + + Status +
e.stopPropagation()}> + {event.status === EventStatus.PENDING_APPROVAL && ( + + )} + +
{event.name}
+
+ {event.type} + +
+ + {formatDate(event.date)} +
+
+
+ + {event.time} +
+
+
+ + + {event.address.city}, {event.address.state} + +
+
+
+ + {event.contacts.length} +
+
+ {event.photographerIds.length > 0 ? ( +
+ {event.photographerIds.slice(0, 3).map((id, idx) => ( +
+ ))} + {event.photographerIds.length > 3 && ( +
+ +{event.photographerIds.length - 3} +
+ )} +
+ ) : ( + - + )} +
+ + {getStatusDisplay(event.status)} + +
+
+ + {events.length === 0 && ( +
+

Nenhum evento encontrado.

+
+ )} +
+ ); +}; diff --git a/frontend/components/Navbar.tsx b/frontend/components/Navbar.tsx index 234776c..afae82c 100644 --- a/frontend/components/Navbar.tsx +++ b/frontend/components/Navbar.tsx @@ -63,7 +63,6 @@ export const Navbar: React.FC = ({ onNavigate, currentPage }) => { case UserRole.PHOTOGRAPHER: return [ { name: "Eventos Designados", path: "dashboard" }, - { name: "Agenda", path: "calendar" }, ]; default: return []; diff --git a/frontend/contexts/DataContext.tsx b/frontend/contexts/DataContext.tsx index 3fb547e..7aa2f24 100644 --- a/frontend/contexts/DataContext.tsx +++ b/frontend/contexts/DataContext.tsx @@ -25,10 +25,10 @@ const INITIAL_INSTITUTIONS: Institution[] = [ const INITIAL_EVENTS: EventData[] = [ { id: "1", - name: "Casamento Juliana & Marcos", - date: "2024-10-15", - time: "16:00", - type: EventType.WEDDING, + name: "Formatura Engenharia Civil", + date: "2025-12-05", + time: "19:00", + type: EventType.GRADUATION, status: EventStatus.CONFIRMED, address: { street: "Av. das Hortênsias", @@ -37,30 +37,29 @@ const INITIAL_EVENTS: EventData[] = [ state: "RS", zip: "95670-000", }, - briefing: - "Cerimônia ao pôr do sol. Foco em fotos espontâneas dos noivos e pais.", + briefing: "Cerimônia de formatura com 120 formandos. Foco em fotos individuais e da turma.", coverImage: "https://picsum.photos/id/1059/800/400", contacts: [ { id: "c1", - name: "Cerimonial Silva", - role: "Cerimonialista", - phone: "9999-9999", - email: "c@teste.com", + name: "Comissão de Formatura", + role: "Organizador", + phone: "51 99999-1111", + email: "formatura@email.com", }, ], checklist: [], ownerId: "client-1", - photographerIds: ["photographer-1"], + photographerIds: ["photographer-1", "photographer-2"], institutionId: "inst-1", }, { id: "2", - name: "Conferência Tech Innovators", - date: "2024-11-05", - time: "08:00", - type: EventType.CORPORATE, - status: EventStatus.PENDING_APPROVAL, + name: "Colação de Grau Medicina", + date: "2025-12-05", + time: "10:00", + type: EventType.COLATION, + status: EventStatus.CONFIRMED, address: { street: "Rua Olimpíadas", number: "205", @@ -68,13 +67,463 @@ const INITIAL_EVENTS: EventData[] = [ state: "SP", zip: "04551-000", }, - briefing: "Cobrir palestras principais e networking no coffee break.", + briefing: "Colação de grau solene. Capturar juramento e entrega de diplomas.", coverImage: "https://picsum.photos/id/3/800/400", + contacts: [ + { + id: "c2", + name: "Secretaria Acadêmica", + role: "Coordenador", + phone: "11 98888-2222", + email: "academico@med.br", + }, + ], + checklist: [], + ownerId: "client-1", + photographerIds: ["photographer-1"], + }, + { + id: "3", + name: "Semana Acadêmica Direito", + date: "2025-12-05", + time: "14:00", + type: EventType.ACADEMIC_WEEK, + status: EventStatus.IN_PROGRESS, + address: { + street: "Av. Paulista", + number: "1500", + city: "São Paulo", + state: "SP", + zip: "01310-100", + }, + briefing: "Palestras e painéis durante toda a semana. Cobertura de 3 dias.", + coverImage: "https://picsum.photos/id/10/800/400", contacts: [], checklist: [], - ownerId: "client-2", // Other client + ownerId: "client-2", + photographerIds: ["photographer-2"], + }, + { + id: "4", + name: "Defesa de Doutorado - Maria Silva", + date: "2025-12-05", + time: "15:30", + type: EventType.DEFENSE, + status: EventStatus.CONFIRMED, + address: { + street: "Rua Ramiro Barcelos", + number: "2600", + city: "Porto Alegre", + state: "RS", + zip: "90035-003", + }, + briefing: "Defesa de tese em sala fechada. Fotos discretas da apresentação e banca.", + coverImage: "https://picsum.photos/id/20/800/400", + contacts: [ + { + id: "c3", + name: "Prof. João Santos", + role: "Orientador", + phone: "51 97777-3333", + email: "joao@univ.br", + }, + ], + checklist: [], + ownerId: "client-1", + photographerIds: ["photographer-1"], + }, + { + id: "5", + name: "Semana de Calouros 2026", + date: "2025-12-06", + time: "09:00", + type: EventType.FRESHMAN_WEEK, + status: EventStatus.PENDING_APPROVAL, + address: { + street: "Campus Universitário", + number: "s/n", + city: "Curitiba", + state: "PR", + zip: "80060-000", + }, + briefing: "Recepção dos calouros com atividades de integração e gincanas.", + coverImage: "https://picsum.photos/id/30/800/400", + contacts: [], + checklist: [], + ownerId: "client-2", photographerIds: [], }, + { + id: "6", + name: "Formatura Administração", + date: "2025-12-06", + time: "20:00", + type: EventType.GRADUATION, + status: EventStatus.CONFIRMED, + address: { + street: "Av. Ipiranga", + number: "6681", + city: "Porto Alegre", + state: "RS", + zip: "90619-900", + }, + briefing: "Formatura noturna com jantar. Fotos da cerimônia e festa.", + coverImage: "https://picsum.photos/id/40/800/400", + contacts: [ + { + id: "c4", + name: "Lucas Oliveira", + role: "Presidente da Comissão", + phone: "51 96666-4444", + email: "lucas@formatura.com", + }, + ], + checklist: [], + ownerId: "client-1", + photographerIds: ["photographer-2"], + }, + { + id: "7", + name: "Congresso de Tecnologia", + date: "2025-12-06", + time: "08:30", + type: EventType.SYMPOSIUM, + status: EventStatus.CONFIRMED, + address: { + street: "Av. das Nações Unidas", + number: "12901", + city: "São Paulo", + state: "SP", + zip: "04578-000", + }, + briefing: "Congresso com múltiplas salas. Cobrir palestrantes principais e stands.", + coverImage: "https://picsum.photos/id/50/800/400", + contacts: [ + { + id: "c5", + name: "Eventos Tech", + role: "Organizadora", + phone: "11 95555-5555", + email: "contato@eventostech.com", + }, + ], + checklist: [], + ownerId: "client-2", + photographerIds: ["photographer-1", "photographer-3"], + }, + { + id: "8", + name: "Campeonato Universitário de Futsal", + date: "2025-12-06", + time: "16:00", + type: EventType.SPORTS_EVENT, + status: EventStatus.CONFIRMED, + address: { + street: "Rua dos Esportes", + number: "500", + city: "Gramado", + state: "RS", + zip: "95670-100", + }, + briefing: "Final do campeonato. Fotos dinâmicas da partida e premiação.", + coverImage: "https://picsum.photos/id/60/800/400", + contacts: [], + checklist: [], + ownerId: "client-1", + photographerIds: ["photographer-3"], + }, + { + id: "9", + name: "Colação de Grau Odontologia", + date: "2025-12-07", + time: "11:00", + type: EventType.COLATION, + status: EventStatus.PLANNING, + address: { + street: "Rua Voluntários da Pátria", + number: "89", + city: "Porto Alegre", + state: "RS", + zip: "90230-010", + }, + briefing: "Cerimônia formal de colação. Fotos individuais e em grupo.", + coverImage: "https://picsum.photos/id/70/800/400", + contacts: [ + { + id: "c6", + name: "Direção da Faculdade", + role: "Coordenador", + phone: "51 94444-6666", + email: "direcao@odonto.edu", + }, + ], + checklist: [], + ownerId: "client-1", + photographerIds: ["photographer-2"], + }, + { + id: "10", + name: "Festival Cultural Universitário", + date: "2025-12-07", + time: "18:00", + type: EventType.CULTURAL_EVENT, + status: EventStatus.CONFIRMED, + address: { + street: "Praça da República", + number: "s/n", + city: "São Paulo", + state: "SP", + zip: "01045-000", + }, + briefing: "Festival com apresentações musicais e teatrais. Cobertura completa.", + coverImage: "https://picsum.photos/id/80/800/400", + contacts: [], + checklist: [], + ownerId: "client-2", + photographerIds: ["photographer-1"], + }, + { + id: "11", + name: "Defesa de Mestrado - Pedro Costa", + date: "2025-12-07", + time: "14:00", + type: EventType.DEFENSE, + status: EventStatus.CONFIRMED, + address: { + street: "Av. Bento Gonçalves", + number: "9500", + city: "Porto Alegre", + state: "RS", + zip: "91509-900", + }, + briefing: "Defesa de dissertação. Registro da apresentação e momento da aprovação.", + coverImage: "https://picsum.photos/id/90/800/400", + contacts: [], + checklist: [], + ownerId: "client-1", + photographerIds: ["photographer-3"], + }, + { + id: "12", + name: "Formatura Psicologia", + date: "2025-12-08", + time: "19:30", + type: EventType.GRADUATION, + status: EventStatus.CONFIRMED, + address: { + street: "Av. Protásio Alves", + number: "7000", + city: "Porto Alegre", + state: "RS", + zip: "91310-000", + }, + briefing: "Formatura emotiva com homenagens. Foco em momentos especiais.", + coverImage: "https://picsum.photos/id/100/800/400", + contacts: [ + { + id: "c7", + name: "Ana Paula", + role: "Formanda", + phone: "51 93333-7777", + email: "ana@email.com", + }, + ], + checklist: [], + ownerId: "client-1", + photographerIds: ["photographer-1", "photographer-2"], + }, + { + id: "13", + name: "Simpósio de Engenharia", + date: "2025-12-08", + time: "09:00", + type: EventType.SYMPOSIUM, + status: EventStatus.CONFIRMED, + address: { + street: "Av. Sertório", + number: "6600", + city: "Porto Alegre", + state: "RS", + zip: "91040-000", + }, + briefing: "Apresentações técnicas e workshops. Cobrir painéis principais.", + coverImage: "https://picsum.photos/id/110/800/400", + contacts: [], + checklist: [], + ownerId: "client-1", + photographerIds: ["photographer-2"], + }, + { + id: "14", + name: "Torneio de Vôlei Universitário", + date: "2025-12-08", + time: "15:00", + type: EventType.SPORTS_EVENT, + status: EventStatus.IN_PROGRESS, + address: { + street: "Rua Faria Santos", + number: "100", + city: "Curitiba", + state: "PR", + zip: "80060-150", + }, + briefing: "Semifinais e final. Fotos de ação e torcida.", + coverImage: "https://picsum.photos/id/120/800/400", + contacts: [], + checklist: [], + ownerId: "client-2", + photographerIds: ["photographer-3"], + }, + { + id: "15", + name: "Colação de Grau Enfermagem", + date: "2025-12-09", + time: "10:30", + type: EventType.COLATION, + status: EventStatus.CONFIRMED, + address: { + street: "Rua São Manoel", + number: "963", + city: "São Paulo", + state: "SP", + zip: "01330-001", + }, + briefing: "Colação com juramento de Florence Nightingale. Momento solene.", + coverImage: "https://picsum.photos/id/130/800/400", + contacts: [ + { + id: "c8", + name: "Coordenação de Enfermagem", + role: "Coordenador", + phone: "11 92222-8888", + email: "coord@enf.br", + }, + ], + checklist: [], + ownerId: "client-2", + photographerIds: ["photographer-1"], + }, + { + id: "16", + name: "Semana Acadêmica Biomedicina", + date: "2025-12-09", + time: "13:00", + type: EventType.ACADEMIC_WEEK, + status: EventStatus.PLANNING, + address: { + street: "Av. Independência", + number: "2293", + city: "Porto Alegre", + state: "RS", + zip: "90035-075", + }, + briefing: "Palestras e atividades práticas. Cobertura de 2 dias.", + coverImage: "https://picsum.photos/id/140/800/400", + contacts: [], + checklist: [], + ownerId: "client-1", + photographerIds: [], + }, + { + id: "17", + name: "Formatura Ciências Contábeis", + date: "2025-12-09", + time: "20:30", + type: EventType.GRADUATION, + status: EventStatus.CONFIRMED, + address: { + street: "Av. das Américas", + number: "3500", + city: "Gramado", + state: "RS", + zip: "95670-200", + }, + briefing: "Formatura elegante em hotel. Cobertura completa da cerimônia e recepção.", + coverImage: "https://picsum.photos/id/150/800/400", + contacts: [ + { + id: "c9", + name: "Rodrigo Almeida", + role: "Tesoureiro", + phone: "51 91111-9999", + email: "rodrigo@turma.com", + }, + ], + checklist: [], + ownerId: "client-1", + photographerIds: ["photographer-2", "photographer-3"], + }, + { + id: "18", + name: "Defesa de TCC - Turma 2025", + date: "2025-12-09", + time: "16:30", + type: EventType.DEFENSE, + status: EventStatus.CONFIRMED, + address: { + street: "Rua Marquês do Pombal", + number: "2000", + city: "Porto Alegre", + state: "RS", + zip: "90540-000", + }, + briefing: "Múltiplas defesas sequenciais. Fotos rápidas de cada apresentação.", + coverImage: "https://picsum.photos/id/160/800/400", + contacts: [], + checklist: [], + ownerId: "client-1", + photographerIds: ["photographer-1"], + }, + { + id: "19", + name: "Festival de Música Universitária", + date: "2025-12-10", + time: "19:00", + type: EventType.CULTURAL_EVENT, + status: EventStatus.PENDING_APPROVAL, + address: { + street: "Parque da Redenção", + number: "s/n", + city: "Porto Alegre", + state: "RS", + zip: "90040-000", + }, + briefing: "Festival ao ar livre com várias bandas. Fotos de palco e público.", + coverImage: "https://picsum.photos/id/170/800/400", + contacts: [], + checklist: [], + ownerId: "client-2", + photographerIds: [], + }, + { + id: "20", + name: "Colação de Grau Arquitetura", + date: "2025-12-10", + time: "11:30", + type: EventType.COLATION, + status: EventStatus.CONFIRMED, + address: { + street: "Av. Borges de Medeiros", + number: "1501", + city: "Gramado", + state: "RS", + zip: "95670-300", + }, + briefing: "Cerimônia especial com exposição de projetos. Fotos criativas.", + coverImage: "https://picsum.photos/id/180/800/400", + contacts: [ + { + id: "c10", + name: "Atelier Arquitetura", + role: "Escritório Parceiro", + phone: "51 90000-1010", + email: "contato@atelier.arq", + }, + ], + checklist: [], + ownerId: "client-1", + photographerIds: ["photographer-3"], + }, ]; interface DataContextType { diff --git a/frontend/pages/Calendar.tsx b/frontend/pages/Calendar.tsx index 2411358..da8c758 100644 --- a/frontend/pages/Calendar.tsx +++ b/frontend/pages/Calendar.tsx @@ -157,221 +157,207 @@ export const CalendarPage: React.FC = () => { }); return ( -
-
+
+
{/* Header */} -
-
-

- Minha Agenda -

-

- Gerencie seus eventos e compromissos fotográficos -

-
-
-
+
+

+ Minha Agenda +

+

+ Gerencie seus eventos e compromissos fotográficos +

-
- {/* Calendar Section */} -
- {/* Calendar Card */} -
- {/* Calendar Header */} -
-
- -

- {currentMonthName} -

- -
- - {/* Stats */} -
-
-

Total

-

{monthEvents.length}

-
-
-

Confirmados

-

- {monthEvents.filter(e => e.status === 'confirmed').length} -

-
-
-

Pendentes

-

- {monthEvents.filter(e => e.status === 'pending').length} -

-
-
+
+ {/* Calendar Card */} +
+ {/* Header */} +
+
+ +

+ {currentMonthName} +

+
+
- {/* Calendar Grid */} -
- {/* Week Days Header */} -
- {['Dom', 'Seg', 'Ter', 'Qua', 'Qui', 'Sex', 'Sáb'].map((day) => ( -
+ {/* Calendar Grid */} +
+ {/* Week Days */} +
+ {['D', 'S', 'T', 'Q', 'Q', 'S', 'S'].map((day, idx) => ( +
+ {day} -
- ))} -
- - {/* Calendar Days */} -
- {calendarDays.map((day, index) => { - if (!day) { - return
; - } - - const dayEvents = getEventsForDate(day); - const today = isToday(day); - - return ( -
0 - ? 'border-[#B9CF32] bg-[#B9CF32]/5 hover:bg-[#B9CF32]/10' - : 'border-gray-200 hover:border-gray-300 hover:bg-gray-50' - }`} - > -
- 0 - ? 'text-gray-900' - : 'text-gray-600' - }`} - > - {day.getDate()} - - {dayEvents.length > 0 && ( -
- {dayEvents.slice(0, 1).map((event) => ( -
- {event.title} -
- ))} - {dayEvents.length > 1 && ( - - +{dayEvents.length - 1} - - )} -
- )} -
-
- ); - })} -
-
-
- - {/* Legend */} -
-

Legenda

-
-
-
- Confirmado -
-
-
- Pendente -
-
-
- Concluído -
-
-
- Formatura -
-
-
- Casamento -
-
-
- Evento -
-
-
-
- - {/* Events List Sidebar */} -
- {/* Search */} -
-
- - -
-
- - {/* Upcoming Events */} -
-

- - Próximos Eventos -

-
- {MOCK_EVENTS.slice(0, 5).map((event) => ( -
-
-

{event.title}

- - {getStatusLabel(event.status)} - -
-
-
- - {new Date(event.date).toLocaleDateString('pt-BR')} às {event.time} -
-
- - {event.location} -
-
- - {event.client} -
-
+
))}
+ + {/* Days */} +
+ {calendarDays.map((day, index) => { + if (!day) { + return
; + } + + const dayEvents = getEventsForDate(day); + const today = isToday(day); + + return ( +
0 + ? 'border-brand-black/20 bg-brand-black text-white hover:border-brand-gold' + : 'border-gray-200 text-gray-700 hover:border-gray-300 hover:bg-gray-50' + }`} + > + + {day.getDate()} + + {dayEvents.length > 0 && !today && ( +
+ {dayEvents.slice(0, 3).map((_, i) => ( +
+ ))} +
+ )} +
+ ); + })} +
+ + {/* Stats Footer */} +
+
+
+

Total

+

{monthEvents.length}

+
+
+

Confirmados

+

+ {monthEvents.filter(e => e.status === 'confirmed').length} +

+
+
+

Pendentes

+

+ {monthEvents.filter(e => e.status === 'pending').length} +

+
+
+
+
+ + {/* Search Bar */} +
+
+ + +
+
+ + {/* Events List - Table Format */} +
+
+ + + + + + + + + + + + + {monthEvents.map((event) => ( + + + + + + + + + ))} + +
+ Evento + + Data + + Horário + + Local + + Cliente + + Status +
+
+
+ {event.title} +
+
+
+ + {new Date(event.date + 'T00:00:00').toLocaleDateString('pt-BR')} +
+
+
+ + {event.time} +
+
+
+ + {event.location} +
+
+
+ + {event.client} +
+
+ + {getStatusLabel(event.status)} + +
+
+ + {monthEvents.length === 0 && ( +
+

Nenhum evento encontrado neste mês.

+
+ )}
diff --git a/frontend/pages/Dashboard.tsx b/frontend/pages/Dashboard.tsx index 6a48322..79ff528 100644 --- a/frontend/pages/Dashboard.tsx +++ b/frontend/pages/Dashboard.tsx @@ -1,6 +1,7 @@ -import React, { useState, useEffect } from "react"; +import React, { useState, useEffect, useMemo } from "react"; import { UserRole, EventData, EventStatus, EventType } from "../types"; -import { EventCard } from "../components/EventCard"; +import { EventTable } from "../components/EventTable"; +import { EventFiltersBar, EventFilters } from "../components/EventFiltersBar"; import { EventForm } from "../components/EventForm"; import { Button } from "../components/Button"; import { @@ -12,6 +13,8 @@ import { Users, Map, Building2, + Calendar, + MapPin, } from "lucide-react"; import { useAuth } from "../contexts/AuthContext"; import { useData } from "../contexts/DataContext"; @@ -39,6 +42,13 @@ export const Dashboard: React.FC = ({ const [searchTerm, setSearchTerm] = useState(""); const [selectedEvent, setSelectedEvent] = useState(null); const [activeFilter, setActiveFilter] = useState("all"); + const [advancedFilters, setAdvancedFilters] = useState({ + date: '', + city: '', + state: '', + timeRange: '', + type: '', + }); // Reset view when initialView prop changes useEffect(() => { @@ -54,6 +64,31 @@ export const Dashboard: React.FC = ({ const myEvents = getEventsByRole(user.id, user.role); + // Extract unique values for filters + const { availableStates, availableCities, availableTypes } = useMemo(() => { + const states = [...new Set(myEvents.map(e => e.address.state))].sort(); + const cities = advancedFilters.state + ? [...new Set(myEvents + .filter(e => e.address.state === advancedFilters.state) + .map(e => e.address.city))].sort() + : []; + const types = [...new Set(myEvents.map(e => e.type))].sort(); + return { availableStates: states, availableCities: cities, availableTypes: types }; + }, [myEvents, advancedFilters.state]); + + // Helper function to check time range + const isInTimeRange = (time: string, range: string): boolean => { + if (!range) return true; + const [hours] = time.split(':').map(Number); + + switch (range) { + case 'morning': return hours >= 6 && hours < 12; + case 'afternoon': return hours >= 12 && hours < 18; + case 'evening': return hours >= 18 || hours < 6; + default: return true; + } + }; + // Filter Logic const filteredEvents = myEvents.filter((e) => { const matchesSearch = e.name @@ -66,7 +101,16 @@ export const Dashboard: React.FC = ({ (activeFilter === "active" && e.status !== EventStatus.ARCHIVED && e.status !== EventStatus.PENDING_APPROVAL); - return matchesSearch && matchesStatus; + + // Advanced filters + const matchesDate = !advancedFilters.date || e.date === advancedFilters.date; + const matchesState = !advancedFilters.state || e.address.state === advancedFilters.state; + const matchesCity = !advancedFilters.city || e.address.city === advancedFilters.city; + const matchesTimeRange = isInTimeRange(e.time, advancedFilters.timeRange); + const matchesType = !advancedFilters.type || e.type === advancedFilters.type; + + return matchesSearch && matchesStatus && matchesDate && matchesState && + matchesCity && matchesTimeRange && matchesType; }); const handleSaveEvent = (data: any) => { @@ -183,27 +227,7 @@ export const Dashboard: React.FC = ({ ); }; - const renderAdminActions = (event: EventData) => { - if ( - user.role !== UserRole.BUSINESS_OWNER && - user.role !== UserRole.SUPERADMIN - ) - return null; - if (event.status === EventStatus.PENDING_APPROVAL) { - return ( -
- -
- ); - } - return null; - }; // --- MAIN RENDER --- @@ -221,7 +245,7 @@ export const Dashboard: React.FC = ({ {/* Content Switcher */} {view === "list" && (
- {/* Filters Bar */} + {/* Search Bar */}
@@ -259,29 +283,32 @@ export const Dashboard: React.FC = ({ )}
- {/* Grid */} -
- {filteredEvents.map((event) => ( -
- {renderAdminActions(event)} - { - setSelectedEvent(event); - setView("details"); - }} - /> -
- ))} + {/* Advanced Filters */} + + + {/* Results Count */} +
+ + Exibindo {filteredEvents.length} de {myEvents.length} eventos +
- {filteredEvents.length === 0 && ( -
-

- Nenhum evento encontrado com os filtros atuais. -

-
- )} + {/* Event Table */} + { + setSelectedEvent(event); + setView("details"); + }} + onApprove={handleApprove} + userRole={user.role} + />
)} @@ -319,51 +346,85 @@ export const Dashboard: React.FC = ({ )}
-
- Cover -
-

- {selectedEvent.name} -

+ {/* Header Section - Sem foto */} +
+
+
+

+ {selectedEvent.name} +

+
+ + + {new Date(selectedEvent.date + 'T00:00:00').toLocaleDateString('pt-BR')} às {selectedEvent.time} + + + + {selectedEvent.address.city}, {selectedEvent.address.state} + +
+
+
+ {selectedEvent.status} +
-
-
-
- {/* Actions Toolbar */} -
- {(user.role === UserRole.BUSINESS_OWNER || - user.role === UserRole.SUPERADMIN) && ( - <> - - - - )} - {user.role === UserRole.EVENT_OWNER && - selectedEvent.status !== EventStatus.ARCHIVED && ( - - )} +
+ {/* Actions Toolbar */} +
+ {(user.role === UserRole.BUSINESS_OWNER || + user.role === UserRole.SUPERADMIN) && ( + <> + + + + )} + {user.role === UserRole.EVENT_OWNER && + selectedEvent.status !== EventStatus.ARCHIVED && ( + + )} + +
+ +
+
+ {/* Quick Info Cards */} +
+
+

Tipo

+

{selectedEvent.type}

+
+
+

Data

+

{new Date(selectedEvent.date + 'T00:00:00').toLocaleDateString('pt-BR')}

+
+
+

Horário

+

{selectedEvent.time}

+
{/* Institution Information */} @@ -467,89 +528,64 @@ export const Dashboard: React.FC = ({ )}
-
-
-

- Status Atual -

-

- {selectedEvent.status} -

-
- -
-

+
+ {/* Localização Card */} +
+

+ Localização

-

- {selectedEvent.address.street},{" "} - {selectedEvent.address.number} +

+ {selectedEvent.address.street}, {selectedEvent.address.number}

-

- {selectedEvent.address.city} -{" "} - {selectedEvent.address.state} +

+ {selectedEvent.address.city} - {selectedEvent.address.state}

- - {selectedEvent.address.mapLink ? ( - - ) : ( - + {selectedEvent.address.zip && ( +

CEP: {selectedEvent.address.zip}

)}
+ {/* Equipe Designada */} {(selectedEvent.photographerIds.length > 0 || - user.role === UserRole.BUSINESS_OWNER) && ( -
-
-

- Equipe Designada + user.role === UserRole.BUSINESS_OWNER || + user.role === UserRole.SUPERADMIN) && ( +
+
+

+ + Equipe ({selectedEvent.photographerIds.length})

{(user.role === UserRole.BUSINESS_OWNER || user.role === UserRole.SUPERADMIN) && ( )}
{selectedEvent.photographerIds.length > 0 ? ( -
+
{selectedEvent.photographerIds.map((id, idx) => ( -
+
+
+ {id} +
))}
) : (

- Nenhum profissional atribuído. + Nenhum profissional atribuído

)}
diff --git a/frontend/pages/Login.tsx b/frontend/pages/Login.tsx index e04c934..4c70b0d 100644 --- a/frontend/pages/Login.tsx +++ b/frontend/pages/Login.tsx @@ -42,35 +42,19 @@ export const Login: React.FC = ({ onNavigate }) => { } return ( -
- {/* Left Side - Image */} -
- Photum Login -
-
-

Photum Formaturas

-

Gestão de eventos premium para quem não abre mão da excelência.

-
+
+
+ {/* Logo */} +
+ Photum Formaturas
-
- {/* Right Side - Form */} -
-
- {/* Logo Mobile */} -
- Photum Formaturas -
- -
+
+
Bem-vindo de volta

Acesse sua conta

@@ -135,39 +119,39 @@ export const Login: React.FC = ({ onNavigate }) => { {isLoading ? 'Entrando...' : 'Entrar no Sistema'} +

- {/* Demo Users Quick Select - Melhorado para Mobile */} -
-

Usuários de Demonstração (Clique para preencher)

-
- {availableUsers.map(user => ( - - ))} -
+ {user.email} +
+ + + + + ))}
diff --git a/frontend/pages/Register.tsx b/frontend/pages/Register.tsx index aee64b0..c0c3e14 100644 --- a/frontend/pages/Register.tsx +++ b/frontend/pages/Register.tsx @@ -128,40 +128,22 @@ export const Register: React.FC = ({ onNavigate }) => { } return ( -
- {/* Left Side - Image */} -
- Photum Cadastro -
-
-

Faça parte da Photum

-

- Eternize seus momentos especiais com a melhor plataforma de gestão de eventos fotográficos. -

-
+
+
+ {/* Logo */} +
+ Photum Formaturas
-
- {/* Right Side - Form */} -
-
- {/* Logo Mobile */} -
- Photum Formaturas -
- -
- Comece agora -

Crie sua conta

-

+

+
+ Comece agora +

Crie sua conta

+

Já tem uma conta?{' '}

-
-
+ +