diff --git a/HAK_AKSES_API_ENDPOINT.md b/HAK_AKSES_API_ENDPOINT.md index 3ff87a8..aac1469 100644 --- a/HAK_AKSES_API_ENDPOINT.md +++ b/HAK_AKSES_API_ENDPOINT.md @@ -1,15 +1,18 @@ # API Endpoint Documentation - Hak Akses ## Deskripsi + Dokumentasi ini menjelaskan semua API endpoint yang diperlukan untuk fitur **Hak Akses** di halaman `/pages/Setting/HakAkses.vue`. Endpoint-endpoint ini akan dihubungkan ke backend API yang disediakan oleh tim backend. --- ## Base URL + ``` {API_BASE_URL}/api/v1 ``` -**Catatan:** Ganti `{API_BASE_URL}` dengan URL backend yang sebenarnya (contoh: `http://10.10.150.131:8089`) + +**Catatan:** Ganti `{API_BASE_URL}` dengan URL backend yang sebenarnya (contoh: `http://10.10.123.140:8089/`) --- @@ -18,12 +21,15 @@ Dokumentasi ini menjelaskan semua API endpoint yang diperlukan untuk fitur **Hak Mengambil daftar semua users beserta roles, groups, dan informasi lainnya yang diperlukan untuk form hak akses. ### Endpoint + ``` GET /api/users/list ``` ### Request + **Headers:** + ``` Content-Type: application/json Accept: application/json @@ -32,7 +38,9 @@ Accept: application/json **Query Parameters:** Tidak ad ### Response + **Success (200 OK):** + ```json [ { @@ -46,10 +54,7 @@ Accept: application/json "realmRoles": ["default-roles-sandbox"], "accountRoles": [], "resourceRoles": [], - "groups": [ - "/Instalasi STIM/Devops/Superadmin", - "/Instalasi STIM/Admin" - ], + "groups": ["/Instalasi STIM/Devops/Superadmin", "/Instalasi STIM/Admin"], "given_name": "John", "family_name": "Doe", "createdAt": 1704067200, @@ -59,6 +64,7 @@ Accept: application/json ``` **Error (500 Internal Server Error):** + ```json { "statusCode": 500, @@ -67,6 +73,7 @@ Accept: application/json ``` ### Field Description + - `id`: Unique identifier user - `namaLengkap`: Nama lengkap user - `namaUser`: Username untuk login @@ -90,12 +97,15 @@ Accept: application/json Mengambil daftar permissions dari backend berdasarkan role dan group yang dipilih. ### Endpoint + ``` GET /api/v1/permission ``` ### Request + **Headers:** + ``` Content-Type: application/json Accept: application/json @@ -108,12 +118,15 @@ Accept: application/json | `groups` | string | Yes | Group name atau group path (contoh: "STIM", "/Instalasi STIM/Devops/Superadmin") | **Example:** + ``` GET /api/v1/permission?roles=superadmin&groups=STIM ``` ### Response + **Success (200 OK):** + ```json { "message": "Data permission berhasil diambil", @@ -169,6 +182,7 @@ GET /api/v1/permission?roles=superadmin&groups=STIM ``` **Error (400 Bad Request):** + ```json { "statusCode": 400, @@ -177,6 +191,7 @@ GET /api/v1/permission?roles=superadmin&groups=STIM ``` **Error (500 Internal Server Error):** + ```json { "message": "Failed to fetch permissions", @@ -189,6 +204,7 @@ GET /api/v1/permission?roles=superadmin&groups=STIM ``` ### Field Description + - `id`: Unique identifier permission - `create`: Boolean, permission untuk create/tambah data - `read`: Boolean, permission untuk read/lihat data @@ -209,12 +225,15 @@ GET /api/v1/permission?roles=superadmin&groups=STIM Mengambil daftar semua hak akses yang sudah tersimpan. ### Endpoint + ``` GET /api/v1/hak-akses ``` ### Request + **Headers:** + ``` Content-Type: application/json Accept: application/json @@ -223,7 +242,9 @@ Accept: application/json **Query Parameters:** Tidak ada ### Response + **Success (200 OK):** + ```json [ { @@ -262,6 +283,7 @@ Accept: application/json ``` **Error (500 Internal Server Error):** + ```json { "statusCode": 500, @@ -270,6 +292,7 @@ Accept: application/json ``` ### Field Description + - `id`: Unique identifier hak akses - `userId`: ID user (optional, untuk backward compatibility) - `namaLengkap`: Nama lengkap user (optional) @@ -297,18 +320,22 @@ Accept: application/json Membuat hak akses baru (group-based atau individual). ### Endpoint + ``` POST /api/v1/hak-akses ``` ### Request + **Headers:** + ``` Content-Type: application/json Accept: application/json ``` **Body:** + ```json { "role": "superadmin", @@ -338,7 +365,9 @@ Accept: application/json ``` ### Response + **Success (201 Created):** + ```json { "success": true, @@ -367,6 +396,7 @@ Accept: application/json ``` **Error (400 Bad Request):** + ```json { "statusCode": 400, @@ -375,6 +405,7 @@ Accept: application/json ``` **Error (409 Conflict):** + ```json { "statusCode": 409, @@ -383,12 +414,15 @@ Accept: application/json ``` ### Field Description + **Required Fields:** + - `role`: Role name (string, required) - `group`: Group name (string, required) - `hakAksesMenu`: Array of menu permissions (array, required) **Optional Fields:** + - `userId`: ID user (string, optional) - `namaLengkap`: Nama lengkap (string, optional) - `namaUser`: Username (string, optional) @@ -404,12 +438,15 @@ Accept: application/json Mengupdate hak akses yang sudah ada. ### Endpoint + ``` PATCH /api/v1/hak-akses/{id} ``` ### Request + **Headers:** + ``` Content-Type: application/json Accept: application/json @@ -421,6 +458,7 @@ Accept: application/json | `id` | integer | Yes | ID hak akses yang akan diupdate | **Body (semua field optional, hanya kirim field yang ingin diupdate):** + ```json { "role": "admin", @@ -440,7 +478,9 @@ Accept: application/json ``` ### Response + **Success (200 OK):** + ```json { "success": true, @@ -469,6 +509,7 @@ Accept: application/json ``` **Error (400 Bad Request):** + ```json { "statusCode": 400, @@ -477,6 +518,7 @@ Accept: application/json ``` **Error (404 Not Found):** + ```json { "statusCode": 404, @@ -485,6 +527,7 @@ Accept: application/json ``` **Error (500 Internal Server Error):** + ```json { "statusCode": 500, @@ -499,12 +542,15 @@ Accept: application/json Menghapus hak akses. ### Endpoint + ``` DELETE /api/v1/hak-akses/{id} ``` ### Request + **Headers:** + ``` Content-Type: application/json Accept: application/json @@ -516,7 +562,9 @@ Accept: application/json | `id` | integer | Yes | ID hak akses yang akan dihapus | ### Response + **Success (200 OK):** + ```json { "success": true, @@ -525,6 +573,7 @@ Accept: application/json ``` **Error (400 Bad Request):** + ```json { "statusCode": 400, @@ -533,6 +582,7 @@ Accept: application/json ``` **Error (404 Not Found):** + ```json { "statusCode": 404, @@ -541,6 +591,7 @@ Accept: application/json ``` **Error (500 Internal Server Error):** + ```json { "statusCode": 500, @@ -558,11 +609,13 @@ Sistem akan otomatis memetakan `pagename` dari API permission ke nama menu di si 2. **Partial match**: `pagename.toLowerCase().includes(menu.name.toLowerCase())` atau sebaliknya ### Contoh Mapping: + - `pagename: "Halaman Utama"` → Menu: "Dashboard" - `pagename: "Pengaturan"` → Menu: "Master Data" - `pagename: "Hak Akses"` → Menu: "Hak Akses" ### Permission Mapping: + - `read: true` → `canView: true` - `create: true` → `canAdd: true` - `update: true` → `canEdit: true` @@ -574,17 +627,21 @@ Sistem akan otomatis memetakan `pagename` dari API permission ke nama menu di si ## Group-Based Access Sistem mendukung **Group-Based Access** dimana: + - Satu hak akses dapat diberikan ke semua user yang memiliki group dan role yang sama - Setiap user dapat memiliki multiple hak akses dari berbagai group - Hak akses disimpan dengan flag `isGroupBased: true` dan `groupPath` untuk identifikasi ### Contoh: + Jika hak akses dibuat dengan: + - `role: "superadmin"` - `groupPath: "/Instalasi STIM/Devops/Superadmin"` - `isGroupBased: true` Maka semua user yang memiliki: + - Role: "superadmin" **DAN** - Group: "/Instalasi STIM/Devops/Superadmin" @@ -604,6 +661,7 @@ Semua endpoint harus mengembalikan error dengan format konsisten: ``` **HTTP Status Codes:** + - `200 OK`: Request berhasil - `201 Created`: Resource berhasil dibuat - `400 Bad Request`: Request tidak valid (missing required fields, invalid format) @@ -618,6 +676,7 @@ Semua endpoint harus mengembalikan error dengan format konsisten: **Catatan:** Tim backend perlu menentukan apakah endpoint-endpoint ini memerlukan authentication token atau tidak. Jika diperlukan, tambahkan: **Headers:** + ``` Authorization: Bearer {access_token} ``` @@ -629,18 +688,21 @@ Authorization: Bearer {access_token} ### Contoh Request dengan cURL: **1. Get Users List:** + ```bash curl -X GET "http://{API_BASE_URL}/api/users/list" \ -H "Content-Type: application/json" ``` **2. Get Permissions:** + ```bash curl -X GET "http://{API_BASE_URL}/api/v1/permission?roles=superadmin&groups=STIM" \ -H "Content-Type: application/json" ``` **3. Create Hak Akses:** + ```bash curl -X POST "http://{API_BASE_URL}/api/v1/hak-akses" \ -H "Content-Type: application/json" \ @@ -664,6 +726,7 @@ curl -X POST "http://{API_BASE_URL}/api/v1/hak-akses" \ ``` **4. Update Hak Akses:** + ```bash curl -X PATCH "http://{API_BASE_URL}/api/v1/hak-akses/1" \ -H "Content-Type: application/json" \ @@ -674,6 +737,7 @@ curl -X PATCH "http://{API_BASE_URL}/api/v1/hak-akses/1" \ ``` **5. Delete Hak Akses:** + ```bash curl -X DELETE "http://{API_BASE_URL}/api/v1/hak-akses/1" \ -H "Content-Type: application/json" @@ -697,4 +761,3 @@ curl -X DELETE "http://{API_BASE_URL}/api/v1/hak-akses/1" \ ## Kontak Jika ada pertanyaan atau perubahan requirement, silakan hubungi tim frontend. - diff --git a/PLACEHOLDER_API_README.md b/PLACEHOLDER_API_README.md index dfa439a..3f0c577 100644 --- a/PLACEHOLDER_API_README.md +++ b/PLACEHOLDER_API_README.md @@ -1,21 +1,27 @@ # Placeholder API Permission ## Deskripsi + Placeholder API untuk permission digunakan untuk testing dan development ketika backend API tidak tersedia. ## Cara Menggunakan ### 1. Menggunakan Placeholder API secara Default -Placeholder API akan otomatis digunakan sebagai fallback jika backend API (`http://10.10.150.131:8089/api/v1/permission`) tidak dapat diakses. + +Placeholder API akan otomatis digunakan sebagai fallback jika backend API (`http://10.10.123.140:8089/api/v1/permission`) tidak dapat diakses. ### 2. Memaksa Menggunakan Placeholder API + Tambahkan query parameter `usePlaceholder=true` pada request: + ``` GET /api/permission?roles=superadmin&groups=STIM&usePlaceholder=true ``` ### 3. Menonaktifkan Placeholder API + Tambahkan query parameter `usePlaceholder=false` pada request: + ``` GET /api/permission?roles=superadmin&groups=STIM&usePlaceholder=false ``` @@ -23,7 +29,9 @@ GET /api/permission?roles=superadmin&groups=STIM&usePlaceholder=false ## Data Placeholder yang Tersedia ### Role: superadmin, Group: STIM + Data placeholder mengembalikan 5 permission items: + - Halaman Utama (read: true, active: true) - Pengaturan (read: true, active: true) - Halaman (read: true, active: true, disable: true) @@ -32,6 +40,7 @@ Data placeholder mengembalikan 5 permission items: ## Mapping Pagename ke Menu Sidebar Sistem akan otomatis memetakan pagename dari API ke nama menu di sidebar: + - "Halaman Utama" → "Dashboard" - "Pengaturan" → "Master Data" - "Halaman" → "Master Data" @@ -40,6 +49,7 @@ Sistem akan otomatis memetakan pagename dari API ke nama menu di sidebar: ## Testing dengan User bayurssa Untuk testing dengan user email "bayurssa": + 1. Pastikan user memiliki role "superadmin" dan group "STIM" di Keycloak 2. Login dengan email "bayurssa" dan password "12345" 3. Sistem akan otomatis menggunakan placeholder API jika backend tidak tersedia @@ -50,21 +60,24 @@ Untuk testing dengan user email "bayurssa": Sistem secara otomatis melakukan normalisasi untuk role dan group: ### Normalisasi Role + - `default-roles-sandbox` → `superadmin` - Role lain akan digunakan apa adanya (lowercase) ### Normalisasi Group + - `Instalasi STIM` → `STIM` - Group yang mengandung "Instalasi" akan diekstrak untuk mengambil bagian "STIM" - Group lain akan digunakan apa adanya (uppercase) ### Contoh Mapping + - Role: `default-roles-sandbox` + Group: `Instalasi STIM` → API akan menggunakan `roles=superadmin&groups=STIM` - Role: `default-roles-sandbox` + Group: `STIM` → API akan menggunakan `roles=superadmin&groups=STIM` ## Catatan + - Placeholder API hanya tersedia untuk kombinasi role dan group yang sudah didefinisikan - Sistem akan otomatis melakukan normalisasi role dan group sebelum memanggil API - Jika role/group tidak ditemukan di placeholder, sistem akan mencoba menggunakan backend API - Jika backend API juga gagal, sistem akan mengembalikan data kosong - diff --git a/nuxt.config.ts b/nuxt.config.ts index 9aaf281..21e93eb 100644 --- a/nuxt.config.ts +++ b/nuxt.config.ts @@ -3,9 +3,9 @@ import vuetify, { transformAssetUrls } from "vite-plugin-vuetify"; export default defineNuxtConfig({ compatibilityDate: "2025-05-15", devtools: { - enabled: true, + enabled: process.env.ENABLE_DEVTOOLS === 'true', timeline: { - enabled: true, + enabled: false, }, }, @@ -81,8 +81,8 @@ export default defineNuxtConfig({ // authUrl: process.env.AUTH_ORIGIN || "http://10.10.150.175:3001", // authUrl: process.env.AUTH_ORIGIN || "http://localhost:3001", wsBaseUrl: process.env.WS_BASE_URL || 'ws://10.10.123.135:8084/api/v1/ws', - ekstrakExpertiseUrl: process.env.EKSTRAK_EXPERTISE_URL || 'http://10.10.123.218/ekstrakexpertise', verificationApiBaseUrl: process.env.VERIFICATION_API_BASE_URL || 'http://10.10.123.140:8089/api/v1', + }, }, diff --git a/pages/Anjungan/Anjungan/[id].vue b/pages/Anjungan/Anjungan/[id].vue index d2b57d1..9b8f1db 100644 --- a/pages/Anjungan/Anjungan/[id].vue +++ b/pages/Anjungan/Anjungan/[id].vue @@ -229,12 +229,14 @@ size="large" block @click="registerPatientDirectly('Eksekutif')" - :disabled="isShift1Full(selectedClinic) || !selectedDoctor" + :loading="isRegistering" + :disabled="isRegistering || isShift1Full(selectedClinic) || !selectedDoctor" :class="{ 'btn-disabled': - isShift1Full(selectedClinic) || !selectedDoctor, + isRegistering || isShift1Full(selectedClinic) || !selectedDoctor, }" > + DAFTAR SEKARANG
@@ -258,9 +260,11 @@ size="large" block @click="registerPatientDirectly('BPJS')" - :disabled="isShift1Full(selectedClinic)" - :class="{ 'btn-disabled': isShift1Full(selectedClinic) }" + :loading="isRegistering" + :disabled="isRegistering || isShift1Full(selectedClinic)" + :class="{ 'btn-disabled': isRegistering || isShift1Full(selectedClinic) }" > + BPJS @@ -271,9 +275,11 @@ size="large" block @click="registerPatientDirectly('UMUM')" - :disabled="isShift1Full(selectedClinic)" - :class="{ 'btn-disabled': isShift1Full(selectedClinic) }" + :loading="isRegistering" + :disabled="isRegistering || isShift1Full(selectedClinic)" + :class="{ 'btn-disabled': isRegistering || isShift1Full(selectedClinic) }" > + UMUM / JKMM / SPM / DLL @@ -292,7 +298,9 @@ size="large" block @click="selectVisitType('JADWAL_LAIN')" + :disabled="isRegistering" > + JADWAL LAIN @@ -303,9 +311,11 @@ size="large" block @click="registerPatientDirectly('FAST_TRACK')" - :disabled="isShift1Full(selectedClinic)" - :class="{ 'btn-disabled': isShift1Full(selectedClinic) }" + :loading="isRegistering" + :disabled="isRegistering || isShift1Full(selectedClinic)" + :class="{ 'btn-disabled': isRegistering || isShift1Full(selectedClinic) }" > + FAST TRACK @@ -321,9 +331,10 @@ size="large" block @click="selectVisitType('JADWAL_LAIN')" - :disabled="!selectedDoctor" - :class="{ 'btn-disabled': !selectedDoctor }" + :disabled="isRegistering || !selectedDoctor" + :class="{ 'btn-disabled': isRegistering || !selectedDoctor }" > + JADWAL LAIN
@@ -472,7 +483,10 @@ class="text-white" variant="flat" @click="submitBooking" + :loading="isRegistering" + :disabled="isRegistering" > + Simpan @@ -543,7 +557,10 @@ class="text-white" variant="flat" @click="submitFastTrackForm" + :loading="isRegistering" + :disabled="isRegistering" > + Lanjutkan @@ -746,7 +763,10 @@ const fetchAllData = async () => { if (anjunganData.value?.jenisPasien === "Reguler") { await clinicStore.fetchRegulerClinics(); } + // Ensure loket configuration is loaded for API registration matching + await loketStore.fetchLoketFromAPI(); queueStore.ensureInitialData(); + console.log('✅ Anjungan refresh: Success'); } catch (err) { console.error('❌ Anjungan refresh error:', err); @@ -906,6 +926,8 @@ const selectedDoctor = ref(null); const snackbar = ref(false); const snackbarText = ref(""); const snackbarColor = ref("success"); +const isRegistering = ref(false); + // Fast Track form data const fastTrackForm = ref({ @@ -1190,7 +1212,9 @@ const registerPatient = async ( isFastTrack = false, fastTrackData = null, ) => { + isRegistering.value = true; try { + // Validasi bahwa fungsi registerPatientFromAnjungan ada if (!queueStore) { console.error("❌ queueStore is not initialized"); @@ -1276,10 +1300,14 @@ const registerPatient = async ( } finally { selectedClinic.value = null; selectedDoctor.value = null; + isRegistering.value = false; } + }; const submitBooking = async () => { + if (isRegistering.value) return; + if ( !bookingForm.value.date || !bookingForm.value.shift || diff --git a/stores/loketStore.js b/stores/loketStore.js index 74b53ab..115b148 100644 --- a/stores/loketStore.js +++ b/stores/loketStore.js @@ -1,4 +1,4 @@ -// stores/loketStore.js + // stores/loketStore.js import { defineStore } from 'pinia'; import { ref, computed } from 'vue'; import { useClinicStore } from './clinicStore'; @@ -25,7 +25,7 @@ const getPelayananContohDariAnjungan = (anjunganItems) => { // Jika tidak ada data dari anjungan, gunakan data default dari master anjungan if (klinikAnjunganReguler.length === 0) { - klinikAnjunganReguler = ['AN', 'AS', 'BD', 'GR', 'HO', 'GI', 'GZ', 'IP', 'JT', 'JW', 'KK', 'MT', 'OB', 'PR', 'RT', 'RM', 'SR']; + klinikAnjunganReguler = ['AK', 'AN', 'AS', 'BD', 'GR', 'HO', 'GI', 'GZ', 'IP', 'JT', 'JW', 'KK', 'MT', 'OB', 'PR', 'RT', 'RM', 'SR']; } // Distribusi pelayanan ke beberapa loket sebagai contoh @@ -296,7 +296,8 @@ export const useLoketStore = defineStore('loket', () => { try { console.log(`🔄 [loketStore] Fetching loket configuration (Try ${retryCount + 1})...`); - const response = await fetch('http://10.10.123.140:8089/api/v1/klinik/loket'); + const response = await fetch('http://10.10.123.140:8089/api/v1/loket'); + if (!response.ok) { if (response.status === 429 && retryCount < 3) { @@ -393,15 +394,15 @@ export const useLoketStore = defineStore('loket', () => { } // FALLBACK 2: If no cache, populate with dummy Reguler data for Dev/Offline mode - console.warn('⚠️ [loketStore] No cache available, using FALLBACK dummy data...'); + console.warn('⚠️ [loketStore] No cache available, using FALLBACK dummy data (v2 - with AK and JKN)...'); const dummyLokets = Array.from({length: 6}, (_, i) => ({ id: i + 1, namaLoket: `LOKET ${i + 1} REG (Mock)`, kodeLoket: `L${i+1}`, kuota: 100, - pelayanan: ['UM', 'BP', 'OB', 'AN', 'IP', 'SR', 'TH', 'MT', 'KK', 'PR'], // Mock all services + pelayanan: ['AK', 'AN', 'AS', 'BD', 'GR', 'HO', 'GI', 'GZ', 'IP', 'JT', 'JW', 'KK', 'MT', 'OB', 'PR', 'RT', 'RM', 'SR', 'TH', 'TD', 'KP', 'MC', 'ON', 'JT', 'GG', 'GE'], // Mock all services _spesialisDetail: [], // Empty detail - pembayaran: ['BPJS', 'UMUM'], // Changed to array for consistency + pembayaran: ['JKN', 'UMUM'], // Using JKN to match backend terminology tipeLoket: 'REGULER', // Add tipeLoket for fallback data source: 'api', // Mimic API loketAktif: true, diff --git a/stores/queueStore.js b/stores/queueStore.js index 06f4d2e..d017aa7 100644 --- a/stores/queueStore.js +++ b/stores/queueStore.js @@ -1600,18 +1600,15 @@ const fetchPatientsForLoket = async (loketId, force = false) => { return loketPayments.some(lp => { const normalized = String(lp).toUpperCase().trim(); - // Handle exact match - if (normalized === patientCategory) return true; + const result = (normalized === patientCategory) || + (patientCategory === 'UMUM' && (normalized.includes('UMUM') || normalized.includes('MANDIRI'))) || + (patientCategory === 'JKN' && (normalized.includes('JKN') || normalized.includes('BPJS'))) || + (patientCategory === 'EKSEKUTIF' && (normalized.includes('EKSEKUTIF') || normalized.includes('VIP'))) || + (normalized === 'BPJS' && patientCategory === 'JKN') || + (normalized === 'JKN' && patientCategory === 'JKN'); - // Handle partial matches for complex payment types like "UMUM / JKMM / SPM / DLL" - if (patientCategory === 'UMUM' && normalized.includes('UMUM')) return true; - if (patientCategory === 'JKN' && (normalized.includes('JKN') || normalized.includes('BPJS'))) return true; - if (patientCategory === 'EKSEKUTIF' && (normalized.includes('EKSEKUTIF') || normalized.includes('VIP'))) return true; - - // Handle alias: BPJS = JKN (only this alias is allowed) - if (normalized === 'BPJS' && patientCategory === 'JKN') return true; - - return false; + console.log(` - Checking loket accepts "${lp}" (normalized: "${normalized}") vs patient category "${patientCategory}": ${result}`); + return result; }); }; @@ -3008,7 +3005,15 @@ const fetchPatientsForLoket = async (loketId, force = false) => { console.log('📋 [queueStore] Payment Type:', paymentType, '| Clinic:', clinic.name, '| Clinic Code:', clinic.kode); // 1. Find appropriate idloket based on BOTH clinic code AND payment type + // IMPROVED: Check specifically for API-source lokets (id < 1000) + const apiLoketsExist = loketStore.lokets?.some(l => l.source === 'api' || l.id < 1000); + if (!apiLoketsExist) { + console.log('🔄 [queueStore] API loket data empty, fetching from API...'); + await loketStore.fetchLoketFromAPI(); + } const allLokets = loketStore.lokets || []; + + // Determine payment type for matching (normalize BPJS to JKN for API compatibility) const paymentTypeForMatching = paymentType === "BPJS" ? "JKN" : paymentType;