hrms-user/src/modules/08_KPI/components/Tab/01_Assessment.vue
2025-01-30 11:36:43 +07:00

717 lines
21 KiB
Vue

<script setup lang="ts">
import { ref, onMounted, computed, watch } from "vue";
import { useQuasar } from "quasar";
import { useRoute, useRouter } from "vue-router";
import config from "@/app.config";
import http from "@/plugins/http";
import { useCounterMixin } from "@/stores/mixin";
import { useKpiDataStore } from "@/modules/08_KPI/store";
import type { ListCriteria } from "@/modules/08_KPI/interface/request/index";
import type { EvaluationIndicatorType } from "@/modules/08_KPI/interface/response/index";
import Work from "@/modules/08_KPI/components/Tab/Topic/01_Indicator.vue";
import Competency from "@/modules/08_KPI/components/Tab/Topic/02_Competency.vue";
import Develop from "@/modules/08_KPI/components/Tab/Topic/03_Develop.vue";
import DialogListCriteria from "@/modules/08_KPI/components/Tab/Dialog/DialogListCriteria.vue";
import DialogHeader from "@/components/DialogHeader.vue";
const dataListCriteria = ref<ListCriteria[]>([]);
const modalReject = ref<boolean>(false); // ตัวแปร dialog ไม่อนุมัติตีกลับให้แก้ไข
const reason = ref<string>(""); //ตัวแปร เหตุผลการตีกลับ
const modalCriteria = ref<boolean>(false);
const $q = useQuasar();
const route = useRoute();
const router = useRouter();
const {
hideLoader,
messageError,
success,
showLoader,
dialogRemove,
dialogConfirm,
} = useCounterMixin();
const store = useKpiDataStore();
const evaluationId = ref<string>(route.params.id.toString());
const rows_01 = ref<EvaluationIndicatorType[]>();
const rows_02 = ref<EvaluationIndicatorType[]>();
const rows_03 = ref<EvaluationIndicatorType[]>();
const totalResults1 = ref<number>(0);
const totalResults2 = ref<number>(0);
const totalResults3 = ref<number>(0);
const weightPlanned = ref<number>(0);
const weightRole = ref<number>(0);
const weightAssigned = ref<number>(0);
const resultPlanned = ref<number>(0);
const resultRole = ref<number>(0);
const resultAssigned = ref<number>(0);
function fetchListPlanned() {
http
.get(config.API.kpiAchievement("planned") + `?id=${evaluationId.value}`)
.then((res) => {
const data = res.data.result;
const newRow = data.map((e: EvaluationIndicatorType) => ({
...e,
evaluationResults: (e.point / 5) * e.weight,
}));
rows_01.value = newRow;
if (newRow.length > 0) {
resultPlanned.value = newRow.reduce(
(sum: number, e: any) => sum + e.evaluationResults,
0
);
store.excusiveIndicator1PercentVal = resultPlanned.value;
const weight = newRow.reduce(
(sum: number, e: any) => sum + e.weight,
0
);
weightPlanned.value = weight;
store.indicatorWeight1Total = Number(weight);
totalResults1.value =
(resultPlanned.value * store.excusiveIndicator1Weight) / weight;
store.excusiveIndicator1ScoreVal = totalResults1.value;
}
})
.catch((err) => {
messageError($q, err);
});
}
function fetchListRole() {
http
.get(config.API.kpiAchievement("role") + `?id=${evaluationId.value}`)
.then((res) => {
const data = res.data.result;
const newRow = data.map((e: EvaluationIndicatorType) => ({
...e,
evaluationResults: (e.point / 5) * e.weight,
}));
rows_02.value = newRow;
if (newRow.length > 0) {
resultRole.value = newRow.reduce(
(sum: number, e: any) => sum + e.evaluationResults,
0
);
const weight = newRow.reduce(
(sum: number, e: any) => sum + e.weight,
0
);
weightRole.value = weight;
}
})
.catch((err) => {
messageError($q, err);
});
}
function fetchAssigned() {
http
.get(config.API.kpiAchievement("special") + `?id=${evaluationId.value}`)
.then((res) => {
const data = res.data.result;
const newRow = data.map((e: any) => ({
...e,
evaluationResults: (e.point / 5) * e.weight,
}));
rows_03.value = newRow;
if (newRow.length > 0) {
resultAssigned.value = newRow.reduce(
(sum: number, e: any) => sum + e.evaluationResults,
0
);
store.excusiveIndicator2PercentVal = resultAssigned.value;
const weight = newRow.reduce(
(sum: number, e: any) => sum + e.weight,
0
);
weightAssigned.value = weight;
store.indicatorWeight2Total = Number(weight);
totalResults3.value =
(resultAssigned.value * store.excusiveIndicator2Weight) / weight;
store.excusiveIndicator2ScoreVal = totalResults3.value;
}
})
.catch((err) => {
messageError($q, err);
});
}
function getCriteria() {
http
.get(config.API.KpiEvaluationInfo)
.then((res) => {
const data = res.data.result.data;
dataListCriteria.value = data;
})
.catch((e) => {
messageError($q, e);
});
}
const isShowScore = computed(() => {
return store.tabMain === "3";
});
watch(
[weightPlanned, weightRole, weightAssigned],
([newA, newB, newC], [prevA, prevB, prevC]) => {
if (newA !== prevA || newB !== prevB || newC !== prevC) {
store.indicatorWeightTotal =
Number(weightPlanned.value) +
Number(weightAssigned.value) +
Number(weightRole.value);
}
}
);
watch(
[resultPlanned, resultRole, resultAssigned],
([newA, newB, newC], [prevA, prevB, prevC]) => {
if (newA !== prevA || newB !== prevB || newC !== prevC) {
store.indicatorPercentVal =
Number(resultPlanned.value) +
Number(resultRole.value) +
Number(resultAssigned.value);
store.indicatorScoreVal =
store.indicatorPercentVal *
((store.dataProfile.isProbation
? store.indicatorProbationScore
: store.indicatorScore) /
100);
}
}
);
function rejectAgreement() {
modalReject.value = true;
}
async function onSubmitReject() {
dialogConfirm($q, async () => {
showLoader();
await http
.put(config.API.kpiReject(evaluationId.value), {
reason: reason.value,
actor: store.roleText(store.rolePerson),
})
.then((res) => {
router.push(`/KPI-evaluator`);
success($q, `บันทึกข้อมูลสำเร็จ`);
})
.catch((e) => {
messageError($q, e);
})
.finally(() => {
hideLoader();
});
});
}
onMounted(() => {
getCriteria();
fetchListPlanned();
fetchListRole();
fetchAssigned();
setTimeout(() => {
hideLoader();
}, 2000);
});
</script>
<template>
<q-scroll-area
style="height: 100vh; width: auto"
class="bg-white row col-12 text-dark q-pa-md"
>
<div class="row items-center full-width">
<div v-if="store.dataEvaluation.isReject == true && store.dataEvaluation.isReject !== null" class="col-12 q-pb-sm q-gutter-sm">
<q-banner
inline-actions
bordered
class="bg-orange-1 text-orange border-orange"
>
<div class="row items-center">
<q-icon name="mdi-information-outline" size="20px" />
<div class="column q-pl-sm">
<span>{{
`${
store.dataEvaluation.actorNameReject
? store.dataEvaluation.actorNameReject
: ""
} ${
store.dataEvaluation.actorReject
? `(${store.dataEvaluation.actorReject})`
: ``
}`
}}</span>
<span
>เหตผลการตกล : {{ store.dataEvaluation.reasonReject }}</span
>
</div>
</div>
</q-banner>
</div>
<span class="txt-under text-blue-6 text-weight-bold text-body2"
>องคประกอบท 1
</span>
<span class="q-ml-sm text-weight-bold text-body2"> ผลสมฤทธของงาน</span>
<q-space />
<q-btn
v-if="
store.rolePerson !== 'USER' &&
store.tabOpen == 1 &&
((store.dataEvaluation.evaluationStatus == 'NEW_EVALUATOR' &&
store.rolePerson == 'EVALUATOR') ||
(store.dataEvaluation.evaluationStatus == 'NEW_COMMANDER' &&
store.rolePerson == 'COMMANDER') ||
(store.dataEvaluation.evaluationStatus == 'NEW_COMMANDER_HIGH' &&
store.rolePerson == 'COMMANDERHIGH'))
"
:disabled="store.dataEvaluation.evaluatorId == null"
unelevated
outline
color="grey-2"
text-color="red"
size="md"
label="ไม่อนุมัติตีกลับให้แก้ไข"
@click="rejectAgreement()"
>
<q-tooltip>ไม่อนุมัติตีกลับให้แก้ไข</q-tooltip>
</q-btn>
</div>
<div class="q-gutter-md q-mt-sm">
<!-- องค์ประกอบที่ 1 -->
<div
v-if="
store.dataEvaluation.posTypeName == 'อำนวยการ' ||
store.dataEvaluation.posTypeName == 'บริหาร'
"
>
<Work
v-model:data="rows_01"
:title="`มิติที่ 1 ภารกิจตามนโยบายและยุทธศาสตร์ของกรุงเทพมหานคร`"
:page="1"
:fetchList="fetchListPlanned"
:total="totalResults1"
/>
<div v-if="isShowScore">
<q-table
flat
dense
bordered
:rows="[
{
name: 'รวมผลการประเมิน (ร้อยละ)',
value: store.excusiveIndicator1PercentVal.toFixed(2),
},
{
name: 'ผลการประเมินมิติที่ 1 (คะแนน)',
value: store.excusiveIndicator1ScoreVal.toFixed(2),
},
]"
:columns="[
{
name: 'name',
field: 'name',
label: 'name',
style: 'font-size: 14px',
},
{
name: 'value',
field: 'value',
label: 'value',
style: 'font-size: 14px; font-weight: bold',
},
]"
row-key="name"
hide-header
hide-bottom
class="q-mt-xs q-mb-md"
/>
</div>
<div v-else class="q-mt-md"></div>
<Work
v-model:data="rows_03"
:title="`มิติที่ 2 วาระเร่งด่วนที่ได้รับมอบหมายพิเศษ (ถ้ามี)`"
:page="3"
:fetchList="fetchAssigned"
:total="totalResults3"
/>
<div v-if="isShowScore">
<q-table
flat
dense
bordered
:rows="[
{
name: 'ผลการประเมินมิติที่ 2 (คะแนน)',
value: store.excusiveIndicator2ScoreVal.toFixed(2),
},
]"
:columns="[
{
name: 'name',
field: 'name',
label: 'name',
style: 'font-size: 14px',
},
{
name: 'value',
field: 'value',
label: 'value',
style: 'font-size: 14px; font-weight: bold',
},
]"
row-key="name"
hide-header
hide-bottom
class="q-mt-xs q-mb-md"
/>
<div class="row text-body2 text-weight-bold">
<div class="col-12 text-center row justify-center">
<span
>สรุปผลการประเมินผลสัมฤทธิ์ของงาน (มิติที่ 1 + มิติที่ 2)
(คะแนนเต็ม
{{ store.excusiveIndicatorScore }}
คะแนน)</span
>
<div class="text-primary q-pl-md">
{{
(
store.excusiveIndicator1ScoreVal +
store.excusiveIndicator2ScoreVal
).toFixed(2)
}}
</div>
</div>
</div>
</div>
</div>
<div v-else>
<Work
v-model:data="rows_01"
:title="`1. งานตามแผนปฏิบัติราชการประจำปี`"
:page="1"
:fetchList="fetchListPlanned"
:total="totalResults1"
/>
<Work
v-model:data="rows_02"
:title="`2. งานตามหน้าที่ความรับผิดชอบหลัก`"
:page="2"
:fetchList="fetchListRole"
:total="totalResults2"
/>
<Work
v-model:data="rows_03"
:title="`3. งานอื่นๆ ที่ได้รับมอบหมาย`"
:page="3"
:fetchList="fetchAssigned"
:total="totalResults3"
/>
<div v-if="isShowScore">
<q-table
flat
dense
bordered
:rows="[
{
name: 'รวมผลการประเมิน (ร้อยละ)',
value: store.indicatorPercentVal.toFixed(2),
},
]"
:columns="[
{
name: 'name',
field: 'name',
label: 'name',
style: 'font-size: 14px',
},
{
name: 'value',
field: 'value',
label: 'value',
style: 'font-size: 14px; font-weight: bold',
},
]"
row-key="name"
hide-header
hide-bottom
class="q-mt-xs q-mb-md"
/>
<div class="row text-body2 text-weight-bold">
<div class="col-12 text-center row justify-center">
<span
>สรุปผลการประเมินผลสัมฤทธิ์ของงาน (คะแนนเต็ม
{{
store.dataProfile.isProbation
? store.indicatorProbationScore
: store.indicatorScore
}}
คะแนน)</span
>
<div class="text-primary q-pl-md">
{{ store.indicatorScoreVal.toFixed(2) }}
</div>
</div>
</div>
</div>
</div>
<q-separator size="3px" class="q-my-lg" />
<!-- องค์ประกอบที่ 2 -->
<div class="text-weight-bold text-body2 q-mb-sm">
<span class="txt-under text-blue-6">องค์ประกอบที่ 2</span>
<span class="q-ml-sm"> พฤติกรรมการปฎิบัติราชการ (สมรรถนะ)</span>
</div>
<Competency v-model:dataListCriteria="dataListCriteria" />
<q-table
v-if="isShowScore"
flat
dense
bordered
:rows="[
{
name: `สรุปผลการประเมินสมรรถนะ (คะแนนเต็ม ${
store.dataEvaluation.posTypeName != 'อำนวยการ' &&
store.dataEvaluation.posTypeName != 'บริหาร'
? store.dataProfile.isProbation
? store.competencyProbationScore
: store.competencyScore
: store.excusiveCompetencyScore
} คะแนน)`,
value: store.competencyScoreVal.toFixed(2),
},
]"
:columns="[
{
name: 'name',
field: 'name',
label: 'name',
style: 'font-size: 14px',
},
{
name: 'value',
field: 'value',
label: 'value',
style: 'font-size: 14px; font-weight: bold',
},
]"
row-key="name"
hide-header
hide-bottom
class="q-mt-xs q-mb-md"
/>
<div
v-if="
store.dataEvaluation.posTypeName != 'อำนวยการ' &&
store.dataEvaluation.posTypeName != 'บริหาร'
"
>
<Develop />
<div v-if="isShowScore">
<q-table
flat
dense
bordered
:rows="[
{
name: `ผลการประเมินการพัฒนาตนเอง (คะแนนเต็ม ${
store.dataProfile.isProbation
? store.devProbationScore
: store.devScore
} คะแนน)`,
value: store.devScoreVal.toFixed(2),
},
]"
:columns="[
{
name: 'name',
field: 'name',
label: 'name',
style: 'font-size: 14px',
},
{
name: 'value',
field: 'value',
label: 'value',
style: 'font-size: 14px; font-weight: bold',
},
]"
row-key="name"
hide-header
hide-bottom
class="q-mt-xs q-mb-md"
/>
</div>
</div>
<div v-if="isShowScore">
<div
v-if="
store.dataEvaluation.posTypeName != 'อำนวยการ' &&
store.dataEvaluation.posTypeName != 'บริหาร'
"
class="row text-body2 text-weight-bold"
>
<div class="col-12 text-center row justify-center">
<span>
สรุปผลการประเมินพฤติกรรมการปฏิบัติราชการ (สมรรถนะ+การพัฒนาตนเอง)
(คะแนนเต็ม
{{
store.dataProfile.isProbation
? store.competencyDevProbationScore
: store.competencyDevScore
}}
คะแนน)
</span>
<div class="text-primary q-pl-md">
{{ (store.competencyScoreVal + store.devScoreVal).toFixed(2) }}
</div>
</div>
</div>
<div v-else class="row text-body2 text-weight-bold">
<div class="col-12 text-center row justify-center">
<span
>สรุปผลการประเมินพฤติกรรมการปฏิบัติราชการ (สมรรถนะ) (คะแนนเต็ม
{{ store.competencyScore }} คะแนน)</span
>
<div class="text-primary q-pl-md">
{{ store.competencyScoreVal.toFixed(2) }}
</div>
</div>
</div>
</div>
</div>
</q-scroll-area>
<q-dialog persistent v-model="modalReject">
<q-card style="width: 40vw; max-width: 80vw">
<q-form greedy @submit.prevent @validation-success="onSubmitReject">
<DialogHeader
:tittle="`ไม่อนุมัติตีกลับให้แก้ไข`"
:close="() => ((modalReject = false), (reason = ''))"
/>
<q-separator />
<q-card-section class="q-pa-md">
<q-input
v-model="reason"
type="textarea"
rows="5"
dense
outlined
label="เหตุผลการตีกลับ"
hide-bottom-space
:rules="[ (val:string) => !!val ||
`${'กรุณาระบุเหตุผลการตีกลับ'}`, ]"
>
</q-input>
</q-card-section>
<q-separator />
<q-card-actions align="right">
<q-btn label="บันทึก" color="secondary" type="submit"
><q-tooltip>นทกขอม</q-tooltip></q-btn
>
</q-card-actions>
</q-form>
</q-card>
</q-dialog>
<DialogListCriteria
v-model:modal="modalCriteria"
v-model:dataListCriteria="dataListCriteria"
/>
</template>
<style scoped>
.txt-under {
text-decoration: underline;
}
.custom-table2 {
max-height: 64vh;
.q-table tr:nth-child(odd) td {
background: white;
}
.q-table tr:nth-child(even) td {
background: #f8f8f8;
}
.q-table thead tr {
background: #ecebeb;
}
.q-table thead tr th {
position: sticky;
}
.q-table td:nth-of-type(2) {
z-index: 3 !important;
}
.q-table th:nth-of-type(2),
.q-table td:nth-of-type(2) {
position: sticky;
left: 0;
z-index: 1;
}
/* this will be the loading indicator */
.q-table thead tr:last-child th {
/* height of all previous header rows */
top: 48px;
}
.q-table thead tr:first-child th {
top: 0;
}
.q-btn-group--outline > .q-btn-item:not(:last-child):before {
border-right: 1px solid #c4c4c4;
}
.q-btn-group--outline > .q-btn-item.active {
color: #2196f3 !important;
background-color: #fff;
}
.q-btn-group--outline > .q-btn-item.active:not(:last-child):before {
border: 1px solid #2196f3;
}
}
</style>