tinyqc/app/Views/master/control/dialog_control_form.php
mahdahar 4ae2c75fdd feat: Add department filtering to Controls, Tests, and Entry pages
Implemented comprehensive department filtering across multiple pages in the QC
system to enable users to filter data by laboratory department.

## Backend Changes

**Models:**
- MasterControlsModel: Enhanced search() method to accept optional dept_id
  parameter, added LEFT JOIN with master_depts to include department info
- MasterTestsModel: Updated search() to support dept_id filtering with JOIN

**Controllers:**
- MasterControlsController: Modified index() to accept and pass dept_id parameter
- MasterTestsController: Modified index() to accept and pass dept_id parameter
- EntryApiController: Updated getControls() to filter by dept_id using model search,
  added debug logging for troubleshooting

## Frontend Changes

**Views Updated:**
1. Controls page (/master/control)
   - Added department dropdown with DaisyUI styling
   - Active filter badge and clear button
   - Fetch controls filtered by selected department
   - Added department field to control form dialog

2. Tests page (/master/test)
   - Added department dropdown with active state indication
   - Filter tests by department
   - Clear button to reset filter

3. Daily Entry page (/entry/daily)
   - Added department dropdown in filter section
   - Resets control selection when department changes
   - Fetches controls and tests filtered by department

4. Monthly Entry page (/entry/monthly)
   - Added department dropdown with month selector
   - Resets test selection when department changes
   - Fetches tests filtered by department

## Key Features

- Dropdown UI shows "All Departments" as default
- Selected department name displayed in dropdown button
- Clear button appears when filter is active
- Active department highlighted in dropdown menu
- Loading state while fetching departments
- Automatic reset of dependent selections when department changes
- Consistent UI pattern across all pages using DaisyUI components

## Bug Fixes

- Fixed syntax error in MasterControlsModel search() method
- Removed duplicate/corrupted code that was causing incorrect results
- Added proper deptName field to SELECT query in MasterControlsModel

## Important Note: Department IDs Required

**ACTION REQUIRED**: Existing controls and tests in the database must be assigned
to departments for the filter to work correctly.

To update existing records, run:
  UPDATE master_controls SET dept_id = 1 WHERE dept_id IS NULL;
  UPDATE master_tests SET dept_id = 1 WHERE dept_id IS NULL;

Replace '1' with a valid department ID from master_depts table.

Alternatively, edit each control/test through the UI to assign a department.

## Technical Details

- Alpine.js data binding for reactive department selection
- API expects 'dept_id' query parameter (snake_case)
- Internal state uses camelCase (deptId)
- Departments loaded on init via /api/master/depts
- Search requests include both keyword and dept_id parameters
2026-02-03 16:55:13 +07:00

106 lines
5.9 KiB
PHP

<dialog class="modal modal-bottom sm:modal-middle" :class="{ 'modal-open': showModal }">
<div class="modal-box p-5 border border-base-300 shadow-2xl bg-base-100 max-w-sm">
<h3 class="font-bold text-base mb-3 flex items-center gap-2 text-base-content">
<i class="fa-solid fa-vial text-primary text-sm"></i>
<span x-text="form.controlId ? 'Edit Control' : 'New Control'"></span>
</h3>
<div class="space-y-2">
<template x-if="!departmentsFetchComplete">
<div class="flex items-center justify-center py-2">
<span class="loading loading-spinner loading-sm text-primary"></span>
<span class="ml-2 text-xs opacity-50">Loading departments...</span>
</div>
</template>
<template x-if="departments">
<div class="form-control">
<label class="label py-1">
<span class="label-text-alt font-semibold text-base-content/70">Department</span>
</label>
<select
class="select select-bordered select-sm w-full focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary bg-base-200 border-base-300 text-base-content"
:class="{'border-error': errors.deptId}"
x-model="form.deptId"
placeholder="Select department">
<option value="">Select Department</option>
<template x-for="dept in departments" :key="dept.deptId">
<option :value="dept.deptId" x-text="dept.deptName"></option>
</template>
</select>
<template x-if="errors.deptId">
<label class="label">
<span class="label-text-alt text-error" x-text="errors.deptId"></span>
</label>
</template>
</div>
</template>
<div class="form-control">
<label class="label py-1">
<span class="label-text-alt font-semibold text-base-content/70">Control Name</span>
</label>
<input type="text"
class="input input-bordered input-sm w-full focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary bg-base-200 border-base-300 text-base-content placeholder:opacity-50"
:class="{'border-error': errors.controlName}" x-model="form.controlName" list="control-names-list"
placeholder="Enter control name" />
<datalist id="control-names-list">
<template x-for="name in uniqueControlNames" :key="name">
<option :value="name" x-text="name"></option>
</template>
</datalist>
<template x-if="errors.controlName">
<label class="label">
<span class="label-text-alt text-error" x-text="errors.controlName"></span>
</label>
</template>
</div>
<div class="grid grid-cols-2 gap-3">
<div class="form-control">
<label class="label py-1">
<span class="label-text-alt font-semibold text-base-content/70">Lot Number</span>
</label>
<input type="text"
class="input input-bordered input-sm w-full focus:outline-none focus:ring-1 focus:ring-primary/40 focus:border-primary bg-base-200 border-base-300 text-base-content placeholder:opacity-50"
x-model="form.lot" placeholder="e.g., LOT12345" />
</div>
<div class="form-control">
<label class="label py-1">
<span class="label-text-alt font-semibold text-base-content/70">Expiry Date</span>
</label>
<input type="date"
class="input input-bordered input-sm w-full focus:outline-none focus:ring-1 focus:ring-primary/40 focus:border-primary bg-base-200 border-base-300 text-base-content"
x-model="form.expDate" />
</div>
</div>
<div class="form-control">
<label class="label py-1">
<span class="label-text-alt font-semibold text-base-content/70">Producer</span>
</label>
<input type="text"
class="input input-bordered input-sm w-full focus:outline-none focus:ring-1 focus:ring-primary/40 focus:border-primary bg-base-200 border-base-300 text-base-content placeholder:opacity-50"
x-model="form.producer" list="producers-list" placeholder="Enter producer name" />
<datalist id="producers-list">
<template x-for="producer in uniqueProducers" :key="producer">
<option :value="producer" x-text="producer"></option>
</template>
</datalist>
</div>
</div>
<div class="modal-action mt-5">
<button class="btn btn-sm btn-ghost opacity-70" @click="closeModal()">Cancel</button>
<button class="btn btn-sm btn-primary gap-2 shadow-md shadow-primary/20 font-medium"
:class="{'loading': loading}" @click="save()" :disabled="loading">
<template x-if="!loading">
<span><i class="fa-solid fa-save"></i> Save</span>
</template>
<template x-if="loading">
<span>Saving...</span>
</template>
</button>
</div>
</div>
<form method="dialog" class="modal-backdrop bg-black/60" @click="closeModal()"></form>
</dialog>