feat(dashboard, api): integrate BMI thresholds and enhance dashboard with health metrics
This commit is contained in:
@@ -1,14 +1,18 @@
|
||||
import { redirect } from 'next/navigation';
|
||||
|
||||
import { toTitleCase } from '@/lib/utils';
|
||||
import { createAccountsApi } from '@/packages/features/accounts/src/server/api';
|
||||
import { getSupabaseServerClient } from '@/packages/supabase/src/clients/server-client';
|
||||
|
||||
import { PageBody, PageHeader } from '@kit/ui/page';
|
||||
import { Trans } from '@kit/ui/trans';
|
||||
|
||||
import { createI18nServerInstance } from '~/lib/i18n/i18n.server';
|
||||
import { withI18n } from '~/lib/i18n/with-i18n';
|
||||
import { PageBody, PageHeader } from '@kit/ui/page';
|
||||
import { Trans } from '@kit/ui/trans';
|
||||
import { toTitleCase } from '@/lib/utils';
|
||||
|
||||
import Dashboard from '../_components/dashboard';
|
||||
import { loadCurrentUserAccount } from '../_lib/server/load-user-account';
|
||||
import DashboardCards from '../_components/dashboard-cards';
|
||||
import { loadCurrentUserAccount } from '../_lib/server/load-user-account';
|
||||
|
||||
export const generateMetadata = async () => {
|
||||
const i18n = await createI18nServerInstance();
|
||||
@@ -20,7 +24,12 @@ export const generateMetadata = async () => {
|
||||
};
|
||||
|
||||
async function UserHomePage() {
|
||||
const client = getSupabaseServerClient();
|
||||
|
||||
const account = await loadCurrentUserAccount();
|
||||
const api = await createAccountsApi(client);
|
||||
const bmiThresholds = await api.fetchBmiThresholds();
|
||||
|
||||
if (!account) {
|
||||
redirect('/');
|
||||
}
|
||||
@@ -28,18 +37,17 @@ async function UserHomePage() {
|
||||
return (
|
||||
<>
|
||||
<DashboardCards />
|
||||
<PageHeader title={
|
||||
<>
|
||||
<Trans i18nKey={'common:welcome'} />
|
||||
{account.name ? `, ${toTitleCase(account.name)}` : ''}
|
||||
</>
|
||||
}
|
||||
description={
|
||||
<Trans i18nKey={'dashboard:recentlyCheckedDescription'} />
|
||||
<PageHeader
|
||||
title={
|
||||
<>
|
||||
<Trans i18nKey={'common:welcome'} />
|
||||
{account.name ? `, ${toTitleCase(account.name)}` : ''}
|
||||
</>
|
||||
}
|
||||
description={<Trans i18nKey={'dashboard:recentlyCheckedDescription'} />}
|
||||
/>
|
||||
<PageBody>
|
||||
<Dashboard account={account} />
|
||||
<Dashboard account={account} bmiThresholds={bmiThresholds} />
|
||||
</PageBody>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -4,14 +4,13 @@ import Link from 'next/link';
|
||||
|
||||
import { InfoTooltip } from '@/components/ui/info-tooltip';
|
||||
import type { AccountWithParams } from '@/packages/features/accounts/src/server/api';
|
||||
import { Database } from '@/packages/supabase/src/database.types';
|
||||
import { BlendingModeIcon, RulerHorizontalIcon } from '@radix-ui/react-icons';
|
||||
import Isikukood from 'isikukood';
|
||||
import {
|
||||
Activity,
|
||||
ChevronRight,
|
||||
Clock9,
|
||||
Droplets,
|
||||
LineChart,
|
||||
Pill,
|
||||
Scale,
|
||||
TrendingUp,
|
||||
@@ -25,95 +24,96 @@ import {
|
||||
CardDescription,
|
||||
CardFooter,
|
||||
CardHeader,
|
||||
CardProps,
|
||||
} from '@kit/ui/card';
|
||||
import { Trans } from '@kit/ui/trans';
|
||||
import { cn } from '@kit/ui/utils';
|
||||
|
||||
import { BmiCategory } from '~/lib/types/bmi';
|
||||
import {
|
||||
bmiFromMetric,
|
||||
getBmiBackgroundColor,
|
||||
getBmiStatus,
|
||||
} from '~/lib/utils';
|
||||
|
||||
const cards = ({
|
||||
gender,
|
||||
age,
|
||||
height,
|
||||
weight,
|
||||
bmiStatus,
|
||||
}: {
|
||||
gender?: string;
|
||||
age?: number;
|
||||
height?: number | null;
|
||||
weight?: number | null;
|
||||
}) => {
|
||||
const heightInMeters = height ? height / 100 : null;
|
||||
const bmi =
|
||||
heightInMeters && weight
|
||||
? (weight / (heightInMeters * heightInMeters)).toFixed(1)
|
||||
: null;
|
||||
return [
|
||||
{
|
||||
title: 'dashboard:gender',
|
||||
description: gender ?? 'dashboard:male',
|
||||
icon: <User />,
|
||||
iconBg: 'bg-success',
|
||||
},
|
||||
{
|
||||
title: 'dashboard:age',
|
||||
description: age ? `${age}` : '-',
|
||||
icon: <Clock9 />,
|
||||
iconBg: 'bg-success',
|
||||
},
|
||||
{
|
||||
title: 'dashboard:height',
|
||||
description: height ? `${height}cm` : '-',
|
||||
icon: <RulerHorizontalIcon className="size-4" />,
|
||||
iconBg: 'bg-success',
|
||||
},
|
||||
{
|
||||
title: 'dashboard:weight',
|
||||
description: weight ? `${weight}kg` : '-',
|
||||
icon: <Scale />,
|
||||
iconBg: 'bg-success',
|
||||
},
|
||||
{
|
||||
title: 'dashboard:bmi',
|
||||
description: bmi,
|
||||
icon: <TrendingUp />,
|
||||
iconBg: 'bg-success',
|
||||
},
|
||||
{
|
||||
title: 'dashboard:bloodPressure',
|
||||
description: '-',
|
||||
icon: <Activity />,
|
||||
iconBg: 'bg-warning',
|
||||
},
|
||||
{
|
||||
title: 'dashboard:cholesterol',
|
||||
description: '-',
|
||||
icon: <BlendingModeIcon className="size-4" />,
|
||||
iconBg: 'bg-destructive',
|
||||
},
|
||||
{
|
||||
title: 'dashboard:ldlCholesterol',
|
||||
description: '-',
|
||||
icon: <Pill />,
|
||||
iconBg: 'bg-warning',
|
||||
},
|
||||
// {
|
||||
// title: 'Score 2',
|
||||
// description: 'Normis',
|
||||
// icon: <LineChart />,
|
||||
// iconBg: 'bg-success',
|
||||
// },
|
||||
// {
|
||||
// title: 'dashboard:smoking',
|
||||
// description: 'dashboard:respondToQuestion',
|
||||
// descriptionColor: 'text-primary',
|
||||
// icon: (
|
||||
// <Button size="icon" variant="outline" className="px-2 text-black">
|
||||
// <ChevronRight className="size-4 stroke-2" />
|
||||
// </Button>
|
||||
// ),
|
||||
// cardVariant: 'gradient-success' as CardProps['variant'],
|
||||
// },
|
||||
];
|
||||
};
|
||||
bmiStatus: BmiCategory | null;
|
||||
}) => [
|
||||
{
|
||||
title: 'dashboard:gender',
|
||||
description: gender ?? 'dashboard:male',
|
||||
icon: <User />,
|
||||
iconBg: 'bg-success',
|
||||
},
|
||||
{
|
||||
title: 'dashboard:age',
|
||||
description: age ? `${age}` : '-',
|
||||
icon: <Clock9 />,
|
||||
iconBg: 'bg-success',
|
||||
},
|
||||
{
|
||||
title: 'dashboard:height',
|
||||
description: height ? `${height}cm` : '-',
|
||||
icon: <RulerHorizontalIcon className="size-4" />,
|
||||
iconBg: 'bg-success',
|
||||
},
|
||||
{
|
||||
title: 'dashboard:weight',
|
||||
description: weight ? `${weight}kg` : '-',
|
||||
icon: <Scale />,
|
||||
iconBg: 'bg-success',
|
||||
},
|
||||
{
|
||||
title: 'dashboard:bmi',
|
||||
description: bmiFromMetric(weight || 0, height || 0).toString(),
|
||||
icon: <TrendingUp />,
|
||||
iconBg: getBmiBackgroundColor(bmiStatus),
|
||||
},
|
||||
{
|
||||
title: 'dashboard:bloodPressure',
|
||||
description: '-',
|
||||
icon: <Activity />,
|
||||
iconBg: 'bg-warning',
|
||||
},
|
||||
{
|
||||
title: 'dashboard:cholesterol',
|
||||
description: '-',
|
||||
icon: <BlendingModeIcon className="size-4" />,
|
||||
iconBg: 'bg-destructive',
|
||||
},
|
||||
{
|
||||
title: 'dashboard:ldlCholesterol',
|
||||
description: '-',
|
||||
icon: <Pill />,
|
||||
iconBg: 'bg-warning',
|
||||
},
|
||||
// {
|
||||
// title: 'Score 2',
|
||||
// description: 'Normis',
|
||||
// icon: <LineChart />,
|
||||
// iconBg: 'bg-success',
|
||||
// },
|
||||
// {
|
||||
// title: 'dashboard:smoking',
|
||||
// description: 'dashboard:respondToQuestion',
|
||||
// descriptionColor: 'text-primary',
|
||||
// icon: (
|
||||
// <Button size="icon" variant="outline" className="px-2 text-black">
|
||||
// <ChevronRight className="size-4 stroke-2" />
|
||||
// </Button>
|
||||
// ),
|
||||
// cardVariant: 'gradient-success' as CardProps['variant'],
|
||||
// },
|
||||
];
|
||||
|
||||
const dummyRecommendations = [
|
||||
{
|
||||
@@ -160,8 +160,22 @@ const getPersonParameters = (personalCode: string) => {
|
||||
}
|
||||
};
|
||||
|
||||
export default function Dashboard({ account }: { account: AccountWithParams }) {
|
||||
export default function Dashboard({
|
||||
account,
|
||||
bmiThresholds,
|
||||
}: {
|
||||
account: AccountWithParams;
|
||||
bmiThresholds: Omit<
|
||||
Database['medreport']['Tables']['bmi_thresholds']['Row'],
|
||||
'id'
|
||||
>[];
|
||||
}) {
|
||||
const params = getPersonParameters(account.personal_code!);
|
||||
const bmiStatus = getBmiStatus(bmiThresholds, {
|
||||
age: params?.age || 0,
|
||||
height: account.account_params?.[0]?.height || 0,
|
||||
weight: account.account_params?.[0]?.weight || 0,
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -171,18 +185,19 @@ export default function Dashboard({ account }: { account: AccountWithParams }) {
|
||||
age: params?.age,
|
||||
height: account.account_params?.[0]?.height,
|
||||
weight: account.account_params?.[0]?.weight,
|
||||
bmiStatus,
|
||||
}).map(
|
||||
({
|
||||
title,
|
||||
description,
|
||||
icon,
|
||||
iconBg,
|
||||
cardVariant,
|
||||
descriptionColor,
|
||||
// cardVariant,
|
||||
// descriptionColor,
|
||||
}) => (
|
||||
<Card
|
||||
key={title}
|
||||
variant={cardVariant}
|
||||
// variant={cardVariant}
|
||||
className="flex flex-col justify-between"
|
||||
>
|
||||
<CardHeader className="items-end-safe">
|
||||
@@ -199,7 +214,9 @@ export default function Dashboard({ account }: { account: AccountWithParams }) {
|
||||
<h5>
|
||||
<Trans i18nKey={title} />
|
||||
</h5>
|
||||
<CardDescription className={descriptionColor}>
|
||||
<CardDescription
|
||||
// className={descriptionColor}
|
||||
>
|
||||
<Trans i18nKey={description} />
|
||||
</CardDescription>
|
||||
</CardFooter>
|
||||
|
||||
@@ -2,6 +2,8 @@ import React from 'react';
|
||||
|
||||
import { Clock, TrendingUp, User } from 'lucide-react';
|
||||
|
||||
import { bmiFromMetric } from '~/lib/utils';
|
||||
|
||||
import { TeamAccountStatisticsProps } from '../../_components/team-account-statistics';
|
||||
|
||||
interface AccountHealthDetailsField {
|
||||
@@ -26,9 +28,7 @@ export const getAccountHealthDetailsFields = (
|
||||
): AccountHealthDetailsField[] => {
|
||||
const averageBMI = (
|
||||
memberParams.reduce((sum, { height, weight }) => {
|
||||
const hMeters = height! / 100;
|
||||
const bmi = weight! / (hMeters * hMeters);
|
||||
return sum + bmi;
|
||||
return bmiFromMetric(weight ?? 0, height ?? 0) + sum;
|
||||
}, 0) / memberParams.length
|
||||
).toFixed(0);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user