feature: Shared UX Primitives
CI / backend (push) Successful in 6m16s
CI / frontend (push) Failing after 1m4s

This commit is contained in:
2026-08-12 03:57:43 +08:00
parent 56e100a495
commit 9811f2a2ed
26 changed files with 1259 additions and 183 deletions
@@ -18,7 +18,11 @@
<BatchList
:batches="batchStore.batches"
:loading="batchStore.loading"
:error="batchStore.error"
empty-title="No batches are waiting for clinical approval."
empty-description="Verified batches appear here after verification is complete."
@select="openBatch"
@retry="loadQueue"
/>
</div>
@@ -36,9 +40,7 @@
<div class="h-full overflow-y-auto p-4 space-y-6">
<div class="flex items-center justify-between">
<h2 class="text-lg font-semibold">Clinical Review</h2>
<span class="bg-purple-100 text-purple-800 status-badge">
Awaiting Clinical Approval
</span>
<StatusBadge :status="currentBatch?.status ?? 'AWAITING_CLINICAL_APPROVAL'" />
</div>
<!-- Supersession info -->
@@ -125,7 +127,7 @@
<!-- Actions -->
<div class="flex flex-col sm:flex-row gap-4 pt-4 border-t">
<button
@click="approve"
@click="showApproveConfirm = true"
class="btn-primary"
:disabled="processing"
>
@@ -178,36 +180,34 @@
</p>
</div>
<!-- Reject dialog -->
<div
v-if="showRejectDialog"
class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50"
<ConfirmDialog
:open="showApproveConfirm"
title="Approve & Promote"
body="Approval will promote the verified records into live clinical tables."
confirm-label="Approve & Promote"
variant="primary"
:confirm-disabled="processing"
@confirm="confirmApprove"
@cancel="showApproveConfirm = false"
/>
<ConfirmDialog
:open="showRejectDialog"
title="Reject Batch"
body="This returns the batch for rework. A reason is required."
confirm-label="Confirm Rejection"
variant="danger"
:confirm-disabled="!rejectionReason.trim() || processing"
@confirm="reject"
@cancel="closeRejectDialog"
>
<div class="bg-white rounded-lg p-6 max-w-md w-full mx-4">
<h3 class="text-lg font-semibold mb-4">Reject Batch</h3>
<textarea
v-model="rejectionReason"
class="form-input"
rows="4"
placeholder="Reason for rejection (required)..."
/>
<div class="flex flex-col-reverse sm:flex-row sm:justify-end gap-4 mt-4">
<button
@click="showRejectDialog = false"
class="px-4 py-2 text-gray-600 hover:text-gray-800"
>
Cancel
</button>
<button
@click="reject"
class="btn-danger"
:disabled="!rejectionReason.trim()"
>
Confirm Rejection
</button>
</div>
</div>
</div>
<textarea
v-model="rejectionReason"
class="form-input"
rows="4"
placeholder="Reason for rejection (required)..."
/>
</ConfirmDialog>
<div v-if="errorMessage" class="text-clinical-danger text-sm">
{{ errorMessage }}
@@ -227,6 +227,8 @@ import AppHeader from '../components/AppHeader.vue'
import ScanViewer from '../components/ScanViewer.vue'
import BatchList from '../components/BatchList.vue'
import ObservationRow from '../components/ObservationRow.vue'
import StatusBadge from '../components/StatusBadge.vue'
import ConfirmDialog from '../components/ConfirmDialog.vue'
const props = defineProps<{ batchId?: string }>()
@@ -243,6 +245,7 @@ const { documentUrl, documentError } = usePresignedUrl(batchId)
const processing = ref(false)
const errorMessage = ref('')
const enableRetroactiveAlerts = ref(false)
const showApproveConfirm = ref(false)
const showRejectDialog = ref(false)
const rejectionReason = ref('')
const promotionResult = ref<{ mrn: string; encounterId: string; observationIds: string[] } | null>(null)
@@ -256,6 +259,15 @@ function formatBatchType(type: string): string {
return type.replace(/_/g, ' ').replace(/\b\w/g, c => c.toUpperCase())
}
function closeRejectDialog() {
showRejectDialog.value = false
}
async function confirmApprove() {
showApproveConfirm.value = false
await approve()
}
async function approve() {
if (!batchId.value) return
processing.value = true
@@ -329,11 +341,15 @@ watch(
onMounted(async () => {
if (!batchId.value) {
await batchStore.listBatches({
status: 'AWAITING_CLINICAL_APPROVAL',
page: 1,
pageSize: 50,
})
await loadQueue()
}
})
async function loadQueue() {
await batchStore.listBatches({
status: 'AWAITING_CLINICAL_APPROVAL',
page: 1,
pageSize: 50,
})
}
</script>