jws-frontend/src/pages/03_customer-management/MainPage.vue

853 lines
26 KiB
Vue
Raw Normal View History

2024-04-22 13:42:02 +07:00
<script setup lang="ts">
import { ref, watch, onMounted, nextTick, reactive } from 'vue';
2024-08-02 16:13:07 +07:00
import { useI18n } from 'vue-i18n';
import { useQuasar } from 'quasar';
2024-08-05 15:09:36 +07:00
import { useRoute, useRouter } from 'vue-router';
2025-09-15 14:53:40 +07:00
import { canAccess } from 'stores/utils';
2024-08-02 16:13:07 +07:00
import type { District, Province, SubDistrict } from 'src/stores/address';
2024-08-02 13:58:44 +07:00
import useCustomerStore from 'stores/customer';
import useEmployeeStore from 'stores/employee';
import useAddressStore from 'src/stores/address';
import useFlowStore from 'stores/flow';
2025-09-15 14:53:40 +07:00
import { dialog } from 'stores/utils';
import { useNavigator } from 'src/stores/navigator';
2024-08-02 16:13:07 +07:00
import { Status } from 'stores/types';
2024-08-22 17:44:34 +07:00
import {
CustomerStats,
Customer,
CustomerBranch,
2024-12-10 15:55:01 +07:00
CustomerType,
2024-08-22 17:44:34 +07:00
} from 'stores/customer/types';
import { SaveButton } from 'components/button';
2025-09-12 17:59:03 +07:00
import TabCustomer from './TabCustomer.vue';
2024-10-30 09:34:46 +07:00
import FloatingActionButton from 'components/FloatingActionButton.vue';
2024-08-02 16:13:07 +07:00
import StatCardComponent from 'components/StatCardComponent.vue';
2024-08-09 15:09:46 +07:00
import BranchPage from './BranchPage.vue';
import SelectBusinessType from 'src/components/shared/select/SelectBusinessType.vue';
import AdvanceSearch from 'src/components/shared/AdvanceSearch.vue';
import TabEmployee from './TabEmployee.vue';
import SelectInput from 'src/components/shared/SelectInput.vue';
2024-08-26 18:04:04 +07:00
2025-09-15 14:53:40 +07:00
import { columnsCustomer, columnsEmployee } from './constant';
2024-08-05 16:48:19 +07:00
import { useCustomerForm, useEmployeeForm } from './form';
2024-08-05 10:15:06 +07:00
import { storeToRefs } from 'pinia';
2025-09-15 14:53:40 +07:00
const { t, locale } = useI18n();
2024-08-02 16:13:07 +07:00
const $q = useQuasar();
const route = useRoute();
const router = useRouter();
const flowStore = useFlowStore();
const navigatorStore = useNavigator();
const addressStore = useAddressStore();
2024-08-02 16:13:07 +07:00
const customerStore = useCustomerStore();
const employeeStore = useEmployeeStore();
2024-08-05 10:15:06 +07:00
const customerFormStore = useCustomerForm();
2024-08-05 16:48:19 +07:00
const employeeFormStore = useEmployeeForm();
2025-09-15 14:53:40 +07:00
const { state: customerFormState, currentFormData: customerFormData } =
storeToRefs(customerFormStore);
const { state: employeeFormState, statusEmployeeCreate } =
storeToRefs(employeeFormStore);
2024-08-02 16:13:07 +07:00
// NOTE: Component ref
2025-09-12 17:59:03 +07:00
const refTabCustomer = ref<InstanceType<typeof TabCustomer>>();
const refTabEmployee = ref<InstanceType<typeof TabEmployee>>();
2024-08-02 16:13:07 +07:00
2024-08-02 17:05:55 +07:00
// NOTE: Page Data
const currentCustomer = ref<Customer>();
// NOTE: Page State
const hideStats = ref(false);
const gridView = ref(false);
const employeeStats = ref(0);
const inputSearch = ref('');
const filterBusinessType = ref('');
const searchDate = ref<string[]>([]);
2024-08-02 16:38:49 +07:00
const currentTab = ref<'employer' | 'employee'>('employer');
const currentStatus = ref<Status | 'All'>('All');
2024-08-02 17:05:55 +07:00
const customerTypeSelected = ref<{
label: string;
value: 'all' | 'customerLegalEntity' | 'customerNaturalPerson';
}>({
2024-08-26 16:24:08 +07:00
label: t('general.all'),
value: 'all',
});
const statsCustomerType = ref<CustomerStats>({
CORP: 0,
PERS: 0,
});
const fieldDisplayCustomer = ref<
2024-07-08 11:21:00 +07:00
{
label: string;
value: string;
}[]
>(
columnsCustomer
.filter((v) => v.name !== 'action')
.map((v) => ({ label: v.label, value: v.name })),
);
const fieldDisplayEmployee = ref<
{
label: string;
value: string;
}[]
>(
columnsEmployee
.filter((v) => v.name !== 'action')
.map((v) => ({ label: v.label, value: v.name })),
);
const fieldSelected = ref<string[]>(
[
...columnsEmployee.map((v) => v.name),
...columnsCustomer.map((v) => v.name),
].filter((v, index, self) => self.indexOf(v) === index),
);
const filterAddress = reactive({
provinceId: '',
districtId: '',
subDistrictId: '',
});
const addressOpt = reactive<{
provinceOpt: Province[];
districtOpt: District[];
subDistrictOpt: SubDistrict[];
}>({
provinceOpt: [],
districtOpt: [],
subDistrictOpt: [],
});
2024-08-02 17:05:55 +07:00
const fieldCustomer = [
'all',
'customerLegalEntity',
'customerNaturalPerson',
2024-08-02 17:05:55 +07:00
] as const;
// image
const imageList = ref<{ selectedImage: string; list: string[] }>();
const branch = ref<CustomerBranch[]>();
2025-07-07 11:19:57 +07:00
async function triggerChangeStatus(
id: string,
status: string,
employeeName?: string,
) {
2024-07-25 10:49:43 +00:00
return await new Promise((resolve, reject) => {
dialog({
color: status !== 'INACTIVE' ? 'warning' : 'info',
2024-08-09 06:18:26 +00:00
icon:
status !== 'INACTIVE' ? 'mdi-alert' : 'mdi-message-processing-outline',
2024-08-27 11:46:26 +07:00
title: t('dialog.title.confirmChangeStatus'),
2024-07-25 10:49:43 +00:00
actionText:
2024-08-27 11:46:26 +07:00
status !== 'INACTIVE' ? t('general.close') : t('general.open'),
2024-07-25 10:49:43 +00:00
message:
status !== 'INACTIVE'
2024-08-27 11:46:26 +07:00
? t('dialog.message.confirmChangeStatusOff')
: t('dialog.message.confirmChangeStatusOn'),
2024-07-25 10:49:43 +00:00
action: async () => {
if (currentTab.value === 'employee') {
2025-09-12 17:59:03 +07:00
await refTabEmployee.value
?.toggleStatusEmployee(
id,
status === 'INACTIVE' ? false : true,
employeeName,
)
2024-07-25 10:49:43 +00:00
.then(resolve)
.catch(reject);
} else {
2025-09-12 17:59:03 +07:00
await refTabCustomer.value
?.toggleStatusCustomer(id, status === 'INACTIVE' ? false : true)
2024-07-25 10:49:43 +00:00
.then(resolve)
.catch(reject);
}
},
cancel: () => {},
});
});
}
2024-09-16 14:38:04 +07:00
async function createCustomerForm(customerType: 'CORP' | 'PERS') {
2024-08-05 15:29:11 +07:00
customerFormState.value.dialogModal = true;
customerFormState.value.dialogType = 'create';
2024-12-10 15:55:01 +07:00
customerFormData.value.customerType =
customerType === 'CORP' ? CustomerType.Corporate : CustomerType.Person;
2024-08-05 15:29:11 +07:00
}
2024-08-06 03:14:31 +00:00
function createEmployeeForm() {
employeeFormStore.resetFormDataEmployee(true);
employeeFormState.value.dialogType = 'create';
2024-08-06 03:14:31 +00:00
employeeFormState.value.dialogModal = true;
2024-08-15 11:53:12 +07:00
employeeFormState.value.isEmployeeEdit = true;
2024-08-06 03:14:31 +00:00
}
2024-09-11 16:53:08 +07:00
async function fetchImageList(
id: string,
selectedName: string,
type: 'customer' | 'employee',
) {
const res =
type === 'customer'
? await customerStore.fetchImageListById(id)
: await employeeStore.fetchImageListById(id);
Squashed commit of the following: commit eb6c7b164a9f182f8d1ce73cc5354866c6d6b10e Author: puriphatt <puriphat@frappet.com> Date: Wed Sep 11 11:29:44 2024 +0700 refactor: no img close to default on create commit eae9eb26071cc2985624bb1c6ce551bf5eb6eb8b Author: puriphatt <puriphat@frappet.com> Date: Wed Sep 11 11:04:04 2024 +0700 refactor/feat: save => apply, disabled selected img, no img close to default commit ccbf80fc53db3144873c049bd6dbd37b4e2e9ff3 Author: puriphatt <puriphat@frappet.com> Date: Wed Sep 11 09:31:32 2024 +0700 fix(01): use submit function commit 36b4f6ca15e5966f37dfefc9fdb744feec60dd27 Author: puriphatt <puriphat@frappet.com> Date: Tue Sep 10 17:45:19 2024 +0700 fix: imgList error commit bac0eaf3ab955672ae0c78d3295b4a839827c5f2 Author: puriphatt <puriphat@frappet.com> Date: Tue Sep 10 17:18:03 2024 +0700 refactor(03): customer new upload img dialog commit 9d7398e9613a738c33e265482cdb7d7bb250ea9f Author: puriphatt <puriphat@frappet.com> Date: Tue Sep 10 15:40:39 2024 +0700 refactor(02): new upload dialog commit 8b91d43f41eae3ba2442f6c742d617c25ee180cb Author: puriphatt <puriphat@frappet.com> Date: Tue Sep 10 15:25:21 2024 +0700 refactor(01): new upload dialog, confirm remove, individual action commit 61caf1919168bc5635568d7ca246574fdc43cd04 Author: puriphatt <puriphat@frappet.com> Date: Mon Sep 9 17:08:42 2024 +0700 refactor(01): branch new img upload commit e791b7316d001d839c8afb1950f7331c62d9e81a Author: puriphatt <puriphat@frappet.com> Date: Mon Sep 9 17:08:42 2024 +0700 refactor(02): personnel new img upload commit af4d11312b9cb666338901efa9971117cb7738c4 Author: puriphatt <puriphat@frappet.com> Date: Mon Sep 9 17:08:42 2024 +0700 feat(02): new image upload commit e4d7afdb8c74d65a550644f2c60f70909d51d4a8 Author: puriphatt <puriphat@frappet.com> Date: Mon Sep 9 17:08:41 2024 +0700 refactor: mock select image function commit 5ab3f045b9c7d2c821920c12114da15eed09655a Author: puriphatt <puriphat@frappet.com> Date: Mon Sep 9 17:08:41 2024 +0700 refactor: mock new image preview
2024-09-11 16:43:41 +07:00
imageList.value = {
selectedImage: selectedName,
2024-09-11 16:53:08 +07:00
list: res.map((n: string) => `${type}/${id}/image/${n}`),
Squashed commit of the following: commit eb6c7b164a9f182f8d1ce73cc5354866c6d6b10e Author: puriphatt <puriphat@frappet.com> Date: Wed Sep 11 11:29:44 2024 +0700 refactor: no img close to default on create commit eae9eb26071cc2985624bb1c6ce551bf5eb6eb8b Author: puriphatt <puriphat@frappet.com> Date: Wed Sep 11 11:04:04 2024 +0700 refactor/feat: save => apply, disabled selected img, no img close to default commit ccbf80fc53db3144873c049bd6dbd37b4e2e9ff3 Author: puriphatt <puriphat@frappet.com> Date: Wed Sep 11 09:31:32 2024 +0700 fix(01): use submit function commit 36b4f6ca15e5966f37dfefc9fdb744feec60dd27 Author: puriphatt <puriphat@frappet.com> Date: Tue Sep 10 17:45:19 2024 +0700 fix: imgList error commit bac0eaf3ab955672ae0c78d3295b4a839827c5f2 Author: puriphatt <puriphat@frappet.com> Date: Tue Sep 10 17:18:03 2024 +0700 refactor(03): customer new upload img dialog commit 9d7398e9613a738c33e265482cdb7d7bb250ea9f Author: puriphatt <puriphat@frappet.com> Date: Tue Sep 10 15:40:39 2024 +0700 refactor(02): new upload dialog commit 8b91d43f41eae3ba2442f6c742d617c25ee180cb Author: puriphatt <puriphat@frappet.com> Date: Tue Sep 10 15:25:21 2024 +0700 refactor(01): new upload dialog, confirm remove, individual action commit 61caf1919168bc5635568d7ca246574fdc43cd04 Author: puriphatt <puriphat@frappet.com> Date: Mon Sep 9 17:08:42 2024 +0700 refactor(01): branch new img upload commit e791b7316d001d839c8afb1950f7331c62d9e81a Author: puriphatt <puriphat@frappet.com> Date: Mon Sep 9 17:08:42 2024 +0700 refactor(02): personnel new img upload commit af4d11312b9cb666338901efa9971117cb7738c4 Author: puriphatt <puriphat@frappet.com> Date: Mon Sep 9 17:08:42 2024 +0700 feat(02): new image upload commit e4d7afdb8c74d65a550644f2c60f70909d51d4a8 Author: puriphatt <puriphat@frappet.com> Date: Mon Sep 9 17:08:41 2024 +0700 refactor: mock select image function commit 5ab3f045b9c7d2c821920c12114da15eed09655a Author: puriphatt <puriphat@frappet.com> Date: Mon Sep 9 17:08:41 2024 +0700 refactor: mock new image preview
2024-09-11 16:43:41 +07:00
};
2024-09-11 16:53:08 +07:00
Squashed commit of the following: commit eb6c7b164a9f182f8d1ce73cc5354866c6d6b10e Author: puriphatt <puriphat@frappet.com> Date: Wed Sep 11 11:29:44 2024 +0700 refactor: no img close to default on create commit eae9eb26071cc2985624bb1c6ce551bf5eb6eb8b Author: puriphatt <puriphat@frappet.com> Date: Wed Sep 11 11:04:04 2024 +0700 refactor/feat: save => apply, disabled selected img, no img close to default commit ccbf80fc53db3144873c049bd6dbd37b4e2e9ff3 Author: puriphatt <puriphat@frappet.com> Date: Wed Sep 11 09:31:32 2024 +0700 fix(01): use submit function commit 36b4f6ca15e5966f37dfefc9fdb744feec60dd27 Author: puriphatt <puriphat@frappet.com> Date: Tue Sep 10 17:45:19 2024 +0700 fix: imgList error commit bac0eaf3ab955672ae0c78d3295b4a839827c5f2 Author: puriphatt <puriphat@frappet.com> Date: Tue Sep 10 17:18:03 2024 +0700 refactor(03): customer new upload img dialog commit 9d7398e9613a738c33e265482cdb7d7bb250ea9f Author: puriphatt <puriphat@frappet.com> Date: Tue Sep 10 15:40:39 2024 +0700 refactor(02): new upload dialog commit 8b91d43f41eae3ba2442f6c742d617c25ee180cb Author: puriphatt <puriphat@frappet.com> Date: Tue Sep 10 15:25:21 2024 +0700 refactor(01): new upload dialog, confirm remove, individual action commit 61caf1919168bc5635568d7ca246574fdc43cd04 Author: puriphatt <puriphat@frappet.com> Date: Mon Sep 9 17:08:42 2024 +0700 refactor(01): branch new img upload commit e791b7316d001d839c8afb1950f7331c62d9e81a Author: puriphatt <puriphat@frappet.com> Date: Mon Sep 9 17:08:42 2024 +0700 refactor(02): personnel new img upload commit af4d11312b9cb666338901efa9971117cb7738c4 Author: puriphatt <puriphat@frappet.com> Date: Mon Sep 9 17:08:42 2024 +0700 feat(02): new image upload commit e4d7afdb8c74d65a550644f2c60f70909d51d4a8 Author: puriphatt <puriphat@frappet.com> Date: Mon Sep 9 17:08:41 2024 +0700 refactor: mock select image function commit 5ab3f045b9c7d2c821920c12114da15eed09655a Author: puriphatt <puriphat@frappet.com> Date: Mon Sep 9 17:08:41 2024 +0700 refactor: mock new image preview
2024-09-11 16:43:41 +07:00
return res;
}
2025-09-01 14:53:52 +07:00
async function triggerExport() {
switch (currentTab.value) {
case 'employer':
customerStore.customerExport({
pageSize: 10000,
startDate: searchDate.value[0],
endDate: searchDate.value[1],
businessType: filterBusinessType.value,
province: filterAddress.provinceId || undefined,
district: filterAddress.districtId || undefined,
subDistrict: filterAddress.subDistrictId || undefined,
customerType:
customerTypeSelected.value.value === 'customerLegalEntity'
? CustomerType.Corporate
: customerTypeSelected.value.value === 'customerNaturalPerson'
? CustomerType.Person
: undefined,
});
2025-09-01 14:53:52 +07:00
break;
case 'employee':
employeeStore.employeeExport({
pageSize: 10000,
startDate: searchDate.value[0],
endDate: searchDate.value[1],
});
2025-09-01 14:53:52 +07:00
break;
}
}
async function fetchAddressData(type: 'province' | 'district' | 'subDistrict') {
let result;
if (type === 'province') {
result = await addressStore.fetchProvince();
if (result) addressOpt.provinceOpt = result;
}
if (type === 'district') {
if (!filterAddress.provinceId) return;
result = await addressStore.fetchDistrictByProvinceId(
filterAddress.provinceId,
);
if (result) addressOpt.districtOpt = result;
}
if (type === 'subDistrict') {
if (!filterAddress.districtId) return;
result = await addressStore.fetchSubDistrictByProvinceId(
filterAddress.districtId,
);
if (result) addressOpt.subDistrictOpt = result;
}
return result;
}
async function handleSelectAddress(type: 'province' | 'district') {
if (type === 'province') {
filterAddress.districtId = filterAddress.subDistrictId = '';
addressOpt.districtOpt = addressOpt.subDistrictOpt = [];
await fetchAddressData('district');
}
if (type === 'district') {
filterAddress.subDistrictId = '';
await fetchAddressData('subDistrict');
}
}
2025-09-12 17:59:03 +07:00
async function init() {
navigatorStore.current.title = 'menu.customer';
navigatorStore.current.path = [
{
text: 'menu.customerCaption',
i18n: true,
handler: () => router.push('/customer-management'),
},
];
2024-08-06 16:33:17 +07:00
2025-09-12 17:59:03 +07:00
gridView.value = $q.screen.lt.md ? true : false;
2025-09-12 17:59:03 +07:00
if (route.query.tab === 'customer') {
currentTab.value = 'employer';
if (route.query.id)
refTabCustomer.value?.openSpecificCustomer(route.query.id as string);
} else if (route.query.tab === 'employee') {
currentTab.value = 'employee';
if (route.query.id)
refTabEmployee.value?.openSpecificEmployee(route.query.id as string);
}
2024-08-13 20:55:13 +07:00
2025-09-12 17:59:03 +07:00
if (
route.name === 'CustomerBranchManagement' &&
typeof route.params.customerId === 'string'
) {
const _data = await customerStore.fetchById(route.params.customerId);
2024-08-28 13:49:57 +07:00
2025-09-12 17:59:03 +07:00
if (_data) {
currentCustomer.value = _data;
navigatorStore.current.path.push({
text: `${
_data.customerType === 'CORP'
? _data.branch[0].registerName
: locale.value === 'eng'
? _data.branch[0].firstNameEN + ' ' + _data.branch[0].lastNameEN
: _data.branch[0].firstName + ' ' + _data.branch[0].lastName
}`,
i18n: false,
});
} else {
router.push('/customer-management');
2024-08-28 13:49:57 +07:00
}
2025-09-12 17:59:03 +07:00
}
2024-08-28 13:49:57 +07:00
await fetchAddressData('province');
2025-09-12 17:59:03 +07:00
flowStore.rotate();
}
2024-09-26 14:01:41 +07:00
2025-09-12 17:59:03 +07:00
watch(() => route.name, init);
2024-11-22 14:09:10 +07:00
2025-09-12 17:59:03 +07:00
watch(locale, () => {
customerTypeSelected.value = {
label: `${customerTypeSelected.value.label}`,
value: customerTypeSelected.value?.value,
};
});
watch(
2025-09-12 17:59:03 +07:00
() => $q.screen.lt.md,
() => $q.screen.lt.md && (gridView.value = true),
Squashed commit of the following: commit eb6c7b164a9f182f8d1ce73cc5354866c6d6b10e Author: puriphatt <puriphat@frappet.com> Date: Wed Sep 11 11:29:44 2024 +0700 refactor: no img close to default on create commit eae9eb26071cc2985624bb1c6ce551bf5eb6eb8b Author: puriphatt <puriphat@frappet.com> Date: Wed Sep 11 11:04:04 2024 +0700 refactor/feat: save => apply, disabled selected img, no img close to default commit ccbf80fc53db3144873c049bd6dbd37b4e2e9ff3 Author: puriphatt <puriphat@frappet.com> Date: Wed Sep 11 09:31:32 2024 +0700 fix(01): use submit function commit 36b4f6ca15e5966f37dfefc9fdb744feec60dd27 Author: puriphatt <puriphat@frappet.com> Date: Tue Sep 10 17:45:19 2024 +0700 fix: imgList error commit bac0eaf3ab955672ae0c78d3295b4a839827c5f2 Author: puriphatt <puriphat@frappet.com> Date: Tue Sep 10 17:18:03 2024 +0700 refactor(03): customer new upload img dialog commit 9d7398e9613a738c33e265482cdb7d7bb250ea9f Author: puriphatt <puriphat@frappet.com> Date: Tue Sep 10 15:40:39 2024 +0700 refactor(02): new upload dialog commit 8b91d43f41eae3ba2442f6c742d617c25ee180cb Author: puriphatt <puriphat@frappet.com> Date: Tue Sep 10 15:25:21 2024 +0700 refactor(01): new upload dialog, confirm remove, individual action commit 61caf1919168bc5635568d7ca246574fdc43cd04 Author: puriphatt <puriphat@frappet.com> Date: Mon Sep 9 17:08:42 2024 +0700 refactor(01): branch new img upload commit e791b7316d001d839c8afb1950f7331c62d9e81a Author: puriphatt <puriphat@frappet.com> Date: Mon Sep 9 17:08:42 2024 +0700 refactor(02): personnel new img upload commit af4d11312b9cb666338901efa9971117cb7738c4 Author: puriphatt <puriphat@frappet.com> Date: Mon Sep 9 17:08:42 2024 +0700 feat(02): new image upload commit e4d7afdb8c74d65a550644f2c60f70909d51d4a8 Author: puriphatt <puriphat@frappet.com> Date: Mon Sep 9 17:08:41 2024 +0700 refactor: mock select image function commit 5ab3f045b9c7d2c821920c12114da15eed09655a Author: puriphatt <puriphat@frappet.com> Date: Mon Sep 9 17:08:41 2024 +0700 refactor: mock new image preview
2024-09-11 16:43:41 +07:00
);
2025-09-12 17:59:03 +07:00
onMounted(async () => await init());
2024-04-22 13:42:02 +07:00
</script>
<template>
2024-10-30 09:34:46 +07:00
<FloatingActionButton
2024-08-09 17:58:35 +07:00
style="z-index: 999"
2024-10-22 10:51:44 +07:00
:hide-icon="currentTab === 'employee'"
v-if="$route.name === 'CustomerManagement' && canAccess('customer', 'edit')"
2024-10-22 10:51:44 +07:00
@click="
() => {
if (currentTab === 'employee') {
createEmployeeForm();
}
}
"
2024-08-09 17:58:35 +07:00
>
<q-fab-action
v-if="currentTab === 'employer'"
id="add-customer-legal-entity"
style="color: white; background-color: hsla(var(--violet-11-hsl))"
@click="createCustomerForm('CORP')"
padding="xs"
icon="mdi-office-building-outline"
2024-08-26 16:24:08 +07:00
:label="$t('general.add') + ' ' + $t('customer.employerLegalEntity')"
2024-08-09 17:58:35 +07:00
external-label
label-position="left"
/>
<q-fab-action
v-if="currentTab === 'employer'"
id="add-customer-natural-person"
2024-08-26 16:24:08 +07:00
:label="$t('general.add') + ' ' + $t('customer.employerNaturalPerson')"
2024-08-09 17:58:35 +07:00
external-label
label-position="left"
@click="createCustomerForm('PERS')"
style="color: white; background-color: hsla(var(--teal-10-hsl))"
padding="xs"
icon="mdi-account-plus-outline"
/>
2024-10-30 09:34:46 +07:00
</FloatingActionButton>
2024-07-02 09:43:06 +00:00
<div class="column full-height no-wrap">
2025-09-12 17:59:03 +07:00
<!-- หน table -->
<div
v-show="$route.name === 'CustomerManagement'"
class="column full-height"
>
2024-07-05 09:29:46 +00:00
<div class="text-body-2 text-weight-medium q-mb-xs flex items-center">
2024-08-26 16:24:08 +07:00
{{ $t('general.dataSum') }}
2024-07-05 09:29:46 +00:00
<q-badge
rounded
class="q-ml-sm"
style="
background-color: hsla(var(--info-bg) / 0.15);
color: hsl(var(--info-bg));
"
>
{{
2024-08-02 16:38:49 +07:00
currentTab === 'employer'
? statsCustomerType.PERS + statsCustomerType.CORP
: employeeStats
2024-07-05 09:29:46 +00:00
}}
</q-badge>
2024-07-05 03:53:08 +00:00
<q-btn
2024-07-05 11:01:53 +00:00
class="q-ml-sm"
icon="mdi-pin-outline"
2024-07-05 03:53:08 +00:00
color="primary"
size="sm"
flat
dense
rounded
2024-08-02 17:05:55 +07:00
@click="hideStats = !hideStats"
:style="hideStats ? 'rotate: 90deg' : ''"
2024-07-05 03:53:08 +00:00
style="transition: 0.1s ease-in-out"
/>
</div>
2025-09-12 17:59:03 +07:00
<!-- stat -->
2024-07-04 13:14:21 +07:00
<div class="row full-width">
2024-07-05 03:53:08 +00:00
<transition name="slide">
2024-08-02 17:05:55 +07:00
<div v-if="!hideStats" class="col-12 q-mb-md">
2024-07-05 03:53:08 +00:00
<div class="scroll">
<StatCardComponent
v-if="statsCustomerType"
2024-10-02 14:04:23 +07:00
label-i18n
2024-07-05 03:53:08 +00:00
:branch="
currentTab === 'employer'
? [
{
count: statsCustomerType.CORP ?? 0,
label: 'customer.employerLegalEntity',
icon: 'mdi-office-building-outline',
color: 'purple',
},
{
count: statsCustomerType.PERS ?? 0,
label: 'customer.employerNaturalPerson',
icon: 'mdi-account-outline',
color: 'green',
},
]
2024-07-05 03:53:08 +00:00
: [
{
2024-08-26 16:24:08 +07:00
label: 'customer.employee',
2024-08-02 17:05:55 +07:00
count: employeeStats,
icon: 'mdi-account-outline',
2024-07-05 03:53:08 +00:00
color: 'pink',
},
]
2024-07-05 03:53:08 +00:00
"
:dark="$q.dark.isActive"
/>
</div>
</div>
2024-07-05 03:53:08 +00:00
</transition>
</div>
2024-04-22 13:42:02 +07:00
<!-- main -->
2024-07-08 14:08:57 +07:00
<div
class="surface-2 bordered rounded col column full-width overflow-hidden"
2024-07-08 14:08:57 +07:00
>
2024-07-05 09:29:46 +00:00
<!-- tabs -->
<div class="column">
2024-07-17 08:31:31 +00:00
<div
class="row q-px-md q-py-sm justify-between full-width surface-3 bordered-b"
>
2024-07-17 07:38:44 +00:00
<q-input
for="input-search"
outlined
dense
2024-08-26 16:24:08 +07:00
:label="$t('general.search')"
2025-09-01 14:53:52 +07:00
class="col col-md-3 q-mr-sm"
2024-07-17 07:38:44 +00:00
:bg-color="$q.dark.isActive ? 'dark' : 'white'"
v-model="inputSearch"
debounce="200"
>
<template #prepend>
<q-icon name="mdi-magnify" />
</template>
<template v-slot:append>
<q-separator vertical inset class="q-mr-xs" />
<AdvanceSearch
v-model="searchDate"
:active="
($q.screen.lt.md && currentStatus !== 'All') ||
!!filterBusinessType ||
!!filterAddress.provinceId
"
>
<div
v-if="$q.screen.lt.md"
class="q-mt-sm text-weight-medium"
>
{{ $t('general.status') }}
</div>
<q-select
v-if="$q.screen.lt.md"
id="select-status"
for="select-status"
v-model="currentStatus"
outlined
dense
autocomplete="off"
option-value="value"
option-label="label"
map-options
emit-value
:options="[
{ label: $t('general.all'), value: 'All' },
{ label: $t('status.ACTIVE'), value: 'ACTIVE' },
{ label: $t('status.INACTIVE'), value: 'INACTIVE' },
]"
/>
<template v-if="currentTab === 'employer'">
<div class="q-mt-sm text-weight-medium">
{{ $t('customer.form.businessType') }}
</div>
<SelectBusinessType
v-model:value="filterBusinessType"
clearable
/>
<div class="q-mt-sm text-weight-medium">
{{ $t('customer.form.address') }}
</div>
<SelectInput
clearable
v-model="filterAddress.provinceId"
option-value="id"
:label="$t('form.province')"
:option="addressOpt.provinceOpt"
:option-label="locale === 'eng' ? 'nameEN' : 'name'"
@update:model-value="handleSelectAddress('province')"
/>
<SelectInput
clearable
class="q-mt-sm"
option-value="id"
v-model="filterAddress.districtId"
:option="addressOpt.districtOpt"
:label="$t('form.district')"
:option-label="locale === 'eng' ? 'nameEN' : 'name'"
@update:model-value="handleSelectAddress('district')"
/>
<SelectInput
clearable
class="q-mt-sm"
option-value="id"
v-model="filterAddress.subDistrictId"
:option="addressOpt.subDistrictOpt"
:label="$t('form.subDistrict')"
:option-label="locale === 'eng' ? 'nameEN' : 'name'"
/>
</template>
</AdvanceSearch>
</template>
2024-07-17 07:38:44 +00:00
</q-input>
2025-09-01 14:53:52 +07:00
<SaveButton
icon-only
class="q-mr-auto"
:icon="'material-symbols:download'"
@click.stop="triggerExport()"
/>
<div class="row col-md-5" style="white-space: nowrap">
2024-07-17 07:38:44 +00:00
<q-select
v-if="$q.screen.gt.sm"
2024-07-18 14:18:16 +07:00
id="select-status"
for="select-status"
2024-07-17 07:38:44 +00:00
v-model="currentStatus"
2024-07-05 09:29:46 +00:00
outlined
dense
autocomplete="off"
2024-07-17 07:38:44 +00:00
option-value="value"
option-label="label"
:class="{ 'offset-md-5': gridView }"
2024-07-17 07:38:44 +00:00
class="col"
map-options
emit-value
2024-07-18 04:51:09 +00:00
:hide-dropdown-icon="$q.screen.lt.sm"
2024-07-17 07:38:44 +00:00
:options="[
2024-08-26 16:24:08 +07:00
{ label: $t('general.all'), value: 'All' },
{ label: $t('status.ACTIVE'), value: 'ACTIVE' },
{ label: $t('status.INACTIVE'), value: 'INACTIVE' },
2024-07-17 07:38:44 +00:00
]"
2025-09-12 17:59:03 +07:00
/>
2024-07-17 07:38:44 +00:00
<q-select
v-if="!gridView"
2024-07-17 07:38:44 +00:00
id="select-field"
for="select-field"
2024-07-18 09:10:51 +00:00
class="q-ml-sm col"
2024-07-17 07:38:44 +00:00
:options="
currentTab === 'employer'
2024-09-19 13:30:20 +07:00
? gridView
? fieldDisplayCustomer.filter((v) => {
return (
v.value !== 'orderNumber' &&
v.value !== 'titleName' &&
v.value !== 'address' &&
v.value !== 'contactName'
);
})
: fieldDisplayCustomer
2024-07-17 07:38:44 +00:00
: fieldDisplayEmployee
"
2024-08-26 16:24:08 +07:00
:display-value="$t('general.displayField')"
2024-07-18 04:51:09 +00:00
:hide-dropdown-icon="$q.screen.lt.sm"
2024-07-17 07:38:44 +00:00
v-model="fieldSelected"
2024-07-23 04:25:09 +00:00
:option-label="(l) => $t(l.label)"
2024-07-17 07:38:44 +00:00
option-value="value"
autocomplete="off"
2024-07-17 07:38:44 +00:00
map-options
emit-value
outlined
multiple
dense
/>
2024-07-05 09:29:46 +00:00
2024-07-17 07:38:44 +00:00
<q-btn-toggle
id="btn-mode"
2024-08-02 16:13:07 +07:00
v-model="gridView"
2024-07-17 07:38:44 +00:00
dense
2024-07-18 09:10:51 +00:00
class="no-shadow bordered rounded surface-1 q-ml-sm"
2024-07-17 07:38:44 +00:00
:toggle-color="$q.dark.isActive ? 'grey-9' : 'grey-2'"
size="xs"
:options="[
{ value: true, slot: 'folder' },
{ value: false, slot: 'list' },
]"
2024-07-16 02:53:06 +00:00
>
2024-07-17 07:38:44 +00:00
<template v-slot:folder>
<q-icon
2024-07-18 14:18:16 +07:00
id="icon-mode-grid"
2024-07-17 07:38:44 +00:00
name="mdi-view-grid-outline"
size="16px"
class="q-px-sm q-py-xs rounded"
:style="{
color: $q.dark.isActive
2024-08-02 16:13:07 +07:00
? gridView
2024-07-17 07:38:44 +00:00
? '#C9D3DB '
: '#787B7C'
2024-08-02 16:13:07 +07:00
: gridView
2024-07-17 07:38:44 +00:00
? '#787B7C'
: '#C9D3DB',
}"
/>
</template>
<template v-slot:list>
<q-icon
2024-07-18 14:18:16 +07:00
id="icon-mode-list"
2024-07-17 07:38:44 +00:00
name="mdi-format-list-bulleted"
class="q-px-sm q-py-xs rounded"
size="16px"
:style="{
color: $q.dark.isActive
2024-08-02 16:13:07 +07:00
? gridView === false
2024-07-17 07:38:44 +00:00
? '#C9D3DB'
: '#787B7C'
2024-08-02 16:13:07 +07:00
: gridView === false
2024-07-17 07:38:44 +00:00
? '#787B7C'
: '#C9D3DB',
}"
/>
</template>
</q-btn-toggle>
2024-07-05 09:29:46 +00:00
</div>
</div>
<div class="surface-2 bordered-b q-px-md">
<q-tabs
dense
2024-07-05 09:29:46 +00:00
v-model="currentTab"
align="left"
class="full-height"
active-color="info"
2024-06-13 13:33:47 +07:00
>
2024-07-05 09:29:46 +00:00
<q-tab
name="employer"
class="text-capitalize"
2024-07-19 14:04:02 +07:00
id="tab-employer"
2024-07-05 09:29:46 +00:00
@click="
2024-08-02 16:38:49 +07:00
() => {
2024-07-05 09:29:46 +00:00
inputSearch = '';
currentStatus = 'All';
flowStore.rotate();
}
"
>
2024-06-13 13:33:47 +07:00
<div
2024-07-05 09:29:46 +00:00
class="row"
:class="
currentTab === 'employer' ? 'text-bold' : 'app-text-muted'
2024-06-13 11:07:04 +00:00
"
2024-06-13 13:33:47 +07:00
>
2024-08-26 16:24:08 +07:00
{{ $t('customer.employer') }}
2024-06-13 13:33:47 +07:00
</div>
2024-07-05 09:29:46 +00:00
</q-tab>
<q-tab
name="employee"
2024-07-19 14:04:02 +07:00
id="tab-employee"
class="text-capitalize"
2024-07-05 09:29:46 +00:00
@click="
2024-08-02 16:38:49 +07:00
() => {
2024-07-05 09:29:46 +00:00
inputSearch = '';
currentStatus = 'All';
flowStore.rotate();
}
"
>
2024-06-13 13:33:47 +07:00
<div
2024-07-05 09:29:46 +00:00
class="row"
:class="
currentTab === 'employee' ? 'text-bold' : 'app-text-muted'
2024-06-13 11:07:04 +00:00
"
2024-06-13 13:33:47 +07:00
>
2024-08-26 16:24:08 +07:00
{{ $t('customer.employee') }}
2024-06-13 13:33:47 +07:00
</div>
2024-07-05 09:29:46 +00:00
</q-tab>
</q-tabs>
</div>
<div
v-if="$q.screen.lt.md && currentTab === 'employer'"
class="q-px-md row q-gutter-x-sm items-center"
>
<nav
2025-01-30 10:10:15 +07:00
class="col rounded q-pa-sm text-center app-text-muted text-caption"
:class="{
'active-tab-sm': customerTypeSelected.value === v,
'bordered surface-1': customerTypeSelected.value !== v,
}"
v-for="v in fieldCustomer"
:key="v"
@click="customerTypeSelected = { label: v, value: v }"
id="`btn-sm-${v}`"
>
{{
$t(
{
all: 'general.all',
customerLegalEntity: 'customer.employerLegalEntity',
customerNaturalPerson: 'customer.employerNaturalPerson',
}[v],
)
}}
</nav>
</div>
2024-07-05 09:29:46 +00:00
</div>
2024-07-05 09:29:46 +00:00
<!-- body -->
2025-09-12 17:59:03 +07:00
<TabCustomer
v-if="currentTab === 'employer'"
ref="refTabCustomer"
v-model:customer-type-selected="customerTypeSelected"
2025-09-12 17:59:03 +07:00
v-model:stats-customer-type="statsCustomerType"
v-model:img-list="imageList"
:filter-address="filterAddress"
:filter-business-type="filterBusinessType"
2025-09-12 17:59:03 +07:00
:gridView
:searchDate
:currentTab
:inputSearch
:currentStatus
:fieldSelected
:fetchImageList
:triggerChangeStatus
/>
2024-07-17 07:38:44 +00:00
2025-09-12 17:59:03 +07:00
<TabEmployee
v-if="currentTab === 'employee'"
ref="refTabEmployee"
v-model:employee-stats="employeeStats"
v-model:img-list="imageList"
:gridView
:searchDate
:currentTab
:inputSearch
:currentStatus
:fieldSelected
:fetchImageList
:triggerChangeStatus
/>
2024-07-02 09:43:06 +00:00
</div>
</div>
2024-08-02 16:13:07 +07:00
2025-09-12 17:59:03 +07:00
<!-- หน id กค -->
2024-08-09 15:09:46 +07:00
<div
class="col column rounded bordered"
style="overflow: hidden"
v-if="$route.name === 'CustomerBranchManagement'"
>
<BranchPage
v-model:status-employee-create="statusEmployeeCreate"
2024-11-22 13:33:02 +07:00
v-model:status-employee-edit="employeeFormState.drawerModal"
2024-08-02 16:13:07 +07:00
v-if="currentCustomer"
:customer-type="currentCustomer.customerType"
2024-09-17 18:01:13 +07:00
:current-customer-name="
currentCustomer.customerType === 'PERS'
? locale === 'eng'
? `${currentCustomer.branch[0]?.firstNameEN} ${currentCustomer.branch[0]?.lastNameEN}`
: `${currentCustomer.branch[0]?.firstName} ${currentCustomer.branch[0]?.lastName}`
: locale === 'eng'
? currentCustomer.branch[0]?.registerNameEN
: currentCustomer.branch[0]?.registerName
"
2024-11-20 17:35:33 +07:00
:current-citizen-id="currentCustomer.branch[0]?.citizenId"
2024-08-30 09:55:03 +07:00
:count-employee="currentCustomer._count.employee"
2024-09-17 18:01:13 +07:00
:selected-image="currentCustomer.selectedImage"
:gender="currentCustomer.branch[0]?.gender"
2024-08-20 18:02:15 +07:00
v-model:customer-id="currentCustomer.id"
2024-08-02 16:13:07 +07:00
v-model:mode-view="gridView"
@back="$router.push('/customer-management')"
@add-employee="
async (currentBranch) => {
createEmployeeForm();
await nextTick();
2025-04-08 15:04:03 +07:00
employeeFormState.currentBranchId = currentBranch.id;
}
"
v-model:branch="branch"
2024-08-02 16:13:07 +07:00
v-model:current-customer-url-image="currentCustomer.imageUrl"
/>
</div>
2024-06-07 11:58:27 +07:00
</div>
2025-09-12 17:59:03 +07:00
</template>
2024-04-23 11:15:25 +00:00
<style scoped>
2024-07-05 03:53:08 +00:00
.slide-enter-active {
transition: all 0.1s ease-out;
}
.slide-leave-active {
transition: all 0.1s cubic-bezier(1, 0.5, 0.8, 1);
}
.slide-enter-from,
.slide-leave-to {
transform: translateY(-20px);
opacity: 0;
}
2024-07-05 09:29:46 +00:00
.status-active {
--_branch-status-color: var(--green-6-hsl);
}
.status-inactive {
2024-08-27 15:03:56 +07:00
--_branch-status-color: var(--stone-5-hsl);
--_branch-badge-bg: var(--stone-5-hsl);
filter: grayscale(0.5);
opacity: 0.5;
}
.active-tab-sm {
color: hsla(var(--info-bg) / 1);
background-color: hsla(var(--info-bg) / 0.1);
}
2024-04-23 11:15:25 +00:00
</style>