MED-198: add notification for new analysis result

This commit is contained in:
Danel Kungla
2025-10-08 16:32:19 +03:00
parent 3a8d73e742
commit 8386e541cb
16 changed files with 126 additions and 16 deletions

View File

@@ -3,6 +3,9 @@ import React from 'react';
import Link from 'next/link';
import { redirect } from 'next/navigation';
import { createNotificationsApi } from '@/packages/features/notifications/src/server/api';
import { getSupabaseServerClient } from '@/packages/supabase/src/clients/server-client';
import { ButtonTooltip } from '@kit/shared/components/ui/button-tooltip';
import { pathsConfig } from '@kit/shared/config';
import { Button } from '@kit/ui/button';
@@ -25,7 +28,9 @@ export default async function AnalysisResultsPage({
id: string;
}>;
}) {
const supabaseClient = getSupabaseServerClient();
const { id: analysisOrderId } = await params;
const notificationsApi = createNotificationsApi(supabaseClient);
const [{ account }, analysisResponse] = await Promise.all([
loadCurrentUserAccount(),
@@ -41,6 +46,11 @@ export default async function AnalysisResultsPage({
action: PageViewAction.VIEW_ANALYSIS_RESULTS,
});
await notificationsApi.dismissNotification(
`/home/analysis-results/${analysisOrderId}`,
'link',
);
if (!analysisResponse) {
return (
<>

View File

@@ -90,6 +90,14 @@ async function OrdersPage() {
),
);
if (
medusaOrderItemsAnalysisPackages.length === 0 &&
medusaOrderItemsOther.length === 0 &&
medusaOrderItemsTtoServices.length === 0
) {
return null;
}
return (
<React.Fragment key={medusaOrder.id}>
<Divider className="my-6" />

View File

@@ -32,6 +32,7 @@ import { Trans } from '@kit/ui/trans';
// home imports
import type { UserWorkspace } from '../_lib/server/load-user-workspace';
import { UserNotifications } from './user-notifications';
const PERSONAL_ACCOUNT_SLUG = 'personal';
@@ -90,7 +91,7 @@ export function HomeMobileNavigation(props: {
return (
<DropdownMenu>
<div className="flex justify-between gap-4">
<div className="flex justify-between gap-3">
<Link href={pathsConfig.app.cart}>
<Button
variant="ghost"
@@ -108,6 +109,9 @@ export function HomeMobileNavigation(props: {
)}
</Button>
</Link>
<UserNotifications userId={user.id} />
<DropdownMenuTrigger>
<Menu className="h-6 w-6" />
</DropdownMenuTrigger>

View File

@@ -61,6 +61,7 @@ export default function OrderBlock({
id: analysisOrder.id,
status: analysisOrder.status,
}}
isPackage
/>
)}
{itemsTtoService && (

View File

@@ -32,11 +32,13 @@ export default function OrderItemsTable({
title,
order,
type = 'analysisOrder',
isPackage = false,
}: {
items: StoreOrderLineItem[];
title: string;
order: Order;
type?: OrderItemType;
isPackage?: boolean;
}) {
const router = useRouter();
const [isConfirmOpen, setIsConfirmOpen] = useState(false);
@@ -100,9 +102,15 @@ export default function OrderItemsTable({
</TableCell>
)}
<TableCell className="min-w-[180px] px-6">
<Trans
i18nKey={`orders:status.${type}.${order?.status ?? 'CONFIRMED'}`}
/>
{isPackage ? (
<Trans
i18nKey={`orders:status.analysisPackageOrder.${order?.status ?? 'CONFIRMED'}`}
/>
) : (
<Trans
i18nKey={`orders:status.${type}.${order?.status ?? 'CONFIRMED'}`}
/>
)}
</TableCell>
<TableCell className="px-6 text-right">

View File

@@ -1,8 +1,6 @@
'use server';
import { AccountWithParams } from '@/packages/features/accounts/src/types/accounts';
import { createNotificationsApi } from '@/packages/features/notifications/src/server/api';
import { getSupabaseServerAdminClient } from '@/packages/supabase/src/clients/server-admin-client';
import { listProductTypes } from '@lib/data';
import { initiateMultiPaymentSession, placeOrder } from '@lib/data/cart';
import type { StoreCart, StoreOrder } from '@medusajs/types';
@@ -327,7 +325,6 @@ const sendEmail = async ({
partnerLocationName: string;
language: string;
}) => {
const client = getSupabaseServerAdminClient();
try {
const { renderSynlabAnalysisPackageEmail } = await import(
'@kit/email-templates'
@@ -353,10 +350,6 @@ const sendEmail = async ({
.catch((error) => {
throw new Error(`Failed to send email, message=${error}`);
});
await createNotificationsApi(client).createNotification({
account_id: account.id,
body: html,
});
} catch (error) {
throw new Error(`Failed to send email, message=${error}`);
}

View File

@@ -3,7 +3,9 @@
import type { PostgrestError } from '@supabase/supabase-js';
import { GetMessageListResponse, MedipostAction } from '@/lib/types/medipost';
import { createNotificationsApi } from '@/packages/features/notifications/src/server/api';
import { createUserAnalysesApi } from '@/packages/features/user-analyses/src/server/api';
import { pathsConfig } from '@/packages/shared/src/config';
import { AnalysisOrderStatus } from '@/packages/shared/src/types/medipost-analysis';
import type {
MedipostOrderResponse,
@@ -16,6 +18,7 @@ import axios from 'axios';
import { toArray } from '@kit/shared/utils';
import { Tables } from '@kit/supabase/database';
import { createI18nServerInstance } from '~/lib/i18n/i18n.server';
import type { AnalysisResponseElement } from '~/lib/types/analysis-response-element';
import type { AnalysisOrder } from '~/lib/types/order';
@@ -268,6 +271,7 @@ export async function syncPrivateMessage({
order: Tables<{ schema: 'medreport' }, 'analysis_orders'>;
}) {
const supabase = getSupabaseServerAdminClient();
const { t } = await createI18nServerInstance();
const orderStatus = AnalysisOrderStatus[TellimuseOlek];
@@ -300,6 +304,7 @@ export async function syncPrivateMessage({
log,
});
let newElementsAdded = 0;
for (const element of newElements) {
try {
await upsertAnalysisResponseElement({
@@ -308,6 +313,7 @@ export async function syncPrivateMessage({
analysis_response_id: analysisResponseId,
},
});
newElementsAdded++;
} catch (e) {
log(
`Failed to create order response element for response id ${analysisResponseId}, element id '${element.analysis_element_original_id}' (order id: ${order.id})`,
@@ -316,6 +322,16 @@ export async function syncPrivateMessage({
}
}
log(`Added ${newElementsAdded} new elements`);
if (newElementsAdded !== 0) {
await createNotificationsApi(supabase).createNotification({
account_id: analysisOrder.user_id,
body: t('analysis-results:notification.body'),
link: `${pathsConfig.app.analysisResults}/${order.id}`,
});
}
return (await hasAllAnalysisResponseElements({ analysisResponseId, order }))
? { isCompleted: orderStatus === 'COMPLETED' }
: { isPartial: true };
@@ -371,8 +387,13 @@ export async function readPrivateMessageResponse({
const hasInvalidOrderId = isNaN(analysisOrderId);
if (hasInvalidOrderId || !messageResponse || !patientPersonalCode) {
console.log({
privateMessageContent,
saadetis: privateMessageContent?.Saadetis,
messageResponse,
});
console.error(
`Invalid order id or message response or patient personal code, medipostExternalOrderId=${medipostExternalOrderId}, privateMessageId=${privateMessageId}`,
`Invalid !order id or message response or patient personal code, medipostExternalOrderId=${medipostExternalOrderId}, privateMessageId=${privateMessageId}`,
);
await upsertMedipostActionLog({
action: 'sync_analysis_results_from_medipost',

View File

@@ -62,7 +62,10 @@ export const listOrders = async (
credentials: 'include',
})
.then(({ orders }) => orders)
.catch((err) => medusaError(err));
.catch((err) => {
console.error('Error receiving orders', { err });
return medusaError(err);
});
};
export const listOrdersByIds = async (ids: string[]) => {

View File

@@ -50,4 +50,13 @@ class NotificationsApi {
createNotification(params: Notification['Insert']) {
return this.service.createNotification(params);
}
/**
* @name createNotification
* @description Create a new notification in the database
* @param params
*/
dismissNotification(eqValue: string, eqColumn?: string) {
return this.service.dismissNotification(eqColumn, eqValue);
}
}

View File

@@ -29,4 +29,21 @@ class NotificationsService {
throw error;
}
}
async dismissNotification(eqColumn = 'id', eqValue: string) {
const logger = await getLogger();
const { error } = await this.client
.schema('medreport')
.from('notifications')
.update({ dismissed: true })
.eq(eqColumn, eqValue);
if (error) {
logger.error(
{ eqColumn, eqValue },
`Could not dismiss notification: ${error.message}`,
);
throw error;
}
}
}

View File

@@ -21,5 +21,8 @@
}
},
"orderTitle": "Order number {{orderNumber}}",
"view": "View results"
"view": "View results",
"notification": {
"body": "You have new analysis results"
}
}

View File

@@ -17,6 +17,15 @@
"REJECTED": "Rejected",
"CANCELLED": "Cancelled",
"analysisOrder": {
"QUEUED": "Queued",
"PROCESSING": "Sent to Synlab",
"PARTIAL_ANALYSIS_RESPONSE": "Partial results",
"FULL_ANALYSIS_RESPONSE": "All results received",
"COMPLETED": "Confirmed",
"REJECTED": "Rejected",
"CANCELLED": "Cancelled"
},
"analysisPackageOrder": {
"QUEUED": "Queued",
"PROCESSING": "Sent to Synlab",
"PARTIAL_ANALYSIS_RESPONSE": "Partial results",

View File

@@ -21,5 +21,8 @@
}
},
"orderTitle": "Tellimus {{orderNumber}}",
"view": "Vaata tulemusi"
"view": "Vaata tulemusi",
"notification": {
"body": "Teil on valmis uued analüüsi tulemused"
}
}

View File

@@ -19,6 +19,15 @@
"REJECTED": "Tagastatud",
"CANCELLED": "Tühistatud",
"analysisOrder": {
"QUEUED": "Esitatud",
"PROCESSING": "Synlabile edastatud",
"PARTIAL_ANALYSIS_RESPONSE": "Osalised tulemused",
"FULL_ANALYSIS_RESPONSE": "Kõik tulemused käes",
"COMPLETED": "Kinnitatud",
"REJECTED": "Tagastatud",
"CANCELLED": "Tühistatud"
},
"analysisPackageOrder": {
"QUEUED": "Esitatud",
"PROCESSING": "Synlabile edastatud",
"PARTIAL_ANALYSIS_RESPONSE": "Osalised tulemused",

View File

@@ -20,5 +20,8 @@
"isNotWithinNorm": "Не в норме"
}
},
"orderTitle": "Заказ {{orderNumber}}"
"orderTitle": "Заказ {{orderNumber}}",
"notification": {
"body": "Teil on valmis uued analüüsi tulemused"
}
}

View File

@@ -17,6 +17,15 @@
"REJECTED": "Отклонено",
"CANCELLED": "Отменено",
"analysisOrder": {
"QUEUED": "Отправлено",
"PROCESSING": "Отправлено в Synlab",
"PARTIAL_ANALYSIS_RESPONSE": "Частичные результаты",
"FULL_ANALYSIS_RESPONSE": "Все результаты получены",
"COMPLETED": "Подтверждено",
"REJECTED": "Отклонено",
"CANCELLED": "Отменено"
},
"analysisPackageOrder": {
"QUEUED": "Отправлено",
"PROCESSING": "Отправлено в Synlab",
"PARTIAL_ANALYSIS_RESPONSE": "Частичные результаты",