// stores/clinicStore.js import { defineStore } from 'pinia'; import { ref, computed } from 'vue'; export const useClinicStore = defineStore('clinic', () => { console.log('🏪 Initializing clinicStore...'); // Data clinics - Single source of truth untuk semua data klinik // Includes basic info (name, kode, icon, doctors, shifts) + master config (totalQuota, jamShiftPerHari, jadwalKlinik, tanggalTutup) // jenisLayanan: "Reguler" (from API) or "Eksekutif" (seed data) const clinics = ref([ // EKSEKUTIF SEED DATA - keeping for backwards compatibility { id: 1000, kode: "AK", name: "ANAK", subtitle: "", icon: "mdi-baby-face", shift: "1 SHIFT", schedule: "Mulai Pukul 07:00", available: true, doctors: ["dr. Sarah Putri, Sp.A", "dr. Andi Wijaya, Sp.A"], shifts: [], totalQuota: 3000, jamShiftPerHari: { 'Senin': [{ dari: '07:00', sampai: '11:00', kuota: 1000 }], 'Rabu': [{ dari: '07:00', sampai: '11:00', kuota: 1000 }], 'Jumat': [{ dari: '07:00', sampai: '11:00', kuota: 1000 }] }, jamShiftList: [], autoShift: false, jadwalKlinik: ['Senin', 'Rabu', 'Jumat'], tanggalTutup: [], jenisLayanan: "Eksekutif", }, { id: 1001, kode: "AN", name: "ANESTESI", subtitle: "", icon: "mdi-face-mask", shift: "2 SHIFT", schedule: "Mulai Pukul 07:00", available: true, doctors: ["dr. Ahmad Fauzi, Sp.An"], shifts: [], totalQuota: 7500, jamShiftPerHari: { 'Senin': [ { dari: '07:00', sampai: '11:00', kuota: 1000 }, { dari: '13:00', sampai: '16:00', kuota: 500 } ], 'Selasa': [ { dari: '07:00', sampai: '11:00', kuota: 1000 }, { dari: '13:00', sampai: '16:00', kuota: 500 } ], 'Rabu': [ { dari: '07:00', sampai: '11:00', kuota: 1000 }, { dari: '13:00', sampai: '16:00', kuota: 500 } ], 'Kamis': [ { dari: '07:00', sampai: '11:00', kuota: 1000 }, { dari: '13:00', sampai: '16:00', kuota: 500 } ], 'Jum\'at': [ { dari: '07:00', sampai: '11:00', kuota: 1000 }, { dari: '13:00', sampai: '16:00', kuota: 500 } ] }, jamShiftList: [], autoShift: false, jadwalKlinik: ['Senin', 'Selasa', 'Rabu', 'Kamis', 'Jum\'at'], tanggalTutup: [], jenisLayanan: "Eksekutif", }, { id: 1002, kode: "BD", name: "BEDAH", subtitle: "", icon: "mdi-medical-bag", shift: "1 SHIFT", schedule: "Mulai Pukul 07:00", available: true, doctors: ["dr. Budi Santoso, Sp.B", "dr. Eko Prasetyo, Sp.B", "dr. Dian Permata, Sp.B"], shifts: [], totalQuota: 2400, jamShiftPerHari: { 'Senin': [{ dari: '07:00', sampai: '11:00', kuota: 800 }], 'Rabu': [{ dari: '07:00', sampai: '11:00', kuota: 800 }], 'Jumat': [{ dari: '07:00', sampai: '11:00', kuota: 800 }] }, jamShiftList: [], autoShift: false, jadwalKlinik: ['Senin', 'Rabu', 'Jumat'], tanggalTutup: [], jenisLayanan: "Eksekutif", }, { id: 1003, kode: "GM", name: "GIGI DAN MULUT", subtitle: "", icon: "mdi-tooth", shift: "3 SHIFT", schedule: "Mulai Pukul 07:00", available: true, doctors: ["drg. Rina Wati, Sp.KG", "drg. Made Surya, Sp.KG"], shifts: [], totalQuota: 10500, jamShiftPerHari: { 'Senin': [ { dari: '07:00', sampai: '11:00', kuota: 1000 }, { dari: '13:00', sampai: '16:00', kuota: 700 }, { dari: '18:00', sampai: '20:00', kuota: 400 } ], 'Selasa': [ { dari: '07:00', sampai: '11:00', kuota: 1000 }, { dari: '13:00', sampai: '16:00', kuota: 700 }, { dari: '18:00', sampai: '20:00', kuota: 400 } ], 'Rabu': [ { dari: '07:00', sampai: '11:00', kuota: 1000 }, { dari: '13:00', sampai: '16:00', kuota: 700 }, { dari: '18:00', sampai: '20:00', kuota: 400 } ], 'Kamis': [ { dari: '07:00', sampai: '11:00', kuota: 1000 }, { dari: '13:00', sampai: '16:00', kuota: 700 }, { dari: '18:00', sampai: '20:00', kuota: 400 } ], 'Jum\'at': [ { dari: '07:00', sampai: '11:00', kuota: 1000 }, { dari: '13:00', sampai: '16:00', kuota: 700 }, { dari: '18:00', sampai: '20:00', kuota: 400 } ] }, jamShiftList: [], autoShift: true, jadwalKlinik: ['Senin', 'Selasa', 'Rabu', 'Kamis', 'Jum\'at'], tanggalTutup: [], jenisLayanan: "Eksekutif", }, { id: 1004, kode: "GR", name: "GERIATRI", subtitle: "", icon: "mdi-human-cane", shift: "1 SHIFT", schedule: "Mulai Pukul 07:00", available: true, doctors: ["dr. Siti Aminah, Sp.PD-KGer"], shifts: [], totalQuota: 1800, jamShiftPerHari: { 'Senin': [{ dari: '07:00', sampai: '11:00', kuota: 600 }], 'Rabu': [{ dari: '07:00', sampai: '11:00', kuota: 600 }], 'Jumat': [{ dari: '07:00', sampai: '11:00', kuota: 600 }] }, jamShiftList: [], autoShift: false, jadwalKlinik: ['Senin', 'Rabu', 'Jumat'], tanggalTutup: [], jenisLayanan: "Eksekutif", }, { id: 1005, kode: "GZ", name: "GIZI", subtitle: "", icon: "mdi-food-apple", shift: "1 SHIFT", schedule: "Mulai Pukul 07:00", available: true, doctors: ["dr. Lisa Andriani, Sp.GK"], shifts: [], totalQuota: 1500, jamShiftPerHari: { 'Senin': [{ dari: '07:00', sampai: '11:00', kuota: 500 }], 'Rabu': [{ dari: '07:00', sampai: '11:00', kuota: 500 }], 'Jumat': [{ dari: '07:00', sampai: '11:00', kuota: 500 }] }, jamShiftList: [], autoShift: false, jadwalKlinik: ['Senin', 'Rabu', 'Jumat'], tanggalTutup: [], jenisLayanan: "Eksekutif", }, { id: 1006, kode: "HO", name: "HOM", subtitle: "", icon: "mdi-water", shift: "1 SHIFT", schedule: "", available: false, doctors: [], shifts: [], totalQuota: 2250, jamShiftPerHari: { 'Senin': [{ dari: '07:00', sampai: '11:00', kuota: 750 }], 'Selasa': [{ dari: '07:00', sampai: '11:00', kuota: 750 }], 'Rabu': [{ dari: '07:00', sampai: '11:00', kuota: 750 }] }, jamShiftList: [], autoShift: false, jadwalKlinik: ['Senin', 'Selasa', 'Rabu'], tanggalTutup: [], jenisLayanan: "Eksekutif", }, { id: 1007, kode: "IP", name: "IPD", subtitle: "", icon: "mdi-hospital", shift: "1 SHIFT", schedule: "Mulai Pukul 07:00", available: true, doctors: ["dr. Hendra Wijaya, Sp.PD", "dr. Agus Salim, Sp.PD", "dr. Retno Wulan, Sp.PD", "dr. Bambang Susilo, Sp.PD"], shifts: [], totalQuota: 2700, jamShiftPerHari: { 'Senin': [{ dari: '07:00', sampai: '11:00', kuota: 900 }], 'Rabu': [{ dari: '07:00', sampai: '11:00', kuota: 900 }], 'Jumat': [{ dari: '07:00', sampai: '11:00', kuota: 900 }] }, jamShiftList: [], autoShift: false, jadwalKlinik: ['Senin', 'Rabu', 'Jumat'], tanggalTutup: [], jenisLayanan: "Eksekutif", }, { id: 1008, kode: "JT", name: "JANTUNG", subtitle: "", icon: "mdi-heart-pulse", shift: "2 SHIFT", schedule: "Mulai Pukul 07:00", available: true, doctors: ["dr. Rudi Hartono, Sp.JP", "dr. Sinta Dewi, Sp.JP"], shifts: [], totalQuota: 6000, jamShiftPerHari: { 'Senin': [ { dari: '07:00', sampai: '11:00', kuota: 800 }, { dari: '13:00', sampai: '16:00', kuota: 400 } ], 'Selasa': [ { dari: '07:00', sampai: '11:00', kuota: 800 }, { dari: '13:00', sampai: '16:00', kuota: 400 } ], 'Rabu': [ { dari: '07:00', sampai: '11:00', kuota: 800 }, { dari: '13:00', sampai: '16:00', kuota: 400 } ], 'Kamis': [ { dari: '07:00', sampai: '11:00', kuota: 800 }, { dari: '13:00', sampai: '16:00', kuota: 400 } ], 'Jum\'at': [ { dari: '07:00', sampai: '11:00', kuota: 800 }, { dari: '13:00', sampai: '16:00', kuota: 400 } ] }, jamShiftList: [], autoShift: false, jadwalKlinik: ['Senin', 'Selasa', 'Rabu', 'Kamis', 'Jum\'at'], tanggalTutup: [], jenisLayanan: "Eksekutif", }, { id: 1009, kode: "JW", name: "JIWA", subtitle: "", icon: "mdi-head-dots-horizontal", shift: "1 SHIFT", schedule: "Mulai Pukul 07:00", available: true, doctors: ["dr. Maya Kusuma, Sp.KJ"], shifts: [], totalQuota: 1400, jamShiftPerHari: { 'Senin': [{ dari: '07:00', sampai: '11:00', kuota: 700 }], 'Rabu': [{ dari: '07:00', sampai: '11:00', kuota: 700 }] }, jamShiftList: [], autoShift: false, jadwalKlinik: ['Senin', 'Rabu'], tanggalTutup: [], jenisLayanan: "Eksekutif", }, { id: 1010, kode: "KM", name: "KEMOTERAPI", subtitle: "", icon: "mdi-virus", shift: "1 SHIFT", schedule: "Mulai Pukul 07:00", available: true, doctors: ["dr. Andi Pratama, Sp.PD-KHOM"], shifts: [], totalQuota: 0, jamShiftPerHari: {}, jamShiftList: [], autoShift: false, jadwalKlinik: [], tanggalTutup: [], jenisLayanan: "Eksekutif", }, { id: 1011, kode: "KK", name: "KUL KEL", subtitle: "", icon: "mdi-human-male-female", shift: "1 SHIFT", schedule: "Mulai Pukul 07:00", available: true, doctors: ["dr. Tika Anggraini, Sp.KK", "dr. Fajar Ramadhan, Sp.KK"], shifts: [], totalQuota: 1200, jamShiftPerHari: { 'Selasa': [{ dari: '07:00', sampai: '11:00', kuota: 600 }], 'Kamis': [{ dari: '07:00', sampai: '11:00', kuota: 600 }] }, jamShiftList: [], autoShift: false, jadwalKlinik: ['Selasa', 'Kamis'], tanggalTutup: [], jenisLayanan: "Eksekutif", }, { id: 1012, kode: "KO", name: "KOMPLEMENTER", subtitle: "", icon: "mdi-medical-bag", shift: "1 SHIFT", schedule: "Mulai Pukul 07:00", available: true, doctors: ["dr. Ratna Sari, Sp.KFR"], shifts: [], totalQuota: 0, jamShiftPerHari: {}, jamShiftList: [], autoShift: false, jadwalKlinik: [], tanggalTutup: [], jenisLayanan: "Eksekutif", }, { id: 1013, kode: "MC", name: "MCU", subtitle: "", icon: "mdi-clipboard-check", shift: "1 SHIFT", schedule: "Mulai Pukul 07:00", available: true, doctors: ["dr. Fitri Handayani, Sp.OK"], shifts: [], totalQuota: 0, jamShiftPerHari: {}, jamShiftList: [], autoShift: false, jadwalKlinik: [], tanggalTutup: [], jenisLayanan: "Eksekutif", }, { id: 1014, kode: "MT", name: "MATA", subtitle: "", icon: "mdi-eye", shift: "1 SHIFT", schedule: "Mulai Pukul 07:00", available: true, doctors: ["dr. Yudi Prasetyo, Sp.M", "dr. Linda Kartika, Sp.M"], shifts: [], totalQuota: 2400, jamShiftPerHari: { 'Senin': [{ dari: '07:00', sampai: '11:00', kuota: 800 }], 'Rabu': [{ dari: '07:00', sampai: '11:00', kuota: 800 }], 'Jumat': [{ dari: '07:00', sampai: '11:00', kuota: 800 }] }, jamShiftList: [], autoShift: false, jadwalKlinik: ['Senin', 'Rabu', 'Jumat'], tanggalTutup: [], jenisLayanan: "Eksekutif", }, { id: 1015, kode: "KD", name: "KANDUNGAN", subtitle: "", icon: "mdi-human-pregnant", shift: "2 SHIFT", schedule: "Mulai Pukul 07:00", available: true, doctors: ["dr. Dewi Lestari, Sp.OG", "dr. Putri Andini, Sp.OG", "dr. Nova Riani, Sp.OG"], shifts: [], totalQuota: 5000, jamShiftPerHari: { 'Senin': [ { dari: '07:00', sampai: '11:00', kuota: 600 }, { dari: '13:00', sampai: '16:00', kuota: 400 } ], 'Selasa': [ { dari: '07:00', sampai: '11:00', kuota: 600 }, { dari: '13:00', sampai: '16:00', kuota: 400 } ], 'Rabu': [ { dari: '07:00', sampai: '11:00', kuota: 600 }, { dari: '13:00', sampai: '16:00', kuota: 400 } ], 'Kamis': [ { dari: '07:00', sampai: '11:00', kuota: 600 }, { dari: '13:00', sampai: '16:00', kuota: 400 } ], 'Jum\'at': [ { dari: '07:00', sampai: '11:00', kuota: 600 }, { dari: '13:00', sampai: '16:00', kuota: 400 } ] }, jamShiftList: [], autoShift: false, jadwalKlinik: ['Senin', 'Selasa', 'Rabu', 'Kamis', 'Jum\'at'], tanggalTutup: [], jenisLayanan: "Eksekutif", }, { id: 1016, kode: "ON", name: "ONKOLOGI", subtitle: "", icon: "mdi-virus", shift: "1 SHIFT", schedule: "Mulai Pukul 07:00", available: true, doctors: ["dr. Dimas Prakoso, Sp.Onk-Rad"], shifts: [], totalQuota: 0, jamShiftPerHari: {}, jamShiftList: [], autoShift: false, jadwalKlinik: [], tanggalTutup: [], jenisLayanan: "Eksekutif", }, { id: 1017, kode: "PR", name: "PARU", subtitle: "", icon: "mdi-lungs", shift: "1 SHIFT", schedule: "Mulai Pukul 07:00", available: true, doctors: ["dr. Nina Marlina, Sp.P", "dr. Arief Budiman, Sp.P"], shifts: [], totalQuota: 1400, jamShiftPerHari: { 'Senin': [{ dari: '07:00', sampai: '11:00', kuota: 700 }], 'Rabu': [{ dari: '07:00', sampai: '11:00', kuota: 700 }] }, jamShiftList: [], autoShift: false, jadwalKlinik: ['Senin', 'Rabu'], tanggalTutup: [], jenisLayanan: "Eksekutif", }, { id: 1018, kode: "RM", name: "REHAB MEDIK", subtitle: "", icon: "mdi-human-cane", shift: "1 SHIFT", schedule: "Mulai Pukul 07:00", available: true, doctors: ["dr. Gita Permata, Sp.KFR", "dr. Rian Saputra, Sp.KFR"], shifts: [], totalQuota: 1800, jamShiftPerHari: { 'Senin': [{ dari: '07:00', sampai: '11:00', kuota: 600 }], 'Rabu': [{ dari: '07:00', sampai: '11:00', kuota: 600 }], 'Jumat': [{ dari: '07:00', sampai: '11:00', kuota: 600 }] }, jamShiftList: [], autoShift: false, jadwalKlinik: ['Senin', 'Rabu', 'Jumat'], tanggalTutup: [], jenisLayanan: "Eksekutif", }, { id: 1019, kode: "RD", name: "RADIOTERAPI", subtitle: "", icon: "mdi-radioactive", shift: "1 SHIFT", schedule: "Mulai Pukul 07:00", available: true, doctors: ["dr. Wulan Safitri, Sp.Rad"], shifts: [], totalQuota: 2500, jamShiftPerHari: { 'Senin': [{ dari: '07:00', sampai: '11:00', kuota: 500 }], 'Selasa': [{ dari: '07:00', sampai: '11:00', kuota: 500 }], 'Rabu': [{ dari: '07:00', sampai: '11:00', kuota: 500 }], 'Kamis': [{ dari: '07:00', sampai: '11:00', kuota: 500 }], 'Jum\'at': [{ dari: '07:00', sampai: '11:00', kuota: 500 }] }, jamShiftList: [], autoShift: false, jadwalKlinik: ['Senin', 'Selasa', 'Rabu', 'Kamis', 'Jum\'at'], tanggalTutup: [], jenisLayanan: "Eksekutif", }, { id: 1020, kode: "SR", name: "SARAF", subtitle: "", icon: "mdi-head-cog", shift: "1 SHIFT", schedule: "Mulai Pukul 07:00", available: false, doctors: [], shifts: [], totalQuota: 1400, jamShiftPerHari: { 'Senin': [{ dari: '07:00', sampai: '11:00', kuota: 700 }], 'Rabu': [{ dari: '07:00', sampai: '11:00', kuota: 700 }] }, jamShiftList: [], autoShift: false, jadwalKlinik: ['Senin', 'Rabu'], tanggalTutup: [], jenisLayanan: "Eksekutif", }, { id: 1021, kode: "RT", name: "R. TINDAKAN", subtitle: "", icon: "mdi-waveform", shift: "1 SHIFT", schedule: "Mulai Pukul 07:00", available: true, doctors: ["dr. Fajar Nugroho, Sp.S"], shifts: [], totalQuota: 0, jamShiftPerHari: {}, jamShiftList: [], autoShift: false, jadwalKlinik: [], tanggalTutup: [], jenisLayanan: "Eksekutif", }, { id: 1022, kode: "TH", name: "THT", subtitle: "", icon: "mdi-ear-hearing", shift: "1 SHIFT", schedule: "Mulai Pukul 07:00", available: true, doctors: ["dr. Reza Maulana, Sp.THT-KL"], shifts: [], totalQuota: 0, jamShiftPerHari: {}, jamShiftList: [], autoShift: false, jadwalKlinik: [], tanggalTutup: [], jenisLayanan: "Eksekutif", }, ]); console.log('📊 Initial clinics count:', clinics.value.length); console.log('📋 Initial clinics:', clinics.value.map(c => ({ id: c.id, name: c.name, jenisLayanan: c.jenisLayanan }))); const isLoadingAPI = ref(false); const apiError = ref(null); const lastSyncTimestamp = ref(null); /** * Check if sync is needed based on 2 AM schedule */ const isSyncNeeded = () => { if (!lastSyncTimestamp.value) return true; const now = new Date(); const lastSync = new Date(lastSyncTimestamp.value); // Create a 2 AM today date const today2AM = new Date(now); today2AM.setHours(2, 0, 0, 0); // If last sync was before 2 AM today, and it's already past 2 AM today, we need to sync if (lastSync < today2AM && now >= today2AM) { return true; } // If last sync was on a different day and it's before 2 AM, but more than 24h passed const diffInHours = (now - lastSync) / (1000 * 60 * 60); if (diffInHours > 24) { return true; } return false; }; // Helper: Convert day name from API to Indonesian const mapDayToIndonesian = (day) => { const mapping = { 'TUE': 'Selasa', 'MON': 'Senin', 'WED': 'Rabu', 'THU': 'Kamis', 'FRI': 'Jum\'at', 'SAT': 'Sabtu', 'SUN': 'Minggu' }; return mapping[day] || day; }; // Helper: Parse jam_operasional to create jamShiftPerHari structure const parseJamOperasional = (jamOperasional) => { console.log('🔄 Parsing jam operasional:', jamOperasional); const jamShiftPerHari = {}; // { 'Senin': [{ dari, sampai, kuota }], 'Selasa': [...] } const jadwalKlinikSet = new Set(); let totalQuota = 0; if (!jamOperasional) { console.log('⚠️ No jamOperasional provided, using defaults'); return { jamShiftPerHari: { 'Senin': [{ dari: '07:00', sampai: '11:00', kuota: 100 }] }, jadwalKlinik: ['Senin'], totalQuota: 100, shift: 1 }; } if (!Array.isArray(jamOperasional)) { console.log('⚠️ jamOperasional is not an array:', typeof jamOperasional); return { jamShiftPerHari: { 'Senin': [{ dari: '07:00', sampai: '11:00', kuota: 100 }] }, jadwalKlinik: ['Senin'], totalQuota: 100, shift: 1 }; } console.log(`📋 Processing ${jamOperasional.length} schedule items`); // Group by day and create shift per day structure jamOperasional.forEach((item, index) => { console.log(` 📍 Schedule ${index + 1}:`, { hari: item.hari, jam_operasional: item.jam_operasional, kuota: item.kuota }); const dayIndo = mapDayToIndonesian(item.hari); if (!dayIndo) { console.log(` ⚠️ Could not map day: ${item.hari}`); return; } jadwalKlinikSet.add(dayIndo); const jamOp = item.jam_operasional || ''; const parts = jamOp.split(' - '); if (parts.length === 2) { const dari = parts[0]?.trim() || '07:00'; const sampai = parts[1]?.trim() || '11:00'; // Handle different kuota formats let kuota = 100; // default if (item.kuota) { if (typeof item.kuota === 'number') { kuota = item.kuota; } else if (typeof item.kuota === 'object' && item.kuota.Intfd !== undefined) { kuota = parseInt(item.kuota.Intfd) || 100; } else if (typeof item.kuota === 'object' && item.kuota.Valid !== undefined) { kuota = parseInt(item.kuota.Valid) || 100; } else if (typeof item.kuota === 'string') { kuota = parseInt(item.kuota) || 100; } } console.log(` ✅ Shift added for ${dayIndo}: ${dari} - ${sampai}, kuota: ${kuota}`); // Initialize array if not exists if (!jamShiftPerHari[dayIndo]) { jamShiftPerHari[dayIndo] = []; } jamShiftPerHari[dayIndo].push({ dari, sampai, kuota }); totalQuota += kuota; } else { console.log(` ⚠️ Invalid jam_operasional format: "${jamOp}"`); } }); // If no valid shift found, add default if (Object.keys(jamShiftPerHari).length === 0) { console.log('⚠️ No valid shifts found, using default'); jamShiftPerHari['Senin'] = [{ dari: '07:00', sampai: '11:00', kuota: 100 }]; jadwalKlinikSet.add('Senin'); totalQuota = 100; } // Count total unique shifts across all days const totalShifts = Object.values(jamShiftPerHari).reduce((sum, shifts) => sum + shifts.length, 0); const result = { jamShiftPerHari, jadwalKlinik: Array.from(jadwalKlinikSet), totalQuota, shift: totalShifts }; console.log('✅ Parse result:', result); return result; }; // State for singleton fetch promise let activeFetchPromise = null; // Fetch Reguler clinics from API const fetchRegulerClinics = async (force = false, retryCount = 0) => { // Guard: singleton pattern - reuse existing promise if one is already in flight if (activeFetchPromise && retryCount === 0) { console.log('⏳ Clinic fetch in progress, reusing existing request...'); return activeFetchPromise; } // Guard: Skip if data already exists and not forced, AND sync not needed const hasRegulerClinics = clinics.value.some(c => c.jenisLayanan === 'Reguler'); if (hasRegulerClinics && !force && retryCount === 0 && !isSyncNeeded()) { return { success: true, message: 'Data sudah tersedia' }; } // Define the actual fetch operation const performFetch = async () => { isLoadingAPI.value = true; apiError.value = null; try { console.log(`🔄 Fetching clinics from API (Try ${retryCount + 1})...`); const response = await fetch('http://10.10.150.131:8089/api/v1/klinik/reguler'); if (!response.ok) { // Handle Rate Limiting with exponential backoff if (response.status === 429 && retryCount < 3) { const delay = (retryCount + 1) * 1500; console.warn(`⚠️ Rate limit hit (429). Retrying in ${delay}ms...`); await new Promise(resolve => setTimeout(resolve, delay)); activeFetchPromise = null; // Reset so next try can start fresh return fetchRegulerClinics(force, retryCount + 1); } throw new Error(`HTTP error! status: ${response.status}`); } const rawData = await response.json(); console.log('📦 Raw API Response received'); // Handle different response formats let data = rawData; if (rawData && typeof rawData === 'object' && !Array.isArray(rawData)) { if (rawData.data) data = rawData.data; else if (rawData.result) data = rawData.result; else if (rawData.response) data = rawData.response; } if (!data || !Array.isArray(data)) { throw new Error('Data format tidak valid. Expected array.'); } // Remove existing Reguler clinics clinics.value = clinics.value.filter(c => c.jenisLayanan !== 'Reguler'); // Filter and map clinics const regulerClinics = data .filter(item => item.Tipe_Anjungan === 'KLINIK REGULER') .map((item) => { const parsed = parseJamOperasional(item.jadwal); const firstDay = parsed.jadwalKlinik[0]; const firstShift = firstDay ? parsed.jamShiftPerHari[firstDay]?.[0] : null; return { id: item.idklinik, kode: item.code || 'XX', name: item.namaklinik || 'Unknown', subtitle: "", icon: "mdi-hospital-box", shift: `${parsed.shift} SHIFT`, schedule: `Mulai Pukul ${(firstShift?.dari || '07:00').substring(0, 5)}`, available: item.active, doctors: [], shifts: [], totalQuota: parsed.totalQuota, jamShiftPerHari: parsed.jamShiftPerHari, jamShiftList: [], autoShift: false, jadwalKlinik: parsed.jadwalKlinik, tanggalTutup: [], spesialis: item.spesialis || [], // Preserve spesialis for room generation jenisLayanan: "Reguler", }; }); clinics.value = [...clinics.value, ...regulerClinics]; // Sort by kode, then jenisLayanan clinics.value.sort((a, b) => { if (a.kode < b.kode) return -1; if (a.kode > b.kode) return 1; if (a.jenisLayanan === 'Reguler' && b.jenisLayanan === 'Eksekutif') return -1; if (a.jenisLayanan === 'Eksekutif' && b.jenisLayanan === 'Reguler') return 1; return a.id - b.id; }); lastSyncTimestamp.value = new Date().toISOString(); return { success: true, message: `${regulerClinics.length} klinik reguler berhasil dimuat` }; } catch (error) { console.error('❌ Error fetching reguler clinics:', error); apiError.value = error.message; return { success: false, message: `Gagal memuat data: ${error.message}` }; } finally { isLoadingAPI.value = false; activeFetchPromise = null; // Clear singleton once finished console.log('✅ API fetch flow completed'); } }; activeFetchPromise = performFetch(); return activeFetchPromise; }; // Get all clinics (sorted by kode first, then by jenisLayanan) // This ensures same clinic code (e.g., "AN") appears together, with Reguler before Eksekutif const getAllClinics = computed(() => { const sorted = [...clinics.value].sort((a, b) => { // First sort by kode if (a.kode < b.kode) return -1; if (a.kode > b.kode) return 1; // If same kode, sort by jenisLayanan (Reguler before Eksekutif) if (a.jenisLayanan === 'Reguler' && b.jenisLayanan === 'Eksekutif') return -1; if (a.jenisLayanan === 'Eksekutif' && b.jenisLayanan === 'Reguler') return 1; // Finally sort by ID if both are same return a.id - b.id; }); console.log('📊 getAllClinics computed:', sorted.length, 'items'); return sorted; }); // Get clinics by jenis layanan const getClinicsByJenisLayanan = (jenisLayanan) => { return computed(() => clinics.value.filter(c => c.jenisLayanan === jenisLayanan)); }; // Get clinic by name (untuk mapping dengan kode klinik) const getClinicByName = (name) => { return clinics.value.find(c => c.name === name); }; // Get clinic by kode (lebih stabil untuk integrasi dengan master klinik & anjungan) const getClinicByKode = (kode) => { return clinics.value.find(c => c.kode === kode); }; // Get clinics list untuk dropdown (format: { name, kode, jenisLayanan }) // Sekarang langsung pakai kode dari clinic, tidak bergantung ke nama di masterStore const getClinicsForDropdown = (jenisLayanan = null) => { let filteredClinics = clinics.value; if (jenisLayanan) { filteredClinics = clinics.value.filter(c => c.jenisLayanan === jenisLayanan); } return filteredClinics .sort((a, b) => { if (a.kode < b.kode) return -1; if (a.kode > b.kode) return 1; if (a.jenisLayanan === 'Reguler' && b.jenisLayanan === 'Eksekutif') return -1; if (a.jenisLayanan === 'Eksekutif' && b.jenisLayanan === 'Reguler') return 1; return a.id - b.id; }) .map(clinic => ({ name: clinic.name, kode: clinic.kode, icon: clinic.icon, available: clinic.available, id: clinic.id, jenisLayanan: clinic.jenisLayanan })); }; const addClinic = (clinicPayload) => { try { const newId = clinics.value.length > 0 ? Math.max(...clinics.value.map(c => c.id || 0)) + 1 : 1000; const newClinic = { id: newId, kode: clinicPayload.kode || 'XX', name: clinicPayload.name || 'Unknown', subtitle: clinicPayload.subtitle || "", icon: clinicPayload.icon || "mdi-hospital-box", shift: clinicPayload.shift || "1 SHIFT", schedule: clinicPayload.schedule || "Mulai Pukul 07:00", available: clinicPayload.available !== undefined ? clinicPayload.available : true, doctors: clinicPayload.doctors || [], shifts: clinicPayload.shifts || [], totalQuota: clinicPayload.totalQuota || 0, jamShiftPerHari: clinicPayload.jamShiftPerHari || {}, jamShiftList: clinicPayload.jamShiftList || [], autoShift: clinicPayload.autoShift || false, jadwalKlinik: clinicPayload.jadwalKlinik || [], tanggalTutup: clinicPayload.tanggalTutup || [], jenisLayanan: clinicPayload.jenisLayanan || "Eksekutif", }; clinics.value.push(newClinic); console.log('✅ Clinic added:', newClinic); return { success: true, message: 'Klinik berhasil ditambahkan', clinic: newClinic }; } catch (error) { console.error('❌ Error adding clinic:', error); return { success: false, message: `Gagal menambah klinik: ${error.message}` }; } }; const updateClinic = (id, updates) => { try { const index = clinics.value.findIndex(c => c.id === id); if (index === -1) { return { success: false, message: 'Klinik tidak ditemukan' }; } clinics.value[index] = { ...clinics.value[index], ...updates, name: updates.name || updates.nama || clinics.value[index].name }; console.log('✅ Clinic updated:', clinics.value[index]); return { success: true, message: 'Klinik berhasil diperbarui', clinic: clinics.value[index] }; } catch (error) { console.error('❌ Error updating clinic:', error); return { success: false, message: `Gagal memperbarui klinik: ${error.message}` }; } }; const deleteClinic = (id) => { try { const index = clinics.value.findIndex(c => c.id === id); if (index === -1) { return { success: false, message: 'Klinik tidak ditemukan' }; } const deletedClinic = clinics.value[index]; clinics.value.splice(index, 1); console.log('✅ Clinic deleted:', id); return { success: true, message: `Klinik ${deletedClinic.name} berhasil dihapus` }; } catch (error) { console.error('❌ Error deleting clinic:', error); return { success: false, message: `Gagal menghapus klinik: ${error.message}` }; } }; // Actions untuk update master config (sync dengan masterStore operations) const updateClinicMasterConfig = (kode, masterConfig) => { const clinic = clinics.value.find(c => c.kode === kode); if (clinic) { clinic.totalQuota = masterConfig.totalQuota || clinic.totalQuota; clinic.jamShiftPerHari = masterConfig.jamShiftPerHari || clinic.jamShiftPerHari; clinic.autoShift = masterConfig.autoShift !== undefined ? masterConfig.autoShift : clinic.autoShift; clinic.jadwalKlinik = masterConfig.jadwalKlinik || clinic.jadwalKlinik; clinic.tanggalTutup = masterConfig.tanggalTutup || clinic.tanggalTutup; return { success: true, clinic }; } return { success: false, message: 'Clinic not found' }; }; return { clinics, isLoadingAPI, apiError, lastSyncTimestamp, isSyncNeeded, getAllClinics, getClinicsByJenisLayanan, getClinicByName, getClinicByKode, getClinicsForDropdown, updateClinicMasterConfig, fetchRegulerClinics, addClinic, updateClinic, deleteClinic, }; }, { persist: { key: 'clinic-store-state', storage: typeof window !== 'undefined' ? localStorage : undefined, paths: ['clinics', 'lastSyncTimestamp'], }, });