'use client'; import { useRef, useState } from 'react'; type ParsedItem = { rawName: string; quantity: number; unit: string; price?: number | null; matchedProductId?: number; matchedProductName?: string; }; type RowState = ParsedItem & { checked: boolean; editQty: string; editUnit: string }; const UNITS = ['st', 'kg', 'g', 'l', 'dl', 'cl', 'ml', 'förp', 'pak', 'burk', 'flaska']; export default function ReceiptImportClient() { const fileRef = useRef(null); const [preview, setPreview] = useState(null); const [parsing, setParsing] = useState(false); const [saving, setSaving] = useState(false); const [rows, setRows] = useState([]); const [error, setError] = useState(null); const [savedCount, setSavedCount] = useState(null); const [selectedFile, setSelectedFile] = useState(null); const handleFileChange = (e: React.ChangeEvent) => { const file = e.target.files?.[0]; if (!file) return; setSelectedFile(file); setPreview(URL.createObjectURL(file)); setRows([]); setError(null); setSavedCount(null); }; const handleParse = async () => { if (!selectedFile) return; setParsing(true); setError(null); try { const fd = new FormData(); fd.append('file', selectedFile); const res = await fetch('/api/receipt-import-proxy', { method: 'POST', body: fd }); if (!res.ok) { const e = await res.json().catch(() => ({ message: 'Okänt fel' })); throw new Error(e.message ?? 'Servern svarade med fel'); } const items: ParsedItem[] = await res.json(); setRows( items.map((item) => ({ ...item, checked: !!item.matchedProductId, editQty: String(item.quantity), editUnit: item.unit, })), ); } catch (err) { setError(err instanceof Error ? err.message : 'Kunde inte tolka kvittot'); } finally { setParsing(false); } }; const updateRow = (i: number, patch: Partial) => { setRows((prev) => prev.map((r, idx) => (idx === i ? { ...r, ...patch } : r))); }; const handleSave = async () => { const toSave = rows.filter((r) => r.checked && r.matchedProductId); if (toSave.length === 0) return; setSaving(true); setError(null); try { await Promise.all( toSave.map((r) => fetch('/api/inventory', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ productId: r.matchedProductId, quantity: parseFloat(r.editQty) || r.quantity, unit: r.editUnit, }), }), ), ); setSavedCount(toSave.length); setRows([]); setPreview(null); setSelectedFile(null); if (fileRef.current) fileRef.current.value = ''; } catch { setError('Något gick fel vid sparning. Försök igen.'); } finally { setSaving(false); } }; const checkedCount = rows.filter((r) => r.checked && r.matchedProductId).length; const unmatchedCount = rows.filter((r) => !r.matchedProductId).length; return (
{/* Fil-input */}
fileRef.current?.click()} > {preview ? ( // eslint-disable-next-line @next/next/no-img-element Kvittoförhandsgranskning ) : (
📷
Fotografera eller välj kvitto
Klicka för att välja bild (JPEG, PNG, WebP)
)}
{preview && rows.length === 0 && ( )} {error && (

{error}

)} {savedCount !== null && (

✓ {savedCount} {savedCount === 1 ? 'vara lades till' : 'varor lades till'} i inventariet.

)} {/* Parsade rader */} {rows.length > 0 && (

Identifierade varor ({rows.length})

{unmatchedCount > 0 && ( {unmatchedCount} {unmatchedCount === 1 ? 'vara' : 'varor'} saknas i produktdatabasen )}
{rows.map((row, i) => { const matched = !!row.matchedProductId; return (
updateRow(i, { checked: e.target.checked })} style={{ width: '18px', height: '18px', cursor: matched ? 'pointer' : 'not-allowed' }} title={!matched ? 'Produkten finns inte i databasen — lägg till den i admin först' : ''} />
{row.matchedProductName ?? row.rawName}
{row.matchedProductName && row.matchedProductName.toLowerCase() !== row.rawName.toLowerCase() && (
Kvitto: {row.rawName}
)} {!matched && (
Ingen matchning — {row.rawName}
)}
updateRow(i, { editQty: e.target.value })} style={{ padding: '0.3rem 0.5rem', border: '1px solid #ced4da', borderRadius: '4px', width: '100%', fontSize: '0.9rem' }} />
); })}
)}
); } function primaryBtn(disabled: boolean): React.CSSProperties { return { padding: '0.6rem 1.25rem', background: disabled ? '#aaa' : '#0070f3', color: '#fff', border: 'none', borderRadius: '6px', cursor: disabled ? 'not-allowed' : 'pointer', fontWeight: 600, fontSize: '0.95rem', }; }