เพิ่มเช็คสังกัด ถ้าไม่มีสังกัดจะแสดงหน้า no-position
This commit is contained in:
parent
c4e246ed74
commit
6c819a6097
5 changed files with 259 additions and 2 deletions
|
|
@ -1,5 +1,9 @@
|
|||
# HRMS Check-in Expert Memory
|
||||
|
||||
## Features
|
||||
|
||||
- [No Position Assignment Page](feature_no_position_page.md) - Implementation for users without organization position assignment
|
||||
|
||||
## Project Issues & Fixes
|
||||
|
||||
- [Position Orientation Change Fix](issue_position_orientation_change_fix.md) - Fix for position map not displaying on screen orientation changes
|
||||
|
|
|
|||
|
|
@ -0,0 +1,68 @@
|
|||
---
|
||||
name: No Position Assignment Page
|
||||
description: Feature implementation for users without organization position assignment
|
||||
type: reference
|
||||
---
|
||||
|
||||
## No Position Assignment Page Feature
|
||||
|
||||
When a user doesn't have a position assignment (สังกัด), they are redirected to a dedicated page explaining they are not part of the organization structure.
|
||||
|
||||
### Files Created/Modified
|
||||
|
||||
1. **Created:** `/Users/waruneeta/Desktop/ChamomindWorking/HRMSProject/hrms-checkin/src/views/NoPositionView.vue`
|
||||
- Displays message in Thai: "ไม่พบข้อมูลสังกัด"
|
||||
- Shows icon and explanation text
|
||||
- "ตกลง" (OK) button that shows confirmation dialog
|
||||
- After confirmation, performs logout and clears positionKeycloak store
|
||||
|
||||
2. **Modified:** `/Users/waruneeta/Desktop/ChamomindWorking/HRMSProject/hrms-checkin/src/router/index.ts`
|
||||
- Added new route `/no-position` with `Auth: false`
|
||||
- Enhanced router guard to check for position data
|
||||
- Redirects to `/no-position` if user has no organization assignment
|
||||
|
||||
3. **Modified:** `/Users/waruneeta/Desktop/ChamomindWorking/HRMSProject/hrms-checkin/src/views/MainView.vue`
|
||||
- Updated `fetchKeycloakPosition()` function
|
||||
- Checks if `organization` object exists and has any data
|
||||
- Redirects to `/no-position` if no organization data found
|
||||
|
||||
### Logic Flow
|
||||
|
||||
1. User logs in successfully
|
||||
2. `MainView.vue` calls `fetchKeycloakPosition()` in `onMounted()`
|
||||
3. API returns position data from `/org/profile/keycloak/position`
|
||||
4. System checks if `organization` object has any non-null values (root, child1-4)
|
||||
5. If no organization data exists:
|
||||
- User is redirected to `/no-position`
|
||||
- User sees message explaining they need to contact staff
|
||||
- User clicks "ตกลง" to logout
|
||||
- Staff adds them to organization structure
|
||||
- User can login again
|
||||
|
||||
### Position Data Structure
|
||||
|
||||
```typescript
|
||||
interface KeycloakPosition {
|
||||
privacyCheckin: boolean
|
||||
avatarName?: string
|
||||
profileId: string
|
||||
organization?: Organization
|
||||
}
|
||||
|
||||
interface Organization {
|
||||
root?: string
|
||||
child1?: string
|
||||
child2?: string
|
||||
child3?: string
|
||||
child4?: string
|
||||
}
|
||||
```
|
||||
|
||||
A user is considered to have "no position" when all organization fields (root, child1, child2, child3, child4) are null or undefined.
|
||||
|
||||
### Thai UI Messages
|
||||
|
||||
- Page title: "ไม่พบข้อมูลสังกัด"
|
||||
- Description: "ท่านยังไม่มีสังกัดในโครงสร้างองค์กร กรุณาติดต่อเจ้าหน้าที่เพื่อดำเนินการเพิ่มข้อมูล"
|
||||
- Confirmation dialog: "ยืนยันการออกจากระบบ" - "ท่านจะถูกนำออกจากระบบเพื่อให้เจ้าหน้าที่ดำเนินการเพิ่มข้อมูลสังกัดในโครงสร้างองค์กร"
|
||||
- Button: "ตกลง"
|
||||
|
|
@ -5,6 +5,7 @@ import MainView from '@/views/MainView.vue'
|
|||
|
||||
const loginView = () => import('@/views/login.vue')
|
||||
const resetPasswordView = () => import('@/views/ResetPassword.vue')
|
||||
const noPositionView = () => import('@/views/NoPositionView.vue')
|
||||
|
||||
import { authenticated, logout } from '@/plugins/auth'
|
||||
|
||||
|
|
@ -87,19 +88,63 @@ const router = createRouter({
|
|||
Auth: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/no-position',
|
||||
name: 'no-position',
|
||||
component: noPositionView,
|
||||
meta: {
|
||||
Auth: false,
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
// authen with keycloak client
|
||||
router.beforeEach(async (to, from, next) => {
|
||||
// ตรวจสอบเส้นทางพิเศษที่ไม่ต้องตรวจสอบสิทธิ์
|
||||
const publicPaths = ['/login', '/auth', '/reset-password', '/no-position']
|
||||
if (publicPaths.includes(to.path)) {
|
||||
next()
|
||||
return
|
||||
}
|
||||
|
||||
// ตรวจสอบการ authenticate
|
||||
if (to.meta.Auth) {
|
||||
const checkAuthen = await authenticated()
|
||||
if (!checkAuthen && to.meta.Auth) {
|
||||
logout()
|
||||
return
|
||||
}
|
||||
} else {
|
||||
next()
|
||||
}
|
||||
|
||||
// ตรวจสอบว่าผู้ใช้มีข้อมูลสังกัดหรือไม่
|
||||
// ต้องทำการ dynamic import เนื่องจากเป็นการใช้งาน Pinia store ใน router
|
||||
if (to.path !== '/no-position') {
|
||||
try {
|
||||
const { usePositionKeycloakStore } = await import('@/stores/positionKeycloak')
|
||||
const positionKeycloakStore = usePositionKeycloakStore()
|
||||
const dataPositionKeycloak = positionKeycloakStore.dataPositionKeycloak
|
||||
|
||||
// ถ้ามีข้อมูล positionKeycloak แล้ว ให้ตรวจสอบว่ามี organization หรือไม่
|
||||
if (dataPositionKeycloak) {
|
||||
const hasOrganization =
|
||||
dataPositionKeycloak.organization &&
|
||||
(dataPositionKeycloak.organization.root ||
|
||||
dataPositionKeycloak.organization.child1 ||
|
||||
dataPositionKeycloak.organization.child2 ||
|
||||
dataPositionKeycloak.organization.child3 ||
|
||||
dataPositionKeycloak.organization.child4)
|
||||
|
||||
if (!hasOrganization) {
|
||||
next('/no-position')
|
||||
return
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error checking position:', error)
|
||||
}
|
||||
}
|
||||
|
||||
next()
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -182,6 +182,22 @@ async function fetchKeycloakPosition() {
|
|||
|
||||
if (existingData) {
|
||||
// มีข้อมูลอยู่แล้ว ใช้ข้อมูลเดิม
|
||||
|
||||
// เช็คว่ามีข้อมูลสังกัด (organization) หรือไม่
|
||||
const hasOrganization =
|
||||
existingData.organization &&
|
||||
(existingData.organization.root ||
|
||||
existingData.organization.child1 ||
|
||||
existingData.organization.child2 ||
|
||||
existingData.organization.child3 ||
|
||||
existingData.organization.child4)
|
||||
|
||||
if (!hasOrganization) {
|
||||
// ไม่มีข้อมูลสังกัด redirect ไปหน้า NoPosition
|
||||
router.replace('/no-position')
|
||||
return
|
||||
}
|
||||
|
||||
privacyStore.modalPrivacy = !existingData.privacyCheckin
|
||||
privacyStore.setAccepted(existingData.privacyCheckin)
|
||||
|
||||
|
|
@ -208,6 +224,24 @@ async function fetchKeycloakPosition() {
|
|||
},
|
||||
}
|
||||
|
||||
// เช็คว่ามีข้อมูลสังกัด (organization) หรือไม่
|
||||
// ถ้าไม่มีข้อมูลสังกัด ให้ redirect ไปหน้า NoPosition
|
||||
const hasOrganization =
|
||||
keycloakData.organization &&
|
||||
(keycloakData.organization.root ||
|
||||
keycloakData.organization.child1 ||
|
||||
keycloakData.organization.child2 ||
|
||||
keycloakData.organization.child3 ||
|
||||
keycloakData.organization.child4)
|
||||
|
||||
if (!hasOrganization) {
|
||||
// บันทึกข้อมูลที่ได้รับไว้ก่อน (เผื่อใช้ในอนาคต)
|
||||
positionKeycloakStore.setPositionKeycloak(keycloakData)
|
||||
// Redirect ไปหน้า NoPosition
|
||||
router.replace('/no-position')
|
||||
return
|
||||
}
|
||||
|
||||
positionKeycloakStore.setPositionKeycloak(keycloakData)
|
||||
privacyStore.modalPrivacy = !keycloakData.privacyCheckin
|
||||
privacyStore.setAccepted(keycloakData.privacyCheckin)
|
||||
|
|
|
|||
106
src/views/NoPositionView.vue
Normal file
106
src/views/NoPositionView.vue
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
<script setup lang="ts">
|
||||
import { useQuasar } from 'quasar'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useCounterMixin } from '@/stores/mixin'
|
||||
import { usePositionKeycloakStore } from '@/stores/positionKeycloak'
|
||||
import { logout } from '@/plugins/auth'
|
||||
import CustomComponent from '@/components/CustomDialog.vue'
|
||||
|
||||
const $q = useQuasar()
|
||||
const router = useRouter()
|
||||
const mixin = useCounterMixin()
|
||||
const positionKeycloakStore = usePositionKeycloakStore()
|
||||
const { showLoader, hideLoader } = mixin
|
||||
|
||||
/**
|
||||
* ฟังก์ชันจัดการเมื่อผู้ใช้กดปุ่มตกลง
|
||||
* ดำเนินการ logout โดยตรง
|
||||
*/
|
||||
function handleOkClick() {
|
||||
performLogout()
|
||||
}
|
||||
|
||||
/**
|
||||
* ฟังก์ชันดำเนินการออกจากระบบ
|
||||
*/
|
||||
async function performLogout() {
|
||||
showLoader()
|
||||
try {
|
||||
// ล้างข้อมูล positionKeycloak ก่อน logout
|
||||
positionKeycloakStore.clearPositionKeycloak()
|
||||
await logout()
|
||||
} catch (error) {
|
||||
console.error('Logout error:', error)
|
||||
} finally {
|
||||
setTimeout(() => {
|
||||
hideLoader()
|
||||
}, 1000)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="fullscreen bg-secondary text-white text-center q-pa-sm flex flex-center"
|
||||
>
|
||||
<div class="no-position-container">
|
||||
<!-- Icon แสดงความผิดปกติ -->
|
||||
<div class="icon-container">
|
||||
<q-icon name="mdi-account-off-outline" size="100px" color="white" />
|
||||
</div>
|
||||
|
||||
<!-- หัวข้อหลัก -->
|
||||
<div class="text-h4 q-mt-lg q-mb-md">ไม่พบข้อมูลสังกัด</div>
|
||||
|
||||
<!-- รายละเอียด -->
|
||||
<div class="text-h6 q-mb-lg text-weight-regular">
|
||||
ท่านยังไม่มีสังกัดในโครงสร้างองค์กร<br />
|
||||
กรุณาติดต่อเจ้าหน้าที่ที่เบอร์ 1171
|
||||
<br />เพื่อดำเนินการเพิ่มข้อมูล
|
||||
</div>
|
||||
|
||||
<div class="text-weight-regular">
|
||||
เมื่อเจ้าหน้าที่ได้เพิ่มท่านในโครงสร้างองค์กรเรียบร้อยแล้ว
|
||||
กรุณาเข้าสู่ระบบใหม่อีกครั้ง
|
||||
</div>
|
||||
<!-- ปุ่มตกลง -->
|
||||
<q-btn
|
||||
class="q-mt-xl"
|
||||
color="white"
|
||||
text-color="secondary"
|
||||
unelevated
|
||||
label="ตกลง"
|
||||
no-caps
|
||||
size="lg"
|
||||
padding="md xl"
|
||||
@click="handleOkClick"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.no-position-container {
|
||||
max-width: 600px;
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
.icon-container {
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.text-h4 {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.text-h6 {
|
||||
line-height: 1.8;
|
||||
opacity: 0.95;
|
||||
}
|
||||
|
||||
.q-btn {
|
||||
border-radius: 8px;
|
||||
font-weight: 600;
|
||||
min-width: 150px;
|
||||
}
|
||||
</style>
|
||||
Loading…
Add table
Add a link
Reference in a new issue