update API master LOKET dan antrian LOKET

This commit is contained in:
Fanrouver
2026-01-23 08:24:14 +07:00
parent 8de89cb579
commit 75a96389fe
6 changed files with 1116 additions and 211 deletions

No files matched your search

+7 -7
View File
@@ -43,7 +43,7 @@
<template #item.jenisLayanan="{ item }">
<v-chip
size="small"
:class="item.jenisLayanan === 'Reguler' ? 'chip-reguler' : 'chip-eksekutif'"
:class="['Reguler', 'REGULER', 'JKN'].includes(item.jenisLayanan) ? 'chip-reguler' : 'chip-eksekutif'"
>
{{ item.jenisLayanan }}
</v-chip>
@@ -808,15 +808,15 @@ $font-weight-bold: 700;
}
.chip-reguler {
background-color: $success-600 !important;
color: $neutral-100 !important;
font-weight: $font-weight-medium;
background-color: #009262 !important;
color: #FFFFFF !important;
font-weight: 500;
}
.chip-eksekutif {
background-color: $secondary-600 !important;
color: $neutral-100 !important;
font-weight: $font-weight-medium;
background-color: #E67E22 !important;
color: #FFFFFF !important;
font-weight: 500;
}
.btn-edit {
+232 -16
View File
@@ -36,7 +36,7 @@
<v-card-text>
<v-data-table
:headers="loketHeaders"
:items="masterStore.loketData"
:items="loketStore.loketData"
:items-per-page="10"
class="elevation-0 data-table"
>
@@ -47,7 +47,7 @@
size="small"
class="mr-1 mb-1 chip-primary-outline"
>
{{ masterStore.getKlinikNameByKode(serviceKode) }}
{{ getServiceName(item, serviceKode) }}
</v-chip>
<v-chip
v-if="item.pelayanan.length > 2"
@@ -58,7 +58,37 @@
</v-chip>
</template>
<template #item.pembayaran="{ item }">
<v-chip
size="small"
:class="['JKN', 'Reguler', 'REGULER'].includes(item.pembayaran) ? 'chip-reguler' : 'chip-eksekutif'"
>
{{ item.pembayaran }}
</v-chip>
</template>
<template #item.loketAktif="{ item }">
<v-chip
size="small"
:color="item.loketAktif ? 'success' : 'error'"
variant="flat"
class="text-capitalize"
>
{{ item.loketAktif ? 'Aktif' : 'Tidak Aktif' }}
</v-chip>
</template>
<template #item.aksi="{ item }">
<v-btn
size="small"
@click="openPreview(item)"
variant="flat"
class="btn-preview mr-2"
>
<v-icon size="16" left>mdi-eye</v-icon>
Preview
</v-btn>
<v-btn
size="small"
variant="flat"
@@ -146,7 +176,7 @@
<v-select
v-model="formData.pembayaran"
label="Pembayaran"
:items="['JKN', 'UMUM', 'SPM', 'JKMM', 'JAMPERSAL', 'T4', 'KARYAWAN']"
:items="['JKN', 'UMUM', 'EKSEKUTIF', 'SPM', 'JKMM', 'JAMPERSAL', 'T4', 'KARYAWAN']"
variant="outlined"
density="compact"
:rules="[v => !!v || 'Pembayaran harus dipilih']"
@@ -155,15 +185,13 @@
/>
</v-col>
<v-col cols="6">
<v-select
v-model="formData.keterangan"
label="Keterangan"
:items="['ONLINE', 'OFFLINE']"
variant="outlined"
<v-switch
v-model="formData.loketAktif"
:label="formData.loketAktif ? 'Loket Aktif' : 'Loket Tidak Aktif'"
color="success"
hide-details
density="compact"
:rules="[v => !!v || 'Keterangan harus dipilih']"
hide-details="auto"
class="input-field"
class="mt-1"
/>
</v-col>
</v-row>
@@ -246,6 +274,34 @@
</v-card>
</v-dialog>
<!-- Preview Dialog -->
<v-dialog v-model="previewDialog" max-width="95vw" max-height="95vh" persistent>
<v-card class="preview-dialog-card">
<v-card-title class="preview-dialog-header">
<div class="preview-header-content">
<v-icon size="24" class="mr-2">mdi-view-dashboard</v-icon>
<span class="headline-4">Preview Loket: {{ previewItem?.namaLoket || '' }}</span>
</div>
<v-btn icon variant="text" size="small" class="btn-close" @click="closePreviewDialog">
<v-icon>mdi-close</v-icon>
</v-btn>
</v-card-title>
<v-divider></v-divider>
<v-card-text class="preview-content pa-0">
<iframe
v-if="previewItem"
:src="previewUrl"
class="preview-iframe"
frameborder="0"
></iframe>
<div v-else class="preview-loading">
<v-progress-circular indeterminate color="primary"></v-progress-circular>
<p class="mt-4">Memuat preview...</p>
</div>
</v-card-text>
</v-card>
</v-dialog>
<!-- Snackbar -->
<v-snackbar v-model="snackbar.show" :color="snackbar.color" :timeout="3000">
<span class="body-3">{{ snackbar.message }}</span>
@@ -258,11 +314,15 @@
</template>
<script setup>
import { ref } from 'vue';
import { ref, onMounted, computed } from 'vue';
import { useMasterStore } from '@/stores/masterStore';
import { useLoketStore } from '@/stores/loketStore';
const masterStore = useMasterStore();
const loketStore = useLoketStore();
const dialog = ref(false);
const previewDialog = ref(false);
const previewItem = ref(null);
const isEdit = ref(false);
const formRef = ref(null);
@@ -272,13 +332,73 @@ const snackbar = ref({
color: 'success'
});
// Helper untuk mendapatkan nama layanan/klinik
const getServiceName = (item, serviceId) => {
// 1. Coba cari di _spesialisDetail (data dari API)
// Convert ID ke string untuk perbandingan aman
const idStr = String(serviceId);
if (item._spesialisDetail && Array.isArray(item._spesialisDetail)) {
const detail = item._spesialisDetail.find(s => String(s.idklinik) === idStr);
if (detail && detail.namaklinik) {
return detail.namaklinik;
}
}
// 2. Fallback ke masterStore.getKlinikById untuk local data (numeric ID seperti 1000, 1001)
// Local data EKSEKUTIF menggunakan ID numeric, bukan kode
const byId = masterStore.getKlinikById(serviceId);
if (byId && byId.nama) return byId.nama;
// 3. Fallback ke masterStore.getKlinikNameByKode untuk data yang pakai kode
return masterStore.getKlinikNameByKode(serviceId);
};
// Auto-fetch loket dari API saat component mount
onMounted(async () => {
console.log('🚀 MasterLoket mounted, fetching loket dari API...');
try {
const result = await loketStore.fetchLoketFromAPI(true); // force = true untuk fresh data
console.log('📊 Fetch result:', result);
console.log('📊 loketStore.loketData length:', loketStore.loketData.value?.length);
console.log('📊 loketStore.loketData:', loketStore.loketData.value);
if (result.success) {
console.log('✅ Fetch successful:', result.message);
snackbar.value = {
show: true,
message: result.message,
color: 'success'
};
} else {
console.error('❌ Fetch failed:', result.message);
snackbar.value = {
show: true,
message: result.message,
color: 'warning'
};
}
} catch (error) {
console.error('❌ Error in onMounted:', error);
snackbar.value = {
show: true,
message: `Error: ${error.message}`,
color: 'error'
};
}
});
const loketHeaders = ref([
{ title: "No", value: "no" },
{ title: "ID", value: "id" },
{ title: "Nama Loket", value: "namaLoket" },
{ title: "Kuota", value: "kuota" },
{ title: "Pelayanan", value: "pelayanan" },
{ title: "Pembayaran", value: "pembayaran" },
{ title: "Keterangan", value: "keterangan" },
{ title: "Status Loket Aktif", value: "loketAktif" },
{ title: "Aksi", value: "aksi", sortable: false },
]);
@@ -288,8 +408,8 @@ const formData = ref({
kuota: null,
statusPelayanan: '',
pembayaran: '',
keterangan: '',
pelayanan: [],
loketAktif: true,
});
const openTambahDialog = () => {
@@ -352,6 +472,22 @@ const submitForm = async () => {
}
};
// Computed property untuk preview URL
const previewUrl = computed(() => {
if (!previewItem.value) return '';
return `/anjungan/antrianloket/${previewItem.value.id}`;
});
const openPreview = (item) => {
previewItem.value = item;
previewDialog.value = true;
};
const closePreviewDialog = () => {
previewDialog.value = false;
previewItem.value = null;
};
const handleDelete = (item) => {
if (confirm(`Hapus loket ${item.namaLoket}?`)) {
const result = masterStore.deleteLoket(item.id);
@@ -365,7 +501,7 @@ const handleDelete = (item) => {
</script>
<style scoped lang="scss">
// Colors from Design System
/* Colors from Design System */
$neutral-900: #212121;
$neutral-800: #4D4D4D;
$neutral-700: #717171;
@@ -478,11 +614,20 @@ $font-weight-semibold: 600;
line-height: 24px;
}
// ============================================
// DATA TABLE
// ============================================
.data-table {
font-family: $font-family-base;
// Vertically center-align table headers
:deep(th) {
vertical-align: middle !important;
}
// Vertically center-align table cells
:deep(td) {
vertical-align: middle !important;
}
}
.chip-primary-outline {
@@ -502,6 +647,18 @@ $font-weight-semibold: 600;
line-height: 16px;
}
.chip-reguler {
background-color: #009262 !important;
color: #FFFFFF !important;
font-weight: 500;
}
.chip-eksekutif {
background-color: #E67E22 !important;
color: #FFFFFF !important;
font-weight: 500;
}
.btn-edit {
background-color: $primary-600 !important;
color: $neutral-100 !important;
@@ -511,6 +668,15 @@ $font-weight-semibold: 600;
line-height: 20px;
}
.btn-preview {
background-color: $secondary-600 !important;
color: $neutral-100 !important;
text-transform: none;
font-weight: $font-weight-semibold;
font-size: 14px;
line-height: 20px;
}
.btn-delete {
border-color: $danger-600 !important;
color: $danger-600 !important;
@@ -664,4 +830,54 @@ $font-weight-semibold: 600;
line-height: 24px;
min-width: 100px;
}
// ============================================
// PREVIEW DIALOG
// ============================================
.preview-dialog-card {
font-family: $font-family-base;
height: 95vh;
display: flex;
flex-direction: column;
}
.preview-dialog-header {
background: linear-gradient(135deg, $primary-600 0%, $primary-700 100%);
color: $neutral-100;
padding: 16px 24px;
display: flex;
justify-content: space-between;
align-items: center;
}
.preview-header-content {
display: flex;
align-items: center;
}
.preview-content {
padding: 0 !important;
flex: 1;
display: flex;
align-items: center;
justify-content: center;
background: $neutral-800;
overflow: auto;
}
.preview-iframe {
width: 100%;
height: 75vh;
min-height: 600px;
border: none;
}
.preview-loading {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
color: $neutral-100;
padding: 40px;
}
</style>