158 lines
4.0 KiB
TypeScript
158 lines
4.0 KiB
TypeScript
import { Database } from '@/packages/supabase/src/database.types';
|
||
import { type ClassValue, clsx } from 'clsx';
|
||
import Isikukood, { Gender } from 'isikukood';
|
||
import { twMerge } from 'tailwind-merge';
|
||
|
||
import { BmiCategory } from './types/bmi';
|
||
|
||
export function cn(...inputs: ClassValue[]) {
|
||
return twMerge(clsx(inputs));
|
||
}
|
||
|
||
export function toArray<T>(input?: T | T[] | null): T[] {
|
||
if (!input) return [];
|
||
return Array.isArray(input) ? input : [input];
|
||
}
|
||
|
||
export function toTitleCase(str?: string) {
|
||
return (
|
||
str
|
||
?.toLowerCase()
|
||
.replace(/[^-'’\s]+/g, (match) =>
|
||
match.replace(/^./, (first) => first.toUpperCase()),
|
||
) ?? ''
|
||
);
|
||
}
|
||
|
||
export function sortByDate<T>(
|
||
a: T[] | undefined,
|
||
key: keyof T,
|
||
): T[] | undefined {
|
||
return a?.sort((a, b) => {
|
||
if (!a[key] || !b[key]) {
|
||
return 0;
|
||
}
|
||
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;
|
||
const m2 = m * m;
|
||
if (m2 === 0) {
|
||
return null;
|
||
}
|
||
const bmi = kg / m2;
|
||
return !Number.isNaN(bmi) ? Math.round(bmi) : null;
|
||
};
|
||
|
||
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 || bmi === null) {
|
||
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:
|
||
case BmiCategory.OVER_WEIGHT:
|
||
return 'bg-warning';
|
||
case BmiCategory.NORMAL:
|
||
return 'bg-success';
|
||
case BmiCategory.VERY_OVERWEIGHT:
|
||
case BmiCategory.OBESE:
|
||
return 'bg-destructive';
|
||
default:
|
||
return 'bg-success';
|
||
}
|
||
}
|
||
|
||
type AgeRange = '18-29' | '30-39' | '40-49' | '50-59' | '60';
|
||
export default class PersonalCode {
|
||
static getPersonalCode(personalCode: string | null) {
|
||
if (!personalCode) {
|
||
return null;
|
||
}
|
||
if (personalCode.toLowerCase().startsWith('ee')) {
|
||
return personalCode.substring(2);
|
||
}
|
||
return personalCode;
|
||
}
|
||
|
||
static parsePersonalCode(personalCode: string): {
|
||
ageRange: AgeRange;
|
||
gender: { label: string; value: string };
|
||
dob: Date;
|
||
age: number;
|
||
} {
|
||
const parsed = new Isikukood(personalCode);
|
||
const ageRange = (() => {
|
||
const age = parsed.getAge();
|
||
if (age >= 18 && age <= 29) {
|
||
return '18-29';
|
||
}
|
||
if (age >= 30 && age <= 39) {
|
||
return '30-39';
|
||
}
|
||
if (age >= 40 && age <= 49) {
|
||
return '40-49';
|
||
}
|
||
if (age >= 50 && age <= 59) {
|
||
return '50-59';
|
||
}
|
||
if (age >= 60) {
|
||
return '60';
|
||
}
|
||
throw new Error('Age range not supported, age=' + age);
|
||
})();
|
||
const gender = (() => {
|
||
const gender = parsed.getGender();
|
||
switch (gender) {
|
||
case Gender.FEMALE:
|
||
return { label: 'common:female', value: 'F' };
|
||
case Gender.MALE:
|
||
return { label: 'common:male', value: 'M' };
|
||
default:
|
||
throw new Error('Gender not supported');
|
||
}
|
||
})();
|
||
|
||
return {
|
||
ageRange,
|
||
gender,
|
||
dob: parsed.getBirthday(),
|
||
age: parsed.getAge(),
|
||
};
|
||
}
|
||
}
|
||
|
||
export const findProductTypeIdByHandle = (
|
||
productTypes: { metadata?: Record<string, unknown> | null; id: string }[],
|
||
handle: string,
|
||
) => {
|
||
return productTypes.find(({ metadata }) => metadata?.handle === handle)?.id;
|
||
};
|