- Integrate `OfflineContext` into book, story settings, and related components. - Add conditional logic to toggle between server API requests and offline IPC handlers (`db:book:delete`, `db:book:story:get`, `db:location:all`, etc.). - Refactor and update IPC handlers to accept structured data arguments for improved consistency (`data: object`). - Ensure stricter typings in IPC handlers and frontend functions. - Improve error handling and user feedback in both online and offline modes.
239 lines
11 KiB
TypeScript
239 lines
11 KiB
TypeScript
import CollapsableArea from "@/components/CollapsableArea";
|
|
import InputField from "@/components/form/InputField";
|
|
import TexteAreaInput from "@/components/form/TexteAreaInput";
|
|
import TextInput from "@/components/form/TextInput";
|
|
import SelectBox from "@/components/form/SelectBox";
|
|
import {AlertContext} from "@/context/AlertContext";
|
|
import {SessionContext} from "@/context/SessionContext";
|
|
import {
|
|
CharacterAttribute,
|
|
characterCategories,
|
|
CharacterElement,
|
|
characterElementCategory,
|
|
CharacterProps,
|
|
characterTitle
|
|
} from "@/lib/models/Character";
|
|
import System from "@/lib/models/System";
|
|
import {
|
|
faAddressCard,
|
|
faArrowLeft,
|
|
faBook,
|
|
faLayerGroup,
|
|
faPlus,
|
|
faSave,
|
|
faScroll,
|
|
faUser
|
|
} from "@fortawesome/free-solid-svg-icons";
|
|
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
|
|
import {Dispatch, SetStateAction, useContext, useEffect} from "react";
|
|
import CharacterSectionElement from "@/components/book/settings/characters/CharacterSectionElement";
|
|
import {useTranslations} from "next-intl";
|
|
import {LangContext} from "@/context/LangContext";
|
|
import OfflineContext, {OfflineContextType} from "@/context/OfflineContext";
|
|
|
|
interface CharacterDetailProps {
|
|
selectedCharacter: CharacterProps | null;
|
|
setSelectedCharacter: Dispatch<SetStateAction<CharacterProps | null>>;
|
|
handleCharacterChange: (key: keyof CharacterProps, value: string) => void;
|
|
handleAddElement: (section: keyof CharacterProps, element: any) => void;
|
|
handleRemoveElement: (
|
|
section: keyof CharacterProps,
|
|
index: number,
|
|
attrId: string,
|
|
) => void;
|
|
handleSaveCharacter: () => void;
|
|
}
|
|
|
|
export default function CharacterDetail(
|
|
{
|
|
setSelectedCharacter,
|
|
selectedCharacter,
|
|
handleCharacterChange,
|
|
handleRemoveElement,
|
|
handleAddElement,
|
|
handleSaveCharacter,
|
|
}: CharacterDetailProps
|
|
) {
|
|
const t = useTranslations();
|
|
const {lang} = useContext(LangContext);
|
|
const {isCurrentlyOffline} = useContext<OfflineContextType>(OfflineContext);
|
|
const {session} = useContext(SessionContext);
|
|
const {errorMessage} = useContext(AlertContext);
|
|
|
|
useEffect((): void => {
|
|
if (selectedCharacter?.id !== null) {
|
|
getAttributes().then();
|
|
}
|
|
}, []);
|
|
|
|
async function getAttributes(): Promise<void> {
|
|
try {
|
|
let response: CharacterAttribute;
|
|
if (isCurrentlyOffline()) {
|
|
response = await window.electron.invoke<CharacterAttribute>('db:character:attributes', {
|
|
characterId: selectedCharacter?.id,
|
|
});
|
|
} else {
|
|
response = await System.authGetQueryToServer<CharacterAttribute>(`character/attribute`, session.accessToken, lang, {
|
|
characterId: selectedCharacter?.id,
|
|
});
|
|
}
|
|
if (response) {
|
|
setSelectedCharacter({
|
|
id: selectedCharacter?.id ?? '',
|
|
name: selectedCharacter?.name ?? '',
|
|
image: selectedCharacter?.image ?? '',
|
|
lastName: selectedCharacter?.lastName ?? '',
|
|
category: selectedCharacter?.category ?? 'none',
|
|
title: selectedCharacter?.title ?? '',
|
|
biography: selectedCharacter?.biography,
|
|
history: selectedCharacter?.history,
|
|
role: selectedCharacter?.role ?? '',
|
|
physical: response.physical ?? [],
|
|
psychological: response.psychological ?? [],
|
|
relations: response.relations ?? [],
|
|
skills: response.skills ?? [],
|
|
weaknesses: response.weaknesses ?? [],
|
|
strengths: response.strengths ?? [],
|
|
goals: response.goals ?? [],
|
|
motivations: response.motivations ?? [],
|
|
});
|
|
}
|
|
} catch (e: unknown) {
|
|
if (e instanceof Error) {
|
|
errorMessage(e.message);
|
|
} else {
|
|
errorMessage(t("characterDetail.fetchAttributesError"));
|
|
}
|
|
}
|
|
}
|
|
|
|
return (
|
|
<div className="space-y-4">
|
|
<div
|
|
className="flex justify-between items-center p-4 border-b border-secondary/50 bg-tertiary/50 backdrop-blur-sm">
|
|
<button onClick={() => setSelectedCharacter(null)}
|
|
className="flex items-center gap-2 bg-secondary/50 py-2 px-4 rounded-xl border border-secondary/50 hover:bg-secondary hover:border-secondary hover:shadow-md hover:scale-105 transition-all duration-200">
|
|
<FontAwesomeIcon icon={faArrowLeft} className="text-primary w-4 h-4"/>
|
|
<span className="text-text-primary font-medium">{t("characterDetail.back")}</span>
|
|
</button>
|
|
<span className="text-text-primary font-semibold text-lg">
|
|
{selectedCharacter?.name || t("characterDetail.newCharacter")}
|
|
</span>
|
|
<button onClick={handleSaveCharacter}
|
|
className="flex items-center justify-center bg-primary w-10 h-10 rounded-xl border border-primary-dark shadow-md hover:shadow-lg hover:scale-110 transition-all duration-200">
|
|
<FontAwesomeIcon icon={selectedCharacter?.id ? faSave : faPlus}
|
|
className="text-text-primary w-5 h-5"/>
|
|
</button>
|
|
</div>
|
|
|
|
<div className="overflow-y-auto max-h-[calc(100vh-350px)] space-y-4 px-2 pb-4">
|
|
<CollapsableArea title={t("characterDetail.basicInfo")} icon={faUser}>
|
|
<div className="space-y-4 p-4 bg-secondary/20 rounded-xl border border-secondary/30">
|
|
<InputField
|
|
fieldName={t("characterDetail.name")}
|
|
input={
|
|
<TextInput
|
|
value={selectedCharacter?.name || ''}
|
|
setValue={(e) => handleCharacterChange('name', e.target.value)}
|
|
placeholder={t("characterDetail.namePlaceholder")}
|
|
/>
|
|
}
|
|
/>
|
|
|
|
<InputField
|
|
fieldName={t("characterDetail.lastName")}
|
|
input={
|
|
<TextInput
|
|
value={selectedCharacter?.lastName || ''}
|
|
setValue={(e) => handleCharacterChange('lastName', e.target.value)}
|
|
placeholder={t("characterDetail.lastNamePlaceholder")}
|
|
/>
|
|
}
|
|
/>
|
|
|
|
<InputField
|
|
fieldName={t("characterDetail.role")}
|
|
input={
|
|
<SelectBox
|
|
defaultValue={selectedCharacter?.category || 'none'}
|
|
onChangeCallBack={(e) => setSelectedCharacter(prev =>
|
|
prev ? {...prev, category: e.target.value as CharacterProps['category']} : prev
|
|
)}
|
|
data={characterCategories}
|
|
/>
|
|
}
|
|
icon={faLayerGroup}
|
|
/>
|
|
|
|
<InputField
|
|
fieldName={t("characterDetail.title")}
|
|
input={
|
|
<SelectBox
|
|
defaultValue={selectedCharacter?.title || 'none'}
|
|
onChangeCallBack={(e) => handleCharacterChange('title', e.target.value)}
|
|
data={characterTitle}
|
|
/>
|
|
}
|
|
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
|
|
fieldName={t("characterDetail.biography")}
|
|
input={
|
|
<TexteAreaInput
|
|
value={selectedCharacter?.biography || ''}
|
|
setValue={(e) => handleCharacterChange('biography', e.target.value)}
|
|
placeholder={t("characterDetail.biographyPlaceholder")}
|
|
/>
|
|
}
|
|
icon={faBook}
|
|
/>
|
|
|
|
<InputField
|
|
fieldName={t("characterDetail.history")}
|
|
input={
|
|
<TexteAreaInput
|
|
value={selectedCharacter?.history || ''}
|
|
setValue={(e) => handleCharacterChange('history', e.target.value)}
|
|
placeholder={t("characterDetail.historyPlaceholder")}
|
|
/>
|
|
}
|
|
icon={faScroll}
|
|
/>
|
|
|
|
<InputField
|
|
fieldName={t("characterDetail.roleFull")}
|
|
input={
|
|
<TexteAreaInput
|
|
value={selectedCharacter?.role || ''}
|
|
setValue={(e) => handleCharacterChange('role', e.target.value)}
|
|
placeholder={t("characterDetail.roleFullPlaceholder")}
|
|
/>
|
|
}
|
|
icon={faScroll}
|
|
/>
|
|
</div>
|
|
</CollapsableArea>
|
|
|
|
{characterElementCategory.map((item: CharacterElement, index: number) => (
|
|
<CharacterSectionElement
|
|
key={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>
|
|
);
|
|
} |