new components

This commit is contained in:
bagus-arie05
2025-08-25 14:40:28 +07:00
parent b1c1ff86f0
commit 1f5b25a873
5 changed files with 569 additions and 1106 deletions

189
components/TabelData.vue Normal file
View File

@@ -0,0 +1,189 @@
<template>
<v-card-text>
<v-toolbar flat color="transparent" dense>
<v-toolbar-title class="text-subtitle-1 font-weight-bold" :class="{ 'red--text': title.includes('TERLAMBAT') }">
{{ title }}
</v-toolbar-title>
<v-spacer></v-spacer>
<div class="d-flex align-center">
<span>Show</span>
<v-select
:items="[10, 25, 50, 100]"
v-model="itemsPerPage"
label="entries"
dense
single-line
hide-details
class="shrink mx-2"
style="width: 80px"
></v-select>
<span>entries</span>
</div>
<div v-if="showSearch" class="d-flex align-center ml-4">
<span class="mr-2">Search:</span>
<v-text-field
v-model="search"
label="Search"
hide-details
density="compact"
style="min-width: 200px"
></v-text-field>
</div>
</v-toolbar>
<v-data-table
:headers="headers"
:items="paginatedItems"
:search="search"
no-data-text="No data available in table"
hide-default-footer
class="elevation-1"
>
<template v-slot:item.aksi="{ item }">
<slot name="actions" :item="item">
</slot>
</template>
</v-data-table>
<div class="d-flex justify-space-between align-center pt-4">
<div class="text-body-2 text-grey-darken-1">
Showing {{ currentPageStart }} to {{ currentPageEnd }} of {{ totalEntries }} entries
</div>
<div class="d-flex align-center">
<v-btn
:disabled="currentPage === 1"
@click="previousPage"
variant="text"
size="small"
class="text-body-2"
>
Previous
</v-btn>
<template v-for="page in visiblePages" :key="page">
<v-btn
v-if="page !== '...'"
:color="page === currentPage ? 'primary' : ''"
:variant="page === currentPage ? 'flat' : 'text'"
@click="goToPage(page)"
size="small"
class="mx-1"
min-width="40"
>
{{ page }}
</v-btn>
<span v-else class="mx-1">...</span>
</template>
<v-btn
:disabled="currentPage === totalPages"
@click="nextPage"
variant="text"
size="small"
class="text-body-2"
>
Next
</v-btn>
</div>
</div>
</v-card-text>
</template>
<script setup>
import { ref, computed, watch } from "vue";
import { defineProps } from "vue";
const props = defineProps({
headers: {
type: Array,
required: true,
},
items: {
type: Array,
required: true,
},
title: {
type: String,
default: "",
},
showSearch: {
type: Boolean,
default: true,
}
});
const search = ref("");
const itemsPerPage = ref(10);
const currentPage = ref(1);
const totalEntries = computed(() => props.items.length);
const totalPages = computed(() => Math.ceil(totalEntries.value / itemsPerPage.value));
const paginatedItems = computed(() => {
const start = (currentPage.value - 1) * itemsPerPage.value;
const end = start + itemsPerPage.value;
return props.items.slice(start, end);
});
const currentPageStart = computed(() => (currentPage.value - 1) * itemsPerPage.value + 1);
const currentPageEnd = computed(() => Math.min(currentPage.value * itemsPerPage.value, totalEntries.value));
const visiblePages = computed(() => {
const pages = [];
const total = totalPages.value;
const current = currentPage.value;
if (total <= 7) {
for (let i = 1; i <= total; i++) {
pages.push(i);
}
} else {
if (current <= 4) {
for (let i = 1; i <= 5; i++) {
pages.push(i);
}
pages.push("...");
pages.push(total);
} else if (current >= total - 3) {
pages.push(1);
pages.push("...");
for (let i = total - 4; i <= total; i++) {
pages.push(i);
}
} else {
pages.push(1);
pages.push("...");
for (let i = current - 1; i <= current + 1; i++) {
pages.push(i);
}
pages.push("...");
pages.push(total);
}
}
return pages;
});
const previousPage = () => {
if (currentPage.value > 1) {
currentPage.value--;
}
};
const nextPage = () => {
if (currentPage.value < totalPages.value) {
currentPage.value++;
}
};
const goToPage = (page) => {
currentPage.value = page;
};
// Reset halaman ke 1 saat jumlah itemsPerPage berubah
watch(itemsPerPage, () => {
currentPage.value = 1;
});
// Reset halaman ke 1 saat data items berubah (misalnya, setelah filter atau fetch data baru)
watch(() => props.items, () => {
currentPage.value = 1;
});
</script>

View File

@@ -152,7 +152,7 @@ const currentActiveMenu = computed(() => {
<style scoped>
.v-list-item--active {
background-color: var(--v-theme-primary);
color: #fff !important;
background-color: #ffffff;
color: #000000 !important;
}
</style>

318
pages/Loket-Admin.vue Normal file
View File

@@ -0,0 +1,318 @@
<template>
<v-app>
<v-app-bar>
<v-container fluid class="pa-4">
<v-row align="center" no-gutters class="fill-height">
<v-col cols="auto">
<div class="d-flex align-center">
<div
class="bg-grey-darken-4 px-3 py-1 mr-2"
style="border-radius: 3px"
>
<span class="text-body-1 font-weight-bold text-white"
>Total 2</span
>
</div>
<div
class="bg-grey-darken-2 px-3 py-1"
style="border-radius: 3px"
>
<span class="text-body-1 text-white">Max 150 Pasien</span>
</div>
</div>
</v-col>
<v-spacer />
<v-col cols="auto">
<div class="d-flex align-center text-body-2">
<v-icon size="small" class="mr-2">mdi-view-dashboard</v-icon>
<span class="mr-6">Dashboard</span>
<span>Loket 12 | Rabu, 13 Agustus 2025 - Pelayanan</span>
</div>
</v-col>
</v-row>
</v-container>
</v-app-bar>
<div class="bg-cyan" style="height: 3px" />
<v-main>
<v-container fluid class="pa-6">
<v-row class="mb-6">
<v-col>
<div class="text-h6 text-grey-darken-2 font-weight-medium">
Loket 12 | Rabu, 13 Agustus 2025 - Pelayanan :
</div>
</v-col>
<v-spacer />
<v-col cols="auto">
<div class="d-flex align-center">
<v-btn
color="success"
dark
size="large"
class="mr-4 px-8"
style="min-width: 120px; height: 40px"
>
<span class="text-h6 font-weight-bold">1</span>
</v-btn>
<v-btn
color="info"
dark
size="large"
class="mr-4 px-4"
style="min-width: 120px; height: 40px"
>
<span class="text-h6 font-weight-bold">5</span>
</v-btn>
<v-btn
color="warning"
dark
size="large"
class="mr-4 px-4"
style="min-width: 120px; height: 40px"
>
<span class="text-h6 font-weight-bold">10</span>
</v-btn>
<v-btn
color="error"
dark
size="large"
class="px-4"
style="min-width: 120px; height: 40px"
>
<span class="text-h6 font-weight-bold">20</span>
</v-btn>
</div>
</v-col>
</v-row>
<v-row justify="center" class="pa-12">
<v-col cols="12" md="10" lg="8">
<v-card
color="success"
dark
flat
class="text-center"
style="min-height: 160px; border-radius: 8px"
>
<v-card-text class="pa-8">
<div
class="text-h2 font-weight-bold mb-2"
style="letter-spacing: 4px"
>
NEXT
</div>
<div class="text-h6 mb-4 font-weight-normal">
Pasien - UM1004
</div>
<div
class="text-body-1 font-weight-normal"
style="opacity: 0.9"
>
Klik untuk memanggil pasien selanjutnya
</div>
</v-card-text>
</v-card>
</v-col>
</v-row>
<v-card class="mb-4">
<TabelData
:headers="mainHeaders"
:items="mainPatients"
title="DATA PASIEN"
>
<template #actions="{ item }">
<div class="d-flex ga-1">
<v-btn size="small" color="success" variant="flat">Panggil</v-btn>
<v-btn size="small" color="info" variant="flat">Cancel</v-btn>
<v-btn size="small" color="primary" variant="flat">Selesai</v-btn>
</div>
</template>
<template #item.jamPanggil="{ item }">
<span :class="getRowClass(item)">{{ item.jamPanggil }}</span>
</template>
</TabelData>
</v-card>
<v-card color="cyan" dark class="mb-4">
<v-card-text class="text-center">
<div class="text-h6">Total Quota Terpakai 5</div>
</v-card-text>
</v-card>
<v-card class="mb-4">
<TabelData
:headers="lateHeaders"
:items="latePatients"
title="INFO PASIEN LAPOR TERLAMBAT"
:show-search="false"
>
</TabelData>
</v-card>
<v-card class="mb-4">
<TabelData
:headers="clinicHeaders"
:items="clinicPatients"
title="INFO PASIEN MASUK KLINIK"
>
</TabelData>
</v-card>
</v-container>
</v-main>
</v-app>
</template>
<script setup lang="ts">
import { ref } from "vue";
import TabelData from "../components/TabelData.vue";
// Define interfaces
interface Patient {
no: number;
jamPanggil: string;
barcode: string;
noAntrian: string;
shift: string;
klinik: string;
fastTrack: string;
pembayaran: string;
panggil: string;
status: string;
}
interface LatePatient {
no: number;
barcode: string;
noAntrian: string;
shift: string;
klinik: string;
aksi: string;
}
interface ClinicPatient {
no: number;
barcode: string;
noAntrian: string;
noRM: string;
shift: string;
klinik: string;
fastTrack: string;
pembayaran: string;
aksi: string;
}
// Data and Headers
const mainHeaders = ref([
{ title: "No", value: "no", sortable: false },
{ title: "Jam Panggil", value: "jamPanggil", sortable: true },
{ title: "Barcode", value: "barcode", sortable: true },
{ title: "No Antrian", value: "noAntrian", sortable: true },
{ title: "Shift", value: "shift", sortable: true },
{ title: "Klinik", value: "klinik", sortable: true },
{ title: "Fast Track", value: "fastTrack", sortable: true },
{ title: "Pembayaran", value: "pembayaran", sortable: true },
{ title: "Panggil", value: "panggil", sortable: true },
{ title: "Aksi", value: "aksi", sortable: false },
]);
const mainPatients = ref<Patient[]>([
{
no: 1,
jamPanggil: "12:49",
barcode: "250811100163",
noAntrian: "UM1001 | Online - 250811100163",
shift: "Shift 1",
klinik: "KANDUNGAN",
fastTrack: "UMUM",
pembayaran: "UMUM",
panggil: "Panggil",
status: "current",
},
{
no: 2,
jamPanggil: "10:52",
barcode: "250811100155",
noAntrian: "UM1002 | Online - 250811100155",
shift: "Shift 1",
klinik: "IPD",
fastTrack: "UMUM",
pembayaran: "UMUM",
panggil: "Cancel",
status: "normal",
},
...Array.from({ length: 20 }, (_, i) => ({
no: i + 3,
jamPanggil: `${String(Math.floor(Math.random() * 12) + 1).padStart(2, "0")}:${String(Math.floor(Math.random() * 60)).padStart(2, "0")}`,
barcode: `25081110${String(i + 200).padStart(4, "0")}`,
noAntrian: `UM100${i + 3} | Online - 25081110${String(i + 200).padStart(4, "0")}`,
shift: "Shift 1",
klinik: ["KANDUNGAN", "IPD", "THT", "SARAF"][Math.floor(Math.random() * 4)],
fastTrack: "UMUM",
pembayaran: "UMUM",
panggil: "Panggil",
status: "normal",
})),
]);
const lateHeaders = ref([
{ title: "No", value: "no", sortable: false },
{ title: "Barcode", value: "barcode", sortable: true },
{ title: "No Antrian", value: "noAntrian", sortable: true },
{ title: "Shift", value: "shift", sortable: true },
{ title: "Klinik", value: "klinik", sortable: true },
{ title: "Aksi", value: "aksi", sortable: false },
]);
const latePatients = ref<LatePatient[]>([]);
const clinicHeaders = ref([
{ title: "#", value: "no", sortable: false },
{ title: "Barcode", value: "barcode", sortable: true },
{ title: "No Antrian", value: "noAntrian", sortable: true },
{ title: "No RM", value: "noRM", sortable: true },
{ title: "Shift", value: "shift", sortable: true },
{ title: "Klinik", value: "klinik", sortable: true },
{ title: "Fast Track", value: "fastTrack", sortable: true },
{ title: "Pembayaran", value: "pembayaran", sortable: true },
{ title: "Aksi", value: "aksi", sortable: false },
]);
const clinicPatients = ref<ClinicPatient[]>([]);
// Method yang tetap di halaman induk (seperti getRowClass)
const getRowClass = (item: Patient): string => {
if (item.status === "current") {
return "text-green font-weight-bold";
}
return "";
};
</script>
<style scoped>
.v-list-item--active {
background-color: rgba(25, 118, 210, 0.12);
color: #1976d2;
}
.text-green {
color: #4caf50 !important;
}
.bg-cyan {
background-color: #00bcd4 !important;
}
/* Row highlighting */
:deep(.v-data-table tbody tr:nth-child(1)) {
background-color: #fff3cd !important;
}
.v-btn {
text-transform: none !important;
}
.v-btn--size-small {
height: 32px;
padding: 0 8px;
}
</style>

View File

@@ -1,783 +0,0 @@
<!-- eslint-disable vue/valid-v-slot -->
<!-- eslint-disable vue/no-unused-vars -->
<template>
<v-app>
<v-app-bar>
<v-container fluid class="pa-4">
<v-row align="center" no-gutters class="fill-height">
<v-col cols="auto">
<div class="d-flex align-center">
<!-- Total 2 with dark background -->
<div
class="bg-grey-darken-4 px-3 py-1 mr-2"
style="border-radius: 3px"
>
<span class="text-body-1 font-weight-bold text-white"
>Total 2</span
>
</div>
<!-- Max 150 Pasien with lighter background -->
<div
class="bg-grey-darken-2 px-3 py-1"
style="border-radius: 3px"
>
<span class="text-body-1 text-white">Max 150 Pasien</span>
</div>
</div>
</v-col>
<v-spacer />
<v-col cols="auto">
<div class="d-flex align-center text-body-2">
<v-icon size="small" class="mr-2">mdi-view-dashboard</v-icon>
<span class="mr-6">Dashboard</span>
<span>Loket 12 | Rabu, 13 Agustus 2025 - Pelayanan</span>
</div>
</v-col>
</v-row>
</v-container>
</v-app-bar>
<!-- Cyan Divider -->
<div class="bg-cyan" style="height: 3px" />
<!-- Main Content -->
<v-main>
<v-container fluid class="pa-6">
<!-- Date and Service Info -->
<v-row class="mb-6">
<v-col>
<div class="text-h6 text-grey-darken-2 font-weight-medium">
Loket 12 | Rabu, 13 Agustus 2025 - Pelayanan :
</div>
</v-col>
<v-spacer />
<!-- Queue Number Buttons on the right -->
<v-col cols="auto">
<div class="d-flex align-center">
<v-btn
color="success"
dark
size="large"
class="mr-4 px-8"
style="min-width: 120px; height: 40px"
>
<span class="text-h6 font-weight-bold">1</span>
</v-btn>
<v-btn
color="info"
dark
size="large"
class="mr-4 px-4"
style="min-width: 120px; height: 40px"
>
<span class="text-h6 font-weight-bold">5</span>
</v-btn>
<v-btn
color="warning"
dark
size="large"
class="mr-4 px-4"
style="min-width: 120px; height: 40px"
>
<span class="text-h6 font-weight-bold">10</span>
</v-btn>
<v-btn
color="error"
dark
size="large"
class="px-4"
style="min-width: 120px; height: 40px"
>
<span class="text-h6 font-weight-bold">20</span>
</v-btn>
</div>
</v-col>
</v-row>
<!-- Next Patient Card -->
<v-row justify="center" class="pa-12">
<v-col cols="12" md="10" lg="8">
<v-card
color="success"
dark
flat
class="text-center"
style="min-height: 160px; border-radius: 8px"
>
<v-card-text class="pa-8">
<div
class="text-h2 font-weight-bold mb-2"
style="letter-spacing: 4px"
>
NEXT
</div>
<div class="text-h6 mb-4 font-weight-normal">
Pasien - UM1004
</div>
<div
class="text-body-1 font-weight-normal"
style="opacity: 0.9"
>
Klik untuk memanggil pasien selanjutnya
</div>
</v-card-text>
</v-card>
</v-col>
</v-row>
<!-- Main Data Table -->
<v-card class="mb-4">
<v-card-title class="text-subtitle-1 font-weight-bold">
DATA PASIEN
</v-card-title>
<div class="d-flex justify-space-between align-center pa-4">
<div class="d-flex align-center">
<span>Show</span>
<v-select
v-model="itemsPerPage"
:items="[10, 25, 50]"
label="Entries"
density="compact"
hide-details
class="mx-2"
style="width: 80px"
/>
<span>entries</span>
</div>
<div class="d-flex align-center">
<span class="mr-2">Search:</span>
<v-text-field
v-model="search"
label="Search"
hide-details
density="compact"
style="min-width: 200px"
/>
</div>
</div>
<v-data-table
:headers="mainHeaders"
:items="paginatedMainPatients"
:search="search"
hide-default-footer
class="elevation-1"
>
<template #item.aksi="{ item }">
<div class="d-flex ga-1">
<v-btn size="small" color="success" variant="flat"
>Panggil</v-btn
>
<v-btn size="small" color="info" variant="flat">Cancel</v-btn>
<v-btn size="small" color="primary" variant="flat"
>Selesai</v-btn
>
</div>
</template>
<template #item.jamPanggil="{ item }">
<span :class="getRowClass(item)">{{ item.jamPanggil }}</span>
</template>
</v-data-table>
<!-- Custom Pagination for Main Table -->
<v-row align="center" class="pa-4">
<v-col cols="auto">
<span class="text-body-2 text-grey-darken-1">
Showing {{ getStartEntry(mainCurrentPage, itemsPerPage) }} to
{{
getEndEntry(
mainCurrentPage,
itemsPerPage,
filteredMainPatients.length
)
}}
of {{ filteredMainPatients.length }} entries
</span>
</v-col>
<v-spacer />
<v-col cols="auto">
<div class="d-flex align-center">
<v-btn
:disabled="mainCurrentPage === 1"
variant="text"
size="small"
class="text-grey-darken-1 mr-2"
@click="goToMainPage(mainCurrentPage - 1)"
>
Previous
</v-btn>
<v-btn
v-for="page in getVisiblePages(
mainCurrentPage,
getMainTotalPages
)"
:key="page"
:variant="page === mainCurrentPage ? 'flat' : 'text'"
:color="
page === mainCurrentPage ? 'primary' : 'grey-lighten-1'
"
size="small"
class="mx-1"
min-width="35"
@click="goToMainPage(page)"
>
{{ page }}
</v-btn>
<v-btn
:disabled="mainCurrentPage === getMainTotalPages"
variant="text"
size="small"
class="text-grey-darken-1 ml-2"
@click="goToMainPage(mainCurrentPage + 1)"
>
Next
</v-btn>
</div>
</v-col>
</v-row>
</v-card>
<!-- Total Quota Used -->
<v-card color="cyan" dark class="mb-4">
<v-card-text class="text-center">
<div class="text-h6">Total Quota Terpakai 5</div>
</v-card-text>
</v-card>
<!-- Late Patients Table -->
<v-card class="mb-4">
<v-card-title
class="text-subtitle-1 font-weight-bold bg-red-lighten-3"
>
INFO PASIEN LAPOR TERLAMBAT
</v-card-title>
<div class="d-flex justify-space-between align-center pa-4">
<div class="d-flex align-center">
<span>Show</span>
<v-select
v-model="lateItemsPerPage"
:items="[10, 25, 50]"
label="Entries"
density="compact"
hide-details
class="mx-2"
style="width: 80px"
/>
<span>entries</span>
</div>
<div class="d-flex align-center">
<span class="mr-2">Search:</span>
<v-text-field
v-model="lateSearch"
label="Search"
hide-details
density="compact"
style="min-width: 200px"
/>
</div>
</div>
<v-data-table
:headers="lateHeaders"
:items="paginatedLatePatients"
:search="lateSearch"
hide-default-footer
class="elevation-1"
>
<template #no-data>
<div class="text-center pa-4">No data available in table</div>
</template>
</v-data-table>
<!-- Custom Pagination for Late Patients -->
<v-row
v-if="filteredLatePatients.length > 0"
align="center"
class="pa-4"
>
<v-col cols="auto">
<span class="text-body-2 text-grey-darken-1">
Showing
{{ getStartEntry(lateCurrentPage, lateItemsPerPage) }} to
{{
getEndEntry(
lateCurrentPage,
lateItemsPerPage,
filteredLatePatients.length
)
}}
of {{ filteredLatePatients.length }} entries
</span>
</v-col>
<v-spacer />
<v-col cols="auto">
<div class="d-flex align-center">
<v-btn
:disabled="lateCurrentPage === 1"
variant="text"
size="small"
class="text-grey-darken-1 mr-2"
@click="goToLatePage(lateCurrentPage - 1)"
>
Previous
</v-btn>
<v-btn
v-for="page in getVisiblePages(
lateCurrentPage,
getLateTotalPages
)"
:key="page"
:variant="page === lateCurrentPage ? 'flat' : 'text'"
:color="
page === lateCurrentPage ? 'primary' : 'grey-lighten-1'
"
size="small"
class="mx-1"
min-width="35"
@click="goToLatePage(page)"
>
{{ page }}
</v-btn>
<v-btn
:disabled="lateCurrentPage === getLateTotalPages"
variant="text"
size="small"
class="text-grey-darken-1 ml-2"
@click="goToLatePage(lateCurrentPage + 1)"
>
Next
</v-btn>
</div>
</v-col>
</v-row>
</v-card>
<!-- Clinic Entry Patients Table -->
<v-card class="mb-4">
<v-card-title
class="text-subtitle-1 font-weight-bold bg-red-lighten-3"
>
INFO PASIEN MASUK KLINIK
</v-card-title>
<div class="d-flex justify-space-between align-center pa-4">
<div class="d-flex align-center">
<span>Show</span>
<v-select
v-model="clinicItemsPerPage"
:items="[10, 25, 50]"
label="Entries"
density="compact"
hide-details
class="mx-2"
style="width: 80px"
/>
<span>entries</span>
</div>
<div class="d-flex align-center">
<span class="mr-2">Search:</span>
<v-text-field
v-model="clinicSearch"
label="Search"
hide-details
density="compact"
style="min-width: 200px"
/>
</div>
</div>
<v-data-table
:headers="clinicHeaders"
:items="paginatedClinicPatients"
:search="clinicSearch"
hide-default-footer
class="elevation-1"
>
<template #no-data>
<div class="text-center pa-4">No data available in table</div>
</template>
</v-data-table>
<!-- Custom Pagination for Clinic Patients -->
<v-row
v-if="filteredClinicPatients.length > 0"
align="center"
class="pa-4"
>
<v-col cols="auto">
<span class="text-body-2 text-grey-darken-1">
Showing
{{ getStartEntry(clinicCurrentPage, clinicItemsPerPage) }} to
{{
getEndEntry(
clinicCurrentPage,
clinicItemsPerPage,
filteredClinicPatients.length
)
}}
of {{ filteredClinicPatients.length }} entries
</span>
</v-col>
<v-spacer />
<v-col cols="auto">
<div class="d-flex align-center">
<v-btn
:disabled="clinicCurrentPage === 1"
variant="text"
size="small"
class="text-grey-darken-1 mr-2"
@click="goToClinicPage(clinicCurrentPage - 1)"
>
Previous
</v-btn>
<v-btn
v-for="page in getVisiblePages(
clinicCurrentPage,
getClinicTotalPages
)"
:key="page"
:variant="page === clinicCurrentPage ? 'flat' : 'text'"
:color="
page === clinicCurrentPage ? 'primary' : 'grey-lighten-1'
"
size="small"
class="mx-1"
min-width="35"
@click="goToClinicPage(page)"
>
{{ page }}
</v-btn>
<v-btn
:disabled="clinicCurrentPage === getClinicTotalPages"
variant="text"
size="small"
class="text-grey-darken-1 ml-2"
@click="goToClinicPage(clinicCurrentPage + 1)"
>
Next
</v-btn>
</div>
</v-col>
</v-row>
</v-card>
</v-container>
</v-main>
</v-app>
</template>
<script setup lang="ts">
import { ref, computed } from "vue";
// Define interfaces for type safety
interface Patient {
no: number;
jamPanggil: string;
barcode: string;
noAntrian: string;
shift: string;
klinik: string;
fastTrack: string;
pembayaran: string;
panggil: string;
status: string;
}
interface LatePatient {
no: number;
barcode: string;
noAntrian: string;
shift: string;
klinik: string;
aksi: string;
}
interface ClinicPatient {
no: number;
barcode: string;
noAntrian: string;
noRM: string;
shift: string;
klinik: string;
fastTrack: string;
pembayaran: string;
aksi: string;
}
// Reactive data
const search = ref<string>("");
const lateSearch = ref<string>("");
const clinicSearch = ref<string>("");
const itemsPerPage = ref<number>(10);
const lateItemsPerPage = ref<number>(10);
const clinicItemsPerPage = ref<number>(10);
// Pagination current pages
const mainCurrentPage = ref<number>(1);
const lateCurrentPage = ref<number>(1);
const clinicCurrentPage = ref<number>(1);
// Table headers
const mainHeaders = ref([
{ title: "No", value: "no", sortable: false },
{ title: "Jam Panggil", value: "jamPanggil", sortable: true },
{ title: "Barcode", value: "barcode", sortable: true },
{ title: "No Antrian", value: "noAntrian", sortable: true },
{ title: "Shift", value: "shift", sortable: true },
{ title: "Klinik", value: "klinik", sortable: true },
{ title: "Fast Track", value: "fastTrack", sortable: true },
{ title: "Pembayaran", value: "pembayaran", sortable: true },
{ title: "Panggil", value: "panggil", sortable: true },
{ title: "Aksi", value: "aksi", sortable: false },
]);
const lateHeaders = ref([
{ title: "No", value: "no", sortable: false },
{ title: "Barcode", value: "barcode", sortable: true },
{ title: "No Antrian", value: "noAntrian", sortable: true },
{ title: "Shift", value: "shift", sortable: true},
{ title: "Klinik", value: "klinik", sortable: true },
{ title: "Aksi", value: "aksi", sortable: false },
]);
const clinicHeaders = ref([
{ title: "#", value: "no", sortable: false },
{ title: "Barcode", value: "barcode", sortable: true },
{ title: "No Antrian", value: "noAntrian", sortable: true },
{ title: "No RM", value: "noRM", sortable: true },
{ title: "Shift", value: "shift", sortable: true },
{ title: "Klinik", value: "klinik", sortable: true },
{ title: "Fast Track", value: "fastTrack", sortable: true },
{ title: "Pembayaran", value: "pembayaran", sortable: true },
{ title: "Aksi", value: "aksi", sortable: false },
]);
// Sample data - Extended for pagination demo
const mainPatients = ref<Patient[]>([
{
no: 1,
jamPanggil: "12:49",
barcode: "250811100163",
noAntrian: "UM1001 | Online - 250811100163",
shift: "Shift 1",
klinik: "KANDUNGAN",
fastTrack: "UMUM",
pembayaran: "UMUM",
panggil: "Panggil",
status: "current",
},
{
no: 2,
jamPanggil: "10:52",
barcode: "250811100155",
noAntrian: "UM1002 | Online - 250811100155",
shift: "Shift 1",
klinik: "IPD",
fastTrack: "UMUM",
pembayaran: "UMUM",
panggil: "Cancel",
status: "normal",
},
// Add more sample data for pagination demo
...Array.from({ length: 20 }, (_, i) => ({
no: i + 3,
jamPanggil: `${String(Math.floor(Math.random() * 12) + 1).padStart(2, "0")}:${String(Math.floor(Math.random() * 60)).padStart(2, "0")}`,
barcode: `25081110${String(i + 200).padStart(4, "0")}`,
noAntrian: `UM100${i + 3} | Online - 25081110${String(i + 200).padStart(4, "0")}`,
shift: "Shift 1",
klinik: ["KANDUNGAN", "IPD", "THT", "SARAF"][Math.floor(Math.random() * 4)],
fastTrack: "UMUM",
pembayaran: "UMUM",
panggil: "Panggil",
status: "normal",
})),
]);
const latePatients = ref<LatePatient[]>([]);
const clinicPatients = ref<ClinicPatient[]>([]);
// Computed properties for filtering
const filteredMainPatients = computed(() => {
if (!search.value) return mainPatients.value;
return mainPatients.value.filter((item) =>
Object.values(item).some((val) =>
String(val).toLowerCase().includes(search.value.toLowerCase())
)
);
});
const filteredLatePatients = computed(() => {
if (!lateSearch.value) return latePatients.value;
return latePatients.value.filter((item) =>
Object.values(item).some((val) =>
String(val).toLowerCase().includes(lateSearch.value.toLowerCase())
)
);
});
const filteredClinicPatients = computed(() => {
if (!clinicSearch.value) return clinicPatients.value;
return clinicPatients.value.filter((item) =>
Object.values(item).some((val) =>
String(val).toLowerCase().includes(clinicSearch.value.toLowerCase())
)
);
});
// Computed properties for pagination
const getMainTotalPages = computed(() => {
return Math.ceil(filteredMainPatients.value.length / itemsPerPage.value);
});
const getLateTotalPages = computed(() => {
return Math.ceil(filteredLatePatients.value.length / lateItemsPerPage.value);
});
const getClinicTotalPages = computed(() => {
return Math.ceil(
filteredClinicPatients.value.length / clinicItemsPerPage.value
);
});
const paginatedMainPatients = computed(() => {
const start = (mainCurrentPage.value - 1) * itemsPerPage.value;
const end = start + itemsPerPage.value;
return filteredMainPatients.value.slice(start, end);
});
const paginatedLatePatients = computed(() => {
const start = (lateCurrentPage.value - 1) * lateItemsPerPage.value;
const end = start + lateItemsPerPage.value;
return filteredLatePatients.value.slice(start, end);
});
const paginatedClinicPatients = computed(() => {
const start = (clinicCurrentPage.value - 1) * clinicItemsPerPage.value;
const end = start + clinicItemsPerPage.value;
return filteredClinicPatients.value.slice(start, end);
});
// Methods
const getRowClass = (item: Patient): string => {
if (item.status === "current") {
return "text-green font-weight-bold";
}
return "";
};
const getStartEntry = (currentPage: number, itemsPerPage: number): number => {
return (currentPage - 1) * itemsPerPage + 1;
};
const getEndEntry = (currentPage: number, itemsPerPage: number, totalItems: number): number => {
const end = currentPage * itemsPerPage;
return Math.min(end, totalItems);
};
const getVisiblePages = (currentPage: number, totalPages: number): (number | string)[] => {
const pages: (number | string)[] = [];
const total = totalPages;
const current = currentPage;
if (total <= 0) return pages;
// Always show first page
pages.push(1);
// Show pages around current page
const start = Math.max(2, current - 1);
const end = Math.min(total - 1, current + 1);
// Add ellipsis if needed
if (start > 2) pages.push("...");
// Add middle pages
for (let i = start; i <= end; i++) {
if (i !== 1 && i !== total) {
pages.push(i);
}
}
// Add ellipsis if needed
if (end < total - 1) pages.push("...");
// Always show last page if more than 1 page
if (total > 1) pages.push(total);
return pages;
};
const goToMainPage = (page: number): void => {
if (
page >= 1 &&
page <= getMainTotalPages.value &&
typeof page === "number"
) {
mainCurrentPage.value = page;
}
};
const goToLatePage = (page: number): void => {
if (
page >= 1 &&
page <= getLateTotalPages.value &&
typeof page === "number"
) {
lateCurrentPage.value = page;
}
};
const goToClinicPage = (page: number): void => {
if (
page >= 1 &&
page <= getClinicTotalPages.value &&
typeof page === "number"
) {
clinicCurrentPage.value = page;
}
};
</script>
<style scoped>
.v-list-item--active {
background-color: rgba(25, 118, 210, 0.12);
color: #1976d2;
}
.text-green {
color: #4caf50 !important;
}
.bg-cyan {
background-color: #00bcd4 !important;
}
/* Custom scrollbar */
:deep(.v-data-table) {
font-size: 14px;
}
/* Row highlighting */
:deep(.v-data-table tbody tr:nth-child(1)) {
background-color: #fff3cd !important;
}
.v-btn {
text-transform: none !important;
}
.v-btn--size-small {
height: 32px;
padding: 0 8px;
}
</style>

View File

@@ -1,5 +1,3 @@
<!-- eslint-disable vue/valid-v-slot -->
<!-- eslint-disable vue/no-unused-vars -->
<template>
<v-container fluid class="bg-grey-lighten-4 pa-4">
<p class="mb-4">Admin Anjungan</p>
@@ -29,119 +27,20 @@
<v-divider :thickness="5" color="deep-orange-darken-4"></v-divider>
<v-card-text>
<v-toolbar flat color="transparent" dense>
<v-toolbar-title class="text-subtitle-1 font-weight-bold red--text">
DATA PENGUNJUNG TERLAMBAT
</v-toolbar-title>
<v-spacer></v-spacer>
<div class="d-flex align-center">
<span>Show</span>
<v-select
:items="[10, 25, 50, 100]"
label="entries"
dense
single-line
hide-details
class="shrink mx-2"
style="width: 80px"
></v-select>
<span>entries</span>
</div>
</v-toolbar>
<v-data-table
<TabelData
:headers="lateHeaders"
:items="lateVisitors"
:search="searchLate"
no-data-text="No data available in table"
hide-default-footer
class="elevation-1"
>
</v-data-table>
<!-- Custom Pagination for Late Visitors -->
<div class="d-flex justify-space-between align-center pt-4">
<div class="text-body-2 text-grey-darken-1">
Showing {{ currentPageStart }} to {{ currentPageEnd }} of {{ totalLateEntries }} entries
</div>
<div class="d-flex align-center">
<v-btn
:disabled="currentPage === 1"
@click="previousPage"
variant="text"
size="small"
class="text-body-2"
>
Previous
</v-btn>
<template v-for="page in visiblePages" :key="page">
<v-btn
v-if="page !== '...'"
:color="page === currentPage ? 'primary' : ''"
:variant="page === currentPage ? 'flat' : 'text'"
@click="goToPage(page)"
size="small"
class="mx-1"
min-width="40"
>
{{ page }}
</v-btn>
<span v-else class="mx-1">...</span>
</template>
<v-btn
:disabled="currentPage === totalPages"
@click="nextPage"
variant="text"
size="small"
class="text-body-2"
>
Next
</v-btn>
</div>
</div>
</v-card-text>
title="DATA PENGUNJUNG TERLAMBAT"
/>
<v-divider :thickness="5" color="deep-orange-darken-4"></v-divider>
<v-card-text>
<v-toolbar flat color="transparent" dense>
<v-toolbar-title class="text-subtitle-1 font-weight-bold">
DATA PENGUNJUNG
</v-toolbar-title>
</v-toolbar>
<div class="d-flex justify-space-between align-center px-4">
<div class="d-flex align-center">
<span>Show</span>
<v-select
:items="[10, 25, 50]"
v-model="itemsPerPage"
label="Entries"
density="compact"
hide-details
class="mx-2"
style="width: 80px"
></v-select>
<span>entries</span>
</div>
<div class="d-flex align-center">
<span class="mr-2">Search:</span>
<v-text-field
v-model="search"
label="Search"
hide-details
density="compact"
style="min-width: 200px"
></v-text-field>
</div>
</div>
<v-data-table
:headers="headers"
:items="paginatedMainPatients"
:search="search"
no-data-text="No data available in table"
class="elevation-1"
hide-default-footer
<TabelData
:headers="mainHeaders"
:items="mainPatients"
title="DATA PENGUNJUNG"
>
<template v-slot:item.aksi="{ item }">
<template v-slot:actions="{ item }">
<div class="d-flex ga-1">
<v-btn small color="success" class="d-flex flex-row" variant="flat">Tiket</v-btn>
<v-btn small color="success" class="d-flex flex-row" variant="flat"
@@ -150,82 +49,20 @@
<v-btn small color="info" class="d-flex flex-row" variant="flat">ByPass</v-btn>
</div>
</template>
</v-data-table>
<!-- Custom Pagination for Main Patients -->
<div class="d-flex justify-space-between align-center pt-4">
<div class="text-body-2 text-grey-darken-1">
Showing {{ mainCurrentPageStart }} to {{ mainCurrentPageEnd }} of {{ totalMainEntries }} entries
</div>
<div class="d-flex align-center">
<v-btn
:disabled="mainCurrentPage === 1"
@click="mainPreviousPage"
variant="text"
size="small"
class="text-body-2"
>
Previous
</v-btn>
<template v-for="page in mainVisiblePages" :key="page">
<v-btn
v-if="page !== '...'"
:color="page === mainCurrentPage ? 'primary' : ''"
:variant="page === mainCurrentPage ? 'flat' : 'text'"
@click="mainGoToPage(page)"
size="small"
class="mx-1"
min-width="40"
>
{{ page }}
</v-btn>
<span v-else class="mx-1">...</span>
</template>
<v-btn
:disabled="mainCurrentPage === mainTotalPages"
@click="mainNextPage"
variant="text"
size="small"
class="text-body-2"
>
Next
</v-btn>
</div>
</div>
</v-card-text>
</TabelData>
</v-card>
</v-container>
</template>
<script setup lang="ts">
import { ref, computed } from "vue";
import { ref } from "vue";
import TabelData from "../components/TabelData.vue"; // Pastikan path-nya benar
// Struktur data yang memisahkan menu dengan dan tanpa submenu
const items = ref([
{ title: "Dashboard", icon: "mdi-view-dashboard", to: "/dashboard" },
{
title: "Setting",
icon: "mdi-cog",
children: [
{ title: "Hak Akses", to: "/setting/hak-akses" },
{ title: "User Login", to: "/setting/user-login" },
{ title: "Master Loket", to: "/setting/master-loket" },
{ title: "Master Klinik", to: "/setting/master-klinik" },
{ title: "Master Klinik Ruang", to: "/setting/master-klinik-ruang" },
{ title: "Screen", to: "/setting/screen" },
],
},
{ title: "Loket Admin", icon: "mdi-account-supervisor" },
{ title: "Ranap Admin", icon: "mdi-bed" },
{ title: "Klinik Admin", icon: "mdi-hospital-box" },
{ title: "Klinik Ruang Admin", icon: "mdi-hospital-marker" },
{ title: "Anjungan", icon: "mdi-account-box-multiple", to: "/anjungan" },
{ title: "Fast Track", icon: "mdi-clock-fast" },
{ title: "Data Pasien", icon: "mdi-account-multiple" },
{ title: "Screen", icon: "mdi-monitor" },
{ title: "List Pasien", icon: "mdi-format-list-bulleted" },
]);
// Ini adalah data yang akan menjadi "single source of truth"
// untuk tabel Anda. Data ini dikirim sebagai props ke komponen anak.
const headers = ref([
const mainHeaders = ref([
{ title: "No", value: "no", sortable: false },
{ title: "Tgl Daftar", value: "tglDaftar", sortable: true },
{ title: "RM", value: "rm", sortable: true },
@@ -294,136 +131,38 @@ const mainPatients = ref([
},
]);
const search = ref("");
const itemsPerPage = ref(10);
const lateHeaders = ref([
{ title: "No", value: "no", sortable: false },
// Tambahkan headers spesifik untuk tabel ini jika berbeda
]);
// Pagination for Late Visitors
const searchLate = ref('');
const lateHeaders = ref([]);
const lateVisitors = ref([]);
const currentPage = ref(1);
const totalLateEntries = computed(() => lateVisitors.value.length);
const totalPages = computed(() => Math.ceil(totalLateEntries.value / itemsPerPage.value));
const currentPageStart = computed(() => (currentPage.value - 1) * itemsPerPage.value + 1);
const currentPageEnd = computed(() => Math.min(currentPage.value * itemsPerPage.value, totalLateEntries.value));
const lateVisitors = ref([
// Tambahkan data spesifik untuk tabel ini jika ada
]);
// Pagination for Main Patients
const mainCurrentPage = ref(1);
const totalMainEntries = computed(() => mainPatients.value.length);
const mainTotalPages = computed(() => Math.ceil(totalMainEntries.value / itemsPerPage.value));
const mainCurrentPageStart = computed(() => (mainCurrentPage.value - 1) * itemsPerPage.value + 1);
const mainCurrentPageEnd = computed(() => Math.min(mainCurrentPage.value * itemsPerPage.value, totalMainEntries.value));
// Paginated data for main patients
const paginatedMainPatients = computed(() => {
const start = (mainCurrentPage.value - 1) * itemsPerPage.value;
const end = start + itemsPerPage.value;
return mainPatients.value.slice(start, end);
});
// Visible pages calculation for Late Visitors
const visiblePages = computed(() => {
const pages = [];
const total = totalPages.value;
const current = currentPage.value;
if (total <= 7) {
for (let i = 1; i <= total; i++) {
pages.push(i);
}
} else {
if (current <= 4) {
for (let i = 1; i <= 5; i++) {
pages.push(i);
}
pages.push('...');
pages.push(total);
} else if (current >= total - 3) {
pages.push(1);
pages.push('...');
for (let i = total - 4; i <= total; i++) {
pages.push(i);
}
} else {
pages.push(1);
pages.push('...');
for (let i = current - 1; i <= current + 1; i++) {
pages.push(i);
}
pages.push('...');
pages.push(total);
}
}
return pages;
});
// Visible pages calculation for Main Patients
const mainVisiblePages = computed(() => {
const pages = [];
const total = mainTotalPages.value;
const current = mainCurrentPage.value;
if (total <= 7) {
for (let i = 1; i <= total; i++) {
pages.push(i);
}
} else {
if (current <= 4) {
for (let i = 1; i <= 5; i++) {
pages.push(i);
}
pages.push('...');
pages.push(total);
} else if (current >= total - 3) {
pages.push(1);
pages.push('...');
for (let i = total - 4; i <= total; i++) {
pages.push(i);
}
} else {
pages.push(1);
pages.push('...');
for (let i = current - 1; i <= current + 1; i++) {
pages.push(i);
}
pages.push('...');
pages.push(total);
}
}
return pages;
});
// Pagination methods for Late Visitors
const previousPage = () => {
if (currentPage.value > 1) {
currentPage.value--;
}
};
const nextPage = () => {
if (currentPage.value < totalPages.value) {
currentPage.value++;
}
};
const goToPage = (page: number) => {
currentPage.value = page;
};
// Pagination methods for Main Patients
const mainPreviousPage = () => {
if (mainCurrentPage.value > 1) {
mainCurrentPage.value--;
}
};
const mainNextPage = () => {
if (mainCurrentPage.value < mainTotalPages.value) {
mainCurrentPage.value++;
}
};
const mainGoToPage = (page: number) => {
mainCurrentPage.value = page;
};
// ... Sisa kode lainnya yang tidak terkait dengan tabel ...
const items = ref([
{ title: "Dashboard", icon: "mdi-view-dashboard", to: "/dashboard" },
{
title: "Setting",
icon: "mdi-cog",
children: [
{ title: "Hak Akses", to: "/setting/hak-akses" },
{ title: "User Login", to: "/setting/user-login" },
{ title: "Master Loket", to: "/setting/master-loket" },
{ title: "Master Klinik", to: "/setting/master-klinik" },
{ title: "Master Klinik Ruang", to: "/setting/master-klinik-ruang" },
{ title: "Screen", to: "/setting/screen" },
],
},
{ title: "Loket Admin", icon: "mdi-account-supervisor" },
{ title: "Ranap Admin", icon: "mdi-bed" },
{ title: "Klinik Admin", icon: "mdi-hospital-box" },
{ title: "Klinik Ruang Admin", icon: "mdi-hospital-marker" },
{ title: "Anjungan", icon: "mdi-account-box-multiple", to: "/anjungan" },
{ title: "Fast Track", icon: "mdi-clock-fast" },
{ title: "Data Pasien", icon: "mdi-account-multiple" },
{ title: "Screen", icon: "mdi-monitor" },
{ title: "List Pasien", icon: "mdi-format-list-bulleted" },
]);
</script>