feature: Degraded Operations Visibility
This commit is contained in:
@@ -0,0 +1,155 @@
|
||||
<script setup>
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import * as thresholdsApi from '@/api/thresholds'
|
||||
import ThresholdFormModal from '@/components/admin/ThresholdFormModal.vue'
|
||||
import Button from '@/components/ui/Button.vue'
|
||||
import Skeleton from '@/components/ui/Skeleton.vue'
|
||||
import EmptyState from '@/components/ui/EmptyState.vue'
|
||||
import { emptyThresholdForm, formatThresholdValue, thresholdToForm } from '@/composables/thresholdForm'
|
||||
|
||||
const thresholds = ref([])
|
||||
const loading = ref(true)
|
||||
const error = ref('')
|
||||
const modalOpen = ref(false)
|
||||
const modalMode = ref('edit')
|
||||
const editingThreshold = ref(null)
|
||||
const submitting = ref(false)
|
||||
const submitError = ref('')
|
||||
|
||||
const modalTitle = computed(() =>
|
||||
modalMode.value === 'create' ? 'Create Threshold' : 'Edit Threshold',
|
||||
)
|
||||
|
||||
const modalInitialValues = computed(() =>
|
||||
modalMode.value === 'create'
|
||||
? emptyThresholdForm()
|
||||
: thresholdToForm(editingThreshold.value ?? {}),
|
||||
)
|
||||
|
||||
async function loadThresholds() {
|
||||
loading.value = true
|
||||
error.value = ''
|
||||
try {
|
||||
thresholds.value = await thresholdsApi.fetchThresholds()
|
||||
} catch (err) {
|
||||
error.value = err.message
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function openCreate() {
|
||||
modalMode.value = 'create'
|
||||
editingThreshold.value = null
|
||||
submitError.value = ''
|
||||
modalOpen.value = true
|
||||
}
|
||||
|
||||
function openEdit(threshold) {
|
||||
modalMode.value = 'edit'
|
||||
editingThreshold.value = threshold
|
||||
submitError.value = ''
|
||||
modalOpen.value = true
|
||||
}
|
||||
|
||||
function closeModal() {
|
||||
modalOpen.value = false
|
||||
}
|
||||
|
||||
async function onSubmit(payload) {
|
||||
submitting.value = true
|
||||
submitError.value = ''
|
||||
try {
|
||||
if (modalMode.value === 'create') {
|
||||
await thresholdsApi.createThreshold(payload)
|
||||
} else {
|
||||
await thresholdsApi.updateThreshold(editingThreshold.value.id, payload)
|
||||
}
|
||||
modalOpen.value = false
|
||||
await loadThresholds()
|
||||
} catch (err) {
|
||||
submitError.value = err.message
|
||||
} finally {
|
||||
submitting.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function onDelete(threshold) {
|
||||
if (!window.confirm(`Delete threshold for ${threshold.observationCode}?`)) return
|
||||
error.value = ''
|
||||
try {
|
||||
await thresholdsApi.deleteThreshold(threshold.id)
|
||||
await loadThresholds()
|
||||
} catch (err) {
|
||||
error.value = err.message
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(loadThresholds)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div class="mb-4 flex flex-wrap items-center justify-between gap-4">
|
||||
<div>
|
||||
<h1 class="text-xl font-bold dark:text-white">Alert Thresholds</h1>
|
||||
<p class="mt-2 text-sm text-gray-600 dark:text-gray-400">
|
||||
Configure warning and critical bounds used for clinical alerting.
|
||||
</p>
|
||||
</div>
|
||||
<Button size="sm" @click="openCreate">Create Threshold</Button>
|
||||
</div>
|
||||
|
||||
<p v-if="error" class="mb-4 text-sm text-red-600 dark:text-red-400">{{ error }}</p>
|
||||
|
||||
<Skeleton v-if="loading" :rows="6" />
|
||||
<EmptyState v-else-if="thresholds.length === 0" message="No thresholds configured" />
|
||||
|
||||
<div v-else class="overflow-x-auto rounded-lg border border-gray-200 dark:border-gray-700">
|
||||
<table class="min-w-full divide-y divide-gray-200 text-sm dark:divide-gray-700">
|
||||
<thead class="bg-gray-50 dark:bg-gray-800">
|
||||
<tr class="text-left text-xs uppercase tracking-wide text-gray-500 dark:text-gray-400">
|
||||
<th class="px-4 py-3">Code</th>
|
||||
<th class="px-4 py-3">Display</th>
|
||||
<th class="px-4 py-3">Unit</th>
|
||||
<th class="px-4 py-3 text-right">Critical Low</th>
|
||||
<th class="px-4 py-3 text-right">Warning Low</th>
|
||||
<th class="px-4 py-3 text-right">Warning High</th>
|
||||
<th class="px-4 py-3 text-right">Critical High</th>
|
||||
<th class="px-4 py-3 text-right">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-200 dark:divide-gray-700">
|
||||
<tr v-for="threshold in thresholds" :key="threshold.id">
|
||||
<td class="px-4 py-3 font-medium text-gray-900 dark:text-white">
|
||||
{{ threshold.observationCode }}
|
||||
</td>
|
||||
<td class="px-4 py-3 text-gray-700 dark:text-gray-300">{{ threshold.displayName }}</td>
|
||||
<td class="px-4 py-3 text-gray-700 dark:text-gray-300">{{ threshold.unit }}</td>
|
||||
<td class="px-4 py-3 text-right">{{ formatThresholdValue(threshold.criticalLow) }}</td>
|
||||
<td class="px-4 py-3 text-right">{{ formatThresholdValue(threshold.warningLow) }}</td>
|
||||
<td class="px-4 py-3 text-right">{{ formatThresholdValue(threshold.warningHigh) }}</td>
|
||||
<td class="px-4 py-3 text-right">{{ formatThresholdValue(threshold.criticalHigh) }}</td>
|
||||
<td class="px-4 py-3">
|
||||
<div class="flex justify-end gap-2">
|
||||
<Button size="sm" variant="secondary" @click="openEdit(threshold)">Edit</Button>
|
||||
<Button size="sm" variant="danger" @click="onDelete(threshold)">Delete</Button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<ThresholdFormModal
|
||||
:open="modalOpen"
|
||||
:title="modalTitle"
|
||||
:initial-values="modalInitialValues"
|
||||
:submitting="submitting"
|
||||
:error="submitError"
|
||||
:read-only-code="modalMode === 'edit'"
|
||||
@close="closeModal"
|
||||
@submit="onSubmit"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
Reference in New Issue
Block a user