portal: refactor teams list to responsive card grid; adjust events therapist badge styling
- Convert from table to Bootstrap card grid in for better long name visibility and mobile UX - Preserve fields: team name, parent org, player/injured counts, therapist-only activity count; keep pagination; no sorting/grouping changes - Events portal templates: set therapist initials badge to #783E88; leave event type badges unchanged - Add portal static assets scaffolding under and update manifest accordingly - Minor template cleanups; no functional changes beyond layout/styling
This commit is contained in:
parent
65b301b2e8
commit
6872e5fac2
5 changed files with 380 additions and 142 deletions
|
|
@ -101,6 +101,7 @@ This module provides a complete sports medicine clinic management solution with
|
|||
"application": True,
|
||||
"assets": {
|
||||
"web.assets_frontend": [
|
||||
"bemade_sports_clinic/static/src/scss/portal_badges.scss",
|
||||
],
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
from odoo.addons.portal.controllers.portal import CustomerPortal, pager
|
||||
from odoo import http, _, fields
|
||||
from odoo.tools import html2plaintext
|
||||
from odoo.exceptions import UserError, AccessError
|
||||
from datetime import datetime, timedelta
|
||||
from .access_control_mixin import AccessControlMixin
|
||||
|
|
@ -134,7 +135,7 @@ class EventsPortal(CustomerPortal, AccessControlMixin):
|
|||
|
||||
@http.route(['/my/events', '/my/events/page/<int:page>'], type='http', auth='user', website=True)
|
||||
def view_events(self, page=1, view_type='all', team_id=None, organization_id=None, assigned_user_id=None,
|
||||
date_from=None, date_to=None, sortby=None, search=None, no_default_dates=None, **kw):
|
||||
date_from=None, date_to=None, sortby=None, group_by=None, search=None, no_default_dates=None, **kw):
|
||||
"""Main events view with filtering and pagination"""
|
||||
|
||||
# Check access
|
||||
|
|
@ -220,7 +221,7 @@ class EventsPortal(CustomerPortal, AccessControlMixin):
|
|||
url='/my/events',
|
||||
url_args={'view_type': view_type, 'team_id': team_id, 'organization_id': organization_id,
|
||||
'assigned_user_id': assigned_user_id, 'date_from': date_from,
|
||||
'date_to': date_to, 'sortby': sortby, 'search': search, 'no_default_dates': no_default_dates},
|
||||
'date_to': date_to, 'sortby': sortby, 'group_by': group_by, 'search': search, 'no_default_dates': no_default_dates},
|
||||
total=event_count,
|
||||
page=page,
|
||||
step=self._items_per_page,
|
||||
|
|
@ -233,6 +234,91 @@ class EventsPortal(CustomerPortal, AccessControlMixin):
|
|||
limit=self._items_per_page,
|
||||
offset=pager_values['offset']
|
||||
)
|
||||
|
||||
# Controller-level sanitation: determine which descriptions have visible text
|
||||
try:
|
||||
desc_visible = {ev.id: bool(html2plaintext(ev.description or '').strip()) for ev in events}
|
||||
except Exception:
|
||||
# Fallback: basic check
|
||||
desc_visible = {ev.id: bool((ev.description or '').strip()) for ev in events}
|
||||
|
||||
# Grouping support
|
||||
grouped_events = None
|
||||
if group_by in ('team', 'month', 'week', 'therapist'):
|
||||
from collections import OrderedDict
|
||||
import unicodedata
|
||||
grouped = OrderedDict()
|
||||
|
||||
def _localize(dt):
|
||||
try:
|
||||
return fields.Datetime.context_timestamp(http.request.env.user, dt) if dt else None
|
||||
except Exception:
|
||||
return dt
|
||||
|
||||
def _slugify(text):
|
||||
# Normalize accents, lower, replace non-alnum with hyphens, collapse repeats
|
||||
if text is None:
|
||||
text = ''
|
||||
if not isinstance(text, str):
|
||||
text = str(text)
|
||||
norm = unicodedata.normalize('NFKD', text)
|
||||
ascii_str = ''.join([c for c in norm if not unicodedata.combining(c)])
|
||||
out = []
|
||||
prev_hyphen = False
|
||||
for ch in ascii_str.lower():
|
||||
if ch.isalnum():
|
||||
out.append(ch)
|
||||
prev_hyphen = False
|
||||
else:
|
||||
if not prev_hyphen:
|
||||
out.append('-')
|
||||
prev_hyphen = True
|
||||
slug = ''.join(out).strip('-') or 'group'
|
||||
return slug
|
||||
|
||||
used_dom_ids = set()
|
||||
|
||||
for ev in events:
|
||||
key = None
|
||||
label = None
|
||||
if group_by == 'team':
|
||||
key = ev.team_id.id or 0
|
||||
label = ev.team_id.name if ev.team_id else _('No Team')
|
||||
elif group_by == 'month':
|
||||
ldt = _localize(ev.date_start)
|
||||
if ldt:
|
||||
key = f"{ldt.year}-{ldt.month:02d}"
|
||||
label = ldt.strftime('%B %Y')
|
||||
else:
|
||||
key = 'no_date'
|
||||
label = _('No Date')
|
||||
elif group_by == 'week':
|
||||
ldt = _localize(ev.date_start)
|
||||
if ldt:
|
||||
iso = ldt.isocalendar()
|
||||
key = f"{iso.year}-W{iso.week:02d}"
|
||||
label = _('Week %(w)s, %(y)s', w=f"{iso.week:02d}", y=str(iso.year))
|
||||
else:
|
||||
key = 'no_date'
|
||||
label = _('No Date')
|
||||
elif group_by == 'therapist':
|
||||
primary = ev.assigned_staff_ids[:1]
|
||||
key = primary.id if primary else 0
|
||||
label = primary.name if primary else _('Unassigned')
|
||||
|
||||
if key not in grouped:
|
||||
# Construct a safe DOM id base
|
||||
base = f"grp-{group_by}-" + (_slugify(label) if group_by in ('month', 'week') else _slugify(key))
|
||||
dom_id = base
|
||||
i = 1
|
||||
while dom_id in used_dom_ids:
|
||||
i += 1
|
||||
dom_id = f"{base}-{i}"
|
||||
used_dom_ids.add(dom_id)
|
||||
grouped[key] = {'key': key, 'label': label, 'events': [], 'dom_id': dom_id}
|
||||
grouped[key]['events'].append(ev)
|
||||
|
||||
grouped_events = list(grouped.values())
|
||||
|
||||
# Get filter options
|
||||
teams = self._get_accessible_teams()
|
||||
|
|
@ -263,12 +349,15 @@ class EventsPortal(CustomerPortal, AccessControlMixin):
|
|||
'date_from': date_from,
|
||||
'date_to': date_to,
|
||||
'sortby': sortby,
|
||||
'group_by': group_by,
|
||||
'search': search,
|
||||
'teams': teams,
|
||||
'organizations': organizations,
|
||||
'treatment_professionals': treatment_professionals,
|
||||
'can_edit': can_edit,
|
||||
'no_default_dates': no_default_dates,
|
||||
'grouped_events': grouped_events,
|
||||
'desc_visible': desc_visible,
|
||||
}
|
||||
|
||||
return http.request.render('bemade_sports_clinic.portal_events_list', values)
|
||||
|
|
|
|||
42
bemade_sports_clinic/static/src/scss/portal_badges.scss
Normal file
42
bemade_sports_clinic/static/src/scss/portal_badges.scss
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
/* Therapist initials badge */
|
||||
.badge-fitcrew-initials,
|
||||
.badge.badge-fitcrew-initials {
|
||||
--bs-badge-bg: #783E88;
|
||||
--bs-badge-color: #E5E5E5;
|
||||
/* Also override theme variables used in `.badge, .o_filter_tag { background: var(--background-color) !important; color: var(--color) !important; }` */
|
||||
--background-color: #783E88 !important;
|
||||
--color: #E5E5E5 !important;
|
||||
/* Use background shorthand to beat theme's `.badge { background: ... !important; }` */
|
||||
background: var(--bs-badge-bg) !important;
|
||||
color: var(--bs-badge-color) !important;
|
||||
border: 0 !important;
|
||||
border-color: transparent !important;
|
||||
}
|
||||
|
||||
/* Event type badge */
|
||||
.badge-event-type,
|
||||
.badge.badge-event-type {
|
||||
--bs-badge-bg: #302E33;
|
||||
--bs-badge-color: #E5E5E5;
|
||||
--background-color: #302E33 !important;
|
||||
--color: #E5E5E5 !important;
|
||||
background: var(--bs-badge-bg) !important;
|
||||
color: var(--bs-badge-color) !important;
|
||||
border: 0 !important;
|
||||
border-color: transparent !important;
|
||||
}
|
||||
|
||||
/* Stronger specificity inside our event cards and against theme `.badge { ... !important }` */
|
||||
.portal-event-card .badge.badge-fitcrew-initials,
|
||||
.badge.badge.badge-fitcrew-initials {
|
||||
background-color: #783E88 !important;
|
||||
color: #E5E5E5 !important;
|
||||
background-image: none !important;
|
||||
}
|
||||
|
||||
.portal-event-card .badge.badge-event-type,
|
||||
.badge.badge.badge-event-type {
|
||||
background-color: #302E33 !important;
|
||||
color: #E5E5E5 !important;
|
||||
background-image: none !important;
|
||||
}
|
||||
|
|
@ -29,19 +29,19 @@
|
|||
<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 ''}#{no_default_dates and '&no_default_dates=1' or ''}">
|
||||
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 ''}#{no_default_dates and '&no_default_dates=1' or ''}">
|
||||
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 ''}#{no_default_dates and '&no_default_dates=1' or ''}">
|
||||
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>
|
||||
|
|
@ -156,7 +156,35 @@
|
|||
</t>
|
||||
</span>
|
||||
</div>
|
||||
<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>
|
||||
|
|
@ -181,121 +209,188 @@
|
|||
</div>
|
||||
</t>
|
||||
|
||||
<t t-if="events">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th>
|
||||
<a t-attf-href="/my/events?#{request.httprequest.query_string.decode()}&sortby=name">
|
||||
Event Name
|
||||
<i t-if="sortby == 'name'" class="fa fa-sort-alpha-asc"/>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a t-attf-href="/my/events?#{request.httprequest.query_string.decode()}&sortby=date">
|
||||
Date
|
||||
<i t-if="sortby == 'date'" class="fa fa-sort-numeric-asc"/>
|
||||
<i t-if="sortby == 'date_desc'" class="fa fa-sort-numeric-desc"/>
|
||||
</a>
|
||||
</th>
|
||||
<th>Type</th>
|
||||
<th>
|
||||
<a t-attf-href="/my/events?#{request.httprequest.query_string.decode()}&sortby=team">
|
||||
Team
|
||||
<i t-if="sortby == 'team'" class="fa fa-sort-alpha-asc"/>
|
||||
</a>
|
||||
</th>
|
||||
<th>Venue</th>
|
||||
<th>Assigned Staff</th>
|
||||
<th t-if="can_edit">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<t t-foreach="events" t-as="event">
|
||||
<tr class="clickable-row" style="cursor: pointer;"
|
||||
t-att-onclick="'window.location.href=\'%s\'' % ('/my/event/%s' % event.id)">
|
||||
<!-- Name -->
|
||||
<td>
|
||||
<strong t-esc="event.name"/>
|
||||
<t t-if="event.description">
|
||||
<br/>
|
||||
<div t-field="event.description" class="text-muted oe_no_empty"/>
|
||||
<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>
|
||||
</td>
|
||||
|
||||
<!-- Date -->
|
||||
<td>
|
||||
</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'}"/>
|
||||
<br/><span t-esc="event.therapist_start" t-options="{'widget': 'datetime', 'format': 'HH:mm'}"/>
|
||||
<span class="text-muted">•</span>
|
||||
<span t-esc="event.therapist_start" t-options="{'widget': 'datetime', 'format': 'HH:mm'}"/>
|
||||
<t t-if="event.date_start">
|
||||
<br/><small class="text-muted">(<span t-esc="event.date_start" t-options="{'widget': 'datetime', 'format': 'HH:mm'}"/>)</small>
|
||||
<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'}"/>
|
||||
<br/><span t-esc="event.date_start" t-options="{'widget': 'datetime', 'format': 'HH:mm'}"/>
|
||||
<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>
|
||||
</td>
|
||||
|
||||
<!-- Type -->
|
||||
<td>
|
||||
<span class="badge bg-info">
|
||||
<t t-esc="dict(event._fields['event_type'].selection).get(event.event_type, event.event_type)"/>
|
||||
</span>
|
||||
</td>
|
||||
|
||||
<!-- Team -->
|
||||
<td>
|
||||
<t t-if="event.team_id">
|
||||
<i class="fa fa-users"/> <span t-esc="event.team_id.name"/>
|
||||
</t>
|
||||
<t t-if="not event.team_id">
|
||||
<span class="text-muted">No Team</span>
|
||||
</t>
|
||||
</td>
|
||||
|
||||
<!-- Venue -->
|
||||
<td>
|
||||
<t t-if="event.venue_id">
|
||||
<i class="fa fa-map-marker"/> <span t-esc="event.venue_id.name"/>
|
||||
</t>
|
||||
<t t-if="not event.venue_id">
|
||||
<span class="text-muted">TBD</span>
|
||||
</t>
|
||||
</td>
|
||||
|
||||
<!-- Assigned Staff -->
|
||||
<td>
|
||||
<t t-if="event.assigned_staff_ids">
|
||||
<t t-foreach="event.assigned_staff_ids" t-as="staff">
|
||||
<span class="badge bg-primary me-1" t-esc="staff.name"/>
|
||||
</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 t-if="not event.assigned_staff_ids">
|
||||
<span class="text-muted">Unassigned</span>
|
||||
</t>
|
||||
</td>
|
||||
|
||||
<!-- Actions -->
|
||||
<td t-if="can_edit">
|
||||
<a t-attf-href="/my/event/#{event.id}" class="btn btn-sm btn-outline-primary me-1">
|
||||
<i class="fa fa-eye"/> View
|
||||
</a>
|
||||
<a t-attf-href="/my/event/#{event.id}/edit" class="btn btn-sm btn-outline-secondary">
|
||||
<i class="fa fa-edit"/> Edit
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</t>
|
||||
</tbody>
|
||||
</table>
|
||||
<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>
|
||||
|
|
|
|||
|
|
@ -123,43 +123,54 @@
|
|||
<template id="portal_my_teams">
|
||||
<t t-call="portal.portal_layout">
|
||||
<h1>Your Teams</h1>
|
||||
<t t-call="portal.portal_table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Team Name</th>
|
||||
<th>Parent Organization</th>
|
||||
<th>Total Players</th>
|
||||
<th>Injured</th>
|
||||
<th t-if="request.env.user.has_group('bemade_sports_clinic.group_portal_treatment_professional')">Activities</th>
|
||||
<th t-if="request.env.user.has_group('bemade_sports_clinic.group_portal_treatment_professional')">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<!-- Card Grid: responsive team cards replacing table rows -->
|
||||
<div class="container-fluid">
|
||||
<div class="row g-3">
|
||||
<t t-foreach="teams" t-as="team">
|
||||
<tr>
|
||||
<td>
|
||||
<strong>
|
||||
<a t-attf-href="/my/team?team_id={{ team.id }}"
|
||||
t-field="team.name"/>
|
||||
</strong>
|
||||
</td>
|
||||
<td><span t-field="team.parent_id"/></td>
|
||||
<td><span t-field="team.player_count"/></td>
|
||||
<td><span t-field="team.injured_count"/></td>
|
||||
<td t-if="request.env.user.has_group('bemade_sports_clinic.group_portal_treatment_professional')">
|
||||
<t t-set="team_activity_count" t-value="team.activity_count or 0"/>
|
||||
<span t-esc="team_activity_count"/>
|
||||
</td>
|
||||
<td t-if="request.env.user.has_group('bemade_sports_clinic.group_portal_treatment_professional')">
|
||||
<a t-attf-href="/my/activities?model=sports.team&res_id={{ team.id }}"
|
||||
class="btn bg-o-color-1 text-white btn-sm">
|
||||
<i class="fa fa-tasks"></i> Activities
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<div class="col-12 col-sm-6 col-lg-4">
|
||||
<div class="card h-100 shadow-sm">
|
||||
<div class="card-body d-flex flex-column">
|
||||
<h5 class="card-title mb-1">
|
||||
<a t-attf-href="/my/team?team_id={{ team.id }}" class="text-decoration-none">
|
||||
<span t-field="team.name"/>
|
||||
</a>
|
||||
</h5>
|
||||
<div class="text-muted small mb-2">
|
||||
<i class="fa fa-sitemap me-1"/>
|
||||
<span t-field="team.parent_id"/>
|
||||
</div>
|
||||
<div class="d-flex flex-wrap gap-2 mb-3">
|
||||
<span class="badge bg-light text-dark">
|
||||
<i class="fa fa-users me-1"/>
|
||||
<t t-esc="(team.player_count or 0)"/> players
|
||||
</span>
|
||||
<span class="badge bg-warning text-dark">
|
||||
<i class="fa fa-medkit me-1"/>
|
||||
<t t-esc="(team.injured_count or 0)"/> injured
|
||||
</span>
|
||||
<t t-if="request.env.user.has_group('bemade_sports_clinic.group_portal_treatment_professional')">
|
||||
<span class="badge bg-info text-dark">
|
||||
<i class="fa fa-tasks me-1"/>
|
||||
<t t-esc="(team.activity_count or 0)"/> activities
|
||||
</span>
|
||||
</t>
|
||||
</div>
|
||||
<div class="mt-auto">
|
||||
<a t-attf-href="/my/team?team_id={{ team.id }}" class="btn btn-outline-secondary btn-sm me-2">
|
||||
<i class="fa fa-eye"></i> View
|
||||
</a>
|
||||
<a t-if="request.env.user.has_group('bemade_sports_clinic.group_portal_treatment_professional')"
|
||||
t-attf-href="/my/activities?model=sports.team&res_id={{ team.id }}"
|
||||
class="btn bg-o-color-1 text-white btn-sm">
|
||||
<i class="fa fa-tasks"></i> Activities
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</tbody>
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Pagination -->
|
||||
<div class="mt-3">
|
||||
<t t-if="pager">
|
||||
|
|
|
|||
Loading…
Reference in a new issue