feature: Barcode/QR Cover Sheet System
This commit is contained in:
@@ -5,6 +5,80 @@
|
||||
<div class="page-container flex-1">
|
||||
<h1 class="text-2xl font-bold mb-6">Upload Scanned Document</h1>
|
||||
|
||||
<!-- Barcode-assisted upload -->
|
||||
<div class="card mb-6">
|
||||
<h2 class="text-lg font-semibold mb-4">Quick Upload with Cover Sheet</h2>
|
||||
<p class="text-sm text-gray-600 mb-4">
|
||||
Scan or type a cover sheet barcode to auto-populate batch details.
|
||||
</p>
|
||||
|
||||
<div class="flex gap-4 items-end">
|
||||
<div class="flex-1">
|
||||
<label class="block text-sm font-medium text-gray-700 mb-2">
|
||||
Cover Sheet Code
|
||||
</label>
|
||||
<input
|
||||
v-model="coverSheetCode"
|
||||
@keydown.enter.prevent="lookupCoverSheet"
|
||||
type="text"
|
||||
class="form-input"
|
||||
placeholder="VCR-CS-XXXXXXXX"
|
||||
autofocus
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
@click="lookupCoverSheet"
|
||||
class="btn-secondary"
|
||||
:disabled="!coverSheetCode.trim() || lookupLoading"
|
||||
>
|
||||
{{ lookupLoading ? 'Looking up...' : 'Lookup' }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div v-if="lookupError" class="text-clinical-danger text-sm mt-3">
|
||||
{{ lookupError }}
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="resolvedCoverSheet"
|
||||
class="mt-4 bg-green-50 border border-green-200 rounded-md p-4"
|
||||
>
|
||||
<p class="text-sm font-medium text-green-800">Cover Sheet Found</p>
|
||||
<dl class="mt-2 text-sm text-green-700 space-y-1">
|
||||
<div>
|
||||
<dt class="inline font-medium">Type:</dt>
|
||||
<dd class="inline">{{ formatBatchType(resolvedCoverSheet.batchType) }}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt class="inline font-medium">Track:</dt>
|
||||
<dd class="inline">{{ formatTrack(resolvedCoverSheet.track) }}</dd>
|
||||
</div>
|
||||
<div v-if="resolvedCoverSheet.patientName">
|
||||
<dt class="inline font-medium">Patient:</dt>
|
||||
<dd class="inline">
|
||||
{{ resolvedCoverSheet.patientName }}
|
||||
({{ resolvedCoverSheet.patientMrn }})
|
||||
</dd>
|
||||
</div>
|
||||
<div v-if="resolvedCoverSheet.assignToUserName">
|
||||
<dt class="inline font-medium">Assign to:</dt>
|
||||
<dd class="inline">{{ resolvedCoverSheet.assignToUserName }}</dd>
|
||||
</div>
|
||||
</dl>
|
||||
<p class="text-sm text-green-600 mt-3">
|
||||
Select a file below and click "Upload with Cover Sheet" to create the batch.
|
||||
</p>
|
||||
<button
|
||||
type="button"
|
||||
@click="clearCoverSheet"
|
||||
class="text-xs text-green-700 hover:text-green-900 mt-2"
|
||||
>
|
||||
Clear cover sheet (use manual upload)
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Upload form -->
|
||||
<div class="card mb-6">
|
||||
<h2 class="text-lg font-semibold mb-4">New Batch</h2>
|
||||
@@ -35,7 +109,11 @@
|
||||
<!-- 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>
|
||||
<select
|
||||
v-model="batchType"
|
||||
class="form-input"
|
||||
:required="!resolvedCoverSheet"
|
||||
>
|
||||
<option value="">Select batch type...</option>
|
||||
<option value="PATIENT_REGISTRATION">Patient Registration</option>
|
||||
<option value="ENCOUNTER_SUMMARY">Encounter Summary</option>
|
||||
@@ -72,6 +150,7 @@
|
||||
<span class="font-mono">{{ supersedesBatchId.substring(0, 8) }}...</span>
|
||||
</p>
|
||||
<button
|
||||
type="button"
|
||||
@click="clearCorrection"
|
||||
class="text-xs text-blue-600 hover:text-blue-800 mt-2"
|
||||
>
|
||||
@@ -86,9 +165,9 @@
|
||||
<button
|
||||
type="submit"
|
||||
class="btn-primary"
|
||||
:disabled="!selectedFile || !batchType || batchStore.loading"
|
||||
:disabled="!canUpload"
|
||||
>
|
||||
{{ batchStore.loading ? 'Uploading...' : 'Upload and Create Batch' }}
|
||||
{{ uploadButtonLabel }}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
@@ -118,14 +197,16 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { ref, computed, onMounted, watch } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { get } from '../api/client'
|
||||
import { useBatchStore } from '../stores/batches'
|
||||
import { useToast } from '../composables/useToast'
|
||||
import AppHeader from '../components/AppHeader.vue'
|
||||
import PatientSearch from '../components/PatientSearch.vue'
|
||||
import BatchList from '../components/BatchList.vue'
|
||||
import AssignClerkDialog from '../components/AssignClerkDialog.vue'
|
||||
import type { CoverSheetResponse } from '../types'
|
||||
|
||||
const route = useRoute()
|
||||
const batchStore = useBatchStore()
|
||||
@@ -141,13 +222,87 @@ const assignError = ref('')
|
||||
const assignDialogOpen = ref(false)
|
||||
const assignBatchId = ref<string | null>(null)
|
||||
|
||||
const coverSheetCode = ref('')
|
||||
const resolvedCoverSheet = ref<CoverSheetResponse | null>(null)
|
||||
const lookupLoading = ref(false)
|
||||
const lookupError = ref('')
|
||||
|
||||
const canUpload = computed(() => {
|
||||
if (!selectedFile.value || batchStore.loading) return false
|
||||
if (resolvedCoverSheet.value) return true
|
||||
return !!batchType.value
|
||||
})
|
||||
|
||||
const uploadButtonLabel = computed(() => {
|
||||
if (batchStore.loading) return 'Uploading...'
|
||||
if (resolvedCoverSheet.value) return 'Upload with Cover Sheet'
|
||||
return 'Upload and Create Batch'
|
||||
})
|
||||
|
||||
watch(coverSheetCode, () => {
|
||||
if (resolvedCoverSheet.value && coverSheetCode.value.trim().toUpperCase() !== resolvedCoverSheet.value.code) {
|
||||
resolvedCoverSheet.value = null
|
||||
lookupError.value = ''
|
||||
}
|
||||
})
|
||||
|
||||
function formatBatchType(type: string): string {
|
||||
return type.replace(/_/g, ' ').replace(/\b\w/g, c => c.toUpperCase())
|
||||
}
|
||||
|
||||
function formatTrack(value: string): string {
|
||||
return value === 'BACKFILL' ? 'Backfill (Track A)' : 'Live Capture (Track B)'
|
||||
}
|
||||
|
||||
function onFileChange(event: Event) {
|
||||
const input = event.target as HTMLInputElement
|
||||
selectedFile.value = input.files?.[0] ?? null
|
||||
}
|
||||
|
||||
async function lookupCoverSheet(): Promise<void> {
|
||||
const code = coverSheetCode.value.trim().toUpperCase()
|
||||
if (!code) return
|
||||
|
||||
lookupLoading.value = true
|
||||
lookupError.value = ''
|
||||
resolvedCoverSheet.value = null
|
||||
|
||||
try {
|
||||
const response = await get<CoverSheetResponse>(
|
||||
`cover-sheets/lookup/${encodeURIComponent(code)}`,
|
||||
)
|
||||
|
||||
if (!response.success || !response.data) {
|
||||
throw new Error(response.error?.message ?? 'Cover sheet not found')
|
||||
}
|
||||
|
||||
if (response.data.isUsed) {
|
||||
throw new Error('Cover sheet has already been used')
|
||||
}
|
||||
|
||||
resolvedCoverSheet.value = response.data
|
||||
coverSheetCode.value = response.data.code
|
||||
batchType.value = response.data.batchType
|
||||
track.value = response.data.track
|
||||
patientId.value = response.data.patientId ?? undefined
|
||||
toast.success('Cover sheet found')
|
||||
} catch (e: unknown) {
|
||||
const msg = e instanceof Error ? e.message : 'Lookup failed'
|
||||
lookupError.value = msg
|
||||
toast.error(msg)
|
||||
} finally {
|
||||
lookupLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function clearCoverSheet(): void {
|
||||
coverSheetCode.value = ''
|
||||
resolvedCoverSheet.value = null
|
||||
lookupError.value = ''
|
||||
}
|
||||
|
||||
async function handleUpload() {
|
||||
if (!selectedFile.value || !batchType.value) return
|
||||
if (!selectedFile.value || !canUpload.value) return
|
||||
|
||||
uploadError.value = ''
|
||||
try {
|
||||
@@ -157,15 +312,24 @@ async function handleUpload() {
|
||||
track.value,
|
||||
patientId.value,
|
||||
supersedesBatchId.value,
|
||||
resolvedCoverSheet.value?.code,
|
||||
)
|
||||
if (batch) {
|
||||
const isCorrection = !!supersedesBatchId.value
|
||||
const usedCoverSheet = !!resolvedCoverSheet.value
|
||||
selectedFile.value = null
|
||||
batchType.value = ''
|
||||
track.value = 'BACKFILL'
|
||||
patientId.value = undefined
|
||||
supersedesBatchId.value = undefined
|
||||
toast.success(isCorrection ? 'Correction batch created' : 'Batch uploaded successfully')
|
||||
clearCoverSheet()
|
||||
toast.success(
|
||||
isCorrection
|
||||
? 'Correction batch created'
|
||||
: usedCoverSheet
|
||||
? 'Batch uploaded with cover sheet'
|
||||
: 'Batch uploaded successfully',
|
||||
)
|
||||
await loadRecent()
|
||||
}
|
||||
} catch (e: unknown) {
|
||||
|
||||
Reference in New Issue
Block a user