Fix portal activity modal dismiss buttons and XML syntax errors
- Replace unreliable data-dismiss='modal' with direct JavaScript closeModal() calls - Add robust closeModal() function with Bootstrap detection and DOM fallback - Fix Complete, Reschedule, and Cancel modal dismiss button functionality - Wrap JavaScript in CDATA section to resolve XML parsing errors with unescaped ampersands - Add copyFeedbackToHidden() and copyDateToHidden() functions for proper form data transfer - Ensure all modal action buttons work consistently across portal UI All portal activity modal interactions now functional with console logging for debugging.
This commit is contained in:
parent
fae0d98637
commit
f5911f0767
7 changed files with 296 additions and 119 deletions
|
|
@ -39,11 +39,20 @@ class TaskManagementPortal(CustomerPortal):
|
||||||
# User must be staff on at least one of the patient's teams
|
# User must be staff on at least one of the patient's teams
|
||||||
if not (user_teams & patient_teams):
|
if not (user_teams & patient_teams):
|
||||||
raise UserError(_('You do not have access to this injury.'))
|
raise UserError(_('You do not have access to this injury.'))
|
||||||
|
|
||||||
|
# For team records, check if user is staff on the team
|
||||||
|
elif model_name == 'sports.team':
|
||||||
|
# Check if user has access through team staff relationships
|
||||||
|
user_teams = user.partner_id.team_staff_rel_ids.mapped('team_id')
|
||||||
|
|
||||||
|
# User must be staff on this specific team
|
||||||
|
if record not in user_teams:
|
||||||
|
raise UserError(_('You do not have access to this team.'))
|
||||||
|
|
||||||
return record
|
return record
|
||||||
|
|
||||||
@http.route(['/my/activities'], type='http', auth='user', website=True)
|
@http.route(['/my/activities'], type='http', auth='user', website=True)
|
||||||
def view_activities(self, model=None, **kw):
|
def view_activities(self, model=None, res_id=None, **kw):
|
||||||
"""Display list of activities assigned to the current user"""
|
"""Display list of activities assigned to the current user"""
|
||||||
user = request.env.user
|
user = request.env.user
|
||||||
partner = user.partner_id
|
partner = user.partner_id
|
||||||
|
|
@ -54,8 +63,11 @@ class TaskManagementPortal(CustomerPortal):
|
||||||
# Apply model filtering if specified
|
# Apply model filtering if specified
|
||||||
if model:
|
if model:
|
||||||
domain.append(('res_model', '=', model))
|
domain.append(('res_model', '=', model))
|
||||||
|
# Apply res_id filtering if specified
|
||||||
|
if res_id:
|
||||||
|
domain.append(('res_id', '=', int(res_id)))
|
||||||
else:
|
else:
|
||||||
domain.append(('res_model', 'in', ['sports.patient', 'sports.patient.injury']))
|
domain.append(('res_model', 'in', ['sports.patient', 'sports.patient.injury', 'sports.team']))
|
||||||
|
|
||||||
# Get activities assigned to this user
|
# Get activities assigned to this user
|
||||||
activities = request.env['mail.activity'].search(domain, order='date_deadline asc')
|
activities = request.env['mail.activity'].search(domain, order='date_deadline asc')
|
||||||
|
|
@ -63,6 +75,7 @@ class TaskManagementPortal(CustomerPortal):
|
||||||
# Group activities by model
|
# Group activities by model
|
||||||
patient_activities = activities.filtered(lambda a: a.res_model == 'sports.patient')
|
patient_activities = activities.filtered(lambda a: a.res_model == 'sports.patient')
|
||||||
injury_activities = activities.filtered(lambda a: a.res_model == 'sports.patient.injury')
|
injury_activities = activities.filtered(lambda a: a.res_model == 'sports.patient.injury')
|
||||||
|
team_activities = activities.filtered(lambda a: a.res_model == 'sports.team')
|
||||||
|
|
||||||
# Get activity types for filtering
|
# Get activity types for filtering
|
||||||
activity_types = request.env['mail.activity.type'].search([])
|
activity_types = request.env['mail.activity.type'].search([])
|
||||||
|
|
@ -73,6 +86,7 @@ class TaskManagementPortal(CustomerPortal):
|
||||||
'activities': activities,
|
'activities': activities,
|
||||||
'patient_activities': patient_activities,
|
'patient_activities': patient_activities,
|
||||||
'injury_activities': injury_activities,
|
'injury_activities': injury_activities,
|
||||||
|
'team_activities': team_activities,
|
||||||
'activity_types': activity_types,
|
'activity_types': activity_types,
|
||||||
'page_name': 'activities',
|
'page_name': 'activities',
|
||||||
'today': date.today().strftime('%Y-%m-%d'),
|
'today': date.today().strftime('%Y-%m-%d'),
|
||||||
|
|
@ -84,7 +98,7 @@ class TaskManagementPortal(CustomerPortal):
|
||||||
def create_activity_form(self, model=None, res_id=None, **kw):
|
def create_activity_form(self, model=None, res_id=None, **kw):
|
||||||
"""Display form to create a new activity"""
|
"""Display form to create a new activity"""
|
||||||
# Validate model and res_id
|
# Validate model and res_id
|
||||||
valid_models = ['sports.patient', 'sports.patient.injury']
|
valid_models = ['sports.patient', 'sports.patient.injury', 'sports.team']
|
||||||
if model not in valid_models or not res_id:
|
if model not in valid_models or not res_id:
|
||||||
return request.redirect('/my/activities')
|
return request.redirect('/my/activities')
|
||||||
|
|
||||||
|
|
@ -118,6 +132,8 @@ class TaskManagementPortal(CustomerPortal):
|
||||||
# Default return URL
|
# Default return URL
|
||||||
if model == 'sports.patient':
|
if model == 'sports.patient':
|
||||||
return_url = f'/my/player?player_id={res_id}'
|
return_url = f'/my/player?player_id={res_id}'
|
||||||
|
elif model == 'sports.team':
|
||||||
|
return_url = f'/my/team?team_id={res_id}'
|
||||||
else: # sports.patient.injury
|
else: # sports.patient.injury
|
||||||
return_url = f'/my/player?player_id={record.patient_id.id}'
|
return_url = f'/my/player?player_id={record.patient_id.id}'
|
||||||
|
|
||||||
|
|
@ -145,7 +161,7 @@ class TaskManagementPortal(CustomerPortal):
|
||||||
res_id = post.get('res_id')
|
res_id = post.get('res_id')
|
||||||
|
|
||||||
# Validate model and res_id
|
# Validate model and res_id
|
||||||
valid_models = ['sports.patient', 'sports.patient.injury']
|
valid_models = ['sports.patient', 'sports.patient.injury', 'sports.team']
|
||||||
if model not in valid_models or not res_id:
|
if model not in valid_models or not res_id:
|
||||||
return request.redirect('/my/activities')
|
return request.redirect('/my/activities')
|
||||||
|
|
||||||
|
|
@ -258,7 +274,7 @@ class TaskManagementPortal(CustomerPortal):
|
||||||
separator = '&' if '?' in return_url else '?'
|
separator = '&' if '?' in return_url else '?'
|
||||||
return request.redirect(f'{return_url}{separator}success=activity_updated')
|
return request.redirect(f'{return_url}{separator}success=activity_updated')
|
||||||
|
|
||||||
@http.route(['/my/activity/complete'], type='http', auth='user', website=True, methods=['POST'], csrf=False)
|
@http.route(['/my/activity/complete'], type='http', auth='user', website=True, methods=['POST'])
|
||||||
def complete_activity(self, **post):
|
def complete_activity(self, **post):
|
||||||
"""Mark an activity as done"""
|
"""Mark an activity as done"""
|
||||||
activity_id = post.get('activity_id')
|
activity_id = post.get('activity_id')
|
||||||
|
|
@ -280,7 +296,7 @@ class TaskManagementPortal(CustomerPortal):
|
||||||
# Redirect to activities page
|
# Redirect to activities page
|
||||||
return request.redirect('/my/activities')
|
return request.redirect('/my/activities')
|
||||||
|
|
||||||
@http.route(['/my/activity/cancel'], type='http', auth='user', website=True, methods=['POST'], csrf=False)
|
@http.route(['/my/activity/cancel'], type='http', auth='user', website=True, methods=['POST'])
|
||||||
def cancel_activity(self, **post):
|
def cancel_activity(self, **post):
|
||||||
"""Cancel an activity"""
|
"""Cancel an activity"""
|
||||||
activity_id = post.get('activity_id')
|
activity_id = post.get('activity_id')
|
||||||
|
|
@ -299,7 +315,7 @@ class TaskManagementPortal(CustomerPortal):
|
||||||
# Redirect to activities page
|
# Redirect to activities page
|
||||||
return request.redirect('/my/activities')
|
return request.redirect('/my/activities')
|
||||||
|
|
||||||
@http.route(['/my/activity/reschedule'], type='http', auth='user', website=True, methods=['POST'], csrf=False)
|
@http.route(['/my/activity/reschedule'], type='http', auth='user', website=True, methods=['POST'])
|
||||||
def reschedule_activity(self, **post):
|
def reschedule_activity(self, **post):
|
||||||
"""Reschedule an activity to a new date"""
|
"""Reschedule an activity to a new date"""
|
||||||
activity_id = post.get('activity_id')
|
activity_id = post.get('activity_id')
|
||||||
|
|
|
||||||
|
|
@ -8,9 +8,12 @@ class TeamStaffPortal(CustomerPortal):
|
||||||
rtn = super()._prepare_home_portal_values(counters)
|
rtn = super()._prepare_home_portal_values(counters)
|
||||||
teams_domain = self._prepare_teams_domain()
|
teams_domain = self._prepare_teams_domain()
|
||||||
players_domain = self._prepare_players_domain(teams_domain)
|
players_domain = self._prepare_players_domain(teams_domain)
|
||||||
|
activities_domain = self._prepare_activities_domain()
|
||||||
rtn['teams_count'] = http.request.env['sports.team'].search_count(teams_domain)
|
rtn['teams_count'] = http.request.env['sports.team'].search_count(teams_domain)
|
||||||
rtn['players_count'] = http.request.env['sports.patient'].search_count(
|
rtn['players_count'] = http.request.env['sports.patient'].search_count(
|
||||||
players_domain)
|
players_domain)
|
||||||
|
rtn['activities_count'] = http.request.env['mail.activity'].search_count(
|
||||||
|
activities_domain)
|
||||||
return rtn
|
return rtn
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|
@ -27,6 +30,13 @@ class TeamStaffPortal(CustomerPortal):
|
||||||
('team_ids', 'in', team_ids),
|
('team_ids', 'in', team_ids),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _prepare_activities_domain(cls):
|
||||||
|
user = http.request.env.user
|
||||||
|
return [
|
||||||
|
('user_id', '=', user.id),
|
||||||
|
]
|
||||||
|
|
||||||
@http.route(route=['/my/teams', '/my/teams/page/<int:page>'], type='http', auth='user', website=True)
|
@http.route(route=['/my/teams', '/my/teams/page/<int:page>'], type='http', auth='user', website=True)
|
||||||
def view_teams(self, page=0, **kw):
|
def view_teams(self, page=0, **kw):
|
||||||
""" Display the list of teams that a portal user has access to """
|
""" Display the list of teams that a portal user has access to """
|
||||||
|
|
|
||||||
|
|
@ -130,6 +130,7 @@ class Patient(models.Model):
|
||||||
)
|
)
|
||||||
last_consultation_date = fields.Date(tracking=True)
|
last_consultation_date = fields.Date(tracking=True)
|
||||||
active_injury_count = fields.Integer(compute="_compute_active_injury_count")
|
active_injury_count = fields.Integer(compute="_compute_active_injury_count")
|
||||||
|
activity_count = fields.Integer(compute="_compute_activity_count")
|
||||||
allergies = fields.Text(
|
allergies = fields.Text(
|
||||||
groups="bemade_sports_clinic.group_sports_clinic_treatment_professional,bemade_sports_clinic.group_portal_treatment_professional",
|
groups="bemade_sports_clinic.group_sports_clinic_treatment_professional,bemade_sports_clinic.group_portal_treatment_professional",
|
||||||
)
|
)
|
||||||
|
|
@ -265,6 +266,13 @@ class Patient(models.Model):
|
||||||
[('patient_id', '=', patient.id)]
|
[('patient_id', '=', patient.id)]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def _compute_activity_count(self):
|
||||||
|
for rec in self:
|
||||||
|
rec.activity_count = self.env['mail.activity'].search_count([
|
||||||
|
('res_model', '=', 'sports.patient'),
|
||||||
|
('res_id', '=', rec.id)
|
||||||
|
])
|
||||||
|
|
||||||
def action_view_patient_form(self):
|
def action_view_patient_form(self):
|
||||||
self.ensure_one()
|
self.ensure_one()
|
||||||
return {
|
return {
|
||||||
|
|
|
||||||
|
|
@ -141,6 +141,10 @@ class PatientInjury(models.Model):
|
||||||
string='Document Count',
|
string='Document Count',
|
||||||
compute='_compute_document_count'
|
compute='_compute_document_count'
|
||||||
)
|
)
|
||||||
|
activity_count = fields.Integer(
|
||||||
|
string='Activity Count',
|
||||||
|
compute='_compute_activity_count'
|
||||||
|
)
|
||||||
|
|
||||||
@api.depends('treatment_note_ids')
|
@api.depends('treatment_note_ids')
|
||||||
def _compute_treatment_note_count(self):
|
def _compute_treatment_note_count(self):
|
||||||
|
|
@ -152,6 +156,13 @@ class PatientInjury(models.Model):
|
||||||
for record in self:
|
for record in self:
|
||||||
record.document_count = len(record.document_ids)
|
record.document_count = len(record.document_ids)
|
||||||
|
|
||||||
|
def _compute_activity_count(self):
|
||||||
|
for rec in self:
|
||||||
|
rec.activity_count = self.env['mail.activity'].search_count([
|
||||||
|
('res_model', '=', 'sports.patient.injury'),
|
||||||
|
('res_id', '=', rec.id)
|
||||||
|
])
|
||||||
|
|
||||||
@api.constrains("injury_date_na", "injury_date")
|
@api.constrains("injury_date_na", "injury_date")
|
||||||
def constrain_date_blank_only_if_na(self):
|
def constrain_date_blank_only_if_na(self):
|
||||||
for rec in self:
|
for rec in self:
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ class SportsTeam(models.Model):
|
||||||
player_count = fields.Integer(compute="_compute_player_counts")
|
player_count = fields.Integer(compute="_compute_player_counts")
|
||||||
injured_count = fields.Integer(compute="_compute_player_counts")
|
injured_count = fields.Integer(compute="_compute_player_counts")
|
||||||
healthy_count = fields.Integer(compute="_compute_player_counts")
|
healthy_count = fields.Integer(compute="_compute_player_counts")
|
||||||
|
activity_count = fields.Integer(compute="_compute_activity_count")
|
||||||
parent_id = fields.Many2one(
|
parent_id = fields.Many2one(
|
||||||
comodel_name="res.partner",
|
comodel_name="res.partner",
|
||||||
string="Parent Organization",
|
string="Parent Organization",
|
||||||
|
|
@ -96,6 +97,13 @@ class SportsTeam(models.Model):
|
||||||
staff = rec.staff_ids.filtered(lambda r: r.role == "head_therapist")
|
staff = rec.staff_ids.filtered(lambda r: r.role == "head_therapist")
|
||||||
rec.head_therapist_id = staff.partner_id if staff else False
|
rec.head_therapist_id = staff.partner_id if staff else False
|
||||||
|
|
||||||
|
def _compute_activity_count(self):
|
||||||
|
for rec in self:
|
||||||
|
rec.activity_count = self.env['mail.activity'].search_count([
|
||||||
|
('res_model', '=', 'sports.team'),
|
||||||
|
('res_id', '=', rec.id)
|
||||||
|
])
|
||||||
|
|
||||||
def _compute_allowed_user_ids(self):
|
def _compute_allowed_user_ids(self):
|
||||||
for rec in self:
|
for rec in self:
|
||||||
rec.allowed_user_ids = rec.staff_ids.user_ids
|
rec.allowed_user_ids = rec.staff_ids.user_ids
|
||||||
|
|
@ -214,16 +222,13 @@ class TeamStaff(models.Model):
|
||||||
|
|
||||||
def _action_revoke_portal_access(self):
|
def _action_revoke_portal_access(self):
|
||||||
"""Private method containing the actual sudo operations for revoking portal access."""
|
"""Private method containing the actual sudo operations for revoking portal access."""
|
||||||
group_portal = self.env.ref("base.group_portal")
|
|
||||||
group_public = self.env.ref("base.group_public")
|
group_public = self.env.ref("base.group_public")
|
||||||
# Deactivate the user and remove from portal group
|
|
||||||
|
# Deactivate the user and set to public user type (standard Odoo approach)
|
||||||
if self.user_ids:
|
if self.user_ids:
|
||||||
self.user_ids.write(
|
self.user_ids.sudo().write(
|
||||||
{
|
{
|
||||||
"groups_id": [
|
"groups_id": [(6, 0, [group_public.id])], # Set to public user only
|
||||||
Command.unlink(group_portal.id),
|
|
||||||
Command.link(group_public.id),
|
|
||||||
],
|
|
||||||
"active": False,
|
"active": False,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,11 @@
|
||||||
<t t-set="url">/my/players</t>
|
<t t-set="url">/my/players</t>
|
||||||
<t t-set="placeholder_count">players_count</t>
|
<t t-set="placeholder_count">players_count</t>
|
||||||
</t>
|
</t>
|
||||||
|
<t t-call="portal.portal_docs_entry">
|
||||||
|
<t t-set="title">Activities</t>
|
||||||
|
<t t-set="url">/my/activities</t>
|
||||||
|
<t t-set="placeholder_count">activities_count</t>
|
||||||
|
</t>
|
||||||
</xpath>
|
</xpath>
|
||||||
</template>
|
</template>
|
||||||
<template id="portal_my_teams">
|
<template id="portal_my_teams">
|
||||||
|
|
@ -25,6 +30,8 @@
|
||||||
<th>Parent Organization</th>
|
<th>Parent Organization</th>
|
||||||
<th>Total Players</th>
|
<th>Total Players</th>
|
||||||
<th>Injured</th>
|
<th>Injured</th>
|
||||||
|
<th>Activities</th>
|
||||||
|
<th>Actions</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
|
|
@ -39,6 +46,16 @@
|
||||||
<td><span t-field="team.parent_id"/></td>
|
<td><span t-field="team.parent_id"/></td>
|
||||||
<td><span t-field="team.player_count"/></td>
|
<td><span t-field="team.player_count"/></td>
|
||||||
<td><span t-field="team.injured_count"/></td>
|
<td><span t-field="team.injured_count"/></td>
|
||||||
|
<td>
|
||||||
|
<t t-set="team_activity_count" t-value="team.activity_count or 0"/>
|
||||||
|
<span t-esc="team_activity_count"/>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<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>
|
</tr>
|
||||||
</t>
|
</t>
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|
@ -91,9 +108,17 @@
|
||||||
</span>
|
</span>
|
||||||
</t>
|
</t>
|
||||||
</h1>
|
</h1>
|
||||||
<a t-attf-href="/my/team/{{ team.id }}/add_player" class="btn bg-o-color-1 text-white">
|
<div>
|
||||||
<i class="fa fa-plus me-1"/> Add Player
|
<a t-attf-href="/my/activities?model=sports.team&res_id={{ team.id }}" class="btn bg-o-color-2 text-white me-2">
|
||||||
</a>
|
<i class="fa fa-tasks"></i> Activities
|
||||||
|
</a>
|
||||||
|
<a t-if="is_treatment_prof" t-attf-href="/my/activity/create?model=sports.team&res_id={{ team.id }}" class="btn bg-o-color-2 text-white me-2">
|
||||||
|
<i class="fa fa-tasks"></i> Add Activity
|
||||||
|
</a>
|
||||||
|
<a t-attf-href="/my/team/{{ team.id }}/add_player" class="btn bg-o-color-1 text-white">
|
||||||
|
<i class="fa fa-plus me-1"/> Add Player
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Pending Removals Alert -->
|
<!-- Pending Removals Alert -->
|
||||||
|
|
@ -243,6 +268,8 @@
|
||||||
<th>Practice Status</th>
|
<th>Practice Status</th>
|
||||||
<th>Estimated Return Date</th>
|
<th>Estimated Return Date</th>
|
||||||
<th>Last Consultation Date</th>
|
<th>Last Consultation Date</th>
|
||||||
|
<th>Activities</th>
|
||||||
|
<th>Actions</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
|
|
@ -282,6 +309,16 @@
|
||||||
<span t-field="player.last_consultation_date"
|
<span t-field="player.last_consultation_date"
|
||||||
t-options="{'widget': 'date'}"/>
|
t-options="{'widget': 'date'}"/>
|
||||||
</td>
|
</td>
|
||||||
|
<td>
|
||||||
|
<t t-set="player_activity_count" t-value="player.activity_count or 0"/>
|
||||||
|
<span t-esc="player_activity_count"/>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a t-attf-href="/my/activities?model=sports.patient&res_id={{ player.id }}"
|
||||||
|
class="btn bg-o-color-1 text-white btn-sm">
|
||||||
|
<i class="fa fa-tasks"></i> Activities
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</t>
|
</t>
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|
@ -299,6 +336,9 @@
|
||||||
<a t-att-href="'/my/patient/injury/new?patient_id=%s' % player.id" class="btn bg-o-color-1 text-white me-2">
|
<a t-att-href="'/my/patient/injury/new?patient_id=%s' % player.id" class="btn bg-o-color-1 text-white me-2">
|
||||||
<i class="fa fa-plus"></i> Report Injury
|
<i class="fa fa-plus"></i> Report Injury
|
||||||
</a>
|
</a>
|
||||||
|
<a t-attf-href="/my/activities?model=sports.patient&res_id={{ player.id }}" class="btn bg-o-color-2 text-white me-2">
|
||||||
|
<i class="fa fa-tasks"></i> Activities
|
||||||
|
</a>
|
||||||
<a t-if="is_treatment_prof" t-att-href="'/my/activity/create?model=sports.patient&res_id=%s' % player.id" class="btn bg-o-color-2 text-white me-2">
|
<a t-if="is_treatment_prof" t-att-href="'/my/activity/create?model=sports.patient&res_id=%s' % player.id" class="btn bg-o-color-2 text-white me-2">
|
||||||
<i class="fa fa-tasks"></i> Add Activity
|
<i class="fa fa-tasks"></i> Add Activity
|
||||||
</a>
|
</a>
|
||||||
|
|
@ -416,6 +456,7 @@
|
||||||
<t t-if="not is_treatment_prof">
|
<t t-if="not is_treatment_prof">
|
||||||
<th>External Notes</th>
|
<th>External Notes</th>
|
||||||
</t>
|
</t>
|
||||||
|
<th>Activities</th>
|
||||||
<t t-if="is_treatment_prof">
|
<t t-if="is_treatment_prof">
|
||||||
<th class="text-end">Actions</th>
|
<th class="text-end">Actions</th>
|
||||||
</t>
|
</t>
|
||||||
|
|
@ -452,6 +493,16 @@
|
||||||
<div t-else="" class="text-muted">No external notes</div>
|
<div t-else="" class="text-muted">No external notes</div>
|
||||||
</td>
|
</td>
|
||||||
</t>
|
</t>
|
||||||
|
<td>
|
||||||
|
<div class="d-flex align-items-center">
|
||||||
|
<t t-set="injury_activity_count" t-value="injury.activity_count or 0"/>
|
||||||
|
<span t-esc="injury_activity_count" class="me-2"/>
|
||||||
|
<a t-attf-href="/my/activities?model=sports.patient.injury&res_id={{ injury.id }}"
|
||||||
|
class="btn bg-o-color-1 text-white btn-sm">
|
||||||
|
<i class="fa fa-tasks"></i>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
<t t-if="is_treatment_prof">
|
<t t-if="is_treatment_prof">
|
||||||
<td class="text-end">
|
<td class="text-end">
|
||||||
<div class="btn-group">
|
<div class="btn-group">
|
||||||
|
|
@ -464,6 +515,9 @@
|
||||||
<a t-att-href="'/my/injury/documents?injury_id=%s' % injury.id" class="btn btn-sm bg-o-color-2 text-white">
|
<a t-att-href="'/my/injury/documents?injury_id=%s' % injury.id" class="btn btn-sm bg-o-color-2 text-white">
|
||||||
<i class="fa fa-file-medical"></i> Docs
|
<i class="fa fa-file-medical"></i> Docs
|
||||||
</a>
|
</a>
|
||||||
|
<a t-attf-href="/my/activity/create?model=sports.patient.injury&res_id={{ injury.id }}" class="btn btn-sm bg-o-color-2 text-white">
|
||||||
|
<i class="fa fa-tasks"></i> Add Activity
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</t>
|
</t>
|
||||||
|
|
|
||||||
|
|
@ -120,106 +120,22 @@
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<!-- Mark as done button -->
|
<!-- Mark as done button -->
|
||||||
<button class="btn btn-sm bg-o-color-1 text-white mb-1" data-toggle="modal" t-attf-data-target="#completeActivityModal_#{activity.id}">
|
<button type="button" class="btn btn-sm bg-o-color-1 text-white mb-1"
|
||||||
|
t-attf-onclick="openActivityModal('completeModal', #{activity.id})">
|
||||||
<i class="fa fa-check"></i> Done
|
<i class="fa fa-check"></i> Done
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<!-- Reschedule button -->
|
<!-- Reschedule button -->
|
||||||
<button class="btn btn-sm bg-o-color-2 text-white mb-1" data-toggle="modal" t-attf-data-target="#rescheduleActivityModal_#{activity.id}">
|
<button type="button" class="btn btn-sm bg-o-color-2 text-white mb-1"
|
||||||
|
t-attf-onclick="openActivityModal('rescheduleModal', #{activity.id})">
|
||||||
<i class="fa fa-calendar"></i> Reschedule
|
<i class="fa fa-calendar"></i> Reschedule
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<!-- Cancel button -->
|
<!-- Cancel button -->
|
||||||
<button class="btn btn-sm btn-danger mb-1" data-toggle="modal" t-attf-data-target="#cancelActivityModal_#{activity.id}">
|
<button type="button" class="btn btn-sm btn-danger mb-1"
|
||||||
|
t-attf-onclick="openActivityModal('cancelModal', #{activity.id})">
|
||||||
<i class="fa fa-times"></i> Cancel
|
<i class="fa fa-times"></i> Cancel
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<!-- Complete Activity Modal -->
|
|
||||||
<div class="modal fade" t-attf-id="completeActivityModal_#{activity.id}" tabindex="-1" role="dialog">
|
|
||||||
<div class="modal-dialog" role="document">
|
|
||||||
<div class="modal-content">
|
|
||||||
<form t-att-action="'/my/activity/complete'" method="post">
|
|
||||||
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
|
|
||||||
<input type="hidden" name="activity_id" t-att-value="activity.id"/>
|
|
||||||
|
|
||||||
<div class="modal-header">
|
|
||||||
<h5 class="modal-title">Complete Activity</h5>
|
|
||||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
|
||||||
<span aria-hidden="true">&times;</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body">
|
|
||||||
<p>Are you sure you want to mark this activity as completed?</p>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="feedback">Feedback (optional)</label>
|
|
||||||
<textarea name="feedback" class="form-control" rows="3"></textarea>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="modal-footer">
|
|
||||||
<button type="button" class="btn bg-o-color-2 text-white" data-dismiss="modal">Cancel</button>
|
|
||||||
<button type="submit" class="btn bg-o-color-1 text-white">Complete Activity</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Reschedule Activity Modal -->
|
|
||||||
<div class="modal fade" t-attf-id="rescheduleActivityModal_#{activity.id}" tabindex="-1" role="dialog">
|
|
||||||
<div class="modal-dialog" role="document">
|
|
||||||
<div class="modal-content">
|
|
||||||
<form t-att-action="'/my/activity/reschedule'" method="post">
|
|
||||||
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
|
|
||||||
<input type="hidden" name="activity_id" t-att-value="activity.id"/>
|
|
||||||
|
|
||||||
<div class="modal-header">
|
|
||||||
<h5 class="modal-title">Reschedule Activity</h5>
|
|
||||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
|
||||||
<span aria-hidden="true">&times;</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body">
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="new_deadline">New Due Date</label>
|
|
||||||
<input type="date" name="new_deadline" class="form-control" required="required"
|
|
||||||
t-att-min="today"
|
|
||||||
t-att-value="today"/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="modal-footer">
|
|
||||||
<button type="button" class="btn bg-o-color-2 text-white" data-dismiss="modal">Cancel</button>
|
|
||||||
<button type="submit" class="btn bg-o-color-1 text-white">Reschedule</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Cancel Activity Modal -->
|
|
||||||
<div class="modal fade" t-attf-id="cancelActivityModal_#{activity.id}" tabindex="-1" role="dialog">
|
|
||||||
<div class="modal-dialog" role="document">
|
|
||||||
<div class="modal-content">
|
|
||||||
<form t-att-action="'/my/activity/cancel'" method="post">
|
|
||||||
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
|
|
||||||
<input type="hidden" name="activity_id" t-att-value="activity.id"/>
|
|
||||||
|
|
||||||
<div class="modal-header">
|
|
||||||
<h5 class="modal-title">Cancel Activity</h5>
|
|
||||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
|
||||||
<span aria-hidden="true">&times;</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body">
|
|
||||||
<p>Are you sure you want to cancel this activity? This action cannot be undone.</p>
|
|
||||||
</div>
|
|
||||||
<div class="modal-footer">
|
|
||||||
<button type="button" class="btn bg-o-color-2 text-white" data-dismiss="modal">No</button>
|
|
||||||
<button type="submit" class="btn btn-danger">Yes, Cancel Activity</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</t>
|
</t>
|
||||||
|
|
@ -229,6 +145,174 @@
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Shared Activity Action Modals -->
|
||||||
|
|
||||||
|
<!-- Complete Activity Modal -->
|
||||||
|
<div class="modal fade" id="completeModal" tabindex="-1" role="dialog">
|
||||||
|
<div class="modal-dialog" role="document">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title">Complete Activity</h5>
|
||||||
|
<button type="button" class="close" onclick="closeModal('completeModal')">
|
||||||
|
<span>×</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<p>Are you sure you want to mark this activity as completed?</p>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="feedback">Feedback (optional)</label>
|
||||||
|
<textarea id="complete_feedback" class="form-control" rows="3" placeholder="Add any feedback or notes about completing this activity..."></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn bg-o-color-2 text-white" onclick="closeModal('completeModal')">Cancel</button>
|
||||||
|
<form method="post" action="/my/activity/complete" style="display: inline;">
|
||||||
|
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
|
||||||
|
<input type="hidden" name="activity_id" id="completeActivityId"/>
|
||||||
|
<input type="hidden" name="feedback" id="complete_feedback_hidden"/>
|
||||||
|
<button type="submit" class="btn bg-o-color-1 text-white" onclick="copyFeedbackToHidden()">Complete Activity</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Reschedule Activity Modal -->
|
||||||
|
<div class="modal fade" id="rescheduleModal" tabindex="-1" role="dialog">
|
||||||
|
<div class="modal-dialog" role="document">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title">Reschedule Activity</h5>
|
||||||
|
<button type="button" class="close" onclick="closeModal('rescheduleModal')">
|
||||||
|
<span>×</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="new_deadline">New Due Date</label>
|
||||||
|
<input type="date" id="reschedule_new_deadline" class="form-control" required="required"
|
||||||
|
t-att-min="datetime.date.today().strftime('%Y-%m-%d')"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn bg-o-color-2 text-white" onclick="closeModal('rescheduleModal')">Cancel</button>
|
||||||
|
<form method="post" action="/my/activity/reschedule" style="display: inline;">
|
||||||
|
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
|
||||||
|
<input type="hidden" name="activity_id" id="rescheduleActivityId"/>
|
||||||
|
<input type="hidden" name="new_deadline" id="reschedule_deadline_hidden"/>
|
||||||
|
<button type="submit" class="btn bg-o-color-1 text-white" onclick="copyDateToHidden()">Reschedule</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Cancel Activity Modal -->
|
||||||
|
<div class="modal fade" id="cancelModal" tabindex="-1" role="dialog">
|
||||||
|
<div class="modal-dialog" role="document">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title">Cancel Activity</h5>
|
||||||
|
<button type="button" class="close" onclick="closeModal('cancelModal')">
|
||||||
|
<span>×</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<p>Are you sure you want to cancel this activity? This action cannot be undone.</p>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn bg-o-color-2 text-white" onclick="closeModal('cancelModal')">No</button>
|
||||||
|
<form method="post" action="/my/activity/cancel" style="display: inline;">
|
||||||
|
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
|
||||||
|
<input type="hidden" name="activity_id" id="cancelActivityId"/>
|
||||||
|
<button type="submit" class="btn btn-danger">Yes, Cancel Activity</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- JavaScript for modal functionality -->
|
||||||
|
<script>
|
||||||
|
<![CDATA[
|
||||||
|
function openActivityModal(modalType, activityId) {
|
||||||
|
console.log('openActivityModal called:', modalType, activityId);
|
||||||
|
|
||||||
|
// Check if Bootstrap is loaded
|
||||||
|
if (typeof $ === 'undefined') {
|
||||||
|
console.error('jQuery not loaded');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof $.fn.modal === 'undefined') {
|
||||||
|
console.error('Bootstrap modal not loaded');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the activity ID in the appropriate modal form
|
||||||
|
if (modalType === 'completeModal') {
|
||||||
|
document.getElementById('completeActivityId').value = activityId;
|
||||||
|
} else if (modalType === 'rescheduleModal') {
|
||||||
|
document.getElementById('rescheduleActivityId').value = activityId;
|
||||||
|
} else if (modalType === 'cancelModal') {
|
||||||
|
document.getElementById('cancelActivityId').value = activityId;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Activity ID set for', modalType, ':', activityId);
|
||||||
|
|
||||||
|
// Open the modal using jQuery
|
||||||
|
$('#' + modalType).modal('show');
|
||||||
|
console.log('Modal opened:', modalType);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test function to manually open modal
|
||||||
|
function testModal() {
|
||||||
|
console.log('Testing modal...');
|
||||||
|
$('#completeModal').modal('show');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to copy date from visible input to hidden input
|
||||||
|
function copyDateToHidden() {
|
||||||
|
var visibleDate = document.getElementById('reschedule_new_deadline').value;
|
||||||
|
document.getElementById('reschedule_deadline_hidden').value = visibleDate;
|
||||||
|
console.log('Date copied to hidden field:', visibleDate);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to copy feedback from visible textarea to hidden input
|
||||||
|
function copyFeedbackToHidden() {
|
||||||
|
var visibleFeedback = document.getElementById('complete_feedback').value;
|
||||||
|
document.getElementById('complete_feedback_hidden').value = visibleFeedback;
|
||||||
|
console.log('Feedback copied to hidden field:', visibleFeedback);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to close modal
|
||||||
|
function closeModal(modalId) {
|
||||||
|
console.log('closeModal called for:', modalId);
|
||||||
|
|
||||||
|
// Check if Bootstrap is loaded
|
||||||
|
if (typeof $ !== 'undefined' && typeof $.fn.modal !== 'undefined') {
|
||||||
|
$('#' + modalId).modal('hide');
|
||||||
|
console.log('Modal closed via Bootstrap:', modalId);
|
||||||
|
} else {
|
||||||
|
// Fallback: manually hide modal
|
||||||
|
var modal = document.getElementById(modalId);
|
||||||
|
if (modal) {
|
||||||
|
modal.style.display = 'none';
|
||||||
|
modal.classList.remove('show');
|
||||||
|
// Remove backdrop
|
||||||
|
var backdrop = document.querySelector('.modal-backdrop');
|
||||||
|
if (backdrop) {
|
||||||
|
backdrop.remove();
|
||||||
|
}
|
||||||
|
// Remove modal-open class from body
|
||||||
|
document.body.classList.remove('modal-open');
|
||||||
|
console.log('Modal closed via fallback method:', modalId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]]>
|
||||||
|
</script>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<!-- Template for creating a new activity -->
|
<!-- Template for creating a new activity -->
|
||||||
|
|
@ -432,18 +516,7 @@
|
||||||
<!-- Activity buttons now integrated directly into the portal_my_player_injuries template -->
|
<!-- Activity buttons now integrated directly into the portal_my_player_injuries template -->
|
||||||
|
|
||||||
<!-- Add activities section to the portal home page -->
|
<!-- Add activities section to the portal home page -->
|
||||||
<template id="portal_my_home_activities" name="Portal My Home : Activities" inherit_id="portal.portal_my_home">
|
<!-- Activities entry removed - now handled in main portal template for consistency -->
|
||||||
<xpath expr="//div[hasclass('o_portal_docs')]" position="inside">
|
|
||||||
<div t-if="activities_count" class="o_portal_docs list-group-item list-group-item-action">
|
|
||||||
<a href="/my/activities">
|
|
||||||
<span class="d-flex justify-content-between align-items-center">
|
|
||||||
<span>Activities</span>
|
|
||||||
<span class="badge badge-pill badge-primary" t-esc="activities_count"/>
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</xpath>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<!-- Add link to activities in the portal navigation -->
|
<!-- Add link to activities in the portal navigation -->
|
||||||
<template id="portal_breadcrumbs_activities" name="Portal Breadcrumbs : Activities" inherit_id="portal.portal_breadcrumbs">
|
<template id="portal_breadcrumbs_activities" name="Portal Breadcrumbs : Activities" inherit_id="portal.portal_breadcrumbs">
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue