ui report leave

This commit is contained in:
DESKTOP-1R2VSQH\Lenovo ThinkPad E490 2024-12-06 15:59:51 +07:00
parent 37cfa0c053
commit b14ca52808
4 changed files with 696 additions and 492 deletions

View file

@ -40,6 +40,10 @@ export default {
leaveDeleteReject: (id: string) => `${leave}/admin/delete/reject/${id}`, leaveDeleteReject: (id: string) => `${leave}/admin/delete/reject/${id}`,
/**รายงาน */ /**รายงาน */
leaveReportTimeRecords: () => `${leaveReport}/time-records/officer`, leaveReportTimeRecords: (type: string) =>
`${leaveReport}/time-records/${type}`,
leaveReportTimeLate: (type: string) => `${leaveReport}/late/${type}`,
leaveReportLeaveday: (type: string) => `${leaveReport}/leaveday/${type}`, leaveReportLeaveday: (type: string) => `${leaveReport}/leaveday/${type}`,
leaveReportLeave2: (type: string) => `${leaveReport}/leave2/${type}`,
}; };

View file

@ -7,19 +7,8 @@ interface DataOption2 {
name: string; name: string;
} }
interface DataDateWeeklyObject {
startDay: number;
endDay: number;
month: number;
year: number;
}
interface DataDateMonthObject { interface DataDateMonthObject {
month: number; month: number;
year: number; year: number;
} }
export type { export type { DataOption, DataOption2, DataDateMonthObject };
DataOption,
DataOption2,
DataDateWeeklyObject,
DataDateMonthObject,
};

View file

@ -4,33 +4,59 @@ import { ref, onMounted } from "vue";
import { VuePDF, usePDF } from "@tato30/vue-pdf"; import { VuePDF, usePDF } from "@tato30/vue-pdf";
import { useQuasar } from "quasar"; import { useQuasar } from "quasar";
import http from "@/plugins/http"; import { useRoute } from "vue-router";
import config from "@/app.config";
import { checkPermission } from "@/utils/permissions"; import { checkPermission } from "@/utils/permissions";
import { useCounterMixin } from "@/stores/mixin"; import { useCounterMixin } from "@/stores/mixin";
import { useStructureTree } from "@/stores/structureTree";
import genReportXLSX from "@/plugins/genreportxlsx";
import http from "@/plugins/http";
import config from "@/app.config";
import type { DataOption } from "@/modules/09_leave/interface/index/Main"; import type { DataStructureTree } from "@/interface/main";
import type {
DataOption,
DataDateMonthObject,
} from "@/modules/09_leave/interface/index/Main";
import LoadView from "@/components/LoadView.vue";
/** use*/ /** use*/
const mixin = useCounterMixin();
const $q = useQuasar(); const $q = useQuasar();
const { showLoader, hideLoader, date2Thai, dateToISO, messageError } = mixin; const route = useRoute();
const { fetchStructureTree } = useStructureTree();
const apiGenReport = const {
"https://report-server.frappet.synology.me/api/v1/report-template/xlsx"; showLoader,
hideLoader,
date2Thai,
dateToISO,
messageError,
monthYear2Thai,
} = useCounterMixin();
const year = ref<number>(new Date().getFullYear()); const year = ref<number>(new Date().getFullYear());
const dateStart = ref<Date>(new Date(year.value, 9, 1)); const dateStart = ref<Date>(new Date(year.value, 9, 1));
const dateEnd = ref<Date>(new Date(year.value + 1, 8, 30)); const dateEnd = ref<Date>(new Date(year.value + 1, 8, 30));
const employeeClass = ref<string>("employee"); const dateMonth = ref<DataDateMonthObject>({
month: new Date().getMonth(),
year: new Date().getFullYear(),
});
const typeReport = ref<string>("");
const optionReport = ref<DataOption[]>([
{ id: "1", name: "รายงานการลางานตามประเภทการลา" },
{ id: "2", name: "รายงานการลางาน" },
]);
const employeeClass = ref<string>("officer");
const yearType = ref<string>("FULL"); const yearType = ref<string>("FULL");
const employeeClassMain = ref<DataOption[]>([ const employeeClassMain = ref<DataOption[]>([
{ id: "employee", name: "ลูกจ้างประจำ กทม." },
{ id: "officer", name: "ข้าราชการ กทม. สามัญ" }, { id: "officer", name: "ข้าราชการ กทม. สามัญ" },
{ id: "employee", name: "ลูกจ้างประจำ กทม." },
]); ]);
const yearTypeOptionMain = ref<DataOption[]>([ const yearTypeOptionMain = ref<DataOption[]>([
{ id: "FULL", name: "รายปี" }, { id: "FULL", name: "รายปี" },
{ id: "MONTH", name: "รายเดือน" },
{ id: "FIRSTHAFT", name: "ครึ่งปีแรก" }, { id: "FIRSTHAFT", name: "ครึ่งปีแรก" },
{ id: "SECONDHAFT", name: "ครึ่งปีหลัง" }, { id: "SECONDHAFT", name: "ครึ่งปีหลัง" },
]); ]);
@ -38,26 +64,76 @@ const employeeClassOption = ref<DataOption[]>(employeeClassMain.value);
const yearTypeOptionOption = ref<DataOption[]>(yearTypeOptionMain.value); const yearTypeOptionOption = ref<DataOption[]>(yearTypeOptionMain.value);
const detailReport = ref<any>(); const detailReport = ref<any>();
const isReport = ref<boolean>(false);
const isLoadPDF = ref<boolean>(false);
/** tree*/
const filterTree = ref<string>("");
const nodeId = ref<string>("");
const nodeLevel = ref<number>(0);
const node = ref<DataStructureTree[]>([]);
const expanded = ref<string[]>([]);
async function fetchDataTree() {
node.value = await fetchStructureTree(route.meta.Key as string, true);
}
function onSelectedNode(id: string, level: number) {
nodeId.value = id;
nodeLevel.value = level;
updateLeaveday();
}
/** function อัปเดทบัญชีแสดงวันลา */ /** function อัปเดทบัญชีแสดงวันลา */
async function updateLeaveday() { async function updateLeaveday() {
if (yearType.value === "FULL") { if (!nodeId.value || !typeReport.value) {
dateStart.value = new Date(year.value - 1, 9, 1); return false;
dateEnd.value = new Date(year.value, 8, 30);
} else if (yearType.value === "FIRSTHAFT") {
dateStart.value = new Date(year.value - 1, 9, 1);
dateEnd.value = new Date(year.value, 2, 31);
} else if (yearType.value === "SECONDHAFT") {
dateStart.value = new Date(year.value, 3, 1);
dateEnd.value = new Date(year.value, 8, 30);
} }
fetchLeaveday( isReport.value = false;
employeeClass.value, isLoadPDF.value = true;
yearType.value, pdfSrc.value = undefined;
dateStart.value, switch (yearType.value) {
dateEnd.value case "FULL":
); dateStart.value = new Date(year.value - 1, 9, 1);
dateEnd.value = new Date(year.value, 8, 30);
break;
case "MONTH":
const mount = dateMonth.value.month + 1;
//
dateStart.value = new Date(dateMonth.value.year, mount - 1, 1);
//
dateEnd.value = new Date(dateMonth.value.year, mount, 0);
break;
case "FIRSTHAFT":
dateStart.value = new Date(year.value - 1, 9, 1);
dateEnd.value = new Date(year.value, 2, 31);
break;
case "SECONDHAFT":
dateStart.value = new Date(year.value, 3, 1);
dateEnd.value = new Date(year.value, 8, 30);
break;
default:
break;
}
typeReport.value === "1"
? fetchLeaveday(
employeeClass.value,
yearType.value,
dateStart.value,
dateEnd.value
)
: fetchLeaveday2(
employeeClass.value,
yearType.value,
dateStart.value,
dateEnd.value
);
} }
/** /**
@ -73,25 +149,57 @@ async function fetchLeaveday(
startDate: Date, startDate: Date,
endDate: Date endDate: Date
) { ) {
showLoader();
const body = { const body = {
type: year === "FULL" ? "FULL" : "HAFT", type: year === "FULL" ? "FULL" : year === "MONTH" ? "MONTH" : "HAFT",
startDate: dateToISO(startDate), startDate: dateToISO(startDate),
endDate: dateToISO(endDate), endDate: dateToISO(endDate),
nodeId: nodeId.value,
node: nodeLevel.value,
}; };
await http await http
.post(config.API.leaveReportLeaveday(type), body) .post(config.API.leaveReportLeaveday(type), body)
.then(async (res) => { .then(async (res) => {
const data = res.data.result; const data = res.data.result;
data && (await genReport(data)); data && (await fetchDocumentTemplate(data));
isReport.value = data ? true : false;
detailReport.value = data; detailReport.value = data;
}) })
.catch((err) => { .catch((err) => {
messageError($q, err); messageError($q, err);
}) })
.finally(() => { .finally(() => {
hideLoader(); isLoadPDF.value = false;
});
}
async function fetchLeaveday2(
type: string,
year: string,
startDate: Date,
endDate: Date
) {
const body = {
type: year === "FULL" ? "FULL" : year === "MONTH" ? "MONTH" : "HAFT",
startDate: dateToISO(startDate),
endDate: dateToISO(endDate),
nodeId: nodeId.value,
node: nodeLevel.value,
};
await http
.post(config.API.leaveReportLeave2(type), body)
.then(async (res) => {
const data = res.data.result;
data && (await fetchDocumentTemplate(data));
isReport.value = data ? true : false;
detailReport.value = data;
})
.catch((err) => {
messageError($q, err);
})
.finally(() => {
isLoadPDF.value = false;
}); });
} }
@ -99,7 +207,7 @@ async function fetchLeaveday(
* function เรยกไฟล PDF * function เรยกไฟล PDF
* @param data อมลบญชนลา * @param data อมลบญชนลา
*/ */
async function genReport(data: any) { async function fetchDocumentTemplate(data: any) {
await axios await axios
.post(`${config.API.reportTemplate}/xlsx`, data, { .post(`${config.API.reportTemplate}/xlsx`, data, {
headers: { headers: {
@ -111,7 +219,6 @@ async function genReport(data: any) {
.then(async (res) => { .then(async (res) => {
const blob = new Blob([res.data]); const blob = new Blob([res.data]);
const objectUrl = URL.createObjectURL(blob); const objectUrl = URL.createObjectURL(blob);
fileBlob.value = blob;
const pdfData = await usePDF(`${objectUrl}`); const pdfData = await usePDF(`${objectUrl}`);
setTimeout(() => { setTimeout(() => {
@ -129,53 +236,11 @@ async function genReport(data: any) {
}); });
} }
/**
* function เรยกไฟล XLSX
* @param data อมลบญชนลา
*/
async function genReportXLSX(data: any) {
await axios
.post(`${config.API.reportTemplate}/xlsx`, data, {
headers: {
accept:
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
"content-Type": "application/json",
},
responseType: "blob",
})
.then(async (res) => {
const blob = new Blob([res.data]);
downloadReport(blob, "xlsx");
})
.catch(async (e) => {
messageError($q, JSON.parse(await e.response.data.text()));
})
.finally(() => {
hideLoader();
});
}
/**
* function Download ไฟล
* @param data อมลบญชนลา
* @param type นามสกลไฟล
*/
async function downloadReport(data: any, type: string) {
const link = document.createElement("a");
var fileName = "บัญชีแสดงวันลา";
link.href = window.URL.createObjectURL(new Blob([data]));
link.setAttribute("download", `${fileName}.${type}`);
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
const splitterModel = ref(14); const splitterModel = ref(14);
const numOfPages = ref<number>(0); const numOfPages = ref<number>(0);
const page = ref<number>(1); const page = ref<number>(1);
const pdfSrc = ref<any>(); const pdfSrc = ref<any>();
// const modalFull = ref<boolean>(false);
const fileBlob = ref<any>();
/** ไปหน้าต่อไปของรายงาน */ /** ไปหน้าต่อไปของรายงาน */
function nextPage() { function nextPage() {
if (page.value < numOfPages.value) { if (page.value < numOfPages.value) {
@ -190,42 +255,23 @@ function backPage() {
} }
} }
/** function monthYearThai(val: DataDateMonthObject) {
* function นหาขอม Option if (val == null) return "";
* @param val คำคนหา else return monthYear2Thai(val.month, val.year);
* @param update function
* @param type ประเภท option
*/
function filterFnOptions(val: any, update: Function, type: string) {
switch (type) {
case "employeeClass":
update(() => {
employeeClassOption.value = employeeClassMain.value.filter(
(v: DataOption) => v.name.indexOf(val) > -1
);
});
break;
case "yearType":
update(() => {
yearTypeOptionOption.value = yearTypeOptionMain.value.filter(
(v: DataOption) => v.name.indexOf(val) > -1
);
});
break;
}
} }
onMounted(() => { onMounted(() => {
updateLeaveday(); fetchDataTree();
}); });
</script> </script>
<template> <template>
<div class="toptitle text-dark col-12 row items-center">ญชแสดงวนลา</div> <div class="toptitle text-dark col-12 row items-center">ญชแสดงวนลา</div>
<q-card flat bordered class="col-12 q-mt-sm q-pa-md"> <div class="q-pa-sm q-gutter-sm">
<div class="q-pa-md q-gutter-y-sm"> <q-card flat bordered class="col-12">
<q-toolbar style="padding: 0"> <div class="row q-col-gutter-sm q-pa-sm">
<div class="q-pr-xs"> <div class="row col-12">
<q-select <q-select
outlined outlined
dense dense
@ -234,48 +280,14 @@ onMounted(() => {
label="สถานภาพ" label="สถานภาพ"
emit-value emit-value
map-options map-options
hide-selected
fill-input
option-label="name" option-label="name"
option-value="id" option-value="id"
use-input
style="width: 230px" style="width: 230px"
@update:model-value="updateLeaveday" @update:model-value="updateLeaveday"
@filter="(inputValue: any, >
doneFn: Function) => filterFnOptions(inputValue, doneFn,'employeeClass')"
><template v-slot:no-option>
<q-item>
<q-item-section class="text-grey"> ไมอม </q-item-section>
</q-item>
</template>
</q-select> </q-select>
</div>
<div class="q-pr-xs"> <q-space />
<q-select
outlined
dense
v-model="yearType"
:options="yearTypeOptionOption"
emit-value
map-options
hide-selected
fill-input
option-label="name"
option-value="id"
use-input
style="width: 230px"
@update:model-value="updateLeaveday"
@filter="(inputValue: any,
doneFn: Function) => filterFnOptions(inputValue, doneFn,'yearType')"
><template v-slot:no-option>
<q-item>
<q-item-section class="text-grey"> ไมอม </q-item-section>
</q-item>
</template>
</q-select>
</div>
<q-space />
<div class="q-py-xs">
<q-btn <q-btn
flat flat
round round
@ -288,7 +300,25 @@ onMounted(() => {
<q-item <q-item
clickable clickable
v-close-popup v-close-popup
@click="downloadReport(fileBlob, 'pdf')" @click="
genReportXLSX(
detailReport,
`${
typeReport === '1'
? `รายงานการลางานตามประเภทการลา (${
employeeClass === 'officer'
? 'ข้าราชการ กทม. สามัญ'
: 'ลูกจ้างประจำ กทม.'
})`
: `รายงานการลางาน (${
employeeClass === 'officer'
? 'ข้าราชการ กทม. สามัญ'
: 'ลูกจ้างประจำ กทม.'
})`
}`,
'pdf'
)
"
> >
<q-item-section avatar <q-item-section avatar
><q-icon color="red" name="mdi-file-pdf" ><q-icon color="red" name="mdi-file-pdf"
@ -298,7 +328,24 @@ onMounted(() => {
<q-item <q-item
clickable clickable
v-close-popup v-close-popup
@click="genReportXLSX(detailReport)" @click="
genReportXLSX(
detailReport,
`${
typeReport === '1'
? `รายงานการลางานตามประเภทการลา (${
employeeClass === 'officer'
? 'ข้าราชการ กทม. สามัญ'
: 'ลูกจ้างประจำ กทม.'
})`
: `รายงานการลางาน (${
employeeClass === 'officer'
? 'ข้าราชการ กทม. สามัญ'
: 'ลูกจ้างประจำ กทม.'
})`
}`
)
"
> >
<q-item-section avatar <q-item-section avatar
><q-icon color="green" name="mdi-file-excel" ><q-icon color="green" name="mdi-file-excel"
@ -309,195 +356,357 @@ onMounted(() => {
</q-menu> </q-menu>
</q-btn> </q-btn>
</div> </div>
</q-toolbar>
<q-toolbar class="q-pa-sm bg-grey-2" style="border-radius: 5px"> <div class="row col-12">
<div class="q-pr-xs"> <q-card bordered class="col-12 filter-card q-pa-sm">
<datepicker <div class="row col-12 q-col-gutter-sm">
v-model="year" <div class="col-3">
:locale="'th'" <q-select
autoApply class="bg-white"
year-picker hide-bottom-space
:enableTimePicker="false" outlined
@update:model-value="updateLeaveday" dense
> lazy-rules
<template #year="{ year }">{{ year + 543 }}</template> borderless
<template #year-overlay-value="{ value }">{{ v-model="typeReport"
parseInt(value + 543) :label="`${'รายงาน'}`"
}}</template> emit-value
<template #trigger> map-options
<q-input :options="optionReport"
class="bg-white" option-value="id"
dense option-label="name"
outlined @update:model-value="updateLeaveday"
:model-value="Number(year) + 543" />
:label="`${'ปีงบประมาณ'}`" </div>
>
<template v-slot:prepend> <div class="col-9 row q-col-gutter-sm" v-if="typeReport">
<q-icon <div>
name="event" <q-select
class="cursor-pointer" class="bg-white"
style="color: var(--q-primary)" outlined
dense
v-model="yearType"
:options="yearTypeOptionOption"
emit-value
map-options
hide-selected
fill-input
option-label="name"
option-value="id"
use-input
@update:model-value="updateLeaveday"
> >
</q-icon> </q-select>
</template>
</q-input>
</template>
</datepicker>
</div>
<div class="q-pr-xs">
<datepicker
menu-class-name="modalfix"
v-model="dateStart"
:locale="'th'"
autoApply
:enableTimePicker="false"
week-start="0"
readonly
>
<template #year="{ year }">{{ year + 543 }}</template>
<template #year-overlay-value="{ value }">{{
parseInt(value + 543)
}}</template>
<template #trigger>
<q-input
class="bg-white"
outlined
dense
borderless
:model-value="dateStart ? date2Thai(dateStart) : null"
:label="`${'ตั้งเเต่วันที่'}`"
readonly
>
<template v-slot:prepend>
<q-icon name="event" class="cursor-pointer" color="primary">
</q-icon>
</template>
</q-input>
</template>
</datepicker>
</div>
<div class="q-pr-xs">
<datepicker
menu-class-name="modalfix"
v-model="dateEnd"
:locale="'th'"
autoApply
:enableTimePicker="false"
week-start="0"
readonly
>
<template #year="{ year }">{{ year + 543 }}</template>
<template #year-overlay-value="{ value }">{{
parseInt(value + 543)
}}</template>
<template #trigger>
<q-input
class="bg-white"
outlined
dense
borderless
:model-value="dateEnd ? date2Thai(dateEnd) : null"
:label="`${'ถึงวันที่'}`"
readonly
>
<template v-slot:prepend>
<q-icon name="event" class="cursor-pointer" color="primary">
</q-icon>
</template>
</q-input>
</template>
</datepicker>
</div>
</q-toolbar>
<q-splitter
v-model="splitterModel"
horizontal
style="
height: 70vh;
border: 1px solid rgb(210, 210, 210);
border-radius: 5px;
"
before-class="overflow-hidden disable"
separator-class="bg-white disabled"
>
<template v-slot:before>
<div class="q-px-sm">
<div class="row items-start items-center">
<div class="col">
<q-btn
padding="xs"
icon="mdi-chevron-left"
color="grey-2"
text-color="grey-5"
size="md"
class="my-auto"
@click="backPage"
:disable="page == 1"
/>
</div>
<div class="col-12 col-md-auto">
<div class="q-pa-md flex">
หนาท {{ page }} จาก {{ numOfPages }}
</div> </div>
</div>
<div class="col text-right">
<q-btn
padding="xs"
icon="mdi-chevron-right"
color="grey-2"
text-color="grey-5"
size="md"
@click="nextPage"
:disable="page === numOfPages"
/>
</div>
</div>
</div>
</template>
<template v-slot:after>
<div class="q-pa-md">
<VuePDF ref="vuePDFRef" :pdf="pdfSrc" :page="page" fit-parent />
</div>
</template>
<template v-slot:default>
<div class="q-pa-md">
<div class="row items-start items-center">
<div class="col">
<q-btn
padding="xs"
icon="mdi-chevron-left"
color="grey-2"
text-color="grey-5"
size="md"
class="my-auto"
@click="backPage"
:disable="page == 1"
/>
</div>
<div class="col-12 col-md-auto"> <div class="row q-col-gutter-sm" v-if="yearType !== 'MONTH'">
<div class="q-pa-md flex"> <datepicker
หนาท {{ page }} จาก {{ numOfPages }} v-model="year"
:locale="'th'"
autoApply
year-picker
:enableTimePicker="false"
@update:model-value="updateLeaveday"
>
<template #year="{ year }">{{ year + 543 }}</template>
<template #year-overlay-value="{ value }">{{
parseInt(value + 543)
}}</template>
<template #trigger>
<q-input
class="bg-white"
dense
outlined
:model-value="Number(year) + 543"
:label="`${'ปีงบประมาณ'}`"
>
<template v-slot:prepend>
<q-icon
name="event"
class="cursor-pointer"
style="color: var(--q-primary)"
>
</q-icon>
</template>
</q-input>
</template>
</datepicker>
<datepicker
menu-class-name="modalfix"
v-model="dateStart"
:locale="'th'"
autoApply
:enableTimePicker="false"
week-start="0"
readonly
>
<template #year="{ year }">{{ year + 543 }}</template>
<template #year-overlay-value="{ value }">{{
parseInt(value + 543)
}}</template>
<template #trigger>
<q-input
class="bg-white"
outlined
dense
borderless
:model-value="dateStart ? date2Thai(dateStart) : null"
:label="`${'ตั้งเเต่วันที่'}`"
readonly
>
<template v-slot:prepend>
<q-icon
name="event"
class="cursor-pointer"
color="primary"
>
</q-icon>
</template>
</q-input>
</template>
</datepicker>
<datepicker
menu-class-name="modalfix"
v-model="dateEnd"
:locale="'th'"
autoApply
:enableTimePicker="false"
week-start="0"
readonly
>
<template #year="{ year }">{{ year + 543 }}</template>
<template #year-overlay-value="{ value }">{{
parseInt(value + 543)
}}</template>
<template #trigger>
<q-input
class="bg-white"
outlined
dense
borderless
:model-value="dateEnd ? date2Thai(dateEnd) : null"
:label="`${'ถึงวันที่'}`"
readonly
>
<template v-slot:prepend>
<q-icon
name="event"
class="cursor-pointer"
color="primary"
>
</q-icon>
</template>
</q-input>
</template>
</datepicker>
</div>
<div class="row" v-else>
<datepicker
v-model="dateMonth"
:locale="'th'"
autoApply
month-picker
:enableTimePicker="false"
@update:model-value="updateLeaveday"
>
<template #year="{ year }">{{ year + 543 }}</template>
<template #year-overlay-value="{ value }">{{
parseInt(value + 543)
}}</template>
<template #trigger>
<q-input
class="bg-white"
outlined
dense
borderless
:label="`${'เดือน'}`"
:model-value="monthYearThai(dateMonth)"
>
<template v-slot:prepend>
<q-icon
name="event"
class="cursor-pointer"
color="primary"
></q-icon>
</template>
</q-input>
</template>
</datepicker>
</div> </div>
</div> </div>
<div class="col text-right"> </div>
<q-btn </q-card>
padding="xs" </div>
icon="mdi-chevron-right" </div>
color="grey-2" </q-card>
text-color="grey-5"
size="md" <q-card flat bordered class="col-12">
@click="nextPage" <q-card-section :horizontal="$q.screen.gt.sm">
:disable="page === numOfPages" <q-card-section class="col-lg-3 col-md-4 col-xs-12 q-gutter-sm">
<div class="col">
<q-input dense outlined v-model="filterTree" label="ค้นหา">
<template v-slot:append>
<q-icon name="search" />
</template>
</q-input>
</div>
<div class="bg-white tree-container q-pa-xs">
<q-tree
dense
:nodes="node"
node-key="orgTreeId"
label-key="labelName"
v-model:expanded="expanded"
:filter="filterTree.trim()"
no-results-label="ไม่พบข้อมูลที่ค้นหา"
no-nodes-label="ไม่มีข้อมูล"
>
<template v-slot:default-header="prop">
<q-item
@click.stop="
onSelectedNode(prop.node.orgTreeId, prop.node.orgLevel)
"
:active="nodeId === prop.node.orgTreeId"
clickable
active-class="my-list-link text-primary text-weight-medium"
class="row col-12 items-center text-dark q-py-xs q-pl-sm rounded-borders my-list"
>
<div>
<div class="text-weight-medium">
{{ prop.node.orgTreeName }}
</div>
<div class="text-weight-light text-grey-8">
{{ prop.node.orgCode == null ? null : prop.node.orgCode }}
{{
prop.node.orgTreeShortName == null
? null
: prop.node.orgTreeShortName
}}
</div>
</div>
</q-item>
</template>
</q-tree>
</div>
</q-card-section>
<q-separator :vertical="$q.screen.gt.xs" />
<q-card-section class="col-lg-9 col-md-8 col-xs-12 scroll">
<q-splitter
v-model="splitterModel"
horizontal
style="
height: 65vh;
border: 1px solid rgb(210, 210, 210);
border-radius: 5px;
"
before-class="overflow-hidden disable"
separator-class="bg-white disabled"
>
<template v-slot:before>
<div class="q-px-sm">
<div class="row items-start items-center">
<div class="col">
<q-btn
padding="xs"
icon="mdi-chevron-left"
color="grey-2"
text-color="grey-5"
size="md"
class="my-auto"
@click="backPage"
:disable="page == 1"
/>
</div>
<div class="col-12 col-md-auto">
<div class="q-pa-md flex">
หนาท {{ page }} จาก {{ numOfPages }}
</div>
</div>
<div class="col text-right">
<q-btn
padding="xs"
icon="mdi-chevron-right"
color="grey-2"
text-color="grey-5"
size="md"
@click="nextPage"
:disable="page === numOfPages"
/>
</div>
</div>
</div>
</template>
<template v-slot:after>
<div class="q-pa-md">
<LoadView v-if="isLoadPDF" />
<VuePDF
v-else
ref="vuePDFRef"
:pdf="pdfSrc"
:page="page"
fit-parent
/> />
</div> </div>
</div> </template>
</div> <template v-slot:default>
</template> <div class="q-pa-md">
</q-splitter> <div class="row items-start items-center">
</div> <div class="col">
</q-card> <q-btn
padding="xs"
icon="mdi-chevron-left"
color="grey-2"
text-color="grey-5"
size="md"
class="my-auto"
@click="backPage"
:disable="page == 1"
/>
</div>
<div class="col-12 col-md-auto">
<div class="q-pa-md flex">
หนาท {{ page }} จาก {{ numOfPages }}
</div>
</div>
<div class="col text-right">
<q-btn
padding="xs"
icon="mdi-chevron-right"
color="grey-2"
text-color="grey-5"
size="md"
@click="nextPage"
:disable="page === numOfPages"
/>
</div>
</div>
</div>
</template>
</q-splitter>
</q-card-section>
</q-card-section>
</q-card>
</div>
</template> </template>
<style scoped></style> <style scoped>
.tree-container {
overflow: auto;
height: 60vh;
border: 1px solid #e6e6e7;
border-radius: 10px;
}
.my-list-link {
color: rgb(118, 168, 222);
border-radius: 5px;
background: #a3d3fb48 !important;
font-weight: 600;
border: 1px solid rgba(175, 185, 196, 0.217);
}
</style>

View file

@ -15,7 +15,6 @@ import genReportXLSX from "@/plugins/genreportxlsx";
import type { DataStructureTree } from "@/interface/main"; import type { DataStructureTree } from "@/interface/main";
import type { import type {
DataOption, DataOption,
DataDateWeeklyObject,
DataDateMonthObject, DataDateMonthObject,
} from "@/modules/09_leave/interface/index/Main"; } from "@/modules/09_leave/interface/index/Main";
@ -161,10 +160,8 @@ async function updateFilterType(type: string) {
* @param body นเรมตนและสนส * @param body นเรมตนและสนส
*/ */
async function fetchReportTimeRecords(body: any) { async function fetchReportTimeRecords(body: any) {
console.log(body);
await http await http
.post(config.API.leaveReportTimeRecords(), body) .post(config.API.leaveReportTimeRecords(employeeClass.value), body)
.then(async (res) => { .then(async (res) => {
const data = res.data.result; const data = res.data.result;
detailReport.value = data; detailReport.value = data;
@ -185,7 +182,7 @@ async function fetchReportTimeRecords(body: any) {
*/ */
async function fetchReportTimeLate(body: any) { async function fetchReportTimeLate(body: any) {
await http await http
.post(config.API.leaveReportTimeRecords(), body) .post(config.API.leaveReportTimeLate(employeeClass.value), body)
.then(async (res) => { .then(async (res) => {
const data = res.data.result; const data = res.data.result;
detailReport.value = data; detailReport.value = data;
@ -274,9 +271,9 @@ onMounted(() => {
รายงานสถการลงเวลา รายงานสถการลงเวลา
</div> </div>
<div class="q-pa-sm"> <div class="q-pa-sm q-gutter-sm">
<q-card flat bordered class="col-12"> <q-card flat bordered class="col-12">
<div class="row q-col-gutter-sm q-pa-md"> <div class="row q-col-gutter-sm q-pa-sm">
<div class="row col-12"> <div class="row col-12">
<div class="row q-col-gutter-sm"> <div class="row q-col-gutter-sm">
<q-select <q-select
@ -340,7 +337,7 @@ onMounted(() => {
/> />
</div> </div>
<div class="q-pr-xs col-2"> <div class="q-pr-xs col-2" v-if="typeReport">
<q-select <q-select
class="bg-white" class="bg-white"
outlined outlined
@ -365,7 +362,10 @@ onMounted(() => {
</q-select> </q-select>
</div> </div>
<div class="q-pr-xs col-3" v-if="filterType === 'DAY'"> <div
class="q-pr-xs col-3"
v-if="filterType === 'DAY' && typeReport"
>
<datepicker <datepicker
menu-class-name="modalfix" menu-class-name="modalfix"
v-model="date" v-model="date"
@ -401,7 +401,10 @@ onMounted(() => {
</datepicker> </datepicker>
</div> </div>
<div class="q-pr-xs col-3" v-if="filterType === 'WEEKLY'"> <div
class="q-pr-xs col-3"
v-if="filterType === 'WEEKLY' && typeReport"
>
<datepicker <datepicker
v-model="dateWeek" v-model="dateWeek"
:locale="'th'" :locale="'th'"
@ -435,7 +438,10 @@ onMounted(() => {
</datepicker> </datepicker>
</div> </div>
<div class="q-pr-xs col-3" v-if="filterType === 'MONTH'"> <div
class="q-pr-xs col-3"
v-if="filterType === 'MONTH' && typeReport"
>
<datepicker <datepicker
v-model="dateMonth" v-model="dateMonth"
:locale="'th'" :locale="'th'"
@ -474,158 +480,154 @@ onMounted(() => {
</div> </div>
</q-card> </q-card>
<q-card class="q-mt-sm"> <q-card flat bordered class="col-12">
<div class="col-12"> <q-card-section :horizontal="$q.screen.gt.sm">
<q-card-section :horizontal="$q.screen.gt.sm"> <q-card-section class="col-lg-3 col-md-4 col-xs-12 q-gutter-sm">
<q-card-section class="col-lg-3 col-md-4 col-xs-12 q-gutter-sm"> <div class="col">
<div class="col"> <q-input dense outlined v-model="filterTree" label="ค้นหา">
<q-input dense outlined v-model="filterTree" label="ค้นหา"> <template v-slot:append>
<template v-slot:append> <q-icon name="search" />
<q-icon name="search" /> </template>
</template> </q-input>
</q-input> </div>
</div>
<div class="bg-white tree-container q-pa-xs"> <div class="bg-white tree-container q-pa-xs">
<q-tree <q-tree
dense dense
:nodes="node" :nodes="node"
node-key="orgTreeId" node-key="orgTreeId"
label-key="labelName" label-key="labelName"
v-model:expanded="expanded" v-model:expanded="expanded"
:filter="filterTree.trim()" :filter="filterTree.trim()"
no-results-label="ไม่พบข้อมูลที่ค้นหา" no-results-label="ไม่พบข้อมูลที่ค้นหา"
no-nodes-label="ไม่มีข้อมูล" no-nodes-label="ไม่มีข้อมูล"
>
<template v-slot:default-header="prop">
<q-item
@click.stop="
onSelectedNode(prop.node.orgTreeId, prop.node.orgLevel)
"
:active="nodeId === prop.node.orgTreeId"
clickable
active-class="my-list-link text-primary text-weight-medium"
class="row col-12 items-center text-dark q-py-xs q-pl-sm rounded-borders my-list"
>
<div>
<div class="text-weight-medium">
{{ prop.node.orgTreeName }}
</div>
<div class="text-weight-light text-grey-8">
{{
prop.node.orgCode == null ? null : prop.node.orgCode
}}
{{
prop.node.orgTreeShortName == null
? null
: prop.node.orgTreeShortName
}}
</div>
</div>
</q-item>
</template>
</q-tree>
</div>
</q-card-section>
<q-separator :vertical="$q.screen.gt.xs" />
<q-card-section class="col-lg-9 col-md-8 col-xs-12 scroll">
<q-splitter
v-model="splitterModel"
horizontal
style="
height: 65vh;
border: 1px solid rgb(210, 210, 210);
border-radius: 5px;
"
before-class="overflow-hidden disable"
separator-class="bg-white disabled"
> >
<template v-slot:before> <template v-slot:default-header="prop">
<div class="q-px-sm"> <q-item
<div class="row items-start items-center"> @click.stop="
<div class="col"> onSelectedNode(prop.node.orgTreeId, prop.node.orgLevel)
<q-btn "
padding="xs" :active="nodeId === prop.node.orgTreeId"
icon="mdi-chevron-left" clickable
color="grey-2" active-class="my-list-link text-primary text-weight-medium"
text-color="grey-5" class="row col-12 items-center text-dark q-py-xs q-pl-sm rounded-borders my-list"
size="md" >
class="my-auto" <div>
@click="backPage" <div class="text-weight-medium">
:disable="page == 1" {{ prop.node.orgTreeName }}
/>
</div> </div>
<div class="col-12 col-md-auto"> <div class="text-weight-light text-grey-8">
<div class="q-pa-md flex"> {{ prop.node.orgCode == null ? null : prop.node.orgCode }}
หนาท {{ page }} จาก {{ numOfPages }} {{
</div> prop.node.orgTreeShortName == null
</div> ? null
<div class="col text-right"> : prop.node.orgTreeShortName
<q-btn }}
padding="xs"
icon="mdi-chevron-right"
color="grey-2"
text-color="grey-5"
size="md"
@click="nextPage"
:disable="page === numOfPages"
/>
</div> </div>
</div> </div>
</div> </q-item>
</template> </template>
<template v-slot:after> </q-tree>
<div class="q-pa-md"> </div>
<LoadView v-if="isLoadPDF" />
<VuePDF
v-else
ref="vuePDFRef"
:pdf="pdfSrc"
:page="page"
fit-parent
/>
</div>
</template>
<template v-slot:default>
<div class="q-pa-md">
<div class="row items-start items-center">
<div class="col">
<q-btn
padding="xs"
icon="mdi-chevron-left"
color="grey-2"
text-color="grey-5"
size="md"
class="my-auto"
@click="backPage"
:disable="page == 1"
/>
</div>
<div class="col-12 col-md-auto">
<div class="q-pa-md flex">
หนาท {{ page }} จาก {{ numOfPages }}
</div>
</div>
<div class="col text-right">
<q-btn
padding="xs"
icon="mdi-chevron-right"
color="grey-2"
text-color="grey-5"
size="md"
@click="nextPage"
:disable="page === numOfPages"
/>
</div>
</div>
</div>
</template>
</q-splitter>
</q-card-section>
</q-card-section> </q-card-section>
</div> <q-separator :vertical="$q.screen.gt.xs" />
<q-card-section class="col-lg-9 col-md-8 col-xs-12 scroll">
<q-splitter
v-model="splitterModel"
horizontal
style="
height: 65vh;
border: 1px solid rgb(210, 210, 210);
border-radius: 5px;
"
before-class="overflow-hidden disable"
separator-class="bg-white disabled"
>
<template v-slot:before>
<div class="q-px-sm">
<div class="row items-start items-center">
<div class="col">
<q-btn
padding="xs"
icon="mdi-chevron-left"
color="grey-2"
text-color="grey-5"
size="md"
class="my-auto"
@click="backPage"
:disable="page == 1"
/>
</div>
<div class="col-12 col-md-auto">
<div class="q-pa-md flex">
หนาท {{ page }} จาก {{ numOfPages }}
</div>
</div>
<div class="col text-right">
<q-btn
padding="xs"
icon="mdi-chevron-right"
color="grey-2"
text-color="grey-5"
size="md"
@click="nextPage"
:disable="page === numOfPages"
/>
</div>
</div>
</div>
</template>
<template v-slot:after>
<div class="q-pa-md">
<LoadView v-if="isLoadPDF" />
<VuePDF
v-else
ref="vuePDFRef"
:pdf="pdfSrc"
:page="page"
fit-parent
/>
</div>
</template>
<template v-slot:default>
<div class="q-pa-md">
<div class="row items-start items-center">
<div class="col">
<q-btn
padding="xs"
icon="mdi-chevron-left"
color="grey-2"
text-color="grey-5"
size="md"
class="my-auto"
@click="backPage"
:disable="page == 1"
/>
</div>
<div class="col-12 col-md-auto">
<div class="q-pa-md flex">
หนาท {{ page }} จาก {{ numOfPages }}
</div>
</div>
<div class="col text-right">
<q-btn
padding="xs"
icon="mdi-chevron-right"
color="grey-2"
text-color="grey-5"
size="md"
@click="nextPage"
:disable="page === numOfPages"
/>
</div>
</div>
</div>
</template>
</q-splitter>
</q-card-section>
</q-card-section>
</q-card> </q-card>
</div> </div>
</template> </template>