feat: mudancas de layout
This commit is contained in:
parent
4e9b743928
commit
f73095e3d4
9 changed files with 1297 additions and 466 deletions
|
|
@ -4,7 +4,6 @@ import { Home } from "./pages/Home";
|
||||||
import { Dashboard } from "./pages/Dashboard";
|
import { Dashboard } from "./pages/Dashboard";
|
||||||
import { Login } from "./pages/Login";
|
import { Login } from "./pages/Login";
|
||||||
import { Register } from "./pages/Register";
|
import { Register } from "./pages/Register";
|
||||||
import { CalendarPage } from "./pages/Calendar";
|
|
||||||
import { TeamPage } from "./pages/Team";
|
import { TeamPage } from "./pages/Team";
|
||||||
import { FinancePage } from "./pages/Finance";
|
import { FinancePage } from "./pages/Finance";
|
||||||
import { SettingsPage } from "./pages/Settings";
|
import { SettingsPage } from "./pages/Settings";
|
||||||
|
|
@ -54,9 +53,6 @@ const AppContent: React.FC = () => {
|
||||||
case "inspiration":
|
case "inspiration":
|
||||||
return <InspirationPage />;
|
return <InspirationPage />;
|
||||||
|
|
||||||
case "calendar":
|
|
||||||
return <CalendarPage />;
|
|
||||||
|
|
||||||
case "team":
|
case "team":
|
||||||
return <TeamPage />;
|
return <TeamPage />;
|
||||||
|
|
||||||
|
|
|
||||||
225
frontend/components/EventFiltersBar.tsx
Normal file
225
frontend/components/EventFiltersBar.tsx
Normal file
|
|
@ -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<EventFiltersBarProps> = ({
|
||||||
|
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 (
|
||||||
|
<div className="bg-white rounded-lg border border-gray-200 p-4 shadow-sm">
|
||||||
|
<div className="flex items-center justify-between mb-4">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Filter size={18} className="text-brand-gold" />
|
||||||
|
<h3 className="font-semibold text-gray-800">Filtros Avançados</h3>
|
||||||
|
</div>
|
||||||
|
{hasActiveFilters && (
|
||||||
|
<button
|
||||||
|
onClick={handleReset}
|
||||||
|
className="flex items-center gap-1 text-sm text-gray-600 hover:text-brand-gold transition-colors"
|
||||||
|
>
|
||||||
|
<X size={16} />
|
||||||
|
Limpar filtros
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-5 gap-3">
|
||||||
|
{/* Filtro por Data */}
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<label className="text-xs font-medium text-gray-600 mb-1 flex items-center gap-1">
|
||||||
|
<Calendar size={14} className="text-brand-gold" />
|
||||||
|
Data
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="date"
|
||||||
|
value={filters.date}
|
||||||
|
onChange={(e) => 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"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Filtro por Estado */}
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<label className="text-xs font-medium text-gray-600 mb-1 flex items-center gap-1">
|
||||||
|
<MapPin size={14} className="text-brand-gold" />
|
||||||
|
Estado
|
||||||
|
</label>
|
||||||
|
<select
|
||||||
|
value={filters.state}
|
||||||
|
onChange={(e) => onFilterChange({ ...filters, state: e.target.value, city: '' })}
|
||||||
|
className="px-3 py-2 border border-gray-300 rounded text-sm focus:outline-none focus:border-brand-gold transition-colors bg-white"
|
||||||
|
>
|
||||||
|
<option value="">Todos os estados</option>
|
||||||
|
{availableStates.map((state) => (
|
||||||
|
<option key={state} value={state}>
|
||||||
|
{state}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Filtro por Cidade */}
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<label className="text-xs font-medium text-gray-600 mb-1 flex items-center gap-1">
|
||||||
|
<MapPin size={14} className="text-brand-gold" />
|
||||||
|
Cidade
|
||||||
|
</label>
|
||||||
|
<select
|
||||||
|
value={filters.city}
|
||||||
|
onChange={(e) => onFilterChange({ ...filters, city: e.target.value })}
|
||||||
|
disabled={!filters.state}
|
||||||
|
className="px-3 py-2 border border-gray-300 rounded text-sm focus:outline-none focus:border-brand-gold transition-colors bg-white disabled:bg-gray-100 disabled:cursor-not-allowed"
|
||||||
|
>
|
||||||
|
<option value="">Todas as cidades</option>
|
||||||
|
{availableCities.map((city) => (
|
||||||
|
<option key={city} value={city}>
|
||||||
|
{city}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Filtro por Horário */}
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<label className="text-xs font-medium text-gray-600 mb-1 flex items-center gap-1">
|
||||||
|
<Clock size={14} className="text-brand-gold" />
|
||||||
|
Horário
|
||||||
|
</label>
|
||||||
|
<select
|
||||||
|
value={filters.timeRange}
|
||||||
|
onChange={(e) => onFilterChange({ ...filters, timeRange: e.target.value })}
|
||||||
|
className="px-3 py-2 border border-gray-300 rounded text-sm focus:outline-none focus:border-brand-gold transition-colors bg-white"
|
||||||
|
>
|
||||||
|
{timeRanges.map((range) => (
|
||||||
|
<option key={range.value} value={range.value}>
|
||||||
|
{range.label}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Filtro por Tipo */}
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<label className="text-xs font-medium text-gray-600 mb-1 flex items-center gap-1">
|
||||||
|
<Filter size={14} className="text-brand-gold" />
|
||||||
|
Tipo de Evento
|
||||||
|
</label>
|
||||||
|
<select
|
||||||
|
value={filters.type}
|
||||||
|
onChange={(e) => onFilterChange({ ...filters, type: e.target.value })}
|
||||||
|
className="px-3 py-2 border border-gray-300 rounded text-sm focus:outline-none focus:border-brand-gold transition-colors bg-white"
|
||||||
|
>
|
||||||
|
<option value="">Todos os tipos</option>
|
||||||
|
{availableTypes.map((type) => (
|
||||||
|
<option key={type} value={type}>
|
||||||
|
{type}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Active Filters Display */}
|
||||||
|
{hasActiveFilters && (
|
||||||
|
<div className="mt-3 pt-3 border-t border-gray-100">
|
||||||
|
<div className="flex flex-wrap gap-2">
|
||||||
|
<span className="text-xs text-gray-500">Filtros ativos:</span>
|
||||||
|
{filters.date && (
|
||||||
|
<span className="inline-flex items-center gap-1 px-2 py-1 bg-brand-gold/10 text-brand-gold text-xs rounded">
|
||||||
|
Data: {new Date(filters.date + 'T00:00:00').toLocaleDateString('pt-BR')}
|
||||||
|
<button
|
||||||
|
onClick={() => onFilterChange({ ...filters, date: '' })}
|
||||||
|
className="hover:text-brand-black"
|
||||||
|
>
|
||||||
|
<X size={12} />
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
{filters.state && (
|
||||||
|
<span className="inline-flex items-center gap-1 px-2 py-1 bg-brand-gold/10 text-brand-gold text-xs rounded">
|
||||||
|
Estado: {filters.state}
|
||||||
|
<button
|
||||||
|
onClick={() => onFilterChange({ ...filters, state: '', city: '' })}
|
||||||
|
className="hover:text-brand-black"
|
||||||
|
>
|
||||||
|
<X size={12} />
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
{filters.city && (
|
||||||
|
<span className="inline-flex items-center gap-1 px-2 py-1 bg-brand-gold/10 text-brand-gold text-xs rounded">
|
||||||
|
Cidade: {filters.city}
|
||||||
|
<button
|
||||||
|
onClick={() => onFilterChange({ ...filters, city: '' })}
|
||||||
|
className="hover:text-brand-black"
|
||||||
|
>
|
||||||
|
<X size={12} />
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
{filters.timeRange && (
|
||||||
|
<span className="inline-flex items-center gap-1 px-2 py-1 bg-brand-gold/10 text-brand-gold text-xs rounded">
|
||||||
|
{timeRanges.find(r => r.value === filters.timeRange)?.label}
|
||||||
|
<button
|
||||||
|
onClick={() => onFilterChange({ ...filters, timeRange: '' })}
|
||||||
|
className="hover:text-brand-black"
|
||||||
|
>
|
||||||
|
<X size={12} />
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
{filters.type && (
|
||||||
|
<span className="inline-flex items-center gap-1 px-2 py-1 bg-brand-gold/10 text-brand-gold text-xs rounded">
|
||||||
|
Tipo: {filters.type}
|
||||||
|
<button
|
||||||
|
onClick={() => onFilterChange({ ...filters, type: '' })}
|
||||||
|
className="hover:text-brand-black"
|
||||||
|
>
|
||||||
|
<X size={12} />
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
174
frontend/components/EventTable.tsx
Normal file
174
frontend/components/EventTable.tsx
Normal file
|
|
@ -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<EventTableProps> = ({
|
||||||
|
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, string> = {
|
||||||
|
[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 (
|
||||||
|
<div className="bg-white rounded-lg border border-gray-200 overflow-hidden shadow-sm">
|
||||||
|
<div className="overflow-x-auto">
|
||||||
|
<table className="w-full">
|
||||||
|
<thead className="bg-gray-50 border-b border-gray-200">
|
||||||
|
<tr>
|
||||||
|
{canApprove && (
|
||||||
|
<th className="px-4 py-3 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider w-20">
|
||||||
|
Ações
|
||||||
|
</th>
|
||||||
|
)}
|
||||||
|
<th className="px-4 py-3 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider">
|
||||||
|
Nome do Evento
|
||||||
|
</th>
|
||||||
|
<th className="px-4 py-3 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider">
|
||||||
|
Tipo
|
||||||
|
</th>
|
||||||
|
<th className="px-4 py-3 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider">
|
||||||
|
Data
|
||||||
|
</th>
|
||||||
|
<th className="px-4 py-3 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider">
|
||||||
|
Horário
|
||||||
|
</th>
|
||||||
|
<th className="px-4 py-3 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider">
|
||||||
|
Local
|
||||||
|
</th>
|
||||||
|
<th className="px-4 py-3 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider">
|
||||||
|
Contatos
|
||||||
|
</th>
|
||||||
|
<th className="px-4 py-3 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider">
|
||||||
|
Equipe
|
||||||
|
</th>
|
||||||
|
<th className="px-4 py-3 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider">
|
||||||
|
Status
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody className="divide-y divide-gray-100">
|
||||||
|
{events.map((event) => (
|
||||||
|
<tr
|
||||||
|
key={event.id}
|
||||||
|
onClick={() => onEventClick(event)}
|
||||||
|
className="hover:bg-gray-50 cursor-pointer transition-colors"
|
||||||
|
>
|
||||||
|
{canApprove && (
|
||||||
|
<td className="px-4 py-3" onClick={(e) => e.stopPropagation()}>
|
||||||
|
{event.status === EventStatus.PENDING_APPROVAL && (
|
||||||
|
<button
|
||||||
|
onClick={(e) => onApprove?.(e, event.id)}
|
||||||
|
className="bg-green-500 text-white px-2 py-1 rounded text-xs font-semibold hover:bg-green-600 transition-colors flex items-center gap-1 whitespace-nowrap"
|
||||||
|
title="Aprovar evento"
|
||||||
|
>
|
||||||
|
<CheckCircle size={12} />
|
||||||
|
Aprovar
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</td>
|
||||||
|
)}
|
||||||
|
<td className="px-4 py-3">
|
||||||
|
<div className="font-medium text-gray-900 text-sm">{event.name}</div>
|
||||||
|
</td>
|
||||||
|
<td className="px-4 py-3">
|
||||||
|
<span className="text-xs text-gray-600 uppercase tracking-wide">{event.type}</span>
|
||||||
|
</td>
|
||||||
|
<td className="px-4 py-3">
|
||||||
|
<div className="flex items-center text-sm text-gray-700">
|
||||||
|
<Calendar size={14} className="mr-1.5 text-brand-gold flex-shrink-0" />
|
||||||
|
{formatDate(event.date)}
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td className="px-4 py-3">
|
||||||
|
<div className="flex items-center text-sm text-gray-700">
|
||||||
|
<Clock size={14} className="mr-1.5 text-gray-400 flex-shrink-0" />
|
||||||
|
{event.time}
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td className="px-4 py-3">
|
||||||
|
<div className="flex items-center text-sm text-gray-700">
|
||||||
|
<MapPin size={14} className="mr-1.5 text-brand-gold flex-shrink-0" />
|
||||||
|
<span className="truncate max-w-[200px]" title={`${event.address.street}, ${event.address.number} - ${event.address.city}/${event.address.state}`}>
|
||||||
|
{event.address.city}, {event.address.state}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td className="px-4 py-3">
|
||||||
|
<div className="flex items-center text-sm text-gray-700">
|
||||||
|
<Users size={14} className="mr-1.5 text-gray-400 flex-shrink-0" />
|
||||||
|
{event.contacts.length}
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td className="px-4 py-3">
|
||||||
|
{event.photographerIds.length > 0 ? (
|
||||||
|
<div className="flex -space-x-1">
|
||||||
|
{event.photographerIds.slice(0, 3).map((id, idx) => (
|
||||||
|
<div
|
||||||
|
key={id}
|
||||||
|
className="w-6 h-6 rounded-full border-2 border-white bg-gray-300"
|
||||||
|
style={{
|
||||||
|
backgroundImage: `url(https://i.pravatar.cc/100?u=${id})`,
|
||||||
|
backgroundSize: 'cover',
|
||||||
|
}}
|
||||||
|
title={id}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
{event.photographerIds.length > 3 && (
|
||||||
|
<div className="w-6 h-6 rounded-full border-2 border-white bg-gray-100 flex items-center justify-center text-[10px] text-gray-600 font-medium">
|
||||||
|
+{event.photographerIds.length - 3}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<span className="text-xs text-gray-400">-</span>
|
||||||
|
)}
|
||||||
|
</td>
|
||||||
|
<td className="px-4 py-3">
|
||||||
|
<span className={`inline-flex items-center px-2 py-1 rounded text-xs font-semibold ${STATUS_COLORS[event.status]}`}>
|
||||||
|
{getStatusDisplay(event.status)}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{events.length === 0 && (
|
||||||
|
<div className="text-center py-12 text-gray-500">
|
||||||
|
<p>Nenhum evento encontrado.</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -63,7 +63,6 @@ export const Navbar: React.FC<NavbarProps> = ({ onNavigate, currentPage }) => {
|
||||||
case UserRole.PHOTOGRAPHER:
|
case UserRole.PHOTOGRAPHER:
|
||||||
return [
|
return [
|
||||||
{ name: "Eventos Designados", path: "dashboard" },
|
{ name: "Eventos Designados", path: "dashboard" },
|
||||||
{ name: "Agenda", path: "calendar" },
|
|
||||||
];
|
];
|
||||||
default:
|
default:
|
||||||
return [];
|
return [];
|
||||||
|
|
|
||||||
|
|
@ -25,10 +25,10 @@ const INITIAL_INSTITUTIONS: Institution[] = [
|
||||||
const INITIAL_EVENTS: EventData[] = [
|
const INITIAL_EVENTS: EventData[] = [
|
||||||
{
|
{
|
||||||
id: "1",
|
id: "1",
|
||||||
name: "Casamento Juliana & Marcos",
|
name: "Formatura Engenharia Civil",
|
||||||
date: "2024-10-15",
|
date: "2025-12-05",
|
||||||
time: "16:00",
|
time: "19:00",
|
||||||
type: EventType.WEDDING,
|
type: EventType.GRADUATION,
|
||||||
status: EventStatus.CONFIRMED,
|
status: EventStatus.CONFIRMED,
|
||||||
address: {
|
address: {
|
||||||
street: "Av. das Hortênsias",
|
street: "Av. das Hortênsias",
|
||||||
|
|
@ -37,30 +37,29 @@ const INITIAL_EVENTS: EventData[] = [
|
||||||
state: "RS",
|
state: "RS",
|
||||||
zip: "95670-000",
|
zip: "95670-000",
|
||||||
},
|
},
|
||||||
briefing:
|
briefing: "Cerimônia de formatura com 120 formandos. Foco em fotos individuais e da turma.",
|
||||||
"Cerimônia ao pôr do sol. Foco em fotos espontâneas dos noivos e pais.",
|
|
||||||
coverImage: "https://picsum.photos/id/1059/800/400",
|
coverImage: "https://picsum.photos/id/1059/800/400",
|
||||||
contacts: [
|
contacts: [
|
||||||
{
|
{
|
||||||
id: "c1",
|
id: "c1",
|
||||||
name: "Cerimonial Silva",
|
name: "Comissão de Formatura",
|
||||||
role: "Cerimonialista",
|
role: "Organizador",
|
||||||
phone: "9999-9999",
|
phone: "51 99999-1111",
|
||||||
email: "c@teste.com",
|
email: "formatura@email.com",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
checklist: [],
|
checklist: [],
|
||||||
ownerId: "client-1",
|
ownerId: "client-1",
|
||||||
photographerIds: ["photographer-1"],
|
photographerIds: ["photographer-1", "photographer-2"],
|
||||||
institutionId: "inst-1",
|
institutionId: "inst-1",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "2",
|
id: "2",
|
||||||
name: "Conferência Tech Innovators",
|
name: "Colação de Grau Medicina",
|
||||||
date: "2024-11-05",
|
date: "2025-12-05",
|
||||||
time: "08:00",
|
time: "10:00",
|
||||||
type: EventType.CORPORATE,
|
type: EventType.COLATION,
|
||||||
status: EventStatus.PENDING_APPROVAL,
|
status: EventStatus.CONFIRMED,
|
||||||
address: {
|
address: {
|
||||||
street: "Rua Olimpíadas",
|
street: "Rua Olimpíadas",
|
||||||
number: "205",
|
number: "205",
|
||||||
|
|
@ -68,13 +67,463 @@ const INITIAL_EVENTS: EventData[] = [
|
||||||
state: "SP",
|
state: "SP",
|
||||||
zip: "04551-000",
|
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",
|
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: [],
|
contacts: [],
|
||||||
checklist: [],
|
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: [],
|
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 {
|
interface DataContextType {
|
||||||
|
|
|
||||||
|
|
@ -157,87 +157,60 @@ export const CalendarPage: React.FC = () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-gradient-to-br from-gray-50 to-gray-100 pt-20 sm:pt-24 md:pt-32 pb-8 sm:pb-12">
|
<div className="min-h-screen bg-white pt-24 pb-12 px-4 sm:px-6 lg:px-8">
|
||||||
<div className="max-w-7xl mx-auto px-3 sm:px-4 md:px-6 lg:px-8">
|
<div className="max-w-7xl mx-auto">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="mb-4 sm:mb-6 md:mb-8 flex flex-col gap-3 sm:gap-4">
|
<div className="mb-8 fade-in">
|
||||||
<div>
|
<h1 className="text-3xl font-serif font-bold text-brand-black">
|
||||||
<h1 className="text-2xl sm:text-3xl md:text-4xl font-bold text-[#B8D033] mb-1 sm:mb-2">
|
|
||||||
Minha Agenda
|
Minha Agenda
|
||||||
</h1>
|
</h1>
|
||||||
<p className="text-xs sm:text-sm md:text-base text-gray-600">
|
<p className="text-gray-500 mt-1">
|
||||||
Gerencie seus eventos e compromissos fotográficos
|
Gerencie seus eventos e compromissos fotográficos
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-2">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-4 sm:gap-6">
|
<div className="space-y-6 fade-in">
|
||||||
{/* Calendar Section */}
|
|
||||||
<div className="lg:col-span-2 space-y-4 sm:space-y-6">
|
|
||||||
{/* Calendar Card */}
|
{/* Calendar Card */}
|
||||||
<div className="bg-white rounded-2xl shadow-xl border border-gray-200 overflow-hidden">
|
<div className="bg-white rounded-xl border border-gray-200 shadow-lg overflow-hidden">
|
||||||
{/* Calendar Header */}
|
{/* Header */}
|
||||||
<div className="bg-gradient-to-r from-[#492E61] to-[#5a3a7a] p-4 sm:p-6">
|
<div className="bg-gradient-to-r from-brand-black to-brand-black/90 px-6 py-5">
|
||||||
<div className="flex items-center justify-between mb-3 sm:mb-4">
|
<div className="flex items-center justify-between">
|
||||||
<button
|
<button
|
||||||
onClick={prevMonth}
|
onClick={prevMonth}
|
||||||
className="p-1.5 sm:p-2 hover:bg-white/20 rounded-lg transition-colors"
|
className="p-2 hover:bg-white/10 rounded-lg transition-all"
|
||||||
>
|
>
|
||||||
<ChevronLeft className="w-5 h-5 sm:w-6 sm:h-6 text-white" />
|
<ChevronLeft className="w-5 h-5 text-white" />
|
||||||
</button>
|
</button>
|
||||||
<h2 className="text-lg sm:text-xl md:text-2xl font-bold text-white capitalize">
|
<h2 className="text-2xl font-serif font-bold text-white capitalize">
|
||||||
{currentMonthName}
|
{currentMonthName}
|
||||||
</h2>
|
</h2>
|
||||||
<button
|
<button
|
||||||
onClick={nextMonth}
|
onClick={nextMonth}
|
||||||
className="p-1.5 sm:p-2 hover:bg-white/20 rounded-lg transition-colors"
|
className="p-2 hover:bg-white/10 rounded-lg transition-all"
|
||||||
>
|
>
|
||||||
<ChevronRight className="w-5 h-5 sm:w-6 sm:h-6 text-white" />
|
<ChevronRight className="w-5 h-5 text-white" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Stats */}
|
|
||||||
<div className="grid grid-cols-3 gap-2 sm:gap-4">
|
|
||||||
<div className="bg-white/10 backdrop-blur-sm rounded-lg p-2 sm:p-3 text-center">
|
|
||||||
<p className="text-white/80 text-[10px] sm:text-xs mb-0.5 sm:mb-1">Total</p>
|
|
||||||
<p className="text-xl sm:text-2xl font-bold text-white">{monthEvents.length}</p>
|
|
||||||
</div>
|
|
||||||
<div className="bg-white/10 backdrop-blur-sm rounded-lg p-2 sm:p-3 text-center">
|
|
||||||
<p className="text-white/80 text-[10px] sm:text-xs mb-0.5 sm:mb-1">Confirmados</p>
|
|
||||||
<p className="text-xl sm:text-2xl font-bold text-green-300">
|
|
||||||
{monthEvents.filter(e => e.status === 'confirmed').length}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div className="bg-white/10 backdrop-blur-sm rounded-lg p-2 sm:p-3 text-center">
|
|
||||||
<p className="text-white/80 text-[10px] sm:text-xs mb-0.5 sm:mb-1">Pendentes</p>
|
|
||||||
<p className="text-xl sm:text-2xl font-bold text-yellow-300">
|
|
||||||
{monthEvents.filter(e => e.status === 'pending').length}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Calendar Grid */}
|
{/* Calendar Grid */}
|
||||||
<div className="p-3 sm:p-4 md:p-6">
|
<div className="p-4">
|
||||||
{/* Week Days Header */}
|
{/* Week Days */}
|
||||||
<div className="grid grid-cols-7 gap-1 sm:gap-2 mb-1 sm:mb-2">
|
<div className="grid grid-cols-7 gap-2 mb-2">
|
||||||
{['Dom', 'Seg', 'Ter', 'Qua', 'Qui', 'Sex', 'Sáb'].map((day) => (
|
{['D', 'S', 'T', 'Q', 'Q', 'S', 'S'].map((day, idx) => (
|
||||||
<div
|
<div key={idx} className="text-center">
|
||||||
key={day}
|
<span className="text-xs font-semibold text-gray-400">
|
||||||
className="text-center text-[10px] sm:text-xs md:text-sm font-bold text-gray-600 py-1 sm:py-2"
|
|
||||||
>
|
|
||||||
{day}
|
{day}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Calendar Days */}
|
{/* Days */}
|
||||||
<div className="grid grid-cols-7 gap-1 sm:gap-2">
|
<div className="grid grid-cols-7 gap-2">
|
||||||
{calendarDays.map((day, index) => {
|
{calendarDays.map((day, index) => {
|
||||||
if (!day) {
|
if (!day) {
|
||||||
return <div key={`empty-${index}`} className="aspect-square" />;
|
return <div key={`empty-${index}`} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
const dayEvents = getEventsForDate(day);
|
const dayEvents = getEventsForDate(day);
|
||||||
|
|
@ -246,132 +219,145 @@ export const CalendarPage: React.FC = () => {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={index}
|
key={index}
|
||||||
className={`aspect-square rounded-lg sm:rounded-xl border-2 p-1 sm:p-2 transition-all cursor-pointer hover:shadow-lg ${today
|
className={`relative w-10 h-10 rounded-lg border-2 flex items-center justify-center cursor-pointer transition-all hover:scale-105 ${
|
||||||
? 'border-[#492E61] bg-[#492E61]/5'
|
today
|
||||||
|
? 'border-brand-gold bg-brand-gold text-white shadow-md font-bold'
|
||||||
: dayEvents.length > 0
|
: dayEvents.length > 0
|
||||||
? 'border-[#B9CF32] bg-[#B9CF32]/5 hover:bg-[#B9CF32]/10'
|
? 'border-brand-black/20 bg-brand-black text-white hover:border-brand-gold'
|
||||||
: 'border-gray-200 hover:border-gray-300 hover:bg-gray-50'
|
: 'border-gray-200 text-gray-700 hover:border-gray-300 hover:bg-gray-50'
|
||||||
}`}
|
|
||||||
>
|
|
||||||
<div className="h-full flex flex-col">
|
|
||||||
<span
|
|
||||||
className={`text-xs sm:text-sm font-semibold mb-0.5 sm:mb-1 ${today
|
|
||||||
? 'text-[#492E61]'
|
|
||||||
: dayEvents.length > 0
|
|
||||||
? 'text-gray-900'
|
|
||||||
: 'text-gray-600'
|
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
|
<span className={`text-sm ${today ? 'font-bold' : 'font-medium'}`}>
|
||||||
{day.getDate()}
|
{day.getDate()}
|
||||||
</span>
|
</span>
|
||||||
{dayEvents.length > 0 && (
|
{dayEvents.length > 0 && !today && (
|
||||||
<div className="flex-1 flex flex-col gap-0.5 sm:gap-1">
|
<div className="absolute bottom-0.5 flex gap-0.5">
|
||||||
{dayEvents.slice(0, 1).map((event) => (
|
{dayEvents.slice(0, 3).map((_, i) => (
|
||||||
<div
|
<div key={i} className="w-0.5 h-0.5 rounded-full bg-brand-gold" />
|
||||||
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}
|
|
||||||
</div>
|
|
||||||
))}
|
))}
|
||||||
{dayEvents.length > 1 && (
|
|
||||||
<span className="text-[6px] sm:text-[8px] text-gray-500 font-medium">
|
|
||||||
+{dayEvents.length - 1}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Legend */}
|
{/* Stats Footer */}
|
||||||
<div className="bg-white rounded-2xl shadow-lg border border-gray-200 p-4 sm:p-6">
|
<div className="border-t border-gray-200 bg-gray-50 px-6 py-4">
|
||||||
<h3 className="text-base sm:text-lg font-bold text-gray-900 mb-3 sm:mb-4">Legenda</h3>
|
<div className="grid grid-cols-3 gap-4">
|
||||||
<div className="grid grid-cols-2 sm:grid-cols-3 gap-3 sm:gap-4">
|
<div className="text-center">
|
||||||
<div className="flex items-center gap-2">
|
<p className="text-xs text-gray-500 uppercase tracking-wide mb-1">Total</p>
|
||||||
<div className="w-3 h-3 sm:w-4 sm:h-4 rounded bg-green-500"></div>
|
<p className="text-2xl font-bold text-gray-900">{monthEvents.length}</p>
|
||||||
<span className="text-xs sm:text-sm text-gray-700">Confirmado</span>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="text-center border-x border-gray-200">
|
||||||
<div className="w-3 h-3 sm:w-4 sm:h-4 rounded bg-yellow-500"></div>
|
<p className="text-xs text-gray-500 uppercase tracking-wide mb-1">Confirmados</p>
|
||||||
<span className="text-xs sm:text-sm text-gray-700">Pendente</span>
|
<p className="text-2xl font-bold text-green-600">
|
||||||
|
{monthEvents.filter(e => e.status === 'confirmed').length}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="text-center">
|
||||||
<div className="w-3 h-3 sm:w-4 sm:h-4 rounded bg-gray-400"></div>
|
<p className="text-xs text-gray-500 uppercase tracking-wide mb-1">Pendentes</p>
|
||||||
<span className="text-xs sm:text-sm text-gray-700">Concluído</span>
|
<p className="text-2xl font-bold text-yellow-600">
|
||||||
</div>
|
{monthEvents.filter(e => e.status === 'pending').length}
|
||||||
<div className="flex items-center gap-2">
|
</p>
|
||||||
<div className="w-3 h-3 sm:w-4 sm:h-4 rounded bg-[#492E61]"></div>
|
|
||||||
<span className="text-xs sm:text-sm text-gray-700">Formatura</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<div className="w-3 h-3 sm:w-4 sm:h-4 rounded bg-pink-500"></div>
|
|
||||||
<span className="text-xs sm:text-sm text-gray-700">Casamento</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<div className="w-3 h-3 sm:w-4 sm:h-4 rounded bg-blue-500"></div>
|
|
||||||
<span className="text-xs sm:text-sm text-gray-700">Evento</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Events List Sidebar */}
|
{/* Search Bar */}
|
||||||
<div className="space-y-4 sm:space-y-6">
|
<div className="bg-gray-50 p-3 rounded-lg border border-gray-100">
|
||||||
{/* Search */}
|
|
||||||
<div className="bg-white rounded-2xl shadow-lg border border-gray-200 p-3 sm:p-4">
|
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<Search className="absolute left-3 top-1/2 -translate-y-1/2 text-gray-400" size={18} />
|
<Search className="absolute left-3 top-1/2 -translate-y-1/2 text-gray-400" size={18} />
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Buscar eventos..."
|
placeholder="Buscar eventos..."
|
||||||
className="w-full pl-9 sm:pl-10 pr-3 sm:pr-4 py-2 sm:py-3 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-[#492E61] focus:border-transparent text-sm"
|
className="w-full pl-10 pr-4 py-2 border border-gray-200 rounded-sm focus:outline-none focus:border-brand-gold text-sm bg-white"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Upcoming Events */}
|
{/* Events List - Table Format */}
|
||||||
<div className="bg-white rounded-2xl shadow-lg border border-gray-200 p-4 sm:p-6">
|
<div className="bg-white rounded-lg border border-gray-200 overflow-hidden shadow-sm">
|
||||||
<h3 className="text-base sm:text-lg font-bold text-gray-900 mb-3 sm:mb-4 flex items-center gap-2">
|
<div className="overflow-x-auto">
|
||||||
<CalendarIcon size={18} className="sm:w-5 sm:h-5 text-[#492E61]" />
|
<table className="w-full">
|
||||||
Próximos Eventos
|
<thead className="bg-gray-50 border-b border-gray-200">
|
||||||
</h3>
|
<tr>
|
||||||
<div className="space-y-2 sm:space-y-3 max-h-[400px] sm:max-h-[600px] overflow-y-auto">
|
<th className="px-4 py-3 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider">
|
||||||
{MOCK_EVENTS.slice(0, 5).map((event) => (
|
Evento
|
||||||
<div
|
</th>
|
||||||
|
<th className="px-4 py-3 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider">
|
||||||
|
Data
|
||||||
|
</th>
|
||||||
|
<th className="px-4 py-3 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider">
|
||||||
|
Horário
|
||||||
|
</th>
|
||||||
|
<th className="px-4 py-3 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider">
|
||||||
|
Local
|
||||||
|
</th>
|
||||||
|
<th className="px-4 py-3 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider">
|
||||||
|
Cliente
|
||||||
|
</th>
|
||||||
|
<th className="px-4 py-3 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider">
|
||||||
|
Status
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody className="divide-y divide-gray-100">
|
||||||
|
{monthEvents.map((event) => (
|
||||||
|
<tr
|
||||||
key={event.id}
|
key={event.id}
|
||||||
className="p-3 sm:p-4 border-l-4 rounded-lg bg-gray-50 hover:bg-gray-100 transition-colors cursor-pointer"
|
className="hover:bg-gray-50 cursor-pointer transition-colors"
|
||||||
style={{ borderLeftColor: event.type === 'formatura' ? '#492E61' : event.type === 'casamento' ? '#ec4899' : '#3b82f6' }}
|
|
||||||
>
|
>
|
||||||
<div className="flex items-start justify-between mb-2 gap-2">
|
<td className="px-4 py-3">
|
||||||
<h4 className="font-semibold text-gray-900 text-xs sm:text-sm flex-1">{event.title}</h4>
|
<div className="flex items-center gap-2">
|
||||||
<span className={`text-[10px] sm:text-xs px-1.5 sm:px-2 py-0.5 sm:py-1 rounded-full border whitespace-nowrap ${getStatusColor(event.status)}`}>
|
<div
|
||||||
|
className={`w-1 h-8 rounded-full ${getTypeColor(event.type)}`}
|
||||||
|
/>
|
||||||
|
<span className="font-medium text-gray-900 text-sm">{event.title}</span>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td className="px-4 py-3">
|
||||||
|
<div className="flex items-center text-sm text-gray-700">
|
||||||
|
<CalendarIcon size={14} className="mr-1.5 text-brand-gold flex-shrink-0" />
|
||||||
|
{new Date(event.date + 'T00:00:00').toLocaleDateString('pt-BR')}
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td className="px-4 py-3">
|
||||||
|
<div className="flex items-center text-sm text-gray-700">
|
||||||
|
<Clock size={14} className="mr-1.5 text-gray-400 flex-shrink-0" />
|
||||||
|
{event.time}
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td className="px-4 py-3">
|
||||||
|
<div className="flex items-center text-sm text-gray-700">
|
||||||
|
<MapPin size={14} className="mr-1.5 text-brand-gold flex-shrink-0" />
|
||||||
|
<span className="truncate max-w-[200px]">{event.location}</span>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td className="px-4 py-3">
|
||||||
|
<div className="flex items-center text-sm text-gray-700">
|
||||||
|
<User size={14} className="mr-1.5 text-gray-400 flex-shrink-0" />
|
||||||
|
{event.client}
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td className="px-4 py-3">
|
||||||
|
<span className={`inline-flex items-center px-2 py-1 rounded text-xs font-semibold border ${getStatusColor(event.status)}`}>
|
||||||
{getStatusLabel(event.status)}
|
{getStatusLabel(event.status)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</td>
|
||||||
<div className="space-y-1">
|
</tr>
|
||||||
<div className="flex items-center gap-1.5 sm:gap-2 text-[10px] sm:text-xs text-gray-600">
|
|
||||||
<Clock size={12} className="sm:w-3.5 sm:h-3.5 flex-shrink-0" />
|
|
||||||
<span>{new Date(event.date).toLocaleDateString('pt-BR')} às {event.time}</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-1.5 sm:gap-2 text-[10px] sm:text-xs text-gray-600">
|
|
||||||
<MapPin size={12} className="sm:w-3.5 sm:h-3.5 flex-shrink-0" />
|
|
||||||
<span className="truncate">{event.location}</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-1.5 sm:gap-2 text-[10px] sm:text-xs text-gray-600">
|
|
||||||
<User size={12} className="sm:w-3.5 sm:h-3.5 flex-shrink-0" />
|
|
||||||
<span>{event.client}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{monthEvents.length === 0 && (
|
||||||
|
<div className="text-center py-12 text-gray-500">
|
||||||
|
<p>Nenhum evento encontrado neste mês.</p>
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -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 { 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 { EventForm } from "../components/EventForm";
|
||||||
import { Button } from "../components/Button";
|
import { Button } from "../components/Button";
|
||||||
import {
|
import {
|
||||||
|
|
@ -12,6 +13,8 @@ import {
|
||||||
Users,
|
Users,
|
||||||
Map,
|
Map,
|
||||||
Building2,
|
Building2,
|
||||||
|
Calendar,
|
||||||
|
MapPin,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { useAuth } from "../contexts/AuthContext";
|
import { useAuth } from "../contexts/AuthContext";
|
||||||
import { useData } from "../contexts/DataContext";
|
import { useData } from "../contexts/DataContext";
|
||||||
|
|
@ -39,6 +42,13 @@ export const Dashboard: React.FC<DashboardProps> = ({
|
||||||
const [searchTerm, setSearchTerm] = useState("");
|
const [searchTerm, setSearchTerm] = useState("");
|
||||||
const [selectedEvent, setSelectedEvent] = useState<EventData | null>(null);
|
const [selectedEvent, setSelectedEvent] = useState<EventData | null>(null);
|
||||||
const [activeFilter, setActiveFilter] = useState<string>("all");
|
const [activeFilter, setActiveFilter] = useState<string>("all");
|
||||||
|
const [advancedFilters, setAdvancedFilters] = useState<EventFilters>({
|
||||||
|
date: '',
|
||||||
|
city: '',
|
||||||
|
state: '',
|
||||||
|
timeRange: '',
|
||||||
|
type: '',
|
||||||
|
});
|
||||||
|
|
||||||
// Reset view when initialView prop changes
|
// Reset view when initialView prop changes
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -54,6 +64,31 @@ export const Dashboard: React.FC<DashboardProps> = ({
|
||||||
|
|
||||||
const myEvents = getEventsByRole(user.id, user.role);
|
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
|
// Filter Logic
|
||||||
const filteredEvents = myEvents.filter((e) => {
|
const filteredEvents = myEvents.filter((e) => {
|
||||||
const matchesSearch = e.name
|
const matchesSearch = e.name
|
||||||
|
|
@ -66,7 +101,16 @@ export const Dashboard: React.FC<DashboardProps> = ({
|
||||||
(activeFilter === "active" &&
|
(activeFilter === "active" &&
|
||||||
e.status !== EventStatus.ARCHIVED &&
|
e.status !== EventStatus.ARCHIVED &&
|
||||||
e.status !== EventStatus.PENDING_APPROVAL);
|
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) => {
|
const handleSaveEvent = (data: any) => {
|
||||||
|
|
@ -183,27 +227,7 @@ export const Dashboard: React.FC<DashboardProps> = ({
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderAdminActions = (event: EventData) => {
|
|
||||||
if (
|
|
||||||
user.role !== UserRole.BUSINESS_OWNER &&
|
|
||||||
user.role !== UserRole.SUPERADMIN
|
|
||||||
)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
if (event.status === EventStatus.PENDING_APPROVAL) {
|
|
||||||
return (
|
|
||||||
<div className="absolute top-3 left-3 flex space-x-2 z-10">
|
|
||||||
<button
|
|
||||||
onClick={(e) => handleApprove(e, event.id)}
|
|
||||||
className="bg-green-500 text-white px-3 py-1 rounded-sm text-xs font-bold shadow hover:bg-green-600 transition-colors flex items-center"
|
|
||||||
>
|
|
||||||
<CheckCircle size={12} className="mr-1" /> APROVAR
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
|
|
||||||
// --- MAIN RENDER ---
|
// --- MAIN RENDER ---
|
||||||
|
|
||||||
|
|
@ -221,7 +245,7 @@ export const Dashboard: React.FC<DashboardProps> = ({
|
||||||
{/* Content Switcher */}
|
{/* Content Switcher */}
|
||||||
{view === "list" && (
|
{view === "list" && (
|
||||||
<div className="space-y-6 fade-in">
|
<div className="space-y-6 fade-in">
|
||||||
{/* Filters Bar */}
|
{/* Search Bar */}
|
||||||
<div className="flex flex-col sm:flex-row gap-4 items-center justify-between bg-gray-50 p-3 rounded-lg border border-gray-100">
|
<div className="flex flex-col sm:flex-row gap-4 items-center justify-between bg-gray-50 p-3 rounded-lg border border-gray-100">
|
||||||
<div className="relative flex-1 w-full">
|
<div className="relative flex-1 w-full">
|
||||||
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 h-4 w-4" />
|
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 h-4 w-4" />
|
||||||
|
|
@ -259,30 +283,33 @@ export const Dashboard: React.FC<DashboardProps> = ({
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Grid */}
|
{/* Advanced Filters */}
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-2 xl:grid-cols-3 gap-4 sm:gap-6 md:gap-8">
|
<EventFiltersBar
|
||||||
{filteredEvents.map((event) => (
|
filters={advancedFilters}
|
||||||
<div key={event.id} className="relative group">
|
onFilterChange={setAdvancedFilters}
|
||||||
{renderAdminActions(event)}
|
availableCities={availableCities}
|
||||||
<EventCard
|
availableStates={availableStates}
|
||||||
event={event}
|
availableTypes={availableTypes}
|
||||||
onClick={() => {
|
/>
|
||||||
|
|
||||||
|
{/* Results Count */}
|
||||||
|
<div className="flex items-center justify-between text-sm text-gray-600">
|
||||||
|
<span>
|
||||||
|
Exibindo <strong className="text-brand-gold">{filteredEvents.length}</strong> de <strong>{myEvents.length}</strong> eventos
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Event Table */}
|
||||||
|
<EventTable
|
||||||
|
events={filteredEvents}
|
||||||
|
onEventClick={(event) => {
|
||||||
setSelectedEvent(event);
|
setSelectedEvent(event);
|
||||||
setView("details");
|
setView("details");
|
||||||
}}
|
}}
|
||||||
|
onApprove={handleApprove}
|
||||||
|
userRole={user.role}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{filteredEvents.length === 0 && (
|
|
||||||
<div className="text-center py-20 bg-gray-50 rounded-lg border border-dashed border-gray-200">
|
|
||||||
<p className="text-gray-500 mb-4">
|
|
||||||
Nenhum evento encontrado com os filtros atuais.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{(view === "create" || view === "edit") && (
|
{(view === "create" || view === "edit") && (
|
||||||
|
|
@ -319,24 +346,33 @@ export const Dashboard: React.FC<DashboardProps> = ({
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="bg-white border rounded-lg overflow-hidden shadow-sm">
|
<div className="bg-white border rounded-lg overflow-hidden shadow-sm">
|
||||||
<div className="h-64 w-full relative">
|
{/* Header Section - Sem foto */}
|
||||||
<img
|
<div className="bg-gradient-to-r from-brand-gold/5 to-brand-black/5 border-b-2 border-brand-gold p-6">
|
||||||
src={selectedEvent.coverImage}
|
<div className="flex items-start justify-between">
|
||||||
className="w-full h-full object-cover"
|
<div>
|
||||||
alt="Cover"
|
<h1 className="text-3xl font-serif font-bold text-brand-black mb-2">
|
||||||
/>
|
|
||||||
<div className="absolute inset-0 bg-black/40 flex items-center justify-center">
|
|
||||||
<h1 className="text-4xl font-serif text-white font-bold text-center px-4 drop-shadow-lg">
|
|
||||||
{selectedEvent.name}
|
{selectedEvent.name}
|
||||||
</h1>
|
</h1>
|
||||||
|
<div className="flex flex-wrap gap-3 text-sm text-gray-600">
|
||||||
|
<span className="flex items-center gap-1">
|
||||||
|
<Calendar size={16} className="text-brand-gold" />
|
||||||
|
{new Date(selectedEvent.date + 'T00:00:00').toLocaleDateString('pt-BR')} às {selectedEvent.time}
|
||||||
|
</span>
|
||||||
|
<span className="flex items-center gap-1">
|
||||||
|
<MapPin size={16} className="text-brand-gold" />
|
||||||
|
{selectedEvent.address.city}, {selectedEvent.address.state}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className={`px-4 py-2 rounded text-sm font-semibold ${STATUS_COLORS[selectedEvent.status]}`}>
|
||||||
|
{selectedEvent.status}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="p-4 sm:p-6 md:p-8">
|
<div className="p-6">
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6 md:gap-8">
|
|
||||||
<div className="lg:col-span-2 space-y-6 md:space-y-8">
|
|
||||||
{/* Actions Toolbar */}
|
{/* Actions Toolbar */}
|
||||||
<div className="flex flex-wrap gap-2 sm:gap-3 border-b pb-4">
|
<div className="flex flex-wrap gap-2 mb-6 pb-4 border-b">
|
||||||
{(user.role === UserRole.BUSINESS_OWNER ||
|
{(user.role === UserRole.BUSINESS_OWNER ||
|
||||||
user.role === UserRole.SUPERADMIN) && (
|
user.role === UserRole.SUPERADMIN) && (
|
||||||
<>
|
<>
|
||||||
|
|
@ -364,6 +400,31 @@ export const Dashboard: React.FC<DashboardProps> = ({
|
||||||
Informações
|
Informações
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
onClick={handleOpenMaps}
|
||||||
|
className="text-sm"
|
||||||
|
>
|
||||||
|
<Map size={16} className="mr-2" /> Abrir no Maps
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
||||||
|
<div className="lg:col-span-2 space-y-6">
|
||||||
|
{/* Quick Info Cards */}
|
||||||
|
<div className="grid grid-cols-1 sm:grid-cols-3 gap-4">
|
||||||
|
<div className="bg-gray-50 p-4 rounded border border-gray-200">
|
||||||
|
<p className="text-xs text-gray-500 uppercase tracking-wide mb-1">Tipo</p>
|
||||||
|
<p className="font-semibold text-gray-900">{selectedEvent.type}</p>
|
||||||
|
</div>
|
||||||
|
<div className="bg-gray-50 p-4 rounded border border-gray-200">
|
||||||
|
<p className="text-xs text-gray-500 uppercase tracking-wide mb-1">Data</p>
|
||||||
|
<p className="font-semibold text-gray-900">{new Date(selectedEvent.date + 'T00:00:00').toLocaleDateString('pt-BR')}</p>
|
||||||
|
</div>
|
||||||
|
<div className="bg-gray-50 p-4 rounded border border-gray-200">
|
||||||
|
<p className="text-xs text-gray-500 uppercase tracking-wide mb-1">Horário</p>
|
||||||
|
<p className="font-semibold text-gray-900">{selectedEvent.time}</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Institution Information */}
|
{/* Institution Information */}
|
||||||
|
|
@ -467,89 +528,64 @@ export const Dashboard: React.FC<DashboardProps> = ({
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="lg:col-span-1 space-y-4 sm:space-y-6">
|
<div className="lg:col-span-1 space-y-4">
|
||||||
<div
|
{/* Localização Card */}
|
||||||
className={`p-6 rounded-sm border ${STATUS_COLORS[selectedEvent.status]
|
<div className="border p-5 rounded bg-gray-50">
|
||||||
} bg-opacity-10`}
|
<h4 className="font-bold text-sm mb-3 text-gray-700 flex items-center gap-2">
|
||||||
>
|
<MapPin size={16} className="text-brand-gold" />
|
||||||
<h4 className="font-bold uppercase tracking-widest text-xs mb-2 opacity-70">
|
|
||||||
Status Atual
|
|
||||||
</h4>
|
|
||||||
<p className="text-xl font-serif font-bold">
|
|
||||||
{selectedEvent.status}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="border p-6 rounded-sm bg-gray-50">
|
|
||||||
<h4 className="font-bold uppercase tracking-widest text-xs mb-4 text-gray-400">
|
|
||||||
Localização
|
Localização
|
||||||
</h4>
|
</h4>
|
||||||
<p className="font-medium text-lg">
|
<p className="font-medium text-base mb-1">
|
||||||
{selectedEvent.address.street},{" "}
|
{selectedEvent.address.street}, {selectedEvent.address.number}
|
||||||
{selectedEvent.address.number}
|
|
||||||
</p>
|
</p>
|
||||||
<p className="text-gray-500 mb-4">
|
<p className="text-gray-600 text-sm">
|
||||||
{selectedEvent.address.city} -{" "}
|
{selectedEvent.address.city} - {selectedEvent.address.state}
|
||||||
{selectedEvent.address.state}
|
|
||||||
</p>
|
</p>
|
||||||
|
{selectedEvent.address.zip && (
|
||||||
{selectedEvent.address.mapLink ? (
|
<p className="text-gray-500 text-xs mt-1">CEP: {selectedEvent.address.zip}</p>
|
||||||
<Button
|
|
||||||
variant="secondary"
|
|
||||||
size="sm"
|
|
||||||
className="w-full"
|
|
||||||
onClick={handleOpenMaps}
|
|
||||||
>
|
|
||||||
<Map size={16} className="mr-2" /> Abrir no Google
|
|
||||||
Maps
|
|
||||||
</Button>
|
|
||||||
) : (
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
size="sm"
|
|
||||||
className="w-full bg-white"
|
|
||||||
onClick={handleOpenMaps}
|
|
||||||
>
|
|
||||||
<Map size={16} className="mr-2" /> Buscar no Maps
|
|
||||||
</Button>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Equipe Designada */}
|
||||||
{(selectedEvent.photographerIds.length > 0 ||
|
{(selectedEvent.photographerIds.length > 0 ||
|
||||||
user.role === UserRole.BUSINESS_OWNER) && (
|
user.role === UserRole.BUSINESS_OWNER ||
|
||||||
<div className="border p-6 rounded-sm">
|
user.role === UserRole.SUPERADMIN) && (
|
||||||
<div className="flex justify-between items-center mb-4">
|
<div className="border p-5 rounded bg-white">
|
||||||
<h4 className="font-bold uppercase tracking-widest text-xs text-gray-400">
|
<div className="flex justify-between items-center mb-3">
|
||||||
Equipe Designada
|
<h4 className="font-bold text-sm text-gray-700 flex items-center gap-2">
|
||||||
|
<Users size={16} className="text-brand-gold" />
|
||||||
|
Equipe ({selectedEvent.photographerIds.length})
|
||||||
</h4>
|
</h4>
|
||||||
{(user.role === UserRole.BUSINESS_OWNER ||
|
{(user.role === UserRole.BUSINESS_OWNER ||
|
||||||
user.role === UserRole.SUPERADMIN) && (
|
user.role === UserRole.SUPERADMIN) && (
|
||||||
<button
|
<button
|
||||||
onClick={handleManageTeam}
|
onClick={handleManageTeam}
|
||||||
className="text-brand-gold hover:text-brand-black"
|
className="text-brand-gold hover:text-brand-black transition-colors"
|
||||||
|
title="Adicionar fotógrafo"
|
||||||
>
|
>
|
||||||
<PlusCircle size={16} />
|
<PlusCircle size={18} />
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{selectedEvent.photographerIds.length > 0 ? (
|
{selectedEvent.photographerIds.length > 0 ? (
|
||||||
<div className="flex -space-x-2">
|
<div className="space-y-2">
|
||||||
{selectedEvent.photographerIds.map((id, idx) => (
|
{selectedEvent.photographerIds.map((id, idx) => (
|
||||||
|
<div key={id} className="flex items-center gap-2 text-sm">
|
||||||
<div
|
<div
|
||||||
key={id}
|
className="w-8 h-8 rounded-full border-2 border-gray-200 bg-gray-300 flex-shrink-0"
|
||||||
className="w-10 h-10 rounded-full border-2 border-white bg-gray-300"
|
|
||||||
style={{
|
style={{
|
||||||
backgroundImage: `url(https://i.pravatar.cc/100?u=${id})`,
|
backgroundImage: `url(https://i.pravatar.cc/100?u=${id})`,
|
||||||
backgroundSize: "cover",
|
backgroundSize: "cover",
|
||||||
}}
|
}}
|
||||||
title={id}
|
|
||||||
></div>
|
></div>
|
||||||
|
<span className="text-gray-700">{id}</span>
|
||||||
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<p className="text-sm text-gray-400 italic">
|
<p className="text-sm text-gray-400 italic">
|
||||||
Nenhum profissional atribuído.
|
Nenhum profissional atribuído
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -42,35 +42,19 @@ export const Login: React.FC<LoginProps> = ({ onNavigate }) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen flex flex-col lg:flex-row bg-white">
|
<div className="min-h-screen flex items-center justify-center bg-gradient-to-br from-gray-50 to-white p-4">
|
||||||
{/* Left Side - Image */}
|
<div className="w-full max-w-md space-y-8 fade-in">
|
||||||
<div className="hidden lg:flex lg:w-1/2 relative overflow-hidden">
|
{/* Logo */}
|
||||||
<img
|
<div className="flex justify-center mb-8">
|
||||||
src="https://plus.unsplash.com/premium_photo-1713296255442-e9338f42aad8?q=80&w=722&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"
|
|
||||||
alt="Photum Login"
|
|
||||||
className="absolute inset-0 w-full h-full object-cover"
|
|
||||||
/>
|
|
||||||
<div className="absolute inset-0 bg-gradient-to-br from-[#492E61]/90 to-[#492E61]/70 flex items-center justify-center">
|
|
||||||
<div className="text-center text-white p-12">
|
|
||||||
<h1 className="text-5xl font-serif font-bold mb-4">Photum Formaturas</h1>
|
|
||||||
<p className="text-xl font-light tracking-wide max-w-md mx-auto">Gestão de eventos premium para quem não abre mão da excelência.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Right Side - Form */}
|
|
||||||
<div className="w-full lg:w-1/2 flex items-center justify-center p-4 sm:p-6 md:p-8 lg:p-16">
|
|
||||||
<div className="max-w-md w-full space-y-6 sm:space-y-8 fade-in">
|
|
||||||
{/* Logo Mobile */}
|
|
||||||
<div className="lg:hidden flex justify-center mb-6">
|
|
||||||
<img
|
<img
|
||||||
src="/logo.png"
|
src="/logo.png"
|
||||||
alt="Photum Formaturas"
|
alt="Photum Formaturas"
|
||||||
className="h-16 w-auto object-contain"
|
className="h-20 w-auto object-contain"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="text-center lg:text-left">
|
<div className="bg-white rounded-2xl shadow-xl border border-gray-100 p-8">
|
||||||
|
<div className="text-center">
|
||||||
<span className="font-bold tracking-widest uppercase text-xs sm:text-sm" style={{ color: '#B9CF33' }}>Bem-vindo de volta</span>
|
<span className="font-bold tracking-widest uppercase text-xs sm:text-sm" style={{ color: '#B9CF33' }}>Bem-vindo de volta</span>
|
||||||
<h2 className="mt-2 text-2xl sm:text-3xl font-serif font-bold text-gray-900">Acesse sua conta</h2>
|
<h2 className="mt-2 text-2xl sm:text-3xl font-serif font-bold text-gray-900">Acesse sua conta</h2>
|
||||||
<p className="mt-2 text-xs sm:text-sm text-gray-600">
|
<p className="mt-2 text-xs sm:text-sm text-gray-600">
|
||||||
|
|
@ -135,9 +119,10 @@ export const Login: React.FC<LoginProps> = ({ onNavigate }) => {
|
||||||
{isLoading ? 'Entrando...' : 'Entrar no Sistema'}
|
{isLoading ? 'Entrando...' : 'Entrar no Sistema'}
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Demo Users Quick Select - Melhorado para Mobile */}
|
{/* Demo Users Quick Select */}
|
||||||
<div className="mt-6 sm:mt-10 pt-6 sm:pt-10 border-t border-gray-200">
|
<div className="bg-white rounded-2xl shadow-xl border border-gray-100 p-6">
|
||||||
<p className="text-[10px] sm:text-xs uppercase tracking-widest mb-3 sm:mb-4 text-center text-gray-400">Usuários de Demonstração (Clique para preencher)</p>
|
<p className="text-[10px] sm:text-xs uppercase tracking-widest mb-3 sm:mb-4 text-center text-gray-400">Usuários de Demonstração (Clique para preencher)</p>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
{availableUsers.map(user => (
|
{availableUsers.map(user => (
|
||||||
|
|
@ -171,6 +156,5 @@ export const Login: React.FC<LoginProps> = ({ onNavigate }) => {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -128,40 +128,22 @@ export const Register: React.FC<RegisterProps> = ({ onNavigate }) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen flex flex-col lg:flex-row bg-white">
|
<div className="min-h-screen flex items-center justify-center bg-gradient-to-br from-gray-50 to-white p-4 py-12">
|
||||||
{/* Left Side - Image */}
|
<div className="w-full max-w-md space-y-8 fade-in">
|
||||||
<div className="hidden lg:flex lg:w-1/2 relative overflow-hidden">
|
{/* Logo */}
|
||||||
<img
|
<div className="flex justify-center mb-8">
|
||||||
src="https://images.unsplash.com/photo-1541339907198-e08756dedf3f?fm=jpg&q=60&w=3000&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"
|
|
||||||
alt="Photum Cadastro"
|
|
||||||
className="absolute inset-0 w-full h-full object-cover"
|
|
||||||
/>
|
|
||||||
<div className="absolute inset-0 bg-gradient-to-br from-[#492E61]/90 to-[#492E61]/70 flex items-center justify-center">
|
|
||||||
<div className="text-center text-white p-12">
|
|
||||||
<h1 className="text-5xl font-serif font-bold mb-4">Faça parte da Photum</h1>
|
|
||||||
<p className="text-xl font-light tracking-wide max-w-md mx-auto">
|
|
||||||
Eternize seus momentos especiais com a melhor plataforma de gestão de eventos fotográficos.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Right Side - Form */}
|
|
||||||
<div className="w-full lg:w-1/2 flex items-center justify-center p-4 sm:p-6 md:p-8 lg:p-16">
|
|
||||||
<div className="max-w-md w-full space-y-6 sm:space-y-8 fade-in">
|
|
||||||
{/* Logo Mobile */}
|
|
||||||
<div className="lg:hidden flex justify-center mb-6">
|
|
||||||
<img
|
<img
|
||||||
src="/logo.png"
|
src="/logo.png"
|
||||||
alt="Photum Formaturas"
|
alt="Photum Formaturas"
|
||||||
className="h-16 w-auto object-contain"
|
className="h-20 w-auto object-contain"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="text-center lg:text-left">
|
<div className="bg-white rounded-2xl shadow-xl border border-gray-100 p-8">
|
||||||
<span className="font-bold tracking-widest uppercase text-xs sm:text-sm" style={{ color: '#B9CF33' }}>Comece agora</span>
|
<div className="text-center">
|
||||||
<h2 className="mt-2 text-2xl sm:text-3xl font-serif font-bold text-gray-900">Crie sua conta</h2>
|
<span className="font-bold tracking-widest uppercase text-sm" style={{ color: '#B9CF33' }}>Comece agora</span>
|
||||||
<p className="mt-2 text-xs sm:text-sm text-gray-600">
|
<h2 className="mt-3 text-3xl font-serif font-bold text-gray-900">Crie sua conta</h2>
|
||||||
|
<p className="mt-3 text-sm text-gray-600">
|
||||||
Já tem uma conta?{' '}
|
Já tem uma conta?{' '}
|
||||||
<button
|
<button
|
||||||
onClick={() => onNavigate('login')}
|
onClick={() => onNavigate('login')}
|
||||||
|
|
@ -173,8 +155,8 @@ export const Register: React.FC<RegisterProps> = ({ onNavigate }) => {
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form className="mt-6 sm:mt-8 space-y-4 sm:space-y-5" onSubmit={handleSubmit}>
|
<form className="mt-8 space-y-5" onSubmit={handleSubmit}>
|
||||||
<div className="space-y-3 sm:space-y-4">
|
<div className="space-y-4">
|
||||||
<Input
|
<Input
|
||||||
label="Nome Completo"
|
label="Nome Completo"
|
||||||
type="text"
|
type="text"
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue