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": { "dependencies": {
"@peaceroad/markdown-it-figure-with-p-caption": "^0.11.0", "@peaceroad/markdown-it-figure-with-p-caption": "^0.11.0",
"@quasar/extras": "^1.16.17", "@quasar/extras": "^1.16.17",
"@tato30/vue-pdf": "^1.11.3", "@tato30/vue-pdf": "^1.11.0",
"@vuepic/vue-datepicker": "^8.8.1", "@vuepic/vue-datepicker": "^8.8.1",
"apexcharts": "^4.5.0", "apexcharts": "^4.5.0",
"axios": "^1.8.4", "axios": "^1.7.4",
"cropperjs": "^1.6.2", "cropperjs": "^1.6.2",
"dayjs": "^1.11.13",
"highlight.js": "^11.11.1", "highlight.js": "^11.11.1",
"keycloak-js": "^25.0.6", "keycloak-js": "^25.0.4",
"markdown-it": "^14.1.0", "markdown-it": "^14.1.0",
"markdown-it-anchor": "^9.2.0", "markdown-it-anchor": "^9.2.0",
"markdown-it-highlightjs": "^4.2.0", "markdown-it-highlightjs": "^4.2.0",
@ -32,41 +31,42 @@
"markdown-it-html5-media": "^0.7.1", "markdown-it-html5-media": "^0.7.1",
"markdown-it-image-figures": "^2.1.1", "markdown-it-image-figures": "^2.1.1",
"markdown-it-video": "^0.6.3", "markdown-it-video": "^0.6.3",
"mime": "^4.0.6", "mime": "^4.0.4",
"moment": "^2.30.1", "moment": "^2.30.1",
"number-to-words": "^1.2.4", "number-to-words": "^1.2.4",
"open-props": "^1.7.14", "open-props": "^1.7.5",
"pinia": "^2.3.1", "pinia": "^2.2.2",
"quasar": "^2.18.1", "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", "tesseract.js": "^5.1.1",
"thai-baht-text": "^2.0.5", "thai-baht-text": "^2.0.5",
"udsv": "^0.6.0", "udsv": "^0.6.0",
"uuid": "^10.0.0", "uuid": "^10.0.0",
"vue": "^3.5.13", "vue": "^3.4.38",
"vue-dragscroll": "^4.0.6", "vue-dragscroll": "^4.0.6",
"vue-i18n": "^11.1.2", "vue-i18n": "^11.1.2",
"vue-pdf": "^4.3.0", "vue-pdf": "^4.3.0",
"vue-router": "^4.5.0", "vue-router": "^4.4.3",
"vue-tsc": "^2.2.8", "vue-tsc": "^2.2.8",
"vue3-apexcharts": "^1.8.0" "vue3-apexcharts": "^1.8.0"
}, },
"devDependencies": { "devDependencies": {
"@faker-js/faker": "^9.6.0", "@faker-js/faker": "^9.3.0",
"@iconify/vue": "^4.3.0", "@iconify/vue": "^4.1.2",
"@intlify/unplugin-vue-i18n": "^6.0.5", "@intlify/unplugin-vue-i18n": "^6.0.5",
"@playwright/test": "^1.51.1", "@playwright/test": "^1.46.1",
"@quasar/app-vite": "^2.2.0", "@quasar/app-vite": "^2.2.0",
"@types/markdown-it": "^14.1.2", "@types/markdown-it": "^14.1.2",
"@types/markdown-it-highlightjs": "^3.3.4", "@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/number-to-words": "^1.2.3",
"@types/uuid": "^10.0.0", "@types/uuid": "^10.0.0",
"autoprefixer": "^10.4.21", "autoprefixer": "^10.4.20",
"dotenv": "^16.4.7", "dotenv": "^16.4.7",
"prettier": "^3.5.3", "prettier": "^3.3.3",
"typescript": "^5.5.4", "typescript": "^5.5.4",
"vue-component-type-helpers": "^2.2.8" "vue-component-type-helpers": "^2.1.10"
}, },
"engines": { "engines": {
"node": "^28 || ^26 || ^24 || ^22 || ^20 || ^18", "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": { "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": [ "workerStatus": [
{ {
"label": "Normal", "label": "Normal",
@ -177,21 +154,20 @@
{ "label": "VS2", "value": "VS2" }, { "label": "VS2", "value": "VS2" },
{ "label": "WO", "value": "WO" }, { "label": "WO", "value": "WO" },
{ "label": "WP390", "value": "WP390" }, { "label": "WP390", "value": "WP390" },
{ "label": "WP44", "value": "WP44" }, { "label": "WP44", "value": "WP44" }
{ "label": "CUST", "value": "CUST" }
], ],
"prefix": [ "prefix": [
{ {
"label": "MR", "label": "Mr",
"value": "mr" "value": "mr"
}, },
{ {
"label": "MRS", "label": "Mrs",
"value": "mrs" "value": "mrs"
}, },
{ {
"label": "MISS", "label": "Miss",
"value": "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" "value": "trainingTak"
}, },
{ {
"label": "Koh Song, Ranong province", "label": "Myanmar Labor Training Center - Kawthoung, Ranong Province",
"value": "trainingRanong" "value": "trainingRanong"
}, },
{ {
"label": "Nong Khai, Nong Khai Province", "label": "Laos Labor Training Center - Nong Khai, Nong Khai Province",
"value": "trainingNongKhai" "value": "trainingNongKhai"
}, },
{ {
"label": "Aranyaprathet, Sa Kaeo Province", "label": "Cambodian Labor Training Center - Aranyaprathet, Sa Kaeo Province",
"value": "trainingSaKaeo" "value": "trainingSaKaeo"
}, },
{ {
"label": "Ban Laem, Chanthaburi Province", "label": "Cambodian Labor Training Center - Ban Laem, Chanthaburi Province",
"value": "trainingChanthaburi" "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": [ "nationality": [
{ {
"label": "Thai", "label": "Thai",
@ -1089,29 +1050,6 @@
}, },
"tha": { "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": [ "workerStatus": [
{ {
"label": "ปกติ", "label": "ปกติ",
@ -1266,8 +1204,7 @@
{ "label": "VS2", "value": "VS2" }, { "label": "VS2", "value": "VS2" },
{ "label": "WO", "value": "WO" }, { "label": "WO", "value": "WO" },
{ "label": "WP390", "value": "WP390" }, { "label": "WP390", "value": "WP390" },
{ "label": "WP44", "value": "WP44" }, { "label": "WP44", "value": "WP44" }
{ "label": "CUST", "value": "CUST" }
], ],
"prefix": [ "prefix": [
@ -1296,44 +1233,29 @@
} }
], ],
"border": [ "training": [
{ {
"label": "แม่สอด จ.ตาก", "label": "สถานที่อบรมแรงงานเมียนมา-แม่สอด จ.ตาก",
"value": "trainingTak" "value": "trainingTak"
}, },
{ {
"label": "เกาะสอง จ.ระนอง", "label": "สถานที่อบรมแรงงานเมียนมา-เกาะสอง จ.ระนอง",
"value": "trainingRanong" "value": "trainingRanong"
}, },
{ {
"label": "หนองคาย จ.หนองคาย", "label": "สถานที่อบรมแรงงานลาว-หนองคาย จ.หนองคาย",
"value": "trainingNongKhai" "value": "trainingNongKhai"
}, },
{ {
"label": "อรัญประเทศ จ.สระแก้ว", "label": "สถานที่อบรมแรงงานกัมพูชา-อรัญประเทศ จ.สระแก้ว",
"value": "trainingSaKaeo" "value": "trainingSaKaeo"
}, },
{ {
"label": "บ้านแหลม จ.จันทบุรี", "label": "สถานที่อบรมแรงงานกัมพูชา-บ้านแหลม จ.จันทบุรี",
"value": "trainingChanthaburi" "value": "trainingChanthaburi"
} }
], ],
"training": [
{
"label": "ศูนย์แรกรับเข้าทำงาน และสิ้นสุดการจ้าง จังหวัดตาก",
"value": "trainingTak"
},
{
"label": "ศูนย์แรกรับเข้าทำงาน และสิ้นสุดการจ้าง จังหวัดหนองคาย",
"value": "trainingNongKhai"
},
{
"label": "ศูนย์แรกรับเข้าทำงาน และสิ้นสุดการจ้าง จังหวัดสระแก้ว",
"value": "trainingSaKaeo"
}
],
"nationality": [ "nationality": [
{ {
"label": "ไทย", "label": "ไทย",

View file

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

View file

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

View file

@ -89,7 +89,15 @@ defineProps<{
</div> </div>
</div> </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> <slot name="data"></slot>
<template v-if="!$slots.data"> <template v-if="!$slots.data">
<div <div

View file

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

View file

@ -159,6 +159,42 @@ function formatCode(input: string | undefined, type: 'code' | 'number') {
]" ]"
for="input-name-en" 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>
<div class="col-12 row q-col-gutter-sm"> <div class="col-12 row q-col-gutter-sm">

View file

@ -1,13 +1,13 @@
<script setup lang="ts"> <script setup lang="ts">
import useUserStore from 'stores/user'; import useUserStore from 'stores/user';
import useOptionStore from 'stores/options'; import useOptionStore from 'stores/options';
import { UserAttachmentDelete, AgencyStatus } from 'stores/user/types'; import { UserAttachmentDelete } from 'stores/user/types';
import { dialog } from 'stores/utils'; import { dialog, selectFilterOptionRefMod } from 'stores/utils';
import { onMounted, ref } from 'vue'; import { onMounted, ref, watch } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { Icon } from '@iconify/vue'; import { Icon } from '@iconify/vue';
import { QSelect } from 'quasar';
import DatePicker from '../shared/DatePicker.vue'; import DatePicker from '../shared/DatePicker.vue';
import SelectInput from 'src/components/shared/SelectInput.vue';
import SelectOffice from 'components/shared/select-muliple/SelectOffice.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>( const sourceNationality = defineModel<string | null | undefined>(
'sourceNationality', 'sourceNationality',
); );
const importNationality = defineModel<string[] | null | undefined>( const importNationality = defineModel<string | null | undefined>(
'importNationality', 'importNationality',
); );
const trainingPlace = defineModel<string | null | undefined>('trainingPlace'); const trainingPlace = defineModel<string | null | undefined>('trainingPlace');
const checkpoint = defineModel<string | null | undefined>('checkpoint'); const checkpoint = defineModel<string | null | undefined>('checkPoint');
const userFile = defineModel<File[]>('userFile'); const checkpointEN = defineModel<string | null | undefined>('checkPointEn');
const userFileList = const agencyFile = defineModel<File[]>('agencyFile');
defineModel<{ name: string; url: string }[]>('userFileList'); const agencyFileList =
const remark = defineModel<string | null | undefined>('remark'); defineModel<{ name: string; url: string }[]>('agencyFileList');
const agencyStatus = defineModel<string | null | undefined>('agencyStatus');
const attachmentRef = ref(); const attachmentRef = ref();
@ -70,12 +69,66 @@ function deleteFile(name: string) {
userStore.deleteAttachment(userId.value, payload); userStore.deleteAttachment(userId.value, payload);
const result = await userStore.fetchAttachment(userId.value); const result = await userStore.fetchAttachment(userId.value);
if (result) { if (result) {
userFileList.value = result; agencyFileList.value = result;
} }
}, },
cancel: () => {}, 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> </script>
<template> <template>
<div class="row col-12"> <div class="row col-12">
@ -133,12 +186,11 @@ function deleteFile(name: string) {
/> />
<SelectOffice <SelectOffice
v-if="userType === 'MESSENGER'"
for="input-responsible-area" for="input-responsible-area"
v-model:value="responsibleArea" v-model:value="responsibleArea"
v-if="userType === 'MESSENGER'"
:readonly="readonly" :readonly="readonly"
:label="$t('personnel.form.responsibleArea')" :label="$t('personnel.form.responsibleArea')"
class="col"
/> />
</div> </div>
<div <div
@ -166,103 +218,138 @@ function deleteFile(name: string) {
class="row col-12 q-col-gutter-sm" class="row col-12 q-col-gutter-sm"
style="margin-left: 0px; padding-left: 0px" style="margin-left: 0px; padding-left: 0px"
> >
<SelectInput <q-select
:model-value="readonly ? sourceNationality || '-' : sourceNationality" 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" id="input-source-nationality"
for="input-source-nationality" for="input-source-nationality"
:option="optionStore.globalOption.nationality" :dense="dense"
class="col-md-3 col-6" :readonly="readonly"
:readonly :hide-dropdown-icon="readonly"
clearable
:label="$t('personnel.form.sourceNationality')" :label="$t('personnel.form.sourceNationality')"
:options="nationalityOptions"
@filter="nationalityFilter"
:model-value="readonly ? sourceNationality || '-' : sourceNationality"
@update:model-value=" @update:model-value="
(v) => (typeof v === 'string' ? (sourceNationality = v) : '') (v) => (typeof v === 'string' ? (sourceNationality = v) : '')
" "
/> @clear="sourceNationality = ''"
>
<SelectInput <template v-slot:no-option>
v-model="importNationality" <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" id="input-import-nationality"
for="input-import-nationality" for="input-import-nationality"
:option="optionStore.globalOption.nationality" :dense="dense"
class="col-md-3 col-6" :readonly="readonly"
:readonly :hide-dropdown-icon="readonly"
multiple
:hideSelected="false"
clearable
fillInput
:label="$t('personnel.form.importNationality')" :label="$t('personnel.form.importNationality')"
/> :options="nationalityOptions"
@filter="nationalityFilter"
<SelectInput :model-value="readonly ? importNationality || '-' : importNationality"
: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
@update:model-value=" @update:model-value="
(v) => (typeof v === 'string' ? (checkpoint = v) : '') (v) => (typeof v === 'string' ? (importNationality = v) : '')
" "
/> @clear="importNationality = ''"
>
<SelectInput <template v-slot:no-option>
:model-value="readonly ? trainingPlace || '-' : trainingPlace" <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" id="select-trainig-place"
for="select-trainig-place" for="select-trainig-place"
:option="optionStore.globalOption.training" :dense="dense"
class="col-md-8 col-12" :readonly="readonly"
:readonly :hide-dropdown-icon="readonly"
:label="$t('personnel.form.trainingPlace')" :label="$t('personnel.form.trainingPlace')"
clearable :options="trainingPlaceOptions"
@filter="trainingPlaceFilter"
:model-value="readonly ? trainingPlace || '-' : trainingPlace"
@update:model-value=" @update:model-value="
(v) => (typeof v === 'string' ? (trainingPlace = v) : '') (v) => (typeof v === 'string' ? (trainingPlace = v) : '')
" "
/> @clear="trainingPlace = ''"
>
<SelectInput <template v-slot:no-option>
:model-value="readonly ? agencyStatus || '-' : agencyStatus" <q-item>
id="select-checkpoint-en" <q-item-section class="text-grey">
for="select-checkpoint-en" {{ $t('general.noData') }}
:option="[ </q-item-section>
{ label: $t('personnel.form.normal'), value: AgencyStatus.Normal }, </q-item>
{ </template>
label: $t('personnel.form.canceled'), </q-select>
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) : '')
"
/>
<q-input <q-input
for="input-discount-condition" for="input-checkpoint"
:dense="dense" :dense="dense"
outlined outlined
:readonly :readonly="readonly"
:label="$t('general.remark')" :label="$t('personnel.form.checkpoint')"
class="col-12" class="col-6"
type="textarea" :model-value="readonly ? checkpoint || '-' : checkpoint"
:model-value="readonly ? remark || '-' : remark"
@update:model-value=" @update:model-value="
(v) => (typeof v === 'string' ? (remark = v) : '') (v) => (typeof v === 'string' ? (checkpoint = v) : '')
" "
@clear="remark = ''" @clear="checkpoint = ''"
/>
<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 = ''"
/> />
</div>
<q-file <q-file
v-if="userType && !readonly"
ref="attachmentRef" ref="attachmentRef"
for="input-attachment" for="input-attachment"
:dense="dense" :dense="dense"
@ -271,8 +358,8 @@ function deleteFile(name: string) {
multiple multiple
append append
:label="$t('personnel.form.attachment')" :label="$t('personnel.form.attachment')"
class="col" class="col-12"
v-model="userFile" v-model="agencyFile"
> >
<template v-slot:prepend> <template v-slot:prepend>
<Icon <Icon
@ -299,12 +386,12 @@ function deleteFile(name: string) {
</template> </template>
</q-file> </q-file>
<div v-if="userFileList && userFileList?.length > 0" class="col-12"> <div v-if="agencyFileList && agencyFileList?.length > 0" class="col-12">
<q-list bordered separator class="rounded" style="padding: 0"> <q-list bordered separator class="rounded" style="padding: 0">
<q-item <q-item
id="attachment-file" id="attachment-file"
for="attachment-file" for="attachment-file"
v-for="item in userFileList" v-for="item in agencyFileList"
clickable clickable
:key="item.url" :key="item.url"
class="items-center row" class="items-center row"
@ -334,4 +421,5 @@ function deleteFile(name: string) {
</div> </div>
</div> </div>
</div> </div>
</div>
</template> </template>

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -3,7 +3,6 @@ import { QSelect } from 'quasar';
import { CustomerBranch } from 'stores/customer/types'; import { CustomerBranch } from 'stores/customer/types';
import { selectFilterOptionRefMod } from 'stores/utils'; import { selectFilterOptionRefMod } from 'stores/utils';
import { onMounted, ref, watch } from 'vue'; import { onMounted, ref, watch } from 'vue';
import SelectCustomer from 'components/shared/select/SelectCustomer.vue';
import { import {
EditButton, EditButton,
DeleteButton, DeleteButton,
@ -12,7 +11,6 @@ import {
} from 'components/button'; } from 'components/button';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import useOptionStore from 'stores/options'; import useOptionStore from 'stores/options';
import { formatAddress } from 'src/utils/address';
const { locale } = useI18n(); const { locale } = useI18n();
@ -23,13 +21,6 @@ const optionsBranch = defineModel<{ id: string; name: string }[]>(
); );
// employee // employee
const customerBranchId = defineModel<string>('customerBranchId');
const currentCustomerBranch = defineModel<CustomerBranch>(
'currentCustomerBranch',
);
const customerBranch = defineModel<{ const customerBranch = defineModel<{
id: string; id: string;
address: string; address: string;
@ -54,7 +45,6 @@ defineProps<{
employeeOwnerOption?: CustomerBranch[]; employeeOwnerOption?: CustomerBranch[];
prefixId: string; prefixId: string;
showBtnSave?: boolean; showBtnSave?: boolean;
disableCustomerSelect?: boolean;
}>(); }>();
defineEmits<{ defineEmits<{
@ -117,18 +107,180 @@ defineEmits<{
</div> </div>
<div class="col-12 row" style="gap: var(--size-2)"> <div class="col-12 row" style="gap: var(--size-2)">
<SelectCustomer <q-select
id="form-select-customer-branch-id" :id="`${prefixId}-select-employer-branch`"
for="form-select-customer-branch-id" :for="`${prefixId}-select-employer-branch`"
v-model:value="customerBranchId" :use-input="!customerBranch"
v-model:value-option="currentCustomerBranch" 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')" :label="$t('customer.form.branchCode')"
class="col-12 field-two" v-model="customerBranch"
simple :option-value="
required (v) => ({
:readonly id: v.id,
:disabled="disableCustomerSelect && !readonly" 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 <q-input
:for="`${prefixId}-input-code`" :for="`${prefixId}-input-code`"

View file

@ -6,14 +6,12 @@ import { useI18n } from 'vue-i18n';
import useUserStore from 'src/stores/user'; import useUserStore from 'src/stores/user';
import useOptionStore from 'src/stores/options'; import useOptionStore from 'src/stores/options';
import { useWorkflowTemplate } from 'src/stores/workflow-template';
import { baseUrl } from 'stores/utils'; import { baseUrl } from 'stores/utils';
import { getRole } from 'src/services/keycloak'; import { getRole } from 'src/services/keycloak';
import { import {
WorkflowUserInTable, WorkflowUserInTable,
WorkflowTemplatePayload, WorkflowTemplatePayload,
WorkFlowPayloadStep, WorkFlowPayloadStep,
Group,
} from 'src/stores/workflow-template/types'; } from 'src/stores/workflow-template/types';
import { User } from 'src/stores/user/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 NoData from '../NoData.vue';
import SelectBranch from '../shared/select/SelectBranch.vue'; import SelectBranch from '../shared/select/SelectBranch.vue';
import AddButton from '../button/AddButton.vue'; import AddButton from '../button/AddButton.vue';
import { QField } from 'quasar';
defineProps<{ defineProps<{
readonly?: boolean; readonly?: boolean;
onDrawer?: boolean; onDrawer?: boolean;
hideAction?: boolean;
}>(); }>();
const { t } = useI18n(); const { t } = useI18n();
const userStore = useUserStore(); const userStore = useUserStore();
const optionStore = useOptionStore(); const optionStore = useOptionStore();
const workflowStore = useWorkflowTemplate();
const userInTable = defineModel<WorkflowUserInTable[]>('userInTable', { const userInTable = defineModel<WorkflowUserInTable[]>('userInTable', {
default: [], default: [],
@ -48,7 +43,7 @@ const flowData = defineModel<WorkflowTemplatePayload>('flowData', {
}, },
}); });
let objectOptions = [ const objectOptions = [
...(optionStore.globalOption?.agenciesType || []), ...(optionStore.globalOption?.agenciesType || []),
{ label: t('flow.customer'), value: 'customer' }, { label: t('flow.customer'), value: 'customer' },
{ label: t('flow.officer'), value: 'officer' }, { label: t('flow.officer'), value: 'officer' },
@ -56,9 +51,7 @@ let objectOptions = [
const options = ref(objectOptions); const options = ref(objectOptions);
const role = ref<string[]>([]); const role = ref<string[]>([]);
const userList = ref<User[]>([]); const userList = ref<User[]>([]);
const groupList = ref<Group[]>([]);
const responsiblePersonSearch = ref(''); const responsiblePersonSearch = ref('');
const responsibleMenu = ref(false);
async function getUserList(opts?: { query: string }) { async function getUserList(opts?: { query: string }) {
const resUser = await userStore.fetchList({ const resUser = await userStore.fetchList({
@ -67,10 +60,10 @@ async function getUserList(opts?: { query: string }) {
if (resUser) userList.value = resUser.result; if (resUser) userList.value = resUser.result;
} }
async function getGroupList() { // async function getUserById(responsiblePersonId: string) {
const resGroup = await workflowStore.getGroupList(); // const resUser = await userStore.fetchById(responsiblePersonId);
if (resGroup) groupList.value = resGroup; // if (resUser) userInTable.value.push(resUser);
} // }
function selectResponsiblePerson(stepIndex: number, responsiblePerson: User) { function selectResponsiblePerson(stepIndex: number, responsiblePerson: User) {
const currStep = flowData.value.step[stepIndex]; const currStep = flowData.value.step[stepIndex];
@ -85,7 +78,6 @@ function selectResponsiblePerson(stepIndex: number, responsiblePerson: User) {
userInTable.value[stepIndex] = { userInTable.value[stepIndex] = {
name: flowData.value.step[stepIndex].name, name: flowData.value.step[stepIndex].name,
responsiblePerson: [], 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( function selectItem(
val: Record<string, unknown>, val: Record<string, unknown>,
responsibleInstitution?: string[], responsibleInstitution?: string[],
@ -177,7 +142,6 @@ watch(
onMounted(async () => { onMounted(async () => {
role.value = getRole() || []; role.value = getRole() || [];
await getUserList(); await getUserList();
await getGroupList();
await userStore.fetchHqOption(); await userStore.fetchHqOption();
}); });
</script> </script>
@ -202,7 +166,6 @@ onMounted(async () => {
:class="{ 'q-ml-lg': $q.screen.gt.xs, 'q-mt-sm': $q.screen.lt.sm }" :class="{ 'q-ml-lg': $q.screen.gt.xs, 'q-mt-sm': $q.screen.lt.sm }"
> >
<ToggleButton <ToggleButton
:disable="hideAction"
class="q-mr-sm" class="q-mr-sm"
two-way two-way
:model-value="flowData.status !== 'INACTIVE'" :model-value="flowData.status !== 'INACTIVE'"
@ -504,29 +467,33 @@ onMounted(async () => {
</div> </div>
<!-- RESPONSIBLE-PERSON --> <!-- RESPONSIBLE-PERSON -->
<q-field <q-select
v-if="step.responsiblePersonId" v-if="step.responsiblePersonId"
behavior="menu"
:for="`select-responsible-person-${index}-${onDrawer ? 'drawer' : 'dialog'}`" :for="`select-responsible-person-${index}-${onDrawer ? 'drawer' : 'dialog'}`"
:bg-color="readonly ? 'transparent' : ''" :bg-color="readonly ? 'transparent' : ''"
:readonly :readonly
outlined outlined
:stack-label=" dense
userInTable[index]?.responsiblePerson.length > 0 || v-model="step.responsiblePersonId"
userInTable[index]?.responsibleGroup.length > 0 multiple
" :options="[1, 2, 3]"
hide-bottom-space
option-label="label"
option-value="value"
emit-value
:label="$t('flow.responsiblePerson')" :label="$t('flow.responsiblePerson')"
dense
class="col-md-6 col-12" class="col-md-6 col-12"
:class="{ 'cursor-pointer': !readonly }" :hide-dropdown-icon="readonly"
> >
<template #control> <template v-slot:selected-item="scope">
<q-item <div class="column full-width">
dense <div
class="items-center full-width no-padding" class="row items-center no-wrap"
v-for="person in userInTable[ v-for="person in userInTable[
index index
]?.responsiblePerson.filter((p) => ]?.responsiblePerson.filter(
step.responsiblePersonId.includes(p.id), (p) => p.id === scope.opt,
)" )"
:key="person.id" :key="person.id"
> >
@ -580,52 +547,12 @@ onMounted(async () => {
{{ person.code }} {{ person.code }}
</span> </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> </div>
<q-item </div>
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>
<template v-if="!readonly" #append>
<q-icon <template v-slot:option></template>
name="mdi-menu-down" <q-menu v-if="!readonly" :offset="[0, 4]">
: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)"
>
<q-list> <q-list>
<q-item> <q-item>
<q-input <q-input
@ -654,7 +581,6 @@ onMounted(async () => {
{{ $t('general.noData') }} {{ $t('general.noData') }}
</q-item> </q-item>
<q-item <q-item
v-else
v-for="(person, i) in userList" v-for="(person, i) in userList"
dense dense
:key="i" :key="i"
@ -729,7 +655,6 @@ onMounted(async () => {
{{ $t('personnel.MESSENGER') }} {{ $t('personnel.MESSENGER') }}
</span> </span>
<q-item <q-item
dense
clickable clickable
@click="step.messengerByArea = !step.messengerByArea" @click="step.messengerByArea = !step.messengerByArea"
class="column" class="column"
@ -745,49 +670,9 @@ onMounted(async () => {
</div> </div>
</div> </div>
</q-item> </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-list>
</q-menu> </q-menu>
</q-field> </q-select>
<!-- RESPONSIBLE-AGENCIES, RESPONSIBLE-INSTITUTION --> <!-- RESPONSIBLE-AGENCIES, RESPONSIBLE-INSTITUTION -->
<q-select <q-select
@ -903,7 +788,7 @@ onMounted(async () => {
:deep( :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; justify-content: start !important;
padding-right: 8px !important; padding-right: 8px !important;
padding-top: 16px; padding-top: 16px;
@ -916,25 +801,18 @@ onMounted(async () => {
:deep( :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); color: var(--brand-1);
} }
:deep( :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-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-focus-helper
) { ) {
visibility: hidden; visibility: hidden;
} }
:deep(.q-dialog.fullscreen.no-pointer-events.q-dialog--modal) { :deep(.q-dialog.fullscreen.no-pointer-events.q-dialog--modal) {
visibility: hidden; visibility: hidden;
} }
.transition-rotate {
transition: transform 0.3s ease;
}
.rotated {
transform: rotate(180deg);
}
</style> </style>

View file

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

View file

@ -98,8 +98,7 @@ watch(
(c, o) => { (c, o) => {
const list = c.map((v: { name: string }) => v.name); const list = c.map((v: { name: string }) => v.name);
const oldList = o.map((v: { name: string }) => v.name); const oldList = o.map((v: { name: string }) => v.name);
const index = workName.value ? oldList.indexOf(workName.value) : -1; const index = oldList.indexOf(workName.value || '');
if (index === -1) return;
if ( if (
list[index] !== oldList[index] && list[index] !== oldList[index] &&
@ -706,7 +705,7 @@ watch(
:deep( :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; justify-content: start !important;
padding-right: 8px !important; padding-right: 8px !important;
padding-top: 16px; padding-top: 16px;
@ -737,7 +736,7 @@ watch(
:deep( :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); color: var(--brand-1);
} }

View file

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

View file

@ -48,7 +48,6 @@ defineProps<{
readonly?: boolean; readonly?: boolean;
onDrawer?: boolean; onDrawer?: boolean;
inputOnly?: boolean; inputOnly?: boolean;
disableToggle?: boolean;
}>(); }>();
defineEmits<{ defineEmits<{
@ -77,7 +76,6 @@ defineEmits<{
<ToggleButton <ToggleButton
class="q-mr-sm" class="q-mr-sm"
two-way two-way
:disable="disableToggle"
:model-value="status !== 'INACTIVE'" :model-value="status !== 'INACTIVE'"
@click=" @click="
() => { () => {
@ -198,7 +196,7 @@ defineEmits<{
:deep( :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; justify-content: start !important;
padding-right: 8px !important; padding-right: 8px !important;
padding-top: 16px; padding-top: 16px;
@ -211,14 +209,14 @@ defineEmits<{
:deep( :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); color: var(--brand-1);
} }
:deep( :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-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-focus-helper
) { ) {
visibility: hidden; visibility: hidden;
} }

View file

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

View file

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

View file

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

View file

@ -1,10 +1,6 @@
<script lang="ts" setup> <script lang="ts" setup>
import { ref } from 'vue';
import { useI18n } from 'vue-i18n';
import SelectInput from '../shared/SelectInput.vue'; import SelectInput from '../shared/SelectInput.vue';
import useOptionStore from 'src/stores/options'; import useOptionStore from 'src/stores/options';
import { Icon } from '@iconify/vue/dist/iconify.js';
import { useInstitution } from 'src/stores/institution';
const optionStore = useOptionStore(); const optionStore = useOptionStore();
@ -14,31 +10,12 @@ defineProps<{
readonly?: boolean; readonly?: boolean;
onDrawer?: boolean; onDrawer?: boolean;
}>(); }>();
const emit = defineEmits<{
(e: 'deleteAttachment', name: string): void;
}>();
const attachmentRef = ref();
const group = defineModel('group', { default: '' }); const group = defineModel('group', { default: '' });
const name = defineModel('name', { default: '' }); const name = defineModel('name', { default: '' });
const nameEn = defineModel('nameEn', { 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 }; type Options = { label: string; value: string };
function openNewTab(url: string) {
window.open(url, '_blank');
}
function deleteAttachment(name: string) {
emit('deleteAttachment', name);
}
</script> </script>
<template> <template>
<div class="row col-12"> <div class="row col-12">
@ -58,7 +35,7 @@ function deleteAttachment(name: string) {
<div class="col-12 row q-col-gutter-sm"> <div class="col-12 row q-col-gutter-sm">
<SelectInput <SelectInput
class="col" :class="{ col: $q.screen.lt.md }"
:disable="!readonly && onDrawer" :disable="!readonly && onDrawer"
:readonly="readonly" :readonly="readonly"
for="input-agencies-code" for="input-agencies-code"
@ -85,15 +62,10 @@ function deleteAttachment(name: string) {
outlined outlined
:readonly="readonly" :readonly="readonly"
hide-bottom-space hide-bottom-space
class="col-md-4 col-12" class="col-md col-12"
:label="$t('agencies.name')" :label="$t('agencies.name')"
v-model="name" v-model="name"
:rules="[ :rules="[(val: string) => !!val || $t('form.error.required')]"
(val) => !!val || $t('form.error.required'),
(val) =>
/^[A-Za-z0-9ก-๙\s&.,'-]+$/.test(val) ||
$t('form.error.branchNameField'),
]"
/> />
<q-input <q-input
for="input-agencies-name-en" for="input-agencies-name-en"
@ -101,159 +73,10 @@ function deleteAttachment(name: string) {
outlined outlined
:readonly="readonly" :readonly="readonly"
hide-bottom-space hide-bottom-space
class="col-md-4 col-12" class="col-md col-12"
:label="'Agencies Name'" :label="'Agencies Name'"
v-model="nameEn" 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>
</div> </div>
</template> </template>

View file

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

View file

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

View file

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

View file

@ -42,15 +42,6 @@ defineProps<{
const modal = defineModel('modal', { default: false }); const modal = defineModel('modal', { default: false });
const currentTab = defineModel<string>('currentTab'); 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> </script>
<template> <template>
<q-dialog <q-dialog
@ -69,7 +60,6 @@ async function onValidationError(ref: any) {
@submit.prevent @submit.prevent
@validation-success="submit" @validation-success="submit"
class="column full-height" class="column full-height"
@validation-error="onValidationError"
> >
<!-- header --> <!-- header -->
<div <div

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -5,7 +5,6 @@ defineEmits<{
(e: 'click', v: MouseEvent): void; (e: 'click', v: MouseEvent): void;
}>(); }>();
defineProps<{ defineProps<{
id?: string;
icon?: string; icon?: string;
color: string; color: string;
iconOnly?: boolean; iconOnly?: boolean;
@ -19,7 +18,6 @@ defineProps<{
<template> <template>
<button <button
:id="id"
@click="(e) => $emit('click', e)" @click="(e) => $emit('click', e)"
class="main-btn" class="main-btn"
:class="{ :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; outlined?: boolean;
disabled?: boolean; disabled?: boolean;
dark?: boolean; dark?: boolean;
color?: string;
label?: string; label?: string;
icon?: string; icon?: string;
@ -24,7 +23,7 @@ defineProps<{
@click="(e) => $emit('click', e)" @click="(e) => $emit('click', e)"
v-bind="{ ...$props, ...$attrs }" v-bind="{ ...$props, ...$attrs }"
:icon="icon || 'mdi-content-save-outline'" :icon="icon || 'mdi-content-save-outline'"
:color="color || '207 96% 32%'" color="207 96% 32%"
:title="iconOnly ? $t('general.save') : undefined" :title="iconOnly ? $t('general.save') : undefined"
> >
{{ label || $t('general.save') }} {{ 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 StateButton } from './StateButton.vue';
export { default as NextButton } from './NextButton.vue'; export { default as NextButton } from './NextButton.vue';
export { default as ImportButton } from './ImportButton.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 }); const state = defineModel({ default: false });
</script> </script>
@ -50,7 +41,6 @@ const state = defineModel({ default: false });
}" }"
> >
<q-form <q-form
@validation-error="onValidationError"
@submit.prevent="(e) => $emit('submit', e)" @submit.prevent="(e) => $emit('submit', e)"
@reset="$emit('reset')" @reset="$emit('reset')"
greedy greedy

View file

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

View file

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

View file

@ -16,7 +16,6 @@ const props = withDefaults(
useUpload?: boolean; useUpload?: boolean;
useCancel?: boolean; useCancel?: boolean;
useRejectCancel?: boolean; useRejectCancel?: boolean;
useCopy?: boolean;
disableCancel?: boolean; disableCancel?: boolean;
disableDelete?: boolean; disableDelete?: boolean;
}>(), }>(),
@ -32,7 +31,6 @@ defineEmits<{
(e: 'link'): void; (e: 'link'): void;
(e: 'upload'): void; (e: 'upload'): void;
(e: 'delete'): void; (e: 'delete'): void;
(e: 'copy'): void;
(e: 'cancel'): void; (e: 'cancel'): void;
(e: 'rejectCancel'): void; (e: 'rejectCancel'): void;
(e: 'changeStatus'): void; (e: 'changeStatus'): void;
@ -174,27 +172,6 @@ watch(
</span> </span>
</q-item> </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 <q-item
v-if="useCancel" v-if="useCancel"
v-close-popup v-close-popup

View file

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

View file

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

View file

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

View file

@ -75,9 +75,9 @@ function setDefaultValue() {
</script> </script>
<template> <template>
<SelectInput <SelectInput
for="select-hq-id"
v-model="value" v-model="value"
incremental incremental
id="select-hq-id"
:label :label
:placeholder :placeholder
:readonly :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 = { type ExclusiveProps = {
simple?: boolean; simple?: boolean;
simpleBranchNo?: boolean; simpleBranchNo?: boolean;
selectFirstValue?: boolean;
}; };
const props = defineProps<SelectProps<typeof getList> & ExclusiveProps>(); const props = defineProps<SelectProps<typeof getList> & ExclusiveProps>();
@ -65,14 +64,8 @@ onMounted(async () => {
setFirstValue(); setFirstValue();
} }
if (props.selectFirstValue) { await getSelectedOption();
setDefaultValue();
} else await getSelectedOption();
}); });
function setDefaultValue() {
setFirstValue();
}
</script> </script>
<template> <template>
<SelectInput <SelectInput
@ -165,9 +158,11 @@ function setDefaultValue() {
</template> </template>
<template #option="{ opt, scope }"> <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 /> <SelectCustomerItem :data="opt" :simple :simple-branch-no />
</q-item> </q-item>
<q-separator class="q-mx-sm" />
</template> </template>
<template #append v-if="clearable"> <template #append v-if="clearable">
@ -180,11 +175,3 @@ function setDefaultValue() {
</template> </template>
</SelectInput> </SelectInput>
</template> </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({ const ret = await getList({
query: query === '' ? undefined : query, query: query === '' ? undefined : query,
...props.params, ...props.params,
activeOnly: true,
}); });
if (ret) return ret.result; if (ret) return ret.result;
}, },

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -4,7 +4,7 @@ export default {
save: 'บันทึก', save: 'บันทึก',
open: 'เปิด', open: 'เปิด',
close: 'ปิด', close: 'ปิด',
edit: 'แก้ไข{text}', edit: 'แก้ไข',
cancel: 'ยกเลิก', cancel: 'ยกเลิก',
back: 'ย้อนกลับ', back: 'ย้อนกลับ',
undo: 'ย้อนกลับ', undo: 'ย้อนกลับ',
@ -31,7 +31,6 @@ export default {
displayField: 'ฟิลด์แสดงผล', displayField: 'ฟิลด์แสดงผล',
order: 'ลำดับ', order: 'ลำดับ',
name: 'ชื่อ{msg}', name: 'ชื่อ{msg}',
nameEN: 'ชื่อ (ภาษาอังกฤษ)',
fullName: 'ชื่อ-สกุล', fullName: 'ชื่อ-สกุล',
detail: 'รายละเอียด{msg}', detail: 'รายละเอียด{msg}',
remark: 'หมายเหตุ{msg}', remark: 'หมายเหตุ{msg}',
@ -154,14 +153,6 @@ export default {
tableOfContent: 'สารบัญ', tableOfContent: 'สารบัญ',
draw: 'วาด', draw: 'วาด',
newUpload: 'อัปโหลดใหม่', newUpload: 'อัปโหลดใหม่',
nativeLanguage: '{msg} ภาษาต้นทาง',
copy: 'คัดลอก',
paste: 'วาง',
period: 'ช่วงเวลา',
documentStatus: 'สถานะเอกสาร',
advanceSearch: 'ค้นหาขั้นสูง',
totalPeople: '{meg} คน',
price: 'ราคา {price} บาท',
}, },
menu: { menu: {
@ -204,14 +195,12 @@ export default {
title: 'จัดการ', title: 'จัดการ',
branch: 'สาขา', branch: 'สาขา',
personnel: 'บุคลากร', personnel: 'บุคลากร',
group: 'กลุ่ม',
productService: 'สินค้าและบริการ', productService: 'สินค้าและบริการ',
workflow: 'ขั้นตอนการทำงาน', workflow: 'ขั้นตอนการทำงาน',
property: 'คุณสมบัติ', property: 'คุณสมบัติ',
customer: 'ลูกค้า', customer: 'ลูกค้า',
mainData: 'ข้อมูลหลัก', mainData: 'ข้อมูลหลัก',
agencies: 'หน่วยงาน', agencies: 'หน่วยงาน',
businessType: 'ประเภทกิจการ',
}, },
sales: { sales: {
@ -259,7 +248,6 @@ export default {
manual: { manual: {
title: 'คู่มือ', title: 'คู่มือ',
usage: 'การใช้งาน', usage: 'การใช้งาน',
troubleshooting: 'การแก้ปัญหา',
}, },
}, },
@ -338,7 +326,7 @@ export default {
letterAndNumOnly: 'โปรดใช้เฉพาะ _ ตัวอักษรภาษาอังกฤษและตัวเลขเท่านั้น', letterAndNumOnly: 'โปรดใช้เฉพาะ _ ตัวอักษรภาษาอังกฤษและตัวเลขเท่านั้น',
numOnly: 'โปรดใช้เฉพาะตัวเลขเท่านั้น', numOnly: 'โปรดใช้เฉพาะตัวเลขเท่านั้น',
requireLength: 'กรุณากรอกให้ครบ {msg} หลัก', requireLength: 'กรุณากรอกให้ครบ {msg} หลัก',
branchNameField: "โปรดใช้ตัวอักษร ตัวเลข หรือ . , - ' ( ) & เท่านั้น", branchNameField: "โปรดใช้ตัวอักษร ตัวเลข หรือ . , - ' & เท่านั้น",
branchNameENField: branchNameENField:
"โปรดใช้ตัวอักษรภาษาอังกฤษ ตัวเลข หรือ . , - ' & เท่านั้น", "โปรดใช้ตัวอักษรภาษาอังกฤษ ตัวเลข หรือ . , - ' & เท่านั้น",
passportFormat: 'กรุณากรอกหมายเลขพาสปอร์ตให้ถูกต้องตามรูปแบบ', passportFormat: 'กรุณากรอกหมายเลขพาสปอร์ตให้ถูกต้องตามรูปแบบ',
@ -467,13 +455,6 @@ export default {
citizenId: 'เลขที่บัตรประชาชน', citizenId: 'เลขที่บัตรประชาชน',
citizenIssue: 'วันที่ออกบัตร', citizenIssue: 'วันที่ออกบัตร',
citizenExpire: 'วันที่หมดอายุ', citizenExpire: 'วันที่หมดอายุ',
agencyStatus: 'สถานะเอเจนซี่',
normal: 'ปกติ',
canceled: 'ยกเลิก',
blacklist: 'แบล็คลิสต์',
contactName: 'ชื่อผู้ติดต่อ',
contactTel: 'เบอร์โทรศัพท์ผู้ติดต่อ',
addressForeign: 'ใช้ที่อยู่ต่างประเทศ',
}, },
}, },
customer: { customer: {
@ -500,16 +481,15 @@ export default {
}, },
prefix: { prefix: {
mr: 'นาย', mr: 'Mr.',
mrs: 'นาง', mrs: 'Mrs.',
miss: 'นางสาว', miss: 'Miss.',
}, },
citizenId: 'บัตรประจำตัวประชาชน', citizenId: 'บัตรประจำตัวประชาชน',
religion: 'ศาสนา', religion: 'ศาสนา',
issueDate: 'วันที่ออกหนังสือ', issueDate: 'วันที่ออกหนังสือ',
passportExpiryDate: 'วันหiมดอายุหนังสือเดินทาง', passportExpiryDate: 'วันหiมดอายุหนังสือเดินทาง',
taxpayyerNo: 'เลขที่ประจำตัวผู้เสียภาษี',
ownerName: 'ชื่อนายจ้าง', ownerName: 'ชื่อนายจ้าง',
firstName: 'ชื่อ ', firstName: 'ชื่อ ',
@ -593,7 +573,7 @@ export default {
family: 'ข้อมูลครอบครัว', family: 'ข้อมูลครอบครัว',
}, },
workerStatus: 'สถานะคนงาน', workerStatus: 'สถานะคนงาน',
previousPassportNumber: 'หมายเลขหนังสือเดินทางเล่มเก่า', previousPassportNumber: 'หมายเลขอันเก่าหนังสือเดินทาง',
employerBranch: 'สาขานายจ้าง', employerBranch: 'สาขานายจ้าง',
employeeCode: 'รหัสลูกจ้าง', employeeCode: 'รหัสลูกจ้าง',
nrcNo: 'เลขบัตรประจำตัวคนซึ่งไม่มีสัญชาติไทย (N.R.C No.)', nrcNo: 'เลขบัตรประจำตัวคนซึ่งไม่มีสัญชาติไทย (N.R.C No.)',
@ -626,7 +606,7 @@ export default {
placeOfBirth: 'สถานที่เกิด', placeOfBirth: 'สถานที่เกิด',
countryOfbirth: 'ประเทศที่เกิด', countryOfbirth: 'ประเทศที่เกิด',
issueCountry: 'ประเทศที่ออก', issueCountry: 'ประเทศที่ออก',
entryCount: 'จำนวนวันที่เข้าประเทศ', entryCount: 'จำนวนที่เข้าประเทศ',
employerSelect: { employerSelect: {
branchName: 'ชื่อสาขา', branchName: 'ชื่อสาขา',
customerName: 'ชื่อนายจ้าง', customerName: 'ชื่อนายจ้าง',
@ -654,7 +634,7 @@ export default {
permitIssuedAt: 'สถานที่ออกใบอนุญาต', permitIssuedAt: 'สถานที่ออกใบอนุญาต',
permitIssueDate: 'วันที่ออกใบอนุญาตทำงาน', permitIssueDate: 'วันที่ออกใบอนุญาตทำงาน',
permitExpireDate: 'วันที่หมดอายุใบอนุญาตทำงาน', permitExpireDate: 'วันที่หมดอายุใบอนุญาตทำงาน',
identityNo: 'เลขประจำตัวคนต่างด้าว (13 หลัก)', identityNo: 'เลขประจำตัวคนต่างด้าว (13หลัก)',
}, },
formFamily: { formFamily: {
citizenId: citizenId:
@ -772,13 +752,10 @@ export default {
}, },
quotation: { quotation: {
ownOnly: 'เห็นเฉพาะใบเสนอราคาของตัวเอง',
quotationDate: 'วันที่ใบเสนอราคา', quotationDate: 'วันที่ใบเสนอราคา',
seller: 'ผู้ขาย', seller: 'ผู้ขาย',
paymentChannels: 'ช่องทางชำระเงิน', paymentChannels: 'ช่องทางชำระเงิน',
channelsThat: 'ช่องทางที่', channelsThat: 'ช่องทางที่',
refNo: 'เลขที่อ้างอิง',
bankAccount: 'บัญชีธนาคาร',
bankAccountNumber: 'เลขบัญชีธนาคาร', bankAccountNumber: 'เลขบัญชีธนาคาร',
bankAccountName: 'ชื่อบัญชี', bankAccountName: 'ชื่อบัญชี',
inTheNameOf: 'ในนาม', inTheNameOf: 'ในนาม',
@ -926,10 +903,6 @@ export default {
code: 'รหัสหน่วยงาน', code: 'รหัสหน่วยงาน',
group: 'กลุ่มหน่วยงาน', group: 'กลุ่มหน่วยงาน',
name: 'ชื่อหน่วยงาน', name: 'ชื่อหน่วยงาน',
contactName: 'ชื่อผู้ติดต่อ',
contactTel: 'เบอร์โทรผู้ติดต่อ',
bankInfo: 'ข้อมูลธนาคาร',
attachment: 'เอกสารเพิ่มเติม',
}, },
requestList: { requestList: {
@ -951,7 +924,6 @@ export default {
nonLocalEmployee: 'พนักงานนอกพื้นที่', nonLocalEmployee: 'พนักงานนอกพื้นที่',
noWorkflowTemplate: 'คุณไม่ได้เลือกแม่แบบขั้นตอนการทำงาน', noWorkflowTemplate: 'คุณไม่ได้เลือกแม่แบบขั้นตอนการทำงาน',
salesRepresentative: 'พนักงานขาย', salesRepresentative: 'พนักงานขาย',
dataOffice: 'สำนักงานเขตจัดหางาน',
ref: 'อ้างอิง', ref: 'อ้างอิง',
action: { action: {
title: 'จัดการ', title: 'จัดการ',
@ -1070,9 +1042,6 @@ export default {
confirmDebitNoteAccept: 'ยืนยันการตอบรับใบเพิ่มหนี้', confirmDebitNoteAccept: 'ยืนยันการตอบรับใบเพิ่มหนี้',
}, },
message: { message: {
copy: 'คัดลอก',
warningPaste: 'คุณต้องการที่จะเเทนที่ข้อมูลที่คัดลอกมาใหม่ใช่หรือไม่',
warningCopyEmpty: 'คุณยังไม่ได้คัดลอกข้อมูล',
quotationAccept: 'เมื่อตอบรับเเล้วจะไม่สามารถแก้ไขได้อีก', quotationAccept: 'เมื่อตอบรับเเล้วจะไม่สามารถแก้ไขได้อีก',
beingUse: '"{msg}" มีการใช้งานอยู่', beingUse: '"{msg}" มีการใช้งานอยู่',
incompleteDataEntry: 'กรอกข้อมูลไม่ครบในหน้า {tap}', incompleteDataEntry: 'กรอกข้อมูลไม่ครบในหน้า {tap}',
@ -1194,7 +1163,6 @@ export default {
'สินค้าที่มีชื่อเดียวกันมีในระบบแล้ว หากคุณต้องการสร้างด้วยชื่อนี้โปรดเลือกรหัสอื่น', 'สินค้าที่มีชื่อเดียวกันมีในระบบแล้ว หากคุณต้องการสร้างด้วยชื่อนี้โปรดเลือกรหัสอื่น',
userExists: 'ชื่อผู้ใช้นี้มีอยู่ในระบบอยู่แล้ว', userExists: 'ชื่อผู้ใช้นี้มีอยู่ในระบบอยู่แล้ว',
sameNameExists: 'ชื่อนี้ถูกใช้ไปแล้ว', sameNameExists: 'ชื่อนี้ถูกใช้ไปแล้ว',
samePropertyNameExists: 'คุณสมบัตินี้มีอยู่ในระบบอยู่แล้ว',
validateError: 'เกิดข้อผิดพลาดจากการตรวจสอบ', validateError: 'เกิดข้อผิดพลาดจากการตรวจสอบ',
codeMisMatch: 'รหัสไม่ตรงกัน', codeMisMatch: 'รหัสไม่ตรงกัน',
@ -1220,8 +1188,6 @@ export default {
'มีงานหนึ่งงานหรือมากกว่าที่ไม่อยู่ในสถานะรอดำเนินการ', 'มีงานหนึ่งงานหรือมากกว่าที่ไม่อยู่ในสถานะรอดำเนินการ',
reqNotMet: 'ไม่ตรงกัน', reqNotMet: 'ไม่ตรงกัน',
systemError: 'ระบบเกิดข้อผิดพลาด', systemError: 'ระบบเกิดข้อผิดพลาด',
taskOrderInvalid: 'โปรดเลือก สินค้าเเละสาขา',
flowAccountProductIdNotFound: 'ไม่พบสินค้าใน flow account',
}, },
}, },
@ -1493,26 +1459,4 @@ export default {
type: 'ประเภท', 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 { getUserId, getRole } from 'src/services/keycloak';
import { useQuasar } from 'quasar'; import { useQuasar } from 'quasar';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { canAccess } from 'src/stores/utils'; import { isRoleInclude } from 'src/stores/utils';
type Menu = { type Menu = {
label: string; label: string;
@ -71,50 +71,82 @@ function initMenu() {
{ {
label: 'branch', label: 'branch',
route: '/branch-management', route: '/branch-management',
hidden: !canAccess('branch'), hidden: !isRoleInclude([
'system',
'head_of_admin',
'admin',
'branch_manager',
'head_of_accountant',
]),
}, },
{ {
label: 'personnel', label: 'personnel',
route: '/personnel-management', route: '/personnel-management',
hidden: !canAccess('personnel'), hidden: !isRoleInclude([
}, 'owner',
{ 'system',
label: 'group', 'head_of_admin',
route: '/group-management', 'admin',
hidden: !canAccess('personnel'), 'branch_manager',
]),
}, },
{ {
label: 'workflow', label: 'workflow',
route: '/workflow', route: '/workflow',
hidden: !canAccess('workflow'), hidden: !isRoleInclude(['system', 'head_of_admin', 'admin']),
}, },
{ {
label: 'property', label: 'property',
route: '/property', route: '/property',
hidden: !canAccess('workflow'), hidden: !isRoleInclude(['system', 'head_of_admin', 'admin']),
},
{
label: 'businessType',
route: '/business-type',
}, },
{ {
label: 'productService', label: 'productService',
route: '/product-service', route: '/product-service',
hidden: !isRoleInclude([
'system',
'head_of_admin',
'admin',
'branch_manager',
'head_of_accountant',
'head_of_sale',
'sale',
]),
}, },
{ {
label: 'customer', label: 'customer',
route: '/customer-management', 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', label: 'agencies',
route: '/agencies-management', route: '/agencies-management',
hidden: !isRoleInclude(['system', 'head_of_admin', 'admin']),
}, },
], ],
}, },
{ {
label: 'menu.sales', label: 'menu.sales',
icon: 'mdi-store-settings-outline', icon: 'mdi-store-settings-outline',
hidden: !isRoleInclude([
'system',
'head_of_admin',
'admin',
'branch_manager',
'head_of_accountant',
'accountant',
'head_of_sale',
'sale',
]),
children: [ children: [
{ label: 'quotation', route: '/quotation' }, { label: 'quotation', route: '/quotation' },
{ label: 'invoice', route: '/invoice' }, { label: 'invoice', route: '/invoice' },
@ -137,6 +169,16 @@ function initMenu() {
label: 'menu.account', label: 'menu.account',
icon: 'mdi-bank-outline', icon: 'mdi-bank-outline',
disabled: false, disabled: false,
hidden: !isRoleInclude([
'system',
'head_of_admin',
'admin',
'branch_manager',
'head_of_accountant',
'accountant',
'head_of_sale',
'sale',
]),
children: [ children: [
{ label: 'receipt', route: '/receipt' }, { label: 'receipt', route: '/receipt' },
{ label: 'creditNote', route: '/credit-note' }, { label: 'creditNote', route: '/credit-note' },
@ -158,13 +200,10 @@ function initMenu() {
{ {
label: 'menu.overall', label: 'menu.overall',
icon: 'mdi-monitor-dashboard', icon: 'mdi-monitor-dashboard',
hidden: !isRoleInclude(['system', 'head_of_admin', 'admin', 'executive']),
children: [ children: [
{ label: 'report', route: '/report' }, { label: 'report', route: '/report' },
{ { label: 'dashboard', route: '/dash-board' },
label: 'dashboard',
route: '/dash-board',
hidden: !canAccess('dashBoard'),
},
], ],
}, },
@ -176,10 +215,6 @@ function initMenu() {
label: 'usage', label: 'usage',
route: '/manual', route: '/manual',
}, },
{
label: 'troubleshooting',
route: '/troubleshooting',
},
], ],
}, },
]; ];

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -6,11 +6,11 @@ import { Icon } from '@iconify/vue';
import { BranchContact } from 'stores/branch-contact/types'; import { BranchContact } from 'stores/branch-contact/types';
import { useQuasar } from 'quasar'; import { useQuasar } from 'quasar';
import { useI18n } from 'vue-i18n'; 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 { resetScrollBar } from 'src/stores/utils';
import useBranchStore from 'stores/branch'; import useBranchStore from 'stores/branch';
import useFlowStore from 'stores/flow'; import useFlowStore from 'stores/flow';
import { isRoleInclude, canAccess } from 'stores/utils'; import { isRoleInclude } from 'stores/utils';
import { import {
BranchWithChildren, BranchWithChildren,
BranchCreate, BranchCreate,
@ -52,7 +52,6 @@ import {
UndoButton, UndoButton,
} from 'components/button'; } from 'components/button';
import { useNavigator } from 'src/stores/navigator'; import { useNavigator } from 'src/stores/navigator';
import AdvanceSearch from 'src/components/shared/AdvanceSearch.vue';
const $q = useQuasar(); const $q = useQuasar();
const { t } = useI18n(); const { t } = useI18n();
@ -73,6 +72,7 @@ const typeBranchItem = [
color: 'var(--blue-6-hsl)', color: 'var(--blue-6-hsl)',
}, },
]; ];
const refFilter = ref<InstanceType<typeof QSelect>>();
const holdDialog = ref(false); const holdDialog = ref(false);
const isSubCreate = ref(false); const isSubCreate = ref(false);
const columns = [ const columns = [
@ -175,8 +175,6 @@ const qrCodeDialog = ref(false);
const qrCodeimageUrl = ref<string>(''); const qrCodeimageUrl = ref<string>('');
const formLastSubBranch = ref<number>(0); const formLastSubBranch = ref<number>(0);
const searchDate = ref<string[]>([]);
const branchStore = useBranchStore(); const branchStore = useBranchStore();
const flowStore = useFlowStore(); const flowStore = useFlowStore();
const { locale } = useI18n(); const { locale } = useI18n();
@ -717,20 +715,12 @@ async function fetchList(opts: {
tree?: boolean; tree?: boolean;
withHead?: boolean; withHead?: boolean;
filter?: 'head' | 'sub'; filter?: 'head' | 'sub';
startDate?: string;
endDate?: string;
}) { }) {
await branchStore.fetchList(opts); await branchStore.fetchList(opts);
} }
watch([inputSearch, searchDate], () => { watch(inputSearch, () => {
fetchList({ fetchList({ tree: true, query: inputSearch.value, withHead: true });
tree: true,
query: inputSearch.value,
withHead: true,
startDate: searchDate.value[0],
endDate: searchDate.value[1],
});
currentSubBranch.value = undefined; currentSubBranch.value = undefined;
}); });
@ -791,8 +781,6 @@ async function onSubmit(submitSelectedItem?: boolean) {
); );
if (!res) return; if (!res) return;
formType.value = 'view';
formData.value.codeHeadOffice = formData.value.code = res.code; formData.value.codeHeadOffice = formData.value.code = res.code;
imageUrl.value = `${baseUrl}/branch/${res.id}/image/${res.selectedImage}`; imageUrl.value = `${baseUrl}/branch/${res.id}/image/${res.selectedImage}`;
@ -867,9 +855,7 @@ async function onSubmit(submitSelectedItem?: boolean) {
actionText: t('dialog.action.ok'), actionText: t('dialog.action.ok'),
persistent: true, persistent: true,
title: t('form.warning.title'), title: t('form.warning.title'),
cancel: () => { cancel: () => {},
formType.value = 'create';
},
action: async () => { action: async () => {
await createBranch(); await createBranch();
}, },
@ -1050,7 +1036,7 @@ watch(currentHq, () => {
{{ $t('branch.allBranch') }} {{ $t('branch.allBranch') }}
</div> </div>
<q-btn <q-btn
v-if="isRoleInclude(['system'])" v-if="isRoleInclude(['head_of_admin', 'admin', 'system'])"
round round
flat flat
size="md" size="md"
@ -1074,7 +1060,6 @@ watch(currentHq, () => {
<div class="col full-width scroll"> <div class="col full-width scroll">
<div class="q-pa-md"> <div class="q-pa-md">
<TreeComponent <TreeComponent
:hide-create="!canAccess('branch', 'create')"
v-model:nodes="treeData" v-model:nodes="treeData"
v-model:expanded-tree="expandedTree" v-model:expanded-tree="expandedTree"
node-key="id" node-key="id"
@ -1185,49 +1170,26 @@ watch(currentHq, () => {
<template v-slot:prepend> <template v-slot:prepend>
<q-icon name="mdi-magnify" /> <q-icon name="mdi-magnify" />
</template> </template>
<template v-slot:append> <template v-if="$q.screen.lt.md" v-slot:append>
<q-separator vertical inset class="q-mr-xs" /> <span class="row">
<q-separator vertical />
<AdvanceSearch <q-btn
v-model="searchDate" icon="mdi-filter-variant"
:active="$q.screen.lt.md && statusFilter !== 'all'" unelevated
> class="q-ml-sm"
<div padding="4px"
v-if="$q.screen.lt.md" size="sm"
class="q-mt-sm text-weight-medium" rounded
> @click="refFilter?.showPopup"
{{ $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',
},
]"
/> />
</AdvanceSearch> </span>
</template> </template>
</q-input> </q-input>
<div class="row col-md-6 justify-end"> <div class="row col-md-6 justify-end">
<q-select <q-select
v-if="$q.screen.gt.sm" v-show="$q.screen.gt.sm"
ref="refFilter"
v-model="statusFilter" v-model="statusFilter"
outlined outlined
dense dense
@ -1562,18 +1524,8 @@ watch(currentHq, () => {
</q-td> </q-td>
<q-td> <q-td>
<KebabAction <KebabAction
v-if="
!currentHq.id
? canAccess('branch', 'create')
: true
"
:status="props.row.status" :status="props.row.status"
:idName="props.row.name" :idName="props.row.name"
:hide-delete="
!currentHq.id
? !isRoleInclude(['system'])
: !canAccess('branch', 'create')
"
@view=" @view="
if (props.row.isHeadOffice) { if (props.row.isHeadOffice) {
triggerEdit( triggerEdit(
@ -1713,18 +1665,8 @@ watch(currentHq, () => {
> >
<template v-slot:action> <template v-slot:action>
<KebabAction <KebabAction
v-if="
!currentHq.id
? canAccess('branch', 'create')
: true
"
:status="props.row.status" :status="props.row.status"
:idName="props.row.name" :idName="props.row.name"
:hide-delete="
!currentHq.id
? !isRoleInclude(['system'])
: !canAccess('branch', 'create')
"
@view=" @view="
if (props.row.isHeadOffice) { if (props.row.isHeadOffice) {
triggerEdit( triggerEdit(
@ -2272,14 +2214,6 @@ watch(currentHq, () => {
@click="drawerEdit()" @click="drawerEdit()"
type="button" type="button"
/> />
<template
v-if="
formType !== 'edit' && formTypeBranch === 'headOffice'
? isRoleInclude(['system'])
: canAccess('branch', 'create')
"
>
<DeleteButton <DeleteButton
v-if="formType !== 'edit'" v-if="formType !== 'edit'"
id="btn-info-basic-delete" id="btn-info-basic-delete"
@ -2287,7 +2221,6 @@ watch(currentHq, () => {
@click="triggerDelete(currentEdit.id)" @click="triggerDelete(currentEdit.id)"
type="button" type="button"
/> />
</template>
</div> </div>
</div> </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 useAddressStore from 'stores/address';
import useMyBranch from 'src/stores/my-branch'; import useMyBranch from 'src/stores/my-branch';
import { calculateAge } from 'src/utils/datetime'; import { calculateAge } from 'src/utils/datetime';
import { useQuasar, type QTableProps } from 'quasar'; import { QSelect, useQuasar, type QTableProps } from 'quasar';
import { dialog, baseUrl, setPrefixName } from 'stores/utils'; import { dialog, baseUrl } from 'stores/utils';
import { useNavigator } from 'src/stores/navigator'; import { useNavigator } from 'src/stores/navigator';
import { isRoleInclude, resetScrollBar } from 'src/stores/utils'; import { isRoleInclude, resetScrollBar } from 'src/stores/utils';
import { BranchUserStats } from 'stores/branch/types'; 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 FormByType from 'components/02_personnel-management/FormByType.vue';
import FormInformation from 'components/02_personnel-management/FormInformation.vue'; import FormInformation from 'components/02_personnel-management/FormInformation.vue';
import PaginationPageSize from 'src/components/PaginationPageSize.vue'; import PaginationPageSize from 'src/components/PaginationPageSize.vue';
import AdvanceSearch from 'src/components/shared/AdvanceSearch.vue';
const { locale, t } = useI18n(); const { locale, t } = useI18n();
const $q = useQuasar(); const $q = useQuasar();
@ -74,6 +73,7 @@ const isImageEdit = ref(false);
const imageDialog = ref(false); const imageDialog = ref(false);
const infoDrawerEdit = ref(false); const infoDrawerEdit = ref(false);
const refreshImageState = ref(false); const refreshImageState = ref(false);
const refFilter = ref<InstanceType<typeof QSelect>>();
const firstScroll = ref(false); const firstScroll = ref(false);
const inputSearch = ref(''); const inputSearch = ref('');
@ -93,14 +93,12 @@ const currentUser = ref<User>();
const userCode = ref<string>(); const userCode = ref<string>();
const statusToggle = ref(true); const statusToggle = ref(true);
const userFile = ref<File[]>([]); const agencyFile = ref<File[]>([]);
const userFileList = ref<{ name: string; url: string }[]>([]); const agencyFileList = ref<{ name: string; url: string }[]>([]);
const typeStats = ref<UserTypeStats>(); const typeStats = ref<UserTypeStats>();
const userStats = ref<BranchUserStats[]>(); const userStats = ref<BranchUserStats[]>();
const searchDate = ref<[]>([]);
const urlProfile = ref<string>(); const urlProfile = ref<string>();
const profileFileImg = ref<File | null>(null); const profileFileImg = ref<File | null>(null);
const imageList = ref<{ selectedImage: string; list: string[] }>(); const imageList = ref<{ selectedImage: string; list: string[] }>();
@ -126,7 +124,7 @@ const defaultFormData = {
streetEN: '', streetEN: '',
street: '', street: '',
trainingPlace: null, trainingPlace: null,
importNationality: [], importNationality: null,
sourceNationality: null, sourceNationality: null,
licenseExpireDate: null, licenseExpireDate: null,
licenseIssueDate: null, licenseIssueDate: null,
@ -153,18 +151,6 @@ const defaultFormData = {
citizenExpire: null, citizenExpire: null,
citizenIssue: null, citizenIssue: null,
citizenId: '', 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>({ const formData = ref<UserCreate>({
@ -186,7 +172,7 @@ const formData = ref<UserCreate>({
streetEN: '', streetEN: '',
street: '', street: '',
trainingPlace: null, trainingPlace: null,
importNationality: [], importNationality: null,
sourceNationality: null, sourceNationality: null,
licenseExpireDate: null, licenseExpireDate: null,
licenseIssueDate: null, licenseIssueDate: null,
@ -213,18 +199,6 @@ const formData = ref<UserCreate>({
citizenExpire: null, citizenExpire: null,
citizenIssue: null, citizenIssue: null,
citizenId: '', 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 }[]>([ const fieldSelectedOption = ref<{ label: string; value: string }[]>([
@ -353,7 +327,7 @@ function onClose(excludeDialog?: boolean) {
urlProfile.value = ''; urlProfile.value = '';
profileFileImg.value = null; profileFileImg.value = null;
infoDrawerEdit.value = false; infoDrawerEdit.value = false;
userFile.value = []; agencyFile.value = [];
isEdit.value = false; isEdit.value = false;
statusToggle.value = true; statusToggle.value = true;
isImageEdit.value = false; isImageEdit.value = false;
@ -362,8 +336,6 @@ function onClose(excludeDialog?: boolean) {
mapUserType(currentTab.value); mapUserType(currentTab.value);
imageList.value = { selectedImage: '', list: [] }; imageList.value = { selectedImage: '', list: [] };
onCreateImageList.value = { selectedImage: '', list: [] }; onCreateImageList.value = { selectedImage: '', list: [] };
userFileList.value = [];
userFile.value = [];
flowStore.rotate(); flowStore.rotate();
} }
@ -384,10 +356,12 @@ async function openDialog(
isEdit.value = true; isEdit.value = true;
await assignFormData(id); await assignFormData(id);
if (formData.value.userType === 'AGENCY') {
const result = await userStore.fetchAttachment(id); const result = await userStore.fetchAttachment(id);
if (result) { if (result) {
userFileList.value = result; agencyFileList.value = result;
}
} }
} }
if (userStore.userOption.hqOpts.length !== 0 && !id) { if (userStore.userOption.hqOpts.length !== 0 && !id) {
@ -445,37 +419,15 @@ async function onSubmit(excludeDialog?: boolean) {
: ''; : '';
const formDataEdit = { const formDataEdit = {
...formData.value, ...formData.value,
checkpointEN: formData.value.checkpoint,
status: !statusToggle.value ? 'INACTIVE' : 'ACTIVE', 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; } as const;
await userStore.editById(currentUser.value.id, formDataEdit); await userStore.editById(currentUser.value.id, formDataEdit);
if (userFile.value) { if (currentUser.value.id && formDataEdit.userType === 'AGENCY') {
if (!agencyFile.value) return;
const payload: UserAttachmentCreate = { const payload: UserAttachmentCreate = {
file: userFile.value, file: agencyFile.value,
}; };
if (payload?.file) { if (payload?.file) {
@ -498,39 +450,16 @@ async function onSubmit(excludeDialog?: boolean) {
: hqId.value : hqId.value
? hqId.value ? hqId.value
: ''; : '';
formData.value.checkpointEN = formData.value.checkpoint;
const result = await userStore.create( const result = await userStore.create(
{ formData.value,
...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,
},
onCreateImageList.value, onCreateImageList.value,
); );
if (userFile.value && result) { if (result && formData.value.userType === 'AGENCY') {
if (!agencyFile.value) return;
const payload: UserAttachmentCreate = { const payload: UserAttachmentCreate = {
file: userFile.value, file: agencyFile.value,
}; };
if (payload?.file) { if (payload?.file) {
@ -622,20 +551,12 @@ async function assignFormData(idEdit: string) {
currentUser.value = foundUser; currentUser.value = foundUser;
formData.value = { formData.value = {
branchId: foundUser.branch[0]?.id, branchId: foundUser.branch[0]?.id,
provinceId: foundUser.addressForeign provinceId: foundUser.provinceId,
? foundUser.provinceText districtId: foundUser.districtId,
: foundUser.provinceId, subDistrictId: foundUser.subDistrictId,
districtId: foundUser.addressForeign
? foundUser.districtText
: foundUser.districtId,
subDistrictId: foundUser.addressForeign
? foundUser.subDistrictText
: foundUser.subDistrictId,
telephoneNo: foundUser.telephoneNo, telephoneNo: foundUser.telephoneNo,
email: foundUser.email, email: foundUser.email,
zipCode: foundUser.addressForeign zipCode: foundUser.zipCode,
? foundUser.zipCodeText
: foundUser.zipCode,
gender: foundUser.gender, gender: foundUser.gender,
addressEN: foundUser.addressEN, addressEN: foundUser.addressEN,
address: foundUser.address, address: foundUser.address,
@ -646,10 +567,7 @@ async function assignFormData(idEdit: string) {
street: foundUser.street, street: foundUser.street,
streetEN: foundUser.streetEN, streetEN: foundUser.streetEN,
trainingPlace: foundUser.trainingPlace, trainingPlace: foundUser.trainingPlace,
importNationality: importNationality: foundUser.importNationality,
typeof foundUser.importNationality === 'string'
? [foundUser.importNationality]
: foundUser.importNationality,
sourceNationality: foundUser.sourceNationality, sourceNationality: foundUser.sourceNationality,
licenseNo: foundUser.licenseNo, licenseNo: foundUser.licenseNo,
discountCondition: foundUser.discountCondition, discountCondition: foundUser.discountCondition,
@ -669,8 +587,6 @@ async function assignFormData(idEdit: string) {
responsibleArea: foundUser.responsibleArea, responsibleArea: foundUser.responsibleArea,
status: foundUser.status, status: foundUser.status,
selectedImage: foundUser.selectedImage, selectedImage: foundUser.selectedImage,
contactName: foundUser.contactName || '',
contactTel: foundUser.contactTel || '',
licenseExpireDate: licenseExpireDate:
(foundUser.licenseExpireDate && (foundUser.licenseExpireDate &&
new Date(foundUser.licenseExpireDate)) || new Date(foundUser.licenseExpireDate)) ||
@ -687,12 +603,6 @@ async function assignFormData(idEdit: string) {
(foundUser.citizenIssue && new Date(foundUser.citizenIssue)) || null, (foundUser.citizenIssue && new Date(foundUser.citizenIssue)) || null,
citizenExpire: citizenExpire:
(foundUser.citizenExpire && new Date(foundUser.citizenExpire)) || null, (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' formData.value.status === 'ACTIVE' || 'CREATED'
@ -751,8 +661,6 @@ async function fetchUserList(mobileFetch?: boolean) {
: statusFilter.value === 'statusACTIVE' : statusFilter.value === 'statusACTIVE'
? 'ACTIVE' ? 'ACTIVE'
: 'INACTIVE', : 'INACTIVE',
startDate: searchDate.value[0],
endDate: searchDate.value[1],
}); });
if (ret) { if (ret) {
@ -819,17 +727,7 @@ watch(
watch( watch(
() => formData.value.userType, () => formData.value.userType,
async (type) => { async () => {
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;
}
if (!infoDrawerEdit.value) return; if (!infoDrawerEdit.value) return;
formData.value.registrationNo = null; formData.value.registrationNo = null;
formData.value.startDate = null; formData.value.startDate = null;
@ -837,11 +735,11 @@ watch(
formData.value.responsibleArea = null; formData.value.responsibleArea = null;
formData.value.discountCondition = null; formData.value.discountCondition = null;
formData.value.sourceNationality = null; formData.value.sourceNationality = null;
formData.value.importNationality = []; formData.value.importNationality = null;
formData.value.trainingPlace = null; formData.value.trainingPlace = null;
formData.value.checkpoint = null; formData.value.checkpoint = null;
formData.value.checkpointEN = 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 = []; if (userData.value) userData.value.result = [];
currentPage.value = 1; currentPage.value = 1;
@ -974,45 +872,26 @@ watch(
<template #prepend> <template #prepend>
<q-icon name="mdi-magnify" /> <q-icon name="mdi-magnify" />
</template> </template>
<template v-slot:append> <template v-if="$q.screen.lt.md" v-slot:append>
<q-separator vertical inset class="q-mr-xs" /> <span class="row">
<AdvanceSearch <q-separator vertical />
v-model="searchDate" <q-btn
:active="$q.screen.lt.md && statusFilter !== 'all'" icon="mdi-filter-variant"
> unelevated
<div class="q-ml-sm"
v-if="$q.screen.lt.md" padding="4px"
class="q-mt-sm text-weight-medium" size="sm"
> rounded
{{ $t('general.status') }} @click="refFilter?.showPopup"
</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',
},
]"
/> />
</AdvanceSearch> </span>
</template> </template>
</q-input> </q-input>
<div class="row col-md-5" style="white-space: nowrap"> <div class="row col-md-5" style="white-space: nowrap">
<q-select <q-select
v-if="$q.screen.gt.sm" v-show="$q.screen.gt.sm"
ref="refFilter"
v-model="statusFilter" v-model="statusFilter"
outlined outlined
dense dense
@ -1362,7 +1241,7 @@ watch(
{{ {{
locale === 'eng' locale === 'eng'
? `${props.row.firstNameEN} ${props.row.lastNameEN}`.trim() ? `${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 <q-tooltip
anchor="bottom left" anchor="bottom left"
@ -1372,7 +1251,7 @@ watch(
{{ {{
locale === 'eng' locale === 'eng'
? `${props.row.firstNameEN} ${props.row.lastNameEN}`.trim() ? `${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> </q-tooltip>
@ -1638,12 +1517,9 @@ watch(
hide-action hide-action
:is-edit="infoDrawerEdit" :is-edit="infoDrawerEdit"
:title=" :title="
(currentUser.namePrefix locale === 'eng'
? $t('customer.form.prefix.' + currentUser.namePrefix) + ' '
: '') +
(locale === 'eng'
? `${currentUser.firstNameEN} ${currentUser.lastNameEN}` ? `${currentUser.firstNameEN} ${currentUser.lastNameEN}`
: `${currentUser.firstName || currentUser.firstNameEN} ${currentUser.lastName || currentUser.lastNameEN}`) : `${currentUser.firstName} ${currentUser.lastName}`
" "
v-model:drawerOpen="infoDrawer" v-model:drawerOpen="infoDrawer"
:submit="() => onSubmit()" :submit="() => onSubmit()"
@ -1675,18 +1551,7 @@ watch(
v-model:toggle-status="formData.status" v-model:toggle-status="formData.status"
hideFade hideFade
:toggle-title="$t('status.title')" :toggle-title="$t('status.title')"
:title=" :title="`${locale === 'eng' ? `${formData.firstNameEN} ${formData.lastNameEN}` : `${formData.firstName} ${formData.lastName}`}`"
setPrefixName(
{
namePrefix: formData.namePrefix,
firstName: formData.firstName || formData.firstNameEN,
lastName: formData.lastName || formData.lastNameEN,
firstNameEN: formData.firstNameEN,
lastNameEN: formData.lastNameEN,
},
{ locale },
)
"
:caption="userCode" :caption="userCode"
:img=" :img="
`${baseUrl}/user/${currentUser.id}/profile-image/${formData.selectedImage}`.concat( `${baseUrl}/user/${currentUser.id}/profile-image/${formData.selectedImage}`.concat(
@ -1871,15 +1736,12 @@ watch(
v-model:citizen-id="formData.citizenId" v-model:citizen-id="formData.citizenId"
v-model:citizen-issue="formData.citizenIssue" v-model:citizen-issue="formData.citizenIssue"
v-model:citizen-expire="formData.citizenExpire" v-model:citizen-expire="formData.citizenExpire"
v-model:contact-name="formData.contactName"
v-model:contact-tel="formData.contactTel"
:title="'personnel.form.personalInformation'" :title="'personnel.form.personalInformation'"
prefix-id="drawer-info-personnel" prefix-id="drawer-info-personnel"
dense dense
outlined outlined
separator separator
:readonly="!infoDrawerEdit" :readonly="!infoDrawerEdit"
:agency="formData.userType === 'AGENCY'"
class="q-mb-xl" class="q-mb-xl"
/> />
@ -1897,15 +1759,10 @@ watch(
v-model:district-id="formData.districtId" v-model:district-id="formData.districtId"
v-model:sub-district-id="formData.subDistrictId" v-model:sub-district-id="formData.subDistrictId"
v-model:zip-code="formData.zipCode" 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" :readonly="!infoDrawerEdit"
prefix-id="drawer-info-personnel" prefix-id="drawer-info-personnel"
:title="'personnel.form.addressInformation'" :title="'personnel.form.addressInformation'"
dense dense
:use-foreign-address="formData.userType === 'AGENCY'"
class="q-mb-xl" class="q-mb-xl"
/> />
<FormByType <FormByType
@ -1924,11 +1781,10 @@ watch(
v-model:import-nationality="formData.importNationality" v-model:import-nationality="formData.importNationality"
v-model:training-place="formData.trainingPlace" v-model:training-place="formData.trainingPlace"
v-model:checkpoint="formData.checkpoint" v-model:checkpoint="formData.checkpoint"
v-model:user-file="userFile" v-model:checkpoint-en="formData.checkpointEN"
v-model:user-file-list="userFileList" v-model:agency-file="agencyFile"
v-model:agency-file-list="agencyFileList"
v-model:user-id="currentUser.id" v-model:user-id="currentUser.id"
v-model:remark="formData.remark"
v-model:agency-status="formData.agencyStatus"
/> />
</div> </div>
</div> </div>
@ -1972,18 +1828,7 @@ watch(
}[formData.gender] }[formData.gender]
" "
:toggleTitle="$t('status.title')" :toggleTitle="$t('status.title')"
:title=" :title="`${locale === 'eng' ? `${formData.firstNameEN} ${formData.lastNameEN}` : `${formData.firstName} ${formData.lastName}`}`"
setPrefixName(
{
namePrefix: formData.namePrefix,
firstName: formData.firstName,
lastName: formData.lastName,
firstNameEN: formData.firstNameEN,
lastNameEN: formData.lastNameEN,
},
{ locale },
)
"
:fallbackImg=" :fallbackImg="
{ {
male: '/no-img-man.png', male: '/no-img-man.png',
@ -2009,6 +1854,7 @@ watch(
<div <div
class="col" class="col"
id="personnel-form"
:class="{ :class="{
'q-px-lg q-pb-lg': $q.screen.gt.sm, 'q-px-lg q-pb-lg': $q.screen.gt.sm,
'q-px-md q-pb-sm': !$q.screen.gt.sm, 'q-px-md q-pb-sm': !$q.screen.gt.sm,
@ -2052,7 +1898,7 @@ watch(
? [ ? [
{ {
name: $t('personnel.form.workInformation'), name: $t('personnel.form.workInformation'),
anchor: 'dialog-form-work', anchor: 'dialog-info-work',
}, },
] ]
: [], : [],
@ -2068,7 +1914,6 @@ watch(
</div> </div>
</div> </div>
<div <div
id="personnel-form"
class="col-md-10 col-12 full-height scroll" class="col-md-10 col-12 full-height scroll"
:class="{ :class="{
'q-py-md q-pr-md ': $q.screen.gt.sm, 'q-py-md q-pr-md ': $q.screen.gt.sm,
@ -2094,7 +1939,6 @@ watch(
id="dialog-form-personal" id="dialog-form-personal"
prefix-id="form-dialog-personnel" prefix-id="form-dialog-personnel"
dense dense
:agency="formData.userType === 'AGENCY'"
outlined outlined
separator separator
:title="'personnel.form.personalInformation'" :title="'personnel.form.personalInformation'"
@ -2113,8 +1957,6 @@ watch(
v-model:citizen-id="formData.citizenId" v-model:citizen-id="formData.citizenId"
v-model:citizen-issue="formData.citizenIssue" v-model:citizen-issue="formData.citizenIssue"
v-model:citizen-expire="formData.citizenExpire" v-model:citizen-expire="formData.citizenExpire"
v-model:contact-name="formData.contactName"
v-model:contact-tel="formData.contactTel"
class="q-mb-xl" class="q-mb-xl"
/> />
<AddressForm <AddressForm
@ -2131,13 +1973,8 @@ watch(
v-model:district-id="formData.districtId" v-model:district-id="formData.districtId"
v-model:sub-district-id="formData.subDistrictId" v-model:sub-district-id="formData.subDistrictId"
v-model:zip-code="formData.zipCode" 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" prefix-id="drawer-info-personnel"
dense dense
:use-foreign-address="formData.userType === 'AGENCY'"
class="q-mb-xl" class="q-mb-xl"
/> />
<FormByType <FormByType
@ -2155,10 +1992,8 @@ watch(
v-model:import-nationality="formData.importNationality" v-model:import-nationality="formData.importNationality"
v-model:training-place="formData.trainingPlace" v-model:training-place="formData.trainingPlace"
v-model:checkpoint="formData.checkpoint" v-model:checkpoint="formData.checkpoint"
v-model:agency-status="formData.agencyStatus" v-model:checkpoint-en="formData.checkpointEN"
v-model:remark="formData.remark" v-model:agency-file="agencyFile"
v-model:user-file="userFile"
v-model:user-file-list="userFileList"
/> />
</div> </div>
</div> </div>

View file

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

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,55 +1,21 @@
import { ref, watch } from 'vue'; import { ref, watch } from 'vue';
import { defineStore } from 'pinia'; import { defineStore } from 'pinia';
import { useI18n } from 'vue-i18n';
import { import {
CustomerBranch,
CustomerBranchCreate, CustomerBranchCreate,
CustomerCreate, CustomerCreate,
CustomerType, CustomerType,
} from 'stores/customer/types'; } from 'stores/customer/types';
import { Employee, EmployeeCreate } from 'stores/employee/types'; import { Employee, EmployeeCreate } from 'stores/employee/types';
import { dialog } from 'stores/utils';
import useMyBranch from 'stores/my-branch'; import useMyBranch from 'stores/my-branch';
import useCustomerStore from 'stores/customer'; import useCustomerStore from 'stores/customer';
import useEmployeeStore from 'stores/employee'; import useEmployeeStore from 'stores/employee';
import useFlowStore from 'stores/flow'; import useFlowStore from 'stores/flow';
import useMyBranchStore from 'stores/my-branch';
import { baseUrl } from 'src/stores/utils'; import { baseUrl } from 'src/stores/utils';
import { getRole, getUserId } from 'src/services/keycloak';
import { useRoute } from 'vue-router';
export const useCustomerForm = defineStore('form-customer', () => { export const useCustomerForm = defineStore('form-customer', () => {
const customerStore = useCustomerStore(); const customerStore = useCustomerStore();
const onCreateImageList = ref<{
selectedImage: string;
list: { url: string; imgFile: File | null; name: string }[];
}>({ selectedImage: '', list: [] });
const { t } = useI18n();
const 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 = { const defaultFormData: CustomerCreate = {
// code: '', // code: '',
@ -88,7 +54,6 @@ export const useCustomerForm = defineStore('form-customer', () => {
formDataOcr: Record<string, any>; formDataOcr: Record<string, any>;
isImageEdit: boolean; isImageEdit: boolean;
currentCustomerId?: string; currentCustomerId?: string;
imageList: { list: string[]; selectedImage: string };
}>({ }>({
dialogType: 'info', dialogType: 'info',
dialogOpen: false, dialogOpen: false,
@ -105,7 +70,6 @@ export const useCustomerForm = defineStore('form-customer', () => {
treeFile: [], treeFile: [],
formDataOcr: {}, formDataOcr: {},
isImageEdit: false, isImageEdit: false,
imageList: { list: [], selectedImage: '' },
}); });
watch( watch(
@ -168,7 +132,6 @@ export const useCustomerForm = defineStore('form-customer', () => {
state.value.editCustomerCode = data.code; state.value.editCustomerCode = data.code;
state.value.customerImageUrl = `${baseUrl}/customer/${id}/image/${data.selectedImage}`; state.value.customerImageUrl = `${baseUrl}/customer/${id}/image/${data.selectedImage}`;
state.value.defaultCustomerImageUrl = `${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.registeredBranchId = data.registeredBranchId;
resetFormData.status = data.status; resetFormData.status = data.status;
@ -190,7 +153,7 @@ export const useCustomerForm = defineStore('form-customer', () => {
payDate: v.payDate, payDate: v.payDate,
jobDescription: v.jobDescription, jobDescription: v.jobDescription,
jobPosition: v.jobPosition, jobPosition: v.jobPosition,
businessTypeId: v.businessTypeId, businessType: v.businessType,
employmentOffice: v.employmentOffice, employmentOffice: v.employmentOffice,
employmentOfficeEN: v.employmentOfficeEN, employmentOfficeEN: v.employmentOfficeEN,
telephoneNo: v.telephoneNo, telephoneNo: v.telephoneNo,
@ -227,6 +190,7 @@ export const useCustomerForm = defineStore('form-customer', () => {
contactTel: v.contactTel, contactTel: v.contactTel,
officeTel: v.officeTel, officeTel: v.officeTel,
agentUserId: v.agentUserId || undefined, agentUserId: v.agentUserId || undefined,
customerName: v.customerName,
authorizedName: v.authorizedName, authorizedName: v.authorizedName,
authorizedNameEN: v.authorizedNameEN, authorizedNameEN: v.authorizedNameEN,
@ -264,6 +228,7 @@ export const useCustomerForm = defineStore('form-customer', () => {
async function addCurrentCustomerBranch() { async function addCurrentCustomerBranch() {
if (currentFormData.value.customerBranch?.some((v) => !v.id)) return; if (currentFormData.value.customerBranch?.some((v) => !v.id)) return;
currentFormData.value.customerBranch?.push({ currentFormData.value.customerBranch?.push({
id: '',
customerId: '', customerId: '',
branchCode: branchCode:
currentFormData.value.customerBranch.length !== 0 currentFormData.value.customerBranch.length !== 0
@ -297,8 +262,8 @@ export const useCustomerForm = defineStore('form-customer', () => {
birthDate: birthDate:
currentFormData.value.customerBranch?.at(0)?.birthDate || undefined, currentFormData.value.customerBranch?.at(0)?.birthDate || undefined,
businessTypeId: businessType:
currentFormData.value.customerBranch?.at(0)?.businessTypeId || '', currentFormData.value.customerBranch?.at(0)?.businessType || '',
jobPosition: jobPosition:
currentFormData.value.customerBranch?.at(0)?.jobPosition || '', currentFormData.value.customerBranch?.at(0)?.jobPosition || '',
jobDescription: jobDescription:
@ -335,6 +300,8 @@ export const useCustomerForm = defineStore('form-customer', () => {
currentFormData.value.customerBranch?.at(0)?.agentUserId || undefined, currentFormData.value.customerBranch?.at(0)?.agentUserId || undefined,
status: 'CREATED', status: 'CREATED',
customerName:
currentFormData.value.customerBranch?.at(0)?.customerName || '',
registerName: registerName:
currentFormData.value.customerBranch?.at(0)?.registerName || '', currentFormData.value.customerBranch?.at(0)?.registerName || '',
registerNameEN: 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 { return {
onCreateImageList,
tabFieldRequired,
registerAbleBranchOption,
state, state,
resetFormData, resetFormData,
currentFormData, currentFormData,
currentBranchRootId,
isFormDataDifferent, isFormDataDifferent,
resetForm, resetForm,
assignFormData, assignFormData,
submitFormCustomer, submitFormCustomer,
addCurrentCustomerBranch, addCurrentCustomerBranch,
deleteAttachment, deleteAttachment,
fetchListOfOptionBranch,
customerFormUndo,
customerConfirmUnsave,
deleteCustomerById,
validateTabField,
deleteCustomerBranchById,
}; };
}); });
export const useCustomerBranchForm = defineStore('form-customer-branch', () => { export const useCustomerBranchForm = defineStore('form-customer-branch', () => {
const customerStore = useCustomerStore(); const customerStore = useCustomerStore();
const customerFormStore = useCustomerForm(); const customerFormStore = useCustomerForm();
const defaultFormData: CustomerBranchCreate & { const defaultFormData: CustomerBranchCreate & {
id?: string; id?: string;
codeCustomer?: string; codeCustomer?: string;
@ -549,7 +397,7 @@ export const useCustomerBranchForm = defineStore('form-customer-branch', () => {
gender: '', gender: '',
birthDate: undefined, birthDate: undefined,
businessTypeId: '', businessType: '',
jobPosition: '', jobPosition: '',
jobDescription: '', jobDescription: '',
payDate: '', payDate: '',
@ -579,6 +427,7 @@ export const useCustomerBranchForm = defineStore('form-customer-branch', () => {
agentUserId: undefined, agentUserId: undefined,
status: 'CREATED', status: 'CREATED',
customerName: '',
registerName: '', registerName: '',
registerNameEN: '', registerNameEN: '',
registerDate: undefined, registerDate: undefined,
@ -630,7 +479,7 @@ export const useCustomerBranchForm = defineStore('form-customer-branch', () => {
payDateEN: _data.payDateEN, payDateEN: _data.payDateEN,
jobDescription: _data.jobDescription, jobDescription: _data.jobDescription,
jobPosition: _data.jobPosition, jobPosition: _data.jobPosition,
businessTypeId: _data.businessTypeId, businessType: _data.businessType,
employmentOffice: _data.employmentOffice, employmentOffice: _data.employmentOffice,
employmentOfficeEN: _data.employmentOfficeEN, employmentOfficeEN: _data.employmentOfficeEN,
telephoneNo: _data.telephoneNo, telephoneNo: _data.telephoneNo,
@ -664,6 +513,7 @@ export const useCustomerBranchForm = defineStore('form-customer-branch', () => {
officeTel: _data.officeTel, officeTel: _data.officeTel,
agentUserId: _data.agentUserId || undefined, agentUserId: _data.agentUserId || undefined,
codeCustomer: _data.codeCustomer, codeCustomer: _data.codeCustomer,
customerName: _data.customerName,
homeCode: _data.homeCode, homeCode: _data.homeCode,
authorizedName: _data.authorizedName, authorizedName: _data.authorizedName,
authorizedNameEN: _data.authorizedNameEN, authorizedNameEN: _data.authorizedNameEN,
@ -731,23 +581,11 @@ export const useCustomerBranchForm = defineStore('form-customer-branch', () => {
}); });
export const useEmployeeForm = defineStore('form-employee', () => { export const useEmployeeForm = defineStore('form-employee', () => {
const { t } = useI18n();
const customerStore = useCustomerStore(); const customerStore = useCustomerStore();
const employeeStore = useEmployeeStore(); const employeeStore = useEmployeeStore();
const flowStore = useFlowStore(); const flowStore = useFlowStore();
const branchStore = useMyBranch(); 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<{ const state = ref<{
dialogType: 'info' | 'create' | 'edit'; dialogType: 'info' | 'create' | 'edit';
imageDialog: boolean; imageDialog: boolean;
@ -756,8 +594,6 @@ export const useEmployeeForm = defineStore('form-employee', () => {
drawerModal: boolean; drawerModal: boolean;
isImageEdit: boolean; isImageEdit: boolean;
currentBranchId: string;
currentCustomerBranch?: CustomerBranch;
currentEmployeeCode: string; currentEmployeeCode: string;
currentEmployee: Employee | null; currentEmployee: Employee | null;
currentIndexPassport: number; currentIndexPassport: number;
@ -790,9 +626,7 @@ export const useEmployeeForm = defineStore('form-employee', () => {
} }
| undefined; | undefined;
ocr: boolean; ocr: boolean;
imageList: { list: string[]; selectedImage: string };
}>({ }>({
currentBranchId: '',
isImageEdit: false, isImageEdit: false,
currentIndexPassport: -1, currentIndexPassport: -1,
currentIndexVisa: -1, currentIndexVisa: -1,
@ -815,10 +649,9 @@ export const useEmployeeForm = defineStore('form-employee', () => {
infoEmployeePersonCard: [], infoEmployeePersonCard: [],
formDataEmployeeOwner: undefined, formDataEmployeeOwner: undefined,
ocr: false, ocr: false,
imageList: { list: [], selectedImage: '' },
}); });
const defaultFormData: EmployeeCreate & { image?: File } = { const defaultFormData: EmployeeCreate = {
id: '', id: '',
code: '', code: '',
customerBranchId: '', customerBranchId: '',
@ -900,7 +733,6 @@ export const useEmployeeForm = defineStore('form-employee', () => {
expireDate: new Date(), expireDate: new Date(),
remark: undefined, remark: undefined,
workerType: '', workerType: '',
reportDate: null,
number: '', number: '',
}, },
], ],
@ -946,7 +778,7 @@ export const useEmployeeForm = defineStore('form-employee', () => {
}; };
let resetEmployeeData = structuredClone(defaultFormData); let resetEmployeeData = structuredClone(defaultFormData);
const currentFromDataEmployee = ref<EmployeeCreate & { image?: File }>( const currentFromDataEmployee = ref<EmployeeCreate>(
structuredClone(defaultFormData), structuredClone(defaultFormData),
); );
@ -968,14 +800,12 @@ export const useEmployeeForm = defineStore('form-employee', () => {
state.value.currentIndexVisa = -1; state.value.currentIndexVisa = -1;
state.value.currentIndexCheckup = -1; state.value.currentIndexCheckup = -1;
state.value.currentIndexWorkHistory = -1; state.value.currentIndexWorkHistory = -1;
state.value.imageList = { list: [], selectedImage: '' }; state.value.currentTab = 'personalInfo';
// state.value.currentTab = 'personalInfo';
if (clean) { if (clean) {
state.value.formDataEmployeeOwner = undefined; state.value.formDataEmployeeOwner = undefined;
resetEmployeeData = structuredClone(defaultFormData); resetEmployeeData = structuredClone(defaultFormData);
state.value.statusSavePersonal = false; state.value.statusSavePersonal = false;
state.value.profileUrl = ''; state.value.profileUrl = '';
state.value.currentBranchId = '';
} else { } else {
resetEmployeeData.selectedImage = resetEmployeeData.selectedImage =
currentFromDataEmployee.value.selectedImage; currentFromDataEmployee.value.selectedImage;
@ -996,16 +826,12 @@ export const useEmployeeForm = defineStore('form-employee', () => {
state.value.currentIndexPassport state.value.currentIndexPassport
].id === undefined ].id === undefined
) { ) {
const { id, employeeId, updatedAt, createdAt, file, ...payload } =
currentFromDataEmployee.value.employeePassport?.[
state.value.currentIndexPassport
];
const res = await employeeStore.postMeta({ const res = await employeeStore.postMeta({
parentId: currentFromDataEmployee.value.id || '', parentId: currentFromDataEmployee.value.id || '',
group: 'passport', group: 'passport',
meta: payload, meta: currentFromDataEmployee.value.employeePassport?.[
file: file, state.value.currentIndexPassport
],
}); });
if (res) { if (res) {
@ -1019,7 +845,7 @@ export const useEmployeeForm = defineStore('form-employee', () => {
state.value.currentIndexPassport state.value.currentIndexPassport
].id !== undefined ].id !== undefined
) { ) {
const { id, employeeId, updatedAt, createdAt, file, ...payload } = const { id, employeeId, updatedAt, createdAt, ...payload } =
currentFromDataEmployee.value.employeePassport?.[ currentFromDataEmployee.value.employeePassport?.[
state.value.currentIndexPassport state.value.currentIndexPassport
]; ];
@ -1032,7 +858,6 @@ export const useEmployeeForm = defineStore('form-employee', () => {
state.value.currentIndexPassport state.value.currentIndexPassport
].id || '', ].id || '',
meta: payload, meta: payload,
file: file || undefined,
}); });
} }
@ -1260,12 +1085,10 @@ export const useEmployeeForm = defineStore('form-employee', () => {
await assignFormDataEmployee(currentFromDataEmployee.value.id); await assignFormDataEmployee(currentFromDataEmployee.value.id);
} }
async function submitPersonal(imgList?: { async function submitPersonal(imgList: {
selectedImage: string; selectedImage: string;
list: { url: string; imgFile: File | null; name: string }[]; list: { url: string; imgFile: File | null; name: string }[];
}) { }) {
let employeeId: string | undefined = undefined;
currentFromDataEmployee.value.firstName = currentFromDataEmployee.value.firstName =
currentFromDataEmployee.value.firstName.trim(); currentFromDataEmployee.value.firstName.trim();
currentFromDataEmployee.value.middleName = currentFromDataEmployee.value.middleName =
@ -1281,11 +1104,10 @@ export const useEmployeeForm = defineStore('form-employee', () => {
currentFromDataEmployee.value.lastNameEN.trim(); currentFromDataEmployee.value.lastNameEN.trim();
if (state.value.dialogType === 'create') { if (state.value.dialogType === 'create') {
delete currentFromDataEmployee.value.image;
const res = await employeeStore.create( const res = await employeeStore.create(
{ {
...currentFromDataEmployee.value, ...currentFromDataEmployee.value,
customerBranchId: state.value.currentBranchId || '', customerBranchId: state.value.formDataEmployeeOwner?.id || '',
employeeWork: [], employeeWork: [],
employeeCheckup: [], employeeCheckup: [],
@ -1295,7 +1117,6 @@ export const useEmployeeForm = defineStore('form-employee', () => {
); );
if (res) { if (res) {
employeeId = res.id;
await assignFormDataEmployee(res.id); await assignFormDataEmployee(res.id);
currentFromDataEmployee.value.id = res.id; currentFromDataEmployee.value.id = res.id;
state.value.statusSavePersonal = true; state.value.statusSavePersonal = true;
@ -1310,19 +1131,17 @@ export const useEmployeeForm = defineStore('form-employee', () => {
state.value.currentEmployee?.status === 'CREATED' state.value.currentEmployee?.status === 'CREATED'
? 'ACTIVE' ? 'ACTIVE'
: state.value.currentEmployee?.status, : state.value.currentEmployee?.status,
customerBranchId: state.value.currentBranchId || '', customerBranchId: state.value.formDataEmployeeOwner?.id || '',
employeeWork: [], employeeWork: [],
employeeCheckup: [], employeeCheckup: [],
employeeOtherInfo: undefined, employeeOtherInfo: undefined,
}, },
); );
if (res) { if (res) {
employeeId = res.id;
await assignFormDataEmployee(res.id); await assignFormDataEmployee(res.id);
state.value.statusSavePersonal = true; state.value.statusSavePersonal = true;
} }
} }
return employeeId;
} }
async function assignFormDataEmployee(id?: string) { async function assignFormDataEmployee(id?: string) {
@ -1364,19 +1183,7 @@ export const useEmployeeForm = defineStore('form-employee', () => {
employeePassport: structuredClone( employeePassport: structuredClone(
payload.employeePassport?.length === 0 payload.employeePassport?.length === 0
? state.value.dialogModal ? state.value.dialogModal
? defaultFormData.employeePassport.map((v) => ({ ? defaultFormData.employeePassport
...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,
}))
: [] : []
: payload.employeePassport, : payload.employeePassport,
), ),
@ -1398,10 +1205,12 @@ export const useEmployeeForm = defineStore('form-employee', () => {
statusSave: true, statusSave: true,
})), })),
), ),
employeeOtherInfo: structuredClone({ employeeOtherInfo: structuredClone(
...(payload.employeeOtherInfo ?? {}), {
statusSave: true, ...payload.employeeOtherInfo,
}), statusSave: !!payload.employeeOtherInfo?.id ? true : false,
} || {},
),
employeeWork: structuredClone( employeeWork: structuredClone(
payload.employeeWork?.length === 0 payload.employeeWork?.length === 0
? state.value.dialogModal ? state.value.dialogModal
@ -1461,8 +1270,6 @@ export const useEmployeeForm = defineStore('form-employee', () => {
state.value.currentIndexVisa = -1; state.value.currentIndexVisa = -1;
} }
state.value.currentBranchId = payload.customerBranchId;
const foundBranch = await customerStore.fetchListCustomerBranchById( const foundBranch = await customerStore.fetchListCustomerBranchById(
payload.customerBranchId, payload.customerBranchId,
); );
@ -1518,17 +1325,17 @@ export const useEmployeeForm = defineStore('form-employee', () => {
issueDate: new Date(), issueDate: new Date(),
type: '', type: '',
expireDate: new Date(), expireDate: new Date(),
birthDate: currentFromDataEmployee.value.dateOfBirth, birthDate: new Date(),
workerStatus: '', workerStatus: '',
nationality: currentFromDataEmployee.value.nationality, nationality: '',
gender: currentFromDataEmployee.value.gender, gender: '',
lastNameEN: currentFromDataEmployee.value.lastNameEN, lastNameEN: '',
lastName: currentFromDataEmployee.value.lastName, lastName: '',
middleNameEN: currentFromDataEmployee.value.middleNameEN, middleNameEN: '',
middleName: currentFromDataEmployee.value.middleName, middleName: '',
firstNameEN: currentFromDataEmployee.value.firstNameEN, firstNameEN: '',
firstName: currentFromDataEmployee.value.firstName, firstName: '',
namePrefix: currentFromDataEmployee.value.namePrefix, namePrefix: '',
number: '', number: '',
}); });
@ -1550,7 +1357,6 @@ export const useEmployeeForm = defineStore('form-employee', () => {
expireDate: new Date(), expireDate: new Date(),
remark: undefined, remark: undefined,
workerType: '', workerType: '',
reportDate: null,
number: '', number: '',
}); });
@ -1590,92 +1396,10 @@ export const useEmployeeForm = defineStore('form-employee', () => {
(currentFromDataEmployee.value.employeeWork?.length || 0) - 1; (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 { return {
refreshImageState,
statusEmployeeCreate,
onCreateImageList,
state, state,
currentFromDataEmployee, currentFromDataEmployee,
resetEmployeeData, resetEmployeeData,
addPassport, addPassport,
addVisa, addVisa,
addCheckup, addCheckup,
@ -1699,9 +1423,5 @@ export const useEmployeeForm = defineStore('form-employee', () => {
employeeFilterOwnerBranch, employeeFilterOwnerBranch,
isFormDataDifferent, isFormDataDifferent,
employeeFormUndo,
employeeConfirmUnsave,
deleteEmployeeById,
}; };
}); });

View file

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

View file

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

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