Files
MotoresArgentinosV2/Frontend/src/components/PremiumGallery.tsx

199 lines
8.7 KiB
TypeScript
Raw Normal View History

2026-01-30 11:39:14 -03:00
import { useState, useRef, useEffect } from 'react';
import { createPortal } from 'react-dom';
import { FaChevronLeft, FaChevronRight, FaExpand, FaTimes, FaArrowLeft } from 'react-icons/fa';
interface Photo {
filePath: string;
}
interface PremiumGalleryProps {
photos: Photo[];
isAdActive: boolean;
onFavoriteToggle: () => void;
isFavorite: boolean;
statusBadge: React.ReactNode;
featuredBadge: React.ReactNode;
locationBadge: React.ReactNode;
}
export default function PremiumGallery({
photos,
isAdActive,
onFavoriteToggle,
isFavorite,
statusBadge,
featuredBadge,
locationBadge
}: PremiumGalleryProps) {
const [activePhoto, setActivePhoto] = useState(0);
const [isFullscreen, setIsFullscreen] = useState(false);
const containerRef = useRef<HTMLDivElement>(null);
const touchStartX = useRef<number | null>(null);
const getImageUrl = (path: string) => {
if (!path) return "/placeholder-car.png";
return path.startsWith('http') ? path : `${import.meta.env.VITE_STATIC_BASE_URL}${path}`;
};
const handleTouchStart = (e: React.TouchEvent) => {
touchStartX.current = e.touches[0].clientX;
};
const handleTouchEnd = (e: React.TouchEvent) => {
if (touchStartX.current === null) return;
const touchEndX = e.changedTouches[0].clientX;
const diff = touchStartX.current - touchEndX;
if (Math.abs(diff) > 50) {
if (diff > 0) nextPhoto();
else prevPhoto();
}
touchStartX.current = null;
};
const nextPhoto = () => setActivePhoto((prev) => (prev + 1) % photos.length);
const prevPhoto = () => setActivePhoto((prev) => (prev - 1 + photos.length) % photos.length);
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
if (isFullscreen) {
if (e.key === 'ArrowRight') nextPhoto();
if (e.key === 'ArrowLeft') prevPhoto();
if (e.key === 'Escape') setIsFullscreen(false);
}
};
window.addEventListener('keydown', handleKeyDown);
return () => window.removeEventListener('keydown', handleKeyDown);
}, [isFullscreen]);
if (!photos || photos.length === 0) return null;
return (
<div className="space-y-4 select-none">
{/* CONTENEDOR PRINCIPAL */}
<div
ref={containerRef}
className="glass rounded-2xl md:rounded-[3rem] overflow-hidden border border-white/5 relative h-[350px] md:h-[550px] shadow-2xl group bg-black/40 cursor-zoom-in"
onTouchStart={handleTouchStart}
onTouchEnd={handleTouchEnd}
onClick={() => setIsFullscreen(true)}
>
<div className="relative w-full h-full overflow-hidden flex items-center justify-center">
<img
src={getImageUrl(photos[activePhoto].filePath)}
className={`max-h-full max-w-full object-contain transition-transform duration-500 ${!isAdActive ? 'grayscale' : ''}`}
alt="Vehículo"
/>
</div>
{/* HUD DE INFO */}
<div className="absolute top-4 md:top-8 left-4 md:left-8 flex flex-col items-start gap-2 z-10">
{statusBadge}
{featuredBadge}
{locationBadge}
</div>
<button
onClick={(e) => { e.stopPropagation(); onFavoriteToggle(); }}
className={`absolute top-4 md:top-8 right-4 md:right-8 w-12 h-12 md:w-14 md:h-14 rounded-xl md:rounded-2xl flex items-center justify-center text-xl md:text-2xl transition-all shadow-xl backdrop-blur-xl border z-10 ${isFavorite ? 'bg-red-600 border-red-500/50 text-white' : 'bg-black/40 border-white/10 text-white hover:bg-white hover:text-red-500'}`}
>
{isFavorite ? '❤️' : '🤍'}
</button>
{photos.length > 1 && (
<>
<button
onClick={(e) => { e.stopPropagation(); prevPhoto(); }}
className="absolute left-4 top-1/2 -translate-y-1/2 w-10 md:w-12 h-10 md:h-12 bg-black/40 hover:bg-white hover:text-black backdrop-blur-md rounded-full border border-white/10 flex items-center justify-center transition-all opacity-0 group-hover:opacity-100 translate-x-[-10px] group-hover:translate-x-0 z-10"
>
<FaChevronLeft />
</button>
<button
onClick={(e) => { e.stopPropagation(); nextPhoto(); }}
className="absolute right-4 top-1/2 -translate-y-1/2 w-10 md:w-12 h-10 md:h-12 bg-black/40 hover:bg-white hover:text-black backdrop-blur-md rounded-full border border-white/10 flex items-center justify-center transition-all opacity-0 group-hover:opacity-100 translate-x-[10px] group-hover:translate-x-0 z-10"
>
<FaChevronRight />
</button>
</>
)}
<div className="absolute bottom-6 right-6 opacity-0 group-hover:opacity-100 transition-opacity z-10 hidden md:block">
<div className="bg-black/60 backdrop-blur-md p-3 rounded-xl border border-white/10 text-white/70">
<FaExpand size={18} />
</div>
</div>
<div className="absolute bottom-6 left-1/2 -translate-x-1/2 bg-black/40 backdrop-blur-md px-4 py-1.5 rounded-full border border-white/10 text-[10px] font-black tracking-[0.2em] text-white/80 z-10">
{activePhoto + 1} / {photos.length}
</div>
</div>
{/* MINIATURAS (THUMBNAILS) */}
{photos.length > 1 && (
<div className="flex gap-3 md:gap-4 overflow-x-auto pt-4 pb-4 px-1 scrollbar-hide no-scrollbar items-center justify-center">
{photos.map((p, idx) => (
<button
key={idx}
onClick={() => setActivePhoto(idx)}
className={`relative w-24 md:w-32 h-16 md:h-20 rounded-xl md:rounded-2xl overflow-hidden flex-shrink-0 border-2 transition-all duration-300 ${activePhoto === idx ? 'border-blue-500 scale-105 shadow-lg ring-4 ring-blue-500/20' : 'border-white/5 opacity-40 hover:opacity-100 hover:border-white/20'}`}
>
<img src={getImageUrl(p.filePath)} className="w-full h-full object-cover" alt="" />
{activePhoto === idx && <div className="absolute inset-0 bg-blue-500/10 pointer-events-none" />}
</button>
))}
</div>
)}
{/* LIGHTBOX CON PORTAL */}
{isFullscreen && createPortal(
<div className="fixed inset-0 z-[99999] bg-black/98 backdrop-blur-3xl flex flex-col items-center justify-center animate-fade-in" onClick={() => setIsFullscreen(false)}>
{/* Botón Cerrar Desktop (X Minimalista) */}
<button
className="absolute top-8 right-8 text-white/40 hover:text-white transition-all hidden md:flex items-center gap-2 group z-[100000]"
onClick={() => setIsFullscreen(false)}
>
<span className="text-[10px] font-black tracking-widest opacity-0 group-hover:opacity-100 transition-opacity">CERRAR</span>
<FaTimes size={24} />
</button>
<div
className="relative w-full h-full flex flex-col items-center justify-center p-4 md:p-12"
onClick={(e) => e.stopPropagation()}
onTouchStart={handleTouchStart}
onTouchEnd={handleTouchEnd}
>
<img
src={getImageUrl(photos[activePhoto].filePath)}
className="max-h-[80vh] max-w-full object-contain animate-scale-up shadow-2xl"
alt="Vista amplia"
/>
{photos.length > 1 && (
<>
<button onClick={(e) => { e.stopPropagation(); prevPhoto(); }} className="absolute left-4 md:left-12 top-1/2 -translate-y-1/2 w-16 h-16 bg-white/5 hover:bg-white hover:text-black rounded-full border border-white/5 flex items-center justify-center transition-all text-2xl hidden md:flex"><FaChevronLeft /></button>
<button onClick={(e) => { e.stopPropagation(); nextPhoto(); }} className="absolute right-4 md:right-12 top-1/2 -translate-y-1/2 w-16 h-16 bg-white/5 hover:bg-white hover:text-black rounded-full border border-white/5 flex items-center justify-center transition-all text-2xl hidden md:flex"><FaChevronRight /></button>
</>
)}
{/* Info inferior (Contador) */}
<div className="mt-8 text-white/30 font-black tracking-[0.4em] text-[10px] uppercase">
Foto {activePhoto + 1} de {photos.length}
</div>
{/* BOTÓN CERRAR MÓVIL (Píldora ergonómica) */}
<button
className="mt-12 md:hidden bg-white/10 border border-white/20 text-white px-8 py-4 rounded-full font-black text-xs tracking-widest flex items-center gap-2 hover:bg-white/20 active:scale-95 transition-all shadow-xl"
onClick={() => setIsFullscreen(false)}
>
<FaArrowLeft size={10} /> VOLVER AL AVISO
</button>
</div>
</div>,
document.body
)}
</div>
);
}