feat(MED-105): update analysis results page
This commit is contained in:
@@ -19,7 +19,7 @@ const Level = ({
|
||||
isLast = false,
|
||||
}: {
|
||||
isActive?: boolean;
|
||||
color: 'destructive' | 'success' | 'warning';
|
||||
color: 'destructive' | 'success' | 'warning' | 'gray-200';
|
||||
isFirst?: boolean;
|
||||
isLast?: boolean;
|
||||
}) => {
|
||||
@@ -40,6 +40,14 @@ const Level = ({
|
||||
);
|
||||
};
|
||||
|
||||
export const AnalysisLevelBarSkeleton = () => {
|
||||
return (
|
||||
<div className="mt-4 flex h-3 max-w-[360px] w-[35%] gap-1 sm:mt-0">
|
||||
<Level color="gray-200" />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const AnalysisLevelBar = ({
|
||||
normLowerIncluded = true,
|
||||
normUpperIncluded = true,
|
||||
@@ -50,7 +58,7 @@ const AnalysisLevelBar = ({
|
||||
level: AnalysisResultLevel;
|
||||
}) => {
|
||||
return (
|
||||
<div className="mt-4 flex h-3 w-full max-w-[360px] gap-1 sm:mt-0">
|
||||
<div className="mt-4 flex h-3 max-w-[360px] w-[35%] gap-1 sm:mt-0">
|
||||
{normLowerIncluded && (
|
||||
<>
|
||||
<Level
|
||||
|
||||
@@ -6,7 +6,10 @@ import { Info } from 'lucide-react';
|
||||
|
||||
import { cn } from '@kit/ui/utils';
|
||||
|
||||
import AnalysisLevelBar, { AnalysisResultLevel } from './analysis-level-bar';
|
||||
import AnalysisLevelBar, { AnalysisLevelBarSkeleton, AnalysisResultLevel } from './analysis-level-bar';
|
||||
import { UserAnalysisElement } from '@/packages/features/accounts/src/types/accounts';
|
||||
import { AnalysisElement } from '~/lib/services/analysis-element.service';
|
||||
import { Trans } from '@kit/ui/trans';
|
||||
|
||||
export enum AnalysisStatus {
|
||||
NORMAL = 0,
|
||||
@@ -15,31 +18,27 @@ export enum AnalysisStatus {
|
||||
}
|
||||
|
||||
const Analysis = ({
|
||||
analysis: {
|
||||
name,
|
||||
status,
|
||||
unit,
|
||||
value,
|
||||
normLowerIncluded,
|
||||
normUpperIncluded,
|
||||
normLower,
|
||||
normUpper,
|
||||
},
|
||||
analysisElement,
|
||||
results,
|
||||
}: {
|
||||
analysis: {
|
||||
name: string;
|
||||
status: AnalysisStatus;
|
||||
unit: string;
|
||||
value: number;
|
||||
normLowerIncluded: boolean;
|
||||
normUpperIncluded: boolean;
|
||||
normLower: number;
|
||||
normUpper: number;
|
||||
};
|
||||
analysisElement: AnalysisElement;
|
||||
results?: UserAnalysisElement;
|
||||
}) => {
|
||||
const name = analysisElement.analysis_name_lab || '';
|
||||
const status = results?.norm_status || AnalysisStatus.NORMAL;
|
||||
const value = results?.response_value || 0;
|
||||
const unit = results?.unit || '';
|
||||
const normLowerIncluded = results?.norm_lower_included || false;
|
||||
const normUpperIncluded = results?.norm_upper_included || false;
|
||||
const normLower = results?.norm_lower || 0;
|
||||
const normUpper = results?.norm_upper || 0;
|
||||
|
||||
const [showTooltip, setShowTooltip] = useState(false);
|
||||
const isUnderNorm = value < normLower;
|
||||
const getAnalysisResultLevel = () => {
|
||||
if (!results) {
|
||||
return null;
|
||||
}
|
||||
if (isUnderNorm) {
|
||||
switch (status) {
|
||||
case AnalysisStatus.MEDIUM:
|
||||
@@ -59,7 +58,7 @@ const Analysis = ({
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="border-border grid grid-cols-2 items-center justify-between rounded-lg border px-5 py-3 sm:flex">
|
||||
<div className="border-border items-center justify-between rounded-lg border px-5 py-3 sm:h-[65px] flex flex-col sm:flex-row px-12 gap-2 sm:gap-0">
|
||||
<div className="flex items-center gap-2 font-semibold">
|
||||
{name}
|
||||
<div
|
||||
@@ -78,19 +77,35 @@ const Analysis = ({
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="font-semibold">{value}</div>
|
||||
<div className="text-muted-foreground text-sm">{unit}</div>
|
||||
</div>
|
||||
<div className="text-muted-foreground mt-4 flex gap-2 text-center text-sm sm:mt-0 sm:block sm:gap-0">
|
||||
{normLower} - {normUpper}
|
||||
<div>Normaalne vahemik</div>
|
||||
</div>
|
||||
<AnalysisLevelBar
|
||||
normLowerIncluded={normLowerIncluded}
|
||||
normUpperIncluded={normUpperIncluded}
|
||||
level={getAnalysisResultLevel()}
|
||||
/>
|
||||
{results ? (
|
||||
<>
|
||||
<div className="flex items-center gap-3 sm:ml-auto">
|
||||
<div className="font-semibold">{value}</div>
|
||||
<div className="text-muted-foreground text-sm">{unit}</div>
|
||||
</div>
|
||||
<div className="text-muted-foreground flex flex-col-reverse gap-2 text-center text-sm sm:block sm:gap-0 mx-8">
|
||||
{normLower} - {normUpper}
|
||||
<div>
|
||||
<Trans i18nKey="analysis-results:results.range.normal" />
|
||||
</div>
|
||||
</div>
|
||||
<AnalysisLevelBar
|
||||
normLowerIncluded={normLowerIncluded}
|
||||
normUpperIncluded={normUpperIncluded}
|
||||
level={getAnalysisResultLevel()!}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<div className="flex items-center gap-3 sm:ml-auto">
|
||||
<div className="font-semibold">
|
||||
<Trans i18nKey="analysis-results:waitingForResults" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-[60px] mx-8"></div>
|
||||
<AnalysisLevelBarSkeleton />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Fragment } from 'react';
|
||||
import Link from 'next/link';
|
||||
|
||||
import { createI18nServerInstance } from '@/lib/i18n/i18n.server';
|
||||
import { withI18n } from '@/lib/i18n/with-i18n';
|
||||
|
||||
@@ -7,11 +8,17 @@ import { PageBody } from '@kit/ui/page';
|
||||
import { Button } from '@kit/ui/shadcn/button';
|
||||
|
||||
import { loadUserAnalysis } from '../../_lib/server/load-user-analysis';
|
||||
import Analysis, { AnalysisStatus } from './_components/analysis';
|
||||
import Analysis from './_components/analysis';
|
||||
import { listProductTypes } from '@lib/data/products';
|
||||
import pathsConfig from '~/config/paths.config';
|
||||
import { redirect } from 'next/navigation';
|
||||
import { getOrders } from '~/lib/services/order.service';
|
||||
import { AnalysisElement, getAnalysisElements } from '~/lib/services/analysis-element.service';
|
||||
import type { UserAnalysisElement } from '@kit/accounts/types/accounts';
|
||||
|
||||
export const generateMetadata = async () => {
|
||||
const i18n = await createI18nServerInstance();
|
||||
const title = i18n.t('account:analysisResults.pageTitle');
|
||||
const title = i18n.t('analysis-results:pageTitle');
|
||||
|
||||
return {
|
||||
title,
|
||||
@@ -21,45 +28,56 @@ export const generateMetadata = async () => {
|
||||
async function AnalysisResultsPage() {
|
||||
const analysisList = await loadUserAnalysis();
|
||||
|
||||
const orders = await getOrders().catch(() => null);
|
||||
const { productTypes } = await listProductTypes();
|
||||
|
||||
if (!orders || !productTypes) {
|
||||
redirect(pathsConfig.auth.signIn);
|
||||
}
|
||||
|
||||
const analysisElementIds = [
|
||||
...new Set(orders?.flatMap((order) => order.analysis_element_ids).filter(Boolean) as number[]),
|
||||
];
|
||||
const analysisElements = await getAnalysisElements({ ids: analysisElementIds });
|
||||
const analysisElementsWithResults = analysisElements.reduce((acc, curr) => {
|
||||
const analysisResponseWithElementResults = analysisList?.find((result) => result.elements.some((element) => element.analysis_element_original_id === curr.analysis_id_original));
|
||||
const elementResults = analysisResponseWithElementResults?.elements.find((element) => element.analysis_element_original_id === curr.analysis_id_original) as UserAnalysisElement | undefined;
|
||||
return {
|
||||
...acc,
|
||||
[curr.id]: {
|
||||
analysisElement: curr,
|
||||
results: elementResults,
|
||||
},
|
||||
}
|
||||
}, {} as Record<number, { analysisElement: AnalysisElement; results: UserAnalysisElement | undefined }>);
|
||||
|
||||
return (
|
||||
<PageBody>
|
||||
<div className="mt-8 flex items-center justify-between">
|
||||
<div className="mt-8 flex flex-col sm:flex-row sm:items-center justify-between gap-4 sm:gap-0">
|
||||
<div>
|
||||
<h4>
|
||||
<Trans i18nKey="account:analysisResults.pageTitle" />
|
||||
<Trans i18nKey="analysis-results:pageTitle" />
|
||||
</h4>
|
||||
<p className="text-muted-foreground text-sm">
|
||||
{analysisList && analysisList.length > 0 ? (
|
||||
<Trans i18nKey="account:analysisResults.description" />
|
||||
<Trans i18nKey="analysis-results:description" />
|
||||
) : (
|
||||
<Trans i18nKey="account:analysisResults.descriptionEmpty" />
|
||||
<Trans i18nKey="analysis-results:descriptionEmpty" />
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
<Button>
|
||||
<Trans i18nKey="account:analysisResults.orderNewAnalysis" />
|
||||
<Button asChild>
|
||||
<Link href={pathsConfig.app.orderAnalysis}>
|
||||
<Trans i18nKey="analysis-results:orderNewAnalysis" />
|
||||
</Link>
|
||||
</Button>
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
{analysisList?.map((analysis) => (
|
||||
<Fragment key={analysis.id}>
|
||||
{analysis.elements.map((element) => (
|
||||
<Analysis
|
||||
key={element.id}
|
||||
analysis={{
|
||||
name: element.analysis_name || '',
|
||||
status: element.norm_status as AnalysisStatus,
|
||||
unit: element.unit || '',
|
||||
value: element.response_value,
|
||||
normLowerIncluded: !!element.norm_lower_included,
|
||||
normUpperIncluded: !!element.norm_upper_included,
|
||||
normLower: element.norm_lower || 0,
|
||||
normUpper: element.norm_upper || 0,
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</Fragment>
|
||||
))}
|
||||
{Object.entries(analysisElementsWithResults).map(([id, { analysisElement, results }]) => {
|
||||
return (
|
||||
<Analysis key={id} analysisElement={analysisElement} results={results} />
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</PageBody>
|
||||
);
|
||||
|
||||
@@ -39,6 +39,7 @@ export const defaultI18nNamespaces = [
|
||||
'order-analysis',
|
||||
'cart',
|
||||
'orders',
|
||||
'analysis-results',
|
||||
];
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
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';
|
||||
@@ -9,10 +8,12 @@ export type AnalysisElement = Tables<{ schema: 'medreport' }, 'analysis_elements
|
||||
|
||||
export async function getAnalysisElements({
|
||||
originalIds,
|
||||
ids,
|
||||
}: {
|
||||
originalIds?: string[];
|
||||
ids?: number[];
|
||||
}): Promise<AnalysisElement[]> {
|
||||
const query = getSupabaseServerClient()
|
||||
const query = getSupabaseServerAdminClient()
|
||||
.schema('medreport')
|
||||
.from('analysis_elements')
|
||||
.select(`*, analysis_groups(*)`)
|
||||
@@ -22,6 +23,10 @@ export async function getAnalysisElements({
|
||||
query.in('analysis_id_original', [...new Set(originalIds)]);
|
||||
}
|
||||
|
||||
if (Array.isArray(ids)) {
|
||||
query.in('id', ids);
|
||||
}
|
||||
|
||||
const { data: analysisElements, error } = await query;
|
||||
|
||||
if (error) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Database } from '@kit/supabase/database';
|
||||
|
||||
export type UserAnalysis =
|
||||
(Database['medreport']['Tables']['analysis_responses']['Row'] & {
|
||||
elements: Database['medreport']['Tables']['analysis_response_elements']['Row'][];
|
||||
})[];
|
||||
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[];
|
||||
|
||||
@@ -42,7 +42,7 @@ function PageWithSidebar(props: PageProps) {
|
||||
>
|
||||
{MobileNavigation}
|
||||
|
||||
<div className={'bg-background flex flex-1 flex-col px-4 lg:px-0'}>
|
||||
<div className={'bg-background flex flex-1 flex-col px-4 lg:px-0 pb-8'}>
|
||||
{Children}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -122,11 +122,5 @@
|
||||
"consentToAnonymizedCompanyData": {
|
||||
"label": "Consent to be included in employer statistics",
|
||||
"description": "Consent to be included in anonymized company statistics"
|
||||
},
|
||||
"analysisResults": {
|
||||
"pageTitle": "My analysis results",
|
||||
"description": "Super, you've already done your analysis. Here are your important results:",
|
||||
"descriptionEmpty": "If you've already done your analysis, your results will appear here soon.",
|
||||
"orderNewAnalysis": "Order new analyses"
|
||||
}
|
||||
}
|
||||
|
||||
12
public/locales/en/analysis-results.json
Normal file
12
public/locales/en/analysis-results.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"pageTitle": "My analysis results",
|
||||
"description": "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",
|
||||
"results": {
|
||||
"range": {
|
||||
"normal": "Normal range"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -145,11 +145,5 @@
|
||||
"successTitle": "Tere, {{firstName}} {{lastName}}",
|
||||
"successDescription": "Teie tervisekonto on aktiveeritud ja kasutamiseks valmis!",
|
||||
"successButton": "Jätka"
|
||||
},
|
||||
"analysisResults": {
|
||||
"pageTitle": "Minu analüüside vastused",
|
||||
"description": "Super, oled käinud tervist kontrollimas. Siin on sinule olulised näitajad:",
|
||||
"descriptionEmpty": "Kui oled juba käinud analüüse andmas, siis varsti jõuavad siia sinu analüüside vastused.",
|
||||
"orderNewAnalysis": "Telli uued analüüsid"
|
||||
}
|
||||
}
|
||||
|
||||
12
public/locales/et/analysis-results.json
Normal file
12
public/locales/et/analysis-results.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"pageTitle": "Minu analüüside vastused",
|
||||
"description": "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",
|
||||
"results": {
|
||||
"range": {
|
||||
"normal": "Normaalne vahemik"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -135,8 +135,8 @@
|
||||
--animate-accordion-down: accordion-down 0.2s ease-out;
|
||||
--animate-accordion-up: accordion-up 0.2s ease-out;
|
||||
|
||||
--breakpoint-xs: 30rem;
|
||||
--breakpoint-sm: 48rem;
|
||||
--breakpoint-xs: 48rem;
|
||||
--breakpoint-sm: 64rem;
|
||||
--breakpoint-md: 70rem;
|
||||
--breakpoint-lg: 80rem;
|
||||
--breakpoint-xl: 96rem;
|
||||
|
||||
@@ -5,7 +5,7 @@ export const getAnalysisElementMedusaProductIds = (products: ({ metadata?: { ana
|
||||
|
||||
const mapped = products
|
||||
.flatMap((product) => {
|
||||
const value = product?.metadata?.analysisElementMedusaProductIds;
|
||||
const value = product?.metadata?.analysisElementMedusaProductIds?.replaceAll("'", '"');
|
||||
try {
|
||||
return JSON.parse(value as string);
|
||||
} catch (e) {
|
||||
|
||||
Reference in New Issue
Block a user