115 lines
3.6 KiB
Vue
115 lines
3.6 KiB
Vue
<script setup>
|
|
import { ref, computed } from 'vue'
|
|
import { useAlertStore } from '@/stores/alerts'
|
|
import { usePolling } from '@/composables/usePolling'
|
|
import Card from '@/components/ui/Card.vue'
|
|
import Badge from '@/components/ui/Badge.vue'
|
|
import Button from '@/components/ui/Button.vue'
|
|
import EmptyState from '@/components/ui/EmptyState.vue'
|
|
import AcknowledgeModal from '@/components/alerts/AcknowledgeModal.vue'
|
|
import { alertTypeLabel } from '@/api/normalize'
|
|
import { formatAcknowledgedByDisplay } from '@/composables/alertAcknowledge'
|
|
|
|
const props = defineProps({
|
|
encounterId: { type: String, required: true },
|
|
selectedId: { type: String, default: null },
|
|
})
|
|
|
|
const emit = defineEmits(['select'])
|
|
|
|
const alertStore = useAlertStore()
|
|
const { alerts, loading } = storeToRefs(alertStore)
|
|
const confirmingAlert = ref(null)
|
|
|
|
function loadEncounterAlerts() {
|
|
return alertStore.loadAlerts(props.encounterId)
|
|
}
|
|
|
|
usePolling(loadEncounterAlerts, 5_000)
|
|
|
|
const visibleAlerts = computed(() =>
|
|
alerts.value.filter(a => a.status !== 'Resolved'),
|
|
)
|
|
|
|
function severityVariant(severity) {
|
|
return severity === 'Critical' ? 'critical' : 'warning'
|
|
}
|
|
|
|
async function handleAcknowledge(note) {
|
|
if (!confirmingAlert.value) return
|
|
await alertStore.acknowledge(confirmingAlert.value.id, note)
|
|
confirmingAlert.value = null
|
|
await loadEncounterAlerts()
|
|
}
|
|
|
|
async function resolve(alertId) {
|
|
await alertStore.resolve(alertId)
|
|
await loadEncounterAlerts()
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<Card>
|
|
<template #header>
|
|
<h2 class="mb-4 text-sm font-semibold uppercase tracking-wide text-gray-500 dark:text-gray-400">
|
|
Active Alerts
|
|
</h2>
|
|
</template>
|
|
|
|
<EmptyState v-if="!loading && visibleAlerts.length === 0" message="No open alerts" />
|
|
<ul v-else class="divide-y divide-gray-200 dark:divide-gray-700">
|
|
<li
|
|
v-for="alert in visibleAlerts"
|
|
:id="`alert-row-${alert.id}`"
|
|
:key="alert.id"
|
|
class="flex cursor-pointer flex-col gap-4 py-4 first:pt-0 last:pb-0 sm:flex-row sm:items-start sm:justify-between"
|
|
:class="selectedId === alert.id ? 'bg-blue-50/50 dark:bg-blue-950/20' : ''"
|
|
@click="emit('select', alert)"
|
|
>
|
|
<div class="min-w-0 flex-1">
|
|
<div class="flex flex-wrap items-center gap-2">
|
|
<Badge :variant="severityVariant(alert.severity)">{{ alert.severity }}</Badge>
|
|
<span class="text-sm font-medium text-gray-900 dark:text-white">
|
|
{{ alertTypeLabel(alert.alertType) }}
|
|
</span>
|
|
</div>
|
|
<p v-if="alert.details" class="mt-2 truncate text-xs text-gray-500 dark:text-gray-400">
|
|
{{ alert.details }}
|
|
</p>
|
|
<p
|
|
v-if="alert.acknowledgedBy"
|
|
class="mt-2 text-xs text-gray-500 dark:text-gray-400"
|
|
>
|
|
Acknowledged by {{ formatAcknowledgedByDisplay(alert.acknowledgedBy) }}
|
|
</p>
|
|
</div>
|
|
<div class="flex shrink-0 gap-2">
|
|
<Button
|
|
v-if="alert.status === 'Open' || alert.status === 'Escalated'"
|
|
size="sm"
|
|
variant="secondary"
|
|
@click.stop="confirmingAlert = alert"
|
|
>
|
|
Ack
|
|
</Button>
|
|
<Button
|
|
v-if="alert.status === 'Acknowledged'"
|
|
size="sm"
|
|
variant="primary"
|
|
@click.stop="resolve(alert.id)"
|
|
>
|
|
Resolve
|
|
</Button>
|
|
</div>
|
|
</li>
|
|
</ul>
|
|
|
|
<AcknowledgeModal
|
|
:open="!!confirmingAlert"
|
|
:alert="confirmingAlert"
|
|
@confirm="handleAcknowledge"
|
|
@close="confirmingAlert = null"
|
|
/>
|
|
</Card>
|
|
</template>
|