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).
555 lines
34 KiB
JavaScript
555 lines
34 KiB
JavaScript
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, 100–120 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 10–15 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,'"')}"><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,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"') : '';
|
||
|
||
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 };
|
||
})();
|