Files
ERitors-Scribe-Desktop/components/series/SeriesSettingSidebar.tsx
natreex 49bb6e06f5 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.
2026-02-09 17:12:03 -05:00

178 lines
6.7 KiB
TypeScript

'use client'
import Link from "next/link";
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
import {
faBook,
faGlobe,
faHatWizard,
faMapMarkedAlt,
faPencilAlt,
faTrash,
faUser
} from "@fortawesome/free-solid-svg-icons";
import React, {Dispatch, SetStateAction, useContext, useState} from "react";
import {IconDefinition} from "@fortawesome/fontawesome-svg-core";
import {useTranslations} from "next-intl";
import AlertBox from "@/components/AlertBox";
import {SessionContext} from "@/context/SessionContext";
import {LangContext, LangContextProps} from "@/context/LangContext";
import {AlertContext} from "@/context/AlertContext";
import System from "@/lib/models/System";
import {SeriesContext, SeriesContextProps} from "@/context/SeriesContext";
import OfflineContext, {OfflineContextType} from "@/context/OfflineContext";
import {SeriesSyncContext, SeriesSyncContextProps} from "@/context/SeriesSyncContext";
import {LocalSyncQueueContext, LocalSyncQueueContextProps} from "@/context/SyncQueueContext";
import {SyncedSeries} from "@/lib/models/SyncedSeries";
interface SeriesSettingOption {
id: string;
name: string;
icon: IconDefinition;
}
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} = useContext(SessionContext);
const {lang} = useContext<LangContextProps>(LangContext);
const {errorMessage, successMessage} = useContext(AlertContext);
const userToken: string = session?.accessToken ? session?.accessToken : '';
const {localSeries} = useContext<SeriesContextProps>(SeriesContext);
const {isCurrentlyOffline} = useContext<OfflineContextType>(OfflineContext);
const {localSyncedSeries} = useContext<SeriesSyncContextProps>(SeriesSyncContext);
const {addToQueue} = useContext<LocalSyncQueueContextProps>(LocalSyncQueueContext);
const [showDeleteConfirm, setShowDeleteConfirm] = useState<boolean>(false);
async function handleDeleteSeries(): Promise<void> {
try {
const deleteData = {seriesId: seriesId, deletedAt: System.timeStampInSeconds()};
let success: boolean;
if (isCurrentlyOffline() || localSeries) {
success = await window.electron.invoke<boolean>('db:series:delete', deleteData);
} else {
success = await System.authDeleteToServer<boolean>(
'series/delete',
deleteData,
userToken,
lang
);
if (localSyncedSeries.find((s: SyncedSeries): boolean => s.id === seriesId)) {
addToQueue('db:series:delete', deleteData);
}
}
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: faPencilAlt
},
{
id: 'books',
name: 'seriesSetting.books',
icon: faBook
},
{
id: 'characters',
name: 'seriesSetting.characters',
icon: faUser
},
{
id: 'worlds',
name: 'seriesSetting.worlds',
icon: faGlobe
},
{
id: 'locations',
name: 'seriesSetting.locations',
icon: faMapMarkedAlt
},
{
id: 'spells',
name: 'seriesSetting.spells',
icon: faHatWizard
}
];
return (
<div className="py-6 px-3 flex flex-col h-full">
<nav className="space-y-1 flex-1">
{
settings.map((setting: SeriesSettingOption) => (
<Link
key={setting.id}
href={''}
onClick={(): void => setSelectedSetting(setting.id)}
className={`flex items-center text-base rounded-xl transition-all duration-200 ${
selectedSetting === setting.id
? 'bg-primary/20 text-text-primary border-l-4 border-primary font-semibold shadow-md scale-105'
: 'text-text-secondary hover:bg-secondary/50 hover:text-text-primary hover:scale-102'
} p-3 mb-1`}>
<FontAwesomeIcon
icon={setting.icon}
className={`mr-3 ${selectedSetting === setting.id ? 'text-primary w-5 h-5' : 'text-text-secondary w-5 h-5'}`}/>
{t(setting.name)}
</Link>
))
}
</nav>
<div className="mt-6 pt-4 border-t border-secondary/50">
<button
onClick={(): void => setShowDeleteConfirm(true)}
className="w-full flex items-center justify-center gap-2 p-3 text-red-400 hover:bg-red-500/20 rounded-xl transition-all duration-200"
>
<FontAwesomeIcon icon={faTrash} className="w-4 h-4"/>
{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>
);
}