diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index faf63e3..fad3ef8 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -1,8 +1,7 @@ import React, { useEffect, useState } from "react"; const API = "/api"; -const CHIP_OPTIONS = ["AL", "JG", "JN", "SN", "TL"]; - +const CHIP_LIST = ["AL", "JG", "JN", "SN", "TL"]; async function api(path, opts = {}) { const res = await fetch(API + path, { @@ -14,59 +13,15 @@ async function api(path, opts = {}) { return res.json(); } -function parseTag(tag) { - // erlaubt: null | "i" | "m" | "s" | "s.AL" - if (!tag) return { base: null, chip: null }; - if (tag === "i") return { base: "i", chip: null }; - if (tag === "m") return { base: "m", chip: null }; - if (tag === "s") return { base: "s", chip: null }; - if (tag.startsWith("s.")) return { base: "s", chip: tag.slice(2) || null }; - return { base: tag, chip: null }; -} - -function baseTag(tag) { - // Backend: null | "i" | "m" | "s" - if (!tag) return null; - if (tag === "i" || tag === "m" || tag === "s") return tag; - return null; -} - -function nextBaseTag(tag) { - const b = baseTag(tag); - if (!b) return "i"; - if (b === "i") return "m"; - if (b === "m") return "s"; - return null; // s -> leer -} - -function chipStorageKey(gameId, entryId) { - return `chip:${gameId}:${entryId}`; -} - -function getChipLS(gameId, entryId) { - try { - return localStorage.getItem(chipStorageKey(gameId, entryId)); - } catch { - return null; - } -} - -function setChipLS(gameId, entryId, chip) { - try { - localStorage.setItem(chipStorageKey(gameId, entryId), chip); - } catch {} -} - -function clearChipLS(gameId, entryId) { - try { - localStorage.removeItem(chipStorageKey(gameId, entryId)); - } catch {} -} - +/** + * Tag-Rotation: + * null/"" -> i -> m -> s (öffnet Popup) -> (nach Auswahl) s.XX -> null + */ function cycleTag(tag) { if (!tag) return "i"; if (tag === "i") return "m"; if (tag === "m") return "s"; + if (tag === "s") return null; if (typeof tag === "string" && tag.startsWith("s.")) return null; return null; } @@ -214,6 +169,7 @@ export default function App() { const [sheet, setSheet] = useState(null); const [pulseId, setPulseId] = useState(null); + // ✅ Chip Modal State const [chipOpen, setChipOpen] = useState(false); const [chipEntry, setChipEntry] = useState(null); @@ -285,7 +241,7 @@ export default function App() { document.body.style.padding = "0"; }, []); - // Global CSS: Dark + Gold base + // Global CSS useEffect(() => { if (document.getElementById("hp-global-style")) return; const style = document.createElement("style"); @@ -307,8 +263,6 @@ export default function App() { } #root { background: transparent; } - - /* Safari/Chrome tap highlight reduzieren (kein hover/flash) */ * { -webkit-tap-highlight-color: transparent; } `; document.head.appendChild(style); @@ -386,15 +340,22 @@ export default function App() { setTimeout(() => setPulseId(null), 220); }; + /** + * Notiz-Button: + * - wenn m -> s: Popup öffnen und NICHT sofort ins Backend schreiben + * - wenn s.XX -> null: normal zurücksetzen + */ const toggleTag = async (entry) => { const next = cycleTag(entry.note_tag); + // Wenn der nächste Schritt "s" wäre -> Popup öffnen if (next === "s") { setChipEntry(entry); setChipOpen(true); return; } + // normal schreiben (i, m, oder null) await api(`/games/${gameId}/sheet/${entry.entry_id}`, { method: "PATCH", body: JSON.stringify({ note_tag: next }), @@ -402,11 +363,7 @@ export default function App() { await reloadSheet(); }; - const closeChipPick = () => { - setChipPickOpen(false); - setChipPickEntry(null); - }; - + // Chip auswählen -> note_tag wird s.XX const chooseChip = async (chip) => { if (!chipEntry) return; @@ -420,6 +377,7 @@ export default function App() { await reloadSheet(); }; + // X drücken: zurück auf — const closeChipModalToDash = async () => { if (chipEntry) { await api(`/games/${gameId}/sheet/${chipEntry.entry_id}`, { @@ -457,7 +415,11 @@ export default function App() { const getStatusBadge = (status) => { if (status === 2) return { color: "#baf3c9", background: "rgba(0,190,80,0.18)" }; if (status === 1) return { color: "#ffb3b3", background: "rgba(255,35,35,0.18)" }; - if (status === 3) return { color: "rgba(233,216,166,0.85)", background: "rgba(140,140,140,0.14)" }; + if (status === 3) + return { + color: "rgba(233,216,166,0.85)", + background: "rgba(140,140,140,0.14)", + }; return { color: "rgba(233,216,166,0.75)", background: "rgba(255,255,255,0.08)" }; }; @@ -546,9 +508,7 @@ export default function App() {
{me.email}
-
- {me.role} -
+
{me.role}
@@ -604,67 +564,107 @@ export default function App() {
- + -
Grün = bestätigt / fix richtig
+
+ Grün = bestätigt / fix richtig +
- + -
Rot = ausgeschlossen / fix falsch
+
+ Rot = ausgeschlossen / fix falsch +
- + ? -
Grau = unsicher / „vielleicht“
+
+ Grau = unsicher / „vielleicht“ +
- + -
Leer = unknown / noch nicht bewertet
+
+ Leer = unknown / noch nicht bewertet +
2) i / m / s Button (Notiz)
-
- Rechts pro Zeile gibt es einen Button, der durch diese Werte rotiert: -
+
Rechts pro Zeile gibt es einen Button, der rotiert:
i -
i = „Ich habe diese Geheimkarte“
+
+ i = „Ich habe diese Geheimkarte“ +
m -
m = „Geheimkarte aus dem mittleren Deck“
+
+ m = „Geheimkarte aus dem mittleren Deck“ +
s -
s = „Ein anderer Spieler hat diese Karte“
+
+ s = „Ein anderer Spieler hat diese Karte“ (Popup) +
-
= keine Notiz
+
+ = keine Notiz +
- Tipp: Jeder Spieler sieht nur seine eigenen Notizen – andere Spieler können nicht in deinen Zettel schauen. + Tipp: Jeder Spieler sieht nur seine eigenen Notizen – andere Spieler können nicht in deinen + Zettel schauen.
)} + {/* ✅ Chip Popup */} {chipOpen && (
e.stopPropagation()}> @@ -675,12 +675,10 @@ export default function App() {
-
- Chip auswählen: -
+
Chip auswählen:
-
- {CHIP_OPTIONS.map((c) => ( +
+ {CHIP_LIST.map((c) => ( @@ -694,7 +692,6 @@ export default function App() {
)} -
{sections.map((sec) => (
@@ -702,10 +699,12 @@ export default function App() {
{sec.entries.map((e) => { - // ✅ i oder s.XX => visuell wie rot (nur UI, kein Backend) - const isIorS = e.note_tag === "i" || (typeof e.note_tag === "string" && e.note_tag.startsWith("s.")); - const effectiveStatus = (e.status === 0 && isIorS) ? 1 : e.status; + // ✅ i oder s.XX => visuell wie rot (nur UI) + const isIorS = + e.note_tag === "i" || + (typeof e.note_tag === "string" && e.note_tag.startsWith("s.")); + const effectiveStatus = e.status === 0 && isIorS ? 1 : e.status; const badge = getStatusBadge(effectiveStatus); return ( @@ -717,17 +716,19 @@ export default function App() { background: getRowBg(effectiveStatus), animation: pulseId === e.entry_id ? "rowPulse 220ms ease-out" : "none", borderLeft: - effectiveStatus === 2 ? "4px solid rgba(0,190,80,0.55)" : - effectiveStatus === 1 ? "4px solid rgba(255,35,35,0.55)" : - effectiveStatus === 3 ? "4px solid rgba(233,216,166,0.22)" : - "4px solid rgba(0,0,0,0)", + effectiveStatus === 2 + ? "4px solid rgba(0,190,80,0.55)" + : effectiveStatus === 1 + ? "4px solid rgba(255,35,35,0.55)" + : effectiveStatus === 3 + ? "4px solid rgba(233,216,166,0.22)" + : "4px solid rgba(0,0,0,0)", }} >
cycleStatus(e)} style={{ ...styles.name, - // ✅ Rot-Look auch bei i/s textDecoration: effectiveStatus === 1 ? "line-through" : "none", color: getNameColor(effectiveStatus), opacity: effectiveStatus === 1 ? 0.8 : 1, @@ -743,7 +744,11 @@ export default function App() {
-
@@ -1196,17 +1201,16 @@ const styles = { backgroundSize: "cover", backgroundPosition: "center", backgroundRepeat: "no-repeat", - /* kleines Dark-Wash, damit Schwarz/Gold lesbar ist */ filter: "saturate(0.9) contrast(1.05) brightness(0.55)", }, + // Chip Buttons chipGrid: { marginTop: 12, display: "grid", gridTemplateColumns: "repeat(5, minmax(0, 1fr))", gap: 8, }, - chipBtn: { padding: "10px 14px", borderRadius: 12, @@ -1217,5 +1221,4 @@ const styles = { cursor: "pointer", minWidth: 64, }, - };