diff --git a/frontend/app/admin/ai/AiAdminClient.tsx b/frontend/app/admin/ai/AiAdminClient.tsx index 6d00675c..d14ef86a 100644 --- a/frontend/app/admin/ai/AiAdminClient.tsx +++ b/frontend/app/admin/ai/AiAdminClient.tsx @@ -76,6 +76,11 @@ export default function AiAdminClient({ keyHint, hasKey, aiFunctions }: Props) {

🔑 Mistral API-nyckel

+ {!hasKey && ( +
+ ⚠️ MISTRAL_API_KEY är inte konfigurerad — alla AI-funktioner är inaktiva tills nyckeln sätts i miljövariablerna. +
+ )}
Status @@ -135,6 +140,7 @@ export default function AiAdminClient({ keyHint, hasKey, aiFunctions }: Props) { + @@ -144,8 +150,14 @@ export default function AiAdminClient({ keyHint, hasKey, aiFunctions }: Props) { {aiFunctions.map((fn, i) => ( - +
{fn.name}
{fn.description}
diff --git a/frontend/app/kvitto/ReceiptImportClient.tsx b/frontend/app/kvitto/ReceiptImportClient.tsx index d688df5f..512d48df 100644 --- a/frontend/app/kvitto/ReceiptImportClient.tsx +++ b/frontend/app/kvitto/ReceiptImportClient.tsx @@ -25,6 +25,7 @@ type ParsedItem = { type Product = { id: number; name: string; canonicalName: string | null }; type RowState = { + productSearch: string; rawName: string; quantity: number; unit: string; @@ -48,15 +49,27 @@ export default function ReceiptImportClient() { const [saving, setSaving] = useState(false); const [rows, setRows] = useState([]); const [allProducts, setAllProducts] = useState([]); + const [productsLoading, setProductsLoading] = useState(true); + const [productsError, setProductsError] = useState(null); const [error, setError] = useState(null); const [savedCount, setSavedCount] = useState(null); const [selectedFile, setSelectedFile] = useState(null); useEffect(() => { fetch('/api/products') - .then((r) => r.json()) - .then((data) => { if (Array.isArray(data)) setAllProducts(data); }) - .catch(() => {}); + .then(async (r) => { + if (!r.ok) throw new Error(`HTTP ${r.status}`); + return r.json(); + }) + .then((data) => { + if (Array.isArray(data)) { + setAllProducts(data); + } else { + setProductsError('Oväntat format från produktlistan'); + } + }) + .catch((e) => setProductsError(`Kunde inte ladda produktlistan: ${e.message}`)) + .finally(() => setProductsLoading(false)); }, []); const handleFileChange = (e: React.ChangeEvent) => { @@ -97,6 +110,7 @@ export default function ReceiptImportClient() { editQty: String(item.quantity), editUnit: item.unit, matchSource: 'alias', + productSearch: item.matchedProductName ?? '', }; } if (item.suggestedProductId) { @@ -112,6 +126,7 @@ export default function ReceiptImportClient() { editQty: String(item.quantity), editUnit: item.unit, matchSource: 'suggestion', + productSearch: item.suggestedProductName ?? '', }; } return { @@ -127,6 +142,7 @@ export default function ReceiptImportClient() { editUnit: item.unit, matchSource: 'none', categorySuggestion: item.categorySuggestion, + productSearch: '', }; }), ); @@ -141,23 +157,6 @@ export default function ReceiptImportClient() { setRows((prev) => prev.map((r, idx) => (idx === i ? { ...r, ...patch } : r))); }; - const handleProductSelect = (i: number, productId: string) => { - if (!productId) { - updateRow(i, { selectedProductId: '', selectedProductName: '', checked: false, matchSource: 'none' }); - return; - } - const prod = allProducts.find((p) => p.id === Number(productId)); - if (prod) { - updateRow(i, { - selectedProductId: prod.id, - selectedProductName: prod.canonicalName ?? prod.name, - checked: true, - matchSource: 'manual', - saveAlias: true, - }); - } - }; - const handleSave = async () => { const toSave = rows.filter((r) => r.checked && r.selectedProductId !== ''); if (toSave.length === 0) return; @@ -236,6 +235,12 @@ export default function ReceiptImportClient() { )} + {productsError && ( +

+ ⚠️ {productsError} +

+ )} + {preview && rows.length === 0 && (
Status Funktion Modell Ă…tkomst
+ {hasKey ? ( + âś… + ) : ( + đź”´ + )} +