(function(){ // ============================================================ // habits.jsx — Gewohnheiten-Tracker // Components: HabitView, HabitForm, SportIcon // Depends on (window): todayISO, localISO // ============================================================ const { useState } = React; // Alle verfügbaren Icons aus habit-icons/ const HABIT_ICONS = [ // Sport { key: "runner", label: "Laufen" }, { key: "bicycle", label: "Velo" }, { key: "dumbbell", label: "Gym" }, { key: "swimming", label: "Schwimmen" }, { key: "yoga", label: "Yoga" }, { key: "walk", label: "Spazieren" }, { key: "hiking", label: "Wandern" }, { key: "stretch", label: "Dehnen" }, { key: "activity", label: "Aktivität" }, { key: "basketball", label: "Basketball" }, { key: "boxing", label: "Boxen" }, { key: "soccer", label: "Fussball" }, { key: "tennis", label: "Tennis" }, { key: "golf", label: "Golf" }, { key: "stopwatch", label: "Workout" }, { key: "target", label: "Ziel" }, { key: "trophy", label: "Trophy" }, // Gesundheit & Lifestyle { key: "water", label: "Wasser" }, { key: "sleep", label: "Schlafen" }, { key: "vitamins", label: "Vitamine" }, { key: "medicine", label: "Medizin" }, { key: "healthy", label: "Gesund" }, { key: "skincare", label: "Hautpflege" }, { key: "teeth", label: "Zähne" }, { key: "sunrise", label: "Frühaufsteher"}, // Geist & Entspannung { key: "meditation", label: "Meditation" }, { key: "reading", label: "Lesen" }, { key: "journaling", label: "Tagebuch" }, { key: "gratitude", label: "Dankbarkeit" }, { key: "pray", label: "Beten" }, // Produktivität { key: "study", label: "Lernen" }, { key: "coding", label: "Coden" }, { key: "music", label: "Musik" }, { key: "money", label: "Finanzen" }, // Haushalt { key: "cook", label: "Kochen" }, { key: "cleaning", label: "Putzen" }, { key: "laundry", label: "Wäsche" }, // Verzicht { key: "nosmoking", label: "Nicht rauchen"}, { key: "alcohol", label: "Kein Alkohol" }, { key: "nophone", label: "Kein Handy" }, { key: "screentime", label: "Weniger Screen"}, ]; // Fallback: alte icon-keys auf neue mappen const ICON_KEY_MAP = { run: "runner", bike: "bicycle", gym: "dumbbell", swim: "swimming", walk: "walk", yoga: "yoga", stretch: "stretch", other: "activity" }; function resolveIconKey(key) { if (!key) return "activity"; if (ICON_KEY_MAP[key]) return ICON_KEY_MAP[key]; if (HABIT_ICONS.find(i => i.key === key)) return key; return "activity"; } function SportIcon({ iconKey, size, color }) { size = size || 20; const resolved = resolveIconKey(iconKey); // color filter: wenn accent-farbe → CSS filter; sonst grau const isAccent = !color || color === "var(--col-accent)"; const style = { width: size, height: size, display: "block", opacity: isAccent ? 1 : 0.45, filter: isAccent ? "var(--icon-accent-filter, none)" : "none", }; return React.createElement("img", { src: "./habit-icons/" + resolved + ".svg", width: size, height: size, alt: resolved, style, draggable: false }); } function HabitView({ habits, onAdd, onUpdate, onDelete, isMobile }) { const [showForm, setShowForm] = useState(false); const [viewMode, setViewMode] = useState("week"); // FEATURE: Monats-Habittracker const [monthOffset, setMonthOffset] = useState(0); const today = todayISO(); habits = (habits || []).filter(h => !h.deletedAt); // Letzte 7 Tage (älteste zuerst, heute letzte) const DAY_KEYS=["habit.day.sun","habit.day.mon","habit.day.tue","habit.day.wed","habit.day.thu","habit.day.fri","habit.day.sat"]; const days = Array.from({length: 7}, (_, i) => { const d = new Date(); d.setDate(d.getDate() - 6 + i); const iso = localISO(d); return { iso, label: t(DAY_KEYS[d.getDay()]), date: d.getDate(), isToday: iso === today }; }); // Tage des angezeigten Monats (für Monats-Ansicht) const monthBase = new Date(); monthBase.setDate(1); monthBase.setMonth(monthBase.getMonth() + monthOffset); const monthLabel = monthBase.toLocaleDateString(window.currentLang==="en"?"en-GB":"de-CH", {month:"long", year:"numeric"}); const daysInMonth = new Date(monthBase.getFullYear(), monthBase.getMonth()+1, 0).getDate(); const monthDays = Array.from({length: daysInMonth}, (_, i) => { const d = new Date(monthBase.getFullYear(), monthBase.getMonth(), i+1); const iso = localISO(d); return { iso, date: i+1, isToday: iso === today }; }); function isCompleted(habit, iso) { return (habit.completions || []).includes(iso); } function toggleCompletion(habit, iso) { if (iso > today) return; const c = habit.completions || []; onUpdate({...habit, completions: c.includes(iso) ? c.filter(d => d !== iso) : [...c, iso], updatedAt: Date.now() }); } // Returns Monday-ISO of the week containing dateIso function weekOf(dateIso) { const d = new Date(dateIso + "T12:00:00"); const dow = (d.getDay() + 6) % 7; // 0=Mon d.setDate(d.getDate() - dow); return localISO(d); } function getStreak(habit) { const set = new Set(habit.completions || []); let streak = 0, gapUsed = false; // FIXED C3: grace day option added const d = new Date(today + "T12:00:00"); for (let i = 0; i < 365; i++) { if (set.has(localISO(d))) { streak++; } else if (habit.graceDay && !gapUsed) { gapUsed = true; } else { break; } d.setDate(d.getDate() - 1); } return streak; } function getLongest(habit) { const set = new Set(habit.completions || []); if (!set.size) return 0; const earliest = [...set].sort()[0]; const d = new Date(today + "T12:00:00"); let best = 0, cur = 0, gapUsed = false; // FIXED C3: grace day option added while (localISO(d) >= earliest) { if (set.has(localISO(d))) { cur++; best = Math.max(best, cur); } else if (habit.graceDay && !gapUsed) { gapUsed = true; } else { cur = 0; gapUsed = false; } d.setDate(d.getDate() - 1); } return best; } function getDays30(habit) { const set = new Set(habit.completions || []); const base = new Date(today + "T12:00:00"); base.setDate(base.getDate() - 29); let done = 0; for (let i = 0; i < 30; i++) { const d = new Date(base); d.setDate(d.getDate() + i); if (localISO(d) > today) break; if (set.has(localISO(d))) done++; } return done; } const todayDone = habits.filter(h => isCompleted(h, today)).length; const maxStreak = habits.length > 0 ? Math.max(...habits.map(getStreak)) : 0; const maxLongest = habits.length > 0 ? Math.max(...habits.map(getLongest)) : 0; const totalDays30 = habits.length > 0 ? Math.round(habits.reduce((s, h) => s + getDays30(h), 0) / habits.length) : 0; return (
{t("habit.createFirst")}
)} {habits.length > 0 && viewMode==="week" && ( <> {/* Tages-Header */}{t("habit.none")}
)} {habits.map(h=>{ const done=(h.completions||[]).includes(today); return ( ); })}