jws-frontend/src/pages/05_quotation/QuotationForm.vue
2024-10-08 16:38:15 +07:00

1223 lines
36 KiB
Vue

<script lang="ts" setup>
import { useI18n } from 'vue-i18n';
import { storeToRefs } from 'pinia';
import { useQuasar } from 'quasar';
import { nextTick, onBeforeMount, onMounted, reactive, ref, watch } from 'vue';
// NOTE: Import stores
import { setLocale, dateFormat, calculateAge } from 'src/utils/datetime';
import { useEmployeeForm } from 'src/pages/03_customer-management/form';
import useProductServiceStore from 'stores/product-service';
import { baseUrl, waitAll } from 'src/stores/utils';
import useCustomerStore from 'stores/customer';
import useEmployeeStore from 'stores/employee';
import useOptionStore from 'stores/options';
import { useQuotationForm } from './form';
import useOcrStore from 'stores/ocr';
import { deleteItem } from 'stores/utils';
import { runOcr, parseResultMRZ } from 'src/utils/ocr';
// NOTE Import Types
import { QuotationPayload } from 'src/stores/quotations/types';
import { EmployeeWorker } from 'src/stores/quotations/types';
import { Employee } from 'src/stores/employee/types';
import {
ProductGroup,
Product,
Service,
Work,
} from 'src/stores/product-service/types';
// NOTE: Import Components
import FormEmployeePassport from 'components/03_customer-management/FormEmployeePassport.vue';
import BasicInformation from 'components/03_customer-management/employee/BasicInformation.vue';
import FormEmployeeVisa from 'components/03_customer-management/FormEmployeeVisa.vue';
import FormReferDocument from 'src/components/05_quotation/FormReferDocument.vue';
import { UploadFileGroup, noticeJobEmployment } from 'components/upload-file';
import FormPerson from 'components/02_personnel-management/FormPerson.vue';
import ProductItem from 'components/05_quotation/ProductItem.vue';
import WorkerItem from 'components/05_quotation/WorkerItem.vue';
import ToggleButton from 'components/button/ToggleButton.vue';
import FormAbout from 'components/05_quotation/FormAbout.vue';
import SelectZone from 'components/shared/SelectZone.vue';
import PersonCard from 'components/shared/PersonCard.vue';
import { AddButton, SaveButton } from 'components/button';
import ProductServiceForm from './ProductServiceForm.vue';
import QuotationFormInfo from './QuotationFormInfo.vue';
import ProfileBanner from 'components/ProfileBanner.vue';
import DialogForm from 'components/DialogForm.vue';
import SideMenu from 'components/SideMenu.vue';
import {
uploadFileListEmployee,
columnsAttachment,
} from 'src/pages/03_customer-management/constant';
import { group } from 'node:console';
defineProps<{
readonly?: boolean;
}>();
type Node = {
[key: string]: any;
opened?: boolean;
checked?: boolean;
bg?: string;
fg?: string;
icon?: string;
children?: Node[];
};
type ProductGroupId = string;
type Id = string;
const productServiceStore = useProductServiceStore();
const employeeFormStore = useEmployeeForm();
const customerStore = useCustomerStore();
const quotationForm = useQuotationForm();
const employeeStore = useEmployeeStore();
const optionStore = useOptionStore();
const ocrStore = useOcrStore();
const { locale } = useI18n();
const $q = useQuasar();
const {
currentFormData: quotationFormData,
currentFormState: quotationFormState,
newWorkerList,
fileItemNewWorker,
} = storeToRefs(quotationForm);
const refSelectZoneEmployee = ref<InstanceType<typeof SelectZone>>();
const mrz = ref<Awaited<ReturnType<typeof parseResultMRZ>>>();
const selectedBranchIssuer = ref('');
const selectedCustomer = ref('');
const toggleWorker = ref(true);
const rows = ref<Node[]>([]);
const branchId = ref('');
const date = ref();
const preSelectedWorker = ref<Employee[]>([]);
const selectedWorker = ref<
(Employee & {
attachment?: {
name?: string;
group?: string;
url?: string;
file?: File;
_meta?: Record<string, any>;
}[];
})[]
>([]);
const workerList = ref<Employee[]>([]);
const selectedProductGroup = ref('');
const agentPrice = ref(false);
const summaryPrice = ref<{
totalPrice: number;
totalDiscount: number;
vat: number;
finalPrice: number;
}>({
totalPrice: 0,
totalDiscount: 0,
vat: 0,
finalPrice: 0,
});
const quotationNo = ref('');
const payBank = ref('');
const pageState = reactive({
hideStat: false,
inputSearch: '',
statusFilter: 'all',
fieldSelected: [],
gridView: false,
isLoaded: false,
currentTab: 'all',
addModal: false,
quotationModal: false,
employeeModal: false,
productServiceModal: false,
});
const productList = ref<Partial<Record<ProductGroupId, Product[]>>>({});
const serviceList = ref<Partial<Record<ProductGroupId, Service[]>>>({});
const productGroup = ref<ProductGroup[]>([]);
const { state: employeeFormState, currentFromDataEmployee } =
storeToRefs(employeeFormStore);
const product = ref<Record<Id, Product>>({});
const service = ref<Record<Id, Service>>({});
const selectedGroupSub = ref<'product' | 'service' | null>(null);
const selectedGroup = ref<ProductGroup | null>(null);
const selectedProductServiceId = ref('');
const formDataEmployee = ref<
EmployeeWorker & {
attachment?: {
name?: string;
group?: string;
url?: string;
file?: File;
_meta?: Record<string, any>;
}[];
}
>({
passportNo: '',
documentExpireDate: new Date(),
lastNameEN: '',
lastName: '',
middleNameEN: '',
middleName: '',
firstNameEN: '',
firstName: '',
namePrefix: '',
nationality: '',
gender: '',
dateOfBirth: new Date(),
attachment: [],
});
const productServiceList = ref<
Required<QuotationPayload['productServiceList'][number]>[]
>([]);
const productServiceTableData = ref<
{
title: string;
product: {
amount: number;
discount: number;
pricePerUnit: number;
vat: number;
product: Product;
service?: Service;
work?: Work;
}[];
}[]
>([{ title: '', product: [] }]);
function convertDataToFormSubmit() {
quotationFormData.value.productServiceList = JSON.parse(
JSON.stringify(
productServiceList.value.map((v) => ({
workerIndex: [0, 1],
discount: 1,
amount: 1,
product: v.product,
work: v.work,
service: v.service,
})),
),
);
selectedWorker.value.forEach((v, i) => {
if (v.attachment !== undefined) {
v.attachment.forEach((value) => {
fileItemNewWorker.value.push({
employeeIndex: i,
name: value.name,
group: value.group as 'passport' | 'visa' | 'in-country-notice',
url: value.url,
file: value.file,
_meta: value._meta,
});
});
}
});
quotationFormData.value.worker = JSON.parse(
JSON.stringify(
selectedWorker.value.map((v) => {
if (v.id === undefined) {
const { attachment, ...payload } = v;
return payload;
} else {
return v.id;
}
}),
),
);
quotationForm.submitQuotation();
}
async function getAllProduct(
groupId: string,
opts?: { force?: false; page?: number; pageSize?: number },
) {
selectedGroupSub.value = 'product';
if (!opts?.force && productList.value[groupId] !== undefined) return;
const ret = await productServiceStore.fetchListProduct({
page: opts?.page ?? 1,
pageSize: opts?.pageSize ?? 9999,
productGroupId: groupId,
});
if (ret) productList.value[groupId] = ret.result;
}
function setDefaultFormEmployee() {
formDataEmployee.value = {
passportNo: '',
documentExpireDate: new Date(),
lastNameEN: '',
lastName: '',
middleNameEN: '',
middleName: '',
firstNameEN: '',
firstName: '',
namePrefix: '',
nationality: '',
gender: '',
dateOfBirth: new Date(),
attachment: [],
};
employeeFormState.value.dialogModal = false;
}
async function getAllService(
groupId: string,
opts?: { force?: false; page?: number; pageSize?: number },
) {
selectedGroupSub.value = 'service';
if (!opts?.force && serviceList.value[groupId] !== undefined) return;
const ret = await productServiceStore.fetchListService({
page: opts?.page ?? 1,
pageSize: opts?.pageSize ?? 9999,
productGroupId: groupId,
fullDetail: true,
});
if (ret) serviceList.value[groupId] = ret.result;
}
function triggerCreateEmployee() {
employeeFormStore.resetFormDataEmployee(true);
employeeFormState.value.dialogType = 'create';
employeeFormState.value.dialogModal = true;
employeeFormState.value.isEmployeeEdit = true;
}
async function triggerSelectEmployeeDialog() {
pageState.employeeModal = true;
await nextTick();
refSelectZoneEmployee.value?.assignSelect(
preSelectedWorker.value,
selectedWorker.value,
);
}
function triggerProductServiceDialog() {
pageState.productServiceModal = true;
}
function toggleDeleteProduct(index: number) {
deleteItem(productServiceList.value, index);
console.log(index);
}
function convertToTable(nodes: Node[]) {
const _recursive = (v: Node): Node | Node[] => {
if (v.checked && v.children) return v.children.flatMap(_recursive);
if (v.checked) return v;
return [];
};
const list = nodes.flatMap(_recursive).map((v) => v.value);
list.forEach((v) => {
v.amount = Math.max(selectedWorker.value.length, 1);
});
productServiceList.value = list;
const pdList: Node[] = [];
const groupByService = nodes
.map((n) => {
if (n.type === 'type' && n.children) {
const products = n.children.flatMap(_recursive).map((v) => v.value);
return {
title: n.title,
product: products,
};
} else if (n.type === 'product') {
pdList.push(n.value);
}
return null;
})
.filter((t) => t !== null);
if (pdList.length > 0) groupByService.push({ title: '', product: pdList });
productServiceTableData.value = [];
productServiceTableData.value = groupByService;
pageState.productServiceModal = false;
}
function convertEmployeeToTable() {
productServiceList.value.map((v) => {
if (selectedWorker.value.length === 0 && v.amount === 1) v.amount -= 1;
v.amount = Math.max(
v.amount + preSelectedWorker.value.length - selectedWorker.value.length,
1,
);
return v;
});
refSelectZoneEmployee.value?.assignSelect(
selectedWorker.value,
preSelectedWorker.value,
);
pageState.employeeModal = false;
}
function changeMode(mode: string) {
if (mode === 'light') {
localStorage.setItem('currentTheme', 'light');
$q.dark.set(false);
return;
}
if (mode === 'dark') {
localStorage.setItem('currentTheme', 'dark');
$q.dark.set(true);
return;
}
if (mode === 'baseOnDevice') {
localStorage.setItem('currentTheme', 'baseOnDevice');
if (
window.matchMedia &&
window.matchMedia('(prefers-color-scheme: dark)').matches
) {
$q.dark.set(true);
} else {
$q.dark.set(false);
}
return;
}
}
onMounted(async () => {
// get language
const getCurLang = localStorage.getItem('currentLanguage');
if (getCurLang === 'English') {
locale.value = 'eng';
setLocale('en-gb');
}
if (getCurLang === 'ไทย') {
locale.value = 'tha';
setLocale('th');
}
// get theme
const getCurTheme = localStorage.getItem('currentTheme');
if (
getCurTheme === 'light' ||
getCurTheme === 'dark' ||
getCurTheme === 'baseOnDevice'
) {
changeMode(getCurTheme);
} else {
changeMode('light');
}
// get query string
const urlParams = new URLSearchParams(window.location.search);
branchId.value = urlParams.get('branchId') || '';
const price = urlParams.get('agentPrice');
agentPrice.value = price === 'true' ? true : false;
date.value = Date.now();
quotationFormData.value.customerBranchId =
urlParams.get('customerBranchId') || '';
quotationFormState.value.mode = urlParams.get('statusDialog') as
| 'info'
| 'edit'
| 'create';
// fetch option
const resultOption = await fetch('/option/option.json');
const rawOption = await resultOption.json();
if (locale.value === 'eng') optionStore.globalOption = rawOption.eng;
if (locale.value === 'tha') optionStore.globalOption = rawOption.tha;
const ret = await productServiceStore.fetchListProductService({
page: 1,
pageSize: 9999,
});
if (ret) productGroup.value = ret.result;
if (quotationFormData.value.customerBranchId) {
const retEmp = await customerStore.fetchBranchEmployee(
quotationFormData.value.customerBranchId,
);
if (retEmp) workerList.value = workerList.value = retEmp.data.result;
}
pageState.isLoaded = true;
});
watch(
() => quotationFormData.value.customerBranchId,
async (v) => {
const url = new URL(window.location.href);
url.searchParams.set('customerBranchId', v);
history.pushState({}, '', url.toString());
if (!v) return;
const retEmp = await customerStore.fetchBranchEmployee(v);
if (retEmp) {
workerList.value = workerList.value = retEmp.data.result;
}
},
);
watch(
() => branchId.value,
async (v) => {
if (!pageState.isLoaded) return;
const url = new URL(window.location.href);
url.searchParams.set('branchId', v);
history.pushState({}, '', url.toString());
quotationFormData.value.customerBranchId = '';
},
);
</script>
<template>
<div class="fullscreen column surface-0">
<div class="color-bar">
<div class="blue-segment"></div>
<div class="orange-segment"></div>
<div class="gray-segment"></div>
</div>
<header class="row q-pa-md items-center">
<div style="flex: 1" class="row items-center">
<q-img src="/icons/favicon-512x512.png" width="4rem" />
<span class="column text-h6 text-bold q-ml-md">
{{ $t('quotation.title') }}
<span class="text-caption text-regular app-text-muted">
{{
$t('quotation.processOn', {
msg: `${dateFormat(date, true)} ${dateFormat(date, true, true)}`,
})
}}
</span>
</span>
</div>
<FormAbout
class="col-4"
input-only
v-model:branch-id="branchId"
v-model:customer-branch-id="quotationFormData.customerBranchId"
@add-customer="triggerSelectTypeCustomerd()"
/>
</header>
<article
class="row col"
style="flex-grow: 1; overflow-y: hidden"
:style="{
overflowY: $q.screen.gt.sm ? 'hidden' : 'auto',
}"
>
<section
class="col-12 col-sm-9 row"
style="padding: var(--size-3); overflow-y: auto"
:style="{
paddingRight: $q.screen.gt.sm ? 'var(--size-2)' : 'var(--size-3)',
maxHeight: $q.screen.gt.sm ? '100%' : undefined,
}"
>
<div class="col q-gutter-y-md">
<q-expansion-item
for="item-up"
id="item-up"
dense
class="overflow-hidden"
switch-toggle-side
default-opened
style="border-radius: var(--radius-2)"
expand-icon="mdi-chevron-down-circle"
header-class="surface-1"
>
<template v-slot:header>
<section class="row items-center full-width">
<div class="row items-center q-pr-md q-py-sm">
<span
class="text-weight-medium q-mr-md"
style="font-size: 18px"
>
{{ $t('quotation.employeeList') }}
</span>
<ToggleButton class="q-mr-sm" v-model="toggleWorker" />
{{
toggleWorker
? $t('general.specify')
: $t('general.noSpecify')
}}
</div>
<nav class="q-ml-auto">
<AddButton
icon-only
@click.stop="triggerSelectEmployeeDialog"
/>
</nav>
</section>
</template>
<div class="surface-1 q-pa-md full-width">
<WorkerItem
:employee-amount="selectedWorker.length"
fallback-img="/images/employee-avatar.png"
:rows="
selectedWorker.map((e: Employee) => ({
foreignRefNo: '123456789',
employeeName:
$i18n.locale === 'eng'
? `${e.firstNameEN} ${e.lastNameEN}`
: `${e.firstName} ${e.lastName}`,
birthDate: dateFormat(e.dateOfBirth),
gender: e.gender,
age: calculateAge(e.dateOfBirth),
nationality: optionStore.mapOption(e.nationality),
documentExpireDate: '1234',
imgUrl: `${baseUrl}/customer/${e.id}/image/${e.selectedImage}`,
status: e.status,
}))
"
@delete="(i) => deleteItem(selectedWorker, i)"
/>
</div>
</q-expansion-item>
<q-expansion-item
for="item-up"
id="item-up"
dense
class="overflow-hidden"
switch-toggle-side
default-opened
style="border-radius: var(--radius-2)"
expand-icon="mdi-chevron-down-circle"
header-class="surface-1"
>
<template v-slot:header>
<section class="row items-center full-width">
<div class="row items-center q-pr-md q-py-sm">
<span class="text-weight-medium" style="font-size: 18px">
{{ $t('quotation.productList') }}
</span>
</div>
<nav class="q-ml-auto">
<AddButton
icon-only
class="q-ml-auto"
@click.stop="triggerProductServiceDialog"
/>
</nav>
</section>
</template>
<div class="surface-1 q-pa-md full-width">
<span
v-if="productServiceList.length > 0"
class="text-weight-bold row items-center q-pb-md"
>
{{
productGroup.find((g) => g.id === selectedProductGroup)
?.name || '-'
}}
<q-icon name="mdi-chevron-right" class="q-pl-sm" size="xs" />
</span>
<ProductItem
:agent-price="agentPrice"
@delete="toggleDeleteProduct"
v-model:groupList="productServiceTableData"
v-model:rows="productServiceList"
v-model:summary-price="summaryPrice"
/>
</div>
</q-expansion-item>
<q-expansion-item
for="item-up"
id="item-up"
dense
class="overflow-hidden"
switch-toggle-side
default-opened
style="border-radius: var(--radius-2)"
expand-icon="mdi-chevron-down-circle"
header-class="surface-1"
>
<template v-slot:header>
<div class="row full-width items-center q-pr-md q-py-sm">
<span class="text-weight-medium" style="font-size: 18px">
{{ $t('general.remark') }}
</span>
</div>
</template>
<div class="surface-1 q-pa-md full-width">
<q-editor
dense
:model-value="''"
min-height="5rem"
class="full-width"
toolbar-bg="input-border"
style="cursor: auto; color: var(--foreground)"
:flat="!readonly"
:readonly="readonly"
:style="`width: ${$q.screen.gt.xs ? '100%' : '63vw'}`"
:toolbar="[['left', 'center', 'justify'], ['clip']]"
:toolbar-toggle-color="readonly ? 'disabled' : 'primary'"
:toolbar-color="
readonly ? 'disabled' : $q.dark.isActive ? 'white' : ''
"
:definitions="{
clip: {
icon: 'mdi-paperclip',
tip: 'Upload',
handler: () => console.log('upload'),
},
}"
/>
</div>
</q-expansion-item>
</div>
</section>
<div
class="col-12 col-sm-3"
style="padding: var(--size-3); overflow-y: auto"
:style="{
paddingLeft: $q.screen.gt.sm ? 'var(--size-1)' : 'var(--size-3)',
maxHeight: $q.screen.gt.sm ? '100%' : undefined,
}"
>
<QuotationFormInfo
v-model:urgent="quotationFormData.urgent"
v-model:quotation-no="quotationNo"
v-model:actor="quotationFormData.actorName"
v-model:work-name="quotationFormData.workName"
v-model:contactor="quotationFormData.contactName"
v-model:telephone="quotationFormData.contactTel"
v-model:document-receive-point="
quotationFormData.documentReceivePoint
"
v-model:due-date="quotationFormData.dueDate"
v-model:pay-type="quotationFormData.payCondition"
v-model:pay-bank="payBank"
v-model:pay-split-count="quotationFormData.paySplitCount"
v-model:pay-split="quotationFormData.paySplit"
:readonly
v-model:summary-price="summaryPrice"
/>
</div>
</article>
<footer class="surface-1 q-pa-md">
<div class="row full-width justify-between">
<q-btn dense outline color="primary" class="rounded" padding="2px 8px">
<q-icon name="mdi-play-box-outline" size="xs" class="q-mr-xs" />
{{ $t('general.view', { msg: $t('general.example') }) }}
</q-btn>
<SaveButton
@click="
() => {
convertDataToFormSubmit();
}
"
solid
/>
</div>
</footer>
<!-- SEC: Dialog -->
<!-- add employee quotation -->
<DialogForm
:title="$t('general.select', { msg: $t('quotation.employeeList') })"
v-model:modal="pageState.employeeModal"
:submit-label="$t('general.select', { msg: $t('quotation.employee') })"
submit-icon="mdi-check"
height="75vh"
:submit="() => convertEmployeeToTable()"
:close="
() => {
(preSelectedWorker = []), (pageState.employeeModal = false);
}
"
>
<section class="col row scroll">
<SelectZone
ref="refSelectZoneEmployee"
v-model:selected-item="preSelectedWorker"
:items="workerList"
:new-items="newWorkerList"
>
<template #top>
<AddButton
icon-only
@click="
() => {
triggerCreateEmployee();
}
"
/>
</template>
<template #data="{ item }">
<PersonCard
noAction
prefixId="asda"
class="full-width"
:data="{
name:
$i18n.locale === 'eng'
? `${item.firstNameEN} ${item.lastNameEN}`
: `${item.firstName} ${item.lastName}`,
code: item.code,
female: item.gender === 'female',
male: item.gender === 'male',
img: `${baseUrl}/customer/${item.id}/image/${item.selectedImage}`,
fallbackImg: '/images/employee-avatar.png',
detail: [
{
icon: 'mdi-passport',
value: optionStore.mapOption(item.nationality),
},
{
icon: 'mdi-clock-outline',
value: calculateAge(item.dateOfBirth),
},
],
}"
></PersonCard>
</template>
<template #newData="{ item }">
<PersonCard
noAction
prefixId="asda"
class="full-width"
:data="{
name:
$i18n.locale === 'eng'
? `${item.firstNameEN} ${item.lastNameEN}`
: `${item.firstName} ${item.lastName}`,
code: item.code,
female: item.gender === 'female',
male: item.gender === 'male',
img: `${baseUrl}/customer/${item.id}/image/${item.selectedImage}`,
fallbackImg: '/images/employee-avatar.png',
detail: [
{
icon: 'mdi-passport',
value: optionStore.mapOption(item.nationality),
},
{
icon: 'mdi-clock-outline',
value: calculateAge(item.dateOfBirth),
},
],
}"
></PersonCard>
</template>
</SelectZone>
</section>
</DialogForm>
<!-- add product service -->
<ProductServiceForm
v-model="pageState.productServiceModal"
v-model:product-group="productGroup"
v-model:product-list="productList"
v-model:service-list="serviceList"
v-model:selected-product-group="selectedProductGroup"
:agent-price="agentPrice"
@submit="convertToTable"
@select-group="
async (id) => {
await getAllService(id);
await getAllProduct(id);
}
"
></ProductServiceForm>
</div>
<!-- NOTE: START - Employee Add Form -->
<DialogForm
hide-footer
ref="formDialogRef"
v-model:modal="employeeFormState.dialogModal"
:title="$t('form.title.create', { name: $t('customer.employee') })"
:submit="
() => {
quotationForm.injectNewEmployee({ data: formDataEmployee }, () =>
setDefaultFormEmployee(),
);
}
"
:close="
() => {
employeeFormState.dialogModal = false;
}
"
>
<div
:class="{
'q-mx-lg q-my-md': $q.screen.gt.sm,
'q-mx-md q-my-sm': !$q.screen.gt.sm,
}"
>
<ProfileBanner
active
useToggle
color="white"
icon="mdi-account-plus-outline"
:bg-color="
employeeFormState.profileUrl
? 'white'
: 'linear-gradient(135deg, rgba(43,137,223,1) 0%, rgba(230,51,81,1) 100%)'
"
v-model:current-tab="employeeFormState.currentTab"
v-model:toggle-status="currentFromDataEmployee.status"
fallbackCover="/images/employee-banner.png"
:img="employeeFormState.profileUrl || `/images/employee-avatar.png`"
:toggleTitle="$t('status.title')"
hideFade
@view="
() => {
employeeFormState.imageDialog = true;
employeeFormState.isImageEdit = false;
}
"
@edit="
employeeFormState.imageDialog = employeeFormState.isImageEdit = true
"
@update:toggle-status="
() => {
currentFromDataEmployee.status =
currentFromDataEmployee.status === 'CREATED'
? 'INACTIVE'
: 'CREATED';
}
"
/>
</div>
<div
class="col"
:class="{
'q-px-lg q-pb-lg': $q.screen.gt.sm,
'q-px-md q-pb-sm': !$q.screen.gt.sm,
}"
>
<div
style="overflow-y: auto"
class="row full-width full-height surface-1 rounded bordered relative-position"
>
<div
:class="{
'q-py-md q-px-lg': $q.screen.gt.sm,
'q-py-sm q-px-lg': !$q.screen.gt.sm,
}"
style="position: absolute; z-index: 99999; top: 0; right: 0"
>
<div
v-if="currentFromDataEmployee.status !== 'INACTIVE'"
class="surface-1 row rounded"
>
<UndoButton
v-if="
employeeFormState.isEmployeeEdit &&
employeeFormState.dialogType !== 'create'
"
id="btn-info-basic-undo"
icon-only
@click="
() => {
employeeFormStore.resetFormDataEmployee();
employeeFormState.isEmployeeEdit = false;
employeeFormState.dialogType = 'info';
}
"
type="button"
/>
<SaveButton
v-if="employeeFormState.isEmployeeEdit"
id="btn-info-basic-save"
icon-only
type="submit"
/>
<EditButton
v-if="!employeeFormState.isEmployeeEdit"
id="btn-info-basic-edit"
icon-only
@click="
() => {
employeeFormState.isEmployeeEdit = true;
employeeFormState.dialogType = 'edit';
}
"
type="button"
/>
<DeleteButton
v-if="!employeeFormState.isEmployeeEdit"
id="btn-info-basic-delete"
icon-only
/>
</div>
</div>
<div
class="col-12 full-height q-col-gutter-sm q-py-md q-pl-md q-pr-sm"
:class="{
'q-py-md q-pr-md ': $q.screen.gt.sm,
'q-py-md q-px-lg': !$q.screen.gt.sm,
}"
id="branch-form"
style="overflow-y: auto"
>
<FormReferDocument
title="form.field.basicInformation"
prefixId="dialog"
dense
v-model:passport-no="formDataEmployee.passportNo"
v-model:document-expire-date="formDataEmployee.documentExpireDate"
class="q-mb-md"
/>
<FormPerson
id="form-personal"
prefix-id="form-employee"
dense
outlined
employee
separator
hideNameEn
title="personnel.form.personalInformation"
class="q-mb-md"
v-model:prefix-name="formDataEmployee.namePrefix"
v-model:first-name="formDataEmployee.firstName"
v-model:mid-name="formDataEmployee.middleName"
v-model:last-name="formDataEmployee.lastName"
v-model:birth-date="formDataEmployee.dateOfBirth"
v-model:gender="formDataEmployee.gender"
v-model:nationality="formDataEmployee.nationality"
/>
<UploadFileGroup
show-title
v-model="formDataEmployee.attachment"
hide-action
:menu="uploadFileListEmployee"
:columns="columnsAttachment"
:ocr="
async (group, file) => {
if (group === 'passport') {
mrz = await runOcr(file, parseResultMRZ);
if (mrz !== null) {
const mapMrz = Object.entries(mrz.result).map(
([key, value]) => ({
name: key,
value: value,
}),
);
const tempValue = {
status: true,
group,
meta: mapMrz,
};
return tempValue;
}
} else {
const res = await ocrStore.sendOcr({
file: file,
category: group,
});
if (res) {
const tempValue = {
status: true,
group,
meta: res.fields,
};
return tempValue;
}
}
return { status: true, group, meta: [] };
}
"
:delete-item="
async () => {
return true;
}
"
:get-file-list="
async (group: 'passport' | 'visa') => {
if (formDataEmployee.attachment !== undefined) {
const resMeta = formDataEmployee.attachment.filter(
(v) => v.group === group,
);
const tempValue = resMeta.map(async (i: any) => {
return {
_meta: { ...i._meta },
name: i.id || '',
group: group,
url: i.url,
file: undefined,
};
});
return await waitAll(tempValue);
}
return [];
}
"
>
<template #form="{ mode, meta, isEdit }">
<FormEmployeePassport
v-if="mode === 'passport' && meta"
prefix-id="drawer-info-employee"
id="form-passport"
dense
outlined
separator
ocr
:title="$t('customerEmployee.form.group.passport')"
:readonly="!isEdit"
v-model:passport-type="meta.type"
v-model:passport-number="meta.number"
v-model:passport-issue-date="meta.issueDate"
v-model:passport-expiry-date="meta.expireDate"
v-model:passport-issuing-place="meta.issuePlace"
v-model:passport-issuing-country="meta.issueCountry"
/>
<FormEmployeeVisa
v-if="mode === 'visa' && meta"
prefix-id="drawer-info-employee"
id="form-visa"
ocr
dense
outlined
title="customerEmployee.form.group.visa"
:readonly="!isEdit"
v-model:visa-type="meta.type"
v-model:visa-number="meta.number"
v-model:visa-issue-date="meta.issueDate"
v-model:visa-expiry-date="meta.expireDate"
v-model:visa-issuing-place="meta.issuePlace"
/>
<noticeJobEmployment v-if="mode === 'noticeJobEmployment'" />
</template>
</UploadFileGroup>
</div>
</div>
</div>
</DialogForm>
<!-- NOTE: END - Employee Add Form -->
</template>
<style scoped>
.worker-list > :deep(*:not(:last-child)) {
margin-bottom: var(--size-2);
}
.icon-wrapper {
display: flex;
align-items: center;
justify-content: center;
aspect-ratio: 1/1;
font-size: 1.5rem;
padding: var(--size-1);
border-radius: var(--radius-2);
}
.bg-color-orange {
--_color: var(--yellow-7-hsl);
color: white;
background: hsla(var(--_color));
}
.dark .bg-color-orange {
--_color: var(--orange-6-hsl);
}
.bg-color-orange-light {
--_color: var(--yellow-7-hsl);
background: hsla(var(--_color) / 0.2);
}
.dark .bg-color-orange {
--_color: var(--orange-6-hsl / 0.2);
}
.color-bar {
width: 100%;
height: 1vh;
background: linear-gradient(
90deg,
rgba(245, 159, 0, 1) 0%,
rgba(255, 255, 255, 1) 77%,
rgba(204, 204, 204, 1) 100%
);
display: flex;
overflow: hidden;
}
.blue-segment {
background-color: var(--yellow-7);
flex-grow: 4;
}
.orange-segment {
background-color: hsla(var(--yellow-7-hsl) / 0.2);
flex-grow: 0.5;
}
.gray-segment {
background-color: #ccc;
flex-grow: 1;
}
.blue-segment,
.orange-segment,
.gray-segment {
transform: skewX(-60deg);
}
:deep(i.q-icon.mdi.mdi-chevron-down-circle.q-expansion-item__toggle-icon) {
color: var(--brand-1);
}
:deep(.q-editor__toolbar-group):nth-child(2) {
margin-left: auto !important;
}
:deep(.q-editor__toolbar.row.no-wrap.scroll-x) {
background-color: var(--surface-2) !important;
}
:deep(.q-editor__toolbar) {
border-color: var(--surface-3) !important;
}
:deep(
.q-item.q-item-type.row.no-wrap.q-item--dense.q-item--clickable.q-link.cursor-pointer.q-focusable.q-hoverable.surface-1
.q-focus-helper
) {
visibility: hidden;
}
</style>