diff --git a/Frontend/src/components/PremiumGallery.tsx b/Frontend/src/components/PremiumGallery.tsx new file mode 100644 index 0000000..6f9ec37 --- /dev/null +++ b/Frontend/src/components/PremiumGallery.tsx @@ -0,0 +1,198 @@ +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(null); + const touchStartX = useRef(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 ( +
+ {/* CONTENEDOR PRINCIPAL */} +
setIsFullscreen(true)} + > +
+ Vehículo +
+ + {/* HUD DE INFO */} +
+ {statusBadge} + {featuredBadge} + {locationBadge} +
+ + + + {photos.length > 1 && ( + <> + + + + )} + +
+
+ +
+
+ +
+ {activePhoto + 1} / {photos.length} +
+
+ + {/* MINIATURAS (THUMBNAILS) */} + {photos.length > 1 && ( +
+ {photos.map((p, idx) => ( + + ))} +
+ )} + + {/* LIGHTBOX CON PORTAL */} + {isFullscreen && createPortal( +
setIsFullscreen(false)}> + + {/* Botón Cerrar Desktop (X Minimalista) */} + + +
e.stopPropagation()} + onTouchStart={handleTouchStart} + onTouchEnd={handleTouchEnd} + > + Vista amplia + + {photos.length > 1 && ( + <> + + + + )} + + {/* Info inferior (Contador) */} +
+ Foto {activePhoto + 1} de {photos.length} +
+ + {/* BOTÓN CERRAR MÓVIL (Píldora ergonómica) */} + +
+
, + document.body + )} +
+ ); +} diff --git a/Frontend/src/pages/VehiculoDetailPage.tsx b/Frontend/src/pages/VehiculoDetailPage.tsx index 247a82b..e96949a 100644 --- a/Frontend/src/pages/VehiculoDetailPage.tsx +++ b/Frontend/src/pages/VehiculoDetailPage.tsx @@ -6,13 +6,13 @@ import ChatModal from '../components/ChatModal'; import { FaWhatsapp, FaMapMarkerAlt, FaInfoCircle, FaShareAlt } from 'react-icons/fa'; import { AD_STATUSES } from '../constants/adStatuses'; import AdStatusBadge from '../components/AdStatusBadge'; +import PremiumGallery from '../components/PremiumGallery'; export default function VehiculoDetailPage() { const { id } = useParams(); const [vehicle, setVehicle] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); - const [activePhoto, setActivePhoto] = useState(0); const [isFavorite, setIsFavorite] = useState(false); const [isChatOpen, setIsChatOpen] = useState(false); const user = AuthService.getCurrentUser(); @@ -88,11 +88,6 @@ export default function VehiculoDetailPage() { const isAdActive = vehicle.statusID === AD_STATUSES.ACTIVE; const isContactable = isAdActive && !isOwnerAdmin; - const getImageUrl = (path: string) => { - if (!path) return "/placeholder-car.png"; - return path.startsWith('http') ? path : `${import.meta.env.VITE_STATIC_BASE_URL}${path}`; - }; - return (