diff --git a/src/app/bottles/[id]/page.tsx b/src/app/bottles/[id]/page.tsx index 19956cd..c61b98a 100644 --- a/src/app/bottles/[id]/page.tsx +++ b/src/app/bottles/[id]/page.tsx @@ -8,6 +8,7 @@ import TastingNoteForm from '@/components/TastingNoteForm'; import StatusSwitcher from '@/components/StatusSwitcher'; import TastingList from '@/components/TastingList'; import DeleteBottleButton from '@/components/DeleteBottleButton'; +import EditBottleForm from '@/components/EditBottleForm'; import { validateSession } from '@/services/validate-session'; export default async function BottlePage({ @@ -143,6 +144,7 @@ export default async function BottlePage({
+
diff --git a/src/components/BottleGrid.tsx b/src/components/BottleGrid.tsx index d801693..d12f576 100644 --- a/src/components/BottleGrid.tsx +++ b/src/components/BottleGrid.tsx @@ -15,6 +15,7 @@ interface Bottle { abv: number; age: number; image_url: string; + purchase_price?: number | null; status: 'sealed' | 'open' | 'sampled' | 'empty'; created_at: string; last_tasted?: string | null; diff --git a/src/components/EditBottleForm.tsx b/src/components/EditBottleForm.tsx new file mode 100644 index 0000000..3fe4b37 --- /dev/null +++ b/src/components/EditBottleForm.tsx @@ -0,0 +1,180 @@ +'use client'; + +import React, { useState } from 'react'; +import { Edit2, Save, X, Info, Tag, FlaskConical, CircleDollarSign } from 'lucide-react'; +import { updateBottle } from '@/services/update-bottle'; + +interface EditBottleFormProps { + bottle: { + id: string; + name: string; + distillery: string; + category: string; + abv: number; + age: number; + whiskybase_id: string | null; + purchase_price?: number | null; + }; + onComplete?: () => void; +} + +export default function EditBottleForm({ bottle, onComplete }: EditBottleFormProps) { + const [isEditing, setIsEditing] = useState(false); + const [isSaving, setIsSaving] = useState(false); + const [error, setError] = useState(null); + + const [formData, setFormData] = useState({ + name: bottle.name, + distillery: bottle.distillery || '', + category: bottle.category || '', + abv: bottle.abv || 0, + age: bottle.age || 0, + whiskybase_id: bottle.whiskybase_id || '', + purchase_price: bottle.purchase_price || '', + }); + + const handleSave = async () => { + setIsSaving(true); + setError(null); + + try { + const response = await updateBottle(bottle.id, { + ...formData, + abv: Number(formData.abv), + age: formData.age ? Number(formData.age) : undefined, + purchase_price: formData.purchase_price ? Number(formData.purchase_price) : undefined, + }); + + if (response.success) { + setIsEditing(false); + if (onComplete) onComplete(); + } else { + setError(response.error || 'Fehler beim Speichern'); + } + } catch (err) { + setError('Etwas ist schiefgelaufen.'); + } finally { + setIsSaving(false); + } + }; + + if (!isEditing) { + return ( +
+ + {bottle.purchase_price && ( +
+ + Kaufpreis: {parseFloat(bottle.purchase_price.toString()).toLocaleString('de-DE', { style: 'currency', currency: 'EUR' })} +
+ )} +
+ ); + } + + return ( +
+
+

+ Details korrigieren +

+ +
+ +
+
+ + setFormData({ ...formData, name: e.target.value })} + className="w-full px-4 py-2 bg-zinc-50 dark:bg-zinc-800 border border-zinc-200 dark:border-zinc-700 rounded-xl outline-none focus:ring-2 focus:ring-amber-500" + /> +
+
+ + setFormData({ ...formData, distillery: e.target.value })} + className="w-full px-4 py-2 bg-zinc-50 dark:bg-zinc-800 border border-zinc-200 dark:border-zinc-700 rounded-xl outline-none focus:ring-2 focus:ring-amber-500" + /> +
+
+ + setFormData({ ...formData, category: e.target.value })} + className="w-full px-4 py-2 bg-zinc-50 dark:bg-zinc-800 border border-zinc-200 dark:border-zinc-700 rounded-xl outline-none focus:ring-2 focus:ring-amber-500" + /> +
+
+
+ + setFormData({ ...formData, abv: parseFloat(e.target.value) })} + className="w-full px-4 py-2 bg-zinc-50 dark:bg-zinc-800 border border-zinc-200 dark:border-zinc-700 rounded-xl outline-none focus:ring-2 focus:ring-amber-500" + /> +
+
+ + setFormData({ ...formData, age: parseInt(e.target.value) })} + className="w-full px-4 py-2 bg-zinc-50 dark:bg-zinc-800 border border-zinc-200 dark:border-zinc-700 rounded-xl outline-none focus:ring-2 focus:ring-amber-500" + /> +
+
+
+ + setFormData({ ...formData, whiskybase_id: e.target.value })} + className="w-full px-4 py-2 bg-zinc-50 dark:bg-zinc-800 border border-zinc-200 dark:border-zinc-700 rounded-xl outline-none focus:ring-2 focus:ring-amber-500" + /> +
+
+ + setFormData({ ...formData, purchase_price: e.target.value })} + className="w-full px-4 py-2 bg-amber-50 dark:bg-amber-900/10 border border-amber-200 dark:border-amber-900/30 rounded-xl outline-none focus:ring-2 focus:ring-amber-500 font-bold text-amber-700 dark:text-amber-400" + /> +
+
+ + {error &&

{error}

} + + +
+ ); +} diff --git a/src/services/update-bottle.ts b/src/services/update-bottle.ts new file mode 100644 index 0000000..25c520c --- /dev/null +++ b/src/services/update-bottle.ts @@ -0,0 +1,50 @@ +'use server'; + +import { createServerActionClient } from '@supabase/auth-helpers-nextjs'; +import { cookies } from 'next/headers'; +import { revalidatePath } from 'next/cache'; + +export async function updateBottle(bottleId: string, data: { + name?: string; + distillery?: string; + category?: string; + abv?: number; + age?: number; + whiskybase_id?: string; + purchase_price?: number; +}) { + const supabase = createServerActionClient({ cookies }); + + try { + const { data: { session } } = await supabase.auth.getSession(); + if (!session) throw new Error('Nicht autorisiert'); + + const { error } = await supabase + .from('bottles') + .update({ + name: data.name, + distillery: data.distillery, + category: data.category, + abv: data.abv, + age: data.age, + whiskybase_id: data.whiskybase_id, + purchase_price: data.purchase_price, + updated_at: new Date().toISOString(), + }) + .eq('id', bottleId) + .eq('user_id', session.user.id); + + if (error) throw error; + + revalidatePath(`/bottles/${bottleId}`); + revalidatePath('/'); + + return { success: true }; + } catch (error) { + console.error('Update Bottle Error:', error); + return { + success: false, + error: error instanceof Error ? error.message : 'Fehler beim Aktualisieren der Flaschendaten', + }; + } +} diff --git a/supa_schema.sql b/supa_schema.sql index 74c50e9..a08773b 100644 --- a/supa_schema.sql +++ b/supa_schema.sql @@ -40,6 +40,7 @@ CREATE TABLE IF NOT EXISTS bottles ( status TEXT DEFAULT 'sealed' CHECK (status IN ('sealed', 'open', 'sampled', 'empty')), whiskybase_id TEXT, image_url TEXT, + purchase_price DECIMAL(10, 2), is_whisky BOOLEAN DEFAULT true, confidence INTEGER DEFAULT 100, created_at TIMESTAMP WITH TIME ZONE DEFAULT timezone('Europe/Berlin'::text, now()),