From cf54ded78ce80b006b82a34c58d288b61071b8ea Mon Sep 17 00:00:00 2001 From: Fanrouver Date: Wed, 4 Feb 2026 10:34:33 +0700 Subject: [PATCH] update tampilan layar screen --- assets/scss/main.scss | 12 +++-- nuxt.config.ts | 2 +- pages/AdminKlinikRuang/index.vue | 8 +-- pages/AdminLoket/index.vue | 8 +-- pages/Anjungan/Anjungan/[id].vue | 7 ++- pages/Anjungan/AntreanMasuk/index.vue | 8 +-- .../AntrianKlinikRuang/[kodeKlinik].vue | 8 +-- pages/Anjungan/AntrianKlinikRuang/index.vue | 7 ++- pages/Anjungan/AntrianLoket/index.vue | 7 ++- pages/Dashboard.vue | 19 +++++-- pages/MonitoringPasien/monitoringPasien.vue | 2 +- stores/doctorStore.js | 52 +++++++++++++++---- 12 files changed, 94 insertions(+), 46 deletions(-) diff --git a/assets/scss/main.scss b/assets/scss/main.scss index e1b27fc..06cef8d 100644 --- a/assets/scss/main.scss +++ b/assets/scss/main.scss @@ -1,7 +1,7 @@ // Gunakan @use daripada @import -@use './variables' as *; -@use './colors' as *; -@use './typography' as *; +@use "./variables" as *; +@use "./colors" as *; +@use "./typography" as *; // Global styles *, @@ -19,7 +19,9 @@ body { padding: 0; background-color: var(--color-background); color: var(--color-textPrimary); - transition: background-color 0.3s ease, color 0.3s ease; + transition: + background-color 0.3s ease, + color 0.3s ease; } // Selection color @@ -68,4 +70,4 @@ button { opacity: 0.5; cursor: not-allowed; } -} \ No newline at end of file +} diff --git a/nuxt.config.ts b/nuxt.config.ts index fb6da14..46186a2 100644 --- a/nuxt.config.ts +++ b/nuxt.config.ts @@ -17,7 +17,7 @@ export default defineNuxtConfig({ { name: 'apple-mobile-web-app-capable', content: 'yes' }, { name: 'apple-mobile-web-app-status-bar-style', content: 'black-translucent' } ] - } + }, }, modules: [// "@nuxt/content", diff --git a/pages/AdminKlinikRuang/index.vue b/pages/AdminKlinikRuang/index.vue index 03ded0d..e0eec8c 100644 --- a/pages/AdminKlinikRuang/index.vue +++ b/pages/AdminKlinikRuang/index.vue @@ -156,10 +156,10 @@ onMounted(async () => { .klinik-grid { display: grid; - grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); - gap: 24px; - max-width: 1400px; - margin: 0 auto; + grid-template-columns: repeat(auto-fill, minmax(340px, 1fr)); + gap: 20px; + width: 100%; + margin: 0; } .klinik-card { diff --git a/pages/AdminLoket/index.vue b/pages/AdminLoket/index.vue index 05d2450..966f842 100644 --- a/pages/AdminLoket/index.vue +++ b/pages/AdminLoket/index.vue @@ -160,10 +160,10 @@ const navigateToLoket = (id) => { .loket-grid { display: grid; - grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); - gap: 24px; - max-width: 1400px; - margin: 0 auto; + grid-template-columns: repeat(auto-fill, minmax(340px, 1fr)); + gap: 20px; + width: 100%; + margin: 0; } .loket-card { diff --git a/pages/Anjungan/Anjungan/[id].vue b/pages/Anjungan/Anjungan/[id].vue index 50597de..02bf8d1 100644 --- a/pages/Anjungan/Anjungan/[id].vue +++ b/pages/Anjungan/Anjungan/[id].vue @@ -743,7 +743,12 @@ const prefetchDoctorsForCurrentAnjungan = async () => { `🔄 Prefetching doctors for ${clinicsForAnjungan.length} klinik (${anjunganData.value.jenisPasien})` ); - await Promise.all(clinicsForAnjungan.map((clinic) => loadDoctorsForClinic(clinic))); + // Fetch doctors sequentially to avoid overwhelming the server + for (const clinic of clinicsForAnjungan) { + await loadDoctorsForClinic(clinic); + // Tiny delay between requests + await new Promise(resolve => setTimeout(resolve, 30)); + } console.log("✅ Prefetch complete. Doctors by klinik ID:", doctorsByKlinikId.value); }; diff --git a/pages/Anjungan/AntreanMasuk/index.vue b/pages/Anjungan/AntreanMasuk/index.vue index fc7bf06..e326b13 100644 --- a/pages/Anjungan/AntreanMasuk/index.vue +++ b/pages/Anjungan/AntreanMasuk/index.vue @@ -217,10 +217,10 @@ const navigateToSettings = () => { .screens-grid { display: grid; - grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); - gap: 24px; - max-width: 1400px; - margin: 0 auto; + grid-template-columns: repeat(auto-fill, minmax(340px, 1fr)); + gap: 20px; + width: 100%; + margin: 0; } .screen-card { diff --git a/pages/Anjungan/AntrianKlinikRuang/[kodeKlinik].vue b/pages/Anjungan/AntrianKlinikRuang/[kodeKlinik].vue index 1153396..d251db7 100644 --- a/pages/Anjungan/AntrianKlinikRuang/[kodeKlinik].vue +++ b/pages/Anjungan/AntrianKlinikRuang/[kodeKlinik].vue @@ -640,7 +640,7 @@ watch(anjunganClientId, (newClientId, oldClientId) => { background: var(--color-neutral-300); width: 100vw; height: 100dvh; - padding: 24px; + padding: 12px; font-family: 'Inter', 'Roboto', sans-serif; overflow: hidden; display: flex; @@ -655,8 +655,8 @@ watch(anjunganClientId, (newClientId, oldClientId) => { align-items: center; background: linear-gradient(135deg, var(--color-primary-600) 0%, var(--color-primary-700) 100%); border-radius: 16px; - padding: 16px 40px; - margin-bottom: 16px; + padding: 12px 32px; + margin-bottom: 12px; box-shadow: 0 8px 24px rgba(33, 150, 243, 0.3); flex-shrink: 0; } @@ -730,7 +730,7 @@ watch(anjunganClientId, (newClientId, oldClientId) => { background: linear-gradient(135deg, var(--color-primary-600) 0%, var(--color-primary-700) 100%); border-radius: 20px; padding: 24px 32px; - margin-bottom: 16px; + margin-bottom: 12px; text-align: center; box-shadow: 0 12px 32px rgba(33, 150, 243, 0.35); animation: pulse-highlight 2s ease-in-out infinite; diff --git a/pages/Anjungan/AntrianKlinikRuang/index.vue b/pages/Anjungan/AntrianKlinikRuang/index.vue index 3b60ad7..3e3b814 100644 --- a/pages/Anjungan/AntrianKlinikRuang/index.vue +++ b/pages/Anjungan/AntrianKlinikRuang/index.vue @@ -241,11 +241,10 @@ onMounted(async () => { .kliniks-grid { display: grid; - grid-template-columns: repeat(4, 1fr); - gap: 24px; - max-width: 1400px; - margin: 0 auto; + grid-template-columns: repeat(auto-fill, minmax(340px, 1fr)); + gap: 20px; width: 100%; + margin: 0; } @media (max-width: 1280px) { diff --git a/pages/Anjungan/AntrianLoket/index.vue b/pages/Anjungan/AntrianLoket/index.vue index e205299..ab9ac8d 100644 --- a/pages/Anjungan/AntrianLoket/index.vue +++ b/pages/Anjungan/AntrianLoket/index.vue @@ -252,11 +252,10 @@ const getKlinikName = (kode, loket = null) => { .lokets-grid { display: grid; - grid-template-columns: repeat(4, 1fr); - gap: 24px; - max-width: 1400px; - margin: 0 auto; + grid-template-columns: repeat(auto-fill, minmax(340px, 1fr)); + gap: 20px; width: 100%; + margin: 0; } @media (max-width: 1280px) { diff --git a/pages/Dashboard.vue b/pages/Dashboard.vue index 421165c..dd0218e 100644 --- a/pages/Dashboard.vue +++ b/pages/Dashboard.vue @@ -114,7 +114,7 @@
{{ stats[0].value }}
-
Pasien Hari Ini
+
{{ stats[0].label }}
@@ -124,7 +124,7 @@
{{ stats[1].value }}
-
Antrean Aktif
+
{{ stats[1].label }}
@@ -937,7 +937,20 @@ const refreshDashboardStats = async (filterParams = {}) => { console.log('📡 API Response Data:', data); if (data) { // 1. Update Stats Cards - stats.value[0].value = data.today_patients.toString(); + // If we are looking at a range, sum up the total from payment distribution + // because today_patients only counts for the current actual 'Today' + const isDateRange = filterDateFrom.value !== filterDateTo.value; + + if (isDateRange && data.total_by_payment_type) { + const totalInRange = Object.values(data.total_by_payment_type).reduce((a, b) => a + b, 0); + stats.value[0].value = totalInRange.toString(); + stats.value[0].label = 'Total Pasien'; + stats.value[1].label = 'Total Antrean'; + } else { + stats.value[0].value = data.today_patients.toString(); + stats.value[0].label = 'Pasien Hari Ini'; + stats.value[1].label = 'Antrean Aktif'; + } const activeQueuesTotal = Object.values(data.today_active_queues_by_service).reduce((sum, val) => sum + val, 0); stats.value[1].value = activeQueuesTotal.toString(); diff --git a/pages/MonitoringPasien/monitoringPasien.vue b/pages/MonitoringPasien/monitoringPasien.vue index 5b90a74..d57bf96 100644 --- a/pages/MonitoringPasien/monitoringPasien.vue +++ b/pages/MonitoringPasien/monitoringPasien.vue @@ -15,7 +15,7 @@ - +
diff --git a/stores/doctorStore.js b/stores/doctorStore.js index b66ef0c..8d0deb5 100644 --- a/stores/doctorStore.js +++ b/stores/doctorStore.js @@ -36,9 +36,9 @@ export const useDoctorStore = defineStore('doctor', () => { }; /** - * Fetch doctors for a clinic from API + * Fetch doctors for a clinic from API with retry logic */ - const fetchDoctorsForClinic = async (clinic, force = false) => { + const fetchDoctorsForClinic = async (clinic, force = false, retries = 2) => { if (!clinic || !clinic.id) return; const idklinik = clinic.id; @@ -50,6 +50,8 @@ export const useDoctorStore = defineStore('doctor', () => { // Skip if already loading if (loadingDoctors.value[idklinik]) { + // Wait a bit if already loading? No, just return existing promise if we had one, + // but for simplicity we just return. return; } @@ -57,9 +59,30 @@ export const useDoctorStore = defineStore('doctor', () => { try { console.log(`🔄 [doctorStore] Fetching doctors for klinik ID: ${idklinik} (${clinic.name})`); - const data = await $fetch( - `http://10.10.150.131:8089/api/v1/dokter/${idklinik}` - ); + + let data; + let lastError; + + // Retry loop + for (let i = 0; i <= retries; i++) { + try { + data = await $fetch( + `http://10.10.150.131:8089/api/v1/dokter/${idklinik}`, + { timeout: 5000 } + ); + lastError = null; + break; // Success! + } catch (err) { + lastError = err; + if (i < retries) { + const delay = 500 * (i + 1); + console.warn(`⚠️ [doctorStore] Retry ${i+1}/${retries} for klinik ${idklinik} after ${delay}ms...`); + await new Promise(resolve => setTimeout(resolve, delay)); + } + } + } + + if (lastError) throw lastError; let doctorList = []; if (Array.isArray(data)) { @@ -79,14 +102,16 @@ export const useDoctorStore = defineStore('doctor', () => { doctorsByKlinikId.value[idklinik] = doctorNames; // Update sync timestamp if this was a refresh - if (force) { + if (force && doctorNames.length > 0) { lastSyncTimestamp.value = new Date().toISOString(); } console.log(`✅ [doctorStore] Loaded ${doctorNames.length} doctors untuk klinik ${clinic.name}`); return doctorNames; } catch (error) { - console.error(`❌ [doctorStore] Gagal mengambil dokter untuk klinik ID ${idklinik}:`, error); + const status = error.response ? error.response.status : (error.status || 'unknown'); + console.error(`❌ [doctorStore] Gagal mengambil dokter untuk klinik ID ${idklinik} (${clinic.name}) - Status: ${status}`); + // Don't overwrite existing data on failure unless it's empty if (!doctorsByKlinikId.value[idklinik]) { doctorsByKlinikId.value[idklinik] = []; @@ -97,15 +122,20 @@ export const useDoctorStore = defineStore('doctor', () => { }; /** - * Sync doctors for all provided clinics + * Sync doctors for all provided clinics sequentially to avoid overloading the server */ const syncAllDoctors = async (clinics) => { - console.log(`🔄 [doctorStore] Syncing all doctors for ${clinics.length} clinics...`); + console.log(`🔄 [doctorStore] Syncing all doctors for ${clinics.length} clinics sequentially...`); - // Filter clinics that need sync (only Reguler clinics usually have dynamic doctors) + // Filter clinics that need sync const clinicsToSync = clinics.filter(c => c.id); - await Promise.all(clinicsToSync.map(c => fetchDoctorsForClinic(c, true))); + // Process one by one instead of Promise.all to be gentle on the server + for (const clinic of clinicsToSync) { + await fetchDoctorsForClinic(clinic, true); + // Small delay between requests + await new Promise(resolve => setTimeout(resolve, 50)); + } lastSyncTimestamp.value = new Date().toISOString(); console.log(`✅ [doctorStore] Global doctor sync complete at ${lastSyncTimestamp.value}`);