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

278 lines
9.3 KiB
Svelte
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<script>
import { onMount } from 'svelte';
import { FlaskConical, Ruler, Clock, Beaker } from 'lucide-svelte';
import { fetchValueSetByKey } from '$lib/api/valuesets.js';
/**
* @typedef {Object} Props
* @property {Object} formData - Form data object
* @property {string} testType - Test type (TEST, PARAM, CALC, GROUP, TITLE)
* @property {(formData: Object) => void} onupdateFormData - Update handler
*/
/** @type {Props} */
let {
formData = $bindable({}),
testType = '',
onupdateFormData = () => {}
} = $props();
// Value set options
let resultTypeOptions = $state([]);
let loading = $state(true);
onMount(async () => {
try {
loading = true;
const resultTypeRes = await fetchValueSetByKey('result_type');
console.log('result_type response:', resultTypeRes);
// Handle different response structures
const resultItems = resultTypeRes.data?.items || resultTypeRes.data?.ValueSetItems || (Array.isArray(resultTypeRes.data) ? resultTypeRes.data : []) || [];
resultTypeOptions = resultItems
.map(item => ({
value: item.value || item.itemCode || item.ItemCode || item.code || item.Code,
label: item.label || item.itemValue || item.ItemValue || item.value || item.Value || item.description || item.Description
}))
.filter(opt => opt.value)
.filter(opt => {
// Filter ResultType based on TestType
if (testType === 'CALC') {
// CALC only allows NMRIC
return opt.value === 'NMRIC';
} else if (testType === 'GROUP' || testType === 'TITLE') {
// GROUP and TITLE only allow NORES
return opt.value === 'NORES';
} else if (testType === 'TEST' || testType === 'PARAM') {
// TEST and PARAM allow NMRIC, RANGE, TEXT, VSET
return ['NMRIC', 'RANGE', 'TEXT', 'VSET'].includes(opt.value);
}
return true;
});
console.log('resultTypeOptions:', resultTypeOptions);
} catch (err) {
console.error('Failed to load value sets:', err);
} finally {
loading = false;
}
});
function updateField(field, value) {
onupdateFormData({ ...formData, [field]: value });
}
function handleResultTypeChange(value) {
// Update ResultType and set appropriate RefType
let newRefType = formData.RefType;
if (value === 'NMRIC' || value === 'RANGE') {
newRefType = 'RANGE';
} else if (value === 'VSET') {
newRefType = 'VSET';
} else if (value === 'TEXT') {
newRefType = 'TEXT';
} else if (value === 'NORES') {
newRefType = 'NOREF';
}
onupdateFormData({ ...formData, ResultType: value, RefType: newRefType });
}
// Check if test is calculated type (doesn't have specimen requirements)
const isCalculated = $derived(formData.TestType === 'CALC');
</script>
{#if loading}
<div class="flex justify-center items-center py-12">
<span class="loading loading-spinner loading-lg"></span>
</div>
{:else}
<div class="space-y-3">
<!-- Result Configuration -->
<div class="bg-base-100 rounded-lg border border-base-200 p-4">
<div class="flex items-center gap-2 mb-4">
<FlaskConical class="w-5 h-5 text-primary" />
<h3 class="font-semibold">Result Configuration</h3>
</div>
<div class="form-control max-w-md">
<label class="label" for="resultType">
<span class="label-text text-sm font-medium">Result Type</span>
</label>
<select
id="resultType"
class="select select-sm select-bordered w-full"
bind:value={formData.ResultType}
onchange={(e) => handleResultTypeChange(e.target.value)}
disabled={testType === 'GROUP' || testType === 'TITLE'}
>
{#if testType !== 'GROUP' && testType !== 'TITLE'}
<option value="">Select result type...</option>
{/if}
{#each resultTypeOptions as option}
<option value={option.value}>{option.label}</option>
{/each}
</select>
</div>
</div>
<!-- Units and Precision -->
<div class="bg-base-100 rounded-lg border border-base-200 p-4">
<div class="flex items-center gap-2 mb-4">
<Ruler class="w-5 h-5 text-primary" />
<h3 class="font-semibold">Units and Precision</h3>
</div>
<div class="grid grid-cols-1 md:grid-cols-4 gap-4">
<div class="form-control">
<label class="label" for="unit1">
<span class="label-text text-sm font-medium">Unit 1</span>
</label>
<input
id="unit1"
type="text"
class="input input-sm input-bordered w-full"
value={formData.Unit1}
oninput={(e) => updateField('Unit1', e.target.value)}
placeholder="e.g., mg/dL"
/>
</div>
<div class="form-control">
<label class="label" for="factor">
<span class="label-text text-sm font-medium">Factor</span>
</label>
<input
id="factor"
type="number"
step="0.000001"
class="input input-sm input-bordered w-full"
value={formData.Factor}
oninput={(e) => updateField('Factor', e.target.value ? parseFloat(e.target.value) : null)}
placeholder="Conversion factor"
/>
</div>
<div class="form-control">
<label class="label" for="unit2">
<span class="label-text text-sm font-medium">Unit 2</span>
</label>
<input
id="unit2"
type="text"
class="input input-sm input-bordered w-full"
value={formData.Unit2}
oninput={(e) => updateField('Unit2', e.target.value)}
placeholder="e.g., mmol/L"
/>
</div>
<div class="form-control">
<label class="label" for="decimal">
<span class="label-text text-sm font-medium">Decimal Places</span>
</label>
<input
id="decimal"
type="number"
min="0"
max="6"
class="input input-sm input-bordered w-full"
value={formData.Decimal}
oninput={(e) => updateField('Decimal', parseInt(e.target.value) || 0)}
placeholder="0-6"
/>
</div>
</div>
{#if formData.Factor}
<div class="mt-3 text-sm text-gray-600 bg-base-200 p-2 rounded">
Formula: {formData.Unit1 || 'Unit1'} × {formData.Factor} = {formData.Unit2 || 'Unit2'}
</div>
{/if}
</div>
<!-- Specimen Requirements -->
{#if !isCalculated}
<div class="bg-base-100 rounded-lg border border-base-200 p-4">
<div class="flex items-center gap-2 mb-4">
<Beaker class="w-5 h-5 text-primary" />
<h3 class="font-semibold">Specimen Requirements</h3>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div class="form-control">
<label class="label" for="reqQty">
<span class="label-text text-sm font-medium">Required Quantity</span>
</label>
<input
id="reqQty"
type="number"
step="0.01"
class="input input-sm input-bordered w-full"
value={formData.ReqQty}
oninput={(e) => updateField('ReqQty', e.target.value ? parseFloat(e.target.value) : null)}
placeholder="Amount"
/>
</div>
<div class="form-control">
<label class="label" for="reqQtyUnit">
<span class="label-text text-sm font-medium">Quantity Unit</span>
</label>
<input
id="reqQtyUnit"
type="text"
class="input input-sm input-bordered w-full"
value={formData.ReqQtyUnit}
oninput={(e) => updateField('ReqQtyUnit', e.target.value)}
placeholder="e.g., mL"
/>
</div>
</div>
</div>
{/if}
<!-- Method and TAT -->
<div class="bg-base-100 rounded-lg border border-base-200 p-4">
<div class="flex items-center gap-2 mb-4">
<Clock class="w-5 h-5 text-primary" />
<h3 class="font-semibold">Method and Turnaround</h3>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div class="form-control">
<label class="label" for="method">
<span class="label-text text-sm font-medium">Method</span>
</label>
<input
id="method"
type="text"
class="input input-sm input-bordered w-full"
value={formData.Method}
oninput={(e) => updateField('Method', e.target.value)}
placeholder="e.g., Enzymatic"
/>
</div>
<div class="form-control">
<label class="label" for="expectedTAT">
<span class="label-text text-sm font-medium">Expected TAT (minutes)</span>
</label>
<input
id="expectedTAT"
type="number"
min="0"
class="input input-sm input-bordered w-full"
value={formData.ExpectedTAT}
oninput={(e) => updateField('ExpectedTAT', e.target.value ? parseInt(e.target.value) : null)}
placeholder="e.g., 60"
/>
</div>
</div>
</div>
</div>
{/if}