{# 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(nav.locale, nav.tr("builder-could-not-continue"), message) %}{% endcall %}{% endif -%} {%- if let Some(message) = page.status %}{% call toast::success(nav.tr("builder-draft-updated"), message) %}{% endcall %}{% endif -%} {%- if let Some(message) = page.error %}{% call dialog::error(nav.locale, nav.tr("builder-could-not-continue"), message) %}{% endcall %}{% endif -%}

{{ nav.tr("builder-table-details") }}

{# Penguin UI custom radio, adapted from radio/custom-radio.html with this form's values and htmx behaviour. #}
{{ nav.tr("builder-scope-question") }}
{% 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. #}

{{ nav.tr("builder-add-column-heading") }}

{# 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 %} {# None of the three applies to a definition row: it leaves no column to index, nothing to keep a quantity ledger on, and no value to require. #} {% if !page.draft.columns.pending_is_compound() %} {# A link is left out rather than defaulted to "no": the server builds an index for every foreign key as it creates the table, and refuses a definition that asks for one on top — so there is no choice to offer. #} {% if page.draft.columns.show_indexing() %} {% else if page.draft.columns.pending_is_auto_indexed() %}
{{ nav.tr("td-indexing") }}

{{ nav.tr("td-indexed-automatically") }}

{% endif %} {% if page.draft.columns.show_quantity_ledger() %} {% endif %} {% endif %}
{% if page.draft.global %}

{{ nav.tr("builder-shared-table-hint") }}

{% endif %} {% if page.draft.columns.pending_is_compound() %}

{{ page.draft.columns.pending_compound_name() }} {{ nav.tr("builder-compound-intro") }}

{{ nav.tr("builder-rename-generated-hint")|safe }}

{{ nav.tr_args("builder-reserved-hint", [("type", page.draft.columns.pending_compound_name())])|safe }}

{% endif %}

{{ nav.tr("builder-columns-heading") }} {{ page.draft.columns.added.len() }}

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

{{ nav.tr("builder-no-columns") }} {{ nav.tr("builder-add-column-button") }}.

{% else %}

{{ nav.tr("builder-columns-order-hint") }}

{# 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 %}
{{ nav.tr("builder-th-order") }}{{ nav.tr("builder-th-name") }}{{ nav.tr("builder-th-type") }}{{ nav.tr("builder-th-indexed") }}{{ nav.tr("builder-th-options") }}
{% 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() %}{{ nav.tr_args("builder-renamed-to", [("alias", row.alias.clone())]) }}{% 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 %} {# Not a choice anyone made: a link and the account foreign key are indexed by the server as it creates the table. #} {{ nav.tr("td-indexed-automatically-tag") }} {% 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. #}
{{ nav.tr("builder-rename-generated") }}

{{ nav.tr("builder-rename-generated-hint2") }}

    {% 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 %}

{{ nav.tr("builder-row-display") }}

{{ nav.tr("builder-row-display-hint")|safe }}

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

{{ nav.tr("builder-preview") }}

{% for row in page.draft.preview_rows(nav.locale) %} {% endfor %}
{{ nav.tr("builder-preview-th-column") }}{{ nav.tr("builder-preview-th-type") }}{{ nav.tr("builder-preview-th-options") }}{{ nav.tr("builder-preview-th-source") }}
{{ row.mark }} {{ row.column }} {{ row.data_type }} {{ row.option }} {{ row.source }}
{{ nav.tr("common-cancel") }}