diff --git a/src/components/BottleGrid.tsx b/src/components/BottleGrid.tsx
index d12f576..0d1f35d 100644
--- a/src/components/BottleGrid.tsx
+++ b/src/components/BottleGrid.tsx
@@ -148,10 +148,19 @@ export default function BottleGrid({ bottles }: BottleGridProps) {
}, [bottles]);
const filteredBottles = useMemo(() => {
- let result = bottles.filter((bottle) => {
+ const result = bottles.filter((bottle) => {
+ const searchLower = searchQuery.toLowerCase();
+ const tastingNotesMatch = bottle.tastings?.some((t: any) =>
+ (t.nose_notes?.toLowerCase().includes(searchLower)) ||
+ (t.palate_notes?.toLowerCase().includes(searchLower)) ||
+ (t.finish_notes?.toLowerCase().includes(searchLower))
+ );
+
const matchesSearch =
- bottle.name?.toLowerCase().includes(searchQuery.toLowerCase()) ||
- bottle.distillery?.toLowerCase().includes(searchQuery.toLowerCase());
+ bottle.name.toLowerCase().includes(searchLower) ||
+ bottle.distillery?.toLowerCase().includes(searchLower) ||
+ bottle.category?.toLowerCase().includes(searchLower) ||
+ tastingNotesMatch;
const matchesCategory = !selectedCategory || bottle.category === selectedCategory;
const matchesDistillery = !selectedDistillery || bottle.distillery === selectedDistillery;
diff --git a/src/components/DramOfTheDay.tsx b/src/components/DramOfTheDay.tsx
new file mode 100644
index 0000000..0341b83
--- /dev/null
+++ b/src/components/DramOfTheDay.tsx
@@ -0,0 +1,101 @@
+'use client';
+
+import React, { useState } from 'react';
+import { Sparkles, GlassWater, Dices, X } from 'lucide-react';
+import Link from 'next/link';
+
+interface Bottle {
+ id: string;
+ name: string;
+ distillery?: string;
+ status: 'sealed' | 'open' | 'sampled' | 'empty';
+}
+
+interface DramOfTheDayProps {
+ bottles: Bottle[];
+}
+
+export default function DramOfTheDay({ bottles }: DramOfTheDayProps) {
+ const [suggestion, setSuggestion] = useState
(null);
+ const [isRolling, setIsRolling] = useState(false);
+
+ const suggestDram = () => {
+ setIsRolling(true);
+ const openBottles = bottles.filter(b => b.status === 'open' || b.status === 'sampled');
+
+ if (openBottles.length === 0) {
+ alert('Keine offenen Flaschen gefunden! Vielleicht Zeit für ein neues Tasting? 🥃');
+ setIsRolling(false);
+ return;
+ }
+
+ // Simulate a "roll" for 800ms
+ setTimeout(() => {
+ const randomBottle = openBottles[Math.floor(Math.random() * openBottles.length)];
+ setSuggestion(randomBottle);
+ setIsRolling(false);
+ }, 800);
+ };
+
+ return (
+
+
+
+ {suggestion && (
+
+
+
+
+
+
+
+
+
+
+
Dein heutiger Dram
+
+ {suggestion.name}
+
+ {suggestion.distillery && (
+
{suggestion.distillery}
+ )}
+
+
+
+ setSuggestion(null)}
+ className="block w-full py-4 bg-zinc-900 dark:bg-zinc-100 text-white dark:text-zinc-900 rounded-2xl font-black uppercase tracking-widest text-xs hover:bg-amber-600 dark:hover:bg-amber-600 hover:text-white transition-all shadow-xl"
+ >
+ Flasche anschauen
+
+
+
+
+
+
+ )}
+
+ );
+}
diff --git a/src/components/StatsDashboard.tsx b/src/components/StatsDashboard.tsx
new file mode 100644
index 0000000..29c4899
--- /dev/null
+++ b/src/components/StatsDashboard.tsx
@@ -0,0 +1,99 @@
+'use client';
+
+import React, { useMemo } from 'react';
+import { TrendingUp, CreditCard, Star, Home, BarChart3 } from 'lucide-react';
+
+interface Bottle {
+ id: string;
+ purchase_price?: number | null;
+ status: 'sealed' | 'open' | 'sampled' | 'empty';
+ distillery?: string;
+ tastings?: { rating: number }[];
+}
+
+interface StatsDashboardProps {
+ bottles: Bottle[];
+}
+
+export default function StatsDashboard({ bottles }: StatsDashboardProps) {
+ const stats = useMemo(() => {
+ const activeBottles = bottles.filter(b => b.status !== 'empty');
+ const totalValue = bottles.reduce((sum, b) => sum + (Number(b.purchase_price) || 0), 0);
+
+ const ratings = bottles.flatMap(b => b.tastings?.map(t => t.rating) || []);
+ const avgRating = ratings.length > 0
+ ? Math.round(ratings.reduce((sum, r) => sum + r, 0) / ratings.length)
+ : 0;
+
+ const distilleries = bottles
+ .filter(b => b.distillery)
+ .reduce((acc, b) => {
+ acc[b.distillery!] = (acc[b.distillery!] || 0) + 1;
+ return acc;
+ }, {} as Record);
+
+ const topDistillery = Object.entries(distilleries).sort((a, b) => b[1] - a[1])[0]?.[0] || 'N/A';
+
+ return {
+ totalValue,
+ activeCount: activeBottles.length,
+ avgRating,
+ topDistillery,
+ totalCount: bottles.length
+ };
+ }, [bottles]);
+
+ const statItems = [
+ {
+ label: 'Gesamtwert',
+ value: stats.totalValue.toLocaleString('de-DE', { style: 'currency', currency: 'EUR' }),
+ icon: CreditCard,
+ color: 'text-green-600',
+ bg: 'bg-green-50 dark:bg-green-900/20'
+ },
+ {
+ label: 'In der Bar',
+ value: stats.activeCount,
+ icon: Home,
+ color: 'text-blue-600',
+ bg: 'bg-blue-50 dark:bg-blue-900/20'
+ },
+ {
+ label: 'Ø Bewertung',
+ value: `${stats.avgRating}/100`,
+ icon: Star,
+ color: 'text-amber-600',
+ bg: 'bg-amber-50 dark:bg-amber-900/20'
+ },
+ {
+ label: 'Top Brennerei',
+ value: stats.topDistillery,
+ icon: BarChart3,
+ color: 'text-purple-600',
+ bg: 'bg-purple-50 dark:bg-purple-900/20'
+ },
+ ];
+
+ return (
+
+ {statItems.map((item, idx) => {
+ const Icon = item.icon;
+ return (
+
+
+
+
+
{item.label}
+
+ {item.value}
+
+
+
+ );
+ })}
+
+ );
+}
diff --git a/src/services/update-bottle-status.ts b/src/services/update-bottle-status.ts
index 8551bf2..6f0a3b9 100644
--- a/src/services/update-bottle-status.ts
+++ b/src/services/update-bottle-status.ts
@@ -13,7 +13,11 @@ export async function updateBottleStatus(bottleId: string, status: 'sealed' | 'o
const { error } = await supabase
.from('bottles')
- .update({ status, updated_at: new Date().toISOString() })
+ .update({
+ status,
+ updated_at: new Date().toISOString(),
+ finished_at: status === 'empty' ? new Date().toISOString() : null
+ })
.eq('id', bottleId)
.eq('user_id', session.user.id);
diff --git a/supa_schema.sql b/supa_schema.sql
index a08773b..30871b6 100644
--- a/supa_schema.sql
+++ b/supa_schema.sql
@@ -43,6 +43,7 @@ CREATE TABLE IF NOT EXISTS bottles (
purchase_price DECIMAL(10, 2),
is_whisky BOOLEAN DEFAULT true,
confidence INTEGER DEFAULT 100,
+ finished_at TIMESTAMP WITH TIME ZONE,
created_at TIMESTAMP WITH TIME ZONE DEFAULT timezone('Europe/Berlin'::text, now()),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT timezone('Europe/Berlin'::text, now())
);