Compare commits

...

8 commits
v1.0.17 ... dev

Author SHA1 Message Date
DESKTOP-1R2VSQH\Lenovo ThinkPad E490
89801e787f Merge branch 'develop' into dev
All checks were successful
Build & Deploy on Dev / build (push) Successful in 1m18s
2026-05-29 13:55:22 +07:00
DESKTOP-1R2VSQH\Lenovo ThinkPad E490
a0f0443a55 refactor: downloadBlobFile with direct blob download logic 2026-05-29 13:55:05 +07:00
DESKTOP-1R2VSQH\Lenovo ThinkPad E490
d022ae189c Merge branch 'develop' into dev
All checks were successful
Build & Deploy on Dev / build (push) Successful in 1m17s
2026-05-29 11:36:06 +07:00
DESKTOP-1R2VSQH\Lenovo ThinkPad E490
650de029f3 feat(leave): DownloadFile form leave 2026-05-29 11:35:50 +07:00
DESKTOP-1R2VSQH\Lenovo ThinkPad E490
423b9f5fe3 Merge branch 'develop' into dev
All checks were successful
Build & Deploy on Dev / build (push) Successful in 1m31s
2026-05-29 10:55:56 +07:00
DESKTOP-1R2VSQH\Lenovo ThinkPad E490
54c7855b61 feat(leave): display leaveCountApproveCount 2026-05-29 10:55:41 +07:00
DESKTOP-1R2VSQH\Lenovo ThinkPad E490
2a27dadff7 Merge branch 'develop' into dev
All checks were successful
Build & Deploy on Dev / build (push) Successful in 1m27s
2026-05-25 13:25:11 +07:00
DESKTOP-1R2VSQH\Lenovo ThinkPad E490
0092133ba6 fix: await API fileByFile 2026-05-25 13:24:55 +07:00
7 changed files with 195 additions and 51 deletions

View file

@ -6,7 +6,7 @@ import http from "@/plugins/http";
import config from "@/app.config"; import config from "@/app.config";
import { useCounterMixin } from "@/stores/mixin"; import { useCounterMixin } from "@/stores/mixin";
import { useLeaveStore } from "@/modules/05_leave/store"; import { useLeaveStore } from "@/modules/05_leave/store";
import { useDataStore } from "@/stores/data"; import genReport from "@/plugins/genreport";
/** import type*/ /** import type*/
import type { import type {
@ -16,8 +16,6 @@ import type {
FromCancelDetail, FromCancelDetail,
} from "@/modules/05_leave/interface/response/leave"; } from "@/modules/05_leave/interface/response/leave";
import DialogHeader from "@/components/DialogHeader.vue";
import Workflow from "@/components/Workflow/Main.vue";
import FormLeave from "@/modules/05_leave/components/formDetail/01_SickForm.vue"; import FormLeave from "@/modules/05_leave/components/formDetail/01_SickForm.vue";
import FormChildbirth from "@/modules/05_leave/components/formDetail/04_HelpWifeBirthForm.vue"; import FormChildbirth from "@/modules/05_leave/components/formDetail/04_HelpWifeBirthForm.vue";
import FormHoliday from "@/modules/05_leave/components/formDetail/05_VacationForm.vue"; import FormHoliday from "@/modules/05_leave/components/formDetail/05_VacationForm.vue";
@ -33,7 +31,6 @@ import FormCancel from "@/modules/05_leave/components/formDetail/formCancel.vue"
const $q = useQuasar(); const $q = useQuasar();
const dataStore = useLeaveStore(); const dataStore = useLeaveStore();
const mainStore = useDataStore();
const { convertStatud } = dataStore; const { convertStatud } = dataStore;
const mixin = useCounterMixin(); const mixin = useCounterMixin();
const { const {
@ -411,6 +408,28 @@ async function onSubmit() {
}); });
} }
/**
* งกนดาวนโหลดไฟล
* @param id รหสการลา
* @param fileName อไฟล
* @param type ประเภทไฟล
*/
async function onClickDownloadFile(id: string, fileName: string, type: string) {
showLoader();
await http
.get(config.API.leaveReport(id))
.then(async (res) => {
const data = res.data.result;
await genReport(data, fileName, type);
})
.catch((err) => {
messageError($q, err);
})
.finally(() => {
hideLoader();
});
}
/**** ตรวจสอบว่ามีการส่งข้อมูลเข้ามาแล้วเปิด modal */ /**** ตรวจสอบว่ามีการส่งข้อมูลเข้ามาแล้วเปิด modal */
watch( watch(
() => props.modal, () => props.modal,
@ -434,10 +453,62 @@ watch(
v-if="props.leaveStatus != 'DELETE'" v-if="props.leaveStatus != 'DELETE'"
style="width: 900px; max-width: 80vw" style="width: 900px; max-width: 80vw"
> >
<DialogHeader <q-toolbar>
:tittle="`${titleMain} ${titleName}`" <q-toolbar-title class="text-subtitle2 text-bold">
:close="props.onClickClose" {{ ` ${titleMain} ${titleName}` }}
<q-btn class="q-mr-sm" icon="mdi-download" round color="primary" flat>
<q-tooltip>ดาวนโหลดไฟล</q-tooltip>
<q-menu>
<q-list style="min-width: 100px">
<q-item
clickable
v-close-popup
@click="
onClickDownloadFile(
formData.id,
formData.leaveSubTypeName
? formData.leaveSubTypeName
: formData.leaveTypeName,
'docx',
)
"
>
<q-item-section avatar>
<q-icon color="blue" name="mdi-file-word" />
</q-item-section>
<q-item-section>ไฟล .DOCX</q-item-section>
</q-item>
<q-item
clickable
v-close-popup
@click="
onClickDownloadFile(
formData.id,
formData.leaveSubTypeName
? formData.leaveSubTypeName
: formData.leaveTypeName,
'pdf',
)
"
>
<q-item-section avatar>
<q-icon color="red" name="mdi-file-pdf" />
</q-item-section>
<q-item-section>ไฟล .pdf</q-item-section>
</q-item>
</q-list>
</q-menu>
</q-btn>
</q-toolbar-title>
<q-btn
icon="close"
unelevated
round
dense
@click="props.onClickClose?.()"
style="color: #ff8080; background-color: #ffdede"
/> />
</q-toolbar>
<q-separator /> <q-separator />
<q-card-section v-if="isLoading"> <q-card-section v-if="isLoading">
@ -580,10 +651,62 @@ watch(
</q-card> </q-card>
<q-card v-if="props.leaveStatus === 'DELETE'" style="min-width: 40vw"> <q-card v-if="props.leaveStatus === 'DELETE'" style="min-width: 40vw">
<DialogHeader <q-toolbar>
:tittle="`${titleMainCancle} ${titleName}`" <q-toolbar-title class="text-subtitle2 text-bold">
:close="props.onClickClose" {{ ` ${titleMainCancle} ${titleName}` }}
<q-btn class="q-mr-sm" icon="mdi-download" round color="primary" flat>
<q-tooltip>ดาวนโหลดไฟล</q-tooltip>
<q-menu>
<q-list style="min-width: 100px">
<q-item
clickable
v-close-popup
@click="
onClickDownloadFile(
formData.id,
formData.leaveSubTypeName
? formData.leaveSubTypeName
: formData.leaveTypeName,
'docx',
)
"
>
<q-item-section avatar>
<q-icon color="blue" name="mdi-file-word" />
</q-item-section>
<q-item-section>ไฟล .DOCX</q-item-section>
</q-item>
<q-item
clickable
v-close-popup
@click="
onClickDownloadFile(
formData.id,
formData.leaveSubTypeName
? formData.leaveSubTypeName
: formData.leaveTypeName,
'pdf',
)
"
>
<q-item-section avatar>
<q-icon color="red" name="mdi-file-pdf" />
</q-item-section>
<q-item-section>ไฟล .pdf</q-item-section>
</q-item>
</q-list>
</q-menu>
</q-btn>
</q-toolbar-title>
<q-btn
icon="close"
unelevated
round
dense
@click="props.onClickClose?.()"
style="color: #ff8080; background-color: #ffdede"
/> />
</q-toolbar>
<q-separator /> <q-separator />
<q-card-section v-if="isLoading"> <q-card-section v-if="isLoading">

View file

@ -43,6 +43,7 @@ interface LeaveItem {
all: number; all: number;
use: number; use: number;
remain: number; remain: number;
leaveCountApproveCount: number;
} }
interface MainList { interface MainList {

View file

@ -141,12 +141,14 @@ async function fetchStatsTable() {
value: value:
el.leaveLimit > 0 el.leaveLimit > 0
? Math.round( ? Math.round(
(Number(el.leaveCountApprove) / Number(el.leaveLimit)) * 100 (Number(el.leaveCountApprove) / Number(el.leaveLimit)) *
100,
) )
: 0, : 0,
all: Number(el.leaveLimit), all: Number(el.leaveLimit),
use: el.leaveCountApprove, use: el.leaveCountApprove,
remain: Number(el.leaveLimit) - Number(el.leaveCountApprove), remain: Number(el.leaveLimit) - Number(el.leaveCountApprove),
leaveCountApproveCount: el.leaveCountApproveCount,
})); }));
}); });
stat.forEach((item) => itemPie.value.push(...item)); stat.forEach((item) => itemPie.value.push(...item));
@ -254,7 +256,7 @@ onMounted(async () => {
</q-knob> </q-knob>
</div> </div>
<div class="col-12 text-center text-weight-medium"> <div class="col-12 text-center text-weight-medium">
ลาพกผอน {{ item.text }}
</div> </div>
</div> </div>
<div class="row gt-xs"><q-separator vertical /></div> <div class="row gt-xs"><q-separator vertical /></div>
@ -262,17 +264,19 @@ onMounted(async () => {
<div class="col-12 row text-dark text-body2 items-center"> <div class="col-12 row text-dark text-body2 items-center">
<div class="col-12 row q-pa-xs q-px-md row"> <div class="col-12 row q-pa-xs q-px-md row">
<span class="text-grey-7 col-6">ได</span> <span class="text-grey-7 col-6">ได</span>
<span class="text-weight-bold">{{ item.all }}</span> <span class="text-weight-bold">{{ item.all }} </span>
</div> </div>
<div class="col-12"><q-separator /></div> <div class="col-12"><q-separator /></div>
<div class="col-12 row q-pa-xs q-px-md"> <div class="col-12 row q-pa-xs q-px-md">
<span class="text-grey-7 col-6">ใชไป</span> <span class="text-grey-7 col-6">ใชไป</span>
<span class="text-weight-bold">{{ item.use }}</span> <span class="text-weight-bold">{{ item.use }} </span>
</div> </div>
<div class="col-12"><q-separator /></div> <div class="col-12"><q-separator /></div>
<div class="col-12 row q-pa-xs q-px-md"> <div class="col-12 row q-pa-xs q-px-md">
<span class="text-grey-7 col-6">คงเหล</span> <span class="text-grey-7 col-6">คงเหล</span>
<span class="text-weight-bold">{{ item.remain }}</span> <span class="text-weight-bold"
>{{ item.remain }} </span
>
</div> </div>
</div> </div>
</div> </div>
@ -294,11 +298,11 @@ onMounted(async () => {
flat flat
class="shadow-0 col-12 fit row items-center q-px-lg" class="shadow-0 col-12 fit row items-center q-px-lg"
> >
<div class="text-subtitle2 col-4">ลาปวย</div> <div class="text-subtitle2 col-4">{{ item.text }}</div>
<div class="text-subtitle2 col-8"> <div class="text-subtitle2 col-8">
<span class="text-grey-7 q-pr-md">ใชไป</span> <span class="text-grey-7 q-pr-md">ใชไป</span>
<span class="text-weight-bold">{{ item.use }}</span> <span class="text-weight-bold">{{ item.use }} </span>
<!-- <span class="text-grey-7 q-pl-md">ลา</span> --> ({{ item.leaveCountApproveCount }} คร)
</div> </div>
</q-card> </q-card>
</div> </div>
@ -317,11 +321,11 @@ onMounted(async () => {
flat flat
class="shadow-0 col-12 fit row items-center q-px-lg" class="shadow-0 col-12 fit row items-center q-px-lg"
> >
<div class="text-subtitle2 col-4">ลากจสวนต</div> <div class="text-subtitle2 col-4">{{ item.text }}</div>
<div class="text-subtitle2 col-8"> <div class="text-subtitle2 col-8">
<span class="text-grey-7 q-pr-md">ใชไป</span> <span class="text-grey-7 q-pr-md">ใชไป</span>
<span class="text-weight-bold">{{ item.use }}</span> <span class="text-weight-bold">{{ item.use }} </span>
<!-- <span class="text-grey-7 q-pl-md">ลา</span> --> ({{ item.leaveCountApproveCount }} คร)
</div> </div>
</q-card> </q-card>
</div> </div>

View file

@ -9,6 +9,7 @@ import http from "@/plugins/http";
import config from "@/app.config"; import config from "@/app.config";
import { useCounterMixin } from "@/stores/mixin"; import { useCounterMixin } from "@/stores/mixin";
import { useKpiDataStore } from "@/modules/08_KPI/store"; import { useKpiDataStore } from "@/modules/08_KPI/store";
import avatar from "@/assets/avatar_user.jpg";
import type { FormProfile } from "@/modules/08_KPI/interface/request/index"; import type { FormProfile } from "@/modules/08_KPI/interface/request/index";
import type { import type {
@ -107,11 +108,13 @@ async function getAvatar(id: string) {
.then(async (res) => { .then(async (res) => {
const data = await res.data.result; const data = await res.data.result;
if (data.avatarName) { if (data.avatarName) {
await fetchProfile(id, data.avatarName); fetchProfile(id, data.avatarName);
} }
}) })
.catch((e) => { .catch((e) => {
messageError($q, e); messageError($q, e);
imgProfile.value = avatar;
store.dataEvaluation.avartar = avatar;
}) })
.finally(() => { .finally(() => {
isLoadAvatar.value = false; isLoadAvatar.value = false;
@ -119,12 +122,16 @@ async function getAvatar(id: string) {
} }
/** ดึงข้อมูล เพื่อเก็บรูปโปรไฟล์ */ /** ดึงข้อมูล เพื่อเก็บรูปโปรไฟล์ */
async function fetchProfile(id: string, avatarName: string) { function fetchProfile(id: string, avatarName: string) {
await http http
.get(config.API.fileByFile("ทะเบียนประวัติ", "โปรไฟล์", id, avatarName)) .get(config.API.fileByFile("ทะเบียนประวัติ", "โปรไฟล์", id, avatarName))
.then(async (res) => { .then((res) => {
store.dataEvaluation.avartar = res.data.downloadUrl; store.dataEvaluation.avartar = res.data.downloadUrl;
imgProfile.value = res.data.downloadUrl; imgProfile.value = res.data.downloadUrl;
})
.catch(() => {
imgProfile.value = avatar;
store.dataEvaluation.avartar = avatar;
}); });
} }
@ -208,7 +215,7 @@ async function getOrgOp() {
name: `${i.prefix}${i.firstName} ${i.lastName}`, name: `${i.prefix}${i.firstName} ${i.lastName}`,
})) }))
.find( .find(
(i: EvaOptionType) => i.id == store.dataEvaluation.commanderHighId (i: EvaOptionType) => i.id == store.dataEvaluation.commanderHighId,
); );
isLoadCommander.value = false; isLoadCommander.value = false;
}) })
@ -225,21 +232,21 @@ function filterOption(val: string, update: Function, refData: string) {
case "evaluatorIdOp": case "evaluatorIdOp":
update(() => { update(() => {
evaluatorIdOp.value = evaluatorIdMainOp.value.filter( evaluatorIdOp.value = evaluatorIdMainOp.value.filter(
(v: DataOptions) => v.name.indexOf(val) > -1 (v: DataOptions) => v.name.indexOf(val) > -1,
); );
}); });
break; break;
case "commanderIdOp": case "commanderIdOp":
update(() => { update(() => {
commanderIdOp.value = commanderIdMainOp.value.filter( commanderIdOp.value = commanderIdMainOp.value.filter(
(v: DataOptions) => v.name.indexOf(val) > -1 (v: DataOptions) => v.name.indexOf(val) > -1,
); );
}); });
break; break;
case "commanderHighOp": case "commanderHighOp":
update(() => { update(() => {
commanderHighOp.value = commanderHighMainOp.value.filter( commanderHighOp.value = commanderHighMainOp.value.filter(
(v: DataOptions) => v.name.indexOf(val) > -1 (v: DataOptions) => v.name.indexOf(val) > -1,
); );
}); });
break; break;
@ -278,7 +285,7 @@ function sendToEvaluatore() {
} }
}, },
"ยืนยันการส่งข้อตกลงให้ผู้ประเมินอนุมัติ", "ยืนยันการส่งข้อตกลงให้ผู้ประเมินอนุมัติ",
"ต้องการยืนยันส่งข้อตกลงนี้ให้ผู้ประเมินอนุมัติใช่หรือไม่?" "ต้องการยืนยันส่งข้อตกลงนี้ให้ผู้ประเมินอนุมัติใช่หรือไม่?",
); );
} }
@ -308,7 +315,7 @@ function sendToEvaluateEvaluatore() {
} }
}, },
"ยืนยันการส่งให้ผู้ประเมินรายงานผลสำเร็จของงาน", "ยืนยันการส่งให้ผู้ประเมินรายงานผลสำเร็จของงาน",
"ต้องการยืนยันส่งให้ผู้ประเมินรายงานผลสำเร็จของงานใช่หรือไม่?" "ต้องการยืนยันส่งให้ผู้ประเมินรายงานผลสำเร็จของงานใช่หรือไม่?",
); );
} }
@ -338,7 +345,7 @@ function requireEdit() {
} }
}, },
"ยืนยันการขอแก้ไขข้อตกลง", "ยืนยันการขอแก้ไขข้อตกลง",
"ต้องการยืนยันการขอแก้ไขข้อตกลงนี้ใช่หรือไม่?" "ต้องการยืนยันการขอแก้ไขข้อตกลงนี้ใช่หรือไม่?",
); );
} }
@ -411,7 +418,7 @@ async function goToSummary() {
store.excusiveIndicator2ScoreVal + store.excusiveIndicator2ScoreVal +
store.competencyScoreVal store.competencyScoreVal
).toFixed(2), ).toFixed(2),
} },
) )
.then((res) => {}); .then((res) => {});
@ -427,7 +434,7 @@ async function goToSummary() {
}); });
}, },
"ยืนยันการส่งไปสรุปผลการประเมิน", "ยืนยันการส่งไปสรุปผลการประเมิน",
"ต้องการยืนยันส่งไปสรุปผลการประเมินใช่หรือไม่?" "ต้องการยืนยันส่งไปสรุปผลการประเมินใช่หรือไม่?",
); );
} }
@ -517,7 +524,7 @@ async function downloadReport() {
store.dataEvaluation.prefix + store.dataEvaluation.prefix +
store.dataEvaluation.firstName + store.dataEvaluation.firstName +
" " + " " +
store.dataEvaluation.lastName store.dataEvaluation.lastName,
); );
}) })
.catch((e) => { .catch((e) => {
@ -556,7 +563,7 @@ async function clickUpload(file: any) {
const foundKey: string | undefined = Object.keys(res.data).find( const foundKey: string | undefined = Object.keys(res.data).find(
(key) => (key) =>
res.data[key]?.fileName !== undefined && res.data[key]?.fileName !== undefined &&
res.data[key]?.fileName !== "" res.data[key]?.fileName !== "",
); );
foundKey && foundKey &&
uploadFileDoc(res.data[foundKey]?.uploadUrl, fileUpload.value); uploadFileDoc(res.data[foundKey]?.uploadUrl, fileUpload.value);
@ -566,7 +573,7 @@ async function clickUpload(file: any) {
}); });
}, },
"ยืนยันการอัปโหลดไฟล์", "ยืนยันการอัปโหลดไฟล์",
"ต้องการยืนยันการอัปโหลดไฟล์นี้หรือไม่ ?" "ต้องการยืนยันการอัปโหลดไฟล์นี้หรือไม่ ?",
); );
} }
@ -623,7 +630,7 @@ function deleteFile(fileName: string) {
showLoader(); showLoader();
http http
.delete( .delete(
config.API.file("แบบกำหนดข้อตกลง", "KPI", id.value) + `/${fileName}` config.API.file("แบบกำหนดข้อตกลง", "KPI", id.value) + `/${fileName}`,
) )
.catch((e) => { .catch((e) => {
messageError($q, e); messageError($q, e);

View file

@ -151,6 +151,7 @@ async function getAvatar(id: string) {
}) })
.catch((e) => { .catch((e) => {
messageError($q, e); messageError($q, e);
profileImg.value = avatar;
}); });
} }
@ -168,14 +169,15 @@ function getList(id: string) {
}); });
} }
async function getImg(id: string, pathName: string) { function getImg(id: string, pathName: string) {
await http http
.get(config.API.fileByFile("ทะเบียนประวัติ", "โปรไฟล์", id, pathName)) .get(config.API.fileByFile("ทะเบียนประวัติ", "โปรไฟล์", id, pathName))
.then((res) => { .then((res) => {
profileImg.value = res.data.downloadUrl; profileImg.value = res.data.downloadUrl;
}) })
.catch((e) => { .catch((e) => {
messageError($q, e); profileImg.value = avatar;
// messageError($q, e);
hideLoader(); hideLoader();
}); });
} }
@ -188,7 +190,7 @@ function onSearch() {
rows.value = onSearchDataTable( rows.value = onSearchDataTable(
filter.value, filter.value,
rowsData.value, rowsData.value,
columns.value ? columns.value : [] columns.value ? columns.value : [],
); );
} }

View file

@ -2,7 +2,6 @@ import axios from "axios";
import config from "@/app.config"; import config from "@/app.config";
import { useCounterMixin } from "@/stores/mixin"; import { useCounterMixin } from "@/stores/mixin";
import { downloadBlobFile } from "@/modules/10_registry/utils/downloadFile";
const mixin = useCounterMixin(); const mixin = useCounterMixin();
const { showLoader, hideLoader } = mixin; const { showLoader, hideLoader } = mixin;
@ -32,11 +31,16 @@ async function genReport(data: any, fileName: string, type: string = "docx") {
const url = URL.createObjectURL(blob); const url = URL.createObjectURL(blob);
const baseName = fileName.trim(); const baseName = fileName.trim();
// const extension = type === "docx" ? "docx" : "pdf"; const extension = type === "docx" ? "docx" : "pdf";
await downloadBlobFile({ const link = document.createElement("a");
downloadUrl: url, link.href = url;
fileName: baseName, link.download = `${baseName}.${extension}`;
}); document.body.appendChild(link);
link.click();
setTimeout(() => {
document.body.removeChild(link);
URL.revokeObjectURL(url);
}, 100);
} }
}) })
.catch((err) => { .catch((err) => {

View file

@ -74,7 +74,7 @@ async function checkUser() {
await dataStore.getProFileType(); await dataStore.getProFileType();
kpiDataStore.dataProfile = data; // Set dataProfile in kpiDataStore kpiDataStore.dataProfile = data; // Set dataProfile in kpiDataStore
if (data.avatarName) { if (data.avatarName) {
await getImg(data.profileId, data.avatarName); getImg(data.profileId, data.avatarName);
} else { } else {
dataStore.profileImg = avatar; dataStore.profileImg = avatar;
} }
@ -106,6 +106,9 @@ function getImg(id: string, pathName: string) {
.get(config.API.fileByFile("ทะเบียนประวัติ", "โปรไฟล์", id, pathName)) .get(config.API.fileByFile("ทะเบียนประวัติ", "โปรไฟล์", id, pathName))
.then((res) => { .then((res) => {
dataStore.profileImg = res.data.downloadUrl; dataStore.profileImg = res.data.downloadUrl;
})
.catch(() => {
dataStore.profileImg = avatar;
}); });
} }