v0.0.6: massa-acties en CSV-export in de lijsten van contacten, relaties, producten en offertes
Rijen aanvinken (shift-klik voor een reeks, of alles op de pagina) en in de balk onderin een actie kiezen (templates/_bulk.html, static/bulk.js). Contacten: status wijzigen, toewijzen, omzetten naar klant, exporteren, verwijderen; relaties: type wijzigen, exporteren, verwijderen; producten: actief/inactief, exporteren, verwijderen (in offertes gebruikt → inactief); offertes: status wijzigen (met de gevolgen voor het contact), exporteren, verwijderen. Exporteer CSV neemt de filters van de lijst mee en levert alle velden; bij contacten ook labels, toegewezen gebruiker, relatie, model, portaaltoegang en de extra formuliervelden als eigen kolom. Nieuwe leadstatus Vervallen (telt als afgesloten). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
3cf53bc248
commit
db3e26d258
@@ -0,0 +1,57 @@
|
||||
/* Massa-acties in lijsten (templates/_bulk.html): rijen aanvinken, shift-klik voor een reeks,
|
||||
dan in de balk onderin een actie kiezen. Een klik op het vinkje opent de rij niet. */
|
||||
(() => {
|
||||
const form = document.getElementById('bulk');
|
||||
if (!form) return;
|
||||
const boxes = Array.from(document.querySelectorAll('input[name="ids"][form="bulk"]'));
|
||||
const all = document.querySelector('[data-bulk-all]');
|
||||
const count = form.querySelector('[data-bulk-count]');
|
||||
const action = form.elements.action;
|
||||
let last = null;
|
||||
|
||||
const update = () => {
|
||||
const n = boxes.filter(b => b.checked).length;
|
||||
count.textContent = n;
|
||||
form.hidden = !n;
|
||||
document.body.classList.toggle('bulk-open', n > 0);
|
||||
if (all) {
|
||||
all.checked = n > 0 && n === boxes.length;
|
||||
all.indeterminate = n > 0 && n < boxes.length;
|
||||
}
|
||||
boxes.forEach(b => b.closest('tr')?.classList.toggle('selected', b.checked));
|
||||
};
|
||||
const showFields = () => form.querySelectorAll('[data-show]').forEach(el => {
|
||||
const on = el.dataset.show === action.value;
|
||||
el.hidden = !on;
|
||||
el.disabled = !on;
|
||||
});
|
||||
|
||||
boxes.forEach((box, i) => box.closest('td').addEventListener('click', e => {
|
||||
e.stopPropagation();
|
||||
if (e.target !== box) box.checked = !box.checked;
|
||||
if (e.shiftKey && last !== null) {
|
||||
boxes.slice(Math.min(last, i), Math.max(last, i) + 1).forEach(b => { b.checked = box.checked; });
|
||||
}
|
||||
last = i;
|
||||
update();
|
||||
}));
|
||||
all?.closest('th').addEventListener('click', e => {
|
||||
if (e.target !== all) all.checked = !all.checked;
|
||||
boxes.forEach(b => { b.checked = all.checked; });
|
||||
update();
|
||||
});
|
||||
form.querySelector('[data-bulk-clear]').addEventListener('click', () => {
|
||||
boxes.forEach(b => { b.checked = false; });
|
||||
update();
|
||||
});
|
||||
action.addEventListener('change', showFields);
|
||||
form.addEventListener('submit', e => {
|
||||
const ask = action.selectedOptions[0]?.dataset.confirm;
|
||||
if (ask && !confirm(ask.replace('{n}', count.textContent))) return e.preventDefault();
|
||||
// een export is een download: de pagina blijft staan, dus de keuze terugzetten
|
||||
if (action.value === 'export') setTimeout(() => { action.value = ''; showFields(); }, 0);
|
||||
});
|
||||
addEventListener('pageshow', update);
|
||||
showFields();
|
||||
update();
|
||||
})();
|
||||
@@ -390,3 +390,24 @@ tr.link td { transition: background-color 0.2s; }
|
||||
.chip.campaign-gestopt, .chip.campaign-gepauzeerd { background: var(--light); color: var(--ink-muted); }
|
||||
.schedule { margin-top: 1.5rem; padding-top: 1.25rem; border-top: 1px solid var(--line); }
|
||||
.progress-bar { background: var(--orange); }
|
||||
|
||||
/* ---------- massa-acties (templates/_bulk.html, static/bulk.js) ---------- */
|
||||
.chip.vervallen { background: var(--light); color: var(--ink-muted); }
|
||||
.table > :not(caption) > * > .bulk-col { width: 2.25rem; padding-right: 0; cursor: pointer; }
|
||||
.bulk-col .form-check-input { margin: 0.2rem 0 0; cursor: pointer; }
|
||||
.table > tbody > tr.selected > * { --bs-table-bg-state: var(--orange-tint); }
|
||||
.bulk-bar { position: fixed; z-index: 1030; left: 50%; bottom: 1rem; transform: translateX(-50%); display: flex; align-items: center; gap: 0.6rem; flex-wrap: wrap; max-width: calc(100% - 2rem); padding: 0.6rem 0.75rem 0.6rem 1.25rem; background: var(--white); border: 1px solid var(--orange-line); border-radius: var(--radius-pill); box-shadow: 0 12px 32px rgba(0, 0, 0, 0.14); }
|
||||
.bulk-bar[hidden] { display: none; }
|
||||
.bulk-bar .form-select { width: auto; max-width: 16rem; }
|
||||
.bulk-count { white-space: nowrap; }
|
||||
body.bulk-open main.content { padding-bottom: 7rem; }
|
||||
@media (max-width: 767.98px) {
|
||||
.bulk-bar { left: 0.75rem; right: 0.75rem; bottom: 0.75rem; transform: none; max-width: none; border-radius: var(--radius-md); padding: 0.75rem; }
|
||||
.bulk-bar .form-select { flex: 1 1 100%; max-width: none; }
|
||||
.bulk-bar .btn { flex: 1 1 auto; }
|
||||
.bulk-count { flex-basis: 100%; }
|
||||
.table-stack > tbody > tr { position: relative; }
|
||||
.table-stack > tbody > tr > td.bulk-col { position: absolute; top: 0.8rem; right: 0; padding: 0; width: auto; }
|
||||
.table-stack > tbody > tr > td.bulk-col::before { content: none; }
|
||||
.table-stack > tbody > tr > td.bulk-col ~ td.stack-title { padding-right: 2.25rem; }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user