Enhance SheetHeader
component: add error handling, implement loading states, support conditional rendering for attributes, and improve state management with useCallback
.
This commit is contained in:
@@ -61,19 +61,23 @@
|
||||
}
|
||||
],
|
||||
"attributeAdjustmentSelected": "ATTR_3",
|
||||
"ae": 0,
|
||||
"kp": 0,
|
||||
"lp": 0,
|
||||
"ae": 15,
|
||||
"kp": 15,
|
||||
"lp": 15,
|
||||
"sp": 2,
|
||||
"permanentAE": {
|
||||
"lost": 0,
|
||||
"lost": 3,
|
||||
"redeemed": 0
|
||||
},
|
||||
"permanentKP": {
|
||||
"lost": 0,
|
||||
"lost": 2,
|
||||
"redeemed": 0
|
||||
},
|
||||
"permanentLP": {
|
||||
"lost": 0
|
||||
"lost": 12
|
||||
},
|
||||
"permanentSP": {
|
||||
"lost": 1
|
||||
}
|
||||
},
|
||||
"activatable": {
|
||||
|
@@ -6,58 +6,90 @@ import {
|
||||
loadRace,
|
||||
loadRaceVariant
|
||||
} from "@/utils/loaders";
|
||||
import {useEffect, useState} from 'react';
|
||||
import {useCallback, useEffect, useState} from 'react';
|
||||
|
||||
export default function SheetHeader({jsonData}: { jsonData: CharacterData }) {
|
||||
const [raceName, setRaceName] = useState<string>(jsonData.r);
|
||||
const [raceVariantName, setRaceVariantName] = useState<string>(jsonData.rv || '');
|
||||
const [professionName, setProfessionName] = useState<string>(jsonData.p);
|
||||
const [attributes, setAttributes] = useState<AttributeWithValue[]>([]);
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
const resetToDefaults = useCallback(() => {
|
||||
setRaceName(jsonData.r);
|
||||
setRaceVariantName(jsonData.rv || '');
|
||||
setProfessionName(jsonData.p);
|
||||
setAttributes([]);
|
||||
}, [jsonData.r, jsonData.rv, jsonData.p]);
|
||||
|
||||
const loadData = useCallback(async (signal: AbortSignal) => {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
// Load race and profession using the new loader functions
|
||||
const [race, raceVariant, profession, loadedAttributes] = await Promise.all([
|
||||
loadRace(jsonData.r),
|
||||
loadRaceVariant(jsonData.rv || ''),
|
||||
loadProfession(jsonData.p),
|
||||
loadAttributesWithValues(jsonData.attr.values),
|
||||
]);
|
||||
|
||||
// Check if component is still mounted and request wasn't cancelled
|
||||
if (signal.aborted) return;
|
||||
|
||||
// Update state with loaded data
|
||||
setRaceName(race?.name || jsonData.r);
|
||||
setRaceVariantName(raceVariant?.name || '');
|
||||
|
||||
// Handle profession name with gender preference
|
||||
if (profession?.name) {
|
||||
setProfessionName(profession.name.m || profession.name.f || jsonData.p);
|
||||
} else {
|
||||
setProfessionName(jsonData.p);
|
||||
}
|
||||
|
||||
setAttributes(loadedAttributes);
|
||||
} catch (err) {
|
||||
if (signal.aborted) return;
|
||||
|
||||
const errorMessage = err instanceof Error ? err.message : 'Unknown error occurred';
|
||||
console.error('Error loading character data:', errorMessage);
|
||||
setError(errorMessage);
|
||||
|
||||
// Reset to default values on error
|
||||
resetToDefaults();
|
||||
} finally {
|
||||
if (!signal.aborted) {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
}, [jsonData.r, jsonData.rv, jsonData.p, jsonData.attr, resetToDefaults]);
|
||||
|
||||
useEffect(() => {
|
||||
let isMounted = true;
|
||||
const abortController = new AbortController();
|
||||
|
||||
const loadData = async () => {
|
||||
try {
|
||||
// Load race and profession using the new loader functions
|
||||
const [race, raceVariant, profession] = await Promise.all([
|
||||
loadRace(jsonData.r),
|
||||
loadRaceVariant(jsonData.rv || ''),
|
||||
loadProfession(jsonData.p)
|
||||
]);
|
||||
|
||||
// Process attributes using the new loadAttributesWithValues function
|
||||
const loadedAttributes = await loadAttributesWithValues(jsonData.attr.values);
|
||||
|
||||
if (isMounted) {
|
||||
setRaceName(race?.name || jsonData.r);
|
||||
setRaceVariantName(raceVariant?.name || '');
|
||||
// For profession, handle gendered name
|
||||
if (profession?.name) {
|
||||
setProfessionName(profession.name.m || profession.name.f || jsonData.p);
|
||||
} else {
|
||||
setProfessionName(jsonData.p);
|
||||
}
|
||||
// Set the attributes with their values, names, and short forms
|
||||
setAttributes(loadedAttributes);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading data:', error);
|
||||
if (isMounted) {
|
||||
setRaceName(jsonData.r);
|
||||
setProfessionName(jsonData.p);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
loadData();
|
||||
loadData(abortController.signal);
|
||||
|
||||
return () => {
|
||||
isMounted = false;
|
||||
abortController.abort();
|
||||
};
|
||||
}, [jsonData.r, jsonData.rv, jsonData.p, jsonData.attr]);
|
||||
}, [loadData]);
|
||||
|
||||
console.log(jsonData)
|
||||
// Optional: Add loading state handling
|
||||
if (loading) {
|
||||
return <div className="animate-pulse">Loading character data...</div>;
|
||||
}
|
||||
|
||||
// Optional: Add error state handling
|
||||
if (error) {
|
||||
return (
|
||||
<div className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded">
|
||||
Error loading character data: {error}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -66,19 +98,19 @@ export default function SheetHeader({jsonData}: { jsonData: CharacterData }) {
|
||||
<p className="border-b border-gray-500 font-semibold">
|
||||
{jsonData.name}
|
||||
</p>
|
||||
<small>Name</small>
|
||||
<small className="text-gray-400">Name</small>
|
||||
</div>
|
||||
<div className="w-[30%]">
|
||||
<p className="border-b border-gray-500 font-semibold">
|
||||
{raceName} {raceVariantName ? <>({raceVariantName})</> : ''}
|
||||
</p>
|
||||
<small>Spezies</small>
|
||||
<small className="text-gray-400">Spezies</small>
|
||||
</div>
|
||||
<div className="w-[30%]">
|
||||
<p className="border-b border-gray-500 font-semibold">
|
||||
{professionName}
|
||||
</p>
|
||||
<small>Profession</small>
|
||||
<small className="text-gray-400">Profession</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -103,7 +135,8 @@ export default function SheetHeader({jsonData}: { jsonData: CharacterData }) {
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="w-[100%] flex pt-2">
|
||||
{jsonData.attr.lp && (
|
||||
<div className="w-[100%] flex pt-2">
|
||||
<span className="relative group cursor-help">
|
||||
LP
|
||||
<span
|
||||
@@ -111,54 +144,88 @@ export default function SheetHeader({jsonData}: { jsonData: CharacterData }) {
|
||||
Lebenspunkte
|
||||
</span>
|
||||
</span>
|
||||
<div
|
||||
className="ms-5 w-[100%] bg-gradient-to-r from-red-900 to-red-600 rounded-full h-6 flex items-center justify-center text-white text-sm font-medium">
|
||||
15 / 15
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="ms-5 w-[100%] bg-gray-600 rounded-full h-6 flex items-center overflow-hidden relative">
|
||||
<div
|
||||
className="bg-gradient-to-r from-red-900 to-red-600 h-full transition-all duration-300"
|
||||
style={{width: `${Math.min(((jsonData.attr.lp - jsonData.attr.permanentLP.lost) / jsonData.attr.lp) * 100, 100)}%`}}
|
||||
/>
|
||||
<div className="absolute inset-0 flex items-center justify-center text-white text-sm font-medium">
|
||||
{jsonData.attr.lp} / {jsonData.attr.lp - jsonData.attr.permanentLP.lost}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="w-[100%] flex pt-2">
|
||||
<span className="relative group cursor-help">
|
||||
AP
|
||||
<span
|
||||
className="absolute left-0 bottom-full mb-1 px-2 bg-gray-800 text-white text-xs rounded opacity-0 group-hover:opacity-100 transition-opacity duration-200 whitespace-nowrap">
|
||||
Astralpunkte
|
||||
</span>
|
||||
</span>
|
||||
<div
|
||||
className="ms-5 w-[100%] bg-gradient-to-r from-blue-900 to-blue-500 rounded-full h-6 flex items-center justify-center text-white text-sm font-medium">
|
||||
16 / 16
|
||||
</div>
|
||||
</div>
|
||||
{jsonData.attr.ae > 0 && (
|
||||
<div className="w-[100%] flex pt-2">
|
||||
<span className="relative group cursor-help">
|
||||
AP
|
||||
<span
|
||||
className="absolute left-0 bottom-full mb-1 px-2 bg-gray-800 text-white text-xs rounded opacity-0 group-hover:opacity-100 transition-opacity duration-200 whitespace-nowrap">
|
||||
Astralpunkte
|
||||
</span>
|
||||
</span>
|
||||
<div
|
||||
className="ms-5 w-[100%] bg-gray-600 rounded-full h-6 flex items-center overflow-hidden relative">
|
||||
<div
|
||||
className="bg-gradient-to-r from-blue-900 to-blue-500 h-full transition-all duration-300"
|
||||
style={{width: `${Math.min(((jsonData.attr.ae - jsonData.attr.permanentAE.lost) / jsonData.attr.ae) * 100, 100)}%`}}
|
||||
/>
|
||||
<div className="absolute inset-0 flex items-center justify-center text-white text-sm font-medium">
|
||||
{jsonData.attr.ae} / {jsonData.attr.ae - jsonData.attr.permanentAE.lost}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="w-[100%] flex pt-2">
|
||||
<span className="relative group cursor-help">
|
||||
KP
|
||||
<span
|
||||
className="absolute left-0 bottom-full mb-1 px-2 bg-gray-800 text-white text-xs rounded opacity-0 group-hover:opacity-100 transition-opacity duration-200 whitespace-nowrap">
|
||||
Karmapunkte
|
||||
</span>
|
||||
</span>
|
||||
<div
|
||||
className="ms-5 w-[100%] bg-gradient-to-r from-emerald-900 to-emerald-500 rounded-full h-6 flex items-center justify-center text-white text-sm font-medium">
|
||||
16 / 16
|
||||
</div>
|
||||
</div>
|
||||
{jsonData.attr.kp > 0 && (
|
||||
<div className="w-[100%] flex pt-2">
|
||||
<span className="relative group cursor-help">
|
||||
KP
|
||||
<span
|
||||
className="absolute left-0 bottom-full mb-1 px-2 bg-gray-800 text-white text-xs rounded opacity-0 group-hover:opacity-100 transition-opacity duration-200 whitespace-nowrap">
|
||||
Karmapunkte
|
||||
</span>
|
||||
</span>
|
||||
<div
|
||||
className="ms-5 w-[100%] bg-gray-600 rounded-full h-6 flex items-center overflow-hidden relative">
|
||||
<div
|
||||
className="bg-gradient-to-r from-emerald-900 to-emerald-500 h-full transition-all duration-300"
|
||||
style={{width: `${Math.min(((jsonData.attr.kp - jsonData.attr.permanentKP.lost) / jsonData.attr.kp) * 100, 100)}%`}}
|
||||
/>
|
||||
<div className="absolute inset-0 flex items-center justify-center text-white text-sm font-medium">
|
||||
{jsonData.attr.kp} / {jsonData.attr.kp - jsonData.attr.permanentKP.lost}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="w-[100%] flex pt-2">
|
||||
<span className="w-[10%] relative group cursor-help">
|
||||
SP
|
||||
<span
|
||||
className="absolute left-0 bottom-full mb-1 px-2 py-1 bg-gray-800 text-white text-xs rounded opacity-0 group-hover:opacity-100 transition-opacity duration-200 whitespace-nowrap">
|
||||
Schicksalspunkte
|
||||
</span>
|
||||
</span>
|
||||
<div className="w-[90%] flex items-center gap-2">
|
||||
<div className="rounded-full w-5 h-5 bg-gradient-to-br from-violet-700 to-violet-500"></div>
|
||||
<div className="rounded-full w-5 h-5 bg-gradient-to-br from-violet-700 to-violet-500"></div>
|
||||
<div className="rounded-full w-5 h-5 bg-gradient-to-br from-violet-700 to-violet-500"></div>
|
||||
</div>
|
||||
</div>
|
||||
{jsonData.attr.sp > 0 && (
|
||||
<div className="w-[100%] flex pt-2">
|
||||
<span className="relative group cursor-help">
|
||||
SP
|
||||
<span
|
||||
className="absolute left-0 bottom-full mb-1 px-2 py-1 bg-gray-800 text-white text-xs rounded opacity-0 group-hover:opacity-100 transition-opacity duration-200 whitespace-nowrap">
|
||||
Schicksalspunkte
|
||||
</span>
|
||||
</span>
|
||||
<div className="ms-5 w-[100%] flex items-center gap-2">
|
||||
{Array.from({length: jsonData.attr.sp - jsonData.attr.permanentSP.lost}, (_, index) => (
|
||||
<div
|
||||
key={`sp-available-${index}`}
|
||||
className="rounded-full w-5 h-5 bg-gradient-to-br from-violet-700 to-violet-500"
|
||||
/>
|
||||
))}
|
||||
{Array.from({length: jsonData.attr.permanentSP.lost}, (_, index) => (
|
||||
<div
|
||||
key={`sp-lost-${index}`}
|
||||
className="rounded-full w-5 h-5 bg-gradient-to-br from-gray-700 to-gray-500"
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
|
@@ -14,11 +14,11 @@ export interface Attributes {
|
||||
ae: number;
|
||||
kp: number;
|
||||
lp: number;
|
||||
sp: number;
|
||||
permanentAE: PermanentAttribute;
|
||||
permanentKP: PermanentAttribute;
|
||||
permanentLP: {
|
||||
lost: number;
|
||||
};
|
||||
permanentLP: PermanentAttribute;
|
||||
permanentSP: PermanentAttribute;
|
||||
}
|
||||
|
||||
export interface PersonalDetails {
|
||||
|
Reference in New Issue
Block a user