diff --git a/frontend/app/admin/products/AdminProductList.tsx b/frontend/app/admin/products/AdminProductList.tsx index d7dc5921..e369d44f 100644 --- a/frontend/app/admin/products/AdminProductList.tsx +++ b/frontend/app/admin/products/AdminProductList.tsx @@ -80,7 +80,7 @@ export default function AdminProductList() { }, [refetchProducts]); useEffect(() => { - fetch('/api/categories') + fetch('/api/categories?tree') .then((r) => r.json()) .then((data) => { if (Array.isArray(data)) setCategoryTree(data); }) .catch(() => {}); diff --git a/frontend/app/admin/products/EditProductForm.tsx b/frontend/app/admin/products/EditProductForm.tsx index 5c393ef2..1352fe2e 100644 --- a/frontend/app/admin/products/EditProductForm.tsx +++ b/frontend/app/admin/products/EditProductForm.tsx @@ -55,7 +55,7 @@ export default function EditProductForm({ product, onSaved, onDeleted }: Props) useEffect(() => { if (isOpen && categoryTree.length === 0) { - fetch('/api/categories') + fetch('/api/categories?tree') .then((r) => r.json()) .then((data: unknown) => { if (Array.isArray(data)) setCategoryTree(data as CategoryNode[]); diff --git a/frontend/app/kvitto/ReceiptImportClient.tsx b/frontend/app/kvitto/ReceiptImportClient.tsx index 3f10b8f3..e75580cd 100644 --- a/frontend/app/kvitto/ReceiptImportClient.tsx +++ b/frontend/app/kvitto/ReceiptImportClient.tsx @@ -173,7 +173,7 @@ export default function ReceiptImportClient({ isAdmin }: { isAdmin: boolean }) { matchSource: 'none', categorySuggestion: item.categorySuggestion, productSearch: '', - selectedCategoryId: '', + selectedCategoryId: item.categorySuggestion?.categoryId ?? '',, }; }), ); @@ -209,8 +209,8 @@ export default function ReceiptImportClient({ isAdmin }: { isAdmin: boolean }) { const product = await res.json(); - // Sätt kategori: AI-förslag har prioritet, annars manuellt val - const categoryId = row.categorySuggestion?.categoryId ?? (row.selectedCategoryId !== '' ? row.selectedCategoryId : null); + // Sätt kategori: manuellt val har prioritet, annars AI-förslag + const categoryId = row.selectedCategoryId !== '' ? row.selectedCategoryId : row.categorySuggestion?.categoryId ?? null; if (categoryId) { const patchRes = await fetch(`/api/admin/update-product/${product.id}`, { method: 'PATCH', @@ -263,7 +263,7 @@ export default function ReceiptImportClient({ isAdmin }: { isAdmin: boolean }) { const product = await createRes.json() as { id: number; name: string; canonicalName: string | null }; // Sätt kategori om vald/föreslagen - const categoryId = row.categorySuggestion?.categoryId ?? (row.selectedCategoryId !== '' ? row.selectedCategoryId : null); + const categoryId = row.selectedCategoryId !== '' ? row.selectedCategoryId : row.categorySuggestion?.categoryId ?? null; if (categoryId) { await fetch(`/api/products/${product.id}`, { method: 'PATCH', @@ -350,12 +350,18 @@ export default function ReceiptImportClient({ isAdmin }: { isAdmin: boolean }) { const checkedCount = rows.filter((r) => r.checked && r.selectedProductId !== '').length; - // Bygg en lista av kategorier under "Övrigt" + "Övrigt" självt som fallback - const ovrigtOptions = (() => { - const ovrigt = allCategories.find((c) => c.name === 'Övrigt' && c.parentId === null); - if (!ovrigt) return allCategories.slice(0, 20); // fallback: visa alla - const children = allCategories.filter((c) => c.parentId === ovrigt.id).sort((a, b) => a.name.localeCompare(b.name, 'sv')); - return [ovrigt, ...children]; + // Bygg flat lista av alla kategorier med hierarki: förälder → indragna barn + const flatCategoryOptions = (() => { + const roots = allCategories.filter((c) => c.parentId === null).sort((a, b) => a.name.localeCompare(b.name, 'sv')); + const result: { id: number; label: string }[] = []; + for (const root of roots) { + result.push({ id: root.id, label: root.name }); + const children = allCategories.filter((c) => c.parentId === root.id).sort((a, b) => a.name.localeCompare(b.name, 'sv')); + for (const child of children) { + result.push({ id: child.id, label: `\u00a0\u00a0↳ ${child.name}` }); + } + } + return result; })(); const sourceLabel = (src: RowState['matchSource']) => { @@ -512,13 +518,23 @@ export default function ReceiptImportClient({ isAdmin }: { isAdmin: boolean }) { style={{ width: '100%', padding: '0.3rem 0.5rem', border: '1px solid #ced4da', borderRadius: '6px', fontSize: '0.82rem', color: '#555', boxSizing: 'border-box' }} /> - {row.categorySuggestion && row.matchSource === 'none' && ( -