Refactor chip selection logic and UI improvements

Unified chip modal functionality by consolidating state variables and methods. Enhanced UI consistency for effective statuses and streamlined code for readability and maintainability. Updated styles for better visual appeal and clarity.
This commit is contained in:
2026-02-03 15:35:55 +01:00
parent b8fc47e881
commit d2e2286627

View File

@@ -3,6 +3,9 @@ import React, { useEffect, useState } from "react";
const API = "/api"; const API = "/api";
const CHIP_LIST = ["AL", "JG", "JN", "SN", "TL"]; const CHIP_LIST = ["AL", "JG", "JN", "SN", "TL"];
const [chipOpen, setChipOpen] = useState(false);
const [chipEntry, setChipEntry] = useState(null);
async function api(path, opts = {}) { async function api(path, opts = {}) {
const res = await fetch(API + path, { const res = await fetch(API + path, {
credentials: "include", credentials: "include",
@@ -378,25 +381,18 @@ export default function App() {
}; };
const toggleTag = async (entry) => { const toggleTag = async (entry) => {
const next = nextBaseTag(entry.note_tag); const next = cycleTag(entry.note_tag);
// Wenn wir bei "s" angekommen sind -> Popup öffnen
if (next === "s") { if (next === "s") {
setChipPickEntry(entry); setChipEntry(entry);
setChipPickOpen(true); setChipOpen(true);
return; return;
} }
// Wenn wir von "s" weg gehen -> Chip local löschen
if (baseTag(entry.note_tag) === "s" && next === null) {
clearChipLS(gameId, entry.entry_id);
}
await api(`/games/${gameId}/sheet/${entry.entry_id}`, { await api(`/games/${gameId}/sheet/${entry.entry_id}`, {
method: "PATCH", method: "PATCH",
body: JSON.stringify({ note_tag: next }), body: JSON.stringify({ note_tag: next }),
}); });
await reloadSheet(); await reloadSheet();
}; };
@@ -406,21 +402,30 @@ export default function App() {
}; };
const chooseChip = async (chip) => { const chooseChip = async (chip) => {
if (!chipPickEntry) return; if (!chipEntry) return;
// Chip lokal speichern await api(`/games/${gameId}/sheet/${chipEntry.entry_id}`, {
setChipLS(gameId, chipPickEntry.entry_id, chip);
// Backend nur "s" setzen
await api(`/games/${gameId}/sheet/${chipPickEntry.entry_id}`, {
method: "PATCH", method: "PATCH",
body: JSON.stringify({ note_tag: "s" }), body: JSON.stringify({ note_tag: `s.${chip}` }),
}); });
closeChipPick(); setChipOpen(false);
setChipEntry(null);
await reloadSheet(); await reloadSheet();
}; };
const closeChipModalToDash = async () => {
if (chipEntry) {
await api(`/games/${gameId}/sheet/${chipEntry.entry_id}`, {
method: "PATCH",
body: JSON.stringify({ note_tag: null }),
});
await reloadSheet();
}
setChipOpen(false);
setChipEntry(null);
};
// --- helpers --- // --- helpers ---
const getRowBg = (status) => { const getRowBg = (status) => {
if (status === 1) return "rgba(255, 35, 35, 0.16)"; if (status === 1) return "rgba(255, 35, 35, 0.16)";
@@ -654,41 +659,30 @@ export default function App() {
</div> </div>
)} )}
{chipPickOpen && ( {chipOpen && (
<div style={styles.modalOverlay} onMouseDown={closeChipPick}> <div style={styles.modalOverlay} onMouseDown={closeChipModalToDash}>
<div style={styles.modalCard} onMouseDown={(e) => e.stopPropagation()}> <div style={styles.modalCard} onMouseDown={(e) => e.stopPropagation()}>
<div style={styles.modalHeader}> <div style={styles.modalHeader}>
<div style={{ fontWeight: 1000, color: stylesTokens.textGold }}> <div style={{ fontWeight: 1000, color: stylesTokens.textGold }}>Wer hat die Karte?</div>
Wer hat die Karte? <button onClick={closeChipModalToDash} style={styles.modalCloseBtn} aria-label="Schließen">
</div>
<button
onClick={closeChipPick}
style={styles.modalCloseBtn}
aria-label="Schließen"
>
</button> </button>
</div> </div>
<div style={{ marginTop: 12, color: stylesTokens.textMain, opacity: 0.9 }}> <div style={{ marginTop: 12, color: stylesTokens.textMain }}>
Chip auswählen: Chip auswählen:
</div> </div>
<div style={styles.chipGrid}> <div style={{ marginTop: 10, display: "flex", gap: 10, flexWrap: "wrap" }}>
{CHIP_LIST.map((c) => ( {CHIP_OPTIONS.map((c) => (
<button <button key={c} onClick={() => chooseChip(c)} style={styles.chipBtn}>
key={c}
onClick={() => chooseChip(c)}
style={styles.chipBtn}
title={`Setze s.${c}`}
>
{c} {c}
</button> </button>
))} ))}
</div> </div>
<div style={{ marginTop: 10, fontSize: 12, color: stylesTokens.textDim }}> <div style={{ marginTop: 12, fontSize: 12, color: stylesTokens.textDim }}>
Tipp: Wenn du wieder auf den Notiz-Button klickst, gehts von <b>s.XX</b> zurück auf <b></b>. Tipp: Wenn du wieder auf den Notiz-Button klickst, gehts von <b>s.XX</b> zurück auf .
</div> </div>
</div> </div>
</div> </div>
@@ -702,19 +696,24 @@ export default function App() {
<div style={{ display: "grid" }}> <div style={{ display: "grid" }}>
{sec.entries.map((e) => { {sec.entries.map((e) => {
const badge = getStatusBadge(e.status); // ✅ 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;
const badge = getStatusBadge(effectiveStatus);
return ( return (
<div <div
key={e.entry_id} key={e.entry_id}
className="hp-row" className="hp-row"
style={{ style={{
...styles.row, ...styles.row,
background: getRowBg(e.status), background: getRowBg(effectiveStatus),
animation: pulseId === e.entry_id ? "rowPulse 220ms ease-out" : "none", animation: pulseId === e.entry_id ? "rowPulse 220ms ease-out" : "none",
borderLeft: borderLeft:
e.status === 2 ? "4px solid rgba(0,190,80,0.55)" : effectiveStatus === 2 ? "4px solid rgba(0,190,80,0.55)" :
e.status === 1 ? "4px solid rgba(255,35,35,0.55)" : effectiveStatus === 1 ? "4px solid rgba(255,35,35,0.55)" :
e.status === 3 ? "4px solid rgba(233,216,166,0.22)" : effectiveStatus === 3 ? "4px solid rgba(233,216,166,0.22)" :
"4px solid rgba(0,0,0,0)", "4px solid rgba(0,0,0,0)",
}} }}
> >
@@ -722,41 +721,24 @@ export default function App() {
onClick={() => cycleStatus(e)} onClick={() => cycleStatus(e)}
style={{ style={{
...styles.name, ...styles.name,
// ✅ Rot-Look auch bei i/s
// ✅ Karte durchstreichen wenn Rot ODER Tag i/s gesetzt ist textDecoration: effectiveStatus === 1 ? "line-through" : "none",
textDecoration: color: getNameColor(effectiveStatus),
e.status === 1 || opacity: effectiveStatus === 1 ? 0.8 : 1,
e.note_tag === "i" ||
e.note_tag === "s"
? "line-through"
: "none",
opacity:
e.status === 1 ||
e.note_tag === "i" ||
e.note_tag === "s"
? 0.65
: 1,
color: getNameColor(e.status),
}} }}
title="Klick: Grün → Rot → Grau → Leer"
> >
{e.label} {e.label}
</div> </div>
<div style={styles.statusCell}> <div style={styles.statusCell}>
<span style={{ ...styles.statusBadge, color: badge.color, background: badge.background }}> <span style={{ ...styles.statusBadge, color: badge.color, background: badge.background }}>
{getStatusSymbol(e.status)} {getStatusSymbol(effectiveStatus)}
</span> </span>
</div> </div>
<button onClick={() => toggleTag(e)} style={styles.tagBtn} title="i → m → s → leer"> <button onClick={() => toggleTag(e)} style={styles.tagBtn} title="i → m → s → leer">
{(() => { {e.note_tag || "—"}
if (!e.note_tag) return "—";
if (e.note_tag !== "s") return e.note_tag;
const chip = getChipLS(gameId, e.entry_id);
return chip ? `s.${chip}` : "s";
})()}
</button> </button>
</div> </div>
); );
@@ -1220,14 +1202,14 @@ const styles = {
}, },
chipBtn: { chipBtn: {
padding: "10px 0", padding: "10px 14px",
borderRadius: 12, borderRadius: 12,
border: `1px solid rgba(233,216,166,0.22)`, border: "1px solid rgba(233,216,166,0.18)",
background: "rgba(255,255,255,0.06)", background: "rgba(255,255,255,0.06)",
color: stylesTokens.textGold, color: stylesTokens.textGold,
fontWeight: 1100, fontWeight: 1000,
cursor: "pointer", cursor: "pointer",
boxShadow: "inset 0 1px 0 rgba(255,255,255,0.06)", minWidth: 64,
}, },
}; };