odoo18/addons_extensions/helpdesk/views/helpdesk_portal_templates.xml

466 lines
27 KiB
XML

<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="0">
<template id="portal_my_home_menu_helpdesk" name="Portal layout : helpdesk tickets menu entries" inherit_id="portal.portal_breadcrumbs" priority="50">
<xpath expr="//ol[hasclass('o_portal_submenu')]" position="inside">
<t t-set="rating_page" t-value="page_name == 'rating'"/>
<li t-if="page_name == 'ticket' or ticket or rating_page" t-attf-class="breadcrumb-item #{'active ' if not ticket else ''}">
<a t-if="ticket or rating_page" t-attf-href="/my/tickets?{{ keep_query() }}">Tickets</a>
<t t-else="">Tickets</t>
</li>
<li t-if="ticket" class="breadcrumb-item active text-truncate">
<span t-field="ticket.name"/> (#<span t-field="ticket.ticket_ref"/>)
</li>
<li t-if="rating_page" t-attf-class="breadcrumb-item active">
Our Ratings
</li>
</xpath>
</template>
<template id="portal_my_home_helpdesk_ticket" name="Helpdesk Tickets" customize_show="True" inherit_id="portal.portal_my_home" priority="50">
<xpath expr="//div[hasclass('o_portal_docs')]" position="before">
<t t-set="portal_service_category_enable" t-value="True"/>
</xpath>
<div id="portal_service_category" position="inside">
<t t-call="portal.portal_docs_entry">
<t t-set="icon" t-value="'/helpdesk/static/src/img/tickets.svg'"/>
<t t-set="title">Tickets</t>
<t t-set="text">Follow all your helpdesk tickets</t>
<t t-set="url" t-value="'/my/tickets'"/>
<t t-set="placeholder_count" t-value="'ticket_count'"/>
</t>
</div>
</template>
<template id="portal_helpdesk_ticket" name="My Tickets">
<t t-call="portal.portal_layout">
<t t-set="breadcrumbs_searchbar" t-value="True"/>
<t t-call="portal.portal_searchbar">
<t t-set="title">Tickets</t>
</t>
<div class="d-flex justify-content-end mb-3">
<a class="btn btn-primary" href="/helpdesk/new">New Ticket</a>
</div>
<div t-if="not grouped_tickets" class="alert alert-info">
There are currently no Ticket for your account.
</div>
<t t-else="">
<t t-call="portal.portal_table">
<thead>
<tr>
<th>Ticket</th>
<th>Type</th>
<th class="text-end" t-if="groupby != 'create_date'">Reported on</th>
<th id="ticket_user_header" t-if="groupby != 'user_id'" class="ps-5">Assigned to</th>
<th t-if="groupby != 'stage_id'" colspan="5" class="text-end">Stage</th>
</tr>
</thead>
<t t-foreach="grouped_tickets" t-as="tickets">
<tbody>
<tr t-if="not groupby =='none'" class="table-light">
<th t-if="groupby == 'stage_id'" colspan="5">
<span t-field="tickets[0].stage_id.name"/>
</th>
<th t-if="groupby == 'user_id'" colspan="5">
<span t-if="tickets[0].user_id" t-out="tickets[0].user_id.name"/>
<span t-else="">Unassigned</span>
</th>
<th t-if="groupby == 'team_id'" colspan="6">
<span t-field="tickets[0].team_id.name"/>
</th>
<th t-if="groupby == 'create_date'" colspan="5">
<span t-field="tickets[0].create_date"/>
</th>
<th t-if="groupby == 'kanban_state'" colspan="6">
<span t-if="tickets[0].kanban_state == 'normal'">In Progress</span>
<span t-elif="tickets[0].kanban_state == 'blocked'">Blocked</span>
<span t-else="tickets[0].kanban_state">Ready</span>
</th>
<th t-if="groupby == 'partner_id'" colspan="6">
<span t-if="tickets[0].partner_id" t-field="tickets[0].partner_id.name"/>
<span t-else="">No Customer</span>
</th>
</tr>
</tbody>
<t t-foreach="tickets" t-as="ticket">
<tr>
<td class="text-start"><a t-attf-href="/helpdesk/ticket/#{ticket.id}"><small>#</small><t t-out="ticket.ticket_ref"/><span class="ms-2" t-att-title="ticket.name" t-field="ticket.name"/></a></td>
<td><span t-field="ticket.portal_ticket_type"/></td>
<td class="text-end" t-if="groupby != 'create_date'">
<span t-field="ticket.create_date" t-options='{"widget": "datetime", "hide_seconds": True}'/>
</td>
<td id="ticket_user_body" t-if="groupby != 'user_id'" class="ps-5 lh-1">
<img t-if="ticket.user_id" class="o_avatar o_portal_contact_img rounded" t-attf-src="#{image_data_uri(ticket.user_id.sudo().avatar_128)}" alt="Contact"/>
<span t-field="ticket.user_id"/>
</td>
<td t-if="groupby != 'stage_id'" class="text-end">
<span t-attf-class="badge rounded-pill fw-normal #{'text-bg-success' if ticket.stage_id.fold else 'text-bg-primary'}" t-esc="ticket.stage_id.name"/>
</td>
</tr>
</t>
</t>
</t>
</t>
</t>
</template>
<template id="portal_helpdesk_ticket_new" name="Raise Helpdesk Ticket">
<t t-call="portal.portal_layout">
<t t-set="breadcrumbs_searchbar" t-value="True"/>
<t t-call="portal.portal_searchbar">
<t t-set="title">New Ticket</t>
</t>
<div t-if="error == 'no_team'" class="alert alert-warning">
No portal helpdesk team is configured yet. Please contact support.
</div>
<div t-if="error == 'missing'" class="alert alert-danger">
Please fill in the ticket type, subject, and details.
</div>
<form action="/helpdesk/new" method="post" class="card border-0 shadow-sm">
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
<div class="card-body">
<div class="row g-3">
<div class="col-12">
<div class="card bg-light border mb-3">
<div class="card-header">
<h6 class="mb-0">
<i class="fa fa-user me-2"/>
<t t-out="default_company.name or default_partner.company_name"/>
</h6>
</div>
<div class="card-body">
<div class="row g-3">
<div class="col-md-4">
<label class="form-label text-muted small">
Full Name
</label>
<input class="form-control"
readonly="readonly"
t-att-value="default_partner.display_name or ''"/>
</div>
<div class="col-md-4">
<label class="form-label text-muted small">
Phone
</label>
<input class="form-control"
id="partner_phone"
name="partner_phone"
t-att-value="default_partner.phone or ''"/>
</div>
<div class="col-md-4">
<label class="form-label text-muted small">
Email
</label>
<input class="form-control"
id="partner_email"
name="partner_email"
t-att-value="default_partner.email or ''"/>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-6">
<label class="form-label" for="portal_ticket_type">Category</label>
<select class="form-select" id="portal_ticket_type" name="portal_ticket_type" required="required">
<t t-foreach="ticket_types" t-as="ticket_type">
<option t-att-value="ticket_type[0]"
t-att-selected="'selected' if ticket_type[0] == default_category else None"><t t-out="ticket_type[1]"/></option>
</t>
</select>
</div>
<div class="col-md-6">
<label class="form-label" for="helpdesk_team">Team</label>
<select class="form-select" id="helpdesk_team" name="helpdesk_team" required="required">
<t t-foreach="ticket_teams" t-as="ticket_team">
<option
t-att-value="ticket_team.id"
t-att-data-category="ticket_team.portal_ticket_type">
<t t-out="ticket_team.display_name"/>
</option>
</t>
</select>
</div>
<div class="col-12">
<label class="form-label" for="name">Subject</label>
<input class="form-control" id="name" name="name" required="required" placeholder="Short summary of the issue"/>
</div>
<div class="col-12">
<label class="form-label" for="description">Details</label>
<textarea class="form-control" id="description" name="description" rows="8" required="required" placeholder="Describe what happened and what help you need"></textarea>
</div>
</div>
</div>
<div class="card-footer bg-transparent d-flex justify-content-between">
<a class="btn btn-light" href="/my/tickets">Cancel</a>
<button class="btn btn-primary" type="submit">Submit Ticket</button>
</div>
</form>
</t>
</template>
<template id="portal_helpdesk_ticket_new_js" inherit_id="portal_helpdesk_ticket_new">
<xpath expr="//form" position="after">
<script type="text/javascript">
document.addEventListener('DOMContentLoaded', function () {
const categorySelect = document.getElementById('portal_ticket_type');
const teamSelect = document.getElementById('helpdesk_team');
if (!categorySelect || !teamSelect) {
return;
}
const allTeams = Array.from(teamSelect.options).map(opt =>
opt.cloneNode(true)
);
function filterTeamsByCategory() {
const category = categorySelect.value;
const currentTeam = teamSelect.value;
teamSelect.innerHTML = '';
allTeams.forEach(option => {
if (option.dataset.category === category) {
teamSelect.appendChild(option.cloneNode(true));
}
});
const found = [...teamSelect.options].find(
opt => opt.value === currentTeam
);
if (found) {
teamSelect.value = currentTeam;
}
}
function setCategoryFromTeam() {
const selected = teamSelect.options[teamSelect.selectedIndex];
if (selected &amp;&amp; selected.dataset.category) {
categorySelect.value = selected.dataset.category;
}
}
categorySelect.addEventListener('change', filterTeamsByCategory);
teamSelect.addEventListener('change', setCategoryFromTeam);
filterTeamsByCategory();
});
</script>
</xpath>
</template>
<template id="tickets_followup" name="Helpdesk Tickets">
<t t-call="portal.portal_layout">
<t t-set="title" t-value="ticket.name"/>
<t t-set="wrapwrap_classes" t-value="'o_portal_bg_dark'"/>
<t t-set="o_portal_fullwidth_alert" groups="helpdesk.group_helpdesk_user">
<t t-call="portal.portal_back_in_edit_mode">
<t t-set="backend_url" t-value="'/odoo/action-helpdesk.helpdesk_ticket_action_main_tree/%s?menu_id=%s' % (ticket.id, ticket.env.ref('helpdesk.helpdesk_ticket_menu_all').id)"/>
</t>
</t>
<div class="row mt16 o_project_portal_sidebar">
<t t-call="portal.portal_record_sidebar">
<t t-set="classes" t-value="'col-lg-4 col-xxl-3 d-print-none'"/>
<t t-set="entries">
<div class="d-flex flex-column">
<div t-if="ticket.stage_id.fold" class="flex-grow-1 mb-3">
<div class="d-grid flex-sm-nowrap">
<a class="btn btn-light pt-1" t-att-href="'/my/ticket/rerequest/%s/%s' % (ticket.id, ticket.access_token)">
Re-request Help
</a>
</div>
</div>
<div t-if="ticket.team_id.allow_portal_ticket_closing and not ticket.stage_id.fold and not ticket.closed_by_partner" class="flex-grow-1">
<div class="d-grid flex-sm-nowrap">
<button class="btn btn-light pt-1" data-bs-target="#helpdesk_ticket_close_modal" data-bs-toggle="modal">
Close Ticket
</button>
</div>
<div class="modal" tabindex="-1" role="dialog" id="helpdesk_ticket_close_modal">
<div class="modal-dialog mt-5" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Close ticket</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<p>We hope to have addressed your request satisfactorily. If you no longer need our assistance, please close this ticket. Thank you for your collaboration.</p>
</div>
<div class="modal-footer">
<a role="button" class="btn btn-primary" t-att-href="'/my/ticket/close/%s/%s' % (ticket.id, ticket.access_token)">Close Ticket</a>
<button type="button" class="btn btn-light" data-bs-dismiss="modal">Discard</button>
</div>
</div>
</div>
</div>
</div>
<div id="ticket-nav" class="flex-grow-1 p-0" t-ignore="true" role="complementary">
<ul class="nav flex-column">
<li class="nav-item" id="nav-header">
<a class="nav-link p-0" href="#card_header">
Ticket
</a>
</li>
<li class="nav-item" id="nav-chat">
<a class="nav-link p-0" href="#ticket_chat">
History
</a>
</li>
</ul>
</div>
<div id="ticket-links" t-if="ticket_link_section" class="flex-grow-1" t-ignore="true" role="complementary" invisible="1">
<ul class="nav flex-column">
<t t-foreach="sorted(ticket_link_section, key=lambda r: r['sequence'])" t-as="ticket_link">
<li class="nav-item">
<a class="nav-link p-0" t-att-href="ticket_link['access_url']">
<t t-out="ticket_link['title']"/>
</a>
</li>
</t>
</ul>
</div>
<div t-if="ticket.user_id or ticket.partner_id" class="mt-4">
<div t-attf-class="col-12 col-md-12" t-if="ticket.user_id">
<h6>
<small class="text-muted">Assigned to</small>
</h6>
<t t-call="portal.portal_my_contact">
<t t-set="_contactAvatar" t-value="image_data_uri(ticket.user_id.avatar_128)"/>
<t t-set="_contactName" t-value="ticket.user_id.name"/>
</t>
</div>
<div t-attf-class="col-12 col-md-12 {{ 'mt-3' if ticket.user_id.name else '' }}" t-if="ticket.partner_id">
<h6>
<small class="text-muted">Customer</small>
</h6>
<t t-call="portal.portal_my_contact">
<t t-set="_contactAvatar" t-value="image_data_uri(ticket.partner_id.avatar_128)"/>
<t t-set="_contactName" t-value="ticket.partner_id.display_name"/>
<a t-attf-href="mailto:{{ticket.partner_id.email}}" t-if="ticket.partner_id.email"><div t-field="ticket.partner_id" t-options='{"widget": "contact", "fields": ["email"]}'/></a>
<a t-attf-href="tel:{{ticket.partner_id.phone}}" t-if="ticket.partner_id.phone"><div t-field="ticket.partner_id" t-options='{"widget": "contact", "fields": ["phone"]}'/></a>
</t>
</div>
</div>
</div>
</t>
</t>
<div id="ticket_content" class="o_portal_content col-12 col-lg-8 col-xxl-9 mt-5 mt-lg-0">
<div t-if="ticket_closed" class="alert alert-success alert-dismissible d-print-none" role="status">
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
<span>Your ticket has successfully been closed. Thank you for your collaboration.</span>
</div>
<div t-if="ticket_created" class="alert alert-success alert-dismissible d-print-none" role="status">
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
<span>Your ticket has been received. Please wait patiently while the assigned team reviews it.</span>
</div>
<div t-if="ticket_reopened" class="alert alert-info alert-dismissible d-print-none" role="status">
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
<span>Your ticket has been re-requested and moved back to the support queue.</span>
</div>
<div id="card">
<div id="card_header" class="container" data-anchor="true">
<div class="row gs-0">
<div class="col-md">
<h5 class="row justify-content-between">
<div class="col-md-9 text-truncate">
<span t-field="ticket.name" class="h3"/>
<small class="text-muted"> (#<span t-field="ticket.ticket_ref"/>)</small>
</div>
<div class="col col-auto">
<small>Stage:</small>
<small t-field="ticket.stage_id.name" t-attf-class="badge rounded-pill smaller #{'text-bg-info' if ticket.stage_id.fold else 'text-bg-light bg-200'}" title="Current stage of this ticket"/>
</div>
</h5>
</div>
</div>
</div>
<div id="card_body">
<div class="row mb-4">
<strong class="col-lg-3">Reported on</strong>
<span class="col-lg-9" t-field="ticket.create_date" t-options='{"widget": "datetime", "hide_seconds": True}'/>
</div>
<div class="row mb-4">
<strong class="col-lg-3">Type</strong>
<span class="col-lg-9" t-field="ticket.portal_ticket_type"/>
</div>
<div t-if="not is_html_empty(ticket.description)" class="row mt-3" name="description">
<div class="col-lg-12" t-field="ticket.description"/>
</div>
</div>
</div>
<hr/>
<div class="o_portal_messages_container" id="ticket_chat" data-anchor="true">
<h3>Communication history</h3>
<t t-call="portal.message_thread">
<t t-set="token" t-value="ticket.access_token"/>
<t t-set="disable_composer" t-value="ticket.stage_id.fold"/>
</t>
</div>
</div>
</div>
</t>
</template>
<!-- Page : Rating of a particular team -->
<template id="team_rating_progress_data" name="Ticket Rating Page">
<div class="progress">
<div class="progress-bar bg-success" t-attf-style="width: #{stats[duration][5]}%;" title="Satisfied" role="img" aria-label="Happy">
<t t-out="int(stats[duration][5])"/>%
</div>
<div class="progress-bar bg-warning" t-attf-style="width: #{stats[duration][3]}%;" title="Okay" role="img" aria-label="Average">
<t t-out="int(stats[duration][3])"/>%
</div>
<div class="progress-bar bg-danger" t-attf-style="width: #{stats[duration][1]}%;" title="Dissatisfied" role="img" aria-label="Bad">
<t t-out="int(stats[duration][1])"/>%
</div>
</div>
</template>
<template id="team_rating_data" name="Helpdesk Ticket Rating Page">
<div class="row">
<div class="col-md-4">
<h5>Last 7 days</h5>
<t t-set="duration" t-value="7"/>
<t t-set="stats" t-value="stats"/>
<t t-call="helpdesk.team_rating_progress_data"/>
</div>
<div class="col-md-4">
<h5>Last 30 days</h5>
<t t-set="duration" t-value="30"/>
<t t-set="stats" t-value="stats"/>
<t t-call="helpdesk.team_rating_progress_data"/>
</div>
<div class="col-md-4">
<h5>Last 3 months</h5>
<t t-set="duration" t-value="90"/>
<t t-set="stats" t-value="stats"/>
<t t-call="helpdesk.team_rating_progress_data"/>
</div>
</div>
<h5 class="o_page_header mt-4">Latest Ratings</h5>
<t t-foreach="ratings" t-as="rating">
<img t-attf-src='/rating/static/src/img/rating_#{int(rating.rating)}.png' t-att-alt="rating.res_name" t-attf-title="{{rating.res_name if t['is_helpdesk_user'] else ''}}"/>
</t>
</template>
</data>
</odoo>