From ba5547f99386f42a9d4e100abe1931e4668db9f7 Mon Sep 17 00:00:00 2001 From: rene Date: Fri, 24 Apr 2026 20:56:47 +0200 Subject: [PATCH] Pflege-System: Pflegetipps im Hundeprofil + Rassen-Autocomplete MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - GET /api/dogs/{id}/pflege: rassenspezifische Pflegetipps - pflege_tipps DB-Tabelle (43 Tipps, 10 Kategorien) geseedet - dogs.rasse_id für Wiki-Verknüpfung (Migration) - Hundeprofil: Tipp des Tages + alle Tipps aufklappbar - Hundeprofil-Edit: Rassen-Autocomplete mit Wiki-Match-Badge - Social: Post-Bestätigung (Gepostet!-Button, Quick-Mark, Pending-Banner) - Social: Pflegetipp-Button (allg. + rassenspezifisch) - Social: Diversitäts-Check, Kategorie-Tagging - Social: 104 Übungen, Übungsübersicht-Modal - Admin: Social-Media-Tracking-Sektion - SW by-v356, APP_VER 343 --- backend/database.py | 2 + backend/routes/dogs.py | 79 +++++ backend/routes/social.py | 386 +++++++++++++++++++++++++ backend/static/js/app.js | 2 +- backend/static/js/pages/dog-profile.js | 173 ++++++++++- backend/static/js/pages/social.js | 162 ++++++++++- backend/static/sw.js | 2 +- 7 files changed, 797 insertions(+), 9 deletions(-) diff --git a/backend/database.py b/backend/database.py index 822319a..3acd66a 100644 --- a/backend/database.py +++ b/backend/database.py @@ -528,6 +528,8 @@ def _migrate(conn_factory): ("social_content", "media_url", "TEXT"), ("social_content", "category", "TEXT"), ("social_content", "exercise_id", "TEXT"), + ("social_content", "post_url", "TEXT"), + ("dogs", "rasse_id", "INTEGER"), ] with conn_factory() as conn: for table, column, col_type in migrations: diff --git a/backend/routes/dogs.py b/backend/routes/dogs.py index da2f9cd..74b6d5b 100644 --- a/backend/routes/dogs.py +++ b/backend/routes/dogs.py @@ -28,6 +28,7 @@ class DogCreate(BaseModel): class DogUpdate(BaseModel): name: Optional[str] = None rasse: Optional[str] = None + rasse_id: Optional[int] = None geburtstag: Optional[str] = None geschlecht: Optional[str] = None gewicht_kg: Optional[float] = None @@ -330,3 +331,81 @@ async def report_found(dog_id: int, data: FoundReport = FoundReport()): }) return {"ok": True} + + +# ------------------------------------------------------------------ +# GET /api/dogs/{id}/pflege — Pflegetipps für diesen Hund +# ------------------------------------------------------------------ +@router.get("/{dog_id}/pflege") +async def get_pflege_tipps(dog_id: int, user=Depends(get_current_user)): + import json as _json + with db() as conn: + dog = conn.execute( + "SELECT id, name, rasse, rasse_id FROM dogs WHERE id=? AND user_id=?", + (dog_id, user["id"]) + ).fetchone() + if not dog: + raise HTTPException(404, "Hund nicht gefunden.") + + # Rassen-Infos für Fell-Typ + rasse_info = None + with db() as conn: + if dog["rasse_id"]: + rasse_info = conn.execute( + "SELECT name, groesse, beschreibung FROM wiki_rassen WHERE id=?", + (dog["rasse_id"],) + ).fetchone() + elif dog["rasse"]: + rasse_info = conn.execute( + "SELECT name, groesse, beschreibung FROM wiki_rassen WHERE name LIKE ? LIMIT 1", + (f"%{dog['rasse']}%",) + ).fetchone() + + # Fell-Typ ableiten + fell_filter = None + if rasse_info: + beschr = (rasse_info["beschreibung"] or "").lower() + if any(w in beschr for w in ["lockig", "wellig", "kraus", "pudel", "doodle"]): + fell_filter = "lockig" + elif any(w in beschr for w in ["langhaar", "seidiges", "fließendes", "langes fell"]): + fell_filter = "lang" + elif any(w in beschr for w in ["kurzhaar", "kurzes fell", "glatthaarig"]): + fell_filter = "kurz" + elif rasse_info["groesse"] in ("gross", "sehr_gross"): + fell_filter = "doppel" + + with db() as conn: + alle_tipps = conn.execute( + "SELECT * FROM pflege_tipps ORDER BY kategorie, titel" + ).fetchall() + + # Relevante Tipps: kein Fell-Filter oder passend + from datetime import date + heute_saison = {1:"winter",2:"winter",3:"fruehling",4:"fruehling",5:"fruehling", + 6:"sommer",7:"sommer",8:"sommer",9:"herbst",10:"herbst", + 11:"herbst",12:"winter"}[date.today().month] + + result = [] + for t in alle_tipps: + t = dict(t) + # Fell-Filter + if fell_filter and t["fell_typ"] and t["fell_typ"] != "alle": + if fell_filter not in t["fell_typ"].split(","): + continue + t["schritte"] = _json.loads(t["schritte"] or "[]") + t["saisonal_aktuell"] = bool(t["saison"] and heute_saison in t["saison"]) + result.append(t) + + # Tipp des Tages: erster aktuell-saisonaler oder zufällig deterministisch + from hashlib import md5 + day_hash = int(md5(str(date.today()).encode()).hexdigest(), 16) + saisonal = [t for t in result if t["saisonal_aktuell"]] + tipp_des_tages = (saisonal or result)[day_hash % len(saisonal or result)] if result else None + + return { + "dog_name": dog["name"], + "rasse_name": rasse_info["name"] if rasse_info else dog["rasse"], + "tipp_des_tages": tipp_des_tages, + "tipps": result, + "kategorien": list(dict.fromkeys(t["kategorie"] for t in result)), + } diff --git a/backend/routes/social.py b/backend/routes/social.py index ea93249..53f7901 100644 --- a/backend/routes/social.py +++ b/backend/routes/social.py @@ -449,6 +449,264 @@ _UEBUNGEN = [ "tipp": "Jede neue Begegnung in dieser Phase prägt lebenslang."}, ] +# ------------------------------------------------------------------ +# Pflege-Tipps-Bibliothek +# ------------------------------------------------------------------ +_PFLEGE_TIPPS = [ + # ── FELL ──────────────────────────────────────────────────────── + {"id":"fell_buersten_kurz","titel":"Bürsten bei Kurzhaarfell","kat":"Fell", + "beschreibung":"Kurzhaar braucht weniger, aber regelmäßige Pflege — für Glanz und Gesundheit.", + "schritte":["Gummibürste oder Hundekamm","Gegen und mit dem Fell bürsten","Totreste entfernen","Glanzspray optional"], + "materialien":"Gummibürste, feiner Kamm, Mikrofasertuch","haeufigkeit":"1–2x pro Woche", + "fell_typ":"kurz","saison":None,"tipp":"Kurzhaar verliert trotzdem Haare — wöchentliches Bürsten hält Wohnung sauber."}, + {"id":"fell_buersten_lang","titel":"Bürsten bei Langhaarfell","kat":"Fell", + "beschreibung":"Langhaar verfilzt schnell — tägliches Bürsten verhindert schmerzhafte Knoten.", + "schritte":["Abschnittweise arbeiten","Zuerst Unterwolle mit Undercoat-Rake","Dann Bürste über alles","Verfilzungen mit Entfilzer-Spray lösen, nie reißen"], + "materialien":"Undercoat-Rake, Slicker-Bürste, Entfilzer-Spray","haeufigkeit":"Täglich", + "fell_typ":"lang","saison":None,"tipp":"Von Beinen und Schwanz beginnen — dort filzt es zuerst."}, + {"id":"fell_buersten_lockig","titel":"Pflege bei Lockenfell (Pudel, Labradoodle)","kat":"Fell", + "beschreibung":"Lockiges Fell verliert kaum Haare, verfilzt aber stark — spezielle Technik nötig.", + "schritte":["Täglich durchkämmen mit Metallkamm","Verfilzungen mit Finger lösen, dann Kamm","Alle 6–8 Wochen Schertermin","Zwischen Augen und Pfoten regelmäßig trimmen"], + "materialien":"Metallkamm, Pin-Bürste, Schere","haeufigkeit":"Täglich + 6–8 Wochen Schertermin", + "fell_typ":"lockig","saison":None,"tipp":"Lockiges Fell = hypoallergen, aber Pflegeaufwand unterschätzt!"}, + {"id":"fell_unterwolle", "titel":"Unterwolle ausbürsten (Fellwechsel)","kat":"Fell", + "beschreibung":"Zweimal jährlich toter Unterwolle-Berg — richtig ausgebürstet statt überall verteilt.", + "schritte":["Undercoat-Rake gegen Haarwuchsrichtung","Abschnittweise: Rücken, Flanken, Bauch","Furminator maximal 2x/Woche","Nach dem Bürsten: Hund ausschütteln lassen"], + "materialien":"Furminator oder Undercoat-Rake","haeufigkeit":"Täglich während Fellwechsel", + "fell_typ":"doppel","saison":"fruehling,herbst","tipp":"Einen Schuh voll Haare pro Session ist normal bei Huskies & Co."}, + {"id":"fell_bad_timing", "titel":"Wann und wie oft Baden?","kat":"Fell", + "beschreibung":"Zu häufiges Baden zerstört den natürlichen Fellschutz — die richtige Balance.", + "schritte":["Kurzhaar: alle 6–8 Wochen","Langhaar/Lockig: alle 4–6 Wochen","Vor dem Bad: komplett ausbürsten","Nur Hundeshampoo verwenden — Menschen-Shampoo macht Fell spröde"], + "materialien":"Hundeshampoo, Handbrause, große Handtücher","haeufigkeit":"4–8 Wochen", + "fell_typ":"alle","saison":None,"tipp":"Fön auf niedrigster Stufe — hohe Hitze schadet dem Fell."}, + {"id":"fell_trocknen", "titel":"Richtiges Trocknen nach dem Bad","kat":"Fell", + "beschreibung":"Nasses Fell eingerollt schläft = Hautprobleme — so trocknet man richtig.", + "schritte":["Handtuch: klopfen, nicht reiben", "Hundeföhn oder normaler Fön auf Kalt/Niedrig","Fell gleichzeitig bürsten","Niemals nass in kalte Luft"], + "materialien":"Absorbier-Handtuch, Hundeföhn","haeufigkeit":"Nach jedem Bad", + "fell_typ":"alle","saison":None,"tipp":"Mikrofaser-Handtücher nehmen 5x mehr Wasser auf als normale."}, + {"id":"fell_geruch", "titel":"Fell-Geruch zwischen Bädern","kat":"Fell", + "beschreibung":"Hund riecht ohne Baden — Trockenshampoo und Hausmittel helfen.", + "schritte":["Trockenshampoo einsprühen","3 Min einwirken lassen","Ausbürsten","Alternative: Backpulver einmassieren, ausklopfen"], + "materialien":"Trockenshampoo für Hunde, Bürste","haeufigkeit":"Nach Bedarf", + "fell_typ":"alle","saison":None,"tipp":"Geruch oft durch Unterkieferdrüsen — dort bürsten!"}, + {"id":"fell_ernaehrung", "titel":"Ernährung für gesundes, glänzendes Fell","kat":"Fell", + "beschreibung":"Schönes Fell kommt von innen — die richtigen Nährstoffe machen den Unterschied.", + "schritte":["Lachsöl täglich: 1 TL pro 10 kg","Omega-3-Fettsäuren im Futter","Hochwertige Proteine","Biotin-Supplement bei stumpfem Fell"], + "materialien":"Lachsöl, hochwertiges Futter","haeufigkeit":"Täglich", + "fell_typ":"alle","saison":None,"tipp":"Verbesserung sichtbar nach 4–8 Wochen."}, + # ── KRALLEN ───────────────────────────────────────────────────── + {"id":"krallen_schneiden", "titel":"Krallen richtig schneiden","kat":"Krallen", + "beschreibung":"Zu lange Krallen verändern die Körperhaltung und können schmerzhaft sein.", + "schritte":["Ruhige Umgebung, Hund entspannt","Krallenschneider scharf und sauber","Weißes Fell: rosa Bereich (Quick) sehen → 2mm davor schneiden","Schwarze Krallen: kleine Scheiben bis weiß/grau sichtbar"], + "materialien":"Krallenschneider oder -feile, Blutungspulver für Notfall","haeufigkeit":"Alle 3–4 Wochen", + "fell_typ":"alle","saison":None,"tipp":"Schneidet man zu kurz: Blutung sofort mit Stärke oder Kaffeepulver stoppen."}, + {"id":"krallen_schleifen", "titel":"Krallen-Schleifbrett trainieren","kat":"Krallen", + "beschreibung":"Hund schleift Krallen selbst ab — nie wieder Krallen-Kampf!", + "schritte":["Schleifbrett (Sandpapier auf Brett) vorlegen","Pfote draufstellen belohnen","Kratzbewegung formen","Hund macht es selbst auf Kommando"], + "materialien":"Kratzbrett selbstgebaut: Holz + grobes Sandpapier","haeufigkeit":"Wöchentlich", + "fell_typ":"alle","saison":None,"tipp":"Hinterkrallen schleifen sich beim Laufen oft selbst ab."}, + {"id":"krallen_dew", "titel":"Daumenkralle / Wolfskralle","kat":"Krallen", + "beschreibung":"Die Wolfskralle berührt keinen Boden — wächst ins Fleisch wenn vergessen!", + "schritte":["Wolfskralle an Innenseite des Vorderbeins suchen","Einige Hunde haben sie auch hinten","Häufiger schneiden als andere Krallen","Auf Einwachsung achten"], + "materialien":"Krallenschneider","haeufigkeit":"Alle 2–3 Wochen", + "fell_typ":"alle","saison":None,"tipp":"Wolfskralle ist DER häufigste Pflegefehler — vergiss sie nie!"}, + # ── ZÄHNE ─────────────────────────────────────────────────────── + {"id":"zahne_putzen", "titel":"Zähne täglich putzen","kat":"Zähne", + "beschreibung":"Zahnfleischentzündung betrifft 80% aller Hunde ab 3 Jahren — täglich putzen hilft.", + "schritte":["Hundezahnpasta auftragen (nie Menschenzahnpasta!)", "Finger einführen → kreisende Bewegung","Schrittweise zur Zahnbürste wechseln","Außenflächen priorisieren"], + "materialien":"Hundezahnpasta (Hühnergeschmack!), Fingerling oder Zahnbürste","haeufigkeit":"Täglich", + "fell_typ":"alle","saison":None,"tipp":"Menschenzahnpasta ist giftig für Hunde — immer Hundezahnpasta!"}, + {"id":"zahne_kauen", "titel":"Kauartikel für gesunde Zähne","kat":"Zähne", + "beschreibung":"Kauen reinigt Zähne mechanisch — das natürlichste Zahnpflegeprogramm.", + "schritte":["Roher Rinderknochen (nur roh!)", "Kauknochen aus Rinderhaut", "Dental-Chews (Greenies, Milkbone)", "Kauspielzeug aus Nylon"], + "materialien":"Kauknochen, Dental Chews","haeufigkeit":"Täglich", + "fell_typ":"alle","saison":None,"tipp":"Gekochte Knochen splittern und sind lebensgefährlich — nur ROH!"}, + {"id":"zahne_zahnstein", "titel":"Zahnstein erkennen & vorbeugen","kat":"Zähne", + "beschreibung":"Gelb-braune Beläge = Zahnstein → nur Tierarzt oder Ultraschall entfernt das.", + "schritte":["Regelmäßig Zähne kontrollieren (Lippen hochziehen)","Gelb = Plaque (wegputzbar), Braun = Zahnstein (Tierarzt)","Zahnpflege-Diät-Futter verwenden","Tierarzt-Kontrolle 1x/Jahr"], + "materialien":"Zahnpflegefutter, Zahngel","haeufigkeit":"Kontrolle wöchentlich", + "fell_typ":"alle","saison":None,"tipp":"Zahnsteinentfernung in Narkose kostet 300–800€ — Vorsorge lohnt sich!"}, + # ── OHREN ─────────────────────────────────────────────────────── + {"id":"ohren_reinigen", "titel":"Ohren sauber halten","kat":"Ohren", + "beschreibung":"Dreckige Ohren = Ohrenentzündung — regelmäßige Reinigung schützt.", + "schritte":["Äußeren Gehörgang begutachten","Ohrenpflege-Lösung einträufeln","Mit weichem Tuch/Wattebausch reinigen","Nie mit Wattestäbchen tief rein"], + "materialien":"Ohrenreiniger für Hunde, Wattebausch","haeufigkeit":"Alle 2–4 Wochen", + "fell_typ":"alle","saison":None,"tipp":"Übler Geruch = Entzündung → sofort zum Tierarzt!"}, + {"id":"ohren_haare", "titel":"Ohrenbehaarung trimmen","kat":"Ohren", + "beschreibung":"Dichte Ohrenbehaarung staut Luft — Otitis vorprogrammiert bei Hängeohrrassen.", + "schritte":["Haare im Außenohr vorsichtig mit Schere trimmen","Haare im inneren Ohrkanal: Trimmer oder Zupfen (nur wenn gelernt)", "Nach Schwimmen: Ohren immer trocknen","Beim Groomer: Ohren entnehmen lassen"], + "materialien":"Schere, Trimmer, Ohrenpfleger","haeufigkeit":"Monatlich", + "fell_typ":"alle","saison":"sommer","rassengruppe":"Hängeohrrassen","tipp":"Hängeohren (Cocker, Beagle) = höheres Risiko → häufiger kontrollieren."}, + {"id":"ohren_check", "titel":"Wöchentlicher Ohren-Check","kat":"Ohren", + "beschreibung":"Früherkennung von Entzündungen spart Schmerzen und Kosten.", + "schritte":["Ohren hochklappen und reinschauen","Normal: hellrosa, leicht glänzend, kaum Geruch","Alarm: rot, geschwollen, dunkler Belag, Kratzen","Bei Auffälligkeiten: Tierarzt binnen 24h"], + "materialien":"Taschenlicht","haeufigkeit":"Wöchentlich", + "fell_typ":"alle","saison":None,"tipp":"Hunde mit Allergien haben häufiger Ohrprobleme."}, + # ── AUGEN ─────────────────────────────────────────────────────── + {"id":"augen_reinigen", "titel":"Augenränder reinigen","kat":"Augen", + "beschreibung":"Tränenflecken und Schlieren — mit der richtigen Technik verschwinden sie.", + "schritte":["Feuchtes weiches Tuch oder Wattebausch","Von innen nach außen wischen","Nie auf die Hornhaut drücken","Augenreiniger für Hunde optional"], + "materialien":"Augenreiniger, Wattepads","haeufigkeit":"Täglich oder bei Bedarf", + "fell_typ":"alle","saison":None,"tipp":"Braune Tränenflecken bei Weißfell: spezielle Produkte oder Tierarzt."}, + {"id":"augen_fell", "titel":"Fell vor Augen schneiden","kat":"Augen", + "beschreibung":"Fell im Gesicht kann Hornhaut kratzen — regelmäßiges Trimmen schützt.", + "schritte":["Abgerundete Schere verwenden","Fell von Augenrändern wegschneiden","Nie beim unruhigen Hund schneiden","Professioneller Groomer alle 4–6 Wochen"], + "materialien":"Abgerundete Schere","haeufigkeit":"Monatlich", + "fell_typ":"lang,lockig","saison":None,"rassengruppe":"Langhaarrassen","tipp":"Niemals spitze Schere in Augennähe — immer stumpf/abgerundet!"}, + {"id":"augen_check", "titel":"Täglicher Augen-Check","kat":"Augen", + "beschreibung":"Augen erzählen viel über die Gesundheit — täglich kurz hinschauen.", + "schritte":["Klar und glänzend = gut","Trüb, gerötet, Ausfluss = Problem","Blinzeln oder Kratzen = Tierarzt","Licht mal mit Taschenlampe auf Pupillen"], + "materialien":None,"haeufigkeit":"Täglich", + "fell_typ":"alle","saison":None,"tipp":"Plötzliche Trübung = Tierarzt HEUTE."}, + # ── PFOTEN ────────────────────────────────────────────────────── + {"id":"pfoten_sommer", "titel":"Pfoten im Sommer schützen","kat":"Pfoten", + "beschreibung":"Asphalt im Sommer bis 70°C heiß — Pfotenballen verbrennen in Sekunden.", + "schritte":["Handtest: Hand 5 Sek auf Asphalt → zu heiß wenn unerträglich","Morgens/abends spazieren","Pfotenbalsam schützt und pflegt","Hundeschuhe für empfindliche Hunde"], + "materialien":"Pfotenbalsam, Hundeschuhe","haeufigkeit":"Täglich im Sommer", + "fell_typ":"alle","saison":"sommer","tipp":"Regel: Wenn du barfuß nicht draufstehen kannst, dein Hund auch nicht!"}, + {"id":"pfoten_winter", "titel":"Pfoten im Winter / Streusalz","kat":"Pfoten", + "beschreibung":"Streusalz ist ätzend — rissige Ballen und Vergiftung durch Ablecken.", + "schritte":["Vor dem Spaziergang: Pfotenbalsam auftragen","Nach dem Spaziergang: Pfoten mit lauwarmem Wasser abspülen","Zwischen Zehen prüfen: kein Eis","Hundeschuhe bei viel Salz"], + "materialien":"Pfotenbalsam, Wasser, kleines Becken","haeufigkeit":"Täglich im Winter", + "fell_typ":"alle","saison":"winter","tipp":"Musher's Secret ist der Pfoten-Klassiker für den Winter."}, + {"id":"pfoten_haare", "titel":"Pfotenhaare trimmen","kat":"Pfoten", + "beschreibung":"Zu lange Haare zwischen den Zehen = Rutschgefahr und Schmutzfänger.", + "schritte":["Haare zwischen Zehenballen mit Schere oder Trimmer","Bündig mit Ballenoberfläche kürzen","Nicht zu kurz — Schutzfunktion erhalten","Auf Wunden und Einschüsse achten"], + "materialien":"Abgerundete Schere oder Trimmer","haeufigkeit":"Alle 3–4 Wochen", + "fell_typ":"lang,lockig","saison":None,"tipp":"Rutschiger Parkett + lange Pfotenhaare = Kreuzbandriss-Risiko!"}, + {"id":"pfoten_ballen", "titel":"Pfotenballen pflegen","kat":"Pfoten", + "beschreibung":"Rissige Ballen sind schmerzhaft — regelmäßige Pflege beugt vor.", + "schritte":["Ballen wöchentlich kontrollieren","Risse eincremen mit Pfotenbalsam","Tiefe Risse: Verbandsmull + Tierarzt","Im Sommer nach Sand/Strand extra eincremen"], + "materialien":"Pfotenbalsam, Wattepads","haeufigkeit":"Wöchentlich (täglich im Winter/Sommer)", + "fell_typ":"alle","saison":None,"tipp":"Bienen-Propolis-Balsam hat heilende Wirkung auf rissige Ballen."}, + {"id":"pfoten_reinigen", "titel":"Pfoten nach dem Spaziergang reinigen","kat":"Pfoten", + "beschreibung":"Dreck, Giftstoffe und Parasiten kommen mit rein — kurze Routine macht Unterschied.", + "schritte":["Kleines Becken mit lauwarmem Wasser","Jede Pfote kurz eintauchen und abwischen","Zwischen Zehen kontrollieren","Abtrocknen — feuchte Pfoten = Pilzgefahr"], + "materialien":"Pfoten-Reinigungsbecher, Handtuch","haeufigkeit":"Nach jedem Spaziergang", + "fell_typ":"alle","saison":None,"tipp":"Pfotenwasch-Becher (Dexas Mudbuster) ist der Gamechanger!"}, + # ── PARASITEN ─────────────────────────────────────────────────── + {"id":"parasiten_zecken_check","titel":"Zecken-Check nach Spaziergang","kat":"Parasiten", + "beschreibung":"Zecken übertragen Borreliose und FSME — täglicher Check rettet Leben.", + "schritte":["Kopf: um Augen, Ohren, Hals","Achseln und Leiste","Zwischen Zehen","Schwanzwurzel und Analbereich","Fell gegen Strich durchkämmen"], + "materialien":"Feine Zeckenzange oder Zeckenhaken","haeufigkeit":"Nach jedem Spaziergang im Gras", + "fell_typ":"alle","saison":"fruehling,sommer,herbst","tipp":"Zecken erst nach 24h übertragen meist Erreger — täglich reicht!"}, + {"id":"parasiten_zecken_entfernen","titel":"Zecke richtig entfernen","kat":"Parasiten", + "beschreibung":"Falsch entfernt = Erkrankungsrisiko erhöht — diese Technik ist richtig.", + "schritte":["Zeckenzange nah an Haut ansetzen","Gerade rausziehen, nicht drehen oder quetschen","Kein Öl, Klebstoff oder Nagellack","Bissstelle 2 Wochen beobachten","Bei Rötung: Tierarzt"], + "materialien":"Zeckenzange/-haken","haeufigkeit":"Sofort wenn Zecke gefunden", + "fell_typ":"alle","saison":"fruehling,sommer,herbst","tipp":"Fotografiere Zecke und Bissstelle zur Dokumentation."}, + {"id":"parasiten_prophylaxe","titel":"Zecken- und Floh-Prophylaxe","kat":"Parasiten", + "beschreibung":"Spot-on, Tablette oder Halsband — die Vor- und Nachteile.", + "schritte":["Spot-on: monatlich auf Nacken, zuverlässig","Tabletten (NexGard, Bravecto): bequem, für Wasserhunde","Zeckenhalsband (Seresto): 8 Monate Schutz","Natürlich: Kokosöl, Schwarzkümmelöl (schwächerer Schutz)"], + "materialien":"Tierarzt-Empfehlung","haeufigkeit":"Je nach Produkt", + "fell_typ":"alle","saison":"fruehling,sommer,herbst","tipp":"Produkte immer vom Tierarzt — manche Katzen-Präparate töten Hunde!"}, + {"id":"parasiten_floh", "titel":"Floh-Befall erkennen und behandeln","kat":"Parasiten", + "beschreibung":"Flöhe sieht man selten — aber Flohkot ist eindeutig.", + "schritte":["Weißes Tuch unter Hund, kämmen → schwarze Punkte = Flohkot","Feuchtes Küchentuch: Flohkot wird rötlich-braun","Hund + Wohnung gleichzeitig behandeln","Flohpuder und -spray für Umgebung"], + "materialien":"Floh-Kamm, Flohspray, Tierarzt-Präparat","haeufigkeit":"Bei Verdacht + Prophylaxe", + "fell_typ":"alle","saison":"sommer","tipp":"80% der Flöhe leben in der Wohnung, nicht am Hund!"}, + {"id":"parasiten_wurm", "titel":"Wurm-Prophylaxe","kat":"Parasiten", + "beschreibung":"Würmer übertragen sich auf Menschen — regelmäßige Behandlung schützt die Familie.", + "schritte":["Wurmkur alle 3–6 Monate (je nach Lebensweise)","Kot-Untersuchung beim Tierarzt","Nach Zeckenbiss: auf Herzwurm testen (Reisehunde)","Hände waschen nach Hundekontakt"], + "materialien":"Wurmmittel vom Tierarzt","haeufigkeit":"Alle 3–6 Monate", + "fell_typ":"alle","saison":None,"tipp":"Rohfleisch-Hunde öfter entwurmen — rohes Fleisch überträgt Parasiten."}, + # ── SAISONAL ──────────────────────────────────────────────────── + {"id":"saison_hitze", "titel":"Pflege im Sommer / Hitzeschutz","kat":"Saisonal", + "beschreibung":"Hunde schwitzen durch Pfoten und Hecheln — Überhitzung ist lebensgefährlich.", + "schritte":["Spaziergänge in früh/spät","Immer Wasser dabei","Nasses Tuch auf Bauch kühlt schnell","Niemals im Auto lassen"], + "materialien":"Kühlmatte, Kühlweste, Wassernapf","haeufigkeit":"Täglich im Sommer", + "fell_typ":"alle","saison":"sommer","tipp":"Kurznasige Rassen (Mops, Bulldogge): extreme Hitzegefahr!"}, + {"id":"saison_scheren", "titel":"Sommerhaarschnitt — ja oder nein?","kat":"Saisonal", + "beschreibung":"Doppelfell scheren schadet mehr als es nützt — Missverständnis aufklären!", + "schritte":["Doppelfell (Husky, Retriever): NICHT scheren — kühlt selbst","Einfaches langes Fell: kürzen ok","Stattdessen: intensiv ausbürsten, Unterwolle entfernen","Scheren nur wenn verfilzt oder Veterinär rät"], + "materialien":"Bürste, Undercoat-Rake","haeufigkeit":"Saisonal", + "fell_typ":"doppel,lang","saison":"sommer","tipp":"Geschorenes Doppelfell kann sich falsch nachwachsen — Textur-Veränderung dauerhaft."}, + {"id":"saison_winter_kalt","titel":"Winterpflege für empfindliche Hunde","kat":"Saisonal", + "beschreibung":"Kleine, kurzhaarige und ältere Hunde frieren — Schutz ist Pflicht.", + "schritte":["Hundemantel für Kurzhaar unter 5°C","Nach draußen: nie nass raus","Fell nach Schnee/Regen sofort trocknen","Alters-Check: Gelenke im Winter öfter warm halten"], + "materialien":"Hundemantel, Pfotenschutz","haeufigkeit":"Täglich im Winter", + "fell_typ":"kurz","saison":"winter","rassengruppe":"Kurzhaarrassen","tipp":"Chihuahua und Co. im Winter wirklich mit Mantel — kein Modethema!"}, + {"id":"saison_pollen", "titel":"Allergiezeit / Pollenflug","kat":"Saisonal", + "beschreibung":"Hunde haben Pollenallergien — Jucken, Pfotenlecken, Ohrenprobleme.", + "schritte":["Nach Spaziergang Fell und Pfoten abwischen","Pollen-App nutzen: Hochphasen meiden","Tierarzt: Antihistaminika für Hunde","Hypoallergenes Shampoo verwenden"], + "materialien":"Pollen-App, feuchte Tücher","haeufigkeit":"Täglich im Frühling", + "fell_typ":"alle","saison":"fruehling","tipp":"Pollenallergie kann Hautprobleme verursachen — oft verwechselt mit Futtermittelallergie."}, + {"id":"saison_fell_wechsel","titel":"Fellwechsel-Saison meistern","kat":"Saisonal", + "beschreibung":"2x jährlich Haare-Chaos — mit der richtigen Routine überleben alle.", + "schritte":["Täglich bürsten während Fellwechsel","Furminator 2x/Woche (nicht öfter)","Lüfter hilft lose Haare wegzublasen","Professionelles Grooming buchen"], + "materialien":"Furminator, Staubsauger mit Tierhaar-Aufsatz","haeufigkeit":"Täglich März–Mai, Sep–Nov", + "fell_typ":"doppel","saison":"fruehling,herbst","tipp":"Ein 10-Minuten-Bürstsession täglich ersetzt stundenlange Staubsauger-Session."}, + # ── GESUNDHEITSVORSORGE ────────────────────────────────────────── + {"id":"gesund_impfung", "titel":"Impfkalender verstehen","kat":"Gesundheitsvorsorge", + "beschreibung":"Welche Impfungen wirklich nötig sind — Kern- vs. Non-Core-Impfungen.", + "schritte":["Kernimpfungen: Staupe, Parvo, Leptospirose jährlich/alle 3 Jahre","Non-Core je nach Lebensweise: Tollwut, Zwinger-Husten","Impfpass immer aktuell halten","Jährlicher Tierarztbesuch zum Check"], + "materialien":"Impfpass","haeufigkeit":"Jährlich", + "fell_typ":"alle","saison":None,"tipp":"Überimpfung gibt es — Titer-Test statt Routine-Booster möglich."}, + {"id":"gesund_vorsorge", "titel":"Jährlicher Gesundheitscheck","kat":"Gesundheitsvorsorge", + "beschreibung":"Blutbild + körperliche Untersuchung einmal jährlich — Früherkennung rettet Leben.", + "schritte":["Einmal jährlich Tierarzt auch ohne Anlass","Ab 7 Jahren: halbjährlich","Blutbild gibt Einblick in Organfunktion","Gewicht kontrollieren"], + "materialien":"Tierarzt-Termin","haeufigkeit":"Jährlich (ab 7 J. halbjährlich)", + "fell_typ":"alle","saison":None,"tipp":"Senior-Hunde ab 7 Jahre: halbjährlicher Check lohnt sich sehr."}, + {"id":"gesund_gewicht", "titel":"Idealgewicht halten","kat":"Gesundheitsvorsorge", + "beschreibung":"70% aller Hunde in Deutschland sind übergewichtig — Rippen müssen fühlbar sein.", + "schritte":["Rippentest: leicht zu fühlen, nicht zu sehen → Idealgewicht","Taille von oben erkennbar","Leckerlis: max. 10% des Tagesbedarfs","Futtermenge nach Aktivität anpassen"], + "materialien":"Küchenwaage für Futter","haeufigkeit":"Wöchentliche Sichtkontrolle", + "fell_typ":"alle","saison":None,"tipp":"1 kg Übergewicht beim 5-kg-Hund = 20 kg beim Menschen!"}, + {"id":"gesund_zahnkontrolle","titel":"Monatliche Mundhöhlen-Kontrolle","kat":"Gesundheitsvorsorge", + "beschreibung":"Zahn- und Zahnfleischprobleme schmerzen — und Hunde zeigen das selten.", + "schritte":["Lippen hochziehen: Zähne und Zahnfleisch sehen","Zahnfleisch: rosa, glatt = gut; rot, geschwollen = Problem","Zähne: weiß/crème = ok; braun = Zahnstein","Mundgeruch plötzlich stark = Tierarzt"], + "materialien":None,"haeufigkeit":"Monatlich", + "fell_typ":"alle","saison":None,"tipp":"Hunde zeigen Zahnschmerzen durch schlechteres Fressen, weniger Spielen."}, + {"id":"gesund_körpercheck","titel":"Wöchentlicher Körper-Check","kat":"Gesundheitsvorsorge", + "beschreibung":"5 Minuten wöchentlich — Beulen, Wunden, Veränderungen früh erkennen.", + "schritte":["Kopf bis Schwanz abtasten","Lymphknoten fühlen (Kieferwinkel, Hals, Achsel, Leiste)","Auf Schmerz-Reaktionen achten","Alles unbekannte: Tierarzt"], + "materialien":"Gutes Licht","haeufigkeit":"Wöchentlich", + "fell_typ":"alle","saison":None,"tipp":"Du kennst deinen Hund am besten — vertrau deinem Gefühl."}, + {"id":"gesund_senior", "titel":"Pflege für Senior-Hunde","kat":"Gesundheitsvorsorge", + "beschreibung":"Ab 7–8 Jahren ändern sich Bedürfnisse — angepasste Pflege macht Unterschied.", + "schritte":["Kürzere, öftere Spaziergänge statt langer Touren","Orthopädische Schlafmatte","Gelenk-Supplemente (Glucosamin, Omega-3)","Mehr Körperchecks, öftere Tierarztbesuche"], + "materialien":"Orthopädie-Schlafmatte, Gelenk-Supplements","haeufigkeit":"Täglich", + "fell_typ":"alle","saison":None,"tipp":"Senior-Hund = mehr Pflege, mehr Liebe, mehr Tierarzt — so einfach."}, + # ── WELPEN-PFLEGE ──────────────────────────────────────────────── + {"id":"welpe_erste_pflege","titel":"Erste Pflege-Einführung beim Welpen","kat":"Welpen-Pflege", + "beschreibung":"Was du jetzt mit dem Welpen übst, macht das spätere Leben einfacher.", + "schritte":["Täglich Pfoten, Ohren, Mund anfassen — immer positiv", "Kämmen einführen mit weicher Bürste","Nagel-Feile zeigen: noch nicht benutzen, nur zeigen","Alles mit Leckerli verbinden"], + "materialien":"Weiche Bürste, Leckerlis","haeufigkeit":"Täglich", + "fell_typ":"alle","saison":None,"tipp":"Was du im ersten Jahr tust, sparst du im Rest des Lebens."}, + {"id":"welpe_badegewöhnung","titel":"Welpe ans Baden gewöhnen","kat":"Welpen-Pflege", + "beschreibung":"Erste Baderr-Erfahrung prägt lebenslang — positiv starten!", + "schritte":["Leeres Becken mit Leckerlis auskleiden","Wasser einlaufen lassen: Welpe schaut zu","Pfoten einweichen lassen","Erstes Bad sehr kurz (2 Min), viel Lob"], + "materialien":"Flache Wanne, mildes Welpen-Shampoo","haeufigkeit":"Alle 6–8 Wochen", + "fell_typ":"alle","saison":None,"tipp":"Wassertemperatur: wie für ein Baby — lauwarm."}, +] + +_PROMPT_PFLEGE = '''\ +Erstelle einen Social-Media-Post über diesen Hunde-Pflegetipp für Ban Yaro (banyaro.app). + +Pflegetipp: {titel} +Kategorie: {kat} +Beschreibung: {beschreibung} +Schritte: {schritte} +Materialien: {materialien} +Häufigkeit: {haeufigkeit} +Profi-Tipp: {tipp} +{rasse_kontext} + +Antworte NUR als JSON: +{{ + "caption": "Post-Text mit Emojis, lehrreich aber locker, max 700 Zeichen. Startet mit einem Fakten-Hook.", + "hashtags": "10 Hashtags kommagetrennt: hundepflege, hundegesundheit, {kat_lower}, banyaro + passende", + "hook": "Erste Zeile die sofort Aufmerksamkeit fängt (Fakt oder Frage)", + "cta": "Frage ans Publikum passend zum Thema", + "visual_brief": "Was man fotografieren/filmen sollte: konkretes Bild das den Tipp zeigt", + "canva_notes": "Infografik-Idee: z.B. Schritt-für-Schritt mit Icons", + "unsplash_query": "2–3 englische Suchbegriffe für passendes Stockfoto", + "ai_score": <1-5>, + "category": "pflege", + "coaching": "Warum ist dieser Pflegetipp wichtig für die Zielgruppe? Wie macht man das als Video? (1–2 Sätze)" +}} +''' + _TRAINING_STILE = [ "tutorial", # "So geht das:" "community", # "Sind grad dran das zu lernen" @@ -598,6 +856,7 @@ class StatusUpdate(BaseModel): scheduled_at: Optional[str] = None published_at: Optional[str] = None notes: Optional[str] = None + post_url: Optional[str] = None def _used_topics(limit: int = 30) -> str: @@ -932,8 +1191,23 @@ def _seed_exercises(): u.get("tipp")), ) +def _seed_pflege(): + with db() as conn: + for p in _PFLEGE_TIPPS: + conn.execute( + """INSERT OR IGNORE INTO pflege_tipps + (tipp_id, titel, kategorie, beschreibung, schritte, + materialien, haeufigkeit, fell_typ, saison, rassengruppe, tipp) + VALUES (?,?,?,?,?,?,?,?,?,?,?)""", + (p["id"], p["titel"], p["kat"], + p.get("beschreibung"), json.dumps(p.get("schritte", []), ensure_ascii=False), + p.get("materialien"), p.get("haeufigkeit"), + p.get("fell_typ"), p.get("saison"), p.get("rassengruppe"), p.get("tipp")), + ) + try: _seed_exercises() + _seed_pflege() except Exception: pass @@ -1210,6 +1484,118 @@ async def get_stats(user=Depends(require_social_media)): } +# ------------------------------------------------------------------ +# POST /api/social/pflege-tipp — Pflegetipp generieren (allg. oder rassenspezifisch) +# ------------------------------------------------------------------ +@router.post("/pflege-tipp") +async def pflege_tipp(breed_id: Optional[int] = None, user=Depends(require_social_media)): + with db() as conn: + used_ids = {r["exercise_id"] for r in conn.execute( + "SELECT exercise_id FROM social_content WHERE exercise_id IS NOT NULL" + ).fetchall()} + + # Rassenspezifische Tipps bevorzugen wenn breed_id angegeben + rasse = None + if breed_id: + rasse = conn.execute( + "SELECT name, groesse, beschreibung, herkunft FROM wiki_rassen WHERE id=?", + (breed_id,), + ).fetchone() + + tipps = conn.execute( + "SELECT * FROM pflege_tipps ORDER BY RANDOM()" + ).fetchall() + + # Noch nicht verwendete bevorzugen + unused = [t for t in tipps if t["tipp_id"] not in used_ids] + pool = unused if unused else list(tipps) + + # Bei Rasse: Fell-Typ-Filter wenn möglich + if rasse and rasse["groesse"]: + fell_filter = "kurz" if rasse["groesse"] in ("klein", "mittel") else "lang" + relevant = [t for t in pool if not t["fell_typ"] or t["fell_typ"] == "alle" + or fell_filter in (t["fell_typ"] or "")] + if relevant: + pool = relevant + + if not pool: + raise HTTPException(404, "Keine Pflegetipps gefunden.") + + t = dict(pool[0]) + schritte_list = json.loads(t["schritte"] or "[]") + schritte_text = "\n".join(f" {i+1}. {s}" for i, s in enumerate(schritte_list[:5])) + + rasse_kontext = "" + if rasse: + rasse_kontext = ( + f"\nRassenspezifisch für: {rasse['name']} " + f"(Größe: {rasse['groesse'] or 'unbekannt'})\n" + "Passe den Post gezielt an diese Rasse an." + ) + + prompt = _PROMPT_PFLEGE.format( + titel=t["titel"], + kat=t["kategorie"], + beschreibung=t["beschreibung"] or t["titel"], + schritte=schritte_text, + materialien=t["materialien"] or "Standard Pflegeutensilien", + haeufigkeit=t["haeufigkeit"] or "Nach Bedarf", + tipp=t["tipp"] or "", + rasse_kontext=rasse_kontext, + kat_lower=t["kategorie"].lower().replace(" ", "").replace("-", ""), + ) + + try: + raw = await _ki_complete(prompt) + data = _parse_json(raw) + except Exception as e: + raise HTTPException(500, f"KI-Fehler: {e}") + + topic = f"Pflegetipp: {t['titel']}" + if rasse: + topic += f" ({rasse['name']})" + + with db() as conn: + cur = conn.execute( + """INSERT INTO social_content + (created_by, platform, format, topic, caption, hashtags, + visual_brief, canva_notes, hook, cta, unsplash_query, + ai_score, source, coaching, category, exercise_id) + VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)""", + (user["id"], "both", "post", topic, + data.get("caption"), data.get("hashtags"), + data.get("visual_brief"), data.get("canva_notes"), + data.get("hook"), data.get("cta"), data.get("unsplash_query"), + data.get("ai_score"), "generated", data.get("coaching"), + "pflege", t["tipp_id"]), + ) + entry_id = cur.lastrowid + + with db() as conn: + row = conn.execute("SELECT * FROM social_content WHERE id=?", (entry_id,)).fetchone() + result = dict(row) + result["pflege_titel"] = t["titel"] + result["pflege_kat"] = t["kategorie"] + result["rasse_name"] = rasse["name"] if rasse else None + return result + + +# ------------------------------------------------------------------ +# GET /api/social/pflege-kategorien — Statistik Pflegetipps +# ------------------------------------------------------------------ +@router.get("/pflege-kategorien") +async def pflege_kategorien(user=Depends(require_social_media)): + with db() as conn: + rows = conn.execute( + """SELECT p.kategorie, COUNT(*) as total, + SUM(CASE WHEN sc.exercise_id IS NOT NULL THEN 1 ELSE 0 END) as used + FROM pflege_tipps p + LEFT JOIN social_content sc ON sc.exercise_id = p.tipp_id + GROUP BY p.kategorie ORDER BY p.kategorie""" + ).fetchall() + return [dict(r) for r in rows] + + # ------------------------------------------------------------------ # POST /api/social/media — Medien-Upload (Foto/Video) # ------------------------------------------------------------------ diff --git a/backend/static/js/app.js b/backend/static/js/app.js index c1baaf7..8b666e7 100644 --- a/backend/static/js/app.js +++ b/backend/static/js/app.js @@ -3,7 +3,7 @@ Router, State-Management, Navigation, Initialisierung. ============================================================ */ -const APP_VER = '337'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen +const APP_VER = '343'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen const App = (() => { diff --git a/backend/static/js/pages/dog-profile.js b/backend/static/js/pages/dog-profile.js index dddb750..9a9df54 100644 --- a/backend/static/js/pages/dog-profile.js +++ b/backend/static/js/pages/dog-profile.js @@ -149,6 +149,7 @@ window.Page_dog_profile = (() => { ` : ''}
+
${dog.is_public ? `
+
+ 🛁 + + Pflegetipps${data.rasse_name ? ` für ${_esc(data.rasse_name)}` : ''} + +
+ + ${t ? ` + +
+
+ ${t.saisonal_aktuell ? '🌸 Aktuell & Saisonal' : '💡 Tipp des Tages'} +
+
+ ${kat_icons[t.kategorie]||'🐾'} ${_esc(t.titel)} +
+
${_esc(t.beschreibung||'')}
+ ${t.haeufigkeit ? `
+ 🔄 ${_esc(t.haeufigkeit)}
` : ''} + ${t.materialien ? `
+ 🛒 ${_esc(t.materialien)}
` : ''} + ${t.schritte?.length ? ` +
+ Anleitung anzeigen +
    + ${t.schritte.map(s=>`
  1. ${_esc(s)}
  2. `).join('')} +
+ ${t.tipp ? `
💜 ${_esc(t.tipp)}
` : ''} +
` : ''} +
` : ''} + + + + +
`; + + el.querySelector('#dp-pflege-alle')?.addEventListener('click', e => { + const liste = el.querySelector('#dp-pflege-liste'); + const btn = e.currentTarget; + if (liste.style.display === 'none') { + liste.style.display = ''; + btn.textContent = 'Pflegetipps einklappen ▲'; + } else { + liste.style.display = 'none'; + btn.textContent = `Alle ${data.tipps.length} Pflegetipps anzeigen`; + } + }); + } + + function _esc(s) { + if (!s) return ''; + return String(s).replace(/&/g,'&').replace(//g,'>').replace(/"/g,'"'); + } + // ---------------------------------------------------------- // SITTER-ZUGANG // ---------------------------------------------------------- @@ -788,11 +909,21 @@ window.Page_dog_profile = (() => { + + +
@@ -882,6 +1013,45 @@ window.Page_dog_profile = (() => { const form = document.getElementById('dp-form'); if (!form) return; + // Rassen-Autocomplete aus Wiki laden + let _wikiBreeds = []; + API.get('/wiki/rassen?limit=1000&offset=0').then(data => { + _wikiBreeds = data.rassen || []; + const list = document.getElementById('dp-rasse-list'); + if (list) { + list.innerHTML = _wikiBreeds.map(r => + `
` : ''} -
+
✓ Gespeichert ${score ? `${score}` : ''} + +
+ ${mediaUrl ? ` @@ -791,6 +868,47 @@ window.Page_social = (() => { if (item) _showPreview(item); }); }); + // "Habe ich gepostet!" — Formular einblenden + el.querySelectorAll('.sm-posted-btn').forEach(btn => { + btn.addEventListener('click', () => { + const form = el.querySelector(`#sm-posted-form-${btn.dataset.id}`); + if (form) { + form.style.display = form.style.display === 'none' ? '' : 'none'; + // Heute als Default-Datum + const dateInput = form.querySelector('.sm-post-date'); + if (dateInput && !dateInput.value) { + dateInput.value = new Date().toISOString().slice(0,10); + } + } + }); + }); + // Bestätigen + el.querySelectorAll('.sm-confirm-posted').forEach(btn => { + btn.addEventListener('click', async () => { + const id = parseInt(btn.dataset.id); + const form = el.querySelector(`#sm-posted-form-${id}`); + const date = form?.querySelector('.sm-post-date')?.value + || new Date().toISOString().slice(0,16); + const url = form?.querySelector('.sm-post-url')?.value || null; + btn.disabled = true; + btn.textContent = '…'; + await API.patch(`/social/content/${id}`, { + status: 'published', + published_at: date, + post_url: url || undefined, + }); + // Form durch Bestätigung ersetzen + if (form) form.innerHTML = ` +
+ 🎉 Super! Post als veröffentlicht markiert. + ${url ? `
Post ansehen →` : ''} +
`; + // Stats aktualisieren + API.get('/social/stats').then(s => { _stats = s; }); + }); + }); } // --------------------------------------------------------------- @@ -883,12 +1001,28 @@ window.Page_social = (() => { async function load(f) { filter = f; const url = f==='alle' ? '/social/content' : `/social/content?status=${f}`; - const items = await API.get(url).catch(() => []); - render(items); + const [items, allItems] = await Promise.all([ + API.get(url).catch(() => []), + f !== 'alle' ? API.get('/social/content').catch(() => []) : Promise.resolve(null), + ]); + const pending = (allItems || items).filter(c => + c.status === 'idea' || c.status === 'draft').length; + render(items, pending); } - function render(items) { + function render(items, pending) { el.innerHTML = ` + ${pending > 0 ? ` +
+ +
+
${pending} Post${pending>1?'s':''} warten auf Bestätigung
+
+ Tippe auf 📤 wenn du einen Post abgesetzt hast — so lernt Luna was wirklich live ging.
+
+
` : ''}
${['alle','idea','draft','scheduled','published','archived'].map(s => `
` : ''}
+ ${c.status !== 'published' ? ` + ` : ` +
`} @@ -953,6 +1094,15 @@ window.Page_social = (() => {
`).join('')}`; el.querySelectorAll('[data-f]').forEach(b => b.addEventListener('click', () => load(b.dataset.f))); + el.querySelectorAll('.sm-quick-post').forEach(b => b.addEventListener('click', async () => { + const url = prompt('Post-URL (optional, leer lassen wenn keine):', '') ?? null; + await API.patch(`/social/content/${b.dataset.id}`, { + status: 'published', + published_at: new Date().toISOString().slice(0,16), + post_url: url || undefined, + }); + load(filter); + })); el.querySelectorAll('.sm-exp').forEach(b => b.addEventListener('click', () => { const d = el.querySelector(`#sm-d-${b.dataset.id}`); if (d) { d.style.display = d.style.display==='none'?'':'none'; } diff --git a/backend/static/sw.js b/backend/static/sw.js index a0e1b3a..9643200 100644 --- a/backend/static/sw.js +++ b/backend/static/sw.js @@ -3,7 +3,7 @@ Offline-Cache + Push Notifications + Tile-Cache ============================================================ */ -const CACHE_VERSION = 'by-v349'; +const CACHE_VERSION = 'by-v356'; const CACHE_STATIC = `${CACHE_VERSION}-static`; const CACHE_TILES = 'ban-yaro-tiles-v1'; // bleibt über SW-Updates erhalten