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:
mahdahar 2026-02-02 14:27:12 +07:00
parent e649b6c398
commit 01908bb002
10 changed files with 64 additions and 451 deletions

View File

@ -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. 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 ## Commands
```bash ```bash

120
CLAUDE.md
View File

@ -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']));
```

View File

@ -1,11 +1,10 @@
# Project Checklist: Glen RME & Lab Management System # Project Checklist: Glen RME & Lab Management System
**Last Updated:** January 26, 2026 **Last Updated:** 20260202
Pending: Pending:
- Restrict Print/Save-to-PDF to CS Role only (Lab can only preview, CS can print/save) - 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) - 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) - Reprint Label (Add functionality to reprint labels)
- Print Result Audit (Track when result reports are printed/exported, log user and timestamp) - Print Result Audit (Track when result reports are printed/exported, log user and timestamp)
@ -28,6 +27,8 @@ Completed:
- 16 : Remove 'UnCollect' - 16 : Remove 'UnCollect'
- 17 : Audit Trail (Track all actions: validation, unvalidation, collection, uncollection) - 17 : Audit Trail (Track all actions: validation, unvalidation, collection, uncollection)
- 18 : Create Validate Page - 18 : Create Validate Page
- 19 : Sync color with old gdc_cmod
- 20 : Add Val1 Val2 on the result
Addition on dev : Addition on dev :

View File

@ -18,27 +18,27 @@
All <span class="badge badge-sm badge-ghost ml-1" x-text="counters.Total"></span> All <span class="badge badge-sm badge-ghost ml-1" x-text="counters.Total"></span>
</button> </button>
<button @click="filterKey = 'Pend'" <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"> 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>
<button @click="filterKey = 'Coll'" <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"> 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>
<button @click="filterKey = 'Recv'" <button @click="filterKey = 'Recv'"
:class="filterKey === 'Recv' ? 'btn-active btn-info text-white' : 'btn-ghost'" class="btn btn-sm join-item"> :class="filterKey === 'Recv' ? 'btn-active btn-status-recv' : 'btn-ghost'" class="btn btn-sm join-item">
Recv <span class="badge badge-sm badge-info ml-1" x-text="counters.Recv"></span> Recv <span class="badge badge-sm badge-status-recv ml-1" x-text="counters.Recv"></span>
</button> </button>
<button @click="filterKey = 'Inc'" <button @click="filterKey = 'Inc'"
:class="filterKey === 'Inc' ? 'btn-active btn-error text-white' : 'btn-ghost'" class="btn btn-sm join-item"> :class="filterKey === 'Inc' ? 'btn-active btn-status-inc' : 'btn-ghost'" class="btn btn-sm join-item">
Inc <span class="badge badge-sm badge-error ml-1" x-text="counters.Inc"></span> Inc <span class="badge badge-sm badge-status-inc ml-1" x-text="counters.Inc"></span>
</button> </button>
<button @click="filterKey = 'Fin'" <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"> 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>
<button @click="filterKey = 'Validated'" <button @click="filterKey = 'Validated'"
:class="filterKey === 'Validated' ? 'btn-active btn-primary text-white' : 'btn-ghost'" :class="filterKey === 'Validated' ? 'btn-active btn-primary text-white' : 'btn-ghost'"
@ -195,15 +195,15 @@
<tbody> <tbody>
<template x-for="req in paginated" :key="req.SP_ACCESSNUMBER"> <template x-for="req in paginated" :key="req.SP_ACCESSNUMBER">
<tr class="hover:bg-base-300"> <tr class="hover:bg-base-300">
<td x-text="req.REQDATE"></td> <td x-text="req.REQDATE" :class="statusRowBg[req.STATS]"></td>
<td x-text="req.Name"></td> <td x-text="req.Name" :class="statusRowBg[req.STATS]"></td>
<td x-text="req.SP_ACCESSNUMBER" class="font-bold cursor-pointer" :class="statusColor[req.STATS]" <td x-text="req.SP_ACCESSNUMBER" class="font-bold cursor-pointer" :class="statusRowBg[req.STATS]"
@click="openSampleDialog(req.SP_ACCESSNUMBER)"></td> @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> @click="openSampleDialog(req.SP_ACCESSNUMBER)"></td>
<td x-text="req.REFF"></td> <td x-text="req.REFF" :class="statusRowBg[req.STATS]"></td>
<td x-text="req.DOC"></td> <td x-text="req.DOC" :class="statusRowBg[req.STATS]"></td>
<td x-text="req.TESTS"></td> <td x-text="req.TESTS" :class="statusRowBg[req.STATS]"></td>
<td x-text="req.ODR_CRESULT_TO"></td> <td x-text="req.ODR_CRESULT_TO"></td>
<td> <td>
<div class='flex gap-1 items-center'> <div class='flex gap-1 items-center'>

View File

@ -3,7 +3,7 @@
<!-- Header & Filters --> <!-- Header & Filters -->
<div class="p-4 border-b border-base-200 bg-gradient-to-r from-primary/10 to-base-100"> <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"> <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"> <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> <i class="fa fa-clipboard-check"></i>

View File

@ -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'); ?>

View File

@ -38,7 +38,7 @@
Close (Esc) Close (Esc)
</button> </button>
</p> </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> class="border border-base-300 rounded"></iframe>
<!-- Loading overlay --> <!-- Loading overlay -->

View File

@ -25,6 +25,13 @@
.card-body { .card-body {
font-size: 0.71rem !important; 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> </style>
</head> </head>

View File

@ -8,12 +8,21 @@ document.addEventListener('alpine:init', () => {
counters: { Pend: 0, Coll: 0, Recv: 0, Inc: 0, Fin: 0, Total: 0 }, counters: { Pend: 0, Coll: 0, Recv: 0, Inc: 0, Fin: 0, Total: 0 },
statusColor: { statusColor: {
Pend: 'bg-white text-black font-bold', Pend: 'bg-white text-black font-bold',
PartColl: 'bg-orange-300 text-white font-bold', PartColl: 'bg-[#ff99aa] text-black font-bold',
Coll: 'bg-orange-500 text-white font-bold', Coll: 'bg-[#d63031] text-white font-bold',
PartRecv: 'bg-blue-200 text-black font-bold', PartRecv: 'bg-[#a0c0d9] text-black font-bold',
Recv: 'bg-blue-500 text-white font-bold', Recv: 'bg-[#0984e3] text-white font-bold',
Inc: 'bg-yellow-500 text-white font-bold', Inc: 'bg-[#ffff00] text-black font-bold',
Fin: 'bg-green-500 text-white 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: "", filterTable: "",
filterKey: 'Total', filterKey: 'Total',
@ -71,20 +80,9 @@ document.addEventListener('alpine:init', () => {
init() { init() {
this.today = new Date().toISOString().slice(0, 10); this.today = new Date().toISOString().slice(0, 10);
// Check if running on development workstation (localhost) // Production: default to today
const isDev = window.location.hostname === 'localhost' || this.filter.date1 = this.today;
window.location.hostname === '127.0.0.1' || this.filter.date2 = this.today;
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.$watch('filterTable', () => { this.$watch('filterTable', () => {
this.currentPage = 1; this.currentPage = 1;

View File

@ -68,20 +68,8 @@ document.addEventListener('alpine:init', () => {
init() { init() {
this.today = new Date().toISOString().slice(0, 10); this.today = new Date().toISOString().slice(0, 10);
// Check if running on development workstation (localhost) this.filter.date1 = this.today;
const isDev = window.location.hostname === 'localhost' || this.filter.date2 = this.today;
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.$watch('filterTable', () => { this.$watch('filterTable', () => {
this.currentPage = 1; this.currentPage = 1;
@ -220,5 +208,10 @@ document.addEventListener('alpine:init', () => {
this.isValidating = false; this.isValidating = false;
}); });
}, },
getPreviewUrl() {
let base = 'http://glenlis/spooler_db/main_dev.php';
return `${base}?acc=${this.valAccessnumber}`;
},
})); }));
}); });