refactor: standardize codebase with BaseModel and new conventions

- Add BaseModel with automatic camel/snake case conversion
- Add stringcase_helper with camel_to_snake(), snake_to_camel() functions
- Update all models to extend BaseModel for consistent data handling
- Update API controllers with standardized JSON response format
- Remove legacy v1 PHP application directory
- Consolidate documentation into AGENTS.md, delete VIEWS_RULES.md
This commit is contained in:
mahdahar 2026-01-15 10:44:09 +07:00
parent ff90e0eb29
commit 18b85815ce
64 changed files with 1434 additions and 53813 deletions

865
AGENTS.md
View File

@ -1,340 +1,597 @@
# AGENTS.md - QC Application Development Guide # AGENTS.md - AI Agent Guidelines for [PROJECT NAME]
This document provides guidelines for agentic coding agents working on this PHP QC (Quality Control) application built with CodeIgniter 4. ## AI Agent Guidelines
## Project Overview 1. **Readability**: Write code that is easy to read and understand.
2. **Maintainability**: Write code that is easy to maintain and update.
3. **Performance**: Write code that is fast and efficient.
4. **Security**: Write code that is secure and protected against attacks.
5. **Testing**: Write code that is tested and verified.
This is a CodeIgniter 4 PHP application using SQL Server database for quality control data management. The app handles control tests, daily/monthly entries, and reporting. Uses Tailwind CSS and Alpine.js for UI. ## Technology Stack
## Build/Lint/Test Commands | Layer | Technology |
|-------|------------|
| Backend | CodeIgniter 4 (PHP 8.1+) |
| Frontend | Alpine.js + TailwindCSS |
| Database | MySQL/MariaDB |
```bash ## Key Files & Locations
# PHP syntax check single file
php -l app/Controllers/Dashboard.php
# PHP syntax check all files recursively ### Backend
find . -name "*.php" -exec php -l {} \; 2>&1 | grep -v "No syntax errors"
# Run all PHPUnit tests ```
./vendor/bin/phpunit app/Controllers/ # API & page controllers
app/Models/ # Eloquent-style models
# Run single test class app/Database/Migrations/ # Schema definitions
./vendor/bin/phpunit tests/unit/HealthTest.php app/Config/Routes.php # All routes defined here
app/Helpers/ # Helper functions
# Run single test method
./vendor/bin/phpunit tests/unit/HealthTest.php --filter=testIsDefinedAppPath
# Run with coverage report
./vendor/bin/phpunit --coverage-html coverage/
# Start development server
php spark serve
``` ```
## Code Style Guidelines ### Frontend
### General Principles ```
app/Views/ # PHP views with Alpine.js
app/Views/layout/ # Base templates
public/ # Static assets (css, js)
```
- Follow CodeIgniter 4 MVC patterns ## Coding Conventions
- Maintain consistency with surrounding code
- Keep files focused (<200 lines preferred)
- Use clear, descriptive names
### PHP Style ### PHP / CodeIgniter 4
- Use `<?php` opening tag (not `<?` short tags) 1. **Controllers** extend `BaseController` and use `ResponseTrait`
- Enable strict types: `declare(strict_types=1);` at top of PHP files 2. **Models** extend `App\Models\BaseModel` (custom base with auto camel/snake conversion)
- Use strict comparison (`===`, `!==`) over loose comparison 3. **Soft deletes** are enabled on all tables (`deleted_at`)
- Use `elseif` (one word) not `else if` 4. **Timestamps** are automatic (`created_at`, `updated_at`)
- Use parentheses with control structures for single statements 5. **Validation** happens in controllers, not models
- Use 4 spaces for indentation (not tabs) 6. **JSON API responses** follow this structure:
- Return types and typed properties required for new code: ```php
```php return $this->respond([
public function index(): string 'status' => 'success',
{ 'message' => 'fetch success',
return view('layout', [...]); 'data' => $rows
} ], 200);
``` ```
### Naming Conventions 7. **Use camelCase for input/output**, snake_case for database:
- Controllers convert camel→snake before insert/update
- Models convert snake→camel after fetch
- Use helper functions: `camel_to_snake()`, `camel_to_snake_array()`, `snake_to_camel()`
- Classes: `PascalCase` (e.g., `Dashboard`, `DictTestModel`) ### Database
- Methods/functions: `$camelCase` (e.g., `getWithDept`, `saveResult`)
- Variables: `$camelCase` (e.g., `$dictTestModel`, `$resultData`)
- Constants: `UPPER_SNAKE_CASE`
- Views: `lowercase_with_underscores` (e.g., `dashboard.php`, `test_index.php`)
- Routes: kebab-case URLs (e.g., `/test/edit/(:num)``/test/edit/1`)
### CodeIgniter 4 Patterns 1. **Primary keys**: `{table_singular}_id` (e.g., `item_id`, `pat_id`)
2. **Foreign keys**: Match the referenced primary key name
3. **Naming**: All lowercase, underscores
4. **Soft deletes**: All tables have `deleted_at` DATETIME column
5. **Master data tables**: Prefix with `master_` (e.g., `master_items`)
6. **Timestamps**: `created_at`, `updated_at` DATETIME columns
7. **Unique constraints**: Add on code fields (e.g., `item_code`)
### Frontend / Alpine.js
1. **x-data** on container elements
2. **Fetch API** for AJAX calls (no jQuery)
3. **DaisyUI components** for UI elements
4. **camelCase** for JavaScript, **snake_case** for PHP/DB
5. **Modals** with x-show and x-transition
6. **ES6 modules** importing Alpine from `app.js`
### File Naming
| Component | Pattern | Example |
|-----------|---------|---------|
| Controller | `PascalCase + Controller` | `ItemsController` |
| Model | `PascalCase + Model` | `ItemsModel` |
| Migration | `YYYY-MM-DD-XXXXXX_Description.php` | `2026-01-15-000001_Items.php` |
| View | `module/action.php` | `items/index.php` |
| Helper | `snake_case + _helper.php` | `stringcase_helper.php` |
| Filter | `PascalCase + Filter.php` | `JwtAuthFilter.php` |
### Directory Structure
```
app/
├── Config/
│ ├── Routes.php
│ └── Filters.php
├── Controllers/
│ ├── BaseController.php
│ ├── ItemsController.php
│ └── Master/
│ └── ItemsController.php
├── Database/
│ └── Migrations/
├── Filters/
│ └── JwtAuthFilter.php
├── Helpers/
│ ├── stringcase_helper.php
│ └── utc_helper.php
├── Models/
│ ├── BaseModel.php
│ ├── ItemsModel.php
│ └── Master/
│ └── ItemsModel.php
└── Views/
├── layout/
│ ├── main_layout.php
│ └── form_layout.php
├── items/
│ ├── items_index.php
│ └── dialog_items_form.php
└── login.php
```
## Common Tasks
### Adding a New Master Data Entity
1. Create migration in `app/Database/Migrations/`
2. Create model in `app/Models/[Module]/`
3. Create controller in `app/Controllers/[Module]/`
4. Add routes in `app/Config/Routes.php`
5. Create view in `app/Views/[module]/`
### Adding a New API Endpoint
**Controllers** extend `App\Controllers\BaseController`:
```php ```php
namespace App\Controllers; // In Routes.php
$routes->get('api/resource', 'ResourceController::index');
use App\Models\DictTestModel; $routes->get('api/resource/(:num)', 'ResourceController::show/$1');
$routes->post('api/resource', 'ResourceController::create');
class Test extends BaseController $routes->patch('api/resource/(:num)', 'ResourceController::update/$1');
{
protected $dictTestModel;
public function __construct()
{
$this->dictTestModel = new DictTestModel();
}
public function index(): string
{
$data = [
'title' => 'Test Dictionary',
'tests' => $this->dictTestModel->findAll(),
];
return view('layout', [
'content' => view('test/index', $data),
'page_title' => 'Test Dictionary',
'active_menu' => 'test'
]);
}
}
``` ```
**Models** extend `CodeIgniter\Model`: ### Controller Template
```php
namespace App\Models;
use CodeIgniter\Model;
class TestModel extends Model
{
protected $table = 'dict_test';
protected $primaryKey = 'id';
protected $returnType = 'array';
protected $useSoftDeletes = false;
protected $allowedFields = ['deptid', 'name', 'unit', 'method'];
}
```
**Views** use PHP short tags `<?= ?>` for output:
```php
<div class="space-y-6">
<h1 class="text-2xl font-bold"><?= $title ?></h1>
<?php foreach ($tests as $test): ?>
<div><?= $test['name'] ?></div>
<?php endforeach; ?>
</div>
```
### Database Operations
- Configure connections in `app/Config/Database.php`
- Use CodeIgniter's Query Builder for queries
- Use parameterized queries to prevent SQL injection:
```php
$builder = $this->db->table('results');
$builder->select('*');
$builder->where('control_ref_id', $controlId);
$results = $builder->get()->getResultArray();
```
- For SQL Server, set `DBDriver` to `'SQLSRV'` in config
### Error Handling
- Use CodeIgniter's exception handling: `throw new \CodeIgniter\Exceptions\PageNotFoundException('Not found')`
- Return JSON responses for AJAX endpoints:
```php
return $this->response->setJSON(['success' => true, 'data' => $results]);
```
- Use `log_message('error', $message)` for logging
- Validate input with `$this->validate()` in controllers
### Frontend (Tailwind CSS + Alpine.js)
The layout already includes Tailwind via CDN and Alpine.js:
```html
<script src="https://cdn.tailwindcss.com"></script>
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
```
**Common JavaScript (`public/js/app.js`):**
- Contains global `App` object with shared utilities
- Handles sidebar toggle functionality
- Provides `App.showToast()`, `App.confirmSave()`, `App.closeAllModals()`
- Initialize with `App.init()` on DOMContentLoaded
**Page-Specific Alpine.js:**
- Complex Alpine.js components should be defined inline in the view PHP file
- Use `x-data` with an object containing all properties and methods
- Example:
```html
<div x-data="{
property1: '',
property2: [],
init() {
// Initialization code
},
async loadData() {
// Fetch and handle data
App.showToast('Loaded successfully');
}
}">
<!-- Component markup -->
</div>
```
### File Organization
- Controllers: `app/Controllers/`
- Models: `app/Models/`
- Views: `app/Views/` (subfolders for modules: `entry/`, `test/`, `control/`, `report/`, `dept/`)
- Config: `app/Config/`
- Routes: `app/Config/Routes.php`
### Modal-based CRUD Pattern
All CRUD operations use a single modal dialog file (`dialog.php`) that handles both add and edit modes.
**File Naming:**
- Single dialog: `dialog.php` (no `_add` or `_edit` suffix)
**Controller Pattern:**
```php
class Dept extends BaseController
{
protected $dictDeptModel;
public function __construct()
{
$this->dictDeptModel = new DictDeptModel();
}
public function index(): string
{
$data = [
'title' => 'Department Dictionary',
'depts' => $this->dictDeptModel->findAll(),
];
return view('layout', [
'content' => view('dept/index', $data),
'page_title' => 'Department Dictionary',
'active_menu' => 'dept'
]);
}
public function save(): \CodeIgniter\HTTP\RedirectResponse
{
$data = ['name' => $this->request->getPost('name')];
$this->dictDeptModel->insert($data);
return redirect()->to('/dept');
}
public function update($id): \CodeIgniter\HTTP\RedirectResponse
{
$data = ['name' => $this->request->getPost('name')];
$this->dictDeptModel->update($id, $data);
return redirect()->to('/dept');
}
public function delete($id): \CodeIgniter\HTTP\RedirectResponse
{
$this->dictDeptModel->delete($id);
return redirect()->to('/dept');
}
}
```
**Dialog View Pattern (`dialog.php`):**
```php ```php
<?php <?php
$isEdit = isset($record); namespace App\Controllers\Module;
$action = $isEdit ? '/controller/update/' . $record['id'] : '/controller/save';
$title = $isEdit ? 'Edit Title' : 'Add Title';
?>
<div x-data="{ open: false }"> use CodeIgniter\API\ResponseTrait;
<?php if (!$isEdit): ?> use App\Controllers\BaseController;
<button @click="open = true" class="...">Add</button> use App\Models\Module\ItemModel;
<?php endif; ?>
<div x-show="open" class="fixed inset-0 z-50 ..." style="display: none;"> class ItemsController extends BaseController {
<h2 class="text-xl font-bold mb-4"><?= $title ?></h2> use ResponseTrait;
<form action="<?= $action ?>" method="post">
<!-- Form fields with values from $record if edit mode -->
<input type="text" name="name" value="<?= $record['name'] ?? '' ?>">
<button type="submit"><?= $isEdit ? 'Update' : 'Save' ?></button>
</form>
</div>
</div>
```
**Index View Pattern (`index.php`):** protected $model;
```php protected $rules;
<div class="space-y-6">
<!-- Table with data attributes for edit/delete -->
<table>
<tbody>
<?php foreach ($records as $record): ?>
<tr>
<td><?= $record['name'] ?></td>
<td>
<button data-edit-id="<?= $record['id'] ?>"
data-name="<?= $record['name'] ?>"
class="...">Edit</button>
<button data-delete-id="<?= $record['id'] ?>"
class="...">Delete</button>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<!-- Dialog for add (no data passed) --> public function __construct() {
<?= view('module/dialog') ?> $this->model = new ItemsModel();
$this->rules = [
'itemCode' => 'required|min_length[1]',
'itemName' => 'required',
];
}
<script> public function index() {
// All modal-related JS in index.php $keyword = $this->request->getGet('keyword');
document.querySelectorAll('[data-edit-id]').forEach(btn => { try {
btn.addEventListener('click', function() { $rows = $this->model->getItems($keyword);
document.getElementById('editId').value = this.dataset.editId; return $this->respond([
document.getElementById('editName').value = this.dataset.name; 'status' => 'success',
document.getElementById('editModal').classList.remove('hidden'); 'message' => 'fetch success',
}); 'data' => $rows
}); ], 200);
} catch (\Exception $e) {
document.querySelectorAll('[data-delete-id]').forEach(btn => { return $this->failServerError('Exception: ' . $e->getMessage());
btn.addEventListener('click', function() {
if (confirm('Delete this record?')) {
window.location.href = '/controller/delete/' + this.dataset.deleteId;
} }
}); }
});
</script> public function show($id = null) {
try {
$rows = $this->model->where('item_id', $id)->findAll();
if (empty($rows)) {
return $this->respond([
'status' => 'success',
'message' => 'data not found.'
], 200);
}
return $this->respond([
'status' => 'success',
'message' => 'fetch success',
'data' => $rows
], 200);
} catch (\Exception $e) {
return $this->failServerError('Something went wrong: ' . $e->getMessage());
}
}
public function create() {
$input = $this->request->getJSON(true);
$input = camel_to_snake_array($input);
if (!$this->validate($this->rules)) {
return $this->failValidationErrors($this->validator->getErrors());
}
try {
$id = $this->model->insert($input, true);
return $this->respondCreated([
'status' => 'success',
'message' => $id
]);
} catch (\Exception $e) {
return $this->failServerError('Something went wrong: ' . $e->getMessage());
}
}
public function update($id = null) {
$input = $this->request->getJSON(true);
$input = camel_to_snake_array($input);
if (!$this->validate($this->rules)) {
return $this->failValidationErrors($this->validator->getErrors());
}
try {
$this->model->update($id, $input);
return $this->respondCreated([
'status' => 'success',
'message' => 'update success',
'data' => $id
]);
} catch (\Exception $e) {
return $this->failServerError('Something went wrong: ' . $e->getMessage());
}
}
}
``` ```
**Searchable Multi-Select:** ### Model Template
- Use Select2 for searchable dropdowns (already included in layout.php)
- Initialize with: `$('#selectId').select2({ placeholder: 'Search...', allowClear: true })`
### Security Considerations ```php
<?php
namespace App\Models\Module;
- Use CodeIgniter's built-in CSRF protection (`$this->validate('csrf')`) use App\Models\BaseModel;
- Escape all output in views with `<?= ?>` (auto-escaped)
- Use `$this->request->getPost()` instead of `$_POST`
- Never commit `.env` files with credentials
## Common Operations class ItemsModel extends BaseModel {
protected $table = 'module_items';
protected $primaryKey = 'item_id';
protected $allowedFields = [
'item_code',
'item_name',
'created_at',
'updated_at',
'deleted_at'
];
protected $useTimestamps = true;
protected $useSoftDeletes = true;
**Add a new CRUD resource:** public function getItems($keyword = null) {
1. Create model in `app/Models/` if ($keyword) {
2. Create controller in `app/Controllers/` return $this->groupStart()
3. Add routes in `app/Config/Routes.php` ->like('item_code', $keyword)
4. Create views in `app/Views/[module]/` ->orLike('item_name', $keyword)
->groupEnd()
->findAll();
}
return $this->findAll();
}
}
```
**Add a menu item:** ### Migration Template
- Add to sidebar in `app/Views/layout.php`
- Add route in `app/Config/Routes.php` ```php
- Create controller method <?php
- Set `active_menu` parameter in view() call namespace App\Database\Migrations;
use CodeIgniter\Database\Migration;
class ModuleItems extends Migration {
public function up() {
$this->forge->addField([
'item_id' => [
'type' => 'int',
'unsigned' => true,
'auto_increment' => true
],
'item_code' => [
'type' => 'VARCHAR',
'constraint' => 50
],
'item_name' => [
'type' => 'VARCHAR',
'constraint' => 150
],
'created_at' => [
'type' => 'DATETIME',
'null' => true
],
'updated_at' => [
'type' => 'DATETIME',
'null' => true
],
'deleted_at' => [
'type' => 'DATETIME',
'null' => true
]
]);
$this->forge->addKey('item_id', true);
$this->forge->addUniqueKey('item_code');
$this->forge->createTable('module_items');
}
public function down() {
$this->forge->dropTable('module_items', true);
}
}
```
### Routes Template
```php
<?php
use CodeIgniter\Router\RouteCollection;
$routes = get('/login', 'PagesController::login');
$routes->post('/login', 'AuthController::login');
$routes->get('/logout', 'AuthController::logout');
$routes->group('', ['filter' => 'jwt-auth'], function ($routes) {
$routes->get('/', 'PagesController::dashboard');
$routes->get('/module', 'PagesController::module');
$routes->get('/master/items', 'PagesController::masterItems');
});
$routes->group('api', function ($routes) {
$routes->get('module/items', 'Module\ItemsController::index');
$routes->get('module/items/(:num)', 'Module\ItemsController::show/$1');
$routes->post('module/items', 'Module\ItemsController::create');
$routes->patch('module/items/(:num)', 'Module\ItemsController::update/$1');
});
```
### View Template (Index with Alpine.js)
```php
<?= $this->extend("layout/main_layout"); ?>
<?= $this->section("content"); ?>
<main class="flex-1 p-6 overflow-auto bg-slate-50/50" x-data="items()">
<div class="flex justify-between items-center mb-6">
<div>
<h1 class="text-2xl font-bold text-slate-800 tracking-tight">Items</h1>
<p class="text-slate-500 text-sm mt-1">Manage your items</p>
</div>
<button
class="btn btn-sm gap-2 shadow-sm border-0 bg-gradient-to-r from-blue-600 to-blue-500 hover:from-blue-700 hover:to-blue-600 text-white transition-all duration-200"
@click="showForm()"
>
<i class="fa-solid fa-plus"></i> New Item
</button>
</div>
<div class="bg-white rounded-xl border border-slate-100 shadow-sm p-4 mb-6">
<div class="flex items-center gap-3">
<div class="relative flex-1 max-w-md">
<i class="fa-solid fa-magnifying-glass absolute left-3 top-1/2 -translate-y-1/2 text-slate-400 text-sm"></i>
<input
type="text"
placeholder="Search by name or code..."
class="w-full pl-10 pr-4 py-2.5 text-sm bg-slate-50 border border-slate-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 transition-all"
x-model="keyword"
@keyup.enter="fetchList()"
/>
</div>
<button
class="px-4 py-2.5 text-sm font-medium bg-slate-800 text-white rounded-lg hover:bg-slate-700 transition-all duration-200 flex items-center gap-2"
@click="fetchList()"
>
<i class="fa-solid fa-magnifying-glass text-xs"></i> Search
</button>
</div>
</div>
<div class="bg-white rounded-xl border border-slate-100 shadow-sm overflow-hidden">
<template x-if="list">
<div class="overflow-x-auto">
<table class="w-full text-sm text-left">
<thead class="bg-slate-50 text-slate-500 text-xs uppercase tracking-wider">
<tr>
<th class="py-3 px-5 font-semibold">Code</th>
<th class="py-3 px-5 font-semibold">Name</th>
<th class="py-3 px-5 font-semibold text-right">Action</th>
</tr>
</thead>
<tbody class="text-slate-600 divide-y divide-slate-100">
<template x-for="item in list" :key="item.itemId">
<tr class="hover:bg-slate-50/50 transition-colors">
<td class="py-3 px-5">
<span class="font-mono text-xs bg-slate-100 text-slate-600 px-2 py-1 rounded" x-text="item.itemCode"></span>
</td>
<td class="py-3 px-5 font-medium text-slate-700" x-text="item.itemName"></td>
<td class="py-3 px-5 text-right">
<button
class="inline-flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium text-amber-700 bg-amber-50 hover:bg-amber-100 rounded-lg transition-colors"
@click="showForm(item.itemId)"
>
<i class="fa-solid fa-pencil"></i> Edit
</button>
</td>
</tr>
</template>
</tbody>
</table>
</div>
</template>
</div>
<?= $this->include('module/items/dialog_items_form'); ?>
</main>
<?= $this->endSection(); ?>
<?= $this->section("script"); ?>
<script type="module">
import Alpine from '<?= base_url('/assets/js/app.js'); ?>';
document.addEventListener('alpine:init', () => {
Alpine.data("items", () => ({
loading: false,
showModal: false,
errors: {},
error: null,
part: "items",
keyword: "",
list: null,
item: null,
form: {
itemCode: "",
itemName: "",
},
async fetchList() {
this.loading = true;
this.error = null;
this.list = null;
try {
const params = new URLSearchParams({ keyword: this.keyword });
const response = await fetch(`${window.BASEURL}api/module/${this.part}?${params}`, {
method: "GET",
headers: { "Content-Type": "application/json" }
});
if (!response.ok) throw new Error("Failed to load data");
const data = await response.json();
this.list = data.data;
} catch (err) {
this.error = err.message;
} finally {
this.loading = false;
}
},
async loadData(id) {
this.loading = true;
try {
const response = await fetch(`${window.BASEURL}api/module/${this.part}/${id}`, {
method: "GET",
headers: { "Content-Type": "application/json" }
});
if (!response.ok) throw new Error("Failed to load item");
const data = await response.json();
this.form = data.data[0];
} catch (err) {
this.error = err.message;
this.form = {};
} finally {
this.loading = false;
}
},
async showForm(id = null) {
this.showModal = true;
if (id) {
await this.loadData(id);
} else {
this.form = {};
}
},
closeModal() {
this.showModal = false;
this.form = {};
},
validate() {
this.errors = {};
if (!this.form.itemCode) this.errors.itemCode = "Code is required.";
if (!this.form.itemName) this.errors.itemName = "Name is required.";
return Object.keys(this.errors).length === 0;
},
async save() {
if (!this.validate()) return;
this.loading = true;
let method = '';
let url = '';
if (this.form.itemId) {
method = 'patch';
url = `${BASEURL}api/module/${this.part}/${this.form.itemId}`;
} else {
method = 'post';
url = `${BASEURL}api/module/${this.part}`;
}
try {
const res = await fetch(url, {
method: method,
headers: { "Content-Type": "application/json" },
body: JSON.stringify(this.form),
});
const data = await res.json();
if (data.status === 'success') {
alert("Data saved successfully!");
this.closeModal();
this.list = null;
} else {
alert(data.message || "Something went wrong.");
}
} catch (err) {
console.error(err);
alert("Failed to save data.");
} finally {
this.loading = false;
}
}
}));
});
Alpine.start();
</script>
<?= $this->endSection(); ?>
```
### Running Migrations
```bash
php spark migrate # Run all pending
php spark migrate:rollback # Rollback last batch
php spark migrate:refresh # Rollback all + re-run
```
## Testing
```bash
# Run all tests
./vendor/bin/phpunit
# Run specific test file
./vendor/bin/phpunit tests/unit/SomeTest.php
```
## Things to Avoid
1. **Don't use jQuery** - Use Alpine.js or vanilla JS
2. **Don't over-engineer** - This is a "no-nonsense" project
3. **Don't skip soft deletes** - Always use `deleted_at`
4. **Don't hardcode** - Use `.env` for configuration
5. **Don't mix concerns** - Controllers handle HTTP, Models handle data
6. **Don't forget camel/snake conversion** - Frontend uses camelCase, DB uses snake_case
## Questions to Ask Before Making Changes
1. Does this follow the existing patterns?
2. Is there a simpler way to do this?
3. Did I add the route?
4. Did I handle errors gracefully?
5. Does the API response match the standard format?
6. Did I convert camelCase to snake_case before DB operations?
7. Did I convert snake_case to camelCase after fetching?
## Post-Change Requirements
**After every significant code change, update `README.md`:**
| Change Type | README Section to Update |
|-------------|--------------------------|
| New Controller | Project Structure, API Endpoints |
| New Model | Project Structure |
| New Migration/Table | Database Schema |
| New View | Project Structure |
| New Route | API Endpoints |
| New Command | Development Commands |
| Config Change | Setup |
Keep the README accurate and up-to-date with the actual codebase.

229
README.md
View File

@ -1,68 +1,207 @@
# CodeIgniter 4 Application Starter # TinyQC - Quality Control Management System
## What is CodeIgniter? A CodeIgniter 4 PHP application for quality control data management in laboratory settings. Built with Tailwind CSS, Alpine.js, and DaisyUI.
CodeIgniter is a PHP full-stack web framework that is light, fast, flexible and secure. ## Features
More information can be found at the [official site](https://codeigniter.com).
This repository holds a composer-installable app starter. - **Dictionary Management**: Maintain departments, tests, and control parameters
It has been built from the - **Control Management**: Configure and manage quality control standards
[development repository](https://github.com/codeigniter4/CodeIgniter4). - **Daily Entries**: Record daily quality control test results
- **Monthly Entries**: Aggregate and track monthly quality control data
- **Comments System**: Add notes and comments to results
- **Reporting**: Generate quality control reports and analysis
More information about the plans for version 4 can be found in [CodeIgniter 4](https://forum.codeigniter.com/forumdisplay.php?fid=28) on the forums. ## Technology Stack
You can read the [user guide](https://codeigniter.com/user_guide/) | Layer | Technology |
corresponding to the latest version of the framework. |-------|------------|
| Backend | CodeIgniter 4 (PHP 8.1+) |
| Database | SQL Server |
| Frontend | TailwindCSS + Alpine.js + DaisyUI |
| Icons | FontAwesome 7 |
| Testing | PHPUnit |
## Installation & updates ## Requirements
`composer create-project codeigniter4/appstarter` then `composer update` whenever - PHP 8.1 or higher
there is a new release of the framework. - SQL Server 2016+
- Composer
- Web server (Apache/Nginx/IIS)
When updating, check the release notes to see if there are any changes you might need to apply ## Installation
to your `app` folder. The affected files can be copied or merged from
`vendor/codeigniter4/framework/app`.
## Setup 1. **Clone the repository**
```bash
git clone <repository-url> tinyqc
cd tinyqc
```
Copy `env` to `.env` and tailor for your app, specifically the baseURL 2. **Install dependencies**
and any database settings. ```bash
composer install
```
## Important Change with index.php 3. **Configure environment**
```bash
copy env .env
```
Edit `.env` with your database settings:
```env
database.default.hostname = localhost
database.default.port = 1433
database.default.database = tinyqc
database.default.username = sa
database.default.password = your_password
database.default.DBDriver = SQLSRV
```
`index.php` is no longer in the root of the project! It has been moved inside the *public* folder, 4. **Set up database**
for better security and separation of components. - Create a new SQL Server database
- Run migrations if applicable
- Seed initial data if needed
This means that you should configure your web server to "point" to your project's *public* folder, and 5. **Configure web server**
not to the project root. A better practice would be to configure a virtual host to point there. A poor practice would be to point your web server to the project root and expect to enter *public/...*, as the rest of your logic and the Point your web server to the `public` directory:
framework are exposed. ```apache
<VirtualHost *:80>
ServerName tinyqc.local
DocumentRoot "D:/data/www/tinyqc/public"
<Directory "D:/data/www/tinyqc">
AllowOverride All
Require all granted
</Directory>
</VirtualHost>
```
**Please** read the user guide for a better explanation of how CI4 works! 6. **Access the application**
Open http://localhost in your browser
## Repository Management ## Project Structure
We use GitHub issues, in our main repository, to track **BUGS** and to track approved **DEVELOPMENT** work packages. ```
We use our [forum](http://forum.codeigniter.com) to provide SUPPORT and to discuss tinyqc/
FEATURE REQUESTS. ├── app/
│ ├── Config/ # Configuration files
│ │ ├── Database.php # Database settings
│ │ └── Routes.php # Route definitions
│ ├── Controllers/ # Application controllers
│ │ ├── Api/ # API controllers
│ │ ├── Dashboard.php
│ │ ├── Dept.php
│ │ ├── Test.php
│ │ ├── Control.php
│ │ ├── Entry.php
│ │ ├── PageController.php
│ │ └── Report.php
│ ├── Models/ # Database models
│ │ ├── DictDeptModel.php
│ │ ├── DictTestModel.php
│ │ ├── DictControlModel.php
│ │ ├── ControlModel.php
│ │ ├── ControlTestModel.php
│ │ ├── DailyResultModel.php
│ │ └── ...
│ └── Views/ # View templates
│ ├── layout/ # Layout templates
│ ├── dashboard.php
│ ├── dept/ # Department views
│ ├── test/ # Test views
│ ├── control/ # Control views
│ ├── entry/ # Entry views
│ └── report/ # Report views
├── public/ # Web root
├── tests/ # Unit tests
├── writable/ # Writable directory
├── env # Environment template
├── composer.json
└── phpunit.xml.dist
```
This repository is a "distribution" one, built by our release preparation script. ## Usage
Problems with it can be raised on our forum, or as issues in the main repository.
## Server Requirements ### Dashboard
The main dashboard provides an overview of quality control status and quick access to all modules.
PHP version 8.1 or higher is required, with the following extensions installed: ### Dictionary Management
- [intl](http://php.net/manual/en/intl.requirements.php) - **Departments**: Manage department/category entries
- [mbstring](http://php.net/manual/en/mbstring.installation.php) - **Tests**: Maintain test parameters and specifications
- **Controls**: Configure control standards and limits
> [!WARNING] ### Data Entry
> - The end of life date for PHP 7.4 was November 28, 2022.
> - The end of life date for PHP 8.0 was November 26, 2023.
> - If you are still using PHP 7.4 or 8.0, you should upgrade immediately.
> - The end of life date for PHP 8.1 will be December 31, 2025.
Additionally, make sure that the following extensions are enabled in your PHP: - **Daily Entry**: Record daily QC test results
- **Monthly Entry**: Aggregate monthly data and comments
- json (enabled by default - don't turn it off) ### Reports
- [mysqlnd](http://php.net/manual/en/mysqlnd.install.php) if you plan to use MySQL
- [libcurl](http://php.net/manual/en/curl.requirements.php) if you plan to use the HTTP\CURLRequest library Generate quality control reports based on:
- Date ranges
- Test types
- Control parameters
## API Endpoints
| Method | Endpoint | Description |
|--------|----------|-------------|
| GET | /api/dept | List departments |
| GET | /api/dept/:id | Get department details |
| POST | /api/dept | Create department |
| PUT | /api/dept/:id | Update department |
| DELETE | /api/dept/:id | Delete department |
| GET | /api/test | List tests |
| GET | /api/test/:id | Get test details |
| POST | /api/test | Create test |
| PUT | /api/test/:id | Update test |
| DELETE | /api/test/:id | Delete test |
| GET | /api/control | List controls |
| GET | /api/control/:id | Get control details |
| POST | /api/control | Create control |
| PUT | /api/control/:id | Update control |
| DELETE | /api/control/:id | Delete control |
| GET | /api/entry/controls | Get controls for entry |
| GET | /api/entry/tests | Get tests for entry |
| POST | /api/entry/daily | Save daily result |
| POST | /api/entry/monthly | Save monthly entry |
| POST | /api/entry/comment | Save comment |
## Development
### Running Tests
```bash
# Run all tests
./vendor/bin/phpunit
# Run with coverage
./vendor/bin/phpunit --coverage-html coverage/
```
### Code Style
Follow these guidelines:
- PSR-12 coding standards
- CamelCase for variables/functions
- PascalCase for classes
- snake_case for database tables/columns
### Adding New Features
1. Create model in `app/Models/`
2. Create API controller in `app/Controllers/Api/`
3. Add routes in `app/Config/Routes.php`
4. Create views in `app/Views/[module]/`
5. Add menu item in layout if needed
## Documentation
- [agent1.md](agent1.md) - Complete development guide
- [VIEWS_RULES.md](VIEWS_RULES.md) - View patterns and conventions
## License
This project is proprietary software.
## Support
For support, please contact the development team.

View File

@ -1,531 +0,0 @@
# TinyLab View Rules
This document defines the view patterns and conventions used in TinyLab. Follow these rules when creating new views or modifying existing ones.
## 1. Directory Structure
```
app/Views/
├── layout/
│ ├── main_layout.php # Primary app layout (sidebar, navbar, dark mode)
│ └── form_layout.php # Lightweight layout for standalone forms
├── [module_name]/ # Feature module (e.g., patients, requests)
│ ├── index.php # Main list page
│ ├── dialog_form.php # Modal form (Create/Edit)
│ └── drawer_filter.php # Filter drawer (optional)
├── master/
│ └── [entity]/ # Master data entity (e.g., doctors, samples)
│ ├── index.php
│ └── dialog_form.php
└── errors/
└── html/
├── error_404.php
└── error_exception.php
```
## 2. Layout Extension
All pages must extend the appropriate layout:
```php
<?= $this->extend("layout/main_layout"); ?>
<?= $this->section("content") ?>
<!-- Page content here -->
<?= $this->endSection(); ?>
<?= $this->section("script") ?>
<!-- Alpine.js scripts here -->
<?= $this->endSection(); ?>
```
Use `form_layout.php` for standalone pages like login:
```php
<?= $this->extend("layout/form_layout"); ?>
```
## 3. Alpine.js Integration
### 3.1 Component Definition
Always use `alpine:init` event listener:
```php
<?= $this->section("script") ?>
<script type="module">
import Alpine from '<?= base_url('/assets/js/app.js'); ?>';
document.addEventListener('alpine:init', () => {
Alpine.data("componentName", () => ({
// State
loading: false,
showModal: false,
list: [],
item: null,
form: {},
errors: {},
keyword: '',
// Lifecycle
init() {
this.fetchList();
},
// Methods
async fetchList() {
this.loading = true;
try {
const res = await fetch(`${window.BASEURL}/api/resource`);
const data = await res.json();
this.list = data.data || [];
} catch (err) {
console.error(err);
} finally {
this.loading = false;
}
},
showForm(id = null) {
this.form = id ? this.list.find(x => x.id === id) : {};
this.showModal = true;
},
closeModal() {
this.showModal = false;
this.errors = {};
},
validate() {
this.errors = {};
if (!this.form.field) this.errors.field = 'Field is required';
return Object.keys(this.errors).length === 0;
},
async save() {
if (!this.validate()) return;
this.loading = true;
try {
const method = this.form.id ? 'PATCH' : 'POST';
const res = await fetch(`${window.BASEURL}/api/resource`, {
method,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(this.form)
});
const data = await res.json();
if (data.status === 'success') {
this.closeModal();
this.fetchList();
}
} finally {
this.loading = false;
}
}
}));
});
Alpine.start();
</script>
<?= $this->endSection(); ?>
```
### 3.2 Alpine Data Attribute
Wrap page content in the component:
```php
<main x-data="componentName()">
<!-- Page content -->
</main>
```
## 4. Modal/Dialog Pattern
### 4.1 Standard Modal Structure
```php
<!-- Backdrop -->
<div x-show="showModal"
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"
class="fixed inset-0 bg-slate-900/50 backdrop-blur-sm z-40"
@click="closeModal()">
</div>
<!-- Modal Panel -->
<div x-show="showModal"
x-cloak
x-transition:enter="transition ease-out duration-200"
x-transition:enter-start="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
x-transition:enter-end="opacity-100 translate-y-0 sm:scale-100"
x-transition:leave="transition ease-in duration-150"
x-transition:leave-start="opacity-100 translate-y-0 sm:scale-100"
x-transition:leave-end="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
class="fixed inset-0 z-50 flex items-center justify-center p-4">
<div class="bg-white rounded-2xl shadow-2xl w-full max-w-2xl" @click.stop>
<!-- Header -->
<div class="px-6 py-4 border-b border-slate-100 flex items-center justify-between">
<h3 class="text-lg font-semibold text-slate-800" x-text="form.id ? 'Edit' : 'New'"></h3>
<button @click="closeModal()" class="text-slate-400 hover:text-slate-600">
<i class="fa-solid fa-xmark text-xl"></i>
</button>
</div>
<!-- Form -->
<form @submit.prevent="save()">
<div class="p-6">
<!-- Form fields -->
<div class="grid grid-cols-2 gap-4">
<div>
<label class="block text-sm font-medium text-slate-700 mb-1">Field <span class="text-red-500">*</span></label>
<input type="text" x-model="form.field"
class="w-full px-4 py-2.5 text-sm bg-slate-50 border border-slate-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 transition-all"
:class="{'border-red-300 bg-red-50': errors.field}" />
<p x-show="errors.field" x-text="errors.field" class="text-red-500 text-xs mt-1"></p>
</div>
</div>
</div>
<!-- Footer -->
<div class="flex items-center justify-end gap-3 px-6 py-4 border-t border-slate-100 bg-slate-50/50 rounded-b-2xl">
<button type="button" @click="closeModal()" class="px-4 py-2 text-sm font-medium text-slate-600 hover:text-slate-800 transition-colors">Cancel</button>
<button type="submit" :disabled="loading" class="px-4 py-2 text-sm font-medium text-white bg-blue-600 rounded-lg hover:bg-blue-700 disabled:opacity-50 disabled:cursor-not-allowed transition-colors">
<template x-if="!loading"><span><i class="fa-solid fa-check mr-1"></i> Save</span></template>
<template x-if="loading"><span><svg class="animate-spin h-4 w-4 mr-1 inline" viewBox="0 0 24 24"><!-- SVG --></svg> Saving...</span></template>
</button>
</div>
</form>
</div>
</div>
```
## 5. Page Structure Pattern
### 5.1 Standard CRUD Page
```php
<?= $this->extend("layout/main_layout"); ?>
<?= $this->section("content") ?>
<main x-data="componentName()">
<!-- Page Header -->
<div class="flex items-center justify-between mb-6">
<div>
<h1 class="text-2xl font-bold text-slate-800">Module Title</h1>
<p class="text-sm text-slate-500 mt-1">Module description</p>
</div>
<button @click="showForm()" class="btn btn-primary">
<i class="fa-solid fa-plus mr-2"></i>New Item
</button>
</div>
<!-- Search Card -->
<div class="bg-white rounded-xl border border-slate-100 shadow-sm p-4 mb-6">
<div class="flex gap-3">
<div class="flex-1 relative">
<i class="fa-solid fa-search absolute left-3 top-1/2 -translate-y-1/2 text-slate-400 text-sm"></i>
<input type="text" x-model="keyword" @keyup.enter="fetchList()"
class="w-full pl-10 pr-4 py-2.5 text-sm bg-slate-50 border border-slate-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500"
placeholder="Search..." />
</div>
<button @click="fetchList()" class="btn btn-primary">
<i class="fa-solid fa-search mr-2"></i>Search
</button>
</div>
</div>
<!-- Error Alert -->
<div x-show="error" x-transition class="bg-red-50 border border-red-200 text-red-700 px-4 py-3 rounded-lg mb-6">
<div class="flex items-center gap-2">
<i class="fa-solid fa-circle-exclamation"></i>
<span x-text="error"></span>
</div>
</div>
<!-- Data Table Card -->
<div class="bg-white rounded-xl border border-slate-100 shadow-sm overflow-hidden">
<!-- Loading State -->
<template x-if="loading">
<div class="p-12 text-center">
<div class="w-16 h-16 rounded-full bg-blue-50 flex items-center justify-center mx-auto mb-4">
<i class="fa-solid fa-spinner fa-spin text-blue-500 text-xl"></i>
</div>
<p class="text-slate-500 text-sm">Loading...</p>
</div>
</template>
<!-- Empty State -->
<template x-if="!loading && (!list || list.length === 0)">
<div class="flex-1 flex items-center justify-center p-8">
<div class="text-center">
<div class="w-12 h-12 rounded-full bg-slate-100 flex items-center justify-center mx-auto mb-3">
<i class="fa-solid fa-inbox text-slate-400"></i>
</div>
<p class="text-slate-500 text-sm">No data found</p>
</div>
</div>
</template>
<!-- Data Table -->
<template x-if="!loading && list && list.length > 0">
<table class="w-full text-sm text-left">
<thead class="bg-slate-50 text-slate-500 text-xs uppercase tracking-wider">
<tr>
<th class="py-3 px-5 font-semibold">#</th>
<th class="py-3 px-5 font-semibold">Code</th>
<th class="py-3 px-5 font-semibold">Name</th>
<th class="py-3 px-5 font-semibold text-right">Actions</th>
</tr>
</thead>
<tbody class="divide-y divide-slate-100">
<template x-for="(item, index) in list" :key="item.id">
<tr class="hover:bg-slate-50/50 transition-colors">
<td class="py-3 px-5" x-text="index + 1"></td>
<td class="py-3 px-5">
<span class="font-mono text-xs bg-slate-100 text-slate-600 px-2 py-1 rounded" x-text="item.code"></span>
</td>
<td class="py-3 px-5 font-medium text-slate-800" x-text="item.name"></td>
<td class="py-3 px-5 text-right">
<button @click="showForm(item.id)" class="text-blue-600 hover:text-blue-800 mr-3">
<i class="fa-solid fa-pen-to-square"></i>
</button>
<button @click="delete(item.id)" class="text-red-600 hover:text-red-800">
<i class="fa-solid fa-trash"></i>
</button>
</td>
</tr>
</template>
</tbody>
</table>
</template>
</div>
<!-- Dialog Include -->
<?= $this->include('module/dialog_form'); ?>
</main>
<?= $this->endSection(); ?>
```
### 5.2 Master-Detail Pattern
```php
<!-- Master-Detail Layout -->
<div class="flex-1 grid grid-cols-1 lg:grid-cols-3 gap-6 min-h-0">
<!-- List Panel -->
<div class="bg-white rounded-xl border border-slate-100 shadow-sm overflow-hidden flex flex-col">
<div class="p-4 border-b border-slate-100">
<input type="text" x-model="keyword" @keyup.enter="fetchList()" placeholder="Search..." class="w-full px-3 py-2 text-sm bg-slate-50 border border-slate-200 rounded-lg" />
</div>
<div class="flex-1 overflow-y-auto">
<template x-for="item in list" :key="item.id">
<div class="px-4 py-3 hover:bg-blue-50/50 cursor-pointer border-b border-slate-50"
:class="{'bg-blue-50': selectedId === item.id}"
@click="fetchDetail(item.id)">
<p class="font-medium text-slate-800" x-text="item.name"></p>
<p class="text-xs text-slate-500" x-text="item.code"></p>
</div>
</template>
</div>
</div>
<!-- Detail Panel -->
<div class="lg:col-span-2 bg-white rounded-xl border border-slate-100 shadow-sm">
<template x-if="!detail">
<div class="flex items-center justify-center h-full min-h-[400px] text-slate-400">
<div class="text-center">
<i class="fa-solid fa-folder-open text-4xl mb-3"></i>
<p>Select an item to view details</p>
</div>
</div>
</template>
<template x-if="detail">
<div class="p-6">
<!-- Detail content -->
</div>
</template>
</div>
</div>
```
## 6. Form Field Patterns
### 6.1 Input with Icon
```php
<div class="relative">
<i class="fa-solid fa-icon absolute left-3 top-1/2 -translate-y-1/2 text-slate-400 text-sm"></i>
<input type="text" x-model="form.field"
class="w-full pl-10 pr-4 py-2.5 text-sm bg-slate-50 border border-slate-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 transition-all"
placeholder="Placeholder text" />
</div>
```
### 6.2 Select Dropdown
```php
<select x-model="form.field" class="w-full px-4 py-2.5 text-sm bg-slate-50 border border-slate-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500">
<option value="">-- Select --</option>
<template x-for="option in options" :key="option.value">
<option :value="option.value" x-text="option.label"></option>
</template>
</select>
```
### 6.3 Checkbox
```php
<label class="flex items-center gap-2 cursor-pointer">
<input type="checkbox" x-model="form.field" class="checkbox checkbox-sm checkbox-primary" />
<span class="text-sm text-slate-700">Checkbox label</span>
</label>
```
### 6.4 Radio Group
```php
<div class="flex gap-4">
<template x-for="option in options" :key="option.value">
<label class="flex items-center gap-2 cursor-pointer">
<input type="radio" :name="'fieldName'" :value="option.value" x-model="form.field" class="radio radio-sm radio-primary" />
<span class="text-sm text-slate-700" x-text="option.label"></span>
</label>
</template>
</div>
```
## 7. Status Badges
```php
<span class="px-2 py-0.5 rounded-full text-xs font-medium"
:class="{
'bg-amber-100 text-amber-700': status === 'P',
'bg-blue-100 text-blue-700': status === 'I',
'bg-emerald-100 text-emerald-700': status === 'C',
'bg-red-100 text-red-700': status === 'X'
}"
x-text="getStatusLabel(status)">
</span>
```
## 8. CSS Classes Reference
### Layout
- `flex`, `flex-col`, `grid`, `grid-cols-2`, `gap-4`
- `w-full`, `max-w-2xl`, `min-h-0`
- `overflow-hidden`, `overflow-y-auto`
### Spacing
- `p-4`, `px-6`, `py-3`, `m-4`, `mb-6`
- `space-x-4`, `gap-3`
### Typography
- `text-sm`, `text-xs`, `text-lg`, `text-2xl`
- `font-medium`, `font-semibold`, `font-bold`
- `text-slate-500`, `text-slate-800`
### Borders & Backgrounds
- `bg-white`, `bg-slate-50`, `bg-blue-50`
- `border`, `border-slate-100`, `border-red-300`
- `rounded-lg`, `rounded-xl`, `rounded-2xl`
### Components
- `btn btn-primary`, `btn btn-ghost`
- `checkbox checkbox-sm`
- `radio radio-primary`
- `input`, `select`, `textarea`
### Icons
FontAwesome 7 via CDN: `<i class="fa-solid fa-icon-name"></i>`
## 9. API Response Format
Views expect API responses in this format:
```json
{
"status": "success",
"message": "Operation successful",
"data": [...]
}
```
## 10. Validation Pattern
```javascript
validate() {
this.errors = {};
if (!this.form.field1) {
this.errors.field1 = 'Field 1 is required';
}
if (!this.form.field2) {
this.errors.field2 = 'Field 2 is required';
}
if (this.form.field3 && !/^\d+$/.test(this.form.field3)) {
this.errors.field3 = 'Must be numeric';
}
return Object.keys(this.errors).length === 0;
}
```
## 11. Error Handling
```javascript
async save() {
this.loading = true;
try {
const res = await fetch(url, { ... });
const data = await res.json();
if (data.status === 'success') {
this.showToast('Saved successfully');
this.fetchList();
} else {
this.error = data.message || 'An error occurred';
}
} catch (err) {
this.error = 'Network error. Please try again.';
console.error(err);
} finally {
this.loading = false;
}
}
```
## 12. Quick Reference: File Templates
### Standard List Page Template
See: `app/Views/master/doctors/doctors_index.php`
### Modal Form Template
See: `app/Views/master/doctors/dialog_doctors_form.php`
### Master-Detail Page Template
See: `app/Views/patients/patients_index.php`
### Result Entry Table Template
See: `app/Views/requests/dialog_result_entry.php`
## 13. Checklist for New Views
- [ ] Create view file in correct directory
- [ ] Extend appropriate layout (`main_layout` or `form_layout`)
- [ ] Define `content` and `script` sections
- [ ] Wrap content in `x-data` component
- [ ] Include modal form if needed
- [ ] Use DaisyUI components for buttons, inputs, selects
- [ ] Add loading and empty states
- [ ] Implement proper error handling
- [ ] Use FontAwesome icons consistently
- [ ] Follow naming conventions (snake_case for PHP, camelCase for JS)
## 14. Conventions Summary
| Aspect | Convention |
|--------|------------|
| File naming | snake_case (e.g., `patients_index.php`) |
| PHP variables | snake_case |
| JavaScript variables | camelCase |
| HTML classes | kebab-case (Tailwind) |
| Icons | FontAwesome 7 |
| UI Framework | TailwindCSS + DaisyUI |
| State management | Alpine.js |
| API format | JSON with `status`, `message`, `data` |
| Primary key | `{table}_id` (e.g., `pat_id`) |
| Soft deletes | All tables use `deleted_at` |

View File

@ -19,20 +19,20 @@ $routes->group('api', function ($routes) {
$routes->get('dept', 'Api\DeptApiController::index'); $routes->get('dept', 'Api\DeptApiController::index');
$routes->get('dept/(:num)', 'Api\DeptApiController::show/$1'); $routes->get('dept/(:num)', 'Api\DeptApiController::show/$1');
$routes->post('dept', 'Api\DeptApiController::store'); $routes->post('dept', 'Api\DeptApiController::store');
$routes->put('dept/(:num)', 'Api\DeptApiController::update/$1'); $routes->patch('dept/(:num)', 'Api\DeptApiController::update/$1');
$routes->delete('dept/(:num)', 'Api\DeptApiController::delete/$1'); $routes->delete('dept/(:num)', 'Api\DeptApiController::delete/$1');
$routes->get('test', 'Api\TestApiController::index'); $routes->get('test', 'Api\TestApiController::index');
$routes->get('test/(:num)', 'Api\TestApiController::show/$1'); $routes->get('test/(:num)', 'Api\TestApiController::show/$1');
$routes->post('test', 'Api\TestApiController::store'); $routes->post('test', 'Api\TestApiController::store');
$routes->put('test/(:num)', 'Api\TestApiController::update/$1'); $routes->patch('test/(:num)', 'Api\TestApiController::update/$1');
$routes->delete('test/(:num)', 'Api\TestApiController::delete/$1'); $routes->delete('test/(:num)', 'Api\TestApiController::delete/$1');
$routes->get('control', 'Api\ControlApiController::index'); $routes->get('control', 'Api\ControlApiController::index');
$routes->get('control/(:num)', 'Api\ControlApiController::show/$1'); $routes->get('control/(:num)', 'Api\ControlApiController::show/$1');
$routes->get('control/(:num)/tests', 'Api\ControlApiController::getTests/$1'); $routes->get('control/(:num)/tests', 'Api\ControlApiController::getTests/$1');
$routes->post('control', 'Api\ControlApiController::store'); $routes->post('control', 'Api\ControlApiController::store');
$routes->put('control/(:num)', 'Api\ControlApiController::update/$1'); $routes->patch('control/(:num)', 'Api\ControlApiController::update/$1');
$routes->delete('control/(:num)', 'Api\ControlApiController::delete/$1'); $routes->delete('control/(:num)', 'Api\ControlApiController::delete/$1');
$routes->get('entry/controls', 'Api\EntryApiController::getControls'); $routes->get('entry/controls', 'Api\EntryApiController::getControls');

View File

@ -10,142 +10,137 @@ class ControlApiController extends BaseController
{ {
protected $dictControlModel; protected $dictControlModel;
protected $controlTestModel; protected $controlTestModel;
protected $rules;
public function __construct() public function __construct()
{ {
$this->dictControlModel = new DictControlModel(); $this->dictControlModel = new DictControlModel();
$this->controlTestModel = new ControlTestModel(); $this->controlTestModel = new ControlTestModel();
$this->rules = [
'name' => 'required|min_length[1]',
'dept_ref_id' => 'required',
];
} }
public function index() public function index()
{ {
$controls = $this->dictControlModel->getWithDept(); try {
return $this->response->setJSON([ $rows = $this->dictControlModel->getWithDept();
'status' => 'success', return $this->respond([
'message' => 'Controls fetched successfully',
'data' => $controls
]);
}
public function show($id)
{
$control = $this->dictControlModel->find($id);
if (!$control) {
return $this->response->setJSON([
'status' => 'error',
'message' => 'Control not found'
])->setStatusCode(404);
}
return $this->response->setJSON([
'status' => 'success',
'data' => $control
]);
}
public function getTests($id)
{
$tests = $this->controlTestModel->where('control_ref_id', $id)->findAll();
$testIds = array_column($tests, 'test_ref_id');
return $this->response->setJSON([
'status' => 'success',
'data' => $testIds
]);
}
public function store()
{
$post = $this->request->getJSON(true);
$controlData = [
'dept_ref_id' => $post['dept_ref_id'] ?? null,
'name' => $post['name'] ?? '',
'lot' => $post['lot'] ?? '',
'producer' => $post['producer'] ?? '',
'expdate' => $post['expdate'] ?? null,
];
$controlId = $this->dictControlModel->insert($controlData, true);
if (!empty($post['test_ids'])) {
foreach ($post['test_ids'] as $testId) {
$this->controlTestModel->insert([
'control_ref_id' => $controlId,
'test_ref_id' => $testId,
'mean' => 0,
'sd' => 0,
]);
}
}
if ($controlId) {
return $this->response->setJSON([
'status' => 'success', 'status' => 'success',
'message' => 'Control saved successfully', 'message' => 'fetch success',
'data' => ['control_id' => $controlId] 'data' => $rows
]); ], 200);
} catch (\Exception $e) {
return $this->failServerError('Exception: ' . $e->getMessage());
} }
return $this->response->setJSON([
'status' => 'error',
'message' => 'Failed to save control'
])->setStatusCode(500);
} }
public function update($id) public function show($id = null)
{ {
$post = $this->request->getJSON(true); try {
$rows = $this->dictControlModel->where('control_id', $id)->findAll();
$controlData = [ if (empty($rows)) {
'dept_ref_id' => $post['dept_ref_id'] ?? null, return $this->respond([
'name' => $post['name'] ?? '', 'status' => 'success',
'lot' => $post['lot'] ?? '', 'message' => 'data not found.'
'producer' => $post['producer'] ?? '', ], 200);
'expdate' => $post['expdate'] ?? null, }
]; return $this->respond([
'status' => 'success',
'message' => 'fetch success',
'data' => $rows
], 200);
} catch (\Exception $e) {
return $this->failServerError('Something went wrong: ' . $e->getMessage());
}
}
$success = $this->dictControlModel->update($id, $controlData); public function getTests($id = null)
{
try {
$rows = $this->controlTestModel->where('control_ref_id', $id)->findAll();
return $this->respond([
'status' => 'success',
'message' => 'fetch success',
'data' => $rows
], 200);
} catch (\Exception $e) {
return $this->failServerError('Something went wrong: ' . $e->getMessage());
}
}
if (!empty($post['test_ids'])) { public function create()
{
$input = $this->request->getJSON(true);
if (!$this->validate($this->rules)) {
return $this->failValidationErrors($this->validator->getErrors());
}
try {
$controlId = $this->dictControlModel->insert($input, true);
if (!empty($input['test_ids'])) {
foreach ($input['test_ids'] as $testId) {
$this->controlTestModel->insert([
'control_ref_id' => $controlId,
'test_ref_id' => $testId,
'mean' => 0,
'sd' => 0,
]);
}
}
return $this->respondCreated([
'status' => 'success',
'message' => $controlId
]);
} catch (\Exception $e) {
return $this->failServerError('Something went wrong: ' . $e->getMessage());
}
}
public function update($id = null)
{
$input = $this->request->getJSON(true);
if (!$this->validate($this->rules)) {
return $this->failValidationErrors($this->validator->getErrors());
}
try {
$this->dictControlModel->update($id, $input);
if (!empty($input['test_ids'])) {
$this->controlTestModel->where('control_ref_id', $id)->delete();
foreach ($input['test_ids'] as $testId) {
$this->controlTestModel->insert([
'control_ref_id' => $id,
'test_ref_id' => $testId,
'mean' => 0,
'sd' => 0,
]);
}
}
return $this->respond([
'status' => 'success',
'message' => 'update success',
'data' => $id
]);
} catch (\Exception $e) {
return $this->failServerError('Something went wrong: ' . $e->getMessage());
}
}
public function delete($id = null)
{
try {
$this->controlTestModel->where('control_ref_id', $id)->delete(); $this->controlTestModel->where('control_ref_id', $id)->delete();
foreach ($post['test_ids'] as $testId) { $this->dictControlModel->delete($id);
$this->controlTestModel->insert([ return $this->respond([
'control_ref_id' => $id,
'test_ref_id' => $testId,
'mean' => 0,
'sd' => 0,
]);
}
}
if ($success) {
return $this->response->setJSON([
'status' => 'success', 'status' => 'success',
'message' => 'Control updated successfully' 'message' => 'delete success'
]); ]);
} catch (\Exception $e) {
return $this->failServerError('Something went wrong: ' . $e->getMessage());
} }
return $this->response->setJSON([
'status' => 'error',
'message' => 'Failed to update control'
])->setStatusCode(500);
}
public function delete($id)
{
$this->controlTestModel->where('control_ref_id', $id)->delete();
$success = $this->dictControlModel->delete($id);
if ($success) {
return $this->response->setJSON([
'status' => 'success',
'message' => 'Control deleted successfully'
]);
}
return $this->response->setJSON([
'status' => 'error',
'message' => 'Failed to delete control'
])->setStatusCode(500);
} }
} }

View File

@ -8,98 +8,95 @@ use App\Models\DictDeptModel;
class DeptApiController extends BaseController class DeptApiController extends BaseController
{ {
protected $dictDeptModel; protected $dictDeptModel;
protected $rules;
public function __construct() public function __construct()
{ {
$this->dictDeptModel = new DictDeptModel(); $this->dictDeptModel = new DictDeptModel();
$this->rules = [
'name' => 'required|min_length[1]',
];
} }
public function index() public function index()
{ {
$depts = $this->dictDeptModel->findAll(); try {
return $this->response->setJSON([ $rows = $this->dictDeptModel->findAll();
'status' => 'success', return $this->respond([
'message' => 'Departments fetched successfully',
'data' => $depts
]);
}
public function show($id)
{
$dept = $this->dictDeptModel->find($id);
if (!$dept) {
return $this->response->setJSON([
'status' => 'error',
'message' => 'Department not found'
])->setStatusCode(404);
}
return $this->response->setJSON([
'status' => 'success',
'data' => $dept
]);
}
public function store()
{
$post = $this->request->getJSON(true);
$data = [
'name' => $post['name'] ?? '',
];
$id = $this->dictDeptModel->insert($data);
if ($id) {
return $this->response->setJSON([
'status' => 'success', 'status' => 'success',
'message' => 'Department saved successfully', 'message' => 'fetch success',
'data' => ['dept_id' => $id] 'data' => $rows
]); ], 200);
} catch (\Exception $e) {
return $this->failServerError('Exception: ' . $e->getMessage());
} }
return $this->response->setJSON([
'status' => 'error',
'message' => 'Failed to save department'
])->setStatusCode(500);
} }
public function update($id) public function show($id = null)
{ {
$post = $this->request->getJSON(true); try {
$rows = $this->dictDeptModel->where('dept_id', $id)->findAll();
$data = [ if (empty($rows)) {
'name' => $post['name'] ?? '', return $this->respond([
]; 'status' => 'success',
'message' => 'data not found.'
$success = $this->dictDeptModel->update($id, $data); ], 200);
}
if ($success) { return $this->respond([
return $this->response->setJSON([
'status' => 'success', 'status' => 'success',
'message' => 'Department updated successfully' 'message' => 'fetch success',
]); 'data' => $rows
], 200);
} catch (\Exception $e) {
return $this->failServerError('Something went wrong: ' . $e->getMessage());
} }
return $this->response->setJSON([
'status' => 'error',
'message' => 'Failed to update department'
])->setStatusCode(500);
} }
public function delete($id) public function create()
{ {
$success = $this->dictDeptModel->delete($id); $input = $this->request->getJSON(true);
if (!$this->validate($this->rules)) {
if ($success) { return $this->failValidationErrors($this->validator->getErrors());
return $this->response->setJSON([
'status' => 'success',
'message' => 'Department deleted successfully'
]);
} }
try {
$id = $this->dictDeptModel->insert($input, true);
return $this->respondCreated([
'status' => 'success',
'message' => $id
]);
} catch (\Exception $e) {
return $this->failServerError('Something went wrong: ' . $e->getMessage());
}
}
return $this->response->setJSON([ public function update($id = null)
'status' => 'error', {
'message' => 'Failed to delete department' $input = $this->request->getJSON(true);
])->setStatusCode(500); if (!$this->validate($this->rules)) {
return $this->failValidationErrors($this->validator->getErrors());
}
try {
$this->dictDeptModel->update($id, $input);
return $this->respond([
'status' => 'success',
'message' => 'update success',
'data' => $id
]);
} catch (\Exception $e) {
return $this->failServerError('Something went wrong: ' . $e->getMessage());
}
}
public function delete($id = null)
{
try {
$this->dictDeptModel->delete($id);
return $this->respond([
'status' => 'success',
'message' => 'delete success'
]);
} catch (\Exception $e) {
return $this->failServerError('Something went wrong: ' . $e->getMessage());
}
} }
} }

View File

@ -25,119 +25,111 @@ class EntryApiController extends BaseController
public function getControls() public function getControls()
{ {
$date = $this->request->getGet('date'); try {
$deptid = $this->request->getGet('deptid'); $date = $this->request->getGet('date');
$deptId = $this->request->getGet('deptId');
$controls = $this->dictControlModel->getActiveByDate($date, $deptid); $rows = $this->dictControlModel->getActiveByDate($date, $deptId);
return $this->respond([
return $this->response->setJSON([ 'status' => 'success',
'status' => 'success', 'message' => 'fetch success',
'message' => 'Controls fetched successfully', 'data' => $rows
'data' => $controls ], 200);
]); } catch (\Exception $e) {
return $this->failServerError('Something went wrong: ' . $e->getMessage());
}
} }
public function getTests() public function getTests()
{ {
$controlid = $this->request->getGet('controlid'); try {
$controlId = $this->request->getGet('controlId');
$tests = $this->controlTestModel->getByControl($controlid); $rows = $this->controlTestModel->getByControl($controlId);
return $this->respond([
return $this->response->setJSON([ 'status' => 'success',
'status' => 'success', 'message' => 'fetch success',
'message' => 'Tests fetched successfully', 'data' => $rows
'data' => $tests ], 200);
]); } catch (\Exception $e) {
return $this->failServerError('Something went wrong: ' . $e->getMessage());
}
} }
public function saveDaily() public function saveDaily()
{ {
$post = $this->request->getPost(); $input = $this->request->getJSON(true);
try {
$resultData = [
'control_ref_id' => $input['controlId'] ?? 0,
'test_ref_id' => $input['testId'] ?? 0,
'resdate' => $input['resdate'] ?? date('Y-m-d'),
'resvalue' => $input['resvalue'] ?? '',
'rescomment' => $input['rescomment'] ?? '',
];
$resultData = [ $this->resultModel->saveResult($resultData);
'control_ref_id' => $post['controlid'] ?? 0, return $this->respond([
'test_ref_id' => $post['testid'] ?? 0,
'resdate' => $post['resdate'] ?? date('Y-m-d'),
'resvalue' => $post['resvalue'] ?? '',
'rescomment' => $post['rescomment'] ?? '',
];
$success = $this->resultModel->saveResult($resultData);
if ($success) {
return $this->response->setJSON([
'status' => 'success', 'status' => 'success',
'message' => 'Result saved successfully' 'message' => 'save success'
]); ]);
} catch (\Exception $e) {
return $this->failServerError('Something went wrong: ' . $e->getMessage());
} }
return $this->response->setJSON([
'status' => 'error',
'message' => 'Failed to save result'
])->setStatusCode(500);
} }
public function saveMonthly() public function saveMonthly()
{ {
$post = $this->request->getPost(); $input = $this->request->getJSON(true);
try {
$controlId = $input['controlId'] ?? 0;
$testId = $input['testId'] ?? 0;
$dates = $input['dates'] ?? '';
$resvalues = $input['resvalue'] ?? [];
$controlid = $post['controlid'] ?? 0; foreach ($resvalues as $day => $value) {
$testid = $post['testid'] ?? 0; if (!empty($value)) {
$dates = $post['dates'] ?? ''; $resultData = [
$resvalues = $post['resvalue'] ?? []; 'control_ref_id' => $controlId,
'test_ref_id' => $testId,
$success = true; 'resdate' => $dates . '-' . str_pad($day, 2, '0', STR_PAD_LEFT),
foreach ($resvalues as $day => $value) { 'resvalue' => $value,
if (!empty($value)) { 'rescomment' => '',
$resultData = [ ];
'control_ref_id' => $controlid, $this->resultModel->saveResult($resultData);
'test_ref_id' => $testid,
'resdate' => $dates . '-' . str_pad($day, 2, '0', STR_PAD_LEFT),
'resvalue' => $value,
'rescomment' => '',
];
if (!$this->resultModel->saveResult($resultData)) {
$success = false;
} }
} }
}
if ($success) { return $this->respond([
return $this->response->setJSON([
'status' => 'success', 'status' => 'success',
'message' => 'Monthly data saved successfully' 'message' => 'save success'
]); ]);
} catch (\Exception $e) {
return $this->failServerError('Something went wrong: ' . $e->getMessage());
} }
return $this->response->setJSON([
'status' => 'error',
'message' => 'Failed to save some entries'
])->setStatusCode(500);
} }
public function saveComment() public function saveComment()
{ {
$post = $this->request->getPost(); $input = $this->request->getJSON(true);
try {
$commentData = [
'control_ref_id' => $input['controlId'] ?? 0,
'test_ref_id' => $input['testId'] ?? 0,
'commonth' => $input['commonth'] ?? '',
'comtext' => $input['comtext'] ?? '',
];
$commentData = [ $this->commentModel->saveComment($commentData);
'control_ref_id' => $post['controlid'] ?? 0, return $this->respond([
'test_ref_id' => $post['testid'] ?? 0,
'commonth' => $post['commonth'] ?? '',
'comtext' => $post['comtext'] ?? '',
];
$success = $this->commentModel->saveComment($commentData);
if ($success) {
return $this->response->setJSON([
'status' => 'success', 'status' => 'success',
'message' => 'Comment saved successfully' 'message' => 'save success'
]); ]);
} catch (\Exception $e) {
return $this->failServerError('Something went wrong: ' . $e->getMessage());
} }
return $this->response->setJSON([
'status' => 'error',
'message' => 'Failed to save comment'
])->setStatusCode(500);
} }
} }

View File

@ -10,111 +10,97 @@ class TestApiController extends BaseController
{ {
protected $dictTestModel; protected $dictTestModel;
protected $dictDeptModel; protected $dictDeptModel;
protected $rules;
public function __construct() public function __construct()
{ {
$this->dictTestModel = new DictTestModel(); $this->dictTestModel = new DictTestModel();
$this->dictDeptModel = new DictDeptModel(); $this->dictDeptModel = new DictDeptModel();
$this->rules = [
'name' => 'required|min_length[1]',
'dept_ref_id' => 'required',
];
} }
public function index() public function index()
{ {
$tests = $this->dictTestModel->getWithDept(); try {
return $this->response->setJSON([ $rows = $this->dictTestModel->getWithDept();
'status' => 'success', return $this->respond([
'message' => 'Tests fetched successfully',
'data' => $tests
]);
}
public function show($id)
{
$test = $this->dictTestModel->find($id);
if (!$test) {
return $this->response->setJSON([
'status' => 'error',
'message' => 'Test not found'
])->setStatusCode(404);
}
return $this->response->setJSON([
'status' => 'success',
'data' => $test
]);
}
public function store()
{
$post = $this->request->getJSON(true);
$testData = [
'dept_ref_id' => $post['dept_ref_id'] ?? null,
'name' => $post['name'] ?? '',
'unit' => $post['unit'] ?? '',
'method' => $post['method'] ?? '',
'cva' => $post['cva'] ?? '',
'ba' => $post['ba'] ?? '',
'tea' => $post['tea'] ?? '',
];
$id = $this->dictTestModel->insert($testData);
if ($id) {
return $this->response->setJSON([
'status' => 'success', 'status' => 'success',
'message' => 'Test saved successfully', 'message' => 'fetch success',
'data' => ['test_id' => $id] 'data' => $rows
]); ], 200);
} catch (\Exception $e) {
return $this->failServerError('Exception: ' . $e->getMessage());
} }
return $this->response->setJSON([
'status' => 'error',
'message' => 'Failed to save test'
])->setStatusCode(500);
} }
public function update($id) public function show($id = null)
{ {
$post = $this->request->getJSON(true); try {
$rows = $this->dictTestModel->where('test_id', $id)->findAll();
$testData = [ if (empty($rows)) {
'dept_ref_id' => $post['dept_ref_id'] ?? null, return $this->respond([
'name' => $post['name'] ?? '', 'status' => 'success',
'unit' => $post['unit'] ?? '', 'message' => 'data not found.'
'method' => $post['method'] ?? '', ], 200);
'cva' => $post['cva'] ?? '', }
'ba' => $post['ba'] ?? '', return $this->respond([
'tea' => $post['tea'] ?? '',
];
$success = $this->dictTestModel->update($id, $testData);
if ($success) {
return $this->response->setJSON([
'status' => 'success', 'status' => 'success',
'message' => 'Test updated successfully' 'message' => 'fetch success',
]); 'data' => $rows
], 200);
} catch (\Exception $e) {
return $this->failServerError('Something went wrong: ' . $e->getMessage());
} }
return $this->response->setJSON([
'status' => 'error',
'message' => 'Failed to update test'
])->setStatusCode(500);
} }
public function delete($id) public function create()
{ {
$success = $this->dictTestModel->delete($id); $input = $this->request->getJSON(true);
if (!$this->validate($this->rules)) {
if ($success) { return $this->failValidationErrors($this->validator->getErrors());
return $this->response->setJSON([
'status' => 'success',
'message' => 'Test deleted successfully'
]);
} }
try {
$id = $this->dictTestModel->insert($input, true);
return $this->respondCreated([
'status' => 'success',
'message' => $id
]);
} catch (\Exception $e) {
return $this->failServerError('Something went wrong: ' . $e->getMessage());
}
}
return $this->response->setJSON([ public function update($id = null)
'status' => 'error', {
'message' => 'Failed to delete test' $input = $this->request->getJSON(true);
])->setStatusCode(500); if (!$this->validate($this->rules)) {
return $this->failValidationErrors($this->validator->getErrors());
}
try {
$this->dictTestModel->update($id, $input);
return $this->respond([
'status' => 'success',
'message' => 'update success',
'data' => $id
]);
} catch (\Exception $e) {
return $this->failServerError('Something went wrong: ' . $e->getMessage());
}
}
public function delete($id = null)
{
try {
$this->dictTestModel->delete($id);
return $this->respond([
'status' => 'success',
'message' => 'delete success'
]);
} catch (\Exception $e) {
return $this->failServerError('Something went wrong: ' . $e->getMessage());
}
} }
} }

View File

@ -3,18 +3,21 @@
namespace App\Controllers; namespace App\Controllers;
use CodeIgniter\Controller; use CodeIgniter\Controller;
use CodeIgniter\API\ResponseTrait;
use CodeIgniter\HTTP\RequestInterface; use CodeIgniter\HTTP\RequestInterface;
use CodeIgniter\HTTP\ResponseInterface; use CodeIgniter\HTTP\ResponseInterface;
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
abstract class BaseController extends Controller abstract class BaseController extends Controller
{ {
use ResponseTrait;
protected $session; protected $session;
public function initController(RequestInterface $request, ResponseInterface $response, LoggerInterface $logger) public function initController(RequestInterface $request, ResponseInterface $response, LoggerInterface $logger)
{ {
parent::initController($request, $response, $logger); parent::initController($request, $response, $logger);
$this->session = \Config\Services::session(); $this->session = \Config\Services::session();
$this->helpers = ['form', 'url']; $this->helpers = ['form', 'url', 'json'];
} }
} }

View File

@ -33,6 +33,18 @@ class CreateCmodQcTables extends Migration
'type' => 'FLOAT', 'type' => 'FLOAT',
'null' => true, 'null' => true,
], ],
'created_at' => [
'type' => 'DATETIME',
'null' => true,
],
'updated_at' => [
'type' => 'DATETIME',
'null' => true,
],
'deleted_at' => [
'type' => 'DATETIME',
'null' => true,
],
]); ]);
$this->forge->addKey('control_test_id', true); $this->forge->addKey('control_test_id', true);
$this->forge->createTable('control_tests'); $this->forge->createTable('control_tests');
@ -67,6 +79,18 @@ class CreateCmodQcTables extends Migration
'type' => 'TEXT', 'type' => 'TEXT',
'null' => true, 'null' => true,
], ],
'created_at' => [
'type' => 'DATETIME',
'null' => true,
],
'updated_at' => [
'type' => 'DATETIME',
'null' => true,
],
'deleted_at' => [
'type' => 'DATETIME',
'null' => true,
],
]); ]);
$this->forge->addKey('result_id', true); $this->forge->addKey('result_id', true);
$this->forge->createTable('results'); $this->forge->createTable('results');
@ -101,6 +125,18 @@ class CreateCmodQcTables extends Migration
'type' => 'DATE', 'type' => 'DATE',
'null' => true, 'null' => true,
], ],
'created_at' => [
'type' => 'DATETIME',
'null' => true,
],
'updated_at' => [
'type' => 'DATETIME',
'null' => true,
],
'deleted_at' => [
'type' => 'DATETIME',
'null' => true,
],
]); ]);
$this->forge->addKey('control_id', true); $this->forge->addKey('control_id', true);
$this->forge->createTable('dict_controls'); $this->forge->createTable('dict_controls');
@ -117,6 +153,18 @@ class CreateCmodQcTables extends Migration
'constraint' => 50, 'constraint' => 50,
'null' => true, 'null' => true,
], ],
'created_at' => [
'type' => 'DATETIME',
'null' => true,
],
'updated_at' => [
'type' => 'DATETIME',
'null' => true,
],
'deleted_at' => [
'type' => 'DATETIME',
'null' => true,
],
]); ]);
$this->forge->addKey('dept_id', true); $this->forge->addKey('dept_id', true);
$this->forge->createTable('dict_depts'); $this->forge->createTable('dict_depts');
@ -163,6 +211,18 @@ class CreateCmodQcTables extends Migration
'constraint' => 50, 'constraint' => 50,
'null' => true, 'null' => true,
], ],
'created_at' => [
'type' => 'DATETIME',
'null' => true,
],
'updated_at' => [
'type' => 'DATETIME',
'null' => true,
],
'deleted_at' => [
'type' => 'DATETIME',
'null' => true,
],
]); ]);
$this->forge->addKey('test_id', true); $this->forge->addKey('test_id', true);
$this->forge->createTable('dict_tests'); $this->forge->createTable('dict_tests');
@ -193,6 +253,18 @@ class CreateCmodQcTables extends Migration
'type' => 'TEXT', 'type' => 'TEXT',
'null' => true, 'null' => true,
], ],
'created_at' => [
'type' => 'DATETIME',
'null' => true,
],
'updated_at' => [
'type' => 'DATETIME',
'null' => true,
],
'deleted_at' => [
'type' => 'DATETIME',
'null' => true,
],
]); ]);
$this->forge->addKey('result_comment_id', true); $this->forge->addKey('result_comment_id', true);
$this->forge->createTable('result_comments'); $this->forge->createTable('result_comments');

View File

@ -0,0 +1,47 @@
<?php
if (!function_exists('camel_to_snake')) {
function camel_to_snake(string $string): string
{
$pattern = '/([a-z])([A-Z])/';
return strtolower(preg_replace($pattern, '$1_$2', $string));
}
}
if (!function_exists('camel_to_snake_array')) {
function camel_to_snake_array(array $data): array
{
$converted = [];
foreach ($data as $key => $value) {
$snakeKey = camel_to_snake($key);
$converted[$snakeKey] = $value;
}
return $converted;
}
}
if (!function_exists('snake_to_camel')) {
function snake_to_camel(string $string): string
{
$parts = explode('_', $string);
$camel = array_map(function ($part, $index) {
if ($index === 0) {
return $part;
}
return ucfirst($part);
}, $parts, array_keys($parts));
return implode('', $camel);
}
}
if (!function_exists('snake_to_camel_array')) {
function snake_to_camel_array(array $data): array
{
$converted = [];
foreach ($data as $key => $value) {
$camelKey = snake_to_camel($key);
$converted[$camelKey] = $value;
}
return $converted;
}
}

113
app/Models/BaseModel.php Normal file
View File

@ -0,0 +1,113 @@
<?php
namespace App\Models;
use CodeIgniter\Model;
class BaseModel extends Model
{
protected $returnType = 'array';
protected $useSoftDeletes = true;
protected $useTimestamps = true;
protected $createdField = 'created_at';
protected $updatedField = 'updated_at';
protected $deletedField = 'deleted_at';
public function __construct()
{
parent::__construct();
}
protected function initialize()
{
if (empty($this->returnType)) {
$this->returnType = 'array';
}
}
public function findAll(?int $limit = null, int $offset = 0)
{
$rows = parent::findAll($limit, $offset);
return $this->snakeToCamelRecursive($rows);
}
public function find($id = null)
{
$row = parent::find($id);
if ($row) {
return $this->snakeToCamel($row);
}
return null;
}
public function first()
{
$row = parent::first();
if ($row) {
return $this->snakeToCamel($row);
}
return null;
}
protected function snakeToCamel(array $row): array
{
$converted = [];
foreach ($row as $key => $value) {
$camelKey = $this->snakeToCamelKey($key);
$converted[$camelKey] = $value;
}
return $converted;
}
protected function snakeToCamelRecursive($data)
{
if (is_array($data)) {
if (isset($data[0]) || empty($data)) {
return array_map([$this, 'snakeToCamelRecursive'], $data);
}
return $this->snakeToCamel($data);
}
return $data;
}
protected function snakeToCamelKey(string $key): string
{
$parts = explode('_', $key);
$camel = array_map(function ($part, $index) {
if ($index === 0) {
return $part;
}
return ucfirst($part);
}, $parts, array_keys($parts));
return implode('', $camel);
}
public function insert($data = null, bool $returnID = true)
{
$snakeData = $this->camelToSnakeArray($data);
return parent::insert($snakeData, $returnID);
}
public function update($id = null, $row = null): bool
{
$snakeData = $this->camelToSnakeArray($row);
return parent::update($id, $snakeData);
}
protected function camelToSnakeArray(array $data): array
{
$converted = [];
foreach ($data as $key => $value) {
$snakeKey = $this->camelToSnakeKey($key);
$converted[$snakeKey] = $value;
}
return $converted;
}
protected function camelToSnakeKey(string $key): string
{
$pattern = '/([a-z])([A-Z])/';
$snake = preg_replace($pattern, '$1_$2', $key);
return strtolower($snake);
}
}

View File

@ -2,38 +2,38 @@
namespace App\Models; namespace App\Models;
use CodeIgniter\Model; use App\Models\BaseModel;
class ControlModel extends Model class ControlModel extends BaseModel
{ {
protected $table = 'dict_control'; protected $table = 'dict_controls';
protected $primaryKey = 'id'; protected $primaryKey = 'control_id';
protected $useAutoIncrement = true; protected $useAutoIncrement = true;
protected $returnType = 'array'; protected $returnType = 'array';
protected $useSoftDeletes = false; protected $useSoftDeletes = true;
protected $allowedFields = ['deptid', 'name', 'lot', 'producer', 'expdate']; protected $useTimestamps = true;
protected $useTimestamps = false; protected $allowedFields = ['dept_ref_id', 'name', 'lot', 'producer', 'expdate'];
public function getByDept($deptid) public function getByDept($deptId)
{ {
return $this->where('deptid', $deptid)->findAll(); return $this->where('dept_ref_id', $deptId)->findAll();
} }
public function getWithDept() public function getWithDept()
{ {
$builder = $this->db->table('dict_control c'); $builder = $this->db->table('dict_controls c');
$builder->select('c.*, d.name as dept_name'); $builder->select('c.*, d.name as dept_name');
$builder->join('dict_dept d', 'd.id = c.deptid', 'left'); $builder->join('dict_depts d', 'd.dept_id = c.dept_ref_id', 'left');
return $builder->get()->getResultArray(); return $builder->get()->getResultArray();
} }
public function getActiveByDate($date, $deptid = null) public function getActiveByDate($date, $deptId = null)
{ {
$builder = $this->db->table('dict_control c'); $builder = $this->db->table('dict_controls c');
$builder->select('c.*'); $builder->select('c.*');
$builder->where('c.expdate >=', $date); $builder->where('c.expdate >=', $date);
if ($deptid) { if ($deptId) {
$builder->where('c.deptid', $deptid); $builder->where('c.dept_ref_id', $deptId);
} }
$builder->orderBy('c.name', 'ASC'); $builder->orderBy('c.name', 'ASC');
return $builder->get()->getResultArray(); return $builder->get()->getResultArray();

View File

@ -2,17 +2,17 @@
namespace App\Models; namespace App\Models;
use CodeIgniter\Model; use App\Models\BaseModel;
class ControlTestModel extends Model class ControlTestModel extends BaseModel
{ {
protected $table = 'control_tests'; protected $table = 'control_tests';
protected $primaryKey = 'control_test_id'; protected $primaryKey = 'control_test_id';
protected $useAutoIncrement = true; protected $useAutoIncrement = true;
protected $returnType = 'array'; protected $returnType = 'array';
protected $useSoftDeletes = false; protected $useSoftDeletes = true;
protected $allowedFields = ['control_test_id', 'control_ref_id', 'test_ref_id', 'mean', 'sd']; protected $useTimestamps = true;
protected $useTimestamps = false; protected $allowedFields = ['control_ref_id', 'test_ref_id', 'mean', 'sd'];
public function getByControl($controlId) public function getByControl($controlId)
{ {

View File

@ -2,41 +2,41 @@
namespace App\Models; namespace App\Models;
use CodeIgniter\Model; use App\Models\BaseModel;
class DailyResultModel extends Model class DailyResultModel extends BaseModel
{ {
protected $table = 'daily_result'; protected $table = 'daily_result';
protected $primaryKey = 'id'; protected $primaryKey = 'daily_result_id';
protected $useAutoIncrement = true; protected $useAutoIncrement = true;
protected $returnType = 'array'; protected $returnType = 'array';
protected $useSoftDeletes = false; protected $useSoftDeletes = true;
protected $allowedFields = ['controlid', 'testid', 'resdate', 'resvalue', 'rescomment']; protected $useTimestamps = true;
protected $useTimestamps = false; protected $allowedFields = ['control_ref_id', 'test_ref_id', 'resdate', 'resvalue', 'rescomment'];
public function getByMonth($controlid, $testid, $yearMonth) public function getByMonth($controlId, $testId, $yearMonth)
{ {
$startDate = $yearMonth . '-01'; $startDate = $yearMonth . '-01';
$endDate = $yearMonth . '-31'; $endDate = $yearMonth . '-31';
$builder = $this->db->table('daily_result'); $builder = $this->db->table('daily_result');
$builder->select('*'); $builder->select('*');
$builder->where('controlid', $controlid); $builder->where('control_ref_id', $controlId);
$builder->where('testid', $testid); $builder->where('test_ref_id', $testId);
$builder->where('resdate >=', $startDate); $builder->where('resdate >=', $startDate);
$builder->where('resdate <=', $endDate); $builder->where('resdate <=', $endDate);
$builder->orderBy('resdate', 'ASC'); $builder->orderBy('resdate', 'ASC');
return $builder->get()->getResultArray(); return $builder->get()->getResultArray();
} }
public function getByControlMonth($controlid, $yearMonth) public function getByControlMonth($controlId, $yearMonth)
{ {
$startDate = $yearMonth . '-01'; $startDate = $yearMonth . '-01';
$endDate = $yearMonth . '-31'; $endDate = $yearMonth . '-31';
$builder = $this->db->table('daily_result'); $builder = $this->db->table('daily_result');
$builder->select('*'); $builder->select('*');
$builder->where('controlid', $controlid); $builder->where('control_ref_id', $controlId);
$builder->where('resdate >=', $startDate); $builder->where('resdate >=', $startDate);
$builder->where('resdate <=', $endDate); $builder->where('resdate <=', $endDate);
return $builder->get()->getResultArray(); return $builder->get()->getResultArray();
@ -46,14 +46,14 @@ class DailyResultModel extends Model
{ {
$builder = $this->db->table('daily_result'); $builder = $this->db->table('daily_result');
$existing = $builder->select('*') $existing = $builder->select('*')
->where('controlid', $data['controlid']) ->where('control_ref_id', $data['control_ref_id'])
->where('testid', $data['testid']) ->where('test_ref_id', $data['test_ref_id'])
->where('resdate', $data['resdate']) ->where('resdate', $data['resdate'])
->get() ->get()
->getRowArray(); ->getRowArray();
if ($existing) { if ($existing) {
return $builder->where('id', $existing['id'])->update($data); return $builder->where('daily_result_id', $existing['daily_result_id'])->update($data);
} else { } else {
return $builder->insert($data); return $builder->insert($data);
} }

View File

@ -2,15 +2,15 @@
namespace App\Models; namespace App\Models;
use CodeIgniter\Model; use App\Models\BaseModel;
class DeptModel extends Model class DeptModel extends BaseModel
{ {
protected $table = 'dict_dept'; protected $table = 'dict_depts';
protected $primaryKey = 'id'; protected $primaryKey = 'dept_id';
protected $useAutoIncrement = true; protected $useAutoIncrement = true;
protected $returnType = 'array'; protected $returnType = 'array';
protected $useSoftDeletes = false; protected $useSoftDeletes = true;
protected $useTimestamps = true;
protected $allowedFields = ['name']; protected $allowedFields = ['name'];
protected $useTimestamps = false;
} }

View File

@ -2,17 +2,17 @@
namespace App\Models; namespace App\Models;
use CodeIgniter\Model; use App\Models\BaseModel;
class DictControlModel extends Model class DictControlModel extends BaseModel
{ {
protected $table = 'dict_controls'; protected $table = 'dict_controls';
protected $primaryKey = 'control_id'; protected $primaryKey = 'control_id';
protected $useAutoIncrement = true; protected $useAutoIncrement = true;
protected $returnType = 'array'; protected $returnType = 'array';
protected $useSoftDeletes = false; protected $useSoftDeletes = true;
protected $allowedFields = ['control_id', 'dept_ref_id', 'name', 'lot', 'producer', 'expdate']; protected $useTimestamps = true;
protected $useTimestamps = false; protected $allowedFields = ['dept_ref_id', 'name', 'lot', 'producer', 'expdate'];
public function getByDept($deptId) public function getByDept($deptId)
{ {

View File

@ -2,15 +2,15 @@
namespace App\Models; namespace App\Models;
use CodeIgniter\Model; use App\Models\BaseModel;
class DictDeptModel extends Model class DictDeptModel extends BaseModel
{ {
protected $table = 'dict_depts'; protected $table = 'dict_depts';
protected $primaryKey = 'dept_id'; protected $primaryKey = 'dept_id';
protected $useAutoIncrement = true; protected $useAutoIncrement = true;
protected $returnType = 'array'; protected $returnType = 'array';
protected $useSoftDeletes = false; protected $useSoftDeletes = true;
protected $allowedFields = ['dept_id', 'name']; protected $useTimestamps = true;
protected $useTimestamps = false; protected $allowedFields = ['name'];
} }

View File

@ -2,17 +2,17 @@
namespace App\Models; namespace App\Models;
use CodeIgniter\Model; use App\Models\BaseModel;
class DictTestModel extends Model class DictTestModel extends BaseModel
{ {
protected $table = 'dict_tests'; protected $table = 'dict_tests';
protected $primaryKey = 'test_id'; protected $primaryKey = 'test_id';
protected $useAutoIncrement = true; protected $useAutoIncrement = true;
protected $returnType = 'array'; protected $returnType = 'array';
protected $useSoftDeletes = false; protected $useSoftDeletes = true;
protected $allowedFields = ['test_id', 'dept_ref_id', 'name', 'unit', 'method', 'cva', 'ba', 'tea']; protected $useTimestamps = true;
protected $useTimestamps = false; protected $allowedFields = ['dept_ref_id', 'name', 'unit', 'method', 'cva', 'ba', 'tea'];
public function getByDept($deptId) public function getByDept($deptId)
{ {

View File

@ -2,35 +2,35 @@
namespace App\Models; namespace App\Models;
use CodeIgniter\Model; use App\Models\BaseModel;
class MonthlyCommentModel extends Model class MonthlyCommentModel extends BaseModel
{ {
protected $table = 'monthly_comment'; protected $table = 'monthly_comment';
protected $primaryKey = 'id'; protected $primaryKey = 'monthly_comment_id';
protected $useAutoIncrement = true; protected $useAutoIncrement = true;
protected $returnType = 'array'; protected $returnType = 'array';
protected $useSoftDeletes = false; protected $useSoftDeletes = true;
protected $allowedFields = ['controlid', 'testid', 'commonth', 'comtext']; protected $useTimestamps = true;
protected $useTimestamps = false; protected $allowedFields = ['control_ref_id', 'test_ref_id', 'commonth', 'comtext'];
public function getByControlTestMonth($controlid, $testid, $yearMonth) public function getByControlTestMonth($controlId, $testId, $yearMonth)
{ {
return $this->where('controlid', $controlid) return $this->where('control_ref_id', $controlId)
->where('testid', $testid) ->where('test_ref_id', $testId)
->where('commonth', $yearMonth) ->where('commonth', $yearMonth)
->first(); ->first();
} }
public function saveComment($data) public function saveComment($data)
{ {
$existing = $this->where('controlid', $data['controlid']) $existing = $this->where('control_ref_id', $data['control_ref_id'])
->where('testid', $data['testid']) ->where('test_ref_id', $data['test_ref_id'])
->where('commonth', $data['commonth']) ->where('commonth', $data['commonth'])
->first(); ->first();
if ($existing) { if ($existing) {
return $this->update($existing['id'], $data); return $this->update($existing['monthly_comment_id'], $data);
} else { } else {
return $this->insert($data); return $this->insert($data);
} }

View File

@ -2,17 +2,17 @@
namespace App\Models; namespace App\Models;
use CodeIgniter\Model; use App\Models\BaseModel;
class ResultCommentModel extends Model class ResultCommentModel extends BaseModel
{ {
protected $table = 'result_comments'; protected $table = 'result_comments';
protected $primaryKey = 'result_comment_id'; protected $primaryKey = 'result_comment_id';
protected $useAutoIncrement = true; protected $useAutoIncrement = true;
protected $returnType = 'array'; protected $returnType = 'array';
protected $useSoftDeletes = false; protected $useSoftDeletes = true;
protected $allowedFields = ['result_comment_id', 'control_ref_id', 'test_ref_id', 'commonth', 'comtext']; protected $useTimestamps = true;
protected $useTimestamps = false; protected $allowedFields = ['control_ref_id', 'test_ref_id', 'commonth', 'comtext'];
public function getByControlTestMonth($controlId, $testId, $yearMonth) public function getByControlTestMonth($controlId, $testId, $yearMonth)
{ {

View File

@ -2,17 +2,17 @@
namespace App\Models; namespace App\Models;
use CodeIgniter\Model; use App\Models\BaseModel;
class ResultModel extends Model class ResultModel extends BaseModel
{ {
protected $table = 'results'; protected $table = 'results';
protected $primaryKey = 'result_id'; protected $primaryKey = 'result_id';
protected $useAutoIncrement = true; protected $useAutoIncrement = true;
protected $returnType = 'array'; protected $returnType = 'array';
protected $useSoftDeletes = false; protected $useSoftDeletes = true;
protected $allowedFields = ['result_id', 'control_ref_id', 'test_ref_id', 'resdate', 'resvalue', 'rescomment']; protected $useTimestamps = true;
protected $useTimestamps = false; protected $allowedFields = ['control_ref_id', 'test_ref_id', 'resdate', 'resvalue', 'rescomment'];
public function getByMonth($controlId, $testId, $yearMonth) public function getByMonth($controlId, $testId, $yearMonth)
{ {

View File

@ -2,28 +2,28 @@
namespace App\Models; namespace App\Models;
use CodeIgniter\Model; use App\Models\BaseModel;
class TestModel extends Model class TestModel extends BaseModel
{ {
protected $table = 'dict_test'; protected $table = 'dict_tests';
protected $primaryKey = 'id'; protected $primaryKey = 'test_id';
protected $useAutoIncrement = true; protected $useAutoIncrement = true;
protected $returnType = 'array'; protected $returnType = 'array';
protected $useSoftDeletes = false; protected $useSoftDeletes = true;
protected $allowedFields = ['deptid', 'name', 'unit', 'method', 'cva', 'ba', 'tea']; protected $useTimestamps = true;
protected $useTimestamps = false; protected $allowedFields = ['dept_ref_id', 'name', 'unit', 'method', 'cva', 'ba', 'tea'];
public function getByDept($deptid) public function getByDept($deptId)
{ {
return $this->where('deptid', $deptid)->findAll(); return $this->where('dept_ref_id', $deptId)->findAll();
} }
public function getWithDept() public function getWithDept()
{ {
$builder = $this->db->table('dict_test t'); $builder = $this->db->table('dict_tests t');
$builder->select('t.*, d.name as dept_name'); $builder->select('t.*, d.name as dept_name');
$builder->join('dict_dept d', 'd.id = t.deptid', 'left'); $builder->join('dict_depts d', 'd.dept_id = t.dept_ref_id', 'left');
return $builder->get()->getResultArray(); return $builder->get()->getResultArray();
} }
} }

View File

@ -197,7 +197,7 @@ document.addEventListener('alpine:init', () => {
: `${window.BASEURL}/api/control`; : `${window.BASEURL}/api/control`;
const res = await fetch(url, { const res = await fetch(url, {
method: this.form.control_id ? 'PUT' : 'POST', method: this.form.control_id ? 'PATCH' : 'POST',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(this.form) body: JSON.stringify(this.form)
}); });

View File

@ -142,7 +142,7 @@ document.addEventListener('alpine:init', () => {
: `${window.BASEURL}/api/dept`; : `${window.BASEURL}/api/dept`;
const res = await fetch(url, { const res = await fetch(url, {
method: this.form.dept_id ? 'PUT' : 'POST', method: this.form.dept_id ? 'PATCH' : 'POST',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name: this.form.name }) body: JSON.stringify({ name: this.form.name })
}); });

View File

@ -94,7 +94,7 @@
</style> </style>
</head> </head>
<body class="bg-gray-50 h-full overflow-hidden text-slate-800 antialiased"> <body x-data="layoutManager()" :class="{ 'overflow-hidden': $store.appState.sidebarOpen && isMobile() }" class="bg-gray-50 h-full text-slate-800 antialiased">
<div x-cloak x-show="App.loading" x-transition.opacity <div x-cloak x-show="App.loading" x-transition.opacity
class="fixed inset-0 z-[100] flex items-center justify-center bg-white/80 backdrop-blur-sm"> class="fixed inset-0 z-[100] flex items-center justify-center bg-white/80 backdrop-blur-sm">
<div class="bg-white shadow-xl rounded-2xl p-6 flex items-center space-x-4 border border-slate-100"> <div class="bg-white shadow-xl rounded-2xl p-6 flex items-center space-x-4 border border-slate-100">
@ -104,16 +104,16 @@
</div> </div>
<div class="flex h-screen bg-slate-50"> <div class="flex h-screen bg-slate-50">
<div id="sidebar-backdrop" x-show="App.sidebarOpen" @click="App.sidebarOpen = false" x-transition.opacity aria-hidden="true" <div id="sidebar-backdrop" x-show="$store.appState.sidebarOpen" @click="$store.appState.sidebarOpen = false" x-transition.opacity aria-hidden="true"
class="fixed inset-0 z-40 bg-slate-900/50 backdrop-blur-sm lg:hidden"></div> class="fixed inset-0 z-40 bg-slate-900/50 backdrop-blur-sm lg:hidden"></div>
<aside id="sidebar" <aside id="sidebar"
x-data="{ x-data="{
resultsOpen: <?= in_array($active_menu, ['entry', 'entry_daily']) ? 'true' : 'false' ?>, resultsOpen: <?= in_array($active_menu, ['entry', 'entry_daily']) ? 'true' : 'false' ?>,
masterOpen: <?= in_array($active_menu, ['test', 'control', 'dept']) ? 'true' : 'false' ?> masterOpen: <?= in_array($active_menu, ['test', 'control', 'dept']) ? 'true' : 'false' ?>
}" }"
:class="App.sidebarOpen ? 'translate-x-0' : '-translate-x-full'" :class="$store.appState.sidebarOpen ? 'translate-x-0' : '-translate-x-full lg:-translate-x-full'"
class="fixed inset-y-0 left-0 z-50 w-72 bg-white border-r border-slate-200 transition-transform duration-300 ease-in-out lg:static lg:translate-x-0 flex flex-col shadow-lg lg:shadow-none"> class="fixed inset-y-0 left-0 z-50 w-64 transition-transform duration-300 ease-in-out flex flex-col shadow-lg">
<div class="flex h-16 items-center px-6 border-b border-slate-100 bg-white/50"> <div class="flex h-16 items-center px-6 border-b border-slate-100 bg-white/50">
<div class="flex items-center space-x-3"> <div class="flex items-center space-x-3">
<div class="h-8 w-8 rounded-lg bg-gradient-to-br from-primary-600 to-primary-700 flex items-center justify-center shadow-lg shadow-primary-500/30"> <div class="h-8 w-8 rounded-lg bg-gradient-to-br from-primary-600 to-primary-700 flex items-center justify-center shadow-lg shadow-primary-500/30">
@ -203,11 +203,12 @@
</div> </div>
</aside> </aside>
<div class="flex-1 flex flex-col overflow-hidden bg-slate-50/50"> <div class="flex-1 flex flex-col overflow-hidden bg-slate-50/50 transition-all duration-300"
:class="$store.appState.sidebarOpen ? 'lg:ml-64' : 'lg:ml-0'">
<header class="bg-white/80 backdrop-blur-md sticky top-0 z-30 border-b border-slate-200/60 supports-backdrop-blur:bg-white/60"> <header class="bg-white/80 backdrop-blur-md sticky top-0 z-30 border-b border-slate-200/60 supports-backdrop-blur:bg-white/60">
<div class="flex items-center justify-between h-16 px-8"> <div class="flex items-center justify-between h-16 px-8">
<div class="flex items-center"> <div class="flex items-center">
<button id="sidebar-toggle" @click="App.sidebarOpen = !App.sidebarOpen" class="lg:hidden p-2 -ml-2 text-slate-500 hover:text-slate-700 hover:bg-slate-100 rounded-lg"> <button id="sidebar-toggle" @click="$store.appState.sidebarOpen = !$store.appState.sidebarOpen" class="p-2 -ml-2 text-slate-500 hover:text-slate-700 hover:bg-slate-100 rounded-lg">
<i class="fa-solid fa-bars h-6 w-6 flex items-center justify-center"></i> <i class="fa-solid fa-bars h-6 w-6 flex items-center justify-center"></i>
</button> </button>
<h2 class="ml-2 lg:ml-0 text-xl font-bold text-slate-800 tracking-tight"><?= $page_title ?? 'Dashboard' ?></h2> <h2 class="ml-2 lg:ml-0 text-xl font-bold text-slate-800 tracking-tight"><?= $page_title ?? 'Dashboard' ?></h2>
@ -236,6 +237,15 @@
<script src="<?= base_url('js/app.js') ?>"></script> <script src="<?= base_url('js/app.js') ?>"></script>
<script src="<?= base_url('js/tables.js') ?>"></script> <script src="<?= base_url('js/tables.js') ?>"></script>
<script src="<?= base_url('js/charts.js') ?>"></script> <script src="<?= base_url('js/charts.js') ?>"></script>
<script>
document.addEventListener('alpine:init', () => {
Alpine.data('layoutManager', () => ({
isMobile() {
return window.innerWidth < 1024;
}
}));
});
</script>
<?= $this->renderSection("script"); ?> <?= $this->renderSection("script"); ?>
</body> </body>

View File

@ -186,7 +186,7 @@ document.addEventListener('alpine:init', () => {
: `${window.BASEURL}/api/test`; : `${window.BASEURL}/api/test`;
const res = await fetch(url, { const res = await fetch(url, {
method: this.form.test_id ? 'PUT' : 'POST', method: this.form.test_id ? 'PATCH' : 'POST',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(this.form) body: JSON.stringify(this.form)
}); });

File diff suppressed because one or more lines are too long

View File

@ -1,3 +1,10 @@
document.addEventListener('alpine:init', () => {
Alpine.store('appState', {
loading: false,
sidebarOpen: false
});
});
window.App = { window.App = {
loading: false, loading: false,
sidebarOpen: false, sidebarOpen: false,
@ -15,12 +22,18 @@ window.App = {
if (toggleBtn) { if (toggleBtn) {
toggleBtn.addEventListener('click', () => { toggleBtn.addEventListener('click', () => {
this.sidebarOpen = !this.sidebarOpen; this.sidebarOpen = !this.sidebarOpen;
if (window.Alpine) {
Alpine.store('appState').sidebarOpen = this.sidebarOpen;
}
}); });
} }
if (backdrop) { if (backdrop) {
backdrop.addEventListener('click', () => { backdrop.addEventListener('click', () => {
this.sidebarOpen = false; this.sidebarOpen = false;
if (window.Alpine) {
Alpine.store('appState').sidebarOpen = false;
}
}); });
} }
}, },

File diff suppressed because one or more lines are too long

4
v1/assets/jquery.js vendored

File diff suppressed because one or more lines are too long

View File

@ -1,349 +0,0 @@
/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */
/* Document
========================================================================== */
/**
* 1. Correct the line height in all browsers.
* 2. Prevent adjustments of font size after orientation changes in iOS.
*/
html {
line-height: 1.15; /* 1 */
-webkit-text-size-adjust: 100%; /* 2 */
}
/* Sections
========================================================================== */
/**
* Remove the margin in all browsers.
*/
body {
margin: 0;
}
/**
* Render the `main` element consistently in IE.
*/
main {
display: block;
}
/**
* Correct the font size and margin on `h1` elements within `section` and
* `article` contexts in Chrome, Firefox, and Safari.
*/
h1 {
font-size: 2em;
margin: 0.67em 0;
}
/* Grouping content
========================================================================== */
/**
* 1. Add the correct box sizing in Firefox.
* 2. Show the overflow in Edge and IE.
*/
hr {
box-sizing: content-box; /* 1 */
height: 0; /* 1 */
overflow: visible; /* 2 */
}
/**
* 1. Correct the inheritance and scaling of font size in all browsers.
* 2. Correct the odd `em` font sizing in all browsers.
*/
pre {
font-family: monospace, monospace; /* 1 */
font-size: 1em; /* 2 */
}
/* Text-level semantics
========================================================================== */
/**
* Remove the gray background on active links in IE 10.
*/
a {
background-color: transparent;
}
/**
* 1. Remove the bottom border in Chrome 57-
* 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.
*/
abbr[title] {
border-bottom: none; /* 1 */
text-decoration: underline; /* 2 */
text-decoration: underline dotted; /* 2 */
}
/**
* Add the correct font weight in Chrome, Edge, and Safari.
*/
b,
strong {
font-weight: bolder;
}
/**
* 1. Correct the inheritance and scaling of font size in all browsers.
* 2. Correct the odd `em` font sizing in all browsers.
*/
code,
kbd,
samp {
font-family: monospace, monospace; /* 1 */
font-size: 1em; /* 2 */
}
/**
* Add the correct font size in all browsers.
*/
small {
font-size: 80%;
}
/**
* Prevent `sub` and `sup` elements from affecting the line height in
* all browsers.
*/
sub,
sup {
font-size: 75%;
line-height: 0;
position: relative;
vertical-align: baseline;
}
sub {
bottom: -0.25em;
}
sup {
top: -0.5em;
}
/* Embedded content
========================================================================== */
/**
* Remove the border on images inside links in IE 10.
*/
img {
border-style: none;
}
/* Forms
========================================================================== */
/**
* 1. Change the font styles in all browsers.
* 2. Remove the margin in Firefox and Safari.
*/
button,
input,
optgroup,
select,
textarea {
font-family: inherit; /* 1 */
font-size: 100%; /* 1 */
line-height: 1.15; /* 1 */
margin: 0; /* 2 */
}
/**
* Show the overflow in IE.
* 1. Show the overflow in Edge.
*/
button,
input { /* 1 */
overflow: visible;
}
/**
* Remove the inheritance of text transform in Edge, Firefox, and IE.
* 1. Remove the inheritance of text transform in Firefox.
*/
button,
select { /* 1 */
text-transform: none;
}
/**
* Correct the inability to style clickable types in iOS and Safari.
*/
button,
[type="button"],
[type="reset"],
[type="submit"] {
-webkit-appearance: button;
}
/**
* Remove the inner border and padding in Firefox.
*/
button::-moz-focus-inner,
[type="button"]::-moz-focus-inner,
[type="reset"]::-moz-focus-inner,
[type="submit"]::-moz-focus-inner {
border-style: none;
padding: 0;
}
/**
* Restore the focus styles unset by the previous rule.
*/
button:-moz-focusring,
[type="button"]:-moz-focusring,
[type="reset"]:-moz-focusring,
[type="submit"]:-moz-focusring {
outline: 1px dotted ButtonText;
}
/**
* Correct the padding in Firefox.
*/
fieldset {
padding: 0.35em 0.75em 0.625em;
}
/**
* 1. Correct the text wrapping in Edge and IE.
* 2. Correct the color inheritance from `fieldset` elements in IE.
* 3. Remove the padding so developers are not caught out when they zero out
* `fieldset` elements in all browsers.
*/
legend {
box-sizing: border-box; /* 1 */
color: inherit; /* 2 */
display: table; /* 1 */
max-width: 100%; /* 1 */
padding: 0; /* 3 */
white-space: normal; /* 1 */
}
/**
* Add the correct vertical alignment in Chrome, Firefox, and Opera.
*/
progress {
vertical-align: baseline;
}
/**
* Remove the default vertical scrollbar in IE 10+.
*/
textarea {
overflow: auto;
}
/**
* 1. Add the correct box sizing in IE 10.
* 2. Remove the padding in IE 10.
*/
[type="checkbox"],
[type="radio"] {
box-sizing: border-box; /* 1 */
padding: 0; /* 2 */
}
/**
* Correct the cursor style of increment and decrement buttons in Chrome.
*/
[type="number"]::-webkit-inner-spin-button,
[type="number"]::-webkit-outer-spin-button {
height: auto;
}
/**
* 1. Correct the odd appearance in Chrome and Safari.
* 2. Correct the outline style in Safari.
*/
[type="search"] {
-webkit-appearance: textfield; /* 1 */
outline-offset: -2px; /* 2 */
}
/**
* Remove the inner padding in Chrome and Safari on macOS.
*/
[type="search"]::-webkit-search-decoration {
-webkit-appearance: none;
}
/**
* 1. Correct the inability to style clickable types in iOS and Safari.
* 2. Change font properties to `inherit` in Safari.
*/
::-webkit-file-upload-button {
-webkit-appearance: button; /* 1 */
font: inherit; /* 2 */
}
/* Interactive
========================================================================== */
/*
* Add the correct display in Edge, IE 10+, and Firefox.
*/
details {
display: block;
}
/*
* Add the correct display in all browsers.
*/
summary {
display: list-item;
}
/* Misc
========================================================================== */
/**
* Add the correct display in IE 10+.
*/
template {
display: none;
}
/**
* Add the correct display in IE 10.
*/
[hidden] {
display: none;
}

File diff suppressed because one or more lines are too long

View File

@ -1,16 +0,0 @@
/*html,pre,th,table { font-family:'Courier New', Courier, monospace; font-size:7.8pt; margin:0;}*/
html,pre,th,table { font-family:'Lucida Console', Monaco, monospace; font-size:9pt; margin:0;}
#page {
z-index:1; background: white; display: block; margin: 0; page-break-after:always;
width: 210mm; height: 280mm;
}
#page { padding:0.5cm 0.5cm 0cm 0.5cm; }
table {border-collapse:collapse; }
th,td { line-height:1.3; padding:3px; }
td { vertical-align:top; border:solid 1px black; }
th { border:solid 1.5px black; }
h1,h2,h3,h4,h5 { padding:0; margin:0; }
#resultdata { float:left; width:6cm; }
#resultimg { float: left; width:7cm; }
#footer { float:left; margin:0cm auto; }

View File

@ -1,26 +0,0 @@
html, body { margin: 0; padding: 0; font-size: 100%; font: inherit; vertical-align: baseline; }
/* HTML5 display-role reset for older browsers */
article, aside, details, figcaption, figure, footer, header, hgroup, menu, nav, section { display: block; }
body {line-height: 1;}
ol, ul {list-style: none; padding:0;}
blockquote, q { quotes: none;}
blockquote:before, blockquote:after,
q:before, q:after { content: ''; content: none; }
table { border-collapse: collapse; border-spacing: 0; }
ul { list-style-type: none; margin: 0; overflow: hidden; background-color: #595959; }
li { float: left; color: white; text-align: center; padding: 5px 0; }
li a { display: block; padding:5px 10px; color: white; text-align: center; text-decoration: none; }
li a:hover { background-color: #7FDBFF; }
table tr td { padding: 5px 5px; }
table tr th { padding: 5px 5px; }
tr:nth-child(even) {background-color: #e6e6ff;}
#container { width:100%; font-size: 10px; font-weight: bold;}
#content { width:98%;margin:0 auto; }
#nav{
font-family: "Courier New";
font-size: 12px;
font-weight: bold;
}

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@ -1,19 +0,0 @@
<?php
$serverName = "localhost";
$db1 = "cmod_qc";
//$db2 = "summitmod";
$connectionInfo = array( "Database"=>$db1, "UID"=>"sa", "PWD"=>"Summittso4516728");
$conn1 = sqlsrv_connect( $serverName, $connectionInfo);
if( !$conn1 ) {
echo "Connection 1 could not be established.<br />";
die( print_r( sqlsrv_errors(), true));
}
/*
$connectionInfo = array( "Database"=>$db2, "UID"=>"td", "PWD"=>"td");
$conn2 = sqlsrv_connect( $serverName, $connectionInfo);
if( !$conn2 ) {
echo "Connection 2 could not be established.<br />";
die( print_r( sqlsrv_errors(), true));
}
*/
?>

View File

@ -1,26 +0,0 @@
<?php
if(!isset($_POST['submit'])) {
?>
<form method='POST'>
control name<br/>
<input type='text' name='cname' /><br/>
lot<br/>
<input type='text' name='lot' /><br/>
producer<br/>
<input type='text' name='producer' /><br/>
expdate<br/>
<input type='date' name='expdate' /><br/>
<input type='submit' name='submit' value='add' />
</form>
<?php
} else {
$cname=stripslashes($_POST['cname']);
$lot=$_POST['lot'];
$expdate=$_POST['expdate'];
$producer=$_POST['producer'];
$sql="INSERT INTO DICT_CONTROL(name,lot,producer,expdate) values ('$cname','$lot','$producer','$expdate')";
$stmt = sqlsrv_query( $conn1, $sql );
if( $stmt == false) { die( print_r( sqlsrv_errors(), true) ); }
echo "<meta http-equiv='refresh' content='0;url=?p=dict' />";
}
?>

View File

@ -1,13 +0,0 @@
<?php
$cid = $_GET['cid'];
$sql="DELETE DICT_CONTROL WHERE id='$cid'";
$stmt = sqlsrv_query( $conn1, $sql );
if( $stmt == false) { die( print_r( sqlsrv_errors(), true) ); }
$sql="DELETE CONTROL_TEST WHERE controlid='$cid'";
$stmt = sqlsrv_query( $conn1, $sql );
if( $stmt == false) { die( print_r( sqlsrv_errors(), true) ); }
echo "<meta http-equiv='refresh' content='0;url=?p=dict' />";
?>

View File

@ -1,45 +0,0 @@
<?php
if(!isset($_POST['submit'])) {
$cid = $_GET['cid'];
$sql = "select name,lot,producer,expdate, deptid from DICT_CONTROL where id='$cid'";
$stmt = sqlsrv_query( $conn1, $sql );
if( $stmt == false) { die( print_r( sqlsrv_errors(), true) ); }
$row = sqlsrv_fetch_array( $stmt, SQLSRV_FETCH_NUMERIC);
$cname = $row[0];
$lot = $row[1];
$producer = $row[2];
if($row[3] != '') { $expdate = date_format($row[3],"Y-m-d"); }
else { $expdate = ''; }
$deptid = $row[4];
?>
<form method='POST'>
dept.<br />
<select id='dept' name="deptid">
<option value='1' <?php if($deptid==1) {echo "selected"; }?>>Kimia</option>
<option value='2' <?php if($deptid==2) {echo "selected"; }?>>Imun</option>
</select><br />
control name<br/>
<input type='text' name='cname' value='<?php echo $cname;?>' /><br/>
lot<br/>
<input type='text' name='lot' value='<?php echo $lot;?>' /><br/>
producer<br/>
<input type='text' name='producer' value='<?php echo $producer;?>' /><br/>
expdate<br/>
<input type='date' name='expdate' value='<?php echo $expdate;?>'/><br/>
<input type='submit' name='submit' value='update' />
<input type='hidden' name='cid' value='<?php echo $cid;?>' /><br/>
</form>
<?php
} else {
$cid=$_POST['cid'];
$deptid = $_POST['deptid'];
$cname=stripslashes($_POST['cname']);
$lot=$_POST['lot'];
$producer=$_POST['producer'];
$expdate=$_POST['expdate'];
$sql="UPDATE DICT_CONTROL SET deptid='$deptid', name='$cname', lot='$lot', expdate='$expdate', producer='$producer' WHERE id='$cid'";
$stmt=sqlsrv_query( $conn1, $sql );
if($stmt==false) { die( print_r( sqlsrv_errors(), true) ); }
echo "<meta http-equiv='refresh' content='0;url=?p=dict' />";
}
?>

View File

@ -1,26 +0,0 @@
<h2>Control</h2>
<a href='?p=control&d=add'>New Control</a>
<table border='1'>
<tr> <th>ID</th> <th>Name</th> <th>Lot#</th> <th>Producer</th> <th>EXPdate</th> </tr>
<?php
$sql = "select id,name,lot,producer,expdate from DICT_CONTROL order by expdate DESC";
$stmt = sqlsrv_query( $conn1, $sql );
if( $stmt == false) { die( print_r( sqlsrv_errors(), true) ); }
$i=1;
while( $row = sqlsrv_fetch_array( $stmt, SQLSRV_FETCH_NUMERIC) ) {
$cid = $row[0];
$cname = $row[1];
$lot = $row[2];
$producer = $row[3];
if($row[4] != '') {
$expdate = date_format($row[4],"d-m-Y");
} else { $expdate = ''; }
echo "<tr><td>$i</td> <td>$cname</td> <td>$lot</td> <td>$producer</td> <td>$expdate</td>
<td><a href='?p=control&d=lot_add'>add lot</a></td>
<td><a href='?p=ct&cid=$cid'>detail</a> - <a href='?p=control&d=edit&cid=$cid'>edit</a> -
<a href='?p=control&d=del&cid=$cid'>del</a>
</td></tr>";
$i++;
}
?>
</table>

View File

@ -1,27 +0,0 @@
<?php
$cid = $_GET['cid'];
$sql = "select name from DICT_CONTROL where id='$cid'";
$stmt = sqlsrv_query( $conn1, $sql );
if( $stmt == false) { die( print_r( sqlsrv_errors(), true) ); }
$row = sqlsrv_fetch_array( $stmt, SQLSRV_FETCH_NUMERIC);
$cname = $row[0];
if(!isset($_POST['submit'])) {
?>
<form method='POST'>
control : <?=$cname;?><br/>
lot<br/>
<input type='text' name='lot' /><br/>
expdate<br/>
<input type='date' name='expdate' /><br/>
<input type='submit' name='submit' value='add' />
</form>
<?php
} else {
$lot=$_POST['lot'];
$expdate=$_POST['expdate'];
$sql="INSERT INTO DICT_CONTROL( name,lot,expdate) values ('$cname','$lot','$expdate')";
$stmt = sqlsrv_query( $conn1, $sql );
if( $stmt == false) { die( print_r( sqlsrv_errors(), true) ); }
echo "<meta http-equiv='refresh' content='0;url=?p=dict' />";
}
?>

View File

@ -1,8 +0,0 @@
<?php
$ctid = $_GET['ctid'];
$cid = $_GET['cid'];
$sql="DELETE CONTROL_TEST WHERE id='$ctid'";
$stmt = sqlsrv_query( $conn1, $sql );
if( $stmt == false) { die( print_r( sqlsrv_errors(), true) ); }
echo "<meta http-equiv='refresh' content='0;url=?p=ct&cid=$cid' />";
?>

View File

@ -1,29 +0,0 @@
<?php
$cid = $_GET['cid'];
$ctid = $_GET['ctid'];
if(!isset($_POST['submit'])) {
$sql = "select dt.name, ct.mean, ct.sd from DICT_TEST dt, CONTROL_TEST ct
where ct.testid=dt.id and ct.id='$ctid'";
$stmt = sqlsrv_query( $conn1, $sql );
if( $stmt == false) { die( print_r( sqlsrv_errors(), true) ); }
$row = sqlsrv_fetch_array( $stmt, SQLSRV_FETCH_NUMERIC );
$tname = $row[0];
$mean = $row[1];
$sd = $row[2];
?>
<form method='POST'>
<h2><?php echo "$tname ";?></h2>
mean<br/><input type='text' name='mean' value='<?php echo $mean;?>' /><br/>
sd<br/><input type='text' name='sd' value='<?php echo $sd;?>' /><br/>
<input type='submit' name='submit' value='update' />
<?php
} else {
$ctid = $_GET['ctid'];
$mean = $_POST['mean'];
$sd = $_POST['sd'];
$sql = "UPDATE CONTROL_TEST SET mean='$mean', sd='$sd' WHERE id='$ctid'";
$stmt = sqlsrv_query( $conn1, $sql );
if( $stmt == false) { die( print_r( sqlsrv_errors(), true) ); }
echo "<META http-equiv='refresh' content='0;url=?p=ct&cid=$cid'>";
}
?>

View File

@ -1,64 +0,0 @@
<?php
$cid = $_GET['cid'];
$sql = "select name, lot, expdate from DICT_CONTROL where id='$cid'";
$stmt = sqlsrv_query( $conn1, $sql );
if( $stmt == false) { die( print_r( sqlsrv_errors(), true) ); }
$row = sqlsrv_fetch_array( $stmt, SQLSRV_FETCH_NUMERIC );
$cname = $row[0];
$lot = $row[1];
if($row[2]!='') { $expdate = date_format($row[2],"d-m-Y"); }
else { $expdate = ''; }
if(isset($_POST['add'])) {
if(isset($_POST['test'])) {
$testid = $_POST['test'];
$mean = $_POST['mean'];
$sd = $_POST['sd'];
$sql = "INSERT INTO CONTROL_TEST (testid,controlid,mean,sd) VALUES ('$testid','$cid','$mean','$sd')";
$stmt = sqlsrv_query( $conn1, $sql );
if( $stmt == false) { die( print_r( sqlsrv_errors(), true) ); }
}
}
?>
Control <?php echo $cname; ?><br/>
Lot <?php echo $lot; ?><br/>
Exp date <?php echo $expdate; ?><br/>
<table border='1'>
<tr><th>no</th><th>test</th><th>mean</th><th>sd</th></tr>
<tr>
<form method='POST'>
<td></td>
<td><select name='test'>
<?php
$sql = "select id, name from DICT_TEST
where id not in (select testid from CONTROL_TEST where controlid='$cid')";
$stmt = sqlsrv_query( $conn1, $sql );
if( $stmt == false) { die( print_r( sqlsrv_errors(), true) ); }
while( $row = sqlsrv_fetch_array( $stmt, SQLSRV_FETCH_NUMERIC) ) {
$tid = $row[0];
$tname = $row[1];
echo "<option value='$tid'>$tname</option>";
}
?>
</select></td>
<td><input type='text' name='mean' /></td>
<td><input type='text' name='sd' /></td>
<td><input type='submit' value='+' name='add' /></td>
</form>
</tr>
<?php
$sql = "select ct.id, dt.name, ct.mean, ct.sd from DICT_TEST dt, CONTROL_TEST ct
where ct.testid=dt.id and ct.controlid='$cid'";
$stmt = sqlsrv_query( $conn1, $sql );
if( $stmt == false) { die( print_r( sqlsrv_errors(), true) ); }
$i=1;
while( $row = sqlsrv_fetch_array( $stmt, SQLSRV_FETCH_NUMERIC) ) {
$ctid = $row[0];
$tname = $row[1];
$tmean = $row[2];
$tsd = $row[3];
echo "<tr> <td>$i</td> <td>$tname</td> <td>$tmean</td> <td>$tsd</td>
<td> <a href='?p=ct&d=edit&ctid=$ctid&cid=$cid'>edit</a> - <a href='?p=ct&d=del&ctid=$ctid&cid=$cid'>delete</a> </td> </tr>";
$i++;
}
?>
</table>

View File

@ -1,99 +0,0 @@
<?php
$date=date("Y-m");
/*
$sql = "select id, name, lot from DICT_CONTROL order by expdate desc";
$stmt = sqlsrv_query( $conn1, $sql );
if( $stmt == false) { die( print_r( sqlsrv_errors(), true) ); }
*/
?>
<form method='GET' target='_blank'>
<input type='hidden' name='p' value='entry_monthly'>
<table>
<tr> <td>Dept.</td> <td>:</td>
<td>
<select id='dept'>
<option value='1'>Kimia</option>
<option value='2'>Imun</option>
</select>
</td>
</tr>
<tr> <td>Date</td> <td>:</td> <td><input type='date' id='dates' name='dates' onInput='getControl(this.value);' /></td> </tr>
<tr> <td>Control</td> <td>:</td> <td><select id='control' name='control' onchange='getTest(this.value);'><option> -- Select Control -- </option></select></td> </tr>
<tr> <td>Test</td> <td>:</td> <td><select id='test' name='test'><option> -- Select Test -- </option></select></td> </tr>
<tr> <td><input type='submit' value='go'></td> </tr>
</table>
</form>
<script src="assets/jquery.js"></script>
<script type="text/javascript">
function getTest(){
// Empty the dropdown
var testel = document.getElementById('test');
test.innerHTML = "";
var testopt = document.createElement('option');
testopt.value = 0;
testopt.innerHTML = '-- Select Test --';
testel.appendChild(testopt);
var control = $("#control").val();
$.ajax({
type: "POST",
contentType: "application/json; charset=utf-8",
url: "inc/entry_ajax.php",
datatype : 'JSON',
data: JSON.stringify( {request:'getTest', control: control} ),
success: function(response){
var result = JSON.parse(response);
var len = result.length;
for(var i=0; i<len; i++){
var id = result[i].id;
var name = result[i].name;
//console.log(result[i]);
// Add option to state dropdown
var opt = document.createElement('option');
opt.value = id;
opt.innerHTML = name;
testel.appendChild(opt);
}
},
error:function(exception){alert('Exeption:'+exception);}
});
}
function getControl(){
// Empty the dropdown
var controlel = document.getElementById('control');
control.innerHTML = "";
var controlopt = document.createElement('option');
controlopt.value = 0;
controlopt.innerHTML = '-- Select Control --';
controlel.appendChild(controlopt);
var dates = $("#dates").val();
var dept = $("#dept").val();
$.ajax({
type: "POST",
contentType: "application/json; charset=utf-8",
url: "inc/entry_ajax.php",
datatype : 'JSON',
data: JSON.stringify( {request:'getControl', dates: dates, dept:dept} ),
success: function(response){
var result = JSON.parse(response);
var len = result.length;
for(var i=0; i<len; i++){
var id = result[i].id;
var name = result[i].name;
//console.log(result[i]);
// Add option to state dropdown
var opt = document.createElement('option');
opt.value = id;
opt.innerHTML = name;
controlel.appendChild(opt);
}
},
error:function(exception){alert('Exeption:'+exception);}
});
}
</script>

View File

@ -1,51 +0,0 @@
<?php
include '../config.php';
// Read POST data
$postData = json_decode(file_get_contents("php://input"));
$request = "";
if(isset($postData->request)){
$request = $postData->request;
}
// Get Test
if($request == 'getTest'){
$result = array();$data = array();
if(isset($postData->control)){
$control = $postData->control;
$sql = "select dt.id, dt.name from CONTROL_TEST ct left join DICT_TEST dt on dt.id=ct.testid where ct.controlid='$control'";
$stmt = sqlsrv_query( $conn1, $sql );
if( $stmt == false) { die( print_r( sqlsrv_errors(), true) ); }
while ( $row = sqlsrv_fetch_array( $stmt, SQLSRV_FETCH_NUMERIC ) ) {
$id = $row[0];
$name = $row[1];
$data[] = array( "id" => $id, "name" => $name );
}
}
echo json_encode($data);
die;
}
// Get Control
elseif($request == 'getControl'){
$result = array();$data = array();
if(isset($postData->dates)){
$dates = $postData->dates;
$dept = $postData->dept;
$sql = "select id, name, lot from DICT_CONTROL where expdate > '$dates' and deptID='$dept'";
$stmt = sqlsrv_query( $conn1, $sql );
if( $stmt == false) { die( print_r( sqlsrv_errors(), true) ); }
while ( $row = sqlsrv_fetch_array( $stmt, SQLSRV_FETCH_NUMERIC ) ) {
$id = $row[0];
$name = $row[1];
$lot = $row[2];
$data[] = array( "id" => $id, "name" => "$name - $lot" );
}
}
echo json_encode($data);
die;
}

View File

@ -1,74 +0,0 @@
<?php
if(isset($_POST['date'])) { $date = $_POST['date']; }
else { $date=date("Y-m-d"); }
$sql = "select id, name, lot from DICT_CONTROL where expdate > '$date' ";
$stmt = sqlsrv_query( $conn1, $sql );
if( $stmt == false) { die( print_r( sqlsrv_errors(), true) ); }
?>
<form method='POST' action='inc/entry_daily_1.php' target='_blank' />
<table>
<tr> <td>Dept.</td> <td>:</td>
<td>
<select id='dept'>
<option value='1'>Kimia</option>
<option value='2'>Imun</option>
</select>
</td>
</tr>
<tr> <td>Date</td> <td>:</td>
<td><input type='date' id='date' name='date' value='<?=$date;?>' onInput='getControl();' /></td>
</tr>
<tr> <td>Control</td> <td>:</td>
<td><select id='control' name='control'>
<option value=''> -- Select Control -- </option>
<?php
while ( $row = sqlsrv_fetch_array( $stmt, SQLSRV_FETCH_NUMERIC ) ) {
$id = $row[0];
$name = "$row[1] - $row[2]";
echo "<option value='$id'>$name</option>";
}?>
</select></td>
</tr>
<tr> <td><input type='submit' value='go'></td> </tr>
</table>
</form>
<script src="assets/jquery.js"></script>
<script type="text/javascript">
function getControl(){
// Empty the dropdown
var controlel = document.getElementById('control');
control.innerHTML = "";
var controlopt = document.createElement('option');
controlopt.value = 0;
controlopt.innerHTML = '-- Select Control --';
controlel.appendChild(controlopt);
var dates = $("#date").val();
var dept = $("#dept").val();
$.ajax({
type: "POST",
contentType: "application/json; charset=utf-8",
url: "inc/entry_ajax.php",
datatype : 'JSON',
data: JSON.stringify( {request:'getControl', dates: dates, dept:dept} ),
success: function(response){
var result = JSON.parse(response);
var len = result.length;
for(var i=0; i<len; i++){
var id = result[i].id;
var name = result[i].name;
var lot = result[i].lot;
console.log(result[i]);
// Add option to state dropdown
var opt = document.createElement('option');
opt.value = id;
opt.innerHTML = name+' ('+lot+')';
controlel.appendChild(opt);
}
},
error:function(exception){alert('Exeption:'+exception);}
});
}
</script>

View File

@ -1,85 +0,0 @@
<?php
include("../config.php");
if(!isset($_POST['submit'])) {
$controlid = $_POST['control'];
$date = $_POST['date'];
/*
$sql = "select dr.testid, dt.name, drx.resvalue, drx.resdate as latestVal, ct.mean, ct.sd
from CONTROL_TEST ct
left join DICT_TEST dt on ct.testid=dt.id
left join (
select controlid, testid, max(resdate) as resdate from DAILY_RESULT
group by controlid, testid
) as dr on dr.controlid=ct.controlid and dr.testid=ct.testid
left join DAILY_RESULT drx on drx.controlid=dr.controlid and drx.testid=dr.testid and drx.resdate=dr.resdate
where ct.controlid='$controlid'";
*/
$sql = "select ct.testid, dt.name, ct.mean, ct.sd, dr.resvalue
from CONTROL_TEST ct
left join DICT_TEST dt on ct.testid=dt.id
full join DAILY_RESULT dr on dr.controlid=ct.controlid and dr.testid=ct.testid
and dr.resdate between '$date 00:00' and '$date 23:59'
where ct.controlid='$controlid'";
$stmt = sqlsrv_query( $conn1, $sql );
if( $stmt == false) { die( print_r( sqlsrv_errors(), true) ); }
?>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<title>QC APP</title>
<link rel="stylesheet" href="../assets/styles.css?v=<?php echo time(); ?>">
<meta http-equiv="content-type" content="text/html; charset=iso-8859-1">
</head>
<body>
<div id='container'>
<div id='content'>
<h3><b>Tanggal</b> <?=$date;?></h3>
<table border='1'>
<form method='POST'>
<tr> <th>Test</th> <th>Value</th> <th>Mean (SD)</th>
<?php
while( $row = sqlsrv_fetch_array( $stmt, SQLSRV_FETCH_NUMERIC ) ) {
$tid = $row[0];
$tname = $row[1];
$tmean = $row[2];
$tsd = $row[3];
$tvalue = $row[4];
echo "<tr> <td>$tname</td>
<td><input type='text' name='value[$tid]' value='$tvalue' /></td>
<td>$tmean ($tsd)</td>
</tr>";
}
?>
<tr> <td colspan='3' style='text-align:right;'> <input type='submit' name='submit' value='save' /> </td></tr>
</table>
<br/>
<input type='hidden' name='controlid' value='<?=$controlid;?>' />
<input type='hidden' name='date' value='<?=$date;?>' />
</form>
</div>
</div>
</body>
</html>
<?php
} else {
$controlid = $_POST['controlid'];
$date = $_POST['date'];
$data = $_POST['value'];
foreach($data as $testid => $value) {
if($value != '') {
$sql = "if exists (select * from DAILY_RESULT where controlid='$controlid' and testid='$testid' and resdate='$date 00:00')
update DAILY_RESULT set resvalue='$value' where controlid='$controlid' and testid='$testid' and resdate='$date 00:00'
else
insert into DAILY_RESULT(controlid, testid, resvalue, resdate) values ('$controlid', '$testid', '$value', '$date 00:00')";
} else {
$sql = "if exists (select * from DAILY_RESULT where controlid='$controlid' and testid='$testid' and resdate='$date 00:00')
delete from DAILY_RESULT where controlid='$controlid' and testid='$testid' and resdate='$date 00:00'";
}
//echo "<pre>$sql</pre><br/>";
$stmt = sqlsrv_query( $conn1, $sql );
if( $stmt == false) { die( print_r( sqlsrv_errors(), true) ); }
}
echo "Data updated...";
}
?>

View File

@ -1,192 +0,0 @@
<?php
include("config.php");
if(!isset($_POST['submit'])) {
if(isset($_GET['control'])) { $control = $_GET['control']; }
else { $control = $_POST['control']; }
if(isset($_GET['dates'])) { $date = $_GET['dates']; }
else { $date = $_POST['dates']; }
$date = substr($date,0,7);
if(isset($_GET['test'])) { $testid = $_GET['test']; }
else { $testid = $_POST['test']; }
// control data
$sql = "select name, lot from DICT_CONTROL where id='$control'";
$stmt = sqlsrv_query( $conn1, $sql );
if( $stmt == false) { die( print_r( sqlsrv_errors(), true) ); }
$row = sqlsrv_fetch_array( $stmt, SQLSRV_FETCH_NUMERIC );
$cname = $row[0];
$lot = $row[1];
// test data
$sql = "select name from DICT_TEST where id='$testid'";
$stmt = sqlsrv_query( $conn1, $sql );
if( $stmt == false) { die( print_r( sqlsrv_errors(), true) ); }
$row = sqlsrv_fetch_array( $stmt, SQLSRV_FETCH_NUMERIC );
$tname = $row[0];
// test result
$sql = "select resdate, resvalue, rescomment from DAILY_RESULT where controlid='$control' and testid='$testid' and convert( varchar(7), resdate, 126) ='$date' ";
$stmt = sqlsrv_query( $conn1, $sql );
if( $stmt == false) { die( print_r( sqlsrv_errors(), true) ); }
while( $row = sqlsrv_fetch_array( $stmt, SQLSRV_FETCH_NUMERIC ) ) {
$day = date_format($row[0],'j');
$res = $row[1];
$rescom = $row[2];
$data [$day] = $res;
$comment [$day] = $rescom;
}
#mean sd
$sql = "select mean, sd from CONTROL_TEST where controlid='$control' and testid='$testid'";
$stmt = sqlsrv_query( $conn1, $sql );
if( $stmt == false) { die( print_r( sqlsrv_errors(), true) ); }
$row = sqlsrv_fetch_array( $stmt, SQLSRV_FETCH_NUMERIC );
$mean = $row[0];
$sd = $row[1];
$max = $mean + ($sd*2);
$min = $mean - ($sd*2);
#comtext
$sql = "select comtext from MONTHLY_COMMENT where controlid='$control' and testid='$testid' and commonth='$date'";
$stmt = sqlsrv_query( $conn1, $sql );
if( $stmt == false) { die( print_r( sqlsrv_errors(), true) ); }
$row = sqlsrv_fetch_array( $stmt, SQLSRV_FETCH_NUMERIC );
$comtext = '';
if(isset($row[0])) { $comtext = $row[0]; }
$hari = date("t",strtotime("$date-1"));
?>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<title>QC APP</title>
<link rel="stylesheet" href="../assets/styles.css?v=<?php echo time(); ?>">
<meta http-equiv="content-type" content="text/html; charset=iso-8859-1">
</head>
<body>
<div id='container'>
<div id='content'>
<b>Periode</b> <?=$date;?><br/>
<table border='1'>
<tr> <th colspan='2'><?php echo "$cname ($lot) - $tname";?></th> </tr>
<tr> <td>Mean</td> <td><?=$mean;?></td></tr>
<tr> <td>SD</td> <td><?=$sd;?></td></tr>
<tr> <td>Range</td> <td><?=$min;?> - <?=$max;?></td></tr>
</table>
<br/>
<form method='POST'>
<table border='1'>
<tr> <th>Day</th> <th>Result</th> <th>Comment</th> </tr>
<?php
for($i=1;$i<=$hari;$i++) {
echo "<tr> <td>$i</td>";
if(isset($data[$i])) { echo "<td><input type='text' name='data[$i]' value='$data[$i]' size='25' /></td>"; }
else { echo "<td><input type='text' name='data[$i]' size='25' /></td>"; }
if(isset($comment[$i])) { echo "<td><input type='text' name='comment[$i]' value='$comment[$i]' size='50' /></td>"; }
else { echo "<td><input type='text' name='comment[$i]' size='50' /></td>"; }
}
?>
<tr> <td colspan='2' valign='top'>Comment:</td> <td><textarea name='monthlycomment' cols='40'><?=$comtext;?></textarea> </td> </tr>
<tr> <td colspan='3' align='right'><input type='submit' name='submit' value='update' /></td> </tr>
</table>
<input type='hidden' name='control' value='<?=$control;?>' />
<input type='hidden' name='dates' value='<?=$date;?>' />
<input type='hidden' name='test' value='<?=$testid;?>' />
</form>
</div>
</div>
</body>
</html>
<?php
} else {
$control = $_POST['control'];
$date = $_POST['dates'];
$testid = $_POST['test'];
$data = $_POST['data'];
$comment = $_POST['comment'];
$i = 1;
foreach($data as $data1) {
if($data1>0) {$rdata[$i]=$data1;}
$i++;
}
$data = $rdata;
$hari = date("t",strtotime("$date-1"));
$sql = "SELECT id,resdate FROM DAILY_RESULT WHERE convert(varchar(7),resdate,126) ='$date' and controlid='$control' and testid='$testid'";
//echo $sql;
$stmt = sqlsrv_query( $conn1, $sql);
while( $row = sqlsrv_fetch_array( $stmt, SQLSRV_FETCH_NUMERIC ) ) {
$id = $row[0];
$cday = date_format($row[1],"j");
$resid[$cday] = $id;
}
///*
for($i=1;$i<=$hari;$i++) {
if( isset($data[$i]) ) {
if( isset($resid[$i]) ) { $dataUpdate[$resid[$i]] = $data[$i]; $commentUpdate[$resid[$i]]= $comment[$i]; } // data update
else { $dataInsert[$i] = $data[$i]; } // data insert
}
elseif( isset($resid[$i]) ) { $dataDelete[] = $resid[$i]; } // data delete
}
//INSERT
if(isset($dataInsert)) {
$sql = "INSERT INTO DAILY_RESULT (controlid, testid, resdate, resvalue, rescomment ) VALUES ";
foreach($dataInsert as $day => $data1) {
if($day < 10) { $day="0$day"; }
$sql.= "('$control', '$testid', '$date-$day', '$data1','$comment[$day]'),";
}
$sql = substr($sql,0,-1);
$stmt = sqlsrv_query( $conn1, $sql);
if( $stmt === false ) { die( print_r( sqlsrv_errors(), true)); }
echo "inserting new control result done... <br/>";
}
//UPDATE
if(isset($dataUpdate)) {
foreach($dataUpdate as $drid => $data1) {
$sql = "update DAILY_RESULT set resvalue='$data1', rescomment='$commentUpdate[$drid]' where id='$drid'";
$stmt = sqlsrv_query( $conn1, $sql);
if( $stmt === false ) { die( print_r( sqlsrv_errors(), true)); }
//echo "update $drid <br/>";
}
echo "updating control result done... <br/>";
}
//DELETE
if(isset($dataDelete)) {
foreach($dataDelete as $drid ) {
$sql = "delete from DAILY_RESULT where id='$drid'";
$stmt = sqlsrv_query( $conn1, $sql);
if( $stmt === false ) { die( print_r( sqlsrv_errors(), true)); }
//echo "delete $drid $sql<br/>";
}
echo "deleting control result done... <br/>";
}
$comtext = $_POST['monthlycomment'];
if( $comtext != '' ) {
$sql = "if exists (select * from MONTHLY_COMMENT with (updlock,serializable) where commonth='$date' and controlid='$control' and testid='$testid')
begin
update MONTHLY_COMMENT set comtext='$comtext'
where commonth='$date' and controlid='$control' and testid='$testid'
end
else
begin
insert into MONTHLY_COMMENT (controlid, testid, commonth, comtext )
values ('$control', '$testid', '$date', '$comtext')
end";
echo $sql;
$stmt = sqlsrv_query( $conn1, $sql);
if( $stmt === false ) { die( print_r( sqlsrv_errors(), true)); }
echo "updating comment done<br/>";
} else {
$sql = "delete from MONTHLY_COMMENT where commonth='$date' and controlid='$control' and testid='$testid'";
$stmt = sqlsrv_query( $conn1, $sql);
if( $stmt === false ) { die( print_r( sqlsrv_errors(), true)); }
echo "deleting comment done<br/>";
}
//echo "<br/>redirecting ....<META http-equiv='refresh' content='2;url=./index.php?p=entry_1&control=$control&dates=$date&test=$testid'>";
echo "<br/>redirecting ....<META http-equiv='refresh' content='2;url=./index.php?p=entry'>";
//*/
}
?>

View File

@ -1,326 +0,0 @@
<?php
$date = $_POST['qcdate'];
$testcode = $_POST['testcode'];
include("config.php");
#$date = "2016-06";
#$testcode = "BUN";
$year = substr($date,0,4);
$month = substr($date,5,2);
$monthName1 = date('F', mktime(0, 0, 0, $month, 10));
$monthName = substr($monthName1,0,3);
$days = cal_days_in_month(CAL_GREGORIAN, $month, $year);
$sql = "select distinct controlcode from DAILY_RUNS
where TESTCODE = '$testcode' AND convert( varchar(7), RESCTRLDATETIMEASP, 126) = '$date'";
$stmt = sqlsrv_query( $conn1, $sql );
if( $stmt == false) { die( print_r( sqlsrv_errors(), true) ); }
while( $row = sqlsrv_fetch_array( $stmt, SQLSRV_FETCH_NUMERIC) ) {
$qccode[] = $row[0];
}
foreach($qccode as $ccode) {
// getting mean,sd on each control
$sql = "select distinct REFASSAYVALUE, REFSTANDARDDEVIATION
from DAILY_RUNS
where TESTCODE = '$testcode' AND CONTROLCODE = '$ccode'
AND convert( varchar(7), RESCTRLDATETIMEASP, 126) = '$date'";
$stmt = sqlsrv_query( $conn1, $sql );
if( $stmt == false) { die( print_r( sqlsrv_errors(), true) ); }
$row = sqlsrv_fetch_array( $stmt, SQLSRV_FETCH_NUMERIC );
$mean[$ccode] = round($row[0],2);
$sd[$ccode] = round($row[1],2);
$min[$ccode] = $mean[$ccode] - (3*$sd[$ccode]);
$max[$ccode] = $mean[$ccode] + (3*$sd[$ccode]);
$total = 0;
$total2 = 0;
$sql = "select day(RESCTRLDATETIMEASP), RESULTCTRL
from daily_runs
where TESTCODE = '$testcode' AND CONTROLCODE = '$ccode'
AND convert( varchar(7), RESCTRLDATETIMEASP, 126) = '$date'";
#AND DISPOSITION NOT IN ('3','4') AND VAL_USERID is not NULL
$stmt = sqlsrv_query( $conn1, $sql );
if( $stmt == false) { die( print_r( sqlsrv_errors(), true) ); }
$i=0;
while( $row = sqlsrv_fetch_array( $stmt, SQLSRV_FETCH_NUMERIC) ) {
$d = $row[0];
$value = $row[1];
$val[$ccode][$d][] = $value;
$total += $value;
$total2 += $value*$value;
$i++;
}
$n[$ccode] = $i;
if($n[$ccode]>1) {
// meanMVA = total/n
$meanMVA[$ccode] = round($total/$n[$ccode],3);
// CV = sqrt ( ( E(x^2) - (total^2 / n) ) / n - 1 )
$cv[$ccode] = round(sqrt(($total2 - ($total*$total)/$n[$ccode]) / ($n[$ccode] - 1)),3);
// %CV = CV / (total/n)
$pcv[$ccode] = round( $cv[$ccode] * 100 / $n[$ccode] ,3);
} else {
// meanMVA = total/n
$meanMVA[$ccode] = 0;
// CV = sqrt ( ( E(x^2) - (total^2 / n) ) / n - 1 )
$cv[$ccode] = 0;
// %CV = CV / (total/n)
$pcv[$ccode] = 0;
}
}
?>
<script src="plotly-latest.min.js"></script>
<style>
#container {width:1100px; margin:0 auto;}
#header {text-align:center;}
#col1 {float:left;margin:20px 40px 0 0;width:300px}
#col2 {float:left;0 width:350px}
#col3 {float:right;margin : 25px 190px 0 0;width:350px}
table { border-collapse: collapse; }
th,td { padding-left: 15px; padding-right: 15px }
#graph_C7 { text-align:center; }
#html{font-size:20px;}
</style>
<div id='container'>
<div id='header'>
<b>Laporan QC - Lab RS Sumber Waras</b> <br/>
<b>TMS 24i PREMIUM </b> <br/>
<?php foreach ($qccode as $ccode) { echo "<div id='graph_$ccode' style='width:1000px;float:left;'></div> "; }?>
</div>
<div id='col1'>
<b>Periode : </b> <?php echo "$monthName1 $year";?> <br/>
<b>Test : </b> <?php echo "$testcode ";?> <br/>
<table border='1'>
<tr> <th>QC Code</th> <?php foreach($qccode as $ccode) echo "<th>$ccode</th>"; ?> </tr>
<tr> <td>Mean</td> <?php foreach($mean as $mean1) echo "<td>$mean1</td>"; ?> </tr>
<tr> <td>1SD</td> <?php foreach($sd as $sd1)echo "<td>$sd1</td>"; ?> </tr>
<tr> <td>Range</td> <?php foreach($qccode as $ccode) echo "<td>$min[$ccode] - $max[$ccode]</td>"; ?> </tr>
<tr> <td></td> </tr>
<tr> <td>Mean MVA</td> <?php foreach($qccode as $ccode) echo "<td>".$meanMVA[$ccode]."</td>"; ?> </tr>
<tr> <td>SD</td> <?php foreach($qccode as $ccode) echo "<td>".$cv[$ccode]."</td>"; ?> </tr>
<tr> <td>%CV</td> <?php foreach($qccode as $ccode) echo "<td>".$pcv[$ccode]."</td>"; ?> </tr>
</table>
</div>
<div id='col2'>
<table border='1'>
<tr> <th>No</th> <th>Datetime</th> <?php foreach($qccode as $ccode) echo "<th>$ccode</th>"; ?> </tr>
<?php
// getting value count per day
for($i=1;$i<=$days;$i++) {
foreach($qccode as $ccode) {
if(isset($val[$ccode][$i])) { $cnt[$i][$ccode] = count($val[$ccode][$i]); }
else { $cnt[$i][$ccode] = 1; }
}
$count[$i] = max($cnt[$i]);
}
//show the value on table
$k = 1;
for($i=1;$i<=16;$i++) { // 1-16
for($j=0;$j<$count[$i];$j++) { // value per day
echo "<tr> <td>$k</td> <td>$i - $monthName</td>";
foreach($qccode as $ccode){
if(isset($val[$ccode][$i][$j])) echo "<td>".$val[$ccode][$i][$j]."</td>";
else echo "<td></td>";
}
$k++;
echo "</tr>";
}
}
?>
</table>
</div>
<div id='col3'>
<table border='1'>
<?php
for($i=17;$i<=$days;$i++) { // 17-end
for($j=0;$j<$count[$i];$j++) { // value per day
echo "<tr> <td>$k</td> <td>$i - $monthName</td>";
foreach($qccode as $ccode){
if(isset($val[$ccode][$i][$j])) echo "<td>".$val[$ccode][$i][$j]."</td>";
else echo "<td></td>";
}
$k++;
echo "</tr>";
}
}
?>
</table>
</div>
</div>
<script>
<?php
foreach ($qccode as $ccode) {
echo "var val_$ccode = {
connectgaps: false,mode: 'lines+markers',
line: { color: 'rgb(0, 0, 0)', width: 1 },
x:[";
// x days
for($i=1;$i<=$days;$i++) { echo $i,","; }
echo "],
";
//y values
echo "y:[";
for($i=1;$i<=$days;$i++) {
if(isset($val[$ccode][$i])) { $count = count($val[$ccode][$i]); }
else { $count = 1; }
for($j=0;$j<$count;$j++) {
if(isset($val[$ccode][$i][$j])) {
if($j==($count-1)) {// separate multi value
echo $val[$ccode][$i][$j].",";
$val1[$ccode][$i] = $val[$ccode][$i][$j];
} else {
$mulx[$ccode][] = $i;
$muly[$ccode][] = $val[$ccode][$i][$j];
}
} else {
echo "null,";
}
}
}
echo "]};
";
//scatter for multi val on one y axis
if(isset($mulx[$ccode])) {
echo "var mul_$ccode = {
mode: 'markers', type: 'scatter',
line: { color: 'rgb(0, 0, 0)', width: 1 },
x:[";
foreach ($mulx[$ccode] as $mulx1) { echo $mulx1,","; }
echo "],
";
//y values
echo "y:[";
foreach ($muly[$ccode] as $muly1) { echo $muly1,","; }
echo "]};
";
}
//mean and sd
echo "var layout_$ccode = {
title: '$ccode',
showlegend : false,
shapes: [";
//mean
echo" {
type: 'line', x0: 0, x1: 32,
y0: $mean[$ccode], y1:$mean[$ccode],
line: { color: 'rgb(0, 0, 0)', width: 2 }
},";
//1sd
$p1sd = $mean[$ccode]+$sd[$ccode];
$p2sd = $mean[$ccode]+(2*$sd[$ccode]);
$p3sd = $mean[$ccode]+(3*$sd[$ccode]);
$p4sd = $mean[$ccode]+(4*$sd[$ccode]);
$m1sd = $mean[$ccode]-$sd[$ccode];
$m2sd = $mean[$ccode]-(2*$sd[$ccode]);
$m3sd = $mean[$ccode]-(3*$sd[$ccode]);
$m4sd = $mean[$ccode]-(4*$sd[$ccode]);
echo" {
type: 'line', x0: 0, x1: 32,
y0:$p1sd, y1:$p1sd,
line: { color: 'rgb(0,255,0)', width: 2 }
},{
type: 'line', x0: 0, x1: 32,
y0:$m1sd, y1:$m1sd,
line: { color: 'rgb(0,255,0)', width: 2 }
},";
//2sd
echo" {
type: 'line', x0: 0, x1: 32,
y0:$p2sd, y1:$p2sd,
line: { color: 'rgb(255, 255, 0)', width: 2 }
},{
type: 'line', x0: 0, x1: 32,
y0:$m2sd, y1:$m2sd,
line: { color: 'rgb(255, 255, 0)', width: 2 }
},";
//3sd
echo" {
type: 'line', x0: 0, x1: 32,
y0:$p3sd, y1:$p3sd,
line: { color: 'rgb(255, 0, 0)', width: 2 }
},{
type: 'line', x0: 0, x1: 32,
y0:$m3sd, y1:$m3sd,
line: { color: 'rgb(255, 0, 0)', width: 2 }
},";
//jumping value LOL
//2,5,9 - 3,5,6
//2,5 - 3,5 ; 5,9 - 5,6
/*
foreach($val1[$ccode] as $i => $value) {
}
//single jump
if(isset($nullx[$ccode])) {
foreach($nullx[$ccode] as $null) {
if($null != 1) {
$y0 = $val1[$ccode][$null-1];
$y1 = $val1[$ccode][$null+1];
echo "{
type: 'line', x0: $null-1, x1: $null+1,
y0:$y0, y1:$y1,
line: { color: 'rgb( 0, 0, 0)', dash:'dot' }
},";
}
}
}
*/
$n = 0;
$i = 1;
while( $i <= $days) {
if(!isset($val1[$ccode][$i])) {
if(isset($val1[$ccode][$i-1])){
$x0 = $i-1;
$y0 = $val1[$ccode][$i-1];
$n=1;
}
} else {
if($n==1) {
$x1 = $i;
$y1 = $val1[$ccode][$i];
echo "{
type: 'line', x0: $x0, x1: $x1,
y0:$y0, y1:$y1,
line: { color: 'rgb( 0, 0, 0)', dash:'dot' }
},";
$n=0;
}
}
$i++;
}
echo "]
};
";
echo "
var txt_$ccode = {
x: [ 33, 33, 33, 33, 33, 33, 33],
y: [ $p3sd, $p2sd, $p1sd, $mean[$ccode], $m1sd, $m2sd, $m3sd],
text: [ '+3SD', '+2SD', '+1SD', 'mean', '-1SD', '-2SD', '-3SD' ],
mode: 'text'
}
";
if(isset($mulx[$ccode])) {
echo "
var data_$ccode = [val_$ccode, txt_$ccode, mul_$ccode];
";
}else {
echo "
var data_$ccode = [val_$ccode, txt_$ccode];
";
}
//end of layout
echo "
Plotly.newPlot('graph_$ccode', data_$ccode, layout_$ccode);";
}
?>
</script>

View File

@ -1,74 +0,0 @@
<?php
function getData($control, $dates, $testid, $lastday) {
require('../config.php');
$cont = explode("|",$control);
$control = $cont[0];
$lot = $cont[1];
$sql = "select name, ct.mean, ct.sd from DICT_CONTROL dc left join CONTROL_TEST ct on dc.id=ct.controlid where dc.id='$control' and ct.testid='$testid'";
$stmt = sqlsrv_query( $conn1, $sql );
if( $stmt == false) { die( print_r( sqlsrv_errors(), true) ); }
$row = sqlsrv_fetch_array( $stmt, SQLSRV_FETCH_NUMERIC );
$data['name'] = $row[0];
$data['mean'] = $row[1];
$data['sd'] = $row[2];
$sql = "select dr.resdate, dr.resvalue, dr.rescomment from DAILY_RESULT dr
left join CONTROL_TEST ct on ct.testid=dr.testid and ct.controlid=dr.controlid
where dr.testid='$testid' and dr.controlid='$control' and convert(varchar(7), dr.resdate, 126)='$dates'
order by resdate asc";
//echo "$sql";
//echo "$testid - $control <br/>";
$data['control'] = $control;
$data['lot'] = $lot;
$stmt = sqlsrv_query( $conn1, $sql );
if( $stmt == false) { die( print_r( sqlsrv_errors(), true) ); }
while ( $row = sqlsrv_fetch_array( $stmt, SQLSRV_FETCH_NUMERIC ) ) {
$day = date_format($row[0],"j");
$data['result'] [$day] = $row[1];
if(is_numeric($row[1])) {
$data['resultx'] [$day] = ( $row[1] - $data['mean'] ) / $data['sd'];
} else { $data['resultx'] [$day] = 0; }
}
// mean and sd
$data['+2s'] = $data['mean'] + (2*$data['sd']);
$data['-2s'] = $data['mean'] - (2*$data['sd']);
$data['cv'] = number_format($data['sd'] / $data['mean'] * 100,3);
// sample mean and sd
$num_of_elements = count($data['result']);
$variance = 0.0;
$data['means'] = array_sum($data['result'])/$num_of_elements;
foreach($data['result'] as $i) {
if(is_numeric($i)) { $variance += pow(($i - $data['mean']), 2); }
}
$data['sds'] = (float)sqrt($variance/$num_of_elements);
$data['means'] = number_format($data['means'],3);
$data['sds'] = number_format($data['sds'],3);
if($data['means'] != 0) {
$data['cvs'] = number_format($data['sds'] / $data['means'] * 100,3);
}else { $data['cvs'] = 0; }
$data['bias'] = abs($data['mean']- $data['means']);
$data['bias'] = number_format($data['bias'] / $data['mean'] * 100,3);
$data['te'] = $data['bias'] + ( 2 * $data['cvs'] );
ksort($data['result']); ksort($data['resultx']);
return $data;
}
$control1 = $_POST['control1'];
if(isset($_POST['control2'])) { $control2 = $_POST['control2']; }
else { $control2 = 0; }
if(isset($_POST['control3'])) { $control3 = $_POST['control3']; }
else { $control3 = 0; }
$dates = $_POST['dates'];
$lastday = date("t", strtotime($dates));
$testid = $_POST['test'];
if( $control3 != 0) { require("report_3c.php"); }
else if( $control2 != 0) { require("report_2c.php"); }
else { require("report_1c.php"); }

View File

@ -1,145 +0,0 @@
<!DOCTYPE html>
<html>
<?php
require("../config.php");
$dates = $_POST['dates'];
$lastday = date("t", strtotime($dates));
$testid = $_POST['test'];
$sql = "select cva, ba, tea, name, method, unit from DICT_TEST where id='$testid'";
$stmt = sqlsrv_query( $conn1, $sql );
if( $stmt == false) { die( print_r( sqlsrv_errors(), true) ); }
$row = sqlsrv_fetch_array( $stmt, SQLSRV_FETCH_NUMERIC );
$cva = $row[0];
$ba = $row[1];
$tea = $row[2];
$testname = $row[3];
$method = $row[4];
$unit = $row[5];
$data = getData($control1, $dates, $testid, $lastday);
$length = count($data['result']);
?>
<head>
<meta http-equiv='Content-Type' content='text/html; charset=utf-8' />
<link rel='stylesheet' href='../assets/normalize.min.css' />
<link rel='stylesheet' href='../assets/style_report.css' />
<script src="../assets/Chart.min.js"></script>
<style>
#title { text-align:center; }
#title, #info, #result, #footer, #resultdata { margin-bottom:0.5cm; }
#footer {width : 100%;}
</style>
</head>
<body style='-webkit-print-color-adjust: exact;'>
<div id='page'>
<div id='title'>
<h3><b>QC Internal</b></h3>
<h3>Kimia Klinik</h3>
<h5>TRISENSA DIAGNOSTIC CENTRE</h5>
</div>
<div id='info'>
<table>
<tr>
<th>INSTITUTION</th> <td>Laboratorium Gleneagles</td>
<th>Instrument</th> <td colspan='3'>TMS 50i</td>
</tr>
<tr>
<th>TEST NAME</th> <td><?=$testname;?></td>
<th>Control Name</th> <td colspan='3'><?=$data['name'];?> </td>
</tr>
<tr>
<th>REAGENT</th> <td> </td>
<th>Lot No.</th> <td colspan='3'><?=$data['lot'];?> </td>
</tr>
<tr>
<th>METHOD</th> <td><?=$method;?></td>
<th>VALUES</th> <td>-2S</td> <td>Target</td> <td>+2S</td>
</tr>
<tr>
<th>UNIT</th> <td><?=$unit;?></td>
<td></td> <td style='text-align:center;'><?=$data['-2s'];?></td> <td style='text-align:center;'><?=$data['mean'];?></td> <td style='text-align:center;'><?=$data['+2s'];?></td>
</tr>
<tr>
<th>PERIODE</th> <td colspan='5'><?=$dates;?></td>
</tr>
</table>
</div>
<div id='resultdata'>
<table>
<tr>
<th>Tanggal</th> <th>Hasil</th>
</tr>
<?php
$i = 1;
foreach($data['result'] as $day => $res) {
if( is_numeric($res) ) { $res = number_format($res,3); }
echo "<tr> <td style='text-align:center;'>$day</td> <td>$res</td> </tr>";
}
?>
</table>
</div>
<div id='resultimg'>
<canvas id="myChart"></canvas>
</div>
<div id='footer'>
<table>
<tr> <th colspan='4'>QC PERFORMANCE</th> </tr>
<tr>
<td>MEAN</td> <td><?=$data['means'];?></td>
<td>SD</td> <td><?=$data['sds'];?></td>
</tr>
<tr>
<td>CV %</td> <td><?=$data['cv'];?></td>
<td>CV <sub>A</sub></td> <td><?=$cva;?></td>
</tr>
<tr>
<td>BIAS %</td> <td><?=$data['bias'];?></td>
<td>B<sub>A</sub> %</td> <td><?=$ba;?></td>
</tr>
<tr>
<td>TE %</td> <td><?=$data['te'];?></td>
<td>TE<sub>A</sub></td> <td><?=$tea;?></td>
</tr>
</table>
</div>
</div>
</body>
<script>
const ctx = document.getElementById('myChart');
ctx.height = 700;
const labels = [ <?php for ($i=1; $i<=$length; $i ++) { echo "$i,"; } ?> ];
const data = {
labels: labels,
datasets: [{
axis: 'y',
label: 'Hasil QC',
data: [
<?php
foreach($data['resultx'] as $res) {
if($res != null) { $res = number_format($res,3); }
echo "$res, ";
}
?>
],
fill: false,
backgroundColor: 'rgba(255, 99, 132, 0.2)',
borderColor: 'rgb(255, 99, 132)',
borderWidth: 1
}]
};
const config = {
type: 'line',
data: data,
options: {
indexAxis: 'y',
scales: {
xAxis : { min:-4, max:4 }
},
},
};
const myChart = new Chart(ctx, config);
</script>
</html>

View File

@ -1,190 +0,0 @@
<!DOCTYPE html>
<html>
<?php
require("../config.php");
$dates = $_POST['dates'];
$lastday = date("t", strtotime($dates));
$testid = $_POST['test'];
$sql = "select cva, ba, tea, name, method, unit from DICT_TEST where id='$testid'";
$stmt = sqlsrv_query( $conn1, $sql );
if( $stmt == false) { die( print_r( sqlsrv_errors(), true) ); }
$row = sqlsrv_fetch_array( $stmt, SQLSRV_FETCH_NUMERIC );
$cva = $row[0];
$ba = $row[1];
$tea = $row[2];
$testname = $row[3];
$method = $row[4];
$unit = $row[5];
## Control 1
$data1 = getData($control1, $dates, $testid, $lastday);
$data2 = getData($control2, $dates, $testid, $lastday);
$length = count($data1['result']);
$length_2 = count($data2['result']);
if($length_2 > $length) { $length = $length_2; }
// repair jumping control
$key1 = array_keys($data1['resultx']);
$key2 = array_keys($data2['resultx']);
$keys = array_merge($key1,$key2);
$keys = array_unique($keys);
asort($keys);
/*
echo "<pre>";
print_r($data1['resultx']);
print_r($data2['resultx']);
print_r($keys);
echo "</pre>";
*/
?>
<head>
<meta http-equiv='Content-Type' content='text/html; charset=utf-8' />
<link rel='stylesheet' href='../assets/normalize.min.css' />
<link rel='stylesheet' href='../assets/style_report.css' />
<script src="../assets/Chart.min.js"></script>
<style>
#title { text-align:center; }
#title, #info, #result, #footer, #resultdata { margin-bottom:0.5cm; }
#footer {width : 100%;}
</style>
</head>
<body style='-webkit-print-color-adjust: exact;'>
<div id='page'>
<div id='title'>
<h3><b>QC Internal</b></h3>
<h3>Kimia Klinik</h3>
<h5>TRISENSA DIAGNOSTIC CENTRE</h5>
</div>
<div id='info'>
<table>
<tr>
<th>INSTITUTION</th> <td>Laboratorium Gleneagles</td>
<th>Instrument</th> <td colspan='3'>TMS 50i</td>
</tr>
<tr>
<th>TEST NAME</th> <td><?=$testname;?></td>
<th>Control</th> <td colspan='3'>C 1 :<?=$data1['name'];?> / C 2 : <?=$data2['name'];?> </td>
</tr>
<tr>
<th>REAGENT</th> <td> </td>
<th>Lot No.</th> <td colspan='3'>Lot 1 : <?=$data1['lot'];?> / Lot 2 : <?=$data2['lot'];?> </td>
</tr>
<tr>
<th>METHOD</th> <td><?=$method;?></td>
<th>VALUES</th> <td>-2S</td> <td>TARGET</td> <td>+2S</td>
</tr>
<tr>
<th>UNIT</th> <td><?=$unit;?></td>
<th>VALUES</th>
<td style='text-align:center;'><?=$data1['-2s'];?></td> <td style='text-align:center;'><?=$data1['mean'];?></td> <td style='text-align:center;'><?=$data1['+2s'];?></td>
</tr>
<tr>
<td></td> <td></td>
<th></th>
<td style='text-align:center;'><?=$data2['-2s'];?></td> <td style='text-align:center;'><?=$data2['mean'];?></td> <td style='text-align:center;'><?=$data2['+2s'];?></td>
</tr>
<tr>
<th>PERIODE</th> <td colspan='5'><?=$dates;?></td>
</tr>
</table>
</div>
<div id='resultdata'>
<table>
<tr>
<th>Tanggal</th> <th>Hasil 1</th> <th>Hasil 2</th>
</tr>
<?php
for($i = 1 ; $i<=$lastday; $i++) {
$res1 = '';
$res2 = '';
if( isset($data1['result'][$i]) ) {
if( is_numeric($data1['result'][$i]) ) { $res1 = number_format($data1['result'][$i],3); }
}
if( isset($data2['result'][$i]) ) {
if( is_numeric($data2['result'][$i]) ) { $res2 = number_format($data2['result'][$i],3); }
}
if( $res1 != '' || $res2 !='' ) { echo "<tr> <td style='text-align:center'>$i</td> <td>$res1</td> <td>$res2</td> </tr>"; }
}
?>
</table>
</div>
<div id='resultimg'>
<canvas id="myChart"></canvas>
</div>
<div id='footer'>
<table>
<tr> <th colspan='4'>QC 1 PERFORMANCE</th> <th colspan='4'>QC 2 PERFORMANCE</th> </tr>
<tr>
<td>MEAN</td> <td><?=$data1['means'];?></td> <td>SD</td> <td><?=$data1['sds'];?></td>
<td>MEAN</td> <td><?=$data2['means'];?></td> <td>SD</td> <td><?=$data2['sds'];?></td>
</tr>
<tr>
<td>CV %</td> <td><?=$data1['cv'];?></td> <td>CV <sub>A</sub></td> <td><?=$cva;?></td>
<td>CV %</td> <td><?=$data2['cv'];?></td> <td>CV <sub>A</sub></td> <td><?=$cva;?></td>
</tr>
<tr>
<td>BIAS %</td> <td><?=$data1['bias'];?></td> <td>B<sub>A</sub> %</td> <td><?=$ba;?></td>
<td>BIAS %</td> <td><?=$data2['bias'];?></td> <td>B<sub>A</sub> %</td> <td><?=$ba;?></td>
</tr>
<tr>
<td>TE %</td> <td><?=$data1['te'];?></td> <td>TE<sub>A</sub></td> <td><?=$tea;?></td>
<td>TE %</td> <td><?=$data2['te'];?></td> <td>TE<sub>A</sub></td> <td><?=$tea;?></td>
</tr>
</table>
</div>
</div>
</body>
<script>
const ctx = document.getElementById('myChart');
ctx.height = 700;
const labels = [ <?php for ($i=1; $i<=$length; $i ++) { echo "$i,"; } ?> ];
const data = {
labels: labels,
datasets: [{
axis: 'y', label: 'QC1', fill: false, backgroundColor: 'rgba(0, 0, 255, 0.5)', borderColor: 'rgb(0, 0, 255)', borderWidth: 1,
data: [
<?php
$res1 = '';
//foreach($data1['resultx'] as $res) {
foreach($keys as $key) {
if(isset($data1['resultx'][$key])) {
$res = $data1['resultx'][$key];
$res1 .= number_format($res,3).',';
} else { $res1 .= 'null,'; }
}
$res1 = rtrim($res1,',');
echo "$res1";
?>
]}, {
axis: 'y', label: 'QC2', fill: false, backgroundColor: 'rgba(0, 0, 0, 0.5)', borderColor: 'rgb(0, 0, 0)', borderWidth: 1,
data: [
<?php
$res1 = '';
//foreach($data2['resultx'] as $res) {
foreach($keys as $key) {
if(isset($data2['resultx'][$key])) {
$res = $data2['resultx'][$key];
$res1 .= number_format($res,3).',';
} else { $res1 .= 'null,'; }
}
$res1 = rtrim($res1,',');
echo "$res1";
?>
]}
]
};
const config = {
type: 'line',
data: data,
options: {
indexAxis: 'y',
scales: {
xAxis : { min:-4, max:4 }
},
},
};
const myChart = new Chart(ctx, config);
</script>
</html>

View File

@ -1,204 +0,0 @@
<!DOCTYPE html>
<html>
<?php
require("../config.php");
$dates = $_POST['dates'];
$lastday = date("t", strtotime($dates));
$testid = $_POST['test'];
$sql = "select cva, ba, tea, name, method, unit from DICT_TEST where id='$testid'";
$stmt = sqlsrv_query( $conn1, $sql );
if( $stmt == false) { die( print_r( sqlsrv_errors(), true) ); }
$row = sqlsrv_fetch_array( $stmt, SQLSRV_FETCH_NUMERIC );
$cva = $row[0];
$ba = $row[1];
$tea = $row[2];
$testname = $row[3];
$method = $row[4];
$unit = $row[5];
## Control 1
$data1 = getData($control1, $dates, $testid, $lastday);
$data2 = getData($control2, $dates, $testid, $lastday);
$data3 = getData($control3, $dates, $testid, $lastday);
$length = count($data1['result']);
$length_2 = count($data2['result']);
$length_3 = count($data3['result']);
if($length_2 > $length) { $length = $length_2; }
if($length_3 > $length) { $length = $length_3; }
// repair jumping control
$key1 = array_keys($data1['resultx']);
$key2 = array_keys($data2['resultx']);
$key3 = array_keys($data3['resultx']);
$keys = array_merge($key1,$key2,$key3);
$keys = array_unique($keys);
asort($keys);
/*
echo "<pre>";
print_r($data1['resultx']);
print_r($data2['resultx']);
print_r($keys);
echo "</pre>";
*/
?>
<head>
<meta http-equiv='Content-Type' content='text/html; charset=utf-8' />
<link rel='stylesheet' href='../assets/normalize.min.css' />
<link rel='stylesheet' href='../assets/style_report.css' />
<script src="../assets/Chart.min.js"></script>
<style>
#title { text-align:center; }
#title, #info, #result, #footer, #resultdata { margin-bottom:0.5cm; }
#footer {width : 100%;}
</style>
</head>
<body style='-webkit-print-color-adjust: exact;'>
<div id='page'>
<div id='title'>
<h3><b>QC Internal</b></h3>
<h3>Kimia Klinik</h3>
<h5>TRISENSA DIAGNOSTIC CENTRE</h5>
</div>
<div id='info'>
<table>
<tr>
<th>INSTITUTION</th> <td>Laboratorium Gleneagles</td>
<th>Instrument</th> <td colspan='3'>TMS 50i</td>
</tr>
<tr>
<th>TEST NAME</th> <td><?=$testname;?></td>
<th>Control</th> <td colspan='3'>C 1 :<?=$data1['name'];?> / C 2 : <?=$data2['name'];?> / C 3 : <?=$data3['name'];?> </td>
</tr>
<tr>
<th>REAGENT</th> <td> </td>
<th>Lot No.</th> <td colspan='3'>Lot 1 : <?=$data1['lot'];?> / Lot 2 : <?=$data2['lot'];?> / Lot 3 : <?=$data3['lot'];?></td>
</tr>
<tr>
<th>METHOD</th> <td><?=$method;?></td>
<th>VALUES</th> <td>-2S</td> <td>TARGET</td> <td>+2S</td>
</tr>
<tr>
<th>UNIT</th> <td><?=$unit;?></td>
<th>VALUES</th>
<td style='text-align:center;'><?=$data1['-2s'];?></td> <td style='text-align:center;'><?=$data1['mean'];?></td> <td style='text-align:center;'><?=$data1['+2s'];?></td>
</tr>
<tr>
<td></td> <td></td>
<th></th>
<td style='text-align:center;'><?=$data2['-2s'];?></td> <td style='text-align:center;'><?=$data2['mean'];?></td> <td style='text-align:center;'><?=$data2['+2s'];?></td>
</tr>
<tr>
<td></td> <td></td>
<th></th>
<td style='text-align:center;'><?=$data3['-2s'];?></td> <td style='text-align:center;'><?=$data3['mean'];?></td> <td style='text-align:center;'><?=$data3['+2s'];?></td>
</tr>
<tr>
<th>PERIODE</th> <td colspan='5'><?=$dates;?></td>
</tr>
</table>
</div>
<div id='resultdata'>
<table>
<tr>
<th>Tanggal</th> <th>Hasil 1</th> <th>Hasil 2</th> <th>Hasil 3</th>
</tr>
<?php
for($i = 1 ; $i<=$lastday; $i++) {
if( isset($data1['result'][$i]) ) {
if( is_numeric($data1['result'][$i]) ) { $res1 = number_format($data1['result'][$i],3); }
} else { $res1 = ''; }
if( isset($data2['result'][$i]) ) {
if( is_numeric($data2['result'][$i]) ) { $res2 = number_format($data2['result'][$i],3); }
} else { $res2 = ''; }
if( isset($data3['result'][$i]) ) {
if( is_numeric($data3['result'][$i]) ) { $res3 = number_format($data3['result'][$i],3); }
} else { $res3 = ''; }
if( $res1 != '' || $res2 !='' || $res3!='' ) { echo "<tr> <td style='text-align:center'>$i</td> <td>$res1</td> <td>$res2</td> <td>$res3</td> </tr>"; }
}
?>
</table>
</div>
<div id='resultimg'>
<canvas id="myChart"></canvas>
</div>
<div id='footer'>
<table>
<tr> <th colspan='4'>QC 1 PERFORMANCE</th> <th colspan='4'>QC 2 PERFORMANCE</th> </tr>
<tr>
<td>MEAN</td> <td><?=$data1['means'];?></td> <td>SD</td> <td><?=$data1['sds'];?></td>
<td>MEAN</td> <td><?=$data2['means'];?></td> <td>SD</td> <td><?=$data2['sds'];?></td>
<td>MEAN</td> <td><?=$data3['means'];?></td> <td>SD</td> <td><?=$data3['sds'];?></td>
</tr>
<tr>
<td>CV %</td> <td><?=$data1['cv'];?></td> <td>CV <sub>A</sub></td> <td><?=$cva;?></td>
<td>CV %</td> <td><?=$data2['cv'];?></td> <td>CV <sub>A</sub></td> <td><?=$cva;?></td>
<td>CV %</td> <td><?=$data3['cv'];?></td> <td>CV <sub>A</sub></td> <td><?=$cva;?></td>
</tr>
<tr>
<td>BIAS %</td> <td><?=$data1['bias'];?></td> <td>B<sub>A</sub> %</td> <td><?=$ba;?></td>
<td>BIAS %</td> <td><?=$data2['bias'];?></td> <td>B<sub>A</sub> %</td> <td><?=$ba;?></td>
<td>BIAS %</td> <td><?=$data3['bias'];?></td> <td>B<sub>A</sub> %</td> <td><?=$ba;?></td>
</tr>
<tr>
<td>TE %</td> <td><?=$data1['te'];?></td> <td>TE<sub>A</sub></td> <td><?=$tea;?></td>
<td>TE %</td> <td><?=$data2['te'];?></td> <td>TE<sub>A</sub></td> <td><?=$tea;?></td>
<td>TE %</td> <td><?=$data3['te'];?></td> <td>TE<sub>A</sub></td> <td><?=$tea;?></td>
</tr>
</table>
</div>
</div>
</body>
<script>
const ctx = document.getElementById('myChart');
ctx.height = 700;
const labels = [ <?php for ($i=1; $i<=$length; $i ++) { echo "$i,"; } ?> ];
const data = {
labels: labels,
datasets: [{
axis: 'y', label: 'QC1', fill: false, backgroundColor: 'rgba(0, 0, 255, 0.5)', borderColor: 'rgb(0, 0, 255)', borderWidth: 1,
data: [
<?php
$res1 = '';
//foreach($data1['resultx'] as $res) {
foreach($keys as $key) {
if(isset($data1['resultx'][$key])) {
$res = $data1['resultx'][$key];
$res1 .= number_format($res,3).',';
} else { $res1 .= 'null,'; }
}
$res1 = rtrim($res1,',');
echo "$res1";
?>
]}, {
axis: 'y', label: 'QC2', fill: false, backgroundColor: 'rgba(0, 0, 0, 0.5)', borderColor: 'rgb(0, 0, 0)', borderWidth: 1,
data: [
<?php
$res1 = '';
//foreach($data2['resultx'] as $res) {
foreach($keys as $key) {
if(isset($data2['resultx'][$key])) {
$res = $data2['resultx'][$key];
$res1 .= number_format($res,3).',';
} else { $res1 .= 'null,'; }
}
$res1 = rtrim($res1,',');
echo "$res1";
?>
]}
]
};
const config = {
type: 'line',
data: data,
options: {
indexAxis: 'y',
scales: {
xAxis : { min:-4, max:4 }
},
},
};
const myChart = new Chart(ctx, config);
</script>
</html>

View File

@ -1,32 +0,0 @@
<?php
if(!isset($_POST['submit'])) {
?>
<form method='POST'>
Test Name<br/>
<input type='text' name='testname' /><br/>
Unit<br/>
<input type='text' name='unit' /><br/>
Method<br/>
<input type='text' name='method' /><br/>
CVA<br/>
<input type='text' name='cva' /><br/>
BA<br/>
<input type='text' name='ba' /><br/>
TEA<br/>
<input type='text' name='tea' /><br/>
<input type='submit' name='submit' value='add' />
</form>
<?php
} else {
$testname=stripslashes($_POST['testname']);
$method = $_POST['method'];
$unit = $_POST['unit'];
$cva = $_POST['cva'];
$ba = $_POST['ba'];
$tea = $_POST['tea'];
$sql="INSERT INTO DICT_TEST(name, method, unit, cva, ba, tea) values ('$testname', '$method', '$unit', '$cva', '$ba', '$tea')";
$stmt = sqlsrv_query( $conn1, $sql );
if( $stmt == false) { die( print_r( sqlsrv_errors(), true) ); }
echo "<meta http-equiv='refresh' content='0;url=?p=dict' />";
}
?>

View File

@ -1,7 +0,0 @@
<?php
$tid = $_GET['tid'];
$sql="DELETE DICT_TEST WHERE id='$tid'";
$stmt = sqlsrv_query( $conn1, $sql );
if( $stmt == false) { die( print_r( sqlsrv_errors(), true) ); }
echo "<meta http-equiv='refresh' content='0;url=?p=dict' />";
?>

View File

@ -1,72 +0,0 @@
<?php
if(!isset($_POST['submit'])) {
$tid = $_GET['tid'];
$tname = '';
$cva = '';
$ba = '';
$tea = '';
$unit = '';
$method = '';
$deptid = '';
if($tid != 0) {
$sql = "select name, cva, ba, tea, unit, method, deptid from DICT_TEST where id='$tid'";
$stmt = sqlsrv_query( $conn1, $sql );
if( $stmt == false) { die( print_r( sqlsrv_errors(), true) ); }
$row = sqlsrv_fetch_array( $stmt, SQLSRV_FETCH_NUMERIC);
$tname = $row[0];
$cva = $row[1];
$ba = $row[2];
$tea = $row[3];
$unit = $row[4];
$method = $row[5];
$deptid = $row[6];
}
$sql = "select id, name from DICT_DEPT";
$stmt = sqlsrv_query( $conn1, $sql );
while($row=sqlsrv_fetch_array( $stmt, SQLSRV_FETCH_NUMERIC)) {
$depts[$row[0]] = $row[1];
}
?>
<form method='POST'>
Dept.<br/>
<select name='deptid'>
<option value=''></option>
<?php
foreach($depts as $qdeptid => $qdeptname) {
echo "<option value='$qdeptid'>$qdeptname</option>";
}
?>
</select>
<br/>
Name<br/>
<input type='text' name='tname' value='<?php echo $tname;?>' /><br/>
Unit<br/>
<input type='text' name='unit' value='<?php echo $unit;?>' /><br/>
Method<br/>
<input type='text' name='method' value='<?php echo $method;?>' /><br/>
CVA<br/>
<input type='text' name='cva' value='<?php echo $cva;?>' /><br/>
BA<br/>
<input type='text' name='ba' value='<?php echo $ba;?>' /><br/>
TEA<br/>
<input type='text' name='tea' value='<?php echo $tea;?>' /><br/>
<input type='hidden' name='tid' value='<?php echo $tid;?>' /><br/>
<input type='submit' name='submit' value='update' />
</form>
<?php
} else {
$tid=$_POST['tid'];
$tname=stripslashes($_POST['tname']);
$deptid = $_POST['deptid'];
$method = $_POST['method'];
$unit = $_POST['unit'];
$cva = $_POST['cva'];
$ba = $_POST['ba'];
$tea = $_POST['tea'];
if($tid != 0) { $sql="UPDATE DICT_TEST SET name='$tname', cva='$cva', ba='$ba', tea='$tea', method='$method', unit='$unit', deptid='$deptid' WHERE id='$tid'"; }
else { $sql="INSERT INTO DICT_TEST(name, method, unit, cva, ba, tea, deptid) values ('$testname', '$method', '$unit', '$cva', '$ba', '$tea', '$deptid')"; }
$stmt=sqlsrv_query( $conn1, $sql );
if($stmt==false) { die( print_r( sqlsrv_errors(), true) ); }
echo "<meta http-equiv='refresh' content='0;url=?p=test' />";
}
?>

View File

@ -1,28 +0,0 @@
<h2>Test</h2>
<a href='?p=test&d=edit&tid=0'>New Test</a> <br/>
<table border='1'>
<tr> <th>ID</th> <th>Dept.</th> <th>Name</th> <th>Method</th> <th>Unit</th> <th>BA</th> <th>CVA</th> <th>TEA</th> </tr>
<?php
$sql = "select t.id, t.name,ba,cva,tea,method,unit, d.id, d.name from DICT_TEST t
left join DICT_DEPT d on d.id=t.deptid";
$stmt = sqlsrv_query( $conn1, $sql );
if( $stmt == false) { die( print_r( sqlsrv_errors(), true) ); }
$i=1;
while( $row = sqlsrv_fetch_array( $stmt, SQLSRV_FETCH_NUMERIC) ) {
$tid = $row[0];
$tname = $row[1];
$ba = $row[2];
$cva = $row[3];
$tea = $row[4];
$method = $row[5];
$unit = $row[6];
$did = $row[7];
$dname = $row[8];
echo "<tr><td>$i</td> <td>$dname</td> <td>$tname</td> <td>$method</td> <td>$unit</td> <td>$ba</td> <td>$cva</td> <td>$tea</td>
<td><a href='?p=test&d=edit&tid=$tid'>edit</a> -
<a href='?p=test&d=del&tid=$tid'>del</a>
</td></tr>";
$i++;
}
?>
</table>

View File

@ -1,113 +0,0 @@
<?php
include("config.php");
$date = date("Y-m-d");
?>
<form method='POST' action='inc/report.php' target='_blank'>
<table>
<tr> <td>Date</td> <td>:</td> <td><input type='month' id='dates' name='dates' onInput='getTest(this.value);' /></td> </tr>
<tr> <td>Test</td> <td>:</td> <td><select id='test' name='test' onchange='getControl(this.value)'><option> -- Select Test -- </option></select></td> </tr>
<tr> <td>Control 1</td> <td>:</td> <td><select id='control1' name='control1' onchange='getControl2()'><option> -- Select Control -- </option></select></td> </tr>
<tr> <td>Control 2</td> <td>:</td> <td><select id='control2' name='control2'><option> -- Select Control -- </option></select></td> </tr>
<tr> <td><input type='submit' value='go'></td> </tr>
</table>
</form>
<script src='assets/jquery.js'></script>
<script type="text/javascript">
function getTest(){
// Empty the dropdown
var testel = document.getElementById('test');
var control1el = document.getElementById('control1');
var control2el = document.getElementById('control2');
test.innerHTML = "";
control1.innerHTML = "";
control2.innerHTML = "";
var testopt = document.createElement('option');
testopt.value = 0;
testopt.innerHTML = '-- Select Test --';
testel.appendChild(testopt);
var controlopt = document.createElement('option');
controlopt.value = 0;
controlopt.innerHTML = '-- Select Control --';
control1el.appendChild(controlopt);
var controlopt = document.createElement('option');
controlopt.value = 0;
controlopt.innerHTML = '-- Select Control --';
control2el.appendChild(controlopt);
var dates = $("#dates").val();
$.ajax({
type: "POST",
contentType: "application/json; charset=utf-8",
url: "inc/view_ajax.php",
datatype : 'JSON',
data: JSON.stringify( {request:'getTest', dates: dates} ),
success: function(response){
var result = JSON.parse(response);
var len = result.length;
for(var i=0; i<len; i++){
var id = result[i].id;
var name = result[i].name;
console.log(result[i]);
// Add option to state dropdown
var opt = document.createElement('option');
opt.value = id;
opt.innerHTML = name;
testel.appendChild(opt);
}
},
error:function(exception){alert('Exeption:'+exception);}
});
}
function getControl(){
// Empty the dropdown
var control1el = document.getElementById('control1');
var control2el = document.getElementById('control2');
control1el.innerHTML = "";
control2el.innerHTML = "";
var controlopt = document.createElement('option');
controlopt.value = 0;
controlopt.innerHTML = '-- Select Control --';
control1el.appendChild(controlopt);
controlopt.value = 0;
controlopt.innerHTML = '-- Select Control --';
control2el.appendChild(controlopt);
// AJAX request
var test = $("#test").val();
var dates = $("#dates").val();
$.ajax({
type: "POST",
contentType: "application/json; charset=utf-8",
url: "inc/view_ajax.php",
datatype : 'JSON',
data: JSON.stringify( {request:'getControl', test: test, dates: dates} ),
success: function(response){
var result = JSON.parse(response);
var len = result.length;
for(var i=0; i<len; i++){
var id = result[i].id;
var name = result[i].name;
console.log(result[i]);
// Add option to state dropdown
var opt = document.createElement('option');
opt.value = id;
opt.innerHTML = name;
control1el.appendChild(opt);
var opt = document.createElement('option');
opt.value = id;
opt.innerHTML = name;
control2el.appendChild(opt);
}
},
error:function(exception){alert('Exeption:'+exception);}
});
}
</script>

View File

@ -1,55 +0,0 @@
<?php
include '../config.php';
// Read POST data
$postData = json_decode(file_get_contents("php://input"));
$request = "";
if(isset($postData->request)){
$request = $postData->request;
}
// Get Test
if($request == 'getTest'){
$dates = 0;
$result = array();$data = array();
if(isset($postData->dates)){
$dates = $postData->dates;
$sql = "select distinct testid, dt.name from DAILY_RESULT dr left join DICT_TEST dt on dt.id=dr.testid where convert(varchar(7), resdate, 126)='$dates'";
$stmt = sqlsrv_query( $conn1, $sql );
if( $stmt == false) { die( print_r( sqlsrv_errors(), true) ); }
while ( $row = sqlsrv_fetch_array( $stmt, SQLSRV_FETCH_NUMERIC ) ) {
$id = $row[0];
$name = $row[1];
$data[] = array( "id" => $id, "name" => $name );
}
}
echo json_encode($data);
die;
}
// Get control
if($request == 'getControl'){
$test = 0;
$result = array();$data = array();
if(isset($postData->test)){
$test = $postData->test;
$dates = $postData->dates;
$sql = "select distinct dr.controlid, dc.name, dc.lot from DAILY_RESULT dr
left join DICT_CONTROL dc on dc.id=dr.controlid
where convert(varchar(7), dr.resdate, 126)='$dates' and dr.testid='$test'";
$stmt = sqlsrv_query( $conn1, $sql );
if( $stmt == false) { die( print_r( sqlsrv_errors(), true) ); }
while ( $row = sqlsrv_fetch_array( $stmt, SQLSRV_FETCH_NUMERIC ) ) {
$id = "$row[0]|$row[2]";
$name = "$row[1] - $row[2]";
$data[] = array( "id" => $id, "name" => $name );
}
}
echo json_encode($data);
die;
}

View File

@ -1,57 +0,0 @@
<?php
include("config.php");
if(isset($_GET['p'])) {$part = $_GET['p'];}
if(isset($_GET['d'])) {$d = $_GET['d'];}
?>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<title>QC APP</title>
<link rel="stylesheet" href="assets/styles.css?v=<?php echo time(); ?>">
<meta http-equiv="content-type" content="text/html; charset=iso-8859-1">
</head>
<body>
<div id="container">
<div id="nav">
<ul>
<li><a href='index.php'>View</a></li>
<li><a href='?p=entry'>Monthly Entry</a></li>
<li><a href='?p=entry_daily'>Daily Entry</a></li>
<li style="float:right"><a href='?p=test'>Dictionary Test</a></li>
<li style="float:right"><a href='?p=control'>Dictionary Control</a></li>
</ul>
</div>
<div id="content">
<br/>
<?php
if(isset($part)){
if($part=="control") {
if(isset($d)){
if($d=="add") { include("inc/control_add.php"); }
elseif($d=="lot_add") { include("inc/control_lotadd.php"); }
elseif($d=="del") { include("inc/control_del.php"); }
elseif($d=="edit") { include("inc/control_edit.php"); }
} else { include("inc/control_index.php"); }
} elseif($part=="ct") {
if(isset($d)){
if($d=="add") { include("inc/ct_add.php"); }
elseif($d=="edit") { include("inc/ct_edit.php"); }
elseif($d=="del") { include("inc/ct_del.php"); }
} else { include("inc/ct_view.php"); }
} elseif($part=="test") {
if(isset($d)){
if($d=="add") { include("inc/test_add.php"); }
elseif($d=="edit") { include("inc/test_edit.php"); }
elseif($d=="del") { include("inc/test_del.php"); }
} else { include("inc/test_index.php"); }
} elseif($part=="entry") {
if(isset($_GET['cid'])) { include('inc/entry_1.php'); }
else { include('inc/entry.php'); }
} elseif($part=="entry_daily") { include('inc/entry_daily.php'); }
elseif($part=="entry_monthly") { include('inc/entry_monthly.php'); }
} else { include('inc/view.php'); }
?>
</div>
</div>
</body>
</html>

Binary file not shown.