mirror of
https://github.com/samsonjs/spirit-tracker.git
synced 2026-03-25 09:25:51 +00:00
219 lines
5.4 KiB
JavaScript
219 lines
5.4 KiB
JavaScript
// viz/app/pending.js
|
|
const LS_KEY = "stviz:v1:pendingSkuEdits";
|
|
const LS_SUBMITTED_KEY = "stviz:v1:submittedSkuEdits";
|
|
|
|
function safeParseJson(s) {
|
|
try {
|
|
return JSON.parse(String(s || ""));
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
function normSku(s) {
|
|
return String(s || "").trim();
|
|
}
|
|
|
|
function linkKey(fromSku, toSku) {
|
|
const f = normSku(fromSku);
|
|
const t = normSku(toSku);
|
|
if (!f || !t || f === t) return "";
|
|
return `${f}→${t}`;
|
|
}
|
|
|
|
function pairKey(a, b) {
|
|
const x = normSku(a);
|
|
const y = normSku(b);
|
|
if (!x || !y || x === y) return "";
|
|
return x < y ? `${x}|${y}` : `${y}|${x}`;
|
|
}
|
|
|
|
function loadEditsFromKey(key) {
|
|
const raw = (() => {
|
|
try {
|
|
return localStorage.getItem(key) || "";
|
|
} catch {
|
|
return "";
|
|
}
|
|
})();
|
|
|
|
const j = safeParseJson(raw);
|
|
const links = Array.isArray(j?.links) ? j.links : [];
|
|
const ignores = Array.isArray(j?.ignores) ? j.ignores : [];
|
|
|
|
return {
|
|
createdAt: String(j?.createdAt || ""),
|
|
links: links
|
|
.map((x) => ({ fromSku: normSku(x?.fromSku), toSku: normSku(x?.toSku) }))
|
|
.filter((x) => linkKey(x.fromSku, x.toSku)),
|
|
ignores: ignores
|
|
.map((x) => ({ skuA: normSku(x?.skuA || x?.a), skuB: normSku(x?.skuB || x?.b) }))
|
|
.filter((x) => pairKey(x.skuA, x.skuB)),
|
|
};
|
|
}
|
|
|
|
function saveEditsToKey(key, edits) {
|
|
const out = {
|
|
createdAt: edits?.createdAt || new Date().toISOString(),
|
|
links: Array.isArray(edits?.links) ? edits.links : [],
|
|
ignores: Array.isArray(edits?.ignores) ? edits.ignores : [],
|
|
};
|
|
try {
|
|
localStorage.setItem(key, JSON.stringify(out));
|
|
} catch {}
|
|
return out;
|
|
}
|
|
|
|
export function loadPendingEdits() {
|
|
return loadEditsFromKey(LS_KEY);
|
|
}
|
|
|
|
export function savePendingEdits(edits) {
|
|
return saveEditsToKey(LS_KEY, edits);
|
|
}
|
|
|
|
export function clearPendingEdits() {
|
|
try {
|
|
localStorage.removeItem(LS_KEY);
|
|
} catch {}
|
|
}
|
|
|
|
export function loadSubmittedEdits() {
|
|
return loadEditsFromKey(LS_SUBMITTED_KEY);
|
|
}
|
|
|
|
export function saveSubmittedEdits(edits) {
|
|
return saveEditsToKey(LS_SUBMITTED_KEY, edits);
|
|
}
|
|
|
|
export function clearSubmittedEdits() {
|
|
try {
|
|
localStorage.removeItem(LS_SUBMITTED_KEY);
|
|
} catch {}
|
|
}
|
|
|
|
export function pendingCounts() {
|
|
const e = loadPendingEdits();
|
|
return {
|
|
links: e.links.length,
|
|
ignores: e.ignores.length,
|
|
total: e.links.length + e.ignores.length,
|
|
};
|
|
}
|
|
|
|
export function addPendingLink(fromSku, toSku) {
|
|
const f = normSku(fromSku);
|
|
const t = normSku(toSku);
|
|
const k = linkKey(f, t);
|
|
if (!k) return false;
|
|
|
|
const pending = loadPendingEdits();
|
|
const submitted = loadSubmittedEdits();
|
|
|
|
const seen = new Set(
|
|
[
|
|
...pending.links.map((x) => linkKey(x.fromSku, x.toSku)),
|
|
...submitted.links.map((x) => linkKey(x.fromSku, x.toSku)),
|
|
].filter(Boolean),
|
|
);
|
|
|
|
if (seen.has(k)) return false;
|
|
|
|
pending.links.push({ fromSku: f, toSku: t });
|
|
savePendingEdits(pending);
|
|
return true;
|
|
}
|
|
|
|
export function addPendingIgnore(skuA, skuB) {
|
|
const a = normSku(skuA);
|
|
const b = normSku(skuB);
|
|
const k = pairKey(a, b);
|
|
if (!k) return false;
|
|
|
|
const pending = loadPendingEdits();
|
|
const submitted = loadSubmittedEdits();
|
|
|
|
const seen = new Set(
|
|
[
|
|
...pending.ignores.map((x) => pairKey(x.skuA, x.skuB)),
|
|
...submitted.ignores.map((x) => pairKey(x.skuA, x.skuB)),
|
|
].filter(Boolean),
|
|
);
|
|
|
|
if (seen.has(k)) return false;
|
|
|
|
pending.ignores.push({ skuA: a, skuB: b });
|
|
savePendingEdits(pending);
|
|
return true;
|
|
}
|
|
|
|
// Merge PENDING + SUBMITTED into a meta object {links, ignores}
|
|
export function applyPendingToMeta(meta) {
|
|
const base = {
|
|
generatedAt: String(meta?.generatedAt || ""),
|
|
links: Array.isArray(meta?.links) ? meta.links.slice() : [],
|
|
ignores: Array.isArray(meta?.ignores) ? meta.ignores.slice() : [],
|
|
};
|
|
|
|
const p0 = loadPendingEdits();
|
|
const p1 = loadSubmittedEdits();
|
|
const overlay = {
|
|
links: [...(p0.links || []), ...(p1.links || [])],
|
|
ignores: [...(p0.ignores || []), ...(p1.ignores || [])],
|
|
};
|
|
|
|
// merge links (dedupe by from→to)
|
|
const seenL = new Set(
|
|
base.links.map((x) => linkKey(String(x?.fromSku || "").trim(), String(x?.toSku || "").trim())).filter(Boolean),
|
|
);
|
|
for (const x of overlay.links) {
|
|
const k = linkKey(x.fromSku, x.toSku);
|
|
if (!k || seenL.has(k)) continue;
|
|
seenL.add(k);
|
|
base.links.push({ fromSku: x.fromSku, toSku: x.toSku });
|
|
}
|
|
|
|
// merge ignores (dedupe by canonical pair key)
|
|
const seenI = new Set(
|
|
base.ignores
|
|
.map((x) => pairKey(String(x?.skuA || x?.a || "").trim(), String(x?.skuB || x?.b || "").trim()))
|
|
.filter(Boolean),
|
|
);
|
|
for (const x of overlay.ignores) {
|
|
const k = pairKey(x.skuA, x.skuB);
|
|
if (!k || seenI.has(k)) continue;
|
|
seenI.add(k);
|
|
base.ignores.push({ skuA: x.skuA, skuB: x.skuB });
|
|
}
|
|
|
|
return base;
|
|
}
|
|
|
|
// Move everything from pending -> submitted, then clear pending.
|
|
// Returns the moved payload (what should be sent in PR/issue).
|
|
export function movePendingToSubmitted() {
|
|
const pending = loadPendingEdits();
|
|
if (!pending.links.length && !pending.ignores.length) return pending;
|
|
|
|
const sub = loadSubmittedEdits();
|
|
|
|
const seenL = new Set(sub.links.map((x) => linkKey(x.fromSku, x.toSku)).filter(Boolean));
|
|
for (const x of pending.links) {
|
|
const k = linkKey(x.fromSku, x.toSku);
|
|
if (!k || seenL.has(k)) continue;
|
|
seenL.add(k);
|
|
sub.links.push({ fromSku: x.fromSku, toSku: x.toSku });
|
|
}
|
|
|
|
const seenI = new Set(sub.ignores.map((x) => pairKey(x.skuA, x.skuB)).filter(Boolean));
|
|
for (const x of pending.ignores) {
|
|
const k = pairKey(x.skuA, x.skuB);
|
|
if (!k || seenI.has(k)) continue;
|
|
seenI.add(k);
|
|
sub.ignores.push({ skuA: x.skuA, skuB: x.skuB });
|
|
}
|
|
|
|
saveSubmittedEdits(sub);
|
|
clearPendingEdits();
|
|
return pending;
|
|
}
|