Files
web-antrean/stores/clinicStore.js
T

1038 lines
33 KiB
JavaScript

// 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'],
},
});