| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772 |
- <?xml version="1.0" encoding="utf-8"?>
- <odoo>
- <!-- Extend portal tickets page to add "New" button for collaborators -->
- <template id="portal_helpdesk_ticket_new_button" name="Portal Helpdesk Tickets New Button" inherit_id="helpdesk.portal_helpdesk_ticket" priority="20">
- <!-- Add button after the searchbar, similar to how helpdesk_sale_timesheet extends this template -->
- <xpath expr="//t[@t-call='portal.portal_searchbar']" position="after">
- <div t-if="collaborator_team_form_url or admin_teams" class="mb-3 text-end">
- <a t-if="collaborator_team_form_url" t-attf-href="#{collaborator_team_form_url}" class="btn btn-primary me-2">
- <i class="fa fa-plus me-2"/>
- Nuevo Ticket
- </a>
- <t t-if="admin_teams" t-foreach="admin_teams" t-as="admin_team">
- <a t-attf-href="/my/helpdesk/teams/#{admin_team.id}/collaborators" class="btn btn-primary me-2">
- <i class="fa fa-users me-2"/>
- Gestionar Colaboradores - <t t-esc="admin_team.name"/>
- </a>
- </t>
- </div>
- </xpath>
- </template>
-
- <!-- Extend ticket detail view to show new fields -->
- <template id="portal_helpdesk_ticket_detail_extras" name="Portal Helpdesk Ticket Detail Extras" inherit_id="helpdesk.tickets_followup" priority="20">
- <!-- Add new fields after "Reported on" -->
- <xpath expr="//div[@name='description']" position="before">
- <div t-if="ticket.request_type_id" class="row mb-4">
- <strong class="col-lg-3">Tipo de Solicitud</strong>
- <span class="col-lg-9" t-field="ticket.request_type_id.name"/>
- </div>
- <div t-if="ticket.affected_module_id" class="row mb-4">
- <strong class="col-lg-3">Módulo Afectado</strong>
- <span class="col-lg-9" t-field="ticket.affected_module_id.name"/>
- </div>
- <div t-if="ticket.business_impact" class="row mb-4">
- <strong class="col-lg-3">Impacto de Negocio</strong>
- <span class="col-lg-9">
- <t t-if="ticket.business_impact == '0'">Crítico</t>
- <t t-elif="ticket.business_impact == '1'">Alto</t>
- <t t-elif="ticket.business_impact == '2'">Normal</t>
- </span>
- </div>
- <div t-if="ticket.reproduce_steps and ticket.request_type_code == 'incident'" class="row mb-4">
- <strong class="col-lg-3">Pasos para Reproducir</strong>
- <div class="col-lg-9" t-field="ticket.reproduce_steps"/>
- </div>
- <div t-if="ticket.business_goal and ticket.request_type_code == 'improvement'" class="row mb-4">
- <strong class="col-lg-3">Objetivo de Negocio</strong>
- <div class="col-lg-9" t-field="ticket.business_goal"/>
- </div>
- <div t-if="ticket.estimated_hours" class="row mb-4">
- <strong class="col-lg-3">Horas Estimadas</strong>
- <span class="col-lg-9" t-field="ticket.estimated_hours" t-options='{"widget": "float"}'/>
- </div>
- <div t-if="ticket.approval_status" class="row mb-4">
- <strong class="col-lg-3">Estado de Aprobación</strong>
- <span class="col-lg-9">
- <t t-if="ticket.approval_status == 'draft'">N/A</t>
- <t t-elif="ticket.approval_status == 'waiting'">Esperando Aprobación</t>
- <t t-elif="ticket.approval_status == 'approved'">Aprobado</t>
- <t t-elif="ticket.approval_status == 'rejected'">Rechazado</t>
- </span>
- </div>
- </xpath>
- </template>
- <!-- Page to manage collaborators -->
- <template id="portal_team_collaborators" name="Team Collaborators Management">
- <t t-call="portal.portal_layout">
- <t t-set="title">Gestionar Colaboradores</t>
- <div class="container mt-3">
- <div class="row">
- <div class="col-12">
- <h2 class="mb-4">
- <i class="fa fa-users me-2"/>
- Gestionar Colaboradores - <t t-esc="team.name"/>
- </h2>
-
- <!-- Success/Error Messages -->
- <t t-if="request.session.get('success')">
- <div class="alert alert-success alert-dismissible fade show" role="alert">
- <t t-esc="request.session.pop('success')"/>
- <button type="button" class="btn-close" data-bs-dismiss="alert"></button>
- </div>
- </t>
- <t t-if="request.session.get('error')">
- <div class="alert alert-danger alert-dismissible fade show" role="alert">
- <t t-esc="request.session.pop('error')"/>
- <button type="button" class="btn-close" data-bs-dismiss="alert"></button>
- </div>
- </t>
-
- <!-- Add Collaborator Form -->
- <div class="card mb-3">
- <div class="card-header">
- <h5 class="mb-0">Agregar Colaborador</h5>
- </div>
- <div class="card-body">
- <form t-attf-action="/my/helpdesk/teams/#{team.id}/collaborators/add" method="post" id="add-collaborator-form">
- <input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
- <div class="row">
- <div class="col-md-5">
- <div class="mb-3" style="position: relative;">
- <label class="form-label">Contacto</label>
- <div class="input-group" style="position: relative;">
- <input type="text"
- class="form-control partner-search"
- placeholder="Buscar contacto o crear nuevo..."
- autocomplete="off"
- id="partner-search-input"
- required="required"/>
- <input type="hidden" name="partner_id" id="partner-id-input"/>
- <a t-attf-href="/my/helpdesk/teams/#{team.id}/collaborators/new" class="btn btn-outline-secondary">
- <i class="fa fa-plus"></i> Nuevo
- </a>
- </div>
- <div class="partner-search-results" id="partner-search-results" style="display: none; position: absolute; z-index: 1000; background: white; border: 1px solid #ddd; max-height: 200px; overflow-y: auto; width: 100%; box-shadow: 0 2px 5px rgba(0,0,0,0.2); top: 100%; left: 0;"></div>
- </div>
- </div>
- <div class="col-md-4">
- <div class="mb-3">
- <label class="form-label">Rol</label>
- <select name="access_mode" class="form-select" required="required">
- <option value="user_own">User - Own Tickets</option>
- <option value="user_all">User - All Tickets</option>
- <option value="admin">Administrator</option>
- </select>
- </div>
- </div>
- <div class="col-md-3">
- <div class="mb-3">
- <label class="form-label"> </label>
- <button type="submit" class="btn btn-primary w-100">
- <i class="fa fa-plus me-2"/>
- Agregar
- </button>
- </div>
- </div>
- </div>
- <small class="text-muted">Solo puedes agregar contactos de tu misma red de contactos (misma empresa).</small>
- </form>
- </div>
- </div>
-
- <!-- Collaborators List -->
- <div class="card">
- <div class="card-header">
- <h5 class="mb-0">Colaboradores del Equipo</h5>
- </div>
- <div class="card-body">
- <t t-if="collaborators">
- <div class="table-responsive">
- <table class="table table-hover">
- <thead>
- <tr>
- <th>Colaborador</th>
- <th>Email</th>
- <th>Rol</th>
- <th class="text-end">Acciones</th>
- </tr>
- </thead>
- <tbody>
- <t t-foreach="collaborators" t-as="collaborator">
- <tr>
- <td>
- <t t-esc="collaborator.partner_id.name"/>
- </td>
- <td>
- <t t-esc="collaborator.partner_email or ''"/>
- </td>
- <td>
- <t t-if="collaborator.partner_id.id != current_partner_id">
- <form t-attf-action="/my/helpdesk/teams/#{team.id}/collaborators/#{collaborator.id}/update" method="post" style="display: inline;">
- <input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
- <select name="access_mode" class="form-select form-select-sm" onchange="this.form.submit();">
- <option value="admin" t-att-selected="collaborator.access_mode == 'admin'">Administrator</option>
- <option value="user_all" t-att-selected="collaborator.access_mode == 'user_all'">User - All Tickets</option>
- <option value="user_own" t-att-selected="collaborator.access_mode == 'user_own'">User - Own Tickets</option>
- </select>
- </form>
- </t>
- <t t-else="">
- <select class="form-select form-select-sm" disabled="disabled">
- <option value="admin" t-att-selected="collaborator.access_mode == 'admin'">Administrator</option>
- <option value="user_all" t-att-selected="collaborator.access_mode == 'user_all'">User - All Tickets</option>
- <option value="user_own" t-att-selected="collaborator.access_mode == 'user_own'">User - Own Tickets</option>
- </select>
- <small class="text-muted d-block mt-1">No puedes cambiar tu propio rol</small>
- </t>
- </td>
- <td class="text-end">
- <t t-if="collaborator.partner_id.id != current_partner_id">
- <form t-attf-action="/my/helpdesk/teams/#{team.id}/collaborators/#{collaborator.id}/delete" method="post" style="display: inline;" onsubmit="return confirm('¿Estás seguro de que deseas eliminar este colaborador?');">
- <input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
- <button type="submit" class="btn btn-sm btn-outline-danger">
- <i class="fa fa-trash me-1"/>
- Eliminar
- </button>
- </form>
- </t>
- <t t-else="">
- <span class="text-muted">No puedes eliminarte a ti mismo</span>
- </t>
- </td>
- </tr>
- </t>
- </tbody>
- </table>
- </div>
- </t>
- <t t-else="">
- <p class="text-muted mb-0">No hay colaboradores en este equipo.</p>
- </t>
- </div>
- </div>
-
- <div class="mt-3">
- <a href="/my/tickets" class="btn btn-secondary">
- <i class="fa fa-arrow-left me-2"/>
- Volver a Tickets
- </a>
- </div>
- </div>
- </div>
- </div>
-
- <script type="text/javascript">
- <t t-set="teamId" t-value="team.id"/>
- <![CDATA[
- document.addEventListener('DOMContentLoaded', function() {
- 'use strict';
-
- var teamId = ]]><t t-esc="teamId"/><![CDATA[;
- var searchUrl = '/my/helpdesk/teams/' + teamId + '/collaborators/search-partners';
- var createUrl = '/my/helpdesk/teams/' + teamId + '/collaborators/create-partner';
-
- var searchInput = document.getElementById('partner-search-input');
- var partnerIdInput = document.getElementById('partner-id-input');
- var resultsDiv = document.getElementById('partner-search-results');
- var inputGroup = searchInput ? searchInput.closest('.input-group') : null;
- var parentDiv = searchInput ? searchInput.closest('.mb-3') : null;
-
- if (!searchInput || !resultsDiv || !partnerIdInput) {
- console.error('Required elements not found');
- return;
- }
-
- // Set width of results div to match input group
- if (inputGroup && parentDiv) {
- resultsDiv.style.width = inputGroup.offsetWidth + 'px';
- }
-
- var searchTimeout;
-
- // Function to perform search
- function performSearch(searchTerm) {
- clearTimeout(searchTimeout);
- var term = searchTerm ? searchTerm.trim() : '';
-
- console.log('🔍 Performing search with term:', term);
-
- // If term is empty or less than 2 chars, show all available contacts (empty search)
- // This allows users to see all related contacts even without typing
- searchTimeout = setTimeout(function() {
- console.log('📡 Fetching from:', searchUrl);
- fetch(searchUrl, {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- 'X-Requested-With': 'XMLHttpRequest'
- },
- body: JSON.stringify({search_term: term, limit: 20})
- })
- .then(function(response) {
- console.log('📥 Response status:', response.status, response.statusText);
- if (!response.ok) {
- throw new Error('Network response was not ok: ' + response.status);
- }
- return response.json();
- })
- .then(function(data) {
- console.log('📦 Raw response data:', data);
-
- // Odoo JSON-RPC wraps the response in {jsonrpc: "2.0", result: {...}}
- // Check if it's wrapped
- var responseData = data;
- if (data && data.jsonrpc && data.result !== undefined) {
- console.log('📦 Unwrapping JSON-RPC response');
- responseData = data.result;
- }
-
- console.log('📦 Processed response data:', responseData);
-
- if (responseData.error) {
- console.error('❌ Error in response:', responseData.error);
- resultsDiv.innerHTML = '<div class="p-2 text-danger">' + (responseData.error || 'Error desconocido') + '</div>';
- resultsDiv.style.display = 'block';
- return;
- }
-
- if (responseData.partners && responseData.partners.length > 0) {
- console.log('✅ Found', responseData.partners.length, 'partners');
- resultsDiv.innerHTML = responseData.partners.map(function(p) {
- var name = (p.name || '').replace(/"/g, '"').replace(/'/g, ''');
- var email = (p.email || 'Sin email').replace(/"/g, '"').replace(/'/g, ''');
- return '<div class="p-2 border-bottom partner-option" style="cursor: pointer;" data-id="' + p.id + '" data-name="' + name + '" data-email="' + email + '"><strong>' + (p.name || 'Sin nombre') + '</strong><br/><small class="text-muted">' + email + '</small></div>';
- }).join('');
-
- resultsDiv.querySelectorAll('.partner-option').forEach(function(option) {
- option.addEventListener('click', function() {
- searchInput.value = this.dataset.name || '';
- partnerIdInput.value = this.dataset.id || '';
- resultsDiv.style.display = 'none';
- });
- });
-
- resultsDiv.style.display = 'block';
- } else {
- console.log('⚠️ No partners found in response');
- resultsDiv.innerHTML = '<div class="p-2 text-muted">No se encontraron contactos</div>';
- resultsDiv.style.display = 'block';
- }
- })
- .catch(function(error) {
- console.error('❌ Search error:', error);
- resultsDiv.innerHTML = '<div class="p-2 text-danger">Error al buscar: ' + (error.message || error) + '</div>';
- resultsDiv.style.display = 'block';
- });
- }, 300);
- }
-
- // Search on input
- searchInput.addEventListener('input', function() {
- var term = this.value.trim();
- if (term.length === 0) {
- partnerIdInput.value = '';
- }
- performSearch(term);
- });
-
- // Show all contacts when focusing on the input (if empty)
- searchInput.addEventListener('focus', function() {
- if (this.value.trim().length === 0) {
- performSearch('');
- }
- });
-
- // Close results when clicking outside
- document.addEventListener('click', function(e) {
- if (parentDiv && !parentDiv.contains(e.target)) {
- resultsDiv.style.display = 'none';
- }
- });
- });
- ]]>
- </script>
- </t>
- </template>
- <!-- Page to create new contact and add as collaborator -->
- <template id="portal_new_collaborator" name="New Collaborator">
- <t t-call="portal.portal_layout">
- <t t-set="title">Crear Nuevo Contacto y Colaborador</t>
- <div class="container mt-3">
- <div class="row">
- <div class="col-12">
- <h2 class="mb-4">
- <i class="fa fa-user-plus me-2"/>
- Crear Nuevo Contacto y Colaborador - <t t-esc="team.name"/>
- </h2>
-
- <!-- Success/Error Messages -->
- <t t-if="request.session.get('success')">
- <div class="alert alert-success alert-dismissible fade show" role="alert">
- <t t-esc="request.session.pop('success')"/>
- <button type="button" class="btn-close" data-bs-dismiss="alert"></button>
- </div>
- </t>
- <t t-if="request.session.get('error')">
- <div class="alert alert-danger alert-dismissible fade show" role="alert">
- <t t-esc="request.session.pop('error')"/>
- <button type="button" class="btn-close" data-bs-dismiss="alert"></button>
- </div>
- </t>
-
- <!-- Create Contact Form -->
- <div class="card">
- <div class="card-header">
- <h5 class="mb-0">Información del Contacto</h5>
- </div>
- <div class="card-body">
- <form t-attf-action="/my/helpdesk/teams/#{team.id}/collaborators/create" method="post" id="create-collaborator-form">
- <input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
-
- <div class="row">
- <div class="col-md-6">
- <div class="mb-3">
- <label for="name" class="form-label">Nombre <span class="text-danger">*</span></label>
- <input type="text"
- class="form-control"
- id="name"
- name="name"
- required="required"
- placeholder="Nombre completo del contacto"
- value=""/>
- <div class="invalid-feedback">El nombre es requerido.</div>
- </div>
- </div>
- <div class="col-md-6">
- <div class="mb-3">
- <label for="email" class="form-label">Email <span class="text-danger">*</span></label>
- <input type="email"
- class="form-control"
- id="email"
- name="email"
- required="required"
- placeholder="email@ejemplo.com"
- value=""/>
- <div class="invalid-feedback">El email es requerido y debe ser válido.</div>
- </div>
- </div>
- </div>
-
- <div class="row">
- <div class="col-md-6">
- <div class="mb-3">
- <label for="phone" class="form-label">Teléfono</label>
- <input type="tel"
- class="form-control"
- id="phone"
- name="phone"
- placeholder="+52 55 1234 5678"
- value=""/>
- <small class="form-text text-muted">Opcional.</small>
- </div>
- </div>
- <div class="col-md-6">
- <div class="mb-3">
- <label for="function" class="form-label">Cargo / Función</label>
- <input type="text"
- class="form-control"
- id="function"
- name="function"
- placeholder="Ej: Gerente de TI, Desarrollador, etc."
- value=""/>
- <small class="form-text text-muted">Opcional.</small>
- </div>
- </div>
- </div>
-
- <div class="row">
- <div class="col-md-6">
- <div class="mb-3">
- <label for="access_mode" class="form-label">Rol del Colaborador <span class="text-danger">*</span></label>
- <select class="form-select" id="access_mode" name="access_mode" required="required">
- <option value="user_own">User - Own Tickets</option>
- <option value="user_all">User - All Tickets</option>
- <option value="admin">Administrator</option>
- </select>
- <small class="form-text text-muted">
- <strong>Administrator:</strong> Puede ver todos los tickets y gestionar otros usuarios.<br/>
- <strong>User - All Tickets:</strong> Puede ver todos los tickets y crear sus propios tickets.<br/>
- <strong>User - Own Tickets:</strong> Solo puede crear y ver sus propios tickets.
- </small>
- </div>
- </div>
- </div>
-
- <div class="alert alert-info">
- <i class="fa fa-info-circle me-2"></i>
- <small>El contacto se creará en tu misma red de contactos y se agregará automáticamente como colaborador del equipo con el rol seleccionado.</small>
- </div>
-
- <div class="mt-4">
- <button type="submit" class="btn btn-primary">
- <i class="fa fa-save me-2"/>
- Crear Contacto y Agregar como Colaborador
- </button>
- <a t-attf-href="/my/helpdesk/teams/#{team.id}/collaborators" class="btn btn-secondary ms-2">
- <i class="fa fa-times me-2"/>
- Cancelar
- </a>
- </div>
- </form>
- </div>
- </div>
-
- <div class="mt-3">
- <a t-attf-href="/my/helpdesk/teams/#{team.id}/collaborators" class="btn btn-secondary">
- <i class="fa fa-arrow-left me-2"/>
- Volver a Colaboradores
- </a>
- </div>
- </div>
- </div>
- </div>
- </t>
- </template>
- <!-- Wizard to share team / manage collaborators -->
- <!-- Wizard to share team / manage collaborators -->
- <template id="portal_team_share_wizard" name="Team Share Wizard">
- <t t-call="portal.portal_layout">
- <t t-set="title">Compartir Equipo</t>
- <div class="container mt-3">
- <div class="row">
- <div class="col-12">
- <h2 class="mb-4">
- <i class="fa fa-share-alt me-2"/>
- Compartir Equipo - <t t-esc="team.name"/>
- </h2>
-
- <form t-attf-action="/my/helpdesk/teams/#{team.id}/collaborators/share" method="post" class="card">
- <div class="card-header">
- <h5 class="mb-0">Gestionar Colaboradores</h5>
- </div>
- <div class="card-body">
- <input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
-
- <div class="mb-3">
- <label class="form-label">Enlace Público</label>
- <div class="input-group">
- <input type="text" class="form-control" t-att-value="wizard.share_link" readonly="readonly"/>
- <button type="button" class="btn btn-outline-secondary" onclick="navigator.clipboard.writeText(this.previousElementSibling.value); this.innerHTML='<i class="fa fa-check"></i> Copiado'; setTimeout(() => this.innerHTML='<i class="fa fa-copy"></i> Copiar', 2000);">
- <i class="fa fa-copy"/>
- Copiar
- </button>
- </div>
- </div>
-
- <div class="mb-3">
- <label class="form-label">Colaboradores</label>
- <div class="table-responsive">
- <table class="table table-sm">
- <thead>
- <tr>
- <th>Colaborador</th>
- <th>Rol</th>
- <th>Enviar Invitación</th>
- <th class="text-end">Acción</th>
- </tr>
- </thead>
- <tbody>
- <t t-foreach="wizard.collaborator_ids" t-as="collab_wiz">
- <tr>
- <td>
- <t t-esc="collab_wiz.partner_id.name"/>
- <br/>
- <small class="text-muted"><t t-esc="collab_wiz.partner_id.email or ''"/></small>
- </td>
- <td>
- <select name="access_mode" class="form-select form-select-sm">
- <option value="admin" t-att-selected="collab_wiz.access_mode == 'admin'">Administrator</option>
- <option value="user_all" t-att-selected="collab_wiz.access_mode == 'user_all'">User - All Tickets</option>
- <option value="user_own" t-att-selected="collab_wiz.access_mode == 'user_own'">User - Own Tickets</option>
- </select>
- </td>
- <td class="text-center">
- <input type="checkbox" name="send_invitation" t-att-checked="collab_wiz.send_invitation" class="form-check-input"/>
- </td>
- <td class="text-end">
- <button type="button" class="btn btn-sm btn-outline-danger" onclick="this.closest('tr').remove();">
- <i class="fa fa-trash"/>
- </button>
- </td>
- </tr>
- </t>
- </tbody>
- </table>
- </div>
- <button type="button" class="btn btn-sm btn-outline-primary mt-2" onclick="addCollaboratorRow(this);">
- <i class="fa fa-plus me-1"/>
- Agregar Colaborador
- </button>
- </div>
-
- <div class="alert alert-info">
- <strong>Roles disponibles:</strong>
- <ul class="mb-0">
- <li><strong>Administrator:</strong> Puede ver todos los tickets y gestionar otros usuarios.</li>
- <li><strong>User - All Tickets:</strong> Puede ver todos los tickets y crear sus propios tickets.</li>
- <li><strong>User - Own Tickets:</strong> Solo puede crear y ver sus propios tickets.</li>
- </ul>
- <p class="mb-0 mt-2"><strong>Nota:</strong> Solo puedes agregar contactos de tu misma red de contactos (misma empresa).</p>
- </div>
- </div>
- <div class="card-footer">
- <button type="submit" name="action_share" class="btn btn-primary">
- <i class="fa fa-share me-2"/>
- Guardar Cambios
- </button>
- <a t-attf-href="/my/helpdesk/teams/#{team.id}/collaborators" class="btn btn-secondary">
- Cancelar
- </a>
- </div>
- </form>
-
- <div class="mt-3">
- <a t-attf-href="/my/helpdesk/teams/#{team.id}/collaborators" class="btn btn-secondary">
- <i class="fa fa-arrow-left me-2"/>
- Volver a Colaboradores
- </a>
- </div>
- </div>
- </div>
- </div>
- <script type="text/javascript">
- <t t-set="teamId" t-value="team.id"/>
- <![CDATA[
- (function() {
- 'use strict';
-
- var teamId = ]]><t t-esc="teamId"/><![CDATA[;
- var searchUrl = '/my/helpdesk/teams/' + teamId + '/collaborators/search-partners';
- var createUrl = '/my/helpdesk/teams/' + teamId + '/collaborators/create-partner';
-
- window.addCollaboratorRow = function(button) {
- var tbody = button.closest('.card-body').querySelector('tbody');
- var row = document.createElement('tr');
- row.innerHTML = '<td><div class="input-group" style="position: relative;"><input type="text" class="form-control partner-search" placeholder="Buscar contacto o crear nuevo..." autocomplete="off" data-partner-id="" data-partner-name=""/><button type="button" class="btn btn-outline-secondary" onclick="createNewPartner(this);"><i class="fa fa-plus"></i> Nuevo</button></div><div class="partner-search-results" style="display: none; position: absolute; z-index: 1000; background: white; border: 1px solid #ddd; max-height: 200px; overflow-y: auto; width: 100%; box-shadow: 0 2px 5px rgba(0,0,0,0.2);"></div></td><td><select name="new_access_mode" class="form-select form-select-sm"><option value="user_own">User - Own Tickets</option><option value="user_all">User - All Tickets</option><option value="admin">Administrator</option></select></td><td class="text-center"><input type="checkbox" name="new_send_invitation" class="form-check-input" checked/></td><td class="text-end"><button type="button" class="btn btn-sm btn-outline-danger" onclick="this.closest(\'tr\').remove();"><i class="fa fa-trash"></i></button></td>';
- tbody.appendChild(row);
-
- var searchInput = row.querySelector('.partner-search');
- var resultsDiv = row.querySelector('.partner-search-results');
- var inputGroup = searchInput.closest('.input-group');
- inputGroup.style.position = 'relative';
- resultsDiv.style.position = 'absolute';
- resultsDiv.style.top = '100%';
- resultsDiv.style.left = '0';
- resultsDiv.style.width = inputGroup.offsetWidth + 'px';
-
- var searchTimeout;
-
- searchInput.addEventListener('input', function() {
- clearTimeout(searchTimeout);
- var term = this.value.trim();
-
- if (term.length < 2) {
- resultsDiv.style.display = 'none';
- return;
- }
-
- searchTimeout = setTimeout(function() {
- fetch(searchUrl, {
- method: 'POST',
- headers: {'Content-Type': 'application/json'},
- body: JSON.stringify({search_term: term, limit: 10})
- })
- .then(function(response) { return response.json(); })
- .then(function(data) {
- if (data.error) {
- resultsDiv.innerHTML = '<div class="p-2 text-danger">' + data.error + '</div>';
- resultsDiv.style.display = 'block';
- return;
- }
-
- if (data.partners && data.partners.length > 0) {
- resultsDiv.innerHTML = data.partners.map(function(p) {
- return '<div class="p-2 border-bottom partner-option" style="cursor: pointer;" data-id="' + p.id + '" data-name="' + p.name.replace(/"/g, '"') + '" data-email="' + (p.email || '') + '"><strong>' + p.name + '</strong><br/><small class="text-muted">' + (p.email || 'Sin email') + '</small></div>';
- }).join('');
-
- resultsDiv.querySelectorAll('.partner-option').forEach(function(option) {
- option.addEventListener('click', function() {
- searchInput.value = this.dataset.name;
- searchInput.dataset.partnerId = this.dataset.id;
- searchInput.dataset.partnerName = this.dataset.name;
- resultsDiv.style.display = 'none';
- });
- });
-
- resultsDiv.style.display = 'block';
- } else {
- resultsDiv.innerHTML = '<div class="p-2 text-muted">No se encontraron contactos</div>';
- resultsDiv.style.display = 'block';
- }
- })
- .catch(function(error) {
- resultsDiv.innerHTML = '<div class="p-2 text-danger">Error al buscar: ' + error + '</div>';
- resultsDiv.style.display = 'block';
- });
- }, 300);
- });
-
- document.addEventListener('click', function(e) {
- if (!row.contains(e.target)) {
- resultsDiv.style.display = 'none';
- }
- });
-
- searchInput.focus();
- };
-
- window.createNewPartner = function(button) {
- var row = button.closest('tr');
- var searchInput = row.querySelector('.partner-search');
- var name = searchInput.value.trim();
-
- if (!name) {
- alert('Por favor ingresa un nombre para el nuevo contacto.');
- return;
- }
-
- var email = prompt('Ingresa el email del nuevo contacto (opcional):', '');
- if (email === null) return;
-
- fetch(createUrl, {
- method: 'POST',
- headers: {'Content-Type': 'application/json'},
- body: JSON.stringify({name: name, email: email || ''})
- })
- .then(function(response) { return response.json(); })
- .then(function(data) {
- if (data.error) {
- alert('Error: ' + data.error);
- return;
- }
-
- if (data.partner) {
- searchInput.value = data.partner.name;
- searchInput.dataset.partnerId = data.partner.id;
- searchInput.dataset.partnerName = data.partner.name;
- alert('Contacto creado exitosamente.');
- }
- })
- .catch(function(error) {
- alert('Error al crear contacto: ' + error);
- });
- };
-
- document.addEventListener('DOMContentLoaded', function() {
- var form = document.querySelector('form[action*="/collaborators/share"]');
- if (form) {
- form.addEventListener('submit', function(e) {
- var rows = form.querySelectorAll('tbody tr');
- rows.forEach(function(row, index) {
- var searchInput = row.querySelector('.partner-search');
- if (searchInput && searchInput.dataset.partnerId) {
- var hiddenInput = document.createElement('input');
- hiddenInput.type = 'hidden';
- hiddenInput.name = 'new_collaborator_partner_id[]';
- hiddenInput.value = searchInput.dataset.partnerId;
- form.appendChild(hiddenInput);
-
- var accessModeSelect = row.querySelector('select[name="new_access_mode"]');
- if (accessModeSelect) {
- var accessModeInput = document.createElement('input');
- accessModeInput.type = 'hidden';
- accessModeInput.name = 'new_collaborator_access_mode[]';
- accessModeInput.value = accessModeSelect.value;
- form.appendChild(accessModeInput);
- }
-
- var sendInvitationCheckbox = row.querySelector('input[name="new_send_invitation"]');
- if (sendInvitationCheckbox) {
- var sendInvitationInput = document.createElement('input');
- sendInvitationInput.type = 'hidden';
- sendInvitationInput.name = 'new_collaborator_send_invitation[]';
- sendInvitationInput.value = sendInvitationCheckbox.checked ? '1' : '0';
- form.appendChild(sendInvitationInput);
- }
- }
- });
- });
- }
- });
- })();
- ]]>
- </script>
- </t>
- </template>
- </odoo>
|