UI รายการผังบัญชีเงินเดือน

This commit is contained in:
DESKTOP-1R2VSQH\Lenovo ThinkPad E490 2024-02-15 18:01:15 +07:00
parent 7a8668271b
commit d1eab09ee4
7 changed files with 851 additions and 6 deletions

View file

@ -660,6 +660,7 @@ onMounted(async () => {
<div class="col-xs-12 col-sm-12 col-md-9 row no-wrap">
<div class="col-12 row q-pl-md q-py-md">
<div class="row col-12 q-col-gutter-md">
<div class="col-xs-12 col-sm-5" id="respondentType">
<q-select
:class="inputEdit(isReadonly)"

View file

@ -1,5 +1,479 @@
<script setup lang="ts"></script>
<script setup lang="ts">
import { computed, ref, reactive, watch } from "vue";
import { useQuasar } from "quasar";
<template>เพ/แกไขผงบญชเงนเดอน</template>
import type {
DataOption,
ObjectSalaryRef,
} from "@/modules/13_salary/interface/index/Main";
import Header from "@/components/DialogHeader.vue";
import { useCounterMixin } from "@/stores/mixin";
const $q = useQuasar();
const {
date2Thai,
dialogConfirm,
showLoader,
hideLoader,
messageError,
success,
} = useCounterMixin();
const modal = defineModel<boolean>("modal", { required: true });
const props = defineProps({
typeAction: {
type: String,
required: true,
},
data: {
type: Object,
defult: [],
},
});
const formData = reactive({
salaryType: "", //* (OFFICER->"",EMPLOYEE->"")
posType: "", //*
posLevel: "", //*
isActive: false, //*
date: null, //
startDate: null, //
endDate: null, //
detail: "", //
});
/** ตัวแปร ref สำหรับแสดง validate */
const salaryTypeRef = ref<Object | null>(null);
const posTypeRef = ref<Object | null>(null);
const posLevelRef = ref<Object | null>(null);
const dateRef = ref<Object | null>(null);
const startDateRef = ref<Object | null>(null);
const endDateRef = ref<Object | null>(null);
const ObjectRef: ObjectSalaryRef = {
salaryType: salaryTypeRef,
posTyp: posTypeRef,
posLevel: posLevelRef,
date: dateRef,
startDate: startDateRef,
endDate: endDateRef,
};
const salaryTypeOption = ref<DataOption[]>([
{ id: "OFFICER", name: "ข้าราชการกรุงเทพมหานครสามัญ" },
{ id: "EMPLOYEE", name: "ลูกจ้างประจำกรุงเทพมหานคร" },
]);
const documentFile = ref<any>(null);
const itemsDocument = ref<any>([]);
const title = computed(() => {
const name =
props.typeAction === "add"
? "เพิ่มผังบัญชีเงินเดือน"
: props.typeAction === "edit"
? "แก้ไขผังบัญชีเงินเดือน"
: "บัญชีเงินเดือน";
return name;
});
watch(
() => modal.value,
() => {
if (modal.value && props.typeAction === "edit") {
if (props.data) {
formData.salaryType = props.data.salaryType;
formData.posType = props.data.posType;
formData.posLevel = props.data.posLevel;
formData.isActive = props.data.isActive;
formData.date = props.data.date;
formData.startDate = props.data.startDate;
formData.endDate = props.data.endDate;
formData.detail = props.data.detail;
fetchDocumentFile(props.data.id);
}
}
}
);
function closeDialog() {
modal.value = !modal.value;
clearFormData();
}
function clearFormData() {
formData.salaryType = "";
formData.posType = "";
formData.posLevel = "";
formData.isActive = false;
formData.date = null;
formData.startDate = null;
formData.endDate = null;
formData.detail = "";
documentFile.value = null;
}
function fetchDocumentFile(id: string) {}
function uploadDocumentFile() {}
function onClickSubmit() {
const hasError = [];
for (const key in ObjectRef) {
if (Object.prototype.hasOwnProperty.call(ObjectRef, key)) {
const property = ObjectRef[key];
if (property.value && typeof property.value.validate === "function") {
const isValid = property.value.validate();
hasError.push(isValid);
}
}
}
if (hasError.every((result) => result === true)) {
createSalary();
}
}
function createSalary() {
dialogConfirm($q, () => {
if (props.typeAction === "add") {
success($q, "add");
} else {
success($q, "edit");
}
closeDialog();
});
}
/** function checkEndDate*/
function checkEndDate() {
if (formData.endDate !== null && formData.startDate !== null) {
if (formData.endDate < formData.startDate) {
formData.endDate = null;
}
}
}
</script>
<template>
<q-dialog v-model="modal" full-width persistent>
<q-card>
<Header :tittle="title" :close="closeDialog" />
<q-separator />
<q-card-section class="q-pt-none">
<div class="row q-gutter-sm q-pa-md">
<div class="row col-xs-12 col-md-9 q-col-gutter-sm">
<div class="col-xs-12 col-md-3">
<q-select
ref="salaryTypeRef"
dense
hide-bottom-space
outlined
option-label="name"
option-value="id"
emit-value
map-options
v-model="formData.salaryType"
:options="salaryTypeOption"
label="ประเภทผังบัญชีเงินเดือน"
:rules="[(val) => !!val || 'กรุณาเลือกประเภทผังบัญชีเงินเดือน']"
lazy-rules
/>
</div>
<div class="col-xs-12 col-md-3">
<q-select
ref="posTypeRef"
dense
hide-bottom-space
outlined
option-label="name"
option-value="id"
emit-value
map-options
v-model="formData.posType"
:options="salaryTypeOption"
label="ประเภทตำแหน่ง/กลุ่ม"
:rules="[(val) => !!val || 'กรุณาเลือกประเภทตำแหน่ง/กลุ่ม']"
lazy-rules
/>
</div>
<div class="col-xs-12 col-md-3">
<q-select
ref="posLevelRef"
dense
hide-bottom-space
outlined
option-label="name"
option-value="id"
emit-value
map-options
v-model="formData.posLevel"
:options="salaryTypeOption"
label="ระดับ"
:rules="[(val) => !!val || 'กรุณาเลือกระดับ']"
lazy-rules
/>
</div>
<div
class="col-xs-12 col-md-3"
style="display: flex; justify-content: flex-end"
>
<q-toggle
color="primary"
label="สถานะการใช้งาน"
v-model="formData.isActive"
/>
</div>
<div class="col-xs-12 col-md-4">
<datepicker
menu-class-name="modalfix"
v-model="formData.date"
:locale="'th'"
autoApply
borderless
:enableTimePicker="false"
week-start="0"
>
<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.date != null ? date2Thai(formData.date) : null
"
label="ให้ไว ณ วันที่"
:rules="[
(val) => !!val || `${'กรุณาเลือกวันที่ ให้ไว ณ วันที่'}`,
]"
lazy-rules
>
<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-xs-12 col-md-4">
<datepicker
menu-class-name="modalfix"
v-model="formData.startDate"
:locale="'th'"
autoApply
borderless
:enableTimePicker="false"
week-start="0"
@update:model-value="checkEndDate"
>
<template #year="{ year }">
{{ year + 543 }}
</template>
<template #year-overlay-value="{ value }">
{{ parseInt(value + 543) }}
</template>
<template #trigger>
<q-input
ref="startDateRef"
outlined
dense
hide-bottom-space
:model-value="
formData.startDate != null
? date2Thai(formData.startDate)
: null
"
label="วันที่มีผลบังคับใช้"
:rules="[
(val) => !!val || `${'กรุณาเลือกวันที่มีผลบังคับใช้'}`,
]"
lazy-rules
>
<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-xs-12 col-md-4">
<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="endDateRef"
outlined
dense
hide-bottom-space
:model-value="
formData.endDate != null
? date2Thai(formData.endDate)
: null
"
label="วันที่สิ้นสุดบังคับใช้"
:rules="[
(val) => !!val || `${'กรุณาเลือกวันที่สิ้นสุดบังคับใช้'}`,
]"
lazy-rules
>
<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">
<q-input
v-model="formData.detail"
outlined
dense
type="textarea"
label="คำอธิบาย"
/>
</div>
</div>
<div class="col-xs-12 col-md-3">
<q-card
bordered
class="row col-12"
style="border: 1px solid #d6dee1"
>
<div class="col-12 text-weight-medium bg-grey-1 q-py-sm q-px-md">
ปโหลดเอกสารอางอ
</div>
<div class="col-12"><q-separator /></div>
<div class="row col-12 q-col-gutter-y-sm q-pa-sm">
<div class="col-12 row">
<q-file
v-if="props.typeAction === 'edit'"
class="col-12"
outlined
dense
v-model="documentFile"
label="เอกสารอ้างอิง"
hide-bottom-space
>
<template v-slot:prepend>
<q-icon name="attach_file" color="primary" />
</template>
<template v-slot:after>
<q-btn
v-if="documentFile"
size="14px"
flat
round
dense
color="add"
icon="mdi-upload"
@click="uploadDocumentFile"
><q-tooltip>ปโหลดเอกสาร</q-tooltip></q-btn
>
</template>
</q-file>
</div>
<div v-if="itemsDocument.length > 0" class="col-xs-12 row">
<q-list class="full-width rounded-borders" bordered separator>
<q-item
v-for="file in itemsDocument"
:key="file.id"
clickable
v-ripple
>
<q-item-section>{{ file.fileName }}</q-item-section>
<q-item-section avatar>
<div class="row">
<div>
<q-btn
dense
flat
round
size="12px"
color="blue"
icon="mdi-download-outline"
>
<q-tooltip>ดาวนโหลดไฟล</q-tooltip>
</q-btn>
</div>
<div>
<q-btn
dense
flat
round
size="12px"
color="red"
icon="mdi-delete-outline"
><q-tooltip>ลบไฟล</q-tooltip></q-btn
>
</div>
</div>
</q-item-section>
</q-item>
</q-list>
</div>
<div class="col-12" v-else>
<q-card class="q-pa-md" bordered> ไมรายการเอกสาร </q-card>
</div>
</div>
</q-card>
</div>
</div>
</q-card-section>
<q-separator />
<q-card-actions align="right" class="bg-white text-teal">
<q-btn label="บันทึก" color="secondary" @click="onClickSubmit"
><q-tooltip>นทกขอม</q-tooltip></q-btn
>
</q-card-actions>
</q-card>
</q-dialog>
</template>
<style lang="scss" scoped></style>

View file

@ -3,4 +3,28 @@ interface DataOption {
name: string;
}
export type { DataOption };
interface NewPagination {
descending: boolean;
page: number;
rowsPerPage: number;
sortBy: string;
}
interface ItemsMenu {
label: string;
icon: string;
color: string;
type: string;
}
interface ObjectSalaryRef {
salaryType: object | null;
posTyp: object | null;
posLevel: object | null;
date: object | null;
startDate: object | null;
endDate: object | null;
[key: string]: any;
}
export type { DataOption, NewPagination, ItemsMenu, ObjectSalaryRef };

View file

@ -0,0 +1,7 @@
interface FormQuerySalary {
page: number; //*หน้า
pageSize: number; //*จำนวนแถวต่อหน้า
keyword: string; //keyword ค้นหา
}
export type { FormQuerySalary };

View file

@ -2,5 +2,16 @@ interface DataOption {
id: string;
name: string;
}
interface Salary {
id: string;
salaryType: string;
posType: string;
posLevel: string;
isActive: boolean;
date: Date; //ให้ไว้ ณ วันที่
startDate: Date;
endDate: Date;
detail: string;
}
export type { DataOption };
export type { Salary };

View file

@ -13,7 +13,7 @@ export default [
},
},
{
path: "/salary/rate",
path: "/salary/rate/:id",
name: "salaryRate",
component: salaryRate,
meta: {

View file

@ -1,12 +1,340 @@
<script setup lang="ts">
import { ref, onMounted, reactive } from "vue";
import { useQuasar } from "quasar";
import { useRouter } from "vue-router";
/** importType*/
import type { QTableProps } from "quasar";
import type {
NewPagination,
ItemsMenu,
} from "@/modules/13_salary/interface/index/Main";
import type { Salary } from "@/modules/13_salary/interface/response/Main";
import type { FormQuerySalary } from "@/modules/13_salary/interface/request/Main";
import DialogFormMain from "@/modules/13_salary/components/SalaryChart/DialogFormMain.vue";
/** importStore*/
import { useCounterMixin } from "@/stores/mixin";
/** use*/
const $q = useQuasar();
const router = useRouter();
const { date2Thai, dialogRemove } = useCounterMixin();
/** modalDialog*/
const modalDialogFormMain = ref<boolean>(false);
/** Table*/
const rows = ref<Salary[]>([]);
const columns = ref<QTableProps["columns"]>([
{
name: "salaryType",
align: "left",
label: "ประเภทผัง",
sortable: false,
field: "salaryType",
headerStyle: "font-size: 14px",
style: "font-size: 14px",
},
{
name: "posType",
align: "left",
label: "เลขที่ตำแหน่ง",
sortable: true,
field: "posType",
headerStyle: "font-size: 14px",
style: "font-size: 14px",
},
{
name: "posLevel",
align: "left",
label: "ระดับ",
field: "posLevel",
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",
},
{
name: "isActive",
align: "left",
label: "สถานะการใช้งาน",
sortable: true,
field: "isActive",
headerStyle: "font-size: 14px",
style: "font-size: 14px",
},
]);
const visibleColumns = ref<string[]>([
"salaryType",
"posType",
"posLevel",
"startDate",
"isActive",
]);
/** List Mune*/
const itemMenu = ref<ItemsMenu[]>([
{
label: "แก้ไข",
icon: "edit",
color: "edit",
type: "edit",
},
{
label: "อัตราเงินเดือน",
icon: "mdi-format-list-bulleted-triangle",
color: "secondary",
type: "salaryRate",
},
{
label: "คัดลอก",
icon: "content_copy",
color: "blue-6",
type: "coppy",
},
{
label: "ลบ",
icon: "delete",
color: "red",
type: "delete",
},
]);
/** queryString*/
const formQuery = reactive<FormQuerySalary>({
page: 1, //*
pageSize: 10, //*
keyword: "", //keyword
});
const totalRow = ref<number>(1);
/**
* function updatePagination
* @param newPagination อม Pagination ใหม
*/
function updatePagination(newPagination: NewPagination) {
// reqMaster.value.pageSize = newPagination.rowsPerPage;
// reqMaster.value.page = 1;
}
async function fetchListSalaly() {
const data = [
{
id: "1",
salaryType: "OFFICER", // (OFFICER->"",EMPLOYEE->"")
posType: "ทั่วไป", //
posLevel: "ชำนาญการ", //
isActive: true, //
date: new Date(), //
startDate: new Date(), //
endDate: new Date(), //
detail: "", //
},
{
id: "2",
salaryType: "EMPLOYEE", // (OFFICER->"",EMPLOYEE->"")
posType: "ทั่วไป", //
posLevel: "ชำนาญการ", //
isActive: false, //
date: new Date(), //
startDate: new Date(), //
endDate: new Date(), //
detail: "", //
},
];
rows.value = data;
}
const typeAction = ref<string>("");
const dataRow = ref<Salary>();
async function onClickSalary(type: string, data: Salary | null) {
modalDialogFormMain.value = !modalDialogFormMain.value;
typeAction.value = type;
if (data) {
dataRow.value = data;
}
}
async function onClickSalaryRate(id: string) {
router.push(`/salary/rate/${id}`);
}
async function onClickCoppy() {}
async function onClickDelete() {
dialogRemove($q, () => {});
}
onMounted(async () => {
await fetchListSalaly();
});
</script>
<template>หนารายการผงบญชเงนเดอน</template>
<template>
<div class="row items-center">
<div class="toptitle text-dark row items-center q-py-xs">
รายการผงบญชเงนเดอน
</div>
</div>
<q-card flat bordered class="q-pa-md">
<q-toolbar class="text-primary q-gutter-sm" style="padding: 0px">
<q-btn flat round dense icon="add" @click="onClickSalary('add', null)">
<q-tooltip>เพมผงบญชเงนเดอน </q-tooltip>
</q-btn>
<q-space />
<q-input
borderless
dense
debounce="300"
outlined
v-model="formQuery.keyword"
placeholder="ค้นหา"
>
<template v-slot:append>
<q-icon name="search" />
</template>
</q-input>
<q-select
for="#select"
v-model="visibleColumns"
multiple
outlined
dense
options-dense
:display-value="$q.lang.table.columns"
emit-value
map-options
:options="columns"
option-value="name"
options-cover
style="min-width: 150px"
class="col-xs-12 col-sm-3 col-md-2"
/>
</q-toolbar>
<div class="col-12 q-pa-sm">
<d-table
ref="table"
:columns="columns"
:rows="rows"
row-key="id"
flat
bordered
:paging="true"
dense
:rows-per-page-options="[10, 25, 50, 100]"
@update:pagination="updatePagination"
: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">
<span class="text-weight-medium">{{ col.label }}</span>
</q-th>
<q-th auto-width></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 === 'isActive'">
<q-icon
:name="col.value ? 'done' : 'close'"
:color="col.value ? 'primary' : 'grey'"
size="24px"
/>
</div>
<div v-else-if="col.name === 'salaryType'">
{{
col.value === "OFFICER"
? "ข้าราชการกรุงเทพมหานครสามัญ"
: "ลูกจ้างประจำกรุงเทพมหานคร"
}}
</div>
<div v-else-if="col.name === 'startDate'">
{{ col.value ? date2Thai(col.value) : "-" }}
</div>
<div v-else>
{{ col.value ? col.value : "-" }}
</div>
</q-td>
<q-td>
<q-btn
flat
dense
icon="mdi-dots-vertical"
class="q-pa-none q-ml-xs"
color="grey-13"
size="12px"
>
<q-menu>
<q-list dense style="min-width: 150px">
<q-item
v-for="(item, index) in itemMenu"
:key="index"
clickable
v-close-popup
@click.stop="
item.type === 'edit'
? onClickSalary('edit', props.row)
: item.type === 'salaryRate'
? onClickSalaryRate(props.row.id)
: item.type === 'coppy'
? onClickCoppy()
: item.type === 'delete'
? onClickDelete()
: null
"
>
<q-item-section>
<div class="row items-center">
<q-icon
:color="item.color"
size="17px"
:name="item.icon"
/>
<div class="q-pl-md">{{ item.label }}</div>
</div>
</q-item-section>
</q-item>
</q-list>
</q-menu>
</q-btn>
</q-td>
</q-tr>
</template>
<template v-slot:pagination="scope">
<q-pagination
v-model="formQuery.page"
active-color="primary"
color="dark"
:max="totalRow"
size="sm"
boundary-links
direction-links
></q-pagination>
</template>
</d-table>
</div>
</q-card>
<DialogFormMain
v-model:modal="modalDialogFormMain"
:typeAction="typeAction"
:data="dataRow"
/>
</template>
<style scoped></style>