From 1334656300b31e1a6afbc62eeca1f40078774d9d Mon Sep 17 00:00:00 2001 From: mARTin Date: Wed, 11 Mar 2026 08:31:53 +0100 Subject: [PATCH] feat: inline unsync confirmation and sync all/unsync all buttons Replace browser confirm() popup when unchecking synced task lists with an inline red confirmation banner. Add sync all / unsync all buttons per provider for batch operations. i18n for EN, DE, FR, ES, IT. v1.26.0 Co-Authored-By: Claude Opus 4.6 --- package.json | 2 +- src/components/WeeklyView.tsx | 266 +++++++++++++++++++++++++++++----- 2 files changed, 231 insertions(+), 37 deletions(-) diff --git a/package.json b/package.json index c12ee99..e63a4ea 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "my-weekly-todo-list", - "version": "1.25.2", + "version": "1.26.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/WeeklyView.tsx b/src/components/WeeklyView.tsx index f90ce1c..7f9460f 100644 --- a/src/components/WeeklyView.tsx +++ b/src/components/WeeklyView.tsx @@ -345,6 +345,11 @@ const translations: Record = { connectOutlook: "Connect Outlook", syncTasks: "Sync Tasks", syncTasksDesc: "Sync tasks with Google Tasks or Microsoft To-Do.", + unsyncConfirmMsg: "Stop syncing \"{title}\"? Its tasks will be moved to trash.", + unsyncConfirm: "Stop syncing", + unsyncCancel: "Cancel", + syncAll: "Sync all", + unsyncAll: "Unsync all", fetchingLists: "(fetching lists...)", listHeader: "List", syncHeader: "Sync", @@ -533,6 +538,11 @@ const translations: Record = { connectOutlook: "Outlook verbinden", syncTasks: "Aufgaben synchronisieren", syncTasksDesc: "Aufgaben mit Google Tasks oder Microsoft To-Do synchronisieren.", + unsyncConfirmMsg: "Synchronisierung von \"{title}\" beenden? Die Aufgaben werden in den Papierkorb verschoben.", + unsyncConfirm: "Sync beenden", + unsyncCancel: "Abbrechen", + syncAll: "Alle synchronisieren", + unsyncAll: "Alle trennen", fetchingLists: "(Listen werden geladen...)", listHeader: "Liste", syncHeader: "Sync", @@ -720,6 +730,11 @@ const translations: Record = { connectOutlook: "Connecter Outlook", syncTasks: "Synchroniser les tâches", syncTasksDesc: "Synchronisez les tâches avec Google Tasks ou Microsoft To-Do.", + unsyncConfirmMsg: "Arrêter la synchronisation de \"{title}\" ? Ses tâches seront mises à la corbeille.", + unsyncConfirm: "Arrêter la sync", + unsyncCancel: "Annuler", + syncAll: "Tout synchroniser", + unsyncAll: "Tout désynchroniser", fetchingLists: "(chargement des listes...)", listHeader: "Liste", syncHeader: "Sync", @@ -907,6 +922,11 @@ const translations: Record = { connectOutlook: "Conectar Outlook", syncTasks: "Sincronizar tareas", syncTasksDesc: "Sincroniza tareas con Google Tasks o Microsoft To-Do.", + unsyncConfirmMsg: "¿Dejar de sincronizar \"{title}\"? Sus tareas se moverán a la papelera.", + unsyncConfirm: "Dejar de sincronizar", + unsyncCancel: "Cancelar", + syncAll: "Sincronizar todo", + unsyncAll: "Desincronizar todo", fetchingLists: "(cargando listas...)", listHeader: "Lista", syncHeader: "Sync", @@ -1094,6 +1114,11 @@ const translations: Record = { connectOutlook: "Connetti Outlook", syncTasks: "Sincronizza attività", syncTasksDesc: "Sincronizza le attività con Google Tasks o Microsoft To-Do.", + unsyncConfirmMsg: "Interrompere la sincronizzazione di \"{title}\"? Le attività verranno spostate nel cestino.", + unsyncConfirm: "Interrompi sync", + unsyncCancel: "Annulla", + syncAll: "Sincronizza tutto", + unsyncAll: "Desincronizza tutto", fetchingLists: "(caricamento liste...)", listHeader: "Lista", syncHeader: "Sync", @@ -1541,6 +1566,10 @@ export default function WeeklyView() { type: "success" | "error"; text: string; } | null>(null); + const [unsyncConfirm, setUnsyncConfirm] = useState<{ + provider: "google" | "apple" | "outlook" | "synology"; + list: { id: string; title: string }; + } | null>(null); const [isImportModalOpen, setIsImportModalOpen] = useState(false); const [importProvider, setImportProvider] = useState< "google" | "apple" | "outlook" | "synology" | null @@ -3294,38 +3323,85 @@ export default function WeeklyView() { ); if (existing) { - // Unsync/Remove - if ( - !confirm( - `Are you sure you want to stop syncing the list "${list.title}"? This will move its tasks to the trash.`, - ) - ) { - return; - } - try { - const res = await fetch(`/api/someday-lists?id=${existing.id}`, { - method: "DELETE", - }); - if (res.ok) { - setSomedayLists((prev) => prev.filter((l) => l.id !== existing.id)); - setImportStatusMsg({ - type: "success", - text: `Stopped syncing "${list.title}".`, - }); - } - } catch (error) { - console.error("Failed to delete list", error); - setImportStatusMsg({ - type: "error", - text: "Failed to stop syncing list.", - }); - } + // Show inline confirmation instead of browser confirm() + setUnsyncConfirm({ provider, list }); + return; } else { // Sync/Import await doImport(provider, [list]); } }; + const confirmUnsync = async () => { + if (!unsyncConfirm) return; + const { provider, list } = unsyncConfirm; + const existing = somedayLists.find( + (l) => l.externalId === list.id && l.externalProvider === provider, + ); + if (!existing) { setUnsyncConfirm(null); return; } + try { + const res = await fetch(`/api/someday-lists?id=${existing.id}`, { + method: "DELETE", + }); + if (res.ok) { + setSomedayLists((prev) => prev.filter((l) => l.id !== existing.id)); + setImportStatusMsg({ + type: "success", + text: `Stopped syncing "${list.title}".`, + }); + } + } catch (error) { + console.error("Failed to delete list", error); + setImportStatusMsg({ + type: "error", + text: "Failed to stop syncing list.", + }); + } + setUnsyncConfirm(null); + }; + + const handleSyncAll = async ( + provider: "google" | "outlook" | "synology", + lists: { id: string; title: string }[], + syncOn: boolean, + ) => { + if (syncOn) { + const unsynced = lists.filter( + (list) => !somedayLists.some( + (sl) => sl.externalId === list.id && sl.externalProvider === provider, + ), + ); + if (unsynced.length > 0) await doImport(provider, unsynced); + } else { + // Unsync all synced lists + const synced = lists.filter( + (list) => somedayLists.some( + (sl) => sl.externalId === list.id && sl.externalProvider === provider, + ), + ); + for (const list of synced) { + const existing = somedayLists.find( + (l) => l.externalId === list.id && l.externalProvider === provider, + ); + if (!existing) continue; + try { + const res = await fetch(`/api/someday-lists?id=${existing.id}`, { + method: "DELETE", + }); + if (res.ok) { + setSomedayLists((prev) => prev.filter((l) => l.id !== existing.id)); + } + } catch (error) { + console.error("Failed to unsync list", error); + } + } + setImportStatusMsg({ + type: "success", + text: `Stopped syncing ${synced.length} list(s).`, + }); + } + }; + // Core import logic — accepts provider directly so it works both from modal and sidebar const doImport = async ( provider: "google" | "apple" | "outlook" | "synology", @@ -7543,6 +7619,10 @@ export default function WeeklyView() { isFetchingProviderLists={isFetchingProviderLists} somedayLists={somedayLists} handleToggleTaskList={handleToggleTaskList} + unsyncConfirm={unsyncConfirm} + onConfirmUnsync={confirmUnsync} + onCancelUnsync={() => setUnsyncConfirm(null)} + handleSyncAll={handleSyncAll} fetchAvailableTaskLists={fetchAvailableTaskLists} setCurrentWeekStart={setCurrentWeekStart} projects={projects} @@ -8823,6 +8903,17 @@ interface SettingsSidebarProps { provider: "google" | "apple" | "outlook" | "synology", list: { id: string; title: string }, ) => Promise; + unsyncConfirm: { + provider: "google" | "apple" | "outlook" | "synology"; + list: { id: string; title: string }; + } | null; + onConfirmUnsync: () => Promise; + onCancelUnsync: () => void; + handleSyncAll: ( + provider: "google" | "outlook" | "synology", + lists: { id: string; title: string }[], + syncOn: boolean, + ) => Promise; fetchAvailableTaskLists: ( provider: "google" | "apple" | "outlook" | "synology", ) => Promise; @@ -9038,6 +9129,10 @@ function SettingsSidebar({ isFetchingProviderLists, somedayLists, handleToggleTaskList, + unsyncConfirm, + onConfirmUnsync, + onCancelUnsync, + handleSyncAll, fetchAvailableTaskLists, initialTab, setCurrentWeekStart, @@ -11118,22 +11213,121 @@ function SettingsSidebar({ gap: "4px", }} > - {/* Column header */} - {providerLists.length > 0 && ( + {/* Column header with sync all / unsync all */} + {providerLists.length > 0 && (() => { + const allSynced = providerLists.every( + (list: { id: string; title: string }) => somedayLists.some( + (sl: SomedayList) => sl.externalId === list.id && sl.externalProvider === conn.provider, + ), + ); + const noneSynced = providerLists.every( + (list: { id: string; title: string }) => !somedayLists.some( + (sl: SomedayList) => sl.externalId === list.id && sl.externalProvider === conn.provider, + ), + ); + return ( +
+ List + {!allSynced && ( + + )} + {!noneSynced && ( + + )} + Sync +
+ ); + })()} + + {/* Inline unsync confirmation */} + {unsyncConfirm && unsyncConfirm.provider === conn.provider && (
- List - Sync + + {t.unsyncConfirmMsg.replace("{title}", unsyncConfirm.list.title)} + + +
)} + {providerLists.map((list: { id: string; title: string }) => { const isSynced = somedayLists.some( (sl: SomedayList) =>