From 7b190e3d2fb83028d75b1969ebc3b3c6dc974383 Mon Sep 17 00:00:00 2001 From: Priec Date: Sat, 15 Aug 2026 14:58:36 +0200 Subject: [PATCH] add column and alias are separate --- web/locales/cs/main.ftl | 17 +-- web/locales/en/main.ftl | 17 +-- web/locales/sk/main.ftl | 17 +-- web/src/lib.rs | 9 +- web/src/pages/admin/admin/ui.rs | 2 +- .../pages/admin/table_definition/loader.rs | 2 +- web/src/pages/admin/table_definition/logic.rs | 55 ++++++---- web/src/pages/admin/table_definition/mod.rs | 19 ++-- web/src/pages/admin/table_definition/state.rs | 2 +- web/src/pages/admin/table_definition/ui.rs | 100 +++++++++++++----- .../pages/admin/admin/workspace.html | 2 +- .../admin/table_definition/add_columns.html | 18 ++++ .../table_definition/add_columns_panel.html | 17 +++ .../admin/table_definition/column_panel.html | 8 +- .../pages/admin/table_definition/columns.html | 23 ---- .../pages/admin/table_definition/context.html | 18 ++-- .../admin/table_definition/presentation.html | 14 +++ ...mns_panel.html => presentation_panel.html} | 24 +---- 18 files changed, 222 insertions(+), 142 deletions(-) create mode 100644 web/templates/pages/admin/table_definition/add_columns.html create mode 100644 web/templates/pages/admin/table_definition/add_columns_panel.html delete mode 100644 web/templates/pages/admin/table_definition/columns.html create mode 100644 web/templates/pages/admin/table_definition/presentation.html rename web/templates/pages/admin/table_definition/{columns_panel.html => presentation_panel.html} (77%) diff --git a/web/locales/cs/main.ftl b/web/locales/cs/main.ftl index f13c8edd..8a7531c5 100644 --- a/web/locales/cs/main.ftl +++ b/web/locales/cs/main.ftl @@ -133,7 +133,7 @@ admin-no-profile-tables = Tento profil nemá vlastní tabulky. admin-no-dependencies = Bez závislostí admin-depends-on-prefix = Závisí na admin-row-display-prefix = zobrazení: -admin-add-rename-columns = Přidat / přejmenovat sloupce +admin-column-presentation = Aliasy sloupců admin-delete-table = Smazat tabulku admin-who-may-use-it = Kdo ji může používat admin-pick-table-hint = Vyberte tabulku, abyste do ní přidali sloupce, smazali ji nebo určili, kdo ji může používat. @@ -156,16 +156,17 @@ admin-select-table-inspect = Vyberte tabulku, abyste zobrazili její sloupce. admin-no-visible-columns = Tato tabulka nemá žádné viditelné sloupce. admin-system-columns-label = Systémové sloupce · skryté před uživateli -# --- Stránky definice tabulky (sloupce / smazat / kopírovat / šablona / historie) -td-columns-title = Sloupce +# --- Stránky definice tabulky (přidat sloupce / zobrazení / smazat / kopírovat / šablona / historie) +td-add-columns-title = Přidat sloupce +td-presentation-title = Zobrazení sloupců td-add-columns-to = Přidat sloupce do td-append-hint = Sloupce se přidávají na konec. Nic, co už existuje, se nemění a nové sloupce lze indexovat hned při přidání. -td-rename-column = Přejmenovat sloupec +td-rename-column = Aliasy a pořadí sloupců td-rename-hint = Přejmenovává se jen zobrazený název, ne fyzický sloupec pod ním, takže uložená data a skripty zůstávají nedotčené. Možné jen dokud tabulka nemá žádné řádky. td-column-label = Sloupec td-choose-column = Vyberte sloupec td-new-name = Nový název -td-rename-button = Přejmenovat sloupec +td-rename-button = Uložit zobrazení td-columns-now = Současné sloupce td-col-column = Sloupec td-col-type = Typ @@ -234,8 +235,10 @@ td-depends-on-prefix = závisí na td-acts-on-profile = Vše na této stránce se týká tohoto profilu. td-global-label = Globální — všechny profily td-tab-aria = akce s tabulkou -td-tab-columns = Sloupce -td-tab-columns-sub = Přidat sloupce a přejmenovat je +td-tab-add-columns = Přidat sloupce +td-tab-add-columns-sub = Připojit nové sloupce +td-tab-presentation = Zobrazení +td-tab-presentation-sub = Změnit aliasy a pořadí td-tab-delete = Smazat td-tab-delete-sub = Odebrat tuto tabulku td-tab-access = Přístup diff --git a/web/locales/en/main.ftl b/web/locales/en/main.ftl index 4fab6857..91a42c10 100644 --- a/web/locales/en/main.ftl +++ b/web/locales/en/main.ftl @@ -137,7 +137,7 @@ admin-no-profile-tables = This profile has no tables of its own. admin-no-dependencies = No dependencies admin-depends-on-prefix = Depends on admin-row-display-prefix = display: -admin-add-rename-columns = Add / rename columns +admin-column-presentation = Column aliases admin-delete-table = Delete table admin-who-may-use-it = Who may use it admin-pick-table-hint = Pick a table to add columns to it, delete it, or say who may use it. @@ -158,16 +158,17 @@ admin-select-table-inspect = Select a table to inspect its columns. admin-no-visible-columns = This table has no visible columns. admin-system-columns-label = System columns · hidden from users -# --- Table definition pages (columns / delete / copy / template / history) - -td-columns-title = Columns +# --- Table definition pages (add columns / presentation / delete / copy / template / history) - +td-add-columns-title = Add columns +td-presentation-title = Column presentation td-add-columns-to = Add columns to td-append-hint = Columns are appended. Nothing that already exists is changed, and the new columns can be indexed as they are added. -td-rename-column = Rename a column +td-rename-column = Column aliases and order td-rename-hint = Renames what the column is called, not the physical column underneath, so stored data and scripts are untouched. Only possible while the table has no rows. td-column-label = Column td-choose-column = Choose a column td-new-name = New name -td-rename-button = Rename column +td-rename-button = Save presentation td-columns-now = Columns it has now td-col-column = Column td-col-type = Type @@ -234,8 +235,10 @@ td-depends-on-prefix = depends on td-acts-on-profile = Everything on this page acts on this profile. td-global-label = Global — all profiles td-tab-aria = table actions -td-tab-columns = Columns -td-tab-columns-sub = Append columns and rename them +td-tab-add-columns = Add columns +td-tab-add-columns-sub = Append new columns +td-tab-presentation = Presentation +td-tab-presentation-sub = Change aliases and order td-tab-delete = Delete td-tab-delete-sub = Drop this table td-tab-access = Access diff --git a/web/locales/sk/main.ftl b/web/locales/sk/main.ftl index 89b6f5be..e2a2b20c 100644 --- a/web/locales/sk/main.ftl +++ b/web/locales/sk/main.ftl @@ -133,7 +133,7 @@ admin-no-profile-tables = Tento profil nemá vlastné tabuľky. admin-no-dependencies = Bez závislostí admin-depends-on-prefix = Závisí od admin-row-display-prefix = zobrazenie: -admin-add-rename-columns = Pridať / premenovať stĺpce +admin-column-presentation = Aliasy stĺpcov admin-delete-table = Vymazať tabuľku admin-who-may-use-it = Kto ju môže používať admin-pick-table-hint = Vyberte tabuľku, aby ste do nej pridali stĺpce, vymazali ju alebo určili, kto ju môže používať. @@ -156,16 +156,17 @@ admin-select-table-inspect = Vyberte tabuľku, aby ste zobrazili jej stĺpce. admin-no-visible-columns = Táto tabuľka nemá žiadne viditeľné stĺpce. admin-system-columns-label = Systémové stĺpce · skryté pred používateľmi -# --- Stránky definície tabuľky (stĺpce / vymazať / kopírovať / šablóna / história) -td-columns-title = Stĺpce +# --- Stránky definície tabuľky (pridať stĺpce / zobrazenie / vymazať / kopírovať / šablóna / história) +td-add-columns-title = Pridať stĺpce +td-presentation-title = Zobrazenie stĺpcov td-add-columns-to = Pridať stĺpce do td-append-hint = Stĺpce sa pridávajú na koniec. Nič, čo už existuje, sa nemení a nové stĺpce možno indexovať hneď pri pridaní. -td-rename-column = Premenovať stĺpec +td-rename-column = Aliasy a poradie stĺpcov td-rename-hint = Premenúva sa len zobrazený názov, nie fyzický stĺpec pod ním, takže uložené údaje a skripty zostávajú nedotknuté. Možné len kým tabuľka nemá žiadne riadky. td-column-label = Stĺpec td-choose-column = Vyberte stĺpec td-new-name = Nový názov -td-rename-button = Premenovať stĺpec +td-rename-button = Uložiť zobrazenie td-columns-now = Súčasné stĺpce td-col-column = Stĺpec td-col-type = Typ @@ -234,8 +235,10 @@ td-depends-on-prefix = závisí od td-acts-on-profile = Všetko na tejto stránke sa týka tohto profilu. td-global-label = Globálne — všetky profily td-tab-aria = akcie s tabuľkou -td-tab-columns = Stĺpce -td-tab-columns-sub = Pridať stĺpce a premenovať ich +td-tab-add-columns = Pridať stĺpce +td-tab-add-columns-sub = Pripojiť nové stĺpce +td-tab-presentation = Zobrazenie +td-tab-presentation-sub = Zmeniť aliasy a poradie td-tab-delete = Vymazať td-tab-delete-sub = Odstrániť túto tabuľku td-tab-access = Prístup diff --git a/web/src/lib.rs b/web/src/lib.rs index 26a4d808..1dc5791a 100644 --- a/web/src/lib.rs +++ b/web/src/lib.rs @@ -301,7 +301,8 @@ mod tests { #[tokio::test] async fn the_table_definition_pages_are_mounted_and_need_a_session() { for path in [ - "/admin/tables/columns", + "/admin/tables/columns/add", + "/admin/tables/presentation", "/admin/tables/delete", "/admin/profiles/copy", "/admin/profiles/history", @@ -321,8 +322,8 @@ mod tests { assert_eq!(status, axum::http::StatusCode::PERMANENT_REDIRECT); for path in [ - "/admin/tables/columns", - "/admin/tables/columns/builder", + "/admin/tables/columns/add", + "/admin/tables/columns/add/builder", "/admin/tables/presentation", "/admin/tables/delete", "/admin/profiles/copy", @@ -452,7 +453,7 @@ mod tests { ("/admin/tables/delete", "profile=billing&table=invoice"), ("/admin/profiles/copy", "profile=billing&table=invoice"), ("/admin/tables/presentation", "profile=billing&table=invoice"), - ("/admin/tables/columns", "profile=billing&table=invoice"), + ("/admin/tables/columns/add", "profile=billing&table=invoice"), ("/admin/tables/builder", ""), ("/admin/tables", ""), ("/admin/logic", ""), diff --git a/web/src/pages/admin/admin/ui.rs b/web/src/pages/admin/admin/ui.rs index 3b50ee5f..ae20723b 100644 --- a/web/src/pages/admin/admin/ui.rs +++ b/web/src/pages/admin/admin/ui.rs @@ -156,7 +156,7 @@ mod tests { let html = render_workspace(&page); for route in [ - "/admin/tables/columns?profile=books&table=invoice", + "/admin/tables/presentation?profile=books&table=invoice", "/admin/tables/delete?profile=books&table=invoice", "/admin/tables/new?profile=books", "/admin/profiles/copy?profile=books", diff --git a/web/src/pages/admin/table_definition/loader.rs b/web/src/pages/admin/table_definition/loader.rs index 542cabb3..d8f885be 100644 --- a/web/src/pages/admin/table_definition/loader.rs +++ b/web/src/pages/admin/table_definition/loader.rs @@ -1,4 +1,4 @@ -//! Reads the context every one of the five pages shows. +//! Reads the context every table-definition page shows. //! //! One loader serves them all, because they all need the same thing: which //! table is being worked on, and what it currently is. Four calls, in this diff --git a/web/src/pages/admin/table_definition/logic.rs b/web/src/pages/admin/table_definition/logic.rs index 76fee28b..87685c31 100644 --- a/web/src/pages/admin/table_definition/logic.rs +++ b/web/src/pages/admin/table_definition/logic.rs @@ -1,4 +1,4 @@ -//! The request handlers behind the five table-definition pages. +//! The request handlers behind the six table-definition pages. //! //! Each page has a GET that renders it and, except for the read-only history, //! a POST per write it offers. A write answers with its own page's body, @@ -43,7 +43,8 @@ use super::{ /// which fragment a write on it swaps back. #[derive(Clone, Copy)] enum Page { - Columns, + AddColumns, + Presentation, Delete, Copy, Template, @@ -53,7 +54,8 @@ enum Page { impl Page { fn name(self) -> &'static str { match self { - Self::Columns => "columns", + Self::AddColumns => "add-columns", + Self::Presentation => "presentation", Self::Delete => "delete", Self::Copy => "copy", Self::Template => "template", @@ -63,7 +65,8 @@ impl Page { fn render_page(self, page: &TableDefinitionPageState) -> String { match self { - Self::Columns => ui::render_columns_page(page), + Self::AddColumns => ui::render_add_columns_page(page), + Self::Presentation => ui::render_presentation_page(page), Self::Delete => ui::render_delete_page(page), Self::Copy => ui::render_copy_page(page), Self::Template => ui::render_template_page(page), @@ -74,7 +77,8 @@ impl Page { /// The `#table-panel` swap. History has no writes, so it never asks. fn render_fragment(self, page: &TableDefinitionPageState) -> String { match self { - Self::Columns => ui::render_columns_fragment(page), + Self::AddColumns => ui::render_add_columns_fragment(page), + Self::Presentation => ui::render_presentation_fragment(page), Self::Delete => ui::render_delete_fragment(page), Self::Copy => ui::render_copy_fragment(page), Self::Template => ui::render_template_fragment(page), @@ -85,13 +89,22 @@ impl Page { // ── The pages ─────────────────────────────────────────────────────────────── -/// GET /admin/tables/columns -pub(crate) async fn columns_page( +/// GET /admin/tables/columns/add +pub(crate) async fn add_columns_page( State(state): State, headers: HeaderMap, Query(selection): Query, ) -> Response { - show(state, headers, PageInputs::for_selection(selection), Page::Columns).await + show(state, headers, PageInputs::for_selection(selection), Page::AddColumns).await +} + +/// GET /admin/tables/presentation +pub(crate) async fn presentation_page( + State(state): State, + headers: HeaderMap, + Query(selection): Query, +) -> Response { + show(state, headers, PageInputs::for_selection(selection), Page::Presentation).await } /// GET /admin/tables/delete @@ -151,7 +164,7 @@ async fn show(state: AppState, headers: HeaderMap, inputs: PageInputs, page: Pag // ── The writes ────────────────────────────────────────────────────────────── -/// POST /admin/tables/columns/builder — staging a column to append. +/// POST /admin/tables/columns/add/builder — staging a column to append. /// /// Nothing is written here; the panel is swapped back with the column added, /// removed, or its index toggled, exactly as the Add-table builder works. @@ -194,14 +207,14 @@ pub(crate) async fn update_columns( match load_page(state, &headers, inputs).await { Ok(mut loaded) => { - loaded.active = Page::Columns.name(); + loaded.active = Page::AddColumns.name(); Html(ui::render_column_panel(&loaded)).into_response() } Err(error) => load_error_response(&headers, error), } } -/// POST /admin/tables/columns — AddTableColumns. +/// POST /admin/tables/columns/add — AddTableColumns. pub(crate) async fn add_columns( State(state): State, headers: HeaderMap, @@ -232,7 +245,7 @@ pub(crate) async fn add_columns( state, headers, inputs, - Page::Columns, + Page::AddColumns, message, ) .await; @@ -246,7 +259,7 @@ pub(crate) async fn add_columns( state, headers, inputs, - Page::Columns, + Page::AddColumns, message, ) .await; @@ -254,7 +267,7 @@ pub(crate) async fn add_columns( // The same checks the server runs, applied to a draft that may have been // rebuilt from a posted form rather than through the panel. if let Err(message) = inputs.columns.validate(Locale::from_headers(&headers)) { - return refuse(state, headers, inputs, Page::Columns, message).await; + return refuse(state, headers, inputs, Page::AddColumns, message).await; } let request = AddTableColumnsRequest { @@ -283,7 +296,7 @@ pub(crate) async fn add_columns( )); // The columns are the table's now, so the panel starts empty. inputs.columns = crate::schema::ColumnDraft::for_append(catalog); - respond(state, headers, inputs, Page::Columns, StatusCode::OK).await + respond(state, headers, inputs, Page::AddColumns, StatusCode::OK).await } Ok(response) => { let message = response.into_inner().sql; @@ -295,10 +308,10 @@ pub(crate) async fn add_columns( } else { message }; - refuse(state, headers, inputs, Page::Columns, message).await + refuse(state, headers, inputs, Page::AddColumns, message).await } Err(error) => { - refuse(state, headers, inputs, Page::Columns, error.message().to_string()).await + refuse(state, headers, inputs, Page::AddColumns, error.message().to_string()).await } } } @@ -331,7 +344,7 @@ pub(crate) async fn set_column_presentation( state, headers, inputs, - Page::Columns, + Page::Presentation, message, ) .await; @@ -378,7 +391,7 @@ pub(crate) async fn set_column_presentation( table: form.table, ..Default::default() }; - respond(state, headers, inputs, Page::Columns, StatusCode::OK).await + respond(state, headers, inputs, Page::Presentation, StatusCode::OK).await } Ok(response) => { let message = response.into_inner().message; @@ -390,10 +403,10 @@ pub(crate) async fn set_column_presentation( } else { message }; - refuse(state, headers, inputs, Page::Columns, message).await + refuse(state, headers, inputs, Page::Presentation, message).await } Err(error) => { - refuse(state, headers, inputs, Page::Columns, error.message().to_string()).await + refuse(state, headers, inputs, Page::Presentation, error.message().to_string()).await } } } diff --git a/web/src/pages/admin/table_definition/mod.rs b/web/src/pages/admin/table_definition/mod.rs index 0af8ec53..88ffe9a6 100644 --- a/web/src/pages/admin/table_definition/mod.rs +++ b/web/src/pages/admin/table_definition/mod.rs @@ -1,8 +1,8 @@ //! What can be done to a table once it exists, as one page per decision. //! -//! Adding columns, dropping the table, copying its profile, generating tables -//! from a template and reading the rename history are five different jobs, and -//! they used to be five panels stacked on one `/admin/table-definition` +//! Adding columns, presenting columns, dropping the table, copying its profile, +//! generating tables from a template and reading the rename history are six +//! different jobs, and they used to be panels stacked on one `/admin/table-definition` //! workspace — which meant that after creating a table you landed on a screen //! where finding the delete form meant scrolling past five other forms. //! @@ -30,13 +30,18 @@ use crate::AppState; pub(crate) fn router() -> Router { Router::new() // Table-scoped. - .route("/admin/tables/columns", get(logic::columns_page)) - .route("/admin/tables/columns", post(logic::add_columns)) .route( - "/admin/tables/columns/builder", + "/admin/tables/columns/add", + get(logic::add_columns_page).post(logic::add_columns), + ) + .route( + "/admin/tables/columns/add/builder", post(logic::update_columns), ) - .route("/admin/tables/presentation", post(logic::set_column_presentation)) + .route( + "/admin/tables/presentation", + get(logic::presentation_page).post(logic::set_column_presentation), + ) .route("/admin/tables/delete", get(logic::delete_page)) .route("/admin/tables/delete", post(logic::delete_table)) // Profile-scoped. diff --git a/web/src/pages/admin/table_definition/state.rs b/web/src/pages/admin/table_definition/state.rs index 05c52aea..6d0f4b60 100644 --- a/web/src/pages/admin/table_definition/state.rs +++ b/web/src/pages/admin/table_definition/state.rs @@ -1,6 +1,6 @@ //! What the table-definition pages render, and the wire formats they post. //! -//! One state type serves all five pages, because each of them needs the same +//! One state type serves all table-definition pages, because each of them needs the same //! context: which profile and table is being worked on, and what that table //! currently is. What differs is which panel the page renders, and `active` //! is what says so. diff --git a/web/src/pages/admin/table_definition/ui.rs b/web/src/pages/admin/table_definition/ui.rs index 574d77f5..8cf924cf 100644 --- a/web/src/pages/admin/table_definition/ui.rs +++ b/web/src/pages/admin/table_definition/ui.rs @@ -2,8 +2,8 @@ //! //! The pages share a state type and an action switcher, so what differs //! between them here is only which panel they render. The column vocabulary -//! is threaded through the two that need it, because askama resolves those -//! fields on the struct rather than on `page`. +//! is threaded through the add-column templates, because askama resolves +//! those fields on the struct rather than on `page`. use askama::Template; @@ -15,25 +15,25 @@ use crate::{ use super::state::TableDefinitionPageState; -/// GET /admin/tables/columns +/// GET /admin/tables/columns/add #[derive(Template)] -#[template(path = "pages/admin/table_definition/columns.html")] -struct ColumnsPage<'a> { +#[template(path = "pages/admin/table_definition/add_columns.html")] +struct AddColumnsPage<'a> { nav: Nav, page: &'a TableDefinitionPageState, column_types: Vec, temporal_types: Vec, gtin_types: Vec, currency_codes: &'static [&'static str], - /// False, as on the fragment: the page embeds both, and the outcome is - /// reported once, at the top. + /// False, as on the fragment: the page reports the outcome once, at the + /// top of the panel containing this fragment. standalone_column_panel: bool, } -/// The `#table-panel` swap on the columns page. +/// The `#table-panel` swap on the add-columns page. #[derive(Template)] -#[template(path = "pages/admin/table_definition/columns_panel.html")] -struct ColumnsFragment<'a> { +#[template(path = "pages/admin/table_definition/add_columns_panel.html")] +struct AddColumnsFragment<'a> { nav: Nav, page: &'a TableDefinitionPageState, column_types: Vec, @@ -58,6 +58,22 @@ struct ColumnPanelFragment<'a> { standalone_column_panel: bool, } +/// GET /admin/tables/presentation +#[derive(Template)] +#[template(path = "pages/admin/table_definition/presentation.html")] +struct PresentationPage<'a> { + nav: Nav, + page: &'a TableDefinitionPageState, +} + +/// The `#table-panel` swap on the column-presentation page. +#[derive(Template)] +#[template(path = "pages/admin/table_definition/presentation_panel.html")] +struct PresentationFragment<'a> { + nav: Nav, + page: &'a TableDefinitionPageState, +} + /// GET /admin/tables/delete #[derive(Template)] #[template(path = "pages/admin/table_definition/delete.html")] @@ -111,8 +127,8 @@ struct HistoryPage<'a> { page: &'a TableDefinitionPageState, } -pub(crate) fn render_columns_page(page: &TableDefinitionPageState) -> String { - render(&ColumnsPage { +pub(crate) fn render_add_columns_page(page: &TableDefinitionPageState) -> String { + render(&AddColumnsPage { nav: page.nav.clone(), page, // Asking the draft, so the picker can only ever offer what the draft @@ -125,8 +141,8 @@ pub(crate) fn render_columns_page(page: &TableDefinitionPageState) -> String { }) } -pub(crate) fn render_columns_fragment(page: &TableDefinitionPageState) -> String { - render(&ColumnsFragment { +pub(crate) fn render_add_columns_fragment(page: &TableDefinitionPageState) -> String { + render(&AddColumnsFragment { nav: page.nav.clone(), page, column_types: page.columns.offered_types(), @@ -147,6 +163,20 @@ pub(crate) fn render_column_panel(page: &TableDefinitionPageState) -> String { }) } +pub(crate) fn render_presentation_page(page: &TableDefinitionPageState) -> String { + render(&PresentationPage { + nav: page.nav.clone(), + page, + }) +} + +pub(crate) fn render_presentation_fragment(page: &TableDefinitionPageState) -> String { + render(&PresentationFragment { + nav: page.nav.clone(), + page, + }) +} + pub(crate) fn render_delete_page(page: &TableDefinitionPageState) -> String { render(&DeletePage { nav: page.nav.clone(), @@ -262,7 +292,7 @@ mod tests { error: None, sql: None, generated: Vec::new(), - active: "columns", + active: "presentation", } } @@ -273,16 +303,18 @@ mod tests { let mut state = page(); for (active, html) in [ - ("columns", render_columns_page(&state)), + ("add-columns", render_add_columns_page(&state)), + ("presentation", render_presentation_page(&state)), ("delete", render_delete_page(&state)), ] { state.active = active; assert!(!html.contains("Template error"), "{html}"); } - let html = render_columns_page(&state); + let html = render_presentation_page(&state); for route in [ - "/admin/tables/columns?profile=billing", + "/admin/tables/columns/add?profile=billing", + "/admin/tables/presentation?profile=billing", "/admin/tables/delete?profile=billing", "/admin/profiles/copy?profile=billing", "/admin/tables/from-template?profile=billing", @@ -308,7 +340,7 @@ mod tests { assert!(html.contains("/admin/tables/delete")); assert!(html.contains("Type invoice to confirm")); // The other writes are links in the switcher, not forms on the page. - assert!(!html.contains("/admin/tables/presentation")); + assert!(!html.contains(r#"hx-post="/admin/tables/presentation""#)); assert!(!html.contains("/admin/profiles/copy?profile=billing\" method")); } @@ -368,13 +400,16 @@ mod tests { assert!(html.contains("backend's own")); assert!(!html.contains(r#"name="confirm_table_name""#)); - let html = render_columns_page(&state); - assert!(!html.contains("/admin/tables/presentation")); + let html = render_add_columns_page(&state); + assert!(!html.contains(r#"id="column-form""#)); + + let html = render_presentation_page(&state); + assert!(!html.contains(r#"name="aliases""#)); } #[test] fn column_presentation_posts_stable_ids_aliases_and_order_controls() { - let html = render_columns_page(&page()); + let html = render_presentation_page(&page()); assert!(html.contains(r#"hx-post="/admin/tables/presentation""#), "{html}"); assert!(html.contains(r#"name="column_ids" value="1""#), "{html}"); @@ -383,6 +418,17 @@ mod tests { assert!(html.contains(r#"name="action" value="save""#), "{html}"); } + #[test] + fn adding_columns_and_presentation_are_separate_pages() { + let add_html = render_add_columns_page(&page()); + assert!(add_html.contains(r#"id="column-form""#), "{add_html}"); + assert!(!add_html.contains(r#"name="aliases""#), "{add_html}"); + + let presentation_html = render_presentation_page(&page()); + assert!(presentation_html.contains(r#"name="aliases""#), "{presentation_html}"); + assert!(!presentation_html.contains(r#"id="column-form""#), "{presentation_html}"); + } + /// The profile-wide pages need only a profile, and say so by still /// rendering with no table selected. #[test] @@ -429,7 +475,7 @@ mod tests { let html = render_column_panel(&state); assert!(!html.contains("Template error"), "{html}"); - assert!(html.contains("/admin/tables/columns/builder")); + assert!(html.contains("/admin/tables/columns/add/builder")); // Escaped, because it is an attribute: `&` is what a browser reads // back as the `&` separating the two parameters. assert!(html.contains("?profile=billing&table=invoice")); @@ -548,10 +594,10 @@ mod tests { #[test] fn a_failure_is_shown_as_a_dialog_as_well_as_an_alert() { let mut state = page(); - assert!(!render_columns_fragment(&state).contains(r#"role="dialog""#)); + assert!(!render_add_columns_fragment(&state).contains(r#"role="dialog""#)); state.error = Some("That column already exists.".to_string()); - let html = render_columns_fragment(&state); + let html = render_add_columns_fragment(&state); assert!(html.contains(r#"role="dialog""#)); // Once in the inline alert, once in the dialog — and not a third time // from the column panel the fragment embeds. @@ -580,7 +626,7 @@ mod tests { state.status = Some("1 column added to `invoice`.".to_string()); state.sql = Some("ALTER TABLE \"billing\".\"invoice\" ADD COLUMN …".to_string()); - let html = render_columns_fragment(&state); + let html = render_add_columns_fragment(&state); assert!(html.contains("ALTER TABLE")); assert!(html.contains("1 column added")); @@ -591,7 +637,7 @@ mod tests { let mut state = page(); state.selection.profile = "__global".to_string(); - let html = render_columns_page(&state); + let html = render_presentation_page(&state); assert!(!html.contains("Template error"), "{html}"); assert!(html.contains("Global — all profiles")); diff --git a/web/templates/pages/admin/admin/workspace.html b/web/templates/pages/admin/admin/workspace.html index 95130970..913b34c8 100644 --- a/web/templates/pages/admin/admin/workspace.html +++ b/web/templates/pages/admin/admin/workspace.html @@ -58,7 +58,7 @@ {% if page.selected_table.as_deref() == Some(table.name.as_str()) %}
{% if page.can_manage_tables && !table.is_system() %} - {{ nav.tr("admin-add-rename-columns") }} + {{ nav.tr("admin-column-presentation") }} {{ nav.tr("admin-delete-table") }} {% endif %} {{ nav.tr("admin-who-may-use-it") }} diff --git a/web/templates/pages/admin/table_definition/add_columns.html b/web/templates/pages/admin/table_definition/add_columns.html new file mode 100644 index 00000000..8b9b0777 --- /dev/null +++ b/web/templates/pages/admin/table_definition/add_columns.html @@ -0,0 +1,18 @@ +{# GET /admin/tables/columns/add — crate::pages::admin::table_definition::ui::AddColumnsPage #} +{% extends "ui/base.html" %} + +{% block title %}{{ nav.tr("td-add-columns-title") }}{% endblock %} + +{% block content %} +
+ {% include "pages/admin/table_definition/context.html" %} + +
+ {% include "pages/admin/table_definition/add_columns_panel.html" %} +
+
+ + + {% for code in currency_codes %}{% endfor %} + +{% endblock %} diff --git a/web/templates/pages/admin/table_definition/add_columns_panel.html b/web/templates/pages/admin/table_definition/add_columns_panel.html new file mode 100644 index 00000000..6f775c89 --- /dev/null +++ b/web/templates/pages/admin/table_definition/add_columns_panel.html @@ -0,0 +1,17 @@ +{# The add-columns page body — crate::pages::admin::table_definition::ui::AddColumnsFragment. #} +{% include "pages/admin/table_definition/feedback.html" %} + +{% if page.table_is_writable() %} +
+

{{ nav.tr("td-add-columns-to") }} {{ page.selection.table }}

+

{{ nav.tr("td-append-hint") }}

+
+
+ {% include "pages/admin/table_definition/column_panel.html" %} +
+
+
+{% endif %} diff --git a/web/templates/pages/admin/table_definition/column_panel.html b/web/templates/pages/admin/table_definition/column_panel.html index 3641d224..2642a718 100644 --- a/web/templates/pages/admin/table_definition/column_panel.html +++ b/web/templates/pages/admin/table_definition/column_panel.html @@ -29,7 +29,7 @@