From d2096eca70b825f9a6034d4e1061b7daeaa45894 Mon Sep 17 00:00:00 2001 From: ChK Date: Sun, 5 Apr 2026 07:51:37 +0200 Subject: [PATCH] CSV funktionert --- public/client.js | 91 +++++++++++++++++++++++++++++++++++++++++++++++ public/index.html | 8 ++++- public/styles.css | 36 +++++++++++++++++++ server/server.js | 23 +++++++++++- 4 files changed, 156 insertions(+), 2 deletions(-) diff --git a/public/client.js b/public/client.js index 39fb8da..1ddd5d5 100755 --- a/public/client.js +++ b/public/client.js @@ -84,16 +84,107 @@ function renderResult(result) { renderTree(treeEl, result, "result", true); } +async function fetchCSV() { + const res = await fetch("/api/latest-snapshot"); + if (!res.ok) throw new Error("Fehler beim Laden des Snapshots"); + + let data; + if (res.headers.get("content-type")?.includes("application/json")) { + data = await res.json(); + } else { + const csvData = await res.text(); + data = { + filename: "latest.csv", + mtime: new Date().toISOString(), + content: csvData + }; + } + + const lines = data.content.trim().split(/\r?\n/).filter(Boolean); + if (lines.length < 2) { + throw new Error("Keine oder unvollständige Daten"); + } + + const headers = lines[0].split(",").map(h => h.trim()); + + const rows = lines.slice(1).map(line => { + const cells = line.split(","); + const obj = {}; + headers.forEach((h, i) => { + const raw = (cells[i] ?? "").trim(); + const numeric = Number(raw); + obj[h] = raw !== "" && Number.isFinite(numeric) ? numeric : raw; + }); + return obj; + }); + + return { data, headers, rows }; +} + +async function renderSnapshot() { + const table = document.getElementById("snapshot-table"); + if (!table) return; + + try { + const { data, headers, rows } = await fetchCSV(); + + // Info anzeigen + const infoEl = document.getElementById("snapshot-info"); + if (infoEl) { + infoEl.textContent = `Datei: ${data.filename}, Geändert: ${new Date(data.mtime).toLocaleString()}, Zeilen: ${rows.length}`; + } + + // Tabelle leeren + table.innerHTML = ""; + + // Header + const thead = document.createElement("thead"); + const headerRow = document.createElement("tr"); + headers.forEach(h => { + const th = document.createElement("th"); + th.textContent = h; + headerRow.appendChild(th); + }); + thead.appendChild(headerRow); + table.appendChild(thead); + + // Body + const tbody = document.createElement("tbody"); + rows.forEach(row => { + const tr = document.createElement("tr"); + headers.forEach(h => { + const td = document.createElement("td"); + let value = row[h]; + if (typeof value === 'number') { + if (h === 'id' || h === 'seen_by') { + value = Math.round(value); + } else { + value = value.toFixed(1); + } + } + td.textContent = value; + tr.appendChild(td); + }); + tbody.appendChild(tr); + }); + table.appendChild(tbody); + } catch (err) { + console.error("Fehler beim Rendern des Snapshots:", err); + } +} + async function onCalculateClick() { clearTextarea("analysis-log"); clearTextarea("result-json"); clearElement("result-tree"); + clearElement("snapshot-table"); appendLog("Starte Berechnung..."); try { const result = await window.calculate(); renderResult(result); + await renderSnapshot(); appendLog("Result angezeigt."); } catch (err) { appendLog(`Fehler: ${err.message}`); diff --git a/public/index.html b/public/index.html index b7d76d1..bfb004c 100755 --- a/public/index.html +++ b/public/index.html @@ -74,7 +74,13 @@
-

Neuester Snapshot

+

Snapshot CSV

+
+
+
+ +
+

Snapshot Image

diff --git a/public/styles.css b/public/styles.css index 4a29131..9deeff5 100755 --- a/public/styles.css +++ b/public/styles.css @@ -175,4 +175,40 @@ textarea { #result-tree .tree-leaf { margin-left: 18px; white-space: pre-wrap; +} + +/* ===== SNAPSHOT TABLE ===== */ + +#snapshot-table { + width: 100%; + border-collapse: collapse; + margin-top: 12px; + font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; + font-size: 12px; + line-height: 1.2; +} + +#snapshot-table th, +#snapshot-table td { + padding: 4px 8px; + text-align: left; +} + +#snapshot-table th { + background: #1e293b; + color: var(--accent); + font-weight: bold; + border-bottom: 1px solid #334155; +} + +#snapshot-table td { + border: none; +} + +#snapshot-table tbody tr:nth-child(even) { + background: #0f172a; +} + +#snapshot-table tbody tr:hover { + background: #1e293b; } \ No newline at end of file diff --git a/server/server.js b/server/server.js index 5711734..7f701e0 100755 --- a/server/server.js +++ b/server/server.js @@ -185,6 +185,10 @@ app.get('/api/events', (req, res) => { }); }); + +//snapshot_video0_1775319258906_two_cam.csv +//snapshot_video0_1775319258906_two_cam_annotated.jpg + // Neuester Snapshot-Endpunkt app.get('/api/latest-snapshot', (req, res) => { const snapshotsDir = path.join(path.resolve('public'), 'snapshots'); @@ -197,18 +201,35 @@ app.get('/api/latest-snapshot', (req, res) => { path: path.join(snapshotsDir, file), mtime: fs.statSync(path.join(snapshotsDir, file)).mtime })).sort((a, b) => b.mtime - a.mtime); + if (csvFiles.length === 0) { return res.status(404).json({ error: 'Keine CSV-Dateien gefunden' }); } const latestFile = csvFiles[0]; + const baseName = path.basename(latestFile.name, path.extname(latestFile.name)); + const imageFilename = `${baseName}_annotated.jpg`; + const imagePath = path.join(snapshotsDir, imageFilename); + fs.readFile(latestFile.path, 'utf8', (err, data) => { if (err) { return res.status(500).json({ error: 'Fehler beim Lesen der Datei' }); } - res.json({ + + const response = { filename: latestFile.name, mtime: latestFile.mtime.toISOString(), content: data + }; + + fs.readFile(imagePath, { encoding: 'base64' }, (jpgErr, jpgBase64) => { + if (!jpgErr && jpgBase64) { + response.imageFile = { + filename: imageFilename, + mimeType: 'image/jpeg', + contentBase64: jpgBase64 + }; + } + res.json(response); }); }); });