diff --git a/app/doctor/_components/analysis-doctor.tsx b/app/doctor/_components/analysis-doctor.tsx index b618107..4b7e4ee 100644 --- a/app/doctor/_components/analysis-doctor.tsx +++ b/app/doctor/_components/analysis-doctor.tsx @@ -28,12 +28,6 @@ export type AnalysisResultForDisplay = Pick< | 'response_time' >; -export enum AnalysisStatus { - NORMAL = 0, - MEDIUM = 1, - HIGH = 2, -} - const AnalysisDoctor = ({ analysisElement, results, @@ -48,38 +42,35 @@ const AnalysisDoctor = ({ endIcon?: ReactNode | null; }) => { const name = analysisElement.analysis_name_lab || ''; - const status = results?.norm_status || AnalysisStatus.NORMAL; + const status = results?.norm_status; const value = results?.response_value || 0; const unit = results?.unit || ''; - const normLowerIncluded = results?.norm_lower_included || false; - const normUpperIncluded = results?.norm_upper_included || false; - const normLower = results?.norm_lower || 0; - const normUpper = results?.norm_upper || 0; + const normLower = results?.norm_lower ?? null; + const normUpper = results?.norm_upper ?? null; const [showTooltip, setShowTooltip] = useState(false); const analysisResultLevel = useMemo(() => { - if (!results) { + if (!results || status === null || status === undefined) { return null; } - const isUnderNorm = value < normLower; - if (isUnderNorm) { - switch (status) { - case AnalysisStatus.MEDIUM: - return AnalysisResultLevel.LOW; - default: - return AnalysisResultLevel.VERY_LOW; - } - } switch (status) { - case AnalysisStatus.MEDIUM: - return AnalysisResultLevel.HIGH; - case AnalysisStatus.HIGH: - return AnalysisResultLevel.VERY_HIGH; + case 1: + return AnalysisResultLevel.WARNING; + case 2: + return AnalysisResultLevel.CRITICAL; + case 0: default: return AnalysisResultLevel.NORMAL; } - }, [results, value, normLower]); + }, [results, status]); + + const normRangeText = useMemo(() => { + if (normLower === null && normUpper === null) { + return null; + } + return `${normLower ?? '...'} - ${normUpper ?? '...'}`; + }, [normLower, normUpper]); return (
@@ -117,16 +108,15 @@ const AnalysisDoctor = ({
{unit}
- {normLower} - {normUpper} + {normRangeText}
{endIcon ||
} diff --git a/app/doctor/_components/analysis-level-bar.tsx b/app/doctor/_components/analysis-level-bar.tsx index ace79d1..c22e357 100644 --- a/app/doctor/_components/analysis-level-bar.tsx +++ b/app/doctor/_components/analysis-level-bar.tsx @@ -7,11 +7,9 @@ import { cn } from '@kit/ui/utils'; import { AnalysisResultForDisplay } from './analysis-doctor'; export enum AnalysisResultLevel { - VERY_LOW = 0, - LOW = 1, - NORMAL = 2, - HIGH = 3, - VERY_HIGH = 4, + NORMAL = 'NORMAL', + WARNING = 'WARNING', + CRITICAL = 'CRITICAL', } const Level = ({ @@ -20,17 +18,19 @@ const Level = ({ isFirst = false, isLast = false, arrowLocation, + normRangeText, }: { isActive?: boolean; color: 'destructive' | 'success' | 'warning' | 'gray-200'; isFirst?: boolean; isLast?: boolean; arrowLocation?: number; + normRangeText?: string | null; }) => { return (
92.5 && { left: '92.5%' }), + ...(arrowLocation < 7.5 && { left: '7.5%' }), + }, + } + : {})} >
)} + + {color === 'success' && typeof normRangeText === 'string' && ( +

+ {normRangeText} +

+ )}
); }; @@ -50,81 +71,144 @@ const Level = ({ export const AnalysisLevelBarSkeleton = () => { return (
- +
); }; const AnalysisLevelBar = ({ - normLowerIncluded = true, - normUpperIncluded = true, level, results, + normRangeText, }: { - normLowerIncluded?: boolean; - normUpperIncluded?: boolean; - level: AnalysisResultLevel; + level: AnalysisResultLevel | null; results: AnalysisResultForDisplay; + normRangeText: string | null; }) => { const { norm_lower: lower, norm_upper: upper, response_value: value, } = results; - const arrowLocation = useMemo(() => { - if (value < lower!) { - return 0; - } - if (normLowerIncluded || normUpperIncluded) { + // Calculate arrow position based on value within normal range + const arrowLocation = useMemo(() => { + // If no response value, center the arrow + if (value === null || value === undefined) { return 50; } - const calculated = ((value - lower!) / (upper! - lower!)) * 100; - - if (calculated > 100) { - return 100; + // If no normal ranges defined, center the arrow + if (lower === null && upper === null) { + return 50; } - return calculated; + // If only upper bound exists + if (lower === null && upper !== null) { + if (value <= upper) { + return Math.min(75, (value / upper) * 75); // Show in left 75% of normal range + } + return 100; // Beyond upper bound + } + + // If only lower bound exists + if (upper === null && lower !== null) { + if (value >= lower) { + // Value is in normal range (above lower bound) + // Position proportionally in the normal range section + const normalizedPosition = Math.min((value - lower) / (lower * 0.5), 1); // Use 50% of lower as scale + return normalizedPosition * 100; + } + // Value is below lower bound - position in the "below normal" section + const belowPosition = Math.max(0, Math.min(1, value / lower)); + return belowPosition * 100; + } + + // Both bounds exist + if (lower !== null && upper !== null) { + if (value < lower) { + return 0; // Below normal range + } + if (value > upper) { + return 100; // Above normal range + } + // Within normal range + return ((value - lower) / (upper - lower)) * 100; + } + + return 50; // Fallback }, [value, upper, lower]); - const [isVeryLow, isLow, isHigh, isVeryHigh] = useMemo( - () => [ - level === AnalysisResultLevel.VERY_LOW, - level === AnalysisResultLevel.LOW, - level === AnalysisResultLevel.HIGH, - level === AnalysisResultLevel.VERY_HIGH, - ], - [level, value, upper, lower], - ); + // Determine level states based on normStatus + const isNormal = level === AnalysisResultLevel.NORMAL; + const isWarning = level === AnalysisResultLevel.WARNING; + const isCritical = level === AnalysisResultLevel.CRITICAL; + const isPending = level === null; - const hasAbnormalLevel = isVeryLow || isLow || isHigh || isVeryHigh; + // If pending results, show gray bar + if (isPending) { + return ( +
+ +
+ ); + } + + // Show appropriate levels based on available norm bounds + const hasLowerBound = lower !== null; + + const [first, second, third] = useMemo(() => { + const [warning, normal, critical] = [ + { + isActive: isWarning, + color: 'warning', + ...(isWarning ? { arrowLocation } : {}), + }, + { + isActive: isNormal, + color: 'success', + normRangeText, + ...(isNormal ? { arrowLocation } : {}), + }, + { + isActive: isCritical, + color: 'destructive', + isLast: true, + ...(isCritical ? { arrowLocation } : {}), + }, + ] as const; + + if (!hasLowerBound) { + return [{ ...normal, isFirst: true }, warning, critical] as const; + } + + return [ + { ...warning, isFirst: true }, + normal, + { ...critical, isLast: true }, + ] as const; + }, [ + arrowLocation, + normRangeText, + isNormal, + isWarning, + isCritical, + hasLowerBound, + ]); return ( -
- {normLowerIncluded && ( - <> - - - - )} - - - - {normUpperIncluded && ( - <> - - - +
+ + +
); };