From f0f5889df49d6d8cb5a37395a88860d40198b69e Mon Sep 17 00:00:00 2001 From: mahdahar <89adham@gmail.com> Date: Wed, 18 Feb 2026 07:12:58 +0700 Subject: [PATCH] Add comprehensive test management module with reference range support - Create TestModal component with tabbed interface for test creation/editing - Add BasicInfoForm for test metadata (name, code, category, etc.) - Implement multiple reference range types: * NumericRefRange for numeric value ranges * TextRefRange for qualitative results * ThresholdRefRange for threshold-based results * ValueSetRefRange for predefined value sets - Add ReferenceRangeSection to manage all reference range types - Create config store for application configuration management - Add static config.json for environment settings - Update DataTable styling - Refactor tests page to integrate new TestModal - Add reference range utility functions - Include comprehensive test types documentation --- docs/test-types-reference.md | 417 ++++++ src/lib/api/client.js | 15 +- src/lib/components/DataTable.svelte | 2 +- src/lib/components/TestModal.svelte | 104 ++ .../test-modal/BasicInfoForm.svelte | 185 +++ .../test-modal/NumericRefRange.svelte | 134 ++ .../test-modal/ReferenceRangeSection.svelte | 210 +++ .../components/test-modal/TextRefRange.svelte | 103 ++ .../test-modal/ThresholdRefRange.svelte | 134 ++ .../test-modal/ValueSetRefRange.svelte | 104 ++ .../test-modal/refRangeConstants.js | 26 + src/lib/stores/config.js | 58 + .../(app)/master-data/contacts/+page.svelte | 4 +- .../(app)/master-data/tests/+page.svelte | 1206 +---------------- .../(app)/master-data/tests/TestModal.svelte | 104 ++ .../(app)/master-data/tests/referenceRange.js | 107 ++ .../tests/test-modal/BasicInfoForm.svelte | 185 +++ .../tests/test-modal/NumericRefRange.svelte | 266 ++++ .../test-modal/ReferenceRangeSection.svelte | 179 +++ .../tests/test-modal/TextRefRange.svelte | 97 ++ .../tests/test-modal/ThresholdRefRange.svelte | 124 ++ .../tests/test-modal/ValueSetRefRange.svelte | 98 ++ src/routes/+layout.svelte | 19 +- static/config.json | 3 + 24 files changed, 2736 insertions(+), 1148 deletions(-) create mode 100644 docs/test-types-reference.md create mode 100644 src/lib/components/TestModal.svelte create mode 100644 src/lib/components/test-modal/BasicInfoForm.svelte create mode 100644 src/lib/components/test-modal/NumericRefRange.svelte create mode 100644 src/lib/components/test-modal/ReferenceRangeSection.svelte create mode 100644 src/lib/components/test-modal/TextRefRange.svelte create mode 100644 src/lib/components/test-modal/ThresholdRefRange.svelte create mode 100644 src/lib/components/test-modal/ValueSetRefRange.svelte create mode 100644 src/lib/components/test-modal/refRangeConstants.js create mode 100644 src/lib/stores/config.js create mode 100644 src/routes/(app)/master-data/tests/TestModal.svelte create mode 100644 src/routes/(app)/master-data/tests/referenceRange.js create mode 100644 src/routes/(app)/master-data/tests/test-modal/BasicInfoForm.svelte create mode 100644 src/routes/(app)/master-data/tests/test-modal/NumericRefRange.svelte create mode 100644 src/routes/(app)/master-data/tests/test-modal/ReferenceRangeSection.svelte create mode 100644 src/routes/(app)/master-data/tests/test-modal/TextRefRange.svelte create mode 100644 src/routes/(app)/master-data/tests/test-modal/ThresholdRefRange.svelte create mode 100644 src/routes/(app)/master-data/tests/test-modal/ValueSetRefRange.svelte create mode 100644 static/config.json diff --git a/docs/test-types-reference.md b/docs/test-types-reference.md new file mode 100644 index 0000000..899acb5 --- /dev/null +++ b/docs/test-types-reference.md @@ -0,0 +1,417 @@ +# ๐Ÿ“‹ Test Types & Reference Types Guide + +> **Quick Overview**: This guide helps you understand the different types of tests and how to display them in the frontend. + +--- + +## ๐ŸŽฏ What Are Test Types? + +Think of test types as "categories" that determine how a test behaves and what information it needs. + +### Quick Reference Card + +``` +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ Type โ”‚ Use This For... โ”‚ Example โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ TEST โ”‚ Standard lab tests โ”‚ Blood Glucose, CBC โ”‚ +โ”‚ PARAM โ”‚ Components of a test โ”‚ WBC count (in CBC) โ”‚ +โ”‚ CALC โ”‚ Formula-based results โ”‚ BMI, eGFR โ”‚ +โ”‚ GROUP โ”‚ Panels/batteries โ”‚ Lipid Panel, CMP โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ +``` + +--- + +## ๐Ÿงช Detailed Test Types + +### 1. TEST - Standard Laboratory Test +**Icon**: ๐Ÿงซ **Color**: Blue + +Use this for regular tests that have: +- Reference ranges (normal values) +- Units (mg/dL, mmol/L, etc.) +- Collection requirements + +**Example**: Blood Glucose, Hemoglobin, Cholesterol + +**What to Display**: +- Test code and name +- Reference range +- Units +- Collection instructions +- Expected turnaround time + +--- + +### 2. PARAM - Parameter +**Icon**: ๐Ÿ“Š **Color**: Light Blue + +Use this for individual components within a larger test. + +**Example**: +- Complete Blood Count (GROUP) contains: + - WBC (PARAM) + - RBC (PARAM) + - Hemoglobin (PARAM) + +**What to Display**: +- Same as TEST, but shown indented under parent +- Often part of a GROUP + +--- + +### 3. CALC - Calculated Test +**Icon**: ๐Ÿงฎ **Color**: Purple + +Use this for tests computed from other test results using formulas. + +**Example**: +- BMI (calculated from height & weight) +- eGFR (calculated from creatinine, age, etc.) + +**What to Display**: +- Formula description +- Input parameters (which tests feed into this) +- Result value +- Reference range (if applicable) + +**Special Fields**: +- `FormulaInput` - What values go in? +- `FormulaCode` - How is it calculated? + +--- + +### 4. GROUP - Group Test (Panel/Battery) +**Icon**: ๐Ÿ“ฆ **Color**: Green + +Use this to bundle multiple related tests together. + +**Example**: +- Lipid Panel (GROUP) contains: + - Total Cholesterol (PARAM) + - HDL (PARAM) + - LDL (PARAM) + - Triglycerides (PARAM) + +**What to Display**: +- Group name +- List of member tests +- Individual results for each member + +**Structure**: +``` +๐Ÿ“ฆ Lipid Panel (GROUP) + โ”œโ”€โ”€ Total Cholesterol (PARAM): 180 mg/dL + โ”œโ”€โ”€ HDL (PARAM): 55 mg/dL + โ”œโ”€โ”€ LDL (PARAM): 110 mg/dL + โ””โ”€โ”€ Triglycerides (PARAM): 150 mg/dL +``` + +--- + +## ๐Ÿ“ Reference Types Explained + +Reference types tell you **how to interpret the results** - what "normal" looks like. + +### Choose Your Reference Type: + +``` + โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” + โ”‚ Reference Type? โ”‚ + โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” + โ”‚ โ”‚ โ”‚ + Numbers? Text values? Threshold? + โ”‚ โ”‚ โ”‚ + โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ” + โ”‚ NMRC โ”‚ โ”‚ TEXT โ”‚ โ”‚ THOLD โ”‚ + โ”‚ (Numeric) โ”‚ โ”‚ (Text) โ”‚ โ”‚ (Threshold) โ”‚ + โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ โ”‚ โ”‚ + โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ” + โ”‚ RANGE โ”‚ โ”‚ Free โ”‚ โ”‚ Positive/ โ”‚ + โ”‚ (Min-Max) โ”‚ โ”‚ Text โ”‚ โ”‚ Negative โ”‚ + โ”‚ OR โ”‚ โ”‚ OR โ”‚ โ”‚ OR โ”‚ + โ”‚ THOLD โ”‚ โ”‚ VSET โ”‚ โ”‚ < > = โ”‚ + โ”‚ (Threshold) โ”‚ โ”‚ (Dropdown) โ”‚ โ”‚ cutoff โ”‚ + โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ +``` + +### Reference Type Details + +| Type | Visual | Example | +|------|--------|---------| +| **NMRC** - Numeric Range | `70 - 100 mg/dL` | Blood glucose: 70-100 mg/dL | +| **TEXT** - Text Value | `"Normal"` or `"Positive"` | Urinalysis: "Clear", "Cloudy" | +| **THOLD** - Threshold | `> 60` or `< 5.5` | eGFR: > 60 (normal) | +| **VSET** - Value Set | Dropdown options | Organism: [E.coli, Staph, etc.] | + +--- + +## ๐ŸŽจ Frontend Display Patterns + +### Pattern 1: Test List View + +```javascript +// When showing a list of tests +function renderTestCard(test) { + const typeIcon = getIcon(test.TestType); + const typeColor = getColor(test.TestType); + + return ` +
+ ${typeIcon} +

${test.TestSiteName}

+ ${test.TestTypeLabel} + ${test.TestSiteCode} +
+ `; +} +``` + +### Pattern 2: Reference Range Display + +```javascript +// Show reference range based on type +function renderReferenceRange(test) { + switch(test.RefType) { + case 'NMRC': + return `${test.MinValue} - ${test.MaxValue} ${test.Unit}`; + + case 'TEXT': + return test.ReferenceText || 'See report'; + + case 'THOLD': + return `${test.ThresholdOperator} ${test.ThresholdValue}`; + + case 'VSET': + return 'Select from list'; + } +} +``` + +### Pattern 3: Group Test Expansion + +```javascript +// Expandable group test +function renderGroupTest(test) { + return ` +
+ +
+ ${test.testdefgrp.map(member => renderTestRow(member)).join('')} +
+
+ `; +} +``` + +--- + +## ๐Ÿ—‚๏ธ Data Structure Visualization + +### Test Hierarchy + +``` +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ TEST DEFINITION โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ TestSiteID: 12345 โ”‚ +โ”‚ TestSiteCode: GLUC โ”‚ +โ”‚ TestSiteName: Glucose โ”‚ +โ”‚ TestType: TEST โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ ๐Ÿ“Ž Additional Info (loaded based on TestType): โ”‚ +โ”‚ โ”‚ +โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ +โ”‚ โ”‚ TestDefTech (for TEST/PARAM) โ”‚ โ”‚ +โ”‚ โ”‚ - DisciplineID, DepartmentID โ”‚ โ”‚ +โ”‚ โ”‚ - ResultType, RefType โ”‚ โ”‚ +โ”‚ โ”‚ - Unit, Method, ExpectedTAT โ”‚ โ”‚ +โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ +โ”‚ โ”‚ +โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ +โ”‚ โ”‚ TestDefCal (for CALC) โ”‚ โ”‚ +โ”‚ โ”‚ - FormulaInput, FormulaCode โ”‚ โ”‚ +โ”‚ โ”‚ - RefType, Unit, Decimal โ”‚ โ”‚ +โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ +โ”‚ โ”‚ +โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ +โ”‚ โ”‚ TestDefGrp (for GROUP) โ”‚ โ”‚ +โ”‚ โ”‚ - Array of member tests โ”‚ โ”‚ +โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ +โ”‚ โ”‚ +โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ +โ”‚ โ”‚ TestMap (for ALL types) โ”‚ โ”‚ +โ”‚ โ”‚ - Mapping to external systems โ”‚ โ”‚ +โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ +``` + +--- + +## ๐Ÿ’ก Common Scenarios + +### Scenario 1: Creating a Simple Test + +**Need**: Add "Hemoglobin" test +**Choose**: TEST type with NMRC reference + +```javascript +const hemoglobin = { + TestSiteCode: 'HGB', + TestSiteName: 'Hemoglobin', + TestType: 'TEST', + testdeftech: { + RefType: 'NMRC', + Unit: 'g/dL', + // Reference range: 12-16 g/dL + } +}; +``` + +### Scenario 2: Creating a Panel + +**Need**: "Complete Blood Count" with multiple components +**Choose**: GROUP type with PARAM children + +```javascript +const cbc = { + TestSiteCode: 'CBC', + TestSiteName: 'Complete Blood Count', + TestType: 'GROUP', + testdefgrp: [ + { TestSiteCode: 'WBC', TestType: 'PARAM' }, + { TestSiteCode: 'RBC', TestType: 'PARAM' }, + { TestSiteCode: 'HGB', TestType: 'PARAM' }, + { TestSiteCode: 'PLT', TestType: 'PARAM' } + ] +}; +``` + +### Scenario 3: Calculated Result + +**Need**: BMI from height and weight +**Choose**: CALC type with formula + +```javascript +const bmi = { + TestSiteCode: 'BMI', + TestSiteName: 'Body Mass Index', + TestType: 'CALC', + testdefcal: { + FormulaInput: 'HEIGHT,WEIGHT', + FormulaCode: 'WEIGHT / ((HEIGHT/100) * (HEIGHT/100))', + RefType: 'NMRC', + Unit: 'kg/mยฒ' + } +}; +``` + +--- + +## ๐ŸŽจ Visual Style Guide + +### Color Coding + +| Type | Primary Color | Background | Usage | +|------|---------------|------------|-------| +| TEST | `#0066CC` | `#E6F2FF` | Main test cards | +| PARAM | `#3399FF` | `#F0F8FF` | Component rows | +| CALC | `#9933CC` | `#F5E6FF` | Calculated fields | +| GROUP | `#00AA44` | `#E6F9EE` | Expandable panels | + +### Icons + +| Type | Icon | Unicode | +|------|------|---------| +| TEST | ๐Ÿงซ | `\u{1F9EB}` | +| PARAM | ๐Ÿ“Š | `\u{1F4CA}` | +| CALC | ๐Ÿงฎ | `\u{1F9EE}` | +| GROUP | ๐Ÿ“ฆ | `\u{1F4E6}` | + +--- + +## ๐Ÿ”Œ Quick API Reference + +### Fetch Tests + +```javascript +// Get all tests for a site +GET /api/tests?siteId=123 + +// Get specific test type +GET /api/tests?testType=GROUP + +// Search tests +GET /api/tests?search=glucose + +// Get single test with details +GET /api/tests/12345 +``` + +### Value Set Helpers + +```javascript +// Get human-readable labels +const labels = { + testType: valueSet.getLabel('test_type', test.TestType), + refType: valueSet.getLabel('reference_type', test.RefType), + resultType: valueSet.getLabel('result_type', test.ResultType) +}; + +// Get dropdown options +const testTypes = valueSet.get('test_type'); +// Returns: [{value: "TEST", label: "Test"}, ...] +``` + +--- + +## ๐Ÿ“š Value Set File Locations + +``` +app/Libraries/Data/ +โ”œโ”€โ”€ test_type.json # TEST, PARAM, CALC, GROUP +โ”œโ”€โ”€ reference_type.json # NMRC, TEXT, THOLD, VSET +โ”œโ”€โ”€ result_type.json # NMRIC, RANGE, TEXT, VSET +โ”œโ”€โ”€ numeric_ref_type.json # RANGE, THOLD +โ””โ”€โ”€ text_ref_type.json # VSET, TEXT +``` + +--- + +## โœ… Checklist for Frontend Developers + +- [ ] Show correct icon for each test type +- [ ] Display reference ranges based on RefType +- [ ] Handle GROUP tests as expandable panels + +- [ ] Display CALC formulas when available +- [ ] Use ValueSet labels for all coded fields +- [ ] Respect VisibleScr/VisibleRpt flags +- [ ] Handle soft-deleted tests (EndDate check) + +--- + +## ๐Ÿ†˜ Need Help? + +**Q: When do I use PARAM vs TEST?** +A: Use PARAM for sub-components of a GROUP. Use TEST for standalone tests. + +**Q: What's the difference between NMRC and THOLD?** +A: NMRC shows a range (70-100). THOLD shows a threshold (> 60 or < 5.5). + +**Q: Can a GROUP contain other GROUPs?** +A: Yes! Groups can be nested (though typically only 1-2 levels deep). + +**Q: How do I know which details to fetch?** +A: Check `TestType` first, then look for the corresponding property: +- TEST/PARAM โ†’ `testdeftech` +- CALC โ†’ `testdefcal` +- GROUP โ†’ `testdefgrp` +- All types โ†’ `testmap` + diff --git a/src/lib/api/client.js b/src/lib/api/client.js index 71b4fd7..ac74728 100644 --- a/src/lib/api/client.js +++ b/src/lib/api/client.js @@ -1,7 +1,16 @@ import { goto } from '$app/navigation'; import { auth } from '$lib/stores/auth.js'; +import { config } from '$lib/stores/config.js'; -const API_URL = import.meta.env.VITE_API_URL || ''; +/** + * Get the API URL from runtime config + * Falls back to build-time env var if runtime config not available + * @returns {string} + */ +function getApiUrl() { + const runtimeUrl = config.getApiUrl(); + return runtimeUrl || import.meta.env.VITE_API_URL || ''; +} /** * Base API client with JWT handling @@ -27,8 +36,8 @@ export async function apiClient(endpoint, options = {}) { headers['Authorization'] = `Bearer ${token}`; } - // Build full URL - const url = `${API_URL}${endpoint}`; + // Build full URL using runtime config + const url = `${getApiUrl()}${endpoint}`; try { const response = await fetch(url, { diff --git a/src/lib/components/DataTable.svelte b/src/lib/components/DataTable.svelte index 547f286..85ab52f 100644 --- a/src/lib/components/DataTable.svelte +++ b/src/lib/components/DataTable.svelte @@ -104,7 +104,7 @@ {#each columns as column} {#if cell} - {@render cell({ column, row, value: getValue(row, column.key) })} + {@render cell({ column, row, value: getValue(row, column.key), index })} {:else if column.render} {@render column.render(row)} {:else} diff --git a/src/lib/components/TestModal.svelte b/src/lib/components/TestModal.svelte new file mode 100644 index 0000000..a7f486e --- /dev/null +++ b/src/lib/components/TestModal.svelte @@ -0,0 +1,104 @@ + + + + +
+ + {#if canHaveRefRange} + + {/if} +
+ + {#if activeTab === 'basic'} + + {:else if activeTab === 'refrange' && canHaveRefRange} + + {/if} + + {#snippet footer()} + + + {/snippet} +
diff --git a/src/lib/components/test-modal/BasicInfoForm.svelte b/src/lib/components/test-modal/BasicInfoForm.svelte new file mode 100644 index 0000000..99354ad --- /dev/null +++ b/src/lib/components/test-modal/BasicInfoForm.svelte @@ -0,0 +1,185 @@ + + +
+ +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+ + +
+ + + {#if canHaveUnit} +
+ {#if canHaveFormula} +
+ + + Use test codes with operators: +, -, *, / +
+ {/if} +
+ + +
+
+ {/if} + + +
+
+ + +
+
+ Visibility +
+ + +
+
+
+
diff --git a/src/lib/components/test-modal/NumericRefRange.svelte b/src/lib/components/test-modal/NumericRefRange.svelte new file mode 100644 index 0000000..7df6b83 --- /dev/null +++ b/src/lib/components/test-modal/NumericRefRange.svelte @@ -0,0 +1,134 @@ + + +
+
+
+ +

Numeric Reference Ranges

+
+ +
+ + {#if refnum?.length === 0} +
+ +

No numeric ranges defined

+ +
+ {/if} + + {#each refnum || [] as ref, index (index)} +
+
+
+ Range {index + 1} + +
+ +
+
+ Sex + +
+ +
+ Age From + +
+ +
+ Age To + +
+ +
+ Flag + +
+
+ +
+
+ Lower Bound +
+ + +
+
+ +
+ Upper Bound +
+ + +
+
+
+ +
+ Interpretation + +
+
+
+ {/each} +
diff --git a/src/lib/components/test-modal/ReferenceRangeSection.svelte b/src/lib/components/test-modal/ReferenceRangeSection.svelte new file mode 100644 index 0000000..7cc8c41 --- /dev/null +++ b/src/lib/components/test-modal/ReferenceRangeSection.svelte @@ -0,0 +1,210 @@ + + +
+ +
+
+ +

Reference Range Type

+ +
+
+ + + + + +
+
+ + + {#if formData.refRangeType === 'num'} + + {/if} + + + {#if formData.refRangeType === 'thold'} + + {/if} + + + {#if formData.refRangeType === 'text'} + + {/if} + + + {#if formData.refRangeType === 'vset'} + + {/if} +
diff --git a/src/lib/components/test-modal/TextRefRange.svelte b/src/lib/components/test-modal/TextRefRange.svelte new file mode 100644 index 0000000..b970237 --- /dev/null +++ b/src/lib/components/test-modal/TextRefRange.svelte @@ -0,0 +1,103 @@ + + +
+
+
+ +

Text Reference Ranges

+
+ +
+ + {#if reftxt?.length === 0} +
+ +

No text ranges defined

+ +
+ {/if} + + {#each reftxt || [] as ref, index (index)} +
+
+
+ Range {index + 1} + +
+ +
+
+ Sex + +
+ +
+ Age From + +
+ +
+ Age To + +
+ +
+ Flag + +
+
+ +
+ Reference Text + +
+
+
+ {/each} +
diff --git a/src/lib/components/test-modal/ThresholdRefRange.svelte b/src/lib/components/test-modal/ThresholdRefRange.svelte new file mode 100644 index 0000000..892b962 --- /dev/null +++ b/src/lib/components/test-modal/ThresholdRefRange.svelte @@ -0,0 +1,134 @@ + + +
+
+
+ +

Threshold Reference Ranges

+
+ +
+ + {#if refthold?.length === 0} +
+ +

No threshold ranges defined

+ +
+ {/if} + + {#each refthold || [] as ref, index (index)} +
+
+
+ Range {index + 1} + +
+ +
+
+ Sex + +
+ +
+ Age From + +
+ +
+ Age To + +
+ +
+ Flag + +
+
+ +
+
+ Lower Value +
+ + +
+
+ +
+ Upper Value +
+ + +
+
+
+ +
+ Interpretation + +
+
+
+ {/each} +
diff --git a/src/lib/components/test-modal/ValueSetRefRange.svelte b/src/lib/components/test-modal/ValueSetRefRange.svelte new file mode 100644 index 0000000..1ed440c --- /dev/null +++ b/src/lib/components/test-modal/ValueSetRefRange.svelte @@ -0,0 +1,104 @@ + + +
+
+
+ +

Value Set Reference Ranges

+
+ +
+ + {#if refvset?.length === 0} +
+ +

No value set ranges defined

+ +
+ {/if} + + {#each refvset || [] as ref, index (index)} +
+
+
+ Range {index + 1} + +
+ +
+
+ Sex + +
+ +
+ Age From + +
+ +
+ Age To + +
+ +
+ Flag + +
+
+ +
+ Value Set + + Comma-separated list of allowed values +
+
+
+ {/each} +
diff --git a/src/lib/components/test-modal/refRangeConstants.js b/src/lib/components/test-modal/refRangeConstants.js new file mode 100644 index 0000000..4e865e2 --- /dev/null +++ b/src/lib/components/test-modal/refRangeConstants.js @@ -0,0 +1,26 @@ +/** + * Reference range shared constants + */ + +/** @type {Array<{value: string, label: string, description: string}>} */ +export const signOptions = [ + { value: 'GE', label: 'โ‰ฅ', description: 'Greater than or equal to' }, + { value: 'GT', label: '>', description: 'Greater than' }, + { value: 'LE', label: 'โ‰ค', description: 'Less than or equal to' }, + { value: 'LT', label: '<', description: 'Less than' } +]; + +/** @type {Array<{value: string, label: string, description: string}>} */ +export const flagOptions = [ + { value: 'N', label: 'N', description: 'Normal' }, + { value: 'L', label: 'L', description: 'Low' }, + { value: 'H', label: 'H', description: 'High' }, + { value: 'C', label: 'C', description: 'Critical' } +]; + +/** @type {Array<{value: string, label: string}>} */ +export const sexOptions = [ + { value: '2', label: 'Male' }, + { value: '1', label: 'Female' }, + { value: '0', label: 'Any' } +]; diff --git a/src/lib/stores/config.js b/src/lib/stores/config.js new file mode 100644 index 0000000..9aa0ef7 --- /dev/null +++ b/src/lib/stores/config.js @@ -0,0 +1,58 @@ +import { writable } from 'svelte/store'; +import { browser } from '$app/environment'; + +/** + * Create config store with runtime configuration + */ +function createConfigStore() { + const { subscribe, set, update } = writable({ + apiUrl: '', + loaded: false, + error: null, + }); + + return { + subscribe, + + /** + * Load configuration from config.json + */ + load: async () => { + if (!browser) return; + + try { + const response = await fetch('/config.json'); + if (!response.ok) { + throw new Error(`Failed to load config: ${response.status}`); + } + const config = await response.json(); + set({ + apiUrl: config.apiUrl || '', + loaded: true, + error: null, + }); + } catch (err) { + console.error('Failed to load config:', err); + set({ + apiUrl: '', + loaded: true, + error: err.message, + }); + } + }, + + /** + * Get current API URL + * @returns {string} + */ + getApiUrl: () => { + let url = ''; + subscribe((config) => { + url = config.apiUrl; + })(); + return url; + }, + }; +} + +export const config = createConfigStore(); diff --git a/src/routes/(app)/master-data/contacts/+page.svelte b/src/routes/(app)/master-data/contacts/+page.svelte index 6dd0909..7468261 100644 --- a/src/routes/(app)/master-data/contacts/+page.svelte +++ b/src/routes/(app)/master-data/contacts/+page.svelte @@ -208,7 +208,7 @@ Loading contacts... - {:else if filteredContacts().length === 0} + {:else if filteredContacts.length === 0}
@@ -232,7 +232,7 @@ {:else} ({ + data={filteredContacts.map((c) => ({ ...c, FullName: getDisplayName(c), SpecialtyLabel: specialtyMap[c.Specialty] || '-', diff --git a/src/routes/(app)/master-data/tests/+page.svelte b/src/routes/(app)/master-data/tests/+page.svelte index fc26287..05548af 100644 --- a/src/routes/(app)/master-data/tests/+page.svelte +++ b/src/routes/(app)/master-data/tests/+page.svelte @@ -1,480 +1,80 @@
- - - +

Test Definitions

-

Manage laboratory tests and panels

+

Manage laboratory tests, panels, and calculated values

- +
-
- e.key === 'Enter' && handleSearch()} - /> + e.key === 'Enter' && handleSearch()} />
- @@ -483,709 +83,33 @@
- +
- ({ - ...t, - DisciplineName: disciplines.find(d => d.DisciplineID === t.DisciplineID)?.DisciplineName || '-', - DepartmentName: departments.find(d => d.DepartmentID === t.DepartmentID)?.DepartmentName || '-' - }))} - {loading} - emptyMessage="No tests found" - hover={true} - bordered={false} - > - {#snippet cell({ column, row, value })} - {#if column.key === 'TestType'} - - {testTypeLabels[value] || value} - - {:else if column.key === 'actions'} -
- - -
- {:else} - {value || '-'} - {/if} + handleRowClick(idx)}> + {#snippet cell({ column, row, index })} + {@const isSelected = index === selectedRowIndex} {@const typeConfig = getTestTypeConfig(row.TestType)} {@const isGroup = row.TestType === 'GROUP'} {@const isExpanded = expandedGroups.has(row.TestSiteID)} + {#if column.key === 'expand'}{#if isGroup}{:else}{/if}{/if} + {#if column.key === 'TestType'}{typeConfig.label}{/if} + {#if column.key === 'TestSiteName'}
{row.TestSiteName}{#if isGroup && isExpanded && row.testdefgrp}
{#each row.testdefgrp as member}{@const memberConfig = getTestTypeConfig(member.TestType)}
{member.TestSiteCode}{member.TestSiteName}
{/each}
{/if}
{/if} + {#if column.key === 'ReferenceRange'}{formatReferenceRange(row)}{/if} + {#if column.key === 'actions'}
{/if} + {#if column.key === 'TestSiteCode'}{row.TestSiteCode}{/if} {/snippet}
- - {#if totalPages > 1} -
-
- Showing {(currentPage - 1) * perPage + 1} - {Math.min(currentPage * perPage, totalItems)} of {totalItems} -
-
- - Page {currentPage} of {totalPages} - -
-
- {/if} + {#if totalPages > 1}
Showing {(currentPage - 1) * perPage + 1} - {Math.min(currentPage * perPage, totalItems)} of {totalItems}
Page {currentPage} of {totalPages}
{/if}
- - -
- - {#if canHaveRefRange} - - {/if} -
- - {#if activeTab === 'basic'} -
{ e.preventDefault(); handleSave(); }}> - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
- - -
- - - {#if canHaveUnit} -
- {#if canHaveFormula} -
- - - Use test codes with operators: +, -, *, / -
- {/if} -
- - -
-
- {/if} - - -
-
- - -
-
- Visibility -
- - -
-
-
-
- {:else if activeTab === 'refrange' && canHaveRefRange} -
- -
-
- -

Reference Range Type

- -
-
- - - -
-
- - - {#if formData.refRangeType === 'numeric'} -
-
-
- -

Numeric Reference Ranges

- -
- -
- - {#if formData.refnum.length === 0} -
- -

No numeric ranges defined

- -
- {/if} - - {#each formData.refnum as ref, index (index)} - {@const validationErrors = validateNumericRange(ref, index)} -
-
- -
-
- Range {index + 1} - {#if validationErrors.length > 0} - Invalid - {/if} -
- -
- - -
-

- 1 - Patient Demographics -

-
-
- Range Type - -
- -
- Sex - -
- -
- Age From (years) - ref.AgeEnd} - /> -
- -
- Age To (years) - ref.AgeEnd} - /> -
-
- {#if ref.AgeStart !== null && ref.AgeEnd !== null && ref.AgeStart > ref.AgeEnd} -

Age start cannot be greater than age end

- {/if} -
- - -
-

- 2 - Range Values - -

-
-
- -
- Lower Bound -
- - -
- - {signOptions.find(o => o.value === ref.LowSign)?.description} - -
- - -
- Upper Bound -
- - -
- - {signOptions.find(o => o.value === ref.HighSign)?.description} - -
-
- - - {#if ref.Low !== null && ref.High !== null} - {@const lowLabel = getSignLabel(ref.LowSign)} - {@const highLabel = getSignLabel(ref.HighSign)} -
- Preview: - - {lowLabel} {ref.Low} and {highLabel} {ref.High} - - {#if ref.Low > ref.High} - โš  Low value exceeds High value - {/if} -
- {/if} -
-
- - -
-

- 3 - Result Interpretation -

-
-
- - Flag - - - -
- -
- Interpretation - -
-
-
-
-
- {/each} -
- {/if} - - - {#if formData.refRangeType === 'text'} -
-
-
- -

Text Reference Ranges

- -
- -
- - {#if formData.reftxt.length === 0} -
- -

No text ranges defined

- -
- {/if} - - {#each formData.reftxt as ref, index (index)} - {@const validationErrors = validateTextRange(ref, index)} -
-
- -
-
- Range {index + 1} - {#if validationErrors.length > 0} - Invalid - {/if} -
- -
- - -
-

- 1 - Patient Demographics -

-
-
- Reference Type - -
- -
- Sex - -
- -
- Age From (years) - ref.AgeEnd} - /> -
- -
- Age To (years) - ref.AgeEnd} - /> -
-
- {#if ref.AgeStart !== null && ref.AgeEnd !== null && ref.AgeStart > ref.AgeEnd} -

Age start cannot be greater than age end

- {/if} -
- - -
-

- 2 - Reference Text - -

-
- - - {#if ref.TxtRefType === 'VSET'} - Format: CODE=Description;CODE2=Description2 - {:else} - Enter descriptive text for the reference range - {/if} - -
-
- - -
-

- 3 - Result Flag - -

-
- -
-
-
-
- {/each} -
- {/if} -
- {/if} - - {#snippet footer()} - - - {/snippet} -
+ modalOpen = false} onupdateFormData={(data) => formData = data} />

Are you sure you want to delete this test?

-

- Code: {testToDelete?.TestSiteCode}
- Name: {testToDelete?.TestSiteName} -

-

- This will deactivate the test. It will no longer appear in test lists but historical data will be preserved. -

+

Code: {testToDelete?.TestSiteCode}
Name: {testToDelete?.TestSiteName}

+

This will deactivate the test. Historical data will be preserved.

- {#snippet footer()} - - - {/snippet} -
+ {#snippet footer()}{/snippet} + \ No newline at end of file diff --git a/src/routes/(app)/master-data/tests/TestModal.svelte b/src/routes/(app)/master-data/tests/TestModal.svelte new file mode 100644 index 0000000..3ec14c4 --- /dev/null +++ b/src/routes/(app)/master-data/tests/TestModal.svelte @@ -0,0 +1,104 @@ + + + + +
+ + {#if canHaveRefRange} + + {/if} +
+ + {#if activeTab === 'basic'} + + {:else if activeTab === 'refrange' && canHaveRefRange} + + {/if} + + {#snippet footer()} + + + {/snippet} +
diff --git a/src/routes/(app)/master-data/tests/referenceRange.js b/src/routes/(app)/master-data/tests/referenceRange.js new file mode 100644 index 0000000..a774ff7 --- /dev/null +++ b/src/routes/(app)/master-data/tests/referenceRange.js @@ -0,0 +1,107 @@ +// Reference Range Management Functions + +export const signOptions = [ + { value: 'GE', label: 'โ‰ฅ', description: 'Greater than or equal to' }, + { value: 'GT', label: '>', description: 'Greater than' }, + { value: 'LE', label: 'โ‰ค', description: 'Less than or equal to' }, + { value: 'LT', label: '<', description: 'Less than' } +]; + +export const flagOptions = [ + { value: 'N', label: 'N', description: 'Normal' }, + { value: 'L', label: 'L', description: 'Low' }, + { value: 'H', label: 'H', description: 'High' }, + { value: 'C', label: 'C', description: 'Critical' } +]; + +export const sexOptions = [ + { value: '2', label: 'Male' }, + { value: '1', label: 'Female' }, + { value: '0', label: 'Any' } +]; + +export function createNumRef() { + return { + Sex: '2', + LowSign: 'GE', + HighSign: 'LE', + Low: null, + High: null, + AgeStart: 0, + AgeEnd: 120, + Flag: 'N', + Interpretation: 'Normal' + }; +} + +export function createTholdRef() { + return { + Sex: '2', + LowSign: 'GE', + HighSign: 'LE', + Low: null, + High: null, + AgeStart: 0, + AgeEnd: 120, + Flag: 'N', + Interpretation: 'Normal' + }; +} + +export function createTextRef() { + return { + Sex: '2', + AgeStart: 0, + AgeEnd: 120, + RefTxt: '', + Flag: 'N' + }; +} + +export function createVsetRef() { + return { + Sex: '2', + AgeStart: 0, + AgeEnd: 120, + valueset: '', + Flag: 'N' + }; +} + +export function validateNumericRange(ref, index) { + const errors = []; + if (ref.Low !== null && ref.High !== null && ref.Low > ref.High) { + errors.push(`Range ${index + 1}: Low value cannot be greater than High value`); + } + if (ref.AgeStart !== null && ref.AgeEnd !== null && ref.AgeStart > ref.AgeEnd) { + errors.push(`Range ${index + 1}: Age start cannot be greater than Age end`); + } + return errors; +} + +export function validateTholdRange(ref, index) { + const errors = []; + if (ref.Low !== null && ref.High !== null && ref.Low > ref.High) { + errors.push(`Range ${index + 1}: Low value cannot be greater than High value`); + } + if (ref.AgeStart !== null && ref.AgeEnd !== null && ref.AgeStart > ref.AgeEnd) { + errors.push(`Range ${index + 1}: Age start cannot be greater than Age end`); + } + return errors; +} + +export function validateTextRange(ref, index) { + const errors = []; + if (ref.AgeStart !== null && ref.AgeEnd !== null && ref.AgeStart > ref.AgeEnd) { + errors.push(`Range ${index + 1}: Age start cannot be greater than Age end`); + } + return errors; +} + +export function validateVsetRange(ref, index) { + const errors = []; + if (ref.AgeStart !== null && ref.AgeEnd !== null && ref.AgeStart > ref.AgeEnd) { + errors.push(`Range ${index + 1}: Age start cannot be greater than Age end`); + } + return errors; +} diff --git a/src/routes/(app)/master-data/tests/test-modal/BasicInfoForm.svelte b/src/routes/(app)/master-data/tests/test-modal/BasicInfoForm.svelte new file mode 100644 index 0000000..1e27145 --- /dev/null +++ b/src/routes/(app)/master-data/tests/test-modal/BasicInfoForm.svelte @@ -0,0 +1,185 @@ + + +
+ +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+ + +
+ + + {#if canHaveUnit} +
+ {#if canHaveFormula} +
+ + + Use test codes with operators: +, -, *, / +
+ {/if} +
+ + +
+
+ {/if} + + +
+
+ + +
+
+ Visibility +
+ + +
+
+
+
diff --git a/src/routes/(app)/master-data/tests/test-modal/NumericRefRange.svelte b/src/routes/(app)/master-data/tests/test-modal/NumericRefRange.svelte new file mode 100644 index 0000000..fd7bb0f --- /dev/null +++ b/src/routes/(app)/master-data/tests/test-modal/NumericRefRange.svelte @@ -0,0 +1,266 @@ + + +
+
+
+ +

Numeric Reference Ranges

+
+ +
+ + {#if refnum?.length === 0} +
+ +

No numeric ranges defined

+ +
+ {/if} + + {#each refnum || [] as ref, index (index)} +
+
+ +
+
+
+ Range {index + 1} + {getFlagLabel(ref.Flag)} +
+ +
+ + {getRangePreview(ref)} +
+
+ +
+ + +
+
+ Lower Bound +
+ + +
+
+ +
+ Upper Bound +
+ + +
+
+
+ + +
+
+ Sex + +
+ +
+ Flag + +
+
+ + +
+ + + {#if expandedRanges[index]?.age} +
+
+
+ From (years) + +
+
+ To (years) + +
+
+
+ {/if} +
+ + +
+ + + {#if expandedRanges[index]?.interpretation} +
+
+ +
+
+ {/if} +
+
+
+ {/each} +
diff --git a/src/routes/(app)/master-data/tests/test-modal/ReferenceRangeSection.svelte b/src/routes/(app)/master-data/tests/test-modal/ReferenceRangeSection.svelte new file mode 100644 index 0000000..e44fb44 --- /dev/null +++ b/src/routes/(app)/master-data/tests/test-modal/ReferenceRangeSection.svelte @@ -0,0 +1,179 @@ + + +
+ +
+
+ +

Reference Range Type

+ +
+
+ + + + + +
+
+ + + {#if formData.refRangeType === 'num'} + + {/if} + + + {#if formData.refRangeType === 'thold'} + + {/if} + + + {#if formData.refRangeType === 'text'} + + {/if} + + + {#if formData.refRangeType === 'vset'} + + {/if} +
diff --git a/src/routes/(app)/master-data/tests/test-modal/TextRefRange.svelte b/src/routes/(app)/master-data/tests/test-modal/TextRefRange.svelte new file mode 100644 index 0000000..97b7487 --- /dev/null +++ b/src/routes/(app)/master-data/tests/test-modal/TextRefRange.svelte @@ -0,0 +1,97 @@ + + +
+
+
+ +

Text Reference Ranges

+
+ +
+ + {#if reftxt?.length === 0} +
+ +

No text ranges defined

+ +
+ {/if} + + {#each reftxt || [] as ref, index (index)} +
+
+
+ Range {index + 1} + +
+ +
+
+ Sex + +
+ +
+ Age From + +
+ +
+ Age To + +
+ +
+ Flag + +
+
+ +
+ Reference Text + +
+
+
+ {/each} +
diff --git a/src/routes/(app)/master-data/tests/test-modal/ThresholdRefRange.svelte b/src/routes/(app)/master-data/tests/test-modal/ThresholdRefRange.svelte new file mode 100644 index 0000000..3abd96c --- /dev/null +++ b/src/routes/(app)/master-data/tests/test-modal/ThresholdRefRange.svelte @@ -0,0 +1,124 @@ + + +
+
+
+ +

Threshold Reference Ranges

+
+ +
+ + {#if refthold?.length === 0} +
+ +

No threshold ranges defined

+ +
+ {/if} + + {#each refthold || [] as ref, index (index)} +
+
+
+ Range {index + 1} + +
+ +
+
+ Sex + +
+ +
+ Age From + +
+ +
+ Age To + +
+ +
+ Flag + +
+
+ +
+
+ Lower Value +
+ + +
+
+ +
+ Upper Value +
+ + +
+
+
+ +
+ Interpretation + +
+
+
+ {/each} +
diff --git a/src/routes/(app)/master-data/tests/test-modal/ValueSetRefRange.svelte b/src/routes/(app)/master-data/tests/test-modal/ValueSetRefRange.svelte new file mode 100644 index 0000000..66dedbd --- /dev/null +++ b/src/routes/(app)/master-data/tests/test-modal/ValueSetRefRange.svelte @@ -0,0 +1,98 @@ + + +
+
+
+ +

Value Set Reference Ranges

+
+ +
+ + {#if refvset?.length === 0} +
+ +

No value set ranges defined

+ +
+ {/if} + + {#each refvset || [] as ref, index (index)} +
+
+
+ Range {index + 1} + +
+ +
+
+ Sex + +
+ +
+ Age From + +
+ +
+ Age To + +
+ +
+ Flag + +
+
+ +
+ Value Set + + Comma-separated list of allowed values +
+
+
+ {/each} +
diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index 69a1469..9f7a1b4 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -1,9 +1,17 @@ @@ -11,6 +19,15 @@
- {@render children()} + {#if configLoading} +
+
+ +

Loading configuration...

+
+
+ {:else} + {@render children()} + {/if}
diff --git a/static/config.json b/static/config.json new file mode 100644 index 0000000..1483461 --- /dev/null +++ b/static/config.json @@ -0,0 +1,3 @@ +{ + "apiUrl": "http://localhost/clqms01" +}