feat: implement member absence tracking and visual styling in calendar interventions
This commit is contained in:
parent
61a099eb4f
commit
9702a19db8
2 changed files with 22 additions and 4 deletions
|
|
@ -532,10 +532,17 @@ document.addEventListener('DOMContentLoaded', function () {
|
|||
const formattedMembers = dayData.members.map(m => {
|
||||
const name = typeof m === 'object' && m ? m.name : m;
|
||||
const id = typeof m === 'object' && m ? m.id : null;
|
||||
const isAbsent = typeof m === 'object' && m ? Boolean(m.is_absent) : false;
|
||||
const absenceReason = typeof m === 'object' && m ? (m.absence_reason || '') : '';
|
||||
|
||||
let memberNameHtml = escapeHtml(name);
|
||||
if (isMemberHistoryAllowed && id) {
|
||||
return `<a href="#" class="member-history-link text-decoration-none text-muted fw-semibold" data-member-id="${id}">${escapeHtml(name)}</a>`;
|
||||
memberNameHtml = `<a href="#" class="member-history-link text-decoration-none ${isAbsent ? 'text-danger' : 'text-muted fw-semibold'}" data-member-id="${id}">${escapeHtml(name)}</a>`;
|
||||
}
|
||||
return escapeHtml(name);
|
||||
if (isAbsent) {
|
||||
return `<span class="text-decoration-line-through text-danger opacity-75" title="${escapeHtml(absenceReason || gettext('Absent'))}">${memberNameHtml}</span> <span class="badge text-bg-danger p-0 px-1 font-monospace" style="font-size:0.6rem;">${escapeHtml(absenceReason || gettext('Absent'))}</span>`;
|
||||
}
|
||||
return memberNameHtml;
|
||||
}).join(', ');
|
||||
membersHTML = `<div class="team-members-list text-muted small fst-italic mb-2" style="border-left: 3px solid ${teamColor};" title="${gettext('Composition de l’équipe')}">
|
||||
<i class="bi bi-people-fill me-1" style="color: ${teamColor};"></i>${formattedMembers}
|
||||
|
|
|
|||
|
|
@ -1506,6 +1506,11 @@ def member_history_ajax(request, member_id):
|
|||
)
|
||||
company_str = ", ".join(sorted(company_names)) if company_names else '—'
|
||||
|
||||
# 0. Récupérer les dates d'absences du membre
|
||||
absent_dates = set(
|
||||
CompanyMemberAbsence.objects.filter(member=member).values_list('date', flat=True)
|
||||
)
|
||||
|
||||
# 1. Équipes et périodes valides du membre selon CompanyMemberHistory
|
||||
team_periods_q = Q()
|
||||
for h in team_history_qs:
|
||||
|
|
@ -1527,7 +1532,7 @@ def member_history_ajax(request, member_id):
|
|||
.order_by('-date', '-intervention__id')
|
||||
)
|
||||
|
||||
# 3. Dédoublonnage strict par ID d'intervention
|
||||
# 3. Dédoublonnage strict par ID d'intervention et exclusion des jours d'absence
|
||||
seen_itv_ids = set()
|
||||
interventions_dict = {}
|
||||
|
||||
|
|
@ -1535,6 +1540,9 @@ def member_history_ajax(request, member_id):
|
|||
itv = a.intervention
|
||||
if not itv or itv.id in seen_itv_ids:
|
||||
continue
|
||||
# Si le membre était absent ce jour-là et n'était pas nommément assigné
|
||||
if a.date and a.date in absent_dates and a.member_id != member.id:
|
||||
continue
|
||||
seen_itv_ids.add(itv.id)
|
||||
interventions_dict[itv.id] = {
|
||||
'id': itv.id,
|
||||
|
|
@ -1564,17 +1572,20 @@ def member_history_ajax(request, member_id):
|
|||
for itv in extra_itvs:
|
||||
if itv.id in seen_itv_ids:
|
||||
continue
|
||||
seen_itv_ids.add(itv.id)
|
||||
|
||||
itv_date = (itv.end_time or itv.begin_time or itv.planned_begin_time or itv.creation_time)
|
||||
if itv_date:
|
||||
itv_date_local = timezone.localtime(itv_date) if timezone.is_aware(itv_date) else itv_date
|
||||
itv_date_only = itv_date_local.date()
|
||||
if itv_date_only in absent_dates and itv.assigned_member_id != member.id and not itv.occupations.filter(member=member).exists():
|
||||
continue
|
||||
date_str = itv_date_local.strftime('%d/%m/%Y')
|
||||
raw_date_str = itv_date_local.date().isoformat()
|
||||
else:
|
||||
date_str = ''
|
||||
raw_date_str = ''
|
||||
|
||||
seen_itv_ids.add(itv.id)
|
||||
interventions_dict[itv.id] = {
|
||||
'id': itv.id,
|
||||
'code': itv.code,
|
||||
|
|
|
|||
Loading…
Reference in a new issue