MED-105: create analysis results page
This commit is contained in:
@@ -0,0 +1,89 @@
|
||||
import React from 'react';
|
||||
|
||||
import { ArrowDown } from 'lucide-react';
|
||||
|
||||
import { cn } from '@kit/ui/utils';
|
||||
|
||||
export enum AnalysisResultLevel {
|
||||
VERY_LOW = 0,
|
||||
LOW = 1,
|
||||
NORMAL = 2,
|
||||
HIGH = 3,
|
||||
VERY_HIGH = 4,
|
||||
}
|
||||
|
||||
const Level = ({
|
||||
isActive = false,
|
||||
color,
|
||||
isFirst = false,
|
||||
isLast = false,
|
||||
}: {
|
||||
isActive?: boolean;
|
||||
color: 'destructive' | 'success' | 'warning';
|
||||
isFirst?: boolean;
|
||||
isLast?: boolean;
|
||||
}) => {
|
||||
return (
|
||||
<div
|
||||
className={cn(`bg-${color} relative h-3 flex-1`, {
|
||||
'opacity-20': !isActive,
|
||||
'rounded-l-lg': isFirst,
|
||||
'rounded-r-lg': isLast,
|
||||
})}
|
||||
>
|
||||
{isActive && (
|
||||
<div className="absolute top-[-14px] left-1/2 -translate-x-1/2 rounded-[10px] bg-white p-[2px]">
|
||||
<ArrowDown strokeWidth={2} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const AnalysisLevelBar = ({
|
||||
normLowerIncluded = true,
|
||||
normUpperIncluded = true,
|
||||
level,
|
||||
}: {
|
||||
normLowerIncluded?: boolean;
|
||||
normUpperIncluded?: boolean;
|
||||
level: AnalysisResultLevel;
|
||||
}) => {
|
||||
return (
|
||||
<div className="flex h-3 w-full max-w-[360px] gap-1">
|
||||
{normLowerIncluded && (
|
||||
<>
|
||||
<Level
|
||||
isActive={level === AnalysisResultLevel.VERY_LOW}
|
||||
color="destructive"
|
||||
isFirst
|
||||
/>
|
||||
<Level isActive={level === AnalysisResultLevel.LOW} color="warning" />
|
||||
</>
|
||||
)}
|
||||
|
||||
<Level
|
||||
isFirst={!normLowerIncluded}
|
||||
isLast={!normUpperIncluded}
|
||||
isActive={level === AnalysisResultLevel.NORMAL}
|
||||
color="success"
|
||||
/>
|
||||
|
||||
{normUpperIncluded && (
|
||||
<>
|
||||
<Level
|
||||
isActive={level === AnalysisResultLevel.HIGH}
|
||||
color="warning"
|
||||
/>
|
||||
<Level
|
||||
isActive={level === AnalysisResultLevel.VERY_HIGH}
|
||||
color="destructive"
|
||||
isLast
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AnalysisLevelBar;
|
||||
@@ -0,0 +1,84 @@
|
||||
import React from 'react';
|
||||
|
||||
import { Info } from 'lucide-react';
|
||||
|
||||
import AnalysisLevelBar, { AnalysisResultLevel } from './analysis-level-bar';
|
||||
|
||||
export enum AnalysisStatus {
|
||||
NORMAL = 0,
|
||||
MEDIUM = 1,
|
||||
HIGH = 2,
|
||||
}
|
||||
|
||||
const Analysis = ({
|
||||
analysis: {
|
||||
name,
|
||||
status,
|
||||
unit,
|
||||
value,
|
||||
normLowerIncluded,
|
||||
normUpperIncluded,
|
||||
normLower,
|
||||
normUpper,
|
||||
},
|
||||
}: {
|
||||
analysis: {
|
||||
name: string;
|
||||
status: AnalysisStatus;
|
||||
unit: string;
|
||||
value: number;
|
||||
normLowerIncluded: boolean;
|
||||
normUpperIncluded: boolean;
|
||||
normLower: number;
|
||||
normUpper: number;
|
||||
};
|
||||
}) => {
|
||||
const isUnderNorm = value < normLower;
|
||||
const getAnalysisResultLevel = () => {
|
||||
if (isUnderNorm) {
|
||||
switch (status) {
|
||||
case AnalysisStatus.MEDIUM:
|
||||
return AnalysisResultLevel.LOW;
|
||||
default:
|
||||
return AnalysisResultLevel.VERY_LOW;
|
||||
}
|
||||
}
|
||||
switch (status) {
|
||||
case AnalysisStatus.MEDIUM:
|
||||
return AnalysisResultLevel.HIGH;
|
||||
case AnalysisStatus.HIGH:
|
||||
return AnalysisResultLevel.VERY_HIGH;
|
||||
default:
|
||||
return AnalysisResultLevel.NORMAL;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="border-border flex items-center justify-between rounded-lg border px-5 py-3">
|
||||
<div className="flex items-center gap-2 font-semibold">
|
||||
{name}
|
||||
<div className="group/tooltip relative">
|
||||
<Info className="hover" />{' '}
|
||||
<div className="absolute bottom-full left-1/2 z-10 mb-2 hidden -translate-x-1/2 rounded bg-gray-800 px-2 py-1 text-sm whitespace-nowrap text-white group-hover/tooltip:block">
|
||||
This text changes when you hover the box above.
|
||||
</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 text-center text-sm">
|
||||
{normLower} - {normUpper}
|
||||
<div>Normaalne vahemik</div>
|
||||
</div>
|
||||
<AnalysisLevelBar
|
||||
normLowerIncluded={normLowerIncluded}
|
||||
normUpperIncluded={normUpperIncluded}
|
||||
level={getAnalysisResultLevel()}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Analysis;
|
||||
59
app/home/(user)/(dashboard)/analysis-results/page.tsx
Normal file
59
app/home/(user)/(dashboard)/analysis-results/page.tsx
Normal file
@@ -0,0 +1,59 @@
|
||||
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 { loadUserAnalysis } from '../../_lib/server/load-user-analysis';
|
||||
import Analysis, { AnalysisStatus } from './_components/analysis';
|
||||
|
||||
export const generateMetadata = async () => {
|
||||
const i18n = await createI18nServerInstance();
|
||||
const title = i18n.t('account:analysisResults.pageTitle');
|
||||
|
||||
return {
|
||||
title,
|
||||
};
|
||||
};
|
||||
|
||||
async function AnalysisResultsPage() {
|
||||
const analysisList = await loadUserAnalysis();
|
||||
|
||||
return (
|
||||
<PageBody>
|
||||
<div className="mt-8 flex items-center justify-between">
|
||||
<div>
|
||||
<h4>
|
||||
<Trans i18nKey="account:analysisResults.pageTitle" />
|
||||
</h4>
|
||||
<p className="text-muted-foreground text-sm">
|
||||
<Trans i18nKey="account:analysisResults.description" />
|
||||
</p>
|
||||
</div>
|
||||
<Button>
|
||||
<Trans i18nKey="account:analysisResults.orderNewAnalysis" />
|
||||
</Button>
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
{analysisList?.map((analysis, index) => (
|
||||
<Analysis
|
||||
key={index}
|
||||
analysis={{
|
||||
name: analysis.element.analysis_name || '',
|
||||
status: analysis.element.norm_status as AnalysisStatus,
|
||||
unit: analysis.element.unit || '',
|
||||
value: analysis.element.response_value,
|
||||
normLowerIncluded: !!analysis.element.norm_lower_included,
|
||||
normUpperIncluded: !!analysis.element.norm_upper_included,
|
||||
normLower: analysis.element.norm_lower || 0,
|
||||
normUpper: analysis.element.norm_upper || 0,
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</PageBody>
|
||||
);
|
||||
}
|
||||
|
||||
export default withI18n(AnalysisResultsPage);
|
||||
22
app/home/(user)/_lib/server/load-user-analysis.ts
Normal file
22
app/home/(user)/_lib/server/load-user-analysis.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 UserAccount = Awaited<ReturnType<typeof loadUserAnalysis>>;
|
||||
|
||||
/**
|
||||
* @name loadUserAccount
|
||||
* @description
|
||||
* Load the user account. 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 loadUserAnalysis = cache(analysisLoader);
|
||||
|
||||
async function analysisLoader(): Promise<UserAnalysis | null> {
|
||||
const client = getSupabaseServerClient();
|
||||
const api = createAccountsApi(client);
|
||||
|
||||
return api.getUserAnalysis();
|
||||
}
|
||||
Reference in New Issue
Block a user