From 149f0cf021ba1bcd60a9f55019329371e2d57272 Mon Sep 17 00:00:00 2001 From: mARTin Date: Mon, 6 Apr 2026 12:25:05 +0200 Subject: [PATCH] feat: priority view resize handle + dense task cards with actions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Resize handle: - Drag the divider between priority panel and calendar to resize it - Height persists in cookie (priorityViewHeight) Dense task cards: - Single-row compact layout: [โœ“] date ยท title ยท action icons - Date shown before the title - Action icon bar: rename (โœ), note (๐Ÿ“), link (๐Ÿ”—), project (๐Ÿ“), delegate (๐Ÿ‘ค), remove (ร—) - Inline title editing: double-click or click โœ icon - Inline note textarea: toggles on ๐Ÿ“ click, saves on blur - Inline URL input with open-link button: toggles on ๐Ÿ”— click - Inline project selector: toggles on ๐Ÿ“ click - Icons colored when field has a value (note, url, project, delegate) - Export/import now round-trips url, kanbanStage, recurrenceDays fields v1.88.0 --- package.json | 2 +- src/components/PriorityView.tsx | 267 ++++++++++++++++++++------------ src/components/WeeklyView.tsx | 62 +++++--- 3 files changed, 213 insertions(+), 118 deletions(-) diff --git a/package.json b/package.json index e3a5ca9..e511a4c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "my-weekly-todo-list", - "version": "1.87.2", + "version": "1.88.0", "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 6d01ec9..c233d5e 100644 --- a/src/components/PriorityView.tsx +++ b/src/components/PriorityView.tsx @@ -24,6 +24,10 @@ import { CalendarDays, FolderOpen, List, + Pencil, + FileText, + Link2, + ExternalLink, } from "lucide-react"; export interface PriorityTask { @@ -43,6 +47,7 @@ export interface PriorityTask { dayOfWeek?: number | null; markdownContent?: string | null; startTime?: string | null; + url?: string | null; } export interface PrioritySomedayList { @@ -385,6 +390,8 @@ export default function PriorityView({ onClear={clearEisenhower} onDelegate={openDelegate} onToggleComplete={toggleComplete} + onUpdateTask={onUpdateTask} + projects={projects} cardBg={cardBg} border={border} textPrimary={textPrimary} @@ -402,6 +409,8 @@ export default function PriorityView({ onSetGrade={setAbcde} onDelegate={openDelegate} onToggleComplete={toggleComplete} + onUpdateTask={onUpdateTask} + projects={projects} cardBg={cardBg} border={border} textPrimary={textPrimary} @@ -419,6 +428,8 @@ export default function PriorityView({ onToggle={toggleIvyLee} onDelegate={openDelegate} onToggleComplete={toggleComplete} + onUpdateTask={onUpdateTask} + projects={projects} cardBg={cardBg} border={border} textPrimary={textPrimary} @@ -436,6 +447,7 @@ export default function PriorityView({ onDelegate={openDelegate} onToggleComplete={toggleComplete} onUpdateTask={onUpdateTask} + projects={projects} cardBg={cardBg} border={border} textPrimary={textPrimary} @@ -565,76 +577,161 @@ interface TaskCardProps { textSecondary: string; onDelegate: (t: PriorityTask) => void; onToggleComplete: (t: PriorityTask) => void; + onUpdateTask?: (id: string, fields: Partial) => Promise; + projects?: PriorityProject[]; + onRemove?: () => void; children?: React.ReactNode; - compact?: boolean; + de?: boolean; } -function TaskCard({ task, darkMode, cardBg, border, textPrimary, textSecondary, onDelegate, onToggleComplete, children, compact }: TaskCardProps) { - const hasDelegate = !!task.delegatedTo; +function TaskCard({ task, darkMode, cardBg, border, textPrimary, textSecondary, onDelegate, onToggleComplete, onUpdateTask, projects, onRemove, children, de }: TaskCardProps) { + const [editingTitle, setEditingTitle] = useState(false); + const [titleVal, setTitleVal] = useState(task.title); + const [showNote, setShowNote] = useState(false); + const [noteVal, setNoteVal] = useState(task.markdownContent || ""); + const [showUrl, setShowUrl] = useState(false); + const [urlVal, setUrlVal] = useState(task.url || ""); + const [showProject, setShowProject] = useState(false); + + const saveTitle = () => { + if (onUpdateTask && titleVal.trim() && titleVal.trim() !== task.title) { + onUpdateTask(task.id, { title: titleVal.trim() }); + } + setEditingTitle(false); + }; + + const iconBtn = (onClick: (e: React.MouseEvent) => void, title: string, active: boolean, activeColor: string, icon: React.ReactNode) => ( + + ); + return (
{ - e.dataTransfer.setData("text/plain", task.id); - e.dataTransfer.effectAllowed = "move"; - }} + 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}`, - borderRadius: "8px", - padding: compact ? "7px 10px" : "10px 12px", - display: "flex", - alignItems: "flex-start", - gap: "8px", + borderRadius: "7px", + padding: "4px 6px", opacity: task.completed ? 0.6 : 1, - transition: "opacity 0.15s", cursor: "grab", + display: "flex", + flexDirection: "column", + gap: "2px", }} > - {/* Checkbox */} - - {/* Body */} -
- - {task.title} - -
- {task.scheduledDate && ( - - {formatDate(task.scheduledDate)} - - )} + {/* Main row */} +
+ {/* Complete toggle */} + + {/* Date */} + {task.scheduledDate && ( + + {formatDate(task.scheduledDate)} + + )} + {/* Title */} + {editingTitle ? ( + setTitleVal(e.target.value)} + onKeyDown={(e) => { if (e.key === "Enter") saveTitle(); if (e.key === "Escape") { setTitleVal(task.title); setEditingTitle(false); } }} + onBlur={saveTitle} + autoFocus + onClick={(e) => e.stopPropagation()} + style={{ flex: 1, fontSize: "0.82rem", fontWeight: 500, border: `1px solid ${border}`, borderRadius: "4px", padding: "1px 4px", background: darkMode ? "#374151" : "#fff", color: textPrimary, outline: "none", minWidth: 0 }} + /> + ) : ( + setEditingTitle(true)} + > + {task.title} + + )} + {/* Action icons */} + {iconBtn(() => setEditingTitle(true), de ? "Umbenennen" : "Rename", false, "#6366f1", )} + {iconBtn(() => { setShowNote((v) => !v); if (!showNote) setNoteVal(task.markdownContent || ""); }, de ? "Notiz" : "Note", !!task.markdownContent || showNote, "#6366f1", )} + {iconBtn(() => { setShowUrl((v) => !v); if (!showUrl) setUrlVal(task.url || ""); }, "Link", !!task.url || showUrl, "#3b82f6", )} + {projects && projects.length > 0 && iconBtn(() => setShowProject((v) => !v), de ? "Projekt" : "Project", !!task.projectId || showProject, "#059669", )} + {iconBtn(() => onDelegate(task), de ? "Delegieren" : "Delegate", !!task.delegatedTo, "#8b5cf6", )} + {onRemove && iconBtn(() => onRemove(), de ? "Entfernen" : "Remove", false, "#ef4444", )} +
+ + {/* Badges */} + {(task.project || task.delegatedTo) && ( +
{task.project && ( - + {task.project.name} )} - {hasDelegate && ( - - {task.delegatedTo === "AI" ? : } + {task.delegatedTo && ( + + {task.delegatedTo === "AI" ? : } {task.delegatedTo} )}
- {children} -
- {/* Delegate button */} - + )} + + {/* Note editor */} + {showNote && ( +