diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index ed57d82..881ce73 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,5 +1,6 @@ import { useState } from "react"; import NavigationMenu from "./components/navigationMenu/NavigationMenu"; +import ShoppingListsPage from "./pages/shoppingLists/ShoppingListsPage"; import { BrowserRouter, @@ -122,9 +123,12 @@ function AppLayout() { - } + element={} + /> + + } /> void; +} + +export default function ShoppingListCard({ + list, + onOpen, +}: ShoppingListCardProps) { + const productCount = list.items.length; + + const itemCount = list.items.reduce( + (total, item) => total + item.quantity, + 0 +); + + const formattedPrice = + list.estimatedTotal !== null + ? list.estimatedTotal.toLocaleString("nb-NO", { + style: "currency", + currency: "NOK", + }) + : "Pris ikke tilgjengelig"; + + const formattedDate = new Date(list.createdAt).toLocaleDateString( + "nb-NO", + { day: "numeric", month: "short" } + ); + + return ( + + ); +} diff --git a/frontend/src/pages/shoppingLists/ShoppingListsPage.css b/frontend/src/pages/shoppingLists/ShoppingListsPage.css new file mode 100644 index 0000000..0eddd0f --- /dev/null +++ b/frontend/src/pages/shoppingLists/ShoppingListsPage.css @@ -0,0 +1,330 @@ + +/* Handlelistekort inspirert av Figma-notatblokken */ + +.shopping-list-card { + position: relative; + display: block; + width: 100%; + min-height: 320px; + padding: 0; + text-align: left; + font: inherit; + color: var(--note-ink); + background: var(--note-paper); + border: 1px solid var(--border-strong); + border-radius: 0; + box-shadow: 4px 4px 0 var(--note-tint); + cursor: pointer; + transition: transform 0.2s, box-shadow 0.2s; +} + +.shopping-list-card:hover { + transform: translateY(-4px) rotate(-0.5deg); + box-shadow: 6px 8px 0 var(--note-tint); +} + +.shopping-list-card:focus-visible { + outline: 3px solid var(--text-accent); + outline-offset: 5px; +} + +.shopping-list-card--in-use { + background: var(--note-tint); +} + +.shopping-list-card__tape { + position: absolute; + top: -9px; + left: 50%; + width: 48px; + height: 16px; + background: var(--note-tape); + transform: translateX(-50%) rotate(3deg); + z-index: 1; +} + +.shopping-list-card__content { + display: flex; + flex-direction: column; + min-height: 320px; + padding: 20px 12px 16px 20px; + border-left: 2px solid var(--note-margin); + margin-left: 16px; +} + +.shopping-list-card__top { + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; +} + +.shopping-list-card__date { + font-family: var(--font-mono); + font-size: 10px; + letter-spacing: 0.04em; + color: var(--text-muted); +} + +.shopping-list-card__badge { + font-family: var(--font-mono); + font-size: 10px; + color: var(--text-on-dark); + background: var(--bg-brand); + padding: 4px 7px; + border-radius: 2px; + white-space: nowrap; +} + +.shopping-list-card__title { + margin: 6px 0 16px; + font-family: var(--font-note); + font-size: 1.45rem; + line-height: 1.3; + font-weight: 700; + color: var(--note-ink); +} + +.shopping-list-card__items { + list-style: none; + padding: 0; + margin: 0; + flex: 1; +} + +.shopping-list-card__items li { + min-height: 34px; + padding: 6px 0; + border-bottom: 1px solid var(--note-rule); + font-family: var(--font-note); + font-size: 0.95rem; +} + +.shopping-list-card__footer { + display: flex; + justify-content: space-between; + align-items: end; + gap: 10px; + margin-top: 24px; +} + +.shopping-list-card__footer span { + font-family: var(--font-mono); + font-size: 10px; + color: var(--text-muted); +} + +.shopping-list-card__footer strong { + font-family: var(--font-ui); + font-size: 0.9rem; + font-weight: 600; + color: var(--text-primary); +} + +@media (prefers-reduced-motion: reduce) { + .shopping-list-card { + transition: none; + } + + .shopping-list-card:hover { + transform: none; + } +} + + +/* Oversiktssiden */ + +.shopping-lists-page { + width: min(100% - 3rem, 1400px); + margin: 0 auto; + padding: 3rem 0 4rem; +} + +.shopping-lists-page__header { + display: flex; + align-items: center; + justify-content: space-between; + flex-wrap: wrap; + gap: 1.5rem; + margin-bottom: 2.5rem; +} + +.shopping-lists-page__header h1 { + margin: 0 0 0.5rem; + color: var(--text-primary); +} + +.shopping-lists-page__header p { + margin: 0; + color: var(--text-secondary); +} + +.shopping-lists-page__new-button { + padding: 0.8rem 1.4rem; + background: var(--bg-primary); + color: var(--text-on-dark); + border: none; + border-radius: 5px; + font-family: var(--font-ui); + font-weight: 600; +} + +.shopping-lists-page__new-button:disabled { + opacity: 0.6; + cursor: not-allowed; +} + +.shopping-lists-page__grid { + display: grid; + grid-template-columns: repeat(4, minmax(0, 1fr)); + gap: 24px; + align-items: stretch; +} + +@media (max-width: 1000px) { + .shopping-lists-page__grid { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } +} + + +.shopping-lists-page__eyebrow { + display: block; + margin-bottom: 12px; + font-family: var(--font-mono); + font-size: 11px; + letter-spacing: 0.08em; + color: var(--text-accent); +} + +.shopping-lists-page__header h1 { + font-family: var(--font-ui); + font-size: clamp(2rem, 4vw, 3rem); + font-weight: 600; + line-height: 1.1; + color: var(--text-primary); +} + +.shopping-lists-page__header h1 em { + font-family: var(--font-accent); + font-weight: 400; + color: var(--text-brand); +} + +.shopping-lists-page__subtitle { + margin-top: 10px; + font-family: var(--font-mono); + font-size: 10px; + letter-spacing: 0.06em; + color: var(--text-muted); +} + + +/* Faner og knapper */ + +.shopping-lists-page__actions { + display: flex; + align-items: center; + gap: 12px; + flex-wrap: wrap; +} + +.shopping-lists-page__tabs { + display: flex; + align-items: center; + padding: 3px; + background: var(--bg-muted); + border: 1px solid var(--border-hairline); + border-radius: 6px; +} + +.shopping-lists-page__tab { + padding: 8px 12px; + font-family: var(--font-mono); + font-size: 10px; + letter-spacing: 0.04em; + text-transform: uppercase; +} + +.shopping-lists-page__tab--active { + background: var(--bg-surface); + border: 1px solid var(--border-brand); + border-radius: 4px; + color: var(--text-brand); +} + +.shopping-lists-page__tab--disabled { + color: var(--text-muted); +} + +/* Rutete bakgrunn */ + +.shopping-lists-page__board { + padding: 32px; + border-top: 1px solid var(--border-hairline); + border-bottom: 1px solid var(--border-hairline); +} + +.shopping-lists-page__board .shopping-lists-page__grid { + gap: 24px; +} + +/* Nettbrett */ +@media (max-width: 1000px) { + .shopping-lists-page__grid { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } +} + +/* Mobil */ +@media (max-width: 600px) { + .shopping-lists-page { + width: calc(100% - 1.5rem); + padding: 2rem 0 3rem; + } + + .shopping-lists-page__grid { + grid-template-columns: 1fr; + } + + .shopping-lists-page__actions { + width: 100%; + } + + .shopping-lists-page__board { + padding: 28px 16px 36px; + } + + .shopping-lists-page__actions { + width: 100%; + } +} + +.shopping-list-card__items .shopping-list-card__more { + font-family: var(--font-note); + color: var(--note-ink); + font-size: 1.1rem; + letter-spacing: 0.1em; +} + +/* Litt ulik rotasjon for et naturlig notatblokkutseende */ +.shopping-list-card:nth-child(4n + 1) { + transform: rotate(-0.6deg); +} + +.shopping-list-card:nth-child(4n + 2) { + transform: rotate(0.4deg); +} + +.shopping-list-card:nth-child(4n + 3) { + transform: rotate(-0.3deg); +} + +.shopping-list-card:nth-child(4n + 4) { + transform: rotate(0.5deg); +} + +/* Kortene retter seg opp ved hover */ +.shopping-list-card:hover { + transform: translateY(-4px) rotate(0deg); +} \ No newline at end of file diff --git a/frontend/src/pages/shoppingLists/ShoppingListsPage.tsx b/frontend/src/pages/shoppingLists/ShoppingListsPage.tsx new file mode 100644 index 0000000..3d04e2f --- /dev/null +++ b/frontend/src/pages/shoppingLists/ShoppingListsPage.tsx @@ -0,0 +1,135 @@ +import { useNavigate } from "react-router"; +import ShoppingListCard from "./ShoppingListCard"; +import type { ShoppingList } from "./shoppingListTypes"; +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 }, + ], + }, + +]; + +export default function ShoppingListsPage() { + const navigate = useNavigate(); + + const activeLists = mockLists.filter( + (list) => list.status === "ACTIVE" + ); + + function handleOpenList(id: string) { + navigate(`/lists/${id}`); + } + + return ( +
+
+
+ + 07 · LISTER + + +

+ Mine handlelister +

+ +

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

+
+ +
+ + + +
+
+ + {activeLists.length > 0 ? ( +
+
+ {activeLists.map((list) => ( + + ))} +
+
+ ) : ( +

Du har ingen aktive handlelister ennå.

+ )} +
+ ); +} diff --git a/frontend/src/pages/shoppingLists/shoppingListTypes.ts b/frontend/src/pages/shoppingLists/shoppingListTypes.ts new file mode 100644 index 0000000..e6648d0 --- /dev/null +++ b/frontend/src/pages/shoppingLists/shoppingListTypes.ts @@ -0,0 +1,18 @@ + +export interface ShoppingListItem { + id: string; + ean: string; + name: string; + quantity: number; + checked: boolean; +} + +export interface ShoppingList { + id: string; + name: string; + status: "ACTIVE" | "COMPLETED"; + inUse: boolean; + createdAt: string; + items: ShoppingListItem[]; + estimatedTotal: number | null; +}