update card, update design system, update layout anjungan
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
<template>
|
||||
<v-card class="patient-table-card" elevation="0">
|
||||
<v-card class="patient-data-container" elevation="0">
|
||||
<v-card-text class="pa-4">
|
||||
<!-- Table Header with Filters -->
|
||||
<div class="table-header mb-4">
|
||||
<!-- Header with Filters -->
|
||||
<div class="data-header mb-4">
|
||||
<div class="section-label">DATA PASIEN</div>
|
||||
|
||||
<div class="filters">
|
||||
@@ -28,73 +28,39 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Data Table -->
|
||||
<v-data-table
|
||||
:headers="headers"
|
||||
:items="filteredItems"
|
||||
:search="searchModel"
|
||||
class="patient-table"
|
||||
:items-per-page="itemsPerPage"
|
||||
density="comfortable"
|
||||
>
|
||||
<template #item.noAntrian="{ item }">
|
||||
<div class="queue-number">{{ item.noAntrian.split(" |")[0] }}</div>
|
||||
</template>
|
||||
<!-- Patient Cards Grid -->
|
||||
<div v-if="filteredAndSearchedItems.length > 0" class="patient-grid">
|
||||
<PatientCard
|
||||
v-for="(patient, index) in paginatedItems"
|
||||
:key="`${patient.barcode}-${index}`"
|
||||
:patient="patient"
|
||||
@action="handleAction"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<template #item.status="{ item }">
|
||||
<v-chip
|
||||
:color="getStatusColor(item.status)"
|
||||
size="small"
|
||||
class="status-chip"
|
||||
>
|
||||
{{ getStatusLabel(item.status) }}
|
||||
</v-chip>
|
||||
</template>
|
||||
<!-- Empty State -->
|
||||
<div v-else class="empty-state">
|
||||
<v-icon size="64" color="neutral-500">mdi-account-search</v-icon>
|
||||
<div class="empty-text mt-3">Tidak ada data pasien</div>
|
||||
<div class="empty-subtext">Data akan muncul ketika ada pasien yang terdaftar</div>
|
||||
</div>
|
||||
|
||||
<template #item.klinik="{ item }">
|
||||
<v-chip size="small" variant="outlined" class="klinik-chip">
|
||||
{{ item.klinik }}
|
||||
</v-chip>
|
||||
</template>
|
||||
|
||||
<template #item.aksi="{ item }">
|
||||
<div class="action-buttons-table">
|
||||
<v-btn
|
||||
v-if="item.status === 'diloket'"
|
||||
size="small"
|
||||
color="primary-600"
|
||||
variant="flat"
|
||||
@click="$emit('action', item, 'proses')"
|
||||
>
|
||||
Proses
|
||||
</v-btn>
|
||||
<v-btn
|
||||
v-else-if="item.status === 'terlambat'"
|
||||
size="small"
|
||||
color="success-600"
|
||||
variant="flat"
|
||||
@click="$emit('action', item, 'aktifkan')"
|
||||
>
|
||||
Aktifkan
|
||||
</v-btn>
|
||||
<v-btn
|
||||
v-else-if="item.status === 'pending'"
|
||||
size="small"
|
||||
color="success-600"
|
||||
variant="flat"
|
||||
@click="$emit('action', item, 'proses')"
|
||||
>
|
||||
Proses
|
||||
</v-btn>
|
||||
</div>
|
||||
</template>
|
||||
</v-data-table>
|
||||
<!-- Pagination -->
|
||||
<div v-if="filteredAndSearchedItems.length > itemsPerPage" class="pagination-container mt-4">
|
||||
<v-pagination
|
||||
v-model="currentPage"
|
||||
:length="totalPages"
|
||||
:total-visible="7"
|
||||
rounded="circle"
|
||||
/>
|
||||
</div>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
import { computed, ref } from 'vue';
|
||||
import PatientCard from './PatientCard.vue';
|
||||
|
||||
const props = defineProps({
|
||||
items: {
|
||||
@@ -123,7 +89,7 @@ const props = defineProps({
|
||||
},
|
||||
itemsPerPage: {
|
||||
type: Number,
|
||||
default: 10
|
||||
default: 9
|
||||
},
|
||||
statusLabels: {
|
||||
type: Object,
|
||||
@@ -138,14 +104,22 @@ const props = defineProps({
|
||||
|
||||
const emit = defineEmits(['update:selectedStatus', 'update:searchQuery', 'action']);
|
||||
|
||||
const currentPage = ref(1);
|
||||
|
||||
const selectedStatusModel = computed({
|
||||
get: () => props.selectedStatus,
|
||||
set: (value) => emit('update:selectedStatus', value)
|
||||
set: (value) => {
|
||||
currentPage.value = 1; // Reset to first page on filter change
|
||||
emit('update:selectedStatus', value);
|
||||
}
|
||||
});
|
||||
|
||||
const searchModel = computed({
|
||||
get: () => props.searchQuery,
|
||||
set: (value) => emit('update:searchQuery', value)
|
||||
set: (value) => {
|
||||
currentPage.value = 1; // Reset to first page on search
|
||||
emit('update:searchQuery', value);
|
||||
}
|
||||
});
|
||||
|
||||
const statusOptions = computed(() => [
|
||||
@@ -160,39 +134,34 @@ const filteredItems = computed(() => {
|
||||
return props.items.filter(p => p.status === selectedStatusModel.value);
|
||||
});
|
||||
|
||||
const headers = [
|
||||
{ title: "No", value: "no", width: "60px", sortable: false },
|
||||
{ title: "Jam Panggil", value: "jamPanggil", width: "100px" },
|
||||
{ title: "Barcode", value: "barcode", width: "130px" },
|
||||
{ title: "No Antrian", value: "noAntrian", width: "140px" },
|
||||
{ title: "Klinik", value: "klinik", width: "100px" },
|
||||
{ title: "Fast Track", value: "fastTrack", width: "100px" },
|
||||
{ title: "Pembayaran", value: "pembayaran", width: "100px" },
|
||||
{ title: "Status", value: "status", width: "100px" },
|
||||
{ title: "Aksi", value: "aksi", width: "100px", sortable: false },
|
||||
];
|
||||
const filteredAndSearchedItems = computed(() => {
|
||||
if (!searchModel.value) return filteredItems.value;
|
||||
|
||||
const searchLower = searchModel.value.toLowerCase();
|
||||
return filteredItems.value.filter(patient =>
|
||||
patient.barcode?.toLowerCase().includes(searchLower) ||
|
||||
patient.noAntrian?.toLowerCase().includes(searchLower) ||
|
||||
patient.klinik?.toLowerCase().includes(searchLower)
|
||||
);
|
||||
});
|
||||
|
||||
const getStatusColor = (status) => {
|
||||
const colors = {
|
||||
diloket: "var(--color-secondary-600)",
|
||||
terlambat: "var(--color-primary-600)",
|
||||
pending: "var(--color-danger-600)"
|
||||
};
|
||||
return colors[status] || "var(--color-neutral-600)";
|
||||
};
|
||||
const totalPages = computed(() =>
|
||||
Math.ceil(filteredAndSearchedItems.value.length / props.itemsPerPage)
|
||||
);
|
||||
|
||||
const getStatusLabel = (status) => {
|
||||
const labels = {
|
||||
diloket: "Di Loket",
|
||||
terlambat: "Terlambat",
|
||||
pending: "Pending"
|
||||
};
|
||||
return labels[status] || status;
|
||||
const paginatedItems = computed(() => {
|
||||
const start = (currentPage.value - 1) * props.itemsPerPage;
|
||||
const end = start + props.itemsPerPage;
|
||||
return filteredAndSearchedItems.value.slice(start, end);
|
||||
});
|
||||
|
||||
const handleAction = (patient, action) => {
|
||||
emit('action', patient, action);
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.patient-table-card {
|
||||
.patient-data-container {
|
||||
border-radius: 12px;
|
||||
border: 1px solid var(--color-neutral-500);
|
||||
background: var(--color-neutral-100);
|
||||
@@ -206,7 +175,7 @@ const getStatusLabel = (status) => {
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.table-header {
|
||||
.data-header {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
@@ -230,6 +199,11 @@ const getStatusLabel = (status) => {
|
||||
border: 1px solid var(--color-neutral-500);
|
||||
background: var(--color-neutral-100);
|
||||
color: var(--color-neutral-600);
|
||||
transition: all 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
background: var(--color-neutral-300);
|
||||
}
|
||||
}
|
||||
|
||||
.status-filter .v-chip.active-chip {
|
||||
@@ -239,56 +213,45 @@ const getStatusLabel = (status) => {
|
||||
}
|
||||
|
||||
.search-field {
|
||||
max-width: 250px;
|
||||
max-width: 300px;
|
||||
}
|
||||
|
||||
:deep(.patient-table) {
|
||||
border-radius: 8px;
|
||||
.patient-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
:deep(.patient-table .v-data-table__thead) {
|
||||
background: var(--color-neutral-300);
|
||||
.empty-state {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 60px 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
:deep(.patient-table th) {
|
||||
font-size: 11px !important;
|
||||
font-weight: 700 !important;
|
||||
color: var(--color-neutral-600) !important;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
padding: 12px 8px !important;
|
||||
}
|
||||
|
||||
:deep(.patient-table td) {
|
||||
font-size: 13px !important;
|
||||
padding: 10px 8px !important;
|
||||
border-bottom: 1px solid var(--color-neutral-400) !important;
|
||||
}
|
||||
|
||||
:deep(.patient-table tbody tr:hover) {
|
||||
background: var(--color-neutral-300) !important;
|
||||
}
|
||||
|
||||
.queue-number {
|
||||
font-weight: 700;
|
||||
color: var(--color-neutral-900);
|
||||
}
|
||||
|
||||
.status-chip {
|
||||
.empty-text {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
font-size: 11px;
|
||||
color: var(--color-neutral-700);
|
||||
}
|
||||
|
||||
.klinik-chip {
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
.empty-subtext {
|
||||
font-size: 13px;
|
||||
color: var(--color-neutral-600);
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.action-buttons-table .v-btn {
|
||||
text-transform: none;
|
||||
.pagination-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding-top: 16px;
|
||||
border-top: 1px solid var(--color-neutral-400);
|
||||
}
|
||||
|
||||
:deep(.v-pagination__item) {
|
||||
font-weight: 600;
|
||||
font-size: 12px;
|
||||
height: 32px;
|
||||
}
|
||||
|
||||
@media (max-width: 960px) {
|
||||
@@ -300,5 +263,22 @@ const getStatusLabel = (status) => {
|
||||
.search-field {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.patient-grid {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 961px) and (max-width: 1264px) {
|
||||
.patient-grid {
|
||||
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1265px) {
|
||||
.patient-grid {
|
||||
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user