- Rename all controllers from X.php to XController.php format - Add new RefTxtModel for text-based reference ranges - Rename group_dialog.php to grp_dialog.php and remove title_dialog.php - Add comprehensive test suite for v2/master/TestDef module - Update Routes.php to reflect controller renames - Remove obsolete data files (clqms_v2.sql, lab.dbml)
305 lines
16 KiB
PHP
305 lines
16 KiB
PHP
<!-- Group Dialog (for GROUP type) -->
|
|
<div x-show="showModal && (getTypeCode(form.TestType) === 'GROUP' || form.TypeCode === 'GROUP')" x-cloak
|
|
class="modal-overlay" @click.self="closeModal()" x-transition:enter="transition ease-out duration-200"
|
|
x-transition:enter-start="opacity-0" x-transition:enter-end="opacity-100"
|
|
x-transition:leave="transition ease-in duration-150" x-transition:leave-start="opacity-100"
|
|
x-transition:leave-end="opacity-0">
|
|
<div class="modal-content p-6 max-w-4xl w-full max-h-[90vh] overflow-y-auto" @click.stop
|
|
x-transition:enter="transition ease-out duration-200" x-transition:enter-start="opacity-0 transform scale-95"
|
|
x-transition:enter-end="opacity-100 transform scale-100" x-transition:leave="transition ease-in duration-150"
|
|
x-transition:leave-start="opacity-100 transform scale-100" x-transition:leave-end="opacity-0 transform scale-95">
|
|
|
|
<!-- Header -->
|
|
<div class="flex items-center justify-between mb-4">
|
|
<div class="flex items-center gap-3">
|
|
<div class="w-10 h-10 rounded-lg bg-primary bg-opacity-20 flex items-center justify-center">
|
|
<i class="fa-solid fa-layer-group text-primary text-lg"></i>
|
|
</div>
|
|
<div>
|
|
<h3 class="font-bold text-lg" style="color: rgb(var(--color-text));">
|
|
<span x-text="isEditing ? 'Edit Test Group' : 'New Test Group'"></span>
|
|
</h3>
|
|
<p class="text-xs" style="color: rgb(var(--color-text-muted));">Group/Panel Definition</p>
|
|
</div>
|
|
</div>
|
|
<button class="btn btn-ghost btn-sm btn-square" @click="closeModal()">
|
|
<i class="fa-solid fa-times"></i>
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Group Type Badge -->
|
|
<div class="mb-4">
|
|
<span class="badge badge-primary gap-1">
|
|
<i class="fa-solid fa-layer-group"></i>
|
|
<span x-text="form.TypeCode || getTypeName(form.TestType)"></span>
|
|
</span>
|
|
</div>
|
|
|
|
<!-- Tabs Navigation -->
|
|
<div class="flex flex-wrap gap-1 mb-4 p-1 rounded-lg"
|
|
style="background: rgb(var(--color-bg-secondary)); border: 1px solid rgb(var(--color-border));">
|
|
<button class="flex-1 px-4 py-2 text-sm font-medium rounded-md transition-all duration-200"
|
|
:class="activeTab === 'basic' ? 'bg-primary text-white shadow' : 'hover:bg-opacity-50'"
|
|
style="color: rgb(var(--color-text));" @click="activeTab = 'basic'">
|
|
<i class="fa-solid fa-info-circle mr-1"></i> Basic
|
|
</button>
|
|
<button class="flex-1 px-4 py-2 text-sm font-medium rounded-md transition-all duration-200"
|
|
:class="activeTab === 'members' ? 'bg-primary text-white shadow' : 'hover:bg-opacity-50'"
|
|
style="color: rgb(var(--color-text));" @click="activeTab = 'members'">
|
|
<i class="fa-solid fa-users mr-1"></i> Members
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Form -->
|
|
<div class="space-y-4">
|
|
|
|
<!-- Tab: Basic Information (includes Seq) -->
|
|
<div x-show="activeTab === 'basic'" x-transition:enter="transition ease-out duration-200"
|
|
x-transition:enter-start="opacity-0 transform -translate-x-4"
|
|
x-transition:enter-end="opacity-100 transform translate-x-0">
|
|
|
|
<!-- Basic Info Section -->
|
|
<div class="mb-4">
|
|
<h4 class="font-medium text-sm mb-3 flex items-center gap-2">
|
|
<i class="fa-solid fa-info-circle text-primary"></i> Basic Information
|
|
</h4>
|
|
<div class="grid grid-cols-2 gap-4">
|
|
<div>
|
|
<label class="label">
|
|
<span class="label-text font-medium text-sm">Group Code <span class="text-error">*</span></span>
|
|
<span class="label-text-alt text-xs">Auto-generated from name</span>
|
|
</label>
|
|
<input type="text" class="input input-bordered font-mono uppercase w-full"
|
|
:class="errors.TestSiteCode && 'input-error'" x-model="form.TestSiteCode" placeholder="Auto-generated"
|
|
maxlength="10" />
|
|
<label class="label" x-show="errors.TestSiteCode">
|
|
<span class="label-text-alt text-error text-xs" x-text="errors.TestSiteCode"></span>
|
|
</label>
|
|
</div>
|
|
<div>
|
|
<label class="label">
|
|
<span class="label-text font-medium text-sm">Group Name <span class="text-error">*</span></span>
|
|
</label>
|
|
<input type="text" class="input input-bordered w-full" :class="errors.TestSiteName && 'input-error'"
|
|
x-model="form.TestSiteName" placeholder="e.g., Lipid Profile, CBC Panel" />
|
|
<label class="label" x-show="errors.TestSiteName">
|
|
<span class="label-text-alt text-error text-xs">Group name is required</span>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
<div class="mt-3">
|
|
<label class="label">
|
|
<span class="label-text font-medium text-sm">Description</span>
|
|
</label>
|
|
<textarea class="textarea textarea-bordered w-full" x-model="form.Description"
|
|
placeholder="e.g., Comprehensive lipid analysis panel..." rows="2"></textarea>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Sequencing Section -->
|
|
<div>
|
|
<h4 class="font-medium text-sm mb-3 flex items-center gap-2">
|
|
<i class="fa-solid fa-list-ol text-primary"></i> Sequencing & Visibility
|
|
</h4>
|
|
<div class="grid grid-cols-4 gap-3">
|
|
<div>
|
|
<label class="label">
|
|
<span class="label-text font-medium text-sm">Seq (Screen)</span>
|
|
</label>
|
|
<input type="number" class="input input-bordered w-full" x-model.number="form.SeqScr" placeholder="0" />
|
|
</div>
|
|
<div>
|
|
<label class="label">
|
|
<span class="label-text font-medium text-sm">Seq (Report)</span>
|
|
</label>
|
|
<input type="number" class="input input-bordered w-full" x-model.number="form.SeqRpt" placeholder="0" />
|
|
</div>
|
|
<div>
|
|
<label class="label">
|
|
<span class="label-text font-medium text-sm">Indent</span>
|
|
</label>
|
|
<input type="number" class="input input-bordered w-full" x-model.number="form.IndentLeft"
|
|
placeholder="0" />
|
|
</div>
|
|
<div class="flex items-center">
|
|
<label class="label cursor-pointer justify-start gap-2">
|
|
<input type="checkbox" class="checkbox checkbox-sm" x-model="form.CountStat" :true-value="1"
|
|
:false-value="0" />
|
|
<span class="label-text text-sm">Count in Statistics</span>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
<div class="grid grid-cols-2 gap-4 mt-3">
|
|
<label class="label cursor-pointer justify-start gap-2">
|
|
<input type="checkbox" class="checkbox checkbox-sm" x-model="form.VisibleScr" :true-value="1"
|
|
:false-value="0" />
|
|
<span class="label-text text-sm">Visible on Screen</span>
|
|
</label>
|
|
<label class="label cursor-pointer justify-start gap-2">
|
|
<input type="checkbox" class="checkbox checkbox-sm" x-model="form.VisibleRpt" :true-value="1"
|
|
:false-value="0" />
|
|
<span class="label-text text-sm">Visible on Report</span>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Tab: Group Members -->
|
|
<div x-show="activeTab === 'members'" x-transition:enter="transition ease-out duration-200"
|
|
x-transition:enter-start="opacity-0 transform -translate-x-4"
|
|
x-transition:enter-end="opacity-100 transform translate-x-0">
|
|
<div class="space-y-4">
|
|
<div class="flex items-center justify-between p-3 rounded-lg bg-primary bg-opacity-10"
|
|
style="border: 1px solid rgb(var(--color-primary));">
|
|
<div class="flex items-center gap-2">
|
|
<i class="fa-solid fa-users text-primary"></i>
|
|
<span class="font-medium text-sm">Group Members (<span
|
|
x-text="form.groupMembers?.length || 0"></span>)</span>
|
|
</div>
|
|
<button class="btn btn-sm btn-primary" @click="showMemberSelector = true">
|
|
<i class="fa-solid fa-plus mr-1"></i> Add Member
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Member List -->
|
|
<template x-if="!form.groupMembers || form.groupMembers.length === 0">
|
|
<div class="text-center py-8 rounded-lg border border-dashed"
|
|
style="border-color: rgb(var(--color-border));">
|
|
<i class="fa-solid fa-inbox text-3xl opacity-40 mb-2"></i>
|
|
<p class="opacity-60">No members added yet</p>
|
|
<p class="text-xs opacity-50">Click "Add Member" to add tests to this group</p>
|
|
</div>
|
|
</template>
|
|
|
|
<template x-if="form.groupMembers && form.groupMembers.length > 0">
|
|
<div class="overflow-x-auto">
|
|
<table class="table table-xs">
|
|
<thead>
|
|
<tr class="bg-base-200">
|
|
<th>Code</th>
|
|
<th>Name</th>
|
|
<th>Type</th>
|
|
<th>Seq</th>
|
|
<th class="w-10">Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<template x-for="(member, index) in form.groupMembers" :key="index">
|
|
<tr class="hover">
|
|
<td><code class="text-xs" x-text="member.TestSiteCode"></code></td>
|
|
<td x-text="member.TestSiteName"></td>
|
|
<td>
|
|
<span class="badge badge-xs" :class="{
|
|
'badge-info': member.MemberTypeCode === 'TEST',
|
|
'badge-success': member.MemberTypeCode === 'PARAM'
|
|
}" x-text="member.MemberTypeCode || 'TEST'"></span>
|
|
</td>
|
|
<td>
|
|
<input type="number" class="input input-xs w-16" x-model.number="member.SeqScr"
|
|
placeholder="0" />
|
|
</td>
|
|
<td>
|
|
<button class="btn btn-ghost btn-xs btn-square text-error" @click="removeMember(index)">
|
|
<i class="fa-solid fa-times"></i>
|
|
</button>
|
|
</td>
|
|
</tr>
|
|
</template>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</template>
|
|
|
|
<!-- Quick Add Common Tests -->
|
|
<div class="p-3 rounded-lg border border-dashed" style="border-color: rgb(var(--color-border));">
|
|
<h4 class="font-medium text-sm mb-2 flex items-center gap-2">
|
|
<i class="fa-solid fa-bolt text-amber-500"></i>
|
|
Quick Add Common Tests
|
|
</h4>
|
|
<div class="flex flex-wrap gap-2">
|
|
<button class="btn btn-xs btn-outline" @click="addCommonMember('HBA1C', 'TEST')">HbA1c</button>
|
|
<button class="btn btn-xs btn-outline" @click="addCommonMember('GLU_R', 'TEST')">Glucose (Random)</button>
|
|
<button class="btn btn-xs btn-outline" @click="addCommonMember('GLU_F', 'TEST')">Glucose
|
|
(Fasting)</button>
|
|
<button class="btn btn-xs btn-outline" @click="addCommonMember('CHOL', 'TEST')">Cholesterol</button>
|
|
<button class="btn btn-xs btn-outline" @click="addCommonMember('TG', 'TEST')">Triglycerides</button>
|
|
<button class="btn btn-xs btn-outline" @click="addCommonMember('HDL', 'TEST')">HDL</button>
|
|
<button class="btn btn-xs btn-outline" @click="addCommonMember('LDL', 'TEST')">LDL</button>
|
|
<button class="btn btn-xs btn-outline" @click="addCommonMember('VLDL', 'TEST')">VLDL</button>
|
|
</div>
|
|
<div class="mt-2 flex flex-wrap gap-2">
|
|
<button class="btn btn-xs btn-outline" @click="addCommonMember('RBC', 'PARAM')">RBC</button>
|
|
<button class="btn btn-xs btn-outline" @click="addCommonMember('WBC', 'PARAM')">WBC</button>
|
|
<button class="btn btn-xs btn-outline" @click="addCommonMember('HGB', 'PARAM')">HGB</button>
|
|
<button class="btn btn-xs btn-outline" @click="addCommonMember('HCT', 'PARAM')">HCT</button>
|
|
<button class="btn btn-xs btn-outline" @click="addCommonMember('PLT', 'PARAM')">PLT</button>
|
|
<button class="btn btn-xs btn-outline" @click="addCommonMember('MCV', 'PARAM')">MCV</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<!-- Member Selector Modal (outside tabs) -->
|
|
<div x-show="showMemberSelector" x-cloak class="modal-overlay" x-transition>
|
|
<div class="modal-content p-6 max-w-3xl w-full max-h-[80vh] overflow-y-auto" @click.stop
|
|
x-transition:enter="transition ease-out duration-200" x-transition:enter-start="opacity-0 transform scale-95"
|
|
x-transition:enter-end="opacity-100 transform scale-100" x-transition:leave="transition ease-in duration-150"
|
|
x-transition:leave-start="opacity-100 transform scale-100"
|
|
x-transition:leave-end="opacity-0 transform scale-95">
|
|
<div class="flex items-center justify-between mb-4">
|
|
<h4 class="font-bold text-lg">Select Test Members</h4>
|
|
<button class="btn btn-ghost btn-sm btn-square" @click="showMemberSelector = false">
|
|
<i class="fa-solid fa-times"></i>
|
|
</button>
|
|
</div>
|
|
|
|
<div class="mb-4">
|
|
<input type="text" class="input input-bordered w-full" placeholder="Search tests..." x-model="memberSearch" />
|
|
</div>
|
|
|
|
<div class="space-y-2 max-h-96 overflow-y-auto">
|
|
<template
|
|
x-for="test in availableTests.filter(t => t.TestSiteName?.toLowerCase().includes(memberSearch?.toLowerCase() || ''))"
|
|
:key="test.TestSiteID">
|
|
<label
|
|
class="flex items-center gap-3 p-3 rounded-lg border cursor-pointer hover:bg-opacity-50 transition-colors"
|
|
style="background: rgb(var(--color-bg-secondary)); border-color: rgb(var(--color-border));">
|
|
<input type="checkbox" class="checkbox checkbox-sm"
|
|
:checked="form.groupMembers?.some(m => m.TestSiteID === test.TestSiteID)"
|
|
@change="toggleMember(test)" />
|
|
<div class="flex-1">
|
|
<div class="font-medium" x-text="test.TestSiteName"></div>
|
|
<div class="text-xs flex items-center gap-2">
|
|
<code x-text="test.TestSiteCode"></code>
|
|
<span class="badge badge-xs" :class="{
|
|
'badge-info': test.TypeCode === 'TEST',
|
|
'badge-success': test.TypeCode === 'PARAM'
|
|
}" x-text="test.TypeCode"></span>
|
|
</div>
|
|
</div>
|
|
</label>
|
|
</template>
|
|
</div>
|
|
|
|
<div class="flex justify-end gap-2 mt-4 pt-4 border-t" style="border-color: rgb(var(--color-border));">
|
|
<button class="btn btn-ghost" @click="showMemberSelector = false">Cancel</button>
|
|
<button class="btn btn-primary" @click="showMemberSelector = false; $forceUpdate()">Done</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Actions -->
|
|
<div class="flex gap-3 mt-6 pt-4 border-t" style="border-color: rgb(var(--color-border));">
|
|
<button class="btn btn-ghost flex-1" @click="closeModal()">
|
|
<i class="fa-solid fa-times mr-2"></i> Cancel
|
|
</button>
|
|
<button class="btn btn-primary flex-1" @click="save()" :disabled="saving">
|
|
<span x-show="saving" class="loading loading-spinner loading-sm mr-2"></span>
|
|
<i x-show="!saving" class="fa-solid fa-save mr-2"></i>
|
|
<span x-text="saving ? 'Saving...' : (isEditing ? 'Update Group' : 'Create Group')"></span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div> |