MED-89: add analysis view with doctor summary (#68)
* add analysis view with doctor summary * remove console.log, also return null if analysis data missing * replace orders table eye with button
This commit is contained in:
107
app/home/(user)/(dashboard)/analysis-results/[id]/page.tsx
Normal file
107
app/home/(user)/(dashboard)/analysis-results/[id]/page.tsx
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
import Link from 'next/link';
|
||||||
|
|
||||||
|
import { ButtonTooltip } from '@kit/shared/components/ui/button-tooltip';
|
||||||
|
import { pathsConfig } from '@kit/shared/config';
|
||||||
|
import { Button } from '@kit/ui/button';
|
||||||
|
import { PageBody, PageHeader } from '@kit/ui/page';
|
||||||
|
import { Trans } from '@kit/ui/trans';
|
||||||
|
|
||||||
|
import { loadCurrentUserAccount } from '~/home/(user)/_lib/server/load-user-account';
|
||||||
|
import { loadUserAnalysis } from '~/home/(user)/_lib/server/load-user-analysis';
|
||||||
|
import {
|
||||||
|
PageViewAction,
|
||||||
|
createPageViewLog,
|
||||||
|
} from '~/lib/services/audit/pageView.service';
|
||||||
|
|
||||||
|
import Analysis from '../_components/analysis';
|
||||||
|
|
||||||
|
export default async function AnalysisResultsPage({
|
||||||
|
params,
|
||||||
|
}: {
|
||||||
|
params: Promise<{
|
||||||
|
id: string;
|
||||||
|
}>;
|
||||||
|
}) {
|
||||||
|
const account = await loadCurrentUserAccount();
|
||||||
|
|
||||||
|
const { id: analysisResponseId } = await params;
|
||||||
|
|
||||||
|
const analysisResponse = await loadUserAnalysis(Number(analysisResponseId));
|
||||||
|
|
||||||
|
if (!account?.id || !analysisResponse) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
await createPageViewLog({
|
||||||
|
accountId: account.id,
|
||||||
|
action: PageViewAction.VIEW_ANALYSIS_RESULTS,
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<PageHeader />
|
||||||
|
<PageBody className="gap-4">
|
||||||
|
<div className="mt-8 flex flex-col justify-between gap-4 sm:flex-row sm:items-center sm:gap-0">
|
||||||
|
<div>
|
||||||
|
<h4>
|
||||||
|
<Trans i18nKey="analysis-results:pageTitle" />
|
||||||
|
</h4>
|
||||||
|
<p className="text-muted-foreground text-sm">
|
||||||
|
{analysisResponse?.elements &&
|
||||||
|
analysisResponse.elements?.length > 0 ? (
|
||||||
|
<Trans i18nKey="analysis-results:description" />
|
||||||
|
) : (
|
||||||
|
<Trans i18nKey="analysis-results:descriptionEmpty" />
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<Button asChild>
|
||||||
|
<Link href={pathsConfig.app.orderAnalysisPackage}>
|
||||||
|
<Trans i18nKey="analysis-results:orderNewAnalysis" />
|
||||||
|
</Link>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col gap-4">
|
||||||
|
<h4>
|
||||||
|
<Trans
|
||||||
|
i18nKey="analysis-results:orderTitle"
|
||||||
|
values={{ orderNumber: analysisResponse.order.medusa_order_id }}
|
||||||
|
/>
|
||||||
|
</h4>
|
||||||
|
<h5>
|
||||||
|
<Trans
|
||||||
|
i18nKey={`orders:status.${analysisResponse.order.status}`}
|
||||||
|
/>
|
||||||
|
<ButtonTooltip
|
||||||
|
content={`${analysisResponse.order.created_at ? new Date(analysisResponse?.order?.created_at).toLocaleString() : ''}`}
|
||||||
|
className="ml-6"
|
||||||
|
/>
|
||||||
|
</h5>
|
||||||
|
</div>
|
||||||
|
{analysisResponse?.summary?.value && (
|
||||||
|
<div>
|
||||||
|
<strong>
|
||||||
|
<Trans i18nKey="account:doctorAnalysisSummary" />
|
||||||
|
</strong>
|
||||||
|
<p>{analysisResponse.summary.value}</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
{analysisResponse.elements ? (
|
||||||
|
analysisResponse.elements.map((element, index) => (
|
||||||
|
<Analysis
|
||||||
|
key={index}
|
||||||
|
analysisElement={{ analysis_name_lab: element.analysis_name }}
|
||||||
|
results={element}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<div className="text-muted-foreground text-sm">
|
||||||
|
<Trans i18nKey="analysis-results:noAnalysisElements" />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</PageBody>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,131 +0,0 @@
|
|||||||
import Link from 'next/link';
|
|
||||||
import { redirect } from 'next/navigation';
|
|
||||||
|
|
||||||
import { loadCurrentUserAccount } from '@/app/home/(user)/_lib/server/load-user-account';
|
|
||||||
import { createI18nServerInstance } from '@/lib/i18n/i18n.server';
|
|
||||||
import { withI18n } from '@/lib/i18n/with-i18n';
|
|
||||||
|
|
||||||
import { Trans } from '@kit/ui/makerkit/trans';
|
|
||||||
import { PageBody } from '@kit/ui/page';
|
|
||||||
import { Button } from '@kit/ui/shadcn/button';
|
|
||||||
|
|
||||||
import { pathsConfig } from '@kit/shared/config';
|
|
||||||
|
|
||||||
import { getAnalysisElements } from '~/lib/services/analysis-element.service';
|
|
||||||
import {
|
|
||||||
PageViewAction,
|
|
||||||
createPageViewLog,
|
|
||||||
} from '~/lib/services/audit/pageView.service';
|
|
||||||
import { AnalysisOrder, getAnalysisOrders } from '~/lib/services/order.service';
|
|
||||||
import { ButtonTooltip } from '@kit/shared/components/ui/button-tooltip';
|
|
||||||
|
|
||||||
import { loadUserAnalysis } from '../../_lib/server/load-user-analysis';
|
|
||||||
import Analysis from './_components/analysis';
|
|
||||||
|
|
||||||
export const generateMetadata = async () => {
|
|
||||||
const i18n = await createI18nServerInstance();
|
|
||||||
const title = i18n.t('analysis-results:pageTitle');
|
|
||||||
|
|
||||||
return {
|
|
||||||
title,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
async function AnalysisResultsPage() {
|
|
||||||
const account = await loadCurrentUserAccount();
|
|
||||||
if (!account) {
|
|
||||||
throw new Error('Account not found');
|
|
||||||
}
|
|
||||||
|
|
||||||
const analysisResponses = await loadUserAnalysis();
|
|
||||||
const analysisResponseElements = analysisResponses?.flatMap(
|
|
||||||
({ elements }) => elements,
|
|
||||||
);
|
|
||||||
|
|
||||||
const analysisOrders = await getAnalysisOrders().catch(() => null);
|
|
||||||
|
|
||||||
if (!analysisOrders) {
|
|
||||||
redirect(pathsConfig.auth.signIn);
|
|
||||||
}
|
|
||||||
|
|
||||||
await createPageViewLog({
|
|
||||||
accountId: account.id,
|
|
||||||
action: PageViewAction.VIEW_ANALYSIS_RESULTS,
|
|
||||||
});
|
|
||||||
|
|
||||||
const getAnalysisElementIds = (analysisOrders: AnalysisOrder[]) => [
|
|
||||||
...new Set(analysisOrders?.flatMap((order) => order.analysis_element_ids).filter(Boolean) as number[]),
|
|
||||||
];
|
|
||||||
|
|
||||||
const analysisElementIds = getAnalysisElementIds(analysisOrders);
|
|
||||||
const analysisElements = await getAnalysisElements({ ids: analysisElementIds });
|
|
||||||
|
|
||||||
return (
|
|
||||||
<PageBody className="gap-4">
|
|
||||||
<div className="mt-8 flex flex-col sm:flex-row sm:items-center justify-between gap-4 sm:gap-0">
|
|
||||||
<div>
|
|
||||||
<h4>
|
|
||||||
<Trans i18nKey="analysis-results:pageTitle" />
|
|
||||||
</h4>
|
|
||||||
<p className="text-muted-foreground text-sm">
|
|
||||||
{analysisResponses && analysisResponses.length > 0 ? (
|
|
||||||
<Trans i18nKey="analysis-results:description" />
|
|
||||||
) : (
|
|
||||||
<Trans i18nKey="analysis-results:descriptionEmpty" />
|
|
||||||
)}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<Button asChild>
|
|
||||||
<Link href={pathsConfig.app.orderAnalysisPackage}>
|
|
||||||
<Trans i18nKey="analysis-results:orderNewAnalysis" />
|
|
||||||
</Link>
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-col gap-8">
|
|
||||||
{analysisOrders.length > 0 && analysisElements.length > 0 ? analysisOrders.map((analysisOrder) => {
|
|
||||||
const analysisResponse = analysisResponses?.find((response) => response.analysis_order_id === analysisOrder.id);
|
|
||||||
const analysisElementIds = getAnalysisElementIds([analysisOrder]);
|
|
||||||
const analysisElementsForOrder = analysisElements.filter((element) => analysisElementIds.includes(element.id));
|
|
||||||
return (
|
|
||||||
<div key={analysisOrder.id} className="flex flex-col gap-4">
|
|
||||||
<h4>
|
|
||||||
<Trans i18nKey="analysis-results:orderTitle" values={{ orderNumber: analysisOrder.medusa_order_id }} />
|
|
||||||
</h4>
|
|
||||||
<h5>
|
|
||||||
<Trans i18nKey={`orders:status.${analysisOrder.status}`} />
|
|
||||||
<ButtonTooltip
|
|
||||||
content={`${new Date(analysisOrder.created_at).toLocaleString()}`}
|
|
||||||
className="ml-6"
|
|
||||||
/>
|
|
||||||
</h5>
|
|
||||||
<div className="flex flex-col gap-2">
|
|
||||||
{analysisElementsForOrder.length > 0 ? analysisElementsForOrder.map((analysisElement) => {
|
|
||||||
const results = analysisResponse?.elements.some((element) => element.analysis_element_original_id === analysisElement.analysis_id_original)
|
|
||||||
&& analysisResponseElements?.find((element) => element.analysis_element_original_id === analysisElement.analysis_id_original);
|
|
||||||
if (!results) {
|
|
||||||
return (
|
|
||||||
<Analysis key={`${analysisOrder.id}-${analysisElement.id}`} analysisElement={analysisElement} isCancelled={analysisOrder.status === 'CANCELLED'}/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<Analysis key={`${analysisOrder.id}-${analysisElement.id}`} analysisElement={analysisElement} results={results} />
|
|
||||||
);
|
|
||||||
}) : (
|
|
||||||
<div className="text-muted-foreground text-sm">
|
|
||||||
<Trans i18nKey="analysis-results:noAnalysisElements" />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}) : (
|
|
||||||
<div className="text-muted-foreground text-sm">
|
|
||||||
<Trans i18nKey="analysis-results:noAnalysisOrders" />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</PageBody>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default withI18n(AnalysisResultsPage);
|
|
||||||
@@ -1,22 +1,32 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { Trans } from '@kit/ui/trans';
|
import { useRouter } from 'next/navigation';
|
||||||
|
|
||||||
|
import { StoreOrderLineItem } from '@medusajs/types';
|
||||||
|
import { formatDate } from 'date-fns';
|
||||||
|
import { Eye } from 'lucide-react';
|
||||||
|
|
||||||
|
import { pathsConfig } from '@kit/shared/config';
|
||||||
|
import { Button } from '@kit/ui/button';
|
||||||
import {
|
import {
|
||||||
Table,
|
Table,
|
||||||
TableBody,
|
TableBody,
|
||||||
TableHead,
|
|
||||||
TableRow,
|
|
||||||
TableHeader,
|
|
||||||
TableCell,
|
TableCell,
|
||||||
|
TableHead,
|
||||||
|
TableHeader,
|
||||||
|
TableRow,
|
||||||
} from '@kit/ui/table';
|
} from '@kit/ui/table';
|
||||||
import { StoreOrderLineItem } from "@medusajs/types";
|
import { Trans } from '@kit/ui/trans';
|
||||||
|
|
||||||
import { AnalysisOrder } from '~/lib/services/order.service';
|
import { AnalysisOrder } from '~/lib/services/order.service';
|
||||||
import { formatDate } from 'date-fns';
|
|
||||||
import { Eye } from 'lucide-react';
|
|
||||||
import { useRouter } from 'next/navigation';
|
|
||||||
import { logAnalysisResultsNavigateAction } from './actions';
|
import { logAnalysisResultsNavigateAction } from './actions';
|
||||||
|
|
||||||
export default function OrderItemsTable({ items, title, analysisOrder }: {
|
export default function OrderItemsTable({
|
||||||
|
items,
|
||||||
|
title,
|
||||||
|
analysisOrder,
|
||||||
|
}: {
|
||||||
items: StoreOrderLineItem[];
|
items: StoreOrderLineItem[];
|
||||||
title: string;
|
title: string;
|
||||||
analysisOrder: AnalysisOrder;
|
analysisOrder: AnalysisOrder;
|
||||||
@@ -29,11 +39,11 @@ export default function OrderItemsTable({ items, title, analysisOrder }: {
|
|||||||
|
|
||||||
const openAnalysisResults = async () => {
|
const openAnalysisResults = async () => {
|
||||||
await logAnalysisResultsNavigateAction(analysisOrder.medusa_order_id);
|
await logAnalysisResultsNavigateAction(analysisOrder.medusa_order_id);
|
||||||
router.push(`/home/analysis-results`);
|
router.push(`${pathsConfig.app.analysisResults}/${analysisOrder.id}`);
|
||||||
}
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Table className="rounded-lg border border-separate">
|
<Table className="border-separate rounded-lg border">
|
||||||
<TableHeader className="text-ui-fg-subtle txt-medium-plus">
|
<TableHeader className="text-ui-fg-subtle txt-medium-plus">
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableHead className="px-6">
|
<TableHead className="px-6">
|
||||||
@@ -45,13 +55,14 @@ export default function OrderItemsTable({ items, title, analysisOrder }: {
|
|||||||
<TableHead className="px-6">
|
<TableHead className="px-6">
|
||||||
<Trans i18nKey="orders:table.status" />
|
<Trans i18nKey="orders:table.status" />
|
||||||
</TableHead>
|
</TableHead>
|
||||||
<TableHead className="px-6">
|
<TableHead className="px-6"></TableHead>
|
||||||
</TableHead>
|
|
||||||
</TableRow>
|
</TableRow>
|
||||||
</TableHeader>
|
</TableHeader>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
{items
|
{items
|
||||||
.sort((a, b) => (a.created_at ?? "") > (b.created_at ?? "") ? -1 : 1)
|
.sort((a, b) =>
|
||||||
|
(a.created_at ?? '') > (b.created_at ?? '') ? -1 : 1,
|
||||||
|
)
|
||||||
.map((orderItem) => (
|
.map((orderItem) => (
|
||||||
<TableRow className="w-full" key={orderItem.id}>
|
<TableRow className="w-full" key={orderItem.id}>
|
||||||
<TableCell className="text-left w-[100%] px-6">
|
<TableCell className="text-left w-[100%] px-6">
|
||||||
@@ -64,23 +75,18 @@ export default function OrderItemsTable({ items, title, analysisOrder }: {
|
|||||||
{formatDate(orderItem.created_at, 'dd.MM.yyyy HH:mm')}
|
{formatDate(orderItem.created_at, 'dd.MM.yyyy HH:mm')}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
|
|
||||||
<TableCell className="px-6 min-w-[180px]">
|
<TableCell className="min-w-[180px] px-6">
|
||||||
<Trans i18nKey={`orders:status.${analysisOrder.status}`} />
|
<Trans i18nKey={`orders:status.${analysisOrder.status}`} />
|
||||||
</TableCell>
|
</TableCell>
|
||||||
|
|
||||||
<TableCell className="text-right px-6">
|
<TableCell className="px-6 text-right">
|
||||||
<span className="flex gap-x-1 justify-end w-[30px]">
|
<Button size="sm" onClick={openAnalysisResults}>
|
||||||
<button
|
<Trans i18nKey="analysis-results:view" />
|
||||||
className="flex gap-x-1 text-ui-fg-subtle hover:text-ui-fg-base cursor-pointer "
|
</Button>
|
||||||
onClick={openAnalysisResults}
|
|
||||||
>
|
|
||||||
<Eye />
|
|
||||||
</button>
|
|
||||||
</span>
|
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
))}
|
))}
|
||||||
</TableBody>
|
</TableBody>
|
||||||
</Table>
|
</Table>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
22
app/home/(user)/_lib/server/load-user-analyses.ts
Normal file
22
app/home/(user)/_lib/server/load-user-analyses.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import { cache } from 'react';
|
||||||
|
|
||||||
|
import { createAccountsApi } from '@kit/accounts/api';
|
||||||
|
import { UserAnalysis } from '@kit/accounts/types/accounts';
|
||||||
|
import { getSupabaseServerClient } from '@kit/supabase/server-client';
|
||||||
|
|
||||||
|
export type UserAnalyses = Awaited<ReturnType<typeof loadUserAnalyses>>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @name loadUserAnalyses
|
||||||
|
* @description
|
||||||
|
* Load the user's analyses. It's a cached per-request function that fetches the user workspace data.
|
||||||
|
* It can be used across the server components to load the user workspace data.
|
||||||
|
*/
|
||||||
|
export const loadUserAnalyses = cache(analysesLoader);
|
||||||
|
|
||||||
|
async function analysesLoader(): Promise<UserAnalysis | null> {
|
||||||
|
const client = getSupabaseServerClient();
|
||||||
|
const api = createAccountsApi(client);
|
||||||
|
|
||||||
|
return api.getUserAnalyses();
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { cache } from 'react';
|
import { cache } from 'react';
|
||||||
|
|
||||||
import { createAccountsApi } from '@kit/accounts/api';
|
import { createAccountsApi } from '@kit/accounts/api';
|
||||||
import { UserAnalysis } from '@kit/accounts/types/accounts';
|
import { AnalysisResultDetails } from '@kit/accounts/types/accounts';
|
||||||
import { getSupabaseServerClient } from '@kit/supabase/server-client';
|
import { getSupabaseServerClient } from '@kit/supabase/server-client';
|
||||||
|
|
||||||
export type UserAnalyses = Awaited<ReturnType<typeof loadUserAnalysis>>;
|
export type UserAnalyses = Awaited<ReturnType<typeof loadUserAnalysis>>;
|
||||||
@@ -9,14 +9,15 @@ export type UserAnalyses = Awaited<ReturnType<typeof loadUserAnalysis>>;
|
|||||||
/**
|
/**
|
||||||
* @name loadUserAnalysis
|
* @name loadUserAnalysis
|
||||||
* @description
|
* @description
|
||||||
* Load the user's analyses. It's a cached per-request function that fetches the user workspace data.
|
* Load the user's analysis based on id. It's a cached per-request function that fetches the user's analysis data.
|
||||||
* It can be used across the server components to load the user workspace data.
|
|
||||||
*/
|
*/
|
||||||
export const loadUserAnalysis = cache(analysisLoader);
|
export const loadUserAnalysis = cache(analysisLoader);
|
||||||
|
|
||||||
async function analysisLoader(): Promise<UserAnalysis | null> {
|
async function analysisLoader(
|
||||||
|
analysisOrderId: number,
|
||||||
|
): Promise<AnalysisResultDetails | null> {
|
||||||
const client = getSupabaseServerClient();
|
const client = getSupabaseServerClient();
|
||||||
const api = createAccountsApi(client);
|
const api = createAccountsApi(client);
|
||||||
|
|
||||||
return api.getUserAnalysis();
|
return api.getUserAnalysis(analysisOrderId);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,12 +21,12 @@ export async function renderDoctorSummaryReceivedEmail({
|
|||||||
language,
|
language,
|
||||||
recipientName,
|
recipientName,
|
||||||
orderNr,
|
orderNr,
|
||||||
orderId,
|
analysisOrderId,
|
||||||
}: {
|
}: {
|
||||||
language?: string;
|
language?: string;
|
||||||
recipientName: string;
|
recipientName: string;
|
||||||
orderNr: string;
|
orderNr: string;
|
||||||
orderId: number;
|
analysisOrderId: number;
|
||||||
}) {
|
}) {
|
||||||
const namespace = 'doctor-summary-received-email';
|
const namespace = 'doctor-summary-received-email';
|
||||||
|
|
||||||
@@ -69,13 +69,13 @@ export async function renderDoctorSummaryReceivedEmail({
|
|||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
<EmailButton
|
<EmailButton
|
||||||
href={`${process.env.NEXT_PUBLIC_SITE_URL}/home/order/${orderId}`}
|
href={`${process.env.NEXT_PUBLIC_SITE_URL}/home/analysis-results/${analysisOrderId}`}
|
||||||
>
|
>
|
||||||
{t(`${namespace}:linkText`, { orderNr })}
|
{t(`${namespace}:linkText`, { orderNr })}
|
||||||
</EmailButton>
|
</EmailButton>
|
||||||
<Text>
|
<Text>
|
||||||
{t(`${namespace}:ifButtonDisabled`)}{' '}
|
{t(`${namespace}:ifButtonDisabled`)}{' '}
|
||||||
{`${process.env.NEXT_PUBLIC_SITE_URL}/home/order/${orderId}`}
|
{`${process.env.NEXT_PUBLIC_SITE_URL}/home/analysis-results/${analysisOrderId}`}
|
||||||
</Text>
|
</Text>
|
||||||
<CommonFooter t={t} />
|
<CommonFooter t={t} />
|
||||||
</EmailContent>
|
</EmailContent>
|
||||||
|
|||||||
@@ -2,7 +2,11 @@ import { SupabaseClient } from '@supabase/supabase-js';
|
|||||||
|
|
||||||
import { Database } from '@kit/supabase/database';
|
import { Database } from '@kit/supabase/database';
|
||||||
|
|
||||||
import { UserAnalysis } from '../types/accounts';
|
import {
|
||||||
|
AnalysisResultDetails,
|
||||||
|
UserAnalysis,
|
||||||
|
UserAnalysisResponse,
|
||||||
|
} from '../types/accounts';
|
||||||
|
|
||||||
export type AccountWithParams =
|
export type AccountWithParams =
|
||||||
Database['medreport']['Tables']['accounts']['Row'] & {
|
Database['medreport']['Tables']['accounts']['Row'] & {
|
||||||
@@ -184,7 +188,49 @@ class AccountsApi {
|
|||||||
return response.data?.customer_id;
|
return response.data?.customer_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getUserAnalysis(): Promise<UserAnalysis | null> {
|
async getUserAnalysis(
|
||||||
|
analysisOrderId: number,
|
||||||
|
): Promise<AnalysisResultDetails | null> {
|
||||||
|
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 { data: analysisResponse } = await this.client
|
||||||
|
.schema('medreport')
|
||||||
|
.from('analysis_responses')
|
||||||
|
.select(
|
||||||
|
`*,
|
||||||
|
elements:analysis_response_elements(analysis_name,norm_status,response_value,unit,norm_lower_included,norm_upper_included,norm_lower,norm_upper,response_time),
|
||||||
|
order:analysis_order_id(medusa_order_id, status, created_at),
|
||||||
|
summary:analysis_order_id(doctor_analysis_feedback(*))`,
|
||||||
|
)
|
||||||
|
.eq('user_id', user.id)
|
||||||
|
.eq('analysis_order_id', analysisOrderId)
|
||||||
|
.throwOnError();
|
||||||
|
|
||||||
|
const responseWithElements = analysisResponse?.[0];
|
||||||
|
if (!responseWithElements) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const feedback = responseWithElements.summary.doctor_analysis_feedback?.[0];
|
||||||
|
|
||||||
|
return {
|
||||||
|
...responseWithElements,
|
||||||
|
summary:
|
||||||
|
feedback?.status === 'COMPLETED'
|
||||||
|
? responseWithElements.summary.doctor_analysis_feedback?.[0]
|
||||||
|
: null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async getUserAnalyses(): Promise<UserAnalysis | null> {
|
||||||
const authUser = await this.client.auth.getUser();
|
const authUser = await this.client.auth.getUser();
|
||||||
const { data, error: userError } = authUser;
|
const { data, error: userError } = authUser;
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import * as z from 'zod';
|
||||||
|
|
||||||
import { Database } from '@kit/supabase/database';
|
import { Database } from '@kit/supabase/database';
|
||||||
|
|
||||||
export type UserAnalysisElement =
|
export type UserAnalysisElement =
|
||||||
@@ -15,3 +17,51 @@ export enum ApplicationRoleEnum {
|
|||||||
Doctor = 'doctor',
|
Doctor = 'doctor',
|
||||||
SuperAdmin = 'super_admin',
|
SuperAdmin = 'super_admin',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export 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(),
|
||||||
|
norm_lower_included: z.boolean(),
|
||||||
|
norm_upper_included: z.boolean(),
|
||||||
|
});
|
||||||
|
export type Element = z.infer<typeof ElementSchema>;
|
||||||
|
|
||||||
|
export const OrderSchema = z.object({
|
||||||
|
status: z.string(),
|
||||||
|
medusa_order_id: z.string(),
|
||||||
|
created_at: z.coerce.date(),
|
||||||
|
});
|
||||||
|
export type Order = z.infer<typeof OrderSchema>;
|
||||||
|
|
||||||
|
export 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(),
|
||||||
|
});
|
||||||
|
export type Summary = z.infer<typeof SummarySchema>;
|
||||||
|
|
||||||
|
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<typeof AnalysisResultDetailsSchema>;
|
||||||
|
|||||||
@@ -656,6 +656,12 @@ export async function submitFeedback(
|
|||||||
.eq('id', analysisOrderId)
|
.eq('id', analysisOrderId)
|
||||||
.limit(1)
|
.limit(1)
|
||||||
.throwOnError(),
|
.throwOnError(),
|
||||||
|
supabase
|
||||||
|
.schema('medreport')
|
||||||
|
.from('analysis_orders')
|
||||||
|
.update({ status: 'COMPLETED' })
|
||||||
|
.eq('id', analysisOrderId)
|
||||||
|
.throwOnError(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if (!recipient?.[0]?.email) {
|
if (!recipient?.[0]?.email) {
|
||||||
@@ -674,7 +680,7 @@ export async function submitFeedback(
|
|||||||
language: preferred_locale ?? 'et',
|
language: preferred_locale ?? 'et',
|
||||||
recipientName: getFullName(name, last_name),
|
recipientName: getFullName(name, last_name),
|
||||||
orderNr: analysisOrder?.[0]?.medusa_order_id ?? '',
|
orderNr: analysisOrder?.[0]?.medusa_order_id ?? '',
|
||||||
orderId: analysisOrder[0].id,
|
analysisOrderId: analysisOrder[0].id,
|
||||||
},
|
},
|
||||||
email,
|
email,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -35,12 +35,6 @@ const routes = [
|
|||||||
Icon: <ShoppingCart className={iconClasses} />,
|
Icon: <ShoppingCart className={iconClasses} />,
|
||||||
end: true,
|
end: true,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
label: 'common:routes.analysisResults',
|
|
||||||
path: pathsConfig.app.analysisResults,
|
|
||||||
Icon: <TestTube2 className={iconClasses} />,
|
|
||||||
end: true,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
label: 'common:routes.orderAnalysisPackage',
|
label: 'common:routes.orderAnalysisPackage',
|
||||||
path: pathsConfig.app.orderAnalysisPackage,
|
path: pathsConfig.app.orderAnalysisPackage,
|
||||||
|
|||||||
@@ -128,5 +128,6 @@
|
|||||||
"updateRoleLoading": "Updating role...",
|
"updateRoleLoading": "Updating role...",
|
||||||
"updatePreferredLocaleSuccess": "Language preference updated",
|
"updatePreferredLocaleSuccess": "Language preference updated",
|
||||||
"updatePreferredLocaleError": "Language preference update failed",
|
"updatePreferredLocaleError": "Language preference update failed",
|
||||||
"updatePreferredLocaleLoading": "Updating language preference..."
|
"updatePreferredLocaleLoading": "Updating language preference...",
|
||||||
|
"doctorAnalysisSummary": "Doctor's summary"
|
||||||
}
|
}
|
||||||
@@ -12,5 +12,6 @@
|
|||||||
"normal": "Normal range"
|
"normal": "Normal range"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"orderTitle": "Order number {{orderNumber}}"
|
"orderTitle": "Order number {{orderNumber}}",
|
||||||
|
"view": "View results"
|
||||||
}
|
}
|
||||||
@@ -151,5 +151,6 @@
|
|||||||
"updateRoleLoading": "Rolli uuendatakse...",
|
"updateRoleLoading": "Rolli uuendatakse...",
|
||||||
"updatePreferredLocaleSuccess": "Eelistatud keel uuendatud",
|
"updatePreferredLocaleSuccess": "Eelistatud keel uuendatud",
|
||||||
"updatePreferredLocaleError": "Eelistatud keele uuendamine ei õnnestunud",
|
"updatePreferredLocaleError": "Eelistatud keele uuendamine ei õnnestunud",
|
||||||
"updatePreferredLocaleLoading": "Eelistatud keelt uuendatakse..."
|
"updatePreferredLocaleLoading": "Eelistatud keelt uuendatakse...",
|
||||||
|
"doctorAnalysisSummary": "Arsti kokkuvõte analüüsitulemuste kohta"
|
||||||
}
|
}
|
||||||
@@ -12,5 +12,6 @@
|
|||||||
"normal": "Normaalne vahemik"
|
"normal": "Normaalne vahemik"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"orderTitle": "Tellimus {{orderNumber}}"
|
"orderTitle": "Tellimus {{orderNumber}}",
|
||||||
|
"view": "Vaata tulemusi"
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user