(function(){ // ============================================================ // forms.jsx — NewTodoForm + TodoItem + SlotAdder // Depends on (window): DatePicker, CatPicker, RecurringPicker, // parseDuration, cleanTitle, todayISO, fmtDate, getCatColor // ============================================================ const { useState, useEffect } = React; // ── NewTodoForm ────────────────────────────────────────────────────── function NewTodoForm({ onAdd, categories, subcategories, onManageCats, initialText="", hideDate=false }){ const [text, setText] = useState(""); const [expanded, setExpanded] = useState(false); const [plannedDay, setPlannedDay] = useState(null); const [deadline, setDeadline] = useState(null); const [category, setCategory] = useState(null); const [subcategory, setSubcategory] = useState(null); const [priority, setPriority] = useState("mittel"); const [recurring, setRecurring] = useState(null); const [subInput, setSubInput] = useState(""); const [subtasks, setSubtasks] = useState([]); const [comInput, setComInput] = useState(""); const [comments, setComments] = useState([]); // FIXED: Vorlagen chips above form, prefill on click useEffect(() => { if (initialText) { setText(initialText); setExpanded(true); } }, [initialText]); const addSub = () => { if (!subInput.trim()) return; setSubtasks(s => [...s, { id: Date.now(), text: subInput.trim(), done: false }]); setSubInput(""); }; function submit(){ if (!text.trim()) return; const dur = parseDuration(text); const t = dur ? cleanTitle(text) : text.trim(); onAdd({ id: Date.now(), createdAt: Date.now(), updatedAt: Date.now(), text: t, done: false, priority, plannedDay: hideDate ? null : plannedDay, deadline, category, subcategory, // FIXED: Inbox hides date field, sets plannedDay null subtasks, comments, duration: dur, recurring }); setText(""); setPlannedDay(null); setDeadline(null); setCategory(null); setSubcategory(null); setPriority("mittel"); setRecurring(null); setSubtasks([]); setComments([]); setSubInput(""); setComInput(""); setExpanded(false); } return (
{ setText(e.target.value); setExpanded(!!e.target.value); }} onKeyDown={e => e.key === "Enter" && submit()} style={{flex:1,fontSize:14,border:"none",background:"transparent",outline:"none",color:"var(--col-text)"}}/> {text.trim() && ( )}
{expanded && text.trim() && (
{window.t("form.priority")}
{["hoch","mittel","niedrig"].map(p => ( ))}
{!hideDate&&(
)}
{window.t("form.subtasks")}
{subtasks.map((s, i) => (
{s.text}
))}
setSubInput(e.target.value)} onKeyDown={e => e.key === "Enter" && addSub()} style={{flex:1,fontSize:12}}/>
{window.t("form.comment")}
setComInput(e.target.value)} onKeyDown={e => { if (e.key === "Enter" && comInput.trim()) { setComments(cs => [...cs, { id: Date.now(), text: comInput.trim() }]); setComInput(""); } }} style={{flex:1,fontSize:12}}/>
{comments.map((c, i) => (
{c.text}
))}
)}
); } // ── TodoItem ───────────────────────────────────────────────────────── function TodoItem({ t, onToggle, onDelete, onUpdate, onDeleteTemplate, categories, subcategories, onSync, isDragging, isDragOver, onDragStart, onDragOver, onDrop, onDragEnd, activeTimerId, timerRunning=false, liveElapsed=0, onStartTimer, onStopTimer, onDuplicate, onSaveTemplate, snoozeBadge=false }){ const [expanded, setExpanded] = useState(false); const [editing, setEditing] = useState(false); const [completing, setCompleting] = useState(false); function handleToggle(){ if (t.done) { onToggle(); return; } setCompleting(true); setTimeout(() => { setCompleting(false); onToggle(); }, 380); } const [editText, setEditText] = useState(t.text); const [editDeadline, setEditDeadline] = useState(t.deadline || null); const [editPlanned, setEditPlanned] = useState(t.plannedDay || null); const [editPrio, setEditPrio] = useState(t.priority || "mittel"); const [editRecurring, setEditRecurring] = useState(t.recurring || null); const [editSubs, setEditSubs] = useState(t.subtasks || []); const [editSubInput, setEditSubInput] = useState(""); const [editComments, setEditComments] = useState(t.comments || []); const [editComInput, setEditComInput] = useState(""); const [editCategory, setEditCategory] = useState(t.category || null); const [editSubcategory, setEditSubcategory] = useState(t.subcategory || null); const [editSnooze, setEditSnooze] = useState(t.snoozedUntil || null); const today = todayISO(); const isOverdue = t.deadline && t.deadline < today; // Timer const isTimerActive = activeTimerId === t.id; const totalMs = isTimerActive ? liveElapsed : (t.timeLog||[]).reduce((sum,e)=>{ const end=e.end||e.start; return sum+(end-e.start); },0); function fmtMs(ms){ const s=Math.floor(ms/1000),h=Math.floor(s/3600),m=Math.floor((s%3600)/60),sc=s%60; return h>0?`${h}:${String(m).padStart(2,'0')}:${String(sc).padStart(2,'0')}`:`${m}:${String(sc).padStart(2,'0')}`; } const durLabel = t.duration ? (t.duration >= 60 ? `${Math.floor(t.duration/60)}h${t.duration%60 ? t.duration%60 + "m" : ""}` : `${t.duration}m`) : null; const doneSubs = t.subtasks?.filter(s => s.done).length || 0; const totalSubs = t.subtasks?.length || 0; function startEdit(){ setEditText(t.text); setEditDeadline(t.deadline || null); setEditPlanned(t.plannedDay || null); setEditPrio(t.priority || "mittel"); setEditSubs(t.subtasks || []); setEditSubInput(""); setEditComments(t.comments || []); setEditComInput(""); setEditCategory(t.category || null); setEditSubcategory(t.subcategory || null); setEditRecurring(t.recurring || null); setEditSnooze(t.snoozedUntil || null); setEditing(true); } function saveEdit(){ const rawText = editText.trim() || t.text; const newDur = parseDuration(rawText); const newText = newDur ? cleanTitle(rawText) : rawText; onUpdate({ ...t, text: newText, duration: newDur !== null ? newDur : t.duration, deadline: editDeadline, plannedDay: editPlanned, priority: editPrio, subtasks: editSubs, comments: editComments, category: editCategory, subcategory: editSubcategory, recurring: editRecurring, snoozedUntil: editSnooze || null }); setEditing(false); } function addEditSub(){ if (!editSubInput.trim()) return; setEditSubs(s => [...s, { id: Date.now(), text: editSubInput.trim(), done: false }]); setEditSubInput(""); } function addEditCom(){ if (!editComInput.trim()) return; setEditComments(cs => [...cs, { id: Date.now(), text: editComInput.trim() }]); setEditComInput(""); } function toggleSub(sid){ onUpdate({ ...t, subtasks: t.subtasks.map(s => s.id === sid ? { ...s, done: !s.done } : s) }); } return (
{ e.dataTransfer.effectAllowed = 'move'; try { e.dataTransfer.setData('text/todo-id', String(t.id)); } catch {} try { e.dataTransfer.setData('text/plain', t.text || ''); } catch {} onDragStart && onDragStart(); }} onDragOver={e => { e.preventDefault(); onDragOver && onDragOver(); }} onDrop={e => { e.preventDefault(); onDrop && onDrop(); }} onDragEnd={onDragEnd} className={completing ? "todo-complete" : ""} style={{ background:"var(--col-bg)", borderBottom: isDragOver ? "2px solid var(--col-accent)" : "1px solid var(--col-border-soft)", borderTop:"none", borderLeft:"none", borderRight:"none", borderRadius:0, marginBottom:0, overflow:"hidden", opacity: isDragging ? 0.4 : 1, transition:"opacity 0.15s, background 0.15s", position:"relative" }}> {isOverdue && }
!editing && setExpanded(x => !x)}>
{t.text}
{(durLabel || t.recurring || totalSubs > 0 || t.category || t.subcategory || t.deadline || (t.plannedDay && t.plannedDay !== today) || (snoozeBadge && t.snoozedUntil)) && (
{durLabel && ( {durLabel} )} {t.deadline && ( {fmtDate(t.deadline)} )} {t.plannedDay === "someday" && ( {window.t("todoitem.someday")} )} {t.plannedDay && t.plannedDay !== today && t.plannedDay !== "someday" && ( {fmtDate(t.plannedDay)} )} {t.category && (() => { const cc = getCatColor(categories, t.category); return ( {t.category}{t.subcategory ? · {t.subcategory} : null} ); })()} {(t.recurring || t._fromTemplate) && ( {window.t("todoitem.recurring")}{(()=>{ const r=t.recurring; if(!r||!(r.deadlineDays>0))return""; const base=t.plannedDay&&t.plannedDay!=="someday"?t.plannedDay:null; if(!base)return` · DL +${r.deadlineDays}T`; const d=new Date(base+"T12:00:00");d.setDate(d.getDate()+r.deadlineDays); return` · DL ${fmtDate(localISO(d))}`; })()} )} {totalSubs > 0 && ( {doneSubs}/{totalSubs} {window.t("todoitem.steps")} )} {(isTimerActive||totalMs>0)&&( {isTimerActive&&} {fmtMs(totalMs)} )} {t._calMissing && ( {window.t("todoitem.calMissing")} )} {t._calDrift && !t._calMissing && ( {window.t("todoitem.calDrift")} )} {snoozeBadge && t.snoozedUntil && ( {window.t("todoitem.wakeAt",{date:t.snoozedUntil})} )}
)}
{t.priority && t.priority !== "mittel" && ( )} {onStartTimer && !t.done && ( )}
{expanded && !editing && (
{totalSubs > 0 && (
{t.subtasks.map(s => (
{s.text}
))}
)} {t.comments?.length > 0 && (
{/* FIXED E7: comment block max-height added */} {t.comments.map(c =>
{c.text}
)}
)}
{onDuplicate&&( )} {onSaveTemplate&&( )} {onSync && ( )} {t._fromTemplate && onDeleteTemplate && ( )}
)} {editing && (
{if(e.key==="Escape"){const changed=editText!==t.text||editDeadline!==(t.deadline||null)||editPlanned!==(t.plannedDay||null)||editPrio!==(t.priority||"mittel")||editCategory!==(t.category||null);if(changed&&!confirm(window.t("form.discardConfirm")))return;setEditing(false);}}}> setEditText(e.target.value)} onKeyDown={e => e.key === "Enter" && saveEdit()} style={{width:"100%",boxSizing:"border-box",fontSize:13,marginBottom:8}}/>
{["hoch","mittel","niedrig"].map(p => ( ))}
{/* FIXED: Wiedervorlage hint text added */}
{window.t("form.snoozeHint")}
{ setEditCategory(v); setEditSubcategory(null); }} onSubChange={setEditSubcategory} categories={categories} subcategories={subcategories || {}} onManage={() => {}}/>
{!t._fromTemplate&&(
)} {/* Unteraufgaben bearbeiten */}
{window.t("form.subtasks")}
{editSubs.map((s, i) => (
{s.text}
))}
setEditSubInput(e.target.value)} onKeyDown={e => e.key === "Enter" && addEditSub()} style={{flex:1,fontSize:12}}/>
{/* Kommentare bearbeiten */}
{window.t("form.comments")}
{editComments.map((c, i) => (
{c.text}
))}
setEditComInput(e.target.value)} onKeyDown={e => e.key === "Enter" && addEditCom()} style={{flex:1,fontSize:12}}/>
)}
); } Object.assign(window, { NewTodoForm, TodoItem }); })();