diff --git a/components/Home.vue b/components/Home.vue index 667b987..0cdbd0c 100644 --- a/components/Home.vue +++ b/components/Home.vue @@ -377,7 +377,7 @@ const loadCarouselItems = async () => { ); carouselItems.value = itemsWithRatio; - console.log('Carousel items loaded:', carouselItems.value); + // console.log('Carousel items loaded:', carouselItems.value); } catch (error) { console.error('Error loading carousel items:', error); } finally { diff --git a/components/PembayaranSukses.vue b/components/PembayaranSukses.vue index 4ee4e53..83f7c2f 100644 --- a/components/PembayaranSukses.vue +++ b/components/PembayaranSukses.vue @@ -48,46 +48,45 @@ -
-
- -
- Nomor RM: - {{ - paymentStore.qrData.display_norm || '-' - }} -
+
+
+
+ Nomor RM: + {{ + paymentStore.qrData.display_norm || "-" + }} +
-
- No. Billing: - {{ - paymentStore.qrData.display_nobill || '-' - }} -
+
+ No. Billing: + {{ + paymentStore.qrData.display_nobill || "-" + }} +
-
- Pasien: - {{ +
+ Pasien: + {{ paymentStore.patientInfo.name - }} -
- -
- Nominal: - {{ + }} +
+ +
+ Nominal: + {{ formatCurrency(paymentStore.patientInfo.amount) - }} -
- -
- Status: - + }} +
+ +
+ Status: + mdi-check Berhasil - + +
+
-
-
@@ -292,5 +291,10 @@ const formatCurrency = (amountString) => { .success-container { max-width: 650px; } + + .logo-section-contact { + display: none; + } + } diff --git a/components/QRISPayment.vue b/components/QRISPayment.vue index 24db124..a8e1da2 100644 --- a/components/QRISPayment.vue +++ b/components/QRISPayment.vue @@ -159,7 +159,7 @@ -
+
diff --git a/composables/useFileSync.ts b/composables/useFileSync.ts index 33c06f7..774513c 100644 --- a/composables/useFileSync.ts +++ b/composables/useFileSync.ts @@ -124,6 +124,39 @@ export function useFileSync() { } + // const tampilFileBrowser = async () => { + + // await syncFiles(); + // if (isSyncing.value) { + // try { + // const localStoreKeys = await localStore.keys() + // // console.log('localStore Keys:', localStoreKeys) + // const files: { name: string; blob: Blob; type: string; url: string }[] = [] + + // for (const key of localStoreKeys) { + // if (!key.endsWith('-meta')) { + // const fileBlob = await localStore.getItem(key) + + // if (fileBlob instanceof Blob) { + // const url = URL.createObjectURL(fileBlob) + // files.push({ name: key, blob: fileBlob, type: fileBlob.type, url: url }) + + // } + // } + // } + // // console.log('Files in IndexedDB:',fileLocal.value) + // return fileLocal.value = files + + // } catch (err) { + // console.error('Gagal memuat file dari IndexedDB:', err) + // } finally { + // isSyncing.value = false + // } + + + // } + // } + const tampilFileBrowser = async () => { await syncFiles(); @@ -144,7 +177,16 @@ export function useFileSync() { } } } - // console.log('Files in IndexedDB:',fileLocal.value) + // console.log('Files in IndexedDB:',files) + // console.log('File Server:', fileServer.value) + const fileTidaksama = files.filter(local => !fileServer.value.some(fs => fs.name === local.name)) + // console.log('File tidak sama dengan server:', fileTidaksama) + for (const namaFile of fileTidaksama) { + const firstNamePart = namaFile.name.split('.')[0]; + console.log('file lokal yang tidak ada di server:', firstNamePart); + await removeFiles(firstNamePart) + } + return fileLocal.value = files } catch (err) { @@ -156,7 +198,6 @@ export function useFileSync() { } } - // Bersihkan object URL biar gak bocor memori onBeforeUnmount(() => { fileLocal.value.forEach(f => URL.revokeObjectURL(f.url)) diff --git a/nuxt.config.ts b/nuxt.config.ts index 5ed84ad..d84a28c 100644 --- a/nuxt.config.ts +++ b/nuxt.config.ts @@ -2,7 +2,19 @@ import vuetify, { transformAssetUrls } from 'vite-plugin-vuetify' export default defineNuxtConfig({ compatibilityDate: '2025-07-15', - devtools: { enabled: true }, + devtools: { + enabled: false, // Enable Nuxt DevTools untuk debugging + timeline: { + enabled: false, // Enable timeline untuk performance monitoring + }, + }, + runtimeConfig: { + public: { + folder_path: "\\\\10.10.123.49\\qris", + url_base: "ws://10.10.150.222:8080/api/v1/ws", + query: "user_id=QRIS&room=BANKJATIM" + } + }, ssr:false, modules: [ '@nuxt/content', diff --git a/pages/index.vue b/pages/index.vue index d127207..2e63e41 100644 --- a/pages/index.vue +++ b/pages/index.vue @@ -47,9 +47,10 @@ import { useWebSocket } from "~/composables/useWebSocket"; const paymentStore = usePaymentStore(); const router = useRouter(); const localIpAddress = ref(null); +const config = useRuntimeConfig() -const WS_URL_BASE = "ws://10.10.150.45:8080/api/v1/ws"; -const WS_QUERY = "user_id=QRIS&room=BANKJATIM"; +const WS_URL_BASE = config.public.url_base; +const WS_QUERY = config.public.query; const fullWsUrl = `${WS_URL_BASE}?${WS_QUERY}`; const { diff --git a/server/api/serverFile/[name].get.ts b/server/api/serverFile/[name].get.ts index 8954bfe..8fb4829 100644 --- a/server/api/serverFile/[name].get.ts +++ b/server/api/serverFile/[name].get.ts @@ -2,8 +2,8 @@ import fs from 'fs' import path from 'path' import { lookup } from 'mime-types' import { createError, setHeader, sendStream } from 'h3' - -const FOLDER_PATH = '\\\\10.10.150.129\\Shared' +const config = useRuntimeConfig() +const FOLDER_PATH = config.public.folder_path; export default defineEventHandler(async (event) => { const rawName = event.context.params?.name as string | undefined diff --git a/server/api/serverFile/files.get.ts b/server/api/serverFile/files.get.ts index 7f41186..12f5d42 100644 --- a/server/api/serverFile/files.get.ts +++ b/server/api/serverFile/files.get.ts @@ -5,9 +5,11 @@ import { promisify } from "util" const readdir = promisify(fs.readdir) const stat = promisify(fs.stat) +const config = useRuntimeConfig() // Lokasi folder network share (pastikan sudah di-mount / accessible dari server) -const FOLDER_PATH = "\\\\10.10.150.129\\Shared" +// const FOLDER_PATH = "\\\\10.10.123.49\\qris" +const FOLDER_PATH = config.public.folder_path; export default defineEventHandler(async (event) => { try { diff --git a/stores/payment.ts b/stores/payment.ts index 2b04453..3fdb3a7 100644 --- a/stores/payment.ts +++ b/stores/payment.ts @@ -1,68 +1,68 @@ // src/stores/payment.ts -import { defineStore } from 'pinia'; +import { defineStore } from "pinia"; -// 1. Definisikan Tipe Data untuk qrData (penting untuk properti expired_at yang di-spread) +// definisi tipe data interface QrData { - qrvalue: string | null; - display_nobill: string | null; - display_name: string | null; - display_amount: string | null; - status: '1' | '2' | '0' | null; // "1": Pending, "2": Sukses, "0": Gagal/Expired - ip: string | null; - // Tambahkan properti yang di-spread dari API, termasuk expired_at - expired_at?: string; // Tambahkan expired_at sebagai properti opsional - [key: string]: any; // Indeks signature untuk properti dinamis (misal: transaction_id, reason) + qrvalue: string | null; + display_nobill: string | null; + display_name: string | null; + display_amount: string | null; + status: "1" | "2" | "0" | null; // "1": Pending, "2": Sukses, "0": Gagal/Pending + ip: string | null; + expired_at?: string; + [key: string]: any; // Indeks signature untuk properti dinamis (misal: transaction_id, reason) } -// 2. Definisikan Tipe Data untuk State +// definisi tipe data untuk State interface PaymentState { - currentStep: 1 | 2 | 3 | 4; - patientInfo: { - name: string; - amount: string; - expiry: string; - }; - qrData: QrData; + currentStep: 1 | 2 | 3 | 4; + patientInfo: { + name: string; + amount: string; + expiry: string; + }; + qrData: QrData; } -// Variabel di luar store untuk menyimpan timer, agar persistensi di luar state Pinia -let autoResetTimer: number | null = null; +let autoResetTimer: number | null = null; -export const usePaymentStore = defineStore('payment', { - state: (): PaymentState => ({ // Gunakan tipedata PaymentState - currentStep: 1, // 1: Menunggu Data, 2: Tampilkan QRIS, 3: Sukses, 4: Gagal +export const usePaymentStore = defineStore("payment", { + state: (): PaymentState => ({ + currentStep: 1, // 1: Menunggu Data, 2: Tampilkan QRIS, 3: Sukses, 4: Gagal/Pending patientInfo: { - name: '', - amount: '', - expiry: '', // Waktu kedaluwarsa diambil dari qrData (via spread) + name: "", + amount: "", + expiry: "", }, qrData: { qrvalue: null, display_nobill: null, display_name: null, display_amount: null, - status: null, + status: null, ip: null, - // created_at DIHAPUS - } as QrData, // Type casting untuk QrData + } as QrData, }), - + getters: { hasQrData: (state): boolean => !!state.qrData.qrvalue, - safeQrValue: (state): string => state.qrData.qrvalue || 'https://www.google.com', - + safeQrValue: (state): string => + state.qrData.qrvalue || "https://www.google.com", + isReadyForStatusCheck: (state): boolean => { return !!(state.qrData.display_nobill && state.qrData.display_name); }, - + debugInfo: (state) => ({ step: state.currentStep, - hasIdentifiers: !!(state.qrData.display_nobill && state.qrData.display_name), + hasIdentifiers: !!( + state.qrData.display_nobill && state.qrData.display_name + ), qrData: state.qrData, patientInfo: state.patientInfo, - }) + }), }, - + actions: { nextStep() { this.currentStep++; @@ -70,32 +70,31 @@ export const usePaymentStore = defineStore('payment', { prevStep() { this.currentStep--; }, - + setStep(step: 1 | 2 | 3 | 4) { console.log(`[STORE] Setting step from ${this.currentStep} to ${step}`); this.currentStep = step; }, startAutoReset() { - if (autoResetTimer) { - clearTimeout(autoResetTimer); - } - console.log('🏠 [STORE] Starting auto return timer (50s) for reset.'); - - // Di TypeScript, setTimeout mengembalikan number - autoResetTimer = setTimeout(() => { - console.log('🏠 [STORE] Auto return to home triggered'); - this.reset(); - }, 10000); + if (autoResetTimer) { + clearTimeout(autoResetTimer); + } + console.log("🏠 [STORE] Starting auto return timer (10s) for reset."); + + autoResetTimer = setTimeout(() => { + console.log("🏠 [STORE] Auto return to home triggered"); + this.reset(); + }, 10000); }, - + reset() { - console.log('--- [STORE] Resetting payment store ---'); + console.log("--- [STORE] Resetting payment store ---"); this.currentStep = 1; this.patientInfo = { - name: '', - amount: '', - expiry: '', + name: "", + amount: "", + expiry: "", }; this.qrData = { qrvalue: null, @@ -105,40 +104,43 @@ export const usePaymentStore = defineStore('payment', { status: null, ip: null, } as QrData; // Pastikan tipe kembali ke QrData - if (autoResetTimer) { - clearTimeout(autoResetTimer); - autoResetTimer = null; - } + if (autoResetTimer) { + clearTimeout(autoResetTimer); + autoResetTimer = null; + } }, - + // Definisikan tipe untuk apiResponse updatePayment(apiResponse: { data: QrData[] }) { console.log("=== [STORE] UPDATE PAYMENT ==="); - + if (!apiResponse?.data || apiResponse.data.length === 0) { console.error("Invalid API response structure or empty data array"); return; } - + const apiData = apiResponse.data[0]; const oldStatus = this.qrData.status; - + // Update qrData dengan semua properti dari API this.qrData = { qrvalue: apiData.qrvalue || apiData.qr_code || this.qrData.qrvalue, display_nobill: apiData.display_nobill || this.qrData.display_nobill, display_name: apiData.display_name || this.qrData.display_name, - display_amount: apiData.display_amount || apiData.nominal || this.qrData.display_amount, - status: apiData.status as QrData['status'] || this.qrData.status, // Type casting untuk status + display_amount: + apiData.display_amount || + apiData.nominal || + this.qrData.display_amount, + status: (apiData.status as QrData["status"]) || this.qrData.status, // Type casting untuk status ip: apiData.ip || this.qrData.ip, - // created_at DIHAPUS - ...apiData // expired_at akan masuk di sini + + ...apiData, // expired_at akan masuk di sini }; - + // Update patientInfo this.patientInfo = { - name: this.qrData.display_name || this.patientInfo.name || 'Unknown', - amount: this.qrData.display_amount || this.patientInfo.amount || '0', + name: this.qrData.display_name || this.patientInfo.name || "Unknown", + amount: this.qrData.display_amount || this.patientInfo.amount || "0", expiry: this.patientInfo.expiry, }; @@ -150,22 +152,26 @@ export const usePaymentStore = defineStore('payment', { } if (this.currentStep === 2 && oldStatus !== newStatus) { - if (newStatus === "2") { - console.log('🎉 [STORE] Status 2 (SUKSES) diterima. Pindah ke Step 3.'); - this.currentStep = 3; - this.startAutoReset(); - } else if (newStatus === "0") { - console.log('❌ [STORE] Status 0 (GAGAL) diterima. Pindah ke Step 4.'); - this.currentStep = 4; - this.startAutoReset(); - } + if (newStatus === "2") { + console.log( + "🎉 [STORE] Status 2 (SUKSES) diterima. Pindah ke Step 3." + ); + this.currentStep = 3; + this.startAutoReset(); + } else if (newStatus === "0") { + console.log( + "❌ [STORE] Status 0 (GAGAL) diterima. Pindah ke Step 4." + ); + this.currentStep = 4; + this.startAutoReset(); + } } - + console.log("Updated qrData:", this.qrData); console.log("Updated patientInfo:", this.patientInfo); console.log("==================="); }, - + // Method untuk debug debugCurrentState() { console.log("=== PAYMENT STORE DEBUG ==="); @@ -177,30 +183,29 @@ export const usePaymentStore = defineStore('payment', { console.log("Safe QR Value:", this.safeQrValue); console.log("=========================="); }, - + // Method untuk validasi data validateData(): boolean { const issues: string[] = []; - + if (!this.qrData.display_nobill) { - issues.push('display_nobill missing'); + issues.push("display_nobill missing"); } - + if (!this.qrData.display_name) { - issues.push('display_name missing'); + issues.push("display_name missing"); } - - // Perlu pengecekan properti qr_code pada apiData, bukan state - if (!this.qrData.qrvalue) { - issues.push('QR code missing'); + + if (!this.qrData.qrvalue) { + issues.push("QR code missing"); } - + if (issues.length > 0) { - console.warn('Data validation issues:', issues); + console.warn("Data validation issues:", issues); return false; } - + return true; - } + }, }, -}); \ No newline at end of file +});