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",
|
||||
"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": {
|
||||
|
||||
@ -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
|
||||
|
||||
Loading…
Reference in New Issue
Block a user