Update new unified dose management, style/other improvements

This commit is contained in:
2025-12-02 21:39:17 +00:00
parent 3fa5bf8360
commit bbdfc5f894
15 changed files with 830 additions and 495 deletions

View File

@@ -34,9 +34,8 @@ const CHART_COLORS = {
} as const;
const SimulationChart = ({
idealProfile,
deviatedProfile,
correctedProfile,
combinedProfile,
templateProfile,
chartView,
showDayTimeOnXAxis,
therapeuticRange,
@@ -57,8 +56,6 @@ const SimulationChart = ({
return ticks;
}, [totalHours]);
const chartWidthPercentage = Math.max(100, (totalHours / ( (parseInt(displayedDays, 10) || 2) * 25)) * 100);
const chartDomain = React.useMemo(() => {
const numMin = parseFloat(yAxisMin);
const numMax = parseFloat(yAxisMax);
@@ -71,49 +68,134 @@ const SimulationChart = ({
const mergedData = React.useMemo(() => {
const dataMap = new Map();
// Add ideal profile data
idealProfile?.forEach((point: any) => {
// Add combined profile data (actual plan with all days)
combinedProfile?.forEach((point: any) => {
dataMap.set(point.timeHours, {
timeHours: point.timeHours,
idealDamph: point.damph,
idealLdx: point.ldx
combinedDamph: point.damph,
combinedLdx: point.ldx
});
});
// Add deviated profile data
deviatedProfile?.forEach((point: any) => {
// Add template profile data (regular plan only) if provided
templateProfile?.forEach((point: any) => {
const existing = dataMap.get(point.timeHours) || { timeHours: point.timeHours };
dataMap.set(point.timeHours, {
...existing,
deviatedDamph: point.damph,
deviatedLdx: point.ldx
});
});
// Add corrected profile data
correctedProfile?.forEach((point: any) => {
const existing = dataMap.get(point.timeHours) || { timeHours: point.timeHours };
dataMap.set(point.timeHours, {
...existing,
correctedDamph: point.damph,
correctedLdx: point.ldx
templateDamph: point.damph,
templateLdx: point.ldx
});
});
return Array.from(dataMap.values()).sort((a, b) => a.timeHours - b.timeHours);
}, [idealProfile, deviatedProfile, correctedProfile]);
}, [combinedProfile, templateProfile]);
// Calculate chart dimensions
const [containerWidth, setContainerWidth] = React.useState(1000);
const containerRef = React.useRef<HTMLDivElement>(null);
React.useEffect(() => {
const updateWidth = () => {
if (containerRef.current) {
setContainerWidth(containerRef.current.clientWidth);
}
};
updateWidth();
window.addEventListener('resize', updateWidth);
return () => window.removeEventListener('resize', updateWidth);
}, []);
const simDays = parseInt(simulationDays, 10) || 3;
const dispDays = parseInt(displayedDays, 10) || 2;
// Y-axis takes ~80px, scrollable area gets the rest
const yAxisWidth = 80;
const scrollableWidth = containerWidth - yAxisWidth;
// Calculate chart width for scrollable area
const chartWidth = simDays <= dispDays
? scrollableWidth
: Math.ceil((scrollableWidth / dispDays) * simDays);
return (
<div className="flex-grow w-full overflow-x-auto overflow-y-hidden">
<div style={{ width: `${chartWidthPercentage}%`, height: '100%', minWidth: '100%' }}>
<div ref={containerRef} className="flex-grow w-full flex flex-col overflow-y-hidden">
{/* Fixed Legend at top */}
<div style={{ height: 40, marginBottom: 8, paddingLeft: yAxisWidth + 10 }}>
<ResponsiveContainer width="100%" height="100%">
<LineChart data={mergedData} margin={{ top: 20, right: 20, left: 0, bottom: 5 }}>
<CartesianGrid strokeDasharray="3 3" />
<LineChart data={mergedData} margin={{ top: 0, right: 20, left: 0, bottom: 0 }}>
<Legend
verticalAlign="top"
align="left"
height={36}
wrapperStyle={{ paddingLeft: 0 }}
/>
{/* Invisible lines just to show in legend */}
{(chartView === 'damph' || chartView === 'both') && (
<Line
dataKey="combinedDamph"
name={`${t.dAmphetamine}`}
stroke={CHART_COLORS.idealDamph}
strokeWidth={2.5}
dot={false}
strokeOpacity={0}
/>
)}
{(chartView === 'ldx' || chartView === 'both') && (
<Line
dataKey="combinedLdx"
name={`${t.lisdexamfetamine}`}
stroke={CHART_COLORS.idealLdx}
strokeWidth={2}
strokeDasharray="3 3"
dot={false}
strokeOpacity={0}
/>
)}
{templateProfile && (chartView === 'damph' || chartView === 'both') && (
<Line
dataKey="templateDamph"
name={`${t.dAmphetamine} (${t.regularPlan} ${t.continuation || 'continuation'})`}
stroke={CHART_COLORS.idealDamph}
strokeWidth={2}
strokeDasharray="3 3"
dot={false}
strokeOpacity={0}
/>
)}
{templateProfile && (chartView === 'ldx' || chartView === 'both') && (
<Line
dataKey="templateLdx"
name={`${t.lisdexamfetamine} (${t.regularPlan} ${t.continuation || 'continuation'})`}
stroke={CHART_COLORS.idealLdx}
strokeWidth={1.5}
strokeDasharray="3 3"
dot={false}
strokeOpacity={0}
/>
)}
</LineChart>
</ResponsiveContainer>
</div>
{/* Chart */}
<div className="flex-grow flex overflow-y-hidden">
{/* Scrollable chart area */}
<div className="flex-grow overflow-x-auto overflow-y-hidden">
<div style={{ width: chartWidth, height: '100%' }}>
<ResponsiveContainer width="100%" height="100%">
<LineChart
data={mergedData}
margin={{ top: 0, right: 20, left: 0, bottom: 5 }}
syncId="medPlanChart"
>
<XAxis
dataKey="timeHours"
type="number"
domain={[0, totalHours]}
ticks={chartTicks}
tickCount={chartTicks.length}
interval={0}
tickFormatter={(h) => {
if (showDayTimeOnXAxis) {
// Show 24h repeating format (0-23h)
@@ -126,19 +208,25 @@ const SimulationChart = ({
xAxisId="hours"
/>
<YAxis
label={{ value: t.concentration, angle: -90, position: 'insideLeft', offset: -10 }}
domain={chartDomain as any}
allowDecimals={false}
/>
yAxisId="concentration"
//label={{ value: t.concentration, angle: -90, position: 'insideLeft', offset: -10 }}
domain={chartDomain as any}
allowDecimals={false}
/>
<Tooltip
formatter={(value: any, name) => [`${typeof value === 'number' ? value.toFixed(1) : value} ${t.ngml}`, name]}
labelFormatter={(label) => `${t.hour.replace('h', 'Hour')}: ${label}${t.hour}`}
labelFormatter={(label, payload) => {
// Extract timeHours from the payload data point
const timeHours = payload?.[0]?.payload?.timeHours ?? label;
return `${t.hour.replace('h', 'Hour')}: ${timeHours}${t.hour}`;
}}
wrapperStyle={{ pointerEvents: 'none', zIndex: 200 }}
allowEscapeViewBox={{ x: false, y: false }}
cursor={{ stroke: CHART_COLORS.cursor, strokeWidth: 1, strokeDasharray: '1 1' }}
position={{ y: 0 }}
/>
<Legend verticalAlign="top" align="left" height={36} wrapperStyle={{ zIndex: 100, marginLeft: 60 }} />
/>
<CartesianGrid strokeDasharray="1 1" xAxisId="hours" yAxisId="concentration" />
{(chartView === 'damph' || chartView === 'both') && (
<ReferenceLine
@@ -147,6 +235,7 @@ const SimulationChart = ({
stroke={CHART_COLORS.therapeuticMin}
strokeDasharray="3 3"
xAxisId="hours"
yAxisId="concentration"
/>
)}
{(chartView === 'damph' || chartView === 'both') && (
@@ -156,10 +245,11 @@ const SimulationChart = ({
stroke={CHART_COLORS.therapeuticMax}
strokeDasharray="3 3"
xAxisId="hours"
yAxisId="concentration"
/>
)}
{[...Array(parseInt(simulationDays, 10) || 0).keys()].map(day => (
{[...Array(parseInt(simulationDays, 10) || 3).keys()].map(day => (
day > 0 && (
<ReferenceLine
key={day}
@@ -174,85 +264,68 @@ const SimulationChart = ({
{(chartView === 'damph' || chartView === 'both') && (
<Line
type="monotone"
dataKey="idealDamph"
name={`${t.dAmphetamine} (Ideal)`}
dataKey="combinedDamph"
name={`${t.dAmphetamine}`}
stroke={CHART_COLORS.idealDamph}
strokeWidth={2.5}
dot={false}
xAxisId="hours"
yAxisId="concentration"
connectNulls
/>
)}
{(chartView === 'ldx' || chartView === 'both') && (
<Line
type="monotone"
dataKey="idealLdx"
name={`${t.lisdexamfetamine} (Ideal)`}
dataKey="combinedLdx"
name={`${t.lisdexamfetamine}`}
stroke={CHART_COLORS.idealLdx}
strokeWidth={2}
dot={false}
strokeDasharray="3 3"
xAxisId="hours"
yAxisId="concentration"
connectNulls
/>
)}
{deviatedProfile && (chartView === 'damph' || chartView === 'both') && (
{templateProfile && (chartView === 'damph' || chartView === 'both') && (
<Line
type="monotone"
dataKey="deviatedDamph"
name={`${t.dAmphetamine} (Deviation)`}
stroke={CHART_COLORS.deviatedDamph}
dataKey="templateDamph"
name={`${t.dAmphetamine} (${t.regularPlan} ${t.continuation || 'continuation'})`}
stroke={CHART_COLORS.idealDamph}
strokeWidth={2}
strokeDasharray="5 5"
strokeDasharray="3 3"
dot={false}
xAxisId="hours"
yAxisId="concentration"
connectNulls
strokeOpacity={0.5}
/>
)}
{deviatedProfile && (chartView === 'ldx' || chartView === 'both') && (
{templateProfile && (chartView === 'ldx' || chartView === 'both') && (
<Line
type="monotone"
dataKey="deviatedLdx"
name={`${t.lisdexamfetamine} (Deviation)`}
stroke={CHART_COLORS.deviatedLdx}
dataKey="templateLdx"
name={`${t.lisdexamfetamine} (${t.regularPlan} ${t.continuation || 'continuation'})`}
stroke={CHART_COLORS.idealLdx}
strokeWidth={1.5}
strokeDasharray="5 5"
strokeDasharray="3 3"
dot={false}
xAxisId="hours"
yAxisId="concentration"
connectNulls
strokeOpacity={0.5}
/>
)}
{correctedProfile && (chartView === 'damph' || chartView === 'both') && (
<Line
type="monotone"
dataKey="correctedDamph"
name={`${t.dAmphetamine} (Correction)`}
stroke={CHART_COLORS.correctedDamph}
strokeWidth={2.5}
strokeDasharray="3 7"
dot={false}
xAxisId="hours"
connectNulls
/>
)}
{correctedProfile && (chartView === 'ldx' || chartView === 'both') && (
<Line
type="monotone"
dataKey="correctedLdx"
name={`${t.lisdexamfetamine} (Correction)`}
stroke={CHART_COLORS.correctedLdx}
strokeWidth={2}
strokeDasharray="3 7"
dot={false}
xAxisId="hours"
connectNulls
/>
)}
</LineChart>
</ResponsiveContainer>
</LineChart>
</ResponsiveContainer>
</div>
</div>
</div>
</div>
);
};export default SimulationChart;
};
export default SimulationChart;