update API dan header
This commit is contained in:
No files matched your search
Generated
+11
-38
@@ -9304,17 +9304,6 @@
|
||||
"url": "https://github.com/sponsors/wooorm"
|
||||
}
|
||||
},
|
||||
"node_modules/commander": {
|
||||
"version": "13.1.0",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-13.1.0.tgz",
|
||||
"integrity": "sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/comment-parser": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/comment-parser/-/comment-parser-1.4.1.tgz",
|
||||
@@ -9521,22 +9510,6 @@
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/crossws": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/crossws/-/crossws-0.4.1.tgz",
|
||||
"integrity": "sha512-E7WKBcHVhAVrY6JYD5kteNqVq1GSZxqGrdSiwXR9at+XHi43HJoCQKXcCczR5LBnBquFZPsB3o7HklulKoBU5w==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"peerDependencies": {
|
||||
"srvx": ">=0.7.1"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"srvx": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/css-declaration-sorter": {
|
||||
"version": "7.3.1",
|
||||
"resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-7.3.1.tgz",
|
||||
@@ -10457,9 +10430,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/devalue": {
|
||||
"version": "5.6.1",
|
||||
"resolved": "https://registry.npmjs.org/devalue/-/devalue-5.6.1.tgz",
|
||||
"integrity": "sha512-jDwizj+IlEZBunHcOuuFVBnIMPAEHvTsJj0BcIp94xYguLRVBcXO853px/MyIJvbVzWdsGvrRweIUWJw8hBP7A==",
|
||||
"version": "5.6.2",
|
||||
"resolved": "https://registry.npmjs.org/devalue/-/devalue-5.6.2.tgz",
|
||||
"integrity": "sha512-nPRkjWzzDQlsejL1WVifk5rvcFi/y1onBRxjaFMjZeR9mFpqu2gmAZ9xUB9/IEanEP/vBtGeGganC/GO1fmufg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/devlop": {
|
||||
@@ -12193,9 +12166,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/h3": {
|
||||
"version": "1.15.4",
|
||||
"resolved": "https://registry.npmjs.org/h3/-/h3-1.15.4.tgz",
|
||||
"integrity": "sha512-z5cFQWDffyOe4vQ9xIqNfCZdV4p//vy6fBnr8Q1AWnVZ0teurKMG66rLj++TKwKPUP3u7iMUvrvKaEUiQw2QWQ==",
|
||||
"version": "1.15.5",
|
||||
"resolved": "https://registry.npmjs.org/h3/-/h3-1.15.5.tgz",
|
||||
"integrity": "sha512-xEyq3rSl+dhGX2Lm0+eFQIAzlDN6Fs0EcC4f7BNUmzaRX/PTzeuM+Tr2lHB8FoXggsQIeXLj8EDVgs5ywxyxmg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"cookie-es": "^1.2.2",
|
||||
@@ -12203,9 +12176,9 @@
|
||||
"defu": "^6.1.4",
|
||||
"destr": "^2.0.5",
|
||||
"iron-webcrypto": "^1.2.1",
|
||||
"node-mock-http": "^1.0.2",
|
||||
"node-mock-http": "^1.0.4",
|
||||
"radix3": "^1.1.2",
|
||||
"ufo": "^1.6.1",
|
||||
"ufo": "^1.6.3",
|
||||
"uncrypto": "^0.1.3"
|
||||
}
|
||||
},
|
||||
@@ -18879,9 +18852,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/tar": {
|
||||
"version": "7.5.2",
|
||||
"resolved": "https://registry.npmjs.org/tar/-/tar-7.5.2.tgz",
|
||||
"integrity": "sha512-7NyxrTE4Anh8km8iEy7o0QYPs+0JKBTj5ZaqHg6B39erLg0qYXN3BijtShwbsNSvQ+LN75+KV+C4QR/f6Gwnpg==",
|
||||
"version": "7.5.5",
|
||||
"resolved": "https://registry.npmjs.org/tar/-/tar-7.5.5.tgz",
|
||||
"integrity": "sha512-ocjwSUBo2V8ExKxy9FH6iROIsK60OCW//h14MFYpivNSYIj7ntgm35ijGT82Jl//xwbxBhIYXkfAovjtVm9nrA==",
|
||||
"license": "BlueOak-1.0.0",
|
||||
"dependencies": {
|
||||
"@isaacs/fs-minipass": "^4.0.0",
|
||||
|
||||
+11
-3
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="loket-container">
|
||||
<div>
|
||||
<!-- Compact Header -->
|
||||
<PageHeader
|
||||
icon="mdi-hospital-building"
|
||||
@@ -9,8 +9,9 @@
|
||||
theme="primary"
|
||||
/>
|
||||
|
||||
<!-- Main Content Grid -->
|
||||
<v-row class="content-grid" dense>
|
||||
<div class="loket-container">
|
||||
<!-- Main Content Grid -->
|
||||
<v-row class="content-grid" dense>
|
||||
<!-- Left Column: Current Patient & Queue Actions -->
|
||||
<v-col cols="12" md="5">
|
||||
<div class="sticky-wrapper">
|
||||
@@ -227,6 +228,7 @@
|
||||
:message="snackbarText"
|
||||
:color="snackbarColor"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -416,6 +418,12 @@ const buatAntreanKlinikRuang = (klinikRuang, ruang) => {
|
||||
background: var(--color-neutral-300);
|
||||
min-height: 100vh;
|
||||
padding: 16px;
|
||||
border-radius: 0 !important;
|
||||
}
|
||||
|
||||
/* Remove rounded edges from PageHeader component */
|
||||
:deep(.page-header) {
|
||||
border-radius: 0 !important;
|
||||
}
|
||||
|
||||
.content-grid {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="admin-klinik-ruang-container">
|
||||
<div>
|
||||
<!-- Header -->
|
||||
<PageHeader
|
||||
icon="mdi-door-open"
|
||||
@@ -9,7 +9,8 @@
|
||||
theme="primary"
|
||||
/>
|
||||
|
||||
<!-- Global Search and Actions -->
|
||||
<div class="admin-klinik-ruang-container">
|
||||
<!-- Global Search and Actions -->
|
||||
<div class="global-search-section sticky-search-section">
|
||||
<div class="search-actions-row">
|
||||
<v-text-field
|
||||
@@ -793,6 +794,7 @@
|
||||
:message="snackbarText"
|
||||
:color="snackbarColor"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1770,6 +1772,12 @@ onMounted(() => {
|
||||
background: var(--color-neutral-300);
|
||||
min-height: 100vh;
|
||||
padding: 16px;
|
||||
border-radius: 0 !important;
|
||||
}
|
||||
|
||||
/* Remove rounded edges from PageHeader component */
|
||||
:deep(.page-header) {
|
||||
border-radius: 0 !important;
|
||||
}
|
||||
|
||||
.generate-ticket-card {
|
||||
@@ -2095,7 +2103,7 @@ onMounted(() => {
|
||||
}
|
||||
|
||||
.dialog-header {
|
||||
background: linear-gradient(135deg, var(--color-secondary-600) 0%, var(--color-secondary-700) 100%);
|
||||
background: linear-gradient(135deg, var(--color-primary-600) 0%, var(--color-primary-700) 100%);
|
||||
color: var(--color-neutral-100);
|
||||
padding: 20px 24px;
|
||||
display: flex;
|
||||
@@ -2104,7 +2112,7 @@ onMounted(() => {
|
||||
}
|
||||
|
||||
.dialog-header-warning {
|
||||
background: linear-gradient(135deg, var(--color-secondary-600) 0%, var(--color-secondary-700) 100%);
|
||||
background: linear-gradient(135deg, var(--color-primary-600) 0%, var(--color-primary-700) 100%);
|
||||
}
|
||||
|
||||
.headline-4 {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="admin-klinik-ruang-index">
|
||||
<div>
|
||||
<PageHeader
|
||||
icon="mdi-door-open"
|
||||
title="Admin Klinik Ruang"
|
||||
@@ -8,7 +8,8 @@
|
||||
theme="primary"
|
||||
/>
|
||||
|
||||
<v-row class="mt-4">
|
||||
<div class="admin-klinik-ruang-index">
|
||||
<v-row class="mt-4">
|
||||
<v-col
|
||||
v-for="klinikRuang in klinikRuangList"
|
||||
:key="klinikRuang.id"
|
||||
@@ -41,6 +42,7 @@
|
||||
<v-icon size="64" color="grey-lighten-2">mdi-hospital-building-outline</v-icon>
|
||||
<div class="empty-text">Tidak ada klinik ruang yang tersedia</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -67,6 +69,12 @@ const navigateToKlinik = (kodeKlinik) => {
|
||||
background: var(--color-neutral-300);
|
||||
min-height: 100vh;
|
||||
padding: 16px;
|
||||
border-radius: 0 !important;
|
||||
}
|
||||
|
||||
/* Remove rounded edges from PageHeader component */
|
||||
:deep(.page-header) {
|
||||
border-radius: 0 !important;
|
||||
}
|
||||
|
||||
.klinik-card {
|
||||
|
||||
+13
-5
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="loket-container">
|
||||
<div>
|
||||
<!-- Compact Header -->
|
||||
<PageHeader
|
||||
icon="mdi-view-dashboard"
|
||||
@@ -9,8 +9,9 @@
|
||||
theme="primary"
|
||||
/>
|
||||
|
||||
<!-- Main Content Grid -->
|
||||
<v-row class="content-grid" dense>
|
||||
<div class="loket-container">
|
||||
<!-- Main Content Grid -->
|
||||
<v-row class="content-grid" dense>
|
||||
<!-- Left Column: Current Patient & Queue Actions -->
|
||||
<v-col cols="12" md="5" class="sticky-column">
|
||||
<div class="sticky-wrapper">
|
||||
@@ -199,6 +200,7 @@
|
||||
:message="snackbarText"
|
||||
:color="snackbarColor"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -518,6 +520,12 @@ const buatAntreanKlinikRuang = async (klinikRuang, ruang) => {
|
||||
width: 100%;
|
||||
max-width: 100vw;
|
||||
box-sizing: border-box;
|
||||
border-radius: 0 !important;
|
||||
}
|
||||
|
||||
/* Remove rounded edges from PageHeader component */
|
||||
:deep(.page-header) {
|
||||
border-radius: 0 !important;
|
||||
}
|
||||
|
||||
.content-grid {
|
||||
@@ -606,7 +614,7 @@ const buatAntreanKlinikRuang = async (klinikRuang, ruang) => {
|
||||
}
|
||||
|
||||
.dialog-header {
|
||||
background: linear-gradient(135deg, var(--color-secondary-600) 0%, var(--color-secondary-700) 100%);
|
||||
background: linear-gradient(135deg, var(--color-primary-600) 0%, var(--color-primary-700) 100%);
|
||||
color: var(--color-neutral-100);
|
||||
padding: 20px 24px;
|
||||
display: flex;
|
||||
@@ -615,7 +623,7 @@ const buatAntreanKlinikRuang = async (klinikRuang, ruang) => {
|
||||
}
|
||||
|
||||
.dialog-header-warning {
|
||||
background: linear-gradient(135deg, var(--color-secondary-600) 0%, var(--color-secondary-700) 100%);
|
||||
background: linear-gradient(135deg, var(--color-primary-600) 0%, var(--color-primary-700) 100%);
|
||||
}
|
||||
|
||||
.headline-4 {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<v-container fluid class="bg-grey-lighten-4 pa-4">
|
||||
<div>
|
||||
<div class="page-header">
|
||||
<div class="header-content">
|
||||
<div class="header-left">
|
||||
@@ -17,7 +17,9 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<v-card flat>
|
||||
|
||||
<v-container fluid class="bg-grey-lighten-4 pa-4">
|
||||
<v-card flat>
|
||||
<v-card-text>
|
||||
<v-row align="center">
|
||||
<v-col cols="12" md="4">
|
||||
@@ -82,7 +84,8 @@
|
||||
</template>
|
||||
</TabelData>
|
||||
</v-card>
|
||||
</v-container>
|
||||
</v-container>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
@@ -200,8 +203,8 @@ const items = ref([
|
||||
<style scoped>
|
||||
.page-header {
|
||||
background: linear-gradient(135deg, #3A61C9 0%, #3556AE 100%);
|
||||
border-radius: 16px;
|
||||
margin-bottom: 24px;
|
||||
border-radius: 0 !important;
|
||||
margin-bottom: 0;
|
||||
box-shadow: 0 8px 32px rgba(58, 97, 201, 0.3);
|
||||
}
|
||||
|
||||
|
||||
+152
-188
@@ -742,151 +742,60 @@ const anjunganData = computed(() => {
|
||||
return list.find((a) => Number(a.id) === Number(id)) || null;
|
||||
});
|
||||
|
||||
//kode mapping table
|
||||
const KODE_MAPPING = {
|
||||
AN: "ANA", // Anak
|
||||
AS: "ANESTESI", // Anestesi
|
||||
BD: "BEDAH", // Bedah
|
||||
GR: "GERIATRI", // Geriatri
|
||||
GI: "GIGI", // Gigi dan Mulut
|
||||
GZ: "GIZI", // Gizi
|
||||
HO: "HEMATO", // Hemato Onkologi
|
||||
IP: "INTERNA", // IPD / Penyakit Dalam
|
||||
JT: "JANTUNG", // Jantung / Cardiologi
|
||||
JW: "JIWA", // Jiwa / Psikiatri
|
||||
OB: "OBGYN", // Kandungan
|
||||
KH: "KEMOTERAPI", // Kemoterapi
|
||||
KN: "NYERI", // Komplementer Nyeri
|
||||
KK: "KULIT", // Kulit Kelamin
|
||||
MT: "MATA", // Mata
|
||||
MC: "MCU", // MCU
|
||||
ON: "ONKOLOGI", // Onkologi
|
||||
PR: "PARU", // Paru
|
||||
TD: "TINDAKAN", // R. Tindakan
|
||||
RT: "RADIOTERAPI", // Radioterapi
|
||||
RM: "REHAB", // Rehab Medik
|
||||
SR: "SARAF", // Saraf / Neurologi
|
||||
TH: "THT", // THT
|
||||
};
|
||||
|
||||
const todayString = new Date().toISOString().substring(0, 10);
|
||||
const spesialisList = ref([]);
|
||||
const doctorsByKode = ref({});
|
||||
const doctorsByKlinikId = ref({});
|
||||
const loadingDoctors = ref({});
|
||||
|
||||
// load list spesialis dari API
|
||||
const loadSpesialisForDate = async (tanggal) => {
|
||||
try {
|
||||
const data = await $fetch(
|
||||
`http://10.10.150.131:8088/api/jadwaldokter/tanggal/${tanggal}`
|
||||
);
|
||||
spesialisList.value = Array.isArray(data) ? data : [];
|
||||
console.log(
|
||||
`✅ Loaded ${spesialisList.value.length} spesialis for ${tanggal}`
|
||||
);
|
||||
} catch (error) {
|
||||
console.error("❌ Gagal mengambil data spesialis dari API", error);
|
||||
spesialisList.value = [];
|
||||
}
|
||||
};
|
||||
|
||||
//get spesialis ID
|
||||
const getSpesialisIdForKode = (kodeKlinik) => {
|
||||
if (!kodeKlinik) return null;
|
||||
|
||||
// 1. normalize kode menggunakan mapping table
|
||||
const kodeUpper = kodeKlinik.toUpperCase();
|
||||
const normalizedKode = KODE_MAPPING[kodeUpper] || kodeUpper;
|
||||
|
||||
console.log(`🔄 Mapping: "${kodeKlinik}" → "${normalizedKode}"`);
|
||||
|
||||
// 2. cari exact match dulu
|
||||
let entry = spesialisList.value.find((item) => {
|
||||
const apiKode = (item.Kode || item.kode || "").toUpperCase();
|
||||
return apiKode === normalizedKode;
|
||||
});
|
||||
|
||||
// 3. jika tidak ada exact match, coba prefix match
|
||||
if (!entry) {
|
||||
entry = spesialisList.value.find((item) => {
|
||||
const apiKode = (item.Kode || item.kode || "").toUpperCase();
|
||||
return apiKode.startsWith(normalizedKode);
|
||||
});
|
||||
|
||||
if (entry) {
|
||||
console.log(
|
||||
`⚠️ Using prefix match: "${normalizedKode}" → "${
|
||||
entry.Kode || entry.kode
|
||||
}"`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const spesialisId = entry
|
||||
? entry.id || entry.ID || entry.FK_daftar_spesialis_ID
|
||||
: null;
|
||||
|
||||
if (!spesialisId) {
|
||||
console.warn(
|
||||
`❌ No match for: "${kodeKlinik}" (normalized: "${normalizedKode}")`
|
||||
);
|
||||
console.log(
|
||||
"Available API kodes:",
|
||||
spesialisList.value.map((s) => s.Kode || s.kode)
|
||||
);
|
||||
} else {
|
||||
console.log(`✅ Found spesialisId: ${spesialisId} for "${kodeKlinik}"`);
|
||||
}
|
||||
|
||||
return spesialisId;
|
||||
};
|
||||
|
||||
const loadDoctorsForKode = async (kodeKlinik) => {
|
||||
if (!kodeKlinik) return;
|
||||
// Load doctors dari API berdasarkan idklinik
|
||||
const loadDoctorsForClinic = async (clinic) => {
|
||||
if (!clinic || !clinic.id) return;
|
||||
|
||||
const idklinik = clinic.id;
|
||||
|
||||
// cek apakah sudah ada data atau sedang loading
|
||||
if (doctorsByKode.value[kodeKlinik] || loadingDoctors.value[kodeKlinik]) {
|
||||
if (doctorsByKlinikId.value[idklinik] || loadingDoctors.value[idklinik]) {
|
||||
return;
|
||||
}
|
||||
|
||||
loadingDoctors.value[kodeKlinik] = true;
|
||||
|
||||
const spesialisId = getSpesialisIdForKode(kodeKlinik);
|
||||
|
||||
if (!spesialisId) {
|
||||
console.warn(
|
||||
`⚠️ Skip loading doctors untuk kode: ${kodeKlinik} (spesialisId not found)`
|
||||
);
|
||||
loadingDoctors.value[kodeKlinik] = false;
|
||||
// set empty array agar tidak retry terus-menerus
|
||||
doctorsByKode.value[kodeKlinik] = [];
|
||||
return;
|
||||
}
|
||||
|
||||
const tanggal = todayString;
|
||||
loadingDoctors.value[idklinik] = true;
|
||||
|
||||
try {
|
||||
console.log(`🔄 Fetching doctors for klinik ID: ${idklinik} (${clinic.name})`);
|
||||
const data = await $fetch(
|
||||
`http://10.10.150.131:8088/api/jadwaldokter/tanggal/${tanggal}/spesialis/${spesialisId}`
|
||||
`http://10.10.150.131:8089/api/v1/dokter/${idklinik}`
|
||||
);
|
||||
|
||||
const list = Array.isArray(data) ? data : [];
|
||||
const doctorNames = list
|
||||
.map((d) => d.nama_lengkap || d.Nama_dokter || d.name)
|
||||
console.log(`📦 Raw doctor data for ${idklinik}:`, data);
|
||||
|
||||
// Handle different response formats
|
||||
let doctorList = [];
|
||||
|
||||
if (Array.isArray(data)) {
|
||||
doctorList = data;
|
||||
} else if (data && typeof data === 'object') {
|
||||
if (Array.isArray(data.data)) {
|
||||
doctorList = data.data;
|
||||
} else if (Array.isArray(data.result)) {
|
||||
doctorList = data.result;
|
||||
}
|
||||
}
|
||||
|
||||
// Extract doctor names
|
||||
const doctorNames = doctorList
|
||||
.map((d) => d.nama_dokter || d.nama_lengkap || d.Nama_dokter || d.name || d.nama)
|
||||
.filter(Boolean);
|
||||
|
||||
doctorsByKode.value[kodeKlinik] = doctorNames;
|
||||
doctorsByKlinikId.value[idklinik] = doctorNames;
|
||||
|
||||
console.log(
|
||||
`✅ Loaded ${doctorNames.length} doctors untuk ${kodeKlinik}:`,
|
||||
`✅ Loaded ${doctorNames.length} doctors untuk klinik ${clinic.name} (ID: ${idklinik}):`,
|
||||
doctorNames
|
||||
);
|
||||
} catch (error) {
|
||||
console.error(`❌ Gagal mengambil dokter untuk kode ${kodeKlinik}:`, error);
|
||||
console.error(`❌ Gagal mengambil dokter untuk klinik ID ${idklinik}:`, error);
|
||||
// set empty array agar tidak retry
|
||||
doctorsByKode.value[kodeKlinik] = [];
|
||||
doctorsByKlinikId.value[idklinik] = [];
|
||||
} finally {
|
||||
loadingDoctors.value[kodeKlinik] = false;
|
||||
loadingDoctors.value[idklinik] = false;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -896,22 +805,27 @@ const prefetchDoctorsForCurrentAnjungan = async () => {
|
||||
return;
|
||||
}
|
||||
|
||||
const uniqueKodes = Array.from(new Set(anjunganData.value.klinik));
|
||||
// Get all clinics for this anjungan
|
||||
const allClinics = clinicStore.getAllClinics;
|
||||
const clinicsForAnjungan = anjunganData.value.klinik
|
||||
.map((kode) => allClinics.find((c) => c.kode === kode))
|
||||
.filter(Boolean);
|
||||
|
||||
console.log(
|
||||
`🔄 Prefetching doctors for ${uniqueKodes.length} klinik:`,
|
||||
uniqueKodes
|
||||
`🔄 Prefetching doctors for ${clinicsForAnjungan.length} klinik`
|
||||
);
|
||||
|
||||
await Promise.all(uniqueKodes.map((kode) => loadDoctorsForKode(kode)));
|
||||
await Promise.all(clinicsForAnjungan.map((clinic) => loadDoctorsForClinic(clinic)));
|
||||
|
||||
console.log("✅ Prefetch complete. Doctors by kode:", doctorsByKode.value);
|
||||
console.log("✅ Prefetch complete. Doctors by klinik ID:", doctorsByKlinikId.value);
|
||||
};
|
||||
|
||||
const dialogDoctors = computed(() => {
|
||||
const kode = selectedClinic.value?.kode;
|
||||
if (!kode) return [];
|
||||
const clinic = selectedClinic.value;
|
||||
if (!clinic || !clinic.id) return [];
|
||||
|
||||
const apiDoctors = doctorsByKode.value[kode];
|
||||
const idklinik = clinic.id;
|
||||
const apiDoctors = doctorsByKlinikId.value[idklinik];
|
||||
|
||||
// prioritas: API > Static
|
||||
if (apiDoctors && apiDoctors.length > 0) {
|
||||
@@ -919,10 +833,10 @@ const dialogDoctors = computed(() => {
|
||||
}
|
||||
|
||||
// fallback ke static data
|
||||
const staticDoctors = selectedClinic.value?.doctors || [];
|
||||
const staticDoctors = clinic.doctors || [];
|
||||
|
||||
if (staticDoctors.length > 0) {
|
||||
console.log(`ℹ️ Using static doctors for ${kode} (API data not available)`);
|
||||
console.log(`ℹ️ Using static doctors for ${clinic.name} (API data not available)`);
|
||||
}
|
||||
|
||||
return staticDoctors;
|
||||
@@ -931,40 +845,10 @@ const dialogDoctors = computed(() => {
|
||||
onMounted(async () => {
|
||||
console.log("🚀 Component mounted, starting data load...");
|
||||
|
||||
// Step 1: Load spesialis list
|
||||
await loadSpesialisForDate(todayString);
|
||||
|
||||
// Step 2: Wait for computed values to be ready
|
||||
// Wait for computed values to be ready
|
||||
await nextTick();
|
||||
|
||||
if (anjunganData.value?.klinik) {
|
||||
console.group("📊 Kode Mapping Comparison");
|
||||
console.table(
|
||||
anjunganData.value.klinik.map((storeKode) => {
|
||||
const clinic = clinicStore.getClinicByKode(storeKode);
|
||||
const normalizedKode =
|
||||
KODE_MAPPING[storeKode.toUpperCase()] || storeKode;
|
||||
const spesialisId = getSpesialisIdForKode(storeKode);
|
||||
const apiMatch = spesialisList.value.find(
|
||||
(s) => (s.id || s.ID || s.FK_daftar_spesialis_ID) === spesialisId
|
||||
);
|
||||
|
||||
return {
|
||||
"Store Kode": storeKode,
|
||||
"Clinic Name": clinic?.name || "❌ Not found",
|
||||
Normalized: normalizedKode,
|
||||
"API Kode": apiMatch
|
||||
? apiMatch.Kode || apiMatch.kode
|
||||
: "❌ Not matched",
|
||||
"Spesialis ID": spesialisId || "❌",
|
||||
Status: spesialisId ? "✅" : "❌",
|
||||
};
|
||||
})
|
||||
);
|
||||
console.groupEnd();
|
||||
}
|
||||
|
||||
// Step 3: Prefetch doctors
|
||||
// Prefetch doctors for all clinics in this anjungan
|
||||
await prefetchDoctorsForCurrentAnjungan();
|
||||
|
||||
console.log("✅ Initial data load complete");
|
||||
@@ -1019,8 +903,8 @@ const isEksekutif = computed(() => {
|
||||
|
||||
// tampilkan doctor info dengan prioritas API
|
||||
const getDisplayDoctorInfo = (clinic) => {
|
||||
const kode = clinic.kode;
|
||||
const apiDoctors = kode ? doctorsByKode.value[kode] : null;
|
||||
const idklinik = clinic.id;
|
||||
const apiDoctors = idklinik ? doctorsByKlinikId.value[idklinik] : null;
|
||||
const doctors =
|
||||
apiDoctors && apiDoctors.length > 0 ? apiDoctors : clinic.doctors || [];
|
||||
|
||||
@@ -1039,43 +923,123 @@ const getDisplayDoctorInfo = (clinic) => {
|
||||
}
|
||||
};
|
||||
|
||||
// Helper to get current day name in Indonesian
|
||||
const getTodayName = () => {
|
||||
const days = ['Minggu', 'Senin', 'Selasa', 'Rabu', 'Kamis', 'Jum\'at', 'Sabtu'];
|
||||
return days[new Date().getDay()];
|
||||
};
|
||||
|
||||
// Helper to get current hour
|
||||
const getCurrentHour = () => {
|
||||
return new Date().getHours();
|
||||
};
|
||||
|
||||
// Get current shift based on time and day
|
||||
const getCurrentShift = (clinic) => {
|
||||
if (!clinic || !clinic.jamShiftPerHari) return null;
|
||||
|
||||
const today = getTodayName();
|
||||
const shiftsToday = clinic.jamShiftPerHari[today];
|
||||
|
||||
if (!shiftsToday || shiftsToday.length === 0) return null;
|
||||
|
||||
const currentHour = getCurrentHour();
|
||||
const currentMinutes = new Date().getMinutes();
|
||||
const currentTime = currentHour * 60 + currentMinutes;
|
||||
|
||||
// Find active shift
|
||||
for (const shift of shiftsToday) {
|
||||
const [startH, startM] = shift.dari.split(':').map(Number);
|
||||
const [endH, endM] = shift.sampai.split(':').map(Number);
|
||||
const startTime = startH * 60 + startM;
|
||||
const endTime = endH * 60 + endM;
|
||||
|
||||
if (currentTime >= startTime && currentTime < endTime) {
|
||||
return shift;
|
||||
}
|
||||
}
|
||||
|
||||
// If no active shift, return first shift
|
||||
return shiftsToday[0];
|
||||
};
|
||||
|
||||
const isShift1Full = (clinic) => {
|
||||
if (!clinic || !clinic.shifts || clinic.shifts.length === 0) return true;
|
||||
const shift1 = clinic.shifts.find((s) => s.name === "Shift 1");
|
||||
return shift1 ? shift1.quota === 0 : true;
|
||||
if (!clinic || !clinic.jamShiftPerHari) return true;
|
||||
|
||||
const today = getTodayName();
|
||||
const shiftsToday = clinic.jamShiftPerHari[today];
|
||||
|
||||
if (!shiftsToday || shiftsToday.length === 0) return true;
|
||||
|
||||
// Check first shift
|
||||
const firstShift = shiftsToday[0];
|
||||
return firstShift ? firstShift.kuota === 0 : true;
|
||||
};
|
||||
|
||||
const getCurrentShiftNumber = () => {
|
||||
return 1;
|
||||
if (!selectedClinic.value) return 1;
|
||||
|
||||
const today = getTodayName();
|
||||
const shiftsToday = selectedClinic.value.jamShiftPerHari?.[today];
|
||||
|
||||
if (!shiftsToday || shiftsToday.length === 0) return 1;
|
||||
|
||||
const currentShift = getCurrentShift(selectedClinic.value);
|
||||
if (!currentShift) return 1;
|
||||
|
||||
return shiftsToday.indexOf(currentShift) + 1;
|
||||
};
|
||||
|
||||
const getCurrentShiftQuota = () => {
|
||||
if (!selectedClinic.value || !selectedClinic.value.shifts) return 0;
|
||||
const currentShift = selectedClinic.value.shifts.find(
|
||||
(s) => s.name === "Shift 1"
|
||||
);
|
||||
return currentShift ? currentShift.quota : 0;
|
||||
if (!selectedClinic.value || !selectedClinic.value.jamShiftPerHari) return 0;
|
||||
|
||||
const today = getTodayName();
|
||||
const shiftsToday = selectedClinic.value.jamShiftPerHari[today];
|
||||
|
||||
if (!shiftsToday || shiftsToday.length === 0) return 0;
|
||||
|
||||
const currentShift = getCurrentShift(selectedClinic.value);
|
||||
return currentShift ? currentShift.kuota : 0;
|
||||
};
|
||||
|
||||
const getAvailableShiftsForBooking = () => {
|
||||
if (!selectedClinic.value || !selectedClinic.value.shifts) return [];
|
||||
if (!selectedClinic.value || !selectedClinic.value.jamShiftPerHari) return [];
|
||||
|
||||
const selectedDate = bookingForm.value.date;
|
||||
const today = new Date().toISOString().substring(0, 10);
|
||||
const isToday = selectedDate === today;
|
||||
|
||||
return selectedClinic.value.shifts
|
||||
.filter((shift) => {
|
||||
if (isToday) {
|
||||
return shift.name !== "Shift 1" && shift.quota > 0;
|
||||
} else {
|
||||
return shift.quota > 0;
|
||||
// Get day name from selected date
|
||||
const date = new Date(selectedDate);
|
||||
const days = ['Minggu', 'Senin', 'Selasa', 'Rabu', 'Kamis', 'Jum\'at', 'Sabtu'];
|
||||
const dayName = days[date.getDay()];
|
||||
|
||||
const shiftsForDay = selectedClinic.value.jamShiftPerHari[dayName];
|
||||
|
||||
if (!shiftsForDay || shiftsForDay.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return shiftsForDay
|
||||
.map((shift, index) => {
|
||||
const shiftName = `Shift ${index + 1}`;
|
||||
const shiftTime = `${shift.dari} - ${shift.sampai}`;
|
||||
|
||||
// If today, skip shift 1
|
||||
if (isToday && index === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (shift.kuota === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
label: `${shiftName} (${shiftTime}) - Kuota: ${shift.kuota} pasien`,
|
||||
value: shiftName,
|
||||
};
|
||||
})
|
||||
.map((shift) => ({
|
||||
label: `${shift.name} (Kuota: ${shift.quota} pasien)`,
|
||||
value: shift.name,
|
||||
}));
|
||||
.filter(Boolean);
|
||||
};
|
||||
|
||||
const getMinDate = () => {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<!-- pages/Anjungan/Anjungan/index.vue -->
|
||||
<template>
|
||||
<div class="anjungan-selection-container">
|
||||
<div>
|
||||
<div class="selection-header">
|
||||
<div class="header-icon">
|
||||
<img
|
||||
@@ -15,7 +15,8 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="anjungan-grid">
|
||||
<div class="anjungan-selection-container">
|
||||
<div class="anjungan-grid">
|
||||
<div
|
||||
v-for="anj in paginatedAnjungan"
|
||||
:key="anj.id"
|
||||
@@ -84,10 +85,11 @@
|
||||
</v-btn>
|
||||
</div>
|
||||
|
||||
<div v-else-if="totalPages > 1" class="pagination">
|
||||
<v-btn variant="outlined" @click="goPrev" :disabled="page <= 1">Prev</v-btn>
|
||||
<span class="page-info">Page {{ page }} / {{ totalPages }}</span>
|
||||
<v-btn variant="outlined" @click="goNext" :disabled="page >= totalPages">Next</v-btn>
|
||||
<div v-else-if="totalPages > 1" class="pagination">
|
||||
<v-btn variant="outlined" @click="goPrev" :disabled="page <= 1">Prev</v-btn>
|
||||
<span class="page-info">Page {{ page }} / {{ totalPages }}</span>
|
||||
<v-btn variant="outlined" @click="goNext" :disabled="page >= totalPages">Next</v-btn>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -152,38 +154,37 @@ const navigateToSettings = () => {
|
||||
<style scoped lang="scss">
|
||||
.anjungan-selection-container {
|
||||
background: var(--color-neutral-300);
|
||||
min-height: 100vh;
|
||||
padding: 40px;
|
||||
min-height: calc(100vh - 80px);
|
||||
padding: 16px;
|
||||
font-family: 'Inter', 'Roboto', sans-serif;
|
||||
}
|
||||
|
||||
.selection-header {
|
||||
background: linear-gradient(135deg, var(--color-primary-600) 0%, var(--color-primary-700) 100%);
|
||||
border-radius: 20px;
|
||||
padding: 40px;
|
||||
margin-bottom: 40px;
|
||||
border-radius: 0 !important;
|
||||
padding: 16px 28px;
|
||||
margin-bottom: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 32px;
|
||||
box-shadow: 0 8px 24px rgba(255, 155, 27, 0.3);
|
||||
gap: 16px;
|
||||
height: 80px;
|
||||
box-shadow: 0 4px 16px rgba(33, 150, 243, 0.2);
|
||||
}
|
||||
|
||||
.header-icon {
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
border-radius: 20px;
|
||||
padding: 24px;
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
border-radius: 12px;
|
||||
padding: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
backdrop-filter: blur(10px);
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.header-logo {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
width: 52px;
|
||||
height: 52px;
|
||||
object-fit: contain;
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.header-content {
|
||||
@@ -192,19 +193,21 @@ const navigateToSettings = () => {
|
||||
}
|
||||
|
||||
.main-title {
|
||||
font-size: 40px;
|
||||
font-weight: 800;
|
||||
font-size: 32px;
|
||||
font-weight: 600;
|
||||
margin: 0;
|
||||
color: var(--color-neutral-100);
|
||||
line-height: 1.2;
|
||||
line-height: 40px;
|
||||
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
margin: 8px 0 0 0;
|
||||
font-size: 15px;
|
||||
font-weight: 400;
|
||||
margin: 2px 0 0 0;
|
||||
color: var(--color-neutral-100);
|
||||
opacity: 0.95;
|
||||
opacity: 0.9;
|
||||
line-height: 22px;
|
||||
}
|
||||
|
||||
.anjungan-grid {
|
||||
@@ -338,21 +341,33 @@ const navigateToSettings = () => {
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.anjungan-selection-container {
|
||||
padding: 20px;
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.selection-header {
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
padding: 32px 24px;
|
||||
flex-direction: row;
|
||||
padding: 12px 16px;
|
||||
height: auto;
|
||||
min-height: 64px;
|
||||
}
|
||||
|
||||
.header-icon {
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.header-logo {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.main-title {
|
||||
font-size: 32px;
|
||||
font-size: 20px;
|
||||
line-height: 28px;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 16px;
|
||||
font-size: 13px;
|
||||
line-height: 18px;
|
||||
}
|
||||
|
||||
.anjungan-grid {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<!-- pages/Anjungan/AntreanMasuk/index.vue -->
|
||||
<template>
|
||||
<div class="screen-selection-container">
|
||||
<div>
|
||||
<div class="selection-header">
|
||||
<div class="header-icon">
|
||||
<v-icon size="64" color="white">mdi-ticket-account</v-icon>
|
||||
@@ -11,7 +11,8 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="paginatedScreens.length > 0" class="screens-grid">
|
||||
<div class="screen-selection-container">
|
||||
<div v-if="paginatedScreens.length > 0" class="screens-grid">
|
||||
<div
|
||||
v-for="screen in paginatedScreens"
|
||||
:key="screen.id"
|
||||
@@ -80,10 +81,11 @@
|
||||
</v-btn>
|
||||
</div>
|
||||
|
||||
<div v-if="paginatedScreens.length > 0 && totalPages > 1" class="pagination">
|
||||
<v-btn variant="outlined" :disabled="page <= 1" @click="goPrev">Prev</v-btn>
|
||||
<span class="page-info">Page {{ page }} / {{ totalPages }}</span>
|
||||
<v-btn variant="outlined" :disabled="page >= totalPages" @click="goNext">Next</v-btn>
|
||||
<div v-if="paginatedScreens.length > 0 && totalPages > 1" class="pagination">
|
||||
<v-btn variant="outlined" :disabled="page <= 1" @click="goPrev">Prev</v-btn>
|
||||
<span class="page-info">Page {{ page }} / {{ totalPages }}</span>
|
||||
<v-btn variant="outlined" :disabled="page >= totalPages" @click="goNext">Next</v-btn>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -147,26 +149,27 @@ const navigateToSettings = () => {
|
||||
<style scoped lang="scss">
|
||||
.screen-selection-container {
|
||||
background: var(--color-neutral-300);
|
||||
min-height: 100vh;
|
||||
padding: 40px;
|
||||
min-height: calc(100vh - 80px);
|
||||
padding: 16px;
|
||||
font-family: 'Inter', 'Roboto', sans-serif;
|
||||
}
|
||||
|
||||
.selection-header {
|
||||
background: linear-gradient(135deg, var(--color-primary-600) 0%, var(--color-primary-700) 100%);
|
||||
border-radius: 20px;
|
||||
padding: 40px;
|
||||
margin-bottom: 40px;
|
||||
border-radius: 0 !important;
|
||||
padding: 16px 28px;
|
||||
margin-bottom: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 32px;
|
||||
box-shadow: 0 8px 24px rgba(25, 118, 210, 0.3);
|
||||
gap: 16px;
|
||||
height: 80px;
|
||||
box-shadow: 0 4px 16px rgba(33, 150, 243, 0.2);
|
||||
}
|
||||
|
||||
.header-icon {
|
||||
background: rgba(255, 255, 255, 0.15);
|
||||
border-radius: 20px;
|
||||
padding: 24px;
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
border-radius: 12px;
|
||||
padding: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@@ -179,21 +182,21 @@ const navigateToSettings = () => {
|
||||
}
|
||||
|
||||
.main-title {
|
||||
font-size: 48px;
|
||||
font-weight: 800;
|
||||
font-size: 32px;
|
||||
font-weight: 600;
|
||||
margin: 0;
|
||||
letter-spacing: 2px;
|
||||
color: var(--color-neutral-100);
|
||||
line-height: 1.2;
|
||||
text-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
|
||||
line-height: 40px;
|
||||
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
margin: 8px 0 0 0;
|
||||
font-size: 15px;
|
||||
font-weight: 400;
|
||||
margin: 2px 0 0 0;
|
||||
color: var(--color-neutral-100);
|
||||
opacity: 0.95;
|
||||
opacity: 0.9;
|
||||
line-height: 22px;
|
||||
}
|
||||
|
||||
.screens-grid {
|
||||
@@ -327,21 +330,28 @@ const navigateToSettings = () => {
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.screen-selection-container {
|
||||
padding: 20px;
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.selection-header {
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
padding: 32px 24px;
|
||||
flex-direction: row;
|
||||
padding: 12px 16px;
|
||||
height: auto;
|
||||
min-height: 64px;
|
||||
}
|
||||
|
||||
.header-icon {
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.main-title {
|
||||
font-size: 32px;
|
||||
font-size: 20px;
|
||||
line-height: 28px;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 16px;
|
||||
font-size: 13px;
|
||||
line-height: 18px;
|
||||
}
|
||||
|
||||
.screens-grid {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<!-- pages/Anjungan/AntrianKlinik/index.vue -->
|
||||
<template>
|
||||
<div class="screen-selection-container">
|
||||
<div>
|
||||
<div class="selection-header">
|
||||
<div class="header-icon">
|
||||
<v-icon size="64" color="white">mdi-monitor-multiple</v-icon>
|
||||
@@ -11,7 +11,8 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="screens-grid">
|
||||
<div class="screen-selection-container">
|
||||
<div class="screens-grid">
|
||||
<div
|
||||
v-for="screen in paginatedScreens"
|
||||
:key="screen.id"
|
||||
@@ -80,10 +81,11 @@
|
||||
</v-btn>
|
||||
</div>
|
||||
|
||||
<div v-else-if="totalPages > 1" class="pagination">
|
||||
<v-btn variant="outlined" @click="goPrev" :disabled="page <= 1">Prev</v-btn>
|
||||
<span class="page-info">Page {{ page }} / {{ totalPages }}</span>
|
||||
<v-btn variant="outlined" @click="goNext" :disabled="page >= totalPages">Next</v-btn>
|
||||
<div v-else-if="totalPages > 1" class="pagination">
|
||||
<v-btn variant="outlined" @click="goPrev" :disabled="page <= 1">Prev</v-btn>
|
||||
<span class="page-info">Page {{ page }} / {{ totalPages }}</span>
|
||||
<v-btn variant="outlined" @click="goNext" :disabled="page >= totalPages">Next</v-btn>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -150,26 +152,27 @@ const navigateToSettings = () => {
|
||||
<style scoped lang="scss">
|
||||
.screen-selection-container {
|
||||
background: var(--color-neutral-300);
|
||||
min-height: 100vh;
|
||||
padding: 40px;
|
||||
min-height: calc(100vh - 80px);
|
||||
padding: 16px;
|
||||
font-family: 'Inter', 'Roboto', sans-serif;
|
||||
}
|
||||
|
||||
.selection-header {
|
||||
background: linear-gradient(135deg, var(--color-primary-600) 0%, var(--color-primary-700) 100%);
|
||||
border-radius: 20px;
|
||||
padding: 40px;
|
||||
margin-bottom: 40px;
|
||||
border-radius: 0 !important;
|
||||
padding: 16px 28px;
|
||||
margin-bottom: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 32px;
|
||||
box-shadow: 0 8px 24px rgba(255, 155, 27, 0.3);
|
||||
gap: 16px;
|
||||
height: 80px;
|
||||
box-shadow: 0 4px 16px rgba(33, 150, 243, 0.2);
|
||||
}
|
||||
|
||||
.header-icon {
|
||||
background: rgba(255, 255, 255, 0.15);
|
||||
border-radius: 20px;
|
||||
padding: 24px;
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
border-radius: 12px;
|
||||
padding: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@@ -182,21 +185,21 @@ const navigateToSettings = () => {
|
||||
}
|
||||
|
||||
.main-title {
|
||||
font-size: 48px;
|
||||
font-weight: 800;
|
||||
font-size: 32px;
|
||||
font-weight: 600;
|
||||
margin: 0;
|
||||
letter-spacing: 2px;
|
||||
color: var(--color-neutral-100);
|
||||
line-height: 1.2;
|
||||
text-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
|
||||
line-height: 40px;
|
||||
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
margin: 8px 0 0 0;
|
||||
font-size: 15px;
|
||||
font-weight: 400;
|
||||
margin: 2px 0 0 0;
|
||||
color: var(--color-neutral-100);
|
||||
opacity: 0.95;
|
||||
opacity: 0.9;
|
||||
line-height: 22px;
|
||||
}
|
||||
|
||||
.screens-grid {
|
||||
@@ -330,21 +333,28 @@ const navigateToSettings = () => {
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.screen-selection-container {
|
||||
padding: 20px;
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.selection-header {
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
padding: 32px 24px;
|
||||
flex-direction: row;
|
||||
padding: 12px 16px;
|
||||
height: auto;
|
||||
min-height: 64px;
|
||||
}
|
||||
|
||||
.header-icon {
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.main-title {
|
||||
font-size: 32px;
|
||||
font-size: 20px;
|
||||
line-height: 28px;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 16px;
|
||||
font-size: 13px;
|
||||
line-height: 18px;
|
||||
}
|
||||
|
||||
.screens-grid {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<!-- pages/Anjungan/AntrianKlinikRuang/index.vue -->
|
||||
<template>
|
||||
<div class="klinik-selection-container">
|
||||
<div>
|
||||
<div class="selection-header">
|
||||
<div class="header-icon">
|
||||
<img
|
||||
@@ -15,7 +15,8 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="kliniks-grid" v-if="paginatedKliniks.length > 0">
|
||||
<div class="klinik-selection-container">
|
||||
<div class="kliniks-grid" v-if="paginatedKliniks.length > 0">
|
||||
<div
|
||||
v-for="klinik in paginatedKliniks"
|
||||
:key="klinik.kodeKlinik"
|
||||
@@ -84,10 +85,11 @@
|
||||
</v-btn>
|
||||
</div>
|
||||
|
||||
<div v-if="paginatedKliniks.length > 0 && totalPages > 1" class="pagination">
|
||||
<v-btn variant="outlined" @click="goPrev" :disabled="page <= 1">Prev</v-btn>
|
||||
<span class="page-info">Page {{ page }} / {{ totalPages }}</span>
|
||||
<v-btn variant="outlined" @click="goNext" :disabled="page >= totalPages">Next</v-btn>
|
||||
<div v-if="paginatedKliniks.length > 0 && totalPages > 1" class="pagination">
|
||||
<v-btn variant="outlined" @click="goPrev" :disabled="page <= 1">Prev</v-btn>
|
||||
<span class="page-info">Page {{ page }} / {{ totalPages }}</span>
|
||||
<v-btn variant="outlined" @click="goNext" :disabled="page >= totalPages">Next</v-btn>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -179,38 +181,37 @@ const navigateToSettings = () => {
|
||||
<style scoped lang="scss">
|
||||
.klinik-selection-container {
|
||||
background: var(--color-neutral-300);
|
||||
min-height: 100vh;
|
||||
padding: 40px;
|
||||
min-height: calc(100vh - 80px);
|
||||
padding: 16px;
|
||||
font-family: 'Inter', 'Roboto', sans-serif;
|
||||
}
|
||||
|
||||
.selection-header {
|
||||
background: linear-gradient(135deg, var(--color-primary-600) 0%, var(--color-primary-700) 100%);
|
||||
border-radius: 20px;
|
||||
padding: 40px;
|
||||
margin-bottom: 40px;
|
||||
border-radius: 0 !important;
|
||||
padding: 16px 28px;
|
||||
margin-bottom: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 32px;
|
||||
box-shadow: 0 8px 24px rgba(33, 150, 243, 0.3);
|
||||
gap: 16px;
|
||||
height: 80px;
|
||||
box-shadow: 0 4px 16px rgba(33, 150, 243, 0.2);
|
||||
}
|
||||
|
||||
.header-icon {
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
border-radius: 20px;
|
||||
padding: 24px;
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
border-radius: 12px;
|
||||
padding: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
backdrop-filter: blur(10px);
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.header-logo {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
width: 52px;
|
||||
height: 52px;
|
||||
object-fit: contain;
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.header-content {
|
||||
@@ -219,19 +220,21 @@ const navigateToSettings = () => {
|
||||
}
|
||||
|
||||
.main-title {
|
||||
font-size: 40px;
|
||||
font-weight: 800;
|
||||
font-size: 32px;
|
||||
font-weight: 600;
|
||||
margin: 0;
|
||||
color: var(--color-neutral-100);
|
||||
line-height: 1.2;
|
||||
line-height: 40px;
|
||||
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
margin: 8px 0 0 0;
|
||||
font-size: 15px;
|
||||
font-weight: 400;
|
||||
margin: 2px 0 0 0;
|
||||
color: var(--color-neutral-100);
|
||||
opacity: 0.95;
|
||||
opacity: 0.9;
|
||||
line-height: 22px;
|
||||
}
|
||||
|
||||
.kliniks-grid {
|
||||
@@ -365,21 +368,33 @@ const navigateToSettings = () => {
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.klinik-selection-container {
|
||||
padding: 20px;
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.selection-header {
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
padding: 32px 24px;
|
||||
flex-direction: row;
|
||||
padding: 12px 16px;
|
||||
height: auto;
|
||||
min-height: 64px;
|
||||
}
|
||||
|
||||
.header-icon {
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.header-logo {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.main-title {
|
||||
font-size: 32px;
|
||||
font-size: 20px;
|
||||
line-height: 28px;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 16px;
|
||||
font-size: 13px;
|
||||
line-height: 18px;
|
||||
}
|
||||
|
||||
.kliniks-grid {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<!-- pages/Anjungan/AntrianLoket/index.vue -->
|
||||
<template>
|
||||
<div class="loket-selection-container">
|
||||
<div>
|
||||
<div class="selection-header">
|
||||
<div class="header-icon">
|
||||
<img
|
||||
@@ -15,7 +15,8 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="paginatedLokets.length > 0" class="lokets-grid">
|
||||
<div class="loket-selection-container">
|
||||
<div v-if="paginatedLokets.length > 0" class="lokets-grid">
|
||||
<div
|
||||
v-for="loket in paginatedLokets"
|
||||
:key="loket.id"
|
||||
@@ -84,10 +85,11 @@
|
||||
</v-btn>
|
||||
</div>
|
||||
|
||||
<div v-if="paginatedLokets.length > 0 && totalPages > 1" class="pagination">
|
||||
<v-btn variant="outlined" :disabled="page <= 1" @click="goPrev">Prev</v-btn>
|
||||
<span class="page-info">Page {{ page }} / {{ totalPages }}</span>
|
||||
<v-btn variant="outlined" :disabled="page >= totalPages" @click="goNext">Next</v-btn>
|
||||
<div v-if="paginatedLokets.length > 0 && totalPages > 1" class="pagination">
|
||||
<v-btn variant="outlined" :disabled="page <= 1" @click="goPrev">Prev</v-btn>
|
||||
<span class="page-info">Page {{ page }} / {{ totalPages }}</span>
|
||||
<v-btn variant="outlined" :disabled="page >= totalPages" @click="goNext">Next</v-btn>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -161,38 +163,37 @@ const getKlinikName = (kode) => {
|
||||
<style scoped lang="scss">
|
||||
.loket-selection-container {
|
||||
background: var(--color-neutral-300);
|
||||
min-height: 100vh;
|
||||
padding: 40px;
|
||||
min-height: calc(100vh - 80px);
|
||||
padding: 16px;
|
||||
font-family: 'Inter', 'Roboto', sans-serif;
|
||||
}
|
||||
|
||||
.selection-header {
|
||||
background: linear-gradient(135deg, var(--color-primary-600) 0%, var(--color-primary-700) 100%);
|
||||
border-radius: 20px;
|
||||
padding: 40px;
|
||||
margin-bottom: 40px;
|
||||
border-radius: 0 !important;
|
||||
padding: 16px 28px;
|
||||
margin-bottom: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 32px;
|
||||
box-shadow: 0 8px 24px rgba(33, 150, 243, 0.3);
|
||||
gap: 16px;
|
||||
height: 80px;
|
||||
box-shadow: 0 4px 16px rgba(33, 150, 243, 0.2);
|
||||
}
|
||||
|
||||
.header-icon {
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
border-radius: 20px;
|
||||
padding: 24px;
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
border-radius: 12px;
|
||||
padding: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
backdrop-filter: blur(10px);
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.header-logo {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
width: 52px;
|
||||
height: 52px;
|
||||
object-fit: contain;
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.header-content {
|
||||
@@ -201,19 +202,21 @@ const getKlinikName = (kode) => {
|
||||
}
|
||||
|
||||
.main-title {
|
||||
font-size: 40px;
|
||||
font-weight: 800;
|
||||
font-size: 32px;
|
||||
font-weight: 600;
|
||||
margin: 0;
|
||||
color: var(--color-neutral-100);
|
||||
line-height: 1.2;
|
||||
line-height: 40px;
|
||||
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
margin: 8px 0 0 0;
|
||||
font-size: 15px;
|
||||
font-weight: 400;
|
||||
margin: 2px 0 0 0;
|
||||
color: var(--color-neutral-100);
|
||||
opacity: 0.95;
|
||||
opacity: 0.9;
|
||||
line-height: 22px;
|
||||
}
|
||||
|
||||
.lokets-grid {
|
||||
@@ -347,21 +350,33 @@ const getKlinikName = (kode) => {
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.loket-selection-container {
|
||||
padding: 20px;
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.selection-header {
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
padding: 32px 24px;
|
||||
flex-direction: row;
|
||||
padding: 12px 16px;
|
||||
height: auto;
|
||||
min-height: 64px;
|
||||
}
|
||||
|
||||
.header-icon {
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.header-logo {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.main-title {
|
||||
font-size: 32px;
|
||||
font-size: 20px;
|
||||
line-height: 28px;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 16px;
|
||||
font-size: 13px;
|
||||
line-height: 18px;
|
||||
}
|
||||
|
||||
.lokets-grid {
|
||||
|
||||
+30
-28
@@ -1,27 +1,28 @@
|
||||
<template>
|
||||
<v-container>
|
||||
<v-card>
|
||||
<!-- Header -->
|
||||
<div class="page-header">
|
||||
<div class="header-content">
|
||||
<div class="header-left">
|
||||
<div class="header-icon">
|
||||
<v-icon size="32" color="white">mdi-clipboard-plus</v-icon>
|
||||
</div>
|
||||
<div class="header-text">
|
||||
<h2 class="page-title">Buat Antrean</h2>
|
||||
<p class="page-subtitle">{{ currentDate }} - Manajemen Pembuatan Antrean</p>
|
||||
</div>
|
||||
<div>
|
||||
<!-- Header -->
|
||||
<div class="page-header">
|
||||
<div class="header-content">
|
||||
<div class="header-left">
|
||||
<div class="header-icon">
|
||||
<v-icon size="32" color="white">mdi-clipboard-plus</v-icon>
|
||||
</div>
|
||||
<div class="header-text">
|
||||
<h2 class="page-title">Buat Antrean</h2>
|
||||
<p class="page-subtitle">{{ currentDate }} - Manajemen Pembuatan Antrean</p>
|
||||
</div>
|
||||
<v-chip color="white" variant="flat" class="stat-chip">
|
||||
<v-icon start size="20" color="primary">mdi-account-multiple</v-icon>
|
||||
<span class="text-semibold">{{ totalPasien }} Pasien</span>
|
||||
</v-chip>
|
||||
</div>
|
||||
<v-chip color="white" variant="flat" class="stat-chip">
|
||||
<v-icon start size="20" color="primary">mdi-account-multiple</v-icon>
|
||||
<span class="text-semibold">{{ totalPasien }} Pasien</span>
|
||||
</v-chip>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Filters & Search -->
|
||||
<v-card-text class="pa-4">
|
||||
<v-container>
|
||||
<v-card>
|
||||
<!-- Filters & Search -->
|
||||
<v-card-text class="pa-4">
|
||||
<v-row dense class="mb-4">
|
||||
<v-col cols="12" md="8">
|
||||
<v-text-field
|
||||
@@ -327,14 +328,15 @@
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
|
||||
<!-- Snackbar -->
|
||||
<v-snackbar v-model="snackbar" :color="snackbarColor" :timeout="3000" location="top right">
|
||||
<span class="body-3">{{ snackbarText }}</span>
|
||||
<template #actions>
|
||||
<v-btn variant="text" size="small" @click="snackbar = false">Tutup</v-btn>
|
||||
</template>
|
||||
</v-snackbar>
|
||||
</v-container>
|
||||
<!-- Snackbar -->
|
||||
<v-snackbar v-model="snackbar" :color="snackbarColor" :timeout="3000" location="top right">
|
||||
<span class="body-3">{{ snackbarText }}</span>
|
||||
<template #actions>
|
||||
<v-btn variant="text" size="small" @click="snackbar = false">Tutup</v-btn>
|
||||
</template>
|
||||
</v-snackbar>
|
||||
</v-container>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
@@ -574,7 +576,7 @@ $font-weight-bold: 700;
|
||||
// ============================================
|
||||
.page-header {
|
||||
background: linear-gradient(135deg, $primary-600 0%, $primary-700 100%);
|
||||
border-radius: 16px 16px 0 0;
|
||||
border-radius: 0 !important;
|
||||
box-shadow: 0 4px 16px rgba(58, 97, 201, 0.2);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="checkin-page-container">
|
||||
<div>
|
||||
<!-- Page Header Component -->
|
||||
<PageHeader
|
||||
logo="/Rumah_Sakit_Umum_Daerah_Dr._Saiful_Anwar.webp"
|
||||
@@ -11,8 +11,9 @@
|
||||
@add-click="showGenerateQRDialog = true"
|
||||
/>
|
||||
|
||||
<!-- Main Content Grid - Same pattern as AdminLoket.vue -->
|
||||
<v-row class="content-grid" dense>
|
||||
<div class="checkin-page-container">
|
||||
<!-- Main Content Grid - Same pattern as AdminLoket.vue -->
|
||||
<v-row class="content-grid" dense>
|
||||
<!-- Left Column: QR Scan Card -->
|
||||
<v-col cols="12" md="6" lg="5" class="sticky-column">
|
||||
<div class="sticky-wrapper">
|
||||
@@ -1292,6 +1293,7 @@
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -1,37 +1,38 @@
|
||||
<template>
|
||||
<v-container>
|
||||
<v-card>
|
||||
<!-- Header -->
|
||||
<div class="page-header">
|
||||
<div class="header-content">
|
||||
<div class="header-left">
|
||||
<div class="header-icon">
|
||||
<v-icon size="32" color="white">mdi-heart-pulse</v-icon>
|
||||
</div>
|
||||
<div class="header-text">
|
||||
<h2 class="page-title">Monitoring Pasien</h2>
|
||||
<p class="page-subtitle">{{ currentDate }} - Manajemen Data Pasien</p>
|
||||
</div>
|
||||
<div>
|
||||
<!-- Header -->
|
||||
<div class="page-header">
|
||||
<div class="header-content">
|
||||
<div class="header-left">
|
||||
<div class="header-icon">
|
||||
<v-icon size="32" color="white">mdi-heart-pulse</v-icon>
|
||||
</div>
|
||||
<div class="header-stats">
|
||||
<v-chip color="white" variant="flat" class="stat-chip mr-2">
|
||||
<v-icon start size="16">mdi-account-group</v-icon>
|
||||
{{ totalCount }} Total
|
||||
</v-chip>
|
||||
<v-chip color="white" variant="flat" class="stat-chip mr-2">
|
||||
<v-icon start size="16">mdi-clock-outline</v-icon>
|
||||
{{ waitingCount }} Menunggu
|
||||
</v-chip>
|
||||
<v-chip color="white" variant="flat" class="stat-chip">
|
||||
<v-icon start size="16">mdi-check-circle</v-icon>
|
||||
{{ doneCount }} Selesai
|
||||
</v-chip>
|
||||
<div class="header-text">
|
||||
<h2 class="page-title">Monitoring Pasien</h2>
|
||||
<p class="page-subtitle">{{ currentDate }} - Manajemen Data Pasien</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="header-stats">
|
||||
<v-chip color="white" variant="flat" class="stat-chip mr-2">
|
||||
<v-icon start size="16">mdi-account-group</v-icon>
|
||||
{{ totalCount }} Total
|
||||
</v-chip>
|
||||
<v-chip color="white" variant="flat" class="stat-chip mr-2">
|
||||
<v-icon start size="16">mdi-clock-outline</v-icon>
|
||||
{{ waitingCount }} Menunggu
|
||||
</v-chip>
|
||||
<v-chip color="white" variant="flat" class="stat-chip">
|
||||
<v-icon start size="16">mdi-check-circle</v-icon>
|
||||
{{ doneCount }} Selesai
|
||||
</v-chip>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Status Filter Section -->
|
||||
<v-card-text class="filter-section-top">
|
||||
<v-container>
|
||||
<v-card>
|
||||
<!-- Status Filter Section -->
|
||||
<v-card-text class="filter-section-top">
|
||||
<div class="section-label">DATA PASIEN</div>
|
||||
|
||||
<v-chip-group
|
||||
@@ -192,7 +193,8 @@
|
||||
</v-data-table>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-container>
|
||||
</v-container>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
@@ -400,7 +402,7 @@ $font-weight-semibold: 600;
|
||||
// ============================================
|
||||
.page-header {
|
||||
background: linear-gradient(135deg, $primary-600 0%, $primary-700 100%);
|
||||
border-radius: 16px 16px 0 0;
|
||||
border-radius: 0 !important;
|
||||
box-shadow: 0 4px 16px rgba(58, 97, 201, 0.2);
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<div class="dialog-header-content">
|
||||
<div class="dialog-header-left">
|
||||
<v-avatar
|
||||
:color="viewMode === 'add' ? 'green-darken-2' : 'orange-darken-2'"
|
||||
color="primary-darken-1"
|
||||
size="56"
|
||||
class="dialog-avatar elevation-4"
|
||||
>
|
||||
@@ -287,7 +287,7 @@
|
||||
<div class="dialog-header-content">
|
||||
<div class="dialog-header-left">
|
||||
<v-avatar
|
||||
color="green-darken-2"
|
||||
color="primary-darken-1"
|
||||
size="56"
|
||||
class="dialog-avatar elevation-4"
|
||||
>
|
||||
@@ -332,7 +332,7 @@
|
||||
<div class="dialog-header-content">
|
||||
<div class="dialog-header-left">
|
||||
<v-avatar
|
||||
color="blue-darken-2"
|
||||
color="primary-darken-1"
|
||||
size="56"
|
||||
class="dialog-avatar elevation-4"
|
||||
>
|
||||
@@ -1911,19 +1911,19 @@ $font-weight-semibold: 600;
|
||||
}
|
||||
|
||||
.header-add {
|
||||
background: linear-gradient(135deg, #4caf50 0%, #2e7d32 100%);
|
||||
background: linear-gradient(135deg, $primary-600 0%, $primary-700 100%);
|
||||
}
|
||||
|
||||
.header-edit {
|
||||
background: linear-gradient(135deg, #ff9800 0%, #f57c00 100%);
|
||||
background: linear-gradient(135deg, $primary-600 0%, $primary-700 100%);
|
||||
}
|
||||
|
||||
.header-edit-access {
|
||||
background: linear-gradient(135deg, #009262 0%, #1B6E53 100%);
|
||||
background: linear-gradient(135deg, $primary-600 0%, $primary-700 100%);
|
||||
}
|
||||
|
||||
.header-view {
|
||||
background: linear-gradient(135deg, #2196f3 0%, #1976d2 100%);
|
||||
background: linear-gradient(135deg, $primary-600 0%, $primary-700 100%);
|
||||
}
|
||||
|
||||
.dialog-content {
|
||||
|
||||
@@ -1,30 +1,31 @@
|
||||
<template>
|
||||
<v-container>
|
||||
<v-card>
|
||||
<div class="page-header">
|
||||
<div class="header-content">
|
||||
<div class="header-left">
|
||||
<div class="header-icon">
|
||||
<v-icon size="32" color="white">mdi-monitor-dashboard</v-icon>
|
||||
</div>
|
||||
<div class="header-text">
|
||||
<h2 class="page-title">Master Anjungan</h2>
|
||||
<p class="page-subtitle">Kelola anjungan dan klinik yang ditampilkan</p>
|
||||
</div>
|
||||
<div>
|
||||
<div class="page-header">
|
||||
<div class="header-content">
|
||||
<div class="header-left">
|
||||
<div class="header-icon">
|
||||
<v-icon size="32" color="white">mdi-monitor-dashboard</v-icon>
|
||||
</div>
|
||||
<div class="header-text">
|
||||
<h2 class="page-title">Master Anjungan</h2>
|
||||
<p class="page-subtitle">Kelola anjungan dan klinik yang ditampilkan</p>
|
||||
</div>
|
||||
<v-btn
|
||||
color="white"
|
||||
@click="openTambahDialog"
|
||||
elevation="0"
|
||||
class="add-btn"
|
||||
>
|
||||
<v-icon left size="20">mdi-plus-circle</v-icon>
|
||||
Tambah Anjungan
|
||||
</v-btn>
|
||||
</div>
|
||||
<v-btn
|
||||
color="white"
|
||||
@click="openTambahDialog"
|
||||
elevation="0"
|
||||
class="add-btn"
|
||||
>
|
||||
<v-icon left size="20">mdi-plus-circle</v-icon>
|
||||
Tambah Anjungan
|
||||
</v-btn>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<v-card-text>
|
||||
<v-container>
|
||||
<v-card>
|
||||
<v-card-text>
|
||||
<v-data-table
|
||||
:headers="headers"
|
||||
:items="anjunganItems"
|
||||
@@ -137,7 +138,7 @@
|
||||
|
||||
<v-select
|
||||
label="Pilih Klinik"
|
||||
:items="clinicStore.getClinicsForDropdown(masterStore)"
|
||||
:items="availableClinics"
|
||||
v-model="formData.klinik"
|
||||
item-title="name"
|
||||
item-value="kode"
|
||||
@@ -149,6 +150,7 @@
|
||||
:rules="[v => v.length > 0 || 'Pilih minimal 1 klinik']"
|
||||
hide-details="auto"
|
||||
class="input-field"
|
||||
:disabled="!formData.jenisPasien"
|
||||
>
|
||||
<template v-slot:chip="{ props, item }">
|
||||
<v-chip
|
||||
@@ -223,17 +225,18 @@
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
|
||||
<v-snackbar v-model="snackbar.show" :color="snackbar.color" :timeout="3000">
|
||||
<span class="body-3">{{ snackbar.message }}</span>
|
||||
<template v-slot:actions>
|
||||
<v-btn variant="text" @click="snackbar.show = false" size="small">Tutup</v-btn>
|
||||
</template>
|
||||
</v-snackbar>
|
||||
</v-container>
|
||||
<v-snackbar v-model="snackbar.show" :color="snackbar.color" :timeout="3000">
|
||||
<span class="body-3">{{ snackbar.message }}</span>
|
||||
<template v-slot:actions>
|
||||
<v-btn variant="text" @click="snackbar.show = false" size="small">Tutup</v-btn>
|
||||
</template>
|
||||
</v-snackbar>
|
||||
</v-container>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue';
|
||||
import { ref, computed, onMounted, watch } from 'vue';
|
||||
import { useMasterStore } from '@/stores/masterStore';
|
||||
import { useAnjunganStore } from '@/stores/anjunganStore';
|
||||
import { useClinicStore } from '@/stores/clinicStore';
|
||||
@@ -264,7 +267,7 @@ const headers = ref([
|
||||
{ title: 'Actions', value: 'actions', sortable: false, width: 'auto' },
|
||||
]);
|
||||
|
||||
const jenisPasienOptions = ['Reguler', 'BPJS', 'Eksekutif'];
|
||||
const jenisPasienOptions = ['Reguler', 'Eksekutif'];
|
||||
|
||||
const anjunganItems = computed(() => anjunganStore.getAllAnjungan);
|
||||
|
||||
@@ -275,6 +278,21 @@ const formData = ref({
|
||||
klinik: [],
|
||||
});
|
||||
|
||||
// Computed property to get filtered clinics based on selected jenisPasien
|
||||
const availableClinics = computed(() => {
|
||||
if (!formData.value.jenisPasien) {
|
||||
return clinicStore.getClinicsForDropdown();
|
||||
}
|
||||
return clinicStore.getClinicsForDropdown(formData.value.jenisPasien);
|
||||
});
|
||||
|
||||
// Watch jenisPasien changes and clear clinic selection
|
||||
watch(() => formData.value.jenisPasien, (newVal, oldVal) => {
|
||||
if (newVal !== oldVal && oldVal !== '') {
|
||||
formData.value.klinik = [];
|
||||
}
|
||||
});
|
||||
|
||||
const openTambahDialog = () => {
|
||||
isEdit.value = false;
|
||||
resetForm();
|
||||
@@ -354,6 +372,11 @@ const closePreviewDialog = () => {
|
||||
previewItem.value = null;
|
||||
previewUrl.value = '';
|
||||
};
|
||||
|
||||
// Fetch Reguler clinics from API on mount
|
||||
onMounted(async () => {
|
||||
await clinicStore.fetchRegulerClinics();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@@ -390,7 +413,7 @@ $font-weight-semibold: 600;
|
||||
|
||||
.page-header {
|
||||
background: linear-gradient(135deg, $primary-600 0%, $primary-700 100%);
|
||||
border-radius: 16px 16px 0 0;
|
||||
border-radius: 0 !important;
|
||||
box-shadow: 0 4px 16px rgba(58, 97, 201, 0.2);
|
||||
}
|
||||
|
||||
|
||||
+546
-157
@@ -1,39 +1,49 @@
|
||||
<!-- eslint-disable vue/valid-v-slot -->
|
||||
<template>
|
||||
<v-container>
|
||||
<v-card>
|
||||
<!-- Header -->
|
||||
<div class="page-header">
|
||||
<div class="header-content">
|
||||
<div class="header-left">
|
||||
<div class="header-icon">
|
||||
<v-icon size="32" color="white">mdi-hospital-building</v-icon>
|
||||
</div>
|
||||
<div class="header-text">
|
||||
<h2 class="page-title">Master Klinik</h2>
|
||||
<p class="page-subtitle">Rabu, 13 Agustus 2025 - Manajemen Klinik</p>
|
||||
</div>
|
||||
<div>
|
||||
<!-- Header -->
|
||||
<div class="page-header">
|
||||
<div class="header-content">
|
||||
<div class="header-left">
|
||||
<div class="header-icon">
|
||||
<v-icon size="32" color="white">mdi-hospital-building</v-icon>
|
||||
</div>
|
||||
<div class="header-text">
|
||||
<h2 class="page-title">Master Klinik</h2>
|
||||
<p class="page-subtitle">Rabu, 13 Agustus 2025 - Manajemen Klinik</p>
|
||||
</div>
|
||||
<v-btn
|
||||
color="white"
|
||||
elevation="0"
|
||||
class="add-btn"
|
||||
@click="openTambahDialog"
|
||||
>
|
||||
<v-icon left size="20">mdi-plus-circle</v-icon>
|
||||
Tambah Klinik
|
||||
</v-btn>
|
||||
</div>
|
||||
<v-btn
|
||||
color="white"
|
||||
elevation="0"
|
||||
class="add-btn"
|
||||
@click="openTambahDialog"
|
||||
>
|
||||
<v-icon left size="20">mdi-plus-circle</v-icon>
|
||||
Tambah Klinik
|
||||
</v-btn>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Table -->
|
||||
<v-card-text>
|
||||
<v-container>
|
||||
<v-card>
|
||||
<!-- Table -->
|
||||
<v-card-text>
|
||||
<v-data-table
|
||||
:headers="headers"
|
||||
:items="masterStore.klinikData"
|
||||
:items-per-page="10"
|
||||
class="elevation-0 data-table"
|
||||
>
|
||||
<template #item.jenisLayanan="{ item }">
|
||||
<v-chip
|
||||
size="small"
|
||||
:class="item.jenisLayanan === 'Reguler' ? 'chip-reguler' : 'chip-eksekutif'"
|
||||
>
|
||||
{{ item.jenisLayanan }}
|
||||
</v-chip>
|
||||
</template>
|
||||
|
||||
<template #item.shift="{ item }">
|
||||
<v-chip size="small" class="chip-secondary">
|
||||
{{ item.shift }}
|
||||
@@ -89,7 +99,7 @@
|
||||
</div>
|
||||
|
||||
<v-row dense>
|
||||
<v-col cols="4">
|
||||
<v-col cols="6">
|
||||
<v-text-field
|
||||
v-model="formData.kode"
|
||||
label="Kode"
|
||||
@@ -102,7 +112,7 @@
|
||||
/>
|
||||
<small class="caption-2">2 Huruf</small>
|
||||
</v-col>
|
||||
<v-col cols="4">
|
||||
<v-col cols="6">
|
||||
<v-text-field
|
||||
v-model="formData.nama"
|
||||
label="Nama"
|
||||
@@ -114,19 +124,6 @@
|
||||
class="mb-3 input-field"
|
||||
/>
|
||||
</v-col>
|
||||
<v-col cols="4">
|
||||
<v-text-field
|
||||
v-model.number="formData.shift"
|
||||
label="Jumlah Shift"
|
||||
type="number"
|
||||
variant="outlined"
|
||||
density="compact"
|
||||
:rules="[v => !!v || 'Shift harus diisi', v => v > 0 || 'Minimal 1']"
|
||||
hide-details="auto"
|
||||
class="mb-3 input-field"
|
||||
@update:model-value="updateShiftCount"
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<v-checkbox
|
||||
@@ -141,87 +138,11 @@
|
||||
|
||||
<v-divider class="my-4 divider-section"/>
|
||||
|
||||
<!-- Konfigurasi Shift & Kuota -->
|
||||
<div class="field-group">
|
||||
<div class="group-label">
|
||||
<v-icon size="18" class="icon-label">mdi-clock-outline</v-icon>
|
||||
<span>Konfigurasi Shift & Kuota</span>
|
||||
</div>
|
||||
|
||||
<div v-for="(shift, index) in formData.jamShiftList" :key="index" class="shift-item">
|
||||
<v-row dense align="center">
|
||||
<v-col cols="2">
|
||||
<div class="shift-badge body-3">Shift {{ index + 1 }}</div>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="3">
|
||||
<v-text-field
|
||||
v-model="shift.dari"
|
||||
label="Mulai"
|
||||
type="time"
|
||||
variant="outlined"
|
||||
density="compact"
|
||||
hide-details
|
||||
class="input-field"
|
||||
/>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="3">
|
||||
<v-text-field
|
||||
v-model="shift.sampai"
|
||||
label="Selesai"
|
||||
type="time"
|
||||
variant="outlined"
|
||||
density="compact"
|
||||
hide-details
|
||||
class="input-field"
|
||||
/>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="3">
|
||||
<v-text-field
|
||||
v-model.number="shift.kuota"
|
||||
label="Kuota"
|
||||
type="number"
|
||||
variant="outlined"
|
||||
density="compact"
|
||||
hide-details
|
||||
placeholder="0"
|
||||
class="input-field"
|
||||
/>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="1">
|
||||
<v-btn
|
||||
v-if="formData.jamShiftList.length > 1"
|
||||
icon
|
||||
size="small"
|
||||
variant="text"
|
||||
class="btn-delete-shift"
|
||||
@click="removeShift(index)"
|
||||
>
|
||||
<v-icon size="18">mdi-delete</v-icon>
|
||||
</v-btn>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</div>
|
||||
|
||||
<div class="total-badge">
|
||||
<v-icon size="18" class="icon-success">mdi-sigma</v-icon>
|
||||
<span class="body-3 text-medium">Total Kuota:</span>
|
||||
<v-chip size="small" variant="flat" class="chip-success">
|
||||
{{ calculateTotalQuota() }}
|
||||
</v-chip>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<v-divider class="my-4 divider-section"/>
|
||||
|
||||
<!-- Jadwal Klinik -->
|
||||
<div class="field-group">
|
||||
<div class="group-label">
|
||||
<v-icon size="18" class="icon-label">mdi-calendar-check</v-icon>
|
||||
<span>Jadwal Klinik</span>
|
||||
<span>Pilih Hari Operasional</span>
|
||||
</div>
|
||||
|
||||
<div class="day-selector">
|
||||
@@ -238,6 +159,192 @@
|
||||
</v-chip>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<v-divider class="my-4 divider-section"/>
|
||||
|
||||
<!-- Konfigurasi Shift Per Hari -->
|
||||
<div v-if="formData.jadwalKlinik.length > 0" class="field-group">
|
||||
<div class="group-label">
|
||||
<v-icon size="18" class="icon-label">mdi-clock-outline</v-icon>
|
||||
<span>Konfigurasi Shift & Kuota Per Hari</span>
|
||||
</div>
|
||||
|
||||
<!-- Day Tabs -->
|
||||
<div class="day-tabs">
|
||||
<v-chip
|
||||
v-for="hari in formData.jadwalKlinik"
|
||||
:key="hari"
|
||||
class="day-tab"
|
||||
:class="{ 'day-tab-active': activeDay === hari }"
|
||||
@click="activeDay = hari"
|
||||
>
|
||||
{{ hari }}
|
||||
</v-chip>
|
||||
</div>
|
||||
|
||||
<!-- Shift Configuration for Active Day -->
|
||||
<div class="shifts-container">
|
||||
<div v-for="(shift, index) in getShiftsForDay(activeDay)" :key="index" class="shift-item">
|
||||
<v-row dense align="center">
|
||||
<v-col cols="2">
|
||||
<div class="shift-badge body-3">Shift {{ index + 1 }}</div>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="3">
|
||||
<v-text-field
|
||||
v-model="shift.dari"
|
||||
label="Mulai"
|
||||
type="time"
|
||||
variant="outlined"
|
||||
density="compact"
|
||||
hide-details
|
||||
class="input-field"
|
||||
/>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="3">
|
||||
<v-text-field
|
||||
v-model="shift.sampai"
|
||||
label="Selesai"
|
||||
type="time"
|
||||
variant="outlined"
|
||||
density="compact"
|
||||
hide-details
|
||||
class="input-field"
|
||||
/>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="3">
|
||||
<v-text-field
|
||||
v-model.number="shift.kuota"
|
||||
label="Kuota"
|
||||
type="number"
|
||||
variant="outlined"
|
||||
density="compact"
|
||||
hide-details
|
||||
placeholder="0"
|
||||
class="input-field"
|
||||
/>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="1">
|
||||
<v-btn
|
||||
v-if="getShiftsForDay(activeDay).length > 1"
|
||||
icon
|
||||
size="small"
|
||||
variant="text"
|
||||
class="btn-delete-shift"
|
||||
@click="removeShiftFromDay(activeDay, index)"
|
||||
>
|
||||
<v-icon size="18">mdi-delete</v-icon>
|
||||
</v-btn>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</div>
|
||||
|
||||
<v-btn
|
||||
size="small"
|
||||
variant="outlined"
|
||||
class="btn-add-shift mt-2"
|
||||
@click="addShiftToDay(activeDay)"
|
||||
>
|
||||
<v-icon left size="16">mdi-plus</v-icon>
|
||||
Tambah Shift untuk {{ activeDay }}
|
||||
</v-btn>
|
||||
</div>
|
||||
|
||||
<div class="total-badge mt-3">
|
||||
<v-icon size="18" class="icon-success">mdi-sigma</v-icon>
|
||||
<span class="body-3 text-medium">Total Kuota Semua Hari:</span>
|
||||
<v-chip size="small" variant="flat" class="chip-success">
|
||||
{{ calculateTotalQuota() }}
|
||||
</v-chip>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<v-divider class="my-4 divider-section"/>
|
||||
|
||||
<!-- Tanggal Tutup Klinik -->
|
||||
<div class="field-group">
|
||||
<div class="group-label">
|
||||
<v-icon size="18" class="icon-label">mdi-calendar-remove</v-icon>
|
||||
<span>Jadwal Tutup Klinik</span>
|
||||
</div>
|
||||
|
||||
<p class="helper-text mb-3">Tambahkan tanggal ketika klinik ini tutup (tidak beroperasi)</p>
|
||||
|
||||
<!-- Input Tanggal Tutup -->
|
||||
<v-row dense>
|
||||
<v-col cols="9">
|
||||
<v-text-field
|
||||
v-model="newClosedDate"
|
||||
type="date"
|
||||
label="Pilih Tanggal Tutup"
|
||||
variant="outlined"
|
||||
density="compact"
|
||||
hide-details
|
||||
:min="new Date().toISOString().split('T')[0]"
|
||||
class="input-field"
|
||||
/>
|
||||
</v-col>
|
||||
<v-col cols="3">
|
||||
<v-btn
|
||||
color="danger-600"
|
||||
class="text-white btn-add-closed-date"
|
||||
size="large"
|
||||
block
|
||||
@click="addClosedDate"
|
||||
:disabled="!newClosedDate"
|
||||
>
|
||||
<v-icon left size="18">mdi-plus</v-icon>
|
||||
Tambah
|
||||
</v-btn>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<!-- List Tanggal Tutup -->
|
||||
<div v-if="formData.tanggalTutup.length > 0" class="closed-dates-list mt-4">
|
||||
<div class="list-header">
|
||||
<span class="body-3 text-medium">{{ formData.tanggalTutup.length }} Hari Tutup</span>
|
||||
<v-btn
|
||||
size="x-small"
|
||||
variant="text"
|
||||
color="danger-600"
|
||||
@click="clearAllClosedDates"
|
||||
>
|
||||
<v-icon size="14">mdi-delete-sweep</v-icon>
|
||||
Hapus Semua
|
||||
</v-btn>
|
||||
</div>
|
||||
|
||||
<div class="closed-dates-items">
|
||||
<div
|
||||
v-for="(date, idx) in sortedClosedDates"
|
||||
:key="idx"
|
||||
class="closed-date-item"
|
||||
>
|
||||
<div class="date-info">
|
||||
<v-icon size="16" color="danger-600">mdi-calendar-remove</v-icon>
|
||||
<span class="date-text">{{ formatClosedDate(date) }}</span>
|
||||
</div>
|
||||
<v-btn
|
||||
icon
|
||||
size="x-small"
|
||||
variant="text"
|
||||
color="danger-600"
|
||||
@click="removeClosedDate(date)"
|
||||
>
|
||||
<v-icon size="16">mdi-close</v-icon>
|
||||
</v-btn>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-else class="empty-closed-dates mt-3">
|
||||
<v-icon size="24" color="neutral-600">mdi-calendar-check</v-icon>
|
||||
<p class="empty-text">Belum ada tanggal tutup</p>
|
||||
</div>
|
||||
</div>
|
||||
</v-form>
|
||||
</v-card-text>
|
||||
|
||||
@@ -265,21 +372,24 @@
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
|
||||
<!-- Snackbar -->
|
||||
<v-snackbar v-model="snackbar.show" :color="snackbar.color" :timeout="3000">
|
||||
<span class="body-3">{{ snackbar.message }}</span>
|
||||
<template #actions>
|
||||
<v-btn variant="text" size="small" @click="snackbar.show = false">Tutup</v-btn>
|
||||
</template>
|
||||
</v-snackbar>
|
||||
</v-container>
|
||||
<!-- Snackbar -->
|
||||
<v-snackbar v-model="snackbar.show" :color="snackbar.color" :timeout="3000">
|
||||
<span class="body-3">{{ snackbar.message }}</span>
|
||||
<template #actions>
|
||||
<v-btn variant="text" size="small" @click="snackbar.show = false">Tutup</v-btn>
|
||||
</template>
|
||||
</v-snackbar>
|
||||
</v-container>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
import { ref, computed, onMounted } from 'vue';
|
||||
import { useMasterStore } from '@/stores/masterStore';
|
||||
import { useClinicStore } from '@/stores/clinicStore';
|
||||
|
||||
const masterStore = useMasterStore();
|
||||
const clinicStore = useClinicStore();
|
||||
const dialog = ref(false);
|
||||
const isEdit = ref(false);
|
||||
const formRef = ref(null);
|
||||
@@ -294,6 +404,7 @@ const headers = ref([
|
||||
{ title: 'No', value: 'no', sortable: true },
|
||||
{ title: 'Kode', value: 'kode', sortable: true },
|
||||
{ title: 'Nama Klinik', value: 'nama', sortable: true },
|
||||
{ title: 'Jenis Layanan', value: 'jenisLayanan', sortable: true },
|
||||
{ title: 'Shift', value: 'shift', sortable: true },
|
||||
{ title: 'Total Kuota', value: 'totalQuota', sortable: true },
|
||||
{ title: 'Aksi', value: 'aksi', sortable: false },
|
||||
@@ -312,54 +423,133 @@ const formData = ref({
|
||||
kode: '',
|
||||
nama: '',
|
||||
shift: 1,
|
||||
jamShiftList: [{ dari: '07:00', sampai: '11:00', kuota: 0 }],
|
||||
jamShiftPerHari: {}, // { 'Senin': [{ dari, sampai, kuota }], ... }
|
||||
jamShiftList: [], // Legacy, kept for compatibility
|
||||
autoShift: false,
|
||||
jadwalKlinik: [],
|
||||
tanggalTutup: [], // Array of closed dates
|
||||
});
|
||||
|
||||
const calculateTotalQuota = () => {
|
||||
return formData.value.jamShiftList.reduce((total, shift) => {
|
||||
return total + (parseInt(shift.kuota) || 0);
|
||||
}, 0);
|
||||
const activeDay = ref('Senin'); // Currently selected day for shift configuration
|
||||
const newClosedDate = ref(''); // For adding new closed date
|
||||
|
||||
// Computed property for sorted closed dates
|
||||
const sortedClosedDates = computed(() => {
|
||||
return [...formData.value.tanggalTutup].sort((a, b) => new Date(a) - new Date(b));
|
||||
});
|
||||
|
||||
// Format closed date for display
|
||||
const formatClosedDate = (dateString) => {
|
||||
const date = new Date(dateString);
|
||||
return date.toLocaleDateString('id-ID', {
|
||||
weekday: 'long',
|
||||
day: 'numeric',
|
||||
month: 'long',
|
||||
year: 'numeric'
|
||||
});
|
||||
};
|
||||
|
||||
const updateShiftCount = (newShiftCount) => {
|
||||
const currentCount = formData.value.jamShiftList.length;
|
||||
// Add new closed date
|
||||
const addClosedDate = () => {
|
||||
if (!newClosedDate.value) return;
|
||||
|
||||
if (newShiftCount > currentCount) {
|
||||
for (let i = currentCount; i < newShiftCount; i++) {
|
||||
let defaultDari = '07:00', defaultSampai = '11:00';
|
||||
|
||||
if (i === 1) { defaultDari = '13:00'; defaultSampai = '16:00'; }
|
||||
else if (i === 2) { defaultDari = '18:00'; defaultSampai = '20:00'; }
|
||||
else if (i > 2) {
|
||||
const prevShift = formData.value.jamShiftList[i - 1];
|
||||
const [prevHour] = prevShift.sampai.split(':');
|
||||
const nextHour = (parseInt(prevHour) + 1) % 24;
|
||||
defaultDari = `${String(nextHour).padStart(2, '0')}:00`;
|
||||
defaultSampai = `${String((nextHour + 4) % 24).padStart(2, '0')}:00`;
|
||||
}
|
||||
|
||||
formData.value.jamShiftList.push({ dari: defaultDari, sampai: defaultSampai, kuota: 0 });
|
||||
}
|
||||
} else if (newShiftCount < currentCount && newShiftCount > 0) {
|
||||
formData.value.jamShiftList = formData.value.jamShiftList.slice(0, newShiftCount);
|
||||
// Check if date already exists
|
||||
if (formData.value.tanggalTutup.includes(newClosedDate.value)) {
|
||||
snackbar.value = {
|
||||
show: true,
|
||||
message: 'Tanggal sudah ada dalam daftar',
|
||||
color: 'warning'
|
||||
};
|
||||
return;
|
||||
}
|
||||
|
||||
formData.value.tanggalTutup.push(newClosedDate.value);
|
||||
newClosedDate.value = ''; // Reset input
|
||||
};
|
||||
|
||||
// Remove a specific closed date
|
||||
const removeClosedDate = (dateToRemove) => {
|
||||
const index = formData.value.tanggalTutup.findIndex(d => d === dateToRemove);
|
||||
if (index > -1) {
|
||||
formData.value.tanggalTutup.splice(index, 1);
|
||||
}
|
||||
};
|
||||
|
||||
// Clear all closed dates
|
||||
const clearAllClosedDates = () => {
|
||||
if (confirm('Hapus semua tanggal tutup?')) {
|
||||
formData.value.tanggalTutup = [];
|
||||
}
|
||||
};
|
||||
|
||||
const calculateTotalQuota = () => {
|
||||
let total = 0;
|
||||
Object.values(formData.value.jamShiftPerHari || {}).forEach(shifts => {
|
||||
if (Array.isArray(shifts)) {
|
||||
shifts.forEach(shift => {
|
||||
total += parseInt(shift.kuota) || 0;
|
||||
});
|
||||
}
|
||||
});
|
||||
return total;
|
||||
};
|
||||
|
||||
// Add shift for specific day
|
||||
const addShiftToDay = (hari) => {
|
||||
if (!formData.value.jamShiftPerHari[hari]) {
|
||||
formData.value.jamShiftPerHari[hari] = [];
|
||||
}
|
||||
|
||||
const shifts = formData.value.jamShiftPerHari[hari];
|
||||
let defaultDari = '07:00', defaultSampai = '11:00';
|
||||
|
||||
if (shifts.length === 1) { defaultDari = '13:00'; defaultSampai = '16:00'; }
|
||||
else if (shifts.length === 2) { defaultDari = '18:00'; defaultSampai = '20:00'; }
|
||||
else if (shifts.length > 2) {
|
||||
const prevShift = shifts[shifts.length - 1];
|
||||
const [prevHour]= prevShift.sampai.split(':');
|
||||
const nextHour = (parseInt(prevHour) + 1) % 24;
|
||||
defaultDari = `${String(nextHour).padStart(2, '0')}:00`;
|
||||
defaultSampai = `${String((nextHour + 4) % 24).padStart(2, '0')}:00`;
|
||||
}
|
||||
|
||||
shifts.push({ dari: defaultDari, sampai: defaultSampai, kuota: 0 });
|
||||
};
|
||||
|
||||
// Remove shift from specific day
|
||||
const removeShiftFromDay = (hari, index) => {
|
||||
if (formData.value.jamShiftPerHari[hari] && formData.value.jamShiftPerHari[hari].length > 1) {
|
||||
formData.value.jamShiftPerHari[hari].splice(index, 1);
|
||||
}
|
||||
};
|
||||
|
||||
// Get shifts for a specific day
|
||||
const getShiftsForDay = (hari) => {
|
||||
return formData.value.jamShiftPerHari[hari] || [];
|
||||
};
|
||||
|
||||
// Legacy compatibility methods
|
||||
const updateShiftCount = (newShiftCount) => {
|
||||
// This is kept for compatibility but not used with new per-day structure
|
||||
formData.value.shift = newShiftCount;
|
||||
};
|
||||
|
||||
const removeShift = (index) => {
|
||||
if (formData.value.jamShiftList.length > 1) {
|
||||
formData.value.jamShiftList.splice(index, 1);
|
||||
formData.value.shift = formData.value.jamShiftList.length;
|
||||
}
|
||||
// Legacy method, kept for compatibility
|
||||
};
|
||||
|
||||
const toggleHari = (hari) => {
|
||||
const index = formData.value.jadwalKlinik.indexOf(hari);
|
||||
if (index > -1) {
|
||||
// Removing day - also remove its shifts
|
||||
formData.value.jadwalKlinik.splice(index, 1);
|
||||
delete formData.value.jamShiftPerHari[hari];
|
||||
} else {
|
||||
// Adding day - initialize with one shift
|
||||
formData.value.jadwalKlinik.push(hari);
|
||||
if (!formData.value.jamShiftPerHari[hari]) {
|
||||
formData.value.jamShiftPerHari[hari] = [{ dari: '07:00', sampai: '11:00', kuota: 100 }];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -376,10 +566,13 @@ const openEditDialog = (item) => {
|
||||
kode: item.kode,
|
||||
nama: item.nama,
|
||||
shift: item.shift,
|
||||
jamShiftList: JSON.parse(JSON.stringify(item.jamShiftList)),
|
||||
jamShiftPerHari: JSON.parse(JSON.stringify(item.jamShiftPerHari || {})),
|
||||
jamShiftList: [], // Legacy
|
||||
autoShift: item.autoShift || false,
|
||||
jadwalKlinik: [...(item.jadwalKlinik || [])],
|
||||
tanggalTutup: [...(item.tanggalTutup || [])],
|
||||
};
|
||||
activeDay.value = formData.value.jadwalKlinik[0] || 'Senin';
|
||||
dialog.value = true;
|
||||
};
|
||||
|
||||
@@ -394,10 +587,14 @@ const resetForm = () => {
|
||||
kode: '',
|
||||
nama: '',
|
||||
shift: 1,
|
||||
jamShiftList: [{ dari: '07:00', sampai: '11:00', kuota: 0 }],
|
||||
jamShiftPerHari: {},
|
||||
jamShiftList: [],
|
||||
autoShift: false,
|
||||
jadwalKlinik: [],
|
||||
tanggalTutup: [],
|
||||
};
|
||||
activeDay.value = 'Senin';
|
||||
newClosedDate.value = '';
|
||||
if (formRef.value) {
|
||||
formRef.value.reset();
|
||||
}
|
||||
@@ -433,6 +630,42 @@ const handleDelete = (item) => {
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
// Fetch Reguler clinics from API on mount
|
||||
onMounted(async () => {
|
||||
console.log('🚀 MasterKlinik mounted, fetching clinics...');
|
||||
console.log('📊 Current clinic count before fetch:', masterStore.klinikData.length);
|
||||
|
||||
try {
|
||||
const result = await clinicStore.fetchRegulerClinics();
|
||||
console.log('✅ Fetch result:', result);
|
||||
console.log('📊 Current clinic count after fetch:', masterStore.klinikData.length);
|
||||
|
||||
if (!result.success) {
|
||||
console.error('❌ Fetch failed:', result.message);
|
||||
snackbar.value = {
|
||||
show: true,
|
||||
message: result.message,
|
||||
color: 'warning'
|
||||
};
|
||||
} else {
|
||||
console.log('🎉 Fetch successful:', result.message);
|
||||
// Show success message
|
||||
snackbar.value = {
|
||||
show: true,
|
||||
message: result.message,
|
||||
color: 'success'
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('❌ Error in onMounted:', error);
|
||||
snackbar.value = {
|
||||
show: true,
|
||||
message: `Error: ${error.message}`,
|
||||
color: 'error'
|
||||
};
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@@ -481,7 +714,7 @@ $font-weight-bold: 700;
|
||||
// ============================================
|
||||
.page-header {
|
||||
background: linear-gradient(135deg, $primary-600 0%, $primary-700 100%);
|
||||
border-radius: 16px 16px 0 0;
|
||||
border-radius: 0 !important;
|
||||
box-shadow: 0 4px 16px rgba(58, 97, 201, 0.2);
|
||||
}
|
||||
|
||||
@@ -546,6 +779,18 @@ $font-weight-bold: 700;
|
||||
font-weight: $font-weight-medium;
|
||||
}
|
||||
|
||||
.chip-reguler {
|
||||
background-color: $success-600 !important;
|
||||
color: $neutral-100 !important;
|
||||
font-weight: $font-weight-medium;
|
||||
}
|
||||
|
||||
.chip-eksekutif {
|
||||
background-color: $secondary-600 !important;
|
||||
color: $neutral-100 !important;
|
||||
font-weight: $font-weight-medium;
|
||||
}
|
||||
|
||||
.btn-edit {
|
||||
background-color: $primary-600 !important;
|
||||
color: $neutral-100 !important;
|
||||
@@ -744,6 +989,150 @@ $font-weight-bold: 700;
|
||||
border-color: $secondary-600 !important;
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// DAY TABS (for shift configuration)
|
||||
// ============================================
|
||||
.day-tabs {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
margin-bottom: 16px;
|
||||
padding: 12px;
|
||||
background: $neutral-300;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.day-tab {
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
font-weight: $font-weight-medium;
|
||||
border: 1px solid $neutral-500;
|
||||
background-color: $neutral-100;
|
||||
color: $neutral-800;
|
||||
|
||||
&:hover {
|
||||
background-color: $primary-600;
|
||||
color: $neutral-100;
|
||||
}
|
||||
}
|
||||
|
||||
.day-tab-active {
|
||||
background-color: $primary-600 !important;
|
||||
color: $neutral-100 !important;
|
||||
border-color: $primary-600 !important;
|
||||
}
|
||||
|
||||
.shifts-container {
|
||||
padding: 12px;
|
||||
background: $neutral-300;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.btn-add-shift {
|
||||
border-color: $primary-600 !important;
|
||||
color: $primary-600 !important;
|
||||
text-transform: none;
|
||||
font-weight: $font-weight-semibold;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// CLOSED DATES
|
||||
// ============================================
|
||||
.helper-text {
|
||||
font-size: 13px;
|
||||
line-height: 18px;
|
||||
font-weight: $font-weight-regular;
|
||||
color: $neutral-700;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.btn-add-closed-date {
|
||||
height: 40px !important;
|
||||
font-weight: $font-weight-semibold;
|
||||
text-transform: none;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.closed-dates-list {
|
||||
background: $neutral-300;
|
||||
border-radius: 8px;
|
||||
border: 1px solid $neutral-400;
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.list-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding-bottom: 8px;
|
||||
margin-bottom: 8px;
|
||||
border-bottom: 1px solid $neutral-400;
|
||||
|
||||
span {
|
||||
color: $neutral-800;
|
||||
}
|
||||
}
|
||||
|
||||
.closed-dates-items {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.closed-date-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 10px 12px;
|
||||
background: $neutral-100;
|
||||
border-radius: 6px;
|
||||
border: 1px solid $neutral-400;
|
||||
transition: all 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
border-color: $danger-400;
|
||||
background: rgba($danger-600, 0.02);
|
||||
}
|
||||
}
|
||||
|
||||
.date-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.date-text {
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
font-weight: $font-weight-medium;
|
||||
color: $neutral-800;
|
||||
}
|
||||
|
||||
.empty-closed-dates {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 24px 16px;
|
||||
background: $neutral-300;
|
||||
border-radius: 8px;
|
||||
border: 1px dashed $neutral-500;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-size: 13px;
|
||||
line-height: 18px;
|
||||
font-weight: $font-weight-medium;
|
||||
color: $neutral-700;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// BUTTONS
|
||||
// ============================================
|
||||
|
||||
@@ -1,42 +1,43 @@
|
||||
<template>
|
||||
<v-container>
|
||||
<v-card>
|
||||
<!-- Header -->
|
||||
<div class="page-header">
|
||||
<div class="header-content">
|
||||
<div class="header-left">
|
||||
<div class="header-icon">
|
||||
<v-icon size="32" color="white">mdi-door-open</v-icon>
|
||||
</div>
|
||||
<div class="header-text">
|
||||
<h2 class="page-title">Master Klinik Ruang</h2>
|
||||
<p class="page-subtitle">Rabu, 13 Agustus 2025 - Manajemen Ruangan</p>
|
||||
</div>
|
||||
<div>
|
||||
<!-- Header -->
|
||||
<div class="page-header">
|
||||
<div class="header-content">
|
||||
<div class="header-left">
|
||||
<div class="header-icon">
|
||||
<v-icon size="32" color="white">mdi-door-open</v-icon>
|
||||
</div>
|
||||
<div class="header-stats">
|
||||
<v-chip color="white" variant="flat" class="stat-chip mr-2">
|
||||
<v-icon start size="16">mdi-hospital-building</v-icon>
|
||||
{{ masterStore.totalKlinikRuang }} Klinik
|
||||
</v-chip>
|
||||
<v-chip color="white" variant="flat" class="stat-chip mr-2">
|
||||
<v-icon start size="16">mdi-door</v-icon>
|
||||
{{ masterStore.totalRuangan }} Ruang
|
||||
</v-chip>
|
||||
<v-btn
|
||||
color="white"
|
||||
@click="openTambahDialog"
|
||||
elevation="0"
|
||||
class="add-btn"
|
||||
>
|
||||
<v-icon left size="20">mdi-plus-circle</v-icon>
|
||||
Tambah Ruang
|
||||
</v-btn>
|
||||
<div class="header-text">
|
||||
<h2 class="page-title">Master Klinik Ruang</h2>
|
||||
<p class="page-subtitle">Rabu, 13 Agustus 2025 - Manajemen Ruangan</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="header-stats">
|
||||
<v-chip color="white" variant="flat" class="stat-chip mr-2">
|
||||
<v-icon start size="16">mdi-hospital-building</v-icon>
|
||||
{{ masterStore.totalKlinikRuang }} Klinik
|
||||
</v-chip>
|
||||
<v-chip color="white" variant="flat" class="stat-chip mr-2">
|
||||
<v-icon start size="16">mdi-door</v-icon>
|
||||
{{ masterStore.totalRuangan }} Ruang
|
||||
</v-chip>
|
||||
<v-btn
|
||||
color="white"
|
||||
@click="openTambahDialog"
|
||||
elevation="0"
|
||||
class="add-btn"
|
||||
>
|
||||
<v-icon left size="20">mdi-plus-circle</v-icon>
|
||||
Tambah Ruang
|
||||
</v-btn>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Table -->
|
||||
<v-card-text>
|
||||
<v-container>
|
||||
<v-card>
|
||||
<!-- Table -->
|
||||
<v-card-text>
|
||||
<v-data-table
|
||||
:headers="headers"
|
||||
:items="masterStore.ruangData"
|
||||
@@ -264,14 +265,15 @@
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
|
||||
<!-- Snackbar -->
|
||||
<v-snackbar v-model="snackbar.show" :color="snackbar.color" :timeout="3000">
|
||||
<span class="body-3">{{ snackbar.message }}</span>
|
||||
<template v-slot:actions>
|
||||
<v-btn variant="text" @click="snackbar.show = false" size="small">Tutup</v-btn>
|
||||
</template>
|
||||
</v-snackbar>
|
||||
</v-container>
|
||||
<!-- Snackbar -->
|
||||
<v-snackbar v-model="snackbar.show" :color="snackbar.color" :timeout="3000">
|
||||
<span class="body-3">{{ snackbar.message }}</span>
|
||||
<template v-slot:actions>
|
||||
<v-btn variant="text" @click="snackbar.show = false" size="small">Tutup</v-btn>
|
||||
</template>
|
||||
</v-snackbar>
|
||||
</v-container>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
@@ -459,7 +461,7 @@ $font-weight-semibold: 600;
|
||||
// ============================================
|
||||
.page-header {
|
||||
background: linear-gradient(135deg, $primary-600 0%, $primary-700 100%);
|
||||
border-radius: 16px 16px 0 0;
|
||||
border-radius: 0 !important;
|
||||
box-shadow: 0 4px 16px rgba(58, 97, 201, 0.2);
|
||||
}
|
||||
|
||||
@@ -729,7 +731,7 @@ $font-weight-semibold: 600;
|
||||
}
|
||||
|
||||
.preview-dialog-header {
|
||||
background: linear-gradient(135deg, $success-600 0%, $success-700 100%);
|
||||
background: linear-gradient(135deg, $primary-600 0%, $primary-700 100%);
|
||||
color: $neutral-100;
|
||||
padding: 16px 24px;
|
||||
display: flex;
|
||||
|
||||
@@ -1,33 +1,34 @@
|
||||
<!-- eslint-disable vue/valid-v-slot -->
|
||||
<template>
|
||||
<v-container>
|
||||
<v-card>
|
||||
<!-- Header -->
|
||||
<div class="page-header">
|
||||
<div class="header-content">
|
||||
<div class="header-left">
|
||||
<div class="header-icon">
|
||||
<v-icon size="32" color="white">mdi-view-dashboard</v-icon>
|
||||
</div>
|
||||
<div class="header-text">
|
||||
<h2 class="page-title">Master Loket</h2>
|
||||
<p class="page-subtitle">Rabu, 13 Agustus 2025 - Pelayanan</p>
|
||||
</div>
|
||||
<div>
|
||||
<!-- Header -->
|
||||
<div class="page-header">
|
||||
<div class="header-content">
|
||||
<div class="header-left">
|
||||
<div class="header-icon">
|
||||
<v-icon size="32" color="white">mdi-view-dashboard</v-icon>
|
||||
</div>
|
||||
<div class="header-text">
|
||||
<h2 class="page-title">Master Loket</h2>
|
||||
<p class="page-subtitle">Rabu, 13 Agustus 2025 - Pelayanan</p>
|
||||
</div>
|
||||
<v-btn
|
||||
color="white"
|
||||
elevation="0"
|
||||
class="add-btn"
|
||||
@click="openTambahDialog"
|
||||
>
|
||||
<v-icon left size="20">mdi-plus-circle</v-icon>
|
||||
Tambah Loket
|
||||
</v-btn>
|
||||
</div>
|
||||
<v-btn
|
||||
color="white"
|
||||
elevation="0"
|
||||
class="add-btn"
|
||||
@click="openTambahDialog"
|
||||
>
|
||||
<v-icon left size="20">mdi-plus-circle</v-icon>
|
||||
Tambah Loket
|
||||
</v-btn>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Table -->
|
||||
<v-card-text>
|
||||
<v-container>
|
||||
<v-card>
|
||||
<!-- Table -->
|
||||
<v-card-text>
|
||||
<v-data-table
|
||||
:headers="loketHeaders"
|
||||
:items="masterStore.loketData"
|
||||
@@ -240,14 +241,15 @@
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
|
||||
<!-- Snackbar -->
|
||||
<v-snackbar v-model="snackbar.show" :color="snackbar.color" :timeout="3000">
|
||||
<span class="body-3">{{ snackbar.message }}</span>
|
||||
<template #actions>
|
||||
<v-btn variant="text" size="small" @click="snackbar.show = false">Tutup</v-btn>
|
||||
</template>
|
||||
</v-snackbar>
|
||||
</v-container>
|
||||
<!-- Snackbar -->
|
||||
<v-snackbar v-model="snackbar.show" :color="snackbar.color" :timeout="3000">
|
||||
<span class="body-3">{{ snackbar.message }}</span>
|
||||
<template #actions>
|
||||
<v-btn variant="text" size="small" @click="snackbar.show = false">Tutup</v-btn>
|
||||
</template>
|
||||
</v-snackbar>
|
||||
</v-container>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
@@ -396,7 +398,7 @@ $font-weight-semibold: 600;
|
||||
// ============================================
|
||||
.page-header {
|
||||
background: linear-gradient(135deg, $primary-600 0%, $primary-700 100%);
|
||||
border-radius: 16px 16px 0 0;
|
||||
border-radius: 0 !important;
|
||||
box-shadow: 0 4px 16px rgba(58, 97, 201, 0.2);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,33 +1,34 @@
|
||||
<!-- pages/MasterPenunjang.vue -->
|
||||
<template>
|
||||
<v-container>
|
||||
<v-card>
|
||||
<!-- Header -->
|
||||
<div class="page-header">
|
||||
<div class="header-content">
|
||||
<div class="header-left">
|
||||
<div class="header-icon">
|
||||
<v-icon size="32" color="white">mdi-clipboard-pulse</v-icon>
|
||||
</div>
|
||||
<div class="header-text">
|
||||
<h2 class="page-title">Master Penunjang</h2>
|
||||
<p class="page-subtitle">Rabu, 13 Agustus 2025 - Manajemen Layanan Penunjang</p>
|
||||
</div>
|
||||
<div>
|
||||
<!-- Header -->
|
||||
<div class="page-header">
|
||||
<div class="header-content">
|
||||
<div class="header-left">
|
||||
<div class="header-icon">
|
||||
<v-icon size="32" color="white">mdi-clipboard-pulse</v-icon>
|
||||
</div>
|
||||
<div class="header-text">
|
||||
<h2 class="page-title">Master Penunjang</h2>
|
||||
<p class="page-subtitle">Rabu, 13 Agustus 2025 - Manajemen Layanan Penunjang</p>
|
||||
</div>
|
||||
<v-btn
|
||||
color="white"
|
||||
elevation="0"
|
||||
class="add-btn"
|
||||
@click="openTambahDialog"
|
||||
>
|
||||
<v-icon left size="20">mdi-plus-circle</v-icon>
|
||||
Tambah Penunjang
|
||||
</v-btn>
|
||||
</div>
|
||||
<v-btn
|
||||
color="white"
|
||||
elevation="0"
|
||||
class="add-btn"
|
||||
@click="openTambahDialog"
|
||||
>
|
||||
<v-icon left size="20">mdi-plus-circle</v-icon>
|
||||
Tambah Penunjang
|
||||
</v-btn>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Table -->
|
||||
<v-card-text>
|
||||
<v-container>
|
||||
<v-card>
|
||||
<!-- Table -->
|
||||
<v-card-text>
|
||||
<v-data-table
|
||||
:headers="headers"
|
||||
:items="masterStore.penunjangData"
|
||||
@@ -334,14 +335,15 @@
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
|
||||
<!-- Snackbar -->
|
||||
<v-snackbar v-model="snackbar.show" :color="snackbar.color" :timeout="3000">
|
||||
<span class="body-3">{{ snackbar.message }}</span>
|
||||
<template #actions>
|
||||
<v-btn variant="text" size="small" @click="snackbar.show = false">Tutup</v-btn>
|
||||
</template>
|
||||
</v-snackbar>
|
||||
</v-container>
|
||||
<!-- Snackbar -->
|
||||
<v-snackbar v-model="snackbar.show" :color="snackbar.color" :timeout="3000">
|
||||
<span class="body-3">{{ snackbar.message }}</span>
|
||||
<template #actions>
|
||||
<v-btn variant="text" size="small" @click="snackbar.show = false">Tutup</v-btn>
|
||||
</template>
|
||||
</v-snackbar>
|
||||
</v-container>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
@@ -559,7 +561,7 @@ $font-weight-bold: 700;
|
||||
// ============================================
|
||||
.page-header {
|
||||
background: linear-gradient(135deg, $primary-600 0%, $primary-700 100%);
|
||||
border-radius: 16px 16px 0 0;
|
||||
border-radius: 0 !important;
|
||||
box-shadow: 0 4px 16px rgba(58, 97, 201, 0.2);
|
||||
}
|
||||
|
||||
|
||||
+35
-33
@@ -1,32 +1,33 @@
|
||||
<template>
|
||||
<v-container>
|
||||
<v-card>
|
||||
<!-- Header -->
|
||||
<div class="page-header">
|
||||
<div class="header-content">
|
||||
<div class="header-left">
|
||||
<div class="header-icon">
|
||||
<v-icon size="32" color="white">mdi-monitor</v-icon>
|
||||
</div>
|
||||
<div class="header-text">
|
||||
<h2 class="page-title">Screen Configuration</h2>
|
||||
<p class="page-subtitle">Rabu, 13 Agustus 2025 - Konfigurasi Layar Antrian</p>
|
||||
</div>
|
||||
<div>
|
||||
<!-- Header -->
|
||||
<div class="page-header">
|
||||
<div class="header-content">
|
||||
<div class="header-left">
|
||||
<div class="header-icon">
|
||||
<v-icon size="32" color="white">mdi-monitor</v-icon>
|
||||
</div>
|
||||
<div class="header-text">
|
||||
<h2 class="page-title">Screen Configuration</h2>
|
||||
<p class="page-subtitle">Rabu, 13 Agustus 2025 - Konfigurasi Layar Antrian</p>
|
||||
</div>
|
||||
<v-btn
|
||||
color="white"
|
||||
@click="openTambahDialog"
|
||||
elevation="0"
|
||||
class="add-btn"
|
||||
>
|
||||
<v-icon left size="20">mdi-plus-circle</v-icon>
|
||||
Tambah Screen
|
||||
</v-btn>
|
||||
</div>
|
||||
<v-btn
|
||||
color="white"
|
||||
@click="openTambahDialog"
|
||||
elevation="0"
|
||||
class="add-btn"
|
||||
>
|
||||
<v-icon left size="20">mdi-plus-circle</v-icon>
|
||||
Tambah Screen
|
||||
</v-btn>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Table -->
|
||||
<v-card-text>
|
||||
<v-container>
|
||||
<v-card>
|
||||
<!-- Table -->
|
||||
<v-card-text>
|
||||
<v-data-table
|
||||
:headers="headers"
|
||||
:items="screenItems"
|
||||
@@ -255,14 +256,15 @@
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
|
||||
<!-- Snackbar -->
|
||||
<v-snackbar v-model="snackbar.show" :color="snackbar.color" :timeout="3000">
|
||||
<span class="body-3">{{ snackbar.message }}</span>
|
||||
<template v-slot:actions>
|
||||
<v-btn variant="text" @click="snackbar.show = false" size="small">Tutup</v-btn>
|
||||
</template>
|
||||
</v-snackbar>
|
||||
</v-container>
|
||||
<!-- Snackbar -->
|
||||
<v-snackbar v-model="snackbar.show" :color="snackbar.color" :timeout="3000">
|
||||
<span class="body-3">{{ snackbar.message }}</span>
|
||||
<template v-slot:actions>
|
||||
<v-btn variant="text" @click="snackbar.show = false" size="small">Tutup</v-btn>
|
||||
</template>
|
||||
</v-snackbar>
|
||||
</v-container>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
@@ -428,7 +430,7 @@ $font-weight-semibold: 600;
|
||||
// ============================================
|
||||
.page-header {
|
||||
background: linear-gradient(135deg, $primary-600 0%, $primary-700 100%);
|
||||
border-radius: 16px 16px 0 0;
|
||||
border-radius: 0 !important;
|
||||
box-shadow: 0 4px 16px rgba(58, 97, 201, 0.2);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="screen-list">
|
||||
<div>
|
||||
<!-- Header -->
|
||||
<div class="page-header">
|
||||
<div class="header-content">
|
||||
@@ -22,7 +22,9 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- <div class="d-flex justify-space-between align-center mb-4">
|
||||
|
||||
<div class="screen-list">
|
||||
<!-- <div class="d-flex justify-space-between align-center mb-4">
|
||||
<h2>Screen</h2>
|
||||
<div class="d-flex gap-2">
|
||||
<v-btn color="primary" variant="outlined" prepend-icon="mdi-eye">
|
||||
@@ -113,6 +115,7 @@
|
||||
:total-visible="5"
|
||||
></v-pagination>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -196,8 +199,8 @@ const editScreen = (item) => {
|
||||
<style scoped>
|
||||
.page-header {
|
||||
background: linear-gradient(135deg, #1976d2 0%, #1565c0 100%);
|
||||
border-radius: 16px;
|
||||
margin-bottom: 24px;
|
||||
border-radius: 0 !important;
|
||||
margin-bottom: 0;
|
||||
box-shadow: 0 8px 32px rgba(25, 118, 210, 0.3);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,32 +1,33 @@
|
||||
<template>
|
||||
<v-container>
|
||||
<v-card>
|
||||
<!-- Header -->
|
||||
<div class="page-header">
|
||||
<div class="header-content">
|
||||
<div class="header-left">
|
||||
<div class="header-icon">
|
||||
<v-icon size="32" color="white">mdi-ticket-account</v-icon>
|
||||
</div>
|
||||
<div class="header-text">
|
||||
<h2 class="page-title">Screen Antrean Masuk</h2>
|
||||
<p class="page-subtitle">Rabu, 13 Agustus 2025 - Konfigurasi Layar Antrean Masuk</p>
|
||||
</div>
|
||||
<div>
|
||||
<!-- Header -->
|
||||
<div class="page-header">
|
||||
<div class="header-content">
|
||||
<div class="header-left">
|
||||
<div class="header-icon">
|
||||
<v-icon size="32" color="white">mdi-ticket-account</v-icon>
|
||||
</div>
|
||||
<div class="header-text">
|
||||
<h2 class="page-title">Screen Antrean Masuk</h2>
|
||||
<p class="page-subtitle">Rabu, 13 Agustus 2025 - Konfigurasi Layar Antrean Masuk</p>
|
||||
</div>
|
||||
<v-btn
|
||||
color="white"
|
||||
@click="openTambahDialog"
|
||||
elevation="0"
|
||||
class="add-btn"
|
||||
>
|
||||
<v-icon left size="20">mdi-plus-circle</v-icon>
|
||||
Tambah Screen
|
||||
</v-btn>
|
||||
</div>
|
||||
<v-btn
|
||||
color="white"
|
||||
@click="openTambahDialog"
|
||||
elevation="0"
|
||||
class="add-btn"
|
||||
>
|
||||
<v-icon left size="20">mdi-plus-circle</v-icon>
|
||||
Tambah Screen
|
||||
</v-btn>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Table -->
|
||||
<v-card-text>
|
||||
<v-container>
|
||||
<v-card>
|
||||
<!-- Table -->
|
||||
<v-card-text>
|
||||
<v-data-table
|
||||
:headers="headers"
|
||||
:items="antreanMasukScreenItems"
|
||||
@@ -255,14 +256,15 @@
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
|
||||
<!-- Snackbar -->
|
||||
<v-snackbar v-model="snackbar.show" :color="snackbar.color" :timeout="3000">
|
||||
<span class="body-3">{{ snackbar.message }}</span>
|
||||
<template v-slot:actions>
|
||||
<v-btn variant="text" @click="snackbar.show = false" size="small">Tutup</v-btn>
|
||||
</template>
|
||||
</v-snackbar>
|
||||
</v-container>
|
||||
<!-- Snackbar -->
|
||||
<v-snackbar v-model="snackbar.show" :color="snackbar.color" :timeout="3000">
|
||||
<span class="body-3">{{ snackbar.message }}</span>
|
||||
<template v-slot:actions>
|
||||
<v-btn variant="text" @click="snackbar.show = false" size="small">Tutup</v-btn>
|
||||
</template>
|
||||
</v-snackbar>
|
||||
</v-container>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
@@ -463,7 +465,7 @@ $font-weight-semibold: 600;
|
||||
// ============================================
|
||||
.page-header {
|
||||
background: linear-gradient(135deg, $primary-600 0%, $primary-700 100%);
|
||||
border-radius: 16px 16px 0 0;
|
||||
border-radius: 0 !important;
|
||||
box-shadow: 0 4px 16px rgba(58, 97, 201, 0.2);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,29 +1,30 @@
|
||||
<template>
|
||||
<v-container>
|
||||
<v-card class="form-card" elevation="0">
|
||||
<!-- Header -->
|
||||
<div class="form-header">
|
||||
<div class="header-content">
|
||||
<div class="header-left">
|
||||
<v-btn
|
||||
icon
|
||||
variant="text"
|
||||
color="white"
|
||||
@click="cancelAdd"
|
||||
class="back-btn"
|
||||
>
|
||||
<v-icon>mdi-arrow-left</v-icon>
|
||||
</v-btn>
|
||||
<div class="header-text">
|
||||
<h1 class="page-title">Tambah Loket Baru</h1>
|
||||
<p class="page-subtitle">Isi form untuk menambahkan loket</p>
|
||||
</div>
|
||||
<div>
|
||||
<!-- Header -->
|
||||
<div class="form-header">
|
||||
<div class="header-content">
|
||||
<div class="header-left">
|
||||
<v-btn
|
||||
icon
|
||||
variant="text"
|
||||
color="white"
|
||||
@click="cancelAdd"
|
||||
class="back-btn"
|
||||
>
|
||||
<v-icon>mdi-arrow-left</v-icon>
|
||||
</v-btn>
|
||||
<div class="header-text">
|
||||
<h1 class="page-title">Tambah Loket Baru</h1>
|
||||
<p class="page-subtitle">Isi form untuk menambahkan loket</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Form Content -->
|
||||
<v-card-text class="form-content">
|
||||
<v-container>
|
||||
<v-card class="form-card" elevation="0">
|
||||
<!-- Form Content -->
|
||||
<v-card-text class="form-content">
|
||||
<v-form @submit.prevent="simpanLoket" ref="formRef">
|
||||
<!-- Section: Informasi Loket -->
|
||||
<div class="form-section">
|
||||
@@ -139,7 +140,8 @@
|
||||
</v-form>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-container>
|
||||
</v-container>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
@@ -220,6 +222,7 @@ const cancelAdd = () => {
|
||||
.form-header {
|
||||
background: linear-gradient(135deg, #1976d2 0%, #1565c0 100%);
|
||||
box-shadow: 0 4px 16px rgba(25, 118, 210, 0.2);
|
||||
border-radius: 0 !important;
|
||||
}
|
||||
|
||||
.header-content {
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
:class="readOnly ? 'bg-blue-darken-3' : isEditMode ? 'bg-orange-darken-2' : 'bg-green-darken-2'"
|
||||
>
|
||||
<v-avatar
|
||||
:color="readOnly ? 'blue-darken-4' : isEditMode ? 'orange-darken-3' : 'green-darken-3'"
|
||||
color="primary-darken-1"
|
||||
size="48"
|
||||
class="mr-3 elevation-2"
|
||||
>
|
||||
@@ -1205,15 +1205,15 @@ $font-weight-semibold: 600;
|
||||
}
|
||||
|
||||
.dialog-header.bg-blue-darken-3 {
|
||||
background: linear-gradient(135deg, #1976d2 0%, #1565c0 100%);
|
||||
background: linear-gradient(135deg, var(--v-theme-primary) 0%, var(--v-theme-primary-darken-1) 100%);
|
||||
}
|
||||
|
||||
.dialog-header.bg-orange-darken-2 {
|
||||
background: linear-gradient(135deg, #f57c00 0%, #e65100 100%);
|
||||
background: linear-gradient(135deg, var(--v-theme-primary) 0%, var(--v-theme-primary-darken-1) 100%);
|
||||
}
|
||||
|
||||
.dialog-header.bg-green-darken-2 {
|
||||
background: linear-gradient(135deg, #388e3c 0%, #2e7d32 100%);
|
||||
background: linear-gradient(135deg, var(--v-theme-primary) 0%, var(--v-theme-primary-darken-1) 100%);
|
||||
}
|
||||
|
||||
// ============================================
|
||||
|
||||
@@ -7,15 +7,15 @@ export const useAnjunganStore = defineStore('anjungan', () => {
|
||||
const anjunganItems = ref([
|
||||
{
|
||||
id: 1,
|
||||
namaAnjungan: 'Anjungan',
|
||||
namaAnjungan: 'Anjungan Reguler',
|
||||
jenisPasien: 'Reguler',
|
||||
klinik: ['AN', 'AS', 'BD', 'GR', 'HO','GI', 'GZ','IP', 'JT', 'JW', 'KK', 'MT', 'OB', 'PR', 'RT', 'RM', 'SR'],
|
||||
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 BPJS',
|
||||
jenisPasien: 'BPJS',
|
||||
klinik: ['AN', 'AS', 'OB', 'PR', 'RT', 'RM', 'SR'],
|
||||
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'],
|
||||
},
|
||||
]);
|
||||
|
||||
|
||||
+723
-299
File diff suppressed because it is too large
Load diff
+27
-1
@@ -22,7 +22,23 @@ export const useMasterStore = defineStore('master', () => {
|
||||
// Ini untuk backward compatibility dengan pages yang masih menggunakan masterStore.klinikData
|
||||
// Format: { id, no, kode, nama, shift, totalQuota, jamShiftList, autoShift, jadwalKlinik }
|
||||
const klinikData = computed(() => {
|
||||
return clinicStore.clinics.map((clinic, index) => {
|
||||
console.log('📊 masterStore.klinikData computed called');
|
||||
console.log('📊 clinicStore.clinics.length:', clinicStore.clinics.length);
|
||||
|
||||
// Sort by kode first, then by jenisLayanan (Reguler before Eksekutif)
|
||||
const sortedClinics = [...clinicStore.clinics].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('📊 sortedClinics.length:', sortedClinics.length);
|
||||
|
||||
const result = sortedClinics.map((clinic, index) => {
|
||||
// Convert clinic data ke format masterStore (untuk backward compatibility)
|
||||
return {
|
||||
id: clinic.id,
|
||||
@@ -32,10 +48,20 @@ export const useMasterStore = defineStore('master', () => {
|
||||
shift: typeof clinic.shift === 'string' ? parseInt(clinic.shift.replace(/[^0-9]/g, '')) || 1 : clinic.shift || 1,
|
||||
totalQuota: clinic.totalQuota || 0,
|
||||
jamShiftList: clinic.jamShiftList || [],
|
||||
jamShiftPerHari: clinic.jamShiftPerHari || {}, // New: shift configuration per day
|
||||
autoShift: clinic.autoShift || false,
|
||||
jadwalKlinik: clinic.jadwalKlinik || [],
|
||||
tanggalTutup: clinic.tanggalTutup || [], // New: array of closed dates
|
||||
jenisLayanan: clinic.jenisLayanan || 'Reguler',
|
||||
};
|
||||
});
|
||||
|
||||
console.log('📊 masterStore.klinikData result:', result.length, 'items');
|
||||
if (result.length > 0) {
|
||||
console.log('📋 First item:', result[0]);
|
||||
}
|
||||
|
||||
return result;
|
||||
});
|
||||
|
||||
// Computed - Get klinik list for dropdowns
|
||||
|
||||
Reference in New Issue
Block a user