diff --git a/package.json b/package.json index 4cac9a6..2b4f78a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "my-weekly-todo-list", - "version": "1.87.0", + "version": "1.87.1", "description": "A web-based weekly task management application that organizes to-dos and calendar events in a single, intuitive weekly view", "main": "index.js", "scripts": { diff --git a/src/components/PriorityView.tsx b/src/components/PriorityView.tsx index 5963db0..6d01ec9 100644 --- a/src/components/PriorityView.tsx +++ b/src/components/PriorityView.tsx @@ -378,6 +378,7 @@ export default function PriorityView({ q3={eisenhowerQuadrants.q3} q4={eisenhowerQuadrants.q4} unset={eisenhowerQuadrants.unset} + allTasks={tasks} darkMode={darkMode} de={de} onSetQuadrant={setEisenhower} @@ -395,6 +396,7 @@ export default function PriorityView({ {method === "abcde" && ( { + e.dataTransfer.setData("text/plain", task.id); + e.dataTransfer.effectAllowed = "move"; + }} style={{ background: task.completed ? (darkMode ? "#111827" : "#f9fafb") : cardBg, border: `1px solid ${border}`, @@ -581,6 +588,7 @@ function TaskCard({ task, darkMode, cardBg, border, textPrimary, textSecondary, gap: "8px", opacity: task.completed ? 0.6 : 1, transition: "opacity 0.15s", + cursor: "grab", }} > {/* Checkbox */} @@ -640,6 +648,7 @@ interface EisenhowerProps { q3: PriorityTask[]; q4: PriorityTask[]; unset: PriorityTask[]; + allTasks: PriorityTask[]; darkMode: boolean; de: boolean; onSetQuadrant: (t: PriorityTask, urgency: boolean, importance: boolean) => void; @@ -659,9 +668,18 @@ const QUADRANT_CONFIG = [ { urgency: false, importance: false, label: "Delete", labelDe: "Eliminieren", desc: "Neither", descDe: "Weder noch", color: "#6b7280", bgLight: "#f9fafb", bgDark: "#1f2937", icon: }, ]; -function EisenhowerMatrix({ q1, q2, q3, q4, unset, darkMode, de, onSetQuadrant, onClear, onDelegate, onToggleComplete, cardBg, border, textPrimary, textSecondary }: EisenhowerProps) { +function EisenhowerMatrix({ q1, q2, q3, q4, unset, allTasks, darkMode, de, onSetQuadrant, onClear, onDelegate, onToggleComplete, cardBg, border, textPrimary, textSecondary }: EisenhowerProps) { const quadrantTasks = [q1, q2, q3, q4]; const [collapsed, setCollapsed] = useState>({}); + const [dragOver, setDragOver] = useState(null); + + const handleQuadrantDrop = (e: React.DragEvent, qc: typeof QUADRANT_CONFIG[0]) => { + e.preventDefault(); + const taskId = e.dataTransfer.getData("text/plain"); + const task = allTasks.find((t) => t.id === taskId); + if (task) onSetQuadrant(task, qc.urgency, qc.importance); + setDragOver(null); + }; return (
@@ -670,9 +688,22 @@ function EisenhowerMatrix({ q1, q2, q3, q4, unset, darkMode, de, onSetQuadrant, {QUADRANT_CONFIG.map((qc, idx) => { const tasks = quadrantTasks[idx]; const isCollapsed = collapsed[idx]; + const isOver = dragOver === idx; const quadBg = darkMode ? qc.bgDark : qc.bgLight; return ( -
+
{ e.preventDefault(); e.dataTransfer.dropEffect = "move"; setDragOver(idx); }} + onDragLeave={(e) => { if (!e.currentTarget.contains(e.relatedTarget as Node)) setDragOver(null); }} + onDrop={(e) => handleQuadrantDrop(e, qc)} + style={{ + background: isOver ? `${qc.color}30` : quadBg, + borderRadius: "10px", + border: isOver ? `2px dashed ${qc.color}` : `1px solid ${border}`, + overflow: "hidden", + transition: "border 0.15s, background 0.15s", + }} + > {/* Quadrant header */}
{/* Tasks */} {!isCollapsed && ( -
+
{tasks.length === 0 && ( -

+

+ {isOver ? (de ? "Hier ablegen" : "Drop here") : "—"} +

)} {tasks.map((t) => ( @@ -705,6 +738,12 @@ function EisenhowerMatrix({ q1, q2, q3, q4, unset, darkMode, de, onSetQuadrant, ))}
)} + {/* Collapsed drop hint */} + {isCollapsed && isOver && ( +
+ {de ? "Hier ablegen" : "Drop here"} +
+ )}
); })} @@ -714,7 +753,7 @@ function EisenhowerMatrix({ q1, q2, q3, q4, unset, darkMode, de, onSetQuadrant, {unset.length > 0 && (

- {de ? `${unset.length} nicht eingestufte Aufgaben — klicke, um sie einzustufen:` : `${unset.length} unclassified tasks — click to assign a quadrant:`} + {de ? `${unset.length} nicht eingestufte Aufgaben — drag in ein Quadrant oder klicke:` : `${unset.length} unclassified tasks — drag into a quadrant or click to assign:`}

{unset.map((t) => ( @@ -746,6 +785,7 @@ function EisenhowerMatrix({ q1, q2, q3, q4, unset, darkMode, de, onSetQuadrant, // ───────────────────────────────────────────── interface AbcdeViewProps { groups: Record; + allTasks: PriorityTask[]; darkMode: boolean; de: boolean; onSetGrade: (t: PriorityTask, grade: string | null) => void; @@ -757,11 +797,20 @@ interface AbcdeViewProps { textSecondary: string; } -function AbcdeView({ groups, darkMode, de, onSetGrade, onDelegate, onToggleComplete, cardBg, border, textPrimary, textSecondary }: AbcdeViewProps) { +function AbcdeView({ groups, allTasks, darkMode, de, onSetGrade, onDelegate, onToggleComplete, cardBg, border, textPrimary, textSecondary }: AbcdeViewProps) { const [collapsed, setCollapsed] = useState>({ none: true }); + const [dragOver, setDragOver] = useState(null); const gradeList = ["A", "B", "C", "D", "E"]; + const handleGradeDrop = (e: React.DragEvent, grade: string) => { + e.preventDefault(); + const taskId = e.dataTransfer.getData("text/plain"); + const task = allTasks.find((t) => t.id === taskId); + if (task) onSetGrade(task, grade); + setDragOver(null); + }; + return (
{gradeList.map((grade) => { @@ -769,9 +818,22 @@ function AbcdeView({ groups, darkMode, de, onSetGrade, onDelegate, onToggleCompl const cfgDark = ABCDE_LABELS_DARK[grade]; const tasks = groups[grade] || []; const isCollapsed = collapsed[grade]; - const bg = darkMode ? cfgDark.bg : cfg.bg; + const isOver = dragOver === grade; + const bg = isOver ? `${cfg.color}28` : (darkMode ? cfgDark.bg : cfg.bg); return ( -
+
{ e.preventDefault(); e.dataTransfer.dropEffect = "move"; setDragOver(grade); }} + onDragLeave={(e) => { if (!e.currentTarget.contains(e.relatedTarget as Node)) setDragOver(null); }} + onDrop={(e) => handleGradeDrop(e, grade)} + style={{ + background: bg, + borderRadius: "10px", + border: isOver ? `2px dashed ${cfg.color}` : `1px solid ${border}`, + overflow: "hidden", + transition: "border 0.15s, background 0.15s", + }} + >
setCollapsed((p) => ({ ...p, [grade]: !p[grade] }))} @@ -789,9 +851,11 @@ function AbcdeView({ groups, darkMode, de, onSetGrade, onDelegate, onToggleCompl
{!isCollapsed && ( -
+
{tasks.length === 0 && ( -

+

+ {isOver ? (de ? "Hier ablegen" : "Drop here") : "—"} +

)} {tasks.map((t) => ( @@ -816,6 +880,11 @@ function AbcdeView({ groups, darkMode, de, onSetGrade, onDelegate, onToggleCompl ))}
)} + {isCollapsed && isOver && ( +
+ {de ? "Hier ablegen" : "Drop here"} +
+ )}
); })} @@ -878,6 +947,16 @@ function IvyLeeView({ tasks, selected, darkMode, de, onToggle, onDelegate, onTog const accentColor = "#6366f1"; const selectedTasks = tasks.filter((t) => selected.has(t.id)); const unselected = tasks.filter((t) => !selected.has(t.id)); + const [dropOverTop6, setDropOverTop6] = useState(false); + + const handleTop6Drop = (e: React.DragEvent) => { + e.preventDefault(); + const taskId = e.dataTransfer.getData("text/plain"); + if (taskId && !selected.has(taskId) && selected.size < 6) { + onToggle(taskId); + } + setDropOverTop6(false); + }; return (
@@ -897,10 +976,15 @@ function IvyLeeView({ tasks, selected, darkMode, de, onToggle, onDelegate, onTog
{/* Selected tasks */} -
+
{ e.preventDefault(); e.dataTransfer.dropEffect = selected.size < 6 ? "move" : "none"; setDropOverTop6(true); }} + onDragLeave={(e) => { if (!e.currentTarget.contains(e.relatedTarget as Node)) setDropOverTop6(false); }} + onDrop={handleTop6Drop} + style={{ marginBottom: "16px", borderRadius: "10px", border: dropOverTop6 ? `2px dashed ${accentColor}` : "2px solid transparent", padding: "4px", transition: "border 0.15s" }} + >
- - {de ? "Meine Top-6" : "My Top 6"} + + {dropOverTop6 ? (de ? "Hier ablegen" : "Drop to add") : (de ? "Meine Top-6" : "My Top 6")} {selected.size}/6