diff --git a/composables/useQueue.js b/composables/useQueue.js index 05a1445..b58b466 100644 --- a/composables/useQueue.js +++ b/composables/useQueue.js @@ -186,14 +186,15 @@ export const useQueue = (adminType = "loket", specificId = null) => { showSnackbar(result.message, result.success ? "success" : "warning"); }; - const processPatient = (patient, action) => { - const result = queueStore.processPatient(patient, action, adminType, idValue.value); + const processPatient = async (patient, action) => { + const result = await queueStore.processPatient(patient, action, adminType, idValue.value); let color = "success"; if (action === "terlambat") color = "warning"; else if (action === "pending") color = "info"; showSnackbar(result.message, color); + return result; }; // Helper function untuk mendapatkan pasien yang sedang diproses dari store @@ -293,9 +294,10 @@ export const useQueue = (adminType = "loket", specificId = null) => { }; - const processNextQueue = () => { - const result = queueStore.processNextQueue(adminType, idValue.value); + const processNextQueue = async () => { + const result = await queueStore.processNextQueue(adminType, idValue.value); showSnackbar(result.message, result.success ? "success" : "warning"); + return result; }; const getRowClass = (item) => { diff --git a/docs/DEVLOG.md b/docs/DEVLOG.md new file mode 100644 index 0000000..bd6373f --- /dev/null +++ b/docs/DEVLOG.md @@ -0,0 +1,581 @@ +# πŸ““ DEVLOG β€” Development Log + +**Project:** Web Antrean β€” Sistem Manajemen Antrean Rawat Jalan RSSA +**Author:** Akbar +**Stack:** JavaScript Β· Vue 3 Β· TypeScript Β· Nuxt 3 Β· Vuetify 3 Β· Pinia + +--- + +## 🧠 AI Prompt β€” Cara Menggunakan Dokumen Ini + +> Salin prompt berikut ke Claude untuk membantu mengisi DEVLOG harian: + +``` +Kamu adalah technical writer. Bantu aku menulis DEVLOG untuk hari ini. + +Context: +- Project: Web Antrean β€” Sistem Manajemen Antrean Rawat Jalan RSSA +- Stack: Nuxt 3, Vue 3, TypeScript, Vuetify 3, Pinia, WebSocket, Keycloak +- Yang dikerjakan hari ini: [ceritakan bebas] +- Masalah yang ditemui: [ceritakan] +- Solusi yang diterapkan: [ceritakan] + +Format output: +## [Tanggal] β€” [Judul Singkat] +**Yang dikerjakan:** ... +**Keputusan teknis:** ... +**Masalah & solusi:** ... +**Besok:** ... +``` + +--- + +## Format Entry + +```markdown +## [YYYY-MM-DD] β€” [Judul Singkat Pekerjaan] + +**Sprint/Phase:** [nama sprint atau fase] +**Durasi:** [X jam] +**Status:** βœ… Done | πŸ”„ In Progress | ⏸ Blocked + +### Yang Dikerjakan +- ... + +### Keputusan Teknis +> Jelaskan keputusan arsitektur/implementasi penting dan alasannya + +### Masalah & Solusi +| Masalah | Solusi | Referensi | +|---------|--------|-----------| +| | | | + +### Besok +- [ ] ... + +### Referensi +- [link] +``` + +--- + +## Log Entries + + + +--- + +## 2026-05-25 β€” Perbaikan & Dokumentasi Project + +**Sprint/Phase:** Phase 5 β€” Stabilisasi & Dokumentasi +**Durasi:** ~4 jam +**Status:** πŸ”„ In Progress + +### Yang Dikerjakan +- Push perbaikan berbagai bug (`ffdb88d`) +- Pembuatan dokumentasi project: PRD, QMD, DEVLOG +- Stabilisasi WebSocket sync dengan deterministic client ID +- Penambahan polling fallback 30 detik di semua admin interface + +### Keputusan Teknis +> Membuat dokumentasi lengkap (PRD, QMD, DEVLOG) untuk meningkatkan maintainability project ke depan. Semua dokumen diisi berdasarkan kondisi aktual project, bukan template kosong. + +### Masalah & Solusi +| Masalah | Solusi | Referensi | +|---------|--------|-----------| +| Dokumentasi project belum ada | Buat PRD, QMD, DEVLOG di folder `docs/` | β€” | + +### Besok +- [ ] Review dan finalisasi dokumen +- [ ] Lanjutkan stabilisasi fitur penunjang + +--- + +## 2026-05-22 β€” Stabilisasi WebSocket & Tooltips Dashboard + +**Sprint/Phase:** Phase 5 β€” Stabilisasi +**Durasi:** ~8 jam +**Status:** βœ… Done + +### Yang Dikerjakan +- Standardisasi header konfigurasi (origin & referer) di `stats-api`, `visit-api`, `klinik-api` ke `http://10.10.150.175:3000` +- Penambahan tooltips di komponen Dashboard +- Standardisasi WebSocket client ID menjadi deterministic untuk reliable cross-device messaging +- Implementasi polling fallback 30 detik di semua admin interface +- Audit semua halaman yang bergantung WebSocket + +### Keputusan Teknis +> WebSocket client ID diubah dari random ke deterministic agar server bisa menargetkan pesan ke device tertentu. Polling 30 detik ditambahkan sebagai safety net jika WebSocket disconnect β€” ini menghindari data drift antar display. + +### Masalah & Solusi +| Masalah | Solusi | Referensi | +|---------|--------|-----------| +| Header origin/referer tidak konsisten antar proxy route | Standardisasi ke `http://10.10.150.175:3000` di semua route handler | `server/routes/` | +| Data antrean tidak sinkron antar display saat WS drop | Polling fallback 30 detik + deterministic WS client ID | `useWebSocket.ts` | + +--- + +## 2026-05-21 β€” Perbaikan Layar Info & Isolasi Loket + +**Sprint/Phase:** Phase 5 β€” Stabilisasi +**Durasi:** ~6 jam +**Status:** βœ… Done + +### Yang Dikerjakan +- Fix: info klinik ruang tidak muncul di layar informasi (`ec9dac0`) +- Isolasi state `currentProcessingPatient` menggunakan unique storage key per loket +- Perbaikan `processNextQueue` dan `callNext` β€” strict filter berdasarkan `loketId` +- Validasi WebSocket event handler agar tidak ada update lintas loket yang tidak sah +- Memastikan `allPatients` tetap single source of truth + +### Keputusan Teknis +> Masalah kritis: loket A memproses pasien loket B karena `currentProcessingPatient` menggunakan key yang sama. Solusi: setiap loket mendapat unique persisted state key (`currentPatient_loket_{id}`). + +### Masalah & Solusi +| Masalah | Solusi | Referensi | +|---------|--------|-----------| +| Cross-loket interference pada `currentProcessingPatient` | Unique storage key per loket di persisted state | `queueStore.js` | +| `processNextQueue` memproses pasien dari loket lain | Strict filter berdasarkan `loketId` dan service mapping | `useQueue.js` | +| WebSocket event mengupdate loket yang salah | Guard check di event handler | `useWebSocket.ts` | + +--- + +## 2026-05-20 β€” Subspesialis & Perbaikan Bug Loket + +**Sprint/Phase:** Phase 4 β€” Fitur Eksekutif +**Durasi:** ~7 jam +**Status:** βœ… Done + +### Yang Dikerjakan +- Penambahan fitur subspesialis di PatientCard (`1446e87`) +- Display subspesialis sebagai pill/chip di samping status badge +- Perbaikan bug tampilan loket (`f622052`) +- UI enhancement: PatientCard menampilkan info ruang/subspesialis secara kondisional + +### Keputusan Teknis +> Subspesialis ditampilkan sebagai pill/chip di PatientCard agar admin klinik bisa langsung melihat spesialisasi pasien tanpa perlu buka detail. Conditional rendering β€” tampilkan ruang ATAU subspesialis, tergantung data yang tersedia. + +### Masalah & Solusi +| Masalah | Solusi | Referensi | +|---------|--------|-----------| +| Info subspesialis tidak tampil di card pasien | Tambah conditional pill/chip di `PatientCard.vue` | `components/features/queue/PatientCard.vue` | +| Bug tampilan loket | Fix layout dan data binding | `pages/AdminLoket.vue` | + +--- + +## 2026-05-19 β€” Docker & Anjungan Eksekutif + +**Sprint/Phase:** Phase 4 β€” Fitur Eksekutif & Deployment +**Durasi:** ~8 jam +**Status:** βœ… Done + +### Yang Dikerjakan +- Penambahan Dockerfile dan docker-compose untuk deployment (`bc74b98`) +- Refinement UI anjungan eksekutif β€” pilihan subspesialis +- Peningkatan kontras dan visibilitas button seleksi (border tebal, shadow, hover state) +- Implementasi handler function untuk selection state yang robust + +### Keputusan Teknis +> Docker ditambahkan untuk standardisasi deployment. Anjungan eksekutif mendapat UI khusus dengan radio-style button yang lebih intuitif β€” target user adalah pasien dengan tech level rendah, jadi kontras dan ukuran harus maksimal. + +### Masalah & Solusi +| Masalah | Solusi | Referensi | +|---------|--------|-----------| +| Deployment manual dan tidak konsisten | Dockerfile + docker-compose | `Dockerfile`, `docker-compose.yml` | +| Button seleksi subspesialis kurang kontras | Thicker border, deeper shadow, distinct hover/selected state | Anjungan pages | + +--- + +## 2026-05-18 β€” Optimasi Performa & Memory + +**Sprint/Phase:** Phase 5 β€” Stabilisasi +**Durasi:** ~6 jam +**Status:** βœ… Done + +### Yang Dikerjakan +- Perbaikan memory usage dan load performance (`cb3310b`) +- Verifikasi status kunjungan operasi +- Pengecekan semua API endpoint yang sudah terintegrasi +- Pembersihan `console.log` verbose di high-frequency path + +### Keputusan Teknis +> Mengurangi memory footprint dengan: (1) membersihkan verbose logging di WebSocket handler dan QR scanner, (2) implementasi blacklist untuk endpoint yang terus-menerus gagal agar tidak spam request. + +### Masalah & Solusi +| Masalah | Solusi | Referensi | +|---------|--------|-----------| +| `doctorStore.js` terus retry endpoint yang 500 | Implementasi blacklist endpoint gagal | `stores/doctorStore.js` | +| `console.log` verbose di `useWebSocket.ts` | Cleanup logging di high-frequency path | `composables/useWebSocket.ts` | +| Memory usage terus naik | Reduce excessive API retry + cleanup logging | β€” | + +--- + +## 2026-04-20 β€” Update Hak Akses & Perbaikan Tiket + +**Sprint/Phase:** Phase 3 β€” Hak Akses & Permission +**Durasi:** ~6 jam +**Status:** βœ… Done + +### Yang Dikerjakan +- Update sistem hak akses berbasis Keycloak role & group (`2bf00ef`) +- Perbaikan API pembuatan tiket antrean +- Standardisasi user role slugs dari Keycloak groups +- Implementasi slugification utility untuk role normalization + +### Keputusan Teknis +> Role dari Keycloak group path (`/Instalasi STIM/Devops/Superadmin`) di-slugify menjadi URL-friendly (`instalasi-stim-devops-superadmin`) untuk konsistensi di permission API dan internal user data. + +### Masalah & Solusi +| Masalah | Solusi | Referensi | +|---------|--------|-----------| +| Role dari Keycloak tidak URL-friendly | Implementasi slugification utility | `composables/useHakAkses.ts` | +| Tiket antrean gagal digenerate | Perbaikan API endpoint pembuatan tiket | `server/api/` | + +--- + +## 2026-04-15 β€” Update Favicon + +**Sprint/Phase:** Phase 3 β€” Polish +**Durasi:** ~1 jam +**Status:** βœ… Done + +### Yang Dikerjakan +- Ganti favicon aplikasi web-antrean +- Update konfigurasi `app.head` di `nuxt.config.ts` untuk mengarah ke file icon baru + +### Keputusan Teknis +> Favicon diletakkan di folder `public/` dan direferensi via `nuxt.config.ts` β†’ `app.head.link`. + +--- + +## 2026-04-13 β€” Integrasi Keycloak Role Access + +**Sprint/Phase:** Phase 3 β€” Hak Akses & Permission +**Durasi:** ~8 jam +**Status:** βœ… Done + +### Yang Dikerjakan +- Verifikasi dan standardisasi akses Keycloak role +- Implementasi middleware `auth.ts`, `guest.ts`, `permissions.ts`, `checkPageAccess.ts` +- Setup permission store (`permissionStore.ts`) +- Integrasi permission API endpoint (`server/api/permission.get.ts`) + +### Keputusan Teknis +> Menggunakan 4 layer middleware: (1) `auth.ts` β€” cek login, (2) `guest.ts` β€” redirect jika sudah login, (3) `permissions.ts` β€” cek hak akses halaman, (4) `checkPageAccess.ts` β€” validasi granular per page. Model ini memastikan defense-in-depth untuk access control. + +### Masalah & Solusi +| Masalah | Solusi | Referensi | +|---------|--------|-----------| +| Role dari Keycloak format path, bukan slug | Konversi ke slug untuk matching di permission API | `middleware/permissions.ts` | + +--- + +## 2026-02-24 β€” Perbaikan Ambil Tiket + +**Sprint/Phase:** Phase 2 β€” Fitur Inti +**Durasi:** ~4 jam +**Status:** βœ… Done + +### Yang Dikerjakan +- Fix bug pengambilan tiket antrean di anjungan (`94ff9f5`) +- Perbaikan flow generate nomor antrean + +### Masalah & Solusi +| Masalah | Solusi | Referensi | +|---------|--------|-----------| +| Tiket gagal digenerate pada kondisi tertentu | Fix logic generate nomor antrean | `composables/useQRGenerator.ts` | + +--- + +## 2026-02-19 β€” Implementasi Anjungan (Kiosk) & Queue Store + +**Sprint/Phase:** Phase 2 β€” Fitur Inti +**Durasi:** ~10 jam +**Status:** βœ… Done + +### Yang Dikerjakan +- Implementasi modul Anjungan (Kiosk) lengkap (`0c4019c`) + - Halaman pilih klinik, klinik ruang, penunjang + - Halaman antrian per klinik, klinik ruang, penunjang + - Check-in pasien via QR + - Admin anjungan +- Implementasi Pinia store baru untuk queue management (`4342cdc`) + - Integrasi dengan Visit API dan Antrian API + - WebSocket integration untuk real-time update + - Halaman admin dan kiosk display + +### Keputusan Teknis +> `queueStore.js` dibuat sebagai central store untuk semua operasi antrean β€” ini menjadi single source of truth untuk `allPatients`. Keputusan ini mempermudah sinkronisasi real-time tapi membuat file menjadi sangat besar (saat ini 141KB). Perlu dipecah di masa depan. + +--- + +## 2026-02-18 β€” Core Anjungan Display & Verification + +**Sprint/Phase:** Phase 2 β€” Fitur Inti +**Durasi:** ~8 jam +**Status:** βœ… Done + +### Yang Dikerjakan +- Implementasi core display system anjungan (`7d07a4f`) + - Display antrean klinik ruang + - Display antrean masuk + - Display antrean loket +- Tambah verification store dan konfigurasi endpoint baru (`f78bbea`) + - `verificationApiBaseUrl` β†’ `http://10.10.123.140:8089/api/v1` + - `ekstrakExpertiseUrl` endpoint +- Fix wsBaseUrl fallback di Nuxt config (`d03fa63`) + +### Keputusan Teknis +> Dual backend API architecture: Visit API (port 8084) untuk data kunjungan utama, Antrian API (port 8089) untuk verifikasi dan data klinik/dokter. Proxy layer Nitro digunakan untuk bypass CORS dan menyembunyikan IP backend dari client. + +--- + +## 2026-02-13 β€” Admin Loket, Klinik Ruang & Queue Management + +**Sprint/Phase:** Phase 2 β€” Fitur Inti +**Durasi:** ~10 jam +**Status:** βœ… Done + +### Yang Dikerjakan +- Implementasi halaman Admin Loket detail (`8c6b3fd`) + - Manajemen antrian pasien per loket + - Aksi pasien: panggil, skip, recall, selesai + - Dialog seleksi (pilih loket, pilih layanan) +- Implementasi `AdminKlinikRuang` page (`67a5514`) + - Manajemen antrian per klinik ruang + - Processing, calling, filtering, global search +- Implementasi komprehensif patient queue management (`dbc9054`) + - Pinia store untuk queue + - API integration + - Admin counter page +- Tambah komponen `PatientCard`, `CurrentPatientCard` (`95ff830`) +- Fitur queue management untuk klinik ruang dan counter (`4df13cb`) + +### Keputusan Teknis +> Memisahkan queue components menjadi 4 komponen: `PatientCard` (card per pasien), `CurrentPatientCard` (pasien sedang dilayani), `QueueActionsCard` (tombol aksi), `TabelPatientData` (tabel lengkap). Ini memastikan reusability di halaman admin loket, klinik, dan penunjang. + +--- + +## 2026-02-12 β€” Anjungan Pages, WebSocket & Queue API + +**Sprint/Phase:** Phase 2 β€” Fitur Inti +**Durasi:** ~10 jam +**Status:** βœ… Done + +### Yang Dikerjakan +- Introduce halaman Anjungan untuk antrian klinik, klinik ruang, dan counter (`a5ab338`) +- Tambah halaman check-in pasien +- Implementasi core queue management system dengan WebSocket (`0e3ee37`) + - Pinia store untuk queue + - WebSocket integration + - Halaman check-in dan kiosk display +- Update post API klinik dan loket di queueStore (`4985aef`) +- Fix status pasien selesai di klinik ruang (`c08941a`) +- Various merge dan bugfix + +### Keputusan Teknis +> WebSocket dipilih sebagai primary communication channel untuk real-time sync karena latensi < 2 detik krusial di rumah sakit β€” pasien harus langsung melihat nomor panggil berubah di layar display. + +--- + +## 2026-02-10–11 β€” WebSocket Bug Fixing + +**Sprint/Phase:** Phase 2 β€” Stabilisasi WebSocket +**Durasi:** ~12 jam (2 hari) +**Status:** βœ… Done + +### Yang Dikerjakan +- Fix bug WebSocket di check-in loket (`b6dc252`) +- Fix socket check-in flow (`e686dda`) +- Update socket data dan token handling (`fb70237`) +- Fix duplikasi data dan ticket menghilang di admin klinik loket (`c02905e`) +- Update WS untuk admin loket dan check-in (`a7a654b`) +- Fix missing function error (`23164bc`) +- Perbaikan sorting pasien di next process (`6af3f58`) + +### Masalah & Solusi +| Masalah | Solusi | Referensi | +|---------|--------|-----------| +| Duplikasi tiket di admin klinik | Fix logic deduplication di WebSocket handler | `useWebSocket.ts` | +| Tiket menghilang setelah check-in | Fix state update flow β€” pastikan re-render setelah WS event | `queueStore.js` | +| Socket check-in tidak trigger update | Fix event listener binding | `composables/useCheckIn.ts` | + +--- + +## 2026-02-09 β€” Integrasi WebSocket + +**Sprint/Phase:** Phase 2 β€” Real-time +**Durasi:** ~6 jam +**Status:** βœ… Done + +### Yang Dikerjakan +- Setup dan implementasi WebSocket client (`101dc38`) +- Koneksi ke `ws://10.10.123.135:8084/api/v1/ws` +- Implementasi `useWebSocket.ts` composable + +### Keputusan Teknis +> Menggunakan native browser WebSocket API dibungkus composable (`useWebSocket.ts`) daripada library seperti `socket.io` β€” mengurangi dependency dan lebih ringan untuk environment LAN internal. + +--- + +## 2026-02-02–06 β€” API Integration & Display Screen + +**Sprint/Phase:** Phase 2 β€” Integrasi API +**Durasi:** ~20 jam (5 hari) +**Status:** βœ… Done + +### Yang Dikerjakan +- Update tampilan layar screen display (`cf54ded`) +- Penambahan API klinik ruang (`a29838e`, `b327997`, `c2d9023`) +- Update logika pemanggilan admin loket by payment (`efeb42e`) +- Fix HTTPS implementation dan check-in (`c899a71`) +- Update session dan WebSocket (`0428017`) +- Update status klinik ruang (`6696881`) +- Update tampilan anjungan (`329ac9c`) +- Fixing admin loket dan blokade loket yang tidak tersedia (`5d139c8`) +- Update verifikasi dan lainnya (`2838016`) +- Update post API status dan data pasien ruang (`e2a5d43`) + +### Keputusan Teknis +> Logika pemanggilan admin loket diubah menjadi payment-based β€” pasien yang sudah bayar diprioritaskan. Loket yang tidak tersedia di-blokade otomatis agar petugas tidak melihat loket yang bukan tanggung jawabnya. + +--- + +## 2026-01-28–30 β€” Master Data & API Queue Store + +**Sprint/Phase:** Phase 1 β€” Fondasi +**Durasi:** ~12 jam (3 hari) +**Status:** βœ… Done + +### Yang Dikerjakan +- Update master data berdasarkan jenis layanan (`9f0f6a7`) +- Update API antrean masuk dan minor changes (`507f415`) +- Fix session dan tampilan screen (`8dd94ed`) +- Fix loop dan duplikasi data (`0bd5311`) +- Update fetch API (`cccefb0`) +- Update layout semua halaman (`ae8b06d`) +- Update queueStore dari API (`19633af`) +- Minor update anjungan (`59e42f3`) + +--- + +## 2026-01-22–27 β€” Admin Loket & API Integrasi + +**Sprint/Phase:** Phase 1 β€” Fondasi +**Durasi:** ~16 jam (4 hari) +**Status:** βœ… Done + +### Yang Dikerjakan +- Update logika penarikan API dan tampilan anjungan (`e900c5a`) +- Update No RM dan desain (`2ccb378`) +- Update API master loket dan antrian loket (`75a9638`) +- Update anjungan (`8de89cb`) +- Update master klinik ruang (`6c08352`) +- Update sidebar check-in dan antrian loket (`525322b`) +- Update API loket admin (`083fe3e`) +- Push update admin loket (`f606045`) + +--- + +## 2026-01-19–21 β€” Layout, Sidebar, Dashboard & Monitoring + +**Sprint/Phase:** Phase 1 β€” Fondasi +**Durasi:** ~20 jam (3 hari) +**Status:** βœ… Done + +### Yang Dikerjakan +- Update flow klinik ruang dan design consistency (`cbd8f44`) +- Update sidebar, profile, dan loket (`f2efd83`) +- Update loket dan monitoring (`81b877b`, `c00c18e`) +- Update page header komponen (`00bb954`) +- Sidebar change (`3db912a`) +- Update header, card, dialog (`fde7111`, `531ca10`, `f7a3b20`) +- Perubahan update sidebar, dashboard, monitoring, antrean loket, verifikasi (`bb71955`) +- Update check-in tampilan dan function (`482294b`) + +### Keputusan Teknis +> Layout system menggunakan Vuetify navigation drawer (`SideBar.vue`) + custom `PageHeader.vue`. Design consistency diterapkan via SCSS variables (`_variables.scss`, `_colors.scss`) untuk warna, spacing, dan typography. + +--- + +## 2026-01-15 β€” Login Page, Check-in & Antrean Masuk + +**Sprint/Phase:** Phase 1 β€” Fondasi +**Durasi:** ~8 jam +**Status:** βœ… Done + +### Yang Dikerjakan +- Implementasi login page dan check-in (`e99a0ab`) +- Update color palette dan konsultasi pasien (`a8a55b9`) +- Tampilan antrean masuk (`ed57e2d`) +- Edit manual check-in (`928be7c`) +- Update logika pemanggilan, sticky konten, tampilan card (`96c6376`) +- Perbaikan generate dan scan tiket QR (`8aa5ab2`) + +### Keputusan Teknis +> QR code digunakan untuk tiket antrean β€” pasien mendapat QR saat ambil antrean, lalu scan QR saat check-in. Library `html5-qrcode` dipilih untuk scanner dan `qrcode.vue` untuk generator. + +--- + +## 2026-01-12–14 β€” Loket, Klinik Ruang & Fast Track + +**Sprint/Phase:** Phase 1 β€” Fondasi +**Durasi:** ~16 jam (3 hari) +**Status:** βœ… Done + +### Yang Dikerjakan +- Update layout klinik dan WebSocket layar klinik (`6e7160c`) +- Update klinik ruang dan status fast track (`27209de`) +- Perubahan tiket dan alur antrean (`5c14227`) +- Push loket baru (`42fe62c`) +- Loket perbaikan "sedang dilayani" (`48e4aab`) +- Anjungan fast track dan tampilan ruang (`3817b2d`) +- Perbaikan loket dan total antrean (`a000ba0`, `0a8e11a`, `9ccbaba`) +- Check-in tampilan (`0c7b33a`) +- Fixing layar loket dan status pasien (`e80032f`) +- QRcode update (`6c80c08`) +- Update warna dan klinik ruang (`f3e90ad`) +- Perubahan antrean masuk (`c5b623f`) +- Update seed data dan fungsi pindah/konsul klinik (`89c3549`) + +--- + +## 2026-01-08–09 β€” Klinik Ruang, QR & Print + +**Sprint/Phase:** Phase 1 β€” Fondasi +**Durasi:** ~12 jam (2 hari) +**Status:** βœ… Done + +### Yang Dikerjakan +- Layar baru antrean klinik dan antrean masuk (`2d3d589`) +- Update klinik ruang (`ca5913c`) +- Nav items (`8a4bb44`) +- Update print admin loket, logika klinik ruang, numbering antrian baru (`676bdc0`) +- Update QR function (`22d7205`) +- Perubahan format No Antrean (`6d5d565`) +- Check-in footers detail (`1b7a142`) +- Klinik ruang dan update admin loket (`9ea8300`) + +### Keputusan Teknis +> Format nomor antrean: `[KodeKlinik]-[NomorUrut]` (contoh: `PDL-001`). Thermal print diimplementasi via `useThermalPrint.ts` composable untuk cetak tiket langsung dari browser ke printer thermal via Web API. + +--- + + + +--- + +## πŸ“Š Summary Stats + +| Metrik | Value | +|--------|-------| +| Total hari dev | ~50+ hari (Jan 2026 β€” Mei 2026) | +| Fase project | 5 fase (Setup β†’ Fondasi β†’ Hak Akses β†’ Eksekutif β†’ Stabilisasi) | +| Fitur selesai | 8 modul utama (Anjungan, Check-in, Loket, Klinik, Penunjang, Dashboard, Setting, Hak Akses) | +| Total commits | 80+ commits | +| Bug ditemukan | 15+ (WebSocket, loket interference, memory, display sync) | +| Bug diselesaikan | 15+ | +| Halaman dibuat | 28 halaman | +| Composables | 16 composable | +| Pinia Stores | 13 stores | +| Komponen | 20+ komponen | diff --git a/docs/DEVPLAN.md b/docs/DEVPLAN.md new file mode 100644 index 0000000..f707038 --- /dev/null +++ b/docs/DEVPLAN.md @@ -0,0 +1,234 @@ +# πŸ—ΊοΈ DEVPLAN β€” Development Plan + +**Project:** Web Antrean β€” Sistem Manajemen Antrean Rawat Jalan RSSA +**Version:** 1.0.0 +**Author:** Akbar +**Stack:** JavaScript Β· Vue 3 Β· TypeScript Β· Nuxt 3 Β· Vuetify 3 Β· Pinia +**Last Updated:** 2026-05-25 + +--- + +## 🧠 AI Prompt β€” Cara Menggunakan Dokumen Ini + +> Salin prompt berikut ke Claude untuk membantu membuat dev plan: + +``` +Kamu adalah tech lead senior fullstack (Nuxt 3, Vue 3, TypeScript). + +Project: Web Antrean β€” Sistem Manajemen Antrean Rawat Jalan RSSA +Deskripsi: Digitalisasi alur antrean pasien rawat jalan di rumah sakit +Deadline: Mei 2026 (Go-Live) +Tim: 1 Fullstack Engineer (Akbar), PO Tim RSSA + +Fitur yang harus dibangun: +1. Anjungan mandiri (kiosk registrasi pasien, pilih klinik/subspesialis) +2. Check-in pasien via QR code +3. Manajemen antrean loket & display screen +4. Manajemen antrean klinik & ruang +5. Manajemen antrean penunjang +6. Real-time sync via WebSocket +7. Dashboard monitoring & statistik +8. Manajemen user, master data, dan hak akses (Keycloak) + +Buatkan: +1. Breakdown fase development (Phase 0 s/d launch) +2. Task list per fase dengan estimasi +3. Urutan prioritas fitur (MoSCoW) +4. Technical dependencies antar task +5. Milestone dan checkpoint + +Format dalam tabel Markdown yang terstruktur. +``` + +--- + +## 1. Project Timeline + +``` +[Start]──Phase 0──Phase 1──Phase 2──Phase 3──Phase 4──Phase 5──[Launch] + Setup Fondasi Inti API Hak Akses Eksekutif Stabilisasi +``` + +| Fase | Nama | Durasi | Target | Status | +|------|------|--------|--------|--------| +| Phase 0 | Setup & Arsitektur | 1 Minggu | Awal Jan 2026 | βœ… Done | +| Phase 1 | Fondasi (Loket, Klinik, QR) | 3 Minggu | Jan 2026 | βœ… Done | +| Phase 2 | Fitur Inti (Anjungan, WS, API) | 3 Minggu | Feb 2026 | βœ… Done | +| Phase 3 | Hak Akses & Keycloak | 2 Minggu | Apr 2026 | βœ… Done | +| Phase 4 | Fitur Eksekutif & Docker | 1 Minggu | Pertengahan Mei | βœ… Done | +| Phase 5 | Stabilisasi & Dokumentasi | 2 Minggu | Akhir Mei 2026 | πŸ”„ In Progress | + +--- + +## 2. Feature Prioritization (MoSCoW) + +| Priority | Fitur | Estimasi | Phase | +|----------|-------|----------|-------| +| πŸ”΄ Must Have | Modul Anjungan (Pilih klinik, ambil tiket, cetak) | 40 jam | 2 | +| πŸ”΄ Must Have | Modul Loket (Panggil, skip, recall, next) | 30 jam | 1 & 2 | +| πŸ”΄ Must Have | Check-in via QR Code | 20 jam | 1 | +| πŸ”΄ Must Have | Sinkronisasi Real-time via WebSocket | 30 jam | 2 | +| πŸ”΄ Must Have | Integrasi API Eksternal (Visit API, Antrian API) | 40 jam | 2 | +| πŸ”΄ Must Have | Manajemen Hak Akses Keycloak | 25 jam | 3 | +| 🟑 Should Have | Antrean Klinik & Penunjang | 30 jam | 1 & 4 | +| 🟑 Should Have | Dashboard Monitoring & Statistik | 15 jam | 1 | +| 🟑 Should Have | Pilihan Subspesialis (Klinik Eksekutif) | 10 jam | 4 | +| 🟒 Could Have | Notifikasi Suara Panggilan (Text-to-Speech) | 15 jam | Future | +| ⬜ Won't Have | Aplikasi Mobile Native | β€” | Out of scope | +| ⬜ Won't Have | Integrasi Billing / Pembayaran Langsung | β€” | Out of scope | + +--- + +## 3. Task Breakdown + +### Phase 0 β€” Setup & Arsitektur +| Task | Estimasi | Dependencies | Status | +|------|----------|--------------|--------| +| Init Nuxt 3 + TypeScript | 2 jam | β€” | βœ… Done | +| Setup Vuetify 3 + SCSS variables | 4 jam | Task 1 | βœ… Done | +| Setup Pinia state management | 2 jam | Task 1 | βœ… Done | +| Setup Vue Router (Nuxt pages/layout) | 3 jam | Task 1 | βœ… Done | +| Konfigurasi Proxy API di `nuxt.config.ts` | 3 jam | Task 1 | βœ… Done | + +### Phase 1 β€” Fondasi (Loket, Klinik, QR) +| Task | Estimasi | Dependencies | Status | +|------|----------|--------------|--------| +| UI Layout, Sidebar, PageHeader | 8 jam | Phase 0 | βœ… Done | +| Komponen PatientCard & Tabel | 8 jam | Phase 0 | βœ… Done | +| UI Admin Loket & Admin Klinik | 12 jam | Task 2 | βœ… Done | +| Check-in Pasien UI & QR Scanner (`html5-qrcode`) | 12 jam | Phase 0 | βœ… Done | +| Generator Tiket QR (`qrcode.vue`) | 6 jam | Phase 0 | βœ… Done | +| Cetak Tiket Thermal (`useThermalPrint.ts`) | 8 jam | Phase 0 | βœ… Done | + +### Phase 2 β€” Fitur Inti (Anjungan, WS, API) +| Task | Estimasi | Dependencies | Status | +|------|----------|--------------|--------| +| Integrasi Visit API & Antrian API | 16 jam | Phase 0 | βœ… Done | +| Setup WebSocket client (`useWebSocket.ts`) | 8 jam | Task 1 | βœ… Done | +| Layar Display Antrean (Klinik & Loket) | 12 jam | Task 2 | βœ… Done | +| Modul Anjungan Mandiri (UI & Logic) | 16 jam | Task 1 | βœ… Done | +| Centralized `queueStore` | 12 jam | Task 1, 2 | βœ… Done | +| Bugfix duplikasi data & WS handling | 12 jam | Task 2, 5 | βœ… Done | + +### Phase 3 β€” Hak Akses & Keycloak +| Task | Estimasi | Dependencies | Status | +|------|----------|--------------|--------| +| Konfigurasi Keycloak SSO di frontend | 8 jam | β€” | βœ… Done | +| Middleware Auth & Guest | 4 jam | Task 1 | βœ… Done | +| CRUD Master Data (Klinik, Loket, Screen) | 12 jam | Phase 1 | βœ… Done | +| Manajemen Hak Akses User/Group (UI & API) | 12 jam | Task 1 | βœ… Done | +| Middleware Permission & Page Access Guard | 8 jam | Task 4 | βœ… Done | + +### Phase 4 β€” Fitur Eksekutif & Docker +| Task | Estimasi | Dependencies | Status | +|------|----------|--------------|--------| +| UI Pemilihan Subspesialis di Anjungan | 8 jam | Phase 2 | βœ… Done | +| Conditional display spesialis di PatientCard | 4 jam | Task 1 | βœ… Done | +| Setup Dockerfile & docker-compose | 4 jam | β€” | βœ… Done | +| Update konfigurasi environment untuk container | 4 jam | Task 3 | βœ… Done | + +### Phase 5 β€” Stabilisasi & QA +| Task | Estimasi | Dependencies | Status | +|------|----------|--------------|--------| +| Standardisasi WS Client ID (Deterministic) | 4 jam | Phase 2 | βœ… Done | +| Polling fallback 30s untuk layar display | 6 jam | Phase 2 | βœ… Done | +| Isolasi state loket (`currentProcessingPatient`) | 6 jam | Phase 2 | βœ… Done | +| Blacklist handler untuk endpoint gagal 500 | 4 jam | Phase 2 | βœ… Done | +| Dokumentasi PRD, QMD, DEVLOG, EVLOG | 8 jam | β€” | βœ… Done | +| Setup Unit Testing & E2E framework | 4 jam | β€” | πŸ”„ In Prog | + +--- + +## 4. Technical Dependencies + +```text +Nuxt 3 + TypeScript +β”œβ”€β”€ Pinia (State Management) +β”‚ └── pinia-plugin-persistedstate (localStorage) +β”œβ”€β”€ Vuetify 3 (UI Framework) +β”‚ └── Material Design Icons +β”œβ”€β”€ Nuxt Nitro (Server Engine) +β”‚ └── Server Routes (CORS Proxy API) +β”œβ”€β”€ Auth Layer +β”‚ └── Keycloak SSO (OIDC/OAuth 2.0) +β”œβ”€β”€ API Layer +β”‚ β”œβ”€β”€ Visit API (10.10.123.135:8084) +β”‚ └── Antrian API (10.10.123.140:8089) +└── Real-time Layer + └── Native Browser WebSocket +``` + +--- + +## 5. Environment Setup + +### Local Development +```bash +# Clone repo +git clone https://git.rssa.top/arie.bagus.2905/web-antrean +cd web-antrean + +# Install dependencies +npm install + +# Copy env +cp .env.example .env + +# Jalankan dev server dengan host spesifik +npm run dev +# ATAU +npm run _command_dev +``` + +### Environment Variables (.env) +```env +AUTH_ORIGIN="http://10.10.150.175:3000" +KEYCLOAK_CLIENT_ID="akbar-test" +KEYCLOAK_ISSUER="https://auth.rssa.top/realms/sandbox" +ANTRIAN_API_URL="http://10.10.123.140:8089/api/v1" +VISIT_API_URL="http://10.10.123.135:8084/api/v1" +WS_API_URL="ws://10.10.123.135:8084/api/v1/ws" +PROXY_CLIENT_ORIGIN="http://10.10.150.175:3000" +``` + +--- + +## 6. Git Strategy + +### Branch Naming +``` +main β†’ production (stable) +Antrean-Code β†’ development / staging +feature/xxx β†’ fitur baru (contoh: feature/anjungan-eksekutif) +fix/xxx β†’ bug fix (contoh: fix/ws-duplicate) +``` + +### Commit Convention +Saat ini commit message banyak menggunakan free-text (contoh: `push perbaikan`, `update bug ws`). Ke depannya, disarankan menggunakan **Conventional Commits**: +``` +feat: tambah halaman dashboard +fix: perbaiki duplikasi antrean di loket +chore: update docker config +docs: update DEVPLAN +``` + +--- + +## 7. Milestones & Checkpoints + +| Milestone | Kriteria | Target Tanggal | Status | +|-----------|----------|----------------|--------| +| 🏁 Project Kickoff | Setup Nuxt, Layout, Styling | Jan 2026 | βœ… Done | +| πŸ”Œ API & WS Connected | Data mengalir dari backend, WS sync jalan | Feb 2026 | βœ… Done | +| πŸ” Auth & Roles | Keycloak jalan, routing terlindungi middleware | Apr 2026 | βœ… Done | +| πŸ₯ Anjungan Ready | Pasien bisa check-in & ambil tiket lancar | Pertengahan Mei | βœ… Done | +| πŸ›‘οΈ Stability Pass | Tidak ada leak, isolasi loket aman, no WS drift | Akhir Mei 2026 | βœ… Done | +| πŸš€ Production Go-Live | Deployment docker di server production | Q2/Q3 2026 | πŸ”„ Pending | + +--- + +## 8. Changelog + +| Versi | Tanggal | Author | Perubahan | +|-------|---------|--------|-----------| +| 1.0.0 | 2026-05-25 | Akbar | Initial plan β€” direkonstruksi berdasarkan timeline aktual | diff --git a/docs/EVLOG.md b/docs/EVLOG.md new file mode 100644 index 0000000..5070719 --- /dev/null +++ b/docs/EVLOG.md @@ -0,0 +1,544 @@ +# 🚨 EVLOG β€” Event & Error Log + +**Project:** Web Antrean β€” Sistem Manajemen Antrean Rawat Jalan RSSA +**Author:** Akbar +**Stack:** JavaScript Β· Vue 3 Β· TypeScript Β· Nuxt 3 Β· Vuetify 3 Β· Pinia + +--- + +## 🧠 AI Prompt β€” Cara Menggunakan Dokumen Ini + +> Salin prompt berikut ke Claude untuk menganalisis dan mendokumentasikan error: + +``` +Kamu adalah senior engineer Nuxt 3 + TypeScript. Aku menemukan error berikut: + +Error message: [paste error] +Stack trace: [paste stack trace] +Context: [dimana terjadi, langkah reproduksi] +Tech: Nuxt 3, Vue 3, TypeScript, Vuetify 3, Pinia, WebSocket, Keycloak + +Bantu aku: +1. Analisis root cause error ini +2. Berikan solusi step-by-step +3. Sarankan cara mencegah error serupa +4. Format hasilnya untuk EVLOG dalam Markdown +``` + +--- + +## Severity Legend + +| Level | Icon | Deskripsi | SLA | +|-------|------|-----------|-----| +| Critical | πŸ”΄ | App down / data loss / security | < 4 jam | +| High | 🟠 | Fitur utama break | < 1 hari | +| Medium | 🟑 | Fitur minor terganggu | < 3 hari | +| Low | 🟒 | UI/kosmetik | Backlog | + +--- + +## Format Entry + +```markdown +### [EV-XXX] β€” [Judul Singkat Error/Event] + +| Field | Value | +|-------|-------| +| **ID** | EV-XXX | +| **Tanggal** | YYYY-MM-DD | +| **Severity** | πŸ”΄ Critical / 🟠 High / 🟑 Medium / 🟒 Low | +| **Environment** | Development / Staging / Production | +| **Status** | πŸ” Investigating / πŸ”§ In Fix / βœ… Resolved / ⏭ Wontfix | +| **Reporter** | Akbar | + +**Error Message:** +> [paste error message] + +**Langkah Reproduksi:** +1. ... + +**Root Cause:** +> ... + +**Solusi / Fix:** +> ... + +**Prevention:** +> ... + +**Related:** [commit / file] +``` + +--- + +## Event Log + + + +--- + +### [EV-012] β€” Info Klinik Ruang Tidak Muncul di Layar Display + +| Field | Value | +|-------|-------| +| **ID** | EV-012 | +| **Tanggal** | 2026-05-21 | +| **Severity** | 🟠 High | +| **Environment** | Development | +| **Status** | βœ… Resolved | +| **Reporter** | Akbar | + +**Error Message:** +> Layar informasi klinik ruang menampilkan data kosong β€” tidak ada info klinik ruang yang muncul. + +**Langkah Reproduksi:** +1. Buka halaman display screen klinik ruang +2. Data klinik ruang seharusnya muncul +3. Layar kosong β€” tidak ada informasi tampil + +**Root Cause:** +> Data binding untuk info klinik ruang tidak ter-update setelah fetch dari API. Kemungkinan reactive state tidak di-watch dengan benar sehingga UI tidak re-render saat data berubah. + +**Solusi / Fix:** +> Perbaikan data binding dan reactive state untuk informasi klinik ruang pada layar display. + +**Related:** Commit `ec9dac0` β€” "push perbaikan layar informasi info klinik ruang tidak muncul" + +--- + +### [EV-011] β€” Data Antrean Tidak Sinkron Antar Display Screen + +| Field | Value | +|-------|-------| +| **ID** | EV-011 | +| **Tanggal** | 2026-05-22 | +| **Severity** | πŸ”΄ Critical | +| **Environment** | Development | +| **Status** | βœ… Resolved | +| **Reporter** | Akbar | + +**Error Message:** +> Display screen di lokasi berbeda menampilkan nomor panggil yang berbeda. Admin memanggil pasien tapi layar display tidak update, atau update terlambat > 10 detik. + +**Langkah Reproduksi:** +1. Buka admin loket di PC A +2. Buka display screen di TV/monitor B +3. Panggil pasien berikutnya di PC A +4. Display di monitor B tidak menampilkan nomor panggil terbaru + +**Root Cause:** +> WebSocket client ID menggunakan random ID, sehingga server tidak bisa menargetkan pesan ke device tertentu secara reliable. Saat koneksi putus lalu reconnect, client ID berubah dan device kehilangan subscription. + +**Solusi / Fix:** +```typescript +// ❌ Before β€” random client ID +const clientId = `client_${Math.random().toString(36).substr(2, 9)}` + +// βœ… After β€” deterministic client ID berdasarkan page + device +const clientId = `${pageType}_${loketId || 'global'}_${deviceFingerprint}` +``` +Ditambahkan juga polling fallback setiap 30 detik sebagai safety net. + +**Prevention:** +> Selalu gunakan deterministic identifier untuk WebSocket client. Implementasi polling fallback untuk semua halaman yang bergantung pada real-time data. + +**Related:** Conversation `6fe234cf` β€” "Stabilizing WebSocket Queue Synchronization" + +--- + +### [EV-010] β€” Cross-Loket Interference pada Patient Processing + +| Field | Value | +|-------|-------| +| **ID** | EV-010 | +| **Tanggal** | 2026-05-21 | +| **Severity** | πŸ”΄ Critical | +| **Environment** | Development | +| **Status** | βœ… Resolved | +| **Reporter** | Akbar | + +**Error Message:** +> Loket A memproses pasien yang seharusnya milik Loket B. `currentProcessingPatient` menampilkan pasien dari loket yang salah. + +**Langkah Reproduksi:** +1. Login sebagai admin loket A di browser tab 1 +2. Login sebagai admin loket B di browser tab 2 +3. Panggil pasien di loket A +4. Loket B menampilkan pasien yang sama sebagai "sedang diproses" + +**Root Cause:** +> `currentProcessingPatient` menggunakan key persisted state yang sama untuk semua loket. Karena `pinia-plugin-persistedstate` menyimpan ke `localStorage` dengan key yang sama, operasi di satu loket menimpa state loket lain. + +**Solusi / Fix:** +```javascript +// ❌ Before β€” shared key +persist: { key: 'currentProcessingPatient' } + +// βœ… After β€” unique key per loket +persist: { key: `currentPatient_loket_${loketId}` } +``` +Ditambahkan juga strict filter di `processNextQueue` dan `callNext` berdasarkan `loketId`, serta guard check di WebSocket event handler. + +**Prevention:** +> Setiap state yang bersifat per-instance (per loket, per klinik) HARUS menggunakan unique key. Jangan pernah share persisted state key antar instance. + +**Related:** Conversation `7a37e693` β€” "Isolating Loket Queue Operations" + +--- + +### [EV-009] β€” Bug Tampilan Loket + +| Field | Value | +|-------|-------| +| **ID** | EV-009 | +| **Tanggal** | 2026-05-20 | +| **Severity** | 🟑 Medium | +| **Environment** | Development | +| **Status** | βœ… Resolved | +| **Reporter** | Akbar | + +**Error Message:** +> Layout admin loket tidak render dengan benar β€” elemen UI tumpang tindih atau alignment salah. + +**Langkah Reproduksi:** +1. Login sebagai admin loket +2. Buka halaman `/admin-loket` +3. Layout tampilan loket tidak sesuai desain + +**Root Cause:** +> CSS layout issue dan data binding yang tidak sinkron dengan state loket. + +**Solusi / Fix:** +> Perbaikan layout dan data binding di halaman admin loket. + +**Related:** Commit `f622052` β€” "perbaikan bug tampilan loket" + +--- + +### [EV-008] β€” Memory Leak & Request Spam dari doctorStore + +| Field | Value | +|-------|-------| +| **ID** | EV-008 | +| **Tanggal** | 2026-05-18 | +| **Severity** | 🟠 High | +| **Environment** | Development | +| **Status** | βœ… Resolved | +| **Reporter** | Akbar | + +**Error Message:** +``` +Internal Server Error (500) β€” /klinik-api/doctors/... +``` +Error terjadi berulang-ulang tanpa henti, menyebabkan request spam ke backend. + +**Langkah Reproduksi:** +1. Buka halaman yang memuat `doctorStore.js` +2. Backend endpoint `/doctors` return 500 +3. Store terus retry tanpa batas +4. Network tab penuh dengan request gagal, memory usage naik terus + +**Root Cause:** +> `doctorStore.js` tidak memiliki mekanisme backoff atau blacklist untuk endpoint yang gagal. Setiap kali fetch gagal, store langsung retry tanpa delay, menyebabkan infinite loop request ke server. + +**Solusi / Fix:** +```javascript +// βœ… Implementasi blacklist endpoint gagal +const blacklistedEndpoints = new Set() + +async function fetchDoctors(endpoint) { + if (blacklistedEndpoints.has(endpoint)) { + return [] // skip, sudah di-blacklist + } + try { + return await $fetch(endpoint) + } catch (error) { + if (error.status === 500) { + blacklistedEndpoints.add(endpoint) + console.warn(`Endpoint blacklisted: ${endpoint}`) + } + return [] + } +} +``` + +**Prevention:** +> Semua API fetch HARUS memiliki: (1) error handling, (2) retry limit atau backoff, (3) blacklist mechanism untuk persistent failures. Jangan pernah retry tanpa batas. + +**Related:** Conversation `c7502197` β€” "Optimizing Web Antrean Memory Usage", commit `cb3310b` + +--- + +### [EV-007] β€” Console.log Verbose Memperlambat Performa + +| Field | Value | +|-------|-------| +| **ID** | EV-007 | +| **Tanggal** | 2026-05-18 | +| **Severity** | 🟒 Low | +| **Environment** | Development | +| **Status** | βœ… Resolved | +| **Reporter** | Akbar | + +**Error Message:** +> Tidak ada error message, tapi browser DevTools penuh dengan log output, menyebabkan performa menurun terutama di device yang lebih lambat. + +**Langkah Reproduksi:** +1. Buka halaman yang menggunakan WebSocket atau QR scanner +2. Buka browser DevTools β†’ Console +3. Log membanjir setiap detik dari `useWebSocket.ts` dan QR scanner di `checkIn.vue` + +**Root Cause:** +> `console.log` debugging statements di high-frequency code paths (WebSocket message handler yang dipanggil per-detik, QR scanner frame processing) tidak dihapus setelah debugging selesai. + +**Solusi / Fix:** +> Cleanup semua `console.log` di: +> - `composables/useWebSocket.ts` β€” WebSocket message handler +> - `pages/CheckInPasien/checkIn.vue` β€” QR scanner frame processing +> Hanya sisakan `console.warn` dan `console.error` untuk kondisi yang benar-benar perlu. + +**Prevention:** +> Gunakan convention: `console.log` hanya untuk debugging sementara, `console.warn`/`console.error` untuk production logging. Tambahkan lint rule atau pre-commit hook untuk mendeteksi `console.log` yang tersisa. + +**Related:** Conversation `c7502197` β€” "Optimizing Web Antrean Memory Usage" + +--- + +### [EV-006] β€” Header Origin/Referer Tidak Konsisten di Proxy Routes + +| Field | Value | +|-------|-------| +| **ID** | EV-006 | +| **Tanggal** | 2026-05-22 | +| **Severity** | 🟑 Medium | +| **Environment** | Development | +| **Status** | βœ… Resolved | +| **Reporter** | Akbar | + +**Error Message:** +> CORS error atau origin validation failure saat memanggil backend API melalui proxy routes. Beberapa request berhasil, beberapa gagal secara intermittent. + +**Langkah Reproduksi:** +1. Panggil API via proxy route `stats-api`, `visit-api`, atau `klinik-api` +2. Beberapa request gagal dengan CORS error +3. Request yang sama kadang berhasil, kadang gagal + +**Root Cause:** +> Setiap proxy route handler (`server/routes/stats-api/`, `visit-api/`, `klinik-api/`) menggunakan header origin dan referer yang berbeda-beda. Backend melakukan validasi origin, dan header yang tidak konsisten menyebabkan intermittent failure. + +**Solusi / Fix:** +> Standardisasi semua proxy route handler agar menggunakan origin dan referer yang sama: +```typescript +// βœ… Semua proxy routes menggunakan header yang sama +headers: { + 'Origin': 'http://10.10.150.175:3000', + 'Referer': 'http://10.10.150.175:3000', +} +``` + +**Prevention:** +> Buat shared utility function untuk proxy header configuration agar semua route handler menggunakan config yang sama. Hindari copy-paste header config per file. + +**Related:** Conversation `97867794` β€” "Standardizing Header Configurations Across APIs" + +--- + +### [EV-005] β€” Duplikasi Tiket di Admin Klinik Loket + +| Field | Value | +|-------|-------| +| **ID** | EV-005 | +| **Tanggal** | 2026-02-11 | +| **Severity** | 🟠 High | +| **Environment** | Development | +| **Status** | βœ… Resolved | +| **Reporter** | Akbar | + +**Error Message:** +> Daftar pasien di admin klinik menampilkan tiket yang sama dua kali. Setelah check-in, tiket pasien menghilang dari daftar. + +**Langkah Reproduksi:** +1. Pasien ambil antrean di anjungan +2. Buka admin klinik loket +3. Pasien yang sama muncul 2x di daftar +4. Setelah check-in, tiket menghilang dari daftar + +**Root Cause:** +> WebSocket event handler menambahkan pasien ke `allPatients` tanpa deduplication check. Saat event masuk bersamaan dari multiple sources (initial load + WebSocket), data terduplikasi. Saat check-in, state update menghapus entry yang salah. + +**Solusi / Fix:** +```javascript +// βœ… Deduplication check sebelum add ke allPatients +function addPatient(patient) { + const exists = allPatients.value.find(p => p.nomorAntrean === patient.nomorAntrean) + if (!exists) { + allPatients.value.push(patient) + } else { + // Update existing instead of duplicate + Object.assign(exists, patient) + } +} +``` + +**Prevention:** +> Semua operasi mutasi pada `allPatients` harus melalui function yang melakukan deduplication check. Jangan pernah langsung `push` tanpa cek duplikat. + +**Related:** Commit `c02905e` β€” "fix duplication and ticket disappear in adminklinik loket" + +--- + +### [EV-004] β€” WebSocket Check-in Tidak Trigger UI Update + +| Field | Value | +|-------|-------| +| **ID** | EV-004 | +| **Tanggal** | 2026-02-10 | +| **Severity** | 🟠 High | +| **Environment** | Development | +| **Status** | βœ… Resolved | +| **Reporter** | Akbar | + +**Error Message:** +> Pasien check-in via QR berhasil (API return success), tapi status di admin loket tidak berubah. UI tetap menampilkan pasien sebagai "belum hadir". + +**Langkah Reproduksi:** +1. Pasien scan QR di halaman check-in +2. API return success β€” pasien tercatat hadir +3. Buka admin loket β€” status pasien masih "belum hadir" +4. Perlu manual refresh untuk melihat update + +**Root Cause:** +> WebSocket event listener untuk check-in event tidak ter-bind dengan benar. Event `checkin_success` diterima oleh WebSocket, tapi handler tidak melakukan state update ke Pinia store. + +**Solusi / Fix:** +> Fix event listener binding di `useWebSocket.ts` β€” pastikan `checkin_success` event memicu update pada `queueStore.allPatients`. + +**Prevention:** +> Setiap WebSocket event type harus memiliki dedicated handler yang ter-test. Buat mapping event β†’ handler yang eksplisit. + +**Related:** Commits `e686dda`, `b6dc252` β€” "fix socket checkin", "update bug ws checkin loket" + +--- + +### [EV-003] β€” HTTPS Implementation Gagal di Check-in + +| Field | Value | +|-------|-------| +| **ID** | EV-003 | +| **Tanggal** | 2026-02-02 | +| **Severity** | 🟑 Medium | +| **Environment** | Development | +| **Status** | βœ… Resolved | +| **Reporter** | Akbar | + +**Error Message:** +> Check-in via QR tidak berfungsi saat HTTPS aktif. Kamera QR scanner tidak bisa diakses. + +**Langkah Reproduksi:** +1. Aktifkan HTTPS di dev server +2. Buka halaman check-in +3. QR scanner gagal β€” kamera tidak muncul + +**Root Cause:** +> Browser memerlukan HTTPS untuk akses kamera (MediaDevices API), tapi mixed content policy memblokir request ke backend HTTP. Konfigurasi HTTPS tidak lengkap β€” SSL certificate self-signed tidak dipercaya browser. + +**Solusi / Fix:** +> Fix implementasi HTTPS menggunakan `@vitejs/plugin-basic-ssl` di `nuxt.config.ts`. Konfigurasi dev server untuk HTTPS dengan self-signed cert yang di-trust browser. + +**Prevention:** +> Saat menggunakan Web API yang memerlukan secure context (kamera, geolocation), pastikan HTTPS sudah configured end-to-end termasuk backend. + +**Related:** Commit `c899a71` β€” "fix https implementation and checkin" + +--- + +### [EV-002] β€” Loop & Duplikasi Data di Fetch API + +| Field | Value | +|-------|-------| +| **ID** | EV-002 | +| **Tanggal** | 2026-01-29 | +| **Severity** | 🟠 High | +| **Environment** | Development | +| **Status** | βœ… Resolved | +| **Reporter** | Akbar | + +**Error Message:** +> Data pasien terduplikasi di daftar antrean. Fetch API dipanggil berulang-ulang dalam loop. + +**Langkah Reproduksi:** +1. Buka halaman yang memuat data antrean +2. Data pasien muncul berkali-kali +3. Network tab menunjukkan API dipanggil berulang-ulang + +**Root Cause:** +> `watch` atau `computed` yang bergantung pada reactive state memicu re-fetch setiap kali state berubah, dan hasil fetch mengubah state lagi β€” menyebabkan infinite loop. + +**Solusi / Fix:** +> Fix logic watch/computed agar tidak circular. Tambahkan guard condition untuk mencegah re-fetch saat data sudah di-load. + +**Prevention:** +> Hindari circular dependency antara watcher dan state mutation. Gunakan flag `isLoading` untuk mencegah concurrent fetch. + +**Related:** Commit `0bd5311` β€” "fix loop dan duplicate data" + +--- + +### [EV-001] β€” Session & Tampilan Screen Tidak Sinkron + +| Field | Value | +|-------|-------| +| **ID** | EV-001 | +| **Tanggal** | 2026-01-30 | +| **Severity** | 🟑 Medium | +| **Environment** | Development | +| **Status** | βœ… Resolved | +| **Reporter** | Akbar | + +**Error Message:** +> Display screen menampilkan data lama setelah session expire. Setelah re-login, data tidak refresh otomatis. + +**Langkah Reproduksi:** +1. Login dan buka display screen +2. Tunggu session expire (1 jam) +3. Re-login +4. Display masih menampilkan data dari session sebelumnya + +**Root Cause:** +> Pinia persisted state menyimpan data lama di `localStorage`. Saat session expire dan user re-login, store tidak di-reset β€” data lama masih tampil. + +**Solusi / Fix:** +> Reset persisted state saat login baru. Pastikan display screen melakukan fresh fetch setelah session recovery. + +**Prevention:** +> Implementasi session lifecycle hooks: on session expire β†’ clear stale state, on re-login β†’ fresh fetch semua data. + +**Related:** Commit `8dd94ed` β€” "update fix session dan tampilan screen" + +--- + +## πŸ“Š Error Statistics + +| Bulan | Critical | High | Medium | Low | Total | +|-------|---------|------|--------|-----|-------| +| Jan 2026 | 0 | 1 | 1 | 0 | 2 | +| Feb 2026 | 0 | 3 | 1 | 0 | 4 | +| Mar 2026 | 0 | 0 | 0 | 0 | 0 | +| Apr 2026 | 0 | 0 | 0 | 0 | 0 | +| Mei 2026 | 2 | 2 | 1 | 1 | 6 | +| **Total** | **2** | **6** | **3** | **1** | **12** | + +--- + +## πŸ” Recurring Issues + +> Daftar error yang muncul lebih dari sekali β€” kandidat untuk refactor/improvement permanen. + +| Error Pattern | Frekuensi | Action | +|---------------|-----------|--------| +| WebSocket disconnect β†’ data tidak sinkron | 3x (EV-004, EV-011, EV-012) | βœ… Implemented: polling fallback 30 detik + auto-reconnect + deterministic client ID | +| Duplikasi data pasien di daftar | 2x (EV-002, EV-005) | βœ… Implemented: deduplication check di `allPatients` mutation | +| Session/state stale setelah reconnect | 2x (EV-001, EV-011) | βœ… Implemented: fresh fetch on reconnect + state reset on re-login | +| Request spam ke endpoint yang gagal | 1x (EV-008) | βœ… Implemented: blacklist + retry limit. **Monitor untuk recurring** | +| Cross-instance state conflict (loket) | 1x (EV-010) | βœ… Implemented: unique persisted state key per instance. **Audit untuk klinik/penunjang** | diff --git a/docs/PRD.md b/docs/PRD.md new file mode 100644 index 0000000..91a2bff --- /dev/null +++ b/docs/PRD.md @@ -0,0 +1,406 @@ +# πŸ“‹ PRD β€” Product Requirements Document + +**Project:** Web Antrean β€” Sistem Manajemen Antrean Rawat Jalan RSSA +**Version:** 1.0.0 +**Status:** `In Review` +**Author:** Akbar +**Stack:** JavaScript Β· Vue 3 Β· TypeScript Β· Nuxt 3 Β· Vuetify 3 Β· Pinia +**Last Updated:** 2026-05-25 + +--- + +## 🧠 AI Prompt β€” Cara Menggunakan Dokumen Ini + +> Salin prompt berikut ke Claude / ChatGPT untuk membantu melanjutkan PRD: + +``` +Kamu adalah product manager senior. Bantu aku mengembangkan PRD untuk project berikut: +- Nama project: Web Antrean β€” Sistem Manajemen Antrean Rawat Jalan RSSA +- Tujuan utama: Digitalisasi alur antrean pasien rawat jalan di rumah sakit +- Target pengguna: Pasien, Admin Loket, Admin Klinik, Admin Penunjang, Superadmin +- Tech stack: Nuxt 3, Vue 3, TypeScript, Vuetify 3, Pinia, WebSocket, Keycloak SSO + +Buatkan: +1. Problem statement yang tajam +2. User stories tambahan dengan acceptance criteria +3. Risiko teknis dan mitigasinya + +Format output dalam Markdown. +``` + +--- + +## 1. Overview + +### 1.1 Executive Summary +Web Antrean adalah aplikasi manajemen antrean rawat jalan berbasis web yang dibangun untuk Rumah Sakit RSSA. Sistem ini mendigitalisasi seluruh alur antrean pasien mulai dari pendaftaran via anjungan mandiri, pemanggilan pasien di loket dan klinik, hingga monitoring real-time oleh administrator. Aplikasi ini kritis karena menggantikan sistem antrean manual yang tidak efisien dan rawan error. + +### 1.2 Problem Statement +Rumah Sakit RSSA menghadapi masalah antrean rawat jalan yang tidak terstruktur: pasien tidak mengetahui posisi antrean mereka, petugas loket kesulitan mengelola urutan panggil, dan tidak ada visibilitas real-time lintas unit (loket, klinik, penunjang). Kondisi ini menyebabkan penumpukan pasien, waktu tunggu tidak terprediksi, dan pengalaman buruk bagi pasien. + +### 1.3 Goals & Success Metrics + +| # | Goal | Metrik | Target | +|---|------|--------|--------| +| 1 | Mengurangi waktu tunggu pasien | Rata-rata waktu tunggu per sesi | < 30 menit | +| 2 | Digitalisasi antrean loket & klinik | % proses antrean via sistem | 100% | +| 3 | Real-time sync antar perangkat | Latensi update antrean via WebSocket | < 2 detik | +| 4 | Kemandirian pasien dalam ambil antrean | % pasien gunakan anjungan mandiri | > 80% | +| 5 | Stabilitas sistem | Uptime saat jam operasional | > 99.5% | + +### 1.4 Non-Goals (Out of Scope) +- [ ] Integrasi dengan sistem pembayaran / billing +- [ ] Manajemen rekam medis (EMR) +- [ ] Aplikasi mobile native (iOS/Android) +- [ ] Antrean rawat inap (ranap) β€” fitur ranap admin tersedia tapi belum final +- [ ] Sistem appointment/penjadwalan janji temu online + +--- + +## 2. Stakeholders + +| Role | Nama | Tanggung Jawab | +|------|------|----------------| +| Product Owner | Tim RSSA | Prioritas fitur & validasi kebutuhan | +| Fullstack Engineer | Akbar | Arsitektur, implementasi, & deployment | +| UI/UX Designer | β€” | Desain antarmuka (dikerjakan oleh engineer) | +| QA Engineer | β€” | Pengujian via Cypress & Vitest | +| System Admin | Tim IT RSSA | Infrastruktur, server, & jaringan internal | + +--- + +## 3. User Personas + +### πŸ§‘ Persona 1: Pasien Rawat Jalan +- **Goals:** Mendapatkan nomor antrean dengan mudah, mengetahui posisi antrean, dan dipanggil tepat waktu +- **Pain Points:** Tidak tahu urutan antrean, harus menunggu tanpa informasi, bingung harus ke mana +- **Tech Level:** Rendah β€” menggunakan anjungan layar sentuh di rumah sakit + +### πŸ§‘ Persona 2: Petugas Loket +- **Goals:** Memanggil pasien secara urut, memproses lebih dari satu loket secara paralel, melihat daftar antrean real-time +- **Pain Points:** Antrean manual rawan konflik antar loket, sulit pantau siapa yang sudah dipanggil +- **Tech Level:** Menengah β€” menggunakan PC/tablet di meja loket + +### πŸ§‘ Persona 3: Admin Klinik / Dokter +- **Goals:** Melihat daftar pasien yang akan dilayani di kliniknya, memanggil pasien sesuai urutan, mencatat status kunjungan +- **Pain Points:** Tidak tahu berapa pasien tersisa, data pasien tidak sinkron antar perangkat +- **Tech Level:** Menengah + +### πŸ§‘ Persona 4: Superadmin / Admin IT +- **Goals:** Mengelola master data (klinik, loket, penunjang, screen), mengatur hak akses user, memonitor seluruh antrean +- **Pain Points:** Tidak ada dashboard terpusat, perubahan konfigurasi butuh restart sistem +- **Tech Level:** Tinggi + +--- + +## 4. User Stories + +| ID | Epic | Story | Priority | Acceptance Criteria | Status | +|----|------|-------|----------|---------------------|--------| +| US-001 | Anjungan | Sebagai pasien, saya ingin mengambil nomor antrean via anjungan mandiri agar tidak perlu antri ke loket | πŸ”΄ High | - [ ] Pasien dapat memilih klinik tujuan
- [ ] Sistem generate nomor antrean unik
- [ ] Tiket dicetak atau ditampilkan di layar | `Done` | +| US-002 | Anjungan | Sebagai pasien eksekutif, saya ingin memilih sub-spesialis di anjungan agar antrean sesuai dokter yang dituju | πŸ”΄ High | - [ ] Daftar subspesialis tampil berdasarkan klinik
- [ ] Pilihan tersimpan ke nomor antrean | `Done` | +| US-003 | Check-in | Sebagai pasien, saya ingin check-in via scan QR agar kedatangan saya tercatat di sistem | πŸ”΄ High | - [ ] QR scanner aktif via kamera
- [ ] Status pasien berubah menjadi "hadir"
- [ ] Notifikasi berhasil muncul | `Done` | +| US-004 | Loket | Sebagai petugas loket, saya ingin memanggil pasien berikutnya agar antrean berjalan terurut | πŸ”΄ High | - [ ] Tombol "Panggil Berikutnya" tersedia
- [ ] Nomor antrean tampil di display screen
- [ ] Sync real-time via WebSocket | `Done` | +| US-005 | Klinik | Sebagai admin klinik, saya ingin melihat daftar pasien yang akan dilayani agar saya bisa mempersiapkan pelayanan | πŸ”΄ High | - [ ] Daftar pasien tampil dengan status terkini
- [ ] Update otomatis tanpa refresh manual | `Done` | +| US-006 | Monitoring | Sebagai superadmin, saya ingin melihat dashboard antrean seluruh unit agar dapat memantau kondisi operasional | 🟑 Medium | - [ ] Statistik antrean per unit tersedia
- [ ] Data diperbarui real-time | `Done` | +| US-007 | Setting | Sebagai superadmin, saya ingin mengatur hak akses per user/group agar keamanan data terjaga | 🟑 Medium | - [ ] CRUD hak akses per role & group Keycloak
- [ ] Perubahan langsung efektif tanpa restart | `Done` | +| US-008 | Penunjang | Sebagai admin penunjang, saya ingin mengelola antrean unit penunjang (lab, radiologi) agar terpisah dari antrean klinik | 🟑 Medium | - [ ] Antrean penunjang terpisah per unit
- [ ] Admin hanya melihat unit penunjangnya | `In Progress` | + +--- + +## 5. Functional Requirements + +### 5.1 Modul: Anjungan Mandiri +- **FR-01:** Pasien dapat memilih klinik tujuan dari daftar klinik aktif +- **FR-02:** Sistem mengenerate nomor antrean berformat `[KodeKlinik]-[Nomor]` (contoh: `PDL-001`) +- **FR-03:** Anjungan mendukung pilihan subspesialis untuk klinik eksekutif +- **FR-04:** Tiket antrean dapat dicetak via thermal printer +- **FR-05:** Anjungan dapat dikonfigurasi per tipe (klinik, penunjang, klinik ruang) + +### 5.2 Modul: Check-in Pasien +- **FR-06:** Pasien dapat check-in mandiri via scan QR code +- **FR-07:** Sistem memvalidasi QR dan mengupdate status kunjungan ke "hadir" +- **FR-08:** Riwayat check-in pasien dapat dilihat oleh petugas + +### 5.3 Modul: Loket +- **FR-09:** Petugas loket dapat memanggil pasien berikutnya sesuai urutan antrean +- **FR-10:** Setiap loket memiliki state antrean yang terisolasi (tidak interferensi antar loket) +- **FR-11:** Petugas dapat skip, recall, atau selesaikan pasien +- **FR-12:** Display nomor panggil sinkron via WebSocket ke layar display + +### 5.4 Modul: Klinik / Dokter +- **FR-13:** Admin klinik melihat daftar pasien berdasarkan klinik yang diampu +- **FR-14:** Status pasien (menunggu, dipanggil, selesai) dapat diupdate +- **FR-15:** Pemanggilan pasien di klinik sync ke display screen ruangan + +### 5.5 Modul: Dashboard & Monitoring +- **FR-16:** Dashboard menampilkan statistik antrean real-time (total, menunggu, selesai) +- **FR-17:** Superadmin dapat melihat data seluruh unit sekaligus +- **FR-18:** Data diperbarui via WebSocket + polling fallback 30 detik + +### 5.6 Modul: Setting & Master Data +- **FR-19:** CRUD Master Klinik, Loket, Penunjang, Klinik Ruang, Screen +- **FR-20:** Manajemen hak akses berbasis Keycloak role & group +- **FR-21:** Konfigurasi screen display untuk setiap loket/klinik +- **FR-22:** Manajemen user login dan sesi + +### 5.7 Modul: Penunjang +- **FR-23:** Antrean unit penunjang (lab, radiologi, dll) dikelola terpisah +- **FR-24:** Admin penunjang hanya mengakses unit yang menjadi tanggung jawabnya + +--- + +## 6. Non-Functional Requirements + +| Kategori | Requirement | Target | +|----------|-------------|--------| +| Performance | WebSocket latency update antrean | < 2 detik | +| Performance | Waktu load halaman utama | < 3 detik | +| Performance | Polling fallback interval | 30 detik | +| Security | Auth method | Keycloak SSO (OAuth 2.0 / OIDC) | +| Security | Session duration | 1 jam (configurable) | +| Security | Hak akses berbasis role & group | Role-Based + Group-Based | +| Availability | Uptime jam operasional (06.00–21.00) | > 99.5% | +| Accessibility | Anjungan β€” touch target size | > 44px (mudah dioperasikan pasien) | +| Scalability | Jumlah koneksi WebSocket simultan | > 50 device sekaligus | +| Compatibility | Browser support | Chrome, Edge (terbaru) | +| Network | Operasi di jaringan LAN internal RS | βœ… Fully LAN-based | + +--- + +## 7. Technical Specifications + +### 7.1 Tech Stack + +| Layer | Teknologi | +|-------|-----------| +| Frontend Framework | Nuxt 3 (SSR β€” server-side rendering) | +| UI Framework | Vue 3 Β· Vuetify 3 | +| Language | TypeScript Β· JavaScript | +| State Management | Pinia + pinia-plugin-persistedstate | +| Styling | SCSS (Vuetify override) + Material Design Icons | +| Real-time | WebSocket (native browser API via `useWebSocket.ts`) | +| Auth | Keycloak SSO (OAuth 2.0 / OIDC) | +| Charts | Chart.js Β· vue-chartjs Β· nuxt-charts | +| QR | html5-qrcode (scanner) Β· qrcode.vue (generator) | +| Print | Thermal printer via `useThermalPrint.ts` | +| Date | Day.js | +| Icons | FontAwesome Β· Material Design Icons | +| Testing | Vitest Β· Cypress | +| Fonts | Inter (Google Fonts) | + +### 7.2 Backend APIs (Eksternal) + +| Service | Base URL | Keterangan | +|---------|----------|------------| +| Visit API | `http://10.10.123.135:8084/api/v1` | Data kunjungan & antrean utama | +| Antrian API (Klinik) | `http://10.10.123.140:8089/api/v1` | Verifikasi & data klinik/dokter | +| WebSocket | `ws://10.10.123.135:8084/api/v1/ws` | Real-time queue update | + +### 7.3 Arsitektur Sistem + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Nuxt 3 Frontend (SSR/CSR) β”‚ +β”‚ Pages Β· Components Β· Stores (Pinia) β”‚ +β”‚ Composables: useWebSocket Β· useQueue Β· β”‚ +β”‚ useCheckIn Β· useThermalPrint Β· useQRScanner β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ $fetch / useFetch β”‚ WebSocket +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Nuxt Server (Nitro) β”‚ β”‚ WebSocket Server β”‚ +β”‚ server/api/ β”‚ β”‚ ws://10.10.123. β”‚ +β”‚ server/routes/ β”‚ β”‚ 135:8084/api/v1/wsβ”‚ +β”‚ (Proxy + API layer) β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ HTTP Proxy (CORS bypass) +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Backend Services (Eksternal) β”‚ +β”‚ Visit API (8084) Β· Antrian API (8089) β”‚ +β”‚ Keycloak SSO (auth.rssa.top) β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +### 7.4 Proxy Routes (Nuxt Server) + +| Proxy Path | Target Backend | Keterangan | +|------------|---------------|------------| +| `/visit-api/**` | `http://10.10.123.135:8084/api/v1/**` | Data kunjungan pasien | +| `/klinik-api/**` | `http://10.10.123.140:8089/api/v1/**` | Data klinik & dokter | +| `/stats-api/**` | `http://10.10.123.135:8084/api/v1/**` | Statistik dashboard | + +### 7.5 Struktur Folder + +``` +web-antrean/ +β”œβ”€β”€ assets/scss/ ← Global styles & variables +β”œβ”€β”€ components/ ← Reusable UI components +β”œβ”€β”€ composables/ ← useWebSocket, useQueue, useCheckIn, +β”‚ useThermalPrint, useQRScanner, useAPI +β”œβ”€β”€ layouts/ ← Layout default & admin +β”œβ”€β”€ middleware/ ← Auth & permission guards +β”œβ”€β”€ pages/ ← Semua halaman (lihat seksi 8.1) +β”œβ”€β”€ server/ +β”‚ β”œβ”€β”€ api/ ← Internal API (auth, hak-akses, queue, users) +β”‚ └── routes/ ← Proxy routes (visit-api, klinik-api, stats-api) +β”œβ”€β”€ stores/ ← Pinia stores (queue, clinic, loket, dll) +β”œβ”€β”€ types/ ← TypeScript interfaces & types +β”œβ”€β”€ public/ ← Static assets (favicon, logo) +β”œβ”€β”€ nuxt.config.ts ← Konfigurasi utama Nuxt +└── .env ← Environment variables (tidak di-commit) +``` + +### 7.6 Internal API Endpoints (Nuxt Nitro) + +| Method | Endpoint | Deskripsi | Auth | +|--------|----------|-----------|------| +| GET | `/api/permission` | Ambil permissions berdasarkan role & group | βœ… | +| GET | `/api/hak-akses` | Daftar semua hak akses | βœ… | +| POST | `/api/hak-akses` | Buat hak akses baru | βœ… | +| PATCH | `/api/hak-akses/:id` | Update hak akses | βœ… | +| DELETE | `/api/hak-akses/:id` | Hapus hak akses | βœ… | +| GET | `/api/users/list` | Daftar user dari Keycloak | βœ… | +| POST | `/api/auth/login` | Login via Keycloak SSO | ❌ | +| POST | `/api/auth/logout` | Logout & invalidate session | βœ… | +| POST | `/api/external/validate-token` | Validasi JWT token eksternal | βœ… | +| GET | `/api/config/...` | Konfigurasi aplikasi | βœ… | + +### 7.7 TypeScript Types Utama + +```typescript +// types/user.ts +export interface User { + id: string + namaLengkap: string + namaUser: string + email: string + tipeUser: string + roles: string[] + groups: string[] + lastLogin: number + createdAt: number +} + +// types/queue.ts +export interface QueuePatient { + nomorAntrean: string + namaPassien: string + noRM: string + klinik: string + status: 'menunggu' | 'dipanggil' | 'selesai' | 'skip' + loketId?: string + subspesialis?: string + createdAt: Date +} + +// types/permission.ts +export interface Permission { + id: number + pagename: string + read: boolean + create: boolean + update: boolean + delete: boolean + disable: boolean + active: boolean + level: number + parent: number | null +} + +// types/api.ts +export interface ApiResponse { + data: T + message: string + success: boolean +} +``` + +--- + +## 8. UI/UX + +### 8.1 Halaman & Routes + +| Halaman | Route | Auth | Role | +|---------|-------|------|------| +| Landing / Home | `/` | ❌ | Public | +| Login | `/login-page` | ❌ | Public | +| Dashboard | `/dashboard` | βœ… | Superadmin | +| Admin Klinik | `/admin-klinik` | βœ… | Admin Klinik | +| Admin Loket | `/admin-loket` | βœ… | Admin Loket | +| Admin Penunjang | `/admin-penunjang` | βœ… | Admin Penunjang | +| Buat Antrean | `/buat-antrean` | βœ… | Loket | +| Klinik Ruang Admin | `/klinik-ruang-admin` | βœ… | Admin Klinik Ruang | +| Ranap Admin | `/ranap-admin` | βœ… | Admin Ranap | +| Anjungan Utama | `/anjungan` | ❌ | Kiosk | +| Admin Anjungan | `/anjungan/admin-anjungan` | βœ… | Superadmin | +| Antrian Klinik | `/anjungan/antrian-klinik` | ❌ | Kiosk | +| Antrian Klinik Ruang | `/anjungan/antrian-klinik-ruang` | ❌ | Kiosk | +| Antrian Penunjang | `/anjungan/antrian-penunjang` | ❌ | Kiosk | +| Antrean Masuk Screen | `/anjungan/antrean-masuk` | ❌ | Display | +| Check In Pasien | `/check-in-pasien/check-in` | ❌ | Kiosk | +| Data Pasien | `/data-pasien` | βœ… | Admin | +| Edit Data Pasien | `/data-pasien/edit/:id` | βœ… | Admin | +| Monitoring Pasien | `/monitoring-pasien/monitoring-pasien` | βœ… | Admin | +| Detail Pasien | `/monitoring-pasien/pasien/:id` | βœ… | Admin | +| Profil | `/profile/profil` | βœ… | Semua | +| User Login | `/setting/user-login` | βœ… | Superadmin | +| Hak Akses | `/setting/hak-akses` | βœ… | Superadmin | +| Master Klinik | `/setting/master-klinik` | βœ… | Superadmin | +| Master Klinik Ruang | `/setting/master-klinik-ruang` | βœ… | Superadmin | +| Master Loket | `/setting/master-loket` | βœ… | Superadmin | +| Master Penunjang | `/setting/master-penunjang` | βœ… | Superadmin | +| Screen Settings | `/setting/screen` | βœ… | Superadmin | +| Verifikasi Akun | `/verifikasi-akun/verifikasi-akun` | ❌ | Public | + +### 8.2 Breakpoints + +| Nama | Size | Keterangan | +|------|------|------------| +| Mobile | < 640px | Tidak diprioritaskan (akses via PC/tablet) | +| Tablet | 640–1024px | Anjungan & loket (tablet) | +| Desktop | > 1024px | Admin & monitoring | + +### 8.3 Design System +- **Framework UI:** Vuetify 3 (Material Design 3) +- **Font:** Inter (400, 500, 600, 700) +- **Icons:** Material Design Icons (`@mdi/font`) + FontAwesome +- **Color scheme:** Mengikuti theme Vuetify (light/dark configurable) +- **SCSS Variables:** Didefinisikan di `assets/scss/_variables.scss` & `_colors.scss` + +--- + +## 9. Risks + +| Risk | Likelihood | Impact | Mitigasi | +|------|-----------|--------|----------| +| WebSocket disconnect saat jaringan LAN tidak stabil | Tinggi | Tinggi | Polling fallback 30 detik + auto-reconnect logic | +| Backend API eksternal down (Visit API / Antrian API) | Sedang | Tinggi | Error handling graceful, retry logic, blacklist endpoint gagal | +| Konflik antrean antar loket (race condition) | Sedang | Tinggi | State isolasi per `loketId`, strict filtering di `processNextQueue` | +| Sesi Keycloak expire saat jam operasional | Sedang | Sedang | Session duration dikonfigurasi 1 jam, refresh token otomatis | +| Thermal printer tidak kompatibel di semua device | Rendah | Sedang | Uji di device target sebelum go-live, fallback ke tampilan layar | +| Data antrean tidak sinkron antar display screen | Sedang | Tinggi | WebSocket deterministic client ID + polling fallback | +| Kapasitas WebSocket server saat pasien peak | Rendah | Tinggi | Monitor jumlah koneksi, koordinasi dengan tim backend | + +--- + +## 10. Deployment + +| Item | Detail | +|------|--------| +| Server | VPS / Server internal RSSA | +| Domain dev | `http://10.10.150.175:3000` | +| Domain staging | `https://antrean.dev.rssa.id` | +| Domain prod | `https://antrean.rssa.id` | +| Containerisasi | Docker + docker-compose | +| Auth Server | Keycloak (`https://auth.rssa.top/realms/sandbox`) | +| Build command | `nuxt build` | +| Start command | `node .output/server/index.mjs` | + +--- + +## 11. Changelog + +| Versi | Tanggal | Author | Perubahan | +|-------|---------|--------|-----------| +| 1.0.0 | 2026-05-25 | Akbar | Initial PRD β€” dibuat berdasarkan kondisi project aktual | diff --git a/docs/QMD.md b/docs/QMD.md new file mode 100644 index 0000000..e8d463b --- /dev/null +++ b/docs/QMD.md @@ -0,0 +1,394 @@ +# βœ… QMD β€” Quality Management Document + +**Project:** Web Antrean β€” Sistem Manajemen Antrean Rawat Jalan RSSA +**Version:** 1.0.0 +**Author:** Akbar +**Stack:** JavaScript Β· Vue 3 Β· TypeScript Β· Nuxt 3 Β· Vuetify 3 Β· Pinia +**Last Updated:** 2026-05-25 + +--- + +## 🧠 AI Prompt β€” Cara Menggunakan Dokumen Ini + +> Salin prompt berikut ke Claude untuk membantu QA planning: + +``` +Kamu adalah QA engineer senior untuk project Nuxt 3 + Vue 3 + TypeScript. +Project: Web Antrean β€” Sistem manajemen antrean rawat jalan RSSA. + +Fitur utama: +- Anjungan mandiri (kiosk registrasi pasien, pilih klinik/subspesialis) +- Check-in pasien via QR code +- Manajemen antrean loket (panggil, skip, recall, selesai) +- Manajemen antrean klinik & penunjang +- Real-time sync via WebSocket + polling fallback +- Dashboard monitoring & statistik +- Setting: master data (klinik, loket, penunjang, screen), hak akses (Keycloak role/group) +- Cetak tiket via thermal printer + +Bantu aku membuat: +1. Test plan lengkap (unit, integration, E2E) +2. Test cases untuk fitur di atas +3. Definition of Done (DoD) per story +4. Checklist code review untuk Vue 3 + TypeScript +5. Standar kualitas kode (naming, linting, typing) + +Format dalam tabel Markdown. Tool: Vitest, Cypress, Vue Test Utils, happy-dom. +``` + +--- + +## 1. Quality Objectives + +| Objektif | Target | Cara Ukur | +|----------|--------|-----------| +| Test Coverage (Unit) | > 60% | Vitest coverage report (`npx vitest run --coverage`) | +| Bug Rate (prod) | < 5 bug/sprint | Manual tracking / issue log | +| Code Review | 100% PR di-review | Git workflow | +| TypeScript strict | Minimal `any` type | ESLint + `tsc --noEmit` | +| WebSocket Reliability | > 99% message delivery | Monitoring log + polling fallback | +| Page Load | < 3 detik | Lighthouse / manual timing di jaringan LAN | + +--- + +## 2. Testing Strategy + +### 2.1 Piramida Testing +``` + β–² + /E2E\ ← Cypress (browser real) + /──────\ + / Integ \ ← Vitest + Nuxt Test Utils + /──────────\ + / Unit Test \ ← Vitest + Vue Test Utils + happy-dom + /______________\ +``` + +### 2.2 Test Toolchain + +| Tipe | Tool | Config File | Status | +|------|------|-------------|--------| +| Unit | Vitest + happy-dom | `vitest.config.ts` | βœ… Terkonfigurasi | +| Component | Vue Test Utils (`@vue/test-utils`) | β€” | βœ… Terinstal | +| E2E | Cypress | `cypress.config.ts` | βœ… Terkonfigurasi | +| Component (Cypress) | Cypress Component Testing | `cypress.config.ts` β†’ `component` | βœ… Terkonfigurasi | +| Linting | ESLint (Nuxt preset) | `eslint.config.mjs` | βœ… Terkonfigurasi | +| Type Check | TypeScript (via Nuxt) | `tsconfig.json` β†’ extends `.nuxt/tsconfig.json` | βœ… | +| Test Environment | happy-dom | `vitest.config.ts` β†’ `environment: 'happy-dom'` | βœ… | + +### 2.3 Test Commands + +```bash +# Unit & Component Tests +npm run test # vitest (watch mode) +npm run test:ui # vitest --ui (browser UI) + +# E2E Tests +npm run cypress:open # Cypress interactive +npm run cypress:run # Cypress headless + +# Linting +npx eslint . + +# Type Check +npx nuxi typecheck +``` + +--- + +## 3. Test Cases + +### 3.1 Unit Tests β€” Composables + +| ID | Modul | Test Case | Expected | Status | +|----|-------|-----------|----------|--------| +| UT-001 | `useAuth.ts` | Login β€” mengembalikan user data setelah Keycloak auth | `user.value` tidak null, memiliki `id`, `roles`, `groups` | `Todo` | +| UT-002 | `useAuth.ts` | Logout β€” session di-clear dan redirect ke login page | `user.value` menjadi null, navigasi ke `/LoginPage` | `Todo` | +| UT-003 | `useQueue.js` | `processNextQueue()` β€” mengambil pasien berikutnya sesuai loketId | Return pasien dengan status `menunggu` dan `loketId` yang cocok | `Todo` | +| UT-004 | `useQueue.js` | `processNextQueue()` β€” skip pasien dari loket lain | Pasien dari loket lain tidak terproses | `Todo` | +| UT-005 | `useWebSocket.ts` | Koneksi sukses β€” state connected | `isConnected.value === true` setelah open event | `Todo` | +| UT-006 | `useWebSocket.ts` | Auto-reconnect setelah disconnect | Reconnect attempt dalam < 5 detik | `Todo` | +| UT-007 | `useCheckIn.ts` | Check-in valid QR β€” update status pasien | Status pasien berubah ke `hadir`, return success | `Todo` | +| UT-008 | `useCheckIn.ts` | Check-in invalid QR β€” error handling | Return error message, status tidak berubah | `Todo` | +| UT-009 | `useQRScanner.ts` | Inisialisasi scanner β€” kamera aktif | Scanner instance terbuat tanpa error | `Todo` | +| UT-010 | `useHakAkses.ts` | Fetch permissions β€” mapping role ke menu | Menu permissions sesuai dengan role & group user | `Todo` | +| UT-011 | `useThermalPrint.ts` | Generate tiket β€” format nomor antrean benar | Output mengandung kode klinik + nomor urut | `Todo` | +| UT-012 | `useClinicAPI.ts` | Fetch daftar klinik β€” return data klinik aktif | Array klinik tidak kosong, setiap item punya `id` & `nama` | `Todo` | + +### 3.2 Unit Tests β€” Stores (Pinia) + +| ID | Modul | Test Case | Expected | Status | +|----|-------|-----------|----------|--------| +| UT-013 | `queueStore.js` | `allPatients` β€” menyimpan & mengembalikan daftar pasien | State `allPatients` terisi array setelah fetch | `Todo` | +| UT-014 | `queueStore.js` | `currentProcessingPatient` β€” isolasi per loket | Setiap loket punya key unik di persisted state | `Todo` | +| UT-015 | `clinicStore.js` | Fetch daftar klinik dari API | `clinics` terisi data dari klinik-api | `Todo` | +| UT-016 | `doctorStore.js` | Blacklist endpoint gagal | Endpoint yang 500 di-blacklist, tidak di-retry spam | `Todo` | +| UT-017 | `loketStore.js` | State loket terisolasi antar loket | Operasi di loket A tidak mempengaruhi loket B | `Todo` | +| UT-018 | `masterStore.js` | CRUD master klinik | Create, read, update, delete berjalan tanpa error | `Todo` | +| UT-019 | `permissionStore.ts` | Load permission sesuai role | Permission loaded dan accessible via getter | `Todo` | + +### 3.3 Unit Tests β€” Middleware + +| ID | Modul | Test Case | Expected | Status | +|----|-------|-----------|----------|--------| +| UT-020 | `auth.ts` | User belum login β†’ redirect ke `/LoginPage` | `navigateTo('/LoginPage')` dipanggil | `Todo` | +| UT-021 | `auth.ts` | User sudah login β†’ lanjut ke halaman tujuan | Tidak ada redirect | `Todo` | +| UT-022 | `guest.ts` | User sudah login akses `/LoginPage` β†’ redirect ke `/dashboard` | `navigateTo('/dashboard')` dipanggil | `Todo` | +| UT-023 | `permissions.ts` | User tanpa akses ke halaman β†’ redirect/block | Akses ditolak, redirect ke halaman authorized | `Todo` | +| UT-024 | `checkPageAccess.ts` | Validasi hak akses per halaman berdasarkan group | Halaman hanya bisa diakses sesuai permission | `Todo` | + +### 3.4 Component Tests + +| ID | Komponen | Skenario | Expected | Status | +|----|----------|----------|----------|--------| +| CT-001 | `PatientCard.vue` | Render data pasien lengkap | Nama, noRM, nomor antrean, status, subspesialis tampil | `Todo` | +| CT-002 | `PatientCard.vue` | Status badge warna sesuai status | `menunggu` = kuning, `dipanggil` = biru, `selesai` = hijau | `Todo` | +| CT-003 | `CurrentPatientCard.vue` | Tampilkan pasien yang sedang diproses | Data pasien aktif tampil dengan aksi (selesai, skip) | `Todo` | +| CT-004 | `QueueActionsCard.vue` | Tombol aksi antrean (panggil, skip, recall) | Semua tombol render dan emit event yang benar | `Todo` | +| CT-005 | `TabelPatientData.vue` | Render tabel daftar pasien | Kolom: nama, noRM, antrean, status, aksi tampil benar | `Todo` | +| CT-006 | `SideBar.vue` | Menu render sesuai hak akses user | Menu yang tidak diizinkan tidak tampil | `Todo` | +| CT-007 | `PageHeader.vue` | Render judul halaman dan breadcrumb | Judul dan navigasi sesuai route aktif | `Todo` | +| CT-008 | `AppSnackbar.vue` | Notifikasi muncul dan auto-dismiss | Snackbar tampil 3 detik lalu hilang | `Todo` | +| CT-009 | `SelectionDialog.vue` | Dialog pilihan dengan konfirmasi | Pilihan terseleksi, emit event saat konfirmasi | `Todo` | +| CT-010 | `ProfileMenu.vue` | Tampil info user dan tombol logout | Nama user tampil, klik logout memanggil `useAuth().logout()` | `Todo` | + +### 3.5 E2E Tests (Cypress) + +| ID | Flow | Steps | Expected | Status | +|----|------|-------|----------|--------| +| E2E-001 | Login | 1. Buka `/` 2. Redirect ke `/LoginPage` 3. Klik login Keycloak 4. Isi credentials | Redirect ke `/dashboard`, user session aktif | `Skeleton` | +| E2E-002 | Anjungan β€” Ambil Antrean | 1. Buka `/anjungan` 2. Pilih klinik 3. Konfirmasi | Nomor antrean di-generate, tiket tampil | `Todo` | +| E2E-003 | Anjungan Eksekutif β€” Pilih Subspesialis | 1. Buka `/anjungan` 2. Pilih klinik eksekutif 3. Pilih subspesialis 4. Konfirmasi | Antrean tercipta dengan subspesialis terpilih | `Todo` | +| E2E-004 | Check-in QR | 1. Buka `/check-in-pasien/check-in` 2. Scan QR valid | Status pasien update ke "hadir", notifikasi sukses | `Todo` | +| E2E-005 | Loket β€” Panggil Pasien | 1. Login sebagai admin loket 2. Buka `/admin-loket` 3. Klik "Panggil Berikutnya" | Pasien berikutnya tampil di current patient card | `Todo` | +| E2E-006 | Loket β€” Skip & Recall | 1. Panggil pasien 2. Klik skip 3. Klik recall | Pasien di-skip lalu bisa di-recall kembali | `Todo` | +| E2E-007 | Klinik β€” Lihat Daftar Pasien | 1. Login sebagai admin klinik 2. Buka `/admin-klinik` | Daftar pasien klinik tampil sesuai klinik user | `Todo` | +| E2E-008 | Dashboard β€” Statistik | 1. Login sebagai superadmin 2. Buka `/dashboard` | Chart statistik dan data antrean tampil | `Todo` | +| E2E-009 | Setting β€” CRUD Master Klinik | 1. Buka `/setting/master-klinik` 2. Tambah klinik 3. Edit 4. Hapus | Data klinik berhasil CRUD tanpa error | `Todo` | +| E2E-010 | Setting β€” Hak Akses | 1. Buka `/setting/hak-akses` 2. Pilih role & group 3. Set permission 4. Simpan | Hak akses tersimpan dan efektif | `Todo` | +| E2E-011 | WebSocket Sync | 1. Buka admin loket di tab A 2. Buka display screen di tab B 3. Panggil pasien di tab A | Tab B menampilkan nomor panggil dalam < 2 detik | `Todo` | +| E2E-012 | Auth Guard | 1. Tanpa login, akses `/dashboard` | Redirect ke `/LoginPage` | `Todo` | + +--- + +## 4. Definition of Done (DoD) + +Sebuah task/story dianggap **Done** jika: + +- [ ] Code sudah diimplementasi dan berjalan di dev server +- [ ] Unit test ditulis untuk logic kritis (composable, store) +- [ ] TypeScript: tidak ada error pada `npx nuxi typecheck` +- [ ] ESLint: tidak ada error (`npx eslint .`) +- [ ] Code review dilakukan minimal 1 orang +- [ ] Tested manual di browser Chrome (target utama) +- [ ] WebSocket sync diverifikasi antar device (jika fitur terkait real-time) +- [ ] Responsive layout dicek untuk tablet (anjungan) dan desktop (admin) +- [ ] Tidak ada `console.log` debugging yang tertinggal di production path +- [ ] State antrean terisolasi per loket (jika fitur terkait loket) +- [ ] Polling fallback 30 detik berfungsi sebagai safety net + +--- + +## 5. Code Review Checklist + +### General +- [ ] Logic mudah dibaca dan dipahami +- [ ] Tidak ada dead code atau file `.txt` backup yang tersisa +- [ ] Error handling ada di semua `$fetch` / `useFetch` call +- [ ] Tidak ada hardcoded IP/URL (gunakan `.env` + `runtimeConfig`) +- [ ] Tidak ada `console.log` di high-frequency path (WebSocket handler, polling loop) + +### Vue 3 + TypeScript +- [ ] ` diff --git a/pages/AdminKlinikRuang/[kodeKlinik].vue b/pages/AdminKlinikRuang/[kodeKlinik].vue index bea0693..bafc2d5 100644 --- a/pages/AdminKlinikRuang/[kodeKlinik].vue +++ b/pages/AdminKlinikRuang/[kodeKlinik].vue @@ -944,10 +944,10 @@ const sendViaPost = (data) => queueStore.sendViaPost(data); const fetchAllData = async () => { if (!kodeKlinik.value) return; - // console.log('πŸ”„ AdminKlinikRuang refresh: Syncing data...'); try { await queueStore.fetchPatientsForClinic(kodeKlinik.value); queueStore.ensureInitialData(); + queueStore.registerClinicInterest(kodeKlinik.value); // console.log('βœ… AdminKlinikRuang refresh: Success'); } catch (err) { console.error('❌ AdminKlinikRuang refresh error:', err); @@ -1622,6 +1622,9 @@ const broadcastUpdate = async () => { // Base broadcast ID anjunganClientIds.push(`anjungan-klinik-ruang-${klinikData.value.kodeKlinik}`); + // Broadcast to other AdminKlinikRuang instances for same clinic + anjunganClientIds.push(`admin-klinik-ruang-${klinikData.value.kodeKlinik}`); + // Screen-specific IDs ruangList.value.forEach(r => { if (r.nomorScreen) { @@ -1733,6 +1736,7 @@ const handleCallPatientByTipe = async (ruang, tipeLayanan) => { try { const anjunganClientIds = []; anjunganClientIds.push(`anjungan-klinik-ruang-${klinikData.value.kodeKlinik}`); + anjunganClientIds.push(`admin-klinik-ruang-${klinikData.value.kodeKlinik}`); if (ruang.nomorScreen) { const specificScreenId = `anjungan-klinik-ruang-${klinikData.value.kodeKlinik}-screen-${ruang.nomorScreen}`; @@ -1757,7 +1761,17 @@ const handleCallPatientByTipe = async (ruang, tipeLayanan) => { to_client: clientId, data: { noantrian: nomorAntrian, - tipeLayanan: tipeLayanan + klinikId: kodeKlinik.value, + tipeLayanan: tipeLayanan, + triggerRefresh: true, + callKlinikEvent: { + noantrian: nomorAntrian, + barcode: updateData.barcode, + kodeKlinik: kodeKlinik.value, + tipeLayanan: tipeLayanan, + lastCalledAt: updateData.lastCalledAt, + nomorRuang: String(ruang.nomorRuang) + } }, }; await sendViaPost(message); diff --git a/pages/AdminLoket/[id].vue b/pages/AdminLoket/[id].vue index 25ae9b7..04c218a 100644 --- a/pages/AdminLoket/[id].vue +++ b/pages/AdminLoket/[id].vue @@ -884,9 +884,10 @@ const nextQueueInfo = computed(() => { return null; }); -const handlePatientAction = (action) => { +const handlePatientAction = async (action) => { if (currentProcessingPatient.value) { - processPatient(currentProcessingPatient.value, action); + await processPatient(currentProcessingPatient.value, action); + broadcastUpdate(); } }; @@ -974,6 +975,13 @@ const broadcastUpdate = async (callData = null) => { // Also broadcast to clinic specific displays if needed // displayClientIds.push(`anjungan-klinik-${...}`); + // Broadcast to ALL loket admins and anjungans (including this one, for other computers opening the same loket) + for (let i = 1; i <= 10; i++) { + displayClientIds.push(`admin-loket-${i}`); + displayClientIds.push(`anjungan-loket-${i}`); + displayClientIds.push(`anjungan-masuk-${i}`); + } + console.log('πŸ“‘ [AdminLoket] Broadcasting update trigger to:', displayClientIds); const payload = { @@ -1060,6 +1068,12 @@ onMounted(() => { setTimeout(() => { queueStore.ensureInitialData(); }, 200); + + // Initialize centralized WebSocket and register interest + if (loketId.value) { + queueStore.initWebSocket(anjunganClientId.value); + queueStore.registerInterest(loketId.value); + } }); onUnmounted(() => { @@ -1067,6 +1081,10 @@ onUnmounted(() => { if (broadcastChannel) { broadcastChannel.close(); } + + if (loketId.value) { + queueStore.unregisterInterest(loketId.value); + } }); const closeKlinikRuangDialog = () => { @@ -1329,6 +1347,10 @@ const buatAntreanKlinikRuang = async (klinikRuang, ruang) => { snackbarColor.value = result.success ? "success" : "error"; snackbar.value = true; + if (result.success) { + broadcastUpdate(); + } + closeKlinikRuangDialog(); }; diff --git a/pages/Anjungan/AntreanMasuk/[id].vue b/pages/Anjungan/AntreanMasuk/[id].vue index 36d3da4..416510b 100644 --- a/pages/Anjungan/AntreanMasuk/[id].vue +++ b/pages/Anjungan/AntreanMasuk/[id].vue @@ -600,12 +600,16 @@ onMounted(() => { // Initialize and connect WebSocket (Centralized) queueStore.initWebSocket(anjunganClientId.value); - // Register global interest to receive staggered bulk refreshes on generic WS messages - queueStore.registerGlobalInterest(); + // Register specific interest for each configured loket to receive immediate WS updates + if (configuredLoketIds.value && configuredLoketIds.value.length > 0) { + configuredLoketIds.value.forEach(id => queueStore.registerInterest(id)); + } onUnmounted(() => { - // Unregister global interest when leaving the page - queueStore.unregisterGlobalInterest(); + // Unregister specific interest when leaving the page + if (configuredLoketIds.value && configuredLoketIds.value.length > 0) { + configuredLoketIds.value.forEach(id => queueStore.unregisterInterest(id)); + } }); updateTime(); diff --git a/pages/CheckInPasien/checkIn.vue b/pages/CheckInPasien/checkIn.vue index 86b0396..7079381 100644 --- a/pages/CheckInPasien/checkIn.vue +++ b/pages/CheckInPasien/checkIn.vue @@ -1669,6 +1669,7 @@ import { ref, computed, nextTick, onMounted, onUnmounted, watch } from "vue"; import { useQueueStore } from "@/stores/queueStore"; import { useMasterStore } from "@/stores/masterStore"; +import { useLoketStore } from "@/stores/loketStore"; import { useThermalPrint } from "@/composables/useThermalPrint"; import PageHeader from "@/components/common/PageHeader.vue"; @@ -1679,6 +1680,7 @@ definePageMeta({ const queueStore = useQueueStore(); const masterStore = useMasterStore(); +const loketStore = useLoketStore(); const { printTicketFromPatient, isPrinting } = useThermalPrint(); const config = useRuntimeConfig(); const wsBaseUrl = @@ -1802,6 +1804,7 @@ const checkInClientId = computed(() => { const fetchAllData = async () => { console.log('πŸ”„ CheckIn refresh: Syncing data...'); try { + await loketStore.fetchLoketFromAPI(); await queueStore.fetchAllPatients(); queueStore.ensureInitialData(); checkAndResetDaily(); @@ -1811,6 +1814,33 @@ const fetchAllData = async () => { } }; +const broadcastCheckIn = async (loketId) => { + if (!loketId) return; + try { + const displayClientIds = [ + `anjungan-loket-${loketId}`, + `anjungan-masuk-${loketId}` + ]; + + const payload = { + loketId: String(loketId), + triggerRefresh: true + }; + + if (queueStore.sendViaPost) { + console.log('πŸ“‘ [CheckIn] Broadcasting check-in update to:', displayClientIds); + await Promise.all(displayClientIds.map(clientId => + queueStore.sendViaPost({ + to_client: clientId, + data: payload + }) + )); + } + } catch (error) { + console.error('❌ [CheckIn] Error broadcasting update:', error); + } +}; + const isConnected = computed(() => queueStore.isWsConnected); // Watch for clientId changes and reconnect if needed @@ -3249,6 +3279,9 @@ const onDetect = async (decodedText: string) => { "success", "mdi-check-circle", ); + + // Broadcast WebSocket notification to instantly update loket and antrean masuk screens + broadcastCheckIn(checkInResult.patient.loketId); } else { // Check-in gagal (misalnya validasi di checkInPatient gagal) saveToHistory({ @@ -3646,6 +3679,9 @@ const checkInManual = async () => { if (manualForm.value) { (manualForm.value as any).reset(); } + + // Broadcast WebSocket notification to instantly update loket and antrean masuk screens + broadcastCheckIn(checkInResult.patient.loketId); } else { // Check-in gagal (misalnya validasi di checkInPatient gagal) saveToHistory({ diff --git a/stores/queueStore.js b/stores/queueStore.js index 1c08f82..8b4bdc3 100644 --- a/stores/queueStore.js +++ b/stores/queueStore.js @@ -395,19 +395,27 @@ export const useQueueStore = defineStore('queue', () => { refreshedSomething = true; } - if (!refreshedSomething) { - const interestingLokets = Object.keys(activeLoketInterest.value); - const interestingClinics = Object.keys(activeClinicInterest.value); - - if (interestingLokets.length > 0) { - interestingLokets.forEach(loketId => { fetchPatientsForLoket(loketId, true); }); - refreshedSomething = true; - } - - if (interestingClinics.length > 0) { - interestingClinics.forEach(kodeKlinik => { fetchPatientsForClinic(kodeKlinik, true); }); - refreshedSomething = true; - } + // ALWAYS refresh our own active interests when a WebSocket message is received, + // because shared lists (e.g. unassigned patients in 'menunggu') might have changed. + const interestingLokets = Object.keys(activeLoketInterest.value); + const interestingClinics = Object.keys(activeClinicInterest.value); + + if (interestingLokets.length > 0) { + interestingLokets.forEach(loketId => { + if (String(loketId) !== String(targetLoketId)) { + fetchPatientsForLoket(loketId, true); + refreshedSomething = true; + } + }); + } + + if (interestingClinics.length > 0) { + interestingClinics.forEach(kodeKlinik => { + if (String(kodeKlinik) !== String(targetKlinikId)) { + fetchPatientsForClinic(kodeKlinik, true); + refreshedSomething = true; + } + }); } if (!refreshedSomething) { @@ -422,7 +430,7 @@ export const useQueueStore = defineStore('queue', () => { url: wsBaseUrl, clientId: wsClientId, fallbackPostUrl: '/stats-api/ws', - reconnectInterval: 5000, // 5 seconds between reconnect attempts + reconnectInterval: 2000, // 2 seconds between reconnect attempts maxReconnectAttempts: 9999, // Effectively infinite β€” never give up on remote machines onOpen: () => { console.log('βœ… [queueStore] WebSocket connected'); @@ -3284,12 +3292,12 @@ const fetchPatientsForLoket = async (loketId, force = false) => { console.log('βœ… Check-in successful locally, patient status updated to di-loket'); - // Sync to API - const apiSyncResult = await checkInPatientViaApi(allPatients.value[patientIndex].barcode); - if (!apiSyncResult.success) { - console.warn('⚠️ Check-in API sync failed:', apiSyncResult.message); - // We still return true because local state is updated, but we could also return failure if critical - } + // Sync to API in background (fire-and-forget to avoid blocking UI) + checkInPatientViaApi(allPatients.value[patientIndex].barcode).then(apiSyncResult => { + if (!apiSyncResult.success) { + console.warn('⚠️ Check-in API sync failed:', apiSyncResult.message); + } + }); return { success: true,