444 lines
24 KiB
HTML
444 lines
24 KiB
HTML
{#
|
|
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 -%}
|
|
|
|
<section class="builder-section">
|
|
<h2>{{ nav.tr("builder-table-details") }}</h2>
|
|
<div class="form-grid">
|
|
{# Penguin UI custom radio, adapted from radio/custom-radio.html with this form's values and htmx behaviour. #}
|
|
<fieldset class="col-span-full m-0 border-0 p-0">
|
|
<legend class="mb-2 text-xs text-on-surface">{{ nav.tr("builder-scope-question") }}</legend>
|
|
<div class="grid grid-cols-1 gap-2">
|
|
<label class="relative flex items-center gap-3 rounded-radius border border-outline bg-surface-alt px-3 py-2.5 text-on-surface transition-colors has-checked:border-primary has-checked:bg-primary/5 has-checked:text-on-surface-strong has-focus:outline-2 has-focus:outline-offset-2 has-focus:outline-primary">
|
|
<input id="scope-profile" type="radio" name="global" value="false" class="peer sr-only"
|
|
aria-describedby="scope-profile-description"
|
|
{% if !page.draft.global %}checked{% endif %}
|
|
hx-post="/admin/tables/builder" hx-trigger="change"
|
|
hx-include="#table-form" hx-target="#builder" hx-swap="innerHTML"
|
|
hx-vals='{"action": "refresh"}'>
|
|
<svg aria-hidden="true" viewBox="0 0 16 16" fill="currentColor" class="invisible size-5 shrink-0 peer-checked:visible">
|
|
<path fill-rule="evenodd" d="M12.416 3.376a.75.75 0 0 1 .208 1.04l-5 7.5a.75.75 0 0 1-1.154.114l-3-3a.75.75 0 0 1 1.06-1.06l2.353 2.353 4.493-6.74a.75.75 0 0 1 1.04-.207Z" clip-rule="evenodd"></path>
|
|
</svg>
|
|
<span class="flex flex-col"><strong class="text-sm font-medium">{{ nav.tr("builder-scope-profile") }}</strong><small id="scope-profile-description">{{ nav.tr("builder-scope-profile-hint") }}</small></span>
|
|
</label>
|
|
<label class="relative flex items-center gap-3 rounded-radius border border-outline bg-surface-alt px-3 py-2.5 text-on-surface transition-colors has-checked:border-primary has-checked:bg-primary/5 has-checked:text-on-surface-strong has-focus:outline-2 has-focus:outline-offset-2 has-focus:outline-primary">
|
|
<input id="scope-global" type="radio" name="global" value="true" class="peer sr-only"
|
|
aria-describedby="scope-global-description"
|
|
{% if page.draft.global %}checked{% endif %}
|
|
hx-post="/admin/tables/builder" hx-trigger="change"
|
|
hx-include="#table-form" hx-target="#builder" hx-swap="innerHTML"
|
|
hx-vals='{"action": "refresh"}'>
|
|
<svg aria-hidden="true" viewBox="0 0 16 16" fill="currentColor" class="invisible size-5 shrink-0 peer-checked:visible">
|
|
<path fill-rule="evenodd" d="M12.416 3.376a.75.75 0 0 1 .208 1.04l-5 7.5a.75.75 0 0 1-1.154.114l-3-3a.75.75 0 0 1 1.06-1.06l2.353 2.353 4.493-6.74a.75.75 0 0 1 1.04-.207Z" clip-rule="evenodd"></path>
|
|
</svg>
|
|
<span class="flex flex-col"><strong class="text-sm font-medium">{{ nav.tr("builder-scope-global") }}</strong><small id="scope-global-description">{{ nav.tr("builder-scope-global-hint") }}</small></span>
|
|
</label>
|
|
</div>
|
|
</fieldset>
|
|
|
|
{% if !page.draft.global %}
|
|
<label>{{ nav.tr("builder-which-profile") }}
|
|
<select name="profile_name" hx-post="/admin/tables/builder" hx-trigger="change"
|
|
hx-include="#table-form" hx-target="#builder" hx-swap="innerHTML"
|
|
hx-vals='{"action": "refresh"}'>
|
|
<option value="">{{ nav.tr("builder-choose-profile") }}</option>
|
|
{% for profile in page.profiles %}
|
|
<option value="{{ profile }}" {% if page.selected_profile() == profile.as_str() %}selected{% endif %}>{{ profile }}</option>
|
|
{% endfor %}
|
|
<option value="__new__" {% if page.draft.creating_new_profile %}selected{% endif %}>+ {{ nav.tr("builder-new-profile") }}…</option>
|
|
</select>
|
|
</label>
|
|
{% else %}
|
|
<input type="hidden" name="profile_name" value="{{ page.draft.profile_name }}">
|
|
{% endif %}
|
|
|
|
{% if page.draft.show_profile_name_input() %}
|
|
<label>{{ nav.tr("builder-new-profile-name") }}
|
|
<input name="profile_name_input" value="{{ page.draft.profile_name_input }}" placeholder="bookkeeping">
|
|
</label>
|
|
{% endif %}
|
|
|
|
{% if page.draft.show_accounting_currency() %}
|
|
<label>{{ nav.tr("builder-accounting-currency") }}
|
|
<input name="accounting_currency" value="{{ page.draft.accounting_currency }}" list="currency-codes"
|
|
maxlength="3" placeholder="EUR">
|
|
</label>
|
|
{% else %}
|
|
<input type="hidden" name="accounting_currency" value="{{ page.draft.accounting_currency }}">
|
|
{% 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.
|
|
#}
|
|
<label>{{ nav.tr("builder-table-name") }}
|
|
<input name="table_name" value="{{ page.draft.table_name }}" placeholder="invoice">
|
|
</label>
|
|
|
|
</div>
|
|
</section>
|
|
|
|
<section class="builder-section">
|
|
<h2>{{ nav.tr("builder-add-column-heading") }}</h2>
|
|
<div class="form-grid">
|
|
{#
|
|
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() %}
|
|
<label>{% if page.draft.columns.show_link_target() %}{{ nav.tr("td-link-alias") }}{% else %}{{ nav.tr("td-column-name") }}{% endif %}
|
|
<input name="column_name_input" value="{{ page.draft.columns.name_input }}" placeholder="number">
|
|
{% if page.draft.columns.show_link_target() %}<small>{{ nav.tr("td-link-alias-hint") }}</small>{% endif %}
|
|
</label>
|
|
{% endif %}
|
|
<label>{{ nav.tr("td-column-type") }}
|
|
<select name="column_type_input" hx-post="/admin/tables/builder" hx-trigger="change"
|
|
hx-include="#table-form" hx-target="#builder" hx-swap="innerHTML"
|
|
hx-vals='{"action": "refresh"}'>
|
|
<option value="">{{ nav.tr("td-choose-type") }}</option>
|
|
{% for column_type in column_types %}
|
|
<option value="{{ column_type }}" {% if page.draft.columns.type_input == *column_type %}selected{% endif %}>{% if *column_type == "link" %}{{ nav.tr("td-fk-link") }}{% else %}{{ column_type }}{% endif %}</option>
|
|
{% endfor %}
|
|
</select>
|
|
</label>
|
|
|
|
{% if page.draft.columns.show_temporal_type() %}
|
|
<label>{{ nav.tr("td-temporal-type") }}
|
|
<select name="temporal_type_input">
|
|
<option value="">{{ nav.tr("td-choose-temporal-type") }}</option>
|
|
{% for temporal_type in temporal_types %}
|
|
<option value="{{ temporal_type }}" {% if page.draft.columns.temporal_type_input == *temporal_type %}selected{% endif %}>{{ temporal_type }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
</label>
|
|
{% endif %}
|
|
|
|
{% if page.draft.columns.show_gtin_type() %}
|
|
<label>{{ nav.tr("td-gtin-type") }}
|
|
<select name="gtin_type_input">
|
|
<option value="">{{ nav.tr("td-choose-gtin-length") }}</option>
|
|
{% for gtin_type in gtin_types %}
|
|
<option value="{{ gtin_type }}" {% if page.draft.columns.gtin_type_input == *gtin_type %}selected{% endif %}>GTIN-{{ gtin_type }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
</label>
|
|
{% endif %}
|
|
|
|
{% if page.draft.columns.show_decimal_arguments() %}
|
|
<label>{{ nav.tr("td-precision") }}
|
|
<input name="decimal_precision_input" value="{{ page.draft.columns.decimal_precision_input }}"
|
|
inputmode="numeric" placeholder="12">
|
|
<small>{{ nav.tr("td-precision-hint") }}</small>
|
|
</label>
|
|
<label>{{ nav.tr("td-scale") }}
|
|
<input name="decimal_scale_input" value="{{ page.draft.columns.decimal_scale_input }}"
|
|
inputmode="numeric" placeholder="3">
|
|
<small>{{ nav.tr("td-scale-hint") }}</small>
|
|
</label>
|
|
{% endif %}
|
|
|
|
{% if page.draft.columns.show_link_target() %}
|
|
<label>{{ nav.tr("td-referenced-table") }}
|
|
<select name="link_table_input">
|
|
<option value="">{{ nav.tr("td-choose-table") }}</option>
|
|
{% if !page.draft.global_relation_tables().is_empty() %}
|
|
<optgroup label="{{ nav.tr("td-group-global") }}">
|
|
{% for table in page.draft.global_relation_tables() %}
|
|
<option value="{{ table }}" {% if page.draft.columns.link_table_input == *table %}selected{% endif %}>{{ table }}</option>
|
|
{% endfor %}
|
|
</optgroup>
|
|
{% endif %}
|
|
{% if !page.draft.user_relation_tables().is_empty() %}
|
|
<optgroup label="{{ nav.tr("td-group-user") }}">
|
|
{% for table in page.draft.user_relation_tables() %}
|
|
<option value="{{ table }}" {% if page.draft.columns.link_table_input == *table %}selected{% endif %}>{{ table }}</option>
|
|
{% endfor %}
|
|
</optgroup>
|
|
{% endif %}
|
|
{% if !page.draft.system_relation_tables().is_empty() %}
|
|
<optgroup label="{{ nav.tr("td-group-system") }}">
|
|
{% for table in page.draft.system_relation_tables() %}
|
|
<option value="{{ table }}" {% if page.draft.columns.link_table_input == *table %}selected{% endif %}>{{ table }}</option>
|
|
{% endfor %}
|
|
</optgroup>
|
|
{% endif %}
|
|
</select>
|
|
{% if page.draft.relation_tables.is_empty() %}<small>{{ nav.tr("td-no-eligible-tables") }}</small>{% endif %}
|
|
</label>
|
|
{% endif %}
|
|
|
|
{% if page.draft.columns.show_money_options() %}
|
|
<label>{{ nav.tr("td-currency") }}
|
|
<input name="column_currency_input" value="{{ page.draft.columns.currency_input }}" list="currency-codes"
|
|
maxlength="3" placeholder="EUR">
|
|
</label>
|
|
<label>{{ nav.tr("td-rounding") }}
|
|
<select name="column_rounding_input">
|
|
<option value="none" {% if page.draft.columns.rounding_input != "half-up" %}selected{% endif %}>{{ nav.tr("td-rounding-none") }}</option>
|
|
<option value="half-up" {% if page.draft.columns.rounding_input == "half-up" %}selected{% endif %}>{{ nav.tr("td-rounding-half-up") }}</option>
|
|
</select>
|
|
</label>
|
|
{% 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() %}
|
|
<label>{{ nav.tr("td-indexing") }}
|
|
<select name="column_indexing_input">
|
|
<option value="no" {% if page.draft.columns.indexing_input != "yes" %}selected{% endif %}>{{ nav.tr("common-no") }}</option>
|
|
<option value="yes" {% if page.draft.columns.indexing_input == "yes" %}selected{% endif %}>{{ nav.tr("common-yes") }}</option>
|
|
</select>
|
|
</label>
|
|
{% else if page.draft.columns.pending_is_auto_indexed() %}
|
|
<div class="field-note">
|
|
<span class="field-label">{{ nav.tr("td-indexing") }}</span>
|
|
<p class="hint">{{ nav.tr("td-indexed-automatically") }}</p>
|
|
</div>
|
|
{% endif %}
|
|
<label>{{ nav.tr("td-required") }}
|
|
<select name="column_required_input">
|
|
<option value="no" {% if page.draft.columns.required_input != "yes" %}selected{% endif %}>{{ nav.tr("common-no") }}</option>
|
|
<option value="yes" {% if page.draft.columns.required_input == "yes" %}selected{% endif %}>{{ nav.tr("common-yes") }}</option>
|
|
</select>
|
|
<small>{{ nav.tr("td-required-hint") }}</small>
|
|
</label>
|
|
{% if page.draft.columns.show_quantity_ledger() %}
|
|
<label>{{ nav.tr("td-quantity-ledger") }}
|
|
<select name="column_quantity_ledger_input">
|
|
<option value="no" {% if page.draft.columns.quantity_ledger_input != "yes" %}selected{% endif %}>{{ nav.tr("common-no") }}</option>
|
|
<option value="yes" {% if page.draft.columns.quantity_ledger_input == "yes" %}selected{% endif %}>{{ nav.tr("common-yes") }}</option>
|
|
</select>
|
|
<small>{{ nav.tr_args("td-ql-only", [("types", page.draft.columns.quantity_ledger_types())]) }}</small>
|
|
</label>
|
|
{% endif %}
|
|
{% endif %}
|
|
</div>
|
|
|
|
{% if page.draft.global %}
|
|
<p class="hint">{{ nav.tr("builder-shared-table-hint") }}</p>
|
|
{% endif %}
|
|
|
|
{% if page.draft.columns.pending_is_compound() %}
|
|
<div class="compound-note">
|
|
<p><code>{{ page.draft.columns.pending_compound_name() }}</code> {{ nav.tr("builder-compound-intro") }}</p>
|
|
<ul>
|
|
{% 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. #}
|
|
<li><code>{{ generated.name }}</code> <span class="hint">{{ generated.data_type }}{% if generated.inherits_currency %} — {{ nav.tr("builder-inherits-currency-hint") }}{% endif %}</span></li>
|
|
{% endfor %}
|
|
{% if page.draft.columns.pending_compound_name() == "accounting" %}
|
|
<li><code>account_id</code> <span class="hint">{{ nav.tr("builder-account-system-hint")|safe }}</span></li>
|
|
{% endif %}
|
|
</ul>
|
|
<p class="hint">{{ nav.tr("builder-rename-generated-hint")|safe }}</p>
|
|
<p class="hint">{{ nav.tr_args("builder-reserved-hint", [("type", page.draft.columns.pending_compound_name())])|safe }}</p>
|
|
</div>
|
|
{% endif %}
|
|
<button type="button" class="secondary" hx-post="/admin/tables/builder" hx-include="#table-form"
|
|
hx-target="#builder" hx-swap="innerHTML" hx-vals='{"action": "add-column"}'>{{ nav.tr("builder-add-column-button") }}</button>
|
|
</section>
|
|
|
|
<section class="builder-section">
|
|
<h2>{{ nav.tr("builder-columns-heading") }} <span class="count">{{ page.draft.columns.added.len() }}</span></h2>
|
|
{% if page.draft.columns.is_empty() %}
|
|
<p class="hint">{{ nav.tr("builder-no-columns") }} <em>{{ nav.tr("builder-add-column-button") }}</em>.</p>
|
|
{% else %}
|
|
<p class="hint">{{ nav.tr("builder-columns-order-hint") }}</p>
|
|
<table class="builder-table">
|
|
<thead><tr><th>{{ nav.tr("builder-th-order") }}</th><th>{{ nav.tr("builder-th-name") }}</th><th>{{ nav.tr("builder-th-type") }}</th><th>{{ nav.tr("builder-th-indexed") }}</th><th>{{ nav.tr("builder-th-options") }}</th><th></th></tr></thead>
|
|
<tbody>
|
|
{#
|
|
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() %}
|
|
<tr {% if row.index.is_none() %}class="generated-row"{% endif %}>
|
|
<td class="order">
|
|
{% if let Some(index) = row.index %}
|
|
<button type="button" class="toggle move" aria-label="{{ nav.tr_args("builder-move-up", [("name", row.name.clone())]) }}"
|
|
{% if row.first %}disabled{% else %}hx-post="/admin/tables/builder" hx-include="#table-form"
|
|
hx-target="#builder" hx-swap="innerHTML"
|
|
hx-vals='{"action": "move-column-up", "index": "{{ index }}"}'{% endif %}>↑</button>
|
|
<button type="button" class="toggle move" aria-label="{{ nav.tr_args("builder-move-down", [("name", row.name.clone())]) }}"
|
|
{% if row.last %}disabled{% else %}hx-post="/admin/tables/builder" hx-include="#table-form"
|
|
hx-target="#builder" hx-swap="innerHTML"
|
|
hx-vals='{"action": "move-column-down", "index": "{{ index }}"}'{% endif %}>↓</button>
|
|
{% else %}
|
|
<span class="hint">↳</span>
|
|
{% endif %}
|
|
</td>
|
|
<td>
|
|
<code>{{ row.name }}</code>
|
|
{# The list only reports a rename; it is asked for below, so that
|
|
this column stays something to read. #}
|
|
{% if !row.alias.is_empty() %}<span class="tag">{{ nav.tr_args("builder-renamed-to", [("alias", row.alias.clone())]) }}</span>{% endif %}
|
|
</td>
|
|
<td>{{ row.data_type }}</td>
|
|
<td>
|
|
{# 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 %}
|
|
<button type="button" class="toggle" hx-post="/admin/tables/builder" hx-include="#table-form"
|
|
hx-target="#builder" hx-swap="innerHTML"
|
|
hx-vals='{"action": "toggle-index", "index": "{{ index }}"}'>
|
|
{% if row.indexed %}[x]{% else %}[ ]{% endif %}
|
|
</button>
|
|
{% 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. #}
|
|
<span class="tag">{{ nav.tr("td-indexed-automatically-tag") }}</span>
|
|
{% else %}
|
|
<span class="hint">—</span>
|
|
{% endif %}
|
|
</td>
|
|
<td>{% for tag in row.tags %}<span class="tag">{{ tag }}</span>{% endfor %}</td>
|
|
<td>
|
|
{% if let Some(index) = row.index %}
|
|
<button type="button" class="danger" hx-post="/admin/tables/builder" hx-include="#table-form"
|
|
hx-target="#builder" hx-swap="innerHTML"
|
|
hx-vals='{"action": "remove-column", "index": "{{ index }}"}'>{{ nav.tr("common-remove") }}</button>
|
|
{% endif %}
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
{% endif %}
|
|
|
|
{# The draft itself: one set of fields per column, in order. #}
|
|
{% for column in page.draft.columns.added %}
|
|
<input type="hidden" name="column_names" value="{{ column.name }}">
|
|
<input type="hidden" name="column_types" value="{{ column.data_type }}">
|
|
<input type="hidden" name="column_indexed" value="{% if column.indexed %}yes{% else %}no{% endif %}">
|
|
<input type="hidden" name="column_quantity_ledger" value="{% if column.quantity_ledger %}yes{% else %}no{% endif %}">
|
|
<input type="hidden" name="column_required" value="{% if column.required %}yes{% else %}no{% endif %}">
|
|
<input type="hidden" name="column_rounding" value="{{ column.money_mode.label() }}">
|
|
<input type="hidden" name="column_currencies" value="{{ column.currency }}">
|
|
{% endfor %}
|
|
</section>
|
|
|
|
{#
|
|
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 `<details>`, so the default — the backend's names — costs
|
|
nothing to read past. Fields inside a closed `<details>` are still part of the
|
|
form, so the aliases travel with every builder post either way.
|
|
#}
|
|
{% if !page.alias_rows().is_empty() %}
|
|
<section class="builder-section">
|
|
{# 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. #}
|
|
<details class="rename-generated" {% if page.draft.has_generated_aliases() %}open{% endif %}>
|
|
<summary>{{ nav.tr("builder-rename-generated") }}</summary>
|
|
<p class="hint">{{ nav.tr("builder-rename-generated-hint2") }}</p>
|
|
<ul class="rename-list">
|
|
{% for row in page.alias_rows() %}
|
|
<li>
|
|
<code>{{ row.source }}</code>
|
|
<span class="hint">{{ row.data_type }}</span>
|
|
<input type="hidden" name="generated_alias_sources" value="{{ row.source }}">
|
|
<input name="generated_alias_names" value="{{ row.alias }}"
|
|
aria-label="{{ nav.tr_args("builder-rename-aria", [("name", row.source.clone())]) }}" placeholder="{{ row.source }}">
|
|
</li>
|
|
{% endfor %}
|
|
</ul>
|
|
{#
|
|
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.
|
|
#}
|
|
<button type="button" class="secondary" hx-post="/admin/tables/builder" hx-include="#table-form"
|
|
hx-target="#builder" hx-swap="innerHTML"
|
|
hx-vals='{"action": "apply-generated-names"}'>{{ nav.tr("builder-apply-names") }}</button>
|
|
</details>
|
|
</section>
|
|
{% endif %}
|
|
|
|
<section class="builder-section">
|
|
{% for table in page.draft.relation_tables %}
|
|
<input type="hidden" name="relation_tables" value="{{ table }}">
|
|
{% endfor %}
|
|
</section>
|
|
|
|
<section class="builder-section">
|
|
<h2>{{ nav.tr("builder-row-display") }}</h2>
|
|
<p class="hint">{{ nav.tr("builder-row-display-hint")|safe }}</p>
|
|
<ul class="builder-list">
|
|
{% for candidate in page.row_display_candidates() %}
|
|
<li>
|
|
<button type="button" class="toggle" hx-post="/admin/tables/builder" hx-include="#table-form"
|
|
hx-target="#builder" hx-swap="innerHTML"
|
|
hx-vals='{"action": "toggle-display", "index": "{{ candidate.index }}"}'>{{ candidate.mark() }}</button>
|
|
<code>{{ candidate.name }}</code>
|
|
</li>
|
|
{% endfor %}
|
|
</ul>
|
|
|
|
{% for display in page.draft.row_display_columns %}
|
|
<input type="hidden" name="row_display_columns" value="{{ display }}">
|
|
{% endfor %}
|
|
</section>
|
|
|
|
<section class="builder-section">
|
|
<h2>{{ nav.tr("builder-preview") }}</h2>
|
|
<table class="builder-table preview">
|
|
<thead><tr><th></th><th>{{ nav.tr("builder-preview-th-column") }}</th><th>{{ nav.tr("builder-preview-th-type") }}</th><th>{{ nav.tr("builder-preview-th-options") }}</th><th>{{ nav.tr("builder-preview-th-source") }}</th></tr></thead>
|
|
<tbody>
|
|
{% for row in page.draft.preview_rows(nav.locale) %}
|
|
<tr class="source-{{ row.source }}">
|
|
<td class="mark">{{ row.mark }}</td>
|
|
<td><code>{{ row.column }}</code></td>
|
|
<td>{{ row.data_type }}</td>
|
|
<td>{{ row.option }}</td>
|
|
<td>{{ row.source }}</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</section>
|
|
|
|
<div class="form-actions">
|
|
<a href="/admin">{{ nav.tr("common-cancel") }}</a>
|
|
<button type="submit">{{ nav.tr("builder-create-table") }}</button>
|
|
</div>
|