Fotos: Gesichtssuche FH-weit (Studierende + Lektoren) als Job mit Live-Progress, Person-Info-Seiten, Scope-Auswahl
This commit is contained in:
@@ -424,10 +424,64 @@ async function reloadVerbands() {
|
||||
} catch (e) { /* Keep static options */ }
|
||||
}
|
||||
|
||||
/* ================= Gesichtssuche: Job-Polling ================= */
|
||||
function initSearchJob() {
|
||||
const job = window.__SEARCH_JOB__;
|
||||
if (!job) return;
|
||||
const state = $("#search-state");
|
||||
const count = $("#search-count");
|
||||
const progress = $("#search-progress");
|
||||
const timeBadge = $("#search-time");
|
||||
const bar = $("#search-bar");
|
||||
const grid = $("#match-grid");
|
||||
const hint = $("#search-hint");
|
||||
const t0 = Date.now();
|
||||
|
||||
function tile(r) {
|
||||
const name = `${esc(r.nachname)}${r.vorname ? ", " + esc(r.vorname) : ""}`;
|
||||
const pct = Math.max(0, Math.round(r.sim * 100));
|
||||
const cls = r.sim >= 0.5 ? "match-strong" : (r.sim >= 0.35 ? "match-mid" : "");
|
||||
const badge = r.kind === "lektor" ? ` <span class="badge badge-muted">Lektor</span>` : "";
|
||||
return `<div class="match-tile ${cls}">
|
||||
<a href="/person/${encodeURIComponent(r.uid)}"><img src="${esc(r.img)}" alt="${name}"></a>
|
||||
<div class="match-name"><a href="/person/${encodeURIComponent(r.uid)}">${name}</a>${badge}</div>
|
||||
<div class="match-sim">
|
||||
<div class="sim-bar"><div style="width:${pct}%"></div></div>
|
||||
<span class="sim-num">${pct} %</span>
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
function poll() {
|
||||
fetch(`/api/suche/status/${job.id}`)
|
||||
.then(r => { if (!r.ok) throw new Error("status"); return r.json(); })
|
||||
.then(d => {
|
||||
if (d.total) bar.style.width = (d.done / d.total * 100).toFixed(1) + "%";
|
||||
progress.textContent = `${d.done}/${d.total}`;
|
||||
grid.innerHTML = (d.top || []).map(tile).join("");
|
||||
count.textContent = `${(d.top || []).filter(r => r.sim >= 0.35).length} Treffer`;
|
||||
if (d.status === "done") {
|
||||
clearInterval(timer);
|
||||
state.textContent = d.error ? "Suche fehlgeschlagen" : "Fertig";
|
||||
hint.style.display = "block";
|
||||
if (d.error) hint.innerHTML = `<span style="color:#b3261e">${esc(d.error)}</span>`;
|
||||
const secs = d.took ? `${d.took} s` : `${((Date.now() - t0) / 1000).toFixed(0)} s`;
|
||||
timeBadge.textContent = secs;
|
||||
timeBadge.style.display = "";
|
||||
}
|
||||
})
|
||||
.catch(() => { /* retry on next tick */ });
|
||||
}
|
||||
|
||||
const timer = setInterval(poll, 2000);
|
||||
poll();
|
||||
}
|
||||
|
||||
/* ================= boot ================= */
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
if (window.__PLAN_DATA__) initPlan();
|
||||
if (window.__STGS__) decodeMat();
|
||||
if (window.__SEARCH_JOB__) initSearchJob();
|
||||
});
|
||||
document.addEventListener("keydown", (e) => {
|
||||
if (e.key === "Escape") closeDetail();
|
||||
|
||||
@@ -215,6 +215,35 @@ table.data .num { text-align: right; font-variant-numeric: tabular-nums; }
|
||||
overflow: hidden; padding: 0;
|
||||
}
|
||||
.match-tile img { width: 100%; aspect-ratio: 1; object-fit: cover; display: block; background: var(--bg); }
|
||||
.match-tile a { display: block; color: inherit; }
|
||||
.search-progressbar {
|
||||
height: 8px; background: #eef2f7; border-radius: 6px; overflow: hidden;
|
||||
margin: 0 0 18px;
|
||||
}
|
||||
.search-progressbar > div {
|
||||
height: 100%; width: 0; background: var(--blue); border-radius: 6px;
|
||||
transition: width .5s ease;
|
||||
}
|
||||
|
||||
/* ---------- person ---------- */
|
||||
.person-card {
|
||||
display: flex; gap: 28px; background: var(--card); border: 1px solid var(--line);
|
||||
border-radius: var(--radius); box-shadow: var(--shadow); padding: 26px;
|
||||
align-items: flex-start;
|
||||
}
|
||||
.person-photo { flex: 0 0 220px; }
|
||||
.person-photo img, .person-photo .avatar-initials {
|
||||
width: 220px; aspect-ratio: 3 / 4; object-fit: cover; border-radius: var(--radius);
|
||||
border: 1px solid var(--line); background: var(--bg);
|
||||
}
|
||||
.person-name { margin: 0 0 10px; font-size: 26px; color: var(--ink); }
|
||||
.person-badges { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 16px; }
|
||||
.badge-green { background: #dcfce7; color: var(--green); }
|
||||
.person-kv { width: 100%; border-collapse: collapse; margin-bottom: 18px; }
|
||||
.person-kv td { padding: 7px 10px 7px 0; border-bottom: 1px solid var(--line); font-size: 14.5px; }
|
||||
.person-kv td:first-child { color: var(--muted); width: 180px; font-weight: 600; }
|
||||
.person-actions { display: flex; gap: 10px; flex-wrap: wrap; }
|
||||
.photo-name a { color: var(--ink); }
|
||||
.match-name { padding: 8px 10px 4px; font-size: 13.5px; font-weight: 600; }
|
||||
.match-name a { color: var(--ink); }
|
||||
.match-sim { padding: 0 10px 12px; display: flex; align-items: center; gap: 8px; }
|
||||
@@ -234,4 +263,8 @@ table.data .num { text-align: right; font-variant-numeric: tabular-nums; }
|
||||
.topbar nav { flex-wrap: wrap; justify-content: center; }
|
||||
.form-row { flex-direction: column; align-items: stretch; }
|
||||
.field input, .field select { min-width: 0; width: 100%; }
|
||||
.person-card { flex-direction: column; align-items: center; text-align: center; }
|
||||
.person-badges { justify-content: center; }
|
||||
.person-actions { justify-content: center; }
|
||||
.person-kv td:first-child { width: 130px; }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user