diff --git a/jira.php b/jira.php index 130b401..85127b7 100644 --- a/jira.php +++ b/jira.php @@ -234,15 +234,42 @@ $rawProjectStatuses = $jiraData['project_statuses'] ?? []; $projectStatusesData = []; $allStatusesMap = []; -foreach (array_keys($activeProjectKeys) as $pKey) { - $pName = $coreProjects[$pKey]['name'] ?? ($allProjects[$pKey]['name'] ?? $pKey); - $pColor = $coreProjects[$pKey]['color'] ?? 'slate'; +$allTargetKeys = array_keys($activeProjectKeys); +if ($isCodeVisible('DX') && !in_array('DX', $allTargetKeys, true)) { + $allTargetKeys[] = 'DX'; +} +if (!empty($queueFilters)) { + foreach ($queueFilters as $qf) { + $qCode = strtoupper(trim($qf['filter_code'] ?? '')); + if (!empty($qCode) && $isCodeVisible($qCode) && !in_array($qCode, $allTargetKeys, true)) { + $allTargetKeys[] = $qCode; + } + } +} + +foreach ($allTargetKeys as $pKey) { + if ($pKey === 'DX') { + $pName = 'DX Koppelingen'; + $pColor = 'amber'; + } else { + $pName = $coreProjects[$pKey]['name'] ?? ($allProjects[$pKey]['name'] ?? $pKey); + $pColor = $coreProjects[$pKey]['color'] ?? 'slate'; + if (!isset($coreProjects[$pKey]) && !empty($queueFilters)) { + foreach ($queueFilters as $qf) { + if (strtoupper(trim($qf['filter_code'] ?? '')) === $pKey && !empty($qf['filter_name'])) { + $pName = $qf['filter_name']; + break; + } + } + } + } $pStatuses = []; // 1. Statussen uit Jira API voor dit project - if (!empty($rawProjectStatuses[$pKey])) { - foreach ($rawProjectStatuses[$pKey] as $st) { + $apiPKey = ($pKey === 'DX') ? 'SD' : $pKey; + if (!empty($rawProjectStatuses[$apiPKey])) { + foreach ($rawProjectStatuses[$apiPKey] as $st) { $sName = trim($st['name'] ?? ''); if (empty($sName)) continue; $sKey = strtolower($sName); @@ -256,11 +283,25 @@ foreach (array_keys($activeProjectKeys) as $pKey) { // 2. Statussen die op de tickets van dit project voorkomen toevoegen/mergen foreach ($allIssues as $iss) { - $issPKey = strtoupper(trim($iss['project_key'] ?? '')); - if (empty($issPKey) && !empty($iss['key']) && preg_match('/^([A-Z0-9]+)-/i', $iss['key'], $m)) { - $issPKey = strtoupper($m[1]); + $matches = false; + if ($pKey === 'DX') { + $matches = $issueMatchesQueue($iss, 'DX'); + } else { + $issPKey = strtoupper(trim($iss['project_key'] ?? '')); + if (empty($issPKey) && !empty($iss['key']) && preg_match('/^([A-Z0-9]+)-/i', $iss['key'], $m)) { + $issPKey = strtoupper($m[1]); + } + $matches = ($issPKey === $pKey); + if (!$matches && !empty($queueFilters)) { + foreach ($queueFilters as $qf) { + if (strtoupper(trim($qf['filter_code'] ?? '')) === $pKey) { + $matches = $issueMatchesQueue($iss, $pKey); + break; + } + } + } } - if ($issPKey === $pKey && !empty($iss['status'])) { + if ($matches && !empty($iss['status'])) { $sName = trim($iss['status']); $sKey = strtolower($sName); if (!isset($pStatuses[$sKey])) { @@ -1114,53 +1155,42 @@ function toggleCurrentProjectOnlyAssigned() { } function updateAssignedPillState() { - const targetKey = (currentProject || 'ALL').toUpperCase().trim(); - const isAssigned = projectOnlyAssigned.has(targetKey) || (currentProject === '' && projectOnlyAssigned.has('ALL')); - const btn = document.getElementById('btnToggleAssignedToMePill'); - const badge = document.getElementById('badgeAssignedToMeCount'); - const label = document.getElementById('labelAssignedToMePill'); - if (!btn) return; - - if (isAssigned) { - btn.className = "flex items-center space-x-1.5 px-2.5 sm:px-3 py-1.5 rounded-xl text-xs font-bold border border-indigo-600 bg-indigo-600 text-white shadow-xs flex-shrink-0 cursor-pointer"; - if (badge) { - badge.className = "px-1.5 py-0.5 rounded-full text-[10px] font-black bg-white/20 text-white"; - } - } else { - btn.className = "flex items-center space-x-1.5 px-2.5 sm:px-3 py-1.5 rounded-xl text-xs font-semibold border transition cursor-pointer shadow-2xs flex-shrink-0 bg-white dark:bg-slate-900 border-slate-200 dark:border-slate-700 text-slate-700 dark:text-slate-300 hover:border-slate-300 dark:hover:border-slate-600"; - if (badge) { - badge.className = "px-1.5 py-0.5 rounded-full text-[10px] font-bold bg-slate-100 dark:bg-slate-800 text-slate-600 dark:text-slate-300"; - } - } - - if (label) { - if (currentProject) { - label.innerHTML = `Toegewezen aan mij (${escapeHtml(currentProject)})`; - } else { - label.innerHTML = `Toegewezen aan mij `; - } - } - const all = getDeduplicatedIssues(); - let targetList = all; - if (currentProject) { - targetList = all.filter(iss => isIssueInQueue(iss, currentProject)); + const myCountAll = all.filter(iss => isIssueAssignedToMe(iss)).length; + + // Update 'Alle Projecten' dropdown + const chkAll = document.getElementById('chkOnlyAssigned_all'); + if (chkAll) { + chkAll.checked = projectOnlyAssigned.has('ALL'); } - const myCount = targetList.filter(iss => isIssueAssignedToMe(iss)).length; - if (badge) { - badge.innerText = myCount; + const cntAllEl = document.getElementById('projAssignedCount_all'); + if (cntAllEl) { + cntAllEl.innerText = myCountAll; + } + const btnMijnAll = document.getElementById('btnDropdownMijn_all'); + if (btnMijnAll) { + if (projectOnlyAssigned.has('ALL')) { + btnMijnAll.className = "px-1.5 py-0.5 rounded font-bold bg-indigo-600 text-white transition cursor-pointer"; + } else { + btnMijnAll.className = "px-1.5 py-0.5 rounded font-bold text-indigo-600 dark:text-indigo-400 hover:bg-indigo-50 dark:hover:bg-indigo-950/60 transition cursor-pointer"; + } } + // Update per-project dropdowns (projectStatusesData || []).forEach(proj => { const pKey = (proj.key || '').toUpperCase().trim(); const chk = document.getElementById(`chkOnlyAssigned_${pKey}`); if (chk) { chk.checked = projectOnlyAssigned.has(pKey) || projectOnlyAssigned.has('ALL'); } - const cCnt = all.filter(iss => { + let projIssues = all.filter(iss => { const k = getIssueProjectKey(iss); - return (k === pKey || (!k && (iss.project || '').toUpperCase().includes(pKey))) && isIssueAssignedToMe(iss); - }).length; + return (k === pKey || (!k && (iss.project || '').toUpperCase().includes(pKey))); + }); + if (pKey === 'DX' || !['SD', 'IN', 'AAN', 'GEN'].includes(pKey)) { + projIssues = all.filter(iss => isIssueInQueue(iss, pKey)); + } + const cCnt = projIssues.filter(iss => isIssueAssignedToMe(iss)).length; const cntEl = document.getElementById(`projAssignedCount_${pKey}`); if (cntEl) cntEl.innerText = cCnt; @@ -1174,6 +1204,10 @@ function updateAssignedPillState() { } } }); + + if (typeof updateProjectPills === 'function') { + updateProjectPills(); + } } // Hulpfunctie voor deduplicatie van tickets (assigned + team) @@ -1327,258 +1361,338 @@ function isIssueStatusSelected(iss) { if (compound && selectedStatuses.has(compound)) return true; if (selectedStatuses.has(issStatus)) return true; + if (typeof isIssueInQueue === 'function' && isIssueInQueue(iss, 'DX') && selectedStatuses.has(`dx::${issStatus}`)) return true; return false; } -// Renders the per-project status dropdowns into #projectStatusDropdownsWrapper -function renderStatusCheckboxes() { - const container = document.getElementById('projectStatusDropdownsWrapper'); - if (!container) return; +// Renders each project's status dropdown directly inside its anchored container (#projectDropdownMenu_${pKey}) +function buildStatusCheckboxRowHtml(pKey, st, sIdx, prefix = '') { + const sNorm = (st.name || '').toLowerCase().trim(); + const pKeyUpper = (pKey || '').toUpperCase().trim(); + const pKeyLower = (pKey || '').toLowerCase().trim(); + const compoundVal = `${pKeyLower}::${sNorm}`; + const cat = (st.status_category || '').toLowerCase().trim(); + const isDone = (cat === 'done' || isDoneStatus(sNorm)); - if (!projectStatusesData || projectStatusesData.length === 0) { - container.innerHTML = 'Geen project statussen gevonden'; - return; + const isChecked = selectedStatuses.has(compoundVal) || selectedStatuses.has(sNorm); + if (isChecked) { + selectedStatuses.add(compoundVal); } + let dotColor = 'bg-blue-500'; + if (cat === 'new' || sNorm.includes('to do') || sNorm.includes('open') || sNorm.includes('nieuw')) { + dotColor = 'bg-slate-400'; + } else if (isDone) { + dotColor = 'bg-emerald-500'; + } else if (sNorm.includes('wacht') || sNorm.includes('klant') || sNorm.includes('pending')) { + dotColor = 'bg-amber-500'; + } + + const safeKey = `${pKeyUpper}_${sNorm.replace(/[^a-z0-9]/gi, '_')}`; + const safeId = `chkStatus_${prefix}${pKeyUpper}_${sIdx}`; + + return ` + + `; +} + +function renderStatusCheckboxes() { const colorClasses = { sky: { badge: 'bg-sky-100 text-sky-800 dark:bg-sky-950/70 dark:text-sky-300 border border-sky-300 dark:border-sky-800', - hdrBg: 'bg-sky-50/70 dark:bg-sky-950/40' + hdrBg: 'bg-sky-50/80 dark:bg-sky-950/40' }, amber: { badge: 'bg-amber-100 text-amber-800 dark:bg-amber-950/70 dark:text-amber-300 border border-amber-300 dark:border-amber-800', - hdrBg: 'bg-amber-50/70 dark:bg-amber-950/40' + hdrBg: 'bg-amber-50/80 dark:bg-amber-950/40' }, emerald: { badge: 'bg-emerald-100 text-emerald-800 dark:bg-emerald-950/70 dark:text-emerald-300 border border-emerald-300 dark:border-emerald-800', - hdrBg: 'bg-emerald-50/70 dark:bg-emerald-950/40' + hdrBg: 'bg-emerald-50/80 dark:bg-emerald-950/40' }, indigo: { badge: 'bg-indigo-100 text-indigo-800 dark:bg-indigo-950/70 dark:text-indigo-300 border border-indigo-300 dark:border-indigo-800', - hdrBg: 'bg-indigo-50/70 dark:bg-indigo-950/40' + hdrBg: 'bg-indigo-50/80 dark:bg-indigo-950/40' }, slate: { badge: 'bg-slate-100 text-slate-800 dark:bg-slate-800 dark:text-slate-200 border border-slate-300 dark:border-slate-700', - hdrBg: 'bg-slate-50 dark:bg-slate-800/40' + hdrBg: 'bg-slate-50/80 dark:bg-slate-800/40' } }; - let html = ''; - projectStatusesData.forEach((proj, pIdx) => { + // 1. Dropdown voor "Alle Projecten" (#projectDropdownMenu_all) + const menuAll = document.getElementById('projectDropdownMenu_all'); + if (menuAll) { + let allStatusesGroupedHtml = ''; + (projectStatusesData || []).forEach(proj => { + const pKey = (proj.key || '').toUpperCase().trim(); + const pColor = proj.color || 'slate'; + const styles = colorClasses[pColor] || colorClasses.slate; + const statuses = proj.statuses || []; + if (statuses.length === 0) return; + + let rows = ''; + statuses.forEach((st, sIdx) => { + rows += buildStatusCheckboxRowHtml(pKey, st, sIdx, 'all_'); + }); + + allStatusesGroupedHtml += ` +
+
+
+ ${escapeHtml(pKey)} + ${escapeHtml(proj.name || pKey)} +
+
+ + • + +
+
+
+ ${rows} +
+
+ `; + }); + + menuAll.innerHTML = ` +
+
+ + Alle Projecten + (0) +
+
+ + • + + • + + • + +
+
+ + +
+ +
+ 0 + +
+
+ +
+ ${allStatusesGroupedHtml || '
Geen project statussen gevonden
'} +
+ `; + } + + // 2. Dropdown per afzonderlijk project (#projectDropdownMenu_${pKey}) + (projectStatusesData || []).forEach(proj => { const pKey = (proj.key || '').toUpperCase().trim(); - const pKeyLower = pKey.toLowerCase(); + const menuEl = document.getElementById(`projectDropdownMenu_${pKey}`); + if (!menuEl) return; + const pColor = proj.color || 'slate'; const styles = colorClasses[pColor] || colorClasses.slate; const statuses = proj.statuses || []; let statusRowsHtml = ''; statuses.forEach((st, sIdx) => { - const sNorm = (st.name || '').toLowerCase().trim(); - const compoundVal = `${pKeyLower}::${sNorm}`; - const cat = (st.status_category || '').toLowerCase().trim(); - const isDone = (cat === 'done' || isDoneStatus(sNorm)); - - const isChecked = selectedStatuses.has(compoundVal) || selectedStatuses.has(sNorm); - if (isChecked) { - selectedStatuses.add(compoundVal); - } - - let dotColor = 'bg-blue-500'; - if (cat === 'new' || sNorm.includes('to do') || sNorm.includes('open') || sNorm.includes('nieuw')) { - dotColor = 'bg-slate-400'; - } else if (isDone) { - dotColor = 'bg-emerald-500'; - } else if (sNorm.includes('wacht') || sNorm.includes('klant') || sNorm.includes('pending')) { - dotColor = 'bg-amber-500'; - } - - const safeKey = `${pKey}_${sNorm.replace(/[^a-z0-9]/gi, '_')}`; - const safeId = `chkStatus_${pKey}_${sIdx}`; - - statusRowsHtml += ` - - `; + statusRowsHtml += buildStatusCheckboxRowHtml(pKey, st, sIdx, ''); }); - html += ` -
- - - +
+ + • + + • + + • + +
+
+ + +
+ +
+ 0 + +
+
+ +
+ ${statusRowsHtml || '
Geen statussen gevonden
'}
`; }); - container.innerHTML = html; if (typeof lucide !== 'undefined') lucide.createIcons(); updateStatusCounts(); updateStatusDropdownLabel(); - updateProjectStatusDropdownVisibility(); - updateAssignedPillState(); + updateProjectPills(); } -function updateProjectStatusDropdownVisibility() { - const items = document.querySelectorAll('.project-status-dropdown-item'); - const curNorm = (currentProject || '').toUpperCase().trim(); - items.forEach(item => { - const pKey = (item.getAttribute('data-project') || '').toUpperCase().trim(); - if (!curNorm) { - // "Alle Projecten": toon alle toegewezen project dropdowns - item.classList.remove('hidden'); - } else if (curNorm === pKey) { - item.classList.remove('hidden'); - } else if (curNorm === 'DX' && pKey === 'SD') { - item.classList.remove('hidden'); - } else { - item.classList.add('hidden'); - } - }); -} - -function toggleProjectStatusDropdown(projectKey, force) { - const targetKey = (projectKey || '').toUpperCase().trim(); - const menu = document.getElementById(`statusDropdownMenu_${targetKey}`); - const chevron = document.getElementById(`statusDropdownChevron_${targetKey}`); - const btn = document.getElementById(`statusDropdownBtn_${targetKey}`); +function toggleProjectDropdown(projectKey, event) { + if (event) { + event.stopPropagation(); + event.preventDefault(); + } + const targetKey = (projectKey || 'all').trim(); + const menu = document.getElementById(`projectDropdownMenu_${targetKey}`); + const chevron = document.getElementById(`projChevron_${targetKey}`); + const btnArrow = document.getElementById(`btnProjectArrow_${targetKey}`); if (!menu) return; - const willOpen = (typeof force === 'boolean') ? force : menu.classList.contains('hidden'); + const isOpen = !menu.classList.contains('hidden'); + closeAllProjectDropdowns(); - // Sluit alle andere geopende status dropdowns - closeAllProjectStatusDropdowns(targetKey); - - if (willOpen) { + if (!isOpen) { menu.classList.remove('hidden'); if (chevron) chevron.classList.add('rotate-180'); - if (btn) btn.setAttribute('aria-expanded', 'true'); + if (btnArrow) btnArrow.setAttribute('aria-expanded', 'true'); + + // Check of menu over de rechter rand van het scherm loopt + const rect = menu.getBoundingClientRect(); + if (rect.right > (window.innerWidth || document.documentElement.clientWidth) - 16) { + menu.classList.remove('left-0'); + menu.classList.add('right-0'); + } else { + menu.classList.remove('right-0'); + menu.classList.add('left-0'); + } + updateStatusCounts(); - } else { - menu.classList.add('hidden'); - if (chevron) chevron.classList.remove('rotate-180'); - if (btn) btn.setAttribute('aria-expanded', 'false'); } } -function closeAllProjectStatusDropdowns(exceptKey = '') { - const exceptNorm = (exceptKey || '').toUpperCase().trim(); - (projectStatusesData || []).forEach(proj => { - const pKey = (proj.key || '').toUpperCase().trim(); - if (pKey === exceptNorm) return; - const menu = document.getElementById(`statusDropdownMenu_${pKey}`); - const chevron = document.getElementById(`statusDropdownChevron_${pKey}`); - const btn = document.getElementById(`statusDropdownBtn_${pKey}`); - if (menu && !menu.classList.contains('hidden')) { +function closeAllProjectDropdowns(exceptKey = '') { + const exceptNorm = (exceptKey || '').trim(); + document.querySelectorAll('.project-dropdown-menu').forEach(menu => { + const mKey = menu.id.replace('projectDropdownMenu_', ''); + if (mKey === exceptNorm) return; + if (!menu.classList.contains('hidden')) { menu.classList.add('hidden'); + const chevron = document.getElementById(`projChevron_${mKey}`); if (chevron) chevron.classList.remove('rotate-180'); - if (btn) btn.setAttribute('aria-expanded', 'false'); + const btnArrow = document.getElementById(`btnProjectArrow_${mKey}`); + if (btnArrow) btnArrow.setAttribute('aria-expanded', 'false'); } }); } +// Backwards compatibility aliases +function toggleProjectStatusDropdown(projectKey, force) { toggleProjectDropdown(projectKey); } +function closeAllProjectStatusDropdowns(exceptKey) { closeAllProjectDropdowns(exceptKey); } +function toggleStatusDropdown(force) { toggleProjectDropdown(currentProject || 'all'); } +function updateProjectStatusDropdownVisibility() {} + // Sluit dropdowns bij klikken buiten dropdowns document.addEventListener('click', (e) => { - const isInside = e.target.closest('.project-status-dropdown-item'); - if (!isInside) { - closeAllProjectStatusDropdowns(); + if (!e.target.closest('.project-pill-container')) { + closeAllProjectDropdowns(); } }); -function toggleStatusDropdown(force) { - if (currentProject) { - toggleProjectStatusDropdown(currentProject, force); - } else if (projectStatusesData && projectStatusesData.length > 0) { - toggleProjectStatusDropdown(projectStatusesData[0].key, force); - } -} - function updateStatusCounts() { const all = getDeduplicatedIssues(); + // 1. Totaal en Toegewezen aan mij per project updaten in de dropdowns + const allAssignedCount = all.filter(iss => isIssueAssignedToMe(iss)).length; + const elAllAssigned = document.getElementById('projAssignedCount_all'); + if (elAllAssigned) elAllAssigned.innerText = allAssignedCount; + + const elAllTotal = document.getElementById('projDropdownTotal_all'); + if (elAllTotal) elAllTotal.innerText = `(${all.length})`; + + const chkAll = document.getElementById('chkOnlyAssigned_all'); + if (chkAll) chkAll.checked = projectOnlyAssigned.has('ALL'); + + const btnMijnAll = document.getElementById('btnDropdownMijn_all'); + if (btnMijnAll) { + if (projectOnlyAssigned.has('ALL')) { + btnMijnAll.className = "px-1.5 py-0.5 rounded font-bold bg-indigo-600 text-white transition cursor-pointer"; + } else { + btnMijnAll.className = "px-1.5 py-0.5 rounded font-bold text-indigo-600 dark:text-indigo-400 hover:bg-indigo-50 dark:hover:bg-indigo-950/60 transition cursor-pointer"; + } + } + (projectStatusesData || []).forEach(proj => { const pKey = (proj.key || '').toUpperCase().trim(); + let projIssues = all.filter(iss => { const k = getIssueProjectKey(iss); const pn = (iss.project || '').toUpperCase().trim(); @@ -1586,60 +1700,81 @@ function updateStatusCounts() { return pn.includes(pKey); }); - // Project filter meenemen indien actief - if (currentProject) { - projIssues = projIssues.filter(iss => isIssueInQueue(iss, currentProject)); + if (pKey === 'DX' || !['SD', 'IN', 'AAN', 'GEN'].includes(pKey)) { + projIssues = all.filter(iss => isIssueInQueue(iss, pKey)); } - // Toegewezen aan mij filter per project meenemen in de status tellingen - if (isProjectOnlyAssignedActive(currentProject, pKey)) { - projIssues = projIssues.filter(iss => isIssueAssignedToMe(iss)); + const projTotalCount = projIssues.length; + const projAssignedCount = projIssues.filter(iss => isIssueAssignedToMe(iss)).length; + + const elDropdownTotal = document.getElementById(`projDropdownTotal_${pKey}`); + if (elDropdownTotal) elDropdownTotal.innerText = `(${projTotalCount})`; + + const elAssignedCount = document.getElementById(`projAssignedCount_${pKey}`); + if (elAssignedCount) elAssignedCount.innerText = projAssignedCount; + + const chk = document.getElementById(`chkOnlyAssigned_${pKey}`); + if (chk) chk.checked = projectOnlyAssigned.has(pKey) || projectOnlyAssigned.has('ALL'); + + const btnMijn = document.getElementById(`btnDropdownMijn_${pKey}`); + if (btnMijn) { + if (projectOnlyAssigned.has(pKey) || projectOnlyAssigned.has('ALL')) { + btnMijn.className = "px-1.5 py-0.5 rounded font-bold bg-indigo-600 text-white transition cursor-pointer"; + } else { + btnMijn.className = "px-1.5 py-0.5 rounded font-bold text-indigo-600 dark:text-indigo-400 hover:bg-indigo-50 dark:hover:bg-indigo-950/60 transition cursor-pointer"; + } } - const projTotalEl = document.getElementById(`projTotal_${pKey}`); - if (projTotalEl) { - projTotalEl.innerText = `(${projIssues.length})`; + // Tellingen per status + let statusBaseList = projIssues; + if (projectOnlyAssigned.has(pKey) || projectOnlyAssigned.has('ALL')) { + statusBaseList = statusBaseList.filter(iss => isIssueAssignedToMe(iss)); } (proj.statuses || []).forEach(st => { const sNorm = (st.name || '').toLowerCase().trim(); const safeKey = `${pKey}_${sNorm.replace(/[^a-z0-9]/gi, '_')}`; - const el = document.getElementById(`statusCount_${safeKey}`); - if (el) { - const cnt = projIssues.filter(iss => (iss.status || '').toLowerCase().trim() === sNorm).length; - el.innerText = cnt; - } + const cnt = statusBaseList.filter(iss => (iss.status || '').toLowerCase().trim() === sNorm).length; + + const countEls = document.querySelectorAll(`.status-count-${safeKey}`); + countEls.forEach(el => el.innerText = cnt); + const singleEl = document.getElementById(`statusCount_${safeKey}`); + if (singleEl) singleEl.innerText = cnt; }); }); - updateAssignedPillState(); + updateProjectCounts(); + updateProjectPills(); } -function onStatusCheckboxChange(projectKey) { - const pNorm = (projectKey || '').toUpperCase().trim(); - const pKeyLower = pNorm.toLowerCase(); - const chks = document.querySelectorAll(`.status-checkbox-${pNorm}`); - - chks.forEach(chk => { - const val = chk.value.toLowerCase().trim(); - if (chk.checked) { - selectedStatuses.add(val); +function onStatusCheckboxChange(projectKey, changedVal, isChecked) { + if (changedVal) { + const normVal = changedVal.toLowerCase().trim(); + if (isChecked) { + selectedStatuses.add(normVal); } else { - selectedStatuses.delete(val); + selectedStatuses.delete(normVal); } - }); + // Sync checkboxes that have this same value across dropdowns + const chks = document.querySelectorAll('.status-checkbox'); + chks.forEach(chk => { + if (chk.value && chk.value.toLowerCase().trim() === normVal) { + chk.checked = isChecked; + } + }); + } try { localStorage.setItem('fociz_jira_selected_statuses', JSON.stringify([...selectedStatuses])); } catch (e) {} updateStatusDropdownLabel(); + updateStatusCounts(); applyFilters(); } function selectProjectStatuses(projectKey, selectAll) { const targetKey = (projectKey || '').toUpperCase().trim(); - const pKeyLower = targetKey.toLowerCase(); const chks = document.querySelectorAll(`.status-checkbox[data-project="${targetKey}"]`); chks.forEach(chk => { @@ -1657,6 +1792,7 @@ function selectProjectStatuses(projectKey, selectAll) { } catch (e) {} updateStatusDropdownLabel(); + updateStatusCounts(); applyFilters(); } @@ -1680,6 +1816,29 @@ function selectProjectActiveStatuses(projectKey) { } catch (e) {} updateStatusDropdownLabel(); + updateStatusCounts(); + applyFilters(); +} + +function selectAllActiveStatuses() { + const chks = document.querySelectorAll('.status-checkbox'); + chks.forEach(chk => { + const isDone = chk.getAttribute('data-is-done') === '1'; + chk.checked = !isDone; + const val = chk.value.toLowerCase().trim(); + if (!isDone) { + selectedStatuses.add(val); + } else { + selectedStatuses.delete(val); + } + }); + + try { + localStorage.setItem('fociz_jira_selected_statuses', JSON.stringify([...selectedStatuses])); + } catch (e) {} + + updateStatusDropdownLabel(); + updateStatusCounts(); applyFilters(); } @@ -1698,91 +1857,29 @@ function selectAllStatuses(selectAll) { } catch (e) {} updateStatusDropdownLabel(); + updateStatusCounts(); applyFilters(); } function updateStatusDropdownLabel() { let hasAnyCustomFilter = false; - (projectStatusesData || []).forEach(proj => { - const pKey = (proj.key || '').toUpperCase().trim(); - const pKeyLower = pKey.toLowerCase(); - const labelEl = document.getElementById(`statusDropdownLabel_${pKey}`); - const badgeEl = document.getElementById(`statusDropdownBadge_${pKey}`); - if (!labelEl) return; + if (currentProject !== '' || projectOnlyAssigned.size > 0 || (document.getElementById('jiraSearchInput')?.value || '').trim().length > 0) { + hasAnyCustomFilter = true; + } - const statuses = proj.statuses || []; - const total = statuses.length; - let selectedCount = 0; - let activeTotal = 0; - let activeSelected = 0; - let singleStatusName = ''; - - statuses.forEach(st => { - const sNorm = (st.name || '').toLowerCase().trim(); - const compound = `${pKeyLower}::${sNorm}`; - const cat = (st.status_category || '').toLowerCase().trim(); - const isDone = (cat === 'done' || isDoneStatus(sNorm)); - - if (!isDone) activeTotal++; - - if (selectedStatuses.has(compound) || selectedStatuses.has(sNorm)) { - selectedCount++; - singleStatusName = st.name; - if (!isDone) activeSelected++; - } - }); - - if (selectedCount < total) { - hasAnyCustomFilter = true; - } - - if (selectedCount === total || (total === 0 && selectedCount === 0)) { - labelEl.innerText = "Alle statussen"; - if (badgeEl) { - badgeEl.innerText = total; - badgeEl.className = "px-1.5 py-0.5 rounded-full text-[10px] font-bold bg-slate-100 dark:bg-slate-800 text-slate-500 dark:text-slate-400"; - } - } else if (selectedCount === 0) { - labelEl.innerText = "Geen"; - if (badgeEl) { - badgeEl.innerText = "0"; - badgeEl.className = "px-1.5 py-0.5 rounded-full text-[10px] font-bold bg-rose-100 dark:bg-rose-950/60 text-rose-700 dark:text-rose-300"; - } - } else if (selectedCount === activeTotal && activeSelected === activeTotal && activeTotal > 0) { - labelEl.innerText = "Actief"; - if (badgeEl) { - badgeEl.innerText = `${activeSelected}/${total}`; - badgeEl.className = "px-1.5 py-0.5 rounded-full text-[10px] font-bold bg-emerald-100 dark:bg-emerald-950/60 text-emerald-700 dark:text-emerald-300"; - } - } else if (selectedCount === 1) { - labelEl.innerText = singleStatusName; - if (badgeEl) { - badgeEl.innerText = `1/${total}`; - badgeEl.className = "px-1.5 py-0.5 rounded-full text-[10px] font-bold bg-indigo-100 dark:bg-indigo-950/60 text-indigo-700 dark:text-indigo-300"; - } - } else { - labelEl.innerText = `${selectedCount} statussen`; - if (badgeEl) { - badgeEl.innerText = `${selectedCount}/${total}`; - badgeEl.className = "px-1.5 py-0.5 rounded-full text-[10px] font-bold bg-indigo-100 dark:bg-indigo-950/60 text-indigo-700 dark:text-indigo-300"; - } - } - - // Toon indicator indien Toegewezen aan mij actief is voor dit project - const isAssignedOnly = projectOnlyAssigned.has(pKey) || projectOnlyAssigned.has('ALL'); - if (isAssignedOnly) { - hasAnyCustomFilter = true; - labelEl.innerText += " • Mijn"; - } - }); + const total = getTotalStatusCount(); + if (selectedStatuses.size > 0 && selectedStatuses.size < total) { + hasAnyCustomFilter = true; + } const btnReset = document.getElementById('btnResetFiltersInline'); if (btnReset) { - const isCustom = hasAnyCustomFilter || currentProject !== '' || projectOnlyAssigned.size > 0 || document.getElementById('jiraSearchInput')?.value; - if (isCustom) btnReset.classList.remove('hidden'); + if (hasAnyCustomFilter) btnReset.classList.remove('hidden'); else btnReset.classList.add('hidden'); } + + updateActiveFilterBadge(); } function initStatusSelection() { @@ -1844,49 +1941,119 @@ function initStatusSelection() { // Project selectie via pill tabs function selectProject(proj) { currentProject = (proj || '').trim(); - closeAllProjectStatusDropdowns(); + closeAllProjectDropdowns(); updateProjectPills(); - updateProjectStatusDropdownVisibility(); - updateAssignedPillState(); updateStatusCounts(); applyFilters(); } function updateProjectPills() { - const pills = document.querySelectorAll('.project-pill'); + const containers = document.querySelectorAll('.project-pill-container'); const curNorm = (currentProject || '').toUpperCase().trim(); - pills.forEach(p => { - const pKey = (p.getAttribute('data-project') || '').toUpperCase().trim(); + + containers.forEach(container => { + const pKey = (container.getAttribute('data-project') || '').toUpperCase().trim(); const isSelected = (pKey === curNorm); - + const btn = container.querySelector('.project-pill-btn'); + const arrow = container.querySelector('.project-pill-arrow'); + const dot = container.querySelector(`[id^="projFilterActiveDot_"]`); + + // Check of filters actief zijn voor dit project + let hasFilter = false; if (pKey === '') { - p.className = isSelected - ? "project-pill flex items-center space-x-1.5 px-2.5 sm:px-3 py-1.5 rounded-xl text-xs font-bold transition cursor-pointer bg-slate-900 dark:bg-white text-white dark:text-slate-900 shadow-xs flex-shrink-0" - : "project-pill flex items-center space-x-1.5 px-2.5 sm:px-3 py-1.5 rounded-xl text-xs font-bold transition cursor-pointer bg-slate-100 dark:bg-slate-800/80 text-slate-700 dark:text-slate-300 hover:bg-slate-200 dark:hover:bg-slate-700 border border-slate-200 dark:border-slate-700 flex-shrink-0"; - } else if (pKey === 'SD') { - p.className = isSelected - ? "project-pill flex items-center space-x-1.5 px-2.5 sm:px-3 py-1.5 rounded-xl text-xs font-bold transition cursor-pointer bg-sky-600 text-white shadow-xs flex-shrink-0" - : "project-pill flex items-center space-x-1.5 px-2.5 sm:px-3 py-1.5 rounded-xl text-xs font-bold transition cursor-pointer bg-sky-50 dark:bg-sky-950/40 text-sky-800 dark:text-sky-300 hover:bg-sky-100 dark:hover:bg-sky-900/60 border border-sky-300 dark:border-sky-800 flex-shrink-0"; - } else if (pKey === 'IN') { - p.className = isSelected - ? "project-pill flex items-center space-x-1.5 px-2.5 sm:px-3 py-1.5 rounded-xl text-xs font-bold transition cursor-pointer bg-amber-600 text-white shadow-xs flex-shrink-0" - : "project-pill flex items-center space-x-1.5 px-2.5 sm:px-3 py-1.5 rounded-xl text-xs font-bold transition cursor-pointer bg-amber-50 dark:bg-amber-950/40 text-amber-800 dark:text-amber-300 hover:bg-amber-100 dark:hover:bg-amber-900/60 border border-amber-300 dark:border-amber-800 flex-shrink-0"; - } else if (pKey === 'AAN') { - p.className = isSelected - ? "project-pill flex items-center space-x-1.5 px-2.5 sm:px-3 py-1.5 rounded-xl text-xs font-bold transition cursor-pointer bg-emerald-600 text-white shadow-xs flex-shrink-0" - : "project-pill flex items-center space-x-1.5 px-2.5 sm:px-3 py-1.5 rounded-xl text-xs font-bold transition cursor-pointer bg-emerald-50 dark:bg-emerald-950/40 text-emerald-800 dark:text-emerald-300 hover:bg-emerald-100 dark:hover:bg-emerald-900/60 border border-emerald-300 dark:border-emerald-800 flex-shrink-0"; - } else if (pKey === 'GEN') { - p.className = isSelected - ? "project-pill flex items-center space-x-1.5 px-2.5 sm:px-3 py-1.5 rounded-xl text-xs font-bold transition cursor-pointer bg-indigo-600 text-white shadow-xs flex-shrink-0" - : "project-pill flex items-center space-x-1.5 px-2.5 sm:px-3 py-1.5 rounded-xl text-xs font-bold transition cursor-pointer bg-indigo-50 dark:bg-indigo-950/40 text-indigo-800 dark:text-indigo-300 hover:bg-indigo-100 dark:hover:bg-indigo-900/60 border border-indigo-300 dark:border-indigo-800 flex-shrink-0"; - } else if (pKey === 'DX') { - p.className = isSelected - ? "project-pill flex items-center space-x-1.5 px-2.5 sm:px-3 py-1.5 rounded-xl text-xs font-bold transition cursor-pointer bg-amber-600 text-white shadow-xs flex-shrink-0" - : "project-pill flex items-center space-x-1.5 px-2.5 sm:px-3 py-1.5 rounded-xl text-xs font-bold transition cursor-pointer bg-amber-50 dark:bg-amber-950/40 text-amber-800 dark:text-amber-300 hover:bg-amber-100 dark:hover:bg-amber-900/60 border border-amber-300 dark:border-amber-800 flex-shrink-0"; + const isAssigned = projectOnlyAssigned.has('ALL'); + const total = getTotalStatusCount(); + const isCustomStatus = (selectedStatuses.size > 0 && selectedStatuses.size < total); + hasFilter = isAssigned || isCustomStatus; } else { - p.className = isSelected - ? "project-pill flex items-center space-x-1.5 px-2.5 sm:px-3 py-1.5 rounded-xl text-xs font-bold transition cursor-pointer bg-indigo-600 text-white shadow-xs flex-shrink-0" - : "project-pill flex items-center space-x-1.5 px-2.5 sm:px-3 py-1.5 rounded-xl text-xs font-bold transition cursor-pointer bg-white dark:bg-slate-800 text-slate-700 dark:text-slate-300 hover:bg-slate-200 dark:hover:bg-slate-700 border border-slate-200 dark:border-slate-700 flex-shrink-0"; + const isAssigned = projectOnlyAssigned.has(pKey) || projectOnlyAssigned.has('ALL'); + const projObj = (projectStatusesData || []).find(p => (p.key || '').toUpperCase().trim() === pKey); + let isCustomStatus = false; + if (projObj && (projObj.statuses || []).length > 0) { + const pKeyLower = pKey.toLowerCase(); + const totalProjStatuses = projObj.statuses.length; + let checkedCount = 0; + projObj.statuses.forEach(st => { + const sNorm = (st.name || '').toLowerCase().trim(); + const compound = `${pKeyLower}::${sNorm}`; + if (selectedStatuses.has(compound) || selectedStatuses.has(sNorm)) { + checkedCount++; + } + }); + if (checkedCount < totalProjStatuses) { + isCustomStatus = true; + } + } + hasFilter = isAssigned || isCustomStatus; + } + + if (dot) { + if (hasFilter) { + dot.classList.remove('hidden'); + } else { + dot.classList.add('hidden'); + } + } + + if (!btn || !arrow) return; + + // Pas styling toe op de linker knop en rechter pijltjesknop + if (pKey === '') { + if (isSelected) { + btn.className = "project-pill-btn flex items-center space-x-1.5 pl-2.5 sm:pl-3 pr-2 py-1.5 rounded-l-xl text-xs font-bold transition cursor-pointer bg-slate-900 dark:bg-white text-white dark:text-slate-900"; + arrow.className = "project-pill-arrow px-2 py-1.5 flex items-center justify-center rounded-r-xl border-l border-white/20 dark:border-slate-800 bg-slate-900 dark:bg-white text-white dark:text-slate-900 hover:bg-slate-800 dark:hover:bg-slate-100 transition cursor-pointer relative"; + } else { + btn.className = "project-pill-btn flex items-center space-x-1.5 pl-2.5 sm:pl-3 pr-2 py-1.5 rounded-l-xl text-xs font-bold transition cursor-pointer bg-slate-100 dark:bg-slate-800/80 text-slate-700 dark:text-slate-300 hover:bg-slate-200 dark:hover:bg-slate-700 border border-slate-200 dark:border-slate-700 border-r-0"; + arrow.className = "project-pill-arrow px-2 py-1.5 flex items-center justify-center rounded-r-xl border border-slate-200 dark:border-slate-700 border-l-slate-300 dark:border-l-slate-600 bg-slate-100 dark:bg-slate-800/80 text-slate-700 dark:text-slate-300 hover:bg-slate-200 dark:hover:bg-slate-700 transition cursor-pointer relative"; + } + } else if (pKey === 'SD') { + if (isSelected) { + btn.className = "project-pill-btn flex items-center space-x-1.5 pl-2.5 sm:pl-3 pr-2 py-1.5 rounded-l-xl text-xs font-bold transition cursor-pointer bg-sky-600 text-white shadow-xs"; + arrow.className = "project-pill-arrow px-2 py-1.5 flex items-center justify-center rounded-r-xl border-l border-sky-500 bg-sky-600 text-white hover:bg-sky-700 transition cursor-pointer relative"; + } else { + btn.className = "project-pill-btn flex items-center space-x-1.5 pl-2.5 sm:pl-3 pr-2 py-1.5 rounded-l-xl text-xs font-bold transition cursor-pointer bg-sky-50 dark:bg-sky-950/40 text-sky-800 dark:text-sky-300 hover:bg-sky-100 dark:hover:bg-sky-900/60 border border-sky-300 dark:border-sky-800 border-r-0"; + arrow.className = "project-pill-arrow px-2 py-1.5 flex items-center justify-center rounded-r-xl border border-sky-300 dark:border-sky-800 border-l-sky-200 dark:border-l-sky-800 bg-sky-50 dark:bg-sky-950/40 text-sky-700 dark:text-sky-300 hover:bg-sky-100 dark:hover:bg-sky-900/60 transition cursor-pointer relative"; + } + } else if (pKey === 'GEN') { + if (isSelected) { + btn.className = "project-pill-btn flex items-center space-x-1.5 pl-2.5 sm:pl-3 pr-2 py-1.5 rounded-l-xl text-xs font-bold transition cursor-pointer bg-indigo-600 text-white shadow-xs"; + arrow.className = "project-pill-arrow px-2 py-1.5 flex items-center justify-center rounded-r-xl border-l border-indigo-500 bg-indigo-600 text-white hover:bg-indigo-700 transition cursor-pointer relative"; + } else { + btn.className = "project-pill-btn flex items-center space-x-1.5 pl-2.5 sm:pl-3 pr-2 py-1.5 rounded-l-xl text-xs font-bold transition cursor-pointer bg-indigo-50 dark:bg-indigo-950/40 text-indigo-800 dark:text-indigo-300 hover:bg-indigo-100 dark:hover:bg-indigo-900/60 border border-indigo-300 dark:border-indigo-800 border-r-0"; + arrow.className = "project-pill-arrow px-2 py-1.5 flex items-center justify-center rounded-r-xl border border-indigo-300 dark:border-indigo-800 border-l-indigo-200 dark:border-l-indigo-800 bg-indigo-50 dark:bg-indigo-950/40 text-indigo-700 dark:text-indigo-300 hover:bg-indigo-100 dark:hover:bg-indigo-900/60 transition cursor-pointer relative"; + } + } else if (pKey === 'IN') { + if (isSelected) { + btn.className = "project-pill-btn flex items-center space-x-1.5 pl-2.5 sm:pl-3 pr-2 py-1.5 rounded-l-xl text-xs font-bold transition cursor-pointer bg-amber-600 text-white shadow-xs"; + arrow.className = "project-pill-arrow px-2 py-1.5 flex items-center justify-center rounded-r-xl border-l border-amber-500 bg-amber-600 text-white hover:bg-amber-700 transition cursor-pointer relative"; + } else { + btn.className = "project-pill-btn flex items-center space-x-1.5 pl-2.5 sm:pl-3 pr-2 py-1.5 rounded-l-xl text-xs font-bold transition cursor-pointer bg-amber-50 dark:bg-amber-950/40 text-amber-800 dark:text-amber-300 hover:bg-amber-100 dark:hover:bg-amber-900/60 border border-amber-300 dark:border-amber-800 border-r-0"; + arrow.className = "project-pill-arrow px-2 py-1.5 flex items-center justify-center rounded-r-xl border border-amber-300 dark:border-amber-800 border-l-amber-200 dark:border-l-amber-800 bg-amber-50 dark:bg-amber-950/40 text-amber-700 dark:text-amber-300 hover:bg-amber-100 dark:hover:bg-amber-900/60 transition cursor-pointer relative"; + } + } else if (pKey === 'AAN') { + if (isSelected) { + btn.className = "project-pill-btn flex items-center space-x-1.5 pl-2.5 sm:pl-3 pr-2 py-1.5 rounded-l-xl text-xs font-bold transition cursor-pointer bg-emerald-600 text-white shadow-xs"; + arrow.className = "project-pill-arrow px-2 py-1.5 flex items-center justify-center rounded-r-xl border-l border-emerald-500 bg-emerald-600 text-white hover:bg-emerald-700 transition cursor-pointer relative"; + } else { + btn.className = "project-pill-btn flex items-center space-x-1.5 pl-2.5 sm:pl-3 pr-2 py-1.5 rounded-l-xl text-xs font-bold transition cursor-pointer bg-emerald-50 dark:bg-emerald-950/40 text-emerald-800 dark:text-emerald-300 hover:bg-emerald-100 dark:hover:bg-emerald-900/60 border border-emerald-300 dark:border-emerald-800 border-r-0"; + arrow.className = "project-pill-arrow px-2 py-1.5 flex items-center justify-center rounded-r-xl border border-emerald-300 dark:border-emerald-800 border-l-emerald-200 dark:border-l-emerald-800 bg-emerald-50 dark:bg-emerald-950/40 text-emerald-700 dark:text-emerald-300 hover:bg-emerald-100 dark:hover:bg-emerald-900/60 transition cursor-pointer relative"; + } + } else if (pKey === 'DX') { + if (isSelected) { + btn.className = "project-pill-btn flex items-center space-x-1.5 pl-2.5 sm:pl-3 pr-2 py-1.5 rounded-l-xl text-xs font-bold transition cursor-pointer bg-amber-600 text-white shadow-xs"; + arrow.className = "project-pill-arrow px-2 py-1.5 flex items-center justify-center rounded-r-xl border-l border-amber-500 bg-amber-600 text-white hover:bg-amber-700 transition cursor-pointer relative"; + } else { + btn.className = "project-pill-btn flex items-center space-x-1.5 pl-2.5 sm:pl-3 pr-2 py-1.5 rounded-l-xl text-xs font-bold transition cursor-pointer bg-amber-50 dark:bg-amber-950/40 text-amber-800 dark:text-amber-300 hover:bg-amber-100 dark:hover:bg-amber-900/60 border border-amber-300 dark:border-amber-800 border-r-0"; + arrow.className = "project-pill-arrow px-2 py-1.5 flex items-center justify-center rounded-r-xl border border-amber-300 dark:border-amber-800 border-l-amber-200 dark:border-l-amber-800 bg-amber-50 dark:bg-amber-950/40 text-amber-700 dark:text-amber-300 hover:bg-amber-100 dark:hover:bg-amber-900/60 transition cursor-pointer relative"; + } + } else { + if (isSelected) { + btn.className = "project-pill-btn flex items-center space-x-1.5 pl-2.5 sm:pl-3 pr-2 py-1.5 rounded-l-xl text-xs font-bold transition cursor-pointer bg-indigo-600 text-white shadow-xs"; + arrow.className = "project-pill-arrow px-2 py-1.5 flex items-center justify-center rounded-r-xl border-l border-indigo-500 bg-indigo-600 text-white hover:bg-indigo-700 transition cursor-pointer relative"; + } else { + btn.className = "project-pill-btn flex items-center space-x-1.5 pl-2.5 sm:pl-3 pr-2 py-1.5 rounded-l-xl text-xs font-bold transition cursor-pointer bg-white dark:bg-slate-800 text-slate-700 dark:text-slate-300 hover:bg-slate-100 dark:hover:bg-slate-700 border border-slate-200 dark:border-slate-700 border-r-0"; + arrow.className = "project-pill-arrow px-2 py-1.5 flex items-center justify-center rounded-r-xl border border-slate-200 dark:border-slate-700 border-l-slate-200 dark:border-l-slate-700 bg-white dark:bg-slate-800 text-slate-600 dark:text-slate-300 hover:bg-slate-100 dark:hover:bg-slate-700 transition cursor-pointer relative"; + } } }); } @@ -2170,8 +2337,8 @@ function updateProjectCounts() { elAll.innerText = allFiltered.length; } - const pills = document.querySelectorAll('.project-pill'); - pills.forEach(p => { + const containers = document.querySelectorAll('.project-pill-container, .project-pill'); + containers.forEach(p => { const pKey = p.getAttribute('data-project'); if (!pKey) return; const el = document.getElementById(`projCount_${pKey}`);