This commit is contained in:
gsinghpal
2026-04-29 03:35:33 -04:00
parent 6ac6d24da6
commit a2fe1fcbcc
61 changed files with 4655 additions and 667 deletions

View File

@@ -1,13 +1,194 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- Redesigned /my/projects page: status columns + rich cards -->
<template id="portal_my_projects" name="Fusion: My Projects (cards)">
<t t-call="portal.portal_layout">
<t t-set="breadcrumbs_searchbar" t-value="True"/>
<t t-set="_active_ids" t-value="[p.id for p in projects if (fp_project_cards or {}).get(p.id, {}).get('bucket') == 'active']"/>
<t t-set="_idle_ids" t-value="[p.id for p in projects if (fp_project_cards or {}).get(p.id, {}).get('bucket') == 'idle']"/>
<t t-set="_done_ids" t-value="[p.id for p in projects if (fp_project_cards or {}).get(p.id, {}).get('bucket') == 'done']"/>
<div class="fp_projects_page" t-att-data-default-group="fp_groupby or 'status'">
<!-- Page header / control bar -->
<div class="fp_projects_header d-flex flex-wrap align-items-center gap-2 mb-3">
<h3 class="mb-0 me-2">
<i class="fa fa-folder-open-o me-2"/>Projects
<span class="badge bg-secondary ms-2"><t t-esc="len(projects)"/></span>
</h3>
<div class="ms-auto d-flex flex-wrap align-items-center gap-2">
<div class="input-group input-group-sm fp_projects_search_wrap">
<span class="input-group-text bg-white"><i class="fa fa-search"/></span>
<input type="search"
class="form-control form-control-sm fp_projects_search"
placeholder="Search projects..."/>
</div>
<div class="btn-group btn-group-sm fp_projects_group_picker" role="group" aria-label="Group by">
<button type="button" class="btn btn-outline-secondary active" data-group="status">By Status</button>
<button type="button" class="btn btn-outline-secondary" data-group="customer">By Customer</button>
<button type="button" class="btn btn-outline-secondary" data-group="none">Flat</button>
</div>
<div class="dropdown fp_projects_sort_picker">
<button class="btn btn-sm btn-outline-secondary dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false">
Sort: <span class="fp_sort_label">Name</span>
</button>
<ul class="dropdown-menu dropdown-menu-end">
<li><a class="dropdown-item active" href="#" data-sort="name">Name</a></li>
<li><a class="dropdown-item" href="#" data-sort="pct">% Complete</a></li>
<li><a class="dropdown-item" href="#" data-sort="tasks">Task Count</a></li>
<li><a class="dropdown-item" href="#" data-sort="activity">Last Activity</a></li>
</ul>
</div>
</div>
</div>
<!-- Empty: no projects at all -->
<t t-if="not projects">
<div class="fp_projects_empty text-center py-5">
<div class="fp_empty_glyph mb-3"><i class="fa fa-folder-open-o fa-3x text-muted"/></div>
<h5 class="text-muted mb-1">You don't have any projects yet</h5>
<p class="text-muted small mb-0">Once a project is shared with you, it will appear here.</p>
</div>
</t>
<!-- Columns + cards -->
<t t-if="projects">
<div class="fp_projects_cols">
<t t-call="fusion_project_portal.fp_project_column">
<t t-set="bucket_key" t-value="'active'"/>
<t t-set="bucket_label" t-value="'Active'"/>
<t t-set="bucket_dot" t-value="'green'"/>
<t t-set="bucket_empty" t-value="'No active projects.'"/>
<t t-set="bucket_ids" t-value="_active_ids"/>
</t>
<t t-call="fusion_project_portal.fp_project_column">
<t t-set="bucket_key" t-value="'idle'"/>
<t t-set="bucket_label" t-value="'Idle'"/>
<t t-set="bucket_dot" t-value="'amber'"/>
<t t-set="bucket_empty" t-value="'No idle projects.'"/>
<t t-set="bucket_ids" t-value="_idle_ids"/>
</t>
<t t-call="fusion_project_portal.fp_project_column">
<t t-set="bucket_key" t-value="'done'"/>
<t t-set="bucket_label" t-value="'Done'"/>
<t t-set="bucket_dot" t-value="'gray'"/>
<t t-set="bucket_empty" t-value="'Completed projects will appear here.'"/>
<t t-set="bucket_ids" t-value="_done_ids"/>
</t>
</div>
<div class="fp_projects_no_match d-none text-center py-4 text-muted small">
No projects match your search.
</div>
</t>
<div t-if="pager and pager.get('page_count', 1) > 1" class="mt-3">
<t t-call="portal.pager"/>
</div>
</div>
</t>
</template>
<!-- Single column with its cards -->
<template id="fp_project_column" name="Fusion: project status column">
<div class="fp_projects_col" t-att-data-bucket="bucket_key">
<div class="fp_projects_col_head">
<span class="fp_col_label">
<span t-attf-class="fp_dot fp_dot_{{ bucket_dot }}"/>
<t t-esc="bucket_label"/>
</span>
<span class="fp_col_count" t-esc="len(bucket_ids or [])"/>
</div>
<div class="fp_projects_col_body">
<t t-if="not bucket_ids">
<div class="fp_col_empty"><t t-esc="bucket_empty"/></div>
</t>
<t t-foreach="projects" t-as="project">
<t t-if="project.id in (bucket_ids or [])">
<t t-call="fusion_project_portal.fp_project_card"/>
</t>
</t>
</div>
</div>
</template>
<!-- Rich project card (used inside columns and flat list) -->
<template id="fp_project_card" name="Fusion: project card">
<t t-set="_d" t-value="(fp_project_cards or {}).get(project.id, {})"/>
<t t-set="_pct" t-value="_d.get('pct') or 0"/>
<t t-set="_bucket" t-value="_d.get('bucket') or 'idle'"/>
<t t-set="_total" t-value="_d.get('total_count') or 0"/>
<t t-set="_done" t-value="_d.get('done_count') or 0"/>
<t t-set="_open" t-value="_d.get('open_count') or 0"/>
<t t-set="_alloc" t-value="_d.get('alloc_hours') or 0.0"/>
<t t-set="_spent" t-value="_d.get('spent_hours') or 0.0"/>
<t t-set="_assignees" t-value="_d.get('assignees') or []"/>
<t t-set="_customer" t-value="_d.get('partner_name') or ''"/>
<t t-set="_last" t-value="_d.get('last_activity')"/>
<a t-attf-href="/my/projects/{{ project.id }}"
class="fp_project_card"
t-att-data-bucket="_bucket"
t-att-data-name="project.name"
t-att-data-customer="_customer"
t-att-data-pct="_pct"
t-att-data-tasks="_total"
t-att-data-activity="(_last and _last.isoformat()) or ''">
<div class="fp_card_top">
<span class="fp_card_title"><t t-esc="project.name"/></span>
<span t-if="_pct" t-attf-class="fp_card_pct fp_pct_{{ 'high' if _pct &gt;= 80 else ('mid' if _pct &gt;= 40 else 'low') }}">
<t t-esc="str(int(_pct)) + '%'"/>
</span>
<span t-if="not _pct" class="fp_card_pct fp_card_pct_muted"></span>
</div>
<div class="fp_card_sub">
<t t-if="_customer"><i class="fa fa-user-o me-1 text-muted"/><t t-esc="_customer"/></t>
<t t-else=""><span class="text-muted">No customer set</span></t>
</div>
<div t-attf-class="fp_card_bar fp_bar_{{ 'high' if _pct &gt;= 80 else ('mid' if _pct &gt;= 40 else 'low') }}">
<span t-attf-style="width: {{ _pct }}%;"/>
</div>
<div class="fp_card_stats">
<span t-if="_total" class="fp_chip fp_chip_blue">
<i class="fa fa-list-ul me-1"/><t t-esc="_total"/> task<t t-if="_total != 1">s</t>
</span>
<span t-if="not _total" class="fp_chip fp_chip_gray">No tasks</span>
<span t-if="_done" class="fp_chip fp_chip_green">
<i class="fa fa-check me-1"/><t t-esc="_done"/> done
</span>
<span t-if="_alloc or _spent" class="fp_chip fp_chip_cyan ms-auto">
<i class="fa fa-clock-o me-1"/>
<t t-esc="'%.1fh' % _spent"/>
<t t-if="_alloc"> / <t t-esc="'%.1fh' % _alloc"/></t>
</span>
</div>
<div class="fp_card_footer">
<div class="fp_avatars">
<t t-foreach="_assignees[:4]" t-as="u">
<span class="fp_avatar" t-att-title="u['name']">
<img t-attf-src="/web/image/res.users/{{ u['id'] }}/avatar_128"
t-att-alt="u['name']"
onerror="this.style.display='none';this.parentElement.classList.add('fp_avatar_initials')"/>
<span class="fp_avatar_text"><t t-esc="u['initials']"/></span>
</span>
</t>
<span t-if="len(_assignees) &gt; 4" class="fp_avatar fp_avatar_more">+<t t-esc="len(_assignees) - 4"/></span>
</div>
<span t-if="_last" class="fp_card_activity small text-muted">
<i class="fa fa-history me-1"/><t t-esc="_last.strftime('%b %-d')" t-translation="off"/>
</span>
</div>
</a>
</template>
<!-- Status actions: Request Changes / Approve / Mark Done from the portal task page -->
<template id="portal_my_task_inherit_state_actions"
inherit_id="project.portal_my_task"
name="Fusion: Status actions on portal task page"
priority="55">
<xpath expr="//div[@id='task_chat']" position="before">
<div t-if="fp_can_create_task" class="card mt-4 mb-4 fp_state_card">
<div t-if="fp_can_change_state" class="card mt-4 mb-4 fp_state_card">
<div class="card-header bg-light d-flex align-items-center">
<h5 class="mb-0"><i class="fa fa-flag me-2"/> Status</h5>
<span class="ms-auto badge"
@@ -163,16 +344,35 @@
name="Fusion: Sub-tasks list inside parent task page">
<xpath expr="//div[@id='task_chat']" position="before">
<div class="card mt-4 mb-4 fp_subtasks_card">
<div class="card-header bg-light d-flex align-items-center">
<div class="card-header bg-light d-flex align-items-center flex-wrap gap-2">
<h5 class="mb-0">
<i class="fa fa-sitemap me-2"/>
Sub-tasks
<span class="badge bg-secondary ms-2">
<t t-esc="len(fp_task_descendants or [])"/>
</span>
<span t-if="fp_descendant_total" class="badge bg-success ms-2"
t-attf-title="{{ fp_descendant_done }} of {{ fp_descendant_total }} sub-tasks done">
<i class="fa fa-check-circle me-1"/>
<t t-esc="'%.0f' % (fp_descendant_pct or 0)"/>%
</span>
<span t-if="fp_descendant_alloc_hours or fp_descendant_spent_hours"
class="badge bg-info text-dark ms-2"
title="Spent / Allocated hours across sub-tasks">
<i class="fa fa-clock-o me-1"/>
<t t-esc="'%.1fh' % (fp_descendant_spent_hours or 0)"/>
<span t-if="fp_descendant_alloc_hours">
/ <t t-esc="'%.1fh' % fp_descendant_alloc_hours"/>
</span>
</span>
</h5>
<input t-if="fp_task_descendants"
type="search"
class="form-control form-control-sm fp_subtask_search ms-auto"
style="max-width: 260px;"
placeholder="Search sub-tasks..."/>
<button t-if="fp_can_create_task"
class="btn btn-sm btn-primary ms-auto"
class="btn btn-sm btn-primary"
type="button"
data-bs-toggle="collapse"
t-attf-data-bs-target="#fp_form_root_{{ task.id }}"
@@ -196,7 +396,7 @@
<span t-attf-style="display:inline-block;width:{{ _d * 1.4 }}rem;"/>
<i t-if="_d" class="fa fa-level-up fa-rotate-90 me-2 text-muted small"/>
<a t-attf-href="/my/projects/{{ task.project_id.id }}/task/{{ sub.id }}"
class="flex-grow-1">
t-attf-class="flex-grow-1 #{'text-decoration-line-through text-muted' if sub.state in (fp_done_states or ()) else ''}">
<span t-esc="sub.name"/>
</a>
<button t-if="fp_can_create_task"
@@ -208,7 +408,23 @@
title="Add sub-task here">
<i class="fa fa-plus"/>
</button>
<span t-if="sub.stage_id" class="badge bg-light text-dark border ms-2">
<t t-set="_fp_alloc" t-value="(fp_alloc_by_id or {}).get(sub.id, 0.0)"/>
<t t-set="_fp_spent" t-value="(fp_spent_by_id or {}).get(sub.id, 0.0)"/>
<span t-if="_fp_alloc or _fp_spent" class="badge bg-info text-dark ms-2 small"
title="Spent / Allocated">
<i class="fa fa-clock-o me-1"/>
<t t-esc="'%.1fh' % (_fp_spent or 0)"/>
<t t-if="_fp_alloc"> / <t t-esc="'%.1fh' % _fp_alloc"/></t>
</span>
<span t-if="sub.state in (fp_done_states or ())"
class="badge bg-success ms-2">
<i class="fa fa-check me-1"/> Done
</span>
<span t-elif="sub.state == '02_changes_requested'"
class="badge bg-warning text-dark ms-2">
<i class="fa fa-exclamation-circle me-1"/> Changes
</span>
<span t-elif="sub.stage_id" class="badge bg-light text-dark border ms-2">
<t t-esc="sub.stage_id.name"/>
</span>
</li>