feat: implement inventory and pantry management views with CRUD functionality and user-friendly interfaces

This commit is contained in:
Nils-Johan Gynther
2026-04-21 14:43:18 +02:00
parent 82c3dc3fee
commit 81b63b3fdb
14 changed files with 352 additions and 59 deletions
+4 -2
View File
@@ -7,6 +7,7 @@ import { UNIT_OPTIONS } from '../../lib/units';
type Props = {
item: InventoryItem;
onUpdated?: () => void;
};
function toDateInputValue(value: string | null) {
@@ -44,7 +45,7 @@ const LOCATION_OPTIONS = [
{ value: 'Annat', label: 'Annat' },
];
export default function InventoryEditForm({ item }: Props) {
export default function InventoryEditForm({ item, onUpdated }: Props) {
const [isEditing, setIsEditing] = useState(false);
const [isPending, setIsPending] = useState(false);
const [error, setError] = useState<string | null>(null);
@@ -103,7 +104,8 @@ export default function InventoryEditForm({ item }: Props) {
throw new Error(data?.error || 'Kunde inte uppdatera');
}
setIsEditing(false);
router.refresh();
if (onUpdated) onUpdated();
else router.refresh();
} catch (err) {
setError(err instanceof Error ? err.message : 'Okänt fel');
} finally {
+4 -2
View File
@@ -7,9 +7,10 @@ import { UNIT_OPTIONS } from '../../lib/units';
type Props = {
products: Product[];
onCreated?: () => void;
};
export default function InventoryForm({ products }: Props) {
export default function InventoryForm({ products, onCreated }: Props) {
const [isPending, setIsPending] = useState(false);
const [error, setError] = useState<string | null>(null);
const [isOpen, setIsOpen] = useState(false);
@@ -108,7 +109,8 @@ export default function InventoryForm({ products }: Props) {
throw new Error(data?.error || 'Kunde inte spara');
}
form.reset();
router.refresh();
if (onCreated) onCreated();
else router.refresh();
} catch (err) {
setError(err instanceof Error ? err.message : 'Okänt fel');
} finally {
+27 -2
View File
@@ -1,6 +1,7 @@
'use client';
import { useState } from 'react';
import { useRouter } from 'next/navigation';
import type { InventoryItem } from '../../features/inventory/types';
import InventoryEditForm from './InventoryEditForm';
import InventoryConsumeForm from './InventoryConsumeForm';
@@ -27,10 +28,12 @@ function getBestBeforeStatus(bestBeforeDate: string | null) {
type Props = {
inventory: InventoryItem[];
onDeleted?: () => void;
};
export default function InventoryList({ inventory }: Props) {
export default function InventoryList({ inventory, onDeleted }: Props) {
const [search, setSearch] = useState('');
const router = useRouter();
// Unika produktnamn för autocomplete
const autocompleteNames = Array.from(
@@ -165,9 +168,31 @@ export default function InventoryList({ inventory }: Props) {
justifyContent: 'flex-start',
}}
>
<InventoryEditForm item={item} />
<InventoryEditForm item={item} onUpdated={onDeleted ?? (() => router.refresh())} />
<InventoryConsumeForm id={item.id} unit={item.unit} />
<InventoryConsumptionHistory id={item.id} />
<button
type="button"
onClick={async () => {
if (!confirm(`Ta bort "${item.product.canonicalName || item.product.name}" från inventariet?`)) return;
const res = await fetch(`/api/admin/inventory-item/${item.id}`, { method: 'DELETE' });
if (res.ok) {
if (onDeleted) onDeleted();
else router.refresh();
}
}}
style={{
padding: '0.5rem 0.75rem',
background: '#fff0f0',
border: '1px solid #f5b8b8',
borderRadius: '6px',
color: '#c00',
cursor: 'pointer',
fontWeight: 500,
}}
>
Ta bort
</button>
</div>
</article>
);