Table sorting preferences will be saved and reused based on the user’s selections

This commit is contained in:
2026-10-25 00:39:55 -03:00
parent 19edee4e11
commit 0f1f399d1a
40 changed files with 896 additions and 230 deletions

View File

@@ -0,0 +1,75 @@
function createTableSorting({ environment }) {
const memory = new Map();
const storageKey = id => `opportunities-extension-table-sort-v1-${id}`;
function read(id, columns) {
let saved = memory.get(id);
if (!saved) {
try { saved = JSON.parse(environment.localStorage?.getItem(storageKey(id)) || "null"); } catch { /* Storage is optional. */ }
}
return saved && columns.some(column => column.key === saved.key && column.sortable !== false)
&& ["ascending", "descending"].includes(saved.direction)
? { key: saved.key, direction: saved.direction } : { key: "", direction: "ascending" };
}
function save(id, state) {
memory.set(id, { ...state });
try { environment.localStorage?.setItem(storageKey(id), JSON.stringify(state)); } catch { /* Retain the page-session preference. */ }
}
function value(raw, type) {
if (raw == null || String(raw).trim() === "") return null;
if (type === "number") return Number.isFinite(Number(raw)) ? Number(raw) : null;
if (type === "date") {
const timestamp = Date.parse(raw);
return Number.isFinite(timestamp) ? timestamp : null;
}
return String(raw);
}
function create(id, columns) {
let state;
const headers = new Map();
const get = () => state || (state = read(id, columns));
function toggle(key) {
if (!columns.some(column => column.key === key && column.sortable !== false)) return;
const previous = get();
state = { key, direction: previous.key === key && previous.direction === "ascending" ? "descending" : "ascending" };
save(id, state);
headers.forEach(update);
}
function select(items) {
const selected = get(), column = columns.find(column => column.key === selected.key);
if (!column) return [...items];
const direction = selected.direction === "descending" ? -1 : 1;
return items.map((item, index) => ({ item, index, value: value(column.value ? column.value(item) : item[column.key], column.type) }))
.sort((a, b) => {
if (a.value === null || b.value === null) return a.value === b.value ? a.index - b.index : a.value === null ? 1 : -1;
const result = typeof a.value === "number" ? a.value - b.value : a.value.localeCompare(b.value, undefined, { numeric: true, sensitivity: "base" });
return result * direction || a.index - b.index;
}).map(record => record.item);
}
function update({ th, button, key, label }) {
const selected = get();
const direction = selected.key === key ? selected.direction : "none";
th.setAttribute("aria-sort", direction);
button.setAttribute("data-sort-direction", direction);
button.setAttribute("aria-label", `Sort by ${label}${direction === "none" ? "" : `, ${direction}`}`);
}
function header(th, key, label, onChange, disabled = false) {
const button = environment.document.createElement("button");
button.type = "button";
button.className = "opportunities-extension-sort-button";
button.disabled = disabled;
button.title = `Sort by ${label}`;
button.setAttribute("data-sort-key", key);
const text = environment.document.createElement("span");
text.className = "opportunities-extension-sort-label";
text.textContent = label;
button.append(text);
const record = { th, button, key, label };
headers.set(key, record); update(record);
th.append(button);
button.addEventListener("click", () => { toggle(key); onChange?.(key); });
return button;
}
return { get, toggle, select, header };
}
return { read, save, create };
}