Files
vigilcare-clinical/vigilcare-dashboard/src/views/WardDashboard.vue
T
Trent 87e551c0d7
CI / backend (push) Successful in 10m14s
CI / frontend (push) Successful in 1m55s
Refractor. Update styling
2026-08-11 06:48:07 +08:00

120 lines
3.6 KiB
Vue

<script setup>
import { ref } from 'vue'
import { storeToRefs } from 'pinia'
import { useRoute } from 'vue-router'
import { useWardStore } from '@/stores/ward'
import { useSettingsStore } from '@/stores/settings'
import { useAuthStore } from '@/stores/auth'
import { usePolling } from '@/composables/usePolling'
import { WARD_SORT_FIELDS } from '@/composables/wardSort'
import WardToolbar from '@/components/ward/WardToolbar.vue'
import WardTable from '@/components/ward/WardTable.vue'
import HandoffReport from '@/components/ward/HandoffReport.vue'
import Skeleton from '@/components/ui/Skeleton.vue'
import EmptyState from '@/components/ui/EmptyState.vue'
import Badge from '@/components/ui/Badge.vue'
const route = useRoute()
const wardStore = useWardStore()
const settingsStore = useSettingsStore()
const authStore = useAuthStore()
const showHandoff = ref(false)
const {
encounters,
displayEncounters,
loading,
error,
criticalCount,
department,
} = storeToRefs(wardStore)
const { wardSortField, wardSortDirection } = storeToRefs(settingsStore)
if (route.query.department) {
wardStore.setDepartment(String(route.query.department))
}
usePolling(() => wardStore.loadEncounters(), 10_000)
const departments = [
{ value: '', label: 'All departments' },
{ value: 'ICU', label: 'ICU' },
{ value: 'GENERAL_MEDICINE', label: 'General Medicine' },
{ value: 'SURGERY', label: 'Surgery' },
]
function onDepartmentChange(event) {
wardStore.setDepartment(event.target.value || null)
}
function onMobileSortChange(event) {
wardStore.setSort(event.target.value)
}
</script>
<template>
<div>
<div class="mb-3 flex flex-wrap items-center justify-end gap-3">
<Badge v-if="criticalCount > 0" variant="critical">
{{ criticalCount }} critical
</Badge>
</div>
<label class="mb-3 block sm:hidden">
<span class="mb-1.5 block text-xs font-medium text-ink-muted">
Department
</span>
<select
:value="department ?? ''"
class="w-full rounded-md border border-border bg-surface-raised px-3 py-2 text-sm text-ink-secondary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand"
@change="onDepartmentChange"
>
<option v-for="dept in departments" :key="dept.value" :value="dept.value">
{{ dept.label }}
</option>
</select>
</label>
<WardToolbar @export-handoff="showHandoff = true" />
<label class="mb-3 block md:hidden">
<span class="mb-1.5 block text-xs font-medium text-ink-muted">
Sort by
</span>
<select
:value="wardSortField"
class="w-full rounded-md border border-border bg-surface-raised px-3 py-2 text-sm text-ink-secondary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand"
@change="onMobileSortChange"
>
<option v-for="option in WARD_SORT_FIELDS" :key="option.key" :value="option.key">
{{ option.label }}
</option>
</select>
</label>
<Skeleton v-if="loading && encounters.length === 0" :rows="5" />
<EmptyState
v-else-if="encounters.length === 0"
message="No active patients"
/>
<EmptyState
v-else-if="displayEncounters.length === 0"
message="No patients match your search or filters"
/>
<WardTable
v-else
:patients="displayEncounters"
:sort-field="wardSortField"
:sort-direction="wardSortDirection"
@sort="wardStore.setSort"
/>
<HandoffReport
v-if="showHandoff"
:encounters="encounters"
:department="department"
:generated-by="authStore.displayName"
@close="showHandoff = false"
/>
</div>
</template>