feat(portal): real-time search + filter pills on 4 FP list pages

Replaces the tab nav / portal.portal_searchbar on the 4 FP list
pages with the new fp_portal_list_controls macro (filter pills +
search input + sort dropdown) and drops portal_pager in favour of
client-side filtering of up to 500 records:

- Quote Requests (/my/quote_requests):
    filters: All / Active / Converted / Declined
    sorts:   Newest / Reference / Status
    extra search fields: contact_name, contact_email, line.part_number,
                         line.description, line.product_id.default_code

- Work Orders (/my/jobs, cards layout):
    filters: All / Active / Ready to Ship / Complete
    sorts:   Newest / Reference / Status
    extra search fields per card: part_catalog.part_number, part_catalog.name,
                                  sale_order.name, sale_order.client_order_ref,
                                  job.notes

- Certifications (/my/certifications):
    no filters (all rows are terminal CoC jobs)
    sorts:   Newest / Reference
    extra search fields: part name, processes (already in card text)

- Packing Slips / Deliveries (/my/deliveries):
    no filters (all rows are state=done)
    sorts:   Newest / Reference
    adds a visible Origin column (sale order ref) so customers can
    locate a slip by the SO it came from

Each route accepts ?filter_state=... and ?sortby=... query params,
returns up to 500 records, and passes result_total + clipped to the
template so the macro can render a "showing latest 500 of N" notice
when the cap is hit.

Hidden <td class="d-none"> cells inside each row carry extra terms
that aren't displayed but are matched by the JS textContent scan.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsinghpal
2026-05-18 00:06:18 -04:00
parent d9bdbd8e18
commit b27f68b8d5
2 changed files with 251 additions and 135 deletions

View File

@@ -7,7 +7,7 @@
<odoo>
<!-- ================================================================== -->
<!-- QUOTE REQUESTS — list with tabs (Active / Converted / Declined) -->
<!-- QUOTE REQUESTS — list with filter pills + real-time search -->
<!-- ================================================================== -->
<template id="portal_my_quote_requests" name="My Quote Requests">
<t t-call="portal.portal_layout">
@@ -15,15 +15,19 @@
<t t-set="title">Quote Requests</t>
</t>
<!-- Tab navigation -->
<ul class="nav nav-tabs mb-3">
<li class="nav-item" t-foreach="searchbar_filters" t-as="f">
<a t-attf-class="nav-link #{'active' if filterby == f else ''}"
t-attf-href="/my/quote_requests?filterby=#{f}&amp;sortby=#{sortby}">
<t t-out="searchbar_filters[f]['label']"/>
</a>
</li>
</ul>
<!-- Filter pills + search + sort strip -->
<t t-call="fusion_plating_portal.fp_portal_list_controls">
<t t-set="filters" t-value="filters"/>
<t t-set="active_filter" t-value="filter_state"/>
<t t-set="sorts" t-value="sorts"/>
<t t-set="active_sort" t-value="sortby"/>
<t t-set="search" t-value="search"/>
<t t-set="url" t-value="url"/>
<t t-set="extra_qs" t-value="extra_qs"/>
<t t-set="target" t-value="target"/>
<t t-set="result_total" t-value="result_total"/>
<t t-set="clipped" t-value="clipped"/>
</t>
<div class="d-flex justify-content-end mb-3">
<a href="/my/quote_requests/new" class="o_fp_btn_primary">
@@ -49,7 +53,7 @@
<th class="text-end">Status</th>
</tr>
</thead>
<tbody>
<tbody class="o_fp_qr_filterable">
<tr t-foreach="quote_requests" t-as="qr">
<td>
<a t-att-href="'/my/quote_requests/%s' % qr.id"
@@ -73,6 +77,16 @@
<t t-set="label" t-value="dict(qr._fields['state']._description_selection(qr.env)).get(qr.state)"/>
</t>
</td>
<!-- Hidden search fields: contact, part numbers, descriptions -->
<td class="d-none" aria-hidden="true">
<span t-out="qr.contact_name or ''"/>
<span t-out="qr.contact_email or ''"/>
<t t-foreach="qr.line_ids" t-as="ln">
<span t-out="ln.part_number or ''"/>
<span t-out="ln.description or ''"/>
<span t-if="ln.product_id" t-out="ln.product_id.default_code or ''"/>
</t>
</td>
</tr>
</tbody>
</t>
@@ -417,7 +431,7 @@
</template>
<!-- ================================================================== -->
<!-- JOBS — list with segmented progress bars -->
<!-- JOBS — list with filter pills + real-time search (cards layout) -->
<!-- ================================================================== -->
<template id="portal_my_jobs" name="My Work Orders">
<t t-call="portal.portal_layout">
@@ -425,6 +439,20 @@
<t t-set="title">Work Orders</t>
</t>
<!-- Filter pills + search + sort strip -->
<t t-call="fusion_plating_portal.fp_portal_list_controls">
<t t-set="filters" t-value="filters"/>
<t t-set="active_filter" t-value="filter_state"/>
<t t-set="sorts" t-value="sorts"/>
<t t-set="active_sort" t-value="sortby"/>
<t t-set="search" t-value="search"/>
<t t-set="url" t-value="url"/>
<t t-set="extra_qs" t-value="extra_qs"/>
<t t-set="target" t-value="target"/>
<t t-set="result_total" t-value="result_total"/>
<t t-set="clipped" t-value="clipped"/>
</t>
<t t-if="not jobs">
<div class="o_fp_card text-center text-muted">
<p class="mb-2">You have no plating jobs yet.</p>
@@ -432,11 +460,32 @@
</div>
</t>
<t t-if="jobs">
<div class="o_fp_dashboard">
<!-- id="fp_jobs_list" is the data-fp-target for the search JS -->
<div class="o_fp_dashboard" id="fp_jobs_list">
<t t-foreach="jobs" t-as="job">
<t t-call="fusion_plating_portal.fp_portal_job_card">
<t t-set="job" t-value="job"/>
</t>
<!-- Wrapper div is the filterable row unit.
Hidden span carries extra search terms that
are not visible in the card UI. -->
<div class="o_fp_job_card_wrap">
<t t-call="fusion_plating_portal.fp_portal_job_card">
<t t-set="job" t-value="job"/>
</t>
<!-- Extra hidden search terms for this card -->
<t t-set="_backend_job" t-value="job.x_fc_job_id if 'x_fc_job_id' in job._fields else False"/>
<t t-set="_so" t-value="_backend_job.sale_order_id if _backend_job and 'sale_order_id' in _backend_job._fields else False"/>
<t t-set="_part" t-value="_backend_job.part_catalog_id if _backend_job and 'part_catalog_id' in _backend_job._fields else False"/>
<span class="d-none" aria-hidden="true">
<t t-if="_part">
<t t-out="_part.part_number or ''"/>
<t t-out="_part.name or ''"/>
</t>
<t t-if="_so">
<t t-out="_so.name or ''"/>
<t t-out="_so.client_order_ref or ''"/>
</t>
<t t-out="job.notes or ''"/>
</span>
</div>
</t>
</div>
</t>
@@ -574,7 +623,7 @@
</template>
<!-- ================================================================== -->
<!-- DELIVERIES / PACKING SLIPS — list -->
<!-- DELIVERIES / PACKING SLIPS — list with search + sort -->
<!-- ================================================================== -->
<template id="portal_my_deliveries" name="My Deliveries">
<t t-call="portal.portal_layout">
@@ -582,6 +631,20 @@
<t t-set="title">Packing Slips / Deliveries</t>
</t>
<!-- Search + sort strip (no filter pills — all rows are delivered) -->
<t t-call="fusion_plating_portal.fp_portal_list_controls">
<t t-set="filters" t-value="filters"/>
<t t-set="active_filter" t-value="filter_state"/>
<t t-set="sorts" t-value="sorts"/>
<t t-set="active_sort" t-value="sortby"/>
<t t-set="search" t-value="search"/>
<t t-set="url" t-value="url"/>
<t t-set="extra_qs" t-value="extra_qs"/>
<t t-set="target" t-value="target"/>
<t t-set="result_total" t-value="result_total"/>
<t t-set="clipped" t-value="clipped"/>
</t>
<t t-if="not deliveries">
<div class="o_fp_portal_card card bg-body-tertiary border-0 p-4 text-center">
<p class="text-muted mb-0">No deliveries found.</p>
@@ -591,13 +654,17 @@
<thead>
<tr class="active">
<th>Reference</th>
<th>Origin</th>
<th>Date</th>
<th class="text-end">Status</th>
</tr>
</thead>
<tbody>
<tbody class="o_fp_deliveries_filterable">
<tr t-foreach="deliveries" t-as="dlv">
<td t-out="dlv.name"/>
<td>
<span t-out="dlv.origin or ''"/>
</td>
<td>
<span t-if="dlv.date_done"
t-field="dlv.date_done"
@@ -608,6 +675,13 @@
<span class="o_fp_badge_dot"/>Delivered
</span>
</td>
<!-- Hidden: partner ref / customer PO on the origin SO -->
<td class="d-none" aria-hidden="true">
<t t-if="dlv.sale_id">
<span t-out="dlv.sale_id.name or ''"/>
<span t-out="dlv.sale_id.client_order_ref or ''"/>
</t>
</td>
</tr>
</tbody>
</t>
@@ -615,7 +689,7 @@
</template>
<!-- ================================================================== -->
<!-- CERTIFICATIONS — list -->
<!-- CERTIFICATIONS — list with search + sort -->
<!-- ================================================================== -->
<template id="portal_my_certifications" name="My Certifications">
<t t-call="portal.portal_layout">
@@ -623,6 +697,20 @@
<t t-set="title">Certifications &amp; Quality</t>
</t>
<!-- Search + sort strip (no filter pills — all certs are terminal) -->
<t t-call="fusion_plating_portal.fp_portal_list_controls">
<t t-set="filters" t-value="filters"/>
<t t-set="active_filter" t-value="filter_state"/>
<t t-set="sorts" t-value="sorts"/>
<t t-set="active_sort" t-value="sortby"/>
<t t-set="search" t-value="search"/>
<t t-set="url" t-value="url"/>
<t t-set="extra_qs" t-value="extra_qs"/>
<t t-set="target" t-value="target"/>
<t t-set="result_total" t-value="result_total"/>
<t t-set="clipped" t-value="clipped"/>
</t>
<t t-if="not cert_jobs">
<div class="o_fp_portal_card card bg-body-tertiary border-0 p-4 text-center">
<p class="text-muted mb-0">No certificates available yet.</p>
@@ -637,7 +725,7 @@
<th class="text-end">Download</th>
</tr>
</thead>
<tbody>
<tbody class="o_fp_certs_filterable">
<tr t-foreach="cert_jobs" t-as="cj">
<td>
<a t-att-href="'/my/jobs/%s' % cj.id" t-out="cj.name"/>
@@ -658,6 +746,19 @@
<i class="fa fa-download"/> CoC
</a>
</td>
<!-- Hidden: part name, customer PO from the backend job -->
<td class="d-none" aria-hidden="true">
<t t-set="_bj" t-value="cj.x_fc_job_id if 'x_fc_job_id' in cj._fields else False"/>
<t t-set="_so" t-value="_bj.sale_order_id if _bj and 'sale_order_id' in _bj._fields else False"/>
<t t-set="_part" t-value="_bj.part_catalog_id if _bj and 'part_catalog_id' in _bj._fields else False"/>
<t t-if="_part">
<span t-out="_part.part_number or ''"/>
<span t-out="_part.name or ''"/>
</t>
<t t-if="_so">
<span t-out="_so.client_order_ref or ''"/>
</t>
</td>
</tr>
</tbody>
</t>