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:
parent
46a05a87c3
commit
a117b60d84
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "my-weekly-todo-list",
|
"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",
|
"description": "A web-based weekly task management application that organizes to-dos and calendar events in a single, intuitive weekly view",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@ -378,6 +378,7 @@ export default function PriorityView({
|
|||||||
q3={eisenhowerQuadrants.q3}
|
q3={eisenhowerQuadrants.q3}
|
||||||
q4={eisenhowerQuadrants.q4}
|
q4={eisenhowerQuadrants.q4}
|
||||||
unset={eisenhowerQuadrants.unset}
|
unset={eisenhowerQuadrants.unset}
|
||||||
|
allTasks={tasks}
|
||||||
darkMode={darkMode}
|
darkMode={darkMode}
|
||||||
de={de}
|
de={de}
|
||||||
onSetQuadrant={setEisenhower}
|
onSetQuadrant={setEisenhower}
|
||||||
@ -395,6 +396,7 @@ export default function PriorityView({
|
|||||||
{method === "abcde" && (
|
{method === "abcde" && (
|
||||||
<AbcdeView
|
<AbcdeView
|
||||||
groups={abcdeGroups}
|
groups={abcdeGroups}
|
||||||
|
allTasks={tasks}
|
||||||
darkMode={darkMode}
|
darkMode={darkMode}
|
||||||
de={de}
|
de={de}
|
||||||
onSetGrade={setAbcde}
|
onSetGrade={setAbcde}
|
||||||
@ -571,6 +573,11 @@ function TaskCard({ task, darkMode, cardBg, border, textPrimary, textSecondary,
|
|||||||
const hasDelegate = !!task.delegatedTo;
|
const hasDelegate = !!task.delegatedTo;
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
draggable
|
||||||
|
onDragStart={(e) => {
|
||||||
|
e.dataTransfer.setData("text/plain", task.id);
|
||||||
|
e.dataTransfer.effectAllowed = "move";
|
||||||
|
}}
|
||||||
style={{
|
style={{
|
||||||
background: task.completed ? (darkMode ? "#111827" : "#f9fafb") : cardBg,
|
background: task.completed ? (darkMode ? "#111827" : "#f9fafb") : cardBg,
|
||||||
border: `1px solid ${border}`,
|
border: `1px solid ${border}`,
|
||||||
@ -581,6 +588,7 @@ function TaskCard({ task, darkMode, cardBg, border, textPrimary, textSecondary,
|
|||||||
gap: "8px",
|
gap: "8px",
|
||||||
opacity: task.completed ? 0.6 : 1,
|
opacity: task.completed ? 0.6 : 1,
|
||||||
transition: "opacity 0.15s",
|
transition: "opacity 0.15s",
|
||||||
|
cursor: "grab",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{/* Checkbox */}
|
{/* Checkbox */}
|
||||||
@ -640,6 +648,7 @@ interface EisenhowerProps {
|
|||||||
q3: PriorityTask[];
|
q3: PriorityTask[];
|
||||||
q4: PriorityTask[];
|
q4: PriorityTask[];
|
||||||
unset: PriorityTask[];
|
unset: PriorityTask[];
|
||||||
|
allTasks: PriorityTask[];
|
||||||
darkMode: boolean;
|
darkMode: boolean;
|
||||||
de: boolean;
|
de: boolean;
|
||||||
onSetQuadrant: (t: PriorityTask, urgency: boolean, importance: boolean) => void;
|
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} /> },
|
{ 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 quadrantTasks = [q1, q2, q3, q4];
|
||||||
const [collapsed, setCollapsed] = useState<Record<number, boolean>>({});
|
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 (
|
return (
|
||||||
<div>
|
<div>
|
||||||
@ -670,9 +688,22 @@ function EisenhowerMatrix({ q1, q2, q3, q4, unset, darkMode, de, onSetQuadrant,
|
|||||||
{QUADRANT_CONFIG.map((qc, idx) => {
|
{QUADRANT_CONFIG.map((qc, idx) => {
|
||||||
const tasks = quadrantTasks[idx];
|
const tasks = quadrantTasks[idx];
|
||||||
const isCollapsed = collapsed[idx];
|
const isCollapsed = collapsed[idx];
|
||||||
|
const isOver = dragOver === idx;
|
||||||
const quadBg = darkMode ? qc.bgDark : qc.bgLight;
|
const quadBg = darkMode ? qc.bgDark : qc.bgLight;
|
||||||
return (
|
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 */}
|
{/* Quadrant header */}
|
||||||
<div
|
<div
|
||||||
style={{ display: "flex", alignItems: "center", justifyContent: "space-between", padding: "8px 12px", cursor: "pointer", borderBottom: isCollapsed ? "none" : `1px solid ${border}` }}
|
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>
|
</div>
|
||||||
{/* Tasks */}
|
{/* Tasks */}
|
||||||
{!isCollapsed && (
|
{!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 && (
|
{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) => (
|
{tasks.map((t) => (
|
||||||
<TaskCard key={t.id} task={t} darkMode={darkMode} cardBg={cardBg} border={border} textPrimary={textPrimary} textSecondary={textSecondary} onDelegate={onDelegate} onToggleComplete={onToggleComplete} compact>
|
<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>
|
</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>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
@ -714,7 +753,7 @@ function EisenhowerMatrix({ q1, q2, q3, q4, unset, darkMode, de, onSetQuadrant,
|
|||||||
{unset.length > 0 && (
|
{unset.length > 0 && (
|
||||||
<div style={{ background: darkMode ? "#1f2937" : "#f3f4f6", borderRadius: "10px", border: `1px solid ${border}`, padding: "12px" }}>
|
<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" }}>
|
<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>
|
</p>
|
||||||
<div style={{ display: "flex", flexDirection: "column", gap: "8px" }}>
|
<div style={{ display: "flex", flexDirection: "column", gap: "8px" }}>
|
||||||
{unset.map((t) => (
|
{unset.map((t) => (
|
||||||
@ -746,6 +785,7 @@ function EisenhowerMatrix({ q1, q2, q3, q4, unset, darkMode, de, onSetQuadrant,
|
|||||||
// ─────────────────────────────────────────────
|
// ─────────────────────────────────────────────
|
||||||
interface AbcdeViewProps {
|
interface AbcdeViewProps {
|
||||||
groups: Record<string, PriorityTask[]>;
|
groups: Record<string, PriorityTask[]>;
|
||||||
|
allTasks: PriorityTask[];
|
||||||
darkMode: boolean;
|
darkMode: boolean;
|
||||||
de: boolean;
|
de: boolean;
|
||||||
onSetGrade: (t: PriorityTask, grade: string | null) => void;
|
onSetGrade: (t: PriorityTask, grade: string | null) => void;
|
||||||
@ -757,11 +797,20 @@ interface AbcdeViewProps {
|
|||||||
textSecondary: string;
|
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 [collapsed, setCollapsed] = useState<Record<string, boolean>>({ none: true });
|
||||||
|
const [dragOver, setDragOver] = useState<string | null>(null);
|
||||||
|
|
||||||
const gradeList = ["A", "B", "C", "D", "E"];
|
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 (
|
return (
|
||||||
<div style={{ display: "flex", flexDirection: "column", gap: "10px" }}>
|
<div style={{ display: "flex", flexDirection: "column", gap: "10px" }}>
|
||||||
{gradeList.map((grade) => {
|
{gradeList.map((grade) => {
|
||||||
@ -769,9 +818,22 @@ function AbcdeView({ groups, darkMode, de, onSetGrade, onDelegate, onToggleCompl
|
|||||||
const cfgDark = ABCDE_LABELS_DARK[grade];
|
const cfgDark = ABCDE_LABELS_DARK[grade];
|
||||||
const tasks = groups[grade] || [];
|
const tasks = groups[grade] || [];
|
||||||
const isCollapsed = collapsed[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 (
|
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
|
<div
|
||||||
style={{ display: "flex", alignItems: "center", justifyContent: "space-between", padding: "9px 14px", cursor: "pointer", borderBottom: isCollapsed ? "none" : `1px solid ${border}` }}
|
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] }))}
|
onClick={() => setCollapsed((p) => ({ ...p, [grade]: !p[grade] }))}
|
||||||
@ -789,9 +851,11 @@ function AbcdeView({ groups, darkMode, de, onSetGrade, onDelegate, onToggleCompl
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{!isCollapsed && (
|
{!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 && (
|
{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) => (
|
{tasks.map((t) => (
|
||||||
<TaskCard key={t.id} task={t} darkMode={darkMode} cardBg={cardBg} border={border} textPrimary={textPrimary} textSecondary={textSecondary} onDelegate={onDelegate} onToggleComplete={onToggleComplete}>
|
<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>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
{isCollapsed && isOver && (
|
||||||
|
<div style={{ padding: "6px 14px", fontSize: "0.72rem", color: cfg.color, textAlign: "center" }}>
|
||||||
|
{de ? "Hier ablegen" : "Drop here"}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
@ -878,6 +947,16 @@ function IvyLeeView({ tasks, selected, darkMode, de, onToggle, onDelegate, onTog
|
|||||||
const accentColor = "#6366f1";
|
const accentColor = "#6366f1";
|
||||||
const selectedTasks = tasks.filter((t) => selected.has(t.id));
|
const selectedTasks = tasks.filter((t) => selected.has(t.id));
|
||||||
const unselected = 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 (
|
return (
|
||||||
<div>
|
<div>
|
||||||
@ -897,10 +976,15 @@ function IvyLeeView({ tasks, selected, darkMode, de, onToggle, onDelegate, onTog
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Selected tasks */}
|
{/* 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" }}>
|
<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" }}>
|
<span style={{ fontSize: "0.78rem", fontWeight: 700, color: dropOverTop6 ? accentColor : textSecondary, textTransform: "uppercase", letterSpacing: "0.05em" }}>
|
||||||
{de ? "Meine Top-6" : "My Top 6"}
|
{dropOverTop6 ? (de ? "Hier ablegen" : "Drop to add") : (de ? "Meine Top-6" : "My Top 6")}
|
||||||
</span>
|
</span>
|
||||||
<span style={{ fontSize: "0.78rem", color: selected.size === 6 ? "#059669" : accentColor, fontWeight: 600 }}>
|
<span style={{ fontSize: "0.78rem", color: selected.size === 6 ? "#059669" : accentColor, fontWeight: 600 }}>
|
||||||
{selected.size}/6
|
{selected.size}/6
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user