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 { 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 { createI18nServerInstance } from '~/lib/i18n/i18n.server';
|
||||||
import { withI18n } from '~/lib/i18n/with-i18n';
|
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 Dashboard from '../_components/dashboard';
|
||||||
import { loadCurrentUserAccount } from '../_lib/server/load-user-account';
|
|
||||||
import DashboardCards from '../_components/dashboard-cards';
|
import DashboardCards from '../_components/dashboard-cards';
|
||||||
|
import { loadCurrentUserAccount } from '../_lib/server/load-user-account';
|
||||||
|
|
||||||
export const generateMetadata = async () => {
|
export const generateMetadata = async () => {
|
||||||
const i18n = await createI18nServerInstance();
|
const i18n = await createI18nServerInstance();
|
||||||
@@ -20,7 +24,12 @@ export const generateMetadata = async () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
async function UserHomePage() {
|
async function UserHomePage() {
|
||||||
|
const client = getSupabaseServerClient();
|
||||||
|
|
||||||
const account = await loadCurrentUserAccount();
|
const account = await loadCurrentUserAccount();
|
||||||
|
const api = await createAccountsApi(client);
|
||||||
|
const bmiThresholds = await api.fetchBmiThresholds();
|
||||||
|
|
||||||
if (!account) {
|
if (!account) {
|
||||||
redirect('/');
|
redirect('/');
|
||||||
}
|
}
|
||||||
@@ -28,18 +37,17 @@ async function UserHomePage() {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<DashboardCards />
|
<DashboardCards />
|
||||||
<PageHeader title={
|
<PageHeader
|
||||||
<>
|
title={
|
||||||
<Trans i18nKey={'common:welcome'} />
|
<>
|
||||||
{account.name ? `, ${toTitleCase(account.name)}` : ''}
|
<Trans i18nKey={'common:welcome'} />
|
||||||
</>
|
{account.name ? `, ${toTitleCase(account.name)}` : ''}
|
||||||
}
|
</>
|
||||||
description={
|
|
||||||
<Trans i18nKey={'dashboard:recentlyCheckedDescription'} />
|
|
||||||
}
|
}
|
||||||
|
description={<Trans i18nKey={'dashboard:recentlyCheckedDescription'} />}
|
||||||
/>
|
/>
|
||||||
<PageBody>
|
<PageBody>
|
||||||
<Dashboard account={account} />
|
<Dashboard account={account} bmiThresholds={bmiThresholds} />
|
||||||
</PageBody>
|
</PageBody>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -4,14 +4,13 @@ import Link from 'next/link';
|
|||||||
|
|
||||||
import { InfoTooltip } from '@/components/ui/info-tooltip';
|
import { InfoTooltip } from '@/components/ui/info-tooltip';
|
||||||
import type { AccountWithParams } from '@/packages/features/accounts/src/server/api';
|
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 { BlendingModeIcon, RulerHorizontalIcon } from '@radix-ui/react-icons';
|
||||||
import Isikukood from 'isikukood';
|
import Isikukood from 'isikukood';
|
||||||
import {
|
import {
|
||||||
Activity,
|
Activity,
|
||||||
ChevronRight,
|
|
||||||
Clock9,
|
Clock9,
|
||||||
Droplets,
|
Droplets,
|
||||||
LineChart,
|
|
||||||
Pill,
|
Pill,
|
||||||
Scale,
|
Scale,
|
||||||
TrendingUp,
|
TrendingUp,
|
||||||
@@ -25,95 +24,96 @@ import {
|
|||||||
CardDescription,
|
CardDescription,
|
||||||
CardFooter,
|
CardFooter,
|
||||||
CardHeader,
|
CardHeader,
|
||||||
CardProps,
|
|
||||||
} from '@kit/ui/card';
|
} from '@kit/ui/card';
|
||||||
import { Trans } from '@kit/ui/trans';
|
import { Trans } from '@kit/ui/trans';
|
||||||
import { cn } from '@kit/ui/utils';
|
import { cn } from '@kit/ui/utils';
|
||||||
|
|
||||||
|
import { BmiCategory } from '~/lib/types/bmi';
|
||||||
|
import {
|
||||||
|
bmiFromMetric,
|
||||||
|
getBmiBackgroundColor,
|
||||||
|
getBmiStatus,
|
||||||
|
} from '~/lib/utils';
|
||||||
|
|
||||||
const cards = ({
|
const cards = ({
|
||||||
gender,
|
gender,
|
||||||
age,
|
age,
|
||||||
height,
|
height,
|
||||||
weight,
|
weight,
|
||||||
|
bmiStatus,
|
||||||
}: {
|
}: {
|
||||||
gender?: string;
|
gender?: string;
|
||||||
age?: number;
|
age?: number;
|
||||||
height?: number | null;
|
height?: number | null;
|
||||||
weight?: number | null;
|
weight?: number | null;
|
||||||
}) => {
|
bmiStatus: BmiCategory | null;
|
||||||
const heightInMeters = height ? height / 100 : null;
|
}) => [
|
||||||
const bmi =
|
{
|
||||||
heightInMeters && weight
|
title: 'dashboard:gender',
|
||||||
? (weight / (heightInMeters * heightInMeters)).toFixed(1)
|
description: gender ?? 'dashboard:male',
|
||||||
: null;
|
icon: <User />,
|
||||||
return [
|
iconBg: 'bg-success',
|
||||||
{
|
},
|
||||||
title: 'dashboard:gender',
|
{
|
||||||
description: gender ?? 'dashboard:male',
|
title: 'dashboard:age',
|
||||||
icon: <User />,
|
description: age ? `${age}` : '-',
|
||||||
iconBg: 'bg-success',
|
icon: <Clock9 />,
|
||||||
},
|
iconBg: 'bg-success',
|
||||||
{
|
},
|
||||||
title: 'dashboard:age',
|
{
|
||||||
description: age ? `${age}` : '-',
|
title: 'dashboard:height',
|
||||||
icon: <Clock9 />,
|
description: height ? `${height}cm` : '-',
|
||||||
iconBg: 'bg-success',
|
icon: <RulerHorizontalIcon className="size-4" />,
|
||||||
},
|
iconBg: 'bg-success',
|
||||||
{
|
},
|
||||||
title: 'dashboard:height',
|
{
|
||||||
description: height ? `${height}cm` : '-',
|
title: 'dashboard:weight',
|
||||||
icon: <RulerHorizontalIcon className="size-4" />,
|
description: weight ? `${weight}kg` : '-',
|
||||||
iconBg: 'bg-success',
|
icon: <Scale />,
|
||||||
},
|
iconBg: 'bg-success',
|
||||||
{
|
},
|
||||||
title: 'dashboard:weight',
|
{
|
||||||
description: weight ? `${weight}kg` : '-',
|
title: 'dashboard:bmi',
|
||||||
icon: <Scale />,
|
description: bmiFromMetric(weight || 0, height || 0).toString(),
|
||||||
iconBg: 'bg-success',
|
icon: <TrendingUp />,
|
||||||
},
|
iconBg: getBmiBackgroundColor(bmiStatus),
|
||||||
{
|
},
|
||||||
title: 'dashboard:bmi',
|
{
|
||||||
description: bmi,
|
title: 'dashboard:bloodPressure',
|
||||||
icon: <TrendingUp />,
|
description: '-',
|
||||||
iconBg: 'bg-success',
|
icon: <Activity />,
|
||||||
},
|
iconBg: 'bg-warning',
|
||||||
{
|
},
|
||||||
title: 'dashboard:bloodPressure',
|
{
|
||||||
description: '-',
|
title: 'dashboard:cholesterol',
|
||||||
icon: <Activity />,
|
description: '-',
|
||||||
iconBg: 'bg-warning',
|
icon: <BlendingModeIcon className="size-4" />,
|
||||||
},
|
iconBg: 'bg-destructive',
|
||||||
{
|
},
|
||||||
title: 'dashboard:cholesterol',
|
{
|
||||||
description: '-',
|
title: 'dashboard:ldlCholesterol',
|
||||||
icon: <BlendingModeIcon className="size-4" />,
|
description: '-',
|
||||||
iconBg: 'bg-destructive',
|
icon: <Pill />,
|
||||||
},
|
iconBg: 'bg-warning',
|
||||||
{
|
},
|
||||||
title: 'dashboard:ldlCholesterol',
|
// {
|
||||||
description: '-',
|
// title: 'Score 2',
|
||||||
icon: <Pill />,
|
// description: 'Normis',
|
||||||
iconBg: 'bg-warning',
|
// icon: <LineChart />,
|
||||||
},
|
// iconBg: 'bg-success',
|
||||||
// {
|
// },
|
||||||
// title: 'Score 2',
|
// {
|
||||||
// description: 'Normis',
|
// title: 'dashboard:smoking',
|
||||||
// icon: <LineChart />,
|
// description: 'dashboard:respondToQuestion',
|
||||||
// iconBg: 'bg-success',
|
// descriptionColor: 'text-primary',
|
||||||
// },
|
// icon: (
|
||||||
// {
|
// <Button size="icon" variant="outline" className="px-2 text-black">
|
||||||
// title: 'dashboard:smoking',
|
// <ChevronRight className="size-4 stroke-2" />
|
||||||
// description: 'dashboard:respondToQuestion',
|
// </Button>
|
||||||
// descriptionColor: 'text-primary',
|
// ),
|
||||||
// icon: (
|
// cardVariant: 'gradient-success' as CardProps['variant'],
|
||||||
// <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 = [
|
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 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 (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -171,18 +185,19 @@ export default function Dashboard({ account }: { account: AccountWithParams }) {
|
|||||||
age: params?.age,
|
age: params?.age,
|
||||||
height: account.account_params?.[0]?.height,
|
height: account.account_params?.[0]?.height,
|
||||||
weight: account.account_params?.[0]?.weight,
|
weight: account.account_params?.[0]?.weight,
|
||||||
|
bmiStatus,
|
||||||
}).map(
|
}).map(
|
||||||
({
|
({
|
||||||
title,
|
title,
|
||||||
description,
|
description,
|
||||||
icon,
|
icon,
|
||||||
iconBg,
|
iconBg,
|
||||||
cardVariant,
|
// cardVariant,
|
||||||
descriptionColor,
|
// descriptionColor,
|
||||||
}) => (
|
}) => (
|
||||||
<Card
|
<Card
|
||||||
key={title}
|
key={title}
|
||||||
variant={cardVariant}
|
// variant={cardVariant}
|
||||||
className="flex flex-col justify-between"
|
className="flex flex-col justify-between"
|
||||||
>
|
>
|
||||||
<CardHeader className="items-end-safe">
|
<CardHeader className="items-end-safe">
|
||||||
@@ -199,7 +214,9 @@ export default function Dashboard({ account }: { account: AccountWithParams }) {
|
|||||||
<h5>
|
<h5>
|
||||||
<Trans i18nKey={title} />
|
<Trans i18nKey={title} />
|
||||||
</h5>
|
</h5>
|
||||||
<CardDescription className={descriptionColor}>
|
<CardDescription
|
||||||
|
// className={descriptionColor}
|
||||||
|
>
|
||||||
<Trans i18nKey={description} />
|
<Trans i18nKey={description} />
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
</CardFooter>
|
</CardFooter>
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ import React from 'react';
|
|||||||
|
|
||||||
import { Clock, TrendingUp, User } from 'lucide-react';
|
import { Clock, TrendingUp, User } from 'lucide-react';
|
||||||
|
|
||||||
|
import { bmiFromMetric } from '~/lib/utils';
|
||||||
|
|
||||||
import { TeamAccountStatisticsProps } from '../../_components/team-account-statistics';
|
import { TeamAccountStatisticsProps } from '../../_components/team-account-statistics';
|
||||||
|
|
||||||
interface AccountHealthDetailsField {
|
interface AccountHealthDetailsField {
|
||||||
@@ -26,9 +28,7 @@ export const getAccountHealthDetailsFields = (
|
|||||||
): AccountHealthDetailsField[] => {
|
): AccountHealthDetailsField[] => {
|
||||||
const averageBMI = (
|
const averageBMI = (
|
||||||
memberParams.reduce((sum, { height, weight }) => {
|
memberParams.reduce((sum, { height, weight }) => {
|
||||||
const hMeters = height! / 100;
|
return bmiFromMetric(weight ?? 0, height ?? 0) + sum;
|
||||||
const bmi = weight! / (hMeters * hMeters);
|
|
||||||
return sum + bmi;
|
|
||||||
}, 0) / memberParams.length
|
}, 0) / memberParams.length
|
||||||
).toFixed(0);
|
).toFixed(0);
|
||||||
|
|
||||||
|
|||||||
7
lib/types/bmi.ts
Normal file
7
lib/types/bmi.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
export enum BmiCategory {
|
||||||
|
UNDER_WEIGHT = 'under_weight',
|
||||||
|
NORMAL = 'normal',
|
||||||
|
OVER_WEIGHT = 'over_weight',
|
||||||
|
VERY_OVERWEIGHT = 'very_overweight',
|
||||||
|
OBESE = 'obese',
|
||||||
|
}
|
||||||
59
lib/utils.ts
59
lib/utils.ts
@@ -1,6 +1,9 @@
|
|||||||
|
import { Database } from '@/packages/supabase/src/database.types';
|
||||||
import { type ClassValue, clsx } from 'clsx';
|
import { type ClassValue, clsx } from 'clsx';
|
||||||
import { twMerge } from 'tailwind-merge';
|
import { twMerge } from 'tailwind-merge';
|
||||||
|
|
||||||
|
import { BmiCategory } from './types/bmi';
|
||||||
|
|
||||||
export function cn(...inputs: ClassValue[]) {
|
export function cn(...inputs: ClassValue[]) {
|
||||||
return twMerge(clsx(inputs));
|
return twMerge(clsx(inputs));
|
||||||
}
|
}
|
||||||
@@ -19,11 +22,63 @@ export function toTitleCase(str?: string) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function sortByDate<T>(a: T[] | undefined, key: keyof T): T[] | undefined {
|
export function sortByDate<T>(
|
||||||
|
a: T[] | undefined,
|
||||||
|
key: keyof T,
|
||||||
|
): T[] | undefined {
|
||||||
return a?.sort((a, b) => {
|
return a?.sort((a, b) => {
|
||||||
if (!a[key] || !b[key]) {
|
if (!a[key] || !b[key]) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
return new Date(b[key] as string).getTime() - new Date(a[key] as string).getTime();
|
return (
|
||||||
|
new Date(b[key] as string).getTime() -
|
||||||
|
new Date(a[key] as string).getTime()
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const bmiFromMetric = (kg: number, cm: number) => {
|
||||||
|
const m = cm / 100;
|
||||||
|
return kg / (m * m);
|
||||||
|
};
|
||||||
|
|
||||||
|
export function getBmiStatus(
|
||||||
|
thresholds: Omit<
|
||||||
|
Database['medreport']['Tables']['bmi_thresholds']['Row'],
|
||||||
|
'id'
|
||||||
|
>[],
|
||||||
|
params: { age: number; height: number; weight: number },
|
||||||
|
): BmiCategory | null {
|
||||||
|
const age = params.age;
|
||||||
|
const thresholdByAge =
|
||||||
|
thresholds.find(
|
||||||
|
(b) => age >= b.age_min && (b.age_max == null || age <= b.age_max),
|
||||||
|
) || null;
|
||||||
|
const bmi = bmiFromMetric(params.weight, params.height);
|
||||||
|
|
||||||
|
if (!thresholdByAge || Number.isNaN(bmi)) return null;
|
||||||
|
|
||||||
|
if (bmi > thresholdByAge.obesity_min) return BmiCategory.OBESE;
|
||||||
|
if (bmi > thresholdByAge.strong_min) return BmiCategory.VERY_OVERWEIGHT;
|
||||||
|
if (bmi > thresholdByAge.overweight_min) return BmiCategory.OVER_WEIGHT;
|
||||||
|
if (bmi >= thresholdByAge.normal_min && bmi <= thresholdByAge.normal_max)
|
||||||
|
return BmiCategory.NORMAL;
|
||||||
|
return BmiCategory.UNDER_WEIGHT;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getBmiBackgroundColor(bmiStatus: BmiCategory | null): string {
|
||||||
|
switch (bmiStatus) {
|
||||||
|
case BmiCategory.UNDER_WEIGHT:
|
||||||
|
return 'bg-warning';
|
||||||
|
case BmiCategory.NORMAL:
|
||||||
|
return 'bg-success';
|
||||||
|
case BmiCategory.OVER_WEIGHT:
|
||||||
|
return 'bg-warning';
|
||||||
|
case BmiCategory.VERY_OVERWEIGHT:
|
||||||
|
return 'bg-destructive';
|
||||||
|
case BmiCategory.OBESE:
|
||||||
|
return 'bg-error';
|
||||||
|
default:
|
||||||
|
return 'bg-success';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -242,6 +242,23 @@ class AccountsApi {
|
|||||||
|
|
||||||
return (count ?? 0) > 0;
|
return (count ?? 0) > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fetchBmiThresholds() {
|
||||||
|
// Fetch BMI
|
||||||
|
const { data, error } = await this.client
|
||||||
|
.schema('medreport')
|
||||||
|
.from('bmi_thresholds')
|
||||||
|
.select(
|
||||||
|
'age_min,age_max,underweight_max,normal_min,normal_max,overweight_min,strong_min,obesity_min',
|
||||||
|
)
|
||||||
|
.order('age_min', { ascending: true });
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
console.error('Error fetching BMI thresholds:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createAccountsApi(client: SupabaseClient<Database>) {
|
export function createAccountsApi(client: SupabaseClient<Database>) {
|
||||||
|
|||||||
@@ -690,6 +690,42 @@ export type Database = {
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
bmi_thresholds: {
|
||||||
|
Row: {
|
||||||
|
age_max: number | null
|
||||||
|
age_min: number
|
||||||
|
id: number
|
||||||
|
normal_max: number
|
||||||
|
normal_min: number
|
||||||
|
obesity_min: number
|
||||||
|
overweight_min: number
|
||||||
|
strong_min: number
|
||||||
|
underweight_max: number
|
||||||
|
}
|
||||||
|
Insert: {
|
||||||
|
age_max?: number | null
|
||||||
|
age_min: number
|
||||||
|
id?: number
|
||||||
|
normal_max: number
|
||||||
|
normal_min: number
|
||||||
|
obesity_min: number
|
||||||
|
overweight_min: number
|
||||||
|
strong_min: number
|
||||||
|
underweight_max: number
|
||||||
|
}
|
||||||
|
Update: {
|
||||||
|
age_max?: number | null
|
||||||
|
age_min?: number
|
||||||
|
id?: number
|
||||||
|
normal_max?: number
|
||||||
|
normal_min?: number
|
||||||
|
obesity_min?: number
|
||||||
|
overweight_min?: number
|
||||||
|
strong_min?: number
|
||||||
|
underweight_max?: number
|
||||||
|
}
|
||||||
|
Relationships: []
|
||||||
|
}
|
||||||
codes: {
|
codes: {
|
||||||
Row: {
|
Row: {
|
||||||
analysis_element_id: number | null
|
analysis_element_id: number | null
|
||||||
@@ -1807,6 +1843,7 @@ export type Database = {
|
|||||||
primary_owner_user_id: string
|
primary_owner_user_id: string
|
||||||
subscription_status: Database["medreport"]["Enums"]["subscription_status"]
|
subscription_status: Database["medreport"]["Enums"]["subscription_status"]
|
||||||
permissions: Database["medreport"]["Enums"]["app_permissions"][]
|
permissions: Database["medreport"]["Enums"]["app_permissions"][]
|
||||||
|
account_role: string
|
||||||
application_role: Database["medreport"]["Enums"]["application_role"]
|
application_role: Database["medreport"]["Enums"]["application_role"]
|
||||||
}[]
|
}[]
|
||||||
}
|
}
|
||||||
|
|||||||
27
supabase/migrations/20250821213200_bmi_config.sql
Normal file
27
supabase/migrations/20250821213200_bmi_config.sql
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
create table medreport.bmi_thresholds (
|
||||||
|
id bigserial primary key,
|
||||||
|
age_min int not null,
|
||||||
|
age_max int,
|
||||||
|
underweight_max numeric not null,
|
||||||
|
normal_min numeric not null,
|
||||||
|
normal_max numeric not null,
|
||||||
|
overweight_min numeric not null,
|
||||||
|
strong_min numeric not null,
|
||||||
|
obesity_min numeric not null
|
||||||
|
);
|
||||||
|
|
||||||
|
insert into medreport.bmi_thresholds
|
||||||
|
(age_min, age_max, underweight_max, normal_min, normal_max, overweight_min, strong_min, obesity_min)
|
||||||
|
values
|
||||||
|
(19, 24, 19, 19, 24, 24, 30, 35),
|
||||||
|
(25, 34, 20, 20, 25, 25, 30, 35),
|
||||||
|
(35, 44, 21, 21, 26, 26, 30, 35),
|
||||||
|
(45, 54, 22, 22, 27, 27, 30, 35),
|
||||||
|
(55, 64, 23, 23, 28, 28, 30, 35),
|
||||||
|
(65, null, 24, 24, 29, 29, 30, 35);
|
||||||
|
|
||||||
|
alter table medreport.bmi_thresholds enable row level security;
|
||||||
|
|
||||||
|
grant select, insert, update, delete on table medreport.bmi_thresholds to authenticated;
|
||||||
|
|
||||||
|
create policy "bmi_thresholds_select" on "medreport"."bmi_thresholds" for select to service_role using (true);
|
||||||
Reference in New Issue
Block a user