Expand character model with additional attributes and advanced customization options
- Added fields such as `nickname`, `age`, `gender`, `species`, `nationality`, `status`, and others to enhance character customization. - Modified localization files to include new field labels and placeholders. - Updated `CharacterComponent` and `CharacterDetail` components with UI elements for the newly added attributes. - Introduced "Advanced Mode" toggle to manage visibility of extended customization options. - Refactored database models and repository methods (`addNewCharacter`, `updateCharacter`, and `fetchCharacters`) to handle the extended schema. - Improved data encryption and decryption workflows for secure storage of added attributes. - Enhanced user experience by reorganizing character customization layouts.
This commit is contained in:
@@ -35,12 +35,23 @@ const initialCharacterState: CharacterProps = {
|
||||
id: null,
|
||||
name: '',
|
||||
lastName: '',
|
||||
nickname: '',
|
||||
age: '',
|
||||
gender: '',
|
||||
species: '',
|
||||
nationality: '',
|
||||
status: 'alive',
|
||||
category: 'none',
|
||||
title: '',
|
||||
role: '',
|
||||
image: 'https://via.placeholder.com/150',
|
||||
biography: '',
|
||||
history: '',
|
||||
speechPattern: '',
|
||||
catchphrase: '',
|
||||
residence: '',
|
||||
notes: '',
|
||||
color: '',
|
||||
physical: [],
|
||||
psychological: [],
|
||||
relations: [],
|
||||
@@ -49,6 +60,16 @@ const initialCharacterState: CharacterProps = {
|
||||
strengths: [],
|
||||
goals: [],
|
||||
motivations: [],
|
||||
arc: [],
|
||||
secrets: [],
|
||||
fears: [],
|
||||
flaws: [],
|
||||
beliefs: [],
|
||||
conflicts: [],
|
||||
quotes: [],
|
||||
distinguishingMarks: [],
|
||||
items: [],
|
||||
affiliations: [],
|
||||
};
|
||||
|
||||
export function CharacterComponent({showToggle = true}: {showToggle?: boolean}, ref: any) {
|
||||
@@ -102,7 +123,8 @@ export function CharacterComponent({showToggle = true}: {showToggle?: boolean},
|
||||
setBook({...book, tools: {
|
||||
characters: enabled,
|
||||
worlds: book.tools?.worlds ?? false,
|
||||
locations: book.tools?.locations ?? false
|
||||
locations: book.tools?.locations ?? false,
|
||||
spells: book.tools?.spells ?? false
|
||||
}});
|
||||
}
|
||||
} catch (e: unknown) {
|
||||
@@ -133,7 +155,8 @@ export function CharacterComponent({showToggle = true}: {showToggle?: boolean},
|
||||
setBook({...book, tools: {
|
||||
characters: response.enabled,
|
||||
worlds: book.tools?.worlds ?? false,
|
||||
locations: book.tools?.locations ?? false
|
||||
locations: book.tools?.locations ?? false,
|
||||
spells: book.tools?.spells ?? false
|
||||
}});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,23 +10,26 @@ import {
|
||||
CharacterAttribute,
|
||||
characterCategories,
|
||||
CharacterElement,
|
||||
characterElementCategory,
|
||||
basicCharacterElements,
|
||||
advancedCharacterElements,
|
||||
CharacterProps,
|
||||
characterTitle
|
||||
characterStatus
|
||||
} from "@/lib/models/Character";
|
||||
import System from "@/lib/models/System";
|
||||
import {
|
||||
faAddressCard,
|
||||
faArrowLeft,
|
||||
faBook,
|
||||
faLayerGroup,
|
||||
faPlus,
|
||||
faSave,
|
||||
faScroll,
|
||||
faUser
|
||||
faUser,
|
||||
faSliders,
|
||||
faGlobe,
|
||||
faCommentDots,
|
||||
faStickyNote
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
|
||||
import {Dispatch, SetStateAction, useContext, useEffect} from "react";
|
||||
import {Dispatch, SetStateAction, useContext, useEffect, useState} from "react";
|
||||
import CharacterSectionElement from "@/components/book/settings/characters/CharacterSectionElement";
|
||||
import DeleteButton from "@/components/form/DeleteButton";
|
||||
import {useTranslations} from "next-intl";
|
||||
@@ -67,6 +70,7 @@ export default function CharacterDetail(
|
||||
const {book} = useContext(BookContext);
|
||||
const {session} = useContext(SessionContext);
|
||||
const {errorMessage} = useContext(AlertContext);
|
||||
const [showAdvanced, setShowAdvanced] = useState<boolean>(false);
|
||||
|
||||
useEffect((): void => {
|
||||
if (selectedCharacter?.id !== null) {
|
||||
@@ -104,13 +108,24 @@ export default function CharacterDetail(
|
||||
setSelectedCharacter({
|
||||
id: selectedCharacter?.id ?? '',
|
||||
name: selectedCharacter?.name ?? '',
|
||||
image: selectedCharacter?.image ?? '',
|
||||
lastName: selectedCharacter?.lastName ?? '',
|
||||
nickname: selectedCharacter?.nickname ?? '',
|
||||
age: selectedCharacter?.age ?? '',
|
||||
gender: selectedCharacter?.gender ?? '',
|
||||
species: selectedCharacter?.species ?? '',
|
||||
nationality: selectedCharacter?.nationality ?? '',
|
||||
status: selectedCharacter?.status ?? 'alive',
|
||||
category: selectedCharacter?.category ?? 'none',
|
||||
title: selectedCharacter?.title ?? '',
|
||||
image: selectedCharacter?.image ?? '',
|
||||
role: selectedCharacter?.role ?? '',
|
||||
biography: selectedCharacter?.biography,
|
||||
history: selectedCharacter?.history,
|
||||
role: selectedCharacter?.role ?? '',
|
||||
speechPattern: selectedCharacter?.speechPattern,
|
||||
catchphrase: selectedCharacter?.catchphrase,
|
||||
residence: selectedCharacter?.residence,
|
||||
notes: selectedCharacter?.notes,
|
||||
color: selectedCharacter?.color,
|
||||
physical: attributes.physical ?? [],
|
||||
psychological: attributes.psychological ?? [],
|
||||
relations: attributes.relations ?? [],
|
||||
@@ -119,6 +134,16 @@ export default function CharacterDetail(
|
||||
strengths: attributes.strengths ?? [],
|
||||
goals: attributes.goals ?? [],
|
||||
motivations: attributes.motivations ?? [],
|
||||
arc: attributes.arc ?? [],
|
||||
secrets: attributes.secrets ?? [],
|
||||
fears: attributes.fears ?? [],
|
||||
flaws: attributes.flaws ?? [],
|
||||
beliefs: attributes.beliefs ?? [],
|
||||
conflicts: attributes.conflicts ?? [],
|
||||
quotes: attributes.quotes ?? [],
|
||||
distinguishingMarks: attributes.distinguishingMarks ?? [],
|
||||
items: attributes.items ?? [],
|
||||
affiliations: attributes.affiliations ?? [],
|
||||
});
|
||||
}
|
||||
} catch (e: unknown) {
|
||||
@@ -173,7 +198,7 @@ export default function CharacterDetail(
|
||||
/>
|
||||
}
|
||||
/>
|
||||
|
||||
|
||||
<InputField
|
||||
fieldName={t("characterDetail.lastName")}
|
||||
input={
|
||||
@@ -184,7 +209,18 @@ export default function CharacterDetail(
|
||||
/>
|
||||
}
|
||||
/>
|
||||
|
||||
|
||||
<InputField
|
||||
fieldName={t("characterDetail.nickname")}
|
||||
input={
|
||||
<TextInput
|
||||
value={selectedCharacter?.nickname || ''}
|
||||
setValue={(e) => handleCharacterChange('nickname', e.target.value)}
|
||||
placeholder={t("characterDetail.nicknamePlaceholder")}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
|
||||
<InputField
|
||||
fieldName={t("characterDetail.role")}
|
||||
input={
|
||||
@@ -196,23 +232,43 @@ export default function CharacterDetail(
|
||||
data={characterCategories}
|
||||
/>
|
||||
}
|
||||
icon={faLayerGroup}
|
||||
/>
|
||||
|
||||
|
||||
<InputField
|
||||
fieldName={t("characterDetail.title")}
|
||||
input={
|
||||
<SelectBox
|
||||
defaultValue={selectedCharacter?.title || 'none'}
|
||||
onChangeCallBack={(e) => handleCharacterChange('title', e.target.value)}
|
||||
data={characterTitle}
|
||||
<TextInput
|
||||
value={selectedCharacter?.title || ''}
|
||||
setValue={(e) => handleCharacterChange('title', e.target.value)}
|
||||
placeholder={t("characterDetail.titlePlaceholder")}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
|
||||
<InputField
|
||||
fieldName={t("characterDetail.gender")}
|
||||
input={
|
||||
<TextInput
|
||||
value={selectedCharacter?.gender || ''}
|
||||
setValue={(e) => handleCharacterChange('gender', e.target.value)}
|
||||
placeholder={t("characterDetail.genderPlaceholder")}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
|
||||
<InputField
|
||||
fieldName={t("characterDetail.age")}
|
||||
input={
|
||||
<TextInput
|
||||
value={selectedCharacter?.age || ''}
|
||||
setValue={(e) => handleCharacterChange('age', e.target.value)}
|
||||
placeholder={t("characterDetail.agePlaceholder")}
|
||||
/>
|
||||
}
|
||||
icon={faAddressCard}
|
||||
/>
|
||||
</div>
|
||||
</CollapsableArea>
|
||||
|
||||
|
||||
<CollapsableArea title={t("characterDetail.historySection")} icon={faUser}>
|
||||
<div className="space-y-4 p-4 bg-secondary/20 rounded-xl border border-secondary/30">
|
||||
<InputField
|
||||
@@ -226,7 +282,7 @@ export default function CharacterDetail(
|
||||
}
|
||||
icon={faBook}
|
||||
/>
|
||||
|
||||
|
||||
<InputField
|
||||
fieldName={t("characterDetail.history")}
|
||||
input={
|
||||
@@ -238,7 +294,7 @@ export default function CharacterDetail(
|
||||
}
|
||||
icon={faScroll}
|
||||
/>
|
||||
|
||||
|
||||
<InputField
|
||||
fieldName={t("characterDetail.roleFull")}
|
||||
input={
|
||||
@@ -252,10 +308,11 @@ export default function CharacterDetail(
|
||||
/>
|
||||
</div>
|
||||
</CollapsableArea>
|
||||
|
||||
{characterElementCategory.map((item: CharacterElement, index: number) => (
|
||||
|
||||
{/* Attributs de base - toujours visibles */}
|
||||
{basicCharacterElements.map((item: CharacterElement, index: number) => (
|
||||
<CharacterSectionElement
|
||||
key={index}
|
||||
key={`basic-${index}`}
|
||||
title={item.title}
|
||||
section={item.section}
|
||||
placeholder={item.placeholder}
|
||||
@@ -266,6 +323,149 @@ export default function CharacterDetail(
|
||||
handleRemoveElement={handleRemoveElement}
|
||||
/>
|
||||
))}
|
||||
|
||||
{/* Toggle Mode Avancé */}
|
||||
<div className="flex items-center justify-between p-4 bg-secondary/30 rounded-xl border border-secondary/50">
|
||||
<div className="flex items-center gap-3">
|
||||
<FontAwesomeIcon icon={faSliders} className="text-primary w-5 h-5"/>
|
||||
<span className="text-text-primary font-medium">{t("characterDetail.advancedMode")}</span>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => setShowAdvanced(!showAdvanced)}
|
||||
className={`px-4 py-2 rounded-lg transition-all duration-200 ${
|
||||
showAdvanced
|
||||
? 'bg-primary text-white'
|
||||
: 'bg-secondary/50 text-text-primary hover:bg-secondary'
|
||||
}`}
|
||||
>
|
||||
{showAdvanced ? t("characterDetail.hideAdvanced") : t("characterDetail.showAdvanced")}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Sections avancées - visibles uniquement si showAdvanced est true */}
|
||||
{showAdvanced && (
|
||||
<>
|
||||
{/* Identité étendue */}
|
||||
<CollapsableArea title={t("characterDetail.identitySection")} icon={faGlobe}>
|
||||
<div className="space-y-4 p-4 bg-secondary/20 rounded-xl border border-secondary/30">
|
||||
<InputField
|
||||
fieldName={t("characterDetail.species")}
|
||||
input={
|
||||
<TextInput
|
||||
value={selectedCharacter?.species || ''}
|
||||
setValue={(e) => handleCharacterChange('species', e.target.value)}
|
||||
placeholder={t("characterDetail.speciesPlaceholder")}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
|
||||
<InputField
|
||||
fieldName={t("characterDetail.nationality")}
|
||||
input={
|
||||
<TextInput
|
||||
value={selectedCharacter?.nationality || ''}
|
||||
setValue={(e) => handleCharacterChange('nationality', e.target.value)}
|
||||
placeholder={t("characterDetail.nationalityPlaceholder")}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
|
||||
<InputField
|
||||
fieldName={t("characterDetail.status")}
|
||||
input={
|
||||
<SelectBox
|
||||
defaultValue={selectedCharacter?.status || 'alive'}
|
||||
onChangeCallBack={(e) => setSelectedCharacter(prev =>
|
||||
prev ? {...prev, status: e.target.value as CharacterProps['status']} : prev
|
||||
)}
|
||||
data={characterStatus}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
|
||||
<InputField
|
||||
fieldName={t("characterDetail.residence")}
|
||||
input={
|
||||
<TextInput
|
||||
value={selectedCharacter?.residence || ''}
|
||||
setValue={(e) => handleCharacterChange('residence', e.target.value)}
|
||||
placeholder={t("characterDetail.residencePlaceholder")}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</CollapsableArea>
|
||||
|
||||
{/* Voix du personnage */}
|
||||
<CollapsableArea title={t("characterDetail.voiceSection")} icon={faCommentDots}>
|
||||
<div className="space-y-4 p-4 bg-secondary/20 rounded-xl border border-secondary/30">
|
||||
<InputField
|
||||
fieldName={t("characterDetail.speechPattern")}
|
||||
input={
|
||||
<TexteAreaInput
|
||||
value={selectedCharacter?.speechPattern || ''}
|
||||
setValue={(e) => handleCharacterChange('speechPattern', e.target.value)}
|
||||
placeholder={t("characterDetail.speechPatternPlaceholder")}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
|
||||
<InputField
|
||||
fieldName={t("characterDetail.catchphrase")}
|
||||
input={
|
||||
<TextInput
|
||||
value={selectedCharacter?.catchphrase || ''}
|
||||
setValue={(e) => handleCharacterChange('catchphrase', e.target.value)}
|
||||
placeholder={t("characterDetail.catchphrasePlaceholder")}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</CollapsableArea>
|
||||
|
||||
{/* Notes de l'auteur */}
|
||||
<CollapsableArea title={t("characterDetail.authorSection")} icon={faStickyNote}>
|
||||
<div className="space-y-4 p-4 bg-secondary/20 rounded-xl border border-secondary/30">
|
||||
<InputField
|
||||
fieldName={t("characterDetail.notes")}
|
||||
input={
|
||||
<TexteAreaInput
|
||||
value={selectedCharacter?.notes || ''}
|
||||
setValue={(e) => handleCharacterChange('notes', e.target.value)}
|
||||
placeholder={t("characterDetail.notesPlaceholder")}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
|
||||
<InputField
|
||||
fieldName={t("characterDetail.colorLabel")}
|
||||
input={
|
||||
<TextInput
|
||||
value={selectedCharacter?.color || ''}
|
||||
setValue={(e) => handleCharacterChange('color', e.target.value)}
|
||||
placeholder={t("characterDetail.colorPlaceholder")}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</CollapsableArea>
|
||||
|
||||
{/* Attributs avancés */}
|
||||
{advancedCharacterElements.map((item: CharacterElement, index: number) => (
|
||||
<CharacterSectionElement
|
||||
key={`advanced-${index}`}
|
||||
title={item.title}
|
||||
section={item.section}
|
||||
placeholder={item.placeholder}
|
||||
icon={item.icon}
|
||||
selectedCharacter={selectedCharacter as CharacterProps}
|
||||
setSelectedCharacter={setSelectedCharacter}
|
||||
handleAddElement={handleAddElement}
|
||||
handleRemoveElement={handleRemoveElement}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user