export works from web

This commit is contained in:
Priec
2026-08-15 19:05:59 +02:00
parent 10d9784b9b
commit e3104a367e
11 changed files with 199 additions and 92 deletions

View File

@@ -10,17 +10,21 @@
{# A real form post, not HTMX: the response is a file download. #}
<form method="post" action="/admin/export.csv">
<div class="form-grid">
<label>{{ nav.tr("export-profile") }}
<select name="profile_name" required>
<option value="">{{ nav.tr("export-choose-profile") }}</option>
<label>{{ nav.tr("export-scope") }}
<select name="profile_name" data-profile-select required>
<option value="">{{ nav.tr("export-choose-scope") }}</option>
{% for profile in page.catalog.profiles %}
<option value="{{ profile.name }}">{{ profile.name }}</option>
<option value="{{ profile.name }}">{{ profile.label }}</option>
{% endfor %}
</select>
</label>
<label>{{ nav.tr("export-tables") }}
<input name="table_names" list="export-tables" required placeholder="invoices, customers">
<small>{{ nav.tr("export-comma-hint") }}</small>
<select name="table_names" data-table-select required>
<option value="">{{ nav.tr("export-choose-table") }}</option>
{%- for profile in page.catalog.profiles %}{% for table in profile.tables %}
<option value="{{ table }}" data-profile="{{ profile.name }}">{{ table }}</option>
{%- endfor %}{% endfor %}
</select>
</label>
</div>
<label class="check">
@@ -28,12 +32,10 @@
{{ nav.tr("export-include-system") }}
</label>
<small>{{ nav.tr("export-include-system-hint") }}</small>
<datalist id="export-tables">
{%- for profile in page.catalog.profiles %}{% for table in profile.tables %}<option value="{{ table }}"></option>{% endfor %}{% endfor -%}
</datalist>
<div class="form-actions">
<a href="/">{{ nav.tr("common-cancel") }}</a>
<button type="submit">{{ nav.tr("export-download") }}</button>
</div>
</form>
{% include "pages/import_export/table_picker.html" %}
{% endblock %}

View File

@@ -11,22 +11,28 @@
<form hx-post="/admin/import" hx-target="#submission-status" hx-swap="innerHTML"
hx-disabled-elt="button[type=submit]">
<div class="form-grid">
<label>{{ nav.tr("import-profile") }}
<input name="profile_name" list="import-profiles" value="{{ page.form.profile_name }}" required placeholder="billing">
<label>{{ nav.tr("import-scope") }}
<select name="profile_name" data-profile-select required>
<option value="">{{ nav.tr("import-choose-scope") }}</option>
{% for profile in page.catalog.profiles %}
<option value="{{ profile.name }}"{% if page.form.profile_name == profile.name %} selected{% endif %}>{{ profile.label }}</option>
{% endfor %}
</select>
</label>
<label>{{ nav.tr("import-target-tables") }}
<select name="table_names" data-table-select required>
<option value="">{{ nav.tr("import-choose-table") }}</option>
{%- for profile in page.catalog.profiles %}{% for table in profile.tables %}
<option value="{{ table }}" data-profile="{{ profile.name }}"{% if page.form.table_names.contains(table) %} selected{% endif %}>{{ table }}</option>
{%- endfor %}{% endfor %}
</select>
</label>
<label>{{ nav.tr("import-target-tables") }}<input name="table_names" list="import-tables" value="{{ page.form.table_names }}" required placeholder="invoices, customers"></label>
<label class="wide">{{ nav.tr("import-csv-file") }}
<input type="file" accept=".csv,text/csv"
onchange="this.files[0]?.text().then(value =&gt; document.getElementById('csv-data').value = value)">
</label>
<label class="wide">{{ nav.tr("import-csv-data") }}<textarea id="csv-data" name="csv_data" rows="14" required>{{ page.form.csv_data }}</textarea></label>
</div>
<datalist id="import-profiles">
{% for profile in page.catalog.profiles %}<option value="{{ profile.name }}"></option>{% endfor %}
</datalist>
<datalist id="import-tables">
{%- for profile in page.catalog.profiles %}{% for table in profile.tables %}<option value="{{ table }}"></option>{% endfor %}{% endfor -%}
</datalist>
<div id="submission-status" aria-live="polite">
{%- if let Some(message) = page.error %}{% call alert::error(nav.locale, nav.tr("import-error-title"), message) %}{% endcall %}{% endif -%}
</div>
@@ -35,4 +41,5 @@
<button type="submit">{{ nav.tr("import-import-rows") }}</button>
</div>
</form>
{% include "pages/import_export/table_picker.html" %}
{% endblock %}

View File

@@ -0,0 +1,33 @@
{#
Leaves the table dropdown holding only the tables of the selected profile.
The options are rebuilt rather than hidden: `hidden` on an `<option>` is not
honoured everywhere, and a hidden option is still selectable with the
keyboard. With scripting off the dropdown lists every table it was rendered
with, and the server still refuses a table that is not in the profile.
#}
<script>
(function () {
const profileSelect = document.querySelector("[data-profile-select]");
const tableSelect = document.querySelector("[data-table-select]");
if (!profileSelect || !tableSelect) return;
const placeholder = tableSelect.options[0];
const all = Array.from(tableSelect.options).slice(1);
function sync() {
const chosen = tableSelect.value;
tableSelect.replaceChildren(placeholder);
for (const option of all) {
if (option.dataset.profile === profileSelect.value) {
tableSelect.append(option);
}
}
tableSelect.value = Array.from(tableSelect.options).some(
(option) => option.value === chosen,
)
? chosen
: "";
}
profileSelect.addEventListener("change", sync);
sync();
})();
</script>