feat(02): citizen id, issue, expire
This commit is contained in:
parent
6a6bf68cd9
commit
aece743ccf
6 changed files with 387 additions and 264 deletions
|
|
@ -20,6 +20,9 @@ const birthDate = defineModel<Date | string | null>('birthDate');
|
|||
const nationality = defineModel<string>('nationality');
|
||||
const midName = defineModel<string | null>('midName');
|
||||
const midNameEN = defineModel<string | null>('midNameEN');
|
||||
const citizenId = defineModel<string>('citizenId');
|
||||
const citizenIssue = defineModel<Date | null>('citizenIssue');
|
||||
const citizenExpire = defineModel<Date | null>('citizenExpire');
|
||||
|
||||
const props = defineProps<{
|
||||
dense?: boolean;
|
||||
|
|
@ -112,6 +115,26 @@ watch(
|
|||
</div>
|
||||
|
||||
<div class="col-12 row q-col-gutter-sm">
|
||||
<q-input
|
||||
v-if="!employee"
|
||||
outlined
|
||||
class="col-5"
|
||||
hide-bottom-space
|
||||
v-model="citizenId"
|
||||
mask="#############"
|
||||
:readonly="readonly"
|
||||
:dense="dense"
|
||||
:label="$t('personnel.form.citizenId')"
|
||||
:rules="[
|
||||
(val) => (val && val.length > 0) || $t('form.error.required'),
|
||||
(val) =>
|
||||
(val && val.length === 13 && /[0-9]+/.test(val)) ||
|
||||
$t('form.error.invalidCustomeMessage', {
|
||||
msg: $t('form.error.requireLength', { msg: 13 }),
|
||||
}),
|
||||
]"
|
||||
for="input-citizen-id"
|
||||
/>
|
||||
<div class="col-12 row" style="display: flex; gap: var(--size-2)">
|
||||
<q-select
|
||||
outlined
|
||||
|
|
@ -196,7 +219,7 @@ watch(
|
|||
:readonly="readonly"
|
||||
:disable="!readonly"
|
||||
class="col-md-1 col-6"
|
||||
:label="$t('personnel.form.prefixName')"
|
||||
label="Title"
|
||||
:model-value="
|
||||
readonly
|
||||
? capitalize(prefixName || '') || '-'
|
||||
|
|
@ -215,7 +238,7 @@ watch(
|
|||
:readonly="readonly"
|
||||
hide-bottom-space
|
||||
class="col"
|
||||
:label="$t('personnel.form.firstNameEN')"
|
||||
label="Name"
|
||||
v-model="firstNameEN"
|
||||
:rules="[
|
||||
(val: string) => !!val || $t('form.error.required'),
|
||||
|
|
@ -230,7 +253,7 @@ watch(
|
|||
:readonly="readonly"
|
||||
hide-bottom-space
|
||||
class="col-md-3 col-6"
|
||||
:label="$t('personnel.form.middleNameEN')"
|
||||
label="Mid Name"
|
||||
:model-value="readonly ? midNameEN || '-' : midNameEN"
|
||||
@update:model-value="
|
||||
(v) => (typeof v === 'string' ? (midNameEN = v) : '')
|
||||
|
|
@ -244,7 +267,7 @@ watch(
|
|||
:readonly="readonly"
|
||||
hide-bottom-space
|
||||
class="col"
|
||||
:label="$t('personnel.form.lastNameEN')"
|
||||
label="Surname"
|
||||
v-model="lastNameEN"
|
||||
:rules="[
|
||||
(val: string) => !!val || $t('form.error.required'),
|
||||
|
|
@ -268,12 +291,22 @@ watch(
|
|||
(v) => (typeof v === 'string' ? (telephoneNo = v) : '')
|
||||
"
|
||||
@clear="telephoneNo = ''"
|
||||
/>
|
||||
>
|
||||
<template #prepend>
|
||||
<q-icon
|
||||
size="xs"
|
||||
name="mdi-phone-outline"
|
||||
class="cursor-pointer"
|
||||
color="primary"
|
||||
/>
|
||||
</template>
|
||||
</q-input>
|
||||
<q-input
|
||||
v-if="!employee"
|
||||
:for="`${prefixId}-input-email`"
|
||||
:dense="dense"
|
||||
outlined
|
||||
hide-bottom-space
|
||||
:readonly="readonly"
|
||||
:label="$t('form.email')"
|
||||
:rules="
|
||||
|
|
@ -290,7 +323,16 @@ watch(
|
|||
:model-value="readonly ? email || '-' : email"
|
||||
@update:model-value="(v) => (typeof v === 'string' ? (email = v) : '')"
|
||||
@clear="email = ''"
|
||||
/>
|
||||
>
|
||||
<template #prepend>
|
||||
<q-icon
|
||||
size="xs"
|
||||
name="mdi-email-outline"
|
||||
class="cursor-pointer"
|
||||
color="primary"
|
||||
/>
|
||||
</template>
|
||||
</q-input>
|
||||
|
||||
<q-select
|
||||
v-if="!employee"
|
||||
|
|
@ -355,6 +397,33 @@ watch(
|
|||
"
|
||||
/>
|
||||
|
||||
<DatePicker
|
||||
v-if="!employee"
|
||||
v-model="citizenIssue"
|
||||
class="col-md-2 col-6"
|
||||
:id="`${prefixId}-input-citizen-issue`"
|
||||
:readonly="readonly"
|
||||
:label="$t('personnel.form.citizenIssue')"
|
||||
:disabled-dates="disabledAfterToday"
|
||||
:rules="[
|
||||
(val: string) =>
|
||||
!!val ||
|
||||
$t('form.error.selectField', {
|
||||
field: $t('personnel.form.citizenIssue'),
|
||||
}),
|
||||
]"
|
||||
/>
|
||||
|
||||
<DatePicker
|
||||
v-if="!employee"
|
||||
v-model="citizenExpire"
|
||||
class="col-md-2 col-6"
|
||||
:id="`${prefixId}-input-citizen-expire`"
|
||||
:readonly="readonly"
|
||||
:label="$t('personnel.form.citizenExpire')"
|
||||
:disabled-dates="disabledAfterToday"
|
||||
/>
|
||||
|
||||
<q-select
|
||||
v-if="employee"
|
||||
outlined
|
||||
|
|
|
|||
|
|
@ -498,7 +498,7 @@ watch(districtId, fetchSubDistrict);
|
|||
:label="$t('form.fullAddress')"
|
||||
readonly
|
||||
:disable="!readonly && !sameWithEmployer"
|
||||
:for="`${prefixId}-${indexId !== undefined ? `input-street-${indexId}` : 'input-street'}`"
|
||||
:for="`${prefixId}-${indexId !== undefined ? `input-full-address-${indexId}` : 'input-full-address'}`"
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
|
@ -727,7 +727,7 @@ watch(districtId, fetchSubDistrict);
|
|||
label="Full Address"
|
||||
readonly
|
||||
:disable="!readonly && !sameWithEmployer"
|
||||
:for="`${prefixId}-${indexId !== undefined ? `input-street-${indexId}` : 'input-street'}`"
|
||||
:for="`${prefixId}-${indexId !== undefined ? `input-full-address-en-${indexId}` : 'input-full-address-en'}`"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -270,6 +270,9 @@ export default {
|
|||
checkpoint: 'Checkpoint',
|
||||
checkpointEN: 'Checkpoint (EN)',
|
||||
attachment: 'Attachment Document',
|
||||
citizenId: 'Citizen ID',
|
||||
citizenIssue: 'Citizen Issue',
|
||||
citizenExpire: 'Citizen Expire',
|
||||
},
|
||||
},
|
||||
customer: {
|
||||
|
|
|
|||
|
|
@ -269,6 +269,9 @@ export default {
|
|||
checkpoint: 'ด่าน',
|
||||
checkpointEN: 'ด่าน ภาษาอังกฤษ',
|
||||
attachment: 'เอกสารประจำตัว',
|
||||
citizenId: 'เลขที่บัตรประชาชน',
|
||||
citizenIssue: 'วันที่ออกบัตร',
|
||||
citizenExpire: 'วันที่หมดอายุ',
|
||||
},
|
||||
},
|
||||
customer: {
|
||||
|
|
|
|||
|
|
@ -1,14 +1,19 @@
|
|||
<script setup lang="ts">
|
||||
import { ref, onMounted, watch, computed } from 'vue';
|
||||
import useUtilsStore, { dialog, baseUrl } from 'stores/utils';
|
||||
import { calculateAge } from 'src/utils/datetime';
|
||||
import { useQuasar, type QTableProps } from 'quasar';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import useFlowStore from 'stores/flow';
|
||||
|
||||
import useUserStore from 'stores/user';
|
||||
import useFlowStore from 'stores/flow';
|
||||
import useBranchStore from 'stores/branch';
|
||||
import useOptionStore from 'stores/options';
|
||||
import useAddressStore from 'stores/address';
|
||||
import useMyBranch from 'src/stores/my-branch';
|
||||
import { calculateAge } from 'src/utils/datetime';
|
||||
import { useQuasar, type QTableProps } from 'quasar';
|
||||
import useUtilsStore, { dialog, baseUrl } from 'stores/utils';
|
||||
import { isRoleInclude, resetScrollBar } from 'src/stores/utils';
|
||||
import { BranchUserStats } from 'stores/branch/types';
|
||||
|
||||
import {
|
||||
User,
|
||||
|
|
@ -24,42 +29,99 @@ import {
|
|||
UndoButton,
|
||||
} from 'components/button';
|
||||
|
||||
import { BranchUserStats } from 'stores/branch/types';
|
||||
import useAddressStore from 'stores/address';
|
||||
import ButtonAddComponent from 'components/ButtonAddCompoent.vue';
|
||||
import PersonCard from 'components/shared/PersonCard.vue';
|
||||
import StatCardComponent from 'components/StatCardComponent.vue';
|
||||
import NoData from 'components/NoData.vue';
|
||||
import { AddressForm } from 'components/form';
|
||||
import SideMenu from 'components/SideMenu.vue';
|
||||
import AddButton from 'components/AddButton.vue';
|
||||
import DialogForm from 'components/DialogForm.vue';
|
||||
import DrawerInfo from 'components/DrawerInfo.vue';
|
||||
import ProfileBanner from 'components/ProfileBanner.vue';
|
||||
import PersonCard from 'components/shared/PersonCard.vue';
|
||||
import KebabAction from 'components/shared/KebabAction.vue';
|
||||
import TooltipComponent from 'components/TooltipComponent.vue';
|
||||
import FormInformation from 'components/02_personnel-management/FormInformation.vue';
|
||||
import StatCardComponent from 'components/StatCardComponent.vue';
|
||||
import ImageUploadDialog from 'components/ImageUploadDialog.vue';
|
||||
import ButtonAddComponent from 'components/ButtonAddCompoent.vue';
|
||||
import PaginationComponent from 'components/PaginationComponent.vue';
|
||||
import InfoForm from 'components/02_personnel-management/InfoForm.vue';
|
||||
import FormPerson from 'components/02_personnel-management/FormPerson.vue';
|
||||
import FormByType from 'components/02_personnel-management/FormByType.vue';
|
||||
import DrawerInfo from 'components/DrawerInfo.vue';
|
||||
import InfoForm from 'components/02_personnel-management/InfoForm.vue';
|
||||
import NoData from 'components/NoData.vue';
|
||||
import PaginationComponent from 'components/PaginationComponent.vue';
|
||||
import useOptionStore from 'stores/options';
|
||||
import ProfileBanner from 'components/ProfileBanner.vue';
|
||||
import SideMenu from 'components/SideMenu.vue';
|
||||
import ImageUploadDialog from 'components/ImageUploadDialog.vue';
|
||||
import { AddressForm } from 'components/form';
|
||||
import DialogForm from 'components/DialogForm.vue';
|
||||
import KebabAction from 'src/components/shared/KebabAction.vue';
|
||||
import useMyBranch from 'src/stores/my-branch';
|
||||
import FormInformation from 'components/02_personnel-management/FormInformation.vue';
|
||||
|
||||
const { locale, t } = useI18n();
|
||||
const $q = useQuasar();
|
||||
|
||||
const optionStore = useOptionStore();
|
||||
const utilsStore = useUtilsStore();
|
||||
const flowStore = useFlowStore();
|
||||
const userStore = useUserStore();
|
||||
const flowStore = useFlowStore();
|
||||
const utilsStore = useUtilsStore();
|
||||
const optionStore = useOptionStore();
|
||||
const branchStore = useBranchStore();
|
||||
const adrressStore = useAddressStore();
|
||||
const useMyBranchStore = useMyBranch();
|
||||
const { data: userData } = storeToRefs(userStore);
|
||||
const { myBranch } = storeToRefs(useMyBranchStore);
|
||||
|
||||
// state & control
|
||||
const modal = ref(false);
|
||||
const isEdit = ref(false);
|
||||
const hideStat = ref(false);
|
||||
const modeView = ref(false);
|
||||
const infoDrawer = ref(false);
|
||||
const isImageEdit = ref(false);
|
||||
const imageDialog = ref(false);
|
||||
const infoDrawerEdit = ref(false);
|
||||
const refreshImageState = ref(false);
|
||||
|
||||
const inputSearch = ref('');
|
||||
const currentTab = ref<string>('ALL');
|
||||
const statusFilter = ref<'all' | 'statusACTIVE' | 'statusINACTIVE'>('all');
|
||||
|
||||
const currentPage = ref(1);
|
||||
const pageSize = ref(30);
|
||||
const currentMaxPage = computed(() =>
|
||||
userData.value ? Math.ceil(userData.value?.total / pageSize.value) : 1,
|
||||
);
|
||||
|
||||
// form & data
|
||||
const hqId = ref<string>();
|
||||
const brId = ref<string>();
|
||||
const currentUser = ref<User>();
|
||||
const userCode = ref<string>();
|
||||
|
||||
const statusToggle = ref(true);
|
||||
const agencyFile = ref<File[]>([]);
|
||||
const agencyFileList = ref<{ name: string; url: string }[]>([]);
|
||||
|
||||
const typeStats = ref<UserTypeStats>();
|
||||
const userStats = ref<BranchUserStats[]>();
|
||||
|
||||
const urlProfile = ref<string>();
|
||||
const profileFileImg = ref<File | null>(null);
|
||||
const imageList = ref<{ selectedImage: string; list: string[] }>();
|
||||
const onCreateImageList = ref<{
|
||||
selectedImage: string;
|
||||
list: { url: string; imgFile: File | null; name: string }[];
|
||||
}>({ selectedImage: '', list: [] });
|
||||
|
||||
const formMenuIcon = ref<{ icon: string; color: string; bgColor: string }[]>([
|
||||
{
|
||||
icon: 'mdi-account-outline',
|
||||
color: 'hsl(var(--info-bg))',
|
||||
bgColor: 'var(--surface-1)',
|
||||
},
|
||||
|
||||
{
|
||||
icon: 'mdi-map-marker-radius-outline',
|
||||
color: 'hsl(var(--info-bg))',
|
||||
bgColor: 'var(--surface-1)',
|
||||
},
|
||||
{
|
||||
icon: 'mdi-briefcase-outline',
|
||||
color: 'hsl(var(--info-bg))',
|
||||
bgColor: 'var(--surface-1)',
|
||||
},
|
||||
]);
|
||||
|
||||
const defaultFormData = {
|
||||
provinceId: null,
|
||||
districtId: null,
|
||||
|
|
@ -70,6 +132,12 @@ const defaultFormData = {
|
|||
gender: '',
|
||||
addressEN: '',
|
||||
address: '',
|
||||
mooEN: '',
|
||||
moo: '',
|
||||
soiEN: '',
|
||||
soi: '',
|
||||
streetEN: '',
|
||||
street: '',
|
||||
trainingPlace: null,
|
||||
importNationality: null,
|
||||
sourceNationality: null,
|
||||
|
|
@ -95,9 +163,58 @@ const defaultFormData = {
|
|||
namePrefix: null,
|
||||
middleNameEN: '',
|
||||
middleName: '',
|
||||
citizenExpire: null,
|
||||
citizenIssue: null,
|
||||
citizenId: '',
|
||||
};
|
||||
|
||||
const modeView = ref(false);
|
||||
const formData = ref<UserCreate>({
|
||||
selectedImage: '',
|
||||
branchId: '',
|
||||
provinceId: null,
|
||||
districtId: null,
|
||||
subDistrictId: null,
|
||||
telephoneNo: '',
|
||||
email: '',
|
||||
zipCode: '',
|
||||
gender: '',
|
||||
addressEN: '',
|
||||
address: '',
|
||||
mooEN: '',
|
||||
moo: '',
|
||||
soiEN: '',
|
||||
soi: '',
|
||||
streetEN: '',
|
||||
street: '',
|
||||
trainingPlace: null,
|
||||
importNationality: null,
|
||||
sourceNationality: null,
|
||||
licenseExpireDate: null,
|
||||
licenseIssueDate: null,
|
||||
licenseNo: null,
|
||||
discountCondition: null,
|
||||
retireDate: null,
|
||||
startDate: null,
|
||||
registrationNo: null,
|
||||
lastNameEN: '',
|
||||
lastName: '',
|
||||
middleNameEN: '',
|
||||
middleName: '',
|
||||
firstNameEN: '',
|
||||
firstName: '',
|
||||
namePrefix: null,
|
||||
userRole: '',
|
||||
userType: '',
|
||||
birthDate: null,
|
||||
responsibleArea: null,
|
||||
checkpoint: null,
|
||||
checkpointEN: null,
|
||||
username: '',
|
||||
status: 'CREATED',
|
||||
citizenExpire: null,
|
||||
citizenIssue: null,
|
||||
citizenId: '',
|
||||
});
|
||||
|
||||
const fieldSelectedOption = ref<{ label: string; value: string }[]>([
|
||||
{
|
||||
|
|
@ -131,25 +248,6 @@ const fieldSelectedOption = ref<{ label: string; value: string }[]>([
|
|||
},
|
||||
]);
|
||||
|
||||
const formMenuIcon = ref<{ icon: string; color: string; bgColor: string }[]>([
|
||||
{
|
||||
icon: 'mdi-account-outline',
|
||||
color: 'hsl(var(--info-bg))',
|
||||
bgColor: 'var(--surface-1)',
|
||||
},
|
||||
|
||||
{
|
||||
icon: 'mdi-map-marker-radius-outline',
|
||||
color: 'hsl(var(--info-bg))',
|
||||
bgColor: 'var(--surface-1)',
|
||||
},
|
||||
{
|
||||
icon: 'mdi-briefcase-outline',
|
||||
color: 'hsl(var(--info-bg))',
|
||||
bgColor: 'var(--surface-1)',
|
||||
},
|
||||
]);
|
||||
|
||||
const fieldSelected = ref<
|
||||
(
|
||||
| 'orderNumber'
|
||||
|
|
@ -170,86 +268,6 @@ const fieldSelected = ref<
|
|||
'userRole',
|
||||
]);
|
||||
|
||||
const refImageUpload = ref<InstanceType<typeof ImageUploadDialog>>();
|
||||
const hideStat = ref(false);
|
||||
const userCode = ref<string>();
|
||||
const currentUser = ref<User>();
|
||||
const infoDrawerEdit = ref(false);
|
||||
const infoPersonId = ref<string>('');
|
||||
const infoDrawer = ref(false);
|
||||
const profileSubmit = ref(false);
|
||||
const urlProfile = ref<string>();
|
||||
const isEdit = ref(false);
|
||||
const modal = ref(false);
|
||||
const statusToggle = ref(true);
|
||||
const statusFilter = ref<'all' | 'statusACTIVE' | 'statusINACTIVE'>('all');
|
||||
const inputSearch = ref('');
|
||||
const userId = ref<string>();
|
||||
const selectorLabel = ref<string>('ALL');
|
||||
const hqId = ref<string>();
|
||||
const brId = ref<string>();
|
||||
const formDialogRef = ref();
|
||||
const userStats = ref<BranchUserStats[]>();
|
||||
|
||||
const typeStats = ref<UserTypeStats>();
|
||||
const agencyFile = ref<File[]>([]);
|
||||
const agencyFileList = ref<{ name: string; url: string }[]>([]);
|
||||
const formData = ref<UserCreate>({
|
||||
selectedImage: '',
|
||||
branchId: '',
|
||||
provinceId: null,
|
||||
districtId: null,
|
||||
subDistrictId: null,
|
||||
telephoneNo: '',
|
||||
email: '',
|
||||
zipCode: '',
|
||||
gender: '',
|
||||
addressEN: '',
|
||||
address: '',
|
||||
trainingPlace: null,
|
||||
importNationality: null,
|
||||
sourceNationality: null,
|
||||
licenseExpireDate: null,
|
||||
licenseIssueDate: null,
|
||||
licenseNo: null,
|
||||
discountCondition: null,
|
||||
retireDate: null,
|
||||
startDate: null,
|
||||
registrationNo: null,
|
||||
lastNameEN: '',
|
||||
lastName: '',
|
||||
middleNameEN: '',
|
||||
middleName: '',
|
||||
firstNameEN: '',
|
||||
firstName: '',
|
||||
namePrefix: null,
|
||||
userRole: '',
|
||||
userType: '',
|
||||
birthDate: null,
|
||||
responsibleArea: null,
|
||||
checkpoint: null,
|
||||
checkpointEN: null,
|
||||
username: '',
|
||||
status: 'CREATED',
|
||||
});
|
||||
|
||||
const isImageEdit = ref<boolean>(false);
|
||||
const imageUrl = ref<string>('');
|
||||
const profileFileImg = ref<File | null>(null);
|
||||
const imageDialog = ref(false);
|
||||
const refreshImageState = ref(false);
|
||||
const imageList = ref<{ selectedImage: string; list: string[] }>();
|
||||
const onCreateImageList = ref<{
|
||||
selectedImage: string;
|
||||
list: { url: string; imgFile: File | null; name: string }[];
|
||||
}>({ selectedImage: '', list: [] });
|
||||
|
||||
// const inputFile = document.createElement('input');
|
||||
// inputFile.type = 'file';
|
||||
// inputFile.accept = 'image/*';
|
||||
|
||||
// const reader = new FileReader();
|
||||
|
||||
const columns = [
|
||||
{
|
||||
name: 'orderNumber',
|
||||
|
|
@ -297,9 +315,53 @@ const columns = [
|
|||
},
|
||||
] satisfies QTableProps['columns'];
|
||||
|
||||
// watch(profileFileImg, () => {
|
||||
// if (profileFileImg.value) reader.readAsDataURL(profileFileImg.value);
|
||||
// });
|
||||
function undo() {
|
||||
if (!currentUser.value) return;
|
||||
isImageEdit.value = false;
|
||||
infoDrawerEdit.value = false;
|
||||
assignFormData(currentUser.value.id);
|
||||
}
|
||||
|
||||
function mapRole(value: string) {
|
||||
const option = userStore.userOption.roleOpts.find(
|
||||
(option) => option.value === value,
|
||||
);
|
||||
return option ? option.label : '-';
|
||||
}
|
||||
|
||||
function mapUserType(label: string) {
|
||||
const userTypeMap: { [key: string]: string } = {
|
||||
USER: 'USER',
|
||||
MESSENGER: 'MESSENGER',
|
||||
DELEGATE: 'DELEGATE',
|
||||
AGENCY: 'AGENCY',
|
||||
};
|
||||
formData.value.userType = userTypeMap[label];
|
||||
}
|
||||
|
||||
function onClose(excludeDialog?: boolean) {
|
||||
if (excludeDialog) {
|
||||
infoDrawer.value = excludeDialog || false;
|
||||
return;
|
||||
}
|
||||
hqId.value = '';
|
||||
brId.value = '';
|
||||
userCode.value = '';
|
||||
urlProfile.value = '';
|
||||
profileFileImg.value = null;
|
||||
infoDrawerEdit.value = false;
|
||||
agencyFile.value = [];
|
||||
modal.value = false;
|
||||
isEdit.value = false;
|
||||
statusToggle.value = true;
|
||||
isImageEdit.value = false;
|
||||
currentUser.value = undefined;
|
||||
Object.assign(formData.value, defaultFormData);
|
||||
mapUserType(currentTab.value);
|
||||
imageList.value = { selectedImage: '', list: [] };
|
||||
onCreateImageList.value = { selectedImage: '', list: [] };
|
||||
flowStore.rotate();
|
||||
}
|
||||
|
||||
async function openDialog(
|
||||
action?: 'FORM' | 'INFO',
|
||||
|
|
@ -366,41 +428,9 @@ async function openDialog(
|
|||
userStore.userOption.brOpts = [];
|
||||
}
|
||||
|
||||
function undo() {
|
||||
if (!infoPersonId.value) return;
|
||||
isImageEdit.value = false;
|
||||
infoDrawerEdit.value = false;
|
||||
assignFormData(infoPersonId.value);
|
||||
}
|
||||
|
||||
function onClose(excludeDialog?: boolean) {
|
||||
if (excludeDialog) {
|
||||
infoDrawer.value = excludeDialog || false;
|
||||
return;
|
||||
}
|
||||
hqId.value = '';
|
||||
brId.value = '';
|
||||
userId.value = '';
|
||||
userCode.value = '';
|
||||
urlProfile.value = '';
|
||||
profileFileImg.value = null;
|
||||
infoDrawerEdit.value = false;
|
||||
agencyFile.value = [];
|
||||
modal.value = false;
|
||||
isEdit.value = false;
|
||||
profileSubmit.value = false;
|
||||
statusToggle.value = true;
|
||||
isImageEdit.value = false;
|
||||
currentUser.value = undefined;
|
||||
Object.assign(formData.value, defaultFormData);
|
||||
mapUserType(selectorLabel.value);
|
||||
imageList.value = { selectedImage: '', list: [] };
|
||||
onCreateImageList.value = { selectedImage: '', list: [] };
|
||||
flowStore.rotate();
|
||||
}
|
||||
async function onSubmit(excludeDialog?: boolean) {
|
||||
if (isEdit.value && userId.value) {
|
||||
if (!userId.value) return;
|
||||
if (isEdit.value && currentUser.value) {
|
||||
if (!currentUser.value.id) return;
|
||||
|
||||
formData.value.branchId = brId.value
|
||||
? brId.value
|
||||
|
|
@ -412,16 +442,16 @@ async function onSubmit(excludeDialog?: boolean) {
|
|||
status: !statusToggle.value ? 'INACTIVE' : 'ACTIVE',
|
||||
} as const;
|
||||
|
||||
await userStore.editById(userId.value, formDataEdit);
|
||||
await userStore.editById(currentUser.value.id, formDataEdit);
|
||||
|
||||
if (userId.value && formDataEdit.userType === 'AGENCY') {
|
||||
if (currentUser.value.id && formDataEdit.userType === 'AGENCY') {
|
||||
if (!agencyFile.value) return;
|
||||
const payload: UserAttachmentCreate = {
|
||||
file: agencyFile.value,
|
||||
};
|
||||
|
||||
if (payload?.file) {
|
||||
await userStore.addAttachment(userId.value, payload);
|
||||
await userStore.addAttachment(currentUser.value.id, payload);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -463,7 +493,7 @@ async function onSubmit(excludeDialog?: boolean) {
|
|||
}
|
||||
}
|
||||
|
||||
selectorLabel.value = formData.value.userType;
|
||||
currentTab.value = formData.value.userType;
|
||||
|
||||
await fetchUserList();
|
||||
|
||||
|
|
@ -496,16 +526,6 @@ async function onDelete(id: string) {
|
|||
});
|
||||
}
|
||||
|
||||
function mapUserType(label: string) {
|
||||
const userTypeMap: { [key: string]: string } = {
|
||||
USER: 'USER',
|
||||
MESSENGER: 'MESSENGER',
|
||||
DELEGATE: 'DELEGATE',
|
||||
AGENCY: 'AGENCY',
|
||||
};
|
||||
formData.value.userType = userTypeMap[label];
|
||||
}
|
||||
|
||||
async function toggleStatus(id: string) {
|
||||
const record = userData.value?.result.find((v) => v.id === id);
|
||||
|
||||
|
|
@ -549,9 +569,6 @@ async function assignFormData(idEdit: string) {
|
|||
|
||||
if (foundUser) {
|
||||
currentUser.value = foundUser;
|
||||
if (currentUser.value) {
|
||||
infoPersonId.value = currentUser.value.id;
|
||||
}
|
||||
formData.value = {
|
||||
branchId: foundUser.branch[0]?.id,
|
||||
provinceId: foundUser.provinceId,
|
||||
|
|
@ -563,6 +580,12 @@ async function assignFormData(idEdit: string) {
|
|||
gender: foundUser.gender,
|
||||
addressEN: foundUser.addressEN,
|
||||
address: foundUser.address,
|
||||
moo: foundUser.moo,
|
||||
mooEN: foundUser.mooEN,
|
||||
soi: foundUser.soi,
|
||||
soiEN: foundUser.soiEN,
|
||||
street: foundUser.street,
|
||||
streetEN: foundUser.streetEN,
|
||||
trainingPlace: foundUser.trainingPlace,
|
||||
importNationality: foundUser.importNationality,
|
||||
sourceNationality: foundUser.sourceNationality,
|
||||
|
|
@ -595,12 +618,16 @@ async function assignFormData(idEdit: string) {
|
|||
(foundUser.retireDate && new Date(foundUser.retireDate)) || null,
|
||||
startDate: (foundUser.startDate && new Date(foundUser.startDate)) || null,
|
||||
birthDate: (foundUser.birthDate && new Date(foundUser.birthDate)) || null,
|
||||
citizenId: foundUser.citizenId,
|
||||
citizenIssue:
|
||||
(foundUser.citizenIssue && new Date(foundUser.citizenIssue)) || null,
|
||||
citizenExpire:
|
||||
(foundUser.citizenExpire && new Date(foundUser.citizenExpire)) || null,
|
||||
};
|
||||
|
||||
formData.value.status === 'ACTIVE' || 'CREATED'
|
||||
? (statusToggle.value = true)
|
||||
: (statusToggle.value = false);
|
||||
userId.value = foundUser.id;
|
||||
|
||||
if (foundUser.branch) {
|
||||
if (foundUser.branch?.[0].isHeadOffice) {
|
||||
|
|
@ -615,7 +642,6 @@ async function assignFormData(idEdit: string) {
|
|||
urlProfile.value = `${baseUrl}/user/${foundUser.id}/profile-image/${foundUser.selectedImage}`;
|
||||
|
||||
isEdit.value = true;
|
||||
profileSubmit.value = true;
|
||||
hqId.value && (await userStore.fetchBrOption(hqId.value));
|
||||
|
||||
await fetchImageList(foundUser.id, foundUser.selectedImage);
|
||||
|
|
@ -628,6 +654,31 @@ async function assignFormData(idEdit: string) {
|
|||
}
|
||||
}
|
||||
|
||||
async function fetchImageList(id: string, selectedName: string) {
|
||||
const res = await userStore.fetchImageListById(id);
|
||||
imageList.value = {
|
||||
selectedImage: selectedName,
|
||||
list: res.map((n: string) => `user/${id}/profile-image/${n}`),
|
||||
};
|
||||
return res;
|
||||
}
|
||||
|
||||
async function fetchUserList() {
|
||||
await userStore.fetchList({
|
||||
includeBranch: true,
|
||||
pageSize: pageSize.value,
|
||||
page: currentPage.value,
|
||||
query: !!inputSearch.value ? inputSearch.value : undefined,
|
||||
userType: currentTab.value === 'ALL' ? undefined : currentTab.value,
|
||||
status:
|
||||
statusFilter.value === 'all'
|
||||
? undefined
|
||||
: statusFilter.value === 'statusACTIVE'
|
||||
? 'ACTIVE'
|
||||
: 'INACTIVE',
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
utilsStore.currentTitle.title = 'personnel.title';
|
||||
utilsStore.currentTitle.path = [{ text: 'personnel.caption', i18n: true }];
|
||||
|
|
@ -649,24 +700,8 @@ onMounted(async () => {
|
|||
flowStore.rotate();
|
||||
});
|
||||
|
||||
function mapRole(value: string) {
|
||||
const option = userStore.userOption.roleOpts.find(
|
||||
(option) => option.value === value,
|
||||
);
|
||||
return option ? option.label : '-';
|
||||
}
|
||||
|
||||
async function fetchImageList(id: string, selectedName: string) {
|
||||
const res = await userStore.fetchImageListById(id);
|
||||
imageList.value = {
|
||||
selectedImage: selectedName,
|
||||
list: res.map((n: string) => `user/${id}/profile-image/${n}`),
|
||||
};
|
||||
return res;
|
||||
}
|
||||
|
||||
watch(
|
||||
() => selectorLabel.value,
|
||||
() => currentTab.value,
|
||||
async (label) => {
|
||||
mapUserType(label);
|
||||
await fetchUserList();
|
||||
|
|
@ -695,28 +730,6 @@ watch(
|
|||
},
|
||||
);
|
||||
|
||||
const currentPage = ref(1);
|
||||
const pageSize = ref(30);
|
||||
const currentMaxPage = computed(() =>
|
||||
userData.value ? Math.ceil(userData.value?.total / pageSize.value) : 1,
|
||||
);
|
||||
|
||||
async function fetchUserList() {
|
||||
await userStore.fetchList({
|
||||
includeBranch: true,
|
||||
pageSize: pageSize.value,
|
||||
page: currentPage.value,
|
||||
query: !!inputSearch.value ? inputSearch.value : undefined,
|
||||
userType: selectorLabel.value === 'ALL' ? undefined : selectorLabel.value,
|
||||
status:
|
||||
statusFilter.value === 'all'
|
||||
? undefined
|
||||
: statusFilter.value === 'statusACTIVE'
|
||||
? 'ACTIVE'
|
||||
: 'INACTIVE',
|
||||
});
|
||||
}
|
||||
|
||||
watch(
|
||||
() => profileFileImg.value,
|
||||
() => {
|
||||
|
|
@ -762,9 +775,9 @@ watch(
|
|||
"
|
||||
>
|
||||
{{
|
||||
selectorLabel === 'ALL'
|
||||
currentTab === 'ALL'
|
||||
? Object.values(typeStats).reduce((acc, val) => acc + val, 0)
|
||||
: typeStats[selectorLabel]
|
||||
: typeStats[currentTab]
|
||||
}}
|
||||
</q-badge>
|
||||
<q-btn
|
||||
|
|
@ -788,7 +801,7 @@ watch(
|
|||
v-if="typeStats && userData?.result"
|
||||
labelI18n
|
||||
:branch="
|
||||
selectorLabel === 'ALL'
|
||||
currentTab === 'ALL'
|
||||
? Object.entries(typeStats).map(([key, val]) => ({
|
||||
count: val,
|
||||
label: `personnel.${key}`,
|
||||
|
|
@ -805,15 +818,15 @@ watch(
|
|||
}))
|
||||
: [
|
||||
{
|
||||
label: `personnel.${selectorLabel}`,
|
||||
count: typeStats[selectorLabel],
|
||||
label: `personnel.${currentTab}`,
|
||||
count: typeStats[currentTab],
|
||||
icon: 'mdi-account-outline',
|
||||
color:
|
||||
selectorLabel === 'USER'
|
||||
currentTab === 'USER'
|
||||
? 'cyan'
|
||||
: selectorLabel === 'MESSENGER'
|
||||
: currentTab === 'MESSENGER'
|
||||
? 'yellow'
|
||||
: selectorLabel === 'DELEGATE'
|
||||
: currentTab === 'DELEGATE'
|
||||
? 'red'
|
||||
: 'magenta',
|
||||
},
|
||||
|
|
@ -952,7 +965,7 @@ watch(
|
|||
inline-label
|
||||
mobile-arrows
|
||||
dense
|
||||
v-model="selectorLabel"
|
||||
v-model="currentTab"
|
||||
align="left"
|
||||
class="full-width"
|
||||
active-color="info"
|
||||
|
|
@ -963,7 +976,7 @@ watch(
|
|||
async () => {
|
||||
currentPage = 1;
|
||||
inputSearch = '';
|
||||
selectorLabel = 'ALL';
|
||||
currentTab = 'ALL';
|
||||
statusFilter = 'all';
|
||||
flowStore.rotate();
|
||||
}
|
||||
|
|
@ -971,9 +984,7 @@ watch(
|
|||
>
|
||||
<div
|
||||
class="row text-capitalize"
|
||||
:class="
|
||||
selectorLabel === 'ALL' ? 'text-bold' : 'app-text-muted'
|
||||
"
|
||||
:class="currentTab === 'ALL' ? 'text-bold' : 'app-text-muted'"
|
||||
>
|
||||
{{ $t('general.all') }}
|
||||
</div>
|
||||
|
|
@ -982,7 +993,7 @@ watch(
|
|||
name="USER"
|
||||
@click="
|
||||
async () => {
|
||||
selectorLabel = 'USER';
|
||||
currentTab = 'USER';
|
||||
statusFilter = 'all';
|
||||
currentPage = 1;
|
||||
inputSearch = '';
|
||||
|
|
@ -992,9 +1003,7 @@ watch(
|
|||
>
|
||||
<div
|
||||
class="row text-capitalize"
|
||||
:class="
|
||||
selectorLabel === 'USER' ? 'text-bold' : 'app-text-muted'
|
||||
"
|
||||
:class="currentTab === 'USER' ? 'text-bold' : 'app-text-muted'"
|
||||
>
|
||||
{{ $t('personnel.USER') }}
|
||||
</div>
|
||||
|
|
@ -1003,7 +1012,7 @@ watch(
|
|||
name="MESSENGER"
|
||||
@click="
|
||||
async () => {
|
||||
selectorLabel = 'MESSENGER';
|
||||
currentTab = 'MESSENGER';
|
||||
statusFilter = 'all';
|
||||
currentPage = 1;
|
||||
inputSearch = '';
|
||||
|
|
@ -1014,7 +1023,7 @@ watch(
|
|||
<div
|
||||
class="row text-capitalize"
|
||||
:class="
|
||||
selectorLabel === 'MESSENGER' ? 'text-bold' : 'app-text-muted'
|
||||
currentTab === 'MESSENGER' ? 'text-bold' : 'app-text-muted'
|
||||
"
|
||||
>
|
||||
{{ $t('personnel.MESSENGER') }}
|
||||
|
|
@ -1024,7 +1033,7 @@ watch(
|
|||
name="DELEGATE"
|
||||
@click="
|
||||
async () => {
|
||||
selectorLabel = 'DELEGATE';
|
||||
currentTab = 'DELEGATE';
|
||||
statusFilter = 'all';
|
||||
currentPage = 1;
|
||||
inputSearch = '';
|
||||
|
|
@ -1035,7 +1044,7 @@ watch(
|
|||
<div
|
||||
class="row text-capitalize"
|
||||
:class="
|
||||
selectorLabel === 'DELEGATE' ? 'text-bold' : 'app-text-muted'
|
||||
currentTab === 'DELEGATE' ? 'text-bold' : 'app-text-muted'
|
||||
"
|
||||
>
|
||||
{{ $t('personnel.DELEGATE') }}
|
||||
|
|
@ -1045,7 +1054,7 @@ watch(
|
|||
name="AGENCY"
|
||||
@click="
|
||||
async () => {
|
||||
selectorLabel = 'AGENCY';
|
||||
currentTab = 'AGENCY';
|
||||
statusFilter = 'all';
|
||||
currentPage = 1;
|
||||
inputSearch = '';
|
||||
|
|
@ -1056,7 +1065,7 @@ watch(
|
|||
<div
|
||||
class="row text-capitalize"
|
||||
:class="
|
||||
selectorLabel === 'AGENCY' ? 'text-bold' : 'app-text-muted'
|
||||
currentTab === 'AGENCY' ? 'text-bold' : 'app-text-muted'
|
||||
"
|
||||
>
|
||||
{{ $t('personnel.AGENCY') }}
|
||||
|
|
@ -1186,7 +1195,7 @@ watch(
|
|||
<div class="column">
|
||||
<div class="col ellipsis" style="max-width: 20vw">
|
||||
{{
|
||||
$i18n.locale === 'eng'
|
||||
locale === 'eng'
|
||||
? `${props.row.firstNameEN} ${props.row.lastNameEN}`.trim()
|
||||
: `${props.row.firstName} ${props.row.lastName}`.trim()
|
||||
}}
|
||||
|
|
@ -1196,7 +1205,7 @@ watch(
|
|||
:delay="300"
|
||||
>
|
||||
{{
|
||||
$i18n.locale === 'eng'
|
||||
locale === 'eng'
|
||||
? `${props.row.firstNameEN} ${props.row.lastNameEN}`.trim()
|
||||
: `${props.row.firstName} ${props.row.lastName}`.trim()
|
||||
}}
|
||||
|
|
@ -1313,7 +1322,7 @@ watch(
|
|||
:data="{
|
||||
code: props.row.code,
|
||||
name:
|
||||
$i18n.locale === 'eng'
|
||||
locale === 'eng'
|
||||
? `${props.row.firstNameEN} ${props.row.lastNameEN}`.trim()
|
||||
: `${props.row.firstName} ${props.row.lastName}`.trim(),
|
||||
img: props.row.profileImageUrl
|
||||
|
|
@ -1501,7 +1510,7 @@ watch(
|
|||
hide-action
|
||||
:isEdit="infoDrawerEdit"
|
||||
:title="
|
||||
$i18n.locale === 'eng'
|
||||
locale === 'eng'
|
||||
? `${currentUser.firstNameEN} ${currentUser.lastNameEN}`
|
||||
: `${currentUser.firstName} ${currentUser.lastName}`
|
||||
"
|
||||
|
|
@ -1530,7 +1539,7 @@ watch(
|
|||
hideFade
|
||||
:menu="formMenuIcon"
|
||||
:toggleTitle="$t('status.title')"
|
||||
:title="`${$i18n.locale === 'eng' ? `${formData.firstNameEN} ${formData.lastNameEN}` : `${formData.firstName} ${formData.lastName}`}`"
|
||||
:title="`${locale === 'eng' ? `${formData.firstNameEN} ${formData.lastNameEN}` : `${formData.firstName} ${formData.lastName}`}`"
|
||||
:caption="userCode"
|
||||
:img="
|
||||
`${baseUrl}/user/${currentUser.id}/profile-image/${formData.selectedImage}`.concat(
|
||||
|
|
@ -1556,7 +1565,8 @@ watch(
|
|||
@edit="imageDialog = isImageEdit = true"
|
||||
@update:toggle-status="
|
||||
async (v) => {
|
||||
await triggerChangeStatus(infoPersonId, v);
|
||||
if (!currentUser) return;
|
||||
await triggerChangeStatus(currentUser.id, v);
|
||||
}
|
||||
"
|
||||
/>
|
||||
|
|
@ -1609,7 +1619,12 @@ watch(
|
|||
v-if="!infoDrawerEdit"
|
||||
id="btn-info-basic-delete"
|
||||
icon-only
|
||||
@click="() => onDelete(infoPersonId)"
|
||||
@click="
|
||||
() => {
|
||||
if (!currentUser) return;
|
||||
onDelete(currentUser.id);
|
||||
}
|
||||
"
|
||||
type="button"
|
||||
/>
|
||||
</div>
|
||||
|
|
@ -1692,6 +1707,9 @@ watch(
|
|||
v-model:email="formData.email"
|
||||
v-model:gender="formData.gender"
|
||||
v-model:birth-date="formData.birthDate"
|
||||
v-model:citizen-id="formData.citizenId"
|
||||
v-model:citizen-issue="formData.citizenIssue"
|
||||
v-model:citizen-expire="formData.citizenExpire"
|
||||
:title="'personnel.form.personalInformation'"
|
||||
prefix-id="drawer-info-personnel"
|
||||
dense
|
||||
|
|
@ -1705,6 +1723,12 @@ watch(
|
|||
id="info-address"
|
||||
v-model:address="formData.address"
|
||||
v-model:addressEN="formData.addressEN"
|
||||
v-model:moo="formData.moo"
|
||||
v-model:mooEN="formData.mooEN"
|
||||
v-model:soi="formData.soi"
|
||||
v-model:soiEN="formData.soiEN"
|
||||
v-model:street="formData.street"
|
||||
v-model:streetEN="formData.streetEN"
|
||||
v-model:provinceId="formData.provinceId"
|
||||
v-model:districtId="formData.districtId"
|
||||
v-model:subDistrictId="formData.subDistrictId"
|
||||
|
|
@ -1734,7 +1758,7 @@ watch(
|
|||
v-model:checkpointEN="formData.checkpointEN"
|
||||
v-model:agencyFile="agencyFile"
|
||||
v-model:agencyFileList="agencyFileList"
|
||||
v-model:userId="userId"
|
||||
v-model:userId="currentUser.id"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -1746,7 +1770,6 @@ watch(
|
|||
<DialogForm
|
||||
hideFooter
|
||||
removeDialog
|
||||
ref="formDialogRef"
|
||||
:badgeClass="formData.gender === 'male' ? 'app-bg-male' : 'app-bg-female'"
|
||||
:badgeLabel="userCode"
|
||||
:title="$t('personnel.addTitle')"
|
||||
|
|
@ -1774,7 +1797,7 @@ watch(
|
|||
}[formData.gender]
|
||||
"
|
||||
:toggleTitle="$t('status.title')"
|
||||
:title="`${$i18n.locale === 'eng' ? `${formData.firstNameEN} ${formData.lastNameEN}` : `${formData.firstName} ${formData.lastName}`}`"
|
||||
:title="`${locale === 'eng' ? `${formData.firstNameEN} ${formData.lastNameEN}` : `${formData.firstName} ${formData.lastName}`}`"
|
||||
:fallbackImg="
|
||||
{
|
||||
male: '/no-img-man.png',
|
||||
|
|
@ -1880,12 +1903,21 @@ watch(
|
|||
v-model:email="formData.email"
|
||||
v-model:gender="formData.gender"
|
||||
v-model:birth-date="formData.birthDate"
|
||||
v-model:citizen-id="formData.citizenId"
|
||||
v-model:citizen-issue="formData.citizenIssue"
|
||||
v-model:citizen-expire="formData.citizenExpire"
|
||||
class="q-mb-xl"
|
||||
/>
|
||||
<AddressForm
|
||||
id="dialog-form-address"
|
||||
v-model:address="formData.address"
|
||||
v-model:addressEN="formData.addressEN"
|
||||
v-model:moo="formData.moo"
|
||||
v-model:mooEN="formData.mooEN"
|
||||
v-model:soi="formData.soi"
|
||||
v-model:soiEN="formData.soiEN"
|
||||
v-model:street="formData.street"
|
||||
v-model:streetEN="formData.streetEN"
|
||||
v-model:provinceId="formData.provinceId"
|
||||
v-model:districtId="formData.districtId"
|
||||
v-model:subDistrictId="formData.subDistrictId"
|
||||
|
|
@ -1916,9 +1948,7 @@ watch(
|
|||
</div>
|
||||
</DialogForm>
|
||||
|
||||
<!-- :default-url="`${baseUrl}/user/${currentUser?.id}/image`" -->
|
||||
<ImageUploadDialog
|
||||
ref="refImageUpload"
|
||||
v-model:dialogState="imageDialog"
|
||||
v-model:file="profileFileImg"
|
||||
v-model:image-url="urlProfile"
|
||||
|
|
@ -1969,7 +1999,7 @@ watch(
|
|||
<span v-if="!modal" class="justify-center flex text-bold">
|
||||
{{ $t('general.image') }}
|
||||
{{
|
||||
$i18n.locale === 'eng'
|
||||
locale === 'eng'
|
||||
? `${formData.firstNameEN} ${formData.lastNameEN}`
|
||||
: `${formData.firstName} ${formData.lastName}`
|
||||
}}
|
||||
|
|
|
|||
|
|
@ -34,6 +34,12 @@ export type User = {
|
|||
provinceId: string | null;
|
||||
addressEN: string;
|
||||
address: string;
|
||||
mooEN: string;
|
||||
moo: string;
|
||||
soiEN: string;
|
||||
soi: string;
|
||||
streetEN: string;
|
||||
street: string;
|
||||
lastNameEN: string;
|
||||
lastName: string;
|
||||
middleNameEN?: string | null;
|
||||
|
|
@ -47,6 +53,9 @@ export type User = {
|
|||
responsibleArea: string;
|
||||
checkpoint?: string | null;
|
||||
checkpointEN?: string | null;
|
||||
citizenExpire?: Date | null;
|
||||
citizenIssue?: Date | null;
|
||||
citizenId: string;
|
||||
branch: Branch[];
|
||||
};
|
||||
|
||||
|
|
@ -62,6 +71,12 @@ export type UserCreate = {
|
|||
gender: string;
|
||||
addressEN: string;
|
||||
address: string;
|
||||
mooEN: string;
|
||||
moo: string;
|
||||
soiEN: string;
|
||||
soi: string;
|
||||
streetEN: string;
|
||||
street: string;
|
||||
trainingPlace?: string | null;
|
||||
importNationality?: string | null;
|
||||
sourceNationality?: string | null;
|
||||
|
|
@ -87,6 +102,9 @@ export type UserCreate = {
|
|||
responsibleArea?: string | null;
|
||||
checkpoint?: string | null;
|
||||
checkpointEN?: string | null;
|
||||
citizenExpire?: Date | null;
|
||||
citizenIssue?: Date | null;
|
||||
citizenId: string;
|
||||
};
|
||||
|
||||
export type UserAttachment = {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue