Compare commits
2 Commits
a038d25489
...
1ad383d27b
| Author | SHA1 | Date | |
|---|---|---|---|
| 1ad383d27b | |||
| 8498d82e10 |
@@ -202,6 +202,20 @@ foreach ($usedProjectKeys as $uKey => $val) {
|
|||||||
if (empty($activeProjectKeys)) {
|
if (empty($activeProjectKeys)) {
|
||||||
$activeProjectKeys = array_fill_keys(['SD', 'IN', 'AAN', 'GEN'], true);
|
$activeProjectKeys = array_fill_keys(['SD', 'IN', 'AAN', 'GEN'], true);
|
||||||
}
|
}
|
||||||
|
if (!empty($userQueueFilters)) {
|
||||||
|
$filteredActiveKeys = [];
|
||||||
|
foreach ($userQueueFilters as $code) {
|
||||||
|
$clean = strtoupper(trim($code));
|
||||||
|
if ($clean === 'DX') {
|
||||||
|
$filteredActiveKeys['SD'] = true;
|
||||||
|
} elseif ($clean !== 'MY_QUEUES') {
|
||||||
|
$filteredActiveKeys[$clean] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!empty($filteredActiveKeys)) {
|
||||||
|
$activeProjectKeys = $filteredActiveKeys;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Haal projectstatussen op uit jiraData
|
// Haal projectstatussen op uit jiraData
|
||||||
$rawProjectStatuses = $jiraData['project_statuses'] ?? [];
|
$rawProjectStatuses = $jiraData['project_statuses'] ?? [];
|
||||||
@@ -587,54 +601,18 @@ $statusesJson = json_encode($allStatusesList, JSON_HEX_TAG | JSON_HEX_APOS | JSO
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Rij 3: Status Multi-select Dropdown & Zoekveld -->
|
<!-- Rij 2: Status Filters PER Project (Direct onder de projectknoppen) & Zoekveld -->
|
||||||
<div class="flex flex-col sm:flex-row sm:items-center justify-between gap-3 relative z-30">
|
<div class="flex flex-col md:flex-row md:items-center justify-between gap-3 pt-0.5 relative z-30">
|
||||||
|
|
||||||
<!-- Linkerkant: Statussen multi-select dropdown -->
|
<!-- Linkerkant: Status Dropdowns PER Project -->
|
||||||
<div class="flex flex-wrap items-center gap-2 relative z-50">
|
<div class="flex flex-wrap items-center gap-2 relative z-50">
|
||||||
<div class="relative z-50" id="statusDropdownWrapper">
|
<div class="flex items-center space-x-1.5 text-slate-400 dark:text-slate-500 mr-0.5 flex-shrink-0">
|
||||||
<button
|
<i data-lucide="filter" class="w-3.5 h-3.5 text-indigo-500"></i>
|
||||||
type="button"
|
<span class="text-[11px] font-bold uppercase tracking-wider">Status:</span>
|
||||||
onclick="toggleStatusDropdown()"
|
</div>
|
||||||
id="statusDropdownBtn"
|
|
||||||
class="flex items-center space-x-2 px-3.5 py-2 rounded-xl text-xs font-semibold bg-white dark:bg-slate-900 border border-slate-200 dark:border-slate-700 hover:border-slate-300 dark:hover:border-slate-600 text-slate-800 dark:text-slate-200 transition cursor-pointer shadow-2xs"
|
|
||||||
aria-expanded="false"
|
|
||||||
>
|
|
||||||
<i data-lucide="check-square" class="w-3.5 h-3.5 text-indigo-500"></i>
|
|
||||||
<span>Status:</span>
|
|
||||||
<span id="statusDropdownLabel" class="font-bold text-indigo-600 dark:text-indigo-400">Alle statussen</span>
|
|
||||||
<span id="statusDropdownBadge" class="hidden 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"></span>
|
|
||||||
<i data-lucide="chevron-down" id="statusDropdownChevron" class="w-3.5 h-3.5 text-slate-400 transition-transform duration-200"></i>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<!-- Dropdown Menu met Checkboxes (Gegroepeerd per project) -->
|
<!-- Hier worden de status dropdowns per project dynamisch ingeladen -->
|
||||||
<div
|
<div id="projectStatusDropdownsWrapper" class="flex flex-wrap items-center gap-2">
|
||||||
id="statusDropdownMenu"
|
|
||||||
class="hidden absolute left-0 top-full mt-2 w-[calc(100vw-2.5rem)] sm:w-96 max-w-sm bg-white dark:bg-slate-900 rounded-2xl shadow-2xl border border-slate-200 dark:border-slate-800 py-3 px-3.5 z-50 text-xs space-y-2.5"
|
|
||||||
>
|
|
||||||
<!-- Quick actions header -->
|
|
||||||
<div class="flex items-center justify-between pb-2 border-b border-slate-100 dark:border-slate-800">
|
|
||||||
<span class="font-bold text-slate-900 dark:text-white text-[11px] uppercase tracking-wider">Statussen per Project</span>
|
|
||||||
<div class="flex items-center space-x-1.5">
|
|
||||||
<button type="button" onclick="selectAllStatuses(true)" class="px-2 py-0.5 rounded-md text-[10px] font-bold text-indigo-600 dark:text-indigo-400 hover:bg-indigo-50 dark:hover:bg-indigo-950/60 transition cursor-pointer">
|
|
||||||
Alles
|
|
||||||
</button>
|
|
||||||
<span class="text-slate-300 dark:text-slate-700">•</span>
|
|
||||||
<button type="button" onclick="selectActiveStatusesOnly()" class="px-2 py-0.5 rounded-md text-[10px] font-bold text-emerald-600 dark:text-emerald-400 hover:bg-emerald-50 dark:hover:bg-emerald-950/60 transition cursor-pointer" title="Alleen actieve taken (geen gesloten)">
|
|
||||||
Actief
|
|
||||||
</button>
|
|
||||||
<span class="text-slate-300 dark:text-slate-700">•</span>
|
|
||||||
<button type="button" onclick="selectAllStatuses(false)" class="px-2 py-0.5 rounded-md text-[10px] font-bold text-slate-500 hover:bg-slate-100 dark:hover:bg-slate-800 transition cursor-pointer">
|
|
||||||
Geen
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Status Checkboxes List (scrollable, gegroepeerd per project) -->
|
|
||||||
<div class="max-h-80 overflow-y-auto space-y-2.5 pr-1 custom-scrollbar" id="statusCheckboxList">
|
|
||||||
<!-- Dynamisch gevuld via renderStatusCheckboxes() -->
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Actieve filters reset knop als er iets gefilterd is -->
|
<!-- Actieve filters reset knop als er iets gefilterd is -->
|
||||||
@@ -645,7 +623,7 @@ $statusesJson = json_encode($allStatusesList, JSON_HEX_TAG | JSON_HEX_APOS | JSO
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Rechterkant: Zoekveld -->
|
<!-- Rechterkant: Zoekveld -->
|
||||||
<div class="relative w-full sm:w-64">
|
<div class="relative w-full md:w-64 flex-shrink-0">
|
||||||
<i data-lucide="search" class="w-3.5 h-3.5 text-slate-400 absolute left-2.5 top-1/2 -translate-y-1/2 pointer-events-none"></i>
|
<i data-lucide="search" class="w-3.5 h-3.5 text-slate-400 absolute left-2.5 top-1/2 -translate-y-1/2 pointer-events-none"></i>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
@@ -1148,7 +1126,7 @@ function updateActiveFilterBadge() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
// STATUS MULTI-SELECT CHECKBOX DROPDOWN LOGICA (GEGROEPEERD PER PROJECT)
|
// STATUS MULTI-SELECT CHECKBOX DROPDOWN LOGICA (PER PROJECT DROPDOWNS)
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
|
|
||||||
function getTotalStatusCount() {
|
function getTotalStatusCount() {
|
||||||
@@ -1175,63 +1153,48 @@ function isIssueStatusSelected(iss) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Renders the per-project status dropdowns into #projectStatusDropdownsWrapper
|
||||||
function renderStatusCheckboxes() {
|
function renderStatusCheckboxes() {
|
||||||
const container = document.getElementById('statusCheckboxList');
|
const container = document.getElementById('projectStatusDropdownsWrapper');
|
||||||
if (!container) return;
|
if (!container) return;
|
||||||
|
|
||||||
if (!projectStatusesData || projectStatusesData.length === 0) {
|
if (!projectStatusesData || projectStatusesData.length === 0) {
|
||||||
container.innerHTML = '<div class="py-2 text-center text-slate-400 italic text-[11px]">Geen project statussen gevonden</div>';
|
container.innerHTML = '<span class="text-slate-400 italic text-[11px]">Geen project statussen gevonden</span>';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const colorClasses = {
|
const colorClasses = {
|
||||||
sky: {
|
sky: {
|
||||||
badge: 'bg-sky-100 dark:bg-sky-900/60 text-sky-800 dark:text-sky-200 border border-sky-300 dark:border-sky-800',
|
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/60 dark:bg-sky-950/30'
|
hdrBg: 'bg-sky-50/70 dark:bg-sky-950/40'
|
||||||
},
|
},
|
||||||
amber: {
|
amber: {
|
||||||
badge: 'bg-amber-100 dark:bg-amber-900/60 text-amber-800 dark:text-amber-200 border border-amber-300 dark:border-amber-800',
|
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/60 dark:bg-amber-950/30'
|
hdrBg: 'bg-amber-50/70 dark:bg-amber-950/40'
|
||||||
},
|
},
|
||||||
emerald: {
|
emerald: {
|
||||||
badge: 'bg-emerald-100 dark:bg-emerald-900/60 text-emerald-800 dark:text-emerald-200 border border-emerald-300 dark:border-emerald-800',
|
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/60 dark:bg-emerald-950/30'
|
hdrBg: 'bg-emerald-50/70 dark:bg-emerald-950/40'
|
||||||
},
|
},
|
||||||
indigo: {
|
indigo: {
|
||||||
badge: 'bg-indigo-100 dark:bg-indigo-900/60 text-indigo-800 dark:text-indigo-200 border border-indigo-300 dark:border-indigo-800',
|
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/60 dark:bg-indigo-950/30'
|
hdrBg: 'bg-indigo-50/70 dark:bg-indigo-950/40'
|
||||||
},
|
},
|
||||||
slate: {
|
slate: {
|
||||||
badge: 'bg-slate-100 dark:bg-slate-800 text-slate-800 dark:text-slate-200 border border-slate-300 dark:border-slate-700',
|
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 dark:bg-slate-800/40'
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let html = '';
|
let html = '';
|
||||||
projectStatusesData.forEach((proj, pIdx) => {
|
projectStatusesData.forEach((proj, pIdx) => {
|
||||||
const pKey = (proj.key || '').toUpperCase();
|
const pKey = (proj.key || '').toUpperCase().trim();
|
||||||
const pKeyLower = pKey.toLowerCase();
|
const pKeyLower = pKey.toLowerCase();
|
||||||
const pColor = proj.color || 'slate';
|
const pColor = proj.color || 'slate';
|
||||||
const styles = colorClasses[pColor] || colorClasses.slate;
|
const styles = colorClasses[pColor] || colorClasses.slate;
|
||||||
const statuses = proj.statuses || [];
|
const statuses = proj.statuses || [];
|
||||||
|
|
||||||
html += `
|
let statusRowsHtml = '';
|
||||||
<div class="project-status-group pb-2 mb-2 border-b border-slate-100 dark:border-slate-800/80 last:border-b-0 last:pb-0 last:mb-0">
|
|
||||||
<div class="flex items-center justify-between px-2 py-1.5 mb-1.5 rounded-lg ${styles.hdrBg}">
|
|
||||||
<div class="flex items-center space-x-2 min-w-0">
|
|
||||||
<span class="px-1.5 py-0.5 rounded font-mono text-[10px] font-black ${styles.badge}">${escapeHtml(pKey)}</span>
|
|
||||||
<span class="font-bold text-slate-800 dark:text-slate-200 text-xs truncate">${escapeHtml(proj.name || pKey)}</span>
|
|
||||||
<span id="projTotal_${pKey}" class="text-[10px] text-slate-400 font-bold">(0)</span>
|
|
||||||
</div>
|
|
||||||
<div class="flex items-center space-x-1.5 text-[10px] flex-shrink-0">
|
|
||||||
<button type="button" onclick="selectProjectStatuses('${escapeHtml(pKey)}', true)" class="px-1.5 py-0.5 rounded font-semibold text-indigo-600 dark:text-indigo-400 hover:bg-indigo-50 dark:hover:bg-indigo-950/60 transition cursor-pointer">Alles</button>
|
|
||||||
<span class="text-slate-300 dark:text-slate-700">•</span>
|
|
||||||
<button type="button" onclick="selectProjectStatuses('${escapeHtml(pKey)}', false)" class="px-1.5 py-0.5 rounded font-semibold text-slate-500 hover:bg-slate-200 dark:hover:bg-slate-700 transition cursor-pointer">Geen</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="space-y-0.5 pl-1">
|
|
||||||
`;
|
|
||||||
|
|
||||||
statuses.forEach((st, sIdx) => {
|
statuses.forEach((st, sIdx) => {
|
||||||
const sNorm = (st.name || '').toLowerCase().trim();
|
const sNorm = (st.name || '').toLowerCase().trim();
|
||||||
const compoundVal = `${pKeyLower}::${sNorm}`;
|
const compoundVal = `${pKeyLower}::${sNorm}`;
|
||||||
@@ -1253,10 +1216,10 @@ function renderStatusCheckboxes() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const safeKey = `${pKey}_${sNorm.replace(/[^a-z0-9]/gi, '_')}`;
|
const safeKey = `${pKey}_${sNorm.replace(/[^a-z0-9]/gi, '_')}`;
|
||||||
const safeId = `chkStatus_${pIdx}_${sIdx}`;
|
const safeId = `chkStatus_${pKey}_${sIdx}`;
|
||||||
|
|
||||||
html += `
|
statusRowsHtml += `
|
||||||
<label for="${safeId}" class="flex items-center justify-between px-2 py-1.5 rounded-lg hover:bg-slate-100 dark:hover:bg-slate-800/70 cursor-pointer transition select-none group">
|
<label for="${safeId}" class="flex items-center justify-between px-2.5 py-1.5 rounded-lg hover:bg-slate-100 dark:hover:bg-slate-800/70 cursor-pointer transition select-none group">
|
||||||
<div class="flex items-center space-x-2.5 min-w-0 flex-1">
|
<div class="flex items-center space-x-2.5 min-w-0 flex-1">
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
@@ -1266,8 +1229,8 @@ function renderStatusCheckboxes() {
|
|||||||
data-status="${escapeHtml(sNorm)}"
|
data-status="${escapeHtml(sNorm)}"
|
||||||
data-is-done="${isDone ? 1 : 0}"
|
data-is-done="${isDone ? 1 : 0}"
|
||||||
${isChecked ? 'checked' : ''}
|
${isChecked ? 'checked' : ''}
|
||||||
onchange="onStatusCheckboxChange()"
|
onchange="onStatusCheckboxChange('${escapeHtml(pKey)}')"
|
||||||
class="status-checkbox w-4 h-4 rounded text-indigo-600 border-slate-300 dark:border-slate-700 focus:ring-indigo-500 cursor-pointer flex-shrink-0"
|
class="status-checkbox status-checkbox-${escapeHtml(pKey)} w-4 h-4 rounded text-indigo-600 border-slate-300 dark:border-slate-700 focus:ring-indigo-500 cursor-pointer flex-shrink-0"
|
||||||
>
|
>
|
||||||
<span class="w-2 h-2 rounded-full ${dotColor} flex-shrink-0"></span>
|
<span class="w-2 h-2 rounded-full ${dotColor} flex-shrink-0"></span>
|
||||||
<span class="font-semibold text-slate-700 dark:text-slate-300 truncate text-xs group-hover:text-indigo-600 dark:group-hover:text-indigo-400 transition">${escapeHtml(st.name)}</span>
|
<span class="font-semibold text-slate-700 dark:text-slate-300 truncate text-xs group-hover:text-indigo-600 dark:group-hover:text-indigo-400 transition">${escapeHtml(st.name)}</span>
|
||||||
@@ -1278,13 +1241,132 @@ function renderStatusCheckboxes() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
html += `
|
html += `
|
||||||
|
<div class="relative project-status-dropdown-item" data-project="${escapeHtml(pKey)}" id="statusDropdownWrapper_${escapeHtml(pKey)}">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onclick="toggleProjectStatusDropdown('${escapeHtml(pKey)}')"
|
||||||
|
id="statusDropdownBtn_${escapeHtml(pKey)}"
|
||||||
|
class="flex items-center space-x-1.5 px-2.5 sm:px-3 py-1.5 rounded-xl text-xs font-semibold bg-white dark:bg-slate-900 border border-slate-200 dark:border-slate-700 hover:border-slate-300 dark:hover:border-slate-600 text-slate-800 dark:text-slate-200 transition cursor-pointer shadow-2xs flex-shrink-0"
|
||||||
|
aria-expanded="false"
|
||||||
|
>
|
||||||
|
<span class="px-1.5 py-0.5 rounded font-mono text-[10px] font-black ${styles.badge}">${escapeHtml(pKey)}</span>
|
||||||
|
<span id="statusDropdownLabel_${escapeHtml(pKey)}" class="font-bold text-slate-700 dark:text-slate-300 truncate max-w-[130px]">Alle</span>
|
||||||
|
<span id="statusDropdownBadge_${escapeHtml(pKey)}" class="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"></span>
|
||||||
|
<i data-lucide="chevron-down" id="statusDropdownChevron_${escapeHtml(pKey)}" class="w-3.5 h-3.5 text-slate-400 transition-transform duration-200 flex-shrink-0"></i>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div
|
||||||
|
id="statusDropdownMenu_${escapeHtml(pKey)}"
|
||||||
|
class="hidden absolute left-0 top-full mt-1.5 w-72 sm:w-80 max-w-[calc(100vw-2.5rem)] bg-white dark:bg-slate-900 rounded-2xl shadow-2xl border border-slate-200 dark:border-slate-800 py-3 px-3.5 z-50 text-xs space-y-2.5"
|
||||||
|
>
|
||||||
|
<div class="flex items-center justify-between pb-2 border-b border-slate-100 dark:border-slate-800 ${styles.hdrBg} -mx-3.5 -mt-3 p-3 rounded-t-2xl">
|
||||||
|
<div class="flex items-center space-x-1.5 min-w-0">
|
||||||
|
<span class="px-1.5 py-0.5 rounded font-mono text-[10px] font-black ${styles.badge}">${escapeHtml(pKey)}</span>
|
||||||
|
<span class="font-bold text-slate-900 dark:text-white text-xs truncate">${escapeHtml(proj.name || pKey)}</span>
|
||||||
|
<span id="projTotal_${escapeHtml(pKey)}" class="text-[10px] text-slate-400 font-bold">(0)</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center space-x-1 text-[10px] flex-shrink-0">
|
||||||
|
<button type="button" onclick="selectProjectStatuses('${escapeHtml(pKey)}', true)" class="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">
|
||||||
|
Alles
|
||||||
|
</button>
|
||||||
|
<span class="text-slate-300 dark:text-slate-700">•</span>
|
||||||
|
<button type="button" onclick="selectProjectActiveStatuses('${escapeHtml(pKey)}')" class="px-1.5 py-0.5 rounded font-bold text-emerald-600 dark:text-emerald-400 hover:bg-emerald-50 dark:hover:bg-emerald-950/60 transition cursor-pointer" title="Alleen actieve taken">
|
||||||
|
Actief
|
||||||
|
</button>
|
||||||
|
<span class="text-slate-300 dark:text-slate-700">•</span>
|
||||||
|
<button type="button" onclick="selectProjectStatuses('${escapeHtml(pKey)}', false)" class="px-1.5 py-0.5 rounded font-bold text-slate-500 hover:bg-slate-200 dark:hover:bg-slate-700 transition cursor-pointer">
|
||||||
|
Geen
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="max-h-60 overflow-y-auto space-y-1 pr-1 custom-scrollbar" id="statusCheckboxList_${escapeHtml(pKey)}">
|
||||||
|
${statusRowsHtml}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
});
|
});
|
||||||
|
|
||||||
container.innerHTML = html;
|
container.innerHTML = html;
|
||||||
|
if (typeof lucide !== 'undefined') lucide.createIcons();
|
||||||
updateStatusCounts();
|
updateStatusCounts();
|
||||||
|
updateStatusDropdownLabel();
|
||||||
|
updateProjectStatusDropdownVisibility();
|
||||||
|
}
|
||||||
|
|
||||||
|
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}`);
|
||||||
|
if (!menu) return;
|
||||||
|
|
||||||
|
const willOpen = (typeof force === 'boolean') ? force : menu.classList.contains('hidden');
|
||||||
|
|
||||||
|
// Sluit alle andere geopende status dropdowns
|
||||||
|
closeAllProjectStatusDropdowns(targetKey);
|
||||||
|
|
||||||
|
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');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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')) {
|
||||||
|
menu.classList.add('hidden');
|
||||||
|
if (chevron) chevron.classList.remove('rotate-180');
|
||||||
|
if (btn) btn.setAttribute('aria-expanded', 'false');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sluit dropdowns bij klikken buiten dropdowns
|
||||||
|
document.addEventListener('click', (e) => {
|
||||||
|
const isInside = e.target.closest('.project-status-dropdown-item');
|
||||||
|
if (!isInside) {
|
||||||
|
closeAllProjectStatusDropdowns();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function toggleStatusDropdown(force) {
|
||||||
|
if (currentProject) {
|
||||||
|
toggleProjectStatusDropdown(currentProject, force);
|
||||||
|
} else if (projectStatusesData && projectStatusesData.length > 0) {
|
||||||
|
toggleProjectStatusDropdown(projectStatusesData[0].key, force);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateStatusCounts() {
|
function updateStatusCounts() {
|
||||||
@@ -1321,37 +1403,18 @@ function updateStatusCounts() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleStatusDropdown(force) {
|
function onStatusCheckboxChange(projectKey) {
|
||||||
const menu = document.getElementById('statusDropdownMenu');
|
const pNorm = (projectKey || '').toUpperCase().trim();
|
||||||
const chevron = document.getElementById('statusDropdownChevron');
|
const pKeyLower = pNorm.toLowerCase();
|
||||||
const btn = document.getElementById('statusDropdownBtn');
|
const chks = document.querySelectorAll(`.status-checkbox-${pNorm}`);
|
||||||
if (!menu) return;
|
|
||||||
|
|
||||||
const willOpen = (typeof force === 'boolean') ? force : menu.classList.contains('hidden');
|
chks.forEach(chk => {
|
||||||
if (willOpen) {
|
const val = chk.value.toLowerCase().trim();
|
||||||
menu.classList.remove('hidden');
|
if (chk.checked) {
|
||||||
if (chevron) chevron.classList.add('rotate-180');
|
selectedStatuses.add(val);
|
||||||
if (btn) btn.setAttribute('aria-expanded', 'true');
|
} else {
|
||||||
updateStatusCounts();
|
selectedStatuses.delete(val);
|
||||||
} 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 {
|
try {
|
||||||
@@ -1364,7 +1427,9 @@ function onStatusCheckboxChange() {
|
|||||||
|
|
||||||
function selectProjectStatuses(projectKey, selectAll) {
|
function selectProjectStatuses(projectKey, selectAll) {
|
||||||
const targetKey = (projectKey || '').toUpperCase().trim();
|
const targetKey = (projectKey || '').toUpperCase().trim();
|
||||||
|
const pKeyLower = targetKey.toLowerCase();
|
||||||
const chks = document.querySelectorAll(`.status-checkbox[data-project="${targetKey}"]`);
|
const chks = document.querySelectorAll(`.status-checkbox[data-project="${targetKey}"]`);
|
||||||
|
|
||||||
chks.forEach(chk => {
|
chks.forEach(chk => {
|
||||||
chk.checked = !!selectAll;
|
chk.checked = !!selectAll;
|
||||||
const val = chk.value.toLowerCase().trim();
|
const val = chk.value.toLowerCase().trim();
|
||||||
@@ -1383,6 +1448,29 @@ function selectProjectStatuses(projectKey, selectAll) {
|
|||||||
applyFilters();
|
applyFilters();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function selectProjectActiveStatuses(projectKey) {
|
||||||
|
const targetKey = (projectKey || '').toUpperCase().trim();
|
||||||
|
const chks = document.querySelectorAll(`.status-checkbox[data-project="${targetKey}"]`);
|
||||||
|
|
||||||
|
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();
|
||||||
|
applyFilters();
|
||||||
|
}
|
||||||
|
|
||||||
function selectAllStatuses(selectAll) {
|
function selectAllStatuses(selectAll) {
|
||||||
selectedStatuses.clear();
|
selectedStatuses.clear();
|
||||||
const chks = document.querySelectorAll('.status-checkbox');
|
const chks = document.querySelectorAll('.status-checkbox');
|
||||||
@@ -1401,55 +1489,78 @@ function selectAllStatuses(selectAll) {
|
|||||||
applyFilters();
|
applyFilters();
|
||||||
}
|
}
|
||||||
|
|
||||||
function selectActiveStatusesOnly() {
|
function updateStatusDropdownLabel() {
|
||||||
selectedStatuses.clear();
|
let hasAnyCustomFilter = false;
|
||||||
const chks = document.querySelectorAll('.status-checkbox');
|
|
||||||
chks.forEach(chk => {
|
(projectStatusesData || []).forEach(proj => {
|
||||||
const isDone = chk.getAttribute('data-is-done') === '1';
|
const pKey = (proj.key || '').toUpperCase().trim();
|
||||||
chk.checked = !isDone;
|
const pKeyLower = pKey.toLowerCase();
|
||||||
if (!isDone && chk.value) {
|
const labelEl = document.getElementById(`statusDropdownLabel_${pKey}`);
|
||||||
selectedStatuses.add(chk.value.toLowerCase().trim());
|
const badgeEl = document.getElementById(`statusDropdownBadge_${pKey}`);
|
||||||
|
if (!labelEl) return;
|
||||||
|
|
||||||
|
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";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
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";
|
|
||||||
}
|
|
||||||
} 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');
|
const btnReset = document.getElementById('btnResetFiltersInline');
|
||||||
if (btnReset) {
|
if (btnReset) {
|
||||||
const isCustom = selected < total || currentProject !== '' || document.getElementById('jiraSearchInput')?.value;
|
const isCustom = hasAnyCustomFilter || currentProject !== '' || document.getElementById('jiraSearchInput')?.value;
|
||||||
if (isCustom) btnReset.classList.remove('hidden');
|
if (isCustom) btnReset.classList.remove('hidden');
|
||||||
else btnReset.classList.add('hidden');
|
else btnReset.classList.add('hidden');
|
||||||
}
|
}
|
||||||
@@ -1465,16 +1576,16 @@ function initStatusSelection() {
|
|||||||
|
|
||||||
let validSaved = false;
|
let validSaved = false;
|
||||||
if (Array.isArray(saved) && saved.length > 0) {
|
if (Array.isArray(saved) && saved.length > 0) {
|
||||||
saved.forEach(s => {
|
const hasCompound = saved.some(s => String(s).includes('::'));
|
||||||
if (s) selectedStatuses.add(String(s).toLowerCase().trim());
|
if (hasCompound) {
|
||||||
});
|
saved.forEach(s => {
|
||||||
if (selectedStatuses.size > 0) {
|
if (s) selectedStatuses.add(String(s).toLowerCase().trim());
|
||||||
|
});
|
||||||
validSaved = true;
|
validSaved = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!validSaved || selectedStatuses.size === 0) {
|
if (!validSaved) {
|
||||||
selectedStatuses.clear();
|
|
||||||
// Standaard alle actieve statussen selecteren, gesloten uit conform gebruikerswens
|
// Standaard alle actieve statussen selecteren, gesloten uit conform gebruikerswens
|
||||||
(projectStatusesData || []).forEach(proj => {
|
(projectStatusesData || []).forEach(proj => {
|
||||||
const pKeyLower = (proj.key || '').toLowerCase().trim();
|
const pKeyLower = (proj.key || '').toLowerCase().trim();
|
||||||
@@ -1509,13 +1620,15 @@ function initStatusSelection() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderStatusCheckboxes();
|
renderStatusCheckboxes();
|
||||||
updateStatusDropdownLabel();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Project selectie via pill tabs
|
// Project selectie via pill tabs
|
||||||
function selectProject(proj) {
|
function selectProject(proj) {
|
||||||
currentProject = (proj || '').trim();
|
currentProject = (proj || '').trim();
|
||||||
|
closeAllProjectStatusDropdowns();
|
||||||
updateProjectPills();
|
updateProjectPills();
|
||||||
|
updateProjectStatusDropdownVisibility();
|
||||||
|
updateStatusCounts();
|
||||||
applyFilters();
|
applyFilters();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1882,6 +1995,7 @@ function clearSearch() {
|
|||||||
function resetAllFilters() {
|
function resetAllFilters() {
|
||||||
currentProject = '';
|
currentProject = '';
|
||||||
currentQueue = '';
|
currentQueue = '';
|
||||||
|
closeAllProjectStatusDropdowns();
|
||||||
selectAllStatuses(true);
|
selectAllStatuses(true);
|
||||||
|
|
||||||
const searchInput = document.getElementById('jiraSearchInput');
|
const searchInput = document.getElementById('jiraSearchInput');
|
||||||
@@ -1894,7 +2008,8 @@ function resetAllFilters() {
|
|||||||
|
|
||||||
document.getElementById('clearSearchBtn')?.classList.add('hidden');
|
document.getElementById('clearSearchBtn')?.classList.add('hidden');
|
||||||
updateProjectPills();
|
updateProjectPills();
|
||||||
updateQueuePills();
|
updateProjectStatusDropdownVisibility();
|
||||||
|
updateStatusCounts();
|
||||||
applyFilters();
|
applyFilters();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user