feat(leave): add Processed Late tab to work-list

This commit is contained in:
DESKTOP-1R2VSQH\Lenovo ThinkPad E490 2026-04-01 11:09:33 +07:00
parent e9bcebee6d
commit 514de15f09
5 changed files with 542 additions and 3 deletions

View file

@ -61,4 +61,6 @@ export default {
leaveReportAPI: (type: string) =>
`${leave}/report/download/time-records/${type}`,
leaveTask: `${leave}/admin/leave-task/process`,
};

View file

@ -0,0 +1,491 @@
<script setup lang="ts">
import { onMounted, reactive, ref } from "vue";
import { useQuasar } from "quasar";
import http from "@/plugins/http";
import config from "@/app.config";
import { useCounterMixin } from "@/stores/mixin";
import type { QTableProps } from "quasar";
import type { FormDataProcess } from "@/modules/09_leave/interface/request/work";
import type { DataProcess } from "@/modules/09_leave/interface/response/work";
import HeaderDialog from "@/components/DialogHeader.vue";
const $q = useQuasar();
const {
dialogConfirm,
showLoader,
hideLoader,
messageError,
success,
date2Thai,
onSearchDataTable,
convertDateToAPI,
dialogRemove,
} = useCounterMixin();
/** ข้อมูลตาราง*/
const keyword = ref<string>("");
const rowsMain = ref<DataProcess[]>([]);
const rows = ref<DataProcess[]>([]);
const columns = ref<QTableProps["columns"]>([
{
name: "no",
align: "left",
label: "ลำดับ",
sortable: false,
field: "no",
headerStyle: "font-size: 14px",
style: "font-size: 14px",
},
{
name: "startDate",
align: "left",
label: "วันเริ่มต้น",
sortable: true,
field: "startDate",
headerStyle: "font-size: 14px",
style: "font-size: 14px",
format(val, row) {
return val ? date2Thai(val) : "-";
},
},
{
name: "endDate",
align: "left",
label: "วันสิ้นสุด",
sortable: true,
field: "endDate",
headerStyle: "font-size: 14px",
style: "font-size: 14px",
format(val, row) {
return val ? date2Thai(val) : "-";
},
},
{
name: "processingDate",
align: "left",
label: "วันที่ประมวลผล",
sortable: true,
field: "processingDate",
headerStyle: "font-size: 14px",
style: "font-size: 14px",
format(val, row) {
return val ? date2Thai(val, false, true) : "-";
},
},
{
name: "completeDate",
align: "left",
label: "วันที่เสร็จสิ้น",
sortable: true,
field: "completeDate",
headerStyle: "font-size: 14px",
style: "font-size: 14px",
format(val, row) {
return val ? date2Thai(val, false, true) : "-";
},
},
{
name: "status",
align: "left",
label: "สถานะ",
sortable: true,
field: "status",
headerStyle: "font-size: 14px",
style: "font-size: 14px",
format(val, row) {
return convertStatus(val);
},
},
{
name: "createdAt",
align: "left",
label: "วันที่สร้าง",
sortable: true,
field: "createdAt",
headerStyle: "font-size: 14px",
style: "font-size: 14px",
format(val, row) {
return val ? date2Thai(val, false, true) : "-";
},
},
{
name: "createdFullName",
align: "left",
label: "ผู้สร้าง",
sortable: true,
field: "createdFullName",
headerStyle: "font-size: 14px",
style: "font-size: 14px",
},
]);
const visibleColumns = ref<string[]>([
"no",
"startDate",
"endDate",
"processingDate",
"completeDate",
"status",
"createdAt",
"createdFullName",
]);
const modal = ref<boolean>(false); // dialog
const isEdit = ref<boolean>(false); //
const editId = ref<string>(""); // id
const formData = reactive<FormDataProcess>({
startDate: null,
endDate: null,
});
/** ฟังก์ชันดึงข้อมูลจาก API */
async function fetchData() {
try {
showLoader();
// API
const res = await http.get(`${config.API.leaveTask}`);
rowsMain.value = res.data.result;
serchDataTable();
} catch (error) {
messageError($q, error);
} finally {
hideLoader();
}
}
/** ฟังก์ชันค้นหาข้อมูลในตาราง */
function serchDataTable() {
rows.value = onSearchDataTable(
keyword.value,
rowsMain.value,
columns.value ? columns.value : []
);
}
/** ฟังก์ชันเปิด dialog เพื่อเพิ่มข้อมูล */
function handleOpenDialog() {
modal.value = true;
isEdit.value = false;
}
/** ฟังก์ชันปิด dialog และรีเซ็ตข้อมูล */
function handleCloseDialog() {
modal.value = false;
formData.startDate = null;
formData.endDate = null;
editId.value = "";
}
/** ฟังก์ชันส่งข้อมูล */
function onSubmit() {
dialogConfirm($q, async () => {
showLoader();
// payload API
const payload = {
startDate: convertDateToAPI(formData.startDate),
endDate: convertDateToAPI(formData.endDate),
};
// URL method API
const url = isEdit.value
? `${config.API.leaveTask}/${editId.value}`
: config.API.leaveTask;
// method API
const method = isEdit.value ? "put" : "post";
try {
await http[method](url, payload);
success($q, "บันทึกข้อมูลสำเร็จ");
fetchData();
handleCloseDialog();
} catch (error) {
messageError($q, error);
} finally {
hideLoader();
}
});
}
/** ฟังก์ชันตรวจสอบและอัปเดตวันที่สิ้นสุดให้เป็น null หากวันที่เริ่มต้นมากกว่าวันที่สิ้นสุด*/
function updateDate() {
if (
formData.startDate &&
formData.endDate &&
new Date(formData.startDate) > new Date(formData.endDate)
) {
formData.endDate = null;
}
}
/**
* งกนแกไขขอม
* @param data อมลทองการแกไข
*/
function handleEdit(data: DataProcess) {
isEdit.value = true;
editId.value = data.id;
formData.startDate = data.startDate;
formData.endDate = data.endDate;
modal.value = true;
}
/**
* งกนลบขอม
* @param id องการลบ
*/
function handleDelete(id: string) {
dialogRemove($q, async () => {
showLoader();
try {
await http.delete(`${config.API.leaveTask}/${id}`);
success($q, "ลบข้อมูลสำเร็จ");
fetchData();
} catch (error) {
messageError($q, error);
} finally {
hideLoader();
}
});
}
/**
* งกนแปลงสถานะเปนขอความ
* @param val
*/
function convertStatus(val: string) {
switch (val) {
case "PENDING":
return "รอดำเนินการ";
case "COMPLETED":
return "ดำเนินการเสร็จสิ้น";
default:
break;
}
}
onMounted(() => {
fetchData();
});
</script>
<template>
<div class="row items-center q-gutter-x-sm q-pb-sm">
<q-btn
round
dense
flat
color="primary"
icon="add"
@click="handleOpenDialog"
>
<q-tooltip>เพมขอม</q-tooltip>
</q-btn>
<q-space />
<q-input
dense
outlined
v-model="keyword"
label="ค้นหา"
@keydown.enter.prevent="serchDataTable"
>
<template v-slot:append>
<q-icon name="search" />
</template>
</q-input>
<q-select
v-model="visibleColumns"
multiple
outlined
dense
options-dense
:display-value="$q.lang.table.columns"
emit-value
map-options
:options="columns"
option-value="name"
style="min-width: 140px"
/>
</div>
<d-table
:columns="columns"
:rows="rows"
row-key="id"
flat
bordered
:paging="true"
dense
class="custom-header-table"
:visible-columns="visibleColumns"
>
<template v-slot:header="props">
<q-tr :props="props">
<q-th auto-width />
<q-th
v-for="col in props.cols"
:key="col.name"
:props="props"
style="color: #000000; font-weight: 500"
>
<span class="text-weight-medium">{{ col.label }}</span>
</q-th>
</q-tr>
</template>
<template v-slot:body="props">
<q-tr :props="props" class="cursor-pointer">
<q-td auto-width>
<q-btn
v-if="props.row.status === 'PENDING'"
dense
flat
round
color="edit"
icon="edit"
@click.stop.prevent="handleEdit(props.row)"
>
<q-tooltip>แกไข</q-tooltip>
</q-btn>
<q-btn
v-if="props.row.status === 'PENDING'"
dense
flat
round
color="red"
icon="delete"
@click.stop.prevent="handleDelete(props.row.id)"
>
<q-tooltip>ลบ</q-tooltip>
</q-btn>
</q-td>
<q-td v-for="col in props.cols" :key="col.name" :props="props">
<div v-if="col.name == 'no'">
{{ props.rowIndex + 1 }}
</div>
<div v-else>
{{ col.value ? col.value : "-" }}
</div>
</q-td>
</q-tr>
</template>
</d-table>
<q-dialog v-model="modal" persistent>
<q-card style="width: 320px">
<q-form greedy @submit.prevent @validation-success="onSubmit">
<HeaderDialog
:tittle="'ประมวลผลการขาดราชการ/มาสาย'"
:close="handleCloseDialog"
/>
<q-separator />
<q-card-section class="q-pt-none">
<div class="row q-col-gutter-md">
<div class="col-12">
<datepicker
menu-class-name="modalfix"
v-model="formData.startDate"
:locale="'th'"
autoApply
borderless
:enableTimePicker="false"
week-start="0"
@update:model-value="updateDate"
>
<template #year="{ year }">
{{ year + 543 }}
</template>
<template #year-overlay-value="{ value }">
{{ parseInt(value + 543) }}
</template>
<template #trigger>
<q-input
ref="dateRef"
outlined
dense
hide-bottom-space
:model-value="
formData.startDate != null
? date2Thai(formData.startDate)
: null
"
label="วันที่เริ่มต้น"
:rules="[
(val:string) => !!val || `${'กรุณาเลือกวันที่เริ่มต้น'}`,
]"
>
<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 class="col-12">
<datepicker
menu-class-name="modalfix"
v-model="formData.endDate"
:locale="'th'"
autoApply
borderless
:enableTimePicker="false"
week-start="0"
:min-date="formData.startDate"
>
<template #year="{ year }">
{{ year + 543 }}
</template>
<template #year-overlay-value="{ value }">
{{ parseInt(value + 543) }}
</template>
<template #trigger>
<q-input
ref="dateRef"
outlined
dense
hide-bottom-space
:model-value="
formData.endDate != null
? date2Thai(formData.endDate)
: null
"
label="วันที่สิ้นสุด"
:rules="[
(val:string) => !!val || `${'กรุณาเลือกวันที่สิ้นสุด'}`,
]"
>
<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-section>
<q-separator />
<q-card-actions align="right">
<q-btn color="secondary" label="บันทึก" type="submit" />
</q-card-actions>
</q-form>
</q-card>
</q-dialog>
</template>
<style scoped></style>

View file

@ -0,0 +1,6 @@
interface FormDataProcess {
startDate: Date | null;
endDate: Date | null;
}
export type { FormDataProcess };

View file

@ -85,4 +85,24 @@ interface FormDetail {
checkInLocationName: string;
checkOutLocationName: string;
}
export type { TableRows, DataResLog, DataResTime, TableRowsTime, FormDetail };
interface DataProcess {
id: string;
createdFullName: string;
createdAt: Date | null;
status: string;
startDate: Date | null;
endDate: Date | null;
processingDate: Date | null;
completedDate: Date | null;
errorMessage: string | null;
}
export type {
TableRows,
DataResLog,
DataResTime,
TableRowsTime,
FormDetail,
DataProcess,
};

View file

@ -1,15 +1,26 @@
<script setup lang="ts">
import { ref, toRefs } from "vue";
import { computed, ref, toRefs } from "vue";
import { useWorklistDataStore } from "@/modules/09_leave/stores/WorkStore";
import { checkPermission } from "@/utils/permissions";
import { useRoute } from "vue-router";
/** import Components */
import Tab1 from "@/modules/09_leave/components/02_WorkList/Tab1.vue";
import Tab2 from "@/modules/09_leave/components/02_WorkList/Tab2.vue";
import Tab3 from "@/modules/09_leave/components/02_WorkList/Tab3_Processed_Late.vue";
const stores = useWorklistDataStore();
const route = useRoute();
const { tabs } = toRefs(stores);
const isPermissionTab3 = computed(() => {
return (
checkPermission(route)?.attrOwnership === "OWNER" ||
(checkPermission(route)?.attrOwnership === "STAFF" &&
checkPermission(route)?.attrIsUpdate)
);
});
</script>
<template>
@ -30,6 +41,12 @@ const { tabs } = toRefs(stores);
>
<q-tab name="1" label="รายการลงเวลาที่ประมวลผลแล้ว" />
<q-tab name="2" label="รายการลงเวลา" />
<q-tab
v-if="isPermissionTab3"
name="3"
label="ประมวลผลการขาดราชการ/มาสาย"
/>
<!-- เพมแทบใหม -->
</q-tabs>
<q-separator />
@ -42,6 +59,9 @@ const { tabs } = toRefs(stores);
<q-tab-panel name="2">
<Tab2 />
</q-tab-panel>
<q-tab-panel name="3">
<Tab3 />
</q-tab-panel>
</q-tab-panels>
</div>
</q-card>