add table is better now
This commit is contained in:
@@ -1,42 +1,24 @@
|
||||
{# GET /admin/tables/new — crate::pages::add_table::ui::AddTablePage #}
|
||||
{% extends "ui/form_page.html" %}
|
||||
{% import "ui/alert.html" as alert %}
|
||||
|
||||
{% block title %}Add table{% endblock %}
|
||||
{% block eyebrow %}Table definition{% endblock %}
|
||||
{% block heading %}Add table{% endblock %}
|
||||
{% block lead %}<p>Create a table through the existing gRPC table-definition service.</p>{% endblock %}
|
||||
{% block lead %}<p>Build the table one column at a time, the same way the terminal client does.</p>{% endblock %}
|
||||
|
||||
{% block form %}
|
||||
<form hx-post="/admin/tables" hx-target="#submission-status" hx-swap="innerHTML"
|
||||
{#
|
||||
One form holds the whole draft. `#builder` is what every interaction swaps,
|
||||
so the form element itself — and therefore the submit target — survives.
|
||||
#}
|
||||
<form id="table-form" hx-post="/admin/tables" hx-target="#builder" hx-swap="innerHTML"
|
||||
hx-disabled-elt="button[type=submit]">
|
||||
<div class="form-grid">
|
||||
<label>Profile
|
||||
<select name="profile_name" required>
|
||||
<option value="">Choose a profile</option>
|
||||
{% for profile in page.profiles %}
|
||||
<option value="{{ profile }}" {% if page.form.profile_name == *profile %}selected{% endif %}>{{ profile }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</label>
|
||||
<label>Table name<input name="table_name" value="{{ page.form.table_name }}" required placeholder="invoices"></label>
|
||||
<label class="wide">Columns
|
||||
<textarea name="columns" rows="9" required
|
||||
placeholder="number: text:indexed issued_on: date stock: int:quantity-ledger">{{ page.form.columns }}</textarea>
|
||||
<small>One per line: <code>name: type: optional flags</code>. Flags: indexed, half-up, quantity-ledger.</small>
|
||||
</label>
|
||||
<label>Additional indexed columns<input name="indexed_columns" value="{{ page.form.indexed_columns }}" placeholder="number, issued_on"></label>
|
||||
<label>Base currency<input name="base_currency" value="{{ page.form.base_currency }}" maxlength="3" placeholder="EUR"></label>
|
||||
<label>Required links<input name="required_links" value="{{ page.form.required_links }}" placeholder="customer, address"></label>
|
||||
<label>Optional links<input name="optional_links" value="{{ page.form.optional_links }}" placeholder="project"></label>
|
||||
<label>Row display columns<input name="row_display_columns" value="{{ page.form.row_display_columns }}" placeholder="name, ico"></label>
|
||||
</div>
|
||||
<div id="submission-status" aria-live="polite">
|
||||
{%- if let Some(message) = page.error %}{% call alert::error("Could not create the table", message) %}{% endcall %}{% endif -%}
|
||||
</div>
|
||||
<div class="form-actions">
|
||||
<a href="/admin">Cancel</a>
|
||||
<button type="submit">Create table</button>
|
||||
<div id="builder" aria-live="polite">
|
||||
{% include "pages/add_table/builder.html" %}
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<datalist id="currency-codes">
|
||||
{% for code in currency_codes %}<option value="{{ code }}"></option>{% endfor %}
|
||||
</datalist>
|
||||
{% endblock %}
|
||||
|
||||
237
web/templates/pages/add_table/builder.html
Normal file
237
web/templates/pages/add_table/builder.html
Normal file
@@ -0,0 +1,237 @@
|
||||
{#
|
||||
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 %}
|
||||
|
||||
{%- if let Some(message) = page.error %}{% call alert::error("Could not continue", message) %}{% endcall %}{% endif -%}
|
||||
{%- if let Some(message) = page.status %}{% call alert::success("Draft updated", message) %}{% endcall %}{% endif -%}
|
||||
|
||||
<section class="builder-section">
|
||||
<h2>Table</h2>
|
||||
<div class="form-grid">
|
||||
<label>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="">Choose a 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 %}>+ New profile…</option>
|
||||
</select>
|
||||
</label>
|
||||
|
||||
{% if page.draft.show_profile_name_input() %}
|
||||
<label>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>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 %}
|
||||
|
||||
<label>Table name
|
||||
<input name="table_name" value="{{ page.draft.table_name }}" placeholder="invoice"
|
||||
hx-post="/admin/tables/builder" hx-trigger="change" hx-include="#table-form"
|
||||
hx-target="#builder" hx-swap="innerHTML" hx-vals='{"action": "refresh"}'>
|
||||
</label>
|
||||
|
||||
{% if page.draft.show_base_currency() %}
|
||||
<label>Base currency
|
||||
<input name="base_currency" value="{{ page.draft.base_currency }}" list="currency-codes"
|
||||
maxlength="3" placeholder="EUR">
|
||||
<small>Required while the table has a MONEY or ACCOUNTING column.</small>
|
||||
</label>
|
||||
{% else %}
|
||||
<input type="hidden" name="base_currency" value="{{ page.draft.base_currency }}">
|
||||
{% endif %}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="builder-section">
|
||||
<h2>Add a column</h2>
|
||||
<div class="form-grid">
|
||||
<label>Column name
|
||||
<input name="column_name_input" value="{{ page.draft.column_name_input }}" placeholder="number">
|
||||
</label>
|
||||
<label>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="">Choose a type</option>
|
||||
{% for column_type in column_types %}
|
||||
<option value="{{ column_type }}" {% if page.draft.column_type_input == *column_type %}selected{% endif %}>{{ column_type }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</label>
|
||||
|
||||
{% if page.draft.show_temporal_type() %}
|
||||
<label>Temporal type
|
||||
<select name="temporal_type_input">
|
||||
<option value="">Choose a temporal type</option>
|
||||
{% for temporal_type in temporal_types %}
|
||||
<option value="{{ temporal_type }}" {% if page.draft.temporal_type_input == *temporal_type %}selected{% endif %}>{{ temporal_type }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</label>
|
||||
{% endif %}
|
||||
|
||||
{% if page.draft.show_gtin_type() %}
|
||||
<label>GTIN type
|
||||
<select name="gtin_type_input">
|
||||
<option value="">Choose a GTIN length</option>
|
||||
{% for gtin_type in gtin_types %}
|
||||
<option value="{{ gtin_type }}" {% if page.draft.gtin_type_input == *gtin_type %}selected{% endif %}>GTIN-{{ gtin_type }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</label>
|
||||
{% endif %}
|
||||
|
||||
{% if page.draft.show_rounding() %}
|
||||
<label>Rounding
|
||||
<select name="column_rounding_input">
|
||||
<option value="none" {% if page.draft.column_rounding_input != "half-up" %}selected{% endif %}>none</option>
|
||||
<option value="half-up" {% if page.draft.column_rounding_input == "half-up" %}selected{% endif %}>half-up</option>
|
||||
</select>
|
||||
</label>
|
||||
{% endif %}
|
||||
|
||||
<label>Indexing
|
||||
<select name="column_indexing_input">
|
||||
<option value="no" {% if page.draft.column_indexing_input != "yes" %}selected{% endif %}>no</option>
|
||||
<option value="yes" {% if page.draft.column_indexing_input == "yes" %}selected{% endif %}>yes</option>
|
||||
</select>
|
||||
</label>
|
||||
<label>Quantity ledger
|
||||
<select name="column_quantity_ledger_input">
|
||||
<option value="no" {% if page.draft.column_quantity_ledger_input != "yes" %}selected{% endif %}>no</option>
|
||||
<option value="yes" {% if page.draft.column_quantity_ledger_input == "yes" %}selected{% endif %}>yes</option>
|
||||
</select>
|
||||
<small>INT, BIGINT, DECIMAL or MONEY only.</small>
|
||||
</label>
|
||||
</div>
|
||||
<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"}'>Add column</button>
|
||||
</section>
|
||||
|
||||
<section class="builder-section">
|
||||
<h2>Columns <span class="count">{{ page.draft.columns.len() }}</span></h2>
|
||||
{% if page.draft.columns.is_empty() %}
|
||||
<p class="hint">No columns yet. Describe one above and press <em>Add column</em>.</p>
|
||||
{% else %}
|
||||
<table class="builder-table">
|
||||
<thead><tr><th>Name</th><th>Type</th><th>Indexed</th><th>Options</th><th></th></tr></thead>
|
||||
<tbody>
|
||||
{% for column in page.draft.columns %}
|
||||
<tr>
|
||||
<td><code>{{ column.name }}</code></td>
|
||||
<td>{{ column.data_type }}</td>
|
||||
<td>
|
||||
<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": "{{ loop.index0 }}"}'>
|
||||
{% if column.indexed %}[x]{% else %}[ ]{% endif %}
|
||||
</button>
|
||||
</td>
|
||||
<td>
|
||||
{% if column.quantity_ledger %}<span class="tag">quantity ledger</span>{% endif %}
|
||||
{% if !column.option_label().is_empty() %}<span class="tag">{{ column.option_label() }}</span>{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
<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": "{{ loop.index0 }}"}'>Remove</button>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% endif %}
|
||||
|
||||
{# The draft itself: one set of fields per column, in order. #}
|
||||
{% for column in page.draft.columns %}
|
||||
<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_rounding" value="{{ column.money_mode.label() }}">
|
||||
{% endfor %}
|
||||
</section>
|
||||
|
||||
<section class="builder-section">
|
||||
<h2>Relations</h2>
|
||||
{% if page.draft.links.is_empty() %}
|
||||
<p class="hint">No other tables in this profile to link to.</p>
|
||||
{% else %}
|
||||
<p class="hint">Each active relation adds a <code><table>_id</code> column.</p>
|
||||
<ul class="builder-list">
|
||||
{% for link in page.draft.links %}
|
||||
<li>
|
||||
<button type="button" class="toggle mode-{{ link.mode.label() }}" hx-post="/admin/tables/builder"
|
||||
hx-include="#table-form" hx-target="#builder" hx-swap="innerHTML"
|
||||
hx-vals='{"action": "cycle-link", "index": "{{ loop.index0 }}"}'>{{ link.mode.label() }}</button>
|
||||
<code>{{ link.linked_table_name }}</code>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
|
||||
{% for link in page.draft.links %}
|
||||
<input type="hidden" name="link_tables" value="{{ link.linked_table_name }}">
|
||||
<input type="hidden" name="link_modes" value="{{ link.mode.label() }}">
|
||||
{% endfor %}
|
||||
</section>
|
||||
|
||||
<section class="builder-section">
|
||||
<h2>Row display columns</h2>
|
||||
<p class="hint">What identifies a row to users, in the order chosen. Pick <code>id</code> to clear the list.</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>Table definition preview</h2>
|
||||
<table class="builder-table preview">
|
||||
<thead><tr><th></th><th>Column</th><th>Type</th><th>Options</th><th>Source</th></tr></thead>
|
||||
<tbody>
|
||||
{% for row in page.draft.preview_rows() %}
|
||||
<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">Cancel</a>
|
||||
<button type="submit">Create table</button>
|
||||
</div>
|
||||
Reference in New Issue
Block a user