Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 83 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,52 @@ Produktsiden viser pris, kilopris, ingredienser, allergener og næringsinnhold.


### Mine handlelister og aktiv liste
Man kan ha flere aktive lister, og den man bruker nå er merket. På den aktive lista kan man endre antall, fjerne varer, sortere etter butikkavdeling og se estimert sum.

Oversiktssiden for aktive handlelister er implementert med utgangspunkt i Figma-designet (FIG. 6).

Handlelistene vises som kort inspirert av notatblokker, og hver liste viser navn, dato, et utvalg produkter, antall forskjellige produkter, totalt antall varer og estimert totalpris. Dersom en liste inneholder flere produkter enn det som får plass på kortet, vises dette med «...».

Brukeren kan ha flere aktive handlelister. Listen som er merket «I BRUK», fremheves visuelt og plasseres alltid først i oversikten.

**Sortering av aktive handlelister:**

- Nyeste først (foreløpig basert på `createdAt`).
- Eldste først.
- Navn A–Å.
- Navn Å–A.

Sorteringen utføres i frontend, og brukerens valg lagres i `localStorage`. Dermed beholdes sorteringen ved navigasjon og oppdatering av nettsiden.

Kortene er klikkbare og navigerer til den tilhørende handlelistens detaljrute. Detaljsidens funksjonalitet utvikles separat.

Oversikten bruker foreløpig mockdata frem til integrasjonen med GraphQL (#63). Designet benytter felles variabler fra `theme.css` og er tilpasset mobil, desktop, lys modus og mørk modus.

### Tidligere handlelister
Ferdige lister er gruppert etter måned. Man kan søke i dem og laste inn eldre lister. Fra en tidligere liste kan man bruke den på nytt eller legge varene på den aktive lappen.

Historikkoversikten er implementert med utgangspunkt i Figma-designet (FIG. 10).

Siden viser fullførte handlelister gruppert etter måned og år. Hver liste vises som en egen rad med dato, listenavn, antall forskjellige produkter, totalt antall varer og estimert totalpris.

**Søk, sortering og visning av flere lister (#70):**

- Brukeren kan søke etter listenavn.
- Listene kan sorteres etter dato, med nyeste eller eldste først.
- De første 10 listene vises som standard.
- «Last inn flere» viser opptil 10 ekstra lister om gangen.
- «Last inn færre» reduserer antall synlige lister med opptil 10, men aldri under de første 10.
- En teller viser hvor mange lister som er synlige av totalt antall treff, for eksempel «20 av 30 handlelister vist».

Søk og sortering skjer før antallet synlige lister begrenses. Dermed fungerer både visning av flere/færre lister og telleren også når brukeren søker eller endrer sortering.

**Lagring av brukerinnstillinger:**

Søkestreng, sorteringsvalg og antall synlige handlelister lagres i `localStorage`. Innstillingene beholdes når brukeren oppdaterer nettsiden eller navigerer mellom sidene. Ved nytt søk eller endret sortering tilbakestilles visningen til de første 10 treffene.

Historikkoversikten benytter foreløpig mockdata. «Bruk på nytt»-knappen vises, men er deaktivert frem til funksjonaliteten er koblet til backend.

Videre arbeid omfatter å hente fullførte handlelister fra GraphQL (#63), gjøre historikkradene klikkbare mot detaljsiden for tidligere handlelister (#59), og implementere funksjonalitet for gjenbruk av handlelister.

Designet benytter eksisterende variabler fra `theme.css`, med responsiv tilpasning og støtte for lys og mørk modus.


### Handletur (mobil)
Expand Down Expand Up @@ -290,6 +332,45 @@ Lister med mange elementer, som søkeresultater og tidligere handlelister, retur
- Støtte for light mode og dark mode gjennom felles temavariabler.
- Testet manuelt mot backend. `npm run build` er gjennomført uten feil.

### Handlelisteoversikter – implementert etter innlevering 2

**Aktive handlelister (#62):**

- Oversikt med responsive handlelistekort.
- Visning av produktantall, vareantall og estimert totalpris.
- Fremheving av listen som er «I BRUK».
- Navigasjon til detaljrute for valgt handleliste.

**Tidligere handlelister (#70):**

- Historikkoversikt gruppert etter måned og år.
- Søk og sortering etter dato.
- Visning av 10 lister om gangen, med mulighet for å vise flere eller færre.
- Teller for antall synlige lister og totalt antall treff.
- Lagring av søk, sortering og visningsantall i `localStorage`.

**Sortering av aktive handlelister (#77):**

- Sortering etter dato og listenavn.
- Listen som er «I BRUK», vises alltid først.
- Sorteringsvalget lagres i `localStorage`.

**Tekniske valg:**

- React med TypeScript og CSS.
- Gjenbrukbare komponenter for handlelistekort og historikkrader.
- Felles designvariabler gjennom `theme.css`.
- Mockdata brukes frem til GraphQL-integrasjonen er ferdig.
- Sortering, søk og begrensning av synlige handlelister utføres foreløpig i frontend.

**Gjenstående arbeid:**

- Integrere handlelisteoversiktene med GraphQL (#63).
- Koble historikkoversikten til detaljsiden for tidligere handlelister (#59).
- Implementere funksjonaliteten «Bruk på nytt».
- Bruke faktisk tidspunkt for siste endring ved datosortering når dette er tilgjengelig fra backend.
- Vurdere serverbasert paginering for større datamengder.

**Gjenstår:**
- Hente ingredienser, allergener og næringsinnhold fra backend.
- Koble handlelistekontrollene til backend.
Expand Down
27 changes: 26 additions & 1 deletion frontend/src/pages/shoppingLists/ShoppingListsPage.css
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@

.shopping-lists-page__actions {
display: flex;
align-items: center;
align-items: flex-end;
gap: 12px;
flex-wrap: wrap;
}
Expand Down Expand Up @@ -327,4 +327,29 @@
/* Kortene retter seg opp ved hover */
.shopping-list-card:hover {
transform: translateY(-4px) rotate(0deg);
}

.shopping-lists-page__sort {
display: flex;
flex-direction: column;
gap: 6px;
font-family: var(--font-mono);
font-size: 11px;
color: var(--text-muted);
}

.shopping-lists-page__sort select {
padding: 10px 14px;
font-family: var(--font-ui);
font-size: 13px;
color: var(--text-primary);
background: var(--bg-surface);
border: 1px solid var(--border-hairline);
border-radius: 6px;
cursor: pointer;
}

.shopping-lists-page__sort select:focus-visible {
outline: 2px solid var(--text-accent);
outline-offset: 3px;
}
74 changes: 71 additions & 3 deletions frontend/src/pages/shoppingLists/ShoppingListsPage.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { useEffect, useState } from "react";
import { useNavigate } from "react-router";
import ShoppingListCard from "./ShoppingListCard";
import type { ShoppingList } from "./shoppingListTypes";
Expand Down Expand Up @@ -62,12 +63,64 @@ const mockLists: ShoppingList[] = [

];

type SortOrder = "newest" | "oldest" | "name-asc" | "name-desc";

const SORT_STORAGE_KEY = "active-lists-sort-order";

function loadSortOrder(): SortOrder {
try {
const saved = localStorage.getItem(SORT_STORAGE_KEY);

if (
saved === "newest" ||
saved === "oldest" ||
saved === "name-asc" ||
saved === "name-desc"
) {
return saved;
}
} catch {
// Bruk standardverdien hvis localStorage ikke er tilgjengelig.
}

return "newest";
}

export default function ShoppingListsPage() {
const navigate = useNavigate();

const activeLists = mockLists.filter(
(list) => list.status === "ACTIVE"
);
const [sortOrder, setSortOrder] = useState<SortOrder>(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;
}

switch (sortOrder) {
case "oldest":
return a.createdAt.localeCompare(b.createdAt);

case "name-asc":
return a.name.localeCompare(b.name, "nb-NO");

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}`);
Expand All @@ -91,6 +144,21 @@ export default function ShoppingListsPage() {
</div>

<div className="shopping-lists-page__actions">
<label className="shopping-lists-page__sort">
<span>Sorter etter</span>

<select
value={sortOrder}
onChange={(event) =>
setSortOrder(event.target.value as SortOrder)
}
>
<option value="newest">Sist endret</option>
<option value="oldest">Eldste først</option>
<option value="name-asc">Navn A–Å</option>
<option value="name-desc">Navn Å–A</option>
</select>
</label>
<nav className="shopping-lists-page__tabs" aria-label="Handlelisteoversikter">
<span className="shopping-lists-page__tab shopping-lists-page__tab--active"
aria-current="page">
Expand Down