From df145c14f700fb1c8aa84789308aea3f091d2577 Mon Sep 17 00:00:00 2001 From: Priec Date: Thu, 13 Aug 2026 22:48:29 +0200 Subject: [PATCH] web add table fix in naming --- web/src/pages/add_table/draft.rs | 7 ++- web/src/pages/add_table/loader.rs | 54 ++++++++++++++-------- web/src/pages/add_table/logic.rs | 15 ++++++ web/src/pages/add_table/ui.rs | 22 +++++++++ web/templates/pages/add_table/builder.html | 12 ++++- 5 files changed, 89 insertions(+), 21 deletions(-) diff --git a/web/src/pages/add_table/draft.rs b/web/src/pages/add_table/draft.rs index cb69f3cb..5131c46e 100644 --- a/web/src/pages/add_table/draft.rs +++ b/web/src/pages/add_table/draft.rs @@ -290,6 +290,11 @@ impl TableDraft { .collect() } + /// Whether any generated column has been given a name of its own. + pub(crate) fn has_generated_aliases(&self) -> bool { + !self.aliased_generated_columns().is_empty() + } + pub(crate) fn aliased_generated_columns(&self) -> Vec<(String, String)> { self.aliasable_generated_columns() .into_iter() @@ -303,7 +308,7 @@ impl TableDraft { /// Every alias has to be a legal column name, and has to be free: the table /// is about to hold the declared columns, the generated ones and the system /// ones, and two columns cannot share a name. - fn validate_generated_aliases(&self) -> Result<(), String> { + pub(crate) fn validate_generated_aliases(&self) -> Result<(), String> { let generated = self.aliasable_generated_columns(); let renames = self.aliased_generated_columns(); diff --git a/web/src/pages/add_table/loader.rs b/web/src/pages/add_table/loader.rs index 502135a2..addda5af 100644 --- a/web/src/pages/add_table/loader.rs +++ b/web/src/pages/add_table/loader.rs @@ -60,21 +60,26 @@ pub(crate) async fn load_page( .into_inner(); let effective_profile = draft.effective_profile_name(); + + // A global table belongs to every profile, so it is a link target from + // every scope. It is listed under each profile in the tree, so it is + // collected once here and deduplicated by name. + let global_tables = tree + .profiles + .iter() + .flat_map(|profile| profile.tables.iter()) + .filter(|table| table.global) + .map(|table| (table.name.clone(), table.table_kind.clone())) + .collect::>() + .into_iter() + .map(|(name, table_kind)| RelationTableOption { + name, + global: true, + system: table_kind == "system", + }) + .collect::>(); + if draft.global { - let global_tables = tree - .profiles - .iter() - .flat_map(|profile| profile.tables.iter()) - .filter(|table| table.global) - .map(|table| (table.name.clone(), table.table_kind.clone())) - .collect::>() - .into_iter() - .map(|(name, table_kind)| RelationTableOption { - name, - global: true, - system: table_kind == "system", - }) - .collect::>(); draft.existing_profile_tables = tree .profiles .iter() @@ -92,7 +97,7 @@ pub(crate) async fn load_page( // An existing profile: its tables are the link targets, and their // names are reserved against duplicate table creation. Some(profile) => { - let table_options = profile + let mut table_options = profile .tables .iter() .filter(|table| table.name != "accounts") @@ -102,17 +107,28 @@ pub(crate) async fn load_page( system: table.table_kind == "system", }) .collect::>(); + // The shared tables belong here whether or not the tree repeated + // them under this profile. + for global in &global_tables { + if !table_options.iter().any(|table| table.name == global.name) { + table_options.push(global.clone()); + } + } draft.existing_profile_tables = table_options .iter() .map(|table| table.name.clone()) .collect(); draft.set_available_relation_table_options(table_options); } - // A brand-new (or not-yet-named) profile has nothing to link to. + // A brand-new (or not-yet-named) profile has no tables of its own, + // but the shared ones are there to link to — and their names are + // taken, so the new table may not reuse one either. None => { - draft.existing_profile_tables.clear(); - draft.relation_tables.clear(); - draft.relation_table_options.clear(); + draft.existing_profile_tables = global_tables + .iter() + .map(|table| table.name.clone()) + .collect(); + draft.set_available_relation_table_options(global_tables); } }} diff --git a/web/src/pages/add_table/logic.rs b/web/src/pages/add_table/logic.rs index 90be2be4..59a583c5 100644 --- a/web/src/pages/add_table/logic.rs +++ b/web/src/pages/add_table/logic.rs @@ -159,6 +159,21 @@ fn apply_action(page: &mut AddTablePageState, form: &BuilderForm) { // in, so moving one is a change to the draft like any other. "move-column-up" => page.status = page.draft.move_column(index, -1), "move-column-down" => page.status = page.draft.move_column(index, 1), + // The alias fields carry no `change` trigger of their own — a text + // input fires `change` on blur, which would swap the builder out from + // under the click that is still in flight. This button is what applies + // what was typed, so the column list and the preview say what the + // table will really be called. + "apply-generated-names" => match page.draft.validate_generated_aliases() { + Ok(()) => { + page.status = Some(match page.draft.aliased_generated_columns().len() { + 0 => "The generated columns keep their own names.".to_string(), + 1 => "1 generated column renamed.".to_string(), + count => format!("{count} generated columns renamed."), + }) + } + Err(message) => page.error = Some(message), + }, "toggle-index" => page.draft.columns.toggle_indexed(index), "toggle-display" => page.draft.toggle_row_display_candidate(index), _ => {} diff --git a/web/src/pages/add_table/ui.rs b/web/src/pages/add_table/ui.rs index bf11d6e0..5c02b69c 100644 --- a/web/src/pages/add_table/ui.rs +++ b/web/src/pages/add_table/ui.rs @@ -184,6 +184,28 @@ mod tests { assert!(html.contains(r#"renamed to md"#), "{html}"); assert!(html.contains(r#"name="generated_alias_names" value="md""#)); + // The names only reach the column list when something posts them, and + // the section stays open so the applied names can be read back. + assert!(html.contains(r#""action": "apply-generated-names""#)); + assert!(html.contains(r#"
"#)); + } + + /// With no name asked for, the section is the closed default it was. + #[test] + fn the_rename_section_starts_closed() { + let mut page = page(); + page.draft.columns.added.push(ColumnDefinition { + name: "accounting".to_string(), + data_type: "accounting".to_string(), + indexed: false, + quantity_ledger: false, + money_mode: MoneyMode::Exact, + currency: "EUR".to_string(), + }); + + let html = render_builder(&page); + + assert!(html.contains(r#"
"#), "{html}"); } #[test] diff --git a/web/templates/pages/add_table/builder.html b/web/templates/pages/add_table/builder.html index fc13bf95..49d0142a 100644 --- a/web/templates/pages/add_table/builder.html +++ b/web/templates/pages/add_table/builder.html @@ -338,7 +338,9 @@ #} {% if !page.alias_rows().is_empty() %}
-
+ {# Open once a name has been asked for: the section is where those names are + read back, and a swap that closed it would hide what was just applied. #} +
Rename generated columns

These columns come from the definition rows above. Give one a name of your own, or leave it empty to keep the name shown.

    @@ -352,6 +354,14 @@ {% endfor %}
+ {# + A text field posts nothing on its own here, so the names typed above are + only carried by the next request. This button is that request: it applies + them, so the column list and the preview show the renamed columns. + #} +
{% endif %}