Add CPR cross-referencing (related documents)
Clicking any flagged card that contains CPR hits now shows a "Related documents" section in the preview panel, listing other items from the same scan session that share at least one CPR number. Items are ordered by number of shared CPRs; clicking any entry opens it in the preview panel. Works in both live mode and scan history mode. Implementation - GDPRDb.get_related_items() — SQL self-join on the existing cpr_index table using the same symmetric 300 s session window as get_session_items. No new data collection needed. - GET /api/db/related/<item_id>?ref=N — new endpoint in routes/database.py, consistent with the ?ref convention used by /api/db/flagged. - #previewRelated div injected between the metadata block and disposition row in the preview panel. - _loadRelated(f) in results.js fetches and renders the list; window._openRelated() resolves items from the live grid or falls back to the API response for history-mode items. Also - Added keyword/FTS5 search as a deferred idea in SUGGESTIONS.md - Updated CHANGELOG.md, README.md, and CLAUDE.md
This commit is contained in:
1 parent
8b55e9d933
commit
d84e57239a
8 files changed
+118
-1
No files matched your search
+40
-1
@@ -110,7 +110,8 @@ async function openPreview(f) {
|
||||
].filter(Boolean).join('');
|
||||
|
||||
_previewItemId = f.id;
|
||||
loadDisposition(f.id); // load disposition for this item (#6)
|
||||
loadDisposition(f.id);
|
||||
_loadRelated(f);
|
||||
|
||||
try {
|
||||
const r = await fetch('/api/preview/' + encodeURIComponent(f.id)
|
||||
@@ -176,6 +177,44 @@ async function openPreview(f) {
|
||||
}
|
||||
}
|
||||
|
||||
// ── Related documents (CPR cross-reference) ───────────────────────────────────
|
||||
|
||||
async function _loadRelated(f) {
|
||||
const el = document.getElementById('previewRelated');
|
||||
if (!el) return;
|
||||
if (!f.cpr_count) { el.style.display = 'none'; return; }
|
||||
|
||||
const ref = S._historyRefScanId ? `&ref=${S._historyRefScanId}` : '';
|
||||
try {
|
||||
const r = await fetch(`/api/db/related/${encodeURIComponent(f.id)}?${ref}`);
|
||||
const items = await r.json();
|
||||
if (f.id !== _previewItemId) return; // stale
|
||||
if (!items.length) { el.style.display = 'none'; return; }
|
||||
|
||||
const rows = items.map(item => {
|
||||
const shared = item.shared_cprs ?? '';
|
||||
const badge = shared ? `<span style="font-size:9px;padding:1px 5px;border-radius:10px;background:var(--danger);color:#fff;font-weight:500;flex-shrink:0">${shared} CPR</span>` : '';
|
||||
const src = item.source ? `<span style="color:var(--muted);font-size:10px;flex-shrink:0">${item.source}</span>` : '';
|
||||
return `<div onclick="window._openRelated('${item.id.replace(/'/g,"\\'")}',${JSON.stringify(item)})"
|
||||
style="display:flex;align-items:center;gap:6px;padding:4px 0;cursor:pointer;border-radius:4px"
|
||||
onmouseover="this.style.background='var(--surface)'" onmouseout="this.style.background=''">
|
||||
<span style="flex:1;font-size:11px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap" title="${item.name}">${item.name}</span>
|
||||
${src}${badge}
|
||||
</div>`;
|
||||
}).join('');
|
||||
|
||||
el.innerHTML = `<div style="font-size:10px;font-weight:600;color:var(--muted);margin-bottom:4px;text-transform:uppercase;letter-spacing:.04em">${t('m365_related_docs','Related documents')} <span style="font-weight:400">(${items.length})</span></div>${rows}`;
|
||||
el.style.display = 'block';
|
||||
} catch(e) {
|
||||
el.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
window._openRelated = function(id, itemData) {
|
||||
const cached = (S.flaggedData || []).find(x => x.id === id);
|
||||
openPreview(cached || itemData);
|
||||
};
|
||||
|
||||
// ── Retention policy (#1) ────────────────────────────────────────────────────
|
||||
|
||||
function toggleRetentionPanel() {
|
||||
|
||||
Reference in new issue
Block a user