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
106 changes: 106 additions & 0 deletions frontend/src/pages/shoppingLists/CompletedListView.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
/* Visning for en handlet liste – fra Figma (Previous list).
Notatblokken, oppsummeringen og oppsettet kommer fra ShoppingListDetailPage.css. */

.completed-list__paper {
display: flex;
flex-direction: column;
gap: 18px;
padding-bottom: 34px;
}

.completed-list__header {
display: flex;
justify-content: space-between;
align-items: flex-start;
gap: 16px;
}

/* Skjevt «HANDLET»-stempel */
.completed-list__stamp {
margin: 8px 24px 0 0;
padding: 6px 14px;
border: 2px solid var(--annotation);
border-radius: 4px;
font: 500 16px var(--font-mono);
letter-spacing: 0.24em;
color: var(--annotation);
transform: rotate(-8deg);
}

/* Varene i to kolonner */
.completed-list__columns {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 36px;
}

.completed-list__column {
margin: 0;
padding: 0;
list-style: none;
}

.completed-row {
display: flex;
align-items: center;
gap: 10px;
padding: 8px 0;
border-bottom: 1px solid var(--note-rule);
}

.completed-row__check {
flex-shrink: 0;
color: var(--text-accent);
}

.completed-row__name {
flex: 1;
min-width: 0;
font: 400 22px/28px var(--font-note);
color: var(--note-ink);
}

.completed-row__price {
white-space: nowrap;
font: 500 10px var(--font-mono);
letter-spacing: 0.06em;
color: var(--text-secondary);
}

/* «Slett lista» ser ut som en lenke, men er en knapp */
.completed-list__delete {
align-self: flex-start;
padding: 0;
border: none;
background: none;
font: 500 10px var(--font-mono);
letter-spacing: 0.06em;
color: var(--text-accent);
cursor: pointer;
}

.completed-list__delete:hover {
text-decoration: underline;
}

.completed-list__delete:focus-visible {
outline: 2px solid var(--text-accent);
outline-offset: 2px;
}

/* Mobil: én kolonne, mindre stempel */
@media (max-width: 600px) {
.completed-list__columns {
grid-template-columns: 1fr;
gap: 0;
}

.completed-list__stamp {
margin-right: 0;
font-size: 12px;
}

.completed-row__name {
font-size: 19px;
}
}
202 changes: 202 additions & 0 deletions frontend/src/pages/shoppingLists/CompletedListView.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
import { Link } from "react-router";
import type { ShoppingList } from "./shoppingListTypes";
import "./CompletedListView.css";
import "../../components/button/Button.css";

type CompletedListViewProps = {
list: ShoppingList;
};

// Gir f.eks. "47,80"
function formatPrice(value: number) {
return value.toFixed(2).replace(".", ",");
}

// Gir f.eks. "18.09.2026"
function formatShortDate(isoDate: string) {
return new Date(isoDate).toLocaleDateString("nb-NO", {
day: "2-digit",
month: "2-digit",
year: "numeric",
});
}

// Gir f.eks. "FREDAG 18. SEPTEMBER 2026"
function formatLongDate(isoDate: string) {
return new Date(isoDate)
.toLocaleDateString("nb-NO", {
weekday: "long",
day: "numeric",
month: "long",
year: "numeric",
})
.toUpperCase();
}

// Liten hake. Bruker currentColor, så fargen følger temaet.
function CheckIcon() {
return (
<svg
className="completed-row__check"
width="16"
height="16"
viewBox="0 0 16 16"
aria-hidden="true"
>
<path
d="M2.5 8.5 6 12l7.5-8"
fill="none"
stroke="currentColor"
strokeWidth="1.8"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
}

function CompletedListView({ list }: CompletedListViewProps) {
const completedAt = list.completedAt ?? list.createdAt;
const total =
list.estimatedTotal ??
list.items.reduce(
(sum, item) => sum + (item.unitPrice ?? 0) * item.quantity,
0,
);

// Deler varene i to kolonner: første halvdel til venstre, resten til høyre
const half = Math.ceil(list.items.length / 2);
const columns = [list.items.slice(0, half), list.items.slice(half)];

return (
<main className="shopping-list-page">
<div className="shopping-list-page__top">
{/* Oversikten over tidligere lister kobles av Alexis */}
<Link to="/lists" className="shopping-list-page__back">
← TIDLIGERE HANDLELISTER
</Link>
<p className="shopping-list-page__fig">FIG. 11 — HANDLET LISTE</p>
</div>

<div className="shopping-list-page__layout">
<div className="notepad">
<div
className="notepad__sheet notepad__sheet--back"
aria-hidden="true"
/>
<div
className="notepad__sheet notepad__sheet--middle"
aria-hidden="true"
/>

<article className="notepad__pad" aria-labelledby="list-title">
<div className="notepad__bar">
<span>HANDLET · LISTE NR. {list.id}</span>
<span>{formatShortDate(completedAt)}</span>
</div>

<div className="notepad__paper completed-list__paper">
<header className="completed-list__header">
<div>
<h1 id="list-title" className="notepad__title">
{list.name}
</h1>
<p className="notepad__meta">
HANDLET {formatLongDate(completedAt)} · {list.items.length}{" "}
VARER
</p>
</div>

<p className="completed-list__stamp">HANDLET</p>
</header>

<div className="completed-list__columns">
{columns.map((column, index) => (
<ul key={index} className="completed-list__column">
{column.map((item) => (
<li key={item.id} className="completed-row">
<CheckIcon />
<span className="completed-row__name">
{item.name}
{item.quantity > 1 && ` ×${item.quantity}`}
</span>
<span className="completed-row__price">
{item.unitPrice == null
? "—"
: `${formatPrice(item.unitPrice * item.quantity)} KR`}
</span>
</li>
))}
</ul>
))}
</div>
</div>
</article>
</div>

<aside className="list-summary" aria-labelledby="about-title">
<h2 id="about-title" className="list-summary__label">
OM LISTA
</h2>

<dl className="list-summary__facts">
<div>
<dt>HANDLET</dt>
<span aria-hidden="true" />
<dd>{formatShortDate(completedAt)}</dd>
</div>
<div>
<dt>VARER</dt>
<span aria-hidden="true" />
<dd>{list.items.length}</dd>
</div>
<div>
<dt>BUTIKK</dt>
<span aria-hidden="true" />
<dd>{list.store?.toUpperCase() ?? "—"}</dd>
</div>
</dl>

<div className="list-summary__total">
<p className="list-summary__label list-summary__label--muted">
TOTALT
</p>
<p className="list-summary__sum">
{formatPrice(total)} <em>kr</em>
</p>
</div>

{/* Knappene kobles til backend senere (#63) */}
<button
type="button"
className="button button--primary button--l list-summary__button"
>
<span aria-hidden="true">↻</span> Bruk på nytt
</button>
<p className="list-summary__info">
Lager en ny aktiv lapp med de samme varene. Prisene oppdateres til
dagens priser.
</p>

<button
type="button"
className="button button--secondary button--l list-summary__button"
>
Legg varer på aktiv lapp
</button>

<button type="button" className="completed-list__delete">
SLETT LISTA
</button>
</aside>
</div>

<footer className="shopping-list-page__footer">
<p>HANDLELAPPEN — IT2810 · GRUPPE 27</p>
<p>SIDE 11 / 11</p>
</footer>
</main>
);
}

export default CompletedListView;
5 changes: 5 additions & 0 deletions frontend/src/pages/shoppingLists/ShoppingListDetailPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import "../../components/shoppingListRow/ShoppingListRow.css";
import "../../components/checkbox/Checkbox.css";
import "../../components/quantityStepper/QuantityStepper.css";
import "../../components/button/Button.css";
import CompletedListView from "./CompletedListView";

type SortOrder = "added" | "alphabetical";

Expand Down Expand Up @@ -97,6 +98,10 @@ function ShoppingListDetailPage() {
</main>
);
}
// Handlede lister vises uten redigering
if (list.status === "COMPLETED") {
return <CompletedListView list={list} />;
}

return (
<main className="shopping-list-page">
Expand Down
Loading