feat: add Priority View — Eisenhower, ABCDE, Ivy Lee, Pareto, delegation
New "Priority View" (Target icon in toolbar) fusing four prioritization frameworks into a single, filterable view: - Eisenhower Matrix: 4 quadrants (Do/Schedule/Delegate/Delete) based on urgency × importance; unclassified tasks shown below for quick assignment - ABCDE Method: five collapsible groups (A=Critical → E=Eliminate) with inline grade assignment buttons - Ivy Lee Method: pick exactly 6 tasks for tomorrow, ordered by priority - Pareto 80/20: highlights the important 20% of tasks with a progress bar and one-click "mark as important" for the rest New DB fields: urgency (Boolean), importance (Boolean), priority (A–E), delegatedTo, delegationNote — all persisted via Prisma + API. Filters: by project, someday list (incl. scheduled / unscheduled), and timespan (today / this week / next week / all). Delegation modal: assign to a named person or to AI with optional instruction notes; displayed as inline badge on task cards. v1.87.0
This commit is contained in:
parent
8ffb6a0690
commit
46a05a87c3
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "my-weekly-todo-list",
|
||||
"version": "1.86.0",
|
||||
"version": "1.87.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": {
|
||||
|
||||
@ -200,6 +200,11 @@ model Task {
|
||||
projectId String?
|
||||
kanbanStage String?
|
||||
url String?
|
||||
urgency Boolean?
|
||||
importance Boolean?
|
||||
priority String?
|
||||
delegatedTo String?
|
||||
delegationNote String?
|
||||
parent Task? @relation("SubTasks", fields: [parentTaskId], references: [id], onDelete: Cascade)
|
||||
subTasks Task[] @relation("SubTasks")
|
||||
project Project? @relation(fields: [projectId], references: [id])
|
||||
|
||||
@ -247,7 +247,7 @@ export async function POST(request: NextRequest) {
|
||||
|
||||
const body = await request.json();
|
||||
|
||||
const { title, description, dayOfWeek, order, markdownContent, somedayListId, somedaySlotIndex, startTime, scheduledDate, recurrenceInterval, recurrenceUnit, recurrenceTime, recurrenceEndDate, recurrenceDays, parentTaskId, projectId, kanbanStage, url } = body;
|
||||
const { title, description, dayOfWeek, order, markdownContent, somedayListId, somedaySlotIndex, startTime, scheduledDate, recurrenceInterval, recurrenceUnit, recurrenceTime, recurrenceEndDate, recurrenceDays, parentTaskId, projectId, kanbanStage, url, urgency, importance, priority, delegatedTo, delegationNote } = body;
|
||||
let { isRolling } = body;
|
||||
const { isRecurring } = body;
|
||||
|
||||
@ -360,6 +360,11 @@ export async function POST(request: NextRequest) {
|
||||
...(projectId !== undefined && { projectId: projectId || null }),
|
||||
...(kanbanStage !== undefined && { kanbanStage: kanbanStage || null }),
|
||||
...(url !== undefined && { url: sanitizeUrl(url) }),
|
||||
...(urgency !== undefined && { urgency: urgency === null ? null : Boolean(urgency) }),
|
||||
...(importance !== undefined && { importance: importance === null ? null : Boolean(importance) }),
|
||||
...(priority !== undefined && { priority: priority || null }),
|
||||
...(delegatedTo !== undefined && { delegatedTo: delegatedTo || null }),
|
||||
...(delegationNote !== undefined && { delegationNote: delegationNote || null }),
|
||||
...(externalId && { externalId, externalProvider, externalListId }),
|
||||
},
|
||||
});
|
||||
@ -404,7 +409,7 @@ export async function PATCH(request: NextRequest) {
|
||||
const body = await request.json();
|
||||
|
||||
const { id } = body;
|
||||
const { title, description, completed, dayOfWeek, order, markdownContent, scheduledDate, startTime, somedayListId, somedaySlotIndex, isRecurring, recurrenceInterval, recurrenceUnit, recurrenceTime, recurrenceEndDate, recurrenceDays, restore, parentTaskId, projectId, kanbanStage, externalProvider, url } = body;
|
||||
const { title, description, completed, dayOfWeek, order, markdownContent, scheduledDate, startTime, somedayListId, somedaySlotIndex, isRecurring, recurrenceInterval, recurrenceUnit, recurrenceTime, recurrenceEndDate, recurrenceDays, restore, parentTaskId, projectId, kanbanStage, externalProvider, url, urgency, importance, priority, delegatedTo, delegationNote } = body;
|
||||
|
||||
if (!id) {
|
||||
return NextResponse.json(
|
||||
@ -497,6 +502,11 @@ export async function PATCH(request: NextRequest) {
|
||||
...(kanbanStage !== undefined && { kanbanStage: kanbanStage || null }),
|
||||
...(externalProvider !== undefined && { externalProvider: externalProvider || null }),
|
||||
...(url !== undefined && { url: sanitizeUrl(url) }),
|
||||
...(urgency !== undefined && { urgency: urgency === null ? null : Boolean(urgency) }),
|
||||
...(importance !== undefined && { importance: importance === null ? null : Boolean(importance) }),
|
||||
...(priority !== undefined && { priority: priority || null }),
|
||||
...(delegatedTo !== undefined && { delegatedTo: delegatedTo || null }),
|
||||
...(delegationNote !== undefined && { delegationNote: delegationNote || null }),
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
1080
src/components/PriorityView.tsx
Normal file
1080
src/components/PriorityView.tsx
Normal file
File diff suppressed because it is too large
Load Diff
@ -103,6 +103,7 @@ import { translations } from "../lib/weeklyViewTranslations";
|
||||
import { WeatherDisplayKey, WEATHER_DISPLAY_DEFAULTS } from "../lib/weeklyViewConstants";
|
||||
const SettingsSidebar = dynamic(() => import("./SettingsSidebar"), { ssr: false });
|
||||
const RichTextEditor = dynamic(() => import("./RichTextEditor"), { ssr: false });
|
||||
const PriorityView = dynamic(() => import("./PriorityView"), { ssr: false });
|
||||
|
||||
// Cookie helpers for per-device settings
|
||||
const DEVICE_SETTINGS_KEYS = ["viewDays", "cellDuration", "startHour", "endHour"];
|
||||
@ -119,7 +120,7 @@ function setCookie(name: string, value: string, days: number = 365) {
|
||||
document.cookie = `${name}=${encodeURIComponent(value)}; expires=${expires}; path=/; SameSite=Lax`;
|
||||
}
|
||||
|
||||
export type ViewStyle = "simple" | "calendar" | "list" | "grid" | "kanban";
|
||||
export type ViewStyle = "simple" | "calendar" | "list" | "grid" | "kanban" | "priority";
|
||||
|
||||
export interface KanbanStage {
|
||||
id: string;
|
||||
@ -168,6 +169,11 @@ export interface Task {
|
||||
project?: { id: string; name: string; icon?: string | null; color?: string | null } | null;
|
||||
kanbanStage?: string | null;
|
||||
url?: string | null;
|
||||
urgency?: boolean | null;
|
||||
importance?: boolean | null;
|
||||
priority?: string | null;
|
||||
delegatedTo?: string | null;
|
||||
delegationNote?: string | null;
|
||||
}
|
||||
|
||||
interface CalendarEvent {
|
||||
@ -5498,8 +5504,9 @@ export default function WeeklyView() {
|
||||
{ key: "calendar", icon: <CalendarDays size={13} /> },
|
||||
{ key: "list", icon: <ListTodo size={13} /> },
|
||||
{ key: "kanban", icon: <Kanban size={13} /> },
|
||||
{ key: "priority", icon: <Target size={13} /> },
|
||||
].map((v) => (
|
||||
<button key={v.key} onClick={() => { setViewStyle(v.key as any); saveSetting("viewStyle", v.key); if (v.key === "list") { setShowTimeGrid(false); saveSetting("showTimeGrid", false); } if (v.key === "simple" || v.key === "calendar") { setShowTimeGrid(true); saveSetting("showTimeGrid", true); } }}
|
||||
<button key={v.key} onClick={() => { setViewStyle(v.key as any); saveSetting("viewStyle", v.key); if (v.key === "list") { setShowTimeGrid(false); saveSetting("showTimeGrid", false); } if (v.key === "simple" || v.key === "calendar") { setShowTimeGrid(true); saveSetting("showTimeGrid", true); } if (v.key === "priority" || v.key === "kanban") { setShowTimeGrid(false); saveSetting("showTimeGrid", false); } }}
|
||||
style={{ flex: 1, padding: "5px 0", borderRadius: "6px", border: "none", cursor: "pointer", display: "flex", alignItems: "center", justifyContent: "center", fontWeight: profile.viewStyle ===v.key ? 700 : 400, background: profile.viewStyle ===v.key ? "#0ea5e9" : (darkMode ? "#1f2937" : "#e5e7eb"), color: profile.viewStyle ===v.key ? "#fff" : (darkMode ? "#9ca3af" : "#6b7280") }}>
|
||||
{v.icon}
|
||||
</button>
|
||||
@ -5788,12 +5795,19 @@ export default function WeeklyView() {
|
||||
<ListTodo size={16} />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => { setViewStyle("kanban"); saveSetting("viewStyle", "kanban"); }}
|
||||
onClick={() => { setViewStyle("kanban"); saveSetting("viewStyle", "kanban"); setShowTimeGrid(false); saveSetting("showTimeGrid", false); }}
|
||||
className={`p-1.5 rounded transition-colors ${profile.viewStyle ==="kanban" ? "bg-white shadow-sm text-black dark:bg-gray-700 dark:text-white" : "text-gray-500 hover:text-gray-900 hover:bg-gray-200 dark:hover:bg-gray-700"}`}
|
||||
title={t.kanbanView}
|
||||
>
|
||||
<Kanban size={16} />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => { setViewStyle("priority"); saveSetting("viewStyle", "priority"); setShowTimeGrid(false); saveSetting("showTimeGrid", false); }}
|
||||
className={`p-1.5 rounded transition-colors ${profile.viewStyle ==="priority" ? "bg-white shadow-sm text-black dark:bg-gray-700 dark:text-white" : "text-gray-500 hover:text-gray-900 hover:bg-gray-200 dark:hover:bg-gray-700"}`}
|
||||
title={profile.language === "de" ? "Prioritäten" : "Priority View"}
|
||||
>
|
||||
<Target size={16} />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Days to Show — desktop only (available in sidebar on tablet) */}
|
||||
@ -6103,8 +6117,22 @@ export default function WeeklyView() {
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{/* All-Day Events Section (above position) — hidden in kanban */}
|
||||
{profile.viewStyle !== "kanban" && effectiveAllDayPosition === "above" && allDaySection}
|
||||
{/* All-Day Events Section (above position) — hidden in kanban/priority */}
|
||||
{profile.viewStyle !== "kanban" && profile.viewStyle !== "priority" && effectiveAllDayPosition === "above" && allDaySection}
|
||||
|
||||
{/* Priority View */}
|
||||
{profile.viewStyle === "priority" && (
|
||||
<div style={{ flex: 1, overflowY: "auto" }}>
|
||||
<PriorityView
|
||||
tasks={[...tasks, ...somedayLists.flatMap(l => l.tasks)]}
|
||||
somedayLists={somedayLists.map(l => ({ id: l.id, title: l.title }))}
|
||||
projects={projects}
|
||||
darkMode={darkMode}
|
||||
language={profile.language}
|
||||
onUpdateTask={async (id, fields) => { await updateTaskFields(id, fields as any); }}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Kanban Board View */}
|
||||
{profile.viewStyle ==="kanban" && (() => {
|
||||
@ -7441,7 +7469,7 @@ export default function WeeklyView() {
|
||||
</div>}
|
||||
|
||||
{/* All-Day Events Section (below position) — hidden in kanban */}
|
||||
{profile.viewStyle !== "kanban" && effectiveAllDayPosition === "below" && allDaySection}
|
||||
{profile.viewStyle !== "kanban" && profile.viewStyle !== "priority" && effectiveAllDayPosition === "below" && allDaySection}
|
||||
|
||||
{/* Someday Section */}
|
||||
{effectiveShowSomeday && (<>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user