690 lines
29 KiB
Markdown
690 lines
29 KiB
Markdown
|
|
# Plan: Multiple Reference Ranges with Advanced Dialog
|
|||
|
|
|
|||
|
|
## Overview
|
|||
|
|
Refactor the "Reff" tab to support multiple reference ranges using the existing `refnum` table schema.
|
|||
|
|
|
|||
|
|
## Existing Database Schema (refnum table)
|
|||
|
|
|
|||
|
|
| Field | Type | Description |
|
|||
|
|
|-------|------|-------------|
|
|||
|
|
| RefNumID | INT AUTO_INCREMENT | Primary key |
|
|||
|
|
| SiteID | INT | Site identifier |
|
|||
|
|
| TestSiteID | INT | Links to test |
|
|||
|
|
| SpcType | INT | Specimen type |
|
|||
|
|
| Sex | INT | Gender (from valueset) |
|
|||
|
|
| Criteria | VARCHAR(100) | Additional criteria |
|
|||
|
|
| AgeStart | INT | Age range start |
|
|||
|
|
| AgeEnd | INT | Age range end |
|
|||
|
|
| **NumRefType** | INT | **Input format: 1=NMRC, 2=TH, 3=TEXT, 4=LIST** |
|
|||
|
|
| **RangeType** | INT | **Result category: 1=REF, 2=CRTC, 3=VAL, 4=RERUN** |
|
|||
|
|
| LowSign | INT | Low operator: 1='<', 2='<=', 3='>=', 4='>', 5='<>' |
|
|||
|
|
| Low | INT | Low value |
|
|||
|
|
| HighSign | INT | High operator |
|
|||
|
|
| High | INT | High value |
|
|||
|
|
| Display | INT | Display order |
|
|||
|
|
| **Flag** | VARCHAR(10) | **Like Label (e.g., "Negative", "Borderline")** |
|
|||
|
|
| Interpretation | VARCHAR(255) | Interpretation text |
|
|||
|
|
| Notes | VARCHAR(255) | Notes |
|
|||
|
|
| CreateDate | Datetime | Creation timestamp |
|
|||
|
|
| StartDate | Datetime | Start date |
|
|||
|
|
| EndDate | Datetime | Soft delete |
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Key Concept: NumRefType vs RangeType
|
|||
|
|
|
|||
|
|
| Aspect | NumRefType | RangeType |
|
|||
|
|
|--------|------------|-----------|
|
|||
|
|
| **Location** | Main Reff Tab + Advanced Dialog | Advanced Dialog |
|
|||
|
|
| **Purpose** | Input format | Result categorization |
|
|||
|
|
| **Values** | 1=NMRC, 2=TH, 3=TEXT, 4=LIST | 1=REF, 2=CRTC, 3=VAL, 4=RERUN |
|
|||
|
|
| **Database Field** | NumRefType | RangeType |
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## UI Design
|
|||
|
|
|
|||
|
|
### Main Reff Tab (Simple)
|
|||
|
|
```
|
|||
|
|
┌─────────────────────────────────────────────────────────────────┐
|
|||
|
|
│ Reference Ranges │
|
|||
|
|
├─────────────────────────────────────────────────────────────────┤
|
|||
|
|
│ │
|
|||
|
|
│ Ref Type: [ Numeric (NMRC) ▼ ] │
|
|||
|
|
│ │
|
|||
|
|
│ ┌─────────────────────────────────────────────────────────────┐│
|
|||
|
|
│ │ For Numeric (with operators > < <= >=): ││
|
|||
|
|
│ │ Ref Low: [0.00 ] Ref High: [100.00 ] ││
|
|||
|
|
│ │ Crit Low: [<55.00 ] Crit High: [>115.00 ] ││
|
|||
|
|
│ │ ││
|
|||
|
|
│ │ Examples: 0-100, <50, >=100, <>0 (not equal to 0) ││
|
|||
|
|
│ └─────────────────────────────────────────────────────────────┘│
|
|||
|
|
│ │
|
|||
|
|
│ ┌─────────────────────────────────────────────────────────────┐│
|
|||
|
|
│ │ For Threshold: ││
|
|||
|
|
│ │ Below Text: [Below Normal] Below Value: [<] [50] ││
|
|||
|
|
│ │ Above Text: [Above Normal] Above Value: [>] [150] ││
|
|||
|
|
│ └─────────────────────────────────────────────────────────────┘│
|
|||
|
|
│ │
|
|||
|
|
│ [Advanced Settings ▼] │
|
|||
|
|
│ │
|
|||
|
|
└─────────────────────────────────────────────────────────────────┘
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Advanced Dialog (Multiple Reference Ranges)
|
|||
|
|
```
|
|||
|
|
┌───────────────────────────────────────────────────────────────────────────────┐
|
|||
|
|
│ Advanced Reference Ranges [X]Close│
|
|||
|
|
├───────────────────────────────────────────────────────────────────────────────┤
|
|||
|
|
│ │
|
|||
|
|
│ [Add RefType ▼] [Add Button] │
|
|||
|
|
│ │
|
|||
|
|
│ ┌─────────────────────────────────────────────────────────────────────────┐ │
|
|||
|
|
│ │ RefType │ Flag/Label │ RangeType │ Sex │ Age │ Low │ High │ [×] │ │
|
|||
|
|
│ │─────────┼────────────┼───────────┼─────┼─────┼────────┼────────┼───────│ │
|
|||
|
|
│ │ NMRC │ Negative │ REF (1) │ All │ 0-150│ 0 │ 25 │ [×] │ │
|
|||
|
|
│ │ NMRC │ Borderline │ REF (1) │ All │ 0-150│ 25 │ 50 │ [×] │ │
|
|||
|
|
│ │ NMRC │ Positive │ REF (1) │ All │ 0-150│ 50 │ │ [×] │ │
|
|||
|
|
│ │ TEXT │ Negative │ REF (1) │ All │ 0-150│ │ │ [×] │ │
|
|||
|
|
│ │ TH │ Low │ REF (1) │ All │ 0-150│ <50 │ │ [×] │ │
|
|||
|
|
│ │ NMRC │ Critical │ CRTC (2) │ All │ 0-150│ <55 │ >115 │ [×] │ │
|
|||
|
|
│ └─────────────────────────────────────────────────────────────────────────┘ │
|
|||
|
|
│ │
|
|||
|
|
│ [Cancel] [Save Advanced Ranges] │
|
|||
|
|
│ │
|
|||
|
|
└───────────────────────────────────────────────────────────────────────────────┘
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Key Features:**
|
|||
|
|
- **RefType** column: NMRC (1), TH (2), TEXT (3), LIST (4)
|
|||
|
|
- **RangeType** column: REF (1), CRTC (2), VAL (3), RERUN (4)
|
|||
|
|
- **Flag** column: Display label for the result (e.g., "Negative", "Borderline")
|
|||
|
|
- **Low/High** columns: Support operators via LowSign/HighSign fields
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Implementation Plan
|
|||
|
|
|
|||
|
|
### Phase 1: Backend Changes (Tests.php Controller)
|
|||
|
|
|
|||
|
|
#### 1.1 Add RefType and RangeType constants
|
|||
|
|
```php
|
|||
|
|
// At top of Tests.php
|
|||
|
|
const REFTYPE_NMRC = 1;
|
|||
|
|
const REFTYPE_TH = 2;
|
|||
|
|
const REFTYPE_TEXT = 3;
|
|||
|
|
const REFTYPE_LIST = 4;
|
|||
|
|
|
|||
|
|
const RANGETYPE_REF = 1;
|
|||
|
|
const RANGETYPE_CRTC = 2;
|
|||
|
|
const RANGETYPE_VAL = 3;
|
|||
|
|
const RANGETYPE_RERUN = 4;
|
|||
|
|
|
|||
|
|
const LOWSIGN_LT = 1;
|
|||
|
|
const LOWSIGN_LTE = 2;
|
|||
|
|
const LOWSIGN_GTE = 3;
|
|||
|
|
const LOWSIGN_GT = 4;
|
|||
|
|
const LOWSIGN_NE = 5;
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 1.2 Update `show()` method to load refnum data
|
|||
|
|
```php
|
|||
|
|
// Add after loading testdeftech/testdefcal
|
|||
|
|
$row['refnum'] = $this->RefNumModel->where('TestSiteID', $id)
|
|||
|
|
->where('EndDate IS NULL')
|
|||
|
|
->orderBy('Display', 'ASC')
|
|||
|
|
->findAll();
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 1.3 Update `saveRefNum()` helper method
|
|||
|
|
```php
|
|||
|
|
private function saveRefNum($testSiteID, $refRanges, $action, $siteID = 1) {
|
|||
|
|
if ($action === 'update') {
|
|||
|
|
// Soft delete existing refnums
|
|||
|
|
$this->RefNumModel->where('TestSiteID', $testSiteID)
|
|||
|
|
->set('EndDate', date('Y-m-d H:i:s'))
|
|||
|
|
->update();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
foreach ($refRanges as $index => $ref) {
|
|||
|
|
$refData = [
|
|||
|
|
'TestSiteID' => $testSiteID,
|
|||
|
|
'SiteID' => $siteID,
|
|||
|
|
'NumRefType' => $ref['RefType'] ?? self::REFTYPE_NMRC,
|
|||
|
|
'RangeType' => $ref['RangeType'] ?? self::RANGETYPE_REF,
|
|||
|
|
'Flag' => $ref['Flag'] ?? null, // Label for display
|
|||
|
|
'Sex' => $ref['Sex'] ?? 0, // 0=All, 1=M, 2=F (from valueset)
|
|||
|
|
'AgeStart' => $ref['AgeStart'] ?? 0,
|
|||
|
|
'AgeEnd' => $ref['AgeEnd'] ?? 150,
|
|||
|
|
'LowSign' => $this->parseSign($ref['Low'] ?? ''),
|
|||
|
|
'Low' => $this->parseValue($ref['Low'] ?? ''),
|
|||
|
|
'HighSign' => $this->parseSign($ref['High'] ?? ''),
|
|||
|
|
'High' => $this->parseValue($ref['High'] ?? ''),
|
|||
|
|
'Display' => $index,
|
|||
|
|
'CreateDate' => date('Y-m-d H:i:s')
|
|||
|
|
];
|
|||
|
|
$this->RefNumModel->insert($refData);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Helper to extract operator from value like "<=50"
|
|||
|
|
private function parseSign($value) {
|
|||
|
|
if (str_starts_with($value, '<>')) return self::LOWSIGN_NE;
|
|||
|
|
if (str_starts_with($value, '<=')) return self::LOWSIGN_LTE;
|
|||
|
|
if (str_starts_with($value, '<')) return self::LOWSIGN_LT;
|
|||
|
|
if (str_starts_with($value, '>=')) return self::LOWSIGN_GTE;
|
|||
|
|
if (str_starts_with($value, '>')) return self::LOWSIGN_GT;
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Helper to extract numeric value from operator-prefixed string
|
|||
|
|
private function parseValue($value) {
|
|||
|
|
return preg_replace('/^[<>=<>]+/', '', $value) ?: null;
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 1.4 Update `handleDetails()` to save refnum
|
|||
|
|
```php
|
|||
|
|
// Add in handleDetails method, after saving tech/calc details
|
|||
|
|
if (isset($input['refnum']) && is_array($input['refnum'])) {
|
|||
|
|
$this->saveRefNum($testSiteID, $input['refnum'], $action, $input['SiteID'] ?? 1);
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 1.5 Update `delete()` to soft delete refnum
|
|||
|
|
```php
|
|||
|
|
// Add in delete method
|
|||
|
|
$now = date('Y-m-d H:i:s');
|
|||
|
|
$this->RefNumModel->where('TestSiteID', $id)
|
|||
|
|
->set('EndDate', $now)
|
|||
|
|
->update();
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### Phase 2: Frontend Changes (tests_index.php)
|
|||
|
|
|
|||
|
|
#### 2.1 Update form state to include advanced ref ranges
|
|||
|
|
```javascript
|
|||
|
|
form: {
|
|||
|
|
// ... existing fields ...
|
|||
|
|
// Advanced ranges
|
|||
|
|
refRanges: [], // Array of advanced reference range objects
|
|||
|
|
// Dialog states
|
|||
|
|
showAdvancedRefModal: false,
|
|||
|
|
advancedRefRanges: [],
|
|||
|
|
newRefType: 1 // Default: NMRC
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// RefType options for select
|
|||
|
|
refTypeOptions: [
|
|||
|
|
{ value: 1, label: 'Numeric (NMRC)' },
|
|||
|
|
{ value: 2, label: 'Threshold (TH)' },
|
|||
|
|
{ value: 3, label: 'Text (TEXT)' },
|
|||
|
|
{ value: 4, label: 'Value Set (LIST)' }
|
|||
|
|
]
|
|||
|
|
|
|||
|
|
// RangeType options
|
|||
|
|
rangeTypeOptions: [
|
|||
|
|
{ value: 1, label: 'REF' },
|
|||
|
|
{ value: 2, label: 'CRTC' },
|
|||
|
|
{ value: 3, label: 'VAL' },
|
|||
|
|
{ value: 4, label: 'RERUN' }
|
|||
|
|
]
|
|||
|
|
|
|||
|
|
// Sex options
|
|||
|
|
sexOptions: [
|
|||
|
|
{ value: 0, label: 'All' },
|
|||
|
|
{ value: 1, label: 'Male' },
|
|||
|
|
{ value: 2, label: 'Female' }
|
|||
|
|
]
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 2.2 Update `editTest()` to load refnum data
|
|||
|
|
```javascript
|
|||
|
|
if (testData.refnum && testData.refnum.length > 0) {
|
|||
|
|
this.form.refRanges = testData.refnum.map(r => ({
|
|||
|
|
RefNumID: r.RefNumID,
|
|||
|
|
RefType: r.NumRefType || 1,
|
|||
|
|
RangeType: r.RangeType || 1,
|
|||
|
|
Flag: r.Flag || '',
|
|||
|
|
Sex: r.Sex || 0,
|
|||
|
|
AgeStart: r.AgeStart || 0,
|
|||
|
|
AgeEnd: r.AgeEnd || 150,
|
|||
|
|
Low: this.formatValueWithSign(r.LowSign, r.Low),
|
|||
|
|
High: this.formatValueWithSign(r.HighSign, r.High)
|
|||
|
|
}));
|
|||
|
|
} else {
|
|||
|
|
this.form.refRanges = [];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Format value with operator sign for display
|
|||
|
|
formatValueWithSign(sign, value) {
|
|||
|
|
if (!value && value !== 0) return '';
|
|||
|
|
const signs = {
|
|||
|
|
1: '<', 2: '<=', 3: '>=', 4: '>', 5: '<>'
|
|||
|
|
};
|
|||
|
|
return (signs[sign] || '') + value;
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 2.3 Update `save()` to include refnum in payload
|
|||
|
|
```javascript
|
|||
|
|
if (this.form.refRanges && this.form.refRanges.length > 0) {
|
|||
|
|
payload.refnum = this.form.refRanges.map(r => ({
|
|||
|
|
RefType: r.RefType,
|
|||
|
|
RangeType: r.RangeType,
|
|||
|
|
Flag: r.Flag,
|
|||
|
|
Sex: r.Sex,
|
|||
|
|
AgeStart: r.AgeStart,
|
|||
|
|
AgeEnd: r.AgeEnd,
|
|||
|
|
Low: r.Low,
|
|||
|
|
High: r.High
|
|||
|
|
}));
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 2.4 Add helper methods for advanced ref ranges
|
|||
|
|
```javascript
|
|||
|
|
// Open advanced dialog
|
|||
|
|
openAdvancedRefDialog() {
|
|||
|
|
this.advancedRefRanges = this.form.refRanges.length > 0
|
|||
|
|
? [...this.form.refRanges]
|
|||
|
|
: [{
|
|||
|
|
RefNumID: null,
|
|||
|
|
RefType: this.form.RefType || 1,
|
|||
|
|
RangeType: 1,
|
|||
|
|
Flag: '',
|
|||
|
|
Sex: 0,
|
|||
|
|
AgeStart: 0,
|
|||
|
|
AgeEnd: 150,
|
|||
|
|
Low: this.form.RefLow || '',
|
|||
|
|
High: this.form.RefHigh || ''
|
|||
|
|
}];
|
|||
|
|
|
|||
|
|
// Add CRTC if critical values exist
|
|||
|
|
if ((this.form.CritLow || this.form.CritHigh) &&
|
|||
|
|
!this.advancedRefRanges.some(r => r.RangeType === 2)) {
|
|||
|
|
this.advancedRefRanges.push({
|
|||
|
|
RefNumID: null,
|
|||
|
|
RefType: 1,
|
|||
|
|
RangeType: 2,
|
|||
|
|
Flag: 'Critical',
|
|||
|
|
Sex: 0,
|
|||
|
|
AgeStart: 0,
|
|||
|
|
AgeEnd: 150,
|
|||
|
|
Low: this.form.CritLow || '',
|
|||
|
|
High: this.form.CritHigh || ''
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
this.showAdvancedRefModal = true;
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// Add new advanced range
|
|||
|
|
addAdvancedRefRange() {
|
|||
|
|
this.advancedRefRanges.push({
|
|||
|
|
RefNumID: null,
|
|||
|
|
RefType: this.newRefType,
|
|||
|
|
RangeType: 1,
|
|||
|
|
Flag: '',
|
|||
|
|
Sex: 0,
|
|||
|
|
AgeStart: 0,
|
|||
|
|
AgeEnd: 150,
|
|||
|
|
Low: '',
|
|||
|
|
High: ''
|
|||
|
|
});
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// Remove advanced range
|
|||
|
|
removeAdvancedRefRange(index) {
|
|||
|
|
this.advancedRefRanges.splice(index, 1);
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// Save advanced ranges and close
|
|||
|
|
saveAdvancedRefRanges() {
|
|||
|
|
this.form.refRanges = [...this.advancedRefRanges];
|
|||
|
|
this.showAdvancedRefModal = false;
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// Cancel advanced dialog
|
|||
|
|
cancelAdvancedRefDialog() {
|
|||
|
|
this.showAdvancedRefModal = false;
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### Phase 3: UI Changes (test_dialog.php)
|
|||
|
|
|
|||
|
|
#### 3.1 Keep main Reff tab with RefType selector
|
|||
|
|
```html
|
|||
|
|
<!-- Reff Tab - Main (Simple) -->
|
|||
|
|
<div x-show="form.dialogTab === 'reff'" class="space-y-4" x-cloak>
|
|||
|
|
|
|||
|
|
<!-- RefType Selector -->
|
|||
|
|
<div>
|
|||
|
|
<label class="label"><span class="label-text font-medium">Reference Type</span></label>
|
|||
|
|
<select class="select w-full" x-model="form.RefType">
|
|||
|
|
<option value="1">Numeric Range (NMRC)</option>
|
|||
|
|
<option value="2">Threshold (TH)</option>
|
|||
|
|
<option value="3">Text Result (TEXT)</option>
|
|||
|
|
<option value="4">Value Set (LIST)</option>
|
|||
|
|
</select>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<!-- Numeric Range Fields -->
|
|||
|
|
<template x-if="form.RefType == '1'">
|
|||
|
|
<div class="space-y-4">
|
|||
|
|
<div class="grid grid-cols-2 gap-4">
|
|||
|
|
<div>
|
|||
|
|
<label class="label"><span class="label-text">Ref Low</span></label>
|
|||
|
|
<input type="text" class="input" x-model="form.RefLow" placeholder="0.00" />
|
|||
|
|
</div>
|
|||
|
|
<div>
|
|||
|
|
<label class="label"><span class="label-text">Ref High</span></label>
|
|||
|
|
<input type="text" class="input" x-model="form.RefHigh" placeholder="10.00" />
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
<div class="grid grid-cols-2 gap-4">
|
|||
|
|
<div>
|
|||
|
|
<label class="label"><span class="label-text text-error">Crit Low</span></label>
|
|||
|
|
<input type="text" class="input border-error/30" x-model="form.CritLow" placeholder="0.00" />
|
|||
|
|
</div>
|
|||
|
|
<div>
|
|||
|
|
<label class="label"><span class="label-text text-error">Crit High</span></label>
|
|||
|
|
<input type="text" class="input border-error/30" x-model="form.CritHigh" placeholder="20.00" />
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
<div class="grid grid-cols-2 gap-4">
|
|||
|
|
<div>
|
|||
|
|
<label class="label"><span class="label-text">Unit</span></label>
|
|||
|
|
<input type="text" class="input" x-model="form.Unit1" placeholder="mg/dL" />
|
|||
|
|
</div>
|
|||
|
|
<div>
|
|||
|
|
<label class="label"><span class="label-text">Decimals</span></label>
|
|||
|
|
<input type="number" class="input" x-model="form.Decimal" />
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</template>
|
|||
|
|
|
|||
|
|
<!-- Threshold Fields -->
|
|||
|
|
<template x-if="form.RefType == '2'">
|
|||
|
|
<div class="space-y-4">
|
|||
|
|
<div class="p-4 bg-info/10 border border-info/20 rounded-lg">
|
|||
|
|
<p class="text-sm mb-3"><strong>Below Threshold:</strong></p>
|
|||
|
|
<div class="grid grid-cols-3 gap-4">
|
|||
|
|
<div>
|
|||
|
|
<label class="label"><span class="label-text">Below Text</span></label>
|
|||
|
|
<input type="text" class="input" x-model="form.RefText" placeholder="Below Normal" />
|
|||
|
|
</div>
|
|||
|
|
<div>
|
|||
|
|
<label class="label"><span class="label-text">Operator</span></label>
|
|||
|
|
<select class="select" x-model="form.BelowOp">
|
|||
|
|
<option value="<"><</option>
|
|||
|
|
<option value="<="><=</option>
|
|||
|
|
<option value="<>"><></option>
|
|||
|
|
</select>
|
|||
|
|
</div>
|
|||
|
|
<div>
|
|||
|
|
<label class="label"><span class="label-text">Value</span></label>
|
|||
|
|
<input type="text" class="input" x-model="form.BelowVal" placeholder="0.00" />
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
<div class="p-4 bg-warning/10 border border-warning/20 rounded-lg">
|
|||
|
|
<p class="text-sm mb-3"><strong>Above Threshold:</strong></p>
|
|||
|
|
<div class="grid grid-cols-3 gap-4">
|
|||
|
|
<div>
|
|||
|
|
<label class="label"><span class="label-text">Above Text</span></label>
|
|||
|
|
<input type="text" class="input" x-model="form.AboveText" placeholder="Above Normal" />
|
|||
|
|
</div>
|
|||
|
|
<div>
|
|||
|
|
<label class="label"><span class="label-text">Operator</span></label>
|
|||
|
|
<select class="select" x-model="form.AboveOp">
|
|||
|
|
<option value=">">></option>
|
|||
|
|
<option value=">=">>=</option>
|
|||
|
|
</select>
|
|||
|
|
</div>
|
|||
|
|
<div>
|
|||
|
|
<label class="label"><span class="label-text">Value</span></label>
|
|||
|
|
<input type="text" class="input" x-model="form.AboveVal" placeholder="0.00" />
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</template>
|
|||
|
|
|
|||
|
|
<!-- Text Result Fields -->
|
|||
|
|
<template x-if="form.RefType == '3'">
|
|||
|
|
<div class="space-y-4">
|
|||
|
|
<div>
|
|||
|
|
<label class="label"><span class="label-text">Default Text</span></label>
|
|||
|
|
<input type="text" class="input" x-model="form.RefText" placeholder="e.g., Negative" />
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</template>
|
|||
|
|
|
|||
|
|
<!-- Value Set Fields -->
|
|||
|
|
<template x-if="form.RefType == '4'">
|
|||
|
|
<div class="space-y-4">
|
|||
|
|
<div>
|
|||
|
|
<label class="label"><span class="label-text">Value Set</span></label>
|
|||
|
|
<select class="select w-full" x-model="form.VSetDefID">
|
|||
|
|
<option value="">Select Value Set...</option>
|
|||
|
|
<template x-for="v in vsetDefsList" :key="v.VSetDefID">
|
|||
|
|
<option :value="v.VSetDefID" x-text="v.VSDesc"></option>
|
|||
|
|
</template>
|
|||
|
|
</select>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</template>
|
|||
|
|
|
|||
|
|
<!-- Advanced Button -->
|
|||
|
|
<div class="mt-4 pt-4 border-t" style="border-color: rgb(var(--color-border));">
|
|||
|
|
<button class="btn btn-outline btn-sm" @click="openAdvancedRefDialog()">
|
|||
|
|
<i class="fa-solid fa-gear mr-1"></i>
|
|||
|
|
Advanced Settings
|
|||
|
|
</button>
|
|||
|
|
<span class="ml-2 text-xs opacity-60" x-show="form.refRanges.length > 0">
|
|||
|
|
<i class="fa-solid fa-check text-success mr-1"></i>
|
|||
|
|
<span x-text="form.refRanges.length + ' advanced ranges configured'"></span>
|
|||
|
|
</span>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 3.2 Add Advanced RefRanges Modal
|
|||
|
|
```html
|
|||
|
|
<!-- Advanced Reference Ranges Modal -->
|
|||
|
|
<div x-show="showAdvancedRefModal" x-cloak class="modal-overlay" @click.self="cancelAdvancedRefDialog()">
|
|||
|
|
<div class="modal-content p-6 max-w-5xl w-full max-h-[90vh] overflow-y-auto">
|
|||
|
|
<div class="flex items-center justify-between mb-4">
|
|||
|
|
<h3 class="font-bold text-lg">Advanced Reference Ranges</h3>
|
|||
|
|
<button class="btn btn-ghost btn-sm btn-square" @click="cancelAdvancedRefDialog()">
|
|||
|
|
<i class="fa-solid fa-times"></i>
|
|||
|
|
</button>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<!-- Add Row Controls -->
|
|||
|
|
<div class="flex gap-2 mb-4 p-3 bg-base-200 rounded-lg">
|
|||
|
|
<select class="select select-sm" x-model="newRefType">
|
|||
|
|
<option :value="1">Numeric (NMRC)</option>
|
|||
|
|
<option :value="2">Threshold (TH)</option>
|
|||
|
|
<option :value="3">Text (TEXT)</option>
|
|||
|
|
<option :value="4">Value Set (LIST)</option>
|
|||
|
|
</select>
|
|||
|
|
<button class="btn btn-sm btn-outline" @click="addAdvancedRefRange()">
|
|||
|
|
<i class="fa-solid fa-plus mr-1"></i> Add Range
|
|||
|
|
</button>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<!-- Advanced Ranges Table -->
|
|||
|
|
<div class="overflow-x-auto mb-4">
|
|||
|
|
<table class="table table-sm table-compact w-full">
|
|||
|
|
<thead>
|
|||
|
|
<tr>
|
|||
|
|
<th style="width: 80px;">RefType</th>
|
|||
|
|
<th style="width: 120px;">Flag/Label</th>
|
|||
|
|
<th style="width: 80px;">RangeType</th>
|
|||
|
|
<th style="width: 60px;">Sex</th>
|
|||
|
|
<th style="width: 70px;">Age From</th>
|
|||
|
|
<th style="width: 70px;">Age To</th>
|
|||
|
|
<th style="width: 100px;">Low</th>
|
|||
|
|
<th style="width: 100px;">High</th>
|
|||
|
|
<th style="width: 40px;"></th>
|
|||
|
|
</tr>
|
|||
|
|
</thead>
|
|||
|
|
<tbody>
|
|||
|
|
<template x-for="(ref, index) in advancedRefRanges" :key="index">
|
|||
|
|
<tr :class="{ 'bg-error/5': ref.RangeType == 2 }">
|
|||
|
|
<!-- RefType -->
|
|||
|
|
<td>
|
|||
|
|
<select
|
|||
|
|
class="select select-xs w-full"
|
|||
|
|
x-model="ref.RefType"
|
|||
|
|
>
|
|||
|
|
<option :value="1">NMRC</option>
|
|||
|
|
<option :value="2">TH</option>
|
|||
|
|
<option :value="3">TEXT</option>
|
|||
|
|
<option :value="4">LIST</option>
|
|||
|
|
</select>
|
|||
|
|
</td>
|
|||
|
|
|
|||
|
|
<!-- Flag/Label -->
|
|||
|
|
<td>
|
|||
|
|
<input
|
|||
|
|
type="text"
|
|||
|
|
class="input input-xs w-full"
|
|||
|
|
x-model="ref.Flag"
|
|||
|
|
placeholder="e.g., Negative"
|
|||
|
|
/>
|
|||
|
|
</td>
|
|||
|
|
|
|||
|
|
<!-- RangeType -->
|
|||
|
|
<td>
|
|||
|
|
<select
|
|||
|
|
class="select select-xs w-full"
|
|||
|
|
:class="{ 'border-error/30 bg-error/10': ref.RangeType == 2 }"
|
|||
|
|
x-model="ref.RangeType"
|
|||
|
|
>
|
|||
|
|
<option :value="1">REF</option>
|
|||
|
|
<option :value="2">CRTC</option>
|
|||
|
|
<option :value="3">VAL</option>
|
|||
|
|
<option :value="4">RERUN</option>
|
|||
|
|
</select>
|
|||
|
|
</td>
|
|||
|
|
|
|||
|
|
<!-- Sex -->
|
|||
|
|
<td>
|
|||
|
|
<select class="select select-xs w-full" x-model="ref.Sex">
|
|||
|
|
<option :value="0">All</option>
|
|||
|
|
<option :value="1">M</option>
|
|||
|
|
<option :value="2">F</option>
|
|||
|
|
</select>
|
|||
|
|
</td>
|
|||
|
|
|
|||
|
|
<!-- Age From -->
|
|||
|
|
<td>
|
|||
|
|
<input type="number" class="input input-xs w-full text-center" x-model="ref.AgeStart" />
|
|||
|
|
</td>
|
|||
|
|
|
|||
|
|
<!-- Age To -->
|
|||
|
|
<td>
|
|||
|
|
<input type="number" class="input input-xs w-full text-center" x-model="ref.AgeEnd" />
|
|||
|
|
</td>
|
|||
|
|
|
|||
|
|
<!-- Low -->
|
|||
|
|
<td>
|
|||
|
|
<input
|
|||
|
|
type="text"
|
|||
|
|
class="input input-xs w-full text-center"
|
|||
|
|
:class="{ 'border-error/30': ref.RangeType == 2 }"
|
|||
|
|
x-model="ref.Low"
|
|||
|
|
placeholder="0.00 or <=10"
|
|||
|
|
/>
|
|||
|
|
</td>
|
|||
|
|
|
|||
|
|
<!-- High -->
|
|||
|
|
<td>
|
|||
|
|
<input
|
|||
|
|
type="text"
|
|||
|
|
class="input input-xs w-full text-center"
|
|||
|
|
:class="{ 'border-error/30': ref.RangeType == 2 }"
|
|||
|
|
x-model="ref.High"
|
|||
|
|
placeholder="0.00"
|
|||
|
|
/>
|
|||
|
|
</td>
|
|||
|
|
|
|||
|
|
<!-- Delete -->
|
|||
|
|
<td>
|
|||
|
|
<button class="btn btn-ghost btn-xs btn-square text-error" @click="removeAdvancedRefRange(index)">
|
|||
|
|
<i class="fa-solid fa-times"></i>
|
|||
|
|
</button>
|
|||
|
|
</td>
|
|||
|
|
</tr>
|
|||
|
|
</template>
|
|||
|
|
|
|||
|
|
<!-- Empty State -->
|
|||
|
|
<template x-if="advancedRefRanges.length === 0">
|
|||
|
|
<tr>
|
|||
|
|
<td colspan="9" class="text-center py-8 text-base-400">
|
|||
|
|
<i class="fa-solid fa-layer-group mr-2"></i>
|
|||
|
|
No advanced ranges. Click "Add Range" to create one.
|
|||
|
|
</td>
|
|||
|
|
</tr>
|
|||
|
|
</template>
|
|||
|
|
</tbody>
|
|||
|
|
</table>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<!-- Legend -->
|
|||
|
|
<div class="text-xs opacity-60 p-2 border rounded bg-base-200 flex gap-4 mb-4">
|
|||
|
|
<span><strong>1</strong>=NMRC</span>
|
|||
|
|
<span><strong>2</strong>=TH</span>
|
|||
|
|
<span><strong>3</strong>=TEXT</span>
|
|||
|
|
<span><strong>4</strong>=LIST</span>
|
|||
|
|
<span class="ml-4"><strong>1</strong>=REF</span>
|
|||
|
|
<span><strong>2</strong>=CRTC</span>
|
|||
|
|
<span><strong>3</strong>=VAL</span>
|
|||
|
|
<span><strong>4</strong>=RERUN</span>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<!-- Actions -->
|
|||
|
|
<div class="flex gap-2 pt-4 border-t" style="border-color: rgb(var(--color-border));">
|
|||
|
|
<button class="btn btn-ghost flex-1" @click="cancelAdvancedRefDialog()">Cancel</button>
|
|||
|
|
<button class="btn btn-primary flex-1" @click="saveAdvancedRefRanges()">
|
|||
|
|
<i class="fa-solid fa-check mr-1"></i> Save Advanced Ranges
|
|||
|
|
</button>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Summary of Files to Modify
|
|||
|
|
|
|||
|
|
| File | Changes |
|
|||
|
|
|------|---------|
|
|||
|
|
| `app/Controllers/Tests.php` | Add constants, refnum loading/save helper, delete update |
|
|||
|
|
| `app/Models/RefRange/RefNumModel.php` | Ensure allowedFields includes all needed fields |
|
|||
|
|
| `app/Views/v2/master/tests/tests_index.php` | Add refRanges state, helper methods, modal state |
|
|||
|
|
| `app/Views/v2/master/tests/test_dialog.php` | Update Reff tab with numeric RefType, add Advanced modal |
|
|||
|
|
|
|||
|
|
## Workflow
|
|||
|
|
|
|||
|
|
1. **Basic users** - Use global RefLow/RefHigh fields on main tab
|
|||
|
|
2. **Advanced users** - Click "Advanced Settings" to open modal
|
|||
|
|
3. **Modal** - Add/edit/remove multiple ranges with criteria
|
|||
|
|
4. **Save** - Advanced ranges saved to refnum table, global fields saved to testdeftech
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Next Steps
|
|||
|
|
|
|||
|
|
1. Review this plan
|
|||
|
|
2. Provide feedback or request changes
|
|||
|
|
3. Once approved, switch to Code mode for implementation
|