clqms-fe1/backup/tests_backup/test-modal/NumericRefRange.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

246 lines
8.7 KiB
Svelte

<script>
import { PlusCircle, Calculator, X, ChevronDown, ChevronUp, Beaker } from 'lucide-svelte';
import { refTypeOptions, createNumRef } from '../referenceRange.js';
import { fetchValueSetByKey } from '$lib/api/valuesets.js';
import { onMount } from 'svelte';
/**
* @typedef {Object} Props
* @property {Array} refnum - Numeric reference ranges array
* @property {(refnum: Array) => void} onupdateRefnum - Update handler
*/
/** @type {Props} */
let {
refnum = [],
onupdateRefnum = () => {}
} = $props();
let showAdvanced = $state({});
let specimenTypeOptions = $state([]);
onMount(async () => {
try {
const response = await fetchValueSetByKey('specimen_type');
specimenTypeOptions = response.data?.items?.map(item => ({
value: item.itemCode,
label: item.itemValue
})) || [];
} catch (err) {
console.error('Failed to load specimen types:', err);
}
});
function addRefRange() {
const newRef = createNumRef();
newRef.Sex = '0';
newRef.Flag = 'N';
onupdateRefnum([...refnum, newRef]);
}
function removeRefRange(index) {
onupdateRefnum(refnum.filter((_, i) => i !== index));
delete showAdvanced[index];
}
function toggleAdvanced(index) {
showAdvanced[index] = !showAdvanced[index];
}
function hasAdvancedData(ref) {
return ref.Interpretation || ref.SpcType || ref.Criteria ||
ref.Sex !== '0' || ref.Flag !== 'N' ||
(ref.AgeStart !== 0 || ref.AgeEnd !== 120);
}
</script>
<div class="space-y-2">
<!-- Header -->
<div class="flex justify-between items-center">
<div class="flex items-center gap-2">
<Calculator class="w-5 h-5 text-primary" />
<h3 class="font-semibold">Numeric Reference Ranges</h3>
{#if refnum?.length > 0}
<span class="badge badge-sm badge-ghost">{refnum.length}</span>
{/if}
</div>
<button type="button" class="btn btn-sm btn-primary" onclick={addRefRange}>
<PlusCircle class="w-4 h-4 mr-1" />
Add Range
</button>
</div>
<!-- Empty State -->
{#if refnum?.length === 0}
<div class="text-center py-8 bg-base-200 rounded-lg border-2 border-dashed border-base-300">
<Calculator class="w-12 h-12 mx-auto text-gray-400 mb-2" />
<p class="text-gray-500">No numeric ranges defined</p>
<button type="button" class="btn btn-sm btn-outline mt-2" onclick={addRefRange}>
<PlusCircle class="w-4 h-4 mr-1" />
Add First Range
</button>
</div>
{/if}
<!-- Range List - Table-like rows -->
{#if refnum?.length > 0}
<div class="border border-base-300 rounded-lg overflow-hidden">
<!-- Table Header -->
<div class="bg-base-200 px-4 py-3 grid grid-cols-12 gap-3 text-xs font-medium text-gray-600 border-b border-base-300">
<div class="col-span-1">#</div>
<div class="col-span-3">Type</div>
<div class="col-span-3">Low</div>
<div class="col-span-3">High</div>
<div class="col-span-2"></div>
</div>
<!-- Table Rows -->
{#each refnum || [] as ref, index (index)}
<div class="border-b border-base-200 last:border-b-0">
<!-- Main Row -->
<div class="px-4 py-3 grid grid-cols-12 gap-3 items-center hover:bg-base-100">
<!-- Row Number -->
<div class="col-span-1 flex items-center gap-1">
<span class="text-xs text-gray-500">{index + 1}</span>
</div>
<!-- Type Dropdown -->
<div class="col-span-3">
<select class="select select-sm select-bordered w-full" bind:value={ref.RefType}>
{#each refTypeOptions as option (option.value)}
<option value={option.value}>{option.label}</option>
{/each}
</select>
</div>
<!-- Low Input -->
<div class="col-span-3">
<input
type="number"
step="0.01"
class="input input-sm input-bordered w-full text-right"
bind:value={ref.Low}
placeholder="-"
/>
</div>
<!-- High Input -->
<div class="col-span-3">
<input
type="number"
step="0.01"
class="input input-sm input-bordered w-full text-right"
bind:value={ref.High}
placeholder="-"
/>
</div>
<!-- More & Delete Buttons -->
<div class="col-span-2 flex items-center justify-end gap-1">
<button
type="button"
class="btn btn-xs btn-ghost btn-square relative"
onclick={() => toggleAdvanced(index)}
title={showAdvanced[index] ? 'Hide details' : 'Show details'}
>
{#if hasAdvancedData(ref)}
<span class="badge badge-xs badge-primary absolute -top-1 -right-1 w-2 h-2 p-0 min-w-0"></span>
{/if}
{#if showAdvanced[index]}
<ChevronUp class="w-4 h-4" />
{:else}
<ChevronDown class="w-4 h-4" />
{/if}
</button>
<button
type="button"
class="btn btn-xs btn-ghost text-error btn-square"
onclick={() => removeRefRange(index)}
title="Remove"
>
<X class="w-4 h-4" />
</button>
</div>
</div>
<!-- Advanced Section (Expanded) -->
{#if showAdvanced[index]}
<div class="px-4 pb-4 pt-2 bg-base-100/50 border-t border-base-200">
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<!-- Sex -->
<div class="form-control">
<span class="label-text text-xs text-gray-500">Sex</span>
<select class="select select-sm select-bordered w-full" bind:value={ref.Sex}>
<option value="0">Any</option>
<option value="1">Female</option>
<option value="2">Male</option>
</select>
</div>
<!-- Age Range -->
<div class="form-control">
<span class="label-text text-xs text-gray-500">Age Range</span>
<div class="flex items-center gap-2">
<input
type="number"
min="0"
max="120"
class="input input-sm input-bordered w-20 text-right"
bind:value={ref.AgeStart}
/>
<span class="text-gray-400">to</span>
<input
type="number"
min="0"
max="120"
class="input input-sm input-bordered w-20 text-right"
bind:value={ref.AgeEnd}
/>
<span class="text-xs text-gray-400">years</span>
</div>
</div>
<!-- Specimen -->
<div class="form-control md:col-span-2">
<span class="label-text text-xs text-gray-500 flex items-center gap-1">
<Beaker class="w-3 h-3" />
Specimen Type
</span>
<select class="select select-sm select-bordered w-full" bind:value={ref.SpcType}>
<option value="">Any specimen</option>
{#each specimenTypeOptions as option (option.value)}
<option value={option.value}>{option.label}</option>
{/each}
</select>
</div>
<!-- Interpretation -->
<div class="form-control md:col-span-2">
<span class="label-text text-xs text-gray-500">Interpretation</span>
<input
type="text"
class="input input-sm input-bordered w-full"
bind:value={ref.Interpretation}
placeholder="e.g., Normal fasting glucose range"
/>
</div>
<!-- Criteria -->
<div class="form-control md:col-span-2">
<span class="label-text text-xs text-gray-500">Criteria</span>
<input
type="text"
class="input input-sm input-bordered w-full"
bind:value={ref.Criteria}
placeholder="e.g., Fasting, Morning sample"
/>
</div>
</div>
</div>
{/if}
</div>
{/each}
</div>
{/if}
</div>