diff --git a/app/doctor/_components/analysis-doctor.tsx b/app/doctor/_components/analysis-doctor.tsx new file mode 100644 index 0000000..4178237 --- /dev/null +++ b/app/doctor/_components/analysis-doctor.tsx @@ -0,0 +1,149 @@ +'use client'; + +import React, { ReactElement, ReactNode, useMemo, useState } from 'react'; + +import { UserAnalysisElement } from '@/packages/features/accounts/src/types/accounts'; +import { format } from 'date-fns'; +import { Info } from 'lucide-react'; + +import { Trans } from '@kit/ui/trans'; +import { cn } from '@kit/ui/utils'; + +import { AnalysisElement } from '~/lib/services/analysis-element.service'; + +import AnalysisLevelBar, { + AnalysisLevelBarSkeleton, + AnalysisResultLevel, +} from './analysis-level-bar'; + +export type AnalysisResultForDisplay = Pick< + UserAnalysisElement, + | 'norm_status' + | 'response_value' + | 'unit' + | 'norm_lower_included' + | 'norm_upper_included' + | 'norm_lower' + | 'norm_upper' + | 'response_time' +>; + +export enum AnalysisStatus { + NORMAL = 0, + MEDIUM = 1, + HIGH = 2, +} + +const AnalysisDoctor = ({ + analysisElement, + results, + startIcon, + endIcon, + isCancelled, +}: { + analysisElement: Pick; + results?: AnalysisResultForDisplay; + isCancelled?: boolean; + startIcon?: ReactElement | null; + endIcon?: ReactNode | null; +}) => { + const name = analysisElement.analysis_name_lab || ''; + const status = results?.norm_status || AnalysisStatus.NORMAL; + 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 [showTooltip, setShowTooltip] = useState(false); + const analysisResultLevel = useMemo(() => { + if (!results) { + 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; + default: + return AnalysisResultLevel.NORMAL; + } + }, [results, value, normLower]); + + return ( +
+
+
+ {startIcon ||
} + {name} + {results?.response_time && ( +
{ + e.stopPropagation(); + setShowTooltip(!showTooltip); + }} + onMouseLeave={() => setShowTooltip(false)} + > + {' '} + +
+ )} +
+ {results ? ( + <> +
+
{value}
+
{unit}
+
+
+ {normLower} - {normUpper} +
+ +
+
+ + {endIcon ||
} + + ) : (isCancelled ? null : ( + <> +
+
+ +
+
+
+ + + ))} +
+
+ ); +}; + +export default AnalysisDoctor; diff --git a/app/doctor/_components/analysis-level-bar.tsx b/app/doctor/_components/analysis-level-bar.tsx new file mode 100644 index 0000000..7bac5f4 --- /dev/null +++ b/app/doctor/_components/analysis-level-bar.tsx @@ -0,0 +1,134 @@ +import { useMemo } from 'react'; + +import { ArrowDown } from 'lucide-react'; + +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, +} + +const Level = ({ + isActive = false, + color, + isFirst = false, + isLast = false, + arrowLocation, +}: { + isActive?: boolean; + color: 'destructive' | 'success' | 'warning' | 'gray-200'; + isFirst?: boolean; + isLast?: boolean; + arrowLocation?: number; +}) => { + return ( +
+ {isActive && ( +
+ +
+ )} +
+ ); +}; + +export const AnalysisLevelBarSkeleton = () => { + return ( +
+ +
+ ); +}; + +const AnalysisLevelBar = ({ + normLowerIncluded = true, + normUpperIncluded = true, + level, + results, +}: { + normLowerIncluded?: boolean; + normUpperIncluded?: boolean; + level: AnalysisResultLevel; + results: AnalysisResultForDisplay; +}) => { + + const { norm_lower: lower, norm_upper: upper, response_value: value } = results; + const arrowLocation = useMemo(() => { + if (value < lower!) { + return 0; + } + + if (normLowerIncluded || normUpperIncluded) { + return 50; + } + + const calculated = ((value - lower!) / (upper! - lower!)) * 100; + + if (calculated > 100) { + return 100; + } + + return calculated; + }, [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]); + + const hasAbnormalLevel = isVeryLow || isLow || isHigh || isVeryHigh; + + return ( +
+ {normLowerIncluded && ( + <> + + + + )} + + + + {normUpperIncluded && ( + <> + + + + )} +
+ ); +}; + +export default AnalysisLevelBar; diff --git a/app/doctor/_components/doctor-analysis-wrapper.tsx b/app/doctor/_components/doctor-analysis-wrapper.tsx index fec4c35..d2f0813 100644 --- a/app/doctor/_components/doctor-analysis-wrapper.tsx +++ b/app/doctor/_components/doctor-analysis-wrapper.tsx @@ -14,6 +14,7 @@ import { import { Trans } from '@kit/ui/trans'; import Analysis from '~/home/(user)/(dashboard)/analysis-results/_components/analysis'; +import AnalysisDoctor from './analysis-doctor'; export default function DoctorAnalysisWrapper({ analysisData, @@ -65,7 +66,7 @@ export default function DoctorAnalysisWrapper({ {analysisData.latestPreviousAnalysis && (
-