banyaro/backend/static/js/pages/erste-hilfe.js
rene 459cd425f2 Design-System Sprint A: utilities.css + 948 Inline-Styles → Utility-Klassen, SW by-v1102
PHASE 1 — Sofort-Cleanup ohne Risiko:
- Neue Datei utilities.css mit ~25 Klassen für häufige Kombinationen:
  * text-xs-muted, text-xs-secondary, text-sm-muted, text-sm-secondary
  * flex-gap-2/3, flex-col-gap-2/3/4, flex-center-gap-1/2/3
  * flex-between, flex-1-min, mb-1/3, mt-1/3
  * icon-xs/sm/md/lg, label-block, caption
- index.html bindet utilities.css ein
- mb-3/mt-3 ergänzt (waren in design-system.css unvollständig)

PHASE 2 — .by-tab Modifier für Vereinheitlichung:
- .by-tabs.grid (mit --tab-cols Variable für Admin/Health/etc.)
- .by-tabs.sticky (Desktop vertikale Tabs für Admin)
- .by-tabs.wrap (Zuchthunde, flex-wrap statt scroll)
- .by-tabs.separated (Sitting, mit eigenem Hintergrund + Border)

PHASE 3 — Inline-Style → Klassen-Migration (Python-Script):
- 948 Inline-Styles entfernt (5101 → 4153, -18%)
- 962 Migrationen über 47 Page-Dateien
- Top-Treffer: admin.js (180), health.js (67), dog-profile.js (67),
  litters.js (62), settings.js (61), zuchthunde.js (51)
- Patterns: text-muted, text-secondary, text-danger, text-xs-muted,
  text-sm-muted, grid-2 (Duplikat-Bug behoben!), flex-col-gap-3,
  p-3/4, mb-2/3/4, hidden, w-full, flex-1, ...
- Bewahrt bestehende class-Attribute (mergt korrekt)

Alle 19 Tests grün. Kein visueller Diff erwartet (gleiche Property-Werte).
2026-05-27 07:11:27 +02:00

555 lines
34 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

window.Page_erste_hilfe = (() => {
let _container = null;
let _appState = null;
async function init(container, appState, params = {}) {
_container = container;
_appState = appState;
_render();
if (params.tab) _activateTab(params.tab);
}
function refresh() {}
function onDogChange() {}
// ----------------------------------------------------------------
// DATA
// ----------------------------------------------------------------
// Liste von Notrufen, nach Land gruppiert.
// Struktur ist erweiterbar: weitere Länder/Städte einfach an die jeweilige
// Gruppe anhängen. Einträge mit tel:null werden als "TODO: Nummer einfügen"
// dargestellt — Rendering kümmert sich um Optik und tel: -Link.
const NOTFALLNUMMERN_GRUPPEN = [
{
land: 'Deutschland',
flag: 'DE',
eintraege: [
{ label: 'Tiergiftzentrale München', tel: '+4989 19240', display: '+49 89 19240' },
{ label: 'Tiergiftzentrale Berlin', tel: '+4930 19240', display: '+49 30 19240' },
],
},
{
land: 'Österreich',
flag: 'AT',
eintraege: [
{ label: 'Vergiftungsinformationszentrale Wien', tel: '+431 4064343', display: '+43 1 4064343' },
{ label: 'Veterinärmedizinische Universität Wien (Notfallklinik)', tel: null, display: 'TODO: Nummer einfügen' },
],
},
{
land: 'Schweiz',
flag: 'CH',
eintraege: [
{ label: 'Tox Info Suisse (Tiergiftnotruf)', tel: null, display: 'TODO: Nummer einfügen (ggf. 145)' },
{ label: 'Tierspital Zürich', tel: null, display: 'TODO: Nummer einfügen' },
],
},
];
const SCHNELL = [
{ notfall: 'Vergiftung / Giftköder', massnahme: 'Ruhig halten, NICHT erbrechen lassen', tierarzt: 'Sofort' },
{ notfall: 'Hitzschlag', massnahme: 'Kühlen, Wasser anbieten', tierarzt: 'Sofort' },
{ notfall: 'Bewusstlosigkeit', massnahme: 'Atemwege frei, Stabile Seitenlage', tierarzt: 'Sofort' },
{ notfall: 'Starke Blutung', massnahme: 'Druckverband anlegen', tierarzt: 'Sofort' },
{ notfall: 'Knochenbruch', massnahme: 'Ruhigstellen, nicht bewegen', tierarzt: 'Sofort' },
{ notfall: 'Zeckenbiss', massnahme: 'Zecke entfernen, Stelle beobachten', tierarzt: 'Bei Entzündung' },
{ notfall: 'Pfotenverletzung', massnahme: 'Reinigen, Verband', tierarzt: 'Bei tiefer Wunde' },
{ notfall: 'Fremdkörper verschluckt', massnahme: 'Beobachten, nicht erbrechen lassen', tierarzt: 'Bei Symptomen' },
{ notfall: 'Bisswunde', massnahme: 'Reinigen, Wunde beurteilen', tierarzt: 'Bei tiefer Wunde' },
{ notfall: 'Epileptischer Anfall', massnahme: 'Nicht festhalten, sichern', tierarzt: 'Nach dem Anfall' },
];
const KATEGORIEN = [
{
id: 'lebensgefahr',
label: 'Lebensbedrohliche Notfälle',
color: 'var(--c-danger, #ef4444)',
icon: 'warning',
eintraege: [
{
titel: 'Vergiftung / Giftköder',
icon: 'skull',
symptome: ['Erbrechen, Durchfall, übermäßiges Speicheln','Zittern, Krämpfe, Muskelzucken','Taumeln, Orientierungslosigkeit','Blasse oder blaue Schleimhäute','Plötzliche Schwäche, Zusammenbruch'],
massnahmen: ['Hund ruhig halten und von der Giftquelle entfernen','NICHT selbst zum Erbrechen bringen — kann die Vergiftung verschlimmern','Giftköder oder Erbrochenes wenn möglich in einem Beutel sichern','Sofort Tierarzt oder Tiergiftzentrale anrufen','Auf dem Weg: Hund warm halten, ruhig sprechen'],
warn: [{ typ: 'danger', text: 'Nie: Erbrechen einleiten ohne Anweisung des Tierarztes' }],
extra: '<p style="margin-top:var(--space-3);font-size:var(--text-sm);color:var(--c-text-secondary)"><strong>Häufige Giftquellen:</strong> Rattengift, Schneckenkorn (Metaldehyd), Ibuprofen, Paracetamol, Schokolade, Weintrauben/Rosinen, Zwiebeln, Xylit (Kaugummi, Erdnussbutter), präparierte Köder.</p>',
},
{
titel: 'Hitzschlag',
icon: 'thermometer-hot',
symptome: ['Starkes, lautes Hecheln','Taumeln, Koordinationsprobleme','Erbrechen','Glasiger Blick, Apathie','Rote oder blasse Schleimhäute','Bewusstlosigkeit'],
massnahmen: ['Sofort in den Schatten / kühlen Raum bringen','Mit lauwarmem (nicht eiskaltem) Wasser kühlen — Pfoten, Leiste, Nacken','Frisches Wasser anbieten — nicht zwingen','Nassen Lappen auf Bauch und Pfoten legen','Sofort zum Tierarzt — auch wenn der Hund sich erholt'],
warn: [{ typ: 'danger', text: 'Nie: Eiswasser oder Eiswürfel — verursacht Schock durch zu schnelle Abkühlung' }],
},
{
titel: 'Bewusstlosigkeit / Herzstillstand',
icon: 'heartbeat',
symptome: ['Hund reagiert nicht auf Ansprechen oder Berühren','Keine sichtbare Atembewegung','Schleimhäute blass oder blau'],
massnahmen: ['Atemwege freihalten: Maul öffnen, Zunge nach vorne, Fremdkörper entfernen','Atmet der Hund? → Stabile Seitenlage, sofort Tierarzt anrufen','Atmet er nicht? → Herz-Lungen-Wiederbelebung beginnen'],
extra: '<div style="margin-top:var(--space-3);padding:var(--space-3);background:var(--c-surface-2);border-radius:var(--radius-md);font-size:var(--text-sm);color:var(--c-text)"><strong>Herzdruckmassage:</strong> Hund auf die rechte Seite, Hände auf breiteste Stelle des Brustkorbs hinter dem Ellenbogen, 100120 Kompressionen/min, ca. 1/3 eindrücken. Bei kleinen Hunden: eine Hand oder zwei Finger.<br><br><strong>Beatmung:</strong> Nach je 30 Kompressionen 2 Atemzüge — Maul schließen, durch die Nase blasen bis der Brustkorb sich hebt.<br><br>Weiterführen bis: Hund selbst atmet, Tierarzt übernimmt oder nach 10 Min. ohne Reaktion.</div>',
},
{
titel: 'Starke Blutung',
icon: 'drop',
symptome: [],
massnahmen: ['Sauberes Tuch fest auf die Wunde drücken','Druck min. 5 Minuten halten — nicht zwischendurch nachschauen','Druckverband anlegen: Watte auf Wunde, fest mit Binde umwickeln','Hund ruhig halten — Bewegung verstärkt die Blutung','Bei arterieller Blutung (spritzend, hellrot): sofort Tierarzt'],
warn: [{ typ: 'danger', text: 'Niemals ein Tourniquet anlegen — außer als letzter Ausweg bei abgetrennter Gliedmaße' }],
},
{
titel: 'Knochenbruch',
icon: 'bone',
symptome: ['Hund belastet Gliedmaße nicht','Sichtbare Fehlstellung','Starke Schmerzen, Schreien bei Berührung','Schwellung, Blutung'],
massnahmen: ['Hund so wenig wie möglich bewegen','Gebrochene Stelle nicht einrenken oder massieren','Improvisierte Schiene nur wenn nötig: gerades Brett mit Tuch fixieren, nicht zu fest','Hund in Decke einwickeln, ruhig transportieren','Sofort Tierarzt'],
},
],
},
{
id: 'haeufig',
label: 'Häufige Notfälle',
color: 'var(--c-warning, #f59e0b)',
icon: 'first-aid',
eintraege: [
{
titel: 'Zeckenbiss',
icon: 'bug',
symptome: [],
massnahmen: ['Zeckenzange oder Zeckenkarte verwenden — kein Öl, kein Klebstoff, kein Feuer','Zecke so nah wie möglich an der Haut fassen','Gerade herausziehen — nicht drehen','Einstichstelle desinfizieren','Datum und Stelle notieren, 4 Wochen beobachten'],
warn: [{ typ: 'warning', text: 'Zum Tierarzt bei: Rötung/Schwellung, Fieber, Apathie, Lahmheit innerhalb von 4 Wochen oder abgebrochenem Zeckenkopf' }],
extra: '<p style="margin-top:var(--space-3);font-size:var(--text-sm);color:var(--c-text-secondary)"><strong>Übertragende Krankheiten (DE):</strong> Borreliose (häufig), FSME (selten), Babesiose (Süddeutschland, zunehmend), Anaplasmose.</p>',
},
{
titel: 'Pfotenverletzung',
icon: 'paw-print',
symptome: [],
massnahmen: ['Pfote vorsichtig mit lauwarmem Wasser reinigen','Sichtbaren Fremdkörper mit Pinzette entfernen','Leichte Verletzung: reinigen, Pfotenschutzspray, beobachten','Tiefer Schnitt: sauberen Verband anlegen, Tierarzt aufsuchen'],
warn: [{ typ: 'warning', text: 'Notverband: Watte auf Wunde, Mullbinde umwickeln (nicht zu fest), mit Kohäsivbinde sichern' }],
extra: '<p style="margin-top:var(--space-3);font-size:var(--text-sm);color:var(--c-text-secondary)"><strong>Zum Tierarzt wenn:</strong> Wunde klafft, Blutung nicht stoppt, tiefer Einstich, oder Hund nach 24 h noch nicht belastet.</p>',
},
{
titel: 'Fremdkörper verschluckt',
icon: 'circle-dashed',
symptome: ['Im Rachen: Würgen, Pfoten ans Maul, Speicheln','Im Magen: Erbrechen, Appetitlosigkeit','Im Darm: Erbrechen, Blähungen, kein Kot, Schmerzen'],
massnahmen: ['Hund beobachten — viele Gegenstände gehen von selbst durch','Nicht zum Erbrechen bringen (außer auf Anweisung des Tierarztes)','Kein Öl oder Futter geben um nachzuschieben','Bei Würgen: Maul öffnen, sichtbaren Gegenstand entfernen — nur wenn gut erreichbar','Bei Atemnot: Heimlich-Manöver anwenden'],
warn: [{ typ: 'warning', text: 'Sofort zum Tierarzt: anhaltend würgen, Atemnot, angespannter Bauch, kein Kot seit 24 h + Unwohlsein' }],
extra: '<p style="margin-top:var(--space-3);font-size:var(--text-sm);color:var(--c-text-secondary)"><strong>Heimlich-Manöver:</strong> Kleiner Hund: auf den Rücken, sanft aber fest auf den Bauch unter dem Brustkorb drücken. Großer Hund: hinter dem Hund stehen, Arme um den Bauch, Hände unter dem Brustkorb zusammenführen, nach oben und innen drücken.</p>',
},
{
titel: 'Bisswunde',
icon: 'dog',
symptome: [],
massnahmen: ['Hund beruhigen — Schmerz macht auch ruhige Hunde aggressiv','Mit lauwarmem Wasser spülen, kein Alkohol direkt in die Wunde','Oberfläche beurteilen — Bisswunden sehen oft klein aus, sind aber tief'],
warn: [{ typ: 'warning', text: 'Bisswunden sind immer tiefer als sie aussehen. Hunde- und Katzenzähne sind lang und dünn.' },{ typ: 'danger', text: 'Sofort zum Tierarzt: Wunde am Hals/Bauch/Brust, Atembeschwerden, starke Blutung, Apathie/Schock, Bisse von fremden Tieren (Tollwut-Risiko)' }],
},
{
titel: 'Epileptischer Anfall',
icon: 'lightning',
symptome: ['Zuckungen, Krämpfe der Gliedmaßen','Bewusstseinsverlust, starrer Blick','Speicheln, Urin- oder Kotabgang','Desorientierung vor und nach dem Anfall'],
massnahmen: ['Ruhe bewahren — Anfälle enden meist von selbst','Hund NICHT festhalten — Verletzungsgefahr','Gefährliche Gegenstände aus dem Weg räumen','Raum abdunkeln, Geräusche minimieren','Zeit messen — dauert länger als 5 Min: Notfalltierarzt'],
warn: [{ typ: 'warning', text: 'Nach dem Anfall: Hund ist oft desorientiert, kann blind wirken — das ist normal (postiktale Phase). Ruhig sprechen, nicht bedrängen.' }],
extra: '<p style="margin-top:var(--space-3);font-size:var(--text-sm);color:var(--c-text-secondary)"><strong>Sofort zum Tierarzt:</strong> erster Anfall überhaupt, Dauer > 5 Min, mehrere Anfälle in 24 h, Hund kommt nach 30 Min nicht zu sich.</p>',
},
{
titel: 'Verbrennung / Verbrühung',
icon: 'fire',
symptome: [],
massnahmen: ['Betroffene Stelle 1015 Min mit kühlem (nicht eiskaltem) Wasser kühlen','Kein Öl, keine Butter, keine Zahncreme — verstärken den Schaden','Leichte Rötung: kühlen, beobachten','Blasenbildung oder offene Wunden: sofort Tierarzt'],
warn: [{ typ: 'warning', text: 'Heißer Asphalt: Handfläche 5 Sek. auf Boden — zu heiß für dich = zu heiß für Pfoten' }],
},
],
},
{
id: 'wissen',
label: 'Nützliches Wissen',
color: '#ca8a04',
icon: 'book-open',
eintraege: [
{
titel: 'Verbotene Medikamente für Hunde',
icon: 'pill',
symptome: [],
massnahmen: [],
extra: `<div style="overflow-x:auto;margin-top:var(--space-2)">
<table style="width:100%;border-collapse:collapse;font-size:var(--text-sm)">
<thead><tr style="background:var(--c-surface-2)">
<th style="padding:var(--space-2) var(--space-3);text-align:left;font-weight:var(--weight-semibold)">Medikament</th>
<th style="padding:var(--space-2) var(--space-3);text-align:left;font-weight:var(--weight-semibold)">Wirkung beim Hund</th>
</tr></thead>
<tbody>
<tr><td style="padding:var(--space-2) var(--space-3);border-bottom:1px solid var(--c-border)">Ibuprofen</td><td style="padding:var(--space-2) var(--space-3);border-bottom:1px solid var(--c-border)">Magenblutungen, Nierenversagen — schon 1 Tablette gefährlich</td></tr>
<tr style="background:var(--c-surface-2)"><td style="padding:var(--space-2) var(--space-3);border-bottom:1px solid var(--c-border)">Paracetamol</td><td style="padding:var(--space-2) var(--space-3);border-bottom:1px solid var(--c-border)">Leberschäden, tödlich</td></tr>
<tr><td style="padding:var(--space-2) var(--space-3);border-bottom:1px solid var(--c-border)">Aspirin</td><td style="padding:var(--space-2) var(--space-3);border-bottom:1px solid var(--c-border)">Magenblutungen</td></tr>
<tr style="background:var(--c-surface-2)"><td style="padding:var(--space-2) var(--space-3);border-bottom:1px solid var(--c-border)">Diclofenac</td><td style="padding:var(--space-2) var(--space-3);border-bottom:1px solid var(--c-border)">Nieren- und Magenprobleme</td></tr>
<tr><td style="padding:var(--space-2) var(--space-3)">Antidepressiva</td><td style="padding:var(--space-2) var(--space-3)">Krämpfe, Herzprobleme</td></tr>
</tbody>
</table></div>`,
},
{
titel: 'Giftige Pflanzen (Auswahl)',
icon: 'plant',
symptome: [],
massnahmen: [],
extra: `<div style="overflow-x:auto;margin-top:var(--space-2)">
<table style="width:100%;border-collapse:collapse;font-size:var(--text-sm)">
<thead><tr style="background:var(--c-surface-2)">
<th style="padding:var(--space-2) var(--space-3);text-align:left;font-weight:var(--weight-semibold)">Pflanze</th>
<th style="padding:var(--space-2) var(--space-3);text-align:left;font-weight:var(--weight-semibold)">Giftigkeit</th>
</tr></thead>
<tbody>
${[['Herbstzeitlose','Sehr giftig, alle Teile'],['Goldregen','Sehr giftig, besonders Samen'],['Eibe','Sehr giftig, alle Teile außer rotem Fruchtfleisch'],['Maiglöckchen','Giftig, Herzrhythmusstörungen'],['Stechapfel','Sehr giftig'],['Oleander','Sehr giftig'],['Kirschlorbeer','Giftig, besonders Samen'],['Buchsbaum','Giftig'],['Narzisse / Tulpe','Giftig, besonders Zwiebel'],['Wisteria (Blauregen)','Giftig']].map((r, i) => `<tr${i%2?' style="background:var(--c-surface-2)"':''}><td style="padding:var(--space-2) var(--space-3);border-bottom:1px solid var(--c-border)">${r[0]}</td><td style="padding:var(--space-2) var(--space-3);border-bottom:1px solid var(--c-border)">${r[1]}</td></tr>`).join('')}
</tbody>
</table></div>`,
},
{
titel: 'Schleimhäute prüfen',
icon: 'stethoscope',
symptome: [],
massnahmen: [],
extra: `<p style="font-size:var(--text-sm);color:var(--c-text-secondary);margin-bottom:var(--space-3)">Zahnfleisch anheben, Finger andrücken, loslassen — Farbe muss binnen 2 Sek. zurückkehren (kapilläre Füllungszeit).</p>
<div style="overflow-x:auto">
<table style="width:100%;border-collapse:collapse;font-size:var(--text-sm)">
<thead><tr style="background:var(--c-surface-2)">
<th style="padding:var(--space-2) var(--space-3);text-align:left;font-weight:var(--weight-semibold)">Farbe</th>
<th style="padding:var(--space-2) var(--space-3);text-align:left;font-weight:var(--weight-semibold)">Bedeutung</th>
</tr></thead>
<tbody>
<tr><td style="padding:var(--space-2) var(--space-3);border-bottom:1px solid var(--c-border)">Rosa, feucht</td><td style="padding:var(--space-2) var(--space-3);border-bottom:1px solid var(--c-border);color:var(--c-success);font-weight:var(--weight-semibold)">Normal</td></tr>
<tr style="background:var(--c-surface-2)"><td style="padding:var(--space-2) var(--space-3);border-bottom:1px solid var(--c-border)">Blass / weiß</td><td style="padding:var(--space-2) var(--space-3);border-bottom:1px solid var(--c-border);color:var(--c-danger)">Schock, Blutverlust, Vergiftung</td></tr>
<tr><td style="padding:var(--space-2) var(--space-3);border-bottom:1px solid var(--c-border)">Blau / grau</td><td style="padding:var(--space-2) var(--space-3);border-bottom:1px solid var(--c-border);color:var(--c-danger);font-weight:var(--weight-semibold)">Sauerstoffmangel — NOTFALL</td></tr>
<tr style="background:var(--c-surface-2)"><td style="padding:var(--space-2) var(--space-3);border-bottom:1px solid var(--c-border)">Gelb</td><td style="padding:var(--space-2) var(--space-3);border-bottom:1px solid var(--c-border);color:var(--c-warning)">Leberprobleme</td></tr>
<tr><td style="padding:var(--space-2) var(--space-3);border-bottom:1px solid var(--c-border)">Ziegelrot</td><td style="padding:var(--space-2) var(--space-3);border-bottom:1px solid var(--c-border);color:var(--c-danger)">Hitzschlag, Vergiftung</td></tr>
<tr style="background:var(--c-surface-2)"><td style="padding:var(--space-2) var(--space-3)">Trocken</td><td style="padding:var(--space-2) var(--space-3);color:var(--c-warning)">Austrocknung</td></tr>
</tbody>
</table></div>`,
},
{
titel: 'Erste-Hilfe-Ausrüstung',
icon: 'backpack',
symptome: [],
massnahmen: ['Mullbinden und Verbandsmull','Kohäsivbinde (haftet selbst, kein Kleber)','Zeckenzange oder Zeckenkarte','Pinzette','Desinfektionsspray (Chlorhexidin)','Pfotenschutzspray','Einmalhandschuhe','Notfalldecke (Rettungsfolie)','Taschenlampe','Tierarzt-Notfallnummer gespeichert'],
},
],
},
];
// ----------------------------------------------------------------
// RENDER
// ----------------------------------------------------------------
function _render() {
_container.innerHTML = `
<div id="eh-wrap" style="padding-bottom:var(--space-8)">
${_renderDisclaimer()}
${_renderNotfallbanner()}
${_renderSchnell()}
<div style="margin:var(--space-6) 0 var(--space-3);display:flex;gap:var(--space-2);flex-wrap:wrap" id="eh-tabs">
${KATEGORIEN.map(k => `
<button class="btn eh-tab-btn" data-tab="${k.id}"
style="border:2px solid ${k.color};padding:var(--space-2) var(--space-4);border-radius:var(--radius-pill);font-size:var(--text-sm);font-weight:var(--weight-semibold);color:${k.color};background:transparent;cursor:pointer">
<svg class="ph-icon" aria-hidden="true" style="color:${k.color}"><use href="/icons/phosphor.svg#${k.icon}"></use></svg>
${k.label}
</button>
`).join('')}
</div>
${KATEGORIEN.map(k => `
<div class="eh-tab-panel" id="eh-panel-${k.id}" class="hidden">
${k.eintraege.map((e, i) => _renderEintrag(e, k.id, i, k.color)).join('')}
</div>
`).join('')}
<div style="margin-top:var(--space-6);padding:var(--space-4);background:var(--c-surface-2);border-radius:var(--radius-md);font-size:var(--text-sm);color:var(--c-text-secondary);line-height:1.6">
<svg class="ph-icon" aria-hidden="true" class="text-primary"><use href="/icons/phosphor.svg#info"></use></svg>
Diese Inhalte ersetzen keinen Tierarztbesuch. Im Zweifel immer sofort zum Tierarzt oder den tierärztlichen Notdienst anrufen.
</div>
</div>
`;
_bindTabs();
_bindAccordions();
_bindNoteButtons();
_activateTab('lebensgefahr');
}
function _renderDisclaimer() {
return `
<div role="alert" style="display:flex;align-items:flex-start;gap:var(--space-3);
background:#fef3c7;color:#78350f;border-left:4px solid #d97706;
border-radius:var(--radius-md);padding:var(--space-3) var(--space-4);
margin-bottom:var(--space-4);font-size:var(--text-sm);line-height:1.5">
<svg class="ph-icon" aria-hidden="true" style="flex-shrink:0;color:#d97706;width:22px;height:22px;margin-top:2px"><use href="/icons/phosphor.svg#warning"></use></svg>
<div>
<strong style="display:block;margin-bottom:2px">Diese Hinweise ersetzen keine tierärztliche Beratung.</strong>
Im Notfall sofort einen Tierarzt aufsuchen!
</div>
</div>
`;
}
function _renderNotfallbanner() {
const renderEintrag = (n) => {
// Eintrag mit verfügbarer Nummer → tel:-Link
if (n.tel) {
return `
<a href="tel:${n.tel}"
style="display:flex;align-items:center;gap:var(--space-2);color:#fff;text-decoration:none;font-size:var(--text-sm);padding:var(--space-2) var(--space-3);background:rgba(255,255,255,0.15);border-radius:var(--radius-md)">
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#phone"></use></svg>
<span><strong>${n.label}</strong><br>${n.display}</span>
</a>
`;
}
// Eintrag ohne Nummer → ausgegrauter Platzhalter
return `
<div style="display:flex;align-items:center;gap:var(--space-2);color:rgba(255,255,255,0.85);font-size:var(--text-sm);padding:var(--space-2) var(--space-3);background:rgba(255,255,255,0.08);border-radius:var(--radius-md);border:1px dashed rgba(255,255,255,0.35)">
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#phone"></use></svg>
<span><strong>${n.label}</strong><br><em style="font-style:normal;opacity:.85">${n.display}</em></span>
</div>
`;
};
const gruppen = NOTFALLNUMMERN_GRUPPEN.map(g => `
<div>
<div style="font-size:var(--text-xs);font-weight:var(--weight-semibold);color:rgba(255,255,255,0.85);text-transform:uppercase;letter-spacing:0.04em;margin-bottom:var(--space-1)">
${g.flag} · ${g.land}
</div>
<div class="flex-col-gap-2">
${g.eintraege.map(renderEintrag).join('')}
</div>
</div>
`).join('');
return `
<div style="background:var(--c-danger);border-radius:var(--radius-lg);padding:var(--space-4);margin-bottom:var(--space-4)">
<div style="display:flex;align-items:center;gap:var(--space-2);color:#fff;font-weight:var(--weight-bold);font-size:var(--text-base);margin-bottom:var(--space-3)">
<svg class="ph-icon" style="width:20px;height:20px" aria-hidden="true"><use href="/icons/phosphor.svg#siren"></use></svg>
Tiergiftzentralen — jetzt anrufen
</div>
<div class="flex-col-gap-3">
${gruppen}
</div>
<p style="margin-top:var(--space-3);font-size:var(--text-xs);color:rgba(255,255,255,0.8)">
Tierärztlicher Notdienst: Über die Tierarztsuche in der Banyaro-Karte
</p>
</div>
`;
}
function _renderSchnell() {
const rows = SCHNELL.map((s, i) => `
<tr style="${i % 2 ? 'background:var(--c-surface-2)' : ''}">
<td style="padding:var(--space-2) var(--space-3);font-size:var(--text-sm);border-bottom:1px solid var(--c-border)">${s.notfall}</td>
<td style="padding:var(--space-2) var(--space-3);font-size:var(--text-sm);border-bottom:1px solid var(--c-border)">${s.massnahme}</td>
<td style="padding:var(--space-2) var(--space-3);font-size:var(--text-sm);border-bottom:1px solid var(--c-border);font-weight:var(--weight-semibold);color:${s.tierarzt === 'Sofort' ? 'var(--c-danger)' : 'var(--c-warning)'}">${s.tierarzt}</td>
</tr>
`).join('');
return `
<div class="card" style="padding:0;overflow:hidden;margin-bottom:var(--space-4)">
<div style="padding:var(--space-3) var(--space-4);background:var(--c-surface-2);font-weight:var(--weight-semibold);font-size:var(--text-sm);display:flex;align-items:center;gap:var(--space-2)">
<svg class="ph-icon" aria-hidden="true" class="text-primary"><use href="/icons/phosphor.svg#list-bullets"></use></svg>
Schnellübersicht: Was tun bei …
</div>
<div style="overflow-x:auto">
<table style="width:100%;border-collapse:collapse">
<thead><tr style="background:var(--c-surface-2)">
<th style="padding:var(--space-2) var(--space-3);text-align:left;font-size:var(--text-xs);color:var(--c-text-secondary);font-weight:var(--weight-semibold)">Notfall</th>
<th style="padding:var(--space-2) var(--space-3);text-align:left;font-size:var(--text-xs);color:var(--c-text-secondary);font-weight:var(--weight-semibold)">Sofortmaßnahme</th>
<th style="padding:var(--space-2) var(--space-3);text-align:left;font-size:var(--text-xs);color:var(--c-text-secondary);font-weight:var(--weight-semibold)">Tierarzt</th>
</tr></thead>
<tbody>${rows}</tbody>
</table>
</div>
</div>
`;
}
function _renderEintrag(e, katId, idx, katColor) {
const accId = `eh-acc-${katId}-${idx}`;
const bodyId = `eh-body-${katId}-${idx}`;
const symptomeHtml = e.symptome.length
? `<p style="font-size:var(--text-sm);font-weight:var(--weight-semibold);color:var(--c-text-secondary);margin:0 0 var(--space-2)">Symptome</p>
<ul style="margin:0 0 var(--space-3);padding-left:var(--space-5);font-size:var(--text-sm);color:var(--c-text);line-height:1.6">
${e.symptome.map(s => `<li>${s}</li>`).join('')}
</ul>`
: '';
const massnahmenHtml = e.massnahmen.length
? `<p style="font-size:var(--text-sm);font-weight:var(--weight-semibold);color:var(--c-text-secondary);margin:0 0 var(--space-2)">Sofortmaßnahmen</p>
<ol style="margin:0 0 var(--space-3);padding-left:var(--space-5);font-size:var(--text-sm);color:var(--c-text);line-height:1.6">
${e.massnahmen.map(m => `<li>${m}</li>`).join('')}
</ol>`
: '';
const warnHtml = (e.warn || []).map(w => `
<div style="padding:var(--space-2) var(--space-3);border-radius:var(--radius-md);margin-bottom:var(--space-2);font-size:var(--text-sm);line-height:1.5;
background:${w.typ === 'danger' ? 'rgba(239,68,68,0.1)' : 'rgba(245,158,11,0.1)'};
color:${w.typ === 'danger' ? 'var(--c-danger)' : 'var(--c-warning)'};
border-left:3px solid ${w.typ === 'danger' ? 'var(--c-danger)' : 'var(--c-warning)'}">
<svg class="ph-icon" aria-hidden="true" style="vertical-align:middle;margin-right:4px"><use href="/icons/phosphor.svg#${w.typ === 'danger' ? 'prohibit' : 'warning-circle'}"></use></svg>
${w.text}
</div>
`).join('');
return `
<div id="${accId}" style="border-bottom:1px solid var(--c-border)">
<button data-acc-id="${bodyId}" data-acc-arrow="arr-${katId}-${idx}"
style="width:100%;display:flex;align-items:center;justify-content:space-between;padding:var(--space-4);background:none;border:none;cursor:pointer;text-align:left;gap:var(--space-3)"
aria-expanded="false">
<span style="display:flex;align-items:center;gap:var(--space-3)">
<svg class="ph-icon" aria-hidden="true" style="color:${katColor};flex-shrink:0"><use href="/icons/phosphor.svg#${e.icon}"></use></svg>
<strong style="font-size:var(--text-base);color:var(--c-text)">${e.titel}</strong>
</span>
<svg class="ph-icon" id="arr-${katId}-${idx}" aria-hidden="true" style="flex-shrink:0;color:var(--c-text-secondary);transition:transform 0.2s"><use href="/icons/phosphor.svg#caret-down"></use></svg>
</button>
<div id="${bodyId}" hidden style="padding:0 var(--space-4) var(--space-4)">
${symptomeHtml}
${massnahmenHtml}
${warnHtml}
${e.extra || ''}
<div style="margin-top:var(--space-3);text-align:right">
<button class="btn btn-ghost btn-xs eh-note-btn" style="font-size:var(--text-xs);color:var(--c-text-muted);padding:2px 8px"
data-kat-id="${katId}" data-titel="${e.titel.replace(/"/g,'&quot;')}"><svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#note-pencil"></use></svg> Notiz</button>
</div>
</div>
</div>
`;
}
// ----------------------------------------------------------------
// EVENTS
// ----------------------------------------------------------------
function _bindTabs() {
_container.querySelectorAll('.eh-tab-btn').forEach(btn => {
btn.addEventListener('click', () => _activateTab(btn.dataset.tab));
});
}
function _activateTab(id) {
_container.querySelectorAll('.eh-tab-btn').forEach(btn => {
const kat = KATEGORIEN.find(k => k.id === btn.dataset.tab);
const active = btn.dataset.tab === id;
btn.style.background = active ? kat.color : 'transparent';
btn.style.color = active ? '#fff' : kat.color;
});
_container.querySelectorAll('.eh-tab-panel').forEach(panel => {
panel.style.display = panel.id === `eh-panel-${id}` ? 'block' : 'none';
});
}
function _bindAccordions() {
_container.querySelectorAll('[data-acc-id]').forEach(btn => {
btn.addEventListener('click', () => {
const bodyId = btn.dataset.accId;
const arrowId = btn.dataset.accArrow;
const body = document.getElementById(bodyId);
const arrow = document.getElementById(arrowId);
if (!body) return;
const open = !body.hidden;
body.hidden = open;
btn.setAttribute('aria-expanded', String(!open));
if (arrow) arrow.style.transform = open ? '' : 'rotate(180deg)';
});
});
}
function _bindNoteButtons() {
_container.querySelectorAll('.eh-note-btn').forEach(btn => {
btn.addEventListener('click', e => {
e.stopPropagation();
const katId = btn.dataset.katId;
const titel = btn.dataset.titel;
const kat = KATEGORIEN.find(k => k.id === katId);
const label = kat ? `${kat.label}${titel}` : titel;
_openNoteModal('erste_hilfe', katId, label, null);
});
});
}
// ----------------------------------------------------------------
// NOTIZ-MODAL (custom DOM, kein UI.modal um Konflikte zu vermeiden)
// ----------------------------------------------------------------
async function _openNoteModal(parentType, parentId, parentLabel, locationName) {
document.getElementById('by-note-modal')?.remove();
const overlay = document.createElement('div');
overlay.id = 'by-note-modal';
overlay.style.cssText = 'position:fixed;inset:0;z-index:2000;background:rgba(0,0,0,.55);display:flex;align-items:flex-end;justify-content:center';
const _esc = s => s ? String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;') : '';
overlay.innerHTML = `
<div style="background:var(--c-surface);border-radius:var(--radius-xl) var(--radius-xl) 0 0;
width:100%;max-width:640px;max-height:90vh;display:flex;flex-direction:column;
padding-bottom:env(safe-area-inset-bottom,0px)">
<div style="padding:var(--space-4) var(--space-5);border-bottom:1px solid var(--c-border);
display:flex;align-items:center;justify-content:space-between;flex-shrink:0">
<div>
<div style="font-weight:var(--weight-semibold);font-size:var(--text-base)"><svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#note-pencil"></use></svg> Notiz</div>
<div style="font-size:var(--text-xs);color:var(--c-text-muted);margin-top:2px">${_esc(parentLabel)}</div>
</div>
<button id="by-note-close" style="background:none;border:none;cursor:pointer;font-size:1.4rem;color:var(--c-text-muted);padding:4px 8px" aria-label="Schließen">×</button>
</div>
<div style="padding:var(--space-4) var(--space-5);flex:1;overflow-y:auto">
<form id="by-note-form">
<textarea id="by-note-text" class="form-control" rows="5"
placeholder="Notiz eingeben…"
style="width:100%;resize:vertical"></textarea>
</form>
</div>
<div style="padding:var(--space-3) var(--space-5);border-top:1px solid var(--c-border);
display:flex;gap:var(--space-2);flex-shrink:0">
<button type="button" id="by-note-cancel" class="btn btn-secondary flex-1">Abbrechen</button>
<button type="submit" form="by-note-form" id="by-note-save" class="btn btn-primary flex-1">Speichern</button>
</div>
</div>
`;
document.body.appendChild(overlay);
const textarea = document.getElementById('by-note-text');
const saveBtn = document.getElementById('by-note-save');
const cancelBtn = document.getElementById('by-note-cancel');
const closeBtn = document.getElementById('by-note-close');
let existingNoteId = null;
try {
const existing = await API.notes.get(parentType, parentId);
if (existing?.id) {
existingNoteId = existing.id;
textarea.value = existing.text || '';
}
} catch (_) { /* keine Notiz vorhanden — ok */ }
setTimeout(() => textarea.focus(), 100);
const _close = () => overlay.remove();
closeBtn.addEventListener('click', _close);
cancelBtn.addEventListener('click', _close);
overlay.addEventListener('click', e => { if (e.target === overlay) _close(); });
document.getElementById('by-note-form').addEventListener('submit', async e => {
e.preventDefault();
const text = textarea.value.trim();
UI.setLoading(saveBtn, true);
try {
const payload = { text, parent_label: parentLabel, location_name: locationName };
if (existingNoteId) {
await API.notes.update(existingNoteId, payload);
} else {
await API.notes.create(parentType, parentId, payload);
}
UI.toast.success('Notiz gespeichert.');
_close();
} catch (err) {
UI.toast.error(err.message || 'Fehler beim Speichern.');
UI.setLoading(saveBtn, false);
}
});
}
// ----------------------------------------------------------------
// PUBLIC
// ----------------------------------------------------------------
return { init, refresh, onDogChange };
})();