This commit is contained in:
Warunee Tamkoo 2024-08-01 12:12:28 +07:00
parent 46533bbd62
commit 15d3ac574d
128 changed files with 347 additions and 322 deletions

View file

@ -0,0 +1,482 @@
<script setup lang="ts">
import { useQuasar, type QTableProps } from "quasar";
import { ref, onMounted, reactive } from "vue";
import type {
RowList,
FormFilter,
MyObjectRef,
} from "@/modules/04_registryPerson/interface/index/other";
import http from "@/plugins/http";
import config from "@/app.config";
import { useCounterMixin } from "@/stores/mixin";
import DialogHeader from "@/components/DialogHeader.vue";
import { useRoute } from "vue-router";
import DialogHistory from "@/modules/04_registryPerson/components/detail/Other/01_OtherInformationHistory.vue";
const route = useRoute();
const $q = useQuasar();
const mixin = useCounterMixin();
const {
date2Thai,
showLoader,
hideLoader,
success,
messageError,
dialogRemove,
dialogConfirm,
} = mixin;
const id = ref<string>("");
const pagination = ref({
page: 1,
rowsPerPage: 10,
});
const profileId = ref<string>(
route.params.id ? route.params.id.toString() : ""
);
const empType = ref<string>(
route.name === "registryNewByid" ? "" : "-employee"
);
const mode = ref<string>("table");
const filterKeyword = ref<string>("");
const rows = ref<RowList[]>([]);
const currentPage = ref<number>(1);
const maxPage = ref<number>(1);
const formFilter = reactive<FormFilter>({
page: 1,
pageSize: 12,
keyword: "",
type: "",
posType: "",
posLevel: "",
retireYear: "",
rangeYear: { min: 0, max: 60 },
isShowRetire: false,
isProbation: false,
});
/** modal */
const modal = ref<boolean>(false);
const edit = ref<boolean>(false);
const modalHistory = ref<boolean>(false);
const date = ref<Date | null>(null);
const detail = ref<string>();
const dateRef = ref<object | null>(null);
const detailRef = ref<object | null>(null);
const objectRef: MyObjectRef = {
date: dateRef,
detail: detailRef,
};
const visibleColumns = ref<String[]>(["date", "detail"]);
const columns = ref<QTableProps["columns"]>([
{
name: "date",
align: "left",
label: "วันที่",
sortable: true,
field: "date",
headerStyle: "font-size: 14px",
style: "font-size: 14px; width: 50px;",
format: (v) => date2Thai(v),
},
{
name: "detail",
align: "left",
label: "รายละเอียด",
sortable: true,
field: "detail",
headerStyle: "font-size: 14px",
style: "font-size: 14px",
sort: (a: string, b: string) =>
a.localeCompare(b, undefined, { numeric: true, sensitivity: "base" }),
},
]);
/** เปิด dialog */
function openDialogAdd() {
modal.value = true;
}
/** dialog
* @param props อม row ของรายการ
*/
function openDialogEdit(props: RowList) {
modal.value = true;
edit.value = true;
id.value = props.id;
date.value = props.date;
detail.value = props.detail;
}
/** dialog
* @param id id รายการ
*/
function openDialogHistory(idOrder: string) {
id.value = idOrder;
modalHistory.value = true;
}
/** ปิด dialog */
function closeDialog() {
modal.value = false;
edit.value = false;
date.value = null;
detail.value = "";
}
/** validate check*/
function validateForm() {
dialogConfirm(
$q,
async () => {
if (edit.value) {
editData();
} else {
saveData();
}
},
"ยืนยันการบันทึกข้อมูล",
"ต้องการยืนยันการบันทึกข้อมูลนี้หรือไม่ ?"
);
}
/**
* นทกเพมขอม
*/
async function saveData() {
showLoader();
await http
.post(config.API.profileNewOther(empType.value), {
profileId: empType.value === "" ? profileId.value : undefined,
profileEmployeeId: empType.value !== "" ? profileId.value : undefined,
date: date.value,
detail: detail.value,
})
.then(() => {
success($q, "บันทึกข้อมูลสำเร็จ");
getData();
closeDialog();
})
.catch((e) => {
messageError($q, e);
})
.finally(() => {
hideLoader();
});
}
/**
* นทกแกไขขอม
*/
async function editData() {
showLoader();
await http
.patch(config.API.profileNewOtherById(id.value, empType.value), {
date: date.value,
detail: detail.value,
})
.then((res) => {
success($q, "บันทึกข้อมูลสำเร็จ");
getData();
closeDialog();
})
.catch((e) => {
messageError($q, e);
})
.finally(() => {
hideLoader();
});
}
async function getData() {
showLoader();
await http
.get(config.API.profileNewOtherByProfileId(profileId.value, empType.value))
.then((res) => {
rows.value = res.data.result;
})
.catch((e) => {
messageError($q, e);
})
.finally(() => {
hideLoader();
});
}
onMounted(() => {
getData();
});
</script>
<template>
<div class="row items-center q-gutter-x-sm q-pb-sm">
<q-btn dense color="primary" icon="add" flat round @click="openDialogAdd()"
><q-tooltip>เพมขอม</q-tooltip></q-btn
>
<q-space />
<q-input
standout
dense
v-model="filterKeyword"
ref="filterRef"
outlined
placeholder="ค้นหา"
debounce="300"
>
<template v-slot:append>
<q-icon
v-if="filterKeyword == ''"
name="search"
@click.stop.prevent="filterKeyword = ''"
class="cursor-pointer"
/>
<q-icon
v-if="filterKeyword"
name="cancel"
@click.stop.prevent="filterKeyword = ''"
class="cursor-pointer"
/>
</template>
</q-input>
<q-select
v-if="mode == 'table'"
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"
/>
<q-btn-toggle
v-model="mode"
dense
class="no-shadow toggle-borderd"
toggle-color="grey-4"
:options="[
{ value: 'table', slot: 'table' },
{ value: 'card', slot: 'card' },
]"
>
<template v-slot:table>
<q-icon
name="format_list_bulleted"
size="24px"
:style="{
color: mode === 'table' ? '#787B7C' : '#C9D3DB',
}"
/>
</template>
<template v-slot:card>
<q-icon
name="mdi-view-grid-outline"
size="24px"
:style="{
color: mode === 'card' ? '#787B7C' : '#C9D3DB',
}"
/>
</template>
</q-btn-toggle>
</div>
<d-table
:rows="rows"
:columns="columns"
dense
bordered
v-model:pagination="pagination"
flat
:card-container-class="mode === 'card' ? 'q-col-gutter-md' : ''"
:grid="mode === 'card'"
:paging="true"
:visible-columns="visibleColumns"
:filter="filterKeyword"
:rows-per-page-options="[10, 25, 50, 100]"
>
<template v-slot:header="props">
<q-tr :props="props">
<q-th auto-width></q-th>
<q-th v-for="col in props.cols" :key="col.name" :props="props">
<span class="text-weight-medium">{{ col.label }}</span>
</q-th>
</q-tr>
</template>
<template v-slot:body="props" v-if="mode === 'table'">
<q-tr :props="props" class="cursor-pointer">
<q-td auto-width>
<q-btn
flat
dense
round
class="q-mr-xs"
size="14px"
color="primary"
icon="mdi-pencil-outline"
@click="openDialogEdit(props.row)"
>
<q-tooltip>แกไขขอม</q-tooltip>
</q-btn>
<q-btn
color="info"
flat
dense
round
size="14px"
icon="mdi-history"
@click="openDialogHistory(props.row.id)"
>
<q-tooltip>ประวแกไขอนๆ</q-tooltip>
</q-btn>
</q-td>
<q-td v-for="col in props.cols" :key="col.id">
<div>
{{ col.value ? col.value : "-" }}
</div>
</q-td>
</q-tr>
</template>
<template v-slot:item="props" v-else>
<div class="col-xs-12 col-sm-6 col-md-6">
<q-card flat bordered>
<div class="row bg-grey-3">
<q-space />
<div class="q-gutter-x-sm">
<q-btn
color="primary"
icon="mdi-pencil-outline"
flat
round
@click="openDialogEdit(props.row)"
><q-tooltip>แกไข</q-tooltip></q-btn
>
<q-btn
color="info"
icon="mdi-history"
flat
round
@click="openDialogHistory(props.row.id)"
><q-tooltip>ประวแกไขอนๆ</q-tooltip></q-btn
>
</div>
</div>
<q-separator />
<q-card-section class="q-pa-none">
<q-item
v-for="(col, index) in props.cols.filter(
(col) => col.name !== 'desc'
)"
:key="col.name"
:class="index % 2 !== 0 ? 'bg-grey-1' : ''"
>
<q-item-section class="text-grey-6">
<q-item-label>{{ col.label }}</q-item-label>
</q-item-section>
<q-item-section class="text-dark">
<q-item-label>
{{ col.value ? col.value : "-" }}
</q-item-label>
</q-item-section>
</q-item>
</q-card-section>
</q-card>
</div>
</template>
</d-table>
<!-- dialog add edit -->
<q-dialog v-model="modal" persistent>
<q-card style="min-width: 70%">
<q-form greedy @submit.prevent @validation-success="validateForm">
<DialogHeader
:tittle="edit ? 'แก้ไขข้อมูลอื่นๆ' : 'เพิ่มข้อมูลอื่นๆ'"
:close="closeDialog"
/>
<q-separator />
<q-card-section style="max-height: 60vh" class="scroll">
<div class="row col-12 q-col-gutter-sm">
<div class="col-xs-6 col-sm-3 col-md-3">
<datepicker
menu-class-name="modalfix"
v-model="date"
:locale="'th'"
autoApply
: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"
class="full-width inputgreen cursor-pointer"
outlined
dense
:model-value="date2Thai(date)"
:rules="[(val) => !!val || `${'กรุณาเลือกวันที่'}`]"
hide-bottom-space
:label="`${'วันที่'}`"
>
<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-6 col-sm-9 col-md-9">
<q-input
ref="detailRef"
class="full-width inputgreen cursor-pointer"
outlined
dense
lazy-rules
v-model="detail"
:rules="[(val) => !!val || `${'กรุณากรอกรายละเอียด'}`]"
hide-bottom-space
:label="`${'รายละเอียด'}`"
/>
</div>
</div>
</q-card-section>
<q-separator />
<q-card-actions align="right">
<q-btn
dense
unelevated
label="บันทึก"
id="onSubmit"
type="submit"
color="public"
class="q-px-md"
>
<q-tooltip>นทกขอม</q-tooltip>
</q-btn>
</q-card-actions>
</q-form>
</q-card>
</q-dialog>
<DialogHistory v-model:modal="modalHistory" v-model:id="id" />
</template>
<style scoped></style>

View file

@ -0,0 +1,217 @@
<script setup lang="ts">
import { ref, watch, reactive } from "vue";
import DialogHeader from "@/components/DialogHeader.vue";
import { useCounterMixin } from "@/stores/mixin";
import { useQuasar, type QTableProps } from "quasar";
import { useRoute } from "vue-router";
import type { RowList } from "@/modules/04_registryPerson/interface/index/other";
import http from "@/plugins/http";
import config from "@/app.config";
import type {
RequestItemsObject,
FormFilter,
} from "@/modules/04_registryPerson/interface/index/discipline";
const modal = defineModel<boolean>("modal", { required: true });
const id = defineModel<string>("id", { required: true });
const $q = useQuasar();
const route = useRoute();
const mixin = useCounterMixin();
const { showLoader, hideLoader, messageError, date2Thai } = mixin;
const historyPagination = ref({
page: 1,
rowsPerPage: 10,
});
const currentPage = ref<number>(1);
const maxPage = ref<number>(1);
const filterKeyword = ref<string>("");
const rows = ref<RowList[]>([]); //select data history
const formFilter = reactive<FormFilter>({
page: 1,
pageSize: 12,
keyword: "",
type: "",
posType: "",
posLevel: "",
retireYear: "",
rangeYear: { min: 0, max: 60 },
isShowRetire: false,
isProbation: false,
});
const empType = ref<string>(
route.name === "registryNewByid" ? "" : "-employee"
);
const visibleColumns = ref<String[]>([
"date",
"detail",
"lastUpdateFullName",
"lastUpdatedAt",
]);
const columns = ref<QTableProps["columns"]>([
{
name: "date",
align: "left",
label: "วันที่",
sortable: true,
field: "date",
headerStyle: "font-size: 14px",
style: "font-size: 14px; width: 50px;",
format: (v) => date2Thai(v),
},
{
name: "detail",
align: "left",
label: "รายละเอียด",
sortable: true,
field: "detail",
headerStyle: "font-size: 14px",
style: "font-size: 14px",
sort: (a: string, b: string) =>
a.localeCompare(b, undefined, { numeric: true, sensitivity: "base" }),
},
{
name: "lastUpdateFullName",
align: "left",
label: "ผู้ดำเนินการ",
sortable: true,
field: "lastUpdateFullName",
headerStyle: "font-size: 14px",
style: "font-size: 14px",
sort: (a: string, b: string) =>
a.localeCompare(b, undefined, { numeric: true, sensitivity: "base" }),
},
{
name: "lastUpdatedAt",
align: "left",
label: "วันที่แก้ไข",
sortable: true,
field: "lastUpdatedAt",
headerStyle: "font-size: 14px",
style: "font-size: 14px",
format: (v) => date2Thai(v),
sort: (a: string, b: string) =>
a.localeCompare(b, undefined, { numeric: true, sensitivity: "base" }),
},
]);
function getHistory() {
showLoader();
http
.get(config.API.profileNewOtherHisById(id.value, empType.value))
.then((res) => {
let data = res.data.result;
rows.value = [];
data.map((e: RowList) => {
rows.value.push({
id: e.id,
date: e.date,
detail: e.detail,
lastUpdateFullName: e.lastUpdateFullName,
lastUpdatedAt: new Date(e.lastUpdatedAt),
});
});
})
.catch((e) => {
messageError($q, e);
})
.finally(() => {
hideLoader();
});
}
watch(modal, (status) => {
if (status == true) {
getHistory();
filterKeyword.value = "";
} else {
filterKeyword.value = "";
}
});
</script>
<template>
<q-dialog v-model="modal" persistent>
<q-card style="min-width: 80%">
<DialogHeader tittle="ประวัติแก้ไขอื่นๆ" :close="() => (modal = false)" />
<q-separator color="grey-4" />
<q-card-section style="max-height: 60vh" class="scroll">
<div class="row q-gutter-sm q-mb-sm">
<q-space />
<q-input
standout
dense
v-model="filterKeyword"
ref="filterRef"
outlined
placeholder="ค้นหา"
debounce="300"
>
<template v-slot:append>
<q-icon
v-if="filterKeyword == ''"
name="search"
@click.stop.prevent="filterKeyword = ''"
class="cursor-pointer"
/>
<q-icon
v-if="filterKeyword"
name="cancel"
@click.stop.prevent="filterKeyword = ''"
class="cursor-pointer"
/>
</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"
options-cover
style="min-width: 150px"
/>
</div>
<d-table
ref="table"
flat
bordered
dense
:columns="columns"
:rows="rows"
:paging="true"
:rows-per-page-options="[10, 25, 50, 100]"
:visible-columns="visibleColumns"
:filter="filterKeyword"
>
>
<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-tr>
</template>
<template v-slot:body="props">
<q-tr :props="props" class="cursor-pointer">
<q-td v-for="col in props.cols" :key="col.id">
<div>
{{ col.value ? col.value : "-" }}
</div>
</q-td>
</q-tr>
</template>
</d-table>
</q-card-section>
</q-card>
</q-dialog>
</template>

View file

@ -0,0 +1,258 @@
<script setup lang="ts">
import { ref, onMounted } from "vue";
import { useRoute } from "vue-router";
import http from "@/plugins/http";
import config from "@/app.config";
import axios from "axios";
import { useCounterMixin } from "@/stores/mixin";
import { useQuasar } from "quasar";
import type { ArrayFileList } from "@/modules/04_registryPerson/interface/index/document";
const $q = useQuasar();
const route = useRoute();
const mixin = useCounterMixin();
const {
success,
messageError,
showLoader,
hideLoader,
dialogConfirm,
dialogRemove,
} = mixin;
const documentFile = ref<any>(null);
const fileList = ref<ArrayFileList[]>([]);
const profileId = ref<string>(
route.params.id ? route.params.id.toString() : ""
);
async function getData() {
showLoader();
await http
.get(
config.API.file("ระบบทะเบียนประวัติ", "เอกสารหลักฐาน", profileId.value)
)
.then((res) => {
fileList.value = res.data;
})
.catch((e) => {
messageError($q, e);
})
.finally(() => {
hideLoader();
});
}
/**
* งกนสำหรบอพโหลดไฟลเอกสารหลกฐาน
*/
async function uploadFileDoc(uploadUrl: string, file: any) {
const Data = new FormData();
Data.append("file", documentFile.value);
showLoader();
await axios
.put(uploadUrl, file, {
headers: {
"Content-Type": file.type,
},
})
.then((res) => {
success($q, "อัปโหลดไฟล์สำเร็จ");
getData();
})
.catch((e) => {
messageError($q, e);
})
.finally(() => {
hideLoader();
documentFile.value = null;
});
}
async function clickUpload(file: any) {
const fileName = { fileName: file.name };
dialogConfirm(
$q,
async () => {
const selectedFile = file;
const formdata = new FormData();
formdata.append("file", selectedFile);
await http
.post(
config.API.file(
"ระบบทะเบียนประวัติ",
"เอกสารหลักฐาน",
profileId.value
),
{
replace: false,
fileList: fileName,
}
)
.then(async (res) => {
const foundKey: string | undefined = Object.keys(res.data).find(
(key) =>
res.data[key]?.fileName !== undefined &&
res.data[key]?.fileName !== ""
);
foundKey &&
uploadFileDoc(res.data[foundKey]?.uploadUrl, documentFile.value);
})
.catch((err) => {
messageError($q, err);
});
},
"ยืนยันการอัปโหลดไฟล์",
"ต้องการยืนยันการอัปโหลดไฟล์นี้หรือไม่ ?"
);
}
/**
* ดาวนโหลดลงคไฟล
* @param fileName file name
*/
function downloadFile(fileName: string) {
showLoader();
http
.get(
config.API.fileByFile(
"ระบบทะเบียนประวัติ",
"เอกสารหลักฐาน",
profileId.value,
fileName
)
)
.then((res) => {
const data = res.data.downloadUrl;
window.open(data, "_blank");
})
.catch((e) => {
messageError($q, e);
})
.finally(async () => {
hideLoader();
});
}
/**
* ลบไฟล
* @param fileName file name
*/
function deleteFile(fileName: string) {
dialogRemove($q, async () => {
showLoader();
http
.delete(
config.API.fileByFile(
"ระบบทะเบียนประวัติ",
"เอกสารหลักฐาน",
profileId.value,
fileName
)
)
.then((res) => {
success($q, `ลบไฟล์สำเร็จ`);
setTimeout(() => {
getData();
hideLoader();
}, 1000);
})
.catch((e) => {
messageError($q, e);
})
.finally(() => {
hideLoader();
});
});
}
onMounted(() => {
getData();
});
</script>
<template>
<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
for="inputFiles"
class="col-12"
outlined
dense
v-model="documentFile"
label="ไฟล์เอกสารหลักฐาน"
hide-bottom-space
accept=".pdf,.xlsx,.docx,.png,.jpg"
clearable
>
<template v-slot:prepend>
<q-icon name="attach_file" color="primary" />
</template>
<template v-slot:after>
<q-btn
size="14px"
v-if="documentFile"
flat
round
dense
color="add"
icon="mdi-upload"
@click="clickUpload(documentFile)"
><q-tooltip>ปโหลดไฟล</q-tooltip></q-btn
>
</template>
</q-file>
<!-- <div class="col-1 self-center" v-if="formData.documentFile"></div> -->
</div>
<div v-if="fileList.length > 0" class="col-xs-12 row">
<q-list class="full-width rounded-borders" bordered separator>
<q-item
clickable
v-ripple
v-for="data in fileList"
:key="data.id"
class="items-center"
>
<q-item-section>{{ data.fileName }}</q-item-section>
<q-space />
<div>
<q-btn
size="12px"
flat
round
dense
color="blue"
icon="mdi-download"
@click="downloadFile(data.fileName)"
><q-tooltip>ดาวนโหลดไฟล</q-tooltip></q-btn
>
<q-btn
size="12px"
flat
round
dense
color="red"
class="q-ml-sm"
icon="mdi-delete-outline"
@click="deleteFile(data.fileName)"
><q-tooltip>ลบไฟล</q-tooltip></q-btn
>
</div>
</q-item>
</q-list>
</div>
<div class="col-12" v-else>
<q-card class="q-pa-md" bordered> ไมรายการเอกสาร </q-card>
</div>
</div>
</q-card>
</template>
<style scoped></style>

View file

@ -0,0 +1,44 @@
<script setup lang="ts">
import { ref } from "vue";
/** importComponents*/
import OtherInformation from "@/modules/04_registryPerson/components/detail/Other/01_OtherInformation.vue";
import Documentipline from "@/modules/04_registryPerson/components/detail/Other/02_Document.vue";
const tab = ref<string>("1");
</script>
<template>
<div class="row items-center q-my-md">
<div class="text-dark row items-center q-px-md">
<q-icon name="mdi-account" class="q-mr-md" size="22px" />
<div class="text-subtitle1 text-weight-bold">เอกสารหลกฐานและอนๆ</div>
</div>
</div>
<q-separator />
<q-tabs
v-model="tab"
active-color="blue-8"
align="left"
bordered
narrow-indicator
indicator-color="transparent"
dense
class="text-grey q-pl-sm"
>
<q-tab name="1" label="ข้อมูลอื่นๆ" />
<q-tab name="2" label="เอกสารหลักฐาน" />
</q-tabs>
<q-separator />
<q-tab-panels v-model="tab" animated>
<q-tab-panel name="1">
<OtherInformation />
</q-tab-panel>
<q-tab-panel name="2">
<Documentipline />
</q-tab-panel>
</q-tab-panels>
</template>
<style scoped></style>