Files
ERitors-Scribe-Desktop/components/series/SeriesSettingSidebar.tsx
natreex dbbe33b19b Refactor and extend offline synchronization logic across components and services
- Integrated sync queue mechanisms with `LocalSyncQueueContext` for offline data handling.
- Updated key sync-related services (e.g., book, chapter, series) to support offline-first functionality.
- Removed redundant database fetch methods to optimize repository logic and improve maintainability.
- Enhanced Tauri IPC usage for sync operations and removed legacy methods in Rust services.
2026-03-30 21:06:58 -04:00

140 lines
6.3 KiB
TypeScript

'use client'
import React, {Dispatch, SetStateAction, useContext, useState} from "react";
import {Book, Globe, LucideIcon, Map, Pencil, Trash2, User, Wand2} from 'lucide-react';
import {useTranslations} from '@/lib/i18n';
import AlertBox from "@/components/ui/AlertBox";
import {SessionContext, SessionContextProps} from "@/context/SessionContext";
import {LangContext, LangContextProps} from "@/context/LangContext";
import {AlertContext, AlertContextProps} from "@/context/AlertContext";
import OfflineContext, {OfflineContextType} from '@/context/OfflineContext';
import {isDesktop} from '@/lib/configs';
import {apiDelete} from '@/lib/api/client';
import {deleteSeries} from '@/lib/tauri';
import {SeriesContext, SeriesContextProps} from '@/context/SeriesContext';
import {SeriesSyncContext, SeriesSyncContextProps} from '@/context/SeriesSyncContext';
import {SyncedSeries} from '@/lib/types/synced-series';
import {LocalSyncQueueContext, LocalSyncQueueContextProps} from '@/context/SyncQueueContext';
interface SeriesSettingOption {
id: string;
name: string;
icon: LucideIcon;
}
interface SeriesSettingSidebarProps {
selectedSetting: string;
setSelectedSetting: Dispatch<SetStateAction<string>>;
seriesId: string;
onClose: () => void;
}
export default function SeriesSettingSidebar(
{
selectedSetting,
setSelectedSetting,
seriesId,
onClose
}: SeriesSettingSidebarProps) {
const t = useTranslations();
const {session}: SessionContextProps = useContext<SessionContextProps>(SessionContext);
const {lang}: LangContextProps = useContext<LangContextProps>(LangContext);
const {errorMessage, successMessage}: AlertContextProps = useContext<AlertContextProps>(AlertContext);
const {isCurrentlyOffline} = useContext(OfflineContext);
const {localSyncedSeries}: SeriesSyncContextProps = useContext<SeriesSyncContextProps>(SeriesSyncContext);
const {addToQueue}: LocalSyncQueueContextProps = useContext<LocalSyncQueueContextProps>(LocalSyncQueueContext);
const userToken: string = session?.accessToken ? session?.accessToken : '';
const [showDeleteConfirm, setShowDeleteConfirm] = useState<boolean>(false);
async function handleDeleteSeries(): Promise<void> {
try {
const useLocal: boolean = isDesktop && isCurrentlyOffline();
const deletedAt: number = Math.floor(Date.now() / 1000);
let success: boolean;
if (useLocal) {
success = await deleteSeries(seriesId, deletedAt);
} else {
success = await apiDelete<boolean>('series/delete', {seriesId: seriesId}, userToken, lang);
if (isDesktop && localSyncedSeries.find((s: SyncedSeries): boolean => s.id === seriesId)) {
addToQueue('delete_series', {seriesId, deletedAt});
}
}
if (success) {
successMessage(t('seriesSetting.deleteSuccess'));
onClose();
window.location.reload();
}
} catch (error: unknown) {
if (error instanceof Error) {
errorMessage(error.message);
} else {
errorMessage(t('seriesSetting.deleteError'));
}
}
}
async function handleDeleteConfirm(): Promise<void> {
await handleDeleteSeries();
setShowDeleteConfirm(false);
}
const settings: SeriesSettingOption[] = [
{id: 'basic-information', name: 'seriesSetting.basicInformation', icon: Pencil},
{id: 'books', name: 'seriesSetting.books', icon: Book},
{id: 'characters', name: 'seriesSetting.characters', icon: User},
{id: 'worlds', name: 'seriesSetting.worlds', icon: Globe},
{id: 'locations', name: 'seriesSetting.locations', icon: Map},
{id: 'spells', name: 'seriesSetting.spells', icon: Wand2},
];
return (
<div className="py-4 px-2 flex flex-col h-full">
<nav className="space-y-0.5 flex-1">
{settings.map((setting: SeriesSettingOption) => {
const Icon: LucideIcon = setting.icon;
const isActive: boolean = selectedSetting === setting.id;
return (
<button
key={setting.id}
onClick={(): void => setSelectedSetting(setting.id)}
className={`flex items-center w-full text-sm rounded-lg transition-colors duration-150 px-3 py-2 ${
isActive
? 'bg-secondary text-text-primary font-medium'
: 'text-text-secondary hover:bg-secondary/50 hover:text-text-primary'
}`}
>
<Icon
className={`mr-2.5 w-4 h-4 flex-shrink-0 ${isActive ? 'text-primary' : 'text-muted'}`}
strokeWidth={1.75}
/>
{t(setting.name)}
</button>
);
})}
</nav>
<div className="mt-4 pt-3 border-t border-secondary">
<button
onClick={(): void => setShowDeleteConfirm(true)}
className="w-full flex items-center justify-center gap-2 px-3 py-2 text-sm text-accent-red hover:bg-accent-red/20 rounded-lg transition-colors duration-150"
>
<Trash2 className="w-4 h-4" strokeWidth={1.75}/>
{t('seriesSetting.deleteSeries')}
</button>
</div>
{showDeleteConfirm && (
<AlertBox
title={t('seriesSetting.deleteSeries')}
message={t('seriesSetting.deleteConfirmMessage')}
type="danger"
onConfirm={handleDeleteConfirm}
onCancel={(): void => setShowDeleteConfirm(false)}
confirmText={t('common.delete')}
cancelText={t('common.cancel')}
/>
)}
</div>
);
}