feat: restore scan quality, implement standardized naming, and add cask_type integration
This commit is contained in:
@@ -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 */}
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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';
|
||||
|
||||
Reference in New Issue
Block a user