feat(portal): shared QWeb macros (badge, stepper, doc chip, doc group)

Macros take dict args so callers never reach into the underlying
records — keeps templates testable + makes the stepper reusable
on dashboard cards AND detail-page if needed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsinghpal
2026-05-17 02:40:31 -04:00
parent 1780b383b9
commit 215e393bdb

View File

@@ -0,0 +1,115 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2026 Nexa Systems Inc.
License OPL-1 (Odoo Proprietary License v1.0)
Shared QWeb macros for the customer portal redesign.
Every template should t-call these instead of inlining stepper/badge/doc HTML.
-->
<odoo>
<!-- ================================================================== -->
<!-- Status badge — pass state (string) and label (string) -->
<!-- ================================================================== -->
<template id="fp_portal_status_badge" name="Portal: Status Badge">
<span t-attf-class="o_fp_badge o_fp_badge_#{state}">
<span class="o_fp_badge_dot"/>
<t t-out="label"/>
</span>
</template>
<!-- ================================================================== -->
<!-- Numbered horizontal stepper — pass `steps` list of dicts: -->
<!-- {label, status: 'done'|'active'|'pending', time_label} -->
<!-- active_state: 'normal' (teal) or 'warn' (amber) -->
<!-- ================================================================== -->
<template id="fp_portal_stepper" name="Portal: Numbered Stepper">
<t t-set="active_state" t-value="active_state or 'normal'"/>
<div class="o_fp_stepper">
<t t-foreach="steps" t-as="step">
<!-- circle -->
<div t-attf-class="o_fp_step_circle #{
'o_fp_step_done' if step['status'] == 'done' else
(('o_fp_step_active_warn' if active_state == 'warn' else 'o_fp_step_active') if step['status'] == 'active' else '')
}">
<t t-if="step['status'] == 'done'"></t>
<t t-elif="step['status'] in ('active', 'pending')">
<t t-out="step_index + 1"/>
</t>
</div>
<!-- connecting line (omit after last circle) -->
<t t-if="not step_last">
<div t-attf-class="o_fp_step_line #{
'o_fp_step_line_done' if step['status'] == 'done' else
('o_fp_step_line_warn' if active_state == 'warn' and step['status'] == 'active' else '')
}"/>
</t>
</t>
</div>
<!-- Labels under -->
<div class="o_fp_step_labels">
<t t-foreach="steps" t-as="step">
<div t-attf-class="o_fp_step_label #{
'o_fp_step_label_done' if step['status'] == 'done' else
(('o_fp_step_label_active_warn' if active_state == 'warn' else 'o_fp_step_label_active') if step['status'] == 'active' else '')
}">
<div class="o_fp_step_label_title" t-out="step['label']"/>
<div class="o_fp_step_label_time" t-out="step.get('time_label') or ''"/>
</div>
</t>
</div>
</template>
<!-- ================================================================== -->
<!-- Doc chip (compact) — pass doc dict {icon, label, url, pending} -->
<!-- ================================================================== -->
<template id="fp_portal_doc_chip" name="Portal: Doc Chip">
<t t-if="doc.get('pending')">
<span class="o_fp_doc_chip o_fp_doc_chip_pending">
<t t-out="doc.get('icon') or '📑'"/>
<span t-out="doc['label']"/> · pending
</span>
</t>
<t t-else="">
<a t-att-href="doc['url']" class="o_fp_doc_chip">
<t t-out="doc.get('icon') or '📄'"/>
<span t-out="doc['label']"/>
</a>
</t>
</template>
<!-- ================================================================== -->
<!-- Doc group (detail page) — pass label + docs list of dicts: -->
<!-- {label, sub, url, icon_class, pending} -->
<!-- ================================================================== -->
<template id="fp_portal_doc_group" name="Portal: Doc Group">
<div class="o_fp_doc_group" style="margin-bottom: 1.1rem">
<div class="o_fp_doc_group_label" t-out="group_label"/>
<t t-foreach="docs" t-as="doc">
<t t-if="doc.get('pending')">
<div class="o_fp_doc_row o_fp_doc_row_pending">
<span t-attf-class="o_fp_doc_icon o_fp_doc_icon_pending">📑</span>
<div class="o_fp_doc_meta">
<div class="o_fp_doc_name" t-out="doc['label']"/>
<div class="o_fp_doc_sub" t-out="doc.get('sub') or ''"/>
</div>
<span style="color: #cbd5e1; font-size: .72rem"></span>
</div>
</t>
<t t-else="">
<a t-att-href="doc['url']" class="o_fp_doc_row">
<span t-attf-class="o_fp_doc_icon #{doc.get('icon_class') or 'o_fp_doc_icon_input'}">
<t t-out="doc.get('icon') or '📄'"/>
</span>
<div class="o_fp_doc_meta">
<div class="o_fp_doc_name" t-out="doc['label']"/>
<div class="o_fp_doc_sub" t-out="doc.get('sub') or ''"/>
</div>
<span class="o_fp_doc_action">↓ Download</span>
</a>
</t>
</t>
</div>
</template>
</odoo>