This commit is contained in:
gsinghpal
2026-04-28 19:43:16 -04:00
parent 2a9fd478f5
commit 6ac6d24da6
6 changed files with 627 additions and 0 deletions

View File

@@ -0,0 +1,335 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- 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 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"
t-attf-class="badge ms-auto #{
'bg-warning' if task.state == '02_changes_requested'
else 'bg-success' if task.state == '03_approved'
else 'bg-primary' if task.state == '1_done'
else 'bg-danger' if task.state == '1_canceled'
else 'bg-secondary'
}">
<t t-esc="fp_state_label"/>
</span>
</div>
<div class="card-body">
<form method="POST"
t-attf-action="/my/projects/{{ task.project_id.id }}/task/{{ task.id }}/state"
class="row g-2">
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
<div class="col-12">
<textarea name="comment" rows="3"
class="form-control form-control-sm"
placeholder="Optional — what changes do you need, or any approval notes?"/>
</div>
<div class="col-12 d-flex flex-wrap gap-2 mt-2">
<button type="submit" name="state" value="02_changes_requested"
class="btn btn-warning btn-sm">
<i class="fa fa-exclamation-circle me-1"/> Request Changes
</button>
<button type="submit" name="state" value="03_approved"
class="btn btn-success btn-sm">
<i class="fa fa-check-circle me-1"/> Approve
</button>
<button type="submit" name="state" value="1_done"
class="btn btn-primary btn-sm">
<i class="fa fa-flag-checkered me-1"/> Mark Done
</button>
<button type="submit" name="state" value="01_in_progress"
class="btn btn-secondary btn-sm ms-auto"
t-if="task.state != '01_in_progress'">
<i class="fa fa-play me-1"/> Reopen / In Progress
</button>
</div>
</form>
</div>
</div>
</xpath>
</template>
<!-- Timesheets / Time logged panel on the parent task page -->
<template id="portal_my_task_inherit_timesheets"
inherit_id="project.portal_my_task"
name="Fusion: Time logged on portal task page"
priority="60">
<xpath expr="//div[@id='task_chat']" position="before">
<div t-if="fp_timesheets" class="card mt-4 mb-4 fp_timesheets_card">
<div class="card-header bg-light d-flex align-items-center">
<h5 class="mb-0">
<i class="fa fa-clock-o me-2"/> Time Logged
<span class="badge bg-success ms-2">
<t t-esc="'%.2f h' % (fp_timesheets_total_hours or 0)"/>
</span>
</h5>
</div>
<ul class="list-group list-group-flush">
<li t-foreach="fp_timesheets" t-as="ts" class="list-group-item">
<div class="d-flex">
<div class="flex-grow-1">
<strong t-esc="ts.name"/>
<div class="small text-muted">
<span t-esc="ts.date"/>
<t t-if="ts.employee_id">
<span t-esc="ts.employee_id.name"/>
</t>
</div>
</div>
<div class="align-self-center ms-2">
<span class="badge bg-primary">
<t t-esc="'%.2f h' % ts.unit_amount"/>
</span>
</div>
</div>
</li>
</ul>
</div>
</xpath>
</template>
<!-- + New Task button on the project's portal page -->
<template id="portal_my_project_inherit_new_task_btn"
inherit_id="project.portal_my_project"
name="Fusion: New Task button on portal project">
<xpath expr="//t[@t-call='portal.portal_searchbar']" position="after">
<div class="d-flex justify-content-end mb-3" t-if="can_create_task">
<a class="btn btn-primary"
t-attf-href="/my/projects/{{ project.id }}/task/new">
<i class="fa fa-plus me-1"/> New Task
</a>
</div>
</xpath>
</template>
<!-- Indent task names by depth in the project task list -->
<template id="portal_tasks_list_inherit_hierarchy"
inherit_id="project.portal_tasks_list"
name="Fusion: Indent sub-tasks in portal task list">
<xpath expr="//t[@t-foreach='tasks']/tr/td/a[@t-attf-href]/span" position="before">
<t t-set="_fp_depth" t-value="(fp_task_depth or {}).get(task.id, 0)"/>
<t t-if="_fp_depth">
<span t-attf-style="display:inline-block;width:{{ _fp_depth * 1.4 }}rem;"/>
<i class="fa fa-level-up fa-rotate-90 me-2 text-muted small"/>
</t>
</xpath>
</template>
<!-- Reusable inline sub-task form (Bootstrap collapse target) -->
<template id="fp_inline_subtask_form" name="Fusion: inline sub-task form">
<div class="card-body bg-light border-top border-bottom py-3">
<form method="POST"
t-attf-action="/my/projects/{{ project_id }}/task/{{ parent_id }}/subtask/new"
class="row g-2">
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
<div class="col-md-7">
<input type="text" name="name" class="form-control form-control-sm"
placeholder="Sub-task title" required="required" maxlength="200"/>
</div>
<div class="col-md-3">
<select name="priority" class="form-select form-select-sm">
<t t-foreach="fp_priority_options or []" t-as="opt">
<option t-att-value="opt[0]"
t-att-selected="opt[0] == '0' or None">
<t t-esc="opt[1]"/>
</option>
</t>
</select>
</div>
<div class="col-md-2 d-flex">
<button type="submit" class="btn btn-sm btn-primary w-100">
<i class="fa fa-paper-plane me-1"/> Add
</button>
</div>
<div class="col-12">
<textarea name="description" rows="2"
class="form-control form-control-sm"
placeholder="Description (optional)"/>
</div>
</form>
</div>
</template>
<!-- Sub-tasks panel + inline "New Sub-task" forms on parent task page -->
<template id="portal_my_task_inherit_subtasks"
inherit_id="project.portal_my_task"
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">
<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>
</h5>
<button t-if="fp_can_create_task"
class="btn btn-sm btn-primary ms-auto"
type="button"
data-bs-toggle="collapse"
t-attf-data-bs-target="#fp_form_root_{{ task.id }}"
aria-expanded="false">
<i class="fa fa-plus me-1"/> New Sub-task
</button>
</div>
<div t-if="fp_can_create_task" class="collapse"
t-attf-id="fp_form_root_{{ task.id }}">
<t t-call="fusion_project_portal.fp_inline_subtask_form">
<t t-set="project_id" t-value="task.project_id.id"/>
<t t-set="parent_id" t-value="task.id"/>
</t>
</div>
<ul t-if="fp_task_descendants" class="list-group list-group-flush">
<t t-foreach="fp_task_descendants" t-as="sub">
<li class="list-group-item d-flex align-items-center">
<t t-set="_d" t-value="(fp_task_depth_inside or {}).get(sub.id, 0)"/>
<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">
<span t-esc="sub.name"/>
</a>
<button t-if="fp_can_create_task"
class="btn btn-sm btn-link text-muted py-0 px-1"
type="button"
data-bs-toggle="collapse"
t-attf-data-bs-target="#fp_form_sub_{{ sub.id }}"
aria-expanded="false"
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-esc="sub.stage_id.name"/>
</span>
</li>
<li t-if="fp_can_create_task"
class="list-group-item p-0 collapse"
t-attf-id="fp_form_sub_{{ sub.id }}">
<t t-call="fusion_project_portal.fp_inline_subtask_form">
<t t-set="project_id" t-value="task.project_id.id"/>
<t t-set="parent_id" t-value="sub.id"/>
</t>
</li>
</t>
</ul>
<div t-if="not fp_task_descendants" class="card-body text-muted small">
No sub-tasks yet.
</div>
</div>
</xpath>
</template>
<!-- Breadcrumbs for the standalone new-task form (top-level tasks only) -->
<template id="portal_breadcrumbs_inherit_fp_new_task"
inherit_id="portal.portal_breadcrumbs"
name="Fusion: Breadcrumbs for new-task form"
priority="50">
<xpath expr="//ol[hasclass('o_portal_submenu')]" position="inside">
<li t-if="page_name == 'fp_new_task' and project" class="breadcrumb-item">
<a t-attf-href="/my/projects">Projects</a>
</li>
<li t-if="page_name == 'fp_new_task' and project" class="breadcrumb-item">
<a t-attf-href="/my/projects/{{ project.id }}">
<t t-esc="project.name"/>
</a>
</li>
<li t-if="page_name == 'fp_new_task' and parent_task" class="breadcrumb-item text-truncate">
<a t-attf-href="/my/projects/{{ project.id }}/task/{{ parent_task.id }}">
<t t-esc="parent_task.name"/>
</a>
</li>
<li t-if="page_name == 'fp_new_task'" class="breadcrumb-item active">
<t t-if="parent_task">New Sub-task</t>
<t t-else="">New Task</t>
</li>
</xpath>
</template>
<!-- Standalone New Task form (still used for top-level "+ New Task" on project page) -->
<template id="portal_new_task_form" name="Submit a new task">
<t t-call="portal.portal_layout">
<t t-set="title" t-value="('New Sub-task in ' + parent_task.name) if parent_task else ('New Task in ' + project.name)"/>
<div class="container my-4">
<div class="d-flex align-items-center mb-3">
<h3 class="mb-0">
<t t-if="parent_task">
New Sub-task in
<span t-esc="parent_task.name"/>
</t>
<t t-else="">
New Task in
<span t-field="project.name"/>
</t>
</h3>
<a t-attf-href="/my/projects/{{ project.id }}{{ '/task/' + str(parent_task.id) if parent_task else '' }}"
class="btn btn-link ms-auto">
<i class="fa fa-times"/> Cancel
</a>
</div>
<form method="POST"
t-attf-action="/my/projects/{{ project.id }}{{ '/task/' + str(parent_task.id) + '/subtask' if parent_task else '/task' }}/new"
class="o_portal_form needs-validation">
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
<div class="mb-3">
<label for="fp_task_name" class="form-label">
Title <span class="text-danger">*</span>
</label>
<input type="text" id="fp_task_name" name="name"
t-att-class="'form-control is-invalid' if errors.get('name') else 'form-control'"
t-att-value="values.get('name')"
required="required" maxlength="200"/>
<div t-if="errors.get('name')" class="invalid-feedback d-block">
<t t-esc="errors.get('name')"/>
</div>
</div>
<div class="mb-3">
<label for="fp_task_priority" class="form-label">Priority</label>
<select id="fp_task_priority" name="priority" class="form-select">
<t t-foreach="priority_options" t-as="opt">
<option t-att-value="opt[0]"
t-att-selected="opt[0] == values.get('priority', '0') or None">
<t t-esc="opt[1]"/>
</option>
</t>
</select>
</div>
<div class="mb-3">
<label for="fp_task_description" class="form-label">Description</label>
<textarea id="fp_task_description" name="description"
class="form-control" rows="6"
t-esc="values.get('description', '')"/>
<div class="form-text">
Add details, context, or steps to reproduce — your team will see this on the task.
</div>
</div>
<div class="d-flex gap-2">
<button type="submit" class="btn btn-primary">
<i class="fa fa-paper-plane me-1"/>
<t t-if="parent_task">Submit Sub-task</t>
<t t-else="">Submit Task</t>
</button>
<a t-attf-href="/my/projects/{{ project.id }}{{ '/task/' + str(parent_task.id) if parent_task else '' }}"
class="btn btn-secondary">Cancel</a>
</div>
</form>
</div>
</t>
</template>
</odoo>

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- Light UX polish on the standard stop-timer confirmation wizard:
- require a non-empty session summary
- show a bigger textarea with a more directive placeholder
- keep the built-in time-edit + Save/Resume/Delete buttons unchanged -->
<record id="view_hr_timesheet_stop_timer_confirmation_wizard_inherit_fc"
model="ir.ui.view">
<field name="name">hr.timesheet.stop.timer.confirmation.wizard.form.fc</field>
<field name="model">hr.timesheet.stop.timer.confirmation.wizard</field>
<field name="inherit_id"
ref="timesheet_grid.hr_timesheet_stop_timer_confirmation_wizard_view_form"/>
<field name="arch" type="xml">
<field name="timesheet_name" position="attributes">
<attribute name="placeholder">What did you complete in this session? — e.g. Implemented quote PDF rendering and validated edge cases</attribute>
<attribute name="required">1</attribute>
</field>
</field>
</record>
</odoo>