KPI + UI สรุปผล

This commit is contained in:
STW_TTTY\stwtt 2024-06-20 16:35:41 +07:00
parent defcfd56da
commit abd7cd8beb
13 changed files with 1011 additions and 75 deletions

View file

@ -56,7 +56,8 @@ const formDataView = reactive<FormCommentByRole>({
id: "",
topic: "",
reason: "",
createdFullName: "",
score: "",
reasonEvaluator: "",
reasonCommander: "",
reasonCommanderHigh: "",

View file

@ -14,11 +14,13 @@ import { useRoute } from "vue-router";
import { useQuasar } from "quasar";
import { useKpiDataStore } from "@/modules/08_KPI/store";
const numLevel = ref<string>("");
const store = useKpiDataStore();
const $q = useQuasar();
const mixin = useCounterMixin();
const { dialogConfirm, showLoader, hideLoader, messageError, success } = mixin;
const rows = defineModel<any>("rows");
const modal = defineModel<boolean>("modal", { required: true });
const type = defineModel<string>("type", { required: true });
const idList = defineModel<string>("idList", { required: true });
@ -45,7 +47,8 @@ const formDataView = reactive<FormCommentByRole>({
id: "",
topic: "",
reason: "",
createdFullName: "",
score: "",
reasonEvaluator: "",
reasonCommander: "",
reasonCommanderHigh: "",
@ -58,6 +61,8 @@ function clickList(index: string, data: any) {
formDataView.id = data.id;
formDataView.topic = data.topic;
formDataView.reason = data.reason;
formDataView.createdFullName = data.createdFullName;
formDataView.score = data.score;
formDataView.reasonEvaluator = data.reasonEvaluator;
formDataView.reasonCommander = data.reasonCommander;
@ -93,6 +98,7 @@ function onSubmitAdd() {
{
reason: formDataAdd.reason,
topic: formDataAdd.topic,
score: type.value == "capacity" ? undefined : numLevel.value,
}
)
.then((res) => {
@ -197,6 +203,7 @@ function onSubmitComment(role: string) {
});
});
}
watch(
() => modal.value,
() => {
@ -210,7 +217,14 @@ watch(
<q-dialog v-model="modal" persistent>
<q-card style="min-width: 70vw">
<q-form greedy @submit.prevent @validation-success="onSubmit">
<DialogHeader tittle="รายงานความก้าวหน้า" :close="close" />
<DialogHeader
:tittle="
type == 'capacity' || type == 'development'
? 'บันทึกเหตุการณ์/พฤติกรรม'
: 'รายงานความก้าวหน้า'
"
:close="close"
/>
<q-separator />
<q-card-section class="q-pa-none">
<q-splitter
@ -233,13 +247,21 @@ watch(
}
"
>
<q-tooltip>เพมความกาวหน</q-tooltip>
<q-tooltip>{{
type == "capacity" || type == "development"
? "เพิ่มเหตุการณ์/พฤติกรรม"
: "เพิ่มความก้าวหน้า"
}}</q-tooltip>
</q-btn>
<q-card bordered flat class="no-shadow bg-white col-12">
<div class="row q-px-md q-py-sm items-center bg-grey-1">
<div class="col-12">
<span>ความกาวหน</span>
<span>{{
type == "capacity" || type == "development"
? "เหตุการณ์/พฤติกรรม"
: "ความก้าวหน้า"
}}</span>
</div>
</div>
@ -295,13 +317,36 @@ watch(
<div v-else>
<div class="row q-pa-md q-col-gutter-sm">
<div class="row col-12 text-weight-medium">
<div class="col-4 text-grey-6">วขอความกาวหน</div>
<div class="col-4 text-grey-6">
{{
type == "capacity" || type == "development"
? "เหตุการณ์/พฤติกรรม"
: "หัวข้อความก้าวหน้า"
}}
</div>
<div class="col-8">{{ formDataView.topic }}</div>
</div>
<div class="row col-12 text-weight-medium">
<div class="col-4 text-grey-6">รายละเอยดความกาวหน</div>
<div class="col-4 text-grey-6">
{{
type == "capacity" || type == "development"
? "รายละเอียด"
: "รายละเอียดความก้าวหน้า"
}}
</div>
<div class="col-8">{{ formDataView.reason }}</div>
</div>
<div
class="row col-12 text-weight-medium"
v-if="type !== 'capacity' && type !== 'development'"
>
<div class="col-4 text-grey-6">คะแนน</div>
<div class="col-8">{{ formDataView.score }}</div>
</div>
<div class="row col-12 text-weight-medium">
<div class="col-4 text-grey-6">สราง</div>
<div class="col-8">{{ formDataView.createdFullName }}</div>
</div>
<div class="col-12">
<q-separator />
</div>
@ -469,7 +514,14 @@ watch(
<q-dialog v-model="modalAdd" persistent>
<q-card style="min-width: 30vw">
<q-form greedy @submit.prevent @validation-success="onSubmitAdd">
<DialogHeader tittle="เพิ่มหัวข้อความก้าวหน้า" :close="closeAdd" />
<DialogHeader
:tittle="
type == 'capacity' || type == 'development'
? 'เพิ่มหัวข้อบันทึกเหตุการณ์/พฤติกรรม'
: 'เพิ่มหัวข้อความก้าวหน้า'
"
:close="closeAdd"
/>
<q-separator />
<q-card-section>
@ -479,11 +531,30 @@ watch(
v-model="formDataAdd.topic"
outlined
class="inputgreen"
label="หัวข้อความก้าวหน้า"
:label="
type == 'capacity' || type == 'development'
? 'เหตุการณ์/พฤติกรรม'
: 'หัวข้อความก้าวหน้า'
"
dense
lazy-rules
hide-bottom-space
:rules="[(val:string) => !!val || `${'กรุณากรอกหัวข้อความก้าวหน้า'}`,]"
:rules="[(val:string) => !!val || `${type == 'capacity'||type == 'development' ? 'กรุณากรอกเหตุการณ์/พฤติกรรม':'กรุณากรอกหัวข้อความก้าวหน้า' }`,]"
/>
</div>
<div
class="col-12"
v-if="type !== 'capacity' && type !== 'development'"
>
<q-input
v-model="numLevel"
outlined
class="inputgreen"
label="คะแนน"
dense
lazy-rules
hide-bottom-space
:rules="[(val:string) => !!val || `${'กรุณากรอกคะแนน'}`,]"
/>
</div>
<div class="col-12">
@ -491,11 +562,14 @@ watch(
v-model="formDataAdd.reason"
outlined
class="inputgreen"
label="รายละเอียดความก้าวหน้า"
:label="
type == 'capacity' || type == 'development'
? 'รายละเอียด'
: 'รายละเอียดความก้าวหน้า'
"
dense
lazy-rules
hide-bottom-space
:rules="[(val:string) => !!val || `${'กรุณากรอกรายละเอียดความก้าวหน้า'}`,]"
type="textarea"
/>
</div>
@ -520,4 +594,20 @@ watch(
background: #ebf9f7 !important;
color: #1bb19ab8 !important;
}
.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: #cde6fb !important;
}
.q-btn-group--outline > .q-btn-item + .q-btn-item.active:before {
border-left: 1px solid #2196f3 !important;
background-color: #cde6fb;
}
.q-btn-group--outline > .q-btn-item.active:not(:last-child):before {
border: 1px solid #2196f3;
background-color: #cde6fb;
}
</style>

View file

@ -0,0 +1,195 @@
<script setup lang="ts">
import { ref, reactive, onMounted, watch } from "vue";
import DialogHeader from "@/components/DialogHeader.vue";
import { useCounterMixin } from "@/stores/mixin";
import type { QTableProps } from "quasar";
const mixin = useCounterMixin();
const { showLoader, hideLoader } = mixin;
const modal = defineModel<boolean>("modal", { required: true });
const rows = defineModel<any>("rows", { required: true });
const dataRows = ref<any[]>([]);
const visibleColumns = ref<String[]>(["level", "description"]);
const columns = ref<QTableProps["columns"]>([
{
name: "level",
align: "center",
label: "ระดับสมรรถนะ",
sortable: true,
field: "level",
headerStyle: "font-size: 14px",
style: "font-size: 14px; width:5px;",
},
{
name: "description",
align: "left",
label: "พฤติกรรมที่คาดหวัง/พฤติกรรมย่อย",
sortable: true,
field: "description",
headerStyle: "font-size: 14px",
style: "font-size: 14px; width:15%;",
},
]);
/** ปิด dialog */
function closeDialog() {
modal.value = false;
rows.value = [];
dataRows.value = [];
}
watch(
() => rows.value,
() => {
if (rows.value) {
const data = rows.value;
dataRows.value = data.sort((a: any, b: any) => a.level - b.level);
}
}
);
</script>
<template>
<q-dialog v-model="modal" persistent>
<q-card class="col-12" style="width: 85%">
<DialogHeader :tittle="`พฤติกรรมที่คาดหวัง/พฤติกรรมย่อย`" :close="closeDialog" />
<q-separator />
<q-card-section class="q-pa-sm scroll" style="max-height: 80vh">
<div class="row">
<div class="col-12">
<d-table
flat
bordered
dense
:paging="true"
row-key="level"
virtual-scroll
:rows="dataRows"
hide-pagination
:columns="columns"
:visible-columns="visibleColumns"
>
<template v-slot:header="props">
<q-tr :props="props">
<q-th
v-for="col in props.cols"
:key="col.name"
:props="props"
class="bg-grey-2"
>
<span class="text-weight-bold">{{ col.label }}</span>
</q-th>
</q-tr>
</template>
<template v-slot:body="props">
<q-tr :props="props" class="cursor-pointer">
<q-td
v-for="col in props.cols"
:key="col.name"
:props="props"
>
<div v-if="(col.name = 'description')">
<span v-html="col.value"></span>
</div>
<div v-else>
{{ col.value }}
</div>
</q-td>
</q-tr>
</template>
<template #item="props">
<div class="q-pa-xs col-xs-12 col-sm-6 col-md-4 col-lg-3">
<q-card bordered flat>
<q-list>
<q-item
v-for="col in props.cols.filter((col:any) => col.name !== 'desc')"
:key="col.name"
>
<q-item-section>
<q-item-label caption>{{ col.label }}</q-item-label>
<q-item-label v-if="(col.name = 'description')">
<span v-html="col.value"></span
></q-item-label>
<q-item-label v-else>{{
col.value ?? "-"
}}</q-item-label>
</q-item-section>
</q-item>
</q-list>
</q-card>
</div>
</template>
<template v-slot:no-data="{ icon, message }">
<div class="q-pa-md text-weight-bold full-width text-center">
<span style="font-size: 16px">ไมพบขอมลสมรรถนะ</span>
</div>
</template>
</d-table>
</div>
</div>
</q-card-section>
</q-card>
</q-dialog>
</template>
<style scoped>
.my-menu-link {
background: #ebf9f7 !important;
color: #1bb19ab8 !important;
}
.no-shadow {
box-shadow: none !important;
}
.lineRight {
border-right: 1px solid #ededed !important;
}
.lineTop {
border-top: 1px solid #ededed !important;
}
.card-box {
border: 1px solid #ededed !important;
border-radius: 8px;
}
.custom-table2 {
.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;
}
}
</style>

View file

@ -0,0 +1,178 @@
<script setup lang="ts">
import { ref, reactive, onMounted, watch } from "vue";
import DialogHeader from "@/components/DialogHeader.vue";
import { useCounterMixin } from "@/stores/mixin";
import type { QTableProps } from "quasar";
const mixin = useCounterMixin();
const { showLoader, hideLoader } = mixin;
const modal = defineModel<boolean>("modal", { required: true });
const rows = defineModel<any>("rows", { required: true });
const dataRows = ref<any[]>([]);
const visibleColumns = ref<String[]>([ "description"]);
const columns = ref<QTableProps["columns"]>([
{
name: "description",
align: "left",
label: "พฤติกรรมที่คาดหวัง/พฤติกรรมย่อย",
sortable: true,
field: "description",
headerStyle: "font-size: 14px",
style: "font-size: 14px; width:15%;",
},
]);
/** ปิด dialog */
function closeDialog() {
modal.value = false;
rows.value = [];
dataRows.value = [];
}
</script>
<template>
<q-dialog v-model="modal" persistent>
<q-card class="col-12" style="width: 85%">
<DialogHeader :tittle="`ข้อมูลพฤติกรรมที่คาดหวัง/พฤติกรรมย่อย`" :close="closeDialog" />
<q-separator />
<q-card-section class="q-pa-sm scroll" style="max-height: 80vh">
<div class="row">
<div class="col-12">
<d-table
flat
bordered
dense
:paging="true"
row-key="level"
virtual-scroll
:rows="rows"
hide-pagination
:columns="columns"
:visible-columns="visibleColumns"
>
<template v-slot:header="props">
<q-tr :props="props">
<q-th
v-for="col in props.cols"
:key="col.name"
:props="props"
class="bg-grey-2"
>
<span class="text-weight-bold">{{ col.label }}</span>
</q-th>
</q-tr>
</template>
<template v-slot:body="props">
<q-tr :props="props" class="cursor-pointer">
<q-td
v-for="col in props.cols"
:key="col.name"
:props="props"
>
<div v-if="(col.name = 'description')">
<span v-html="col.value"></span>
</div>
<div v-else>
{{ col.value }}
</div>
</q-td>
</q-tr>
</template>
<template #item="props">
<div class="q-pa-xs col-xs-12 col-sm-6 col-md-4 col-lg-3">
<q-card bordered flat>
<q-list>
<q-item
v-for="col in props.cols.filter((col:any) => col.name !== 'desc')"
:key="col.name"
>
<q-item-section>
<q-item-label caption>{{ col.label }}</q-item-label>
<q-item-label v-if="(col.name = 'description')">
<span v-html="col.value"></span
></q-item-label>
<q-item-label v-else>{{
col.value ?? "-"
}}</q-item-label>
</q-item-section>
</q-item>
</q-list>
</q-card>
</div>
</template>
<template v-slot:no-data="{ icon, message }">
<div class="q-pa-md text-weight-bold full-width text-center">
<span style="font-size: 16px">ไมพบขอมลสมรรถนะ</span>
</div>
</template>
</d-table>
</div>
</div>
</q-card-section>
</q-card>
</q-dialog>
</template>
<style scoped>
.my-menu-link {
background: #ebf9f7 !important;
color: #1bb19ab8 !important;
}
.no-shadow {
box-shadow: none !important;
}
.lineRight {
border-right: 1px solid #ededed !important;
}
.lineTop {
border-top: 1px solid #ededed !important;
}
.card-box {
border: 1px solid #ededed !important;
border-radius: 8px;
}
.custom-table2 {
.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;
}
}
</style>

View file

@ -35,29 +35,29 @@ function close() {
modal.value = false;
}
// function getData() {
// showLoader();
// http
// .get(config.API.orgPosition+`/${store.dataProfile.profileId}`)
// .then((res) => {
// const data = res.data.result.isProbation;
// work.value = data;
// })
// .catch((e) => {
// messageError($q, e);
// })
// .finally(() => {
// hideLoader();
// });
// }
// watch(
// () => modal.value,
// (n) => {
// if (n == true) {
// getData();
// }
// }
// );
function getData() {
showLoader();
http
.get(config.API.orgPosition+`/${store.dataProfile.profileId}`)
.then((res) => {
const data = res.data.result.isProbation;
work.value = data;
})
.catch((e) => {
messageError($q, e);
})
.finally(() => {
hideLoader();
});
}
watch(
() => modal.value,
(n) => {
if (n == true) {
getData();
}
}
);
</script>
<template>