Compare commits

...

5 commits

Author SHA1 Message Date
DESKTOP-1R2VSQH\Lenovo ThinkPad E490
b4b60cc89b Merge branch 'dev' of https://192.168.1.60/hrms-bangkok/hrms-checkin into dev
All checks were successful
Build & Deploy on Dev / build (push) Successful in 2m28s
2026-01-19 15:03:20 +07:00
DESKTOP-1R2VSQH\Lenovo ThinkPad E490
530d7de122 Merge branch 'develop' into dev 2026-01-19 15:01:46 +07:00
DESKTOP-1R2VSQH\Lenovo ThinkPad E490
112e6911ad fix 2026-01-19 15:01:13 +07:00
DESKTOP-1R2VSQH\Lenovo ThinkPad E490
8b26530d52 fix:privacy 2026-01-19 14:47:32 +07:00
DESKTOP-1R2VSQH\Lenovo ThinkPad E490
9793df1c57 fix 2026-01-19 13:35:54 +07:00
6 changed files with 138 additions and 14 deletions

View file

@ -4,9 +4,11 @@ import { loadModules } from 'esri-loader'
import axios from 'axios' import axios from 'axios'
import { useCounterMixin } from '@/stores/mixin' import { useCounterMixin } from '@/stores/mixin'
import { useQuasar } from 'quasar' import { useQuasar } from 'quasar'
import { usePrivacyStore } from '@/stores/privacy'
const mixin = useCounterMixin() const mixin = useCounterMixin()
const { messageError } = mixin const { messageError } = mixin
const privacyStore = usePrivacyStore()
// import type { LocationObject } from '@/interface/index/Main' // import type { LocationObject } from '@/interface/index/Main'
const mapElement = ref<HTMLElement | null>(null) const mapElement = ref<HTMLElement | null>(null)
@ -200,6 +202,16 @@ async function initializeMap() {
const locationGranted = ref(false) const locationGranted = ref(false)
// Function to request location permission // Function to request location permission
const requestLocationPermission = () => { const requestLocationPermission = () => {
// privacy
if (!privacyStore.isAccepted) {
$q.notify({
type: 'warning',
message: 'กรุณายอมรับนโยบายคุ้มครองข้อมูลส่วนบุคคลก่อนใช้งานแผนที่',
position: 'top',
})
return
}
if (!navigator.geolocation) { if (!navigator.geolocation) {
messageError( messageError(
$q, $q,
@ -261,7 +273,10 @@ defineExpose({
}) })
onMounted(async () => { onMounted(async () => {
await initializeMap() // privacy
if (privacyStore.isAccepted) {
await initializeMap()
}
}) })
</script> </script>

View file

@ -1,7 +1,12 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, computed } from 'vue' import { ref, computed, nextTick } from 'vue'
import { useQuasar } from 'quasar'
import http from '@/plugins/http' import http from '@/plugins/http'
import config from '@/app.config' import config from '@/app.config'
import { usePrivacyStore } from '@/stores/privacy'
const $q = useQuasar()
const privacyStore = usePrivacyStore()
const modal = defineModel<boolean>('modal', { const modal = defineModel<boolean>('modal', {
required: true, required: true,
@ -19,7 +24,7 @@ const privacyContent = {
title: 'วัตถุประสงค์การใช้ข้อมูล', title: 'วัตถุประสงค์การใช้ข้อมูล',
items: [ items: [
'เพื่อยืนยันตัวตนของผู้ปฏิบัติงานในการบันทึกเวลาเข้า-ออกการปฏิบัติราชการ', 'เพื่อยืนยันตัวตนของผู้ปฏิบัติงานในการบันทึกเวลาเข้า-ออกการปฏิบัติราชการ',
'เพื่อใช้เป็นหลักฐานประกอบการบริหารงานด้านทรัพยากรบุคคล การจ่ายค่าตอบแน และการกำกับ ดูแลการปฏิบัติราชการ', 'เพื่อใช้เป็นหลักฐานประกอบการบริหารงานด้านทรัพยากรบุคคล การจ่ายค่าตอบแน และการกำกับ ดูแลการปฏิบัติราชการ',
'เพื่อรักษาความถูกต้อง โปร่งใส และป้องกันการทุจริตในการบันทึกเวลาการปฏิบัติงาน', 'เพื่อรักษาความถูกต้อง โปร่งใส และป้องกันการทุจริตในการบันทึกเวลาการปฏิบัติงาน',
], ],
}, },
@ -48,6 +53,7 @@ const privacyContent = {
}, },
} }
const scrollContainer = ref<HTMLElement | null>(null)
const hasScrolledToBottom = ref(false) const hasScrolledToBottom = ref(false)
const acceptPrivacy = ref(false) const acceptPrivacy = ref(false)
const showDetails = ref(false) const showDetails = ref(false)
@ -55,6 +61,7 @@ const isAcceptDisabled = computed(() => !acceptPrivacy.value)
const handleScroll = (event: Event) => { const handleScroll = (event: Event) => {
const target = event.target as HTMLElement const target = event.target as HTMLElement
if (!target) return if (!target) return
const { scrollTop, scrollHeight, clientHeight } = target const { scrollTop, scrollHeight, clientHeight } = target
@ -70,15 +77,27 @@ const handleAccept = async () => {
system: 'checkin', system: 'checkin',
accept: true, accept: true,
}) })
privacyStore.setAccepted(true)
modal.value = false
} catch (error) {} } catch (error) {}
} }
const handleDecline = () => {
modal.value = false
}
const toggleDetails = () => { const toggleDetails = () => {
showDetails.value = !showDetails.value showDetails.value = !showDetails.value
checkIfScrollable()
}
const checkIfScrollable = () => {
nextTick(() => {
const container = scrollContainer.value?.$el || scrollContainer.value
if (container) {
const { scrollHeight, clientHeight } = container
if (scrollHeight <= clientHeight + 20) {
hasScrolledToBottom.value = true
}
}
})
} }
</script> </script>
@ -103,7 +122,6 @@ const toggleDetails = () => {
dense dense
class="absolute-top-right q-ma-sm" class="absolute-top-right q-ma-sm"
v-close-popup v-close-popup
@click="handleDecline"
/> />
</q-card-section> </q-card-section>
@ -237,7 +255,6 @@ const toggleDetails = () => {
unelevated unelevated
no-caps no-caps
class="action-btn" class="action-btn"
@click="handleDecline"
v-close-popup v-close-popup
/> />
</q-card-actions> </q-card-actions>

View file

@ -0,0 +1,52 @@
import { useQuasar } from 'quasar'
import { usePrivacyStore } from '@/stores/privacy'
export function usePermissions() {
const $q = useQuasar()
const privacyStore = usePrivacyStore()
// const checkCameraPermission = (): boolean => {
// if (!privacyStore.isAccepted) {
// privacyStore.modalPrivacy = true
// $q.notify({
// type: 'warning',
// message: 'กรุณายอมรับนโยบายคุ้มครองข้อมูลส่วนบุคคลก่อนใช้งานกล้อง',
// position: 'top',
// })
// return false
// }
// return true
// }
// const checkLocationPermission = (): boolean => {
// if (!privacyStore.isAccepted) {
// privacyStore.modalPrivacy = true
// $q.notify({
// type: 'warning',
// message: 'กรุณายอมรับนโยบายคุ้มครองข้อมูลส่วนบุคคลก่อนใช้งานแผนที่',
// position: 'top',
// })
// return false
// }
// return true
// }
const checkPrivacyAccepted = (): boolean => {
if (!privacyStore.isAccepted) {
privacyStore.modalPrivacy = true
$q.notify({
type: 'warning',
message: 'กรุณายอมรับนโยบายคุ้มครองข้อมูลส่วนบุคคลก่อนใช้งาน',
position: 'top',
})
return false
}
return true
}
return {
// checkCameraPermission,
// checkLocationPermission,
checkPrivacyAccepted,
}
}

22
src/stores/privacy.ts Normal file
View file

@ -0,0 +1,22 @@
import { defineStore } from 'pinia'
import { ref } from 'vue'
export const usePrivacyStore = defineStore('privacy', () => {
const modalPrivacy = ref<boolean>(false)
const isAccepted = ref<boolean>(false)
const setAccepted = (value: boolean) => {
isAccepted.value = value
}
const reset = () => {
isAccepted.value = false
}
return {
modalPrivacy,
isAccepted,
setAccepted,
reset,
}
})

View file

@ -7,6 +7,8 @@ import Camera from 'simple-vue-camera'
import config from '@/app.config' import config from '@/app.config'
import http from '@/plugins/http' import http from '@/plugins/http'
import { useCounterMixin } from '@/stores/mixin' import { useCounterMixin } from '@/stores/mixin'
import { usePermissions } from '@/composables/usePermissions'
import { usePrivacyStore } from '@/stores/privacy'
import type { FormRef, OptionReason } from '@/interface/response/checkin' import type { FormRef, OptionReason } from '@/interface/response/checkin'
@ -15,6 +17,8 @@ import MapCheck from '@/components/AscGISMap.vue'
const mixin = useCounterMixin() const mixin = useCounterMixin()
const { date2Thai, showLoader, hideLoader, messageError, dialogConfirm } = mixin const { date2Thai, showLoader, hideLoader, messageError, dialogConfirm } = mixin
const $q = useQuasar() const $q = useQuasar()
const { checkPrivacyAccepted } = usePermissions()
const privacyStore = usePrivacyStore()
const modalTime = ref<boolean>(false) // Dailog const modalTime = ref<boolean>(false) // Dailog
const checkStatus = ref<string>('') const checkStatus = ref<string>('')
@ -261,6 +265,11 @@ async function stopChecking() {
/** function เปิดกล้อง*/ /** function เปิดกล้อง*/
async function openCamera() { async function openCamera() {
// privacy
if (!checkPrivacyAccepted()) {
return
}
// change camera device // change camera device
if (cameraIsOn.value) { if (cameraIsOn.value) {
await camera.value?.stop() await camera.value?.stop()
@ -336,6 +345,11 @@ const timeChickin = ref<string>('') //เวลาเข้างาน,เว
/** function ยืนยันการลงเวลาเข้า - ออก*/ /** function ยืนยันการลงเวลาเข้า - ออก*/
async function confirm() { async function confirm() {
// privacy
if (!checkPrivacyAccepted()) {
return
}
if (!formLocation.POI || !formLocation.lat || !formLocation.lng) { if (!formLocation.POI || !formLocation.lat || !formLocation.lng) {
mapRef.value?.requestLocationPermission() mapRef.value?.requestLocationPermission()
return return
@ -473,7 +487,11 @@ onMounted(async () => {
isLoadingCheckTime.value = true isLoadingCheckTime.value = true
updateClock() updateClock()
startChecking() // status #1 startChecking() // status #1
mapRef.value?.requestLocationPermission()
// privacy
if (privacyStore.isAccepted) {
mapRef.value?.requestLocationPermission()
}
}) })
</script> </script>

View file

@ -8,6 +8,7 @@ import config from '@/app.config'
import avatar from '@/assets/avatar_user.jpg' import avatar from '@/assets/avatar_user.jpg'
import { logout, tokenParsed, getCookie } from '@/plugins/auth' import { logout, tokenParsed, getCookie } from '@/plugins/auth'
import { useCounterMixin } from '@/stores/mixin' import { useCounterMixin } from '@/stores/mixin'
import { usePrivacyStore } from '@/stores/privacy'
import type { notiType } from '@/interface/index/Main' import type { notiType } from '@/interface/index/Main'
import type { Noti } from '@/interface/response/Main' import type { Noti } from '@/interface/response/Main'
@ -16,6 +17,7 @@ import DialogHeader from '@/components/DialogHeader.vue'
import PopupPrivacy from '@/components/PopupPrivacy.vue' import PopupPrivacy from '@/components/PopupPrivacy.vue'
const mixin = useCounterMixin() const mixin = useCounterMixin()
const privacyStore = usePrivacyStore()
const { const {
date2Thai, date2Thai,
hideLoader, hideLoader,
@ -56,8 +58,6 @@ const isPwdOld = ref<boolean>(true)
const isPwdNewOld = ref<boolean>(true) const isPwdNewOld = ref<boolean>(true)
const isPwdReNewOld = ref<boolean>(true) const isPwdReNewOld = ref<boolean>(true)
const modalPrivacy = ref<boolean>(false)
/** /**
* งกนดงขอมลจำนวนการแจงเตอน * งกนดงขอมลจำนวนการแจงเตอน
*/ */
@ -173,7 +173,7 @@ async function fetchKeycloakPosition() {
.get(config.API.keycloakPosition()) .get(config.API.keycloakPosition())
.then(async (res) => { .then(async (res) => {
const data = await res.data.result const data = await res.data.result
modalPrivacy.value = !data.privacyCheckin ? true : false privacyStore.modalPrivacy = !data.privacyCheckin ? true : false
// //
if (data.avatarName) { if (data.avatarName) {
await getImg(data.profileId, data.avatarName) await getImg(data.profileId, data.avatarName)
@ -606,7 +606,7 @@ onMounted(async () => {
</q-card> </q-card>
</q-dialog> </q-dialog>
<popup-privacy v-model:modal="modalPrivacy" /> <popup-privacy v-model:modal="privacyStore.modalPrivacy" />
</template> </template>
<style> <style>