Add user analysis view bars + nested element logic to doctor view also
This commit is contained in:
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
import React, { ReactElement, ReactNode, useMemo, useState } from 'react';
|
import React, { ReactElement, ReactNode, useMemo, useState } from 'react';
|
||||||
|
|
||||||
import { UserAnalysisElement } from '@/packages/features/accounts/src/types/accounts';
|
|
||||||
import { format } from 'date-fns';
|
import { format } from 'date-fns';
|
||||||
import { Info } from 'lucide-react';
|
import { Info } from 'lucide-react';
|
||||||
|
|
||||||
@@ -10,23 +9,24 @@ import { Trans } from '@kit/ui/trans';
|
|||||||
import { cn } from '@kit/ui/utils';
|
import { cn } from '@kit/ui/utils';
|
||||||
|
|
||||||
import { AnalysisElement } from '~/lib/services/analysis-element.service';
|
import { AnalysisElement } from '~/lib/services/analysis-element.service';
|
||||||
|
import { NestedAnalysisElement } from '@/packages/features/doctor/src/lib/server/schema/doctor-analysis-detail-view.schema';
|
||||||
|
|
||||||
import AnalysisLevelBar, {
|
import AnalysisLevelBar, {
|
||||||
AnalysisLevelBarSkeleton,
|
AnalysisLevelBarSkeleton,
|
||||||
AnalysisResultLevel,
|
AnalysisResultLevel,
|
||||||
} from './analysis-level-bar';
|
} from './analysis-level-bar';
|
||||||
|
|
||||||
export type AnalysisResultForDisplay = Pick<
|
export type AnalysisResultForDisplay = {
|
||||||
UserAnalysisElement,
|
norm_status?: number | null;
|
||||||
| 'norm_status'
|
response_value?: number | null;
|
||||||
| 'response_value'
|
unit?: string | null;
|
||||||
| 'unit'
|
norm_lower_included?: boolean | null;
|
||||||
| 'norm_lower_included'
|
norm_upper_included?: boolean | null;
|
||||||
| 'norm_upper_included'
|
norm_lower?: number | null;
|
||||||
| 'norm_lower'
|
norm_upper?: number | null;
|
||||||
| 'norm_upper'
|
response_time?: string | null;
|
||||||
| 'response_time'
|
nestedElements?: NestedAnalysisElement[];
|
||||||
>;
|
};
|
||||||
|
|
||||||
const AnalysisDoctor = ({
|
const AnalysisDoctor = ({
|
||||||
analysisElement,
|
analysisElement,
|
||||||
@@ -72,6 +72,12 @@ const AnalysisDoctor = ({
|
|||||||
return `${normLower ?? '...'} - ${normUpper ?? '...'}`;
|
return `${normLower ?? '...'} - ${normUpper ?? '...'}`;
|
||||||
}, [normLower, normUpper]);
|
}, [normLower, normUpper]);
|
||||||
|
|
||||||
|
const nestedElements = results?.nestedElements ?? null;
|
||||||
|
const hasNestedElements =
|
||||||
|
Array.isArray(nestedElements) && nestedElements.length > 0;
|
||||||
|
|
||||||
|
const isAnalysisLevelBarHidden = isCancelled || !results || hasNestedElements;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="border-border rounded-lg border px-5">
|
<div className="border-border rounded-lg border px-5">
|
||||||
<div className="flex flex-col items-center justify-between gap-2 py-3 sm:h-[65px] sm:flex-row sm:gap-0">
|
<div className="flex flex-col items-center justify-between gap-2 py-3 sm:h-[65px] sm:flex-row sm:gap-0">
|
||||||
@@ -101,7 +107,7 @@ const AnalysisDoctor = ({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{results ? (
|
{isAnalysisLevelBarHidden ? null : (
|
||||||
<>
|
<>
|
||||||
<div className="flex items-center gap-3 sm:ml-auto">
|
<div className="flex items-center gap-3 sm:ml-auto">
|
||||||
<div className="font-semibold">{value}</div>
|
<div className="font-semibold">{value}</div>
|
||||||
@@ -120,7 +126,22 @@ const AnalysisDoctor = ({
|
|||||||
/>
|
/>
|
||||||
{endIcon || <div className="mx-2 w-4" />}
|
{endIcon || <div className="mx-2 w-4" />}
|
||||||
</>
|
</>
|
||||||
) : isCancelled ? null : (
|
)}
|
||||||
|
{(() => {
|
||||||
|
// If parent has nested elements, don't show anything
|
||||||
|
if (hasNestedElements) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// If we're showing the level bar, don't show waiting
|
||||||
|
if (!isAnalysisLevelBarHidden) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// If cancelled, don't show waiting
|
||||||
|
if (isCancelled) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// Otherwise, show waiting for results
|
||||||
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="flex items-center gap-3 sm:ml-auto">
|
<div className="flex items-center gap-3 sm:ml-auto">
|
||||||
<div className="font-semibold">
|
<div className="font-semibold">
|
||||||
@@ -130,7 +151,8 @@ const AnalysisDoctor = ({
|
|||||||
<div className="mx-8 w-[60px]"></div>
|
<div className="mx-8 w-[60px]"></div>
|
||||||
<AnalysisLevelBarSkeleton />
|
<AnalysisLevelBarSkeleton />
|
||||||
</>
|
</>
|
||||||
)}
|
);
|
||||||
|
})()}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -105,35 +105,38 @@ const AnalysisLevelBar = ({
|
|||||||
|
|
||||||
// If only upper bound exists
|
// If only upper bound exists
|
||||||
if (lower === null && upper !== null) {
|
if (lower === null && upper !== null) {
|
||||||
if (value <= upper) {
|
if (value <= upper!) {
|
||||||
return Math.min(75, (value / upper) * 75); // Show in left 75% of normal range
|
return Math.min(75, (value / upper!) * 75); // Show in left 75% of normal range
|
||||||
}
|
}
|
||||||
return 100; // Beyond upper bound
|
return 100; // Beyond upper bound
|
||||||
}
|
}
|
||||||
|
|
||||||
// If only lower bound exists
|
// If only lower bound exists
|
||||||
if (upper === null && lower !== null) {
|
if (upper === null && lower !== null) {
|
||||||
if (value >= lower) {
|
if (value >= lower!) {
|
||||||
// Value is in normal range (above lower bound)
|
// Value is in normal range (above lower bound)
|
||||||
// Position proportionally in the normal range section
|
// Position proportionally in the normal range section
|
||||||
const normalizedPosition = Math.min((value - lower) / (lower * 0.5), 1); // Use 50% of lower as scale
|
const normalizedPosition = Math.min(
|
||||||
|
(value - lower!) / (lower! * 0.5),
|
||||||
|
1,
|
||||||
|
); // Use 50% of lower as scale
|
||||||
return normalizedPosition * 100;
|
return normalizedPosition * 100;
|
||||||
}
|
}
|
||||||
// Value is below lower bound - position in the "below normal" section
|
// Value is below lower bound - position in the "below normal" section
|
||||||
const belowPosition = Math.max(0, Math.min(1, value / lower));
|
const belowPosition = Math.max(0, Math.min(1, value / lower!));
|
||||||
return belowPosition * 100;
|
return belowPosition * 100;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Both bounds exist
|
// Both bounds exist
|
||||||
if (lower !== null && upper !== null) {
|
if (lower !== null && upper !== null) {
|
||||||
if (value < lower) {
|
if (value < lower!) {
|
||||||
return 0; // Below normal range
|
return 0; // Below normal range
|
||||||
}
|
}
|
||||||
if (value > upper) {
|
if (value > upper!) {
|
||||||
return 100; // Above normal range
|
return 100; // Above normal range
|
||||||
}
|
}
|
||||||
// Within normal range
|
// Within normal range
|
||||||
return ((value - lower) / (upper - lower)) * 100;
|
return ((value - lower!) / (upper! - lower!)) * 100;
|
||||||
}
|
}
|
||||||
|
|
||||||
return 50; // Fallback
|
return 50; // Fallback
|
||||||
@@ -145,18 +148,10 @@ const AnalysisLevelBar = ({
|
|||||||
const isCritical = level === AnalysisResultLevel.CRITICAL;
|
const isCritical = level === AnalysisResultLevel.CRITICAL;
|
||||||
const isPending = level === null;
|
const isPending = level === null;
|
||||||
|
|
||||||
// If pending results, show gray bar
|
|
||||||
if (isPending) {
|
|
||||||
return (
|
|
||||||
<div className="w-60% mt-4 flex h-3 max-w-[360px] gap-1 sm:mt-0 sm:w-[35%]">
|
|
||||||
<Level color="gray-200" isFirst isLast />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Show appropriate levels based on available norm bounds
|
// Show appropriate levels based on available norm bounds
|
||||||
const hasLowerBound = lower !== null;
|
const hasLowerBound = lower !== null;
|
||||||
|
|
||||||
|
// Calculate level configuration (must be called before any returns)
|
||||||
const [first, second, third] = useMemo(() => {
|
const [first, second, third] = useMemo(() => {
|
||||||
const [warning, normal, critical] = [
|
const [warning, normal, critical] = [
|
||||||
{
|
{
|
||||||
@@ -196,6 +191,15 @@ const AnalysisLevelBar = ({
|
|||||||
hasLowerBound,
|
hasLowerBound,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
// If pending results, show gray bar
|
||||||
|
if (isPending) {
|
||||||
|
return (
|
||||||
|
<div className="w-60% mt-4 flex h-3 max-w-[360px] gap-1 sm:mt-0 sm:w-[35%]">
|
||||||
|
<Level color="gray-200" isFirst isLast />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
import { CaretDownIcon, QuestionMarkCircledIcon } from '@radix-ui/react-icons';
|
import { CaretDownIcon, QuestionMarkCircledIcon } from '@radix-ui/react-icons';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
@@ -23,6 +25,7 @@ export default function DoctorAnalysisWrapper({
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<>
|
||||||
<Collapsible className="w-full" key={analysisData.id}>
|
<Collapsible className="w-full" key={analysisData.id}>
|
||||||
<CollapsibleTrigger
|
<CollapsibleTrigger
|
||||||
disabled={!analysisData.latestPreviousAnalysis}
|
disabled={!analysisData.latestPreviousAnalysis}
|
||||||
@@ -64,7 +67,7 @@ export default function DoctorAnalysisWrapper({
|
|||||||
</CollapsibleTrigger>
|
</CollapsibleTrigger>
|
||||||
{analysisData.latestPreviousAnalysis && (
|
{analysisData.latestPreviousAnalysis && (
|
||||||
<CollapsibleContent>
|
<CollapsibleContent>
|
||||||
<div className="my-1 flex flex-col">
|
<div className="my-1 flex flex-col gap-2">
|
||||||
<AnalysisDoctor
|
<AnalysisDoctor
|
||||||
endIcon={
|
endIcon={
|
||||||
analysisData.latestPreviousAnalysis.comment && (
|
analysisData.latestPreviousAnalysis.comment && (
|
||||||
@@ -89,15 +92,62 @@ export default function DoctorAnalysisWrapper({
|
|||||||
analysisElement={{
|
analysisElement={{
|
||||||
analysis_name_lab: t('doctor:previousResults', {
|
analysis_name_lab: t('doctor:previousResults', {
|
||||||
date: formatDate(
|
date: formatDate(
|
||||||
analysisData.latestPreviousAnalysis.response_time,
|
analysisData.latestPreviousAnalysis.response_time!,
|
||||||
),
|
),
|
||||||
}),
|
}),
|
||||||
}}
|
}}
|
||||||
results={analysisData.latestPreviousAnalysis}
|
results={analysisData.latestPreviousAnalysis}
|
||||||
/>
|
/>
|
||||||
|
{analysisData.latestPreviousAnalysis.nestedElements?.map(
|
||||||
|
(nestedElement, nestedIndex) => (
|
||||||
|
<div
|
||||||
|
key={`prev-nested-${nestedElement.analysisElementOriginalId}-${nestedIndex}`}
|
||||||
|
className="ml-8"
|
||||||
|
>
|
||||||
|
<AnalysisDoctor
|
||||||
|
analysisElement={{
|
||||||
|
analysis_name_lab: nestedElement.analysisNameLab ?? '',
|
||||||
|
}}
|
||||||
|
results={{
|
||||||
|
norm_status: nestedElement.normStatus,
|
||||||
|
response_value: nestedElement.responseValue,
|
||||||
|
unit: nestedElement.unit,
|
||||||
|
norm_lower: nestedElement.normLower,
|
||||||
|
norm_upper: nestedElement.normUpper,
|
||||||
|
norm_lower_included: nestedElement.normLowerIncluded,
|
||||||
|
norm_upper_included: nestedElement.normUpperIncluded,
|
||||||
|
response_time: nestedElement.responseTime,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</CollapsibleContent>
|
</CollapsibleContent>
|
||||||
)}
|
)}
|
||||||
</Collapsible>
|
</Collapsible>
|
||||||
|
{analysisData.nestedElements?.map((nestedElement, nestedIndex) => (
|
||||||
|
<div
|
||||||
|
key={`nested-${nestedElement.analysisElementOriginalId}-${nestedIndex}`}
|
||||||
|
className="ml-8"
|
||||||
|
>
|
||||||
|
<AnalysisDoctor
|
||||||
|
analysisElement={{
|
||||||
|
analysis_name_lab: nestedElement.analysisNameLab ?? '',
|
||||||
|
}}
|
||||||
|
results={{
|
||||||
|
norm_status: nestedElement.normStatus,
|
||||||
|
response_value: nestedElement.responseValue,
|
||||||
|
unit: nestedElement.unit,
|
||||||
|
norm_lower: nestedElement.normLower,
|
||||||
|
norm_upper: nestedElement.normUpper,
|
||||||
|
norm_lower_included: nestedElement.normLowerIncluded,
|
||||||
|
norm_upper_included: nestedElement.normUpperIncluded,
|
||||||
|
response_time: nestedElement.responseTime,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,6 +52,23 @@ export const AnalysisResponsesSchema = z.object({
|
|||||||
});
|
});
|
||||||
export type AnalysisResponses = z.infer<typeof AnalysisResponsesSchema>;
|
export type AnalysisResponses = z.infer<typeof AnalysisResponsesSchema>;
|
||||||
|
|
||||||
|
// Nested element schema (used recursively)
|
||||||
|
export const NestedAnalysisElementSchema = z.object({
|
||||||
|
analysisElementOriginalId: z.string(),
|
||||||
|
analysisName: z.string().optional().nullable(),
|
||||||
|
unit: z.string().nullable(),
|
||||||
|
normLower: z.number().nullable(),
|
||||||
|
normUpper: z.number().nullable(),
|
||||||
|
normStatus: z.number().nullable(),
|
||||||
|
responseTime: z.string().nullable(),
|
||||||
|
responseValue: z.number().nullable(),
|
||||||
|
normLowerIncluded: z.boolean(),
|
||||||
|
normUpperIncluded: z.boolean(),
|
||||||
|
status: z.number(),
|
||||||
|
analysisNameLab: z.string().optional().nullable(),
|
||||||
|
});
|
||||||
|
export type NestedAnalysisElement = z.infer<typeof NestedAnalysisElementSchema>;
|
||||||
|
|
||||||
export const AnalysisResponseSchema = z.object({
|
export const AnalysisResponseSchema = z.object({
|
||||||
id: z.number(),
|
id: z.number(),
|
||||||
analysis_response_id: z.number(),
|
analysis_response_id: z.number(),
|
||||||
@@ -69,6 +86,7 @@ export const AnalysisResponseSchema = z.object({
|
|||||||
analysis_name: z.string().nullable(),
|
analysis_name: z.string().nullable(),
|
||||||
analysis_responses: AnalysisResponsesSchema,
|
analysis_responses: AnalysisResponsesSchema,
|
||||||
comment: z.string().nullable(),
|
comment: z.string().nullable(),
|
||||||
|
nestedElements: z.array(NestedAnalysisElementSchema).optional(),
|
||||||
latestPreviousAnalysis: z
|
latestPreviousAnalysis: z
|
||||||
.object({
|
.object({
|
||||||
id: z.number(),
|
id: z.number(),
|
||||||
@@ -86,6 +104,7 @@ export const AnalysisResponseSchema = z.object({
|
|||||||
updated_at: z.string().nullable(),
|
updated_at: z.string().nullable(),
|
||||||
analysis_name: z.string().nullable(),
|
analysis_name: z.string().nullable(),
|
||||||
comment: z.string().nullable(),
|
comment: z.string().nullable(),
|
||||||
|
nestedElements: z.array(NestedAnalysisElementSchema).optional(),
|
||||||
})
|
})
|
||||||
.optional()
|
.optional()
|
||||||
.nullable(),
|
.nullable(),
|
||||||
|
|||||||
@@ -5,12 +5,16 @@ import { isBefore } from 'date-fns';
|
|||||||
|
|
||||||
import { renderDoctorSummaryReceivedEmail } from '@kit/email-templates';
|
import { renderDoctorSummaryReceivedEmail } from '@kit/email-templates';
|
||||||
import { getLogger } from '@kit/shared/logger';
|
import { getLogger } from '@kit/shared/logger';
|
||||||
import { getFullName } from '@kit/shared/utils';
|
import type { UuringuVastus, ResponseUuring } from '@kit/shared/types/medipost-analysis';
|
||||||
|
import { getFullName, toArray } from '@kit/shared/utils';
|
||||||
import { getSupabaseServerClient } from '@kit/supabase/server-client';
|
import { getSupabaseServerClient } from '@kit/supabase/server-client';
|
||||||
import { createUserAnalysesApi } from '@kit/user-analyses/api';
|
import { createUserAnalysesApi } from '@kit/user-analyses/api';
|
||||||
|
|
||||||
import { sendEmailFromTemplate } from '../../../../../../../lib/services/mailer.service';
|
import { sendEmailFromTemplate } from '../../../../../../../lib/services/mailer.service';
|
||||||
import { AnalysisResultDetails } from '../schema/doctor-analysis-detail-view.schema';
|
import {
|
||||||
|
AnalysisResultDetails,
|
||||||
|
NestedAnalysisElement,
|
||||||
|
} from '../schema/doctor-analysis-detail-view.schema';
|
||||||
import {
|
import {
|
||||||
AnalysisResponseBase,
|
AnalysisResponseBase,
|
||||||
DoctorAnalysisFeedbackTable,
|
DoctorAnalysisFeedbackTable,
|
||||||
@@ -20,6 +24,63 @@ import {
|
|||||||
} from '../schema/doctor-analysis.schema';
|
} from '../schema/doctor-analysis.schema';
|
||||||
import { ErrorReason } from '../schema/error.type';
|
import { ErrorReason } from '../schema/error.type';
|
||||||
|
|
||||||
|
function mapUuringVastus({ uuringVastus }: { uuringVastus?: UuringuVastus }) {
|
||||||
|
const vastuseVaartus = uuringVastus?.VastuseVaartus;
|
||||||
|
const responseValue = (() => {
|
||||||
|
const valueAsNumber = Number(vastuseVaartus);
|
||||||
|
if (isNaN(valueAsNumber)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return valueAsNumber;
|
||||||
|
})();
|
||||||
|
const responseValueNumber = Number(responseValue);
|
||||||
|
const responseValueIsNumeric = !isNaN(responseValueNumber);
|
||||||
|
return {
|
||||||
|
normLower: uuringVastus?.NormAlum?.['#text'] ?? null,
|
||||||
|
normUpper: uuringVastus?.NormYlem?.['#text'] ?? null,
|
||||||
|
normStatus: (uuringVastus?.NormiStaatus ?? null) as number | null,
|
||||||
|
responseTime: uuringVastus?.VastuseAeg ?? null,
|
||||||
|
responseValue:
|
||||||
|
responseValueIsNumeric ? (responseValueNumber ?? null) : null,
|
||||||
|
normLowerIncluded:
|
||||||
|
uuringVastus?.NormAlum?.['@_kaasaarvatud']?.toLowerCase() === 'jah',
|
||||||
|
normUpperIncluded:
|
||||||
|
uuringVastus?.NormYlem?.['@_kaasaarvatud']?.toLowerCase() === 'jah',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseNestedElements(
|
||||||
|
originalResponseElement: ResponseUuring | null | undefined,
|
||||||
|
status: number,
|
||||||
|
): NestedAnalysisElement[] {
|
||||||
|
if (!originalResponseElement?.UuringuElement) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const nestedElements = toArray(originalResponseElement.UuringuElement);
|
||||||
|
|
||||||
|
return nestedElements.map<NestedAnalysisElement>((element) => {
|
||||||
|
const mappedResponse = mapUuringVastus({
|
||||||
|
uuringVastus: element.UuringuVastus as UuringuVastus | undefined,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
analysisElementOriginalId: element.UuringId,
|
||||||
|
analysisName: undefined, // Will be populated later from analysis_elements table
|
||||||
|
unit: element.Mootyhik ?? null,
|
||||||
|
normLower: mappedResponse.normLower,
|
||||||
|
normUpper: mappedResponse.normUpper,
|
||||||
|
normStatus: mappedResponse.normStatus,
|
||||||
|
responseTime: mappedResponse.responseTime,
|
||||||
|
responseValue: mappedResponse.responseValue,
|
||||||
|
normLowerIncluded: mappedResponse.normLowerIncluded,
|
||||||
|
normUpperIncluded: mappedResponse.normUpperIncluded,
|
||||||
|
status,
|
||||||
|
analysisNameLab: element.UuringNimi,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async function enrichAnalysisData(analysisResponses?: AnalysisResponseBase[]) {
|
async function enrichAnalysisData(analysisResponses?: AnalysisResponseBase[]) {
|
||||||
const supabase = getSupabaseServerClient();
|
const supabase = getSupabaseServerClient();
|
||||||
|
|
||||||
@@ -388,7 +449,8 @@ export async function getAnalysisResultsForDoctor(
|
|||||||
.from(`analysis_response_elements`)
|
.from(`analysis_response_elements`)
|
||||||
.select(
|
.select(
|
||||||
`*,
|
`*,
|
||||||
analysis_responses(user_id, analysis_order:analysis_order_id(id,medusa_order_id, analysis_element_ids))`,
|
analysis_responses(user_id, analysis_order:analysis_order_id(id,medusa_order_id, analysis_element_ids)),
|
||||||
|
original_response_element`,
|
||||||
)
|
)
|
||||||
.eq('analysis_response_id', analysisResponseId);
|
.eq('analysis_response_id', analysisResponseId);
|
||||||
|
|
||||||
@@ -452,7 +514,8 @@ export async function getAnalysisResultsForDoctor(
|
|||||||
*,
|
*,
|
||||||
analysis_responses!inner(
|
analysis_responses!inner(
|
||||||
user_id
|
user_id
|
||||||
)
|
),
|
||||||
|
original_response_element
|
||||||
`,
|
`,
|
||||||
)
|
)
|
||||||
.in(
|
.in(
|
||||||
@@ -491,8 +554,14 @@ export async function getAnalysisResultsForDoctor(
|
|||||||
preferred_locale,
|
preferred_locale,
|
||||||
} = accountWithParams[0];
|
} = accountWithParams[0];
|
||||||
|
|
||||||
|
// Parse nested elements for current and previous analyses
|
||||||
const analysisResponseElementsWithPreviousData = [];
|
const analysisResponseElementsWithPreviousData = [];
|
||||||
for (const analysisResponseElement of analysisResponsesData) {
|
for (const analysisResponseElement of analysisResponsesData) {
|
||||||
|
const nestedElements = parseNestedElements(
|
||||||
|
analysisResponseElement.original_response_element as ResponseUuring,
|
||||||
|
Number(analysisResponseElement.status),
|
||||||
|
);
|
||||||
|
|
||||||
const latestPreviousAnalysis = previousAnalyses.find(
|
const latestPreviousAnalysis = previousAnalyses.find(
|
||||||
({ analysis_element_original_id, response_time }) => {
|
({ analysis_element_original_id, response_time }) => {
|
||||||
if (response_time && analysisResponseElement.response_time) {
|
if (response_time && analysisResponseElement.response_time) {
|
||||||
@@ -507,12 +576,95 @@ export async function getAnalysisResultsForDoctor(
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Parse nested elements for previous analysis if it exists
|
||||||
|
const latestPreviousAnalysisWithNested = latestPreviousAnalysis
|
||||||
|
? {
|
||||||
|
...latestPreviousAnalysis,
|
||||||
|
nestedElements: parseNestedElements(
|
||||||
|
latestPreviousAnalysis.original_response_element as ResponseUuring,
|
||||||
|
Number(latestPreviousAnalysis.status),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
: undefined;
|
||||||
|
|
||||||
analysisResponseElementsWithPreviousData.push({
|
analysisResponseElementsWithPreviousData.push({
|
||||||
...analysisResponseElement,
|
...analysisResponseElement,
|
||||||
latestPreviousAnalysis,
|
nestedElements,
|
||||||
|
latestPreviousAnalysis: latestPreviousAnalysisWithNested,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Collect all nested element IDs to fetch their names
|
||||||
|
const nestedElementIds = analysisResponseElementsWithPreviousData
|
||||||
|
.flatMap((element) => [
|
||||||
|
...(element.nestedElements?.map((ne) => ne.analysisElementOriginalId) ??
|
||||||
|
[]),
|
||||||
|
...(element.latestPreviousAnalysis?.nestedElements?.map(
|
||||||
|
(ne) => ne.analysisElementOriginalId,
|
||||||
|
) ?? []),
|
||||||
|
])
|
||||||
|
.filter(Boolean);
|
||||||
|
|
||||||
|
// Fetch analysis names for nested elements
|
||||||
|
if (nestedElementIds.length > 0) {
|
||||||
|
const { data: nestedAnalysisElements, error: nestedAnalysisElementsError } =
|
||||||
|
await supabase
|
||||||
|
.schema('medreport')
|
||||||
|
.from('analysis_elements')
|
||||||
|
.select('*')
|
||||||
|
.in('analysis_id_original', nestedElementIds);
|
||||||
|
|
||||||
|
if (!nestedAnalysisElementsError && nestedAnalysisElements) {
|
||||||
|
// Populate analysis names for current nested elements
|
||||||
|
for (const element of analysisResponseElementsWithPreviousData) {
|
||||||
|
if (element.nestedElements) {
|
||||||
|
for (const nestedElement of element.nestedElements) {
|
||||||
|
const analysisElement = nestedAnalysisElements.find(
|
||||||
|
(ae) =>
|
||||||
|
ae.analysis_id_original ===
|
||||||
|
nestedElement.analysisElementOriginalId,
|
||||||
|
);
|
||||||
|
if (analysisElement) {
|
||||||
|
nestedElement.analysisName =
|
||||||
|
analysisElement.analysis_name_lab as string | undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Sort nested elements by name
|
||||||
|
element.nestedElements.sort(
|
||||||
|
(a, b) => a.analysisName?.localeCompare(b.analysisName ?? '') ?? 0,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Populate analysis names for previous nested elements
|
||||||
|
if (element.latestPreviousAnalysis?.nestedElements) {
|
||||||
|
for (const nestedElement of element.latestPreviousAnalysis
|
||||||
|
.nestedElements) {
|
||||||
|
const analysisElement = nestedAnalysisElements.find(
|
||||||
|
(ae) =>
|
||||||
|
ae.analysis_id_original ===
|
||||||
|
nestedElement.analysisElementOriginalId,
|
||||||
|
);
|
||||||
|
if (analysisElement) {
|
||||||
|
nestedElement.analysisName =
|
||||||
|
analysisElement.analysis_name_lab as string | undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Sort nested elements by name
|
||||||
|
element.latestPreviousAnalysis.nestedElements.sort(
|
||||||
|
(a, b) => a.analysisName?.localeCompare(b.analysisName ?? '') ?? 0,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.error(
|
||||||
|
'Failed to get nested analysis elements by ids=',
|
||||||
|
nestedElementIds,
|
||||||
|
nestedAnalysisElementsError,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
analysisResponse: analysisResponseElementsWithPreviousData,
|
analysisResponse: analysisResponseElementsWithPreviousData,
|
||||||
order: {
|
order: {
|
||||||
|
|||||||
Reference in New Issue
Block a user