feat: refine session workflow with global state, quick tasting, and statistics
This commit is contained in:
@@ -43,6 +43,10 @@ export default async function BottlePage({
|
||||
.from('tastings')
|
||||
.select(`
|
||||
*,
|
||||
tasting_sessions (
|
||||
id,
|
||||
name
|
||||
),
|
||||
tasting_tags (
|
||||
buddies (
|
||||
id,
|
||||
@@ -65,17 +69,6 @@ export default async function BottlePage({
|
||||
Zurück zur Sammlung
|
||||
</Link>
|
||||
|
||||
{sessionId && (
|
||||
<div className="bg-amber-600/10 border border-amber-600/20 p-4 rounded-2xl flex items-center gap-4 text-amber-700 dark:text-amber-400 animate-in slide-in-from-top-4 duration-500">
|
||||
<div className="bg-amber-600 text-white p-2 rounded-xl">
|
||||
<PlusCircle size={20} />
|
||||
</div>
|
||||
<div>
|
||||
<p className="font-bold">Session Aktiv!</p>
|
||||
<p className="text-sm opacity-80">Jede Tasting Note, die du jetzt speicherst, wird automatisch deiner Session zugeordnet. 🥃</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Hero Section */}
|
||||
<section className="grid grid-cols-1 md:grid-cols-2 gap-8 items-start">
|
||||
@@ -165,7 +158,7 @@ export default async function BottlePage({
|
||||
{/* Form */}
|
||||
<div className="lg:col-span-1 border border-zinc-200 dark:border-zinc-800 rounded-3xl p-6 bg-white dark:bg-zinc-900/50 md:sticky md:top-24">
|
||||
<h3 className="text-lg font-bold mb-6 flex items-center gap-2 text-amber-600">
|
||||
<Droplets size={20} /> {sessionId ? 'Session-Notiz' : 'Neu Verkosten'}
|
||||
<Droplets size={20} /> Dram bewerten
|
||||
</h3>
|
||||
<TastingNoteForm bottleId={bottle.id} sessionId={sessionId} />
|
||||
</div>
|
||||
|
||||
@@ -5,6 +5,9 @@ import PWARegistration from "@/components/PWARegistration";
|
||||
import OfflineIndicator from "@/components/OfflineIndicator";
|
||||
import UploadQueue from "@/components/UploadQueue";
|
||||
import { I18nProvider } from "@/i18n/I18nContext";
|
||||
import { SessionProvider } from "@/context/SessionContext";
|
||||
import ActiveSessionBanner from "@/components/ActiveSessionBanner";
|
||||
import MainContentWrapper from "@/components/MainContentWrapper";
|
||||
|
||||
const inter = Inter({ subsets: ["latin"] });
|
||||
|
||||
@@ -42,10 +45,15 @@ export default function RootLayout({
|
||||
<html lang="de">
|
||||
<body className={inter.className}>
|
||||
<I18nProvider>
|
||||
<PWARegistration />
|
||||
<OfflineIndicator />
|
||||
<UploadQueue />
|
||||
{children}
|
||||
<SessionProvider>
|
||||
<ActiveSessionBanner />
|
||||
<MainContentWrapper>
|
||||
<PWARegistration />
|
||||
<OfflineIndicator />
|
||||
<UploadQueue />
|
||||
{children}
|
||||
</MainContentWrapper>
|
||||
</SessionProvider>
|
||||
</I18nProvider>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -2,9 +2,11 @@
|
||||
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { createClientComponentClient } from '@supabase/auth-helpers-nextjs';
|
||||
import { ChevronLeft, Users, Calendar, GlassWater, Plus, Trash2, Loader2, Sparkles, ChevronRight } from 'lucide-react';
|
||||
import { ChevronLeft, Users, Calendar, GlassWater, Plus, Trash2, Loader2, Sparkles, ChevronRight, Play } from 'lucide-react';
|
||||
import Link from 'next/link';
|
||||
import { useSession } from '@/context/SessionContext';
|
||||
import { useParams, useRouter } from 'next/navigation';
|
||||
import { useI18n } from '@/i18n/I18nContext';
|
||||
|
||||
interface Buddy {
|
||||
id: string;
|
||||
@@ -35,6 +37,7 @@ interface SessionTasting {
|
||||
}
|
||||
|
||||
export default function SessionDetailPage() {
|
||||
const { t } = useI18n();
|
||||
const { id } = useParams();
|
||||
const router = useRouter();
|
||||
const supabase = createClientComponentClient();
|
||||
@@ -43,6 +46,7 @@ export default function SessionDetailPage() {
|
||||
const [tastings, setTastings] = useState<SessionTasting[]>([]);
|
||||
const [allBuddies, setAllBuddies] = useState<Buddy[]>([]);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const { activeSession, setActiveSession } = useSession();
|
||||
const [isAddingParticipant, setIsAddingParticipant] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -175,8 +179,31 @@ export default function SessionDetailPage() {
|
||||
<Calendar size={16} className="text-zinc-400" />
|
||||
{new Date(session.scheduled_at).toLocaleDateString('de-DE')}
|
||||
</span>
|
||||
{tastings.length > 0 && (
|
||||
<span className="flex items-center gap-1.5 transition-all animate-in fade-in slide-in-from-left-2">
|
||||
<GlassWater size={16} className="text-zinc-400" />
|
||||
{tastings.length} {tastings.length === 1 ? 'Whisky' : 'Whiskys'}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-2">
|
||||
{activeSession?.id !== session.id ? (
|
||||
<button
|
||||
onClick={() => setActiveSession({ id: session.id, name: session.name })}
|
||||
className="px-6 py-3 bg-amber-600 hover:bg-amber-700 text-white rounded-2xl text-sm font-black uppercase tracking-widest flex items-center gap-2 transition-all shadow-xl shadow-amber-600/20"
|
||||
>
|
||||
<Play size={18} fill="currentColor" />
|
||||
Session Starten
|
||||
</button>
|
||||
) : (
|
||||
<div className="px-6 py-3 bg-zinc-900 dark:bg-zinc-100 text-white dark:text-zinc-900 rounded-2xl text-sm font-black uppercase tracking-widest flex items-center gap-2 border border-zinc-200 dark:border-zinc-800">
|
||||
<Sparkles size={18} className="text-amber-500" />
|
||||
Aktiv
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
@@ -275,8 +302,8 @@ export default function SessionDetailPage() {
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div >
|
||||
</main >
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user