From 99d622ad05651360d8003cf5ba2eaa3948edf0e0 Mon Sep 17 00:00:00 2001
From: mahdahar <89adham@gmail.com>
Date: Fri, 20 Feb 2026 13:51:54 +0700
Subject: [PATCH] refactor(tests): consolidate test management modal components
- Consolidate fragmented test modal components into unified TestFormModal.svelte
- Reorganize reference range components into organized tabs (RefNumTab, RefTxtTab)
- Add new tab components: BasicInfoTab, TechDetailsTab, CalcDetailsTab, MappingsTab, GroupMembersTab
- Move test-related type definitions to src/lib/types/test.types.ts for better type organization
- Delete old component files: TestModal.svelte and 10+ sub-components
- Add backup directory for old component preservation
- Update AGENTS.md with coding guidelines and project conventions
- Update tests.js API client with improved structure
- Add documentation for frontend test management architecture
---
AGENTS.md | 193 +-
backup/tests_backup/+page.svelte | 432 +++++
.../tests_backup}/TestModal.svelte | 2 +
.../tests_backup}/referenceRange.js | 0
.../test-modal/BasicInfoForm.svelte | 0
.../test-modal/GroupMembersTab.svelte | 0
.../test-modal/NumericRefRange.svelte | 0
.../test-modal/ReferenceRangeSection.svelte | 103 +-
.../test-modal/TechnicalConfigForm.svelte | 50 +-
.../test-modal/TestTypeSelector.svelte | 0
.../test-modal/TextRefRange.svelte | 0
.../test-modal/ThresholdRefRange.svelte | 0
.../test-modal/ValueSetRefRange.svelte | 0
docs/FRONTEND_TEST_MANAGEMENT_PROMPT.md | 1581 +++++++++++++++++
src/lib/api/tests.js | 382 +++-
src/lib/types/index.ts | 26 +
src/lib/types/test.types.ts | 306 ++++
.../(app)/master-data/tests/+page.svelte | 561 +++---
.../tests/test-modal/TestFormModal.svelte | 364 ++++
.../tests/test-modal/tabs/BasicInfoTab.svelte | 264 +++
.../test-modal/tabs/CalcDetailsTab.svelte | 197 ++
.../test-modal/tabs/GroupMembersTab.svelte | 168 ++
.../tests/test-modal/tabs/MappingsTab.svelte | 250 +++
.../tests/test-modal/tabs/RefNumTab.svelte | 419 +++++
.../tests/test-modal/tabs/RefTxtTab.svelte | 241 +++
.../test-modal/tabs/TechDetailsTab.svelte | 306 ++++
26 files changed, 5365 insertions(+), 480 deletions(-)
create mode 100644 backup/tests_backup/+page.svelte
rename {src/routes/(app)/master-data/tests => backup/tests_backup}/TestModal.svelte (98%)
rename {src/routes/(app)/master-data/tests => backup/tests_backup}/referenceRange.js (100%)
rename {src/routes/(app)/master-data/tests => backup/tests_backup}/test-modal/BasicInfoForm.svelte (100%)
rename {src/routes/(app)/master-data/tests => backup/tests_backup}/test-modal/GroupMembersTab.svelte (100%)
rename {src/routes/(app)/master-data/tests => backup/tests_backup}/test-modal/NumericRefRange.svelte (100%)
rename {src/routes/(app)/master-data/tests => backup/tests_backup}/test-modal/ReferenceRangeSection.svelte (65%)
rename {src/routes/(app)/master-data/tests => backup/tests_backup}/test-modal/TechnicalConfigForm.svelte (81%)
rename {src/routes/(app)/master-data/tests => backup/tests_backup}/test-modal/TestTypeSelector.svelte (100%)
rename {src/routes/(app)/master-data/tests => backup/tests_backup}/test-modal/TextRefRange.svelte (100%)
rename {src/routes/(app)/master-data/tests => backup/tests_backup}/test-modal/ThresholdRefRange.svelte (100%)
rename {src/routes/(app)/master-data/tests => backup/tests_backup}/test-modal/ValueSetRefRange.svelte (100%)
create mode 100644 docs/FRONTEND_TEST_MANAGEMENT_PROMPT.md
create mode 100644 src/lib/types/index.ts
create mode 100644 src/lib/types/test.types.ts
create mode 100644 src/routes/(app)/master-data/tests/test-modal/TestFormModal.svelte
create mode 100644 src/routes/(app)/master-data/tests/test-modal/tabs/BasicInfoTab.svelte
create mode 100644 src/routes/(app)/master-data/tests/test-modal/tabs/CalcDetailsTab.svelte
create mode 100644 src/routes/(app)/master-data/tests/test-modal/tabs/GroupMembersTab.svelte
create mode 100644 src/routes/(app)/master-data/tests/test-modal/tabs/MappingsTab.svelte
create mode 100644 src/routes/(app)/master-data/tests/test-modal/tabs/RefNumTab.svelte
create mode 100644 src/routes/(app)/master-data/tests/test-modal/tabs/RefTxtTab.svelte
create mode 100644 src/routes/(app)/master-data/tests/test-modal/tabs/TechDetailsTab.svelte
diff --git a/AGENTS.md b/AGENTS.md
index 213236f..195984a 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -43,48 +43,46 @@ pnpm run prepare
```svelte
```
### Naming Conventions
-- **Components**: PascalCase (`LoginForm.svelte`)
+- **Components**: PascalCase (`LoginForm.svelte`, `PatientFormModal.svelte`)
- **Files/Routes**: lowercase with hyphens (`+page.svelte`, `user-profile/`)
- **Variables**: camelCase (`isLoading`, `userName`)
- **Constants**: UPPER_SNAKE_CASE (`API_URL`, `STORAGE_KEY`)
- **Stores**: camelCase, descriptive (`auth`, `userStore`)
- **Event handlers**: prefix with `handle` (`handleSubmit`, `handleClick`)
+- **Form state**: `formLoading`, `formError`, `deleteConfirmOpen`
### Imports Order
1. Svelte imports (`svelte`, `$app/*`)
-2. $lib aliases (`$lib/stores/*`, `$lib/api/*`, `$lib/components/*`)
+2. $lib aliases (`$lib/stores/*`, `$lib/api/*`, `$lib/components/*`, `$lib/utils/*`)
3. External libraries (`lucide-svelte`)
4. Relative imports (minimize these, prefer `$lib`)
@@ -93,12 +91,13 @@ pnpm run prepare
```
src/
lib/
- api/ # API client and endpoints
- stores/ # Svelte stores
- components/ # Reusable components
+ api/ # API client and endpoints (per feature)
+ stores/ # Svelte stores (auth, config, valuesets)
+ components/ # Reusable components (DataTable, Modal, Sidebar)
+ utils/ # Utilities (toast, helpers)
assets/ # Static assets
routes/ # SvelteKit routes
- (app)/ # Route groups
+ (app)/ # Route groups (protected)
login/
dashboard/
```
@@ -106,59 +105,163 @@ src/
### API Client Patterns
```javascript
-// src/lib/api/client.js - Base client handles auth
-import { apiClient, get, post, put, del } from '$lib/api/client.js';
+// src/lib/api/client.js - Base client handles auth, 401 redirects
+import { apiClient, get, post, put, patch, del } from '$lib/api/client.js';
-// src/lib/api/feature.js - Feature-specific endpoints
-export async function getItem(id) {
- return get(`/api/items/${id}`);
+// src/lib/api/feature.js - Feature-specific endpoints with JSDoc
+export async function fetchItems(params = {}) {
+ const query = new URLSearchParams(params).toString();
+ return get(query ? `/api/items?${query}` : '/api/items');
}
export async function createItem(data) {
return post('/api/items', data);
}
+
+export async function updateItem(data) {
+ return patch('/api/items', data);
+}
+
+export async function deleteItem(id) {
+ return del(`/api/items/${id}`);
+}
+```
+
+### Store Patterns
+
+```javascript
+// Use writable for stores with localStorage persistence
+import { writable } from 'svelte/store';
+import { browser } from '$app/environment';
+
+function createStore() {
+ const getInitialState = () => {
+ if (!browser) return { data: null };
+ return { data: JSON.parse(localStorage.getItem('key')) };
+ };
+
+ const { subscribe, set, update } = writable(getInitialState());
+
+ return {
+ subscribe,
+ setData: (data) => {
+ if (browser) localStorage.setItem('key', JSON.stringify(data));
+ set({ data });
+ }
+ };
+}
+```
+
+### Component Patterns
+
+```svelte
+
+let { open = $bindable(false), selected = $bindable(null) } = $props();
+
+
+{@render children?.()}
+{@render footer()}
+
+
+
```
### Error Handling
```javascript
-// API errors are thrown with message
try {
- const result = await api.login(username, password);
+ const result = await api.fetchData();
+ toastSuccess('Operation successful');
} catch (err) {
- error = err.message || 'An unexpected error occurred';
- console.error('Login failed:', err);
+ const message = err.message || 'An unexpected error occurred';
+ toastError(message);
+ console.error('Operation failed:', err);
}
```
### Styling with Tailwind & DaisyUI
-- Use Tailwind utility classes
-- DaisyUI components: `btn`, `card`, `alert`, `input`, `navbar`
-- Color scheme: `primary` (emerald), `base-100`, `base-200`
-- Custom colors in `app.css` with `@theme`
+- DaisyUI components: `btn`, `card`, `alert`, `input`, `navbar`, `modal`, `dropdown`, `menu`
+- Color scheme: `primary` (emerald), `secondary` (dark blue), `accent` (royal blue)
+- Custom compact classes: `.compact-y`, `.compact-p`, `.compact-input`, `.compact-btn`, `.compact-card`
+- Size modifiers: `.btn-sm`, `.input-sm`, `.select-sm` for compact forms
### Authentication Patterns
-- Auth state in `$lib/stores/auth.js`
-- Check auth in layout `onMount`
-- Redirect to `/login` if unauthenticated
-- API client auto-redirects on 401
+- Auth state in `$lib/stores/auth.js` using writable store
+- Check `$auth.isAuthenticated` in layout `onMount`
+- Redirect to `/login` if unauthenticated using `goto('/login')`
+- API client auto-redirects on 401 responses
-### Environment Variables
+### Runtime Config Pattern
```javascript
-const API_URL = import.meta.env.VITE_API_URL || '';
+// Use runtime config for API URL that can be changed at runtime
+import { config } from '$lib/stores/config.js';
+function getApiUrl() {
+ return config.getApiUrl() || import.meta.env.VITE_API_URL || '';
+}
```
### LocalStorage
- Only access in browser: check `browser` from `$app/environment`
-- Use descriptive keys: `clqms_username`, `clqms_remember`
+- Use descriptive keys: `clqms_username`, `clqms_remember`, `auth_token`
+
+### Form Handling Patterns
+
+```javascript
+// Form state with validation
+let formLoading = $state(false);
+let formError = $state('');
+let formData = $state({ username: '', password: '' });
+
+function validateForm() {
+ formError = '';
+ if (!formData.username.trim()) {
+ formError = 'Username is required';
+ return false;
+ }
+ return true;
+}
+
+async function handleSubmit() {
+ if (!validateForm()) return;
+ formLoading = true;
+ try {
+ await api.submit(formData);
+ toastSuccess('Success');
+ } catch (err) {
+ formError = err.message;
+ } finally {
+ formLoading = false;
+ }
+}
+```
+
+### Toast/Notification System
+
+```javascript
+import { success, error, info, warning } from '$lib/utils/toast.js';
+
+// Use toast notifications for user feedback
+success('Item created successfully');
+error('Failed to create item');
+info('New message received');
+warning('Action requires confirmation');
+```
## Proxy Configuration
-API requests to `/api` are proxied to `http://localhost:8000` in dev.
+API requests to `/api` are proxied to `http://localhost:8000` in dev. See `vite.config.js`.
## API Documentation
@@ -168,5 +271,7 @@ API Reference (Swagger UI): https://clqms01-api.services-summit.my.id/swagger/
- No ESLint or Prettier configured yet - add if needed
- No test framework configured yet
-- Uses Svelte 5 runes: `$props`, `$state`, `$derived`, `$effect`
+- Uses Svelte 5 runes: `$props`, `$state`, `$derived`, `$effect`, `$bindable`
- SvelteKit file-based routing with `+page.svelte`, `+layout.svelte`
+- Static adapter configured for static export
+- Runtime config allows API URL changes without rebuild
diff --git a/backup/tests_backup/+page.svelte b/backup/tests_backup/+page.svelte
new file mode 100644
index 0000000..66cc4e2
--- /dev/null
+++ b/backup/tests_backup/+page.svelte
@@ -0,0 +1,432 @@
+
+
+
+
+
+
+
Test Definitions
+
Manage laboratory tests, panels, and calculated values
+
+
+
+
+
+
+
+ e.key === 'Enter' && handleSearch()} />
+
+
+
+
+
+
+
+
+
+
+
handleRowClick(idx)}>
+ {#snippet cell({ column, row, index })}
+ {@const isSelected = index === selectedRowIndex} {@const typeConfig = getTestTypeConfig(row.TestType)} {@const isGroup = row.TestType === 'GROUP'} {@const isExpanded = expandedGroups.has(row.TestSiteID)}
+ {#if column.key === 'expand'}{#if isGroup}{:else}{/if}{/if}
+ {#if column.key === 'TestType'}{typeConfig.label}{/if}
+ {#if column.key === 'TestSiteName'}{row.TestSiteName}{#if isGroup && isExpanded && row.testdefgrp}
{#each row.testdefgrp as member}{@const memberConfig = getTestTypeConfig(member.TestType)}
{member.TestSiteCode}{member.TestSiteName}
{/each}
{/if}
{/if}
+ {#if column.key === 'ReferenceRange'}{formatReferenceRange(row)}{/if}
+ {#if column.key === 'actions'}{/if}
+ {#if column.key === 'TestSiteCode'}{row.TestSiteCode}{/if}
+ {/snippet}
+
+ {#if totalPages > 1}
Showing {(currentPage - 1) * perPage + 1} - {Math.min(currentPage * perPage, totalItems)} of {totalItems}
Page {currentPage} of {totalPages}
{/if}
+
+
+
+
+ typeSelectorOpen = false}
+ />
+
+
+ modalOpen = false}
+ onupdateFormData={(data) => formData = data}
+/>
+
+
+
+
Are you sure you want to delete this test?
+
Code: {testToDelete?.TestSiteCode}
Name: {testToDelete?.TestSiteName}
+
This will deactivate the test. Historical data will be preserved.
+
+ {#snippet footer()}{/snippet}
+
\ No newline at end of file
diff --git a/src/routes/(app)/master-data/tests/TestModal.svelte b/backup/tests_backup/TestModal.svelte
similarity index 98%
rename from src/routes/(app)/master-data/tests/TestModal.svelte
rename to backup/tests_backup/TestModal.svelte
index e5ce513..8a8266b 100644
--- a/src/routes/(app)/master-data/tests/TestModal.svelte
+++ b/backup/tests_backup/TestModal.svelte
@@ -124,11 +124,13 @@
{:else if activeTab === 'technical' && canHaveTechnical}
{:else if activeTab === 'refrange' && canHaveRefRange}
{:else if activeTab === 'members' && isGroupTest}
diff --git a/src/routes/(app)/master-data/tests/referenceRange.js b/backup/tests_backup/referenceRange.js
similarity index 100%
rename from src/routes/(app)/master-data/tests/referenceRange.js
rename to backup/tests_backup/referenceRange.js
diff --git a/src/routes/(app)/master-data/tests/test-modal/BasicInfoForm.svelte b/backup/tests_backup/test-modal/BasicInfoForm.svelte
similarity index 100%
rename from src/routes/(app)/master-data/tests/test-modal/BasicInfoForm.svelte
rename to backup/tests_backup/test-modal/BasicInfoForm.svelte
diff --git a/src/routes/(app)/master-data/tests/test-modal/GroupMembersTab.svelte b/backup/tests_backup/test-modal/GroupMembersTab.svelte
similarity index 100%
rename from src/routes/(app)/master-data/tests/test-modal/GroupMembersTab.svelte
rename to backup/tests_backup/test-modal/GroupMembersTab.svelte
diff --git a/src/routes/(app)/master-data/tests/test-modal/NumericRefRange.svelte b/backup/tests_backup/test-modal/NumericRefRange.svelte
similarity index 100%
rename from src/routes/(app)/master-data/tests/test-modal/NumericRefRange.svelte
rename to backup/tests_backup/test-modal/NumericRefRange.svelte
diff --git a/src/routes/(app)/master-data/tests/test-modal/ReferenceRangeSection.svelte b/backup/tests_backup/test-modal/ReferenceRangeSection.svelte
similarity index 65%
rename from src/routes/(app)/master-data/tests/test-modal/ReferenceRangeSection.svelte
rename to backup/tests_backup/test-modal/ReferenceRangeSection.svelte
index 28b77bc..6ad4fd4 100644
--- a/src/routes/(app)/master-data/tests/test-modal/ReferenceRangeSection.svelte
+++ b/backup/tests_backup/test-modal/ReferenceRangeSection.svelte
@@ -8,12 +8,14 @@
/**
* @typedef {Object} Props
* @property {Object} formData - Form data object
+ * @property {string} testType - Test type
* @property {(formData: Object) => void} onupdateFormData - Update handler
*/
/** @type {Props} */
let {
formData = $bindable({}),
+ testType = '',
onupdateFormData = () => {}
} = $props();
@@ -26,13 +28,37 @@
'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' }
- ];
+ // Filter options based on TestType
+ let refTypeOptions = $derived(() => {
+ const baseOptions = [
+ { value: 'none', label: 'None - No reference range' }
+ ];
+
+ if (testType === 'GROUP' || testType === 'TITLE') {
+ return baseOptions;
+ }
+
+ // TEST and PARAM can have all types
+ if (testType === 'TEST' || testType === 'PARAM') {
+ return [
+ ...baseOptions,
+ { 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' }
+ ];
+ }
+
+ // CALC only allows RANGE (num)
+ if (testType === 'CALC') {
+ return [
+ ...baseOptions,
+ { value: 'num', label: 'RANGE - Range' }
+ ];
+ }
+
+ return baseOptions;
+ });
// Ensure all reference range items have defined values, never undefined
function normalizeRefNum(ref) {
@@ -75,6 +101,62 @@
let normalizedReftxt = $derived(allReftxt.filter(ref => ref.RefType !== 'VSET'));
let normalizedRefvset = $derived(allReftxt.filter(ref => ref.RefType === 'VSET'));
+ // Sync refRangeType with ResultType
+ $effect(() => {
+ const resultType = formData.ResultType;
+ const currentRefRangeType = formData.refRangeType;
+
+ if (testType === 'GROUP' || testType === 'TITLE') {
+ // GROUP and TITLE should have no reference range
+ if (currentRefRangeType !== 'none') {
+ onupdateFormData({ ...formData, refRangeType: 'none', RefType: 'NOREF' });
+ }
+ return;
+ }
+
+ // Map ResultType to refRangeType
+ let expectedRefRangeType = 'none';
+ if (resultType === 'NMRIC' || resultType === 'RANGE') {
+ expectedRefRangeType = 'num';
+ } else if (resultType === 'VSET') {
+ expectedRefRangeType = 'vset';
+ } else if (resultType === 'TEXT') {
+ expectedRefRangeType = 'text';
+ }
+
+ // Auto-sync if they don't match and we're not in the middle of editing
+ if (expectedRefRangeType !== 'none' && currentRefRangeType !== expectedRefRangeType) {
+ // Initialize the appropriate reference array if empty
+ const currentRefnum = formData.refnum || [];
+ const currentReftxt = formData.reftxt || [];
+
+ if (expectedRefRangeType === 'num' && normalizedRefnum.length === 0) {
+ onupdateFormData({
+ ...formData,
+ refRangeType: expectedRefRangeType,
+ RefType: 'RANGE',
+ refnum: [...currentRefnum, createNumRef()]
+ });
+ } else if (expectedRefRangeType === 'vset' && normalizedRefvset.length === 0) {
+ onupdateFormData({
+ ...formData,
+ refRangeType: expectedRefRangeType,
+ RefType: 'VSET',
+ reftxt: [...currentReftxt, createVsetRef()]
+ });
+ } else if (expectedRefRangeType === 'text' && normalizedReftxt.length === 0) {
+ onupdateFormData({
+ ...formData,
+ refRangeType: expectedRefRangeType,
+ RefType: 'TEXT',
+ reftxt: [...currentReftxt, createTextRef()]
+ });
+ } else {
+ onupdateFormData({ ...formData, refRangeType: expectedRefRangeType });
+ }
+ }
+ });
+
function updateRefRangeType(type) {
// Initialize arrays if they don't exist
const currentRefnum = formData.refnum || [];
@@ -157,12 +239,13 @@
-
diff --git a/src/routes/(app)/master-data/tests/test-modal/TechnicalConfigForm.svelte b/backup/tests_backup/test-modal/TechnicalConfigForm.svelte
similarity index 81%
rename from src/routes/(app)/master-data/tests/test-modal/TechnicalConfigForm.svelte
rename to backup/tests_backup/test-modal/TechnicalConfigForm.svelte
index 5a34239..e2daf02 100644
--- a/src/routes/(app)/master-data/tests/test-modal/TechnicalConfigForm.svelte
+++ b/backup/tests_backup/test-modal/TechnicalConfigForm.svelte
@@ -6,12 +6,14 @@
/**
* @typedef {Object} Props
* @property {Object} formData - Form data object
+ * @property {string} testType - Test type (TEST, PARAM, CALC, GROUP, TITLE)
* @property {(formData: Object) => void} onupdateFormData - Update handler
*/
/** @type {Props} */
let {
formData = $bindable({}),
+ testType = '',
onupdateFormData = () => {}
} = $props();
@@ -29,10 +31,26 @@
// Handle different response structures
const resultItems = resultTypeRes.data?.items || resultTypeRes.data?.ValueSetItems || (Array.isArray(resultTypeRes.data) ? resultTypeRes.data : []) || [];
- resultTypeOptions = resultItems.map(item => ({
- value: item.value || item.itemCode || item.ItemCode || item.code || item.Code,
- label: item.label || item.itemValue || item.ItemValue || item.value || item.Value || item.description || item.Description
- })).filter(opt => opt.value);
+ resultTypeOptions = resultItems
+ .map(item => ({
+ value: item.value || item.itemCode || item.ItemCode || item.code || item.Code,
+ label: item.label || item.itemValue || item.ItemValue || item.value || item.Value || item.description || item.Description
+ }))
+ .filter(opt => opt.value)
+ .filter(opt => {
+ // Filter ResultType based on TestType
+ if (testType === 'CALC') {
+ // CALC only allows NMRIC
+ return opt.value === 'NMRIC';
+ } else if (testType === 'GROUP' || testType === 'TITLE') {
+ // GROUP and TITLE only allow NORES
+ return opt.value === 'NORES';
+ } else if (testType === 'TEST' || testType === 'PARAM') {
+ // TEST and PARAM allow NMRIC, RANGE, TEXT, VSET
+ return ['NMRIC', 'RANGE', 'TEXT', 'VSET'].includes(opt.value);
+ }
+ return true;
+ });
console.log('resultTypeOptions:', resultTypeOptions);
} catch (err) {
@@ -46,6 +64,23 @@
onupdateFormData({ ...formData, [field]: value });
}
+ function handleResultTypeChange(value) {
+ // Update ResultType and set appropriate RefType
+ let newRefType = formData.RefType;
+
+ if (value === 'NMRIC' || value === 'RANGE') {
+ newRefType = 'RANGE';
+ } else if (value === 'VSET') {
+ newRefType = 'VSET';
+ } else if (value === 'TEXT') {
+ newRefType = 'TEXT';
+ } else if (value === 'NORES') {
+ newRefType = 'NOREF';
+ }
+
+ onupdateFormData({ ...formData, ResultType: value, RefType: newRefType });
+ }
+
// Check if test is calculated type (doesn't have specimen requirements)
const isCalculated = $derived(formData.TestType === 'CALC');
@@ -71,9 +106,12 @@
id="resultType"
class="select select-sm select-bordered w-full"
bind:value={formData.ResultType}
- onchange={(e) => updateField('ResultType', e.target.value)}
+ onchange={(e) => handleResultTypeChange(e.target.value)}
+ disabled={testType === 'GROUP' || testType === 'TITLE'}
>
-
+ {#if testType !== 'GROUP' && testType !== 'TITLE'}
+
+ {/if}
{#each resultTypeOptions as option}
{/each}
diff --git a/src/routes/(app)/master-data/tests/test-modal/TestTypeSelector.svelte b/backup/tests_backup/test-modal/TestTypeSelector.svelte
similarity index 100%
rename from src/routes/(app)/master-data/tests/test-modal/TestTypeSelector.svelte
rename to backup/tests_backup/test-modal/TestTypeSelector.svelte
diff --git a/src/routes/(app)/master-data/tests/test-modal/TextRefRange.svelte b/backup/tests_backup/test-modal/TextRefRange.svelte
similarity index 100%
rename from src/routes/(app)/master-data/tests/test-modal/TextRefRange.svelte
rename to backup/tests_backup/test-modal/TextRefRange.svelte
diff --git a/src/routes/(app)/master-data/tests/test-modal/ThresholdRefRange.svelte b/backup/tests_backup/test-modal/ThresholdRefRange.svelte
similarity index 100%
rename from src/routes/(app)/master-data/tests/test-modal/ThresholdRefRange.svelte
rename to backup/tests_backup/test-modal/ThresholdRefRange.svelte
diff --git a/src/routes/(app)/master-data/tests/test-modal/ValueSetRefRange.svelte b/backup/tests_backup/test-modal/ValueSetRefRange.svelte
similarity index 100%
rename from src/routes/(app)/master-data/tests/test-modal/ValueSetRefRange.svelte
rename to backup/tests_backup/test-modal/ValueSetRefRange.svelte
diff --git a/docs/FRONTEND_TEST_MANAGEMENT_PROMPT.md b/docs/FRONTEND_TEST_MANAGEMENT_PROMPT.md
new file mode 100644
index 0000000..4f51eb0
--- /dev/null
+++ b/docs/FRONTEND_TEST_MANAGEMENT_PROMPT.md
@@ -0,0 +1,1581 @@
+# CLQMS Master Data - Test Management Frontend Development Prompt
+
+## š Project Overview
+
+Build a modern, responsive Svelte 5 frontend for CLQMS (Clinical Laboratory Quality Management System) Test Management module. The frontend will consume the existing REST API backend (`/api/tests`) and provide a comprehensive interface for managing laboratory test definitions across all test types.
+
+---
+
+## šÆ Objective
+
+Create a user-friendly, type-safe frontend application that enables laboratory administrators to:
+- Browse and search all test definitions with filtering
+- Create new tests of any type (TEST, PARAM, CALC, GROUP, TITLE)
+- Edit existing tests with type-specific configurations
+- Manage reference ranges (numeric and text-based)
+- Configure test mappings for external systems
+- Organize tests into groups and panels
+- Manage calculated test formulas
+
+---
+
+## š ļø Technology Stack (Svelte 5)
+
+### Core Requirements
+- **Framework**: Svelte 5 with runes (`$state`, `$derived`, `$effect`)
+- **Meta-Framework**: SvelteKit (for routing and server-side features)
+- **Language**: TypeScript (strict mode enabled)
+- **Styling**: Tailwind CSS
+- **UI Components**: Skeleton UI (or Melt UI)
+- **Forms**: Headless form components with validation
+- **HTTP Client**: Axios (with request/response interceptors)
+- **State Management**: Svelte 5 runes (no external store library required)
+- **Build Tool**: Vite (comes with SvelteKit)
+
+### Recommended Dependencies
+```json
+{
+ "dependencies": {
+ "svelte": "^5.0.0",
+ "@sveltejs/kit": "^2.0.0",
+ "axios": "^1.6.0",
+ "zod": "^3.22.0",
+ "date-fns": "^3.0.0"
+ },
+ "devDependencies": {
+ "skeleton": "^2.8.0",
+ "@skeletonlabs/tw-plugin": "^0.3.0",
+ "tailwindcss": "^3.4.0",
+ "autoprefixer": "^10.4.0",
+ "postcss": "^8.4.0",
+ "typescript": "^5.3.0"
+ }
+}
+```
+
+---
+
+## š Project Structure
+
+```
+src/
+āāā lib/
+ā āāā components/
+ā ā āāā test/
+ā ā ā āāā TestList.svelte # Main test listing page
+ā ā ā āāā TestForm.svelte # Main form container with tabs
+ā ā ā āāā TestCard.svelte # Single test card/row
+ā ā ā āāā TestFilterPanel.svelte # Search/filter panel
+ā ā ā āāā SidebarTabs.svelte # Left navigation tabs
+ā ā ā āāā tabs/
+ā ā ā ā āāā BasicInfoTab.svelte # Basic test info
+ā ā ā ā āāā TechDetailsTab.svelte # Technical specifications
+ā ā ā ā āāā CalcDetailsTab.svelte # Calculated test formula
+ā ā ā ā āāā GroupMembersTab.svelte # Group member management
+ā ā ā ā āāā MappingsTab.svelte # System mappings
+ā ā ā ā āāā RefNumTab.svelte # Numeric reference ranges
+ā ā ā ā āāā RefTxtTab.svelte # Text reference ranges
+ā ā ā āāā modals/
+ā ā ā āāā RefNumModal.svelte # Edit reference range modal
+ā ā ā āāā RefTxtModal.svelte # Edit text reference modal
+ā ā ā āāā MappingModal.svelte # Edit mapping modal
+ā ā ā āāā MemberModal.svelte # Add group member modal
+ā ā āāā ui/ # Reusable UI components
+ā ā āāā Button.svelte
+ā ā āāā Input.svelte
+ā ā āāā Select.svelte
+ā ā āāā Checkbox.svelte
+ā ā āāā Table.svelte
+ā ā āāā Modal.svelte
+ā ā āāā Badge.svelte
+ā ā āāā Tabs.svelte
+ā ā āāā Alert.svelte
+ā ā āāā Spinner.svelte
+ā āāā stores/
+ā ā āāā testStore.ts # Test form state with runes
+ā ā āāā valueSetStore.ts # ValueSet/dropdown data
+ā ā āāā authStore.ts # Authentication state
+ā ā āāā uiStore.ts # UI state (modals, tabs)
+ā āāā services/
+ā ā āāā api.ts # Axios instance with interceptors
+ā ā āāā testService.ts # Test API operations
+ā ā āāā valueSetService.ts # ValueSet API calls
+ā ā āāā validationService.ts # Frontend validation logic
+ā āāā types/
+ā ā āāā test.types.ts # All test-related types
+ā ā āāā api.types.ts # API response/request types
+ā ā āāā valueset.types.ts # ValueSet types
+ā ā āāā index.ts # Type exports
+ā āāā utils/
+ā āāā validation.ts # Validation helpers
+ā āāā format.ts # Formatters (dates, numbers)
+ā āāā constants.ts # App constants
+ā āāā helpers.ts # Utility functions
+āāā routes/
+ā āāā +layout.svelte # Root layout with nav
+ā āāā +page.svelte # Landing page
+ā āāā tests/
+ā ā āāā +page.svelte # Test list page
+ā ā āāā [id]/
+ā ā āāā +page.svelte # Test detail/edit page
+ā āāā login/
+ā āāā +page.svelte # Login page
+āāā app.html # HTML template
+āāā app.css # Global styles
+```
+
+---
+
+## š API Integration
+
+### Base Configuration
+
+**API Base URL**: `http://localhost:8080/api` (configurable via environment variable)
+
+**Authentication**: JWT token via HTTP header
+```
+Authorization: Bearer {token}
+```
+
+### Endpoints
+
+#### 1. List Tests
+```
+GET /api/tests
+Query Parameters:
+ - SiteID (optional): Filter by site
+ - TestType (optional): Filter by test type (TEST, PARAM, CALC, GROUP, TITLE)
+ - VisibleScr (optional): Filter by screen visibility (0/1)
+ - VisibleRpt (optional): Filter by report visibility (0/1)
+ - TestSiteName (optional): Search by test name (partial match)
+```
+
+**Response**:
+```typescript
+{
+ status: "success";
+ message: string;
+ data: TestSummary[];
+}
+
+interface TestSummary {
+ TestSiteID: number;
+ TestSiteCode: string;
+ TestSiteName: string;
+ TestType: string;
+ TestTypeLabel: string;
+ SeqScr: number;
+ SeqRpt: number;
+ VisibleScr: number;
+ VisibleRpt: number;
+ CountStat: number;
+ StartDate: string;
+ DisciplineID?: number;
+ DepartmentID?: number;
+ DisciplineName?: string;
+ DepartmentName?: string;
+}
+```
+
+#### 2. Get Single Test
+```
+GET /api/tests/:id
+```
+
+**Response**:
+```typescript
+{
+ status: "success";
+ message: string;
+ data: TestDetail;
+}
+
+interface TestDetail {
+ // Base fields
+ TestSiteID: number;
+ TestSiteCode: string;
+ TestSiteName: string;
+ TestType: string;
+ TestTypeLabel: string;
+ Description?: string;
+ SiteID: number;
+ SeqScr: number;
+ SeqRpt: number;
+ VisibleScr: number;
+ VisibleRpt: number;
+ CountStat: number;
+ StartDate: string;
+ EndDate?: string;
+
+ // Technical details (TEST/PARAM/CALC)
+ DisciplineID?: number;
+ DepartmentID?: number;
+ DisciplineName?: string;
+ DepartmentName?: string;
+ ResultType?: string;
+ RefType?: string;
+ VSet?: string;
+ Unit1?: string;
+ Factor?: number;
+ Unit2?: string;
+ Decimal?: number;
+ ReqQty?: number;
+ ReqQtyUnit?: string;
+ CollReq?: string;
+ Method?: string;
+ ExpectedTAT?: number;
+
+ // Nested data based on TestType
+ testdefcal?: Calculation[]; // For CALC type
+ testdefgrp?: GroupMember[]; // For GROUP type
+ testmap?: TestMapping[]; // For all types
+ testdeftech?: TechDetail[]; // For TEST/PARAM
+ refnum?: RefNumRange[]; // For TEST/PARAM (numeric ref)
+ reftxt?: RefTxtRange[]; // For TEST/PARAM (text ref)
+}
+
+interface RefNumRange {
+ RefNumID: number;
+ NumRefType: string; // REF, CRTC, VAL, RERUN
+ NumRefTypeLabel: string;
+ RangeType: string; // RANGE, THOLD
+ RangeTypeLabel: string;
+ Sex: string; // 0=All, 1=Female, 2=Male
+ SexLabel: string;
+ AgeStart: number;
+ AgeEnd: number;
+ LowSign?: string; // =, <, <=
+ LowSignLabel?: string;
+ Low?: number;
+ HighSign?: string; // =, >, >=
+ HighSignLabel?: string;
+ High?: number;
+ Flag?: string; // H, L, A, etc.
+ Interpretation?: string;
+}
+
+interface RefTxtRange {
+ RefTxtID: number;
+ TxtRefType: string; // Normal, Abnormal, Critical
+ TxtRefTypeLabel: string;
+ Sex: string;
+ SexLabel: string;
+ AgeStart: number;
+ AgeEnd: number;
+ RefTxt: string;
+ Flag?: string;
+}
+```
+
+#### 3. Create Test
+```
+POST /api/tests
+Content-Type: application/json
+Body: CreateTestPayload
+```
+
+**Request**:
+```typescript
+interface CreateTestPayload {
+ SiteID: number;
+ TestSiteCode: string;
+ TestSiteName: string;
+ TestType: 'TEST' | 'PARAM' | 'CALC' | 'GROUP' | 'TITLE';
+ Description?: string;
+ SeqScr?: number;
+ SeqRpt?: number;
+ VisibleScr?: number;
+ VisibleRpt?: number;
+ CountStat?: number;
+ StartDate?: string;
+
+ // Nested details (based on TestType)
+ details?: {
+ // Technical (TEST/PARAM/CALC)
+ DisciplineID?: number;
+ DepartmentID?: number;
+ ResultType?: string;
+ RefType?: string;
+ VSet?: string;
+ Unit1?: string;
+ Factor?: number;
+ Unit2?: string;
+ Decimal?: number;
+ ReqQty?: number;
+ ReqQtyUnit?: string;
+ CollReq?: string;
+ Method?: string;
+ ExpectedTAT?: number;
+
+ // CALC only
+ FormulaInput?: string;
+ FormulaCode?: string;
+
+ // GROUP only
+ members?: number[]; // Array of TestSiteIDs
+ };
+
+ // Reference ranges (TEST/PARAM)
+ refnum?: Omit
[];
+ reftxt?: Omit[];
+
+ // Mappings (all types)
+ testmap?: TestMapping[];
+}
+```
+
+**Response**:
+```typescript
+{
+ status: "created";
+ message: "Test created successfully";
+ data: { TestSiteId: number };
+}
+```
+
+#### 4. Update Test
+```
+PATCH /api/tests
+Content-Type: application/json
+Body: CreateTestPayload & { TestSiteID: number }
+```
+
+**Response**:
+```typescript
+{
+ status: "success";
+ message: "Test updated successfully";
+ data: { TestSiteId: number };
+}
+```
+
+#### 5. Delete Test (Soft Delete)
+```
+DELETE /api/tests
+Content-Type: application/json
+Body: { TestSiteID: number }
+```
+
+**Response**:
+```typescript
+{
+ status: "success";
+ message: "Test disabled successfully";
+ data: { TestSiteId: number; EndDate: string };
+}
+```
+
+---
+
+## šØ UI/UX Design Specifications
+
+### Layout Architecture
+
+**Page Layout**: Fixed sidebar + scrollable content area
+
+```
+āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
+ā Header: CLQMS Test Management [User] [Logout] ā
+āāāāāāāāāāāāāā¬āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¤
+ā ā ā
+ā Sidebar ā Main Content Area ā
+ā (Left) ā ā
+ā ā ā
+ā Tab 1 ā āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā ā
+ā Tab 2 ā ā ā ā
+ā Tab 3 ā ā Dynamic Content ā ā
+ā ... ā ā ā ā
+ā ā ā ā ā
+ā ā ā ā ā
+ā ā āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā ā
+ā ā ā
+ā ā [Save] [Cancel] [Delete] ā
+ā ā ā
+āāāāāāāāāāāāāā“āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
+```
+
+### Test List Page
+
+**Components**:
+1. **Filter Panel** (top of page):
+ - Site dropdown (multi-select)
+ - Test Type dropdown (checkboxes)
+ - Visibility toggles (Screen/Report)
+ - Search input (debounced, 300ms)
+ - Clear filters button
+
+2. **Test Table/Card Grid**:
+ - Columns: Code, Name, Type, Discipline, Department, SeqScr, SeqRpt, Visibility, Actions
+ - Sortable headers (Code, Name, SeqScr, SeqRpt)
+ - Row actions: View, Edit, Delete (with confirmation)
+ - Row hover effect
+ - Test type badge with color coding
+
+3. **Pagination**:
+ - Show 20 items per page
+ - Page navigation buttons
+ - Page size selector (10, 20, 50, 100)
+ - Total count display
+
+**Table Design**:
+```
+āāāāāāāāāāā¬āāāāāāāāāāāāāāāāāāāāā¬āāāāāāāā¬āāāāāāāāāāāāā¬āāāāāāāāā¬āāāāāāāāā¬āāāāāāāāā¬āāāāāāāāāāāāā¬āāāāāāāāāā
+ā Code ā Name ā Type ā Discipline ā Dept ā ScrVis ā RptVis ā Actions ā ā
+āāāāāāāāāāā¼āāāāāāāāāāāāāāāāāāāāā¼āāāāāāāā¼āāāāāāāāāāāāā¼āāāāāāāāā¼āāāāāāāāā¼āāāāāāāāā¼āāāāāāāāāāāāā¼āāāāāāāāāā¤
+ā CBC ā Complete Blood ā TEST ā Hematology ā Hema ā ā ā ā ā šļø āļø šļø ā ā
+ā ā Count ā ā ā ā ā ā ā ā
+ā HGB ā Hemoglobin ā PARAM ā Hematology ā Hema ā ā ā ā ā šļø āļø šļø ā ā
+ā CALC_A1Cā A1C Calculated ā CALC ā Chemistry ā Chem ā ā ā ā ā šļø āļø šļø ā ā
+ā CMP_GRP ā Comprehensive ā GROUP ā - ā - ā ā ā ā ā šļø āļø šļø ā ā
+ā ā Panel ā ā ā ā ā ā ā ā
+ā HEADER1 ā Chemistry Results ā TITLE ā - ā - ā ā ā ā ā šļø āļø šļø ā ā
+āāāāāāāāāāā“āāāāāāāāāāāāāāāāāāāāā“āāāāāāāā“āāāāāāāāāāāāā“āāāāāāāāā“āāāāāāāāā“āāāāāāāāā“āāāāāāāāāāāāā“āāāāāāāāāā
+```
+
+### Test Form Page
+
+**Left Sidebar Tabs** (Navigation):
+```
+āāāāāāāāāāāāāāāā
+ā Basic Info ā
+āāāāāāāāāāāāāāāā¤
+ā Tech Details ā
+āāāāāāāāāāāāāāāā¤
+ā Calculations ā (CALC only)
+āāāāāāāāāāāāāāāā¤
+ā Group Memb ā (GROUP only)
+āāāāāāāāāāāāāāāā¤
+ā Mappings ā
+āāāāāāāāāāāāāāāā¤
+ā Ref Num ā (TEST/PARAM/CALC)
+āāāāāāāāāāāāāāāā¤
+ā Ref Txt ā (TEST/PARAM)
+āāāāāāāāāāāāāāāā
+```
+
+**Tab Visibility Rules**:
+| Tab | TEST | PARAM | CALC | GROUP | TITLE |
+|-----|------|-------|------|-------|-------|
+| Basic Info | ā
| ā
| ā
| ā
| ā
|
+| Tech Details | ā
| ā
| ā | ā | ā |
+| Calculations | ā | ā | ā
| ā | ā |
+| Group Members | ā | ā | ā | ā
| ā |
+| Mappings | ā
| ā
| ā
| ā
| ā
|
+| Ref Num | ā
| ā
| ā
| ā | ā |
+| Ref Txt | ā
| ā
| ā | ā | ā |
+
+**Active Tab Styling**:
+- Left border accent color (primary theme color)
+- Light background highlight
+- Bold text
+- Icon indicator
+
+### Tab Content Specifications
+
+#### 1. Basic Info Tab
+
+**Form Layout** (Two-column grid):
+```
+āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
+ā Test Code: [CBC_______] *Required ā
+ā Test Name: [Complete Blood Count________] *Required ā
+ā Test Type: [TEST ā¼] (dropdown) ā
+ā Description: [Standard hematology test_______________] ā
+ā ā
+ā Site: [Main Lab ā¼] ā
+ā ā
+ā Screen Seq: [1___] Report Seq: [1___] ā
+ā ā
+ā [ā] Visible on Screen [ā] Visible on Report ā
+ā ā
+ā [ā] Count in Statistics ā
+ā ā
+ā Start Date: [2024-01-01_______] ā
+āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
+```
+
+**Fields**:
+- TestSiteCode (required, 3-6 chars, uppercase, unique validation)
+- TestSiteName (required, 3-255 chars)
+- TestType (required, dropdown: TEST, PARAM, CALC, GROUP, TITLE)
+- Description (optional, textarea, max 500 chars)
+- SiteID (required, dropdown from sites API)
+- SeqScr (optional, number, default 0)
+- SeqRpt (optional, number, default 0)
+- VisibleScr (checkbox, default true)
+- VisibleRpt (checkbox, default true)
+- CountStat (checkbox, default true)
+- StartDate (optional, datetime, default current)
+
+**Dynamic Behavior**:
+- When TestType changes ā Show/hide relevant tabs
+- When TestType = CALC/PARAM/TEST ā Auto-populate defaults
+- TestType change triggers confirmation if form has unsaved changes
+
+#### 2. Tech Details Tab
+
+**Form Layout** (Three sections):
+
+**Section 1: Categorization**
+```
+āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
+ā Discipline: [Hematology ā¼] ā
+ā Department: [CBC Dept ā¼] ā
+āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
+```
+
+**Section 2: Result Configuration**
+```
+āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
+ā Result Type: [Numeric ā¼] (dynamic based on TestType) ā
+ā Ref Type: [Range ā¼] (dynamic based on ResultType) ā
+ā Value Set: [____________] (if ResultType = VSET) ā
+āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
+```
+
+**Section 3: Units & Conversion**
+```
+āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
+ā Unit 1: [g/dL ā¼] ā
+ā Factor: [1.0__] (conversion factor) ā
+ā Unit 2: [g/L ā¼] ā
+ā Decimal: [2__] (decimal places) ā
+āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
+```
+
+**Section 4: Sample Requirements**
+```
+āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
+ā Required Qty: [5.0__] ā
+ā Qty Unit: [mL ā¼] ā
+ā Collection Req: [Fasting required_______________] ā
+āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
+```
+
+**Section 5: Method & TAT**
+```
+āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
+ā Method: [Automated Analyzer_______________] ā
+ā Expected TAT: [60__] (minutes) ā
+āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
+```
+
+**Fields**:
+- DisciplineID (dropdown, optional)
+- DepartmentID (dropdown, optional)
+- ResultType (dropdown, dynamic options based on TestType)
+- RefType (dropdown, dynamic options based on ResultType)
+- VSet (text input, shown only if ResultType = VSET)
+- Unit1 (dropdown from units valueset)
+- Factor (number, optional)
+- Unit2 (dropdown from units valueset)
+- Decimal (number, default 2, min 0, max 6)
+- ReqQty (number, optional)
+- ReqQtyUnit (dropdown)
+- CollReq (textarea, optional)
+- Method (text input, optional)
+- ExpectedTAT (number, optional)
+
+**Dynamic Dropdown Logic**:
+```typescript
+// TestType ā ResultType mapping
+const getResultTypeOptions = (testType: string) => {
+ switch (testType) {
+ case 'CALC': return ['NMRIC'];
+ case 'GROUP':
+ case 'TITLE': return ['NORES'];
+ default: return ['NMRIC', 'RANGE', 'TEXT', 'VSET'];
+ }
+};
+
+// ResultType ā RefType mapping
+const getRefTypeOptions = (resultType: string) => {
+ switch (resultType) {
+ case 'NMRIC':
+ case 'RANGE': return ['RANGE', 'THOLD'];
+ case 'VSET': return ['VSET'];
+ case 'TEXT': return ['TEXT'];
+ case 'NORES': return ['NOREF'];
+ default: return [];
+ }
+};
+```
+
+#### 3. Calculations Tab (CALC only)
+
+**Form Layout**:
+```
+āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
+ā Formula Input: [HGB + MCV + MCHC________] ā
+ā ā
+ā Formula Code: [{HGB} + {MCV} + {MCHC}_____________] ā
+ā ā
+ā Discipline: [Hematology ā¼] ā
+ā Department: [CBC Dept ā¼] ā
+ā ā
+ā Method: [Calculated from components_______] ā
+ā ā
+ā Unit 1: [g/dL ā¼] ā
+ā Factor: [1.0__] ā
+ā Unit 2: [g/L ā¼] ā
+ā Decimal: [2__] ā
+ā ā
+ā Ref Type: [Range ā¼] ā
+āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
+```
+
+**Fields**:
+- FormulaInput (text input, description of calculation)
+- FormulaCode (text input, actual formula with placeholders like {A}, {B})
+- DisciplineID (dropdown)
+- DepartmentID (dropdown)
+- Method (text input)
+- Unit1, Factor, Unit2, Decimal (same as Tech Details)
+- RefType (dropdown: RANGE, THOLD)
+
+**Validation**:
+- FormulaCode must contain valid syntax
+- FormulaCode must reference valid test codes
+- Test codes in formula must exist in system
+
+#### 4. Group Members Tab (GROUP only)
+
+**Form Layout**:
+```
+āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
+ā Group: CBC - Complete Blood Count ā
+ā ā
+ā Current Members: ā
+ā āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā ā
+ā ā Code ā Name ā Type ā Seq ā Actions ā ā
+ā āāāāāāāā¼āāāāāāāāāāāāāāāāāā¼āāāāāāāāā¼āāāāāā¼āāāāāāāāāāāāāāā⤠ā
+ā ā HGB ā Hemoglobin ā PARAM ā 1 ā [ā] [ā] [ā] ā ā
+ā ā RBC ā Red Blood Cells ā TEST ā 2 ā [ā] [ā] [ā] ā ā
+ā ā WBC ā White Blood Cellā TEST ā 3 ā [ā] [ā] [ā] ā ā
+ā āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā ā
+ā ā
+ā Add Member: [HGB ā¼] [Add Member +] ā
+ā ā
+ā Available Tests: (searchable dropdown) ā
+ā - HGB - Hemoglobin ā
+ā - RBC - Red Blood Cells ā
+ā - WBC - White Blood Cells ā
+āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
+```
+
+**Features**:
+- List current members with Code, Name, Type
+- Reorder members (drag-and-drop or āā buttons)
+- Remove member button (with confirmation)
+- Add member dropdown (searchable, excludes current group and members)
+- Prevent circular references (group cannot contain itself)
+- Prevent duplicate members
+
+**Member Selection**:
+- Dropdown with search
+- Group by TestType (TEST, PARAM, CALC)
+- Show TestSiteCode - TestSiteName format
+- Filter out already added members
+- Filter out current group itself
+
+#### 5. Mappings Tab (All test types)
+
+**Form Layout**:
+```
+āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
+ā Test: CBC - Complete Blood Count ā
+ā ā
+ā Current Mappings: ā
+ā āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā ā
+ā ā Host ā Host Code ā Client ā Client Code ā Actions ā ā
+ā āāāāāāāā¼āāāāāāāāāāāā¼āāāāāāāāā¼āāāāāāāāāāāāā¼āāāāāāāāāāā⤠ā
+ā ā HIS ā CBC ā WST-1 ā CBC01 ā [āļø] [ā] ā ā
+ā ā SITE ā CBC ā INST-1 ā CBC ā [āļø] [ā] ā ā
+ā āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā ā
+ā ā
+ā [Add Mapping +] ā
+āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
+```
+
+**Add/Edit Mapping Modal**:
+```
+āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
+ā Add/Edit Mapping ā
+ā ā
+ā Host System: ā
+ā āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā ā
+ā ā Type: [HIS ā¼] ā ā
+ā ā ID: [1____] ā ā
+ā ā Data Src: [DB____] ā ā
+ā ā Test Code:[CBC____] ā ā
+ā ā Test Name:[Complete Blood Count___________] ā ā
+ā āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā ā
+ā ā
+ā Client System: ā
+ā āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā ā
+ā ā Type: [WST ā¼] ā ā
+ā ā ID: [1____] ā ā
+ā ā Data Src: [API____] ā ā
+ā ā Container: [Tube1 ā¼] ā ā
+ā ā Test Code: [CBC01____] ā ā
+ā ā Test Name: [CBC_____________] ā ā
+ā āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā ā
+ā ā
+ā [Save] [Cancel] ā
+āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
+```
+
+**Fields**:
+- HostType: dropdown (HIS, SITE, WST, INST)
+- HostID: text/number input
+- HostDataSource: text input
+- HostTestCode: text input
+- HostTestName: text input
+- ClientType: dropdown (HIS, SITE, WST, INST)
+- ClientID: text/number input
+- ClientDataSource: text input
+- ConDefID: dropdown (container definitions)
+- ClientTestCode: text input
+- ClientTestName: text input
+
+**Validation**:
+- At least one of Host or Client must be specified
+- Test codes must be unique per Host/Client combination
+
+#### 6. Ref Num Tab (Numeric Reference Ranges)
+
+**Form Layout**:
+```
+āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
+ā Test: CBC - Complete Blood Count ā
+ā Numeric Reference Ranges ā
+ā ā
+ā āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā ā
+ā āType āRange āSex ā Age ā Low āHigh āFlag ā ā
+ā ā ā ā ā āBound āBound ā ā ā
+ā āāāāāāāāā¼āāāāāāā¼āāāāā¼āāāāāāāā¼āāāāāāāāāāā¼āāāāāāāāāā¼āāāāā⤠ā
+ā āRef āRANGE āAll ā0-150 ā[ā„ 4.0] ā[< 5.5] ā N ā ā
+ā ā ā ā ā ā ā ā ā ā
+ā āCrtc āTHOLD āAll ā0-150 ā[< 3.5] ā[> 6.0] ā H/L ā ā
+ā ā ā ā ā ā ā ā ā ā
+ā āRef āRANGE āF ā18-150 ā[ā„ 4.5] ā[< 5.0] ā N ā ā
+ā ā ā ā ā ā ā ā ā ā
+ā āCrtc āTHOLD āM ā18-150 ā[< 3.8] ā[> 5.8] ā H/L ā ā
+ā ā ā ā ā ā ā ā ā ā
+ā āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā ā
+ā ā
+ā [Add Range] [Delete Selected] [Copy from Similar Test] ā
+ā ā
+ā Selected Ranges: 0 ā
+āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
+```
+
+**Add/Edit Reference Range Modal**:
+```
+āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
+ā Numeric Reference Range ā
+ā ā
+ā Reference Type: [Reference ā¼] ā
+ā (Reference, Critical, Validation, Rerun) ā
+ā ā
+ā Range Type: [Range ā¼] ā
+ā (Range, Threshold) ā
+ā ā
+ā Sex: [All ā¼] ā
+ā (All, Female, Male) ā
+ā ā
+ā Age Start: [0__] Age End: [150__] ā
+ā ā
+ā Low Bound: ā
+ā āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā ā
+ā ā Sign: [ā„ ā¼] Value: [4.0__] ā ā
+ā āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā ā
+ā ā
+ā High Bound: ā
+ā āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā ā
+ā ā Sign: [< ā¼] Value: [5.5__] ā ā
+ā āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā ā
+ā ā
+ā Flag: [H/L/A/N___] (High/Low/Abnormal/Normal) ā
+ā ā
+ā Interpretation:[Normal range for hemoglobin_______________] ā
+ā ā
+ā [Save] [Cancel] ā
+āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
+```
+
+**Fields**:
+- NumRefType: dropdown (REF, CRTC, VAL, RERUN)
+- RangeType: dropdown (RANGE, THOLD)
+- Sex: dropdown (0=All, 1=Female, 2=Male)
+- AgeStart: number input (min 0, max 150)
+- AgeEnd: number input (min 0, max 150)
+- LowSign: dropdown (=, <, <=)
+- Low: number input (optional)
+- HighSign: dropdown (=, >, >=)
+- High: number input (optional)
+- Flag: text input (single char: H, L, A, N)
+- Interpretation: textarea (optional)
+
+**Validation**:
+- AgeStart must be less than AgeEnd
+- If both Low and High are present: Low must be less than High
+- LowSign must be appropriate for Low value (e.g., if Low = 4.0, LowSign should be >=)
+- HighSign must be appropriate for High value (e.g., if High = 5.5, HighSign should be <=)
+
+**Reference Range Logic**:
+- Reference (REF): Normal ranges for reporting
+- Critical (CRTC): Critical values requiring immediate notification
+- Validation (VAL): Validation checks for result entry
+- Rerun (RERUN): Conditions triggering automatic rerun
+
+**Range Type**:
+- RANGE: Standard range (Low to High)
+- THOLD: Threshold (single value with comparison)
+
+#### 7. Ref Txt Tab (Text Reference Ranges)
+
+**Form Layout**:
+```
+āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
+ā Test: URINE - Urinalysis ā
+ā Text Reference Ranges ā
+ā ā
+ā āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā ā
+ā āType āSex ā Age ā Reference Text ā Flag ā ā
+ā āāāāāāāāā¼āāāāā¼āāāāāāāā¼āāāāāāāāāāāāāāāāāā¼āāāāāāāāāāāāā⤠ā
+ā āNormal āAll ā0-150 āClear ā N ā ā
+ā ā ā ā ā ā ā ā
+ā āAbnml āAll ā0-150 āCloudy ā A ā ā
+ā ā ā ā ā ā ā ā
+ā āCrtc āAll ā0-150 āBloody ā C ā ā
+ā ā ā ā ā ā ā ā
+ā āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā ā
+ā ā
+ā [Add Range] [Delete Selected] [Copy from Similar Test] ā
+āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
+```
+
+**Add/Edit Text Reference Modal**:
+```
+āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
+ā Text Reference Range ā
+ā ā
+ā Reference Type: [Normal ā¼] ā
+ā (Normal, Abnormal, Critical) ā
+ā ā
+ā Sex: [All ā¼] ā
+ā (All, Female, Male) ā
+ā ā
+ā Age Start: [0__] Age End: [150__] ā
+ā ā
+ā Reference Text: [Clear_______________] ā
+ā ā
+ā Flag: [N/A/C___] (Normal/Abnormal/Critical) ā
+ā ā
+ā [Save] [Cancel] ā
+āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
+```
+
+**Fields**:
+- TxtRefType: dropdown (Normal, Abnormal, Critical)
+- Sex: dropdown (0=All, 1=Female, 2=Male)
+- AgeStart: number input
+- AgeEnd: number input
+- RefTxt: text input
+- Flag: text input (N, A, C)
+
+**Validation**:
+- AgeStart must be less than AgeEnd
+- RefTxt is required
+- Flag must match TxtRefType (Normal=N, Abnormal=A, Critical=C)
+
+---
+
+## šļø Component Implementation
+
+### Core Component Requirements
+
+#### 1. State Management with Svelte 5 Runes
+
+**testStore.ts**:
+```typescript
+import { writable } from 'svelte/store';
+
+interface TestFormState {
+ TestSiteID?: number;
+ TestSiteCode: string;
+ TestSiteName: string;
+ TestType: 'TEST' | 'PARAM' | 'CALC' | 'GROUP' | 'TITLE';
+ Description?: string;
+ SiteID: number;
+ SeqScr: number;
+ SeqRpt: number;
+ VisibleScr: number;
+ VisibleRpt: number;
+ CountStat: number;
+ StartDate?: string;
+ details?: {
+ DisciplineID?: number;
+ DepartmentID?: number;
+ ResultType?: string;
+ RefType?: string;
+ VSet?: string;
+ Unit1?: string;
+ Factor?: number;
+ Unit2?: string;
+ Decimal?: number;
+ ReqQty?: number;
+ ReqQtyUnit?: string;
+ CollReq?: string;
+ Method?: string;
+ ExpectedTAT?: number;
+ FormulaInput?: string;
+ FormulaCode?: string;
+ members?: number[];
+ };
+ refnum?: RefNumRange[];
+ reftxt?: RefTxtRange[];
+ testmap?: TestMapping[];
+}
+
+export const testStore = writable({
+ TestSiteCode: '',
+ TestSiteName: '',
+ TestType: 'TEST',
+ SiteID: 1,
+ SeqScr: 0,
+ SeqRpt: 0,
+ VisibleScr: 1,
+ VisibleRpt: 1,
+ CountStat: 1,
+ refnum: [],
+ reftxt: [],
+ testmap: [],
+});
+```
+
+**Derived Stores**:
+```typescript
+// Derive visible tabs based on TestType
+export const visibleTabs = derived(testStore, ($store) => {
+ const type = $store.TestType;
+ return allTabs.filter(tab => tab.isVisible(type));
+});
+
+// Derive valid ResultType options
+export const validResultTypes = derived(testStore, ($store) => {
+ const type = $store.TestType;
+ // Return options based on type
+});
+
+// Derive valid RefType options
+export const validRefTypes = derived([testStore, validResultTypes], ([$store, $resultTypes]) => {
+ const resultType = $store.details?.ResultType;
+ // Return options based on resultType
+});
+```
+
+#### 2. Reusable UI Components
+
+**Button.svelte**:
+```svelte
+
+
+
+```
+
+**Input.svelte**:
+```svelte
+
+
+
+ {#if label}
+
+ {/if}
+
+ {#if error}
+ {error}
+ {/if}
+
+```
+
+**Select.svelte**:
+```svelte
+
+
+
+ {#if label}
+
+ {/if}
+
+
+```
+
+#### 3. Test Form Component
+
+**TestForm.svelte** (Main container):
+```svelte
+
+
+
+```
+
+---
+
+## ā
Validation Requirements
+
+### Frontend Validation
+
+#### TestSiteCode
+- Required
+- 3-6 characters
+- Uppercase only
+- Alphanumeric only
+- Unique (check via API)
+- Regex: `^[A-Z0-9]{3,6}$`
+
+#### TestSiteName
+- Required
+- 3-255 characters
+- No special characters (except hyphen, space, parenthesis)
+- Regex: `^[a-zA-Z0-9\s\-\(\)]{3,255}$`
+
+#### TestType
+- Required
+- Must be one of: TEST, PARAM, CALC, GROUP, TITLE
+
+#### Type Combination Validation
+```typescript
+const validateTypeCombination = (testType: string, resultType: string, refType: string) => {
+ const valid = TestValidationService.validate(testType, resultType, refType);
+ if (!valid.valid) {
+ throw new Error(valid.error);
+ }
+};
+```
+
+#### Reference Range Validation
+
+**Numeric Ranges**:
+- AgeStart < AgeEnd (both 0-150)
+- If Low and High both present: Low < High
+- LowSign appropriate for Low value
+- HighSign appropriate for High value
+- Flag is single character (H, L, A, N)
+
+**Text Ranges**:
+- AgeStart < AgeEnd
+- RefTxt is required
+- Flag matches TxtRefType
+
+#### Group Validation
+- Group cannot contain itself
+- No circular references (Group A contains Group B, Group B contains Group A)
+- No duplicate members
+- Minimum 1 member for GROUP type
+
+---
+
+## šØ Styling & Design System
+
+### Color Palette
+```css
+:root {
+ /* Primary */
+ --primary-50: #e0f2fe;
+ --primary-100: #bae6fd;
+ --primary-500: #0ea5e9;
+ --primary-600: #0284c7;
+ --primary-700: #0369a1;
+
+ /* Secondary */
+ --secondary-500: #64748b;
+ --secondary-600: #475569;
+
+ /* Success */
+ --success-500: #22c55e;
+ --success-600: #16a34a;
+
+ /* Danger */
+ --danger-500: #ef4444;
+ --danger-600: #dc2626;
+
+ /* Warning */
+ --warning-500: #f59e0b;
+ --warning-600: #d97706;
+
+ /* Neutral */
+ --gray-50: #f9fafb;
+ --gray-100: #f3f4f6;
+ --gray-200: #e5e7eb;
+ --gray-300: #d1d5db;
+ --gray-500: #6b7280;
+ --gray-700: #374151;
+ --gray-900: #111827;
+}
+```
+
+### Typography
+```css
+/* Font sizes */
+--text-xs: 0.75rem; /* 12px */
+--text-sm: 0.875rem; /* 14px */
+--text-base: 1rem; /* 16px */
+--text-lg: 1.125rem; /* 18px */
+--text-xl: 1.25rem; /* 20px */
+--text-2xl: 1.5rem; /* 24px */
+--text-3xl: 1.875rem; /* 30px */
+```
+
+### Spacing
+```css
+--space-1: 0.25rem; /* 4px */
+--space-2: 0.5rem; /* 8px */
+--space-3: 0.75rem; /* 12px */
+--space-4: 1rem; /* 16px */
+--space-6: 1.5rem; /* 24px */
+--space-8: 2rem; /* 32px */
+```
+
+### Components
+
+**Buttons**:
+```css
+.btn {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ padding: var(--space-2) var(--space-4);
+ border-radius: 0.375rem;
+ font-weight: 500;
+ transition: all 0.2s;
+ cursor: pointer;
+}
+
+.btn-primary {
+ background-color: var(--primary-600);
+ color: white;
+}
+
+.btn-primary:hover:not(:disabled) {
+ background-color: var(--primary-700);
+}
+
+.btn-secondary {
+ background-color: var(--gray-200);
+ color: var(--gray-700);
+}
+
+.btn-danger {
+ background-color: var(--danger-600);
+ color: white;
+}
+
+.btn:disabled {
+ opacity: 0.5;
+ cursor: not-allowed;
+}
+```
+
+**Inputs**:
+```css
+.input-field {
+ width: 100%;
+ padding: var(--space-2) var(--space-3);
+ border: 1px solid var(--gray-300);
+ border-radius: 0.375rem;
+ font-size: var(--text-base);
+ transition: border-color 0.2s;
+}
+
+.input-field:focus {
+ outline: none;
+ border-color: var(--primary-500);
+ box-shadow: 0 0 0 3px rgba(14, 165, 233, 0.1);
+}
+
+.input-field.has-error {
+ border-color: var(--danger-500);
+}
+```
+
+**Sidebar Tabs**:
+```css
+.sidebar-tab {
+ padding: var(--space-3) var(--space-4);
+ border-left: 3px solid transparent;
+ cursor: pointer;
+ transition: all 0.2s;
+}
+
+.sidebar-tab:hover {
+ background-color: var(--gray-100);
+}
+
+.sidebar-tab.active {
+ background-color: var(--primary-50);
+ border-left-color: var(--primary-600);
+ font-weight: 600;
+}
+```
+
+---
+
+## š± Responsive Design
+
+### Breakpoints
+```css
+--breakpoint-sm: 640px;
+--breakpoint-md: 768px;
+--breakpoint-lg: 1024px;
+--breakpoint-xl: 1280px;
+```
+
+### Responsive Behavior
+
+**Desktop (> 1024px)**:
+- Sidebar: Fixed width, visible
+- Content: Full width
+- Form: 2-column grid layout
+
+**Tablet (768px - 1024px)**:
+- Sidebar: Collapsible (hamburger menu)
+- Content: Full width
+- Form: Single column layout
+- Table: Horizontal scroll
+
+**Mobile (< 768px)**:
+- Sidebar: Off-canvas drawer
+- Content: Full width
+- Form: Single column, stacked
+- Table: Card view instead of table
+
+---
+
+## š Implementation Checklist
+
+### Phase 1: Project Setup & Infrastructure
+- [ ] Initialize SvelteKit project with TypeScript
+- [ ] Install and configure Tailwind CSS
+- [ ] Set up Skeleton UI or Melt UI
+- [ ] Configure Axios with interceptors
+- [ ] Create type definitions (test.types.ts, api.types.ts)
+- [ ] Set up API service layer
+- [ ] Create auth store and testStore
+- [ ] Set up routing structure
+
+### Phase 2: Reusable Components
+- [ ] Button component
+- [ ] Input component
+- [ ] Select component
+- [ ] Checkbox component
+- [ ] Table component
+- [ ] Modal component
+- [ ] Badge component
+- [ ] Alert component
+- [ ] Spinner component
+- [ ] Tabs component
+
+### Phase 3: Test List Page
+- [ ] Test list page layout
+- [ ] Filter panel component
+- [ ] Test table component
+- [ ] Pagination component
+- [ ] Search functionality
+- [ ] Filter functionality
+- [ ] Sort functionality
+- [ ] Load test data from API
+
+### Phase 4: Test Form - Basic & Tech
+- [ ] Test form container with sidebar tabs
+- [ ] Basic Info tab
+- [ ] Tech Details tab
+- [ ] Dynamic dropdown logic
+- [ ] Form validation
+- [ ] Save functionality
+- [ ] Update functionality
+
+### Phase 5: Type-Specific Tabs
+- [ ] Calculations tab (CALC)
+- [ ] Group Members tab (GROUP)
+- [ ] Mappings tab (all types)
+- [ ] Member selection dropdown
+- [ ] Mapping add/edit modal
+
+### Phase 6: Reference Ranges
+- [ ] RefNum tab with table
+- [ ] RefTxt tab with table
+- [ ] Reference range modal
+- [ ] Reference range validation
+- [ ] Add/Edit/Delete operations
+
+### Phase 7: Polish & Testing
+- [ ] Responsive design
+- [ ] Loading states
+- [ ] Error handling
+- [ ] Form dirty state tracking
+- [ ] Confirmation dialogs
+- [ ] Toast notifications
+- [ ] Accessibility (ARIA labels)
+- [ ] Keyboard navigation
+- [ ] Cross-browser testing
+
+### Phase 8: Documentation
+- [ ] Component documentation
+- [ ] API integration guide
+- [ ] User guide
+- [ ] Deployment instructions
+
+---
+
+## š Additional Notes
+
+### ValueSet Integration
+- Use backend API `/api/valueset` to fetch dropdown options
+- Cache valuesets locally to reduce API calls
+- Transform labels: API returns both code and label (e.g., `TestType` and `TestTypeLabel`)
+- Display labels in UI, use codes for API calls
+
+### Authentication
+- JWT token stored in localStorage
+- Include token in Authorization header for all API calls
+- Handle token expiration and refresh
+- Redirect to login if unauthorized
+
+### Error Handling
+- Display user-friendly error messages
+- Log technical errors to console
+- Retry logic for failed requests (with backoff)
+- Show appropriate feedback for network errors
+
+### Performance
+- Implement debounced search (300ms)
+- Lazy load test data (pagination)
+- Optimize re-renders with Svelte 5 runes
+- Memoize expensive computations
+
+### Accessibility
+- ARIA labels for form inputs
+- Keyboard navigation support
+- Screen reader compatibility
+- Focus management in modals
+- Color contrast compliance (WCAG AA)
+
+---
+
+## š References
+
+### Backend Documentation
+- API Endpoints: `/api/tests`
+- Models: `TestDefSiteModel`, `TestDefCalModel`, `TestDefGrpModel`, `TestMapModel`
+- Validation: `TestValidationService`
+
+### Type System
+- Test types: TEST, PARAM, CALC, GROUP, TITLE
+- Result types: NMRIC, RANGE, TEXT, VSET, NORES
+- Reference types: RANGE, THOLD, TEXT, VSET, NOREF
+
+### Business Rules
+- Soft deletes only (set EndDate)
+- Test type + ResultType + RefType must be valid combination
+- Reference ranges are type-specific (numeric vs text)
+- Calculations use formula placeholders like {A}, {B}
+
+---
+
+## šÆ Success Criteria
+
+The frontend is considered complete when:
+
+1. **Functional Requirements**
+ - All CRUD operations work for all test types
+ - Reference ranges can be managed (add/edit/delete)
+ - Group members can be added/removed/reordered
+ - Mappings can be configured for external systems
+ - Form validation prevents invalid data submission
+
+2. **User Experience**
+ - Intuitive navigation with sidebar tabs
+ - Clear visual feedback for actions
+ - Responsive design works on all devices
+ - Loading states indicate progress
+ - Error messages are helpful and actionable
+
+3. **Code Quality**
+ - TypeScript strict mode with no errors
+ - Component reusability and modularity
+ - Proper error handling throughout
+ - Clean, readable code with comments
+ - Efficient state management with Svelte 5 runes
+
+4. **Performance**
+ - Page load time < 2 seconds
+ - Search results appear within 300ms
+ - Form submissions complete within 1 second
+ - No memory leaks or performance degradation
+
+5. **Testing**
+ - Unit tests for components
+ - Integration tests for API calls
+ - E2E tests for critical user flows
+ - Cross-browser compatibility verified
+
+---
+
+## š Support
+
+For questions or issues during development:
+1. Review backend API documentation in `README.md`
+2. Check model definitions in `app/Models/Test/`
+3. Refer to validation service in `app/Libraries/TestValidationService.php`
+4. Test API endpoints directly using tools like Postman
+
+---
+
+**Last Updated**: February 2025
+**Version**: 1.0
diff --git a/src/lib/api/tests.js b/src/lib/api/tests.js
index 461d401..e9c8ac1 100644
--- a/src/lib/api/tests.js
+++ b/src/lib/api/tests.js
@@ -1,100 +1,338 @@
import { get, post, patch } from './client.js';
+/**
+ * @typedef {import('$lib/types/test.types.js').TestSummary} TestSummary
+ * @typedef {import('$lib/types/test.types.js').TestDetail} TestDetail
+ * @typedef {import('$lib/types/test.types.js').CreateTestPayload} CreateTestPayload
+ * @typedef {import('$lib/types/test.types.js').UpdateTestPayload} UpdateTestPayload
+ * @typedef {import('$lib/types/test.types.js').TestListResponse} TestListResponse
+ * @typedef {import('$lib/types/test.types.js').TestDetailResponse} TestDetailResponse
+ * @typedef {import('$lib/types/test.types.js').CreateTestResponse} CreateTestResponse
+ * @typedef {import('$lib/types/test.types.js').UpdateTestResponse} UpdateTestResponse
+ * @typedef {import('$lib/types/test.types.js').DeleteTestResponse} DeleteTestResponse
+ * @typedef {import('$lib/types/test.types.js').TestFilterOptions} TestFilterOptions
+ */
+
/**
* Fetch tests list with optional filters, pagination, and search
- * @param {Object} params - Query parameters
- * @param {number} [params.page=1] - Page number
- * @param {number} [params.perPage=20] - Items per page
- * @param {string} [params.search] - Search by test code or name
- * @param {string} [params.TestType] - Filter by test type (TEST, PARAM, CALC, GROUP, TITLE)
- * @returns {Promise