diff --git a/package.json b/package.json index 0182eb5..468614b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "my-weekly-todo-list", - "version": "1.89.0", + "version": "1.90.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 1ddbc5e..2098496 100644 --- a/src/components/PriorityView.tsx +++ b/src/components/PriorityView.tsx @@ -1,6 +1,6 @@ "use client"; -import React, { useState, useMemo, useCallback } from "react"; +import React, { useState, useMemo, useCallback, useEffect } from "react"; import { AlertTriangle, Target, @@ -88,6 +88,14 @@ const ABCDE_LABELS_DARK: Record = { E: { bg: "#1f2937" }, }; +const ABCDE_LABELS_DE: Record = { + A: { label: "A — Kritisch", desc: "Muss erledigt werden. Ernste Konsequenzen." }, + B: { label: "B — Sollte erledigt werden", desc: "Leichte Konsequenzen wenn nicht erledigt." }, + C: { label: "C — Schön wenn erledigt", desc: "Keine Konsequenzen wenn nicht erledigt." }, + D: { label: "D — Delegieren", desc: "Kann von jemand anderem erledigt werden." }, + E: { label: "E — Eliminieren", desc: "Streichen — kein echter Mehrwert." }, +}; + function today(): string { return new Date().toISOString().split("T")[0]; } @@ -129,9 +137,17 @@ export default function PriorityView({ const [delegateType, setDelegateType] = useState<"person" | "ai">("person"); const [ivyLeeSelected, setIvyLeeSelected] = useState>(new Set()); const [showFilters, setShowFilters] = useState(false); + const [isMobile, setIsMobile] = useState(false); const de = language === "de"; + useEffect(() => { + const check = () => setIsMobile(window.innerWidth < 768); + check(); + window.addEventListener("resize", check); + return () => window.removeEventListener("resize", check); + }, []); + // --- Filter tasks --- const filteredTasks = useMemo(() => { let result = tasks.filter((t) => !t.id.startsWith("virtual-")); @@ -258,97 +274,108 @@ export default function PriorityView({ const accentColor = "#6366f1"; const methodButtons: { key: PriorityMethod; label: string; icon: React.ReactNode }[] = [ - { key: "eisenhower", label: de ? "Eisenhower" : "Eisenhower", icon: }, + { key: "eisenhower", label: "Eisenhower", icon: }, { key: "abcde", label: "ABCDE", icon: }, - { key: "ivylee", label: de ? "Ivy Lee" : "Ivy Lee", icon: }, - { key: "pareto", label: de ? "80/20" : "80/20", icon: }, + { key: "ivylee", label: "Ivy Lee", icon: }, + { key: "pareto", label: "80/20", icon: }, ]; + // Filter panel — rendered in Eisenhower's right column or above content in other modes + const filterPanel = ( +
+
+ + +
+
+ + +
+
+ + +
+
+ + {filteredTasks.length} {de ? "Aufgaben" : "tasks"} + +
+
+ ); + return ( -
+
{/* ─── Header ─── */} -
+
- - + + {de ? "Prioritäten" : "Priority View"}
-
+ {/* Filter toggle — only for non-Eisenhower modes (Eisenhower shows filters in its right panel) */} + {method !== "eisenhower" && ( -
+ )}
- {/* ─── Filters Panel ─── */} - {showFilters && ( -
-
- - -
-
- - -
-
- - -
-
- - {filteredTasks.length} {de ? "Aufgaben" : "tasks"} - -
+ {/* ─── Filters Panel (non-Eisenhower modes only) ─── */} + {method !== "eisenhower" && showFilters && ( +
+ {filterPanel}
)} {/* ─── Method Tabs ─── */} -
+
{methodButtons.map((m) => ( - ))} -
- - ))} -
- )} + // Ungraded tasks panel (right column) + const hasUngraded = (groups.none || []).length > 0; + const ungradedPanel = ( +
+
setCollapsed((p) => ({ ...p, none: !p.none }))} + > + + {hasUngraded + ? `${de ? "Nicht eingestuft" : "Ungraded"} (${(groups.none || []).length})` + : (de ? "Alle Aufgaben sind eingestuft ✓" : "All tasks graded ✓")} + + {hasUngraded && (collapsed.none ? : )} +
+ {!collapsed.none && hasUngraded && ( +
+ {(groups.none || []).map((t) => ( + +
+ {gradeList.map((g) => ( + + ))} +
+
+ ))}
)}
); + + if (isMobile) { + return ( +
+ {gradeBuckets} + {ungradedPanel} +
+ ); + } + + return ( +
+
{gradeBuckets}
+
{ungradedPanel}
+
+ ); } // ───────────────────────────────────────────── @@ -1024,9 +1106,10 @@ interface IvyLeeViewProps { border: string; textPrimary: string; textSecondary: string; + isMobile?: boolean; } -function IvyLeeView({ tasks, selected, darkMode, de, onToggle, onDelegate, onToggleComplete, onUpdateTask, projects, cardBg, border, textPrimary, textSecondary }: IvyLeeViewProps) { +function IvyLeeView({ tasks, selected, darkMode, de, onToggle, onDelegate, onToggleComplete, onUpdateTask, projects, cardBg, border, textPrimary, textSecondary, isMobile }: IvyLeeViewProps) { const accentColor = "#6366f1"; const selectedTasks = tasks.filter((t) => selected.has(t.id)); const unselected = tasks.filter((t) => !selected.has(t.id)); @@ -1041,96 +1124,116 @@ function IvyLeeView({ tasks, selected, darkMode, de, onToggle, onDelegate, onTog setDropOverTop6(false); }; - return ( -
- {/* Header info */} -
- -
-

- {de ? "Ivy Lee Methode" : "Ivy Lee Method"} -

-

- {de - ? "Wähle genau 6 wichtige Aufgaben für morgen, geordnet nach Priorität." - : "Select exactly 6 important tasks for tomorrow, ordered by priority."} -

-
+ // Info banner + const infoBanner = ( +
+ +
+

+ {de ? "Ivy Lee Methode" : "Ivy Lee Method"} +

+

+ {de + ? "Wähle genau 6 wichtige Aufgaben für morgen, geordnet nach Priorität." + : "Select exactly 6 important tasks for tomorrow, ordered by priority."} +

+
+ ); - {/* Selected tasks */} -
{ 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" }} - > -
- - {dropOverTop6 ? (de ? "Hier ablegen" : "Drop to add") : (de ? "Meine Top-6" : "My Top 6")} - - - {selected.size}/6 - -
-
- {Array.from({ length: 6 }).map((_, i) => { - const t = selectedTasks[i]; - if (t) { - return ( -
- {i + 1} -
- onToggle(t.id)} de={de} /> -
-
- ); - } + // Top-6 selection panel (left column) + const top6Panel = ( +
{ 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={{ borderRadius: "10px", border: dropOverTop6 ? `2px dashed ${accentColor}` : "2px solid transparent", padding: "4px", transition: "border 0.15s" }} + > +
+ + {dropOverTop6 ? (de ? "Hier ablegen" : "Drop to add") : (de ? "Meine Top-6" : "My Top 6")} + + + {selected.size}/6 + +
+
+ {Array.from({ length: 6 }).map((_, i) => { + const t = selectedTasks[i]; + if (t) { return ( -
- {i + 1} -
- {de ? "Leer — wähle eine Aufgabe unten" : "Empty — pick a task below"} +
+ {i + 1} +
+ onToggle(t.id)} de={de} />
); - })} -
-
- - {/* All other tasks to pick from */} - {unselected.length > 0 && ( -
-

- {de ? "Verfügbare Aufgaben:" : "Available tasks:"} -

-
- {unselected.map((t) => ( -
onToggle(t.id)} - style={{ - background: cardBg, - border: `1px solid ${border}`, - borderRadius: "8px", - padding: "8px 12px", - cursor: selected.size >= 6 ? "not-allowed" : "pointer", - opacity: selected.size >= 6 ? 0.5 : 1, - display: "flex", - alignItems: "center", - gap: "8px", - transition: "background 0.1s", - }} - > - - {t.title} - {t.scheduledDate && {formatDate(t.scheduledDate)}} - {t.project && {t.project.name}} + } + return ( +
+ {i + 1} +
+ {de ? "Leer — wähle eine Aufgabe" : "Empty — pick a task"}
- ))} +
+ ); + })} +
+
+ ); + + // Available tasks pool (right column) + const availablePool = ( +
+

+ {unselected.length > 0 ? (de ? "Verfügbare Aufgaben:" : "Available tasks:") : (de ? "Alle Aufgaben ausgewählt ✓" : "All tasks selected ✓")} +

+
+ {unselected.map((t) => ( +
onToggle(t.id)} + style={{ + background: cardBg, + border: `1px solid ${border}`, + borderRadius: "8px", + padding: "8px 12px", + cursor: selected.size >= 6 ? "not-allowed" : "pointer", + opacity: selected.size >= 6 ? 0.5 : 1, + display: "flex", + alignItems: "center", + gap: "8px", + transition: "background 0.1s", + }} + > + + {t.title} + {t.scheduledDate && {formatDate(t.scheduledDate)}} + {t.project && {t.project.name}}
-
- )} + ))} +
+
+ ); + + if (isMobile) { + return ( +
+ {infoBanner} + {top6Panel} + {unselected.length > 0 &&
{availablePool}
} +
+ ); + } + + return ( +
+ {infoBanner} +
+
{top6Panel}
+
{availablePool}
+
); } @@ -1151,17 +1254,17 @@ interface ParetoViewProps { border: string; textPrimary: string; textSecondary: string; + isMobile?: boolean; } -function ParetoView({ important, rest, darkMode, de, onDelegate, onToggleComplete, onUpdateTask, projects, cardBg, border, textPrimary, textSecondary }: ParetoViewProps) { - const [showRest, setShowRest] = useState(false); +function ParetoView({ important, rest, darkMode, de, onDelegate, onToggleComplete, onUpdateTask, projects, cardBg, border, textPrimary, textSecondary, isMobile }: ParetoViewProps) { const total = important.length + rest.length; const pct = total > 0 ? Math.round((important.length / total) * 100) : 0; - return ( -
- {/* Info banner */} -
+ // Info banner + progress bar (full-width header) + const header = ( + <> +

@@ -1169,66 +1272,87 @@ function ParetoView({ important, rest, darkMode, de, onDelegate, onToggleComplet

{de - ? `${important.length} von ${total} Aufgaben (${pct}%) sind als wichtig markiert. Fokussiere dich auf diese, um 80% der Ergebnisse zu erzielen.` - : `${important.length} of ${total} tasks (${pct}%) are marked as important. Focus on these to achieve 80% of your outcomes.`} + ? `${important.length} von ${total} Aufgaben (${pct}%) sind wichtig. Fokussiere dich auf diese für 80% der Ergebnisse.` + : `${important.length} of ${total} tasks (${pct}%) are marked as important. Focus on these to achieve 80% of outcomes.`}

- - {/* Progress bar */} -
+
+ + ); - {/* Important tasks */} -
-
- - {de ? "Wichtige Aufgaben (Fokus)" : "High-Impact Tasks (Focus)"} - - {important.length} -
+ // High-impact tasks (left column) + const importantPanel = ( +
+
+ + {de ? "Wichtige Aufgaben (Fokus)" : "High-Impact Tasks (Focus)"} + + {important.length} +
+
+ {important.length === 0 && ( +

+ {de + ? "Keine wichtigen Aufgaben. Markiere Aufgaben als wichtig im Eisenhower- oder ABCDE-Modus." + : "No important tasks. Mark tasks as important via Eisenhower or ABCDE mode."} +

+ )} + {important.map((t) => ( +
+ onUpdateTask(t.id, { importance: false })} de={de} /> +
+ ))} +
+
+ ); + + // Remaining tasks (right column — always visible, no toggle) + const remainingPanel = ( +
+
+ + {de ? "Restliche Aufgaben" : "Remaining tasks"} + + {rest.length} +
+ {rest.length === 0 ? ( +

{de ? "Keine weiteren Aufgaben." : "No remaining tasks."}

+ ) : (
- {important.length === 0 && ( -

- {de - ? "Keine wichtigen Aufgaben. Markiere Aufgaben als wichtig im Eisenhower- oder ABCDE-Modus." - : "No important tasks. Mark tasks as important via Eisenhower or ABCDE mode."} -

- )} - {important.map((t) => ( -
- onUpdateTask(t.id, { importance: false })} de={de} /> -
+ {rest.map((t) => ( + + + ))}
-
+ )} +
+ ); - {/* The rest (80%) */} + if (isMobile) { + return (
- - {showRest && ( -
- {rest.map((t) => ( - - - - ))} -
- )} + {header} + {importantPanel} +
{remainingPanel}
+
+ ); + } + + return ( +
+ {header} +
+
{importantPanel}
+
{remainingPanel}
);