(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 });
})();