diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index c5c1bb8..ce45843 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,6 +1,7 @@ import { useState } from "react"; import NavigationMenu from "./components/navigationMenu/NavigationMenu"; import ShoppingListsPage from "./pages/shoppingLists/ShoppingListsPage"; +import ShoppingListDetailPage from "./pages/shoppingLists/ShoppingListDetailPage"; import { BrowserRouter, @@ -126,10 +127,9 @@ function AppLayout() { element={} /> - } - /> + } /> + + } /> candidate.id === id); + + const [items, setItems] = useState(list?.items ?? []); + const [sortOrder, setSortOrder] = useState("added"); + const [newItem, setNewItem] = useState(""); + + const checkedCount = items.filter((item) => item.checked).length; + const total = items.reduce( + (sum, item) => sum + (item.unitPrice ?? 0) * item.quantity, + 0, + ); + + const sortedItems = + sortOrder === "alphabetical" + ? [...items].sort((a, b) => a.name.localeCompare(b.name, "nb")) + : items; + + function toggleChecked(itemId: string) { + setItems((current) => + current.map((item) => + item.id === itemId ? { ...item, checked: !item.checked } : item, + ), + ); + } + + function changeQuantity(itemId: string, change: number) { + setItems((current) => + current.map((item) => + item.id === itemId + ? { ...item, quantity: Math.max(1, item.quantity + change) } + : item, + ), + ); + } + + function removeItem(itemId: string) { + setItems((current) => current.filter((item) => item.id !== itemId)); + } + + function addItem(event: FormEvent) { + event.preventDefault(); + const name = newItem.trim(); + if (!name) return; + setItems((current) => [ + ...current, + { + id: crypto.randomUUID(), + ean: "", // fritt skrevet vare, ikke koblet til et produkt + name, + quantity: 1, + unitPrice: null, + checked: false, + }, + ]); + setNewItem(""); + } + + // Ugyldig id i adressen, f.eks. /lists/999 + if (!list) { + return ( +
+ + ← MINE HANDLELISTER + +

Fant ikke handlelisten

+

Listen finnes ikke, eller den er slettet.

+
+ ); + } + + return ( +
+
+ + ← MINE HANDLELISTER + +

FIG. 8 — NOTATBLOKKEN

+
+ +
+
+ + +
+

HANDLELAPPEN — IT2810 · GRUPPE 27

+

SIDE 08 / 11

+
+
+ ); +} + +export default ShoppingListDetailPage; diff --git a/frontend/src/pages/shoppingLists/ShoppingListsPage.tsx b/frontend/src/pages/shoppingLists/ShoppingListsPage.tsx index 96ef276..057fa0a 100644 --- a/frontend/src/pages/shoppingLists/ShoppingListsPage.tsx +++ b/frontend/src/pages/shoppingLists/ShoppingListsPage.tsx @@ -1,68 +1,9 @@ import { useEffect, useState } from "react"; import { useNavigate } from "react-router"; import ShoppingListCard from "./ShoppingListCard"; -import type { ShoppingList } from "./shoppingListTypes"; +import { mockLists } from "./shoppingListMockData"; import "./ShoppingListsPage.css"; -const mockLists: ShoppingList[] = [ - { - id: "1", - name: "Fredagshandel", - status: "ACTIVE", - inUse: true, - createdAt: "2026-10-19", - estimatedTotal: 423.5, - items: [ - { id: "1", ean: "1", name: "Melk", quantity: 2, checked: false }, - { id: "2", ean: "2", name: "Brød", quantity: 1, checked: false }, - { id: "3", ean: "3", name: "Egg", quantity: 1, checked: false }, - { id: "14", ean: "14", name: "Yoghurt", quantity: 1, checked: false }, - { id: "15", ean: "15", name: "Bananer", quantity: 1, checked: false }, - ], - }, - { - id: "2", - name: "Taco-kveld", - status: "ACTIVE", - inUse: false, - createdAt: "2026-10-18", - estimatedTotal: 287, - items: [ - { id: "4", ean: "4", name: "Tortilla", quantity: 2, checked: false }, - { id: "5", ean: "5", name: "Kjøttdeig", quantity: 1, checked: false }, - { id: "6", ean: "6", name: "Ost", quantity: 1, checked: false }, - ], - }, - { - id: "3", - name: "Hyttetur", - status: "ACTIVE", - inUse: false, - createdAt: "2026-10-17", - estimatedTotal: 756, - items: [ - { id: "7", ean: "7", name: "Pasta", quantity: 2, checked: false }, - { id: "8", ean: "8", name: "Kaffe", quantity: 1, checked: false }, - { id: "9", ean: "9", name: "Sjokolade", quantity: 3, checked: false }, - ], - }, - { - id: "4", - name: "Matpakke-basics", - status: "ACTIVE", - inUse: false, - createdAt: "2026-10-14", - estimatedTotal: 148.2, - items: [ - { id: "10", ean: "10", name: "Knekkebrød", quantity: 1, checked: false }, - { id: "11", ean: "11", name: "Leverpostei", quantity: 1, checked: false }, - { id: "12", ean: "12", name: "Agurk", quantity: 1, checked: false }, - { id: "13", ean: "13", name: "Paprika", quantity: 1, checked: false }, - ], - }, - -]; - type SortOrder = "newest" | "oldest" | "name-asc" | "name-desc"; const SORT_STORAGE_KEY = "active-lists-sort-order"; @@ -89,38 +30,38 @@ function loadSortOrder(): SortOrder { export default function ShoppingListsPage() { const navigate = useNavigate(); -const [sortOrder, setSortOrder] = useState(loadSortOrder); + const [sortOrder, setSortOrder] = useState(loadSortOrder); -useEffect(() => { - try { - localStorage.setItem(SORT_STORAGE_KEY, sortOrder); - } catch { - // Sorteringen fungerer selv om lagring ikke er tilgjengelig. - } -}, [sortOrder]); - -const activeLists = mockLists - .filter((list) => list.status === "ACTIVE") - .sort((a, b) => { - // Listen som er i bruk skal alltid ligge først. - if (a.inUse !== b.inUse) { - return a.inUse ? -1 : 1; + useEffect(() => { + try { + localStorage.setItem(SORT_STORAGE_KEY, sortOrder); + } catch { + // Sorteringen fungerer selv om lagring ikke er tilgjengelig. } + }, [sortOrder]); - switch (sortOrder) { - case "oldest": - return a.createdAt.localeCompare(b.createdAt); + const activeLists = mockLists + .filter((list) => list.status === "ACTIVE") + .sort((a, b) => { + // Listen som er i bruk skal alltid ligge først. + if (a.inUse !== b.inUse) { + return a.inUse ? -1 : 1; + } - case "name-asc": - return a.name.localeCompare(b.name, "nb-NO"); + switch (sortOrder) { + case "oldest": + return a.createdAt.localeCompare(b.createdAt); - case "name-desc": - return b.name.localeCompare(a.name, "nb-NO"); + case "name-asc": + return a.name.localeCompare(b.name, "nb-NO"); - default: - return b.createdAt.localeCompare(a.createdAt); - } - }); + case "name-desc": + return b.name.localeCompare(a.name, "nb-NO"); + + default: + return b.createdAt.localeCompare(a.createdAt); + } + }); function handleOpenList(id: string) { navigate(`/lists/${id}`); @@ -130,17 +71,15 @@ const activeLists = mockLists
- - 07 · LISTER - + 07 · LISTER -

+

Mine handlelister -

+ -

+

{activeLists.length} AKTIVE LISTER · DEN DU BRUKER NÅ ER MERKET -

+

@@ -159,41 +98,46 @@ const activeLists = mockLists - - - + Aktive + + + Tidligere + + + +
{activeLists.length > 0 ? (
-
+
{activeLists.map((list) => ( - + /> ))} -
+
) : (

Du har ingen aktive handlelister ennå.

diff --git a/frontend/src/pages/shoppingLists/shoppingListMockData.ts b/frontend/src/pages/shoppingLists/shoppingListMockData.ts new file mode 100644 index 0000000..6be5388 --- /dev/null +++ b/frontend/src/pages/shoppingLists/shoppingListMockData.ts @@ -0,0 +1,194 @@ +import type { ShoppingList } from "./shoppingListTypes"; + +// Midlertidige data som brukes av både oversikten og handlelistesiden. +// Byttes ut med GraphQL når backend er klar. +export const mockLists: ShoppingList[] = [ + { + id: "1", + name: "Fredagshandel", + status: "ACTIVE", + inUse: true, + createdAt: "2026-10-19", + estimatedTotal: 386.4, + items: [ + { + id: "1", + ean: "1", + name: "Lettmelk", + quantity: 2, + unitPrice: 23.9, + checked: false, + }, + { + id: "2", + ean: "2", + name: "Norvegia 26 %", + note: "skivet, hvis de har", + quantity: 1, + unitPrice: 79.9, + checked: false, + }, + { + id: "3", + ean: "3", + name: "Mozzarella", + quantity: 2, + unitPrice: 24.9, + checked: false, + }, + { + id: "4", + ean: "4", + name: "Egg, 12 stk", + note: "frittgående", + quantity: 1, + unitPrice: 54.9, + checked: false, + }, + { + id: "5", + ean: "5", + name: "Bananer", + quantity: 1, + unitPrice: 29.9, + checked: true, + }, + { + id: "6", + ean: "6", + name: "Tomater", + quantity: 4, + unitPrice: 6.9, + checked: false, + }, + { + id: "7", + ean: "7", + name: "Kaffe", + note: "filtermalt", + quantity: 1, + unitPrice: 64.9, + checked: true, + }, + { + id: "8", + ean: "8", + name: "Pasta", + note: "spaghetti", + quantity: 1, + unitPrice: 31.6, + checked: false, + }, + ], + }, + { + id: "2", + name: "Taco-kveld", + status: "ACTIVE", + inUse: false, + createdAt: "2026-10-18", + estimatedTotal: 287, + items: [ + { + id: "9", + ean: "9", + name: "Tortilla", + quantity: 2, + unitPrice: 34.9, + checked: false, + }, + { + id: "10", + ean: "10", + name: "Kjøttdeig", + quantity: 1, + unitPrice: 89.9, + checked: false, + }, + { + id: "11", + ean: "11", + name: "Ost", + quantity: 1, + unitPrice: 127.3, + checked: false, + }, + ], + }, + { + id: "3", + name: "Hyttetur", + status: "ACTIVE", + inUse: false, + createdAt: "2026-10-17", + estimatedTotal: 256.8, + items: [ + { + id: "12", + ean: "12", + name: "Pasta", + quantity: 2, + unitPrice: 31.6, + checked: false, + }, + { + id: "13", + ean: "13", + name: "Kaffe", + quantity: 1, + unitPrice: 64.9, + checked: false, + }, + { + id: "14", + ean: "14", + name: "Sjokolade", + quantity: 3, + unitPrice: 42.9, + checked: false, + }, + ], + }, + { + id: "4", + name: "Matpakke-basics", + status: "ACTIVE", + inUse: false, + createdAt: "2026-10-14", + estimatedTotal: 103.6, + items: [ + { + id: "15", + ean: "15", + name: "Knekkebrød", + quantity: 1, + unitPrice: 39.9, + checked: false, + }, + { + id: "16", + ean: "16", + name: "Leverpostei", + quantity: 1, + unitPrice: 24.9, + checked: false, + }, + { + id: "17", + ean: "17", + name: "Agurk", + quantity: 1, + unitPrice: 16.9, + checked: false, + }, + { + id: "18", + ean: "18", + name: "Paprika", + quantity: 1, + unitPrice: 21.9, + checked: false, + }, + ], + }, +]; diff --git a/frontend/src/pages/shoppingLists/shoppingListTypes.ts b/frontend/src/pages/shoppingLists/shoppingListTypes.ts index e6648d0..93be9f5 100644 --- a/frontend/src/pages/shoppingLists/shoppingListTypes.ts +++ b/frontend/src/pages/shoppingLists/shoppingListTypes.ts @@ -1,10 +1,11 @@ - export interface ShoppingListItem { id: string; ean: string; name: string; quantity: number; checked: boolean; + note?: string; + unitPrice?: number | null; } export interface ShoppingList {