Compare commits

..

No commits in common. "develop" and "version-0.11.10" have entirely different histories.

199 changed files with 9826 additions and 16801 deletions

View file

@ -17,14 +17,13 @@
"dependencies": {
"@peaceroad/markdown-it-figure-with-p-caption": "^0.11.0",
"@quasar/extras": "^1.16.17",
"@tato30/vue-pdf": "^1.11.3",
"@tato30/vue-pdf": "^1.11.0",
"@vuepic/vue-datepicker": "^8.8.1",
"apexcharts": "^4.5.0",
"axios": "^1.8.4",
"axios": "^1.7.4",
"cropperjs": "^1.6.2",
"dayjs": "^1.11.13",
"highlight.js": "^11.11.1",
"keycloak-js": "^25.0.6",
"keycloak-js": "^25.0.4",
"markdown-it": "^14.1.0",
"markdown-it-anchor": "^9.2.0",
"markdown-it-highlightjs": "^4.2.0",
@ -32,41 +31,42 @@
"markdown-it-html5-media": "^0.7.1",
"markdown-it-image-figures": "^2.1.1",
"markdown-it-video": "^0.6.3",
"mime": "^4.0.6",
"mime": "^4.0.4",
"moment": "^2.30.1",
"number-to-words": "^1.2.4",
"open-props": "^1.7.14",
"pinia": "^2.3.1",
"open-props": "^1.7.5",
"pinia": "^2.2.2",
"quasar": "^2.18.1",
"signature_pad": "^5.0.7",
"signature_pad": "^5.0.2",
"socket.io-client": "^4.7.5",
"tesseract.js": "^5.1.1",
"thai-baht-text": "^2.0.5",
"udsv": "^0.6.0",
"uuid": "^10.0.0",
"vue": "^3.5.13",
"vue": "^3.4.38",
"vue-dragscroll": "^4.0.6",
"vue-i18n": "^11.1.2",
"vue-pdf": "^4.3.0",
"vue-router": "^4.5.0",
"vue-router": "^4.4.3",
"vue-tsc": "^2.2.8",
"vue3-apexcharts": "^1.8.0"
},
"devDependencies": {
"@faker-js/faker": "^9.6.0",
"@iconify/vue": "^4.3.0",
"@faker-js/faker": "^9.3.0",
"@iconify/vue": "^4.1.2",
"@intlify/unplugin-vue-i18n": "^6.0.5",
"@playwright/test": "^1.51.1",
"@playwright/test": "^1.46.1",
"@quasar/app-vite": "^2.2.0",
"@types/markdown-it": "^14.1.2",
"@types/markdown-it-highlightjs": "^3.3.4",
"@types/node": "^20.17.28",
"@types/node": "^20.16.1",
"@types/number-to-words": "^1.2.3",
"@types/uuid": "^10.0.0",
"autoprefixer": "^10.4.21",
"autoprefixer": "^10.4.20",
"dotenv": "^16.4.7",
"prettier": "^3.5.3",
"prettier": "^3.3.3",
"typescript": "^5.5.4",
"vue-component-type-helpers": "^2.2.8"
"vue-component-type-helpers": "^2.1.10"
},
"engines": {
"node": "^28 || ^26 || ^24 || ^22 || ^20 || ^18",

1066
pnpm-lock.yaml generated

File diff suppressed because it is too large Load diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 151 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 128 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 70 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 63 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.7 KiB

After

Width:  |  Height:  |  Size: 37 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.4 KiB

After

Width:  |  Height:  |  Size: 47 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.4 KiB

After

Width:  |  Height:  |  Size: 37 KiB

Before After
Before After

View file

@ -1,28 +1,5 @@
{
"eng": {
"visaType": [
{
"label": "Non-LA",
"value": "nla"
},
{
"label": "Non-B",
"value": "nb"
},
{
"label": "TV.60",
"value": "tv60"
},
{
"label": "Non-TR",
"value": "ntr"
},
{
"label": "TV.30",
"value": "tv30"
}
],
"workerStatus": [
{
"label": "Normal",
@ -177,21 +154,20 @@
{ "label": "VS2", "value": "VS2" },
{ "label": "WO", "value": "WO" },
{ "label": "WP390", "value": "WP390" },
{ "label": "WP44", "value": "WP44" },
{ "label": "CUST", "value": "CUST" }
{ "label": "WP44", "value": "WP44" }
],
"prefix": [
{
"label": "MR",
"label": "Mr",
"value": "mr"
},
{
"label": "MRS",
"label": "Mrs",
"value": "mrs"
},
{
"label": "MISS",
"label": "Miss",
"value": "miss"
}
],
@ -207,44 +183,29 @@
}
],
"border": [
"training": [
{
"label": "Mae Sot, Tak Province",
"label": "Myanmar Labor Training Center - Mae Sot, Tak Province",
"value": "trainingTak"
},
{
"label": "Koh Song, Ranong province",
"label": "Myanmar Labor Training Center - Kawthoung, Ranong Province",
"value": "trainingRanong"
},
{
"label": "Nong Khai, Nong Khai Province",
"label": "Laos Labor Training Center - Nong Khai, Nong Khai Province",
"value": "trainingNongKhai"
},
{
"label": "Aranyaprathet, Sa Kaeo Province",
"label": "Cambodian Labor Training Center - Aranyaprathet, Sa Kaeo Province",
"value": "trainingSaKaeo"
},
{
"label": "Ban Laem, Chanthaburi Province",
"label": "Cambodian Labor Training Center - Ban Laem, Chanthaburi Province",
"value": "trainingChanthaburi"
}
],
"training": [
{
"label": "The first center accepts work. and end of employment Tak Province",
"value": "trainingTak"
},
{
"label": "The first center accepts work. and end of employment Nong Khai Province",
"value": "trainingNongKhai"
},
{
"label": "The first center accepts work. and end of employment Sa Kaeo Province",
"value": "trainingSaKaeo"
}
],
"nationality": [
{
"label": "Thai",
@ -1089,29 +1050,6 @@
},
"tha": {
"visaType": [
{
"label": "Non-LA",
"value": "nla"
},
{
"label": "Non-B",
"value": "nb"
},
{
"label": "ผผ.60",
"value": "tv60"
},
{
"label": "Non-TR",
"value": "ntr"
},
{
"label": "ผผ.30",
"value": "tv30"
}
],
"workerStatus": [
{
"label": "ปกติ",
@ -1266,8 +1204,7 @@
{ "label": "VS2", "value": "VS2" },
{ "label": "WO", "value": "WO" },
{ "label": "WP390", "value": "WP390" },
{ "label": "WP44", "value": "WP44" },
{ "label": "CUST", "value": "CUST" }
{ "label": "WP44", "value": "WP44" }
],
"prefix": [
@ -1296,44 +1233,29 @@
}
],
"border": [
"training": [
{
"label": "แม่สอด จ.ตาก",
"label": "สถานที่อบรมแรงงานเมียนมา-แม่สอด จ.ตาก",
"value": "trainingTak"
},
{
"label": "เกาะสอง จ.ระนอง",
"label": "สถานที่อบรมแรงงานเมียนมา-เกาะสอง จ.ระนอง",
"value": "trainingRanong"
},
{
"label": "หนองคาย จ.หนองคาย",
"label": "สถานที่อบรมแรงงานลาว-หนองคาย จ.หนองคาย",
"value": "trainingNongKhai"
},
{
"label": "อรัญประเทศ จ.สระแก้ว",
"label": "สถานที่อบรมแรงงานกัมพูชา-อรัญประเทศ จ.สระแก้ว",
"value": "trainingSaKaeo"
},
{
"label": "บ้านแหลม จ.จันทบุรี",
"label": "สถานที่อบรมแรงงานกัมพูชา-บ้านแหลม จ.จันทบุรี",
"value": "trainingChanthaburi"
}
],
"training": [
{
"label": "ศูนย์แรกรับเข้าทำงาน และสิ้นสุดการจ้าง จังหวัดตาก",
"value": "trainingTak"
},
{
"label": "ศูนย์แรกรับเข้าทำงาน และสิ้นสุดการจ้าง จังหวัดหนองคาย",
"value": "trainingNongKhai"
},
{
"label": "ศูนย์แรกรับเข้าทำงาน และสิ้นสุดการจ้าง จังหวัดสระแก้ว",
"value": "trainingSaKaeo"
}
],
"nationality": [
{
"label": "ไทย",

View file

@ -31,7 +31,7 @@ export default defineConfig((ctx) => {
devServer: {
host: '0.0.0.0',
open: false,
port: 5174,
port: 5173,
},
framework: {
config: {},

View file

@ -2,7 +2,6 @@ import { defineBoot } from '#q-app/wrappers';
import { createI18n } from 'vue-i18n';
import messages from 'src/i18n';
import { Lang } from 'src/utils/ui';
export type MessageLanguages = keyof typeof messages;
// Type-define 'eng' as the master schema for the resource
@ -27,7 +26,7 @@ export const i18n = createI18n<
MessageLanguages,
false
>({
locale: 'tha',
locale: 'en-US',
legacy: false,
messages,
});

View file

@ -89,7 +89,15 @@ defineProps<{
</div>
</div>
</div>
<q-separator />
<div
style="
display: block;
width: 100%;
height: 1px;
background: hsla(0 0% 0% / 0.1);
margin-bottom: var(--size-2);
"
/>
<slot name="data"></slot>
<template v-if="!$slots.data">
<div

View file

@ -36,7 +36,6 @@ defineProps<{
outlined?: boolean;
readonly?: boolean;
view?: boolean;
single?: boolean;
}>();
defineEmits<{
@ -122,7 +121,7 @@ watch(
/>
{{ $t(`${title}`) }}
<AddButton
v-if="!readonly && !single"
v-if="!readonly"
id="btn-add-bank"
icon-only
class="q-ml-sm"
@ -142,10 +141,7 @@ watch(
style="padding-block: 0.01px"
spaced="lg"
/>
<span
v-if="!single"
class="col-12 app-text-muted-2 flex justify-between items-center"
>
<span class="col-12 app-text-muted-2 flex justify-between items-center">
{{ `${$t('branch.form.bankAccountNo')} ${i + 1}` }}
<div class="row items-center">
<div style="height: 30.8px" />
@ -176,8 +172,7 @@ watch(
</span>
<div
v-if="!single"
class="bordered q-mr-sm rounded col-4 text-center overflow-hidden"
class="bordered q-mr-sm rounded col text-center overflow-hidden"
:class="{ 'pointer-none': readonly, 'q-my-sm': $q.screen.lt.md }"
>
<ImageHover

View file

@ -159,6 +159,42 @@ function formatCode(input: string | undefined, type: 'code' | 'number') {
]"
for="input-name-en"
/>
<q-select
v-if="
typeBranch !== 'headOffice' &&
isRoleInclude(['head_of_admin', 'head_of_account'])
"
outlined
use-input
fill-input
emit-value
map-options
hide-selected
hide-bottom-space
input-debounce="0"
option-label="label"
option-value="value"
class="col-2"
dense
for="input-branch-status"
:readonly="readonly || isRoleInclude(['head_of_account'])"
:options="['Virtual', 'Branch']"
:hide-dropdown-icon="readonly"
:label="$t('general.branchStatus')"
:model-value="virtual ? 'Virtual' : 'Branch'"
@update:model-value="(v) => (virtual = v === 'Virtual')"
:rules="[(val) => val && val.length > 0]"
:error-message="$t('form.error.required')"
>
<template v-slot:no-option>
<q-item>
<q-item-section class="text-grey">
{{ $t('general.noData') }}
</q-item-section>
</q-item>
</template>
</q-select>
</div>
<div class="col-12 row q-col-gutter-sm">

View file

@ -1,13 +1,13 @@
<script setup lang="ts">
import useUserStore from 'stores/user';
import useOptionStore from 'stores/options';
import { UserAttachmentDelete, AgencyStatus } from 'stores/user/types';
import { dialog } from 'stores/utils';
import { onMounted, ref } from 'vue';
import { UserAttachmentDelete } from 'stores/user/types';
import { dialog, selectFilterOptionRefMod } from 'stores/utils';
import { onMounted, ref, watch } from 'vue';
import { useI18n } from 'vue-i18n';
import { Icon } from '@iconify/vue';
import { QSelect } from 'quasar';
import DatePicker from '../shared/DatePicker.vue';
import SelectInput from 'src/components/shared/SelectInput.vue';
import SelectOffice from 'components/shared/select-muliple/SelectOffice.vue';
@ -29,16 +29,15 @@ const discountCondition = defineModel<string | null | undefined>(
const sourceNationality = defineModel<string | null | undefined>(
'sourceNationality',
);
const importNationality = defineModel<string[] | null | undefined>(
const importNationality = defineModel<string | null | undefined>(
'importNationality',
);
const trainingPlace = defineModel<string | null | undefined>('trainingPlace');
const checkpoint = defineModel<string | null | undefined>('checkpoint');
const userFile = defineModel<File[]>('userFile');
const userFileList =
defineModel<{ name: string; url: string }[]>('userFileList');
const remark = defineModel<string | null | undefined>('remark');
const agencyStatus = defineModel<string | null | undefined>('agencyStatus');
const checkpoint = defineModel<string | null | undefined>('checkPoint');
const checkpointEN = defineModel<string | null | undefined>('checkPointEn');
const agencyFile = defineModel<File[]>('agencyFile');
const agencyFileList =
defineModel<{ name: string; url: string }[]>('agencyFileList');
const attachmentRef = ref();
@ -70,12 +69,66 @@ function deleteFile(name: string) {
userStore.deleteAttachment(userId.value, payload);
const result = await userStore.fetchAttachment(userId.value);
if (result) {
userFileList.value = result;
agencyFileList.value = result;
}
},
cancel: () => {},
});
}
const nationalityOptions = ref<Record<string, unknown>[]>([]);
let nationalityFilter: (
value: string,
update: (callbackFn: () => void, afterFn?: (ref: QSelect) => void) => void,
) => void;
const trainingPlaceOptions = ref<Record<string, unknown>[]>([]);
let trainingPlaceFilter: (
value: string,
update: (callbackFn: () => void, afterFn?: (ref: QSelect) => void) => void,
) => void;
const responsibleAreaOptions = ref<Record<string, unknown>[]>([]);
let responsibleAreaFilter: (
value: string,
update: (callbackFn: () => void, afterFn?: (ref: QSelect) => void) => void,
) => void;
onMounted(() => {
if (optionStore.globalOption?.nationality) {
nationalityFilter = selectFilterOptionRefMod(
ref(optionStore.globalOption.nationality),
nationalityOptions,
'label',
);
trainingPlaceFilter = selectFilterOptionRefMod(
ref(optionStore.globalOption.training),
trainingPlaceOptions,
'label',
);
responsibleAreaFilter = selectFilterOptionRefMod(
ref(optionStore.globalOption.area),
responsibleAreaOptions,
'label',
);
}
});
watch(
() => optionStore.globalOption,
() => {
nationalityFilter = selectFilterOptionRefMod(
ref(optionStore.globalOption.nationality),
nationalityOptions,
'label',
);
trainingPlaceFilter = selectFilterOptionRefMod(
ref(optionStore.globalOption.training),
trainingPlaceOptions,
'label',
);
},
);
</script>
<template>
<div class="row col-12">
@ -133,12 +186,11 @@ function deleteFile(name: string) {
/>
<SelectOffice
v-if="userType === 'MESSENGER'"
for="input-responsible-area"
v-model:value="responsibleArea"
v-if="userType === 'MESSENGER'"
:readonly="readonly"
:label="$t('personnel.form.responsibleArea')"
class="col"
/>
</div>
<div
@ -166,171 +218,207 @@ function deleteFile(name: string) {
class="row col-12 q-col-gutter-sm"
style="margin-left: 0px; padding-left: 0px"
>
<SelectInput
:model-value="readonly ? sourceNationality || '-' : sourceNationality"
<q-select
outlined
clearable
use-input
fill-input
emit-value
map-options
hide-selected
hide-bottom-space
input-debounce="0"
option-value="value"
option-label="label"
class="col-md-3 col-6"
id="input-source-nationality"
for="input-source-nationality"
:option="optionStore.globalOption.nationality"
class="col-md-3 col-6"
:readonly
clearable
:dense="dense"
:readonly="readonly"
:hide-dropdown-icon="readonly"
:label="$t('personnel.form.sourceNationality')"
:options="nationalityOptions"
@filter="nationalityFilter"
:model-value="readonly ? sourceNationality || '-' : sourceNationality"
@update:model-value="
(v) => (typeof v === 'string' ? (sourceNationality = v) : '')
"
/>
<SelectInput
v-model="importNationality"
@clear="sourceNationality = ''"
>
<template v-slot:no-option>
<q-item>
<q-item-section class="text-grey">
{{ $t('general.noData') }}
</q-item-section>
</q-item>
</template>
</q-select>
<q-select
outlined
clearable
use-input
fill-input
emit-value
map-options
hide-selected
hide-bottom-space
input-debounce="0"
option-value="value"
option-label="label"
class="col-md-3 col-6"
id="input-import-nationality"
for="input-import-nationality"
:option="optionStore.globalOption.nationality"
class="col-md-3 col-6"
:readonly
multiple
:hideSelected="false"
clearable
fillInput
:dense="dense"
:readonly="readonly"
:hide-dropdown-icon="readonly"
:label="$t('personnel.form.importNationality')"
/>
<SelectInput
:model-value="readonly ? checkpoint || '-' : checkpoint"
id="select-checkpoint"
for="select-checkpoint"
:option="optionStore.globalOption.border"
class="col-md-6 col-12"
:readonly
:label="$t('personnel.form.checkpoint')"
clearable
:options="nationalityOptions"
@filter="nationalityFilter"
:model-value="readonly ? importNationality || '-' : importNationality"
@update:model-value="
(v) => (typeof v === 'string' ? (checkpoint = v) : '')
(v) => (typeof v === 'string' ? (importNationality = v) : '')
"
/>
<SelectInput
:model-value="readonly ? trainingPlace || '-' : trainingPlace"
@clear="importNationality = ''"
>
<template v-slot:no-option>
<q-item>
<q-item-section class="text-grey">
{{ $t('general.noData') }}
</q-item-section>
</q-item>
</template>
</q-select>
<q-select
outlined
clearable
use-input
fill-input
emit-value
map-options
hide-selected
hide-bottom-space
input-debounce="0"
option-label="label"
option-value="label"
class="col-md-6 col-12"
id="select-trainig-place"
for="select-trainig-place"
:option="optionStore.globalOption.training"
class="col-md-8 col-12"
:readonly
:dense="dense"
:readonly="readonly"
:hide-dropdown-icon="readonly"
:label="$t('personnel.form.trainingPlace')"
clearable
:options="trainingPlaceOptions"
@filter="trainingPlaceFilter"
:model-value="readonly ? trainingPlace || '-' : trainingPlace"
@update:model-value="
(v) => (typeof v === 'string' ? (trainingPlace = v) : '')
"
/>
<SelectInput
:model-value="readonly ? agencyStatus || '-' : agencyStatus"
id="select-checkpoint-en"
for="select-checkpoint-en"
:option="[
{ label: $t('personnel.form.normal'), value: AgencyStatus.Normal },
{
label: $t('personnel.form.canceled'),
value: AgencyStatus.Canceled,
},
{
label: $t('personnel.form.blacklist'),
value: AgencyStatus.Blacklist,
},
]"
class="col-md-4 col-12"
:readonly
:label="$t('personnel.form.agencyStatus')"
clearable
@update:model-value="
(v) => (typeof v === 'string' ? (agencyStatus = v) : '')
"
/>
@clear="trainingPlace = ''"
>
<template v-slot:no-option>
<q-item>
<q-item-section class="text-grey">
{{ $t('general.noData') }}
</q-item-section>
</q-item>
</template>
</q-select>
<q-input
for="input-discount-condition"
for="input-checkpoint"
:dense="dense"
outlined
:readonly
:label="$t('general.remark')"
class="col-12"
type="textarea"
:model-value="readonly ? remark || '-' : remark"
:readonly="readonly"
:label="$t('personnel.form.checkpoint')"
class="col-6"
:model-value="readonly ? checkpoint || '-' : checkpoint"
@update:model-value="
(v) => (typeof v === 'string' ? (remark = v) : '')
(v) => (typeof v === 'string' ? (checkpoint = v) : '')
"
@clear="remark = ''"
@clear="checkpoint = ''"
/>
</div>
<q-file
v-if="userType && !readonly"
ref="attachmentRef"
for="input-attachment"
:dense="dense"
outlined
:readonly="readonly"
multiple
append
:label="$t('personnel.form.attachment')"
class="col"
v-model="userFile"
>
<template v-slot:prepend>
<Icon
icon="material-symbols:attach-file"
width="20px"
style="color: var(--brand-1)"
/>
</template>
<template v-slot:file="file">
<div class="row full-width items-center">
<span class="col ellipsis">
{{ file.file.name }}
</span>
<q-btn
dense
rounded
flat
padding="2 2"
class="app-text-muted"
icon="mdi-close-circle"
@click.stop="attachmentRef.removeAtIndex(file.index)"
<q-input
for="input-checkpoint-en"
:dense="dense"
outlined
:readonly="readonly"
:label="$t('personnel.form.checkpointEN')"
class="col-6"
:model-value="readonly ? checkpointEN || '-' : checkpointEN"
@update:model-value="
(v) => (typeof v === 'string' ? (checkpointEN = v) : '')
"
@clear="checkpointEN = ''"
/>
<q-file
ref="attachmentRef"
for="input-attachment"
:dense="dense"
outlined
:readonly="readonly"
multiple
append
:label="$t('personnel.form.attachment')"
class="col-12"
v-model="agencyFile"
>
<template v-slot:prepend>
<Icon
icon="material-symbols:attach-file"
width="20px"
style="color: var(--brand-1)"
/>
</div>
</template>
</q-file>
</template>
<template v-slot:file="file">
<div class="row full-width items-center">
<span class="col ellipsis">
{{ file.file.name }}
</span>
<q-btn
dense
rounded
flat
padding="2 2"
class="app-text-muted"
icon="mdi-close-circle"
@click.stop="attachmentRef.removeAtIndex(file.index)"
/>
</div>
</template>
</q-file>
<div v-if="userFileList && userFileList?.length > 0" class="col-12">
<q-list bordered separator class="rounded" style="padding: 0">
<q-item
id="attachment-file"
for="attachment-file"
v-for="item in userFileList"
clickable
:key="item.url"
class="items-center row"
@click="() => openNewTab(item.url)"
>
<q-item-section>
<div class="row items-center justify-between">
<div class="col">
{{ item.name }}
<div v-if="agencyFileList && agencyFileList?.length > 0" class="col-12">
<q-list bordered separator class="rounded" style="padding: 0">
<q-item
id="attachment-file"
for="attachment-file"
v-for="item in agencyFileList"
clickable
:key="item.url"
class="items-center row"
@click="() => openNewTab(item.url)"
>
<q-item-section>
<div class="row items-center justify-between">
<div class="col">
{{ item.name }}
</div>
<q-btn
id="delete-file"
v-if="!readonly && userId"
rounded
flat
dense
unelevated
size="md"
icon="mdi-trash-can-outline"
class="app-text-negative"
@click.stop="deleteFile(item.name)"
/>
</div>
<q-btn
id="delete-file"
v-if="!readonly && userId"
rounded
flat
dense
unelevated
size="md"
icon="mdi-trash-can-outline"
class="app-text-negative"
@click.stop="deleteFile(item.name)"
/>
</div>
</q-item-section>
</q-item>
</q-list>
</q-item-section>
</q-item>
</q-list>
</div>
</div>
</div>
</div>

View file

@ -1,8 +1,10 @@
<script setup lang="ts">
import { QSelect } from 'quasar';
import useOptionStore from 'stores/options';
import { selectFilterOptionRefMod } from 'stores/utils';
import { calculateAge, disabledAfterToday } from 'src/utils/datetime';
import { watch } from 'vue';
import SelectInput from '../shared/SelectInput.vue';
import { ref, onMounted, watch } from 'vue';
import { capitalize } from 'vue';
import DatePicker from '../shared/DatePicker.vue';
const optionStore = useOptionStore();
@ -21,8 +23,6 @@ const midNameEN = defineModel<string | null>('midNameEn');
const citizenId = defineModel<string>('citizenId');
const citizenIssue = defineModel<Date | null>('citizenIssue');
const citizenExpire = defineModel<Date | null>('citizenExpire');
const contactName = defineModel<string>('contactName');
const contactTel = defineModel<string>('contactTel');
const props = defineProps<{
dense?: boolean;
@ -30,19 +30,73 @@ const props = defineProps<{
readonly?: boolean;
separator?: boolean;
employee?: boolean;
agency?: boolean;
title?: string;
prefixId: string;
hideNameEn?: boolean;
}>();
function matchPreFixName() {
const prefixNameOptions = ref<Record<string, unknown>[]>([]);
let prefixNameFilter: (
value: string,
update: (callbackFn: () => void, afterFn?: (ref: QSelect) => void) => void,
) => void;
const genderOptions = ref<Record<string, unknown>[]>([]);
let genderFilter: (
value: string,
update: (callbackFn: () => void, afterFn?: (ref: QSelect) => void) => void,
) => void;
const nationalityOptions = ref<Record<string, unknown>[]>([]);
let nationalityFilter: (
value: string,
update: (callbackFn: () => void, afterFn?: (ref: QSelect) => void) => void,
) => void;
function matPreFixName() {
if (gender.value === 'male') prefixName.value = 'mr';
if (gender.value === 'female' && prefixName.value === 'mr') {
prefixName.value = 'mrs';
}
if (gender.value === 'female') prefixName.value = 'mrs';
}
onMounted(() => {
prefixNameFilter = selectFilterOptionRefMod(
ref(optionStore.globalOption?.prefix),
prefixNameOptions,
'label',
);
genderFilter = selectFilterOptionRefMod(
ref(optionStore.globalOption?.gender),
genderOptions,
'label',
);
nationalityFilter = selectFilterOptionRefMod(
ref(optionStore.globalOption?.nationality),
nationalityOptions,
'label',
);
});
watch(
() => optionStore.globalOption,
() => {
prefixNameFilter = selectFilterOptionRefMod(
ref(optionStore.globalOption.prefix),
prefixNameOptions,
'label',
);
genderFilter = selectFilterOptionRefMod(
ref(optionStore.globalOption.gender),
genderOptions,
'label',
);
nationalityFilter = selectFilterOptionRefMod(
ref(optionStore.globalOption.nationality),
nationalityOptions,
'label',
);
},
);
watch(
() => prefixName.value,
(v) => {
@ -56,7 +110,7 @@ watch(
() => gender.value,
() => {
if (props.readonly) return;
matchPreFixName();
matPreFixName();
},
);
</script>
@ -96,19 +150,40 @@ watch(
for="input-citizen-id"
/>
<div class="col-12 row" style="display: flex; gap: var(--size-2)">
<SelectInput
<q-select
outlined
use-input
fill-input
emit-value
map-options
hide-selected
hide-bottom-space
input-debounce="0"
option-label="label"
option-value="value"
hide-dropdown-icon
:readonly
:option="optionStore.globalOption?.prefix"
:id="`${prefixId}-select-prefix-name`"
:for="`${prefixId}-select-prefix-name`"
:rules="
agency ? [] : [(val: string) => !!val || $t('form.error.required')]
"
:label="$t('personnel.form.prefixName')"
autocomplete="off"
class="col-md-1 col-6"
v-model="prefixName"
/>
:dense="dense"
:readonly="readonly"
:options="prefixNameOptions"
:for="`${prefixId}-select-prefix-name`"
:label="$t('personnel.form.prefixName')"
@filter="prefixNameFilter"
:model-value="readonly ? prefixName || '-' : prefixName"
@update:model-value="
(v) => (typeof v === 'string' ? (prefixName = v) : '')
"
@clear="prefixName = ''"
>
<template v-slot:no-option>
<q-item>
<q-item-section class="text-grey">
{{ $t('general.noData') }}
</q-item-section>
</q-item>
</template>
</q-select>
<q-input
:for="`${prefixId}-input-first-name`"
@ -119,11 +194,7 @@ watch(
class="col"
:label="$t('personnel.form.firstName')"
v-model="firstName"
:rules="
employee || agency
? []
: [(val: string) => !!val || $t('form.error.required')]
"
:rules="[(val: string) => !!val || $t('form.error.required')]"
/>
<q-input
@ -158,16 +229,24 @@ watch(
class="col-12 row"
style="display: flex; gap: var(--size-2)"
>
<SelectInput
hide-dropdown-icon
:readonly
:option="optionStore.rawOption?.eng.prefix"
:id="`${prefixId}-select-prefix-name-en`"
:for="`${prefixId}-select-prefix-name-en`"
:rules="[(val: string) => !!val || $t('form.error.required')]"
label="Prefix"
<q-input
:for="`${prefixId}-input-first-name`"
:dense="dense"
outlined
hide-bottom-space
:readonly="readonly"
:disable="!readonly"
class="col-md-1 col-6"
v-model="prefixName"
label="Title"
:model-value="
readonly
? capitalize(prefixName || '') || '-'
: capitalize(prefixName || '')
"
@update:model-value="
(v) => (typeof v === 'string' ? (prefixName = v) : '')
"
@clear="prefixName = ''"
/>
<q-input
@ -208,16 +287,10 @@ watch(
class="col"
label="Surname"
v-model="lastNameEN"
:rules="
employee
? []
: [
(val: string) =>
!val ||
/^[A-Za-z\s]+$/.test(val) ||
$t('form.error.letterOnly'),
]
"
:rules="[
(val: string) =>
!val || /^[A-Za-z\s]+$/.test(val) || $t('form.error.letterOnly'),
]"
/>
</div>
@ -253,13 +326,16 @@ watch(
hide-bottom-space
:readonly="readonly"
:label="$t('form.email')"
:rules="[
(val) => (val && val.length > 0) || $t('form.error.required'),
(v: string) =>
!v ||
/^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$/g.test(v) ||
$t('form.error.invalid'),
]"
:rules="
readonly
? undefined
: [
(v: string) =>
!v ||
/^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$/g.test(v) ||
$t('form.error.invalid'),
]
"
class="col-md-3 col-6"
:model-value="readonly ? email || '-' : email"
@update:model-value="(v) => (typeof v === 'string' ? (email = v) : '')"
@ -275,16 +351,39 @@ watch(
</template>
</q-input>
<SelectInput
<q-select
v-if="!employee"
:readonly
:option="optionStore.globalOption?.gender"
:id="`${prefixId}-select-gender`"
outlined
use-input
fill-input
emit-value
map-options
hide-selected
hide-bottom-space
input-debounce="0"
option-label="label"
option-value="value"
autocomplete="off"
class="col-md-2 col-6"
:dense="dense"
:readonly="readonly"
:options="genderOptions"
:hide-dropdown-icon="readonly"
:for="`${prefixId}-select-gender`"
:label="$t('form.gender')"
class="col-md-2 col-6"
v-model="gender"
/>
@filter="genderFilter"
:model-value="readonly ? gender || '-' : gender"
@update:model-value="(v) => (typeof v === 'string' ? (gender = v) : '')"
@clear="gender = ''"
>
<template v-slot:no-option>
<q-item>
<q-item-section class="text-grey">
{{ $t('general.noData') }}
</q-item-section>
</q-item>
</template>
</q-select>
<DatePicker
v-model="birthDate"
@ -357,67 +456,72 @@ watch(
"
/>
<SelectInput
<q-select
v-if="employee"
:readonly
:option="optionStore.globalOption?.gender"
:id="`${prefixId}-select-gender`"
outlined
clearable
use-input
fill-input
emit-value
map-options
hide-selected
autocomplete="off"
hide-bottom-space
input-debounce="0"
option-label="label"
option-value="value"
class="col-md-2 col-6"
:dense="dense"
v-model="gender"
:readonly="readonly"
:options="genderOptions"
:hide-dropdown-icon="readonly"
:for="`${prefixId}-select-gender`"
:label="$t('form.gender')"
class="col-md-2 col-6"
v-model="gender"
/>
<SelectInput
:rules="[(val: string) => !!val || $t('form.error.required')]"
@filter="genderFilter"
>
<template v-slot:no-option>
<q-item>
<q-item-section class="text-grey">
{{ $t('general.noData') }}
</q-item-section>
</q-item>
</template>
</q-select>
<q-select
v-if="employee"
:readonly
:option="optionStore.globalOption.nationality"
:id="`${prefixId}-select-nationality`"
outlined
clearable
use-input
fill-input
emit-value
map-options
hide-selected
hide-bottom-space
autocomplete="off"
input-debounce="0"
option-label="label"
option-value="value"
v-model="nationality"
class="col-md-2 col-6"
:dense="dense"
:readonly="readonly"
:options="nationalityOptions"
:hide-dropdown-icon="readonly"
:for="`${prefixId}-select-nationality`"
:label="$t('general.nationality')"
class="col-md-2 col-6"
v-model="nationality"
clearable
/>
<q-input
v-if="agency"
for="input-agencies-contact-name"
dense
outlined
:readonly="readonly"
hide-bottom-space
class="col-md-4 col-12"
:label="$t('personnel.form.contactName')"
:model-value="readonly ? contactName || '-' : contactName"
@update:model-value="
(v) => (typeof v === 'string' ? (contactName = v) : '')
"
/>
<q-input
v-if="agency"
for="input-agencies-contact-tel"
id="input-agencies-contact-tel"
dense
outlined
:readonly="readonly"
hide-bottom-space
class="col-md-4 col-12"
:label="$t('personnel.form.contactTel')"
:model-value="readonly ? contactTel || '-' : contactTel"
@update:model-value="
(v) => (typeof v === 'string' ? (contactTel = v) : '')
"
:rules="[(val: string) => !!val || $t('form.error.required')]"
@filter="nationalityFilter"
>
<template #prepend>
<q-icon
size="xs"
name="mdi-phone-outline"
class="cursor-pointer"
color="primary"
/>
<template v-slot:no-option>
<q-item>
<q-item-section class="text-grey">
{{ $t('general.noData') }}
</q-item-section>
</q-item>
</template>
</q-input>
</q-select>
</div>
</div>
</template>

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -268,7 +268,6 @@ const insuranceCompanyFilter = selectFilterOptionRefMod(
<div class="col-md col-6">
<DatePicker
:label="$t('customerEmployee.formHealthCheck.coverageStartDate')"
v-model="checkup.coverageStartDate"
:id="`${prefixId}-input-coverage-start-date`"
:readonly="readonly || checkup.statusSave"

View file

@ -106,7 +106,7 @@ const employeeOther = defineModel<EmployeeOtherCreate>('employeeOther');
:readonly="readonly || employeeOther.statusSave"
hide-bottom-space
class="col-md-3 col-6"
:label="$t('general.nativeLanguage', { msg: $t('form.firstName') })"
:label="$t('form.firstName')"
:model-value="employeeOther.fatherFirstName"
@update:model-value="
(v) =>
@ -122,7 +122,7 @@ const employeeOther = defineModel<EmployeeOtherCreate>('employeeOther');
:readonly="readonly || employeeOther.statusSave"
hide-bottom-space
class="col-md-3 col-6"
:label="$t('general.nativeLanguage', { msg: $t('form.lastName') })"
:label="$t('form.lastName')"
:model-value="employeeOther.fatherLastName"
@update:model-value="
(v) =>
@ -177,7 +177,7 @@ const employeeOther = defineModel<EmployeeOtherCreate>('employeeOther');
:readonly="readonly || employeeOther.statusSave"
hide-bottom-space
class="col-md-3 col-6"
:label="$t('general.nativeLanguage', { msg: $t('form.firstName') })"
:label="$t('form.firstName')"
:model-value="employeeOther.motherFirstName"
@update:model-value="
(v) =>
@ -193,7 +193,7 @@ const employeeOther = defineModel<EmployeeOtherCreate>('employeeOther');
:readonly="readonly || employeeOther.statusSave"
hide-bottom-space
class="col-md-3 col-6"
:label="$t('general.nativeLanguage', { msg: $t('form.lastName') })"
:label="$t('form.lastName')"
:model-value="employeeOther.motherLastName"
@update:model-value="
(v) =>

View file

@ -9,8 +9,6 @@ import useOptionStore from 'stores/options';
import DatePicker from '../shared/DatePicker.vue';
import { dateFormat } from 'src/utils/datetime';
const optionStore = useOptionStore();
const { locale } = useI18n();
@ -20,8 +18,8 @@ const issuePlace = defineModel<string>('issuePlace');
const issueCountry = defineModel<string>('issueCountry');
const issueDate = defineModel<Date | null | string>('issueDate');
const type = defineModel<string>('type');
const expireDate = defineModel<Date | string>('expireDate');
const birthDate = defineModel<Date | string>('birthDate');
const expireDate = defineModel<Date>('expireDate');
const birthDate = defineModel<Date>('birthDate');
const workerStatus = defineModel<string>('workerStatus');
const nationality = defineModel<string>('nationality');
const gender = defineModel<string>('gender');
@ -34,8 +32,6 @@ const firstName = defineModel<string>('firstName');
const namePrefix = defineModel<string>('namePrefix');
const passportNumber = defineModel<string>('passportNumber');
const file = defineModel<File>('file');
const passportValidator = /[a-zA-Z]{1}[a-zA-Z0-9]{1}[0-9]{5,7}$/;
const genderOptions = ref<Record<string, unknown>[]>([]);
@ -107,7 +103,7 @@ onMounted(() => {
'label',
);
passportIssuingCountryFilter = selectFilterOptionRefMod(
ref(optionStore.rawOption?.eng.nationality),
ref(optionStore.globalOption.nationality),
passportIssuingCountryOptions,
'label',
);
@ -125,13 +121,13 @@ onMounted(() => {
);
genderFilter = selectFilterOptionRefMod(
ref(optionStore.rawOption?.eng.gender),
ref(optionStore.globalOption?.gender),
genderOptions,
'label',
);
nationalityFilter = selectFilterOptionRefMod(
ref(optionStore.rawOption?.eng.nationality),
ref(optionStore.globalOption?.nationality),
nationalityOptions,
'label',
);
@ -156,7 +152,7 @@ watch(
);
passportIssuingCountryFilter = selectFilterOptionRefMod(
ref(optionStore.rawOption?.eng.nationality),
ref(optionStore.globalOption.nationality),
passportIssuingCountryOptions,
'label',
);
@ -168,43 +164,19 @@ watch(
);
genderFilter = selectFilterOptionRefMod(
ref(optionStore.rawOption?.eng.gender),
ref(optionStore.globalOption?.gender),
genderOptions,
'label',
);
nationalityFilter = selectFilterOptionRefMod(
ref(optionStore.rawOption?.eng.nationality),
ref(optionStore.globalOption?.nationality),
nationalityOptions,
'label',
);
},
);
function browse() {
inputFile?.click();
}
const inputFile = (() => {
const _element = document.createElement('input');
_element.type = 'file';
_element.accept = 'image/jpeg,image/png';
_element.addEventListener('change', change);
return _element;
})();
async function change(e: Event) {
const _element = e.target as HTMLInputElement | null;
const _file = _element?.files?.[0];
if (_file) {
const newFileName = `passport-${dateFormat(new Date().toISOString())}-${_file.name}`;
const renamedFile = new File([_file], newFileName, { type: _file.type });
file.value = renamedFile;
}
}
watch(
() => namePrefix.value,
(v) => {
@ -249,14 +221,20 @@ watch(
</div>
<div class="q-col-gutter-sm" :class="{ row: $q.screen.gt.sm }">
<div v-if="!ocr">
<q-btn
flat
color="primary"
icon="mdi-upload-box-outline"
@click="() => browse()"
:disable="readonly"
></q-btn>
<div
class="col row justify-center q-col-gutter-sml"
style="max-height: 50%"
v-if="!ocr"
>
<q-avatar
style="border: 1px dashed; border-color: black"
square
size="100px"
font-size="50px"
color="grey-4"
text-color="grey"
icon="mdi-image-outline"
/>
</div>
<div
class="row q-col-gutter-sm"
@ -280,7 +258,7 @@ watch(
:options="workerStatusOptions"
:hide-dropdown-icon="readonly"
:for="`${prefixId}-select-visa-type`"
:label="$t('customerEmployee.form.workerStatus')"
:label="$t('customerEmployee.form.workerType')"
@filter="workerStatusFilter"
:model-value="readonly ? workerStatus || '-' : workerStatus"
@update:model-value="

View file

@ -28,22 +28,20 @@ const arrivalAt = defineModel<string>('arrivalAt');
const arrivalTMNo = defineModel<string>('arrivalTmNo');
const arrivalTM = defineModel<string>('arrivalTm');
const mrz = defineModel<string>('mrz');
const entryCount = defineModel<number | string>('entryCount');
const entryCount = defineModel<number>('entryCount');
const issuePlace = defineModel<string>('issuePlace');
const issueCountry = defineModel<string>('issueCountry');
const issueDate = defineModel<Date | null | string>('visaIssueDate');
const type = defineModel<string>('type');
const expireDate = defineModel<Date | string>('expireDate');
const type = defineModel<string>('visaType');
const expireDate = defineModel<Date>('expireDate');
const remark = defineModel<string>('remark');
const workerType = defineModel<string>('workerType');
const number = defineModel<string>('number');
const reportDate = defineModel<Date | null | string>('reportDate');
const number = defineModel<string>('visaNumber');
//
// const calculatedVisaDate = computed(() => {
// if (!issueDate.value) return undefined;
// return calculate90DayNext(issueDate.value);
// });
const calculatedVisaDate = computed(() => {
if (!issueDate.value) return undefined;
return calculate90DayNext(issueDate.value);
});
defineProps<{
title?: string;
@ -80,12 +78,6 @@ onMounted(async () => {
await fetchProvince();
});
const visaIssueCountryOptions = ref<Record<string, unknown>[]>([]);
let visaIssueCountryFilter: (
value: string,
update: (callbackFn: () => void, afterFn?: (ref: QSelect) => void) => void,
) => void;
const visaTypeOptions = ref<Record<string, unknown>[]>([]);
let visaTypeFilter: (
value: string,
@ -100,14 +92,8 @@ let workerTypeFilter: (
onMounted(() => {
visaTypeFilter = selectFilterOptionRefMod(
ref(optionStore.globalOption?.visaType),
visaTypeOptions,
'label',
);
visaIssueCountryFilter = selectFilterOptionRefMod(
ref(optionStore.globalOption?.nationality),
visaIssueCountryOptions,
visaTypeOptions,
'label',
);
@ -121,14 +107,8 @@ onMounted(() => {
watch(
() => optionStore.globalOption,
() => {
visaIssueCountryFilter = selectFilterOptionRefMod(
ref(optionStore.globalOption?.nationality),
visaIssueCountryOptions,
'label',
);
visaTypeFilter = selectFilterOptionRefMod(
optionStore.globalOption.visaType,
optionStore.globalOption.nationality,
visaTypeOptions,
'label',
);
@ -140,10 +120,6 @@ watch(
);
},
);
//
// watch([() => issueDate.value], () => {
// reportDate.value = calculate90DayNext(issueDate.value);
// });
</script>
<template>
@ -157,7 +133,7 @@ watch(
name="mdi-passport"
style="background-color: var(--surface-3)"
/>
{{ $t(title) }}
{{ title }}
</div>
<div
@ -377,12 +353,10 @@ watch(
<DatePicker
:id="`${prefixId}-date-picker-visa-issuance`"
:readonly
:disabled="!readonly"
:label="$t('customerEmployee.form.visa90Day')"
v-model="reportDate"
:model-value="calculatedVisaDate"
clearable
:rules="[
(val) => (val && val.length > 0) || $t('form.error.required'),
]"
/>
</div>
</div>
@ -448,11 +422,11 @@ watch(
class="col-md-4 col-6"
:dense="dense"
:readonly="readonly"
:options="visaIssueCountryOptions"
:options="visaTypeOptions"
:hide-dropdown-icon="readonly"
:for="`${prefixId}-select-issue-country`"
:label="$t('customerEmployee.form.issueCountry')"
@filter="visaIssueCountryFilter"
@filter="visaTypeFilter"
:model-value="readonly ? issueCountry || '-' : issueCountry"
@update:model-value="
(v) => (typeof v === 'string' ? (issueCountry = v) : '')

View file

@ -22,8 +22,6 @@ const prop = withDefaults(
inTable?: boolean;
addButton?: boolean;
prefixId?: string;
hideAction?: boolean;
hideDelete?: boolean;
}>(),
{
gridView: false,
@ -141,9 +139,8 @@ defineEmits<{
<q-avatar size="md">
<q-img
:src="
props.row.selectedImage
? `${baseUrl}/employee/${props.row.id}/image/${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"
:ratio="1"
@ -268,10 +265,9 @@ defineEmits<{
@click.stop="$emit('view', props.row)"
/>
<KebabAction
v-if="!inTable && !hideAction"
v-if="!inTable"
:id-name="props.row.firstName"
:status="props.row.status"
:hide-delete="hideDelete"
@view="$emit('view', props.row)"
@edit="$emit('edit', props.row)"
@delete="$emit('delete', props.row)"
@ -284,11 +280,9 @@ defineEmits<{
<template v-slot:item="props">
<div class="col-12 col-md-3 col-sm-6">
<PersonCard
history
:hide-delete="hideDelete"
:hide-action="hideAction"
:id="`card-${props.row.firstNameEN}`"
:field-selected="fieldSelected"
history
:prefix-id="props.row.firstNameEN ?? props.rowIndex"
:data="{
code: props.row.code,
@ -296,9 +290,9 @@ defineEmits<{
$i18n.locale === 'eng'
? `${props.row.firstNameEN} ${props.row.lastNameEN} `.trim()
: `${props.row.firstName} ${props.row.lastName} `.trim(),
img: props.row.selectedImage
? `${baseUrl}/employee/${props.row.id}/image/${props.row.selectedImage}`
: `/images/employee-avatar-${props.row.gender}.png`,
img:
`${baseUrl}/employee/${props.row.id}/image/${props.row.selectedImage}` ||
`/images/employee-avatar-${props.row.gender}.png`,
fallbackImg: `/images/employee-avatar-${props.row.gender}.png`,
male: props.row.gender === 'male',
female: props.row.gender === 'female',

View file

@ -3,7 +3,6 @@ import { QSelect } from 'quasar';
import { CustomerBranch } from 'stores/customer/types';
import { selectFilterOptionRefMod } from 'stores/utils';
import { onMounted, ref, watch } from 'vue';
import SelectCustomer from 'components/shared/select/SelectCustomer.vue';
import {
EditButton,
DeleteButton,
@ -12,7 +11,6 @@ import {
} from 'components/button';
import { useI18n } from 'vue-i18n';
import useOptionStore from 'stores/options';
import { formatAddress } from 'src/utils/address';
const { locale } = useI18n();
@ -23,13 +21,6 @@ const optionsBranch = defineModel<{ id: string; name: string }[]>(
);
// employee
const customerBranchId = defineModel<string>('customerBranchId');
const currentCustomerBranch = defineModel<CustomerBranch>(
'currentCustomerBranch',
);
const customerBranch = defineModel<{
id: string;
address: string;
@ -54,7 +45,6 @@ defineProps<{
employeeOwnerOption?: CustomerBranch[];
prefixId: string;
showBtnSave?: boolean;
disableCustomerSelect?: boolean;
}>();
defineEmits<{
@ -117,18 +107,180 @@ defineEmits<{
</div>
<div class="col-12 row" style="gap: var(--size-2)">
<SelectCustomer
id="form-select-customer-branch-id"
for="form-select-customer-branch-id"
v-model:value="customerBranchId"
v-model:value-option="currentCustomerBranch"
<q-select
:id="`${prefixId}-select-employer-branch`"
:for="`${prefixId}-select-employer-branch`"
:use-input="!customerBranch"
autocomplete="off"
input-debounce="0"
:hide-dropdown-icon="readonly"
:dense="dense"
outlined
:readonly="readonly"
hide-bottom-space
class="col-12"
:label="$t('customer.form.branchCode')"
class="col-12 field-two"
simple
required
:readonly
:disabled="disableCustomerSelect && !readonly"
/>
v-model="customerBranch"
:option-value="
(v) => ({
id: v.id,
address: v.address,
addressEN: v.addressEN,
provinceId: v.provinceId,
districtId: v.districtId,
subDistrictId: v.subDistrictId,
zipCode: v.zipCode,
})
"
emit-value
map-options
:options="employeeOwnerOption"
@filter="(val, update) => $emit('filterOwnerBranch', val, update)"
:rules="[
(val: string) =>
!!val ||
$t('form.error.selectField', {
field: $t('customerEmployee.branch'),
}),
]"
>
<template v-slot:option="scope">
<q-item
v-if="scope.opt"
v-bind="scope.itemProps"
class="row items-start col-12 no-padding"
>
<div class="q-ma-sm">
<i class="isax isax-frame5" style="color: var(--brand-1)" />
</div>
<div class="q-mt-sm">
<div>
<span v-if="scope.opt.customer.customerType">
<span style="font-weight: 600">
{{
scope.opt.customer.customerType === 'CORP'
? $t('customer.form.registerName')
: $t('customer.form.ownerName')
}}:
</span>
{{
scope.opt.customer.customerType === 'CORP'
? $i18n.locale === 'eng'
? scope.opt.registerNameEN
: scope.opt.registerName
: $i18n.locale === 'eng'
? `${optionStore.mapOption(scope.opt.namePrefix)} ${scope.opt.firstNameEN} ${scope.opt.lastNameEN}` ||
'-'
: `${optionStore.mapOption(scope.opt.namePrefix)} ${scope.opt.firstName} ${scope.opt.lastName}` ||
'-'
}}
({{ scope.opt.code }})
</span>
</div>
<div class="text-caption app-text-muted-2 q-mb-xs">
<span v-if="scope.opt.customer" class="col column">
{{
$t(
`branch.form.title.${scope.opt.code.endsWith('-00') ? 'branchHQLabel' : 'branchLabel'}`,
)
}}
{{
!scope.opt.code.endsWith('-00')
? +scope.opt.code.split('-')[1]
: ''
}}
</span>
<span v-if="scope.opt.province" class="col">
{{ $t('general.address') }}
{{
$i18n.locale === 'eng'
? `${scope.opt.addressEN || ''}, ${scope.opt.mooEN && `${$t('form.moo')} ${scope.opt.mooEN},`} ${scope.opt.soiEN && `${$t('form.soi')} ${scope.opt.soiEN},`} ${scope.opt.streetEN && `${scope.opt.streetEN} Rd,`} ${scope.opt.subDistrict.nameEN || ''}, ${scope.opt.district.nameEN || ''}, ${scope.opt.province.nameEN || ''}`
: `${scope.opt.address || ''}, ${scope.opt.moo && `${$t('form.moo')} ${scope.opt.moo},`} ${scope.opt.soi && `${$t('form.soi')} ${scope.opt.soi},`} ${scope.opt.street && `${$t('form.road')} ${scope.opt.street},`} ${scope.opt.subDistrict.name || ''}, ${scope.opt.district.name || ''}, ${scope.opt.province.name || ''}`
}}
{{ scope.opt.subDistrict?.zipCode || '' }}
</span>
</div>
</div>
</q-item>
<q-separator class="q-mx-sm" />
</template>
<template v-slot:selected-item="scope">
<div
v-if="scope.opt"
class="row items-center no-wrap"
style="width: 1px"
>
<div class="q-mr-sm">
<span style="font-weight: 600">
{{
scope.opt.customer.customerType === 'CORP'
? $t('customer.form.registerName')
: $t('customer.form.ownerName')
}}:
</span>
{{
scope.opt.customer.customerType === 'CORP'
? $i18n.locale === 'eng'
? scope.opt.registerNameEN
: scope.opt.registerName
: $i18n.locale === 'eng'
? `${optionStore.mapOption(scope.opt.namePrefix)} ${scope.opt.firstNameEN} ${scope.opt.lastNameEN}` ||
'-'
: `${optionStore.mapOption(scope.opt.namePrefix)} ${scope.opt.firstName} ${scope.opt.lastName}` ||
'-'
}}
({{ scope.opt.code }})
</div>
<div
class="text-caption app-text-muted-2"
v-if="scope.opt.customer && scope.opt.province"
>
{{
$t(
`branch.form.title.${scope.opt.code.endsWith('-00') ? 'branchHQLabel' : 'branchLabel'}`,
)
}}
{{
!scope.opt.code.endsWith('-00')
? +scope.opt.code.split('-')[1]
: ''
}}
{{ $t('general.address') }}
{{
$i18n.locale === 'eng'
? `${scope.opt.addressEN || ''}, ${scope.opt.mooEN && `${$t('form.moo')} ${scope.opt.mooEN},`} ${scope.opt.soiEN && `${$t('form.soi')} ${scope.opt.soiEN},`} ${scope.opt.streetEN && `${scope.opt.streetEN} Rd,`} ${scope.opt.subDistrict.nameEN || ''}, ${scope.opt.district.nameEN || ''}, ${scope.opt.province.nameEN || ''}`
: `${scope.opt.address || ''}, ${scope.opt.moo && `${$t('form.moo')} ${scope.opt.moo},`} ${scope.opt.soi && `${$t('form.soi')} ${scope.opt.soi},`} ${scope.opt.street && `${$t('form.road')} ${scope.opt.street},`} ${scope.opt.subDistrict.name || ''}, ${scope.opt.district.name || ''}, ${scope.opt.province.name || ''}`
}}
{{ scope.opt.subDistrict?.zipCode || '' }}
<q-tooltip v-if="scope.opt.customer && scope.opt.province">
{{ $t('customerBranch.form.title') }}:
{{ $t('general.address') }}
{{
$i18n.locale === 'eng'
? `${scope.opt.addressEN || ''}, ${scope.opt.mooEN && `${$t('form.moo')} ${scope.opt.mooEN},`} ${scope.opt.soiEN && `${$t('form.soi')} ${scope.opt.soiEN},`} ${scope.opt.streetEN && `${scope.opt.streetEN} Rd,`} ${scope.opt.subDistrict.nameEN || ''}, ${scope.opt.district.nameEN || ''}, ${scope.opt.province.nameEN || ''}`
: `${scope.opt.address || ''}, ${scope.opt.moo && `${$t('form.moo')} ${scope.opt.moo},`} ${scope.opt.soi && `${$t('form.soi')} ${scope.opt.soi},`} ${scope.opt.street && `${$t('form.road')} ${scope.opt.street},`} ${scope.opt.subDistrict.name || ''}, ${scope.opt.district.name || ''}, ${scope.opt.province.name || ''}`
}}
{{ scope.opt.subDistrict?.zipCode || '' }}
</q-tooltip>
</div>
</div>
</template>
<template v-slot:append>
<q-icon
v-if="!readonly && customerBranch"
name="mdi-close-circle"
@click.stop="customerBranch = undefined"
class="cursor-pointer clear-btn"
/>
</template>
</q-select>
<q-input
:for="`${prefixId}-input-code`"

View file

@ -6,14 +6,12 @@ import { useI18n } from 'vue-i18n';
import useUserStore from 'src/stores/user';
import useOptionStore from 'src/stores/options';
import { useWorkflowTemplate } from 'src/stores/workflow-template';
import { baseUrl } from 'stores/utils';
import { getRole } from 'src/services/keycloak';
import {
WorkflowUserInTable,
WorkflowTemplatePayload,
WorkFlowPayloadStep,
Group,
} from 'src/stores/workflow-template/types';
import { User } from 'src/stores/user/types';
@ -22,18 +20,15 @@ import ToggleButton from 'src/components/button/ToggleButton.vue';
import NoData from '../NoData.vue';
import SelectBranch from '../shared/select/SelectBranch.vue';
import AddButton from '../button/AddButton.vue';
import { QField } from 'quasar';
defineProps<{
readonly?: boolean;
onDrawer?: boolean;
hideAction?: boolean;
}>();
const { t } = useI18n();
const userStore = useUserStore();
const optionStore = useOptionStore();
const workflowStore = useWorkflowTemplate();
const userInTable = defineModel<WorkflowUserInTable[]>('userInTable', {
default: [],
@ -48,7 +43,7 @@ const flowData = defineModel<WorkflowTemplatePayload>('flowData', {
},
});
let objectOptions = [
const objectOptions = [
...(optionStore.globalOption?.agenciesType || []),
{ label: t('flow.customer'), value: 'customer' },
{ label: t('flow.officer'), value: 'officer' },
@ -56,9 +51,7 @@ let objectOptions = [
const options = ref(objectOptions);
const role = ref<string[]>([]);
const userList = ref<User[]>([]);
const groupList = ref<Group[]>([]);
const responsiblePersonSearch = ref('');
const responsibleMenu = ref(false);
async function getUserList(opts?: { query: string }) {
const resUser = await userStore.fetchList({
@ -67,10 +60,10 @@ async function getUserList(opts?: { query: string }) {
if (resUser) userList.value = resUser.result;
}
async function getGroupList() {
const resGroup = await workflowStore.getGroupList();
if (resGroup) groupList.value = resGroup;
}
// async function getUserById(responsiblePersonId: string) {
// const resUser = await userStore.fetchById(responsiblePersonId);
// if (resUser) userInTable.value.push(resUser);
// }
function selectResponsiblePerson(stepIndex: number, responsiblePerson: User) {
const currStep = flowData.value.step[stepIndex];
@ -85,7 +78,6 @@ function selectResponsiblePerson(stepIndex: number, responsiblePerson: User) {
userInTable.value[stepIndex] = {
name: flowData.value.step[stepIndex].name,
responsiblePerson: [],
responsibleGroup: [],
};
}
@ -109,33 +101,6 @@ function selectResponsiblePerson(stepIndex: number, responsiblePerson: User) {
}
}
function selectResponsibleGroup(stepIndex: number, responsibleGroup: string) {
const currStep = flowData.value.step[stepIndex];
const existGroupIndex = currStep.responsibleGroup?.findIndex(
(p) => p === responsibleGroup,
);
if (existGroupIndex === -1) {
currStep.responsibleGroup?.push(responsibleGroup);
if (!userInTable.value[stepIndex]) {
userInTable.value[stepIndex] = {
name: flowData.value.step[stepIndex].name,
responsiblePerson: [],
responsibleGroup: [],
};
}
userInTable.value[stepIndex]?.responsibleGroup.push(responsibleGroup);
} else {
currStep.responsibleGroup?.splice(Number(existGroupIndex), 1);
userInTable.value[stepIndex]?.responsibleGroup.splice(
Number(existGroupIndex),
1,
);
}
}
function selectItem(
val: Record<string, unknown>,
responsibleInstitution?: string[],
@ -177,7 +142,6 @@ watch(
onMounted(async () => {
role.value = getRole() || [];
await getUserList();
await getGroupList();
await userStore.fetchHqOption();
});
</script>
@ -202,7 +166,6 @@ onMounted(async () => {
:class="{ 'q-ml-lg': $q.screen.gt.xs, 'q-mt-sm': $q.screen.lt.sm }"
>
<ToggleButton
:disable="hideAction"
class="q-mr-sm"
two-way
:model-value="flowData.status !== 'INACTIVE'"
@ -504,128 +467,92 @@ onMounted(async () => {
</div>
<!-- RESPONSIBLE-PERSON -->
<q-field
<q-select
v-if="step.responsiblePersonId"
behavior="menu"
:for="`select-responsible-person-${index}-${onDrawer ? 'drawer' : 'dialog'}`"
:bg-color="readonly ? 'transparent' : ''"
:readonly
outlined
:stack-label="
userInTable[index]?.responsiblePerson.length > 0 ||
userInTable[index]?.responsibleGroup.length > 0
"
:label="$t('flow.responsiblePerson')"
dense
v-model="step.responsiblePersonId"
multiple
:options="[1, 2, 3]"
hide-bottom-space
option-label="label"
option-value="value"
emit-value
:label="$t('flow.responsiblePerson')"
class="col-md-6 col-12"
:class="{ 'cursor-pointer': !readonly }"
:hide-dropdown-icon="readonly"
>
<template #control>
<q-item
dense
class="items-center full-width no-padding"
v-for="person in userInTable[
index
]?.responsiblePerson.filter((p) =>
step.responsiblePersonId.includes(p.id),
)"
:key="person.id"
>
<q-avatar class="q-ml-sm" size="md">
<q-img
class="text-center"
:ratio="1"
:src="`${baseUrl}/user/${person.id}/profile-image/${person.selectedImage}`"
>
<template #error>
<div
class="no-padding full-width full-height flex items-center justify-center"
:style="`${person.gender ? 'background: white' : 'background: linear-gradient(135deg,rgba(43, 137, 223, 1) 0%, rgba(230, 51, 81, 1) 100%);'}`"
>
<q-img
v-if="person.gender"
:src="
person.gender === 'male'
? '/no-img-man.png'
: '/no-img-female.png'
"
/>
<q-icon
v-else
size="sm"
name="mdi-account-outline"
style="color: white"
/>
</div>
</template>
</q-img>
</q-avatar>
<template v-slot:selected-item="scope">
<div class="column full-width">
<div
class="column q-pl-md"
style="color: var(--foreground)"
class="row items-center no-wrap"
v-for="person in userInTable[
index
]?.responsiblePerson.filter(
(p) => p.id === scope.opt,
)"
:key="person.id"
>
<span>
{{
`${optionStore.mapOption(person.namePrefix || '')} ${
$i18n.locale === 'eng'
? person.firstNameEN
: person.firstName
} ${
$i18n.locale === 'eng'
? person.lastNameEN
: person.lastName
}`
}}
</span>
<span class="text-caption app-text-muted">
{{ person.code }}
</span>
<q-avatar class="q-ml-sm" size="md">
<q-img
class="text-center"
:ratio="1"
:src="`${baseUrl}/user/${person.id}/profile-image/${person.selectedImage}`"
>
<template #error>
<div
class="no-padding full-width full-height flex items-center justify-center"
:style="`${person.gender ? 'background: white' : 'background: linear-gradient(135deg,rgba(43, 137, 223, 1) 0%, rgba(230, 51, 81, 1) 100%);'}`"
>
<q-img
v-if="person.gender"
:src="
person.gender === 'male'
? '/no-img-man.png'
: '/no-img-female.png'
"
/>
<q-icon
v-else
size="sm"
name="mdi-account-outline"
style="color: white"
/>
</div>
</template>
</q-img>
</q-avatar>
<div
class="column q-pl-md"
style="color: var(--foreground)"
>
<span>
{{
`${optionStore.mapOption(person.namePrefix || '')} ${
$i18n.locale === 'eng'
? person.firstNameEN
: person.firstName
} ${
$i18n.locale === 'eng'
? person.lastNameEN
: person.lastName
}`
}}
</span>
<span class="text-caption app-text-muted">
{{ person.code }}
</span>
</div>
</div>
</q-item>
<div
v-if="step.responsibleGroup.length > 0"
class="full-width app-text-muted text-weight-medium"
style="font-size: 10px"
>
{{ $t('general.group') }}
</div>
<q-item
class="items-center full-width no-padding"
v-for="group in userInTable[
index
]?.responsibleGroup.filter((g) =>
step.responsibleGroup.includes(g),
)"
:key="group"
dense
>
<q-avatar class="q-ml-sm" size="md">
<q-img
class="text-center"
:ratio="1"
:src="`/img-group.png`"
/>
</q-avatar>
<span class="q-pl-md">
{{ group }}
</span>
</q-item>
</template>
<template v-if="!readonly" #append>
<q-icon
name="mdi-menu-down"
:class="{ rotated: responsibleMenu }"
class="transition-rotate"
/>
</template>
<q-menu
v-if="!readonly"
no-focus
no-refocus
:offset="[0, 4]"
@before-show="() => (responsibleMenu = true)"
@before-hide="() => (responsibleMenu = false)"
>
<template v-slot:option></template>
<q-menu v-if="!readonly" :offset="[0, 4]">
<q-list>
<q-item>
<q-input
@ -654,7 +581,6 @@ onMounted(async () => {
{{ $t('general.noData') }}
</q-item>
<q-item
v-else
v-for="(person, i) in userList"
dense
:key="i"
@ -729,7 +655,6 @@ onMounted(async () => {
{{ $t('personnel.MESSENGER') }}
</span>
<q-item
dense
clickable
@click="step.messengerByArea = !step.messengerByArea"
class="column"
@ -745,49 +670,9 @@ onMounted(async () => {
</div>
</div>
</q-item>
<span class="text-caption app-text-muted-2 q-px-md">
{{ $t('general.group') }}
</span>
<q-item
v-if="groupList.length === 0"
class="app-text-muted q-px-lg"
>
{{ $t('general.noData') }}
</q-item>
<q-item
v-else
v-for="(group, i) in groupList"
dense
clickable
@click="selectResponsibleGroup(index, group.name)"
class="column"
>
<div class="row items-center">
<q-checkbox
size="xs"
:model-value="
step.responsibleGroup.includes(group.name)
"
@click.stop="
selectResponsibleGroup(index, group.name)
"
/>
<q-avatar class="q-ml-sm" size="md">
<q-img
class="text-center"
:ratio="1"
:src="`/img-group.png`"
/>
</q-avatar>
<div class="column q-pl-md">
<span>{{ group.name }}</span>
</div>
</div>
</q-item>
</q-list>
</q-menu>
</q-field>
</q-select>
<!-- RESPONSIBLE-AGENCIES, RESPONSIBLE-INSTITUTION -->
<q-select
@ -902,8 +787,8 @@ onMounted(async () => {
}
:deep(
.q-item__section.column.q-item__section--side.justify-center.q-item__section--avatar.q-focusable.relative-position.cursor-pointer
) {
.q-item__section.column.q-item__section--side.justify-center.q-item__section--avatar.q-focusable.relative-position.cursor-pointer
) {
justify-content: start !important;
padding-right: 8px !important;
padding-top: 16px;
@ -915,26 +800,19 @@ onMounted(async () => {
}
:deep(
i.q-icon.mdi.mdi-chevron-down-circle.q-expansion-item__toggle-icon.q-expansion-item__toggle-icon--rotated
) {
i.q-icon.mdi.mdi-chevron-down-circle.q-expansion-item__toggle-icon.q-expansion-item__toggle-icon--rotated
) {
color: var(--brand-1);
}
:deep(
.q-item.q-item-type.row.no-wrap.q-item--dense.q-item--clickable.q-link.cursor-pointer.q-focusable.q-hoverable.expansion-rounded.surface-2
.q-focus-helper
) {
.q-item.q-item-type.row.no-wrap.q-item--dense.q-item--clickable.q-link.cursor-pointer.q-focusable.q-hoverable.expansion-rounded.surface-2
.q-focus-helper
) {
visibility: hidden;
}
:deep(.q-dialog.fullscreen.no-pointer-events.q-dialog--modal) {
visibility: hidden;
}
.transition-rotate {
transition: transform 0.3s ease;
}
.rotated {
transform: rotate(180deg);
}
</style>

View file

@ -167,28 +167,26 @@ withDefaults(
<div class="col-12 full-width">
<q-table
:rows-per-page-options="[0]"
:rows="
[
priceDisplay.price && {
label: $t('productService.product.salePrice'),
pricePerUnit: price,
calcVat,
vatIncluded,
},
priceDisplay.agentPrice && {
label: $t('productService.product.agentPrice'),
calcVat: agentPriceCalcVat,
vatIncluded: agentPriceVatIncluded,
pricePerUnit: agentPrice,
},
priceDisplay.serviceCharge && {
label: $t('productService.product.processingPrice'),
calcVat: serviceChargeCalcVat,
vatIncluded: serviceChargeVatIncluded,
pricePerUnit: serviceCharge,
},
].filter(Boolean)
"
:rows="[
{
label: $t('productService.product.salePrice'),
pricePerUnit: price,
calcVat,
vatIncluded,
},
{
label: $t('productService.product.agentPrice'),
calcVat: agentPriceCalcVat,
vatIncluded: agentPriceVatIncluded,
pricePerUnit: agentPrice,
},
{
label: $t('productService.product.processingPrice'),
calcVat: serviceChargeCalcVat,
vatIncluded: serviceChargeVatIncluded,
pricePerUnit: serviceCharge,
},
]"
:columns
hide-bottom
bordered
@ -246,19 +244,16 @@ withDefaults(
<template v-if="col.name === '#calcVat'">
<q-checkbox
v-if="priceDisplay?.price && props.rowIndex === 0"
:disable="readonly"
v-model="calcVat"
size="xs"
/>
<q-checkbox
v-if="priceDisplay?.agentPrice && props.rowIndex === 1"
:disable="readonly"
v-model="agentPriceCalcVat"
size="xs"
/>
<q-checkbox
v-if="priceDisplay?.serviceCharge && props.rowIndex === 2"
:disable="readonly"
v-model="serviceChargeCalcVat"
size="xs"
/>
@ -276,8 +271,6 @@ withDefaults(
flat
outlined
dense
:readonly
:hide-dropdown-icon="readonly"
v-model="vatIncluded"
></q-select>
<q-select
@ -292,8 +285,6 @@ withDefaults(
flat
outlined
dense
:readonly
:hide-dropdown-icon="readonly"
v-model="agentPriceVatIncluded"
></q-select>
<q-select
@ -308,8 +299,6 @@ withDefaults(
flat
outlined
dense
:readonly
:hide-dropdown-icon="readonly"
v-model="serviceChargeVatIncluded"
></q-select>
</template>

View file

@ -98,8 +98,7 @@ watch(
(c, o) => {
const list = c.map((v: { name: string }) => v.name);
const oldList = o.map((v: { name: string }) => v.name);
const index = workName.value ? oldList.indexOf(workName.value) : -1;
if (index === -1) return;
const index = oldList.indexOf(workName.value || '');
if (
list[index] !== oldList[index] &&
@ -705,8 +704,8 @@ watch(
}
:deep(
.q-item__section.column.q-item__section--side.justify-center.q-item__section--avatar.q-focusable.relative-position.cursor-pointer
) {
.q-item__section.column.q-item__section--side.justify-center.q-item__section--avatar.q-focusable.relative-position.cursor-pointer
) {
justify-content: start !important;
padding-right: 8px !important;
padding-top: 16px;
@ -736,8 +735,8 @@ watch(
}
:deep(
i.q-icon.mdi.mdi-chevron-down-circle.q-expansion-item__toggle-icon.q-expansion-item__toggle-icon--rotated
) {
i.q-icon.mdi.mdi-chevron-down-circle.q-expansion-item__toggle-icon.q-expansion-item__toggle-icon--rotated
) {
color: var(--brand-1);
}

View file

@ -1,6 +1,7 @@
<script lang="ts" setup>
import { nextTick, onMounted, ref, watch } from 'vue';
import { DeleteButton, EditButton, SaveButton, UndoButton } from '../button';
import { scrollToElement } from 'stores/utils';
// import { useI18n } from 'vue-i18n';
// import { storeToRefs } from 'pinia';
@ -137,8 +138,11 @@ watch(
@click="
() => {
assignClone();
cloneList = cloneList.filter((item) => item.name !== '');
nameList = nameList.filter((item) => item.name !== '');
if (nameList[nameList.length - 1].name === '') {
$emit('delete', cloneList[cloneList.length - 1].id, true);
cloneList = cloneList.filter((item) => item.name !== '');
nameList = nameList.filter((item) => item.name !== '');
}
}
"
/>

View file

@ -48,7 +48,6 @@ defineProps<{
readonly?: boolean;
onDrawer?: boolean;
inputOnly?: boolean;
disableToggle?: boolean;
}>();
defineEmits<{
@ -77,7 +76,6 @@ defineEmits<{
<ToggleButton
class="q-mr-sm"
two-way
:disable="disableToggle"
:model-value="status !== 'INACTIVE'"
@click="
() => {
@ -197,8 +195,8 @@ defineEmits<{
}
:deep(
.q-item__section.column.q-item__section--side.justify-center.q-item__section--avatar.q-focusable.relative-position.cursor-pointer
) {
.q-item__section.column.q-item__section--side.justify-center.q-item__section--avatar.q-focusable.relative-position.cursor-pointer
) {
justify-content: start !important;
padding-right: 8px !important;
padding-top: 16px;
@ -210,15 +208,15 @@ defineEmits<{
}
:deep(
i.q-icon.mdi.mdi-chevron-down-circle.q-expansion-item__toggle-icon.q-expansion-item__toggle-icon--rotated
) {
i.q-icon.mdi.mdi-chevron-down-circle.q-expansion-item__toggle-icon.q-expansion-item__toggle-icon--rotated
) {
color: var(--brand-1);
}
:deep(
.q-item.q-item-type.row.no-wrap.q-item--dense.q-item--clickable.q-link.cursor-pointer.q-focusable.q-hoverable.expansion-rounded.surface-2
.q-focus-helper
) {
.q-item.q-item-type.row.no-wrap.q-item--dense.q-item--clickable.q-link.cursor-pointer.q-focusable.q-hoverable.expansion-rounded.surface-2
.q-focus-helper
) {
visibility: hidden;
}

View file

@ -2,18 +2,11 @@
import SelectCustomer from '../shared/select/SelectCustomer.vue';
import SelectBranch from '../shared/select/SelectBranch.vue';
import { CustomerBranch } from 'src/stores/customer';
import { ref } from 'vue';
const branchId = defineModel<string>('branchId');
const customerBranchId = defineModel<string>('customerBranchId');
const agentPrice = defineModel<boolean>('agentPrice');
const special = defineModel<boolean>('special');
const customerBranchOption = defineModel<CustomerBranch>(
'customerBranchOption',
);
defineProps<{
outlined?: boolean;
readonly?: boolean;
@ -74,12 +67,8 @@ defineEmits<{
required
:readonly
/>
<SelectCustomer
id="about-select-customer-branch-id"
for="about-select-customer-branch-id"
v-model:value="customerBranchId"
v-model:value-option="customerBranchOption"
:label="$t('quotation.customer')"
:creatable-disabled-text="`(${$t('form.error.selectField', {
field: $t('quotation.branchVirtual'),
@ -99,14 +88,14 @@ defineEmits<{
<style scoped>
:deep(
label.q-field.row.no-wrap.items-start.q-field--outlined.q-select.field-one
) {
label.q-field.row.no-wrap.items-start.q-field--outlined.q-select.field-one
) {
padding-right: 4px;
}
:deep(
label.q-field.row.no-wrap.items-start.q-field--outlined.q-select.field-two
) {
label.q-field.row.no-wrap.items-start.q-field--outlined.q-select.field-two
) {
padding-left: 4px;
}
</style>

View file

@ -58,15 +58,16 @@ const currentBtnOpen = ref<{ title: string; opened: boolean[] }[]>([
function calcPrice(c: (typeof rows.value)[number]) {
const originalPrice = c.pricePerUnit;
const finalPricePerUnit = precisionRound(
originalPrice +
(c.product[props.agentPrice ? 'agentPriceCalcVat' : 'calcVat']
? originalPrice * (config.value?.vat || 0.07)
: 0),
const finalPriceWithVat = precisionRound(
originalPrice + originalPrice * (config.value?.vat || 0.07),
);
const price = finalPricePerUnit * c.amount - c.discount;
const finalPriceNoVat = finalPriceWithVat / (1 + (config.value?.vat || 0.07));
return precisionRound(price);
const price = finalPriceNoVat * c.amount - c.discount;
const vat = c.product[props.agentPrice ? 'agentPriceCalcVat' : 'calcVat']
? (finalPriceNoVat * c.amount - c.discount) * (config.value?.vat || 0.07)
: 0;
return precisionRound(price + vat);
}
const discount4Show = ref<string[]>([]);
@ -434,20 +435,8 @@ watch(
<q-td align="right">
{{
formatNumberDecimal(
props.row.product[
agentPrice ? 'agentPriceCalcVat' : 'calcVat'
]
? precisionRound(
(props.row.pricePerUnit *
(1 + (config?.vat || 0.07)) *
props.row.amount -
props.row.discount) /
(1 + (config?.vat || 0.07)),
)
: precisionRound(
props.row.pricePerUnit * props.row.amount -
props.row.discount,
),
props.row.pricePerUnit * props.row.amount -
props.row.discount,
2,
)
}}
@ -459,12 +448,9 @@ watch(
agentPrice ? 'agentPriceCalcVat' : 'calcVat'
]
? precisionRound(
((props.row.pricePerUnit *
(1 + (config?.vat || 0.07)) *
props.row.amount -
props.row.discount) /
(1 + (config?.vat || 0.07))) *
0.07,
(props.row.pricePerUnit * props.row.amount -
props.row.discount) *
(config?.vat || 0.07),
)
: 0,
2,

View file

@ -1,6 +1,6 @@
<script lang="ts" setup>
import { QTableProps } from 'quasar';
import { dateFormat, dateFormatJS } from 'src/utils/datetime';
import { dateFormat } from 'src/utils/datetime';
import { formatNumberDecimal } from 'stores/utils';
@ -19,8 +19,6 @@ const props = withDefaults(
page?: number;
pageSize?: number;
hideBtnPreview?: boolean;
hideAction?: boolean;
hideDelete?: boolean;
}>(),
{
row: () => [],
@ -86,11 +84,11 @@ defineEmits<{
</q-td>
<q-td v-if="visibleColumns.includes('createdAt')">
{{ dateFormatJS({ date: props.row.createdAt }) }}
{{ dateFormat(props.row.createdAt) }}
</q-td>
<q-td v-if="visibleColumns.includes('dueDate')">
{{ dateFormatJS({ date: props.row.dueDate }) }}
{{ dateFormat(props.row.dueDate) }}
</q-td>
<q-td v-if="visibleColumns.includes('contactName')">
@ -149,12 +147,12 @@ defineEmits<{
flat
@click.stop="$emit('view', props.row)"
/>
<KebabAction
v-if="!hideAction"
:idName="`btn-kebab-${props.row.workName}`"
status="'ACTIVE'"
hide-toggle
:hide-delete
hide-delete
:hide-edit="hideEdit"
@view="$emit('view', props.row)"
@edit="$emit('edit', props.row)"

View file

@ -1,10 +1,6 @@
<script lang="ts" setup>
import { ref } from 'vue';
import { useI18n } from 'vue-i18n';
import SelectInput from '../shared/SelectInput.vue';
import useOptionStore from 'src/stores/options';
import { Icon } from '@iconify/vue/dist/iconify.js';
import { useInstitution } from 'src/stores/institution';
const optionStore = useOptionStore();
@ -14,31 +10,12 @@ defineProps<{
readonly?: boolean;
onDrawer?: boolean;
}>();
const emit = defineEmits<{
(e: 'deleteAttachment', name: string): void;
}>();
const attachmentRef = ref();
const group = defineModel('group', { default: '' });
const name = defineModel('name', { default: '' });
const nameEn = defineModel('nameEn', { default: '' });
const contactName = defineModel('contactName', { default: '' });
const email = defineModel('email', { default: '' });
const contactTel = defineModel('contactTel', { default: '' });
const attachment = defineModel<File[]>('attachment');
const attachmentList =
defineModel<{ name: string; url: string }[]>('attachmentList');
type Options = { label: string; value: string };
function openNewTab(url: string) {
window.open(url, '_blank');
}
function deleteAttachment(name: string) {
emit('deleteAttachment', name);
}
</script>
<template>
<div class="row col-12">
@ -58,7 +35,7 @@ function deleteAttachment(name: string) {
<div class="col-12 row q-col-gutter-sm">
<SelectInput
class="col"
:class="{ col: $q.screen.lt.md }"
:disable="!readonly && onDrawer"
:readonly="readonly"
for="input-agencies-code"
@ -85,15 +62,10 @@ function deleteAttachment(name: string) {
outlined
:readonly="readonly"
hide-bottom-space
class="col-md-4 col-12"
class="col-md col-12"
:label="$t('agencies.name')"
v-model="name"
:rules="[
(val) => !!val || $t('form.error.required'),
(val) =>
/^[A-Za-z0-9ก-๙\s&.,'-]+$/.test(val) ||
$t('form.error.branchNameField'),
]"
:rules="[(val: string) => !!val || $t('form.error.required')]"
/>
<q-input
for="input-agencies-name-en"
@ -101,159 +73,10 @@ function deleteAttachment(name: string) {
outlined
:readonly="readonly"
hide-bottom-space
class="col-md-4 col-12"
class="col-md col-12"
:label="'Agencies Name'"
v-model="nameEn"
:rules="
nameEn
? [
(val) =>
/^[A-Za-z0-9ก-๙\s&.,'-]+$/.test(val) ||
$t('form.error.branchNameENField'),
]
: []
"
/>
<q-input
for="input-agencies-contact-name"
dense
outlined
:readonly="readonly"
hide-bottom-space
class="col-md-4 col-12"
:label="$t('agencies.contactName')"
:model-value="readonly ? contactName || '-' : contactName"
@update:model-value="
(v) => (typeof v === 'string' ? (contactName = v) : '')
"
/>
<q-input
for="input-agencies-email"
dense
outlined
hide-bottom-space
:readonly="readonly"
:label="$t('form.email')"
:rules="
readonly
? undefined
: [
(v: string) =>
!v ||
/^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$/g.test(v) ||
$t('form.error.invalid'),
]
"
class="col-md-4 col-12"
:model-value="readonly ? email || '-' : email"
@update:model-value="(v) => (typeof v === 'string' ? (email = v) : '')"
@clear="email = ''"
>
<template #prepend>
<q-icon
size="xs"
name="mdi-email-outline"
class="cursor-pointer"
color="primary"
/>
</template>
</q-input>
<q-input
for="input-agencies-contact-tel"
id="input-agencies-contact-tel"
dense
outlined
:readonly="readonly"
hide-bottom-space
class="col-md-4 col-12"
:label="$t('agencies.contactTel')"
:model-value="readonly ? contactTel || '-' : contactTel"
@update:model-value="
(v) => (typeof v === 'string' ? (contactTel = v) : '')
"
>
<template #prepend>
<q-icon
size="xs"
name="mdi-phone-outline"
class="cursor-pointer"
color="primary"
/>
</template>
</q-input>
<q-file
v-if="!readonly"
ref="attachmentRef"
for="input-attachment"
dense
outlined
multiple
append
:readonly
:label="$t('personnel.form.attachment')"
class="col"
v-model="attachment"
>
<template v-slot:prepend>
<Icon
icon="material-symbols:attach-file"
width="20px"
style="color: var(--brand-1)"
/>
</template>
<template v-slot:file="file">
<div class="row full-width items-center">
<span class="col ellipsis">
{{ file.file.name }}
</span>
<q-btn
dense
rounded
flat
padding="2 2"
class="app-text-muted"
icon="mdi-close-circle"
@click.stop="attachmentRef.removeAtIndex(file.index)"
/>
</div>
</template>
</q-file>
<div v-if="attachmentList && attachmentList?.length > 0" class="col-12">
<q-list bordered separator class="rounded" style="padding: 0">
<q-item
id="attachment-file"
for="attachment-file"
v-for="item in attachmentList"
clickable
:key="item.url"
class="items-center row"
@click="() => openNewTab(item.url)"
>
<q-item-section>
<div class="row items-center justify-between">
<div class="col">
{{ item.name }}
</div>
<q-btn
v-if="!readonly"
id="delete-file"
rounded
flat
dense
unelevated
size="md"
icon="mdi-trash-can-outline"
class="app-text-negative"
@click.stop="deleteAttachment(item.name)"
/>
</div>
</q-item-section>
</q-item>
</q-list>
</div>
</div>
</div>
</template>

View file

@ -27,38 +27,26 @@ withDefaults(
class="app-text-muted q-pr-sm"
:width="iconSize || '2rem'"
/>
<span :id="`dd-wrapper-${label}`" class="row col">
<span
:id="`dd-label-${label}`"
class="col-12 app-text-muted-2"
style="font-size: 10px"
>
<span class="row col">
<span class="col-12 app-text-muted-2" style="font-size: 10px">
{{ label }}
</span>
<span :id="`dd-value-wrapper-${label}`" class="col-12 ellipsis">
<span class="col-12 ellipsis">
<slot name="value">
<span
:class="{ 'link cursor-pointer': clickable }"
v-if="typeof value === 'string'"
@click="clickable ? $emit('labelClick', value, null) : undefined"
:id="`link-${value}`"
:for="`link-${value}`"
@click="$emit('labelClick', value, null)"
>
{{ value }}
<q-tooltip v-if="tooltip" :delay="500">{{ value }}</q-tooltip>
</span>
<span
:id="`dd-value-${label}`"
v-else
:class="{ 'link cursor-pointer': clickable }"
>
<span v-else :class="{ 'link cursor-pointer': clickable }">
<span
v-for="(item, index) in value"
:key="index"
@click="$emit('labelClick', item, index)"
class="link cursor-pointer"
:id="`link-${item}`"
:for="`link-${item}`"
>
{{ item }}
<span v-if="index < value.length - 1">,&nbsp;</span>

View file

@ -8,10 +8,6 @@ defineProps<{
const quotationId = defineModel<string>('quotationId', {
required: true,
});
const isDebitNote = defineModel<boolean>('isDebitNote', {
required: false,
default: false,
});
</script>
<template>
<div class="row col-12">
@ -41,7 +37,6 @@ const isDebitNote = defineModel<boolean>('isDebitNote', {
cancelIncludeDebitNote: true,
hasCancel: true,
}"
@selected="(v) => (isDebitNote = v.isDebitNote)"
/>
</section>
</div>

View file

@ -93,6 +93,13 @@ function clearUpload() {
imgUrl.value = '';
}
watch(
() => tab.value,
async () => {
await initializeSignaturePad();
},
);
onMounted(async () => {
await initializeSignaturePad();
});

View file

@ -42,15 +42,6 @@ defineProps<{
const modal = defineModel('modal', { default: false });
const currentTab = defineModel<string>('currentTab');
async function onValidationError(ref: any) {
const el = ref.$el as Element;
el.scrollIntoView({
behavior: 'smooth',
block: 'center',
inline: 'nearest',
});
}
</script>
<template>
<q-dialog
@ -69,7 +60,6 @@ async function onValidationError(ref: any) {
@submit.prevent
@validation-success="submit"
class="column full-height"
@validation-error="onValidationError"
>
<!-- header -->
<div

View file

@ -8,7 +8,7 @@ import {
UndoButton,
} from 'components/button';
const props = withDefaults(
withDefaults(
defineProps<{
title: string;
category?: string;
@ -42,24 +42,10 @@ const drawerOpen = defineModel<boolean>('drawerOpen', {
const myForm = ref();
function reset() {
if (props.beforeClose) {
drawerOpen.value = props.beforeClose
? props.beforeClose()
: !drawerOpen.value;
}
if (myForm.value) {
myForm.value.resetValidation();
}
}
async function onValidationError(ref: any) {
const el = ref.$el as Element;
el.scrollIntoView({
behavior: 'smooth',
block: 'center',
inline: 'nearest',
});
}
</script>
<template>
<q-drawer
@ -67,6 +53,7 @@ async function onValidationError(ref: any) {
@show="show"
@before-hide="reset"
@hide="close"
@update:model-value="(v) => (drawerOpen = beforeClose ? beforeClose() : v)"
:width="$q.screen.gt.xs ? windowSize * 0.85 : windowSize"
v-model="drawerOpen"
behavior="mobile"
@ -79,7 +66,6 @@ async function onValidationError(ref: any) {
greedy
@submit.prevent
@validation-success="submit"
@validation-error="onValidationError"
>
<div
class="column justify-between full-height"

View file

@ -1,9 +1,6 @@
<script setup lang="ts">
import { Icon } from '@iconify/vue/dist/iconify.js';
defineProps<{
hideIcon?: boolean;
icon?: string;
}>();
</script>
@ -13,13 +10,10 @@ defineProps<{
v-if="!hideIcon"
id="btn-add"
padding="sm"
:icon="icon ? undefined : 'mdi-plus'"
icon="mdi-plus"
direction="up"
class="color-btn"
>
<template #icon v-if="icon">
<Icon :icon width="24" />
</template>
<slot>
<q-fab-action
padding="xs"
@ -35,12 +29,10 @@ defineProps<{
fab
id="btn-add"
padding="sm"
:icon="icon ? undefined : 'mdi-plus'"
icon="mdi-plus"
direction="up"
class="color-btn"
>
<Icon v-if="icon" :icon width="24" />
</q-btn>
/>
</q-page-sticky>
</template>

View file

@ -46,7 +46,6 @@ defineProps<{
color="grey"
icon="mdi-close"
v-close-popup
@click="cancel"
/>
</div>

View file

@ -1,12 +1,5 @@
<script setup lang="ts">
const pageSize = defineModel<number>({ required: true });
withDefaults(
defineProps<{
fetchData?: (...args: unknown[]) => void;
}>(),
{},
);
</script>
<template>
@ -17,12 +10,7 @@ withDefaults(
:key="v"
clickable
v-close-popup
@click="
() => {
pageSize = v;
fetchData();
}
"
@click="pageSize = v"
>
<q-item-section>
<q-item-label>{{ v }}</q-item-label>

View file

@ -229,7 +229,6 @@ const smallBanner = ref(false);
<ToggleButton
v-if="useToggle"
:disable="readonly"
two-way
:model-value="toggleStatus !== 'INACTIVE'"
@click="$emit('update:toggleStatus', toggleStatus)"
@ -266,7 +265,6 @@ const smallBanner = ref(false);
class="app-text-muted full-width"
align="left"
v-if="typeof tabsList === 'object'"
@update:model-value="(v) => $emit('update:currentTab', v)"
>
<q-tab
v-for="tab in tabsList"

View file

@ -1,6 +1,7 @@
<script setup lang="ts">
import { BranchWithChildren } from 'stores/branch/types';
import KebabAction from './shared/KebabAction.vue';
import { isRoleInclude } from 'stores/utils';
const nodes = defineModel<(any | BranchWithChildren)[]>('nodes', {
default: [],
@ -16,7 +17,6 @@ withDefaults(
labelKey?: string;
childrenKey: string;
action?: boolean;
hideCreate?: boolean;
}>(),
{
color: 'transparent',
@ -96,9 +96,7 @@ defineEmits<{
expandedTree[expandedTree.length - 1] === node.id,
}"
>
{{
$i18n.locale === 'eng' ? node.nameEN || node.name : node.name
}}
{{ node.name }}
</span>
<span class="app-text-muted text-caption ellipsis">
{{ node.code }}
@ -122,7 +120,11 @@ defineEmits<{
/>
<q-btn
v-if="node.isHeadOffice && typeTree === 'branch' && !hideCreate"
v-if="
node.isHeadOffice &&
typeTree === 'branch' &&
isRoleInclude(['head_of_admin', 'admin', 'system'])
"
:id="`create-sub-branch-btn-${node.name}`"
@click.stop="$emit('create', node)"
icon="mdi-file-plus-outline"

View file

@ -1,10 +1,8 @@
<script lang="ts" setup>
import { ref } from 'vue';
import MainButton from './MainButton.vue';
const emit = defineEmits<{
defineEmits<{
(e: 'click', v: MouseEvent): void;
(e: 'fileSelected', v: File[]): void;
}>();
defineProps<{
iconOnly?: boolean;
@ -12,29 +10,15 @@ defineProps<{
outlined?: boolean;
disabled?: boolean;
dark?: boolean;
importFile?: boolean;
label?: string;
icon?: string;
}>();
const inputRef = ref<HTMLInputElement | null>(null);
function triggerFileInput() {
inputRef.value?.click();
}
function handleFileChange(event: Event) {
const files = (event.target as HTMLInputElement).files;
if (files && files.length > 0) {
emit('fileSelected', Array.from(files));
}
}
</script>
<template>
<MainButton
@click="(e) => (importFile ? triggerFileInput() : $emit('click', e))"
@click="(e) => $emit('click', e)"
v-bind="{ ...$props, ...$attrs }"
:icon="icon || 'mdi-import'"
color="var(--info-bg)"
@ -42,13 +26,4 @@ function handleFileChange(event: Event) {
>
{{ label || $t('general.import') }}
</MainButton>
<input
ref="inputRef"
type="file"
@change="(e) => handleFileChange(e)"
hidden
accept=".xls, .xlsx , .csv"
multiple
/>
</template>

View file

@ -5,7 +5,6 @@ defineEmits<{
(e: 'click', v: MouseEvent): void;
}>();
defineProps<{
id?: string;
icon?: string;
color: string;
iconOnly?: boolean;
@ -19,7 +18,6 @@ defineProps<{
<template>
<button
:id="id"
@click="(e) => $emit('click', e)"
class="main-btn"
:class="{

View file

@ -1,32 +0,0 @@
<script lang="ts" setup>
import MainButton from './MainButton.vue';
defineEmits<{
(e: 'click', v: MouseEvent): void;
}>();
defineProps<{
iconOnly?: boolean;
solid?: boolean;
outlined?: boolean;
disabled?: boolean;
dark?: boolean;
label?: string;
icon?: string;
amount?: number;
}>();
</script>
<template>
<MainButton
@click="(e) => $emit('click', e)"
v-bind="{ ...$props, ...$attrs }"
:icon="icon || 'mdi-file-replace'"
color="207 96% 32%"
:title="iconOnly ? $t('general.paste') : undefined"
>
{{ label || $t('general.paste') }}
{{ amount && amount > 0 ? `(${amount})` : '' }}
</MainButton>
</template>

View file

@ -10,7 +10,6 @@ defineProps<{
outlined?: boolean;
disabled?: boolean;
dark?: boolean;
color?: string;
label?: string;
icon?: string;
@ -24,7 +23,7 @@ defineProps<{
@click="(e) => $emit('click', e)"
v-bind="{ ...$props, ...$attrs }"
:icon="icon || 'mdi-content-save-outline'"
:color="color || '207 96% 32%'"
color="207 96% 32%"
:title="iconOnly ? $t('general.save') : undefined"
>
{{ label || $t('general.save') }}

View file

@ -14,4 +14,3 @@ export { default as PrintButton } from './PrintButton.vue';
export { default as StateButton } from './StateButton.vue';
export { default as NextButton } from './NextButton.vue';
export { default as ImportButton } from './ImportButton.vue';
export { default as PasteButton } from './PasteButton.vue';

View file

@ -23,15 +23,6 @@ function update(value: boolean) {
}
}
async function onValidationError(ref: any) {
const el = ref.$el as Element;
el.scrollIntoView({
behavior: 'smooth',
block: 'center',
inline: 'nearest',
});
}
const state = defineModel({ default: false });
</script>
@ -50,7 +41,6 @@ const state = defineModel({ default: false });
}"
>
<q-form
@validation-error="onValidationError"
@submit.prevent="(e) => $emit('submit', e)"
@reset="$emit('reset')"
greedy

View file

@ -12,7 +12,7 @@ import { formatAddress } from 'src/utils/address';
import useOptionStore from 'stores/options';
const optionStore = useOptionStore();
const props = defineProps<{
defineProps<{
title?: string;
addressTitle?: string;
addressTitleEN?: string;
@ -30,7 +30,6 @@ const props = defineProps<{
useEmployment?: boolean;
useWorkPlace?: boolean;
useForeignAddress?: boolean;
}>();
const addressStore = useAddressStore();
@ -58,25 +57,6 @@ const subDistrictId = defineModel<string | null | undefined>('subDistrictId');
const zipCode = defineModel<string | null | undefined>('zipCode');
const sameWithEmployer = defineModel<boolean>('sameWithEmployer');
const provinceTextEN = defineModel<string | null | undefined>(
'provinceTextEn',
{
default: '',
},
);
const districtTextEN = defineModel<string | null | undefined>(
'districtTextEn',
{
default: '',
},
);
const subDistrictTextEN = defineModel<string | null | undefined>(
'subDistrictTextEn',
{
default: '',
},
);
const homeCode = defineModel<string | null | undefined>('homeCode');
const employmentOffice = defineModel<string | null | undefined>(
'employmentOffice',
@ -84,7 +64,6 @@ const employmentOffice = defineModel<string | null | undefined>(
const employmentOfficeEN = defineModel<string | null | undefined>(
'employmentOfficeEn',
);
const addressForeign = defineModel<boolean>('addressForeign');
const addrOptions = reactive<{
provinceOps: Province[];
@ -99,18 +78,14 @@ const addrOptions = reactive<{
const area = ref<Office[]>([]);
const fullAddress = computed(() => {
const province = addressForeign.value
? { id: '1', name: provinceId.value }
: provinceOptions.value.find((v) => v.id === provinceId.value);
const district = addressForeign.value
? { id: '1', name: districtId.value }
: districtOptions.value.find((v) => v.id === districtId.value);
const sDistrict = addressForeign.value
? { id: '1', name: subDistrictId.value }
: subDistrictOptions.value.find((v) => v.id === subDistrictId.value);
const province = provinceOptions.value.find((v) => v.id === provinceId.value);
const district = districtOptions.value.find((v) => v.id === districtId.value);
const sDistrict = subDistrictOptions.value.find(
(v) => v.id === subDistrictId.value,
);
if (province?.name && district?.name && sDistrict?.name) {
const fullAddressText = formatAddress({
if (province && district && sDistrict) {
const fullAddress = formatAddress({
address: address.value,
addressEN: addressEN.value,
moo: moo.value ? moo.value : '',
@ -122,26 +97,21 @@ const fullAddress = computed(() => {
province: province as unknown as Province,
district: district as unknown as District,
subDistrict: sDistrict as unknown as SubDistrict,
zipCode: addressForeign.value ? zipCode.value || ' ' : undefined,
});
return fullAddressText;
return fullAddress;
}
return '-';
});
const fullAddressEN = computed(() => {
const province = addressForeign.value
? { nameEN: provinceTextEN.value }
: provinceOptions.value.find((v) => v.id === provinceId.value);
const district = addressForeign.value
? { nameEN: districtTextEN.value }
: districtOptions.value.find((v) => v.id === districtId.value);
const sDistrict = addressForeign.value
? { nameEN: subDistrictTextEN.value }
: subDistrictOptions.value.find((v) => v.id === subDistrictId.value);
const province = provinceOptions.value.find((v) => v.id === provinceId.value);
const district = districtOptions.value.find((v) => v.id === districtId.value);
const sDistrict = subDistrictOptions.value.find(
(v) => v.id === subDistrictId.value,
);
if (province?.nameEN && district?.nameEN && sDistrict?.nameEN) {
const fullAddressText = formatAddress({
if (province && district && sDistrict) {
const fullAddress = formatAddress({
address: address.value,
addressEN: addressEN.value,
moo: moo.value ? moo.value : '',
@ -154,9 +124,8 @@ const fullAddressEN = computed(() => {
district: district as unknown as District,
subDistrict: sDistrict as unknown as SubDistrict,
en: true,
zipCode: addressForeign.value ? zipCode.value || ' ' : undefined,
});
return fullAddressText;
return fullAddress;
}
return '-';
});
@ -180,7 +149,7 @@ async function fetchProvince() {
}
async function fetchDistrict() {
if (!provinceId.value || addressForeign.value) return;
if (!provinceId.value) return;
const result = await addressStore.fetchDistrictByProvinceId(provinceId.value);
if (result) addrOptions.districtOps = result;
@ -199,7 +168,7 @@ async function fetchDistrict() {
}
async function fetchSubDistrict() {
if (!districtId.value || addressForeign.value) return;
if (!districtId.value) return;
const result = await addressStore.fetchSubDistrictByProvinceId(
districtId.value,
);
@ -286,16 +255,6 @@ onMounted(async () => {
await fetchSubDistrict();
});
function clearAddress() {
provinceId.value = null;
districtId.value = null;
subDistrictId.value = null;
provinceTextEN.value = null;
districtTextEN.value = null;
subDistrictTextEN.value = null;
zipCode.value = null;
}
watch(provinceId, fetchDistrict);
watch(districtId, fetchSubDistrict);
@ -354,15 +313,6 @@ watchEffect(async () => {
{{ $t('customerEmployee.form.addressCustom') }}
</span>
</div>
<div v-if="useForeignAddress" class="text-caption q-ml-md app-text-muted">
<q-checkbox
size="xs"
v-model="addressForeign"
@update:model-value="clearAddress"
/>
{{ $t('personnel.form.addressForeign') }}
</div>
</div>
<div class="col-12 row q-col-gutter-y-md">
@ -499,24 +449,7 @@ watchEffect(async () => {
(v) => (typeof v === 'string' ? (street = v) : '')
"
/>
<q-input
v-if="addressForeign"
outlined
hide-bottom-space
class="col-md-3 col-6"
v-model="provinceId"
:dense="dense"
:label="$t('form.province')"
:readonly="readonly || sameWithEmployer"
:for="`${prefixId}-${indexId !== undefined ? `input-province-${indexId}` : 'input-province'}`"
:rules="
disabledRule
? []
: [(val) => (val && val.length > 0) || $t('form.error.required')]
"
/>
<q-select
v-else
autocomplete="off"
outlined
clearable
@ -560,24 +493,7 @@ watchEffect(async () => {
</template>
</q-select>
<q-input
v-if="addressForeign"
outlined
hide-bottom-space
class="col-md-3 col-6"
v-model="districtId"
:dense="dense"
:label="$t('form.district')"
:readonly="readonly || sameWithEmployer"
:for="`${prefixId}-${indexId !== undefined ? `input-district-${indexId}` : 'input-district'}`"
:rules="
disabledRule
? []
: [(val) => (val && val.length > 0) || $t('form.error.required')]
"
/>
<q-select
v-else
autocomplete="off"
outlined
clearable
@ -620,25 +536,7 @@ watchEffect(async () => {
</q-item>
</template>
</q-select>
<q-input
v-if="addressForeign"
outlined
hide-bottom-space
class="col-md-3 col-6"
v-model="subDistrictId"
:dense="dense"
:label="$t('form.district')"
:readonly="readonly || sameWithEmployer"
:for="`${prefixId}-${indexId !== undefined ? `input-sub-district-${indexId}` : 'input-sub-district'}`"
:rules="
disabledRule
? []
: [(val) => (val && val.length > 0) || $t('form.error.required')]
"
/>
<q-select
v-else
autocomplete="off"
outlined
clearable
@ -682,27 +580,17 @@ watchEffect(async () => {
</template>
</q-select>
<q-input
:key="Number(addressForeign)"
hide-bottom-space
:for="`${prefixId}-${indexId !== undefined ? `input-zip-code-${indexId}` : 'input-zip-code'}`"
:dense="dense"
outlined
:disable="!addressForeign && !readonly && !sameWithEmployer"
:readonly="!addressForeign || readonly"
:disable="!readonly && !sameWithEmployer"
readonly
:label="$t('form.zipCode')"
class="col-md-3 col-6"
:model-value="
!addressForeign
? (addrOptions.subDistrictOps
?.filter((x) => x.id === subDistrictId)
.map((x) => x.zipCode)[0] ?? '')
: zipCode
"
@update:model-value="(v) => (zipCode = v.toString())"
:rules="
!addressForeign
? []
: [(val) => (val && val.length > 0) || $t('form.error.required')]
addrOptions.subDistrictOps
?.filter((x) => x.id === subDistrictId)
.map((x) => x.zipCode)[0] ?? ''
"
/>
<q-input
@ -801,24 +689,7 @@ watchEffect(async () => {
(v) => (typeof v === 'string' ? (streetEN = v) : '')
"
/>
<q-input
v-if="addressForeign"
outlined
hide-bottom-space
class="col-md-3 col-6"
v-model="provinceTextEN"
:dense="dense"
label="Province"
:readonly="readonly || sameWithEmployer"
:for="`${prefixId}-${indexId !== undefined ? `input-province-en-${indexId}` : 'input-province-en'}`"
:rules="
disabledRule
? []
: [(val) => (val && val.length > 0) || $t('form.error.required')]
"
/>
<q-select
v-else
autocomplete="off"
outlined
clearable
@ -861,25 +732,7 @@ watchEffect(async () => {
</q-item>
</template>
</q-select>
<q-input
v-if="addressForeign"
outlined
hide-bottom-space
class="col-md-3 col-6"
v-model="districtTextEN"
:dense="dense"
label="District"
:readonly="readonly || sameWithEmployer"
:for="`${prefixId}-${indexId !== undefined ? `input-district-en-${indexId}` : 'input-district-en'}`"
:rules="
disabledRule
? []
: [(val) => (val && val.length > 0) || $t('form.error.required')]
"
/>
<q-select
v-else
autocomplete="off"
outlined
clearable
@ -922,25 +775,7 @@ watchEffect(async () => {
</q-item>
</template>
</q-select>
<q-input
v-if="addressForeign"
outlined
hide-bottom-space
class="col-md-3 col-6"
v-model="subDistrictTextEN"
:dense="dense"
label="Sub-District"
:readonly="readonly || sameWithEmployer"
:for="`${prefixId}-${indexId !== undefined ? `input-sub-district-en-${indexId}` : 'input-sub-district-en'}`"
:rules="
disabledRule
? []
: [(val) => (val && val.length > 0) || $t('form.error.required')]
"
/>
<q-select
v-else
autocomplete="off"
outlined
clearable
@ -984,28 +819,19 @@ watchEffect(async () => {
</template>
</q-select>
<q-input
:key="Number(addressForeign)"
hide-bottom-space
:for="`${prefixId}-${indexId !== undefined ? `input-zip-code-${indexId}` : 'input-zip-code'}`"
:dense="dense"
outlined
:readonly="!addressForeign || readonly"
:disable="!addressForeign && !readonly && !sameWithEmployer"
readonly
:disable="!readonly && !sameWithEmployer"
zip="zip-en"
label="Zip Code"
class="col-md-3 col-6"
:model-value="
!addressForeign
? (addrOptions.subDistrictOps
?.filter((x) => x.id === subDistrictId)
.map((x) => x.zipCode)[0] ?? '')
: zipCode
"
@update:model-value="(v) => (zipCode = v.toString())"
:rules="
!addressForeign
? []
: [(val) => (val && val.length > 0) || $t('form.error.required')]
addrOptions.subDistrictOps
?.filter((x) => x.id === subDistrictId)
.map((x) => x.zipCode)[0] ?? ''
"
/>
<q-input

View file

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

View file

@ -1,190 +0,0 @@
<script lang="ts" setup>
import { ref, watch } from 'vue';
import { dateFormatJS } from 'src/utils/datetime';
import SelectInput from './SelectInput.vue';
import VueDatePicker from '@vuepic/vue-datepicker';
import dayjs from 'dayjs';
defineProps<{
active?: boolean;
}>();
const date = defineModel<string[]>();
const dateRange = ref<string>('');
const isDateSelect = ref(false);
function mapDateRange(val: string) {
const today = dayjs();
let start: dayjs.Dayjs, end: dayjs.Dayjs;
switch (val) {
case 'toDay':
start = today.startOf('day');
end = today.endOf('day');
break;
case 'yesterday':
start = today.subtract(1, 'day').startOf('day');
end = today.subtract(1, 'day').endOf('day');
break;
case 'thisWeek':
start = today.startOf('week');
end = today.endOf('week');
break;
case 'lastWeek':
start = today.subtract(1, 'week').startOf('week');
end = today.subtract(1, 'week').endOf('week');
break;
case 'thisMonth':
start = today.startOf('month');
end = today.endOf('month');
break;
case 'lastMonth':
start = today.subtract(1, 'month').startOf('month');
end = today.subtract(1, 'month').endOf('month');
break;
case 'thisYear':
start = today.startOf('year');
end = today.endOf('year');
break;
case 'lastYear':
start = today.subtract(1, 'year').startOf('year');
end = today.subtract(1, 'year').endOf('year');
break;
case 'last7Days':
start = today.subtract(6, 'day').startOf('day');
end = today.endOf('day');
break;
case 'last30Days':
start = today.subtract(29, 'day').startOf('day');
end = today.endOf('day');
break;
case 'last90Days':
start = today.subtract(89, 'day').startOf('day');
end = today.endOf('day');
break;
case 'customDateRange':
start = today.startOf('day');
end = today.endOf('day');
break;
default:
return;
}
return [start.toDate().toISOString(), end.toDate().toISOString()];
}
watch(
() => dateRange.value,
() => {
if (!dateRange.value) return;
date.value = mapDateRange(dateRange.value);
},
);
watch(
() => date.value,
() => {
if (date.value && date.value.length === 0) dateRange.value = '';
},
);
</script>
<template>
<q-btn
size="xs"
round
dense
unelevated
icon="mdi-tune-variant"
:flat="active ? false : !dateRange"
:color="active || dateRange ? 'info' : undefined"
>
<q-menu
:offset="[5, 10]"
max-width="300px"
class="bordered"
:persistent="isDateSelect"
>
<div class="q-pa-sm">
<slot name="prepend"></slot>
<div class="text-weight-medium">
{{ $t('general.advanceSearch') }}
</div>
<SelectInput
v-model="dateRange"
:label="$t('general.period')"
:option="[
{ label: $t('dateRange.today'), value: 'toDay' },
{ label: $t('dateRange.yesterday'), value: 'yesterday' },
{ label: $t('dateRange.thisWeek'), value: 'thisWeek' },
{ label: $t('dateRange.lastWeek'), value: 'lastWeek' },
{ label: $t('dateRange.thisMonth'), value: 'thisMonth' },
{ label: $t('dateRange.lastMonth'), value: 'lastMonth' },
{ label: $t('dateRange.thisYear'), value: 'thisYear' },
{ label: $t('dateRange.lastYear'), value: 'lastYear' },
{ label: $t('dateRange.last7Days'), value: 'last7Days' },
{ label: $t('dateRange.last30Days'), value: 'last30Days' },
{ label: $t('dateRange.last90Days'), value: 'last90Days' },
{
label: $t('dateRange.customDateRange'),
value: 'customDateRange',
},
]"
clearable
@clear="() => (date = [])"
/>
<VueDatePicker
v-if="dateRange === 'customDateRange'"
utc
range
teleport
auto-apply
for="select-date-range"
class="q-mt-sm"
v-model="date"
:dark="$q.dark.isActive"
:locale="$i18n.locale === 'tha' ? 'th' : 'en'"
@open="() => (isDateSelect = true)"
@closed="() => (isDateSelect = false)"
>
<template #trigger>
<q-input
placeholder="DD/MM/YYYY"
hide-bottom-space
dense
outlined
for="select-date-range"
:model-value="
date
? dateFormatJS({ date: date[0] }) +
' - ' +
dateFormatJS({ date: date[1] })
: ''
"
>
<template #prepend>
<q-icon name="mdi-calendar-outline" class="app-text-muted" />
</template>
<q-tooltip>
{{
date
? dateFormatJS({ date: date[0] }) +
' - ' +
dateFormatJS({ date: date[1] })
: ''
}}
</q-tooltip>
</q-input>
</template>
</VueDatePicker>
<slot></slot>
<!-- <SelectInput :label="$t('general.documentStatus')" :option="[]" /> -->
</div>
</q-menu>
<q-tooltip v-if="$q.screen.gt.sm">
{{ $t('general.advanceSearch') }}
</q-tooltip>
</q-btn>
</template>

View file

@ -25,11 +25,7 @@ withDefaults(
alt="Image"
/>
</div>
<div
v-if="data.length > 3"
class="avatar remaining-count"
style="cursor: default"
>
<div v-if="data.length > 3" class="avatar remaining-count">
<q-tooltip>
<div v-for="(person, i) in data.slice(3)" :key="i + 3">
{{ person.name }}

View file

@ -16,7 +16,6 @@ const props = withDefaults(
useUpload?: boolean;
useCancel?: boolean;
useRejectCancel?: boolean;
useCopy?: boolean;
disableCancel?: boolean;
disableDelete?: boolean;
}>(),
@ -32,7 +31,6 @@ defineEmits<{
(e: 'link'): void;
(e: 'upload'): void;
(e: 'delete'): void;
(e: 'copy'): void;
(e: 'cancel'): void;
(e: 'rejectCancel'): void;
(e: 'changeStatus'): void;
@ -174,27 +172,6 @@ watch(
</span>
</q-item>
<q-item
v-if="useCopy"
v-close-popup
dense
clickable
class="row q-py-sm"
style="white-space: nowrap"
:id="`btn-kebab-copy-${idName}`"
@click.stop="() => $emit('copy')"
>
<q-icon
size="xs"
class="col-3"
name="mdi-content-copy"
style="color: hsl(var(--teal-5-hsl))"
/>
<span class="col-9 q-px-md flex items-center">
{{ $t('general.copy') }}
</span>
</q-item>
<q-item
v-if="useCancel"
v-close-popup

View file

@ -23,8 +23,6 @@ defineProps<{
history?: boolean;
prefixId?: string;
separateEnter?: boolean;
hideAction?: boolean;
hideDelete?: boolean;
}>();
defineEmits<{
@ -78,10 +76,8 @@ defineEmits<{
/>
<KebabAction
v-if="!hideAction"
:id-name="prefixId"
:status="disabled ? 'INACTIVE' : 'ACTIVE'"
:hide-delete="hideDelete"
@view="
separateEnter
? $emit('viewCard', 'INFO')

View file

@ -13,7 +13,6 @@ let defaultFilter: (
const props = withDefaults(
defineProps<{
prefix?: string;
id?: string;
label?: string;
option: T[];
@ -29,7 +28,6 @@ const props = withDefaults(
disable?: boolean;
multiple?: boolean;
hideInput?: boolean;
hideDropdownIcon?: boolean;
rules?: ((value: string) => string | true)[];
}>(),
@ -72,7 +70,6 @@ watch(
</script>
<template>
<q-select
:id="id"
:placeholder="placeholder"
outlined
:clearable
@ -85,7 +82,7 @@ watch(
:hide-selected
hide-bottom-space
:fill-input="fillInput && !!model"
:hide-dropdown-icon="readonly || hideDropdownIcon"
:hide-dropdown-icon="readonly"
input-debounce="500"
:option-value="
typeof props.optionValue === 'string' ? props.optionValue : 'value'
@ -106,11 +103,6 @@ watch(
}
"
:rules
@clear="
() => {
multiple ? (model = []) : (model = '');
}
"
>
<template v-if="$slots.prepend" v-slot:prepend>
<slot name="prepend"></slot>

View file

@ -72,11 +72,7 @@ onMounted(async () => {
:option="selectOptions"
:hide-selected="false"
:fill-input="false"
:rules="[
(v: string) => {
return !!v?.length || $t('form.error.required');
},
]"
:rules="[(v: string) => !!v || $t('form.error.required')]"
@filter="filter"
>
<template #before-options v-if="creatable">

View file

@ -75,9 +75,9 @@ function setDefaultValue() {
</script>
<template>
<SelectInput
for="select-hq-id"
v-model="value"
incremental
id="select-hq-id"
:label
:placeholder
:readonly

View file

@ -1,213 +0,0 @@
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import { createSelect, SelectProps } from './select';
import SelectInput from '../SelectInput.vue';
import { BusinessType } from 'src/stores/business-type/types';
import useStore from 'src/stores/business-type';
type SelectOption = BusinessType;
const value = defineModel<string | null | undefined>('value', {
required: true,
});
const valueOption = defineModel<SelectOption>('valueOption', {
required: false,
});
const selectOptions = ref<SelectOption[]>([]);
const { fetchList: getList, fetchById: getById } = useStore();
defineEmits<{
(e: 'create'): void;
}>();
type ExclusiveProps = {
lang?: string;
codeOnly?: boolean;
selectFirstValue?: boolean;
branchVirtual?: boolean;
checkRole?: string[];
};
const props = defineProps<SelectProps<typeof getList> & ExclusiveProps>();
const { getOptions, setFirstValue, getSelectedOption, filter } =
createSelect<SelectOption>(
{
value,
valueOption,
selectOptions,
getList: async (query) => {
const ret = await getList({
query,
...props.params,
pageSize: 99999,
});
if (ret) return ret.result;
},
getByValue: async (id) => {
const ret = await getById(id);
if (ret) return ret;
},
},
{ valueField: 'id' },
);
onMounted(async () => {
await getOptions();
if (props.autoSelectOnSingle && selectOptions.value.length === 1) {
setFirstValue();
}
if (props.selectFirstValue) {
setDefaultValue();
} else await getSelectedOption();
});
function setDefaultValue() {
setFirstValue();
}
</script>
<template>
<SelectInput
v-model="value"
incremental
option-value="id"
:label="label || $t('menu.manage.businessType')"
:placeholder
:readonly
:disable="disabled"
:option="selectOptions"
:hide-selected="false"
:fill-input="false"
:rules="[
(v: string) => !props.required || !!v || $t('form.error.required'),
]"
@filter="filter"
>
<template #selected-item="{ opt }">
{{ (lang ?? $i18n.locale) !== 'eng' ? opt.name : opt.nameEN }}
</template>
<template #no-option v-if="creatable">
<q-item
:disable="creatableDisabled"
clickable
v-close-popup
@click.stop="$emit('create')"
>
<q-item-section>
<span class="row items-center">
<q-icon
name="mdi-plus-circle-outline"
class="q-mr-sm"
style="color: hsl(var(--positive-bg))"
/>
<b>
{{ $t('general.add', { text: $t('businessType.title') }) }}
</b>
<span
v-if="creatableDisabled && creatableDisabledText"
class="app-text-muted q-pl-xs"
style="font-size: 80%"
>
{{ creatableDisabledText }}
</span>
</span>
</q-item-section>
</q-item>
<q-separator class="q-mx-sm" />
</template>
<template #before-options v-if="creatable">
<q-item
:disable="creatableDisabled"
clickable
v-close-popup
@click.stop="$emit('create')"
for="select-biz-type-add-new"
id="select-biz-type-add-new"
>
<q-item-section>
<span class="row items-center">
<q-icon
name="mdi-plus-circle-outline"
class="q-mr-sm"
style="color: hsl(var(--positive-bg))"
/>
<b>
{{ $t('general.add', { text: $t('businessType.title') }) }}
</b>
<span
v-if="creatableDisabled && creatableDisabledText"
class="app-text-muted q-pl-xs"
style="font-size: 80%"
>
{{ creatableDisabledText }}
</span>
</span>
</q-item-section>
</q-item>
<q-separator class="q-mx-sm" />
</template>
<template #before-options v-if="creatable">
<q-item
:disable="creatableDisabled"
clickable
v-close-popup
@click.stop="$emit('create')"
for="select-business-type-add-new"
id="select-business-type-add-new"
>
<q-item-section>
<span class="row items-center">
<q-icon
name="mdi-plus-circle-outline"
class="q-mr-sm"
style="color: hsl(var(--positive-bg))"
/>
<b>
{{ $t('general.add', { text: $t('menu.manage.businessType') }) }}
</b>
<span
v-if="creatableDisabled && creatableDisabledText"
class="app-text-muted q-pl-xs"
style="font-size: 80%"
>
{{ creatableDisabledText }}
</span>
</span>
</q-item-section>
</q-item>
<q-separator class="q-mx-sm" />
</template>
<template #option="{ opt, scope }">
<q-item v-bind="scope.itemProps">
<span class="row items-center">
{{ (lang ?? $i18n.locale) !== 'eng' ? opt.name : opt.nameEN }}
</span>
</q-item>
<q-separator class="q-mx-sm" />
</template>
<template #append v-if="clearable">
<q-icon
v-if="!readonly && value"
name="mdi-close-circle"
@click.stop="value = ''"
class="cursor-pointer clear-btn"
/>
</template>
</SelectInput>
</template>

View file

@ -30,7 +30,6 @@ defineEmits<{
type ExclusiveProps = {
simple?: boolean;
simpleBranchNo?: boolean;
selectFirstValue?: boolean;
};
const props = defineProps<SelectProps<typeof getList> & ExclusiveProps>();
@ -65,14 +64,8 @@ onMounted(async () => {
setFirstValue();
}
if (props.selectFirstValue) {
setDefaultValue();
} else await getSelectedOption();
await getSelectedOption();
});
function setDefaultValue() {
setFirstValue();
}
</script>
<template>
<SelectInput
@ -165,9 +158,11 @@ function setDefaultValue() {
</template>
<template #option="{ opt, scope }">
<q-item v-bind="scope.itemProps" class="q-mx-sm bodrder">
<q-item v-bind="scope.itemProps">
<SelectCustomerItem :data="opt" :simple :simple-branch-no />
</q-item>
<q-separator class="q-mx-sm" />
</template>
<template #append v-if="clearable">
@ -180,11 +175,3 @@ function setDefaultValue() {
</template>
</SelectInput>
</template>
<style scoped>
.bodrder {
border-bottom: solid;
border-bottom-width: 1px;
border-color: var(--border-color);
}
</style>

View file

@ -43,7 +43,6 @@ const { getOptions, setFirstValue, getSelectedOption, filter } =
const ret = await getList({
query: query === '' ? undefined : query,
...props.params,
activeOnly: true,
});
if (ret) return ret.result;
},

View file

@ -25,7 +25,6 @@ const { getQuotationList: getList, getQuotation: getById } = useStore();
defineEmits<{
(e: 'create'): void;
(e: 'selected', value: SelectOption): void;
}>();
type ExclusiveProps = {
@ -118,14 +117,6 @@ function setDefaultValue() {
(v: string) => !props.required || !!v || $t('form.error.required'),
]"
@filter="filter"
@update:model-value="
(v) => {
$emit(
'selected',
selectOptions.find((opt) => opt.id === v),
);
}
"
>
<template #append v-if="clearable">
<q-icon

View file

@ -26,7 +26,6 @@ defineEmits<{
type ExclusiveProps = {
selectFirstValue?: boolean;
prefix?: string;
};
const props = defineProps<SelectProps<typeof getList> & ExclusiveProps>();
@ -72,7 +71,6 @@ function setDefaultValue() {
<SelectInput
v-model="value"
incremental
:id="`${prefix || 'nome'}-select-user`"
:label
:placeholder
:readonly
@ -94,9 +92,7 @@ function setDefaultValue() {
:hide-selected="false"
:fill-input="false"
:rules="
required && !readonly
? [(v: string) => !!v || $t('form.error.required')]
: undefined
required ? [(v: string) => !!v || $t('form.error.required')] : undefined
"
@filter="filter"
>

View file

@ -35,13 +35,7 @@ export const createSelect = <T extends Record<string, any>>(
let previousSearch = '';
watch(value, (v) => {
if (!v) return;
if (cache && cache.find((opt) => opt[valueField] === v)) {
valueOption.value = cache.find((opt) => opt[valueField] === v);
return;
}
if (!v || (cache && cache.find((opt) => opt[valueField] === v))) return;
getSelectedOption();
});
@ -69,26 +63,15 @@ export const createSelect = <T extends Record<string, any>>(
const currentValue = value.value;
if (!currentValue) return;
if (selectOptions.value.find((v) => v[valueField] === currentValue)) return;
if (valueOption.value && valueOption.value[valueField] === currentValue) {
selectOptions.value.unshift(valueOption.value);
selectOptions.value = selectOptions.value.filter((curr, idx, arr) => {
return (
arr.findIndex((item) => item[valueField] === curr[valueField]) === idx
);
});
return;
return selectOptions.value.unshift(valueOption.value);
}
const ret = await getByValue(currentValue);
if (ret) {
selectOptions.value.unshift(ret);
selectOptions.value = selectOptions.value.filter((curr, idx, arr) => {
return (
arr.findIndex((item) => item[valueField] === curr[valueField]) === idx
);
});
valueOption.value = ret;
}
}

View file

@ -251,10 +251,7 @@ function selectedIndex(item: any) {
>
<!-- NOTE: custom column will starts with # -->
<template v-if="!col.name.startsWith('#')">
<span v-if="col.name === 'serviceDetail'">
{{ props.row.detail.replace(/<\/?[^>]+(>|$)/g, '') || '-' }}
</span>
<span v-else>
<span>
{{
typeof col.field === 'string'
? props.row[col.field as keyof (Product | Service)]

View file

@ -54,9 +54,7 @@ const columns = [
field: (v: Employee) =>
locale.value === Lang.English
? `${v.firstNameEN} ${v.lastNameEN}`
: v.firstName
? `${v.firstName} ${v.lastName}`
: `${v.firstNameEN} ${v.lastNameEN}`,
: `${v.firstName} ${v.lastName}`,
},
{
name: 'birthDate',
@ -145,7 +143,6 @@ function selectedIndex(item: Employee) {
<template v-if="col.name === '#check'">
<q-checkbox
id="select-worker-all"
for="select-worker-all"
v-model="props.selected"
@update:model-value="(v) => handleUpdate()"
size="sm"
@ -201,7 +198,6 @@ function selectedIndex(item: Employee) {
v-model="props.selected"
size="sm"
:id="`select-worker-${props.row.firstName}`"
:for="`select-worker-${props.row.firstName}`"
/>
</template>
</q-td>

View file

@ -188,7 +188,6 @@ function formatCode(input: string | undefined, type: 'code' | 'number') {
:label="$t('customer.form.citizenId')"
for="input-citizen-id"
v-model="citizenId"
:rules="[(val: string) => !!val || $t('form.error.required')]"
/>
<DatePicker
@ -222,7 +221,6 @@ function formatCode(input: string | undefined, type: 'code' | 'number') {
:label="$t('customer.form.religion')"
for="input-religion"
v-model="religion"
:rules="[(val: string) => !!val || $t('form.error.required')]"
/>
<q-select
outlined
@ -307,7 +305,6 @@ function formatCode(input: string | undefined, type: 'code' | 'number') {
:label="$t('customer.form.firstName')"
for="input-first-name"
v-model="firstName"
:rules="[(val: string) => !!val || $t('form.error.required')]"
/>
<q-input
@ -319,7 +316,6 @@ function formatCode(input: string | undefined, type: 'code' | 'number') {
:label="$t('customer.form.lastName')"
for="input-last-name"
v-model="lastName"
:rules="[(val: string) => !!val || $t('form.error.required')]"
/>
<q-input

View file

@ -22,7 +22,6 @@ type Props = {
autoSave?: boolean;
data?: Data;
hideBtn?: boolean;
disabledSubmit?: boolean;
};
type HandleProps = {
@ -110,7 +109,6 @@ async function change(e: Event) {
hide-delete
hide-btn
edit
:disabledSubmit
:title
:is-edit
:readonly

View file

@ -31,7 +31,7 @@ const currentIndexDropdownList = ref(0);
const props = withDefaults(
defineProps<{
treeFile?: { label: string; file: { label: string }[] }[];
treeFile: { label: string; file: { label: string }[] }[];
readonly?: boolean;
dropdownList?: { label: string; value: string }[];
hideAction?: boolean;

View file

@ -54,11 +54,10 @@ onMounted(() => {
@click="$emit('click')"
>
<q-icon :name="icon" size="lg" :style="`color: ${color}`" />
<div class="col column q-pl-md">
<div class="ellipsis full-width" style="max-width: 65vw !important">
<article class="col column q-pl-md">
<span class="ellipsis full-width">
{{ name }}
</div>
</span>
<span class="text-caption app-text-muted-2">
{{
uploading.loaded
@ -80,7 +79,7 @@ onMounted(() => {
/>
{{ idle ? `Pending` : progress !== 1 ? `Uploading...` : 'Completed' }}
</span>
</div>
</article>
<q-btn
v-if="closeable"
icon="mdi-close"

View file

@ -45,9 +45,9 @@ const props = withDefaults(
readonly?: boolean;
showTitle?: boolean;
ocr?: (
group: string,
group: any,
file: File,
) => Promise<{
) => void | Promise<{
status: boolean;
group: string;
meta: { name: string; value: string }[];
@ -123,7 +123,7 @@ async function change(e: Event) {
...obj.value,
{
_meta: structuredClone(toRaw(selectedMenu.value)._meta || {}),
group: selectedMenu.value?.group,
group: selectedMenu.value?.value,
file: renamedFile,
},
];
@ -168,8 +168,8 @@ async function change(e: Event) {
type: map['doc_type'],
number: map['doc_number'],
gender: map['sex'],
firstName: map['last_name'],
lastName: map['first_name'],
firstName: map['first_name'],
lastName: map['last_name'],
issueDate: map['issue_date'],
expireDate: map['expire_date'],
issuePlace: map['nationality'],
@ -327,7 +327,7 @@ defineEmits<{
:rows="
obj
.filter((v) => {
if (!autoSave && v.group !== selectedMenu?.group) {
if (!autoSave && v.group !== selectedMenu?.value) {
return false;
}
return true;

View file

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

View file

@ -4,7 +4,7 @@ export default {
save: 'Save',
open: 'Open',
close: 'Close',
edit: 'Edit{text}',
edit: 'Edit',
cancel: 'Cancel',
back: 'Back',
undo: 'Undo',
@ -31,7 +31,6 @@ export default {
displayField: 'Display Fields',
order: 'Order',
name: '{msg} Name',
nameEN: 'Name (English)',
fullName: 'Full Name',
detail: '{msg} Detail',
remark: '{msg} Remark',
@ -61,7 +60,7 @@ export default {
branchStatus: 'Branch Status',
success: 'Success',
taxNo: 'Legal Person',
contactName: 'Contact Person',
contactName: 'Contact Name',
image: 'Image of ',
apply: 'Apply',
licenseNumber: 'License number',
@ -154,14 +153,6 @@ export default {
tableOfContent: 'Table of Contents',
draw: 'Draw',
newUpload: 'New Upload',
nativeLanguage: '{msg} Native Language',
copy: 'Copy',
paste: 'Paste',
period: 'Period',
documentStatus: 'Document Status',
advanceSearch: 'Advance Search',
totalPeople: '{meg} people',
price: 'Price {price} Baht',
},
menu: {
@ -204,14 +195,12 @@ export default {
title: 'Manage',
branch: 'Branch',
personnel: 'Personnel',
group: 'Group',
productService: 'Product and Service',
workflow: 'Workflow',
property: 'Property',
customer: 'Customer',
mainData: 'Main Data',
agencies: 'Agencies',
businessType: 'Business Type',
},
sales: {
@ -258,8 +247,7 @@ export default {
manual: {
title: 'Manual',
usage: 'Usage',
troubleshooting: 'Troubleshooting',
usage: 'การใช้งาน',
},
},
@ -341,7 +329,7 @@ export default {
requireLength: 'Please enter {msg} character',
branchNameField: "Only letters, numbers, or the characters . , - ' &.",
branchNameENField:
"Only English letters, numbers, or the characters . , - ' &. ( )",
"Only English letters, numbers, or the characters . , - ' &.",
passportFormat: 'Please enter the passport number in the correct format.',
},
warning: {
@ -388,7 +376,7 @@ export default {
branchLabel: 'Branch',
branchHQLabel: 'Headoffice',
taxNo: 'Legal Person',
contactName: 'Contact Person',
contactName: 'Contact Name',
},
page: {
captionManage: 'Manage',
@ -409,8 +397,8 @@ export default {
code: 'Headoffice Code',
codeBranch: 'Branch Code',
taxNo: 'Tax Identification Number',
contactName: 'Contact Person',
contactTelephone: 'Contact Number',
contactName: 'Contact Name',
contactTelephone: 'Contact Telephone',
branchName: 'Branch Name',
branchNameEN: 'Branch Name (EN)',
servicePointName: 'Service Point Name',
@ -460,10 +448,10 @@ export default {
regisNo: 'Registration Number',
startDate: 'Start Date',
retireDate: 'Retire Date',
responsibleArea: 'Responsible Area',
responsibleArea: 'Responsibel Area',
discount: 'Discount Condition',
sourceNationality: 'Source Nationality',
importNationality: 'Import Nationality',
importNationality: 'import Nationality',
trainingPlace: 'Training Place',
checkpoint: 'Checkpoint',
checkpointEN: 'Checkpoint (EN)',
@ -471,13 +459,6 @@ export default {
citizenId: 'Citizen ID',
citizenIssue: 'Citizen Issue',
citizenExpire: 'Citizen Expire',
agencyStatus: 'Agency Status',
normal: 'Normal',
canceled: 'Canceled',
blacklist: 'Black list',
contactName: 'Contact Person',
contactTel: 'Contact Number',
addressForeign: 'Use foreign address',
},
},
customer: {
@ -491,9 +472,10 @@ export default {
powerOfAttorney: 'Power of Attorney',
others: 'Others',
},
employer: 'Employer',
employerLegalEntity: 'Legal Entity',
employerNaturalPerson: 'Natural Person',
employerNaturalPerson: 'Natrual Person',
employerType: 'Employer Type',
employee: 'Employee',
form: {
@ -503,22 +485,24 @@ export default {
},
prefix: {
mr: 'MR.',
mrs: 'MRS.',
miss: 'MISS.',
mr: 'Mr.',
mrs: 'Mrs.',
miss: 'Miss.',
},
taxpayyerNo: 'Taxpayer Identification Number',
citizenId: 'Citizen ID',
religion: 'Religion',
issueDate: 'Issue Date',
passportExpiryDate: 'Passport Expiry Date',
ownerName: 'Customer Name',
firstName: 'First Name ',
lastName: 'Last Name ',
firstNameEN: 'First Name in English',
lastNameEN: 'Last Name in English',
cardNumber: 'ID Card Number',
prefixName: 'Prefix',
legalPersonNo: 'Legal Entity Registration Number',
registerName: 'Company Name',
@ -526,6 +510,7 @@ export default {
registerDate: 'Registered On',
registerCompanyName: 'Registered Name',
authorizedCapital: 'Authorized Capital',
workplace: 'Workplace',
workplaceEN: 'Workplace (EN)',
address: 'Address',
@ -533,6 +518,7 @@ export default {
branchCode: 'Branch Code',
customerCode: 'Employer Code',
legalPersonCode: 'Legal Entity Code',
codeAbbrev: 'Company Abbreviation',
codeNumber: 'Company Number',
registeredBranch: 'Registered Branch',
@ -570,7 +556,7 @@ export default {
jobPosition: 'Job Position',
address: 'Address',
workPlace: 'Workplace',
contactName: 'Contact Person',
contactName: 'Contact Name',
contactPhone: 'Contact Phone',
totalEmployee: 'Total Employee',
officeTel: 'Headoffice Telephone',
@ -624,7 +610,7 @@ export default {
placeOfBirth: 'Place of Birth',
countryOfbirth: 'Country of Birth',
issueCountry: 'Issue Country',
entryCount: 'Number of Days in the Country',
entryCount: 'Entry Count',
employerSelect: {
branchName: 'Branch Name',
customerName: 'Employer Name',
@ -774,13 +760,10 @@ export default {
},
quotation: {
ownOnly: 'View Own Quotation Only',
quotationDate: 'Quotation Date',
seller: 'Seller',
paymentChannels: 'Payment Channels',
channelsThat: 'Channels That',
refNo: 'Reference Number',
bankAccount: 'Bank Account',
bankAccountNumber: 'Bank Account Number',
bankAccountName: 'Bank Account Name',
inTheNameOf: 'In The Name Of',
@ -811,7 +794,7 @@ export default {
employee: 'Employee',
employeeName: 'Full Name',
workName: 'Work Name',
contactName: 'Contact Person',
contactName: 'Contact Name',
documentReceivePoint: 'Document Drop-Off Point"',
dueDate: 'Quotation Due Date',
specialCondition: 'Special Conditions',
@ -929,10 +912,6 @@ export default {
code: 'Agencies Code',
group: 'Agencies Group',
name: 'Agencies Name',
contactName: 'Contact Person',
contactTel: 'Contact Number',
bankInfo: 'Bank Information',
attachment: 'Attachment',
},
requestList: {
@ -954,9 +933,8 @@ export default {
localEmployee: 'Local Employee',
nonLocalEmployee: 'Non Local Employee',
noWorkflowTemplate: 'A workflow template has not been selected.',
salesRepresentative: 'Sales Representative',
dataOffice: 'Employment Office District',
salesRepresentative: 'Sales Representative',
ref: 'Reference',
action: {
title: 'Action',
@ -1020,7 +998,7 @@ export default {
issueBranch: 'Issue Branch',
issueDate: 'Issue Date',
madeBy: 'Made By',
contactName: 'Contact Person',
contactName: 'Contact Name',
workOrderCode: 'Work Order Code',
workOrderName: 'Work Order Name',
telephone: 'Telephone',
@ -1079,10 +1057,6 @@ export default {
confirmDebitNoteAccept: 'Confirm acceptance of the debit note.',
},
message: {
copy: 'Copy',
warningPaste:
'Do you want to replace the data with the newly copied information?',
warningCopyEmpty: 'You have not copied any data yet',
quotationAccept: 'Once accepted, no further modifications can be made',
beingUse: '"{msg}" is being used.',
incompleteDataEntry: 'Incomplete data entry on {tap} page',
@ -1127,7 +1101,7 @@ export default {
oneOrMoreBranchMissing:
'One or more branch cannot be delete and is missing.',
cantMakeHQAndBranchSameTime:
'Cannot make this as headquarters and branch at the same time.',
'Cannot make this as headquaters and branch at the same time.',
unknowHowToVerify: 'Unknown how to verify identity.',
noPermission:
'You do not have permission to access or perform with this resource.',
@ -1209,7 +1183,6 @@ export default {
'Product with the same name already exists. If you want to create with this name please select another code.',
userExists: 'User already exits.',
sameNameExists: 'Same name exists.',
samePropertyNameExists: 'Same property name exists.',
validateError: 'Validate Error',
codeMisMatch: 'Code Mismatch',
@ -1234,9 +1207,6 @@ export default {
taskListNotPending: 'One or more task is not pending.',
reqNotMet: 'Not Match',
systemError: 'A system error occurred.',
taskOrderInvalid: 'Please select the product and the organization.',
flowAccountProductIdNotFound: 'Product not found in flow account',
},
},
@ -1506,26 +1476,4 @@ export default {
type: 'Type',
},
},
dateRange: {
today: 'Today',
yesterday: 'Yesterday',
thisWeek: 'This Week',
lastWeek: 'Last Week',
thisMonth: 'This Month',
lastMonth: 'Last Month',
thisYear: 'This Year',
lastYear: 'Last Year',
last7Days: 'Last 7 Days',
last30Days: 'Last 30 Days',
last90Days: 'Last 90 Days',
customDateRange: 'Custom Date Range',
},
businessType: {
title: 'Business Type',
caption: 'Manage Business Type',
name: 'Business Type Name',
nameEn: 'Business Type Name (English)',
},
};

View file

@ -1,7 +1,7 @@
import eng from './eng';
import tha from './tha'; // spellchecker:disable-line
import tha from './tha';
export default {
eng,
tha, // spellchecker:disable-line
tha,
};

View file

@ -4,7 +4,7 @@ export default {
save: 'บันทึก',
open: 'เปิด',
close: 'ปิด',
edit: 'แก้ไข{text}',
edit: 'แก้ไข',
cancel: 'ยกเลิก',
back: 'ย้อนกลับ',
undo: 'ย้อนกลับ',
@ -31,7 +31,6 @@ export default {
displayField: 'ฟิลด์แสดงผล',
order: 'ลำดับ',
name: 'ชื่อ{msg}',
nameEN: 'ชื่อ (ภาษาอังกฤษ)',
fullName: 'ชื่อ-สกุล',
detail: 'รายละเอียด{msg}',
remark: 'หมายเหตุ{msg}',
@ -154,14 +153,6 @@ export default {
tableOfContent: 'สารบัญ',
draw: 'วาด',
newUpload: 'อัปโหลดใหม่',
nativeLanguage: '{msg} ภาษาต้นทาง',
copy: 'คัดลอก',
paste: 'วาง',
period: 'ช่วงเวลา',
documentStatus: 'สถานะเอกสาร',
advanceSearch: 'ค้นหาขั้นสูง',
totalPeople: '{meg} คน',
price: 'ราคา {price} บาท',
},
menu: {
@ -204,14 +195,12 @@ export default {
title: 'จัดการ',
branch: 'สาขา',
personnel: 'บุคลากร',
group: 'กลุ่ม',
productService: 'สินค้าและบริการ',
workflow: 'ขั้นตอนการทำงาน',
property: 'คุณสมบัติ',
customer: 'ลูกค้า',
mainData: 'ข้อมูลหลัก',
agencies: 'หน่วยงาน',
businessType: 'ประเภทกิจการ',
},
sales: {
@ -259,7 +248,6 @@ export default {
manual: {
title: 'คู่มือ',
usage: 'การใช้งาน',
troubleshooting: 'การแก้ปัญหา',
},
},
@ -338,7 +326,7 @@ export default {
letterAndNumOnly: 'โปรดใช้เฉพาะ _ ตัวอักษรภาษาอังกฤษและตัวเลขเท่านั้น',
numOnly: 'โปรดใช้เฉพาะตัวเลขเท่านั้น',
requireLength: 'กรุณากรอกให้ครบ {msg} หลัก',
branchNameField: "โปรดใช้ตัวอักษร ตัวเลข หรือ . , - ' ( ) & เท่านั้น",
branchNameField: "โปรดใช้ตัวอักษร ตัวเลข หรือ . , - ' & เท่านั้น",
branchNameENField:
"โปรดใช้ตัวอักษรภาษาอังกฤษ ตัวเลข หรือ . , - ' & เท่านั้น",
passportFormat: 'กรุณากรอกหมายเลขพาสปอร์ตให้ถูกต้องตามรูปแบบ',
@ -467,13 +455,6 @@ export default {
citizenId: 'เลขที่บัตรประชาชน',
citizenIssue: 'วันที่ออกบัตร',
citizenExpire: 'วันที่หมดอายุ',
agencyStatus: 'สถานะเอเจนซี่',
normal: 'ปกติ',
canceled: 'ยกเลิก',
blacklist: 'แบล็คลิสต์',
contactName: 'ชื่อผู้ติดต่อ',
contactTel: 'เบอร์โทรศัพท์ผู้ติดต่อ',
addressForeign: 'ใช้ที่อยู่ต่างประเทศ',
},
},
customer: {
@ -500,16 +481,15 @@ export default {
},
prefix: {
mr: 'นาย',
mrs: 'นาง',
miss: 'นางสาว',
mr: 'Mr.',
mrs: 'Mrs.',
miss: 'Miss.',
},
citizenId: 'บัตรประจำตัวประชาชน',
religion: 'ศาสนา',
issueDate: 'วันที่ออกหนังสือ',
passportExpiryDate: 'วันหiมดอายุหนังสือเดินทาง',
taxpayyerNo: 'เลขที่ประจำตัวผู้เสียภาษี',
ownerName: 'ชื่อนายจ้าง',
firstName: 'ชื่อ ',
@ -593,7 +573,7 @@ export default {
family: 'ข้อมูลครอบครัว',
},
workerStatus: 'สถานะคนงาน',
previousPassportNumber: 'หมายเลขหนังสือเดินทางเล่มเก่า',
previousPassportNumber: 'หมายเลขอันเก่าหนังสือเดินทาง',
employerBranch: 'สาขานายจ้าง',
employeeCode: 'รหัสลูกจ้าง',
nrcNo: 'เลขบัตรประจำตัวคนซึ่งไม่มีสัญชาติไทย (N.R.C No.)',
@ -626,7 +606,7 @@ export default {
placeOfBirth: 'สถานที่เกิด',
countryOfbirth: 'ประเทศที่เกิด',
issueCountry: 'ประเทศที่ออก',
entryCount: 'จำนวนวันที่เข้าประเทศ',
entryCount: 'จำนวนที่เข้าประเทศ',
employerSelect: {
branchName: 'ชื่อสาขา',
customerName: 'ชื่อนายจ้าง',
@ -654,7 +634,7 @@ export default {
permitIssuedAt: 'สถานที่ออกใบอนุญาต',
permitIssueDate: 'วันที่ออกใบอนุญาตทำงาน',
permitExpireDate: 'วันที่หมดอายุใบอนุญาตทำงาน',
identityNo: 'เลขประจำตัวคนต่างด้าว (13 หลัก)',
identityNo: 'เลขประจำตัวคนต่างด้าว (13หลัก)',
},
formFamily: {
citizenId:
@ -772,13 +752,10 @@ export default {
},
quotation: {
ownOnly: 'เห็นเฉพาะใบเสนอราคาของตัวเอง',
quotationDate: 'วันที่ใบเสนอราคา',
seller: 'ผู้ขาย',
paymentChannels: 'ช่องทางชำระเงิน',
channelsThat: 'ช่องทางที่',
refNo: 'เลขที่อ้างอิง',
bankAccount: 'บัญชีธนาคาร',
bankAccountNumber: 'เลขบัญชีธนาคาร',
bankAccountName: 'ชื่อบัญชี',
inTheNameOf: 'ในนาม',
@ -926,10 +903,6 @@ export default {
code: 'รหัสหน่วยงาน',
group: 'กลุ่มหน่วยงาน',
name: 'ชื่อหน่วยงาน',
contactName: 'ชื่อผู้ติดต่อ',
contactTel: 'เบอร์โทรผู้ติดต่อ',
bankInfo: 'ข้อมูลธนาคาร',
attachment: 'เอกสารเพิ่มเติม',
},
requestList: {
@ -951,7 +924,6 @@ export default {
nonLocalEmployee: 'พนักงานนอกพื้นที่',
noWorkflowTemplate: 'คุณไม่ได้เลือกแม่แบบขั้นตอนการทำงาน',
salesRepresentative: 'พนักงานขาย',
dataOffice: 'สำนักงานเขตจัดหางาน',
ref: 'อ้างอิง',
action: {
title: 'จัดการ',
@ -1070,9 +1042,6 @@ export default {
confirmDebitNoteAccept: 'ยืนยันการตอบรับใบเพิ่มหนี้',
},
message: {
copy: 'คัดลอก',
warningPaste: 'คุณต้องการที่จะเเทนที่ข้อมูลที่คัดลอกมาใหม่ใช่หรือไม่',
warningCopyEmpty: 'คุณยังไม่ได้คัดลอกข้อมูล',
quotationAccept: 'เมื่อตอบรับเเล้วจะไม่สามารถแก้ไขได้อีก',
beingUse: '"{msg}" มีการใช้งานอยู่',
incompleteDataEntry: 'กรอกข้อมูลไม่ครบในหน้า {tap}',
@ -1194,7 +1163,6 @@ export default {
'สินค้าที่มีชื่อเดียวกันมีในระบบแล้ว หากคุณต้องการสร้างด้วยชื่อนี้โปรดเลือกรหัสอื่น',
userExists: 'ชื่อผู้ใช้นี้มีอยู่ในระบบอยู่แล้ว',
sameNameExists: 'ชื่อนี้ถูกใช้ไปแล้ว',
samePropertyNameExists: 'คุณสมบัตินี้มีอยู่ในระบบอยู่แล้ว',
validateError: 'เกิดข้อผิดพลาดจากการตรวจสอบ',
codeMisMatch: 'รหัสไม่ตรงกัน',
@ -1220,8 +1188,6 @@ export default {
'มีงานหนึ่งงานหรือมากกว่าที่ไม่อยู่ในสถานะรอดำเนินการ',
reqNotMet: 'ไม่ตรงกัน',
systemError: 'ระบบเกิดข้อผิดพลาด',
taskOrderInvalid: 'โปรดเลือก สินค้าเเละสาขา',
flowAccountProductIdNotFound: 'ไม่พบสินค้าใน flow account',
},
},
@ -1493,26 +1459,4 @@ export default {
type: 'ประเภท',
},
},
dateRange: {
today: 'วันนี้',
yesterday: 'เมื่อวานนี้',
thisWeek: 'สัปดาห์นี้',
lastWeek: 'สัปดาห์ที่แล้ว',
thisMonth: 'เดือนนี้',
lastMonth: 'เดือนที่แล้ว',
thisYear: 'ปีนี้',
lastYear: 'ปีที่แล้ว',
last7Days: '7 วันที่ผ่านมา',
last30Days: '30 วันที่ผ่านมา',
last90Days: '90 วันที่ผ่านมา',
customDateRange: 'กำหนดช่วงวันที่เอง',
},
businessType: {
title: 'ประเภทกิจการ',
caption: 'จัดการประเภทกิจการ',
name: 'ชื่อประเภทกิจการ',
nameEn: 'ชื่อประเภทกิจการ (ภาษาอังกฤษ)',
},
};

View file

@ -7,7 +7,7 @@ import useMyBranch from 'stores/my-branch';
import { getUserId, getRole } from 'src/services/keycloak';
import { useQuasar } from 'quasar';
import { useI18n } from 'vue-i18n';
import { canAccess } from 'src/stores/utils';
import { isRoleInclude } from 'src/stores/utils';
type Menu = {
label: string;
@ -71,50 +71,82 @@ function initMenu() {
{
label: 'branch',
route: '/branch-management',
hidden: !canAccess('branch'),
hidden: !isRoleInclude([
'system',
'head_of_admin',
'admin',
'branch_manager',
'head_of_accountant',
]),
},
{
label: 'personnel',
route: '/personnel-management',
hidden: !canAccess('personnel'),
},
{
label: 'group',
route: '/group-management',
hidden: !canAccess('personnel'),
hidden: !isRoleInclude([
'owner',
'system',
'head_of_admin',
'admin',
'branch_manager',
]),
},
{
label: 'workflow',
route: '/workflow',
hidden: !canAccess('workflow'),
hidden: !isRoleInclude(['system', 'head_of_admin', 'admin']),
},
{
label: 'property',
route: '/property',
hidden: !canAccess('workflow'),
},
{
label: 'businessType',
route: '/business-type',
hidden: !isRoleInclude(['system', 'head_of_admin', 'admin']),
},
{
label: 'productService',
route: '/product-service',
hidden: !isRoleInclude([
'system',
'head_of_admin',
'admin',
'branch_manager',
'head_of_accountant',
'head_of_sale',
'sale',
]),
},
{
label: 'customer',
route: '/customer-management',
hidden: !canAccess('customer'),
hidden: !isRoleInclude([
'system',
'head_of_admin',
'admin',
'branch_manager',
'head_of_accountant',
'accountant',
'head_of_sale',
'sale',
]),
},
{
label: 'agencies',
route: '/agencies-management',
hidden: !isRoleInclude(['system', 'head_of_admin', 'admin']),
},
],
},
{
label: 'menu.sales',
icon: 'mdi-store-settings-outline',
hidden: !isRoleInclude([
'system',
'head_of_admin',
'admin',
'branch_manager',
'head_of_accountant',
'accountant',
'head_of_sale',
'sale',
]),
children: [
{ label: 'quotation', route: '/quotation' },
{ label: 'invoice', route: '/invoice' },
@ -137,6 +169,16 @@ function initMenu() {
label: 'menu.account',
icon: 'mdi-bank-outline',
disabled: false,
hidden: !isRoleInclude([
'system',
'head_of_admin',
'admin',
'branch_manager',
'head_of_accountant',
'accountant',
'head_of_sale',
'sale',
]),
children: [
{ label: 'receipt', route: '/receipt' },
{ label: 'creditNote', route: '/credit-note' },
@ -158,13 +200,10 @@ function initMenu() {
{
label: 'menu.overall',
icon: 'mdi-monitor-dashboard',
hidden: !isRoleInclude(['system', 'head_of_admin', 'admin', 'executive']),
children: [
{ label: 'report', route: '/report' },
{
label: 'dashboard',
route: '/dash-board',
hidden: !canAccess('dashBoard'),
},
{ label: 'dashboard', route: '/dash-board' },
],
},
@ -176,10 +215,6 @@ function initMenu() {
label: 'usage',
route: '/manual',
},
{
label: 'troubleshooting',
route: '/troubleshooting',
},
],
},
];

View file

@ -2,7 +2,7 @@
import { ref, onMounted, computed, reactive } from 'vue';
import { storeToRefs } from 'pinia';
import { useQuasar } from 'quasar';
import { getUserId, getUsername, getName, logout, getRole } from 'src/services/keycloak';
import { getUserId, getUsername, logout, getRole } from 'src/services/keycloak';
import { Icon } from '@iconify/vue';
import { useI18n } from 'vue-i18n';
import moment from 'moment';
@ -39,7 +39,7 @@ const configStore = useConfigStore();
const { data: notificationData } = storeToRefs(notificationStore);
const { visible } = storeToRefs(loaderStore);
const { t, locale } = useI18n({ useScope: 'global' });
const { t } = useI18n({ useScope: 'global' });
const userStore = useUserStore();
const canvasModal = ref(false);
@ -52,14 +52,8 @@ const unread = computed<number>(
);
const userImage = ref<string>();
const userGender = ref('');
const userName = ref({ th: '', en: '' });
const canvasRef = ref();
const displayName = computed(() => {
if (!userName.value.th && !userName.value.en) return getName() || 'Guest';
return locale.value === 'eng' ? userName.value.en : userName.value.th;
});
const language: {
value: Lang;
label: string;
@ -167,14 +161,9 @@ onMounted(async () => {
if (user === 'admin') return;
if (uid) {
const res = await userStore.fetchById(uid);
if (res) {
if (res.gender) {
userGender.value = res.gender;
userImage.value = `${baseUrl}/user/${uid}/profile-image/${res.selectedImage}`;
}
//
userName.value.th = `${res.firstName || ''} ${res.lastName || ''}`.trim();
userName.value.en = `${res.firstNameEN || ''} ${res.lastNameEN || ''}`.trim();
if (res && res.gender) {
userGender.value = res.gender;
userImage.value = `${baseUrl}/user/${uid}/profile-image/${res.selectedImage}`;
}
}
});
@ -495,7 +484,6 @@ onMounted(async () => {
<!-- User -->
<ProfileMenu
id="btn-profile-menu"
:user-name="displayName"
@logout="doLogout"
@edit-personal-info="console.log('edit')"
@signature="

View file

@ -12,7 +12,6 @@ const filterRole = ref<string[]>();
defineProps<{
userImage?: string;
gender?: string;
userName?: string;
}>();
const inputFile = document.createElement('input');
@ -148,9 +147,9 @@ onMounted(async () => {
class="text-weight-bold ellipsis"
style="max-width: 9vw"
>
{{ userName || getName() }}
{{ getName() }}
<q-tooltip>
{{ userName || getName() }}
{{ getName() }}
</q-tooltip>
</span>
<span
@ -235,12 +234,12 @@ onMounted(async () => {
style="margin-top: 58px"
>
<span v-if="isLoggedIn()">
{{ userName || getName() }}
{{ getName() }}
</span>
<span v-else>{{ 'Guest' }}</span>
<q-tooltip>
<span v-if="isLoggedIn()">
{{ userName || getName() }}
{{ getName() }}
</span>
<span v-else>{{ 'Guest' }}</span>
</q-tooltip>

View file

@ -1,7 +1,7 @@
<script setup lang="ts">
// NOTE: Library
import { storeToRefs } from 'pinia';
import { onMounted, watch } from 'vue';
import { onMounted } from 'vue';
// NOTE: Components
@ -10,44 +10,22 @@ import { onMounted, watch } from 'vue';
import { useManualStore } from 'src/stores/manual';
import { useNavigator } from 'src/stores/navigator';
import { Icon } from '@iconify/vue/dist/iconify.js';
import { useRoute, useRouter } from 'vue-router';
// NOTE: Variable
const route = useRoute();
const router = useRouter();
const manualStore = useManualStore();
const navigatorStore = useNavigator();
const { dataManual, dataTroubleshooting } = storeToRefs(manualStore);
const { dataManual } = storeToRefs(manualStore);
async function fetchManual() {
const res = await manualStore.getManual();
dataManual.value = res ? res : [];
}
onMounted(async () => {
navigatorStore.current.title = 'menu.manual.title';
navigatorStore.current.path = [{ text: '' }];
await fetchManual();
});
watch(
() => route.name,
async () => {
if (route.name === 'Manual') {
const res = await manualStore.getManual();
dataManual.value = res ? res : [];
}
if (route.name === 'Troubleshooting') {
const res = await manualStore.getTroubleshooting();
dataTroubleshooting.value = res ? res : [];
if (
res.length &&
res.length === 1 &&
res[0].page &&
res[0].page.length === 1
) {
router.replace(
`/troubleshooting/${res[0].category}/${res[0].page[0].name}`,
);
}
}
},
{ immediate: true },
);
</script>
<template>
@ -56,7 +34,7 @@ watch(
>
<section class="scroll q-gutter-y-sm">
<q-expansion-item
v-for="v in $route.name === 'Manual' ? dataManual : dataTroubleshooting"
v-for="v in dataManual"
:key="v.labelEN"
:content-inset-level="0.5"
class="rounded overflow-hidden bordered"
@ -80,11 +58,7 @@ watch(
clickable
dense
class="dot items-center rounded q-my-xs"
:to="
$route.name === 'Manual'
? `/manual/${v.category}/${x.name}`
: `/troubleshooting/${v.category}/${x.name}`
"
:to="`/manual/${v.category}/${x.name}`"
>
<Icon
v-if="!!x.icon"

View file

@ -56,28 +56,14 @@ onUnmounted(() => {
async function getContent() {
if (!category.value || !page.value) return;
if (ROUTE.name === 'ManualView') {
const res = await manualStore.getManualByPage({
category: category.value,
pageName: page.value,
});
if (res && res.ok) {
const text = await res.text();
content.value = text;
contentParsed.value = md.parse(text, {});
}
}
if (ROUTE.name === 'TroubleshootingView') {
const res = await manualStore.getTroubleshootingByPage({
category: category.value,
pageName: page.value,
});
if (res && res.ok) {
const text = await res.text();
content.value = text;
contentParsed.value = md.parse(text, {});
}
const res = await manualStore.getManualByPage({
category: category.value,
pageName: page.value,
});
if (res && res.ok) {
const text = await res.text();
content.value = text;
contentParsed.value = md.parse(text, {});
}
}
@ -116,8 +102,8 @@ async function scrollTo(id: string) {
<template>
<main
class="full-height q-gutter-sm no-wrap"
:class="{ column: !toc && $q.screen.lt.md, 'row reverse': $q.screen.gt.sm }"
class="full-height q-gutter-sm"
:class="{ 'row reverse': $q.screen.gt.xs, column: $q.screen.xs }"
>
<section
v-if="toc"
@ -168,7 +154,7 @@ async function scrollTo(id: string) {
</q-list>
</section>
<section v-if="!toc && $q.screen.lt.md">
<section v-if="!toc && $q.screen.xs">
<q-btn
dense
class="full-width text-capitalize"
@ -181,7 +167,7 @@ async function scrollTo(id: string) {
</section>
<section
v-if="$q.screen.gt.xs || (!toc && $q.screen.xs)"
v-if="content || (!toc && $q.screen.xs)"
ref="wrapper"
class="markdown col scroll full-height rounded"
>
@ -198,9 +184,7 @@ async function scrollTo(id: string) {
md.render(
content.replaceAll(
'assets/',
$route.name === 'ManualView'
? `${baseUrl}/manual/${category}/assets/`
: `${baseUrl}/troubleshooting/${category}/assets/`,
`${baseUrl}/manual/${category}/assets/`,
),
)
"
@ -328,31 +312,7 @@ async function scrollTo(id: string) {
padding: 0px 16px;
}
.markdown :deep(h4) {
text-align: left;
margin-block: 0;
font-size: 16px;
font-weight: 600;
padding: 0px 16px;
}
.markdown :deep(video) {
width: 100%;
}
.markdown :deep(table) {
width: 100%;
border-collapse: collapse;
margin-bottom: 1.5rem;
}
.markdown :deep(:where(table th)) {
background: var(--surface-2);
border: 1px solid var(--border-color);
}
.markdown :deep(:where(table td, table th)) {
border: 1px solid var(--border-color);
padding: 0.25rem 1rem;
}
</style>

View file

@ -94,22 +94,21 @@ onMounted(async () => {
<template>
<main class="column full-height no-wrap">
<div class="surface-1 col bordered rounded column">
<div class="q-py-xs row items-center" style="padding-inline: 38px">
<div class="q-px-lg q-py-xs row items-center">
<q-checkbox
size="xs"
:model-value="noti.length > 0 && selectedNoti.length === noti.length"
:disable="noti.length === 0"
:model-value="selectedNoti.length === noti.length"
class="q-px-sm"
@click="toggleSelection('', true)"
/>
<q-separator vertical inset spaced="md" />
<q-btn
v-if="selectedNoti.length === 0"
icon="mdi-refresh"
rounded
flat
dense
size="sm"
class="app-text-muted-2 q-mt-xs"
size="xs"
class="app-text-muted-2 q-ml-sm q-mt-xs"
@click="async () => await fetchNoti()"
>
<q-tooltip>Refresh</q-tooltip>
@ -120,8 +119,8 @@ onMounted(async () => {
rounded
flat
dense
size="sm"
class="app-text-muted-2"
size="xs"
class="app-text-muted-2 q-ml-sm"
@click="async () => await deleteNoti()"
>
<q-tooltip>{{ $t('general.delete') }}</q-tooltip>
@ -132,7 +131,7 @@ onMounted(async () => {
rounded
flat
dense
size="sm"
size="xs"
class="app-text-muted-2 q-mx-sm"
@click="async () => await markAsRead()"
>
@ -178,13 +177,7 @@ onMounted(async () => {
<q-badge
rounded
class="q-ml-md"
:color="
(tab.value === 'all'
? noti.length
: noti.filter((v) => !v.read).length) > 0
? 'info'
: 'grey'
"
style="background: hsl(var(--info-bg))"
>
{{
tab.value === 'all'

View file

@ -6,11 +6,11 @@ import { Icon } from '@iconify/vue';
import { BranchContact } from 'stores/branch-contact/types';
import { useQuasar } from 'quasar';
import { useI18n } from 'vue-i18n';
import type { QTableProps, QTableSlots } from 'quasar';
import type { QSelect, QTableProps, QTableSlots } from 'quasar';
import { resetScrollBar } from 'src/stores/utils';
import useBranchStore from 'stores/branch';
import useFlowStore from 'stores/flow';
import { isRoleInclude, canAccess } from 'stores/utils';
import { isRoleInclude } from 'stores/utils';
import {
BranchWithChildren,
BranchCreate,
@ -52,7 +52,6 @@ import {
UndoButton,
} from 'components/button';
import { useNavigator } from 'src/stores/navigator';
import AdvanceSearch from 'src/components/shared/AdvanceSearch.vue';
const $q = useQuasar();
const { t } = useI18n();
@ -73,6 +72,7 @@ const typeBranchItem = [
color: 'var(--blue-6-hsl)',
},
];
const refFilter = ref<InstanceType<typeof QSelect>>();
const holdDialog = ref(false);
const isSubCreate = ref(false);
const columns = [
@ -175,8 +175,6 @@ const qrCodeDialog = ref(false);
const qrCodeimageUrl = ref<string>('');
const formLastSubBranch = ref<number>(0);
const searchDate = ref<string[]>([]);
const branchStore = useBranchStore();
const flowStore = useFlowStore();
const { locale } = useI18n();
@ -717,20 +715,12 @@ async function fetchList(opts: {
tree?: boolean;
withHead?: boolean;
filter?: 'head' | 'sub';
startDate?: string;
endDate?: string;
}) {
await branchStore.fetchList(opts);
}
watch([inputSearch, searchDate], () => {
fetchList({
tree: true,
query: inputSearch.value,
withHead: true,
startDate: searchDate.value[0],
endDate: searchDate.value[1],
});
watch(inputSearch, () => {
fetchList({ tree: true, query: inputSearch.value, withHead: true });
currentSubBranch.value = undefined;
});
@ -791,8 +781,6 @@ async function onSubmit(submitSelectedItem?: boolean) {
);
if (!res) return;
formType.value = 'view';
formData.value.codeHeadOffice = formData.value.code = res.code;
imageUrl.value = `${baseUrl}/branch/${res.id}/image/${res.selectedImage}`;
@ -867,9 +855,7 @@ async function onSubmit(submitSelectedItem?: boolean) {
actionText: t('dialog.action.ok'),
persistent: true,
title: t('form.warning.title'),
cancel: () => {
formType.value = 'create';
},
cancel: () => {},
action: async () => {
await createBranch();
},
@ -1050,7 +1036,7 @@ watch(currentHq, () => {
{{ $t('branch.allBranch') }}
</div>
<q-btn
v-if="isRoleInclude(['system'])"
v-if="isRoleInclude(['head_of_admin', 'admin', 'system'])"
round
flat
size="md"
@ -1074,7 +1060,6 @@ watch(currentHq, () => {
<div class="col full-width scroll">
<div class="q-pa-md">
<TreeComponent
:hide-create="!canAccess('branch', 'create')"
v-model:nodes="treeData"
v-model:expanded-tree="expandedTree"
node-key="id"
@ -1185,49 +1170,26 @@ watch(currentHq, () => {
<template v-slot: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 && statusFilter !== 'all'"
>
<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"
v-model="statusFilter"
outlined
dense
autocomplete="off"
option-value="value"
option-label="label"
map-options
emit-value
:for="'field-select-status'"
:options="[
{ label: $t('general.all'), value: 'all' },
{
label: $t('status.ACTIVE'),
value: 'statusACTIVE',
},
{
label: $t('status.INACTIVE'),
value: 'statusINACTIVE',
},
]"
<template v-if="$q.screen.lt.md" v-slot:append>
<span class="row">
<q-separator vertical />
<q-btn
icon="mdi-filter-variant"
unelevated
class="q-ml-sm"
padding="4px"
size="sm"
rounded
@click="refFilter?.showPopup"
/>
</AdvanceSearch>
</span>
</template>
</q-input>
<div class="row col-md-6 justify-end">
<q-select
v-if="$q.screen.gt.sm"
v-show="$q.screen.gt.sm"
ref="refFilter"
v-model="statusFilter"
outlined
dense
@ -1562,18 +1524,8 @@ watch(currentHq, () => {
</q-td>
<q-td>
<KebabAction
v-if="
!currentHq.id
? canAccess('branch', 'create')
: true
"
:status="props.row.status"
:idName="props.row.name"
:hide-delete="
!currentHq.id
? !isRoleInclude(['system'])
: !canAccess('branch', 'create')
"
@view="
if (props.row.isHeadOffice) {
triggerEdit(
@ -1713,18 +1665,8 @@ watch(currentHq, () => {
>
<template v-slot:action>
<KebabAction
v-if="
!currentHq.id
? canAccess('branch', 'create')
: true
"
:status="props.row.status"
:idName="props.row.name"
:hide-delete="
!currentHq.id
? !isRoleInclude(['system'])
: !canAccess('branch', 'create')
"
@view="
if (props.row.isHeadOffice) {
triggerEdit(
@ -2272,22 +2214,13 @@ watch(currentHq, () => {
@click="drawerEdit()"
type="button"
/>
<template
v-if="
formType !== 'edit' && formTypeBranch === 'headOffice'
? isRoleInclude(['system'])
: canAccess('branch', 'create')
"
>
<DeleteButton
v-if="formType !== 'edit'"
id="btn-info-basic-delete"
icon-only
@click="triggerDelete(currentEdit.id)"
type="button"
/>
</template>
<DeleteButton
v-if="formType !== 'edit'"
id="btn-info-basic-delete"
icon-only
@click="triggerDelete(currentEdit.id)"
type="button"
/>
</div>
</div>
<div

View file

@ -1,67 +0,0 @@
<script setup lang="ts">
import { onMounted, ref, watch } from 'vue';
import { getInstance } from 'src/services/keycloak';
import { useNavigator } from 'src/stores/navigator';
import { useI18n } from 'vue-i18n';
import { useQuasar } from 'quasar';
import { initLang } from 'src/utils/ui';
const $q = useQuasar();
const { locale } = useI18n();
const navigatorStore = useNavigator();
const EDM_SERVICE = import.meta.env.VITE_EDM_MICRO_FRONTEND_URL;
const kc = getInstance();
const at = ref(kc.token);
const rt = ref(kc.refreshToken);
const iframe = ref<InstanceType<typeof HTMLIFrameElement>>();
function sendMessage() {
iframe?.value?.contentWindow?.postMessage(
{
i18n: locale.value,
darkMode: $q.dark.isActive,
},
'*',
);
}
onMounted(() => {
initLang();
currentLocale.value = locale.value;
navigatorStore.current.title = 'menu.dms';
navigatorStore.current.path = [
{
text: '',
i18n: true,
handler: () => {},
},
];
sendMessage();
});
watch(
() => kc.token,
() => {
at.value = kc.token;
rt.value = kc.refreshToken;
},
);
watch([locale, $q.dark], () => {
sendMessage();
});
const currentLocale = ref(locale.value);
</script>
<template>
<iframe
ref="iframe"
:src="`${EDM_SERVICE}/user?at=${at}&rt=${rt}&lang=${currentLocale}`"
frameborder="0"
class="full-width full-height rounded"
allowtransparency="true"
></iframe>
</template>
<style scoped></style>

View file

@ -10,8 +10,8 @@ import useOptionStore from 'stores/options';
import useAddressStore from 'stores/address';
import useMyBranch from 'src/stores/my-branch';
import { calculateAge } from 'src/utils/datetime';
import { useQuasar, type QTableProps } from 'quasar';
import { dialog, baseUrl, setPrefixName } from 'stores/utils';
import { QSelect, useQuasar, type QTableProps } from 'quasar';
import { dialog, baseUrl } from 'stores/utils';
import { useNavigator } from 'src/stores/navigator';
import { isRoleInclude, resetScrollBar } from 'src/stores/utils';
import { BranchUserStats } from 'stores/branch/types';
@ -49,7 +49,6 @@ import FormPerson from 'components/02_personnel-management/FormPerson.vue';
import FormByType from 'components/02_personnel-management/FormByType.vue';
import FormInformation from 'components/02_personnel-management/FormInformation.vue';
import PaginationPageSize from 'src/components/PaginationPageSize.vue';
import AdvanceSearch from 'src/components/shared/AdvanceSearch.vue';
const { locale, t } = useI18n();
const $q = useQuasar();
@ -74,6 +73,7 @@ const isImageEdit = ref(false);
const imageDialog = ref(false);
const infoDrawerEdit = ref(false);
const refreshImageState = ref(false);
const refFilter = ref<InstanceType<typeof QSelect>>();
const firstScroll = ref(false);
const inputSearch = ref('');
@ -93,14 +93,12 @@ const currentUser = ref<User>();
const userCode = ref<string>();
const statusToggle = ref(true);
const userFile = ref<File[]>([]);
const userFileList = ref<{ name: string; url: string }[]>([]);
const agencyFile = ref<File[]>([]);
const agencyFileList = ref<{ name: string; url: string }[]>([]);
const typeStats = ref<UserTypeStats>();
const userStats = ref<BranchUserStats[]>();
const searchDate = ref<[]>([]);
const urlProfile = ref<string>();
const profileFileImg = ref<File | null>(null);
const imageList = ref<{ selectedImage: string; list: string[] }>();
@ -126,7 +124,7 @@ const defaultFormData = {
streetEN: '',
street: '',
trainingPlace: null,
importNationality: [],
importNationality: null,
sourceNationality: null,
licenseExpireDate: null,
licenseIssueDate: null,
@ -153,18 +151,6 @@ const defaultFormData = {
citizenExpire: null,
citizenIssue: null,
citizenId: '',
contactName: '',
contactTel: '',
remark: '',
agencyStatus: '',
addressForeign: false,
provinceText: null,
districtText: null,
subDistrictText: null,
provinceTextEN: null,
districtTextEN: null,
subDistrictTextEN: null,
zipCodeText: null,
};
const formData = ref<UserCreate>({
@ -186,7 +172,7 @@ const formData = ref<UserCreate>({
streetEN: '',
street: '',
trainingPlace: null,
importNationality: [],
importNationality: null,
sourceNationality: null,
licenseExpireDate: null,
licenseIssueDate: null,
@ -213,18 +199,6 @@ const formData = ref<UserCreate>({
citizenExpire: null,
citizenIssue: null,
citizenId: '',
contactName: '',
contactTel: '',
remark: '',
agencyStatus: '',
addressForeign: false,
provinceText: null,
districtText: null,
subDistrictText: null,
provinceTextEN: null,
districtTextEN: null,
subDistrictTextEN: null,
zipCodeText: null,
});
const fieldSelectedOption = ref<{ label: string; value: string }[]>([
@ -353,7 +327,7 @@ function onClose(excludeDialog?: boolean) {
urlProfile.value = '';
profileFileImg.value = null;
infoDrawerEdit.value = false;
userFile.value = [];
agencyFile.value = [];
isEdit.value = false;
statusToggle.value = true;
isImageEdit.value = false;
@ -362,8 +336,6 @@ function onClose(excludeDialog?: boolean) {
mapUserType(currentTab.value);
imageList.value = { selectedImage: '', list: [] };
onCreateImageList.value = { selectedImage: '', list: [] };
userFileList.value = [];
userFile.value = [];
flowStore.rotate();
}
@ -384,10 +356,12 @@ async function openDialog(
isEdit.value = true;
await assignFormData(id);
const result = await userStore.fetchAttachment(id);
if (formData.value.userType === 'AGENCY') {
const result = await userStore.fetchAttachment(id);
if (result) {
userFileList.value = result;
if (result) {
agencyFileList.value = result;
}
}
}
if (userStore.userOption.hqOpts.length !== 0 && !id) {
@ -445,37 +419,15 @@ async function onSubmit(excludeDialog?: boolean) {
: '';
const formDataEdit = {
...formData.value,
checkpointEN: formData.value.checkpoint,
status: !statusToggle.value ? 'INACTIVE' : 'ACTIVE',
provinceId: formData.value.addressForeign
? null
: formData.value.provinceId,
districtId: formData.value.addressForeign
? null
: formData.value.districtId,
subDistrictId: formData.value.addressForeign
? null
: formData.value.subDistrictId,
provinceText: formData.value.addressForeign
? formData.value.provinceId
: null,
districtText: formData.value.addressForeign
? formData.value.districtId
: null,
subDistrictText: formData.value.addressForeign
? formData.value.subDistrictId
: null,
zipCodeText: formData.value.addressForeign
? formData.value.zipCode
: null,
} as const;
await userStore.editById(currentUser.value.id, formDataEdit);
if (userFile.value) {
if (currentUser.value.id && formDataEdit.userType === 'AGENCY') {
if (!agencyFile.value) return;
const payload: UserAttachmentCreate = {
file: userFile.value,
file: agencyFile.value,
};
if (payload?.file) {
@ -498,39 +450,16 @@ async function onSubmit(excludeDialog?: boolean) {
: hqId.value
? hqId.value
: '';
formData.value.checkpointEN = formData.value.checkpoint;
const result = await userStore.create(
{
...formData.value,
provinceId: formData.value.addressForeign
? null
: formData.value.provinceId,
districtId: formData.value.addressForeign
? null
: formData.value.districtId,
subDistrictId: formData.value.addressForeign
? null
: formData.value.subDistrictId,
provinceText: formData.value.addressForeign
? formData.value.provinceId
: null,
districtText: formData.value.addressForeign
? formData.value.districtId
: null,
subDistrictText: formData.value.addressForeign
? formData.value.subDistrictId
: null,
zipCodeText: formData.value.addressForeign
? formData.value.zipCode
: null,
},
formData.value,
onCreateImageList.value,
);
if (userFile.value && result) {
if (result && formData.value.userType === 'AGENCY') {
if (!agencyFile.value) return;
const payload: UserAttachmentCreate = {
file: userFile.value,
file: agencyFile.value,
};
if (payload?.file) {
@ -622,20 +551,12 @@ async function assignFormData(idEdit: string) {
currentUser.value = foundUser;
formData.value = {
branchId: foundUser.branch[0]?.id,
provinceId: foundUser.addressForeign
? foundUser.provinceText
: foundUser.provinceId,
districtId: foundUser.addressForeign
? foundUser.districtText
: foundUser.districtId,
subDistrictId: foundUser.addressForeign
? foundUser.subDistrictText
: foundUser.subDistrictId,
provinceId: foundUser.provinceId,
districtId: foundUser.districtId,
subDistrictId: foundUser.subDistrictId,
telephoneNo: foundUser.telephoneNo,
email: foundUser.email,
zipCode: foundUser.addressForeign
? foundUser.zipCodeText
: foundUser.zipCode,
zipCode: foundUser.zipCode,
gender: foundUser.gender,
addressEN: foundUser.addressEN,
address: foundUser.address,
@ -646,10 +567,7 @@ async function assignFormData(idEdit: string) {
street: foundUser.street,
streetEN: foundUser.streetEN,
trainingPlace: foundUser.trainingPlace,
importNationality:
typeof foundUser.importNationality === 'string'
? [foundUser.importNationality]
: foundUser.importNationality,
importNationality: foundUser.importNationality,
sourceNationality: foundUser.sourceNationality,
licenseNo: foundUser.licenseNo,
discountCondition: foundUser.discountCondition,
@ -669,8 +587,6 @@ async function assignFormData(idEdit: string) {
responsibleArea: foundUser.responsibleArea,
status: foundUser.status,
selectedImage: foundUser.selectedImage,
contactName: foundUser.contactName || '',
contactTel: foundUser.contactTel || '',
licenseExpireDate:
(foundUser.licenseExpireDate &&
new Date(foundUser.licenseExpireDate)) ||
@ -687,12 +603,6 @@ async function assignFormData(idEdit: string) {
(foundUser.citizenIssue && new Date(foundUser.citizenIssue)) || null,
citizenExpire:
(foundUser.citizenExpire && new Date(foundUser.citizenExpire)) || null,
remark: foundUser.remark || '',
agencyStatus: foundUser.agencyStatus || '',
addressForeign: foundUser.addressForeign || false,
provinceTextEN: foundUser.provinceTextEN,
districtTextEN: foundUser.districtTextEN,
subDistrictTextEN: foundUser.subDistrictTextEN,
};
formData.value.status === 'ACTIVE' || 'CREATED'
@ -751,8 +661,6 @@ async function fetchUserList(mobileFetch?: boolean) {
: statusFilter.value === 'statusACTIVE'
? 'ACTIVE'
: 'INACTIVE',
startDate: searchDate.value[0],
endDate: searchDate.value[1],
});
if (ret) {
@ -819,17 +727,7 @@ watch(
watch(
() => formData.value.userType,
async (type) => {
if (type !== 'AGENCY') {
formData.value.addressForeign = false;
formData.value.provinceId = null;
formData.value.districtId = null;
formData.value.subDistrictId = null;
formData.value.provinceTextEN = null;
formData.value.districtTextEN = null;
formData.value.subDistrictTextEN = null;
formData.value.zipCodeText = null;
}
async () => {
if (!infoDrawerEdit.value) return;
formData.value.registrationNo = null;
formData.value.startDate = null;
@ -837,11 +735,11 @@ watch(
formData.value.responsibleArea = null;
formData.value.discountCondition = null;
formData.value.sourceNationality = null;
formData.value.importNationality = [];
formData.value.importNationality = null;
formData.value.trainingPlace = null;
formData.value.checkpoint = null;
formData.value.checkpointEN = null;
userFile.value = [];
agencyFile.value = [];
},
);
@ -852,7 +750,7 @@ watch(
},
);
watch([inputSearch, statusFilter, pageSize, searchDate], async () => {
watch([inputSearch, statusFilter, pageSize], async () => {
if (userData.value) userData.value.result = [];
currentPage.value = 1;
@ -974,45 +872,26 @@ watch(
<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 && statusFilter !== 'all'"
>
<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"
v-model="statusFilter"
outlined
dense
option-value="value"
option-label="label"
map-options
emit-value
autocomplete="off"
:for="'field-select-status'"
:options="[
{ label: $t('general.all'), value: 'all' },
{ label: $t('general.active'), value: 'statusACTIVE' },
{
label: $t('general.inactive'),
value: 'statusINACTIVE',
},
]"
<template v-if="$q.screen.lt.md" v-slot:append>
<span class="row">
<q-separator vertical />
<q-btn
icon="mdi-filter-variant"
unelevated
class="q-ml-sm"
padding="4px"
size="sm"
rounded
@click="refFilter?.showPopup"
/>
</AdvanceSearch>
</span>
</template>
</q-input>
<div class="row col-md-5" style="white-space: nowrap">
<q-select
v-if="$q.screen.gt.sm"
v-show="$q.screen.gt.sm"
ref="refFilter"
v-model="statusFilter"
outlined
dense
@ -1362,7 +1241,7 @@ watch(
{{
locale === 'eng'
? `${props.row.firstNameEN} ${props.row.lastNameEN}`.trim()
: `${props.row.firstName || props.row.firstNameEN} ${props.row.lastName || props.row.lastNameEN}`.trim()
: `${props.row.firstName} ${props.row.lastName}`.trim()
}}
<q-tooltip
anchor="bottom left"
@ -1372,7 +1251,7 @@ watch(
{{
locale === 'eng'
? `${props.row.firstNameEN} ${props.row.lastNameEN}`.trim()
: `${props.row.firstName || props.row.firstNameEN} ${props.row.lastName || props.row.lastNameEN}`.trim()
: `${props.row.firstName} ${props.row.lastName}`.trim()
}}
</q-tooltip>
@ -1638,12 +1517,9 @@ watch(
hide-action
:is-edit="infoDrawerEdit"
:title="
(currentUser.namePrefix
? $t('customer.form.prefix.' + currentUser.namePrefix) + ' '
: '') +
(locale === 'eng'
locale === 'eng'
? `${currentUser.firstNameEN} ${currentUser.lastNameEN}`
: `${currentUser.firstName || currentUser.firstNameEN} ${currentUser.lastName || currentUser.lastNameEN}`)
: `${currentUser.firstName} ${currentUser.lastName}`
"
v-model:drawerOpen="infoDrawer"
:submit="() => onSubmit()"
@ -1675,18 +1551,7 @@ watch(
v-model:toggle-status="formData.status"
hideFade
:toggle-title="$t('status.title')"
:title="
setPrefixName(
{
namePrefix: formData.namePrefix,
firstName: formData.firstName || formData.firstNameEN,
lastName: formData.lastName || formData.lastNameEN,
firstNameEN: formData.firstNameEN,
lastNameEN: formData.lastNameEN,
},
{ locale },
)
"
:title="`${locale === 'eng' ? `${formData.firstNameEN} ${formData.lastNameEN}` : `${formData.firstName} ${formData.lastName}`}`"
:caption="userCode"
:img="
`${baseUrl}/user/${currentUser.id}/profile-image/${formData.selectedImage}`.concat(
@ -1871,15 +1736,12 @@ watch(
v-model:citizen-id="formData.citizenId"
v-model:citizen-issue="formData.citizenIssue"
v-model:citizen-expire="formData.citizenExpire"
v-model:contact-name="formData.contactName"
v-model:contact-tel="formData.contactTel"
:title="'personnel.form.personalInformation'"
prefix-id="drawer-info-personnel"
dense
outlined
separator
:readonly="!infoDrawerEdit"
:agency="formData.userType === 'AGENCY'"
class="q-mb-xl"
/>
@ -1897,15 +1759,10 @@ watch(
v-model:district-id="formData.districtId"
v-model:sub-district-id="formData.subDistrictId"
v-model:zip-code="formData.zipCode"
v-model:address-foreign="formData.addressForeign"
v-model:province-text-en="formData.provinceTextEN"
v-model:district-text-en="formData.districtTextEN"
v-model:sub-district-text-en="formData.subDistrictTextEN"
:readonly="!infoDrawerEdit"
prefix-id="drawer-info-personnel"
:title="'personnel.form.addressInformation'"
dense
:use-foreign-address="formData.userType === 'AGENCY'"
class="q-mb-xl"
/>
<FormByType
@ -1924,11 +1781,10 @@ watch(
v-model:import-nationality="formData.importNationality"
v-model:training-place="formData.trainingPlace"
v-model:checkpoint="formData.checkpoint"
v-model:user-file="userFile"
v-model:user-file-list="userFileList"
v-model:checkpoint-en="formData.checkpointEN"
v-model:agency-file="agencyFile"
v-model:agency-file-list="agencyFileList"
v-model:user-id="currentUser.id"
v-model:remark="formData.remark"
v-model:agency-status="formData.agencyStatus"
/>
</div>
</div>
@ -1972,18 +1828,7 @@ watch(
}[formData.gender]
"
:toggleTitle="$t('status.title')"
:title="
setPrefixName(
{
namePrefix: formData.namePrefix,
firstName: formData.firstName,
lastName: formData.lastName,
firstNameEN: formData.firstNameEN,
lastNameEN: formData.lastNameEN,
},
{ locale },
)
"
:title="`${locale === 'eng' ? `${formData.firstNameEN} ${formData.lastNameEN}` : `${formData.firstName} ${formData.lastName}`}`"
:fallbackImg="
{
male: '/no-img-man.png',
@ -2009,6 +1854,7 @@ watch(
<div
class="col"
id="personnel-form"
:class="{
'q-px-lg q-pb-lg': $q.screen.gt.sm,
'q-px-md q-pb-sm': !$q.screen.gt.sm,
@ -2052,7 +1898,7 @@ watch(
? [
{
name: $t('personnel.form.workInformation'),
anchor: 'dialog-form-work',
anchor: 'dialog-info-work',
},
]
: [],
@ -2068,7 +1914,6 @@ watch(
</div>
</div>
<div
id="personnel-form"
class="col-md-10 col-12 full-height scroll"
:class="{
'q-py-md q-pr-md ': $q.screen.gt.sm,
@ -2094,7 +1939,6 @@ watch(
id="dialog-form-personal"
prefix-id="form-dialog-personnel"
dense
:agency="formData.userType === 'AGENCY'"
outlined
separator
:title="'personnel.form.personalInformation'"
@ -2113,8 +1957,6 @@ watch(
v-model:citizen-id="formData.citizenId"
v-model:citizen-issue="formData.citizenIssue"
v-model:citizen-expire="formData.citizenExpire"
v-model:contact-name="formData.contactName"
v-model:contact-tel="formData.contactTel"
class="q-mb-xl"
/>
<AddressForm
@ -2131,13 +1973,8 @@ watch(
v-model:district-id="formData.districtId"
v-model:sub-district-id="formData.subDistrictId"
v-model:zip-code="formData.zipCode"
v-model:address-foreign="formData.addressForeign"
v-model:province-text-en="formData.provinceTextEN"
v-model:district-text-en="formData.districtTextEN"
v-model:sub-district-text-en="formData.subDistrictTextEN"
prefix-id="drawer-info-personnel"
dense
:use-foreign-address="formData.userType === 'AGENCY'"
class="q-mb-xl"
/>
<FormByType
@ -2155,10 +1992,8 @@ watch(
v-model:import-nationality="formData.importNationality"
v-model:training-place="formData.trainingPlace"
v-model:checkpoint="formData.checkpoint"
v-model:agency-status="formData.agencyStatus"
v-model:remark="formData.remark"
v-model:user-file="userFile"
v-model:user-file-list="userFileList"
v-model:checkpoint-en="formData.checkpointEN"
v-model:agency-file="agencyFile"
/>
</div>
</div>

View file

@ -9,7 +9,7 @@ import { baseUrl } from 'src/stores/utils';
import useCustomerStore from 'stores/customer';
import useFlowStore from 'stores/flow';
import useOptionStore from 'stores/options';
import { dialog, canAccess } from 'stores/utils';
import { dialog } from 'stores/utils';
import { Status } from 'stores/types';
import { Employee } from 'stores/employee/types';
@ -18,8 +18,6 @@ import { CustomerBranch, CustomerType } from 'stores/customer/types';
import { columnsEmployee } from './constant';
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 FloatingActionButton from 'components/FloatingActionButton.vue';
import SideMenu from 'components/SideMenu.vue';
@ -91,11 +89,6 @@ const prop = withDefaults(
currentCitizenId?: string;
gender: string;
selectedImage: string;
fetchImageList: (
id: string,
selectedName: string,
type: 'customer' | 'employee',
) => Promise<void>;
}>(),
{
color: 'green',
@ -103,6 +96,7 @@ const prop = withDefaults(
);
const currentBranchEmployee = ref<string>('');
const listEmployee = ref<Employee[]>([]);
const customerId = defineModel<string>('customerId', { required: true });
defineEmits<{
@ -112,6 +106,16 @@ defineEmits<{
(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 = [
{
name: 'branchName',
@ -253,6 +257,10 @@ async function fetchEmployee(opts: { branchId: string; pageSize?: number }) {
}
}
onMounted(async () => {
await fetchList();
});
watch([customerId, inputSearch, currentStatus, pageSizeBranch], async () => {
await fetchList();
});
@ -272,22 +280,12 @@ watch(
}
},
);
onMounted(async () => {
customerBranchFormState.value.currentCustomerId = route.params
.customerId as string;
await fetchList();
branch.value?.forEach((v) => {
currentBtnOpen.value.push(false);
});
});
</script>
<template>
<FloatingActionButton
style="z-index: 999"
v-if="$route.name !== 'CustomerManagement' && canAccess('customer', 'edit')"
v-if="$route.name !== 'CustomerManagement'"
@click="openEmployerBranchForm('create')"
hide-icon
></FloatingActionButton>
@ -475,6 +473,7 @@ onMounted(async () => {
<q-tr
:class="{
'app-text-muted': props.row.status === 'INACTIVE',
'cursor-pointer': props.row._count?.branch !== 0,
}"
:props="props"
@click="$emit('viewDetail', props.row, props.rowIndex)"
@ -548,13 +547,7 @@ onMounted(async () => {
v-if="branchFieldSelected.includes('businessTypePure')"
class="text-left"
>
{{
props.row.businessType
? props.row.businessType[
$i18n.locale === 'eng' ? 'nameEN' : 'name'
]
: '-'
}}
{{ useOptionStore().mapOption(props.row.businessType) || '-' }}
</q-td>
<q-td
v-if="branchFieldSelected.includes('totalEmployee')"
@ -573,6 +566,8 @@ onMounted(async () => {
await fetchEmployee({
branchId: currentBranchEmployee,
pageSize: 999,
passport: true,
visa: true,
});
currentBtnOpen.map((v, i) => {
@ -620,7 +615,7 @@ onMounted(async () => {
<div class="text-center">
<TableEmpoloyee
:prefix-id="props.row.registerName || props.row.firstName"
:add-button="canAccess('customer', 'edit')"
add-button
in-table
:list-employee="listEmployee"
:columns-employee="columnsEmployee"
@ -643,15 +638,10 @@ onMounted(async () => {
"
@history="(item) => {}"
@view="
async (item) => {
(item) => {
employeeFormState.drawerModal = true;
//employeeFormState.isEmployeeEdit = true;
employeeFormStore.assignFormDataEmployee(item.id);
await fetchImageList(
item.id,
item.selectedImage || '',
'employee',
);
}
"
/>
@ -678,11 +668,9 @@ onMounted(async () => {
? `${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 || ''}`,
telephone: props.row.telephoneNo,
businessTypePure: props.row.businessType
? props.row.businessType[
$i18n.locale === 'eng' ? 'nameEN' : 'name'
]
: '-',
businessTypePure: useOptionStore().mapOption(
props.row.businessType,
),
totalEmployee: props.row._count?.employee,
}"
:visible-columns="branchFieldSelected"
@ -771,10 +759,7 @@ onMounted(async () => {
/>
<DeleteButton
icon-only
v-if="
customerBranchFormState.dialogType === 'info' &&
canAccess('customer', 'edit')
"
v-if="customerBranchFormState.dialogType === 'info'"
@click="
() => {
deleteBranchById(customerBranchFormData.id || '');
@ -868,6 +853,7 @@ onMounted(async () => {
v-model:last-name-en="customerBranchFormData.lastNameEN"
v-model:gender="customerBranchFormData.gender"
v-model:birth-date="customerBranchFormData.birthDate"
v-model:customer-name="customerBranchFormData.customerName"
v-model:legal-person-no="customerBranchFormData.legalPersonNo"
v-model:branch-code="customerBranchFormData.code"
v-model:register-name="customerBranchFormData.registerName"
@ -897,14 +883,15 @@ onMounted(async () => {
</div>
<EmployerFormBusiness
dense
class="q-mb-xl"
outlined
prefix-id="employer-branch"
:readonly="customerBranchFormState.dialogType === 'info'"
v-model:business-type-id="customerBranchFormData.businessTypeId"
v-model:bussiness-type="customerBranchFormData.businessType"
v-model:job-position="customerBranchFormData.jobPosition"
v-model:job-description="customerBranchFormData.jobDescription"
v-model:pay-date="customerBranchFormData.payDate"
v-model:pay-date-en="customerBranchFormData.payDateEN"
v-model:pay-date-e-n="customerBranchFormData.payDateEN"
v-model:wage-rate="customerBranchFormData.wageRate"
v-model:wage-rate-text="customerBranchFormData.wageRateText"
/>
@ -995,7 +982,7 @@ onMounted(async () => {
v-model:email="customerBranchFormData.email"
v-model:contact-tel="customerBranchFormData.contactTel"
v-model:office-tel="customerBranchFormData.officeTel"
v-model:agent-user-id="customerBranchFormData.agentUserId"
v-model:agent="customerBranchFormData.agent"
/>
</div>
</div>
@ -1011,18 +998,6 @@ onMounted(async () => {
/>
</template>
</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>
<style scoped>

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,514 +0,0 @@
<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

@ -137,12 +137,7 @@ watch(
@update:model-value="
(v) => (typeof v === 'string' ? (registerName = v.trim()) : '')
"
:rules="[
(val) => !!val || $t('form.error.required'),
(val) =>
/^[A-Za-z0-9ก-๙\s&.,'()-]+$/.test(val) ||
$t('form.error.branchNameField'),
]"
:rules="[(val: string) => !!val || $t('form.error.required')]"
/>
<q-input
@ -158,15 +153,29 @@ watch(
(v) => (typeof v === 'string' ? (registerNameEN = v.trim()) : '')
"
:rules="[
(val) => !!val || $t('form.error.required'),
(val) =>
/^[A-Za-z0-9\s&.,'-]+$/.test(val) ||
$t('form.error.branchNameENField'),
(val: string) =>
val === '' ||
/^[0-9A-Za-z\s.,]+$/.test(val) ||
$t('form.error.letterOnly'),
]"
/>
</div>
<div class="col-12 row q-col-gutter-sm">
<q-input
dense
outlined
:readonly="readonly"
hide-bottom-space
class="col-12 col-md-5"
:label="$t('customer.form.employerName')"
for="input-legal-person-no"
:model-value="customerName"
@update:model-value="
(v) => (typeof v === 'string' ? (customerName = v.trim()) : '')
"
/>
<q-input
dense
outlined
@ -336,7 +345,6 @@ watch(
(v) => (typeof v === 'string' ? (prefixName = v) : '')
"
@clear="prefixName = ''"
:rules="[(val: string) => !!val || $t('form.error.required')]"
>
<template v-slot:no-option>
<q-item>
@ -385,11 +393,11 @@ watch(
:readonly="readonly"
:disable="!readonly"
class="col-md-2 col-6"
label="Prefix"
label="Title"
:model-value="
readonly
? prefixName.toUpperCase() || '-'
: prefixName.toUpperCase() || ''
? capitalize(prefixName || '') || '-'
: capitalize(prefixName || '')
"
@update:model-value="
(v) => (typeof v === 'string' ? (prefixName = v) : '')

View file

@ -2,7 +2,6 @@
import useOptionStore from 'stores/options';
import SelectBranch from 'src/components/shared/select/SelectBranch.vue';
import { isRoleInclude } from 'src/stores/utils';
import SelectBusinessType from 'src/components/shared/select/SelectBusinessType.vue';
withDefaults(
defineProps<{
@ -103,13 +102,7 @@ const telephoneNo = defineModel<string>('telephoneNo', { default: '' });
class="col-md-6"
:readonly
:disabled="
!isRoleInclude([
'admin',
'system',
'head_of_admin',
'executive',
'accountant',
]) && !readonly
!isRoleInclude(['admin', 'system', 'head_of_admin']) && !readonly
"
:label="$t('customer.form.registeredBranch')"
select-first-value
@ -143,10 +136,15 @@ const telephoneNo = defineModel<string>('telephoneNo', { default: '' });
for="input-tax"
v-model="legalPersonNo"
/>
<SelectBusinessType
<q-input
dense
outlined
:readonly="readonly"
hide-bottom-space
class="col-6 col-md-3"
v-model:value="businessType"
:readonly
:label="$t('customer.table.businessTypePure')"
for="input-business-type"
:model-value="optionStore.mapOption(businessType)"
/>
</div>
@ -175,10 +173,15 @@ const telephoneNo = defineModel<string>('telephoneNo', { default: '' });
:label="$t('personnel.form.citizenId')"
for="input-citizen-id"
/>
<SelectBusinessType
<q-input
dense
outlined
:readonly="readonly"
hide-bottom-space
class="col-6 col-md-3"
v-model:value="businessType"
:readonly
:label="$t('customer.table.businessTypePure')"
for="input-first-name-en"
:model-value="optionStore.mapOption(businessType)"
/>
</div>

View file

@ -7,8 +7,6 @@ import { CustomerCreate } from 'stores/customer/types';
import EmployerFormAbout from './EmployerFormAbout.vue';
import EmployerFormAuthorized from './EmployerFormAuthorized.vue';
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 {
FormCitizen,
CorpFormBusinessRegistration,
@ -53,7 +51,6 @@ withDefaults(
actionDisabled?: boolean;
customerType?: 'CORP' | 'PERS';
hideAction?: boolean;
hideDelete?: boolean;
}>(),
{
hideAction: false,
@ -84,7 +81,7 @@ withDefaults(
/>
<DeleteButton
icon-only
v-if="readonly && !hideDelete"
v-if="readonly"
@click="$emit('delete')"
type="button"
:disabled="actionDisabled"
@ -144,6 +141,7 @@ withDefaults(
v-model:last-name-en="item.lastNameEN"
v-model:gender="item.gender"
v-model:birth-date="item.birthDate"
v-model:customer-name="item.customerName"
v-model:legal-person-no="item.legalPersonNo"
v-model:branch-code="item.code"
v-model:register-name="item.registerName"
@ -160,7 +158,7 @@ withDefaults(
outlined
:prefix-id="prefixId || 'employer'"
:readonly="readonly"
v-model:business-type-id="item.businessTypeId"
v-model:bussiness-type="item.businessType"
v-model:job-position="item.jobPosition"
v-model:job-description="item.jobDescription"
v-model:pay-date="item.payDate"
@ -222,6 +220,7 @@ withDefaults(
hide-action
:ocr="
async (group, file) => {
console.log(group);
if (group !== 'attachment') {
const res = await ocrStore.sendOcr({
file: file,
@ -246,20 +245,12 @@ withDefaults(
:auto-save="item.id !== ''"
:download="
(obj) => {
if (obj.group === 'citizen') {
customerStore.getFile({
parentId: item.id || '',
group: obj.group,
fileId: obj._meta.id,
download: true,
});
} else {
customerStore.getAttachment({
parentId: item.id || '',
name: obj._meta.id,
download: true,
});
}
customerStore.getFile({
parentId: item.id || '',
group: obj.group,
fileId: obj._meta.id,
download: true,
});
}
"
:delete-item="
@ -288,7 +279,7 @@ withDefaults(
_meta: any,
file: File | undefined,
) => {
if (group === 'citizen') {
if (group !== 'attachment') {
if (file !== undefined && item.id) {
const res = await customerStore.postMeta({
parentId: item.id || '',
@ -356,6 +347,7 @@ withDefaults(
});
const tempValue = resMeta.map(async (v: any) => {
console.log(v.expireDate);
return {
_meta: { ...v },
name: `${group}-${dateFormat(v.expireDate)}`,
@ -376,6 +368,7 @@ withDefaults(
});
const tempValue = (res as string[]).map(async (i: any) => {
console.log(i);
return {
_meta: { id: i, name: i },
name: i || '',

View file

@ -8,28 +8,12 @@ import { onMounted, watch } from 'vue';
import { ref } from 'vue';
import { useI18n } from 'vue-i18n';
import ThaiBahtText from 'thai-baht-text';
import SelectBusinessType from 'src/components/shared/select/SelectBusinessType.vue';
import BusinessTypeDialog from 'src/pages/16_business-type-management/BusinessTypeDialog.vue';
import useBusinessTypeStore from 'src/stores/business-type';
const { locale } = useI18n({ useScope: 'global' });
const rawOption = ref();
const businessTypeDialog = ref<boolean>(false);
const formDataBusinessType = ref<{
name: string;
nameEN: string;
}>({
name: '',
nameEN: '',
});
const useBusinessType = useBusinessTypeStore();
const businessTypeId = defineModel<string>('businessTypeId');
const bussinessType = defineModel<string>('bussinessType');
const jobPosition = defineModel<string>('jobPosition');
const jobDescription = defineModel<string>('jobDescription');
const payDate = defineModel<string>('payDate');
@ -44,8 +28,6 @@ const typeBusinessENOption = ref([]);
const jobPositionOption = ref([]);
const jobPositionENOption = ref([]);
const keySelect = ref<number>(0);
defineProps<{
title?: string;
dense?: boolean;
@ -55,33 +37,35 @@ defineProps<{
showTitle?: boolean;
}>();
function resetFormBusinessType() {
businessTypeDialog.value = false;
formDataBusinessType.value = { name: '', nameEN: '' };
}
async function submitBusinessType() {
const res = await useBusinessType.create(formDataBusinessType.value);
if (res) {
businessTypeId.value = res.id;
resetFormBusinessType();
keySelect.value++;
}
}
onMounted(async () => {
const resultOption = await fetch('/option/option.json');
rawOption.value = await resultOption.json();
typeBusinessENOption.value = rawOption.value.eng.businessType;
jobPositionENOption.value = rawOption.value.eng.position;
if (locale.value === 'eng') {
typeBusinessOption.value = rawOption.value.eng.businessType;
jobPositionOption.value = rawOption.value.eng.position;
}
if (locale.value === 'tha') {
typeBusinessOption.value = rawOption.value.tha.businessType;
jobPositionOption.value = rawOption.value.tha.position;
}
});
watch([typeBusinessOption, typeBusinessENOption], () => {
typeBusinessFilter = selectFilterOptionRefMod(
typeBusinessOption,
typeBusinessOptions,
'label',
);
typeBusinessENFilter = selectFilterOptionRefMod(
typeBusinessENOption,
typeBusinessENOptions,
'label',
);
});
watch(
() => rate.value,
(newValue) => {
@ -151,26 +135,69 @@ let jobPositionENFilter = selectFilterOptionRefMod(
<span>{{ $t('customerBranch.tab.business') }}</span>
</div>
<SelectBusinessType
:key="keySelect"
<q-select
outlined
clearable
use-input
fill-input
emit-value
map-options
hide-selected
hide-bottom-space
:hide-dropdown-icon="readonly"
input-debounce="0"
option-value="value"
option-label="label"
v-model="bussinessType"
class="col-md-6 col-12"
v-model:value="businessTypeId"
:readonly
creatable
lang="tha"
:dense="dense"
:readonly="readonly"
:label="$t('customer.form.businessType')"
:options="typeBusinessOptions"
:for="`${prefixId}-select-business-type`"
@filter="typeBusinessFilter"
:rules="[(val: string) => !!val || $t('form.error.required')]"
@create="() => (businessTypeDialog = true)"
/>
<SelectBusinessType
:key="keySelect"
>
<template v-slot:no-option>
<q-item>
<q-item-section class="text-grey">
{{ $t('general.noData') }}
</q-item-section>
</q-item>
</template>
</q-select>
<q-select
:for="`${prefixId}-input-bussiness-type-en`"
:id="`${prefixId}-input-bussiness-type-en`"
:label="`${$t('customer.form.businessType')} (EN)`"
outlined
clearable
use-input
fill-input
emit-value
map-options
hide-selected
hide-bottom-space
:hide-dropdown-icon="readonly"
input-debounce="0"
option-value="value"
option-label="label"
v-model="bussinessType"
class="col-md-6 col-12"
v-model:value="businessTypeId"
:readonly
creatable
lang="eng"
:dense="dense"
:readonly="readonly"
:options="typeBusinessENOptions"
@filter="typeBusinessENFilter"
:rules="[(val: string) => !!val || $t('form.error.required')]"
@create="() => (businessTypeDialog = true)"
/>
>
<template v-slot:no-option>
<q-item>
<q-item-section class="text-grey">
{{ $t('general.noData') }}
</q-item-section>
</q-item>
</template>
</q-select>
<q-select
outlined
@ -321,12 +348,4 @@ let jobPositionENFilter = selectFilterOptionRefMod(
"
/>
</div>
<BusinessTypeDialog
ref="refBusinessTypeDialog"
@close="resetFormBusinessType()"
@submit="submitBusinessType()"
v-model="businessTypeDialog"
v-model:data="formDataBusinessType"
/>
</template>

View file

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

View file

@ -69,19 +69,19 @@ export const uploadFileListCustomer: {
}[] = [
{
label: 'customer.typeFile.citizenId',
group: 'citizen',
value: 'citizen',
group: 'citizen',
},
{
label: 'customer.typeFile.registrationBook',
group: 'houseRegistration',
value: 'attachment',
group: 'houseRegistration',
},
{
label: 'customer.typeFile.houseMap',
group: 'vatRegistration',
value: 'attachment',
group: 'vatRegistration',
},
{
@ -92,7 +92,7 @@ export const uploadFileListCustomer: {
{
label: 'customer.typeFile.dbdCertificate',
group: 'dbdCertificate',
group: 'powerOfAttorney',
value: 'attachment',
},
@ -144,43 +144,43 @@ export const uploadFileListEmployee: {
},
{
label: 'customerEmployee.fileType.tm6',
group: 'tm6',
value: 'attachment',
group: 'tm6',
},
{
label: 'customerEmployee.fileType.workPermit',
group: 'workPermit',
value: 'attachment',
group: 'workPermit',
},
{
label: 'customerEmployee.fileType.noticeJobEmployment',
group: 'noticeJobEmployment',
value: 'attachment',
group: 'noticeJobEmployment',
},
{
label: 'customerEmployee.fileType.noticeJobEntry',
group: 'noticeJobEntry',
value: 'attachment',
group: 'noticeJobEntry',
},
{
label: 'customerEmployee.fileType.historyJob',
group: 'historyJob',
value: 'attachment',
group: 'historyJob',
},
{
label: 'customerEmployee.fileType.acceptJob',
group: 'acceptJob',
value: 'attachment',
group: 'acceptJob',
},
{
label: 'customerEmployee.fileType.receipt',
group: 'receipt',
value: 'attachment',
group: 'receipt',
},
{
label: 'customerEmployee.fileType.other',
group: 'other',
value: 'attachment',
group: 'other',
},
];

View file

@ -1,55 +1,21 @@
import { ref, watch } from 'vue';
import { defineStore } from 'pinia';
import { useI18n } from 'vue-i18n';
import {
CustomerBranch,
CustomerBranchCreate,
CustomerCreate,
CustomerType,
} from 'stores/customer/types';
import { Employee, EmployeeCreate } from 'stores/employee/types';
import { dialog } from 'stores/utils';
import useMyBranch from 'stores/my-branch';
import useCustomerStore from 'stores/customer';
import useEmployeeStore from 'stores/employee';
import useFlowStore from 'stores/flow';
import useMyBranchStore from 'stores/my-branch';
import { baseUrl } from 'src/stores/utils';
import { getRole, getUserId } from 'src/services/keycloak';
import { useRoute } from 'vue-router';
export const useCustomerForm = defineStore('form-customer', () => {
const customerStore = useCustomerStore();
const onCreateImageList = ref<{
selectedImage: string;
list: { url: string; imgFile: File | null; name: string }[];
}>({ selectedImage: '', list: [] });
const { t } = useI18n();
const flowStore = useFlowStore();
const userBranchStore = useMyBranchStore();
const registerAbleBranchOption = ref<{ id: string; name: string }[]>();
const currentBranchRootId = ref<string>('');
const tabFieldRequired = ref<{
[key: string]: (keyof CustomerBranchCreate)[];
}>({
main: [],
business: ['businessTypeId', 'jobPosition'],
address: [
'address',
'addressEN',
'provinceId',
'districtId',
'subDistrictId',
],
contact: [],
});
const defaultFormData: CustomerCreate = {
// code: '',
@ -88,7 +54,6 @@ export const useCustomerForm = defineStore('form-customer', () => {
formDataOcr: Record<string, any>;
isImageEdit: boolean;
currentCustomerId?: string;
imageList: { list: string[]; selectedImage: string };
}>({
dialogType: 'info',
dialogOpen: false,
@ -105,7 +70,6 @@ export const useCustomerForm = defineStore('form-customer', () => {
treeFile: [],
formDataOcr: {},
isImageEdit: false,
imageList: { list: [], selectedImage: '' },
});
watch(
@ -168,7 +132,6 @@ export const useCustomerForm = defineStore('form-customer', () => {
state.value.editCustomerCode = data.code;
state.value.customerImageUrl = `${baseUrl}/customer/${id}/image/${data.selectedImage}`;
state.value.defaultCustomerImageUrl = `${baseUrl}/customer/${id}/image/${data.selectedImage}`;
currentBranchRootId.value = data.branch[0].id || '';
resetFormData.registeredBranchId = data.registeredBranchId;
resetFormData.status = data.status;
@ -190,7 +153,7 @@ export const useCustomerForm = defineStore('form-customer', () => {
payDate: v.payDate,
jobDescription: v.jobDescription,
jobPosition: v.jobPosition,
businessTypeId: v.businessTypeId,
businessType: v.businessType,
employmentOffice: v.employmentOffice,
employmentOfficeEN: v.employmentOfficeEN,
telephoneNo: v.telephoneNo,
@ -227,6 +190,7 @@ export const useCustomerForm = defineStore('form-customer', () => {
contactTel: v.contactTel,
officeTel: v.officeTel,
agentUserId: v.agentUserId || undefined,
customerName: v.customerName,
authorizedName: v.authorizedName,
authorizedNameEN: v.authorizedNameEN,
@ -264,6 +228,7 @@ export const useCustomerForm = defineStore('form-customer', () => {
async function addCurrentCustomerBranch() {
if (currentFormData.value.customerBranch?.some((v) => !v.id)) return;
currentFormData.value.customerBranch?.push({
id: '',
customerId: '',
branchCode:
currentFormData.value.customerBranch.length !== 0
@ -297,8 +262,8 @@ export const useCustomerForm = defineStore('form-customer', () => {
birthDate:
currentFormData.value.customerBranch?.at(0)?.birthDate || undefined,
businessTypeId:
currentFormData.value.customerBranch?.at(0)?.businessTypeId || '',
businessType:
currentFormData.value.customerBranch?.at(0)?.businessType || '',
jobPosition:
currentFormData.value.customerBranch?.at(0)?.jobPosition || '',
jobDescription:
@ -335,6 +300,8 @@ export const useCustomerForm = defineStore('form-customer', () => {
currentFormData.value.customerBranch?.at(0)?.agentUserId || undefined,
status: 'CREATED',
customerName:
currentFormData.value.customerBranch?.at(0)?.customerName || '',
registerName:
currentFormData.value.customerBranch?.at(0)?.registerName || '',
registerNameEN:
@ -393,142 +360,23 @@ export const useCustomerForm = defineStore('form-customer', () => {
}
}
async function fetchListOfOptionBranch() {
if (registerAbleBranchOption.value) return;
const uid = getUserId();
const role = getRole();
if (!uid) return; // should not possible as the system require login to be able to access resource.
if (role?.includes('system')) {
const result = await userBranchStore.fetchListOptionBranch();
if (result && result.total > 0)
registerAbleBranchOption.value = result.result;
} else {
const result = await userBranchStore.fetchListMyBranch(uid);
if (result && result.total > 0)
registerAbleBranchOption.value = result.result;
}
// TODO: Assign (first) branch of the user as register branch of the data
}
function customerFormUndo(close = true) {
if (isFormDataDifferent()) {
return customerConfirmUnsave(close);
}
resetForm();
state.value.readonly = true;
}
function customerConfirmUnsave(close = true) {
dialog({
color: 'warning',
icon: 'mdi-alert',
title: t('form.warning.title'),
actionText: t('general.ok'),
persistent: true,
message: t('form.warning.unsave'),
action: () => {
resetForm();
state.value.readonly = true;
if (!state.value.drawerModal) {
state.value.dialogModal = !close;
} else {
state.value.drawerModal = !close;
}
},
cancel: () => {},
});
}
function deleteCustomerById(
id: string,
fetch?: (...args: unknown[]) => unknown,
) {
dialog({
color: 'negative',
icon: 'mdi-alert',
title: t('dialog.title.confirmDelete'),
actionText: t('general.delete'),
persistent: true,
message: t('dialog.message.confirmDelete'),
action: async () => {
await customerStore.deleteById(id);
await fetch();
state.value.dialogModal = false;
flowStore.rotate();
},
cancel: () => {},
});
}
function validateTabField<T = CustomerBranchCreate>(
value: T,
fieldRequired: { [key: string]: (keyof T)[] },
) {
const list: string[] = [];
for (const tab in fieldRequired) {
for (const field of fieldRequired[tab]) {
if (!value[field] && !list.includes(tab)) list.push(tab);
}
}
return list;
}
async function deleteCustomerBranchById(id: string) {
return await new Promise((resolve) => {
dialog({
color: 'negative',
icon: 'mdi-alert',
title: t('dialog.title.confirmDelete'),
actionText: t('general.delete'),
persistent: true,
message: t('dialog.message.confirmDelete'),
action: async () => {
await customerStore.deleteBranchById(id);
flowStore.rotate();
resolve(true);
},
cancel: () => {
resolve(false);
},
});
});
}
return {
onCreateImageList,
tabFieldRequired,
registerAbleBranchOption,
state,
resetFormData,
currentFormData,
currentBranchRootId,
isFormDataDifferent,
resetForm,
assignFormData,
submitFormCustomer,
addCurrentCustomerBranch,
deleteAttachment,
fetchListOfOptionBranch,
customerFormUndo,
customerConfirmUnsave,
deleteCustomerById,
validateTabField,
deleteCustomerBranchById,
};
});
export const useCustomerBranchForm = defineStore('form-customer-branch', () => {
const customerStore = useCustomerStore();
const customerFormStore = useCustomerForm();
const defaultFormData: CustomerBranchCreate & {
id?: string;
codeCustomer?: string;
@ -549,7 +397,7 @@ export const useCustomerBranchForm = defineStore('form-customer-branch', () => {
gender: '',
birthDate: undefined,
businessTypeId: '',
businessType: '',
jobPosition: '',
jobDescription: '',
payDate: '',
@ -579,6 +427,7 @@ export const useCustomerBranchForm = defineStore('form-customer-branch', () => {
agentUserId: undefined,
status: 'CREATED',
customerName: '',
registerName: '',
registerNameEN: '',
registerDate: undefined,
@ -630,7 +479,7 @@ export const useCustomerBranchForm = defineStore('form-customer-branch', () => {
payDateEN: _data.payDateEN,
jobDescription: _data.jobDescription,
jobPosition: _data.jobPosition,
businessTypeId: _data.businessTypeId,
businessType: _data.businessType,
employmentOffice: _data.employmentOffice,
employmentOfficeEN: _data.employmentOfficeEN,
telephoneNo: _data.telephoneNo,
@ -664,6 +513,7 @@ export const useCustomerBranchForm = defineStore('form-customer-branch', () => {
officeTel: _data.officeTel,
agentUserId: _data.agentUserId || undefined,
codeCustomer: _data.codeCustomer,
customerName: _data.customerName,
homeCode: _data.homeCode,
authorizedName: _data.authorizedName,
authorizedNameEN: _data.authorizedNameEN,
@ -731,23 +581,11 @@ export const useCustomerBranchForm = defineStore('form-customer-branch', () => {
});
export const useEmployeeForm = defineStore('form-employee', () => {
const { t } = useI18n();
const customerStore = useCustomerStore();
const employeeStore = useEmployeeStore();
const flowStore = useFlowStore();
const branchStore = useMyBranch();
const route = useRoute();
const refreshImageState = ref(false);
const onCreateImageList = ref<{
selectedImage: string;
list: { url: string; imgFile: File | null; name: string }[];
}>({ selectedImage: '', list: [] });
const statusEmployeeCreate = ref<boolean>(false);
const state = ref<{
dialogType: 'info' | 'create' | 'edit';
imageDialog: boolean;
@ -756,8 +594,6 @@ export const useEmployeeForm = defineStore('form-employee', () => {
drawerModal: boolean;
isImageEdit: boolean;
currentBranchId: string;
currentCustomerBranch?: CustomerBranch;
currentEmployeeCode: string;
currentEmployee: Employee | null;
currentIndexPassport: number;
@ -790,9 +626,7 @@ export const useEmployeeForm = defineStore('form-employee', () => {
}
| undefined;
ocr: boolean;
imageList: { list: string[]; selectedImage: string };
}>({
currentBranchId: '',
isImageEdit: false,
currentIndexPassport: -1,
currentIndexVisa: -1,
@ -815,10 +649,9 @@ export const useEmployeeForm = defineStore('form-employee', () => {
infoEmployeePersonCard: [],
formDataEmployeeOwner: undefined,
ocr: false,
imageList: { list: [], selectedImage: '' },
});
const defaultFormData: EmployeeCreate & { image?: File } = {
const defaultFormData: EmployeeCreate = {
id: '',
code: '',
customerBranchId: '',
@ -900,7 +733,6 @@ export const useEmployeeForm = defineStore('form-employee', () => {
expireDate: new Date(),
remark: undefined,
workerType: '',
reportDate: null,
number: '',
},
],
@ -946,7 +778,7 @@ export const useEmployeeForm = defineStore('form-employee', () => {
};
let resetEmployeeData = structuredClone(defaultFormData);
const currentFromDataEmployee = ref<EmployeeCreate & { image?: File }>(
const currentFromDataEmployee = ref<EmployeeCreate>(
structuredClone(defaultFormData),
);
@ -968,14 +800,12 @@ export const useEmployeeForm = defineStore('form-employee', () => {
state.value.currentIndexVisa = -1;
state.value.currentIndexCheckup = -1;
state.value.currentIndexWorkHistory = -1;
state.value.imageList = { list: [], selectedImage: '' };
// state.value.currentTab = 'personalInfo';
state.value.currentTab = 'personalInfo';
if (clean) {
state.value.formDataEmployeeOwner = undefined;
resetEmployeeData = structuredClone(defaultFormData);
state.value.statusSavePersonal = false;
state.value.profileUrl = '';
state.value.currentBranchId = '';
} else {
resetEmployeeData.selectedImage =
currentFromDataEmployee.value.selectedImage;
@ -996,16 +826,12 @@ export const useEmployeeForm = defineStore('form-employee', () => {
state.value.currentIndexPassport
].id === undefined
) {
const { id, employeeId, updatedAt, createdAt, file, ...payload } =
currentFromDataEmployee.value.employeePassport?.[
state.value.currentIndexPassport
];
const res = await employeeStore.postMeta({
parentId: currentFromDataEmployee.value.id || '',
group: 'passport',
meta: payload,
file: file,
meta: currentFromDataEmployee.value.employeePassport?.[
state.value.currentIndexPassport
],
});
if (res) {
@ -1019,7 +845,7 @@ export const useEmployeeForm = defineStore('form-employee', () => {
state.value.currentIndexPassport
].id !== undefined
) {
const { id, employeeId, updatedAt, createdAt, file, ...payload } =
const { id, employeeId, updatedAt, createdAt, ...payload } =
currentFromDataEmployee.value.employeePassport?.[
state.value.currentIndexPassport
];
@ -1032,7 +858,6 @@ export const useEmployeeForm = defineStore('form-employee', () => {
state.value.currentIndexPassport
].id || '',
meta: payload,
file: file || undefined,
});
}
@ -1260,12 +1085,10 @@ export const useEmployeeForm = defineStore('form-employee', () => {
await assignFormDataEmployee(currentFromDataEmployee.value.id);
}
async function submitPersonal(imgList?: {
async function submitPersonal(imgList: {
selectedImage: string;
list: { url: string; imgFile: File | null; name: string }[];
}) {
let employeeId: string | undefined = undefined;
currentFromDataEmployee.value.firstName =
currentFromDataEmployee.value.firstName.trim();
currentFromDataEmployee.value.middleName =
@ -1281,11 +1104,10 @@ export const useEmployeeForm = defineStore('form-employee', () => {
currentFromDataEmployee.value.lastNameEN.trim();
if (state.value.dialogType === 'create') {
delete currentFromDataEmployee.value.image;
const res = await employeeStore.create(
{
...currentFromDataEmployee.value,
customerBranchId: state.value.currentBranchId || '',
customerBranchId: state.value.formDataEmployeeOwner?.id || '',
employeeWork: [],
employeeCheckup: [],
@ -1295,7 +1117,6 @@ export const useEmployeeForm = defineStore('form-employee', () => {
);
if (res) {
employeeId = res.id;
await assignFormDataEmployee(res.id);
currentFromDataEmployee.value.id = res.id;
state.value.statusSavePersonal = true;
@ -1310,19 +1131,17 @@ export const useEmployeeForm = defineStore('form-employee', () => {
state.value.currentEmployee?.status === 'CREATED'
? 'ACTIVE'
: state.value.currentEmployee?.status,
customerBranchId: state.value.currentBranchId || '',
customerBranchId: state.value.formDataEmployeeOwner?.id || '',
employeeWork: [],
employeeCheckup: [],
employeeOtherInfo: undefined,
},
);
if (res) {
employeeId = res.id;
await assignFormDataEmployee(res.id);
state.value.statusSavePersonal = true;
}
}
return employeeId;
}
async function assignFormDataEmployee(id?: string) {
@ -1364,19 +1183,7 @@ export const useEmployeeForm = defineStore('form-employee', () => {
employeePassport: structuredClone(
payload.employeePassport?.length === 0
? state.value.dialogModal
? defaultFormData.employeePassport.map((v) => ({
...v,
namePrefix: payload.namePrefix,
firstName: payload.firstName,
firstNameEN: payload.firstNameEN,
middleName: payload.middleName,
middleNameEN: payload.middleNameEN,
lastName: payload.lastName,
lastNameEN: payload.lastNameEN,
gender: payload.gender,
nationality: payload.nationality,
birthDate: payload.dateOfBirth,
}))
? defaultFormData.employeePassport
: []
: payload.employeePassport,
),
@ -1398,10 +1205,12 @@ export const useEmployeeForm = defineStore('form-employee', () => {
statusSave: true,
})),
),
employeeOtherInfo: structuredClone({
...(payload.employeeOtherInfo ?? {}),
statusSave: true,
}),
employeeOtherInfo: structuredClone(
{
...payload.employeeOtherInfo,
statusSave: !!payload.employeeOtherInfo?.id ? true : false,
} || {},
),
employeeWork: structuredClone(
payload.employeeWork?.length === 0
? state.value.dialogModal
@ -1461,8 +1270,6 @@ export const useEmployeeForm = defineStore('form-employee', () => {
state.value.currentIndexVisa = -1;
}
state.value.currentBranchId = payload.customerBranchId;
const foundBranch = await customerStore.fetchListCustomerBranchById(
payload.customerBranchId,
);
@ -1518,17 +1325,17 @@ export const useEmployeeForm = defineStore('form-employee', () => {
issueDate: new Date(),
type: '',
expireDate: new Date(),
birthDate: currentFromDataEmployee.value.dateOfBirth,
birthDate: new Date(),
workerStatus: '',
nationality: currentFromDataEmployee.value.nationality,
gender: currentFromDataEmployee.value.gender,
lastNameEN: currentFromDataEmployee.value.lastNameEN,
lastName: currentFromDataEmployee.value.lastName,
middleNameEN: currentFromDataEmployee.value.middleNameEN,
middleName: currentFromDataEmployee.value.middleName,
firstNameEN: currentFromDataEmployee.value.firstNameEN,
firstName: currentFromDataEmployee.value.firstName,
namePrefix: currentFromDataEmployee.value.namePrefix,
nationality: '',
gender: '',
lastNameEN: '',
lastName: '',
middleNameEN: '',
middleName: '',
firstNameEN: '',
firstName: '',
namePrefix: '',
number: '',
});
@ -1550,7 +1357,6 @@ export const useEmployeeForm = defineStore('form-employee', () => {
expireDate: new Date(),
remark: undefined,
workerType: '',
reportDate: null,
number: '',
});
@ -1590,92 +1396,10 @@ export const useEmployeeForm = defineStore('form-employee', () => {
(currentFromDataEmployee.value.employeeWork?.length || 0) - 1;
}
function employeeFormUndo(close = true) {
if (isFormDataDifferent()) {
return employeeConfirmUnsave(close);
}
resetFormDataEmployee();
state.value.editReadonly = true;
}
function employeeConfirmUnsave(close = true) {
dialog({
color: 'warning',
icon: 'mdi-alert',
title: t('form.warning.title'),
actionText: t('general.ok'),
persistent: true,
message: t('form.warning.unsave'),
action: () => {
resetFormDataEmployee();
onCreateImageList.value = { selectedImage: '', list: [] };
state.value.editReadonly = true;
state.value.dialogModal = !close;
state.value.drawerModal = !close;
},
cancel: () => {},
});
}
async function deleteEmployeeById(opts: {
id?: string;
type?: 'passport' | 'visa' | 'healthCheck' | 'work';
index?: number;
fetch?: (...args: unknown[]) => unknown;
removeArray?: (...args: unknown[]) => unknown;
}) {
dialog({
color: 'negative',
icon: 'mdi-alert',
title: t('dialog.title.confirmDelete'),
actionText: t('general.delete'),
persistent: true,
message: t('dialog.message.confirmDelete'),
action: async () => {
if (opts.type === 'passport' && opts.index !== undefined) {
await deletePassport(opts.index);
}
if (opts.type === 'visa' && opts.index !== undefined) {
await deleteVisa(opts.index);
}
if (opts.type === 'healthCheck' && opts.index !== undefined) {
await deleteHealthCheck(opts.index);
}
if (opts.type === 'work' && opts.index !== undefined) {
await deleteWorkHistory(opts.index);
} else {
if (!!opts.id) {
const result = await employeeStore.deleteById(opts.id);
if (result) {
state.value.drawerModal = false;
state.value.dialogModal = false;
}
}
}
if (route.name !== 'CustomerBranchManagement') {
await opts.fetch?.();
flowStore.rotate();
}
opts.removeArray?.();
},
cancel: () => {},
});
}
return {
refreshImageState,
statusEmployeeCreate,
onCreateImageList,
state,
currentFromDataEmployee,
resetEmployeeData,
addPassport,
addVisa,
addCheckup,
@ -1699,9 +1423,5 @@ export const useEmployeeForm = defineStore('form-employee', () => {
employeeFilterOwnerBranch,
isFormDataDifferent,
employeeFormUndo,
employeeConfirmUnsave,
deleteEmployeeById,
};
});

View file

@ -43,7 +43,6 @@ withDefaults(
defineProps<{
readonly?: boolean;
isEdit?: boolean;
hideAction?: boolean;
}>(),
{ readonly: false, isEdit: false },
);
@ -61,7 +60,6 @@ async function addStep() {
flowData.value.step.push({
responsibleInstitution: [],
responsiblePersonId: [],
responsibleGroup: [],
value: [],
detail: '',
name: '',
@ -168,7 +166,6 @@ function triggerPropertiesDialog(step: WorkFlowPayloadStep) {
id="flow-form-dialog"
>
<FormFlow
v-model:user-in-table="userInTable"
v-model:flow-data="flowData"
v-model:register-branch-id="registerBranchId"
@trigger-properties="triggerPropertiesDialog"
@ -208,7 +205,7 @@ function triggerPropertiesDialog(step: WorkFlowPayloadStep) {
style="position: absolute; z-index: 999; top: 0; right: 0"
>
<div
v-if="flowData.status !== 'INACTIVE' && !hideAction"
v-if="flowData.status !== 'INACTIVE'"
class="surface-1 row rounded"
>
<UndoButton
@ -288,7 +285,6 @@ function triggerPropertiesDialog(step: WorkFlowPayloadStep) {
>
<template v-slot:btn-form-flow-step-drawer>
<q-btn
v-if="!hideAction"
dense
flat
icon="mdi-plus"
@ -317,7 +313,6 @@ function triggerPropertiesDialog(step: WorkFlowPayloadStep) {
<FormFlow
:readonly
onDrawer
:hide-action="hideAction"
v-model:user-in-table="userInTable"
v-model:flow-data="flowData"
v-model:register-branch-id="registerBranchId"

View file

@ -1,6 +1,6 @@
<script lang="ts" setup>
import { onMounted, reactive, ref, watch } from 'vue';
import { QTableProps } from 'quasar';
import { QSelect, QTableProps } from 'quasar';
import { storeToRefs } from 'pinia';
import { useI18n } from 'vue-i18n';
@ -11,7 +11,7 @@ import {
} from 'src/stores/workflow-template/types';
import { useWorkflowTemplate } from 'src/stores/workflow-template';
import { useNavigator } from 'src/stores/navigator';
import { dialog, canAccess } from 'src/stores/utils';
import { dialog } from 'src/stores/utils';
import FloatingActionButton from 'components/FloatingActionButton.vue';
import StatCardComponent from 'src/components/StatCardComponent.vue';
@ -22,7 +22,6 @@ import NoData from 'src/components/NoData.vue';
import KebabAction from 'src/components/shared/KebabAction.vue';
import PaginationPageSize from 'src/components/PaginationPageSize.vue';
import { useQuasar } from 'quasar';
import AdvanceSearch from 'src/components/shared/AdvanceSearch.vue';
const { t } = useI18n();
const workflowStore = useWorkflowTemplate();
@ -46,7 +45,6 @@ const pageState = reactive({
addModal: false,
viewDrawer: false,
isDrawerEdit: true,
searchDate: [],
});
const fieldSelected = ref<('order' | 'name' | 'step')[]>([
@ -70,6 +68,7 @@ const fieldSelectedOption = ref<{ label: string; value: string }[]>([
},
]);
const refFilter = ref<InstanceType<typeof QSelect>>();
const currWorkflowData = ref<WorkflowTemplate>();
const formDataWorkflow = ref<WorkflowTemplatePayload>({
status: 'CREATED',
@ -103,7 +102,6 @@ const columns = [
function triggerDialog(type: 'add' | 'edit' | 'view') {
if (type === 'add') {
registeredBranchId.value = '';
userInTable.value = [];
formDataWorkflow.value = {
status: 'CREATED',
name: '',
@ -208,7 +206,7 @@ async function submit() {
...formDataWorkflow.value,
});
} else {
await workflowStore.createWorkflowTemplate({
await workflowStore.creatWorkflowTemplate({
registeredBranchId: registeredBranchId.value,
...formDataWorkflow.value,
});
@ -224,11 +222,7 @@ function assignFormData(workflowData: WorkflowTemplate) {
status: workflowData.status,
name: workflowData.name,
step: workflowData.step.map((s, i) => {
userInTable.value[i] = {
name: s.name,
responsiblePerson: [],
responsibleGroup: [],
};
userInTable.value[i] = { name: s.name, responsiblePerson: [] };
s.responsiblePerson.forEach((p) => {
userInTable.value[i].responsiblePerson.push({
id: p.user.id,
@ -242,16 +236,12 @@ function assignFormData(workflowData: WorkflowTemplate) {
code: p.user.code,
});
});
s.responsibleGroup.forEach((g) => {
userInTable.value[i].responsibleGroup.push(g);
});
return {
id: s.id,
name: s.name,
detail: s.detail,
messengerByArea: s.messengerByArea || false,
value: s.value.length > 0 ? JSON.parse(JSON.stringify(s.value)) : [],
responsibleGroup: s.responsibleGroup.map((g) => g),
responsiblePersonId: s.responsiblePerson.map((p) => p.userId),
responsibleInstitution: JSON.parse(
JSON.stringify(s.responsibleInstitution),
@ -292,8 +282,6 @@ async function fetchWorkflowList(mobileFetch?: boolean) {
: statusFilter.value === 'statusACTIVE'
? 'ACTIVE'
: 'INACTIVE',
startDate: pageState.searchDate[0],
endDate: pageState.searchDate[1],
});
if (res) {
workflowData.value =
@ -323,18 +311,14 @@ watch(
fetchWorkflowList();
},
);
watch(
[() => pageState.inputSearch, workflowPageSize, () => pageState.searchDate],
() => {
workflowData.value = [];
workflowPage.value = 1;
fetchWorkflowList();
},
);
watch([() => pageState.inputSearch, workflowPageSize], () => {
workflowData.value = [];
workflowPage.value = 1;
fetchWorkflowList();
});
</script>
<template>
<FloatingActionButton
v-if="canAccess('workflow', 'edit')"
style="z-index: 999"
hide-icon
@click="triggerDialog('add')"
@ -408,44 +392,26 @@ watch(
<template #prepend>
<q-icon name="mdi-magnify" />
</template>
<template v-slot:append>
<q-separator vertical inset class="q-mr-xs" />
<AdvanceSearch
v-model="pageState.searchDate"
:active="$q.screen.lt.md && statusFilter !== 'all'"
>
<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"
v-model="statusFilter"
outlined
dense
option-value="value"
option-label="label"
map-options
emit-value
:for="'field-select-status'"
:options="[
{ label: $t('general.all'), value: 'all' },
{ label: $t('general.active'), value: 'statusACTIVE' },
{
label: $t('general.inactive'),
value: 'statusINACTIVE',
},
]"
<template v-if="$q.screen.lt.md" v-slot:append>
<span class="row">
<q-separator vertical />
<q-btn
icon="mdi-filter-variant"
unelevated
class="q-ml-sm"
padding="4px"
size="sm"
rounded
@click="refFilter?.showPopup"
/>
</AdvanceSearch>
</span>
</template>
</q-input>
<div class="row col-md-5" style="white-space: nowrap">
<q-select
v-if="$q.screen.gt.sm"
v-show="$q.screen.gt.sm"
ref="refFilter"
v-model="statusFilter"
outlined
dense
@ -543,12 +509,12 @@ watch(
class="col surface-2 flex items-center justify-center"
>
<NoData
v-if="pageState.total !== 0 || pageState.searchDate.length > 0"
v-if="pageState.total !== 0"
:not-found="!!pageState.inputSearch"
/>
<CreateButton
v-if="pageState.total === 0 && pageState.searchDate.length === 0"
v-if="pageState.total === 0"
@click="triggerDialog('add')"
label="general.add"
:i18n-args="{ text: $t('flow.title') }"
@ -683,7 +649,6 @@ watch(
"
/>
<KebabAction
v-if="canAccess('workflow', 'edit')"
:id-name="props.row.name"
:status="props.row.status"
@view="
@ -765,8 +730,7 @@ watch(
"
/>
<KebabAction
v-if="canAccess('workflow', 'edit')"
:id-name="props.row.name"
:id-name="props.row.id"
:status="props.row.status"
@view="
() => {
@ -849,7 +813,6 @@ watch(
@drawer-undo="undo"
@close="resetForm"
@submit="submit"
:hide-action="!canAccess('workflow', 'edit')"
:readonly="!pageState.isDrawerEdit"
:isEdit="pageState.isDrawerEdit"
v-model="pageState.addModal"

Some files were not shown because too many files have changed in this diff Show more