{#
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. #}
{% 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:
{% for generated in page.draft.columns.pending_generated_columns() %}
{# The currency and rounding are named rather than shown: those two
inputs sit above with no `change` trigger of their own, so their
current values are only known here on the next render. #}
{{ generated.name }}{{ generated.data_type }}{% if generated.inherits_currency %} — kept in the currency and rounding chosen above{% endif %}
{% endfor %}
{% if page.draft.columns.pending_compound_name() == "accounting" %}
account_idsystem column — the account each row is posted to, written as account
{% endif %}
Each of these can be given a name of your own in the column list 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.
Order
Name
Type
Indexed
Options
{#
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() %}
{% if let Some(index) = row.index %}
{% else %}
↳
{% endif %}
{{ row.name }}
{#
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 it empty keeps the backend's name.
#}
{% if let Some(source) = row.alias_source %}
{% 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 %}
{% endfor %}
{% endif %}
{# The draft itself: one set of fields per column, in order. #}
{% for column in page.draft.columns.added %}
{% endfor %}
{% 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 %}