diff --git a/public/css/style.css b/public/css/style.css
index 00d3333..693d189 100644
--- a/public/css/style.css
+++ b/public/css/style.css
@@ -23,6 +23,45 @@
--gantt-sticky-bg: #1a1a1a;
--gantt-group-bg: rgba(255, 255, 255, 0.03);
--gantt-grid-border: rgba(255, 255, 255, 0.05);
+
+ /* Status Colors - Dark Mode */
+ --status-opportunity: #c084fc;
+ --status-opportunity-bg: rgba(192, 132, 252, 0.15);
+ --status-opportunity-border: #a855f7;
+ --status-opportunity-badge: #9333ea;
+
+ --status-delayed: #94a3b8;
+ --status-delayed-bg: rgba(148, 163, 184, 0.15);
+ --status-delayed-border: #64748b;
+ --status-delayed-badge: #475569;
+
+ --status-analyse: #fbbf24;
+ --status-analyse-bg: rgba(251, 191, 36, 0.15);
+ --status-analyse-border: #f59e0b;
+ --status-analyse-badge: #d97706;
+
+ --status-analysed: #22d3ee;
+ --status-analysed-bg: rgba(34, 211, 238, 0.15);
+ --status-analysed-border: #06b6d4;
+ --status-analysed-badge: #0891b2;
+
+ --status-ongoing: #60a5fa;
+ --status-ongoing-bg: rgba(96, 165, 250, 0.18);
+ --status-ongoing-border: #3b82f6;
+ --status-ongoing-badge: #2563eb;
+
+ --status-rejected: #f87171;
+ --status-rejected-bg: rgba(248, 113, 113, 0.16);
+ --status-rejected-border: #ef4444;
+ --status-rejected-badge: #dc2626;
+
+ --status-done: #34d399;
+ --status-done-bg: rgba(52, 211, 153, 0.16);
+ --status-done-border: #10b981;
+ --status-done-badge: #059669;
+
+ --gantt-bar-bg: #141f19;
+ --gantt-bar-border: rgba(255, 255, 255, 0.12);
}
[data-theme="light"] {
@@ -48,6 +87,45 @@
--gantt-sticky-bg: #e6ede8;
--gantt-group-bg: rgba(0, 0, 0, 0.03);
--gantt-grid-border: rgba(0, 0, 0, 0.06);
+
+ /* Status Colors - Light Mode (High Contrast) */
+ --status-opportunity: #6d28d9;
+ --status-opportunity-bg: #f5f3ff;
+ --status-opportunity-border: #7c3aed;
+ --status-opportunity-badge: #7c3aed;
+
+ --status-delayed: #334155;
+ --status-delayed-bg: #f1f5f9;
+ --status-delayed-border: #64748b;
+ --status-delayed-badge: #475569;
+
+ --status-analyse: #92400e;
+ --status-analyse-bg: #fffbeb;
+ --status-analyse-border: #d97706;
+ --status-analyse-badge: #b45309;
+
+ --status-analysed: #0e7490;
+ --status-analysed-bg: #ecfeff;
+ --status-analysed-border: #0891b2;
+ --status-analysed-badge: #0e7490;
+
+ --status-ongoing: #1e40af;
+ --status-ongoing-bg: #eff6ff;
+ --status-ongoing-border: #2563eb;
+ --status-ongoing-badge: #1d4ed8;
+
+ --status-rejected: #991b1b;
+ --status-rejected-bg: #fef2f2;
+ --status-rejected-border: #dc2626;
+ --status-rejected-badge: #dc2626;
+
+ --status-done: #065f46;
+ --status-done-bg: #ecfdf5;
+ --status-done-border: #059669;
+ --status-done-badge: #047857;
+
+ --gantt-bar-bg: #ffffff;
+ --gantt-bar-border: #cbd5e1;
}
* {
@@ -543,58 +621,69 @@ tr:hover td {
padding: 3px 10px;
border-radius: 20px;
font-size: 11px;
- font-weight: 600;
+ font-weight: 700;
white-space: nowrap;
+ border: 1px solid transparent;
+}
+
+.s-opportunity {
+ background: var(--status-opportunity-bg);
+ color: var(--status-opportunity);
+ border-color: var(--status-opportunity-border);
+}
+
+.s-delayed {
+ background: var(--status-delayed-bg);
+ color: var(--status-delayed);
+ border-color: var(--status-delayed-border);
+}
+
+.s-analyse, .s-to-be-analysed {
+ background: var(--status-analyse-bg);
+ color: var(--status-analyse);
+ border-color: var(--status-analyse-border);
+}
+
+.s-analysed {
+ background: var(--status-analysed-bg);
+ color: var(--status-analysed);
+ border-color: var(--status-analysed-border);
+}
+
+.s-ongoing, .s-project-ongoing, .s-progress {
+ background: var(--status-ongoing-bg);
+ color: var(--status-ongoing);
+ border-color: var(--status-ongoing-border);
+}
+
+.s-rejected {
+ background: var(--status-rejected-bg);
+ color: var(--status-rejected);
+ border-color: var(--status-rejected-border);
}
.s-done {
- background: rgba(0, 230, 138, 0.15);
- color: #00e68a;
-}
-
-.s-progress {
- background: rgba(77, 200, 255, 0.15);
- color: #4dc8ff;
+ background: var(--status-done-bg);
+ color: var(--status-done);
+ border-color: var(--status-done-border);
}
.s-todo {
- background: rgba(255, 184, 77, 0.15);
- color: #ffb84d;
+ background: var(--status-opportunity-bg);
+ color: var(--status-opportunity);
+ border-color: var(--status-opportunity-border);
}
.s-blocked {
- background: rgba(255, 77, 106, 0.15);
- color: #ff4d6a;
+ background: var(--status-rejected-bg);
+ color: var(--status-rejected);
+ border-color: var(--status-rejected-border);
}
.s-other {
- background: rgba(122, 148, 136, 0.15);
- color: #7a9488;
-}
-
-[data-theme="light"] .s-done {
- background: rgba(0, 153, 92, 0.12);
- color: #007a49;
-}
-
-[data-theme="light"] .s-progress {
- background: rgba(2, 132, 199, 0.12);
- color: #0284c7;
-}
-
-[data-theme="light"] .s-todo {
- background: rgba(217, 119, 6, 0.12);
- color: #b45309;
-}
-
-[data-theme="light"] .s-blocked {
- background: rgba(220, 38, 38, 0.12);
- color: #dc2626;
-}
-
-[data-theme="light"] .s-other {
- background: rgba(83, 110, 97, 0.12);
- color: #4e6b5d;
+ background: var(--status-delayed-bg);
+ color: var(--status-delayed);
+ border-color: var(--status-delayed-border);
}
.empty-cell {
@@ -1009,18 +1098,68 @@ GANTT
/* La barre Gantt elle-même */
.gantt-bar {
position: absolute;
- height: 28px;
- border-radius: 4px;
+ height: 32px;
+ border-radius: 6px;
display: flex;
align-items: center;
- padding: 0 8px;
- font-size: 10px;
+ gap: 8px;
+ padding: 0 10px;
+ font-size: 11px;
color: var(--fg);
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
- cursor: help;
+ cursor: pointer;
z-index: 10;
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.25);
+ border: 1px solid var(--border);
+ transition: transform 0.15s ease, box-shadow 0.15s ease;
+}
+
+[data-theme="light"] .gantt-bar {
+ box-shadow: 0 2px 6px rgba(0, 0, 0, 0.12);
+}
+
+.gantt-bar:hover {
+ transform: translateY(-2px);
+ box-shadow: 0 6px 16px rgba(0, 0, 0, 0.35);
+ z-index: 25;
+}
+
+[data-theme="light"] .gantt-bar:hover {
+ box-shadow: 0 4px 14px rgba(0, 0, 0, 0.18);
+}
+
+.gantt-status-badge {
+ color: #ffffff;
+ font-size: 9px;
+ padding: 3px 7px;
+ border-radius: 4px;
+ font-weight: 800;
+ text-transform: uppercase;
+ letter-spacing: 0.4px;
+ white-space: nowrap;
+ flex-shrink: 0;
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25);
+}
+
+.gantt-type-tag {
+ font-weight: 800;
+ font-size: 10px;
+ padding: 2px 6px;
+ border-radius: 4px;
+ flex-shrink: 0;
+ font-family: 'JetBrains Mono', monospace;
+ letter-spacing: 0.3px;
+}
+
+.gantt-bar-title {
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ font-size: 12px;
+ font-weight: 600;
+ color: var(--fg);
}
/* Colonne assigné/ticket figée à gauche */
diff --git a/public/js/script.js b/public/js/script.js
index 609db12..70744aa 100644
--- a/public/js/script.js
+++ b/public/js/script.js
@@ -10,7 +10,192 @@ let currentSortCol = null;
let isAsc = true;
-// --- GESTION DU THÈME (CLAIR / SOMBRE) ---
+// --- JIRA STATUS CONFIG & MAPPING ---
+const STATUS_DEFINITIONS = {
+ 'Opportunity': {
+ label: 'Opportunity',
+ cssClass: 's-opportunity',
+ dark: {
+ color: '#c084fc',
+ bg: 'rgba(192, 132, 252, 0.16)',
+ border: '#a855f7',
+ badgeBg: '#9333ea',
+ badgeText: '#ffffff'
+ },
+ light: {
+ color: '#6d28d9',
+ bg: '#f5f3ff',
+ border: '#7c3aed',
+ badgeBg: '#7c3aed',
+ badgeText: '#ffffff'
+ },
+ drawioColor: '#9333ea'
+ },
+ 'Delayed': {
+ label: 'Delayed',
+ cssClass: 's-delayed',
+ dark: {
+ color: '#94a3b8',
+ bg: 'rgba(148, 163, 184, 0.16)',
+ border: '#64748b',
+ badgeBg: '#475569',
+ badgeText: '#ffffff'
+ },
+ light: {
+ color: '#334155',
+ bg: '#f1f5f9',
+ border: '#64748b',
+ badgeBg: '#475569',
+ badgeText: '#ffffff'
+ },
+ drawioColor: '#64748b'
+ },
+ 'To be Analysed': {
+ label: 'To be Analysed',
+ cssClass: 's-analyse',
+ dark: {
+ color: '#fbbf24',
+ bg: 'rgba(251, 191, 36, 0.16)',
+ border: '#f59e0b',
+ badgeBg: '#d97706',
+ badgeText: '#ffffff'
+ },
+ light: {
+ color: '#92400e',
+ bg: '#fffbeb',
+ border: '#d97706',
+ badgeBg: '#b45309',
+ badgeText: '#ffffff'
+ },
+ drawioColor: '#d97706'
+ },
+ 'Analysed': {
+ label: 'Analysed',
+ cssClass: 's-analysed',
+ dark: {
+ color: '#22d3ee',
+ bg: 'rgba(34, 211, 238, 0.16)',
+ border: '#06b6d4',
+ badgeBg: '#0891b2',
+ badgeText: '#ffffff'
+ },
+ light: {
+ color: '#0e7490',
+ bg: '#ecfeff',
+ border: '#0891b2',
+ badgeBg: '#0e7490',
+ badgeText: '#ffffff'
+ },
+ drawioColor: '#0891b2'
+ },
+ 'Project Ongoing': {
+ label: 'Project Ongoing',
+ cssClass: 's-ongoing',
+ dark: {
+ color: '#60a5fa',
+ bg: 'rgba(96, 165, 250, 0.20)',
+ border: '#3b82f6',
+ badgeBg: '#2563eb',
+ badgeText: '#ffffff'
+ },
+ light: {
+ color: '#1e40af',
+ bg: '#eff6ff',
+ border: '#2563eb',
+ badgeBg: '#1d4ed8',
+ badgeText: '#ffffff'
+ },
+ drawioColor: '#2563eb'
+ },
+ 'Rejected': {
+ label: 'Rejected',
+ cssClass: 's-rejected',
+ dark: {
+ color: '#f87171',
+ bg: 'rgba(248, 113, 113, 0.18)',
+ border: '#ef4444',
+ badgeBg: '#dc2626',
+ badgeText: '#ffffff'
+ },
+ light: {
+ color: '#991b1b',
+ bg: '#fef2f2',
+ border: '#dc2626',
+ badgeBg: '#dc2626',
+ badgeText: '#ffffff'
+ },
+ drawioColor: '#dc2626'
+ },
+ 'Done': {
+ label: 'Done',
+ cssClass: 's-done',
+ dark: {
+ color: '#34d399',
+ bg: 'rgba(52, 211, 153, 0.18)',
+ border: '#10b981',
+ badgeBg: '#059669',
+ badgeText: '#ffffff'
+ },
+ light: {
+ color: '#065f46',
+ bg: '#ecfdf5',
+ border: '#059669',
+ badgeBg: '#047857',
+ badgeText: '#ffffff'
+ },
+ drawioColor: '#059669'
+ }
+};
+
+function mapJiraStatus(rawStatus) {
+ if (!rawStatus) return 'Opportunity';
+ const s = String(rawStatus).trim().toLowerCase();
+
+ // "Resolved" and "Closed" => DONE
+ if (s.includes('done') || s.includes('resolv') || s.includes('clos') || s.includes('ferm') || s.includes('termin') || s.includes('rsolu')) {
+ return 'Done';
+ }
+ // "Rejected" => Rejected
+ if (s.includes('reject') || s.includes('rejet') || s.includes('refus') || s.includes('abandon')) {
+ return 'Rejected';
+ }
+ // "In Progress" => Project Ongoing
+ if (s.includes('progress') || s.includes('en cours') || s.includes('ongoing') || s.includes('review') || s.includes('dev')) {
+ return 'Project Ongoing';
+ }
+ // "On Hold" => Analysed
+ if (s.includes('hold') || s.includes('analysé') || s.includes('analysed') || s.includes('attente')) {
+ return 'Analysed';
+ }
+ // "Open" and "SPQI" => To be Analysed
+ if (s === 'open' || s.includes('spqi') || s.includes('ouvert') || s.includes('to analyse') || s.includes('to be analysed')) {
+ return 'To be Analysed';
+ }
+ // "Delayed" => Delayed
+ if (s.includes('delay') || s.includes('retard') || s.includes('reporté')) {
+ return 'Delayed';
+ }
+ // "New" and "Reopened" => Opportunity
+ if (s.includes('new') || s.includes('reopen') || s.includes('nouveau') || s.includes('opportunit') || s.includes('backlog')) {
+ return 'Opportunity';
+ }
+
+ return 'Opportunity';
+}
+
+function getStatusThemeConfig(mappedStatus) {
+ const isLight = document.documentElement.getAttribute('data-theme') === 'light';
+ const def = STATUS_DEFINITIONS[mappedStatus] || STATUS_DEFINITIONS['Opportunity'];
+ return isLight ? def.light : def.dark;
+}
+
+function getStatusClass(s) {
+ const mapped = mapJiraStatus(s);
+ const def = STATUS_DEFINITIONS[mapped];
+ return def ? def.cssClass : 's-opportunity';
+}
+
+// --- THEME MANAGEMENT (LIGHT / DARK) ---
function initTheme() {
const savedTheme = localStorage.getItem('jira_extractor_theme') || 'dark';
setTheme(savedTheme, false);
@@ -31,19 +216,23 @@ function setTheme(theme, withToast = false) {
if (icon && label) {
if (theme === 'light') {
icon.className = 'fa-solid fa-moon';
- label.textContent = 'Mode sombre';
+ label.textContent = 'Dark mode';
} else {
icon.className = 'fa-solid fa-sun';
- label.textContent = 'Mode clair';
+ label.textContent = 'Light mode';
}
}
+ if (document.getElementById('panel-roadmap') && document.getElementById('panel-roadmap').classList.contains('active')) {
+ renderRoadmap();
+ }
+
if (withToast) {
- showToast(theme === 'light' ? 'Mode clair activé' : 'Mode sombre activé');
+ showToast(theme === 'light' ? 'Light mode enabled' : 'Dark mode enabled');
}
}
-// Sauvegarde les paramètres dans le stockage local du navigateur
+// Save settings to local storage
function saveSettings() {
const settings = {
projectKey: document.getElementById('projectKey').value,
@@ -53,7 +242,7 @@ function saveSettings() {
localStorage.setItem('jira_roadmap_settings', JSON.stringify(settings));
}
-// Charge les paramètres au démarrage
+// Load settings on startup
function loadSettings() {
const saved = localStorage.getItem('jira_roadmap_settings');
if (saved) {
@@ -65,7 +254,7 @@ function loadSettings() {
}
-// --- UTILITAIRES ---
+// --- UTILITIES ---
function showToast(msg, type='success') {
const c = document.getElementById('toastContainer');
const t = document.createElement('div');
@@ -87,13 +276,13 @@ async function pingApi() {
saveSettings();
const btn = event.target.closest('.btn');
- const orig = btn.innerHTML; btn.innerHTML = '
Test...'; btn.disabled = true;
+ const orig = btn.innerHTML; btn.innerHTML = '
Testing...'; btn.disabled = true;
try {
const r = await fetch(`${API_BASE}/api/extract`, { method: 'POST', headers: {'Content-Type':'application/json'}, body: JSON.stringify({username:'_', apiToken:'_', projectKey:'_'}) });
- if (r.status === 400) showToast('API locale connectée !', 'success');
- else showToast('API répond : ' + r.status, 'success');
+ if (r.status === 400) showToast('Local API connected!', 'success');
+ else showToast('API responded: ' + r.status, 'success');
} catch(e) {
- showToast('API inaccessible. Lancez "npm start".', 'error');
+ showToast('API unreachable. Please run "npm start".', 'error');
} finally { btn.innerHTML = orig; btn.disabled = false; }
}
@@ -107,9 +296,9 @@ async function startExtraction() {
const jql = document.getElementById('jqlQuery').value.trim();
const custom = document.getElementById('customFields').value.trim();
- if (!user || !token || !project) { showToast('Champs requis manquants.', 'error'); return; }
+ if (!user || !token || !project) { showToast('Required fields missing.', 'error'); return; }
- btn.innerHTML = '
Extraction...'; btn.disabled = true;
+ btn.innerHTML = '
Extracting...'; btn.disabled = true;
try {
const resp = await fetch(`${API_BASE}/api/extract`, {
@@ -118,7 +307,7 @@ async function startExtraction() {
body: JSON.stringify({ username: user, apiToken: token, projectKey: project, jql: jql, customFields: custom })
});
const data = await resp.json();
- if (!resp.ok) throw new Error(data.error || `Erreur ${resp.status}`);
+ if (!resp.ok) throw new Error(data.error || `Error ${resp.status}`);
allTickets = data.tickets;
filteredTickets = [...allTickets];
@@ -127,11 +316,11 @@ async function startExtraction() {
lastApiToken = token;
renderResults();
- showToast(`${allTickets.length} tickets extraits.`, 'success');
+ showToast(`${allTickets.length} tickets extracted.`, 'success');
} catch (err) {
showToast(err.message, 'error');
} finally {
- btn.innerHTML = '
Lancer l\'extraction'; btn.disabled = false;
+ btn.innerHTML = '
Start Extraction'; btn.disabled = false;
}
}
@@ -147,7 +336,6 @@ function parseCustomFields(customFields) {
}
function sortTable(colKey) {
- // Si on clique sur la même colonne, on inverse l'ordre, sinon on passe en croissant
if (currentSortCol === colKey) {
isAsc = !isAsc;
} else {
@@ -159,7 +347,6 @@ function sortTable(colKey) {
let valA = a[colKey] || '';
let valB = b[colKey] || '';
- // Gestion spécifique pour les dates pour un tri chronologique correct
if (colKey.toLowerCase().includes('date') || colKey === 'created' || colKey === 'dueDate') {
valA = new Date(valA);
valB = new Date(valB);
@@ -173,10 +360,10 @@ function sortTable(colKey) {
renderTable();
}
-// --- MISE À JOUR TICKET ---
+// --- TICKET UPDATE ---
async function updateTicket(ticketKey, dueDate, goLiveDate) {
if (!lastUsername || !lastApiToken) {
- showToast('Veuillez d\'abord extraire les tickets.', 'error');
+ showToast('Please extract tickets first.', 'error');
return;
}
@@ -198,7 +385,7 @@ async function updateTicket(ticketKey, dueDate, goLiveDate) {
});
if (!resp.ok) {
const data = await resp.json();
- throw new Error(data.error || `Erreur HTTP ${resp.status}`);
+ throw new Error(data.error || `HTTP Error ${resp.status}`);
}
const ticket = allTickets.find(t => t.key === ticketKey);
@@ -209,7 +396,7 @@ async function updateTicket(ticketKey, dueDate, goLiveDate) {
renderTable();
renderRoadmap();
- showToast(`Ticket ${ticketKey} mis à jour !`, 'success');
+ showToast(`Ticket ${ticketKey} updated!`, 'success');
} catch (err) {
showToast(err.message, 'error');
} finally {
@@ -235,7 +422,7 @@ function handleUpdateClick(ticketKey, goLiveColName) {
updateTicket(ticketKey, dueDate, goLiveDate);
}
-// --- RENDU ---
+// --- RENDERING ---
function renderResults() {
document.getElementById('results-section').classList.add('visible');
renderStats(); renderTable(); renderJSON(); renderRoadmap();
@@ -244,18 +431,17 @@ function renderResults() {
function renderStats() {
const unassigned = allTickets.filter(t => !t.assignee).length;
const now = new Date();
- const overdue = allTickets.filter(t => t.dueDate && t.statusCategory !== 'Done' && new Date(t.dueDate) < now).length;
+ const overdue = allTickets.filter(t => t.dueDate && mapJiraStatus(t.status) !== 'Done' && new Date(t.dueDate) < now).length;
document.getElementById('statsRow').innerHTML = `
${allTickets.length}
Tickets
-
${new Set(allTickets.map(t=>t.status)).size}
Statuts
-
${unassigned}
Non assignés
-
+
${new Set(allTickets.map(t=>mapJiraStatus(t.status))).size}
Statuses
+
+
`;
}
function escHtml(s) { if(!s) return ''; const d=document.createElement('div'); d.textContent=String(s); return d.innerHTML; }
-function fmtDate(d) { if(!d) return '
-'; try { return new Date(d).toLocaleDateString('fr-FR',{day:'2-digit',month:'short',year:'numeric'}); } catch { return escHtml(d); } }
-function getStatusClass(s) { if(!s) return 's-other'; const l=s.toLowerCase(); if(l.includes('done')||l.includes('ferm')||l.includes('résolu')) return 's-done'; if(l.includes('progress')||l.includes('en cours')||l.includes('review')) return 's-progress'; if(l.includes('to do')||l.includes('open')||l.includes('backlog')) return 's-todo'; if(l.includes('block')) return 's-blocked'; return 's-other'; }
+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;
@@ -265,28 +451,26 @@ function renderTable() {
);
}
- // Mapping pour faire le lien entre le texte de l'en-tête et la propriété de l'objet ticket
const colMapping = {
'Ticket': 'key',
'Summary': 'summary',
- 'Statut': 'status',
- 'Assigné': 'assignee',
- 'Priorité': 'priority',
+ 'Status': 'status',
+ 'Assignee': 'assignee',
+ 'Priority': 'priority',
'Type': 'issueType',
'Due Date': 'dueDate'
};
- const cols = ['Ticket','Summary','Statut','Assigné','Priorité','Type','Due Date'];
+ const cols = ['Ticket','Summary','Status','Assignee','Priority','Type','Due Date'];
if (goLiveColName) {
cols.push(goLiveColName);
- colMapping[goLiveColName] = goLiveColName; // Ajout dynamique du champ personnalisé
+ colMapping[goLiveColName] = goLiveColName;
}
cols.push('Actions');
- // Génération des en-têtes avec gestion du clic pour le tri
document.getElementById('tableHead').innerHTML = '
' + cols.map(c => {
const key = colMapping[c];
- if (!key) return `| ${c} | `; // Pour la colonne 'Actions'
+ if (!key) return `${c} | `;
const icon = currentSortCol === key ? (isAsc ? ' ▴' : ' ▾') : '';
return `
@@ -297,10 +481,15 @@ function renderTable() {
let body = '';
filteredTickets.forEach(t => {
const dueDateValue = t.dueDate ? t.dueDate.substring(0, 10) : '';
+ const mappedStatus = mapJiraStatus(t.status);
body += ` |
| ${escHtml(t.key)} |
${escHtml(t.summary)} |
- ${escHtml(t.status)} |
+
+
+ ${escHtml(mappedStatus)}
+
+ |
${escHtml(t.assignee) || '-'} |
${escHtml(t.priority)} |
${escHtml(t.issueType)} |
@@ -317,16 +506,7 @@ function renderTable() {
`;
});
- document.getElementById('tableBody').innerHTML = body || '
| Aucun résultat |
';
-}
-
-function getStatusColor(status) {
- const s = (status || '').toLowerCase();
- if (s.includes('done') || s.includes('ferm') || s.includes('rsolu')) return '#00e68a';
- if (s.includes('progress') || s.includes('en cours') || s.includes('review')) return '#4dc8ff';
- if (s.includes('to do') || s.includes('open') || s.includes('backlog')) return '#ffb84d';
- if (s.includes('block')) return '#ff4d6a';
- return '#7a9488';
+ document.getElementById('tableBody').innerHTML = body || '
| No results found |
';
}
// --- ROADMAP (GANTT) ---
@@ -357,64 +537,62 @@ function updateGanttExpandState() {
if (icon && label) {
if (isGanttExpanded) {
icon.className = 'fa-solid fa-down-left-and-up-right-to-center';
- label.textContent = 'Replier';
+ label.textContent = 'Collapse';
if (btn) btn.classList.add('active');
} else {
icon.className = 'fa-solid fa-up-right-and-down-left-from-center';
- label.textContent = 'Déplier';
+ label.textContent = 'Expand';
if (btn) btn.classList.remove('active');
}
}
}
-// --- FONCTIONS UTILITAIRES POUR LE GANTT ---
+// --- GANTT UTILITIES ---
/**
- * Extrait le titre court et le type selon le pattern : "Titre - Type - Montant - ID"
+ * Extracts short title and project type according to the pattern: "Title - Type - Amount - ID"
*/
function parseTicketSummary(summary) {
- if (!summary) return { shortTitle: 'Sans titre', type: 'OTHER' };
+ if (!summary) return { shortTitle: 'Untitled', type: 'OTHER' };
const parts = summary.split(' - ');
const shortTitle = parts[0].trim();
const typePart = parts[1] ? parts[1].trim().toUpperCase() : 'OTHER';
- // On valide si c'est un type connu, sinon OTHER
const type = ['PR', 'CR', 'TR'].includes(typePart) ? typePart : 'OTHER';
return { shortTitle, type };
}
/**
- * Définit la couleur de la barre selon le type de projet
+ * Defines activity type accent color
*/
function getTypeColor(type) {
+ const isLight = document.documentElement.getAttribute('data-theme') === 'light';
switch(type) {
- case 'PR': return '#4dc8ff'; // Bleu info
- case 'CR': return '#ffb84d'; // Orange warning
- case 'TR': return '#a371f7'; // Violet
- default: return '#7a9488'; // Gris par défaut
+ case 'PR': return isLight ? '#0284c7' : '#38bdf8'; // Blue
+ case 'CR': return isLight ? '#b45309' : '#fbbf24'; // Amber
+ case 'TR': return isLight ? '#6d28d9' : '#c084fc'; // Purple
+ default: return isLight ? '#475569' : '#94a3b8'; // Gray
}
}
/**
- * FONCTION PRINCIPALE : RENDER ROADMAP
+ * MAIN ROADMAP RENDERING FUNCTION
*/
function renderRoadmap() {
const preview = document.getElementById('roadmapPreview');
if (!filteredTickets || filteredTickets.length === 0) {
- preview.innerHTML = `
Aucun ticket à afficher.
`;
+ preview.innerHTML = `
No tickets to display.
`;
return;
}
- // Détection dynamique de la colonne Go Live
const goLiveColName = Object.keys(filteredTickets[0]).find(k => k.toLowerCase().includes('go live')) || "Go Live Date";
- // Configuration des largeurs
- const leftWidth = 280; // Un peu plus large pour les titres split 0
+ const leftWidth = 280;
let colWidth = 140;
if (currentGanttView === 'quarter') colWidth = 200;
if (currentGanttView === 'year') colWidth = 250;
- // 1. Filtrer les tickets : on ne garde que ceux qui ont au moins une date
+ // Filter tickets with at least one date
const ticketsWithDates = filteredTickets.filter(t => {
const d1 = t.dueDate ? new Date(t.dueDate) : null;
const d2 = t[goLiveColName] ? new Date(t[goLiveColName]) : null;
@@ -422,11 +600,10 @@ function renderRoadmap() {
});
if (ticketsWithDates.length === 0) {
- preview.innerHTML = `
Aucune date de Due Date ou Go Live renseignée pour les tickets filtrés.
`;
+ preview.innerHTML = `
No Due Date or Go Live date found for filtered tickets.
`;
return;
}
- // Calcul des bornes du calendrier
const allDates = ticketsWithDates.flatMap(t => [
t.dueDate ? new Date(t.dueDate) : null,
t[goLiveColName] ? new Date(t[goLiveColName]) : null
@@ -439,16 +616,15 @@ function renderRoadmap() {
if (currentGanttView === 'quarter') minDate.setMonth(Math.floor(minDate.getMonth() / 3) * 3);
if (currentGanttView === 'year') minDate.setMonth(0);
- maxDate.setMonth(maxDate.getMonth() + 2); // Un peu de marge au bout
+ maxDate.setMonth(maxDate.getMonth() + 2);
maxDate.setDate(0);
- // Construction de l'échelle de temps
const timeScale = [];
let tempDate = new Date(minDate);
while (tempDate <= maxDate) {
let label = "", key = "";
if (currentGanttView === 'month') {
- label = tempDate.toLocaleDateString('fr-FR', { month: 'short', year: 'numeric' });
+ label = tempDate.toLocaleDateString('en-US', { month: 'short', year: 'numeric' });
key = `${tempDate.getFullYear()}-${tempDate.getMonth()}`;
tempDate.setMonth(tempDate.getMonth() + 1);
} else if (currentGanttView === 'quarter') {
@@ -482,32 +658,26 @@ function renderRoadmap() {
return leftWidth + (index * colWidth) + (ratio * colWidth);
};
- // 2. Groupement par assigné (uniquement ceux qui ont des tickets visibles)
const grouped = ticketsWithDates.reduce((acc, t) => {
- const name = t.assignee || "Non assigné";
+ const name = t.assignee || "Unassigned";
if (!acc[name]) acc[name] = [];
acc[name].push(t);
return acc;
}, {});
- // Début HTML
let html = `
`;
- // Ligne "Aujourd'hui"
const todayX = getXPos(new Date());
if (todayX > leftWidth) {
html += `
`;
- html += `
Aujourd'hui
`;
+ html += `
Today
`;
}
- // Header
- html += `