'use client'; import React, { useState, useEffect } from 'react'; import { createClient } from '@/lib/supabase/client'; import { ChevronLeft, Users, Calendar, GlassWater, Plus, Trash2, Loader2, Sparkles, ChevronRight, Play, Square, Zap } from 'lucide-react'; import Link from 'next/link'; import AvatarStack from '@/components/AvatarStack'; import { deleteSession } from '@/services/delete-session'; import { closeSession } from '@/services/close-session'; import { useSession } from '@/context/SessionContext'; import { useParams, useRouter } from 'next/navigation'; import { useI18n } from '@/i18n/I18nContext'; import SessionTimeline from '@/components/SessionTimeline'; import SessionABVCurve from '@/components/SessionABVCurve'; import OfflineIndicator from '@/components/OfflineIndicator'; import BulkScanSheet from '@/components/BulkScanSheet'; import BottleSkeletonCard from '@/components/BottleSkeletonCard'; interface Buddy { id: string; name: string; } interface Participant { buddy_id: string; buddies: { name: string; }; } interface Session { id: string; name: string; scheduled_at: string; ended_at?: string; } interface SessionTasting { id: string; rating: number; tasted_at: string; bottles: { id: string; name: string; distillery: string; image_url?: string | null; abv: number; category?: string; processing_status?: string; }; tasting_tags: { tags: { name: string; }; }[]; } export default function SessionDetailPage() { const { t } = useI18n(); const { id } = useParams(); const router = useRouter(); const supabase = createClient(); const [session, setSession] = useState(null); const [participants, setParticipants] = useState([]); const [tastings, setTastings] = useState([]); const [allBuddies, setAllBuddies] = useState([]); const [isLoading, setIsLoading] = useState(true); const { activeSession, setActiveSession } = useSession(); const [isAddingParticipant, setIsAddingParticipant] = useState(false); const [isDeleting, setIsDeleting] = useState(false); const [isClosing, setIsClosing] = useState(false); const [isBulkScanOpen, setIsBulkScanOpen] = useState(false); useEffect(() => { fetchSessionData(); // Subscribe to bottle updates for realtime processing status const channel = supabase .channel('bottle-updates') .on( 'postgres_changes', { event: 'UPDATE', schema: 'public', table: 'bottles' }, (payload) => { // Refresh if a bottle's processing_status changed if (payload.new && payload.old) { if (payload.new.processing_status !== payload.old.processing_status) { fetchSessionData(); } } } ) .subscribe(); return () => { supabase.removeChannel(channel); }; }, [id]); const fetchSessionData = async () => { setIsLoading(true); // Fetch Session const { data: sessionData } = await supabase .from('tasting_sessions') .select('*') .eq('id', id) .single(); if (sessionData) { setSession(sessionData); // Fetch Participants const { data: partData } = await supabase .from('session_participants') .select('buddy_id, buddies(name)') .eq('session_id', id); setParticipants((partData as any)?.map((p: any) => ({ buddy_id: p.buddy_id, buddies: p.buddies })) || []); // Fetch Tastings in this session const { data: tastingData } = await supabase .from('tastings') .select(` id, rating, tasted_at, bottles(id, name, distillery, image_url, abv, category, processing_status), tasting_tags(tags(name)) `) .eq('session_id', id) .order('tasted_at', { ascending: true }); setTastings((tastingData as any) || []); // Fetch all buddies for the picker const { data: buddies } = await supabase .from('buddies') .select('id, name') .order('name'); setAllBuddies(buddies || []); } setIsLoading(false); }; const handleAddParticipant = async (buddyId: string) => { if (participants.some(p => p.buddy_id === buddyId)) return; const { data: { user } } = await supabase.auth.getUser(); if (!user) return; const { error } = await supabase .from('session_participants') .insert([{ session_id: id, buddy_id: buddyId, user_id: user.id }]); if (!error) { fetchSessionData(); } }; const handleRemoveParticipant = async (buddyId: string) => { const { error } = await supabase .from('session_participants') .delete() .eq('session_id', id) .eq('buddy_id', buddyId); if (!error) { fetchSessionData(); } }; const handleCloseSession = async () => { if (!confirm('Möchtest du diese Session wirklich abschließen?')) return; setIsClosing(true); const result = await closeSession(id as string); if (result.success) { if (activeSession?.id === id) { setActiveSession(null); } fetchSessionData(); } else { alert(result.error); } setIsClosing(false); }; const handleDeleteSession = async () => { if (!confirm('Möchtest du diese Session wirklich löschen? Alle Verknüpfungen gehen verloren.')) return; setIsDeleting(true); const result = await deleteSession(id as string); if (result.success) { if (activeSession?.id === id) { setActiveSession(null); } router.push('/'); } else { alert(result.error); setIsDeleting(false); } }; if (isLoading) { return (
); } if (!session) { return (

Session nicht gefunden

Zurück zum Start
); } return (
{/* Back Button */}
Alle Sessions
{/* Hero */}
{/* Visual Eyecatcher: Background Glow */} {tastings.length > 0 && tastings[0].bottles.image_url && (
)}
{/* Visual Eyecatcher: Bottle Preview */} {tastings.length > 0 && tastings[0].bottles.image_url && (
{tastings[0].bottles.name}
LATEST
)}
Tasting Session
{session.ended_at && ( Abgeschlossen )}

{session.name}

{new Date(session.scheduled_at).toLocaleDateString('de-DE')} {participants.length > 0 && (
p.buddies.name)} limit={5} />
)} {tastings.length > 0 && ( {tastings.length} {tastings.length === 1 ? 'Whisky' : 'Whiskys'} )}
{!session.ended_at && ( activeSession?.id !== session.id ? ( ) : ( ) )}
{/* Sidebar: Participants */} {/* Main Content: Bottle List */}

Verkostete Flaschen

{!session.ended_at && ( )} Flasche
({ id: t.id, bottle_id: t.bottles.id, bottle_name: t.bottles.name, tasted_at: t.tasted_at, rating: t.rating, tags: t.tasting_tags?.map((tg: any) => tg.tags.name) || [], category: t.bottles.category }))} sessionStart={session.scheduled_at} // Fallback to scheduled time if no started_at />
{/* Bulk Scan Sheet */} setIsBulkScanOpen(false)} sessionId={id as string} sessionName={session.name} onSuccess={(bottleIds) => { setIsBulkScanOpen(false); fetchSessionData(); }} />
); }