clqms-fe1/backup/tests_backup/test-modal/ReferenceRangeSection.svelte
mahdahar 99d622ad05 refactor(tests): consolidate test management modal components
- Consolidate fragmented test modal components into unified TestFormModal.svelte
- Reorganize reference range components into organized tabs (RefNumTab, RefTxtTab)
- Add new tab components: BasicInfoTab, TechDetailsTab, CalcDetailsTab, MappingsTab, GroupMembersTab
- Move test-related type definitions to src/lib/types/test.types.ts for better type organization
- Delete old component files: TestModal.svelte and 10+ sub-components
- Add backup directory for old component preservation
- Update AGENTS.md with coding guidelines and project conventions
- Update tests.js API client with improved structure
- Add documentation for frontend test management architecture
2026-02-20 13:51:54 +07:00

286 lines
9.0 KiB
Svelte

<script>
import { Ruler, Info } from 'lucide-svelte';
import HelpTooltip from '$lib/components/HelpTooltip.svelte';
import NumericRefRange from './NumericRefRange.svelte';
import TextRefRange from './TextRefRange.svelte';
import { createNumRef, createTholdRef, createTextRef, createVsetRef } from '../referenceRange.js';
/**
* @typedef {Object} Props
* @property {Object} formData - Form data object
* @property {string} testType - Test type
* @property {(formData: Object) => void} onupdateFormData - Update handler
*/
/** @type {Props} */
let {
formData = $bindable({}),
testType = '',
onupdateFormData = () => {}
} = $props();
// Map refRangeType to RefType labels for display
const refTypeLabels = {
'none': 'None',
'num': 'RANGE - Range',
'thold': 'Threshold (THOLD)',
'text': 'Text (TEXT)',
'vset': 'Value Set (VSET)'
};
// Filter options based on TestType
let refTypeOptions = $derived(() => {
const baseOptions = [
{ value: 'none', label: 'None - No reference range' }
];
if (testType === 'GROUP' || testType === 'TITLE') {
return baseOptions;
}
// TEST and PARAM can have all types
if (testType === 'TEST' || testType === 'PARAM') {
return [
...baseOptions,
{ value: 'num', label: 'RANGE - Range' },
{ value: 'thold', label: 'Threshold (THOLD) - Limit values' },
{ value: 'text', label: 'Text (TEXT) - Descriptive' },
{ value: 'vset', label: 'Value Set (VSET) - Predefined values' }
];
}
// CALC only allows RANGE (num)
if (testType === 'CALC') {
return [
...baseOptions,
{ value: 'num', label: 'RANGE - Range' }
];
}
return baseOptions;
});
// Ensure all reference range items have defined values, never undefined
function normalizeRefNum(ref) {
return {
RefType: ref.RefType ?? 'REF',
Sex: ref.Sex ?? '0',
LowSign: ref.LowSign ?? 'GE',
HighSign: ref.HighSign ?? 'LE',
Low: ref.Low ?? null,
High: ref.High ?? null,
AgeStart: ref.AgeStart ?? 0,
AgeEnd: ref.AgeEnd ?? 120,
Flag: ref.Flag ?? 'N',
Interpretation: ref.Interpretation ?? '',
SpcType: ref.SpcType ?? '',
Criteria: ref.Criteria ?? ''
};
}
function normalizeRefTxt(ref) {
return {
RefType: ref.RefType ?? 'TEXT',
Sex: ref.Sex ?? '0',
AgeStart: ref.AgeStart ?? 0,
AgeEnd: ref.AgeEnd ?? 120,
RefTxt: ref.RefTxt ?? '',
Flag: ref.Flag ?? 'N',
SpcType: ref.SpcType ?? '',
Criteria: ref.Criteria ?? ''
};
}
// Reactive normalized data - filter by RefType
let allRefnum = $derived((formData.refnum || []).map(normalizeRefNum));
let allReftxt = $derived((formData.reftxt || []).map(normalizeRefTxt));
// Filtered arrays for display
let normalizedRefnum = $derived(allRefnum.filter(ref => ref.RefType !== 'THOLD'));
let normalizedRefthold = $derived(allRefnum.filter(ref => ref.RefType === 'THOLD'));
let normalizedReftxt = $derived(allReftxt.filter(ref => ref.RefType !== 'VSET'));
let normalizedRefvset = $derived(allReftxt.filter(ref => ref.RefType === 'VSET'));
// Sync refRangeType with ResultType
$effect(() => {
const resultType = formData.ResultType;
const currentRefRangeType = formData.refRangeType;
if (testType === 'GROUP' || testType === 'TITLE') {
// GROUP and TITLE should have no reference range
if (currentRefRangeType !== 'none') {
onupdateFormData({ ...formData, refRangeType: 'none', RefType: 'NOREF' });
}
return;
}
// Map ResultType to refRangeType
let expectedRefRangeType = 'none';
if (resultType === 'NMRIC' || resultType === 'RANGE') {
expectedRefRangeType = 'num';
} else if (resultType === 'VSET') {
expectedRefRangeType = 'vset';
} else if (resultType === 'TEXT') {
expectedRefRangeType = 'text';
}
// Auto-sync if they don't match and we're not in the middle of editing
if (expectedRefRangeType !== 'none' && currentRefRangeType !== expectedRefRangeType) {
// Initialize the appropriate reference array if empty
const currentRefnum = formData.refnum || [];
const currentReftxt = formData.reftxt || [];
if (expectedRefRangeType === 'num' && normalizedRefnum.length === 0) {
onupdateFormData({
...formData,
refRangeType: expectedRefRangeType,
RefType: 'RANGE',
refnum: [...currentRefnum, createNumRef()]
});
} else if (expectedRefRangeType === 'vset' && normalizedRefvset.length === 0) {
onupdateFormData({
...formData,
refRangeType: expectedRefRangeType,
RefType: 'VSET',
reftxt: [...currentReftxt, createVsetRef()]
});
} else if (expectedRefRangeType === 'text' && normalizedReftxt.length === 0) {
onupdateFormData({
...formData,
refRangeType: expectedRefRangeType,
RefType: 'TEXT',
reftxt: [...currentReftxt, createTextRef()]
});
} else {
onupdateFormData({ ...formData, refRangeType: expectedRefRangeType });
}
}
});
function updateRefRangeType(type) {
// Initialize arrays if they don't exist
const currentRefnum = formData.refnum || [];
const currentReftxt = formData.reftxt || [];
if (type === 'num') {
// Add numeric range to refnum
onupdateFormData({
...formData,
refRangeType: type,
RefType: 'NMRC',
refnum: [...currentRefnum, createNumRef()]
});
} else if (type === 'thold') {
// Add threshold range to refnum
onupdateFormData({
...formData,
refRangeType: type,
RefType: 'THOLD',
refnum: [...currentRefnum, createTholdRef()]
});
} else if (type === 'text') {
// Add text range to reftxt
onupdateFormData({
...formData,
refRangeType: type,
RefType: 'TEXT',
reftxt: [...currentReftxt, createTextRef()]
});
} else if (type === 'vset') {
// Add value set range to reftxt
onupdateFormData({
...formData,
refRangeType: type,
RefType: 'VSET',
reftxt: [...currentReftxt, createVsetRef()]
});
} else {
// None selected
onupdateFormData({
...formData,
refRangeType: type,
RefType: ''
});
}
}
function updateRefnum(refnum) {
onupdateFormData({ ...formData, refnum });
}
function updateRefthold(refthold) {
// Merge thold items back into refnum
const nonThold = (formData.refnum || []).filter(ref => ref.RefType !== 'THOLD');
onupdateFormData({ ...formData, refnum: [...nonThold, ...refthold] });
}
function updateReftxt(reftxt) {
onupdateFormData({ ...formData, reftxt });
}
function updateRefvset(refvset) {
// Merge vset items back into reftxt
const nonVset = (formData.reftxt || []).filter(ref => ref.RefType !== 'VSET');
onupdateFormData({ ...formData, reftxt: [...nonVset, ...refvset] });
}
</script>
<div class="space-y-3">
<!-- Reference Range Type Selection -->
<div class="bg-base-100 rounded-lg border border-base-200 p-4">
<div class="flex items-center gap-2 mb-3">
<Ruler class="w-5 h-5 text-primary" />
<h3 class="font-semibold">Reference Range Type</h3>
<HelpTooltip
text="Choose how to define normal/abnormal ranges for this test."
title="Reference Range Help"
/>
</div>
<!-- Dropdown Select -->
<div class="form-control">
<select
class="select select-bordered w-full"
value={formData.refRangeType || 'none'}
onchange={(e) => updateRefRangeType(e.target.value)}
disabled={testType === 'GROUP' || testType === 'TITLE'}
>
{#each refTypeOptions() as option (option.value)}
<option value={option.value}>{option.label}</option>
{/each}
</select>
</div>
<!-- Show selected RefType info -->
{#if formData.refRangeType && formData.refRangeType !== 'none'}
<div class="mt-3 flex items-center gap-2 p-2 bg-info/10 rounded-lg border border-info/20">
<Info class="w-4 h-4 text-info" />
<span class="text-sm">
<span class="font-medium">Selected:</span>
{refTypeLabels[formData.refRangeType]}
</span>
</div>
{/if}
</div>
<!-- Numeric Reference Ranges -->
{#if formData.refRangeType === 'num'}
<NumericRefRange refnum={normalizedRefnum} onupdateRefnum={updateRefnum} />
{/if}
<!-- Threshold Reference Ranges (uses same component as numeric) -->
{#if formData.refRangeType === 'thold'}
<NumericRefRange refnum={normalizedRefthold} onupdateRefnum={updateRefthold} />
{/if}
<!-- Text Reference Ranges -->
{#if formData.refRangeType === 'text'}
<TextRefRange reftxt={normalizedReftxt} onupdateReftxt={updateReftxt} />
{/if}
<!-- Value Set Reference Ranges (uses same component as text) -->
{#if formData.refRangeType === 'vset'}
<TextRefRange reftxt={normalizedRefvset} onupdateReftxt={updateRefvset} />
{/if}
</div>