Remove unused components and models for improved maintainability

- Deleted redundant components (`AddActionButton`, `AlertBox`, `AlertStack`, `BackButton`, `CancelButton`, and `CollapsableArea`) and related files.
- Removed unused models (`Book`, `BookSerie`, `BookTables`, `Character`, and `Chapter`) to reduce codebase clutter.
- Updated project structure and references to reflect these removals.
This commit is contained in:
natreex
2026-03-22 22:37:31 -04:00
parent e8aaef108b
commit 64ed90d993
229 changed files with 15091 additions and 21289 deletions

View File

@@ -1,22 +1,24 @@
'use client'
import {useCallback, useContext, useEffect, useState} from 'react';
import {Attribute, CharacterListResponse, CharacterProps} from '@/lib/models/Character';
import {SeriesCharacterProps} from '@/lib/models/Series';
import * as tauri from '@/lib/tauri';
import {SessionContext} from '@/context/SessionContext';
import {BookContext} from '@/context/BookContext';
import {AlertContext} from '@/context/AlertContext';
import {
Attribute,
CharacterAttributeSection,
CharacterListResponse,
CharacterProps,
isCharacterCategory,
isCharacterStatus
} from '@/lib/types/character';
import {SeriesCharacterProps} from '@/lib/types/series';
import {SessionContext, SessionContextProps} from '@/context/SessionContext';
import {BookContext, BookContextProps} from '@/context/BookContext';
import {AlertContext, AlertContextProps} from '@/context/AlertContext';
import {LangContext, LangContextProps} from '@/context/LangContext';
import {apiDelete, apiGet, apiPatch, apiPost} from '@/lib/api/client';
import {useTranslations} from '@/lib/i18n';
import {ViewMode} from '@/lib/types/settings';
import {isDesktop} from '@/lib/configs';
import * as tauri from '@/lib/tauri';
import OfflineContext, {OfflineContextType} from '@/context/OfflineContext';
import {LocalSyncQueueContext, LocalSyncQueueContextProps} from '@/context/SyncQueueContext';
import {BooksSyncContext, BooksSyncContextProps} from '@/context/BooksSyncContext';
import {SyncedBook} from '@/lib/models/SyncedBook';
import {SeriesContext, SeriesContextProps} from '@/context/SeriesContext';
import {SeriesSyncContext, SeriesSyncContextProps} from '@/context/SeriesSyncContext';
import {SyncedSeries} from '@/lib/models/SyncedSeries';
import System from '@/lib/models/System';
import {useTranslations} from 'next-intl';
import {ViewMode} from '@/shared/interface';
const initialCharacterState: CharacterProps = {
id: null,
@@ -73,11 +75,11 @@ export interface UseCharactersReturn {
isLoading: boolean;
isSeriesMode: boolean;
bookSeriesId: string | null;
// Navigation state
viewMode: ViewMode;
characterBackup: CharacterProps | null;
// Actions
selectCharacter: (character: CharacterProps) => void;
addNewCharacter: () => void;
@@ -85,15 +87,15 @@ export interface UseCharactersReturn {
saveCharacter: () => Promise<boolean>;
deleteCharacter: (characterId: string) => Promise<void>;
updateCharacterField: (key: keyof CharacterProps, value: string | number | null) => void;
addAttribute: (section: keyof CharacterProps, value: Attribute) => Promise<void>;
removeAttribute: (section: keyof CharacterProps, index: number, attrId: string) => Promise<void>;
addAttribute: (section: CharacterAttributeSection, value: Attribute) => Promise<void>;
removeAttribute: (section: CharacterAttributeSection, index: number, attrId: string) => Promise<void>;
toggleTool: (enabled: boolean) => Promise<void>;
importFromSeries: (seriesCharacterId: string) => Promise<void>;
exportToSeries: () => Promise<void>;
refreshCharacters: () => Promise<void>;
refreshSeriesCharacters: () => Promise<void>;
setSelectedCharacter: React.Dispatch<React.SetStateAction<CharacterProps | null>>;
// Navigation actions
enterDetailMode: (character: CharacterProps) => void;
enterEditMode: () => void;
@@ -104,85 +106,66 @@ export interface UseCharactersReturn {
export function useCharacters(config: UseCharactersConfig): UseCharactersReturn {
const {entityType, entityId} = config;
const t = useTranslations();
const {lang} = useContext<LangContextProps>(LangContext);
const {session} = useContext(SessionContext);
const {book, setBook} = useContext(BookContext);
const {errorMessage, successMessage} = useContext(AlertContext);
const {isCurrentlyOffline} = useContext<OfflineContextType>(OfflineContext);
const {addToQueue} = useContext<LocalSyncQueueContextProps>(LocalSyncQueueContext);
const {localSyncedBooks} = useContext<BooksSyncContextProps>(BooksSyncContext);
const {localSeries} = useContext<SeriesContextProps>(SeriesContext);
const {localSyncedSeries} = useContext<SeriesSyncContextProps>(SeriesSyncContext);
const {lang}: LangContextProps = useContext<LangContextProps>(LangContext);
const {session}: SessionContextProps = useContext<SessionContextProps>(SessionContext);
const {book, setBook}: BookContextProps = useContext<BookContextProps>(BookContext);
const {errorMessage, successMessage}: AlertContextProps = useContext<AlertContextProps>(AlertContext);
const {isCurrentlyOffline}: OfflineContextType = useContext<OfflineContextType>(OfflineContext);
const [characters, setCharacters] = useState<CharacterProps[]>([]);
const [seriesCharacters, setSeriesCharacters] = useState<SeriesCharacterProps[]>([]);
const [selectedCharacter, setSelectedCharacter] = useState<CharacterProps | null>(null);
const [toolEnabled, setToolEnabled] = useState<boolean>(entityType === 'series' || (book?.tools?.characters ?? false));
const [isLoading, setIsLoading] = useState<boolean>(true);
// Navigation state
const [viewMode, setViewMode] = useState<ViewMode>('list');
const [characterBackup, setCharacterBackup] = useState<CharacterProps | null>(null);
const isSeriesMode: boolean = entityType === 'series';
const bookSeriesId: string | null = book?.seriesId || null;
const userToken: string = session?.accessToken || '';
// Load characters on mount
useEffect(function (): void {
if (entityId) {
refreshCharacters();
}
}, [entityId]);
// Load series characters for book mode
useEffect(function (): void {
if (bookSeriesId && !isSeriesMode) {
refreshSeriesCharacters();
}
}, [bookSeriesId, isSeriesMode]);
const refreshSeriesCharacters = useCallback(async function (): Promise<void> {
if (!bookSeriesId) return;
try {
let response: SeriesCharacterProps[];
// Dual logic: offline ou livre local → IPC, sinon serveur
if (isCurrentlyOffline() || book?.localBook) {
response = await tauri.getSeriesCharacterList(bookSeriesId);
} else {
response = await System.authGetQueryToServer<SeriesCharacterProps[]>(
'series/character/list',
userToken,
lang,
{seriesid: bookSeriesId}
);
}
const response: SeriesCharacterProps[] = await apiGet<SeriesCharacterProps[]>('series/character/list', userToken, lang, {
seriesid: bookSeriesId
});
if (response) {
setSeriesCharacters(response);
}
} catch (e: unknown) {
if (e instanceof Error) {
console.error('Error loading series characters:', e.message);
errorMessage(e.message);
}
}
}, [bookSeriesId, userToken, lang, isCurrentlyOffline, book?.localBook]);
}, [bookSeriesId, userToken, lang]);
const refreshCharacters = useCallback(async function (): Promise<void> {
setIsLoading(true);
try {
if (isSeriesMode) {
// Series mode - dual logic
let response: SeriesCharacterProps[];
if (isCurrentlyOffline() || localSeries) {
response = await tauri.getSeriesCharacterList(entityId);
} else {
response = await System.authGetQueryToServer<SeriesCharacterProps[]>(
'series/character/list',
userToken,
lang,
{seriesid: entityId}
);
}
const response: SeriesCharacterProps[] = await apiGet<SeriesCharacterProps[]>(
'series/character/list',
userToken,
lang,
{seriesid: entityId}
);
if (response) {
const mappedCharacters: CharacterProps[] = response.map(function (char: SeriesCharacterProps): CharacterProps {
return {
@@ -194,8 +177,8 @@ export function useCharacters(config: UseCharactersConfig): UseCharactersReturn
gender: '',
species: '',
nationality: '',
status: 'alive' as const,
category: char.category as CharacterProps['category'],
status: char.status && isCharacterStatus(char.status) ? char.status : 'alive',
category: isCharacterCategory(char.category) ? char.category : 'none',
title: '',
role: char.role || '',
image: char.image || 'https://via.placeholder.com/150',
@@ -223,22 +206,11 @@ export function useCharacters(config: UseCharactersConfig): UseCharactersReturn
setCharacters(mappedCharacters);
}
} else {
// Pattern B: GET dans contexte livre
let response: CharacterListResponse;
if (isCurrentlyOffline()) {
// Offline → Tauri
response = await tauri.getCharacterList(entityId, true) as CharacterListResponse;
} else if (book?.localBook) {
// Online mais livre local → Tauri
response = await tauri.getCharacterList(entityId, true) as CharacterListResponse;
if (isDesktop && (isCurrentlyOffline() || book?.localBook)) {
response = await tauri.getCharacterList(entityId, book?.tools?.characters ?? false) as CharacterListResponse;
} else {
// Online + livre serveur → Server
response = await System.authGetQueryToServer<CharacterListResponse>(
'character/list',
userToken,
lang,
{bookid: entityId}
);
response = await apiGet<CharacterListResponse>('character/list', userToken, lang, {bookid: entityId});
}
if (response) {
setCharacters(response.characters);
@@ -265,54 +237,44 @@ export function useCharacters(config: UseCharactersConfig): UseCharactersReturn
} finally {
setIsLoading(false);
}
}, [entityId, isSeriesMode, userToken, lang, book, setBook, errorMessage, t, isCurrentlyOffline]);
}, [entityId, isSeriesMode, userToken, lang, book, setBook, errorMessage, t]);
const selectCharacter = useCallback(function (character: CharacterProps): void {
setSelectedCharacter({...character});
}, []);
const addNewCharacter = useCallback(function (): void {
setSelectedCharacter({...initialCharacterState});
setViewMode('edit');
setCharacterBackup(null);
}, []);
const clearSelection = useCallback(function (): void {
setSelectedCharacter(null);
setViewMode('list');
setCharacterBackup(null);
}, []);
const updateCharacterField = useCallback(function (key: keyof CharacterProps, value: string | number | null): void {
setSelectedCharacter(function (prev: CharacterProps | null): CharacterProps | null {
if (!prev) return null;
return {...prev, [key]: value};
});
}, []);
const toggleTool = useCallback(async function (enabled: boolean): Promise<void> {
if (isSeriesMode) return;
try {
const requestData = {
bookId: book?.bookId,
toolName: 'characters',
enabled: enabled
};
let response: boolean;
if (isCurrentlyOffline() || book?.localBook) {
// Offline OU livre local → Tauri
response = await tauri.updateBookToolSetting(requestData.bookId, requestData.toolName, requestData.enabled);
if (isDesktop && (isCurrentlyOffline() || book?.localBook)) {
response = await tauri.updateBookToolSetting(book?.bookId ?? '', 'characters', enabled);
} else {
// Online + livre serveur → Server
response = await System.authPatchToServer<boolean>('book/tool-setting', requestData, userToken, lang);
// Si le livre a une copie locale → addToQueue pour sync
if (book?.bookId && localSyncedBooks.find((sb: SyncedBook): boolean => sb.id === book.bookId)) {
addToQueue('update_book_tool_setting', {data: requestData});
}
response = await apiPatch<boolean>('book/tool-setting', {
bookId: book?.bookId,
toolName: 'characters',
enabled: enabled
}, userToken, lang);
}
if (response && setBook && book) {
setToolEnabled(enabled);
setBook({
@@ -330,8 +292,8 @@ export function useCharacters(config: UseCharactersConfig): UseCharactersReturn
errorMessage(e.message);
}
}
}, [isSeriesMode, book, setBook, userToken, lang, errorMessage, isCurrentlyOffline, addToQueue, localSyncedBooks]);
}, [isSeriesMode, book, setBook, userToken, lang, errorMessage]);
const saveCharacter = useCallback(async function (): Promise<boolean> {
if (!selectedCharacter) return false;
@@ -341,7 +303,7 @@ export function useCharacters(config: UseCharactersConfig): UseCharactersReturn
return await updateCharacterInternal(selectedCharacter);
}
}, [selectedCharacter]);
async function addCharacterInternal(character: CharacterProps): Promise<boolean> {
if (!character.name) {
errorMessage(t("characterComponent.errorNameRequired"));
@@ -354,63 +316,42 @@ export function useCharacters(config: UseCharactersConfig): UseCharactersReturn
try {
let characterId: string;
if (isSeriesMode) {
// Series mode - dual logic
const seriesCharacterData = {
seriesId: entityId,
character: {
name: character.name,
lastName: character.lastName || null,
nickname: character.nickname || null,
age: character.age || null,
gender: character.gender || null,
species: character.species || null,
nationality: character.nationality || null,
status: character.status || null,
category: character.category,
title: character.title || null,
image: character.image || null,
role: character.role || null,
biography: character.biography || null,
history: character.history || null,
speechPattern: character.speechPattern || null,
catchphrase: character.catchphrase || null,
residence: character.residence || null,
notes: character.notes || null,
color: character.color || null,
}
};
if (isCurrentlyOffline() || localSeries) {
characterId = await tauri.addSeriesCharacter(seriesCharacterData);
} else {
characterId = await System.authPostToServer<string>(
'series/character/add',
seriesCharacterData,
userToken,
lang
);
if (localSyncedSeries.find((s: SyncedSeries): boolean => s.id === entityId)) {
addToQueue('add_series_character', {data: {...seriesCharacterData, id: characterId}});
}
}
characterId = await apiPost<string>(
'series/character/add',
{
seriesId: entityId,
character: {
name: character.name,
lastName: character.lastName || null,
nickname: character.nickname || null,
age: character.age || null,
gender: character.gender || null,
species: character.species || null,
nationality: character.nationality || null,
status: character.status || null,
category: character.category,
title: character.title || null,
image: character.image || null,
role: character.role || null,
biography: character.biography || null,
history: character.history || null,
speechPattern: character.speechPattern || null,
catchphrase: character.catchphrase || null,
residence: character.residence || null,
notes: character.notes || null,
color: character.color || null,
}
},
userToken,
lang
);
} else if (isDesktop && (isCurrentlyOffline() || book?.localBook)) {
characterId = await tauri.createCharacter(character, entityId);
} else {
// Pattern A: mutations
const requestData = {
characterId = await apiPost<string>('character/add', {
bookId: entityId,
character: character,
};
if (isCurrentlyOffline() || book?.localBook) {
// Offline OU livre local → Tauri
characterId = await tauri.createCharacter(requestData.character, requestData.bookId, requestData.id);
} else {
// Online + livre serveur → Server
characterId = await System.authPostToServer<string>('character/add', requestData, userToken, lang);
// Si le livre a une copie locale → addToQueue pour sync
if (localSyncedBooks.find((sb: SyncedBook): boolean => sb.id === entityId)) {
addToQueue('create_character', {data: {...requestData, id: characterId}});
}
}
}, userToken, lang);
}
if (!characterId) {
@@ -433,13 +374,12 @@ export function useCharacters(config: UseCharactersConfig): UseCharactersReturn
return false;
}
}
async function updateCharacterInternal(character: CharacterProps): Promise<boolean> {
try {
let response: boolean;
if (isSeriesMode) {
// Series mode - dual logic
const updateData = {
response = await apiPatch<boolean>('series/character/update', {
characterId: character.id,
character: {
id: character.id,
@@ -463,40 +403,20 @@ export function useCharacters(config: UseCharactersConfig): UseCharactersReturn
notes: character.notes || null,
color: character.color || null,
}
};
if (isCurrentlyOffline() || localSeries) {
response = await tauri.updateSeriesCharacter(updateData);
} else {
response = await System.authPatchToServer<boolean>('series/character/update', updateData, userToken, lang);
if (localSyncedSeries.find((s: SyncedSeries): boolean => s.id === entityId)) {
addToQueue('update_series_character', {data: updateData});
}
}
}, userToken, lang);
} else if (isDesktop && (isCurrentlyOffline() || book?.localBook)) {
response = await tauri.updateCharacter(character);
} else {
// Pattern A: mutations
const requestData = {
response = await apiPost<boolean>('character/update', {
character: character,
};
if (isCurrentlyOffline() || book?.localBook) {
// Offline OU livre local → Tauri
response = await tauri.updateCharacter(requestData.character);
} else {
// Online + livre serveur → Server
response = await System.authPostToServer<boolean>('character/update', requestData, userToken, lang);
// Si le livre a une copie locale → addToQueue pour sync
if (localSyncedBooks.find((sb: SyncedBook): boolean => sb.id === entityId)) {
addToQueue('update_character', {data: requestData});
}
}
}, userToken, lang);
}
if (!response) {
errorMessage(t("characterComponent.errorUpdateCharacter"));
return false;
}
setCharacters(function (prev: CharacterProps[]): CharacterProps[] {
return prev.map(function (c: CharacterProps): CharacterProps {
return c.id === character.id ? character : c;
@@ -514,45 +434,27 @@ export function useCharacters(config: UseCharactersConfig): UseCharactersReturn
return false;
}
}
const deleteCharacter = useCallback(async function (characterId: string): Promise<void> {
try {
let response: boolean;
const deletedAt: number = System.timeStampInSeconds();
if (isSeriesMode) {
// Series mode - dual logic
const requestData = {characterId, deletedAt};
if (isCurrentlyOffline() || localSeries) {
response = await tauri.deleteSeriesCharacter(requestData.characterId, requestData.deletedAt);
} else {
response = await System.authDeleteToServer<boolean>('series/character/delete', requestData, userToken, lang);
if (localSyncedSeries.find((s: SyncedSeries): boolean => s.id === entityId)) {
addToQueue('delete_series_character', {data: requestData});
}
}
response = await apiDelete<boolean>('series/character/delete', {
characterId: characterId,
}, userToken, lang);
} else if (isDesktop && (isCurrentlyOffline() || book?.localBook)) {
response = await tauri.deleteCharacter(characterId, book?.bookId ?? '', Date.now());
} else {
// Pattern A: mutations
const requestData = {characterId, bookId: entityId, deletedAt};
if (isCurrentlyOffline() || book?.localBook) {
// Offline OU livre local → Tauri
response = await tauri.deleteCharacter(requestData.characterId, requestData.bookId, requestData.deletedAt);
} else {
// Online + livre serveur → Server
response = await System.authDeleteToServer<boolean>('character/delete', requestData, userToken, lang);
// Si le livre a une copie locale → addToQueue pour sync
if (localSyncedBooks.find((sb: SyncedBook): boolean => sb.id === entityId)) {
addToQueue('delete_character', {data: requestData});
}
}
response = await apiDelete<boolean>('character/delete', {
characterId: characterId,
}, userToken, lang);
}
if (!response) {
errorMessage(t("characterComponent.errorDeleteCharacter"));
return;
}
setCharacters(function (prev: CharacterProps[]): CharacterProps[] {
return prev.filter(function (c: CharacterProps): boolean {
return c.id !== characterId;
@@ -567,62 +469,46 @@ export function useCharacters(config: UseCharactersConfig): UseCharactersReturn
errorMessage(t("common.unknownError"));
}
}
}, [isSeriesMode, userToken, lang, errorMessage, successMessage, t, isCurrentlyOffline, book, addToQueue, localSyncedBooks, entityId]);
const addAttribute = useCallback(async function (section: keyof CharacterProps, value: Attribute): Promise<void> {
}, [isSeriesMode, userToken, lang, errorMessage, successMessage, t]);
const addAttribute = useCallback(async function (section: CharacterAttributeSection, value: Attribute): Promise<void> {
if (!selectedCharacter) return;
if (selectedCharacter.id === null) {
const updatedSection: Attribute[] = [
...(selectedCharacter[section] as Attribute[]),
...selectedCharacter[section],
value,
];
setSelectedCharacter({...selectedCharacter, [section]: updatedSection});
} else {
try {
const requestData = {
characterId: selectedCharacter.id,
type: section,
name: value.name,
};
let attributeId: string;
if (isSeriesMode) {
// Series mode - dual logic
if (isCurrentlyOffline() || localSeries) {
attributeId = await tauri.addSeriesCharacterAttribute(requestData);
} else {
attributeId = await System.authPostToServer<string>('series/character/attribute/add', requestData, userToken, lang);
if (localSyncedSeries.find((s: SyncedSeries): boolean => s.id === entityId)) {
addToQueue('add_series_character_attribute', {data: {...requestData, id: attributeId}});
}
}
attributeId = await apiPost<string>('series/character/attribute/add', {
characterId: selectedCharacter.id,
type: section,
name: value.name,
}, userToken, lang);
} else if (isDesktop && (isCurrentlyOffline() || book?.localBook)) {
attributeId = await tauri.addCharacterAttribute(selectedCharacter.id, section, value.name);
} else {
// Pattern A: mutations
if (isCurrentlyOffline() || book?.localBook) {
// Offline OU livre local → Tauri
attributeId = await tauri.addCharacterAttribute(requestData.characterId, requestData.type, requestData.name, requestData.id);
} else {
// Online + livre serveur → Server
attributeId = await System.authPostToServer<string>('character/attribute/add', requestData, userToken, lang);
// Si le livre a une copie locale → addToQueue pour sync
if (localSyncedBooks.find((sb: SyncedBook): boolean => sb.id === entityId)) {
addToQueue('add_character_attribute', {data: {...requestData, id: attributeId}});
}
}
attributeId = await apiPost<string>('character/attribute/add', {
characterId: selectedCharacter.id,
type: section,
name: value.name,
}, userToken, lang);
}
if (!attributeId) {
errorMessage(t("characterComponent.errorAddAttribute"));
return;
}
const newValue: Attribute = {
name: value.name,
id: attributeId,
};
const updatedSection: Attribute[] = [...(selectedCharacter[section] as Attribute[]), newValue];
const updatedSection: Attribute[] = [...selectedCharacter[section], newValue];
setSelectedCharacter({...selectedCharacter, [section]: updatedSection});
} catch (e: unknown) {
if (e instanceof Error) {
@@ -632,59 +518,37 @@ export function useCharacters(config: UseCharactersConfig): UseCharactersReturn
}
}
}
}, [selectedCharacter, isSeriesMode, userToken, lang, errorMessage, t, isCurrentlyOffline, book, addToQueue, localSyncedBooks, entityId]);
const removeAttribute = useCallback(async function (section: keyof CharacterProps, index: number, attrId: string): Promise<void> {
}, [selectedCharacter, isSeriesMode, userToken, lang, errorMessage, t]);
const removeAttribute = useCallback(async function (section: CharacterAttributeSection, index: number, attrId: string): Promise<void> {
if (!selectedCharacter) return;
if (selectedCharacter.id === null) {
const updatedSection: Attribute[] = (
selectedCharacter[section] as Attribute[]
).filter(function (_: Attribute, i: number): boolean {
const updatedSection: Attribute[] = selectedCharacter[section].filter(function (_: Attribute, i: number): boolean {
return i !== index;
});
setSelectedCharacter({...selectedCharacter, [section]: updatedSection});
} else {
try {
const deletedAt: number = System.timeStampInSeconds();
let response: boolean;
if (isSeriesMode) {
// Series mode - dual logic
const requestData = {attributeId: attrId, deletedAt};
if (isCurrentlyOffline() || localSeries) {
response = await tauri.deleteSeriesCharacterAttribute(requestData.attributeId, requestData.deletedAt);
} else {
response = await System.authDeleteToServer<boolean>('series/character/attribute/delete', requestData, userToken, lang);
if (localSyncedSeries.find((s: SyncedSeries): boolean => s.id === entityId)) {
addToQueue('delete_series_character_attribute', {data: requestData});
}
}
response = await apiDelete<boolean>('series/character/attribute/delete', {
attributeId: attrId,
}, userToken, lang);
} else if (isDesktop && (isCurrentlyOffline() || book?.localBook)) {
response = await tauri.deleteCharacterAttribute(attrId, book?.bookId ?? '', Date.now());
} else {
// Pattern A: mutations
const requestData = {attributeId: attrId, bookId: entityId, deletedAt};
if (isCurrentlyOffline() || book?.localBook) {
// Offline OU livre local → Tauri
response = await tauri.deleteCharacterAttribute(requestData.attributeId, requestData.bookId, requestData.deletedAt);
} else {
// Online + livre serveur → Server
response = await System.authDeleteToServer<boolean>('character/attribute/delete', requestData, userToken, lang);
// Si le livre a une copie locale → addToQueue pour sync
if (localSyncedBooks.find((sb: SyncedBook): boolean => sb.id === entityId)) {
addToQueue('delete_character_attribute', {data: requestData});
}
}
response = await apiDelete<boolean>('character/attribute/delete', {
attributeId: attrId,
}, userToken, lang);
}
if (!response) {
errorMessage(t("characterComponent.errorRemoveAttribute"));
return;
}
const updatedSection: Attribute[] = (
selectedCharacter[section] as Attribute[]
).filter(function (_: Attribute, i: number): boolean {
const updatedSection: Attribute[] = selectedCharacter[section].filter(function (_: Attribute, i: number): boolean {
return i !== index;
});
setSelectedCharacter({...selectedCharacter, [section]: updatedSection});
@@ -696,76 +560,50 @@ export function useCharacters(config: UseCharactersConfig): UseCharactersReturn
}
}
}
}, [selectedCharacter, isSeriesMode, userToken, lang, errorMessage, t, isCurrentlyOffline, book, addToQueue, localSyncedBooks, entityId]);
}, [selectedCharacter, isSeriesMode, userToken, lang, errorMessage, t]);
const exportToSeries = useCallback(async function (): Promise<void> {
if (!selectedCharacter || !bookSeriesId) return;
try {
const seriesCharacterData = {
seriesId: bookSeriesId,
character: {
name: selectedCharacter.name,
lastName: selectedCharacter.lastName || null,
nickname: selectedCharacter.nickname || null,
age: selectedCharacter.age || null,
gender: selectedCharacter.gender || null,
species: selectedCharacter.species || null,
nationality: selectedCharacter.nationality || null,
status: selectedCharacter.status || null,
category: selectedCharacter.category,
title: selectedCharacter.title || null,
image: selectedCharacter.image || null,
role: selectedCharacter.role || null,
biography: selectedCharacter.biography || null,
history: selectedCharacter.history || null,
speechPattern: selectedCharacter.speechPattern || null,
catchphrase: selectedCharacter.catchphrase || null,
residence: selectedCharacter.residence || null,
notes: selectedCharacter.notes || null,
color: selectedCharacter.color || null,
}
};
let seriesCharacterId: string;
if (isCurrentlyOffline() || book?.localBook) {
// Mode offline ou livre local → Tauri
seriesCharacterId = await tauri.addSeriesCharacter(seriesCharacterData);
} else {
// Mode online → Serveur
seriesCharacterId = await System.authPostToServer<string>(
'series/character/add',
seriesCharacterData,
userToken,
lang
);
// Si la série a une copie locale → addToQueue
if (localSyncedSeries.find((s: SyncedSeries): boolean => s.id === bookSeriesId)) {
addToQueue('add_series_character', {data: {...seriesCharacterData, id: seriesCharacterId}});
}
}
const seriesCharacterId: string = await apiPost<string>(
'series/character/add',
{
seriesId: bookSeriesId,
character: {
name: selectedCharacter.name,
lastName: selectedCharacter.lastName || null,
nickname: selectedCharacter.nickname || null,
age: selectedCharacter.age || null,
gender: selectedCharacter.gender || null,
species: selectedCharacter.species || null,
nationality: selectedCharacter.nationality || null,
status: selectedCharacter.status || null,
category: selectedCharacter.category,
title: selectedCharacter.title || null,
image: selectedCharacter.image || null,
role: selectedCharacter.role || null,
biography: selectedCharacter.biography || null,
history: selectedCharacter.history || null,
speechPattern: selectedCharacter.speechPattern || null,
catchphrase: selectedCharacter.catchphrase || null,
residence: selectedCharacter.residence || null,
notes: selectedCharacter.notes || null,
color: selectedCharacter.color || null,
}
},
userToken,
lang
);
if (seriesCharacterId) {
const updateData = {
const updateResponse: boolean = await apiPost<boolean>('character/update', {
character: {
...selectedCharacter,
seriesCharacterId: seriesCharacterId
},
};
let updateResponse: boolean;
if (isCurrentlyOffline() || book?.localBook) {
// Mode offline ou livre local → Tauri
updateResponse = await tauri.updateCharacter(updateData.character);
} else {
// Mode online → Serveur
updateResponse = await System.authPostToServer<boolean>('character/update', updateData, userToken, lang);
// Si le livre a une copie locale → addToQueue
if (localSyncedBooks.find((sb: SyncedBook): boolean => sb.id === entityId)) {
addToQueue('update_character', {data: updateData});
}
}
}, userToken, lang);
if (updateResponse) {
setSelectedCharacter({...selectedCharacter, seriesCharacterId: seriesCharacterId});
setCharacters(function (prev: CharacterProps[]): CharacterProps[] {
@@ -806,14 +644,14 @@ export function useCharacters(config: UseCharactersConfig): UseCharactersReturn
errorMessage(e.message);
}
}
}, [selectedCharacter, bookSeriesId, userToken, lang, successMessage, errorMessage, t, isCurrentlyOffline, book, addToQueue, localSyncedBooks, localSyncedSeries, entityId]);
}, [selectedCharacter, bookSeriesId, userToken, lang, successMessage, errorMessage, t]);
const importFromSeries = useCallback(async function (seriesCharacterId: string): Promise<void> {
const seriesChar = seriesCharacters.find(function (c: SeriesCharacterProps): boolean {
const seriesChar: SeriesCharacterProps | undefined = seriesCharacters.find(function (c: SeriesCharacterProps): boolean {
return c.id === seriesCharacterId;
});
if (!seriesChar) return;
try {
const characterToImport: CharacterProps = {
id: null,
@@ -824,8 +662,8 @@ export function useCharacters(config: UseCharactersConfig): UseCharactersReturn
gender: seriesChar.gender || '',
species: seriesChar.species || '',
nationality: seriesChar.nationality || '',
status: (seriesChar.status as 'alive' | 'dead' | 'unknown') || 'alive',
category: (seriesChar.category as CharacterProps['category']) || 'none',
status: seriesChar.status && isCharacterStatus(seriesChar.status) ? seriesChar.status : 'alive',
category: isCharacterCategory(seriesChar.category) ? seriesChar.category : 'none',
title: seriesChar.title || '',
role: seriesChar.role || '',
image: seriesChar.image || 'https://via.placeholder.com/150',
@@ -856,30 +694,22 @@ export function useCharacters(config: UseCharactersConfig): UseCharactersReturn
affiliations: [],
seriesCharacterId: seriesCharacterId,
};
const requestData = {
bookId: entityId,
character: characterToImport,
};
let characterId: string;
if (isCurrentlyOffline() || book?.localBook) {
// Mode offline ou livre local → Tauri
characterId = await tauri.createCharacter(requestData.character, requestData.bookId, requestData.id);
if (isDesktop && (isCurrentlyOffline() || book?.localBook)) {
characterId = await tauri.createCharacter(characterToImport, entityId);
} else {
// Mode online → Serveur
characterId = await System.authPostToServer<string>('character/add', requestData, userToken, lang);
// Si le livre a une copie locale → addToQueue
if (localSyncedBooks.find((sb: SyncedBook): boolean => sb.id === entityId)) {
addToQueue('create_character', {data: {...requestData, id: characterId}});
}
characterId = await apiPost<string>('character/add', {
bookId: entityId,
character: characterToImport,
}, userToken, lang);
}
if (!characterId) {
errorMessage(t("characterComponent.importError"));
return;
}
characterToImport.id = characterId;
setCharacters(function (prev: CharacterProps[]): CharacterProps[] {
return [...prev, characterToImport];
@@ -889,29 +719,26 @@ export function useCharacters(config: UseCharactersConfig): UseCharactersReturn
errorMessage(e.message);
}
}
}, [seriesCharacters, entityId, userToken, lang, errorMessage, successMessage, t, isCurrentlyOffline, book, addToQueue, localSyncedBooks]);
}, [seriesCharacters, entityId, userToken, lang, errorMessage, successMessage, t]);
// Navigation functions
const enterDetailMode = useCallback(function (character: CharacterProps): void {
setSelectedCharacter({...character});
setViewMode('detail');
setCharacterBackup(null);
}, []);
const enterEditMode = useCallback(function (): void {
if (selectedCharacter) {
setCharacterBackup({...selectedCharacter});
}
setViewMode('edit');
}, [selectedCharacter]);
const exitEditMode = useCallback(async function (save: boolean): Promise<void> {
if (save) {
const success: boolean = await saveCharacter();
if (!success) {
// Stay in edit mode on error
return;
}
if (!success) return;
if (characterBackup) {
setViewMode('detail');
} else {
@@ -928,13 +755,13 @@ export function useCharacters(config: UseCharactersConfig): UseCharactersReturn
}
setCharacterBackup(null);
}, [saveCharacter, characterBackup]);
const backToList = useCallback(function (): void {
setSelectedCharacter(null);
setCharacterBackup(null);
setViewMode('list');
}, []);
return {
// State
characters,
@@ -944,11 +771,11 @@ export function useCharacters(config: UseCharactersConfig): UseCharactersReturn
isLoading,
isSeriesMode,
bookSeriesId,
// Navigation state
viewMode,
characterBackup,
// Actions
selectCharacter,
addNewCharacter,
@@ -964,7 +791,7 @@ export function useCharacters(config: UseCharactersConfig): UseCharactersReturn
refreshCharacters,
refreshSeriesCharacters,
setSelectedCharacter,
// Navigation actions
enterDetailMode,
enterEditMode,