diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
index ce45843..92d8830 100644
--- a/frontend/src/App.tsx
+++ b/frontend/src/App.tsx
@@ -1,8 +1,4 @@
import { useState } from "react";
-import NavigationMenu from "./components/navigationMenu/NavigationMenu";
-import ShoppingListsPage from "./pages/shoppingLists/ShoppingListsPage";
-import ShoppingListDetailPage from "./pages/shoppingLists/ShoppingListDetailPage";
-
import {
BrowserRouter,
Routes,
@@ -11,17 +7,25 @@ import {
useNavigate,
} from "react-router";
+// Felles komponenter
import Header from "./components/header/Header";
+import NavigationMenu from "./components/navigationMenu/NavigationMenu";
+
+// Sider
import HomePage from "./pages/home/HomePage";
import SearchResultsPage from "./pages/SearchResults/SearchResultsPage";
+import ShoppingListsPage from "./pages/shoppingLists/ShoppingListsPage";
+import ShoppingListDetailPage from "./pages/shoppingLists/ShoppingListDetailPage";
+import PreviousListsPage from "./pages/shoppingLists/PreviousListsPage";
+// Hjelpefunksjoner
import {
loadSearchPreferences,
saveSearchPreferences,
} from "./pages/SearchResults/searchPreferences";
-
import { createCategoryUrl } from "./pages/home/categoryNavigation";
+// Styling
import "./App.css";
function AppLayout() {
@@ -131,6 +135,11 @@ function AppLayout() {
} />
+ }
+ />
+
void;
+}
+
+export default function PreviousListRow({
+ list,
+ onReuse,
+}: PreviousListRowProps) {
+ const formattedDate = new Date(
+ `${list.createdAt.slice(0, 10)}T12:00:00`
+ ).toLocaleDateString("nb-NO", {
+ day: "2-digit",
+ month: "2-digit",
+ });
+
+ 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";
+
+ return (
+
+
+
+ {formattedDate}
+
+
+
+ {list.name}
+
+
+
+ {productCount} produkter · {itemCount} varer
+
+
+
+ {formattedPrice}
+
+
+
+
+
+ );
+}
diff --git a/frontend/src/pages/shoppingLists/PreviousListsPage.css b/frontend/src/pages/shoppingLists/PreviousListsPage.css
new file mode 100644
index 0000000..bcd5778
--- /dev/null
+++ b/frontend/src/pages/shoppingLists/PreviousListsPage.css
@@ -0,0 +1,350 @@
+
+/* Historikkoversikt */
+
+.previous-lists-page {
+ width: min(100% - 3rem, 1200px);
+ margin: 0 auto;
+ padding: 3rem 0 5rem;
+}
+
+.previous-lists-page__header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ flex-wrap: wrap;
+ gap: 1.5rem;
+ margin-bottom: 2.5rem;
+}
+
+.previous-lists-page__heading {
+ flex: 1;
+ min-width: 0;
+}
+
+.previous-lists-page__eyebrow {
+ display: block;
+ margin-bottom: 12px;
+ font: 500 11px var(--font-mono);
+ letter-spacing: 0.08em;
+ color: var(--text-accent);
+}
+
+.previous-lists-page__header h1 {
+ margin: 0 0 8px;
+ font-family: var(--font-ui);
+ font-size: clamp(2rem, 4vw, 3rem);
+ line-height: 1.1;
+ color: var(--text-primary);
+}
+
+.previous-lists-page__header h1 em {
+ font-family: var(--font-accent);
+ font-weight: 400;
+ color: var(--text-brand);
+}
+
+.previous-lists-page__header p {
+ margin: 0;
+ font-size: 0.95rem;
+ color: var(--text-secondary);
+}
+
+/* Søk og sortering */
+
+.previous-lists-page__controls {
+ display: flex;
+ justify-content: space-between;
+ align-items: end;
+ gap: 1rem;
+ margin-bottom: 1.5rem;
+}
+
+.previous-lists-page__search,
+.previous-lists-page__sort {
+ display: flex;
+ flex-direction: column;
+ gap: 6px;
+ font: 11px var(--font-mono);
+ color: var(--text-muted);
+}
+
+.previous-lists-page__search input,
+.previous-lists-page__sort select {
+ padding: 10px 14px;
+ font: 13px var(--font-ui);
+ color: var(--text-primary);
+ background: var(--bg-surface);
+ border: 1px solid var(--border-hairline);
+ border-radius: 6px;
+}
+
+.previous-lists-page__search input {
+ width: min(100%, 320px);
+}
+
+/* Historikkramme */
+
+.previous-lists-page__history {
+ border: 1px solid var(--border-brand);
+ border-radius: 10px;
+ padding: 20px 24px;
+ background: var(--bg-surface);
+ box-shadow: 3px 3px 0 var(--note-tint);
+}
+
+.previous-lists-page__month + .previous-lists-page__month {
+ margin-top: 2rem;
+}
+
+.previous-lists-page__month-heading {
+ display: flex;
+ justify-content: space-between;
+ align-items: baseline;
+ gap: 1rem;
+ padding-bottom: 10px;
+ border-bottom: 1px solid var(--border-strong);
+}
+
+.previous-lists-page__month-heading h2 {
+ margin: 0;
+ font: italic 1.7rem var(--font-accent);
+ color: var(--text-brand);
+ text-transform: capitalize;
+}
+
+.previous-lists-page__month-heading span {
+ font: 10px var(--font-mono);
+ text-transform: uppercase;
+ color: var(--text-muted);
+}
+
+
+.previous-list-row {
+ display: grid;
+ grid-template-columns: minmax(0, 1fr) 125px;
+ align-items: center;
+ gap: 1rem;
+ padding: 14px 0;
+ border-bottom: 1px solid var(--border-hairline);
+}
+
+/* Klikkbart område for å åpne en tidligere handleliste */
+.previous-list-row__link {
+ display: grid;
+ grid-template-columns: 65px minmax(0, 1fr) 180px 110px;
+ align-items: center;
+ gap: 1rem;
+ min-width: 0;
+ text-decoration: none;
+ border-radius: 4px;
+}
+
+.previous-list-row__link:hover .previous-list-row__name {
+ color: var(--text-brand);
+ text-decoration: underline;
+}
+
+.previous-list-row__link:focus-visible {
+ outline: 2px solid var(--text-accent);
+ outline-offset: 3px;
+}
+
+.previous-list-row__date {
+ font: 500 11px var(--font-mono);
+ color: var(--text-accent);
+}
+
+.previous-list-row__name {
+ font: 500 14px var(--font-ui);
+ color: var(--text-primary);
+ overflow-wrap: anywhere;
+}
+
+.previous-list-row__count {
+ font: 10px var(--font-mono);
+ color: var(--text-muted);
+}
+
+.previous-list-row__price {
+ font: 500 14px var(--font-ui);
+ color: var(--text-primary);
+ text-align: right;
+ white-space: nowrap;
+}
+
+.previous-list-row__reuse {
+ padding: 8px 10px;
+ border: 1px solid var(--border-hairline);
+ border-radius: 6px;
+ font: 12px var(--font-ui);
+ background: var(--bg-surface);
+ color: var(--text-primary);
+ cursor: pointer;
+}
+
+.previous-list-row__reuse:hover:not(:disabled) {
+ border-color: var(--border-brand);
+}
+
+.previous-list-row__reuse:disabled {
+ opacity: 0.5;
+ cursor: not-allowed;
+}
+
+/* Tom tilstand og last inn flere */
+
+.previous-lists-page__empty {
+ padding: 3rem 1rem;
+ text-align: center;
+ color: var(--text-secondary);
+}
+
+.previous-lists-page__load-more {
+ display: block;
+ margin: 2rem 0 0 auto;
+ padding: 10px 16px;
+ border: 1px solid var(--border-hairline);
+ border-radius: 6px;
+ background: var(--bg-surface);
+ color: var(--text-primary);
+ font: 13px var(--font-ui);
+ cursor: pointer;
+}
+
+.previous-lists-page__load-more:hover {
+ border-color: var(--border-brand);
+}
+
+/* Tastaturnavigasjon */
+
+.previous-lists-page input:focus-visible,
+.previous-lists-page select:focus-visible,
+.previous-lists-page button:focus-visible {
+ outline: 2px solid var(--text-accent);
+ outline-offset: 3px;
+}
+
+/* Mobil og nettbrett */
+
+
+/* Mobil og nettbrett */
+
+@media (max-width: 900px) {
+ .previous-list-row {
+ grid-template-columns: minmax(0, 1fr) auto;
+ gap: 10px;
+ }
+
+ .previous-list-row__link {
+ grid-template-columns: 55px minmax(0, 1fr) auto;
+ gap: 8px 10px;
+ }
+
+ .previous-list-row__date {
+ grid-column: 1;
+ grid-row: 1;
+ }
+
+ .previous-list-row__name {
+ grid-column: 2;
+ grid-row: 1;
+ }
+
+ .previous-list-row__count {
+ grid-column: 2;
+ grid-row: 2;
+ }
+
+ .previous-list-row__price {
+ grid-column: 3;
+ grid-row: 1;
+ }
+
+ .previous-list-row__reuse {
+ grid-column: 2;
+ grid-row: 1;
+ }
+}
+
+@media (max-width: 600px) {
+ .previous-lists-page {
+ width: calc(100% - 1.5rem);
+ padding: 2rem 0 3rem;
+ }
+
+ .previous-lists-page__controls {
+ align-items: stretch;
+ flex-direction: column;
+ }
+
+ .previous-lists-page__search input {
+ width: 100%;
+ box-sizing: border-box;
+ }
+
+ .previous-lists-page__history {
+ padding: 16px;
+ }
+
+ .previous-list-row {
+ grid-template-columns: minmax(0, 1fr);
+ gap: 10px;
+ }
+
+ .previous-list-row__link {
+ grid-template-columns: minmax(0, 1fr) auto;
+ gap: 6px 12px;
+ }
+
+ .previous-list-row__date {
+ grid-column: 1;
+ grid-row: 1;
+ }
+
+ .previous-list-row__name {
+ grid-column: 1;
+ grid-row: 2;
+ }
+
+ .previous-list-row__count {
+ grid-column: 1;
+ grid-row: 3;
+ }
+
+ .previous-list-row__price {
+ grid-column: 2;
+ grid-row: 2;
+ }
+
+ .previous-list-row__reuse {
+ grid-column: 1;
+ grid-row: 2;
+ justify-self: end;
+ }
+}
+
+.previous-lists-page__pagination {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 14px;
+ margin-top: 2rem;
+}
+
+.previous-lists-page__pagination-info {
+ margin: 0;
+ font-family: var(--font-mono);
+ font-size: 12px;
+ color: var(--text-muted);
+}
+
+.previous-lists-page__pagination-buttons {
+ display: flex;
+ justify-content: center;
+ flex-wrap: wrap;
+ gap: 12px;
+}
+
+.previous-lists-page__pagination-buttons .previous-lists-page__load-more {
+ margin: 0;
+}
\ No newline at end of file
diff --git a/frontend/src/pages/shoppingLists/PreviousListsPage.tsx b/frontend/src/pages/shoppingLists/PreviousListsPage.tsx
new file mode 100644
index 0000000..d59aa88
--- /dev/null
+++ b/frontend/src/pages/shoppingLists/PreviousListsPage.tsx
@@ -0,0 +1,252 @@
+import { Link } from "react-router";
+import { useEffect, useMemo, useState } from "react";
+import PreviousListRow from "./PreviousListRow";
+import type { ShoppingList } from "./shoppingListTypes";
+import "./PreviousListsPage.css";
+import "./ShoppingListsPage.css";
+import { mockLists } from "./shoppingListMockData";
+
+
+
+const monthFormatter = new Intl.DateTimeFormat("nb-NO", {
+ month: "long",
+ year: "numeric",
+ timeZone: "UTC",
+});
+
+function getMonthKey(date: string) {
+ return date.slice(0, 7);
+}
+
+function formatMonth(monthKey: string) {
+ return monthFormatter.format(new Date(`${monthKey}-01T12:00:00Z`));
+}
+
+const PAGE_SIZE = 5;
+
+const STORAGE_KEY = "previous-lists-preferences";
+
+type SortOrder = "newest" | "oldest";
+
+interface PreviousListsPreferences {
+ search: string;
+ sortOrder: SortOrder;
+ visibleCount: number;
+}
+
+function loadPreferences(): PreviousListsPreferences {
+ try {
+ const saved = localStorage.getItem(STORAGE_KEY);
+
+ if (saved) {
+ const parsed = JSON.parse(saved);
+
+ return {
+ search: typeof parsed.search === "string" ? parsed.search : "",
+ sortOrder:
+ parsed.sortOrder === "oldest" ? "oldest" : "newest",
+ visibleCount:
+ typeof parsed.visibleCount === "number" &&
+ Number.isInteger(parsed.visibleCount) &&
+ parsed.visibleCount >= PAGE_SIZE
+ ? parsed.visibleCount
+ : PAGE_SIZE,
+ };
+ }
+ } catch {
+ // Bruk standardverdier dersom lagrede data er ugyldige.
+ }
+
+ return {
+ search: "",
+ sortOrder: "newest" as SortOrder,
+ visibleCount: PAGE_SIZE,
+ };
+}
+
+export default function PreviousListsPage() {
+ const [preferences, setPreferences] = useState(loadPreferences);
+
+ const { search, sortOrder, visibleCount } = preferences;
+
+ useEffect(() => {
+ try {
+ localStorage.setItem(STORAGE_KEY, JSON.stringify(preferences));
+ } catch {
+ // Siden fungerer fortsatt hvis lagring ikke er tilgjengelig.
+ }
+}, [preferences]);
+
+ const filteredLists = useMemo(() => {
+ return mockLists
+ .filter(
+ (list) =>
+ list.status === "COMPLETED" &&
+ list.name.toLocaleLowerCase("nb-NO").includes(
+ search.trim().toLocaleLowerCase("nb-NO")
+ )
+ )
+ .sort((a, b) =>
+ sortOrder === "newest"
+ ? b.createdAt.localeCompare(a.createdAt)
+ : a.createdAt.localeCompare(b.createdAt)
+ );
+ }, [search, sortOrder]);
+
+ const visibleLists = filteredLists.slice(0, visibleCount);
+
+ const groupedLists = visibleLists.reduce(
+ (groups, list) => {
+ const month = getMonthKey(list.createdAt);
+
+ if (!groups.has(month)) {
+ groups.set(month, []);
+ }
+
+ groups.get(month)!.push(list);
+
+ return groups;
+ },
+ new Map()
+ );
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+ {visibleLists.length === 0 ? (
+
+ Ingen tidligere handlelister funnet.
+
+ ) : (
+
+ {Array.from(groupedLists.entries()).map(([month, lists]) => (
+
+
+
{formatMonth(month)}
+ {lists?.length ?? 0} viste lister
+
+
+ {lists?.map((list) => (
+
+ ))}
+
+ ))}
+
+ )}
+
+
+
+ {visibleLists.length} av {filteredLists.length} handlelister vist
+
+
+
+ {visibleCount > PAGE_SIZE && (
+
+ )}
+
+ {visibleCount < filteredLists.length && (
+
+ )}
+
+
+
+
+ );
+}
diff --git a/frontend/src/pages/shoppingLists/ShoppingListCard.tsx b/frontend/src/pages/shoppingLists/ShoppingListCard.tsx
index 99935e0..cd68215 100644
--- a/frontend/src/pages/shoppingLists/ShoppingListCard.tsx
+++ b/frontend/src/pages/shoppingLists/ShoppingListCard.tsx
@@ -72,12 +72,12 @@ export default function ShoppingListCard({
)}
-
-
- {productCount} PRODUKTER · {itemCount} VARER
-
- {formattedPrice}
-
+
+
+ {productCount} PRODUKTER · {itemCount} VARER
+
+ {formattedPrice}
+
);
diff --git a/frontend/src/pages/shoppingLists/ShoppingListsPage.css b/frontend/src/pages/shoppingLists/ShoppingListsPage.css
index fd9ef6a..ff3ab31 100644
--- a/frontend/src/pages/shoppingLists/ShoppingListsPage.css
+++ b/frontend/src/pages/shoppingLists/ShoppingListsPage.css
@@ -181,12 +181,6 @@
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;
@@ -329,6 +323,35 @@
transform: translateY(-4px) rotate(0deg);
}
+
+/* Faner mellom aktive og tidligere handlelister */
+.shopping-lists-page__tabs {
+ width: fit-content;
+ flex-shrink: 0;
+}
+
+.shopping-lists-page__tab {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ text-decoration: none;
+ color: var(--text-muted);
+}
+
+.shopping-lists-page__tab--active {
+ color: var(--text-brand);
+}
+
+.shopping-lists-page__tab:hover {
+ color: var(--text-accent);
+}
+
+.shopping-lists-page__tab:focus-visible {
+ outline: 2px solid var(--text-accent);
+ outline-offset: 3px;
+}
+
+/* Sortering av aktive handlelister */
.shopping-lists-page__sort {
display: flex;
flex-direction: column;
@@ -352,4 +375,4 @@
.shopping-lists-page__sort select:focus-visible {
outline: 2px solid var(--text-accent);
outline-offset: 3px;
-}
\ No newline at end of file
+}
diff --git a/frontend/src/pages/shoppingLists/ShoppingListsPage.tsx b/frontend/src/pages/shoppingLists/ShoppingListsPage.tsx
index 057fa0a..1243b31 100644
--- a/frontend/src/pages/shoppingLists/ShoppingListsPage.tsx
+++ b/frontend/src/pages/shoppingLists/ShoppingListsPage.tsx
@@ -1,5 +1,5 @@
import { useEffect, useState } from "react";
-import { useNavigate } from "react-router";
+import { Link, useNavigate } from "react-router";
import ShoppingListCard from "./ShoppingListCard";
import { mockLists } from "./shoppingListMockData";
import "./ShoppingListsPage.css";
@@ -82,6 +82,7 @@ export default function ShoppingListsPage() {
+
+