Merge remote-tracking branch 'forgejo/refactor/customer' into develop
Some checks failed
Spell Check / Spell Check with Typos (push) Failing after 5s

This commit is contained in:
puriphatt 2025-09-18 09:57:35 +07:00
commit 8cae967f50
26 changed files with 6447 additions and 5791 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 151 KiB

After

Width:  |  Height:  |  Size: 25 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 128 KiB

After

Width:  |  Height:  |  Size: 23 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 63 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 7.7 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 5.4 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 9.4 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 7.4 KiB

Before After
Before After

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -20,8 +20,8 @@ const issuePlace = defineModel<string>('issuePlace');
const issueCountry = defineModel<string>('issueCountry'); const issueCountry = defineModel<string>('issueCountry');
const issueDate = defineModel<Date | null | string>('issueDate'); const issueDate = defineModel<Date | null | string>('issueDate');
const type = defineModel<string>('type'); const type = defineModel<string>('type');
const expireDate = defineModel<Date>('expireDate'); const expireDate = defineModel<Date | string>('expireDate');
const birthDate = defineModel<Date>('birthDate'); const birthDate = defineModel<Date | string>('birthDate');
const workerStatus = defineModel<string>('workerStatus'); const workerStatus = defineModel<string>('workerStatus');
const nationality = defineModel<string>('nationality'); const nationality = defineModel<string>('nationality');
const gender = defineModel<string>('gender'); const gender = defineModel<string>('gender');

View file

@ -28,12 +28,12 @@ const arrivalAt = defineModel<string>('arrivalAt');
const arrivalTMNo = defineModel<string>('arrivalTmNo'); const arrivalTMNo = defineModel<string>('arrivalTmNo');
const arrivalTM = defineModel<string>('arrivalTm'); const arrivalTM = defineModel<string>('arrivalTm');
const mrz = defineModel<string>('mrz'); const mrz = defineModel<string>('mrz');
const entryCount = defineModel<number>('entryCount'); const entryCount = defineModel<number | string>('entryCount');
const issuePlace = defineModel<string>('issuePlace'); const issuePlace = defineModel<string>('issuePlace');
const issueCountry = defineModel<string>('issueCountry'); const issueCountry = defineModel<string>('issueCountry');
const issueDate = defineModel<Date | null | string>('visaIssueDate'); const issueDate = defineModel<Date | null | string>('visaIssueDate');
const type = defineModel<string>('type'); const type = defineModel<string>('type');
const expireDate = defineModel<Date>('expireDate'); const expireDate = defineModel<Date | string>('expireDate');
const remark = defineModel<string>('remark'); const remark = defineModel<string>('remark');
const workerType = defineModel<string>('workerType'); const workerType = defineModel<string>('workerType');
const number = defineModel<string>('number'); const number = defineModel<string>('number');

View file

@ -141,8 +141,9 @@ defineEmits<{
<q-avatar size="md"> <q-avatar size="md">
<q-img <q-img
:src=" :src="
`${baseUrl}/employee/${props.row.id}/image/${props.row.selectedImage}` || props.row.selectedImage
`/images/employee-avatar-${props.row.gender}.png` ? `${baseUrl}/employee/${props.row.id}/image/${props.row.selectedImage}`
: `/images/employee-avatar-${props.row.gender}.png`
" "
class="text-center" class="text-center"
:ratio="1" :ratio="1"
@ -295,9 +296,9 @@ defineEmits<{
$i18n.locale === 'eng' $i18n.locale === 'eng'
? `${props.row.firstNameEN} ${props.row.lastNameEN} `.trim() ? `${props.row.firstNameEN} ${props.row.lastNameEN} `.trim()
: `${props.row.firstName} ${props.row.lastName} `.trim(), : `${props.row.firstName} ${props.row.lastName} `.trim(),
img: img: props.row.selectedImage
`${baseUrl}/employee/${props.row.id}/image/${props.row.selectedImage}` || ? `${baseUrl}/employee/${props.row.id}/image/${props.row.selectedImage}`
`/images/employee-avatar-${props.row.gender}.png`, : `/images/employee-avatar-${props.row.gender}.png`,
fallbackImg: `/images/employee-avatar-${props.row.gender}.png`, fallbackImg: `/images/employee-avatar-${props.row.gender}.png`,
male: props.row.gender === 'male', male: props.row.gender === 'male',
female: props.row.gender === 'female', female: props.row.gender === 'female',

View file

@ -8,7 +8,7 @@ import {
UndoButton, UndoButton,
} from 'components/button'; } from 'components/button';
withDefaults( const props = withDefaults(
defineProps<{ defineProps<{
title: string; title: string;
category?: string; category?: string;
@ -42,6 +42,11 @@ const drawerOpen = defineModel<boolean>('drawerOpen', {
const myForm = ref(); const myForm = ref();
function reset() { function reset() {
if (props.beforeClose) {
drawerOpen.value = props.beforeClose
? props.beforeClose()
: !drawerOpen.value;
}
if (myForm.value) { if (myForm.value) {
myForm.value.resetValidation(); myForm.value.resetValidation();
} }
@ -62,7 +67,6 @@ async function onValidationError(ref: any) {
@show="show" @show="show"
@before-hide="reset" @before-hide="reset"
@hide="close" @hide="close"
@update:model-value="(v) => (drawerOpen = beforeClose ? beforeClose() : v)"
:width="$q.screen.gt.xs ? windowSize * 0.85 : windowSize" :width="$q.screen.gt.xs ? windowSize * 0.85 : windowSize"
v-model="drawerOpen" v-model="drawerOpen"
behavior="mobile" behavior="mobile"

View file

@ -16,3 +16,4 @@ export { default as SideMenu } from './SideMenu.vue';
export { default as StatCardComponent } from './StatCardComponent.vue'; export { default as StatCardComponent } from './StatCardComponent.vue';
export { default as TooltipComponent } from './TooltipComponent.vue'; export { default as TooltipComponent } from './TooltipComponent.vue';
export { default as TreeComponent } from './TreeComponent.vue'; export { default as TreeComponent } from './TreeComponent.vue';
export { default as PaginationPageSize } from './PaginationPageSize.vue';

View file

@ -45,9 +45,9 @@ const props = withDefaults(
readonly?: boolean; readonly?: boolean;
showTitle?: boolean; showTitle?: boolean;
ocr?: ( ocr?: (
group: any, group: string,
file: File, file: File,
) => void | Promise<{ ) => Promise<{
status: boolean; status: boolean;
group: string; group: string;
meta: { name: string; value: string }[]; meta: { name: string; value: string }[];

View file

@ -198,3 +198,10 @@ i.q-icon.mdi.mdi-chevron-down-circle.q-expansion-item__toggle-icon.q-expansion-i
.q-focus-helper { .q-focus-helper {
visibility: hidden; visibility: hidden;
} }
.clear-btn {
opacity: 0.6;
&:hover {
opacity: 1;
}
}

View file

@ -18,6 +18,8 @@ import { CustomerBranch, CustomerType } from 'stores/customer/types';
import { columnsEmployee } from './constant'; import { columnsEmployee } from './constant';
import { useCustomerBranchForm, useEmployeeForm } from './form'; import { useCustomerBranchForm, useEmployeeForm } from './form';
import DialogEmployee from 'src/components/03_customer-management/DialogEmployee.vue';
import DrawerEmployee from 'src/components/03_customer-management/DrawerEmployee.vue';
import EmployerFormAuthorized from './components/employer/EmployerFormAuthorized.vue'; import EmployerFormAuthorized from './components/employer/EmployerFormAuthorized.vue';
import FloatingActionButton from 'components/FloatingActionButton.vue'; import FloatingActionButton from 'components/FloatingActionButton.vue';
import SideMenu from 'components/SideMenu.vue'; import SideMenu from 'components/SideMenu.vue';
@ -89,6 +91,11 @@ const prop = withDefaults(
currentCitizenId?: string; currentCitizenId?: string;
gender: string; gender: string;
selectedImage: string; selectedImage: string;
fetchImageList: (
id: string,
selectedName: string,
type: 'customer' | 'employee',
) => Promise<void>;
}>(), }>(),
{ {
color: 'green', color: 'green',
@ -96,7 +103,6 @@ const prop = withDefaults(
); );
const currentBranchEmployee = ref<string>(''); const currentBranchEmployee = ref<string>('');
const listEmployee = ref<Employee[]>([]); const listEmployee = ref<Employee[]>([]);
const customerId = defineModel<string>('customerId', { required: true }); const customerId = defineModel<string>('customerId', { required: true });
defineEmits<{ defineEmits<{
@ -106,16 +112,6 @@ defineEmits<{
(e: 'dialog'): void; (e: 'dialog'): void;
}>(); }>();
onMounted(async () => {
customerBranchFormState.value.currentCustomerId = route.params
.customerId as string;
await fetchList();
branch.value?.forEach((v) => {
currentBtnOpen.value.push(false);
});
});
const columns = [ const columns = [
{ {
name: 'branchName', name: 'branchName',
@ -257,10 +253,6 @@ async function fetchEmployee(opts: { branchId: string; pageSize?: number }) {
} }
} }
onMounted(async () => {
await fetchList();
});
watch([customerId, inputSearch, currentStatus, pageSizeBranch], async () => { watch([customerId, inputSearch, currentStatus, pageSizeBranch], async () => {
await fetchList(); await fetchList();
}); });
@ -280,6 +272,16 @@ watch(
} }
}, },
); );
onMounted(async () => {
customerBranchFormState.value.currentCustomerId = route.params
.customerId as string;
await fetchList();
branch.value?.forEach((v) => {
currentBtnOpen.value.push(false);
});
});
</script> </script>
<template> <template>
@ -473,7 +475,6 @@ watch(
<q-tr <q-tr
:class="{ :class="{
'app-text-muted': props.row.status === 'INACTIVE', 'app-text-muted': props.row.status === 'INACTIVE',
'cursor-pointer': props.row._count?.branch !== 0,
}" }"
:props="props" :props="props"
@click="$emit('viewDetail', props.row, props.rowIndex)" @click="$emit('viewDetail', props.row, props.rowIndex)"
@ -547,7 +548,13 @@ watch(
v-if="branchFieldSelected.includes('businessTypePure')" v-if="branchFieldSelected.includes('businessTypePure')"
class="text-left" class="text-left"
> >
{{ useOptionStore().mapOption(props.row.businessType) || '-' }} {{
props.row.businessType
? props.row.businessType[
$i18n.locale === 'eng' ? 'nameEN' : 'name'
]
: '-'
}}
</q-td> </q-td>
<q-td <q-td
v-if="branchFieldSelected.includes('totalEmployee')" v-if="branchFieldSelected.includes('totalEmployee')"
@ -566,8 +573,6 @@ watch(
await fetchEmployee({ await fetchEmployee({
branchId: currentBranchEmployee, branchId: currentBranchEmployee,
pageSize: 999, pageSize: 999,
passport: true,
visa: true,
}); });
currentBtnOpen.map((v, i) => { currentBtnOpen.map((v, i) => {
@ -638,10 +643,15 @@ watch(
" "
@history="(item) => {}" @history="(item) => {}"
@view=" @view="
(item) => { async (item) => {
employeeFormState.drawerModal = true; employeeFormState.drawerModal = true;
//employeeFormState.isEmployeeEdit = true; //employeeFormState.isEmployeeEdit = true;
employeeFormStore.assignFormDataEmployee(item.id); employeeFormStore.assignFormDataEmployee(item.id);
await fetchImageList(
item.id,
item.selectedImage || '',
'employee',
);
} }
" "
/> />
@ -668,9 +678,11 @@ watch(
? `${props.row.addressEN || ''} ${props.row.subDistrict?.nameEN || ''} ${props.row.district?.nameEN || ''} ${props.row.province?.nameEN || ''}` ? `${props.row.addressEN || ''} ${props.row.subDistrict?.nameEN || ''} ${props.row.district?.nameEN || ''} ${props.row.province?.nameEN || ''}`
: `${props.row.address || ''} ${props.row.subDistrict?.name || ''} ${props.row.district?.name || ''} ${props.row.province?.name || ''}`, : `${props.row.address || ''} ${props.row.subDistrict?.name || ''} ${props.row.district?.name || ''} ${props.row.province?.name || ''}`,
telephone: props.row.telephoneNo, telephone: props.row.telephoneNo,
businessTypePure: useOptionStore().mapOption( businessTypePure: props.row.businessType
props.row.businessType, ? props.row.businessType[
), $i18n.locale === 'eng' ? 'nameEN' : 'name'
]
: '-',
totalEmployee: props.row._count?.employee, totalEmployee: props.row._count?.employee,
}" }"
:visible-columns="branchFieldSelected" :visible-columns="branchFieldSelected"
@ -885,15 +897,14 @@ watch(
</div> </div>
<EmployerFormBusiness <EmployerFormBusiness
dense dense
class="q-mb-xl"
outlined outlined
prefix-id="employer-branch" prefix-id="employer-branch"
:readonly="customerBranchFormState.dialogType === 'info'" :readonly="customerBranchFormState.dialogType === 'info'"
v-model:bussiness-type="customerBranchFormData.businessType" v-model:business-type-id="customerBranchFormData.businessTypeId"
v-model:job-position="customerBranchFormData.jobPosition" v-model:job-position="customerBranchFormData.jobPosition"
v-model:job-description="customerBranchFormData.jobDescription" v-model:job-description="customerBranchFormData.jobDescription"
v-model:pay-date="customerBranchFormData.payDate" v-model:pay-date="customerBranchFormData.payDate"
v-model:pay-date-e-n="customerBranchFormData.payDateEN" v-model:pay-date-en="customerBranchFormData.payDateEN"
v-model:wage-rate="customerBranchFormData.wageRate" v-model:wage-rate="customerBranchFormData.wageRate"
v-model:wage-rate-text="customerBranchFormData.wageRateText" v-model:wage-rate-text="customerBranchFormData.wageRateText"
/> />
@ -984,7 +995,7 @@ watch(
v-model:email="customerBranchFormData.email" v-model:email="customerBranchFormData.email"
v-model:contact-tel="customerBranchFormData.contactTel" v-model:contact-tel="customerBranchFormData.contactTel"
v-model:office-tel="customerBranchFormData.officeTel" v-model:office-tel="customerBranchFormData.officeTel"
v-model:agent="customerBranchFormData.agent" v-model:agent-user-id="customerBranchFormData.agentUserId"
/> />
</div> </div>
</div> </div>
@ -1000,6 +1011,18 @@ watch(
/> />
</template> </template>
</DialogFormContainer> </DialogFormContainer>
<DialogEmployee
:fetch-list-employee="fetchEmployee"
:fetch-image-list="fetchImageList"
current-tab="employer"
/>
<DrawerEmployee
:fetch-list-employee="fetchEmployee"
:fetch-image-list="fetchImageList"
current-tab="employer"
/>
</template> </template>
<style scoped> <style scoped>

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,514 @@
<script setup lang="ts">
import { computed, onMounted, ref, watch } from 'vue';
import { useQuasar } from 'quasar';
import { useI18n } from 'vue-i18n';
import { storeToRefs } from 'pinia';
import {
PaginationComponent,
PaginationPageSize,
ImageUploadDialog,
DialogForm,
NoData,
} from 'src/components';
import DialogEmployee from 'src/components/03_customer-management/DialogEmployee.vue';
import HistoryEditComponent from 'src/components/03_customer-management/HistoryEditComponent.vue';
import TableEmpoloyee from 'src/components/03_customer-management/TableEmpoloyee.vue';
import DrawerEmployee from 'src/components/03_customer-management/DrawerEmployee.vue';
import useFlowStore from 'src/stores/flow';
import useEmployeeStore from 'src/stores/employee';
import { Status } from 'src/stores/types';
import { Employee, EmployeeHistory } from 'src/stores/employee/types';
import { baseUrl, dialog, canAccess } from 'src/stores/utils';
import { calculateAge, toISOStringWithTimezone } from 'src/utils/datetime';
import { useCustomerForm, useEmployeeForm } from './form';
import { columnsEmployee } from './constant';
const $q = useQuasar();
const flowStore = useFlowStore();
const employeeStore = useEmployeeStore();
const employeeFormStore = useEmployeeForm();
const customerFormStore = useCustomerForm();
const { t } = useI18n();
const { state: employeeFormState, currentFromDataEmployee } =
storeToRefs(employeeFormStore);
const { deleteEmployeeById } = employeeFormStore;
const { state: customerFormState } = storeToRefs(customerFormStore);
const employeeStats = defineModel('employeeStats', { default: 0 });
const props = defineProps<{
currentTab: 'employee' | 'employer';
currentStatus: Status | 'All';
gridView: boolean;
inputSearch: string;
searchDate: string[];
fieldSelected: string[];
fetchImageList: (
id: string,
selectedName: string,
type: 'customer' | 'employee',
) => Promise<void>;
triggerChangeStatus: (
id: string,
status: string,
employeeName?: string,
) => void;
}>();
defineExpose({
openSpecificEmployee,
fetchListEmployee,
toggleStatusEmployee,
});
const listEmployee = ref<Employee[]>([]);
const currentPageEmployee = ref<number>(1);
const maxPageEmployee = ref<number>(1);
const pageSize = ref<number>(30);
const employeeHistoryDialog = ref(false);
const employeeHistory = ref<EmployeeHistory[]>();
const splitPercent = computed(() => ($q.screen.lt.md ? 0 : 15));
async function fetchListEmployee(opt?: {
fetchStats?: boolean;
page?: number;
pageSize?: number;
customerId?: string;
mobileFetch?: boolean;
}) {
const resultListEmployee = await employeeStore.fetchList({
customerId: opt?.customerId,
page: opt
? opt.mobileFetch
? 1
: opt.page || currentPageEmployee.value
: currentPageEmployee.value,
pageSize: opt
? opt.mobileFetch
? listEmployee.value.length +
(employeeStats.value === listEmployee.value.length ? 1 : 0)
: opt.pageSize || pageSize.value
: pageSize.value,
status:
props.currentStatus === 'All'
? undefined
: props.currentStatus === 'ACTIVE'
? 'ACTIVE'
: 'INACTIVE',
query: props.inputSearch,
passport: true,
visa: true,
startDate: props.searchDate[0],
endDate: props.searchDate[1],
});
if (resultListEmployee) {
maxPageEmployee.value = Math.ceil(
resultListEmployee.total / pageSize.value,
);
$q.screen.xs && !(opt && opt.mobileFetch)
? listEmployee.value.push(...resultListEmployee.result)
: (listEmployee.value = resultListEmployee.result);
}
if (opt && opt.fetchStats)
employeeStats.value = await employeeStore.getStatsEmployee();
}
async function openHistory(id: string) {
const res = await employeeStore.getEditHistory(id);
employeeHistory.value = res.reverse();
employeeHistoryDialog.value = true;
}
async function editEmployeeFormPersonal(id: string) {
await employeeFormStore.assignFormDataEmployee(id);
await props.fetchImageList(
id,
currentFromDataEmployee.value.selectedImage || '',
'employee',
);
employeeFormState.value.isEmployeeEdit = true;
employeeFormState.value.dialogType = 'edit';
employeeFormState.value.drawerModal = true;
}
async function openSpecificEmployee(id: string) {
await employeeFormStore.assignFormDataEmployee(id);
await props.fetchImageList(
id,
currentFromDataEmployee.value.selectedImage || '',
'employee',
);
employeeFormState.value.dialogType = 'info';
employeeFormState.value.drawerModal = true;
}
async function toggleStatusEmployee(
id: string,
status: boolean,
employeeName: string,
) {
const res = await employeeStore.editById(id, {
status: !status ? 'ACTIVE' : 'INACTIVE',
firstNameEN: employeeName,
});
if (res && employeeFormState.value.drawerModal) {
currentFromDataEmployee.value.status = res.status;
}
await employeeFormStore.assignFormDataEmployee(id);
await fetchListEmployee({ mobileFetch: $q.screen.xs });
flowStore.rotate();
}
watch(
() => [
props.inputSearch,
props.searchDate,
props.currentStatus,
pageSize.value,
],
async () => {
currentPageEmployee.value = 1;
listEmployee.value = [];
await fetchListEmployee({ fetchStats: true });
customerFormState.value.currentCustomerId = undefined;
flowStore.rotate();
},
);
watch(
() => employeeFormState.value.currentCustomerBranch,
(e) => {
if (!e) return;
if (employeeFormState.value.formDataEmployeeSameAddr) {
currentFromDataEmployee.value.address = e.address;
currentFromDataEmployee.value.addressEN = e.addressEN;
currentFromDataEmployee.value.provinceId = e.provinceId;
currentFromDataEmployee.value.districtId = e.districtId;
currentFromDataEmployee.value.subDistrictId = e.subDistrictId;
}
currentFromDataEmployee.value.customerBranchId = e.id;
},
);
watch(
() => employeeFormState.value.formDataEmployeeSameAddr,
(isSame) => {
if (!employeeFormState.value.currentCustomerBranch) return;
if (isSame) {
currentFromDataEmployee.value.address =
employeeFormState.value.currentCustomerBranch.address;
currentFromDataEmployee.value.addressEN =
employeeFormState.value.currentCustomerBranch.addressEN;
currentFromDataEmployee.value.provinceId =
employeeFormState.value.currentCustomerBranch.provinceId;
currentFromDataEmployee.value.districtId =
employeeFormState.value.currentCustomerBranch.districtId;
currentFromDataEmployee.value.subDistrictId =
employeeFormState.value.currentCustomerBranch.subDistrictId;
}
currentFromDataEmployee.value.customerBranchId =
employeeFormState.value.currentCustomerBranch.id;
},
);
watch(
() => currentFromDataEmployee.value.dateOfBirth,
(v) => {
const isEdit =
employeeFormState.value.drawerModal &&
employeeFormState.value.isEmployeeEdit;
let currentFormDate = v && toISOStringWithTimezone(new Date(v));
let currentDate: string = '';
if (isEdit && employeeFormState.value.currentEmployee) {
currentDate = toISOStringWithTimezone(
new Date(employeeFormState.value.currentEmployee.dateOfBirth),
);
}
if (
employeeFormState.value.dialogModal ||
(isEdit && currentFormDate !== currentDate)
) {
const age = calculateAge(
currentFromDataEmployee.value.dateOfBirth,
'year',
);
if (currentFromDataEmployee.value.dateOfBirth && Number(age) < 15) {
dialog({
color: 'warning',
icon: 'mdi-alert',
title: t('dialog.title.youngWorker15'),
cancelText: t('general.edit'),
persistent: true,
message: t('dialog.message.youngWorker15'),
cancel: async () => {
currentFromDataEmployee.value.dateOfBirth = null;
return;
},
});
}
if (
currentFromDataEmployee.value.dateOfBirth &&
Number(age) > 15 &&
Number(age) <= 18
) {
dialog({
color: 'warning',
icon: 'mdi-alert',
title: t('dialog.title.youngWorker18'),
cancelText: t('general.cancel'),
actionText: t('general.confirm'),
persistent: true,
message: t('dialog.message.youngWorker18'),
action: () => {},
cancel: async () => {
currentFromDataEmployee.value.dateOfBirth = null;
return;
},
});
}
}
},
);
watch(
() => currentFromDataEmployee.value.image,
() => {
if (currentFromDataEmployee.value.image !== null)
employeeFormState.value.isImageEdit = true;
},
);
onMounted(async () => {
currentPageEmployee.value = 1;
listEmployee.value = [];
await fetchListEmployee({ fetchStats: true });
});
</script>
<template>
<q-splitter
v-model="splitPercent"
:limits="[0, 100]"
class="col full-width"
before-class="overflow-hidden"
after-class="overflow-hidden"
:disable="$q.screen.lt.sm"
>
<template v-slot:before>
<div
class="column q-pa-md surface-1 full-height full-width"
style="gap: var(--size-1)"
>
<q-item
active
dense
active-class="employer-active"
class="no-padding items-center rounded full-width"
v-close-popup
clickable
>
<span class="q-px-md ellipsis">
{{ $t('general.all') }}
</span>
</q-item>
</div>
</template>
<template v-slot:after>
<div class="column full-height no-wrap">
<!-- employee -->
<template
v-if="listEmployee && employeeStats > 0 && currentTab === 'employee'"
>
<div
v-if="listEmployee.length === 0"
class="row col full-width items-center justify-center"
style="min-height: 250px"
>
<NoData :not-found="!!inputSearch" />
</div>
<article
v-if="listEmployee.length !== 0"
class="column scroll q-pa-md col"
>
<q-infinite-scroll
:offset="10"
@load="
(_, done) => {
if (
$q.screen.gt.xs ||
currentPageEmployee === maxPageEmployee
)
return;
currentPageEmployee = currentPageEmployee + 1;
fetchListEmployee().then(() =>
done(currentPageEmployee >= maxPageEmployee),
);
}
"
>
<TableEmpoloyee
:hide-delete="!canAccess('customer', 'edit')"
v-model:page-size="pageSize"
v-model:current-page="currentPageEmployee"
:grid-view="gridView"
:list-employee="listEmployee"
:columns-employee="columnsEmployee"
:field-selected="fieldSelected"
@history="
(item: any) => {
openHistory(item.id);
}
"
@view="
async (item: any) => {
employeeFormState.drawerModal = true;
employeeFormState.isEmployeeEdit = false;
employeeFormStore.assignFormDataEmployee(item.id);
await fetchImageList(
item.id,
item.selectedImage || '',
'employee',
);
}
"
@edit="(item: any) => editEmployeeFormPersonal(item.id)"
@delete="
(item: any) => {
deleteEmployeeById({
id: item.id,
fetch: async () =>
await fetchListEmployee(
currentTab === 'employer'
? {
page: 1,
pageSize: 999,
customerId: customerFormState.currentCustomerId,
}
: {
fetchStats: true,
mobileFetch: $q.screen.xs,
},
),
});
}
"
@toggle-status="
async (item: any) => {
triggerChangeStatus(item.id, item.status, item.firstNameEN);
}
"
/>
<template v-slot:loading>
<div
v-if="
$q.screen.lt.sm && currentPageEmployee !== maxPageEmployee
"
class="row justify-center"
>
<q-spinner-dots color="primary" size="40px" />
</div>
</template>
</q-infinite-scroll>
</article>
<footer
v-if="listEmployee.length !== 0 && $q.screen.gt.xs"
class="row justify-between items-center q-px-md q-py-sm"
>
<div class="row col-4 items-center">
<div
class="app-text-muted"
style="width: 80px"
v-if="$q.screen.gt.sm"
>
{{ $t('general.recordPerPage') }}
</div>
<div><PaginationPageSize v-model="pageSize" /></div>
</div>
<div class="col-4 flex justify-center app-text-muted">
{{
$q.screen.gt.sm
? $t('general.recordsPage', {
resultcurrentPage: listEmployee.length,
total: employeeStats,
})
: $t('general.ofPage', {
current: listEmployee.length,
total: employeeStats,
})
}}
</div>
<div class="col-4 flex justify-end">
<PaginationComponent
v-model:current-page="currentPageEmployee"
v-model:max-page="maxPageEmployee"
:fetch-data="
async () => {
await fetchListEmployee();
flowStore.rotate();
}
"
/>
</div>
</footer>
</template>
</div>
</template>
</q-splitter>
<!-- add employee -->
<DialogEmployee
:fetch-list-employee="fetchListEmployee"
:fetch-image-list="fetchImageList"
:current-tab="currentTab"
/>
<!-- กจาง edit employee -->
<DrawerEmployee
:fetch-list-employee="fetchListEmployee"
:fetch-image-list="fetchImageList"
:current-tab="currentTab"
@change-status="
(s) =>
triggerChangeStatus(
currentFromDataEmployee.id,
s,
currentFromDataEmployee.firstNameEN,
)
"
/>
<DialogForm
:title="$t('general.historyEdit')"
hide-footer
v-model:modal="employeeHistoryDialog"
>
<div class="q-pa-md">
<HistoryEditComponent
v-if="employeeHistory"
v-model:history-list="employeeHistory"
/>
</div>
</DialogForm>
</template>
<style scoped>
.employer-active {
background-color: hsla(var(--info-bg) / 0.1);
color: hsl(var(--info-bg));
}
</style>

View file

@ -336,6 +336,7 @@ watch(
(v) => (typeof v === 'string' ? (prefixName = v) : '') (v) => (typeof v === 'string' ? (prefixName = v) : '')
" "
@clear="prefixName = ''" @clear="prefixName = ''"
:rules="[(val: string) => !!val || $t('form.error.required')]"
> >
<template v-slot:no-option> <template v-slot:no-option>
<q-item> <q-item>

View file

@ -7,6 +7,8 @@ import { CustomerCreate } from 'stores/customer/types';
import EmployerFormAbout from './EmployerFormAbout.vue'; import EmployerFormAbout from './EmployerFormAbout.vue';
import EmployerFormAuthorized from './EmployerFormAuthorized.vue'; import EmployerFormAuthorized from './EmployerFormAuthorized.vue';
import { waitAll } from 'src/stores/utils'; import { waitAll } from 'src/stores/utils';
import FormEmployeePassport from 'src/components/03_customer-management/FormEmployeePassport.vue';
import FormEmployeeVisa from 'src/components/03_customer-management/FormEmployeeVisa.vue';
import { import {
FormCitizen, FormCitizen,
CorpFormBusinessRegistration, CorpFormBusinessRegistration,

View file

@ -9,8 +9,6 @@ const contactName = defineModel<string>('contactName');
const email = defineModel<string>('email'); const email = defineModel<string>('email');
const contactTel = defineModel<string>('contactTel'); const contactTel = defineModel<string>('contactTel');
const officeTel = defineModel<string>('officeTel'); const officeTel = defineModel<string>('officeTel');
const agent = defineModel<string>('agent');
const agentUserId = defineModel<string>('agentUserId'); const agentUserId = defineModel<string>('agentUserId');
</script> </script>
@ -109,7 +107,6 @@ const agentUserId = defineModel<string>('agentUserId');
/> />
</template> </template>
</q-input> </q-input>
<SelectAgent <SelectAgent
:label="$t('customer.form.agent')" :label="$t('customer.form.agent')"
v-model:value="agentUserId" v-model:value="agentUserId"

View file

@ -22,6 +22,10 @@ import { useRoute } from 'vue-router';
export const useCustomerForm = defineStore('form-customer', () => { export const useCustomerForm = defineStore('form-customer', () => {
const customerStore = useCustomerStore(); const customerStore = useCustomerStore();
const onCreateImageList = ref<{
selectedImage: string;
list: { url: string; imgFile: File | null; name: string }[];
}>({ selectedImage: '', list: [] });
const { t } = useI18n(); const { t } = useI18n();
const flowStore = useFlowStore(); const flowStore = useFlowStore();
@ -84,6 +88,7 @@ export const useCustomerForm = defineStore('form-customer', () => {
formDataOcr: Record<string, any>; formDataOcr: Record<string, any>;
isImageEdit: boolean; isImageEdit: boolean;
currentCustomerId?: string; currentCustomerId?: string;
imageList: { list: string[]; selectedImage: string };
}>({ }>({
dialogType: 'info', dialogType: 'info',
dialogOpen: false, dialogOpen: false,
@ -100,6 +105,7 @@ export const useCustomerForm = defineStore('form-customer', () => {
treeFile: [], treeFile: [],
formDataOcr: {}, formDataOcr: {},
isImageEdit: false, isImageEdit: false,
imageList: { list: [], selectedImage: '' },
}); });
watch( watch(
@ -497,6 +503,7 @@ export const useCustomerForm = defineStore('form-customer', () => {
} }
return { return {
onCreateImageList,
tabFieldRequired, tabFieldRequired,
registerAbleBranchOption, registerAbleBranchOption,
state, state,
@ -783,6 +790,7 @@ export const useEmployeeForm = defineStore('form-employee', () => {
} }
| undefined; | undefined;
ocr: boolean; ocr: boolean;
imageList: { list: string[]; selectedImage: string };
}>({ }>({
currentBranchId: '', currentBranchId: '',
isImageEdit: false, isImageEdit: false,
@ -807,6 +815,7 @@ export const useEmployeeForm = defineStore('form-employee', () => {
infoEmployeePersonCard: [], infoEmployeePersonCard: [],
formDataEmployeeOwner: undefined, formDataEmployeeOwner: undefined,
ocr: false, ocr: false,
imageList: { list: [], selectedImage: '' },
}); });
const defaultFormData: EmployeeCreate & { image?: File } = { const defaultFormData: EmployeeCreate & { image?: File } = {
@ -959,6 +968,7 @@ export const useEmployeeForm = defineStore('form-employee', () => {
state.value.currentIndexVisa = -1; state.value.currentIndexVisa = -1;
state.value.currentIndexCheckup = -1; state.value.currentIndexCheckup = -1;
state.value.currentIndexWorkHistory = -1; state.value.currentIndexWorkHistory = -1;
state.value.imageList = { list: [], selectedImage: '' };
// state.value.currentTab = 'personalInfo'; // state.value.currentTab = 'personalInfo';
if (clean) { if (clean) {
state.value.formDataEmployeeOwner = undefined; state.value.formDataEmployeeOwner = undefined;
@ -1388,12 +1398,10 @@ export const useEmployeeForm = defineStore('form-employee', () => {
statusSave: true, statusSave: true,
})), })),
), ),
employeeOtherInfo: structuredClone( employeeOtherInfo: structuredClone({
{ ...(payload.employeeOtherInfo ?? {}),
...payload.employeeOtherInfo, statusSave: true,
statusSave: !!payload.employeeOtherInfo?.id ? true : false, }),
} || {},
),
employeeWork: structuredClone( employeeWork: structuredClone(
payload.employeeWork?.length === 0 payload.employeeWork?.length === 0
? state.value.dialogModal ? state.value.dialogModal

View file

@ -115,6 +115,10 @@ const useCustomerStore = defineStore('api-customer', () => {
customerType?: CustomerType; customerType?: CustomerType;
startDate?: string; startDate?: string;
endDate?: string; endDate?: string;
businessType?: string;
province?: string;
district?: string;
subDistrict?: string;
}, },
Data extends Pagination< Data extends Pagination<
(Customer & (Customer &
@ -484,6 +488,10 @@ const useCustomerStore = defineStore('api-customer', () => {
activeBranchOnly?: boolean; activeBranchOnly?: boolean;
startDate?: string | Date; startDate?: string | Date;
endDate?: string | Date; endDate?: string | Date;
businessType?: string;
province?: string;
district?: string;
subDistrict?: string;
}) { }) {
let url = baseUrl + '/' + 'customer-export'; let url = baseUrl + '/' + 'customer-export';