Compare commits

..

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

196 changed files with 9815 additions and 16666 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

@ -183,15 +183,15 @@
"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 +207,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",
@ -1296,44 +1281,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

@ -27,7 +27,7 @@ export const i18n = createI18n<
MessageLanguages, MessageLanguages,
false false
>({ >({
locale: 'tha', locale: Lang.Thai,
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,75 @@ 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 === 'mr') {
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 +112,7 @@ watch(
() => gender.value, () => gender.value,
() => { () => {
if (props.readonly) return; if (props.readonly) return;
matchPreFixName(); matPreFixName();
}, },
); );
</script> </script>
@ -96,19 +152,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 +196,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 +231,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 +289,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 +328,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 +353,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 +458,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

@ -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>[]>([]);
@ -181,30 +177,6 @@ watch(
}, },
); );
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,
@ -105,12 +97,6 @@ onMounted(() => {
'label', 'label',
); );
visaIssueCountryFilter = selectFilterOptionRefMod(
ref(optionStore.globalOption?.nationality),
visaIssueCountryOptions,
'label',
);
workerTypeFilter = selectFilterOptionRefMod( workerTypeFilter = selectFilterOptionRefMod(
ref(optionStore.globalOption?.workerType), ref(optionStore.globalOption?.workerType),
workerTypeOptions, workerTypeOptions,
@ -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,
@ -23,13 +22,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 +46,6 @@ defineProps<{
employeeOwnerOption?: CustomerBranch[]; employeeOwnerOption?: CustomerBranch[];
prefixId: string; prefixId: string;
showBtnSave?: boolean; showBtnSave?: boolean;
disableCustomerSelect?: boolean;
}>(); }>();
defineEmits<{ defineEmits<{
@ -117,18 +108,213 @@ 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') }}
{{
formatAddress({
address: scope.opt.address,
addressEN: scope.opt.addressEN,
moo: scope.opt.moo,
mooEN: scope.opt.mooEN,
soi: scope.opt.soi,
soiEN: scope.opt.soiEN,
street: scope.opt.street,
streetEN: scope.opt.streetEN,
subDistrict: scope.opt.subDistrict,
district: scope.opt.district,
province: scope.opt.province,
en: $i18n.locale === 'eng',
})
}}
{{ 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') }}
{{
formatAddress({
address: scope.opt.address,
addressEN: scope.opt.addressEN,
moo: scope.opt.moo,
mooEN: scope.opt.mooEN,
soi: scope.opt.soi,
soiEN: scope.opt.soiEN,
street: scope.opt.street,
streetEN: scope.opt.streetEN,
subDistrict: scope.opt.subDistrict,
district: scope.opt.district,
province: scope.opt.province,
en: $i18n.locale === 'eng',
})
}}
{{ scope.opt.subDistrict?.zipCode || '' }}
<q-tooltip v-if="scope.opt.customer && scope.opt.province">
{{ $t('customerBranch.form.title') }}:
{{ $t('general.address') }}
{{
formatAddress({
address: scope.opt.address,
addressEN: scope.opt.addressEN,
moo: scope.opt.moo,
mooEN: scope.opt.mooEN,
soi: scope.opt.soi,
soiEN: scope.opt.soiEN,
street: scope.opt.street,
streetEN: scope.opt.streetEN,
subDistrict: scope.opt.subDistrict,
district: scope.opt.district,
province: scope.opt.province,
en: $i18n.locale === 'eng',
})
}}
{{ 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

@ -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',
@ -155,13 +154,6 @@ export default {
draw: 'Draw', draw: 'Draw',
newUpload: 'New Upload', newUpload: 'New Upload',
nativeLanguage: '{msg} Native Language', 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 +196,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 +248,7 @@ export default {
manual: { manual: {
title: 'Manual', title: 'Manual',
usage: 'Usage', usage: 'การใช้งาน',
troubleshooting: 'Troubleshooting',
}, },
}, },
@ -341,7 +330,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 +377,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 +398,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 +449,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 +460,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 +473,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 +486,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 +511,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 +519,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 +557,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 +611,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 +761,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 +795,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 +913,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 +934,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 +999,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 +1058,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 +1102,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.',
@ -1234,9 +1209,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 +1478,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}',
@ -155,13 +154,6 @@ export default {
draw: 'วาด', draw: 'วาด',
newUpload: 'อัปโหลดใหม่', newUpload: 'อัปโหลดใหม่',
nativeLanguage: '{msg} ภาษาต้นทาง', nativeLanguage: '{msg} ภาษาต้นทาง',
copy: 'คัดลอก',
paste: 'วาง',
period: 'ช่วงเวลา',
documentStatus: 'สถานะเอกสาร',
advanceSearch: 'ค้นหาขั้นสูง',
totalPeople: '{meg} คน',
price: 'ราคา {price} บาท',
}, },
menu: { menu: {
@ -204,14 +196,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 +249,6 @@ export default {
manual: { manual: {
title: 'คู่มือ', title: 'คู่มือ',
usage: 'การใช้งาน', usage: 'การใช้งาน',
troubleshooting: 'การแก้ปัญหา',
}, },
}, },
@ -338,7 +327,7 @@ export default {
letterAndNumOnly: 'โปรดใช้เฉพาะ _ ตัวอักษรภาษาอังกฤษและตัวเลขเท่านั้น', letterAndNumOnly: 'โปรดใช้เฉพาะ _ ตัวอักษรภาษาอังกฤษและตัวเลขเท่านั้น',
numOnly: 'โปรดใช้เฉพาะตัวเลขเท่านั้น', numOnly: 'โปรดใช้เฉพาะตัวเลขเท่านั้น',
requireLength: 'กรุณากรอกให้ครบ {msg} หลัก', requireLength: 'กรุณากรอกให้ครบ {msg} หลัก',
branchNameField: "โปรดใช้ตัวอักษร ตัวเลข หรือ . , - ' ( ) & เท่านั้น", branchNameField: "โปรดใช้ตัวอักษร ตัวเลข หรือ . , - ' & เท่านั้น",
branchNameENField: branchNameENField:
"โปรดใช้ตัวอักษรภาษาอังกฤษ ตัวเลข หรือ . , - ' & เท่านั้น", "โปรดใช้ตัวอักษรภาษาอังกฤษ ตัวเลข หรือ . , - ' & เท่านั้น",
passportFormat: 'กรุณากรอกหมายเลขพาสปอร์ตให้ถูกต้องตามรูปแบบ', passportFormat: 'กรุณากรอกหมายเลขพาสปอร์ตให้ถูกต้องตามรูปแบบ',
@ -467,13 +456,6 @@ export default {
citizenId: 'เลขที่บัตรประชาชน', citizenId: 'เลขที่บัตรประชาชน',
citizenIssue: 'วันที่ออกบัตร', citizenIssue: 'วันที่ออกบัตร',
citizenExpire: 'วันที่หมดอายุ', citizenExpire: 'วันที่หมดอายุ',
agencyStatus: 'สถานะเอเจนซี่',
normal: 'ปกติ',
canceled: 'ยกเลิก',
blacklist: 'แบล็คลิสต์',
contactName: 'ชื่อผู้ติดต่อ',
contactTel: 'เบอร์โทรศัพท์ผู้ติดต่อ',
addressForeign: 'ใช้ที่อยู่ต่างประเทศ',
}, },
}, },
customer: { customer: {
@ -500,16 +482,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 +574,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 +607,7 @@ export default {
placeOfBirth: 'สถานที่เกิด', placeOfBirth: 'สถานที่เกิด',
countryOfbirth: 'ประเทศที่เกิด', countryOfbirth: 'ประเทศที่เกิด',
issueCountry: 'ประเทศที่ออก', issueCountry: 'ประเทศที่ออก',
entryCount: 'จำนวนวันที่เข้าประเทศ', entryCount: 'จำนวนที่เข้าประเทศ',
employerSelect: { employerSelect: {
branchName: 'ชื่อสาขา', branchName: 'ชื่อสาขา',
customerName: 'ชื่อนายจ้าง', customerName: 'ชื่อนายจ้าง',
@ -654,7 +635,7 @@ export default {
permitIssuedAt: 'สถานที่ออกใบอนุญาต', permitIssuedAt: 'สถานที่ออกใบอนุญาต',
permitIssueDate: 'วันที่ออกใบอนุญาตทำงาน', permitIssueDate: 'วันที่ออกใบอนุญาตทำงาน',
permitExpireDate: 'วันที่หมดอายุใบอนุญาตทำงาน', permitExpireDate: 'วันที่หมดอายุใบอนุญาตทำงาน',
identityNo: 'เลขประจำตัวคนต่างด้าว (13 หลัก)', identityNo: 'เลขประจำตัวคนต่างด้าว (13หลัก)',
}, },
formFamily: { formFamily: {
citizenId: citizenId:
@ -772,13 +753,10 @@ export default {
}, },
quotation: { quotation: {
ownOnly: 'เห็นเฉพาะใบเสนอราคาของตัวเอง',
quotationDate: 'วันที่ใบเสนอราคา', quotationDate: 'วันที่ใบเสนอราคา',
seller: 'ผู้ขาย', seller: 'ผู้ขาย',
paymentChannels: 'ช่องทางชำระเงิน', paymentChannels: 'ช่องทางชำระเงิน',
channelsThat: 'ช่องทางที่', channelsThat: 'ช่องทางที่',
refNo: 'เลขที่อ้างอิง',
bankAccount: 'บัญชีธนาคาร',
bankAccountNumber: 'เลขบัญชีธนาคาร', bankAccountNumber: 'เลขบัญชีธนาคาร',
bankAccountName: 'ชื่อบัญชี', bankAccountName: 'ชื่อบัญชี',
inTheNameOf: 'ในนาม', inTheNameOf: 'ในนาม',
@ -803,7 +781,7 @@ export default {
branch: 'สาขาที่ออกใบเสนอราคา', branch: 'สาขาที่ออกใบเสนอราคา',
branchVirtual: 'จุดรับบริการที่ออกใบเสนอราคา', branchVirtual: 'จุดรับบริการที่ออกใบเสนอราคา',
customer: 'ลูกค้า', customer: 'ลูกค้า',
newCustomer: 'แรงงานใหม่', newCustomer: 'ลูกค้าใหม่',
employeeList: 'รายชื่อแรงงาน', employeeList: 'รายชื่อแรงงาน',
employee: 'แรงงาน', employee: 'แรงงาน',
employeeName: 'ชื่อ-นามสกุล แรงงาน', employeeName: 'ชื่อ-นามสกุล แรงงาน',
@ -926,10 +904,6 @@ export default {
code: 'รหัสหน่วยงาน', code: 'รหัสหน่วยงาน',
group: 'กลุ่มหน่วยงาน', group: 'กลุ่มหน่วยงาน',
name: 'ชื่อหน่วยงาน', name: 'ชื่อหน่วยงาน',
contactName: 'ชื่อผู้ติดต่อ',
contactTel: 'เบอร์โทรผู้ติดต่อ',
bankInfo: 'ข้อมูลธนาคาร',
attachment: 'เอกสารเพิ่มเติม',
}, },
requestList: { requestList: {
@ -951,7 +925,6 @@ export default {
nonLocalEmployee: 'พนักงานนอกพื้นที่', nonLocalEmployee: 'พนักงานนอกพื้นที่',
noWorkflowTemplate: 'คุณไม่ได้เลือกแม่แบบขั้นตอนการทำงาน', noWorkflowTemplate: 'คุณไม่ได้เลือกแม่แบบขั้นตอนการทำงาน',
salesRepresentative: 'พนักงานขาย', salesRepresentative: 'พนักงานขาย',
dataOffice: 'สำนักงานเขตจัดหางาน',
ref: 'อ้างอิง', ref: 'อ้างอิง',
action: { action: {
title: 'จัดการ', title: 'จัดการ',
@ -1070,9 +1043,6 @@ export default {
confirmDebitNoteAccept: 'ยืนยันการตอบรับใบเพิ่มหนี้', confirmDebitNoteAccept: 'ยืนยันการตอบรับใบเพิ่มหนี้',
}, },
message: { message: {
copy: 'คัดลอก',
warningPaste: 'คุณต้องการที่จะเเทนที่ข้อมูลที่คัดลอกมาใหม่ใช่หรือไม่',
warningCopyEmpty: 'คุณยังไม่ได้คัดลอกข้อมูล',
quotationAccept: 'เมื่อตอบรับเเล้วจะไม่สามารถแก้ไขได้อีก', quotationAccept: 'เมื่อตอบรับเเล้วจะไม่สามารถแก้ไขได้อีก',
beingUse: '"{msg}" มีการใช้งานอยู่', beingUse: '"{msg}" มีการใช้งานอยู่',
incompleteDataEntry: 'กรอกข้อมูลไม่ครบในหน้า {tap}', incompleteDataEntry: 'กรอกข้อมูลไม่ครบในหน้า {tap}',
@ -1220,8 +1190,6 @@ export default {
'มีงานหนึ่งงานหรือมากกว่าที่ไม่อยู่ในสถานะรอดำเนินการ', 'มีงานหนึ่งงานหรือมากกว่าที่ไม่อยู่ในสถานะรอดำเนินการ',
reqNotMet: 'ไม่ตรงกัน', reqNotMet: 'ไม่ตรงกัน',
systemError: 'ระบบเกิดข้อผิดพลาด', systemError: 'ระบบเกิดข้อผิดพลาด',
taskOrderInvalid: 'โปรดเลือก สินค้าเเละสาขา',
flowAccountProductIdNotFound: 'ไม่พบสินค้าใน flow account',
}, },
}, },
@ -1493,26 +1461,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) {
@ -1398,10 +1217,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 +1282,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,
); );
@ -1550,7 +1369,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 +1408,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 +1435,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"

View file

@ -3,7 +3,7 @@ import { nextTick, ref, watch, reactive } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { onMounted } from 'vue'; import { onMounted } from 'vue';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import { useQuasar, type QTableProps } from 'quasar'; import { QSelect, useQuasar, type QTableProps } from 'quasar';
import DialogProperties from 'src/components/dialog/DialogProperties.vue'; import DialogProperties from 'src/components/dialog/DialogProperties.vue';
import ProductCardComponent from 'components/04_product-service/ProductCardComponent.vue'; import ProductCardComponent from 'components/04_product-service/ProductCardComponent.vue';
@ -33,7 +33,6 @@ import {
SaveButton, SaveButton,
UndoButton, UndoButton,
ToggleButton, ToggleButton,
ImportButton,
} from 'components/button'; } from 'components/button';
import TableProduct from 'src/components/04_product-service/TableProduct.vue'; import TableProduct from 'src/components/04_product-service/TableProduct.vue';
import PaginationPageSize from 'src/components/PaginationPageSize.vue'; import PaginationPageSize from 'src/components/PaginationPageSize.vue';
@ -41,7 +40,7 @@ import PaginationPageSize from 'src/components/PaginationPageSize.vue';
import useFlowStore from 'stores/flow'; import useFlowStore from 'stores/flow';
import { dateFormat } from 'src/utils/datetime'; import { dateFormat } from 'src/utils/datetime';
import { formatNumberDecimal, isRoleInclude, canAccess } from 'stores/utils'; import { formatNumberDecimal, isRoleInclude } from 'stores/utils';
const { getWorkflowTemplate } = useWorkflowTemplate(); const { getWorkflowTemplate } = useWorkflowTemplate();
import { Status } from 'stores/types'; import { Status } from 'stores/types';
@ -60,7 +59,6 @@ import {
ServiceById, ServiceById,
WorkItems, WorkItems,
Attributes, Attributes,
WorkCreate,
} from 'stores/product-service/types'; } from 'stores/product-service/types';
import { computed } from 'vue'; import { computed } from 'vue';
import { import {
@ -69,8 +67,6 @@ 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 { deepEquals } from 'src/utils/arr'; import { deepEquals } from 'src/utils/arr';
import { toRaw } from 'vue';
import AdvanceSearch from 'src/components/shared/AdvanceSearch.vue';
const flowStore = useFlowStore(); const flowStore = useFlowStore();
const navigatorStore = useNavigator(); const navigatorStore = useNavigator();
@ -100,15 +96,10 @@ const {
createWork, createWork,
editWork, editWork,
deleteWork, deleteWork,
importProduct,
productExport,
} = productServiceStore; } = productServiceStore;
const { workNameItems } = storeToRefs(productServiceStore); const { workNameItems } = storeToRefs(productServiceStore);
const allStat = ref<{ mode: string; count: number }[]>([]); const allStat = ref<{ mode: string; count: number }[]>([]);
const stat = ref< const stat = ref<
{ {
icon: string; icon: string;
@ -145,29 +136,33 @@ const { t } = useI18n();
const baseUrl = ref<string>(import.meta.env.VITE_API_BASE_URL); const baseUrl = ref<string>(import.meta.env.VITE_API_BASE_URL);
const priceDisplay = computed(() => ({ const priceDisplay = computed(() => ({
// price: !isRoleInclude(['sale_agent']), price: !isRoleInclude(['sale_agent']),
price: true,
agentPrice: isRoleInclude([ agentPrice: isRoleInclude([
'system',
'head_of_admin',
'admin', 'admin',
'executive', 'head_of_admin',
'accountant',
'head_of_sale', 'head_of_sale',
'system',
'owner',
'accountant',
'sale_agent',
]), ]),
serviceCharge: isRoleInclude([ serviceCharge: isRoleInclude([
'system',
'head_of_admin',
'admin', 'admin',
'executive', 'head_of_admin',
'system',
'owner',
'accountant', 'accountant',
]), ]),
})); }));
const actionDisplay = computed(() => canAccess('product', 'edit')); const actionDisplay = computed(() =>
isRoleInclude(['admin', 'head_of_admin', 'system', 'owner', 'accountant']),
);
const splitterModel = computed(() => const splitterModel = computed(() =>
$q.screen.lt.md ? (productMode.value !== 'group' ? 0 : 100) : 25, $q.screen.lt.md ? (productMode.value !== 'group' ? 0 : 100) : 25,
); );
const refFilterGroup = ref<InstanceType<typeof QSelect>>();
const refFilterProductService = ref<InstanceType<typeof QSelect>>();
const holdDialog = ref(false); const holdDialog = ref(false);
const imageDialog = ref(false); const imageDialog = ref(false);
const currentNode = ref<ProductGroup & { type: string }>(); const currentNode = ref<ProductGroup & { type: string }>();
@ -525,7 +520,6 @@ const currentStatusGroupType = ref<Status>('CREATED');
const currentIdGroupType = ref(''); const currentIdGroupType = ref('');
const currentStatus = ref<Status | 'All'>('All'); const currentStatus = ref<Status | 'All'>('All');
const searchDate = ref<string[]>([]);
// img // img
const isImageEdit = ref<boolean>(false); const isImageEdit = ref<boolean>(false);
@ -621,8 +615,6 @@ async function fetchListGroups(mobileFetch?: boolean) {
: currentStatus.value === 'ACTIVE' : currentStatus.value === 'ACTIVE'
? 'ACTIVE' ? 'ACTIVE'
: 'INACTIVE', : 'INACTIVE',
startDate: searchDate.value[0],
endDate: searchDate.value[1],
}); });
if (res) { if (res) {
@ -683,8 +675,6 @@ async function fetchListOfProduct(mobileFetch?: boolean) {
? 'ACTIVE' ? 'ACTIVE'
: undefined, : undefined,
productGroupId: currentIdGroup.value, productGroupId: currentIdGroup.value,
startDate: searchDate.value[0],
endDate: searchDate.value[1],
}); });
if (res) { if (res) {
@ -730,8 +720,6 @@ async function fetchListOfService(mobileFetch?: boolean) {
? 'ACTIVE' ? 'ACTIVE'
: undefined, : undefined,
productGroupId: currentIdGroup.value, productGroupId: currentIdGroup.value,
startDate: searchDate.value[0],
endDate: searchDate.value[1],
}); });
if (res) { if (res) {
@ -1171,7 +1159,6 @@ function clearFormService() {
profileSubmit.value = false; profileSubmit.value = false;
imageProduct.value = undefined; imageProduct.value = undefined;
profileFileImg.value = null; profileFileImg.value = null;
serviceTab.value = 1;
} }
function sameFormService() { function sameFormService() {
@ -1398,7 +1385,6 @@ function submitAddWorkProduct() {
if (!s.hasOwnProperty('productsId')) { if (!s.hasOwnProperty('productsId')) {
s.productsId = []; s.productsId = [];
} }
if (s.productsId.length === 0) return;
s.productsId.push(i.id); s.productsId.push(i.id);
}, },
); );
@ -1451,11 +1437,17 @@ function confirmDeleteWork(id: string, noDialog?: boolean) {
} }
} }
function triggerConfirmCloseWorkName() { function triggerConfirmCloseWork() {
dialogWarningClose(t, { dialogWarningClose(t, {
message: t('dialog.message.warningClose'), message: t('dialog.message.warningClose'),
action: () => { action: () => {
manageWorkNameDialog.value = false; manageWorkNameDialog.value = false;
if (workNameItems.value[workNameItems.value.length - 1].name === '') {
confirmDeleteWork(
workNameItems.value[workNameItems.value.length - 1].id,
true,
);
}
}, },
cancel: () => {}, cancel: () => {},
}); });
@ -1598,7 +1590,6 @@ async function enterNext(type: 'service' | 'product') {
inputSearchProductAndService.value = ''; inputSearchProductAndService.value = '';
currentStatus.value = 'All'; currentStatus.value = 'All';
filterStat.value = []; filterStat.value = [];
searchDate.value = [];
if ( if (
expandedTree.value.length > 1 && expandedTree.value.length > 1 &&
@ -1754,7 +1745,7 @@ watch(currentStatus, async () => {
flowStore.rotate(); flowStore.rotate();
}); });
watch([inputSearch, () => searchDate.value], async () => { watch(inputSearch, async () => {
if (productMode.value === 'group') { if (productMode.value === 'group') {
productGroup.value = []; productGroup.value = [];
currentPageGroup.value = 1; currentPageGroup.value = 1;
@ -1763,7 +1754,7 @@ watch([inputSearch, () => searchDate.value], async () => {
} }
}); });
watch([inputSearchProductAndService, () => searchDate.value], async () => { watch(inputSearchProductAndService, async () => {
product.value = []; product.value = [];
service.value = []; service.value = [];
currentPageServiceAndProduct.value = 1; currentPageServiceAndProduct.value = 1;
@ -1840,84 +1831,6 @@ function handleSubmitSameWorkflow() {
); );
} }
async function copy(id: string) {
{
const res = await fetchListServiceById(id);
if (res) {
formService.value = {
code: res.code.slice(0, -3),
name: res.name,
detail: res.detail,
attributes: res.attributes,
work: res.work.map((v) => ({
id: v.id,
name: v.name,
attributes: v.attributes,
product: v.productOnWork.map((productOnWorkItem) => ({
id: productOnWorkItem.product.id,
installmentNo: productOnWorkItem.installmentNo,
stepCount: productOnWorkItem.stepCount,
})),
})),
status: res.status,
productGroupId: res.productGroupId,
selectedImage: res.selectedImage,
installments: res.installments,
};
workItems.value = res.work.map((item) => {
return {
id: item.id,
name: item.name,
attributes: item.attributes,
product: item.productOnWork.map((productOnWorkItem) => {
return {
...productOnWorkItem.product,
nameEn: productOnWorkItem.product.name,
installmentNo: productOnWorkItem.installmentNo,
};
}),
};
});
}
}
dialogService.value = true;
}
function addWorkName(data: { name: string; order: number }) {
workNameItems.value.push({ id: '', name: data.name, isEdit: true });
}
async function submitWorkName(
workId: string,
data: Partial<WorkCreate & { status: string }>,
) {
if (workNameItems.value.length === 0) return;
if (!workId) await createWork({ ...data, order: 1 });
else await editWork(workId, data);
}
async function triggerExport() {
productExport({
pageSize: 100_000,
productGroupId: currentIdGroup.value,
query: !!inputSearchProductAndService.value
? inputSearchProductAndService.value
: undefined,
status:
currentStatus.value === 'INACTIVE'
? 'INACTIVE'
: currentStatus.value === 'ACTIVE'
? 'ACTIVE'
: undefined,
startDate: searchDate.value[0] ? new Date(searchDate.value[0]) : undefined,
endDate: searchDate.value[1] ? new Date(searchDate.value[1]) : undefined,
});
}
watch( watch(
() => formService.value.attributes.workflowId, () => formService.value.attributes.workflowId,
async (a, b) => { async (a, b) => {
@ -2035,34 +1948,19 @@ watch(
<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">
<AdvanceSearch <q-separator vertical />
v-model="searchDate" <q-btn
:active="$q.screen.lt.md && currentStatus !== 'All'" icon="mdi-filter-variant"
> unelevated
<div class="q-mt-sm text-weight-medium"> class="q-ml-sm"
{{ $t('general.status') }} padding="4px"
</div> size="sm"
<q-select rounded
v-model="currentStatus" @click="refFilterGroup?.showPopup"
for="select-status"
outlined
dense
option-value="value"
option-label="label"
map-options
emit-value
:options="[
{ label: $t('general.all'), value: 'All' },
{ label: $t('general.active'), value: 'ACTIVE' },
{
label: $t('general.inactive'),
value: 'INACTIVE',
},
]"
/> />
</AdvanceSearch> </span>
</template> </template>
</q-input> </q-input>
</div> </div>
@ -2217,43 +2115,26 @@ watch(
<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">
<AdvanceSearch <q-separator vertical />
v-model="searchDate" <q-btn
:active="$q.screen.lt.md && currentStatus !== '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="refFilterGroup?.showPopup"
</div>
<q-select
v-if="$q.screen.lt.md"
v-model="currentStatus"
for="select-status"
outlined
dense
option-value="value"
option-label="label"
map-options
emit-value
:options="[
{ label: $t('general.all'), value: 'All' },
{ label: $t('general.active'), value: 'ACTIVE' },
{
label: $t('general.inactive'),
value: 'INACTIVE',
},
]"
/> />
</AdvanceSearch> </span>
</template> </template>
</q-input> </q-input>
<div class="row col-md-6" style="white-space: nowrap"> <div class="row col-md-6" style="white-space: nowrap">
<q-select <q-select
v-show="$q.screen.gt.sm" v-show="$q.screen.gt.sm"
ref="refFilterGroup"
v-model="currentStatus" v-model="currentStatus"
for="select-status" for="select-status"
outlined outlined
@ -2274,6 +2155,7 @@ watch(
}, },
]" ]"
></q-select> ></q-select>
<q-select <q-select
v-if="modeView === false" v-if="modeView === false"
id="select-field" id="select-field"
@ -2296,6 +2178,7 @@ watch(
multiple multiple
dense dense
/> />
<q-btn-toggle <q-btn-toggle
v-model="modeView" v-model="modeView"
id="btn-mode" id="btn-mode"
@ -2485,10 +2368,7 @@ watch(
) )
" "
> >
{{ {{ props.row.detail || '-' }}
props.row.detail.replace(/<\/?[^>]+(>|$)/g, '') ||
'-'
}}
</q-td> </q-td>
<q-td <q-td
@ -2538,7 +2418,6 @@ watch(
/> />
<KebabAction <KebabAction
v-if="actionDisplay"
:disable-delete="props.row.status !== 'CREATED'" :disable-delete="props.row.status !== 'CREATED'"
:status="props.row.status" :status="props.row.status"
:id-name="props.row.name" :id-name="props.row.name"
@ -2681,15 +2560,7 @@ watch(
{{ $t('general.recordPerPage') }} {{ $t('general.recordPerPage') }}
</div> </div>
<div> <div>
<PaginationPageSize <PaginationPageSize v-model="pageSizeGroup" />
v-model="pageSizeGroup"
:fetch-data="
async () => {
await fetchListGroups();
flowStore.rotate();
}
"
/>
</div> </div>
</div> </div>
</div> </div>
@ -2747,72 +2618,26 @@ watch(
<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">
<AdvanceSearch <q-separator vertical />
v-model="searchDate" <q-btn
:active="$q.screen.lt.md && currentStatus !== '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="refFilterProductService?.showPopup"
</div>
<q-select
v-if="$q.screen.lt.md"
:for="'field-select-status'"
v-model="currentStatus"
outlined
dense
option-value="value"
option-label="label"
map-options
emit-value
:options="[
{ label: $t('general.all'), value: 'All' },
{ label: $t('general.active'), value: 'ACTIVE' },
{
label: $t('general.inactive'),
value: 'INACTIVE',
},
]"
@update:model-value="fetchStatus()"
/> />
</AdvanceSearch> </span>
</template> </template>
</q-input> </q-input>
<div
class="flex q-mr-auto q-pl-sm"
v-if="productAndServiceTab === 'product'"
>
<input ref="fileImport" type="file" hidden />
<ImportButton
type="file"
import-file
icon-only
@file-selected="
(file) => {
importProduct(
currentIdGroup,
file,
async () => await fetchListOfProduct(),
);
}
"
/>
<SaveButton
icon-only
:icon="'material-symbols:download'"
color="var(--info-bg)"
@click.stop="triggerExport()"
/>
</div>
<div class="row col-md-6" style="white-space: nowrap"> <div class="row col-md-6" style="white-space: nowrap">
<q-select <q-select
v-show="$q.screen.gt.sm" v-show="$q.screen.gt.sm"
ref="refFilterProductService"
:for="'field-select-status'" :for="'field-select-status'"
v-model="currentStatus" v-model="currentStatus"
outlined outlined
@ -2834,6 +2659,7 @@ watch(
]" ]"
@update:model-value="fetchStatus()" @update:model-value="fetchStatus()"
></q-select> ></q-select>
<q-select <q-select
v-if="modeView === false" v-if="modeView === false"
:hide-dropdown-icon="$q.screen.lt.sm" :hide-dropdown-icon="$q.screen.lt.sm"
@ -2868,6 +2694,7 @@ watch(
multiple multiple
dense dense
/> />
<q-btn-toggle <q-btn-toggle
v-model="modeView" v-model="modeView"
id="btn-mode" id="btn-mode"
@ -3300,15 +3127,8 @@ watch(
" "
/> />
<KebabAction <KebabAction
v-if="actionDisplay"
:use-copy="productAndServiceTab === 'service'"
:status="props.row.status" :status="props.row.status"
:id-name="props.row.name" :id-name="props.row.name"
@copy="
() => {
copy(props.row.id);
}
"
@view=" @view="
async () => { async () => {
if (props.row.type === 'product') { if (props.row.type === 'product') {
@ -3455,15 +3275,7 @@ watch(
{{ $t('general.recordPerPage') }} {{ $t('general.recordPerPage') }}
</div> </div>
<div> <div>
<PaginationPageSize <PaginationPageSize v-model="pageSizeServiceAndProduct" />
v-model="pageSizeServiceAndProduct"
:fetch-data="
async () => {
await alternativeFetch();
flowStore.rotate();
}
"
/>
</div> </div>
</div> </div>
</div> </div>
@ -3595,7 +3407,7 @@ watch(
</div> </div>
<div <div
class="col-12 col-md-10" class="col-12 col-md-10"
id="group-create" id="customer-form-content"
:class="{ :class="{
'q-py-md q-pr-md ': $q.screen.gt.sm, 'q-py-md q-pr-md ': $q.screen.gt.sm,
'q-py-md q-px-lg': !$q.screen.gt.sm, 'q-py-md q-px-lg': !$q.screen.gt.sm,
@ -4114,7 +3926,7 @@ watch(
</div> </div>
<div <div
class="col-12 col-md-10" class="col-12 col-md-10"
id="product-create" id="customer-form-content"
:class="{ :class="{
'q-py-md q-pr-md ': $q.screen.gt.sm, 'q-py-md q-pr-md ': $q.screen.gt.sm,
'q-pa-sm': !$q.screen.gt.sm, 'q-pa-sm': !$q.screen.gt.sm,
@ -4340,7 +4152,7 @@ watch(
'q-py-md q-pr-md ': $q.screen.gt.sm, 'q-py-md q-pr-md ': $q.screen.gt.sm,
'q-pa-sm': !$q.screen.gt.sm, 'q-pa-sm': !$q.screen.gt.sm,
}" }"
id="product-info" id="customer-form-content"
style="height: 100%; max-height: 100%; overflow-y: auto" style="height: 100%; max-height: 100%; overflow-y: auto"
> >
<BasicInfoProduct <BasicInfoProduct
@ -4385,7 +4197,6 @@ watch(
<!-- add service --> <!-- add service -->
<DialogForm <DialogForm
v-if="dialogService"
hide-footer hide-footer
no-address no-address
no-app-box no-app-box
@ -4522,11 +4333,6 @@ watch(
sub: true, sub: true,
})) }))
" "
:active="{
background: 'hsla(var(--blue-6-hsl) / .2)',
foreground: 'var(--blue-6)',
}"
scroll-element="#service-create"
/> />
</div> </div>
<span <span
@ -4549,7 +4355,7 @@ watch(
</div> </div>
<div <div
class="col-12 col-md-10" class="col-12 col-md-10"
id="service-create" id="customer-form-content"
:class="{ :class="{
'q-py-md q-pr-md ': $q.screen.gt.sm, 'q-py-md q-pr-md ': $q.screen.gt.sm,
'q-pa-sm': !$q.screen.gt.sm, 'q-pa-sm': !$q.screen.gt.sm,
@ -4616,7 +4422,6 @@ watch(
@click="serviceTreeView = false" @click="serviceTreeView = false"
/> />
</div> </div>
<SaveButton id="btn-info-basic-save" icon-only type="submit" /> <SaveButton id="btn-info-basic-save" icon-only type="submit" />
</div> </div>
@ -4730,7 +4535,7 @@ watch(
? workNameRef.isWorkNameEdit() ? workNameRef.isWorkNameEdit()
: false; : false;
if (isWorkNameEdit) { if (isWorkNameEdit) {
triggerConfirmCloseWorkName(); triggerConfirmCloseWork();
return true; return true;
} }
return false; return false;
@ -4742,16 +4547,15 @@ watch(
ref="workNameRef" ref="workNameRef"
v-model:name-list="workNameItems" v-model:name-list="workNameItems"
@delete="confirmDeleteWork" @delete="confirmDeleteWork"
@edit="submitWorkName" @edit="editWork"
@add="addWorkName" @add="createWork"
/> />
</div> </div>
</DialogForm> </DialogForm>
<!-- edit service edit package--> <!-- edit service -->
<!-- :edit="!(formDataProductService.status === 'INACTIVE')" --> <!-- :edit="!(formDataProductService.status === 'INACTIVE')" -->
<DialogForm <DialogForm
v-if="dialogServiceEdit"
hide-footer hide-footer
no-address no-address
height="95vh" height="95vh"
@ -5004,7 +4808,6 @@ watch(
background: 'hsla(var(--blue-6-hsl) / .2)', background: 'hsla(var(--blue-6-hsl) / .2)',
foreground: 'var(--blue-6)', foreground: 'var(--blue-6)',
}" }"
scroll-element="#service-info"
/> />
</div> </div>
<span <span
@ -5027,7 +4830,7 @@ watch(
</div> </div>
<div <div
class="col-12 col-md-10" class="col-12 col-md-10"
id="service-info" id="customer-form-content"
:class="{ :class="{
'q-py-md q-pr-md ': $q.screen.gt.sm, 'q-py-md q-pr-md ': $q.screen.gt.sm,
'q-pa-sm': !$q.screen.gt.sm, 'q-pa-sm': !$q.screen.gt.sm,

View file

@ -2,7 +2,7 @@
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import { onMounted, reactive, ref } from 'vue'; import { onMounted, reactive, ref } from 'vue';
import { QTableProps } from 'quasar'; import { QSelect, QTableProps } from 'quasar';
import { useNavigator } from 'src/stores/navigator'; import { useNavigator } from 'src/stores/navigator';
import { useProperty } from 'src/stores/property'; import { useProperty } from 'src/stores/property';
import { useQuasar } from 'quasar'; import { useQuasar } from 'quasar';
@ -14,10 +14,9 @@ import { FloatingActionButton, PaginationComponent } from 'src/components';
import PaginationPageSize from 'src/components/PaginationPageSize.vue'; import PaginationPageSize from 'src/components/PaginationPageSize.vue';
import PropertyDialog from './PropertyDialog.vue'; import PropertyDialog from './PropertyDialog.vue';
import { Property } from 'src/stores/property/types'; import { Property } from 'src/stores/property/types';
import { dialog, toCamelCase, canAccess } from 'src/stores/utils'; import { dialog, toCamelCase } from 'src/stores/utils';
import CreateButton from 'src/components/AddButton.vue'; import CreateButton from 'src/components/AddButton.vue';
import useOptionStore from 'stores/options'; import useOptionStore from 'stores/options';
import AdvanceSearch from 'src/components/shared/AdvanceSearch.vue';
const { t, locale } = useI18n(); const { t, locale } = useI18n();
const $q = useQuasar(); const $q = useQuasar();
@ -39,6 +38,7 @@ const formProperty = ref<Property>({
type: {}, type: {},
}); });
const statusFilter = ref<'all' | 'statusACTIVE' | 'statusINACTIVE'>('all'); const statusFilter = ref<'all' | 'statusACTIVE' | 'statusINACTIVE'>('all');
const refFilter = ref<InstanceType<typeof QSelect>>();
const fieldSelected = ref<('order' | 'name' | 'type')[]>([ const fieldSelected = ref<('order' | 'name' | 'type')[]>([
'order', 'order',
'name', 'name',
@ -118,7 +118,6 @@ const pageState = reactive({
addModal: false, addModal: false,
viewDrawer: false, viewDrawer: false,
isDrawerEdit: true, isDrawerEdit: true,
searchDate: [],
}); });
async function fetchPropertyList(mobileFetch?: boolean) { async function fetchPropertyList(mobileFetch?: boolean) {
@ -135,8 +134,6 @@ async function fetchPropertyList(mobileFetch?: boolean) {
: statusFilter.value === 'statusACTIVE' : statusFilter.value === 'statusACTIVE'
? 'ACTIVE' ? 'ACTIVE'
: 'INACTIVE', : 'INACTIVE',
startDate: pageState.searchDate[0],
endDate: pageState.searchDate[1],
}); });
if (res) { if (res) {
propertyData.value = propertyData.value =
@ -320,18 +317,14 @@ watch(
fetchPropertyList(); fetchPropertyList();
}, },
); );
watch( watch([() => pageState.inputSearch, propertyPageSize], () => {
[() => pageState.inputSearch, propertyPageSize, () => pageState.searchDate],
() => {
propertyData.value = []; propertyData.value = [];
propertyPage.value = 1; propertyPage.value = 1;
fetchPropertyList(); fetchPropertyList();
}, });
);
</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')"
@ -405,44 +398,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
@ -537,19 +512,11 @@ watch(
class="col surface-2 flex items-center justify-center" class="col surface-2 flex items-center justify-center"
> >
<NoData <NoData
v-if=" v-if="pageState.total !== 0"
pageState.total !== 0 ||
pageState.searchDate.length > 0 ||
!canAccess('workflow', 'edit')
"
:not-found="!!pageState.inputSearch" :not-found="!!pageState.inputSearch"
/> />
<CreateButton <CreateButton
v-if=" v-if="pageState.total === 0"
pageState.total === 0 &&
pageState.searchDate.length === 0 &&
canAccess('workflow', 'edit')
"
@click="triggerDialog('add')" @click="triggerDialog('add')"
label="general.add" label="general.add"
:i18n-args="{ text: $t('flow.title') }" :i18n-args="{ text: $t('flow.title') }"
@ -707,7 +674,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="
@ -825,7 +791,6 @@ watch(
" "
/> />
<KebabAction <KebabAction
v-if="canAccess('workflow', 'edit')"
:id-name="props.row.id" :id-name="props.row.id"
:status="props.row.status" :status="props.row.status"
@view=" @view="
@ -917,7 +882,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