feat: restore scan quality, implement standardized naming, and add cask_type integration

This commit is contained in:
2026-01-05 23:33:10 +01:00
parent 21ca704abc
commit 68ac857091
22 changed files with 576 additions and 1020 deletions

View File

@@ -173,6 +173,11 @@ export default function BottleDetails({ bottleId, sessionId, userId }: BottleDet
<FactCard label="ABV" value={bottle.abv ? `${bottle.abv}%` : '%'} icon={<Droplets size={14} />} highlight={!bottle.abv} />
<FactCard label="Age" value={bottle.age ? `${bottle.age}Y` : '-'} icon={<Award size={14} />} />
<FactCard label="Price" value={bottle.purchase_price ? `${bottle.purchase_price}` : '-'} icon={<CircleDollarSign size={14} />} />
{(bottle as any).cask_type && (
<div className="col-span-2">
<FactCard label="Cask" value={(bottle as any).cask_type} icon={<Package size={14} />} />
</div>
)}
</div>
{/* Status & Last Dram Row */}

View File

@@ -16,7 +16,7 @@ import Link from 'next/link';
import { useI18n } from '@/i18n/I18nContext';
import { useSession } from '@/context/SessionContext';
import { shortenCategory } from '@/lib/format';
import { scanLabel } from '@/app/actions/scan-label';
import { scanLabel } from '@/app/actions/scanner';
import { enrichData } from '@/app/actions/enrich-data';
import { processImageForAI } from '@/utils/image-processing';

View File

@@ -19,6 +19,7 @@ interface EditBottleFormProps {
distilled_at?: string | null;
bottled_at?: string | null;
batch_info?: string | null;
cask_type?: string | null;
};
onComplete?: () => void;
}
@@ -42,6 +43,7 @@ export default function EditBottleForm({ bottle, onComplete }: EditBottleFormPro
distilled_at: bottle.distilled_at || '',
bottled_at: bottle.bottled_at || '',
batch_info: bottle.batch_info || '',
cask_type: bottle.cask_type || '',
});
const handleDiscover = async () => {
@@ -87,6 +89,7 @@ export default function EditBottleForm({ bottle, onComplete }: EditBottleFormPro
distilled_at: formData.distilled_at || undefined,
bottled_at: formData.bottled_at || undefined,
batch_info: formData.batch_info || undefined,
cask_type: formData.cask_type || undefined,
});
if (response.success) {
@@ -251,16 +254,28 @@ export default function EditBottleForm({ bottle, onComplete }: EditBottleFormPro
</div>
</div>
{/* Batch Info */}
<div className="space-y-2 md:col-span-2">
<label className="text-[10px] font-black uppercase text-zinc-500 ml-1 tracking-widest">{t('bottle.batchLabel')}</label>
<input
type="text"
placeholder="e.g. Batch 12 or L-Code"
value={formData.batch_info}
onChange={(e) => setFormData({ ...formData, batch_info: e.target.value })}
className="w-full px-5 py-4 bg-black/40 border border-white/5 rounded-2xl outline-none focus:ring-2 focus:ring-orange-600/50 text-zinc-100 text-sm font-bold transition-all placeholder:text-zinc-700"
/>
{/* Batch and Cask */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 md:col-span-2">
<div className="space-y-2">
<label className="text-[10px] font-black uppercase text-zinc-500 ml-1 tracking-widest">{t('bottle.batchLabel')}</label>
<input
type="text"
placeholder="e.g. Batch 12 or L-Code"
value={formData.batch_info}
onChange={(e) => setFormData({ ...formData, batch_info: e.target.value })}
className="w-full px-5 py-4 bg-black/40 border border-white/5 rounded-2xl outline-none focus:ring-2 focus:ring-orange-600/50 text-zinc-100 text-sm font-bold transition-all placeholder:text-zinc-700"
/>
</div>
<div className="space-y-2">
<label className="text-[10px] font-black uppercase text-zinc-500 ml-1 tracking-widest">Fass-Typ (Cask)</label>
<input
type="text"
placeholder="e.g. Oloroso Sherry"
value={formData.cask_type}
onChange={(e) => setFormData({ ...formData, cask_type: e.target.value })}
className="w-full px-5 py-4 bg-black/40 border border-white/5 rounded-2xl outline-none focus:ring-2 focus:ring-orange-600/50 text-zinc-100 text-sm font-bold transition-all placeholder:text-zinc-700"
/>
</div>
</div>
</div>

View File

@@ -48,6 +48,7 @@ export default function TastingEditor({ bottleMetadata, image, onSave, onOpenSes
const [bottleCategory, setBottleCategory] = useState(bottleMetadata.category || 'Whisky');
const [bottleVintage, setBottleVintage] = useState(bottleMetadata.vintage || '');
const [bottleCaskType, setBottleCaskType] = useState(bottleMetadata.cask_type || '');
const [bottleBottler, setBottleBottler] = useState(bottleMetadata.bottler || '');
const [bottleBatchInfo, setBottleBatchInfo] = useState(bottleMetadata.batch_info || '');
const [bottleCode, setBottleCode] = useState(bottleMetadata.bottleCode || '');
@@ -106,6 +107,7 @@ export default function TastingEditor({ bottleMetadata, image, onSave, onOpenSes
if (bottleMetadata.age) setBottleAge(bottleMetadata.age.toString());
if (bottleMetadata.category) setBottleCategory(bottleMetadata.category);
if (bottleMetadata.vintage) setBottleVintage(bottleMetadata.vintage);
if (bottleMetadata.cask_type) setBottleCaskType(bottleMetadata.cask_type);
if (bottleMetadata.bottler) setBottleBottler(bottleMetadata.bottler);
if (bottleMetadata.batch_info) setBottleBatchInfo(bottleMetadata.batch_info);
if (bottleMetadata.distilled_at) setBottleDistilledAt(bottleMetadata.distilled_at);
@@ -118,6 +120,7 @@ export default function TastingEditor({ bottleMetadata, image, onSave, onOpenSes
if (!bottleAge && bottleMetadata.age) setBottleAge(bottleMetadata.age.toString());
if ((!bottleCategory || bottleCategory === 'Whisky') && bottleMetadata.category) setBottleCategory(bottleMetadata.category);
if (!bottleVintage && bottleMetadata.vintage) setBottleVintage(bottleMetadata.vintage);
if (!bottleCaskType && bottleMetadata.cask_type) setBottleCaskType(bottleMetadata.cask_type);
if (!bottleBottler && bottleMetadata.bottler) setBottleBottler(bottleMetadata.bottler);
if (!bottleBatchInfo && bottleMetadata.batch_info) setBottleBatchInfo(bottleMetadata.batch_info);
if (!bottleDistilledAt && bottleMetadata.distilled_at) setBottleDistilledAt(bottleMetadata.distilled_at);
@@ -182,17 +185,23 @@ export default function TastingEditor({ bottleMetadata, image, onSave, onOpenSes
// Automatic Whiskybase discovery when details are expanded
useEffect(() => {
const searchWhiskybase = async () => {
if (showBottleDetails && !whiskybaseId && !whiskybaseDiscovery && !isDiscoveringWb) {
if (showBottleDetails && (bottleName || bottleMetadata.name) && !whiskybaseId && !whiskybaseDiscovery && !isDiscoveringWb) {
setIsDiscoveringWb(true);
try {
const searchName = bottleName || bottleMetadata.name;
if (!searchName) {
setIsDiscoveringWb(false);
return;
}
const result = await discoverWhiskybaseId({
name: bottleMetadata.name || '',
distillery: bottleMetadata.distillery ?? undefined,
abv: bottleMetadata.abv ?? undefined,
age: bottleMetadata.age ?? undefined,
batch_info: bottleMetadata.batch_info ?? undefined,
distilled_at: bottleMetadata.distilled_at ?? undefined,
bottled_at: bottleMetadata.bottled_at ?? undefined,
name: searchName,
distillery: bottleDistillery || undefined,
abv: bottleAbv ? parseFloat(bottleAbv) : undefined,
age: bottleAge ? parseInt(bottleAge) : undefined,
batch_info: bottleBatchInfo || undefined,
distilled_at: bottleDistilledAt || undefined,
bottled_at: bottleBottledAt || undefined,
});
if (result.success && result.id) {
@@ -238,6 +247,7 @@ export default function TastingEditor({ bottleMetadata, image, onSave, onOpenSes
age: bottleAge ? parseInt(bottleAge) : bottleMetadata.age,
category: bottleCategory || bottleMetadata.category,
vintage: bottleVintage || null,
cask_type: bottleCaskType || null,
bottler: bottleBottler || null,
batch_info: bottleBatchInfo || null,
bottleCode: bottleCode || null,
@@ -388,6 +398,19 @@ export default function TastingEditor({ bottleMetadata, image, onSave, onOpenSes
className="w-full bg-zinc-950 border border-zinc-800 rounded-lg px-3 py-2 text-sm text-zinc-100 placeholder-zinc-700 focus:outline-none focus:border-orange-600 transition-colors"
/>
</div>
{/* Cask Type */}
<div>
<label className="text-[9px] font-bold uppercase tracking-wider text-zinc-600 block mb-1.5">
Fass-Typ (Cask)
</label>
<input
type="text"
value={bottleCaskType}
onChange={(e) => setBottleCaskType(e.target.value)}
placeholder="e.g. Oloroso Sherry Cask"
className="w-full bg-zinc-950 border border-zinc-800 rounded-lg px-3 py-2 text-sm text-zinc-100 placeholder-zinc-700 focus:outline-none focus:border-orange-600 transition-colors"
/>
</div>
{/* Vintage */}
<div>
<label className="text-[9px] font-bold uppercase tracking-wider text-zinc-600 block mb-1.5">

View File

@@ -3,7 +3,7 @@
import React, { useEffect, useState, useCallback } from 'react';
import { useLiveQuery } from 'dexie-react-hooks';
import { db, PendingScan, PendingTasting } from '@/lib/db';
import { scanLabel } from '@/app/actions/scan-label';
import { scanLabel } from '@/app/actions/scanner';
import { enrichData } from '@/app/actions/enrich-data';
import { saveBottle } from '@/services/save-bottle';
import { saveTasting } from '@/services/save-tasting';