feat: implement order notifications service with TTO reservation confirmation handling feat: create migration for TTO booking email webhook trigger
211 lines
5.1 KiB
TypeScript
211 lines
5.1 KiB
TypeScript
'use client';
|
|
|
|
import Link from 'next/link';
|
|
|
|
import { BlendingModeIcon, RulerHorizontalIcon } from '@radix-ui/react-icons';
|
|
import { isNil } from 'lodash';
|
|
import {
|
|
Activity,
|
|
ChevronRight,
|
|
Clock9,
|
|
Pill,
|
|
Scale,
|
|
TrendingUp,
|
|
User,
|
|
} from 'lucide-react';
|
|
|
|
import type {
|
|
AccountWithParams,
|
|
BmiThresholds,
|
|
} from '@kit/accounts/types/accounts';
|
|
import { pathsConfig } from '@kit/shared/config';
|
|
import { Button } from '@kit/ui/button';
|
|
import {
|
|
Card,
|
|
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 PersonalCode, {
|
|
bmiFromMetric,
|
|
getBmiBackgroundColor,
|
|
getBmiStatus,
|
|
} from '~/lib/utils';
|
|
|
|
import DashboardRecommendations from './dashboard-recommendations';
|
|
|
|
const getCardVariant = (isSuccess: boolean | null): CardProps['variant'] => {
|
|
if (isSuccess === null) return 'default';
|
|
if (isSuccess) return 'gradient-success';
|
|
return 'gradient-destructive';
|
|
};
|
|
|
|
const cards = ({
|
|
gender,
|
|
age,
|
|
height,
|
|
weight,
|
|
bmiStatus,
|
|
smoking,
|
|
}: {
|
|
gender?: string;
|
|
age?: number;
|
|
height?: number | null;
|
|
weight?: number | null;
|
|
bmiStatus: BmiCategory | null;
|
|
smoking?: boolean | null;
|
|
}) => [
|
|
{
|
|
title: 'dashboard:gender',
|
|
description: gender ?? '-',
|
|
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: isNil(smoking)
|
|
? 'dashboard:respondToQuestion'
|
|
: smoking
|
|
? 'common:yes'
|
|
: 'common:no',
|
|
descriptionColor: 'text-primary',
|
|
icon: isNil(smoking) ? (
|
|
<Link href={pathsConfig.app.personalAccountSettings}>
|
|
<Button size="icon" variant="outline" className="px-2 text-black">
|
|
<ChevronRight className="size-4 stroke-2" />
|
|
</Button>
|
|
</Link>
|
|
) : null,
|
|
cardVariant: getCardVariant(isNil(smoking) ? null : !smoking),
|
|
},
|
|
];
|
|
|
|
const IS_SHOWN_RECOMMENDATIONS = false as boolean;
|
|
|
|
export default function Dashboard({
|
|
account,
|
|
bmiThresholds,
|
|
}: {
|
|
account: AccountWithParams;
|
|
bmiThresholds: Omit<BmiThresholds, 'id'>[];
|
|
}) {
|
|
const height = account.accountParams?.height || 0;
|
|
const weight = account.accountParams?.weight || 0;
|
|
|
|
let age: number = 0;
|
|
let gender: { label: string; value: string } | null = null;
|
|
try {
|
|
({ age = 0, gender } = PersonalCode.parsePersonalCode(
|
|
account.personal_code!,
|
|
));
|
|
} catch (e) {
|
|
console.error('Failed to parse personal code', e);
|
|
}
|
|
const bmiStatus = getBmiStatus(bmiThresholds, { age, height, weight });
|
|
|
|
return (
|
|
<>
|
|
<div className="xs:grid-cols-2 grid auto-rows-fr gap-3 sm:grid-cols-4 lg:grid-cols-5">
|
|
{cards({
|
|
gender: gender?.label,
|
|
age,
|
|
height,
|
|
weight,
|
|
bmiStatus,
|
|
smoking: account.accountParams?.isSmoker,
|
|
}).map(
|
|
({
|
|
title,
|
|
description,
|
|
icon,
|
|
iconBg,
|
|
cardVariant,
|
|
// descriptionColor,
|
|
}) => (
|
|
<Card
|
|
key={title}
|
|
variant={cardVariant}
|
|
className="flex flex-col justify-between"
|
|
>
|
|
<CardHeader className="items-end-safe">
|
|
<div
|
|
className={cn(
|
|
'flex size-8 items-center-safe justify-center-safe rounded-full text-white',
|
|
iconBg,
|
|
)}
|
|
>
|
|
{icon}
|
|
</div>
|
|
</CardHeader>
|
|
<CardFooter className="flex flex-col items-start">
|
|
<h5>
|
|
<Trans i18nKey={title} />
|
|
</h5>
|
|
<CardDescription
|
|
// className={descriptionColor}
|
|
>
|
|
<Trans i18nKey={description} />
|
|
</CardDescription>
|
|
</CardFooter>
|
|
</Card>
|
|
),
|
|
)}
|
|
</div>
|
|
{IS_SHOWN_RECOMMENDATIONS && <DashboardRecommendations />}
|
|
</>
|
|
);
|
|
}
|