Initial commit: Add CodeIgniter 4 QC application with full MVC structure
- CodeIgniter 4 framework setup with SQL Server database config
- Models: Control, Test, Dept, Result, Daily/ Monthly entry models
- Controllers: Dashboard, Control, Test, Dept, Entry, Report, API endpoints
- Views: CRUD pages with modal dialogs, dashboard, reports
- Database: Migrations for control test and daily/monthly result tables
- Legacy v1 PHP application preserved in /v1 directory
- Documentation: AGENTS.md, VIEWS_RULES.md for development guidelines
2026-01-14 16:49:27 +07:00
|
|
|
<?= $this->extend("layout/main_layout"); ?>
|
|
|
|
|
<?= $this->section("content") ?>
|
|
|
|
|
<main x-data="controlIndex()">
|
|
|
|
|
<!-- Page Header -->
|
|
|
|
|
<div class="flex items-center justify-between mb-6">
|
|
|
|
|
<div>
|
|
|
|
|
<h1 class="text-2xl font-bold text-slate-800">Control Dictionary</h1>
|
|
|
|
|
<p class="text-sm text-slate-500 mt-1">Manage control materials and lot numbers</p>
|
|
|
|
|
</div>
|
|
|
|
|
<button @click="showForm()" class="btn btn-primary">
|
|
|
|
|
<i class="fa-solid fa-plus mr-2"></i>Add Control
|
|
|
|
|
</button>
|
|
|
|
|
</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>
|
|
|
|
|
|
|
|
|
|
<!-- 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-magnifying-glass absolute left-3 top-1/2 -translate-y-1/2 text-slate-400"></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 controls...">
|
|
|
|
|
</div>
|
2026-01-16 16:37:13 +07:00
|
|
|
<select x-model="deptId" @change="fetchList()" class="select select-bordered select-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="">All Departments</option>
|
|
|
|
|
<template x-for="dept in depts" :key="dept.dept_id">
|
|
|
|
|
<option :value="dept.dept_id" x-text="dept.name"></option>
|
|
|
|
|
</template>
|
|
|
|
|
</select>
|
Initial commit: Add CodeIgniter 4 QC application with full MVC structure
- CodeIgniter 4 framework setup with SQL Server database config
- Models: Control, Test, Dept, Result, Daily/ Monthly entry models
- Controllers: Dashboard, Control, Test, Dept, Entry, Report, API endpoints
- Views: CRUD pages with modal dialogs, dashboard, reports
- Database: Migrations for control test and daily/monthly result tables
- Legacy v1 PHP application preserved in /v1 directory
- Documentation: AGENTS.md, VIEWS_RULES.md for development guidelines
2026-01-14 16:49:27 +07:00
|
|
|
<button @click="fetchList()" class="btn btn-primary">
|
|
|
|
|
<i class="fa-solid fa-magnifying-glass mr-2"></i>Search
|
|
|
|
|
</button>
|
|
|
|
|
</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 controls...</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-sliders text-slate-400 text-xl"></i>
|
|
|
|
|
</div>
|
|
|
|
|
<p class="text-slate-500 text-sm">No controls 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">Name</th>
|
|
|
|
|
<th class="py-3 px-5 font-semibold">Lot</th>
|
|
|
|
|
<th class="py-3 px-5 font-semibold">Department</th>
|
2026-01-16 16:37:13 +07:00
|
|
|
<th class="py-3 px-5 font-semibold">Status</th>
|
Initial commit: Add CodeIgniter 4 QC application with full MVC structure
- CodeIgniter 4 framework setup with SQL Server database config
- Models: Control, Test, Dept, Result, Daily/ Monthly entry models
- Controllers: Dashboard, Control, Test, Dept, Entry, Report, API endpoints
- Views: CRUD pages with modal dialogs, dashboard, reports
- Database: Migrations for control test and daily/monthly result tables
- Legacy v1 PHP application preserved in /v1 directory
- Documentation: AGENTS.md, VIEWS_RULES.md for development guidelines
2026-01-14 16:49:27 +07:00
|
|
|
<th class="py-3 px-5 font-semibold">Expiry Date</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.control_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 font-medium text-slate-800" x-text="item.name"></td>
|
|
|
|
|
<td class="py-3 px-5 text-slate-600">
|
|
|
|
|
<span class="font-mono text-xs bg-slate-100 text-slate-600 px-2 py-1 rounded" x-text="item.lot || '-'"></span>
|
|
|
|
|
</td>
|
|
|
|
|
<td class="py-3 px-5 text-slate-600" x-text="item.dept_name || '-'"></td>
|
2026-01-16 16:37:13 +07:00
|
|
|
<td class="py-3 px-5">
|
|
|
|
|
<span :class="isExpired(item.expdate) ? 'bg-red-100 text-red-700' : 'bg-emerald-100 text-emerald-700'" class="px-2 py-1 text-xs font-medium rounded-full" x-text="isExpired(item.expdate) ? 'Expired' : 'Active'"></span>
|
|
|
|
|
</td>
|
Initial commit: Add CodeIgniter 4 QC application with full MVC structure
- CodeIgniter 4 framework setup with SQL Server database config
- Models: Control, Test, Dept, Result, Daily/ Monthly entry models
- Controllers: Dashboard, Control, Test, Dept, Entry, Report, API endpoints
- Views: CRUD pages with modal dialogs, dashboard, reports
- Database: Migrations for control test and daily/monthly result tables
- Legacy v1 PHP application preserved in /v1 directory
- Documentation: AGENTS.md, VIEWS_RULES.md for development guidelines
2026-01-14 16:49:27 +07:00
|
|
|
<td class="py-3 px-5 text-slate-600" x-text="item.expdate ? new Date(item.expdate).toLocaleDateString() : '-'"></td>
|
|
|
|
|
<td class="py-3 px-5 text-right">
|
|
|
|
|
<button @click="showForm(item.control_id)" class="text-blue-600 hover:text-blue-800 mr-3">
|
|
|
|
|
<i class="fa-solid fa-pen-to-square"></i>
|
|
|
|
|
</button>
|
|
|
|
|
<button @click="deleteItem(item.control_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('control/dialog_form'); ?>
|
|
|
|
|
</main>
|
|
|
|
|
<?= $this->endSection(); ?>
|
|
|
|
|
|
|
|
|
|
<?= $this->section("script") ?>
|
|
|
|
|
<script>
|
|
|
|
|
document.addEventListener('alpine:init', () => {
|
|
|
|
|
Alpine.data("controlIndex", () => ({
|
|
|
|
|
loading: false,
|
|
|
|
|
showModal: false,
|
|
|
|
|
list: [],
|
|
|
|
|
form: {},
|
|
|
|
|
errors: {},
|
|
|
|
|
error: '',
|
|
|
|
|
keyword: '',
|
2026-01-16 16:37:13 +07:00
|
|
|
deptId: '',
|
Initial commit: Add CodeIgniter 4 QC application with full MVC structure
- CodeIgniter 4 framework setup with SQL Server database config
- Models: Control, Test, Dept, Result, Daily/ Monthly entry models
- Controllers: Dashboard, Control, Test, Dept, Entry, Report, API endpoints
- Views: CRUD pages with modal dialogs, dashboard, reports
- Database: Migrations for control test and daily/monthly result tables
- Legacy v1 PHP application preserved in /v1 directory
- Documentation: AGENTS.md, VIEWS_RULES.md for development guidelines
2026-01-14 16:49:27 +07:00
|
|
|
depts: <?= json_encode($depts ?? []) ?>,
|
|
|
|
|
tests: <?= json_encode($tests ?? []) ?>,
|
|
|
|
|
|
|
|
|
|
init() {
|
|
|
|
|
this.fetchList();
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
async fetchList() {
|
|
|
|
|
this.loading = true;
|
|
|
|
|
this.error = '';
|
|
|
|
|
try {
|
2026-01-16 16:37:13 +07:00
|
|
|
const params = new URLSearchParams();
|
|
|
|
|
if (this.keyword) params.append('keyword', this.keyword);
|
|
|
|
|
if (this.deptId) params.append('deptId', this.deptId);
|
|
|
|
|
const res = await fetch(`${window.BASEURL}/api/control?${params}`);
|
Initial commit: Add CodeIgniter 4 QC application with full MVC structure
- CodeIgniter 4 framework setup with SQL Server database config
- Models: Control, Test, Dept, Result, Daily/ Monthly entry models
- Controllers: Dashboard, Control, Test, Dept, Entry, Report, API endpoints
- Views: CRUD pages with modal dialogs, dashboard, reports
- Database: Migrations for control test and daily/monthly result tables
- Legacy v1 PHP application preserved in /v1 directory
- Documentation: AGENTS.md, VIEWS_RULES.md for development guidelines
2026-01-14 16:49:27 +07:00
|
|
|
const data = await res.json();
|
|
|
|
|
if (data.status === 'success') {
|
|
|
|
|
this.list = data.data || [];
|
|
|
|
|
} else {
|
|
|
|
|
this.error = data.message || 'Failed to load controls';
|
|
|
|
|
}
|
|
|
|
|
} catch (err) {
|
|
|
|
|
console.error(err);
|
|
|
|
|
this.error = 'Network error. Please try again.';
|
|
|
|
|
} finally {
|
|
|
|
|
this.loading = false;
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
2026-01-16 16:37:13 +07:00
|
|
|
isExpired(expdate) {
|
|
|
|
|
if (!expdate) return false;
|
|
|
|
|
return new Date(expdate) < new Date();
|
|
|
|
|
},
|
|
|
|
|
|
Initial commit: Add CodeIgniter 4 QC application with full MVC structure
- CodeIgniter 4 framework setup with SQL Server database config
- Models: Control, Test, Dept, Result, Daily/ Monthly entry models
- Controllers: Dashboard, Control, Test, Dept, Entry, Report, API endpoints
- Views: CRUD pages with modal dialogs, dashboard, reports
- Database: Migrations for control test and daily/monthly result tables
- Legacy v1 PHP application preserved in /v1 directory
- Documentation: AGENTS.md, VIEWS_RULES.md for development guidelines
2026-01-14 16:49:27 +07:00
|
|
|
async showForm(id = null) {
|
|
|
|
|
this.errors = {};
|
|
|
|
|
if (id) {
|
|
|
|
|
const item = this.list.find(x => x.control_id === id);
|
|
|
|
|
if (item) {
|
|
|
|
|
this.form = {
|
|
|
|
|
control_id: item.control_id,
|
|
|
|
|
dept_ref_id: item.dept_ref_id,
|
|
|
|
|
name: item.name,
|
|
|
|
|
lot: item.lot || '',
|
|
|
|
|
producer: item.producer || '',
|
|
|
|
|
expdate: item.expdate || '',
|
|
|
|
|
test_ids: []
|
|
|
|
|
};
|
|
|
|
|
// Fetch assigned tests
|
|
|
|
|
try {
|
|
|
|
|
const res = await fetch(`${window.BASEURL}/api/control/${id}/tests`);
|
|
|
|
|
const data = await res.json();
|
|
|
|
|
if (data.status === 'success') {
|
|
|
|
|
this.form.test_ids = data.data || [];
|
|
|
|
|
}
|
|
|
|
|
} catch (err) {
|
|
|
|
|
console.error('Failed to fetch assigned tests', err);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
this.form = {
|
|
|
|
|
dept_ref_id: '',
|
|
|
|
|
name: '',
|
|
|
|
|
lot: '',
|
|
|
|
|
producer: '',
|
|
|
|
|
expdate: '',
|
|
|
|
|
test_ids: []
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
this.showModal = true;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
closeModal() {
|
|
|
|
|
this.showModal = false;
|
|
|
|
|
this.errors = {};
|
|
|
|
|
this.form = {};
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
validate() {
|
|
|
|
|
this.errors = {};
|
|
|
|
|
if (!this.form.dept_ref_id) this.errors.dept_ref_id = 'Department is required';
|
|
|
|
|
if (!this.form.name) this.errors.name = 'Control name is required';
|
|
|
|
|
return Object.keys(this.errors).length === 0;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
async save() {
|
|
|
|
|
if (!this.validate()) return;
|
|
|
|
|
this.loading = true;
|
|
|
|
|
try {
|
|
|
|
|
const url = this.form.control_id
|
|
|
|
|
? `${window.BASEURL}/api/control/${this.form.control_id}`
|
|
|
|
|
: `${window.BASEURL}/api/control`;
|
|
|
|
|
|
|
|
|
|
const res = await fetch(url, {
|
2026-01-15 10:44:09 +07:00
|
|
|
method: this.form.control_id ? 'PATCH' : 'POST',
|
Initial commit: Add CodeIgniter 4 QC application with full MVC structure
- CodeIgniter 4 framework setup with SQL Server database config
- Models: Control, Test, Dept, Result, Daily/ Monthly entry models
- Controllers: Dashboard, Control, Test, Dept, Entry, Report, API endpoints
- Views: CRUD pages with modal dialogs, dashboard, reports
- Database: Migrations for control test and daily/monthly result tables
- Legacy v1 PHP application preserved in /v1 directory
- Documentation: AGENTS.md, VIEWS_RULES.md for development guidelines
2026-01-14 16:49:27 +07:00
|
|
|
headers: { 'Content-Type': 'application/json' },
|
|
|
|
|
body: JSON.stringify(this.form)
|
|
|
|
|
});
|
|
|
|
|
const data = await res.json();
|
|
|
|
|
|
|
|
|
|
if (data.status === 'success') {
|
|
|
|
|
this.closeModal();
|
|
|
|
|
this.fetchList();
|
|
|
|
|
} else {
|
|
|
|
|
this.error = data.message || 'Failed to save control';
|
|
|
|
|
}
|
|
|
|
|
} catch (err) {
|
|
|
|
|
console.error(err);
|
|
|
|
|
this.error = 'Network error. Please try again.';
|
|
|
|
|
} finally {
|
|
|
|
|
this.loading = false;
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
async deleteItem(id) {
|
|
|
|
|
if (!confirm('Are you sure you want to delete this control?')) return;
|
|
|
|
|
this.loading = true;
|
|
|
|
|
try {
|
|
|
|
|
const res = await fetch(`${window.BASEURL}/api/control/${id}`, { method: 'DELETE' });
|
|
|
|
|
const data = await res.json();
|
|
|
|
|
if (data.status === 'success') {
|
|
|
|
|
this.fetchList();
|
|
|
|
|
} else {
|
|
|
|
|
this.error = data.message || 'Failed to delete control';
|
|
|
|
|
}
|
|
|
|
|
} catch (err) {
|
|
|
|
|
console.error(err);
|
|
|
|
|
this.error = 'Network error. Please try again.';
|
|
|
|
|
} finally {
|
|
|
|
|
this.loading = false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}));
|
|
|
|
|
});
|
|
|
|
|
</script>
|
|
|
|
|
<?= $this->endSection(); ?>
|
|
|
|
|
|