feat: Implement status-based row coloring and enhance validation dialog
This commit implements a comprehensive status color system across the dashboard and validation interfaces, ensuring visual consistency between table rows and filter buttons. Color System Changes: - Updated statusRowBg mapping in script_requests.php with custom hex colors: * Pend: white (#ffffff) with black text * PartColl: pink (#ff99aa) with black text * Coll: red (#d63031) with white text * PartRecv: light blue (#a0c0d9) with black text * Recv: blue (#0984e3) with white text * Inc: yellow (#ffff00) with black text * Fin: green (#008000) with white text - Added custom CSS button classes in layout.php matching row background colors - Applied color backgrounds to table rows (Order through Tests columns) - Removed hardcoded text-white classes, now using dynamic text colors from mapping UI/UX Improvements: - Table rows now have consistent color-coded backgrounds based on request status - Filter button badges match their corresponding row background colors - Yellow status uses black text for better readability - Swapped Coll (yellow) and Inc (orange) colors as requested Validation Dialog Enhancement: - Updated dialog_val.php iframe to use dynamic URL generation - Removed preview type selection (ID, EN, PDF options) - uses default only - Added getPreviewUrl() method in script_validation.php - Now uses same URL pattern as preview dialog: http://glenlis/spooler_db/main_dev.php?acc={accessnumber} Documentation Updates: - Added Serena MCP tool usage guidelines to AGENTS.md - Renamed CHECKLIST.md to TODO.md - Removed CLAUDE.md Technical Details: - Color mappings now include both background and text color classes - Implemented using Tailwind arbitrary values for precise hex color matching - Status buttons use btn-status-{status} and badge-status-{status} classes - All 7 columns from Order through Tests have status-colored backgrounds
This commit is contained in:
parent
e649b6c398
commit
01908bb002
10
AGENTS.md
10
AGENTS.md
@ -6,6 +6,16 @@ This file provides guidance to agents when working with code in this repository.
|
||||
|
||||
CodeIgniter 4 PHP application for laboratory management (GDC CMOD). Handles specimen tracking, request validation, and result management with role-based access control. SQL Server database with Firebird legacy patient data.
|
||||
|
||||
## Tool Usage
|
||||
|
||||
Always use Serena MCP tools for anything possible:
|
||||
- Use `serena_find_symbol` instead of grep when looking for classes, methods, or functions
|
||||
- Use `serena_search_for_pattern` instead of grep for code pattern searches
|
||||
- Use `serena_read_file` or `serena_replace_content` instead of Read/Edit tools
|
||||
- Use `serena_find_referencing_symbols` to find where symbols are used
|
||||
- Use `serena_replace_symbol_body` or `serena_insert_after_symbol` for code modifications
|
||||
- Only use Bash for shell commands (git, composer, php, etc.)
|
||||
|
||||
## Commands
|
||||
|
||||
```bash
|
||||
|
||||
120
CLAUDE.md
120
CLAUDE.md
@ -1,120 +0,0 @@
|
||||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
## Project Overview
|
||||
|
||||
This is a **CodeIgniter 4** PHP application for laboratory management (GDC CMOD - Laboratory Request Management System). It handles specimen collection tracking, request validation, and result management with role-based access control.
|
||||
|
||||
## Development Commands
|
||||
|
||||
```bash
|
||||
# Run PHP built-in server (from project root)
|
||||
php spark serve
|
||||
|
||||
# Run tests
|
||||
composer test
|
||||
```
|
||||
|
||||
**Note:** This is a Windows-based deployment using IIS/XAMPP. For production, configure a virtual host pointing to the `public/` folder.
|
||||
|
||||
## Database Configuration
|
||||
|
||||
- **Primary DB:** SQL Server (`GDC_CMOD.dbo`) via Microsoft ODBC Driver (MSOLEDBSQL)
|
||||
- **Legacy DB:** Firebird/InterBase (`GLENEAGLES` via ODBC) for patient data
|
||||
- **Connection:** `\Config\Database::connect()` returns MySQLi connection
|
||||
- **No CI4 Models** - uses raw SQL queries via `Database::connect()`
|
||||
|
||||
## Architecture
|
||||
|
||||
### Role-Based Access Control (RBAC)
|
||||
|
||||
| Role ID | Name | Route Prefix | Permissions |
|
||||
|---------|------|--------------|-------------|
|
||||
| 0 | Superuser | `/superuser` | Full access + Users CRUD |
|
||||
| 1 | Admin | `/admin` | Full access + Users CRUD |
|
||||
| 2 | Lab | `/lab` | Request validation, Sample collection |
|
||||
| 3 | Phlebo | `/phlebo` | Request validation, Sample collection |
|
||||
| 4 | CS | `/cs` | Request validation, Sample collection |
|
||||
|
||||
### Authentication Flow
|
||||
|
||||
1. `Auth::login()` - Verifies credentials against `GDC_CMOD.dbo.USERS`, sets session
|
||||
2. `RoleFilter` - Checks `session()->get('isLoggedIn')` and role ID
|
||||
3. `GuestFilter` - Redirects logged-in users to role-based dashboard
|
||||
|
||||
### Key Database Tables
|
||||
|
||||
- `GDC_CMOD.dbo.USERS` - Users with `USERID`, `USERROLEID`, `PASSWORD`
|
||||
- `GDC_CMOD.dbo.CM_REQUESTS` - Validation tracking (`ISVAL1`, `ISVAL2`, validation users/dates)
|
||||
- `GDC_CMOD.dbo.TUBES` - Sample collection status
|
||||
- `GDC_CMOD.dbo.V_DASHBOARD_DEV` - Dashboard data view
|
||||
- `glendb.dbo.*` - Legacy Firebird patient data
|
||||
|
||||
### Request Validation (Dual-Level)
|
||||
|
||||
Validation requires 2 different users to validate the same request:
|
||||
1. First validation sets `ISVAL1=1`, `VAL1USER`, `VAL1DATE`
|
||||
2. Second validation (different user) sets `ISVAL2=1`, `VAL2USER`, `VAL2DATE`
|
||||
|
||||
## Code Conventions
|
||||
|
||||
### Controllers
|
||||
- All extend `BaseController` (which extends `CodeIgniter\Controller`)
|
||||
- Use `ResponseTrait` for JSON APIs
|
||||
- Raw SQL queries via `\Config\Database::connect()->query()`
|
||||
|
||||
### Routing Pattern
|
||||
|
||||
```php
|
||||
$routes->group('prefix', ['filter' => 'role:N'], function($routes) {
|
||||
$routes->get('', 'Controller::index');
|
||||
$routes->get('api/resource', 'Controller::method');
|
||||
});
|
||||
```
|
||||
|
||||
### Session Structure
|
||||
|
||||
```php
|
||||
session()->set([
|
||||
'isLoggedIn' => true,
|
||||
'userid' => (string) $user['USERID'],
|
||||
'userroleid' => (int) $user['USERROLEID'],
|
||||
'userrole' => (string) $role,
|
||||
]);
|
||||
```
|
||||
|
||||
## Important Routes
|
||||
|
||||
| Route | Purpose |
|
||||
|-------|---------|
|
||||
| `/login`, `/logout` | Authentication |
|
||||
| `/label/coll/:accessnumber` | Zebra printer label (public) |
|
||||
| `/api/requests` | Dashboard data (date-filtered) |
|
||||
| `/api/requests/validate/:accessnumber` | Dual-level validation |
|
||||
| `/api/samples/collect/:accessnumber` | Mark sample collected |
|
||||
| `/api/samples/receive/:accessnumber` | Mark sample received (Admin/Superuser only) |
|
||||
|
||||
## Frontend Stack
|
||||
|
||||
- TailwindCSS + DaisyUI 5 (CDN)
|
||||
- Alpine.js for reactivity
|
||||
- Font Awesome 7 for icons
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### JSON API Response
|
||||
```php
|
||||
return $this->response->setJSON(['status' => 'success', 'data' => $result]);
|
||||
```
|
||||
|
||||
### Database Query
|
||||
```php
|
||||
$db = \Config\Database::connect();
|
||||
$result = $db->query("SELECT * FROM table WHERE col = ?", [$value])->getResultArray();
|
||||
```
|
||||
|
||||
### Date Formatting from SQL Server
|
||||
```php
|
||||
$row['DATE_COLUMN'] = date('Y-m-d H:i', strtotime($row['DATE_COLUMN']));
|
||||
```
|
||||
@ -1,11 +1,10 @@
|
||||
# Project Checklist: Glen RME & Lab Management System
|
||||
|
||||
**Last Updated:** January 26, 2026
|
||||
**Last Updated:** 20260202
|
||||
|
||||
Pending:
|
||||
- Restrict Print/Save-to-PDF to CS Role only (Lab can only preview, CS can print/save)
|
||||
- Add Dedicated Print Button (Trigger browser/system print dialog)
|
||||
- Update PDF Report Metadata (Replace 'Printed By' with validating user's name)
|
||||
- Reprint Label (Add functionality to reprint labels)
|
||||
- Print Result Audit (Track when result reports are printed/exported, log user and timestamp)
|
||||
|
||||
@ -28,6 +27,8 @@ Completed:
|
||||
- 16 : Remove 'UnCollect'
|
||||
- 17 : Audit Trail (Track all actions: validation, unvalidation, collection, uncollection)
|
||||
- 18 : Create Validate Page
|
||||
- 19 : Sync color with old gdc_cmod
|
||||
- 20 : Add Val1 Val2 on the result
|
||||
|
||||
|
||||
Addition on dev :
|
||||
@ -18,27 +18,27 @@
|
||||
All <span class="badge badge-sm badge-ghost ml-1" x-text="counters.Total"></span>
|
||||
</button>
|
||||
<button @click="filterKey = 'Pend'"
|
||||
:class="filterKey === 'Pend' ? 'btn-active btn-neutral text-white' : 'btn-ghost'"
|
||||
:class="filterKey === 'Pend' ? 'btn-active btn-status-pend' : 'btn-ghost'"
|
||||
class="btn btn-sm join-item">
|
||||
Pending <span class="badge badge-sm ml-1" x-text="counters.Pend"></span>
|
||||
Pending <span class="badge badge-sm badge-status-pend ml-1" x-text="counters.Pend"></span>
|
||||
</button>
|
||||
<button @click="filterKey = 'Coll'"
|
||||
:class="filterKey === 'Coll' ? 'btn-active btn-warning text-white' : 'btn-ghost'"
|
||||
:class="filterKey === 'Coll' ? 'btn-active btn-status-coll' : 'btn-ghost'"
|
||||
class="btn btn-sm join-item">
|
||||
Coll <span class="badge badge-sm badge-warning ml-1" x-text="counters.Coll"></span>
|
||||
Coll <span class="badge badge-sm badge-status-coll ml-1" x-text="counters.Coll"></span>
|
||||
</button>
|
||||
<button @click="filterKey = 'Recv'"
|
||||
:class="filterKey === 'Recv' ? 'btn-active btn-info text-white' : 'btn-ghost'" class="btn btn-sm join-item">
|
||||
Recv <span class="badge badge-sm badge-info ml-1" x-text="counters.Recv"></span>
|
||||
: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="filterKey = 'Inc'"
|
||||
:class="filterKey === 'Inc' ? 'btn-active btn-error text-white' : 'btn-ghost'" class="btn btn-sm join-item">
|
||||
Inc <span class="badge badge-sm badge-error ml-1" x-text="counters.Inc"></span>
|
||||
: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="filterKey = 'Fin'"
|
||||
:class="filterKey === 'Fin' ? 'btn-active btn-success text-white' : 'btn-ghost'"
|
||||
:class="filterKey === 'Fin' ? 'btn-active btn-status-fin' : 'btn-ghost'"
|
||||
class="btn btn-sm join-item">
|
||||
Fin <span class="badge badge-sm badge-success ml-1" x-text="counters.Fin"></span>
|
||||
Fin <span class="badge badge-sm badge-status-fin ml-1" x-text="counters.Fin"></span>
|
||||
</button>
|
||||
<button @click="filterKey = 'Validated'"
|
||||
:class="filterKey === 'Validated' ? 'btn-active btn-primary text-white' : 'btn-ghost'"
|
||||
@ -195,15 +195,15 @@
|
||||
<tbody>
|
||||
<template x-for="req in paginated" :key="req.SP_ACCESSNUMBER">
|
||||
<tr class="hover:bg-base-300">
|
||||
<td x-text="req.REQDATE"></td>
|
||||
<td x-text="req.Name"></td>
|
||||
<td x-text="req.SP_ACCESSNUMBER" class="font-bold cursor-pointer" :class="statusColor[req.STATS]"
|
||||
<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 cursor-pointer" :class="statusRowBg[req.STATS]"
|
||||
@click="openSampleDialog(req.SP_ACCESSNUMBER)"></td>
|
||||
<td x-text="req.HOSTORDERNUMBER" class="font-bold cursor-pointer" :class="statusColor[req.STATS]"
|
||||
<td x-text="req.HOSTORDERNUMBER" class="font-bold cursor-pointer" :class="statusRowBg[req.STATS]"
|
||||
@click="openSampleDialog(req.SP_ACCESSNUMBER)"></td>
|
||||
<td x-text="req.REFF"></td>
|
||||
<td x-text="req.DOC"></td>
|
||||
<td x-text="req.TESTS"></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='flex gap-1 items-center'>
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
|
||||
<!-- Header & Filters -->
|
||||
<div class="p-4 border-b border-base-200 bg-gradient-to-r from-primary/10 to-base-100">
|
||||
<div class="flex flex-col md:flex-row justify-between items-center gap-4">
|
||||
<div class="flex flex-col md:flex-row justify-between items-center gap-4 mb-2">
|
||||
<h2 class="text-2xl font-bold flex items-center gap-3">
|
||||
<span class="w-10 h-10 rounded-lg bg-primary text-primary-content flex items-center justify-center shadow-lg">
|
||||
<i class="fa fa-clipboard-check"></i>
|
||||
|
||||
@ -1,276 +0,0 @@
|
||||
<div class="card bg-base-100 shadow-xl h-full border border-base-200 overflow-hidden">
|
||||
<div class="card-body p-0 h-full flex flex-col">
|
||||
|
||||
<!-- Header & Statistics -->
|
||||
<div class="p-4 border-b border-base-200 bg-gradient-to-r from-blue-50 to-indigo-50">
|
||||
<div class="flex flex-col md:flex-row justify-between items-start md:items-center gap-4 mb-4">
|
||||
<div class="flex-1">
|
||||
<h2 class="text-2xl font-bold flex items-center gap-2 text-primary">
|
||||
<i class="fa fa-shield-alt"></i> Pending Validation
|
||||
</h2>
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-2 text-xs">
|
||||
<div class="badge badge-lg badge-outline gap-1 bg-base-100">
|
||||
<span class="inline-flex items-center justify-center w-2 h-2 rounded-full bg-base-300"></span>
|
||||
<span class="text-base-content/70">Not Started</span>
|
||||
<span class="badge badge-sm badge-ghost ml-1" x-text="valStats.notStarted"></span>
|
||||
</div>
|
||||
<div class="badge badge-lg badge-primary gap-1 bg-base-100 border-primary">
|
||||
<span class="inline-flex items-center justify-center w-2 h-2 rounded-full bg-primary"></span>
|
||||
<span class="text-primary font-medium">1st Val</span>
|
||||
<span class="badge badge-sm badge-primary ml-1" x-text="valStats.firstVal"></span>
|
||||
</div>
|
||||
<div class="badge badge-lg badge-success gap-1 bg-base-100 border-success">
|
||||
<span class="inline-flex items-center justify-center w-2 h-2 rounded-full bg-success"></span>
|
||||
<span class="text-success font-medium">Done</span>
|
||||
<span class="badge badge-sm badge-success ml-1" x-text="valStats.fullyValidated"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Progress Bar -->
|
||||
<div class="bg-base-100 rounded-lg p-3 border border-base-200 shadow-sm">
|
||||
<div class="flex justify-between items-center mb-2">
|
||||
<span class="text-xs font-medium text-base-content/70">Overall Progress</span>
|
||||
<span class="text-xs font-bold text-primary" x-text="valStats.progress + '%'"></span>
|
||||
</div>
|
||||
<div class="w-full bg-base-200 rounded-full h-2 overflow-hidden">
|
||||
<div class="h-full rounded-full transition-all duration-500 flex"
|
||||
:style="'width: ' + valStats.progress + '%'">
|
||||
<div class="h-full bg-primary flex-1 first-val-progress"></div>
|
||||
<div class="h-full bg-success flex-1 second-val-progress"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex justify-between mt-1 text-xs text-base-content/60">
|
||||
<span x-text="valStats.fullyValidated + ' fully validated'"></span>
|
||||
<span x-text="valStats.firstVal + ' need 2nd validation'"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Date Filter -->
|
||||
<div class="flex flex-col md:flex-row gap-3 items-end mt-4 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="fetchUnvalidated()">
|
||||
<i class="fa fa-search"></i> Search
|
||||
</button>
|
||||
<button class="btn btn-sm btn-neutral" @click="resetUnvalidated()">
|
||||
<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" />
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Table Section -->
|
||||
<div class="flex-1 overflow-y-auto px-4 pb-4">
|
||||
<!-- Legend -->
|
||||
<div class="flex items-center gap-4 mb-2 text-xs text-base-content/60">
|
||||
<span class="font-medium">Legend:</span>
|
||||
<div class="flex items-center gap-1">
|
||||
<span class="inline-flex items-center justify-center w-3 h-3 rounded-full bg-base-300"></span>
|
||||
<span>Pending</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-1">
|
||||
<span class="inline-flex items-center justify-center w-3 h-3 rounded-full bg-primary"></span>
|
||||
<span>1st Val</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-1">
|
||||
<span class="inline-flex items-center justify-center w-3 h-3 rounded-full bg-success"></span>
|
||||
<span>Done</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<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: 15%;">
|
||||
<div class="skeleton h-4 w-28"></div>
|
||||
</th>
|
||||
<th style="width: 12%;">
|
||||
<div class="skeleton h-4 w-24"></div>
|
||||
</th>
|
||||
<th style="width: 10%;">
|
||||
<div class="skeleton h-4 w-20"></div>
|
||||
</th>
|
||||
<th style="width: 10%;">
|
||||
<div class="skeleton h-4 w-20"></div>
|
||||
</th>
|
||||
<th style="width: 10%;">
|
||||
<div class="skeleton h-4 w-20"></div>
|
||||
</th>
|
||||
<th style="width: 10%;">
|
||||
<div class="skeleton h-4 w-20"></div>
|
||||
</th>
|
||||
<th style="width: 13%;">
|
||||
<div class="skeleton h-4 w-24"></div>
|
||||
</th>
|
||||
<th style="width: 8%;">
|
||||
<div class="skeleton h-4 w-16"></div>
|
||||
</th>
|
||||
<th style="width: 12%;">
|
||||
<div class="skeleton h-4 w-full"></div>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<template x-for="i in 5" :key="i">
|
||||
<tr>
|
||||
<td colspan="9">
|
||||
<div class="skeleton h-4 w-full"></div>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
</tbody>
|
||||
</table>
|
||||
</template>
|
||||
|
||||
<template x-if="!isLoading && !unvalidatedList.length">
|
||||
<div class="flex flex-col items-center justify-center py-16">
|
||||
<div class="relative">
|
||||
<div class="w-24 h-24 rounded-full bg-success/10 flex items-center justify-center animate-pulse">
|
||||
<i class="fa fa-check-circle text-6xl text-success"></i>
|
||||
</div>
|
||||
<div class="absolute -bottom-2 -right-2 w-8 h-8 bg-success rounded-full flex items-center justify-center text-white text-sm font-bold">
|
||||
<i class="fa fa-check"></i>
|
||||
</div>
|
||||
</div>
|
||||
<h3 class="text-xl font-bold text-success mt-4">All Caught Up!</h3>
|
||||
<p class="text-base-content/60 mt-1">No pending validations for this date range</p>
|
||||
<div class="flex gap-4 mt-4 text-sm text-base-content/70">
|
||||
<div class="text-center">
|
||||
<div class="text-2xl font-bold text-success" x-text="valStats.fullyValidated"></div>
|
||||
<div>Fully Validated</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template x-if="!isLoading && unvalidatedList.length">
|
||||
<table class="table table-xs table-zebra w-full">
|
||||
<thead class="bg-base-100 sticky top-0 z-10 shadow-sm">
|
||||
<tr>
|
||||
<th style="width: 15%;" @click="sort('Name')"
|
||||
class="cursor-pointer hover:bg-blue-100 transition-colors select-none">
|
||||
<div class="flex items-center gap-1">
|
||||
Patient
|
||||
<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: 12%;" @click="sort('SP_ACCESSNUMBER')"
|
||||
class="cursor-pointer hover:bg-blue-100 transition-colors select-none">
|
||||
<div class="flex items-center gap-1">
|
||||
Lab No
|
||||
<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: 10%;" @click="sort('HOSTORDERNUMBER')"
|
||||
class="cursor-pointer hover:bg-blue-100 transition-colors select-none">
|
||||
<div class="flex items-center gap-1">
|
||||
Reg No
|
||||
<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: 10%;">Reff</th>
|
||||
<th style="width: 10%;">Doctor</th>
|
||||
<th style="width: 10%;">ResTo</th>
|
||||
<th style="width: 13%;">Status</th>
|
||||
<th style="width: 8%;">Action</th>
|
||||
<th style="width: 12%;">Tests</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<template x-for="req in unvalidatedPaginated" :key="req.SP_ACCESSNUMBER">
|
||||
<tr class="hover:bg-blue-50 cursor-pointer transition-colors"
|
||||
@click="openValDialog(req.SP_ACCESSNUMBER)"
|
||||
tabindex="0"
|
||||
@keydown.enter="openValDialog(req.SP_ACCESSNUMBER)"
|
||||
@keydown.escape="closeValDialog()">
|
||||
<td>
|
||||
<div class="font-medium" x-text="req.Name"></div>
|
||||
<div class="text-xs opacity-60" x-text="req.PATNUMBER?.substring(14) || req.PATNUMBER"></div>
|
||||
</td>
|
||||
<td x-text="req.SP_ACCESSNUMBER" class="font-bold font-mono text-xs"></td>
|
||||
<td x-text="req.HOSTORDERNUMBER" class="font-bold font-mono text-xs"></td>
|
||||
<td x-text="req.REFF" class="text-xs"></td>
|
||||
<td x-text="req.DOC" class="text-xs truncate max-w-[80px]" :title="req.DOC"></td>
|
||||
<td x-text="req.ODR_CRESULT_TO" class="text-xs"></td>
|
||||
<td>
|
||||
<div class="flex items-center gap-1">
|
||||
<div class="flex gap-0.5" :title="getValTooltip(req)">
|
||||
<span class="w-3 h-3 rounded-full flex items-center justify-center text-[8px]"
|
||||
:class="req.ISVAL1 == 1 ? 'bg-primary text-white' : 'bg-base-300 text-transparent'">
|
||||
<i class="fa fa-check"></i>
|
||||
</span>
|
||||
<span class="w-3 h-3 rounded-full flex items-center justify-center text-[8px]"
|
||||
:class="req.ISVAL2 == 1 ? 'bg-success text-white' : 'bg-base-300 text-transparent'">
|
||||
<i class="fa fa-check"></i>
|
||||
</span>
|
||||
</div>
|
||||
<div class="text-xs ml-1" :class="getValStatusClass(req)">
|
||||
<span x-text="getValStatusText(req)"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-[10px] opacity-60 mt-0.5" x-show="req.VAL1USER || req.VAL2USER">
|
||||
<span x-show="req.VAL1USER">1: <span x-text="req.VAL1USER"></span></span>
|
||||
<span x-show="req.VAL2USER" class="ml-2">2: <span x-text="req.VAL2USER"></span></span>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<button class="btn btn-xs btn-primary btn-outline" @click.stop="openValDialog(req.SP_ACCESSNUMBER)">
|
||||
<i class="fa fa-check"></i> <span x-text="req.ISVAL1 == 1 ? '2nd' : '1st'"></span>
|
||||
</button>
|
||||
</td>
|
||||
<td class="max-w-[100px] truncate text-xs" :title="req.TESTNAMES || req.TESTS" x-text="req.TESTNAMES || req.TESTS"></td>
|
||||
</tr>
|
||||
</template>
|
||||
</tbody>
|
||||
</table>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<!-- Pagination Control -->
|
||||
<div class="p-2 border-t border-base-200 bg-base-50 flex flex-col sm:flex-row justify-between items-center gap-2">
|
||||
<div class="text-xs text-base-content/80">
|
||||
Showing <span class="font-bold" x-text="((currentPage - 1) * pageSize) + 1"></span> to
|
||||
<span class="font-bold" x-text="Math.min(currentPage * pageSize, unvalidatedFiltered.length)"></span> of
|
||||
<span class="font-bold" x-text="unvalidatedFiltered.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="unvalidatedTotalPages"></span>
|
||||
</button>
|
||||
<button class="join-item btn btn-sm" @click="nextPage()" :disabled="currentPage === unvalidatedTotalPages">
|
||||
<i class="fa fa-chevron-right"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Validate Dialog -->
|
||||
<?= $this->include('shared/dialog_val'); ?>
|
||||
@ -38,7 +38,7 @@
|
||||
Close (Esc)
|
||||
</button>
|
||||
</p>
|
||||
<iframe id="result-iframe" x-ref="resultIframe" src="<?=base_url('dummypage');?>" width="100%" height="500px"
|
||||
<iframe id="result-iframe" x-ref="resultIframe" :src="getPreviewUrl()" width="100%" height="500px"
|
||||
class="border border-base-300 rounded"></iframe>
|
||||
|
||||
<!-- Loading overlay -->
|
||||
|
||||
@ -25,6 +25,13 @@
|
||||
.card-body {
|
||||
font-size: 0.71rem !important;
|
||||
}
|
||||
|
||||
/* Status Button Colors - Matching Row Backgrounds */
|
||||
.btn-status-pend, .badge-status-pend { background-color: #ffffff; color: black; border: 1px solid #d1d5db; }
|
||||
.btn-status-coll, .badge-status-coll { background-color: #d63031; color: white; }
|
||||
.btn-status-recv, .badge-status-recv { background-color: #0984e3; color: white; }
|
||||
.btn-status-inc, .badge-status-inc { background-color: #ffff00; color: black; }
|
||||
.btn-status-fin, .badge-status-fin { background-color: #008000; color: white; }
|
||||
</style>
|
||||
</head>
|
||||
|
||||
|
||||
@ -8,12 +8,21 @@ document.addEventListener('alpine:init', () => {
|
||||
counters: { Pend: 0, Coll: 0, Recv: 0, Inc: 0, Fin: 0, Total: 0 },
|
||||
statusColor: {
|
||||
Pend: 'bg-white text-black font-bold',
|
||||
PartColl: 'bg-orange-300 text-white font-bold',
|
||||
Coll: 'bg-orange-500 text-white font-bold',
|
||||
PartRecv: 'bg-blue-200 text-black font-bold',
|
||||
Recv: 'bg-blue-500 text-white font-bold',
|
||||
Inc: 'bg-yellow-500 text-white font-bold',
|
||||
Fin: 'bg-green-500 text-white font-bold',
|
||||
PartColl: 'bg-[#ff99aa] text-black font-bold',
|
||||
Coll: 'bg-[#d63031] text-white font-bold',
|
||||
PartRecv: 'bg-[#a0c0d9] text-black font-bold',
|
||||
Recv: 'bg-[#0984e3] text-white font-bold',
|
||||
Inc: 'bg-[#ffff00] text-black font-bold',
|
||||
Fin: 'bg-[#008000] text-white font-bold',
|
||||
},
|
||||
statusRowBg: {
|
||||
Pend: 'bg-white text-black',
|
||||
PartColl: 'bg-[#ff99aa] text-black',
|
||||
Coll: 'bg-[#d63031] text-white',
|
||||
PartRecv: 'bg-[#a0c0d9] text-black',
|
||||
Recv: 'bg-[#0984e3] text-white',
|
||||
Inc: 'bg-[#ffff00] text-black',
|
||||
Fin: 'bg-[#008000] text-white',
|
||||
},
|
||||
filterTable: "",
|
||||
filterKey: 'Total',
|
||||
@ -71,20 +80,9 @@ document.addEventListener('alpine:init', () => {
|
||||
init() {
|
||||
this.today = new Date().toISOString().slice(0, 10);
|
||||
|
||||
// Check if running on development workstation (localhost)
|
||||
const isDev = window.location.hostname === 'localhost' ||
|
||||
window.location.hostname === '127.0.0.1' ||
|
||||
window.location.hostname.includes('.test');
|
||||
|
||||
if (isDev) {
|
||||
// Development: specific date range for test data
|
||||
this.filter.date1 = '2025-01-02';
|
||||
this.filter.date2 = '2025-01-03';
|
||||
} else {
|
||||
// Production: default to today
|
||||
this.filter.date1 = this.today;
|
||||
this.filter.date2 = this.today;
|
||||
}
|
||||
// Production: default to today
|
||||
this.filter.date1 = this.today;
|
||||
this.filter.date2 = this.today;
|
||||
|
||||
this.$watch('filterTable', () => {
|
||||
this.currentPage = 1;
|
||||
|
||||
@ -68,20 +68,8 @@ document.addEventListener('alpine:init', () => {
|
||||
init() {
|
||||
this.today = new Date().toISOString().slice(0, 10);
|
||||
|
||||
// Check if running on development workstation (localhost)
|
||||
const isDev = window.location.hostname === 'localhost' ||
|
||||
window.location.hostname === '127.0.0.1' ||
|
||||
window.location.hostname.includes('.test');
|
||||
|
||||
if (isDev) {
|
||||
// Development: specific date range for test data
|
||||
this.filter.date1 = '2025-01-02';
|
||||
this.filter.date2 = '2025-01-03';
|
||||
} else {
|
||||
// Production: default to today
|
||||
this.filter.date1 = this.today;
|
||||
this.filter.date2 = this.today;
|
||||
}
|
||||
this.filter.date1 = this.today;
|
||||
this.filter.date2 = this.today;
|
||||
|
||||
this.$watch('filterTable', () => {
|
||||
this.currentPage = 1;
|
||||
@ -220,5 +208,10 @@ document.addEventListener('alpine:init', () => {
|
||||
this.isValidating = false;
|
||||
});
|
||||
},
|
||||
|
||||
getPreviewUrl() {
|
||||
let base = 'http://glenlis/spooler_db/main_dev.php';
|
||||
return `${base}?acc=${this.valAccessnumber}`;
|
||||
},
|
||||
}));
|
||||
});
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user