import React, { useState, useEffect } from 'react'; import { useParams, useLocation, useNavigate } from 'react-router-dom'; import { Dialog, DialogContent } from '@/components/ui/dialog'; import { Button } from '@/components/ui/button'; import { Heart, ChevronLeft, ChevronRight, X } from 'lucide-react'; import { likePhoto } from '../services/photosApi'; type Photo = { id: number; file_path?: string; thumbnail_path?: string; likes_count?: number; created_at?: string; task_id?: number; task_title?: string; }; type Task = { id: number; title: string }; interface Props { photos?: Photo[]; currentIndex?: number; onClose?: () => void; onIndexChange?: (index: number) => void; slug?: string; } export default function PhotoLightbox({ photos, currentIndex, onClose, onIndexChange, slug }: Props) { const params = useParams(); const location = useLocation(); const navigate = useNavigate(); const photoId = params.photoId; const eventSlug = params.slug || slug; const [standalonePhoto, setStandalonePhoto] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [task, setTask] = useState(null); const [taskLoading, setTaskLoading] = useState(false); const [likes, setLikes] = useState(0); const [liked, setLiked] = useState(false); // Determine mode and photo const isStandalone = !photos || photos.length === 0; const currentPhotos = isStandalone ? (standalonePhoto ? [standalonePhoto] : []) : photos || []; const currentIndexVal = isStandalone ? 0 : (currentIndex || 0); const photo = currentPhotos[currentIndexVal]; // Fallback onClose for standalone const handleClose = onClose || (() => navigate(-1)); // Fetch single photo for standalone mode useEffect(() => { if (isStandalone && photoId && !standalonePhoto && eventSlug) { const fetchPhoto = async () => { setLoading(true); setError(null); try { const res = await fetch(`/api/v1/photos/${photoId}`); if (res.ok) { const fetchedPhoto: Photo = await res.json(); setStandalonePhoto(fetchedPhoto); // Check state for initial photo if (location.state?.photo) { setStandalonePhoto(location.state.photo); } } else { setError('Foto nicht gefunden'); } } catch (err) { setError('Fehler beim Laden des Fotos'); } finally { setLoading(false); } }; fetchPhoto(); } else if (!isStandalone) { setLoading(false); } }, [isStandalone, photoId, eventSlug, standalonePhoto, location.state]); // Update likes when photo changes React.useEffect(() => { if (photo) { setLikes(photo.likes_count ?? 0); // Check if liked from localStorage try { const raw = localStorage.getItem('liked-photo-ids'); const likedIds = raw ? JSON.parse(raw) : []; setLiked(likedIds.includes(photo.id)); } catch { setLiked(false); } } }, [photo]); const touchRef = React.useRef(null); const startX = React.useRef(0); const currentX = React.useRef(0); const handleTouchStart = (e: React.TouchEvent) => { startX.current = e.touches[0].clientX; }; const handleTouchMove = (e: React.TouchEvent) => { if (!touchRef.current) return; currentX.current = e.touches[0].clientX; const deltaX = currentX.current - startX.current; touchRef.current.style.transform = `translateX(${deltaX}px)`; }; const handleTouchEnd = () => { if (!touchRef.current) return; const deltaX = currentX.current - startX.current; const threshold = 50; // pixels touchRef.current.style.transform = 'translateX(0)'; if (Math.abs(deltaX) > threshold) { if (deltaX > 0 && currentIndexVal > 0) { // Swipe right - previous onIndexChange?.(currentIndexVal - 1); } else if (deltaX < 0 && currentIndexVal < currentPhotos.length - 1) { // Swipe left - next onIndexChange?.(currentIndexVal + 1); } } }; // Load task info if photo has task_id and slug is available React.useEffect(() => { if (!photo?.task_id || !slug) { setTask(null); setTaskLoading(false); return; } const taskId = photo.task_id; (async () => { setTaskLoading(true); try { const res = await fetch(`/api/v1/events/${slug}/tasks`); if (res.ok) { const tasks = await res.json(); const foundTask = tasks.find((t: any) => t.id === taskId); if (foundTask) { setTask({ id: foundTask.id, title: foundTask.title || `Aufgabe ${taskId}` }); } else { setTask({ id: taskId, title: `Unbekannte Aufgabe ${taskId}` }); } } else { setTask({ id: taskId, title: `Unbekannte Aufgabe ${taskId}` }); } } catch (error) { console.error('Failed to load task:', error); setTask({ id: taskId, title: `Unbekannte Aufgabe ${taskId}` }); } finally { setTaskLoading(false); } })(); }, [photo?.task_id, slug]); async function onLike() { if (liked || !photo) return; setLiked(true); try { const count = await likePhoto(photo.id); setLikes(count); // Update localStorage try { const raw = localStorage.getItem('liked-photo-ids'); const arr: number[] = raw ? JSON.parse(raw) : []; if (!arr.includes(photo.id)) { localStorage.setItem('liked-photo-ids', JSON.stringify([...arr, photo.id])); } } catch {} } catch (error) { console.error('Like failed:', error); setLiked(false); } } function onOpenChange(open: boolean) { if (!open) handleClose(); } return ( {/* Header with controls */}
{currentIndexVal > 0 && ( )} {currentIndexVal < currentPhotos.length - 1 && ( )}
{/* Task Info Overlay */} {task && (
Task: {task.title}
{taskLoading && (
Lade Aufgabe...
)}
)} {/* Photo Display */}
{`Foto { console.error('Image load error:', e); (e.target as HTMLImageElement).style.display = 'none'; }} />
{/* Loading state for task */} {taskLoading && !task && (
Lade Aufgabe...
)}
); }