Files
antrean-operasi/components/pendaftaran/TableAntrian.vue
T
Yusron alamsyah f72984775b change color theme
2026-02-05 08:12:34 +07:00

215 lines
6.5 KiB
Vue

<script setup lang="ts">
import type { Header, Action } from '~/types/common';
import { STATUS } from '~/types/antrean';
interface Props {
headers: Header[];
items: any[];
search?: string;
itemsPerPage?: number;
minWidth?: string;
actions?: Action[];
serverSide?: boolean;
totalItems?: number;
currentPage?: number;
}
const props = withDefaults(defineProps<Props>(), {
search: '',
itemsPerPage: 10,
minWidth: '1200px',
actions: () => [],
serverSide: false,
totalItems: 0,
currentPage: 1
});
const emit = defineEmits<{
(e: string, item: any): void;
(e: 'update:page', page: number): void;
(e: 'update:itemsPerPage', itemsPerPage: number): void;
}>();
// Status helper
const getStatusText = (status: string) => {
switch (status) {
case STATUS.BELUM: return 'Belum';
case STATUS.SELESAI: return 'Selesai';
case STATUS.TUNDA: return 'Tunda';
case STATUS.BATAL: return 'Batal';
default: return 'Unknown';
}
};
const getStatusColor = (status: string) => {
switch (status) {
case STATUS.BELUM: return 'info';
case STATUS.SELESAI: return 'success';
case STATUS.TUNDA: return 'warning';
case STATUS.BATAL: return 'error';
default: return 'default';
}
};
const handleAction = (event: string, item: any) => {
emit(event, item);
};
const page = ref(props.currentPage);
const itemsPerPageLocal = ref(props.itemsPerPage);
watch(() => props.currentPage, (newVal) => {
page.value = newVal;
});
watch(page, (newVal) => {
emit('update:page', newVal);
});
watch(itemsPerPageLocal, (newVal) => {
emit('update:itemsPerPage', newVal);
});
</script>
<template>
<div class="table-container">
<div class="table-wrapper">
<v-data-table-server
:headers="headers"
:items="items"
:items-length="serverSide ? totalItems : 0"
v-model:page="page"
v-model:items-per-page="itemsPerPageLocal"
:search="serverSide ? undefined : search"
class="elevation-1"
item-value="id"
hide-default-footer
>
<!-- Pass through all slots to parent -->
<template v-for="(_, name) in $slots" v-slot:[name]="slotData">
<slot :name="name" v-bind="slotData" />
</template>
<!-- Default slot for Jenis Kelamin if not provided -->
<template #item.JenisKelamin="{ item }">
<slot name="item.JenisKelamin" :item="item">
<v-chip
:color="item.JenisKelamin === 'L' ? 'primary' : 'error'"
size="small"
>
{{ item.JenisKelamin }}
</v-chip>
</slot>
</template>
<!-- Default slot for Status if not provided -->
<template #item.StatusOperasi="{ item }">
<slot name="item.StatusOperasi" :item="item">
<v-chip
:color="getStatusColor(item.StatusOperasi)"
size="small"
>
{{ getStatusText(item.StatusOperasi) }}
</v-chip>
</slot>
</template>
<!-- Action slot if enabled -->
<template v-if="actions && actions.length > 0" #item.actions="{ item }">
<slot name="item.actions" :item="item">
<div class="d-flex ga-2">
<v-btn
v-for="(action, index) in actions"
:key="index"
icon
size="small"
variant="text"
:color="action.color || 'primary'"
@click="handleAction(action.event, item)"
>
<v-icon>{{ action.icon }}</v-icon>
<v-tooltip v-if="action.tooltip" activator="parent" location="top">
{{ action.tooltip }}
</v-tooltip>
</v-btn>
</div>
</slot>
</template>
</v-data-table-server>
</div>
<!-- Custom pagination footer - fixed and on the left -->
<div class="pagination-footer">
<div class="pagination-info">
<span class="text-caption">
Showing {{ ((page - 1) * itemsPerPageLocal) + 1 }} to {{ Math.min(page * itemsPerPageLocal, serverSide ? totalItems : items.length) }} of {{ serverSide ? totalItems : items.length }} entries
</span>
</div>
<div class="pagination-controls">
<v-select
v-model="itemsPerPageLocal"
:items="[10, 25, 50, 100]"
density="compact"
variant="outlined"
hide-details
style="max-width: 100px;"
label="Rows"
></v-select>
<v-pagination
v-model="page"
:length="Math.ceil((serverSide ? totalItems : items.length) / itemsPerPageLocal)"
:total-visible="5"
density="comfortable"
></v-pagination>
</div>
</div>
</div>
</template>
<style scoped>
.table-container {
width: 100%;
display: flex;
flex-direction: column;
}
.table-wrapper {
overflow-x: auto;
width: 100%;
margin-bottom: 16px;
}
.table-wrapper :deep(.v-data-table) {
min-width: v-bind(minWidth);
}
.pagination-footer {
display: flex;
align-items: center;
justify-content: space-between;
padding: 12px 16px;
border-top: 1px solid rgba(0, 0, 0, 0.12);
background-color: white;
position: sticky;
left: 0;
right: 0;
}
.pagination-info {
flex-shrink: 0;
}
.pagination-controls {
display: flex;
align-items: center;
gap: 16px;
flex-wrap: wrap;
}
/* Dark theme support */
:deep(.v-theme--dark) .pagination-footer {
border-top-color: rgba(255, 255, 255, 0.12);
background-color: rgb(30, 30, 30);
}
</style>