From 49013c64fb6951f168ff429a96a2ac0f8b7b1f77 Mon Sep 17 00:00:00 2001 From: gsinghpal Date: Sun, 17 May 2026 03:56:53 -0400 Subject: [PATCH] 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/ detail page: - New POST /my/jobs//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) --- .../fusion_plating_portal/__manifest__.py | 2 +- .../controllers/portal.py | 44 +++++++++++++++++ .../static/src/scss/fp_portal_dashboard.scss | 23 +++++++-- .../static/src/scss/fp_portal_stepper.scss | 25 ++++++++++ .../static/src/scss/fp_portal_timeline.scss | 4 ++ .../views/fp_portal_dashboard.xml | 49 +++++++++++++++++++ .../views/fp_portal_templates.xml | 11 ++++- 7 files changed, 151 insertions(+), 7 deletions(-) diff --git a/fusion_plating/fusion_plating_portal/__manifest__.py b/fusion_plating/fusion_plating_portal/__manifest__.py index 8b23a936..7387002d 100644 --- a/fusion_plating/fusion_plating_portal/__manifest__.py +++ b/fusion_plating/fusion_plating_portal/__manifest__.py @@ -5,7 +5,7 @@ { 'name': 'Fusion Plating โ€” Customer Portal', - 'version': '19.0.3.4.0', + 'version': '19.0.3.5.0', 'category': 'Manufacturing/Plating', 'summary': 'Customer-facing portal for plating shops: online RFQ, job status, ' 'CoC downloads, invoice access.', diff --git a/fusion_plating/fusion_plating_portal/controllers/portal.py b/fusion_plating/fusion_plating_portal/controllers/portal.py index 10d16c57..dea28e54 100644 --- a/fusion_plating/fusion_plating_portal/controllers/portal.py +++ b/fusion_plating/fusion_plating_portal/controllers/portal.py @@ -891,6 +891,50 @@ class FpCustomerPortal(CustomerPortal): ], ) + # ========================================================================== + # JOBS -- repeat order (clone into a new draft quote_request) + # ========================================================================== + # Customer-initiated "order again". Creates a draft fusion.plating.quote. + # request pre-filled with the user's contact info + job's quantity, then + # redirects to the new RFQ so the customer can adjust + submit. EN + # Plating then prices it via the normal quote workflow. + # + # POST-only so a stray browser prefetch can't accidentally spawn RFQs. + @http.route( + ['/my/jobs//repeat'], + type='http', + auth='user', + methods=['POST'], + website=True, + csrf=True, + ) + def portal_repeat_order(self, job_id, access_token=None, **kw): + try: + job_sudo = self._document_check_access( + 'fusion.plating.portal.job', + job_id, + access_token, + ) + except (AccessError, MissingError): + return request.redirect('/my') + + user = request.env.user + Quote = request.env['fusion.plating.quote.request'].sudo() + new_quote = Quote.create({ + 'partner_id': user.partner_id.id, + 'contact_name': user.name, + 'contact_email': user.email or '', + 'contact_phone': user.partner_id.phone or '', + 'quantity': job_sudo.quantity or 1, + 'state': 'new', + # name auto-generated by sequence + }) + # Cross-link via chatter so EN Plating's estimator sees the origin. + new_quote.message_post( + body=_('Repeat order requested from portal job %s') % job_sudo.name, + ) + return request.redirect('/my/quote_requests/%s' % new_quote.id) + # ========================================================================== # PURCHASE ORDERS -- list # ========================================================================== diff --git a/fusion_plating/fusion_plating_portal/static/src/scss/fp_portal_dashboard.scss b/fusion_plating/fusion_plating_portal/static/src/scss/fp_portal_dashboard.scss index c1f80f00..cbb7e319 100644 --- a/fusion_plating/fusion_plating_portal/static/src/scss/fp_portal_dashboard.scss +++ b/fusion_plating/fusion_plating_portal/static/src/scss/fp_portal_dashboard.scss @@ -148,13 +148,11 @@ .o_fp_secondary_panels { display: grid; - grid-template-columns: repeat(3, 1fr); + // Auto-fit so 5 panels arrange nicely as 3+2 / 2+2+1 / 1 column at + // various widths instead of overflowing or cramping. + grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: $fp-space-3; - @media (max-width: 768px) { - grid-template-columns: 1fr; - } - .o_fp_panel { @extend .o_fp_card_compact; @@ -178,6 +176,14 @@ justify-content: center; font-size: .78rem; } + .o_fp_panel_view_all { + margin-left: auto; + font-size: .7rem; + color: $fp-teal; + text-decoration: none; + font-weight: 500; + &:hover { color: $fp-teal-dark; } + } } .o_fp_panel_row { font-size: .72rem; @@ -185,5 +191,12 @@ margin-top: .2rem; &:first-of-type { margin-top: 0; } } + .o_fp_panel_inline_cta { + margin-left: .35rem; + color: $fp-teal; + text-decoration: none; + font-weight: 500; + &:hover { color: $fp-teal-dark; } + } } } diff --git a/fusion_plating/fusion_plating_portal/static/src/scss/fp_portal_stepper.scss b/fusion_plating/fusion_plating_portal/static/src/scss/fp_portal_stepper.scss index ff2d0f1c..7c8c3f28 100644 --- a/fusion_plating/fusion_plating_portal/static/src/scss/fp_portal_stepper.scss +++ b/fusion_plating/fusion_plating_portal/static/src/scss/fp_portal_stepper.scss @@ -36,6 +36,7 @@ color: $fp-teal; border: 2.5px solid $fp-teal; box-shadow: $fp-glow-ring-teal; + animation: fp-pulse-teal 1.8s ease-in-out infinite; } .o_fp_step_active_warn { // Used when the active step is in QC (amber) @@ -43,6 +44,30 @@ color: $fp-amber-text; border: 2.5px solid $fp-amber; box-shadow: $fp-glow-ring-amber; + animation: fp-pulse-amber 1.8s ease-in-out infinite; + } +} + +// Pulsing glow for the active step indicator. Kept subtle - the ring +// breathes in width + fades; the inner dot stays still. Two color +// variants (teal for normal flow, amber for QC) so the warn state +// retains its meaning. Defined here, used in both fp_portal_stepper.scss +// and fp_portal_timeline.scss. +@keyframes fp-pulse-teal { + 0%, 100% { box-shadow: 0 0 0 4px rgba(46, 175, 147, 0.20); } + 50% { box-shadow: 0 0 0 9px rgba(46, 175, 147, 0.06); } +} +@keyframes fp-pulse-amber { + 0%, 100% { box-shadow: 0 0 0 4px rgba(245, 158, 11, 0.20); } + 50% { box-shadow: 0 0 0 9px rgba(245, 158, 11, 0.06); } +} + +// Accessibility: kill the animation for users who've opted out of motion. +@media (prefers-reduced-motion: reduce) { + .o_fp_step_active, + .o_fp_step_active_warn, + .o_fp_timeline_active .o_fp_timeline_dot { + animation: none; } .o_fp_step_line { diff --git a/fusion_plating/fusion_plating_portal/static/src/scss/fp_portal_timeline.scss b/fusion_plating/fusion_plating_portal/static/src/scss/fp_portal_timeline.scss index db21f718..2b8c319b 100644 --- a/fusion_plating/fusion_plating_portal/static/src/scss/fp_portal_timeline.scss +++ b/fusion_plating/fusion_plating_portal/static/src/scss/fp_portal_timeline.scss @@ -56,6 +56,10 @@ background: $fp-card-bg; border: 2.5px solid $fp-teal; box-shadow: $fp-glow-ring-teal; + // Pulsing glow defined in fp_portal_stepper.scss (@keyframes + // fp-pulse-teal) - reused here so the active timeline dot + // breathes in sync with the stepper circle on /my/home. + animation: fp-pulse-teal 1.8s ease-in-out infinite; } .o_fp_timeline_title { diff --git a/fusion_plating/fusion_plating_portal/views/fp_portal_dashboard.xml b/fusion_plating/fusion_plating_portal/views/fp_portal_dashboard.xml index 4e585517..a437541c 100644 --- a/fusion_plating/fusion_plating_portal/views/fp_portal_dashboard.xml +++ b/fusion_plating/fusion_plating_portal/views/fp_portal_dashboard.xml @@ -128,9 +128,52 @@
+ +
+
+ ๐Ÿ“„ Recent Quote Requests + View all โ†’ +
+ + + + + + + + +
+ + +
+
+ ๐Ÿ›’ Recent Purchase Orders + View all โ†’ +
+ + +
+ + ยท +
+
+
+ +
No purchase orders yet.
+
+
+ +
๐Ÿ“‘ Recent Certifications + View all โ†’
@@ -146,9 +189,12 @@
No certifications yet.
+ +
๐Ÿ“ฆ Recent Packing Slips + View all โ†’
@@ -162,9 +208,12 @@
No deliveries yet.
+ +
๐Ÿ’ฐ Recent Invoices + View all โ†’
diff --git a/fusion_plating/fusion_plating_portal/views/fp_portal_templates.xml b/fusion_plating/fusion_plating_portal/views/fp_portal_templates.xml index 80d02500..88a68b70 100644 --- a/fusion_plating/fusion_plating_portal/views/fp_portal_templates.xml +++ b/fusion_plating/fusion_plating_portal/views/fp_portal_templates.xml @@ -566,7 +566,16 @@ Invoice (pending)
- โ† Back to all jobs +
+ +
+ + + + โ† Back to all jobs +