- {queue.slice(0, 3).map((item) => (
-
+ {/* Scans */}
+ {pendingScans.map((item) => (
+
-
+
-
- {currentProgress?.id === item.id ? currentProgress.status : 'Wartet auf Netz...'}
+
+ {currentProgress?.id === `scan-${item.id}` ? currentProgress.status : 'Scan wartet...'}
- {currentProgress?.id === item.id ? (
+ {currentProgress?.id === `scan-${item.id}` ? (
- ) : (
-
- )}
+ ) :
}
))}
- {queue.length > 3 && (
-
- + {queue.length - 3} weitere Flaschen
+
+ {/* Tastings */}
+ {pendingTastings.map((item) => (
+
+
+
+ {item.data.rating}
+
+
+ {currentProgress?.id === `tasting-${item.id}` ? currentProgress.status : 'Tasting wartet...'}
+
+
+ {currentProgress?.id === `tasting-${item.id}` ? (
+
+ ) :
}
- )}
+ ))}
{navigator.onLine && !isSyncing && (
)}
diff --git a/src/hooks/useCacheSync.ts b/src/hooks/useCacheSync.ts
new file mode 100644
index 0000000..d17277e
--- /dev/null
+++ b/src/hooks/useCacheSync.ts
@@ -0,0 +1,44 @@
+import { useEffect } from 'react';
+import { createClientComponentClient } from '@supabase/auth-helpers-nextjs';
+import { db } from '@/lib/db';
+import { getAllSystemTags } from '@/services/tags';
+
+export function useCacheSync() {
+ const supabase = createClientComponentClient();
+
+ useEffect(() => {
+ const syncCache = async () => {
+ if (!navigator.onLine) return;
+
+ try {
+ // 1. Sync Tags
+ const tags = await getAllSystemTags();
+ if (tags.length > 0) {
+ await db.cache_tags.bulkPut(tags.map(t => ({
+ id: t.id,
+ name: t.name,
+ category: t.category,
+ is_system_default: t.is_system_default,
+ popularity_score: t.popularity_score || 0
+ })));
+ }
+
+ // 2. Sync Buddies
+ const { data: buddies } = await supabase
+ .from('buddies')
+ .select('id, name')
+ .order('name');
+
+ if (buddies && buddies.length > 0) {
+ await db.cache_buddies.bulkPut(buddies);
+ }
+ } catch (err) {
+ console.error('Failed to sync offline cache:', err);
+ }
+ };
+
+ syncCache();
+ window.addEventListener('online', syncCache);
+ return () => window.removeEventListener('online', syncCache);
+ }, [supabase]);
+}
diff --git a/src/lib/db.ts b/src/lib/db.ts
new file mode 100644
index 0000000..90c8de6
--- /dev/null
+++ b/src/lib/db.ts
@@ -0,0 +1,57 @@
+import Dexie, { type Table } from 'dexie';
+
+export interface PendingScan {
+ id?: number;
+ imageBase64: string;
+ timestamp: number;
+ provider?: 'gemini' | 'nebius';
+}
+
+export interface PendingTasting {
+ id?: number;
+ bottle_id: string;
+ data: {
+ session_id?: string;
+ rating: number;
+ nose_notes?: string;
+ palate_notes?: string;
+ finish_notes?: string;
+ is_sample?: boolean;
+ buddy_ids?: string[];
+ tag_ids?: string[];
+ };
+ photo?: string; // Optional photo if taken during tasting
+ tasted_at: string;
+}
+
+export interface CachedTag {
+ id: string;
+ name: string;
+ category: string;
+ is_system_default: boolean;
+ popularity_score: number;
+}
+
+export interface CachedBuddy {
+ id: string;
+ name: string;
+}
+
+export class WhiskyDexie extends Dexie {
+ pending_scans!: Table
;
+ pending_tastings!: Table;
+ cache_tags!: Table;
+ cache_buddies!: Table;
+
+ constructor() {
+ super('WhiskyVault');
+ this.version(1).stores({
+ pending_scans: '++id, timestamp',
+ pending_tastings: '++id, bottle_id, tasted_at',
+ cache_tags: 'id, category, name',
+ cache_buddies: 'id, name'
+ });
+ }
+}
+
+export const db = new WhiskyDexie();
diff --git a/src/lib/offline-db.ts b/src/lib/offline-db.ts
deleted file mode 100644
index f80516c..0000000
--- a/src/lib/offline-db.ts
+++ /dev/null
@@ -1,66 +0,0 @@
-export interface PendingBottle {
- id: string;
- imageBase64: string;
- timestamp: number;
-}
-
-const DB_NAME = 'WhiskyVaultOffline';
-const STORE_NAME = 'pendingCaptures';
-const DB_VERSION = 1;
-
-export const openDB = (): Promise => {
- return new Promise((resolve, reject) => {
- const request = indexedDB.open(DB_NAME, DB_VERSION);
-
- request.onupgradeneeded = (event) => {
- const db = (event.target as IDBOpenDBRequest).result;
- if (!db.objectStoreNames.contains(STORE_NAME)) {
- db.createObjectStore(STORE_NAME, { keyPath: 'id' });
- }
- };
-
- request.onsuccess = (event) => {
- resolve((event.target as IDBOpenDBRequest).result);
- };
-
- request.onerror = (event) => {
- reject((event.target as IDBOpenDBRequest).error);
- };
- });
-};
-
-export const savePendingBottle = async (bottle: PendingBottle): Promise => {
- const db = await openDB();
- return new Promise((resolve, reject) => {
- const transaction = db.transaction(STORE_NAME, 'readwrite');
- const store = transaction.objectStore(STORE_NAME);
- const request = store.put(bottle);
-
- request.onsuccess = () => resolve();
- request.onerror = () => reject(request.error);
- });
-};
-
-export const getAllPendingBottles = async (): Promise => {
- const db = await openDB();
- return new Promise((resolve, reject) => {
- const transaction = db.transaction(STORE_NAME, 'readonly');
- const store = transaction.objectStore(STORE_NAME);
- const request = store.getAll();
-
- request.onsuccess = () => resolve(request.result);
- request.onerror = () => reject(request.error);
- });
-};
-
-export const deletePendingBottle = async (id: string): Promise => {
- const db = await openDB();
- return new Promise((resolve, reject) => {
- const transaction = db.transaction(STORE_NAME, 'readwrite');
- const store = transaction.objectStore(STORE_NAME);
- const request = store.delete(id);
-
- request.onsuccess = () => resolve();
- request.onerror = () => reject(request.error);
- });
-};
diff --git a/src/services/save-tasting.ts b/src/services/save-tasting.ts
index e7fe53d..b18cd64 100644
--- a/src/services/save-tasting.ts
+++ b/src/services/save-tasting.ts
@@ -15,6 +15,7 @@ export async function saveTasting(data: {
is_sample?: boolean;
buddy_ids?: string[];
tag_ids?: string[];
+ tasted_at?: string;
}) {
const supabase = createServerActionClient({ cookies });
@@ -41,6 +42,7 @@ export async function saveTasting(data: {
palate_notes: data.palate_notes,
finish_notes: data.finish_notes,
is_sample: data.is_sample || false,
+ created_at: data.tasted_at || undefined,
})
.select()
.single();