1038 lines
33 KiB
JavaScript
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'],
|
|
},
|
|
});
|