|
|
|
@@ -19,6 +19,7 @@
|
|
|
|
|
<strong>{{ repo }}</strong>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="board-actions">
|
|
|
|
|
<button class="btn btn-sm" onclick="openBoardConfig('{{ owner }}', '{{ repo }}')" title="Gérer les colonnes">⚙ Colonnes</button>
|
|
|
|
|
<button class="btn btn-sm" onclick="registerWebhook('{{ owner }}', '{{ repo }}')" title="Enregistrer webhook">🔔 Webhook</button>
|
|
|
|
|
<a href="/notes/{{ owner }}/{{ repo }}" class="btn btn-sm">📝 Notes</a>
|
|
|
|
|
<button class="btn btn-sm btn-primary" onclick="openCreateModal('{{ owner }}','{{ repo }}','Backlog')">+ Issue</button>
|
|
|
|
@@ -82,6 +83,19 @@
|
|
|
|
|
{% endif %}
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- Board Config Modal (column management) -->
|
|
|
|
|
<div id="board-config-modal" class="modal-overlay" style="display:none">
|
|
|
|
|
<div class="modal-container">
|
|
|
|
|
<div class="modal-header">
|
|
|
|
|
<h3>⚙ Configuration des colonnes</h3>
|
|
|
|
|
<button onclick="closeBoardConfig()" class="modal-close">×</button>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="modal-body" id="board-config-content">
|
|
|
|
|
<div class="loading">Chargement...</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- Create Issue Modal (reuses card-modal) -->
|
|
|
|
|
<div id="create-modal" class="modal-overlay" style="display:none">
|
|
|
|
|
<div class="modal-container">
|
|
|
|
@@ -385,5 +399,147 @@
|
|
|
|
|
alert(d.status==='already_registered'?'Webhook déjà enregistré':'Webhook enregistré');
|
|
|
|
|
} catch(e) { alert('Erreur: '+e); }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ── Column Configuration ──
|
|
|
|
|
let _colMappings = {};
|
|
|
|
|
|
|
|
|
|
function toggleColConfig(evt, owner, repo, col) {
|
|
|
|
|
evt.stopPropagation();
|
|
|
|
|
const safeId = col.replace(/ /g, '_');
|
|
|
|
|
const panel = document.getElementById('col-config-' + safeId);
|
|
|
|
|
if (!panel) return;
|
|
|
|
|
// Close all others
|
|
|
|
|
document.querySelectorAll('.col-config-panel').forEach(p => { if (p !== panel) p.style.display = 'none'; });
|
|
|
|
|
const isOpen = panel.style.display === 'block';
|
|
|
|
|
panel.style.display = isOpen ? 'none' : 'block';
|
|
|
|
|
if (!isOpen) {
|
|
|
|
|
// Populate label select
|
|
|
|
|
const sel = document.getElementById('col-label-' + safeId);
|
|
|
|
|
if (sel && sel.options.length <= 1) {
|
|
|
|
|
sel.innerHTML = '<option value="">— Aucun mapping —</option>';
|
|
|
|
|
ALL_LABELS.forEach(l => {
|
|
|
|
|
sel.innerHTML += '<option value="'+escHtml(l.name)+'">'+escHtml(l.name)+'</option>';
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
// Pre-select current mapping
|
|
|
|
|
if (_colMappings[col]) sel.value = _colMappings[col];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function saveColMapping(owner, repo, col) {
|
|
|
|
|
const safeId = col.replace(/ /g, '_');
|
|
|
|
|
const sel = document.getElementById('col-label-' + safeId);
|
|
|
|
|
if (!sel) return;
|
|
|
|
|
const label = sel.value;
|
|
|
|
|
if (!label) { alert('Sélectionnez un label'); return; }
|
|
|
|
|
const params = new URLSearchParams({owner, repo, column: col, gitea_label: label});
|
|
|
|
|
await fetch('/api/col-mapping?' + params, {method:'POST', headers:{'X-CSRF-Token': getCSRFToken()}});
|
|
|
|
|
_colMappings[col] = label;
|
|
|
|
|
document.getElementById('col-config-' + safeId).style.display = 'none';
|
|
|
|
|
htmx.ajax('GET', `/board/${owner}/${repo}?fragment=1`, {target: '#board-columns', swap: 'outerHTML'});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function deleteColMapping(owner, repo, col) {
|
|
|
|
|
if (!confirm('Supprimer le mapping pour « ' + col + ' » ?')) return;
|
|
|
|
|
const safeId = col.replace(/ /g, '_');
|
|
|
|
|
await fetch(`/api/col-mapping?owner=${owner}&repo=${repo}&column=${encodeURIComponent(col)}`, {method:'DELETE', headers:{'X-CSRF-Token': getCSRFToken()}});
|
|
|
|
|
delete _colMappings[col];
|
|
|
|
|
document.getElementById('col-config-' + safeId).style.display = 'none';
|
|
|
|
|
htmx.ajax('GET', `/board/${owner}/${repo}?fragment=1`, {target: '#board-columns', swap: 'outerHTML'});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ── Board Config (Column Management) ──
|
|
|
|
|
async function openBoardConfig(owner, repo) {
|
|
|
|
|
document.getElementById('card-modal').style.display = 'none';
|
|
|
|
|
document.getElementById('create-modal').style.display = 'none';
|
|
|
|
|
const modal = document.getElementById('board-config-modal');
|
|
|
|
|
const content = document.getElementById('board-config-content');
|
|
|
|
|
modal.style.display = 'flex';
|
|
|
|
|
content.innerHTML = '<div class="loading">Chargement...</div>';
|
|
|
|
|
|
|
|
|
|
const resp = await fetch(`/api/board-config/${owner}/${repo}`);
|
|
|
|
|
const cfg = await resp.json();
|
|
|
|
|
|
|
|
|
|
let h = '<div class="detail-section"><h4>Colonnes actuelles</h4>';
|
|
|
|
|
h += '<ul class="config-column-list" id="config-column-list">';
|
|
|
|
|
cfg.columns.forEach((c, i) => {
|
|
|
|
|
h += '<li class="config-column-row" data-col="'+escHtml(c)+'">';
|
|
|
|
|
h += '<input type="text" value="'+escHtml(c)+'" class="form-input config-col-name" style="width:200px;margin:0" data-orig="'+escHtml(c)+'">';
|
|
|
|
|
const mapped = (cfg.col_mappings||[]).find(m => m.column_name === c);
|
|
|
|
|
const labelName = mapped ? mapped.gitea_label : '';
|
|
|
|
|
h += '<span class="col-mapping-badge" style="font-size:11px;color:var(--text-dim);margin:0 8px">' + (labelName ? '🏷 '+escHtml(labelName) : '') + '</span>';
|
|
|
|
|
h += '<button class="btn btn-sm" onclick="renameColumn(\''+owner+'\',\''+repo+'\','+i+',\''+escHtml(c)+'\')">Renommer</button>';
|
|
|
|
|
h += '<button class="btn btn-sm btn-del" onclick="deleteColumn(\''+owner+'\',\''+repo+'\','+i+',\''+escHtml(c)+'\')">✕</button>';
|
|
|
|
|
h += '</li>';
|
|
|
|
|
});
|
|
|
|
|
h += '</ul></div>';
|
|
|
|
|
|
|
|
|
|
h += '<div class="detail-section"><h4>Ajouter une colonne</h4>';
|
|
|
|
|
h += '<div class="form-row">';
|
|
|
|
|
h += '<input type="text" id="new-col-name" class="form-input" placeholder="Nom de la colonne..." style="flex:1">';
|
|
|
|
|
h += '<button class="btn btn-primary btn-sm" onclick="addColumn(\''+owner+'\',\''+repo+'\')">+ Ajouter</button>';
|
|
|
|
|
h += '</div></div>';
|
|
|
|
|
|
|
|
|
|
h += '<div class="detail-actions">';
|
|
|
|
|
h += '<button onclick="closeBoardConfig()" class="btn btn-sm">Fermer</button>';
|
|
|
|
|
h += '</div>';
|
|
|
|
|
|
|
|
|
|
content.innerHTML = h;
|
|
|
|
|
window._boardConfig = cfg;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function closeBoardConfig() {
|
|
|
|
|
document.getElementById('board-config-modal').style.display = 'none';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function addColumn(owner, repo) {
|
|
|
|
|
const inp = document.getElementById('new-col-name');
|
|
|
|
|
const name = inp.value.trim();
|
|
|
|
|
if (!name) return;
|
|
|
|
|
const cfg = window._boardConfig;
|
|
|
|
|
const cols = [...cfg.columns, name];
|
|
|
|
|
await fetch(`/api/board-config/${owner}/${repo}?columns=${encodeURIComponent(JSON.stringify(cols))}`, {method:'POST', headers:{'X-CSRF-Token': getCSRFToken()}});
|
|
|
|
|
closeBoardConfig();
|
|
|
|
|
htmx.ajax('GET', `/board/${owner}/${repo}?fragment=1`, {target: '#board-columns', swap: 'outerHTML'});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function renameColumn(owner, repo, idx, oldName) {
|
|
|
|
|
const row = document.querySelector(`.config-column-row[data-col="${oldName.replace(/"/g,'"')}"]`);
|
|
|
|
|
const inp = row ? row.querySelector('.config-col-name') : null;
|
|
|
|
|
const newName = inp ? inp.value.trim() : '';
|
|
|
|
|
if (!newName || newName === oldName) return;
|
|
|
|
|
const cfg = window._boardConfig;
|
|
|
|
|
cfg.columns[idx] = newName;
|
|
|
|
|
await fetch(`/api/board-config/${owner}/${repo}?columns=${encodeURIComponent(JSON.stringify(cfg.columns))}`, {method:'POST', headers:{'X-CSRF-Token': getCSRFToken()}});
|
|
|
|
|
// Update col_mapping if one existed for old name
|
|
|
|
|
const oldMapping = (cfg.col_mappings||[]).find(m => m.column_name === oldName);
|
|
|
|
|
if (oldMapping) {
|
|
|
|
|
await fetch(`/api/col-mapping?owner=${owner}&repo=${repo}&column=${encodeURIComponent(newName)}&gitea_label=${oldMapping.gitea_label}`, {method:'POST', headers:{'X-CSRF-Token': getCSRFToken()}});
|
|
|
|
|
}
|
|
|
|
|
closeBoardConfig();
|
|
|
|
|
htmx.ajax('GET', `/board/${owner}/${repo}?fragment=1`, {target: '#board-columns', swap: 'outerHTML'});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function deleteColumn(owner, repo, idx, colName) {
|
|
|
|
|
if (!confirm('Supprimer la colonne « ' + colName + ' » ? Les cartes iront dans la première colonne.')) return;
|
|
|
|
|
const cfg = window._boardConfig;
|
|
|
|
|
const cols = cfg.columns.filter((_, i) => i !== idx);
|
|
|
|
|
await fetch(`/api/board-config/${owner}/${repo}?columns=${encodeURIComponent(JSON.stringify(cols))}`, {method:'POST', headers:{'X-CSRF-Token': getCSRFToken()}});
|
|
|
|
|
// Delete col_mapping
|
|
|
|
|
await fetch(`/api/col-mapping?owner=${owner}&repo=${repo}&column=${encodeURIComponent(colName)}`, {method:'DELETE', headers:{'X-CSRF-Token': getCSRFToken()}});
|
|
|
|
|
closeBoardConfig();
|
|
|
|
|
htmx.ajax('GET', `/board/${owner}/${repo}?fragment=1`, {target: '#board-columns', swap: 'outerHTML'});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Load col mappings on init
|
|
|
|
|
(async function() {
|
|
|
|
|
const owner = '{{ owner }}';
|
|
|
|
|
const repo = '{{ repo }}';
|
|
|
|
|
try {
|
|
|
|
|
const resp = await fetch(`/api/board-config/${owner}/${repo}`);
|
|
|
|
|
const cfg = await resp.json();
|
|
|
|
|
(cfg.col_mappings||[]).forEach(m => { _colMappings[m.column_name] = m.gitea_label; });
|
|
|
|
|
} catch(e) {}
|
|
|
|
|
})();
|
|
|
|
|
</script>
|
|
|
|
|
{% endblock %}
|
|
|
|
|