ปรับ ui checkin

This commit is contained in:
Tanyalak 2024-01-19 15:34:09 +07:00
parent 4dbf1de05d
commit f9ec074f03
10 changed files with 705 additions and 695 deletions

BIN
src/assets/logo1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

View file

@ -129,10 +129,8 @@ onMounted(async () => {
<template> <template>
<!-- Loading skeleton --> <!-- Loading skeleton -->
<div v-if="!poiPlaceName" class="q-pa-md"> <div v-if="!poiPlaceName" class="col-12">
<div class="q-gutter-md"> <q-skeleton height="35vh" width="100%" class="bg-grey-4" />
<q-skeleton height="35vh" width="100%" class="bg-grey-4" />
</div>
</div> </div>
<q-card v-show="poiPlaceName" bordered flat class="col-12 bg-grey-2 shadow-0"> <q-card v-show="poiPlaceName" bordered flat class="col-12 bg-grey-2 shadow-0">
@ -142,7 +140,7 @@ onMounted(async () => {
:class=" :class="
$q.screen.gt.xs $q.screen.gt.xs
? 'q-pa-xs text-weight-medium text-grey-8' ? 'q-pa-xs text-weight-medium text-grey-8'
: ' text-weight-medium text-grey-8' : 'q-pa-xs text-weight-medium text-grey-8'
" "
> >
นทใกลเคยง นทใกลเคยง

View file

@ -139,138 +139,139 @@ watch(
) )
</script> </script>
<template> <template>
<q-card-section class="col q-pt-none"> <div class="col-12 row">
<div class="row q-pa-sm q-col-q-gutter-sm"> <div class="row q-col-q-gutter-sm q-pa-md">
<q-card <q-card
flat flat
bordered bordered
:class="$q.screen.gt.xs ? 'col-12 bg-grey-1' : 'col-12 '" :class="$q.screen.gt.xs ? 'col-12 bg-grey-1' : 'col-12 '"
> >
<q-card-section class="bg-primary text-white q-pa-sm"> <q-card-section class="bg-primary text-white q-pa-sm">
<div class="text-center text-bold">เวลาปจจ</div> <div class="text-center text-bold">เวลาปจจ</div>
</q-card-section> </q-card-section>
<!-- <div class="q-pa-sm text-primary">เวลาปจจ</div> --> <!-- <div class="q-pa-sm text-primary">เวลาปจจ</div> -->
<q-card-section class="text-center q-pa-sm"> <q-card-section class="text-center q-pa-sm">
<div class="row q-gutter-md"> <div class="row q-gutter-md">
<div class="col">{{ date2Thai(dateNow) }}</div> <div class="col">{{ date2Thai(dateNow) }}</div>
<div class="col">{{ timeNoew }}</div> <div class="col">{{ timeNoew }}</div>
</div>
</q-card-section>
</q-card>
<q-card flat bordered class="col-12 q-mt-sm">
<div class="q-pa-sm">
<datepicker
:readonly="!statusAction"
v-model="date"
:locale="'th'"
:enableTimePicker="false"
week-start="0"
autoApply
outlined
lazy-rules
:max-date="dateNow"
>
<template #year="{ year }">
{{ year + 543 }}
</template>
<template #year-overlay-value="{ value }">
{{ parseInt(value + 543) }}
</template>
<template #trigger>
<q-input
:readonly="!statusAction"
ref="dateRef"
outlined
dense
:model-value="date !== null ? date2Thai(new Date(date)) : null"
:label="`${'วันที่ขอแก้ไข'}`"
format-header="YYYY-MM-DD"
lazy-rules
:rules="[(val) => !!val || 'กรุณาเลือกวันที่']"
hide-bottom-space
>
<template v-slot:prepend>
<q-icon
name="event"
class="cursor-pointer"
style="color: var(--q-primary)"
>
</q-icon>
</template>
</q-input> </template
></datepicker>
</div> </div>
</q-card> </q-card-section>
</q-card>
<!-- <q-card flat bordered class="q-pa-sm col-12 bg-grey-1 q-mt-sm" v-else> <q-card flat bordered class="col-12 q-mt-sm">
<div class="row q-gutter-md text-grey-5"> <div class="q-pa-sm">
<div class="col-1"> <datepicker
<q-icon color="grey-5" name="calendar_today" /> :readonly="!statusAction"
</div> v-model="date"
<div class="col">{{ dataByIdVal.checkInDate }}</div> :locale="'th'"
</div> :enableTimePicker="false"
</q-card> --> week-start="0"
autoApply
<q-card
flat
bordered
class="q-pa-sm col-12 q-mt-sm"
:style="{
borderColor: checkstatusBox ? '#B22222' : '',
borderWidth: checkstatusBox ? '2px' : '',
}"
>
<div class="row q-gutter-xs">
<div class="col-12">
<q-checkbox
keep-color
color="primary"
v-model="checkboxIn"
label="ขอแก้ไขเวลาเข้างาน"
/>
</div>
<div class="col-12">
<q-checkbox
keep-color
v-model="checkboxOut"
color="primary"
label="ขอแก้ไขเวลาออกงาน"
/>
</div>
</div>
</q-card>
<div
v-if="checkstatusBox"
class="text-red-9 q-pa-sm"
style="font-size: 10px"
>
กรณาเลอก
</div>
<q-card flat bordered class="q-pa-sm col-12 q-mt-sm">
<q-input
ref="reasonRef"
dense
outlined outlined
v-model="reason"
label="เหตุผล"
type="textarea"
:rows="4"
label-color="grey-5"
:rules="[(val) => !!val || 'กรุณากรอกเหตุผล']"
lazy-rules lazy-rules
hide-bottom-space :max-date="dateNow"
class="custom-aqua-border" >
/> <template #year="{ year }">
</q-card> {{ year + 543 }}
</template>
<template #year-overlay-value="{ value }">
{{ parseInt(value + 543) }}
</template>
<template #trigger>
<q-input
:readonly="!statusAction"
ref="dateRef"
outlined
dense
:model-value="date !== null ? date2Thai(new Date(date)) : null"
:label="`${'วันที่ขอแก้ไข'}`"
format-header="YYYY-MM-DD"
lazy-rules
:rules="[(val) => !!val || 'กรุณาเลือกวันที่']"
hide-bottom-space
>
<template v-slot:prepend>
<q-icon
name="event"
class="cursor-pointer"
style="color: var(--q-primary)"
>
</q-icon>
</template>
</q-input> </template
></datepicker>
</div>
</q-card>
<!-- <q-card flat bordered class="q-pa-sm col-12 bg-grey-1 q-mt-sm" v-else>
<div class="row q-gutter-md text-grey-5">
<div class="col-1">
<q-icon color="grey-5" name="calendar_today" />
</div>
<div class="col">{{ dataByIdVal.checkInDate }}</div>
</div>
</q-card> -->
<q-card
flat
bordered
class="q-pa-sm col-12 q-mt-sm"
:style="{
borderColor: checkstatusBox ? '#B22222' : '',
borderWidth: checkstatusBox ? '2px' : '',
}"
>
<div class="row q-gutter-xs">
<div class="col-12">
<q-checkbox
keep-color
color="primary"
v-model="checkboxIn"
label="ขอแก้ไขเวลาเข้างาน"
/>
</div>
<div class="col-12">
<q-checkbox
keep-color
v-model="checkboxOut"
color="primary"
label="ขอแก้ไขเวลาออกงาน"
/>
</div>
</div>
</q-card>
<div
v-if="checkstatusBox"
class="text-red-9 q-pa-sm"
style="font-size: 10px"
>
กรณาเลอก
</div> </div>
</q-card-section>
<q-separator /> <q-card flat bordered class="q-pa-sm col-12 q-mt-sm">
<q-card-actions align="right"> <q-input
<q-btn dense color="secondary" label="บันทึก" @click="onCkickSave" /> ref="reasonRef"
</q-card-actions> dense
outlined
v-model="reason"
label="เหตุผล"
type="textarea"
:rows="4"
label-color="grey-5"
:rules="[(val) => !!val || 'กรุณากรอกเหตุผล']"
lazy-rules
hide-bottom-space
class="custom-aqua-border"
/>
</q-card>
</div>
<div class="col-12"><q-separator /></div>
<div class="row col-12 q-pa-sm">
<q-space/>
<q-btn color="secondary" label="บันทึก" @click="onCkickSave" />
</div>
</div>
</template> </template>
<style scoped></style> <style scoped></style>

View file

@ -41,7 +41,7 @@ watch(
</script> </script>
<template> <template>
<q-dialog v-model="props.modal" persistent> <q-dialog v-model="props.modal" persistent>
<q-card class="column" style="width: 300px; min-height: 600px"> <q-card class="column" :style="$q.screen.lt.sm ? 'width: 100%;': 'width: 450px;'">
<HeaderPopup :title="props.title" :clickClose="clickClosePopup" /> <HeaderPopup :title="props.title" :clickClose="clickClosePopup" />
<FormTime <FormTime
:dataById="data" :dataById="data"

View file

@ -376,7 +376,7 @@ function onClickOpenPopupRejrct(data: any) {
icon="edit" icon="edit"
label="ขอแก้ไข" label="ขอแก้ไข"
color="info" color="info"
size="sm" size="12px"
@click="openPopup(props.row)" @click="openPopup(props.row)"
/> />
</div> </div>

View file

@ -58,89 +58,86 @@ const monthYearThai = (val: DataDateMonthObject) => {
} }
</script> </script>
<template> <template>
<div class="q-pb-sm row"> <div class="items-center col-12 row q-pb-sm">
<div class="items-center col-12 row q-gutter-sm"> <datepicker
<datepicker v-if="tab === 'history'"
v-if="tab === 'history'" menu-class-name="modalfix"
menu-class-name="modalfix" v-model="filterYear"
v-model="filterYear" class="col-xs-5 col-sm-3 col-md-2"
class="col-xs-5 col-sm-3 col-md-2" :locale="'th'"
:locale="'th'" autoApply
autoApply year-picker
year-picker :enableTimePicker="false"
:enableTimePicker="false" @update:modelValue="filterYearFn('year')"
@update:modelValue="filterYearFn('year')" >
> <template #year="{ year }">{{ year + 543 }}</template>
<template #year="{ year }">{{ year + 543 }}</template> <template #year-overlay-value="{ value }">{{
<template #year-overlay-value="{ value }">{{ parseInt(value + 543)
parseInt(value + 543) }}</template>
}}</template> <template #trigger>
<template #trigger> <q-input
<q-input dense
dense lazy-rules
lazy-rules outlined
outlined :model-value="filterYear + 543"
:model-value="filterYear + 543" :label="`${'ปีงบประมาณ'}`"
:label="`${'ปีงบประมาณ'}`" >
> <template v-slot:prepend>
<template v-slot:prepend> <q-icon
<q-icon name="event"
name="event" class="cursor-pointer"
class="cursor-pointer" style="color: var(--q-primary)"
style="color: var(--q-primary)" >
> </q-icon>
</q-icon> </template>
</template> </q-input>
</q-input> </template>
</template> </datepicker>
</datepicker>
<datepicker <datepicker
v-else-if="tab === 'time'" v-else-if="tab === 'time'"
menu-class-name="modalfix" menu-class-name="modalfix"
v-model="dateMonth" v-model="dateMonth"
class="col-xs-5 col-sm-3 col-md-2" class="col-xs-5 col-sm-3 col-md-2"
:locale="'th'" :locale="'th'"
autoApply autoApply
month-picker month-picker
:enableTimePicker="false" :enableTimePicker="false"
@update:modelValue="filterYearFn('mount')" @update:modelValue="filterYearFn('mount')"
> >
<template #year="{ year }">{{ year + 543 }}</template> <template #year="{ year }">{{ year + 543 }}</template>
<template #year-overlay-value="{ value }">{{ <template #year-overlay-value="{ value }">{{
parseInt(value + 543) parseInt(value + 543)
}}</template> }}</template>
<template #trigger> <template #trigger>
<q-input <q-input
dense dense
lazy-rules lazy-rules
outlined outlined
:model-value="monthYearThai(dateMonth)" :model-value="monthYearThai(dateMonth)"
:label="`${'ปีงบประมาณ'}`" :label="`${'ปีงบประมาณ'}`"
> >
<template v-slot:prepend> <template v-slot:prepend>
<q-icon <q-icon
name="event" name="event"
class="cursor-pointer" class="cursor-pointer"
style="color: var(--q-primary)" style="color: var(--q-primary)"
> >
</q-icon> </q-icon>
</template> </template>
</q-input> </q-input>
</template> </template>
</datepicker> </datepicker>
<q-space /> <q-space />
<q-btn <q-btn
unelevated unelevated
outline icon="add"
icon="add" :dense="$q.screen.lt.sm"
color="light-blue" color="secondary"
:class="$q.screen.gt.xs ? 'q-px-sm bg-blue-1' : ''" :label="$q.screen.gt.xs ? 'เพิ่มรายการลงเวลากรณีพิเศษ' : ''"
:label="$q.screen.gt.xs ? 'เพิ่มรายการลงเวลากรณีพิเศษ' : ''" @click="onClickopen"
@click="onClickopen" />
/>
</div>
</div> </div>
<Popup <Popup

View file

@ -1,6 +1,7 @@
import { createRouter, createWebHistory } from 'vue-router' import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '@/views/HomeView.vue' import HomeView from '@/views/HomeView.vue'
import MapView from '@/views/MapView.vue' import MapView from '@/views/MapView.vue'
import MianView from '@/views/MianView.vue'
import keycloak from '@/plugins/keycloak' import keycloak from '@/plugins/keycloak'
@ -8,56 +9,63 @@ const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL), history: createWebHistory(import.meta.env.BASE_URL),
routes: [ routes: [
{ {
path: '/', path: "/",
name: 'home', name: "home",
component: HomeView, component: MianView,
meta: { children: [
Auth: true, {
}, path: '/',
}, name: 'home',
{ component: HomeView,
path: '/map', meta: {
name: 'map', Auth: true,
component: MapView, },
meta: { },
Auth: true, {
}, path: '/map',
}, name: 'map',
{ component: MapView,
path: '/about', meta: {
name: 'about', Auth: true,
// route level code-splitting },
// this generates a separate chunk (about.[hash].js) for this route },
// which is lazy-loaded when the route is visited. {
component: () => import('@/views/AboutView.vue'), path: '/about',
meta: { name: 'about',
Auth: true, // route level code-splitting
}, // this generates a separate chunk (about.[hash].js) for this route
}, // which is lazy-loaded when the route is visited.
{ component: () => import('@/views/AboutView.vue'),
path: '/history', meta: {
name: 'history', Auth: true,
component: () => import('@/views/HistoryView.vue'), },
meta: { },
Auth: true, {
}, path: '/history',
}, name: 'history',
// { component: () => import('@/views/HistoryView.vue'),
// path: '/camera', meta: {
// name: 'camera', Auth: true,
// component: () => import('@/views/SampleCamera.vue'), },
// }, },
/** // {
* 404 Not Found // path: '/camera',
* ref: https://router.vuejs.org/guide/essentials/dynamic-matching.html#catch-all-404-not-found-route // name: 'camera',
*/ // component: () => import('@/views/SampleCamera.vue'),
{ // },
path: '/:pathMatch(.*)*', /**
name: 'NotFound', * 404 Not Found
component: () => import('@/views/ErrorNotFoundPage.vue'), * ref: https://router.vuejs.org/guide/essentials/dynamic-matching.html#catch-all-404-not-found-route
meta: { */
Auth: true, {
}, path: '/:pathMatch(.*)*',
name: 'NotFound',
component: () => import('@/views/ErrorNotFoundPage.vue'),
meta: {
Auth: true,
},
},
]
}, },
], ],
}) })

View file

@ -131,7 +131,6 @@ watch(
<div <div
class="bg-secondary text-white col-12 row items-center q-px-md q-py-sm" class="bg-secondary text-white col-12 row items-center q-px-md q-py-sm"
> >
<div class="col-2">
<q-btn <q-btn
icon="arrow_back" icon="arrow_back"
round round
@ -140,20 +139,11 @@ watch(
color="white" color="white"
@click="router.go(-1)" @click="router.go(-1)"
/> />
</div> <span class="text-body1 text-weight-bold q-pl-md"
<q-space />
<span class="text-body1 text-weight-bold col-8 text-center"
>ประวการลงเวลา >ประวการลงเวลา
</span> </span>
<div class="col-2"></div>
</div> </div>
<div class="col-12 q-pa-md text-grey-9"> <div class="col-12 text-grey-9">
<ToolBar
:fetchData="functionFetch"
@update:year="updateYear"
:tab="stores.tab"
/>
<q-card bordered flat>
<q-tabs <q-tabs
v-model="stores.tab" v-model="stores.tab"
dense dense
@ -172,6 +162,11 @@ watch(
<q-tab-panels v-model="stores.tab" animated> <q-tab-panels v-model="stores.tab" animated>
<q-tab-panel name="history"> <q-tab-panel name="history">
<ToolBar
:fetchData="functionFetch"
@update:year="updateYear"
:tab="stores.tab"
/>
<TableHistory <TableHistory
:fetchData="functionFetch" :fetchData="functionFetch"
:page-size="pageSize" :page-size="pageSize"
@ -185,6 +180,11 @@ watch(
</q-tab-panel> </q-tab-panel>
<q-tab-panel name="time"> <q-tab-panel name="time">
<ToolBar
:fetchData="functionFetch"
@update:year="updateYear"
:tab="stores.tab"
/>
<TableHistory <TableHistory
:fetchData="functionFetch" :fetchData="functionFetch"
:page-size="pageSize" :page-size="pageSize"
@ -198,7 +198,6 @@ watch(
</q-tab-panel> </q-tab-panel>
</q-tab-panels> </q-tab-panels>
<!-- </q-card> --> <!-- </q-card> -->
</q-card>
<!-- <Table <!-- <Table
:fetchData="fetchlistHistory" :fetchData="fetchlistHistory"
:page-size="pageSize" :page-size="pageSize"

View file

@ -10,7 +10,6 @@ import config from '@/app.config'
// import Type // import Type
import type { FormRef } from '@/interface/response/checkin' import type { FormRef } from '@/interface/response/checkin'
import type { notiType } from '@/interface/index/Main'
// import components // import components
import MapCheck from '@/components/AscGISMap.vue' import MapCheck from '@/components/AscGISMap.vue'
@ -24,10 +23,7 @@ const {
showLoader, showLoader,
hideLoader, hideLoader,
messageError, messageError,
dialogRemove,
success,
} = mixin } = mixin
const router = useRouter()
const $q = useQuasar() const $q = useQuasar()
const dialogTime = ref<boolean>(false) const dialogTime = ref<boolean>(false)
@ -51,79 +47,6 @@ async function fetchCheckTime() {
}) })
} }
const notiTrigger = ref<boolean>(false)
const notiList = ref<notiType[]>([])
const totalNotiList = ref<number>(0)
/** function เรียกข้อมุลแจ้งเตือน */
async function fetchNotifications(index: number, type: string) {
showLoader()
await http
.get(config.API.msgNotificate + `?page=${index}&pageSize=${20}`)
.then((res) => {
const response = res.data.result.data
const list: notiType[] = []
if (type === 'DEL') {
totalNotiList.value = res.data.result.total
if (notiList.value.length === 14) {
notiList.value = []
response.map((e: any) => {
list.push({
id: e.id,
sender:
e.createdFullName == '' || e.createdFullName == null
? 'เจ้าหน้าที่'[0]
: e.createdFullName[0],
body: e.body ?? '',
timereceive: e.receiveDate ? date2Thai(e.receiveDate) : '-',
})
})
notiList.value.push(...list)
}
} else {
response.map((e: any) => {
list.push({
id: e.id,
sender:
e.createdFullName == '' || e.createdFullName == null
? 'เจ้าหน้าที่'[0]
: e.createdFullName[0],
body: e.body ?? '',
timereceive: e.receiveDate ? date2Thai(e.receiveDate) : '-',
})
})
notiList.value.push(...list)
totalNotiList.value = res.data.result.total
}
})
.catch((err) => {
messageError($q, err)
})
.finally(() => {
hideLoader()
})
}
/**
* function ลบรายการแจงเตอน
* @param id noti
*/
async function onClickDelete(id: string, index: number) {
dialogRemove($q, async () => {
await http
.delete(config.API.msgId(id))
.then(() => {
notiList.value.splice(index, 1)
success($q, 'ลบข้อมูลสำเร็จ')
})
.catch((e) => {
messageError($q, e)
})
.finally(async () => {
fetchNotifications(1, 'DEL')
hideLoader()
})
})
}
/** ref อัพเดทเวลา*/ /** ref อัพเดทเวลา*/
const dateNow = ref<Date>(new Date()) const dateNow = ref<Date>(new Date())
@ -307,27 +230,6 @@ async function onClickConfirm() {
dialogTime.value = false dialogTime.value = false
} }
/** function logout*/
function onClickLogout() {
$q.dialog({
title: 'ยืนยันการออกจากระบบ',
message: `ต้องการออกจากระบบใช้หรือไม่?`,
cancel: 'ยกเลิก',
ok: 'ยืนยัน',
persistent: true,
}).onOk(() => {
keycloak.logout()
})
}
function onLoad(index: any, done: any) {
if (notiList.value.length < totalNotiList.value) {
setTimeout(() => {
fetchNotifications(index + 1, 'NOMAL')
done()
}, 2000)
}
}
// class // class
const getClass = (val: boolean) => { const getClass = (val: boolean) => {
@ -341,11 +243,6 @@ const fullName = ref<string>('')
/** Hook*/ /** Hook*/
onMounted(async () => { onMounted(async () => {
await fetchCheckTime() await fetchCheckTime()
await fetchNotifications(1, 'NOMAL')
if (keycloak.tokenParsed != null) {
fullName.value = keycloak.tokenParsed.name
}
updateClock() updateClock()
}) })
</script> </script>
@ -356,283 +253,16 @@ onMounted(async () => {
<q-card flat class="row col-12 cardNone"> <q-card flat class="row col-12 cardNone">
<!-- <q-header elevated class="bg-purple"> --> <!-- <q-header elevated class="bg-purple"> -->
<q-toolbar :class="getClass(stetusCheckin)"> <q-toolbar :class="getClass(stetusCheckin)">
<q-btn <div class="row col-12 justify-center">
icon="history" <strong v-if="stetusCheckin" style="font-size: 1rem">
unelevated ลงเวลาเขางาน
rounded </strong>
dense <strong v-else style="font-size: 1rem">ลงเวลาออกงาน</strong>
flat </div>
color="white"
:label="$q.screen.gt.xs ? 'ประวัติการลงเวลา' : ''"
:class="$q.screen.gt.xs ? 'q-px-md' : ''"
@click="router.push('/history')"
/>
<q-space />
<strong v-if="stetusCheckin" style="font-size: 1rem">
ลงเวลาเขางาน
</strong>
<strong v-else style="font-size: 1rem">ลงเวลาออกงาน</strong>
<q-space />
<q-btn
round
dense
flat
size="13px"
class="q-mx-md"
:disable="totalNotiList == 0"
>
<q-icon name="notifications" size="24px" color="white" />
<q-badge
rounded
v-show="totalNotiList !== 0"
color="negative"
text-color="white"
floating
>{{ totalNotiList }}</q-badge
>
<q-menu v-model="notiTrigger" max-width="480px" :offset="[0, 10]">
<div class="q-px-md q-py-sm row col-12 items-center">
<div class="text-subtitle1 text-weight-medium">
การแจงเตอน
</div>
</div>
<q-infinite-scroll @load="onLoad" :offset="250">
<div
v-for="(item, index) in notiList"
:key="index"
class="caption"
>
<q-item v-ripple class="mytry q-py-sm" dense>
<q-item-section avatar top style="min-width: 10px">
<q-avatar
rounded
color="primary"
size="25px"
text-color="white"
>
<span class="text-weight-medium text-uppercase">{{
item.sender[0]
}}</span>
</q-avatar>
</q-item-section>
<q-item-section>
<q-item-label caption class="text-black">{{
item.body
}}</q-item-label>
<q-item-label
caption
class="row items-center text-grey-7"
style="font-size: 12px"
>{{ item.timereceive }}</q-item-label
>
</q-item-section>
<div>
<q-btn
size="sm"
unelevated
dense
icon="mdi-close"
class="mybtn q-mx-xs"
@click="onClickDelete(item.id, index)"
></q-btn>
</div>
</q-item>
</div>
<template v-slot:loading v-if="notiList.length < totalNotiList">
<div class="row justify-center q-my-md">
<q-spinner-dots color="primary" size="40px" />
</div>
</template>
</q-infinite-scroll>
<!-- <q-list
style="min-width: 300px"
v-for="n in notiList"
:key="n.id"
>
<q-item v-ripple class="mytry q-py-xs" dense>
<q-item-section avatar top style="min-width: 40px">
<q-avatar color="primary" size="22px" text-color="white">
<span class="text-weight-medium text-uppercase">{{
n.sender
}}</span>
</q-avatar>
</q-item-section>
<q-item-section>
<q-item-label caption class="text-black">{{
n.body
}}</q-item-label>
<q-item-label
caption
class="row items-center text-grey-7"
>{{ date2Thai(n.timereceive) }}</q-item-label
>
</q-item-section>
<q-btn
size="sm"
unelevated
dense
icon="mdi-close"
class="mybtn q-mx-xs"
@click="onClickDelete(n.id)"
></q-btn>
</q-item>
<q-separator color="grey-2" />
</q-list> -->
</q-menu>
</q-btn>
<q-btn round dense flat icon="account_circle" style="font-size: 16px">
<q-menu>
<div class="q-pa-md" style="max-width: 500px">
<q-list>
<q-item>
<q-item-section avatar>
<q-icon color="primary" name="account_circle" />
</q-item-section>
<q-item-section>{{ fullName }}</q-item-section>
</q-item>
<q-item class="text-center">
<q-item-section>
<q-item-label>
<q-btn
color="primary"
label="ออกจากระบบ"
push
size="sm"
@click="onClickLogout"
/></q-item-label>
</q-item-section>
</q-item>
</q-list>
</div> </q-menu
></q-btn>
</q-toolbar> </q-toolbar>
<div class="col-12 text-grey-9">
<!-- </q-header> -->
<!-- <div :class="getClass(stetusCheckin)">
<div class="col-2">
<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>
<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
round
dense
flat
size="13px"
class="q-mx-md"
:disable="notiList.length === 0"
>
<q-icon name="notifications" size="24px" color="white" />
<q-badge
rounded
v-show="notiList.length > 0"
color="negative"
text-color="white"
floating
>{{ notiList.length }}</q-badge
>
<q-menu v-model="notiTrigger" max-width="480px" :offset="[0, 10]">
<div class="q-px-md q-py-sm row col-12 items-center">
<div class="text-subtitle1 text-weight-medium">
การแจงเตอน
</div>
</div>
<q-list
style="min-width: 300px"
v-for="n in notiList"
:key="n.id"
>
<q-item v-ripple class="mytry q-py-xs" dense>
<q-item-section avatar top style="min-width: 40px">
<q-avatar color="primary" size="22px" text-color="white">
<span class="text-weight-medium text-uppercase">{{
n.sender
}}</span>
</q-avatar>
</q-item-section>
<q-item-section>
<q-item-label caption class="text-black">{{
n.body
}}</q-item-label>
<q-item-label
caption
class="row items-center text-grey-7"
>{{ date2Thai(n.timereceive) }}</q-item-label
>
</q-item-section>
<q-btn
size="sm"
unelevated
dense
icon="mdi-close"
class="mybtn q-mx-xs"
@click="onClickDelete(n.id)"
></q-btn>
</q-item>
<q-separator color="grey-2" />
</q-list>
</q-menu>
</q-btn>
<q-btn
round
dense
flat
icon="account_circle"
style="font-size: 16px"
>
<q-menu>
<div class="q-pa-md" style="max-width: 500px">
<q-list>
<q-item>
<q-item-section avatar>
<q-icon color="primary" name="account_circle" />
</q-item-section>
<q-item-section>{{ fullName }}</q-item-section>
</q-item>
<q-item class="text-center">
<q-item-section>
<q-item-label>
<q-btn
color="primary"
label="ออกจากระบบ"
push
size="sm"
@click="onClickLogout"
/></q-item-label>
</q-item-section>
</q-item>
</q-list>
</div> </q-menu
></q-btn>
</div>
</div> -->
<div class="col-12 q-pa-md text-grey-9">
<div class="col-12 row justify-center"> <div class="col-12 row justify-center">
<div class="col-12 row q-py-sm justify-center"> <div class="col-12 row q-pt-md justify-center">
<div <div
class="col-xs-12 col-sm-10 text-h6 text-center text-weight-bold" class="col-xs-12 col-sm-10 text-h6 text-center text-weight-bold"
> >
@ -654,13 +284,12 @@ onMounted(async () => {
</div> </div>
</div> </div>
</div> </div>
<div class="col-xs-12 col-md-11 q-pa-md q-col-gutter-md row">
<div class="col-xs-12 col-md-11 row q-col-gutter-md"> <div class="col-xs-12 col-sm-8">
<div class="col-12 col-sm-8"> <MapCheck @update:location="updateLocation" class="col-12" />
<MapCheck @update:location="updateLocation" />
<!-- <AscMaps /> --> <!-- <AscMaps /> -->
</div> </div>
<div class="col-12 col-sm-4"> <div class="col-xs-12 col-sm-4">
<q-card flat bordered class="card-container"> <q-card flat bordered class="card-container">
<div <div
v-if="!cameraIsOn && img == null" v-if="!cameraIsOn && img == null"
@ -676,7 +305,7 @@ onMounted(async () => {
/> />
</div> </div>
</div> </div>
<div> <div class="col-12 row items-center">
<!-- แสดงกลองตอนกดถายภาพ --> <!-- แสดงกลองตอนกดถายภาพ -->
<Camera <Camera
:resolution="{ width: photoWidth, height: photoHeight }" :resolution="{ width: photoWidth, height: photoHeight }"
@ -717,7 +346,7 @@ onMounted(async () => {
</q-card> </q-card>
</div> </div>
<div class="col-12 q-mb-md"> <div class="col-xs-12 col-sm-12 items-center">
<q-card <q-card
bordered bordered
flat flat
@ -765,6 +394,7 @@ onMounted(async () => {
:rules="[(val) => !!val || 'กรุณาระบุสถานที่']" :rules="[(val) => !!val || 'กรุณาระบุสถานที่']"
lazy-rules lazy-rules
@update:model-value="selectLocation()" @update:model-value="selectLocation()"
hide-bottom-space
/> />
</div> </div>
<div <div
@ -783,45 +413,55 @@ onMounted(async () => {
/> />
</div> </div>
</q-card> </q-card>
</div>
<q-card bordered flat class="q-pa-sm">
<div class="col-12"> <div class="col-xs-12 col-sm-12">
<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="col-12 q-pt-sm">
<q-input <q-input
outlined outlined
v-model="remark" v-model="remark"
label="หมายเหตุ"
lazy-rules lazy-rules
dense dense
/> />
</div> </div>
</q-card> </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="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>
<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="img == null ? 'grey-6' : 'primary'"
push
size="18px"
:class="$q.screen.gt.xs ? 'q-px-md' : 'full-width q-pa-sm'"
:disable="camera && img ? false : true"
@click="validateForm"
/>
</div> </div>
</div> </div>
</div> </div>
@ -889,8 +529,9 @@ onMounted(async () => {
.card-container { .card-container {
position: relative; position: relative;
overflow: hidden; overflow: hidden;
height: 39vh; /* Adjust as needed */ height: autu; /* Adjust as needed */
background: #f6f5f5; background: #f6f5f5;
height: 35vh !important;
} }
.image-container { .image-container {
position: absolute; position: absolute;
@ -904,7 +545,7 @@ onMounted(async () => {
width: 100%; width: 100%;
height: 100%; height: 100%;
object-fit: cover; object-fit: cover;
border-radius: 5px; /* Adjust as needed */ border-radius: 8px; /* Adjust as needed */
} }
.preview-placeholder { .preview-placeholder {

366
src/views/MianView.vue Normal file
View file

@ -0,0 +1,366 @@
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import { useQuasar } from 'quasar'
import keycloak from '@/plugins/keycloak'
import http from '@/plugins/http'
import config from '@/app.config'
// import Type
import type { notiType } from '@/interface/index/Main'
// import Stores
import { useCounterMixin } from '@/stores/mixin'
const mixin = useCounterMixin()
const {
date2Thai,
showLoader,
hideLoader,
messageError,
dialogRemove,
success,
} = mixin
const router = useRouter()
const $q = useQuasar()
const notiTrigger = ref<boolean>(false)
const notiList = ref<notiType[]>([])
const totalNotiList = ref<number>(0)
/** function เรียกข้อมุลแจ้งเตือน */
async function fetchNotifications(index: number, type: string) {
showLoader()
await http
.get(config.API.msgNotificate + `?page=${index}&pageSize=${20}`)
.then((res) => {
const response = res.data.result.data
const list: notiType[] = []
if (type === 'DEL') {
totalNotiList.value = res.data.result.total
if (notiList.value.length === 14) {
notiList.value = []
response.map((e: any) => {
list.push({
id: e.id,
sender:
e.createdFullName == '' || e.createdFullName == null
? 'เจ้าหน้าที่'[0]
: e.createdFullName[0],
body: e.body ?? '',
timereceive: e.receiveDate ? date2Thai(e.receiveDate) : '-',
})
})
notiList.value.push(...list)
}
} else {
response.map((e: any) => {
list.push({
id: e.id,
sender:
e.createdFullName == '' || e.createdFullName == null
? 'เจ้าหน้าที่'[0]
: e.createdFullName[0],
body: e.body ?? '',
timereceive: e.receiveDate ? date2Thai(e.receiveDate) : '-',
})
})
notiList.value.push(...list)
totalNotiList.value = res.data.result.total
}
})
.catch((err) => {
messageError($q, err)
})
.finally(() => {
hideLoader()
})
}
/**
* function ลบรายการแจงเตอน
* @param id noti
*/
async function onClickDelete(id: string, index: number) {
dialogRemove($q, async () => {
await http
.delete(config.API.msgId(id))
.then(() => {
notiList.value.splice(index, 1)
success($q, 'ลบข้อมูลสำเร็จ')
})
.catch((e) => {
messageError($q, e)
})
.finally(async () => {
fetchNotifications(1, 'DEL')
hideLoader()
})
})
}
/** function logout*/
function onClickLogout() {
$q.dialog({
title: 'ยืนยันการออกจากระบบ',
message: `ต้องการออกจากระบบใช้หรือไม่?`,
cancel: 'ยกเลิก',
ok: 'ยืนยัน',
persistent: true,
}).onOk(() => {
keycloak.logout()
})
}
function onLoad(index: any, done: any) {
if (notiList.value.length < totalNotiList.value) {
setTimeout(() => {
fetchNotifications(index + 1, 'NOMAL')
done()
}, 2000)
}
}
// 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,
}
}
const fullName = ref<string>('')
onMounted(async () => {
await fetchNotifications(1, 'NOMAL')
if (keycloak.tokenParsed != null) {
fullName.value = keycloak.tokenParsed.name
}
})
</script>
<template>
<q-layout view="hHh LpR fFr">
<!-- header -->
<q-header flat class="text-dark col-12 bg-top header-br" height-hint="7">
<q-toolbar
class="q-my-xs items-center"
:style="$q.screen.gt.xs ? 'padding: 1% 2%;' : 'padding: 1% 4%;'"
>
<div class="row items-center">
<q-avatar style="background: linear-gradient(#4CE2D3, #02A998);">
<q-img
src="@/assets/logo1.png"
spinner-color="white"
style="height: 35px; width: 35px"
/>
</q-avatar>
<div class="row q-ml-md text-left items-center gt-xs">
<div
style="color: #ffffff; line-height: 10px"
class="text-body2 text-weight-bolder col-12"
>
ระบบ<span class="text-primary">ทรพยากรบคคล</span>
</div>
<div class="text-caption text-white">กรงเทพมหานคร</div>
</div>
</div>
<q-space />
<q-btn
icon="history"
unelevated
rounded
dense
flat
color="white"
@click="router.push('/history')"
/>
<q-btn
round
dense
flat
size="13px"
class="q-mx-md"
:disable="totalNotiList == 0"
>
<q-icon name="notifications" size="24px" color="white" />
<q-badge
rounded
v-show="totalNotiList !== 0"
color="negative"
text-color="white"
floating
>{{ totalNotiList }}</q-badge
>
<q-menu v-model="notiTrigger" max-width="480px" :offset="[0, 10]">
<div class="q-px-md q-py-sm row col-12 items-center">
<div class="text-subtitle1 text-weight-medium">
การแจงเตอน
</div>
</div>
<q-infinite-scroll @load="onLoad" :offset="250">
<div
v-for="(item, index) in notiList"
:key="index"
class="caption"
>
<q-item v-ripple class="mytry q-py-sm" dense>
<q-item-section avatar top style="min-width: 10px">
<q-avatar
rounded
color="primary"
size="25px"
text-color="white"
>
<span class="text-weight-medium text-uppercase">{{
item.sender[0]
}}</span>
</q-avatar>
</q-item-section>
<q-item-section>
<q-item-label caption class="text-black">{{
item.body
}}</q-item-label>
<q-item-label
caption
class="row items-center text-grey-7"
style="font-size: 12px"
>{{ item.timereceive }}</q-item-label
>
</q-item-section>
<div>
<q-btn
size="sm"
unelevated
dense
icon="mdi-close"
class="mybtn q-mx-xs"
@click="onClickDelete(item.id, index)"
></q-btn>
</div>
</q-item>
</div>
<template v-slot:loading v-if="notiList.length < totalNotiList">
<div class="row justify-center q-my-md">
<q-spinner-dots color="primary" size="40px" />
</div>
</template>
</q-infinite-scroll>
</q-menu>
</q-btn>
<q-btn round dense color="white" flat icon="account_circle" style="font-size: 16px">
<q-menu>
<div class="q-pa-md" style="max-width: 500px">
<q-list>
<q-item>
<q-item-section avatar>
<q-icon color="primary" name="account_circle" />
</q-item-section>
<q-item-section>{{ fullName }}</q-item-section>
</q-item>
<q-item class="text-center">
<q-item-section>
<q-item-label>
<q-btn
color="primary"
label="ออกจากระบบ"
push
size="sm"
@click="onClickLogout"
/></q-item-label>
</q-item-section>
</q-item>
</q-list>
</div> </q-menu
></q-btn>
</q-toolbar>
</q-header>
<div
class="bg-top"
:style="$q.screen.gt.xs ? 'height: 200px;' : 'height: 200px;'"
/>
<q-page-container class="bg-grey-2 q-pb-md">
<q-page
:style="
$q.screen.gt.xs
? 'padding: 1.8% 2%; margin-top: -200px;'
: 'padding: 5% 4%; margin-top: -200px;'
"
>
<router-view :key="$route.fullPath" />
</q-page>
</q-page-container>
</q-layout>
</template>
<style>
.bg-drawer {
background: #242a3d;
}
.menu {
padding-bottom: 5px;
padding-top: 5px;
}
.tabsHome .q-tab__icon {
font-size: 18px;
}
.border-100 {
border-radius: 100px;
}
.bg-top {
background: #273238 !important;
}
.header-br {
border-bottom: 1px solid #fdfdfd31;
}
.menuActive {
background: #1e2234;
border-radius: 0 100px 100px 0;
margin-right: 4%;
}
.q-card {
box-shadow: 3px 3px 20px -10px rgba(151, 150, 150, 0.261) !important;
/* border: 1px solid #eeeded; */
border-radius: 8px;
}
.q-menu {
box-shadow: 3px 3px 10px 1px rgba(95, 95, 95, 0.15) !important;
}
.toptitle {
font-size: 1.2rem;
font-weight: bold;
margin-bottom: 1.2%;
}
.q-field--outlined .q-field__control {
border-radius: 5px;
}
.q-field--outlined .q-field__control:before {
border-color: #c8d3db;
}
/* .q-field--outlined .q-icon {
color: #7474747f;
} */
.shadow-0 {
box-shadow: none !important;
}
.btnBlue {
background-color: #016987;
color: #fff;
}
</style>