From 47615ddabeb1ea22c622e48f077c7aa2b1dcabaf Mon Sep 17 00:00:00 2001 From: Esa Kataja Date: Sat, 5 Sep 2026 22:15:54 +0300 Subject: [PATCH] Give the catalog a structure The page had no top-level headings at all: the mains simply began with "Liha", so the first thing on screen was a category with nothing saying what it belonged to. Both halves now carry a heading and a count. "Lisukkeet" was also styled exactly like "Liha", which made a category look like the peer of the entire side-dish list. There are two levels here, so they no longer look alike: section titles are set large with a rule under them, categories stay small and muted beneath. The add and edit forms collapse. They were two permanently open cards, so the page opened on a screenful of empty form before any content appeared. They now open on demand, and force themselves open when editing or after a rejected submission, which is when the form is the thing that matters. Search moves above them, since finding something is the usual reason to be here. The forms stay outside the Datastar-patched fragment on purpose: inside it, typing in the search box would collapse a form halfway through an edit. --- cmd/foodster/static/app.css | 41 +++++++++++- cmd/foodster/views.templ | 121 +++++++++++++++++++++--------------- 2 files changed, 109 insertions(+), 53 deletions(-) diff --git a/cmd/foodster/static/app.css b/cmd/foodster/static/app.css index 4fd1cf7..4990e15 100644 --- a/cmd/foodster/static/app.css +++ b/cmd/foodster/static/app.css @@ -423,15 +423,52 @@ html[data-theme="light"] .themetoggle .i-moon { display: none; } cursor: pointer; } -/* Catalog rows */ +/* Catalog structure: Pääruuat and Lisukkeet are the two halves of the + catalog, the categories are subdivisions of the first. Two levels, so they + must not look alike. */ +.section + .section { margin-top: 34px; } +.sectiontitle { + display: flex; + align-items: center; + gap: 9px; + margin: 0 0 4px; + padding-bottom: 8px; + border-bottom: 2px solid var(--ink); + font-size: 18px; + font-weight: 700; + letter-spacing: -0.03em; +} +.sectiontitle .count { + padding: 2px 8px; + border-radius: 999px; + background: var(--sunk); + color: var(--muted); + font-size: 12px; + font-weight: 600; + letter-spacing: 0; +} + .sechead { - margin: 26px 0 6px; + margin: 20px 0 2px; font-size: 11px; font-weight: 600; letter-spacing: 0.09em; text-transform: uppercase; color: var(--muted); } + +/* Collapsed add/edit forms, so the page opens on the catalog. */ +.addform > summary { + cursor: pointer; + font-weight: 600; + font-size: 15px; + min-height: 24px; +} +.addform[open] > summary { + margin-bottom: 14px; + padding-bottom: 10px; + border-bottom: 1px solid var(--line); +} .row { display: flex; align-items: center; diff --git a/cmd/foodster/views.templ b/cmd/foodster/views.templ index 2e46d0e..5be62d6 100644 --- a/cmd/foodster/views.templ +++ b/cmd/foodster/views.templ @@ -531,8 +531,6 @@ templ catalogPage(v catalogView) { if v.Report != nil { @importReport(v.Report) } - @mainForm_(v.Main) - @sideForm_(v.Side)
+ // The add and edit forms stay outside the patched fragment, or + // typing in the search box would collapse a form mid-edit. + @mainForm_(v.Main) + @sideForm_(v.Side) @catalogList(v)
@@ -557,55 +559,69 @@ templ catalogPage(v catalogView) { } // catalogList carries the id Datastar patches, so typing in the search box -// swaps the lists without reloading the forms above them. +// swaps the lists without touching the forms above them. +// +// Two levels of heading, because there are two: Pääruuat and Lisukkeet are +// the halves of the catalog, and the categories are subdivisions of the +// first. They were previously styled the same, which made a category look +// like a peer of the entire side-dish list. templ catalogList(v catalogView) {
- if v.Mains == 0 { -

Pääruuat

-

- if v.Search == "" { - Ei vielä pääruokia. - } else { - Ei osumia. - } -

- } - // Grouped by category, alphabetical inside. The catalog is a list you - // manage, so a predictable position beats a useful one. - for _, g := range v.Groups { -

{ g.Label }

- for _, d := range g.Dishes { -
- @categoryIcon(d.CategoryKey()) -
-
{ d.Name }
- if !d.HasSides { -
Ei lisukkeita
- } +
+ @sectionTitle("Pääruuat", v.Mains) + if v.Mains == 0 { + @emptyNote(v.Search) + } + // Grouped by category, alphabetical inside. The catalog is a list + // you manage, so a predictable position beats a useful one. + for _, g := range v.Groups { +

{ g.Label }

+ for _, d := range g.Dishes { +
+ @categoryIcon(d.CategoryKey()) +
+
{ d.Name }
+ if !d.HasSides { +
Ei lisukkeita
+ } +
+ @rowActions(v, "/ruuat?muokkaa="+strconv.FormatInt(d.ID, 10), d.ID, "paa")
- @rowActions(v, "/ruuat?muokkaa="+strconv.FormatInt(d.ID, 10), d.ID, "paa") + } + } +
+
+ @sectionTitle("Lisukkeet", len(v.Sides)) + if len(v.Sides) == 0 { + @emptyNote(v.Search) + } + for _, s := range v.Sides { +
+
{ s.Name }
+ @rowActions(v, "/ruuat?muokkaa-lisuke="+strconv.FormatInt(s.ID, 10), s.ID, "lisuke")
} - } -

Lisukkeet

- if len(v.Sides) == 0 { -

- if v.Search == "" { - Ei vielä lisukkeita. - } else { - Ei osumia. - } -

- } - for _, s := range v.Sides { -
-
{ s.Name }
- @rowActions(v, "/ruuat?muokkaa-lisuke="+strconv.FormatInt(s.ID, 10), s.ID, "lisuke") -
- } +
} +templ sectionTitle(label string, n int) { +

+ { label } + { strconv.Itoa(n) } +

+} + +templ emptyNote(search string) { +

+ if search == "" { + Ei vielä mitään. + } else { + Ei osumia haulle { search }. + } +

+} + // rowActions is a pencil and a bin, until the bin is tapped: then the row // asks. An icon is a smaller target to hit by accident than a word, and the // dish disappears from every picker the moment it goes. @@ -651,15 +667,18 @@ templ iconTrash() { } +// Collapsed by default so the page opens on the catalog rather than on two +// screens of empty form. Forced open when editing or after a rejected +// submission, since the form is then the thing that needs attention. templ mainForm_(f mainForm) { -
-

+
+ if f.ID == 0 { Lisää pääruoka } else { Muokkaa pääruokaa } -

+ if f.Err != "" {

{ f.Err }

} @@ -693,7 +712,7 @@ templ mainForm_(f mainForm) { if f.ID != 0 { Peruuta } -
+
} templ categoryChip(value, label string, f mainForm) { @@ -709,14 +728,14 @@ templ categoryChip(value, label string, f mainForm) { } templ sideForm_(f sideForm) { -
-

+
+ if f.ID == 0 { Lisää lisuke } else { Muokkaa lisuketta } -

+ if f.Err != "" {

{ f.Err }

} @@ -733,7 +752,7 @@ templ sideForm_(f sideForm) { if f.ID != 0 { Peruuta } -
+ } templ importForm() {