240 lines
8.0 KiB
Svelte
240 lines
8.0 KiB
Svelte
<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 {(formData: Object) => void} onupdateFormData - Update handler
|
||
*/
|
||
|
||
/** @type {Props} */
|
||
let {
|
||
formData = $bindable({}),
|
||
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);
|
||
|
||
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 });
|
||
}
|
||
|
||
// 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) => updateField('ResultType', e.target.value)}
|
||
>
|
||
<option value="">Select result type...</option>
|
||
{#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}
|