diff --git a/app/doctor/_components/doctor-dashboard.tsx b/app/doctor/_components/doctor-dashboard.tsx index 4f265c2..d748b93 100644 --- a/app/doctor/_components/doctor-dashboard.tsx +++ b/app/doctor/_components/doctor-dashboard.tsx @@ -9,7 +9,7 @@ import { import ResultsTableWrapper from './results-table-wrapper'; -export default function Dashboard() { +export default function DoctorDashboard() { return ( <> diff --git a/app/doctor/analysis/[id]/page.tsx b/app/doctor/analysis/[id]/page.tsx index ad7b311..8a68b25 100644 --- a/app/doctor/analysis/[id]/page.tsx +++ b/app/doctor/analysis/[id]/page.tsx @@ -36,7 +36,7 @@ async function AnalysisPage({ return ( <> - + - + - + - + - - + + ); diff --git a/app/home/(user)/(dashboard)/order/[orderId]/confirmed/order-confirmed-loading-wrapper.tsx b/app/home/(user)/(dashboard)/order/[orderId]/confirmed/order-confirmed-loading-wrapper.tsx index 7e99300..b34030a 100644 --- a/app/home/(user)/(dashboard)/order/[orderId]/confirmed/order-confirmed-loading-wrapper.tsx +++ b/app/home/(user)/(dashboard)/order/[orderId]/confirmed/order-confirmed-loading-wrapper.tsx @@ -13,7 +13,7 @@ import { GlobalLoader } from '@kit/ui/makerkit/global-loader'; import { PageBody, PageHeader } from '@kit/ui/page'; import { Trans } from '@kit/ui/trans'; -import { AnalysisOrder } from '~/lib/types/analysis-order'; +import { AnalysisOrder } from '~/lib/types/order'; function OrderConfirmedLoadingWrapper({ medusaOrder: initialMedusaOrder, diff --git a/packages/features/doctor/src/lib/server/actions/doctor-server-actions.ts b/packages/features/doctor/src/lib/server/actions/doctor-server-actions.ts index 610b70a..0f49315 100644 --- a/packages/features/doctor/src/lib/server/actions/doctor-server-actions.ts +++ b/packages/features/doctor/src/lib/server/actions/doctor-server-actions.ts @@ -42,14 +42,14 @@ export const selectJobAction = doctorAction( revalidateDoctorAnalysis(); return { success: true }; - } catch (e) { - logger.error('Failed to select job', e); - if (e instanceof Error) { + } catch (error) { + logger.error({ error }, 'Failed to select job'); + if (error instanceof Error) { revalidateDoctorAnalysis(); return { success: false, reason: - e['message'] === ErrorReason.JOB_ASSIGNED + error['message'] === ErrorReason.JOB_ASSIGNED ? ErrorReason.JOB_ASSIGNED : ErrorReason.UNKNOWN, }; @@ -133,16 +133,16 @@ export const giveFeedbackAction = doctorAction( } return { success: true }; - } catch (e: any) { + } catch (error) { if (isCompleted) { await createNotificationLog({ action: NotificationAction.PATIENT_DOCTOR_FEEDBACK_RECEIVED, status: 'FAIL', - comment: e?.message, + comment: error instanceof Error ? error.message : '', relatedRecordId: analysisOrderId, }); } - logger.error('Failed to give feedback', e); + logger.error({ error }, 'Failed to give feedback'); return { success: false, reason: ErrorReason.UNKNOWN }; } }, diff --git a/packages/features/doctor/src/lib/server/actions/table-data-fetching-actions.ts b/packages/features/doctor/src/lib/server/actions/table-data-fetching-actions.ts index 8802874..8741e9b 100644 --- a/packages/features/doctor/src/lib/server/actions/table-data-fetching-actions.ts +++ b/packages/features/doctor/src/lib/server/actions/table-data-fetching-actions.ts @@ -62,7 +62,7 @@ export const getOpenResponsesAction = doctorAction( const data = await getOpenResponses({ page, pageSize }); return { success: true, data }; } catch (error) { - logger.error(`Error fetching open analysis response jobs`, error); + logger.error({ error }, `Error fetching open analysis response jobs`); return { success: false, error: 'Failed to fetch data from the server.' }; } }, diff --git a/packages/features/doctor/src/lib/server/schema/doctor-analysis-detail-view.schema.ts b/packages/features/doctor/src/lib/server/schema/doctor-analysis-detail-view.schema.ts index 439e047..b4c1f16 100644 --- a/packages/features/doctor/src/lib/server/schema/doctor-analysis-detail-view.schema.ts +++ b/packages/features/doctor/src/lib/server/schema/doctor-analysis-detail-view.schema.ts @@ -47,7 +47,7 @@ export type Patient = z.infer; export const AnalysisResponsesSchema = z.object({ user_id: z.string(), - analysis_order_id: AnalysisOrderIdSchema, + analysis_order: AnalysisOrderIdSchema, }); export type AnalysisResponses = z.infer; @@ -56,8 +56,8 @@ export const AnalysisResponseSchema = z.object({ analysis_response_id: z.number(), analysis_element_original_id: z.string(), unit: z.string().nullable(), - response_value: z.number(), - response_time: z.string(), + response_value: z.number().nullable(), + response_time: z.string().nullable(), norm_upper: z.number().nullable(), norm_upper_included: z.boolean().nullable(), norm_lower: z.number().nullable(), @@ -74,8 +74,8 @@ export const AnalysisResponseSchema = z.object({ analysis_response_id: z.number(), analysis_element_original_id: z.string(), unit: z.string().nullable(), - response_value: z.number(), - response_time: z.string(), + response_value: z.number().nullable(), + response_time: z.string().nullable(), norm_upper: z.number().nullable(), norm_upper_included: z.boolean().nullable(), norm_lower: z.number().nullable(), @@ -92,7 +92,7 @@ export const AnalysisResponseSchema = z.object({ export type AnalysisResponse = z.infer; export const AnalysisResultDetailsSchema = z.object({ - analysisResponse: z.array(AnalysisResponseSchema), + analysisResponse: z.array(AnalysisResponseSchema).nullable(), order: OrderSchema, doctorFeedback: DoctorFeedbackSchema, patient: PatientSchema, diff --git a/packages/features/doctor/src/lib/server/schema/doctor-analysis.schema.ts b/packages/features/doctor/src/lib/server/schema/doctor-analysis.schema.ts index efda780..ec0de70 100644 --- a/packages/features/doctor/src/lib/server/schema/doctor-analysis.schema.ts +++ b/packages/features/doctor/src/lib/server/schema/doctor-analysis.schema.ts @@ -47,8 +47,8 @@ export const ElementSchema = z.object({ analysis_response_id: z.number(), analysis_element_original_id: z.string(), unit: z.string().nullable(), - response_value: z.number(), - response_time: z.string(), + response_value: z.number().nullable(), + response_time: z.string().nullable(), norm_upper: z.number().nullable(), norm_upper_included: z.boolean().nullable(), norm_lower: z.number().nullable(), diff --git a/packages/features/doctor/src/lib/server/services/doctor-analysis.service.ts b/packages/features/doctor/src/lib/server/services/doctor-analysis.service.ts index 7033567..8068a32 100644 --- a/packages/features/doctor/src/lib/server/services/doctor-analysis.service.ts +++ b/packages/features/doctor/src/lib/server/services/doctor-analysis.service.ts @@ -1,8 +1,10 @@ import 'server-only'; +import { listOrdersByIds, retrieveOrder } from '@lib/data/orders'; import { isBefore } from 'date-fns'; import { renderDoctorSummaryReceivedEmail } from '@kit/email-templates'; +import { getLogger } from '@kit/shared/logger'; import { getFullName } from '@kit/shared/utils'; import { getSupabaseServerClient } from '@kit/supabase/server-client'; import { createUserAnalysesApi } from '@kit/user-analyses/api'; @@ -31,7 +33,7 @@ async function enrichAnalysisData(analysisResponses?: AnalysisResponseBase[]) { const [ { data: doctorFeedbackItems }, - { data: medusaOrderItems }, + medusaOrders, { data: analysisResponseElements }, { data: accounts }, ] = await Promise.all([ @@ -43,11 +45,7 @@ async function enrichAnalysisData(analysisResponses?: AnalysisResponseBase[]) { 'analysis_order_id', analysisResponses.map((r) => r.analysis_order_id.id), ), - supabase - .schema('public') - .from('order_item') - .select('order_id, item_id(product_title, product_type)') - .in('order_id', medusaOrderIds), + listOrdersByIds(medusaOrderIds), supabase .schema('medreport') .from('analysis_response_elements') @@ -56,7 +54,7 @@ async function enrichAnalysisData(analysisResponses?: AnalysisResponseBase[]) { supabase .schema('medreport') .from('accounts') - .select('name, last_name, id, primary_owner_user_id, preferred_locale') + .select('name,last_name,id,primary_owner_user_id,preferred_locale,slug') .in('primary_owner_user_id', userIds), ]); @@ -69,7 +67,7 @@ async function enrichAnalysisData(analysisResponses?: AnalysisResponseBase[]) { ? await supabase .schema('medreport') .from('accounts') - .select('name, last_name, id, primary_owner_user_id, preferred_locale') + .select('name,last_name,id,primary_owner_user_id,preferred_locale,slug') .in('primary_owner_user_id', doctorUserIds) : { data: [] }; @@ -82,21 +80,26 @@ async function enrichAnalysisData(analysisResponses?: AnalysisResponseBase[]) { ) || []; const firstSampleGivenAt = responseElements.length - ? responseElements.reduce((earliest, current) => - new Date(current.response_time) < new Date(earliest.response_time) - ? current - : earliest, - )?.response_time + ? responseElements.reduce((earliest, current) => { + if (current.response_time && earliest.response_time) { + if ( + new Date(current.response_time) < new Date(earliest.response_time) + ) { + return current; + } + return earliest; + } + return current; + }).response_time : null; - const medusaOrder = medusaOrderItems?.find( - ({ order_id }) => - order_id === analysisResponse.analysis_order_id.medusa_order_id, + const medusaOrder = medusaOrders?.find( + ({ id }) => id === analysisResponse.analysis_order_id.medusa_order_id, ); const patientAccount = allAccounts?.find( - ({ primary_owner_user_id }) => - analysisResponse.user_id === primary_owner_user_id, + ({ primary_owner_user_id, slug }) => + analysisResponse.user_id === primary_owner_user_id && !slug, ); const feedback = doctorFeedbackItems?.find( @@ -110,9 +113,10 @@ async function enrichAnalysisData(analysisResponses?: AnalysisResponseBase[]) { ); const order = { - title: medusaOrder?.item_id.product_title, + title: medusaOrder?.items?.[0]?.product_title, isPackage: - medusaOrder?.item_id.product_type?.toLowerCase() === 'analysis package', + medusaOrder?.items?.[0]?.product_type?.toLowerCase() === + 'analysis package', analysisOrderId: analysisResponse.analysis_order_id.id, status: analysisResponse.order_status, }; @@ -177,6 +181,7 @@ export async function getUserInProgressResponses({ `, { count: 'exact' }, ) + .neq('status', 'ON_HOLD') .in('analysis_order_id', analysisOrderIds) .range(offset, offset + pageSize - 1) .order('created_at', { ascending: false }); @@ -280,6 +285,7 @@ export async function getOpenResponses({ `, { count: 'exact' }, ) + .neq('order_status', 'ON_HOLD') .order('created_at', { ascending: false }); if (assignedIds.length > 0) { @@ -365,47 +371,50 @@ export async function getOtherResponses({ export async function getAnalysisResultsForDoctor( analysisResponseId: number, ): Promise { + const logger = await getLogger(); + const ctx = { + action: 'get-analysis-results-for-doctor', + analysisResponseId, + }; const supabase = getSupabaseServerClient(); - const { data: analysisResponseElements, error } = await supabase - .schema('medreport') - .from(`analysis_response_elements`) - .select( - `*, - analysis_responses(user_id, analysis_order_id(id,medusa_order_id, analysis_element_ids))`, - ) - .eq('analysis_response_id', analysisResponseId); + const { data: analysisResponsesData, error: analysisResponsesError } = + await supabase + .schema('medreport') + .from(`analysis_response_elements`) + .select( + `*, + analysis_responses(user_id, analysis_order:analysis_order_id(id,medusa_order_id, analysis_element_ids))`, + ) + .eq('analysis_response_id', analysisResponseId); - if (error) { - throw new Error('Something went wrong.'); + if (analysisResponsesError) { + logger.error( + { ...ctx, analysisResponsesError }, + 'No order response for this analysis response id', + ); + throw new Error('No order for this analysis id'); } - - const firstAnalysisResponse = analysisResponseElements?.[0]; + const firstAnalysisResponse = analysisResponsesData?.[0]; const userId = firstAnalysisResponse?.analysis_responses.user_id; const medusaOrderId = - firstAnalysisResponse?.analysis_responses?.analysis_order_id - ?.medusa_order_id; + firstAnalysisResponse?.analysis_responses?.analysis_order?.medusa_order_id; - if (!analysisResponseElements?.length || !userId || !medusaOrderId) { + if (!analysisResponsesData?.length || !userId || !medusaOrderId) { throw new Error('Failed to retrieve full analysis data.'); } - const responseElementAnalysisElementOriginalIds = - analysisResponseElements.map( - ({ analysis_element_original_id }) => analysis_element_original_id, - ); + const responseElementAnalysisElementOriginalIds = analysisResponsesData.map( + ({ analysis_element_original_id }) => analysis_element_original_id, + ); const [ - { data: medusaOrderItems, error: medusaOrderError }, + medusaOrder, { data: accountWithParams, error: accountError }, { data: doctorFeedback, error: feedbackError }, { data: previousAnalyses, error: previousAnalysesError }, ] = await Promise.all([ - supabase - .schema('public') - .from('order_item') - .select(`order_id, item_id(product_title, product_type)`) - .eq('order_id', medusaOrderId), + retrieveOrder(medusaOrderId, true, '*items'), supabase .schema('medreport') .from('accounts') @@ -422,7 +431,7 @@ export async function getAnalysisResultsForDoctor( .select(`*`) .eq( 'analysis_order_id', - firstAnalysisResponse.analysis_responses.analysis_order_id.id, + firstAnalysisResponse.analysis_responses.analysis_order.id, ) .limit(1), supabase @@ -452,12 +461,7 @@ export async function getAnalysisResultsForDoctor( .order('response_time'), ]); - if ( - medusaOrderError || - accountError || - feedbackError || - previousAnalysesError - ) { + if (!medusaOrder || accountError || feedbackError || previousAnalysesError) { throw new Error('Something went wrong.'); } @@ -478,15 +482,20 @@ export async function getAnalysisResultsForDoctor( } = accountWithParams[0]; const analysisResponseElementsWithPreviousData = []; - for (const analysisResponseElement of analysisResponseElements) { + for (const analysisResponseElement of analysisResponsesData) { const latestPreviousAnalysis = previousAnalyses.find( - ({ analysis_element_original_id, response_time }) => - analysis_element_original_id === - analysisResponseElement.analysis_element_original_id && - isBefore( - new Date(response_time), - new Date(analysisResponseElement.response_time), - ), + ({ analysis_element_original_id, response_time }) => { + if (response_time && analysisResponseElement.response_time) { + return ( + analysis_element_original_id === + analysisResponseElement.analysis_element_original_id && + isBefore( + new Date(response_time), + new Date(analysisResponseElement.response_time), + ) + ); + } + }, ); analysisResponseElementsWithPreviousData.push({ ...analysisResponseElement, @@ -497,12 +506,12 @@ export async function getAnalysisResultsForDoctor( return { analysisResponse: analysisResponseElementsWithPreviousData, order: { - title: medusaOrderItems?.[0]?.item_id.product_title ?? '-', + title: medusaOrder.items?.[0]?.product_title ?? '-', isPackage: - medusaOrderItems?.[0]?.item_id.product_type?.toLowerCase() === + medusaOrder.items?.[0]?.product_type?.toLowerCase() === 'analysis package', analysisOrderId: - firstAnalysisResponse.analysis_responses.analysis_order_id.id, + firstAnalysisResponse.analysis_responses.analysis_order.id, }, doctorFeedback: doctorFeedback?.[0], patient: { @@ -525,8 +534,15 @@ export async function selectJob(analysisOrderId: number, userId: string) { const { data: { user }, } = await supabase.auth.getUser(); + const logger = await getLogger(); + const ctx = { + action: 'select-job', + patientUserId: userId, + currentUserId: user?.id, + }; if (!user?.id) { + logger.error(ctx, 'No user logged in'); throw new Error('No user logged in.'); } @@ -541,6 +557,7 @@ export async function selectJob(analysisOrderId: number, userId: string) { const jobAssignedToUserId = existingFeedback?.[0]?.doctor_user_id; if (!!jobAssignedToUserId && jobAssignedToUserId !== user.id) { + logger.error(ctx, 'Job assigned to a different user'); throw new Error(ErrorReason.JOB_ASSIGNED); } @@ -557,6 +574,10 @@ export async function selectJob(analysisOrderId: number, userId: string) { ); if (error || existingFeedbackError) { + logger.error( + { ...ctx, error, existingFeedbackError }, + 'Failed updating doctor feedback', + ); throw new Error('Something went wrong'); } diff --git a/packages/features/doctor/tsconfig.json b/packages/features/doctor/tsconfig.json index d05f5e7..9ad6dfe 100644 --- a/packages/features/doctor/tsconfig.json +++ b/packages/features/doctor/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "@kit/tsconfig/base.json", + "extends": "../../../tsconfig.json", "compilerOptions": { "tsBuildInfoFile": "node_modules/.cache/tsbuildinfo.json" }, diff --git a/packages/features/medusa-storefront/src/lib/data/orders.ts b/packages/features/medusa-storefront/src/lib/data/orders.ts index 464a747..48b3638 100644 --- a/packages/features/medusa-storefront/src/lib/data/orders.ts +++ b/packages/features/medusa-storefront/src/lib/data/orders.ts @@ -6,7 +6,11 @@ import { HttpTypes } from '@medusajs/types'; import { getAuthHeaders, getCacheOptions } from './cookies'; -export const retrieveOrder = async (id: string, allowCache = true) => { +export const retrieveOrder = async ( + id: string, + allowCache = true, + fields = '*payment_collections.payments,*items,*items.metadata,*items.variant,*items.product', +) => { const headers = { ...(await getAuthHeaders()), }; @@ -19,8 +23,7 @@ export const retrieveOrder = async (id: string, allowCache = true) => { .fetch(`/store/orders/${id}`, { method: 'GET', query: { - fields: - '*payment_collections.payments,*items,*items.metadata,*items.variant,*items.product', + fields, }, headers, next, @@ -62,6 +65,14 @@ export const listOrders = async ( .catch((err) => medusaError(err)); }; +export const listOrdersByIds = async (ids: string[]) => { + try { + return Promise.all(ids.map((id) => retrieveOrder(id))); + } catch (error) { + console.error('response Error', error); + } +}; + export const createTransferRequest = async ( formData: FormData, ): Promise<{ diff --git a/packages/features/notifications/src/hooks/use-notifications-stream.ts b/packages/features/notifications/src/hooks/use-notifications-stream.ts index 6530716..0923d96 100644 --- a/packages/features/notifications/src/hooks/use-notifications-stream.ts +++ b/packages/features/notifications/src/hooks/use-notifications-stream.ts @@ -31,7 +31,7 @@ export function useNotificationsStream(params: { 'postgres_changes', { event: 'INSERT', - schema: 'public', + schema: 'medreport', filter: `account_id=in.(${params.accountIds.join(', ')})`, table: 'notifications', },