Frontend: - Migração da API de Geocoding do Mapbox para o Google Maps Places API (googleMapsService.ts) no formulário de eventos, garantindo a busca correta pelo nome de locais (estádios, teatros) e com autopreenchimento. - Correção do fluxo de estado do 'horario_fim', propagando e persistindo o 'endTime' pelo DataContext, garantindo a população dos dados na edição do EventForm. - Adição da visualização do horário final na listagem do Dashboard, no EventCard, painéis de EventDetails e atualização das props defaultEndTime no EventScheduler. Backend: - Atualização e migração dos arquivos gerados pelo sqlc (models.go, agenda.sql.go) para suportar operações no novo design do banco. - Atualização síncrona dos artefatos Swagger de documentação de API (docs.go, swagger.json, swagger.yaml).
100 lines
5.1 KiB
TypeScript
100 lines
5.1 KiB
TypeScript
|
|
import React, { useState } from 'react';
|
|
import { EventData, EventStatus } from '../types';
|
|
import { Calendar, MapPin, ArrowRight, UserCheck } from 'lucide-react';
|
|
import { STATUS_COLORS } from '../constants';
|
|
|
|
interface EventCardProps {
|
|
event: EventData;
|
|
onClick: () => void;
|
|
}
|
|
|
|
export const EventCard: React.FC<EventCardProps> = ({ event, onClick }) => {
|
|
const [imageLoaded, setImageLoaded] = useState(false);
|
|
const fullAddress = `${event.address.street}, ${event.address.number} - ${event.address.city}/${event.address.state}`;
|
|
|
|
return (
|
|
<div
|
|
className="group bg-white rounded-lg border border-gray-100 overflow-hidden hover:shadow-xl transition-all duration-300 cursor-pointer flex flex-col h-full"
|
|
onClick={onClick}
|
|
>
|
|
<div className="relative h-40 sm:h-44 md:h-48 overflow-hidden bg-gray-100">
|
|
{/* Skeleton / Loading State */}
|
|
{!imageLoaded && (
|
|
<div className="absolute inset-0 bg-gray-200 animate-pulse flex items-center justify-center">
|
|
<div className="w-6 h-6 sm:w-8 sm:h-8 border-2 border-gray-300 border-t-brand-gold rounded-full animate-spin"></div>
|
|
</div>
|
|
)}
|
|
|
|
<img
|
|
src={event.coverImage}
|
|
alt={event.name}
|
|
className={`w-full h-full object-cover transition-all duration-700 group-hover:scale-105 ${imageLoaded ? 'opacity-100' : 'opacity-0'}`}
|
|
onLoad={() => setImageLoaded(true)}
|
|
/>
|
|
<div className="absolute inset-0 bg-gradient-to-t from-black/60 to-transparent opacity-60"></div>
|
|
<div className="absolute top-2 sm:top-3 right-2 sm:right-3">
|
|
<span className={`text-[10px] sm:text-xs font-semibold px-1.5 sm:px-2 py-0.5 sm:py-1 rounded-sm uppercase tracking-wide ${STATUS_COLORS[event.status]}`}>
|
|
{event.status}
|
|
</span>
|
|
</div>
|
|
<div className="absolute bottom-2 sm:bottom-3 left-3 sm:left-4 text-white">
|
|
<p className="text-[10px] sm:text-xs font-light uppercase tracking-widest opacity-90">{event.type}</p>
|
|
<h3 className="text-base sm:text-lg font-serif font-medium drop-shadow-md line-clamp-1">{event.name}</h3>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="p-3 sm:p-4 md:p-5 flex-1 flex flex-col justify-between">
|
|
<div className="space-y-2 sm:space-y-3">
|
|
<div className="flex items-center text-gray-500 text-xs sm:text-sm">
|
|
<Calendar size={14} className="sm:w-4 sm:h-4 mr-1.5 sm:mr-2 text-brand-gold flex-shrink-0" />
|
|
<span className="truncate">{new Date(event.date).toLocaleDateString()} às {event.startTime || event.time}{event.endTime || event.horario_fim ? ` - ${event.endTime || event.horario_fim}` : ''}</span>
|
|
</div>
|
|
|
|
{/* Location with Tooltip */}
|
|
<div className="relative group/tooltip">
|
|
<div
|
|
className="flex items-center text-gray-500 text-xs sm:text-sm"
|
|
title={fullAddress} // Native tooltip fallback
|
|
>
|
|
<MapPin size={14} className="sm:w-4 sm:h-4 mr-1.5 sm:mr-2 text-brand-gold flex-shrink-0" />
|
|
<span className="truncate">{event.address.city}, {event.address.state}</span>
|
|
</div>
|
|
|
|
{/* Custom Tooltip - Hidden on mobile */}
|
|
<div className="absolute bottom-full left-0 mb-2 hidden sm:group-hover/tooltip:block z-20 pointer-events-none">
|
|
<div className="bg-brand-black text-white text-xs rounded py-1.5 px-3 whitespace-nowrap shadow-xl">
|
|
{event.address.street}, {event.address.number}
|
|
<br />
|
|
{event.address.city} - {event.address.state}
|
|
{/* Arrow */}
|
|
<div className="absolute top-full left-3 -mt-1 border-4 border-transparent border-t-brand-black"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{event.contacts.length > 0 && (
|
|
<div className="flex items-center text-gray-500 text-xs sm:text-sm">
|
|
<UserCheck size={14} className="sm:w-4 sm:h-4 mr-1.5 sm:mr-2 text-brand-gold flex-shrink-0" />
|
|
<span className="truncate">{event.contacts.length} {event.contacts.length === 1 ? 'Contato' : 'Contatos'}</span>
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
<div className="mt-3 sm:mt-4 md:mt-5 pt-3 sm:pt-4 border-t border-gray-50 flex items-center justify-between">
|
|
<div className="flex -space-x-1.5 sm:-space-x-2">
|
|
{[1, 2, 3].map(i => (
|
|
<div key={i} className="w-6 h-6 sm:w-7 sm:h-7 md:w-8 md:h-8 rounded-full border-2 border-white bg-gray-200" style={{ backgroundImage: `url(https://i.pravatar.cc/100?img=${i})`, backgroundSize: 'cover' }}></div>
|
|
))}
|
|
<div className="w-6 h-6 sm:w-7 sm:h-7 md:w-8 md:h-8 rounded-full border-2 border-white bg-gray-100 flex items-center justify-center text-[10px] sm:text-xs text-gray-500 font-medium">
|
|
+4
|
|
</div>
|
|
</div>
|
|
<button className="text-brand-black group-hover:text-brand-gold transition-colors">
|
|
<ArrowRight size={18} className="sm:w-5 sm:h-5" />
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|