Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d65463dab8 | |||
| 8cff08f253 |
@@ -98,15 +98,102 @@ foreach ($allIssues as $iss) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Statussen uitbreiden met officiële Jira instantie statussen
|
// Bepaal welke projecten getoond worden: alleen projecten die tickets hebben, plus SD/IN/AAN/GEN
|
||||||
$availableStatuses = $jiraData['available_statuses'] ?? [];
|
// Geen statussen van projecten die niet gebruikt worden!
|
||||||
if (!empty($availableStatuses)) {
|
$activeProjectKeys = [];
|
||||||
foreach ($availableStatuses as $st) {
|
foreach ($coreProjects as $cKey => $cMeta) {
|
||||||
if (!empty($st['name'])) {
|
if (isset($usedProjectKeys[$cKey]) || !empty($projectCounts[$cMeta['name']]) || !empty($projectCounts[$cKey])) {
|
||||||
$allStatuses[$st['name']] = $st['name'];
|
$activeProjectKeys[$cKey] = true;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
foreach ($usedProjectKeys as $uKey => $val) {
|
||||||
|
$activeProjectKeys[$uKey] = true;
|
||||||
|
}
|
||||||
|
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'] ?? '',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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($allProjects);
|
||||||
ksort($allTypes);
|
ksort($allTypes);
|
||||||
@@ -117,6 +204,8 @@ $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);
|
$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);
|
$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);
|
||||||
?>
|
?>
|
||||||
|
|
||||||
<div class="space-y-6 w-full">
|
<div class="space-y-6 w-full">
|
||||||
@@ -393,48 +482,48 @@ $boardsJson = json_encode($boards, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex items-center gap-1.5 overflow-x-auto pb-1 md:pb-0" id="queueButtonsContainer">
|
<div class="flex flex-wrap items-center gap-1.5" id="queueButtonsContainer">
|
||||||
<button type="button" onclick="selectQueueFilter('')" data-queue="" class="queue-pill flex items-center space-x-1 px-2.5 py-1.5 rounded-xl text-xs font-bold transition cursor-pointer bg-slate-200/80 dark:bg-slate-800 text-slate-700 dark:text-slate-300 flex-shrink-0">
|
<button type="button" onclick="selectQueueFilter('')" data-queue="" class="queue-pill flex items-center space-x-1 px-2.5 py-1.5 rounded-xl text-xs font-bold transition cursor-pointer bg-slate-200/80 dark:bg-slate-800 text-slate-700 dark:text-slate-300 flex-shrink-0">
|
||||||
<i data-lucide="list" class="w-3 h-3"></i>
|
<i data-lucide="list" class="w-3 h-3"></i>
|
||||||
<span>Alle Wachtrijen</span>
|
<span>Alle</span>
|
||||||
<span id="queueCount_all" class="px-1.5 py-0.5 rounded-full text-[10px] bg-slate-300 dark:bg-slate-700 font-bold">0</span>
|
<span id="queueCount_all" class="px-1.5 py-0.5 rounded-full text-[10px] bg-slate-300 dark:bg-slate-700 font-bold">0</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<?php if (!empty($userQueueFilters)): ?>
|
<?php if (!empty($userQueueFilters)): ?>
|
||||||
<button type="button" onclick="selectQueueFilter('MY_QUEUES')" data-queue="MY_QUEUES" class="queue-pill flex items-center space-x-1.5 px-3 py-1.5 rounded-xl text-xs font-bold transition cursor-pointer bg-amber-50 dark:bg-amber-950/50 text-amber-800 dark:text-amber-200 border border-amber-300 dark:border-amber-700/80 flex-shrink-0" title="Alleen tickets uit jouw gekoppelde wachtrijen (<?= htmlspecialchars(implode(', ', $userQueueFilters)) ?>)">
|
<button type="button" onclick="selectQueueFilter('MY_QUEUES')" data-queue="MY_QUEUES" class="queue-pill flex items-center space-x-1.5 px-3 py-1.5 rounded-xl text-xs font-bold transition cursor-pointer bg-amber-50 dark:bg-amber-950/50 text-amber-800 dark:text-amber-200 border border-amber-300 dark:border-amber-700/80 flex-shrink-0" title="Alleen tickets uit jouw gekoppelde wachtrijen (<?= htmlspecialchars(implode(', ', $userQueueFilters)) ?>)">
|
||||||
<i data-lucide="star" class="w-3.5 h-3.5 text-amber-500 fill-amber-500"></i>
|
<i data-lucide="star" class="w-3.5 h-3.5 text-amber-500 fill-amber-500"></i>
|
||||||
<span>Mijn Wachtrijen (<?= htmlspecialchars(implode('+', $userQueueFilters)) ?>)</span>
|
<span>Mijn</span>
|
||||||
<span id="queueCount_MY_QUEUES" class="px-1.5 py-0.5 rounded-full text-[10px] bg-amber-200 dark:bg-amber-800 font-bold">0</span>
|
<span id="queueCount_MY_QUEUES" class="px-1.5 py-0.5 rounded-full text-[10px] bg-amber-200 dark:bg-amber-800 font-bold">0</span>
|
||||||
</button>
|
</button>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
<button type="button" onclick="selectQueueFilter('SD')" data-queue="SD" class="queue-pill flex items-center space-x-1.5 px-2.5 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 dark:border-slate-700 flex-shrink-0">
|
<button type="button" onclick="selectQueueFilter('SD')" data-queue="SD" class="queue-pill flex items-center space-x-1.5 px-2.5 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 dark:border-slate-700 flex-shrink-0">
|
||||||
<span class="w-2 h-2 rounded-full bg-sky-500"></span>
|
<span class="w-2 h-2 rounded-full bg-sky-500"></span>
|
||||||
<span>SD (Servicedesk)</span>
|
<span>SD</span>
|
||||||
<span id="queueCount_SD" class="px-1.5 py-0.5 rounded-full text-[10px] bg-sky-100 dark:bg-sky-950/60 text-sky-800 dark:text-sky-300 font-bold">0</span>
|
<span id="queueCount_SD" class="px-1.5 py-0.5 rounded-full text-[10px] bg-sky-100 dark:bg-sky-950/60 text-sky-800 dark:text-sky-300 font-bold">0</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button type="button" onclick="selectQueueFilter('IN')" data-queue="IN" class="queue-pill flex items-center space-x-1.5 px-2.5 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 dark:border-slate-700 flex-shrink-0">
|
<button type="button" onclick="selectQueueFilter('IN')" data-queue="IN" class="queue-pill flex items-center space-x-1.5 px-2.5 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 dark:border-slate-700 flex-shrink-0">
|
||||||
<span class="w-2 h-2 rounded-full bg-amber-500"></span>
|
<span class="w-2 h-2 rounded-full bg-amber-500"></span>
|
||||||
<span>IN (Interfaces)</span>
|
<span>IN</span>
|
||||||
<span id="queueCount_IN" class="px-1.5 py-0.5 rounded-full text-[10px] bg-amber-100 dark:bg-amber-950/60 text-amber-800 dark:text-amber-300 font-bold">0</span>
|
<span id="queueCount_IN" class="px-1.5 py-0.5 rounded-full text-[10px] bg-amber-100 dark:bg-amber-950/60 text-amber-800 dark:text-amber-300 font-bold">0</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button type="button" onclick="selectQueueFilter('AAN')" data-queue="AAN" class="queue-pill flex items-center space-x-1.5 px-2.5 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 dark:border-slate-700 flex-shrink-0">
|
<button type="button" onclick="selectQueueFilter('AAN')" data-queue="AAN" class="queue-pill flex items-center space-x-1.5 px-2.5 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 dark:border-slate-700 flex-shrink-0">
|
||||||
<span class="w-2 h-2 rounded-full bg-emerald-500"></span>
|
<span class="w-2 h-2 rounded-full bg-emerald-500"></span>
|
||||||
<span>AAN (Aanvragen)</span>
|
<span>AAN</span>
|
||||||
<span id="queueCount_AAN" class="px-1.5 py-0.5 rounded-full text-[10px] bg-emerald-100 dark:bg-emerald-950/60 text-emerald-800 dark:text-emerald-300 font-bold">0</span>
|
<span id="queueCount_AAN" class="px-1.5 py-0.5 rounded-full text-[10px] bg-emerald-100 dark:bg-emerald-950/60 text-emerald-800 dark:text-emerald-300 font-bold">0</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button type="button" onclick="selectQueueFilter('GEN')" data-queue="GEN" class="queue-pill flex items-center space-x-1.5 px-2.5 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 dark:border-slate-700 flex-shrink-0">
|
<button type="button" onclick="selectQueueFilter('GEN')" data-queue="GEN" class="queue-pill flex items-center space-x-1.5 px-2.5 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 dark:border-slate-700 flex-shrink-0">
|
||||||
<span class="w-2 h-2 rounded-full bg-indigo-500"></span>
|
<span class="w-2 h-2 rounded-full bg-indigo-500"></span>
|
||||||
<span>GEN (General)</span>
|
<span>GEN</span>
|
||||||
<span id="queueCount_GEN" class="px-1.5 py-0.5 rounded-full text-[10px] bg-indigo-100 dark:bg-indigo-950/60 text-indigo-800 dark:text-indigo-300 font-bold">0</span>
|
<span id="queueCount_GEN" class="px-1.5 py-0.5 rounded-full text-[10px] bg-indigo-100 dark:bg-indigo-950/60 text-indigo-800 dark:text-indigo-300 font-bold">0</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button type="button" onclick="selectQueueFilter('DX')" data-queue="DX" class="queue-pill flex items-center space-x-1.5 px-2.5 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 dark:border-slate-700 flex-shrink-0">
|
<button type="button" onclick="selectQueueFilter('DX')" data-queue="DX" class="queue-pill flex items-center space-x-1.5 px-2.5 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 dark:border-slate-700 flex-shrink-0">
|
||||||
<i data-lucide="zap" class="w-3 h-3 text-amber-500"></i>
|
<i data-lucide="zap" class="w-3 h-3 text-amber-500"></i>
|
||||||
<span>DX (Koppelingen)</span>
|
<span>DX</span>
|
||||||
<span id="queueCount_DX" class="px-1.5 py-0.5 rounded-full text-[10px] bg-amber-100 dark:bg-amber-950/60 text-amber-800 dark:text-amber-300 font-bold">0</span>
|
<span id="queueCount_DX" class="px-1.5 py-0.5 rounded-full text-[10px] bg-amber-100 dark:bg-amber-950/60 text-amber-800 dark:text-amber-300 font-bold">0</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
@@ -446,51 +535,55 @@ $boardsJson = json_encode($boards, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Rij 3: Tabs, Verberg Gesloten, In behandeling & Zoekveld -->
|
<!-- Rij 3: Status Multi-select Dropdown & Zoekveld -->
|
||||||
<div class="flex flex-col lg:flex-row lg:items-center justify-between gap-3">
|
<div class="flex flex-col sm:flex-row sm:items-center justify-between gap-3 relative z-30">
|
||||||
<!-- Tabs Navigatie -->
|
|
||||||
<div class="flex items-center space-x-1 sm:space-x-1.5 overflow-x-auto pb-1 lg:pb-0">
|
<!-- Linkerkant: Statussen multi-select dropdown -->
|
||||||
<button onclick="switchTab('assigned')" id="tabBtn_assigned" class="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">
|
<div class="flex flex-wrap items-center gap-2 relative z-50">
|
||||||
<i data-lucide="user-check" class="w-3.5 h-3.5"></i>
|
<div class="relative z-50" id="statusDropdownWrapper">
|
||||||
<span>Aan mij</span>
|
<button
|
||||||
<span id="tabCount_assigned" class="px-1.5 py-0.5 rounded-full text-[10px] font-bold bg-indigo-200/70 dark:bg-indigo-800/60 text-indigo-800 dark:text-indigo-200">
|
type="button"
|
||||||
<?= count($assignedIssues) ?>
|
onclick="toggleStatusDropdown()"
|
||||||
</span>
|
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>
|
</button>
|
||||||
|
|
||||||
<button onclick="switchTab('team')" id="tabBtn_team" class="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">
|
<!-- Dropdown Menu met Checkboxes (Gegroepeerd per project) -->
|
||||||
<i data-lucide="users" class="w-3.5 h-3.5"></i>
|
<div
|
||||||
<span>Team & Projecten</span>
|
id="statusDropdownMenu"
|
||||||
<span id="tabCount_team" class="px-1.5 py-0.5 rounded-full text-[10px] font-bold bg-slate-200/80 dark:bg-slate-800 text-slate-700 dark:text-slate-300">
|
class="hidden absolute left-0 top-full mt-2 w-80 sm:w-96 max-w-[95vw] 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"
|
||||||
<?= count($teamIssues) ?>
|
>
|
||||||
</span>
|
<!-- 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>
|
</button>
|
||||||
|
<span class="text-slate-300 dark:text-slate-700">•</span>
|
||||||
<button onclick="switchTab('in_progress')" id="tabBtn_in_progress" class="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">
|
<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)">
|
||||||
<i data-lucide="clock" class="w-3.5 h-3.5"></i>
|
Actief
|
||||||
<span>In Uitvoering</span>
|
|
||||||
<span id="tabCount_in_progress" class="px-1.5 py-0.5 rounded-full text-[10px] font-bold bg-slate-200/80 dark:bg-slate-800 text-slate-700 dark:text-slate-300">
|
|
||||||
0
|
|
||||||
</span>
|
|
||||||
</button>
|
</button>
|
||||||
|
<span class="text-slate-300 dark:text-slate-700">•</span>
|
||||||
<button onclick="switchTab('done')" id="tabBtn_done" class="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">
|
<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">
|
||||||
<i data-lucide="check-circle" class="w-3.5 h-3.5"></i>
|
Geen
|
||||||
<span>Afgerond</span>
|
|
||||||
<span id="tabCount_done" class="px-1.5 py-0.5 rounded-full text-[10px] font-bold bg-slate-200/80 dark:bg-slate-800 text-slate-700 dark:text-slate-300">
|
|
||||||
0
|
|
||||||
</span>
|
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Status Acties: Verberg Gesloten + Dropdown + Zoeken -->
|
<!-- Status Checkboxes List (scrollable, gegroepeerd per project) -->
|
||||||
<div class="flex flex-wrap items-center gap-2">
|
<div class="max-h-80 overflow-y-auto space-y-2.5 pr-1 custom-scrollbar" id="statusCheckboxList">
|
||||||
<!-- Toggle: Verberg Gesloten (Standaard Actief!) -->
|
<!-- Dynamisch gevuld via renderStatusCheckboxes() -->
|
||||||
<button type="button" onclick="toggleHideClosed()" id="btnHideClosed" class="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" title="Verberg afgeronde en gesloten tickets">
|
</div>
|
||||||
<i data-lucide="eye-off" id="iconHideClosed" class="w-3.5 h-3.5 text-emerald-600"></i>
|
</div>
|
||||||
<span>Verberg Gesloten</span>
|
</div>
|
||||||
<span class="w-1.5 h-1.5 rounded-full bg-emerald-500"></span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<!-- Actieve filters reset knop als er iets gefilterd is -->
|
<!-- Actieve filters reset knop als er iets gefilterd is -->
|
||||||
<button type="button" onclick="resetAllFilters()" id="btnResetFiltersInline" class="hidden flex items-center space-x-1 px-2.5 py-1.5 rounded-xl text-xs font-semibold text-slate-500 hover:text-slate-800 dark:hover:text-slate-200 hover:bg-slate-100 dark:hover:bg-slate-800 transition cursor-pointer">
|
<button type="button" onclick="resetAllFilters()" id="btnResetFiltersInline" class="hidden flex items-center space-x-1 px-2.5 py-1.5 rounded-xl text-xs font-semibold text-slate-500 hover:text-slate-800 dark:hover:text-slate-200 hover:bg-slate-100 dark:hover:bg-slate-800 transition cursor-pointer">
|
||||||
@@ -866,6 +959,8 @@ $boardsJson = json_encode($boards, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT
|
|||||||
// Data vanuit PHP
|
// Data vanuit PHP
|
||||||
const jiraData = <?= $jiraDataJson ?>;
|
const jiraData = <?= $jiraDataJson ?>;
|
||||||
const availableBoards = <?= $boardsJson ?>;
|
const availableBoards = <?= $boardsJson ?>;
|
||||||
|
const projectStatusesData = <?= $projectStatusesJson ?>;
|
||||||
|
const allJiraStatuses = <?= $statusesJson ?>;
|
||||||
|
|
||||||
let currentProject = '';
|
let currentProject = '';
|
||||||
let currentQueue = (jiraData.user_queue_filters && jiraData.user_queue_filters.length > 0) ? 'MY_QUEUES' : (jiraData.default_queue_filter || '');
|
let currentQueue = (jiraData.user_queue_filters && jiraData.user_queue_filters.length > 0) ? 'MY_QUEUES' : (jiraData.default_queue_filter || '');
|
||||||
@@ -874,6 +969,21 @@ let activeUrgentOnly = false;
|
|||||||
let expandedKeys = new Set();
|
let expandedKeys = new Set();
|
||||||
const ticketStore = new Map();
|
const ticketStore = new Map();
|
||||||
|
|
||||||
|
// Hulpfunctie voor deduplicatie van tickets (assigned + team)
|
||||||
|
function getDeduplicatedIssues() {
|
||||||
|
const assigned = jiraData.assigned_issues || [];
|
||||||
|
const team = jiraData.team_issues || [];
|
||||||
|
const seen = new Set();
|
||||||
|
const list = [];
|
||||||
|
[...assigned, ...team].forEach(iss => {
|
||||||
|
if (iss && iss.key && !seen.has(iss.key)) {
|
||||||
|
seen.add(iss.key);
|
||||||
|
list.push(iss);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
function isDoneStatus(st) {
|
function isDoneStatus(st) {
|
||||||
const s = (st || '').toLowerCase().trim();
|
const s = (st || '').toLowerCase().trim();
|
||||||
return s.includes('done') || s.includes('gesloten') || s.includes('afgerond') || s.includes('gereed') || s.includes('closed') || s.includes('resolved');
|
return s.includes('done') || s.includes('gesloten') || s.includes('afgerond') || s.includes('gereed') || s.includes('closed') || s.includes('resolved');
|
||||||
@@ -949,8 +1059,8 @@ function updateActiveFilterBadge() {
|
|||||||
if (currentQueue && currentQueue !== 'MY_QUEUES') activeCount++;
|
if (currentQueue && currentQueue !== 'MY_QUEUES') activeCount++;
|
||||||
if (document.getElementById('filterType')?.value) activeCount++;
|
if (document.getElementById('filterType')?.value) activeCount++;
|
||||||
if (document.getElementById('filterPriority')?.value) activeCount++;
|
if (document.getElementById('filterPriority')?.value) activeCount++;
|
||||||
if (document.getElementById('filterStatus')?.value) activeCount++;
|
const total = getTotalStatusCount();
|
||||||
if (onlyInProgress) activeCount++;
|
if (selectedStatuses.size > 0 && selectedStatuses.size < total) activeCount++;
|
||||||
|
|
||||||
if (activeCount > 0) {
|
if (activeCount > 0) {
|
||||||
badge.innerText = activeCount;
|
badge.innerText = activeCount;
|
||||||
@@ -960,73 +1070,365 @@ function updateActiveFilterBadge() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wissel tussen tabs
|
// =========================================================================
|
||||||
function switchTab(tab) {
|
// STATUS MULTI-SELECT CHECKBOX DROPDOWN LOGICA (GEGROEPEERD PER PROJECT)
|
||||||
currentTab = tab;
|
// =========================================================================
|
||||||
activeUrgentOnly = false;
|
|
||||||
|
|
||||||
// Update tab knoppen styling
|
function getTotalStatusCount() {
|
||||||
const tabs = ['assigned', 'team', 'in_progress', 'done'];
|
let total = 0;
|
||||||
tabs.forEach(t => {
|
(projectStatusesData || []).forEach(p => {
|
||||||
const btn = document.getElementById(`tabBtn_${t}`);
|
total += (p.statuses || []).length;
|
||||||
if (!btn) return;
|
});
|
||||||
if (t === tab) {
|
return total;
|
||||||
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";
|
}
|
||||||
|
|
||||||
|
// Hulpfunctie: Bepaal of de status van een ticket geselecteerd is (ondersteunt compound keys en statusnamen)
|
||||||
|
function isIssueStatusSelected(iss) {
|
||||||
|
const total = getTotalStatusCount();
|
||||||
|
if (selectedStatuses.size === 0) return false;
|
||||||
|
if (selectedStatuses.size >= total) return true;
|
||||||
|
|
||||||
|
let issPKey = (iss.project_key || '').toUpperCase().trim();
|
||||||
|
if (!issPKey && iss.key) {
|
||||||
|
const m = iss.key.match(/^([A-Z0-9]+)-/i);
|
||||||
|
if (m) issPKey = m[1].toUpperCase();
|
||||||
|
}
|
||||||
|
const issStatus = (iss.status || '').toLowerCase().trim();
|
||||||
|
const compound = issPKey ? `${issPKey.toLowerCase()}::${issStatus}` : '';
|
||||||
|
|
||||||
|
if (compound && selectedStatuses.has(compound)) return true;
|
||||||
|
if (selectedStatuses.has(issStatus)) return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderStatusCheckboxes() {
|
||||||
|
const container = document.getElementById('statusCheckboxList');
|
||||||
|
if (!container) return;
|
||||||
|
|
||||||
|
if (!projectStatusesData || projectStatusesData.length === 0) {
|
||||||
|
container.innerHTML = '<div class="py-2 text-center text-slate-400 italic text-[11px]">Geen project statussen gevonden</div>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const colorClasses = {
|
||||||
|
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',
|
||||||
|
hdrBg: 'bg-sky-50/60 dark:bg-sky-950/30'
|
||||||
|
},
|
||||||
|
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',
|
||||||
|
hdrBg: 'bg-amber-50/60 dark:bg-amber-950/30'
|
||||||
|
},
|
||||||
|
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',
|
||||||
|
hdrBg: 'bg-emerald-50/60 dark:bg-emerald-950/30'
|
||||||
|
},
|
||||||
|
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',
|
||||||
|
hdrBg: 'bg-indigo-50/60 dark:bg-indigo-950/30'
|
||||||
|
},
|
||||||
|
slate: {
|
||||||
|
badge: 'bg-slate-100 dark:bg-slate-800 text-slate-800 dark:text-slate-200 border border-slate-300 dark:border-slate-700',
|
||||||
|
hdrBg: 'bg-slate-50 dark:bg-slate-800/40'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let html = '';
|
||||||
|
projectStatusesData.forEach((proj, pIdx) => {
|
||||||
|
const pKey = (proj.key || '').toUpperCase();
|
||||||
|
const pKeyLower = pKey.toLowerCase();
|
||||||
|
const pColor = proj.color || 'slate';
|
||||||
|
const styles = colorClasses[pColor] || colorClasses.slate;
|
||||||
|
const statuses = proj.statuses || [];
|
||||||
|
|
||||||
|
html += `
|
||||||
|
<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) => {
|
||||||
|
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 += `
|
||||||
|
<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">
|
||||||
|
<div class="flex items-center space-x-2.5 min-w-0 flex-1">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
id="${safeId}"
|
||||||
|
value="${escapeHtml(compoundVal)}"
|
||||||
|
data-project="${escapeHtml(pKey)}"
|
||||||
|
data-status="${escapeHtml(sNorm)}"
|
||||||
|
data-is-done="${isDone ? 1 : 0}"
|
||||||
|
${isChecked ? 'checked' : ''}
|
||||||
|
onchange="onStatusCheckboxChange()"
|
||||||
|
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"
|
||||||
|
>
|
||||||
|
<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>
|
||||||
|
</div>
|
||||||
|
<span id="statusCount_${safeKey}" class="text-[10px] font-bold px-1.5 py-0.5 rounded-full bg-slate-100 dark:bg-slate-800 text-slate-500 dark:text-slate-400 ml-2 flex-shrink-0">0</span>
|
||||||
|
</label>
|
||||||
|
`;
|
||||||
|
});
|
||||||
|
|
||||||
|
html += `
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
});
|
||||||
|
|
||||||
|
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 {
|
} else {
|
||||||
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";
|
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);
|
||||||
|
} else {
|
||||||
|
selectedStatuses.delete(val);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
localStorage.setItem('fociz_jira_selected_statuses', JSON.stringify([...selectedStatuses]));
|
||||||
|
} catch (e) {}
|
||||||
|
|
||||||
|
updateStatusDropdownLabel();
|
||||||
applyFilters();
|
applyFilters();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Toggle: Verberg Gesloten
|
function selectAllStatuses(selectAll) {
|
||||||
function toggleHideClosed() {
|
selectedStatuses.clear();
|
||||||
hideClosed = !hideClosed;
|
const chks = document.querySelectorAll('.status-checkbox');
|
||||||
updateHideClosedButton();
|
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();
|
||||||
applyFilters();
|
applyFilters();
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateHideClosedButton() {
|
function selectActiveStatusesOnly() {
|
||||||
const btn = document.getElementById('btnHideClosed');
|
selectedStatuses.clear();
|
||||||
if (!btn) return;
|
const chks = document.querySelectorAll('.status-checkbox');
|
||||||
if (hideClosed) {
|
chks.forEach(chk => {
|
||||||
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";
|
const isDone = chk.getAttribute('data-is-done') === '1';
|
||||||
btn.innerHTML = `<i data-lucide="eye-off" class="w-3.5 h-3.5 text-emerald-600"></i><span>Verberg Gesloten</span><span class="w-1.5 h-1.5 rounded-full bg-emerald-500"></span>`;
|
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";
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
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";
|
labelEl.innerText = `${selected} statussen`;
|
||||||
btn.innerHTML = `<i data-lucide="eye" class="w-3.5 h-3.5 text-slate-400"></i><span>Toon Ook Gesloten</span>`;
|
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');
|
||||||
}
|
}
|
||||||
if (typeof lucide !== 'undefined') lucide.createIcons();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Toggle: Snelfilter Alleen In Behandeling
|
function initStatusSelection() {
|
||||||
function toggleOnlyInProgress() {
|
selectedStatuses = new Set();
|
||||||
onlyInProgress = !onlyInProgress;
|
let saved = null;
|
||||||
updateOnlyInProgressButton();
|
try {
|
||||||
applyFilters();
|
const raw = localStorage.getItem('fociz_jira_selected_statuses');
|
||||||
}
|
if (raw) saved = JSON.parse(raw);
|
||||||
|
} catch (e) {}
|
||||||
|
|
||||||
function updateOnlyInProgressButton() {
|
let validSaved = false;
|
||||||
const btn = document.getElementById('btnOnlyInProgress');
|
if (Array.isArray(saved) && saved.length > 0) {
|
||||||
if (!btn) return;
|
const hasCompound = saved.some(s => String(s).includes('::'));
|
||||||
if (onlyInProgress) {
|
if (hasCompound) {
|
||||||
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";
|
saved.forEach(s => {
|
||||||
btn.innerHTML = `<i data-lucide="play" class="w-3 h-3 text-white fill-white"></i><span>In behandeling (Actief)</span>`;
|
if (s) selectedStatuses.add(String(s).toLowerCase().trim());
|
||||||
} else {
|
});
|
||||||
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";
|
validSaved = true;
|
||||||
btn.innerHTML = `<i data-lucide="play" class="w-3 h-3 text-blue-500"></i><span>In behandeling</span>`;
|
}
|
||||||
}
|
}
|
||||||
if (typeof lucide !== 'undefined') lucide.createIcons();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Status dropdown verandering
|
if (!validSaved) {
|
||||||
function onStatusSelectChange(val) {
|
// Standaard alle actieve statussen selecteren, gesloten uit conform gebruikerswens
|
||||||
if (val && isDoneStatus(val)) {
|
(projectStatusesData || []).forEach(proj => {
|
||||||
hideClosed = false;
|
const pKeyLower = (proj.key || '').toLowerCase().trim();
|
||||||
updateHideClosedButton();
|
(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}`);
|
||||||
}
|
}
|
||||||
applyFilters();
|
});
|
||||||
|
});
|
||||||
|
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}`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
renderStatusCheckboxes();
|
||||||
|
updateStatusDropdownLabel();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Project selectie via pill tabs
|
// Project selectie via pill tabs
|
||||||
@@ -1142,21 +1544,14 @@ function updateQueuePills() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function updateProjectCounts() {
|
function updateProjectCounts() {
|
||||||
const assigned = jiraData.assigned_issues || [];
|
const all = getDeduplicatedIssues();
|
||||||
const team = jiraData.team_issues || [];
|
const total = getTotalStatusCount();
|
||||||
const all = [...assigned, ...team];
|
|
||||||
|
|
||||||
let currentList = all;
|
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') {
|
if (selectedStatuses.size > 0 && selectedStatuses.size < total) {
|
||||||
currentList = currentList.filter(iss => !isDoneStatus(iss.status) && (iss.status_category || '').toLowerCase() !== 'done');
|
currentList = currentList.filter(iss => isIssueStatusSelected(iss));
|
||||||
|
} else if (selectedStatuses.size === 0) {
|
||||||
|
currentList = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
const elAll = document.getElementById('projCount_all');
|
const elAll = document.getElementById('projCount_all');
|
||||||
@@ -1181,21 +1576,14 @@ function updateProjectCounts() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function updateQueueCounts() {
|
function updateQueueCounts() {
|
||||||
const assigned = jiraData.assigned_issues || [];
|
const all = getDeduplicatedIssues();
|
||||||
const team = jiraData.team_issues || [];
|
const total = getTotalStatusCount();
|
||||||
const all = [...assigned, ...team];
|
|
||||||
|
|
||||||
let currentList = all;
|
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') {
|
if (selectedStatuses.size > 0 && selectedStatuses.size < total) {
|
||||||
currentList = currentList.filter(iss => !isDoneStatus(iss.status) && (iss.status_category || '').toLowerCase() !== 'done');
|
currentList = currentList.filter(iss => isIssueStatusSelected(iss));
|
||||||
|
} else if (selectedStatuses.size === 0) {
|
||||||
|
currentList = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentProject) {
|
if (currentProject) {
|
||||||
@@ -1303,12 +1691,11 @@ function applyFilters() {
|
|||||||
const filtered = all.filter(iss => {
|
const filtered = all.filter(iss => {
|
||||||
const issPKey = (iss.project_key || '').toUpperCase().trim();
|
const issPKey = (iss.project_key || '').toUpperCase().trim();
|
||||||
const issPName = (iss.project || '').toLowerCase().trim();
|
const issPName = (iss.project || '').toLowerCase().trim();
|
||||||
const issStatus = (iss.status || '').toLowerCase().trim();
|
|
||||||
const issCat = (iss.status_category || '').toLowerCase().trim();
|
|
||||||
|
|
||||||
// 1. Verberg Gesloten filter (als tab niet 'done' is en er niet expliciet een gesloten status gekozen is)
|
// 1. Status Filter via Checkboxes
|
||||||
if (currentTab !== 'done' && hideClosed && !isDoneStatus(statusVal)) {
|
const totalStatuses = getTotalStatusCount();
|
||||||
if (issCat === 'done' || isDoneStatus(issStatus)) {
|
if (selectedStatuses.size > 0 && selectedStatuses.size < totalStatuses) {
|
||||||
|
if (!isIssueStatusSelected(iss)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else if (selectedStatuses.size === 0) {
|
} else if (selectedStatuses.size === 0) {
|
||||||
|
|||||||
Reference in New Issue
Block a user