{# POST /admin/tables/builder — crate::pages::add_table::ui::BuilderFragment. The whole builder, which is also what `add_table.html` embeds on first load. Every control posts the entire draft back to /admin/tables/builder and swaps this markup in again, so the rules live on the server exactly as they do in the TUI client. The hidden inputs in the columns, relations and display sections are what carry the draft across requests. #} {% import "ui/alert.html" as alert %} {% import "ui/dialog.html" as dialog %} {% import "ui/toast.html" as toast %} {# A failure is a dialog, not an inline alert: the builder is taller than the viewport, so an alert at the top is off screen for anyone who has scrolled down to the column list or the Create table button. The alert stays too, as what is left on the page after the dialog is dismissed. #} {%- if let Some(message) = page.error %}{% call alert::error("Could not continue", message) %}{% endcall %}{% endif -%} {%- if let Some(message) = page.status %}{% call toast::success("Draft updated", message) %}{% endcall %}{% endif -%} {%- if let Some(message) = page.error %}{% call dialog::error("Could not continue", message) %}{% endcall %}{% endif -%}

Table details

{# Penguin UI custom radio, adapted from radio/custom-radio.html with this form's values and htmx behaviour. #}
Where should this table be available?
{% if !page.draft.global %} {% else %} {% endif %} {% if page.draft.show_profile_name_input() %} {% endif %} {% if page.draft.show_accounting_currency() %} {% else %} {% endif %} {# No hx-trigger here. A text input fires `change` on blur, so mousing into the next field would swap the whole builder out from under the click that is still in flight: the click lands on a detached node and the field only takes focus on a second click. Nothing rendered depends on the table name on its own — it filters the table out of its own link-target list, and that list is only rendered after a column-type `change`, which posts the current name along with the rest of the form. #}

Add a column

{# A compound choice is a definition row, not a column: it is named after its own type and expands into companions the server creates. There is nothing to name, so the field is replaced by what the choice will add. #} {% if !page.draft.columns.pending_is_compound() %} {% endif %} {% if page.draft.columns.show_temporal_type() %} {% endif %} {% if page.draft.columns.show_gtin_type() %} {% endif %} {% if page.draft.columns.show_decimal_arguments() %} {% endif %} {% if page.draft.columns.show_link_target() %} {% endif %} {% if page.draft.columns.show_money_options() %} {% endif %} {# Neither applies to a definition row: it leaves no column to index, and nothing to keep a quantity ledger on. #} {% if !page.draft.columns.pending_is_compound() %} {% endif %}
{% if page.draft.columns.pending_is_compound() %}

{{ page.draft.columns.pending_compound_name() }} is a definition row, not a column of its own. Adding it gives the table:

Each of these can be given a name of your own under Rename generated columns once the row is added; leave a field empty to keep the name shown here.

These names are reserved: no column of your own may use them, and the table may hold only one {{ page.draft.columns.pending_compound_name() }}. They land where this row sits in the column list, and can be moved with it.

{% endif %}

Columns {{ page.draft.columns.added.len() }}

{% if page.draft.columns.is_empty() %}

No columns yet. Describe one above and press Add column.

{% else %}

Columns are created in this order. Use the arrows to move one.

{# Every declared column, each followed by the columns the server will generate from it. `row.index` is what says which is which: a generated column has none, because it is not the user's to move, index or remove — it belongs to the definition row above it. #} {% for row in page.column_rows() %} {% endfor %}
OrderNameTypeIndexedOptions
{% if let Some(index) = row.index %} {% else %} {% endif %} {{ row.name }} {# The list only reports a rename; it is asked for below, so that this column stays something to read. #} {% if !row.alias.is_empty() %}renamed to {{ row.alias }}{% endif %} {{ row.data_type }} {# A compound column expands into schema-managed companions, so there is no column of its own name to index. #} {% if row.indexable %} {% if let Some(index) = row.index %} {% endif %} {% else if row.indexed %} indexed {% else %} {% endif %} {% for tag in row.tags %}{{ tag }}{% endfor %} {% if let Some(index) = row.index %} {% endif %}
{% endif %} {# The draft itself: one set of fields per column, in order. #} {% for column in page.draft.columns.added %} {% endfor %}
{# Renaming what a definition row generates. A generated column is named by the backend, so it cannot be named in the request that creates the table — but the name is a display name, and an alias here is applied as a rename as soon as the table exists. Leaving a field empty keeps the backend's name. It is a closed `
`, so the default — the backend's names — costs nothing to read past. Fields inside a closed `
` are still part of the form, so the aliases travel with every builder post either way. #} {% 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.

    {% for row in page.alias_rows() %}
  • {{ row.source }} {{ row.data_type }}
  • {% 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 %}
{% for table in page.draft.relation_tables %} {% endfor %}

Row display columns

What identifies a row to users, in the order chosen. Pick id to clear the list.

    {% for candidate in page.row_display_candidates() %}
  • {{ candidate.name }}
  • {% endfor %}
{% for display in page.draft.row_display_columns %} {% endfor %}

Table definition preview

{% for row in page.draft.preview_rows() %} {% endfor %}
ColumnTypeOptionsSource
{{ row.mark }} {{ row.column }} {{ row.data_type }} {{ row.option }} {{ row.source }}
Cancel