import { AnalysisResponses } from '@/app/home/(user)/_components/ai/types'; import { type ClassValue, clsx } from 'clsx'; import Isikukood, { Gender } from 'isikukood'; import { twMerge } from 'tailwind-merge'; import type { BmiThresholds } from '@kit/accounts/types/accounts'; import { BmiCategory } from './types/bmi'; export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)); } export function toTitleCase(str?: string) { return ( str ?.toLowerCase() .replace(/[^-'’\s]+/g, (match) => match.replace(/^./, (first) => first.toUpperCase()), ) ?? '' ); } export function sortByDate( 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[], 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 | null; id: string }[], handle: string, ) => { return productTypes.find(({ metadata }) => metadata?.handle === handle)?.id; }; export function getLatestResponseTime(items?: AnalysisResponses) { if (!items?.length) return new Date().toISOString(); let latest = null; for (const it of items) { const d = new Date(it.response_time); const t = d.getTime(); if (!Number.isNaN(t) && (latest === null || t > latest.getTime())) { latest = d; } } return latest ? new Date(latest).toISOString() : new Date().toISOString(); }