// ==UserScript== // @name Lucca - Toggle absents du planning // @description Ajoute un bouton pour masquer/afficher les collaborateurs qui ne sont pas au bureau aujourd'hui (télétravail ou congé) sur le planning des absences Lucca. // @version 1.0.0 // @namespace https://ilucca.net // @author https://github.com/slashome // @updateURL https://raw.githubusercontent.com/slashome/userscripts/main/scripts/lucca-planning-toggle.user.js // @downloadURL https://raw.githubusercontent.com/slashome/userscripts/main/scripts/lucca-planning-toggle.user.js // @match *://*.ilucca.net/* // @run-at document-idle // @grant none // ==/UserScript== (function() { 'use strict'; const TABLE_SELECTOR = 'table[aria-label="Planning des absences"]'; const BUTTON_ID = 'lucca-planning-toggle-button'; let hideAbsent = false; let observedTbody = null; let reapplyTimer = null; function findTodayColumnIndex() { const dayHeaderCells = document.querySelectorAll('tr.table-head-rowDays th.table-head-row-cell'); for (let i = 0; i < dayHeaderCells.length; i++) { if (dayHeaderCells[i].querySelector('.table-head-rowDays-today')) { return i; } } return -1; } function isAbsentToday(userRow, todayIndex) { const dayCells = userRow.querySelectorAll('td.table-body-row-cell'); const todayCell = dayCells[todayIndex]; if (!todayCell) return false; const halves = todayCell.querySelectorAll('.table-body-row-cell-day-half'); return Array.from(halves).some(half => !half.classList.contains('is-off')); } function applyFilter() { const table = document.querySelector(TABLE_SELECTOR); if (!table) return; const todayIndex = findTodayColumnIndex(); const rows = Array.from(table.querySelectorAll('tbody > tr')); let deptGroupRows = []; let deptGroupHasVisibleUser = false; let inDeptGroup = false; function closeDeptGroup() { if (deptGroupRows.length) { const display = deptGroupHasVisibleUser ? '' : 'none'; deptGroupRows.forEach(row => { row.style.display = display; }); } deptGroupRows = []; deptGroupHasVisibleUser = false; } rows.forEach(row => { if (row.classList.contains('table-body-rowDepartment')) { if (!inDeptGroup) { closeDeptGroup(); inDeptGroup = true; } deptGroupRows.push(row); } else if (row.classList.contains('table-body-rowUser')) { inDeptGroup = false; const absent = todayIndex !== -1 && isAbsentToday(row, todayIndex); const visible = !(hideAbsent && absent); row.style.display = visible ? '' : 'none'; if (visible) deptGroupHasVisibleUser = true; } }); closeDeptGroup(); } function updateButtonLabel(button) { const label = button.querySelector('.lucca-planning-toggle-label'); const icon = button.querySelector('.lucca-planning-toggle-icon'); label.textContent = hideAbsent ? 'Afficher tout le monde' : 'Masquer les indisponibles'; icon.innerHTML = hideAbsent ? EYE_OFF_ICON : EYE_ICON; button.style.background = hideAbsent ? '#f1edfb' : '#ffffff'; button.style.borderColor = hideAbsent ? '#a48ce8' : '#d8dce6'; button.style.color = hideAbsent ? '#5b3cc4' : '#33415c'; } const EYE_ICON = ''; const EYE_OFF_ICON = ''; function createButton() { const button = document.createElement('button'); button.id = BUTTON_ID; button.type = 'button'; button.style.position = 'fixed'; button.style.top = '72px'; button.style.right = '24px'; button.style.zIndex = '9999'; button.style.display = 'flex'; button.style.alignItems = 'center'; button.style.gap = '6px'; button.style.padding = '7px 14px'; button.style.border = '1px solid #d8dce6'; button.style.borderRadius = '999px'; button.style.background = '#ffffff'; button.style.color = '#33415c'; button.style.fontFamily = '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif'; button.style.fontSize = '13px'; button.style.fontWeight = '500'; button.style.cursor = 'pointer'; button.style.boxShadow = '0 1px 4px rgba(15, 23, 42, 0.12)'; button.style.transition = 'background-color .15s ease, border-color .15s ease, color .15s ease'; const icon = document.createElement('span'); icon.className = 'lucca-planning-toggle-icon'; icon.style.display = 'inline-flex'; const label = document.createElement('span'); label.className = 'lucca-planning-toggle-label'; button.appendChild(icon); button.appendChild(label); button.addEventListener('click', () => { hideAbsent = !hideAbsent; updateButtonLabel(button); applyFilter(); }); updateButtonLabel(button); return button; } function attachTbodyObserver(table) { const tbody = table.querySelector('tbody'); if (!tbody || tbody === observedTbody) return; observedTbody = tbody; const observer = new MutationObserver(() => { clearTimeout(reapplyTimer); reapplyTimer = setTimeout(applyFilter, 100); }); observer.observe(tbody, { childList: true, subtree: true }); } function ensureButton() { const table = document.querySelector(TABLE_SELECTOR); let button = document.getElementById(BUTTON_ID); if (!table) { if (button) button.style.display = 'none'; return; } if (!button) { button = createButton(); document.body.appendChild(button); } button.style.display = 'flex'; attachTbodyObserver(table); if (hideAbsent) applyFilter(); } const bodyObserver = new MutationObserver(() => ensureButton()); bodyObserver.observe(document.body, { childList: true, subtree: true }); ensureButton(); })();