feat: Add Flight Recorder, Timeline, ABV Curve and Offline Bottle Caching with Draft Notes support
This commit is contained in:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user