add calendar icon next to the tasks to edit dates within the gant
This commit is contained in:
+28
@@ -146,6 +146,34 @@
|
|||||||
<div class="tab-panel" id="panel-json"><div class="json-viewer" id="jsonViewer"></div></div>
|
<div class="tab-panel" id="panel-json"><div class="json-viewer" id="jsonViewer"></div></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="modal-overlay" id="dateEditModal">
|
||||||
|
<div class="modal-box">
|
||||||
|
<div class="modal-header">
|
||||||
|
<div class="modal-title"><i class="fa-solid fa-calendar-days"></i> Edit Dates</div>
|
||||||
|
<button class="modal-close" onclick="closeDateEditModal()" aria-label="Close"><i class="fa-solid fa-xmark"></i></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="modal-ticket-key" id="dateEditTicketKey"></div>
|
||||||
|
<div class="modal-ticket-summary" id="dateEditTicketSummary"></div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="modalDueDate">Due Date</label>
|
||||||
|
<input type="date" id="modalDueDate" class="date-input modal-date-input">
|
||||||
|
</div>
|
||||||
|
<div class="form-group" id="modalGoLiveGroup">
|
||||||
|
<label for="modalGoLiveDate" id="modalGoLiveLabel">Go Live Date</label>
|
||||||
|
<input type="date" id="modalGoLiveDate" class="date-input modal-date-input">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button class="btn btn-secondary" onclick="closeDateEditModal()">Cancel</button>
|
||||||
|
<button class="btn btn-primary" id="modalSaveBtn" onclick="saveDateEditModal()">
|
||||||
|
<i class="fa-solid fa-check"></i> Save
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<script src="/js/script.js"></script>
|
<script src="/js/script.js"></script>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -1249,6 +1249,39 @@ GANTT
|
|||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
color: var(--fg-muted);
|
color: var(--fg-muted);
|
||||||
padding-left: 20px;
|
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) */
|
/* Grille de fond (colonnes verticales sous les barres) */
|
||||||
@@ -1353,4 +1386,99 @@ GANTT
|
|||||||
|
|
||||||
.mr-4 { margin-right: 4px; }
|
.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);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+78
-13
@@ -368,9 +368,8 @@ async function updateTicket(ticketKey, dueDate, goLiveDate) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const btn = document.querySelector(`[data-ticket="${ticketKey}"]`);
|
const btn = document.querySelector(`[data-ticket="${ticketKey}"]`);
|
||||||
const orig = btn.innerHTML;
|
const orig = btn ? btn.innerHTML : null;
|
||||||
btn.innerHTML = '<span class="spinner"></span>';
|
if (btn) { btn.innerHTML = '<span class="spinner"></span>'; btn.disabled = true; }
|
||||||
btn.disabled = true;
|
|
||||||
|
|
||||||
const goLiveField = customFieldsMap.find(cf => cf.label.toLowerCase().includes('go live'));
|
const goLiveField = customFieldsMap.find(cf => cf.label.toLowerCase().includes('go live'));
|
||||||
const updates = {};
|
const updates = {};
|
||||||
@@ -400,8 +399,7 @@ async function updateTicket(ticketKey, dueDate, goLiveDate) {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
showToast(err.message, 'error');
|
showToast(err.message, 'error');
|
||||||
} finally {
|
} finally {
|
||||||
btn.innerHTML = orig;
|
if (btn) { btn.innerHTML = orig; btn.disabled = false; }
|
||||||
btn.disabled = false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -422,6 +420,66 @@ function handleUpdateClick(ticketKey, goLiveColName) {
|
|||||||
updateTicket(ticketKey, dueDate, goLiveDate);
|
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 = '<span class="spinner"></span>';
|
||||||
|
btn.disabled = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await updateTicket(ticketKey, dueDate, goLiveDate);
|
||||||
|
closeDateEditModal();
|
||||||
|
} finally {
|
||||||
|
btn.innerHTML = orig;
|
||||||
|
btn.disabled = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// --- RENDERING ---
|
// --- RENDERING ---
|
||||||
function renderResults() {
|
function renderResults() {
|
||||||
document.getElementById('results-section').classList.add('visible');
|
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 '<span class="empty-cell">-</span>'; try { return new Date(d).toLocaleDateString('en-US',{day:'2-digit',month:'short',year:'numeric'}); } catch { return escHtml(d); } }
|
function fmtDate(d) { if(!d) return '<span class="empty-cell">-</span>'; try { return new Date(d).toLocaleDateString('en-US',{day:'2-digit',month:'short',year:'numeric'}); } catch { return escHtml(d); } }
|
||||||
|
|
||||||
function renderTable() {
|
function renderTable() {
|
||||||
let goLiveColName = null;
|
const goLiveColName = getGoLiveColumnName();
|
||||||
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 colMapping = {
|
const colMapping = {
|
||||||
'Ticket': 'key',
|
'Ticket': 'key',
|
||||||
@@ -704,7 +757,10 @@ function renderRoadmap() {
|
|||||||
|
|
||||||
html += `<div class="gantt-row">
|
html += `<div class="gantt-row">
|
||||||
<div class="gantt-sticky-col gantt-ticket-id" style="width:${leftWidth}px;" title="${escHtml(t.key)}: ${escHtml(t.summary)}">
|
<div class="gantt-sticky-col gantt-ticket-id" style="width:${leftWidth}px;" title="${escHtml(t.key)}: ${escHtml(t.summary)}">
|
||||||
${escHtml(shortTitle)}
|
<button class="gantt-date-edit-btn" onclick="event.stopPropagation(); openDateEditModal('${t.key}')" title="Edit dates">
|
||||||
|
<i class="fa-solid fa-calendar-days"></i>
|
||||||
|
</button>
|
||||||
|
<span class="gantt-ticket-id-label">${escHtml(shortTitle)}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="gantt-bar"
|
<div class="gantt-bar"
|
||||||
@@ -906,9 +962,18 @@ function copyJSON() { navigator.clipboard.writeText(JSON.stringify(allTickets, n
|
|||||||
|
|
||||||
function clearResults() { allTickets=[]; filteredTickets=[]; document.getElementById('results-section').classList.remove('visible'); }
|
function clearResults() { allTickets=[]; filteredTickets=[]; document.getElementById('results-section').classList.remove('visible'); }
|
||||||
|
|
||||||
document.addEventListener('keydown', e => { 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', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
initTheme();
|
initTheme();
|
||||||
loadSettings();
|
loadSettings();
|
||||||
|
|
||||||
|
const modalOverlay = document.getElementById('dateEditModal');
|
||||||
|
if (modalOverlay) {
|
||||||
|
modalOverlay.addEventListener('click', e => { if (e.target === modalOverlay) closeDateEditModal(); });
|
||||||
|
}
|
||||||
|
|
||||||
console.log("Settings loaded from local storage.");
|
console.log("Settings loaded from local storage.");
|
||||||
});
|
});
|
||||||
Reference in New Issue
Block a user