fix: add drag-and-drop from week/someday lists into Priority View

Tasks dragged from any view (simple, list, someday sidebar) now land
in the Priority View:

- Eisenhower: drag onto any quadrant → highlights with dashed border
  + color tint; drops immediately set urgency/importance on the task
- ABCDE: drag onto any grade section (A–E) → same visual feedback;
  drops set the priority grade
- Ivy Lee: drag onto the Top-6 zone → adds task to selection (up to 6)
- TaskCard inside PriorityView is now draggable too, so tasks can be
  re-classified by dragging between quadrants/grades

All drop zones show "Drop here" hint text when a drag is in progress
and collapse-state is respected (collapsed zones still accept drops).
allTasks (unfiltered) passed to sub-components so IDs from external
drags resolve correctly even when the task is filtered out.

v1.87.1
This commit is contained in:
mARTin 2026-04-06 11:01:03 +02:00
parent 46a05a87c3
commit a117b60d84
2 changed files with 98 additions and 14 deletions

View File

@ -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": {

View File

@ -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" && (
<AbcdeView
groups={abcdeGroups}
allTasks={tasks}
darkMode={darkMode}
de={de}
onSetGrade={setAbcde}
@ -571,6 +573,11 @@ function TaskCard({ task, darkMode, cardBg, border, textPrimary, textSecondary,
const hasDelegate = !!task.delegatedTo;
return (
<div
draggable
onDragStart={(e) => {
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: <Trash2 size={14} /> },
];
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<Record<number, boolean>>({});
const [dragOver, setDragOver] = useState<number | null>(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 (
<div>
@ -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 (
<div key={idx} style={{ background: quadBg, borderRadius: "10px", border: `1px solid ${border}`, overflow: "hidden" }}>
<div
key={idx}
onDragOver={(e) => { 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 */}
<div
style={{ display: "flex", alignItems: "center", justifyContent: "space-between", padding: "8px 12px", cursor: "pointer", borderBottom: isCollapsed ? "none" : `1px solid ${border}` }}
@ -692,9 +723,11 @@ function EisenhowerMatrix({ q1, q2, q3, q4, unset, darkMode, de, onSetQuadrant,
</div>
{/* Tasks */}
{!isCollapsed && (
<div style={{ padding: "8px", display: "flex", flexDirection: "column", gap: "6px", maxHeight: "260px", overflowY: "auto" }}>
<div style={{ padding: "8px", display: "flex", flexDirection: "column", gap: "6px", maxHeight: "260px", overflowY: "auto", minHeight: "44px" }}>
{tasks.length === 0 && (
<p style={{ fontSize: "0.75rem", color: textSecondary, textAlign: "center", padding: "8px 0", margin: 0 }}></p>
<p style={{ fontSize: "0.75rem", color: isOver ? qc.color : textSecondary, textAlign: "center", padding: "8px 0", margin: 0 }}>
{isOver ? (de ? "Hier ablegen" : "Drop here") : "—"}
</p>
)}
{tasks.map((t) => (
<TaskCard key={t.id} task={t} darkMode={darkMode} cardBg={cardBg} border={border} textPrimary={textPrimary} textSecondary={textSecondary} onDelegate={onDelegate} onToggleComplete={onToggleComplete} compact>
@ -705,6 +738,12 @@ function EisenhowerMatrix({ q1, q2, q3, q4, unset, darkMode, de, onSetQuadrant,
))}
</div>
)}
{/* Collapsed drop hint */}
{isCollapsed && isOver && (
<div style={{ padding: "6px 12px", fontSize: "0.72rem", color: qc.color, textAlign: "center" }}>
{de ? "Hier ablegen" : "Drop here"}
</div>
)}
</div>
);
})}
@ -714,7 +753,7 @@ function EisenhowerMatrix({ q1, q2, q3, q4, unset, darkMode, de, onSetQuadrant,
{unset.length > 0 && (
<div style={{ background: darkMode ? "#1f2937" : "#f3f4f6", borderRadius: "10px", border: `1px solid ${border}`, padding: "12px" }}>
<p style={{ fontSize: "0.78rem", fontWeight: 600, color: textSecondary, marginBottom: "10px" }}>
{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:`}
</p>
<div style={{ display: "flex", flexDirection: "column", gap: "8px" }}>
{unset.map((t) => (
@ -746,6 +785,7 @@ function EisenhowerMatrix({ q1, q2, q3, q4, unset, darkMode, de, onSetQuadrant,
// ─────────────────────────────────────────────
interface AbcdeViewProps {
groups: Record<string, PriorityTask[]>;
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<Record<string, boolean>>({ none: true });
const [dragOver, setDragOver] = useState<string | null>(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 (
<div style={{ display: "flex", flexDirection: "column", gap: "10px" }}>
{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 (
<div key={grade} style={{ background: bg, borderRadius: "10px", border: `1px solid ${border}`, overflow: "hidden" }}>
<div
key={grade}
onDragOver={(e) => { 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",
}}
>
<div
style={{ display: "flex", alignItems: "center", justifyContent: "space-between", padding: "9px 14px", cursor: "pointer", borderBottom: isCollapsed ? "none" : `1px solid ${border}` }}
onClick={() => setCollapsed((p) => ({ ...p, [grade]: !p[grade] }))}
@ -789,9 +851,11 @@ function AbcdeView({ groups, darkMode, de, onSetGrade, onDelegate, onToggleCompl
</div>
</div>
{!isCollapsed && (
<div style={{ padding: "10px", display: "flex", flexDirection: "column", gap: "6px" }}>
<div style={{ padding: "10px", display: "flex", flexDirection: "column", gap: "6px", minHeight: "36px" }}>
{tasks.length === 0 && (
<p style={{ fontSize: "0.75rem", color: textSecondary, textAlign: "center", padding: "6px 0", margin: 0 }}></p>
<p style={{ fontSize: "0.75rem", color: isOver ? cfg.color : textSecondary, textAlign: "center", padding: "6px 0", margin: 0 }}>
{isOver ? (de ? "Hier ablegen" : "Drop here") : "—"}
</p>
)}
{tasks.map((t) => (
<TaskCard key={t.id} task={t} darkMode={darkMode} cardBg={cardBg} border={border} textPrimary={textPrimary} textSecondary={textSecondary} onDelegate={onDelegate} onToggleComplete={onToggleComplete}>
@ -816,6 +880,11 @@ function AbcdeView({ groups, darkMode, de, onSetGrade, onDelegate, onToggleCompl
))}
</div>
)}
{isCollapsed && isOver && (
<div style={{ padding: "6px 14px", fontSize: "0.72rem", color: cfg.color, textAlign: "center" }}>
{de ? "Hier ablegen" : "Drop here"}
</div>
)}
</div>
);
})}
@ -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 (
<div>
@ -897,10 +976,15 @@ function IvyLeeView({ tasks, selected, darkMode, de, onToggle, onDelegate, onTog
</div>
{/* Selected tasks */}
<div style={{ marginBottom: "16px" }}>
<div
onDragOver={(e) => { 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" }}
>
<div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", marginBottom: "8px" }}>
<span style={{ fontSize: "0.78rem", fontWeight: 700, color: textSecondary, textTransform: "uppercase", letterSpacing: "0.05em" }}>
{de ? "Meine Top-6" : "My Top 6"}
<span style={{ fontSize: "0.78rem", fontWeight: 700, color: dropOverTop6 ? accentColor : textSecondary, textTransform: "uppercase", letterSpacing: "0.05em" }}>
{dropOverTop6 ? (de ? "Hier ablegen" : "Drop to add") : (de ? "Meine Top-6" : "My Top 6")}
</span>
<span style={{ fontSize: "0.78rem", color: selected.size === 6 ? "#059669" : accentColor, fontWeight: 600 }}>
{selected.size}/6