diff --git a/package.json b/package.json index f3aaf63..d819ed8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "my-weekly-todo-list", - "version": "1.44.1", + "version": "1.45.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/app/globals.css b/src/app/globals.css index a274aaf..792b996 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -4358,10 +4358,15 @@ h3 { } @media (max-width: 768px) { - .time-grid-on .weekly-day-header, - .time-grid-on .time-column-header { + .time-grid-on .weekly-day-header { top: calc(48px + env(safe-area-inset-top, 0px)) !important; } + /* On mobile, time-column-header should NOT be sticky — it wastes vertical space */ + .time-grid-on .time-column-header { + position: relative !important; + top: auto !important; + z-index: auto !important; + } } /* Provide a solid background for the top-left empty corner above the time column */ diff --git a/src/components/WeeklyView.tsx b/src/components/WeeklyView.tsx index 7eb1a9d..ad244a0 100644 --- a/src/components/WeeklyView.tsx +++ b/src/components/WeeklyView.tsx @@ -1807,7 +1807,7 @@ export default function WeeklyView() { // Moved state definitions to the top const [showSettings, setShowSettings] = useState(false); const [activeTab, setActiveTab] = useState< - "calendar" | "general" | "account" | "styling" | "motivation" | "about" | "projects" + "calendar" | "general" | "account" | "styling" | "motivation" | "about" >("general"); const [exportStartDate, setExportStartDate] = useState(""); const [exportEndDate, setExportEndDate] = useState(""); @@ -1991,6 +1991,7 @@ export default function WeeklyView() { const [showDatePicker, setShowDatePicker] = useState(false); const datePickerBtnRef = useRef(null); const [showQuickSettings, setShowQuickSettings] = useState(false); + const [showProjectsSidebar, setShowProjectsSidebar] = useState(false); const [focusTimerDuration, setFocusTimerDuration] = useState(25); const [fontSize, setFontSize] = useState<"S" | "M" | "L">("M"); @@ -5279,6 +5280,18 @@ export default function WeeklyView() { const activeDateLayout = isMobile ? (profile.mobileDateLayout || "below") : (profile.dateLayout || "right"); + // Quick settings sidebar button styles + const qsBtnStyle = (dm: boolean): React.CSSProperties => ({ + padding: "6px", borderRadius: "6px", border: "none", cursor: "pointer", + display: "flex", alignItems: "center", justifyContent: "center", + background: dm ? "#1f2937" : "#e5e7eb", color: dm ? "#9ca3af" : "#6b7280", + }); + const qsActionStyle = (dm: boolean): React.CSSProperties => ({ + display: "flex", alignItems: "center", gap: "8px", padding: "7px 8px", + fontSize: "0.8rem", borderRadius: "6px", border: "none", cursor: "pointer", + background: "none", color: dm ? "#d1d5db" : "#333", textAlign: "left" as const, + }); + // All-Day Events Section (reusable for above/below positioning) const allDaySection = (() => { if (!showAllDay) return null; @@ -5471,166 +5484,167 @@ export default function WeeklyView() { left: 0, top: 0, bottom: 0, - width: "220px", + width: "240px", background: darkMode ? "#1a1a2e" : "#fafafa", borderRight: `1px solid ${darkMode ? "#333" : "#e5e7eb"}`, zIndex: 1000, display: "flex", flexDirection: "column", - padding: "20px 16px", - gap: "18px", + padding: "16px 14px", + gap: "10px", overflowY: "auto", boxShadow: "2px 0 12px rgba(0,0,0,0.08)", }} > -
+
{language === "de" ? "Einstellungen" : "Preferences"}
- {/* Columns */} + {/* Navigation Row */} +
+ + + + + +
+ + {/* Quick Actions */} +
+ + + + + + + +
+ + {/* Separator */} +
+ + {/* View Style */}
- - {language === "de" ? "Spalten" : "Columns"} + + {language === "de" ? "Ansicht" : "View"} + +
+ {[ + { key: "simple", icon: }, + { key: "calendar", icon: }, + { key: "list", icon: }, + { key: "kanban", icon: }, + ].map((v) => ( + + ))} +
+
+ + {/* Columns / Days */} +
+ + {language === "de" ? "Spalten" : "Days"}
{[1, 2, 3, 5, 7].map((num) => ( - ))}
+ {/* Slot Duration (only with time grid) */} + {showTimeGrid && ( +
+ + {language === "de" ? "Zeitfenster" : "Slot"} + +
+ {[15, 30, 60].map((d) => ( + + ))} +
+
+ )} + + {/* Visible Hours (only with time grid) */} + {showTimeGrid && ( +
+ + { const h = Math.max(0, Math.min(23, parseInt(e.target.value) || 0)); setStartHour(h); saveSetting("startHour", h); }} + style={{ width: "44px", padding: "4px 4px", borderRadius: "6px", border: `1px solid ${darkMode ? "#555" : "#e5e7eb"}`, background: darkMode ? "#1f2937" : "#f3f4f6", color: darkMode ? "#e5e7eb" : "#333", fontSize: "0.8rem", textAlign: "center", outline: "none" }} /> + + { const h = Math.max(1, Math.min(24, parseInt(e.target.value) || 24)); setEndHour(h); saveSetting("endHour", h); }} + style={{ width: "44px", padding: "4px 4px", borderRadius: "6px", border: `1px solid ${darkMode ? "#555" : "#e5e7eb"}`, background: darkMode ? "#1f2937" : "#f3f4f6", color: darkMode ? "#e5e7eb" : "#333", fontSize: "0.8rem", textAlign: "center", outline: "none" }} /> + h +
+ )} + {/* Text size */}
- + {language === "de" ? "Textgröße" : "Text size"}
{(["S", "M", "L"] as const).map((size) => ( - ))}
- {/* Someday section */} + {/* Separator */} +
+ + {/* Toggle switches */}
- - {language === "de" ? "Irgendwann" : "Someday"} - -
- - {/* Schedule / Time Grid */}
- - {language === "de" ? "Zeitplan" : "Schedule"} - -
- - {/* All-day events */}
- - {language === "de" ? "Ganztägig" : "All-day"} - -
- - {/* Checkboxes */}
- - {language === "de" ? "Checkboxen" : "Checkboxes"} - -
{/* Start on */}
- - {language === "de" ? "Starten mit" : "Start on"} - + {language === "de" ? "Starten mit" : "Start on"}
- -
@@ -5638,49 +5652,36 @@ export default function WeeklyView() { {/* Display mode */}
- - {language === "de" ? "Anzeige" : "Display"} - + {language === "de" ? "Anzeige" : "Display"}
- -
+ {/* Separator */} +
+ + {/* Undo / Redo / Refresh */} +
+ + + +
+ {/* Spacer */}
{/* Close button at bottom */} - +
{language === "de" ? "Tage" : "Days"}: @@ -6176,7 +6188,7 @@ export default function WeeklyView() { {/* Desktop only: Recurring Tasks + New Project */} - + {/* User Menu */} { setShowSettings(true); setActiveTab("projects"); }, + onAddProject: () => { setShowProjectsSidebar(true); }, onRecurringTasks: () => setIsRecurringTasksOpen(true), onToggleNextTask: () => { const newVal = !showNextTask; setShowNextTask(newVal); saveSetting("showNextTask", newVal); }, onFocusMode: () => setShowFocusMode(true), @@ -8401,7 +8413,7 @@ export default function WeeklyView() {
{language === "de" ? "Suche" : "Search"} - @@ -9487,6 +9499,172 @@ function TaskItem({ ); } +// Projects Sidebar Component +const projectEmojisGlobal = [ + "📁", "📂", "💼", "🎯", "🚀", "⭐", "💡", "🔥", "🎨", "🎵", + "📱", "💻", "🌐", "🏠", "🏢", "📊", "📈", "🔧", "⚡", "🎮", + "📝", "📖", "🎓", "🧪", "🔬", "🏋️", "🍽️", "✈️", "🌿", "❤️", + "🛒", "💰", "🎁", "📸", "🎬", "🧹", "🐾", "🌍", "🔒", "✅", +]; + +function ProjectsSidebar({ darkMode, language, projects, onProjectsChanged, onClose }: { + darkMode: boolean; + language: string; + projects: { id: string; name: string; icon?: string | null; color?: string | null }[]; + onProjectsChanged: () => void; + onClose: () => void; +}) { + const [newProjectName, setNewProjectName] = useState(""); + const [newProjectColor, setNewProjectColor] = useState("#3b82f6"); + const [newProjectIcon, setNewProjectIcon] = useState("📁"); + const [showNewIconPicker, setShowNewIconPicker] = useState(false); + const [editingId, setEditingId] = useState(null); + const [editName, setEditName] = useState(""); + const [editColor, setEditColor] = useState(""); + const [editIcon, setEditIcon] = useState(""); + const [showEditIconPicker, setShowEditIconPicker] = useState(false); + + const t = { + projects: language === "de" ? "Projekte" : "Projects", + desc: language === "de" ? "Aufgaben mit farbcodierten Projekten organisieren" : "Organize tasks with color-coded projects", + noProjects: language === "de" ? "Noch keine Projekte erstellt" : "No projects yet", + addProject: language === "de" ? "Projekt hinzufügen" : "Add Project", + name: language === "de" ? "Name" : "Name", + cancel: language === "de" ? "Abbrechen" : "Cancel", + save: language === "de" ? "Speichern" : "Save", + color: language === "de" ? "Farbe" : "Color", + deleteConfirm: (name: string) => language === "de" ? `Projekt "${name}" löschen?` : `Delete project "${name}"?`, + }; + + const createProject = () => { + if (!newProjectName.trim()) return; + fetch("/api/projects", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ name: newProjectName.trim(), color: newProjectColor, icon: newProjectIcon }) }) + .then(() => { onProjectsChanged(); setNewProjectName(""); setNewProjectIcon("📁"); }); + }; + + const updateProject = (id: string) => { + fetch("/api/projects", { method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ id, name: editName, color: editColor, icon: editIcon }) }) + .then(() => { onProjectsChanged(); setEditingId(null); }); + }; + + const deleteProject = (id: string, name: string) => { + if (confirm(t.deleteConfirm(name))) { + fetch(`/api/projects?id=${id}`, { method: "DELETE" }).then(() => onProjectsChanged()); + } + }; + + return ( + <> +
+
+ {/* Header */} +
+

+ {t.projects} +

+ +
+ + {/* Scrollable content */} +
+

{t.desc}

+ + {projects.length === 0 ? ( +
+
📁
+

{t.noProjects}

+
+ ) : ( + projects.map((p) => ( +
+ {editingId === p.id ? ( +
+
+
+ + {showEditIconPicker && ( +
+
+ {projectEmojisGlobal.map((emoji) => ( + + ))} +
+
+ )} +
+ setEditName(e.target.value)} className="weekly-input" style={{ flex: 1, padding: "8px 12px", fontSize: "0.9rem" }} onKeyDown={(e) => { if (e.key === "Enter") updateProject(p.id); if (e.key === "Escape") setEditingId(null); }} autoFocus /> +
+
+ setEditColor(e.target.value)} style={{ width: "32px", height: "32px", border: "none", cursor: "pointer", padding: 0, borderRadius: "8px" }} /> + {t.color} +
+ + +
+
+ ) : ( + <> + {p.icon || "📁"} +
+ {p.name} +
+ + + + )} +
+ )) + )} +
+ + {/* Add project form — fixed at bottom */} +
+

{t.addProject}

+
+
+ + {showNewIconPicker && ( +
+
+ {projectEmojisGlobal.map((emoji) => ( + + ))} +
+
+ )} +
+ setNewProjectColor(e.target.value)} style={{ width: "32px", height: "32px", border: "none", cursor: "pointer", padding: 0, borderRadius: "8px" }} /> + setNewProjectName(e.target.value)} placeholder={t.name} className="weekly-input" style={{ flex: 1, padding: "8px 12px", fontSize: "0.9rem" }} onKeyDown={(e) => { if (e.key === "Enter") createProject(); }} /> + +
+
+
+ + ); +} + // Settings Modal Component interface SettingsSidebarProps { onClose: () => void; @@ -9639,7 +9817,7 @@ interface SettingsSidebarProps { fetchAvailableTaskLists: ( provider: "google" | "apple" | "outlook" | "synology", ) => Promise; - initialTab?: "calendar" | "general" | "account" | "styling" | "motivation" | "about" | "projects"; + initialTab?: "calendar" | "general" | "account" | "styling" | "motivation" | "about"; projects: { id: string; name: string; icon?: string | null; color?: string | null }[]; onProjectsChanged: () => void; kanbanStages: KanbanStage[]; @@ -9905,7 +10083,7 @@ function SettingsSidebar({ mobileActions, }: SettingsSidebarProps) { const [activeTab, setActiveTab] = useState< - "calendar" | "general" | "account" | "styling" | "motivation" | "about" | "localisation" | "projects" + "calendar" | "general" | "account" | "styling" | "motivation" | "about" | "localisation" >(initialTab || "general"); const [isLoading, setIsLoading] = useState(true); const [isSyncing, setIsSyncing] = useState(false); @@ -10375,193 +10553,6 @@ function SettingsSidebar({ - {/* Quick Actions Panel */} - {mobileActions && ( -
- {/* Navigation Row */} -
- - - - - -
- - {/* Quick Action Buttons */} -
- - - - - - - - -
- - {/* View Style */} -
-
- {t.view || "View"} -
- {[ - { key: "simple", icon: , label: "Simple" }, - { key: "calendar", icon: , label: "Calendar" }, - { key: "list", icon: , label: "List" }, - { key: "kanban", icon: , label: "Kanban" }, - ].map((v) => ( - - ))} -
-
- - {/* Days */} -
- {t.days || "Days"} -
- {[1, 2, 3, 5, 7].map((num) => ( - - ))} -
-
- - {/* Slot Duration */} - {mobileActions.showTimeGrid && ( -
- {t.slot || "Slot"} -
- {[15, 30, 60].map((d) => ( - - ))} -
-
- )} - - {/* Visible Hours */} - {mobileActions.showTimeGrid && ( -
- - - -
- mobileActions.onStartHourChange(Math.max(0, Math.min(23, parseInt(e.target.value) || 0)))} - style={{ - width: "48px", padding: "4px 6px", borderRadius: "6px", border: "1px solid var(--weekly-border, #e5e7eb)", - background: "var(--weekly-settings-toggle-bg, #f3f4f6)", color: "var(--weekly-text, #333)", - fontSize: "0.8rem", textAlign: "center", outline: "none", - }} - /> - - mobileActions.onEndHourChange(Math.max(1, Math.min(24, parseInt(e.target.value) || 24)))} - style={{ - width: "48px", padding: "4px 6px", borderRadius: "6px", border: "1px solid var(--weekly-border, #e5e7eb)", - background: "var(--weekly-settings-toggle-bg, #f3f4f6)", color: "var(--weekly-text, #333)", - fontSize: "0.8rem", textAlign: "center", outline: "none", - }} - /> - h -
-
- )} -
- - {/* Utility Row */} -
- - - -
-
- )} -
{([ { key: "general", icon: , label: t.general }, - { key: "projects", icon: , label: t.projects }, { key: "localisation", icon: , label: t.localisation }, { key: "calendar", icon: , label: t.calendar }, { key: "account", icon: , label: t.account }, @@ -11526,125 +11516,6 @@ function SettingsSidebar({
- ) : activeTab === "projects" ? ( -
-
-

- {t.projects} -

-

{t.projectsDesc}

-
- - {projects.length === 0 ? ( -
-
📁
-

{t.noProjects}

-
- ) : ( -
- {projects.map((p) => ( -
- {editingProjectId === p.id ? ( -
-
-
- - {showEditProjectIconPicker && ( -
-
- {projectEmojis.map((emoji) => ( - - ))} -
-
- )} -
- setEditProjectName(e.target.value)} - className="weekly-input" - style={{ flex: 1, padding: "8px 12px", fontSize: "0.9rem" }} - onKeyDown={(e) => { - if (e.key === "Enter") { - fetch("/api/projects", { method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ id: p.id, name: editProjectName, color: editProjectColor, icon: editProjectIcon }) }).then(() => { onProjectsChanged(); setEditingProjectId(null); }); - } - if (e.key === "Escape") setEditingProjectId(null); - }} - autoFocus - /> -
-
- setEditProjectColor(e.target.value)} style={{ width: "32px", height: "32px", border: "none", cursor: "pointer", padding: 0, borderRadius: "8px" }} /> - {profile.language === "de" ? "Farbe" : "Color"} -
- - -
-
- ) : ( - <> - {p.icon || "📁"} -
- {p.name} -
- - - - )} -
- ))} -
- )} - - {/* Add new project */} -
-

{t.addProject}

-
-
- - {showNewProjectIconPicker && ( -
-
- {projectEmojis.map((emoji) => ( - - ))} -
-
- )} -
- setNewProjectColor(e.target.value)} style={{ width: "32px", height: "32px", border: "none", cursor: "pointer", padding: 0, borderRadius: "8px" }} /> - setNewProjectName(e.target.value)} placeholder={t.projectName} className="weekly-input" style={{ flex: 1, padding: "8px 12px", fontSize: "0.9rem" }} onKeyDown={(e) => { if (e.key === "Enter" && newProjectName.trim()) { fetch("/api/projects", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ name: newProjectName.trim(), color: newProjectColor, icon: newProjectIcon }) }).then(() => { onProjectsChanged(); setNewProjectName(""); setNewProjectIcon("📁"); }); } }} /> - -
-
-
) : activeTab === "calendar" ? ( isLoading ? (

Loading connections...