refactor(ssr): render the bulk-code modal fully through Selmer
Move all markup in the Transaction Bulk Code modal out of Clojure and into
Selmer templates so bulk_code.clj only assembles data.
- Replace the inline sel/raw HTML strings and one Hiccup [:p] with templates:
head, form-errors, footer, account-entries, success-body.
- Render the expense-account grid from a {% for %} template (account-grid.html
+ account-row.html) driven by a per-row view-model (account-row-vm); the row
reuses the shared components/typeahead.html via a {% with %} include (no fork).
- Extract behaviour-preserving data-prep helpers reused by the view-model:
sc/typeahead-ctx, sc/money-input-attrs, sc/validated-field-classes,
sc/errors-str, edit/account-typeahead-ctx, edit/location-select-ctx.
Verified: REPL render parity + browser QA (add/remove row, typeahead select,
per-row location swap, percentage validation, submit, vendor auto-populate);
no JS errors.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
{# Wrapper around the expense-account grid (the body of the accounts validated-field). #}
|
||||
<div id="account-entries" class="space-y-3">{{ grid|safe }}</div>
|
||||
22
resources/templates/transaction-bulk-code/account-grid.html
Normal file
22
resources/templates/transaction-bulk-code/account-grid.html
Normal file
@@ -0,0 +1,22 @@
|
||||
{# Expense-account grid -- fully template-driven. A single for-loop over the per-row
|
||||
view-models (bulk-code/account-row-vm), each delegating to account-row.html. Replaces
|
||||
the Clojure data-grid / data-grid-row / data-grid-cell composition. The trailing
|
||||
"New account" button posts the whole #bulk-code-form (op=new-account). #}
|
||||
<div class="shrink overflow-y-scroll">
|
||||
<table class="w-full text-sm text-left text-gray-500 dark:text-gray-400 shrink">
|
||||
<thead class="text-xs text-gray-800 uppercase bg-gray-50 dark:bg-gray-700 dark:text-gray-400 group-[.raw]:sticky group-[.raw]:z-10 group-[.raw]:top-0">
|
||||
<tr>
|
||||
<th class="px-4 py-3" scope="col">Account</th>
|
||||
<th class="px-4 py-3 w-32" scope="col">Location</th>
|
||||
<th class="px-4 py-3 w-16" scope="col">%</th>
|
||||
<th class="px-4 py-3 w-16" scope="col"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for row in rows %}{% include "templates/transaction-bulk-code/account-row.html" %}{% endfor %}
|
||||
<tr class="new-row border-b dark:border-gray-600 group hover:bg-gray-100 dark:hover:bg-gray-700">
|
||||
<td class="px-4 py-2" colspan="4">{{ new_account_button|safe }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
35
resources/templates/transaction-bulk-code/account-row.html
Normal file
35
resources/templates/transaction-bulk-code/account-row.html
Normal file
@@ -0,0 +1,35 @@
|
||||
{# One expense-account row, read entirely from a loop-bound `row` view-model
|
||||
(bulk-code/account-row-vm). The account typeahead reuses the shared
|
||||
components/typeahead.html partial (its context mapped in from row.account via a with
|
||||
block); the location select, percentage input, and remove button are inlined plain
|
||||
HTML. The location cell (#account-location-N) swaps just itself on account change; the
|
||||
remove button swaps the whole #bulk-code-form. Every dynamic attribute arrives
|
||||
pre-serialized as a string. #}
|
||||
<tr class="{{ row.tr_classes }}"{{ row.tr_attrs|safe }}>
|
||||
<input type="hidden" name="{{ row.db_id_name }}"{% if row.db_id_value %} value="{{ row.db_id_value }}"{% endif %}>
|
||||
<td class="px-4 py-2">
|
||||
<div class="{{ row.account_field_classes }}">
|
||||
<div class="flex flex-col">{% with x_data=row.account.x_data x_model=row.account.x_model key=row.account.key disabled=row.account.disabled a_class=row.account.a_class a_xinit=row.account.a_xinit search_class=row.account.search_class placeholder=row.account.placeholder hidden_attrs=row.account.hidden_attrs %}{% include "templates/components/typeahead.html" %}{% endwith %}</div>
|
||||
<p class="mt-2 text-xs text-red-600 dark:text-red-500 h-4">{{ row.account_error }}</p>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-4 py-2" id="{{ row.location_cell_id }}">
|
||||
<div class="{{ row.location_field_classes }}"{{ row.location_field_attrs|safe }}>
|
||||
<select name="{{ row.location.name }}" class="{{ row.location.classes }}">
|
||||
{% for opt in row.location.options %}<option value="{{ opt.value }}"{% if opt.selected %} selected{% endif %}>{{ opt.label }}</option>{% endfor %}
|
||||
</select>
|
||||
<p class="mt-2 text-xs text-red-600 dark:text-red-500 h-4">{{ row.location_error }}</p>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-4 py-2">
|
||||
<div class="{{ row.pct_field_classes }}">
|
||||
<input {{ row.pct_attrs|safe }}>
|
||||
<p class="mt-2 text-xs text-red-600 dark:text-red-500 h-4">{{ row.pct_error }}</p>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-4 py-2 align-top">
|
||||
<a class="account-remove-action p-3 inline-flex items-center justify-center bg-white dark:bg-gray-600 items-center text-sm font-medium border border-gray-300 dark:border-gray-700 text-center text-gray-500 hover:text-gray-800 rounded-lg dark:text-gray-400 dark:hover:text-gray-100"{{ row.remove_attrs|safe }}>
|
||||
<div class="h-4 w-4">{% include "templates/components/svg-x.html" %}</div>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
5
resources/templates/transaction-bulk-code/footer.html
Normal file
5
resources/templates/transaction-bulk-code/footer.html
Normal file
@@ -0,0 +1,5 @@
|
||||
{# Modal footer: the form-errors sink on the left, the Save button on the right.
|
||||
Both are pre-rendered fragments (errors sink = form-errors.html, save = sc/button). #}
|
||||
<div class="flex justify-end">
|
||||
<div class="flex items-baseline gap-x-4">{{ form_errors|safe }}{{ save_button|safe }}</div>
|
||||
</div>
|
||||
@@ -0,0 +1,4 @@
|
||||
{# Submit-error sink. A 4xx submit swaps the inner `.error-content` (hx-target-400);
|
||||
the span is present only when there are form-level errors, matching the prior
|
||||
hand-rolled markup byte-for-byte. #}
|
||||
<div id="form-errors">{% if errors_str %}<span class="error-content"><p class="mt-2 text-xs text-red-600 dark:text-red-500 h-4">{{ errors_str }}</p></span>{% endif %}</div>
|
||||
2
resources/templates/transaction-bulk-code/head.html
Normal file
2
resources/templates/transaction-bulk-code/head.html
Normal file
@@ -0,0 +1,2 @@
|
||||
{# Modal header label: how many transactions this bulk-code operation will touch. #}
|
||||
<div class="p-2">Bulk editing {{ count }} transactions</div>
|
||||
@@ -0,0 +1,2 @@
|
||||
{# Post-submit confirmation message embedded in the shared success modal. #}
|
||||
<p>Successfully coded {{ count }} transactions.</p>
|
||||
Reference in New Issue
Block a user