feat(portal): pulse animation, repeat-order button, 5-panel dashboard

1. Pulse animation on the active step indicator:
   - New @keyframes fp-pulse-teal / fp-pulse-amber in stepper.scss
   - Applied to .o_fp_step_active / _warn and .o_fp_timeline_active
     .o_fp_timeline_dot so dashboard stepper + detail-page timeline
     breathe in sync. 1.8s ease-in-out, ring grows 4px -> 9px and
     fades 20% -> 6% opacity. Two color variants so QC (warn) keeps
     its amber meaning.
   - prefers-reduced-motion: reduce kills the animation for users
     who opted out.

2. Repeat Order button on /my/jobs/<id> detail page:
   - New POST /my/jobs/<id>/repeat route that creates a draft
     fusion.plating.quote.request seeded with the user's contact +
     the job's quantity, posts a chatter link back to the original
     job, redirects to the new RFQ for review/submit.
   - Button placed in the detail footer next to 'Back to all jobs',
     CSRF-protected via the form's csrf_token hidden field.

3. Dashboard expanded from 3 secondary panels to 5 (Recent Quote
   Requests + Recent Purchase Orders added) so every previously-
   designed customer page is reachable from /my/home.
   - Auto-fit grid: 3+2 / 2+2+1 / single column depending on width.
   - Every panel header gets a 'View all ->' link to its list page
     (Quote Requests / POs / Certs / Deliveries / Invoices).
   - Empty-state for Quote Requests gets an inline 'Get a quote ->'
     CTA so first-time customers know where to start.

Version bump: 19.0.3.4.0 -> 19.0.3.5.0.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsinghpal
2026-05-17 03:56:53 -04:00
parent ba6f39375a
commit 49013c64fb
7 changed files with 151 additions and 7 deletions

View File

@@ -128,9 +128,52 @@
<!-- Secondary panels -->
<div class="o_fp_secondary_panels">
<!-- Quote Requests -->
<div class="o_fp_panel">
<div class="o_fp_panel_title">
<span class="o_fp_panel_icon">📄</span> Recent Quote Requests
<a href="/my/quote_requests" class="o_fp_panel_view_all">View all →</a>
</div>
<t t-if="recent_quotes">
<t t-foreach="recent_quotes[:3]" t-as="qr">
<div class="o_fp_panel_row">
<a t-att-href="'/my/quote_requests/%s' % qr.id" class="text-decoration-none" t-out="qr.name"/>
<t t-if="qr.create_date"> · <span t-field="qr.create_date" t-options='{"widget": "date"}'/></t>
</div>
</t>
</t>
<t t-else="">
<div class="o_fp_panel_row text-muted">
No quotes yet.
<a href="/my/configurator" class="o_fp_panel_inline_cta">Get a quote →</a>
</div>
</t>
</div>
<!-- Purchase Orders -->
<div class="o_fp_panel">
<div class="o_fp_panel_title">
<span class="o_fp_panel_icon">🛒</span> Recent Purchase Orders
<a href="/my/purchase_orders" class="o_fp_panel_view_all">View all →</a>
</div>
<t t-if="recent_pos">
<t t-foreach="recent_pos[:3]" t-as="po">
<div class="o_fp_panel_row">
<span t-out="po.name"/>
<t t-if="po.amount_total"> · <span t-field="po.amount_total" t-options='{"widget": "monetary", "display_currency": po.currency_id}'/></t>
</div>
</t>
</t>
<t t-else="">
<div class="o_fp_panel_row text-muted">No purchase orders yet.</div>
</t>
</div>
<!-- Certifications -->
<div class="o_fp_panel">
<div class="o_fp_panel_title">
<span class="o_fp_panel_icon">📑</span> Recent Certifications
<a href="/my/certifications" class="o_fp_panel_view_all">View all →</a>
</div>
<t t-if="recent_certs">
<t t-foreach="recent_certs[:3]" t-as="cert">
@@ -146,9 +189,12 @@
<div class="o_fp_panel_row text-muted">No certifications yet.</div>
</t>
</div>
<!-- Packing Slips / Deliveries -->
<div class="o_fp_panel">
<div class="o_fp_panel_title">
<span class="o_fp_panel_icon">📦</span> Recent Packing Slips
<a href="/my/deliveries" class="o_fp_panel_view_all">View all →</a>
</div>
<t t-if="recent_deliveries">
<t t-foreach="recent_deliveries[:3]" t-as="d">
@@ -162,9 +208,12 @@
<div class="o_fp_panel_row text-muted">No deliveries yet.</div>
</t>
</div>
<!-- Invoices -->
<div class="o_fp_panel">
<div class="o_fp_panel_title">
<span class="o_fp_panel_icon">💰</span> Recent Invoices
<a href="/my/fp_invoices" class="o_fp_panel_view_all">View all →</a>
</div>
<t t-if="recent_invoices">
<t t-foreach="recent_invoices[:3]" t-as="inv">

View File

@@ -566,7 +566,16 @@
<a t-if="job.invoice_ref" href="#" t-out="'Invoice ' + job.invoice_ref"/>
<a t-else="" class="disabled">Invoice (pending)</a>
</div>
<a href="/my/jobs" class="o_fp_btn_secondary">← Back to all jobs</a>
<div class="d-flex gap-2 align-items-center">
<!-- POST-only form so the action is intentional -->
<form t-attf-action="/my/jobs/#{job.id}/repeat" method="post" class="m-0">
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
<button type="submit" class="o_fp_btn_primary">
<i class="fa fa-repeat"/> Repeat Order
</button>
</form>
<a href="/my/jobs" class="o_fp_btn_secondary">← Back to all jobs</a>
</div>
</div>
</div>
</t>