Remove CharacterComponent and CharacterDetail components

- Deleted `CharacterComponent` and `CharacterDetail` files from the project.
- Refactored related logic to improve code maintainability and reduce redundancy.
This commit is contained in:
natreex
2026-02-05 14:12:08 -05:00
parent cec5830360
commit 209dc6f85a
133 changed files with 17673 additions and 3110 deletions

View File

@@ -70,6 +70,7 @@ export interface BookProps {
title: string;
author?: Author;
serie?: number;
seriesId?: string | null;
subTitle?: string;
summary?: string;
publicationDate?: string;

View File

@@ -23,28 +23,16 @@ import {SelectBoxProps} from "@/shared/interface";
type CharacterCategory = 'main' | 'secondary' | 'recurring' | 'none';
export const characterCategories: SelectBoxProps[] = [
{
value: 'none',
label: 'Sélectionner son rôle',
},
{
value: 'main',
label: 'Principal',
},
{
value: 'secondary',
label: 'Secondaire',
},
{
value: 'recurring',
label: 'Récurrent',
},
{value: 'none', label: 'characterCategories.none'},
{value: 'main', label: 'characterCategories.main'},
{value: 'secondary', label: 'characterCategories.secondary'},
{value: 'recurring', label: 'characterCategories.recurring'},
];
export const characterStatus: SelectBoxProps[] = [
{value: 'alive', label: 'Vivant'},
{value: 'dead', label: 'Décédé'},
{value: 'unknown', label: 'Inconnu'},
{value: 'alive', label: 'characterStatus.alive'},
{value: 'dead', label: 'characterStatus.dead'},
{value: 'unknown', label: 'characterStatus.unknown'},
];
export interface Relation {
@@ -68,7 +56,7 @@ export interface CharacterProps {
name: string;
lastName: string;
nickname: string;
age: string;
age: number | null;
gender: string;
species: string;
nationality: string;
@@ -102,6 +90,7 @@ export interface CharacterProps {
residence?: string;
notes?: string;
color?: string;
seriesCharacterId?: string | null;
}
export interface CharacterListResponse {

170
lib/models/Series.ts Normal file
View File

@@ -0,0 +1,170 @@
export interface SeriesProps {
id: string | null;
name: string;
description: string;
coverImage: string | null;
}
export interface SeriesBookProps {
bookId: string;
title: string;
order: number;
coverImage: string | null;
}
export interface SeriesDetailResponse {
id: string;
name: string;
description: string;
coverImage: string | null;
books: SeriesBookProps[];
}
export interface SeriesAddResponse {
seriesId: string;
}
export interface SeriesUpdateResponse {
success: boolean;
}
export interface SeriesListItemProps {
id: string;
name: string;
description: string;
coverImage: string | null;
bookCount: number;
bookIds: string[];
}
// Personnages de série
export interface SeriesCharacterListItem {
id: string;
name: string;
lastName: string | null;
category: string;
role: string | null;
color: string | null;
image: string | null;
}
export interface SeriesCharacterDetailResponse {
id: string;
name: string;
lastName: string | null;
nickname: string | null;
age: number | null;
gender: string | null;
species: string | null;
nationality: string | null;
status: string | null;
category: string;
title: string | null;
image: string | null;
role: string | null;
biography: string | null;
history: string | null;
speechPattern: string | null;
catchphrase: string | null;
residence: string | null;
notes: string | null;
color: string | null;
attributes?: SeriesCharacterAttribute[];
}
export interface SeriesCharacterAttribute {
id: string;
name: string;
value: string;
}
export type SeriesCharacterProps = SeriesCharacterDetailResponse;
// Mondes de série
export interface SeriesWorldElementItem {
id: string;
name: string;
description: string;
}
export interface SeriesWorldListItem {
id: string;
name: string;
history: string;
politics: string;
economy: string;
religion: string;
languages: string;
laws: SeriesWorldElementItem[];
biomes: SeriesWorldElementItem[];
issues: SeriesWorldElementItem[];
customs: SeriesWorldElementItem[];
kingdoms: SeriesWorldElementItem[];
climate: SeriesWorldElementItem[];
resources: SeriesWorldElementItem[];
wildlife: SeriesWorldElementItem[];
arts: SeriesWorldElementItem[];
ethnicGroups: SeriesWorldElementItem[];
socialClasses: SeriesWorldElementItem[];
importantCharacters: SeriesWorldElementItem[];
}
export type SeriesWorldProps = SeriesWorldListItem;
export interface SeriesWorldElement {
id: string;
type: number;
name: string;
description: string;
}
// Lieux de série
export interface SeriesLocationSubElement {
id: string;
name: string;
description: string;
}
export interface SeriesLocationElement {
id: string;
name: string;
description: string;
subElements: SeriesLocationSubElement[];
}
export interface SeriesLocationItem {
id: string;
name: string;
elements: SeriesLocationElement[];
}
// Sorts de série (Grimoire)
export interface SeriesSpellTag {
id: string;
name: string;
color: string | null;
}
export interface SeriesSpellListItem {
id: string;
name: string;
description: string;
tags: string[] | null;
}
export interface SeriesSpellListResponse {
spells: SeriesSpellListItem[];
tags: SeriesSpellTag[];
}
export interface SeriesSpellDetailResponse {
id: string;
name: string;
description: string;
appearance: string;
tags: string[];
powerLevel: string | null;
components: string | null;
limitations: string | null;
notes: string | null;
}

View File

@@ -21,6 +21,7 @@ export interface SpellProps {
components: string | null;
limitations: string | null;
notes: string | null;
seriesSpellId?: string | null;
}
// Pour POST /spell/add et PUT /spell/update
@@ -34,6 +35,7 @@ export interface SpellPropsPost {
components?: string | null;
limitations?: string | null;
notes?: string | null;
seriesSpellId?: string | null;
}
// Item dans la liste (GET /spell/list)
@@ -42,6 +44,7 @@ export interface SpellListItem {
name: string;
description: string;
tags: SpellTagProps[]; // Tags résolus (pas les IDs)
seriesSpellId?: string | null;
}
// Réponse de GET /spell/list
@@ -62,6 +65,7 @@ export interface SpellEditState {
components: string | null;
limitations: string | null;
notes: string | null;
seriesSpellId?: string | null;
}
export const initialSpellState: SpellEditState = {
@@ -74,6 +78,7 @@ export const initialSpellState: SpellEditState = {
components: null,
limitations: null,
notes: null,
seriesSpellId: null,
};
export const spellPowerLevels: SelectBoxProps[] = [

View File

@@ -7,6 +7,7 @@ export interface SyncedBook {
type: string;
title: string;
subTitle: string | null;
seriesId: string | null;
lastUpdate: number;
chapters: SyncedChapter[];
characters: SyncedCharacter[];

280
lib/models/SyncedSeries.ts Normal file
View File

@@ -0,0 +1,280 @@
/**
* Lightweight sync structures for series comparison.
* These interfaces mirror the backend SyncedSeries* types from Book.ts
* but are used in the frontend for sync status detection.
*/
export interface SyncedSeriesBook {
bookId: string;
order: number;
lastUpdate: number;
}
export interface SyncedSeriesCharacterAttribute {
id: string;
name: string;
lastUpdate: number;
}
export interface SyncedSeriesCharacter {
id: string;
name: string;
lastUpdate: number;
attributes: SyncedSeriesCharacterAttribute[];
}
export interface SyncedSeriesWorldElement {
id: string;
name: string;
lastUpdate: number;
}
export interface SyncedSeriesWorld {
id: string;
name: string;
lastUpdate: number;
elements: SyncedSeriesWorldElement[];
}
export interface SyncedSeriesLocationSubElement {
id: string;
name: string;
lastUpdate: number;
}
export interface SyncedSeriesLocationElement {
id: string;
name: string;
lastUpdate: number;
subElements: SyncedSeriesLocationSubElement[];
}
export interface SyncedSeriesLocation {
id: string;
name: string;
lastUpdate: number;
elements: SyncedSeriesLocationElement[];
}
export interface SyncedSeriesSpell {
id: string;
name: string;
lastUpdate: number;
}
export interface SyncedSeriesSpellTag {
id: string;
name: string;
lastUpdate: number;
}
export interface SyncedSeries {
id: string;
name: string;
description: string | null;
lastUpdate: number;
books: SyncedSeriesBook[];
characters: SyncedSeriesCharacter[];
worlds: SyncedSeriesWorld[];
locations: SyncedSeriesLocation[];
spells: SyncedSeriesSpell[];
spellTags: SyncedSeriesSpellTag[];
}
/**
* Comparison result containing IDs of changed entities.
* Used for partial synchronization - only changed entities are transferred.
*/
export interface SeriesSyncCompare {
id: string;
books: string[];
characters: string[];
characterAttributes: string[];
worlds: string[];
worldElements: string[];
locations: string[];
locationElements: string[];
locationSubElements: string[];
spells: string[];
spellTags: string[];
}
/**
* Compares two versions of a series to find changed entities.
* The "newer" series is compared against the "older" one to find
* entities that have been added or modified.
*
* @param newerSeries - The series version with potentially newer data
* @param olderSeries - The series version to compare against
* @returns SeriesSyncCompare with IDs of changed entities, or null if no changes
*/
export function compareSeriesSyncs(newerSeries: SyncedSeries, olderSeries: SyncedSeries): SeriesSyncCompare | null {
const changedBookIds: string[] = [];
const changedCharacterIds: string[] = [];
const changedCharacterAttributeIds: string[] = [];
const changedWorldIds: string[] = [];
const changedWorldElementIds: string[] = [];
const changedLocationIds: string[] = [];
const changedLocationElementIds: string[] = [];
const changedLocationSubElementIds: string[] = [];
const changedSpellIds: string[] = [];
const changedSpellTagIds: string[] = [];
// Compare books
newerSeries.books.forEach((newerBook: SyncedSeriesBook): void => {
const olderBook: SyncedSeriesBook | undefined = olderSeries.books.find(
(book: SyncedSeriesBook): boolean => book.bookId === newerBook.bookId
);
if (!olderBook || newerBook.lastUpdate > olderBook.lastUpdate) {
changedBookIds.push(newerBook.bookId);
}
});
// Compare characters and their attributes
newerSeries.characters.forEach((newerCharacter: SyncedSeriesCharacter): void => {
const olderCharacter: SyncedSeriesCharacter | undefined = olderSeries.characters.find(
(character: SyncedSeriesCharacter): boolean => character.id === newerCharacter.id
);
if (!olderCharacter) {
changedCharacterIds.push(newerCharacter.id);
newerCharacter.attributes.forEach((attr: SyncedSeriesCharacterAttribute): void => {
changedCharacterAttributeIds.push(attr.id);
});
} else if (newerCharacter.lastUpdate > olderCharacter.lastUpdate) {
changedCharacterIds.push(newerCharacter.id);
} else {
// Check attributes even if character hasn't changed
newerCharacter.attributes.forEach((newerAttr: SyncedSeriesCharacterAttribute): void => {
const olderAttr: SyncedSeriesCharacterAttribute | undefined = olderCharacter.attributes.find(
(attr: SyncedSeriesCharacterAttribute): boolean => attr.id === newerAttr.id
);
if (!olderAttr || newerAttr.lastUpdate > olderAttr.lastUpdate) {
changedCharacterAttributeIds.push(newerAttr.id);
}
});
}
});
// Compare worlds and their elements
newerSeries.worlds.forEach((newerWorld: SyncedSeriesWorld): void => {
const olderWorld: SyncedSeriesWorld | undefined = olderSeries.worlds.find(
(world: SyncedSeriesWorld): boolean => world.id === newerWorld.id
);
if (!olderWorld) {
changedWorldIds.push(newerWorld.id);
newerWorld.elements.forEach((element: SyncedSeriesWorldElement): void => {
changedWorldElementIds.push(element.id);
});
} else if (newerWorld.lastUpdate > olderWorld.lastUpdate) {
changedWorldIds.push(newerWorld.id);
} else {
// Check elements even if world hasn't changed
newerWorld.elements.forEach((newerElement: SyncedSeriesWorldElement): void => {
const olderElement: SyncedSeriesWorldElement | undefined = olderWorld.elements.find(
(element: SyncedSeriesWorldElement): boolean => element.id === newerElement.id
);
if (!olderElement || newerElement.lastUpdate > olderElement.lastUpdate) {
changedWorldElementIds.push(newerElement.id);
}
});
}
});
// Compare locations, their elements, and sub-elements
newerSeries.locations.forEach((newerLocation: SyncedSeriesLocation): void => {
const olderLocation: SyncedSeriesLocation | undefined = olderSeries.locations.find(
(location: SyncedSeriesLocation): boolean => location.id === newerLocation.id
);
if (!olderLocation) {
changedLocationIds.push(newerLocation.id);
newerLocation.elements.forEach((element: SyncedSeriesLocationElement): void => {
changedLocationElementIds.push(element.id);
element.subElements.forEach((subElement: SyncedSeriesLocationSubElement): void => {
changedLocationSubElementIds.push(subElement.id);
});
});
} else if (newerLocation.lastUpdate > olderLocation.lastUpdate) {
changedLocationIds.push(newerLocation.id);
} else {
// Check elements
newerLocation.elements.forEach((newerElement: SyncedSeriesLocationElement): void => {
const olderElement: SyncedSeriesLocationElement | undefined = olderLocation.elements.find(
(element: SyncedSeriesLocationElement): boolean => element.id === newerElement.id
);
if (!olderElement) {
changedLocationElementIds.push(newerElement.id);
newerElement.subElements.forEach((subElement: SyncedSeriesLocationSubElement): void => {
changedLocationSubElementIds.push(subElement.id);
});
} else if (newerElement.lastUpdate > olderElement.lastUpdate) {
changedLocationElementIds.push(newerElement.id);
} else {
// Check sub-elements
newerElement.subElements.forEach((newerSubElement: SyncedSeriesLocationSubElement): void => {
const olderSubElement: SyncedSeriesLocationSubElement | undefined = olderElement.subElements.find(
(subElement: SyncedSeriesLocationSubElement): boolean => subElement.id === newerSubElement.id
);
if (!olderSubElement || newerSubElement.lastUpdate > olderSubElement.lastUpdate) {
changedLocationSubElementIds.push(newerSubElement.id);
}
});
}
});
}
});
// Compare spells
newerSeries.spells.forEach((newerSpell: SyncedSeriesSpell): void => {
const olderSpell: SyncedSeriesSpell | undefined = olderSeries.spells.find(
(spell: SyncedSeriesSpell): boolean => spell.id === newerSpell.id
);
if (!olderSpell || newerSpell.lastUpdate > olderSpell.lastUpdate) {
changedSpellIds.push(newerSpell.id);
}
});
// Compare spell tags
newerSeries.spellTags.forEach((newerTag: SyncedSeriesSpellTag): void => {
const olderTag: SyncedSeriesSpellTag | undefined = olderSeries.spellTags.find(
(tag: SyncedSeriesSpellTag): boolean => tag.id === newerTag.id
);
if (!olderTag || newerTag.lastUpdate > olderTag.lastUpdate) {
changedSpellTagIds.push(newerTag.id);
}
});
// Check if there are any changes
const hasChanges: boolean =
changedBookIds.length > 0 ||
changedCharacterIds.length > 0 ||
changedCharacterAttributeIds.length > 0 ||
changedWorldIds.length > 0 ||
changedWorldElementIds.length > 0 ||
changedLocationIds.length > 0 ||
changedLocationElementIds.length > 0 ||
changedLocationSubElementIds.length > 0 ||
changedSpellIds.length > 0 ||
changedSpellTagIds.length > 0;
if (!hasChanges) {
return null;
}
return {
id: newerSeries.id,
books: changedBookIds,
characters: changedCharacterIds,
characterAttributes: changedCharacterAttributeIds,
worlds: changedWorldIds,
worldElements: changedWorldElementIds,
locations: changedLocationIds,
locationElements: changedLocationElementIds,
locationSubElements: changedLocationSubElementIds,
spells: changedSpellIds,
spellTags: changedSpellTagIds
};
}

View File

@@ -7,6 +7,11 @@ export default class System{
return pattern.test(input);
}
public static timeStampInSeconds(): number {
const date: number = new Date().getTime();
return Math.floor(date / 1000);
}
public static formatHTMLContent(htmlContent: string): string {
return htmlContent
.replace(/<h1>/g, '<h1 style="color: #FFFFFF; text-indent: 5px; font-size: 28px; font-weight: bold; text-align: left; margin-vertical: 10px;">')

View File

@@ -11,8 +11,14 @@ import {
faSnowflake,
faUserCog,
faUserFriends,
IconDefinition,
} from '@fortawesome/free-solid-svg-icons';
import {ElementSection} from "@/components/book/settings/world/WorldSetting";
export interface ElementSection {
title: string;
section: keyof WorldProps;
icon: IconDefinition;
}
export interface WorldElement {
id: string;
@@ -40,6 +46,7 @@ export interface WorldProps {
ethnicGroups: WorldElement[];
socialClasses: WorldElement[];
importantCharacters: WorldElement[];
seriesWorldId?: string | null;
}
export interface WorldListResponse {