Jira bord changes
This commit is contained in:
@@ -56,16 +56,22 @@ foreach ($boards as &$b) {
|
||||
}
|
||||
unset($b);
|
||||
|
||||
// Verzamel unieke projecten en types voor filter dropdowns
|
||||
// Verzamel unieke projecten en types voor filter dropdowns en project switcher
|
||||
$allIssues = array_merge($assignedIssues, $teamIssues);
|
||||
$allProjects = [];
|
||||
$allTypes = [];
|
||||
$allPriorities = ['Highest', 'High', 'Medium', 'Low'];
|
||||
$allStatuses = [];
|
||||
$projectCounts = [];
|
||||
|
||||
foreach ($allIssues as $iss) {
|
||||
if (!empty($iss['project'])) {
|
||||
$allProjects[$iss['project']] = $iss['project'];
|
||||
$p = $iss['project'];
|
||||
$allProjects[$p] = [
|
||||
'name' => $p,
|
||||
'key' => $iss['project_key'] ?? '',
|
||||
];
|
||||
$projectCounts[$p] = ($projectCounts[$p] ?? 0) + 1;
|
||||
}
|
||||
if (!empty($iss['issue_type'])) {
|
||||
$allTypes[$iss['issue_type']] = $iss['issue_type'];
|
||||
@@ -78,6 +84,9 @@ ksort($allProjects);
|
||||
ksort($allTypes);
|
||||
ksort($allStatuses);
|
||||
|
||||
$queueFilters = $jiraData['queue_filters'] ?? jira_get_queue_filters(true);
|
||||
$defaultQueueFilter = $jiraData['default_queue_filter'] ?? jira_get_user_queue_preference($userId);
|
||||
|
||||
$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);
|
||||
?>
|
||||
@@ -229,6 +238,97 @@ $boardsJson = json_encode($boards, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 1. Snelle Project Switcher (Pill Tabs) -->
|
||||
<div class="fociz-card p-4 sm:p-5">
|
||||
<div class="flex flex-col sm:flex-row sm:items-center justify-between gap-2 pb-3 mb-3 border-b border-slate-100 dark:border-slate-800/80">
|
||||
<div class="flex items-center space-x-2.5">
|
||||
<div class="w-8 h-8 rounded-xl bg-indigo-50 dark:bg-indigo-950/50 text-indigo-600 dark:text-indigo-400 flex items-center justify-center font-bold">
|
||||
<i data-lucide="folder-kanban" class="w-4 h-4"></i>
|
||||
</div>
|
||||
<div>
|
||||
<h2 class="text-xs font-bold text-slate-900 dark:text-white uppercase tracking-wider">Jira Projecten</h2>
|
||||
<p class="text-[11px] text-slate-500 dark:text-slate-400">Klik op een project om direct te filteren</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-[11px] text-slate-400 hidden sm:block">
|
||||
<span><?= count($allProjects) ?> actieve projecten gevonden</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-2 overflow-x-auto pb-1" id="projectPillsContainer">
|
||||
<!-- Alle Projecten Pill -->
|
||||
<button type="button" onclick="selectProject('')" data-project="" class="project-pill flex items-center space-x-2 px-3.5 py-2 rounded-xl text-xs font-bold transition cursor-pointer bg-indigo-600 text-white shadow-sm shadow-indigo-600/20 flex-shrink-0">
|
||||
<i data-lucide="layers" class="w-3.5 h-3.5"></i>
|
||||
<span>Alle Projecten</span>
|
||||
<span class="px-2 py-0.5 rounded-full text-[10px] bg-white/20 text-white font-black"><?= count($allIssues) ?></span>
|
||||
</button>
|
||||
|
||||
<!-- Per project pills -->
|
||||
<?php foreach ($allProjects as $pName => $pData): ?>
|
||||
<button type="button" onclick="selectProject('<?= htmlspecialchars(addslashes($pName)) ?>')" data-project="<?= htmlspecialchars($pName) ?>" class="project-pill flex items-center space-x-2 px-3.5 py-2 rounded-xl text-xs font-bold transition cursor-pointer bg-slate-100 dark:bg-slate-800/80 text-slate-700 dark:text-slate-300 hover:bg-slate-200 dark:hover:bg-slate-700/80 border border-slate-200/80 dark:border-slate-700/80 flex-shrink-0">
|
||||
<?php if (!empty($pData['key'])): ?>
|
||||
<span class="px-1.5 py-0.5 rounded text-[10px] font-mono font-black bg-indigo-100 dark:bg-indigo-950/80 text-indigo-700 dark:text-indigo-300"><?= htmlspecialchars($pData['key']) ?></span>
|
||||
<?php else: ?>
|
||||
<i data-lucide="folder" class="w-3.5 h-3.5 text-slate-400"></i>
|
||||
<?php endif; ?>
|
||||
<span><?= htmlspecialchars($pName) ?></span>
|
||||
<span class="px-1.5 py-0.5 rounded-full text-[10px] font-bold bg-slate-200 dark:bg-slate-700 text-slate-600 dark:text-slate-300"><?= $projectCounts[$pName] ?? 0 ?></span>
|
||||
</button>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 2. Servicedesk Wachtrijfilter Sub-balk (DX / SD) -->
|
||||
<div id="queueFiltersBar" class="fociz-card p-4 bg-gradient-to-r from-slate-50 via-indigo-50/30 to-sky-50/20 dark:from-[#0c0f17] dark:via-indigo-950/20 dark:to-sky-950/20 border-indigo-200/70 dark:border-indigo-900/40">
|
||||
<div class="flex flex-col md:flex-row md:items-center justify-between gap-3">
|
||||
<div class="flex items-center space-x-3">
|
||||
<div class="w-8 h-8 rounded-xl bg-indigo-500/15 text-indigo-600 dark:text-indigo-400 flex items-center justify-center font-bold flex-shrink-0">
|
||||
<i data-lucide="filter" class="w-4 h-4"></i>
|
||||
</div>
|
||||
<div>
|
||||
<div class="flex items-center space-x-2">
|
||||
<span class="text-xs font-bold text-slate-900 dark:text-white">Servicedesk Wachtrijfilters</span>
|
||||
<?php if (!empty($defaultQueueFilter)): ?>
|
||||
<span class="text-[10px] font-bold px-2 py-0.5 rounded-full bg-indigo-100 text-indigo-800 dark:bg-indigo-950/70 dark:text-indigo-300 border border-indigo-200 dark:border-indigo-800/80">
|
||||
Jouw voorkeur: <?= htmlspecialchars($defaultQueueFilter) ?>
|
||||
</span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<p class="text-[11px] text-slate-500 dark:text-slate-400">
|
||||
Filtert op onderwerp logging routering (DX voor koppelingen & endpoints, SD voor reguliere support)
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Knoppen rij voor wachtrijen -->
|
||||
<div class="flex items-center gap-2 overflow-x-auto" id="queueButtonsContainer">
|
||||
<button type="button" onclick="selectQueueFilter('')" data-queue="" 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-slate-200/80 dark:bg-slate-800 text-slate-700 dark:text-slate-300 flex-shrink-0">
|
||||
<i data-lucide="list" class="w-3.5 h-3.5"></i>
|
||||
<span>Alle Wachtrijen</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 type="button" onclick="selectQueueFilter('DX')" data-queue="DX" 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-slate-100 dark:bg-slate-800/80 text-slate-700 dark:text-slate-300 hover:bg-slate-200 dark:hover:bg-slate-700/80 border border-slate-200/80 dark:border-slate-700/80 flex-shrink-0">
|
||||
<i data-lucide="zap" class="w-3.5 h-3.5 text-amber-500"></i>
|
||||
<span>DX Wachtrij (Koppelingen)</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 type="button" onclick="selectQueueFilter('SD')" data-queue="SD" 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-slate-100 dark:bg-slate-800/80 text-slate-700 dark:text-slate-300 hover:bg-slate-200 dark:hover:bg-slate-700/80 border border-slate-200/80 dark:border-slate-700/80 flex-shrink-0">
|
||||
<i data-lucide="wrench" class="w-3.5 h-3.5 text-sky-500"></i>
|
||||
<span>SD Wachtrij (Regulier)</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>
|
||||
|
||||
<?php if ($isAdmin): ?>
|
||||
<a href="/admin.php#jira-wachtrijen" title="Wachtrijfilters beheren in Admin" class="p-1.5 rounded-xl text-slate-400 hover:text-slate-700 dark:hover:text-slate-200 hover:bg-slate-200/60 dark:hover:bg-slate-800 transition cursor-pointer flex-shrink-0">
|
||||
<i data-lucide="settings" class="w-3.5 h-3.5"></i>
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Filterbalk & Zoekveld -->
|
||||
<div class="fociz-card p-4">
|
||||
<div class="grid grid-cols-1 md:grid-cols-12 gap-3">
|
||||
@@ -250,10 +350,10 @@ $boardsJson = json_encode($boards, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT
|
||||
|
||||
<!-- Filter: Project -->
|
||||
<div class="md:col-span-2">
|
||||
<select id="filterProject" onchange="applyFilters()" class="w-full bg-slate-50 dark:bg-slate-900 border border-slate-200 dark:border-slate-800 rounded-xl px-3 py-2 text-xs text-slate-900 dark:text-white focus:outline-none focus:ring-2 focus:ring-indigo-500/40 cursor-pointer">
|
||||
<select id="filterProject" onchange="onProjectSelectChange(this.value)" class="w-full bg-slate-50 dark:bg-slate-900 border border-slate-200 dark:border-slate-800 rounded-xl px-3 py-2 text-xs text-slate-900 dark:text-white focus:outline-none focus:ring-2 focus:ring-indigo-500/40 cursor-pointer">
|
||||
<option value="">Alle projecten</option>
|
||||
<?php foreach ($allProjects as $p): ?>
|
||||
<option value="<?= htmlspecialchars($p) ?>"><?= htmlspecialchars($p) ?></option>
|
||||
<?php foreach ($allProjects as $pName => $pData): ?>
|
||||
<option value="<?= htmlspecialchars($pName) ?>"><?= htmlspecialchars($pName) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
@@ -573,7 +673,10 @@ const jiraData = <?= $jiraDataJson ?>;
|
||||
const availableBoards = <?= $boardsJson ?>;
|
||||
|
||||
let currentTab = 'assigned'; // 'assigned', 'team', 'in_progress', 'done'
|
||||
let currentProject = '';
|
||||
let currentQueue = jiraData.default_queue_filter || '';
|
||||
let activeUrgentOnly = false;
|
||||
let expandedKeys = new Set();
|
||||
|
||||
// Bereken statussen en KPI statistieken
|
||||
function calculateKpis() {
|
||||
@@ -629,9 +732,97 @@ function switchTab(tab) {
|
||||
}
|
||||
});
|
||||
|
||||
updateQueueCounts();
|
||||
applyFilters();
|
||||
}
|
||||
|
||||
// Project selectie via pill tabs
|
||||
function selectProject(proj) {
|
||||
currentProject = proj;
|
||||
const pSelect = document.getElementById('filterProject');
|
||||
if (pSelect) {
|
||||
pSelect.value = proj;
|
||||
}
|
||||
updateProjectPills();
|
||||
updateQueueCounts();
|
||||
applyFilters();
|
||||
}
|
||||
|
||||
// Project selectie via dropdown
|
||||
function onProjectSelectChange(proj) {
|
||||
currentProject = proj;
|
||||
updateProjectPills();
|
||||
updateQueueCounts();
|
||||
applyFilters();
|
||||
}
|
||||
|
||||
function updateProjectPills() {
|
||||
const pills = document.querySelectorAll('.project-pill');
|
||||
pills.forEach(p => {
|
||||
const pKey = p.getAttribute('data-project') || '';
|
||||
if (pKey === currentProject) {
|
||||
p.className = "project-pill flex items-center space-x-2 px-3.5 py-2 rounded-xl text-xs font-bold transition cursor-pointer bg-indigo-600 text-white shadow-sm shadow-indigo-600/20 flex-shrink-0";
|
||||
} else {
|
||||
p.className = "project-pill flex items-center space-x-2 px-3.5 py-2 rounded-xl text-xs font-bold transition cursor-pointer bg-slate-100 dark:bg-slate-800/80 text-slate-700 dark:text-slate-300 hover:bg-slate-200 dark:hover:bg-slate-700/80 border border-slate-200/80 dark:border-slate-700/80 flex-shrink-0";
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Wachtrijfilter selectie
|
||||
function selectQueueFilter(code) {
|
||||
currentQueue = code;
|
||||
updateQueuePills();
|
||||
applyFilters();
|
||||
}
|
||||
|
||||
function updateQueuePills() {
|
||||
const pills = document.querySelectorAll('.queue-pill');
|
||||
pills.forEach(p => {
|
||||
const qCode = p.getAttribute('data-queue') || '';
|
||||
if (qCode === currentQueue) {
|
||||
p.className = "queue-pill flex items-center space-x-1.5 px-3 py-1.5 rounded-xl text-xs font-bold transition cursor-pointer bg-indigo-600 text-white shadow-sm shadow-indigo-600/20 flex-shrink-0";
|
||||
} else {
|
||||
p.className = "queue-pill flex items-center space-x-1.5 px-3 py-1.5 rounded-xl text-xs font-bold transition cursor-pointer bg-slate-100 dark:bg-slate-800/80 text-slate-700 dark:text-slate-300 hover:bg-slate-200 dark:hover:bg-slate-700/80 border border-slate-200/80 dark:border-slate-700/80 flex-shrink-0";
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Update de tellers op de DX / SD wachtrijfilter knoppen
|
||||
function updateQueueCounts() {
|
||||
const assigned = jiraData.assigned_issues || [];
|
||||
const team = jiraData.team_issues || [];
|
||||
const all = [...assigned, ...team];
|
||||
|
||||
let currentList = all;
|
||||
if (currentTab === 'assigned') currentList = assigned;
|
||||
else if (currentTab === 'team') currentList = team;
|
||||
|
||||
if (currentProject) {
|
||||
currentList = currentList.filter(iss => iss.project === currentProject);
|
||||
}
|
||||
|
||||
const dxValues = ['koppelingen', 'sso-koppeling', 'rest-endpoints', 'datadump', 'uwv-koppeling', 'migratie'];
|
||||
let dxCount = 0;
|
||||
let sdCount = 0;
|
||||
|
||||
currentList.forEach(iss => {
|
||||
const ol = (iss.onderwerp_logging || '').toLowerCase().trim();
|
||||
if (dxValues.includes(ol)) {
|
||||
dxCount++;
|
||||
} else {
|
||||
sdCount++;
|
||||
}
|
||||
});
|
||||
|
||||
const elAll = document.getElementById('queueCount_all');
|
||||
const elDx = document.getElementById('queueCount_DX');
|
||||
const elSd = document.getElementById('queueCount_SD');
|
||||
|
||||
if (elAll) elAll.innerText = currentList.length;
|
||||
if (elDx) elDx.innerText = dxCount;
|
||||
if (elSd) elSd.innerText = sdCount;
|
||||
}
|
||||
|
||||
// Snelkoppeling KPI-kaart Urgent
|
||||
function filterByUrgent() {
|
||||
const pSelect = document.getElementById('filterPriority');
|
||||
@@ -653,6 +844,8 @@ function clearSearch() {
|
||||
|
||||
// Alle filters herstellen
|
||||
function resetAllFilters() {
|
||||
currentProject = '';
|
||||
currentQueue = '';
|
||||
const searchInput = document.getElementById('jiraSearchInput');
|
||||
const projectSelect = document.getElementById('filterProject');
|
||||
const typeSelect = document.getElementById('filterType');
|
||||
@@ -665,14 +858,17 @@ function resetAllFilters() {
|
||||
if (prioritySelect) prioritySelect.value = '';
|
||||
if (statusSelect) statusSelect.value = '';
|
||||
|
||||
document.getElementById('clearSearchBtn').classList.add('hidden');
|
||||
document.getElementById('clearSearchBtn')?.classList.add('hidden');
|
||||
updateProjectPills();
|
||||
updateQueuePills();
|
||||
updateQueueCounts();
|
||||
applyFilters();
|
||||
}
|
||||
|
||||
// Filter en render logic
|
||||
function applyFilters() {
|
||||
const searchVal = (document.getElementById('jiraSearchInput')?.value || '').trim().toLowerCase();
|
||||
const projectVal = document.getElementById('filterProject')?.value || '';
|
||||
const projectVal = currentProject || (document.getElementById('filterProject')?.value || '');
|
||||
const typeVal = document.getElementById('filterType')?.value || '';
|
||||
const priorityVal = document.getElementById('filterPriority')?.value || '';
|
||||
const statusVal = document.getElementById('filterStatus')?.value || '';
|
||||
@@ -706,19 +902,42 @@ function applyFilters() {
|
||||
});
|
||||
}
|
||||
|
||||
// Pas zoek- en selectiefilters toe
|
||||
const dxValues = ['koppelingen', 'sso-koppeling', 'rest-endpoints', 'datadump', 'uwv-koppeling', 'migratie'];
|
||||
|
||||
// Pas zoek-, project-, wachtrij-, type-, prioriteit- en statusfilters toe
|
||||
const filtered = sourceList.filter(iss => {
|
||||
if (searchVal) {
|
||||
const keyMatch = (iss.key || '').toLowerCase().includes(searchVal);
|
||||
const sumMatch = (iss.summary || '').toLowerCase().includes(searchVal);
|
||||
const prjMatch = (iss.project || '').toLowerCase().includes(searchVal);
|
||||
if (!keyMatch && !sumMatch && !prjMatch) return false;
|
||||
const descMatch = (iss.description || '').toLowerCase().includes(searchVal);
|
||||
const repMatch = (iss.reporter_name || '').toLowerCase().includes(searchVal);
|
||||
const olMatch = (iss.onderwerp_logging || '').toLowerCase().includes(searchVal);
|
||||
if (!keyMatch && !sumMatch && !prjMatch && !descMatch && !repMatch && !olMatch) return false;
|
||||
}
|
||||
|
||||
if (projectVal && iss.project !== projectVal) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (currentQueue) {
|
||||
const qCode = currentQueue.toUpperCase();
|
||||
const ol = (iss.onderwerp_logging || '').toLowerCase().trim();
|
||||
|
||||
if (qCode === 'DX') {
|
||||
if (!dxValues.includes(ol)) return false;
|
||||
} else if (qCode === 'SD') {
|
||||
if (ol !== '' && ol !== 'empty' && dxValues.includes(ol)) return false;
|
||||
} else {
|
||||
const fObj = (jiraData.queue_filters || []).find(f => f.filter_code.toUpperCase() === qCode);
|
||||
if (fObj) {
|
||||
if (ol && !ol.includes(qCode.toLowerCase()) && !fObj.jql_clause.toLowerCase().includes(ol)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (typeVal && iss.issue_type !== typeVal) {
|
||||
return false;
|
||||
}
|
||||
@@ -741,6 +960,36 @@ function applyFilters() {
|
||||
renderTickets(filtered);
|
||||
}
|
||||
|
||||
// Uitklappen en inklappen van ticket details
|
||||
function toggleTicketDetails(key, event) {
|
||||
if (event) {
|
||||
event.stopPropagation();
|
||||
}
|
||||
const details = document.getElementById(`details_${key}`);
|
||||
const chevron = document.getElementById(`chevron_${key}`);
|
||||
if (!details) return;
|
||||
|
||||
if (details.classList.contains('hidden')) {
|
||||
details.classList.remove('hidden');
|
||||
if (chevron) chevron.classList.add('rotate-180');
|
||||
expandedKeys.add(key);
|
||||
} else {
|
||||
details.classList.add('hidden');
|
||||
if (chevron) chevron.classList.remove('rotate-180');
|
||||
expandedKeys.delete(key);
|
||||
}
|
||||
if (typeof lucide !== 'undefined') lucide.createIcons();
|
||||
}
|
||||
|
||||
function copyTicketKey(key) {
|
||||
navigator.clipboard.writeText(key).then(() => {
|
||||
showToast({
|
||||
message: `Ticket ${key} gekopieerd!`,
|
||||
subMessage: 'De ticketsleutel is direct naar je klembord gekopieerd.'
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Renders the list of tickets
|
||||
function renderTickets(tickets) {
|
||||
const container = document.getElementById('ticketsContainer');
|
||||
@@ -842,11 +1091,38 @@ function renderTickets(tickets) {
|
||||
`;
|
||||
}
|
||||
|
||||
// Onderwerp logging badge
|
||||
let onderwerpBadge = '<span class="text-slate-400 italic font-normal">Niet ingevuld</span>';
|
||||
if (ticket.onderwerp_logging) {
|
||||
const olLower = ticket.onderwerp_logging.toLowerCase();
|
||||
const dxList = ['koppelingen', 'sso-koppeling', 'rest-endpoints', 'datadump', 'uwv-koppeling', 'migratie'];
|
||||
const isDx = dxList.includes(olLower);
|
||||
if (isDx) {
|
||||
onderwerpBadge = `
|
||||
<span class="inline-flex items-center space-x-1 px-2 py-0.5 rounded-md text-[11px] font-bold bg-amber-50 text-amber-800 dark:bg-amber-950/60 dark:text-amber-300 border border-amber-300 dark:border-amber-800/80">
|
||||
<i data-lucide="zap" class="w-3 h-3 text-amber-500"></i>
|
||||
<span>DX: ${escapeHtml(ticket.onderwerp_logging)}</span>
|
||||
</span>
|
||||
`;
|
||||
} else {
|
||||
onderwerpBadge = `
|
||||
<span class="inline-flex items-center space-x-1 px-2 py-0.5 rounded-md text-[11px] font-bold bg-sky-50 text-sky-800 dark:bg-sky-950/60 dark:text-sky-300 border border-sky-300 dark:border-sky-800/80">
|
||||
<i data-lucide="wrench" class="w-3 h-3 text-sky-500"></i>
|
||||
<span>SD: ${escapeHtml(ticket.onderwerp_logging)}</span>
|
||||
</span>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
const isExpanded = expandedKeys.has(ticket.key);
|
||||
const detailsClass = isExpanded ? '' : 'hidden';
|
||||
const chevronRotateClass = isExpanded ? 'rotate-180' : '';
|
||||
|
||||
// JSON stringified ticket data voor modalaanroep
|
||||
const ticketSafeJson = JSON.stringify(ticket).replace(/"/g, '"');
|
||||
|
||||
html += `
|
||||
<div class="fociz-card p-4 sm:p-5 transition hover:border-indigo-500/40 dark:hover:border-indigo-500/40">
|
||||
<div id="ticketCard_${escapeHtml(ticket.key)}" class="fociz-card p-4 sm:p-5 transition hover:border-indigo-500/40 dark:hover:border-indigo-500/40">
|
||||
<div class="flex flex-col lg:flex-row lg:items-center justify-between gap-3">
|
||||
|
||||
<!-- Linkerkant: Metadata & Titel -->
|
||||
@@ -888,23 +1164,29 @@ function renderTickets(tickets) {
|
||||
${dueDateHtml}
|
||||
</div>
|
||||
|
||||
<!-- Ticket Titel / Summary -->
|
||||
<h3 class="text-sm sm:text-base font-bold text-slate-900 dark:text-white leading-snug">
|
||||
<a href="${escapeHtml(ticket.url)}" target="_blank" rel="noopener noreferrer" class="hover:text-indigo-600 dark:hover:text-indigo-400 transition">
|
||||
<!-- Ticket Titel / Summary (Klikbaar voor accordion) -->
|
||||
<h3 class="text-sm sm:text-base font-bold text-slate-900 dark:text-white leading-snug cursor-pointer group" onclick="toggleTicketDetails('${escapeHtml(ticket.key)}', event)">
|
||||
<span class="group-hover:text-indigo-600 dark:group-hover:text-indigo-400 transition">
|
||||
${escapeHtml(ticket.summary || '(Geen titel)')}
|
||||
</a>
|
||||
</span>
|
||||
</h3>
|
||||
|
||||
<!-- Sub-info: Assignee & Update timestamp -->
|
||||
<!-- Sub-info: Assignee, Update timestamp & Inklap knop -->
|
||||
<div class="flex flex-wrap items-center gap-4 pt-1">
|
||||
${assigneeHtml}
|
||||
${ticket.updated ? `<span class="text-[11px] text-slate-400">Gewijzigd: ${escapeHtml(ticket.updated)}</span>` : ''}
|
||||
${ticket.onderwerp_logging ? onderwerpBadge : ''}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Rechterkant: Snelle Actieknoppen -->
|
||||
<!-- Rechterkant: Snelle Actieknoppen + Uitklapknop -->
|
||||
<div class="flex items-center space-x-2 pt-2 lg:pt-0 flex-shrink-0 border-t lg:border-t-0 border-slate-100 dark:border-slate-800">
|
||||
<button type="button" onclick="toggleTicketDetails('${escapeHtml(ticket.key)}', event)" class="flex items-center space-x-1 px-2.5 py-2 rounded-xl text-xs font-bold text-slate-600 dark:text-slate-300 hover:text-indigo-600 dark:hover:text-indigo-400 bg-slate-100 hover:bg-slate-200 dark:bg-slate-800 dark:hover:bg-slate-700/80 transition cursor-pointer shadow-2xs">
|
||||
<span>Details</span>
|
||||
<i data-lucide="chevron-down" id="chevron_${escapeHtml(ticket.key)}" class="w-3.5 h-3.5 transition-transform duration-200 ${chevronRotateClass}"></i>
|
||||
</button>
|
||||
|
||||
<button onclick='openPlanModal(${ticketSafeJson})' class="flex items-center space-x-1.5 px-3 py-2 rounded-xl text-xs font-bold bg-indigo-50 hover:bg-indigo-100 text-indigo-700 dark:bg-indigo-950/50 dark:hover:bg-indigo-900/60 dark:text-indigo-300 border border-indigo-200/80 dark:border-indigo-800/80 transition cursor-pointer shadow-2xs">
|
||||
<i data-lucide="calendar" class="w-3.5 h-3.5"></i>
|
||||
<span>Plan in Dagplanner</span>
|
||||
@@ -921,6 +1203,79 @@ function renderTickets(tickets) {
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- UITKLAPBAAR PANEEL (Accordion Details) -->
|
||||
<div id="details_${escapeHtml(ticket.key)}" class="${detailsClass} mt-4 pt-4 border-t border-slate-100 dark:border-slate-800 space-y-4 text-xs">
|
||||
|
||||
<!-- Beschrijving -->
|
||||
<div class="space-y-1.5">
|
||||
<div class="flex items-center space-x-1.5 font-bold text-slate-700 dark:text-slate-300">
|
||||
<i data-lucide="align-left" class="w-3.5 h-3.5 text-indigo-500"></i>
|
||||
<span>Volledige Ticket Omschrijving</span>
|
||||
</div>
|
||||
<div class="p-4 rounded-xl bg-slate-50 dark:bg-slate-900/90 border border-slate-200/80 dark:border-slate-800 text-slate-800 dark:text-slate-200 leading-relaxed whitespace-pre-wrap select-text font-normal">
|
||||
${escapeHtml(ticket.description || 'Geen uitgebreide omschrijving beschikbaar in Jira.')}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Metadata Grid (Reporter, Aanmaakdatum, Wijziging, Onderwerp) -->
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-3 p-3.5 rounded-xl bg-slate-50/60 dark:bg-slate-950/40 border border-slate-200/60 dark:border-slate-800/60 text-[11px]">
|
||||
<div>
|
||||
<span class="text-slate-400 block mb-1 font-medium">Aangemeld door (Reporter):</span>
|
||||
<div class="flex items-center space-x-1.5 font-semibold text-slate-800 dark:text-slate-200">
|
||||
<div class="w-4 h-4 rounded-full bg-slate-200 dark:bg-slate-700 flex items-center justify-center font-bold text-[9px] flex-shrink-0">
|
||||
${escapeHtml((ticket.reporter_name || 'J').charAt(0).toUpperCase())}
|
||||
</div>
|
||||
<span class="truncate">${escapeHtml(ticket.reporter_name || 'Onbekend')}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<span class="text-slate-400 block mb-1 font-medium">Aangemaakt op:</span>
|
||||
<span class="font-medium text-slate-700 dark:text-slate-300">
|
||||
${ticket.created ? escapeHtml(ticket.created) : 'Niet opgegeven'}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<span class="text-slate-400 block mb-1 font-medium">Laatst gewijzigd:</span>
|
||||
<span class="font-medium text-slate-700 dark:text-slate-300">
|
||||
${ticket.updated ? escapeHtml(ticket.updated) : 'Recent'}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<span class="text-slate-400 block mb-1 font-medium">Onderwerp logging:</span>
|
||||
${onderwerpBadge}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Snelle acties onderaan detailvak -->
|
||||
<div class="flex flex-wrap items-center justify-between gap-2 pt-2 border-t border-slate-100 dark:border-slate-800/80">
|
||||
<div class="flex items-center space-x-2">
|
||||
<button type="button" onclick='copyTicketKey("${escapeHtml(ticket.key)}")' class="flex items-center space-x-1 px-2.5 py-1.5 rounded-lg bg-slate-100 dark:bg-slate-800 hover:bg-slate-200 dark:hover:bg-slate-700 text-slate-700 dark:text-slate-300 text-xs font-semibold transition cursor-pointer">
|
||||
<i data-lucide="copy" class="w-3 h-3"></i>
|
||||
<span>Sleutel Kopiëren</span>
|
||||
</button>
|
||||
<a href="${escapeHtml(ticket.url)}" target="_blank" rel="noopener noreferrer" class="flex items-center space-x-1 px-2.5 py-1.5 rounded-lg bg-slate-100 dark:bg-slate-800 hover:bg-slate-200 dark:hover:bg-slate-700 text-indigo-600 dark:text-indigo-400 text-xs font-semibold transition cursor-pointer">
|
||||
<i data-lucide="external-link" class="w-3 h-3"></i>
|
||||
<span>Open in Jira Cloud</span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="flex items-center space-x-2">
|
||||
<button type="button" onclick='openPlanModal(${ticketSafeJson})' class="flex items-center space-x-1.5 px-3 py-1.5 rounded-xl text-xs font-bold bg-indigo-600 hover:bg-indigo-700 text-white transition cursor-pointer shadow-xs">
|
||||
<i data-lucide="calendar" class="w-3 h-3"></i>
|
||||
<span>Inplannen in Dagplanner</span>
|
||||
</button>
|
||||
<button type="button" onclick='openBoardModal(${ticketSafeJson})' class="flex items-center space-x-1.5 px-3 py-1.5 rounded-xl text-xs font-bold bg-emerald-600 hover:bg-emerald-700 text-white transition cursor-pointer shadow-xs">
|
||||
<i data-lucide="kanban" class="w-3 h-3"></i>
|
||||
<span>Naar Kanban Bord</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
@@ -1191,6 +1546,11 @@ document.addEventListener('keydown', function(e) {
|
||||
// Initiële opstart
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
calculateKpis();
|
||||
updateProjectPills();
|
||||
updateQueueCounts();
|
||||
if (currentQueue) {
|
||||
updateQueuePills();
|
||||
}
|
||||
applyFilters();
|
||||
});
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user