// stores/ruangStore.js // Merged dari masterStore.ruangData + klinikruangstore.klinikRuangList import { defineStore } from 'pinia'; import { ref, computed } from 'vue'; import { useClinicStore } from './clinicStore'; export const useRuangStore = defineStore('ruang', () => { const clinicStore = useClinicStore(); // State/Computed - List Master Klinik (disinkronkan dengan clinicStore) const masterKlinikList = computed(() => { const baseList = typeof clinicStore.getClinicsForDropdown === 'function' ? clinicStore.getClinicsForDropdown() : []; return baseList.map((c) => ({ kode: c.kode, nama: c.name, id: c.id, })); }); // State - Ruang Data (merged dari masterStore.ruangData + klinikruangstore.klinikRuangList) const ruangData = ref([ { id: 1, no: 1, kodeKlinik: 'AN', namaKlinik: 'ANAK', namaRuang: 'R. TINDAKAN', ruangList: [ { nomorRuang: '1', namaRuang: 'R. TINDAKAN', nomorScreen: '101' } ] }, { id: 2, no: 2, kodeKlinik: 'AS', namaKlinik: 'ANESTESI', namaRuang: 'Ruang 1, Ruang 2, Ruang 3, Ruang 4, Ruang 5, Ruang 6', ruangList: [ { nomorRuang: '1', namaRuang: 'Ruang 1', nomorScreen: '201' }, { nomorRuang: '2', namaRuang: 'Ruang 2', nomorScreen: '202' }, { nomorRuang: '3', namaRuang: 'Ruang 3', nomorScreen: '203' }, { nomorRuang: '4', namaRuang: 'Ruang 4', nomorScreen: '204' }, { nomorRuang: '5', namaRuang: 'Ruang 5', nomorScreen: '205' }, { nomorRuang: '6', namaRuang: 'Ruang 6', nomorScreen: '206' } ] }, { id: 3, no: 3, kodeKlinik: 'BD', namaKlinik: 'BEDAH', namaRuang: 'Ruang Konsultasi', ruangList: [ { nomorRuang: '1', namaRuang: 'Ruang Konsultasi', nomorScreen: '301' } ] }, { id: 4, no: 4, kodeKlinik: 'GR', namaKlinik: 'GERIATRI', namaRuang: 'Ruang Pemeriksaan', ruangList: [ { nomorRuang: '1', namaRuang: 'Ruang Pemeriksaan', nomorScreen: '401' } ] }, { id: 5, no: 5, kodeKlinik: 'GI', namaKlinik: 'GIGI DAN MULUT', namaRuang: 'Ruang 1, Ruang 2, Ruang 3', ruangList: [ { nomorRuang: '1', namaRuang: 'Ruang 1', nomorScreen: '501' }, { nomorRuang: '2', namaRuang: 'Ruang 2', nomorScreen: '502' }, { nomorRuang: '3', namaRuang: 'Ruang 3', nomorScreen: '503' } ] }, ]); // Computed const totalKlinikRuang = computed(() => ruangData.value.length); const totalRuangan = computed(() => { return ruangData.value.reduce((total, klinik) => { return total + klinik.ruangList.length; }, 0); }); // Get all ruang list (for antrian display) const getAllRuangList = computed(() => { const allRuang = []; ruangData.value.forEach(klinik => { klinik.ruangList.forEach(ruang => { allRuang.push({ kodeKlinik: klinik.kodeKlinik, namaKlinik: klinik.namaKlinik, ...ruang }); }); }); return allRuang; }); // Getters const getKlinikByCode = (kode) => { return ruangData.value.find(k => k.kodeKlinik === kode); }; const getRuangByKlinik = (kodeKlinik) => { return ruangData.value.filter(r => r.kodeKlinik === kodeKlinik); }; // Actions - CRUD Operations const createKlinikRuang = (data) => { const newId = Math.max(...ruangData.value.map(r => r.id), 0) + 1; const newNo = ruangData.value.length + 1; // Generate nama ruang untuk display const namaRuangDisplay = data.ruangList .map(r => r.namaRuang) .filter(n => n) .join(', '); const newKlinikRuang = { id: newId, no: newNo, kodeKlinik: data.kodeKlinik, namaKlinik: data.namaKlinik, namaRuang: namaRuangDisplay, ruangList: data.ruangList }; ruangData.value.push(newKlinikRuang); return { success: true, message: `Klinik Ruang ${data.namaKlinik} berhasil ditambahkan`, data: newKlinikRuang }; }; const updateKlinikRuang = (id, data) => { const index = ruangData.value.findIndex(r => r.id === id); if (index === -1) { return { success: false, message: 'Klinik Ruang tidak ditemukan' }; } // Generate nama ruang untuk display const namaRuangDisplay = data.ruangList .map(r => r.namaRuang) .filter(n => n) .join(', '); ruangData.value[index] = { ...ruangData.value[index], kodeKlinik: data.kodeKlinik, namaKlinik: data.namaKlinik, namaRuang: namaRuangDisplay, ruangList: data.ruangList }; return { success: true, message: `Klinik Ruang ${data.namaKlinik} berhasil diupdate`, data: ruangData.value[index] }; }; const deleteKlinikRuang = (id) => { const index = ruangData.value.findIndex(r => r.id === id); if (index === -1) { return { success: false, message: 'Klinik Ruang tidak ditemukan' }; } const deletedKlinik = ruangData.value[index]; ruangData.value.splice(index, 1); // Reorder numbers ruangData.value.forEach((r, idx) => { r.no = idx + 1; }); return { success: true, message: `Klinik Ruang ${deletedKlinik.namaKlinik} berhasil dihapus` }; }; const addRuangToKlinik = (klinikId, ruangDataItem) => { const klinik = ruangData.value.find(k => k.id === klinikId); if (!klinik) { return { success: false, message: 'Klinik tidak ditemukan' }; } klinik.ruangList.push(ruangDataItem); // Update display name klinik.namaRuang = klinik.ruangList .map(r => r.namaRuang) .filter(n => n) .join(', '); return { success: true, message: `Ruang ${ruangDataItem.namaRuang} berhasil ditambahkan`, data: klinik }; }; const removeRuangFromKlinik = (klinikId, ruangIndex) => { const klinik = ruangData.value.find(k => k.id === klinikId); if (!klinik) { return { success: false, message: 'Klinik tidak ditemukan' }; } if (klinik.ruangList.length <= 1) { return { success: false, message: 'Minimal harus ada 1 ruangan' }; } const removedRuang = klinik.ruangList[ruangIndex]; klinik.ruangList.splice(ruangIndex, 1); // Update display name klinik.namaRuang = klinik.ruangList .map(r => r.namaRuang) .filter(n => n) .join(', '); return { success: true, message: `Ruang ${removedRuang.namaRuang} berhasil dihapus`, data: klinik }; }; const searchKlinikRuang = (searchTerm) => { if (!searchTerm) return ruangData.value; const term = searchTerm.toLowerCase(); return ruangData.value.filter(k => k.kodeKlinik.toLowerCase().includes(term) || k.namaKlinik.toLowerCase().includes(term) || k.namaRuang.toLowerCase().includes(term) ); }; // Alias methods untuk backward compatibility dengan masterStore const addRuang = (ruangPayload) => { return createKlinikRuang(ruangPayload); }; const updateRuang = (ruangPayload) => { return updateKlinikRuang(ruangPayload.id, ruangPayload); }; const deleteRuang = (ruangId) => { return deleteKlinikRuang(ruangId); }; // New method for replacing all rooms (used by MasterKlinikRuang when generating from API) const replaceAllRooms = (newRoomsData) => { // Group rooms by clinic CODE + JENIS LAYANAN (to keep Reguler and Eksekutif separate) const groupedByClinic = {}; newRoomsData.forEach(room => { // Use kodeKlinik + jenisLayanan as key to separate same clinic with different service types const key = `${room.kodeKlinik}-${room.jenisLayanan}`; if (!groupedByClinic[key]) { groupedByClinic[key] = { id: room.id || generateRoomId(), kodeKlinik: room.kodeKlinik, namaKlinik: room.namaKlinik, jenisLayanan: room.jenisLayanan, ruangList: [] }; } groupedByClinic[key].ruangList.push({ nomorRuang: room.nomorRuang, namaRuang: room.namaRuang, nomorScreen: room.nomorScreen, kodeRuang: room.kodeRuang }); }); // Convert to array and add display names const newRuangData = Object.values(groupedByClinic).map((clinic, index) => ({ ...clinic, no: index + 1, namaRuang: clinic.ruangList.map(r => r.namaRuang).join(', ') })); ruangData.value = newRuangData; return { success: true, message: `${newRuangData.length} klinik dengan total ${newRoomsData.length} ruangan berhasil dimuat` }; }; /** * Fetch room data from API and merge with clinic metadata * preserving seed/local data (Eksekutif) */ const fetchRuangFromAPI = async (force = false) => { try { console.log('🔄 Fetching rooms from API...'); // 1. Ensure clinics are loaded (from clinicStore) // clinicStore.fetchRegulerClinics is already called in MasterKlinikRuang.vue // but we use the existing clinics in the store const allClinics = clinicStore.clinics; // 2. Fetch room data from new API const response = await fetch('http://10.10.150.131:8089/api/v1/loket/ruang'); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const rawData = await response.json(); const apiRoomsRaw = rawData.data || []; console.log('đŸ“Ĩ Received API rooms:', apiRoomsRaw.length); // 3. Prepare rooms list (API + Seed) const mergedRooms = []; let roomIdCounter = 1; // 3. Group and De-duplicate API data by Clinic + Service Type const apiGroups = new Map(); // Key: clinic.kode-jenisLayanan, Value: { clinic, apiRooms: [], rawClinicData } const handledClinicKeys = new Set(); // Track unique combinations of kode-jenisLayanan handled by API apiRoomsRaw.forEach(apiClinic => { const clinicId = apiClinic.idklinik; const clinicCode = apiClinic.kodeloket || apiClinic.kodeklinik; // Find clinic metadata in store - Specifically look for Reguler clinic first let clinic = allClinics.find(c => c.jenisLayanan === 'Reguler' && ( (clinicId && String(c.id) === String(clinicId)) || (clinicCode && String(c.kode) === String(clinicCode)) || (apiClinic.namaklinik && c.name.toUpperCase() === apiClinic.namaklinik.toUpperCase()) ) ) || allClinics.find(c => (clinicId && String(c.id) === String(clinicId)) || (clinicCode && String(c.kode) === String(clinicCode)) || (apiClinic.namaklinik && c.name.toUpperCase() === apiClinic.namaklinik.toUpperCase()) ); // If clinic metadata not found in store, use API data to create a basic clinic object if (!clinic) { console.log(`📡 Clinic ${apiClinic.namaklinik} (${clinicId}) not found in store, using API data directly`); clinic = { id: clinicId, kode: clinicCode || clinicId, name: apiClinic.namaklinik, jenisLayanan: 'Reguler', totalQuota: 0 // Assume 0 if not in store }; } // FILTER: Skip if totalQuota is 0 (referencing MasterKlinik.vue logic) if (clinic.totalQuota === 0) { console.log(`â­ī¸ Skipping clinic ${clinic.name} because totalQuota is 0`); return; } const clinicKey = `${clinic.kode}-${clinic.jenisLayanan || 'Reguler'}`; if (!apiGroups.has(clinicKey)) { apiGroups.set(clinicKey, { clinic, apiRooms: [], rawClinicData: apiClinic // Keep for fallback fields }); } const group = apiGroups.get(clinicKey); const newRooms = apiClinic.ruangan || []; newRooms.forEach(nr => { // Check for duplicates by ID OR Name within this specific clinic group const roomId = nr.idruangan || nr.idruang; const roomName = (nr.namaruangan || nr.nama_ruang || nr.nama || '').trim().toUpperCase(); const isDuplicate = group.apiRooms.some(existing => { const existingId = existing.idruangan || existing.idruang; const existingName = (existing.namaruangan || existing.nama_ruang || existing.nama || '').trim().toUpperCase(); // Duplicate if ID matches OR Name matches (more aggressive de-duplication) if (roomId && existingId && String(roomId) === String(existingId)) return true; if (roomName && existingName && roomName === existingName) return true; return false; }); if (!isDuplicate) { group.apiRooms.push(nr); } }); }); // 4. Process Grouped Clinics into mergedRooms apiGroups.forEach((group, clinicKey) => { const { clinic, apiRooms, rawClinicData } = group; handledClinicKeys.add(clinicKey); // Check if API room data is "generic" (only 1 room with same name as clinic) const isGenericSingleRoom = apiRooms.length === 1 && (apiRooms[0].namaruangan?.trim().toUpperCase() === clinic.name.toUpperCase() || !apiRooms[0].namaruangan || apiRooms[0].namaruangan === '-'); const hasManySpecialists = clinic.spesialis && clinic.spesialis.length > 1; // Priority 1: Use pooled and de-duplicated rooms from API if (apiRooms.length > 0 && !(isGenericSingleRoom && hasManySpecialists)) { apiRooms.forEach((apiRoom, index) => { const fallbackName = `Ruang ${apiRoom.idruangan || apiRoom.nomor_ruang || index + 1}`; mergedRooms.push({ id: roomIdCounter++, kodeKlinik: clinic.kode, namaKlinik: clinic.name, kodeRuang: apiRoom.idruangan || apiRoom.idruang || `${clinic.kode}-${roomIdCounter}`, namaRuang: apiRoom.namaruangan || apiRoom.nama_ruang || apiRoom.nama || fallbackName, nomorRuang: apiRoom.idruangan || apiRoom.nomor_ruang || (index + 1).toString(), nomorScreen: apiRoom.nomor_screen || `${clinic.kode}${apiRoom.idruangan || index + 1}`, jenisLayanan: clinic.jenisLayanan || 'Reguler' }); }); } // Priority 2: Fallback to clinic identity if no rooms found else { console.log(`â„šī¸ Using clinic fallback for ${clinic.name} (${apiRooms.length === 0 ? 'Empty API' : 'Generic API'})`); mergedRooms.push({ id: roomIdCounter++, kodeKlinik: clinic.kode, namaKlinik: clinic.name, kodeRuang: rawClinicData.idruangan || rawClinicData.idruang || clinic.id || clinic.kode, namaRuang: clinic.name || rawClinicData.namaklinik || 'Ruang 1', nomorRuang: rawClinicData.nomor_ruang || '1', nomorScreen: rawClinicData.nomor_screen || `${clinic.kode}1`, jenisLayanan: clinic.jenisLayanan || 'Reguler' }); } }); // 5. Add Seed Data (Eksekutif) + Reguler clinics not in API allClinics.forEach(clinic => { const clinicKey = `${clinic.kode}-${clinic.jenisLayanan || 'Reguler'}`; // Skip if this specific clinic+layanan combination was already handled by API if (!handledClinicKeys.has(clinicKey)) { // FILTER: Skip if totalQuota is 0 if (clinic.totalQuota === 0) { console.log(`â­ī¸ Skipping seed clinic ${clinic.name} because totalQuota is 0`); return; } console.log(`➕ Adding clinic from seed/metadata: ${clinic.name} (${clinic.jenisLayanan})`); mergedRooms.push({ id: roomIdCounter++, kodeKlinik: clinic.kode, namaKlinik: clinic.name, kodeRuang: clinic.id || clinic.kode, namaRuang: clinic.name, nomorRuang: '1', nomorScreen: `${clinic.kode}1`, jenisLayanan: clinic.jenisLayanan || 'Reguler' }); } }); // 5. Update store const result = replaceAllRooms(mergedRooms); return { success: true, message: `Berhasil sinkronisasi ${apiRoomsRaw.length} ruang dari API dan ${allClinics.length} data klinik` }; } catch (error) { console.error('❌ Error in fetchRuangFromAPI:', error); return { success: false, message: `Gagal memuat data ruang: ${error.message}` }; } }; // Helper to generate unique room ID const generateRoomId = () => { return Date.now() + Math.floor(Math.random() * 1000); }; return { // State masterKlinikList, ruangData, // Computed totalKlinikRuang, totalRuangan, getAllRuangList, // Getters getKlinikByCode, getRuangByKlinik, // Actions (new names) createKlinikRuang, updateKlinikRuang, deleteKlinikRuang, addRuangToKlinik, removeRuangFromKlinik, searchKlinikRuang, // Alias for backward compatibility addRuang, updateRuang, deleteRuang, // New method for API-based room generation replaceAllRooms, fetchRuangFromAPI, }; }, { persist: { key: 'ruang-store-state', storage: typeof window !== 'undefined' ? localStorage : undefined, paths: ['ruangData'], }, });