/* ========================================================================== DATENBANK: geschuetzte, rein lesende Katalogansicht. Die vier Kataloge bleiben fachlich getrennt und werden erst hier unter einem Premium-Zugang zusammengefuehrt. Uebungen kommen aus dem bestehenden ExerciseCatalog; die grossen Lebensmittel-, Evidenz- und Molekuellisten werden serverseitig gefiltert und in kleinen Fenstern nachgeladen. ========================================================================== */ const DB_BEREICHE = [ { id: "uebungen", icon: "◫", label: "Übungen", blurb: "Körperpartien, Geräte, Muskelziele und Anleitungen.", }, { id: "nahrungsmittel", icon: "▤", label: "Nahrungsmittel", blurb: "Makros und Energie pro 100 g aus der Lebensmittelbasis.", }, { id: "evidenz", icon: "⌁", label: "Evidenz", blurb: "Quellen und Einzelnachweise hinter den Wissensartikeln.", }, { id: "molekuele", icon: "◈", label: "Moleküle", blurb: "Substanzen, Metaboliten und Zielproteine aus der Molekülkarte.", }, ]; const DB_FOOD_GROUP_LABELS = { fleisch: "Fleisch", fisch: "Fisch", ei: "Eier", milch: "Milchprodukte", huelsenfrucht: "Hülsenfrüchte", getreide: "Getreide und Beilagen", gemuese: "Gemüse", obst: "Obst", nuss: "Nüsse und Samen", fett: "Fette und Öle", suess: "Süßes", sonstiges: "Sonstiges", markt: "Markt", }; const DB_MOLECULE_TYPES = [ ["substance", "Substanz"], ["metabolite", "Metabolit"], ["receptor", "Rezeptor"], ["enzyme", "Enzym"], ["transporter", "Transporter"], ["regulator", "Regulator"], ["endpoint", "Analyse-Endpunkt"], ]; function datenbankDarf(user) { return !!user && (user.plan === "premium" || user.role === "admin" || user.role === "mod"); } function datenbankBereich(value) { return DB_BEREICHE.some((bereich) => bereich.id === value) ? value : "uebungen"; } function datenbankHref(id) { return "/datenbank?bereich=" + encodeURIComponent(id); } function datenbankZahl(value, suffix = "") { if (value === null || value === undefined || value === "") return "–"; const number = Number(value); if (!Number.isFinite(number)) return "–"; const digits = Number.isInteger(number) ? 0 : 2; return new Intl.NumberFormat(BB.lang || "de", { maximumFractionDigits: digits, }).format(number) + suffix; } function DatenbankLanding({ user }) { return (
{BB.t("Premium-Datenbank")}

{BB.t("Die Datenbank hinter deinen Entscheidungen.")}

{BB.t("Übungen, Nahrungsmittel, Evidenz und Moleküle – zentral gesammelt, nachvollziehbar und nur lesend.")}

{BB.t("Zugriff auf die Datenbank gehört zur Premium-Mitgliedschaft.")} {BB.t("Alle Einträge bleiben unverändert. Du kannst sie durchsuchen und die Quellen einsehen.")}

{!user && {BB.t("Konto anlegen")}} {user && {BB.t("Premium entdecken")}} {!user && {BB.t("Schon dabei? Einloggen")}}
{BB.t("Lesender Zugriff")}

{BB.t("Vier Kataloge, ein Einstieg.")}

{DB_BEREICHE.map((bereich) => (

{BB.t(bereich.label)}

{BB.t(bereich.blurb)}

))}
); } function DatenbankNavigation({ active, overview }) { const counts = {}; (overview && overview.sections || []).forEach((item) => { counts[item.id] = item.count; }); return ( ); } function DatenbankFehler({ error, onRetry }) { return (
{BB.t("Die Datenbank konnte nicht geladen werden.")}

{error || BB.t("Das hat nicht geklappt.")}

{onRetry && }
); } function DatenbankExercises() { const [data, setData] = useState(null); const [error, setError] = useState(""); const [version, setVersion] = useState(0); useEffect(() => { let alive = true; setData(null); setError(""); BB.get("/datenbank/uebungen") .then((payload) => { if (alive) setData(payload); }) .catch((err) => { if (alive) setError(err.message || BB.t("Das hat nicht geklappt.")); }); return () => { alive = false; }; }, [version]); const labelFuer = useCallback((id) => { const muskel = (data && data.muskeln || []).find((item) => item.id === id); return muskel ? BB.t(muskel.label) : id; }, [data]); return (
{BB.t("Katalog")}

{BB.t("Übungen")}

{BB.t("Durchsuche Übungen nach Körperpartie und Gerät. Die Angaben stammen aus dem ExerciseDB-Katalog.")}

{BB.t("Nur lesen")}
{error && setVersion((n) => n + 1)} />} {!data && !error &&
} {data && (
)}
); } function DatenbankFood() { const [suche, setSuche] = useState(""); const [gruppe, setGruppe] = useState(""); const [items, setItems] = useState([]); const [gruppen, setGruppen] = useState([]); const [quelle, setQuelle] = useState(null); const [total, setTotal] = useState(0); const [loading, setLoading] = useState(true); const [loadingMore, setLoadingMore] = useState(false); const [error, setError] = useState(""); const request = useRef(0); useEffect(() => { const id = ++request.current; let alive = true; setLoading(true); setItems([]); setError(""); const timer = setTimeout(() => { BB.get("/datenbank/nahrungsmittel" + BB.qs({ q: suche.trim(), gruppe, land: BB.country, lang: BB.lang, limit: 60, offset: 0, })) .then((data) => { if (!alive || id !== request.current) return; setItems(data.items || []); setGruppen(data.gruppen || []); setQuelle(data.quelle || null); setTotal(data.total || 0); setLoading(false); }) .catch((err) => { if (!alive || id !== request.current) return; setError(err.message || BB.t("Das hat nicht geklappt.")); setLoading(false); }); }, 180); return () => { alive = false; clearTimeout(timer); }; }, [suche, gruppe]); function mehrLaden() { if (loadingMore || items.length >= total) return; setLoadingMore(true); BB.get("/datenbank/nahrungsmittel" + BB.qs({ q: suche.trim(), gruppe, land: BB.country, lang: BB.lang, limit: 60, offset: items.length, })) .then((data) => { setItems((old) => old.concat(data.items || [])); setTotal(data.total || 0); }) .catch((err) => setError(err.message || BB.t("Das hat nicht geklappt."))) .finally(() => setLoadingMore(false)); } return (
{BB.t("Katalog")}

{BB.t("Nahrungsmittel")}

{BB.t("Durchsuche die Lebensmittelbasis nach Namen, Gruppe und Nährwerten.")}

{BB.t("Nur lesen")}
setSuche(event.target.value)} placeholder={BB.t("Suche in der Datenbank …")} aria-label={BB.t("Nahrungsmittel suchen")} />
{BB.num(total)} {BB.t("Einträge")} {quelle && {BB.t("Quelle")}: {quelle.name}}
{error && } {loading ? : items.length === 0 ? ( ) : ( <>
{items.map((item) => (

{item.label}

{item.gruppe && {BB.t(DB_FOOD_GROUP_LABELS[item.gruppe] || item.gruppe)}}
{BB.t("Kalorien")}{datenbankZahl(item.kcal, " kcal")}
{BB.t("Eiweiß")}{datenbankZahl(item.protein, " g")}
{BB.t("Kohlenhydrate")}{datenbankZahl(item.kh, " g")}
{BB.t("Fett")}{datenbankZahl(item.fett, " g")}

{BB.t("Nährwerte je 100 g")} · {item.quelle || BB.t("Kuratierte Lebensmittelbasis")}

))}
{items.length < total && ( )} )}
); } function DatenbankEvidence() { const [suche, setSuche] = useState(""); const [items, setItems] = useState([]); const [total, setTotal] = useState(0); const [loading, setLoading] = useState(true); const [loadingMore, setLoadingMore] = useState(false); const [error, setError] = useState(""); const request = useRef(0); useEffect(() => { const id = ++request.current; let alive = true; setLoading(true); setItems([]); setError(""); const timer = setTimeout(() => { BB.get("/datenbank/evidenz" + BB.qs({ q: suche.trim(), limit: 50, offset: 0 })) .then((data) => { if (!alive || id !== request.current) return; setItems(data.items || []); setTotal(data.total || 0); setLoading(false); }) .catch((err) => { if (!alive || id !== request.current) return; setError(err.message || BB.t("Das hat nicht geklappt.")); setLoading(false); }); }, 180); return () => { alive = false; clearTimeout(timer); }; }, [suche]); function mehrLaden() { if (loadingMore || items.length >= total) return; setLoadingMore(true); BB.get("/datenbank/evidenz" + BB.qs({ q: suche.trim(), limit: 50, offset: items.length })) .then((data) => { setItems((old) => old.concat(data.items || [])); setTotal(data.total || 0); }) .catch((err) => setError(err.message || BB.t("Das hat nicht geklappt."))) .finally(() => setLoadingMore(false)); } return (
{BB.t("Freigegebene Evidenz")}

{BB.t("Evidenz")}

{BB.t("Finde die Einzelnachweise hinter den freigegebenen Wissensartikeln.")}

{BB.t("Nur lesen")}
setSuche(event.target.value)} placeholder={BB.t("Suche in der Datenbank …")} aria-label={BB.t("Evidenz suchen")} />
{BB.num(total)} {BB.t("Nachweis")}
{error && } {loading ? : items.length === 0 ? ( ) : ( <>
{items.map((item) => (
{item.kategorie || BB.t("Evidenz")} {BB.t("Referenz")} {item.nummer} {item.pmid && PMID {item.pmid}}

{item.referenz}

{BB.t("Zum Wissensartikel")} · {item.artikelTitel} {item.doi && DOI {item.doi}} {item.url && {BB.t("Quelle öffnen")} ↗}
))}
{items.length < total && ( )} )}
); } function DatenbankMolecules() { const [suche, setSuche] = useState(""); const [typ, setTyp] = useState(""); const [items, setItems] = useState([]); const [typen, setTypen] = useState(DB_MOLECULE_TYPES.map(([id, label]) => ({ id, label }))); const [total, setTotal] = useState(0); const [loading, setLoading] = useState(true); const [loadingMore, setLoadingMore] = useState(false); const [error, setError] = useState(""); const request = useRef(0); useEffect(() => { const id = ++request.current; let alive = true; setLoading(true); setItems([]); setError(""); const timer = setTimeout(() => { BB.get("/datenbank/molekuele" + BB.qs({ q: suche.trim(), typ, limit: 60, offset: 0 })) .then((data) => { if (!alive || id !== request.current) return; setItems(data.items || []); setTypen(data.typen || typen); setTotal(data.total || 0); setLoading(false); }) .catch((err) => { if (!alive || id !== request.current) return; setError(err.message || BB.t("Das hat nicht geklappt.")); setLoading(false); }); }, 180); return () => { alive = false; clearTimeout(timer); }; }, [suche, typ]); function mehrLaden() { if (loadingMore || items.length >= total) return; setLoadingMore(true); BB.get("/datenbank/molekuele" + BB.qs({ q: suche.trim(), typ, limit: 60, offset: items.length })) .then((data) => { setItems((old) => old.concat(data.items || [])); setTotal(data.total || 0); }) .catch((err) => setError(err.message || BB.t("Das hat nicht geklappt."))) .finally(() => setLoadingMore(false)); } return (
{BB.t("Molekülkarte")}

{BB.t("Moleküle")}

{BB.t("Durchsuche die vorhandenen Moleküle, Metaboliten und Zielproteine.")}

{BB.t("Nur lesen")}
setSuche(event.target.value)} placeholder={BB.t("Suche in der Datenbank …")} aria-label={BB.t("Moleküle suchen")} />
{BB.num(total)} {BB.t("Einträge")}
{error && } {loading ? : items.length === 0 ? ( ) : ( <>
{items.map((item) => (
{BB.t(item.typLabel || item.typ)} {item.seed && {BB.t("Aus der Wissensbasis")}}

{item.label}

{item.name && item.name !== item.label &&

{item.name}

}
{item.pubchemCid &&
{BB.t("PubChem CID")}
{item.pubchemCid}
} {item.uniprot &&
{BB.t("UniProt")}
{item.uniprot}
}
{item.artikelSlug && {BB.t("Zum Wissensartikel")} ↗}
))}
{items.length < total && ( )} )}
); } function DatenbankBereich({ id }) { if (id === "nahrungsmittel") return ; if (id === "evidenz") return ; if (id === "molekuele") return ; return ; } function DatenbankPage({ params, legacy = false }) { const { user, ready } = useAuth(); const requested = params && (params.get("bereich") || params.get("section")); const active = datenbankBereich(requested || (legacy ? "uebungen" : "uebungen")); const darf = datenbankDarf(user); const [overview, setOverview] = useState(null); const [error, setError] = useState(""); const [overviewVersion, setOverviewVersion] = useState(0); useEffect(() => { if (!darf) return undefined; let alive = true; setOverview(null); setError(""); BB.get("/datenbank") .then((data) => { if (alive) setOverview(data); }) .catch((err) => { if (alive) setError(err.message || BB.t("Das hat nicht geklappt.")); }); return () => { alive = false; }; }, [darf, overviewVersion]); if (!ready) return
; if (!darf) return ; return (
{BB.t("Premium-Datenbank")} · {BB.t("Nur lesen")}

{BB.t("Datenbank")}

{BB.t("Die Datenbank enthält die kuratierten Kataloge der Bodybuilding Bible.")}

{overview && {BB.num((overview.sections || []).reduce((sum, item) => sum + (item.count || 0), 0))} {BB.t("Einträge")}}
{error ? setOverviewVersion((n) => n + 1)} /> : }
); } Object.assign(window, { DatenbankPage, DatenbankLanding, DatenbankNavigation, });