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:
kdeterme 2026-10-05 09:42:10 +02:00
parent 03fa3adaf6
commit 6ee0aa470b
3 changed files with 363 additions and 82 deletions

View file

@ -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 */

View file

@ -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() {

View file

@ -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">