This commit is contained in:
2026-09-15 22:31:53 +02:00
parent 8498d82e10
commit 6bc351aaa4
4 changed files with 412 additions and 76 deletions
+220 -3
View File
@@ -26,6 +26,16 @@ $syncError = $jiraData['error'] ?? null;
$assignedIssues = $jiraData['assigned_issues'] ?? [];
$teamIssues = $jiraData['team_issues'] ?? [];
// Markeer expliciet welke tickets zijn toegewezen aan de ingelogde gebruiker
foreach ($assignedIssues as &$ai) {
$ai['is_assigned_to_me'] = true;
}
unset($ai);
$jiraData['assigned_issues'] = $assignedIssues;
$currentUserName = $currentUser['name'] ?? ($accountName ?: '');
$currentUserEmail = $currentUser['email'] ?? ($email ?: '');
// Haal borden en kolommen op voor het "Naar Bord" modal
$db = get_db_connection();
if ($isAdmin) {
@@ -306,6 +316,8 @@ $jiraDataJson = json_encode($jiraData, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_Q
$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);
$currentUserNameJson = json_encode($currentUserName, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP);
$currentUserEmailJson = json_encode($currentUserEmail, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP);
?>
<div class="space-y-6 w-full">
@@ -615,6 +627,17 @@ $statusesJson = json_encode($allStatusesList, JSON_HEX_TAG | JSON_HEX_APOS | JSO
<div id="projectStatusDropdownsWrapper" class="flex flex-wrap items-center gap-2">
</div>
<!-- Snelle optie: Toegewezen aan mij (uitvoerder) per geselecteerd project -->
<button type="button"
id="btnToggleAssignedToMePill"
onclick="toggleCurrentProjectOnlyAssigned()"
class="flex items-center space-x-1.5 px-2.5 sm:px-3 py-1.5 rounded-xl text-xs font-semibold border transition cursor-pointer shadow-2xs flex-shrink-0 bg-white dark:bg-slate-900 border-slate-200 dark:border-slate-700 text-slate-700 dark:text-slate-300 hover:border-slate-300 dark:hover:border-slate-600"
title="Filter op taken toegewezen aan mijzelf (uitvoerder)">
<i data-lucide="user-check" class="w-3.5 h-3.5 text-indigo-500 flex-shrink-0"></i>
<span id="labelAssignedToMePill">Toegewezen aan mij <span class="hidden sm:inline">(uitvoerder)</span></span>
<span id="badgeAssignedToMeCount" 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">0</span>
</button>
<!-- 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">
<i data-lucide="rotate-ccw" class="w-3 h-3"></i>
@@ -991,6 +1014,8 @@ const jiraData = <?= $jiraDataJson ?>;
const availableBoards = <?= $boardsJson ?>;
const projectStatusesData = <?= $projectStatusesJson ?>;
const allJiraStatuses = <?= $statusesJson ?>;
const currentUserName = <?= $currentUserNameJson ?>;
const currentUserEmail = <?= $currentUserEmailJson ?>;
let currentProject = '';
let currentQueue = '';
@@ -999,6 +1024,134 @@ let activeUrgentOnly = false;
let expandedKeys = new Set();
const ticketStore = new Map();
// Filter per project: 'Toegewezen aan mij (uitvoerder)'
let projectOnlyAssigned = new Set();
try {
const savedAssigned = localStorage.getItem('fociz_jira_project_only_assigned');
if (savedAssigned) {
const parsed = JSON.parse(savedAssigned);
if (Array.isArray(parsed)) {
parsed.forEach(p => projectOnlyAssigned.add(String(p).toUpperCase().trim()));
}
}
} catch (e) {}
// Set van alle aan mij toegewezen issue keys
const assignedIssueKeys = new Set((jiraData.assigned_issues || []).map(i => i.key));
function isIssueAssignedToMe(iss) {
if (!iss) return false;
if (iss.is_assigned_to_me === true) return true;
if (iss.key && assignedIssueKeys.has(iss.key)) return true;
const ass = (iss.assignee_name || '').toLowerCase().trim();
if (!ass || ass === 'niet toegewezen' || ass === 'unassigned') return false;
if (ass === 'mijzelf' || ass === 'myself' || ass === 'me') return true;
const myName = (currentUserName || jiraData.account_name || '').toLowerCase().trim();
const myEmail = (currentUserEmail || jiraData.email || '').toLowerCase().trim();
if (myName && (ass === myName || ass.includes(myName) || myName.includes(ass))) return true;
if (myEmail && (ass.includes(myEmail) || (iss.assignee_email && String(iss.assignee_email).toLowerCase().includes(myEmail)))) return true;
return false;
}
function isProjectOnlyAssignedActive(projectVal, issPKey) {
if (projectOnlyAssigned.has('ALL')) return true;
const pNorm = (projectVal || '').toUpperCase().trim();
const issPNorm = (issPKey || '').toUpperCase().trim();
if (pNorm && projectOnlyAssigned.has(pNorm)) return true;
if (!pNorm && issPNorm && projectOnlyAssigned.has(issPNorm)) return true;
return false;
}
function toggleProjectOnlyAssigned(pKey, forceVal) {
const key = (pKey || 'ALL').toUpperCase().trim();
const isCurrently = projectOnlyAssigned.has(key);
const willSet = (typeof forceVal === 'boolean') ? forceVal : !isCurrently;
if (willSet) {
projectOnlyAssigned.add(key);
} else {
projectOnlyAssigned.delete(key);
}
try {
localStorage.setItem('fociz_jira_project_only_assigned', JSON.stringify([...projectOnlyAssigned]));
} catch (e) {}
updateAssignedPillState();
updateStatusDropdownLabel();
updateStatusCounts();
applyFilters();
}
function toggleCurrentProjectOnlyAssigned() {
const targetKey = (currentProject || 'ALL').toUpperCase().trim();
toggleProjectOnlyAssigned(targetKey);
}
function updateAssignedPillState() {
const targetKey = (currentProject || 'ALL').toUpperCase().trim();
const isAssigned = projectOnlyAssigned.has(targetKey) || (currentProject === '' && projectOnlyAssigned.has('ALL'));
const btn = document.getElementById('btnToggleAssignedToMePill');
const badge = document.getElementById('badgeAssignedToMeCount');
const label = document.getElementById('labelAssignedToMePill');
if (!btn) return;
if (isAssigned) {
btn.className = "flex items-center space-x-1.5 px-2.5 sm:px-3 py-1.5 rounded-xl text-xs font-bold border border-indigo-600 bg-indigo-600 text-white shadow-xs flex-shrink-0 cursor-pointer";
if (badge) {
badge.className = "px-1.5 py-0.5 rounded-full text-[10px] font-black bg-white/20 text-white";
}
} else {
btn.className = "flex items-center space-x-1.5 px-2.5 sm:px-3 py-1.5 rounded-xl text-xs font-semibold border transition cursor-pointer shadow-2xs flex-shrink-0 bg-white dark:bg-slate-900 border-slate-200 dark:border-slate-700 text-slate-700 dark:text-slate-300 hover:border-slate-300 dark:hover:border-slate-600";
if (badge) {
badge.className = "px-1.5 py-0.5 rounded-full text-[10px] font-bold bg-slate-100 dark:bg-slate-800 text-slate-600 dark:text-slate-300";
}
}
if (label) {
if (currentProject) {
label.innerHTML = `Toegewezen aan mij <span class="text-[10px] opacity-80 uppercase font-mono">(${escapeHtml(currentProject)})</span>`;
} else {
label.innerHTML = `Toegewezen aan mij <span class="hidden sm:inline">(uitvoerder)</span>`;
}
}
const all = getDeduplicatedIssues();
let targetList = all;
if (currentProject) {
targetList = all.filter(iss => isIssueInQueue(iss, currentProject));
}
const myCount = targetList.filter(iss => isIssueAssignedToMe(iss)).length;
if (badge) {
badge.innerText = myCount;
}
(projectStatusesData || []).forEach(proj => {
const pKey = (proj.key || '').toUpperCase().trim();
const chk = document.getElementById(`chkOnlyAssigned_${pKey}`);
if (chk) {
chk.checked = projectOnlyAssigned.has(pKey) || projectOnlyAssigned.has('ALL');
}
const cCnt = all.filter(iss => {
const k = getIssueProjectKey(iss);
return (k === pKey || (!k && (iss.project || '').toUpperCase().includes(pKey))) && isIssueAssignedToMe(iss);
}).length;
const cntEl = document.getElementById(`projAssignedCount_${pKey}`);
if (cntEl) cntEl.innerText = cCnt;
const btnMijn = document.getElementById(`btnDropdownMijn_${pKey}`);
if (btnMijn) {
const isPActive = projectOnlyAssigned.has(pKey) || projectOnlyAssigned.has('ALL');
if (isPActive) {
btnMijn.className = "px-1.5 py-0.5 rounded font-bold bg-indigo-600 text-white transition cursor-pointer";
} else {
btnMijn.className = "px-1.5 py-0.5 rounded font-bold text-indigo-600 dark:text-indigo-400 hover:bg-indigo-50 dark:hover:bg-indigo-950/60 transition cursor-pointer";
}
}
});
}
// Hulpfunctie voor deduplicatie van tickets (assigned + team)
function getDeduplicatedIssues() {
const assigned = jiraData.assigned_issues || [];
@@ -1274,12 +1427,39 @@ function renderStatusCheckboxes() {
Actief
</button>
<span class="text-slate-300 dark:text-slate-700">•</span>
<button type="button" id="btnDropdownMijn_${escapeHtml(pKey)}" onclick="toggleProjectOnlyAssigned('${escapeHtml(pKey)}')" class="px-1.5 py-0.5 rounded font-bold ${projectOnlyAssigned.has(pKey) || projectOnlyAssigned.has('ALL') ? 'bg-indigo-600 text-white' : 'text-indigo-600 dark:text-indigo-400 hover:bg-indigo-50 dark:hover:bg-indigo-950/60'} transition cursor-pointer" title="Toegewezen aan mij (uitvoerder)">
Mijn
</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>
<!-- Toegewezen aan mij (uitvoerder) toggle card -->
<div class="px-3 py-2 -mx-3.5 bg-slate-50/80 dark:bg-slate-800/50 border-b border-slate-100 dark:border-slate-800/80 flex items-center justify-between">
<label for="chkOnlyAssigned_${escapeHtml(pKey)}" class="flex items-center space-x-2 cursor-pointer select-none min-w-0 flex-1 group">
<div class="w-6 h-6 rounded-lg ${projectOnlyAssigned.has(pKey) || projectOnlyAssigned.has('ALL') ? 'bg-indigo-600 text-white' : 'bg-slate-200 dark:bg-slate-700 text-slate-600 dark:text-slate-300'} flex items-center justify-center flex-shrink-0 transition">
<i data-lucide="user-check" class="w-3.5 h-3.5"></i>
</div>
<div class="min-w-0 flex-1">
<div class="text-xs font-bold text-slate-800 dark:text-slate-200 group-hover:text-indigo-600 dark:group-hover:text-indigo-400 transition truncate">Toegewezen aan mij (uitvoerder)</div>
<div class="text-[10px] text-slate-400">Alleen mijn taken in ${escapeHtml(pKey)}</div>
</div>
</label>
<div class="flex items-center space-x-2 flex-shrink-0 ml-2">
<span id="projAssignedCount_${escapeHtml(pKey)}" class="px-1.5 py-0.5 rounded-full text-[10px] font-black bg-indigo-100 dark:bg-indigo-950/70 text-indigo-700 dark:text-indigo-300">0</span>
<input
type="checkbox"
id="chkOnlyAssigned_${escapeHtml(pKey)}"
${projectOnlyAssigned.has(pKey) || projectOnlyAssigned.has('ALL') ? 'checked' : ''}
onchange="toggleProjectOnlyAssigned('${escapeHtml(pKey)}', this.checked)"
class="w-4 h-4 rounded text-indigo-600 border-slate-300 dark:border-slate-700 focus:ring-indigo-500 cursor-pointer"
>
</div>
</div>
<div class="max-h-60 overflow-y-auto space-y-1 pr-1 custom-scrollbar" id="statusCheckboxList_${escapeHtml(pKey)}">
${statusRowsHtml}
</div>
@@ -1293,6 +1473,7 @@ function renderStatusCheckboxes() {
updateStatusCounts();
updateStatusDropdownLabel();
updateProjectStatusDropdownVisibility();
updateAssignedPillState();
}
function updateProjectStatusDropdownVisibility() {
@@ -1386,6 +1567,11 @@ function updateStatusCounts() {
projIssues = projIssues.filter(iss => isIssueInQueue(iss, currentProject));
}
// Toegewezen aan mij filter per project meenemen in de status tellingen
if (isProjectOnlyAssignedActive(currentProject, pKey)) {
projIssues = projIssues.filter(iss => isIssueAssignedToMe(iss));
}
const projTotalEl = document.getElementById(`projTotal_${pKey}`);
if (projTotalEl) {
projTotalEl.innerText = `(${projIssues.length})`;
@@ -1401,6 +1587,8 @@ function updateStatusCounts() {
}
});
});
updateAssignedPillState();
}
function onStatusCheckboxChange(projectKey) {
@@ -1556,11 +1744,18 @@ function updateStatusDropdownLabel() {
badgeEl.className = "px-1.5 py-0.5 rounded-full text-[10px] font-bold bg-indigo-100 dark:bg-indigo-950/60 text-indigo-700 dark:text-indigo-300";
}
}
// Toon indicator indien Toegewezen aan mij actief is voor dit project
const isAssignedOnly = projectOnlyAssigned.has(pKey) || projectOnlyAssigned.has('ALL');
if (isAssignedOnly) {
hasAnyCustomFilter = true;
labelEl.innerText += " • Mijn";
}
});
const btnReset = document.getElementById('btnResetFiltersInline');
if (btnReset) {
const isCustom = hasAnyCustomFilter || currentProject !== '' || document.getElementById('jiraSearchInput')?.value;
const isCustom = hasAnyCustomFilter || currentProject !== '' || projectOnlyAssigned.size > 0 || document.getElementById('jiraSearchInput')?.value;
if (isCustom) btnReset.classList.remove('hidden');
else btnReset.classList.add('hidden');
}
@@ -1628,6 +1823,7 @@ function selectProject(proj) {
closeAllProjectStatusDropdowns();
updateProjectPills();
updateProjectStatusDropdownVisibility();
updateAssignedPillState();
updateStatusCounts();
applyFilters();
}
@@ -1942,7 +2138,13 @@ function updateProjectCounts() {
}
const elAll = document.getElementById('projCount_all');
if (elAll) elAll.innerText = currentList.length;
if (elAll) {
let allFiltered = currentList;
if (projectOnlyAssigned.has('ALL')) {
allFiltered = allFiltered.filter(iss => isIssueAssignedToMe(iss));
}
elAll.innerText = allFiltered.length;
}
const pills = document.querySelectorAll('.project-pill');
pills.forEach(p => {
@@ -1950,7 +2152,11 @@ function updateProjectCounts() {
if (!pKey) return;
const el = document.getElementById(`projCount_${pKey}`);
if (el) {
el.innerText = currentList.filter(iss => isIssueInQueue(iss, pKey)).length;
let pList = currentList.filter(iss => isIssueInQueue(iss, pKey));
if (projectOnlyAssigned.has(pKey.toUpperCase()) || projectOnlyAssigned.has('ALL')) {
pList = pList.filter(iss => isIssueAssignedToMe(iss));
}
el.innerText = pList.length;
}
});
}
@@ -1995,6 +2201,10 @@ function clearSearch() {
function resetAllFilters() {
currentProject = '';
currentQueue = '';
projectOnlyAssigned.clear();
try {
localStorage.removeItem('fociz_jira_project_only_assigned');
} catch (e) {}
closeAllProjectStatusDropdowns();
selectAllStatuses(true);
@@ -2009,6 +2219,7 @@ function resetAllFilters() {
document.getElementById('clearSearchBtn')?.classList.add('hidden');
updateProjectPills();
updateProjectStatusDropdownVisibility();
updateAssignedPillState();
updateStatusCounts();
applyFilters();
}
@@ -2048,6 +2259,12 @@ function applyFilters() {
}
}
// 3. Toegewezen aan mij (uitvoerder) per project filter
const isAssignedFilterActive = isProjectOnlyAssignedActive(projectVal, issPKey);
if (isAssignedFilterActive && !isIssueAssignedToMe(iss)) {
return false;
}
// 4. Type filter (uit drawer)
if (typeVal && (iss.issue_type || '').toLowerCase() !== typeVal) {
return false;