From d0d45de518a3a663cf94cbfd29e5c7ed1ebd80c9 Mon Sep 17 00:00:00 2001 From: Laurent Deleers Date: Fri, 25 Sep 2026 08:59:13 +0200 Subject: [PATCH] add calendar icon next to the tasks to edit dates within the gant --- index.html | 28 ++++++++++ public/css/style.css | 128 +++++++++++++++++++++++++++++++++++++++++++ public/js/script.js | 91 +++++++++++++++++++++++++----- 3 files changed, 234 insertions(+), 13 deletions(-) diff --git a/index.html b/index.html index e94a459..e532f06 100644 --- a/index.html +++ b/index.html @@ -146,6 +146,34 @@
+ + + diff --git a/public/css/style.css b/public/css/style.css index 693d189..ad58f69 100644 --- a/public/css/style.css +++ b/public/css/style.css @@ -1249,6 +1249,39 @@ GANTT font-size: 11px; color: var(--fg-muted); padding-left: 20px; + display: flex; + align-items: center; + justify-content: space-between; + gap: 6px; +} + +.gantt-ticket-id-label { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.gantt-date-edit-btn { + flex-shrink: 0; + display: inline-flex; + align-items: center; + justify-content: center; + width: 22px; + height: 22px; + border-radius: 6px; + border: 1px solid var(--border); + background: var(--bg-input); + color: var(--accent); + font-size: 11px; + cursor: pointer; + margin-right: 8px; + transition: all 0.2s ease; +} + +.gantt-date-edit-btn:hover { + background: var(--accent-glow); + border-color: var(--accent); + transform: scale(1.08); } /* Grille de fond (colonnes verticales sous les barres) */ @@ -1353,4 +1386,99 @@ GANTT .mr-4 { margin-right: 4px; } +/* --- Date Edit Modal --- */ +.modal-overlay { + display: none; + position: fixed; + inset: 0; + z-index: 9998; + background: rgba(0, 0, 0, 0.55); + align-items: center; + justify-content: center; + animation: fadeIn 0.2s ease; +} + +.modal-overlay.visible { + display: flex; +} + +.modal-box { + background: var(--bg-card); + border: 1px solid var(--border); + border-radius: var(--radius-lg); + box-shadow: var(--shadow); + width: 100%; + max-width: 420px; + margin: 20px; + overflow: hidden; +} + +.modal-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 18px 20px; + border-bottom: 1px solid var(--border); +} + +.modal-title { + display: flex; + align-items: center; + gap: 10px; + font-size: 15px; + font-weight: 700; + color: var(--fg); +} + +.modal-title i { color: var(--accent); } + +.modal-close { + background: transparent; + border: none; + color: var(--fg-muted); + font-size: 16px; + cursor: pointer; + padding: 4px; + line-height: 1; + border-radius: 6px; + transition: all 0.2s ease; +} + +.modal-close:hover { + color: var(--danger); + background: var(--accent-glow); +} + +.modal-body { + padding: 20px; + display: flex; + flex-direction: column; + gap: 16px; +} + +.modal-ticket-key { + font-family: 'JetBrains Mono', monospace; + font-weight: 700; + color: var(--accent); + font-size: 13px; +} + +.modal-ticket-summary { + font-size: 12px; + color: var(--fg-muted); + margin-top: -10px; +} + +.modal-date-input { + width: 100%; +} + +.modal-footer { + display: flex; + justify-content: flex-end; + gap: 10px; + padding: 16px 20px; + border-top: 1px solid var(--border); +} + diff --git a/public/js/script.js b/public/js/script.js index 70744aa..a6ac577 100644 --- a/public/js/script.js +++ b/public/js/script.js @@ -368,9 +368,8 @@ async function updateTicket(ticketKey, dueDate, goLiveDate) { } const btn = document.querySelector(`[data-ticket="${ticketKey}"]`); - const orig = btn.innerHTML; - btn.innerHTML = ''; - btn.disabled = true; + const orig = btn ? btn.innerHTML : null; + if (btn) { btn.innerHTML = ''; btn.disabled = true; } const goLiveField = customFieldsMap.find(cf => cf.label.toLowerCase().includes('go live')); const updates = {}; @@ -400,8 +399,7 @@ async function updateTicket(ticketKey, dueDate, goLiveDate) { } catch (err) { showToast(err.message, 'error'); } finally { - btn.innerHTML = orig; - btn.disabled = false; + if (btn) { btn.innerHTML = orig; btn.disabled = false; } } } @@ -422,6 +420,66 @@ function handleUpdateClick(ticketKey, goLiveColName) { updateTicket(ticketKey, dueDate, goLiveDate); } +// --- GANTT DATE EDIT MODAL --- +let dateEditContext = { ticketKey: null, goLiveColName: null }; + +// Detects the real go-live custom field name, ignoring known built-in ticket properties +function getGoLiveColumnName() { + if (!allTickets.length) return null; + return Object.keys(allTickets[0]).find(key => + !['key','url','summary','description','status','statusCategory','assignee','reporter','priority','issueType','created','updated','dueDate','resolution','resolutionDate','labels','components','fixVersions','projectName'].includes(key) + ) || null; +} + +function openDateEditModal(ticketKey) { + const ticket = allTickets.find(t => t.key === ticketKey); + if (!ticket) return; + + const goLiveColName = getGoLiveColumnName(); + dateEditContext = { ticketKey, goLiveColName }; + + document.getElementById('dateEditTicketKey').textContent = ticket.key; + document.getElementById('dateEditTicketSummary').textContent = ticket.summary || ''; + document.getElementById('modalDueDate').value = ticket.dueDate ? ticket.dueDate.substring(0, 10) : ''; + + const goLiveGroup = document.getElementById('modalGoLiveGroup'); + if (goLiveColName) { + goLiveGroup.style.display = ''; + document.getElementById('modalGoLiveLabel').textContent = goLiveColName; + document.getElementById('modalGoLiveDate').value = ticket[goLiveColName] ? ticket[goLiveColName].substring(0, 10) : ''; + } else { + goLiveGroup.style.display = 'none'; + } + + document.getElementById('dateEditModal').classList.add('visible'); +} + +function closeDateEditModal() { + document.getElementById('dateEditModal').classList.remove('visible'); + dateEditContext = { ticketKey: null, goLiveColName: null }; +} + +async function saveDateEditModal() { + const { ticketKey, goLiveColName } = dateEditContext; + if (!ticketKey) return; + + const dueDate = document.getElementById('modalDueDate').value || null; + const goLiveDate = goLiveColName ? (document.getElementById('modalGoLiveDate').value || null) : undefined; + + const btn = document.getElementById('modalSaveBtn'); + const orig = btn.innerHTML; + btn.innerHTML = ''; + btn.disabled = true; + + try { + await updateTicket(ticketKey, dueDate, goLiveDate); + closeDateEditModal(); + } finally { + btn.innerHTML = orig; + btn.disabled = false; + } +} + // --- RENDERING --- function renderResults() { document.getElementById('results-section').classList.add('visible'); @@ -444,12 +502,7 @@ function escHtml(s) { if(!s) return ''; const d=document.createElement('div'); d function fmtDate(d) { if(!d) return '-'; try { return new Date(d).toLocaleDateString('en-US',{day:'2-digit',month:'short',year:'numeric'}); } catch { return escHtml(d); } } function renderTable() { - let goLiveColName = null; - if (allTickets.length > 0) { - goLiveColName = Object.keys(allTickets[0]).find(key => - !['key','url','summary','description','status','statusCategory','assignee','reporter','priority','issueType','created','updated','dueDate','resolution','resolutionDate','labels','components','fixVersions','projectName'].includes(key) - ); - } + const goLiveColName = getGoLiveColumnName(); const colMapping = { 'Ticket': 'key', @@ -704,7 +757,10 @@ function renderRoadmap() { html += `
- ${escHtml(shortTitle)} + + ${escHtml(shortTitle)}
{ if((e.ctrlKey||e.metaKey)&&e.key==='Enter'){e.preventDefault();startExtraction()} }); +document.addEventListener('keydown', e => { + if((e.ctrlKey||e.metaKey)&&e.key==='Enter'){e.preventDefault();startExtraction()} + if(e.key === 'Escape') closeDateEditModal(); +}); document.addEventListener('DOMContentLoaded', () => { initTheme(); loadSettings(); + + const modalOverlay = document.getElementById('dateEditModal'); + if (modalOverlay) { + modalOverlay.addEventListener('click', e => { if (e.target === modalOverlay) closeDateEditModal(); }); + } + console.log("Settings loaded from local storage."); }); \ No newline at end of file