feat(api): update category fetch requests to include tree structure
This commit is contained in:
@@ -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' }}
|
||||
/>
|
||||
</div>
|
||||
{row.categorySuggestion && row.matchSource === 'none' && (
|
||||
<div style={{ marginTop: '0.5rem', display: 'flex', alignItems: 'center', gap: '0.75rem', flexWrap: 'wrap' }}>
|
||||
<div style={{ fontSize: '0.8rem', color: '#7c3aed', background: '#f5f3ff', border: '1px solid #ddd6fe', borderRadius: '5px', padding: '4px 8px', display: 'inline-flex', alignItems: 'center', gap: '0.4rem' }}>
|
||||
<span>✨</span>
|
||||
<span>AI-förslag: <strong>{row.categorySuggestion.path}</strong></span>
|
||||
{row.categorySuggestion.usedFallback && <span style={{ color: '#b45309' }}>(osäker)</span>}
|
||||
</div>
|
||||
{row.matchSource === 'none' && (
|
||||
<div style={{ marginTop: '0.5rem', display: 'flex', alignItems: 'center', gap: '0.5rem', flexWrap: 'wrap' }}>
|
||||
<select
|
||||
value={row.selectedCategoryId}
|
||||
onChange={(e) => updateRow(i, { selectedCategoryId: e.target.value === '' ? '' : Number(e.target.value) })}
|
||||
style={{ fontSize: '0.8rem', padding: '3px 6px', border: '1px solid #d1d5db', borderRadius: '5px', color: '#374151', maxWidth: '260px' }}
|
||||
>
|
||||
<option value="">— Välj kategori —</option>
|
||||
{flatCategoryOptions.map((c) => (
|
||||
<option key={c.id} value={c.id}>{c.label}</option>
|
||||
))}
|
||||
</select>
|
||||
{row.categorySuggestion && (
|
||||
<span style={{ fontSize: '0.75rem', color: '#7c3aed', background: '#f5f3ff', border: '1px solid #ddd6fe', borderRadius: '5px', padding: '2px 7px', display: 'inline-flex', alignItems: 'center', gap: '0.3rem' }}>
|
||||
✨ AI: {row.categorySuggestion.path}{row.categorySuggestion.usedFallback && <span style={{ color: '#b45309' }}> (osäker)</span>}
|
||||
</span>
|
||||
)}
|
||||
{isAdmin ? (
|
||||
<button
|
||||
onClick={() => handleCreateProduct(i)}
|
||||
@@ -538,37 +554,6 @@ export default function ReceiptImportClient({ isAdmin }: { isAdmin: boolean }) {
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{row.matchSource === 'none' && !row.categorySuggestion && (
|
||||
<div style={{ marginTop: '0.5rem', display: 'flex', alignItems: 'center', gap: '0.5rem', flexWrap: 'wrap' }}>
|
||||
<select
|
||||
value={row.selectedCategoryId}
|
||||
onChange={(e) => updateRow(i, { selectedCategoryId: e.target.value === '' ? '' : Number(e.target.value) })}
|
||||
style={{ fontSize: '0.8rem', padding: '3px 6px', border: '1px solid #d1d5db', borderRadius: '5px', color: '#374151', maxWidth: '220px' }}
|
||||
>
|
||||
<option value="">— Välj kategori (Övrigt) —</option>
|
||||
{ovrigtOptions.map((c) => (
|
||||
<option key={c.id} value={c.id}>{c.name}</option>
|
||||
))}
|
||||
</select>
|
||||
{isAdmin ? (
|
||||
<button
|
||||
onClick={() => handleCreateProduct(i)}
|
||||
disabled={creatingProduct === i}
|
||||
style={{ fontSize: '0.8rem', padding: '3px 10px', background: creatingProduct === i ? '#e5e7eb' : '#f9fafb', color: creatingProduct === i ? '#9ca3af' : '#374151', border: '1px solid #d1d5db', borderRadius: '5px', cursor: creatingProduct === i ? 'not-allowed' : 'pointer' }}
|
||||
>
|
||||
{creatingProduct === i ? '⏳ Skapar...' : '+ Skapa ny produkt'}
|
||||
</button>
|
||||
) : (
|
||||
<button
|
||||
onClick={() => handleSuggestProduct(i)}
|
||||
disabled={creatingProduct === i}
|
||||
style={{ fontSize: '0.8rem', padding: '3px 10px', background: creatingProduct === i ? '#e5e7eb' : '#fefce8', color: creatingProduct === i ? '#9ca3af' : '#854d0e', border: '1px solid #fde68a', borderRadius: '5px', cursor: creatingProduct === i ? 'not-allowed' : 'pointer' }}
|
||||
>
|
||||
{creatingProduct === i ? '⏳ Skickar...' : '+ Föreslå ny vara'}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{row.selectedProductId !== '' && row.matchSource !== 'alias' && (
|
||||
<label style={{ display: 'flex', alignItems: 'center', gap: '0.4rem', marginTop: '0.5rem', fontSize: '0.82rem', color: '#555', cursor: 'pointer' }}>
|
||||
<input type="checkbox" checked={row.saveAlias} onChange={(e) => updateRow(i, { saveAlias: e.target.checked })} />
|
||||
|
||||
Reference in New Issue
Block a user