push perbaikan
This commit is contained in:
@@ -1,5 +1,70 @@
|
||||
<script setup>
|
||||
import { onMounted, ref } from 'vue';
|
||||
|
||||
const nuxtApp = useNuxtApp();
|
||||
const loading = ref(false);
|
||||
|
||||
nuxtApp.hook('page:start', () => {
|
||||
loading.value = true;
|
||||
});
|
||||
nuxtApp.hook('page:finish', () => {
|
||||
loading.value = false;
|
||||
});
|
||||
|
||||
// Naikkan versi ini setiap kali ada perubahan struktur state (schema migration).
|
||||
// Saat versi tidak cocok, semua cache Pinia akan dibersihkan otomatis.
|
||||
const STORE_SCHEMA_VERSION = 3;
|
||||
const VERSION_KEY = 'app-store-version';
|
||||
|
||||
// Daftar semua localStorage key yang dikelola oleh Pinia stores
|
||||
const PINIA_STORE_KEYS = [
|
||||
'queue-store-state',
|
||||
'clinic-store-state',
|
||||
'doctor-store',
|
||||
'loket-store-state',
|
||||
'ruang-store-state',
|
||||
'klinikruang-store-state',
|
||||
'antrean-masuk-screen-store',
|
||||
'anjungan-store',
|
||||
'screen-store',
|
||||
'penunjang-store',
|
||||
];
|
||||
|
||||
onMounted(() => {
|
||||
try {
|
||||
if (typeof window === 'undefined' || !window.localStorage) return;
|
||||
|
||||
const savedVersion = parseInt(localStorage.getItem(VERSION_KEY) || '0', 10);
|
||||
|
||||
if (savedVersion < STORE_SCHEMA_VERSION) {
|
||||
// Versi lama terdeteksi → hapus semua cache Pinia sekaligus
|
||||
console.log(`[Migration] Store schema v${savedVersion} → v${STORE_SCHEMA_VERSION}. Clearing all caches...`);
|
||||
PINIA_STORE_KEYS.forEach(key => localStorage.removeItem(key));
|
||||
localStorage.setItem(VERSION_KEY, String(STORE_SCHEMA_VERSION));
|
||||
console.log('[Migration] Done. App will fetch fresh data from API.');
|
||||
}
|
||||
} catch (e) {
|
||||
// Ignore storage errors (e.g., private browsing mode)
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-app>
|
||||
<v-overlay
|
||||
:model-value="loading"
|
||||
class="align-center justify-center"
|
||||
persistent
|
||||
z-index="9999"
|
||||
>
|
||||
<v-progress-circular
|
||||
color="primary"
|
||||
indeterminate
|
||||
size="64"
|
||||
width="6"
|
||||
></v-progress-circular>
|
||||
</v-overlay>
|
||||
|
||||
<NuxtLayout>
|
||||
<NuxtPage />
|
||||
</NuxtLayout>
|
||||
|
||||
@@ -77,6 +77,9 @@ export default defineNuxtConfig({
|
||||
// External API
|
||||
externalApiBaseUrl: process.env.EXTERNAL_API_BASE_URL || 'http://10.10.123.135:8084',
|
||||
externalApiTimeout: parseInt(process.env.EXTERNAL_API_TIMEOUT || '10000', 10),
|
||||
proxyClientOrigin: process.env.PROXY_CLIENT_ORIGIN || 'http://10.10.150.175:3000',
|
||||
proxyTargetHostFallback: process.env.PROXY_TARGET_HOST_FALLBACK || '10.10.123.135:8084',
|
||||
proxyTargetHostKlinikFallback: process.env.PROXY_TARGET_HOST_KLINIK_FALLBACK || '10.10.123.140:8089',
|
||||
|
||||
public: {
|
||||
authUrl: process.env.AUTH_ORIGIN,
|
||||
|
||||
@@ -933,12 +933,10 @@ const filterOptionsList = {
|
||||
// WebSocket configuration
|
||||
const config = useRuntimeConfig();
|
||||
|
||||
// Generate a unique session suffix (random ID)
|
||||
const uniqueSessionSuffix = ref(process.client ? Math.random().toString(36).substring(2, 8) : '')
|
||||
|
||||
// WebSocket client ID for admin
|
||||
// Use a DETERMINISTIC client ID so that other pages can broadcast directly to this admin.
|
||||
// Format: admin-klinik-ruang-{kodeKlinik} — stable and targetable from any device.
|
||||
const adminClientId = computed(() => {
|
||||
return `admin-klinik-ruang-${kodeKlinik.value}-${uniqueSessionSuffix.value}`
|
||||
return `admin-klinik-ruang-${kodeKlinik.value}`
|
||||
})
|
||||
|
||||
const isConnected = computed(() => queueStore.isWsConnected);
|
||||
@@ -1941,23 +1939,25 @@ onMounted(async () => {
|
||||
await fetchAllData();
|
||||
|
||||
// 3. Centralized WebSocket & interest registration
|
||||
// initWebSocket is already called via the watcher on adminClientId
|
||||
// Explicitly init WS with deterministic ID so other pages can target this admin directly.
|
||||
if (kodeKlinik.value) {
|
||||
queueStore.registerClinicInterest(kodeKlinik.value);
|
||||
queueStore.initWebSocket(adminClientId.value);
|
||||
queueStore.registerClinicInterest(kodeKlinik.value);
|
||||
}
|
||||
});
|
||||
|
||||
// 4. Lifecycle cleanup (Keep outside async block to preserve context)
|
||||
let pollInterval;
|
||||
onMounted(() => {
|
||||
// Check for daily reset and poll data every minute
|
||||
// Safety-net polling: re-fetch every 30 seconds to catch any missed WS events.
|
||||
// This guarantees data is never more than 30 seconds stale.
|
||||
pollInterval = setInterval(() => {
|
||||
const didReset = queueStore.checkAndResetDaily();
|
||||
if (didReset) {
|
||||
console.log("🕒 [AdminKlinikRuang] 2 AM threshold reached. Data reset performed.");
|
||||
}
|
||||
fetchAllData();
|
||||
}, 60000);
|
||||
}, 30000); // 30 seconds
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
|
||||
@@ -392,12 +392,11 @@ const changeKlinik = async (klinik) => {
|
||||
};
|
||||
|
||||
|
||||
// Generate a unique session suffix (random ID)
|
||||
const uniqueSessionSuffix = ref(process.client ? Math.random().toString(36).substring(2, 8) : '')
|
||||
|
||||
// Use a DETERMINISTIC client ID so that Anjungan can broadcast directly to this admin.
|
||||
// Format: admin-loket-{loketId} — stable and targetable from any device.
|
||||
const anjunganClientId = computed(() => {
|
||||
if (!loketId.value) return ''
|
||||
return `admin-loket-${loketId.value}-${uniqueSessionSuffix.value}`
|
||||
return `admin-loket-${loketId.value}`
|
||||
})
|
||||
|
||||
const fetchAllData = async () => {
|
||||
@@ -450,6 +449,9 @@ watch(anjunganClientId, (newClientId, oldClientId) => {
|
||||
})
|
||||
|
||||
// PERSISTENCE FIX: Ensure data exists on mount
|
||||
// Periodic polling interval ref (safety-net fallback)
|
||||
let periodicRefreshInterval = null;
|
||||
|
||||
onMounted(async () => {
|
||||
console.log("🚀 AdminLoket Component mounted");
|
||||
|
||||
@@ -470,6 +472,12 @@ onMounted(async () => {
|
||||
}
|
||||
}, 60000); // Check every minute
|
||||
|
||||
// Safety-net: Poll every 30 seconds to catch patients registered from Anjungan
|
||||
// even if a WebSocket notification was missed or not received.
|
||||
periodicRefreshInterval = setInterval(() => {
|
||||
fetchPatientsForCurrentLoket();
|
||||
}, 30000);
|
||||
|
||||
// Initialize and connect WebSocket (Centralized)
|
||||
queueStore.initWebSocket(anjunganClientId.value);
|
||||
|
||||
@@ -484,6 +492,7 @@ onMounted(async () => {
|
||||
|
||||
onUnmounted(() => {
|
||||
if (resetCheckInterval) clearInterval(resetCheckInterval);
|
||||
if (periodicRefreshInterval) clearInterval(periodicRefreshInterval);
|
||||
// WebSocket is now global, we might not want to disconnect on every unmount
|
||||
// if other pages are still open, but usually for Admin/Anjungan it's okay.
|
||||
// We keep it connected unless specified otherwise.
|
||||
|
||||
@@ -1280,6 +1280,26 @@ const registerPatient = async (
|
||||
lastRegisteredPatient.value,
|
||||
);
|
||||
|
||||
// REAL-TIME SYNC: Notify AdminLoket to refresh its patient list immediately.
|
||||
// AdminLoket listens on the deterministic client ID "admin-loket-{loketId}".
|
||||
// Without this, AdminLoket only updates on manual refresh or 30s polling.
|
||||
if (result.patient.loketId && queueStore.sendViaPost) {
|
||||
const targetLoketId = result.patient.loketId;
|
||||
console.log(`📡 [Anjungan] Notifying AdminLoket ${targetLoketId} of new patient registration...`);
|
||||
queueStore.sendViaPost({
|
||||
to_client: `admin-loket-${targetLoketId}`,
|
||||
data: {
|
||||
loketId: targetLoketId,
|
||||
triggerRefresh: true,
|
||||
source: 'anjungan-registration',
|
||||
ticket: result.patient.noAntrian?.split(' |')[0] || '',
|
||||
}
|
||||
}).catch(err => {
|
||||
// Non-critical: polling will catch this within 30s anyway
|
||||
console.warn('⚠️ [Anjungan] Could not notify AdminLoket via WS:', err?.message);
|
||||
});
|
||||
}
|
||||
|
||||
// Tampilkan dialog print - gunakan nextTick untuk memastikan reactive update
|
||||
await nextTick();
|
||||
showPrintDialog.value = true;
|
||||
|
||||
@@ -98,7 +98,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
import { computed, onMounted } from 'vue';
|
||||
import { useAnjunganStore } from '@/stores/anjunganStore';
|
||||
import { useMasterStore } from '@/stores/masterStore';
|
||||
import { useRoute } from '#app';
|
||||
@@ -111,6 +111,10 @@ const anjunganStore = useAnjunganStore();
|
||||
const masterStore = useMasterStore();
|
||||
const route = useRoute();
|
||||
|
||||
onMounted(() => {
|
||||
anjunganStore.fetchAnjungan();
|
||||
});
|
||||
|
||||
// Safeguard supaya tidak undefined saat store belum terisi
|
||||
const anjunganList = computed(() => {
|
||||
const fromGetter = anjunganStore.getAllAnjungan?.value;
|
||||
|
||||
@@ -1383,6 +1383,25 @@ const registerPatient = async (
|
||||
lastRegisteredPatient.value,
|
||||
);
|
||||
|
||||
// REAL-TIME SYNC: Notify AdminLoket to refresh its patient list immediately.
|
||||
// AdminLoket listens on the deterministic client ID "admin-loket-{loketId}".
|
||||
if (result.patient.loketId && queueStore.sendViaPost) {
|
||||
const targetLoketId = result.patient.loketId;
|
||||
console.log(`📡 [AnjunganCopy] Notifying AdminLoket ${targetLoketId} of new patient registration...`);
|
||||
queueStore.sendViaPost({
|
||||
to_client: `admin-loket-${targetLoketId}`,
|
||||
data: {
|
||||
loketId: targetLoketId,
|
||||
triggerRefresh: true,
|
||||
source: 'anjungan-registration',
|
||||
ticket: result.patient.noAntrian?.split(' |')[0] || '',
|
||||
}
|
||||
}).catch(err => {
|
||||
// Non-critical: polling will catch this within 30s anyway
|
||||
console.warn('⚠️ [AnjunganCopy] Could not notify AdminLoket via WS:', err?.message);
|
||||
});
|
||||
}
|
||||
|
||||
// Tampilkan dialog print - gunakan nextTick untuk memastikan reactive update
|
||||
await nextTick();
|
||||
showPrintDialog.value = true;
|
||||
|
||||
@@ -98,7 +98,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
import { computed, onMounted } from 'vue';
|
||||
import { useAnjunganStore } from '@/stores/anjunganStore';
|
||||
import { useMasterStore } from '@/stores/masterStore';
|
||||
import { useRoute } from '#app';
|
||||
@@ -111,6 +111,10 @@ const anjunganStore = useAnjunganStore();
|
||||
const masterStore = useMasterStore();
|
||||
const route = useRoute();
|
||||
|
||||
onMounted(() => {
|
||||
anjunganStore.fetchAnjungan();
|
||||
});
|
||||
|
||||
// Safeguard supaya tidak undefined saat store belum terisi
|
||||
const anjunganList = computed(() => {
|
||||
const fromGetter = anjunganStore.getAllAnjungan?.value;
|
||||
|
||||
@@ -98,7 +98,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
import { computed, onMounted } from 'vue';
|
||||
import { useAntreanMasukScreenStore } from '@/stores/antreanMasukScreenStore';
|
||||
import { useLoketStore } from '@/stores/loketStore';
|
||||
import { useRoute } from '#app';
|
||||
@@ -111,6 +111,10 @@ const antreanMasukScreenStore = useAntreanMasukScreenStore();
|
||||
const loketStore = useLoketStore();
|
||||
const route = useRoute();
|
||||
|
||||
onMounted(() => {
|
||||
antreanMasukScreenStore.fetchAntreanMasukScreens();
|
||||
});
|
||||
|
||||
// Helper to get loket name by ID
|
||||
const getLoketNameById = (loketId) => {
|
||||
const loket = loketStore.getLoketById(loketId);
|
||||
@@ -145,7 +149,7 @@ const goNext = () => {
|
||||
};
|
||||
|
||||
const navigateToScreen = (screenId) => {
|
||||
navigateTo(`/anjungan/antreanmasuk/${screenId}`);
|
||||
navigateTo(`/Anjungan/AntreanMasuk/${screenId}`);
|
||||
};
|
||||
|
||||
const navigateToSettings = () => {
|
||||
|
||||
@@ -91,7 +91,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
import { computed, onMounted } from 'vue';
|
||||
import { useScreenStore } from '@/stores/screenStore';
|
||||
import { useMasterStore } from '@/stores/masterStore';
|
||||
import { useRoute } from '#app';
|
||||
@@ -104,6 +104,10 @@ const screenStore = useScreenStore();
|
||||
const masterStore = useMasterStore();
|
||||
const route = useRoute();
|
||||
|
||||
onMounted(() => {
|
||||
screenStore.fetchScreens();
|
||||
});
|
||||
|
||||
// Safeguard supaya tidak undefined saat store belum terisi
|
||||
const screens = computed(() => {
|
||||
const fromGetter = screenStore.getAllScreens?.value;
|
||||
|
||||
+6
-4
@@ -681,7 +681,7 @@ const areaChartOptions = ref({
|
||||
backgroundColor: 'rgba(255, 255, 255, 0.95)',
|
||||
titleColor: colors.neutral[900],
|
||||
bodyColor: colors.neutral[700],
|
||||
borderColor: colors.neutral[400],
|
||||
borderColor: colors.neutral[500],
|
||||
borderWidth: 1,
|
||||
padding: 12,
|
||||
boxPadding: 6,
|
||||
@@ -778,7 +778,7 @@ const doughnutOptions = ref({
|
||||
backgroundColor: 'rgba(255, 255, 255, 0.95)',
|
||||
titleColor: colors.neutral[900],
|
||||
bodyColor: colors.neutral[700],
|
||||
borderColor: colors.neutral[400],
|
||||
borderColor: colors.neutral[500],
|
||||
borderWidth: 1,
|
||||
padding: 12,
|
||||
boxPadding: 6,
|
||||
@@ -814,7 +814,7 @@ const horizontalBarOptions = ref({
|
||||
backgroundColor: 'rgba(255, 255, 255, 0.95)',
|
||||
titleColor: colors.neutral[900],
|
||||
bodyColor: colors.neutral[700],
|
||||
borderColor: colors.neutral[400],
|
||||
borderColor: colors.neutral[500],
|
||||
borderWidth: 1,
|
||||
padding: 12,
|
||||
boxPadding: 6,
|
||||
@@ -889,7 +889,7 @@ const polarAreaOptions = ref({
|
||||
backgroundColor: 'rgba(255, 255, 255, 0.95)',
|
||||
titleColor: colors.neutral[900],
|
||||
bodyColor: colors.neutral[700],
|
||||
borderColor: colors.neutral[400],
|
||||
borderColor: colors.neutral[500],
|
||||
borderWidth: 1,
|
||||
padding: 12,
|
||||
boxPadding: 6,
|
||||
@@ -1841,6 +1841,7 @@ const downloadBlob = (blob, filename) => {
|
||||
background: linear-gradient(90deg, var(--color-primary-500) 0%, var(--color-primary-600) 50%, var(--color-secondary-500) 100%);
|
||||
opacity: 0;
|
||||
transition: opacity 0.4s ease;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
&::after {
|
||||
@@ -1853,6 +1854,7 @@ const downloadBlob = (blob, filename) => {
|
||||
background: radial-gradient(circle, rgba(58, 97, 201, 0.05) 0%, transparent 70%);
|
||||
opacity: 0;
|
||||
transition: opacity 0.4s ease;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
|
||||
@@ -348,9 +348,9 @@ const submitForm = async () => {
|
||||
|
||||
let result;
|
||||
if (isEdit.value) {
|
||||
result = anjunganStore.updateAnjungan(formData.value);
|
||||
result = await anjunganStore.updateAnjungan(formData.value);
|
||||
} else {
|
||||
result = anjunganStore.addAnjungan(formData.value);
|
||||
result = await anjunganStore.addAnjungan(formData.value);
|
||||
}
|
||||
|
||||
snackbar.value = {
|
||||
@@ -364,14 +364,10 @@ const submitForm = async () => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleDelete = (item) => {
|
||||
const handleDelete = async (item) => {
|
||||
if (confirm(`Hapus anjungan ${item.namaAnjungan}?`)) {
|
||||
const result = anjunganStore.deleteAnjungan(item.id);
|
||||
snackbar.value = {
|
||||
show: true,
|
||||
message: result.message,
|
||||
color: result.success ? 'success' : 'error',
|
||||
};
|
||||
const result = await anjunganStore.deleteAnjungan(item.id);
|
||||
snackbar.value = { show: true, message: result.message, color: result.success ? 'success' : 'error' };
|
||||
}
|
||||
};
|
||||
|
||||
@@ -391,6 +387,7 @@ const closePreviewDialog = () => {
|
||||
// Fetch Reguler clinics from API on mount
|
||||
onMounted(async () => {
|
||||
await clinicStore.fetchRegulerClinics();
|
||||
await anjunganStore.fetchAnjungan();
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
@@ -376,20 +376,21 @@
|
||||
definePageMeta({
|
||||
middleware: ['auth', 'check-page-access']
|
||||
})
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { ref, onMounted, computed, watch } from 'vue';
|
||||
import { useMasterStore } from '@/stores/masterStore';
|
||||
import { useClinicStore } from '@/stores/clinicStore';
|
||||
import { useRuangStore } from '@/stores/ruangStore';
|
||||
import { useKlinikRuangStore } from '@/stores/klinikruangstore';
|
||||
|
||||
const masterStore = useMasterStore();
|
||||
const clinicStore = useClinicStore();
|
||||
const ruangStore = useRuangStore();
|
||||
const klinikRuangStore = useKlinikRuangStore();
|
||||
const page = ref(1);
|
||||
const itemsPerPage = ref(10);
|
||||
const search = ref('');
|
||||
const filteredTotal = ref(masterStore.ruangData.length);
|
||||
|
||||
import { watch } from 'vue';
|
||||
watch(() => masterStore.ruangData.length, (newLen) => {
|
||||
if (!search.value) filteredTotal.value = newLen;
|
||||
}, { immediate: true });
|
||||
@@ -511,9 +512,9 @@ const submitForm = async () => {
|
||||
|
||||
let result;
|
||||
if (isEdit.value) {
|
||||
result = masterStore.updateRuang(formData.value);
|
||||
result = await klinikRuangStore.updateKlinikRuang(formData.value.id, formData.value);
|
||||
} else {
|
||||
result = masterStore.addRuang(formData.value);
|
||||
result = await klinikRuangStore.createKlinikRuang(formData.value);
|
||||
}
|
||||
|
||||
if (result.success) {
|
||||
@@ -524,14 +525,10 @@ const submitForm = async () => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleDelete = (item) => {
|
||||
if (confirm(`Hapus ruangan ${item.namaRuang} dari klinik ${item.namaKlinik}?`)) {
|
||||
const result = masterStore.deleteRuang(item.id);
|
||||
snackbar.value = {
|
||||
show: true,
|
||||
message: result.message,
|
||||
color: result.success ? 'success' : 'error'
|
||||
};
|
||||
const handleDelete = async (item) => {
|
||||
if (confirm(`Hapus ruangan dari klinik ${item.namaKlinik}?`)) {
|
||||
const result = await klinikRuangStore.deleteKlinikRuang(item.id);
|
||||
snackbar.value = { show: true, message: result.message, color: result.success ? 'success' : 'error' };
|
||||
}
|
||||
};
|
||||
|
||||
@@ -552,36 +549,16 @@ const closePreviewDialog = () => {
|
||||
// Fetch clinics and sync rooms on mount
|
||||
onMounted(async () => {
|
||||
console.log('🚀 MasterKlinikRuang mounted, syncing data...');
|
||||
|
||||
try {
|
||||
// 1. Fetch clinics from API (uses cache if available)
|
||||
const fetchClinicResult = await clinicStore.fetchRegulerClinics();
|
||||
console.log('📥 Clinic fetch result:', fetchClinicResult);
|
||||
|
||||
if (!fetchClinicResult.success) {
|
||||
snackbar.value = {
|
||||
show: true,
|
||||
message: `Klinik: ${fetchClinicResult.message}`,
|
||||
color: 'warning'
|
||||
};
|
||||
}
|
||||
|
||||
// 2. Fetch and merge rooms from API
|
||||
const syncRuangResult = await ruangStore.fetchRuangFromAPI();
|
||||
console.log('🔄 Room sync result:', syncRuangResult);
|
||||
|
||||
snackbar.value = {
|
||||
show: true,
|
||||
message: syncRuangResult.message,
|
||||
color: syncRuangResult.success ? 'success' : 'error'
|
||||
};
|
||||
await Promise.all([
|
||||
clinicStore.fetchRegulerClinics(),
|
||||
klinikRuangStore.fetchKlinikRuang(),
|
||||
ruangStore.fetchRuangFromAPI(),
|
||||
]);
|
||||
console.log('✅ MasterKlinikRuang data loaded');
|
||||
} catch (error) {
|
||||
console.error('❌ Error in onMounted:', error);
|
||||
snackbar.value = {
|
||||
show: true,
|
||||
message: `Error: ${error.message}`,
|
||||
color: 'error'
|
||||
};
|
||||
snackbar.value = { show: true, message: `Error: ${error.message}`, color: 'error' };
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
+13
-23
@@ -271,12 +271,16 @@
|
||||
definePageMeta({
|
||||
middleware: ['auth', 'check-page-access']
|
||||
})
|
||||
import { ref, computed } from 'vue';
|
||||
import { ref, computed, onMounted } from 'vue';
|
||||
import { useMasterStore } from '@/stores/masterStore';
|
||||
import { useScreenStore } from '@/stores/screenStore';
|
||||
|
||||
const masterStore = useMasterStore();
|
||||
const screenStore = useScreenStore();
|
||||
|
||||
onMounted(() => {
|
||||
screenStore.fetchScreens();
|
||||
});
|
||||
const dialog = ref(false);
|
||||
const isEdit = ref(false);
|
||||
const formRef = ref(null);
|
||||
@@ -348,34 +352,20 @@ const submitForm = async () => {
|
||||
if (!valid) return;
|
||||
|
||||
if (isEdit.value) {
|
||||
// Update screen
|
||||
const result = screenStore.updateScreen(formData.value);
|
||||
snackbar.value = {
|
||||
show: true,
|
||||
message: result.message,
|
||||
color: result.success ? 'success' : 'error'
|
||||
};
|
||||
const result = await screenStore.updateScreen(formData.value);
|
||||
snackbar.value = { show: true, message: result.message, color: result.success ? 'success' : 'error' };
|
||||
} else {
|
||||
// Add new screen
|
||||
const result = screenStore.addScreen(formData.value);
|
||||
snackbar.value = {
|
||||
show: true,
|
||||
message: result.message,
|
||||
color: result.success ? 'success' : 'error'
|
||||
};
|
||||
const result = await screenStore.addScreen(formData.value);
|
||||
snackbar.value = { show: true, message: result.message, color: result.success ? 'success' : 'error' };
|
||||
}
|
||||
|
||||
closeDialog();
|
||||
if (snackbar.value.color === 'success') closeDialog();
|
||||
};
|
||||
|
||||
const handleDelete = (item) => {
|
||||
const handleDelete = async (item) => {
|
||||
if (confirm(`Hapus screen ${item.namaScreen}?`)) {
|
||||
const result = screenStore.deleteScreen(item.id);
|
||||
snackbar.value = {
|
||||
show: true,
|
||||
message: result.message,
|
||||
color: result.success ? 'success' : 'error'
|
||||
};
|
||||
const result = await screenStore.deleteScreen(item.id);
|
||||
snackbar.value = { show: true, message: result.message, color: result.success ? 'success' : 'error' };
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -316,9 +316,9 @@ const initData = async () => {
|
||||
// Call immediately (non-blocking)
|
||||
initData();
|
||||
|
||||
// Also try onMounted for good measure
|
||||
onMounted(() => {
|
||||
initData();
|
||||
onMounted(async () => {
|
||||
await antreanMasukScreenStore.fetchAntreanMasukScreens();
|
||||
initData();
|
||||
});
|
||||
|
||||
const refreshData = async () => {
|
||||
@@ -429,34 +429,20 @@ const submitForm = async () => {
|
||||
if (!valid) return;
|
||||
|
||||
if (isEdit.value) {
|
||||
// Update screen
|
||||
const result = antreanMasukScreenStore.updateAntreanMasukScreen(formData.value);
|
||||
snackbar.value = {
|
||||
show: true,
|
||||
message: result.message,
|
||||
color: result.success ? 'success' : 'error'
|
||||
};
|
||||
const result = await antreanMasukScreenStore.updateAntreanMasukScreen(formData.value);
|
||||
snackbar.value = { show: true, message: result.message, color: result.success ? 'success' : 'error' };
|
||||
} else {
|
||||
// Add new screen
|
||||
const result = antreanMasukScreenStore.addAntreanMasukScreen(formData.value);
|
||||
snackbar.value = {
|
||||
show: true,
|
||||
message: result.message,
|
||||
color: result.success ? 'success' : 'error'
|
||||
};
|
||||
const result = await antreanMasukScreenStore.addAntreanMasukScreen(formData.value);
|
||||
snackbar.value = { show: true, message: result.message, color: result.success ? 'success' : 'error' };
|
||||
}
|
||||
|
||||
closeDialog();
|
||||
if (snackbar.value.color === 'success') closeDialog();
|
||||
};
|
||||
|
||||
const handleDelete = (item) => {
|
||||
const handleDelete = async (item) => {
|
||||
if (confirm(`Hapus screen ${item.namaScreen}?`)) {
|
||||
const result = antreanMasukScreenStore.deleteAntreanMasukScreen(item.id);
|
||||
snackbar.value = {
|
||||
show: true,
|
||||
message: result.message,
|
||||
color: result.success ? 'success' : 'error'
|
||||
};
|
||||
const result = await antreanMasukScreenStore.deleteAntreanMasukScreen(item.id);
|
||||
snackbar.value = { show: true, message: result.message, color: result.success ? 'success' : 'error' };
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
// server/api/config/anjungan.get.ts
|
||||
import { getConfigDb, parseConfigRow } from '~/server/utils/configDb';
|
||||
|
||||
export default defineEventHandler(() => {
|
||||
const db = getConfigDb();
|
||||
const rows = db.prepare('SELECT * FROM config_anjungan ORDER BY id ASC').all();
|
||||
return { success: true, data: rows.map(parseConfigRow) };
|
||||
});
|
||||
@@ -0,0 +1,19 @@
|
||||
// server/api/config/anjungan.post.ts
|
||||
import { getConfigDb, parseConfigRow } from '~/server/utils/configDb';
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const body = await readBody(event);
|
||||
const { namaAnjungan, jenisPasien, klinik = [] } = body;
|
||||
|
||||
if (!namaAnjungan || !jenisPasien) {
|
||||
throw createError({ statusCode: 400, message: 'namaAnjungan dan jenisPasien wajib diisi' });
|
||||
}
|
||||
|
||||
const db = getConfigDb();
|
||||
const result = db.prepare(
|
||||
`INSERT INTO config_anjungan (namaAnjungan, jenisPasien, klinik) VALUES (?, ?, ?)`
|
||||
).run(namaAnjungan, jenisPasien, JSON.stringify(klinik));
|
||||
|
||||
const newRow = db.prepare('SELECT * FROM config_anjungan WHERE id = ?').get(result.lastInsertRowid);
|
||||
return { success: true, data: parseConfigRow(newRow), message: `Anjungan ${namaAnjungan} berhasil ditambahkan` };
|
||||
});
|
||||
@@ -0,0 +1,14 @@
|
||||
// server/api/config/anjungan/[id].delete.ts
|
||||
import { getConfigDb } from '~/server/utils/configDb';
|
||||
|
||||
export default defineEventHandler((event) => {
|
||||
const id = Number(getRouterParam(event, 'id'));
|
||||
if (!id) throw createError({ statusCode: 400, message: 'ID tidak valid' });
|
||||
|
||||
const db = getConfigDb();
|
||||
const existing = db.prepare('SELECT * FROM config_anjungan WHERE id = ?').get(id) as any;
|
||||
if (!existing) throw createError({ statusCode: 404, message: 'Anjungan tidak ditemukan' });
|
||||
|
||||
db.prepare('DELETE FROM config_anjungan WHERE id = ?').run(id);
|
||||
return { success: true, message: `Anjungan ${existing.namaAnjungan} berhasil dihapus` };
|
||||
});
|
||||
@@ -0,0 +1,31 @@
|
||||
// server/api/config/anjungan/[id].put.ts
|
||||
import { getConfigDb, parseConfigRow } from '~/server/utils/configDb';
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const id = Number(getRouterParam(event, 'id'));
|
||||
if (!id) throw createError({ statusCode: 400, message: 'ID tidak valid' });
|
||||
|
||||
const body = await readBody(event);
|
||||
const { namaAnjungan, jenisPasien, klinik } = body;
|
||||
|
||||
const db = getConfigDb();
|
||||
const existing = db.prepare('SELECT * FROM config_anjungan WHERE id = ?').get(id) as any;
|
||||
if (!existing) throw createError({ statusCode: 404, message: 'Anjungan tidak ditemukan' });
|
||||
|
||||
db.prepare(
|
||||
`UPDATE config_anjungan SET
|
||||
namaAnjungan = ?,
|
||||
jenisPasien = ?,
|
||||
klinik = ?,
|
||||
updatedAt = strftime('%s', 'now')
|
||||
WHERE id = ?`
|
||||
).run(
|
||||
namaAnjungan ?? existing.namaAnjungan,
|
||||
jenisPasien ?? existing.jenisPasien,
|
||||
klinik !== undefined ? JSON.stringify(klinik) : existing.klinik,
|
||||
id
|
||||
);
|
||||
|
||||
const updated = db.prepare('SELECT * FROM config_anjungan WHERE id = ?').get(id);
|
||||
return { success: true, data: parseConfigRow(updated), message: 'Anjungan berhasil diperbarui' };
|
||||
});
|
||||
@@ -0,0 +1,8 @@
|
||||
// server/api/config/antrean-masuk-screen.get.ts
|
||||
import { getConfigDb, parseConfigRow } from '~/server/utils/configDb';
|
||||
|
||||
export default defineEventHandler(() => {
|
||||
const db = getConfigDb();
|
||||
const rows = db.prepare('SELECT * FROM config_antrean_masuk_screen ORDER BY id ASC').all();
|
||||
return { success: true, data: rows.map(parseConfigRow) };
|
||||
});
|
||||
@@ -0,0 +1,26 @@
|
||||
// server/api/config/antrean-masuk-screen.post.ts
|
||||
import { getConfigDb, parseConfigRow } from '~/server/utils/configDb';
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const body = await readBody(event);
|
||||
const { namaScreen, nomorScreen, loket = [] } = body;
|
||||
|
||||
if (!namaScreen || !nomorScreen) {
|
||||
throw createError({ statusCode: 400, message: 'namaScreen dan nomorScreen wajib diisi' });
|
||||
}
|
||||
|
||||
const db = getConfigDb();
|
||||
try {
|
||||
const result = db.prepare(
|
||||
`INSERT INTO config_antrean_masuk_screen (namaScreen, nomorScreen, loket) VALUES (?, ?, ?)`
|
||||
).run(namaScreen, nomorScreen, JSON.stringify(loket));
|
||||
|
||||
const newRow = db.prepare('SELECT * FROM config_antrean_masuk_screen WHERE id = ?').get(result.lastInsertRowid);
|
||||
return { success: true, data: parseConfigRow(newRow), message: `Screen ${namaScreen} berhasil ditambahkan` };
|
||||
} catch (e: any) {
|
||||
if (e.message?.includes('UNIQUE')) {
|
||||
throw createError({ statusCode: 409, message: `nomorScreen "${nomorScreen}" sudah digunakan` });
|
||||
}
|
||||
throw createError({ statusCode: 500, message: e.message });
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,14 @@
|
||||
// server/api/config/antrean-masuk-screen/[id].delete.ts
|
||||
import { getConfigDb } from '~/server/utils/configDb';
|
||||
|
||||
export default defineEventHandler((event) => {
|
||||
const id = Number(getRouterParam(event, 'id'));
|
||||
if (!id) throw createError({ statusCode: 400, message: 'ID tidak valid' });
|
||||
|
||||
const db = getConfigDb();
|
||||
const existing = db.prepare('SELECT * FROM config_antrean_masuk_screen WHERE id = ?').get(id) as any;
|
||||
if (!existing) throw createError({ statusCode: 404, message: 'Screen tidak ditemukan' });
|
||||
|
||||
db.prepare('DELETE FROM config_antrean_masuk_screen WHERE id = ?').run(id);
|
||||
return { success: true, message: `Screen ${existing.namaScreen} berhasil dihapus` };
|
||||
});
|
||||
@@ -0,0 +1,29 @@
|
||||
// server/api/config/antrean-masuk-screen/[id].put.ts
|
||||
import { getConfigDb, parseConfigRow } from '~/server/utils/configDb';
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const id = Number(getRouterParam(event, 'id'));
|
||||
if (!id) throw createError({ statusCode: 400, message: 'ID tidak valid' });
|
||||
|
||||
const body = await readBody(event);
|
||||
const { namaScreen, nomorScreen, loket } = body;
|
||||
|
||||
const db = getConfigDb();
|
||||
const existing = db.prepare('SELECT * FROM config_antrean_masuk_screen WHERE id = ?').get(id) as any;
|
||||
if (!existing) throw createError({ statusCode: 404, message: 'Screen tidak ditemukan' });
|
||||
|
||||
db.prepare(
|
||||
`UPDATE config_antrean_masuk_screen SET
|
||||
namaScreen = ?, nomorScreen = ?, loket = ?,
|
||||
updatedAt = strftime('%s', 'now')
|
||||
WHERE id = ?`
|
||||
).run(
|
||||
namaScreen ?? existing.namaScreen,
|
||||
nomorScreen ?? existing.nomorScreen,
|
||||
loket !== undefined ? JSON.stringify(loket) : existing.loket,
|
||||
id
|
||||
);
|
||||
|
||||
const updated = db.prepare('SELECT * FROM config_antrean_masuk_screen WHERE id = ?').get(id);
|
||||
return { success: true, data: parseConfigRow(updated), message: 'Screen berhasil diperbarui' };
|
||||
});
|
||||
@@ -0,0 +1,8 @@
|
||||
// server/api/config/klinik-ruang.get.ts
|
||||
import { getConfigDb, parseConfigRow } from '~/server/utils/configDb';
|
||||
|
||||
export default defineEventHandler(() => {
|
||||
const db = getConfigDb();
|
||||
const rows = db.prepare('SELECT * FROM config_klinik_ruang ORDER BY id ASC').all();
|
||||
return { success: true, data: rows.map(parseConfigRow) };
|
||||
});
|
||||
@@ -0,0 +1,19 @@
|
||||
// server/api/config/klinik-ruang.post.ts
|
||||
import { getConfigDb, parseConfigRow } from '~/server/utils/configDb';
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const body = await readBody(event);
|
||||
const { kodeKlinik, namaKlinik, ruangList = [] } = body;
|
||||
|
||||
if (!kodeKlinik || !namaKlinik) {
|
||||
throw createError({ statusCode: 400, message: 'kodeKlinik dan namaKlinik wajib diisi' });
|
||||
}
|
||||
|
||||
const db = getConfigDb();
|
||||
const result = db.prepare(
|
||||
`INSERT INTO config_klinik_ruang (kodeKlinik, namaKlinik, ruangList) VALUES (?, ?, ?)`
|
||||
).run(kodeKlinik, namaKlinik, JSON.stringify(ruangList));
|
||||
|
||||
const newRow = db.prepare('SELECT * FROM config_klinik_ruang WHERE id = ?').get(result.lastInsertRowid);
|
||||
return { success: true, data: parseConfigRow(newRow), message: `Klinik Ruang ${namaKlinik} berhasil ditambahkan` };
|
||||
});
|
||||
@@ -0,0 +1,14 @@
|
||||
// server/api/config/klinik-ruang/[id].delete.ts
|
||||
import { getConfigDb } from '~/server/utils/configDb';
|
||||
|
||||
export default defineEventHandler((event) => {
|
||||
const id = Number(getRouterParam(event, 'id'));
|
||||
if (!id) throw createError({ statusCode: 400, message: 'ID tidak valid' });
|
||||
|
||||
const db = getConfigDb();
|
||||
const existing = db.prepare('SELECT * FROM config_klinik_ruang WHERE id = ?').get(id) as any;
|
||||
if (!existing) throw createError({ statusCode: 404, message: 'Klinik Ruang tidak ditemukan' });
|
||||
|
||||
db.prepare('DELETE FROM config_klinik_ruang WHERE id = ?').run(id);
|
||||
return { success: true, message: `Klinik Ruang ${existing.namaKlinik} berhasil dihapus` };
|
||||
});
|
||||
@@ -0,0 +1,29 @@
|
||||
// server/api/config/klinik-ruang/[id].put.ts
|
||||
import { getConfigDb, parseConfigRow } from '~/server/utils/configDb';
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const id = Number(getRouterParam(event, 'id'));
|
||||
if (!id) throw createError({ statusCode: 400, message: 'ID tidak valid' });
|
||||
|
||||
const body = await readBody(event);
|
||||
const { kodeKlinik, namaKlinik, ruangList } = body;
|
||||
|
||||
const db = getConfigDb();
|
||||
const existing = db.prepare('SELECT * FROM config_klinik_ruang WHERE id = ?').get(id) as any;
|
||||
if (!existing) throw createError({ statusCode: 404, message: 'Klinik Ruang tidak ditemukan' });
|
||||
|
||||
db.prepare(
|
||||
`UPDATE config_klinik_ruang SET
|
||||
kodeKlinik = ?, namaKlinik = ?, ruangList = ?,
|
||||
updatedAt = strftime('%s', 'now')
|
||||
WHERE id = ?`
|
||||
).run(
|
||||
kodeKlinik ?? existing.kodeKlinik,
|
||||
namaKlinik ?? existing.namaKlinik,
|
||||
ruangList !== undefined ? JSON.stringify(ruangList) : existing.ruangList,
|
||||
id
|
||||
);
|
||||
|
||||
const updated = db.prepare('SELECT * FROM config_klinik_ruang WHERE id = ?').get(id);
|
||||
return { success: true, data: parseConfigRow(updated), message: 'Klinik Ruang berhasil diperbarui' };
|
||||
});
|
||||
@@ -0,0 +1,8 @@
|
||||
// server/api/config/screen.get.ts
|
||||
import { getConfigDb, parseConfigRow } from '~/server/utils/configDb';
|
||||
|
||||
export default defineEventHandler(() => {
|
||||
const db = getConfigDb();
|
||||
const rows = db.prepare('SELECT * FROM config_screen ORDER BY id ASC').all();
|
||||
return { success: true, data: rows.map(parseConfigRow) };
|
||||
});
|
||||
@@ -0,0 +1,26 @@
|
||||
// server/api/config/screen.post.ts
|
||||
import { getConfigDb, parseConfigRow } from '~/server/utils/configDb';
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const body = await readBody(event);
|
||||
const { namaScreen, nomorScreen, klinik = [] } = body;
|
||||
|
||||
if (!namaScreen || !nomorScreen) {
|
||||
throw createError({ statusCode: 400, message: 'namaScreen dan nomorScreen wajib diisi' });
|
||||
}
|
||||
|
||||
const db = getConfigDb();
|
||||
try {
|
||||
const result = db.prepare(
|
||||
`INSERT INTO config_screen (namaScreen, nomorScreen, klinik) VALUES (?, ?, ?)`
|
||||
).run(namaScreen, nomorScreen, JSON.stringify(klinik));
|
||||
|
||||
const newRow = db.prepare('SELECT * FROM config_screen WHERE id = ?').get(result.lastInsertRowid);
|
||||
return { success: true, data: parseConfigRow(newRow), message: `Screen ${namaScreen} berhasil ditambahkan` };
|
||||
} catch (e: any) {
|
||||
if (e.message?.includes('UNIQUE')) {
|
||||
throw createError({ statusCode: 409, message: `nomorScreen "${nomorScreen}" sudah digunakan` });
|
||||
}
|
||||
throw createError({ statusCode: 500, message: e.message });
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,14 @@
|
||||
// server/api/config/screen/[id].delete.ts
|
||||
import { getConfigDb } from '~/server/utils/configDb';
|
||||
|
||||
export default defineEventHandler((event) => {
|
||||
const id = Number(getRouterParam(event, 'id'));
|
||||
if (!id) throw createError({ statusCode: 400, message: 'ID tidak valid' });
|
||||
|
||||
const db = getConfigDb();
|
||||
const existing = db.prepare('SELECT * FROM config_screen WHERE id = ?').get(id) as any;
|
||||
if (!existing) throw createError({ statusCode: 404, message: 'Screen tidak ditemukan' });
|
||||
|
||||
db.prepare('DELETE FROM config_screen WHERE id = ?').run(id);
|
||||
return { success: true, message: `Screen ${existing.namaScreen} berhasil dihapus` };
|
||||
});
|
||||
@@ -0,0 +1,31 @@
|
||||
// server/api/config/screen/[id].put.ts
|
||||
import { getConfigDb, parseConfigRow } from '~/server/utils/configDb';
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const id = Number(getRouterParam(event, 'id'));
|
||||
if (!id) throw createError({ statusCode: 400, message: 'ID tidak valid' });
|
||||
|
||||
const body = await readBody(event);
|
||||
const { namaScreen, nomorScreen, klinik } = body;
|
||||
|
||||
const db = getConfigDb();
|
||||
const existing = db.prepare('SELECT * FROM config_screen WHERE id = ?').get(id);
|
||||
if (!existing) throw createError({ statusCode: 404, message: 'Screen tidak ditemukan' });
|
||||
|
||||
db.prepare(
|
||||
`UPDATE config_screen SET
|
||||
namaScreen = ?,
|
||||
nomorScreen = ?,
|
||||
klinik = ?,
|
||||
updatedAt = strftime('%s', 'now')
|
||||
WHERE id = ?`
|
||||
).run(
|
||||
namaScreen ?? (existing as any).namaScreen,
|
||||
nomorScreen ?? (existing as any).nomorScreen,
|
||||
klinik !== undefined ? JSON.stringify(klinik) : (existing as any).klinik,
|
||||
id
|
||||
);
|
||||
|
||||
const updated = db.prepare('SELECT * FROM config_screen WHERE id = ?').get(id);
|
||||
return { success: true, data: parseConfigRow(updated), message: 'Screen berhasil diperbarui' };
|
||||
});
|
||||
@@ -16,12 +16,13 @@ export default defineEventHandler(async (event) => {
|
||||
const urlObj = new URL(targetBase)
|
||||
headers.host = urlObj.host
|
||||
} catch (e) {
|
||||
headers.host = '10.10.123.140:8089'
|
||||
headers.host = config.proxyTargetHostKlinikFallback || '10.10.123.140:8089'
|
||||
}
|
||||
|
||||
// Spoof the origin and referer to match the server's trusted/whitelisted client origin
|
||||
headers.origin = 'http://10.10.150.175:3000'
|
||||
headers.referer = 'http://10.10.150.175:3000/'
|
||||
const proxyOrigin = config.proxyClientOrigin || 'http://10.10.150.175:3000'
|
||||
headers.origin = proxyOrigin
|
||||
headers.referer = proxyOrigin.endsWith('/') ? proxyOrigin : `${proxyOrigin}/`
|
||||
|
||||
console.log(`[Proxy Klinik-API] Forwarding ${event.node.req.method} to: ${targetUrl}`)
|
||||
console.log(`[Proxy Klinik-API] Headers - Host: ${headers.host}, Origin: ${headers.origin}`)
|
||||
|
||||
@@ -16,12 +16,13 @@ export default defineEventHandler(async (event) => {
|
||||
const urlObj = new URL(targetBase)
|
||||
headers.host = urlObj.host
|
||||
} catch (e) {
|
||||
headers.host = '10.10.123.135:8084'
|
||||
headers.host = config.proxyTargetHostFallback || '10.10.123.135:8084'
|
||||
}
|
||||
|
||||
// Spoof the origin and referer to match the server's trusted/whitelisted client origin
|
||||
headers.origin = 'http://10.10.150.175:3000'
|
||||
headers.referer = 'http://10.10.150.175:3000/'
|
||||
const proxyOrigin = config.proxyClientOrigin || 'http://10.10.150.175:3000'
|
||||
headers.origin = proxyOrigin
|
||||
headers.referer = proxyOrigin.endsWith('/') ? proxyOrigin : `${proxyOrigin}/`
|
||||
|
||||
console.log(`[Proxy Stats-API] Forwarding ${event.node.req.method} to: ${targetUrl}`)
|
||||
console.log(`[Proxy Stats-API] Headers - Host: ${headers.host}, Origin: ${headers.origin}`)
|
||||
|
||||
@@ -16,12 +16,13 @@ export default defineEventHandler(async (event) => {
|
||||
const urlObj = new URL(targetBase)
|
||||
headers.host = urlObj.host
|
||||
} catch (e) {
|
||||
headers.host = '10.10.123.135:8084'
|
||||
headers.host = config.proxyTargetHostFallback || '10.10.123.135:8084'
|
||||
}
|
||||
|
||||
// Spoof the origin and referer to match the server's trusted/whitelisted client origin
|
||||
headers.origin = 'http://10.10.150.175:3000'
|
||||
headers.referer = 'http://10.10.150.175:3000/'
|
||||
const proxyOrigin = config.proxyClientOrigin || 'http://10.10.150.175:3000'
|
||||
headers.origin = proxyOrigin
|
||||
headers.referer = proxyOrigin.endsWith('/') ? proxyOrigin : `${proxyOrigin}/`
|
||||
|
||||
console.log(`[Proxy Visit-API] Forwarding ${event.node.req.method} to: ${targetUrl}`)
|
||||
console.log(`[Proxy Visit-API] Headers - Host: ${headers.host}, Origin: ${headers.origin}`)
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
// server/utils/configDb.ts
|
||||
// Shared SQLite utility for device configuration (screens, anjungan, klinik-ruang, etc.)
|
||||
// Designed to be a temporary layer - can be swapped to an external API with minimal changes.
|
||||
|
||||
import Database from 'better-sqlite3';
|
||||
import { join } from 'path';
|
||||
import { existsSync, mkdirSync } from 'fs';
|
||||
|
||||
const getDbPath = () => {
|
||||
const dbDir = join(process.cwd(), 'data');
|
||||
if (!existsSync(dbDir)) mkdirSync(dbDir, { recursive: true });
|
||||
return join(dbDir, 'users.db'); // Reuse same DB file
|
||||
};
|
||||
|
||||
// ─── Default Seed Data ───────────────────────────────────────────────────────
|
||||
// Used when tables are empty on first run. Mirrors the hardcoded defaults in Pinia stores.
|
||||
|
||||
const SEED_SCREENS = [
|
||||
{ namaScreen: 'Layar Screen 1', nomorScreen: 'SCR-001', klinik: JSON.stringify(['AN', 'AS', 'BD', 'GI', 'GR', 'GZ', 'IP', 'JT']) },
|
||||
{ namaScreen: 'Layar Screen 2', nomorScreen: 'SCR-002', klinik: JSON.stringify(['JW', 'KK', 'MT', 'SR', 'OB', 'PR']) },
|
||||
{ namaScreen: 'Layar Screen 3', nomorScreen: 'SCR-003', klinik: JSON.stringify(['RT', 'RM', 'HO']) },
|
||||
];
|
||||
|
||||
const SEED_ANJUNGAN = [
|
||||
{ namaAnjungan: 'Anjungan Reguler', jenisPasien: 'Reguler', klinik: JSON.stringify(['AK', 'AN', 'BD', 'GR', 'GM', 'GZ', 'HO', 'IP', 'JT', 'JW', 'KD', 'KK', 'KM', 'KO', 'MC', 'MT', 'ON', 'PR', 'RD', 'RM', 'RT', 'SR', 'TH']) },
|
||||
{ namaAnjungan: 'Anjungan Eksekutif', jenisPasien: 'Eksekutif', klinik: JSON.stringify(['AK', 'AN', 'BD', 'GM', 'GR', 'GZ', 'HO', 'IP', 'JT', 'JW', 'KD', 'KK', 'KM', 'KO', 'MC', 'MT', 'ON', 'PR', 'RD', 'RM', 'RT', 'SR', 'TH']) },
|
||||
];
|
||||
|
||||
const SEED_ANTREAN_MASUK_SCREENS = [
|
||||
{ namaScreen: 'Layar Antrean Masuk 1', nomorScreen: 'AM-001', loket: JSON.stringify([1, 2, 12, 14]) },
|
||||
{ namaScreen: 'Layar Antrean Masuk 2', nomorScreen: 'AM-002', loket: JSON.stringify([3, 4]) },
|
||||
];
|
||||
|
||||
const SEED_KLINIK_RUANG = [
|
||||
{ kodeKlinik: 'AN', namaKlinik: 'ANAK', ruangList: JSON.stringify([{ nomorRuang: '1', namaRuang: 'R. TINDAKAN', nomorScreen: '101' }]) },
|
||||
{ kodeKlinik: 'AS', namaKlinik: 'ANESTESI', ruangList: JSON.stringify([{ nomorRuang: '1', namaRuang: 'Ruang 1', nomorScreen: '201' }, { nomorRuang: '2', namaRuang: 'Ruang 2', nomorScreen: '202' }, { nomorRuang: '3', namaRuang: 'Ruang 3', nomorScreen: '203' }]) },
|
||||
{ kodeKlinik: 'BD', namaKlinik: 'BEDAH', ruangList: JSON.stringify([{ nomorRuang: '1', namaRuang: 'Ruang Konsultasi', nomorScreen: '301' }]) },
|
||||
{ kodeKlinik: 'GR', namaKlinik: 'GERIATRI', ruangList: JSON.stringify([{ nomorRuang: '1', namaRuang: 'Ruang Pemeriksaan', nomorScreen: '401' }]) },
|
||||
{ kodeKlinik: 'GI', namaKlinik: 'GIGI DAN MULUT', ruangList: JSON.stringify([{ nomorRuang: '1', namaRuang: 'Ruang 1', nomorScreen: '501' }, { nomorRuang: '2', namaRuang: 'Ruang 2', nomorScreen: '502' }, { nomorRuang: '3', namaRuang: 'Ruang 3', nomorScreen: '503' }]) },
|
||||
];
|
||||
|
||||
// ─── DB Initialization ────────────────────────────────────────────────────────
|
||||
|
||||
let _db: InstanceType<typeof Database> | null = null;
|
||||
|
||||
export const getConfigDb = () => {
|
||||
if (_db) return _db;
|
||||
|
||||
const dbPath = getDbPath();
|
||||
const db = new Database(dbPath);
|
||||
db.pragma('journal_mode = WAL'); // Better concurrent performance
|
||||
|
||||
db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS config_screen (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
namaScreen TEXT NOT NULL,
|
||||
nomorScreen TEXT UNIQUE NOT NULL,
|
||||
klinik TEXT DEFAULT '[]',
|
||||
createdAt INTEGER DEFAULT (strftime('%s', 'now')),
|
||||
updatedAt INTEGER DEFAULT (strftime('%s', 'now'))
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS config_anjungan (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
namaAnjungan TEXT NOT NULL,
|
||||
jenisPasien TEXT NOT NULL,
|
||||
klinik TEXT DEFAULT '[]',
|
||||
createdAt INTEGER DEFAULT (strftime('%s', 'now')),
|
||||
updatedAt INTEGER DEFAULT (strftime('%s', 'now'))
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS config_antrean_masuk_screen (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
namaScreen TEXT NOT NULL,
|
||||
nomorScreen TEXT UNIQUE NOT NULL,
|
||||
loket TEXT DEFAULT '[]',
|
||||
createdAt INTEGER DEFAULT (strftime('%s', 'now')),
|
||||
updatedAt INTEGER DEFAULT (strftime('%s', 'now'))
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS config_klinik_ruang (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
kodeKlinik TEXT NOT NULL,
|
||||
namaKlinik TEXT NOT NULL,
|
||||
ruangList TEXT DEFAULT '[]',
|
||||
createdAt INTEGER DEFAULT (strftime('%s', 'now')),
|
||||
updatedAt INTEGER DEFAULT (strftime('%s', 'now'))
|
||||
);
|
||||
`);
|
||||
|
||||
// Seed defaults if tables are empty
|
||||
_seedIfEmpty(db, 'config_screen', SEED_SCREENS);
|
||||
_seedIfEmpty(db, 'config_anjungan', SEED_ANJUNGAN);
|
||||
_seedIfEmpty(db, 'config_antrean_masuk_screen', SEED_ANTREAN_MASUK_SCREENS);
|
||||
_seedIfEmpty(db, 'config_klinik_ruang', SEED_KLINIK_RUANG);
|
||||
|
||||
console.log('✅ [configDb] Config database initialized');
|
||||
_db = db;
|
||||
return db;
|
||||
};
|
||||
|
||||
function _seedIfEmpty(db: InstanceType<typeof Database>, table: string, rows: Record<string, any>[]) {
|
||||
const count = (db.prepare(`SELECT COUNT(*) as c FROM ${table}`).get() as any).c;
|
||||
if (count === 0) {
|
||||
console.log(`🌱 [configDb] Seeding ${table} with ${rows.length} default rows`);
|
||||
const keys = Object.keys(rows[0]);
|
||||
const placeholders = keys.map(() => '?').join(', ');
|
||||
const stmt = db.prepare(`INSERT INTO ${table} (${keys.join(', ')}) VALUES (${placeholders})`);
|
||||
rows.forEach(row => stmt.run(...keys.map(k => row[k])));
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Helper: Parse JSON columns ───────────────────────────────────────────────
|
||||
export const parseConfigRow = (row: any) => {
|
||||
if (!row) return null;
|
||||
const parsed = { ...row };
|
||||
// Auto-parse any column that looks like a JSON array/object
|
||||
for (const key of Object.keys(parsed)) {
|
||||
if (typeof parsed[key] === 'string' && (parsed[key].startsWith('[') || parsed[key].startsWith('{'))) {
|
||||
try { parsed[key] = JSON.parse(parsed[key]); } catch {}
|
||||
}
|
||||
}
|
||||
return parsed;
|
||||
};
|
||||
+48
-60
@@ -1,23 +1,12 @@
|
||||
// stores/anjunganStore.js
|
||||
// Konfigurasi anjungan — data disimpan di server SQLite via /api/config/anjungan
|
||||
import { defineStore } from 'pinia';
|
||||
import { ref, computed } from 'vue';
|
||||
|
||||
export const useAnjunganStore = defineStore('anjungan', () => {
|
||||
// Seed: existing static Anjungan page sebagai id 1
|
||||
const anjunganItems = ref([
|
||||
{
|
||||
id: 1,
|
||||
namaAnjungan: 'Anjungan Reguler',
|
||||
jenisPasien: 'Reguler',
|
||||
klinik: ['AK', 'AN', 'BD', 'GR', 'GM', 'GZ', 'HO', 'IP', 'JT', 'JW', 'KD', 'KK', 'KM', 'KO', 'MC', 'MT', 'ON', 'PR', 'RD', 'RM', 'RT', 'SR', 'TH'],
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
namaAnjungan: 'Anjungan Eksekutif',
|
||||
jenisPasien: 'Eksekutif',
|
||||
klinik: ['AK', 'AN', 'BD', 'GM', 'GR', 'GZ', 'HO', 'IP', 'JT', 'JW', 'KD', 'KK', 'KM', 'KO', 'MC', 'MT', 'ON', 'PR', 'RD', 'RM', 'RT', 'SR', 'TH'],
|
||||
},
|
||||
]);
|
||||
const anjunganItems = ref([]);
|
||||
const isLoading = ref(false);
|
||||
const error = ref(null);
|
||||
|
||||
const getAllAnjungan = computed(() => anjunganItems.value);
|
||||
|
||||
@@ -28,66 +17,65 @@ export const useAnjunganStore = defineStore('anjungan', () => {
|
||||
});
|
||||
};
|
||||
|
||||
const addAnjungan = (payload) => {
|
||||
const maxId =
|
||||
anjunganItems.value.length > 0
|
||||
? Math.max(...anjunganItems.value.map((a) => Number(a.id) || 0))
|
||||
: 0;
|
||||
const newId = maxId + 1;
|
||||
const newItem = {
|
||||
...payload,
|
||||
id: newId,
|
||||
};
|
||||
anjunganItems.value.push(newItem);
|
||||
return {
|
||||
success: true,
|
||||
message: `Anjungan ${newItem.namaAnjungan} berhasil ditambahkan`,
|
||||
data: newItem,
|
||||
};
|
||||
// ── Fetch from API ─────────────────────────────────────────────────────────
|
||||
const fetchAnjungan = async () => {
|
||||
if (isLoading.value) return;
|
||||
isLoading.value = true;
|
||||
error.value = null;
|
||||
try {
|
||||
const res = await $fetch('/api/config/anjungan');
|
||||
anjunganItems.value = res.data || [];
|
||||
} catch (e) {
|
||||
error.value = e.message;
|
||||
console.error('❌ [anjunganStore] Gagal fetch anjungan config:', e);
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const updateAnjungan = (payload) => {
|
||||
const idx = anjunganItems.value.findIndex(
|
||||
(a) => Number(a.id) === Number(payload.id)
|
||||
);
|
||||
if (idx === -1) {
|
||||
return { success: false, message: 'Anjungan tidak ditemukan' };
|
||||
// ── CRUD via API ───────────────────────────────────────────────────────────
|
||||
const addAnjungan = async (payload) => {
|
||||
try {
|
||||
const res = await $fetch('/api/config/anjungan', { method: 'POST', body: payload });
|
||||
anjunganItems.value.push(res.data);
|
||||
return { success: true, message: res.message, data: res.data };
|
||||
} catch (e) {
|
||||
return { success: false, message: e.data?.message || e.message };
|
||||
}
|
||||
anjunganItems.value[idx] = {
|
||||
...anjunganItems.value[idx],
|
||||
...payload,
|
||||
};
|
||||
return {
|
||||
success: true,
|
||||
message: `Anjungan ${payload.namaAnjungan} berhasil diperbarui`,
|
||||
};
|
||||
};
|
||||
|
||||
const deleteAnjungan = (id) => {
|
||||
const idx = anjunganItems.value.findIndex(
|
||||
(a) => Number(a.id) === Number(id)
|
||||
);
|
||||
if (idx === -1) {
|
||||
return { success: false, message: 'Anjungan tidak ditemukan' };
|
||||
const updateAnjungan = async (payload) => {
|
||||
try {
|
||||
const res = await $fetch(`/api/config/anjungan/${payload.id}`, { method: 'PUT', body: payload });
|
||||
const idx = anjunganItems.value.findIndex((a) => Number(a.id) === Number(payload.id));
|
||||
if (idx !== -1) anjunganItems.value[idx] = res.data;
|
||||
return { success: true, message: res.message };
|
||||
} catch (e) {
|
||||
return { success: false, message: e.data?.message || e.message };
|
||||
}
|
||||
};
|
||||
|
||||
const deleteAnjungan = async (id) => {
|
||||
try {
|
||||
const res = await $fetch(`/api/config/anjungan/${id}`, { method: 'DELETE' });
|
||||
anjunganItems.value = anjunganItems.value.filter((a) => Number(a.id) !== Number(id));
|
||||
return { success: true, message: res.message };
|
||||
} catch (e) {
|
||||
return { success: false, message: e.data?.message || e.message };
|
||||
}
|
||||
const name = anjunganItems.value[idx].namaAnjungan;
|
||||
anjunganItems.value.splice(idx, 1);
|
||||
return { success: true, message: `Anjungan ${name} berhasil dihapus` };
|
||||
};
|
||||
|
||||
return {
|
||||
anjunganItems,
|
||||
isLoading,
|
||||
error,
|
||||
getAllAnjungan,
|
||||
getAnjunganById,
|
||||
fetchAnjungan,
|
||||
addAnjungan,
|
||||
updateAnjungan,
|
||||
deleteAnjungan,
|
||||
};
|
||||
}, {
|
||||
persist: {
|
||||
key: 'anjungan-store-state',
|
||||
storage: typeof window !== 'undefined' ? localStorage : undefined,
|
||||
paths: ['anjunganItems'],
|
||||
},
|
||||
persist: false,
|
||||
});
|
||||
|
||||
@@ -1,25 +1,15 @@
|
||||
// stores/antreanMasukScreenStore.js
|
||||
// Konfigurasi layar antrean masuk — data disimpan di server SQLite via /api/config/antrean-masuk-screen
|
||||
import { defineStore } from 'pinia';
|
||||
import { ref, computed } from 'vue';
|
||||
|
||||
export const useAntreanMasukScreenStore = defineStore('antreanMasukScreen', () => {
|
||||
// Initial antrean masuk screen items data
|
||||
const antreanMasukScreenItems = ref([
|
||||
{
|
||||
id: 1,
|
||||
namaScreen: "Layar Antrean Masuk 1",
|
||||
nomorScreen: "AM-001",
|
||||
loket: [1, 2, 12, 14], // Array of loket IDs
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
namaScreen: "Layar Antrean Masuk 2",
|
||||
nomorScreen: "AM-002",
|
||||
loket: [3, 4],
|
||||
},
|
||||
]);
|
||||
const antreanMasukScreenItems = ref([]);
|
||||
const isLoading = ref(false);
|
||||
const error = ref(null);
|
||||
|
||||
const getAllAntreanMasukScreens = computed(() => antreanMasukScreenItems.value);
|
||||
|
||||
// Computed
|
||||
const getAntreanMasukScreenById = (id) => {
|
||||
return computed(() => {
|
||||
const targetId = Number(id);
|
||||
@@ -27,63 +17,65 @@ export const useAntreanMasukScreenStore = defineStore('antreanMasukScreen', () =
|
||||
});
|
||||
};
|
||||
|
||||
const getAllAntreanMasukScreens = computed(() => antreanMasukScreenItems.value);
|
||||
|
||||
// Actions
|
||||
const addAntreanMasukScreen = (screenPayload) => {
|
||||
// Ensure we get a valid ID even if antreanMasukScreenItems is empty
|
||||
const maxId = antreanMasukScreenItems.value.length > 0
|
||||
? Math.max(...antreanMasukScreenItems.value.map(s => s.id), 0)
|
||||
: 0;
|
||||
const newId = maxId + 1;
|
||||
// Pastikan id baru tidak tertimpa payload (payload.id bisa null)
|
||||
const newScreen = {
|
||||
...screenPayload,
|
||||
id: newId,
|
||||
};
|
||||
antreanMasukScreenItems.value.push(newScreen);
|
||||
return { success: true, message: `Screen ${newScreen.namaScreen} berhasil ditambahkan`, data: newScreen };
|
||||
// ── Fetch from API ─────────────────────────────────────────────────────────
|
||||
const fetchAntreanMasukScreens = async () => {
|
||||
if (isLoading.value) return;
|
||||
isLoading.value = true;
|
||||
error.value = null;
|
||||
try {
|
||||
const res = await $fetch('/api/config/antrean-masuk-screen');
|
||||
antreanMasukScreenItems.value = res.data || [];
|
||||
} catch (e) {
|
||||
error.value = e.message;
|
||||
console.error('❌ [antreanMasukScreenStore] Gagal fetch config:', e);
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const updateAntreanMasukScreen = (screenPayload) => {
|
||||
const index = antreanMasukScreenItems.value.findIndex(s => s.id === screenPayload.id);
|
||||
if (index !== -1) {
|
||||
antreanMasukScreenItems.value[index] = {
|
||||
...antreanMasukScreenItems.value[index],
|
||||
...screenPayload,
|
||||
};
|
||||
return { success: true, message: `Konfigurasi ${screenPayload.namaScreen} berhasil disimpan` };
|
||||
// ── CRUD via API ───────────────────────────────────────────────────────────
|
||||
const addAntreanMasukScreen = async (screenPayload) => {
|
||||
try {
|
||||
const res = await $fetch('/api/config/antrean-masuk-screen', { method: 'POST', body: screenPayload });
|
||||
antreanMasukScreenItems.value.push(res.data);
|
||||
return { success: true, message: res.message, data: res.data };
|
||||
} catch (e) {
|
||||
return { success: false, message: e.data?.message || e.message };
|
||||
}
|
||||
return { success: false, message: 'Screen tidak ditemukan' };
|
||||
};
|
||||
|
||||
const deleteAntreanMasukScreen = (screenId) => {
|
||||
const index = antreanMasukScreenItems.value.findIndex(s => s.id === screenId);
|
||||
if (index !== -1) {
|
||||
const screenName = antreanMasukScreenItems.value[index].namaScreen;
|
||||
antreanMasukScreenItems.value.splice(index, 1);
|
||||
return { success: true, message: `Screen ${screenName} berhasil dihapus` };
|
||||
const updateAntreanMasukScreen = async (screenPayload) => {
|
||||
try {
|
||||
const res = await $fetch(`/api/config/antrean-masuk-screen/${screenPayload.id}`, { method: 'PUT', body: screenPayload });
|
||||
const index = antreanMasukScreenItems.value.findIndex(s => s.id === screenPayload.id);
|
||||
if (index !== -1) antreanMasukScreenItems.value[index] = res.data;
|
||||
return { success: true, message: res.message };
|
||||
} catch (e) {
|
||||
return { success: false, message: e.data?.message || e.message };
|
||||
}
|
||||
};
|
||||
|
||||
const deleteAntreanMasukScreen = async (screenId) => {
|
||||
try {
|
||||
const res = await $fetch(`/api/config/antrean-masuk-screen/${screenId}`, { method: 'DELETE' });
|
||||
antreanMasukScreenItems.value = antreanMasukScreenItems.value.filter(s => s.id !== screenId);
|
||||
return { success: true, message: res.message };
|
||||
} catch (e) {
|
||||
return { success: false, message: e.data?.message || e.message };
|
||||
}
|
||||
return { success: false, message: 'Screen tidak ditemukan' };
|
||||
};
|
||||
|
||||
return {
|
||||
// State
|
||||
antreanMasukScreenItems,
|
||||
|
||||
// Computed
|
||||
isLoading,
|
||||
error,
|
||||
getAllAntreanMasukScreens,
|
||||
getAntreanMasukScreenById,
|
||||
|
||||
// Actions
|
||||
fetchAntreanMasukScreens,
|
||||
addAntreanMasukScreen,
|
||||
updateAntreanMasukScreen,
|
||||
deleteAntreanMasukScreen,
|
||||
};
|
||||
}, {
|
||||
persist: {
|
||||
key: 'antrean-masuk-screen-store-state',
|
||||
storage: typeof window !== 'undefined' ? localStorage : undefined,
|
||||
paths: ['antreanMasukScreenItems'],
|
||||
},
|
||||
persist: false,
|
||||
});
|
||||
@@ -1000,6 +1000,6 @@ export const useClinicStore = defineStore('clinic', () => {
|
||||
persist: {
|
||||
key: 'clinic-store-state',
|
||||
storage: typeof window !== 'undefined' ? localStorage : undefined,
|
||||
paths: ['clinics', 'lastSyncTimestamp'],
|
||||
paths: ['lastSyncTimestamp'],
|
||||
},
|
||||
});
|
||||
@@ -184,6 +184,6 @@ export const useDoctorStore = defineStore('doctor', () => {
|
||||
}, {
|
||||
persist: {
|
||||
key: 'doctor-store',
|
||||
pick: ['doctorsByKlinikId', 'lastSyncTimestamp']
|
||||
pick: ['lastSyncTimestamp']
|
||||
}
|
||||
});
|
||||
+95
-247
@@ -1,4 +1,5 @@
|
||||
// stores/klinikRuangStore.js
|
||||
// stores/klinikruangstore.js
|
||||
// Konfigurasi mapping klinik ke ruangan — data disimpan di server SQLite via /api/config/klinik-ruang
|
||||
import { defineStore } from 'pinia';
|
||||
import { ref, computed } from 'vue';
|
||||
import { useClinicStore } from './clinicStore';
|
||||
@@ -6,284 +7,131 @@ import { useClinicStore } from './clinicStore';
|
||||
export const useKlinikRuangStore = defineStore('klinikRuang', () => {
|
||||
const clinicStore = useClinicStore();
|
||||
|
||||
// State/Computed - List Master Klinik (disinkronkan dengan clinicStore)
|
||||
// Master klinik list (from clinicStore — in-memory, from API)
|
||||
const masterKlinikList = computed(() => {
|
||||
const baseList = typeof clinicStore.getClinicsForDropdown === 'function'
|
||||
? clinicStore.getClinicsForDropdown()
|
||||
: [];
|
||||
|
||||
return baseList.map((c) => ({
|
||||
kode: c.kode,
|
||||
nama: c.name,
|
||||
}));
|
||||
return baseList.map((c) => ({ kode: c.kode, nama: c.name }));
|
||||
});
|
||||
|
||||
// State - Klinik Ruang Data
|
||||
const klinikRuangList = 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' }
|
||||
]
|
||||
},
|
||||
]);
|
||||
const klinikRuangList = ref([]);
|
||||
const isLoading = ref(false);
|
||||
const error = ref(null);
|
||||
|
||||
// Computed
|
||||
const totalKlinikRuang = computed(() => klinikRuangList.value.length);
|
||||
const totalRuangan = computed(() =>
|
||||
klinikRuangList.value.reduce((total, k) => total + (k.ruangList?.length || 0), 0)
|
||||
);
|
||||
|
||||
const totalRuangan = computed(() => {
|
||||
return klinikRuangList.value.reduce((total, klinik) => {
|
||||
return total + klinik.ruangList.length;
|
||||
}, 0);
|
||||
});
|
||||
|
||||
// Get klinik by code
|
||||
const getKlinikByCode = (kode) => {
|
||||
return klinikRuangList.value.find(k => k.kodeKlinik === kode);
|
||||
};
|
||||
|
||||
// Get all ruang for a specific klinik
|
||||
const getKlinikByCode = (kode) => klinikRuangList.value.find(k => k.kodeKlinik === kode);
|
||||
const getRuangByKlinik = (kodeKlinik) => {
|
||||
const klinik = klinikRuangList.value.find(k => k.kodeKlinik === kodeKlinik);
|
||||
return klinik ? klinik.ruangList : [];
|
||||
};
|
||||
|
||||
// Actions - CRUD Operations
|
||||
|
||||
// Create new Klinik Ruang
|
||||
const createKlinikRuang = (data) => {
|
||||
const newId = Math.max(...klinikRuangList.value.map(r => r.id), 0) + 1;
|
||||
const newNo = klinikRuangList.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
|
||||
};
|
||||
|
||||
klinikRuangList.value.push(newKlinikRuang);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: `Klinik Ruang ${data.namaKlinik} berhasil ditambahkan`,
|
||||
data: newKlinikRuang
|
||||
};
|
||||
};
|
||||
|
||||
// Update existing Klinik Ruang
|
||||
const updateKlinikRuang = (id, data) => {
|
||||
const index = klinikRuangList.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(', ');
|
||||
|
||||
klinikRuangList.value[index] = {
|
||||
...klinikRuangList.value[index],
|
||||
kodeKlinik: data.kodeKlinik,
|
||||
namaKlinik: data.namaKlinik,
|
||||
namaRuang: namaRuangDisplay,
|
||||
ruangList: data.ruangList
|
||||
};
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: `Klinik Ruang ${data.namaKlinik} berhasil diupdate`,
|
||||
data: klinikRuangList.value[index]
|
||||
};
|
||||
};
|
||||
|
||||
// Delete Klinik Ruang
|
||||
const deleteKlinikRuang = (id) => {
|
||||
const index = klinikRuangList.value.findIndex(r => r.id === id);
|
||||
|
||||
if (index === -1) {
|
||||
return {
|
||||
success: false,
|
||||
message: 'Klinik Ruang tidak ditemukan'
|
||||
};
|
||||
}
|
||||
|
||||
const deletedKlinik = klinikRuangList.value[index];
|
||||
klinikRuangList.value.splice(index, 1);
|
||||
|
||||
// Reorder numbers
|
||||
klinikRuangList.value.forEach((r, idx) => {
|
||||
r.no = idx + 1;
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: `Klinik Ruang ${deletedKlinik.namaKlinik} berhasil dihapus`
|
||||
};
|
||||
};
|
||||
|
||||
// Add ruang to existing klinik
|
||||
const addRuangToKlinik = (klinikId, ruangData) => {
|
||||
const klinik = klinikRuangList.value.find(k => k.id === klinikId);
|
||||
|
||||
if (!klinik) {
|
||||
return {
|
||||
success: false,
|
||||
message: 'Klinik tidak ditemukan'
|
||||
};
|
||||
}
|
||||
|
||||
klinik.ruangList.push(ruangData);
|
||||
|
||||
// Update display name
|
||||
klinik.namaRuang = klinik.ruangList
|
||||
.map(r => r.namaRuang)
|
||||
.filter(n => n)
|
||||
.join(', ');
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: `Ruang ${ruangData.namaRuang} berhasil ditambahkan`,
|
||||
data: klinik
|
||||
};
|
||||
};
|
||||
|
||||
// Remove ruang from klinik
|
||||
const removeRuangFromKlinik = (klinikId, ruangIndex) => {
|
||||
const klinik = klinikRuangList.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
|
||||
};
|
||||
};
|
||||
|
||||
// Search klinik ruang
|
||||
const searchKlinikRuang = (searchTerm) => {
|
||||
if (!searchTerm) return klinikRuangList.value;
|
||||
|
||||
const term = searchTerm.toLowerCase();
|
||||
return klinikRuangList.value.filter(k =>
|
||||
k.kodeKlinik.toLowerCase().includes(term) ||
|
||||
k.namaKlinik.toLowerCase().includes(term) ||
|
||||
k.namaRuang.toLowerCase().includes(term)
|
||||
return klinikRuangList.value.filter(k =>
|
||||
k.kodeKlinik?.toLowerCase().includes(term) ||
|
||||
k.namaKlinik?.toLowerCase().includes(term)
|
||||
);
|
||||
};
|
||||
|
||||
// ── Fetch from API ─────────────────────────────────────────────────────────
|
||||
const fetchKlinikRuang = async () => {
|
||||
if (isLoading.value) return;
|
||||
isLoading.value = true;
|
||||
error.value = null;
|
||||
try {
|
||||
const res = await $fetch('/api/config/klinik-ruang');
|
||||
klinikRuangList.value = (res.data || []).map((item, idx) => ({
|
||||
...item,
|
||||
no: idx + 1,
|
||||
namaRuang: (item.ruangList || []).map(r => r.namaRuang).filter(Boolean).join(', '),
|
||||
}));
|
||||
} catch (e) {
|
||||
error.value = e.message;
|
||||
console.error('❌ [klinikRuangStore] Gagal fetch config:', e);
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// ── CRUD via API ───────────────────────────────────────────────────────────
|
||||
const createKlinikRuang = async (data) => {
|
||||
try {
|
||||
const res = await $fetch('/api/config/klinik-ruang', { method: 'POST', body: data });
|
||||
const newItem = { ...res.data, no: klinikRuangList.value.length + 1, namaRuang: (res.data.ruangList || []).map(r => r.namaRuang).join(', ') };
|
||||
klinikRuangList.value.push(newItem);
|
||||
return { success: true, message: res.message, data: newItem };
|
||||
} catch (e) {
|
||||
return { success: false, message: e.data?.message || e.message };
|
||||
}
|
||||
};
|
||||
|
||||
const updateKlinikRuang = async (id, data) => {
|
||||
try {
|
||||
const res = await $fetch(`/api/config/klinik-ruang/${id}`, { method: 'PUT', body: data });
|
||||
const index = klinikRuangList.value.findIndex(r => r.id === id);
|
||||
if (index !== -1) {
|
||||
klinikRuangList.value[index] = {
|
||||
...res.data,
|
||||
no: klinikRuangList.value[index].no,
|
||||
namaRuang: (res.data.ruangList || []).map(r => r.namaRuang).join(', '),
|
||||
};
|
||||
}
|
||||
return { success: true, message: res.message, data: res.data };
|
||||
} catch (e) {
|
||||
return { success: false, message: e.data?.message || e.message };
|
||||
}
|
||||
};
|
||||
|
||||
const deleteKlinikRuang = async (id) => {
|
||||
try {
|
||||
const res = await $fetch(`/api/config/klinik-ruang/${id}`, { method: 'DELETE' });
|
||||
klinikRuangList.value = klinikRuangList.value.filter(r => r.id !== id);
|
||||
// Reorder 'no'
|
||||
klinikRuangList.value.forEach((r, idx) => { r.no = idx + 1; });
|
||||
return { success: true, message: res.message };
|
||||
} catch (e) {
|
||||
return { success: false, message: e.data?.message || e.message };
|
||||
}
|
||||
};
|
||||
|
||||
const addRuangToKlinik = async (klinikId, ruangData) => {
|
||||
const klinik = klinikRuangList.value.find(k => k.id === klinikId);
|
||||
if (!klinik) return { success: false, message: 'Klinik tidak ditemukan' };
|
||||
const newRuangList = [...(klinik.ruangList || []), ruangData];
|
||||
return updateKlinikRuang(klinikId, { ...klinik, ruangList: newRuangList });
|
||||
};
|
||||
|
||||
const removeRuangFromKlinik = async (klinikId, ruangIndex) => {
|
||||
const klinik = klinikRuangList.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 newRuangList = klinik.ruangList.filter((_, i) => i !== ruangIndex);
|
||||
return updateKlinikRuang(klinikId, { ...klinik, ruangList: newRuangList });
|
||||
};
|
||||
|
||||
return {
|
||||
// State
|
||||
masterKlinikList,
|
||||
klinikRuangList,
|
||||
|
||||
// Computed
|
||||
isLoading,
|
||||
error,
|
||||
totalKlinikRuang,
|
||||
totalRuangan,
|
||||
|
||||
// Getters
|
||||
getKlinikByCode,
|
||||
getRuangByKlinik,
|
||||
|
||||
// Actions
|
||||
searchKlinikRuang,
|
||||
fetchKlinikRuang,
|
||||
createKlinikRuang,
|
||||
updateKlinikRuang,
|
||||
deleteKlinikRuang,
|
||||
addRuangToKlinik,
|
||||
removeRuangFromKlinik,
|
||||
searchKlinikRuang,
|
||||
};
|
||||
}, {
|
||||
persist: {
|
||||
key: 'klinikruang-store-state',
|
||||
storage: typeof window !== 'undefined' ? localStorage : undefined,
|
||||
paths: ['klinikRuangList'],
|
||||
},
|
||||
persist: false,
|
||||
});
|
||||
+62
-2
@@ -266,6 +266,8 @@ export const useQueueStore = defineStore('queue', () => {
|
||||
allPatients.value.push(...newPatientMap.values());
|
||||
|
||||
// console.log(`✅ [queueStore] Successfully fetched ${mappedClinicPatients.length} patients for clinic ${kodeKlinik}`);
|
||||
// SYNC COUNTERS WITH API DATA SO NEW TICKETS DONT RESET
|
||||
syncCountersWithState();
|
||||
return { success: true, message: `${mappedClinicPatients.length} pasien dimuat` };
|
||||
|
||||
} catch (error) {
|
||||
@@ -420,6 +422,8 @@ export const useQueueStore = defineStore('queue', () => {
|
||||
url: wsBaseUrl,
|
||||
clientId: wsClientId,
|
||||
fallbackPostUrl: '/stats-api/ws',
|
||||
reconnectInterval: 5000, // 5 seconds between reconnect attempts
|
||||
maxReconnectAttempts: 9999, // Effectively infinite — never give up on remote machines
|
||||
onOpen: () => {
|
||||
console.log('✅ [queueStore] WebSocket connected');
|
||||
isWsConnected.value = true;
|
||||
@@ -435,12 +439,60 @@ export const useQueueStore = defineStore('queue', () => {
|
||||
onMessage: onWsMessage
|
||||
});
|
||||
|
||||
// ============================================
|
||||
// STORE-LEVEL AUTO-POLLING (cross-device sync fallback)
|
||||
// ============================================
|
||||
// Runs on every browser instance every 30 seconds.
|
||||
// Fetches data based on whatever active interests are registered
|
||||
// (lokets, clinics, or global). This ensures any device always
|
||||
// has fresh data regardless of WS delivery reliability.
|
||||
let _autoSyncInterval = null;
|
||||
|
||||
const startAutoSync = () => {
|
||||
// Guard: only run on client, and only start once
|
||||
if (typeof window === 'undefined') return;
|
||||
if (_autoSyncInterval) return; // Already running
|
||||
|
||||
console.log('🔄 [queueStore] Starting store-level auto-sync (30s interval)');
|
||||
|
||||
_autoSyncInterval = setInterval(async () => {
|
||||
const hasLoketInterest = Object.keys(activeLoketInterest.value).length > 0;
|
||||
const hasClinicInterest = Object.keys(activeClinicInterest.value).length > 0;
|
||||
const hasGlobalInterest = globalInterestCount.value > 0;
|
||||
|
||||
if (hasGlobalInterest) {
|
||||
fetchAllPatients();
|
||||
} else {
|
||||
if (hasLoketInterest) {
|
||||
Object.keys(activeLoketInterest.value).forEach(loketId => {
|
||||
fetchPatientsForLoket(loketId, true);
|
||||
});
|
||||
}
|
||||
if (hasClinicInterest) {
|
||||
Object.keys(activeClinicInterest.value).forEach(kodeKlinik => {
|
||||
fetchPatientsForClinic(kodeKlinik, true);
|
||||
});
|
||||
}
|
||||
}
|
||||
}, 30000); // 30 seconds
|
||||
};
|
||||
|
||||
const stopAutoSync = () => {
|
||||
if (_autoSyncInterval) {
|
||||
clearInterval(_autoSyncInterval);
|
||||
_autoSyncInterval = null;
|
||||
console.log('⏹️ [queueStore] Store-level auto-sync stopped');
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Initialize Global WebSocket
|
||||
*/
|
||||
const initWebSocket = (customClientId = null) => {
|
||||
if (isConnected.value && customClientId === wsClientId.value) {
|
||||
console.log('🔌 [queueStore] WebSocket already connected with same ID.');
|
||||
// Auto-sync should still start even if WS is already connected
|
||||
startAutoSync();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -448,11 +500,14 @@ export const useQueueStore = defineStore('queue', () => {
|
||||
wsClientId.value = customClientId;
|
||||
// Re-connect with new ID if changed
|
||||
disconnect();
|
||||
// useWebSocket will use the new wsClientId.value if it's reactive
|
||||
}
|
||||
|
||||
console.log(`🔌 [queueStore] Connecting to WebSocket: ${wsBaseUrl} as ${wsClientId.value}`);
|
||||
connect();
|
||||
|
||||
// Start store-level auto-polling if not already running (client-side only).
|
||||
// This guarantees cross-device sync even when WS messages are missed.
|
||||
startAutoSync();
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -925,6 +980,9 @@ const fetchPatientsForLoket = async (loketId, force = false) => {
|
||||
// console.log(`✅ [queueStore] Successfully fetched ${finalPatients.length} unique patients for loket ${loketId} (Original: ${mappedPatients.length})`);
|
||||
// console.log(`📊 [queueStore] Total patients in allPatients: ${allPatients.value.length}`);
|
||||
|
||||
// SYNC COUNTERS WITH API DATA SO NEW TICKETS DONT RESET
|
||||
syncCountersWithState();
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: `${mappedPatients.length} pasien berhasil dimuat`,
|
||||
@@ -3366,13 +3424,15 @@ const fetchPatientsForLoket = async (loketId, force = false) => {
|
||||
activeClinicInterest,
|
||||
registerGlobalInterest,
|
||||
unregisterGlobalInterest,
|
||||
startAutoSync,
|
||||
stopAutoSync,
|
||||
};
|
||||
|
||||
}, {
|
||||
persist: {
|
||||
key: 'queue-store-state',
|
||||
storage: typeof window !== 'undefined' ? localStorage : undefined,
|
||||
paths: ['allPatients', 'quotaUsed', 'currentProcessingPatient', 'apiPatientsPerLoket', 'lastUpdated'],
|
||||
paths: ['quotaUsed', 'lastUpdated'],
|
||||
serializer: {
|
||||
deserialize: JSON.parse,
|
||||
serialize: JSON.stringify,
|
||||
|
||||
@@ -552,10 +552,6 @@ export const useRuangStore = defineStore('ruang', () => {
|
||||
fetchRuangFromAPI,
|
||||
};
|
||||
}, {
|
||||
persist: {
|
||||
key: 'ruang-store-state',
|
||||
storage: typeof window !== 'undefined' ? localStorage : undefined,
|
||||
paths: ['ruangData'],
|
||||
},
|
||||
persist: false,
|
||||
});
|
||||
|
||||
+51
-65
@@ -1,31 +1,15 @@
|
||||
// stores/screenStore.js
|
||||
// Konfigurasi layar TV klinik — data disimpan di server SQLite via /api/config/screen
|
||||
import { defineStore } from 'pinia';
|
||||
import { ref, computed } from 'vue';
|
||||
|
||||
export const useScreenStore = defineStore('screen', () => {
|
||||
// Initial screen items data
|
||||
const screenItems = ref([
|
||||
{
|
||||
id: 1,
|
||||
namaScreen: "Layar Screen 1",
|
||||
nomorScreen: "SCR-001",
|
||||
klinik: ["AN", "AS", "BD", "GI", "GR", "GZ", "IP", "JT"],
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
namaScreen: "Layar Screen 2",
|
||||
nomorScreen: "SCR-002",
|
||||
klinik: ["JW", "KK", "MT", "SR", "OB", "PR"],
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
namaScreen: "Layar Screen 3",
|
||||
nomorScreen: "SCR-003",
|
||||
klinik: ["RT", "RM", "HO"],
|
||||
},
|
||||
]);
|
||||
const screenItems = ref([]);
|
||||
const isLoading = ref(false);
|
||||
const error = ref(null);
|
||||
|
||||
const getAllScreens = computed(() => screenItems.value);
|
||||
|
||||
// Computed
|
||||
const getScreenById = (id) => {
|
||||
return computed(() => {
|
||||
const targetId = Number(id);
|
||||
@@ -33,64 +17,66 @@ export const useScreenStore = defineStore('screen', () => {
|
||||
});
|
||||
};
|
||||
|
||||
const getAllScreens = computed(() => screenItems.value);
|
||||
|
||||
// Actions
|
||||
const addScreen = (screenPayload) => {
|
||||
// Ensure we get a valid ID even if screenItems is empty
|
||||
const maxId = screenItems.value.length > 0
|
||||
? Math.max(...screenItems.value.map(s => s.id), 0)
|
||||
: 0;
|
||||
const newId = maxId + 1;
|
||||
// Pastikan id baru tidak tertimpa payload (payload.id bisa null)
|
||||
const newScreen = {
|
||||
...screenPayload,
|
||||
id: newId,
|
||||
};
|
||||
screenItems.value.push(newScreen);
|
||||
return { success: true, message: `Screen ${newScreen.namaScreen} berhasil ditambahkan`, data: newScreen };
|
||||
// ── Fetch from API ─────────────────────────────────────────────────────────
|
||||
const fetchScreens = async () => {
|
||||
if (isLoading.value) return;
|
||||
isLoading.value = true;
|
||||
error.value = null;
|
||||
try {
|
||||
const res = await $fetch('/api/config/screen');
|
||||
screenItems.value = res.data || [];
|
||||
} catch (e) {
|
||||
error.value = e.message;
|
||||
console.error('❌ [screenStore] Gagal fetch screen config:', e);
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const updateScreen = (screenPayload) => {
|
||||
const index = screenItems.value.findIndex(s => s.id === screenPayload.id);
|
||||
if (index !== -1) {
|
||||
screenItems.value[index] = {
|
||||
...screenItems.value[index],
|
||||
...screenPayload,
|
||||
};
|
||||
return { success: true, message: `Konfigurasi ${screenPayload.namaScreen} berhasil disimpan` };
|
||||
// ── CRUD via API ───────────────────────────────────────────────────────────
|
||||
const addScreen = async (screenPayload) => {
|
||||
try {
|
||||
const res = await $fetch('/api/config/screen', { method: 'POST', body: screenPayload });
|
||||
screenItems.value.push(res.data);
|
||||
return { success: true, message: res.message, data: res.data };
|
||||
} catch (e) {
|
||||
return { success: false, message: e.data?.message || e.message };
|
||||
}
|
||||
return { success: false, message: 'Screen tidak ditemukan' };
|
||||
};
|
||||
|
||||
const deleteScreen = (screenId) => {
|
||||
const index = screenItems.value.findIndex(s => s.id === screenId);
|
||||
if (index !== -1) {
|
||||
const screenName = screenItems.value[index].namaScreen;
|
||||
screenItems.value.splice(index, 1);
|
||||
return { success: true, message: `Screen ${screenName} berhasil dihapus` };
|
||||
const updateScreen = async (screenPayload) => {
|
||||
try {
|
||||
const res = await $fetch(`/api/config/screen/${screenPayload.id}`, { method: 'PUT', body: screenPayload });
|
||||
const index = screenItems.value.findIndex(s => s.id === screenPayload.id);
|
||||
if (index !== -1) screenItems.value[index] = res.data;
|
||||
return { success: true, message: res.message };
|
||||
} catch (e) {
|
||||
return { success: false, message: e.data?.message || e.message };
|
||||
}
|
||||
};
|
||||
|
||||
const deleteScreen = async (screenId) => {
|
||||
try {
|
||||
const res = await $fetch(`/api/config/screen/${screenId}`, { method: 'DELETE' });
|
||||
screenItems.value = screenItems.value.filter(s => s.id !== screenId);
|
||||
return { success: true, message: res.message };
|
||||
} catch (e) {
|
||||
return { success: false, message: e.data?.message || e.message };
|
||||
}
|
||||
return { success: false, message: 'Screen tidak ditemukan' };
|
||||
};
|
||||
|
||||
return {
|
||||
// State
|
||||
screenItems,
|
||||
|
||||
// Computed
|
||||
isLoading,
|
||||
error,
|
||||
getAllScreens,
|
||||
getScreenById,
|
||||
|
||||
// Actions
|
||||
fetchScreens,
|
||||
addScreen,
|
||||
updateScreen,
|
||||
deleteScreen,
|
||||
};
|
||||
}, {
|
||||
persist: {
|
||||
key: 'screen-store-state',
|
||||
storage: typeof window !== 'undefined' ? localStorage : undefined,
|
||||
paths: ['screenItems'],
|
||||
},
|
||||
// No persist — data lives in the server DB
|
||||
persist: false,
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user