Replace native confirm() with a branded modal on every delete action, both tools

This commit is contained in:
2026-08-24 16:32:44 -07:00
parent 18da00addf
commit 88f2bf3782
2 changed files with 127 additions and 21 deletions

View File

@@ -164,6 +164,13 @@
.toast{position:fixed; bottom:24px; left:50%; transform:translateX(-50%); background:var(--text); color:#fff; padding:12px 22px; font-size:14px; opacity:0; pointer-events:none; transition:opacity .2s; z-index:50;}
.toast.show{opacity:1;}
#jsonFile{display:none;}
.btn.danger{background:var(--danger); color:#fff; border:none;}
.btn.danger:hover{background:#b3121d;}
.confirm-backdrop{position:fixed; inset:0; background:rgba(22,22,22,.5); display:flex; align-items:center; justify-content:center; z-index:70; padding:20px;}
.confirm-card{background:var(--layer); border:1px solid var(--border); border-left:4px solid var(--danger); padding:22px 26px; max-width:420px; width:100%;}
.confirm-card h3{font-size:16px; font-weight:600; margin-bottom:8px;}
.confirm-card p{font-size:14px; color:var(--text-secondary); line-height:1.5; margin-bottom:20px;}
.confirm-actions{display:flex; justify-content:flex-end; gap:10px;}
@media (max-width:1200px){ .bc-top{grid-template-columns:1fr;} .bc-out{grid-template-columns:1fr 1fr;} }
@media (prefers-reduced-motion: reduce){ *{transition:none !important;} }
</style>
@@ -503,10 +510,16 @@ document.getElementById('panel-map').addEventListener('click', e=>{
}
if(t.dataset.cdel){
const c = clusterById(t.dataset.cdel);
if(c && confirm(`Remove cluster "${c.name}"? Its issues return to Unclustered.`)){
S.problems.forEach(p=>{ if(p.cluster===c.id) p.cluster=''; });
S.clusters = S.clusters.filter(x=>x.id!==c.id);
save(); renderMap();
if(c){
confirmModal({
title: 'Remove cluster?',
message: `Remove cluster "${c.name}"? Its issues return to Unclustered.`,
confirmLabel: 'Remove cluster'
}, ()=>{
S.problems.forEach(p=>{ if(p.cluster===c.id) p.cluster=''; });
S.clusters = S.clusters.filter(x=>x.id!==c.id);
save(); renderMap();
});
}
return;
}
@@ -530,9 +543,16 @@ document.getElementById('panel-map').addEventListener('click', e=>{
return;
}
if(t.dataset.pdel){
S.problems = S.problems.filter(p=>p.id !== t.dataset.pdel);
editingItems.delete(t.dataset.pdel);
save(); renderMap();
const pid = t.dataset.pdel;
confirmModal({
title: 'Remove issue?',
message: 'Remove this issue from the problem map? This cannot be undone.',
confirmLabel: 'Remove issue'
}, ()=>{
S.problems = S.problems.filter(p=>p.id !== pid);
editingItems.delete(pid);
save(); renderMap();
});
return;
}
const head = t.closest('[data-toggle]');
@@ -685,8 +705,15 @@ document.getElementById('bcDetail').addEventListener('click', e=>{
}
if(e.target.dataset.aibc){ aiDraftBreadcrumb(e.target.dataset.aibc, e.target); return; }
if(e.target.dataset.bdel){
S.breadcrumbs = S.breadcrumbs.filter(b=>b.id!==e.target.dataset.bdel);
save(); renderBc();
const bid = e.target.dataset.bdel;
confirmModal({
title: 'Remove breadcrumb?',
message: 'Remove this breadcrumb? Its fields will be lost unless you have already saved or exported this session.',
confirmLabel: 'Remove breadcrumb'
}, ()=>{
S.breadcrumbs = S.breadcrumbs.filter(b=>b.id!==bid);
save(); renderBc();
});
}
});
document.getElementById('btnAddBc').addEventListener('click', ()=>{
@@ -762,7 +789,14 @@ function renderAct(){
}
document.getElementById('actList').addEventListener('click', e=>{
if(e.target.dataset.done){ const a=S.actions.find(x=>x.id===e.target.dataset.done); if(a){a.done=!a.done; save(); renderAct();} }
if(e.target.dataset.adel){ S.actions=S.actions.filter(a=>a.id!==e.target.dataset.adel); save(); renderAct(); }
if(e.target.dataset.adel){
const aid = e.target.dataset.adel;
confirmModal({
title: 'Remove action?',
message: 'Remove this action item? This cannot be undone.',
confirmLabel: 'Remove action'
}, ()=>{ S.actions=S.actions.filter(a=>a.id!==aid); save(); renderAct(); });
}
});
document.getElementById('actList').addEventListener('change', e=>{
const t = e.target;
@@ -864,10 +898,14 @@ document.getElementById('btnExport').addEventListener('click', ()=>{
toast('Summary downloaded');
});
document.getElementById('btnReset').addEventListener('click', ()=>{
if(confirm('Reset to the clustered July 21 baseline? Anything entered since will be lost unless saved to JSON.')){
confirmModal({
title: 'Reset to baseline?',
message: 'Reset to the clustered July 21 baseline? Anything entered since will be lost unless saved to JSON.',
confirmLabel: 'Reset'
}, ()=>{
S = JSON.parse(JSON.stringify(DEFAULT_STATE));
save(); renderAll(); toast('Reset to baseline');
}
});
});
let toastTimer;
@@ -879,6 +917,27 @@ function toast(msg){
toastTimer = setTimeout(()=>t.classList.remove('show'), 2200);
}
function confirmModal(opts, onConfirm){
const backdrop = document.createElement('div');
backdrop.className = 'confirm-backdrop';
backdrop.innerHTML = `<div class="confirm-card" role="alertdialog" aria-modal="true" aria-labelledby="confirmTitle">
<h3 id="confirmTitle">${esc(opts.title || 'Remove this?')}</h3>
<p>${esc(opts.message || 'This cannot be undone.')}</p>
<div class="confirm-actions">
<button class="btn ghost" data-cc="cancel">Cancel</button>
<button class="btn danger" data-cc="ok">${esc(opts.confirmLabel || 'Delete')}</button>
</div>
</div>`;
document.body.appendChild(backdrop);
function close(){ document.removeEventListener('keydown', onKey); backdrop.remove(); }
function onKey(e){ if(e.key==='Escape') close(); }
document.addEventListener('keydown', onKey);
backdrop.addEventListener('click', e=>{ if(e.target===backdrop) close(); });
backdrop.querySelector('[data-cc="cancel"]').addEventListener('click', close);
backdrop.querySelector('[data-cc="ok"]').addEventListener('click', ()=>{ close(); onConfirm(); });
backdrop.querySelector('[data-cc="ok"]').focus();
}
function renderAll(){ renderMap(); renderBc(); renderAct(); }
renderAll();
</script>