# ✅ 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
- [ ] `<script setup>` digunakan (Composition API)
- [ ] Props & emits memiliki type yang jelas
- [ ] Minimal penggunaan `any` type (justified jika ada)
- [ ] Composables dipakai untuk logic reusable (`composables/use*.ts`)
- [ ] Reactive data menggunakan `ref()` / `reactive()` dengan benar
- [ ] `watch` / `computed` digunakan daripada manual mutation

### Nuxt 3 Specific
- [ ] Data fetching pakai `$fetch` / `useFetch` / `useAsyncData`
- [ ] Middleware auth terpasang di route yang memerlukan login
- [ ] Server routes (proxy) mengikuti konvensi `server/routes/[prefix]/[...].ts`
- [ ] Internal API mengikuti konvensi `server/api/[resource].[method].ts`
- [ ] Environment variables diakses via `useRuntimeConfig()`

### Pinia Store
- [ ] Store menggunakan `defineStore()` dengan naming `use[Name]Store`
- [ ] State yang perlu persisten menggunakan `pinia-plugin-persistedstate`
- [ ] Key persisted state unik per loket/klinik (hindari konflik antar instance)
- [ ] `allPatients` tetap single source of truth (tidak duplikasi state)

### WebSocket
- [ ] Client ID deterministic (bukan random) untuk targetable messaging
- [ ] Auto-reconnect logic aktif
- [ ] Polling fallback 30 detik sebagai safety net
- [ ] Event handler tidak melakukan full re-render (surgical update)

---

## 6. Standar Kode

### Naming Convention

| Tipe | Konvensi | Contoh (Aktual) |
|------|----------|-----------------|
| Page (Vue) | PascalCase | `Dashboard.vue`, `AdminLoket.vue` |
| Component | PascalCase | `PatientCard.vue`, `QueueActionsCard.vue` |
| Composable | camelCase + `use` prefix | `useWebSocket.ts`, `useQueue.js` |
| Store (Pinia) | camelCase + `Store` suffix | `queueStore.js`, `clinicStore.js` |
| Middleware | camelCase | `auth.ts`, `permissions.ts` |
| Type/Interface | PascalCase | `User`, `Permission`, `ApiResponse<T>` |
| Constants | SCREAMING_SNAKE | `API_BASE_URL`, `WS_API_URL` |
| CSS Class | kebab-case | `.patient-card`, `.queue-actions` |
| Server API | `[resource].[method].ts` | `permission.get.ts`, `validate-token.post.ts` |
| Server Proxy Route | `[...].ts` di folder prefix | `server/routes/visit-api/[...].ts` |

### File Organization Rules

```
# ✅ Good — composable terpisah untuk concern berbeda
composables/
  useAuth.ts          ← Authentication logic
  useWebSocket.ts     ← WebSocket connection management
  useQueue.js         ← Queue business logic
  useCheckIn.ts       ← Check-in flow
  useThermalPrint.ts  ← Thermal printer integration

# ✅ Good — komponen terorganisir per fitur
components/
  common/             ← Reusable (AppSnackbar, Avatar, PageHeader)
  layout/             ← Layout (SideBar, ProfileMenu)
  features/
    queue/            ← PatientCard, CurrentPatientCard, QueueActionsCard
    antrean/          ← Komponen anjungan
    master/           ← Komponen setting master data
    monitoring/       ← Komponen monitoring pasien

# ❌ Bad — logic besar langsung di <script setup> page
pages/AdminLoket.vue  ← Jangan taruh >100 baris logic di sini, extract ke composable
```

### Code Pattern — Composable

```typescript
// ✅ Good — typed composable dengan error handling
export function useClinicAPI() {
  const config = useRuntimeConfig()

  async function fetchClinics(): Promise<Clinic[]> {
    try {
      const data = await $fetch('/klinik-api/clinics')
      return data
    } catch (error) {
      console.error('Failed to fetch clinics:', error)
      return []
    }
  }

  return { fetchClinics }
}

// ❌ Bad — untyped, no error handling
export function useClinicAPI() {
  async function fetchClinics() {
    const data = await $fetch('/klinik-api/clinics') // bisa crash
    return data
  }
  return { fetchClinics }
}
```

---

## 7. CI/CD Quality Gates

> **Catatan:** CI/CD belum diimplementasi. Berikut rencana pipeline saat siap.

```yaml
# GitHub Actions — quality checks (planned)
name: Quality Gate

on:
  push:
    branches: [main, dev]
  pull_request:
    branches: [main]

jobs:
  quality:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20

      - name: Install Dependencies
        run: npm ci

      - name: Type Check
        run: npx nuxi typecheck

      - name: Lint
        run: npx eslint .

      - name: Unit Tests
        run: npx vitest run --coverage

      - name: Build
        run: npx nuxt build
```

### Manual Quality Gate (Saat Ini)

Sebelum merge / deploy, lakukan checklist manual berikut:

- [ ] `npx nuxi typecheck` — tidak ada error
- [ ] `npx eslint .` — tidak ada error
- [ ] `npm run test` — semua test pass
- [ ] `npm run build` — build sukses tanpa error
- [ ] Test manual di browser Chrome di jaringan LAN (`http://10.10.150.175:3000`)
- [ ] Verifikasi WebSocket sync antara admin & display screen
- [ ] Pastikan Docker build berhasil (`docker compose build`)

---

## 8. Bug Severity Matrix

| Level | Deskripsi | Contoh di Web Antrean | SLA Fix |
|-------|-----------|----------------------|---------|
| 🔴 Critical | App crash / data loss / security breach | WebSocket down total → antrean tidak sync, Keycloak auth bypass, pasien kehilangan antrean | < 4 jam |
| 🟠 High | Fitur utama tidak bisa dipakai | Tombol panggil pasien tidak berfungsi, anjungan tidak bisa generate antrean, QR scanner error | < 1 hari |
| 🟡 Medium | Fitur minor terganggu, ada workaround | Thermal print gagal (pasien masih bisa lihat di layar), statistik dashboard delayed | < 3 hari |
| 🟢 Low | UI/kosmetik, tidak mengganggu fungsi | Alignment card tidak rapi, warna badge sedikit off, tooltip tidak muncul | Backlog |

### Known Issues & Mitigasi

| Issue | Severity | Mitigasi Saat Ini |
|-------|----------|-------------------|
| WebSocket disconnect saat jaringan LAN tidak stabil | 🔴 | Auto-reconnect + polling fallback 30 detik |
| Endpoint 500 menyebabkan request spam | 🟠 | Blacklist endpoint gagal di `doctorStore.js` |
| `console.log` verbose di WebSocket handler | 🟢 | Dibersihkan di high-frequency path |
| Race condition antar loket | 🟠 | State isolasi per `loketId` + unique storage key |
| Mixed JS/TS — beberapa store masih `.js` | 🟢 | Migrasi bertahap ke `.ts` |

---

## 9. Technical Debt Tracker

| Item | Impact | Priority | Plan |
|------|--------|----------|------|
| Store files masih `.js` (queueStore, clinicStore, dll) | Type safety rendah | 🟡 Medium | Migrasi bertahap ke TypeScript |
| `queueStore.js` terlalu besar (141KB) | Sulit maintain & test | 🟠 High | Pecah menjadi sub-stores per concern |
| Tidak ada test coverage saat ini | Regresi tidak terdeteksi | 🟠 High | Mulai dari composable kritis |
| CI/CD belum ada | Manual quality gate | 🟡 Medium | Setup GitHub Actions |
| Beberapa file backup (`.txt`, `old_*.vue`) masih ada | Noise di codebase | 🟢 Low | Cleanup & gitignore |

---

## 10. Changelog

| Versi | Tanggal | Author | Perubahan |
|-------|---------|--------|-----------|
| 1.0.0 | 2026-05-25 | Akbar | Initial QMD — dibuat berdasarkan kondisi project aktual |
