diff --git a/app/home/(user)/(dashboard)/analysis-results/[id]/page.tsx b/app/home/(user)/(dashboard)/analysis-results/[id]/page.tsx index 386d584..30da3bb 100644 --- a/app/home/(user)/(dashboard)/analysis-results/[id]/page.tsx +++ b/app/home/(user)/(dashboard)/analysis-results/[id]/page.tsx @@ -1,3 +1,4 @@ +import React from 'react'; import Link from 'next/link'; import { redirect } from 'next/navigation'; @@ -64,8 +65,7 @@ export default async function AnalysisResultsPage({

- {analysisResponse?.elements && - analysisResponse.elements?.length > 0 ? ( + {orderedAnalysisElements.length > 0 ? ( ) : ( @@ -106,7 +106,16 @@ export default async function AnalysisResultsPage({

{orderedAnalysisElements ? ( orderedAnalysisElements.map((element, index) => ( - + + + {element.results?.nestedElements?.map((nestedElement, nestedIndex) => ( + + ))} + )) ) : (
diff --git a/app/home/(user)/(dashboard)/analysis-results/_components/analysis-level-bar.tsx b/app/home/(user)/(dashboard)/analysis-results/_components/analysis-level-bar.tsx index 0384a7d..f53a04b 100644 --- a/app/home/(user)/(dashboard)/analysis-results/_components/analysis-level-bar.tsx +++ b/app/home/(user)/(dashboard)/analysis-results/_components/analysis-level-bar.tsx @@ -3,7 +3,7 @@ import { useMemo } from 'react'; import { ArrowDown } from 'lucide-react'; import { cn } from '@kit/ui/utils'; -import { AnalysisResultDetailsElementResults } from '@/packages/features/accounts/src/types/analysis-results'; +import type { AnalysisResultDetailsElementResults } from '@/packages/features/user-analyses/src/types/analysis-results'; export enum AnalysisResultLevel { NORMAL = 0, @@ -11,6 +11,8 @@ export enum AnalysisResultLevel { CRITICAL = 2, } +type AnalysisResultLevelBarResults = Pick; + const Level = ({ isActive = false, color, @@ -60,28 +62,19 @@ const Level = ({ ); }; -export const AnalysisLevelBarSkeleton = () => { - return ( -
- -
- ); -}; - const AnalysisLevelBar = ({ level, - results, + results: { + normLower: lower, + normUpper: upper, + responseValue: value, + }, normRangeText, }: { level: AnalysisResultLevel; - results: AnalysisResultDetailsElementResults; + results: AnalysisResultLevelBarResults; normRangeText: string | null; }) => { - - const { normLower: lower, normUpper: upper, responseValue: value, normStatus } = results; - const normLowerIncluded = results?.normLowerIncluded || false; - const normUpperIncluded = results?.normUpperIncluded || false; - // Calculate arrow position based on value within normal range const arrowLocation = useMemo(() => { // If no response value, center the arrow @@ -147,7 +140,6 @@ const AnalysisLevelBar = ({ // Show appropriate levels based on available norm bounds const hasLowerBound = lower !== null; - const isLowerBoundZero = hasLowerBound && lower === 0; const hasUpperBound = upper !== null; // Determine which section the value falls into diff --git a/app/home/(user)/(dashboard)/analysis-results/_components/analysis.tsx b/app/home/(user)/(dashboard)/analysis-results/_components/analysis.tsx index 757b120..fd25201 100644 --- a/app/home/(user)/(dashboard)/analysis-results/_components/analysis.tsx +++ b/app/home/(user)/(dashboard)/analysis-results/_components/analysis.tsx @@ -3,7 +3,11 @@ import React, { useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; -import { AnalysisResultDetailsElement } from '@/packages/features/accounts/src/types/analysis-results'; +import type { + AnalysisResultDetailsElementResults, + AnalysisResultDetailsElement, + AnalysisResultsDetailsElementNested, +} from '@/packages/features/user-analyses/src/types/analysis-results'; import { format } from 'date-fns'; import { Info } from 'lucide-react'; @@ -21,14 +25,45 @@ export enum AnalysisStatus { } const Analysis = ({ - element, + element: elementOriginal, + nestedElement, + isNestedElement = false, }: { - element: AnalysisResultDetailsElement; + element?: AnalysisResultDetailsElement; + nestedElement?: AnalysisResultsDetailsElementNested; + isNestedElement?: boolean; }) => { const { t } = useTranslation(); + const element = (() => { + if (isNestedElement) { + return nestedElement!; + } + return elementOriginal!; + })(); + const results: AnalysisResultDetailsElementResults = useMemo(() => { + if (isNestedElement) { + const nestedElement = element as AnalysisResultsDetailsElementNested; + return { + analysisElementOriginalId: nestedElement.analysisElementOriginalId, + normLower: nestedElement.normLower, + normUpper: nestedElement.normUpper, + normStatus: nestedElement.normStatus, + responseTime: nestedElement.responseTime, + responseValue: nestedElement.responseValue, + responseValueIsNegative: nestedElement.responseValueIsNegative, + responseValueIsWithinNorm: nestedElement.responseValueIsWithinNorm, + normLowerIncluded: nestedElement.normLowerIncluded, + normUpperIncluded: nestedElement.normUpperIncluded, + unit: nestedElement.unit, + status: nestedElement.status, + nestedElements: [], + }; + } + return (element as AnalysisResultDetailsElement).results; + }, [element, isNestedElement]); + const name = element.analysisName || ''; - const results = element.results; const hasIsWithinNorm = results?.responseValueIsWithinNorm !== null; const hasIsNegative = results?.responseValueIsNegative !== null; @@ -58,8 +93,8 @@ const Analysis = ({ return responseValue; })(); const unit = results?.unit || ''; - const normLower = results?.normLower; - const normUpper = results?.normUpper; + const normLower = results?.normLower ?? null; + const normUpper = results?.normUpper ?? null; const normStatus = results?.normStatus ?? null; const [showTooltip, setShowTooltip] = useState(false); @@ -82,13 +117,18 @@ const Analysis = ({ const isCancelled = Number(results?.status) === 5; const hasNestedElements = results?.nestedElements.length > 0; - const normRangeText = normLower !== null ? `${normLower} - ${normUpper || ''}` : null; + const normRangeText = (() => { + if (normLower === null && normUpper === null) { + return null; + } + return `${normLower ?? '...'} - ${normUpper ?? '...'}`; + })(); const hasTextualResponse = hasIsNegative || hasIsWithinNorm; return ( -
+
-
+
{name} {results?.responseTime && (
; diff --git a/app/home/(user)/_lib/server/load-user-analysis.ts b/app/home/(user)/_lib/server/load-user-analysis.ts index 77c40bd..5e70c3f 100644 --- a/app/home/(user)/_lib/server/load-user-analysis.ts +++ b/app/home/(user)/_lib/server/load-user-analysis.ts @@ -1,6 +1,6 @@ import { cache } from 'react'; -import { AnalysisResultDetailsMapped } from '@kit/accounts/types/analysis-results'; +import type { AnalysisResultDetailsMapped } from '@/packages/features/user-analyses/src/types/analysis-results'; import { getSupabaseServerClient } from '@kit/supabase/server-client'; import { createUserAnalysesApi } from '@/packages/features/user-analyses/src/server/api'; diff --git a/packages/features/accounts/src/types/analysis-orders.ts b/packages/features/accounts/src/types/analysis-orders.ts deleted file mode 100644 index 4ef4027..0000000 --- a/packages/features/accounts/src/types/analysis-orders.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { Tables } from '@kit/supabase/database'; - -export type AnalysisOrder = Tables<{ schema: 'medreport' }, 'analysis_orders'>; diff --git a/packages/features/accounts/src/types/analysis-results.ts b/packages/features/accounts/src/types/analysis-results.ts deleted file mode 100644 index e2b5e66..0000000 --- a/packages/features/accounts/src/types/analysis-results.ts +++ /dev/null @@ -1,139 +0,0 @@ -import * as z from 'zod'; - -import { Database } from '@kit/supabase/database'; - -export type UserAnalysisElement = - Database['medreport']['Tables']['analysis_response_elements']['Row']; -export type UserAnalysisResponse = - Database['medreport']['Tables']['analysis_responses']['Row'] & { - elements: UserAnalysisElement[]; - }; -export type UserAnalysis = UserAnalysisResponse[]; - -const ElementSchema = z.object({ - unit: z.string(), - norm_lower: z.number(), - norm_upper: z.number(), - norm_status: z.number(), - analysis_name: z.string(), - response_time: z.string(), - response_value: z.number(), - response_value_is_negative: z.boolean(), - norm_lower_included: z.boolean(), - norm_upper_included: z.boolean(), - status: z.string(), - analysis_element_original_id: z.string(), - original_response_element: z.object({ - - }), -}); - -const OrderSchema = z.object({ - status: z.string(), - medusa_order_id: z.string(), - created_at: z.coerce.date(), -}); - -const DoctorAnalysisFeedbackSchema = z.object({ - id: z.number(), - status: z.string(), - user_id: z.string(), - created_at: z.coerce.date(), - created_by: z.string(), -}); - -const SummarySchema = z.object({ - id: z.number(), - value: z.string(), - status: z.string(), - user_id: z.string(), - created_at: z.coerce.date(), - created_by: z.string(), - updated_at: z.coerce.date().nullable(), - updated_by: z.string(), - doctor_user_id: z.string().nullable(), - analysis_order_id: z.number(), - doctor_analysis_feedback: z.array(DoctorAnalysisFeedbackSchema), -}); - -export const AnalysisResultDetailsSchema = z.object({ - id: z.number(), - analysis_order_id: z.number(), - order_number: z.string(), - order_status: z.string(), - user_id: z.string(), - created_at: z.coerce.date(), - updated_at: z.coerce.date().nullable(), - elements: z.array(ElementSchema), - order: OrderSchema, - summary: SummarySchema.nullable(), -}); -export type AnalysisResultDetails = z.infer; - -export type AnalysisResultDetailsElementResults = { - unit: string | null; - normLower: number | null; - normUpper: number | null; - normStatus: number | null; - responseTime: string | null; - responseValue: number | null; - responseValueIsNegative: boolean | null; - responseValueIsWithinNorm: boolean | null; - normLowerIncluded: boolean; - normUpperIncluded: boolean; - status: string; - analysisElementOriginalId: string; - nestedElements: { - analysisElementOriginalId: string; - normLower?: number | null; - normLowerIncluded: boolean; - normStatus: number; - normUpper?: number | null; - normUpperIncluded: boolean; - responseTime: string; - responseValue: number; - status: number; - unit: string; - }[]; - labComment?: string | null; -}; - -export type AnalysisResultDetailsElement = { - analysisIdOriginal: string; - isWaitingForResults: boolean; - analysisName: string; - results: AnalysisResultDetailsElementResults; -}; - -export type AnalysisResultDetailsMapped = { - id: number; - order: { - status: string; - medusaOrderId: string; - createdAt: Date | string; - }; - elements: { - id: string; - unit: string; - norm_lower: number; - norm_upper: number; - norm_status: number; - analysis_name: string; - response_time: string; - response_value: number; - norm_lower_included: boolean; - norm_upper_included: boolean; - status: string; - analysis_element_original_id: string; - }[]; - orderedAnalysisElementIds: number[]; - orderedAnalysisElements: AnalysisResultDetailsElement[]; - summary: { - id: number; - status: string; - user_id: string; - created_at: Date; - created_by: string; - value?: string; - } | null; -}; diff --git a/packages/features/user-analyses/src/server/api.ts b/packages/features/user-analyses/src/server/api.ts index e6a7924..51e595c 100644 --- a/packages/features/user-analyses/src/server/api.ts +++ b/packages/features/user-analyses/src/server/api.ts @@ -2,9 +2,9 @@ import { SupabaseClient } from '@supabase/supabase-js'; import { Database } from '@kit/supabase/database'; import { toArray } from '@kit/shared/utils'; -import type { UuringElement, UuringuVastus } from '@kit/shared/types/medipost-analysis'; +import type { UuringuVastus } from '@kit/shared/types/medipost-analysis'; -import type { AnalysisResultDetails, AnalysisResultDetailsMapped, UserAnalysis } from '../types/analysis-results'; +import type { AnalysisResultDetails, AnalysisResultDetailsMapped, AnalysisResultsDetailsElementNested, UserAnalysis } from '../types/analysis-results'; import type { AnalysisOrder } from '../types/analysis-orders'; /** @@ -166,28 +166,14 @@ class UserAnalysesApi { if (!nestedAnalysisElement) { continue; } - results.nestedElements.push({ - ...nestedAnalysisElement, - analysisElementOriginalId, - analysisName: nestedAnalysisElement.analysis_name_lab, - }); - } - } - mappedOrderedAnalysisElements.forEach(({ results }) => { - results?.nestedElements.forEach(({ analysisElementOriginalId }) => { - const nestedAnalysisElement = nestedAnalysisElements.find(({ id }) => id === analysisElementOriginalId); - if (nestedAnalysisElement) { - results?.nestedElements.push({ - ...nestedAnalysisElement, - analysisElementOriginalId, - analysisName: nestedAnalysisElement.analysis_name_lab, - }); - } else { - console.error('Nested analysis element not found for analysis element original id=', analysisElementOriginalId); - } - }); - }); + nestedElement.analysisElementOriginalId = analysisElementOriginalId; + nestedElement.analysisName = nestedAnalysisElement.analysis_name_lab as string | undefined; + } + results.nestedElements = results.nestedElements.sort((a, b) => a.analysisName?.localeCompare(b.analysisName ?? '') ?? 0); + } + } else { + console.error('Failed to get nested analysis elements by ids=', nestedAnalysisElementIds, nestedAnalysisElementsError); } } @@ -218,16 +204,12 @@ class UserAnalysesApi { analysisName: analysisNameLab, results: { nestedElements: (() => { - const nestedElements = elementResponse.original_response_element?.UuringuElement as UuringElement[] | undefined; - if (!nestedElements) { - return []; - } - return toArray(nestedElements).map((element) => { + const nestedElements = toArray(elementResponse.original_response_element?.UuringuElement) + return nestedElements.map((element) => { const mappedResponse = this.mapUuringVastus({ uuringVastus: element.UuringuVastus as UuringuVastus | undefined, }); return { - status: element.UuringOlek, unit: element.Mootyhik ?? null, normLower: mappedResponse.normLower, normUpper: mappedResponse.normUpper, @@ -239,6 +221,7 @@ class UserAnalysesApi { normLowerIncluded: mappedResponse.normLowerIncluded, normUpperIncluded: mappedResponse.normUpperIncluded, analysisElementOriginalId: element.UuringId, + status: elementResponse.status, analysisName: undefined, }; }); @@ -251,12 +234,13 @@ class UserAnalysesApi { normStatus: elementResponse.norm_status, responseTime: elementResponse.response_time, responseValue: elementResponse.response_value, - responseValueIsNegative: elementResponse.response_value_is_negative === true, - responseValueIsWithinNorm: elementResponse.response_value_is_within_norm === true, + responseValueIsNegative: elementResponse.response_value_is_negative === null ? null : elementResponse.response_value_is_negative === true, + responseValueIsWithinNorm: elementResponse.response_value_is_within_norm === null ? null : elementResponse.response_value_is_within_norm === true, normLowerIncluded: elementResponse.norm_lower_included, normUpperIncluded: elementResponse.norm_upper_included, status: elementResponse.status, analysisElementOriginalId: elementResponse.analysis_element_original_id, + elementResponse, } }; } diff --git a/packages/features/user-analyses/src/types/analysis-results.ts b/packages/features/user-analyses/src/types/analysis-results.ts index ad854bf..27383c4 100644 --- a/packages/features/user-analyses/src/types/analysis-results.ts +++ b/packages/features/user-analyses/src/types/analysis-results.ts @@ -1,6 +1,5 @@ -import * as z from 'zod'; - import { Database } from '@kit/supabase/database'; +import { AnalysisOrderStatus, NormStatus } from '@kit/shared/types/medipost-analysis'; export type UserAnalysisElement = Database['medreport']['Tables']['analysis_response_elements']['Row']; @@ -10,66 +9,109 @@ export type UserAnalysisResponse = }; export type UserAnalysis = UserAnalysisResponse[]; -const ElementSchema = z.object({ - unit: z.string(), - norm_lower: z.number(), - norm_upper: z.number(), - norm_status: z.number(), - analysis_name: z.string(), - response_time: z.string(), - response_value: z.number(), - response_value_is_negative: z.boolean(), - response_value_is_within_norm: z.boolean(), - norm_lower_included: z.boolean(), - norm_upper_included: z.boolean(), - status: z.string(), - analysis_element_original_id: z.string(), - original_response_element: z.object({ +type ElementSchema = { + unit: string, + norm_lower: number, + norm_upper: number, + norm_status: number, + analysis_name: string, + response_time: string, + response_value: number, + response_value_is_negative: boolean, + response_value_is_within_norm: boolean, + norm_lower_included: boolean, + norm_upper_included: boolean, + status: string, + analysis_element_original_id: string, + original_response_element: { + UuringuElement: { + UuringIdOID: string, + UuringId: string, + TLyhend: string, + KNimetus: string, + UuringNimi: string, + UuringuKommentaar: string | null, + TellijaUuringId: number, + TeostajaUuringId: string, + UuringOlek: keyof typeof AnalysisOrderStatus, + Mootyhik: string | null, + Kood: { + HkKood: number, + HkKoodiKordaja: number, + Koefitsient: number, + Hind: number, + }, + UuringuVastus: { + VastuseVaartus: string, + VastuseAeg: string, + NormiStaatus: keyof typeof NormStatus, + ProoviJarjenumber: number, + }, + UuringuTaitjaAsutuseJnr: number, + }, + UuringuKommentaar: string | null, + }, +}; - }), -}); +type OrderSchema = { + status: string, + medusa_order_id: string, + created_at: string, +}; -const OrderSchema = z.object({ - status: z.string(), - medusa_order_id: z.string(), - created_at: z.coerce.date(), -}); +type DoctorAnalysisFeedbackSchema = { + id: number, + status: string, + user_id: string, + created_at: string, + created_by: string, +}; -const DoctorAnalysisFeedbackSchema = z.object({ - id: z.number(), - status: z.string(), - user_id: z.string(), - created_at: z.coerce.date(), - created_by: z.string(), -}); +type SummarySchema = { + id: number, + value: string, + status: string, + user_id: string, + created_at: string, + created_by: string, + updated_at: string | null, + updated_by: string, + doctor_user_id: string | null, + analysis_order_id: number, + doctor_analysis_feedback: DoctorAnalysisFeedbackSchema[], +}; -const SummarySchema = z.object({ - id: z.number(), - value: z.string(), - status: z.string(), - user_id: z.string(), - created_at: z.coerce.date(), - created_by: z.string(), - updated_at: z.coerce.date().nullable(), - updated_by: z.string(), - doctor_user_id: z.string().nullable(), - analysis_order_id: z.number(), - doctor_analysis_feedback: z.array(DoctorAnalysisFeedbackSchema), -}); - -export const AnalysisResultDetailsSchema = z.object({ - id: z.number(), - analysis_order_id: z.number(), - order_number: z.string(), - order_status: z.string(), - user_id: z.string(), - created_at: z.coerce.date(), - updated_at: z.coerce.date().nullable(), - elements: z.array(ElementSchema), +export type AnalysisResultDetails = { + id: number, + analysis_order_id: number, + order_number: string, + order_status: string, + user_id: string, + created_at: string, + updated_at: string | null, + elements: ElementSchema[], order: OrderSchema, - summary: SummarySchema.nullable(), -}); -export type AnalysisResultDetails = z.infer; + summary: SummarySchema | null, +}; + +export type AnalysisResultsDetailsElementNested = { + analysisElementOriginalId: string; + analysisName?: string; +} & Pick< + AnalysisResultDetailsElementResults, + 'unit' | + 'normLower' | + 'normUpper' | + 'normStatus' | + 'responseTime' | + 'responseValue' | + 'responseValueIsNegative' | + 'responseValueIsWithinNorm' | + 'normLowerIncluded' | + 'normUpperIncluded' | + 'status' | + 'analysisElementOriginalId' +>; export type AnalysisResultDetailsElementResults = { unit: string | null; @@ -84,19 +126,7 @@ export type AnalysisResultDetailsElementResults = { normUpperIncluded: boolean; status: string; analysisElementOriginalId: string; - nestedElements: { - analysisElementOriginalId: string; - normLower?: number | null; - normLowerIncluded: boolean; - normStatus: number | null; - normUpper?: number | null; - normUpperIncluded: boolean; - responseTime: string | null; - responseValue: number | null; - status: number; - unit: string | null; - analysisName?: string | null; - }[]; + nestedElements: AnalysisResultsDetailsElementNested[]; labComment?: string | null; }; @@ -114,27 +144,13 @@ export type AnalysisResultDetailsMapped = { medusaOrderId: string; createdAt: Date | string; }; - elements: { - id: string; - unit: string; - norm_lower: number; - norm_upper: number; - norm_status: number; - analysis_name: string; - response_time: string; - response_value: number; - norm_lower_included: boolean; - norm_upper_included: boolean; - status: string; - analysis_element_original_id: string; - }[]; orderedAnalysisElementIds: number[]; orderedAnalysisElements: AnalysisResultDetailsElement[]; summary: { id: number; status: string; user_id: string; - created_at: Date; + created_at: string; created_by: string; value?: string; } | null;