242 lines
7.7 KiB
Svelte
Raw Normal View History

<script>
import { Plus, Trash2, Type } from 'lucide-svelte';
import Modal from '$lib/components/Modal.svelte';
let { formData = $bindable(), isDirty = $bindable(false) } = $props();
let modalOpen = $state(false);
let modalMode = $state('create');
let selectedRangeIndex = $state(null);
let editingRange = $state({
TxtRefType: 'Normal',
Sex: '0',
AgeStart: 0,
AgeEnd: 150,
RefTxt: '',
Flag: 'N'
});
const refTypes = [
{ value: 'Normal', label: 'Normal' },
{ value: 'Abnormal', label: 'Abnormal' },
{ value: 'Critical', label: 'Critical' }
];
const sexOptions = [
{ value: '0', label: 'All' },
{ value: '1', label: 'Female' },
{ value: '2', label: 'Male' }
];
const flagOptions = $derived.by(() => {
const type = editingRange.TxtRefType;
if (type === 'Normal') return [{ value: 'N', label: 'N - Normal' }];
if (type === 'Abnormal') return [{ value: 'A', label: 'A - Abnormal' }];
if (type === 'Critical') return [{ value: 'C', label: 'C - Critical' }];
return [];
});
const autoFlag = $derived.by(() => {
const type = editingRange.TxtRefType;
if (type === 'Normal') return 'N';
if (type === 'Abnormal') return 'A';
if (type === 'Critical') return 'C';
return editingRange.Flag;
});
function handleFieldChange() {
isDirty = true;
}
function openAddRange() {
modalMode = 'create';
selectedRangeIndex = null;
editingRange = {
TxtRefType: 'Normal',
Sex: '0',
AgeStart: 0,
AgeEnd: 150,
RefTxt: '',
Flag: 'N'
};
modalOpen = true;
}
function openEditRange(index) {
modalMode = 'edit';
selectedRangeIndex = index;
editingRange = { ...formData.reftxt[index] };
modalOpen = true;
}
function removeRange(index) {
const newRanges = formData.reftxt?.filter((_, i) => i !== index) || [];
formData.reftxt = newRanges;
handleFieldChange();
}
function saveRange() {
if (modalMode === 'create') {
formData.reftxt = [...(formData.reftxt || []), { ...editingRange, Flag: autoFlag }];
} else {
const newRanges = formData.reftxt?.map((r, i) =>
i === selectedRangeIndex ? { ...editingRange, Flag: autoFlag } : r
) || [];
formData.reftxt = newRanges;
}
modalOpen = false;
handleFieldChange();
}
function getRefTypeLabel(type) {
return refTypes.find(t => t.value === type)?.label || type;
}
function getSexLabel(sex) {
return sexOptions.find(s => s.value === sex)?.label || sex;
}
</script>
<div class="space-y-6">
<h2 class="text-lg font-semibold text-gray-800">Text Reference Ranges</h2>
<div class="alert alert-info text-sm">
<Type class="w-4 h-4" />
<div>
<strong>Text Ranges:</strong> Define expected text values for tests with text-based results.
</div>
</div>
<div class="space-y-4">
<h3 class="text-sm font-medium text-gray-600 uppercase tracking-wide">Current Ranges ({formData.reftxt?.length || 0})</h3>
{#if !formData.reftxt || formData.reftxt.length === 0}
<div class="text-center py-8 bg-base-200 rounded-lg">
<Type class="w-12 h-12 mx-auto text-gray-400 mb-2" />
<p class="text-sm text-gray-500">No text ranges defined</p>
<p class="text-xs text-gray-400">Add reference ranges for this test</p>
</div>
{:else}
<div class="overflow-x-auto border border-base-200 rounded-lg">
<table class="table table-sm table-compact">
<thead>
<tr class="bg-base-200">
<th class="w-20">Type</th>
<th class="w-16">Sex</th>
<th class="w-24">Age</th>
<th>Reference Text</th>
<th class="w-16">Flag</th>
<th class="w-24 text-center">Actions</th>
</tr>
</thead>
<tbody>
{#each formData.reftxt as range, idx (idx)}
<tr class="hover:bg-base-100">
<td>
<span class="badge badge-xs
{range.TxtRefType === 'Normal' ? 'badge-success' :
range.TxtRefType === 'Abnormal' ? 'badge-warning' :
'badge-error'}">
{getRefTypeLabel(range.TxtRefType)}
</span>
</td>
<td>{getSexLabel(range.Sex)}</td>
<td class="text-sm">{range.AgeStart}-{range.AgeEnd}</td>
<td class="font-mono text-sm">{range.RefTxt || '-'}</td>
<td>
<span class="badge badge-xs badge-secondary">{range.Flag}</span>
</td>
<td>
<div class="flex justify-center gap-1">
<button
class="btn btn-ghost btn-xs"
onclick={() => openEditRange(idx)}
title="Edit Range"
>
<Plus class="w-3 h-3" />
</button>
<button
class="btn btn-ghost btn-xs text-error"
onclick={() => removeRange(idx)}
title="Remove Range"
>
<Trash2 class="w-3 h-3" />
</button>
</div>
</td>
</tr>
{/each}
</tbody>
</table>
</div>
{/if}
</div>
<div class="flex justify-end">
<button class="btn btn-sm btn-primary" onclick={openAddRange}>
<Plus class="w-4 h-4 mr-2" />
Add Range
</button>
</div>
</div>
<Modal bind:open={modalOpen} title={modalMode === 'create' ? 'Add Text Range' : 'Edit Text Range'} size="md">
<div class="space-y-4">
<div class="form-control">
<label class="label text-sm">Reference Type</label>
<select class="select select-sm select-bordered" bind:value={editingRange.TxtRefType}>
{#each refTypes as rt (rt.value)}
<option value={rt.value}>{rt.label}</option>
{/each}
</select>
</div>
<div class="form-control">
<label class="label text-sm">Sex</label>
<select class="select select-sm select-bordered" bind:value={editingRange.Sex}>
{#each sexOptions as opt (opt.value)}
<option value={opt.value}>{opt.label}</option>
{/each}
</select>
</div>
<div class="grid grid-cols-2 gap-4">
<div class="form-control">
<label class="label text-sm">Age Start</label>
<input type="number" class="input input-sm input-bordered" bind:value={editingRange.AgeStart} min="0" max="150" />
</div>
<div class="form-control">
<label class="label text-sm">Age End</label>
<input type="number" class="input input-sm input-bordered" bind:value={editingRange.AgeEnd} min="0" max="150" />
</div>
</div>
<div class="form-control">
<label class="label text-sm">
Reference Text
<span class="label-text-alt text-xs text-error">*</span>
</label>
<input
type="text"
class="input input-sm input-bordered"
bind:value={editingRange.RefTxt}
placeholder="e.g., Clear, Cloudy, Bloody"
maxlength="255"
/>
</div>
<div class="form-control">
<label class="label text-sm">Flag</label>
<input type="text" class="input input-sm input-bordered" value={autoFlag} readonly />
<label class="label">
<span class="label-text-alt text-xs text-gray-500">Flag is automatically set based on reference type</span>
</label>
</div>
</div>
{#snippet footer()}
<button class="btn btn-ghost" onclick={() => modalOpen = false}>Cancel</button>
<button class="btn btn-primary" onclick={saveRange}>Save</button>
{/snippet}
</Modal>