361 lines
13 KiB
Vue
361 lines
13 KiB
Vue
<script setup lang="ts">
|
|
import { ref, computed, nextTick } from 'vue'
|
|
import { useQuasar } from 'quasar'
|
|
import http from '@/plugins/http'
|
|
import config from '@/app.config'
|
|
import { usePrivacyStore } from '@/stores/privacy'
|
|
import { usePositionKeycloakStore } from '@/stores/positionKeycloak'
|
|
|
|
const $q = useQuasar()
|
|
const privacyStore = usePrivacyStore()
|
|
const positionKeycloakStore = usePositionKeycloakStore()
|
|
|
|
const modal = defineModel<boolean>('modal', {
|
|
required: true,
|
|
})
|
|
|
|
// Privacy content data
|
|
const privacyContent = {
|
|
title: 'การคุ้มครองข้อมูลส่วนบุคคล',
|
|
mainText:
|
|
'ระบบนี้มีการเก็บ และใช้ภาพถ่ายของท่าน เพื่อยืนยันตัวตนในการลงเวลาปฏิบัติราชการ ข้อมูลจะถูกใช้เฉพาะตามวัตถุประสงค์ของระบบ และจัดเก็บอย่างปลอดภัยตามกฎหมายคุ้มครองข้อมูลส่วนบุคคล (PDPA)',
|
|
warningText: '* หากท่านไม่กดยอมรับ อาจทำให้ไม่สามารถใช้งานระบบได้',
|
|
detailIntro:
|
|
'ข้าพเจ้าตกลงให้กรุงเทพมหานครเก็บ ใช้ และประมวลผลข้อมูลส่วนบุคคลประเภทภาพถ่ายของข้าพเจ้า ซึ่งได้มาจากการใช้งานระบบลงเวลาปฏิบัติราชการอิเล็กทรอนิกส์ ของระบบบริหารทรัพยากรบุคคลของกรุงเทพมหานคร (BMA - HRMS) ทั้งนี้ เพื่อวัตถุประสงค์ดังต่อไปนี้',
|
|
purposes: {
|
|
title: 'วัตถุประสงค์การใช้ข้อมูล',
|
|
items: [
|
|
'เพื่อยืนยันตัวตนของผู้ปฏิบัติงานในการบันทึกเวลาเข้า-ออกการปฏิบัติราชการ',
|
|
'เพื่อใช้เป็นหลักฐานประกอบการบริหารงานด้านทรัพยากรบุคคล การจ่ายค่าตอบแทน และการกำกับ ดูแลการปฏิบัติราชการ',
|
|
'เพื่อรักษาความถูกต้อง โปร่งใส และป้องกันการทุจริตในการบันทึกเวลาการปฏิบัติงาน',
|
|
],
|
|
},
|
|
understanding: {
|
|
items: [
|
|
'การเก็บ ใช้ และประมวลผลข้อมูลส่วนบุคคลดังกล่าว จะดำเนินการ เท่าที่จำเป็นตามวัตถุประสงค์ที่ระบุไว้เท่านั้น',
|
|
'หน่วยงานจะจัดให้มีมาตรการรักษาความมั่นคงปลอดภัยของข้อมูลส่วนบุคคล ตามที่กฎหมายกำหนด',
|
|
'ข้อมูลส่วนบุคคลของข้าพเจ้าจะถูกเก็บรักษา ตามระยะเวลาที่จำเป็น ต่อการปฏิบัติงานหรือเป็นไปตามที่กฎหมายกำหนด',
|
|
],
|
|
},
|
|
rights: {
|
|
title:
|
|
'ข้าพเจ้าทราบถึง สิทธิของเจ้าของข้อมูลส่วนบุคคล ตามพระราชบัญญัติคุ้มครองข้อมูลส่วนบุคคล พ.ศ. ๒๕๖๒ ได้แก่',
|
|
items: [
|
|
'สิทธิขอเข้าถึงและขอรับสำเนาข้อมูล',
|
|
'สิทธิขอแก้ไขข้อมูลให้ถูกต้อง',
|
|
'สิทธิขอถอนความยินยอม (ทั้งนี้ การถอนความยินยอมอาจส่งผลต่อการใช้งานระบบลงเวลาการปฏิบัติราชการ)',
|
|
'สิทธิร้องเรียนต่อคณะกรรมการคุ้มครองข้อมูลส่วนบุคคล',
|
|
],
|
|
},
|
|
consentText:
|
|
'ข้าพเจ้าได้อ่านและเข้าใจรายละเอียดทั้งหมดแล้ว และยินยอมให้เก็บ ใช้ และประมวลผลข้อมูลส่วนบุคคล ประเภทภาพถ่ายของข้าพเจ้า ตามที่ระบุไว้ข้างต้นโดยสมัครใจ',
|
|
buttons: {
|
|
accept: 'ยอมรับ',
|
|
decline: 'ไม่ยอมรับ',
|
|
},
|
|
}
|
|
|
|
const scrollContainer = ref<HTMLElement | null>(null)
|
|
const hasScrolledToBottom = ref(false)
|
|
const acceptPrivacy = ref(false)
|
|
const showDetails = ref(true)
|
|
const isAcceptDisabled = computed(() => !acceptPrivacy.value)
|
|
|
|
const handleScroll = (event: Event) => {
|
|
const target = event.target as HTMLElement
|
|
|
|
if (!target) return
|
|
|
|
const { scrollTop, scrollHeight, clientHeight } = target
|
|
// Enable checkbox when scrolled to near bottom (within 20px)
|
|
if (scrollTop + clientHeight >= scrollHeight - 20) {
|
|
hasScrolledToBottom.value = true
|
|
}
|
|
}
|
|
|
|
const handleAccept = async () => {
|
|
try {
|
|
await http.put(config.API.privacy, {
|
|
system: 'checkin',
|
|
accept: true,
|
|
})
|
|
privacyStore.setAccepted(true)
|
|
|
|
// อัปเดต privacyCheckin ใน positionKeycloak store ด้วย
|
|
if (positionKeycloakStore.dataPositionKeycloak) {
|
|
positionKeycloakStore.dataPositionKeycloak.privacyCheckin = true
|
|
}
|
|
|
|
modal.value = false
|
|
} catch (error) {}
|
|
}
|
|
|
|
const toggleDetails = () => {
|
|
showDetails.value = !showDetails.value
|
|
checkIfScrollable()
|
|
}
|
|
|
|
const checkIfScrollable = () => {
|
|
nextTick(() => {
|
|
const container = (scrollContainer.value as any)?.$el || scrollContainer.value
|
|
if (container) {
|
|
const { scrollHeight, clientHeight } = container
|
|
|
|
if (scrollHeight <= clientHeight + 20) {
|
|
hasScrolledToBottom.value = true
|
|
}
|
|
}
|
|
})
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<q-dialog
|
|
v-model="modal"
|
|
persistent
|
|
transition-show="slide-up"
|
|
transition-hide="slide-down"
|
|
:maximized="$q.screen.lt.sm"
|
|
@show="checkIfScrollable"
|
|
>
|
|
<q-card class="privacy-card" style="max-width: 560px; max-height: 95vh">
|
|
<!-- Header -->
|
|
<q-card-section class="bg-primary text-white q-pa-sm">
|
|
<div class="text-h6 text-center">
|
|
{{ privacyContent.title }}
|
|
</div>
|
|
<q-btn
|
|
icon="close"
|
|
flat
|
|
round
|
|
dense
|
|
class="absolute-top-right q-ma-sm"
|
|
v-close-popup
|
|
/>
|
|
</q-card-section>
|
|
|
|
<!-- Scrollable Content -->
|
|
<q-card-section
|
|
ref="scrollContainer"
|
|
class="col scroll q-px-lg q-pb-none"
|
|
@scroll="handleScroll"
|
|
>
|
|
<!-- Main Content -->
|
|
<div class="q-my-md">
|
|
<p class="main-text">
|
|
{{ privacyContent.mainText }}
|
|
<q-link @click.stop="toggleDetails" class="privacy-link text-blue-7"
|
|
>นโยบายคุ้มครองข้อมูลส่วนบุคคล (Privacy Policy)</q-link
|
|
>
|
|
</p>
|
|
|
|
<q-banner
|
|
inline-actions
|
|
class="bg-amber-1 rounded-borders text-center text-amber-7"
|
|
style="border-left: 4px solid #f0ad4e"
|
|
>
|
|
{{ privacyContent.warningText }}
|
|
</q-banner>
|
|
</div>
|
|
|
|
<!-- Details Section -->
|
|
<div
|
|
v-show="showDetails"
|
|
class="bg-white rounded-borders q-pa-sm details-section"
|
|
>
|
|
<p class="detail-intro q-mb-md">
|
|
{{ privacyContent.detailIntro }}
|
|
</p>
|
|
|
|
<!-- วัตถุประสงค์ -->
|
|
<div class="q-mb-md">
|
|
<ol class="q-pl-lg list-style">
|
|
<li
|
|
v-for="(item, index) in privacyContent.purposes.items"
|
|
:key="`p-${index}`"
|
|
class="q-mb-xs"
|
|
>
|
|
{{ item }}
|
|
</li>
|
|
</ol>
|
|
</div>
|
|
|
|
<!-- ข้าพเจ้าทราบและเข้าใจว่า -->
|
|
<div class="q-mx-sm">
|
|
ข้าพเจ้าทราบและเข้าใจว่า
|
|
<ul class="q-mt-sm">
|
|
<li
|
|
v-for="(item, index) in privacyContent.understanding.items"
|
|
:key="`u-${index}`"
|
|
class="q-mb-xs"
|
|
>
|
|
{{ item }}
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
|
|
<!-- สิทธิ์ของเจ้าของข้อมูล -->
|
|
<div>
|
|
<p>
|
|
{{ privacyContent.rights.title }}
|
|
</p>
|
|
<ul>
|
|
<li
|
|
v-for="(item, index) in privacyContent.rights.items"
|
|
:key="`r-${index}`"
|
|
class="q-mb-xs"
|
|
>
|
|
{{ item }}
|
|
</li>
|
|
</ul>
|
|
|
|
<p class="consent-text q-mt-lg">{{ privacyContent.consentText }}</p>
|
|
</div>
|
|
</div>
|
|
</q-card-section>
|
|
|
|
<!-- Fixed Bottom Section -->
|
|
<q-card-section class="bg-white q-px-lg">
|
|
<!-- Checkbox -->
|
|
<div
|
|
class="consent-box rounded-borders q-pa-sm row items-center"
|
|
:class="{
|
|
'consent-checked': acceptPrivacy,
|
|
'consent-disabled': !hasScrolledToBottom,
|
|
'cursor-pointer': hasScrolledToBottom,
|
|
}"
|
|
@click="hasScrolledToBottom && (acceptPrivacy = !acceptPrivacy)"
|
|
>
|
|
<q-checkbox
|
|
v-model="acceptPrivacy"
|
|
color="primary"
|
|
size="md"
|
|
:disable="!hasScrolledToBottom"
|
|
class="q-mr-sm"
|
|
/>
|
|
<div class="col">
|
|
ข้าพเจ้าได้อ่านและยอมรับ
|
|
<q-link @click.stop="toggleDetails" class="privacy-link text-blue-7"
|
|
>นโยบายคุ้มครองข้อมูลส่วนบุคคล</q-link
|
|
>
|
|
</div>
|
|
</div>
|
|
</q-card-section>
|
|
|
|
<!-- Action Buttons -->
|
|
<q-card-actions
|
|
align="center"
|
|
class="bg-white q-pa-md q-gutter-md"
|
|
:class="$q.screen.lt.sm ? 'row-reverse' : ''"
|
|
>
|
|
<q-btn
|
|
:label="privacyContent.buttons.accept"
|
|
color="primary"
|
|
unelevated
|
|
no-caps
|
|
class="action-btn"
|
|
:disable="isAcceptDisabled"
|
|
@click="handleAccept"
|
|
v-close-popup
|
|
/>
|
|
<q-btn
|
|
:label="privacyContent.buttons.decline"
|
|
color="grey-7"
|
|
unelevated
|
|
no-caps
|
|
class="action-btn"
|
|
v-close-popup
|
|
/>
|
|
</q-card-actions>
|
|
</q-card>
|
|
</q-dialog>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.privacy-card {
|
|
display: flex;
|
|
flex-direction: column;
|
|
border-radius: 8px;
|
|
border: 1px solid #dee2e6;
|
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
|
|
}
|
|
|
|
.main-text {
|
|
font-size: 1rem;
|
|
}
|
|
|
|
.details-section {
|
|
border: 1px solid #e9ecef;
|
|
}
|
|
|
|
.detail-intro {
|
|
line-height: 1.7;
|
|
}
|
|
|
|
.section-header {
|
|
font-weight: 600;
|
|
margin-bottom: 10px;
|
|
padding-bottom: 6px;
|
|
border-bottom: 1px solid #e9ecef;
|
|
}
|
|
|
|
/* .list-style,
|
|
.list-style-disc {
|
|
font-size: 13px;
|
|
line-height: 1.8;
|
|
} */
|
|
|
|
.list-style {
|
|
list-style-type: thai;
|
|
}
|
|
|
|
.list-style-disc {
|
|
list-style-type: disc;
|
|
}
|
|
|
|
.consent-box {
|
|
background: #f8f9fa;
|
|
border: 1px solid #ced4da;
|
|
transition: background-color 0.2s ease;
|
|
}
|
|
|
|
.consent-box:hover {
|
|
background: #e9ecef;
|
|
}
|
|
|
|
.consent-checked {
|
|
border-color: var(--q-primary);
|
|
background: #e7f1ff;
|
|
}
|
|
|
|
.consent-disabled {
|
|
opacity: 0.7;
|
|
cursor: not-allowed;
|
|
}
|
|
|
|
.consent-disabled:hover {
|
|
background: #f8f9fa;
|
|
}
|
|
|
|
p {
|
|
text-indent: 2em;
|
|
}
|
|
|
|
.action-btn {
|
|
flex: 1;
|
|
max-width: 180px;
|
|
font-weight: 500;
|
|
}
|
|
|
|
.privacy-link {
|
|
text-decoration: underline;
|
|
text-decoration-color: #c4d0dd;
|
|
cursor: pointer;
|
|
font-weight: bold;
|
|
}
|
|
|
|
.privacy-link:hover {
|
|
color: #7cadde;
|
|
text-decoration-color: #7cadde;
|
|
}
|
|
</style>
|