This commit sets up the foundational project structure for PhotumManager. It includes: - Initializing a new React project with Vite. - Configuring essential dependencies such as React, Lucide React, and the Google Generative AI SDK. - Setting up TypeScript and Vite configurations for optimal development. - Defining core application metadata and initial type definitions for users and events. - Establishing basic styling and font configurations in `index.html` with Tailwind CSS. - Adding a `.gitignore` file to manage project dependencies and build artifacts. - Updating the README with instructions for local development.
100 lines
4.5 KiB
TypeScript
100 lines
4.5 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-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-8 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-3 right-3">
|
|
<span className={`text-xs font-semibold px-2 py-1 rounded-sm uppercase tracking-wide ${STATUS_COLORS[event.status]}`}>
|
|
{event.status}
|
|
</span>
|
|
</div>
|
|
<div className="absolute bottom-3 left-4 text-white">
|
|
<p className="text-xs font-light uppercase tracking-widest opacity-90">{event.type}</p>
|
|
<h3 className="text-lg font-serif font-medium drop-shadow-md">{event.name}</h3>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="p-5 flex-1 flex flex-col justify-between">
|
|
<div className="space-y-3">
|
|
<div className="flex items-center text-gray-500 text-sm">
|
|
<Calendar size={16} className="mr-2 text-brand-gold" />
|
|
<span>{new Date(event.date).toLocaleDateString()} às {event.time}</span>
|
|
</div>
|
|
|
|
{/* Location with Tooltip */}
|
|
<div className="relative group/tooltip">
|
|
<div
|
|
className="flex items-center text-gray-500 text-sm"
|
|
title={fullAddress} // Native tooltip fallback
|
|
>
|
|
<MapPin size={16} className="mr-2 text-brand-gold flex-shrink-0" />
|
|
<span className="truncate">{event.address.city}, {event.address.state}</span>
|
|
</div>
|
|
|
|
{/* Custom Tooltip */}
|
|
<div className="absolute bottom-full left-0 mb-2 hidden 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-sm">
|
|
<UserCheck size={16} className="mr-2 text-brand-gold" />
|
|
<span>{event.contacts.length} {event.contacts.length === 1 ? 'Fornecedor/Contato' : 'Fornecedores/Contatos'}</span>
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
<div className="mt-5 pt-4 border-t border-gray-50 flex items-center justify-between">
|
|
<div className="flex -space-x-2">
|
|
{[1,2,3].map(i => (
|
|
<div key={i} className="w-8 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-8 h-8 rounded-full border-2 border-white bg-gray-100 flex items-center justify-center text-xs text-gray-500 font-medium">
|
|
+4
|
|
</div>
|
|
</div>
|
|
<button className="text-brand-black group-hover:text-brand-gold transition-colors">
|
|
<ArrowRight size={20} />
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|