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:
@@ -70,6 +70,7 @@ export interface BookProps {
|
||||
title: string;
|
||||
author?: Author;
|
||||
serie?: number;
|
||||
seriesId?: string | null;
|
||||
subTitle?: string;
|
||||
summary?: string;
|
||||
publicationDate?: string;
|
||||
|
||||
@@ -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
170
lib/models/Series.ts
Normal 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;
|
||||
}
|
||||
@@ -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[] = [
|
||||
|
||||
@@ -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
280
lib/models/SyncedSeries.ts
Normal 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
|
||||
};
|
||||
}
|
||||
@@ -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;">')
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user