(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 && (
)}
);
}
Object.assign(window, { Spinner, DatePicker, CatPicker, CatManager, RecurringPicker });
})();