feat: Add specimen collection page and improve error handling

- Add new phlebo/collect.php view (341 lines) for specimen collection workflow
- Add route for phlebotomist collection page in Routes.php
- Add collect() method to PhlebotomistController
- Update error pages (notfound.php, unauthorized.php) for better user experience
- Enhance login page with improved UI elements
- Update shared dialogs (dialog_results_generate.php, dialog_sample.php) with improvements
- Update config.php with new configurations
- Update UAT checklist documentation
- Fix AuthController login handling
This commit is contained in:
mahdahar 2026-02-09 13:39:51 +07:00
parent 46dc493af1
commit 08337b300d
11 changed files with 467 additions and 110 deletions

View File

@ -52,6 +52,15 @@
margin: 0;
page-break-after: avoid;
}
h2 {
color: #000;
text-align: center;
font-size: 10pt;
border-bottom: 1px solid #000;
padding: 1px 0;
margin: 10px 0 0 0;
page-break-after: avoid;
}
.data-table {
width: 100%;
border-collapse: collapse;
@ -107,88 +116,89 @@
<tr><th>ID</th><th>Judul Test Case</th><th>Langkah Utama</th><th>Expected Result</th><th>Role</th><th>Hasil</th><th>Issue/Jawaban</th></tr>
<tr><td>🎯</td><td>📌 Ready to Test!</td><td>🚀 Let's Go!</td><td>✨ Good Luck!</td><td>👥 All Roles</td><td>🎉 Fun!</td><td>📝 Notes...</td></tr>
<tr><td>TC-001</td><td>Login Berhasil 🔐</td><td>Login dengan username &amp; password valid</td><td>Redirect ke dashboard sesuai role 🎯</td><td>AUTENTIKASI &amp; SESI 🔑</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-002</td><td>Login Gagal - Username Salah 🚫</td><td>Masukkan username tidak ada</td><td>Error "Invalid credentials" ⚠️</td><td>AUTENTIKASI &amp; SESI 🔐</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-003</td><td>Login Gagal - Password Salah 🙅</td><td>Password salah</td><td>Error invalid credentials 🚨</td><td>AUTENTIKASI &amp; SESI 🔐</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-004</td><td>Login Gagal - Akun Terkunci 🔒</td><td>Login ke akun locked dari legacy</td><td>Error "Account locked" 🛑</td><td>AUTENTIKASI &amp; SESI 🔐</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-005</td><td>Logout Berhasil 👋</td><td>Klik menu Logout</td><td>Session terhapus, redirect ke login ✨</td><td>AUTENTIKASI &amp; SESI 🔑</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-006</td><td>Redirect ke Login Belum Login 🔄</td><td>Akses protected page tanpa login</td><td>Redirect otomatis ke halaman login 🚪</td><td>AUTENTIKASI &amp; SESI 🛡️</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-007</td><td>Ganti Password Berhasil 🔑</td><td>Change password baru</td><td>Password berhasil diubah dan di-hash ✅</td><td>AUTENTIKASI &amp; SESI 🆕</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-008</td><td>Ganti Password Gagal - Password Lama Salah ❌</td><td>Masukkan password lama salah</td><td>Error "Invalid old password" 🚫</td><td>AUTENTIKASI &amp; SESI 🔐</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-009</td><td>Role-Based Redirect Login 👥</td><td>Login dengan berbagai role</td><td>Redirect ke dashboard sesuai role 🎯</td><td>AUTENTIKASI &amp; SESI 🎭</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-010</td><td>Tampilkan Semua Request 📊</td><td>Login Superuser → <code>/superuser</code></td><td>Tabel requests semua status ditampilkan 📋</td><td>SU, ADM, PHLEB, CS</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-011</td><td>Filter by Status 🎛️</td><td>Pilih status di dashboard filter</td><td>Filter berfungsi sesuai status 🔍</td><td>SU</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-012</td><td>Filter by Date Range 📅</td><td>Set date1 &amp; date2 → filter</td><td>Filter tanggal berfungsi 🗓️</td><td>SU</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-013</td><td>Search by Patient Name 👤</td><td>Masukkan nama pasien</td><td>Tampilkan request dengan nama tsb 🔍</td><td>SU</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-014</td><td>Search by Lab Number 🔢</td><td>Masukkan Lab Number</td><td>Tampilkan request tsb 🎯</td><td>SU</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-015</td><td>Search by Register Number #️⃣</td><td>Masukkan Register Number</td><td>Tampilkan request tsb 📌</td><td>SU</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-016</td><td>Table Sorting ↕️</td><td>Klik header kolom</td><td>Kolom di-sort ASC/DESC 📈📉</td><td>SU</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-017</td><td>Pagination 📖</td><td>Klik halaman berbeda</td><td>Berpindah halaman dengan benar 📄</td><td>SU</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-018</td><td>Tampilkan List User 👥</td><td>Access <code>/superuser/users</code></td><td>Tabel users ditampilkan lengkap 📋</td><td>SU, ADM</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-019</td><td>Buat User Baru </td><td>Add User → isi form</td><td>User berhasil dibuat dan password di-hash 🆔</td><td>SU, ADM</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-020</td><td>Validasi Password Min 3 Karakter 🔢</td><td>Buat user dengan password &lt; 3 char</td><td>Error "Password min 3 karakter" ⚠️</td><td>SU</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-021</td><td>Update User Role ✏️</td><td>Edit user → ubah role</td><td>Role berhasil diubah 🔄</td><td>SU</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-022</td><td>Update Password User 🔐</td><td>Edit user → ganti password</td><td>Password di-hash dengan benar 🔄</td><td>SU</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-023</td><td>Delete User 🗑️</td><td>Delete user</td><td>User terhapus dari database 🗑️</td><td>SU, ADM</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-024</td><td>Tampilkan Request Final ✨</td><td>Access <code>/superuser/validate</code></td><td>Hanya request "Fin" yang tampil ✅</td><td>SU, ADM, LAB</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-025</td><td>Filter by Date Range Validasi 📅</td><td>Set date1 &amp; date2 → filter</td><td>Filter berfungsi di halaman validasi 🔍</td><td>SU</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-026</td><td>First Validation (VAL1) ✅</td><td>Validasi request "Fin"</td><td>ISVAL1=1, VAL1USER &amp; VAL1DATE diset 📝</td><td>SU, ADM, LAB</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-027</td><td>Second Validation (VAL2) ✅✅</td><td>Validasi dengan user berbeda</td><td>ISVAL2=1, request duavalidated 🎉</td><td>SU, ADM, LAB</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-028</td><td>Gagal Validasi Sendiri 🚫</td><td>Coba validasi ulang sama user</td><td>Error "Cannot validate own request" 🙅</td><td>SU, ADM, LAB</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-029</td><td>Un-Validate Request ↩️</td><td>Un-validate dengan reason</td><td>ISVAL reset, PENDING diset 🔄</td><td>SU, ADM</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-030</td><td>Shortcut Keyboard ⌨️</td><td>Enter=Validate, N=Skip, Esc=Close</td><td>Shortcut berfungsi dengan benar ⚡</td><td>SU</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-031</td><td>Progress Indicator 📊</td><td>Buka validation modal</td><td>Progress muncul (posisi / total) 📈</td><td>SU</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-032</td><td>Collect Sample 🧪</td><td>Buka dialog sample → Collect</td><td>STATUS=1, COLLECTIONDATE &amp; USERID diset ✅</td><td>SU, ADM, LAB</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-033</td><td>Un-Collect Sample ↩️</td><td>Buka dialog sample → Un-Collect</td><td>STATUS di-reset, audit log tercatat 📝</td><td>SU, ADM, LAB</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-034</td><td>Un-Receive Sample 🔙</td><td>Un-receive sample yang received</td><td>Status berubah, audit log tercatat 📋</td><td>SU, ADM, LAB</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-035</td><td>Print Label Individu 🏷️</td><td>Print label per tube</td><td>Label tercetak dengan ZPL format 🖨️</td><td>SU</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-036</td><td>Print Collection Label 📋</td><td>Print collection label</td><td>Label collection tercetak 🏷️</td><td>SU</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-037</td><td>Print All Labels 📄</td><td>Print semua tubes</td><td>Semua labels tercetak 🖨️</td><td>SU</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-038</td><td>View Request Audit 🔍</td><td>Buka audit dialog request</td><td>Audit trail timeline ditampilkan 📜</td><td>SU</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-039</td><td>Filter by Category 🎯</td><td>Filter: All, Validation, Receive, Sample</td><td>Filter berfungsi sesuai kategori 🔍</td><td>SU</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-040</td><td>View Report Validated 📊</td><td>View report request duavalidated</td><td>Report ditampilkan lengkap 📋</td><td>SU, ADM, LAB, CS</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-041</td><td>Cannot View Not Validated 🚫</td><td>View report belum duavalidated</td><td>Error: "Validated request required" ⚠️</td><td>SU</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-042</td><td>Print Report 🖨️</td><td>Print report request validated</td><td>Print dialog buka, audit log tercatat 📝</td><td>SU, ADM, LAB, CS</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-043</td><td>Generate PDF 📑</td><td>Generate PDF request validated</td><td>PDF berhasil di-generate ✅</td><td>SU, ADM, LAB, CS</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-044</td><td>Preview Report (No Audit) 👀</td><td>Preview report tanpa log audit</td><td>Report tampil, tidak ada audit log 📄</td><td>SU</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-045</td><td>English Version Report 🇬🇧</td><td>Access <code>/report/{id}/eng</code></td><td>Report dalam Bahasa Inggris 🗣️</td><td>SU</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-046</td><td>Edit Comment ✏️</td><td>Edit comment di dashboard</td><td>Comment berubah tersimpan 💾</td><td>SU</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-047</td><td>Error Handling 404 🚫</td><td>Akses URL tidak valid</td><td>Custom notfound page tampil 📄</td><td>SU</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-048</td><td>Error Handling Unauthorized 🔒</td><td>Coba akses role lain</td><td>Redirect ke <code>/unauthorized</code> 🚫</td><td>SU</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-049</td><td>Filter &amp; Search 🔍</td><td>Uji berbagai filter dan search</td><td>Semua berfungsi seperti Superuser ⚡</td><td>ADMIN, PHLEB, CS</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-050</td><td>Sample Collection Buttons Enabled ✅</td><td>Buka dialog sample</td><td>Tombol Collect/Un-Coll/Un-Recv enabled 🔘</td><td>ADMIN</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-051</td><td>Edit Comment Enabled ✏️</td><td>Edit comment dashboard</td><td>Comment dapat diubah 💬</td><td>ADMIN</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-052</td><td>Print Labels 🏷️</td><td>Print individual/collection/all</td><td>Semua labels tercetak 🖨️</td><td>ADM, LAB, PHLEB</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-053</td><td>View Audit Trail 📜</td><td>Buka audit dialog</td><td>Timeline audit ditampilkan 🔍</td><td>ADM, LAB, PHLEB, CS</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-054</td><td>Update User 🔄</td><td>Edit user → ubah data</td><td>User berhasil di-update ✅</td><td>ADMIN</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-055</td><td>Cannot Access Role Lain 🚫</td><td>Coba <code>/superuser</code>, <code>/lab</code>, <code>/phlebo</code>, <code>/cs</code></td><td>Redirect ke unauthorized/error ⛔</td><td>ADM, LAB, PHLEB, CS</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-056</td><td>Filter Test Code Alphabetical 🔤</td><td>Login Lab → <code>/lab</code></td><td>Hanya request TESTS A-Z yang tampil 📋</td><td>LAB</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-057</td><td>View Request Details 👁️</td><td>Buka request</td><td>Detail request ditampilkan 📄</td><td>LAB</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-058</td><td>Filter &amp; Search dalam Batas Filter 🔍</td><td>Uji filter &amp; search</td><td>Sesuai batas test code A-Z ✅</td><td>LAB</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-059</td><td>Sample Buttons Enabled ✅</td><td>Buka dialog sample</td><td>Tombol Collect/Un-Coll/Un-Recv enabled 🔘</td><td>LAB</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-060</td><td>Edit Comment Disabled 🔒</td><td>Cek comment field</td><td>Comment field non-editable 🚫</td><td>LAB, PHLEB, CS</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-061</td><td>Un-Validate Disabled 🔒</td><td>Coba un-validate</td><td>Error/Unauthorized - cannot un-validate 🚫</td><td>LAB</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-062</td><td>Sample Collection Disabled 🔒</td><td>Buka dialog sample</td><td>Tombol Collect/Un-Coll/Un-Recv DISABLED 🚫</td><td>PHLEB, CS</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-063</td><td>Validation Page Denied 🔒</td><td>Coba <code>/phlebo/validate</code></td><td>Halaman tidak tersedia/unauthorized ⛔</td><td>PHLEB, CS</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-064</td><td>View Sample Details (Read-Only) 👀</td><td>Buka dialog sample</td><td>Sample details view-only 📄</td><td>PHLEB, CS</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-065</td><td>Cannot View Report 🔒</td><td>Coba view report</td><td>Report tidak tersedia/error 🚫</td><td>PHLEB</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-066</td><td>Cannot Print Report 🖨️❌</td><td>Coba print report</td><td>Print tidak tersedia 🚫</td><td>PHLEB</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-067</td><td>Cannot Generate PDF 📑❌</td><td>Coba generate PDF</td><td>PDF tidak tersedia 🚫</td><td>PHLEB</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-068</td><td>Print Labels (Limited) 🏷️</td><td>Print labels</td><td>Labels tercetak sesuai kapasitas 🖨️</td><td>CS</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-069</td><td>User Mgmt Role Restrictions 🔐</td><td>Lab/Phlebo/CS coba user management API</td><td>Error unauthorized 🚫</td><td>FITUR CROSS-ROLE 🔗</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-070</td><td>Cross-Role Validation 🤝</td><td>Lab A validasi VAL1, Superuser B validasi VAL2</td><td>Cross-role validation berhasil ✅</td><td>FITUR CROSS-ROLE 🔄</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-071</td><td>Un-Validate Role Restrictions 🔒</td><td>Lab/Phlebo/CS coba un-validate</td><td>Error unauthorized - only SU/Admin 🚫</td><td>FITUR CROSS-ROLE 🛡️</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-072</td><td>Un-Receive Role Restrictions 🔒</td><td>Lab/Phlebo/CS coba un-receive</td><td>Error unauthorized - only SU/Admin 🚫</td><td>FITUR CROSS-ROLE 🛡️</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-073</td><td>Report Access Restrictions 👁️</td><td>Coba view report semua role</td><td>SU/Admin/Lab/CS OK, Phlebo NOT ✅❌</td><td>FITUR CROSS-ROLE 🔍</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-074</td><td>Report Validated Requirement ✅✅</td><td>View report hanya VAR1 vs duavalidated</td><td>Hanya duavalidated dapat dilihat 🎯</td><td>FITUR CROSS-ROLE ✅</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-075</td><td>Comment Edit Restrictions 💬🔒</td><td>Lab/Phlebo/CS coba edit comment</td><td>Error - only SU/Admin editable 🚫</td><td>FITUR CROSS-ROLE ✏️</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-076</td><td>Validasi tidak Pengaruhi Data Lain 🛡️</td><td>Record data sebelum &amp; sesudah validasi</td><td>Hanya status validasi berubah 📝</td><td>FITUR CROSS-ROLE 🔍</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-077</td><td>Un-Validate Mempertahankan Data 🔄</td><td>Record data sebelum &amp; sesudah un-validate</td><td>Hanya status validasi berubah 📝</td><td>FITUR CROSS-ROLE 🔍</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-078</td><td>Audit Trail Logging 📜</td><td>Lakukan berbagai aktivitas</td><td>Semua tercatat di audit log ✅</td><td>FITUR CROSS-ROLE 🔍</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-079</td><td>Concurrent Validation ⚡</td><td>2 user buka validation dialog bersamaan</td><td>Validasi berhasil, tidak conflict ✅</td><td>FITUR CROSS-ROLE 🤝</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-080</td><td>Concurrent Sample Collection ⚡</td><td>2 user collect tube berbeda bersamaan</td><td>Semua berhasil tercatat ✅</td><td>FITUR CROSS-ROLE 🧪</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-081</td><td>Session Timeout ⏱️</td><td>Tunggu session timeout</td><td>Redirect ke login 🔄</td><td>FITUR CROSS-ROLE 🔐</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-082</td><td>Password Hashing Security 🔒</td><td>Buat user → cek database</td><td>Password dalam HASH bukan plain 🛡️</td><td>FITUR CROSS-ROLE 🔐</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-083</td><td>Legacy Read-Only 👀</td><td>Cek koneksi &amp; fungsi Firebird</td><td>Hanya READ dari Firebird, TIDAK WRITE 🚫✍️</td><td>FITUR CROSS-ROLE 🗄️</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-002</td><td>Login Gagal - Username / Password Salah 🚫</td><td>Salah username / salah password</td><td>Error "Invalid credentials" ⚠️</td><td>AUTENTIKASI &amp; SESI 🔐</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-003</td><td>Logout Berhasil 👋</td><td>Klik menu Logout</td><td>Session terhapus, redirect ke login ✨</td><td>AUTENTIKASI &amp; SESI 🔑</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-004</td><td>Redirect ke Login Belum Login 🔄</td><td>Akses protected page tanpa login</td><td>Redirect otomatis ke halaman login 🚪</td><td>AUTENTIKASI &amp; SESI 🛡️</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-005</td><td>Ganti Password Berhasil 🔑</td><td>Change password baru</td><td>Password berhasil diubah dan di-hash ✅</td><td>AUTENTIKASI &amp; SESI 🆕</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-006</td><td>Ganti Password Gagal - Password Lama Salah ❌</td><td>Masukkan password lama salah</td><td>Error "Invalid old password" 🚫</td><td>AUTENTIKASI &amp; SESI 🔐</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-007</td><td>Role-Based Redirect Login 👥</td><td>Login dengan berbagai role</td><td>Redirect ke dashboard sesuai role 🎯</td><td>AUTENTIKASI &amp; SESI 🎭</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-008</td><td>Tampilkan Semua Request 📊</td><td>Login Superuser → <code>/superuser</code></td><td>Tabel requests semua status ditampilkan 📋</td><td>SU, ADM, PHLEB, CS</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-009</td><td>Filter by Status 🎛️</td><td>Pilih status di dashboard filter</td><td>Filter berfungsi sesuai status 🔍</td><td>SU</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-010</td><td>Filter by Date Range 📅</td><td>Set date1 &amp; date2 → filter</td><td>Filter tanggal berfungsi 🗓️</td><td>SU</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-011</td><td>Search by Patient Name 👤</td><td>Masukkan nama pasien</td><td>Tampilkan request dengan nama tsb 🔍</td><td>SU</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-012</td><td>Search by Lab Number 🔢</td><td>Masukkan Lab Number</td><td>Tampilkan request tsb 🎯</td><td>SU</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-013</td><td>Search by Register Number #️⃣</td><td>Masukkan Register Number</td><td>Tampilkan request tsb 📌</td><td>SU</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-014</td><td>Table Sorting ↕️</td><td>Klik header kolom</td><td>Kolom di-sort ASC/DESC 📈📉</td><td>SU</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-015</td><td>Pagination 📖</td><td>Klik halaman berbeda</td><td>Berpindah halaman dengan benar 📄</td><td>SU</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-016</td><td>Tampilkan List User 👥</td><td>Access <code>/superuser/users</code></td><td>Tabel users ditampilkan lengkap 📋</td><td>SU, ADM</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-017</td><td>Buat User Baru </td><td>Add User → isi form</td><td>User berhasil dibuat dan password di-hash 🆔</td><td>SU, ADM</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-018</td><td>Validasi Password Min 3 Karakter 🔢</td><td>Buat user dengan password &lt; 3 char</td><td>Error "Password min 3 karakter" ⚠️</td><td>SU</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-019</td><td>Update User Role ✏️</td><td>Edit user → ubah role</td><td>Role berhasil diubah 🔄</td><td>SU</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-020</td><td>Update Password User 🔐</td><td>Edit user → ganti password</td><td>Password di-hash dengan benar 🔄</td><td>SU</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-021</td><td>Delete User 🗑️</td><td>Delete user</td><td>User terhapus dari database 🗑️</td><td>SU, ADM</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-022</td><td>Tampilkan Request Final ✨</td><td>Access <code>/superuser/validate</code></td><td>Hanya request "Fin" yang tampil ✅</td><td>SU, ADM, LAB</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-023</td><td>Filter by Date Range Validasi 📅</td><td>Set date1 &amp; date2 → filter</td><td>Filter berfungsi di halaman validasi 🔍</td><td>SU</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-024</td><td>First Validation (VAL1) ✅</td><td>Validasi request "Fin"</td><td>ISVAL1=1, VAL1USER &amp; VAL1DATE diset 📝</td><td>SU, ADM, LAB</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-025</td><td>Second Validation (VAL2) ✅✅</td><td>Validasi dengan user berbeda</td><td>ISVAL2=1, request duavalidated 🎉</td><td>SU, ADM, LAB</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-026</td><td>Gagal Validasi Sendiri 🚫</td><td>Coba validasi ulang sama user</td><td>Error "Cannot validate own request" 🙅</td><td>SU, ADM, LAB</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-027</td><td>Un-Validate Request ↩️</td><td>Un-validate dengan reason</td><td>ISVAL reset, PENDING diset 🔄</td><td>SU, ADM</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-028</td><td>Shortcut Keyboard ⌨️</td><td>Enter=Validate, N=Skip, Esc=Close</td><td>Shortcut berfungsi dengan benar ⚡</td><td>SU</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-029</td><td>Un-Receive Sample 🔙</td><td>Un-receive sample yang received</td><td>Status berubah, audit log tercatat 📋</td><td>SU, ADM, LAB</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-030</td><td>Print Label Individu 🏷️</td><td>Print label per tube</td><td>Label tercetak dengan ZPL format 🖨️</td><td>SU</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-031</td><td>Print Collection Label 📋</td><td>Print collection label</td><td>Label collection tercetak 🏷️</td><td>SU</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-032</td><td>Print All Labels 📄</td><td>Print semua tubes</td><td>Semua labels tercetak 🖨️</td><td>SU</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-033</td><td>View Request Audit 🔍</td><td>Buka audit dialog request</td><td>Audit trail timeline ditampilkan 📜</td><td>SU</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-034</td><td>Filter by Category 🎯</td><td>Filter: All, Validation, Receive, Sample</td><td>Filter berfungsi sesuai kategori 🔍</td><td>SU</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-035</td><td>View Report Validated 📊</td><td>View report request duavalidated</td><td>Report ditampilkan lengkap 📋</td><td>SU, ADM, LAB, CS</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-036</td><td>Cannot View Not Validated 🚫</td><td>View report belum duavalidated</td><td>Error: "Validated request required" ⚠️</td><td>SU</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-037</td><td>Print Report 🖨️</td><td>Print report request validated</td><td>Print dialog buka, audit log tercatat 📝</td><td>SU, ADM, LAB, CS</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-038</td><td>Generate PDF 📑</td><td>Generate PDF request validated</td><td>PDF berhasil di-generate ✅</td><td>SU, ADM, LAB, CS</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-039</td><td>Preview Report (No Audit) 👀</td><td>Preview report tanpa log audit</td><td>Report tampil, tidak ada audit log 📄</td><td>SU</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-040</td><td>English Version Report 🇬🇧</td><td>Access <code>/report/{id}/eng</code></td><td>Report dalam Bahasa Inggris 🗣️</td><td>SU</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-041</td><td>Error Handling 404 🚫</td><td>Akses URL tidak valid</td><td>Custom notfound page tampil 📄</td><td>SU</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-042</td><td>Error Handling Unauthorized 🔒</td><td>Coba akses role lain</td><td>Redirect ke <code>/unauthorized</code> 🚫</td><td>SU</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-043</td><td>Filter &amp; Search 🔍</td><td>Uji berbagai filter dan search</td><td>Semua berfungsi seperti Superuser ⚡</td><td>ADMIN, PHLEB, CS</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-044</td><td>Edit Comment Enabled ✏️</td><td>Edit comment dashboard</td><td>Comment dapat diubah 💬</td><td>ADMIN</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-045</td><td>View Audit Trail 📜</td><td>Buka audit dialog</td><td>Timeline audit ditampilkan 🔍</td><td>ADM, LAB, PHLEB, CS</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-046</td><td>Update User 🔄</td><td>Edit user → ubah data</td><td>User berhasil di-update ✅</td><td>ADMIN</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-047</td><td>Cannot Access Role Lain 🚫</td><td>Coba <code>/superuser</code>, <code>/lab</code>, <code>/phlebo</code>, <code>/cs</code></td><td>Redirect ke unauthorized/error ⛔</td><td>ADM, LAB, PHLEB, CS</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-048</td><td>Filter Test Code Alphabetical 🔤</td><td>Login Lab → <code>/lab</code></td><td>Hanya request TESTS A-Z yang tampil 📋</td><td>LAB</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-049</td><td>View Request Details 👁️</td><td>Buka request</td><td>Detail request ditampilkan 📄</td><td>LAB</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-050</td><td>Filter &amp; Search dalam Batas Filter 🔍</td><td>Uji filter &amp; search</td><td>Sesuai batas test code A-Z ✅</td><td>LAB</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-051</td><td>Sample Buttons Enabled ✅</td><td>Buka dialog sample</td><td>Tombol Collect/Un-Coll/Un-Recv enabled 🔘</td><td>LAB</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-052</td><td>Edit Comment Disabled 🔒</td><td>Cek comment field</td><td>Comment field non-editable 🚫</td><td>LAB, PHLEB, CS</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-053</td><td>Un-Validate Disabled 🔒</td><td>Coba un-validate</td><td>Error/Unauthorized - cannot un-validate 🚫</td><td>LAB</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-054</td><td>Sample Collection Disabled 🔒</td><td>Buka dialog sample</td><td>Tombol Collect/Un-Coll/Un-Recv DISABLED 🚫</td><td>PHLEB, CS</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-055</td><td>Validation Page Denied 🔒</td><td>Coba <code>/phlebo/validate</code></td><td>Halaman tidak tersedia/unauthorized ⛔</td><td>PHLEB, CS</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-056</td><td>View Sample Details (Read-Only) 👀</td><td>Buka dialog sample</td><td>Sample details view-only 📄</td><td>PHLEB, CS</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-057</td><td>Cannot View Report 🔒</td><td>Coba view report</td><td>Report tidak tersedia/error 🚫</td><td>PHLEB</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-058</td><td>Cannot Print Report 🖨️❌</td><td>Coba print report</td><td>Print tidak tersedia 🚫</td><td>PHLEB</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-059</td><td>Cannot Generate PDF 📑❌</td><td>Coba generate PDF</td><td>PDF tidak tersedia 🚫</td><td>PHLEB</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-060</td><td>Print Labels (Limited) 🏷️</td><td>Print labels</td><td>Labels tercetak sesuai kapasitas 🖨️</td><td>CS</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-061</td><td>User Mgmt Role Restrictions 🔐</td><td>Lab/Phlebo/CS coba user management API</td><td>Error unauthorized 🚫</td><td>FITUR CROSS-ROLE 🔗</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-062</td><td>Cross-Role Validation 🤝</td><td>Lab A validasi VAL1, Superuser B validasi VAL2</td><td>Cross-role validation berhasil ✅</td><td>FITUR CROSS-ROLE 🔄</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-063</td><td>Un-Validate Role Restrictions 🔒</td><td>Lab/Phlebo/CS coba un-validate</td><td>Error unauthorized - only SU/Admin 🚫</td><td>FITUR CROSS-ROLE 🛡️</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-064</td><td>Un-Receive Role Restrictions 🔒</td><td>Lab/Phlebo/CS coba un-receive</td><td>Error unauthorized - only SU/Admin 🚫</td><td>FITUR CROSS-ROLE 🛡️</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-065</td><td>Report Access Restrictions 👁️</td><td>Coba view report semua role</td><td>SU/Admin/Lab/CS OK, Phlebo NOT ✅❌</td><td>FITUR CROSS-ROLE 🔍</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-066</td><td>Report Validated Requirement ✅✅</td><td>View report hanya VAR1 vs duavalidated</td><td>Hanya duavalidated dapat dilihat 🎯</td><td>FITUR CROSS-ROLE ✅</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-067</td><td>Comment Edit Restrictions 💬🔒</td><td>Lab/Phlebo/CS coba edit comment</td><td>Error - only SU/Admin editable 🚫</td><td>FITUR CROSS-ROLE ✏️</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-068</td><td>Validasi tidak Pengaruhi Data Lain 🛡️</td><td>Record data sebelum &amp; sesudah validasi</td><td>Hanya status validasi berubah 📝</td><td>FITUR CROSS-ROLE 🔍</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-069</td><td>Un-Validate Mempertahankan Data 🔄</td><td>Record data sebelum &amp; sesudah un-validate</td><td>Hanya status validasi berubah 📝</td><td>FITUR CROSS-ROLE 🔍</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-070</td><td>Audit Trail Logging 📜</td><td>Lakukan berbagai aktivitas</td><td>Semua tercatat di audit log ✅</td><td>FITUR CROSS-ROLE 🔍</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-071</td><td>Concurrent Validation ⚡</td><td>2 user buka validation dialog bersamaan</td><td>Validasi berhasil, tidak conflict ✅</td><td>FITUR CROSS-ROLE 🤝</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-072</td><td>Concurrent Sample Collection ⚡</td><td>2 user collect tube berbeda bersamaan</td><td>Semua berhasil tercatat ✅</td><td>FITUR CROSS-ROLE 🧪</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-073</td><td>Session Timeout ⏱️</td><td>Tunggu session timeout</td><td>Redirect ke login 🔄</td><td>FITUR CROSS-ROLE 🔐</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-074</td><td>Password Hashing Security 🔒</td><td>Buat user → cek database</td><td>Password dalam HASH bukan plain 🛡️</td><td>FITUR CROSS-ROLE 🔐</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-075</td><td>Legacy Read-Only 👀</td><td>Cek koneksi &amp; fungsi Firebird</td><td>Hanya READ dari Firebird, TIDAK WRITE 🚫✍️</td><td>FITUR CROSS-ROLE 🗄️</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-076</td><td>Print Labels 🏷️</td><td>Print individual/collection/all</td><td>Semua labels tercetak 🖨️</td><td>ADM, LAB, PHLEB</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>TC-077</td><td>Edit Comment ✏️</td><td>Edit comment di dashboard</td><td>Comment berubah tersimpan 💾</td><td>SU</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
</table>
<h2>📋 Next Plan 📋</h2>
<table class="data-table">
<tr><th>ID</th><th>Judul Test Case</th><th>Langkah Utama</th><th>Expected Result</th><th>Role</th><th>Hasil</th><th>Issue/Jawaban</th></tr>
<tr><td>NP-001</td><td>Collect Sample 🧪</td><td>Buka dialog sample → Collect</td><td>STATUS=1, COLLECTIONDATE &amp; USERID diset ✅</td><td>SU, ADM, LAB</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>NP-002</td><td>Un-Collect Sample ↩️</td><td>Buka dialog sample → Un-Collect</td><td>STATUS di-reset, audit log tercatat 📝</td><td>SU, ADM, LAB</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
<tr><td>NP-004</td><td>Sample Collection Buttons Enabled ✅</td><td>Buka dialog sample</td><td>Tombol Collect/Un-Coll/Un-Recv enabled 🔘</td><td>ADMIN</td><td><input type="checkbox"><br> <input type="checkbox"></td><td><input type="text" value="___________"></td></tr>
</table>
</div>
</body>

View File

@ -84,6 +84,7 @@ $routes->group('lab', ['filter' => 'role:2'], function ($routes) {
$routes->group('phlebo', ['filter' => 'role:3'], function ($routes) {
$routes->get('', 'Pages\PhlebotomistController::index');
$routes->get('collect', 'Pages\PhlebotomistController::collect');
});
$routes->group('cs', ['filter' => 'role:4'], function ($routes) {

View File

@ -51,7 +51,7 @@ class AuthController extends BaseController
return redirect()->to('login');
}
} else {
$session->setFlashdata('error', 'USERID atau PASSWORD salah.');
$session->setFlashdata('error', 'Invalid User ID or Password.');
return redirect()->back();
}
}

View File

@ -18,4 +18,14 @@ class PhlebotomistController extends BaseController
return view('phlebo/index', ['roleConfig' => $config['phlebo']]);
}
public function collect()
{
$config = require APPPATH . 'Views/shared/config.php';
return view('phlebo/collect', [
'roleConfig' => $config['phlebo']
]);
}
}

View File

@ -6,7 +6,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Not Found</title>
<link href="<?= base_url('css/daisyui.min.css'); ?>" rel="stylesheet" type="text/css" />
<script src="<?= base_url('js/tailwind.min.js'); ?>"></script>
<script src="<?= base_url('css/tailwind.min.js'); ?>"></script>
<link href="<?= base_url('css/themes.min.css'); ?>" rel="stylesheet" type="text/css" />
<script src="<?= base_url('js/fontawesome.min.js'); ?>"></script>
<style>

View File

@ -6,7 +6,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Unauthorized</title>
<link href="<?= base_url('css/daisyui.min.css'); ?>" rel="stylesheet" type="text/css" />
<script src="<?= base_url('js/tailwind.min.js'); ?>"></script>
<script src="<?= base_url('css/tailwind.min.js'); ?>"></script>
<link href="<?= base_url('css/themes.min.css'); ?>" rel="stylesheet" type="text/css" />
<script src="<?= base_url('js/fontawesome.min.js'); ?>"></script>
<style>

View File

@ -22,6 +22,12 @@
</div>
<h2 class="card-title text-2xl font-semibold text-base-content mb-1">CMOD</h2>
<p class="mb-6 text-sm text-base-content/70">Sign in to continue</p>
<?php if (session()->getFlashdata('error')): ?>
<div class="alert alert-error text-sm">
<i class="fa fa-exclamation-circle"></i>
<?= esc(session()->getFlashdata('error')) ?>
</div>
<?php endif; ?>
<form method="post" action="<?= base_url('login') ?>" class="w-full flex flex-col gap-4">
<div class="form-control">
<label class="input input-bordered flex items-center gap-2 w-full">

View File

@ -0,0 +1,341 @@
<?php
$config = include __DIR__ . '/../shared/config.php';
$roleConfig = $config['phlebo'];
?>
<?= $this->extend('shared/layout', ['roleConfig' => $roleConfig]); ?>
<?= $this->section('content'); ?>
<main class="p-2 flex-1 flex flex-col items-center" x-data="collectionPage">
<div class="card bg-base-100 shadow-lg border border-base-200 w-full max-w-4xl">
<div class="card-body p-4">
<div class="flex items-center justify-between mb-3">
<h2 class="card-title text-xl font-bold flex items-center gap-2">
<i class="fa fa-vial text-primary"></i> Specimen Collection
</h2>
<div class="text-xs text-base-content/60">
<i class="fa fa-user"></i> <?= esc(session('userid')) ?>
</div>
</div>
<div class="form-control mb-3">
<label class="label py-1">
<span class="label-text text-sm font-medium">Access Number</span>
</label>
<div class="join w-full">
<input type="text"
x-model="accessnumber"
@keyup.enter="fetchPatientData()"
@keydown.esc="resetForm()"
placeholder="Scan or type access number and press Enter..."
class="input input-bordered input-sm join-item w-full"
x-ref="accessInput"
autofocus>
<button class="btn btn-primary btn-sm join-item" @click="fetchPatientData()">
<i class="fa fa-search"></i>
</button>
</div>
</div>
<template x-if="isLoading">
<div class="text-center py-6">
<span class="loading loading-spinner loading-md text-primary"></span>
<p class="mt-1 text-sm">Loading...</p>
</div>
</template>
<template x-if="patient.name && !isLoading">
<div class="space-y-3">
<div class="bg-base-200 p-3 rounded-lg">
<div class="flex items-center justify-between mb-2">
<h3 class="font-bold text-sm text-primary">Patient Information</h3>
<span class="text-xs badge badge-sm" :class="patient.sex === 'M' ? 'badge-info' : 'badge-secondary'" x-text="patient.sex"></span>
</div>
<div class="grid grid-cols-2 gap-2 text-xs">
<div class="truncate">
<span class="text-base-content/60">Name:</span>
<span class="font-medium" x-text="patient.name"></span>
</div>
<div>
<span class="text-base-content/60">Age:</span>
<span class="font-medium" x-text="patient.age + 'Y'"></span>
</div>
<div class="col-span-2">
<span class="text-base-content/60">Access:</span>
<span class="font-mono font-medium" x-text="patient.accessnumber"></span>
</div>
</div>
<div x-show="patient.comment" class="mt-1 text-xs">
<span class="text-base-content/60">Note:</span>
<span class="font-medium" x-text="patient.comment"></span>
</div>
</div>
<div class="flex items-center justify-between mb-1">
<h3 class="font-bold text-sm">Required Tubes</h3>
<div class="flex gap-1">
<button class="btn btn-xs btn-ghost" @click="selectAll()" x-show="hasUncollectedSamples()">
<i class="fa fa-check-square"></i> All
</button>
<button class="btn btn-xs btn-ghost" @click="deselectAll()" x-show="hasSelectedSamples()">
<i class="fa fa-square-o"></i> None
</button>
</div>
</div>
<div class="overflow-x-auto border rounded-lg">
<table class="table table-xs w-full">
<thead class="bg-base-200">
<tr>
<th class="w-10 text-center p-2">
<input type="checkbox"
class="checkbox checkbox-xs checkbox-primary"
@change="toggleAll($event.target.checked)"
:checked="allSelected()"
:indeterminate="someSelected()">
</th>
<th class="p-2">Code</th>
<th class="p-2">Tube</th>
<th class="w-20 text-center p-2">Status</th>
</tr>
</thead>
<tbody>
<template x-for="sample in samples" :key="sample.sampcode">
<tr :class="sample.colstatus == 1 ? 'bg-success/10' : (sample.selected ? 'bg-warning/10' : '')">
<td class="text-center p-2">
<input type="checkbox"
class="checkbox checkbox-xs checkbox-primary"
x-model="sample.selected"
:checked="sample.colstatus == 1"
:disabled="sample.colstatus == 1">
</td>
<td class="font-mono text-xs p-2" x-text="sample.sampcode"></td>
<td class="text-xs p-2" x-text="sample.name"></td>
<td class="text-center p-2">
<span x-show="sample.colstatus == 1" class="badge badge-success badge-xs">Done</span>
<span x-show="sample.colstatus != 1 && sample.selected" class="badge badge-warning badge-xs">Ready</span>
<span x-show="sample.colstatus != 1 && !sample.selected" class="badge badge-ghost badge-xs">-</span>
</td>
</tr>
</template>
</tbody>
</table>
</div>
<div class="form-control">
<label class="label py-1">
<span class="label-text text-xs">Comment</span>
</label>
<input type="text"
x-model="comment"
placeholder="Add comment..."
class="input input-bordered input-sm w-full">
</div>
<div class="flex items-center justify-between pt-2 border-t">
<div class="text-xs text-base-content/60">
<span x-text="getSelectedCount()"></span> of <span x-text="samples.length"></span> selected
</div>
<div class="flex gap-2">
<button class="btn btn-sm btn-ghost" @click="resetForm()">
<i class="fa fa-refresh"></i> Clear
</button>
<button class="btn btn-sm btn-primary"
@click="saveCollection()"
:disabled="isSaving || !hasSelectedSamples()"
:class="isSaving ? 'loading' : ''">
<i class="fa fa-save mr-1" x-show="!isSaving"></i>
<span x-text="isSaving ? 'Saving' : 'Save'"></span>
</button>
</div>
</div>
</div>
</template>
<template x-if="!patient.name && !isLoading && searched">
<div class="alert alert-warning alert-sm py-2">
<i class="fa fa-exclamation-triangle"></i>
<span class="text-sm">No patient found</span>
</div>
</template>
</div>
</div>
</main>
<?= $this->endSection(); ?>
<?= $this->section('script'); ?>
<script type="module">
import Alpine from '<?= base_url("js/app.js"); ?>';
document.addEventListener('alpine:init', () => {
Alpine.data('collectionPage', () => ({
userid: '<?= session("userid") ?>',
accessnumber: '',
patient: {},
samples: [],
comment: '',
isLoading: false,
isSaving: false,
searched: false,
init() {
this.$nextTick(() => {
this.$refs.accessInput.focus();
});
},
async fetchPatientData() {
if (!this.accessnumber.trim()) return;
this.isLoading = true;
this.searched = true;
try {
const response = await fetch(`${BASEURL}/api/samples/${this.accessnumber}`, {
method: 'GET',
headers: { 'Content-Type': 'application/json' }
});
const data = await response.json();
if (data.data) {
this.patient = {
name: data.data.patname || '',
age: data.data.age || '',
sex: data.data.gender || '',
accessnumber: data.data.accessnumber || this.accessnumber,
comment: data.data.comment || ''
};
this.comment = data.data.comment || '';
this.samples = (data.data.samples || []).map(s => ({
...s,
selected: s.colstatus == 1
}));
} else {
this.patient = {};
this.samples = [];
}
} catch (error) {
console.error('Error:', error);
this.showToast('Error loading data', 'error');
} finally {
this.isLoading = false;
}
},
hasSelectedSamples() {
return this.samples.some(s => s.selected && s.colstatus != 1);
},
hasUncollectedSamples() {
return this.samples.some(s => s.colstatus != 1 && !s.selected);
},
getSelectedCount() {
return this.samples.filter(s => s.selected && s.colstatus != 1).length;
},
allSelected() {
const uncollected = this.samples.filter(s => s.colstatus != 1);
return uncollected.length > 0 && uncollected.every(s => s.selected);
},
someSelected() {
const uncollected = this.samples.filter(s => s.colstatus != 1);
const selectedCount = uncollected.filter(s => s.selected).length;
return selectedCount > 0 && selectedCount < uncollected.length;
},
toggleAll(checked) {
this.samples.forEach(s => {
if (s.colstatus != 1) {
s.selected = checked;
}
});
},
selectAll() {
this.samples.forEach(s => {
if (s.colstatus != 1) s.selected = true;
});
},
deselectAll() {
this.samples.forEach(s => {
if (s.colstatus != 1) s.selected = false;
});
},
async saveCollection() {
this.isSaving = true;
try {
const samplesToCollect = this.samples.filter(s => s.selected && s.colstatus != 1);
for (const sample of samplesToCollect) {
await fetch(`${BASEURL}/api/samples/collect/${this.patient.accessnumber}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
samplenumber: sample.sampcode,
userid: this.userid
})
});
}
if (this.comment !== this.patient.comment) {
await this.saveComment();
}
this.showToast(`Collected ${samplesToCollect.length} sample(s)`, 'success');
setTimeout(() => {
this.resetForm();
}, 1000);
} catch (error) {
console.error('Error:', error);
this.showToast('Save failed', 'error');
} finally {
this.isSaving = false;
}
},
async saveComment() {
try {
await fetch(`${BASEURL}/api/requests/comment/${this.patient.accessnumber}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
comment: this.comment,
userid: this.userid
})
});
} catch (error) {
console.error('Error saving comment:', error);
}
},
resetForm() {
this.accessnumber = '';
this.patient = {};
this.samples = [];
this.comment = '';
this.searched = false;
this.$nextTick(() => {
this.$refs.accessInput.focus();
});
},
showToast(message, type = 'success') {
const toast = document.createElement('div');
toast.className = `alert alert-${type} fixed top-4 right-4 z-50 alert-sm`;
toast.innerHTML = `<i class="fa ${type === 'error' ? 'fa-times-circle' : 'fa-check-circle'}"></i> ${message}`;
document.body.appendChild(toast);
setTimeout(() => toast.remove(), 2500);
}
}));
});
Alpine.start();
</script>
<?= $this->endSection(); ?>

View File

@ -56,11 +56,12 @@ return [
'title' => 'Phlebotomist Dashboard',
'sampleDialog' => [
'commentEditable' => false,
'showCollectButtons' => false,
'showCollectButtons' => true,
'defaultPrinter' => 'phlebo',
],
'menuItems' => [
['label' => 'Dashboard', 'href' => 'phlebo', 'icon' => 'chart-bar'],
['label' => 'Collect Samples', 'href' => 'phlebo/collect', 'icon' => 'vial'],
],
],
'superuser' => [

View File

@ -37,10 +37,17 @@
<!-- Report Display Area -->
<div class="flex-1 flex flex-col min-h-0">
<iframe
:src="`${BASEURL}/report/${generateAccessnumber}?eng=${generateLang}`"
class="w-full h-full border-0 rounded-lg"
style="min-height: 60vh;"></iframe>
<template x-if="generateAccessnumber">
<iframe
:src="`${BASEURL}/report/${generateAccessnumber}?eng=${generateLang}`"
class="w-full h-full border-0 rounded-lg"
style="min-height: 60vh;"></iframe>
</template>
<template x-if="!generateAccessnumber">
<div class="flex items-center justify-center h-full text-base-content/50">
<p>No report selected</p>
</div>
</template>
</div>
<!-- Footer -->

View File

@ -45,13 +45,7 @@
</tr>
<tr>
<td>Note</td>
<td colspan='3'>
<textarea x-text="item.comment" class="textarea textarea-bordered w-full"
<?= ($config['sampleDialog']['commentEditable'] ?? true) ? '' : 'disabled' ?>></textarea>
<?php if ($config['sampleDialog']['commentEditable'] ?? true): ?>
<button class="btn btn-sm btn-primary mt-2" @click="saveComment(item.accessnumber)">Save</button>
<?php endif; ?>
</td>
<td colspan='3' x-text="item.comment"></td>
</tr>
</table>
@ -70,18 +64,11 @@
<tbody>
<tr>
<td></td>
<td></td>
<td>All</td>
<td></td>
<td></td>
<td>
<button class="btn btn-sm btn-secondary px-2 py-1" @click="printAllLabels(item.accessnumber)"><i class="fa-solid fa-print"></i></button>
<?php if ($config['sampleDialog']['showCollectButtons'] ?? true): ?>
<button class="btn btn-sm btn-success px-2 py-1" onclick="">
<h6 class="p-0 m-0">Coll.</h6>
</button>
<?php endif; ?>
</td>
</tr>
<tr>
@ -91,13 +78,7 @@
<td></td>
<td>
<button class="btn btn-sm btn-secondary px-2 py-1" @click="printCollectionLabel(item.accessnumber)"><i class="fa-solid fa-print"></i></button>
<?php if ($config['sampleDialog']['showCollectButtons'] ?? true): ?>
<button class="btn btn-sm btn-success px-2 py-1" onclick="">
<h6 class="p-0 m-0">Coll.</h6>
</button>
<?php endif; ?>
</td>
</tr>
<template x-for="sample in item.samples">