Files
web-antrean/components/verification/QRVerificationDialog.vue
T

315 lines
6.8 KiB
Vue

<template>
<v-dialog
:model-value="modelValue"
@update:model-value="$emit('update:modelValue', $event)"
max-width="600"
transition="dialog-bottom-transition"
scrollable
>
<v-card class="modal-card">
<!-- Modal Header -->
<div class="modal-header">
<v-icon size="72" color="white" class="mb-3">mdi-qrcode-scan</v-icon>
<h2 class="modal-title">Aktivasi Akun</h2>
<p class="modal-subtitle">{{ patient.nama }}</p>
<v-chip color="white" class="modal-chip" variant="flat" size="default">
<span class="modal-chip-text">RM: {{ patient.rm }}</span>
</v-chip>
</div>
<!-- Modal Content -->
<v-card-text class="pa-7">
<v-text-field
:model-value="phoneNumber"
@update:model-value="$emit('update:phoneNumber', $event)"
:disabled="qrGenerated"
label="Nomor Telepon Pasien"
placeholder="08xxxxxxxxxx"
variant="outlined"
density="comfortable"
:color="qrGenerated ? 'grey' : 'primary-600'"
:rules="[v => v.length >= 8 || 'Min. 8 digit']"
prepend-inner-icon="mdi-phone"
class="mb-3 phone-field"
base-color="grey-darken-1"
/>
<!-- Generate Button -->
<v-btn
v-if="!qrGenerated"
:disabled="!isPhoneValid"
color="secondary-600"
block
size="x-large"
class="generate-btn"
@click="$emit('generate')"
rounded="xl"
>
<v-icon left size="32">mdi-qrcode-plus</v-icon>
Generate QR Code
</v-btn>
<!-- QR Code Container -->
<div v-else class="qr-container">
<div class="pulse-icon">
<v-icon color="secondary-600" :size="isMobile ? 48 : 64">
mdi-cellphone-check
</v-icon>
</div>
<h3 class="qr-title">Pindai QR Code</h3>
<p class="qr-subtitle">Arahkan kamera smartphone ke QR code</p>
<div class="d-flex justify-center mb-3 mb-sm-4">
<div class="qr-frame">
<slot name="qr-code" />
</div>
</div>
</div>
</v-card-text>
<!-- Modal Actions -->
<v-card-actions v-if="qrGenerated" class="modal-actions">
<v-btn
color="secondary-600"
variant="outlined"
@click="$emit('reload')"
prepend-icon="mdi-reload"
size="x-large"
class="modal-action-btn"
rounded="xl"
>
Reload QR
</v-btn>
<v-btn
color="primary-600"
variant="flat"
@click="$emit('complete')"
prepend-icon="mdi-check-circle"
size="x-large"
class="modal-action-btn-primary"
rounded="xl"
>
Selesai
</v-btn>
</v-card-actions>
<v-card-actions v-else class="modal-actions">
<v-btn
color="neutral-600"
variant="text"
@click="$emit('close')"
size="x-large"
block
rounded="xl"
class="modal-cancel-btn"
>
Batal
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</template>
<script setup>
import { computed } from 'vue';
const props = defineProps({
modelValue: {
type: Boolean,
required: true
},
patient: {
type: Object,
required: true
},
phoneNumber: {
type: String,
default: ''
},
qrGenerated: {
type: Boolean,
default: false
},
isMobile: {
type: Boolean,
default: false
}
});
const emit = defineEmits([
'update:modelValue',
'update:phoneNumber',
'generate',
'reload',
'complete',
'close'
]);
const isPhoneValid = computed(() => props.phoneNumber.length >= 8);
</script>
<style scoped lang="scss">
$neutral-100: #FFFFFF;
$neutral-600: #89939E;
$neutral-700: #717171;
$primary-600: #FFA532;
$secondary-200: #EDF5FF;
$secondary-600: #0671E0;
$secondary-700: #0053AD;
$font-family-base: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
$font-weight-semibold: 600;
$font-weight-bold: 700;
$font-weight-extra-bold: 800;
$font-weight-black: 900;
.modal-card {
border-radius: 16px;
overflow: hidden;
padding: 0;
font-family: $font-family-base;
}
.modal-header {
background: linear-gradient(135deg, $secondary-600 0%, $secondary-700 100%);
text-align: center;
padding: 28px;
box-shadow: 0 4px 12px rgba(6, 99, 199, 0.2);
}
.modal-title {
font-size: 32px;
font-weight: $font-weight-black;
color: $neutral-100;
margin: 8px 0;
font-family: $font-family-base;
}
.modal-subtitle {
font-size: 20px;
color: $neutral-100;
opacity: 0.95;
font-weight: $font-weight-semibold;
margin: 0;
font-family: $font-family-base;
}
.modal-chip {
margin-top: 8px;
font-weight: $font-weight-bold;
}
.modal-chip-text {
color: $primary-600;
font-weight: $font-weight-bold;
font-family: $font-family-base;
}
.phone-field {
font-family: $font-family-base;
}
.generate-btn {
margin-top: 12px;
font-weight: $font-weight-extra-bold;
font-size: 18px;
color: $neutral-100;
box-shadow: 0 2px 8px rgba(6, 99, 199, 0.25);
font-family: $font-family-base;
}
.qr-container {
background: $secondary-200;
border: 3px solid $secondary-600;
border-radius: 16px;
padding: 28px 16px;
margin-top: 24px;
box-shadow: 0 6px 20px rgba(6, 99, 199, 0.15);
text-align: center;
}
@media (min-width: 600px) {
.qr-container {
padding: 28px;
}
}
.pulse-icon {
margin-bottom: 16px;
}
.qr-title {
font-size: 24px;
font-weight: $font-weight-black;
color: $secondary-700;
margin-bottom: 8px;
font-family: $font-family-base;
}
@media (min-width: 600px) {
.qr-title {
font-size: 28px;
margin-bottom: 12px;
}
}
.qr-subtitle {
font-size: 13px;
color: $neutral-700;
margin-bottom: 16px;
font-weight: $font-weight-semibold;
font-family: $font-family-base;
}
@media (min-width: 600px) {
.qr-subtitle {
font-size: 14px;
margin-bottom: 24px;
}
}
.qr-frame {
padding: 16px;
background: $neutral-100;
border-radius: 12px;
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
display: inline-block;
border: 3px solid $secondary-600;
}
@media (min-width: 600px) {
.qr-frame {
padding: 24px;
border-radius: 16px;
border: 4px solid $secondary-600;
}
}
.modal-actions {
padding: 28px;
padding-top: 0;
}
.modal-action-btn,
.modal-action-btn-primary {
font-weight: $font-weight-bold;
font-size: 14px;
text-transform: none;
flex-grow: 1;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
font-family: $font-family-base;
}
.modal-action-btn-primary {
color: $neutral-100;
font-weight: $font-weight-extra-bold;
}
.modal-cancel-btn {
font-weight: $font-weight-bold;
font-size: 14px;
text-transform: none;
font-family: $font-family-base;
}
</style>