เพิ่ม ฟิลเตอร์หน่วยงาน

This commit is contained in:
setthawutttty 2024-10-15 14:57:32 +07:00
parent a8f37ef058
commit 0f648e397e
3 changed files with 520 additions and 336 deletions

View file

@ -1,14 +1,19 @@
<script setup lang="ts"> <script setup lang="ts">
import { cloneVNode, onMounted, ref } from "vue"; import { cloneVNode, onMounted, ref, reactive } from "vue";
import type { QTableProps } from "quasar"; import { useQuasar, type QTableProps } from "quasar";
import { useRoute } from "vue-router";
import http from "@/plugins/http";
import config from "@/app.config";
import { useCounterMixin } from "@/stores/mixin"; import { useCounterMixin } from "@/stores/mixin";
import { useDataStore } from "@/modules/03_logs/stores/main"; import { useDataStore } from "@/modules/03_logs/stores/main";
import { storeToRefs } from "pinia"; import { storeToRefs } from "pinia";
import { useRoute } from "vue-router";
import type { import type {
Pagination, Pagination,
ItemsDropdown, ItemsDropdown,
DataTree,
QueryProfile,
} from "@/modules/03_logs/interface/index/Main"; } from "@/modules/03_logs/interface/index/Main";
import DialogDataDiff from "@/modules/03_logs/components/DialogDataDiff.vue"; import DialogDataDiff from "@/modules/03_logs/components/DialogDataDiff.vue";
@ -18,9 +23,10 @@ const route = useRoute();
import type { ResLog } from "@/modules/03_logs/interface/response/Main"; import type { ResLog } from "@/modules/03_logs/interface/response/Main";
/** use*/ /** use*/
const $q = useQuasar();
const storeData = useDataStore(); const storeData = useDataStore();
const { logData, size, searchAfter, systemName } = storeToRefs(storeData); const { logData, size, searchAfter, systemName } = storeToRefs(storeData);
const { date2Thai } = useCounterMixin(); const { date2Thai, messageError, hideLoader } = useCounterMixin();
const startTime = ref<Date | null>(null); // const startTime = ref<Date | null>(null); //
const endTime = ref<Date | null>(null); // const endTime = ref<Date | null>(null); //
@ -182,6 +188,66 @@ const itemsDropdown = ref<ItemsDropdown[]>([
]); ]);
const statusOpt = ref<string[]>(["info", "warning", "error"]); const statusOpt = ref<string[]>(["info", "warning", "error"]);
/******* โครงสร้าง *******/
const filter = ref<string>(""); //
const nodeTree = ref<DataTree[]>([]); //
const expanded = ref<Array<string>>([]); //
const orgId = ref<string>(""); // id
const qureyBody = reactive<QueryProfile>({
searchKeyword: "", //
searchField: "fullName", // field
page: 1, //
pageSize: 10, //
id: null, //
});
/**
* งกนเลอกหนวยงาน
* @param id หนวยงานทเลอก
*
* กำหนดคาของ qureyBody ใหเปนค defult และกำหนดคาของ qureyBody.id เปนหนวยงานทเลอก
* และดงขอมลรายชอคนททธดการโครงสรางในหนวยงานทเลอก
*/
async function selectedOrg(id: string) {
orgId.value = id;
qureyBody.id = id;
qureyBody.searchKeyword = "";
qureyBody.searchField = "fullName";
qureyBody.page = 1;
await storeData.fetchLog(
{
rootId: qureyBody.id ?? undefined,
size: size.value ?? undefined,
search: inputSearch.value ?? undefined,
systemName: systemName.value ?? undefined,
searchAfter: searchAfter.value ?? undefined,
sort: sortTime.value,
startDate: new Date(startDate.value) ?? undefined,
endDate: new Date(endDate.value) ?? undefined,
},
true
);
}
/**
* งกนดงขอมลโครงสราง
*
* เกบขอมลโครงสรางไวใน nodeTree
*/
async function fatchOrg() {
await http
.get(config.API.permissionOrg)
.then(async (res) => {
const data = await res.data.result;
nodeTree.value = data;
})
.catch((err) => {
messageError($q, err);
hideLoader();
})
.finally(() => {});
}
/** /**
* scroll และโหลดขอมลรายการ Logs เพ * scroll และโหลดขอมลรายการ Logs เพ
@ -341,6 +407,7 @@ function updateDate() {
} }
onMounted(() => { onMounted(() => {
fatchOrg();
systemName.value = route.query.system as string; systemName.value = route.query.system as string;
const date = new Date(); const date = new Date();
date.setHours(0, 0, 0, 0); // 00:00:00.000 date.setHours(0, 0, 0, 0); // 00:00:00.000
@ -352,350 +419,421 @@ onMounted(() => {
<template> <template>
<div class="bg-white q-pa-md"> <div class="bg-white q-pa-md">
<div class="row no-wrap"> <div class="row q-col-gutter-sm">
<div class="row col-7 q-col-gutter-md"> <div class="col-4">
<div> <div class="q-mb-sm">
<q-btn-dropdown outline color="grey-5"> <q-input dense outlined v-model="filter" label="ค้นหา">
<template v-slot:label> <template v-slot:append>
<div class="row items-center no-wrap"> <q-icon
<div class="text-black">{{ labelDropdown }}</div> v-if="filter !== ''"
</div> name="clear"
class="cursor-pointer"
@click="filter = ''"
/>
<q-icon v-else name="search" color="grey-5" />
</template> </template>
<q-list> </q-input>
</div>
<div class="bg-white tree-container q-pa-xs">
<q-tree
class="q-pa-sm q-gutter-sm"
dense
:nodes="nodeTree"
node-key="orgRootName"
label-key="orgRootName"
:filter="filter"
no-results-label="ไม่พบข้อมูลที่ค้นหา"
no-nodes-label="ไม่มีข้อมูล"
v-model:expanded="expanded"
>
<template v-slot:default-header="prop">
<q-item <q-item
dense
v-for="(item, index) in itemsDropdown"
clickable clickable
v-close-popup class="row col-12 items-center text-dark q-py-xs q-pl-sm rounded-borders my-list"
@click="onItemClick(item.labal, item.val)" :active="orgId == prop.node.id"
active-class="my-list-link text-primary text-weight-medium"
@click.stop="selectedOrg(prop.node.id)"
> >
<q-item-section> <div>
<q-item-label>{{ item.labal }}</q-item-label> <div class="text-weight-medium">
</q-item-section> {{ prop.node.orgRootName }}
</div>
<div class="text-weight-light text-grey-8">
{{
prop.node.orgRootCode == null
? null
: prop.node.orgRootCode
}}
{{
prop.node.orgRootShortName == null
? null
: prop.node.orgRootShortName
}}
</div>
</div>
</q-item> </q-item>
</q-list> </template>
</q-btn-dropdown> </q-tree>
</div> </div>
</div> </div>
<div class="col-8">
<q-space /> <div class="row no-wrap">
<div <div class="row col-7 q-col-gutter-md">
class="items-center justify-end q-gutter-md col-5" <div>
style="display: flex" <q-btn-dropdown outline color="grey-5">
> <template v-slot:label>
<!-- นหาขอความใน table --> <div class="row items-center no-wrap">
<q-input <div class="text-black">{{ labelDropdown }}</div>
standout
dense
clearable
v-model="inputSearch"
ref="filterRef"
@update:model-value="
storeData.fetchLog({
size: size ?? undefined,
search: inputSearch ?? undefined,
systemName: systemName ?? undefined,
sort: sortTime,
// date: new Date(startDate),
startDate: new Date(startDate),
endDate: new Date(endDate),
})
"
outlined
debounce="500"
placeholder="ค้นหา"
style="max-width: 200px"
class="q-ml-sm"
>
<template v-slot:append>
<q-icon name="search" />
</template>
</q-input>
<q-select
v-model="searchStatus"
label="ค้นหาสถานะ"
dense
emit-value
map-options
:options="statusOpt"
option-value="id"
option-label="name"
lazy-rules
hide-bottom-space
borderless
outlined
style="width: 150px"
:hide-dropdown-icon="false"
@update:modelValue="selectedDate()"
clearable
>
<template v-slot:selected-item="scope">
<q-chip
dense
:tabindex="scope.tabindex"
:color="
scope.opt === 'info'
? 'primary'
: scope.opt === 'warning'
? 'warning'
: scope.opt === 'error'
? 'red'
: 'white'
"
text-color="white"
class="q-ma-none text-caption"
style="border-radius: 3px; max-width: 60vw; word-wrap: break-word"
>
{{ scope.opt }}
</q-chip>
</template>
</q-select>
<!-- แสดงคอลมนใน table -->
<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>
</div>
<div
v-if="valDropdown === 'customized'"
class="row q-col-gutter-md q-mt-xs"
>
<div class="col-3">
<datepicker
v-model="startTime"
:locale="'th'"
:max-date="endTime"
selectText="เลือก"
cancelText="ยกเลิก"
@update:modelValue="updateDate()"
>
<template #year="{ year }">{{ year + 543 }}</template>
<template #year-overlay-value="{ value }">{{
parseInt(value + 543)
}}</template>
<template #trigger>
<q-input
dense
outlined
:model-value="
startTime ? date2Thai(startTime, false, true) : null
"
:label="`${'วันเริ่มต้น'}`"
hide-bottom-space
>
<template v-slot:prepend>
<q-icon
name="event"
class="cursor-pointer"
style="color: var(--q-primary)"
>
</q-icon>
</template>
</q-input>
</template>
<template #action-preview="{ value }">
{{ date2Thai(value, false, true) }}
</template>
</datepicker>
</div>
<div class="col-3">
<datepicker
v-model="endTime"
:locale="'th'"
:enableTimePicker="true"
selectText="เลือก"
cancelText="ยกเลิก"
@update:modelValue="updateDate()"
:min-date="startTime"
>
<template #year="{ year }">{{ year + 543 }}</template>
<template #year-overlay-value="{ value }">{{
parseInt(value + 543)
}}</template>
<template #trigger>
<q-input
dense
outlined
:model-value="endTime ? date2Thai(endTime, false, true) : null"
:label="`${'วันสิ้นสุด'}`"
hide-bottom-space
>
<template v-slot:prepend>
<q-icon
name="event"
class="cursor-pointer"
style="color: var(--q-primary)"
>
</q-icon>
</template>
</q-input>
</template>
<template #action-preview="{ value }">
{{ date2Thai(value, false, true) }}
</template>
</datepicker>
</div>
</div>
<div
class="q-mt-md"
style="max-height: 70vh; overflow: scroll"
@scroll="infiniteScroll"
>
<d-table
ref="table"
:columns="columns"
:rows="storeData.logData"
row-key="name"
flat
bordered
hide-pagination
v-model:pagination="pagination"
dense
:rows-per-page-options="[0]"
class="custom-header-table"
: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">
<div
v-if="col.name === 'startTimeStamp'"
class="text-weight-medium"
>
{{ col.label }}
<q-btn
v-if="sortTime === 'desc'"
flat
round
size="8px"
icon="mdi-arrow-up"
@click="
() => {
sortTime = 'asc';
selectedDate();
}
"
/>
<q-btn
v-else
flat
round
size="8px"
icon="mdi-arrow-down"
@click="
() => {
sortTime = 'desc';
selectedDate();
}
"
/>
</div>
<span v-else class="text-weight-medium">{{ col.label }}</span>
</q-th>
</q-tr>
</template>
<template v-slot:body="props">
<q-tr :props="props" class="cursor-pointer" style="overflow: scroll">
<q-td v-for="col in props.cols" :key="col.id">
<div v-if="col.name === 'username'">
<template v-if="props.row.userName && props.row.user">
{{ props.row.userName ?? "-" }}
<div class="text-grey" style="font-size: 65%">
{{ props.row.user ?? "-" }}
</div> </div>
</template> </template>
<template v-else> - </template> <q-list>
</div> <q-item
<div v-else-if="col.name === 'method'"> dense
<q-badge v-for="(item, index) in itemsDropdown"
:text-color="classColorMethod(col.value as string)" clickable
style="background-color: #f0ecec" v-close-popup
>{{ col.value ?? "-" }}</q-badge @click="onItemClick(item.labal, item.val)"
> >
</div> <q-item-section>
<div v-else-if="col.name === 'logType'"> <q-item-label>{{ item.labal }}</q-item-label>
<q-badge </q-item-section>
text-color="white" </q-item>
:color=" </q-list>
col.value === 'error' </q-btn-dropdown>
? 'red' </div>
: col.value === 'info' </div>
? 'primary'
: 'warning'
"
>{{ col.value ?? "-" }}</q-badge
>
</div>
<div
v-else-if="col.name === 'responseDescription'"
:class="`text-${
props.row.logType === 'Error'
? 'red'
: props.row.logType === 'info'
? 'primary'
: 'warning'
}`"
class="ellipsis"
style="max-width: 10vw"
>
{{ col.value ?? "-" }}
<q-tooltip> {{ col.value }} </q-tooltip>
</div>
<div v-else-if="col.name === 'dataDiff'"> <q-space />
<q-btn <div
class="items-center justify-end q-gutter-md col-5"
style="display: flex"
>
<!-- นหาขอความใน table -->
<q-input
standout
dense
clearable
v-model="inputSearch"
ref="filterRef"
@update:model-value="
storeData.fetchLog({
size: size ?? undefined,
search: inputSearch ?? undefined,
systemName: systemName ?? undefined,
sort: sortTime,
// date: new Date(startDate),
startDate: new Date(startDate),
endDate: new Date(endDate),
})
"
outlined
debounce="500"
placeholder="ค้นหา"
style="max-width: 200px"
class="q-ml-sm"
>
<template v-slot:append>
<q-icon name="search" />
</template>
</q-input>
<q-select
v-model="searchStatus"
label="ค้นหาสถานะ"
dense
emit-value
map-options
:options="statusOpt"
option-value="id"
option-label="name"
lazy-rules
hide-bottom-space
borderless
outlined
style="width: 150px"
:hide-dropdown-icon="false"
@update:modelValue="selectedDate()"
clearable
>
<template v-slot:selected-item="scope">
<q-chip
dense dense
flat :tabindex="scope.tabindex"
round :color="
icon="mdi-eye" scope.opt === 'info'
color="info" ? 'primary'
@click.stop=" : scope.opt === 'warning'
() => { ? 'warning'
currentDataDiff = col.value; : scope.opt === 'error'
currentlogData = { ? 'red'
startTimeStamp: props.row.startTimeStamp, : 'white'
username: props.row.userName, "
host: props.row.host, text-color="white"
endpoint: props.row.endpoint, class="q-ma-none text-caption"
method: props.row.method, style="
responseCode: props.row.responseCode, border-radius: 3px;
logType: props.row.logType, max-width: 60vw;
responseDescription: props.row.responseDescription, word-wrap: break-word;
sequence: props.row.sequence,
dataDiff: props.row.dataDiff,
input: props.row.input,
output: props.row.output,
};
openDialog = true;
}
" "
> >
<q-tooltip>รายละเอยด </q-tooltip> {{ scope.opt }}
</q-btn> </q-chip>
</div> </template>
</q-select>
<!-- แสดงคอลมนใน table -->
<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>
</div>
<div
v-if="valDropdown === 'customized'"
class="row q-col-gutter-md q-mt-xs"
>
<div class="col-3">
<datepicker
v-model="startTime"
:locale="'th'"
:max-date="endTime"
selectText="เลือก"
cancelText="ยกเลิก"
@update:modelValue="updateDate()"
>
<template #year="{ year }">{{ year + 543 }}</template>
<template #year-overlay-value="{ value }">{{
parseInt(value + 543)
}}</template>
<template #trigger>
<q-input
dense
outlined
:model-value="
startTime ? date2Thai(startTime, false, true) : null
"
:label="`${'วันเริ่มต้น'}`"
hide-bottom-space
>
<template v-slot:prepend>
<q-icon
name="event"
class="cursor-pointer"
style="color: var(--q-primary)"
>
</q-icon>
</template>
</q-input>
</template>
<div v-else class="ellipsis" style="max-width: 10vw"> <template #action-preview="{ value }">
{{ col.value === "" || col.value === null ? "-" : col.value }} {{ date2Thai(value, false, true) }}
<q-tooltip> {{ col.value }} </q-tooltip> </template>
</div> </datepicker>
</q-td> </div>
</q-tr> <div class="col-3">
</template> <datepicker
</d-table> v-model="endTime"
:locale="'th'"
:enableTimePicker="true"
selectText="เลือก"
cancelText="ยกเลิก"
@update:modelValue="updateDate()"
:min-date="startTime"
>
<template #year="{ year }">{{ year + 543 }}</template>
<template #year-overlay-value="{ value }">{{
parseInt(value + 543)
}}</template>
<template #trigger>
<q-input
dense
outlined
:model-value="
endTime ? date2Thai(endTime, false, true) : null
"
:label="`${'วันสิ้นสุด'}`"
hide-bottom-space
>
<template v-slot:prepend>
<q-icon
name="event"
class="cursor-pointer"
style="color: var(--q-primary)"
>
</q-icon>
</template>
</q-input>
</template>
<template #action-preview="{ value }">
{{ date2Thai(value, false, true) }}
</template>
</datepicker>
</div>
</div>
<div
class="q-mt-md"
style="max-height: 70vh; overflow: scroll"
@scroll="infiniteScroll"
>
<d-table
ref="table"
:columns="columns"
:rows="storeData.logData"
row-key="name"
flat
bordered
hide-pagination
v-model:pagination="pagination"
dense
:rows-per-page-options="[0]"
class="custom-header-table"
: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">
<div
v-if="col.name === 'startTimeStamp'"
class="text-weight-medium"
>
{{ col.label }}
<q-btn
v-if="sortTime === 'desc'"
flat
round
size="8px"
icon="mdi-arrow-up"
@click="
() => {
sortTime = 'asc';
selectedDate();
}
"
/>
<q-btn
v-else
flat
round
size="8px"
icon="mdi-arrow-down"
@click="
() => {
sortTime = 'desc';
selectedDate();
}
"
/>
</div>
<span v-else class="text-weight-medium">{{ col.label }}</span>
</q-th>
</q-tr>
</template>
<template v-slot:body="props">
<q-tr
:props="props"
class="cursor-pointer"
style="overflow: scroll"
>
<q-td v-for="col in props.cols" :key="col.id">
<div v-if="col.name === 'username'">
<template v-if="props.row.userName && props.row.user">
{{ props.row.userName ?? "-" }}
<div class="text-grey" style="font-size: 65%">
{{ props.row.user ?? "-" }}
</div>
</template>
<template v-else> - </template>
</div>
<div v-else-if="col.name === 'method'">
<q-badge
:text-color="classColorMethod(col.value as string)"
style="background-color: #f0ecec"
>{{ col.value ?? "-" }}</q-badge
>
</div>
<div v-else-if="col.name === 'logType'">
<q-badge
text-color="white"
:color="
col.value === 'error'
? 'red'
: col.value === 'info'
? 'primary'
: 'warning'
"
>{{ col.value ?? "-" }}</q-badge
>
</div>
<div
v-else-if="col.name === 'responseDescription'"
:class="`text-${
props.row.logType === 'Error'
? 'red'
: props.row.logType === 'info'
? 'primary'
: 'warning'
}`"
class="ellipsis"
style="max-width: 10vw"
>
{{ col.value ?? "-" }}
<q-tooltip> {{ col.value }} </q-tooltip>
</div>
<div v-else-if="col.name === 'dataDiff'">
<q-btn
dense
flat
round
icon="mdi-eye"
color="info"
@click.stop="
() => {
currentDataDiff = col.value;
currentlogData = {
startTimeStamp: props.row.startTimeStamp,
username: props.row.userName,
host: props.row.host,
endpoint: props.row.endpoint,
method: props.row.method,
responseCode: props.row.responseCode,
logType: props.row.logType,
responseDescription: props.row.responseDescription,
sequence: props.row.sequence,
dataDiff: props.row.dataDiff,
input: props.row.input,
output: props.row.output,
};
openDialog = true;
}
"
>
<q-tooltip>รายละเอยด </q-tooltip>
</q-btn>
</div>
<div v-else class="ellipsis" style="max-width: 10vw">
{{
col.value === "" || col.value === null ? "-" : col.value
}}
<q-tooltip> {{ col.value }} </q-tooltip>
</div>
</q-td>
</q-tr>
</template>
</d-table>
</div>
</div>
</div> </div>
</div> </div>
@ -868,7 +1006,7 @@ onMounted(() => {
</DialogDataDiff> </DialogDataDiff>
</template> </template>
<style> <style scoped>
.row-color:nth-child(2n + 1) { .row-color:nth-child(2n + 1) {
background-color: #eeeeee; background-color: #eeeeee;
} }
@ -876,4 +1014,19 @@ onMounted(() => {
.dp__action_row { .dp__action_row {
padding: 2px !important; padding: 2px !important;
} }
.tree-container {
overflow: auto;
height: 74vh;
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> </style>

View file

@ -8,4 +8,33 @@ interface ItemsDropdown {
val: string; val: string;
} }
export type { Pagination, ItemsDropdown }; interface DataTree {
ancestorDNA: string;
createdAt: string;
createdFullName: string;
createdUserId: string;
id: string;
lastUpdateFullName: string;
lastUpdateUserId: string;
lastUpdatedAt: string;
orgRevisionId: string;
orgRootCode: string;
orgRootFax: string;
orgRootName: string;
orgRootOrder: number;
orgRootPhoneEx: string;
orgRootPhoneIn: string;
orgRootRank: string;
orgRootRankSub: string;
orgRootShortName: string;
responsibility: string;
}
interface QueryProfile {
searchKeyword: string;
searchField: string;
page: number;
pageSize: number;
id: string | null;
}
export type { Pagination, ItemsDropdown,DataTree,QueryProfile };

View file

@ -36,6 +36,7 @@ export const useDataStore = defineStore("logStore", () => {
startDate?: Date; startDate?: Date;
endDate?: Date; endDate?: Date;
searchStatus?: string; searchStatus?: string;
rootId?: string;
}, },
isBottom?: boolean isBottom?: boolean
) { ) {
@ -74,6 +75,7 @@ export const useDataStore = defineStore("logStore", () => {
}); });
} }
return { return {
size, size,
logData, logData,