434 lines
14 KiB
Vue
434 lines
14 KiB
Vue
<script setup lang="ts">
|
||
import { ref, onMounted } from 'vue'
|
||
import { useRouter } from 'vue-router'
|
||
import { useQuasar } from 'quasar'
|
||
import moment from 'moment'
|
||
import Camera from 'simple-vue-camera'
|
||
|
||
// import Type
|
||
import type { FormRef } from '@/interface/response/checkin'
|
||
// import components
|
||
import MapCheck from '@/components/MapCheckin.vue'
|
||
// import Stores
|
||
import { useCounterMixin } from '@/stores/mixin'
|
||
|
||
const mixin = useCounterMixin()
|
||
const { date2Thai, dialogConfirm } = mixin
|
||
const router = useRouter()
|
||
const $q = useQuasar()
|
||
|
||
const stetusCheckin = ref(true)
|
||
|
||
onMounted(() => {
|
||
updateClock()
|
||
})
|
||
|
||
//time
|
||
const dateNow = ref<Date>(new Date())
|
||
const Thai = ref<Date>(dateNow.value)
|
||
const formattedS = ref()
|
||
const formattedM = ref()
|
||
const formattedH = ref()
|
||
|
||
function updateClock() {
|
||
const date = Date.now()
|
||
const hh = moment(date).format('HH')
|
||
const mm = moment(date).format('mm')
|
||
const ss = moment(date).format('ss')
|
||
formattedS.value = ss
|
||
formattedM.value = mm
|
||
formattedH.value = hh
|
||
}
|
||
setInterval(updateClock, 1000)
|
||
|
||
//location
|
||
const location = ref<string>('')
|
||
const coordinates = ref<string>('13° 43’ 45” N 100° 31’ 26” E')
|
||
const workplace = ref<string>('in-place')
|
||
const useLocation = ref<string | null>('')
|
||
const model = ref<string | null>('')
|
||
const options = ref<string[]>([
|
||
'ปฏิบัติงานที่บ้าน',
|
||
'ลืมลงเวลาปฏิบัติงาน',
|
||
'ไปประชุม/อบรม/สัมมนา/ปฏิบัติงานที่บ้านนอกสถานที่',
|
||
'ขออนุญาตออกนอกสถานที่',
|
||
'อื่นๆ',
|
||
])
|
||
function selectLocation() {
|
||
if (model.value === 'อื่นๆ') {
|
||
useLocation.value = ''
|
||
} else {
|
||
useLocation.value = model.value
|
||
}
|
||
}
|
||
//camera
|
||
const camera = ref<InstanceType<typeof Camera>>()
|
||
const cameraIsOn = ref<boolean>(false)
|
||
const img = ref<any>(undefined)
|
||
const photoWidth = ref<number>(350)
|
||
const photoHeight = ref<number>(350)
|
||
|
||
const openCamera = () => {
|
||
cameraIsOn.value ? camera.value?.stop() : camera.value?.start()
|
||
cameraIsOn.value = !cameraIsOn.value
|
||
}
|
||
|
||
async function capturePhoto() {
|
||
const imageBlob: any = await camera.value?.snapshot(
|
||
{ width: photoWidth.value, height: photoHeight.value },
|
||
'image/png',
|
||
0.5
|
||
)
|
||
|
||
//ไฟล์รูป
|
||
camera.value?.stop()
|
||
const url = URL.createObjectURL(imageBlob)
|
||
img.value = url
|
||
}
|
||
|
||
function refreshPhoto() {
|
||
img.value = undefined
|
||
camera.value?.start()
|
||
}
|
||
|
||
// validate
|
||
const useLocationRef = ref<object | null>(null)
|
||
const modelRef = ref<object | null>(null)
|
||
const objectRef: FormRef = {
|
||
model: modelRef,
|
||
useLocation: useLocationRef,
|
||
}
|
||
function validateForm() {
|
||
const hasError = []
|
||
for (const key in objectRef) {
|
||
if (Object.prototype.hasOwnProperty.call(objectRef, key)) {
|
||
const property = objectRef[key]
|
||
if (property.value && typeof property.value.validate === 'function') {
|
||
const isValid = property.value.validate()
|
||
hasError.push(isValid)
|
||
}
|
||
}
|
||
}
|
||
if (hasError.every((result) => result === true)) {
|
||
confirm()
|
||
} else {
|
||
console.log('ไม่ผ่าน ')
|
||
}
|
||
}
|
||
// ยืนยันการลงเวลา
|
||
const dialogTime = ref<boolean>(false)
|
||
const confirm = () => {
|
||
// ยิงไปที่ api แล้วแสดง popup
|
||
dialogTime.value = true
|
||
}
|
||
|
||
// class
|
||
const getClass = (val: boolean) => {
|
||
return {
|
||
'bg-primary text-white col-12 row items-center q-px-md q-py-sm': val,
|
||
'bg-red-9 text-white col-12 row items-center q-px-md q-py-sm': !val,
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<template>
|
||
<div class="col-12 row justify-center">
|
||
<div class="col-xs-12 col-sm-12 col-md-12">
|
||
<q-card flat class="row col-12 cardNone">
|
||
<div :class="getClass(stetusCheckin)">
|
||
<div class="col-2">
|
||
<!-- <q-btn
|
||
icon="mdi-arrow-left"
|
||
unelevated
|
||
round
|
||
dense
|
||
flat
|
||
color="white"
|
||
@click="router.go(-1)"
|
||
/> -->
|
||
</div>
|
||
<span class="text-body1 text-weight-bold col-8 text-center">
|
||
<span v-if="stetusCheckin">ลงเวลาเข้างาน</span>
|
||
<span v-else>ลงเวลาออกงาน</span>
|
||
</span>
|
||
<div class="col-2 text-right">
|
||
<q-btn
|
||
icon="history"
|
||
unelevated
|
||
rounded
|
||
dense
|
||
flat
|
||
color="white"
|
||
:label="$q.screen.gt.xs ? 'ประวัติการลงเวลา' : ''"
|
||
:class="$q.screen.gt.xs ? 'q-px-md' : ''"
|
||
@click="router.push('/history')"
|
||
/>
|
||
</div>
|
||
</div>
|
||
<div class="col-12 q-pa-md text-grey-9">
|
||
<div class="col-12 row justify-center">
|
||
<div class="col-12 row q-py-sm justify-center">
|
||
<div
|
||
class="col-xs-12 col-sm-10 text-h6 text-center text-weight-bold"
|
||
>
|
||
{{ date2Thai(Thai) }}
|
||
</div>
|
||
<div class="row col-12 justify-center q-py-sm">
|
||
<div class="colunm">
|
||
<div class="text-h3 text-weight-bold">
|
||
{{ formattedH }}<span class="q-ma-md">:</span>
|
||
</div>
|
||
</div>
|
||
<div class="colunm">
|
||
<div class="text-h3 text-weight-bold">
|
||
{{ formattedM }}<span class="q-ma-md">:</span>
|
||
</div>
|
||
</div>
|
||
<div class="colunm">
|
||
<div class="text-h3 text-weight-bold">{{ formattedS }}</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="col-xs-12 col-md-11 row q-col-gutter-md">
|
||
<div class="col-12 col-sm-8">
|
||
<MapCheck />
|
||
</div>
|
||
<div class="col-12 col-sm-4">
|
||
<q-card flat bordered class="card-container">
|
||
<div
|
||
v-if="!cameraIsOn && img == null"
|
||
class="preview-placeholder"
|
||
@click="openCamera()"
|
||
>
|
||
<div class="text-center">
|
||
<q-icon
|
||
name="photo_camera"
|
||
color="blue-grey-3"
|
||
size="100px"
|
||
class="center-icon"
|
||
/>
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<!-- แสดงกล้องตอนกดถ่ายภาพ -->
|
||
<Camera
|
||
:resolution="{ width: photoWidth, height: photoHeight }"
|
||
ref="camera"
|
||
:autoplay="false"
|
||
:style="!img ? 'display: block' : 'display: none'"
|
||
/>
|
||
|
||
<!-- แสดงรูปเมื่อกด capture -->
|
||
<div v-if="img" class="image-container">
|
||
<q-img :src="img" class="image-element"></q-img>
|
||
</div>
|
||
|
||
<div
|
||
v-if="cameraIsOn"
|
||
class="absolute-bottom-right q-ma-md"
|
||
>
|
||
<q-btn
|
||
v-if="img == null"
|
||
round
|
||
push
|
||
icon="photo_camera"
|
||
size="md"
|
||
color="positive"
|
||
@click="capturePhoto"
|
||
/>
|
||
<q-btn
|
||
v-else
|
||
round
|
||
push
|
||
icon="refresh"
|
||
size="md"
|
||
color="negative"
|
||
@click="refreshPhoto"
|
||
/>
|
||
</div>
|
||
</div>
|
||
</q-card>
|
||
</div>
|
||
|
||
<div class="col-12 q-mb-md">
|
||
<q-card
|
||
bordered
|
||
flat
|
||
:class="
|
||
$q.screen.gt.xs
|
||
? 'q-px-md q-py-sm row items-center shadow-0'
|
||
: 'q-pa-md row items-center shadow-0'
|
||
"
|
||
>
|
||
<div class="text-weight-bold">สถานที่ทำงาน</div>
|
||
<div
|
||
:class="
|
||
$q.screen.gt.xs
|
||
? 'row q-gutter-md q-pl-md col-sm-6 col-md-3'
|
||
: 'column col-12'
|
||
"
|
||
>
|
||
<q-radio
|
||
v-model="workplace"
|
||
checked-icon="task_alt"
|
||
unchecked-icon="panorama_fish_eye"
|
||
val="in-place"
|
||
label="ในสถานที่"
|
||
/>
|
||
<q-radio
|
||
v-model="workplace"
|
||
checked-icon="task_alt"
|
||
unchecked-icon="panorama_fish_eye"
|
||
val="off-site"
|
||
label="นอกสถานที่"
|
||
/>
|
||
</div>
|
||
<div
|
||
class="col-xs-12 col-sm-6 col-md-4"
|
||
v-if="workplace == 'off-site'"
|
||
>
|
||
<q-select
|
||
ref="modelRef"
|
||
dense
|
||
class="q-ml-md"
|
||
outlined
|
||
v-model="model"
|
||
:options="options"
|
||
prefix="ระบุสถานที่ :"
|
||
:rules="[(val) => !!val || 'กรุณาระบุสถานที่']"
|
||
lazy-rules
|
||
@update:model-value="selectLocation()"
|
||
/>
|
||
</div>
|
||
<div
|
||
class="col-xs-12 col-sm-6 col-md-4"
|
||
v-if="model == 'อื่นๆ' && workplace === 'off-site'"
|
||
>
|
||
<q-input
|
||
ref="useLocationRef"
|
||
dense
|
||
class="q-ml-md"
|
||
outlined
|
||
v-model="useLocation"
|
||
label="ระบุสถานที่"
|
||
:rules="[(val) => !!val || 'กรุณาระบุสถานที่']"
|
||
lazy-rules
|
||
/>
|
||
</div>
|
||
</q-card>
|
||
|
||
<div class="col-12 text-right">
|
||
<q-separator />
|
||
<div class="col-12 q-pa-md">
|
||
<p
|
||
:class="
|
||
$q.screen.gt.xs
|
||
? 'text-red text-caption '
|
||
: 'text-red text-caption text-center'
|
||
"
|
||
>
|
||
*หมายเหตุ คลิกลงเวลาเข้างานแล้วระบบจะลงเวลาทันที
|
||
</p>
|
||
<q-btn
|
||
:label="
|
||
stetusCheckin == true ? 'ลงเวลาเข้างาน' : 'ลงเวลาออกงาน'
|
||
"
|
||
:color="
|
||
stetusCheckin == true && img == null
|
||
? 'grey-6'
|
||
: 'primary'
|
||
"
|
||
push
|
||
size="14px"
|
||
:class="$q.screen.gt.xs ? 'q-px-md' : 'full-width'"
|
||
:disable="camera && img ? false : true"
|
||
@click="validateForm"
|
||
/>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</q-card>
|
||
</div>
|
||
</div>
|
||
|
||
<q-dialog v-model="dialogTime">
|
||
<q-card class="full-width cardNone">
|
||
<div :class="getClass(stetusCheckin)">
|
||
<div class="text-body1 text-center col-12 text-weight-bold">
|
||
<span v-if="stetusCheckin">ลงเวลาเข้างานของคุณ</span>
|
||
<span v-else>ลงเวลาออกงานของคุณ</span>
|
||
</div>
|
||
</div>
|
||
<q-card-section class="row col-12 justify-center">
|
||
<div class="bg-grey-2 rounded-borders q-pa-md col-11">
|
||
<div class="col-12 text-subtitle1 text-center text-weight-medium">
|
||
{{ date2Thai(Thai) }}
|
||
</div>
|
||
<div class="row col-12 justify-center q-pt-sm">
|
||
<div class="text-h3 text-weight-bold">
|
||
{{ formattedH }}<span class="q-ma-md">:</span>
|
||
</div>
|
||
<div class="text-h3 text-weight-bold">{{ formattedM }}</div>
|
||
</div>
|
||
</div>
|
||
<div class="col-12 text-center row q-pt-md">
|
||
<div class="col-12 text-subtitle1 text-weight-medium text-secondary">
|
||
{{ location }}
|
||
</div>
|
||
<div class="col-12 text-grey-7">{{ coordinates }}</div>
|
||
</div>
|
||
</q-card-section>
|
||
|
||
<q-card-actions align="center" class="q-mb-md row">
|
||
<q-btn
|
||
class="col-xs-11 col-sm-6"
|
||
push
|
||
label="ตกลง"
|
||
color="secondary"
|
||
v-close-popup
|
||
/>
|
||
</q-card-actions>
|
||
</q-card>
|
||
</q-dialog>
|
||
</template>
|
||
<style scoped>
|
||
.q-card.cardImg:hover {
|
||
border: 1px solid #02a998 !important;
|
||
}
|
||
|
||
.center-icon {
|
||
position: absolute;
|
||
top: 50%;
|
||
left: 50%;
|
||
transform: translate(-50%, -50%);
|
||
}
|
||
.card-container {
|
||
position: relative;
|
||
overflow: hidden;
|
||
height: 350px; /* Adjust as needed */
|
||
background: #f6f5f5;
|
||
}
|
||
.image-container {
|
||
position: absolute;
|
||
top: 0;
|
||
left: 0;
|
||
width: 100%;
|
||
height: 100%;
|
||
}
|
||
|
||
.image-element {
|
||
width: 100%;
|
||
height: 100%;
|
||
object-fit: cover;
|
||
border-radius: 5px; /* Adjust as needed */
|
||
}
|
||
|
||
.preview-placeholder {
|
||
width: 100%;
|
||
height: 100%;
|
||
}
|
||
</style>
|