feat(persons): add router for persons feature

This commit is contained in:
DESKTOP-1R2VSQH\Lenovo ThinkPad E490 2026-05-18 13:44:38 +07:00
parent 463a35059e
commit 985f1bcf23
6 changed files with 482 additions and 0 deletions

View file

@ -0,0 +1,306 @@
<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 { usePersonsStore } from "@/modules/23_persons/stores/PersonsStore";
import { usePagination } from "@/composables/usePagination";
import type { QTableProps } from "quasar";
import type {
DataOptions,
PosTypes,
PosLevels,
Person,
} from "@/modules/23_persons/interface/Main";
const $q = useQuasar();
const { showLoader, hideLoader, messageError } = useCounterMixin();
const { pagination, params, onRequest } = usePagination("", fetchData);
const store = usePersonsStore();
const props = defineProps<{ type: "current" | "draft" }>();
const options = reactive({
posType: [] as DataOptions[],
posLevel: [] as DataOptions[],
});
const filters = reactive({
keyword: "",
position: "",
posTypeId: "",
posLevelId: "",
});
const rows = ref<Person[]>([]);
const visibleColumns = ref<string[]>([
"citizenId",
"fullName",
"position",
"posType",
"posLevel",
]);
const columns = ref<QTableProps["columns"]>([
{
name: "citizenId",
align: "left",
label: "เลขบัตรประชาชน",
sortable: false,
field: "citizenId",
headerStyle: "font-size: 14px",
style: "font-size: 14px",
},
{
name: "fullName",
align: "left",
label: "ชื่อ-นามสกุล",
sortable: false,
field: "fullName",
format(val, row) {
return `${row.prefix || ""}${row.firstName || ""} ${
row.lastName || ""
}`.trim();
},
headerStyle: "font-size: 14px",
style: "font-size: 14px",
},
{
name: "position",
align: "left",
label: "ตำแหน่งในสายงาน",
sortable: false,
field: "position",
headerStyle: "font-size: 14px",
style: "font-size: 14px",
},
{
name: "posType",
align: "left",
label: "ประเภทตำแหน่ง",
sortable: false,
field: "posType",
headerStyle: "font-size: 14px",
style: "font-size: 14px",
},
{
name: "posLevel",
align: "left",
label: "ระดับตำแหน่ง",
sortable: false,
field: "posLevel",
headerStyle: "font-size: 14px",
style: "font-size: 14px",
},
]);
/** ฟังก์ชันดึงข้อมูลรายชื่อขรก. ที่ไม่อยู่ในโครงสร้าง */
async function fetchData() {
try {
showLoader();
const payload = {
...params.value,
posTypeId: filters.posTypeId,
posLevelId: filters.posLevelId,
position: filters.position.trim(),
keyword: filters.keyword.trim(),
};
const endpoint =
props.type === "current"
? config.API.orgSearchCurrentProfile
: config.API.orgSearchProfile;
const res = await http.post(endpoint, payload);
const result = res.data.result;
pagination.value.rowsNumber = result?.total || 0;
rows.value = result?.data || [];
} catch (error) {
messageError($q, error);
} finally {
hideLoader();
}
}
/** ฟังก์ชันดึงข้อมูลประเภทตำแหน่ง */
async function getPosType() {
try {
options.posType = await store.fetchPosType();
} catch (error) {
messageError($q, error);
}
}
/**
* งกนอปเดตตวเลอกระดบตำแหนงเมอเลอกประเภทตำแหน
* @param val ID เภทตำแหน
*/
function updateSelectType(val: string) {
const listLevel: PosTypes | undefined = store.optionsData.dataType.find(
(e: PosTypes) => e.id === val
);
store.optionsData.posLevel =
listLevel?.posLevels?.map((e: PosLevels) => ({
id: e.id,
name: e.posLevelName,
})) || [];
options.posLevel = store.optionsData.posLevel;
filters.posLevelId = "";
searchData();
}
/** ฟังก์ชันค้นหาข้อมูล*/
function searchData() {
pagination.value.page = 1;
fetchData();
}
/**
* งกนเปดหนาทะเบยนขรก. ในแทบใหม
* @param personId ID ของขรก.
*/
function handleRedirect(personId: string) {
window.open(`/registry-officer/${personId}`, "_blank");
}
onMounted(() => {
fetchData();
getPosType();
});
</script>
<template>
<div class="col-12">
<!-- Filter Section -->
<div class="row col-12 q-col-gutter-sm q-mb-sm">
<div class="col-xs-12 col-sm-6 col-md-2">
<q-input
v-model="filters.position"
dense
outlined
label="ตำแหน่งในสายงาน"
@keydown.enter.prevent="searchData"
/>
</div>
<div class="col-xs-12 col-sm-6 col-md-2">
<q-select
label="ตำแหน่งประเภท"
v-model="filters.posTypeId"
:options="options.posType"
emit-value
dense
map-options
outlined
option-label="name"
option-value="id"
clearable
@clear="filters.posTypeId = ''"
@update:model-value="updateSelectType"
/>
</div>
<div class="col-xs-12 col-sm-6 col-md-2">
<q-select
label="ระดับตำแหน่ง"
v-model="filters.posLevelId"
:disable="!filters.posTypeId"
:options="options.posLevel"
emit-value
dense
map-options
outlined
option-label="name"
option-value="id"
clearable
@clear="filters.posLevelId = ''"
@update:model-value="searchData"
/>
</div>
<q-space />
<div class="col-xs-12 col-sm-6 col-md-2">
<q-input
v-model="filters.keyword"
dense
outlined
label="ค้นหาจากชื่อ-นามสกุล หรือเลขประจำตัวประชาชน"
@keydown.enter.prevent="searchData"
>
<template v-slot:append>
<q-icon name="search" class="cursor-pointer" @click="searchData" />
</template>
</q-input>
</div>
<div class="col-xs-12 col-sm-6 col-md-2">
<q-select
for="visibleColumns"
v-model="visibleColumns"
multiple
outlined
dense
options-dense
:display-value="$q.lang.table.columns"
emit-value
map-options
:options="columns"
option-value="name"
/>
</div>
</div>
<!-- Table Section -->
<div class="col-12">
<p-table
ref="table"
:columns="columns"
:rows="rows"
row-key="id"
flat
bordered
dense
class="custom-header-table"
:visible-columns="visibleColumns"
:rows-per-page-options="[1, 10, 25, 50, 100]"
v-model:pagination="pagination"
@request="onRequest"
>
<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">
<q-tr :props="props">
<q-td auto-width>
<q-btn
flat
dense
round
color="info"
icon="mdi-eye"
@click.prevent="handleRedirect(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>
{{ col.value ? col.value : "-" }}
</div>
</q-td>
</q-tr>
</template>
</p-table>
</div>
</div>
</template>
<style scoped></style>

View file

@ -0,0 +1,38 @@
interface DataOptions {
id: string;
name: string;
}
interface PosTypes {
createdAt: Date;
id: string;
lastUpdateFullName: string;
lastUpdatedAt: Date;
posTypeName: string;
posTypeRank: number;
posLevels: PosLevels[];
}
interface PosLevels {
createdAt: Date;
id: string;
lastUpdateFullName: string;
lastUpdatedAt: Date;
posLevelAuthority: string;
posLevelName: string;
posLevelRank: number;
}
interface Person {
citizenId: string;
firstName: string;
id: string;
lastName: string;
posLevel: string;
posType: string;
position: string;
prefix: string;
rank: string;
}
export type { DataOptions, PosTypes, PosLevels, Person };

View file

@ -0,0 +1,14 @@
const Main = () => import("@/modules/23_persons/views/Main.vue");
export default [
{
path: "/persons",
name: "personsMain",
component: Main,
meta: {
Auth: true,
Key: "PERSONS",
Role: "PERSONS",
},
},
];

View file

@ -0,0 +1,45 @@
import { defineStore } from "pinia";
import { reactive } from "vue";
import http from "@/plugins/http";
import config from "@/app.config";
import type {
DataOptions,
PosTypes,
} from "@/modules/23_persons/interface/Main";
export const usePersonsStore = defineStore("personsStore", () => {
const routeCheck = {
registry: {
meta: { Auth: true, Key: "SYS_REGISTRY_OFFICER", Role: "OWNER" },
},
org: {
meta: { Auth: true, Key: "SYS_ORG", Role: "OWNER" },
},
};
const optionsData = reactive({
posType: [] as DataOptions[],
posLevel: [] as DataOptions[],
dataType: [] as PosTypes[],
});
/** ฟังก์ชันดึงข้อมูลประเภทตำแหน่ง */
async function fetchPosType() {
if (optionsData.posType.length > 0) {
return optionsData.posType;
}
const res = await http.get(config.API.orgPosType);
optionsData.dataType = res.data.result;
optionsData.posType = res.data.result.map((e: PosTypes) => ({
id: e.id,
name: e.posTypeName,
}));
return optionsData.posType;
}
return { routeCheck, optionsData, fetchPosType };
});

View file

@ -0,0 +1,77 @@
<script setup lang="ts">
import { watch, computed, ref } from "vue";
import { useRouter } from "vue-router";
import { usePersonsStore } from "@/modules/23_persons/stores/PersonsStore";
import { checkPermission } from "@/utils/permissions";
import TablePersons from "@/modules/23_persons/components/TablePersons.vue";
const router = useRouter();
const { routeCheck } = usePersonsStore();
/// .
const hasPermission = computed(() => {
const registryPermission = checkPermission(routeCheck.registry);
const orgPermission = checkPermission(routeCheck.org);
if (registryPermission === null || orgPermission === null) {
return null;
}
return (
registryPermission?.attrOwnership === "OWNER" &&
orgPermission?.attrOwnership === "OWNER"
);
});
const tabs = ref("1");
watch(
() => hasPermission.value,
(allowed) => {
if (!allowed) {
router.push("/404");
}
}
);
</script>
<template>
<div class="row items-center">
<div class="toptitle text-dark row items-center q-py-xs">
รายชอขรก. ไมอยในโครงสราง
</div>
</div>
<q-card bordered flat>
<q-tabs
v-model="tabs"
dense
align="left"
inline-label
class="rounded-borders"
indicator-color="primary"
active-bg-color="teal-1"
active-class="text-primary"
>
<q-tab name="1" label="ปัจจุบัน" />
<q-tab name="2" label="แบบร่าง" />
</q-tabs>
<q-separator />
<div class="q-pa-sm">
<q-tab-panels v-model="tabs" animated>
<q-tab-panel name="1">
<TablePersons v-if="hasPermission" type="current" />
</q-tab-panel>
<q-tab-panel name="2">
<TablePersons v-if="hasPermission" type="draft" />
</q-tab-panel>
</q-tab-panels>
</div>
</q-card>
</template>
<style scoped></style>

View file

@ -29,6 +29,7 @@ import ModulePositionCondition from "@/modules/19_condition/router";
import ModulePositionTemp from "@/modules/20_positionTemp/router";
import ModuleReport from "@/modules/21_report/router";
import ModuleIssues from "@/modules/22_issues/router";
import ModulePersons from "@/modules/23_persons/router";
// TODO: ใช้หรือไม่?
import { authenticated, logout } from "@/plugins/auth";
@ -81,6 +82,7 @@ const router = createRouter({
...ModulePositionTemp,
...ModuleReport,
...ModuleIssues,
...ModulePersons,
],
},
/**