diff --git a/frontend/app/api/products-create/route.ts b/frontend/app/api/products-create/route.ts new file mode 100644 index 00000000..10290c4a --- /dev/null +++ b/frontend/app/api/products-create/route.ts @@ -0,0 +1,46 @@ +import { getAuthHeaders } from '@/lib/auth-headers'; + +const API_BASE = process.env.NEXT_PUBLIC_API_URL_INTERNAL || 'http://recipe-api:8080'; + +export async function POST(req: Request) { + try { + const body = await req.json(); + const { name } = body; + + if (!name || typeof name !== 'string') { + return Response.json({ error: 'Name is required' }, { status: 400 }); + } + + const authHeaders = await getAuthHeaders(); + console.log('[products-create] Auth headers:', authHeaders ? 'YES' : 'NO'); + + const res = await fetch(`${API_BASE}/api/products`, { + method: 'POST', + headers: { 'Content-Type': 'application/json', ...authHeaders }, + body: JSON.stringify({ name }), + }); + + if (!res.ok) { + const e = await res.json().catch(() => ({})); + return Response.json( + { error: e.message ?? `HTTP ${res.status}` }, + { status: res.status }, + ); + } + + const product = await res.json(); + + // Return only serializable fields + return Response.json({ + id: product.id, + name: product.name, + canonicalName: product.canonicalName ?? null, + }); + } catch (err) { + console.error('[products-create] Error:', err); + return Response.json( + { error: err instanceof Error ? err.message : 'Unknown error' }, + { status: 500 }, + ); + } +} diff --git a/frontend/app/api/products-update/[id]/route.ts b/frontend/app/api/products-update/[id]/route.ts new file mode 100644 index 00000000..158b37f0 --- /dev/null +++ b/frontend/app/api/products-update/[id]/route.ts @@ -0,0 +1,51 @@ +import { getAuthHeaders } from '@/lib/auth-headers'; + +const API_BASE = process.env.NEXT_PUBLIC_API_URL_INTERNAL || 'http://recipe-api:8080'; + +export async function PATCH( + req: Request, + { params }: { params: { id: string } }, +) { + try { + const productId = parseInt(params.id, 10); + const body = await req.json(); + const { categoryId } = body; + + if (!categoryId || typeof categoryId !== 'number') { + return Response.json({ error: 'categoryId is required' }, { status: 400 }); + } + + const authHeaders = await getAuthHeaders(); + console.log('[products-update] Auth headers:', authHeaders ? 'YES' : 'NO'); + + const res = await fetch(`${API_BASE}/api/products/${productId}`, { + method: 'PATCH', + headers: { 'Content-Type': 'application/json', ...authHeaders }, + body: JSON.stringify({ categoryId }), + }); + + if (!res.ok) { + const e = await res.json().catch(() => ({})); + return Response.json( + { error: e.message ?? `HTTP ${res.status}` }, + { status: res.status }, + ); + } + + const product = await res.json(); + + // Return only serializable fields + return Response.json({ + id: product.id, + name: product.name, + canonicalName: product.canonicalName ?? null, + categoryId: product.categoryId ?? null, + }); + } catch (err) { + console.error('[products-update] Error:', err); + return Response.json( + { error: err instanceof Error ? err.message : 'Unknown error' }, + { status: 500 }, + ); + } +} diff --git a/frontend/app/kvitto/ReceiptImportClient.tsx b/frontend/app/kvitto/ReceiptImportClient.tsx index 80ded876..2d6f3ebe 100644 --- a/frontend/app/kvitto/ReceiptImportClient.tsx +++ b/frontend/app/kvitto/ReceiptImportClient.tsx @@ -1,7 +1,6 @@ 'use client'; import { useRef, useState, useEffect } from 'react'; -import { createProductAction, updateProductCategoryAction } from './actions'; type CategorySuggestion = { categoryId: number; @@ -194,15 +193,34 @@ export default function ReceiptImportClient({ isAdmin }: { isAdmin: boolean }) { setCreatingProduct(i); setError(null); // eslint-disable-next-line no-console - console.log('handleCreateProduct: isAdmin =', isAdmin, 'endpoint = /api/products'); + console.log('handleCreateProduct: isAdmin =', isAdmin, 'endpoint = /api/products-create'); try { - // Admin skapar aktiv produkt direkt via Server Action - const product = await createProductAction(row.rawName); + // Admin skapar aktiv produkt via API route + const res = await fetch('/api/products-create', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ name: row.rawName }), + }); + + if (!res.ok) { + const e = await res.json().catch(() => ({})); + throw new Error(e.error ?? `HTTP ${res.status}`); + } + + 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); if (categoryId) { - await updateProductCategoryAction(product.id, categoryId); + const patchRes = await fetch(`/api/products-update/${product.id}`, { + method: 'PATCH', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ categoryId }), + }); + if (!patchRes.ok) { + const e = await patchRes.json().catch(() => ({})); + throw new Error(e.error ?? `HTTP ${patchRes.status}`); + } } // Uppdatera produktlistan lokalt