diff --git a/app.js b/app.js index 791a6bb..eb04999 100644 --- a/app.js +++ b/app.js @@ -9,6 +9,7 @@ const ROUTES = { backup: "/admin/backup-repo", servers: "/admin/server", keys: "/admin/key", + crm: "/crm", }; /* ─── State ──────────────────────────────────────────────────────── */ @@ -19,6 +20,7 @@ let backupRecords = []; let serversData = []; let currentServerIdx = 0; let keysData = []; +let crmRecords = []; let currentSession = null; let currentEmail = null; @@ -187,6 +189,7 @@ document.querySelectorAll(".nav-item[data-section]").forEach((el) => { servers: "Servers", keys: "Keys", sales: "Sales", + crm: "CRM", }; document.getElementById("page-title").textContent = labels[key] || key; @@ -208,6 +211,9 @@ document.querySelectorAll(".nav-item[data-section]").forEach((el) => { } else if (key === "keys") { topbar.style.display = ""; btnRefresh.onclick = keysLoadRecords; + } else if (key === "crm") { + topbar.style.display = ""; + btnRefresh.onclick = crmLoadRecords; } else { topbar.style.display = "none"; } @@ -225,6 +231,9 @@ document.querySelectorAll(".nav-item[data-section]").forEach((el) => { if (key === "keys" && keysData.length === 0) { keysLoadRecords(); } + if (key === "crm" && crmRecords.length === 0) { + crmLoadRecords(); + } }); }); @@ -1356,6 +1365,223 @@ async function keysDelete(btn) { } } +/* ─── CRM — Load ────────────────────────────────────────────────── */ +async function crmLoadRecords() { + const tbody = document.getElementById("crm-table-body"); + tbody.innerHTML = `

Loading…

`; + try { + const res = await apiFetch(apiUrl(ROUTES.crm), { + headers: apiHeaders(), + }); + if (!res.ok) throw new Error(`HTTP ${res.status}: ${res.statusText}`); + const json = await res.json(); + // Normalise: response may be [[{...},...]] or [{...},...] or {response:[...]} + let data; + if (Array.isArray(json)) { + if (json.length > 0 && Array.isArray(json[0])) { + data = json[0]; + } else { + data = json; + } + } else if (json && json.response) { + data = json.response; + } else { + data = []; + } + crmRecords = Array.isArray(data) ? data : []; + crmRender(); + toast( + `Loaded ${crmRecords.length} contact${crmRecords.length !== 1 ? "s" : ""}`, + "success", + ); + } catch (err) { + crmRecords = []; + tbody.innerHTML = `
+ +

${err.message}

+
`; + toast("Failed to load CRM contacts: " + err.message, "error"); + } +} + +/* ─── CRM — Render table ────────────────────────────────────────── */ +function crmRender() { + const search = ( + document.getElementById("crm-search")?.value ?? "" + ).toLowerCase(); + + const filtered = crmRecords.filter((r) => { + if (!search) return true; + const haystack = [ + r.Name, + r.Company, + r.email, + r.Stage, + r.strategy, + r.category, + r.affiliate, + r.History, + r.Number, + ] + .filter((v) => v != null) + .join(" ") + .toLowerCase(); + return haystack.includes(search); + }); + + const total = crmRecords.length; + const countEl = document.getElementById("crm-record-count"); + countEl.textContent = + filtered.length === total + ? `${total} contact${total !== 1 ? "s" : ""}` + : `${filtered.length} / ${total} contacts`; + + // Reset check-all state + const checkAll = document.getElementById("crm-check-all"); + if (checkAll) checkAll.checked = false; + + const tbody = document.getElementById("crm-table-body"); + if (filtered.length === 0) { + tbody.innerHTML = `
+ + + + + + +

No contacts found

+
`; + document.getElementById("crm-delete-selected-btn").disabled = true; + return; + } + + tbody.innerHTML = filtered + .map((r) => { + const id = r.id; + const name = escHtml(r.Name ?? ""); + const company = escHtml(r.Company ?? ""); + const email = escHtml(r.email ?? ""); + const stage = escHtml(r.Stage ?? ""); + const strategy = escHtml(r.strategy ?? ""); + const category = escHtml(r.category ?? ""); + const dateNext = r.date_next_contact + ? formatDate(r.date_next_contact) + : "—"; + return ` + + ${name || "—"} + ${company || "—"} + ${email || "—"} + ${stage || "—"} + ${strategy || "—"} + ${category ? escHtml(category) : "—"} + ${dateNext} + + + +`; + }) + .join(""); + + crmUpdateDeleteBtn(); +} + +/* ─── CRM — Checkbox helpers ────────────────────────────────────── */ +function crmUpdateDeleteBtn() { + const checked = document.querySelectorAll(".crm-select:checked").length; + const btn = document.getElementById("crm-delete-selected-btn"); + btn.disabled = checked === 0; + const label = + checked > 0 ? `Delete Selected (${checked})` : "Delete Selected"; + btn.innerHTML = ` + + + ${label}`; +} + +function crmToggleAll() { + const checked = document.getElementById("crm-check-all").checked; + document.querySelectorAll(".crm-select").forEach((cb) => { + cb.checked = checked; + }); + crmUpdateDeleteBtn(); +} + +/* ─── CRM — Delete single contact ──────────────────────────────── */ +async function crmDeleteContact(btn) { + const id = btn.dataset.id; + const name = btn.dataset.name || `#${id}`; + const ok = await confirmDialog( + "Delete Contact", + `Delete contact “${name}” (id: ${id})? This cannot be undone.`, + ); + if (!ok) return; + + try { + const res = await apiFetch(apiUrl(ROUTES.crm), { + method: "DELETE", + headers: apiHeaders(), + body: JSON.stringify({ id: Number(id) }), + }); + if (!res.ok) throw new Error(`HTTP ${res.status}`); + toast(`Contact “${name}” deleted`, "success"); + await crmLoadRecords(); + } catch (err) { + toast("Delete failed: " + err.message, "error"); + } +} + +/* ─── CRM — Delete selected contacts ────────────────────────────── */ +async function crmDeleteSelected() { + const ids = Array.from(document.querySelectorAll(".crm-select:checked")).map( + (cb) => Number(cb.dataset.id), + ); + if (ids.length === 0) return; + + const ok = await confirmDialog( + "Delete Selected Contacts", + `Delete ${ids.length} contact${ids.length !== 1 ? "s" : ""}? This cannot be undone.`, + ); + if (!ok) return; + + // Delete sequentially + let failed = 0; + for (const id of ids) { + try { + const res = await apiFetch(apiUrl(ROUTES.crm), { + method: "DELETE", + headers: apiHeaders(), + body: JSON.stringify({ id }), + }); + if (!res.ok) failed++; + } catch { + failed++; + } + } + + if (failed === 0) { + toast( + `Deleted ${ids.length} contact${ids.length !== 1 ? "s" : ""}`, + "success", + ); + } else { + toast( + `Deleted ${ids.length - failed} contact${ids.length - failed !== 1 ? "s" : ""} (${failed} failed)`, + "warning", + ); + } + await crmLoadRecords(); +} + /* ─── Init ────────────────────────────────────────────────────────── */ settingsLoad(); const _boot = getCookie("al_session"); diff --git a/index.html b/index.html index 8f489bb..b600c07 100644 --- a/index.html +++ b/index.html @@ -111,6 +111,24 @@ > Dashboards + + +
+ +
+
+
+ +
+
+ +
+
+
+ +
+
+ + + + + + + + CRM Contacts + + +
+
+ + + + + + + + + + + + + + + + + + + +
+ + NameCompanyEmailStageStrategyCategoryDate Next ContactActions
+
+
+

+ Loading contacts… +

+
+
+
+
+
+