diff --git a/TODO-Legacy.md b/TODO-Legacy.md index e05693b..111bdb3 100644 --- a/TODO-Legacy.md +++ b/TODO-Legacy.md @@ -9,10 +9,10 @@ Purpose: Track features and workflows from legacy Paradox (.SC) scripts to port - [x] CSV import: one-time and repeatable import from legacy CSVs - Example: Import `old-database/Office/FILES.csv` into `files` table; validate required fields and normalize dates; log row count and any rejects. -- [ ] Answer table pattern for query results +- [x] Answer table pattern for query results - Example: After searching `Rolodex`, show a paginated results view with bulk actions (reports, document assembly, return to full dataset). -- [ ] Field prompts/help (tooltips or side panel) +- [x] Field prompts/help (tooltips or side panel) - Example: When focusing `Files.File_Type`, show: "F1 to select area of law" as inline help text. - [ ] Structured logging/audit trail @@ -94,7 +94,7 @@ Purpose: Track features and workflows from legacy Paradox (.SC) scripts to port ### Plan Info - [ ] CRUD for plan catalog - - Example: Add a plan with `Plan_Id = \"ABC-123\"`, `Plan_Type = DB`, and memo details; searchable in Pensions and QDRO. + - Example: Add a plan with `Plan_Id = "ABC-123"`, `Plan_Type = DB`, and memo details; searchable in Pensions and QDRO. ### Pensions (annuity evaluator) - [ ] Life Expectancy Method (uses `LifeTabl`) diff --git a/app/__pycache__/main.cpython-313.pyc b/app/__pycache__/main.cpython-313.pyc index 748beca..64d47ab 100644 Binary files a/app/__pycache__/main.cpython-313.pyc and b/app/__pycache__/main.cpython-313.pyc differ diff --git a/app/templates/case.html b/app/templates/case.html index 7d65444..a142655 100644 --- a/app/templates/case.html +++ b/app/templates/case.html @@ -133,11 +133,12 @@ Case {{ case.file_no if case else '' }} · Delphi Database
+
Focus a field to see help.
- @@ -145,24 +146,24 @@ Case {{ case.file_no if case else '' }} · Delphi Database
-
- +
-
-
diff --git a/app/templates/partials/answer_table_macros.html b/app/templates/partials/answer_table_macros.html new file mode 100644 index 0000000..a425deb --- /dev/null +++ b/app/templates/partials/answer_table_macros.html @@ -0,0 +1,96 @@ +{# + Reusable macros for answer-table pattern: summary, table with selection, + bulk actions bar, and pagination controls. + + Usage in a page: + {% from "partials/answer_table_macros.html" import results_summary, pagination, answer_table, bulk_actions_bar %} + +
{{ results_summary(start_index, end_index, total) }}
+ + {% set headers = [ + { 'title': 'Name', 'width': '220px' }, + { 'title': 'Company' }, + { 'title': 'Address' }, + { 'title': 'City' }, + { 'title': 'State', 'width': '80px' }, + { 'title': 'ZIP', 'width': '110px' }, + { 'title': 'Phones', 'width': '200px' }, + { 'title': 'Actions', 'width': '140px', 'align': 'end' }, + ] %} + + {% call(answer_table(headers, form_action='/reports/phone-book', select_name='client_ids', enable_bulk=enable_bulk)) %} + {# render ... rows here #} + {% endcall %} + + {% if enable_bulk %} + {% call(bulk_actions_bar()) %} + {# render bulk action buttons/links here #} + {% endcall %} + {% endif %} + + {{ pagination('/rolodex', page, total_pages, page_size, {'q': q, 'phone': phone}) }} +#} + +{% macro results_summary(start_index, end_index, total) -%} + {% if total and total > 0 %} + Showing {{ start_index }}–{{ end_index }} of {{ total }} + {% else %} + No results + {% endif %} +{%- endmacro %} + +{% macro answer_table(headers, form_action=None, select_name='selected_ids', enable_bulk=False) -%} + + + + + {% if enable_bulk %} + + {% endif %} + {% for h in headers %} + {{ h.title }} + {% endfor %} + + + + {{ caller() }} + +
+ +{%- endmacro %} + +{% macro bulk_actions_bar() -%} +
+ {{ caller() }} +
+{%- endmacro %} + +{% macro pagination(base_url, page, total_pages, page_size, params=None) -%} + {% if total_pages and total_pages > 1 %} + + {% endif %} +{%- endmacro %} + + diff --git a/app/templates/rolodex.html b/app/templates/rolodex.html index 105378c..66dd3f6 100644 --- a/app/templates/rolodex.html +++ b/app/templates/rolodex.html @@ -2,6 +2,8 @@ {% block title %}Rolodex · Delphi Database{% endblock %} +{% from "partials/answer_table_macros.html" import results_summary, pagination, answer_table, bulk_actions_bar %} + {% block content %}
@@ -33,121 +35,72 @@
-
- {% if total and total > 0 %} - Showing {{ start_index }}–{{ end_index }} of {{ total }} - {% else %} - No results - {% endif %} -
+
{{ results_summary(start_index, end_index, total) }}
-
- - + {% set headers = [ + { 'title': 'Name', 'width': '220px' }, + { 'title': 'Company' }, + { 'title': 'Address' }, + { 'title': 'City' }, + { 'title': 'State', 'width': '80px' }, + { 'title': 'ZIP', 'width': '110px' }, + { 'title': 'Phones', 'width': '200px' }, + { 'title': 'Actions', 'width': '140px', 'align': 'end' }, + ] %} + {% call(answer_table(headers, form_action='/reports/phone-book', select_name='client_ids', enable_bulk=enable_bulk)) %} + {% if clients and clients|length > 0 %} + {% for c in clients %} {% if enable_bulk %} - + {% endif %} - - - - - - - - - - - - {% if clients and clients|length > 0 %} - {% for c in clients %} - - {% if enable_bulk %} - + + + + + + + - - - - - - - - - {% endfor %} - {% else %} - - - - {% endif %} - -
NameCompanyAddressCityStateZIPPhonesActions
- - {{ c.last_name or '' }}, {{ c.first_name or '' }}{{ c.company or '' }}{{ c.address or '' }}{{ c.city or '' }}{{ c.state or '' }}{{ c.zip_code or '' }} + {% if c.phones and c.phones|length > 0 %} + {% for p in c.phones[:3] %} + {{ p.phone_number }} + {% endfor %} + {% else %} + {% endif %} - - {{ c.last_name or '' }}, {{ c.first_name or '' }} - {{ c.company or '' }}{{ c.address or '' }}{{ c.city or '' }}{{ c.state or '' }}{{ c.zip_code or '' }} - {% if c.phones and c.phones|length > 0 %} - {% for p in c.phones[:3] %} - {{ p.phone_number }} - {% endfor %} - {% else %} - - {% endif %} - - - View - -
No clients found.
+ + + + View + + + + {% endfor %} + {% else %} + + No clients found. + + {% endif %} + {% endcall %} + {% if enable_bulk %} -
- - - Phone Book CSV (Current Filter) - -
+ {% call(bulk_actions_bar()) %} + + + Phone Book CSV (Current Filter) + + {% endcall %} {% endif %} -
- {% if total_pages and total_pages > 1 %} - - {% endif %} + {{ pagination('/rolodex', page, total_pages, page_size, {'q': q, 'phone': phone}) }}
-{% block extra_scripts %} - -{% endblock %} +{% block extra_scripts %}{% endblock %} {% endblock %} diff --git a/app/templates/rolodex_edit.html b/app/templates/rolodex_edit.html index cd30b9a..813dc90 100644 --- a/app/templates/rolodex_edit.html +++ b/app/templates/rolodex_edit.html @@ -16,18 +16,19 @@
+
Focus a field to see help.
- +
- +
- +
@@ -36,20 +37,20 @@
- +
- +
- +
- +
diff --git a/static/js/custom.js b/static/js/custom.js index 64cfa86..6708056 100644 --- a/static/js/custom.js +++ b/static/js/custom.js @@ -68,6 +68,39 @@ document.addEventListener('DOMContentLoaded', function() { if (qtyInput) qtyInput.addEventListener('input', recomputeAmount); if (rateInput) rateInput.addEventListener('input', recomputeAmount); + + // Generic select-all for answer tables + document.querySelectorAll('.js-answer-table').forEach(function(form) { + var selectAll = form.querySelector('.js-select-all'); + if (!selectAll) return; + selectAll.addEventListener('change', function() { + var checkboxes = form.querySelectorAll('input[type="checkbox"][name]'); + checkboxes.forEach(function(cb) { + if (cb !== selectAll) cb.checked = selectAll.checked; + }); + }); + }); + + // Field help: show contextual help from data-help on focus + function attachFieldHelp(container) { + if (!container) return; + var helpEl = container.querySelector('#fieldHelp'); + if (!helpEl) return; + container.querySelectorAll('input, select, textarea').forEach(function(field) { + field.addEventListener('focus', function() { + var text = field.getAttribute('data-help'); + if (text) helpEl.textContent = text; + }); + field.addEventListener('blur', function() { + // Optionally keep last help, or reset + }); + }); + } + + // Attach help to known forms/sections + document.querySelectorAll('form').forEach(function(form) { + attachFieldHelp(form.closest('.card-body') || form); + }); }); // Utility functions