diff --git a/components/features/antrean/QueueCreationDialog.vue b/components/features/antrean/QueueCreationDialog.vue index a07db38..efa9095 100644 --- a/components/features/antrean/QueueCreationDialog.vue +++ b/components/features/antrean/QueueCreationDialog.vue @@ -11,7 +11,7 @@ - +

Informasi Pasien

@@ -26,7 +26,7 @@
- + - + - +
+ {{ status.icon }} {{ status.label }} ({{ status.count }}) @@ -76,21 +77,6 @@ - - - - - Fast Track: {{ selectedFastTrack }} + Fast Track: {{ selectedFastTrackModel }}
@@ -231,21 +217,55 @@ const props = defineProps({ terlambat: 'Terlambat', pending: 'Pending' }) + }, + selectedFastTrack: { + type: String, + default: null + }, + fastTrackOptions: { + type: Array, + default: () => [] } }); -const emit = defineEmits(['update:selectedStatus', 'update:searchQuery', 'action']); +const emit = defineEmits(['update:selectedStatus', 'update:searchQuery', 'update:selectedFastTrack', 'action']); const currentPage = ref(1); const selectedKlinik = ref(null); const selectedPembayaran = ref(null); const selectedShift = ref(null); -const selectedFastTrack = ref(null); -const selectedStatusModel = computed({ - get: () => props.selectedStatus, +const selectedFastTrackModel = computed({ + get: () => props.selectedFastTrack, set: (value) => { currentPage.value = 1; + emit('update:selectedFastTrack', value); + } +}); + +const selectedStatusModel = computed({ + get: () => { + // If Fast Track is selected, return 'fasttrack' as status + if (selectedFastTrackModel.value === 'YA') { + return 'fasttrack'; + } + return props.selectedStatus; + }, + set: (value) => { + currentPage.value = 1; + + // Handle Fast Track selection + if (value === 'fasttrack') { + selectedFastTrackModel.value = 'YA'; + // Emit 'all' as status since Fast Track is a separate filter + emit('update:selectedStatus', 'all'); + return; + } + + // Reset Fast Track when other status is selected + if (selectedFastTrackModel.value) { + selectedFastTrackModel.value = null; + } emit('update:selectedStatus', value); } }); @@ -280,6 +300,16 @@ const statusOptions = computed(() => { { value: 'pending', label: props.statusLabels.pending, count: props.pendingCount } ); + // Add Fast Track as a status option + if (fastTrackYaCount.value > 0) { + baseOptions.push({ + value: 'fasttrack', + label: 'Fast Track', + count: fastTrackYaCount.value, + icon: 'mdi-flash' + }); + } + return baseOptions; }); @@ -299,28 +329,36 @@ const shiftOptions = computed(() => { return shifts.sort(); }); -const fastTrackOptions = computed(() => { - const normalizedValues = props.items - .map((p) => (p.fastTrack ?? "").toString().trim().toUpperCase()) - .filter((v) => v.length > 0); - - const uniqueTracks = [...new Set(normalizedValues)]; - return uniqueTracks.sort(); +// Count Fast Track "YA" +const fastTrackYaCount = computed(() => { + return props.items.filter(p => { + const patientFastTrack = (p.fastTrack ?? "").toString().trim().toUpperCase(); + return patientFastTrack === 'YA'; + }).length; }); + const hasActiveFilters = computed(() => { - return !!(selectedKlinik.value || selectedPembayaran.value || selectedShift.value || selectedFastTrack.value); + return !!(selectedKlinik.value || selectedPembayaran.value || selectedShift.value || selectedFastTrackModel.value); }); const clearAllFilters = () => { selectedKlinik.value = null; selectedPembayaran.value = null; selectedShift.value = null; - selectedFastTrack.value = null; + selectedFastTrackModel.value = null; currentPage.value = 1; }; const filteredItems = computed(() => { + // Handle Fast Track as a status category + if (selectedStatusModel.value === 'fasttrack') { + return props.items.filter(p => { + const patientFastTrack = (p.fastTrack ?? "").toString().trim().toUpperCase(); + return patientFastTrack === 'YA'; + }); + } + if (selectedStatusModel.value === 'all') return props.items; return props.items.filter(p => p.status === selectedStatusModel.value); }); @@ -338,9 +376,7 @@ const filteredAndSearchedItems = computed(() => { if (selectedShift.value) { result = result.filter(p => p.shift === selectedShift.value); } - if (selectedFastTrack.value) { - result = result.filter(p => p.fastTrack === selectedFastTrack.value); - } + // Fast Track filtering is now handled in filteredItems as a status category // Apply search if (searchModel.value) { diff --git a/pages/AdminKlinik.vue b/pages/AdminKlinik.vue index 80eead8..480f950 100644 --- a/pages/AdminKlinik.vue +++ b/pages/AdminKlinik.vue @@ -13,71 +13,127 @@ - - +
+ + - - + + - -
- - - - mdi-hospital-building - Buat Antrean Klinik - - + +
+ + + + mdi-hospital-building + Buat Antrean Klinik + + - - - mdi-clipboard-pulse - Buat Antrean Penunjang - - - + + + mdi-clipboard-pulse + Buat Antrean Penunjang + + + +
- + + + +
+ + +
+ + + + Semua ({{ allPatients.length }}) + + + Di Klinik ({{ (diLoketPatients || []).length }}) + + + Terlambat ({{ (terlambatPatients || []).length }}) + + + Pending ({{ (pendingPatients || []).length }}) + + + + + + + + + + +
+
+ + +
+
@@ -170,6 +226,16 @@ const currentDate = ref( const selectedStatus = ref("all"); const searchQuery = ref(""); +const selectedFastTrack = ref(null); + +// Fast Track options from all patients +const fastTrackOptions = computed(() => { + const normalizedValues = allPatients.value + .map((p) => (p.fastTrack ?? "").toString().trim().toUpperCase()) + .filter((v) => v.length > 0); + const uniqueTracks = [...new Set(normalizedValues)]; + return uniqueTracks.sort(); +}); // Custom status labels for Klinik const statusLabels = { @@ -254,9 +320,100 @@ const handleProcessNext = () => { font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; } +.sticky-wrapper { + position: sticky; + top: 16px; + align-self: flex-start; + max-height: calc(100vh - 32px); + overflow-y: auto; +} + +.filters { + display: flex; + gap: 12px; + align-items: center; + flex-wrap: wrap; +} + +.status-filter { + flex: 1; + min-width: 300px; +} + +.status-filter .v-chip { + font-size: 12px; + font-weight: 600; + height: 32px; + border: 1px solid var(--color-neutral-500); + background: var(--color-neutral-100); + color: var(--color-neutral-600); + transition: all 0.2s ease; +} + +.status-filter .v-chip:hover { + background: var(--color-neutral-300); +} + +.status-filter .v-chip.active-chip { + background: var(--color-secondary-600); + color: var(--color-neutral-100); + border-color: var(--color-secondary-600); +} + +.fast-track-filter { + min-width: 180px; + max-width: 220px; +} + +.search-field { + max-width: 300px; + min-width: 250px; +} + +.section-label { + font-size: 11px; + font-weight: 700; + letter-spacing: 0.5px; + color: var(--color-neutral-600); + text-transform: uppercase; +} + +.data-header { + display: flex; + flex-direction: column; + gap: 12px; +} + +.patient-data-container { + border-radius: 12px; + border: 1px solid var(--color-neutral-500); + background: var(--color-neutral-100); +} + @media (max-width: 960px) { .loket-container { padding: 12px; } + + .sticky-wrapper { + position: relative; + top: 0; + max-height: none; + } + + .filters { + flex-direction: column; + align-items: stretch; + } + + .status-filter { + min-width: 100%; + } + + .search-field, + .fast-track-filter { + max-width: 100%; + min-width: 100%; + } } \ No newline at end of file diff --git a/pages/AdminLoket.vue b/pages/AdminLoket.vue index 29ecb3b..99195f3 100644 --- a/pages/AdminLoket.vue +++ b/pages/AdminLoket.vue @@ -13,71 +13,95 @@ - - +
+ + - - + + - -
- - - - mdi-hospital-building - Buat Antrean Klinik - - + +
+ + + + mdi-hospital-building + Buat Antrean Klinik + + - - - mdi-clipboard-pulse - Buat Antrean Penunjang - - - + + + mdi-clipboard-pulse + Buat Antrean Penunjang + + + +
- - + + + +
+ + +
+ + +
+
+ + +
+
@@ -170,6 +194,16 @@ const currentDate = ref( const selectedStatus = ref("all"); const searchQuery = ref(""); +const selectedFastTrack = ref(null); + +// Fast Track options from all patients +const fastTrackOptions = computed(() => { + const normalizedValues = allPatientsForStage.value + .map((p) => (p.fastTrack ?? "").toString().trim().toUpperCase()) + .filter((v) => v.length > 0); + const uniqueTracks = [...new Set(normalizedValues)]; + return uniqueTracks.sort(); +}); // Combine all patients with status - PRESERVE ALL PROPERTIES const allPatientsForStage = computed(() => { @@ -266,9 +300,94 @@ const handleProcessNext = () => { font-weight: 600; } +.sticky-wrapper { + position: sticky; + top: 16px; + align-self: flex-start; + max-height: calc(100vh - 32px); + overflow-y: auto; +} + +.filters { + display: flex; + gap: 12px; + align-items: center; + flex-wrap: wrap; +} + +.status-filter { + flex: 1; + min-width: 300px; +} + +.status-filter .v-chip { + font-size: 12px; + font-weight: 600; + height: 32px; + border: 1px solid var(--color-neutral-500); + background: var(--color-neutral-100); + color: var(--color-neutral-600); + transition: all 0.2s ease; +} + +.status-filter .v-chip:hover { + background: var(--color-neutral-300); +} + +.status-filter .v-chip.active-chip { + background: var(--color-primary-600); + color: var(--color-neutral-100); + border-color: var(--color-primary-600); +} + +.search-field { + max-width: 300px; + min-width: 250px; +} + +.section-label { + font-size: 11px; + font-weight: 700; + letter-spacing: 0.5px; + color: var(--color-neutral-600); + text-transform: uppercase; +} + +.data-header { + display: flex; + flex-direction: column; + gap: 12px; +} + +.patient-data-container { + border-radius: 12px; + border: 1px solid var(--color-neutral-500); + background: var(--color-neutral-100); +} + @media (max-width: 960px) { .loket-container { padding: 12px; } + + .sticky-wrapper { + position: relative; + top: 0; + max-height: none; + } + + .filters { + flex-direction: column; + align-items: stretch; + } + + .status-filter { + min-width: 100%; + } + + .search-field { + max-width: 100%; + min-width: 100%; + } } diff --git a/pages/AdminPenunjang.vue b/pages/AdminPenunjang.vue index c160a87..bac47aa 100644 --- a/pages/AdminPenunjang.vue +++ b/pages/AdminPenunjang.vue @@ -18,79 +18,81 @@ - - - - -
-
{{ currentProcessingPatient.noAntrian.split(" |")[0] }}
-
-
{{ currentProcessingPatient.barcode }}
-
{{ currentProcessingPatient.klinik }} | {{ currentProcessingPatient.pembayaran }}
+
+ + + + +
+
{{ currentProcessingPatient.noAntrian.split(" |")[0] }}
+
+
{{ currentProcessingPatient.barcode }}
+
{{ currentProcessingPatient.klinik }} | {{ currentProcessingPatient.pembayaran }}
+
+ +
+ + mdi-check + Selesai + + + mdi-clock-alert + Terlambat + + + mdi-pause + Pending + + + mdi-swap-horizontal + Ubah Ruang + +
-
- - mdi-check - Selesai - - - mdi-clock-alert - Terlambat - - - mdi-pause - Pending - - - mdi-swap-horizontal - Ubah Ruang - +
+ mdi-account-off-outline +
Tidak ada pasien yang diproses
-
- -
- mdi-account-off-outline -
Tidak ada pasien yang diproses
-
-
-
+ + - - - - - -
-
- Kuota - 150 + + + + + +
+
+ Kuota + 150 +
+
+ Tersedia + {{ 150 - quotaUsed }} +
+
+ + Terpakai: {{ quotaUsed }} +
-
- Tersedia - {{ 150 - quotaUsed }} -
-
- - Terpakai: {{ quotaUsed }} -
-
-
- 1 - 5 - 10 - 20 -
- - +
+ 1 + 5 + 10 + 20 +
+ + - -
- - mdi-clipboard-pulse - Buat Antrean Penunjang - + +
+ + mdi-clipboard-pulse + Buat Antrean Penunjang + +
@@ -424,6 +426,14 @@ const getStatusLabel = (status) => { padding: 8px; } +.sticky-wrapper { + position: sticky; + top: 16px; + align-self: flex-start; + max-height: calc(100vh - 32px); + overflow-y: auto; +} + .current-patient-card, .queue-actions-card, .patient-table-card { @@ -663,6 +673,12 @@ const getStatusLabel = (status) => { padding: 12px; } + .sticky-wrapper { + position: relative; + top: 0; + max-height: none; + } + .filters { flex-direction: column; align-items: stretch; diff --git a/pages/Anjungan/Anjungan/[id].vue b/pages/Anjungan/Anjungan/[id].vue index 9c54205..7798db6 100644 --- a/pages/Anjungan/Anjungan/[id].vue +++ b/pages/Anjungan/Anjungan/[id].vue @@ -8,18 +8,24 @@

{{ anjunganData.namaAnjungan }}

-

Pilih Klinik untuk Pendaftaran - {{ anjunganData.jenisPasien }}

+

+ Pilih Klinik untuk Pendaftaran - {{ anjunganData.jenisPasien }} +

1 - Pilih Poli (Hijau = Buka, Merah = Tutup) + Pilih Poli (Hijau = Buka, Merah = Tutup)
2 - Pilih: Daftar Sekarang atau Jadwal Lain + Pilih: Daftar Sekarang atau Jadwal Lain
3 @@ -58,24 +64,42 @@
- + mdi-doctor {{ getDisplayDoctorInfo(clinic) }}
- + mdi-clock-outline - {{ clinic.schedule || 'Tidak tersedia' }} + {{ clinic.schedule || "Tidak tersedia" }}
@@ -84,14 +108,22 @@
- mdi-hospital-marker-outline + mdi-hospital-marker-outline

Tidak ada klinik yang sesuai

-

Anjungan ini belum memiliki klinik terpilih

+

+ Anjungan ini belum memiliki klinik terpilih +

- + mdi-check-circle @@ -101,7 +133,11 @@
- +

{{ selectedClinic.name }}

@@ -119,44 +155,56 @@

- +
-

Kuota Hari Ini (Shift {{ getCurrentShiftNumber() }}):

-
- {{ getCurrentShiftQuota() }} +

+ Kuota Hari Ini (Shift + {{ getCurrentShiftNumber() }}): +

+
+ {{ + getCurrentShiftQuota() + }} pasien tersedia
- +

Jadwal: {{ selectedClinic.schedule }}

- + - + - DAFTAR SEKARANG -

Kuota hari ini habis

+

+ Kuota hari ini habis +

- JADWAL LAIN @@ -164,36 +212,66 @@
- + - + Tutup - + - Jenis Pembayaran + Jenis Pembayaran - + BPJS - + UMUM / JKMM / SPM / DLL - + Tutup - + Pilih Jadwal Kunjungan @@ -236,17 +314,31 @@ - + Tutup - + Simpan - + {{ snackbarText }} @@ -685,7 +794,11 @@ const backToList = () => { } .page-header { - background: linear-gradient(135deg, var(--color-primary-600) 0%, var(--color-primary-700) 100%); + background: linear-gradient( + 135deg, + var(--color-primary-600) 0%, + var(--color-primary-700) 100% + ); border-radius: 12px; margin-bottom: 16px; box-shadow: 0 4px 16px rgba(255, 155, 27, 0.25); @@ -952,12 +1065,12 @@ const backToList = () => { .dialog-info { text-align: left; font-size: 14px; - + p { margin: 8px 0; color: var(--color-neutral-800); } - + strong { color: var(--color-neutral-900); } @@ -965,7 +1078,7 @@ const backToList = () => { .doctor-list-section { margin-bottom: 12px; - + p { margin-bottom: 8px; } @@ -975,7 +1088,7 @@ const backToList = () => { list-style: none; padding: 0; margin: 0; - + li { padding: 6px 12px; margin: 4px 0; @@ -988,7 +1101,7 @@ const backToList = () => { .quota-section { margin-bottom: 16px; - + p { margin-bottom: 12px; font-size: 15px; @@ -1000,19 +1113,27 @@ const backToList = () => { flex-direction: column; align-items: center; padding: 20px; - background: linear-gradient(135deg, var(--color-success-100) 0%, var(--color-success-200) 100%); + background: linear-gradient( + 135deg, + var(--color-success-100) 0%, + var(--color-success-200) 100% + ); border-radius: 12px; border: 2px solid var(--color-success-400); margin-bottom: 12px; - + &.quota-empty { - background: linear-gradient(135deg, var(--color-neutral-200) 0%, var(--color-neutral-300) 100%); + background: linear-gradient( + 135deg, + var(--color-neutral-200) 0%, + var(--color-neutral-300) 100% + ); border-color: var(--color-neutral-500); - + .quota-large-number { color: var(--color-neutral-600); } - + .quota-label { color: var(--color-neutral-700); } @@ -1066,20 +1187,20 @@ const backToList = () => { flex-direction: column; gap: 12px; } - + .header-right { width: 100%; } - + .info-guide { width: 100%; } - + .guide-item { font-size: 12px; justify-content: center; } - + .guide-number { width: 22px; height: 22px; @@ -1089,11 +1210,11 @@ const backToList = () => { .page-title { font-size: 24px; } - + .clinic-card { height: 170px; } - + .clinic-name { font-size: 20px; } @@ -1132,11 +1253,12 @@ const backToList = () => { .clinic-icon-wrapper .v-icon { font-size: 32px !important; } - - .doctor-info, .schedule-info { + + .doctor-info, + .schedule-info { font-size: 11px; } - + .doctor-info { padding-right: 60px; }