{user ? (
-
-
-

-
-
- {user.name}
-
-
- {getRoleLabel()}
-
-
+
+
+
+ {user.name}
+
+
+ {getRoleLabel()}
+
+
+
+ {/* Avatar com dropdown */}
+
+
+
+ {/* Profile Preview Dropdown */}
+ {isAccountDropdownOpen && (
+
+ {/* Header com foto e info */}
+
+
+

+
+
{user.name}
+
{user.email}
+
+ {getRoleLabel()}
+
+
+
+ {/* Menu Items */}
+
+ {/* Editar Perfil - Apenas para Fotógrafos e Clientes */}
+ {(user.role === UserRole.PHOTOGRAPHER || user.role === UserRole.EVENT_OWNER) && (
+
+ )}
+
+ {/* Configurações - Apenas para CEO e Business Owner */}
+ {(user.role === UserRole.BUSINESS_OWNER || user.role === UserRole.SUPERADMIN) && (
+
+ )}
+
+ {/* Sair */}
+
+
+
+ )}
-
) : (
-
-
+
+ {/* Mobile Button */}
+
+ setIsMobileMenuOpen(!isMobileMenuOpen)}
+ className="text-brand-black hover:text-brand-gold p-2"
+ >
+ {isMobileMenuOpen ? : }
+
+
- )}
-
+
+ {/* Mobile Menu */}
+ {isMobileMenuOpen && (
+
+
+ {user &&
+ getLinks().map((link) => (
+
{
+ onNavigate(link.path);
+ setIsMobileMenuOpen(false);
+ }}
+ className="block w-full text-left text-base font-medium text-gray-700 hover:text-brand-gold py-2 border-b border-gray-50"
+ >
+ {link.name}
+
+ ))}
+
+ {user ? (
+
+ {/* Info do usuário */}
+
+
+

+
+
+ {user.name}
+
+
+ {getRoleLabel()}
+
+
+
+
+
+ {/* Botão Editar Perfil - Apenas para Fotógrafos e Clientes */}
+ {(user.role === UserRole.PHOTOGRAPHER || user.role === UserRole.EVENT_OWNER) && (
+
{
+ setIsEditProfileModalOpen(true);
+ setIsMobileMenuOpen(false);
+ }}
+ className="w-full flex items-center gap-3 px-4 py-3 bg-[#492E61]/5 hover:bg-[#492E61]/10 rounded-xl transition-colors"
+ >
+
+
+
+
+
Editar Perfil
+
Atualize suas informações
+
+
+ )}
+
+ {/* Botão Sair */}
+
{
+ logout();
+ setIsMobileMenuOpen(false);
+ }}
+ className="w-full flex items-center gap-3 px-4 py-3 bg-red-50 hover:bg-red-100 rounded-xl transition-colors"
+ >
+
+
+
+
+
Sair da Conta
+
Desconectar do sistema
+
+
+
+ ) : (
+
+ {
+ onNavigate("login");
+ setIsMobileMenuOpen(false);
+ }}
+ >
+ ENTRAR
+
+ {
+ onNavigate("register");
+ setIsMobileMenuOpen(false);
+ }}
+ >
+ Cadastre-se agora
+
+
+ )}
+
+
+
+ )}
+
+
+ {/* Modal de Edição de Perfil - Apenas para Fotógrafos e Clientes */}
+ {
+ isEditProfileModalOpen && (user?.role === UserRole.PHOTOGRAPHER || user?.role === UserRole.EVENT_OWNER) && (
+
setIsEditProfileModalOpen(false)}
+ >
+
e.stopPropagation()}
+ >
+ {/* Header */}
+
+
setIsEditProfileModalOpen(false)}
+ className="absolute top-4 right-4 text-white/80 hover:text-white transition-colors"
+ >
+
+
+
+

+
+
+
+
+
Editar Perfil
+
Atualize suas informações pessoais
+
+
+ {/* Form */}
+
+
+
+ )
+ }
+ >
);
};
diff --git a/frontend/index.html b/frontend/index.html
index c26ce3f..1874f82 100644
--- a/frontend/index.html
+++ b/frontend/index.html
@@ -229,6 +229,16 @@
padding: 15px !important;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15) !important;
}
+
+ /* Hide scrollbar for mobile tabs */
+ .scrollbar-hide {
+ -ms-overflow-style: none;
+ scrollbar-width: none;
+ }
+
+ .scrollbar-hide::-webkit-scrollbar {
+ display: none;
+ }
diff --git a/frontend/pages/Calendar.tsx b/frontend/pages/Calendar.tsx
index 8b30743..a0ccae1 100644
--- a/frontend/pages/Calendar.tsx
+++ b/frontend/pages/Calendar.tsx
@@ -1,5 +1,5 @@
import React, { useState } from 'react';
-import { Calendar, Clock, MapPin, User, ChevronLeft, ChevronRight } from 'lucide-react';
+import { Calendar as CalendarIcon, Clock, MapPin, User, ChevronLeft, ChevronRight, Plus, Filter, Search } from 'lucide-react';
interface Event {
id: string;
@@ -78,61 +78,32 @@ const MOCK_EVENTS: Event[] = [
export const CalendarPage: React.FC = () => {
const [selectedMonth, setSelectedMonth] = useState(new Date());
const [selectedEvent, setSelectedEvent] = useState
(null);
- const [selectedDate, setSelectedDate] = useState(null);
+ const [viewMode, setViewMode] = useState<'month' | 'week'>('month');
const getStatusColor = (status: Event['status']) => {
switch (status) {
- case 'confirmed':
- return 'bg-green-100 text-green-800';
- case 'pending':
- return 'bg-yellow-100 text-yellow-800';
- case 'completed':
- return 'bg-gray-100 text-gray-800';
+ case 'confirmed': return 'bg-green-100 text-green-700 border-green-200';
+ case 'pending': return 'bg-yellow-100 text-yellow-700 border-yellow-200';
+ case 'completed': return 'bg-gray-100 text-gray-700 border-gray-200';
}
};
const getStatusLabel = (status: Event['status']) => {
switch (status) {
- case 'confirmed':
- return 'Confirmado';
- case 'pending':
- return 'Pendente';
- case 'completed':
- return 'Concluído';
+ case 'confirmed': return 'Confirmado';
+ case 'pending': return 'Pendente';
+ case 'completed': return 'Concluído';
}
};
const getTypeColor = (type: Event['type']) => {
switch (type) {
- case 'formatura':
- return 'bg-blue-100 text-blue-800';
- case 'casamento':
- return 'bg-pink-100 text-pink-800';
- case 'evento':
- return 'bg-purple-100 text-purple-800';
+ case 'formatura': return 'bg-[#492E61] text-white';
+ case 'casamento': return 'bg-pink-500 text-white';
+ case 'evento': return 'bg-blue-500 text-white';
}
};
- const getTypeLabel = (type: Event['type']) => {
- switch (type) {
- case 'formatura':
- return 'Formatura';
- case 'casamento':
- return 'Casamento';
- case 'evento':
- return 'Evento';
- }
- };
-
- const formatDate = (dateString: string) => {
- const date = new Date(dateString + 'T00:00:00');
- return date.toLocaleDateString('pt-BR', {
- day: '2-digit',
- month: 'long',
- year: 'numeric'
- });
- };
-
const nextMonth = () => {
setSelectedMonth(new Date(selectedMonth.getFullYear(), selectedMonth.getMonth() + 1));
};
@@ -141,110 +112,129 @@ export const CalendarPage: React.FC = () => {
setSelectedMonth(new Date(selectedMonth.getFullYear(), selectedMonth.getMonth() - 1));
};
- const currentMonthName = selectedMonth.toLocaleDateString('pt-BR', {
- month: 'long',
- year: 'numeric'
- });
-
- // Generate calendar days
const generateCalendarDays = () => {
const year = selectedMonth.getFullYear();
const month = selectedMonth.getMonth();
-
const firstDay = new Date(year, month, 1);
const lastDay = new Date(year, month + 1, 0);
-
- const firstDayOfWeek = firstDay.getDay();
const daysInMonth = lastDay.getDate();
-
+ const startingDayOfWeek = firstDay.getDay();
+
const days: (Date | null)[] = [];
-
- // Add empty cells for days before month starts
- for (let i = 0; i < firstDayOfWeek; i++) {
+
+ // Add empty cells for days before the first day of the month
+ for (let i = 0; i < startingDayOfWeek; i++) {
days.push(null);
}
-
+
// Add all days of the month
for (let day = 1; day <= daysInMonth; day++) {
days.push(new Date(year, month, day));
}
-
+
return days;
};
const getEventsForDate = (date: Date) => {
return MOCK_EVENTS.filter(event => {
const eventDate = new Date(event.date + 'T00:00:00');
- return eventDate.getDate() === date.getDate() &&
- eventDate.getMonth() === date.getMonth() &&
- eventDate.getFullYear() === date.getFullYear();
+ return eventDate.toDateString() === date.toDateString();
});
};
const isToday = (date: Date) => {
const today = new Date();
- return date.getDate() === today.getDate() &&
- date.getMonth() === today.getMonth() &&
- date.getFullYear() === today.getFullYear();
+ return date.toDateString() === today.toDateString();
};
+ const currentMonthName = selectedMonth.toLocaleDateString('pt-BR', { month: 'long', year: 'numeric' });
const calendarDays = generateCalendarDays();
- // Filter events for selected month
const monthEvents = MOCK_EVENTS.filter(event => {
const eventDate = new Date(event.date + 'T00:00:00');
return eventDate.getMonth() === selectedMonth.getMonth() &&
eventDate.getFullYear() === selectedMonth.getFullYear();
});
- // Sort events by date
- const sortedEvents = [...MOCK_EVENTS].sort((a, b) =>
- new Date(a.date).getTime() - new Date(b.date).getTime()
- );
-
return (
-
+
{/* Header */}
-
-
- Minha Agenda
-
-
- Gerencie seus eventos e compromissos fotográficos
-
+
+
+
+ Minha Agenda
+
+
+ Gerencie seus eventos e compromissos fotográficos
+
+
+
+
+
+ Novo Evento
+ Novo
+
+
+
+
+
- {/* Full Calendar Grid */}
-
-
-
-
-
-
-
- {currentMonthName}
-
-
-
-
+ {/* 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 Grid */}
-
+
{/* Week Days Header */}
-
+
{['Dom', 'Seg', 'Ter', 'Qua', 'Qui', 'Sex', 'Sáb'].map((day) => (
{day}
@@ -252,332 +242,147 @@ export const CalendarPage: React.FC = () => {
{/* Calendar Days */}
-
- {calendarDays.map((date, index) => {
- if (!date) {
- return (
-
- );
+
+ {calendarDays.map((day, index) => {
+ if (!day) {
+ return
;
}
- const dayEvents = getEventsForDate(date);
- const hasEvents = dayEvents.length > 0;
- const today = isToday(date);
+ const dayEvents = getEventsForDate(day);
+ const today = isToday(day);
return (
-
setSelectedDate(date)}
- className={`
- h-10 sm:h-12 rounded p-0.5 sm:p-1 transition-all duration-200
- ${today ? 'bg-[#B9CF33] text-white font-bold' : 'hover:bg-gray-100 active:bg-gray-200'}
- ${hasEvents && !today ? 'bg-blue-50 border border-[#B9CF33] sm:border-2' : 'bg-white border border-gray-200'}
- relative group
- `}
+ className={`aspect-square rounded-lg sm:rounded-xl border-2 p-1 sm:p-2 transition-all cursor-pointer hover:shadow-lg ${today
+ ? 'border-[#492E61] bg-[#492E61]/5'
+ : dayEvents.length > 0
+ ? 'border-[#B9CF32] bg-[#B9CF32]/5 hover:bg-[#B9CF32]/10'
+ : 'border-gray-200 hover:border-gray-300 hover:bg-gray-50'
+ }`}
>
-
-
- {date.getDate()}
+
+
0
+ ? 'text-gray-900'
+ : 'text-gray-600'
+ }`}
+ >
+ {day.getDate()}
- {hasEvents && (
-
- {dayEvents.slice(0, 3).map((event, i) => (
+ {dayEvents.length > 0 && (
+
+ {dayEvents.slice(0, 1).map((event) => (
+ key={event.id}
+ className={`text-[6px] sm:text-[8px] px-0.5 sm:px-1 py-0.5 rounded ${getTypeColor(event.type)} truncate leading-tight`}
+ >
+ {event.title}
+
))}
+ {dayEvents.length > 1 && (
+
+ +{dayEvents.length - 1}
+
+ )}
)}
-
- {/* Tooltip on hover - hide on mobile */}
- {hasEvents && (
-
-
- {dayEvents.length} evento{dayEvents.length > 1 ? 's' : ''}
-
-
- )}
-
+
);
})}
+
-
-
-
- Eventos este mês:
- {monthEvents.length}
-
-
- Total:
- {MOCK_EVENTS.length}
-
+ {/* Legend */}
+
- {/* Legend and Info Sidebar */}
-
-
-
Legenda
-
-
-
-
-
-
+ {/* Events List Sidebar */}
+
+ {/* Search */}
+
- {selectedDate && (
-
-
- {selectedDate.toLocaleDateString('pt-BR', {
- day: '2-digit',
- month: 'long',
- year: 'numeric'
- })}
-
- {getEventsForDate(selectedDate).length > 0 ? (
-
- {getEventsForDate(selectedDate).map(event => (
-
setSelectedEvent(event)}
- >
-
- {event.title}
-
-
-
- {event.time}
-
-
- ))}
+ {/* Upcoming Events */}
+
+
+
+ Próximos Eventos
+
+
+ {MOCK_EVENTS.slice(0, 5).map((event) => (
+
+
+
{event.title}
+
+ {getStatusLabel(event.status)}
+
- ) : (
-
Nenhum evento neste dia
- )}
-
- )}
-
-
-
- {/* Events List */}
-
-
-
-
Próximos Eventos
-
-
-
- {sortedEvents.length === 0 ? (
-
-
-
Nenhum evento agendado
-
- ) : (
- sortedEvents.map((event) => (
-
setSelectedEvent(event)}
- >
-
-
-
-
- {event.title}
-
-
- {getTypeLabel(event.type)}
-
-
-
-
-
- {formatDate(event.date)}
-
-
-
- {event.time}
-
-
-
- {event.location}
-
-
-
- {event.client}
-
-
-
-
- {getStatusLabel(event.status)}
-
+
+
+
+ {new Date(event.date).toLocaleDateString('pt-BR')} às {event.time}
+
+
+
+ {event.location}
+
+
+
+ {event.client}
- ))
- )}
+
+ ))}
-
- {/* Event Detail Modal - Improved & Centered */}
- {selectedEvent && (
-
setSelectedEvent(null)}
- >
-
e.stopPropagation()}
- >
- {/* Header with gradient */}
-
-
setSelectedEvent(null)}
- className="absolute top-4 right-4 w-8 h-8 flex items-center justify-center rounded-full bg-white/20 hover:bg-white/30 text-white transition-colors"
- >
- ✕
-
-
-
-
- {selectedEvent.title}
-
-
-
- {getTypeLabel(selectedEvent.type)}
-
-
- {getStatusLabel(selectedEvent.status)}
-
-
-
-
-
- {/* Content */}
-
- {/* Date */}
-
-
-
-
-
-
Data
-
{formatDate(selectedEvent.date)}
-
-
-
- {/* Time */}
-
-
-
-
-
-
Horário
-
{selectedEvent.time}
-
-
-
- {/* Location */}
-
-
-
-
-
-
Local
-
{selectedEvent.location}
-
-
-
- {/* Client */}
-
-
-
-
-
-
Cliente
-
{selectedEvent.client}
-
-
-
-
- {/* Actions */}
-
- setSelectedEvent(null)}
- className="flex-1 px-6 py-3 bg-white border-2 border-gray-200 text-gray-700 rounded-xl hover:bg-gray-50 hover:border-gray-300 active:scale-95 transition-all font-medium"
- >
- Fechar
-
- e.currentTarget.style.backgroundColor = '#a5bd2e'}
- onMouseLeave={(e) => e.currentTarget.style.backgroundColor = '#B9CF33'}
- >
- Ver Detalhes
-
-
-
-
- )}
-
-
);
};
diff --git a/frontend/pages/Dashboard.tsx b/frontend/pages/Dashboard.tsx
index 9270c87..6a48322 100644
--- a/frontend/pages/Dashboard.tsx
+++ b/frontend/pages/Dashboard.tsx
@@ -236,33 +236,31 @@ export const Dashboard: React.FC
= ({
{(user.role === UserRole.BUSINESS_OWNER ||
user.role === UserRole.SUPERADMIN) && (
-
-
setActiveFilter("all")}
- className={`px-3 py-1 text-xs font-medium rounded-sm ${
- activeFilter === "all"
+
+ setActiveFilter("all")}
+ className={`px-3 py-1 text-xs font-medium rounded-sm ${activeFilter === "all"
? "bg-brand-black text-white"
: "text-gray-600 hover:bg-gray-100"
- }`}
- >
- Todos
-
- setActiveFilter("pending")}
- className={`px-3 py-1 text-xs font-medium rounded-sm flex items-center ${
- activeFilter === "pending"
+ }`}
+ >
+ Todos
+
+ setActiveFilter("pending")}
+ className={`px-3 py-1 text-xs font-medium rounded-sm flex items-center ${activeFilter === "pending"
? "bg-brand-gold text-white"
: "text-gray-600 hover:bg-gray-100"
- }`}
- >
- Pendentes
-
-
- )}
+ }`}
+ >
+ Pendentes
+
+
+ )}
{/* Grid */}
-
+
{filteredEvents.map((event) => (
{renderAdminActions(event)}
@@ -334,31 +332,33 @@ export const Dashboard: React.FC = ({
-
-
-
+
+
+
{/* Actions Toolbar */}
-
+
{(user.role === UserRole.BUSINESS_OWNER ||
user.role === UserRole.SUPERADMIN) && (
- <>
- setView("edit")}
- >
- Editar Detalhes
-
-
- Gerenciar
- Equipe
-
- >
- )}
+ <>
+ setView("edit")}
+ className="text-sm"
+ >
+ Editar Detalhes
+
+
+ Gerenciar
+ Equipe
+
+ >
+ )}
{user.role === UserRole.EVENT_OWNER &&
selectedEvent.status !== EventStatus.ARCHIVED && (
setView("edit")}
+ className="text-sm"
>
Editar
Informações
@@ -467,11 +467,10 @@ export const Dashboard: React.FC = ({
)}
-
+
Status Atual
@@ -518,43 +517,43 @@ export const Dashboard: React.FC = ({
{(selectedEvent.photographerIds.length > 0 ||
user.role === UserRole.BUSINESS_OWNER) && (
-
-
-
- Equipe Designada
-
- {(user.role === UserRole.BUSINESS_OWNER ||
- user.role === UserRole.SUPERADMIN) && (
-
-
-
+
+
+
+ Equipe Designada
+
+ {(user.role === UserRole.BUSINESS_OWNER ||
+ user.role === UserRole.SUPERADMIN) && (
+
+
+
+ )}
+
+
+ {selectedEvent.photographerIds.length > 0 ? (
+
+ {selectedEvent.photographerIds.map((id, idx) => (
+
+ ))}
+
+ ) : (
+
+ Nenhum profissional atribuído.
+
)}
-
- {selectedEvent.photographerIds.length > 0 ? (
-
- {selectedEvent.photographerIds.map((id, idx) => (
-
- ))}
-
- ) : (
-
- Nenhum profissional atribuído.
-
- )}
-
- )}
+ )}
diff --git a/frontend/pages/Finance.tsx b/frontend/pages/Finance.tsx
index ad7309a..b408814 100644
--- a/frontend/pages/Finance.tsx
+++ b/frontend/pages/Finance.tsx
@@ -172,26 +172,26 @@ export const FinancePage: React.FC = () => {
};
return (
-
+
{/* Header */}
-
+
-
+
Financeiro
-
+
Acompanhe receitas, despesas e fluxo de caixa
-
-
- Exportar Relatório
+
+
+ Exportar
{/* Stats Cards */}
-
+
Receitas
@@ -232,14 +232,14 @@ export const FinancePage: React.FC = () => {
{/* Filters */}
-
-
-
+
+
+
setFilterType('all')}
className={`px-4 py-2 rounded-md font-medium transition-colors ${filterType === 'all'
- ? 'bg-brand-gold text-white'
- : 'bg-gray-100 text-gray-700 hover:bg-gray-200'
+ ? 'bg-brand-gold text-white'
+ : 'bg-gray-100 text-gray-700 hover:bg-gray-200'
}`}
>
Todas
@@ -247,8 +247,8 @@ export const FinancePage: React.FC = () => {
setFilterType('income')}
className={`px-4 py-2 rounded-md font-medium transition-colors ${filterType === 'income'
- ? 'bg-green-600 text-white'
- : 'bg-gray-100 text-gray-700 hover:bg-gray-200'
+ ? 'bg-green-600 text-white'
+ : 'bg-gray-100 text-gray-700 hover:bg-gray-200'
}`}
>
Receitas
@@ -256,19 +256,19 @@ export const FinancePage: React.FC = () => {
setFilterType('expense')}
className={`px-4 py-2 rounded-md font-medium transition-colors ${filterType === 'expense'
- ? 'bg-red-600 text-white'
- : 'bg-gray-100 text-gray-700 hover:bg-gray-200'
+ ? 'bg-red-600 text-white'
+ : 'bg-gray-100 text-gray-700 hover:bg-gray-200'
}`}
>
Despesas
-
+
setFilterStatus('all')}
className={`px-4 py-2 rounded-md font-medium transition-colors ${filterStatus === 'all'
- ? 'bg-brand-black text-white'
- : 'bg-gray-100 text-gray-700 hover:bg-gray-200'
+ ? 'bg-brand-black text-white'
+ : 'bg-gray-100 text-gray-700 hover:bg-gray-200'
}`}
>
Todos Status
@@ -276,8 +276,8 @@ export const FinancePage: React.FC = () => {
setFilterStatus('paid')}
className={`px-4 py-2 rounded-md font-medium transition-colors ${filterStatus === 'paid'
- ? 'bg-green-600 text-white'
- : 'bg-gray-100 text-gray-700 hover:bg-gray-200'
+ ? 'bg-green-600 text-white'
+ : 'bg-gray-100 text-gray-700 hover:bg-gray-200'
}`}
>
Pagos
@@ -285,8 +285,8 @@ export const FinancePage: React.FC = () => {
setFilterStatus('pending')}
className={`px-4 py-2 rounded-md font-medium transition-colors ${filterStatus === 'pending'
- ? 'bg-yellow-600 text-white'
- : 'bg-gray-100 text-gray-700 hover:bg-gray-200'
+ ? 'bg-yellow-600 text-white'
+ : 'bg-gray-100 text-gray-700 hover:bg-gray-200'
}`}
>
Pendentes
diff --git a/frontend/pages/Home.tsx b/frontend/pages/Home.tsx
index a24321e..5e2203b 100644
--- a/frontend/pages/Home.tsx
+++ b/frontend/pages/Home.tsx
@@ -98,11 +98,11 @@ export const Home: React.FC = ({ onEnter }) => {
-
-