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
|
||||
if not (user_teams & patient_teams):
|
||||
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
|
||||
|
||||
@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"""
|
||||
user = request.env.user
|
||||
partner = user.partner_id
|
||||
|
|
@ -54,8 +63,11 @@ class TaskManagementPortal(CustomerPortal):
|
|||
# Apply model filtering if specified
|
||||
if model:
|
||||
domain.append(('res_model', '=', model))
|
||||
# Apply res_id filtering if specified
|
||||
if res_id:
|
||||
domain.append(('res_id', '=', int(res_id)))
|
||||
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
|
||||
activities = request.env['mail.activity'].search(domain, order='date_deadline asc')
|
||||
|
|
@ -63,6 +75,7 @@ class TaskManagementPortal(CustomerPortal):
|
|||
# Group activities by model
|
||||
patient_activities = activities.filtered(lambda a: a.res_model == 'sports.patient')
|
||||
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
|
||||
activity_types = request.env['mail.activity.type'].search([])
|
||||
|
|
@ -73,6 +86,7 @@ class TaskManagementPortal(CustomerPortal):
|
|||
'activities': activities,
|
||||
'patient_activities': patient_activities,
|
||||
'injury_activities': injury_activities,
|
||||
'team_activities': team_activities,
|
||||
'activity_types': activity_types,
|
||||
'page_name': 'activities',
|
||||
'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):
|
||||
"""Display form to create a new activity"""
|
||||
# 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:
|
||||
return request.redirect('/my/activities')
|
||||
|
||||
|
|
@ -118,6 +132,8 @@ class TaskManagementPortal(CustomerPortal):
|
|||
# Default return URL
|
||||
if model == 'sports.patient':
|
||||
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
|
||||
return_url = f'/my/player?player_id={record.patient_id.id}'
|
||||
|
||||
|
|
@ -145,7 +161,7 @@ class TaskManagementPortal(CustomerPortal):
|
|||
res_id = post.get('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:
|
||||
return request.redirect('/my/activities')
|
||||
|
||||
|
|
@ -258,7 +274,7 @@ class TaskManagementPortal(CustomerPortal):
|
|||
separator = '&' if '?' in return_url else '?'
|
||||
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):
|
||||
"""Mark an activity as done"""
|
||||
activity_id = post.get('activity_id')
|
||||
|
|
@ -280,7 +296,7 @@ class TaskManagementPortal(CustomerPortal):
|
|||
# Redirect to activities page
|
||||
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):
|
||||
"""Cancel an activity"""
|
||||
activity_id = post.get('activity_id')
|
||||
|
|
@ -299,7 +315,7 @@ class TaskManagementPortal(CustomerPortal):
|
|||
# Redirect to activities page
|
||||
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):
|
||||
"""Reschedule an activity to a new date"""
|
||||
activity_id = post.get('activity_id')
|
||||
|
|
|
|||
|
|
@ -8,9 +8,12 @@ class TeamStaffPortal(CustomerPortal):
|
|||
rtn = super()._prepare_home_portal_values(counters)
|
||||
teams_domain = self._prepare_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['players_count'] = http.request.env['sports.patient'].search_count(
|
||||
players_domain)
|
||||
rtn['activities_count'] = http.request.env['mail.activity'].search_count(
|
||||
activities_domain)
|
||||
return rtn
|
||||
|
||||
@classmethod
|
||||
|
|
@ -27,6 +30,13 @@ class TeamStaffPortal(CustomerPortal):
|
|||
('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)
|
||||
def view_teams(self, page=0, **kw):
|
||||
""" 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)
|
||||
active_injury_count = fields.Integer(compute="_compute_active_injury_count")
|
||||
activity_count = fields.Integer(compute="_compute_activity_count")
|
||||
allergies = fields.Text(
|
||||
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)]
|
||||
)
|
||||
|
||||
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):
|
||||
self.ensure_one()
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -141,6 +141,10 @@ class PatientInjury(models.Model):
|
|||
string='Document Count',
|
||||
compute='_compute_document_count'
|
||||
)
|
||||
activity_count = fields.Integer(
|
||||
string='Activity Count',
|
||||
compute='_compute_activity_count'
|
||||
)
|
||||
|
||||
@api.depends('treatment_note_ids')
|
||||
def _compute_treatment_note_count(self):
|
||||
|
|
@ -152,6 +156,13 @@ class PatientInjury(models.Model):
|
|||
for record in self:
|
||||
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")
|
||||
def constrain_date_blank_only_if_na(self):
|
||||
for rec in self:
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ class SportsTeam(models.Model):
|
|||
player_count = fields.Integer(compute="_compute_player_counts")
|
||||
injured_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(
|
||||
comodel_name="res.partner",
|
||||
string="Parent Organization",
|
||||
|
|
@ -96,6 +97,13 @@ class SportsTeam(models.Model):
|
|||
staff = rec.staff_ids.filtered(lambda r: r.role == "head_therapist")
|
||||
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):
|
||||
for rec in self:
|
||||
rec.allowed_user_ids = rec.staff_ids.user_ids
|
||||
|
|
@ -214,16 +222,13 @@ class TeamStaff(models.Model):
|
|||
|
||||
def _action_revoke_portal_access(self):
|
||||
"""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")
|
||||
# Deactivate the user and remove from portal group
|
||||
|
||||
# Deactivate the user and set to public user type (standard Odoo approach)
|
||||
if self.user_ids:
|
||||
self.user_ids.write(
|
||||
self.user_ids.sudo().write(
|
||||
{
|
||||
"groups_id": [
|
||||
Command.unlink(group_portal.id),
|
||||
Command.link(group_public.id),
|
||||
],
|
||||
"groups_id": [(6, 0, [group_public.id])], # Set to public user only
|
||||
"active": False,
|
||||
}
|
||||
)
|
||||
|
|
|
|||
|
|
@ -13,6 +13,11 @@
|
|||
<t t-set="url">/my/players</t>
|
||||
<t t-set="placeholder_count">players_count</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>
|
||||
</template>
|
||||
<template id="portal_my_teams">
|
||||
|
|
@ -25,6 +30,8 @@
|
|||
<th>Parent Organization</th>
|
||||
<th>Total Players</th>
|
||||
<th>Injured</th>
|
||||
<th>Activities</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
|
@ -39,6 +46,16 @@
|
|||
<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 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>
|
||||
</t>
|
||||
</tbody>
|
||||
|
|
@ -91,9 +108,17 @@
|
|||
</span>
|
||||
</t>
|
||||
</h1>
|
||||
<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>
|
||||
<a t-attf-href="/my/activities?model=sports.team&res_id={{ team.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-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>
|
||||
|
||||
<!-- Pending Removals Alert -->
|
||||
|
|
@ -243,6 +268,8 @@
|
|||
<th>Practice Status</th>
|
||||
<th>Estimated Return Date</th>
|
||||
<th>Last Consultation Date</th>
|
||||
<th>Activities</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
|
@ -282,6 +309,16 @@
|
|||
<span t-field="player.last_consultation_date"
|
||||
t-options="{'widget': 'date'}"/>
|
||||
</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>
|
||||
</t>
|
||||
</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">
|
||||
<i class="fa fa-plus"></i> Report Injury
|
||||
</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">
|
||||
<i class="fa fa-tasks"></i> Add Activity
|
||||
</a>
|
||||
|
|
@ -416,6 +456,7 @@
|
|||
<t t-if="not is_treatment_prof">
|
||||
<th>External Notes</th>
|
||||
</t>
|
||||
<th>Activities</th>
|
||||
<t t-if="is_treatment_prof">
|
||||
<th class="text-end">Actions</th>
|
||||
</t>
|
||||
|
|
@ -452,6 +493,16 @@
|
|||
<div t-else="" class="text-muted">No external notes</div>
|
||||
</td>
|
||||
</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">
|
||||
<td class="text-end">
|
||||
<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">
|
||||
<i class="fa fa-file-medical"></i> Docs
|
||||
</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>
|
||||
</td>
|
||||
</t>
|
||||
|
|
|
|||
|
|
@ -120,106 +120,22 @@
|
|||
</td>
|
||||
<td>
|
||||
<!-- 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
|
||||
</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
|
||||
</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
|
||||
</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>
|
||||
</tr>
|
||||
</t>
|
||||
|
|
@ -229,6 +145,174 @@
|
|||
</tbody>
|
||||
</table>
|
||||
</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 for creating a new activity -->
|
||||
|
|
@ -432,18 +516,7 @@
|
|||
<!-- Activity buttons now integrated directly into the portal_my_player_injuries template -->
|
||||
|
||||
<!-- 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">
|
||||
<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>
|
||||
<!-- Activities entry removed - now handled in main portal template for consistency -->
|
||||
|
||||
<!-- Add link to activities in the portal navigation -->
|
||||
<template id="portal_breadcrumbs_activities" name="Portal Breadcrumbs : Activities" inherit_id="portal.portal_breadcrumbs">
|
||||
|
|
|
|||
Loading…
Reference in a new issue