Compare commits
2 Commits
955d3ad650
...
fbba3d6122
| Author | SHA1 | Date | |
|---|---|---|---|
| fbba3d6122 | |||
| a1298d64a7 |
@@ -394,11 +394,12 @@ const Settings = ({
|
|||||||
min={0}
|
min={0}
|
||||||
max={500}
|
max={500}
|
||||||
placeholder={t('min')}
|
placeholder={t('min')}
|
||||||
required={true}
|
required={false}
|
||||||
error={!!therapeuticRangeError || !therapeuticRange.min}
|
error={!!therapeuticRangeError}
|
||||||
errorMessage={formatText(therapeuticRangeError || t('errorTherapeuticRangeMinRequired') || 'Minimum therapeutic range is required')}
|
errorMessage={formatText(therapeuticRangeError)}
|
||||||
showResetButton={true}
|
showResetButton={true}
|
||||||
defaultValue={defaultsForT.therapeuticRangeMin}
|
defaultValue={defaultsForT.therapeuticRangeMin}
|
||||||
|
allowEmpty={true}
|
||||||
/>
|
/>
|
||||||
<span className="text-muted-foreground">-</span>
|
<span className="text-muted-foreground">-</span>
|
||||||
<FormNumericInput
|
<FormNumericInput
|
||||||
@@ -409,11 +410,12 @@ const Settings = ({
|
|||||||
max={500}
|
max={500}
|
||||||
placeholder={t('max')}
|
placeholder={t('max')}
|
||||||
unit="ng/ml"
|
unit="ng/ml"
|
||||||
required={true}
|
required={false}
|
||||||
error={!!therapeuticRangeError || !therapeuticRange.max}
|
error={!!therapeuticRangeError}
|
||||||
errorMessage={formatText(therapeuticRangeError || t('errorTherapeuticRangeMaxRequired') || 'Maximum therapeutic range is required')}
|
errorMessage={formatText(therapeuticRangeError)}
|
||||||
showResetButton={true}
|
showResetButton={true}
|
||||||
defaultValue={defaultsForT.therapeuticRangeMax}
|
defaultValue={defaultsForT.therapeuticRangeMax}
|
||||||
|
allowEmpty={true}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -76,6 +76,11 @@ const SimulationChart = React.memo(({
|
|||||||
const containerRef = React.useRef<HTMLDivElement>(null);
|
const containerRef = React.useRef<HTMLDivElement>(null);
|
||||||
const { width: containerWidth } = useElementSize(containerRef, 150);
|
const { width: containerWidth } = useElementSize(containerRef, 150);
|
||||||
|
|
||||||
|
// Guard against invalid dimensions during initial render
|
||||||
|
const yAxisWidth = 80;
|
||||||
|
const minContainerWidth = yAxisWidth + 100; // Minimum 100px for chart area
|
||||||
|
const safeContainerWidth = Math.max(containerWidth, minContainerWidth);
|
||||||
|
|
||||||
// Track current theme for chart styling
|
// Track current theme for chart styling
|
||||||
const [isDarkTheme, setIsDarkTheme] = React.useState(false);
|
const [isDarkTheme, setIsDarkTheme] = React.useState(false);
|
||||||
|
|
||||||
@@ -96,9 +101,8 @@ const SimulationChart = React.memo(({
|
|||||||
return () => observer.disconnect();
|
return () => observer.disconnect();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// Y-axis takes ~80px, scrollable area gets the rest
|
// Calculate scrollable width using safe container width
|
||||||
const yAxisWidth = 80;
|
const scrollableWidth = safeContainerWidth - yAxisWidth;
|
||||||
const scrollableWidth = containerWidth - yAxisWidth;
|
|
||||||
|
|
||||||
// Calculate chart width for scrollable area
|
// Calculate chart width for scrollable area
|
||||||
const chartWidth = simDays <= dispDays
|
const chartWidth = simDays <= dispDays
|
||||||
@@ -106,7 +110,7 @@ const SimulationChart = React.memo(({
|
|||||||
: Math.ceil((scrollableWidth / dispDays) * simDays);
|
: Math.ceil((scrollableWidth / dispDays) * simDays);
|
||||||
|
|
||||||
// Use shorter captions on narrow containers to reduce wrapping
|
// Use shorter captions on narrow containers to reduce wrapping
|
||||||
const isCompactLabels = containerWidth < 640; // tweakable threshold for mobile
|
const isCompactLabels = safeContainerWidth < 640; // tweakable threshold for mobile
|
||||||
|
|
||||||
// Precompute series labels with translations
|
// Precompute series labels with translations
|
||||||
const seriesLabels = React.useMemo<Record<string, { full: string; short: string; display: string }>>(() => {
|
const seriesLabels = React.useMemo<Record<string, { full: string; short: string; display: string }>>(() => {
|
||||||
@@ -446,6 +450,15 @@ const SimulationChart = React.memo(({
|
|||||||
);
|
);
|
||||||
}, [seriesLabels]);
|
}, [seriesLabels]);
|
||||||
|
|
||||||
|
// Don't render chart if dimensions are invalid (prevents crash during initialization)
|
||||||
|
if (chartWidth <= 0 || scrollableWidth <= 0) {
|
||||||
|
return (
|
||||||
|
<div ref={containerRef} className="flex-grow w-full flex flex-col overflow-y-hidden items-center justify-center text-muted-foreground">
|
||||||
|
<p>{t('loadingChart', { defaultValue: 'Loading chart...' })}</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Render the chart
|
// Render the chart
|
||||||
return (
|
return (
|
||||||
<div ref={containerRef} className="flex-grow w-full flex flex-col overflow-y-hidden">
|
<div ref={containerRef} className="flex-grow w-full flex flex-col overflow-y-hidden">
|
||||||
@@ -625,20 +638,20 @@ const SimulationChart = React.memo(({
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
{showTherapeuticRange && (chartView === 'damph' || chartView === 'both') && (
|
{showTherapeuticRange && (chartView === 'damph' || chartView === 'both') && therapeuticRange.min && !isNaN(parseFloat(therapeuticRange.min)) && (
|
||||||
<ReferenceLine
|
<ReferenceLine
|
||||||
y={parseFloat(therapeuticRange.min) || 0}
|
y={parseFloat(therapeuticRange.min)}
|
||||||
label={{ value: t('refLineMin'), position: 'insideTopLeft' }}
|
label={{ value: t('refLineMin'), position: 'insideBottomLeft', style: { fontSize: '0.75rem', fontStyle: 'italic', fill: CHART_COLORS.therapeuticMin } }}
|
||||||
stroke={CHART_COLORS.therapeuticMin}
|
stroke={CHART_COLORS.therapeuticMin}
|
||||||
strokeDasharray="3 3"
|
strokeDasharray="3 3"
|
||||||
xAxisId="hours"
|
xAxisId="hours"
|
||||||
yAxisId="concentration"
|
yAxisId="concentration"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{showTherapeuticRange && (chartView === 'damph' || chartView === 'both') && (
|
{showTherapeuticRange && (chartView === 'damph' || chartView === 'both') && therapeuticRange.max && !isNaN(parseFloat(therapeuticRange.max)) && (
|
||||||
<ReferenceLine
|
<ReferenceLine
|
||||||
y={parseFloat(therapeuticRange.max) || 0}
|
y={parseFloat(therapeuticRange.max)}
|
||||||
label={{ value: t('refLineMax'), position: 'insideTopLeft' }}
|
label={{ value: t('refLineMax'), position: 'insideTopLeft', style: { fontSize: '0.75rem', fontStyle: 'italic', fill: CHART_COLORS.therapeuticMax } }}
|
||||||
stroke={CHART_COLORS.therapeuticMax}
|
stroke={CHART_COLORS.therapeuticMax}
|
||||||
strokeDasharray="3 3"
|
strokeDasharray="3 3"
|
||||||
xAxisId="hours"
|
xAxisId="hours"
|
||||||
|
|||||||
@@ -38,18 +38,26 @@ export function useElementSize<T extends HTMLElement>(
|
|||||||
const element = ref.current;
|
const element = ref.current;
|
||||||
if (!element) return;
|
if (!element) return;
|
||||||
|
|
||||||
// Set initial size
|
// Set initial size (guard against 0 dimensions)
|
||||||
|
const initialWidth = element.clientWidth;
|
||||||
|
const initialHeight = element.clientHeight;
|
||||||
|
|
||||||
|
if (initialWidth > 0 && initialHeight > 0) {
|
||||||
setSize({
|
setSize({
|
||||||
width: element.clientWidth,
|
width: initialWidth,
|
||||||
height: element.clientHeight,
|
height: initialHeight,
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Use ResizeObserver for efficient element size tracking
|
// Use ResizeObserver for efficient element size tracking
|
||||||
const resizeObserver = new ResizeObserver((entries) => {
|
const resizeObserver = new ResizeObserver((entries) => {
|
||||||
for (const entry of entries) {
|
for (const entry of entries) {
|
||||||
const { width, height } = entry.contentRect;
|
const { width, height } = entry.contentRect;
|
||||||
|
// Guard against invalid dimensions
|
||||||
|
if (width > 0 && height > 0) {
|
||||||
setSize({ width, height });
|
setSize({ width, height });
|
||||||
}
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
resizeObserver.observe(element);
|
resizeObserver.observe(element);
|
||||||
|
|||||||
Reference in New Issue
Block a user