Jira 26
This commit is contained in:
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user