(function(){ // ============================================================ // pickers.jsx — Date / Category / Recurring pickers + manager // Components: Spinner, DatePicker, CatPicker, CatManager, RecurringPicker // Depends on (window): todayISO, localISO, fmtDate, getCatColor // ============================================================ const { useState } = React; // ── Spinner ────────────────────────────────────────────────────────── function Spinner(){ return ( ); } // ── DatePicker ─────────────────────────────────────────────────────── function DatePicker({ value, onChange, label, dots = [] }){ const today = todayISO(); const todayDate = new Date(today + "T12:00:00"); const dow0 = (todayDate.getDay() + 6) % 7; // 0=Mon…6=Sun const [weekOffset, setWeekOffset] = useState(() => { if (!value || value === "someday") return 0; try { const valDate = new Date(value + "T12:00:00"); const weekMon = new Date(todayDate); weekMon.setDate(weekMon.getDate() - dow0); const diffDays = Math.round((valDate - weekMon) / 86400000); return Math.floor(diffDays / 14); } catch { return 0; } }); const weekStart = new Date(todayDate); weekStart.setDate(weekStart.getDate() - dow0 + weekOffset * 14); const days = Array.from({length: 14}, (_, i) => { const d = new Date(weekStart); d.setDate(weekStart.getDate() + i); return { iso: localISO(d), day: d.getDate(), wday: d.getDay() }; }); const mCount = {}; days.forEach(({iso}) => { const m = iso.slice(0,7); mCount[m] = (mCount[m]||0)+1; }); const domMonth = Object.entries(mCount).sort((a,b)=>b[1]-a[1])[0]?.[0]; const monthLabel = domMonth ? new Date(domMonth + "-15T12:00:00").toLocaleDateString("de-CH", {month:"long", year:"numeric"}) : ""; const navBtn = {padding:"2px 7px",fontSize:14,border:"1px solid var(--col-border)",borderRadius:4,color:"var(--col-text-sec)",background:"transparent",cursor:"pointer",lineHeight:1,display:"inline-flex",alignItems:"center"}; const dayCell = ({iso, day, wday}) => { const isToday = iso === today; const isSel = iso === value; const isPast = iso < today; const hasDot = dots.includes(iso); const isWknd = wday === 0 || wday === 6; return (
{hasDot && }
); }; return (
{label &&
{label}
}
{monthLabel}
{["Mo","Di","Mi","Do","Fr","Sa","So"].map(l => (
{l}
))}
{days.slice(0,7).map(dayCell)}
{days.slice(7).map(dayCell)}
{value && ( )} {value && value !== "someday" && !days.some(d=>d.iso===value) && ( ✓ {fmtDate(value)} )}
); } // ── CatPicker ──────────────────────────────────────────────────────── function CatPicker({ value, subcategory, onChange, onSubChange, categories, subcategories, onManage }){ const subs = value && subcategories ? subcategories[value] || [] : []; return (
Kategorie
{categories.map(cat => { const cc = getCatColor(categories, cat); const sel = value === cat; return ( ); })}
{subs.length > 0 && (
{subs.map(sub => { const sel = subcategory === sub; return ( ); })}
)}
); } // ── CatManager ─────────────────────────────────────────────────────── function CatManager({ categories, subcategories, categoryIcons, onChange, onSubChange, onIconChange, onClose }){ const [input, setInput] = useState(""); const [expanded, setExpanded] = useState(null); const [subInput, setSubInput] = useState(""); const [dupErr, setDupErr] = useState(false); const add = () => { const val = input.trim(); if (!val) return; if (categories.map(c=>c.toLowerCase()).includes(val.toLowerCase())) { setDupErr(true); setTimeout(() => setDupErr(false), 2000); return; } onChange([...categories, val]); setInput(""); setDupErr(false); }; function addSub(cat){ if (!subInput.trim()) return; const cur = subcategories[cat] || []; if (!cur.includes(subInput.trim())) onSubChange(cat, [...cur, subInput.trim()]); setSubInput(""); } function removeSub(cat, sub){ onSubChange(cat, (subcategories[cat] || []).filter(s => s !== sub)); } return (
e.stopPropagation()}>
Kategorien & Unterkategorien
{categories.map(cat => { const cc = getCatColor(categories, cat); const subs = subcategories[cat] || []; const open = expanded === cat; return (
{categoryIcons?.[cat] && ( )} {cat}
{open && (
{CATEGORY_ICONS.map(icon => { const sel = categoryIcons?.[cat] === icon; return ( ); })}
{subs.map(sub => (
{sub}
))}
setSubInput(e.target.value)} onKeyDown={e => e.key === "Enter" && addSub(cat)} style={{flex:1,fontSize:12}}/>
)}
); })}
{setInput(e.target.value);setDupErr(false);}} onKeyDown={e => e.key === "Enter" && add()} style={{flex:1,fontSize:13,borderColor:dupErr?"#b91c1c":undefined}}/>
{dupErr&&
Kategorie existiert bereits.
}
); } // ── RecurringPicker ────────────────────────────────────────────────── function RecurringPicker({ value, onChange, plannedDay }){ const cur = value || { freq: null, days: [], endDate: null }; const set = patch => onChange(patch.freq === null ? null : { ...cur, ...patch }); const dayNames = ["Mo","Di","Mi","Do","Fr","Sa","So"]; // Wochentag automatisch aus plannedDay ableiten (1=Mo … 7=So) const autoDay = (plannedDay && plannedDay !== "someday") ? ((new Date(plannedDay + "T12:00:00").getDay()) || 7) : null; const autoLabel = autoDay ? dayNames[autoDay - 1] : null; return (
Wiederholen
{[null,"daily","weekly"].map((t, i) => ( ))}
{cur.freq === "weekly" && autoLabel && (
Jeden {autoLabel}
)} {cur.freq === "weekly" && !autoLabel && (
{dayNames.map((d, i) => { const idx = i + 1; const sel = cur.days.includes(idx); return ( ); })}
)} {cur.freq && (
Reihe bis: set({ endDate: e.target.value || null })} style={{fontSize:12,padding:"3px 6px"}}/> {cur.endDate && ( )}
Deadline + set({ deadlineDays: e.target.value !== "" ? parseInt(e.target.value) : null })} style={{fontSize:12,padding:"3px 6px",width:48}}/> Tage {cur.deadlineDays != null && ( )}
)}
); } Object.assign(window, { Spinner, DatePicker, CatPicker, CatManager, RecurringPicker }); })();