This commit is contained in:
oliver
2026-06-14 20:20:36 -03:00
parent 0502073bd9
commit f4c1a5cf96
2 changed files with 341 additions and 51 deletions
+113 -36
View File
@@ -1399,27 +1399,40 @@ async function crmLoadRecords() {
/* ─── CRM — Render table ────────────────────────────────────────── */
function crmRender() {
const search = (
document.getElementById("crm-search")?.value ?? ""
const searchName = (
document.getElementById("crm-search-name")?.value ?? ""
).toLowerCase();
const searchCompany = (
document.getElementById("crm-search-company")?.value ?? ""
).toLowerCase();
const searchEmail = (
document.getElementById("crm-search-email")?.value ?? ""
).toLowerCase();
const searchStage = (
document.getElementById("crm-search-stage")?.value ?? ""
).toLowerCase();
const searchStrategy = (
document.getElementById("crm-search-strategy")?.value ?? ""
).toLowerCase();
const searchCategory = (
document.getElementById("crm-search-category")?.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 name = (r.Name ?? "").toLowerCase();
const company = (r.Company ?? "").toLowerCase();
const email = (r.email ?? "").toLowerCase();
const stage = (r.Stage ?? "").toLowerCase();
const strategy = (r.strategy ?? "").toLowerCase();
const category = (r.category ?? "").toLowerCase();
return (
(!searchName || name.includes(searchName)) &&
(!searchCompany || company.includes(searchCompany)) &&
(!searchEmail || email.includes(searchEmail)) &&
(!searchStage || stage.includes(searchStage)) &&
(!searchStrategy || strategy.includes(searchStrategy)) &&
(!searchCategory || category.includes(searchCategory))
);
});
const total = crmRecords.length;
@@ -1435,7 +1448,7 @@ function crmRender() {
const tbody = document.getElementById("crm-table-body");
if (filtered.length === 0) {
tbody.innerHTML = `<tr><td colspan="9"><div class="empty-state">
tbody.innerHTML = `<tr><td colspan="8"><div class="empty-state">
<svg width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/>
<circle cx="9" cy="7" r="4"/>
@@ -1460,27 +1473,16 @@ function crmRender() {
const dateNext = r.date_next_contact
? formatDate(r.date_next_contact)
: "—";
return `<tr data-crm-id="${id}">
<td><input type="checkbox" class="crm-select" data-id="${id}" onchange="crmUpdateDeleteBtn()" /></td>
// Prevent row click when clicking the checkbox
return `<tr data-crm-id="${id}" style="cursor:pointer;" onclick="crmOpenEdit(${id})">
<td style="cursor:default;" onclick="event.stopPropagation()"><input type="checkbox" class="crm-select" data-id="${id}" onchange="crmUpdateDeleteBtn()" /></td>
<td><strong>${name || "—"}</strong></td>
<td>${company || "—"}</td>
<td><a href="mailto:${email}" style="color:var(--accent)">${email || "—"}</a></td>
<td><a href="mailto:${email}" style="color:var(--accent)" onclick="event.stopPropagation()">${email || "—"}</a></td>
<td><span class="badge ${stage === "Hold" ? "badge-other" : "badge-green"}">${stage || "—"}</span></td>
<td style="max-width:180px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap" title="${strategy}">${strategy || "—"}</td>
<td>${category ? escHtml(category) : "—"}</td>
<td class="text-muted">${dateNext}</td>
<td style="white-space:nowrap;">
<button class="btn btn-danger btn-sm"
data-id="${id}"
data-name="${name}"
onclick="crmDeleteContact(this)">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<polyline points="3 6 5 6 21 6"/><path d="M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6"/>
<path d="M10 11v6M14 11v6"/><path d="M9 6V4a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v2"/>
</svg>
Delete
</button>
</td>
</tr>`;
})
.join("");
@@ -1493,8 +1495,7 @@ 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";
const label = checked > 0 ? `Delete Checked (${checked})` : "Delete Checked";
btn.innerHTML = `<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" style="margin-right:4px;vertical-align:middle">
<polyline points="3 6 5 6 21 6"/><path d="M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6"/>
<path d="M10 11v6M14 11v6"/><path d="M9 6V4a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v2"/>
@@ -1575,6 +1576,82 @@ async function crmDeleteSelected() {
await crmLoadRecords();
}
/* ─── CRM — Edit modal ──────────────────────────────────────────── */
function crmOpenEdit(id) {
const r = crmRecords.find((c) => c.id === id);
if (!r) return;
document.getElementById("crm-edit-id").value = r.id;
document.getElementById("crm-edit-name").value = r.Name ?? "";
document.getElementById("crm-edit-company").value = r.Company ?? "";
document.getElementById("crm-edit-email").value = r.email ?? "";
document.getElementById("crm-edit-stage").value = r.Stage ?? "";
document.getElementById("crm-edit-strategy").value = r.strategy ?? "";
document.getElementById("crm-edit-category").value = r.category ?? "";
document.getElementById("crm-edit-affiliate").value = r.affiliate ?? "";
document.getElementById("crm-edit-number").value = r.Number ?? "";
document.getElementById("crm-edit-date-next").value = r.date_next_contact
? r.date_next_contact.split("T")[0]
: "";
document.getElementById("crm-edit-history").value = r.History ?? "";
document.getElementById("crm-edit-title").textContent =
`Edit Contact — ${r.Name || r.email || `#${r.id}`}`;
document.getElementById("crm-edit-backdrop").classList.add("open");
setTimeout(() => document.getElementById("crm-edit-name").focus(), 80);
}
function crmCloseEdit() {
document.getElementById("crm-edit-backdrop").classList.remove("open");
}
document.getElementById("crm-edit-backdrop")?.addEventListener("click", (e) => {
if (e.target === e.currentTarget) crmCloseEdit();
});
async function crmSaveEdit() {
const id = Number(document.getElementById("crm-edit-id").value);
const payload = {
id,
Name: document.getElementById("crm-edit-name").value.trim(),
Company: document.getElementById("crm-edit-company").value.trim(),
email: document.getElementById("crm-edit-email").value.trim(),
Stage: document.getElementById("crm-edit-stage").value,
strategy: document.getElementById("crm-edit-strategy").value.trim(),
category: document.getElementById("crm-edit-category").value.trim(),
affiliate: document.getElementById("crm-edit-affiliate").value.trim(),
Number: document.getElementById("crm-edit-number").value.trim(),
date_next_contact:
document.getElementById("crm-edit-date-next").value || null,
History: document.getElementById("crm-edit-history").value.trim(),
};
const btn = document.getElementById("crm-save-btn");
btn.disabled = true;
const orig = btn.innerHTML;
btn.innerHTML = `<div class="spinner" style="width:14px;height:14px;"></div> Saving…`;
try {
const res = await apiFetch(apiUrl(ROUTES.crm), {
method: "PUT",
headers: apiHeaders(),
body: JSON.stringify(payload),
});
if (!res.ok) throw new Error(`HTTP ${res.status}: ${res.statusText}`);
toast(
`Contact “${payload.Name || payload.email || `#${id}`}” updated`,
"success",
);
crmCloseEdit();
await crmLoadRecords();
} catch (err) {
toast("Save failed: " + err.message, "error");
} finally {
btn.disabled = false;
btn.innerHTML = orig;
}
}
/* ─── Init ────────────────────────────────────────────────────────── */
settingsLoad();
const _boot = getCookie("al_session");