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",
|
"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",
|
"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": {
|
||||||
|
|||||||
@ -200,6 +200,11 @@ model Task {
|
|||||||
projectId String?
|
projectId String?
|
||||||
kanbanStage String?
|
kanbanStage String?
|
||||||
url String?
|
url String?
|
||||||
|
urgency Boolean?
|
||||||
|
importance Boolean?
|
||||||
|
priority String?
|
||||||
|
delegatedTo String?
|
||||||
|
delegationNote String?
|
||||||
parent Task? @relation("SubTasks", fields: [parentTaskId], references: [id], onDelete: Cascade)
|
parent Task? @relation("SubTasks", fields: [parentTaskId], references: [id], onDelete: Cascade)
|
||||||
subTasks Task[] @relation("SubTasks")
|
subTasks Task[] @relation("SubTasks")
|
||||||
project Project? @relation(fields: [projectId], references: [id])
|
project Project? @relation(fields: [projectId], references: [id])
|
||||||
|
|||||||
@ -247,7 +247,7 @@ export async function POST(request: NextRequest) {
|
|||||||
|
|
||||||
const body = await request.json();
|
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;
|
let { isRolling } = body;
|
||||||
const { isRecurring } = body;
|
const { isRecurring } = body;
|
||||||
|
|
||||||
@ -360,6 +360,11 @@ export async function POST(request: NextRequest) {
|
|||||||
...(projectId !== undefined && { projectId: projectId || null }),
|
...(projectId !== undefined && { projectId: projectId || null }),
|
||||||
...(kanbanStage !== undefined && { kanbanStage: kanbanStage || null }),
|
...(kanbanStage !== undefined && { kanbanStage: kanbanStage || null }),
|
||||||
...(url !== undefined && { url: sanitizeUrl(url) }),
|
...(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 }),
|
...(externalId && { externalId, externalProvider, externalListId }),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@ -404,7 +409,7 @@ export async function PATCH(request: NextRequest) {
|
|||||||
const body = await request.json();
|
const body = await request.json();
|
||||||
|
|
||||||
const { id } = body;
|
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) {
|
if (!id) {
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
@ -497,6 +502,11 @@ export async function PATCH(request: NextRequest) {
|
|||||||
...(kanbanStage !== undefined && { kanbanStage: kanbanStage || null }),
|
...(kanbanStage !== undefined && { kanbanStage: kanbanStage || null }),
|
||||||
...(externalProvider !== undefined && { externalProvider: externalProvider || null }),
|
...(externalProvider !== undefined && { externalProvider: externalProvider || null }),
|
||||||
...(url !== undefined && { url: sanitizeUrl(url) }),
|
...(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";
|
import { WeatherDisplayKey, WEATHER_DISPLAY_DEFAULTS } from "../lib/weeklyViewConstants";
|
||||||
const SettingsSidebar = dynamic(() => import("./SettingsSidebar"), { ssr: false });
|
const SettingsSidebar = dynamic(() => import("./SettingsSidebar"), { ssr: false });
|
||||||
const RichTextEditor = dynamic(() => import("./RichTextEditor"), { ssr: false });
|
const RichTextEditor = dynamic(() => import("./RichTextEditor"), { ssr: false });
|
||||||
|
const PriorityView = dynamic(() => import("./PriorityView"), { ssr: false });
|
||||||
|
|
||||||
// Cookie helpers for per-device settings
|
// Cookie helpers for per-device settings
|
||||||
const DEVICE_SETTINGS_KEYS = ["viewDays", "cellDuration", "startHour", "endHour"];
|
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`;
|
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 {
|
export interface KanbanStage {
|
||||||
id: string;
|
id: string;
|
||||||
@ -168,6 +169,11 @@ export interface Task {
|
|||||||
project?: { id: string; name: string; icon?: string | null; color?: string | null } | null;
|
project?: { id: string; name: string; icon?: string | null; color?: string | null } | null;
|
||||||
kanbanStage?: string | null;
|
kanbanStage?: string | null;
|
||||||
url?: string | null;
|
url?: string | null;
|
||||||
|
urgency?: boolean | null;
|
||||||
|
importance?: boolean | null;
|
||||||
|
priority?: string | null;
|
||||||
|
delegatedTo?: string | null;
|
||||||
|
delegationNote?: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface CalendarEvent {
|
interface CalendarEvent {
|
||||||
@ -5498,8 +5504,9 @@ export default function WeeklyView() {
|
|||||||
{ key: "calendar", icon: <CalendarDays size={13} /> },
|
{ key: "calendar", icon: <CalendarDays size={13} /> },
|
||||||
{ key: "list", icon: <ListTodo size={13} /> },
|
{ key: "list", icon: <ListTodo size={13} /> },
|
||||||
{ key: "kanban", icon: <Kanban size={13} /> },
|
{ key: "kanban", icon: <Kanban size={13} /> },
|
||||||
|
{ key: "priority", icon: <Target size={13} /> },
|
||||||
].map((v) => (
|
].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") }}>
|
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}
|
{v.icon}
|
||||||
</button>
|
</button>
|
||||||
@ -5788,12 +5795,19 @@ export default function WeeklyView() {
|
|||||||
<ListTodo size={16} />
|
<ListTodo size={16} />
|
||||||
</button>
|
</button>
|
||||||
<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"}`}
|
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}
|
title={t.kanbanView}
|
||||||
>
|
>
|
||||||
<Kanban size={16} />
|
<Kanban size={16} />
|
||||||
</button>
|
</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>
|
</div>
|
||||||
|
|
||||||
{/* Days to Show — desktop only (available in sidebar on tablet) */}
|
{/* Days to Show — desktop only (available in sidebar on tablet) */}
|
||||||
@ -6103,8 +6117,22 @@ export default function WeeklyView() {
|
|||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
{/* All-Day Events Section (above position) — hidden in kanban */}
|
{/* All-Day Events Section (above position) — hidden in kanban/priority */}
|
||||||
{profile.viewStyle !== "kanban" && effectiveAllDayPosition === "above" && allDaySection}
|
{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 */}
|
{/* Kanban Board View */}
|
||||||
{profile.viewStyle ==="kanban" && (() => {
|
{profile.viewStyle ==="kanban" && (() => {
|
||||||
@ -7441,7 +7469,7 @@ export default function WeeklyView() {
|
|||||||
</div>}
|
</div>}
|
||||||
|
|
||||||
{/* All-Day Events Section (below position) — hidden in kanban */}
|
{/* 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 */}
|
{/* Someday Section */}
|
||||||
{effectiveShowSomeday && (<>
|
{effectiveShowSomeday && (<>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user