From 49bb6e06f5ea83a10a273ddf794ea8be9ed64ab2 Mon Sep 17 00:00:00 2001 From: natreex Date: Mon, 9 Feb 2026 17:12:03 -0500 Subject: [PATCH] Add `deletedAt` timestamps to delete operations for better audit tracking - Updated delete methods across hooks and components to include `deletedAt: System.timeStampInSeconds()`. - Refactored synchronized delete logic to pass `deletedAt` for both offline and online states. - Improved synchronization workflows to include `deletedAt` in server and IPC requests. - Enhanced destructuring patterns for cleaner and more consistent request data. --- app/page.tsx | 78 +++++++++---------- components/SyncBook.tsx | 6 +- .../settings/locations/LocationComponent.tsx | 27 ++++--- components/book/settings/story/Act.tsx | 6 +- components/book/settings/story/Issue.tsx | 4 + .../book/settings/world/WorldElement.tsx | 9 ++- components/leftbar/ScribeChapterComponent.tsx | 11 ++- components/series/SeriesSettingSidebar.tsx | 2 +- .../series/settings/SeriesBooksManager.tsx | 3 +- hooks/settings/useCharacters.ts | 8 +- hooks/settings/useLocations.ts | 15 ++-- hooks/settings/useSpells.ts | 51 ++++++++---- hooks/useSyncBooks.ts | 7 ++ hooks/useSyncSeries.ts | 11 ++- 14 files changed, 146 insertions(+), 92 deletions(-) diff --git a/app/page.tsx b/app/page.tsx index 462c74c..0373c15 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1,5 +1,5 @@ 'use client'; -import {useContext, useEffect, useState} from 'react'; +import {useCallback, useContext, useEffect, useRef, useState} from 'react'; import {BookContext} from "@/context/BookContext"; import {ChapterProps} from "@/lib/models/Chapter"; import {ChapterContext} from '@/context/ChapterContext'; @@ -67,59 +67,57 @@ const messagesMap = { function AutoSyncOnReconnect() { const {offlineMode} = useContext(OfflineContext); - const {syncAllToServer: syncAllBooksToServer, refreshBooks, booksToSyncToServer} = useSyncBooks(); - const {syncAllToServer: syncAllSeriesToServer, refreshSeries, seriesToSyncToServer} = useSyncSeries(); - const [pendingSync, setPendingSync] = useState(false); - const [isRefreshing, setIsRefreshing] = useState(false); + const {session} = useContext(SessionContext); + const {syncAllToServer: syncAllBooksToServer, syncAllFromServer: syncAllBooksFromServer, refreshBooks, booksToSyncToServer, booksToSyncFromServer} = useSyncBooks(); + const {syncAllToServer: syncAllSeriesToServer, syncAllFromServer: syncAllSeriesFromServer, refreshSeries, seriesToSyncToServer, seriesToSyncFromServer} = useSyncSeries(); + const isSyncingRef = useRef(false); + const hasRefreshedRef = useRef(false); - const saveLastOnlineTimestamp = (): void => { + const saveLastOnlineTimestamp = useCallback((): void => { const timestamp: number = Math.floor(Date.now() / 1000); localStorage.setItem('lastOnlineTimestamp', timestamp.toString()); - }; + }, []); + // Refresh sync data when online + authenticated + DB ready useEffect((): void => { - if (!offlineMode.isOffline) { - setPendingSync(true); - setIsRefreshing(true); - Promise.all([refreshBooks(), refreshSeries()]).then(() => { - setIsRefreshing(false); + if (!offlineMode.isOffline && session.isConnected && offlineMode.isDatabaseInitialized) { + hasRefreshedRef.current = true; + Promise.all([refreshBooks(), refreshSeries()]); + } + }, [offlineMode.isOffline, session.isConnected, offlineMode.isDatabaseInitialized]); + + // Auto-sync when diffs become available (reactive, no flags) + useEffect((): void => { + if (offlineMode.isOffline || !session.isConnected || isSyncingRef.current || !hasRefreshedRef.current) return; + + const syncPromises: Promise[] = []; + + if (booksToSyncToServer.length > 0) syncPromises.push(syncAllBooksToServer()); + if (booksToSyncFromServer.length > 0) syncPromises.push(syncAllBooksFromServer()); + if (seriesToSyncToServer.length > 0) syncPromises.push(syncAllSeriesToServer()); + if (seriesToSyncFromServer.length > 0) syncPromises.push(syncAllSeriesFromServer()); + + if (syncPromises.length > 0) { + isSyncingRef.current = true; + Promise.all(syncPromises).then((): void => { + saveLastOnlineTimestamp(); + isSyncingRef.current = false; + }).catch((): void => { + isSyncingRef.current = false; }); } - }, [offlineMode.isOffline]); - - useEffect((): void => { - if (pendingSync && !isRefreshing) { - const syncPromises: Promise[] = []; - - if (booksToSyncToServer.length > 0) { - syncPromises.push(syncAllBooksToServer()); - } - if (seriesToSyncToServer.length > 0) { - syncPromises.push(syncAllSeriesToServer()); - } - - if (syncPromises.length > 0) { - Promise.all(syncPromises).then(() => { - saveLastOnlineTimestamp(); - }); - } else { - saveLastOnlineTimestamp(); - } - - setPendingSync(false); - } - }, [booksToSyncToServer, seriesToSyncToServer, pendingSync, isRefreshing]); + }, [booksToSyncToServer, booksToSyncFromServer, seriesToSyncToServer, seriesToSyncFromServer]); // Update lastOnlineTimestamp every 5 minutes while online useEffect((): (() => void) | void => { - if (!offlineMode.isOffline) { + if (!offlineMode.isOffline && session.isConnected) { const intervalId: NodeJS.Timeout = setInterval((): void => { saveLastOnlineTimestamp(); - }, 5 * 60 * 1000); // 5 minutes + }, 5 * 60 * 1000); return (): void => clearInterval(intervalId); } - }, [offlineMode.isOffline]); + }, [offlineMode.isOffline, session.isConnected, saveLastOnlineTimestamp]); return null; } @@ -284,8 +282,6 @@ function ScribeContent() { useEffect((): void => { if (session.isConnected) { - refreshBooks().then() - refreshSeries().then() setIsTermsAccepted(session.user?.termsAccepted ?? false); setHomeStepsGuide(User.guideTourDone(session.user?.guideTour ?? [], 'home-basic')); setIsLoading(false); diff --git a/components/SyncBook.tsx b/components/SyncBook.tsx index c9f4d7f..9fdcc29 100644 --- a/components/SyncBook.tsx +++ b/components/SyncBook.tsx @@ -1,7 +1,7 @@ import {FontAwesomeIcon} from "@fortawesome/react-fontawesome"; import {faCloud, faCloudArrowDown, faCloudArrowUp, faSpinner} from "@fortawesome/free-solid-svg-icons"; import {useTranslations} from "next-intl"; -import {useState, useContext} from "react"; +import {useState, useEffect, useContext} from "react"; import OfflineContext, {OfflineContextType} from "@/context/OfflineContext"; import {SyncType} from "@/context/BooksSyncContext"; import useSyncBooks from "@/hooks/useSyncBooks"; @@ -18,6 +18,10 @@ export default function SyncBook({bookId, status}: SyncBookProps) { const [currentStatus, setCurrentStatus] = useState(status); const {upload: hookUpload, download: hookDownload, syncFromServer: hookSyncFromServer, syncToServer: hookSyncToServer} = useSyncBooks(); + useEffect((): void => { + setCurrentStatus(status); + }, [status]); + const isOffline: boolean = isCurrentlyOffline(); async function upload(): Promise { diff --git a/components/book/settings/locations/LocationComponent.tsx b/components/book/settings/locations/LocationComponent.tsx index fa0a39e..9eb689d 100644 --- a/components/book/settings/locations/LocationComponent.tsx +++ b/components/book/settings/locations/LocationComponent.tsx @@ -486,8 +486,9 @@ export function LocationComponent(props: LocationComponentProps, ref: React.Ref< let response: boolean; const elementId = sections.find((section: LocationProps): boolean => section.id === sectionId) ?.elements[elementIndex].id; + const deletedAt: number = System.timeStampInSeconds(); if (isSeriesMode) { - const deleteData = {elementId: elementId}; + const deleteData = {elementId: elementId, deletedAt}; if (isCurrentlyOffline() || localSeries) { response = await window.electron.invoke('db:series:location:element:delete', deleteData); } else { @@ -498,16 +499,16 @@ export function LocationComponent(props: LocationComponentProps, ref: React.Ref< } } else if (isCurrentlyOffline() || book?.localBook) { response = await window.electron.invoke('db:location:element:delete', { - elementId: elementId, + elementId: elementId, bookId: currentEntityId, deletedAt, }); } else { response = await System.authDeleteToServer(`location/element/delete`, { - elementId: elementId, + elementId: elementId, bookId: currentEntityId, deletedAt, }, token, lang); if (localSyncedBooks.find((syncedBook: SyncedBook): boolean => syncedBook.id === currentEntityId)) { addToQueue('db:location:element:delete', { - elementId: elementId, + elementId: elementId, bookId: currentEntityId, deletedAt, }); } } @@ -536,8 +537,9 @@ export function LocationComponent(props: LocationComponentProps, ref: React.Ref< try { let response: boolean; const subElementId = sections.find((section: LocationProps): boolean => section.id === sectionId)?.elements[elementIndex].subElements[subElementIndex].id; + const deletedAt: number = System.timeStampInSeconds(); if (isSeriesMode) { - const deleteData = {subElementId: subElementId}; + const deleteData = {subElementId: subElementId, deletedAt}; if (isCurrentlyOffline() || localSeries) { response = await window.electron.invoke('db:series:location:subelement:delete', deleteData); } else { @@ -548,16 +550,16 @@ export function LocationComponent(props: LocationComponentProps, ref: React.Ref< } } else if (isCurrentlyOffline() || book?.localBook) { response = await window.electron.invoke('db:location:subelement:delete', { - subElementId: subElementId, + subElementId: subElementId, bookId: currentEntityId, deletedAt, }); } else { response = await System.authDeleteToServer(`location/sub-element/delete`, { - subElementId: subElementId, + subElementId: subElementId, bookId: currentEntityId, deletedAt, }, token, lang); if (localSyncedBooks.find((syncedBook: SyncedBook): boolean => syncedBook.id === currentEntityId)) { addToQueue('db:location:subelement:delete', { - subElementId: subElementId, + subElementId: subElementId, bookId: currentEntityId, deletedAt, }); } } @@ -581,8 +583,9 @@ export function LocationComponent(props: LocationComponentProps, ref: React.Ref< async function handleRemoveSection(sectionId: string): Promise { try { let response: boolean; + const deletedAt: number = System.timeStampInSeconds(); if (isSeriesMode) { - const deleteData = {locationId: sectionId}; + const deleteData = {locationId: sectionId, deletedAt}; if (isCurrentlyOffline() || localSeries) { response = await window.electron.invoke('db:series:location:delete', deleteData); } else { @@ -593,16 +596,16 @@ export function LocationComponent(props: LocationComponentProps, ref: React.Ref< } } else if (isCurrentlyOffline() || book?.localBook) { response = await window.electron.invoke('db:location:delete', { - locationId: sectionId, + locationId: sectionId, bookId: currentEntityId, deletedAt, }); } else { response = await System.authDeleteToServer(`location/delete`, { - locationId: sectionId, + locationId: sectionId, bookId: currentEntityId, deletedAt, }, token, lang); if (localSyncedBooks.find((syncedBook: SyncedBook): boolean => syncedBook.id === currentEntityId)) { addToQueue('db:location:delete', { - locationId: sectionId, + locationId: sectionId, bookId: currentEntityId, deletedAt, }); } } diff --git a/components/book/settings/story/Act.tsx b/components/book/settings/story/Act.tsx index 64be161..50544bf 100644 --- a/components/book/settings/story/Act.tsx +++ b/components/book/settings/story/Act.tsx @@ -132,7 +132,7 @@ export default function Act({acts, setActs, mainChapters}: ActProps) { async function deleteIncident(actId: number, incidentId: string): Promise { try { let response: boolean; - const deleteData = { bookId, incidentId }; + const deleteData = { bookId, incidentId, deletedAt: System.timeStampInSeconds() }; if (isCurrentlyOffline() || book?.localBook) { response = await window.electron.invoke('db:book:incident:remove', deleteData); } else { @@ -223,7 +223,7 @@ export default function Act({acts, setActs, mainChapters}: ActProps) { async function deletePlotPoint(actId: number, plotPointId: string): Promise { try { let response: boolean; - const deleteData = { plotId: plotPointId }; + const deleteData = { plotId: plotPointId, bookId, deletedAt: System.timeStampInSeconds() }; if (isCurrentlyOffline() || book?.localBook) { response = await window.electron.invoke('db:book:plot:remove', deleteData); } else { @@ -365,7 +365,7 @@ export default function Act({acts, setActs, mainChapters}: ActProps) { ): Promise { try { let response: boolean; - const unlinkData = { chapterInfoId }; + const unlinkData = { chapterInfoId, bookId, deletedAt: System.timeStampInSeconds() }; if (isCurrentlyOffline() || book?.localBook) { response = await window.electron.invoke('db:chapter:information:remove', unlinkData); } else { diff --git a/components/book/settings/story/Issue.tsx b/components/book/settings/story/Issue.tsx index de2b001..f55bca4 100644 --- a/components/book/settings/story/Issue.tsx +++ b/components/book/settings/story/Issue.tsx @@ -88,10 +88,12 @@ export default function Issues({issues, setIssues}: IssuesProps) { try { let response: boolean; + const deletedAt: number = System.timeStampInSeconds(); if (isCurrentlyOffline() || book?.localBook) { response = await window.electron.invoke('db:book:issue:remove', { bookId, issueId, + deletedAt, }); } else { response = await System.authDeleteToServer( @@ -99,6 +101,7 @@ export default function Issues({issues, setIssues}: IssuesProps) { { bookId, issueId, + deletedAt, }, token, lang @@ -108,6 +111,7 @@ export default function Issues({issues, setIssues}: IssuesProps) { addToQueue('db:book:issue:remove', { bookId, issueId, + deletedAt, }); } } diff --git a/components/book/settings/world/WorldElement.tsx b/components/book/settings/world/WorldElement.tsx index b2a9405..50ebc20 100644 --- a/components/book/settings/world/WorldElement.tsx +++ b/components/book/settings/world/WorldElement.tsx @@ -65,8 +65,9 @@ export default function WorldElementComponent({sectionLabel, sectionType}: World try { let response: boolean; const elementId = (worlds[selectedWorldIndex][section] as WorldElement[])[index].id; + const deletedAt: number = System.timeStampInSeconds(); if (isSeriesMode) { - const deleteData = {elementId: elementId}; + const deleteData = {elementId, deletedAt}; if (isCurrentlyOffline() || localSeries) { response = await window.electron.invoke('db:series:world:element:delete', deleteData); } else { @@ -77,16 +78,16 @@ export default function WorldElementComponent({sectionLabel, sectionType}: World } } else if (isCurrentlyOffline() || book?.localBook) { response = await window.electron.invoke('db:book:world:element:remove', { - elementId: elementId, + elementId, bookId: book?.bookId, deletedAt, }); } else { response = await System.authDeleteToServer('book/world/element/delete', { - elementId: elementId, + elementId, bookId: book?.bookId, deletedAt, }, session.accessToken, lang); if (localSyncedBooks.find((syncedBook: SyncedBook): boolean => syncedBook.id === book?.bookId)) { addToQueue('db:book:world:element:remove', { - elementId: elementId, + elementId: elementId, bookId: book?.bookId, deletedAt, }); } } diff --git a/components/leftbar/ScribeChapterComponent.tsx b/components/leftbar/ScribeChapterComponent.tsx index 1841a92..f2a10bf 100644 --- a/components/leftbar/ScribeChapterComponent.tsx +++ b/components/leftbar/ScribeChapterComponent.tsx @@ -188,16 +188,25 @@ export default function ScribeChapterComponent() { try { setDeleteConfirmationMessage(false); let response:boolean = false; + const deletedAt: number = System.timeStampInSeconds(); if (isCurrentlyOffline() || book?.localBook) { - response = await window.electron.invoke('db:chapter:remove', removeChapterId); + response = await window.electron.invoke('db:chapter:remove', { + chapterId: removeChapterId, + bookId: book?.bookId, + deletedAt, + }); } else { response = await System.authDeleteToServer('chapter/remove', { chapterId: removeChapterId, + bookId: book?.bookId, + deletedAt, }, userToken, lang); if (localSyncedBooks.find((syncedBook: SyncedBook): boolean => syncedBook.id === book?.bookId)) { addToQueue('db:chapter:remove', { chapterId: removeChapterId, + bookId: book?.bookId, + deletedAt, }); } } diff --git a/components/series/SeriesSettingSidebar.tsx b/components/series/SeriesSettingSidebar.tsx index 759d5d2..5afd65c 100644 --- a/components/series/SeriesSettingSidebar.tsx +++ b/components/series/SeriesSettingSidebar.tsx @@ -58,7 +58,7 @@ export default function SeriesSettingSidebar( async function handleDeleteSeries(): Promise { try { - const deleteData = {seriesId: seriesId}; + const deleteData = {seriesId: seriesId, deletedAt: System.timeStampInSeconds()}; let success: boolean; if (isCurrentlyOffline() || localSeries) { diff --git a/components/series/settings/SeriesBooksManager.tsx b/components/series/settings/SeriesBooksManager.tsx index e69f413..14537c9 100644 --- a/components/series/settings/SeriesBooksManager.tsx +++ b/components/series/settings/SeriesBooksManager.tsx @@ -174,7 +174,8 @@ function SeriesBooksManager(props: object, ref: React.Ref<{ handleSave: () => Pr try { const removeData = { seriesId: seriesId, - bookId: bookId + bookId: bookId, + deletedAt: System.timeStampInSeconds(), }; let response: boolean; diff --git a/hooks/settings/useCharacters.ts b/hooks/settings/useCharacters.ts index 4992444..d6d0605 100644 --- a/hooks/settings/useCharacters.ts +++ b/hooks/settings/useCharacters.ts @@ -523,10 +523,11 @@ export function useCharacters(config: UseCharactersConfig): UseCharactersReturn const deleteCharacter = useCallback(async function (characterId: string): Promise { try { let response: boolean; - const requestData = {characterId: characterId}; + const deletedAt: number = System.timeStampInSeconds(); if (isSeriesMode) { // Series mode - dual logic + const requestData = {characterId, deletedAt}; if (isCurrentlyOffline() || localSeries) { response = await window.electron.invoke('db:series:character:delete', requestData); } else { @@ -537,6 +538,7 @@ export function useCharacters(config: UseCharactersConfig): UseCharactersReturn } } else { // Pattern A: mutations + const requestData = {characterId, bookId: entityId, deletedAt}; if (isCurrentlyOffline() || book?.localBook) { // Offline OU livre local → IPC response = await window.electron.invoke('db:character:delete', requestData); @@ -649,11 +651,12 @@ export function useCharacters(config: UseCharactersConfig): UseCharactersReturn setSelectedCharacter({...selectedCharacter, [section]: updatedSection}); } else { try { - const requestData = {attributeId: attrId}; + const deletedAt: number = System.timeStampInSeconds(); let response: boolean; if (isSeriesMode) { // Series mode - dual logic + const requestData = {attributeId: attrId, deletedAt}; if (isCurrentlyOffline() || localSeries) { response = await window.electron.invoke('db:series:character:attribute:delete', requestData); } else { @@ -664,6 +667,7 @@ export function useCharacters(config: UseCharactersConfig): UseCharactersReturn } } else { // Pattern A: mutations + const requestData = {attributeId: attrId, bookId: entityId, deletedAt}; if (isCurrentlyOffline() || book?.localBook) { // Offline OU livre local → IPC response = await window.electron.invoke('db:character:attribute:delete', requestData); diff --git a/hooks/settings/useLocations.ts b/hooks/settings/useLocations.ts index ded3da3..6e55710 100644 --- a/hooks/settings/useLocations.ts +++ b/hooks/settings/useLocations.ts @@ -514,9 +514,10 @@ export function useLocations(config: UseLocationsConfig): UseLocationsReturn { const removeSection = useCallback(async function (sectionId: string): Promise { try { let success: boolean; + const deletedAt: number = System.timeStampInSeconds(); if (isSeriesMode) { // Series mode - dual logic - const deleteData = {locationId: sectionId}; + const deleteData = {locationId: sectionId, deletedAt}; if (isCurrentlyOffline() || localSeries) { success = await window.electron.invoke('db:series:location:delete', deleteData); } else { @@ -527,7 +528,7 @@ export function useLocations(config: UseLocationsConfig): UseLocationsReturn { } } else { const requestData = { - locationId: sectionId, + locationId: sectionId, bookId: entityId, deletedAt, }; if (isCurrentlyOffline() || book?.localBook) { success = await window.electron.invoke('db:location:delete', requestData); @@ -563,9 +564,10 @@ export function useLocations(config: UseLocationsConfig): UseLocationsReturn { return section.id === sectionId; })?.elements[elementIndex].id; let success: boolean; + const deletedAt: number = System.timeStampInSeconds(); if (isSeriesMode) { // Series mode - dual logic - const deleteData = {elementId: elementId}; + const deleteData = {elementId, deletedAt}; if (isCurrentlyOffline() || localSeries) { success = await window.electron.invoke('db:series:location:element:delete', deleteData); } else { @@ -576,7 +578,7 @@ export function useLocations(config: UseLocationsConfig): UseLocationsReturn { } } else { const requestData = { - elementId: elementId, + elementId, bookId: entityId, deletedAt, }; if (isCurrentlyOffline() || book?.localBook) { success = await window.electron.invoke('db:location:element:delete', requestData); @@ -615,9 +617,10 @@ export function useLocations(config: UseLocationsConfig): UseLocationsReturn { return section.id === sectionId; })?.elements[elementIndex].subElements[subElementIndex].id; let success: boolean; + const deletedAt: number = System.timeStampInSeconds(); if (isSeriesMode) { // Series mode - dual logic - const deleteData = {subElementId: subElementId}; + const deleteData = {subElementId, deletedAt}; if (isCurrentlyOffline() || localSeries) { success = await window.electron.invoke('db:series:location:subelement:delete', deleteData); } else { @@ -628,7 +631,7 @@ export function useLocations(config: UseLocationsConfig): UseLocationsReturn { } } else { const requestData = { - subElementId: subElementId, + subElementId, bookId: entityId, deletedAt, }; if (isCurrentlyOffline() || book?.localBook) { success = await window.electron.invoke('db:location:subelement:delete', requestData); diff --git a/hooks/settings/useSpells.ts b/hooks/settings/useSpells.ts index 762bb09..1cd4f68 100644 --- a/hooks/settings/useSpells.ts +++ b/hooks/settings/useSpells.ts @@ -296,12 +296,20 @@ export function useSpells(config: UseSpellsConfig): UseSpellsReturn { }); if (response.seriesSpellId) { - const seriesSpellResponse: SeriesSpellDetailResponse = await System.authGetQueryToServer( - 'series/spell/detail', - userToken, - lang, - {spellid: response.seriesSpellId} - ); + let seriesSpellResponse: SeriesSpellDetailResponse; + if (isCurrentlyOffline() || book?.localBook) { + seriesSpellResponse = await window.electron.invoke( + 'db:series:spell:detail', + {spellId: response.seriesSpellId} + ); + } else { + seriesSpellResponse = await System.authGetQueryToServer( + 'series/spell/detail', + userToken, + lang, + {spellid: response.seriesSpellId} + ); + } if (seriesSpellResponse) { setSelectedSeriesSpell(seriesSpellResponse); } @@ -536,9 +544,10 @@ export function useSpells(config: UseSpellsConfig): UseSpellsReturn { const deleteSpell = useCallback(async function (spellId: string): Promise { try { let success: boolean; - const requestData = {spellId}; + const deletedAt: number = System.timeStampInSeconds(); if (isSeriesMode) { // Series mode - dual logic + const requestData = {spellId, deletedAt}; if (isCurrentlyOffline() || localSeries) { success = await window.electron.invoke('db:series:spell:delete', requestData); } else { @@ -548,6 +557,7 @@ export function useSpells(config: UseSpellsConfig): UseSpellsReturn { } } } else { + const requestData = {spellId, bookId: entityId, deletedAt}; if (isCurrentlyOffline() || book?.localBook) { success = await window.electron.invoke('db:spell:delete', requestData); } else { @@ -844,9 +854,10 @@ export function useSpells(config: UseSpellsConfig): UseSpellsReturn { const deleteTag = useCallback(async function (tagId: string): Promise { try { let success: boolean; + const deletedAt: number = System.timeStampInSeconds(); if (isSeriesMode) { // Series mode - dual logic - const deleteData = {tagId}; + const deleteData = {tagId, deletedAt}; if (isCurrentlyOffline() || localSeries) { success = await window.electron.invoke('db:series:spell:tag:delete', deleteData); } else { @@ -856,7 +867,7 @@ export function useSpells(config: UseSpellsConfig): UseSpellsReturn { } } } else { - const requestData = {tagId, bookId: entityId}; + const requestData = {tagId, bookId: entityId, deletedAt}; if (isCurrentlyOffline() || book?.localBook) { success = await window.electron.invoke('db:spell:tag:delete', requestData); } else { @@ -885,17 +896,25 @@ export function useSpells(config: UseSpellsConfig): UseSpellsReturn { const handleSyncComplete = useCallback(async function (): Promise { if (selectedSpell?.seriesSpellId) { - const seriesSpellResponse: SeriesSpellDetailResponse = await System.authGetQueryToServer( - 'series/spell/detail', - userToken, - lang, - {spellid: selectedSpell.seriesSpellId} - ); + let seriesSpellResponse: SeriesSpellDetailResponse; + if (isCurrentlyOffline() || (isSeriesMode ? localSeries : book?.localBook)) { + seriesSpellResponse = await window.electron.invoke( + 'db:series:spell:detail', + {spellId: selectedSpell.seriesSpellId} + ); + } else { + seriesSpellResponse = await System.authGetQueryToServer( + 'series/spell/detail', + userToken, + lang, + {spellid: selectedSpell.seriesSpellId} + ); + } if (seriesSpellResponse) { setSelectedSeriesSpell(seriesSpellResponse); } } - }, [selectedSpell?.seriesSpellId, userToken, lang]); + }, [selectedSpell?.seriesSpellId, userToken, lang, isCurrentlyOffline, isSeriesMode, localSeries, book?.localBook]); const enterDetailMode = useCallback(async function (spell: SpellListItem): Promise { await selectSpell(spell); diff --git a/hooks/useSyncBooks.ts b/hooks/useSyncBooks.ts index a0fbe6f..51fdae8 100644 --- a/hooks/useSyncBooks.ts +++ b/hooks/useSyncBooks.ts @@ -186,6 +186,12 @@ export default function useSyncBooks() { } } + async function syncAllFromServer(): Promise { + for (const diff of booksToSyncFromServer) { + await syncFromServer(diff.id); + } + } + async function refreshBooks(): Promise { try { let localBooksResponse: SyncedBook[] = []; @@ -250,6 +256,7 @@ export default function useSyncBooks() { syncFromServer, syncToServer, syncAllToServer, + syncAllFromServer, refreshBooks, localOnlyBooks, serverOnlyBooks, diff --git a/hooks/useSyncSeries.ts b/hooks/useSyncSeries.ts index 022d67f..d346c1e 100644 --- a/hooks/useSyncSeries.ts +++ b/hooks/useSyncSeries.ts @@ -275,10 +275,12 @@ export default function useSyncSeries() { } } - /** - * Refreshes the sync status of all series by comparing local and server data. - * Updates the context with the latest sync information. - */ + async function syncAllFromServer(): Promise { + for (const diff of seriesToSyncFromServer) { + await syncFromServer(diff.id); + } + } + async function refreshSeries(): Promise { try { let localSeriesResponse: SyncedSeries[] = []; @@ -343,6 +345,7 @@ export default function useSyncSeries() { syncFromServer, syncToServer, syncAllToServer, + syncAllFromServer, refreshSeries, localOnlySeries, serverOnlySeries,