43 lines
2.4 KiB
TypeScript
43 lines
2.4 KiB
TypeScript
import type { HealthAssessmentAnswers } from './health'
|
|
|
|
export function finiteNumberInRange(value: unknown, minimum: number, maximum: number, integer = false): value is number {
|
|
return typeof value === 'number' && Number.isFinite(value) && value >= minimum && value <= maximum && (!integer || Number.isInteger(value))
|
|
}
|
|
|
|
// Birthdays are calendar dates, not instants. Keep the displayed day unchanged
|
|
// even when the API serializes a DateTime or the browser has a UTC offset.
|
|
// Admin.NET returns yyyy-MM-dd HH:mm:ss; also accept date-only and ISO timestamps.
|
|
export function ageFromBirthday(value: unknown, today = new Date()): number | undefined {
|
|
if (typeof value !== 'string') return undefined
|
|
const match = /^(\d{4})-(\d{2})-(\d{2})(?:[T ]\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})?)?$/.exec(value)
|
|
if (!match) return undefined
|
|
const [, yearText, monthText, dayText] = match
|
|
const year = Number(yearText), month = Number(monthText), day = Number(dayText)
|
|
const birthday = new Date(year, month - 1, day)
|
|
if (birthday.getFullYear() !== year || birthday.getMonth() !== month - 1 || birthday.getDate() !== day) return undefined
|
|
const currentDay = new Date(today.getFullYear(), today.getMonth(), today.getDate())
|
|
if (birthday > currentDay) return undefined
|
|
const beforeBirthday = today.getMonth() < month - 1 || (today.getMonth() === month - 1 && today.getDate() < day)
|
|
return today.getFullYear() - year - (beforeBirthday ? 1 : 0)
|
|
}
|
|
|
|
const healthAnswerOptions: Record<keyof HealthAssessmentAnswers, readonly string[]> = {
|
|
sleepDuration: ['<6', '6-7', '7-8', '>8'],
|
|
fallingAsleepSpeed: ['hard', 'normal', 'fast'],
|
|
temperaturePreference: ['heat_fear', 'cold_fear', 'normal'],
|
|
nightAwakening: ['frequent', 'light', 'none'],
|
|
mattressPreference: ['firm', 'medium', 'soft'],
|
|
exerciseFrequency: ['none', '1-2', '3-5', 'daily'],
|
|
muscleFatigueArea: ['back_neck', 'waist_back', 'legs', 'none'],
|
|
activeTimeOfDay: ['morning', 'evening', 'night', 'irregular'],
|
|
mealRegularity: ['regular', 'skip_breakfast', 'irregular'],
|
|
flavorPreference: ['light', 'moderate', 'heavy'],
|
|
lateNightEating: ['often', 'sometimes', 'rarely'],
|
|
dailyWaterIntake: ['abundant', 'moderate', 'scarce']
|
|
}
|
|
|
|
export function healthAnswersComplete(answers: HealthAssessmentAnswers): boolean {
|
|
return (Object.keys(healthAnswerOptions) as (keyof HealthAssessmentAnswers)[])
|
|
.every(key => healthAnswerOptions[key].includes(answers[key]))
|
|
}
|