feature: Digitization Workstation UI

This commit is contained in:
voltsrage
2026-06-27 12:15:43 +08:00
parent 88e70b3dbe
commit e22d33b654
55 changed files with 6411 additions and 143 deletions
@@ -0,0 +1,179 @@
<template>
<div class="min-h-screen flex flex-col">
<AppHeader title="Intake" />
<div class="page-container flex-1">
<h1 class="text-2xl font-bold mb-6">Upload Scanned Document</h1>
<!-- Upload form -->
<div class="card mb-6">
<h2 class="text-lg font-semibold mb-4">New Batch</h2>
<form @submit.prevent="handleUpload" class="space-y-4">
<!-- File input -->
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">
Scanned Document (PDF, JPEG, PNG &mdash; max 25 MB)
</label>
<input
type="file"
ref="fileInput"
@change="onFileChange"
accept=".pdf,.jpg,.jpeg,.png"
class="block w-full text-sm text-gray-500
file:mr-4 file:py-2 file:px-4
file:rounded-md file:border-0
file:text-sm file:font-semibold
file:bg-primary-50 file:text-primary-700
hover:file:bg-primary-100"
/>
<p v-if="selectedFile" class="text-sm text-gray-500 mt-2">
{{ selectedFile.name }} ({{ (selectedFile.size / 1024 / 1024).toFixed(2) }} MB)
</p>
</div>
<!-- Batch type -->
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Batch Type</label>
<select v-model="batchType" class="form-input" required>
<option value="">Select batch type...</option>
<option value="PATIENT_REGISTRATION">Patient Registration</option>
<option value="ENCOUNTER_SUMMARY">Encounter Summary</option>
<option value="VITALS_SHEET">Vitals Sheet</option>
<option value="LAB_RESULTS">Lab Results</option>
<option value="MEDICATION_LIST">Medication List</option>
<option value="ALLERGY_UPDATE">Allergy Update</option>
<option value="MIXED">Mixed</option>
</select>
</div>
<!-- Track -->
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Track</label>
<select v-model="track" class="form-input">
<option value="BACKFILL">Backfill (Track A)</option>
<option value="LIVE_CAPTURE">Live Capture (Track B)</option>
</select>
</div>
<!-- Patient search -->
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">
Patient (optional &mdash; can link later)
</label>
<PatientSearch v-model="patientId" />
</div>
<div v-if="uploadError" class="text-clinical-danger text-sm">
{{ uploadError }}
</div>
<button
type="submit"
class="btn-primary"
:disabled="!selectedFile || !batchType || batchStore.loading"
>
{{ batchStore.loading ? 'Uploading...' : 'Upload and Create Batch' }}
</button>
</form>
</div>
<!-- Recent uploads -->
<div class="card">
<h2 class="text-lg font-semibold mb-4">Recent Uploads</h2>
<div v-if="assignError" class="text-clinical-danger text-sm mb-4">
{{ assignError }}
</div>
<BatchList
:batches="batchStore.batches"
:loading="batchStore.loading"
show-assign
@assign="openAssignDialog"
/>
</div>
</div>
<AssignClerkDialog
:show="assignDialogOpen"
:batch-id="assignBatchId"
@close="closeAssignDialog"
@assigned="handleAssign"
/>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { useBatchStore } from '../stores/batches'
import AppHeader from '../components/AppHeader.vue'
import PatientSearch from '../components/PatientSearch.vue'
import BatchList from '../components/BatchList.vue'
import AssignClerkDialog from '../components/AssignClerkDialog.vue'
const batchStore = useBatchStore()
const selectedFile = ref<File | null>(null)
const batchType = ref('')
const track = ref('BACKFILL')
const patientId = ref<string | undefined>(undefined)
const uploadError = ref('')
const assignError = ref('')
const assignDialogOpen = ref(false)
const assignBatchId = ref<string | null>(null)
function onFileChange(event: Event) {
const input = event.target as HTMLInputElement
selectedFile.value = input.files?.[0] ?? null
}
async function handleUpload() {
if (!selectedFile.value || !batchType.value) return
uploadError.value = ''
try {
const batch = await batchStore.uploadBatch(
selectedFile.value,
batchType.value,
track.value,
patientId.value
)
if (batch) {
selectedFile.value = null
batchType.value = ''
track.value = 'BACKFILL'
patientId.value = undefined
await loadRecent()
}
} catch (e: unknown) {
uploadError.value = e instanceof Error ? e.message : 'Upload failed'
}
}
function openAssignDialog(batchId: string) {
assignError.value = ''
assignBatchId.value = batchId
assignDialogOpen.value = true
}
function closeAssignDialog() {
assignDialogOpen.value = false
assignBatchId.value = null
}
async function handleAssign(batchId: string, clerkUserId: string) {
assignError.value = ''
try {
await batchStore.assignBatch(batchId, clerkUserId)
closeAssignDialog()
await loadRecent()
} catch (e: unknown) {
assignError.value = e instanceof Error ? e.message : 'Assignment failed'
}
}
async function loadRecent() {
await batchStore.listBatches({ status: 'UPLOADED', page: 1, pageSize: 20 })
}
onMounted(loadRecent)
</script>