Commit Graph

11 Commits

Author SHA1 Message Date
damir dd2f7daaf8 CRISIS V3: definitive apiAuth + mobile hamburger + Playwright E2E test
apiAuth in app.html:
- Pre-checks JWT exp client-side BEFORE making request
- On expired: clears localStorage + redirects /login?reason=expired
- On 401 from server: clears + redirects /login?reason=unauthorized
- Single-flight redirect via window.__pgz_redirecting flag

login.html:
- Toast for ?reason=expired (red) / ?reason=unauthorized (orange)

app.html mobile:
- Hamburger button injected into topbar (.tb)
- Mobile CSS: sidebar slide-in -280→0, backdrop overlay, full-width drill-down
- toggleMobileSidebar() global function
- @media (max-width:768px) display:inline-flex, sidebar fixed pos

scripts/playwright_e2e.py:
- Desktop test (1280x800): login, JWT persist, profile, logo, logout
- Mobile test (375x812 iPhone X): viewport, login flow, hamburger, no h-scroll
- Output: _audit/playwright_<TS>/results.json + screenshots/*.png

Reproducible: TS=YYYYmmdd_HHMM python3 scripts/playwright_e2e.py
2026-05-05 09:21:39 +02:00
damir 8e136351f9 CRISIS FIX: login flow + mobile responsive + token expiry handling
ROOT CAUSE ISOLATED:
Backend POST /api/auth/login, GET/PUT /api/auth/me, POST avatar, POST /logout
all return 200 OK (verified curl). Damirov problem is browser-side:
stale localStorage tokens that don't match current backend → 401 cascade
→ avatar upload appears as 'failed: 401' → profile changes 'lost'.

FIXES:
1. apiAuth() in app.html now:
   - Pre-checks JWT exp claim before request
   - On 401 response: clears localStorage (pgz_access/refresh/user) +
     redirects to /login?reason=unauthorized
   - On JWT expired: redirects to /login?reason=expired

2. login.html displays toast for ?reason=expired/unauthorized

3. Mobile responsive CSS (max-width: 768px):
   - app.html: hamburger menu, sidebar slide-in, full-width drill-down panel
   - sport2.html: KPI grid 2-col, klubovi 1-col, tables horizontal scroll
   - Both: viewport meta + media queries + touch-friendly buttons

4. Mobile menu toggle button + backdrop overlay added

VERIFIED E2E (curl):
- POST /auth/login → 200 + JWT
- GET /auth/me → 200 + telefon persisted
- PUT /auth/me → 200, DB row updated
- POST /auth/me/avatar → 200, file saved + avatar_url returned
- POST /auth/logout → 200, token revoked (next /me returns 401)
2026-05-05 09:14:46 +02:00
damir c38f15a566 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
2026-05-05 02:24:30 +02:00
damir 67372d6c58 R7: GDPR /users/me/request-deletion alias + remove duplicate profileDeleteAccount
- auth/gdpr.py: dodan @me_router.post('/request-deletion') alias
  koji proxy-a na request_erasure (Art. 17). Koristi pravi EraseReq pydantic.
- static/app.html: obrisana placeholder profileDeleteAccount funkcija
  na liniji 944 (M10 mock alert) — sada samo real implementacija na 1902.
- E2E verified: damir@pgz.hr → POST /users/me/request-deletion → 200,
  DB row pgz_sport.gdpr_erasure_requests #1 pending.

Tag: P0-demo-fix
2026-05-05 02:06:34 +02:00
Damir Radulić 3a79965899 CC3 R3: Sectioned sidebar redesign (DABI-style) — PORTAL/OPERATIVA/CRM/ERP/ANALITIKA/ADMIN
Reference: app.rinet.one/klasik/dabi — uppercase section headers + grouped items.

Shared module rewrite:
- /static/shared/sidebar.css   v2.0
   * 6 named sections, 240px expanded / 58px collapsed
   * Active item: gold left-border + transparent gradient fill
   * Hover: blue left-border accent
   * Section header hidden in collapsed mode (replaced with dashed separator)
   * Tooltip on hover (data-label) when collapsed
   * Mobile <768px overlay with backdrop
- /static/shared/sidebar.js    v2.0
   * SIDEBAR_SECTIONS = [PORTAL, OPERATIVA, CRM, ERP, ANALITIKA, ADMIN]
   * ADMIN section hidden unless user_type ∈ {pgz_admin, super_admin} (gated by /api/auth/me)
   * Cross-portal links (↗ marker) for items that target a different page
   * Same-page items trigger hashchange instead of full reload
   * Footer = avatar + name + role + ▾ user menu (Profil / Postavke / Public portal / Prijava ↔ Odjava)
   * localStorage 'sidebarCollapsed' persists across all 8 pages

Page integration:
- sport2.html  ← native .sb hidden; data-active=dashboard; hashchange→navTo
- app.html     ← native .sb hidden; data-active=profil; hashchange→navTo
- admin.html   ← native .sidebar hidden; data-active=korisnici
- erp.html     ← native .sidebar hidden; data-active=racuni
- crm.html     ← data-active=clanarine
- audit.html   ← data-active=audit (existing)
- kpi.html     ← data-active=kpi (existing)
- login.html   ← data-active=login (no item match → no highlight; user menu shows Prijava)

Backups: _backups/*.cc3_pre_redesign.{TS}

Live verified: all 8 pages HTTP 200; shared sidebar.css 200 (8664 B); sidebar.js 200 (12678 B); 6 sections present.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 01:42:16 +02:00
Damir Radulić 7e674ad1ec CC5 R5 UI: Kalendar + Stats + Notifs + bulk akcije + XLSX export
app.html — Kalendar sekcija (NOVO za sve role):
- Mjesečni grid (pon-ned), klik na dan = prikaz svih eventa
- KPI: liječnički isteci, manifestacije, neprocitano InApp, ukupno eventa
- Eventi: liječnički termini + manifestacije iz API + ZZJZ termin slot mock
- Navigacija prev/next + month picker
- "Scan isteke → notifikacije" gumb (one-click backend POST)
- Lista nadolazećih (10) + lista InApp neprocitanih s mark-read

crm.html — 2 nova taba:
- 📊 Statistika: aktivni vs neaktivni, reprezentativci, kategorizirani,
  članarine summary, liječnički status, SVG bar chart trend uplata 12 mj,
  podjela po spolu/kategoriji, top 10 najnovijih uplata
- 🔔 Notifikacije: lista InApp+Email s filterima (channel/status), gumb
  za scan liječničkih (kreira 30/15/7 + expired bucket), mark-read pojedinačno
  i bulk, deep-link na /lijecnicki/{id}/zakazi i /clanarine/{id}/uplatnica.pdf
  iz meta polja

Bulk akcije za clanarine (R5 #3):
- Checkbox po retku + master + "Sve nepladene" gumb
- Bulk bar pokazuje selected count + total dug
- "Pošalji opomenu" → POST /bulk/notify (sa specifičnim ids ili sve dužnike)
- "Generiraj uplatnice" → POST /bulk/uplatnice → modal s linkovima na PDF/QR

XLSX export (R5 #4):
- "📥 Export XLSX" gumb na Članovi tab → otvara /clanovi/export.xlsx
  s trenutnim filterima (klub_id, q)
2026-05-05 01:36:45 +02:00
Damir Radulić 8dce58c5f9 CC3: Unified sidebar with external portal links + collapsible icon mode
Shared module:
- /static/shared/sidebar.css   ← unified CSS (#pgz-sb, .pgz-collapsed, mobile overlay, tooltip)
- /static/shared/sidebar.js    ← auto-mounting JS shell + PGZSidebar API
   * Auto-renders #pgz-sb na <body> start (data-inline=1 to opt out)
   * NAV_EXTERNAL: Prijava, Aplikacija, Administracija, CRM, ERP, KPI, Audit, Public portal
   * Toggle (≡) -> localStorage 'sidebarCollapsed' (perzistira preko SVIH stranica)
   * Mobile <768px: ≡ burger + ✕ close, body backdrop
   * Loads /api/auth/me u footer (avatar/username/uloga); ⎋ logout briše JWT i ide na /login
   * data-active="<key>" highlight aktivnog portala

Page integration:
- sport2.html  ← inline NAV_EXTERNAL u buildNav() + "Portali" separator (zadrži postojeći sidebar)
- app.html     ← inline NAV_EXTERNAL u buildNav() (zadrži role-based interni nav, dopuni Portalima)
- admin.html   ← Portali stavke u <aside class="sidebar"> (matching .nav-item style)
- erp.html     ← Portali stavke u <aside class="sidebar"> (matching .nav-item style)
- crm.html     ← include shared sidebar.css + sidebar.js  data-active="crm"
- audit.html   ← include shared sidebar.css + sidebar.js  data-active="audit"
- kpi.html     ← include shared sidebar.css + sidebar.js  data-active="kpi"
- login.html   ← include shared sidebar.css + sidebar.js  data-active="login"

Backups: _backups/{*.cc3_pre_unified_sidebar.*}

Live verified: 8 pages serve HTTP 200; sidebar.css/js HTTP 200; portal markers per page OK.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 01:11:24 +02:00
Damir Radulić bd3773434e CC2 R4 #6: real TOTP 2FA (setup + verify + disable + login flow)
- auth/auth_v2.py:
  - pyotp-based TOTP (RFC 6238, base32 secret, ±30s window)
  - new pgz_sport.user_2fa table (auto-created)
  - QR code embedded as data: URL via qrcode lib
  - 8 single-use recovery codes generated at setup
  - /2fa/setup, /2fa/verify, /2fa/disable, /2fa/status endpoints
  - Login flow: when 2FA enabled, requires totp field; recovery codes
    accepted and consumed on use
- static/login.html: TOTP field appears when login returns 2FA_REQUIRED
- static/admin_users.html: full 2FA panel in Sigurnost tab
  (status badge, QR + secret + recovery code display, verify input)

Live tests pass:
  T1 status (no setup) → enabled:false
  T2 setup → secret + 1.5KB QR PNG + 8 recovery codes
  T3 verify wrong code → 401
  T4 verify real TOTP → enabled:true
  T5 login w/o TOTP after enable → 401 detail=2FA_REQUIRED
  T6 login w/ TOTP → 200
2026-05-05 00:50:28 +02:00
Damir Radulić a0db65fc31 CC2 R4 #4: /api/users/me/gdpr-export alias
- New auth.gdpr.me_router prefix /api/users/me with:
  - GET/POST /gdpr-export → Art.20 JSON download with Content-Disposition
  - POST /gdpr-erase → Art.17 erasure request
  - GET /gdpr-consent → consent history for caller
- jsonable_encoder fixes datetime serialisation in JSONResponse
- admin_users.html: 'Izvezi moje podatke' now POSTs to alias and uses
  filename from Content-Disposition header
- 401 enforced on no-auth, 200 on valid Bearer (verified live)
2026-05-05 00:47:22 +02:00
Damir Radulić 47c366de7e CC5 R3 UI: link iz app.html sekcija na live /sport/crm workspace
Standalone /sport/crm stranica (static/crm.html) već je deployana s prethodnim
commit-om (CC2). Ovaj commit dodaje vidljivi link u 5 SECTIONS handlera u
app.html (pgz:crm, klub:clanarine, klub:lijecnicki, sportas:lijecnicki,
sportas:obrasci) tako da klikom na taj gumb korisnik dolazi do live tablica
(M7 + M8 + M9):

- Tablice s filterima (status / godina / klub / vrsta pregleda)
- Action gumbi: registriraj uplatu, generiraj HUB-3 PDF, EPC QR, zakaži pregled
  preko ZZJZ PGŽ (online ili e-mail fallback), popuni i potpiši obrazac
- Live PDF generator za uplatnice (HUB-3) i potpisane obrasce (sa SHA-256)

Mock SECTION sadržaj zadržan radi instant-pregleda u app.html;
puna funkcionalnost iza linka.

Live curl tests passed (5/5):
  ✓ /api/crm/clanarine + summary
  ✓ /api/crm/clanarine/{id}/uplatnica.pdf (52 KB %PDF)
  ✓ /api/crm/lijecnicki/uskoro-isticu (11 istekli)
  ✓ /api/crm/zzjz/info (live scrape; available=False, fallback=email)
  ✓ /api/crm/forms + draft + submit + sign + PDF (45 KB %PDF)
2026-05-05 00:23:34 +02:00
Damir Radulić 59a537388d CC3 R3 M3+M4: sport2 sidebar + app.html operativna aplikacija
M3 (sport2.html):
- ≡ toggle gumb u sidebar headeru, .sb.collapsed -> 58px (samo ikone)
- localStorage 'sidebar-state' (expanded|collapsed)
- restoreSidebar() pri DOMContentLoaded, prije buildNav
- Hover tooltip na collapsed nav itemima preko data-label

M4 (static/app.html — novi):
- 4 dashboard varijante po roli: PGŽ admin, Savez admin, Klub admin, Sportaš
- Role switch u topbar-u (demo) + localStorage 'app-role'
- Sidebar collapse (M3 logika), tooltip-ovi na collapsed
- Sidebar footer s avatar/username/role i Odjava (⎋) gumbom
- Klikabilni KPI/cards -> detail sub-stranice (savezi, klubovi, financije...)
- PGŽ: KPI + zahtjevi pending + audit log + Chart.js trend grafikon
- Savez: klubovi grid + zahtjevi PGŽ + lijecnicki uskoro istek + kalendar
- Klub: clanovi tablica + clanarine + lijecnicki + dokumenti + manifestacije + HUB-3 placeholder
- Sportaš: profile card + clanarina + lijecnicki + ZZJZ link + obrasci za potpis
- Iste CSS varijable kao sport2.html (PGŽ blue/gold dark theme)
- Real API: /sport/api/dashboard, /api/savezi, /api/klubovi, /api/clanovi, /api/proracun
- Mock fallback gdje API još ne postoji (M5/M7/M9 produkti)

Backups: static/sport2.html.bak.cc3.m3*, static/app.html.bak.cc3.m4*

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 00:16:29 +02:00