HELP-1: in-app German user guide (/hilfe, accordion, 9 sections)
HilfePage.tsx: accordion via <details>/<summary> (native, accessible, mobile-first); 9 sections in du-Form: Erste Schritte, Rennmaeuse, Becken, Wuerfe, Genetik, Stammbaum, Abgabe-Ablauf, Statistik, Datensicherung. All UI labels imported from de.ts (rename-safe); SCHECKE/Rex warnings verbatim from genetics.warnings. de.ts: de.nav.help + de.hilfe (title, subtitle, 9 section keys) AppShell.tsx: Hilfe (heart question mark) added to SECONDARY nav (Mehr-sheet on phone, sidebar on desktop) App.tsx: Route path=hilfe -> <HilfePage /> hilfe.css: accordion, hint boxes, warning colour; piggybacks on existing CSS vars e2e/hilfe.spec.ts: 3 smoke tests x 2 viewports = 6 new tests -- Hilfe reachable via nav -- First accordion section expands on click -- SCHECKE warning visible in Genetik section Full evidence: dotnet test 18/18, npm test 47/47, npm run build ok, npm run e2e 54/54 (48 existing + 6 new hilfe tests). Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
196
gerbil-manager-web/src/pages/HilfePage.tsx
Normal file
196
gerbil-manager-web/src/pages/HilfePage.tsx
Normal file
@@ -0,0 +1,196 @@
|
||||
/**
|
||||
* HELP-1: In-App-Anleitung für die Züchterin (nicht-technisch, du-Form).
|
||||
* Accordion via <details>/<summary> — kein JS-State nötig, barrierefrei, mobiltauglich.
|
||||
* Texte referenzieren nur Bezeichnungen aus de.ts (bleiben bei Umbenennung korrekt).
|
||||
*/
|
||||
import { de } from '../strings/de'
|
||||
import './hilfe.css'
|
||||
|
||||
const t = de.hilfe
|
||||
const nav = de.nav
|
||||
const g = de.pages.gerbils
|
||||
const l = de.pages.litters
|
||||
const b = de.pages.becken
|
||||
const st = de.pages.stammbaum
|
||||
const ge = de.pages.genetik
|
||||
const ab = de.pages.abgabe
|
||||
const stat = de.pages.statistik
|
||||
const scheckeWarning = de.genetics.warnings.SCHECKE_LETHAL
|
||||
const rexWarning = de.genetics.warnings.REX_SEMI_LETHAL
|
||||
|
||||
interface Section {
|
||||
id: string
|
||||
title: string
|
||||
content: React.ReactNode
|
||||
}
|
||||
|
||||
const sections: Section[] = [
|
||||
{
|
||||
id: 'erste-schritte',
|
||||
title: t.sections.ersteSchritte,
|
||||
content: (
|
||||
<>
|
||||
<p>Willkommen im <strong>{de.app.title}</strong>! Diese Anleitung führt dich durch die wichtigsten Bereiche.</p>
|
||||
<p>Die Navigation erreichst du unten (Smartphone) oder links (Laptop). Mit <strong>„{nav.more}"</strong> öffnest du alle weiteren Bereiche.</p>
|
||||
<p>Empfohlene Reihenfolge beim ersten Mal:</p>
|
||||
<ol>
|
||||
<li>Becken anlegen (wo leben deine Tiere?)</li>
|
||||
<li>Rennmäuse anlegen mit Foto</li>
|
||||
<li>Würfe eintragen</li>
|
||||
<li>Genetik erkunden</li>
|
||||
</ol>
|
||||
<p className="hilfe-hint">Tipp: Lege zuerst die Becken an — dann kannst du beim Erfassen der Tiere direkt das richtige Becken auswählen.</p>
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'tiere',
|
||||
title: t.sections.tiere,
|
||||
content: (
|
||||
<>
|
||||
<p>Unter <strong>„{nav.gerbils}"</strong> siehst du alle deine Rennmäuse. Mit <strong>„{g.newButton}"</strong> legst du ein neues Tier an.</p>
|
||||
<p>Pflichtfelder sind <strong>„{g.fields.name}"</strong> und <strong>„{g.fields.gender}"</strong>. Optional kannst du Geburtsdatum, Farbschlag, Genotyp, Becken und Herkunft eintragen.</p>
|
||||
<p>Auf der Detailseite findest du drei Tabs:</p>
|
||||
<ul>
|
||||
<li><strong>„{g.detail.tabs.photos}"</strong> — Fotos hochladen; das erste Foto wird als Profilfoto angezeigt.</li>
|
||||
<li><strong>„{g.detail.tabs.health}"</strong> — Tierarztbesuche, Behandlungen und Untersuchungen eintragen.</li>
|
||||
<li><strong>„{g.detail.tabs.weight}"</strong> — Gewicht in Gramm erfassen; nach zwei Einträgen erscheint eine Verlaufskurve.</li>
|
||||
</ul>
|
||||
<p>Mit den Filtern oben in der Liste kannst du nach Status, Geschlecht und Farbschlag filtern oder nach Name suchen.</p>
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'becken',
|
||||
title: t.sections.becken,
|
||||
content: (
|
||||
<>
|
||||
<p>Unter <strong>„{nav.enclosures}"</strong> verwaltest du deine Gehege. Mit <strong>„{b.newButton}"</strong> legst du ein neues Becken an (nur Name ist Pflicht).</p>
|
||||
<p>Die Detailseite zeigt dir die aktuellen <strong>„{b.occupancy.title}"</strong> — mit Links direkt zur Tierakte. So siehst du auf einen Blick, wer wo lebt.</p>
|
||||
<p>Ein Becken lässt sich nur löschen, wenn es leer ist — das schützt vor versehentlichem Datenverlust.</p>
|
||||
<p>Unter <strong>„{nav.contacts}"</strong> kannst du Züchter, Käufer und andere Kontakte anlegen. Diese lassen sich dann als Herkunft oder Abnehmer bei Tieren eintragen.</p>
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'wuerfe',
|
||||
title: t.sections.wuerfe,
|
||||
content: (
|
||||
<>
|
||||
<p>Unter <strong>„{nav.litters}"</strong> erfasst du Würfe. Mit <strong>„{l.newButton}"</strong> trägst du Vater, Mutter, Wurfdatum und Wurfstärke ein.</p>
|
||||
<p>Das <strong>„{l.fields.expectedGoHomeDate}"</strong> wird automatisch auf 35 Tage nach dem Wurfdatum vorgeschlagen — du kannst es anpassen.</p>
|
||||
<p>Auf der Wurf-Detailseite siehst du unter <strong>„{l.detail.registered}"</strong> die bereits erfassten Jungtiere. Mit <strong>„{l.detail.registerJuvenile}"</strong> legst du ein neues Tier an, das direkt mit diesem Wurf verknüpft ist.</p>
|
||||
<p>Im Tab <strong>„{l.tabs.pairs}"</strong> siehst du alle Verpaarungen aus deinen Würfen auf einen Blick — mit Anzahl Würfe und Datum des letzten Wurfes.</p>
|
||||
<p className="hilfe-hint">Tipp: Wenn du auf der Genetik-Seite eine Probeverpaarung berechnest, kannst du mit <strong>„Probeverpaarung"</strong> direkt zur Genetikseite springen und das Tierpaar vorauswählen.</p>
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'genetik',
|
||||
title: t.sections.genetik,
|
||||
content: (
|
||||
<>
|
||||
<p>Unter <strong>„{nav.genetics}"</strong> berechnest du, welche Farbschläge bei einer Verpaarung möglich sind.</p>
|
||||
<p>Wähle <strong>„{ge.father}"</strong> und <strong>„{ge.mother}"</strong> aus deinen Tieren oder gib die Genotypen manuell ein. Klicke dann auf <strong>„{ge.run}"</strong>.</p>
|
||||
<p>Das Ergebnis zeigt alle möglichen Farbschläge mit Wahrscheinlichkeit — du kannst zwischen Anzeige nach Farbschlag und nach Genotyp wechseln.</p>
|
||||
<p><strong>Wichtige Warnungen:</strong></p>
|
||||
<ul>
|
||||
<li className="hilfe-warning"><strong>Schecke × Schecke:</strong> {scheckeWarning}</li>
|
||||
<li className="hilfe-warning"><strong>Rex × Rex:</strong> {rexWarning}</li>
|
||||
</ul>
|
||||
<p>Diese Warnungen erscheinen automatisch, wenn die entsprechende Kombination erkannt wird. Bitte beachte sie beim Zuchtentscheid.</p>
|
||||
<p>Den Genotyp eines Tieres trägst du auf der Tier-Detailseite unter <strong>„{g.detail.masterData}"</strong> ein. Format: z. B. <code>Aa CC Dd EE GG Pp Spsp rere</code>. Unbekannte Stellen als <code>?</code>.</p>
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'stammbaum',
|
||||
title: t.sections.stammbaum,
|
||||
content: (
|
||||
<>
|
||||
<p>Auf der Tier-Detailseite öffnest du mit <strong>„{st.openButton}"</strong> den interaktiven Stammbaum.</p>
|
||||
<p><strong>Navigieren:</strong> {st.tapHint}</p>
|
||||
<p><strong>Zoomen:</strong> Mit den Schaltflächen <strong>„{st.zoomIn}"</strong> / <strong>„{st.zoomOut}"</strong> oder per Pinch-Geste auf dem Smartphone. <strong>„{st.zoomFit}"</strong> passt die Ansicht an den Bildschirm an.</p>
|
||||
<p><strong>Drucken / PDF:</strong> Mit <strong>„{st.print}"</strong> öffnest du die Druckansicht (Ahnentafel). Wähle im Druckdialog „Als PDF speichern", um eine Datei zu erzeugen.</p>
|
||||
<p>Der <strong>„{st.inbreeding.label}"</strong> wird automatisch für das angezeigte Tier berechnet und oben angezeigt.</p>
|
||||
<p className="hilfe-hint">Tipp: Je mehr Elterntiere und Würfe du erfasst hast, desto tiefer reicht der Stammbaum.</p>
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'abgabe',
|
||||
title: t.sections.abgabe,
|
||||
content: (
|
||||
<>
|
||||
<p>Der Abgabe-Ablauf läuft in mehreren Schritten:</p>
|
||||
<ol>
|
||||
<li>
|
||||
<strong>Tier zur Abgabe stellen:</strong> Öffne die Tierakte und klicke auf <strong>„{ab.markAction}"</strong>. Das Tier wechselt in den Status <em>„{de.pages.gerbils.statusLabels.ForSale}"</em>.
|
||||
</li>
|
||||
<li>
|
||||
<strong>Gruppen bilden:</strong> Auf der <strong>„{nav.forSale}"</strong>-Seite werden Tiere nach Becken vorgruppiert. Du kannst Tiere zwischen Gruppen verschieben oder neue Gruppen anlegen.
|
||||
</li>
|
||||
<li>
|
||||
<strong>Inserat erstellen:</strong> Fülle für jede Gruppe den emotionalen Einzeiler und den Charakter-Text aus.
|
||||
</li>
|
||||
<li>
|
||||
<strong>Text kopieren & Fotos herunterladen:</strong> Mit <strong>„{ab.export.copyText}"</strong> kommt der fertige Inseratstext in die Zwischenablage. Mit <strong>„{ab.export.downloadPhotos}"</strong> lädst du alle Fotos der Gruppe als ZIP herunter.
|
||||
</li>
|
||||
<li>
|
||||
<strong>KI-Unterstützung (optional):</strong> Mit <strong>„{ab.ai.improve}"</strong> kann der Text automatisch verbessert werden — dazu ist ein API-Schlüssel nötig.
|
||||
</li>
|
||||
</ol>
|
||||
<p className="hilfe-hint">Tipp: Bearbeite jede Gruppe einzeln — Text und Fotos für eine Gruppe gleichzeitig zusammenstellen, dann weiter zur nächsten.</p>
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'statistik',
|
||||
title: t.sections.statistik,
|
||||
content: (
|
||||
<>
|
||||
<p>Unter <strong>„{nav.statistics}"</strong> findest du Auswertungen über deinen Bestand:</p>
|
||||
<ul>
|
||||
<li><strong>„{stat.littersPerYear}"</strong> — wie viele Würfe pro Jahr.</li>
|
||||
<li><strong>„{stat.avgLitterSize}"</strong> — durchschnittliche Wurfstärke pro Jahr.</li>
|
||||
<li><strong>„{stat.farbschlag}"</strong> — wie viele aktive Tiere welchen Farbschlag haben.</li>
|
||||
<li><strong>„{stat.population}"</strong> — wie sich dein Bestand über die Zeit entwickelt hat.</li>
|
||||
<li><strong>„{stat.losses}"</strong> — verstorbene Tiere nach Jahr.</li>
|
||||
</ul>
|
||||
<p>Die Statistiken werden automatisch aus deinen Daten berechnet. Je vollständiger deine Einträge (Geburtsdaten, Farbschläge, Wurfstärken), desto aussagekräftiger die Auswertungen.</p>
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'datensicherung',
|
||||
title: t.sections.datensicherung,
|
||||
content: (
|
||||
<>
|
||||
<p>Deine Daten liegen sicher auf Julian's Server zu Hause — kein Cloud-Konto nötig.</p>
|
||||
<p>Julian erstellt automatisch jeden Nacht um 3 Uhr ein Backup der Datenbank und aller Fotos. Bei einem Problem kann er den Stand eines beliebigen Tages wiederherstellen.</p>
|
||||
<p className="hilfe-hint">Wenn du versehentlich Daten gelöscht hast, wende dich an Julian. Er kann aus einem der letzten 7 Tages-Backups wiederherstellen.</p>
|
||||
</>
|
||||
),
|
||||
},
|
||||
]
|
||||
|
||||
export default function HilfePage() {
|
||||
return (
|
||||
<section className="page hilfe-page">
|
||||
<h2>{t.title}</h2>
|
||||
<p className="hilfe-subtitle">{t.subtitle}</p>
|
||||
|
||||
<div className="hilfe-accordion">
|
||||
{sections.map((s) => (
|
||||
<details key={s.id} className="hilfe-section">
|
||||
<summary className="hilfe-section-summary">
|
||||
<span className="hilfe-section-title">{s.title}</span>
|
||||
<span className="hilfe-section-chevron" aria-hidden="true">▾</span>
|
||||
</summary>
|
||||
<div className="hilfe-section-body">{s.content}</div>
|
||||
</details>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
105
gerbil-manager-web/src/pages/hilfe.css
Normal file
105
gerbil-manager-web/src/pages/hilfe.css
Normal file
@@ -0,0 +1,105 @@
|
||||
.hilfe-page {
|
||||
max-width: 720px;
|
||||
}
|
||||
|
||||
.hilfe-subtitle {
|
||||
color: var(--color-muted, #666);
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
/* Accordion */
|
||||
.hilfe-accordion {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.hilfe-section {
|
||||
border: 1px solid var(--color-border, #ddd);
|
||||
border-radius: 6px;
|
||||
background: var(--color-surface, #fff);
|
||||
}
|
||||
|
||||
.hilfe-section-summary {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0.875rem 1rem;
|
||||
cursor: pointer;
|
||||
list-style: none;
|
||||
user-select: none;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.hilfe-section-summary::-webkit-details-marker {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.hilfe-section-summary:hover {
|
||||
background: var(--color-hover, #f5f5f5);
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.hilfe-section[open] > .hilfe-section-summary {
|
||||
border-bottom: 1px solid var(--color-border, #ddd);
|
||||
border-radius: 6px 6px 0 0;
|
||||
}
|
||||
|
||||
.hilfe-section-title {
|
||||
font-weight: 600;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.hilfe-section-chevron {
|
||||
font-size: 1.1rem;
|
||||
transition: transform 0.2s ease;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.hilfe-section[open] .hilfe-section-chevron {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
.hilfe-section-body {
|
||||
padding: 1rem 1rem 1.25rem;
|
||||
}
|
||||
|
||||
.hilfe-section-body p {
|
||||
margin: 0 0 0.75rem;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.hilfe-section-body p:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.hilfe-section-body ul,
|
||||
.hilfe-section-body ol {
|
||||
margin: 0.25rem 0 0.75rem 1.25rem;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.hilfe-section-body li {
|
||||
margin-bottom: 0.4rem;
|
||||
}
|
||||
|
||||
.hilfe-section-body code {
|
||||
font-family: monospace;
|
||||
background: var(--color-hover, #f5f5f5);
|
||||
padding: 0.1em 0.35em;
|
||||
border-radius: 3px;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
/* Hint boxes */
|
||||
.hilfe-hint {
|
||||
background: var(--color-info-bg, #eff6ff);
|
||||
border-left: 3px solid var(--color-info, #3b82f6);
|
||||
padding: 0.5rem 0.75rem;
|
||||
border-radius: 0 4px 4px 0;
|
||||
font-size: 0.93rem;
|
||||
}
|
||||
|
||||
.hilfe-warning {
|
||||
color: var(--color-warning-text, #92400e);
|
||||
}
|
||||
Reference in New Issue
Block a user