tinyqc/CLAUDE.md
mahdahar 14baa6b758 docs: add comprehensive documentation and refactor API structure
This commit introduces a complete documentation suite, refactors the API layer for
better consistency, and updates the database schema and seeding logic.

Key Changes:
- Documentation:
  - Added `CLAUDE.md` with development guidelines and architecture overview.
  - Created `docs/` directory with detailed guides for architecture, development,
    and source tree analysis.
- Database & Migrations:
  - Implemented `RenameMasterColumns` migration to standardize column naming
    (e.g., `name` -> `dept_name`, `name` -> `control_name`).
  - Added `CmodQcSeeder` to populate the system with realistic sample data
    for depts, controls, tests, and results.
- Backend API:
  - Created `DashboardApiController` with `getRecent()` for dashboard stats.
  - Created `ReportApiController` for managed reporting access.
  - Updated `app/Config/Routes.php` with new API groupings and documentation routes.
- Frontend & Views:
  - Refactored master data views (`dept`, `test`, `control`) to use Alpine.js
    and the updated API structure.
  - Modernized `dashboard.php` and `main_layout.php` with improved UI/UX.
- Infrastructure:
  - Updated `.gitignore` to exclude development-specific artifacts (`_bmad/`, `.claude/`).
2026-01-20 14:44:46 +07:00

5.0 KiB

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

Commands

# Development server
php spark serve

# Database migrations
php spark migrate           # Run pending migrations
php spark migrate:rollback  # Rollback last batch
php spark db seed CmodQcSeeder  # Seed initial data

# Run tests
./vendor/bin/phpunit                    # All tests
./vendor/bin/phpunit tests/unit/SomeTest.php  # Specific test file
./vendor/bin/phpunit --coverage-html coverage/  # With coverage report

Architecture

This is a CodeIgniter 4 Quality Control management system with:

  • Backend: PHP 8.1+, CodeIgniter 4
  • Database: SQL Server (uses SQLSRV driver)
  • Frontend: TailwindCSS + Alpine.js + DaisyUI (CDN-based, no build step)
  • Testing: PHPUnit 10
  • Icons: FontAwesome 6

Key Components

Models (app/Models/):

  • BaseModel - Custom base model with automatic camelCase/snake_case conversion
    • findAll(), find(), first() return camelCase keys
    • insert(), update() accept camelCase, convert to snake_case for DB
  • Organized in subdirectories: Master/, Qc/

Controllers (app/Controllers/):

  • PageController - Renders page views with main_layout
  • Api\* - Generic entry API controllers (Dashboard, Report, Entry)
  • Master\* - CRUD for master data (Depts, Tests, Controls)
  • Qc\* - QC domain controllers (ControlTests, Results, ResultComments)

Views (app/Views/):

  • PHP templates extending layout/main_layout
  • Alpine.js components in x-data blocks
  • DaisyUI components for UI

Helpers (app/Helpers/):

  • stringcase_helper.php - camel_to_snake_array(), snake_to_camel_array()

Database Schema

Tables use soft deletes (deleted_at) and timestamps (created_at, updated_at):

  • dict_depts, dict_tests, dict_controls - Master data
  • control_tests - Control-test associations with QC parameters (mean, sd)
  • results - Daily test results
  • result_comments - Comments per result

Conventions

Case Convention

  • Frontend/JS/API: camelCase
  • Backend PHP variables: camelCase
  • Database: snake_case
  • Models handle automatic conversion; use helpers for manual conversions

API Response Format

return $this->respond([
    'status' => 'success',
    'message' => 'fetch success',
    'data' => $rows
], 200);

Controller Pattern

namespace App\Controllers\Master;

use CodeIgniter\API\ResponseTrait;
use App\Controllers\BaseController;

class DeptsController extends BaseController {
    use ResponseTrait;
    protected $model;
    protected $rules;

    public function __construct() {
        $this->model = new MasterDeptsModel();
        $this->rules = ['name' => 'required|min_length[1]'];
    }

    public function index() {
        $keyword = $this->request->getGet('keyword');
        $rows = $this->model->search($keyword);
        return $this->respond([...], 200);
    }

    public function create() {
        $input = camel_to_snake_array($this->request->getJSON(true));
        if (!$this->validate($this->rules)) {
            return $this->failValidationErrors($this->validator->getErrors());
        }
        $id = $this->model->insert($input, true);
        return $this->respondCreated(['status' => 'success', 'message' => $id]);
    }
}

Model Pattern

namespace App\Models\Master;

use App\Models\BaseModel;

class MasterDeptsModel extends BaseModel {
    protected $table = 'dict_depts';
    protected $primaryKey = 'dept_id';
    protected $allowedFields = ['dept_name', 'deleted_at'];
    protected $useTimestamps = true;
    protected $useSoftDeletes = true;

    public function search(?string $keyword) {
        if ($keyword) {
            $this->like('dept_name', $keyword);
        }
        return $this->findAll();
    }
}

Routes Pattern

  • Page routes: $routes->get('/path', 'PageController::method');
  • API routes: $routes->group('api', function($routes) { ... });
  • API sub-groups: api/master, api/qc

Frontend Patterns

  • Alpine.js x-data for component state (inline or in <script> blocks)
  • Fetch API for AJAX (no jQuery)
  • DaisyUI components for UI
  • Modals with x-show and x-transition
  • window.BASEURL available globally for API calls
  • Views access page data via $pageData['title'], $pageData['userInitials'], $pageData['userName'], $pageData['userRole']

View Template Pattern

<?= $this->extend("layout/main_layout"); ?>
<?= $this->section("content"); ?>
<main x-data="componentName()">
    <!-- UI content -->
</main>
<?= $this->endSection(); ?>
<?= $this->section("script"); ?>
<script>
    document.addEventListener('alpine:init', () => {
        Alpine.data("componentName", () => ({
            // state and methods
        }));
    });
</script>
<?= $this->endSection(); ?>

Things to Avoid

  1. Don't skip soft deletes (deleted_at)
  2. Don't mix concerns - controllers handle HTTP, models handle data
  3. Don't forget case conversion - use helpers or BaseModel