feat(dashboard, api): integrate BMI thresholds and enhance dashboard with health metrics

This commit is contained in:
Danel Kungla
2025-08-21 22:09:17 +03:00
parent b1b0846234
commit 1fb8df7c89
9 changed files with 269 additions and 101 deletions

View File

@@ -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>
</>
);

View File

@@ -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>

View File

@@ -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);