Files
fotospiel-app/resources/js/guest/components/GalleryPreview.tsx

139 lines
5.3 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import React from 'react';
import { Link } from 'react-router-dom';
import { Card, CardContent } from '@/components/ui/card';
import { getDeviceId } from '../lib/device';
import { usePollGalleryDelta } from '../polling/usePollGalleryDelta';
type Props = { token: string };
type PreviewFilter = 'latest' | 'popular' | 'mine' | 'photobooth';
export default function GalleryPreview({ token }: Props) {
const { photos, loading } = usePollGalleryDelta(token);
const [mode, setMode] = React.useState<PreviewFilter>('latest');
const items = React.useMemo(() => {
let arr = photos.slice();
// MyPhotos filter (requires session_id matching)
if (mode === 'mine') {
const deviceId = getDeviceId();
arr = arr.filter((photo: any) => photo.session_id === deviceId);
} else if (mode === 'photobooth') {
arr = arr.filter((photo: any) => photo.ingest_source === 'photobooth');
}
// Sorting
if (mode === 'popular') {
arr.sort((a: any, b: any) => (b.likes_count ?? 0) - (a.likes_count ?? 0));
} else {
arr.sort((a: any, b: any) => new Date(b.created_at ?? 0).getTime() - new Date(a.created_at ?? 0).getTime());
}
return arr.slice(0, 4); // 2x2 = 4 items
}, [photos, mode]);
// Helper function to generate photo title (must be before return)
function getPhotoTitle(photo: any): string {
if (photo.task_id) {
return `Task: ${photo.task_title || 'Unbekannte Aufgabe'}`;
}
if (photo.emotion_id) {
return `Emotion: ${photo.emotion_name || 'Gefühl'}`;
}
// Fallback based on creation time or placeholder
const now = new Date();
const created = new Date(photo.created_at || now);
const hours = created.getHours();
if (hours < 12) return 'Morgenmoment';
if (hours < 18) return 'Nachmittagslicht';
return 'Abendstimmung';
}
return (
<div>
<div className="mb-4 flex items-center justify-between">
<div className="inline-flex rounded-full bg-white/80 backdrop-blur-sm border border-pink-200 p-1 shadow-sm">
<button
onClick={() => setMode('latest')}
className={`px-4 py-2 text-sm font-medium rounded-full transition-all duration-200 ${
mode === 'latest'
? 'bg-gradient-to-r from-pink-500 to-pink-600 text-white shadow-lg scale-105'
: 'text-gray-600 hover:bg-pink-50 hover:text-pink-700'
}`}
>
Newest
</button>
<button
onClick={() => setMode('popular')}
className={`px-4 py-2 text-sm font-medium rounded-full transition-all duration-200 ${
mode === 'popular'
? 'bg-gradient-to-r from-pink-500 to-pink-600 text-white shadow-lg scale-105'
: 'text-gray-600 hover:bg-pink-50 hover:text-pink-700'
}`}
>
Popular
</button>
<button
onClick={() => setMode('mine')}
className={`px-4 py-2 text-sm font-medium rounded-full transition-all duration-200 ${
mode === 'mine'
? 'bg-gradient-to-r from-pink-500 to-pink-600 text-white shadow-lg scale-105'
: 'text-gray-600 hover:bg-pink-50 hover:text-pink-700'
}`}
>
My Photos
</button>
<button
onClick={() => setMode('photobooth')}
className={`px-4 py-2 text-sm font-medium rounded-full transition-all duration-200 ${
mode === 'photobooth'
? 'bg-gradient-to-r from-pink-500 to-pink-600 text-white shadow-lg scale-105'
: 'text-gray-600 hover:bg-pink-50 hover:text-pink-700'
}`}
>
Fotobox
</button>
</div>
<Link to={`/e/${encodeURIComponent(token)}/gallery?mode=${mode}`} className="text-sm text-pink-600 hover:text-pink-700 font-medium">
Alle ansehen
</Link>
</div>
{loading && <p className="text-sm text-muted-foreground">Lädt</p>}
{!loading && items.length === 0 && (
<Card>
<CardContent className="p-3 text-sm text-muted-foreground">
Noch keine Fotos. Starte mit deinem ersten Upload!
</CardContent>
</Card>
)}
<div className="grid grid-cols-2 gap-3">
{items.map((p: any) => (
<Link key={p.id} to={`/e/${encodeURIComponent(token)}/gallery?photoId=${p.id}`} className="block">
<div className="relative">
<img
src={p.thumbnail_path || p.file_path}
alt={p.title || 'Foto'}
className="aspect-square w-full rounded-xl object-cover shadow-lg hover:shadow-xl transition-shadow duration-200"
loading="lazy"
/>
{/* Photo Title */}
<div className="mt-2">
<div className="text-xs font-medium text-gray-900 line-clamp-2 bg-white/80 px-2 py-1 rounded-md">
{p.title || getPhotoTitle(p)}
</div>
{p.likes_count > 0 && (
<div className="mt-1 flex items-center gap-1 text-xs text-pink-600">
{p.likes_count}
</div>
)}
</div>
</div>
</Link>
))}
</div>
</div>
);
}