diff --git a/jira.php b/jira.php index 16162b4..fa4bbc1 100644 --- a/jira.php +++ b/jira.php @@ -93,107 +93,21 @@ foreach ($allIssues as $iss) { if (!empty($iss['issue_type'])) { $allTypes[$iss['issue_type']] = $iss['issue_type']; } -} - -// Bepaal welke projecten getoond worden: alleen projecten die tickets hebben, plus SD/IN/AAN/GEN -// Geen statussen van projecten die niet gebruikt worden! -$activeProjectKeys = []; -foreach ($coreProjects as $cKey => $cMeta) { - if (isset($usedProjectKeys[$cKey]) || !empty($projectCounts[$cMeta['name']]) || !empty($projectCounts[$cKey])) { - $activeProjectKeys[$cKey] = true; + if (!empty($iss['status'])) { + $allStatuses[$iss['status']] = $iss['status']; } } -// Ook eventuele andere gebruikte projecten toevoegen die tickets hebben in $allIssues -foreach ($usedProjectKeys as $uKey => $val) { - $activeProjectKeys[$uKey] = true; -} -// Fallback als er nog geen tickets geladen zijn (bijv. lege sync), toon minimaal de 4 core projecten -if (empty($activeProjectKeys)) { - $activeProjectKeys = array_fill_keys(['SD', 'IN', 'AAN', 'GEN'], true); -} -// Haal projectstatussen op uit jiraData -$rawProjectStatuses = $jiraData['project_statuses'] ?? []; - -// Bouw gestructureerde lijst van statussen gegroepeerd per project -$projectStatusesData = []; -$allStatusesMap = []; - -foreach (array_keys($activeProjectKeys) as $pKey) { - $pName = $coreProjects[$pKey]['name'] ?? ($allProjects[$pKey]['name'] ?? $pKey); - $pColor = $coreProjects[$pKey]['color'] ?? 'slate'; - - $pStatuses = []; - - // 1. Statussen uit Jira API voor dit project - if (!empty($rawProjectStatuses[$pKey])) { - foreach ($rawProjectStatuses[$pKey] as $st) { - $sName = trim($st['name'] ?? ''); - if (empty($sName)) continue; - $sKey = strtolower($sName); - $pStatuses[$sKey] = [ - 'name' => $sName, - 'status_category' => $st['status_category'] ?? 'indeterminate', - 'category_name' => $st['category_name'] ?? '', - ]; +// Statussen uitbreiden met officiële Jira instantie statussen +$availableStatuses = $jiraData['available_statuses'] ?? []; +if (!empty($availableStatuses)) { + foreach ($availableStatuses as $st) { + if (!empty($st['name'])) { + $allStatuses[$st['name']] = $st['name']; } } - - // 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]); - } - if ($issPKey === $pKey && !empty($iss['status'])) { - $sName = trim($iss['status']); - $sKey = strtolower($sName); - if (!isset($pStatuses[$sKey])) { - $pStatuses[$sKey] = [ - 'name' => $sName, - 'status_category' => $iss['status_category'] ?? 'indeterminate', - 'category_name' => '', - ]; - } - } - } - - // Fallback als een project nog geen statussen heeft - if (empty($pStatuses)) { - $fallback = [ - ['name' => 'To Do', 'status_category' => 'new'], - ['name' => 'In behandeling', 'status_category' => 'indeterminate'], - ['name' => 'Gesloten', 'status_category' => 'done'], - ]; - foreach ($fallback as $fb) { - $pStatuses[strtolower($fb['name'])] = $fb; - } - } - - // Sorteren: To Do / Nieuw (1), In behandeling / Uitvoering (2), Gesloten / Gereed (3) - uasort($pStatuses, function($a, $b) { - $order = ['new' => 1, 'indeterminate' => 2, 'done' => 3]; - $oa = $order[$a['status_category'] ?? 'indeterminate'] ?? 2; - $ob = $order[$b['status_category'] ?? 'indeterminate'] ?? 2; - if ($oa !== $ob) return $oa - $ob; - return strcasecmp($a['name'], $b['name']); - }); - - $statusList = array_values($pStatuses); - foreach ($statusList as $st) { - $allStatusesMap[strtolower($st['name'])] = $st; - } - - $projectStatusesData[] = [ - 'key' => $pKey, - 'name' => $pName, - 'color' => $pColor, - 'statuses' => $statusList, - ]; } -$allStatusesList = array_values($allStatusesMap); - ksort($allProjects); ksort($allTypes); @@ -203,8 +117,6 @@ $userQueueFilters = $jiraData['user_queue_filters'] ?? jira_get_user_queue_prefe $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); -$statusesJson = json_encode($allStatusesList, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP); ?>
@@ -534,55 +446,51 @@ $statusesJson = json_encode($allStatusesList, JSON_HEX_TAG | JSON_HEX_APOS | JSO
- -
- - -
-
- + +
+ +
+ - - -
+ + + +
+ + +
+ + - • - -
-
-
- `; - - 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_${pIdx}_${sIdx}`; - - html += ` - - `; - }); - - html += ` -
-
- `; - }); - - container.innerHTML = html; - updateStatusCounts(); -} - -function updateStatusCounts() { - const all = getDeduplicatedIssues(); - - (projectStatusesData || []).forEach(proj => { - const pKey = (proj.key || '').toUpperCase().trim(); - let projIssues = all.filter(iss => { - let k = (iss.project_key || '').toUpperCase().trim(); - if (!k && iss.key) { - const parts = iss.key.split('-'); - if (parts.length > 1) k = parts[0].toUpperCase().trim(); - } - const pn = (iss.project || '').toUpperCase().trim(); - return k === pKey || pn.includes(pKey); - }); - - // Wachtrij filter meenemen indien actief - if (currentQueue) { - projIssues = projIssues.filter(iss => isIssueInQueue(iss, currentQueue)); - } - - const projTotalEl = document.getElementById(`projTotal_${pKey}`); - if (projTotalEl) { - projTotalEl.innerText = `(${projIssues.length})`; - } - - (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; - } - }); - }); -} - -function toggleStatusDropdown(force) { - const menu = document.getElementById('statusDropdownMenu'); - const chevron = document.getElementById('statusDropdownChevron'); - const btn = document.getElementById('statusDropdownBtn'); - if (!menu) return; - - const willOpen = (typeof force === 'boolean') ? force : menu.classList.contains('hidden'); - if (willOpen) { - menu.classList.remove('hidden'); - if (chevron) chevron.classList.add('rotate-180'); - if (btn) btn.setAttribute('aria-expanded', 'true'); - updateStatusCounts(); - } else { - menu.classList.add('hidden'); - if (chevron) chevron.classList.remove('rotate-180'); - if (btn) btn.setAttribute('aria-expanded', 'false'); - } -} - -document.addEventListener('click', (e) => { - const wrapper = document.getElementById('statusDropdownWrapper'); - if (wrapper && !wrapper.contains(e.target)) { - toggleStatusDropdown(false); - } -}); - -function onStatusCheckboxChange() { - selectedStatuses.clear(); - const checked = document.querySelectorAll('.status-checkbox:checked'); - checked.forEach(chk => { - if (chk.value) selectedStatuses.add(chk.value.toLowerCase().trim()); - }); - - try { - localStorage.setItem('fociz_jira_selected_statuses', JSON.stringify([...selectedStatuses])); - } catch (e) {} - - updateStatusDropdownLabel(); - applyFilters(); -} - -function selectProjectStatuses(projectKey, selectAll) { - const targetKey = (projectKey || '').toUpperCase().trim(); - const chks = document.querySelectorAll(`.status-checkbox[data-project="${targetKey}"]`); - chks.forEach(chk => { - chk.checked = !!selectAll; - const val = chk.value.toLowerCase().trim(); - if (selectAll) { - selectedStatuses.add(val); + // Update tab knoppen styling + const tabs = ['assigned', 'team', 'in_progress', 'done']; + tabs.forEach(t => { + const btn = document.getElementById(`tabBtn_${t}`); + if (!btn) return; + if (t === tab) { + btn.className = "flex items-center space-x-1.5 px-3 py-1.5 text-xs font-bold rounded-xl transition cursor-pointer bg-indigo-50 dark:bg-indigo-950/50 text-indigo-600 dark:text-indigo-400 border border-indigo-200 dark:border-indigo-800/60 flex-shrink-0"; } else { - selectedStatuses.delete(val); + btn.className = "flex items-center space-x-1.5 px-3 py-1.5 text-xs font-bold rounded-xl transition cursor-pointer text-slate-600 dark:text-slate-400 hover:text-slate-900 dark:hover:text-white hover:bg-slate-100 dark:hover:bg-slate-800/60 border border-transparent flex-shrink-0"; } }); - try { - localStorage.setItem('fociz_jira_selected_statuses', JSON.stringify([...selectedStatuses])); - } catch (e) {} - - updateStatusDropdownLabel(); applyFilters(); } -function selectAllStatuses(selectAll) { - selectedStatuses.clear(); - const chks = document.querySelectorAll('.status-checkbox'); - chks.forEach(chk => { - chk.checked = !!selectAll; - if (selectAll && chk.value) { - selectedStatuses.add(chk.value.toLowerCase().trim()); - } - }); - - try { - localStorage.setItem('fociz_jira_selected_statuses', JSON.stringify([...selectedStatuses])); - } catch (e) {} - - updateStatusDropdownLabel(); +// Toggle: Verberg Gesloten +function toggleHideClosed() { + hideClosed = !hideClosed; + updateHideClosedButton(); applyFilters(); } -function selectActiveStatusesOnly() { - selectedStatuses.clear(); - const chks = document.querySelectorAll('.status-checkbox'); - chks.forEach(chk => { - const isDone = chk.getAttribute('data-is-done') === '1'; - chk.checked = !isDone; - if (!isDone && chk.value) { - selectedStatuses.add(chk.value.toLowerCase().trim()); - } - }); - - try { - localStorage.setItem('fociz_jira_selected_statuses', JSON.stringify([...selectedStatuses])); - } catch (e) {} - - updateStatusDropdownLabel(); - applyFilters(); -} - -function updateStatusDropdownLabel() { - const labelEl = document.getElementById('statusDropdownLabel'); - const badgeEl = document.getElementById('statusDropdownBadge'); - if (!labelEl) return; - - const total = getTotalStatusCount(); - const selected = selectedStatuses.size; - - if (selected === total || (total === 0 && selected === 0)) { - labelEl.innerText = "Alle statussen"; - if (badgeEl) badgeEl.classList.add('hidden'); - } else if (selected === 0) { - labelEl.innerText = "Geen geselecteerd"; - if (badgeEl) { - badgeEl.innerText = "0"; - badgeEl.classList.remove('hidden'); - 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"; - } +function updateHideClosedButton() { + const btn = document.getElementById('btnHideClosed'); + if (!btn) return; + if (hideClosed) { + btn.className = "flex items-center space-x-1.5 px-2.5 py-1.5 rounded-xl text-xs font-bold transition cursor-pointer bg-emerald-50 dark:bg-emerald-950/40 text-emerald-700 dark:text-emerald-300 border border-emerald-300 dark:border-emerald-800/80 shadow-2xs"; + btn.innerHTML = `Verberg Gesloten`; } else { - labelEl.innerText = `${selected} statussen`; - if (badgeEl) { - badgeEl.innerText = `${selected}/${total}`; - badgeEl.classList.remove('hidden'); - 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"; - } - } - - const btnReset = document.getElementById('btnResetFiltersInline'); - if (btnReset) { - const isCustom = selected < total || currentProject !== '' || (currentQueue !== '' && currentQueue !== 'MY_QUEUES') || document.getElementById('jiraSearchInput')?.value; - if (isCustom) btnReset.classList.remove('hidden'); - else btnReset.classList.add('hidden'); + btn.className = "flex items-center space-x-1.5 px-2.5 py-1.5 rounded-xl text-xs font-semibold transition cursor-pointer bg-slate-100 dark:bg-slate-800 text-slate-600 dark:text-slate-400 hover:bg-slate-200 dark:hover:bg-slate-700 border border-slate-200 dark:border-slate-700"; + btn.innerHTML = `Toon Ook Gesloten`; } + if (typeof lucide !== 'undefined') lucide.createIcons(); } -function initStatusSelection() { - selectedStatuses = new Set(); - let saved = null; - try { - const raw = localStorage.getItem('fociz_jira_selected_statuses'); - if (raw) saved = JSON.parse(raw); - } catch (e) {} +// Toggle: Snelfilter Alleen In Behandeling +function toggleOnlyInProgress() { + onlyInProgress = !onlyInProgress; + updateOnlyInProgressButton(); + applyFilters(); +} - if (Array.isArray(saved) && saved.length > 0) { - saved.forEach(s => { - if (s) selectedStatuses.add(String(s).toLowerCase().trim()); - }); +function updateOnlyInProgressButton() { + const btn = document.getElementById('btnOnlyInProgress'); + if (!btn) return; + if (onlyInProgress) { + btn.className = "flex items-center space-x-1.5 px-2.5 py-1.5 rounded-xl text-xs font-bold transition cursor-pointer bg-blue-600 text-white shadow-xs"; + btn.innerHTML = `In behandeling (Actief)`; } else { - // Standaard alle actieve statussen selecteren, gesloten uit conform gebruikerswens - (projectStatusesData || []).forEach(proj => { - const pKeyLower = (proj.key || '').toLowerCase().trim(); - (proj.statuses || []).forEach(st => { - const sNorm = (st.name || '').toLowerCase().trim(); - const cat = (st.status_category || '').toLowerCase().trim(); - const isDone = (cat === 'done' || isDoneStatus(sNorm)); - if (!isDone) { - selectedStatuses.add(`${pKeyLower}::${sNorm}`); - } - }); - }); - if (selectedStatuses.size === 0) { - (projectStatusesData || []).forEach(proj => { - const pKeyLower = (proj.key || '').toLowerCase().trim(); - (proj.statuses || []).forEach(st => { - const sNorm = (st.name || '').toLowerCase().trim(); - selectedStatuses.add(`${pKeyLower}::${sNorm}`); - }); - }); - } + btn.className = "flex items-center space-x-1.5 px-2.5 py-1.5 rounded-xl text-xs font-semibold transition cursor-pointer bg-slate-100 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"; + btn.innerHTML = `In behandeling`; } + if (typeof lucide !== 'undefined') lucide.createIcons(); +} - renderStatusCheckboxes(); - updateStatusDropdownLabel(); +// Status dropdown verandering +function onStatusSelectChange(val) { + if (val && isDoneStatus(val)) { + hideClosed = false; + updateHideClosedButton(); + } + applyFilters(); } // Project selectie via pill tabs @@ -1537,14 +1142,21 @@ function updateQueuePills() { } function updateProjectCounts() { - const all = getDeduplicatedIssues(); - const total = getTotalStatusCount(); - let currentList = all; + const assigned = jiraData.assigned_issues || []; + const team = jiraData.team_issues || []; + const all = [...assigned, ...team]; - if (selectedStatuses.size > 0 && selectedStatuses.size < total) { - currentList = currentList.filter(iss => isIssueStatusSelected(iss)); - } else if (selectedStatuses.size === 0) { - currentList = []; + let currentList = all; + if (currentTab === 'assigned') currentList = assigned; + else if (currentTab === 'team') currentList = team; + else if (currentTab === 'in_progress') { + currentList = all.filter(iss => isInProgressStatus(iss.status, iss.status_category)); + } else if (currentTab === 'done') { + currentList = all.filter(iss => isDoneStatus(iss.status) || (iss.status_category || '').toLowerCase() === 'done'); + } + + if (hideClosed && currentTab !== 'done') { + currentList = currentList.filter(iss => !isDoneStatus(iss.status) && (iss.status_category || '').toLowerCase() !== 'done'); } const elAll = document.getElementById('projCount_all'); @@ -1569,14 +1181,21 @@ function updateProjectCounts() { } function updateQueueCounts() { - const all = getDeduplicatedIssues(); - const total = getTotalStatusCount(); - let currentList = all; + const assigned = jiraData.assigned_issues || []; + const team = jiraData.team_issues || []; + const all = [...assigned, ...team]; - if (selectedStatuses.size > 0 && selectedStatuses.size < total) { - currentList = currentList.filter(iss => isIssueStatusSelected(iss)); - } else if (selectedStatuses.size === 0) { - currentList = []; + let currentList = all; + if (currentTab === 'assigned') currentList = assigned; + else if (currentTab === 'team') currentList = team; + else if (currentTab === 'in_progress') { + currentList = all.filter(iss => isInProgressStatus(iss.status, iss.status_category)); + } else if (currentTab === 'done') { + currentList = all.filter(iss => isDoneStatus(iss.status) || (iss.status_category || '').toLowerCase() === 'done'); + } + + if (hideClosed && currentTab !== 'done') { + currentList = currentList.filter(iss => !isDoneStatus(iss.status) && (iss.status_category || '').toLowerCase() !== 'done'); } if (currentProject) { @@ -1685,11 +1304,11 @@ function applyFilters() { const issPKey = (iss.project_key || '').toUpperCase().trim(); const issPName = (iss.project || '').toLowerCase().trim(); const issStatus = (iss.status || '').toLowerCase().trim(); + const issCat = (iss.status_category || '').toLowerCase().trim(); - // 1. Status Filter via Checkboxes - const totalStatuses = getTotalStatusCount(); - if (selectedStatuses.size > 0 && selectedStatuses.size < totalStatuses) { - if (!isIssueStatusSelected(iss)) { + // 1. Verberg Gesloten filter (als tab niet 'done' is en er niet expliciet een gesloten status gekozen is) + if (currentTab !== 'done' && hideClosed && !isDoneStatus(statusVal)) { + if (issCat === 'done' || isDoneStatus(issStatus)) { return false; } } else if (selectedStatuses.size === 0) {