feat: Add Flight Recorder, Timeline, ABV Curve and Offline Bottle Caching with Draft Notes support

This commit is contained in:
2025-12-19 20:45:20 +01:00
parent 24e243fff8
commit e8c3032954
13 changed files with 864 additions and 337 deletions

View File

@@ -9,6 +9,8 @@ import { deleteSession } from '@/services/delete-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';
interface Buddy {
id: string;
@@ -31,12 +33,20 @@ interface Session {
interface SessionTasting {
id: string;
rating: number;
tasted_at: string;
bottles: {
id: string;
name: string;
distillery: string;
image_url?: string | null;
abv: number;
category?: string;
};
tasting_tags: {
tags: {
name: string;
};
}[];
}
export default function SessionDetailPage() {
@@ -84,15 +94,17 @@ export default function SessionDetailPage() {
// Fetch Tastings in this session
const { data: tastingData } = await supabase
.from('tastings')
.select('id, rating, bottles(id, name, distillery, image_url)')
.select(`
id,
rating,
tasted_at,
bottles(id, name, distillery, image_url, abv, category),
tasting_tags(tags(name))
`)
.eq('session_id', id)
.order('created_at', { ascending: false });
.order('tasted_at', { ascending: true });
setTastings((tastingData as any)?.map((t: any) => ({
id: t.id,
rating: t.rating,
bottles: t.bottles
})) || []);
setTastings((tastingData as any) || []);
// Fetch all buddies for the picker
const { data: buddies } = await supabase
@@ -323,6 +335,17 @@ export default function SessionDetailPage() {
</select>
</div>
</div>
{/* ABV Curve */}
{tastings.length > 0 && (
<SessionABVCurve
tastings={tastings.map(t => ({
id: t.id,
abv: t.bottles.abv || 40,
tasted_at: t.tasted_at
}))}
/>
)}
</aside>
{/* Main Content: Bottle List */}
@@ -342,33 +365,18 @@ export default function SessionDetailPage() {
</Link>
</div>
{tastings.length === 0 ? (
<div className="text-center py-12 text-zinc-500 italic text-sm">
Noch keine Flaschen in dieser Session verkostet. 🥃
</div>
) : (
<div className="space-y-4">
{tastings.map((t) => (
<div key={t.id} className="flex items-center justify-between p-4 bg-zinc-50 dark:bg-zinc-800/50 rounded-2xl border border-zinc-100 dark:border-zinc-800">
<div>
<div className="text-[9px] font-black text-amber-600 uppercase tracking-widest">{t.bottles.distillery}</div>
<div className="font-bold text-zinc-800 dark:text-zinc-100">{t.bottles.name}</div>
</div>
<div className="flex items-center gap-4">
<div className="bg-amber-100 dark:bg-amber-900/30 text-amber-700 px-3 py-1 rounded-xl text-xs font-black">
{t.rating}/100
</div>
<Link
href={`/bottles/${t.bottles.id}`}
className="p-2 text-zinc-300 hover:text-amber-600 transition-colors"
>
<ChevronRight size={20} />
</Link>
</div>
</div>
))}
</div>
)}
<SessionTimeline
tastings={tastings.map(t => ({
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
/>
</div>
</section>
</div>