feat: add 7j view mode to team planning

This commit is contained in:
kdeterme 2026-09-25 09:27:32 +02:00
parent 40ce03d812
commit 5e9fced68f
3 changed files with 45 additions and 10 deletions

View file

@ -726,9 +726,10 @@
<div class="d-flex align-items-center gap-1"> <div class="d-flex align-items-center gap-1">
<span class="text-muted small text-nowrap"><i class="bi bi-eye me-1"></i>{% trans "Vue :" %}</span> <span class="text-muted small text-nowrap"><i class="bi bi-eye me-1"></i>{% trans "Vue :" %}</span>
<div class="btn-group btn-group-sm" role="group" id="planning-viewmode-group"> <div class="btn-group btn-group-sm" role="group" id="planning-viewmode-group">
<button type="button" class="btn btn-outline-primary" data-mode="work_week">{% trans "5j (Ouvré)" %}</button> <button type="button" class="btn btn-outline-primary" data-mode="work_week" title="{% trans "Semaine de travail de 5 jours (du lundi au vendredi)" %}">{% trans "5j (Ouvré)" %}</button>
<button type="button" class="btn btn-primary" data-mode="week">{% trans "Semaine" %}</button> <button type="button" class="btn btn-outline-primary" data-mode="days_7" title="{% trans "Semaine complète de 7 jours (avec week-end)" %}">{% trans "7j" %}</button>
<button type="button" class="btn btn-outline-primary" data-mode="month">{% trans "Mois" %}</button> <button type="button" class="btn btn-primary" data-mode="week" title="{% trans "Vue par semaine (5 semaines glissantes)" %}">{% trans "Semaine" %}</button>
<button type="button" class="btn btn-outline-primary" data-mode="month" title="{% trans "Vue par mois (6 mois glissants)" %}">{% trans "Mois" %}</button>
</div> </div>
</div> </div>
@ -1650,7 +1651,7 @@ const initialDate = (function() {
const initialViewMode = (function() { const initialViewMode = (function() {
const saved = getStoredSetting('view_mode', 'week'); const saved = getStoredSetting('view_mode', 'week');
if (['work_week', 'week', 'two_weeks', 'month'].includes(saved)) return saved; if (['work_week', 'days_7', '7_days', 'week', 'two_weeks', 'month'].includes(saved)) return saved;
return 'week'; return 'week';
})(); })();
@ -1719,7 +1720,7 @@ document.addEventListener('DOMContentLoaded', function() {
loadPlanningData(); loadPlanningData();
}); });
// Changement de mode de vue (5j / Semaine / Mois) // Changement de mode de vue (5j / 7j / Semaine / Mois)
$('#planning-viewmode-group button').on('click', function() { $('#planning-viewmode-group button').on('click', function() {
$('#planning-viewmode-group button').removeClass('btn-primary').addClass('btn-outline-primary'); $('#planning-viewmode-group button').removeClass('btn-primary').addClass('btn-outline-primary');
$(this).removeClass('btn-outline-primary').addClass('btn-primary'); $(this).removeClass('btn-outline-primary').addClass('btn-primary');
@ -1736,7 +1737,7 @@ document.addEventListener('DOMContentLoaded', function() {
setStoredSetting('assign_scope', 'week'); setStoredSetting('assign_scope', 'week');
$('#planning-assign-scope-group button').removeClass('btn-primary').addClass('btn-outline-primary'); $('#planning-assign-scope-group button').removeClass('btn-primary').addClass('btn-outline-primary');
$('#planning-assign-scope-group button[data-scope="week"]').removeClass('btn-outline-primary').addClass('btn-primary'); $('#planning-assign-scope-group button[data-scope="week"]').removeClass('btn-outline-primary').addClass('btn-primary');
} else if (_planningState.viewMode === 'work_week') { } else if (_planningState.viewMode === 'work_week' || _planningState.viewMode === 'days_7' || _planningState.viewMode === '7_days') {
_planningState.assignScope = 'day'; _planningState.assignScope = 'day';
setStoredSetting('assign_scope', 'day'); setStoredSetting('assign_scope', 'day');
$('#planning-assign-scope-group button').removeClass('btn-primary').addClass('btn-outline-primary'); $('#planning-assign-scope-group button').removeClass('btn-primary').addClass('btn-outline-primary');
@ -1941,10 +1942,12 @@ function renderPlanningMatrix(data) {
quickDupTitle = "{% trans 'Dupliquer vers le mois suivant (avec absences)'|escapejs %}"; quickDupTitle = "{% trans 'Dupliquer vers le mois suivant (avec absences)'|escapejs %}";
optionsTitle = "{% trans 'Options du mois'|escapejs %}"; optionsTitle = "{% trans 'Options du mois'|escapejs %}";
dupCustomTitle = "{% trans 'Dupliquer ce mois…'|escapejs %}"; dupCustomTitle = "{% trans 'Dupliquer ce mois…'|escapejs %}";
} else if (_planningState.viewMode === 'work_week') { } else if (_planningState.viewMode === 'work_week' || _planningState.viewMode === 'days_7' || _planningState.viewMode === '7_days') {
const dParts = day.date.split('-'); const dParts = day.date.split('-');
const dateObj = new Date(parseInt(dParts[0], 10), parseInt(dParts[1], 10) - 1, parseInt(dParts[2], 10)); const dateObj = new Date(parseInt(dParts[0], 10), parseInt(dParts[1], 10) - 1, parseInt(dParts[2], 10));
if (dateObj.getDay() === 5) { if (_planningState.viewMode === 'work_week' && dateObj.getDay() === 5) {
quickDupTitle = "{% trans 'Dupliquer vers le lundi suivant (avec absences)'|escapejs %}";
} else if (dateObj.getDay() === 0) {
quickDupTitle = "{% trans 'Dupliquer vers le lundi suivant (avec absences)'|escapejs %}"; quickDupTitle = "{% trans 'Dupliquer vers le lundi suivant (avec absences)'|escapejs %}";
} }
} }

View file

@ -622,6 +622,18 @@ class TeamPlanningAjaxViewsTests(TestCase):
self.assertEqual(len(data['days']), 5) self.assertEqual(len(data['days']), 5)
self.assertEqual(data['days'][0]['col_type'], 'day') self.assertEqual(data['days'][0]['col_type'], 'day')
def test_planning_data_ajax_days_7_view(self):
"""Tester le retour du mode 'days_7' (7 jours complets avec week-end) dans team_planning_data_ajax."""
url = reverse('contracts:team_planning_data_ajax')
response = self.client.get(url, {'date': '2026-09-02', 'view_mode': 'days_7'})
self.assertEqual(response.status_code, 200)
data = response.json()
self.assertEqual(len(data['days']), 7)
self.assertEqual(data['days'][0]['col_type'], 'day')
self.assertFalse(data['days'][0]['is_weekend']) # Lundi
self.assertTrue(data['days'][5]['is_weekend']) # Samedi
self.assertTrue(data['days'][6]['is_weekend']) # Dimanche
def test_planning_assign_member_ajax_week_scope(self): def test_planning_assign_member_ajax_week_scope(self):
"""Tester l'assignation avec portée 'week' via l'API AJAX.""" """Tester l'assignation avec portée 'week' via l'API AJAX."""
# Prendre un lundi dans le futur # Prendre un lundi dans le futur

View file

@ -436,7 +436,7 @@ def team_planning_data_ajax(request):
Retourne la matrice complète de planification des équipes sur une période. Retourne la matrice complète de planification des équipes sur une période.
Paramètres GET : Paramètres GET :
- date : date d'ancrage (YYYY-MM-DD), par défaut aujourd'hui - date : date d'ancrage (YYYY-MM-DD), par défaut aujourd'hui
- view_mode : 'week' (7j, défaut), 'two_weeks' (14j), 'work_week' (5j) - view_mode : 'work_week' (5j), 'days_7' (7j), 'week' (5 semaines), 'month' (6 mois), 'two_weeks' (14j)
- company_id : filtrer sur une société spécifique (optionnel) - company_id : filtrer sur une société spécifique (optionnel)
""" """
if not user_can_view_teams(request.user): if not user_can_view_teams(request.user):
@ -477,6 +477,24 @@ def team_planning_data_ajax(request):
'is_weekend': False, 'is_weekend': False,
'col_type': 'day', 'col_type': 'day',
}) })
elif view_mode in ('days_7', '7_days'):
num_days = 7
start_date = monday
end_date = monday + timedelta(days=6)
for i in range(num_days):
d = start_date + timedelta(days=i)
w_idx = d.weekday()
days_list.append({
'date': d.isoformat(),
'day_name': DAYS_FR_FULL[w_idx],
'day_short': DAYS_FR_SHORT[w_idx],
'day_num': f"{d.day:02d}/{d.month:02d}",
'formatted': f"{DAYS_FR_SHORT[w_idx]} {d.day:02d} {MONTHS_FR[d.month]}",
'is_today': d == today,
'is_past': d < today,
'is_weekend': w_idx >= 5,
'col_type': 'day',
})
elif view_mode == 'two_weeks': elif view_mode == 'two_weeks':
num_days = 14 num_days = 14
start_date = monday start_date = monday
@ -643,13 +661,15 @@ def team_planning_data_ajax(request):
}) })
# 7. Structurer les membres pour le volet latéral (Sidebar) # 7. Structurer les membres pour le volet latéral (Sidebar)
if view_mode == 'week': if view_mode in ('week', 'days_7', '7_days'):
absent_member_ids = {mid for (mid, d) in absences.keys() if monday <= d <= monday + timedelta(days=6)} absent_member_ids = {mid for (mid, d) in absences.keys() if monday <= d <= monday + timedelta(days=6)}
elif view_mode == 'month': elif view_mode == 'month':
_, d_in_m = calendar.monthrange(anchor_date.year, anchor_date.month) _, d_in_m = calendar.monthrange(anchor_date.year, anchor_date.month)
m_s = date(anchor_date.year, anchor_date.month, 1) m_s = date(anchor_date.year, anchor_date.month, 1)
m_e = date(anchor_date.year, anchor_date.month, d_in_m) m_e = date(anchor_date.year, anchor_date.month, d_in_m)
absent_member_ids = {mid for (mid, d) in absences.keys() if m_s <= d <= m_e} absent_member_ids = {mid for (mid, d) in absences.keys() if m_s <= d <= m_e}
elif view_mode == 'work_week':
absent_member_ids = {mid for (mid, d) in absences.keys() if monday <= d <= monday + timedelta(days=4)}
else: else:
absent_member_ids = {mid for (mid, d) in absences.keys() if d == anchor_date} absent_member_ids = {mid for (mid, d) in absences.keys() if d == anchor_date}
members_data = [] members_data = []