326 lines
8.9 KiB
Vue
326 lines
8.9 KiB
Vue
<script setup lang="ts">
|
|
import QRCode from 'qrcode';
|
|
import { onMounted } from 'vue';
|
|
|
|
const props = defineProps<{
|
|
patientId: string;
|
|
status: string;
|
|
generatedQRData: string | null;
|
|
statusOptions: Array<{ title: string; value: string }>;
|
|
primaryColor: string;
|
|
generateRandomPatientId: () => string;
|
|
}>();
|
|
|
|
const emit = defineEmits<{
|
|
'update:patientId': [value: string];
|
|
'update:status': [value: string];
|
|
'generate': [];
|
|
'quick-generate': [patientId: string, status: string];
|
|
'download': [];
|
|
'copy': [];
|
|
'share': [];
|
|
'generate-random-id': [];
|
|
}>();
|
|
|
|
const localPatientId = computed({
|
|
get: () => props.patientId,
|
|
set: (value) => emit('update:patientId', value),
|
|
});
|
|
|
|
const localStatus = computed({
|
|
get: () => props.status,
|
|
set: (value) => emit('update:status', value),
|
|
});
|
|
|
|
const handleGenerate = () => {
|
|
emit('generate');
|
|
};
|
|
|
|
const handleQuickGenerate = (status: string) => {
|
|
const randomId = props.generateRandomPatientId();
|
|
emit('quick-generate', randomId, status);
|
|
};
|
|
|
|
const handleDownload = () => {
|
|
emit('download');
|
|
};
|
|
|
|
const handleCopy = () => {
|
|
emit('copy');
|
|
};
|
|
|
|
const handleShare = () => {
|
|
emit('share');
|
|
};
|
|
|
|
const handleGenerateRandomId = () => {
|
|
emit('generate-random-id');
|
|
};
|
|
|
|
const qrContainerRef = ref<HTMLDivElement | null>(null);
|
|
|
|
// Function to render QR code
|
|
const renderQRCode = async (data: string) => {
|
|
if (!data) {
|
|
return;
|
|
}
|
|
|
|
// Wait for DOM to be ready
|
|
await nextTick();
|
|
|
|
// Wait a bit more to ensure ref is mounted (especially when switching tabs)
|
|
await new Promise(resolve => setTimeout(resolve, 150));
|
|
|
|
if (!qrContainerRef.value) {
|
|
console.warn('QR container ref not available yet, retrying...');
|
|
// Retry after a short delay
|
|
setTimeout(() => {
|
|
if (qrContainerRef.value && data) {
|
|
renderQRCode(data);
|
|
}
|
|
}, 300);
|
|
return;
|
|
}
|
|
|
|
try {
|
|
// Clear previous content
|
|
qrContainerRef.value.innerHTML = '';
|
|
|
|
// Generate QR code
|
|
const qrDataUrl = await QRCode.toDataURL(data, {
|
|
errorCorrectionLevel: 'M',
|
|
type: 'image/png',
|
|
quality: 0.92,
|
|
margin: 1,
|
|
color: {
|
|
dark: '#000000',
|
|
light: '#FFFFFF',
|
|
},
|
|
width: 300,
|
|
});
|
|
|
|
// Create and append image
|
|
const img = document.createElement('img');
|
|
img.src = qrDataUrl;
|
|
img.alt = 'QR Code';
|
|
img.style.width = '100%';
|
|
img.style.maxWidth = '300px';
|
|
img.style.height = 'auto';
|
|
img.style.display = 'block';
|
|
img.style.margin = '0 auto';
|
|
qrContainerRef.value.appendChild(img);
|
|
} catch (error) {
|
|
console.error('Error generating QR code:', error);
|
|
}
|
|
};
|
|
|
|
// Watch for generatedQRData changes to render QR code
|
|
watch(() => props.generatedQRData, async (newData) => {
|
|
if (newData) {
|
|
// Use setTimeout to ensure DOM is ready, especially when switching tabs
|
|
setTimeout(() => {
|
|
renderQRCode(newData);
|
|
}, 200);
|
|
} else if (qrContainerRef.value) {
|
|
// Clear container if data is cleared
|
|
qrContainerRef.value.innerHTML = '';
|
|
}
|
|
}, { immediate: true });
|
|
|
|
// Also watch for when component is mounted and ref is ready
|
|
onMounted(() => {
|
|
if (props.generatedQRData) {
|
|
// Delay to ensure ref is mounted
|
|
setTimeout(() => {
|
|
renderQRCode(props.generatedQRData!);
|
|
}, 300);
|
|
}
|
|
});
|
|
</script>
|
|
|
|
<template>
|
|
<div class="tab-content">
|
|
<!-- Status Header -->
|
|
<div class="status-header mb-3">
|
|
<div class="status-icon-wrapper">
|
|
<v-icon :color="primaryColor" size="24">mdi-qrcode</v-icon>
|
|
</div>
|
|
<div class="status-text">
|
|
<h3 class="status-title">Generate QR Code untuk Testing</h3>
|
|
<p class="status-subtitle">Buat QR code yang bisa Anda scan di tab "Scan QR"</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Quick Preset Buttons -->
|
|
<div class="mb-3">
|
|
<p class="text-caption text-grey text-center mb-2">Quick Test QR Codes:</p>
|
|
<v-row dense>
|
|
<v-col cols="6">
|
|
<v-btn
|
|
variant="outlined"
|
|
color="success"
|
|
size="small"
|
|
@click="handleQuickGenerate('ALLOWED')"
|
|
class="text-none"
|
|
block
|
|
>
|
|
<v-icon start size="16">mdi-check-circle</v-icon>
|
|
Test ALLOWED
|
|
</v-btn>
|
|
</v-col>
|
|
<v-col cols="6">
|
|
<v-btn
|
|
variant="outlined"
|
|
color="warning"
|
|
size="small"
|
|
@click="handleQuickGenerate('NOT_ALLOWED')"
|
|
class="text-none"
|
|
block
|
|
>
|
|
<v-icon start size="16">mdi-clock-alert</v-icon>
|
|
Test NOT_ALLOWED
|
|
</v-btn>
|
|
</v-col>
|
|
</v-row>
|
|
</div>
|
|
|
|
<!-- Form Generate -->
|
|
<v-form @submit.prevent="handleGenerate">
|
|
<v-text-field
|
|
v-model="localPatientId"
|
|
label="ID Pasien"
|
|
placeholder="Contoh: P-123456"
|
|
prepend-inner-icon="mdi-identifier"
|
|
variant="outlined"
|
|
:color="primaryColor"
|
|
class="input-modern mb-3"
|
|
density="comfortable"
|
|
clearable
|
|
hide-details="auto"
|
|
>
|
|
<template #append-inner>
|
|
<v-btn
|
|
icon
|
|
size="small"
|
|
variant="text"
|
|
@click="handleGenerateRandomId"
|
|
title="Generate Random ID"
|
|
>
|
|
<v-icon size="20">mdi-refresh</v-icon>
|
|
</v-btn>
|
|
</template>
|
|
</v-text-field>
|
|
|
|
<v-select
|
|
v-model="localStatus"
|
|
label="Status Check-in"
|
|
:items="statusOptions"
|
|
prepend-inner-icon="mdi-shield-check"
|
|
variant="outlined"
|
|
:color="primaryColor"
|
|
class="input-modern mb-4"
|
|
density="comfortable"
|
|
hide-details="auto"
|
|
></v-select>
|
|
|
|
<div class="d-flex justify-center">
|
|
<v-btn
|
|
class="btn-primary-modern btn-centered"
|
|
size="large"
|
|
type="submit"
|
|
elevation="0"
|
|
:disabled="!localPatientId"
|
|
>
|
|
<v-icon start size="20">mdi-qrcode-plus</v-icon>
|
|
Generate QR Code
|
|
</v-btn>
|
|
</div>
|
|
</v-form>
|
|
|
|
<!-- QR Code Display -->
|
|
<div v-if="props.generatedQRData" class="qr-display mt-6">
|
|
<v-card variant="outlined" class="pa-4">
|
|
<div class="text-center">
|
|
<p class="text-subtitle-2 text-grey mb-3">QR Code Anda:</p>
|
|
<div ref="qrContainerRef" class="qr-code-container mb-4"></div>
|
|
|
|
<v-chip :color="localStatus === 'ALLOWED' ? 'success' : 'warning'" class="mb-3">
|
|
<v-icon start>{{ localStatus === 'ALLOWED' ? 'mdi-check' : 'mdi-clock-alert' }}</v-icon>
|
|
{{ localStatus === 'ALLOWED' ? 'Diizinkan Check-in' : 'Belum Diizinkan' }}
|
|
</v-chip>
|
|
|
|
<p class="text-body-2 text-grey mb-4">
|
|
Data: {{ props.generatedQRData }}
|
|
</p>
|
|
|
|
<!-- Action Buttons -->
|
|
<v-row dense>
|
|
<v-col cols="4">
|
|
<v-btn
|
|
variant="outlined"
|
|
:color="primaryColor"
|
|
block
|
|
size="small"
|
|
@click="handleDownload"
|
|
class="text-none"
|
|
>
|
|
<v-icon start size="18">mdi-download</v-icon>
|
|
Download
|
|
</v-btn>
|
|
</v-col>
|
|
<v-col cols="4">
|
|
<v-btn
|
|
variant="outlined"
|
|
:color="primaryColor"
|
|
block
|
|
size="small"
|
|
@click="handleCopy"
|
|
class="text-none"
|
|
>
|
|
<v-icon start size="18">mdi-content-copy</v-icon>
|
|
Copy
|
|
</v-btn>
|
|
</v-col>
|
|
<v-col cols="4">
|
|
<v-btn
|
|
variant="outlined"
|
|
:color="primaryColor"
|
|
block
|
|
size="small"
|
|
@click="handleShare"
|
|
class="text-none"
|
|
>
|
|
<v-icon start size="18">mdi-share-variant</v-icon>
|
|
Share
|
|
</v-btn>
|
|
</v-col>
|
|
</v-row>
|
|
</div>
|
|
</v-card>
|
|
|
|
<!-- Instructions -->
|
|
<v-alert
|
|
type="success"
|
|
variant="tonal"
|
|
class="mt-4 text-body-2"
|
|
>
|
|
<template #prepend>
|
|
<v-icon>mdi-information</v-icon>
|
|
</template>
|
|
<strong>Cara menggunakan untuk Testing:</strong>
|
|
<ol class="ml-4 mt-2">
|
|
<li>Gunakan tombol <strong>"Test ALLOWED"</strong> atau <strong>"Test NOT_ALLOWED"</strong> untuk generate QR cepat, atau isi form manual</li>
|
|
<li>Klik <strong>"Download"</strong> untuk menyimpan QR code ke komputer</li>
|
|
<li>Buka file QR code yang didownload (bisa di HP atau layar lain)</li>
|
|
<li>Pindah ke tab <strong>"Scan QR"</strong> dan scan QR code tersebut</li>
|
|
<li>Atau gunakan <strong>"Copy"</strong> untuk menyalin QR ke clipboard dan paste di aplikasi lain</li>
|
|
</ol>
|
|
</v-alert>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped lang="scss">
|
|
@import '~/assets/scss/checkin/components';
|
|
</style>
|