fixed rolodex page
This commit is contained in:
Binary file not shown.
10
app/main.py
10
app/main.py
@@ -2028,8 +2028,14 @@ async def rolodex_list(
|
|||||||
# Use EXISTS over join to avoid duplicate rows
|
# Use EXISTS over join to avoid duplicate rows
|
||||||
query = query.filter(Client.phones.any(Phone.phone_number.ilike(like_phone)))
|
query = query.filter(Client.phones.any(Phone.phone_number.ilike(like_phone)))
|
||||||
|
|
||||||
# Order by last then first for stable display
|
# Order by last then first for stable display (SQLite-safe nulls last)
|
||||||
query = query.order_by(Client.last_name.asc().nulls_last(), Client.first_name.asc().nulls_last())
|
# SQLite does not support "NULLS LAST"; emulate by sorting non-nulls first, then value
|
||||||
|
query = query.order_by(
|
||||||
|
Client.last_name.is_(None),
|
||||||
|
Client.last_name.asc(),
|
||||||
|
Client.first_name.is_(None),
|
||||||
|
Client.first_name.asc(),
|
||||||
|
)
|
||||||
|
|
||||||
total: int = query.count()
|
total: int = query.count()
|
||||||
total_pages: int = (total + page_size - 1) // page_size if total > 0 else 1
|
total_pages: int = (total + page_size - 1) // page_size if total > 0 else 1
|
||||||
|
|||||||
@@ -1,35 +1,4 @@
|
|||||||
{#
|
{# Jinja macros for reusable table patterns. #}
|
||||||
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 %}
|
|
||||||
|
|
||||||
<div class="col-12 text-muted small">{{ results_summary(start_index, end_index, total) }}</div>
|
|
||||||
|
|
||||||
{% 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 <tr>...</tr> 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) -%}
|
{% macro results_summary(start_index, end_index, total) -%}
|
||||||
{% if total and total > 0 %}
|
{% if total and total > 0 %}
|
||||||
|
|||||||
@@ -48,60 +48,74 @@
|
|||||||
{ 'title': 'Phones', 'width': '200px' },
|
{ 'title': 'Phones', 'width': '200px' },
|
||||||
{ 'title': 'Actions', 'width': '140px', 'align': 'end' },
|
{ 'title': 'Actions', 'width': '140px', 'align': 'end' },
|
||||||
] %}
|
] %}
|
||||||
{% call(answer_table(headers, form_action='/reports/phone-book', select_name='client_ids', enable_bulk=enable_bulk)) %}
|
<form method="post" action="/reports/phone-book" class="js-answer-table">
|
||||||
{% if clients and clients|length > 0 %}
|
<table class="table table-hover align-middle">
|
||||||
|
<thead class="table-light">
|
||||||
|
<tr>
|
||||||
|
{% if enable_bulk %}
|
||||||
|
<th style="width: 40px;"><input class="form-check-input js-select-all" type="checkbox"></th>
|
||||||
|
{% endif %}
|
||||||
|
{% for h in headers %}
|
||||||
|
<th{% if h.width %} style="width: {{ h.width }};"{% endif %}{% if h.align == 'end' %} class="text-end"{% endif %}>{{ h.title }}</th>
|
||||||
|
{% endfor %}
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% if clients and clients|length > 0 %}
|
||||||
{% for c in clients %}
|
{% for c in clients %}
|
||||||
<tr>
|
<tr>
|
||||||
{% if enable_bulk %}
|
{% if enable_bulk %}
|
||||||
<td><input class="form-check-input" type="checkbox" name="client_ids" value="{{ c.id }}"></td>
|
<td><input class="form-check-input" type="checkbox" name="client_ids" value="{{ c.id }}"></td>
|
||||||
|
{% endif %}
|
||||||
|
<td><span class="fw-semibold">{{ c.last_name or '' }}, {{ c.first_name or '' }}</span></td>
|
||||||
|
<td>{{ c.company or '' }}</td>
|
||||||
|
<td>{{ c.address or '' }}</td>
|
||||||
|
<td>{{ c.city or '' }}</td>
|
||||||
|
<td>{{ c.state or '' }}</td>
|
||||||
|
<td>{{ c.zip_code or '' }}</td>
|
||||||
|
<td>
|
||||||
|
{% if c.phones and c.phones|length > 0 %}
|
||||||
|
{% for p in c.phones[:3] %}
|
||||||
|
<span class="badge bg-light text-dark me-1">{{ p.phone_number }}</span>
|
||||||
|
{% endfor %}
|
||||||
|
{% else %}
|
||||||
|
<span class="text-muted">—</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<td><span class="fw-semibold">{{ c.last_name or '' }}, {{ c.first_name or '' }}</span></td>
|
</td>
|
||||||
<td>{{ c.company or '' }}</td>
|
<td class="text-end">
|
||||||
<td>{{ c.address or '' }}</td>
|
<a class="btn btn-sm btn-outline-primary" href="/rolodex/{{ c.id }}">
|
||||||
<td>{{ c.city or '' }}</td>
|
<i class="bi bi-person-lines-fill me-1"></i>View
|
||||||
<td>{{ c.state or '' }}</td>
|
</a>
|
||||||
<td>{{ c.zip_code or '' }}</td>
|
</td>
|
||||||
<td>
|
|
||||||
{% if c.phones and c.phones|length > 0 %}
|
|
||||||
{% for p in c.phones[:3] %}
|
|
||||||
<span class="badge bg-light text-dark me-1">{{ p.phone_number }}</span>
|
|
||||||
{% endfor %}
|
|
||||||
{% else %}
|
|
||||||
<span class="text-muted">—</span>
|
|
||||||
{% endif %}
|
|
||||||
</td>
|
|
||||||
<td class="text-end">
|
|
||||||
<a class="btn btn-sm btn-outline-primary" href="/rolodex/{{ c.id }}">
|
|
||||||
<i class="bi bi-person-lines-fill me-1"></i>View
|
|
||||||
</a>
|
|
||||||
</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% else %}
|
{% else %}
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="8" class="text-center text-muted py-4">No clients found.</td>
|
<td colspan="8" class="text-center text-muted py-4">No clients found.</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endcall %}
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</form>
|
||||||
|
|
||||||
{% if enable_bulk %}
|
{% if enable_bulk %}
|
||||||
{% call(bulk_actions_bar()) %}
|
<div class="d-flex gap-2 mb-2">
|
||||||
<button type="submit" class="btn btn-outline-secondary">
|
<button type="submit" class="btn btn-outline-secondary">
|
||||||
<i class="bi bi-journal-text me-1"></i>Phone Book (Selected)
|
<i class="bi bi-journal-text me-1"></i>Phone Book (Selected)
|
||||||
</button>
|
</button>
|
||||||
<a class="btn btn-outline-secondary" href="/reports/phone-book?format=csv{% if q %}&q={{ q | urlencode }}{% endif %}">
|
<a class="btn btn-outline-secondary" href="/reports/phone-book?format=csv{% if q %}&q={{ q | urlencode }}{% endif %}">
|
||||||
<i class="bi bi-filetype-csv me-1"></i>Phone Book CSV (Current Filter)
|
<i class="bi bi-filetype-csv me-1"></i>Phone Book CSV (Current Filter)
|
||||||
</a>
|
</a>
|
||||||
<a class="btn btn-outline-secondary js-submit-to" data-action="/reports/phone-book-address" href="#">
|
<a class="btn btn-outline-secondary js-submit-to" data-action="/reports/phone-book-address" href="#">
|
||||||
<i class="bi bi-journal-text me-1"></i>Phone+Address (Selected)
|
<i class="bi bi-journal-text me-1"></i>Phone+Address (Selected)
|
||||||
</a>
|
</a>
|
||||||
<a class="btn btn-outline-secondary js-submit-to" data-action="/reports/envelope" href="#">
|
<a class="btn btn-outline-secondary js-submit-to" data-action="/reports/envelope" href="#">
|
||||||
<i class="bi bi-envelope me-1"></i>Envelope (Selected)
|
<i class="bi bi-envelope me-1"></i>Envelope (Selected)
|
||||||
</a>
|
</a>
|
||||||
<a class="btn btn-outline-secondary js-submit-to" data-action="/reports/rolodex-info" href="#">
|
<a class="btn btn-outline-secondary js-submit-to" data-action="/reports/rolodex-info" href="#">
|
||||||
<i class="bi bi-card-text me-1"></i>Rolodex Info (Selected)
|
<i class="bi bi-card-text me-1"></i>Rolodex Info (Selected)
|
||||||
</a>
|
</a>
|
||||||
{% endcall %}
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
52060
data-import/b575ed85-1b50-469b-8d32-ed07a50cf16f.csv
Normal file
52060
data-import/b575ed85-1b50-469b-8d32-ed07a50cf16f.csv
Normal file
File diff suppressed because it is too large
Load Diff
52060
data-import/client_c381cb33-f886-42c1-96ed-fbe5078c26ae.csv
Normal file
52060
data-import/client_c381cb33-f886-42c1-96ed-fbe5078c26ae.csv
Normal file
File diff suppressed because it is too large
Load Diff
1
test_upload.csv
Normal file
1
test_upload.csv
Normal file
@@ -0,0 +1 @@
|
|||||||
|
id,name,email,phone
|
||||||
|
Reference in New Issue
Block a user