diff --git a/src/styles.css b/src/styles.css index 72380a9..15fe8c6 100644 --- a/src/styles.css +++ b/src/styles.css @@ -120,7 +120,7 @@ code { .tab-bar { display: grid; - grid-template-columns: repeat(6, minmax(110px, 1fr)); + grid-template-columns: repeat(7, minmax(110px, 1fr)); width: min(1080px, calc(100% - 40px)); margin: 22px auto 0; overflow-x: auto; @@ -237,6 +237,7 @@ code { .field input, .field select, +.field textarea, .compact-field select { width: 100%; min-height: 46px; @@ -247,6 +248,48 @@ code { background: #fff; } +.field textarea { + min-height: 96px; + resize: vertical; +} + +.history-grid, +.das21-grid { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 0 18px; +} + +.bmi-readout { + display: flex; + align-items: baseline; + gap: 9px; + margin: -2px 0 22px; + padding: 12px 14px; + border: 1px solid var(--line); + border-radius: 8px; + background: var(--soft); +} + +.bmi-readout span, +.das21-card > p { + color: var(--muted); +} + +.das21-card { + margin-top: 24px; +} + +.das21-grid { + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 16px; +} + +.das21-grid .compact-field { + display: grid; + gap: 7px; +} + .field small { color: var(--muted); line-height: 1.45; @@ -1412,7 +1455,12 @@ pre { } .tab-bar { - grid-template-columns: repeat(6, 105px); + grid-template-columns: repeat(7, 105px); + } + + .history-grid, + .das21-grid { + grid-template-columns: 1fr; } .screen { @@ -1423,6 +1471,11 @@ pre { padding: 17px; } + .drug-ranking__item { grid-template-columns: 30px 1fr; } + .drug-ranking__item > span:last-child { grid-column: 2; } + .quick-guidance { grid-template-columns: 1fr; } + .daily-details__heading { align-items: flex-start; flex-direction: column; } + .action-row, .group-heading, .review-output__heading { diff --git a/src/ui/ValidationConsole.tsx b/src/ui/ValidationConsole.tsx index c341770..7d5dfba 100644 --- a/src/ui/ValidationConsole.tsx +++ b/src/ui/ValidationConsole.tsx @@ -8,7 +8,7 @@ import { type ClinicalReviewItem, type ClinicalReviewResult, } from '../ai/clinical-review' -import { canonicalDrug } from '../data/drug-lexicon' +import { canonicalDrug, DRUG_LEXICON } from '../data/drug-lexicon' import { labelFor } from '../data/openfda' import { OFFICIAL_PHARMCAT_EXAMPLES, @@ -56,7 +56,7 @@ import { sourceIdsForGene, } from '../validation/view-model' -type TabId = 'file' | 'genes' | 'medicines' | 'daily' | 'ai' | 'evidence' +type TabId = 'file' | 'genes' | 'history' | 'medicines' | 'daily' | 'ai' | 'evidence' type InputMode = 'genome' | 'example' | 'report' type RunStatus = 'idle' | 'reading' | 'uploading' | 'analysing' | 'running' | 'complete' | 'error' @@ -106,6 +106,34 @@ const EMPTY_ROUTINE: RoutineAnswers = { eatingDisorderHistory: '', } +interface LifestyleMedicalHistory { + chronicConditions: string + surgicalHistory: string + medications: string + familyHistory: string + ethnicity: string + age: string + heightCm: string + weightKg: string + exerciseHours: string + exerciseDetails: string +} + +const EMPTY_LIFESTYLE_MEDICAL_HISTORY: LifestyleMedicalHistory = { + chronicConditions: '', + surgicalHistory: '', + medications: '', + familyHistory: '', + ethnicity: '', + age: '', + heightCm: '', + weightKg: '', + exerciseHours: '', + exerciseDetails: '', +} + +const DAS21_ITEM_COUNT = 21 + const BASE_CARE_CONTEXT: CareContext = { checkIn: null, goals: [], @@ -512,6 +540,106 @@ function FilePanel({ ) } +function LifestyleMedicalHistoryPanel({ + history, + onHistory, + das21Answers, + onDas21Answers, + onNext, +}: { + history: LifestyleMedicalHistory + onHistory: (history: LifestyleMedicalHistory) => void + das21Answers: string[] + onDas21Answers: (answers: string[]) => void + onNext: () => void +}) { + const height = Number(history.heightCm) + const weight = Number(history.weightKg) + const bmi = height > 0 && weight > 0 ? weight / ((height / 100) ** 2) : null + const update = (key: keyof LifestyleMedicalHistory, value: string) => onHistory({ ...history, [key]: value }) + const updateDas21 = (index: number, value: string) => onDas21Answers(das21Answers.map((answer, answerIndex) => answerIndex === index ? value : answer)) + + return ( +
+
+

Lifestyle and Medical History

+

Add the context that may be useful when discussing medicine options with your clinician.

+
+ +
+