Portal Timesheets Feature\n- Add new portal timesheets list with edit modal: views/portal_timesheets_templates.xml\n- New controller: controllers/timesheets_portal.py\n- New model for event timesheets: models/sports_event_timesheet.py\n- Security: sports_event_timesheet_rules.xml + ir.model.access.csv updates\n\nEvent/Portal Integration\n- Wire timesheets into events portal: controllers/events_portal.py, views/portal_event_* templates, menus\n- Sports event model enhancements: models/sports_event.py\n\nGeneral Portal/View Updates\n- Updates across player/team/user/partner views for consistency and UX\n\nAsset Cleanup\n- Remove unused 24h datetime initializer from frontend bundles\n * Dropped bemade_sports_clinic/static/src/js/portal_datetime_24h.js from __manifest__.py assets\n\nNotes\n- No functional dependency on the 24h initializer remains; inputs use native datetime-local or explicit formatting in templates.
407 lines
32 KiB
XML
407 lines
32 KiB
XML
<?xml version="1.0" encoding="UTF-8" ?>
|
|
<odoo>
|
|
<data>
|
|
<!-- Events List Template -->
|
|
<template id="portal_events_list" name="Portal Events List">
|
|
<t t-call="portal.portal_layout">
|
|
<t t-set="breadcrumbs_searchbar" t-value="True"/>
|
|
<t t-call="portal.portal_searchbar">
|
|
<t t-set="title">Events</t>
|
|
</t>
|
|
|
|
<div class="container-fluid">
|
|
<!-- Breadcrumbs -->
|
|
<div class="row">
|
|
<div class="col-lg-12">
|
|
<nav aria-label="breadcrumb">
|
|
<ol class="breadcrumb">
|
|
<li class="breadcrumb-item">
|
|
<a href="/my">Home</a>
|
|
</li>
|
|
<li class="breadcrumb-item active" aria-current="page">Events</li>
|
|
</ol>
|
|
</nav>
|
|
</div>
|
|
</div>
|
|
<!-- View Type Navigation -->
|
|
<div class="row mb-1">
|
|
<div class="col-12">
|
|
<ul class="nav nav-pills">
|
|
<li class="nav-item">
|
|
<a t-attf-class="nav-link #{'active' if view_type == 'all' else ''}"
|
|
t-attf-href="/my/events?view_type=all#{project_id and '&project_id=%s' % project_id or ''}#{assigned_user_id and '&assigned_user_id=%s' % assigned_user_id or ''}#{date_from and '&date_from=%s' % date_from or ''}#{date_to and '&date_to=%s' % date_to or ''}#{group_by and '&group_by=%s' % group_by or ''}#{sortby and '&sortby=%s' % sortby or ''}#{no_default_dates and '&no_default_dates=1' or ''}">
|
|
All Events
|
|
</a>
|
|
</li>
|
|
<li class="nav-item">
|
|
<a t-attf-class="nav-link #{'active' if view_type == 'my' else ''}"
|
|
t-attf-href="/my/events?view_type=my#{project_id and '&project_id=%s' % project_id or ''}#{assigned_user_id and '&assigned_user_id=%s' % assigned_user_id or ''}#{date_from and '&date_from=%s' % date_from or ''}#{date_to and '&date_to=%s' % date_to or ''}#{group_by and '&group_by=%s' % group_by or ''}#{sortby and '&sortby=%s' % sortby or ''}#{no_default_dates and '&no_default_dates=1' or ''}">
|
|
My Events
|
|
</a>
|
|
</li>
|
|
<li class="nav-item">
|
|
<a t-attf-class="nav-link #{'active' if view_type == 'unassigned' else ''}"
|
|
t-attf-href="/my/events?view_type=unassigned#{project_id and '&project_id=%s' % project_id or ''}#{assigned_user_id and '&assigned_user_id=%s' % assigned_user_id or ''}#{date_from and '&date_from=%s' % date_from or ''}#{date_to and '&date_to=%s' % date_to or ''}#{group_by and '&group_by=%s' % group_by or ''}#{sortby and '&sortby=%s' % sortby or ''}#{no_default_dates and '&no_default_dates=1' or ''}">
|
|
Unassigned Events
|
|
</a>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
<div class="row mb-3">
|
|
<div class="col-12">
|
|
<div class="d-flex flex-wrap gap-2">
|
|
<a href="/my/events?view_type=all&no_default_dates=1" class="btn btn-outline-secondary btn-sm">
|
|
<i class="fa fa-times"/> Clear Filters
|
|
</a>
|
|
<t t-if="can_edit">
|
|
<a href="/my/event/create" class="btn btn-primary btn-sm">
|
|
<i class="fa fa-plus"/> Add Event
|
|
</a>
|
|
</t>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Filters -->
|
|
<div class="row mb-3">
|
|
<div class="col-12">
|
|
<form method="get" class="o_portal_search_panel" action="/my/events">
|
|
<input type="hidden" name="view_type" t-att-value="view_type"/>
|
|
|
|
<!-- First Row: Main Filters -->
|
|
<div class="row g-2 mb-2">
|
|
<!-- Organization Filter -->
|
|
<div class="col-md-4">
|
|
<select name="organization_id" class="form-select" onchange="this.form.submit();">
|
|
<option value="">All Organizations</option>
|
|
<t t-foreach="organizations" t-as="organization">
|
|
<option t-att-value="organization.id" t-att-selected="'selected' if organization_id and organization_id == organization.id else None">
|
|
<t t-esc="organization.name"/>
|
|
</option>
|
|
</t>
|
|
</select>
|
|
</div>
|
|
|
|
<!-- Team Filter -->
|
|
<div class="col-md-4">
|
|
<select name="team_id" class="form-select" onchange="this.form.submit();">
|
|
<option value="">All Teams</option>
|
|
<t t-foreach="teams" t-as="team">
|
|
<option t-att-value="team.id" t-att-selected="team_id == team.id">
|
|
<t t-esc="team.name"/>
|
|
</option>
|
|
</t>
|
|
</select>
|
|
</div>
|
|
|
|
<!-- Assigned Professional Filter -->
|
|
<div class="col-md-4">
|
|
<select name="assigned_user_id" class="form-select" onchange="this.form.submit();">
|
|
<option value="">All Assigned Professionals</option>
|
|
<t t-foreach="treatment_professionals" t-as="user">
|
|
<option t-att-value="user.id" t-att-selected="assigned_user_id == user.id">
|
|
<t t-esc="user.name"/>
|
|
</option>
|
|
</t>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Second Row: Date Filters -->
|
|
<div class="row g-2">
|
|
<input type="hidden" name="no_default_dates" t-att-value="no_default_dates or ''"/>
|
|
<!-- Date From Filter -->
|
|
<div class="col-md-6">
|
|
<label for="date_from" class="form-label text-muted small">From Date</label>
|
|
<div class="input-group">
|
|
<input type="date" id="date_from" name="date_from" class="form-control"
|
|
t-att-value="date_from" onchange="this.form.submit();"/>
|
|
<button type="button" class="btn btn-outline-secondary"
|
|
title="Clear From Date"
|
|
onclick="document.getElementById('date_from').value=''; this.form.no_default_dates.value='1'; this.form.submit();">
|
|
<i class="fa fa-times"/>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Date To Filter -->
|
|
<div class="col-md-6">
|
|
<label for="date_to" class="form-label text-muted small">To Date</label>
|
|
<div class="input-group">
|
|
<input type="date" id="date_to" name="date_to" class="form-control"
|
|
t-att-value="date_to" onchange="this.form.submit();"/>
|
|
<button type="button" class="btn btn-outline-secondary"
|
|
title="Clear To Date"
|
|
onclick="document.getElementById('date_to').value=''; this.form.no_default_dates.value='1'; this.form.submit();">
|
|
<i class="fa fa-times"/>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Record Count Display -->
|
|
<div class="row mb-3">
|
|
<div class="col-12">
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<div>
|
|
<span class="text-muted">
|
|
<t t-if="events">
|
|
Showing <strong t-esc="len(events)"/> events
|
|
<t t-if="pager and pager.get('page_count', 1) > 1">
|
|
(Page <t t-esc="pager.get('page', {}).get('num', 1)"/> of <t t-esc="pager.get('page_count', 1)"/>)
|
|
</t>
|
|
</t>
|
|
<t t-if="not events">
|
|
<strong>0</strong> events found
|
|
</t>
|
|
</span>
|
|
</div>
|
|
<div class="d-flex align-items-center gap-2">
|
|
<!-- Sort & Group Controls -->
|
|
<form method="get" action="/my/events" class="d-flex align-items-center gap-2">
|
|
<input type="hidden" name="view_type" t-att-value="view_type"/>
|
|
<input type="hidden" name="team_id" t-att-value="team_id or ''"/>
|
|
<input type="hidden" name="organization_id" t-att-value="organization_id or ''"/>
|
|
<input type="hidden" name="assigned_user_id" t-att-value="assigned_user_id or ''"/>
|
|
<input type="hidden" name="date_from" t-att-value="date_from or ''"/>
|
|
<input type="hidden" name="date_to" t-att-value="date_to or ''"/>
|
|
<input type="hidden" name="no_default_dates" t-att-value="no_default_dates or ''"/>
|
|
|
|
<label class="small text-muted">Sort by</label>
|
|
<select name="sortby" class="form-select form-select-sm" onchange="this.form.submit()">
|
|
<option value="date" t-att-selected="'selected' if (not sortby or sortby == 'date') else None">Date (asc)</option>
|
|
<option value="date_desc" t-att-selected="'selected' if sortby == 'date_desc' else None">Date (desc)</option>
|
|
<option value="name" t-att-selected="'selected' if sortby == 'name' else None">Name</option>
|
|
<option value="team" t-att-selected="'selected' if sortby == 'team' else None">Team</option>
|
|
<option value="assigned" t-att-selected="'selected' if sortby == 'assigned' else None">Assigned</option>
|
|
</select>
|
|
|
|
<label class="small text-muted">Group by</label>
|
|
<select name="group_by" class="form-select form-select-sm" onchange="this.form.submit()">
|
|
<option value="" t-att-selected="'selected' if not group_by else None">None</option>
|
|
<option value="team" t-att-selected="'selected' if group_by == 'team' else None">Team</option>
|
|
<option value="month" t-att-selected="'selected' if group_by == 'month' else None">Month</option>
|
|
<option value="week" t-att-selected="'selected' if group_by == 'week' else None">Week</option>
|
|
<option value="therapist" t-att-selected="'selected' if group_by == 'therapist' else None">Therapist</option>
|
|
</select>
|
|
</form>
|
|
<t t-if="view_type == 'all'">
|
|
<span class="badge bg-secondary">All Events</span>
|
|
</t>
|
|
<t t-if="view_type == 'my'">
|
|
<span class="badge bg-primary">My Events</span>
|
|
</t>
|
|
<t t-if="view_type == 'unassigned'">
|
|
<span class="badge bg-warning">Unassigned Events</span>
|
|
</t>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Events Table -->
|
|
<div class="row">
|
|
<div class="col-12">
|
|
<t t-if="not events">
|
|
<div class="alert alert-info text-center">
|
|
<h4>No events found</h4>
|
|
<p>There are no events matching your criteria.</p>
|
|
</div>
|
|
</t>
|
|
|
|
<t t-if="events and group_by">
|
|
<!-- Grouped accordion view -->
|
|
<div class="accordion" id="eventsAccordion">
|
|
<t t-foreach="grouped_events" t-as="group">
|
|
<div class="accordion-item mb-2">
|
|
<h2 class="accordion-header">
|
|
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" t-attf-data-bs-target="##{group.get('dom_id')}" aria-expanded="false" t-attf-aria-controls="#{group.get('dom_id')}">
|
|
<span class="fw-semibold" t-esc="group.get('label')"/>
|
|
<span class="ms-2 badge bg-light text-muted" t-esc="len(group.get('events') or [])"/>
|
|
</button>
|
|
</h2>
|
|
<div class="accordion-collapse collapse" t-attf-id="#{group.get('dom_id')}" data-bs-parent="#eventsAccordion">
|
|
<div class="accordion-body">
|
|
<div class="row g-2">
|
|
<t t-foreach="group.get('events')" t-as="event">
|
|
<div class="col-12">
|
|
<div class="card h-100 shadow-sm portal-event-card" style="cursor: pointer;"
|
|
t-att-onclick="'window.location.href=\'%s\'' % ('/my/event/%s' % event.id)">
|
|
<div class="card-header d-flex align-items-start justify-content-between">
|
|
<div class="fw-semibold text-break">
|
|
<t t-esc="event.name"/>
|
|
</div>
|
|
<div class="ms-2">
|
|
<t t-if="event.assigned_staff_ids">
|
|
<t t-foreach="event.assigned_staff_ids" t-as="staff">
|
|
<span class="badge rounded-pill me-1 mb-1 badge-fitcrew-initials" t-att-style="'background-color:#783E88 !important;color:#E5E5E5 !important;background-image:none !important'" t-esc="''.join([p[0] for p in (staff.name or '').split()])"/>
|
|
</t>
|
|
</t>
|
|
<t t-if="not event.assigned_staff_ids">
|
|
<span class="badge rounded-pill bg-danger">-</span>
|
|
</t>
|
|
</div>
|
|
</div>
|
|
<div class="card-body">
|
|
<!-- Dates + Type -->
|
|
<div class="mb-2">
|
|
<i class="fa fa-calendar me-1 text-muted"/>
|
|
<t t-if="event.therapist_start">
|
|
<span t-esc="event.therapist_start" t-options="{'widget': 'date', 'format': 'yyyy-MM-dd'}"/>
|
|
<span class="text-muted">•</span>
|
|
<span t-esc="event.therapist_start" t-options="{'widget': 'datetime', 'format': 'HH:mm'}"/>
|
|
<t t-if="event.date_start">
|
|
<small class="text-muted"> (sched <span t-esc="event.date_start" t-options="{'widget': 'datetime', 'format': 'HH:mm'}"/>)</small>
|
|
</t>
|
|
</t>
|
|
<t t-if="not event.therapist_start and event.date_start">
|
|
<span t-esc="event.date_start" t-options="{'widget': 'date', 'format': 'yyyy-MM-dd'}"/>
|
|
<span class="text-muted">•</span>
|
|
<span t-esc="event.date_start" t-options="{'widget': 'datetime', 'format': 'HH:mm'}"/>
|
|
</t>
|
|
<t t-if="not event.therapist_start and not event.date_start">
|
|
<span class="text-muted">No date</span>
|
|
</t>
|
|
</div>
|
|
<!-- Team + Type / Venue -->
|
|
<div class="row g-2 mb-2">
|
|
<div class="col-12 d-flex justify-content-between align-items-center">
|
|
<div>
|
|
<i class="fa fa-users me-1 text-muted"/>
|
|
<t t-if="event.team_id">
|
|
<span t-esc="event.team_id.name"/>
|
|
</t>
|
|
<t t-if="not event.team_id">
|
|
<span class="text-muted">No Team</span>
|
|
</t>
|
|
</div>
|
|
<span class="badge ms-2 badge-event-type" style="background-color:#302E33 !important;color:#E5E5E5 !important;background-image:none !important">
|
|
<t t-esc="dict(event._fields['event_type'].selection).get(event.event_type, event.event_type)"/>
|
|
</span>
|
|
</div>
|
|
<div class="col-12">
|
|
<i class="fa fa-map-marker me-1 text-muted"/>
|
|
<t t-if="event.venue_id">
|
|
<span t-esc="event.venue_id.name"/>
|
|
</t>
|
|
<t t-if="not event.venue_id">
|
|
<span class="text-muted">TBD</span>
|
|
</t>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Assigned Staff moved to header as initials -->
|
|
<t t-if="desc_visible.get(event.id)">
|
|
<div class="text-muted small oe_no_empty" t-field="event.description"/>
|
|
</t>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
</t>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</t>
|
|
</div>
|
|
|
|
<!-- Pagination -->
|
|
<t t-call="portal.pager"/>
|
|
</t>
|
|
<t t-if="events and not group_by">
|
|
<!-- Flat Card Grid View for Events -->
|
|
<div class="row g-2">
|
|
<t t-foreach="events" t-as="event">
|
|
<div class="col-12">
|
|
<div class="card h-100 shadow-sm portal-event-card" style="cursor: pointer;"
|
|
t-att-onclick="'window.location.href=\'%s\'' % ('/my/event/%s' % event.id)">
|
|
<div class="card-header d-flex align-items-start justify-content-between">
|
|
<div class="fw-semibold text-break">
|
|
<t t-esc="event.name"/>
|
|
</div>
|
|
<div class="ms-2">
|
|
<t t-if="event.assigned_staff_ids">
|
|
<t t-foreach="event.assigned_staff_ids" t-as="staff">
|
|
<span class="badge rounded-pill me-1 mb-1 badge-fitcrew-initials" style="background-color:#783E88 !important;color:#E5E5E5 !important;background-image:none !important" t-esc="''.join([p[0] for p in (staff.name or '').split()])"/>
|
|
</t>
|
|
</t>
|
|
<t t-if="not event.assigned_staff_ids">
|
|
<span class="badge rounded-pill bg-danger">-</span>
|
|
</t>
|
|
</div>
|
|
</div>
|
|
<div class="card-body">
|
|
<!-- Dates + Type -->
|
|
<div class="mb-2">
|
|
<i class="fa fa-calendar me-1 text-muted"/>
|
|
<t t-if="event.therapist_start">
|
|
<span t-esc="event.therapist_start" t-options="{'widget': 'date', 'format': 'yyyy-MM-dd'}"/>
|
|
<span class="text-muted">•</span>
|
|
<span t-esc="event.therapist_start" t-options="{'widget': 'datetime', 'format': 'HH:mm'}"/>
|
|
<t t-if="event.date_start">
|
|
|
|
</t>
|
|
</t>
|
|
<t t-if="not event.therapist_start and event.date_start">
|
|
<span t-esc="event.date_start" t-options="{'widget': 'date', 'format': 'yyyy-MM-dd'}"/>
|
|
<span class="text-muted">•</span>
|
|
<span t-esc="event.date_start" t-options="{'widget': 'datetime', 'format': 'HH:mm'}"/>
|
|
</t>
|
|
<t t-if="not event.therapist_start and not event.date_start">
|
|
<span class="text-muted">No date</span>
|
|
</t>
|
|
</div>
|
|
<!-- Team + Type / Venue -->
|
|
<div class="row g-2 mb-2">
|
|
<div class="col-12 d-flex justify-content-between align-items-center">
|
|
<div>
|
|
<i class="fa fa-users me-1 text-muted"/>
|
|
<t t-if="event.team_id">
|
|
<span t-esc="event.team_id.name"/>
|
|
</t>
|
|
<t t-if="not event.team_id">
|
|
<span class="text-muted">No Team</span>
|
|
</t>
|
|
</div>
|
|
<span class="badge ms-2 badge-event-type" style="background-color:#302E33 !important;color:#E5E5E5 !important;background-image:none !important">
|
|
<t t-esc="dict(event._fields['event_type'].selection).get(event.event_type, event.event_type)"/>
|
|
</span>
|
|
</div>
|
|
<div class="col-12">
|
|
<i class="fa fa-map-marker me-1 text-muted"/>
|
|
<t t-if="event.venue_id">
|
|
<span t-esc="event.venue_id.name"/>
|
|
</t>
|
|
<t t-if="not event.venue_id">
|
|
<span class="text-muted">TBD</span>
|
|
</t>
|
|
</div>
|
|
</div>
|
|
|
|
<t t-if="desc_visible.get(event.id)">
|
|
<div class="text-muted small oe_no_empty" t-field="event.description"/>
|
|
</t>
|
|
|
|
<!-- Assigned Staff moved to header as initials -->
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
</t>
|
|
</div>
|
|
|
|
<!-- Pagination -->
|
|
<t t-call="portal.pager"/>
|
|
</t>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</t>
|
|
</template>
|
|
</data>
|
|
</odoo>
|