Items
-Manage your items
-| Code | -Name | -Action | -
|---|---|---|
| - - | -- | - - | -
From ef6be6522e662636b0fa96a454e57eeef0aab19e Mon Sep 17 00:00:00 2001
From: mahdahar <89adham@gmail.com>
Date: Thu, 5 Feb 2026 20:06:51 +0700
Subject: [PATCH] refactor: Replace dropdowns with select inputs and improve
caching
- Replace dropdown components with select elements in department filters
- Add cache-control headers to test and control API endpoints
- Add merged report page for consolidated reporting
- Update navigation sidebar with separate report links
- Refactor AGENTS.md to concise format with Serena tools emphasis
- Clean up gitignore and remove CLAUDE.md
---
.gitignore | 4 +-
AGENTS.md | 670 +++-----------
CLAUDE.md | 178 ----
app/Config/Routes.php | 2 +-
.../Master/MasterControlsController.php | 2 +-
.../Master/MasterTestsController.php | 2 +-
app/Controllers/PageController.php | 4 +
app/Views/entry/daily.php | 49 +-
app/Views/entry/monthly.php | 49 +-
app/Views/layout/main_layout.php | 11 +-
.../master/control/dialog_control_form.php | 2 +-
app/Views/master/control/index.php | 67 +-
app/Views/master/control_test/index.php | 7 +-
app/Views/master/dept/dialog_dept_form.php | 4 +-
app/Views/master/dept/index.php | 22 +-
app/Views/master/test/dialog_test_form.php | 32 +-
app/Views/master/test/index.php | 87 +-
app/Views/report/merged.php | 814 ++++++++++++++++++
18 files changed, 1070 insertions(+), 936 deletions(-)
delete mode 100644 CLAUDE.md
create mode 100644 app/Views/report/merged.php
diff --git a/.gitignore b/.gitignore
index 1c59c9e..d952fae 100644
--- a/.gitignore
+++ b/.gitignore
@@ -126,5 +126,5 @@ _modules/*
/phpunit*.xml
.claude/
-_bmad/
-_bmad-output/
\ No newline at end of file
+.serena/
+CLAUDE.md
diff --git a/AGENTS.md b/AGENTS.md
index 6ccfb87..f6190a7 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -1,597 +1,169 @@
-# AGENTS.md - AI Agent Guidelines for [PROJECT NAME]
+# AGENTS.md - AI Agent Guidelines for TinyQC
-## AI Agent Guidelines
+## Important: Always Use Serena Tools
-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.
+**When editing this codebase, prefer Serena tools over regular file operations:**
+- Use `find_symbol` to locate code rather than grep/glob when possible
+- Use `replace_symbol_body` for entire method/class changes
+- Use `replace_content` with regex for targeted edits
+- Use `get_symbols_overview` before reading full files
+- Serena tools provide structured, semantically-aware code editing
+
+## Build/Lint/Test Commands
+
+```bash
+
+never start dev server just use the base_url from .env file
+
+# Run all tests
+./vendor/bin/phpunit
+composer test
+
+# Run single test file
+./vendor/bin/phpunit tests/unit/HealthTest.php
+
+# Run with coverage
+./vendor/bin/phpunit --coverage-html coverage/
+
+# Run specific test method
+./vendor/bin/phpunit --filter testMethodName
+
+# Run migrations
+php spark migrate
+php spark migrate:rollback
+
+# Clear cache
+php spark cache:clear
+```
## Technology Stack
| Layer | Technology |
|-------|------------|
| Backend | CodeIgniter 4 (PHP 8.1+) |
-| Frontend | Alpine.js + TailwindCSS |
-| Database | MySQL/MariaDB |
+| Frontend | Alpine.js + TailwindCSS + DaisyUI |
+| Database | SQL Server |
-## Key Files & Locations
+## Code Style Guidelines
-### Backend
-
-```
-app/Controllers/ # API & page controllers
-app/Models/ # Eloquent-style models
-app/Database/Migrations/ # Schema definitions
-app/Config/Routes.php # All routes defined here
-app/Helpers/ # Helper functions
-```
-
-### Frontend
-
-```
-app/Views/ # PHP views with Alpine.js
-app/Views/layout/ # Base templates
-public/ # Static assets (css, js)
-```
-
-## Coding Conventions
-
-### PHP / CodeIgniter 4
-
-1. **Controllers** extend `BaseController` and use `ResponseTrait`
-2. **Models** extend `App\Models\BaseModel` (custom base with auto camel/snake conversion)
-3. **Soft deletes** are enabled on all tables (`deleted_at`)
-4. **Timestamps** are automatic (`created_at`, `updated_at`)
-5. **Validation** happens in controllers, not models
-6. **JSON API responses** follow this structure:
- ```php
- return $this->respond([
- 'status' => 'success',
- 'message' => 'fetch success',
- 'data' => $rows
- ], 200);
- ```
-
-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()`
-
-### Database
-
-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
-
-```php
-// In Routes.php
-$routes->get('api/resource', 'ResourceController::index');
-$routes->get('api/resource/(:num)', 'ResourceController::show/$1');
-$routes->post('api/resource', 'ResourceController::create');
-$routes->patch('api/resource/(:num)', 'ResourceController::update/$1');
-```
-
-### Controller Template
+### PHP
+**Imports** - Order: CodeIgniter, App namespace, use statements
```php
model = new ItemsModel();
- $this->rules = [
- 'itemCode' => 'required|min_length[1]',
- 'itemName' => 'required',
- ];
- }
+**Comments**
+- Minimal/none - write self-documenting code
- public function index() {
- $keyword = $this->request->getGet('keyword');
- try {
- $rows = $this->model->getItems($keyword);
- return $this->respond([
- 'status' => 'success',
- 'message' => 'fetch success',
- 'data' => $rows
- ], 200);
- } catch (\Exception $e) {
- return $this->failServerError('Exception: ' . $e->getMessage());
- }
- }
-
- 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());
- }
- }
+**Error Handling**
+```php
+try {
+ $rows = $this->model->findAll();
+ return $this->respond(['status' => 'success', 'message' => 'fetch success', 'data' => $rows], 200);
+} catch (\Exception $e) {
+ return $this->failServerError($e->getMessage());
}
```
-### Model Template
+### Controllers
+**Structure**
+- Extend `BaseController`, use `ResponseTrait`
+- Define `$model` and `$rules` in `__construct()`
+- CRUD pattern: `index()`, `show($id)`, `create()`, `update($id)`, `delete($id)`
+
+**Input Handling**
```php
-request->getJSON(true);
+$input = camel_to_snake_array($input); // Convert before DB
+```
-use App\Models\BaseModel;
+**Response Format**
+```php
+return $this->respond([
+ 'status' => 'success',
+ 'message' => 'fetch success',
+ 'data' => $rows
+], 200);
+```
-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;
-
- public function getItems($keyword = null) {
- if ($keyword) {
- return $this->groupStart()
- ->like('item_code', $keyword)
- ->orLike('item_name', $keyword)
- ->groupEnd()
- ->findAll();
- }
- return $this->findAll();
- }
+**Validation**
+```php
+if (!$this->validate($this->rules)) {
+ return $this->failValidationErrors($this->validator->getErrors());
}
```
-### Migration Template
+### Models
+**Structure**
+- Extend `App\Models\BaseModel` (auto camel/snake conversion)
+- Always enable: `$useSoftDeletes = true`, `$useTimestamps = true`
+- Define: `$table`, `$primaryKey`, `$allowedFields`
+
+**Example**
```php
-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);
- }
-}
+protected $table = 'results';
+protected $primaryKey = 'result_id';
+protected $allowedFields = ['control_id', 'test_id', 'res_date', 'res_value'];
```
-### Routes Template
+### Database Conventions
-```php
-post('/login', 'AuthController::login');
-$routes->get('/logout', 'AuthController::logout');
+**JavaScript**
+- No jQuery - use Fetch API
+- camelCase in JavaScript, snake_case in PHP/DB
+- Use Alpine.js: `x-data`, `x-model`, `x-show`, `x-for`, `@click`
-$routes->group('', ['filter' => 'jwt-auth'], function ($routes) {
- $routes->get('/', 'PagesController::dashboard');
- $routes->get('/module', 'PagesController::module');
- $routes->get('/master/items', 'PagesController::masterItems');
-});
+**DaisyUI**
+- Use DaisyUI components (btn, input, modal, dropdown, etc.)
+- TailwindCSS for utilities
-$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');
-});
+**Base URL**
+```javascript
+const BASEURL = '= base_url(''); ?>'; // or window.BASEURL
```
-### View Template (Index with Alpine.js)
+## File Naming
-```php
-= $this->extend("layout/main_layout"); ?>
-
-= $this->section("content"); ?>
- Manage your itemsItems
-
-
-
-
-
-
-
-
- Code
- Name
- Action
-
-
-
-
-
-
-
-
-
-
-
-