PDFs were being saved based on collection date but links were using REQDATE, causing mismatches. Updated both regular and English PDF links to use COLLECTIONDATE for consistency.
379 lines
16 KiB
PHP
379 lines
16 KiB
PHP
<?php
|
|
$configFile = include __DIR__ . '/config.php';
|
|
$roleMap = ['superuser' => 'superuser', 'admin' => 'admin', 'lab analyst' => 'lab', 'phlebotomist' => 'phlebo', 'customer service' => 'cs'];
|
|
$userRole = strtolower(session('userrole') ?? '');
|
|
$configKey = $roleMap[$userRole] ?? '';
|
|
$previewEnabled = $configFile[$configKey]['previewEnabled'] ?? false;
|
|
$userRoleId = (int) session('userroleid');
|
|
$userId = (string) session('userid');
|
|
$userLevel = (int) session()->get('userlevel');
|
|
$isPhlebo = $userRoleId === 3;
|
|
$isAdminSuper = !in_array($userRoleId, [3, 4]);
|
|
$canUnval = $userLevel <= 1;
|
|
?>
|
|
<div class="card bg-base-100 shadow-xl border border-base-200 h-full">
|
|
<div class="card-body p-0 flex flex-col h-full">
|
|
|
|
<!-- Header & Filters -->
|
|
<div class="p-4 border-b border-base-200 bg-base-50">
|
|
<div class="flex flex-col md:flex-row justify-between items-center gap-4 mb-4">
|
|
<div class="flex-1">
|
|
<h2 class="text-2xl font-bold flex items-center gap-2 text-base-content">
|
|
<i class="fa fa-chart-bar text-primary"></i> Requests Overview
|
|
</h2>
|
|
</div>
|
|
|
|
<!-- Status Filters -->
|
|
<div class="join shadow-sm bg-base-100 rounded-lg">
|
|
<button @click="setFilterKey('Total')"
|
|
:class="filterKey === 'Total' ? 'btn-active btn-neutral text-white' : 'btn-ghost'"
|
|
class="btn btn-sm join-item">
|
|
All <span class="badge badge-sm badge-ghost ml-1" x-text="counters.Total"></span>
|
|
</button>
|
|
<button @click="setFilterKey('Pend')"
|
|
:class="filterKey === 'Pend' ? 'btn-active btn-status-pend' : 'btn-ghost'"
|
|
class="btn btn-sm join-item">
|
|
Pending <span class="badge badge-sm badge-status-pend ml-1" x-text="counters.Pend"></span>
|
|
</button>
|
|
<button @click="setFilterKey('Coll')"
|
|
:class="filterKey === 'Coll' ? 'btn-active btn-status-coll' : 'btn-ghost'"
|
|
class="btn btn-sm join-item">
|
|
Coll <span class="badge badge-sm badge-status-coll ml-1" x-text="counters.Coll"></span>
|
|
</button>
|
|
<button @click="setFilterKey('Recv')"
|
|
:class="filterKey === 'Recv' ? 'btn-active btn-status-recv' : 'btn-ghost'" class="btn btn-sm join-item">
|
|
Recv <span class="badge badge-sm badge-status-recv ml-1" x-text="counters.Recv"></span>
|
|
</button>
|
|
<button @click="setFilterKey('Inc')"
|
|
:class="filterKey === 'Inc' ? 'btn-active btn-status-inc' : 'btn-ghost'" class="btn btn-sm join-item">
|
|
Inc <span class="badge badge-sm badge-status-inc ml-1" x-text="counters.Inc"></span>
|
|
</button>
|
|
<button @click="setFilterKey('Fin')"
|
|
:class="filterKey === 'Fin' ? 'btn-active btn-status-fin' : 'btn-ghost'"
|
|
class="btn btn-sm join-item">
|
|
Fin <span class="badge badge-sm badge-status-fin ml-1" x-text="counters.Fin"></span>
|
|
</button>
|
|
<button @click="setFilterKey('Validated')"
|
|
:class="filterKey === 'Validated' ? 'btn-active btn-primary text-white' : 'btn-ghost'"
|
|
class="btn btn-sm join-item">
|
|
Val <span class="badge badge-sm badge-primary ml-1" x-text="validatedCount"></span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Search & Date Filter -->
|
|
<div
|
|
class="flex flex-col md:flex-row gap-3 items-end bg-base-100 p-3 rounded-lg border border-base-200 shadow-sm">
|
|
<div class="form-control">
|
|
<label class="label text-xs font-bold py-1 text-base-content/60">Date Range</label>
|
|
<div class="join">
|
|
<input type="date" class="input input-sm input-bordered join-item" x-model="filter.date1" />
|
|
<span class="join-item btn btn-sm btn-ghost no-animation bg-base-200 font-normal px-2">-</span>
|
|
<input type="date" class="input input-sm input-bordered join-item" x-model="filter.date2" />
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex gap-2">
|
|
<button class="btn btn-sm btn-primary" @click='fetchList()'><i class='fa fa-search'></i> Search</button>
|
|
<button class="btn btn-sm btn-neutral" @click='reset()'><i class='fa fa-sync-alt'></i> Reset</button>
|
|
</div>
|
|
|
|
<span class="flex-1"></span>
|
|
|
|
<div class="form-control w-full md:w-auto">
|
|
<label class='input input-sm input-bordered'>
|
|
<i class="fa fa-filter"></i>
|
|
<input type="text" placeholder="Type to filter..." x-model="filterTable" @input.debounce.300ms="setFilterTable(filterTable)" />
|
|
</label>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex-1 px-4 pb-4">
|
|
<template x-if="isLoading">
|
|
<table class="table table-xs table-zebra w-full">
|
|
<thead class="bg-base-100 sticky top-0 z-10">
|
|
<tr>
|
|
<th style='width:9%;'>
|
|
<div class="skeleton h-4 w-20"></div>
|
|
</th>
|
|
<th style='width:18%;'>
|
|
<div class="skeleton h-4 w-32"></div>
|
|
</th>
|
|
<th style='width:9%;'>
|
|
<div class="skeleton h-4 w-16"></div>
|
|
</th>
|
|
<th style='width:9%;'>
|
|
<div class="skeleton h-4 w-16"></div>
|
|
</th>
|
|
<th style='width:9%;'>
|
|
<div class="skeleton h-4 w-16"></div>
|
|
</th>
|
|
<th style='width:9%;'>
|
|
<div class="skeleton h-4 w-20"></div>
|
|
</th>
|
|
<th style='width:20%;'>
|
|
<div class="skeleton h-4 w-32"></div>
|
|
</th>
|
|
<th style='width:4%;'>
|
|
<div class="skeleton h-4 w-12"></div>
|
|
</th>
|
|
<th style='width:8%;'>
|
|
<div class="skeleton h-4 w-16"></div>
|
|
</th>
|
|
<th style='width:5%;'>
|
|
<div class="skeleton h-4 w-12"></div>
|
|
</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<template x-for="i in 5" :key="i">
|
|
<tr>
|
|
<td colspan="10">
|
|
<div class="skeleton h-4 w-full"></div>
|
|
</td>
|
|
</tr>
|
|
</template>
|
|
</tbody>
|
|
</table>
|
|
</template>
|
|
<template x-if="!isLoading && !list.length">
|
|
<div class="text-center py-10">
|
|
<i class="fa fa-inbox text-4xl mb-2 opacity-50"></i>
|
|
<p>No records found</p>
|
|
</div>
|
|
</template>
|
|
<template x-if="!isLoading && list.length">
|
|
<table class="table table-xs table-zebra w-full">
|
|
<thead class="bg-base-100 sticky top-0 z-10">
|
|
<tr>
|
|
<th style='width:9%;' @click="sort('REQDATE')"
|
|
class="cursor-pointer hover:bg-base-200 transition-colors select-none">
|
|
<div class="flex items-center gap-1">
|
|
Order Datetime
|
|
<i class="fa text-xs"
|
|
:class="sortCol === 'REQDATE' ? (sortAsc ? 'fa-sort-up' : 'fa-sort-down') : 'fa-sort opacity-20'"></i>
|
|
</div>
|
|
</th>
|
|
<th style='width:18%;' @click="sort('Name')"
|
|
class="cursor-pointer hover:bg-base-200 transition-colors select-none">
|
|
<div class="flex items-center gap-1">
|
|
Patient Name
|
|
<i class="fa text-xs"
|
|
:class="sortCol === 'Name' ? (sortAsc ? 'fa-sort-up' : 'fa-sort-down') : 'fa-sort opacity-20'"></i>
|
|
</div>
|
|
</th>
|
|
<th style='width:9%;' @click="sort('SP_ACCESSNUMBER')"
|
|
class="cursor-pointer hover:bg-base-200 transition-colors select-none">
|
|
<div class="flex items-center gap-1">
|
|
No Lab
|
|
<i class="fa text-xs"
|
|
:class="sortCol === 'SP_ACCESSNUMBER' ? (sortAsc ? 'fa-sort-up' : 'fa-sort-down') : 'fa-sort opacity-20'"></i>
|
|
</div>
|
|
</th>
|
|
<th style='width:9%;' @click="sort('HOSTORDERNUMBER')"
|
|
class="cursor-pointer hover:bg-base-200 transition-colors select-none">
|
|
<div class="flex items-center gap-1">
|
|
No Register
|
|
<i class="fa text-xs"
|
|
:class="sortCol === 'HOSTORDERNUMBER' ? (sortAsc ? 'fa-sort-up' : 'fa-sort-down') : 'fa-sort opacity-20'"></i>
|
|
</div>
|
|
</th>
|
|
<th style='width:9%;' @click="sort('REFF')"
|
|
class="cursor-pointer hover:bg-base-200 transition-colors select-none">
|
|
<div class="flex items-center gap-1">
|
|
Reff
|
|
<i class="fa text-xs"
|
|
:class="sortCol === 'REFF' ? (sortAsc ? 'fa-sort-up' : 'fa-sort-down') : 'fa-sort opacity-20'"></i>
|
|
</div>
|
|
</th>
|
|
<th style='width:9%;' @click="sort('DOC')"
|
|
class="cursor-pointer hover:bg-base-200 transition-colors select-none">
|
|
<div class="flex items-center gap-1">
|
|
Doctor
|
|
<i class="fa text-xs"
|
|
:class="sortCol === 'DOC' ? (sortAsc ? 'fa-sort-up' : 'fa-sort-down') : 'fa-sort opacity-20'"></i>
|
|
</div>
|
|
</th>
|
|
<th style='width:20%;'>Tests</th>
|
|
<th style='width:4%;'>ResTo</th>
|
|
<th style='width:5%;'>Val</th>
|
|
<th style='width:3%;'>PDF</th>
|
|
<th style='width:5%;'>Result</th>
|
|
<th style='width:5%;'></th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<template x-for="req in paginated" :key="req.SP_ACCESSNUMBER">
|
|
<tr class="hover:bg-base-300">
|
|
<td x-text="req.REQDATE" :class="statusRowBg[req.STATS]"></td>
|
|
<td x-text="req.Name" :class="statusRowBg[req.STATS]"></td>
|
|
<td x-text="req.SP_ACCESSNUMBER" class="font-bold" :class="statusRowBg[req.STATS]"></td>
|
|
<td x-text="req.HOSTORDERNUMBER" class="font-bold" :class="statusRowBg[req.STATS]"></td>
|
|
<td x-text="req.REFF" :class="statusRowBg[req.STATS]"></td>
|
|
<td x-text="req.DOC" :class="statusRowBg[req.STATS]"></td>
|
|
<td x-text="req.TESTS" :class="statusRowBg[req.STATS]"></td>
|
|
<td x-text="req.ODR_CRESULT_TO"></td>
|
|
<td>
|
|
<div class='text-xs'>
|
|
<p>1: <span x-text="req.VAL1USER"></span></p>
|
|
<p>2: <span x-text="req.VAL2USER"></span></p>
|
|
</div>
|
|
</td>
|
|
<td class="text-center">
|
|
<i class="fa fa-file-pdf" :class="req.ISPDF ? 'text-success' : 'text-base-300'"></i>
|
|
</td>
|
|
<td>
|
|
<?php if ($isPhlebo): ?>
|
|
<span class="text-xs font-bold" :class="req.VAL1USER && req.VAL2USER ? 'text-success' : 'text-warning'" x-text="req.VAL1USER && req.VAL2USER ? 'Ready' : 'Pending'"></span>
|
|
<?php else: ?>
|
|
<?php if ($previewEnabled): ?>
|
|
<template x-if="['Pend', 'PartColl', 'Coll'].includes(req.STATS)">
|
|
<button disabled class="btn btn-xs w-full btn-warning opacity-70 cursor-not-allowed">
|
|
<i class="fa fa-clock mr-1"></i>
|
|
<span class="text-xs">Pending</span>
|
|
</button>
|
|
</template>
|
|
<template x-if="!['Pend', 'PartColl', 'Coll'].includes(req.STATS) && (!req.VAL1USER || !req.VAL2USER)">
|
|
<button @click="openPreviewDialog(req)"
|
|
class="btn btn-xs w-full btn-warning">
|
|
<i class="fa fa-eye mr-1"></i>
|
|
<span class="text-xs">Preview</span>
|
|
</button>
|
|
</template>
|
|
<template x-if="req.VAL1USER && req.VAL2USER">
|
|
<div class="dropdown dropdown-top dropdown-end dropdown-hover">
|
|
<div tabindex="0" role="button"
|
|
class="btn btn-xs w-full btn-success text-white">
|
|
<i class="fa fa-clipboard-check mr-1"></i>
|
|
<span class="text-xs">Ready</span>
|
|
</div>
|
|
<ul tabindex="0"
|
|
class="dropdown-content menu bg-base-100 rounded-box z-50 w-40 p-2 shadow-lg border border-base-300 text-xs">
|
|
<?php else: ?>
|
|
<div class="dropdown dropdown-top dropdown-end dropdown-hover">
|
|
<div tabindex="0" role="button"
|
|
class="btn btn-xs w-full"
|
|
:class="req.VAL1USER && req.VAL2USER ? 'btn-success text-white' : 'btn-warning'">
|
|
<i class="fa fa-clipboard-check mr-1"></i>
|
|
<span class="text-xs" x-text="req.VAL1USER && req.VAL2USER ? 'Ready' : 'Pending'"></span>
|
|
</div>
|
|
<ul tabindex="0"
|
|
class="dropdown-content menu bg-base-100 rounded-box z-50 w-40 p-2 shadow-lg border border-base-300 text-xs">
|
|
<?php endif; ?>
|
|
<template x-if="req.VAL1USER && req.VAL2USER">
|
|
<div>
|
|
<li>
|
|
<a :href="'<?=base_url('report/');?>' + req.SP_ACCESSNUMBER" target="_blank">
|
|
<i class="fa fa-print mr-2"></i> Print
|
|
</a>
|
|
</li>
|
|
<li>
|
|
<a :href="'http://glenlis/pdf/' + req.COLLECTIONDATE.split(' ')[0].split('-').join('/') + '/' + req.SP_ACCESSNUMBER + '.pdf'" target="_blank">
|
|
<i class="fa fa-file-pdf mr-2"></i> PDF
|
|
</a>
|
|
</li>
|
|
<li x-show="req.REPORT_LANG == 1">
|
|
<a :href="'<?=base_url('report/');?>' + req.SP_ACCESSNUMBER + '/print/eng'" target="_blank">
|
|
<i class="fa fa-print mr-2"></i> Print Eng
|
|
</a>
|
|
</li>
|
|
<li x-show="req.REPORT_LANG == 1">
|
|
<a :href="'http://glenlis/pdf/' + req.COLLECTIONDATE.split(' ')[0].split('-').join('/') + '/' + req.SP_ACCESSNUMBER + '_eng.pdf'" target="_blank">
|
|
<i class="fa fa-file-pdf mr-2"></i> PDF Eng
|
|
</a>
|
|
</li>
|
|
<?php if ($isAdminSuper): ?>
|
|
<li>
|
|
<a @click="openGenerateDialog(req.SP_ACCESSNUMBER)">
|
|
<i class="fa fa-file-pdf mr-2"></i>
|
|
<span>Generate Result</span>
|
|
</a>
|
|
</li>
|
|
<li x-show="!req.REPORT_LANG || req.REPORT_LANG != 1">
|
|
<a @click="openEngResultDialog(req)">
|
|
<i class="fa fa-language mr-2"></i>
|
|
<span>Create Eng Result</span>
|
|
</a>
|
|
</li>
|
|
<?php endif; ?>
|
|
</div>
|
|
</template>
|
|
<?php if (!$previewEnabled): ?>
|
|
<template x-if="!req.VAL1USER || !req.VAL2USER">
|
|
<div>
|
|
<li class="disabled opacity-50 cursor-not-allowed">
|
|
<span class="flex items-center">
|
|
<i class="fa fa-clock mr-2"></i> Result Pending
|
|
</span>
|
|
</li>
|
|
</div>
|
|
</template>
|
|
<?php endif; ?>
|
|
</ul>
|
|
<?php if ($previewEnabled): ?>
|
|
</template>
|
|
<?php else: ?>
|
|
</div>
|
|
<?php endif; ?>
|
|
<?php endif; ?>
|
|
</td>
|
|
<td>
|
|
<div class="dropdown dropdown-top dropdown-end dropdown-hover">
|
|
<div tabindex="0" role="button" class="btn btn-xs btn-primary w-full">
|
|
<i class="fa fa-cog mr-1"></i> Actions
|
|
</div>
|
|
<ul tabindex="0"
|
|
class="dropdown-content menu bg-base-100 rounded-box z-50 w-48 p-2 shadow-lg border border-base-300 text-xs">
|
|
<?php if ($isAdminSuper): ?>
|
|
<li x-show="req.ISVAL == 1 && req.ISPENDING != 1 && (req.VAL1USER == '<?= $userId; ?>' || req.VAL2USER == '<?= $userId; ?>')">
|
|
<?php if ($canUnval): ?>
|
|
<a @click="openUnvalDialog(req.SP_ACCESSNUMBER)" class="text-error hover:bg-error/10">
|
|
<i class="fa fa-times-circle mr-2"></i> UnVal
|
|
</a>
|
|
<?php endif; ?>
|
|
</li>
|
|
<?php endif; ?>
|
|
<li>
|
|
<a @click="openSampleDialog(req.SP_ACCESSNUMBER)">
|
|
<i class="fa fa-vial mr-2 text-success"></i> View Samples
|
|
</a>
|
|
</li>
|
|
<li>
|
|
<a @click="openAuditDialog(req.SP_ACCESSNUMBER)">
|
|
<i class="fa fa-history mr-2 text-info"></i> View Audit Trail
|
|
</a>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
</template>
|
|
</tbody>
|
|
</table>
|
|
</template>
|
|
</div>
|
|
|
|
<!-- Pagination Control -->
|
|
<div class="p-2 border-t border-base-200 bg-base-50 flex justify-between items-center"
|
|
x-show="!isLoading && list.length > 0">
|
|
<div class="text-xs text-base-content/60">
|
|
Showing <span class="font-bold" x-text="((currentPage - 1) * pageSize) + 1"></span> to
|
|
<span class="font-bold" x-text="Math.min(currentPage * pageSize, filtered.length)"></span> of
|
|
<span class="font-bold" x-text="filtered.length"></span> entries
|
|
</div>
|
|
<div class="join">
|
|
<button class="join-item btn btn-sm" @click="prevPage()" :disabled="currentPage === 1">
|
|
<i class="fa fa-chevron-left"></i>
|
|
</button>
|
|
<button class="join-item btn btn-sm no-animation bg-base-100 cursor-default">
|
|
Page <span x-text="currentPage"></span> / <span x-text="totalPages"></span>
|
|
</button>
|
|
<button class="join-item btn btn-sm" @click="nextPage()" :disabled="currentPage === totalPages">
|
|
<i class="fa fa-chevron-right"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|