From 88f2bf3782093ba1a7b8389c0ae2f6f642427303 Mon Sep 17 00:00:00 2001 From: Matt Mabrey Date: Mon, 24 Aug 2026 16:32:44 -0700 Subject: [PATCH] Replace native confirm() with a branded modal on every delete action, both tools --- tools/field-problem-workshop.html | 83 ++++++++++++++++++++++++----- tools/scope-lock-meeting-suite.html | 65 ++++++++++++++++++---- 2 files changed, 127 insertions(+), 21 deletions(-) diff --git a/tools/field-problem-workshop.html b/tools/field-problem-workshop.html index 106d669..5c40938 100644 --- a/tools/field-problem-workshop.html +++ b/tools/field-problem-workshop.html @@ -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;} } @@ -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 = `
+

${esc(opts.title || 'Remove this?')}

+

${esc(opts.message || 'This cannot be undone.')}

+
+ + +
+
`; + 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(); diff --git a/tools/scope-lock-meeting-suite.html b/tools/scope-lock-meeting-suite.html index 7de1b29..495aba7 100644 --- a/tools/scope-lock-meeting-suite.html +++ b/tools/scope-lock-meeting-suite.html @@ -214,6 +214,13 @@ opacity:0; pointer-events:none; transition:opacity .2s; } .toast.show{opacity:1;} + .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:1100px){ .board{grid-template-columns:repeat(2,1fr);} .reg-form{grid-template-columns:1fr 1fr;} } @media (prefers-reduced-motion: reduce){ *{transition:none !important;} } @@ -502,8 +509,12 @@ document.getElementById('panel-board').addEventListener('click', e=>{ if(item){ item.b = (item.b === t.dataset.b) ? '' : t.dataset.b; save(); renderScope(); } } if(t.dataset && t.dataset.del){ - S.scope = S.scope.filter(i=>i.id!==t.dataset.del); - save(); renderScope(); + const iid = t.dataset.del; + confirmModal({ + title: 'Remove item?', + message: 'Remove this item from the Scope Boundary Board? This cannot be undone.', + confirmLabel: 'Remove item' + }, ()=>{ S.scope = S.scope.filter(i=>i.id!==iid); save(); renderScope(); }); } }); @@ -553,9 +564,16 @@ document.getElementById('rankList').addEventListener('click', e=>{ [S.rank[i], S.rank[i+1]] = [S.rank[i+1], S.rank[i]]; save(); renderRank(); } else if(t.dataset.rdel !== undefined){ - S.rank.splice(+t.dataset.rdel, 1); - if(S.cutAfter > S.rank.length) S.cutAfter = S.rank.length; - save(); renderRank(); + const idx = +t.dataset.rdel; + confirmModal({ + title: 'Remove item?', + message: 'Remove this item from the priority ranking? This cannot be undone.', + confirmLabel: 'Remove item' + }, ()=>{ + S.rank.splice(idx, 1); + if(S.cutAfter > S.rank.length) S.cutAfter = S.rank.length; + save(); renderRank(); + }); } }); @@ -609,8 +627,12 @@ document.getElementById('btnAddDecision').addEventListener('click', ()=>{ document.getElementById('regTableWrap').addEventListener('click', e=>{ if(e.target.dataset.ddel !== undefined){ - S.decisions.splice(+e.target.dataset.ddel, 1); - save(); renderRegistry(); + const idx = +e.target.dataset.ddel; + confirmModal({ + title: 'Remove decision?', + message: 'Remove this decision from the registry? This cannot be undone.', + confirmLabel: 'Remove decision' + }, ()=>{ S.decisions.splice(idx, 1); save(); renderRegistry(); }); } }); @@ -719,10 +741,14 @@ document.getElementById('btnExport').addEventListener('click', ()=>{ /* ================= RESET ================= */ document.getElementById('btnReset').addEventListener('click', ()=>{ - if(confirm('Reset all four tools to their starting state? This clears everything entered in this meeting.')){ + confirmModal({ + title: 'Reset everything?', + message: 'Reset all four tools to their starting state? This clears everything entered in this meeting.', + confirmLabel: 'Reset' + }, ()=>{ S = JSON.parse(JSON.stringify(DEFAULT_STATE)); save(); renderAll(); toast('Reset complete'); - } + }); }); /* ================= TOAST ================= */ @@ -735,6 +761,27 @@ function toast(msg){ toastTimer = setTimeout(()=>t.classList.remove('show'), 2000); } +function confirmModal(opts, onConfirm){ + const backdrop = document.createElement('div'); + backdrop.className = 'confirm-backdrop'; + backdrop.innerHTML = `
+

${esc(opts.title || 'Remove this?')}

+

${esc(opts.message || 'This cannot be undone.')}

+
+ + +
+
`; + 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(){ renderScope(); renderRank(); renderRegistry(); renderReady(); } renderAll();