feat(MED-131): show package analysis elements in comparison modal
This commit is contained in:
@@ -20,7 +20,7 @@ export const generateMetadata = async () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
async function OrderAnalysisPackagePage() {
|
async function OrderAnalysisPackagePage() {
|
||||||
const { analysisPackages, countryCode } = await loadAnalysisPackages();
|
const { analysisElements, analysisPackages, countryCode } = await loadAnalysisPackages();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PageBody>
|
<PageBody>
|
||||||
@@ -29,6 +29,7 @@ async function OrderAnalysisPackagePage() {
|
|||||||
<Trans i18nKey={'marketing:selectPackage'} />
|
<Trans i18nKey={'marketing:selectPackage'} />
|
||||||
</h3>
|
</h3>
|
||||||
<ComparePackagesModal
|
<ComparePackagesModal
|
||||||
|
analysisElements={analysisElements}
|
||||||
analysisPackages={analysisPackages}
|
analysisPackages={analysisPackages}
|
||||||
triggerElement={
|
triggerElement={
|
||||||
<Button variant="secondary" className="gap-2">
|
<Button variant="secondary" className="gap-2">
|
||||||
|
|||||||
@@ -23,79 +23,8 @@ import { withI18n } from '~/lib/i18n/with-i18n';
|
|||||||
import { PackageHeader } from '@/components/package-header';
|
import { PackageHeader } from '@/components/package-header';
|
||||||
import { InfoTooltip } from '@/components/ui/info-tooltip';
|
import { InfoTooltip } from '@/components/ui/info-tooltip';
|
||||||
import { StoreProduct } from '@medusajs/types';
|
import { StoreProduct } from '@medusajs/types';
|
||||||
|
import type { AnalysisElement } from '~/lib/services/analysis-element.service';
|
||||||
const dummyCards = [
|
import { getAnalysisElementOriginalIds } from '@lib/data/products';
|
||||||
{
|
|
||||||
titleKey: 'product:standard.label',
|
|
||||||
price: 40,
|
|
||||||
nrOfAnalyses: 4,
|
|
||||||
tagColor: 'bg-cyan',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
titleKey: 'product:standardPlus.label',
|
|
||||||
price: 85,
|
|
||||||
nrOfAnalyses: 10,
|
|
||||||
tagColor: 'bg-warning',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
titleKey: 'product:premium.label',
|
|
||||||
price: 140,
|
|
||||||
nrOfAnalyses: '12+',
|
|
||||||
tagColor: 'bg-purple',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const dummyRows = [
|
|
||||||
{
|
|
||||||
analysisNameKey: 'product:clinicalBloodDraw.label',
|
|
||||||
tooltipContentKey: 'product:clinicalBloodDraw.description',
|
|
||||||
includedInStandard: 1,
|
|
||||||
includedInStandardPlus: 1,
|
|
||||||
includedInPremium: 1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
analysisNameKey: 'product:crp.label',
|
|
||||||
tooltipContentKey: 'product:crp.description',
|
|
||||||
includedInStandard: 1,
|
|
||||||
includedInStandardPlus: 1,
|
|
||||||
includedInPremium: 1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
analysisNameKey: 'product:ferritin.label',
|
|
||||||
tooltipContentKey: 'product:ferritin.description',
|
|
||||||
includedInStandard: 0,
|
|
||||||
includedInStandardPlus: 1,
|
|
||||||
includedInPremium: 1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
analysisNameKey: 'product:vitaminD.label',
|
|
||||||
tooltipContentKey: 'product:vitaminD.description',
|
|
||||||
includedInStandard: 0,
|
|
||||||
includedInStandardPlus: 1,
|
|
||||||
includedInPremium: 1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
analysisNameKey: 'product:glucose.label',
|
|
||||||
tooltipContentKey: 'product:glucose.description',
|
|
||||||
includedInStandard: 1,
|
|
||||||
includedInStandardPlus: 1,
|
|
||||||
includedInPremium: 1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
analysisNameKey: 'product:alat.label',
|
|
||||||
tooltipContentKey: 'product:alat.description',
|
|
||||||
includedInStandard: 1,
|
|
||||||
includedInStandardPlus: 1,
|
|
||||||
includedInPremium: 1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
analysisNameKey: 'product:ast.label',
|
|
||||||
tooltipContentKey: 'product:ast.description',
|
|
||||||
includedInStandard: 1,
|
|
||||||
includedInStandardPlus: 1,
|
|
||||||
includedInPremium: 1,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const CheckWithBackground = () => {
|
const CheckWithBackground = () => {
|
||||||
return (
|
return (
|
||||||
@@ -106,14 +35,28 @@ const CheckWithBackground = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const ComparePackagesModal = async ({
|
const ComparePackagesModal = async ({
|
||||||
|
analysisElements,
|
||||||
analysisPackages,
|
analysisPackages,
|
||||||
triggerElement,
|
triggerElement,
|
||||||
}: {
|
}: {
|
||||||
|
analysisElements: AnalysisElement[];
|
||||||
analysisPackages: StoreProduct[];
|
analysisPackages: StoreProduct[];
|
||||||
triggerElement: JSX.Element;
|
triggerElement: JSX.Element;
|
||||||
}) => {
|
}) => {
|
||||||
const { t, language } = await createI18nServerInstance();
|
const { t, language } = await createI18nServerInstance();
|
||||||
|
|
||||||
|
const standardPackage = analysisPackages.find(({ metadata }) => metadata?.analysisPackageTier === 'standard')!;
|
||||||
|
const standardPlusPackage = analysisPackages.find(({ metadata }) => metadata?.analysisPackageTier === 'standard-plus')!;
|
||||||
|
const premiumPackage = analysisPackages.find(({ metadata }) => metadata?.analysisPackageTier === 'premium')!;
|
||||||
|
|
||||||
|
if (!standardPackage || !standardPlusPackage || !premiumPackage) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const standardPackageAnalyses = await getAnalysisElementOriginalIds([standardPackage]);
|
||||||
|
const standardPlusPackageAnalyses = await getAnalysisElementOriginalIds([standardPlusPackage]);
|
||||||
|
const premiumPackageAnalyses = await getAnalysisElementOriginalIds([premiumPackage]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog>
|
<Dialog>
|
||||||
<DialogTrigger asChild>{triggerElement}</DialogTrigger>
|
<DialogTrigger asChild>{triggerElement}</DialogTrigger>
|
||||||
@@ -138,7 +81,7 @@ const ComparePackagesModal = async ({
|
|||||||
<p className="text-muted-foreground mx-auto w-3/5 text-sm">
|
<p className="text-muted-foreground mx-auto w-3/5 text-sm">
|
||||||
{t('product:healthPackageComparison.description')}
|
{t('product:healthPackageComparison.description')}
|
||||||
</p>
|
</p>
|
||||||
<div className="rounded-md border">
|
<div className="rounded-md border max-h-[80vh] overflow-y-auto">
|
||||||
<Table>
|
<Table>
|
||||||
<TableHeader>
|
<TableHeader>
|
||||||
<TableRow>
|
<TableRow>
|
||||||
@@ -165,37 +108,41 @@ const ComparePackagesModal = async ({
|
|||||||
</TableRow>
|
</TableRow>
|
||||||
</TableHeader>
|
</TableHeader>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
{dummyRows.map(
|
{analysisElements.map(
|
||||||
(
|
(
|
||||||
{
|
{
|
||||||
analysisNameKey,
|
analysis_name_lab: analysisName,
|
||||||
tooltipContentKey,
|
analysis_id_original: analysisId,
|
||||||
includedInStandard,
|
|
||||||
includedInStandardPlus,
|
|
||||||
includedInPremium,
|
|
||||||
},
|
},
|
||||||
index,
|
index,
|
||||||
) => (
|
) => {
|
||||||
<TableRow key={index}>
|
if (!analysisName) {
|
||||||
<TableCell className="py-6">
|
return null;
|
||||||
{t(analysisNameKey)}{' '}
|
}
|
||||||
<InfoTooltip
|
const includedInStandard = standardPackageAnalyses.includes(analysisId);
|
||||||
content={t(tooltipContentKey)}
|
const includedInStandardPlus = standardPlusPackageAnalyses.includes(analysisId);
|
||||||
icon={<QuestionMarkCircledIcon />}
|
const includedInPremium = premiumPackageAnalyses.includes(analysisId);
|
||||||
/>
|
return (
|
||||||
</TableCell>
|
<TableRow key={index}>
|
||||||
<TableCell align="center" className="py-6">
|
<TableCell className="py-6">
|
||||||
{!!includedInStandard && <CheckWithBackground />}
|
{analysisName}{' '}
|
||||||
</TableCell>
|
{/* <InfoTooltip
|
||||||
<TableCell align="center" className="py-6">
|
content={t(tooltipContentKey)}
|
||||||
{!!includedInStandardPlus && <CheckWithBackground />}
|
icon={<QuestionMarkCircledIcon />}
|
||||||
</TableCell>
|
/> */}
|
||||||
<TableCell align="center" className="py-6">
|
</TableCell>
|
||||||
{!!includedInPremium && <CheckWithBackground />}
|
<TableCell align="center" className="py-6">
|
||||||
</TableCell>
|
{includedInStandard && <CheckWithBackground />}
|
||||||
</TableRow>
|
</TableCell>
|
||||||
),
|
<TableCell align="center" className="py-6">
|
||||||
)}
|
{(includedInStandard || includedInStandardPlus) && <CheckWithBackground />}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell align="center" className="py-6">
|
||||||
|
{(includedInStandard || includedInStandardPlus || includedInPremium) && <CheckWithBackground />}
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</TableBody>
|
</TableBody>
|
||||||
</Table>
|
</Table>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import { cache } from 'react';
|
import { cache } from 'react';
|
||||||
|
|
||||||
import { listProductTypes, listProducts, listRegions } from "@lib/data";
|
import { getAnalysisElementOriginalIds, listProductTypes, listProducts } from "@lib/data/products";
|
||||||
|
import { listRegions } from '@lib/data/regions';
|
||||||
|
import { AnalysisElement, getAnalysisElements } from '~/lib/services/analysis-element.service';
|
||||||
|
|
||||||
async function countryCodesLoader() {
|
async function countryCodesLoader() {
|
||||||
const countryCodes = await listRegions().then((regions) =>
|
const countryCodes = await listRegions().then((regions) =>
|
||||||
@@ -22,13 +24,21 @@ async function analysisPackagesLoader() {
|
|||||||
|
|
||||||
const productType = productTypes.find(({ metadata }) => metadata?.handle === 'analysis-packages');
|
const productType = productTypes.find(({ metadata }) => metadata?.handle === 'analysis-packages');
|
||||||
if (!productType) {
|
if (!productType) {
|
||||||
return { analysisPackages: [], countryCode };
|
return { analysisElements: [], analysisPackages: [], countryCode };
|
||||||
}
|
}
|
||||||
|
|
||||||
const { response } = await listProducts({
|
const { response } = await listProducts({
|
||||||
countryCode,
|
countryCode,
|
||||||
queryParams: { limit: 100, "type_id[0]": productType.id },
|
queryParams: { limit: 100, "type_id[0]": productType.id },
|
||||||
});
|
});
|
||||||
return { analysisPackages: response.products, countryCode };
|
const analysisPackages = response.products;
|
||||||
|
let analysisElements: AnalysisElement[] = [];
|
||||||
|
const analysisElementOriginalIds = await getAnalysisElementOriginalIds(analysisPackages);
|
||||||
|
|
||||||
|
if (analysisElementOriginalIds.length) {
|
||||||
|
analysisElements = await getAnalysisElements({ originalIds: analysisElementOriginalIds });
|
||||||
|
}
|
||||||
|
|
||||||
|
return { analysisElements, analysisPackages, countryCode };
|
||||||
}
|
}
|
||||||
export const loadAnalysisPackages = cache(analysisPackagesLoader);
|
export const loadAnalysisPackages = cache(analysisPackagesLoader);
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ export const generateMetadata = async () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
async function SelectPackagePage() {
|
async function SelectPackagePage() {
|
||||||
const { analysisPackages, countryCode } = await loadAnalysisPackages();
|
const { analysisElements, analysisPackages, countryCode } = await loadAnalysisPackages();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="container mx-auto my-24 flex flex-col items-center space-y-12">
|
<div className="container mx-auto my-24 flex flex-col items-center space-y-12">
|
||||||
@@ -34,6 +34,7 @@ async function SelectPackagePage() {
|
|||||||
<Trans i18nKey={'marketing:selectPackage'} />
|
<Trans i18nKey={'marketing:selectPackage'} />
|
||||||
</h3>
|
</h3>
|
||||||
<ComparePackagesModal
|
<ComparePackagesModal
|
||||||
|
analysisElements={analysisElements}
|
||||||
analysisPackages={analysisPackages}
|
analysisPackages={analysisPackages}
|
||||||
triggerElement={
|
triggerElement={
|
||||||
<Button variant="secondary" className="gap-2">
|
<Button variant="secondary" className="gap-2">
|
||||||
|
|||||||
60
lib/services/analysis-element.service.ts
Normal file
60
lib/services/analysis-element.service.ts
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
import { getSupabaseServerClient } from '@kit/supabase/server-client';
|
||||||
|
import { Json, Tables } from '@kit/supabase/database';
|
||||||
|
import { getSupabaseServerAdminClient } from '@kit/supabase/server-admin-client';
|
||||||
|
import type { IMaterialGroup, IUuringElement } from './medipost.types';
|
||||||
|
|
||||||
|
export type AnalysisElement = Tables<{ schema: 'medreport' }, 'analysis_elements'> & {
|
||||||
|
analysis_groups: Tables<{ schema: 'medreport' }, 'analysis_groups'>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export async function getAnalysisElements({
|
||||||
|
originalIds,
|
||||||
|
}: {
|
||||||
|
originalIds: string[]
|
||||||
|
}) {
|
||||||
|
const { data: analysisElements } = await getSupabaseServerClient()
|
||||||
|
.schema('medreport')
|
||||||
|
.from('analysis_elements')
|
||||||
|
.select(`*, analysis_groups(*)`)
|
||||||
|
.in('analysis_id_original', [...new Set(originalIds)])
|
||||||
|
.order('order', { ascending: true });
|
||||||
|
|
||||||
|
return analysisElements ?? [];
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createAnalysisElement({
|
||||||
|
analysisElement,
|
||||||
|
analysisGroupId,
|
||||||
|
materialGroups,
|
||||||
|
}: {
|
||||||
|
analysisElement: IUuringElement;
|
||||||
|
analysisGroupId: number;
|
||||||
|
materialGroups: IMaterialGroup[];
|
||||||
|
}) {
|
||||||
|
const { data: insertedAnalysisElement, error } = await getSupabaseServerAdminClient()
|
||||||
|
.schema('medreport')
|
||||||
|
.from('analysis_elements')
|
||||||
|
.upsert(
|
||||||
|
{
|
||||||
|
analysis_id_oid: analysisElement.UuringIdOID,
|
||||||
|
analysis_id_original: analysisElement.UuringId,
|
||||||
|
tehik_short_loinc: analysisElement.TLyhend,
|
||||||
|
tehik_loinc_name: analysisElement.KNimetus,
|
||||||
|
analysis_name_lab: analysisElement.UuringNimi,
|
||||||
|
order: analysisElement.Jarjekord,
|
||||||
|
parent_analysis_group_id: analysisGroupId,
|
||||||
|
material_groups: materialGroups as unknown as Json[],
|
||||||
|
},
|
||||||
|
{ onConflict: 'analysis_id_original', ignoreDuplicates: false },
|
||||||
|
)
|
||||||
|
.select('id');
|
||||||
|
|
||||||
|
const id = insertedAnalysisElement?.[0]?.id;
|
||||||
|
if (error || !id) {
|
||||||
|
throw new Error(
|
||||||
|
`Failed to insert analysis element (id: ${analysisElement.UuringId}), error: ${error?.message}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return id;
|
||||||
|
}
|
||||||
85
lib/services/medipost.types.ts
Normal file
85
lib/services/medipost.types.ts
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
export interface IUuringElement {
|
||||||
|
UuringIdOID: string;
|
||||||
|
UuringId: string;
|
||||||
|
TLyhend: string;
|
||||||
|
KNimetus: string;
|
||||||
|
UuringNimi: string;
|
||||||
|
Jarjekord: number;
|
||||||
|
Kood: {
|
||||||
|
HkKood: string;
|
||||||
|
HkKoodiKordaja: number;
|
||||||
|
Koefitsient: number;
|
||||||
|
Hind: number;
|
||||||
|
}[];
|
||||||
|
UuringuElement: {
|
||||||
|
UuringIdOID: string;
|
||||||
|
UuringId: string;
|
||||||
|
TLyhend: string;
|
||||||
|
KNimetus: string;
|
||||||
|
UuringNimi: string;
|
||||||
|
Jarjekord: number;
|
||||||
|
Kood: {
|
||||||
|
HkKood: string;
|
||||||
|
HkKoodiKordaja: number;
|
||||||
|
Koefitsient: number;
|
||||||
|
Hind: number;
|
||||||
|
}[];
|
||||||
|
}[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IMaterialGroup {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
order: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IMedipostPublicMessageDataParsed {
|
||||||
|
ANSWER: {
|
||||||
|
CODE: number;
|
||||||
|
MESSAGE: string;
|
||||||
|
};
|
||||||
|
Saadetis: {
|
||||||
|
Teenused: {
|
||||||
|
Teostaja: {
|
||||||
|
UuringuGrupp: {
|
||||||
|
UuringuGruppId: string;
|
||||||
|
UuringuGruppNimi: string;
|
||||||
|
UuringuGruppJarjekord: number;
|
||||||
|
Kood: {
|
||||||
|
HkKood: string;
|
||||||
|
HkKoodiKordaja: number;
|
||||||
|
Koefitsient: number;
|
||||||
|
Hind: number;
|
||||||
|
}[];
|
||||||
|
Uuring: {
|
||||||
|
UuringId: string;
|
||||||
|
UuringNimi: string;
|
||||||
|
UuringJarjekord: number;
|
||||||
|
UuringuElement: {
|
||||||
|
UuringIdOID: string;
|
||||||
|
UuringId: string;
|
||||||
|
TLyhend: string;
|
||||||
|
KNimetus: string;
|
||||||
|
UuringNimi: string;
|
||||||
|
Jarjekord: number;
|
||||||
|
Kood: {
|
||||||
|
HkKood: string;
|
||||||
|
HkKoodiKordaja: number;
|
||||||
|
Koefitsient: number;
|
||||||
|
Hind: number;
|
||||||
|
}[];
|
||||||
|
UuringuElement: IUuringElement;
|
||||||
|
}[];
|
||||||
|
MaterjalideGrupp: IMaterialGroup[];
|
||||||
|
Kood: {
|
||||||
|
HkKood: string;
|
||||||
|
HkKoodiKordaja: number;
|
||||||
|
Koefitsient: number;
|
||||||
|
Hind: number;
|
||||||
|
}[];
|
||||||
|
}[];
|
||||||
|
}[];
|
||||||
|
}[];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -85,6 +85,20 @@ export const listProducts = async ({
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const getAnalysisElementOriginalIds = async (products: HttpTypes.StoreProduct[]) => {
|
||||||
|
return products
|
||||||
|
.flatMap(({ metadata }) => {
|
||||||
|
const value = metadata?.analysisElementOriginalIds;
|
||||||
|
try {
|
||||||
|
return JSON.parse(value as string);
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Failed to parse analysisElementOriginalIds from analysis package, possibly invalid format", e);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.filter(Boolean) as string[];
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This will fetch 100 products to the Next.js cache and sort them based on the sortBy parameter.
|
* This will fetch 100 products to the Next.js cache and sort them based on the sortBy parameter.
|
||||||
* It will then return the paginated products based on the page and limit parameters.
|
* It will then return the paginated products based on the page and limit parameters.
|
||||||
|
|||||||
Reference in New Issue
Block a user