diff --git a/admin.php b/admin.php index 53a4ffd..6da445e 100644 --- a/admin.php +++ b/admin.php @@ -1322,15 +1322,34 @@ require_once __DIR__ . '/includes/header.php';
diff --git a/jira.php b/jira.php index e06b75d..4a33f9d 100644 --- a/jira.php +++ b/jira.php @@ -56,7 +56,97 @@ foreach ($boards as &$b) { } unset($b); -// Verzamel unieke projecten en types voor filter dropdowns en project switcher +// Ophalen van actieve wachtrijfilters en toewijzingen van de ingelogde gebruiker +$queueFilters = $jiraData['queue_filters'] ?? jira_get_queue_filters(true); +$defaultQueueFilter = $jiraData['default_queue_filter'] ?? jira_get_user_queue_preference($userId); +$userQueueFilters = $jiraData['user_queue_filters'] ?? jira_get_user_queue_preferences($userId); +$userQueueFilters = array_values(array_filter(array_map('strtoupper', array_map('trim', $userQueueFilters ?? [])))); +$jiraData['user_queue_filters'] = $userQueueFilters; + +// Hulpfunctie om te controleren of een ticket behoort tot een wachtrij / project +$issueMatchesQueue = function(array $iss, string $code) use ($queueFilters): bool { + $code = strtoupper(trim($code)); + if (empty($code)) return true; + + $pKey = strtoupper(trim($iss['project_key'] ?? '')); + if (empty($pKey) && !empty($iss['key']) && preg_match('/^([A-Z0-9]+)-/i', $iss['key'], $m)) { + $pKey = strtoupper($m[1]); + } + $pName = strtolower(trim($iss['project'] ?? '')); + $ol = strtolower(trim($iss['onderwerp_logging'] ?? '')); + $sum = strtolower(trim($iss['summary'] ?? '')); + + // 1. Zoek geconfigureerd filter in database + foreach ($queueFilters as $qf) { + if (strtoupper(trim($qf['filter_code'] ?? '')) === $code) { + if (!empty($qf['project_key'])) { + $filterPKey = strtoupper(trim($qf['project_key'])); + if ($pKey) { + if ($pKey !== $filterPKey) return false; + } elseif (strpos($pName, strtolower($filterPKey)) === false) { + return false; + } + } + if (!empty($qf['jql_clause'])) { + $jql = $qf['jql_clause']; + if (preg_match('/onderwerp\s+logging.*?in\s*\(([^)]+)\)/i', $jql, $jm)) { + $terms = array_map(function($t) { return trim($t, " '\"\t\n\r\0\x0B"); }, explode(',', strtolower($jm[1]))); + $found = false; + foreach ($terms as $t) { + if ($t !== '' && (strpos($ol, $t) !== false || strpos($sum, $t) !== false)) { + $found = true; + break; + } + } + if (!$found) return false; + } + } + return true; + } + } + + // 2. Fallbacks voor standaard wachtrijen/projecten + if ($code === 'DX') { + $isSd = ($pKey === 'SD') || strpos($pName, 'servicedesk') !== false; + if (!$isSd) return false; + $dxTerms = ['koppeling', 'sso', 'endpoint', 'rest', 'datadump', 'uwv', 'migratie']; + foreach ($dxTerms as $t) { + if (strpos($ol, $t) !== false || strpos($sum, $t) !== false) return true; + } + return false; + } + if ($code === 'SD') { + return ($pKey === 'SD') || strpos($pName, 'servicedesk') !== false; + } + if ($code === 'IN') { + return ($pKey === 'IN') || strpos($pName, 'interface') !== false; + } + if ($code === 'AAN') { + return ($pKey === 'AAN') || strpos($pName, 'aanvraag') !== false; + } + if ($code === 'GEN') { + return ($pKey === 'GEN') || strpos($pName, 'general') !== false || strpos($pName, 'algemeen') !== false; + } + + return ($pKey === $code); +}; + +// Als er specifieke projecten aan de gebruiker zijn toegewezen: verberg tickets van niet-toegewezen projecten +if (!empty($userQueueFilters)) { + $filterByAssigned = function(array $issues) use ($userQueueFilters, $issueMatchesQueue): array { + return array_values(array_filter($issues, function($iss) use ($userQueueFilters, $issueMatchesQueue) { + foreach ($userQueueFilters as $qCode) { + if ($issueMatchesQueue($iss, $qCode)) return true; + } + return false; + })); + }; + $assignedIssues = $filterByAssigned($assignedIssues); + $teamIssues = $filterByAssigned($teamIssues); + $jiraData['assigned_issues'] = $assignedIssues; + $jiraData['team_issues'] = $teamIssues; +} + // Verzamel unieke projecten en types voor filter dropdowns en project switcher $allIssues = array_merge($assignedIssues, $teamIssues); $allProjects = []; @@ -198,10 +288,6 @@ $allStatusesList = array_values($allStatusesMap); ksort($allProjects); ksort($allTypes); -$queueFilters = $jiraData['queue_filters'] ?? jira_get_queue_filters(true); -$defaultQueueFilter = $jiraData['default_queue_filter'] ?? jira_get_user_queue_preference($userId); -$userQueueFilters = $jiraData['user_queue_filters'] ?? jira_get_user_queue_preferences($userId); - $jiraDataJson = json_encode($jiraData, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP); $boardsJson = json_encode($boards, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP); $projectStatusesJson = json_encode($projectStatusesData, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP); @@ -408,7 +494,7 @@ $statusesJson = json_encode($allStatusesList, JSON_HEX_TAG | JSON_HEX_APOS | JSO
- +
@@ -416,7 +502,7 @@ $statusesJson = json_encode($allStatusesList, JSON_HEX_TAG | JSON_HEX_APOS | JSO
Projecten -

Direct filteren op projectcode

+

Direct schakelen tussen jouw toegewezen projecten

@@ -427,108 +513,74 @@ $statusesJson = json_encode($allStatusesList, JSON_HEX_TAG | JSON_HEX_APOS | JSO - - - - - - - - - - - - - $pData): - $pKeyNorm = strtoupper($pKey); - $pDataKeyNorm = strtoupper($pData['key'] ?? ''); - if (in_array($pKeyNorm, ['SD', 'IN', 'AAN', 'GEN']) || in_array($pDataKeyNorm, ['SD', 'IN', 'AAN', 'GEN'])) continue; - $displayKey = !empty($pData['key']) ? $pData['key'] : $pKey; + - - -
-
- -
-
-
- -
-
- Wachtrij -

Filter op gekoppelde wachtrijen

-
-
- -
- - - - - + + + + - + + + + - + + + + - + + + + - + + + + - + @@ -963,7 +1015,7 @@ const projectStatusesData = ; const allJiraStatuses = ; let currentProject = ''; -let currentQueue = (jiraData.user_queue_filters && jiraData.user_queue_filters.length > 0) ? 'MY_QUEUES' : (jiraData.default_queue_filter || ''); +let currentQueue = ''; let selectedStatuses = new Set(); let activeUrgentOnly = false; let expandedKeys = new Set(); @@ -975,9 +1027,18 @@ function getDeduplicatedIssues() { const team = jiraData.team_issues || []; const seen = new Set(); const list = []; + const myQueues = (jiraData.user_queue_filters || []).map(q => String(q).toUpperCase().trim()); + const hasAssignments = myQueues.length > 0; + [...assigned, ...team].forEach(iss => { if (iss && iss.key && !seen.has(iss.key)) { seen.add(iss.key); + // Als er specifieke projecten aan de gebruiker zijn toegewezen: + // verberg tickets die niet tot minstens één van de toegewezen projecten behoren + if (hasAssignments) { + const isAllowed = myQueues.some(q => isIssueInQueue(iss, q)); + if (!isAllowed) return; + } list.push(iss); } }); @@ -1009,8 +1070,15 @@ function isInProgressStatus(st, sc) { // Bereken statussen en KPI statistieken voor de drawer function calculateKpis() { const all = getDeduplicatedIssues(); - const assigned = jiraData.assigned_issues || []; - const team = jiraData.team_issues || []; + const myQueues = (jiraData.user_queue_filters || []).map(q => String(q).toUpperCase().trim()); + const hasAssignments = myQueues.length > 0; + + let assigned = jiraData.assigned_issues || []; + let team = jiraData.team_issues || []; + if (hasAssignments) { + assigned = assigned.filter(iss => myQueues.some(q => isIssueInQueue(iss, q))); + team = team.filter(iss => myQueues.some(q => isIssueInQueue(iss, q))); + } let inProgressCount = 0; let highPriorityCount = 0; @@ -1039,7 +1107,6 @@ function calculateKpis() { if (elStatHigh) elStatHigh.innerText = highPriorityCount; updateProjectCounts(); - updateQueueCounts(); } // Drawer open/dicht @@ -1067,7 +1134,6 @@ function updateActiveFilterBadge() { let activeCount = 0; if (currentProject) activeCount++; - if (currentQueue && currentQueue !== 'MY_QUEUES') activeCount++; if (document.getElementById('filterType')?.value) activeCount++; if (document.getElementById('filterPriority')?.value) activeCount++; const total = getTotalStatusCount(); @@ -1233,9 +1299,9 @@ function updateStatusCounts() { return pn.includes(pKey); }); - // Wachtrij filter meenemen indien actief - if (currentQueue) { - projIssues = projIssues.filter(iss => isIssueInQueue(iss, currentQueue)); + // Project filter meenemen indien actief + if (currentProject) { + projIssues = projIssues.filter(iss => isIssueInQueue(iss, currentProject)); } const projTotalEl = document.getElementById(`projTotal_${pKey}`); @@ -1383,7 +1449,7 @@ function updateStatusDropdownLabel() { const btnReset = document.getElementById('btnResetFiltersInline'); if (btnReset) { - const isCustom = selected < total || currentProject !== '' || (currentQueue !== '' && currentQueue !== 'MY_QUEUES') || document.getElementById('jiraSearchInput')?.value; + const isCustom = selected < total || currentProject !== '' || document.getElementById('jiraSearchInput')?.value; if (isCustom) btnReset.classList.remove('hidden'); else btnReset.classList.add('hidden'); } @@ -1480,6 +1546,10 @@ function updateProjectPills() { 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"; } 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" @@ -1742,22 +1812,11 @@ function isIssueInQueue(iss, qCode) { } function selectQueueFilter(code) { - currentQueue = code; - updateQueuePills(); - applyFilters(); + selectProject(code); } function updateQueuePills() { - const pills = document.querySelectorAll('.queue-pill'); - pills.forEach(p => { - const qCode = p.getAttribute('data-queue') || ''; - const isSelected = (qCode === currentQueue); - if (isSelected) { - p.className = "queue-pill flex items-center space-x-1.5 px-3 py-1.5 rounded-xl text-xs font-bold transition cursor-pointer bg-indigo-600 text-white shadow-sm shadow-indigo-600/20 flex-shrink-0"; - } else { - p.className = "queue-pill flex items-center space-x-1.5 px-3 py-1.5 rounded-xl text-xs font-bold transition cursor-pointer bg-white dark:bg-slate-800/80 text-slate-700 dark:text-slate-300 hover:bg-slate-100 dark:hover:bg-slate-700 border border-slate-200/80 dark:border-slate-700/80 flex-shrink-0"; - } - }); + updateProjectPills(); } function updateProjectCounts() { @@ -1772,66 +1831,19 @@ function updateProjectCounts() { const elAll = document.getElementById('projCount_all'); if (elAll) elAll.innerText = currentList.length; - ['SD', 'IN', 'AAN', 'GEN'].forEach(key => { - const el = document.getElementById(`projCount_${key}`); + const pills = document.querySelectorAll('.project-pill'); + pills.forEach(p => { + const pKey = p.getAttribute('data-project'); + if (!pKey) return; + const el = document.getElementById(`projCount_${pKey}`); if (el) { - const cnt = currentList.filter(iss => { - const pk = getIssueProjectKey(iss); - const pn = (iss.project || '').toLowerCase().trim(); - if (pk) return pk === key; - if (key === 'SD' && pn.includes('servicedesk')) return true; - if (key === 'IN' && pn.includes('interface')) return true; - if (key === 'AAN' && pn.includes('aanvraag')) return true; - if (key === 'GEN' && (pn.includes('general') || pn.includes('algemeen'))) return true; - return false; - }).length; - el.innerText = cnt; + el.innerText = currentList.filter(iss => isIssueInQueue(iss, pKey)).length; } }); } function updateQueueCounts() { - const all = getDeduplicatedIssues(); - const total = getTotalStatusCount(); - let currentList = all; - - if (selectedStatuses.size > 0 && selectedStatuses.size < total) { - currentList = currentList.filter(iss => isIssueStatusSelected(iss)); - } - - if (currentProject) { - const pNorm = currentProject.trim().toUpperCase(); - currentList = currentList.filter(iss => { - const pk = getIssueProjectKey(iss); - const pn = (iss.project || '').trim().toLowerCase(); - if (pk) return pk === pNorm; - return pn.includes(pNorm.toLowerCase()); - }); - } - - const elAll = document.getElementById('queueCount_all'); - if (elAll) elAll.innerText = currentList.length; - - const elMy = document.getElementById('queueCount_MY_QUEUES'); - if (elMy) { - elMy.innerText = currentList.filter(iss => isIssueInQueue(iss, 'MY_QUEUES')).length; - } - - ['SD', 'IN', 'AAN', 'GEN', 'DX'].forEach(code => { - const el = document.getElementById(`queueCount_${code}`); - if (el) { - el.innerText = currentList.filter(iss => isIssueInQueue(iss, code)).length; - } - }); - - (jiraData.queue_filters || []).forEach(f => { - if (!['SD', 'IN', 'AAN', 'GEN', 'DX'].includes(f.filter_code)) { - const el = document.getElementById(`queueCount_${f.filter_code}`); - if (el) { - el.innerText = currentList.filter(iss => isIssueInQueue(iss, f.filter_code)).length; - } - } - }); + updateProjectCounts(); } function filterByMyTickets() { @@ -1914,24 +1926,9 @@ function applyFilters() { } } - // 2. Project Filter (SD, IN, AAN, GEN of ander) + // 2. Project Filter (SD, GEN, IN, AAN, DX of ander) if (projectVal) { - if (issPKey) { - if (issPKey !== projectVal) return false; - } else { - let matchesProj = false; - if (projectVal === 'SD' && issPName.includes('servicedesk')) matchesProj = true; - else if (projectVal === 'IN' && issPName.includes('interface')) matchesProj = true; - else if (projectVal === 'AAN' && issPName.includes('aanvraag')) matchesProj = true; - else if (projectVal === 'GEN' && (issPName.includes('general') || issPName.includes('algemeen'))) matchesProj = true; - else if (issPName === projectVal.toLowerCase()) matchesProj = true; - if (!matchesProj) return false; - } - } - - // 3. Wachtrijfilter (MY_QUEUES, SD, IN, AAN, GEN, DX) - if (currentQueue) { - if (!isIssueInQueue(iss, currentQueue)) { + if (!isIssueInQueue(iss, projectVal)) { return false; } } @@ -2835,10 +2832,6 @@ document.addEventListener('DOMContentLoaded', () => { initStatusSelection(); calculateKpis(); updateProjectPills(); - updateQueueCounts(); - if (currentQueue) { - updateQueuePills(); - } applyFilters(); // Luister naar live events voor nieuwe/gewijzigde Jira tickets