R7+: 5x P0 demo fixes — HNS direct link, avatar cache, logo home, klub→sportaši, smarter enrichment

1) HNS direct link u research_links: za sportaš s profile_url/source_url
   (npr. https://semafor.hns.family/igraci/X/...) generira [DIRECT] link na vrhu liste,
   umjesto generic Google search. _research_links sada prima row dict.

2) Avatar cache buster: applyMeToHeader dodaje ?t=Date.now() na sve avatar img tagove.
   Avatar upload handler dodatno persistira novi avatar_url u localStorage.pgz_user
   tako da preživi page refresh + cross-page navigacije.

3) Logo home link: <div class='logo'> → <a href='/' class='logo'> u app.html i sport2.html.
   Klik na PGŽ SPORT logo vodi na public portal.

4) Klub → Sportaši drill-down: u klub Info tabu dodan button
   '👥 Vidi sportaše ovog kluba (N)' koji prebacuje na k-clan tab.
   Plus '🌐 Službena stranica' link kad klub ima web.

5) Smarter klub enrichment:
   - URL validacija (skip placeholder strings poput 'godisnjak_zspgz_2025')
   - Domain candidate guesser (slug → 16 candidate URLs s common HR TLD-ovima i sport prefix-ima)
   - Parallel HEAD probe (8 threads, 10s budget) — first 200 + name token match wins
   - Subpage scrape (/kontakt, /uprava, /o-nama, /o-klubu, /predsjednik) za richer evidence
   - HNK Orijent (id 3766) test: pogađa https://www.orijent.hr/, predlaže web+email+telefon+opis

E2E verified:
- 9/9 sidebar URL-ova → 200
- /users/me/gdpr-export → 200 (28KB JSON)
- /users/me/request-deletion → 200 (DB row pgz_sport.gdpr_erasure_requests)
- /enrich/klub/3766 → 4 proposed fields (web, email, telefon, opis)
- HNS sportaš research_links:  HNS profil DIRECT link na vrhu

Backend: routers/enrich_router.py
Frontend: static/app.html, static/sport2.html
Backups: _backups/sprint_1777940670/

Tag: R7-demo-ready
This commit is contained in:
2026-05-05 02:24:30 +02:00
parent 67372d6c58
commit c38f15a566
6 changed files with 6715 additions and 8 deletions
+13 -3
View File
@@ -265,7 +265,7 @@ table tbody tr:hover{background:var(--bg3)}
<div class="app">
<aside class="sb" id="sb">
<div class="sb-h">
<div class="logo">PGŽ <span class="g">SPORT</span></div>
<a href="/" class="logo" style="text-decoration:none;color:inherit;cursor:pointer" title="Početna"><span style="font-weight:800;letter-spacing:.5px">PGŽ</span> <span class="g">SPORT</span></a>
<div class="sub" id="role-sub">Operativna aplikacija</div>
<div class="sb-toggle" id="sb-toggle" onclick="toggleSidebar()" title="Skupi/raširi sidebar"></div>
</div>
@@ -449,7 +449,7 @@ function applyMeToHeader(){
$('#user-role-label')?.replaceChildren(document.createTextNode(roleLabel));
// Avatar topbar
if(me.avatar_url){
$('#user-av').innerHTML = `<img src="${esc(me.avatar_url)}" alt="">`;
$('#user-av').innerHTML = `<img src="${esc(me.avatar_url)}${me.avatar_url.includes('?')?'&':'?'}t=${Date.now()}" alt="">`;
} else if(me.google_picture){
$('#user-av').innerHTML = `<img src="${esc(me.google_picture)}" alt="">`;
} else {
@@ -459,7 +459,7 @@ function applyMeToHeader(){
if($('#sf-name')) $('#sf-name').textContent = name;
if($('#sf-role')) $('#sf-role').textContent = roleLabel;
if($('#sf-av')){
if(me.avatar_url) $('#sf-av').innerHTML = `<img src="${esc(me.avatar_url)}" alt="" style="width:100%;height:100%;object-fit:cover;border-radius:50%">`;
if(me.avatar_url) $('#sf-av').innerHTML = `<img src="${esc(me.avatar_url)}${me.avatar_url.includes('?')?'&':'?'}t=${Date.now()}" alt="" style="width:100%;height:100%;object-fit:cover;border-radius:50%">`;
else if(me.google_picture) $('#sf-av').innerHTML = `<img src="${esc(me.google_picture)}" alt="" style="width:100%;height:100%;object-fit:cover;border-radius:50%">`;
else $('#sf-av').textContent = initials(name);
}
@@ -856,6 +856,16 @@ async function onAvatarPick(input){
input.value = '';
if(r && r.avatar_url){
if(_state.me) _state.me.avatar_url = r.avatar_url;
// Update localStorage so other pages (sport2.html footer, sidebar) see new avatar
try{
const stored = localStorage.getItem('pgz_user') || sessionStorage.getItem('pgz_user');
if(stored){
const u = JSON.parse(stored);
u.avatar_url = r.avatar_url;
if(localStorage.getItem('pgz_user')) localStorage.setItem('pgz_user', JSON.stringify(u));
else sessionStorage.setItem('pgz_user', JSON.stringify(u));
}
}catch(e){console.warn('avatar storage update failed', e);}
applyMeToHeader();
loadSection(); // re-render profile
} else {