diff --git a/backend/src/inventory/inventory.service.ts b/backend/src/inventory/inventory.service.ts
index f490cbdd..12a11a68 100644
--- a/backend/src/inventory/inventory.service.ts
+++ b/backend/src/inventory/inventory.service.ts
@@ -26,6 +26,8 @@ export class InventoryService {
orderBy.push({ bestBeforeDate: 'asc' });
} else if (query?.sort === 'bestBeforeDesc') {
orderBy.push({ bestBeforeDate: 'desc' });
+ } else if (query?.sort === 'nameAsc') {
+ orderBy.push({ product: { name: 'asc' } } as any);
} else if (query?.sort === 'purchaseDateAsc') {
orderBy.push({ purchaseDate: 'asc' });
} else if (query?.sort === 'purchaseDateDesc') {
diff --git a/frontend/app/admin/products/AdminProductList.tsx b/frontend/app/admin/products/AdminProductList.tsx
new file mode 100644
index 00000000..b4d6b57a
--- /dev/null
+++ b/frontend/app/admin/products/AdminProductList.tsx
@@ -0,0 +1,130 @@
+'use client';
+
+import { useState, useMemo } from 'react';
+import type { Product } from '../../../features/inventory/types';
+import CanonicalNameForm from './CanonicalNameForm';
+
+type Props = {
+ products: Product[];
+};
+
+const sortOptions = [
+ { value: 'createdDesc', label: 'Senast tillagda' },
+ { value: 'nameAsc', label: 'Namn A–Ö' },
+];
+
+export default function AdminProductList({ products }: Props) {
+ const [search, setSearch] = useState('');
+ const [sort, setSort] = useState('createdDesc');
+
+ const filtered = useMemo(() => {
+ const q = search.trim().toLowerCase();
+
+ let result = q
+ ? products.filter(
+ (p) =>
+ p.name.toLowerCase().includes(q) ||
+ (p.canonicalName ?? '').toLowerCase().includes(q) ||
+ (p.normalizedName ?? '').toLowerCase().includes(q),
+ )
+ : [...products];
+
+ if (sort === 'nameAsc') {
+ result.sort((a, b) =>
+ (a.canonicalName || a.name).localeCompare(b.canonicalName || b.name, 'sv'),
+ );
+ } else {
+ result.sort((a, b) => b.id - a.id);
+ }
+
+ return result;
+ }, [products, search, sort]);
+
+ return (
+ <>
+
+
setSearch(e.target.value)}
+ style={{
+ flex: '1 1 200px',
+ padding: '0.5rem 0.75rem',
+ border: '1px solid #ddd',
+ borderRadius: '6px',
+ fontSize: '1rem',
+ }}
+ />
+
+
+ {sortOptions.map((opt) => (
+
+ ))}
+
+
+ {search && (
+
+ {filtered.length} av {products.length} produkter
+
+ )}
+
+
+
+ {filtered.map((product) => (
+
+
+ ID: {product.id}
+
+
+ Namn: {product.name}
+
+
+ Canonical name: {product.canonicalName || 'Saknas'}
+
+
+ Normalized: {product.normalizedName}
+
+
+
+
+ ))}
+
+ >
+ );
+}
diff --git a/frontend/app/admin/products/page.tsx b/frontend/app/admin/products/page.tsx
index 6caaa628..93465bb9 100644
--- a/frontend/app/admin/products/page.tsx
+++ b/frontend/app/admin/products/page.tsx
@@ -1,7 +1,7 @@
import { fetchJson } from '../../../lib/api';
import type { Product } from '../../../features/inventory/types';
-import CanonicalNameForm from './CanonicalNameForm';
import MergePreviewForm from './MergePreviewForm';
+import AdminProductList from './AdminProductList';
export default async function AdminProductsPage() {
const products = await fetchJson('/api/products');
@@ -13,38 +13,7 @@ export default async function AdminProductsPage() {
-
- {products.map((product) => (
-
-
- ID: {product.id}
-
-
- Namn: {product.name}
-
-
- Canonical name: {product.canonicalName || 'Saknas'}
-
-
- Normalized: {product.normalizedName}
-
-
-
-
- ))}
-
+
);
}
\ No newline at end of file
diff --git a/frontend/app/inventory/InventoryForm.tsx b/frontend/app/inventory/InventoryForm.tsx
index 9a537aa1..d7cf1748 100644
--- a/frontend/app/inventory/InventoryForm.tsx
+++ b/frontend/app/inventory/InventoryForm.tsx
@@ -11,6 +11,7 @@ type Props = {
export default function InventoryForm({ products }: Props) {
const [isPending, setIsPending] = useState(false);
const [error, setError] = useState(null);
+ const [isOpen, setIsOpen] = useState(false);
const UNIT_OPTIONS = [
{ value: '', label: 'Välj enhet' },
@@ -56,8 +57,32 @@ export default function InventoryForm({ products }: Props) {
}
return (
-