2026-02-13 16:07:59 +07:00
< script >
2026-02-18 07:12:58 +07:00
import { onMount , onDestroy } from 'svelte';
refactor(tests): Move TestModal to route folder and add technical config support
- Move TestModal from lib/components to routes/(app)/master-data/tests
- Add technical configuration form (ResultType, RefType, SpcType, units, etc.)
- Add GroupMembersTab for managing group test members
- Enhance reference ranges with refvset and refthold support
- Update API to handle new test fields (ReqQty, Factor, Decimal, TAT, etc.)
- Add database schema documentation (DBML format)
- Remove old test-types-reference.md documentation
- UI improvements: compact design, updated sidebar, modal sizing
- Update DataTable, Modal, SelectDropdown components for compact style
- Enhance patient and visit modals with compact layout
2026-02-18 16:31:20 +07:00
import { fetchTests , fetchTest , createTest , updateTest , deleteTest } from '$lib/api/tests.js';
2026-02-13 16:07:59 +07:00
import { fetchDisciplines , fetchDepartments } from '$lib/api/organization.js';
2026-02-18 07:12:58 +07:00
import { success as toastSuccess , error as toastError } from '$lib/utils/toast.js';
2026-02-13 16:07:59 +07:00
import DataTable from '$lib/components/DataTable.svelte';
import Modal from '$lib/components/Modal.svelte';
2026-02-18 07:12:58 +07:00
import TestModal from './TestModal.svelte';
2026-02-19 07:12:11 +07:00
import TestTypeSelector from './test-modal/TestTypeSelector.svelte';
2026-02-18 07:12:58 +07:00
import { validateNumericRange , validateTholdRange , validateTextRange , validateVsetRange } from './referenceRange.js';
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([]);
2026-02-19 07:12:11 +07:00
let modalOpen = $state(false), selectedRowIndex = $state(-1), expandedGroups = $state(new Set()), typeSelectorOpen = $state(false);
2026-02-18 07:12:58 +07:00
let currentPage = $state(1), perPage = $state(20), totalItems = $state(0), totalPages = $state(1);
let modalMode = $state('create'), saving = $state(false), selectedType = $state(''), searchQuery = $state(''), searchInputRef = $state(null);
let deleteModalOpen = $state(false), testToDelete = $state(null), deleting = $state(false);
refactor(tests): Move TestModal to route folder and add technical config support
- Move TestModal from lib/components to routes/(app)/master-data/tests
- Add technical configuration form (ResultType, RefType, SpcType, units, etc.)
- Add GroupMembersTab for managing group test members
- Enhance reference ranges with refvset and refthold support
- Update API to handle new test fields (ReqQty, Factor, Decimal, TAT, etc.)
- Add database schema documentation (DBML format)
- Remove old test-types-reference.md documentation
- UI improvements: compact design, updated sidebar, modal sizing
- Update DataTable, Modal, SelectDropdown components for compact style
- Enhance patient and visit modals with compact layout
2026-02-18 16:31:20 +07:00
let formData = $state({
// Basic Info (testdefsite)
TestSiteID: null,
TestSiteCode: '',
TestSiteName: '',
TestType: 'TEST',
DisciplineID: null,
DepartmentID: null,
SeqScr: '0',
SeqRpt: '0',
VisibleScr: true,
VisibleRpt: true,
Description: '',
CountStat: false,
Unit: '',
Formula: '',
refnum: [],
refthold: [],
reftxt: [],
refvset: [],
refRangeType: 'none',
// Technical Config (testdeftech)
ResultType: '',
RefType: '',
ReqQty: null,
ReqQtyUnit: '',
Unit1: '',
Factor: null,
Unit2: '',
Decimal: 0,
CollReq: '',
Method: '',
ExpectedTAT: null,
// Group Members (testdefgrp)
groupMembers: []
});
2026-02-18 07:12:58 +07:00
const testTypeConfig = { TEST : { label : 'Test' , badgeClass : 'badge-primary' , icon : Microscope , color : '#0066CC' , bgColor : '#E6F2FF' }, PARAM : { label : 'Parameter' , badgeClass : 'badge-secondary' , icon : Variable , color : '#3399FF' , bgColor : '#F0F8FF' }, CALC : { label : 'Calculated' , badgeClass : 'badge-accent' , icon : Calculator , color : '#9933CC' , bgColor : '#F5E6FF' }, GROUP : { label : 'Panel' , badgeClass : 'badge-info' , icon : Box , color : '#00AA44' , bgColor : '#E6F9EE' }, TITLE : { label : 'Header' , badgeClass : 'badge-ghost' , icon : Layers , color : '#666666' , bgColor : '#F5F5F5' } } ;
refactor(tests): Move TestModal to route folder and add technical config support
- Move TestModal from lib/components to routes/(app)/master-data/tests
- Add technical configuration form (ResultType, RefType, SpcType, units, etc.)
- Add GroupMembersTab for managing group test members
- Enhance reference ranges with refvset and refthold support
- Update API to handle new test fields (ReqQty, Factor, Decimal, TAT, etc.)
- Add database schema documentation (DBML format)
- Remove old test-types-reference.md documentation
- UI improvements: compact design, updated sidebar, modal sizing
- Update DataTable, Modal, SelectDropdown components for compact style
- Enhance patient and visit modals with compact layout
2026-02-18 16:31:20 +07:00
const canHaveRefRange = $derived(formData.TestType === 'TEST' || formData.TestType === 'CALC');
2026-02-18 07:12:58 +07:00
const canHaveFormula = $derived(formData.TestType === 'CALC');
refactor(tests): Move TestModal to route folder and add technical config support
- Move TestModal from lib/components to routes/(app)/master-data/tests
- Add technical configuration form (ResultType, RefType, SpcType, units, etc.)
- Add GroupMembersTab for managing group test members
- Enhance reference ranges with refvset and refthold support
- Update API to handle new test fields (ReqQty, Factor, Decimal, TAT, etc.)
- Add database schema documentation (DBML format)
- Remove old test-types-reference.md documentation
- UI improvements: compact design, updated sidebar, modal sizing
- Update DataTable, Modal, SelectDropdown components for compact style
- Enhance patient and visit modals with compact layout
2026-02-18 16:31:20 +07:00
const canHaveTechnical = $derived(formData.TestType === 'TEST' || formData.TestType === 'PARAM' || formData.TestType === 'CALC');
const isGroupTest = $derived(formData.TestType === 'GROUP');
2026-02-18 07:12:58 +07:00
const columns = [{ key : 'expand' , label : '' , class : 'w-8' } , { key : 'TestSiteCode' , label : 'Code' , class : 'font-medium w-24' } , { key : 'TestSiteName' , label : 'Name' , class : 'min-w-[200px]' } , { key : 'TestType' , label : 'Type' , class : 'w-28' } , { key : 'ReferenceRange' , label : 'Reference Range' , class : 'w-40' } , { key : 'Unit' , label : 'Unit' , class : 'w-20' } , { key : 'actions' , label : 'Actions' , class : 'w-24 text-center' } ];
const disciplineOptions = $derived(disciplines.map(d => ({ value : d.DisciplineID , label : d.DisciplineName } )));
const departmentOptions = $derived(departments.map(d => ({ value : d.DepartmentID , label : d.DepartmentName } )));
onMount(async () => { document . addEventListener ( 'keydown' , handleKeydown ); await Promise . all ([ loadTests (), loadDisciplines (), loadDepartments ()]); } );
onDestroy(() => document.removeEventListener('keydown', handleKeydown));
async function loadTests() { loading = true ; try { const params = { page : currentPage , perPage }; if ( selectedType ) params . TestType = selectedType ; if ( searchQuery . trim ()) params . search = searchQuery . trim (); const response = await fetchTests ( params ); let allTests = Array . isArray ( response . data ) ? response . data : []; allTests = allTests . filter ( test => test . IsActive !== '0' && test . IsActive !== 0 ); tests = allTests ; if ( response . pagination ) { totalItems = response . pagination . total || 0 ; totalPages = Math . ceil ( totalItems / perPage ) || 1 ; } selectedRowIndex = - 1 ; } catch ( err ) { toastError ( err . message || 'Failed to load tests' ); tests = []; } finally { loading = false ; } }
async function loadDisciplines() { try { const response = await fetchDisciplines (); disciplines = Array . isArray ( response . data ) ? response . data : []; } catch ( err ) { disciplines = []; } }
async function loadDepartments() { try { const response = await fetchDepartments (); departments = Array . isArray ( response . data ) ? response . data : []; } catch ( err ) { departments = []; } }
function handleKeydown(e) { if ( e . key === '/' && ! modalOpen && ! deleteModalOpen ) { e . preventDefault (); searchInputRef ? . focus (); return ; } if ( e . key === 'Escape' ) { if ( modalOpen ) modalOpen = false ; else if ( deleteModalOpen ) deleteModalOpen = false ; return ; } if ( ! modalOpen && ! deleteModalOpen && document . activeElement === document . body ) { const visibleTests = getVisibleTests (); if ( e . key === 'ArrowDown' && selectedRowIndex < visibleTests . length - 1 ) selectedRowIndex ++ ; else if ( e . key === 'ArrowUp' && selectedRowIndex > 0 ) selectedRowIndex -- ; else if ( e . key === 'Enter' && selectedRowIndex >= 0 ) openEditModal ( visibleTests [ selectedRowIndex ]); } }
function getVisibleTests() { return tests . filter ( t => t . IsActive !== '0' && t . IsActive !== 0 ); }
function getTestTypeConfig(type) { return testTypeConfig [ type ] || testTypeConfig . TEST ; }
function formatReferenceRange(test) { return '-' ; }
2026-02-19 07:12:11 +07:00
function openTypeSelector() {
typeSelectorOpen = true;
}
function handleTypeSelect(type) {
typeSelectorOpen = false;
openCreateModal(type);
}
function openCreateModal(type = 'TEST') {
refactor(tests): Move TestModal to route folder and add technical config support
- Move TestModal from lib/components to routes/(app)/master-data/tests
- Add technical configuration form (ResultType, RefType, SpcType, units, etc.)
- Add GroupMembersTab for managing group test members
- Enhance reference ranges with refvset and refthold support
- Update API to handle new test fields (ReqQty, Factor, Decimal, TAT, etc.)
- Add database schema documentation (DBML format)
- Remove old test-types-reference.md documentation
- UI improvements: compact design, updated sidebar, modal sizing
- Update DataTable, Modal, SelectDropdown components for compact style
- Enhance patient and visit modals with compact layout
2026-02-18 16:31:20 +07:00
modalMode = 'create';
formData = {
// Basic Info
TestSiteID: null,
TestSiteCode: '',
TestSiteName: '',
2026-02-19 07:12:11 +07:00
TestType: type,
refactor(tests): Move TestModal to route folder and add technical config support
- Move TestModal from lib/components to routes/(app)/master-data/tests
- Add technical configuration form (ResultType, RefType, SpcType, units, etc.)
- Add GroupMembersTab for managing group test members
- Enhance reference ranges with refvset and refthold support
- Update API to handle new test fields (ReqQty, Factor, Decimal, TAT, etc.)
- Add database schema documentation (DBML format)
- Remove old test-types-reference.md documentation
- UI improvements: compact design, updated sidebar, modal sizing
- Update DataTable, Modal, SelectDropdown components for compact style
- Enhance patient and visit modals with compact layout
2026-02-18 16:31:20 +07:00
DisciplineID: null,
DepartmentID: null,
SeqScr: '0',
SeqRpt: '0',
VisibleScr: true,
VisibleRpt: true,
Description: '',
CountStat: false,
Unit: '',
Formula: '',
refnum: [],
refthold: [],
reftxt: [],
refvset: [],
refRangeType: 'none',
// Technical Config
ResultType: '',
RefType: '',
ReqQty: null,
ReqQtyUnit: '',
Unit1: '',
Factor: null,
Unit2: '',
Decimal: 0,
CollReq: '',
Method: '',
ExpectedTAT: null,
// Group Members
groupMembers: []
};
modalOpen = true;
}
async function openEditModal(row) {
try {
// Fetch full test details including reference ranges, technical config, group members
const response = await fetchTest(row.TestSiteID);
const testDetail = response.data;
modalMode = 'edit';
let refRangeType = 'none';
if (testDetail.refnum?.length > 0) refRangeType = 'num';
else if (testDetail.refthold?.length > 0) refRangeType = 'thold';
else if (testDetail.reftxt?.length > 0) refRangeType = 'text';
else if (testDetail.refvset?.length > 0) refRangeType = 'vset';
// Normalize reference range data to ensure all fields have values (not undefined)
const normalizeRefNum = (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 ?? '',
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 ?? '',
Criteria: ref.Criteria ?? ''
});
const normalizeRefTxt = (ref) => ({
Sex: ref.Sex ?? '2',
AgeStart: ref.AgeStart ?? 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,
AgeEnd: ref.AgeEnd ?? 120,
RefTxt: ref.RefTxt ?? '',
Flag: ref.Flag ?? 'N',
SpcType: ref.SpcType ?? '',
Criteria: ref.Criteria ?? ''
});
formData = {
// Basic Info
TestSiteID: testDetail.TestSiteID,
TestSiteCode: testDetail.TestSiteCode,
TestSiteName: testDetail.TestSiteName,
TestType: testDetail.TestType,
DisciplineID: testDetail.testdeftech?.[0]?.DisciplineID || null,
DepartmentID: testDetail.testdeftech?.[0]?.DepartmentID || null,
SeqScr: testDetail.SeqScr || '0',
SeqRpt: testDetail.SeqRpt || '0',
VisibleScr: testDetail.VisibleScr === '1' || testDetail.VisibleScr === 1 || testDetail.VisibleScr === true,
VisibleRpt: testDetail.VisibleRpt === '1' || testDetail.VisibleRpt === 1 || testDetail.VisibleRpt === true,
Description: testDetail.Description || '',
CountStat: testDetail.CountStat === '1' || testDetail.CountStat === 1 || testDetail.CountStat === true,
Unit: testDetail.Unit || '',
Formula: testDetail.Formula || '',
refnum: (testDetail.refnum || []).map(normalizeRefNum),
refthold: (testDetail.refthold || []).map(normalizeRefThold),
reftxt: (testDetail.reftxt || []).map(normalizeRefTxt),
refvset: (testDetail.refvset || []).map(normalizeRefVset),
refRangeType,
// Technical Config (from testdeftech[0])
ResultType: testDetail.testdeftech?.[0]?.ResultType || '',
RefType: testDetail.testdeftech?.[0]?.RefType || '',
ReqQty: testDetail.testdeftech?.[0]?.ReqQty || null,
ReqQtyUnit: testDetail.testdeftech?.[0]?.ReqQtyUnit || '',
Unit1: testDetail.testdeftech?.[0]?.Unit1 || '',
Factor: testDetail.testdeftech?.[0]?.Factor || null,
Unit2: testDetail.testdeftech?.[0]?.Unit2 || '',
Decimal: testDetail.testdeftech?.[0]?.Decimal || 0,
Method: testDetail.testdeftech?.[0]?.Method || '',
ExpectedTAT: testDetail.testdeftech?.[0]?.ExpectedTAT || null,
// Group Members - API returns as testdefgrp
groupMembers: testDetail.testdefgrp || []
};
modalOpen = true;
} catch (err) {
toastError(err.message || 'Failed to load test details');
console.error('Failed to fetch test details:', err);
}
}
2026-02-18 07:12:58 +07:00
function isDuplicateCode(code, excludeId = null) { return tests . some ( test => test . TestSiteCode . toLowerCase () === code . toLowerCase () && test . TestSiteID !== excludeId ); }
2026-02-15 17:58:42 +07:00
2026-02-13 16:07:59 +07:00
async function handleSave() {
2026-02-18 07:12:58 +07:00
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 (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 ; } } }
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 ; } } }
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 ; } } }
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 ; } } }
refactor(tests): Move TestModal to route folder and add technical config support
- Move TestModal from lib/components to routes/(app)/master-data/tests
- Add technical configuration form (ResultType, RefType, SpcType, units, etc.)
- Add GroupMembersTab for managing group test members
- Enhance reference ranges with refvset and refthold support
- Update API to handle new test fields (ReqQty, Factor, Decimal, TAT, etc.)
- Add database schema documentation (DBML format)
- Remove old test-types-reference.md documentation
- UI improvements: compact design, updated sidebar, modal sizing
- Update DataTable, Modal, SelectDropdown components for compact style
- Enhance patient and visit modals with compact layout
2026-02-18 16:31:20 +07:00
saving = true;
try {
const payload = { ... formData } ;
// Remove fields based on test type
if (!canHaveFormula) delete payload.Formula;
if (!canHaveRefRange) {
delete payload.refnum;
delete payload.refthold;
delete payload.reftxt;
delete payload.refvset;
}
if (!canHaveTechnical) {
delete payload.ResultType;
delete payload.RefType;
delete payload.Unit1;
delete payload.Factor;
delete payload.Unit2;
delete payload.Decimal;
delete payload.ReqQty;
delete payload.ReqQtyUnit;
delete payload.CollReq;
delete payload.Method;
delete payload.ExpectedTAT;
}
if (!isGroupTest) {
delete payload.groupMembers;
}
delete payload.refRangeType;
if (modalMode === 'create') {
await createTest(payload);
toastSuccess('Test created successfully');
} else {
await updateTest(payload);
toastSuccess('Test updated successfully');
}
modalOpen = false;
await loadTests();
} catch (err) {
toastError(err.message || 'Failed to save test');
} finally {
saving = false;
}
2026-02-18 07:12:58 +07:00
}
function openDeleteModal(row) { testToDelete = row ; deleteModalOpen = true ; }
async function handleDelete() { if ( ! testToDelete ? . TestSiteID ) return ; deleting = true ; try { await deleteTest ( testToDelete . TestSiteID ); toastSuccess ( 'Test deleted successfully' ); deleteModalOpen = false ; testToDelete = null ; await loadTests (); } catch ( err ) { toastError ( err . message || 'Failed to delete test' ); } finally { deleting = false ; } }
function handleFilter() { currentPage = 1 ; loadTests (); }
function handleSearch() { currentPage = 1 ; loadTests (); }
function handlePageChange(newPage) { if ( newPage >= 1 && newPage <= totalPages ) { currentPage = newPage ; selectedRowIndex = - 1 ; loadTests (); } }
function handleRowClick(index) { selectedRowIndex = index ; }
function toggleGroup(testId) { if ( expandedGroups . has ( testId )) expandedGroups . delete ( testId ); else expandedGroups . add ( testId ); expandedGroups = new Set ( expandedGroups ); }
2026-02-13 16:07:59 +07:00
< / script >
refactor(tests): Move TestModal to route folder and add technical config support
- Move TestModal from lib/components to routes/(app)/master-data/tests
- Add technical configuration form (ResultType, RefType, SpcType, units, etc.)
- Add GroupMembersTab for managing group test members
- Enhance reference ranges with refvset and refthold support
- Update API to handle new test fields (ReqQty, Factor, Decimal, TAT, etc.)
- Add database schema documentation (DBML format)
- Remove old test-types-reference.md documentation
- UI improvements: compact design, updated sidebar, modal sizing
- Update DataTable, Modal, SelectDropdown components for compact style
- Enhance patient and visit modals with compact layout
2026-02-18 16:31:20 +07:00
< div class = "p-4" >
2026-02-13 16:07:59 +07:00
< div class = "flex items-center gap-4 mb-6" >
2026-02-18 07:12:58 +07:00
< a href = "/master-data" class = "btn btn-ghost btn-circle" > < ArrowLeft class = "w-5 h-5" / > < / a >
2026-02-13 16:07:59 +07:00
< div class = "flex-1" >
< h1 class = "text-3xl font-bold text-gray-800" > Test Definitions< / h1 >
2026-02-18 07:12:58 +07:00
< p class = "text-gray-600" > Manage laboratory tests, panels, and calculated values< / p >
2026-02-13 16:07:59 +07:00
< / div >
2026-02-19 07:12:11 +07:00
< button class = "btn btn-primary" onclick = { openTypeSelector } > <Plus class = "w-4 h-4 mr-2" /> Add Test</ button >
2026-02-13 16:07:59 +07:00
< / div >
< 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" >
2026-02-16 15:58:06 +07:00
< div class = "flex-1 relative" >
2026-02-18 07:12:58 +07:00
< 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 ()} / >
2026-02-16 15:58:06 +07:00
< Search class = "w-5 h-5 absolute left-3 top-1/2 -translate-y-1/2 text-gray-400" / >
2026-02-13 16:07:59 +07:00
< / div >
< div class = "w-full sm:w-48" >
refactor(tests): Move TestModal to route folder and add technical config support
- Move TestModal from lib/components to routes/(app)/master-data/tests
- Add technical configuration form (ResultType, RefType, SpcType, units, etc.)
- Add GroupMembersTab for managing group test members
- Enhance reference ranges with refvset and refthold support
- Update API to handle new test fields (ReqQty, Factor, Decimal, TAT, etc.)
- Add database schema documentation (DBML format)
- Remove old test-types-reference.md documentation
- UI improvements: compact design, updated sidebar, modal sizing
- Update DataTable, Modal, SelectDropdown components for compact style
- Enhance patient and visit modals with compact layout
2026-02-18 16:31:20 +07:00
< select class = "select select-sm select-bordered w-full" bind:value = { selectedType } onchange= { handleFilter } >
2026-02-13 16:07:59 +07:00
< option value = "" > All Types< / option >
2026-02-19 07:12:11 +07:00
< option value = "TEST" > Single Test< / option >
2026-02-13 16:07:59 +07:00
< option value = "PARAM" > Parameter< / option >
< option value = "CALC" > Calculated< / option >
< option value = "GROUP" > Panel/Profile< / option >
< option value = "TITLE" > Section Header< / option >
< / select >
< / div >
2026-02-18 07:12:58 +07:00
< button class = "btn btn-outline" onclick = { handleFilter } > <Filter class = "w-4 h-4 mr-2" /> Filter</ button >
2026-02-13 16:07:59 +07:00
< / div >
< / div >
< div class = "bg-base-100 rounded-lg shadow border border-base-200" >
2026-02-18 07:12:58 +07:00
< DataTable { columns } data = { getVisibleTests ()} { loading } emptyMessage="No tests found " hover = { true } bordered= { false } onRowClick = {( row , idx ) => 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 } < button class = "btn btn-ghost btn-xs btn-circle" onclick = {() => toggleGroup ( row . TestSiteID )} > { #if isExpanded } <ChevronDown class = "w-4 h-4" /> { : else } < ChevronRight class = "w-4 h-4" /> { /if } </ button > { : else } < span class = "w-8 inline-block" ></ span > { /if }{ /if }
{ #if column . key === 'TestType' } < span class = "badge { typeConfig . badgeClass } gap-1" style = "background-color: { typeConfig . bgColor } ; color: { typeConfig . color } ; border-color: { typeConfig . color } ;" >< svelte:component this = { typeConfig . icon } class="w-3 h-3 " /> { typeConfig . label } </ span > { /if }
{ #if column . key === 'TestSiteName' } < div class = "flex flex-col" >< span class = "font-medium" > { row . TestSiteName } </ span > { #if isGroup && isExpanded && row . testdefgrp } < div class = "mt-2 ml-4 pl-3 border-l-2 border-base-300 space-y-1" > { #each row . testdefgrp as member }{ @const memberConfig = getTestTypeConfig ( member . TestType )} < div class = "flex items-center gap-2 text-sm text-gray-600 py-1" >< svelte:component this = { memberConfig . icon } class="w-3 h-3 " style = "color: { memberConfig . color } " />< span class = "font-mono text-xs" > { member . TestSiteCode } </ span >< span > { member . TestSiteName } </ span ></ div > { /each } </ div > { /if } </ div > { /if }
{ #if column . key === 'ReferenceRange' } < span class = "text-sm font-mono text-gray-600" > { formatReferenceRange ( row )} </ span > { /if }
{ #if column . key === 'actions' } < div class = "flex justify-center gap-1" >< button class = "btn btn-sm btn-ghost" onclick = {() => openEditModal ( row )} > <Edit2 class = "w-4 h-4" /></ button >< button class = "btn btn-sm btn-ghost text-error" onclick = {() => openDeleteModal ( row )} > <Trash2 class = "w-4 h-4" /></ button ></ div > { /if }
{ #if column . key === 'TestSiteCode' } < span class = "font-mono text-sm" > { row . TestSiteCode } </ span > { /if }
2026-02-13 16:07:59 +07:00
{ /snippet }
< / DataTable >
2026-02-18 07:12:58 +07:00
{ #if totalPages > 1 } < div class = "flex items-center justify-between px-4 py-3 border-t border-base-200 bg-base-100" >< div class = "text-sm text-gray-600" > Showing {( currentPage - 1 ) * perPage + 1 } - { Math . min ( currentPage * perPage , totalItems )} of { totalItems } </ div >< div class = "flex gap-2" >< button class = "btn btn-sm btn-ghost" onclick = {() => handlePageChange ( currentPage - 1 )} disabled= { currentPage === 1 } > Previous</ button >< span class = "btn btn-sm btn-ghost no-animation" > Page { currentPage } of { totalPages } </ span >< button class = "btn btn-sm btn-ghost" onclick = {() => handlePageChange ( currentPage + 1 )} disabled= { currentPage === totalPages } > Next</ button ></ div ></ div > { /if }
2026-02-13 16:07:59 +07:00
< / div >
< / div >
2026-02-19 07:12:11 +07:00
< Modal bind:open = { typeSelectorOpen } title="Add Test " size = "md" >
< TestTypeSelector
onselect={ handleTypeSelect }
oncancel={() => typeSelectorOpen = false }
/>
< / Modal >
refactor(tests): Move TestModal to route folder and add technical config support
- Move TestModal from lib/components to routes/(app)/master-data/tests
- Add technical configuration form (ResultType, RefType, SpcType, units, etc.)
- Add GroupMembersTab for managing group test members
- Enhance reference ranges with refvset and refthold support
- Update API to handle new test fields (ReqQty, Factor, Decimal, TAT, etc.)
- Add database schema documentation (DBML format)
- Remove old test-types-reference.md documentation
- UI improvements: compact design, updated sidebar, modal sizing
- Update DataTable, Modal, SelectDropdown components for compact style
- Enhance patient and visit modals with compact layout
2026-02-18 16:31:20 +07:00
< TestModal
bind:open={ modalOpen }
mode={ modalMode }
bind:formData
{ canHaveRefRange }
{ canHaveFormula }
{ canHaveTechnical }
{ isGroupTest }
{ disciplineOptions }
departmentOptions={ departmentOptions }
availableTests={ tests }
{ saving }
onsave={ handleSave }
oncancel={() => modalOpen = false }
onupdateFormData={( data ) => formData = data }
/>
2026-02-15 17:58:42 +07:00
< Modal bind:open = { deleteModalOpen } title="Confirm Delete Test " size = "sm" >
< div class = "py-2" >
< p > Are you sure you want to delete this test?< / p >
2026-02-18 07:12:58 +07:00
< p class = "text-sm text-gray-600 mt-2" > Code: < strong > { testToDelete ? . TestSiteCode } </ strong >< br /> Name: < strong > { testToDelete ? . TestSiteName } </ strong ></ p >
< p class = "text-sm text-warning mt-2" > This will deactivate the test. Historical data will be preserved.< / p >
2026-02-15 17:58:42 +07:00
< / div >
2026-02-18 07:12:58 +07:00
{ # snippet footer ()} < button class = "btn btn-ghost" onclick = {() => deleteModalOpen = false } type="button" > Cancel</ button >< button class = "btn btn-error" onclick = { handleDelete } disabled= { deleting } type = "button" > { #if deleting } < span class = "loading loading-spinner loading-sm mr-2" ></ span > { /if }{ deleting ? 'Deleting...' : 'Delete' } </ button > { /snippet }
< / Modal >