feat: implement proxy routes for APIs, add Pinia stores for loket, clinic, and queue management, and create new pages for admin and anjungan dashboard.
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import { defineEventHandler, proxyRequest } from 'h3'
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const config = useRuntimeConfig()
|
||||
const config = useRuntimeConfig(event)
|
||||
const targetBase = config.public.verificationApiBaseUrl || 'http://10.10.123.140:8089/api/v1'
|
||||
|
||||
// Extract path suffix after /klinik-api
|
||||
@@ -20,7 +20,7 @@ export default defineEventHandler(async (event) => {
|
||||
}
|
||||
|
||||
// Spoof the origin and referer to match the server's trusted/whitelisted client origin
|
||||
const proxyOrigin = config.proxyClientOrigin || 'http://10.10.150.175:3000'
|
||||
const proxyOrigin = config.proxyClientOrigin as string
|
||||
headers.origin = proxyOrigin
|
||||
headers.referer = proxyOrigin.endsWith('/') ? proxyOrigin : `${proxyOrigin}/`
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { defineEventHandler, proxyRequest } from 'h3'
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const config = useRuntimeConfig()
|
||||
const config = useRuntimeConfig(event)
|
||||
const targetBase = config.public.externalApiBaseUrl || 'http://10.10.123.135:8084/api/v1'
|
||||
|
||||
// Extract path suffix after /stats-api
|
||||
@@ -20,7 +20,7 @@ export default defineEventHandler(async (event) => {
|
||||
}
|
||||
|
||||
// Spoof the origin and referer to match the server's trusted/whitelisted client origin
|
||||
const proxyOrigin = config.proxyClientOrigin || 'http://10.10.150.175:3000'
|
||||
const proxyOrigin = config.proxyClientOrigin as string
|
||||
headers.origin = proxyOrigin
|
||||
headers.referer = proxyOrigin.endsWith('/') ? proxyOrigin : `${proxyOrigin}/`
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { defineEventHandler, proxyRequest } from 'h3'
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const config = useRuntimeConfig()
|
||||
const config = useRuntimeConfig(event)
|
||||
const targetBase = config.public.externalApiBaseUrl || 'http://10.10.123.135:8084/api/v1'
|
||||
|
||||
// Extract path suffix after /visit-api
|
||||
@@ -20,7 +20,7 @@ export default defineEventHandler(async (event) => {
|
||||
}
|
||||
|
||||
// Spoof the origin and referer to match the server's trusted/whitelisted client origin
|
||||
const proxyOrigin = config.proxyClientOrigin || 'http://10.10.150.175:3000'
|
||||
const proxyOrigin = config.proxyClientOrigin as string
|
||||
headers.origin = proxyOrigin
|
||||
headers.referer = proxyOrigin.endsWith('/') ? proxyOrigin : `${proxyOrigin}/`
|
||||
|
||||
@@ -18,8 +18,8 @@ export interface VisitStats {
|
||||
|
||||
export const useVisitAPI = () => {
|
||||
const config = useRuntimeConfig();
|
||||
// We use the configured proxy path to avoid CORS issues
|
||||
const statsURL = '/stats-api/visit/stats';
|
||||
// We use the configured external API URL directly
|
||||
const statsURL = `${config.public.externalApiBaseUrl}/visit/stats`;
|
||||
|
||||
/**
|
||||
* Fetch visit statistics
|
||||
|
||||
@@ -26,6 +26,9 @@ services:
|
||||
- NUXT_PUBLIC_WS_API_URL=${WS_API_URL}
|
||||
- NUXT_SESSION_DURATION_HOURS=${SESSION_DURATION_HOURS}
|
||||
- NUXT_OAUTH_STATE_DURATION_MINUTES=${OAUTH_STATE_DURATION_MINUTES}
|
||||
- NUXT_PROXY_CLIENT_ORIGIN=${PROXY_CLIENT_ORIGIN}
|
||||
- NUXT_PROXY_TARGET_HOST_FALLBACK=${PROXY_TARGET_HOST_FALLBACK}
|
||||
- NUXT_PROXY_TARGET_HOST_KLINIK_FALLBACK=${PROXY_TARGET_HOST_KLINIK_FALLBACK}
|
||||
volumes:
|
||||
# Mount local data directory to persist the sqlite users database
|
||||
- ./data:/app/data
|
||||
|
||||
@@ -809,6 +809,7 @@ const queueStore = useQueueStore();
|
||||
const masterStore = useMasterStore();
|
||||
const clinicStore = useClinicStore();
|
||||
const ruangStore = useRuangStore();
|
||||
const config = useRuntimeConfig();
|
||||
|
||||
const kodeKlinik = computed(() => {
|
||||
const kode = route.params.kodeKlinik;
|
||||
@@ -931,7 +932,6 @@ const filterOptionsList = {
|
||||
};
|
||||
|
||||
// WebSocket configuration
|
||||
const config = useRuntimeConfig();
|
||||
|
||||
// Use a DETERMINISTIC client ID so that other pages can broadcast directly to this admin.
|
||||
// Format: admin-klinik-ruang-{kodeKlinik} — stable and targetable from any device.
|
||||
@@ -1717,7 +1717,7 @@ const handleCallPatientByTipe = async (ruang, tipeLayanan) => {
|
||||
visit_code: patient.barcode || patient.visitCode,
|
||||
visit_status_id: [visitStatusId]
|
||||
};
|
||||
const visitApiBase = '/visit-api';
|
||||
const visitApiBase = config.public.externalApiBaseUrl || 'http://10.10.123.135:8084/api/v1';
|
||||
const apiResponse = await fetch(`${visitApiBase}/visit/status/finish`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
|
||||
@@ -339,6 +339,7 @@ const loketStore = useLoketStore();
|
||||
const clinicStore = useClinicStore();
|
||||
const ruangStore = useRuangStore();
|
||||
const { printTicketFromPatient } = useThermalPrint();
|
||||
const config = useRuntimeConfig();
|
||||
|
||||
// Broadcast Channel
|
||||
let broadcastChannel = null;
|
||||
@@ -503,7 +504,7 @@ const apiQuota = ref(null);
|
||||
// Fetch latest quota data specifically for this loket
|
||||
const fetchQuotaFromAPI = async () => {
|
||||
try {
|
||||
const apiBase = '/klinik-api';
|
||||
const apiBase = config.public.verificationApiBaseUrl || 'http://10.10.123.140:8089/api/v1';
|
||||
const response = await fetch(
|
||||
`${apiBase}/klinik/loket`,
|
||||
);
|
||||
@@ -1232,7 +1233,7 @@ const buatAntreanKlinikRuang = async (klinikRuang, ruang) => {
|
||||
|
||||
console.log("📤 Sending visit ticket to API:", visitTicketBody);
|
||||
|
||||
const visitApiBase = '/visit-api';
|
||||
const visitApiBase = config.public.externalApiBaseUrl || 'http://10.10.123.135:8084/api/v1';
|
||||
const visitResponse = await fetch(
|
||||
`${visitApiBase}/visit/ticket/klinik`,
|
||||
{
|
||||
|
||||
@@ -699,7 +699,11 @@
|
||||
</v-snackbar>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="!anjunganData" class="not-found">
|
||||
<div v-else-if="anjunganStore.isLoading" class="not-found">
|
||||
<v-progress-circular indeterminate color="primary" size="64"></v-progress-circular>
|
||||
<p class="mt-4">Memuat Konfigurasi Anjungan...</p>
|
||||
</div>
|
||||
<div v-else class="not-found">
|
||||
<v-icon size="64" color="grey">mdi-alert-circle-outline</v-icon>
|
||||
<p>Anjungan tidak ditemukan</p>
|
||||
<v-btn color="primary" variant="flat" @click="backToList">Kembali</v-btn>
|
||||
@@ -791,6 +795,10 @@ onMounted(async () => {
|
||||
// Wait for stores to be hydrated and ready
|
||||
await nextTick();
|
||||
|
||||
if (anjunganItems.value.length === 0) {
|
||||
await anjunganStore.fetchAnjungan();
|
||||
}
|
||||
|
||||
// Initial fetch
|
||||
await fetchAllData();
|
||||
|
||||
|
||||
@@ -746,7 +746,11 @@
|
||||
</v-snackbar>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="!anjunganData" class="not-found">
|
||||
<div v-else-if="anjunganStore.isLoading" class="not-found">
|
||||
<v-progress-circular indeterminate color="primary" size="64"></v-progress-circular>
|
||||
<p class="mt-4">Memuat Konfigurasi Anjungan...</p>
|
||||
</div>
|
||||
<div v-else class="not-found">
|
||||
<v-icon size="64" color="grey">mdi-alert-circle-outline</v-icon>
|
||||
<p>Anjungan tidak ditemukan</p>
|
||||
<v-btn color="primary" variant="flat" @click="backToList">Kembali</v-btn>
|
||||
@@ -842,6 +846,10 @@ onMounted(async () => {
|
||||
// Wait for stores to be hydrated and ready
|
||||
await nextTick();
|
||||
|
||||
if (anjunganItems.value.length === 0) {
|
||||
await anjunganStore.fetchAnjungan();
|
||||
}
|
||||
|
||||
// Initial fetch
|
||||
await fetchAllData();
|
||||
|
||||
|
||||
@@ -515,7 +515,7 @@ const initWebSocketLocal = () => {
|
||||
wsInstance = useWebSocket({
|
||||
url: wsBaseUrl,
|
||||
clientId: anjunganClientId.value,
|
||||
fallbackPostUrl: '/stats-api/ws',
|
||||
fallbackPostUrl: `${config.public.externalApiBaseUrl}/ws`,
|
||||
onMessage: (data) => {
|
||||
console.log('📨 WebSocket message received:', data)
|
||||
fetchAllData()
|
||||
|
||||
@@ -591,7 +591,7 @@ const initWebSocketLocal = () => {
|
||||
wsInstance = useWebSocket({
|
||||
url: wsBaseUrl,
|
||||
clientId: anjunganClientId.value,
|
||||
fallbackPostUrl: '/stats-api/ws',
|
||||
fallbackPostUrl: `${config.public.externalApiBaseUrl}/ws`,
|
||||
onMessage: (data) => {
|
||||
console.log('📨 WebSocket raw message received:', data)
|
||||
let messageData = data
|
||||
|
||||
@@ -941,7 +941,7 @@ const initWebSocketLocal = () => {
|
||||
wsInstance = useWebSocket({
|
||||
url: wsBaseUrl,
|
||||
clientId: anjunganClientId.value,
|
||||
fallbackPostUrl: '/stats-api/ws',
|
||||
fallbackPostUrl: `${config.public.externalApiBaseUrl}/ws`,
|
||||
onMessage: (data) => {
|
||||
console.log('📨 WebSocket message received:', data)
|
||||
const messageData = data?.data || data
|
||||
|
||||
@@ -3,6 +3,7 @@ import { defineStore } from 'pinia';
|
||||
import { ref, computed } from 'vue';
|
||||
|
||||
export const useClinicStore = defineStore('clinic', () => {
|
||||
const config = useRuntimeConfig();
|
||||
|
||||
// Data clinics - Single source of truth untuk semua data klinik
|
||||
// Includes basic info (name, kode, icon, doctors, shifts) + master config (totalQuota, jamShiftPerHari, jadwalKlinik, tanggalTutup)
|
||||
@@ -744,7 +745,7 @@ export const useClinicStore = defineStore('clinic', () => {
|
||||
|
||||
let rawData;
|
||||
try {
|
||||
rawData = await $fetch('/klinik-api/klinik/reguler');
|
||||
rawData = await $fetch(`${config.public.verificationApiBaseUrl}/klinik/reguler`);
|
||||
} catch (error) {
|
||||
// Handle Rate Limiting with exponential backoff
|
||||
if (error.response?.status === 429 && retryCount < 3) {
|
||||
|
||||
@@ -68,6 +68,7 @@ const getPelayananContohDariAnjungan = (anjunganItems) => {
|
||||
export const useLoketStore = defineStore('loket', () => {
|
||||
const clinicStore = useClinicStore();
|
||||
const anjunganStore = useAnjunganStore();
|
||||
const config = useRuntimeConfig();
|
||||
|
||||
// ============================================
|
||||
// STATE - SEPARATED DATA SOURCES
|
||||
@@ -289,7 +290,7 @@ export const useLoketStore = defineStore('loket', () => {
|
||||
|
||||
let rawData;
|
||||
try {
|
||||
rawData = await $fetch('/klinik-api/klinik/loket');
|
||||
rawData = await $fetch(`${config.public.verificationApiBaseUrl}/klinik/loket`);
|
||||
} catch (error) {
|
||||
if (error.response?.status === 429 && retryCount < 3) {
|
||||
const delay = (retryCount + 1) * 1500;
|
||||
@@ -428,7 +429,7 @@ export const useLoketStore = defineStore('loket', () => {
|
||||
|
||||
let rawData;
|
||||
try {
|
||||
rawData = await $fetch(`/klinik-api/klinik/loket/${loketId}`);
|
||||
rawData = await $fetch(`${config.public.verificationApiBaseUrl}/klinik/loket/${loketId}`);
|
||||
} catch (error) {
|
||||
throw new Error(`HTTP error! status: ${error.response?.status || error.message}`);
|
||||
}
|
||||
|
||||
@@ -100,7 +100,7 @@ export const useQueueStore = defineStore('queue', () => {
|
||||
throw new Error(`Klinik ID tidak ditemukan untuk kode: ${kodeKlinik}`);
|
||||
}
|
||||
|
||||
const url = `/visit-api/visit?klinik_id=${clinic.id}&limit=500`;
|
||||
const url = `${config.public.externalApiBaseUrl}/visit?klinik_id=${clinic.id}&limit=500`;
|
||||
console.log(`🔄 [queueStore] Fetching patients for clinic ${kodeKlinik} (ID: ${clinic.id})...`);
|
||||
|
||||
const response = await fetch(url);
|
||||
@@ -429,7 +429,7 @@ export const useQueueStore = defineStore('queue', () => {
|
||||
const { connect, disconnect, sendViaPost, isConnected } = useWebSocket({
|
||||
url: wsBaseUrl,
|
||||
clientId: wsClientId,
|
||||
fallbackPostUrl: '/stats-api/ws',
|
||||
fallbackPostUrl: `${config.public.externalApiBaseUrl}/ws`,
|
||||
reconnectInterval: 2000, // 2 seconds between reconnect attempts
|
||||
maxReconnectAttempts: 9999, // Effectively infinite — never give up on remote machines
|
||||
onOpen: () => {
|
||||
@@ -2789,7 +2789,7 @@ const fetchPatientsForLoket = async (loketId, force = false) => {
|
||||
case "terlambat":
|
||||
// Send API call to update status to 33 (TR PEMERIKSAAN)
|
||||
try {
|
||||
const visitApiBase = '/visit-api';
|
||||
const visitApiBase = config.public.externalApiBaseUrl || 'http://10.10.123.135:8084/api/v1';
|
||||
const apiUrl = `${visitApiBase}/visit/status/finish`;
|
||||
const requestBody = {
|
||||
patient_visit_healthcare_service_id: patient.healthcareServiceId,
|
||||
@@ -2814,7 +2814,7 @@ const fetchPatientsForLoket = async (loketId, force = false) => {
|
||||
case "pending":
|
||||
// Send API call to update status to 32 (PE PEMERIKSAAN)
|
||||
try {
|
||||
const visitApiBase = '/visit-api';
|
||||
const visitApiBase = config.public.externalApiBaseUrl || 'http://10.10.123.135:8084/api/v1';
|
||||
const apiUrl = `${visitApiBase}/visit/status/finish`;
|
||||
const requestBody = {
|
||||
patient_visit_healthcare_service_id: patient.healthcareServiceId,
|
||||
|
||||
Reference in New Issue
Block a user