From d99016aa5e3cc56f80406914e5625aed7ff69a9c Mon Sep 17 00:00:00 2001 From: Karli Date: Thu, 18 Sep 2025 16:41:05 +0300 Subject: [PATCH 01/11] feat(MED-168): keep medipost response data unique, no need for duplicate rows --- app/api/order/medipost-test-response/route.ts | 4 +- lib/services/analysis-order.service.ts | 22 ++++++++-- .../medipost/medipostMessageBase.service.ts | 42 ++++++++++++------- .../medipostPrivateMessage.service.ts | 16 +++---- ...lysis_response_element_unique_by_order.sql | 18 ++++++++ 5 files changed, 75 insertions(+), 27 deletions(-) create mode 100644 supabase/migrations/20250918143530_analysis_response_element_unique_by_order.sql diff --git a/app/api/order/medipost-test-response/route.ts b/app/api/order/medipost-test-response/route.ts index 5142972..5b7ac9a 100644 --- a/app/api/order/medipost-test-response/route.ts +++ b/app/api/order/medipost-test-response/route.ts @@ -3,7 +3,7 @@ import { getAnalysisOrder } from "~/lib/services/order.service"; import { composeOrderTestResponseXML, sendPrivateMessageTestResponse } from "~/lib/services/medipost/medipostTest.service"; import { retrieveOrder } from "@lib/data"; import { getAccountAdmin } from "~/lib/services/account.service"; -import { createMedipostActionLog } from "~/lib/services/medipost/medipostMessageBase.service"; +import { upsertMedipostActionLog } from "~/lib/services/medipost/medipostMessageBase.service"; import { getOrderedAnalysisIds } from "~/lib/services/medusaOrder.service"; export async function POST(request: Request) { @@ -35,7 +35,7 @@ export async function POST(request: Request) { }); try { - await createMedipostActionLog({ + await upsertMedipostActionLog({ action: 'send_fake_analysis_results_to_medipost', xml: messageXml, medusaOrderId, diff --git a/lib/services/analysis-order.service.ts b/lib/services/analysis-order.service.ts index cd57b9a..5fa5ed5 100644 --- a/lib/services/analysis-order.service.ts +++ b/lib/services/analysis-order.service.ts @@ -18,16 +18,32 @@ export async function getExistingAnalysisResponseElements({ return data as AnalysisResponseElement[]; } -export async function createAnalysisResponseElement({ +export async function upsertAnalysisResponseElement({ element, }: { element: Omit; }) { - await getSupabaseServerAdminClient() + const { data } = await getSupabaseServerAdminClient() .schema('medreport') .from('analysis_response_elements') - .insert(element) + .upsert( + element, + { + onConflict: 'analysis_response_id,analysis_element_original_id', + ignoreDuplicates: false + } + ) + .select('id') .throwOnError(); + + const analysisResponseElementId = data?.[0]?.id; + if (!analysisResponseElementId) { + throw new Error( + `Failed to insert or update analysis response element (response id: ${element.analysis_response_id}, element id: ${element.analysis_element_original_id})` + ); + } + + return { analysisResponseElementId }; } export async function upsertAnalysisResponse({ diff --git a/lib/services/medipost/medipostMessageBase.service.ts b/lib/services/medipost/medipostMessageBase.service.ts index ef452ec..d3194e8 100644 --- a/lib/services/medipost/medipostMessageBase.service.ts +++ b/lib/services/medipost/medipostMessageBase.service.ts @@ -27,7 +27,7 @@ export async function getLatestMessage({ ); } -export async function createMedipostActionLog({ +export async function upsertMedipostActionLog({ action, xml, hasAnalysisResults = false, @@ -40,8 +40,7 @@ export async function createMedipostActionLog({ action: | 'send_order_to_medipost' | 'sync_analysis_results_from_medipost' - | 'send_fake_analysis_results_to_medipost' - | 'send_analysis_results_to_medipost'; + | 'send_fake_analysis_results_to_medipost'; xml: string; hasAnalysisResults?: boolean; medusaOrderId?: string | null; @@ -50,19 +49,34 @@ export async function createMedipostActionLog({ medipostExternalOrderId?: string | null; medipostPrivateMessageId?: string | null; }) { - await getSupabaseServerAdminClient() + const { data } = await getSupabaseServerAdminClient() .schema('medreport') .from('medipost_actions') - .insert({ - action, - xml, - has_analysis_results: hasAnalysisResults, - medusa_order_id: medusaOrderId, - response_xml: responseXml, - has_error: hasError, - medipost_external_order_id: medipostExternalOrderId, - medipost_private_message_id: medipostPrivateMessageId, - }) + .upsert( + { + action, + xml, + has_analysis_results: hasAnalysisResults, + medusa_order_id: medusaOrderId, + response_xml: responseXml, + has_error: hasError, + medipost_external_order_id: medipostExternalOrderId, + medipost_private_message_id: medipostPrivateMessageId, + }, + { + onConflict: 'medipost_private_message_id', + ignoreDuplicates: false + } + ) .select('id') .throwOnError(); + + const medipostActionId = data?.[0]?.id; + if (!medipostActionId) { + throw new Error( + `Failed to insert or update medipost action (private message id: ${medipostPrivateMessageId})` + ); + } + + return { medipostActionId }; } diff --git a/lib/services/medipost/medipostPrivateMessage.service.ts b/lib/services/medipost/medipostPrivateMessage.service.ts index f6356e3..fba8b76 100644 --- a/lib/services/medipost/medipostPrivateMessage.service.ts +++ b/lib/services/medipost/medipostPrivateMessage.service.ts @@ -21,7 +21,7 @@ import { Tables } from '@kit/supabase/database'; import { getSupabaseServerAdminClient } from '@/packages/supabase/src/clients/server-admin-client'; import { getAnalysisElementsAdmin } from '../analysis-element.service'; import { getAnalyses } from '../analyses.service'; -import { createMedipostActionLog, getLatestMessage } from './medipostMessageBase.service'; +import { upsertMedipostActionLog, getLatestMessage } from './medipostMessageBase.service'; import { validateMedipostResponse } from './medipostValidate.service'; import { getAnalysisOrder, updateAnalysisOrderStatus } from '../order.service'; import { parseXML } from '../util/xml.service'; @@ -29,7 +29,7 @@ import { composeOrderXML, OrderedAnalysisElement } from './medipostXML.service'; import { getAccountAdmin } from '../account.service'; import { logMedipostDispatch } from '../audit.service'; import { MedipostValidationError } from './MedipostValidationError'; -import { createAnalysisResponseElement, getExistingAnalysisResponseElements, upsertAnalysisResponse } from '../analysis-order.service'; +import { upsertAnalysisResponseElement, getExistingAnalysisResponseElements, upsertAnalysisResponse } from '../analysis-order.service'; const BASE_URL = process.env.MEDIPOST_URL!; const USER = process.env.MEDIPOST_USER!; @@ -242,7 +242,7 @@ export async function syncPrivateMessage({ for (const element of newElements) { try { - await createAnalysisResponseElement({ + await upsertAnalysisResponseElement({ element: { ...element, analysis_response_id: analysisResponseId, @@ -305,7 +305,7 @@ export async function readPrivateMessageResponse({ const hasInvalidOrderId = isNaN(analysisOrderId); if (hasInvalidOrderId || !messageResponse || !patientPersonalCode) { - await createMedipostActionLog({ + await upsertMedipostActionLog({ action: 'sync_analysis_results_from_medipost', xml: privateMessageXml, hasAnalysisResults: false, @@ -342,7 +342,7 @@ export async function readPrivateMessageResponse({ const status = await syncPrivateMessage({ messageResponse, order: analysisOrder }); - await createMedipostActionLog({ + await upsertMedipostActionLog({ action: 'sync_analysis_results_from_medipost', xml: privateMessageXml, hasAnalysisResults: true, @@ -475,7 +475,7 @@ export async function sendOrderToMedipost({ isMedipostError, errorMessage: e.response, }); - await createMedipostActionLog({ + await upsertMedipostActionLog({ action: 'send_order_to_medipost', xml: orderXml, hasAnalysisResults: false, @@ -489,7 +489,7 @@ export async function sendOrderToMedipost({ isSuccess: false, isMedipostError, }); - await createMedipostActionLog({ + await upsertMedipostActionLog({ action: 'send_order_to_medipost', xml: orderXml, hasAnalysisResults: false, @@ -505,7 +505,7 @@ export async function sendOrderToMedipost({ isSuccess: true, isMedipostError: false, }); - await createMedipostActionLog({ + await upsertMedipostActionLog({ action: 'send_order_to_medipost', xml: orderXml, hasAnalysisResults: false, diff --git a/supabase/migrations/20250918143530_analysis_response_element_unique_by_order.sql b/supabase/migrations/20250918143530_analysis_response_element_unique_by_order.sql new file mode 100644 index 0000000..082cab4 --- /dev/null +++ b/supabase/migrations/20250918143530_analysis_response_element_unique_by_order.sql @@ -0,0 +1,18 @@ +-- Add unique constraint on analysis_response_elements by analysis_response_id + analysis_element_original_id +CREATE UNIQUE INDEX analysis_response_elements_unique_by_response_and_element +ON "medreport"."analysis_response_elements" +USING btree (analysis_response_id, analysis_element_original_id); + +ALTER TABLE "medreport"."analysis_response_elements" +ADD CONSTRAINT "analysis_response_elements_unique_by_response_and_element" +UNIQUE USING INDEX "analysis_response_elements_unique_by_response_and_element"; + +-- Add updated_at column to medipost_actions table +ALTER TABLE "medreport"."medipost_actions" ADD COLUMN "updated_at" TIMESTAMP WITH TIME ZONE DEFAULT now(); + +-- Add unique constraint on medipost_actions by medipost_private_message_id +-- Using partial index to allow multiple NULL values but enforce uniqueness for non-NULL values +CREATE UNIQUE INDEX medipost_actions_unique_by_private_message_id +ON "medreport"."medipost_actions" +USING btree (medipost_private_message_id) +WHERE medipost_private_message_id IS NOT NULL; From 54209553ec76fb3e8dbf19fae097706dafc34092 Mon Sep 17 00:00:00 2001 From: Karli Date: Thu, 18 Sep 2025 16:42:12 +0300 Subject: [PATCH 02/11] feat(MED-168): update analysis results display --- .../analysis-results/_components/analysis.tsx | 24 +++++++++++-------- .../analysis-results/test/test-responses.ts | 22 ++++++++++++++++- 2 files changed, 35 insertions(+), 11 deletions(-) diff --git a/app/home/(user)/(dashboard)/analysis-results/_components/analysis.tsx b/app/home/(user)/(dashboard)/analysis-results/_components/analysis.tsx index d19d0d1..757b120 100644 --- a/app/home/(user)/(dashboard)/analysis-results/_components/analysis.tsx +++ b/app/home/(user)/(dashboard)/analysis-results/_components/analysis.tsx @@ -60,19 +60,14 @@ const Analysis = ({ const unit = results?.unit || ''; const normLower = results?.normLower; const normUpper = results?.normUpper; + const normStatus = results?.normStatus ?? null; const [showTooltip, setShowTooltip] = useState(false); const analysisResultLevel = useMemo(() => { - if (!results) { + if (normStatus === null) { return null; } - if (results.responseValue === null || results.responseValue === undefined) { - return null; - } - - const normStatus = results.normStatus; - switch (normStatus) { case 1: return AnalysisResultLevel.WARNING; @@ -82,12 +77,13 @@ const Analysis = ({ default: return AnalysisResultLevel.NORMAL; } - }, [results]); + }, [normStatus]); const isCancelled = Number(results?.status) === 5; const hasNestedElements = results?.nestedElements.length > 0; const normRangeText = normLower !== null ? `${normLower} - ${normUpper || ''}` : null; + const hasTextualResponse = hasIsNegative || hasIsWithinNorm; return (
@@ -127,10 +123,18 @@ const Analysis = ({ {isCancelled || !results || hasNestedElements ? null : ( <>
-
{value}
+
+ {value} +
{unit}
- {!(hasIsNegative || hasIsWithinNorm) && ( + {!hasTextualResponse && ( <>
{normRangeText} diff --git a/app/home/(user)/(dashboard)/analysis-results/test/test-responses.ts b/app/home/(user)/(dashboard)/analysis-results/test/test-responses.ts index e4da467..14ef88e 100644 --- a/app/home/(user)/(dashboard)/analysis-results/test/test-responses.ts +++ b/app/home/(user)/(dashboard)/analysis-results/test/test-responses.ts @@ -139,7 +139,7 @@ const big1: AnalysisTestResponse = { "unit": null, "normLower": null, "normUpper": 2, - "normStatus": 0, + "normStatus": 2, "responseTime": "2024-02-29T10:13:01+00:00", "responseValue": null, "responseValueIsNegative": null, @@ -150,6 +150,26 @@ const big1: AnalysisTestResponse = { "analysisElementOriginalId": "59156-0" } }, + { + "analysisIdOriginal": "59156-0", + "isWaitingForResults": false, + "analysisName": "Glükoos", + "results": { + "nestedElements": [], + "unit": null, + "normLower": null, + "normUpper": 2, + "normStatus": 0, + "responseTime": "2024-02-29T10:13:01+00:00", + "responseValue": null, + "responseValueIsNegative": null, + "responseValueIsWithinNorm": true, + "normLowerIncluded": false, + "normUpperIncluded": false, + "status": "4", + "analysisElementOriginalId": "59156-0" + } + }, { "analysisIdOriginal": "13955-0", "isWaitingForResults": false, From 08b27b022f726b697077679f9542c646de4ca05a Mon Sep 17 00:00:00 2001 From: Karli Date: Fri, 19 Sep 2025 09:17:23 +0300 Subject: [PATCH 03/11] feat(MED-168): move toArray to shared utils --- app/api/job/handler/sync-analysis-groups.ts | 6 +----- .../analysis-results/_components/analysis-level-bar.tsx | 1 - lib/services/mailer.service.ts | 3 +-- lib/services/medipost/medipostPrivateMessage.service.ts | 2 +- lib/services/medipost/medipostXML.service.ts | 2 +- lib/utils.ts | 5 ----- packages/shared/src/utils.ts | 5 +++++ 7 files changed, 9 insertions(+), 15 deletions(-) diff --git a/app/api/job/handler/sync-analysis-groups.ts b/app/api/job/handler/sync-analysis-groups.ts index 59703fc..fcbf612 100644 --- a/app/api/job/handler/sync-analysis-groups.ts +++ b/app/api/job/handler/sync-analysis-groups.ts @@ -9,11 +9,7 @@ import { AnalysisElement, createAnalysisElement, getAnalysisElements } from '~/l import { createCodes } from '~/lib/services/codes.service'; import { getLatestPublicMessageListItem } from '~/lib/services/medipost/medipostPublicMessage.service'; import type { ICode } from '~/lib/types/code'; - -function toArray(input?: T | T[] | null): T[] { - if (!input) return []; - return Array.isArray(input) ? input : [input]; -} +import { toArray } from '@kit/shared/utils'; const WRITE_XML_TO_FILE = false as boolean; 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 dadc42b..0384a7d 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 @@ -148,7 +148,6 @@ const AnalysisLevelBar = ({ // Show appropriate levels based on available norm bounds const hasLowerBound = lower !== null; const isLowerBoundZero = hasLowerBound && lower === 0; - console.info('isLowerBoundZero', results.analysisElementOriginalId, { isLowerBoundZero, hasLowerBound, lower }); const hasUpperBound = upper !== null; // Determine which section the value falls into diff --git a/lib/services/mailer.service.ts b/lib/services/mailer.service.ts index 8e2a7ea..dbb95fb 100644 --- a/lib/services/mailer.service.ts +++ b/lib/services/mailer.service.ts @@ -1,10 +1,9 @@ 'use server'; -import { toArray } from '@/lib/utils'; - import { getMailer } from '@kit/mailers'; import { enhanceAction } from '@kit/next/actions'; import { getLogger } from '@kit/shared/logger'; +import { toArray } from '@kit/shared/utils'; import { emailSchema } from '~/lib/validations/email.schema'; diff --git a/lib/services/medipost/medipostPrivateMessage.service.ts b/lib/services/medipost/medipostPrivateMessage.service.ts index fba8b76..a8852c0 100644 --- a/lib/services/medipost/medipostPrivateMessage.service.ts +++ b/lib/services/medipost/medipostPrivateMessage.service.ts @@ -13,7 +13,7 @@ import type { MedipostOrderResponse, UuringElement, } from '@/packages/shared/src/types/medipost-analysis'; -import { toArray } from '@/lib/utils'; +import { toArray } from '@kit/shared/utils'; import type { AnalysisOrder } from '~/lib/types/analysis-order'; import type { AnalysisResponseElement } from '~/lib/types/analysis-response-element'; diff --git a/lib/services/medipost/medipostXML.service.ts b/lib/services/medipost/medipostXML.service.ts index 064aec5..1b1f7d3 100644 --- a/lib/services/medipost/medipostXML.service.ts +++ b/lib/services/medipost/medipostXML.service.ts @@ -14,7 +14,7 @@ import { import { MaterjalideGrupp, } from '@/lib/types/medipost'; -import { toArray } from '@/lib/utils'; +import { toArray } from '@kit/shared/utils'; import { uniqBy } from 'lodash'; import { Tables } from '@kit/supabase/database'; diff --git a/lib/utils.ts b/lib/utils.ts index 233042a..70a6054 100644 --- a/lib/utils.ts +++ b/lib/utils.ts @@ -9,11 +9,6 @@ export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)); } -export function toArray(input?: T | T[] | null): T[] { - if (!input) return []; - return Array.isArray(input) ? input : [input]; -} - export function toTitleCase(str?: string) { return ( str diff --git a/packages/shared/src/utils.ts b/packages/shared/src/utils.ts index 877cbca..f08b199 100644 --- a/packages/shared/src/utils.ts +++ b/packages/shared/src/utils.ts @@ -57,3 +57,8 @@ export const getPersonParameters = (personalCode: string) => { return null; } }; + +export function toArray(input?: T | T[] | null): T[] { + if (!input) return []; + return Array.isArray(input) ? input : [input]; +} From c37706f6a9ab8a7533ddab553a74027caa48be18 Mon Sep 17 00:00:00 2001 From: Karli Date: Fri, 19 Sep 2025 09:20:44 +0300 Subject: [PATCH 04/11] feat(MED-168): separate user analysis results building --- .../features/user-analyses/src/server/api.ts | 117 +++++++++++------- 1 file changed, 74 insertions(+), 43 deletions(-) diff --git a/packages/features/user-analyses/src/server/api.ts b/packages/features/user-analyses/src/server/api.ts index e44faec..e7d9ed2 100644 --- a/packages/features/user-analyses/src/server/api.ts +++ b/packages/features/user-analyses/src/server/api.ts @@ -43,31 +43,13 @@ class UserAnalysesApi { async getUserAnalysis( analysisOrderId: number, ): Promise { - const authUser = await this.client.auth.getUser(); - const { data, error: userError } = authUser; - - if (userError) { - console.error('Failed to get user', userError); - throw userError; - } - - const { user } = data; - const analysisOrder = await this.getAnalysisOrder({ analysisOrderId }); const orderedAnalysisElementIds = analysisOrder.analysis_element_ids ?? []; if (orderedAnalysisElementIds.length === 0) { console.error('No ordered analysis element ids found for analysis order id=', analysisOrderId); return null; } - const { data: orderedAnalysisElements, error: orderedAnalysisElementsError } = await this.client - .schema('medreport') - .from('analysis_elements') - .select('analysis_id_original,analysis_name_lab') - .in('id', orderedAnalysisElementIds); - if (orderedAnalysisElementsError) { - console.error('Failed to get ordered analysis elements for analysis order id=', analysisOrderId, orderedAnalysisElementsError); - throw orderedAnalysisElementsError; - } + const orderedAnalysisElements = await this.getOrderedAnalysisElements({ analysisOrderId, orderedAnalysisElementIds }); const orderedAnalysisElementOriginalIds = orderedAnalysisElements.map(({ analysis_id_original }) => analysis_id_original); if (orderedAnalysisElementOriginalIds.length === 0) { @@ -75,6 +57,44 @@ class UserAnalysesApi { return null; } + const responseWithElements = await this.getAnalysisResponseWithElements({ analysisOrderId }); + if (!responseWithElements) { + return null; + } + + const mappedOrderedAnalysisElements = await this.getMappedOrderedAnalysisElements({ + analysisResponseElements: responseWithElements.elements, + orderedAnalysisElements, + }); + + const feedback = responseWithElements.summary?.doctor_analysis_feedback?.[0]; + return { + id: analysisOrderId, + order: { + status: analysisOrder.status, + medusaOrderId: analysisOrder.medusa_order_id, + createdAt: new Date(analysisOrder.created_at), + }, + orderedAnalysisElementIds, + orderedAnalysisElements: mappedOrderedAnalysisElements, + summary: + feedback?.status === 'COMPLETED' + ? (responseWithElements.summary?.doctor_analysis_feedback?.[0] ?? null) + : null, + }; + } + + async getAnalysisResponseWithElements({ + analysisOrderId, + }: { + analysisOrderId: number; + }) { + const { data, error: userError } = await this.client.auth.getUser(); + if (userError) { + throw userError; + } + const { user } = data; + const { data: analysisResponse } = await this.client .schema('medreport') .from('analysis_responses') @@ -87,23 +107,45 @@ class UserAnalysesApi { .eq('analysis_order_id', analysisOrderId) .throwOnError(); - const responseWithElements = analysisResponse?.[0] as AnalysisResultDetails | null; - if (!responseWithElements) { - return null; + return analysisResponse?.[0] as AnalysisResultDetails | null; + } + + async getOrderedAnalysisElements({ + analysisOrderId, + orderedAnalysisElementIds, + }: { + analysisOrderId: number; + orderedAnalysisElementIds: number[]; + }) { + const { data: orderedAnalysisElements, error: orderedAnalysisElementsError } = await this.client + .schema('medreport') + .from('analysis_elements') + .select('analysis_id_original,analysis_name_lab') + .in('id', orderedAnalysisElementIds); + if (orderedAnalysisElementsError) { + console.error(`Failed to get ordered analysis elements for analysis order id=${analysisOrderId}`, orderedAnalysisElementsError); + throw orderedAnalysisElementsError; } + return orderedAnalysisElements; + } - const analysisResponseElements = responseWithElements.elements; - - const feedback = responseWithElements.summary?.doctor_analysis_feedback?.[0]; - + async getMappedOrderedAnalysisElements({ + analysisResponseElements, + orderedAnalysisElements, + }: { + analysisResponseElements: AnalysisResultDetails['elements']; + orderedAnalysisElements: { analysis_id_original: string; analysis_name_lab: string }[]; + }) { const mappedOrderedAnalysisElements = orderedAnalysisElements.map(({ analysis_id_original, analysis_name_lab }) => { - return this.getOrderedAnalysisElements({ + return this.getOrderedAnalysisElement({ analysisIdOriginal: analysis_id_original, analysisNameLab: analysis_name_lab, analysisResponseElements, }); }).sort((a, b) => a.analysisName.localeCompare(b.analysisName)); - const nestedAnalysisElementIds = mappedOrderedAnalysisElements.map(({ results }) => results?.nestedElements.map(({ analysisElementOriginalId }) => analysisElementOriginalId)).flat().filter(Boolean); + const nestedAnalysisElementIds = mappedOrderedAnalysisElements + .map(({ results }) => results?.nestedElements.map(({ analysisElementOriginalId }) => analysisElementOriginalId)) + .flat().filter(Boolean); if (nestedAnalysisElementIds.length > 0) { const { data: nestedAnalysisElements, error: nestedAnalysisElementsError } = await this.client .schema('medreport') @@ -139,29 +181,18 @@ class UserAnalysesApi { analysisElementOriginalId, analysisName: nestedAnalysisElement.analysis_name_lab, }); + } else { + console.error('Nested analysis element not found for analysis element original id=', analysisElementOriginalId); } }); }); } } - return { - id: analysisOrderId, - order: { - status: analysisOrder.status, - medusaOrderId: analysisOrder.medusa_order_id, - createdAt: new Date(analysisOrder.created_at), - }, - orderedAnalysisElementIds, - orderedAnalysisElements: mappedOrderedAnalysisElements, - summary: - feedback?.status === 'COMPLETED' - ? (responseWithElements.summary?.doctor_analysis_feedback?.[0] ?? null) - : null, - }; + return mappedOrderedAnalysisElements; } - getOrderedAnalysisElements({ + getOrderedAnalysisElement({ analysisIdOriginal, analysisNameLab, analysisResponseElements, From 81f7a03388bc5a0b0a060ac20fb0ccb67394b87b Mon Sep 17 00:00:00 2001 From: Karli Date: Fri, 19 Sep 2025 10:00:08 +0300 Subject: [PATCH 05/11] feat(MED-168): disable useless console logs on browserside, too many logs --- packages/analytics/src/null-analytics-service.ts | 3 +++ packages/monitoring/core/src/console-monitoring.service.ts | 6 ++++++ 2 files changed, 9 insertions(+) diff --git a/packages/analytics/src/null-analytics-service.ts b/packages/analytics/src/null-analytics-service.ts index 081355c..0e8dcbb 100644 --- a/packages/analytics/src/null-analytics-service.ts +++ b/packages/analytics/src/null-analytics-service.ts @@ -4,6 +4,9 @@ const noop = (event: string) => { // do nothing - this is to prevent errors when the analytics service is not initialized return async (...args: unknown[]) => { + if (typeof window !== 'undefined') { + return; + } console.debug( `Noop analytics service called with event: ${event}`, ...args.filter(Boolean), diff --git a/packages/monitoring/core/src/console-monitoring.service.ts b/packages/monitoring/core/src/console-monitoring.service.ts index 48d1ca5..7943481 100644 --- a/packages/monitoring/core/src/console-monitoring.service.ts +++ b/packages/monitoring/core/src/console-monitoring.service.ts @@ -2,6 +2,9 @@ import { MonitoringService } from '@kit/monitoring-core'; export class ConsoleMonitoringService implements MonitoringService { identifyUser(data: { id: string }) { + if (typeof window !== 'undefined') { + return; + } console.log(`[Console Monitoring] Identified user`, data); } @@ -12,6 +15,9 @@ export class ConsoleMonitoringService implements MonitoringService { } captureEvent(event: string) { + if (typeof window !== 'undefined') { + return; + } console.log(`[Console Monitoring] Captured event: ${event}`); } From e7b484e1d40525f34ece588007003461f1fadf9b Mon Sep 17 00:00:00 2001 From: Karli Date: Fri, 19 Sep 2025 12:48:57 +0300 Subject: [PATCH 06/11] feat(MED-168): move analysis result element mapping to shared --- .../medipostPrivateMessage.service.ts | 26 ++++---- .../features/user-analyses/src/server/api.ts | 65 ++++++++++++++----- .../src/types/analysis-results.ts | 11 ++-- packages/supabase/src/database.types.ts | 6 +- 4 files changed, 68 insertions(+), 40 deletions(-) diff --git a/lib/services/medipost/medipostPrivateMessage.service.ts b/lib/services/medipost/medipostPrivateMessage.service.ts index a8852c0..775d5bc 100644 --- a/lib/services/medipost/medipostPrivateMessage.service.ts +++ b/lib/services/medipost/medipostPrivateMessage.service.ts @@ -16,6 +16,7 @@ import type { import { toArray } from '@kit/shared/utils'; import type { AnalysisOrder } from '~/lib/types/analysis-order'; import type { AnalysisResponseElement } from '~/lib/types/analysis-response-element'; +import { createUserAnalysesApi } from '@/packages/features/user-analyses/src/server/api'; import { Tables } from '@kit/supabase/database'; import { getSupabaseServerAdminClient } from '@/packages/supabase/src/clients/server-admin-client'; @@ -138,28 +139,25 @@ export async function getAnalysisResponseElementsForGroup({ continue; } - const responseValueIsNumeric = responseValue !== null; - const responseValueIsNegative = vastuseVaartus === 'Negatiivne'; - const responseValueIsWithinNorm = vastuseVaartus === 'Normi piires'; + const mappedResponse = createUserAnalysesApi(getSupabaseServerAdminClient()) + .mapUuringVastus({ uuringVastus: response }); results.push({ analysis_element_original_id: analysisElementOriginalId, - norm_lower: response.NormAlum?.['#text'] ?? null, - norm_lower_included: - response.NormAlum?.['@_kaasaarvatud'].toLowerCase() === 'jah', - norm_status: response.NormiStaatus, - norm_upper: response.NormYlem?.['#text'] ?? null, - norm_upper_included: - response.NormYlem?.['@_kaasaarvatud'].toLowerCase() === 'jah', - response_time: response.VastuseAeg ?? null, - response_value: responseValue, + norm_lower: mappedResponse.normLower, + norm_lower_included: mappedResponse.normLowerIncluded, + norm_status: mappedResponse.normStatus, + norm_upper: mappedResponse.normUpper, + norm_upper_included: mappedResponse.normUpperIncluded, + response_time: mappedResponse.responseTime, + response_value: mappedResponse.responseValue, unit: groupUuringElement.Mootyhik ?? null, original_response_element: groupUuringElement, analysis_name: groupUuringElement.UuringNimi || groupUuringElement.KNimetus, comment: groupUuringElement.UuringuKommentaar ?? null, status: status.toString(), - response_value_is_within_norm: responseValueIsNumeric ? null : responseValueIsWithinNorm, - response_value_is_negative: responseValueIsNumeric ? null : responseValueIsNegative, + response_value_is_within_norm: mappedResponse.responseValueIsWithinNorm, + response_value_is_negative: mappedResponse.responseValueIsNegative, }); } } diff --git a/packages/features/user-analyses/src/server/api.ts b/packages/features/user-analyses/src/server/api.ts index e7d9ed2..e6a7924 100644 --- a/packages/features/user-analyses/src/server/api.ts +++ b/packages/features/user-analyses/src/server/api.ts @@ -1,6 +1,7 @@ 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 { AnalysisResultDetails, AnalysisResultDetailsMapped, UserAnalysis } from '../types/analysis-results'; @@ -143,6 +144,7 @@ class UserAnalysesApi { analysisResponseElements, }); }).sort((a, b) => a.analysisName.localeCompare(b.analysisName)); + const nestedAnalysisElementIds = mappedOrderedAnalysisElements .map(({ results }) => results?.nestedElements.map(({ analysisElementOriginalId }) => analysisElementOriginalId)) .flat().filter(Boolean); @@ -151,7 +153,7 @@ class UserAnalysesApi { .schema('medreport') .from('analysis_elements') .select('*') - .in('id', nestedAnalysisElementIds); + .in('analysis_id_original', nestedAnalysisElementIds); if (!nestedAnalysisElementsError && nestedAnalysisElements) { for (const mappedOrderedAnalysisElement of mappedOrderedAnalysisElements) { const { results } = mappedOrderedAnalysisElement; @@ -160,7 +162,7 @@ class UserAnalysesApi { } for (const nestedElement of results.nestedElements) { const { analysisElementOriginalId } = nestedElement; - const nestedAnalysisElement = nestedAnalysisElements.find(({ id }) => id === analysisElementOriginalId); + const nestedAnalysisElement = nestedAnalysisElements.find(({ analysis_id_original }) => analysis_id_original === analysisElementOriginalId); if (!nestedAnalysisElement) { continue; } @@ -220,25 +222,24 @@ class UserAnalysesApi { if (!nestedElements) { return []; } - return nestedElements.map((element) => { - const elementVastus = element.UuringuVastus as UuringuVastus | undefined; - const responseValue = elementVastus?.VastuseVaartus; - const responseValueIsNumeric = !isNaN(Number(responseValue)); - const responseValueIsNegative = responseValue === 'Negatiivne'; - const responseValueIsWithinNorm = responseValue === 'Normi piires'; + return toArray(nestedElements).map((element) => { + const mappedResponse = this.mapUuringVastus({ + uuringVastus: element.UuringuVastus as UuringuVastus | undefined, + }); return { status: element.UuringOlek, - unit: element.Mootyhik, - normLower: elementVastus?.NormAlum?.['#text'], - normUpper: elementVastus?.NormYlem?.['#text'], - normStatus: elementVastus?.NormiStaatus, - responseTime: elementVastus?.VastuseAeg, - response_value: responseValueIsNegative || !responseValueIsNumeric ? null : (responseValue ?? null), - response_value_is_negative: responseValueIsNumeric ? null : responseValueIsNegative, - response_value_is_within_norm: responseValueIsNumeric ? null : responseValueIsWithinNorm, - normLowerIncluded: elementVastus?.NormAlum?.['@_kaasaarvatud'] === 'JAH', - normUpperIncluded: elementVastus?.NormYlem?.['@_kaasaarvatud'] === 'JAH', + unit: element.Mootyhik ?? null, + normLower: mappedResponse.normLower, + normUpper: mappedResponse.normUpper, + normStatus: mappedResponse.normStatus, + responseTime: mappedResponse.responseTime, + responseValue: mappedResponse.responseValue, + responseValueIsNegative: mappedResponse.responseValueIsNegative, + responseValueIsWithinNorm: mappedResponse.responseValueIsWithinNorm, + normLowerIncluded: mappedResponse.normLowerIncluded, + normUpperIncluded: mappedResponse.normUpperIncluded, analysisElementOriginalId: element.UuringId, + analysisName: undefined, }; }); })(), @@ -260,6 +261,34 @@ class UserAnalysesApi { }; } + 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); + const responseValueIsNegative = vastuseVaartus === 'Negatiivne'; + const responseValueIsWithinNorm = vastuseVaartus === 'Normi piires'; + return { + normLower: uuringVastus?.NormAlum?.['#text'] ?? null, + normUpper: uuringVastus?.NormYlem?.['#text'] ?? null, + normStatus: uuringVastus?.NormiStaatus ?? null, + responseTime: uuringVastus?.VastuseAeg ?? null, + responseValue: responseValueIsNegative || !responseValueIsNumeric ? null : (responseValueNumber ?? null), + responseValueIsNegative: responseValueIsNumeric ? null : responseValueIsNegative, + responseValueIsWithinNorm: responseValueIsNumeric ? null : responseValueIsWithinNorm, + normLowerIncluded: + uuringVastus?.NormAlum?.['@_kaasaarvatud'].toLowerCase() === 'jah', + normUpperIncluded: + uuringVastus?.NormYlem?.['@_kaasaarvatud'].toLowerCase() === 'jah', + }; + } + // @TODO unused currently async getUserAnalyses(): Promise { const authUser = await this.client.auth.getUser(); diff --git a/packages/features/user-analyses/src/types/analysis-results.ts b/packages/features/user-analyses/src/types/analysis-results.ts index 3176499..ad854bf 100644 --- a/packages/features/user-analyses/src/types/analysis-results.ts +++ b/packages/features/user-analyses/src/types/analysis-results.ts @@ -88,13 +88,14 @@ export type AnalysisResultDetailsElementResults = { analysisElementOriginalId: string; normLower?: number | null; normLowerIncluded: boolean; - normStatus: number; + normStatus: number | null; normUpper?: number | null; normUpperIncluded: boolean; - responseTime: string; - responseValue: number; + responseTime: string | null; + responseValue: number | null; status: number; - unit: string; + unit: string | null; + analysisName?: string | null; }[]; labComment?: string | null; }; @@ -103,7 +104,7 @@ export type AnalysisResultDetailsElement = { analysisIdOriginal: string; isWaitingForResults: boolean; analysisName: string; - results: AnalysisResultDetailsElementResults; + results?: AnalysisResultDetailsElementResults; }; export type AnalysisResultDetailsMapped = { diff --git a/packages/supabase/src/database.types.ts b/packages/supabase/src/database.types.ts index 6907fac..f893f49 100644 --- a/packages/supabase/src/database.types.ts +++ b/packages/supabase/src/database.types.ts @@ -685,7 +685,7 @@ export type Database = { norm_upper: number | null norm_upper_included: boolean | null original_response_element: Json - response_time: string + response_time: string | null response_value: number | null response_value_is_negative?: boolean | null response_value_is_within_norm?: boolean | null @@ -706,7 +706,7 @@ export type Database = { norm_upper?: number | null norm_upper_included?: boolean | null original_response_element: Json - response_time: string + response_time: string | null response_value: number | null response_value_is_negative?: boolean | null response_value_is_within_norm?: boolean | null @@ -727,7 +727,7 @@ export type Database = { norm_upper?: number | null norm_upper_included?: boolean | null original_response_element?: Json - response_time?: string + response_time?: string | null response_value?: number | null response_value_is_negative?: boolean | null response_value_is_within_norm?: boolean | null From 091144d942df115c68c7f5b77ad12552c491f653 Mon Sep 17 00:00:00 2001 From: Karli Date: Fri, 19 Sep 2025 15:28:56 +0300 Subject: [PATCH 07/11] feat(MED-168): clean up types, show nested elements --- .../analysis-results/[id]/page.tsx | 15 +- .../_components/analysis-level-bar.tsx | 26 +-- .../analysis-results/_components/analysis.tsx | 58 +++++- .../analysis-results/test/test-responses.ts | 2 +- .../(user)/_lib/server/load-user-analysis.ts | 2 +- .../accounts/src/types/analysis-orders.ts | 3 - .../accounts/src/types/analysis-results.ts | 139 ------------- .../features/user-analyses/src/server/api.ts | 46 ++--- .../src/types/analysis-results.ts | 186 ++++++++++-------- 9 files changed, 188 insertions(+), 289 deletions(-) delete mode 100644 packages/features/accounts/src/types/analysis-orders.ts delete mode 100644 packages/features/accounts/src/types/analysis-results.ts 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; From 47bbeeb155fcc766112842424dff0cf508ab79ec Mon Sep 17 00:00:00 2001 From: Karli Date: Fri, 19 Sep 2025 14:44:47 +0300 Subject: [PATCH 08/11] feat(MED-168): remove unused field --- packages/features/user-analyses/src/server/api.ts | 1 - packages/features/user-analyses/src/types/analysis-results.ts | 1 - 2 files changed, 2 deletions(-) diff --git a/packages/features/user-analyses/src/server/api.ts b/packages/features/user-analyses/src/server/api.ts index 51e595c..ebc65fe 100644 --- a/packages/features/user-analyses/src/server/api.ts +++ b/packages/features/user-analyses/src/server/api.ts @@ -76,7 +76,6 @@ class UserAnalysesApi { medusaOrderId: analysisOrder.medusa_order_id, createdAt: new Date(analysisOrder.created_at), }, - orderedAnalysisElementIds, orderedAnalysisElements: mappedOrderedAnalysisElements, summary: feedback?.status === 'COMPLETED' diff --git a/packages/features/user-analyses/src/types/analysis-results.ts b/packages/features/user-analyses/src/types/analysis-results.ts index 27383c4..2739d50 100644 --- a/packages/features/user-analyses/src/types/analysis-results.ts +++ b/packages/features/user-analyses/src/types/analysis-results.ts @@ -144,7 +144,6 @@ export type AnalysisResultDetailsMapped = { medusaOrderId: string; createdAt: Date | string; }; - orderedAnalysisElementIds: number[]; orderedAnalysisElements: AnalysisResultDetailsElement[]; summary: { id: number; From afdaebc8046abf9398bdbb71d6c4ac6af1b068bd Mon Sep 17 00:00:00 2001 From: Karli Date: Fri, 19 Sep 2025 15:14:03 +0300 Subject: [PATCH 09/11] feat(MED-168): clean up types --- .../_components/analysis-level-bar.tsx | 47 ++--- .../analysis-results/_components/analysis.tsx | 26 +-- .../features/user-analyses/src/server/api.ts | 24 ++- .../src/types/analysis-results.ts | 174 +++++++++--------- 4 files changed, 127 insertions(+), 144 deletions(-) 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 f53a04b..938d6bb 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 @@ -4,12 +4,7 @@ import { ArrowDown } from 'lucide-react'; import { cn } from '@kit/ui/utils'; import type { AnalysisResultDetailsElementResults } from '@/packages/features/user-analyses/src/types/analysis-results'; - -export enum AnalysisResultLevel { - NORMAL = 0, - WARNING = 1, - CRITICAL = 2, -} +import { AnalysisResultLevel } from '@/packages/features/user-analyses/src/types/analysis-results'; type AnalysisResultLevelBarResults = Pick; @@ -148,34 +143,10 @@ const AnalysisLevelBar = ({ const isValueInNormalRange = !isValueBelowLower && !isValueAboveUpper; const [first, second, third] = useMemo(() => { - if (!hasLowerBound) { - return [ - { - isActive: isNormal, - color: "success", - isFirst: true, - normRangeText, - ...(isNormal ? { arrowLocation } : {}), - }, - { - isActive: isWarning, - color: "warning", - ...(isWarning ? { arrowLocation } : {}), - }, - { - isActive: isCritical, - color: "destructive", - isLast: true, - ...(isCritical ? { arrowLocation } : {}), - }, - ] as const; - } - - return [ + const [warning, normal, critical] = [ { isActive: isWarning, color: "warning", - isFirst: true, ...(isWarning ? { arrowLocation } : {}), }, { @@ -191,6 +162,20 @@ const AnalysisLevelBar = ({ ...(isCritical ? { arrowLocation } : {}), }, ] as const; + + if (!hasLowerBound) { + return [ + { ...normal, isFirst: true }, + warning, + critical, + ] as const; + } + + return [ + { ...warning, isFirst: true }, + normal, + { ...critical, isLast: true }, + ] as const; }, [isValueBelowLower, isValueAboveUpper, isValueInNormalRange, arrowLocation, normRangeText, isNormal, isWarning, isCritical]); return ( diff --git a/app/home/(user)/(dashboard)/analysis-results/_components/analysis.tsx b/app/home/(user)/(dashboard)/analysis-results/_components/analysis.tsx index fd25201..61ef091 100644 --- a/app/home/(user)/(dashboard)/analysis-results/_components/analysis.tsx +++ b/app/home/(user)/(dashboard)/analysis-results/_components/analysis.tsx @@ -2,27 +2,18 @@ import React, { useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; - -import type { - AnalysisResultDetailsElementResults, - AnalysisResultDetailsElement, - AnalysisResultsDetailsElementNested, -} from '@/packages/features/user-analyses/src/types/analysis-results'; import { format } from 'date-fns'; import { Info } from 'lucide-react'; +import type { + AnalysisResultDetailsElement, + AnalysisResultsDetailsElementNested, +} from '@/packages/features/user-analyses/src/types/analysis-results'; +import { AnalysisResultLevel } from '@/packages/features/user-analyses/src/types/analysis-results'; import { Trans } from '@kit/ui/trans'; import { cn } from '@kit/ui/utils'; -import AnalysisLevelBar, { - AnalysisResultLevel, -} from './analysis-level-bar'; - -export enum AnalysisStatus { - NORMAL = 0, - MEDIUM = 1, - HIGH = 2, -} +import AnalysisLevelBar from './analysis-level-bar'; const Analysis = ({ element: elementOriginal, @@ -41,7 +32,7 @@ const Analysis = ({ } return elementOriginal!; })(); - const results: AnalysisResultDetailsElementResults = useMemo(() => { + const results: AnalysisResultDetailsElement['results'] = useMemo(() => { if (isNestedElement) { const nestedElement = element as AnalysisResultsDetailsElementNested; return { @@ -115,7 +106,8 @@ const Analysis = ({ }, [normStatus]); const isCancelled = Number(results?.status) === 5; - const hasNestedElements = results?.nestedElements.length > 0; + const nestedElements = results?.nestedElements ?? null; + const hasNestedElements = Array.isArray(nestedElements) && nestedElements.length > 0; const normRangeText = (() => { if (normLower === null && normUpper === null) { diff --git a/packages/features/user-analyses/src/server/api.ts b/packages/features/user-analyses/src/server/api.ts index ebc65fe..21589c6 100644 --- a/packages/features/user-analyses/src/server/api.ts +++ b/packages/features/user-analyses/src/server/api.ts @@ -4,7 +4,7 @@ import { Database } from '@kit/supabase/database'; import { toArray } from '@kit/shared/utils'; import type { UuringuVastus } from '@kit/shared/types/medipost-analysis'; -import type { AnalysisResultDetails, AnalysisResultDetailsMapped, AnalysisResultsDetailsElementNested, UserAnalysis } from '../types/analysis-results'; +import type { AnalysisResultsQuery, AnalysisResultDetailsElement, AnalysisResultDetailsMapped, AnalysisResultLevel, AnalysisResultsDetailsElementNested, AnalysisStatus, UserAnalysis } from '../types/analysis-results'; import type { AnalysisOrder } from '../types/analysis-orders'; /** @@ -74,7 +74,7 @@ class UserAnalysesApi { order: { status: analysisOrder.status, medusaOrderId: analysisOrder.medusa_order_id, - createdAt: new Date(analysisOrder.created_at), + createdAt: analysisOrder.created_at, }, orderedAnalysisElements: mappedOrderedAnalysisElements, summary: @@ -107,7 +107,7 @@ class UserAnalysesApi { .eq('analysis_order_id', analysisOrderId) .throwOnError(); - return analysisResponse?.[0] as AnalysisResultDetails | null; + return analysisResponse?.[0] as AnalysisResultsQuery | null; } async getOrderedAnalysisElements({ @@ -133,9 +133,9 @@ class UserAnalysesApi { analysisResponseElements, orderedAnalysisElements, }: { - analysisResponseElements: AnalysisResultDetails['elements']; + analysisResponseElements: AnalysisResultsQuery['elements']; orderedAnalysisElements: { analysis_id_original: string; analysis_name_lab: string }[]; - }) { + }): Promise { const mappedOrderedAnalysisElements = orderedAnalysisElements.map(({ analysis_id_original, analysis_name_lab }) => { return this.getOrderedAnalysisElement({ analysisIdOriginal: analysis_id_original, @@ -186,8 +186,8 @@ class UserAnalysesApi { }: { analysisIdOriginal: string; analysisNameLab: string; - analysisResponseElements: AnalysisResultDetails['elements']; - }) { + analysisResponseElements: AnalysisResultsQuery['elements']; + }): AnalysisResultDetailsElement { const elementResponse = analysisResponseElements.find((element) => element.analysis_element_original_id === analysisIdOriginal); if (!elementResponse) { return { @@ -202,7 +202,7 @@ class UserAnalysesApi { isWaitingForResults: false, analysisName: analysisNameLab, results: { - nestedElements: (() => { + nestedElements: ((): AnalysisResultsDetailsElementNested[] => { const nestedElements = toArray(elementResponse.original_response_element?.UuringuElement) return nestedElements.map((element) => { const mappedResponse = this.mapUuringVastus({ @@ -220,13 +220,12 @@ class UserAnalysesApi { normLowerIncluded: mappedResponse.normLowerIncluded, normUpperIncluded: mappedResponse.normUpperIncluded, analysisElementOriginalId: element.UuringId, - status: elementResponse.status, + status: Number(elementResponse.status) as AnalysisStatus, analysisName: undefined, }; }); })(), labComment, - //originalResponseElement: elementResponse.original_response_element ?? null, unit: elementResponse.unit, normLower: elementResponse.norm_lower, normUpper: elementResponse.norm_upper, @@ -237,9 +236,8 @@ class UserAnalysesApi { 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, + status: Number(elementResponse.status) as AnalysisStatus, analysisElementOriginalId: elementResponse.analysis_element_original_id, - elementResponse, } }; } @@ -260,7 +258,7 @@ class UserAnalysesApi { return { normLower: uuringVastus?.NormAlum?.['#text'] ?? null, normUpper: uuringVastus?.NormYlem?.['#text'] ?? null, - normStatus: uuringVastus?.NormiStaatus ?? null, + normStatus: (uuringVastus?.NormiStaatus ?? null) as AnalysisResultLevel | null, responseTime: uuringVastus?.VastuseAeg ?? null, responseValue: responseValueIsNegative || !responseValueIsNumeric ? null : (responseValueNumber ?? null), responseValueIsNegative: responseValueIsNumeric ? null : responseValueIsNegative, diff --git a/packages/features/user-analyses/src/types/analysis-results.ts b/packages/features/user-analyses/src/types/analysis-results.ts index 2739d50..6ccc486 100644 --- a/packages/features/user-analyses/src/types/analysis-results.ts +++ b/packages/features/user-analyses/src/types/analysis-results.ts @@ -1,5 +1,7 @@ -import { Database } from '@kit/supabase/database'; -import { AnalysisOrderStatus, NormStatus } from '@kit/shared/types/medipost-analysis'; +import type { Database } from '@kit/supabase/database'; +import type { AnalysisOrderStatus, NormStatus } from '@kit/shared/types/medipost-analysis'; + +import type { AnalysisOrder } from './analysis-orders'; export type UserAnalysisElement = Database['medreport']['Tables']['analysis_response_elements']['Row']; @@ -9,79 +11,7 @@ export type UserAnalysisResponse = }; export type UserAnalysis = UserAnalysisResponse[]; -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, -}; - -type DoctorAnalysisFeedbackSchema = { - id: number, - status: string, - user_id: string, - created_at: string, - created_by: 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[], -}; - -export type AnalysisResultDetails = { +export type AnalysisResultsQuery = { id: number, analysis_order_id: number, order_number: string, @@ -89,9 +19,73 @@ export type AnalysisResultDetails = { user_id: string, created_at: string, updated_at: string | null, - elements: ElementSchema[], - order: OrderSchema, - summary: SummarySchema | null, + elements: { + 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, + }, + }[], + order: { + status: string, + medusa_order_id: string, + created_at: string, + }, + summary: { + 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: { + id: number, + status: string, + user_id: string, + created_at: string, + created_by: string, + }[], + } | null, }; export type AnalysisResultsDetailsElementNested = { @@ -113,18 +107,33 @@ export type AnalysisResultsDetailsElementNested = { 'analysisElementOriginalId' >; +export enum AnalysisResultLevel { + NORMAL = 0, + WARNING = 1, + CRITICAL = 2, +} + +export enum AnalysisStatus { + QUEUED = 1, + PENDING = 2, + ONGOING = 3, + COMPLETED = 4, + REFUSED = 5, + CANCELLED = 6, +} + export type AnalysisResultDetailsElementResults = { unit: string | null; normLower: number | null; normUpper: number | null; - normStatus: number | null; + normStatus: AnalysisResultLevel | null; responseTime: string | null; responseValue: number | null; responseValueIsNegative: boolean | null; responseValueIsWithinNorm: boolean | null; normLowerIncluded: boolean; normUpperIncluded: boolean; - status: string; + status: AnalysisStatus; analysisElementOriginalId: string; nestedElements: AnalysisResultsDetailsElementNested[]; labComment?: string | null; @@ -140,10 +149,9 @@ export type AnalysisResultDetailsElement = { export type AnalysisResultDetailsMapped = { id: number; order: { - status: string; medusaOrderId: string; - createdAt: Date | string; - }; + createdAt: string; + } & Pick; orderedAnalysisElements: AnalysisResultDetailsElement[]; summary: { id: number; From 71a9ff75c4e271e505bc4e8b299a805f2fe4c5ea Mon Sep 17 00:00:00 2001 From: Karli Date: Fri, 19 Sep 2025 15:26:33 +0300 Subject: [PATCH 10/11] feat(MED-168): improve styles --- .../analysis-results/[id]/page.tsx | 42 +++++++++---------- .../_components/analysis-level-bar.tsx | 10 ++++- packages/ui/src/makerkit/page.tsx | 4 +- public/locales/en/analysis-results.json | 3 +- public/locales/et/analysis-results.json | 3 +- public/locales/ru/analysis-results.json | 3 +- styles/globals.css | 4 +- 7 files changed, 38 insertions(+), 31 deletions(-) diff --git a/app/home/(user)/(dashboard)/analysis-results/[id]/page.tsx b/app/home/(user)/(dashboard)/analysis-results/[id]/page.tsx index 30da3bb..fa70adf 100644 --- a/app/home/(user)/(dashboard)/analysis-results/[id]/page.tsx +++ b/app/home/(user)/(dashboard)/analysis-results/[id]/page.tsx @@ -54,46 +54,44 @@ export default async function AnalysisResultsPage({ } const orderedAnalysisElements = analysisResponse.orderedAnalysisElements; + const hasOrderedAnalysisElements = orderedAnalysisElements.length > 0; + const isPartialStatus = analysisResponse.order.status === 'PARTIAL_ANALYSIS_RESPONSE'; return ( <> - - -
-
-

- -

-

- {orderedAnalysisElements.length > 0 ? ( - - ) : ( - - )} -

-
+ } + description={hasOrderedAnalysisElements ? ( + isPartialStatus + ? + : + ) : ( + + )} + > +
+
+
-

+

-
-
- +
+
+ -
+
{analysisResponse?.summary?.value && (
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 938d6bb..51d1c34 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 @@ -47,7 +47,7 @@ const Level = ({ )} {color === 'success' && typeof normRangeText === 'string' && ( -

{normRangeText} @@ -179,7 +179,13 @@ const AnalysisLevelBar = ({ }, [isValueBelowLower, isValueAboveUpper, isValueInNormalRange, arrowLocation, normRangeText, isNormal, isWarning, isCritical]); return ( -

+
diff --git a/packages/ui/src/makerkit/page.tsx b/packages/ui/src/makerkit/page.tsx index aaf48ad..a22565a 100644 --- a/packages/ui/src/makerkit/page.tsx +++ b/packages/ui/src/makerkit/page.tsx @@ -158,11 +158,11 @@ export function PageHeader({ return (
-
+
{title} diff --git a/public/locales/en/analysis-results.json b/public/locales/en/analysis-results.json index 02a2195..60084f9 100644 --- a/public/locales/en/analysis-results.json +++ b/public/locales/en/analysis-results.json @@ -1,6 +1,7 @@ { "pageTitle": "My analysis results", - "description": "All analysis results will appear here within 1-3 business days after they have been done.", + "description": "", + "descriptionPartial": "All analysis results will appear here within 1-3 business days after they have been done.", "descriptionEmpty": "If you've already done your analysis, your results will appear here soon.", "orderNewAnalysis": "Order new analyses", "waitingForResults": "Waiting for results", diff --git a/public/locales/et/analysis-results.json b/public/locales/et/analysis-results.json index 1fabbf8..bce0958 100644 --- a/public/locales/et/analysis-results.json +++ b/public/locales/et/analysis-results.json @@ -1,6 +1,7 @@ { "pageTitle": "Minu analüüside vastused", - "description": "Kõikide analüüside tulemused ilmuvad 1-3 tööpäeva jooksul peale nende andmist.", + "description": "", + "descriptionPartial": "Kõikide analüüside tulemused ilmuvad 1-3 tööpäeva jooksul peale nende andmist.", "descriptionEmpty": "Kui oled juba käinud analüüse andmas, siis varsti jõuavad siia sinu analüüside vastused.", "orderNewAnalysis": "Telli uued analüüsid", "waitingForResults": "Tulemuse ootel", diff --git a/public/locales/ru/analysis-results.json b/public/locales/ru/analysis-results.json index c79497e..27e6988 100644 --- a/public/locales/ru/analysis-results.json +++ b/public/locales/ru/analysis-results.json @@ -1,6 +1,7 @@ { "pageTitle": "Мои результаты анализов", - "description": "Все результаты анализов появляются в течение 1-3 рабочих дней после их сдачи.", + "description": "", + "descriptionPartial": "Все результаты анализов появляются в течение 1-3 рабочих дней после их сдачи.", "descriptionEmpty": "Если вы уже сдали анализы, то вскоре здесь появятся ваши результаты.", "orderNewAnalysis": "Заказать новые анализы", "waitingForResults": "Ожидание результатов", diff --git a/styles/globals.css b/styles/globals.css index 45f8195..b3a2fdd 100644 --- a/styles/globals.css +++ b/styles/globals.css @@ -75,11 +75,11 @@ } h5 { - @apply text-base; + @apply text-lg; } h6 { - @apply text-lg; + @apply text-base; } .lucide { From ffd8cc1b3f9d52dde40089c18544ba81aa98c82e Mon Sep 17 00:00:00 2001 From: Karli Date: Fri, 19 Sep 2025 15:27:52 +0300 Subject: [PATCH 11/11] feat(MED-168): mark order as completed if more than expected response elements come from post --- lib/services/medipost/medipostPrivateMessage.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/services/medipost/medipostPrivateMessage.service.ts b/lib/services/medipost/medipostPrivateMessage.service.ts index 775d5bc..ef3e9ac 100644 --- a/lib/services/medipost/medipostPrivateMessage.service.ts +++ b/lib/services/medipost/medipostPrivateMessage.service.ts @@ -196,7 +196,7 @@ async function hasAllAnalysisResponseElements({ }) { const allOrderResponseElements = await getExistingAnalysisResponseElements({ analysisResponseId }); const expectedOrderResponseElements = order.analysis_element_ids?.length ?? 0; - return allOrderResponseElements.length === expectedOrderResponseElements; + return allOrderResponseElements.length >= expectedOrderResponseElements; } export async function syncPrivateMessage({