feat: Update test modal UX and improve DataTable/Modal components

This commit is contained in:
mahdahar 2026-02-19 16:30:41 +07:00
parent 995cdd3fec
commit 1af4adddf7
21 changed files with 5454 additions and 703 deletions

4866
docs/api-docs.bundled.yaml Normal file

File diff suppressed because it is too large Load Diff

View File

@ -68,7 +68,7 @@
<thead> <thead>
<tr class="bg-base-200"> <tr class="bg-base-200">
{#each columns as column} {#each columns as column}
<th class="font-semibold {column.class || ''}"> <th class="text-sm font-semibold {column.class || ''}">
{column.label} {column.label}
</th> </th>
{/each} {/each}
@ -102,7 +102,7 @@
role={onRowClick ? 'button' : undefined} role={onRowClick ? 'button' : undefined}
> >
{#each columns as column} {#each columns as column}
<td class="{column.class || ''}"> <td class="text-sm {column.class || ''}">
{#if cell} {#if cell}
{@render cell({ column, row, value: getValue(row, column.key), index })} {@render cell({ column, row, value: getValue(row, column.key), index })}
{:else if column.render} {:else if column.render}

View File

@ -83,7 +83,7 @@
<!-- Header --> <!-- Header -->
<div class="flex items-center justify-between mb-4"> <div class="flex items-center justify-between mb-4">
{#if title} {#if title}
<h3 id="modal-title" class="font-bold text-lg">{title}</h3> <h3 id="modal-title" class="font-bold text-base">{title}</h3>
{:else} {:else}
<div></div> <div></div>
{/if} {/if}

View File

@ -80,8 +80,8 @@
</script> </script>
<div class="p-4"> <div class="p-4">
<h1 class="text-3xl font-bold text-gray-800 mb-2">Master Data</h1> <h1 class="text-xl font-bold text-gray-800 mb-2">Master Data</h1>
<p class="text-gray-600 mb-8">Manage reference data and lookup values used throughout the system</p> <p class="text-sm text-gray-600 mb-8">Manage reference data and lookup values used throughout the system</p>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6"> <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{#each modules as module} {#each modules as module}

View File

@ -172,8 +172,8 @@
<ArrowLeft class="w-5 h-5" /> <ArrowLeft class="w-5 h-5" />
</a> </a>
<div class="flex-1"> <div class="flex-1">
<h1 class="text-3xl font-bold text-gray-800">Contacts</h1> <h1 class="text-xl font-bold text-gray-800">Contacts</h1>
<p class="text-gray-600">Manage physicians and contacts</p> <p class="text-sm text-gray-600">Manage physicians and contacts</p>
</div> </div>
<button class="btn btn-primary" onclick={openCreateModal}> <button class="btn btn-primary" onclick={openCreateModal}>
<Plus class="w-4 h-4 mr-2" /> <Plus class="w-4 h-4 mr-2" />
@ -187,7 +187,7 @@
<Search class="absolute left-3 top-1/2 -translate-y-1/2 w-5 h-5 text-gray-400" /> <Search class="absolute left-3 top-1/2 -translate-y-1/2 w-5 h-5 text-gray-400" />
<input <input
type="text" type="text"
class="input input-bordered w-full pl-10" class="input input-sm input-bordered w-full pl-10"
placeholder="Search by name or initial..." placeholder="Search by name or initial..."
bind:value={searchQuery} bind:value={searchQuery}
/> />
@ -214,10 +214,10 @@
<div class="bg-base-200 rounded-full p-6 mb-4"> <div class="bg-base-200 rounded-full p-6 mb-4">
<Users class="w-12 h-12 text-gray-400" /> <Users class="w-12 h-12 text-gray-400" />
</div> </div>
<h3 class="text-lg font-semibold text-gray-700 mb-1"> <h3 class="text-base font-semibold text-gray-700 mb-1">
{searchQuery ? 'No contacts found' : 'No contacts yet'} {searchQuery ? 'No contacts found' : 'No contacts yet'}
</h3> </h3>
<p class="text-gray-500 text-center max-w-sm mb-4"> <p class="text-xs text-gray-500 text-center max-w-sm mb-4">
{searchQuery {searchQuery
? `No contacts matching "${searchQuery}". Try a different search term.` ? `No contacts matching "${searchQuery}". Try a different search term.`
: 'Get started by adding your first physician or contact.'} : 'Get started by adding your first physician or contact.'}
@ -266,7 +266,7 @@
<div class="grid grid-cols-1 md:grid-cols-2 gap-4"> <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div class="form-control"> <div class="form-control">
<label class="label" for="initial"> <label class="label" for="initial">
<span class="label-text font-medium flex items-center gap-2"> <span class="label-text text-sm font-medium flex items-center gap-2">
Initial Initial
<HelpTooltip <HelpTooltip
text="A unique short code used to identify this doctor (e.g., 'JS' for John Smith). This will be used in reports and quick reference." text="A unique short code used to identify this doctor (e.g., 'JS' for John Smith). This will be used in reports and quick reference."
@ -274,7 +274,7 @@
position="right" position="right"
/> />
</span> </span>
<span class="label-text-alt text-error">*</span> <span class="label-text-alt text-xs text-error">*</span>
</label> </label>
<input <input
id="initial" id="initial"
@ -286,12 +286,12 @@
required required
/> />
<label class="label" for="initial"> <label class="label" for="initial">
<span class="label-text-alt text-gray-500">Unique identifier for this doctor</span> <span class="label-text-alt text-xs text-gray-500">Unique identifier for this doctor</span>
</label> </label>
</div> </div>
<div class="form-control"> <div class="form-control">
<label class="label" for="title"> <label class="label" for="title">
<span class="label-text font-medium">Title</span> <span class="label-text text-sm font-medium">Title</span>
</label> </label>
<input <input
id="title" id="title"
@ -305,7 +305,7 @@
<div class="grid grid-cols-1 md:grid-cols-2 gap-4"> <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div class="form-control"> <div class="form-control">
<label class="label" for="nameFirst"> <label class="label" for="nameFirst">
<span class="label-text font-medium">First Name</span> <span class="label-text text-sm font-medium">First Name</span>
</label> </label>
<input <input
id="nameFirst" id="nameFirst"
@ -317,7 +317,7 @@
</div> </div>
<div class="form-control"> <div class="form-control">
<label class="label" for="nameLast"> <label class="label" for="nameLast">
<span class="label-text font-medium">Last Name</span> <span class="label-text text-sm font-medium">Last Name</span>
</label> </label>
<input <input
id="nameLast" id="nameLast"

View File

@ -152,8 +152,8 @@
<ArrowLeft class="w-5 h-5" /> <ArrowLeft class="w-5 h-5" />
</a> </a>
<div class="flex-1"> <div class="flex-1">
<h1 class="text-3xl font-bold text-gray-800">Containers</h1> <h1 class="text-xl font-bold text-gray-800">Containers</h1>
<p class="text-gray-600">Manage specimen containers and tubes</p> <p class="text-sm text-gray-600">Manage specimen containers and tubes</p>
</div> </div>
<button class="btn btn-primary" onclick={openCreateModal}> <button class="btn btn-primary" onclick={openCreateModal}>
<Plus class="w-4 h-4 mr-2" /> <Plus class="w-4 h-4 mr-2" />
@ -188,10 +188,10 @@
<div class="bg-base-200 rounded-full p-6 mb-4"> <div class="bg-base-200 rounded-full p-6 mb-4">
<FlaskConical class="w-12 h-12 text-gray-400" /> <FlaskConical class="w-12 h-12 text-gray-400" />
</div> </div>
<h3 class="text-lg font-semibold text-gray-700 mb-2"> <h3 class="text-base font-semibold text-gray-700 mb-2">
{searchQuery.trim() ? 'No containers match your search' : 'No containers found'} {searchQuery.trim() ? 'No containers match your search' : 'No containers found'}
</h3> </h3>
<p class="text-gray-500 text-center max-w-md mb-6"> <p class="text-xs text-gray-500 text-center max-w-md mb-6">
{searchQuery.trim() {searchQuery.trim()
? `No containers found matching "${searchQuery}". Try a different search term or clear the filter.` ? `No containers found matching "${searchQuery}". Try a different search term or clear the filter.`
: 'Get started by adding your first specimen container or tube to the system.'} : 'Get started by adding your first specimen container or tube to the system.'}
@ -253,8 +253,8 @@
<div class="grid grid-cols-1 md:grid-cols-2 gap-4"> <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div class="form-control"> <div class="form-control">
<label class="label" for="code"> <label class="label" for="code">
<span class="label-text font-medium">Container Code</span> <span class="label-text text-sm font-medium">Container Code</span>
<span class="label-text-alt text-error">*</span> <span class="label-text-alt text-xs text-error">*</span>
</label> </label>
<input <input
id="code" id="code"
@ -264,12 +264,12 @@
placeholder="e.g., SST, EDTA, HEP" placeholder="e.g., SST, EDTA, HEP"
required required
/> />
<span class="label-text-alt text-gray-500">Unique identifier for this container type</span> <span class="label-text-alt text-xs text-gray-500">Unique identifier for this container type</span>
</div> </div>
<div class="form-control"> <div class="form-control">
<label class="label" for="name"> <label class="label" for="name">
<span class="label-text font-medium">Container Name</span> <span class="label-text text-sm font-medium">Container Name</span>
<span class="label-text-alt text-error">*</span> <span class="label-text-alt text-xs text-error">*</span>
</label> </label>
<input <input
id="name" id="name"
@ -279,12 +279,12 @@
placeholder="e.g., Serum Separator Tube" placeholder="e.g., Serum Separator Tube"
required required
/> />
<span class="label-text-alt text-gray-500">Descriptive name displayed in the system</span> <span class="label-text-alt text-xs text-gray-500">Descriptive name displayed in the system</span>
</div> </div>
</div> </div>
<div class="form-control"> <div class="form-control">
<label class="label" for="desc"> <label class="label" for="desc">
<span class="label-text font-medium">Description</span> <span class="label-text text-sm font-medium">Description</span>
</label> </label>
<input <input
id="desc" id="desc"
@ -293,12 +293,12 @@
bind:value={formData.ConDesc} bind:value={formData.ConDesc}
placeholder="e.g., Evacuated blood collection tube with gel separator" placeholder="e.g., Evacuated blood collection tube with gel separator"
/> />
<span class="label-text-alt text-gray-500">Optional detailed description of the container</span> <span class="label-text-alt text-xs text-gray-500">Optional detailed description of the container</span>
</div> </div>
<div class="grid grid-cols-1 md:grid-cols-3 gap-4"> <div class="grid grid-cols-1 md:grid-cols-3 gap-4">
<div class="form-control"> <div class="form-control">
<label class="label" for="class"> <label class="label" for="class">
<span class="label-text font-medium flex items-center gap-2"> <span class="label-text text-sm font-medium flex items-center gap-2">
Container Class Container Class
<HelpTooltip <HelpTooltip
text="The general category of this container. Examples: Tube (for blood collection tubes), Cup (for urine or fluid cups), Swab (for specimen swabs)." text="The general category of this container. Examples: Tube (for blood collection tubes), Cup (for urine or fluid cups), Swab (for specimen swabs)."
@ -312,11 +312,11 @@
bind:value={formData.ConClass} bind:value={formData.ConClass}
placeholder="Select class..." placeholder="Select class..."
/> />
<span class="label-text-alt text-gray-500">Category of container</span> <span class="label-text-alt text-xs text-gray-500">Category of container</span>
</div> </div>
<div class="form-control"> <div class="form-control">
<label class="label" for="additive"> <label class="label" for="additive">
<span class="label-text font-medium flex items-center gap-2"> <span class="label-text text-sm font-medium flex items-center gap-2">
Additive Additive
<HelpTooltip <HelpTooltip
text="Any chemical additive present in the container. Examples: EDTA (anticoagulant for CBC), Heparin (anticoagulant for chemistry), SST (Serum Separator Tube with gel), None (plain tube)." text="Any chemical additive present in the container. Examples: EDTA (anticoagulant for CBC), Heparin (anticoagulant for chemistry), SST (Serum Separator Tube with gel), None (plain tube)."
@ -330,11 +330,11 @@
bind:value={formData.Additive} bind:value={formData.Additive}
placeholder="Select additive..." placeholder="Select additive..."
/> />
<span class="label-text-alt text-gray-500">Chemical additive inside</span> <span class="label-text-alt text-xs text-gray-500">Chemical additive inside</span>
</div> </div>
<div class="form-control"> <div class="form-control">
<label class="label" for="color"> <label class="label" for="color">
<span class="label-text font-medium flex items-center gap-2"> <span class="label-text text-sm font-medium flex items-center gap-2">
Cap Color Cap Color
<HelpTooltip <HelpTooltip
text="The color of the container cap or closure. This is an industry standard for identifying container types at a glance (e.g., Lavender = EDTA, Red = Plain serum, Green = Heparin)." text="The color of the container cap or closure. This is an industry standard for identifying container types at a glance (e.g., Lavender = EDTA, Red = Plain serum, Green = Heparin)."
@ -348,7 +348,7 @@
bind:value={formData.Color} bind:value={formData.Color}
placeholder="Select color..." placeholder="Select color..."
/> />
<span class="label-text-alt text-gray-500">Visual identification color</span> <span class="label-text-alt text-xs text-gray-500">Visual identification color</span>
</div> </div>
</div> </div>
</form> </form>

View File

@ -204,8 +204,8 @@
<ArrowLeft class="w-5 h-5" /> <ArrowLeft class="w-5 h-5" />
</a> </a>
<div class="flex-1"> <div class="flex-1">
<h1 class="text-3xl font-bold text-gray-800">Counters</h1> <h1 class="text-xl font-bold text-gray-800">Counters</h1>
<p class="text-gray-600">Manage ID generation counters</p> <p class="text-sm text-gray-600">Manage ID generation counters</p>
</div> </div>
<button class="btn btn-primary" onclick={openCreateModal}> <button class="btn btn-primary" onclick={openCreateModal}>
<Plus class="w-4 h-4 mr-2" /> <Plus class="w-4 h-4 mr-2" />
@ -232,7 +232,7 @@
<Search class="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-base-content/50" /> <Search class="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-base-content/50" />
<input <input
type="text" type="text"
class="input input-bordered w-full pl-10" class="input input-sm input-bordered w-full pl-10"
placeholder="Search by counter description..." placeholder="Search by counter description..."
bind:value={searchQuery} bind:value={searchQuery}
/> />
@ -262,7 +262,7 @@
<div class="bg-base-200 rounded-full p-4 mb-4"> <div class="bg-base-200 rounded-full p-4 mb-4">
<Hash class="w-8 h-8 text-base-content/40" /> <Hash class="w-8 h-8 text-base-content/40" />
</div> </div>
<h3 class="text-lg font-semibold text-base-content/70 mb-1"> <h3 class="text-base font-semibold text-base-content/70 mb-1">
{#if searchQuery} {#if searchQuery}
No counters found No counters found
{:else} {:else}
@ -331,8 +331,8 @@
<!-- Description --> <!-- Description -->
<div class="form-control"> <div class="form-control">
<label class="label" for="counterDesc"> <label class="label" for="counterDesc">
<span class="label-text font-medium">Counter Description</span> <span class="label-text text-sm font-medium">Counter Description</span>
<span class="label-text-alt text-error">*</span> <span class="label-text-alt text-xs text-error">*</span>
</label> </label>
<input <input
id="counterDesc" id="counterDesc"
@ -344,7 +344,7 @@
required required
/> />
<label class="label" for="counterDesc"> <label class="label" for="counterDesc">
<span class="label-text-alt text-base-content/60"> <span class="label-text-alt text-xs text-base-content/60">
A descriptive name for this counter A descriptive name for this counter
</span> </span>
</label> </label>
@ -355,8 +355,8 @@
<!-- Current Value --> <!-- Current Value -->
<div class="form-control"> <div class="form-control">
<label class="label" for="counterValue"> <label class="label" for="counterValue">
<span class="label-text font-medium">Current Value</span> <span class="label-text text-sm font-medium">Current Value</span>
<span class="label-text-alt text-error">*</span> <span class="label-text-alt text-xs text-error">*</span>
<HelpTooltip <HelpTooltip
text="The next number that will be assigned. This increments automatically each time an ID is generated." text="The next number that will be assigned. This increments automatically each time an ID is generated."
title="Current Value" title="Current Value"
@ -376,7 +376,7 @@
/> />
{#if formErrors.CounterValue} {#if formErrors.CounterValue}
<label class="label" for="counterValue"> <label class="label" for="counterValue">
<span class="label-text-alt text-error">{formErrors.CounterValue}</span> <span class="label-text-alt text-xs text-error">{formErrors.CounterValue}</span>
</label> </label>
{/if} {/if}
</div> </div>
@ -384,8 +384,8 @@
<!-- Start Value --> <!-- Start Value -->
<div class="form-control"> <div class="form-control">
<label class="label" for="counterStart"> <label class="label" for="counterStart">
<span class="label-text font-medium">Start Value</span> <span class="label-text text-sm font-medium">Start Value</span>
<span class="label-text-alt text-error">*</span> <span class="label-text-alt text-xs text-error">*</span>
<HelpTooltip <HelpTooltip
text="The minimum value this counter can have. When reset, the counter returns to this value." text="The minimum value this counter can have. When reset, the counter returns to this value."
title="Start Value" title="Start Value"
@ -405,7 +405,7 @@
/> />
{#if formErrors.CounterStart} {#if formErrors.CounterStart}
<label class="label" for="counterStart"> <label class="label" for="counterStart">
<span class="label-text-alt text-error">{formErrors.CounterStart}</span> <span class="label-text-alt text-xs text-error">{formErrors.CounterStart}</span>
</label> </label>
{/if} {/if}
</div> </div>
@ -413,7 +413,7 @@
<!-- End Value --> <!-- End Value -->
<div class="form-control"> <div class="form-control">
<label class="label" for="counterEnd"> <label class="label" for="counterEnd">
<span class="label-text font-medium">End Value</span> <span class="label-text text-sm font-medium">End Value</span>
<HelpTooltip <HelpTooltip
text="Optional maximum value. When reached, the counter will reset to the start value. Leave empty for no limit." text="Optional maximum value. When reached, the counter will reset to the start value. Leave empty for no limit."
title="End Value" title="End Value"
@ -432,7 +432,7 @@
/> />
{#if formErrors.CounterEnd} {#if formErrors.CounterEnd}
<label class="label" for="counterEnd"> <label class="label" for="counterEnd">
<span class="label-text-alt text-error">{formErrors.CounterEnd}</span> <span class="label-text-alt text-xs text-error">{formErrors.CounterEnd}</span>
</label> </label>
{/if} {/if}
</div> </div>
@ -441,7 +441,7 @@
<!-- Reset Pattern --> <!-- Reset Pattern -->
<div class="form-control"> <div class="form-control">
<label class="label" for="counterReset"> <label class="label" for="counterReset">
<span class="label-text font-medium">Reset Pattern</span> <span class="label-text text-sm font-medium">Reset Pattern</span>
<HelpTooltip <HelpTooltip
text="Determines when the counter automatically resets to the start value. Daily resets at midnight, monthly on the 1st, yearly on Jan 1st." text="Determines when the counter automatically resets to the start value. Daily resets at midnight, monthly on the 1st, yearly on Jan 1st."
title="Reset Pattern" title="Reset Pattern"
@ -460,7 +460,7 @@
{/each} {/each}
</select> </select>
<label class="label" for="counterReset"> <label class="label" for="counterReset">
<span class="label-text-alt text-base-content/60"> <span class="label-text-alt text-xs text-base-content/60">
Choose when the counter automatically resets to the start value Choose when the counter automatically resets to the start value
</span> </span>
</label> </label>

View File

@ -190,14 +190,14 @@
</a> </a>
<div class="flex-1"> <div class="flex-1">
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<h1 class="text-3xl font-bold text-gray-800">Geography</h1> <h1 class="text-xl font-bold text-gray-800">Geography</h1>
<HelpTooltip <HelpTooltip
title="Geography Data" title="Geography Data"
text="Geography data is used for patient address management throughout the system. This includes province, city, and detailed area information." text="Geography data is used for patient address management throughout the system. This includes province, city, and detailed area information."
position="right" position="right"
/> />
</div> </div>
<p class="text-gray-600">View geographical areas, provinces, and cities</p> <p class="text-sm text-gray-600">View geographical areas, provinces, and cities</p>
</div> </div>
</div> </div>
@ -264,7 +264,7 @@
<input <input
type="text" type="text"
placeholder="Search provinces by name..." placeholder="Search provinces by name..."
class="input input-bordered w-full pl-10" class="input input-sm input-bordered w-full pl-10"
bind:value={provinceSearch} bind:value={provinceSearch}
/> />
</div> </div>
@ -350,7 +350,7 @@
? `Search cities in ${selectedProvinceLabel}...` ? `Search cities in ${selectedProvinceLabel}...`
: "Search cities by name..." : "Search cities by name..."
} }
class="input input-bordered w-full pl-10" class="input input-sm input-bordered w-full pl-10"
bind:value={citySearch} bind:value={citySearch}
/> />
</div> </div>
@ -412,7 +412,7 @@
<input <input
type="text" type="text"
placeholder="Search areas by name, code, or class..." placeholder="Search areas by name, code, or class..."
class="input input-bordered w-full pl-10" class="input input-sm input-bordered w-full pl-10"
bind:value={areaSearch} bind:value={areaSearch}
/> />
</div> </div>

View File

@ -169,8 +169,8 @@
<ArrowLeft class="w-5 h-5" /> <ArrowLeft class="w-5 h-5" />
</a> </a>
<div class="flex-1"> <div class="flex-1">
<h1 class="text-3xl font-bold text-gray-800">Locations</h1> <h1 class="text-xl font-bold text-gray-800">Locations</h1>
<p class="text-gray-600">Manage locations and facilities</p> <p class="text-sm text-gray-600">Manage locations and facilities</p>
</div> </div>
<button class="btn btn-primary" onclick={openCreateModal}> <button class="btn btn-primary" onclick={openCreateModal}>
<Plus class="w-4 h-4 mr-2" /> <Plus class="w-4 h-4 mr-2" />
@ -185,7 +185,7 @@
<Search class="w-4 h-4 absolute left-3 top-1/2 -translate-y-1/2 text-gray-400" /> <Search class="w-4 h-4 absolute left-3 top-1/2 -translate-y-1/2 text-gray-400" />
<input <input
type="text" type="text"
class="input input-bordered w-full pl-10" class="input input-sm input-bordered w-full pl-10"
placeholder="Search by code or name..." placeholder="Search by code or name..."
bind:value={searchQuery} bind:value={searchQuery}
/> />
@ -203,7 +203,7 @@
<div class="w-16 h-16 rounded-full bg-base-200 flex items-center justify-center mb-4"> <div class="w-16 h-16 rounded-full bg-base-200 flex items-center justify-center mb-4">
<MapPin class="w-8 h-8 text-gray-400" /> <MapPin class="w-8 h-8 text-gray-400" />
</div> </div>
<h3 class="text-lg font-semibold text-base-content mb-1"> <h3 class="text-base font-semibold text-base-content mb-1">
{searchQuery ? 'No locations found' : 'No locations yet'} {searchQuery ? 'No locations found' : 'No locations yet'}
</h3> </h3>
<p class="text-sm text-base-content/60 text-center max-w-sm mb-4"> <p class="text-sm text-base-content/60 text-center max-w-sm mb-4">
@ -258,8 +258,8 @@
<div class="grid grid-cols-1 md:grid-cols-2 gap-4"> <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div class="form-control"> <div class="form-control">
<label class="label" for="code"> <label class="label" for="code">
<span class="label-text font-medium">Location Code</span> <span class="label-text text-sm font-medium">Location Code</span>
<span class="label-text-alt text-error">*</span> <span class="label-text-alt text-xs text-error">*</span>
</label> </label>
<input <input
id="code" id="code"
@ -270,13 +270,13 @@
required required
/> />
<label class="label" for="code"> <label class="label" for="code">
<span class="label-text-alt text-gray-500">Unique identifier for this location</span> <span class="label-text-alt text-xs text-gray-500">Unique identifier for this location</span>
</label> </label>
</div> </div>
<div class="form-control"> <div class="form-control">
<label class="label" for="name"> <label class="label" for="name">
<span class="label-text font-medium">Location Name</span> <span class="label-text text-sm font-medium">Location Name</span>
<span class="label-text-alt text-error">*</span> <span class="label-text-alt text-xs text-error">*</span>
</label> </label>
<input <input
id="name" id="name"
@ -287,15 +287,15 @@
required required
/> />
<label class="label" for="name"> <label class="label" for="name">
<span class="label-text-alt text-gray-500">Descriptive name for this location</span> <span class="label-text-alt text-xs text-gray-500">Descriptive name for this location</span>
</label> </label>
</div> </div>
</div> </div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4"> <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div class="form-control"> <div class="form-control">
<label class="label" for="type"> <label class="label" for="type">
<span class="label-text font-medium">Location Type</span> <span class="label-text text-sm font-medium">Location Type</span>
<span class="label-text-alt text-error">*</span> <span class="label-text-alt text-xs text-error">*</span>
</label> </label>
<select <select
id="type" id="type"
@ -311,12 +311,12 @@
<option value="AREA">Area</option> <option value="AREA">Area</option>
</select> </select>
<label class="label" for="type"> <label class="label" for="type">
<span class="label-text-alt text-gray-500">Category of this location</span> <span class="label-text-alt text-xs text-gray-500">Category of this location</span>
</label> </label>
</div> </div>
<div class="form-control"> <div class="form-control">
<label class="label" for="parent"> <label class="label" for="parent">
<span class="label-text font-medium">Parent Location</span> <span class="label-text text-sm font-medium">Parent Location</span>
<HelpTooltip <HelpTooltip
text="Select a parent location to create a hierarchy. For example, a Room can be inside a Building, or a Floor can be part of a Building." text="Select a parent location to create a hierarchy. For example, a Room can be inside a Building, or a Floor can be part of a Building."
title="Location Hierarchy" title="Location Hierarchy"
@ -330,7 +330,7 @@
placeholder="None (top-level location)" placeholder="None (top-level location)"
/> />
<label class="label" for="parent"> <label class="label" for="parent">
<span class="label-text-alt text-gray-500">Optional: parent location in hierarchy</span> <span class="label-text-alt text-xs text-gray-500">Optional: parent location in hierarchy</span>
</label> </label>
</div> </div>
</div> </div>

View File

@ -160,8 +160,8 @@
<ArrowLeft class="w-5 h-5" /> <ArrowLeft class="w-5 h-5" />
</a> </a>
<div class="flex-1"> <div class="flex-1">
<h1 class="text-3xl font-bold text-gray-800">Occupations</h1> <h1 class="text-xl font-bold text-gray-800">Occupations</h1>
<p class="text-gray-600">Manage occupation codes for patient demographics</p> <p class="text-sm text-gray-600">Manage occupation codes for patient demographics</p>
</div> </div>
<button class="btn btn-primary" onclick={openCreateModal}> <button class="btn btn-primary" onclick={openCreateModal}>
<Plus class="w-4 h-4 mr-2" /> <Plus class="w-4 h-4 mr-2" />
@ -177,7 +177,7 @@
<input <input
type="text" type="text"
placeholder="Search by code or occupation name..." placeholder="Search by code or occupation name..."
class="input input-bordered w-full pl-10" class="input input-sm input-bordered w-full pl-10"
bind:value={searchQuery} bind:value={searchQuery}
onkeydown={handleSearchKeydown} onkeydown={handleSearchKeydown}
/> />
@ -196,10 +196,10 @@
<Briefcase class="w-12 h-12 text-gray-400" /> <Briefcase class="w-12 h-12 text-gray-400" />
{/if} {/if}
</div> </div>
<h3 class="text-lg font-semibold text-gray-700 mb-2"> <h3 class="text-base font-semibold text-gray-700 mb-2">
{searchQuery.trim() ? 'No occupations match your search' : 'No occupations found'} {searchQuery.trim() ? 'No occupations match your search' : 'No occupations found'}
</h3> </h3>
<p class="text-gray-500 text-center max-w-md mb-6"> <p class="text-xs text-gray-500 text-center max-w-md mb-6">
{searchQuery.trim() {searchQuery.trim()
? `No occupations found matching "${searchQuery}". Try a different search term or clear the filter.` ? `No occupations found matching "${searchQuery}". Try a different search term or clear the filter.`
: 'Get started by adding your first occupation code. These codes are used when registering patients to identify their profession.'} : 'Get started by adding your first occupation code. These codes are used when registering patients to identify their profession.'}
@ -264,7 +264,7 @@
<div class="grid grid-cols-1 md:grid-cols-2 gap-4"> <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div class="form-control"> <div class="form-control">
<label class="label" for="occCode"> <label class="label" for="occCode">
<span class="label-text font-medium flex items-center gap-2"> <span class="label-text text-sm font-medium flex items-center gap-2">
Occupation Code Occupation Code
<HelpTooltip <HelpTooltip
text="A short, unique code used to identify this occupation. This code will be displayed in patient demographics. Examples: DR (Doctor), NUR (Nurse), ENG (Engineer), TCH (Teacher). Keep it short (2-5 characters) for easy reference." text="A short, unique code used to identify this occupation. This code will be displayed in patient demographics. Examples: DR (Doctor), NUR (Nurse), ENG (Engineer), TCH (Teacher). Keep it short (2-5 characters) for easy reference."
@ -272,7 +272,7 @@
position="top" position="top"
/> />
</span> </span>
<span class="label-text-alt text-error">*</span> <span class="label-text-alt text-xs text-error">*</span>
</label> </label>
<input <input
id="occCode" id="occCode"
@ -285,15 +285,15 @@
disabled={saving} disabled={saving}
/> />
{#if formErrors.OccCode} {#if formErrors.OccCode}
<span class="label-text-alt text-error mt-1">{formErrors.OccCode}</span> <span class="label-text-alt text-xs text-error mt-1">{formErrors.OccCode}</span>
{:else} {:else}
<span class="label-text-alt text-gray-500">Short unique code (max 10 characters)</span> <span class="label-text-alt text-xs text-gray-500">Short unique code (max 10 characters)</span>
{/if} {/if}
</div> </div>
<div class="form-control"> <div class="form-control">
<label class="label" for="occText"> <label class="label" for="occText">
<span class="label-text font-medium">Occupation Name</span> <span class="label-text text-sm font-medium">Occupation Name</span>
<span class="label-text-alt text-error">*</span> <span class="label-text-alt text-xs text-error">*</span>
</label> </label>
<input <input
id="occText" id="occText"
@ -306,15 +306,15 @@
disabled={saving} disabled={saving}
/> />
{#if formErrors.OccText} {#if formErrors.OccText}
<span class="label-text-alt text-error mt-1">{formErrors.OccText}</span> <span class="label-text-alt text-xs text-error mt-1">{formErrors.OccText}</span>
{:else} {:else}
<span class="label-text-alt text-gray-500">Full name of the occupation</span> <span class="label-text-alt text-xs text-gray-500">Full name of the occupation</span>
{/if} {/if}
</div> </div>
</div> </div>
<div class="form-control"> <div class="form-control">
<label class="label" for="description"> <label class="label" for="description">
<span class="label-text font-medium">Description</span> <span class="label-text text-sm font-medium">Description</span>
</label> </label>
<input <input
id="description" id="description"
@ -326,9 +326,9 @@
disabled={saving} disabled={saving}
/> />
{#if formErrors.Description} {#if formErrors.Description}
<span class="label-text-alt text-error mt-1">{formErrors.Description}</span> <span class="label-text-alt text-xs text-error mt-1">{formErrors.Description}</span>
{:else} {:else}
<span class="label-text-alt text-gray-500">Optional additional details about this occupation</span> <span class="label-text-alt text-xs text-gray-500">Optional additional details about this occupation</span>
{/if} {/if}
</div> </div>
</form> </form>

View File

@ -149,8 +149,8 @@
<ArrowLeft class="w-5 h-5" /> <ArrowLeft class="w-5 h-5" />
</a> </a>
<div class="flex-1"> <div class="flex-1">
<h1 class="text-3xl font-bold text-gray-800">Medical Specialties</h1> <h1 class="text-xl font-bold text-gray-800">Medical Specialties</h1>
<p class="text-gray-600">Manage medical specialty codes and their hierarchical relationships</p> <p class="text-sm text-gray-600">Manage medical specialty codes and their hierarchical relationships</p>
</div> </div>
<button class="btn btn-primary" onclick={openCreateModal}> <button class="btn btn-primary" onclick={openCreateModal}>
<Plus class="w-4 h-4 mr-2" /> <Plus class="w-4 h-4 mr-2" />
@ -164,7 +164,7 @@
<Search class="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-gray-400" /> <Search class="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-gray-400" />
<input <input
type="text" type="text"
class="input input-bordered w-full pl-10" class="input input-sm input-bordered w-full pl-10"
placeholder="Search by specialty name, title, or parent..." placeholder="Search by specialty name, title, or parent..."
bind:value={searchQuery} bind:value={searchQuery}
/> />
@ -187,7 +187,7 @@
<div class="bg-base-200 rounded-full p-6 mb-4"> <div class="bg-base-200 rounded-full p-6 mb-4">
<Stethoscope class="w-12 h-12 text-base-content/40" /> <Stethoscope class="w-12 h-12 text-base-content/40" />
</div> </div>
<h3 class="text-lg font-semibold text-base-content mb-2"> <h3 class="text-base font-semibold text-base-content mb-2">
{searchQuery ? 'No specialties match your search' : 'No specialties yet'} {searchQuery ? 'No specialties match your search' : 'No specialties yet'}
</h3> </h3>
<p class="text-base-content/60 text-center max-w-sm mb-4"> <p class="text-base-content/60 text-center max-w-sm mb-4">
@ -256,8 +256,8 @@
<div class="grid grid-cols-1 md:grid-cols-2 gap-4"> <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div class="form-control"> <div class="form-control">
<label class="label" for="specialtyText"> <label class="label" for="specialtyText">
<span class="label-text font-medium">Specialty Name</span> <span class="label-text text-sm font-medium">Specialty Name</span>
<span class="label-text-alt text-error">*</span> <span class="label-text-alt text-xs text-error">*</span>
</label> </label>
<input <input
id="specialtyText" id="specialtyText"
@ -268,12 +268,12 @@
required required
/> />
<label class="label" for="specialtyText"> <label class="label" for="specialtyText">
<span class="label-text-alt text-base-content/50">Unique name for the specialty</span> <span class="label-text-alt text-xs text-base-content/50">Unique name for the specialty</span>
</label> </label>
</div> </div>
<div class="form-control"> <div class="form-control">
<label class="label" for="title"> <label class="label" for="title">
<span class="label-text font-medium">Professional Title</span> <span class="label-text text-sm font-medium">Professional Title</span>
</label> </label>
<input <input
id="title" id="title"
@ -283,14 +283,14 @@
placeholder="e.g., Sp. PD, Sp. A, Sp. And" placeholder="e.g., Sp. PD, Sp. A, Sp. And"
/> />
<label class="label" for="title"> <label class="label" for="title">
<span class="label-text-alt text-base-content/50">Official abbreviation or title</span> <span class="label-text-alt text-xs text-base-content/50">Official abbreviation or title</span>
</label> </label>
</div> </div>
</div> </div>
<div class="form-control"> <div class="form-control">
<div class="flex items-center gap-2 mb-1"> <div class="flex items-center gap-2 mb-1">
<label class="label py-0" for="parent"> <label class="label py-0" for="parent">
<span class="label-text font-medium">Parent Specialty</span> <span class="label-text text-sm font-medium">Parent Specialty</span>
</label> </label>
<HelpTooltip <HelpTooltip
text="Organize specialties hierarchically. For example, select 'Internal Medicine' as the parent for 'Cardiology' to show it as a subspecialty." text="Organize specialties hierarchically. For example, select 'Internal Medicine' as the parent for 'Cardiology' to show it as a subspecialty."
@ -306,7 +306,7 @@
placeholder="None (Top-level specialty)" placeholder="None (Top-level specialty)"
/> />
<label class="label" for="parent"> <label class="label" for="parent">
<span class="label-text-alt text-base-content/50"> <span class="label-text-alt text-xs text-base-content/50">
Optional: Select a parent specialty to create a subspecialty Optional: Select a parent specialty to create a subspecialty
</span> </span>
</label> </label>

View File

@ -7,7 +7,7 @@
import Modal from '$lib/components/Modal.svelte'; import Modal from '$lib/components/Modal.svelte';
import TestModal from './TestModal.svelte'; import TestModal from './TestModal.svelte';
import TestTypeSelector from './test-modal/TestTypeSelector.svelte'; import TestTypeSelector from './test-modal/TestTypeSelector.svelte';
import { validateNumericRange, validateTholdRange, validateTextRange, validateVsetRange } from './referenceRange.js'; import { validateNumericRange, validateTextRange } from './referenceRange.js';
import { Plus, Edit2, Trash2, ArrowLeft, Filter, Search, ChevronDown, ChevronRight, Microscope, Variable, Calculator, Box, Layers } from 'lucide-svelte'; import { Plus, Edit2, Trash2, ArrowLeft, Filter, Search, ChevronDown, ChevronRight, Microscope, Variable, Calculator, Box, Layers } from 'lucide-svelte';
let loading = $state(false), tests = $state([]), disciplines = $state([]), departments = $state([]); let loading = $state(false), tests = $state([]), disciplines = $state([]), departments = $state([]);
@ -32,11 +32,9 @@
Unit: '', Unit: '',
Formula: '', Formula: '',
refnum: [], refnum: [],
refthold: [],
reftxt: [], reftxt: [],
refvset: [],
refRangeType: 'none', refRangeType: 'none',
// Technical Config (testdeftech) // Technical Config (flat structure)
ResultType: '', ResultType: '',
RefType: '', RefType: '',
ReqQty: null, ReqQty: null,
@ -99,9 +97,7 @@ function openCreateModal(type = 'TEST') {
Unit: '', Unit: '',
Formula: '', Formula: '',
refnum: [], refnum: [],
refthold: [],
reftxt: [], reftxt: [],
refvset: [],
refRangeType: 'none', refRangeType: 'none',
// Technical Config // Technical Config
ResultType: '', ResultType: '',
@ -127,15 +123,33 @@ function openCreateModal(type = 'TEST') {
const testDetail = response.data; const testDetail = response.data;
modalMode = 'edit'; modalMode = 'edit';
// Consolidate refthold into refnum and refvset into reftxt
const consolidatedRefnum = [
...(testDetail.refnum || []),
...(testDetail.refthold || []).map(ref => ({ ...ref, RefType: 'THOLD' }))
];
const consolidatedReftxt = [
...(testDetail.reftxt || []),
...(testDetail.refvset || []).map(ref => ({ ...ref, RefType: 'VSET' }))
];
// Determine refRangeType based on consolidated arrays
let refRangeType = 'none'; let refRangeType = 'none';
if (testDetail.refnum?.length > 0) refRangeType = 'num'; const hasNum = consolidatedRefnum.some(ref => ref.RefType !== 'THOLD');
else if (testDetail.refthold?.length > 0) refRangeType = 'thold'; const hasThold = consolidatedRefnum.some(ref => ref.RefType === 'THOLD');
else if (testDetail.reftxt?.length > 0) refRangeType = 'text'; const hasText = consolidatedReftxt.some(ref => ref.RefType !== 'VSET');
else if (testDetail.refvset?.length > 0) refRangeType = 'vset'; const hasVset = consolidatedReftxt.some(ref => ref.RefType === 'VSET');
if (hasNum) refRangeType = 'num';
else if (hasThold) refRangeType = 'thold';
else if (hasText) refRangeType = 'text';
else if (hasVset) refRangeType = 'vset';
// Normalize reference range data to ensure all fields have values (not undefined) // Normalize reference range data to ensure all fields have values (not undefined)
const normalizeRefNum = (ref) => ({ const normalizeRefNum = (ref) => ({
Sex: ref.Sex ?? '2', RefType: ref.RefType ?? 'REF',
Sex: ref.Sex ?? '0',
LowSign: ref.LowSign ?? 'GE', LowSign: ref.LowSign ?? 'GE',
HighSign: ref.HighSign ?? 'LE', HighSign: ref.HighSign ?? 'LE',
Low: ref.Low ?? null, Low: ref.Low ?? null,
@ -143,37 +157,14 @@ function openCreateModal(type = 'TEST') {
AgeStart: ref.AgeStart ?? 0, AgeStart: ref.AgeStart ?? 0,
AgeEnd: ref.AgeEnd ?? 120, AgeEnd: ref.AgeEnd ?? 120,
Flag: ref.Flag ?? 'N', Flag: ref.Flag ?? 'N',
Interpretation: ref.Interpretation ?? 'Normal', Interpretation: ref.Interpretation ?? '',
SpcType: ref.SpcType ?? '',
Criteria: ref.Criteria ?? ''
});
const normalizeRefThold = (ref) => ({
Sex: ref.Sex ?? '2',
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 ?? 'Normal',
SpcType: ref.SpcType ?? '', SpcType: ref.SpcType ?? '',
Criteria: ref.Criteria ?? '' Criteria: ref.Criteria ?? ''
}); });
const normalizeRefTxt = (ref) => ({ const normalizeRefTxt = (ref) => ({
Sex: ref.Sex ?? '2', RefType: ref.RefType ?? 'TEXT',
AgeStart: ref.AgeStart ?? 0, Sex: ref.Sex ?? '0',
AgeEnd: ref.AgeEnd ?? 120,
RefTxt: ref.RefTxt ?? '',
Flag: ref.Flag ?? 'N',
SpcType: ref.SpcType ?? '',
Criteria: ref.Criteria ?? ''
});
const normalizeRefVset = (ref) => ({
Sex: ref.Sex ?? '2',
AgeStart: ref.AgeStart ?? 0, AgeStart: ref.AgeStart ?? 0,
AgeEnd: ref.AgeEnd ?? 120, AgeEnd: ref.AgeEnd ?? 120,
RefTxt: ref.RefTxt ?? '', RefTxt: ref.RefTxt ?? '',
@ -188,8 +179,8 @@ function openCreateModal(type = 'TEST') {
TestSiteCode: testDetail.TestSiteCode, TestSiteCode: testDetail.TestSiteCode,
TestSiteName: testDetail.TestSiteName, TestSiteName: testDetail.TestSiteName,
TestType: testDetail.TestType, TestType: testDetail.TestType,
DisciplineID: testDetail.testdeftech?.[0]?.DisciplineID || null, DisciplineID: testDetail.DisciplineID || null,
DepartmentID: testDetail.testdeftech?.[0]?.DepartmentID || null, DepartmentID: testDetail.DepartmentID || null,
SeqScr: testDetail.SeqScr || '0', SeqScr: testDetail.SeqScr || '0',
SeqRpt: testDetail.SeqRpt || '0', SeqRpt: testDetail.SeqRpt || '0',
VisibleScr: testDetail.VisibleScr === '1' || testDetail.VisibleScr === 1 || testDetail.VisibleScr === true, VisibleScr: testDetail.VisibleScr === '1' || testDetail.VisibleScr === 1 || testDetail.VisibleScr === true,
@ -198,22 +189,20 @@ function openCreateModal(type = 'TEST') {
CountStat: testDetail.CountStat === '1' || testDetail.CountStat === 1 || testDetail.CountStat === true, CountStat: testDetail.CountStat === '1' || testDetail.CountStat === 1 || testDetail.CountStat === true,
Unit: testDetail.Unit || '', Unit: testDetail.Unit || '',
Formula: testDetail.Formula || '', Formula: testDetail.Formula || '',
refnum: (testDetail.refnum || []).map(normalizeRefNum), refnum: consolidatedRefnum.map(normalizeRefNum),
refthold: (testDetail.refthold || []).map(normalizeRefThold), reftxt: consolidatedReftxt.map(normalizeRefTxt),
reftxt: (testDetail.reftxt || []).map(normalizeRefTxt),
refvset: (testDetail.refvset || []).map(normalizeRefVset),
refRangeType, refRangeType,
// Technical Config (from testdeftech[0]) // Technical Config (flat structure)
ResultType: testDetail.testdeftech?.[0]?.ResultType || '', ResultType: testDetail.ResultType || '',
RefType: testDetail.testdeftech?.[0]?.RefType || '', RefType: testDetail.RefType || '',
ReqQty: testDetail.testdeftech?.[0]?.ReqQty || null, ReqQty: testDetail.ReqQty || null,
ReqQtyUnit: testDetail.testdeftech?.[0]?.ReqQtyUnit || '', ReqQtyUnit: testDetail.ReqQtyUnit || '',
Unit1: testDetail.testdeftech?.[0]?.Unit1 || '', Unit1: testDetail.Unit1 || '',
Factor: testDetail.testdeftech?.[0]?.Factor || null, Factor: testDetail.Factor || null,
Unit2: testDetail.testdeftech?.[0]?.Unit2 || '', Unit2: testDetail.Unit2 || '',
Decimal: testDetail.testdeftech?.[0]?.Decimal || 0, Decimal: testDetail.Decimal || 0,
Method: testDetail.testdeftech?.[0]?.Method || '', Method: testDetail.Method || '',
ExpectedTAT: testDetail.testdeftech?.[0]?.ExpectedTAT || null, ExpectedTAT: testDetail.ExpectedTAT || null,
// Group Members - API returns as testdefgrp // Group Members - API returns as testdefgrp
groupMembers: testDetail.testdefgrp || [] groupMembers: testDetail.testdefgrp || []
}; };
@ -228,10 +217,25 @@ function openCreateModal(type = 'TEST') {
async function handleSave() { async function handleSave() {
if (isDuplicateCode(formData.TestSiteCode, modalMode === 'edit' ? formData.TestSiteID : null)) { toastError(`Test code '${formData.TestSiteCode}' already exists`); return; } if (isDuplicateCode(formData.TestSiteCode, modalMode === 'edit' ? formData.TestSiteID : null)) { toastError(`Test code '${formData.TestSiteCode}' already exists`); return; }
if (canHaveFormula && !formData.Formula.trim()) { toastError('Formula is required for calculated tests'); return; } if (canHaveFormula && !formData.Formula.trim()) { toastError('Formula is required for calculated tests'); return; }
if (formData.refRangeType === 'num') { for (let i = 0; i < formData.refnum.length; i++) { const errors = validateNumericRange(formData.refnum[i], i); if (errors.length > 0) { toastError(errors[0]); return; } } } // Validate reference ranges based on type
else if (formData.refRangeType === 'thold') { for (let i = 0; i < formData.refthold.length; i++) { const errors = validateTholdRange(formData.refthold[i], i); if (errors.length > 0) { toastError(errors[0]); return; } } } if (formData.refRangeType === 'num' || formData.refRangeType === 'thold') {
else if (formData.refRangeType === 'text') { for (let i = 0; i < formData.reftxt.length; i++) { const errors = validateTextRange(formData.reftxt[i], i); if (errors.length > 0) { toastError(errors[0]); return; } } } const rangesToValidate = (formData.refnum || []).filter(ref =>
else if (formData.refRangeType === 'vset') { for (let i = 0; i < formData.refvset.length; i++) { const errors = validateVsetRange(formData.refvset[i], i); if (errors.length > 0) { toastError(errors[0]); return; } } } formData.refRangeType === 'num' ? ref.RefType !== 'THOLD' : ref.RefType === 'THOLD'
);
for (let i = 0; i < rangesToValidate.length; i++) {
const errors = validateNumericRange(rangesToValidate[i], i);
if (errors.length > 0) { toastError(errors[0]); return; }
}
}
else if (formData.refRangeType === 'text' || formData.refRangeType === 'vset') {
const rangesToValidate = (formData.reftxt || []).filter(ref =>
formData.refRangeType === 'text' ? ref.RefType !== 'VSET' : ref.RefType === 'VSET'
);
for (let i = 0; i < rangesToValidate.length; i++) {
const errors = validateTextRange(rangesToValidate[i], i);
if (errors.length > 0) { toastError(errors[0]); return; }
}
}
saving = true; saving = true;
try { try {
const payload = { ...formData }; const payload = { ...formData };
@ -240,9 +244,30 @@ function openCreateModal(type = 'TEST') {
if (!canHaveFormula) delete payload.Formula; if (!canHaveFormula) delete payload.Formula;
if (!canHaveRefRange) { if (!canHaveRefRange) {
delete payload.refnum; delete payload.refnum;
delete payload.refthold;
delete payload.reftxt; delete payload.reftxt;
delete payload.refvset; } else {
// Filter refnum and reftxt based on selected refRangeType
if (formData.refRangeType === 'num') {
// Keep only non-THOLD items in refnum
payload.refnum = (formData.refnum || []).filter(ref => ref.RefType !== 'THOLD');
delete payload.reftxt;
} else if (formData.refRangeType === 'thold') {
// Keep only THOLD items in refnum
payload.refnum = (formData.refnum || []).filter(ref => ref.RefType === 'THOLD');
delete payload.reftxt;
} else if (formData.refRangeType === 'text') {
// Keep only non-VSET items in reftxt
payload.reftxt = (formData.reftxt || []).filter(ref => ref.RefType !== 'VSET');
delete payload.refnum;
} else if (formData.refRangeType === 'vset') {
// Keep only VSET items in reftxt
payload.reftxt = (formData.reftxt || []).filter(ref => ref.RefType === 'VSET');
delete payload.refnum;
} else {
// No ref range type selected
delete payload.refnum;
delete payload.reftxt;
}
} }
if (!canHaveTechnical) { if (!canHaveTechnical) {
delete payload.ResultType; delete payload.ResultType;
@ -291,8 +316,8 @@ function openCreateModal(type = 'TEST') {
<div class="flex items-center gap-4 mb-6"> <div class="flex items-center gap-4 mb-6">
<a href="/master-data" class="btn btn-ghost btn-circle"><ArrowLeft class="w-5 h-5" /></a> <a href="/master-data" class="btn btn-ghost btn-circle"><ArrowLeft class="w-5 h-5" /></a>
<div class="flex-1"> <div class="flex-1">
<h1 class="text-3xl font-bold text-gray-800">Test Definitions</h1> <h1 class="text-xl font-bold text-gray-800">Test Definitions</h1>
<p class="text-gray-600">Manage laboratory tests, panels, and calculated values</p> <p class="text-sm text-gray-600">Manage laboratory tests, panels, and calculated values</p>
</div> </div>
<button class="btn btn-primary" onclick={openTypeSelector}><Plus class="w-4 h-4 mr-2" />Add Test</button> <button class="btn btn-primary" onclick={openTypeSelector}><Plus class="w-4 h-4 mr-2" />Add Test</button>
</div> </div>
@ -300,7 +325,7 @@ function openCreateModal(type = 'TEST') {
<div class="bg-base-100 rounded-lg shadow border border-base-200 p-4 mb-4"> <div class="bg-base-100 rounded-lg shadow border border-base-200 p-4 mb-4">
<div class="flex flex-col sm:flex-row gap-4"> <div class="flex flex-col sm:flex-row gap-4">
<div class="flex-1 relative"> <div class="flex-1 relative">
<input type="text" placeholder="Search by code or name..." class="input input-bordered w-full pl-10" bind:value={searchQuery} bind:this={searchInputRef} onkeydown={(e) => e.key === 'Enter' && handleSearch()} /> <input type="text" placeholder="Search by code or name..." class="input input-sm input-bordered w-full pl-10" bind:value={searchQuery} bind:this={searchInputRef} onkeydown={(e) => e.key === 'Enter' && handleSearch()} />
<Search class="w-5 h-5 absolute left-3 top-1/2 -translate-y-1/2 text-gray-400" /> <Search class="w-5 h-5 absolute left-3 top-1/2 -translate-y-1/2 text-gray-400" />
</div> </div>
<div class="w-full sm:w-48"> <div class="w-full sm:w-48">

View File

@ -59,8 +59,7 @@
// Get tab count badge for reference range // Get tab count badge for reference range
function getRefRangeCount() { function getRefRangeCount() {
return (formData.refnum?.length || 0) + (formData.reftxt?.length || 0) + return (formData.refnum?.length || 0) + (formData.reftxt?.length || 0);
(formData.refthold?.length || 0) + (formData.refvset?.length || 0);
} }
// Get tab count badge for group members // Get tab count badge for group members

View File

@ -14,6 +14,19 @@ export const flagOptions = [
{ value: 'C', label: 'C', description: 'Critical' } { value: 'C', label: 'C', description: 'Critical' }
]; ];
export const refTypeOptions = [
{ value: 'REF', label: 'REF', description: 'Reference Range' },
{ value: 'CRTC', label: 'CRTC', description: 'Critical Range' },
{ value: 'VAL', label: 'VAL', description: 'Validation Range' },
{ value: 'RERUN', label: 'RERUN', description: 'Rerun Range' },
{ value: 'THOLD', label: 'THOLD', description: 'Threshold Range' }
];
export const textRefTypeOptions = [
{ value: 'TEXT', label: 'TEXT', description: 'Text Reference' },
{ value: 'VSET', label: 'VSET', description: 'Value Set Reference' }
];
export const sexOptions = [ export const sexOptions = [
{ value: '2', label: 'Male' }, { value: '2', label: 'Male' },
{ value: '1', label: 'Female' }, { value: '1', label: 'Female' },
@ -22,7 +35,8 @@ export const sexOptions = [
export function createNumRef() { export function createNumRef() {
return { return {
Sex: '2', RefType: 'REF',
Sex: '0',
LowSign: 'GE', LowSign: 'GE',
HighSign: 'LE', HighSign: 'LE',
Low: null, Low: null,
@ -30,7 +44,7 @@ export function createNumRef() {
AgeStart: 0, AgeStart: 0,
AgeEnd: 120, AgeEnd: 120,
Flag: 'N', Flag: 'N',
Interpretation: 'Normal', Interpretation: '',
SpcType: '', SpcType: '',
Criteria: '' Criteria: ''
}; };
@ -38,7 +52,8 @@ export function createNumRef() {
export function createTholdRef() { export function createTholdRef() {
return { return {
Sex: '2', RefType: 'THOLD',
Sex: '0',
LowSign: 'GE', LowSign: 'GE',
HighSign: 'LE', HighSign: 'LE',
Low: null, Low: null,
@ -46,7 +61,7 @@ export function createTholdRef() {
AgeStart: 0, AgeStart: 0,
AgeEnd: 120, AgeEnd: 120,
Flag: 'N', Flag: 'N',
Interpretation: 'Normal', Interpretation: '',
SpcType: '', SpcType: '',
Criteria: '' Criteria: ''
}; };
@ -54,7 +69,8 @@ export function createTholdRef() {
export function createTextRef() { export function createTextRef() {
return { return {
Sex: '2', RefType: 'TEXT',
Sex: '0',
AgeStart: 0, AgeStart: 0,
AgeEnd: 120, AgeEnd: 120,
RefTxt: '', RefTxt: '',
@ -66,7 +82,8 @@ export function createTextRef() {
export function createVsetRef() { export function createVsetRef() {
return { return {
Sex: '2', RefType: 'VSET',
Sex: '0',
AgeStart: 0, AgeStart: 0,
AgeEnd: 120, AgeEnd: 120,
RefTxt: '', RefTxt: '',
@ -87,16 +104,8 @@ export function validateNumericRange(ref, index) {
return errors; return errors;
} }
export function validateTholdRange(ref, index) { // Alias for threshold validation (same logic)
const errors = []; export const validateTholdRange = validateNumericRange;
if (ref.Low !== null && ref.High !== null && ref.Low > ref.High) {
errors.push(`Range ${index + 1}: Low value cannot be greater than High value`);
}
if (ref.AgeStart !== null && ref.AgeEnd !== null && ref.AgeStart > ref.AgeEnd) {
errors.push(`Range ${index + 1}: Age start cannot be greater than Age end`);
}
return errors;
}
export function validateTextRange(ref, index) { export function validateTextRange(ref, index) {
const errors = []; const errors = [];
@ -106,10 +115,5 @@ export function validateTextRange(ref, index) {
return errors; return errors;
} }
export function validateVsetRange(ref, index) { // Alias for value set validation (same logic)
const errors = []; export const validateVsetRange = validateTextRange;
if (ref.AgeStart !== null && ref.AgeEnd !== null && ref.AgeStart > ref.AgeEnd) {
errors.push(`Range ${index + 1}: Age start cannot be greater than Age end`);
}
return errors;
}

View File

@ -21,7 +21,7 @@
} = $props(); } = $props();
const typeLabels = { const typeLabels = {
TEST: 'Single Test', TEST: 'Test',
PARAM: 'Parameter', PARAM: 'Parameter',
CALC: 'Calculated', CALC: 'Calculated',
GROUP: 'Panel' GROUP: 'Panel'
@ -44,8 +44,8 @@ const typeLabels = {
<div class="grid grid-cols-1 md:grid-cols-2 gap-4"> <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div class="form-control"> <div class="form-control">
<label class="label" for="testCode"> <label class="label" for="testCode">
<span class="label-text font-medium">Test Code</span> <span class="label-text text-sm font-medium">Test Code</span>
<span class="label-text-alt text-error">*</span> <span class="label-text-alt text-xs text-error">*</span>
</label> </label>
<input <input
id="testCode" id="testCode"
@ -58,8 +58,8 @@ const typeLabels = {
</div> </div>
<div class="form-control"> <div class="form-control">
<label class="label" for="testName"> <label class="label" for="testName">
<span class="label-text font-medium">Test Name</span> <span class="label-text text-sm font-medium">Test Name</span>
<span class="label-text-alt text-error">*</span> <span class="label-text-alt text-xs text-error">*</span>
</label> </label>
<input <input
id="testName" id="testName"
@ -72,20 +72,6 @@ const typeLabels = {
</div> </div>
</div> </div>
<!-- Sequence -->
<div class="form-control max-w-xs">
<label class="label" for="seqScr">
<span class="label-text font-medium">Screen Sequence</span>
</label>
<input
id="seqScr"
type="number"
class="input input-sm input-bordered w-full"
bind:value={formData.SeqScr}
placeholder="0"
/>
</div>
<!-- Discipline and Department --> <!-- Discipline and Department -->
<div class="grid grid-cols-1 md:grid-cols-2 gap-4"> <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<SelectDropdown <SelectDropdown
@ -109,14 +95,14 @@ const typeLabels = {
<div class="grid grid-cols-1 md:grid-cols-1 gap-4"> <div class="grid grid-cols-1 md:grid-cols-1 gap-4">
<div class="form-control"> <div class="form-control">
<label class="label" for="formula"> <label class="label" for="formula">
<span class="label-text font-medium flex items-center gap-2"> <span class="label-text text-sm font-medium flex items-center gap-2">
Formula Formula
<HelpTooltip <HelpTooltip
text="Enter a mathematical formula using test codes (e.g., BUN / Creatinine). Supported operators: +, -, *, /, parentheses." text="Enter a mathematical formula using test codes (e.g., BUN / Creatinine). Supported operators: +, -, *, /, parentheses."
title="Formula Help" title="Formula Help"
/> />
</span> </span>
<span class="label-text-alt text-error">*</span> <span class="label-text-alt text-xs text-error">*</span>
</label> </label>
<input <input
id="formula" id="formula"
@ -126,7 +112,7 @@ const typeLabels = {
placeholder="e.g., BUN / Creatinine" placeholder="e.g., BUN / Creatinine"
required={canHaveFormula} required={canHaveFormula}
/> />
<span class="label-text-alt text-gray-500">Use test codes with operators: +, -, *, /</span> <span class="label-text-alt text-xs text-gray-500">Use test codes with operators: +, -, *, /</span>
</div> </div>
</div> </div>
{/if} {/if}
@ -134,7 +120,7 @@ const typeLabels = {
<!-- Description --> <!-- Description -->
<div class="form-control"> <div class="form-control">
<label class="label" for="description"> <label class="label" for="description">
<span class="label-text font-medium">Description</span> <span class="label-text text-sm font-medium">Description</span>
</label> </label>
<textarea <textarea
id="description" id="description"
@ -145,11 +131,23 @@ const typeLabels = {
></textarea> ></textarea>
</div> </div>
<!-- Report Sequence, Visibility, and CountStat --> <!-- Screen Sequence, Report Sequence, Visibility, and Statistics -->
<div class="grid grid-cols-1 md:grid-cols-3 gap-4"> <div class="grid grid-cols-2 md:grid-cols-4 gap-4">
<div class="form-control">
<label class="label" for="seqScr">
<span class="label-text text-sm font-medium">Screen Sequence</span>
</label>
<input
id="seqScr"
type="number"
class="input input-sm input-bordered w-full"
bind:value={formData.SeqScr}
placeholder="0"
/>
</div>
<div class="form-control"> <div class="form-control">
<label class="label" for="seqRpt"> <label class="label" for="seqRpt">
<span class="label-text font-medium">Report Sequence</span> <span class="label-text text-sm font-medium">Report Sequence</span>
</label> </label>
<input <input
id="seqRpt" id="seqRpt"
@ -160,7 +158,7 @@ const typeLabels = {
/> />
</div> </div>
<div class="form-control"> <div class="form-control">
<span class="label-text font-medium mb-2 block">Visibility</span> <span class="label-text text-sm font-medium mb-2 block">Visibility</span>
<div class="flex gap-4"> <div class="flex gap-4">
<label class="label cursor-pointer gap-2"> <label class="label cursor-pointer gap-2">
<input type="checkbox" class="checkbox" bind:checked={formData.VisibleScr} /> <input type="checkbox" class="checkbox" bind:checked={formData.VisibleScr} />
@ -173,7 +171,7 @@ const typeLabels = {
</div> </div>
</div> </div>
<div class="form-control"> <div class="form-control">
<span class="label-text font-medium mb-2 block">Statistics</span> <span class="label-text text-sm font-medium mb-2 block">Statistics</span>
<label class="label cursor-pointer gap-2"> <label class="label cursor-pointer gap-2">
<input type="checkbox" class="checkbox" bind:checked={formData.CountStat} /> <input type="checkbox" class="checkbox" bind:checked={formData.CountStat} />
<span class="label-text">Count in Statistics</span> <span class="label-text">Count in Statistics</span>

View File

@ -71,7 +71,7 @@
<Search class="w-5 h-5 absolute left-3 top-1/2 -translate-y-1/2 text-gray-400" /> <Search class="w-5 h-5 absolute left-3 top-1/2 -translate-y-1/2 text-gray-400" />
<input <input
type="text" type="text"
class="input input-bordered w-full pl-10" class="input input-sm input-bordered w-full pl-10"
bind:value={searchQuery} bind:value={searchQuery}
placeholder="Search by test name or code..." placeholder="Search by test name or code..."
/> />

View File

@ -1,6 +1,6 @@
<script> <script>
import { PlusCircle, Calculator, X, ChevronDown, ChevronUp, Info, Beaker, Filter } from 'lucide-svelte'; import { PlusCircle, Calculator, X, ChevronDown, ChevronUp, Beaker } from 'lucide-svelte';
import { flagOptions, sexOptions, createNumRef } from '../referenceRange.js'; import { refTypeOptions, createNumRef } from '../referenceRange.js';
import { fetchValueSetByKey } from '$lib/api/valuesets.js'; import { fetchValueSetByKey } from '$lib/api/valuesets.js';
import { onMount } from 'svelte'; import { onMount } from 'svelte';
@ -16,8 +16,7 @@
onupdateRefnum = () => {} onupdateRefnum = () => {}
} = $props(); } = $props();
// Track expanded state for each range's optional fields let showAdvanced = $state({});
let expandedRanges = $state({});
let specimenTypeOptions = $state([]); let specimenTypeOptions = $state([]);
onMount(async () => { onMount(async () => {
@ -34,77 +33,36 @@
function addRefRange() { function addRefRange() {
const newRef = createNumRef(); const newRef = createNumRef();
// Set smarter defaults newRef.Sex = '0';
newRef.Sex = '0'; // Any newRef.Flag = 'N';
newRef.Flag = 'N'; // Normal
onupdateRefnum([...refnum, newRef]); onupdateRefnum([...refnum, newRef]);
// Auto-expand the new range
expandedRanges[refnum.length] = { age: false, interpretation: false, specimen: false };
} }
function removeRefRange(index) { function removeRefRange(index) {
onupdateRefnum(refnum.filter((_, i) => i !== index)); onupdateRefnum(refnum.filter((_, i) => i !== index));
delete expandedRanges[index]; delete showAdvanced[index];
} }
function toggleAgeExpand(index) { function toggleAdvanced(index) {
expandedRanges[index] = { ...expandedRanges[index], age: !expandedRanges[index]?.age }; showAdvanced[index] = !showAdvanced[index];
} }
function toggleInterpExpand(index) { function hasAdvancedData(ref) {
expandedRanges[index] = { ...expandedRanges[index], interpretation: !expandedRanges[index]?.interpretation }; return ref.Interpretation || ref.SpcType || ref.Criteria ||
} ref.Sex !== '0' || ref.Flag !== 'N' ||
(ref.AgeStart !== 0 || ref.AgeEnd !== 120);
function toggleSpecimenExpand(index) {
expandedRanges[index] = { ...expandedRanges[index], specimen: !expandedRanges[index]?.specimen };
}
function getSexLabel(value) {
return sexOptions.find(o => o.value === value)?.label || 'Any';
}
function getFlagColor(flag) {
const colors = {
'N': 'badge-success',
'L': 'badge-warning',
'H': 'badge-warning',
'C': 'badge-error'
};
return colors[flag] || 'badge-ghost';
}
function getFlagLabel(flag) {
const option = flagOptions.find(o => o.value === flag);
return option ? `${option.label} (${option.description})` : flag;
}
// Generate human-readable preview
function getRangePreview(ref) {
const sex = getSexLabel(ref.Sex);
const ageText = (ref.AgeStart !== 0 || ref.AgeEnd !== 120)
? `Age ${ref.AgeStart}-${ref.AgeEnd}`
: 'All ages';
let rangeText = '';
if (ref.Low !== null && ref.High !== null) {
rangeText = `${ref.Low} - ${ref.High}`;
} else if (ref.Low !== null) {
rangeText = `> ${ref.Low}`;
} else if (ref.High !== null) {
rangeText = `< ${ref.High}`;
} else {
rangeText = 'Not set';
}
return `${sex}, ${ageText}: ${rangeText}`;
} }
</script> </script>
<div class="space-y-3"> <div class="space-y-2">
<!-- Header -->
<div class="flex justify-between items-center"> <div class="flex justify-between items-center">
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<Calculator class="w-5 h-5 text-primary" /> <Calculator class="w-5 h-5 text-primary" />
<h3 class="font-semibold">Numeric Reference Ranges</h3> <h3 class="font-semibold">Numeric Reference Ranges</h3>
{#if refnum?.length > 0}
<span class="badge badge-sm badge-ghost">{refnum.length}</span>
{/if}
</div> </div>
<button type="button" class="btn btn-sm btn-primary" onclick={addRefRange}> <button type="button" class="btn btn-sm btn-primary" onclick={addRefRange}>
<PlusCircle class="w-4 h-4 mr-1" /> <PlusCircle class="w-4 h-4 mr-1" />
@ -112,6 +70,7 @@
</button> </button>
</div> </div>
<!-- Empty State -->
{#if refnum?.length === 0} {#if refnum?.length === 0}
<div class="text-center py-8 bg-base-200 rounded-lg border-2 border-dashed border-base-300"> <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" /> <Calculator class="w-12 h-12 mx-auto text-gray-400 mb-2" />
@ -123,187 +82,152 @@
</div> </div>
{/if} {/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)} {#each refnum || [] as ref, index (index)}
<div class="card bg-base-100 border-2 border-base-200 hover:border-primary/50 transition-colors"> <div class="border-b border-base-200 last:border-b-0">
<div class="card-body p-4"> <!-- Main Row -->
<!-- Header with Preview --> <div class="px-4 py-3 grid grid-cols-12 gap-3 items-center hover:bg-base-100">
<div class="flex justify-between items-start mb-4 pb-3 border-b border-base-200"> <!-- Row Number -->
<div class="flex-1"> <div class="col-span-1 flex items-center gap-1">
<div class="flex items-center gap-2 mb-2"> <span class="text-xs text-gray-500">{index + 1}</span>
<span class="badge badge-primary">Range {index + 1}</span>
<span class="badge {getFlagColor(ref.Flag)}">{getFlagLabel(ref.Flag)}</span>
</div>
<!-- Live Preview -->
<div class="text-sm text-gray-600 bg-base-200 p-2 rounded flex items-center gap-2">
<Info class="w-4 h-4 text-primary flex-shrink-0" />
<span>{getRangePreview(ref)}</span>
</div>
</div>
<button type="button" class="btn btn-sm btn-ghost text-error ml-2" onclick={() => removeRefRange(index)}>
<X class="w-4 h-4" />
</button>
</div> </div>
<!-- Main Fields - Range Values (Most Important) --> <!-- Type Dropdown -->
<div class="grid grid-cols-1 sm:grid-cols-2 gap-3 mb-3"> <div class="col-span-3">
<div class="form-control"> <select class="select select-sm select-bordered w-full" bind:value={ref.RefType}>
<span class="label-text text-xs mb-1 font-medium">Lower Bound</span> {#each refTypeOptions as option (option.value)}
<input
type="number"
step="0.01"
class="input input-sm input-bordered w-full"
bind:value={ref.Low}
placeholder="70"
/>
</div>
<div class="form-control">
<span class="label-text text-xs mb-1 font-medium">Upper Bound</span>
<input
type="number"
step="0.01"
class="input input-sm input-bordered w-full"
bind:value={ref.High}
placeholder="100"
/>
</div>
</div>
<!-- Sex & Flag Row -->
<div class="grid grid-cols-2 gap-3 mb-3">
<div class="form-control">
<span class="label-text text-xs mb-1">Sex</span>
<select class="select select-sm select-bordered w-full" bind:value={ref.Sex}>
{#each sexOptions as option (option.value)}
<option value={option.value}>{option.label}</option> <option value={option.value}>{option.label}</option>
{/each} {/each}
</select> </select>
</div> </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"> <div class="form-control">
<span class="label-text text-xs mb-1">Flag</span> <span class="label-text text-xs text-gray-500">Sex</span>
<select class="select select-sm select-bordered w-full" bind:value={ref.Flag}> <select class="select select-sm select-bordered w-full" bind:value={ref.Sex}>
{#each flagOptions as option (option.value)} <option value="0">Any</option>
<option value={option.value}>{option.label} - {option.description}</option> <option value="1">Female</option>
{/each} <option value="2">Male</option>
</select> </select>
</div> </div>
</div>
<!-- Expandable: Age Range --> <!-- Age Range -->
<div class="border border-base-200 rounded-lg mb-2 overflow-hidden">
<button
type="button"
class="w-full px-3 py-2 bg-base-200 hover:bg-base-300 flex items-center justify-between text-sm transition-colors"
onclick={() => toggleAgeExpand(index)}
>
<span class="flex items-center gap-2">
<span class="text-xs">Age Range</span>
{#if ref.AgeStart !== 0 || ref.AgeEnd !== 120}
<span class="text-xs text-primary">({ref.AgeStart}-{ref.AgeEnd})</span>
{:else}
<span class="text-xs text-gray-500">(All ages)</span>
{/if}
</span>
{#if expandedRanges[index]?.age}
<ChevronUp class="w-4 h-4" />
{:else}
<ChevronDown class="w-4 h-4" />
{/if}
</button>
{#if expandedRanges[index]?.age}
<div class="p-3 bg-base-100">
<div class="grid grid-cols-2 gap-3">
<div class="form-control"> <div class="form-control">
<span class="label-text text-xs mb-1">From (years)</span> <span class="label-text text-xs text-gray-500">Age Range</span>
<input type="number" min="0" max="120" class="input input-sm input-bordered w-full" bind:value={ref.AgeStart} /> <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>
<div class="form-control">
<span class="label-text text-xs mb-1">To (years)</span>
<input type="number" min="0" max="120" class="input input-sm input-bordered w-full" bind:value={ref.AgeEnd} />
</div>
</div>
</div>
{/if}
</div> </div>
<!-- Expandable: Interpretation --> <!-- Specimen -->
<div class="border border-base-200 rounded-lg overflow-hidden mb-2"> <div class="form-control md:col-span-2">
<button <span class="label-text text-xs text-gray-500 flex items-center gap-1">
type="button"
class="w-full px-3 py-2 bg-base-200 hover:bg-base-300 flex items-center justify-between text-sm transition-colors"
onclick={() => toggleInterpExpand(index)}
>
<span class="flex items-center gap-2">
<span class="text-xs">Interpretation</span>
{#if ref.Interpretation}
<span class="text-xs text-primary truncate max-w-[200px]">({ref.Interpretation})</span>
{/if}
</span>
{#if expandedRanges[index]?.interpretation}
<ChevronUp class="w-4 h-4" />
{:else}
<ChevronDown class="w-4 h-4" />
{/if}
</button>
{#if expandedRanges[index]?.interpretation}
<div class="p-3 bg-base-100">
<div class="form-control">
<textarea
class="textarea textarea-bordered w-full text-sm"
rows="2"
bind:value={ref.Interpretation}
placeholder="e.g., Normal fasting glucose range"
></textarea>
</div>
</div>
{/if}
</div>
<!-- Expandable: Specimen and Criteria -->
<div class="border border-base-200 rounded-lg overflow-hidden">
<button
type="button"
class="w-full px-3 py-2 bg-base-200 hover:bg-base-300 flex items-center justify-between text-sm transition-colors"
onclick={() => toggleSpecimenExpand(index)}
>
<span class="flex items-center gap-2">
<Beaker class="w-3 h-3" />
<span class="text-xs">Specimen & Criteria</span>
{#if ref.SpcType || ref.Criteria}
<span class="text-xs text-primary">(Custom)</span>
{:else}
<span class="text-xs text-gray-500">(Optional)</span>
{/if}
</span>
{#if expandedRanges[index]?.specimen}
<ChevronUp class="w-4 h-4" />
{:else}
<ChevronDown class="w-4 h-4" />
{/if}
</button>
{#if expandedRanges[index]?.specimen}
<div class="p-3 bg-base-100 space-y-3">
<div class="form-control">
<span class="label-text text-xs mb-1 flex items-center gap-1">
<Beaker class="w-3 h-3" /> <Beaker class="w-3 h-3" />
Specimen Type Specimen Type
</span> </span>
<select class="select select-sm select-bordered w-full" bind:value={ref.SpcType}> <select class="select select-sm select-bordered w-full" bind:value={ref.SpcType}>
<option value="">Any specimen</option> <option value="">Any specimen</option>
{#each specimenTypeOptions as option} {#each specimenTypeOptions as option (option.value)}
<option value={option.value}>{option.label}</option> <option value={option.value}>{option.label}</option>
{/each} {/each}
</select> </select>
</div> </div>
<div class="form-control">
<span class="label-text text-xs mb-1 flex items-center gap-1"> <!-- Interpretation -->
<Filter class="w-3 h-3" /> <div class="form-control md:col-span-2">
Criteria <span class="label-text text-xs text-gray-500">Interpretation</span>
</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 <input
type="text" type="text"
class="input input-sm input-bordered w-full" class="input input-sm input-bordered w-full"
@ -312,9 +236,10 @@
/> />
</div> </div>
</div> </div>
</div>
{/if} {/if}
</div> </div>
</div>
</div>
{/each} {/each}
</div> </div>
{/if}
</div>

View File

@ -1,10 +1,8 @@
<script> <script>
import { Ruler, Calculator, FileText, Box } from 'lucide-svelte'; import { Ruler, Info } from 'lucide-svelte';
import HelpTooltip from '$lib/components/HelpTooltip.svelte'; import HelpTooltip from '$lib/components/HelpTooltip.svelte';
import NumericRefRange from './NumericRefRange.svelte'; import NumericRefRange from './NumericRefRange.svelte';
import ThresholdRefRange from './ThresholdRefRange.svelte';
import TextRefRange from './TextRefRange.svelte'; import TextRefRange from './TextRefRange.svelte';
import ValueSetRefRange from './ValueSetRefRange.svelte';
import { createNumRef, createTholdRef, createTextRef, createVsetRef } from '../referenceRange.js'; import { createNumRef, createTholdRef, createTextRef, createVsetRef } from '../referenceRange.js';
/** /**
@ -19,10 +17,28 @@
onupdateFormData = () => {} onupdateFormData = () => {}
} = $props(); } = $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)'
};
const refTypeOptions = [
{ value: 'none', label: 'None - No reference range' },
{ 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' }
];
// Ensure all reference range items have defined values, never undefined // Ensure all reference range items have defined values, never undefined
function normalizeRefNum(ref) { function normalizeRefNum(ref) {
return { return {
Sex: ref.Sex ?? '2', RefType: ref.RefType ?? 'REF',
Sex: ref.Sex ?? '0',
LowSign: ref.LowSign ?? 'GE', LowSign: ref.LowSign ?? 'GE',
HighSign: ref.HighSign ?? 'LE', HighSign: ref.HighSign ?? 'LE',
Low: ref.Low ?? null, Low: ref.Low ?? null,
@ -30,23 +46,7 @@
AgeStart: ref.AgeStart ?? 0, AgeStart: ref.AgeStart ?? 0,
AgeEnd: ref.AgeEnd ?? 120, AgeEnd: ref.AgeEnd ?? 120,
Flag: ref.Flag ?? 'N', Flag: ref.Flag ?? 'N',
Interpretation: ref.Interpretation ?? 'Normal', Interpretation: ref.Interpretation ?? '',
SpcType: ref.SpcType ?? '',
Criteria: ref.Criteria ?? ''
};
}
function normalizeRefThold(ref) {
return {
Sex: ref.Sex ?? '2',
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 ?? 'Normal',
SpcType: ref.SpcType ?? '', SpcType: ref.SpcType ?? '',
Criteria: ref.Criteria ?? '' Criteria: ref.Criteria ?? ''
}; };
@ -54,7 +54,8 @@
function normalizeRefTxt(ref) { function normalizeRefTxt(ref) {
return { return {
Sex: ref.Sex ?? '2', RefType: ref.RefType ?? 'TEXT',
Sex: ref.Sex ?? '0',
AgeStart: ref.AgeStart ?? 0, AgeStart: ref.AgeStart ?? 0,
AgeEnd: ref.AgeEnd ?? 120, AgeEnd: ref.AgeEnd ?? 120,
RefTxt: ref.RefTxt ?? '', RefTxt: ref.RefTxt ?? '',
@ -64,46 +65,61 @@
}; };
} }
function normalizeRefVset(ref) { // Reactive normalized data - filter by RefType
return { let allRefnum = $derived((formData.refnum || []).map(normalizeRefNum));
Sex: ref.Sex ?? '2', let allReftxt = $derived((formData.reftxt || []).map(normalizeRefTxt));
AgeStart: ref.AgeStart ?? 0,
AgeEnd: ref.AgeEnd ?? 120,
RefTxt: ref.RefTxt ?? '',
Flag: ref.Flag ?? 'N',
SpcType: ref.SpcType ?? '',
Criteria: ref.Criteria ?? ''
};
}
// Reactive normalized data // Filtered arrays for display
let normalizedRefnum = $derived((formData.refnum || []).map(normalizeRefNum)); let normalizedRefnum = $derived(allRefnum.filter(ref => ref.RefType !== 'THOLD'));
let normalizedRefthold = $derived((formData.refthold || []).map(normalizeRefThold)); let normalizedRefthold = $derived(allRefnum.filter(ref => ref.RefType === 'THOLD'));
let normalizedReftxt = $derived((formData.reftxt || []).map(normalizeRefTxt)); let normalizedReftxt = $derived(allReftxt.filter(ref => ref.RefType !== 'VSET'));
let normalizedRefvset = $derived((formData.refvset || []).map(normalizeRefVset)); let normalizedRefvset = $derived(allReftxt.filter(ref => ref.RefType === 'VSET'));
function updateRefRangeType(type) { function updateRefRangeType(type) {
let newFormData = { // 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, ...formData,
refRangeType: type, refRangeType: type,
refnum: [], RefType: 'NMRC',
refthold: [], refnum: [...currentRefnum, createNumRef()]
reftxt: [], });
refvset: []
};
// Initialize the selected type
if (type === 'num') {
newFormData.refnum = [createNumRef()];
} else if (type === 'thold') { } else if (type === 'thold') {
newFormData.refthold = [createTholdRef()]; // Add threshold range to refnum
onupdateFormData({
...formData,
refRangeType: type,
RefType: 'THOLD',
refnum: [...currentRefnum, createTholdRef()]
});
} else if (type === 'text') { } else if (type === 'text') {
newFormData.reftxt = [createTextRef()]; // Add text range to reftxt
onupdateFormData({
...formData,
refRangeType: type,
RefType: 'TEXT',
reftxt: [...currentReftxt, createTextRef()]
});
} else if (type === 'vset') { } else if (type === 'vset') {
newFormData.refvset = [createVsetRef()]; // Add value set range to reftxt
onupdateFormData({
...formData,
refRangeType: type,
RefType: 'VSET',
reftxt: [...currentReftxt, createVsetRef()]
});
} else {
// None selected
onupdateFormData({
...formData,
refRangeType: type,
RefType: ''
});
} }
onupdateFormData(newFormData);
} }
function updateRefnum(refnum) { function updateRefnum(refnum) {
@ -111,7 +127,9 @@
} }
function updateRefthold(refthold) { function updateRefthold(refthold) {
onupdateFormData({ ...formData, refthold }); // Merge thold items back into refnum
const nonThold = (formData.refnum || []).filter(ref => ref.RefType !== 'THOLD');
onupdateFormData({ ...formData, refnum: [...nonThold, ...refthold] });
} }
function updateReftxt(reftxt) { function updateReftxt(reftxt) {
@ -119,7 +137,9 @@
} }
function updateRefvset(refvset) { function updateRefvset(refvset) {
onupdateFormData({ ...formData, refvset }); // Merge vset items back into reftxt
const nonVset = (formData.reftxt || []).filter(ref => ref.RefType !== 'VSET');
onupdateFormData({ ...formData, reftxt: [...nonVset, ...refvset] });
} }
</script> </script>
@ -134,90 +154,30 @@
title="Reference Range Help" title="Reference Range Help"
/> />
</div> </div>
<div class="flex flex-wrap gap-3">
<label class="label cursor-pointer gap-2 p-3 bg-base-200 rounded-lg hover:bg-base-300 transition-colors flex-1 min-w-[100px]"> <!-- Dropdown Select -->
<input <div class="form-control">
type="radio" <select
name="refRangeType" class="select select-bordered w-full"
class="radio radio-primary" value={formData.refRangeType || 'none'}
value="none" onchange={(e) => updateRefRangeType(e.target.value)}
checked={formData.refRangeType === 'none'} >
onchange={() => updateRefRangeType('none')} {#each refTypeOptions as option (option.value)}
/> <option value={option.value}>{option.label}</option>
<div class="flex flex-col"> {/each}
<span class="label-text font-medium">None</span> </select>
<span class="text-xs text-gray-500">No reference range</span>
</div> </div>
</label>
<label class="label cursor-pointer gap-2 p-3 bg-base-200 rounded-lg hover:bg-base-300 transition-colors flex-1 min-w-[100px]"> <!-- Show selected RefType info -->
<input {#if formData.refRangeType && formData.refRangeType !== 'none'}
type="radio" <div class="mt-3 flex items-center gap-2 p-2 bg-info/10 rounded-lg border border-info/20">
name="refRangeType" <Info class="w-4 h-4 text-info" />
class="radio radio-primary" <span class="text-sm">
value="num" <span class="font-medium">Selected:</span>
checked={formData.refRangeType === 'num'} {refTypeLabels[formData.refRangeType]}
onchange={() => updateRefRangeType('num')}
/>
<div class="flex flex-col">
<span class="label-text font-medium flex items-center gap-1">
Numeric
<Calculator class="w-3 h-3" />
</span> </span>
<span class="text-xs text-gray-500">Range (e.g., 70-100)</span>
</div>
</label>
<label class="label cursor-pointer gap-2 p-3 bg-base-200 rounded-lg hover:bg-base-300 transition-colors flex-1 min-w-[100px]">
<input
type="radio"
name="refRangeType"
class="radio radio-primary"
value="thold"
checked={formData.refRangeType === 'thold'}
onchange={() => updateRefRangeType('thold')}
/>
<div class="flex flex-col">
<span class="label-text font-medium flex items-center gap-1">
Threshold
<Ruler class="w-3 h-3" />
</span>
<span class="text-xs text-gray-500">Limit values</span>
</div>
</label>
<label class="label cursor-pointer gap-2 p-3 bg-base-200 rounded-lg hover:bg-base-300 transition-colors flex-1 min-w-[100px]">
<input
type="radio"
name="refRangeType"
class="radio radio-primary"
value="text"
checked={formData.refRangeType === 'text'}
onchange={() => updateRefRangeType('text')}
/>
<div class="flex flex-col">
<span class="label-text font-medium flex items-center gap-1">
Text
<FileText class="w-3 h-3" />
</span>
<span class="text-xs text-gray-500">Descriptive</span>
</div>
</label>
<label class="label cursor-pointer gap-2 p-3 bg-base-200 rounded-lg hover:bg-base-300 transition-colors flex-1 min-w-[100px]">
<input
type="radio"
name="refRangeType"
class="radio radio-primary"
value="vset"
checked={formData.refRangeType === 'vset'}
onchange={() => updateRefRangeType('vset')}
/>
<div class="flex flex-col">
<span class="label-text font-medium flex items-center gap-1">
Value Set
<Box class="w-3 h-3" />
</span>
<span class="text-xs text-gray-500">Predefined values</span>
</div>
</label>
</div> </div>
{/if}
</div> </div>
<!-- Numeric Reference Ranges --> <!-- Numeric Reference Ranges -->
@ -225,9 +185,9 @@
<NumericRefRange refnum={normalizedRefnum} onupdateRefnum={updateRefnum} /> <NumericRefRange refnum={normalizedRefnum} onupdateRefnum={updateRefnum} />
{/if} {/if}
<!-- Threshold Reference Ranges --> <!-- Threshold Reference Ranges (uses same component as numeric) -->
{#if formData.refRangeType === 'thold'} {#if formData.refRangeType === 'thold'}
<ThresholdRefRange refthold={normalizedRefthold} onupdateRefthold={updateRefthold} /> <NumericRefRange refnum={normalizedRefthold} onupdateRefnum={updateRefthold} />
{/if} {/if}
<!-- Text Reference Ranges --> <!-- Text Reference Ranges -->
@ -235,8 +195,8 @@
<TextRefRange reftxt={normalizedReftxt} onupdateReftxt={updateReftxt} /> <TextRefRange reftxt={normalizedReftxt} onupdateReftxt={updateReftxt} />
{/if} {/if}
<!-- Value Set Reference Ranges --> <!-- Value Set Reference Ranges (uses same component as text) -->
{#if formData.refRangeType === 'vset'} {#if formData.refRangeType === 'vset'}
<ValueSetRefRange refvset={normalizedRefvset} onupdateRefvset={updateRefvset} /> <TextRefRange reftxt={normalizedRefvset} onupdateReftxt={updateRefvset} />
{/if} {/if}
</div> </div>

View File

@ -17,36 +17,24 @@
// Value set options // Value set options
let resultTypeOptions = $state([]); let resultTypeOptions = $state([]);
let refTypeOptions = $state([]);
let loading = $state(true); let loading = $state(true);
onMount(async () => { onMount(async () => {
try { try {
loading = true; loading = true;
const [resultTypeRes, refTypeRes] = await Promise.all([ const resultTypeRes = await fetchValueSetByKey('result_type');
fetchValueSetByKey('result_type'),
fetchValueSetByKey('reference_type')
]);
console.log('result_type response:', resultTypeRes); console.log('result_type response:', resultTypeRes);
console.log('reference_type response:', refTypeRes);
// Handle different response structures // Handle different response structures
const resultItems = resultTypeRes.data?.items || resultTypeRes.data?.ValueSetItems || (Array.isArray(resultTypeRes.data) ? resultTypeRes.data : []) || []; const resultItems = resultTypeRes.data?.items || resultTypeRes.data?.ValueSetItems || (Array.isArray(resultTypeRes.data) ? resultTypeRes.data : []) || [];
const refItems = refTypeRes.data?.items || refTypeRes.data?.ValueSetItems || (Array.isArray(refTypeRes.data) ? refTypeRes.data : []) || [];
resultTypeOptions = resultItems.map(item => ({ resultTypeOptions = resultItems.map(item => ({
value: item.value || item.itemCode || item.ItemCode || item.code || item.Code, 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 label: item.label || item.itemValue || item.ItemValue || item.value || item.Value || item.description || item.Description
})).filter(opt => opt.value); })).filter(opt => opt.value);
refTypeOptions = refItems.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); console.log('resultTypeOptions:', resultTypeOptions);
console.log('refTypeOptions:', refTypeOptions);
} catch (err) { } catch (err) {
console.error('Failed to load value sets:', err); console.error('Failed to load value sets:', err);
} finally { } finally {
@ -57,6 +45,9 @@
function updateField(field, value) { function updateField(field, value) {
onupdateFormData({ ...formData, [field]: value }); onupdateFormData({ ...formData, [field]: value });
} }
// Check if test is calculated type (doesn't have specimen requirements)
const isCalculated = $derived(formData.TestType === 'CALC');
</script> </script>
{#if loading} {#if loading}
@ -72,10 +63,9 @@
<h3 class="font-semibold">Result Configuration</h3> <h3 class="font-semibold">Result Configuration</h3>
</div> </div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4"> <div class="form-control max-w-md">
<div class="form-control">
<label class="label" for="resultType"> <label class="label" for="resultType">
<span class="label-text font-medium">Result Type</span> <span class="label-text text-sm font-medium">Result Type</span>
</label> </label>
<select <select
id="resultType" id="resultType"
@ -89,24 +79,6 @@
{/each} {/each}
</select> </select>
</div> </div>
<div class="form-control">
<label class="label" for="refType">
<span class="label-text font-medium">Reference Type</span>
</label>
<select
id="refType"
class="select select-sm select-bordered w-full"
bind:value={formData.RefType}
onchange={(e) => updateField('RefType', e.target.value)}
>
<option value="">Select reference type...</option>
{#each refTypeOptions as option}
<option value={option.value}>{option.label}</option>
{/each}
</select>
</div>
</div>
</div> </div>
<!-- Units and Precision --> <!-- Units and Precision -->
@ -119,7 +91,7 @@
<div class="grid grid-cols-1 md:grid-cols-4 gap-4"> <div class="grid grid-cols-1 md:grid-cols-4 gap-4">
<div class="form-control"> <div class="form-control">
<label class="label" for="unit1"> <label class="label" for="unit1">
<span class="label-text font-medium">Unit 1</span> <span class="label-text text-sm font-medium">Unit 1</span>
</label> </label>
<input <input
id="unit1" id="unit1"
@ -133,7 +105,7 @@
<div class="form-control"> <div class="form-control">
<label class="label" for="factor"> <label class="label" for="factor">
<span class="label-text font-medium">Factor</span> <span class="label-text text-sm font-medium">Factor</span>
</label> </label>
<input <input
id="factor" id="factor"
@ -148,7 +120,7 @@
<div class="form-control"> <div class="form-control">
<label class="label" for="unit2"> <label class="label" for="unit2">
<span class="label-text font-medium">Unit 2</span> <span class="label-text text-sm font-medium">Unit 2</span>
</label> </label>
<input <input
id="unit2" id="unit2"
@ -162,7 +134,7 @@
<div class="form-control"> <div class="form-control">
<label class="label" for="decimal"> <label class="label" for="decimal">
<span class="label-text font-medium">Decimal Places</span> <span class="label-text text-sm font-medium">Decimal Places</span>
</label> </label>
<input <input
id="decimal" id="decimal"
@ -185,6 +157,7 @@
</div> </div>
<!-- Specimen Requirements --> <!-- Specimen Requirements -->
{#if !isCalculated}
<div class="bg-base-100 rounded-lg border border-base-200 p-4"> <div class="bg-base-100 rounded-lg border border-base-200 p-4">
<div class="flex items-center gap-2 mb-4"> <div class="flex items-center gap-2 mb-4">
<Beaker class="w-5 h-5 text-primary" /> <Beaker class="w-5 h-5 text-primary" />
@ -194,7 +167,7 @@
<div class="grid grid-cols-1 md:grid-cols-2 gap-4"> <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div class="form-control"> <div class="form-control">
<label class="label" for="reqQty"> <label class="label" for="reqQty">
<span class="label-text font-medium">Required Quantity</span> <span class="label-text text-sm font-medium">Required Quantity</span>
</label> </label>
<input <input
id="reqQty" id="reqQty"
@ -209,7 +182,7 @@
<div class="form-control"> <div class="form-control">
<label class="label" for="reqQtyUnit"> <label class="label" for="reqQtyUnit">
<span class="label-text font-medium">Quantity Unit</span> <span class="label-text text-sm font-medium">Quantity Unit</span>
</label> </label>
<input <input
id="reqQtyUnit" id="reqQtyUnit"
@ -222,6 +195,7 @@
</div> </div>
</div> </div>
</div> </div>
{/if}
<!-- Method and TAT --> <!-- Method and TAT -->
<div class="bg-base-100 rounded-lg border border-base-200 p-4"> <div class="bg-base-100 rounded-lg border border-base-200 p-4">
@ -233,7 +207,7 @@
<div class="grid grid-cols-1 md:grid-cols-2 gap-4"> <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div class="form-control"> <div class="form-control">
<label class="label" for="method"> <label class="label" for="method">
<span class="label-text font-medium">Method</span> <span class="label-text text-sm font-medium">Method</span>
</label> </label>
<input <input
id="method" id="method"
@ -247,7 +221,7 @@
<div class="form-control"> <div class="form-control">
<label class="label" for="expectedTAT"> <label class="label" for="expectedTAT">
<span class="label-text font-medium">Expected TAT (minutes)</span> <span class="label-text text-sm font-medium">Expected TAT (minutes)</span>
</label> </label>
<input <input
id="expectedTAT" id="expectedTAT"

View File

@ -113,7 +113,7 @@
<div class="form-control mt-3"> <div class="form-control mt-3">
<span class="label-text text-xs mb-1">Value Set</span> <span class="label-text text-xs mb-1">Value Set</span>
<input type="text" class="input input-sm input-bordered w-full" bind:value={ref.RefTxt} placeholder="e.g., Positive, Negative, Borderline" /> <input type="text" class="input input-sm input-bordered w-full" bind:value={ref.RefTxt} placeholder="e.g., Positive, Negative, Borderline" />
<span class="label-text-alt text-gray-500 mt-1">Comma-separated list of allowed values</span> <span class="label-text-alt text-xs text-gray-500 mt-1">Comma-separated list of allowed values</span>
</div> </div>
<!-- Expandable: Specimen and Criteria --> <!-- Expandable: Specimen and Criteria -->

View File

@ -133,14 +133,14 @@
</a> </a>
<div class="flex-1"> <div class="flex-1">
<div class="flex items-center gap-3"> <div class="flex items-center gap-3">
<h1 class="text-3xl font-bold text-gray-800">ValueSets</h1> <h1 class="text-xl font-bold text-gray-800">ValueSets</h1>
<HelpTooltip <HelpTooltip
text="ValueSets are reusable lookup tables used throughout the system for dropdown menus, form fields, and standardized values. They ensure consistency in data entry and reporting." text="ValueSets are reusable lookup tables used throughout the system for dropdown menus, form fields, and standardized values. They ensure consistency in data entry and reporting."
title="About ValueSets" title="About ValueSets"
position="right" position="right"
/> />
</div> </div>
<p class="text-gray-600">System lookup values and dropdown options</p> <p class="text-sm text-gray-600">System lookup values and dropdown options</p>
</div> </div>
</div> </div>
@ -154,7 +154,7 @@
<input <input
type="text" type="text"
placeholder="Search ValueSets by key (e.g., priority_status, test_category)..." placeholder="Search ValueSets by key (e.g., priority_status, test_category)..."
class="input input-bordered w-full pl-10" class="input input-sm input-bordered w-full pl-10"
bind:value={searchQuery} bind:value={searchQuery}
onkeydown={handleSearchKeydown} onkeydown={handleSearchKeydown}
/> />