feat(calendar): enable drag and drop for team members in daily view
- Render team members as individual draggable chips in daily teams calendar view - Decouple click (modal inspection) from drag and drop (team/date reassignment) - Add multi-team conflict confirmation modal on drop - Maintain permission checks and past date protection
This commit is contained in:
parent
03fa3adaf6
commit
6ee0aa470b
3 changed files with 363 additions and 82 deletions
|
|
@ -938,19 +938,86 @@
|
|||
line-height: 1.35;
|
||||
border-left: 3px solid #cbd5e1;
|
||||
box-shadow: 0 1px 2px rgba(0,0,0,0.03);
|
||||
cursor: pointer;
|
||||
transition: background-color 0.15s ease, box-shadow 0.15s ease;
|
||||
}
|
||||
|
||||
.team-members-list.clickable-team-members {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.team-members-list:hover {
|
||||
background: #f1f5f9;
|
||||
box-shadow: 0 2px 5px rgba(0,0,0,0.08);
|
||||
}
|
||||
|
||||
.team-members-list strong,
|
||||
.team-members-list .member-name {
|
||||
font-weight: 700;
|
||||
color: #0f172a;
|
||||
}
|
||||
|
||||
.team-members-list:hover {
|
||||
background: #f1f5f9;
|
||||
box-shadow: 0 2px 5px rgba(0,0,0,0.08);
|
||||
/* Member chips in Teams Daily View */
|
||||
.team-daily-members-chips {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 4px;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.team-daily-member-chip {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
background-color: #ffffff;
|
||||
border: 1px solid #cbd5e1;
|
||||
border-radius: 4px;
|
||||
padding: 2px 6px;
|
||||
font-size: 0.76rem;
|
||||
font-weight: 600;
|
||||
color: #1e293b;
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);
|
||||
transition: all 0.15s ease;
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.team-daily-member-chip.draggable {
|
||||
cursor: grab;
|
||||
}
|
||||
|
||||
.team-daily-member-chip.draggable:hover {
|
||||
background-color: #f8fafc;
|
||||
border-color: #0d6efd;
|
||||
box-shadow: 0 2px 6px rgba(13, 110, 253, 0.2);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.team-daily-member-chip.draggable:active {
|
||||
cursor: grabbing;
|
||||
}
|
||||
|
||||
.team-daily-member-chip.is-dragging {
|
||||
opacity: 0.45;
|
||||
border: 1px dashed #0d6efd;
|
||||
background-color: #e7f1ff;
|
||||
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.team-daily-member-chip.is-absent {
|
||||
background-color: #fff5f5;
|
||||
border-color: #f8d7da;
|
||||
}
|
||||
|
||||
.team-daily-member-chip.is-absent .member-name-text {
|
||||
text-decoration: line-through;
|
||||
color: #dc3545;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.team-daily-member-chip .member-name-text {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
/* Stack of interventions */
|
||||
|
|
|
|||
|
|
@ -294,6 +294,10 @@ document.addEventListener('DOMContentLoaded', function () {
|
|||
let currentModalMemberId = null;
|
||||
let draggedBacklogItem = null;
|
||||
let draggedBacklogGroup = null;
|
||||
let _draggedTeamMember = null;
|
||||
let _memberDragJustEnded = false;
|
||||
let _pendingMemberDailyAssign = null;
|
||||
let _lastTeamsDailyData = null;
|
||||
const csrfToken = getCookie('csrftoken');
|
||||
let suppressDurationInputEvent = false;
|
||||
let suppressEndInputEvent = false;
|
||||
|
|
@ -451,6 +455,141 @@ document.addEventListener('DOMContentLoaded', function () {
|
|||
return TEAM_PALETTE[Math.abs(teamId || 0) % TEAM_PALETTE.length];
|
||||
}
|
||||
|
||||
function buildTeamDailyMembersHtml(members, teamId, dateStr, teamName, canEditThisTeam, isPastDay) {
|
||||
const teamColor = typeof getTeamColor === 'function' ? getTeamColor(teamId) : '#0d6efd';
|
||||
const isMemberHistoryAllowed = typeof canViewMemberHistory !== 'undefined' ? Boolean(canViewMemberHistory) : false;
|
||||
const titleTooltip = canEditThisTeam && !isPastDay ? gettext('Modifier la composition de l’équipe pour ce jour') : gettext('Composition de l’équipe');
|
||||
const membersList = (members && Array.isArray(members)) ? members : [];
|
||||
|
||||
if (membersList.length > 0) {
|
||||
const chipsHtml = membersList.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 || '') : '';
|
||||
const isMultiTeam = typeof m === 'object' && m ? Boolean(m.is_multi_team) : false;
|
||||
const otherTeams = typeof m === 'object' && m && m.other_teams ? m.other_teams : [];
|
||||
|
||||
const canDrag = Boolean(canEditThisTeam && !isPastDay && id);
|
||||
let chipTitle = escapeHtml(name);
|
||||
if (canDrag) {
|
||||
chipTitle += ` — ${gettext('Glisser pour réassigner. Cliquer pour gérer l’équipe.')}`;
|
||||
}
|
||||
|
||||
let multiTeamHtml = '';
|
||||
if (isMultiTeam) {
|
||||
const otherNames = otherTeams.map(t => t.name).join(', ');
|
||||
const tooltip = otherNames ? `${gettext('Planifié aussi dans')} : ${otherNames}` : gettext('Planifié dans plusieurs équipes ce jour');
|
||||
multiTeamHtml = ` <i class="bi bi-diagram-3 text-warning ms-1" style="font-size:0.7rem;" title="${escapeHtml(tooltip)}"></i>`;
|
||||
}
|
||||
|
||||
let absentBadgeHtml = '';
|
||||
if (isAbsent) {
|
||||
absentBadgeHtml = ` <span class="badge text-bg-danger p-0 px-1 ms-1 font-monospace" style="font-size:0.6rem;">${escapeHtml(absenceReason || gettext('Absent'))}</span>`;
|
||||
}
|
||||
|
||||
let historyLinkHtml = '';
|
||||
if (isMemberHistoryAllowed && id) {
|
||||
historyLinkHtml = ` <i class="bi bi-clock-history text-muted member-history-icon ms-1" style="font-size:0.7rem; cursor:pointer;" data-member-id="${id}" title="${gettext('Historique du membre')}"></i>`;
|
||||
}
|
||||
|
||||
return `<div class="team-daily-member-chip${canDrag ? ' draggable' : ''}${isAbsent ? ' is-absent' : ''}" ${canDrag ? 'draggable="true"' : ''} data-member-id="${id || ''}" data-member-name="${escapeHtml(name)}" data-team-id="${teamId || ''}" data-date="${dateStr}" title="${chipTitle}">
|
||||
<span class="member-name-text">${escapeHtml(name)}</span>${multiTeamHtml}${absentBadgeHtml}${historyLinkHtml}
|
||||
</div>`;
|
||||
}).join('');
|
||||
|
||||
return `<div class="team-members-list small mb-2" style="border-left: 3px solid ${teamColor};" data-team-id="${teamId || ''}" data-date="${dateStr}" data-team-name="${escapeHtml(teamName || '')}">
|
||||
<div class="d-flex align-items-center justify-content-between">
|
||||
<span class="clickable-team-members d-inline-flex align-items-center" style="cursor: pointer;" title="${escapeHtml(titleTooltip)}" data-team-id="${teamId || ''}" data-date="${dateStr}" data-team-name="${escapeHtml(teamName || '')}">
|
||||
<i class="bi bi-people-fill me-1" style="color: ${teamColor};"></i>
|
||||
<span class="fw-semibold text-secondary" style="font-size: 0.72rem;">${gettext('Équipe')} (${membersList.length})</span>
|
||||
</span>
|
||||
${canEditThisTeam && !isPastDay ? `
|
||||
<button type="button" class="btn btn-link btn-sm p-0 text-muted clickable-team-members" style="font-size: 0.72rem; text-decoration: none;" title="${escapeHtml(titleTooltip)}" data-team-id="${teamId || ''}" data-date="${dateStr}" data-team-name="${escapeHtml(teamName || '')}">
|
||||
<i class="bi bi-pencil"></i>
|
||||
</button>
|
||||
` : ''}
|
||||
</div>
|
||||
<div class="team-daily-members-chips">
|
||||
${chipsHtml}
|
||||
</div>
|
||||
</div>`;
|
||||
} else if (teamId !== null && teamId !== '' && typeof teamId !== 'undefined') {
|
||||
return `<div class="team-members-list clickable-team-members text-muted small fst-italic mb-2 text-opacity-50" style="border-left: 3px solid #cbd5e1; cursor: pointer;" title="${escapeHtml(titleTooltip)}" data-team-id="${teamId || ''}" data-date="${dateStr}" data-team-name="${escapeHtml(teamName || '')}">
|
||||
<i class="bi bi-people me-1"></i>(${gettext('Aucun membre')})
|
||||
${canEditThisTeam && !isPastDay ? `<i class="bi bi-plus-circle ms-1 text-primary"></i>` : ''}
|
||||
</div>`;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
function initTeamDailyMemberChips(container) {
|
||||
const root = container || document.getElementById('teams-daily-table-body');
|
||||
if (!root) return;
|
||||
|
||||
root.querySelectorAll('.team-daily-member-chip[draggable="true"]').forEach(chip => {
|
||||
chip.addEventListener('dragstart', (e) => {
|
||||
const memberId = parseInt(chip.getAttribute('data-member-id'));
|
||||
const memberName = chip.getAttribute('data-member-name') || '';
|
||||
const sourceTeamId = chip.getAttribute('data-team-id') ? parseInt(chip.getAttribute('data-team-id')) : null;
|
||||
const sourceDate = chip.getAttribute('data-date');
|
||||
|
||||
_draggedTeamMember = {
|
||||
id: memberId,
|
||||
name: memberName,
|
||||
sourceTeamId: sourceTeamId,
|
||||
sourceDate: sourceDate
|
||||
};
|
||||
|
||||
chip.classList.add('is-dragging');
|
||||
e.dataTransfer.effectAllowed = 'move';
|
||||
e.dataTransfer.setData('text/plain', String(memberId));
|
||||
});
|
||||
|
||||
chip.addEventListener('dragend', () => {
|
||||
chip.classList.remove('is-dragging');
|
||||
_draggedTeamMember = null;
|
||||
_memberDragJustEnded = true;
|
||||
setTimeout(() => {
|
||||
_memberDragJustEnded = false;
|
||||
}, 200);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function showTeamDailyMultiTeamConfirmModal(member, targetTeamId, targetDate, otherAssignedTeams) {
|
||||
const modalEl = document.getElementById('teamDailyMultiTeamConfirmModal');
|
||||
if (!modalEl) {
|
||||
if (confirm(gettext("Ce membre est déjà affecté à une autre équipe ce jour-là. Voulez-vous le déplacer vers cette équipe ?"))) {
|
||||
assignMemberToDailyTeam(member.id, targetTeamId, targetDate, 'move');
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const otherNames = otherAssignedTeams.map(t => t.name).join(', ');
|
||||
const targetTeamObj = (_lastTeamsDailyData?.teams || []).find(t => t.id === targetTeamId);
|
||||
const targetName = targetTeamObj ? targetTeamObj.name : `Équipe #${targetTeamId}`;
|
||||
|
||||
const memberNameEl = document.getElementById('team-daily-multi-member-name');
|
||||
const dateEl = document.getElementById('team-daily-multi-date');
|
||||
const existingTeamsEl = document.getElementById('team-daily-multi-existing-teams');
|
||||
const targetTeamEl = document.getElementById('team-daily-multi-target-team');
|
||||
|
||||
if (memberNameEl) memberNameEl.textContent = member.name;
|
||||
if (dateEl) dateEl.textContent = targetDate;
|
||||
if (existingTeamsEl) existingTeamsEl.textContent = otherNames;
|
||||
if (targetTeamEl) targetTeamEl.textContent = targetName;
|
||||
|
||||
_pendingMemberDailyAssign = {
|
||||
memberId: member.id,
|
||||
targetTeamId: targetTeamId,
|
||||
targetDate: targetDate
|
||||
};
|
||||
|
||||
const modal = bootstrap.Modal.getOrCreateInstance(modalEl);
|
||||
modal.show();
|
||||
}
|
||||
|
||||
function renderTeamsDailyGrid(data) {
|
||||
const todayStr = formatDateISO(new Date());
|
||||
const headerRow = document.getElementById('teams-daily-table-header');
|
||||
|
|
@ -459,6 +598,7 @@ document.addEventListener('DOMContentLoaded', function () {
|
|||
const datepicker = document.getElementById('teams-daily-datepicker');
|
||||
|
||||
if (!headerRow || !tbody || !data) return;
|
||||
_lastTeamsDailyData = data;
|
||||
|
||||
const startDate = new Date(data.start_date);
|
||||
teamsDailyActiveDate = startDate;
|
||||
|
|
@ -550,40 +690,7 @@ document.addEventListener('DOMContentLoaded', function () {
|
|||
|
||||
const dayData = team.days[day.date] || { members: [], interventions: [] };
|
||||
|
||||
let membersHTML = '';
|
||||
const isPastDay = day.date < todayStr;
|
||||
const isMemberHistoryAllowed = typeof canViewMemberHistory !== 'undefined' ? Boolean(canViewMemberHistory) : false;
|
||||
const canEditThisTeam = (typeof canEditTeams !== 'undefined' && Boolean(canEditTeams)) && (team.can_edit !== false);
|
||||
const titleTooltip = canEditThisTeam && !isPastDay ? gettext('Modifier la composition de l’équipe pour ce jour') : gettext('Composition de l’équipe');
|
||||
|
||||
if (dayData.members && dayData.members.length > 0) {
|
||||
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 || '') : '';
|
||||
const isMultiTeam = typeof m === 'object' && m ? Boolean(m.is_multi_team) : false;
|
||||
const otherTeams = typeof m === 'object' && m && m.other_teams ? m.other_teams : [];
|
||||
|
||||
let memberNameHtml = `<strong class="fw-bold text-dark member-name">${escapeHtml(name)}</strong>`;
|
||||
if (isMultiTeam) {
|
||||
const otherNames = otherTeams.map(t => t.name).join(', ');
|
||||
const tooltip = otherNames ? `${gettext('Planifié aussi dans')} : ${otherNames}` : gettext('Planifié dans plusieurs équipes ce jour');
|
||||
memberNameHtml += ` <i class="bi bi-diagram-3 text-warning ms-1" style="font-size:0.75rem;" title="${escapeHtml(tooltip)}"></i>`;
|
||||
}
|
||||
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 clickable-team-members small mb-2" style="border-left: 3px solid ${teamColor}; cursor: pointer;" title="${escapeHtml(titleTooltip)}" data-team-id="${team.id}" data-date="${day.date}" data-team-name="${escapeHtml(team.name)}">
|
||||
<i class="bi bi-people-fill me-1" style="color: ${teamColor};"></i>${formattedMembers}
|
||||
</div>`;
|
||||
} else if (team.id !== null) {
|
||||
membersHTML = `<div class="team-members-list clickable-team-members text-muted small fst-italic mb-2 text-opacity-50" style="border-left: 3px solid #cbd5e1; cursor: pointer;" title="${escapeHtml(titleTooltip)}" data-team-id="${team.id}" data-date="${day.date}" data-team-name="${escapeHtml(team.name)}">
|
||||
<i class="bi bi-people me-1"></i>(${gettext('Aucun membre')})
|
||||
</div>`;
|
||||
}
|
||||
const membersHTML = buildTeamDailyMembersHtml(dayData.members, team.id, day.date, team.name, canEditThisTeam, isPastDay);
|
||||
|
||||
let itvsHTML = '<div class="teams-daily-interventions-stack">';
|
||||
if (dayData.interventions && dayData.interventions.length > 0) {
|
||||
|
|
@ -696,7 +803,7 @@ document.addEventListener('DOMContentLoaded', function () {
|
|||
if (isPastCell) return;
|
||||
|
||||
cell.addEventListener('dragover', (e) => {
|
||||
if (draggedBacklogItem || draggedBacklogGroup) {
|
||||
if (draggedBacklogItem || draggedBacklogGroup || _draggedTeamMember) {
|
||||
e.preventDefault();
|
||||
e.dataTransfer.dropEffect = 'move';
|
||||
cell.classList.add('teams-daily-grid-cell--highlight');
|
||||
|
|
@ -709,9 +816,56 @@ document.addEventListener('DOMContentLoaded', function () {
|
|||
|
||||
cell.addEventListener('drop', (e) => {
|
||||
cell.classList.remove('teams-daily-grid-cell--highlight');
|
||||
if (!draggedBacklogItem && !draggedBacklogGroup) return;
|
||||
if (!draggedBacklogItem && !draggedBacklogGroup && !_draggedTeamMember) return;
|
||||
e.preventDefault();
|
||||
|
||||
if (_draggedTeamMember) {
|
||||
const member = _draggedTeamMember;
|
||||
_draggedTeamMember = null;
|
||||
|
||||
const targetDate = dateStr;
|
||||
const targetTeamId = teamId ? parseInt(teamId) : null;
|
||||
|
||||
if (!member.id || !targetDate || !targetTeamId) return;
|
||||
|
||||
// Si même équipe et même date, rien à faire
|
||||
if (member.sourceTeamId === targetTeamId && member.sourceDate === targetDate) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Vérifier si le membre est déjà dans l'équipe cible ce jour-là
|
||||
let alreadyInTargetTeam = false;
|
||||
const otherAssignedTeams = [];
|
||||
if (_lastTeamsDailyData && _lastTeamsDailyData.teams) {
|
||||
_lastTeamsDailyData.teams.forEach(t => {
|
||||
if (t.days && t.days[targetDate] && t.days[targetDate].members) {
|
||||
const found = t.days[targetDate].members.some(m => {
|
||||
const mId = typeof m === 'object' && m ? m.id : null;
|
||||
return mId === member.id;
|
||||
});
|
||||
if (found) {
|
||||
if (t.id === targetTeamId) {
|
||||
alreadyInTargetTeam = true;
|
||||
} else {
|
||||
otherAssignedTeams.push(t);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (alreadyInTargetTeam) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (otherAssignedTeams.length > 0) {
|
||||
showTeamDailyMultiTeamConfirmModal(member, targetTeamId, targetDate, otherAssignedTeams);
|
||||
} else {
|
||||
assignMemberToDailyTeam(member.id, targetTeamId, targetDate, 'move');
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const start = new Date(dateStr + 'T08:00:00');
|
||||
|
||||
if (draggedBacklogGroup) {
|
||||
|
|
@ -741,6 +895,8 @@ document.addEventListener('DOMContentLoaded', function () {
|
|||
});
|
||||
});
|
||||
|
||||
initTeamDailyMemberChips(tbody);
|
||||
|
||||
setTimeout(scrollToTodayColumn, 100);
|
||||
}
|
||||
|
||||
|
|
@ -5412,55 +5568,27 @@ function updateTeamsDailyGridCell(teamId, dateStr, currentMembers, teamObj) {
|
|||
const cell = document.querySelector(`.teams-daily-grid-cell[data-team-id="${teamId}"][data-date="${dateStr}"]`);
|
||||
if (!cell) return;
|
||||
|
||||
let membersContainer = cell.querySelector('.team-members-list');
|
||||
const teamColor = typeof getTeamColor === 'function' ? getTeamColor(teamId) : (window.getTeamColor ? window.getTeamColor(teamId) : '#0d6efd');
|
||||
const canEditThisTeam = (typeof canEditTeams !== 'undefined' && Boolean(canEditTeams)) && (!teamObj || teamObj.can_edit !== false);
|
||||
const todayStr = new Date().toISOString().split('T')[0];
|
||||
const isPastDay = dateStr < todayStr;
|
||||
const titleTooltip = canEditThisTeam && !isPastDay ? gettext('Modifier la composition de l’équipe pour ce jour') : gettext('Composition de l’équipe');
|
||||
const teamName = teamObj && teamObj.name ? teamObj.name : '';
|
||||
|
||||
if (!membersContainer) {
|
||||
membersContainer = document.createElement('div');
|
||||
cell.prepend(membersContainer);
|
||||
}
|
||||
const membersContainer = cell.querySelector('.team-members-list');
|
||||
const newHtml = buildTeamDailyMembersHtml(currentMembers, teamId, dateStr, teamName, canEditThisTeam, isPastDay);
|
||||
|
||||
membersContainer.setAttribute('data-team-id', teamId);
|
||||
membersContainer.setAttribute('data-date', dateStr);
|
||||
if (teamName) {
|
||||
membersContainer.setAttribute('data-team-name', teamName);
|
||||
if (membersContainer) {
|
||||
membersContainer.outerHTML = newHtml;
|
||||
} else if (newHtml) {
|
||||
cell.insertAdjacentHTML('afterbegin', newHtml);
|
||||
}
|
||||
membersContainer.setAttribute('title', titleTooltip);
|
||||
initTeamDailyMemberChips(cell);
|
||||
|
||||
if (currentMembers && currentMembers.length > 0) {
|
||||
const formattedMembers = currentMembers.map(m => {
|
||||
const name = typeof m === 'object' && m ? m.name : m;
|
||||
const isAbsent = typeof m === 'object' && m ? Boolean(m.is_absent) : false;
|
||||
const absenceReason = typeof m === 'object' && m ? (m.absence_reason || '') : '';
|
||||
const isMultiTeam = typeof m === 'object' && m ? Boolean(m.is_multi_team) : false;
|
||||
const otherTeams = typeof m === 'object' && m && m.other_teams ? m.other_teams : [];
|
||||
|
||||
let memberNameHtml = `<strong class="fw-bold text-dark member-name">${escapeHtml(name)}</strong>`;
|
||||
if (isMultiTeam) {
|
||||
const otherNames = otherTeams.map(t => t.name).join(', ');
|
||||
const tooltip = otherNames ? `${gettext('Planifié aussi dans')} : ${otherNames}` : gettext('Planifié dans plusieurs équipes ce jour');
|
||||
memberNameHtml += ` <i class="bi bi-diagram-3 text-warning ms-1" style="font-size:0.75rem;" title="${escapeHtml(tooltip)}"></i>`;
|
||||
// Mettre à jour les données en cache
|
||||
if (_lastTeamsDailyData && _lastTeamsDailyData.teams) {
|
||||
const teamInCache = _lastTeamsDailyData.teams.find(t => t.id === parseInt(teamId));
|
||||
if (teamInCache && teamInCache.days && teamInCache.days[dateStr]) {
|
||||
teamInCache.days[dateStr].members = currentMembers || [];
|
||||
}
|
||||
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(', ');
|
||||
|
||||
membersContainer.className = 'team-members-list clickable-team-members small mb-2';
|
||||
membersContainer.style.borderLeft = `3px solid ${teamColor}`;
|
||||
membersContainer.style.cursor = 'pointer';
|
||||
membersContainer.innerHTML = `<i class="bi bi-people-fill me-1" style="color: ${teamColor};"></i>${formattedMembers}`;
|
||||
} else {
|
||||
membersContainer.className = 'team-members-list clickable-team-members text-muted small fst-italic mb-2 text-opacity-50';
|
||||
membersContainer.style.borderLeft = '3px solid #cbd5e1';
|
||||
membersContainer.style.cursor = 'pointer';
|
||||
membersContainer.innerHTML = `<i class="bi bi-people me-1"></i>(${gettext('Aucun membre')})`;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -5670,6 +5798,34 @@ function assignMemberToDailyTeam(memberId, teamId, dateStr, action) {
|
|||
}
|
||||
|
||||
document.addEventListener('click', function(e) {
|
||||
const btnMultiMove = e.target.closest('#btn-team-daily-multi-move');
|
||||
if (btnMultiMove && _pendingMemberDailyAssign) {
|
||||
e.preventDefault();
|
||||
const { memberId, targetTeamId, targetDate } = _pendingMemberDailyAssign;
|
||||
_pendingMemberDailyAssign = null;
|
||||
const modalEl = document.getElementById('teamDailyMultiTeamConfirmModal');
|
||||
if (modalEl) {
|
||||
const modal = bootstrap.Modal.getInstance(modalEl);
|
||||
if (modal) modal.hide();
|
||||
}
|
||||
assignMemberToDailyTeam(memberId, targetTeamId, targetDate, 'move');
|
||||
return;
|
||||
}
|
||||
|
||||
const btnMultiAdd = e.target.closest('#btn-team-daily-multi-add');
|
||||
if (btnMultiAdd && _pendingMemberDailyAssign) {
|
||||
e.preventDefault();
|
||||
const { memberId, targetTeamId, targetDate } = _pendingMemberDailyAssign;
|
||||
_pendingMemberDailyAssign = null;
|
||||
const modalEl = document.getElementById('teamDailyMultiTeamConfirmModal');
|
||||
if (modalEl) {
|
||||
const modal = bootstrap.Modal.getInstance(modalEl);
|
||||
if (modal) modal.hide();
|
||||
}
|
||||
assignMemberToDailyTeam(memberId, targetTeamId, targetDate, 'add');
|
||||
return;
|
||||
}
|
||||
|
||||
const btnAdd = e.target.closest('#btn-team-daily-add-member');
|
||||
if (btnAdd) {
|
||||
e.preventDefault();
|
||||
|
|
@ -5696,6 +5852,19 @@ document.addEventListener('click', function(e) {
|
|||
|
||||
const teamMembersList = e.target.closest('.clickable-team-members, .team-members-list');
|
||||
if (teamMembersList) {
|
||||
if (_memberDragJustEnded) return;
|
||||
|
||||
const historyIcon = e.target.closest('.member-history-icon');
|
||||
if (historyIcon) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
const mId = historyIcon.getAttribute('data-member-id');
|
||||
if (mId) {
|
||||
openMemberHistoryModal(mId);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.target.closest('.member-history-link, .btn-team-daily-remove-member, #btn-team-daily-add-member')) return;
|
||||
|
||||
const teamId = teamMembersList.getAttribute('data-team-id');
|
||||
|
|
@ -5709,6 +5878,13 @@ document.addEventListener('click', function(e) {
|
|||
}
|
||||
});
|
||||
|
||||
const _multiTeamModalEl = document.getElementById('teamDailyMultiTeamConfirmModal');
|
||||
if (_multiTeamModalEl) {
|
||||
_multiTeamModalEl.addEventListener('hidden.bs.modal', function () {
|
||||
_pendingMemberDailyAssign = null;
|
||||
});
|
||||
}
|
||||
|
||||
const _memberHistoryModalEl = document.getElementById('memberHistoryModal');
|
||||
if (_memberHistoryModalEl) {
|
||||
function ensureHistoryModalOnTop() {
|
||||
|
|
|
|||
|
|
@ -745,6 +745,44 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modal Bootstrap pour la confirmation multi-équipes lors du drag & drop d'un membre -->
|
||||
<div class="modal fade" id="teamDailyMultiTeamConfirmModal" tabindex="-1" aria-labelledby="teamDailyMultiTeamConfirmModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header border-bottom-0 pb-0">
|
||||
<h5 class="modal-title text-warning d-flex align-items-center gap-2" id="teamDailyMultiTeamConfirmModalLabel">
|
||||
<i class="bi bi-exclamation-triangle-fill"></i>
|
||||
<span>{% translate "Membre déjà planifié ce jour" %}</span>
|
||||
</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="{% translate 'Fermer' %}"></button>
|
||||
</div>
|
||||
<div class="modal-body py-3">
|
||||
<p class="mb-2">
|
||||
<strong id="team-daily-multi-member-name"></strong> {% translate "est déjà planifié(e) le" %} <strong id="team-daily-multi-date"></strong> {% translate "dans l'équipe" %} :
|
||||
<span class="badge bg-secondary" id="team-daily-multi-existing-teams"></span>.
|
||||
</p>
|
||||
<p class="mb-0 text-muted small">
|
||||
{% translate "Vous tentez de l'assigner à l'équipe" %} : <strong id="team-daily-multi-target-team"></strong>.
|
||||
{% translate "Souhaitez-vous déplacer ce membre ou l'affecter aux deux équipes ?" %}
|
||||
</p>
|
||||
</div>
|
||||
<div class="modal-footer border-top-0 pt-0 d-flex justify-content-between">
|
||||
<button type="button" class="btn btn-sm btn-outline-secondary" data-bs-dismiss="modal">
|
||||
{% translate "Annuler" %}
|
||||
</button>
|
||||
<div class="d-flex gap-2">
|
||||
<button type="button" class="btn btn-sm btn-outline-primary fw-semibold" id="btn-team-daily-multi-add">
|
||||
<i class="bi bi-diagram-3 me-1"></i>{% translate "Affecter aussi à cette équipe" %}
|
||||
</button>
|
||||
<button type="button" class="btn btn-sm btn-warning fw-semibold" id="btn-team-daily-multi-move">
|
||||
<i class="bi bi-arrow-right me-1"></i>{% translate "Déplacer vers cette équipe" %}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modal Bootstrap pour l'historique d'un membre -->
|
||||
<div class="modal fade" id="memberHistoryModal" tabindex="-1" aria-labelledby="memberHistoryModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered modal-xl">
|
||||
|
|
|
|||
Loading…
Reference in a new issue