Files
web-antrean/stores/ruangStore.js
T
2026-02-11 10:29:19 +07:00

560 lines
17 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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'],
},
});