560 lines
17 KiB
JavaScript
560 lines
17 KiB
JavaScript
// 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;
|
||
// Only de-duplicate if IDs match. If IDs are different but names same, they might be valid distinct rooms.
|
||
if (roomId && existingId && String(roomId) === String(existingId)) 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}`;
|
||
const finalRoomId = apiRoom.idruangan || apiRoom.idruang || (index + 1).toString();
|
||
|
||
mergedRooms.push({
|
||
id: roomIdCounter++,
|
||
kodeKlinik: clinic.kode,
|
||
namaKlinik: clinic.name,
|
||
kodeRuang: finalRoomId,
|
||
namaRuang: apiRoom.namaruangan || apiRoom.nama_ruang || apiRoom.nama || fallbackName,
|
||
nomorRuang: finalRoomId,
|
||
nomorScreen: apiRoom.nomor_screen || `${clinic.kode}${finalRoomId}`,
|
||
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'})`);
|
||
const fallbackRoomId = rawClinicData.idruangan || rawClinicData.idruang || clinic.id || clinic.kode;
|
||
mergedRooms.push({
|
||
id: roomIdCounter++,
|
||
kodeKlinik: clinic.kode,
|
||
namaKlinik: clinic.name,
|
||
kodeRuang: fallbackRoomId,
|
||
namaRuang: clinic.name || rawClinicData.namaklinik || 'Ruang 1',
|
||
nomorRuang: String(fallbackRoomId),
|
||
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'],
|
||
},
|
||
});
|
||
|