Compare commits

...

2 Commits

+139 -25
View File
@@ -1756,9 +1756,9 @@ if ($isAjaxRequest) {
</div> </div>
<?php endif; ?> <?php endif; ?>
<!-- Modal: Jira Ticket Inplannen (met tijdsuggestie) --> <!-- Modal: Jira Ticket Inplannen (met datum, tijdsuggestie & aanpasbare titel) -->
<div id="jiraPlanModal" class="fixed inset-0 z-50 bg-slate-950/70 backdrop-blur-sm hidden items-center justify-center p-4"> <div id="jiraPlanModal" class="fixed inset-0 z-50 bg-slate-950/70 backdrop-blur-sm hidden items-center justify-center p-4">
<div class="fociz-card p-6 w-full max-w-md shadow-2xl space-y-4"> <div class="fociz-card p-6 w-full max-w-lg max-h-[90vh] overflow-y-auto shadow-2xl space-y-4">
<div class="flex items-center justify-between pb-3 border-b border-slate-100 dark:border-slate-800/80"> <div class="flex items-center justify-between pb-3 border-b border-slate-100 dark:border-slate-800/80">
<div class="flex items-center space-x-2.5"> <div class="flex items-center space-x-2.5">
<div class="w-8 h-8 rounded-xl bg-sky-50 dark:bg-sky-500/10 border border-sky-200 dark:border-sky-500/20 text-sky-600 dark:text-sky-400 flex items-center justify-center"> <div class="w-8 h-8 rounded-xl bg-sky-50 dark:bg-sky-500/10 border border-sky-200 dark:border-sky-500/20 text-sky-600 dark:text-sky-400 flex items-center justify-center">
@@ -1769,29 +1769,78 @@ if ($isAjaxRequest) {
<p class="text-[10px] text-slate-500 dark:text-slate-400" id="jiraPlanModalKey">—</p> <p class="text-[10px] text-slate-500 dark:text-slate-400" id="jiraPlanModalKey">—</p>
</div> </div>
</div> </div>
<button onclick="closeJiraPlanModal()" class="p-1.5 text-slate-400 hover:text-slate-600 dark:hover:text-slate-200 rounded-lg hover:bg-slate-100 dark:hover:bg-slate-800 transition cursor-pointer"> <button type="button" onclick="closeJiraPlanModal()" class="p-1.5 text-slate-400 hover:text-slate-600 dark:hover:text-slate-200 rounded-lg hover:bg-slate-100 dark:hover:bg-slate-800 transition cursor-pointer">
<i data-lucide="x" class="w-4 h-4"></i> <i data-lucide="x" class="w-4 h-4"></i>
</button> </button>
</div> </div>
<!-- Ticket samenvatting --> <!-- Ticket samenvatting & Uitklapbare beschrijving -->
<div class="bg-slate-50 dark:bg-slate-900/50 rounded-xl border border-slate-200 dark:border-slate-800 p-3"> <div class="bg-slate-50 dark:bg-slate-900/50 rounded-xl border border-slate-200 dark:border-slate-800 p-3.5 space-y-2.5">
<p class="text-xs font-semibold text-slate-800 dark:text-slate-200 leading-snug" id="jiraPlanModalSummary">—</p> <div>
<div class="flex items-center space-x-2 mt-1.5"> <p class="text-xs font-semibold text-slate-800 dark:text-slate-200 leading-snug" id="jiraPlanModalSummary">—</p>
<span class="text-[10px] text-slate-500" id="jiraPlanModalStatus">—</span> <div class="flex items-center space-x-2 mt-1.5">
<span class="text-slate-300 dark:text-slate-600">•</span> <span class="text-[10px] text-slate-500" id="jiraPlanModalStatus">—</span>
<span class="text-[10px] text-slate-500" id="jiraPlanModalPriority">—</span> <span class="text-slate-300 dark:text-slate-600">•</span>
<span id="jiraPlanModalSpBadge" class="hidden px-1.5 py-0.5 rounded bg-violet-50 dark:bg-violet-500/10 border border-violet-200 dark:border-violet-500/20 text-violet-700 dark:text-violet-400 font-mono text-[9px] font-bold"></span> <span class="text-[10px] text-slate-500" id="jiraPlanModalPriority">—</span>
<span id="jiraPlanModalSpBadge" class="hidden px-1.5 py-0.5 rounded bg-violet-50 dark:bg-violet-500/10 border border-violet-200 dark:border-violet-500/20 text-violet-700 dark:text-violet-400 font-mono text-[9px] font-bold"></span>
</div>
</div>
<!-- Uitklapknop voor inhoud ticket (10 regels verticaal scrollbaar) -->
<div class="pt-2 border-t border-slate-200/70 dark:border-slate-800/70">
<button type="button" onclick="toggleJiraPlanDescription()" class="w-full flex items-center justify-between text-[11px] font-semibold text-indigo-600 dark:text-indigo-400 hover:text-indigo-700 dark:hover:text-indigo-300 transition cursor-pointer group">
<span class="flex items-center space-x-1.5">
<i data-lucide="file-text" class="w-3.5 h-3.5"></i>
<span id="jiraPlanDescToggleLabel">Inhoud van het item bekijken</span>
</span>
<i data-lucide="chevron-down" id="jiraPlanDescChevron" class="w-3.5 h-3.5 transition-transform duration-200 group-hover:translate-y-0.5"></i>
</button>
<div id="jiraPlanDescWrapper" class="hidden mt-2.5">
<div id="jiraPlanModalDescription" class="max-h-48 overflow-y-auto p-3 bg-white dark:bg-[#0c0f17] rounded-xl border border-slate-200 dark:border-slate-800 text-xs text-slate-700 dark:text-slate-300 whitespace-pre-wrap font-sans leading-relaxed"></div>
</div>
</div> </div>
</div> </div>
<!-- Tijdsuggestie --> <!-- Formulier velden -->
<div class="space-y-3"> <div class="space-y-3.5">
<!-- Titel voor de agenda / dagtaak -->
<div> <div>
<label class="block text-[11px] font-bold text-slate-700 dark:text-slate-300 mb-1.5">Starttijd (suggestie)</label> <label for="jiraPlanCustomTitle" class="block text-[11px] font-bold text-slate-700 dark:text-slate-300 mb-1.5">
<input type="time" id="jiraPlanStartTime" class="w-full bg-white dark:bg-[#0c0f17] border border-slate-200 dark:border-slate-800 rounded-xl px-3 py-2 text-sm text-slate-900 dark:text-white focus:outline-none focus:ring-1 focus:ring-indigo-500"> Hoe het in de agenda komt (Titel)
<p class="text-[10px] text-slate-400 mt-1">Laat leeg voor handmatig inplannen op de tijdlijn</p> </label>
<input type="text" id="jiraPlanCustomTitle" class="w-full bg-white dark:bg-[#0c0f17] border border-slate-200 dark:border-slate-800 rounded-xl px-3 py-2 text-sm text-slate-900 dark:text-white focus:outline-none focus:ring-1 focus:ring-indigo-500 placeholder-slate-400" placeholder="Oppakken Jira item ...">
</div> </div>
<!-- Datum & Starttijd -->
<div class="grid grid-cols-1 sm:grid-cols-2 gap-3">
<div>
<label for="jiraPlanDate" class="block text-[11px] font-bold text-slate-700 dark:text-slate-300 mb-1.5">
Datum
</label>
<input type="date" id="jiraPlanDate" class="w-full bg-white dark:bg-[#0c0f17] border border-slate-200 dark:border-slate-800 rounded-xl px-3 py-2 text-sm text-slate-900 dark:text-white focus:outline-none focus:ring-1 focus:ring-indigo-500">
</div>
<div>
<label for="jiraPlanStartTime" class="block text-[11px] font-bold text-slate-700 dark:text-slate-300 mb-1.5">
Starttijd
</label>
<div class="relative flex items-center">
<input type="time" id="jiraPlanStartTime" class="w-full bg-white dark:bg-[#0c0f17] border border-slate-200 dark:border-slate-800 rounded-xl pl-3 pr-8 py-2 text-sm text-slate-900 dark:text-white focus:outline-none focus:ring-1 focus:ring-indigo-500">
<button type="button" onclick="clearJiraPlanStartTime()" title="Starttijd wissen" class="absolute right-2 p-1 text-slate-400 hover:text-rose-500 dark:hover:text-rose-400 rounded transition cursor-pointer">
<i data-lucide="x" class="w-3.5 h-3.5"></i>
</button>
</div>
<p class="text-[10px] text-slate-400 mt-1 flex items-center justify-between">
<span>Laat leeg voor handmatig inplannen</span>
<button type="button" onclick="clearJiraPlanStartTime()" class="text-indigo-600 dark:text-indigo-400 hover:underline cursor-pointer flex items-center space-x-0.5" title="Starttijd wissen">
<i data-lucide="x" class="w-2.5 h-2.5"></i>
<span>Wissen</span>
</button>
</p>
</div>
</div>
<!-- Duur -->
<div> <div>
<label class="block text-[11px] font-bold text-slate-700 dark:text-slate-300 mb-1.5">Duur</label> <label class="block text-[11px] font-bold text-slate-700 dark:text-slate-300 mb-1.5">Duur</label>
<div class="grid grid-cols-4 gap-2"> <div class="grid grid-cols-4 gap-2">
@@ -1802,8 +1851,10 @@ if ($isAjaxRequest) {
</div> </div>
<input type="hidden" id="jiraPlanDuration" value="15"> <input type="hidden" id="jiraPlanDuration" value="15">
</div> </div>
<!-- Prioriteit -->
<div> <div>
<label class="block text-[11px] font-bold text-slate-700 dark:text-slate-300 mb-1.5">Prioriteit</label> <label for="jiraPlanPriority" class="block text-[11px] font-bold text-slate-700 dark:text-slate-300 mb-1.5">Prioriteit</label>
<select id="jiraPlanPriority" class="w-full bg-white dark:bg-[#0c0f17] border border-slate-200 dark:border-slate-800 rounded-xl px-3 py-2 text-sm text-slate-900 dark:text-white focus:outline-none focus:ring-1 focus:ring-indigo-500"> <select id="jiraPlanPriority" class="w-full bg-white dark:bg-[#0c0f17] border border-slate-200 dark:border-slate-800 rounded-xl px-3 py-2 text-sm text-slate-900 dark:text-white focus:outline-none focus:ring-1 focus:ring-indigo-500">
<option value="P1">P1 — Urgent</option> <option value="P1">P1 — Urgent</option>
<option value="P2">P2 — Hoog</option> <option value="P2">P2 — Hoog</option>
@@ -1812,7 +1863,7 @@ if ($isAjaxRequest) {
</div> </div>
</div> </div>
<div class="flex space-x-3 pt-1"> <div class="flex space-x-3 pt-2 border-t border-slate-100 dark:border-slate-800/80">
<button type="button" onclick="closeJiraPlanModal()" class="flex-1 px-4 py-2.5 rounded-xl bg-slate-100 hover:bg-slate-200 dark:bg-slate-800 dark:hover:bg-slate-700 text-slate-700 dark:text-slate-300 font-semibold text-sm transition cursor-pointer"> <button type="button" onclick="closeJiraPlanModal()" class="flex-1 px-4 py-2.5 rounded-xl bg-slate-100 hover:bg-slate-200 dark:bg-slate-800 dark:hover:bg-slate-700 text-slate-700 dark:text-slate-300 font-semibold text-sm transition cursor-pointer">
Annuleren Annuleren
</button> </button>
@@ -2970,6 +3021,9 @@ const plannerTicketStore = new Map();
plannerJiraAll.forEach(iss => { plannerJiraAll.forEach(iss => {
if (iss && iss.key) plannerTicketStore.set(iss.key, iss); if (iss && iss.key) plannerTicketStore.set(iss.key, iss);
}); });
plannerJiraAssigned.forEach(iss => {
if (iss && iss.key && !plannerTicketStore.has(iss.key)) plannerTicketStore.set(iss.key, iss);
});
let activePlannerJiraQueue = ''; // '': Alle, 'my': Mijn, 'DX': DX Queue, 'SD': SD Queue let activePlannerJiraQueue = ''; // '': Alle, 'my': Mijn, 'DX': DX Queue, 'SD': SD Queue
@@ -3191,9 +3245,35 @@ function openJiraPlanFromDetail() {
} }
} }
// --- Jira Plan Modal (met tijdsuggestie) --- // --- Jira Plan Modal (met datum, tijdsuggestie & aanpasbare titel) ---
let _jiraPlanCurrentIssue = null; let _jiraPlanCurrentIssue = null;
function clearJiraPlanStartTime() {
const input = document.getElementById('jiraPlanStartTime');
if (input) {
input.value = '';
input.focus();
}
}
function toggleJiraPlanDescription() {
const wrapper = document.getElementById('jiraPlanDescWrapper');
const chevron = document.getElementById('jiraPlanDescChevron');
const label = document.getElementById('jiraPlanDescToggleLabel');
if (!wrapper) return;
const isHidden = wrapper.classList.contains('hidden');
if (isHidden) {
wrapper.classList.remove('hidden');
if (chevron) chevron.classList.add('rotate-180');
if (label) label.textContent = 'Inhoud inklappen';
} else {
wrapper.classList.add('hidden');
if (chevron) chevron.classList.remove('rotate-180');
if (label) label.textContent = 'Inhoud van het item bekijken';
}
if (typeof lucide !== 'undefined') lucide.createIcons();
}
function openJiraPlanModal(issueKey) { function openJiraPlanModal(issueKey) {
const issue = plannerTicketStore.get(issueKey); const issue = plannerTicketStore.get(issueKey);
if (!issue) return; if (!issue) return;
@@ -3214,6 +3294,32 @@ function openJiraPlanModal(issueKey) {
spBadge.classList.add('hidden'); spBadge.classList.add('hidden');
} }
// Vul beschrijving (10 regels scrollbaar venstertje) en reset naar ingeklapt
const descEl = document.getElementById('jiraPlanModalDescription');
if (descEl) {
const descText = (issue.description && issue.description.trim()) ? issue.description.trim() : 'Geen beschrijving beschikbaar voor dit ticket.';
descEl.textContent = descText;
}
const descWrapper = document.getElementById('jiraPlanDescWrapper');
const descChevron = document.getElementById('jiraPlanDescChevron');
const descLabel = document.getElementById('jiraPlanDescToggleLabel');
if (descWrapper) descWrapper.classList.add('hidden');
if (descChevron) descChevron.classList.remove('rotate-180');
if (descLabel) descLabel.textContent = 'Inhoud van het item bekijken';
// Titel hoe het in de agenda komt: standaard "Oppakken Jira item <issue.key>"
const titleInput = document.getElementById('jiraPlanCustomTitle');
if (titleInput) {
titleInput.value = `Oppakken Jira item ${issue.key}`;
}
// Datum: standaard de actieve planner datum of vandaag
const dateInput = document.getElementById('jiraPlanDate');
const targetDate = (typeof activePlannerDate !== 'undefined' && activePlannerDate) ? activePlannerDate : new Date().toISOString().slice(0, 10);
if (dateInput) {
dateInput.value = targetDate;
}
// Standaard duur: 15 min (of op basis van SP: 1 SP = 30 min) // Standaard duur: 15 min (of op basis van SP: 1 SP = 30 min)
let defaultDuration = 15; let defaultDuration = 15;
if (issue.story_points && issue.story_points > 0) { if (issue.story_points && issue.story_points > 0) {
@@ -3233,9 +3339,8 @@ function openJiraPlanModal(issueKey) {
// Slimme starttijd: stel het volgende vrije kwartier voor // Slimme starttijd: stel het volgende vrije kwartier voor
const now = new Date(); const now = new Date();
const suggestTime = activePlannerDate === now.toISOString().slice(0,10) const todayStr = now.toISOString().slice(0, 10);
? suggestNextFreeSlot() const suggestTime = targetDate === todayStr ? suggestNextFreeSlot() : '09:00';
: '09:00';
document.getElementById('jiraPlanStartTime').value = suggestTime; document.getElementById('jiraPlanStartTime').value = suggestTime;
const modal = document.getElementById('jiraPlanModal'); const modal = document.getElementById('jiraPlanModal');
@@ -3281,6 +3386,13 @@ async function submitJiraPlanModal() {
if (!_jiraPlanCurrentIssue) return; if (!_jiraPlanCurrentIssue) return;
const issue = _jiraPlanCurrentIssue; const issue = _jiraPlanCurrentIssue;
const titleInput = document.getElementById('jiraPlanCustomTitle');
const customTitle = titleInput ? titleInput.value.trim() : '';
const finalTitle = customTitle || `Oppakken Jira item ${issue.key}`;
const dateInput = document.getElementById('jiraPlanDate');
const planDate = (dateInput && dateInput.value) ? dateInput.value : ((typeof activePlannerDate !== 'undefined' && activePlannerDate) ? activePlannerDate : new Date().toISOString().slice(0, 10));
const duration = parseInt(document.getElementById('jiraPlanDuration').value, 10) || 15; const duration = parseInt(document.getElementById('jiraPlanDuration').value, 10) || 15;
const startTime = document.getElementById('jiraPlanStartTime').value.trim(); const startTime = document.getElementById('jiraPlanStartTime').value.trim();
const priority = document.getElementById('jiraPlanPriority').value; const priority = document.getElementById('jiraPlanPriority').value;
@@ -3292,13 +3404,13 @@ async function submitJiraPlanModal() {
try { try {
const payload = { const payload = {
title: `[${issue.key}] ${issue.summary || issue.key}`, title: finalTitle,
priority: priority, priority: priority,
source: 'JIRA', source: 'JIRA',
external_id: issue.key, external_id: issue.key,
external_url: issue.url || '', external_url: issue.url || '',
duration_minutes: duration, duration_minutes: duration,
date: activePlannerDate date: planDate
}; };
if (startTime) { if (startTime) {
payload.time_block_start = startTime; payload.time_block_start = startTime;
@@ -3318,7 +3430,9 @@ async function submitJiraPlanModal() {
if (data.success) { if (data.success) {
closeJiraPlanModal(); closeJiraPlanModal();
showWidgetToast(`<strong>${escapeHtml(issue.key)}</strong> ingepland als dagtaak`, 'success'); showWidgetToast(`<strong>${escapeHtml(issue.key)}</strong> ingepland als dagtaak`, 'success');
switchPlannerDate(activePlannerDate, null, false); if (typeof switchPlannerDate === 'function') {
switchPlannerDate(planDate, null, false);
}
} else { } else {
alert(data.error || 'Kon ticket niet inplannen'); alert(data.error || 'Kon ticket niet inplannen');
btn.innerHTML = origHtml; btn.innerHTML = origHtml;