From 77daa6e30b75b3e497eff2b9cc3cecd0a884817e Mon Sep 17 00:00:00 2001 From: mARTin Date: Sun, 8 Mar 2026 10:30:28 +0100 Subject: [PATCH] chore: release v1.17.0 - Reorganized settings, added Localisation tab, and refined UI layout --- package.json | 2 +- src/app/api/tasks/sync/route.ts | 11 +- src/components/SimpleDatePicker.tsx | 2 +- src/components/WeeklyView.tsx | 582 +++++++++++++++++----------- tsconfig.tsbuildinfo | 2 +- 5 files changed, 362 insertions(+), 237 deletions(-) diff --git a/package.json b/package.json index 62334e4..d950144 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "my-weekly-todo-list", - "version": "1.16.1", + "version": "1.17.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/api/tasks/sync/route.ts b/src/app/api/tasks/sync/route.ts index ce5983c..c755e23 100644 --- a/src/app/api/tasks/sync/route.ts +++ b/src/app/api/tasks/sync/route.ts @@ -383,9 +383,18 @@ export async function GET(req: NextRequest) { // Create new local tasks from remote const somedayListInfo = synoListIdToSomedayList.get(listId); if (somedayListInfo) { + // Also check ALL synology tasks in DB (not just this list) to avoid duplicates + const allSynoIds = await prisma.task.findMany({ + where: { userId: user.id, externalProvider: 'synology', externalId: { not: null }, deletedAt: null }, + select: { externalId: true } + }); + const allExistingIds = new Set(allSynoIds.map(t => t.externalId!.replace(/^synology::/, ''))); + // Merge with the per-list set + for (const id of existingExternalIds) { if (id) allExistingIds.add(id); } + const newRemoteTasks = remoteTasks.filter(rt => { const cleanId = rt.id.replace(/^synology::/, ''); - return !existingExternalIds.has(cleanId); + return !allExistingIds.has(cleanId); }); for (const remote of newRemoteTasks) { diff --git a/src/components/SimpleDatePicker.tsx b/src/components/SimpleDatePicker.tsx index d578cbd..5894466 100644 --- a/src/components/SimpleDatePicker.tsx +++ b/src/components/SimpleDatePicker.tsx @@ -126,7 +126,7 @@ export default function SimpleDatePicker({ selected, onSelect, onClose, language borderRadius: '0.5rem', boxShadow: '0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04)', padding: '1rem', - zIndex: 50, + zIndex: 2000, width: '18rem', border: '1px solid #f3f4f6', animation: 'fadeIn 0.15s ease-out' diff --git a/src/components/WeeklyView.tsx b/src/components/WeeklyView.tsx index c81305f..29b25c4 100644 --- a/src/components/WeeklyView.tsx +++ b/src/components/WeeklyView.tsx @@ -58,6 +58,7 @@ import { X, Cable, Link, + Globe, } from "lucide-react"; // Types @@ -222,6 +223,7 @@ const translations: Record = { settings: "Settings", general: "General", calendar: "Connections", + localisation: "Localisation", account: "Account", runningList: "Running List (Auto-roll tasks to today)", protectEventTimes: "Protect Event Times", @@ -303,11 +305,19 @@ const translations: Record = { noProjects: "No projects yet", assignProject: "Assign project", removeProject: "Remove project", + weekdayFormat: "Weekday Format", + weekdayFormatFull: "Full Name (Monday)", + weekdayFormatShort: "Short (Mon)", + weekdayFormatNarrow: "Narrow (M)", + weekdayFormatCustom: "Custom", + customWeekdayNamesMon: "Mo; Tu; We; Th; Fr; Sa; Su", + customWeekdayNamesSun: "Su; Mo; Tu; We; Th; Fr; Sa", }, de: { settings: "Einstellungen", general: "Allgemein", calendar: "Verbindungen", + localisation: "Lokalisierung", account: "Konto", runningList: "Laufende Liste (Aufgaben automatisch auf heute verschieben)", protectEventTimes: "Ereigniszeiten schützen", @@ -390,6 +400,13 @@ const translations: Record = { noProjects: "Noch keine Projekte", assignProject: "Projekt zuweisen", removeProject: "Projekt entfernen", + weekdayFormat: "Wochentag-Format", + weekdayFormatFull: "Vollständiger Name (Montag)", + weekdayFormatShort: "Kurz (Mo)", + weekdayFormatNarrow: "Schmal (M)", + weekdayFormatCustom: "Benutzerdefiniert", + customWeekdayNamesMon: "Mo; Di; Mi; Do; Fr; Sa; So", + customWeekdayNamesSun: "So; Mo; Di; Mi; Do; Fr; Sa", }, }; @@ -421,8 +438,23 @@ function formatDateHeader(date: Date, locale: string = "en-US"): string { return date.toLocaleDateString(locale, { day: "numeric", month: "short" }); // e.g. 12. Feb. } -function getDayName(date: Date, locale: string = "en-US"): string { - return date.toLocaleDateString(locale, { weekday: "long" }).toUpperCase(); +function getDayName(date: Date, locale: string = "en-US", format?: string, customNames?: string, weekStartDay: number = 0): string { + if (format === "custom" && customNames) { + // Split by comma or semicolon to allow spaces in names + const names = customNames.split(/[,;]+/).map(s => s.trim()).filter(Boolean); + if (names.length === 7) { + // Adjust index based on weekStartDay (0=Sun, 1=Mon) + const index = (date.getDay() - weekStartDay + 7) % 7; + return names[index].toUpperCase(); + } + } + + const weekdayOption = format === "narrow" ? "narrow" : (format === "short" ? "short" : "long"); + try { + return date.toLocaleDateString(locale, { weekday: weekdayOption }).toUpperCase(); + } catch (e) { + return date.toLocaleDateString("en-US", { weekday: weekdayOption }).toUpperCase(); + } } function isSameDay(d1: Date, d2: Date): boolean { @@ -761,6 +793,8 @@ export default function WeeklyView() { showTaskCheckboxes?: boolean; quoteSourceUrls?: string[]; startDayOffset?: number; + weekdayFormat?: "long" | "short" | "narrow" | "custom"; + customWeekdayNames?: string; }>({ name: session?.user?.name || "", email: session?.user?.email || "", @@ -874,6 +908,8 @@ export default function WeeklyView() { const [showFocusMode, setShowFocusMode] = useState(false); const [showSchedule, setShowSchedule] = useState(true); const [focusBreakDuration, setFocusBreakDuration] = useState(5); + const [weekdayFormat, setWeekdayFormat] = useState<"long" | "short" | "narrow" | "custom">("long"); + const [customWeekdayNames, setCustomWeekdayNames] = useState(""); // New UI State const [isSearchOpen, setIsSearchOpen] = useState(false); @@ -1545,6 +1581,8 @@ export default function WeeklyView() { setShowSomeday(newSettings.showSomeday); setShowAllDay(newSettings.showAllDayEvents); setShowSchedule(newSettings.showSchedule); + if (newSettings.weekdayFormat) setWeekdayFormat(newSettings.weekdayFormat); + if (newSettings.customWeekdayNames !== undefined) setCustomWeekdayNames(newSettings.customWeekdayNames); setHeadlineFont(newSettings.headlineFont); setHeadlineFontSize(newSettings.headlineFontSize); setHeadlineFontWeight(newSettings.headlineFontWeight); @@ -1626,6 +1664,12 @@ export default function WeeklyView() { else if (width <= 1024) setViewDays(Math.min(v, 5)); else setViewDays(v); } + if (data.user.weekdayFormat) { + setProfile((prev: any) => ({ ...prev, weekdayFormat: data.user.weekdayFormat })); + } + if (data.user.customWeekdayNames) { + setProfile((prev: any) => ({ ...prev, customWeekdayNames: data.user.customWeekdayNames })); + } const cookieCellDuration = getCookie("setting_cellDuration"); if (cookieCellDuration) setCellDuration(Number(cookieCellDuration) as CellDuration); const cookieStartHour = getCookie("setting_startHour"); @@ -1643,6 +1687,10 @@ export default function WeeklyView() { setShowAllDay(data.user.showAllDayEvents); if (data.user.showSchedule !== undefined) setShowSchedule(data.user.showSchedule); + if (data.user.weekdayFormat) + setWeekdayFormat(data.user.weekdayFormat as any); + if (data.user.customWeekdayNames) + setCustomWeekdayNames(data.user.customWeekdayNames); if (data.user.hourLabelFormat) setHourLabelFormat(data.user.hourLabelFormat as "short" | "full"); if (data.user.showSubHourSlots !== undefined) @@ -4748,11 +4796,12 @@ export default function WeeklyView() { style={{ visibility: "hidden", pointerEvents: "none", display: "flex", + width: "100%", alignItems: activeDateLayout === "above" || activeDateLayout === "below" ? (profile.dateAlignment === "left" ? "flex-start" : profile.dateAlignment === "right" ? "flex-end" : "center") - : "baseline", + : "center", justifyContent: profile.dateAlignment === "left" ? "flex-start" @@ -4836,11 +4885,12 @@ export default function WeeklyView() {
{activeDateLayout === "left" && ( - + {formatDateHeader(date, language)} )}

- {getDayName(date, language)} + {getDayName(date, language, weekdayFormat, customWeekdayNames, weekStartDay)}

{(activeDateLayout === "right" || activeDateLayout === "above" || activeDateLayout === "below" || activeDateLayout === undefined) && ( - + {formatDateHeader(date, language)} )} @@ -7007,6 +7057,12 @@ function TaskItem({ > {task.title} + {/* DEBUG: always show provider info */} + {!isSomeday && ( + + [{task.externalProvider || "no-ext"}] + + )} {task.externalProvider && !isSomeday && ( void; dateLayout?: "above" | "below" | "left" | "right" | "hidden"; mobileDateLayout?: "above" | "below" | "left" | "right" | "hidden"; + weekdayFormat?: "long" | "short" | "narrow" | "custom"; + customWeekdayNames?: string; dateAlignment?: "left" | "center" | "right" | "tight"; hourLabelFormat: "short" | "full"; setHourLabelFormat: (fmt: "short" | "full") => void; @@ -7880,7 +7938,7 @@ function SettingsSidebar({ onProjectsChanged, }: SettingsSidebarProps) { const [activeTab, setActiveTab] = useState< - "calendar" | "general" | "account" | "styling" | "motivation" | "about" + "calendar" | "general" | "account" | "styling" | "motivation" | "about" | "localisation" >(initialTab || "general"); const [isLoading, setIsLoading] = useState(true); const [isSyncing, setIsSyncing] = useState(false); @@ -8013,6 +8071,8 @@ function SettingsSidebar({ quoteSourceUrls?: string[]; startDayOffset?: number; id?: string; + weekdayFormat?: "long" | "short" | "narrow" | "custom"; + customWeekdayNames?: string; }>({ name: "", email: "", @@ -8066,6 +8126,9 @@ function SettingsSidebar({ dateColor: "#888888", taskColor: "#333333", todayHighlightColor: "#f0fafa", + pastDayColor: "#a6a6a7", + weekdayFormat: "long", + customWeekdayNames: "", }); const t = translations[profile.language || "en"] || translations["en"]; @@ -8175,6 +8238,8 @@ function SettingsSidebar({ dayHeaderGap: profile.dayHeaderGap, showTaskCheckboxes: profile.showTaskCheckboxes, startDayOffset: profile.startDayOffset, + weekdayFormat: profile.weekdayFormat, + customWeekdayNames: profile.customWeekdayNames, } as any); }, [profile, showTimeGrid, cellDuration, viewStyle, fontSize, showNextTask, showSomeday, showAllDay, showSchedule]); @@ -8211,6 +8276,8 @@ function SettingsSidebar({ ? data.user.showTimeGrid : true, cellDuration: data.user.cellDuration || 30, + weekdayFormat: data.user.weekdayFormat || "long", + customWeekdayNames: data.user.customWeekdayNames || "", viewStyle: data.user.viewStyle || "list", fontSize: data.user.fontSize || "M", headlineFont: data.user.headlineFont || "Inter", @@ -8671,6 +8738,7 @@ function SettingsSidebar({ > {([ { key: "general", icon: , label: t.general }, + { key: "localisation", icon: , label: t.localisation }, { key: "calendar", icon: , label: t.calendar }, { key: "account", icon: , label: t.account }, { key: "styling", icon: , label: "Styling" }, @@ -8999,88 +9067,64 @@ function SettingsSidebar({ />
+ + {/* Hour Label Format */} +
+ + +
+ + {/* Sub-hour Slot Labels */} +
+ { + setShowSubHourSlots(e.target.checked); + saveSetting("showSubHourSlots", e.target.checked); + }} + style={{ width: "16px", height: "16px" }} + /> + +
)} -
-

- {t.localization} -

- {/* Start Week Setting + Start View On */} -
-
- -
- - -
-
-
- -
- - -
-
-
@@ -9135,150 +9179,6 @@ function SettingsSidebar({
-
- - -
- -
- - -
- -
- - -
- - {/* Hour Label Format */} -
- - -
- - {/* Sub-hour Slot Labels */} -
- { - setShowSubHourSlots(e.target.checked); - saveSetting("showSubHourSlots", e.target.checked); - }} - style={{ width: "16px", height: "16px" }} - /> - -
- {/* Projects Section */}

@@ -9429,6 +9329,222 @@ function SettingsSidebar({ )}

+ ) : activeTab === "localisation" ? ( +
+

+ {t.localisation || "Localisation"} +

+ + {/* Start Week Setting + Start View On */} +
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+ + {/* Weekday Format */} +
+ +
+ + + {profile.weekdayFormat === "custom" && ( + setProfile({ ...profile, customWeekdayNames: e.target.value })} + placeholder={ + weekStartDay === 1 + ? (t.customWeekdayNamesMon || translations["en"].customWeekdayNamesMon) + : (t.customWeekdayNamesSun || translations["en"].customWeekdayNamesSun) + } + className="weekly-input" + style={{ width: "100%", padding: "8px", fontSize: "0.85rem", border: "1px solid var(--weekly-settings-input-border)", borderRadius: "4px", background: "var(--weekly-settings-input-bg)", color: "var(--weekly-settings-text)" }} + /> + )} +
+
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+
) : activeTab === "calendar" ? ( isLoading ? (

Loading connections...

@@ -10190,9 +10306,9 @@ function SettingsSidebar({ type="text" value={profile.headlineFontSize || "1.25rem"} onChange={(e) => setProfile({ ...profile, headlineFontSize: e.target.value })} - placeholder="1.25rem" + placeholder="Font size (e.g. 1.25rem)" className="weekly-input" - style={{ flex: 1, padding: "8px", fontSize: "0.9rem", border: "1px solid var(--weekly-settings-input-border)", borderRadius: "4px", background: "var(--weekly-settings-input-bg)", color: "var(--weekly-settings-text)" }} + style={{ flex: 1, padding: "8px", fontSize: "0.85rem", border: "1px solid var(--weekly-settings-input-border)", borderRadius: "4px", background: "var(--weekly-settings-input-bg)", color: "var(--weekly-settings-text)" }} />