feat: two-column priority layout + German ABCDE translations
All four priority modes (Eisenhower, ABCDE, Ivy Lee, Pareto) now use a two-column layout on desktop: main content on the left, secondary panel (unassigned tasks / pool / remaining) on the right. On mobile (<768px) the layout stacks vertically automatically. Eisenhower: filters are now always visible in the right panel instead of behind a toggle button, making project/list/timespan selection more accessible. The filter button is hidden for Eisenhower mode. ABCDE: added full German translations for grade labels and descriptions (A–E now show "Kritisch", "Sollte erledigt werden", etc. in German). Ungraded tasks appear in the right column instead of below the buckets. Pareto: remaining tasks (80%) are always visible in the right column, no longer behind a show/hide toggle. v1.90.0
This commit is contained in:
parent
0a84bf3114
commit
3ee5bf33b5
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "my-weekly-todo-list",
|
"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",
|
"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": {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import React, { useState, useMemo, useCallback } from "react";
|
import React, { useState, useMemo, useCallback, useEffect } from "react";
|
||||||
import {
|
import {
|
||||||
AlertTriangle,
|
AlertTriangle,
|
||||||
Target,
|
Target,
|
||||||
@ -88,6 +88,14 @@ const ABCDE_LABELS_DARK: Record<string, { bg: string }> = {
|
|||||||
E: { bg: "#1f2937" },
|
E: { bg: "#1f2937" },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const ABCDE_LABELS_DE: Record<string, { label: string; desc: string }> = {
|
||||||
|
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 {
|
function today(): string {
|
||||||
return new Date().toISOString().split("T")[0];
|
return new Date().toISOString().split("T")[0];
|
||||||
}
|
}
|
||||||
@ -129,9 +137,17 @@ export default function PriorityView({
|
|||||||
const [delegateType, setDelegateType] = useState<"person" | "ai">("person");
|
const [delegateType, setDelegateType] = useState<"person" | "ai">("person");
|
||||||
const [ivyLeeSelected, setIvyLeeSelected] = useState<Set<string>>(new Set());
|
const [ivyLeeSelected, setIvyLeeSelected] = useState<Set<string>>(new Set());
|
||||||
const [showFilters, setShowFilters] = useState(false);
|
const [showFilters, setShowFilters] = useState(false);
|
||||||
|
const [isMobile, setIsMobile] = useState(false);
|
||||||
|
|
||||||
const de = language === "de";
|
const de = language === "de";
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const check = () => setIsMobile(window.innerWidth < 768);
|
||||||
|
check();
|
||||||
|
window.addEventListener("resize", check);
|
||||||
|
return () => window.removeEventListener("resize", check);
|
||||||
|
}, []);
|
||||||
|
|
||||||
// --- Filter tasks ---
|
// --- Filter tasks ---
|
||||||
const filteredTasks = useMemo(() => {
|
const filteredTasks = useMemo(() => {
|
||||||
let result = tasks.filter((t) => !t.id.startsWith("virtual-"));
|
let result = tasks.filter((t) => !t.id.startsWith("virtual-"));
|
||||||
@ -258,97 +274,108 @@ export default function PriorityView({
|
|||||||
const accentColor = "#6366f1";
|
const accentColor = "#6366f1";
|
||||||
|
|
||||||
const methodButtons: { key: PriorityMethod; label: string; icon: React.ReactNode }[] = [
|
const methodButtons: { key: PriorityMethod; label: string; icon: React.ReactNode }[] = [
|
||||||
{ key: "eisenhower", label: de ? "Eisenhower" : "Eisenhower", icon: <Grid size={14} /> },
|
{ key: "eisenhower", label: "Eisenhower", icon: <Grid size={14} /> },
|
||||||
{ key: "abcde", label: "ABCDE", icon: <Tag size={14} /> },
|
{ key: "abcde", label: "ABCDE", icon: <Tag size={14} /> },
|
||||||
{ key: "ivylee", label: de ? "Ivy Lee" : "Ivy Lee", icon: <ListOrdered size={14} /> },
|
{ key: "ivylee", label: "Ivy Lee", icon: <ListOrdered size={14} /> },
|
||||||
{ key: "pareto", label: de ? "80/20" : "80/20", icon: <BarChart2 size={14} /> },
|
{ key: "pareto", label: "80/20", icon: <BarChart2 size={14} /> },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// Filter panel — rendered in Eisenhower's right column or above content in other modes
|
||||||
|
const filterPanel = (
|
||||||
|
<div style={{ background: cardBg, border: `1px solid ${border}`, borderRadius: "10px", padding: "10px", display: "flex", flexWrap: "wrap", gap: "8px" }}>
|
||||||
|
<div style={{ display: "flex", flexDirection: "column", gap: "3px", flex: "1 1 120px" }}>
|
||||||
|
<label style={{ fontSize: "0.68rem", fontWeight: 600, color: textSecondary, textTransform: "uppercase", letterSpacing: "0.05em" }}>
|
||||||
|
<FolderOpen size={9} style={{ display: "inline", marginRight: "3px" }} />
|
||||||
|
{de ? "Projekt" : "Project"}
|
||||||
|
</label>
|
||||||
|
<select
|
||||||
|
value={filterProject}
|
||||||
|
onChange={(e) => setFilterProject(e.target.value)}
|
||||||
|
style={{ padding: "4px 7px", borderRadius: "6px", border: `1px solid ${border}`, background: darkMode ? "#374151" : "#f3f4f6", color: textPrimary, fontSize: "0.8rem", cursor: "pointer" }}
|
||||||
|
>
|
||||||
|
<option value="">{de ? "Alle Projekte" : "All projects"}</option>
|
||||||
|
{projects.map((p) => (
|
||||||
|
<option key={p.id} value={p.id}>{p.name}</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div style={{ display: "flex", flexDirection: "column", gap: "3px", flex: "1 1 130px" }}>
|
||||||
|
<label style={{ fontSize: "0.68rem", fontWeight: 600, color: textSecondary, textTransform: "uppercase", letterSpacing: "0.05em" }}>
|
||||||
|
<List size={9} style={{ display: "inline", marginRight: "3px" }} />
|
||||||
|
{de ? "Liste" : "List"}
|
||||||
|
</label>
|
||||||
|
<select
|
||||||
|
value={filterList}
|
||||||
|
onChange={(e) => setFilterList(e.target.value)}
|
||||||
|
style={{ padding: "4px 7px", borderRadius: "6px", border: `1px solid ${border}`, background: darkMode ? "#374151" : "#f3f4f6", color: textPrimary, fontSize: "0.8rem", cursor: "pointer" }}
|
||||||
|
>
|
||||||
|
<option value="">{de ? "Alle Listen" : "All lists"}</option>
|
||||||
|
<option value="__scheduled__">{de ? "Geplante Aufgaben" : "Scheduled"}</option>
|
||||||
|
<option value="__unscheduled__">{de ? "Ungeplant" : "Unscheduled"}</option>
|
||||||
|
{somedayLists.map((l) => (
|
||||||
|
<option key={l.id} value={l.id}>{l.title}</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div style={{ display: "flex", flexDirection: "column", gap: "3px", flex: "1 1 120px" }}>
|
||||||
|
<label style={{ fontSize: "0.68rem", fontWeight: 600, color: textSecondary, textTransform: "uppercase", letterSpacing: "0.05em" }}>
|
||||||
|
<CalendarDays size={9} style={{ display: "inline", marginRight: "3px" }} />
|
||||||
|
{de ? "Zeitraum" : "Timespan"}
|
||||||
|
</label>
|
||||||
|
<select
|
||||||
|
value={filterTimespan}
|
||||||
|
onChange={(e) => setFilterTimespan(e.target.value)}
|
||||||
|
style={{ padding: "4px 7px", borderRadius: "6px", border: `1px solid ${border}`, background: darkMode ? "#374151" : "#f3f4f6", color: textPrimary, fontSize: "0.8rem", cursor: "pointer" }}
|
||||||
|
>
|
||||||
|
<option value="all">{de ? "Alle" : "All time"}</option>
|
||||||
|
<option value="today">{de ? "Heute" : "Today"}</option>
|
||||||
|
<option value="this-week">{de ? "Diese Woche" : "This week"}</option>
|
||||||
|
<option value="next-week">{de ? "Nächste Woche" : "Next week"}</option>
|
||||||
|
<option value="unscheduled">{de ? "Ungeplant" : "Unscheduled"}</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div style={{ display: "flex", alignItems: "flex-end", paddingBottom: "2px" }}>
|
||||||
|
<span style={{ fontSize: "0.75rem", color: textSecondary }}>
|
||||||
|
{filteredTasks.length} {de ? "Aufgaben" : "tasks"}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ background: bg, minHeight: "100%", padding: "16px 12px", boxSizing: "border-box", fontFamily: "inherit" }}>
|
<div style={{ background: bg, minHeight: "100%", padding: "12px", boxSizing: "border-box", fontFamily: "inherit" }}>
|
||||||
{/* ─── Header ─── */}
|
{/* ─── Header ─── */}
|
||||||
<div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", marginBottom: "16px", flexWrap: "wrap", gap: "8px" }}>
|
<div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", marginBottom: "10px", flexWrap: "wrap", gap: "8px" }}>
|
||||||
<div style={{ display: "flex", alignItems: "center", gap: "8px" }}>
|
<div style={{ display: "flex", alignItems: "center", gap: "8px" }}>
|
||||||
<Target size={20} style={{ color: accentColor }} />
|
<Target size={18} style={{ color: accentColor }} />
|
||||||
<span style={{ fontSize: "1.1rem", fontWeight: 700, color: textPrimary }}>
|
<span style={{ fontSize: "1rem", fontWeight: 700, color: textPrimary }}>
|
||||||
{de ? "Prioritäten" : "Priority View"}
|
{de ? "Prioritäten" : "Priority View"}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: "flex", gap: "6px", alignItems: "center" }}>
|
{/* Filter toggle — only for non-Eisenhower modes (Eisenhower shows filters in its right panel) */}
|
||||||
|
{method !== "eisenhower" && (
|
||||||
<button
|
<button
|
||||||
onClick={() => setShowFilters((v) => !v)}
|
onClick={() => setShowFilters((v) => !v)}
|
||||||
style={{ display: "flex", alignItems: "center", gap: "4px", padding: "5px 10px", borderRadius: "7px", border: `1px solid ${border}`, background: showFilters ? accentColor : cardBg, color: showFilters ? "#fff" : textSecondary, cursor: "pointer", fontSize: "0.8rem" }}
|
style={{ display: "flex", alignItems: "center", gap: "4px", padding: "5px 10px", borderRadius: "7px", border: `1px solid ${border}`, background: showFilters ? accentColor : cardBg, color: showFilters ? "#fff" : textSecondary, cursor: "pointer", fontSize: "0.8rem" }}
|
||||||
>
|
>
|
||||||
<SlidersHorizontal size={13} />
|
<SlidersHorizontal size={13} />
|
||||||
{de ? "Filter" : "Filter"}
|
{de ? "Filter" : "Filter"}
|
||||||
|
{(filterProject || filterList || filterTimespan !== "all") && (
|
||||||
|
<span style={{ background: "#ef4444", color: "#fff", borderRadius: "99px", fontSize: "0.65rem", padding: "0 5px", fontWeight: 700 }}>!</span>
|
||||||
|
)}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* ─── Filters Panel ─── */}
|
{/* ─── Filters Panel (non-Eisenhower modes only) ─── */}
|
||||||
{showFilters && (
|
{method !== "eisenhower" && showFilters && (
|
||||||
<div style={{ background: cardBg, border: `1px solid ${border}`, borderRadius: "10px", padding: "12px", marginBottom: "16px", display: "flex", flexWrap: "wrap", gap: "10px" }}>
|
<div style={{ marginBottom: "12px" }}>
|
||||||
<div style={{ display: "flex", flexDirection: "column", gap: "4px", minWidth: "140px" }}>
|
{filterPanel}
|
||||||
<label style={{ fontSize: "0.7rem", fontWeight: 600, color: textSecondary, textTransform: "uppercase", letterSpacing: "0.05em" }}>
|
|
||||||
<FolderOpen size={10} style={{ display: "inline", marginRight: "3px" }} />
|
|
||||||
{de ? "Projekt" : "Project"}
|
|
||||||
</label>
|
|
||||||
<select
|
|
||||||
value={filterProject}
|
|
||||||
onChange={(e) => setFilterProject(e.target.value)}
|
|
||||||
style={{ padding: "5px 8px", borderRadius: "6px", border: `1px solid ${border}`, background: darkMode ? "#374151" : "#f3f4f6", color: textPrimary, fontSize: "0.82rem", cursor: "pointer" }}
|
|
||||||
>
|
|
||||||
<option value="">{de ? "Alle Projekte" : "All projects"}</option>
|
|
||||||
{projects.map((p) => (
|
|
||||||
<option key={p.id} value={p.id}>{p.name}</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div style={{ display: "flex", flexDirection: "column", gap: "4px", minWidth: "160px" }}>
|
|
||||||
<label style={{ fontSize: "0.7rem", fontWeight: 600, color: textSecondary, textTransform: "uppercase", letterSpacing: "0.05em" }}>
|
|
||||||
<List size={10} style={{ display: "inline", marginRight: "3px" }} />
|
|
||||||
{de ? "Liste" : "List"}
|
|
||||||
</label>
|
|
||||||
<select
|
|
||||||
value={filterList}
|
|
||||||
onChange={(e) => setFilterList(e.target.value)}
|
|
||||||
style={{ padding: "5px 8px", borderRadius: "6px", border: `1px solid ${border}`, background: darkMode ? "#374151" : "#f3f4f6", color: textPrimary, fontSize: "0.82rem", cursor: "pointer" }}
|
|
||||||
>
|
|
||||||
<option value="">{de ? "Alle Listen" : "All lists"}</option>
|
|
||||||
<option value="__scheduled__">{de ? "Geplante Aufgaben" : "Scheduled tasks"}</option>
|
|
||||||
<option value="__unscheduled__">{de ? "Ungeplant" : "Unscheduled"}</option>
|
|
||||||
{somedayLists.map((l) => (
|
|
||||||
<option key={l.id} value={l.id}>{l.title}</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div style={{ display: "flex", flexDirection: "column", gap: "4px", minWidth: "140px" }}>
|
|
||||||
<label style={{ fontSize: "0.7rem", fontWeight: 600, color: textSecondary, textTransform: "uppercase", letterSpacing: "0.05em" }}>
|
|
||||||
<CalendarDays size={10} style={{ display: "inline", marginRight: "3px" }} />
|
|
||||||
{de ? "Zeitraum" : "Timespan"}
|
|
||||||
</label>
|
|
||||||
<select
|
|
||||||
value={filterTimespan}
|
|
||||||
onChange={(e) => setFilterTimespan(e.target.value)}
|
|
||||||
style={{ padding: "5px 8px", borderRadius: "6px", border: `1px solid ${border}`, background: darkMode ? "#374151" : "#f3f4f6", color: textPrimary, fontSize: "0.82rem", cursor: "pointer" }}
|
|
||||||
>
|
|
||||||
<option value="all">{de ? "Alle" : "All time"}</option>
|
|
||||||
<option value="today">{de ? "Heute" : "Today"}</option>
|
|
||||||
<option value="this-week">{de ? "Diese Woche" : "This week"}</option>
|
|
||||||
<option value="next-week">{de ? "Nächste Woche" : "Next week"}</option>
|
|
||||||
<option value="unscheduled">{de ? "Ungeplant" : "Unscheduled"}</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div style={{ display: "flex", alignItems: "flex-end" }}>
|
|
||||||
<span style={{ fontSize: "0.78rem", color: textSecondary, paddingBottom: "6px" }}>
|
|
||||||
{filteredTasks.length} {de ? "Aufgaben" : "tasks"}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* ─── Method Tabs ─── */}
|
{/* ─── Method Tabs ─── */}
|
||||||
<div style={{ display: "flex", gap: "4px", marginBottom: "20px", background: darkMode ? "#1f2937" : "#e5e7eb", borderRadius: "9px", padding: "3px" }}>
|
<div style={{ display: "flex", gap: "4px", marginBottom: "14px", background: darkMode ? "#1f2937" : "#e5e7eb", borderRadius: "9px", padding: "3px" }}>
|
||||||
{methodButtons.map((m) => (
|
{methodButtons.map((m) => (
|
||||||
<button
|
<button
|
||||||
key={m.key}
|
key={m.key}
|
||||||
@ -398,6 +425,8 @@ export default function PriorityView({
|
|||||||
border={border}
|
border={border}
|
||||||
textPrimary={textPrimary}
|
textPrimary={textPrimary}
|
||||||
textSecondary={textSecondary}
|
textSecondary={textSecondary}
|
||||||
|
isMobile={isMobile}
|
||||||
|
filterPanel={filterPanel}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@ -417,6 +446,7 @@ export default function PriorityView({
|
|||||||
border={border}
|
border={border}
|
||||||
textPrimary={textPrimary}
|
textPrimary={textPrimary}
|
||||||
textSecondary={textSecondary}
|
textSecondary={textSecondary}
|
||||||
|
isMobile={isMobile}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@ -436,6 +466,7 @@ export default function PriorityView({
|
|||||||
border={border}
|
border={border}
|
||||||
textPrimary={textPrimary}
|
textPrimary={textPrimary}
|
||||||
textSecondary={textSecondary}
|
textSecondary={textSecondary}
|
||||||
|
isMobile={isMobile}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@ -454,6 +485,7 @@ export default function PriorityView({
|
|||||||
border={border}
|
border={border}
|
||||||
textPrimary={textPrimary}
|
textPrimary={textPrimary}
|
||||||
textSecondary={textSecondary}
|
textSecondary={textSecondary}
|
||||||
|
isMobile={isMobile}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@ -760,16 +792,18 @@ interface EisenhowerProps {
|
|||||||
border: string;
|
border: string;
|
||||||
textPrimary: string;
|
textPrimary: string;
|
||||||
textSecondary: string;
|
textSecondary: string;
|
||||||
|
isMobile?: boolean;
|
||||||
|
filterPanel?: React.ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
const QUADRANT_CONFIG = [
|
const QUADRANT_CONFIG = [
|
||||||
{ urgency: true, importance: true, label: "Do", labelDe: "Sofort erledigen", desc: "Urgent & Important", descDe: "Dringend & Wichtig", color: "#ef4444", bgLight: "#fef2f2", bgDark: "#450a0a", icon: <AlertTriangle size={14} /> },
|
{ urgency: true, importance: true, label: "Do", labelDe: "Sofort erledigen", desc: "Urgent & Important", descDe: "Dringend & Wichtig", color: "#ef4444", bgLight: "#fef2f2", bgDark: "#450a0a", icon: <AlertTriangle size={14} /> },
|
||||||
{ urgency: false, importance: true, label: "Schedule", labelDe: "Planen", desc: "Important, Not Urgent", descDe: "Wichtig, nicht dringend", color: "#3b82f6", bgLight: "#eff6ff", bgDark: "#1e3a5f", icon: <Clock size={14} /> },
|
{ urgency: false, importance: true, label: "Schedule", labelDe: "Planen", desc: "Important, Not Urgent", descDe: "Wichtig, nicht dringend", color: "#3b82f6", bgLight: "#eff6ff", bgDark: "#1e3a5f", icon: <Clock size={14} /> },
|
||||||
{ urgency: true, importance: false, label: "Delegate", labelDe: "Delegieren", desc: "Urgent, Not Important", descDe: "Dringend, nicht wichtig", color: "#f97316", bgLight: "#fff7ed", bgDark: "#431407", icon: <User size={14} /> },
|
{ urgency: true, importance: false, label: "Delegate", labelDe: "Delegieren", desc: "Urgent, Not Important", descDe: "Dringend, nicht wichtig", color: "#f97316", bgLight: "#fff7ed", bgDark: "#431407", icon: <User size={14} /> },
|
||||||
{ urgency: false, importance: false, label: "Delete", labelDe: "Eliminieren", desc: "Neither", descDe: "Weder noch", color: "#6b7280", bgLight: "#f9fafb", bgDark: "#1f2937", icon: <Trash2 size={14} /> },
|
{ 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, allTasks, darkMode, de, onSetQuadrant, onClear, onDelegate, onToggleComplete, onUpdateTask, projects, cardBg, border, textPrimary, textSecondary }: EisenhowerProps) {
|
function EisenhowerMatrix({ q1, q2, q3, q4, unset, allTasks, darkMode, de, onSetQuadrant, onClear, onDelegate, onToggleComplete, onUpdateTask, projects, cardBg, border, textPrimary, textSecondary, isMobile, filterPanel }: EisenhowerProps) {
|
||||||
const quadrantTasks = [q1, q2, q3, q4];
|
const quadrantTasks = [q1, q2, q3, q4];
|
||||||
const [collapsed, setCollapsed] = useState<Record<number, boolean>>({});
|
const [collapsed, setCollapsed] = useState<Record<number, boolean>>({});
|
||||||
const [dragOver, setDragOver] = useState<number | null>(null);
|
const [dragOver, setDragOver] = useState<number | null>(null);
|
||||||
@ -782,77 +816,85 @@ function EisenhowerMatrix({ q1, q2, q3, q4, unset, allTasks, darkMode, de, onSet
|
|||||||
setDragOver(null);
|
setDragOver(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
// 2×2 quadrant grid
|
||||||
<div>
|
const quadrantGrid = (
|
||||||
{/* Axis labels */}
|
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: "10px" }}>
|
||||||
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: "12px", marginBottom: "12px" }}>
|
{QUADRANT_CONFIG.map((qc, idx) => {
|
||||||
{QUADRANT_CONFIG.map((qc, idx) => {
|
const tasks = quadrantTasks[idx];
|
||||||
const tasks = quadrantTasks[idx];
|
const isCollapsed = collapsed[idx];
|
||||||
const isCollapsed = collapsed[idx];
|
const isOver = dragOver === idx;
|
||||||
const isOver = dragOver === idx;
|
const quadBg = darkMode ? qc.bgDark : qc.bgLight;
|
||||||
const quadBg = darkMode ? qc.bgDark : qc.bgLight;
|
return (
|
||||||
return (
|
<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
|
<div
|
||||||
key={idx}
|
style={{ display: "flex", alignItems: "center", justifyContent: "space-between", padding: "8px 12px", cursor: "pointer", borderBottom: isCollapsed ? "none" : `1px solid ${border}` }}
|
||||||
onDragOver={(e) => { e.preventDefault(); e.dataTransfer.dropEffect = "move"; setDragOver(idx); }}
|
onClick={() => setCollapsed((p) => ({ ...p, [idx]: !p[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", gap: "6px" }}>
|
||||||
<div
|
<span style={{ color: qc.color }}>{qc.icon}</span>
|
||||||
style={{ display: "flex", alignItems: "center", justifyContent: "space-between", padding: "8px 12px", cursor: "pointer", borderBottom: isCollapsed ? "none" : `1px solid ${border}` }}
|
<div>
|
||||||
onClick={() => setCollapsed((p) => ({ ...p, [idx]: !p[idx] }))}
|
<div style={{ fontSize: "0.82rem", fontWeight: 700, color: qc.color }}>{de ? qc.labelDe : qc.label}</div>
|
||||||
>
|
<div style={{ fontSize: "0.68rem", color: textSecondary }}>{de ? qc.descDe : qc.desc}</div>
|
||||||
<div style={{ display: "flex", alignItems: "center", gap: "6px" }}>
|
|
||||||
<span style={{ color: qc.color }}>{qc.icon}</span>
|
|
||||||
<div>
|
|
||||||
<div style={{ fontSize: "0.82rem", fontWeight: 700, color: qc.color }}>{de ? qc.labelDe : qc.label}</div>
|
|
||||||
<div style={{ fontSize: "0.68rem", color: textSecondary }}>{de ? qc.descDe : qc.desc}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div style={{ display: "flex", alignItems: "center", gap: "6px" }}>
|
|
||||||
<span style={{ fontSize: "0.75rem", fontWeight: 600, color: qc.color, background: `${qc.color}22`, borderRadius: "9px", padding: "1px 7px" }}>{tasks.length}</span>
|
|
||||||
{isCollapsed ? <ChevronDown size={14} style={{ color: textSecondary }} /> : <ChevronUp size={14} style={{ color: textSecondary }} />}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/* Tasks */}
|
<div style={{ display: "flex", alignItems: "center", gap: "6px" }}>
|
||||||
{!isCollapsed && (
|
<span style={{ fontSize: "0.75rem", fontWeight: 600, color: qc.color, background: `${qc.color}22`, borderRadius: "9px", padding: "1px 7px" }}>{tasks.length}</span>
|
||||||
<div style={{ padding: "8px", display: "flex", flexDirection: "column", gap: "6px", maxHeight: "260px", overflowY: "auto", minHeight: "44px" }}>
|
{isCollapsed ? <ChevronDown size={14} style={{ color: textSecondary }} /> : <ChevronUp size={14} style={{ color: textSecondary }} />}
|
||||||
{tasks.length === 0 && (
|
</div>
|
||||||
<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} onUpdateTask={onUpdateTask} projects={projects} onRemove={() => onClear(t)} de={de} />
|
|
||||||
))}
|
|
||||||
</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>
|
</div>
|
||||||
);
|
{/* Tasks */}
|
||||||
})}
|
{!isCollapsed && (
|
||||||
</div>
|
<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: 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} onUpdateTask={onUpdateTask} projects={projects} onRemove={() => onClear(t)} de={de} />
|
||||||
|
))}
|
||||||
|
</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>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
// Right panel: filters + unassigned tasks
|
||||||
|
const rightPanel = (
|
||||||
|
<div style={{ display: "flex", flexDirection: "column", gap: "10px" }}>
|
||||||
|
{/* Filters always visible in Eisenhower right column */}
|
||||||
|
{filterPanel}
|
||||||
|
|
||||||
{/* Unassigned tasks */}
|
{/* Unassigned tasks */}
|
||||||
{unset.length > 0 && (
|
<div style={{ background: darkMode ? "#1f2937" : "#f3f4f6", borderRadius: "10px", border: `1px solid ${border}`, padding: "12px" }}>
|
||||||
<div style={{ background: darkMode ? "#1f2937" : "#f3f4f6", borderRadius: "10px", border: `1px solid ${border}`, padding: "12px" }}>
|
<p style={{ fontSize: "0.78rem", fontWeight: 600, color: textSecondary, marginBottom: unset.length > 0 ? "10px" : 0, margin: unset.length > 0 ? undefined : 0 }}>
|
||||||
<p style={{ fontSize: "0.78rem", fontWeight: 600, color: textSecondary, marginBottom: "10px" }}>
|
{unset.length > 0
|
||||||
{de ? `${unset.length} nicht eingestufte Aufgaben — drag in ein Quadrant oder klicke:` : `${unset.length} unclassified tasks — drag into a quadrant or click to assign:`}
|
? (de ? `${unset.length} nicht eingestufte Aufgaben — in Quadrant ziehen oder klicken:` : `${unset.length} unclassified — drag to a quadrant or click to assign:`)
|
||||||
</p>
|
: (de ? "Alle Aufgaben sind eingestuft ✓" : "All tasks classified ✓")}
|
||||||
<div style={{ display: "flex", flexDirection: "column", gap: "8px" }}>
|
</p>
|
||||||
|
{unset.length > 0 && (
|
||||||
|
<div style={{ display: "flex", flexDirection: "column", gap: "8px", maxHeight: "400px", overflowY: "auto" }}>
|
||||||
{unset.map((t) => (
|
{unset.map((t) => (
|
||||||
<div key={t.id}>
|
<div key={t.id}>
|
||||||
<TaskCard task={t} darkMode={darkMode} cardBg={cardBg} border={border} textPrimary={textPrimary} textSecondary={textSecondary} onDelegate={onDelegate} onToggleComplete={onToggleComplete} onUpdateTask={onUpdateTask} projects={projects} de={de}>
|
<TaskCard task={t} darkMode={darkMode} cardBg={cardBg} border={border} textPrimary={textPrimary} textSecondary={textSecondary} onDelegate={onDelegate} onToggleComplete={onToggleComplete} onUpdateTask={onUpdateTask} projects={projects} de={de}>
|
||||||
@ -871,8 +913,26 @@ function EisenhowerMatrix({ q1, q2, q3, q4, unset, allTasks, darkMode, de, onSet
|
|||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
)}
|
||||||
)}
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
// Mobile: stack vertically
|
||||||
|
if (isMobile) {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{quadrantGrid}
|
||||||
|
<div style={{ marginTop: "12px" }}>{rightPanel}</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Desktop: quadrants left (60%), filter+unassigned right (40%)
|
||||||
|
return (
|
||||||
|
<div style={{ display: "flex", gap: "14px", alignItems: "flex-start" }}>
|
||||||
|
<div style={{ flex: 3, minWidth: 0 }}>{quadrantGrid}</div>
|
||||||
|
<div style={{ flex: 2, minWidth: "220px", maxWidth: "400px" }}>{rightPanel}</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -894,9 +954,10 @@ interface AbcdeViewProps {
|
|||||||
border: string;
|
border: string;
|
||||||
textPrimary: string;
|
textPrimary: string;
|
||||||
textSecondary: string;
|
textSecondary: string;
|
||||||
|
isMobile?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
function AbcdeView({ groups, allTasks, darkMode, de, onSetGrade, onDelegate, onToggleComplete, onUpdateTask, projects, cardBg, border, textPrimary, textSecondary }: AbcdeViewProps) {
|
function AbcdeView({ groups, allTasks, darkMode, de, onSetGrade, onDelegate, onToggleComplete, onUpdateTask, projects, cardBg, border, textPrimary, textSecondary, isMobile }: AbcdeViewProps) {
|
||||||
const [collapsed, setCollapsed] = useState<Record<string, boolean>>({ none: true });
|
const [collapsed, setCollapsed] = useState<Record<string, boolean>>({ none: true });
|
||||||
const [dragOver, setDragOver] = useState<string | null>(null);
|
const [dragOver, setDragOver] = useState<string | null>(null);
|
||||||
|
|
||||||
@ -910,10 +971,12 @@ function AbcdeView({ groups, allTasks, darkMode, de, onSetGrade, onDelegate, onT
|
|||||||
setDragOver(null);
|
setDragOver(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
// A–E grade buckets
|
||||||
|
const gradeBuckets = (
|
||||||
<div style={{ display: "flex", flexDirection: "column", gap: "10px" }}>
|
<div style={{ display: "flex", flexDirection: "column", gap: "10px" }}>
|
||||||
{gradeList.map((grade) => {
|
{gradeList.map((grade) => {
|
||||||
const cfg = ABCDE_LABELS[grade];
|
const cfg = ABCDE_LABELS[grade];
|
||||||
|
const cfgDe = ABCDE_LABELS_DE[grade];
|
||||||
const cfgDark = ABCDE_LABELS_DARK[grade];
|
const cfgDark = ABCDE_LABELS_DARK[grade];
|
||||||
const tasks = groups[grade] || [];
|
const tasks = groups[grade] || [];
|
||||||
const isCollapsed = collapsed[grade];
|
const isCollapsed = collapsed[grade];
|
||||||
@ -940,8 +1003,8 @@ function AbcdeView({ groups, allTasks, darkMode, de, onSetGrade, onDelegate, onT
|
|||||||
<div style={{ display: "flex", alignItems: "center", gap: "8px" }}>
|
<div style={{ display: "flex", alignItems: "center", gap: "8px" }}>
|
||||||
<span style={{ fontWeight: 800, fontSize: "1.1rem", color: cfg.color, lineHeight: 1 }}>{grade}</span>
|
<span style={{ fontWeight: 800, fontSize: "1.1rem", color: cfg.color, lineHeight: 1 }}>{grade}</span>
|
||||||
<div>
|
<div>
|
||||||
<div style={{ fontSize: "0.82rem", fontWeight: 700, color: cfg.color }}>{cfg.label}</div>
|
<div style={{ fontSize: "0.82rem", fontWeight: 700, color: cfg.color }}>{de ? cfgDe.label : cfg.label}</div>
|
||||||
<div style={{ fontSize: "0.68rem", color: textSecondary }}>{cfg.desc}</div>
|
<div style={{ fontSize: "0.68rem", color: textSecondary }}>{de ? cfgDe.desc : cfg.desc}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: "flex", alignItems: "center", gap: "6px" }}>
|
<div style={{ display: "flex", alignItems: "center", gap: "6px" }}>
|
||||||
@ -969,42 +1032,61 @@ function AbcdeView({ groups, allTasks, darkMode, de, onSetGrade, onDelegate, onT
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
{/* Ungraded */}
|
// Ungraded tasks panel (right column)
|
||||||
{(groups.none || []).length > 0 && (
|
const hasUngraded = (groups.none || []).length > 0;
|
||||||
<div style={{ background: darkMode ? "#1f2937" : "#f3f4f6", borderRadius: "10px", border: `1px solid ${border}`, overflow: "hidden" }}>
|
const ungradedPanel = (
|
||||||
<div
|
<div style={{ background: darkMode ? "#1f2937" : "#f3f4f6", borderRadius: "10px", border: `1px solid ${border}`, overflow: "hidden" }}>
|
||||||
style={{ display: "flex", alignItems: "center", justifyContent: "space-between", padding: "9px 14px", cursor: "pointer", borderBottom: collapsed.none ? "none" : `1px solid ${border}` }}
|
<div
|
||||||
onClick={() => setCollapsed((p) => ({ ...p, none: !p.none }))}
|
style={{ display: "flex", alignItems: "center", justifyContent: "space-between", padding: "9px 14px", cursor: "pointer", borderBottom: collapsed.none ? "none" : `1px solid ${border}` }}
|
||||||
>
|
onClick={() => setCollapsed((p) => ({ ...p, none: !p.none }))}
|
||||||
<span style={{ fontSize: "0.82rem", fontWeight: 600, color: textSecondary }}>
|
>
|
||||||
{de ? "Nicht eingestuft" : "Ungraded"} ({(groups.none || []).length})
|
<span style={{ fontSize: "0.82rem", fontWeight: 600, color: textSecondary }}>
|
||||||
</span>
|
{hasUngraded
|
||||||
{collapsed.none ? <ChevronDown size={14} style={{ color: textSecondary }} /> : <ChevronUp size={14} style={{ color: textSecondary }} />}
|
? `${de ? "Nicht eingestuft" : "Ungraded"} (${(groups.none || []).length})`
|
||||||
</div>
|
: (de ? "Alle Aufgaben sind eingestuft ✓" : "All tasks graded ✓")}
|
||||||
{!collapsed.none && (
|
</span>
|
||||||
<div style={{ padding: "10px", display: "flex", flexDirection: "column", gap: "6px" }}>
|
{hasUngraded && (collapsed.none ? <ChevronDown size={14} style={{ color: textSecondary }} /> : <ChevronUp size={14} style={{ color: textSecondary }} />)}
|
||||||
{(groups.none || []).map((t) => (
|
</div>
|
||||||
<TaskCard key={t.id} task={t} darkMode={darkMode} cardBg={cardBg} border={border} textPrimary={textPrimary} textSecondary={textSecondary} onDelegate={onDelegate} onToggleComplete={onToggleComplete} onUpdateTask={onUpdateTask} projects={projects} de={de}>
|
{!collapsed.none && hasUngraded && (
|
||||||
<div style={{ display: "flex", gap: "3px", marginTop: "5px", flexWrap: "wrap" }}>
|
<div style={{ padding: "10px", display: "flex", flexDirection: "column", gap: "6px", maxHeight: "500px", overflowY: "auto" }}>
|
||||||
{gradeList.map((g) => (
|
{(groups.none || []).map((t) => (
|
||||||
<button
|
<TaskCard key={t.id} task={t} darkMode={darkMode} cardBg={cardBg} border={border} textPrimary={textPrimary} textSecondary={textSecondary} onDelegate={onDelegate} onToggleComplete={onToggleComplete} onUpdateTask={onUpdateTask} projects={projects} de={de}>
|
||||||
key={g}
|
<div style={{ display: "flex", gap: "3px", marginTop: "5px", flexWrap: "wrap" }}>
|
||||||
onClick={() => onSetGrade(t, g)}
|
{gradeList.map((g) => (
|
||||||
style={{ fontSize: "0.65rem", padding: "1px 6px", borderRadius: "4px", border: `1px solid ${ABCDE_LABELS[g].color}`, background: `${ABCDE_LABELS[g].color}18`, color: ABCDE_LABELS[g].color, cursor: "pointer", fontWeight: 700 }}
|
<button
|
||||||
>
|
key={g}
|
||||||
{g}
|
onClick={() => onSetGrade(t, g)}
|
||||||
</button>
|
style={{ fontSize: "0.65rem", padding: "1px 6px", borderRadius: "4px", border: `1px solid ${ABCDE_LABELS[g].color}`, background: `${ABCDE_LABELS[g].color}18`, color: ABCDE_LABELS[g].color, cursor: "pointer", fontWeight: 700 }}
|
||||||
))}
|
>
|
||||||
</div>
|
{g}
|
||||||
</TaskCard>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
)}
|
</TaskCard>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (isMobile) {
|
||||||
|
return (
|
||||||
|
<div style={{ display: "flex", flexDirection: "column", gap: "10px" }}>
|
||||||
|
{gradeBuckets}
|
||||||
|
{ungradedPanel}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{ display: "flex", gap: "14px", alignItems: "flex-start" }}>
|
||||||
|
<div style={{ flex: 3, minWidth: 0 }}>{gradeBuckets}</div>
|
||||||
|
<div style={{ flex: 2, minWidth: "200px", maxWidth: "380px" }}>{ungradedPanel}</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─────────────────────────────────────────────
|
// ─────────────────────────────────────────────
|
||||||
@ -1024,9 +1106,10 @@ interface IvyLeeViewProps {
|
|||||||
border: string;
|
border: string;
|
||||||
textPrimary: string;
|
textPrimary: string;
|
||||||
textSecondary: 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 accentColor = "#6366f1";
|
||||||
const selectedTasks = tasks.filter((t) => selected.has(t.id));
|
const selectedTasks = tasks.filter((t) => selected.has(t.id));
|
||||||
const unselected = 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);
|
setDropOverTop6(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
// Info banner
|
||||||
<div>
|
const infoBanner = (
|
||||||
{/* Header info */}
|
<div style={{ background: darkMode ? "#1e3a5f" : "#eff6ff", borderRadius: "10px", padding: "12px 14px", marginBottom: "14px", display: "flex", alignItems: "flex-start", gap: "10px" }}>
|
||||||
<div style={{ background: darkMode ? "#1e3a5f" : "#eff6ff", borderRadius: "10px", padding: "12px 14px", marginBottom: "16px", display: "flex", alignItems: "flex-start", gap: "10px" }}>
|
<Star size={16} style={{ color: "#3b82f6", flexShrink: 0, marginTop: "1px" }} />
|
||||||
<Star size={16} style={{ color: "#3b82f6", flexShrink: 0, marginTop: "1px" }} />
|
<div>
|
||||||
<div>
|
<p style={{ fontSize: "0.82rem", color: darkMode ? "#93c5fd" : "#1d4ed8", fontWeight: 600, margin: "0 0 3px" }}>
|
||||||
<p style={{ fontSize: "0.82rem", color: darkMode ? "#93c5fd" : "#1d4ed8", fontWeight: 600, margin: "0 0 3px" }}>
|
{de ? "Ivy Lee Methode" : "Ivy Lee Method"}
|
||||||
{de ? "Ivy Lee Methode" : "Ivy Lee Method"}
|
</p>
|
||||||
</p>
|
<p style={{ fontSize: "0.78rem", color: darkMode ? "#60a5fa" : "#3b82f6", margin: 0, lineHeight: 1.5 }}>
|
||||||
<p style={{ fontSize: "0.78rem", color: darkMode ? "#60a5fa" : "#3b82f6", margin: 0, lineHeight: 1.5 }}>
|
{de
|
||||||
{de
|
? "Wähle genau 6 wichtige Aufgaben für morgen, geordnet nach Priorität."
|
||||||
? "Wähle genau 6 wichtige Aufgaben für morgen, geordnet nach Priorität."
|
: "Select exactly 6 important tasks for tomorrow, ordered by priority."}
|
||||||
: "Select exactly 6 important tasks for tomorrow, ordered by priority."}
|
</p>
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
{/* Selected tasks */}
|
// Top-6 selection panel (left column)
|
||||||
<div
|
const top6Panel = (
|
||||||
onDragOver={(e) => { e.preventDefault(); e.dataTransfer.dropEffect = selected.size < 6 ? "move" : "none"; setDropOverTop6(true); }}
|
<div
|
||||||
onDragLeave={(e) => { if (!e.currentTarget.contains(e.relatedTarget as Node)) setDropOverTop6(false); }}
|
onDragOver={(e) => { e.preventDefault(); e.dataTransfer.dropEffect = selected.size < 6 ? "move" : "none"; setDropOverTop6(true); }}
|
||||||
onDrop={handleTop6Drop}
|
onDragLeave={(e) => { if (!e.currentTarget.contains(e.relatedTarget as Node)) setDropOverTop6(false); }}
|
||||||
style={{ marginBottom: "16px", borderRadius: "10px", border: dropOverTop6 ? `2px dashed ${accentColor}` : "2px solid transparent", padding: "4px", transition: "border 0.15s" }}
|
onDrop={handleTop6Drop}
|
||||||
>
|
style={{ 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: dropOverTop6 ? accentColor : textSecondary, textTransform: "uppercase", letterSpacing: "0.05em" }}>
|
<div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", marginBottom: "8px" }}>
|
||||||
{dropOverTop6 ? (de ? "Hier ablegen" : "Drop to add") : (de ? "Meine Top-6" : "My Top 6")}
|
<span style={{ fontSize: "0.78rem", fontWeight: 700, color: dropOverTop6 ? accentColor : textSecondary, textTransform: "uppercase", letterSpacing: "0.05em" }}>
|
||||||
</span>
|
{dropOverTop6 ? (de ? "Hier ablegen" : "Drop to add") : (de ? "Meine Top-6" : "My Top 6")}
|
||||||
<span style={{ fontSize: "0.78rem", color: selected.size === 6 ? "#059669" : accentColor, fontWeight: 600 }}>
|
</span>
|
||||||
{selected.size}/6
|
<span style={{ fontSize: "0.78rem", color: selected.size === 6 ? "#059669" : accentColor, fontWeight: 600 }}>
|
||||||
</span>
|
{selected.size}/6
|
||||||
</div>
|
</span>
|
||||||
<div style={{ display: "flex", flexDirection: "column", gap: "6px" }}>
|
</div>
|
||||||
{Array.from({ length: 6 }).map((_, i) => {
|
<div style={{ display: "flex", flexDirection: "column", gap: "6px" }}>
|
||||||
const t = selectedTasks[i];
|
{Array.from({ length: 6 }).map((_, i) => {
|
||||||
if (t) {
|
const t = selectedTasks[i];
|
||||||
return (
|
if (t) {
|
||||||
<div key={t.id} style={{ display: "flex", alignItems: "center", gap: "8px" }}>
|
|
||||||
<span style={{ fontSize: "0.78rem", fontWeight: 700, color: accentColor, width: "16px", textAlign: "center", flexShrink: 0 }}>{i + 1}</span>
|
|
||||||
<div style={{ flex: 1 }}>
|
|
||||||
<TaskCard task={t} darkMode={darkMode} cardBg={cardBg} border={border} textPrimary={textPrimary} textSecondary={textSecondary} onDelegate={onDelegate} onToggleComplete={onToggleComplete} onUpdateTask={onUpdateTask} projects={projects} onRemove={() => onToggle(t.id)} de={de} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return (
|
return (
|
||||||
<div key={`empty-${i}`} style={{ display: "flex", alignItems: "center", gap: "8px" }}>
|
<div key={t.id} style={{ display: "flex", alignItems: "center", gap: "8px" }}>
|
||||||
<span style={{ fontSize: "0.78rem", fontWeight: 700, color: textSecondary, width: "16px", textAlign: "center", flexShrink: 0 }}>{i + 1}</span>
|
<span style={{ fontSize: "0.78rem", fontWeight: 700, color: accentColor, width: "16px", textAlign: "center", flexShrink: 0 }}>{i + 1}</span>
|
||||||
<div style={{ flex: 1, background: darkMode ? "#1f2937" : "#f9fafb", border: `1px dashed ${border}`, borderRadius: "8px", padding: "10px 12px" }}>
|
<div style={{ flex: 1 }}>
|
||||||
<span style={{ fontSize: "0.78rem", color: textSecondary }}>{de ? "Leer — wähle eine Aufgabe unten" : "Empty — pick a task below"}</span>
|
<TaskCard task={t} darkMode={darkMode} cardBg={cardBg} border={border} textPrimary={textPrimary} textSecondary={textSecondary} onDelegate={onDelegate} onToggleComplete={onToggleComplete} onUpdateTask={onUpdateTask} projects={projects} onRemove={() => onToggle(t.id)} de={de} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
}
|
||||||
</div>
|
return (
|
||||||
</div>
|
<div key={`empty-${i}`} style={{ display: "flex", alignItems: "center", gap: "8px" }}>
|
||||||
|
<span style={{ fontSize: "0.78rem", fontWeight: 700, color: textSecondary, width: "16px", textAlign: "center", flexShrink: 0 }}>{i + 1}</span>
|
||||||
{/* All other tasks to pick from */}
|
<div style={{ flex: 1, background: darkMode ? "#1f2937" : "#f9fafb", border: `1px dashed ${border}`, borderRadius: "8px", padding: "10px 12px" }}>
|
||||||
{unselected.length > 0 && (
|
<span style={{ fontSize: "0.78rem", color: textSecondary }}>{de ? "Leer — wähle eine Aufgabe" : "Empty — pick a task"}</span>
|
||||||
<div>
|
|
||||||
<p style={{ fontSize: "0.78rem", fontWeight: 600, color: textSecondary, marginBottom: "8px" }}>
|
|
||||||
{de ? "Verfügbare Aufgaben:" : "Available tasks:"}
|
|
||||||
</p>
|
|
||||||
<div style={{ display: "flex", flexDirection: "column", gap: "5px" }}>
|
|
||||||
{unselected.map((t) => (
|
|
||||||
<div
|
|
||||||
key={t.id}
|
|
||||||
onClick={() => 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",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Circle size={14} style={{ color: "#9ca3af", flexShrink: 0 }} />
|
|
||||||
<span style={{ fontSize: "0.85rem", color: textPrimary, flex: 1 }}>{t.title}</span>
|
|
||||||
{t.scheduledDate && <span style={{ fontSize: "0.7rem", color: "#6b7280" }}>{formatDate(t.scheduledDate)}</span>}
|
|
||||||
{t.project && <span style={{ fontSize: "0.7rem", color: t.project.color || "#6366f1" }}>{t.project.name}</span>}
|
|
||||||
</div>
|
</div>
|
||||||
))}
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
// Available tasks pool (right column)
|
||||||
|
const availablePool = (
|
||||||
|
<div>
|
||||||
|
<p style={{ fontSize: "0.78rem", fontWeight: 600, color: textSecondary, marginBottom: "8px" }}>
|
||||||
|
{unselected.length > 0 ? (de ? "Verfügbare Aufgaben:" : "Available tasks:") : (de ? "Alle Aufgaben ausgewählt ✓" : "All tasks selected ✓")}
|
||||||
|
</p>
|
||||||
|
<div style={{ display: "flex", flexDirection: "column", gap: "5px" }}>
|
||||||
|
{unselected.map((t) => (
|
||||||
|
<div
|
||||||
|
key={t.id}
|
||||||
|
onClick={() => 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",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Circle size={14} style={{ color: "#9ca3af", flexShrink: 0 }} />
|
||||||
|
<span style={{ fontSize: "0.85rem", color: textPrimary, flex: 1 }}>{t.title}</span>
|
||||||
|
{t.scheduledDate && <span style={{ fontSize: "0.7rem", color: "#6b7280" }}>{formatDate(t.scheduledDate)}</span>}
|
||||||
|
{t.project && <span style={{ fontSize: "0.7rem", color: t.project.color || "#6366f1" }}>{t.project.name}</span>}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
))}
|
||||||
)}
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
if (isMobile) {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{infoBanner}
|
||||||
|
{top6Panel}
|
||||||
|
{unselected.length > 0 && <div style={{ marginTop: "16px" }}>{availablePool}</div>}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{infoBanner}
|
||||||
|
<div style={{ display: "flex", gap: "14px", alignItems: "flex-start" }}>
|
||||||
|
<div style={{ flex: 1, minWidth: 0 }}>{top6Panel}</div>
|
||||||
|
<div style={{ flex: 1, minWidth: "200px" }}>{availablePool}</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -1151,17 +1254,17 @@ interface ParetoViewProps {
|
|||||||
border: string;
|
border: string;
|
||||||
textPrimary: string;
|
textPrimary: string;
|
||||||
textSecondary: string;
|
textSecondary: string;
|
||||||
|
isMobile?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
function ParetoView({ important, rest, darkMode, de, onDelegate, onToggleComplete, onUpdateTask, projects, cardBg, border, textPrimary, textSecondary }: ParetoViewProps) {
|
function ParetoView({ important, rest, darkMode, de, onDelegate, onToggleComplete, onUpdateTask, projects, cardBg, border, textPrimary, textSecondary, isMobile }: ParetoViewProps) {
|
||||||
const [showRest, setShowRest] = useState(false);
|
|
||||||
const total = important.length + rest.length;
|
const total = important.length + rest.length;
|
||||||
const pct = total > 0 ? Math.round((important.length / total) * 100) : 0;
|
const pct = total > 0 ? Math.round((important.length / total) * 100) : 0;
|
||||||
|
|
||||||
return (
|
// Info banner + progress bar (full-width header)
|
||||||
<div>
|
const header = (
|
||||||
{/* Info banner */}
|
<>
|
||||||
<div style={{ background: darkMode ? "#422006" : "#fff7ed", borderRadius: "10px", padding: "12px 14px", marginBottom: "16px", display: "flex", gap: "10px", alignItems: "flex-start" }}>
|
<div style={{ background: darkMode ? "#422006" : "#fff7ed", borderRadius: "10px", padding: "12px 14px", marginBottom: "12px", display: "flex", gap: "10px", alignItems: "flex-start" }}>
|
||||||
<BarChart2 size={16} style={{ color: "#f97316", flexShrink: 0, marginTop: "1px" }} />
|
<BarChart2 size={16} style={{ color: "#f97316", flexShrink: 0, marginTop: "1px" }} />
|
||||||
<div>
|
<div>
|
||||||
<p style={{ fontSize: "0.82rem", fontWeight: 700, color: "#f97316", margin: "0 0 3px" }}>
|
<p style={{ fontSize: "0.82rem", fontWeight: 700, color: "#f97316", margin: "0 0 3px" }}>
|
||||||
@ -1169,66 +1272,87 @@ function ParetoView({ important, rest, darkMode, de, onDelegate, onToggleComplet
|
|||||||
</p>
|
</p>
|
||||||
<p style={{ fontSize: "0.78rem", color: darkMode ? "#fb923c" : "#c2410c", margin: 0, lineHeight: 1.5 }}>
|
<p style={{ fontSize: "0.78rem", color: darkMode ? "#fb923c" : "#c2410c", margin: 0, lineHeight: 1.5 }}>
|
||||||
{de
|
{de
|
||||||
? `${important.length} von ${total} Aufgaben (${pct}%) sind als wichtig markiert. Fokussiere dich auf diese, um 80% der Ergebnisse zu erzielen.`
|
? `${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 your outcomes.`}
|
: `${important.length} of ${total} tasks (${pct}%) are marked as important. Focus on these to achieve 80% of outcomes.`}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div style={{ background: darkMode ? "#374151" : "#e5e7eb", borderRadius: "99px", height: "8px", marginBottom: "16px", overflow: "hidden" }}>
|
||||||
{/* Progress bar */}
|
|
||||||
<div style={{ background: darkMode ? "#374151" : "#e5e7eb", borderRadius: "99px", height: "8px", marginBottom: "20px", overflow: "hidden" }}>
|
|
||||||
<div style={{ width: `${pct}%`, height: "100%", background: "linear-gradient(90deg, #f97316, #ef4444)", borderRadius: "99px", transition: "width 0.4s" }} />
|
<div style={{ width: `${pct}%`, height: "100%", background: "linear-gradient(90deg, #f97316, #ef4444)", borderRadius: "99px", transition: "width 0.4s" }} />
|
||||||
</div>
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
{/* Important tasks */}
|
// High-impact tasks (left column)
|
||||||
<div style={{ marginBottom: "16px" }}>
|
const importantPanel = (
|
||||||
<div style={{ display: "flex", alignItems: "center", gap: "8px", marginBottom: "10px" }}>
|
<div>
|
||||||
<span style={{ fontSize: "0.78rem", fontWeight: 700, color: "#f97316", textTransform: "uppercase", letterSpacing: "0.05em" }}>
|
<div style={{ display: "flex", alignItems: "center", gap: "8px", marginBottom: "10px" }}>
|
||||||
{de ? "Wichtige Aufgaben (Fokus)" : "High-Impact Tasks (Focus)"}
|
<span style={{ fontSize: "0.78rem", fontWeight: 700, color: "#f97316", textTransform: "uppercase", letterSpacing: "0.05em" }}>
|
||||||
</span>
|
{de ? "Wichtige Aufgaben (Fokus)" : "High-Impact Tasks (Focus)"}
|
||||||
<span style={{ fontSize: "0.75rem", color: "#f97316", background: "#f9731622", borderRadius: "9px", padding: "1px 7px" }}>{important.length}</span>
|
</span>
|
||||||
</div>
|
<span style={{ fontSize: "0.75rem", color: "#f97316", background: "#f9731622", borderRadius: "9px", padding: "1px 7px" }}>{important.length}</span>
|
||||||
|
</div>
|
||||||
|
<div style={{ display: "flex", flexDirection: "column", gap: "6px" }}>
|
||||||
|
{important.length === 0 && (
|
||||||
|
<p style={{ fontSize: "0.8rem", color: textSecondary, padding: "10px 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."}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
{important.map((t) => (
|
||||||
|
<div key={t.id} style={{ borderLeft: "3px solid #f97316", borderRadius: "0 8px 8px 0" }}>
|
||||||
|
<TaskCard task={t} darkMode={darkMode} cardBg={cardBg} border={border} textPrimary={textPrimary} textSecondary={textSecondary} onDelegate={onDelegate} onToggleComplete={onToggleComplete} onUpdateTask={onUpdateTask} projects={projects} onRemove={() => onUpdateTask(t.id, { importance: false })} de={de} />
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
// Remaining tasks (right column — always visible, no toggle)
|
||||||
|
const remainingPanel = (
|
||||||
|
<div>
|
||||||
|
<div style={{ display: "flex", alignItems: "center", gap: "8px", marginBottom: "10px" }}>
|
||||||
|
<span style={{ fontSize: "0.78rem", fontWeight: 600, color: textSecondary, textTransform: "uppercase", letterSpacing: "0.05em" }}>
|
||||||
|
{de ? "Restliche Aufgaben" : "Remaining tasks"}
|
||||||
|
</span>
|
||||||
|
<span style={{ fontSize: "0.75rem", color: textSecondary, background: darkMode ? "#374151" : "#e5e7eb", borderRadius: "9px", padding: "1px 7px" }}>{rest.length}</span>
|
||||||
|
</div>
|
||||||
|
{rest.length === 0 ? (
|
||||||
|
<p style={{ fontSize: "0.78rem", color: textSecondary }}>{de ? "Keine weiteren Aufgaben." : "No remaining tasks."}</p>
|
||||||
|
) : (
|
||||||
<div style={{ display: "flex", flexDirection: "column", gap: "6px" }}>
|
<div style={{ display: "flex", flexDirection: "column", gap: "6px" }}>
|
||||||
{important.length === 0 && (
|
{rest.map((t) => (
|
||||||
<p style={{ fontSize: "0.8rem", color: textSecondary, padding: "10px 0" }}>
|
<TaskCard key={t.id} task={t} darkMode={darkMode} cardBg={cardBg} border={border} textPrimary={textPrimary} textSecondary={textSecondary} onDelegate={onDelegate} onToggleComplete={onToggleComplete} onUpdateTask={onUpdateTask} projects={projects} de={de}>
|
||||||
{de
|
<button
|
||||||
? "Keine wichtigen Aufgaben. Markiere Aufgaben als wichtig im Eisenhower- oder ABCDE-Modus."
|
onClick={() => onUpdateTask(t.id, { importance: true })}
|
||||||
: "No important tasks. Mark tasks as important via Eisenhower or ABCDE mode."}
|
style={{ fontSize: "0.65rem", marginTop: "3px", background: "none", border: "none", color: "#f97316", cursor: "pointer", padding: 0, display: "flex", alignItems: "center", gap: "2px" }}
|
||||||
</p>
|
>
|
||||||
)}
|
<Star size={9} /> {de ? "Als wichtig markieren" : "Mark as important"}
|
||||||
{important.map((t) => (
|
</button>
|
||||||
<div key={t.id} style={{ borderLeft: "3px solid #f97316", borderRadius: "0 8px 8px 0" }}>
|
</TaskCard>
|
||||||
<TaskCard task={t} darkMode={darkMode} cardBg={cardBg} border={border} textPrimary={textPrimary} textSecondary={textSecondary} onDelegate={onDelegate} onToggleComplete={onToggleComplete} onUpdateTask={onUpdateTask} projects={projects} onRemove={() => onUpdateTask(t.id, { importance: false })} de={de} />
|
|
||||||
</div>
|
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
{/* The rest (80%) */}
|
if (isMobile) {
|
||||||
|
return (
|
||||||
<div>
|
<div>
|
||||||
<button
|
{header}
|
||||||
onClick={() => setShowRest((v) => !v)}
|
{importantPanel}
|
||||||
style={{ display: "flex", alignItems: "center", gap: "6px", background: "none", border: "none", cursor: "pointer", padding: "6px 0", marginBottom: "8px" }}
|
<div style={{ marginTop: "16px" }}>{remainingPanel}</div>
|
||||||
>
|
</div>
|
||||||
<span style={{ fontSize: "0.78rem", fontWeight: 600, color: textSecondary, textTransform: "uppercase", letterSpacing: "0.05em" }}>
|
);
|
||||||
{de ? "Restliche Aufgaben" : "Remaining tasks"} ({rest.length})
|
}
|
||||||
</span>
|
|
||||||
{showRest ? <ChevronUp size={13} style={{ color: textSecondary }} /> : <ChevronDown size={13} style={{ color: textSecondary }} />}
|
return (
|
||||||
</button>
|
<div>
|
||||||
{showRest && (
|
{header}
|
||||||
<div style={{ display: "flex", flexDirection: "column", gap: "6px" }}>
|
<div style={{ display: "flex", gap: "14px", alignItems: "flex-start" }}>
|
||||||
{rest.map((t) => (
|
<div style={{ flex: 1, minWidth: 0 }}>{importantPanel}</div>
|
||||||
<TaskCard key={t.id} task={t} darkMode={darkMode} cardBg={cardBg} border={border} textPrimary={textPrimary} textSecondary={textSecondary} onDelegate={onDelegate} onToggleComplete={onToggleComplete} onUpdateTask={onUpdateTask} projects={projects} de={de}>
|
<div style={{ flex: 1, minWidth: "200px" }}>{remainingPanel}</div>
|
||||||
<button
|
|
||||||
onClick={() => onUpdateTask(t.id, { importance: true })}
|
|
||||||
style={{ fontSize: "0.65rem", marginTop: "3px", background: "none", border: "none", color: "#f97316", cursor: "pointer", padding: 0, display: "flex", alignItems: "center", gap: "2px" }}
|
|
||||||
>
|
|
||||||
<Star size={9} /> {de ? "Als wichtig markieren" : "Mark as important"}
|
|
||||||
</button>
|
|
||||||
</TaskCard>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user