Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| cef4d2575b | |||
| fbbe953de3 |
+151
-15
@@ -357,34 +357,78 @@ async function apiPost(path, body){
|
||||
}
|
||||
}
|
||||
|
||||
// Cache the latest preview so /apply can pass back the same sources
|
||||
window._enrichPreviews = window._enrichPreviews || {};
|
||||
|
||||
async function enrichEntity(kind, id){
|
||||
const targetId = 'enrich-out-'+kind+'-'+id;
|
||||
const target = document.getElementById(targetId);
|
||||
if(target) target.innerHTML = '<div class="loading">⏳ Obogaćivanje u tijeku — pretraživanje izvora…</div>';
|
||||
const r = await apiPost('/v2/enrich/'+kind+'/'+id);
|
||||
if(!r){ if(target) target.innerHTML = '<div class="empty">Greška pri obogaćivanju</div>'; return; }
|
||||
window._enrichPreviews[kind+':'+id] = r;
|
||||
const cov = r.coverage||0;
|
||||
const covCls = cov>=70?'high':(cov>=40?'mid':'low');
|
||||
const proposed = r.proposed || {};
|
||||
const current = r.current || {};
|
||||
const propKeys = Object.keys(proposed);
|
||||
const lastEnr = r.last_enriched_at
|
||||
? `<span class="tb-s" title="${esc(r.last_enriched_at)}">✓ Obogaćeno ${esc(String(r.last_enriched_at).slice(0,10))}</span>`
|
||||
: '';
|
||||
|
||||
let diffHtml = '';
|
||||
if(propKeys.length){
|
||||
const rows = propKeys.map(k => {
|
||||
const cv = current[k]; const pv = proposed[k];
|
||||
const cvHtml = cv
|
||||
? '<span style="color:var(--t1)">'+esc(String(cv).slice(0,200))+'</span>'
|
||||
: '<span class="tag rd">prazno</span>';
|
||||
const pvHtml = '<span style="color:var(--ok)">'+esc(String(pv).slice(0,400))+'</span>';
|
||||
return `<tr>
|
||||
<td style="vertical-align:top;padding:6px 8px"><label style="display:flex;gap:6px;align-items:center;cursor:pointer">
|
||||
<input type="checkbox" data-field="${esc(k)}" checked style="width:16px;height:16px"> <b style="font-family:monospace;font-size:12px">${esc(k)}</b>
|
||||
</label></td>
|
||||
<td style="vertical-align:top;padding:6px 8px;font-size:12px;max-width:240px">${cvHtml}</td>
|
||||
<td style="vertical-align:top;padding:6px 8px;font-size:12px">${pvHtml}</td>
|
||||
</tr>`;
|
||||
}).join('');
|
||||
diffHtml = `
|
||||
<div style="margin:10px 0;border:1px solid var(--ln);border-radius:6px;overflow:hidden">
|
||||
<div style="padding:8px 10px;background:var(--bg3);font-size:11px;color:var(--t4);text-transform:uppercase;letter-spacing:.5px">📋 Predložene izmjene (uncheck za preskočiti)</div>
|
||||
<table style="width:100%;border-collapse:collapse;font-size:12px">
|
||||
<thead><tr style="background:var(--bg2)"><th style="text-align:left;padding:6px 8px;width:160px">Polje</th><th style="text-align:left;padding:6px 8px;width:240px">Trenutno</th><th style="text-align:left;padding:6px 8px">Predloženo</th></tr></thead>
|
||||
<tbody id="enrich-diff-${kind}-${id}">${rows}</tbody>
|
||||
</table>
|
||||
<div style="padding:8px 10px;background:var(--bg2);display:flex;gap:8px;justify-content:flex-end">
|
||||
<button class="btn" onclick="enrichSelectAll('${kind}',${id},true)">Označi sve</button>
|
||||
<button class="btn" onclick="enrichSelectAll('${kind}',${id},false)">Poništi sve</button>
|
||||
<button class="btn primary" onclick="enrichApply('${kind}',${id})">💾 Spremi izmjene</button>
|
||||
</div>
|
||||
</div>`;
|
||||
} else {
|
||||
diffHtml = '<div class="empty" style="padding:14px">Nema novih predloženih dopuna iz vanjskih izvora.</div>';
|
||||
}
|
||||
|
||||
const sourcesHtml = (r.sources||[]).map(s => `
|
||||
<div style="padding:8px 10px;background:var(--bg3);border-left:3px solid var(--pgz-gold);border-radius:4px;margin-bottom:6px">
|
||||
<div style="font-size:11px;color:var(--t4);text-transform:uppercase;letter-spacing:.5px">${esc(s.source||'')}</div>
|
||||
${s.title ? '<div style="font-weight:700;color:var(--t0);font-size:13px">'+esc(s.title)+'</div>' : ''}
|
||||
${s.extract ? '<div style="font-size:11.5px;color:var(--t1);line-height:1.5">'+esc(String(s.extract).slice(0,300))+'…</div>' : ''}
|
||||
${s.url ? '<div style="margin-top:4px"><a href="'+esc(s.url)+'" target="_blank" style="font-size:11px">↗ '+esc(String(s.url).slice(0,90))+'</a></div>' : ''}
|
||||
</div>`).join('');
|
||||
|
||||
const html = `
|
||||
<div style="display:flex;gap:8px;align-items:center;margin-bottom:10px">
|
||||
<div style="display:flex;gap:8px;align-items:center;flex-wrap:wrap;margin-bottom:10px">
|
||||
<span class="tag gr">🟢 OBOGAĆENO</span>
|
||||
<span class="score ${covCls}">Coverage ${cov}%</span>
|
||||
<span class="tb-s">${r.filled_fields}/${r.total_fields} polja popunjeno</span>
|
||||
${lastEnr}
|
||||
</div>
|
||||
${r.live_snippet && r.live_snippet.title ? `
|
||||
<div style="padding:10px;background:var(--bg3);border-left:3px solid var(--pgz-gold);border-radius:5px;margin-bottom:10px">
|
||||
<div style="font-size:11px;color:var(--t4);text-transform:uppercase;letter-spacing:.5px;margin-bottom:4px">📡 Live snippet</div>
|
||||
<div style="font-weight:700;color:var(--t0);font-size:13px;margin-bottom:4px">${esc(r.live_snippet.title)}</div>
|
||||
${r.live_snippet.description ? '<div style="font-size:11.5px;color:var(--t1);line-height:1.5">'+esc(r.live_snippet.description)+'</div>' : ''}
|
||||
<div style="margin-top:6px"><a href="${esc(r.live_snippet.url)}" target="_blank">↗ ${esc(r.live_snippet.url.slice(0,80))}</a></div>
|
||||
</div>
|
||||
` : ''}
|
||||
${r.missing_fields && r.missing_fields.length ? `
|
||||
<div style="margin-bottom:10px">
|
||||
<div style="font-size:11px;color:var(--t2);margin-bottom:4px">Nedostaje:</div>
|
||||
<div>${r.missing_fields.map(f=>'<span class="tag rd">'+esc(f)+'</span>').join('')}</div>
|
||||
</div>
|
||||
` : ''}
|
||||
${diffHtml}
|
||||
${sourcesHtml ? `<div style="margin:10px 0">
|
||||
<div style="font-size:11px;color:var(--t4);text-transform:uppercase;letter-spacing:.5px;margin-bottom:6px">🔗 Izvori</div>
|
||||
${sourcesHtml}
|
||||
</div>` : ''}
|
||||
<div>
|
||||
<div style="font-size:11px;color:var(--t2);margin-bottom:6px">🔍 Istraži dalje:</div>
|
||||
<div style="display:flex;flex-wrap:wrap;gap:6px">
|
||||
@@ -395,6 +439,54 @@ async function enrichEntity(kind, id){
|
||||
if(target) target.innerHTML = html;
|
||||
}
|
||||
|
||||
function enrichSelectAll(kind, id, on){
|
||||
const tbody = document.getElementById('enrich-diff-'+kind+'-'+id);
|
||||
if(!tbody) return;
|
||||
tbody.querySelectorAll('input[type=checkbox]').forEach(cb => { cb.checked = !!on; });
|
||||
}
|
||||
|
||||
async function enrichApply(kind, id){
|
||||
const target = document.getElementById('enrich-out-'+kind+'-'+id);
|
||||
const tbody = document.getElementById('enrich-diff-'+kind+'-'+id);
|
||||
const preview = (window._enrichPreviews||{})[kind+':'+id];
|
||||
if(!preview){ alert('Prvo pokreni "▶ Pokreni"'); return; }
|
||||
const proposed = preview.proposed || {};
|
||||
const fields = {};
|
||||
if(tbody){
|
||||
tbody.querySelectorAll('input[type=checkbox]:checked').forEach(cb => {
|
||||
const f = cb.getAttribute('data-field');
|
||||
if(f && proposed[f] !== undefined) fields[f] = proposed[f];
|
||||
});
|
||||
} else {
|
||||
Object.assign(fields, proposed);
|
||||
}
|
||||
if(!Object.keys(fields).length){ alert('Označi barem jedno polje za primjenu.'); return; }
|
||||
if(target) target.innerHTML = '<div class="loading">⏳ Spremam u bazu…</div>';
|
||||
try{
|
||||
const r = await fetch(API+'/v2/enrich/'+kind+'/'+id+'/apply', {
|
||||
method:'POST',
|
||||
headers:{'Content-Type':'application/json'},
|
||||
body: JSON.stringify({fields, sources: preview.sources || []}),
|
||||
});
|
||||
const data = await r.json();
|
||||
if(!r.ok){ throw new Error(data.detail || ('HTTP '+r.status)); }
|
||||
// Refresh the entire detail panel so the new values render
|
||||
if(kind === 'klub' && typeof openKlub === 'function') await openKlub(id);
|
||||
else if(kind === 'savez' && typeof openSavez === 'function') await openSavez(id);
|
||||
else if(kind === 'sportas' && typeof openSportas === 'function') await openSportas(id);
|
||||
setTimeout(() => enrichEntity(kind, id), 350);
|
||||
const cnt = Object.keys(data.applied||{}).length;
|
||||
const t = document.createElement('div');
|
||||
t.style.cssText = 'position:fixed;bottom:20px;right:20px;background:var(--ok,#1ec773);color:#0b1a16;padding:10px 16px;border-radius:6px;font-weight:700;z-index:9999;box-shadow:0 4px 16px rgba(0,0,0,.4)';
|
||||
t.textContent = '✓ Spremljeno '+cnt+' polja u bazu';
|
||||
document.body.appendChild(t);
|
||||
setTimeout(()=>t.remove(), 3500);
|
||||
}catch(e){
|
||||
console.error(e);
|
||||
if(target) target.innerHTML = '<div class="empty" style="color:var(--bad,#ff6b6b)">Greška pri spremanju: '+esc(e.message||String(e))+'</div>';
|
||||
}
|
||||
}
|
||||
|
||||
function enrichBlock(kind, id){
|
||||
return `
|
||||
<div class="card" id="enrich-card-${kind}-${id}">
|
||||
@@ -2417,10 +2509,54 @@ function renderCustomFindingPanel(c){
|
||||
<div class="card-h"><div class="card-t">📄 Dokumenti / dokazi</div></div>
|
||||
<div class="empty" style="padding:14px">Za ovaj manualni nalaz nisu priloženi PDF dokazi. Pokreni "Obogati podatke" za prikupljanje izvora.</div>
|
||||
</div>
|
||||
|
||||
${customFindingEnrichBlock(c.id, c.osoba || c.naslov)}
|
||||
`;
|
||||
openPanel('Forenzika · '+c.naslov, html);
|
||||
}
|
||||
|
||||
function customFindingEnrichBlock(customId, queryName){
|
||||
const safeId = String(customId).replace(/[^a-z0-9_-]/gi,'_');
|
||||
return `
|
||||
<div class="card" id="fenrich-card-${safeId}">
|
||||
<div class="card-h">
|
||||
<div class="card-t">✨ Obogati podatke (Wikipedia)</div>
|
||||
<button class="btn primary" onclick="enrichCustomFinding('${safeId}', '${esc(queryName).replace(/'/g,'\\\\'')}')">▶ Pokreni</button>
|
||||
</div>
|
||||
<div id="fenrich-out-${safeId}">
|
||||
<div class="empty" style="padding:14px">Lookup Wikipedia HR za "${esc(queryName)}" i prikaži dopune.</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
async function enrichCustomFinding(safeId, queryName){
|
||||
const out = document.getElementById('fenrich-out-'+safeId);
|
||||
if(out) out.innerHTML = '<div class="loading">Lookup Wikipedia HR…</div>';
|
||||
// Custom findings aren't in DB — call wiki lookup via a synthesised forensic_findings.id of -1 won't work.
|
||||
// Instead, use the existing /v2/enrich/sportas pattern to query Wikipedia by name.
|
||||
// We re-use the wiki summary via a mini fetch helper.
|
||||
try{
|
||||
const wiki = await fetch('https://hr.wikipedia.org/api/rest_v1/page/summary/'+encodeURIComponent(queryName.replace(/ /g,'_')))
|
||||
.then(r => r.ok ? r.json() : null).catch(()=>null);
|
||||
if(!wiki || wiki.type==='disambiguation' || !wiki.extract){
|
||||
if(out) out.innerHTML = '<div class="empty" style="padding:14px">Nije pronađen Wikipedia HR članak za <b>'+esc(queryName)+'</b>.</div>';
|
||||
return;
|
||||
}
|
||||
const w = {title: wiki.title, extract: wiki.extract, description: wiki.description, url: (wiki.content_urls||{}).desktop?.page};
|
||||
if(out) out.innerHTML = `
|
||||
<div style="margin-bottom:8px"><span class="tag gr">🟢 Wikipedia HR</span></div>
|
||||
<div style="padding:10px;background:var(--bg3);border-left:3px solid var(--pgz-gold);border-radius:5px">
|
||||
<div style="font-weight:700;color:var(--t0);font-size:14px;margin-bottom:6px">${esc(w.title||'')}</div>
|
||||
${w.description?'<div style="font-size:11px;color:var(--t2);margin-bottom:6px;font-style:italic">'+esc(w.description)+'</div>':''}
|
||||
${w.extract?'<div style="font-size:12px;line-height:1.6;color:var(--t1)">'+esc(w.extract)+'</div>':''}
|
||||
${w.url?'<div style="margin-top:8px"><a href="'+esc(w.url)+'" target="_blank">↗ Otvori članak</a></div>':''}
|
||||
</div>`;
|
||||
}catch(e){
|
||||
if(out) out.innerHTML = '<div class="empty" style="color:var(--red);padding:14px">Greška: '+esc(String(e))+'</div>';
|
||||
}
|
||||
}
|
||||
|
||||
function renderAlertPanel(a){
|
||||
const sevColor = a.razina==='CRITICAL'?'rd':(a.razina==='HIGH'?'am':'b');
|
||||
const html = `
|
||||
|
||||
Reference in New Issue
Block a user