MAJOR UPPDATE: "First Ai"

feat: add AI categorization for products and enhance user management

- Integrated AI service for category suggestions in receipt import and product management.
- Added premium subscription feature for users with corresponding API endpoints.
- Implemented admin interface for managing pending product suggestions.
- Enhanced user management to include premium status and corresponding UI updates.
- Updated database schema to support new fields for premium status and product status.
This commit is contained in:
Nils-Johan Gynther
2026-04-19 10:34:21 +02:00
parent 0286ab0991
commit 054a19ed7c
30 changed files with 917 additions and 77 deletions
@@ -2,6 +2,14 @@
import { useRef, useState, useEffect } from 'react';
type CategorySuggestion = {
categoryId: number;
categoryName: string;
path: string;
confidence: 'high' | 'medium' | 'low';
usedFallback: boolean;
};
type ParsedItem = {
rawName: string;
quantity: number;
@@ -11,6 +19,7 @@ type ParsedItem = {
matchedProductName?: string;
suggestedProductId?: number;
suggestedProductName?: string;
categorySuggestion?: CategorySuggestion;
};
type Product = { id: number; name: string; canonicalName: string | null };
@@ -27,6 +36,7 @@ type RowState = {
editQty: string;
editUnit: string;
matchSource: 'alias' | 'suggestion' | 'manual' | 'none';
categorySuggestion?: CategorySuggestion;
};
const UNITS = ['st', 'kg', 'g', 'l', 'dl', 'cl', 'ml', 'förp', 'pak', 'burk', 'flaska'];
@@ -116,6 +126,7 @@ export default function ReceiptImportClient() {
editQty: String(item.quantity),
editUnit: item.unit,
matchSource: 'none',
categorySuggestion: item.categorySuggestion,
};
}),
);
@@ -272,6 +283,13 @@ export default function ReceiptImportClient() {
{UNITS.map((u) => <option key={u} value={u}>{u}</option>)}
</select>
</div>
{row.categorySuggestion && row.matchSource === 'none' && (
<div style={{ marginTop: '0.5rem', 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.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 })} />