Merge branch 'develop'
This commit is contained in:
commit
18844c70bc
80 changed files with 2772 additions and 869 deletions
|
|
@ -22,6 +22,7 @@
|
||||||
"apexcharts": "^4.5.0",
|
"apexcharts": "^4.5.0",
|
||||||
"axios": "^1.8.4",
|
"axios": "^1.8.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.6",
|
||||||
"markdown-it": "^14.1.0",
|
"markdown-it": "^14.1.0",
|
||||||
|
|
|
||||||
8
pnpm-lock.yaml
generated
8
pnpm-lock.yaml
generated
|
|
@ -29,6 +29,9 @@ importers:
|
||||||
cropperjs:
|
cropperjs:
|
||||||
specifier: ^1.6.2
|
specifier: ^1.6.2
|
||||||
version: 1.6.2
|
version: 1.6.2
|
||||||
|
dayjs:
|
||||||
|
specifier: ^1.11.13
|
||||||
|
version: 1.11.13
|
||||||
highlight.js:
|
highlight.js:
|
||||||
specifier: ^11.11.1
|
specifier: ^11.11.1
|
||||||
version: 11.11.1
|
version: 11.11.1
|
||||||
|
|
@ -1473,6 +1476,9 @@ packages:
|
||||||
date-fns@3.6.0:
|
date-fns@3.6.0:
|
||||||
resolution: {integrity: sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==}
|
resolution: {integrity: sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==}
|
||||||
|
|
||||||
|
dayjs@1.11.13:
|
||||||
|
resolution: {integrity: sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==}
|
||||||
|
|
||||||
de-indent@1.0.2:
|
de-indent@1.0.2:
|
||||||
resolution: {integrity: sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==}
|
resolution: {integrity: sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==}
|
||||||
|
|
||||||
|
|
@ -5272,6 +5278,8 @@ snapshots:
|
||||||
|
|
||||||
date-fns@3.6.0: {}
|
date-fns@3.6.0: {}
|
||||||
|
|
||||||
|
dayjs@1.11.13: {}
|
||||||
|
|
||||||
de-indent@1.0.2: {}
|
de-indent@1.0.2: {}
|
||||||
|
|
||||||
debug@2.6.9:
|
debug@2.6.9:
|
||||||
|
|
|
||||||
BIN
public/img-group.png
Normal file
BIN
public/img-group.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 77 KiB |
|
|
@ -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: 5173,
|
port: 5174,
|
||||||
},
|
},
|
||||||
framework: {
|
framework: {
|
||||||
config: {},
|
config: {},
|
||||||
|
|
|
||||||
|
|
@ -177,7 +177,7 @@ watch(
|
||||||
|
|
||||||
<div
|
<div
|
||||||
v-if="!single"
|
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
|
||||||
|
|
|
||||||
|
|
@ -29,19 +29,18 @@ 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 agencyFile = defineModel<File[]>('agencyFile');
|
const userFile = defineModel<File[]>('userFile');
|
||||||
const agencyFileList =
|
const userFileList =
|
||||||
defineModel<{ name: string; url: string }[]>('agencyFileList');
|
defineModel<{ name: string; url: string }[]>('userFileList');
|
||||||
const remark = defineModel<string | null | undefined>('remark');
|
const remark = defineModel<string | null | undefined>('remark');
|
||||||
const agencyStatus = defineModel<string | null | undefined>('agencyStatus');
|
const agencyStatus = defineModel<string | null | undefined>('agencyStatus');
|
||||||
|
|
||||||
const attachmentRef = ref();
|
const attachmentRef = ref();
|
||||||
const checkpointENOption = ref([]);
|
|
||||||
|
|
||||||
defineProps<{
|
defineProps<{
|
||||||
dense?: boolean;
|
dense?: boolean;
|
||||||
|
|
@ -71,18 +70,12 @@ 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) {
|
||||||
agencyFileList.value = result;
|
userFileList.value = result;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
cancel: () => {},
|
cancel: () => {},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(async () => {
|
|
||||||
const resultOption = await fetch('/option/option.json');
|
|
||||||
const rawOption = await resultOption.json();
|
|
||||||
checkpointENOption.value = rawOption.eng.border;
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div class="row col-12">
|
<div class="row col-12">
|
||||||
|
|
@ -140,11 +133,12 @@ onMounted(async () => {
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<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
|
||||||
|
|
@ -185,38 +179,27 @@ onMounted(async () => {
|
||||||
(v) => (typeof v === 'string' ? (sourceNationality = v) : '')
|
(v) => (typeof v === 'string' ? (sourceNationality = v) : '')
|
||||||
"
|
"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<SelectInput
|
<SelectInput
|
||||||
:model-value="readonly ? importNationality || '-' : importNationality"
|
v-model="importNationality"
|
||||||
id="input-import-nationality"
|
id="input-import-nationality"
|
||||||
for="input-import-nationality"
|
for="input-import-nationality"
|
||||||
:option="optionStore.globalOption.nationality"
|
:option="optionStore.globalOption.nationality"
|
||||||
class="col-md-3 col-6"
|
class="col-md-3 col-6"
|
||||||
:readonly
|
:readonly
|
||||||
|
multiple
|
||||||
|
:hideSelected="false"
|
||||||
clearable
|
clearable
|
||||||
|
fillInput
|
||||||
:label="$t('personnel.form.importNationality')"
|
:label="$t('personnel.form.importNationality')"
|
||||||
@update:model-value="
|
|
||||||
(v) => (typeof v === 'string' ? (importNationality = v) : '')
|
|
||||||
"
|
|
||||||
/>
|
|
||||||
<SelectInput
|
|
||||||
:model-value="readonly ? trainingPlace || '-' : trainingPlace"
|
|
||||||
id="select-trainig-place"
|
|
||||||
for="select-trainig-place"
|
|
||||||
:option="optionStore.globalOption.training"
|
|
||||||
class="col-md-6 col-12"
|
|
||||||
:readonly
|
|
||||||
:label="$t('personnel.form.trainingPlace')"
|
|
||||||
clearable
|
|
||||||
@update:model-value="
|
|
||||||
(v) => (typeof v === 'string' ? (trainingPlace = v) : '')
|
|
||||||
"
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<SelectInput
|
<SelectInput
|
||||||
:model-value="readonly ? checkpoint || '-' : checkpoint"
|
:model-value="readonly ? checkpoint || '-' : checkpoint"
|
||||||
id="select-checkpoint"
|
id="select-checkpoint"
|
||||||
for="select-checkpoint"
|
for="select-checkpoint"
|
||||||
:option="optionStore.globalOption.border"
|
:option="optionStore.globalOption.border"
|
||||||
class="col-6"
|
class="col-md-6 col-12"
|
||||||
:readonly
|
:readonly
|
||||||
:label="$t('personnel.form.checkpoint')"
|
:label="$t('personnel.form.checkpoint')"
|
||||||
clearable
|
clearable
|
||||||
|
|
@ -224,17 +207,18 @@ onMounted(async () => {
|
||||||
(v) => (typeof v === 'string' ? (checkpoint = v) : '')
|
(v) => (typeof v === 'string' ? (checkpoint = v) : '')
|
||||||
"
|
"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<SelectInput
|
<SelectInput
|
||||||
:model-value="readonly ? checkpoint || '-' : checkpoint"
|
:model-value="readonly ? trainingPlace || '-' : trainingPlace"
|
||||||
id="select-checkpoint-en"
|
id="select-trainig-place"
|
||||||
for="select-checkpoint-en"
|
for="select-trainig-place"
|
||||||
:option="checkpointENOption"
|
:option="optionStore.globalOption.training"
|
||||||
class="col-6"
|
class="col-md-8 col-12"
|
||||||
:readonly
|
:readonly
|
||||||
:label="$t('personnel.form.checkpointEN')"
|
:label="$t('personnel.form.trainingPlace')"
|
||||||
clearable
|
clearable
|
||||||
@update:model-value="
|
@update:model-value="
|
||||||
(v) => (typeof v === 'string' ? (checkpoint = v) : '')
|
(v) => (typeof v === 'string' ? (trainingPlace = v) : '')
|
||||||
"
|
"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|
@ -253,7 +237,7 @@ onMounted(async () => {
|
||||||
value: AgencyStatus.Blacklist,
|
value: AgencyStatus.Blacklist,
|
||||||
},
|
},
|
||||||
]"
|
]"
|
||||||
class="col-md-6 col-12"
|
class="col-md-4 col-12"
|
||||||
:readonly
|
:readonly
|
||||||
:label="$t('personnel.form.agencyStatus')"
|
:label="$t('personnel.form.agencyStatus')"
|
||||||
clearable
|
clearable
|
||||||
|
|
@ -275,76 +259,78 @@ onMounted(async () => {
|
||||||
"
|
"
|
||||||
@clear="remark = ''"
|
@clear="remark = ''"
|
||||||
/>
|
/>
|
||||||
<q-file
|
</div>
|
||||||
ref="attachmentRef"
|
|
||||||
for="input-attachment"
|
|
||||||
:dense="dense"
|
|
||||||
outlined
|
|
||||||
:readonly="readonly"
|
|
||||||
multiple
|
|
||||||
append
|
|
||||||
:label="$t('personnel.form.attachment')"
|
|
||||||
class="col"
|
|
||||||
v-model="agencyFile"
|
|
||||||
>
|
|
||||||
<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="agencyFileList && agencyFileList?.length > 0" class="col-12">
|
<q-file
|
||||||
<q-list bordered separator class="rounded" style="padding: 0">
|
v-if="userType"
|
||||||
<q-item
|
ref="attachmentRef"
|
||||||
id="attachment-file"
|
for="input-attachment"
|
||||||
for="attachment-file"
|
:dense="dense"
|
||||||
v-for="item in agencyFileList"
|
outlined
|
||||||
clickable
|
:readonly="readonly"
|
||||||
:key="item.url"
|
multiple
|
||||||
class="items-center row"
|
append
|
||||||
@click="() => openNewTab(item.url)"
|
:label="$t('personnel.form.attachment')"
|
||||||
>
|
class="col"
|
||||||
<q-item-section>
|
v-model="userFile"
|
||||||
<div class="row items-center justify-between">
|
>
|
||||||
<div class="col">
|
<template v-slot:prepend>
|
||||||
{{ item.name }}
|
<Icon
|
||||||
</div>
|
icon="material-symbols:attach-file"
|
||||||
<q-btn
|
width="20px"
|
||||||
id="delete-file"
|
style="color: var(--brand-1)"
|
||||||
v-if="!readonly && userId"
|
/>
|
||||||
rounded
|
</template>
|
||||||
flat
|
<template v-slot:file="file">
|
||||||
dense
|
<div class="row full-width items-center">
|
||||||
unelevated
|
<span class="col ellipsis">
|
||||||
size="md"
|
{{ file.file.name }}
|
||||||
icon="mdi-trash-can-outline"
|
</span>
|
||||||
class="app-text-negative"
|
<q-btn
|
||||||
@click.stop="deleteFile(item.name)"
|
dense
|
||||||
/>
|
rounded
|
||||||
|
flat
|
||||||
|
padding="2 2"
|
||||||
|
class="app-text-muted"
|
||||||
|
icon="mdi-close-circle"
|
||||||
|
@click.stop="attachmentRef.removeAtIndex(file.index)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</q-file>
|
||||||
|
|
||||||
|
<div v-if="userFileList && userFileList?.length > 0" class="col-12">
|
||||||
|
<q-list bordered separator class="rounded" style="padding: 0">
|
||||||
|
<q-item
|
||||||
|
id="attachment-file"
|
||||||
|
for="attachment-file"
|
||||||
|
v-for="item in userFileList"
|
||||||
|
clickable
|
||||||
|
:key="item.url"
|
||||||
|
class="items-center row"
|
||||||
|
@click="() => openNewTab(item.url)"
|
||||||
|
>
|
||||||
|
<q-item-section>
|
||||||
|
<div class="row items-center justify-between">
|
||||||
|
<div class="col">
|
||||||
|
{{ item.name }}
|
||||||
</div>
|
</div>
|
||||||
</q-item-section>
|
<q-btn
|
||||||
</q-item>
|
id="delete-file"
|
||||||
</q-list>
|
v-if="!readonly && userId"
|
||||||
</div>
|
rounded
|
||||||
|
flat
|
||||||
|
dense
|
||||||
|
unelevated
|
||||||
|
size="md"
|
||||||
|
icon="mdi-trash-can-outline"
|
||||||
|
class="app-text-negative"
|
||||||
|
@click.stop="deleteFile(item.name)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
</q-list>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,8 @@
|
||||||
<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 { ref, onMounted, watch } from 'vue';
|
import { watch } from 'vue';
|
||||||
import { capitalize } from 'vue';
|
import SelectInput from '../shared/SelectInput.vue';
|
||||||
import DatePicker from '../shared/DatePicker.vue';
|
import DatePicker from '../shared/DatePicker.vue';
|
||||||
|
|
||||||
const optionStore = useOptionStore();
|
const optionStore = useOptionStore();
|
||||||
|
|
@ -23,6 +21,8 @@ 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,94 +30,19 @@ 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;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const prefixNameOptions = ref<Record<string, unknown>[]>([]);
|
function matchPreFixName() {
|
||||||
let prefixNameFilter: (
|
|
||||||
value: string,
|
|
||||||
update: (callbackFn: () => void, afterFn?: (ref: QSelect) => void) => void,
|
|
||||||
) => void;
|
|
||||||
|
|
||||||
const prefixNameOptionsEn = ref<Record<string, unknown>[]>([]);
|
|
||||||
let prefixNameFilterEn: (
|
|
||||||
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',
|
|
||||||
);
|
|
||||||
|
|
||||||
prefixNameFilterEn = selectFilterOptionRefMod(
|
|
||||||
ref(optionStore.rawOption?.eng.prefix),
|
|
||||||
prefixNameOptionsEn,
|
|
||||||
'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',
|
|
||||||
);
|
|
||||||
|
|
||||||
prefixNameFilterEn = selectFilterOptionRefMod(
|
|
||||||
ref(optionStore.rawOption?.eng.prefix),
|
|
||||||
prefixNameOptionsEn,
|
|
||||||
'label',
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => prefixName.value,
|
() => prefixName.value,
|
||||||
(v) => {
|
(v) => {
|
||||||
|
|
@ -131,7 +56,7 @@ watch(
|
||||||
() => gender.value,
|
() => gender.value,
|
||||||
() => {
|
() => {
|
||||||
if (props.readonly) return;
|
if (props.readonly) return;
|
||||||
matPreFixName();
|
matchPreFixName();
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -171,41 +96,19 @@ 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)">
|
||||||
<q-select
|
<SelectInput
|
||||||
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
|
||||||
autocomplete="off"
|
:readonly
|
||||||
class="col-md-1 col-6"
|
:option="optionStore.globalOption?.prefix"
|
||||||
:dense="dense"
|
:id="`${prefixId}-select-prefix-name`"
|
||||||
:readonly="readonly"
|
|
||||||
:options="prefixNameOptions"
|
|
||||||
:for="`${prefixId}-select-prefix-name`"
|
:for="`${prefixId}-select-prefix-name`"
|
||||||
:label="$t('personnel.form.prefixName')"
|
:rules="
|
||||||
@filter="prefixNameFilter"
|
agency ? [] : [(val: string) => !!val || $t('form.error.required')]
|
||||||
:model-value="readonly ? prefixName || '-' : prefixName"
|
|
||||||
@update:model-value="
|
|
||||||
(v) => (typeof v === 'string' ? (prefixName = v) : '')
|
|
||||||
"
|
"
|
||||||
@clear="prefixName = ''"
|
:label="$t('personnel.form.prefixName')"
|
||||||
:rules="[(val: string) => !!val || $t('form.error.required')]"
|
class="col-md-1 col-6"
|
||||||
>
|
v-model="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`"
|
||||||
|
|
@ -217,7 +120,7 @@ watch(
|
||||||
:label="$t('personnel.form.firstName')"
|
:label="$t('personnel.form.firstName')"
|
||||||
v-model="firstName"
|
v-model="firstName"
|
||||||
:rules="
|
:rules="
|
||||||
employee
|
employee || agency
|
||||||
? []
|
? []
|
||||||
: [(val: string) => !!val || $t('form.error.required')]
|
: [(val: string) => !!val || $t('form.error.required')]
|
||||||
"
|
"
|
||||||
|
|
@ -255,41 +158,17 @@ watch(
|
||||||
class="col-12 row"
|
class="col-12 row"
|
||||||
style="display: flex; gap: var(--size-2)"
|
style="display: flex; gap: var(--size-2)"
|
||||||
>
|
>
|
||||||
<q-select
|
<SelectInput
|
||||||
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
|
||||||
autocomplete="off"
|
:readonly
|
||||||
class="col-md-1 col-6"
|
:option="optionStore.rawOption?.eng.prefix"
|
||||||
:dense="dense"
|
:id="`${prefixId}-select-prefix-name-en`"
|
||||||
:readonly="readonly"
|
|
||||||
:options="prefixNameOptionsEn"
|
|
||||||
:for="`${prefixId}-select-prefix-name-en`"
|
:for="`${prefixId}-select-prefix-name-en`"
|
||||||
label="Prefix"
|
|
||||||
@filter="prefixNameFilter"
|
|
||||||
:model-value="readonly ? prefixName || '-' : prefixName"
|
|
||||||
@update:model-value="
|
|
||||||
(v) => (typeof v === 'string' ? (prefixName = v) : '')
|
|
||||||
"
|
|
||||||
@clear="prefixName = ''"
|
|
||||||
:rules="[(val: string) => !!val || $t('form.error.required')]"
|
:rules="[(val: string) => !!val || $t('form.error.required')]"
|
||||||
>
|
:label="$t('personnel.form.prefixName')"
|
||||||
<template v-slot:no-option>
|
class="col-md-1 col-6"
|
||||||
<q-item>
|
v-model="prefixName"
|
||||||
<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-en`"
|
:for="`${prefixId}-input-first-name-en`"
|
||||||
|
|
@ -331,13 +210,7 @@ watch(
|
||||||
v-model="lastNameEN"
|
v-model="lastNameEN"
|
||||||
:rules="
|
:rules="
|
||||||
employee
|
employee
|
||||||
? [
|
? []
|
||||||
(val: string) => !!val || $t('form.error.required'),
|
|
||||||
(val: string) =>
|
|
||||||
!val ||
|
|
||||||
/^[A-Za-z\s]+$/.test(val) ||
|
|
||||||
$t('form.error.letterOnly'),
|
|
||||||
]
|
|
||||||
: [
|
: [
|
||||||
(val: string) =>
|
(val: string) =>
|
||||||
!val ||
|
!val ||
|
||||||
|
|
@ -405,39 +278,16 @@ watch(
|
||||||
</template>
|
</template>
|
||||||
</q-input>
|
</q-input>
|
||||||
|
|
||||||
<q-select
|
<SelectInput
|
||||||
v-if="!employee"
|
v-if="!employee"
|
||||||
outlined
|
:readonly
|
||||||
use-input
|
:option="optionStore.globalOption?.gender"
|
||||||
fill-input
|
:id="`${prefixId}-select-gender`"
|
||||||
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')"
|
||||||
@filter="genderFilter"
|
class="col-md-2 col-6"
|
||||||
:model-value="readonly ? gender || '-' : gender"
|
v-model="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"
|
||||||
|
|
@ -514,70 +364,67 @@ watch(
|
||||||
"
|
"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<q-select
|
<SelectInput
|
||||||
v-if="employee"
|
v-if="employee"
|
||||||
outlined
|
:readonly
|
||||||
clearable
|
:option="optionStore.globalOption?.gender"
|
||||||
use-input
|
:id="`${prefixId}-select-gender`"
|
||||||
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')"
|
||||||
@filter="genderFilter"
|
|
||||||
>
|
|
||||||
<template v-slot:no-option>
|
|
||||||
<q-item>
|
|
||||||
<q-item-section class="text-grey">
|
|
||||||
{{ $t('general.noData') }}
|
|
||||||
</q-item-section>
|
|
||||||
</q-item>
|
|
||||||
</template>
|
|
||||||
</q-select>
|
|
||||||
<q-select
|
|
||||||
v-if="employee"
|
|
||||||
outlined
|
|
||||||
clearable
|
|
||||||
use-input
|
|
||||||
fill-input
|
|
||||||
emit-value
|
|
||||||
map-options
|
|
||||||
hide-selected
|
|
||||||
hide-bottom-space
|
|
||||||
autocomplete="off"
|
|
||||||
input-debounce="0"
|
|
||||||
option-label="label"
|
|
||||||
option-value="value"
|
|
||||||
v-model="nationality"
|
|
||||||
class="col-md-2 col-6"
|
class="col-md-2 col-6"
|
||||||
:dense="dense"
|
v-model="gender"
|
||||||
:readonly="readonly"
|
/>
|
||||||
:options="nationalityOptions"
|
<SelectInput
|
||||||
:hide-dropdown-icon="readonly"
|
v-if="employee"
|
||||||
|
:readonly
|
||||||
|
:option="optionStore.globalOption.nationality"
|
||||||
|
:id="`${prefixId}-select-nationality`"
|
||||||
:for="`${prefixId}-select-nationality`"
|
:for="`${prefixId}-select-nationality`"
|
||||||
:label="$t('general.nationality')"
|
:label="$t('general.nationality')"
|
||||||
@filter="nationalityFilter"
|
class="col-md-2 col-6"
|
||||||
|
v-model="nationality"
|
||||||
|
clearable
|
||||||
|
/>
|
||||||
|
|
||||||
|
<q-input
|
||||||
|
v-if="agency"
|
||||||
|
for="input-agencies-contact-name"
|
||||||
|
dense
|
||||||
|
outlined
|
||||||
|
:readonly="readonly"
|
||||||
|
hide-bottom-space
|
||||||
|
class="col-md-4 col-12"
|
||||||
|
:label="$t('personnel.form.contactName')"
|
||||||
|
:model-value="readonly ? contactName || '-' : contactName"
|
||||||
|
@update:model-value="
|
||||||
|
(v) => (typeof v === 'string' ? (contactName = v) : '')
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<q-input
|
||||||
|
v-if="agency"
|
||||||
|
for="input-agencies-contact-tel"
|
||||||
|
id="input-agencies-contact-tel"
|
||||||
|
dense
|
||||||
|
outlined
|
||||||
|
:readonly="readonly"
|
||||||
|
hide-bottom-space
|
||||||
|
class="col-md-4 col-12"
|
||||||
|
:label="$t('personnel.form.contactTel')"
|
||||||
|
:model-value="readonly ? contactTel || '-' : contactTel"
|
||||||
|
@update:model-value="
|
||||||
|
(v) => (typeof v === 'string' ? (contactTel = v) : '')
|
||||||
|
"
|
||||||
>
|
>
|
||||||
<template v-slot:no-option>
|
<template #prepend>
|
||||||
<q-item>
|
<q-icon
|
||||||
<q-item-section class="text-grey">
|
size="xs"
|
||||||
{{ $t('general.noData') }}
|
name="mdi-phone-outline"
|
||||||
</q-item-section>
|
class="cursor-pointer"
|
||||||
</q-item>
|
color="primary"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
</q-select>
|
</q-input>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -32,11 +32,11 @@ 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>('visaType');
|
const type = defineModel<string>('type');
|
||||||
const expireDate = defineModel<Date>('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>('visaNumber');
|
const number = defineModel<string>('number');
|
||||||
|
|
||||||
const calculatedVisaDate = computed(() => {
|
const calculatedVisaDate = computed(() => {
|
||||||
if (!issueDate.value) return undefined;
|
if (!issueDate.value) return undefined;
|
||||||
|
|
@ -78,6 +78,12 @@ 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,
|
||||||
|
|
@ -97,6 +103,12 @@ 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,
|
||||||
|
|
@ -107,8 +119,14 @@ onMounted(() => {
|
||||||
watch(
|
watch(
|
||||||
() => optionStore.globalOption,
|
() => optionStore.globalOption,
|
||||||
() => {
|
() => {
|
||||||
|
visaIssueCountryFilter = selectFilterOptionRefMod(
|
||||||
|
ref(optionStore.globalOption?.nationality),
|
||||||
|
visaIssueCountryOptions,
|
||||||
|
'label',
|
||||||
|
);
|
||||||
|
|
||||||
visaTypeFilter = selectFilterOptionRefMod(
|
visaTypeFilter = selectFilterOptionRefMod(
|
||||||
optionStore.globalOption.nationality,
|
optionStore.globalOption.visaType,
|
||||||
visaTypeOptions,
|
visaTypeOptions,
|
||||||
'label',
|
'label',
|
||||||
);
|
);
|
||||||
|
|
@ -422,11 +440,11 @@ watch(
|
||||||
class="col-md-4 col-6"
|
class="col-md-4 col-6"
|
||||||
:dense="dense"
|
:dense="dense"
|
||||||
:readonly="readonly"
|
:readonly="readonly"
|
||||||
:options="visaTypeOptions"
|
:options="visaIssueCountryOptions"
|
||||||
: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="visaTypeFilter"
|
@filter="visaIssueCountryFilter"
|
||||||
: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) : '')
|
||||||
|
|
|
||||||
|
|
@ -6,12 +6,14 @@ 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';
|
||||||
|
|
||||||
|
|
@ -20,6 +22,7 @@ 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;
|
||||||
|
|
@ -29,6 +32,7 @@ defineProps<{
|
||||||
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: [],
|
||||||
|
|
@ -51,7 +55,9 @@ 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({
|
||||||
|
|
@ -60,10 +66,10 @@ async function getUserList(opts?: { query: string }) {
|
||||||
if (resUser) userList.value = resUser.result;
|
if (resUser) userList.value = resUser.result;
|
||||||
}
|
}
|
||||||
|
|
||||||
// async function getUserById(responsiblePersonId: string) {
|
async function getGroupList() {
|
||||||
// const resUser = await userStore.fetchById(responsiblePersonId);
|
const resGroup = await workflowStore.getGroupList();
|
||||||
// if (resUser) userInTable.value.push(resUser);
|
if (resGroup) groupList.value = resGroup;
|
||||||
// }
|
}
|
||||||
|
|
||||||
function selectResponsiblePerson(stepIndex: number, responsiblePerson: User) {
|
function selectResponsiblePerson(stepIndex: number, responsiblePerson: User) {
|
||||||
const currStep = flowData.value.step[stepIndex];
|
const currStep = flowData.value.step[stepIndex];
|
||||||
|
|
@ -78,6 +84,7 @@ 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: [],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -101,6 +108,33 @@ 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[],
|
||||||
|
|
@ -142,6 +176,7 @@ 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>
|
||||||
|
|
@ -467,92 +502,128 @@ onMounted(async () => {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- RESPONSIBLE-PERSON -->
|
<!-- RESPONSIBLE-PERSON -->
|
||||||
<q-select
|
<q-field
|
||||||
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
|
||||||
dense
|
:stack-label="
|
||||||
v-model="step.responsiblePersonId"
|
userInTable[index]?.responsiblePerson.length > 0 ||
|
||||||
multiple
|
userInTable[index]?.responsibleGroup.length > 0
|
||||||
: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"
|
||||||
:hide-dropdown-icon="readonly"
|
:class="{ 'cursor-pointer': !readonly }"
|
||||||
>
|
>
|
||||||
<template v-slot:selected-item="scope">
|
<template #control>
|
||||||
<div class="column full-width">
|
<q-item
|
||||||
<div
|
dense
|
||||||
class="row items-center no-wrap"
|
class="items-center full-width no-padding"
|
||||||
v-for="person in userInTable[
|
v-for="person in userInTable[
|
||||||
index
|
index
|
||||||
]?.responsiblePerson.filter(
|
]?.responsiblePerson.filter((p) =>
|
||||||
(p) => p.id === scope.opt,
|
step.responsiblePersonId.includes(p.id),
|
||||||
)"
|
)"
|
||||||
:key="person.id"
|
:key="person.id"
|
||||||
>
|
>
|
||||||
<q-avatar class="q-ml-sm" size="md">
|
<q-avatar class="q-ml-sm" size="md">
|
||||||
<q-img
|
<q-img
|
||||||
class="text-center"
|
class="text-center"
|
||||||
:ratio="1"
|
:ratio="1"
|
||||||
:src="`${baseUrl}/user/${person.id}/profile-image/${person.selectedImage}`"
|
:src="`${baseUrl}/user/${person.id}/profile-image/${person.selectedImage}`"
|
||||||
>
|
|
||||||
<template #error>
|
|
||||||
<div
|
|
||||||
class="no-padding full-width full-height flex items-center justify-center"
|
|
||||||
:style="`${person.gender ? 'background: white' : 'background: linear-gradient(135deg,rgba(43, 137, 223, 1) 0%, rgba(230, 51, 81, 1) 100%);'}`"
|
|
||||||
>
|
|
||||||
<q-img
|
|
||||||
v-if="person.gender"
|
|
||||||
:src="
|
|
||||||
person.gender === 'male'
|
|
||||||
? '/no-img-man.png'
|
|
||||||
: '/no-img-female.png'
|
|
||||||
"
|
|
||||||
/>
|
|
||||||
<q-icon
|
|
||||||
v-else
|
|
||||||
size="sm"
|
|
||||||
name="mdi-account-outline"
|
|
||||||
style="color: white"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</q-img>
|
|
||||||
</q-avatar>
|
|
||||||
<div
|
|
||||||
class="column q-pl-md"
|
|
||||||
style="color: var(--foreground)"
|
|
||||||
>
|
>
|
||||||
<span>
|
<template #error>
|
||||||
{{
|
<div
|
||||||
`${optionStore.mapOption(person.namePrefix || '')} ${
|
class="no-padding full-width full-height flex items-center justify-center"
|
||||||
$i18n.locale === 'eng'
|
:style="`${person.gender ? 'background: white' : 'background: linear-gradient(135deg,rgba(43, 137, 223, 1) 0%, rgba(230, 51, 81, 1) 100%);'}`"
|
||||||
? person.firstNameEN
|
>
|
||||||
: person.firstName
|
<q-img
|
||||||
} ${
|
v-if="person.gender"
|
||||||
$i18n.locale === 'eng'
|
:src="
|
||||||
? person.lastNameEN
|
person.gender === 'male'
|
||||||
: person.lastName
|
? '/no-img-man.png'
|
||||||
}`
|
: '/no-img-female.png'
|
||||||
}}
|
"
|
||||||
</span>
|
/>
|
||||||
<span class="text-caption app-text-muted">
|
<q-icon
|
||||||
{{ person.code }}
|
v-else
|
||||||
</span>
|
size="sm"
|
||||||
</div>
|
name="mdi-account-outline"
|
||||||
|
style="color: white"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</q-img>
|
||||||
|
</q-avatar>
|
||||||
|
<div
|
||||||
|
class="column q-pl-md"
|
||||||
|
style="color: var(--foreground)"
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
{{
|
||||||
|
`${optionStore.mapOption(person.namePrefix || '')} ${
|
||||||
|
$i18n.locale === 'eng'
|
||||||
|
? person.firstNameEN
|
||||||
|
: person.firstName
|
||||||
|
} ${
|
||||||
|
$i18n.locale === 'eng'
|
||||||
|
? person.lastNameEN
|
||||||
|
: person.lastName
|
||||||
|
}`
|
||||||
|
}}
|
||||||
|
</span>
|
||||||
|
<span class="text-caption app-text-muted">
|
||||||
|
{{ person.code }}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</q-item>
|
||||||
</template>
|
|
||||||
|
|
||||||
<template v-slot:option></template>
|
<div
|
||||||
<q-menu v-if="!readonly" :offset="[0, 4]">
|
v-if="step.responsibleGroup.length > 0"
|
||||||
|
class="full-width app-text-muted text-weight-medium"
|
||||||
|
style="font-size: 10px"
|
||||||
|
>
|
||||||
|
{{ $t('general.group') }}
|
||||||
|
</div>
|
||||||
|
<q-item
|
||||||
|
class="items-center full-width no-padding"
|
||||||
|
v-for="group in userInTable[
|
||||||
|
index
|
||||||
|
]?.responsibleGroup.filter((g) =>
|
||||||
|
step.responsibleGroup.includes(g),
|
||||||
|
)"
|
||||||
|
:key="group"
|
||||||
|
dense
|
||||||
|
>
|
||||||
|
<q-avatar class="q-ml-sm" size="md">
|
||||||
|
<q-img
|
||||||
|
class="text-center"
|
||||||
|
:ratio="1"
|
||||||
|
:src="`/img-group.png`"
|
||||||
|
/>
|
||||||
|
</q-avatar>
|
||||||
|
<span class="q-pl-md">
|
||||||
|
{{ group }}
|
||||||
|
</span>
|
||||||
|
</q-item>
|
||||||
|
</template>
|
||||||
|
<template #append>
|
||||||
|
<q-icon
|
||||||
|
name="mdi-menu-down"
|
||||||
|
:class="{ rotated: responsibleMenu }"
|
||||||
|
class="transition-rotate"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<q-menu
|
||||||
|
v-if="!readonly"
|
||||||
|
no-focus
|
||||||
|
no-refocus
|
||||||
|
:offset="[0, 4]"
|
||||||
|
@before-show="() => (responsibleMenu = true)"
|
||||||
|
@before-hide="() => (responsibleMenu = false)"
|
||||||
|
>
|
||||||
<q-list>
|
<q-list>
|
||||||
<q-item>
|
<q-item>
|
||||||
<q-input
|
<q-input
|
||||||
|
|
@ -581,6 +652,7 @@ 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"
|
||||||
|
|
@ -655,6 +727,7 @@ 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"
|
||||||
|
|
@ -670,9 +743,49 @@ 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-select>
|
</q-field>
|
||||||
|
|
||||||
<!-- RESPONSIBLE-AGENCIES, RESPONSIBLE-INSTITUTION -->
|
<!-- RESPONSIBLE-AGENCIES, RESPONSIBLE-INSTITUTION -->
|
||||||
<q-select
|
<q-select
|
||||||
|
|
@ -815,4 +928,11 @@ onMounted(async () => {
|
||||||
: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>
|
||||||
|
|
|
||||||
|
|
@ -244,16 +244,19 @@ 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"
|
||||||
/>
|
/>
|
||||||
|
|
@ -271,6 +274,8 @@ 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
|
||||||
|
|
@ -285,6 +290,8 @@ 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
|
||||||
|
|
@ -299,6 +306,8 @@ withDefaults(
|
||||||
flat
|
flat
|
||||||
outlined
|
outlined
|
||||||
dense
|
dense
|
||||||
|
:readonly
|
||||||
|
:hide-dropdown-icon="readonly"
|
||||||
v-model="serviceChargeVatIncluded"
|
v-model="serviceChargeVatIncluded"
|
||||||
></q-select>
|
></q-select>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -133,7 +133,7 @@ type Options = { label: string; value: string };
|
||||||
:readonly="readonly"
|
:readonly="readonly"
|
||||||
hide-bottom-space
|
hide-bottom-space
|
||||||
class="col-md-4 col-12"
|
class="col-md-4 col-12"
|
||||||
:label="$t('form.telephone')"
|
:label="$t('agencies.contactTel')"
|
||||||
:model-value="readonly ? contactTel || '-' : contactTel"
|
:model-value="readonly ? contactTel || '-' : contactTel"
|
||||||
@update:model-value="
|
@update:model-value="
|
||||||
(v) => (typeof v === 'string' ? (contactTel = v) : '')
|
(v) => (typeof v === 'string' ? (contactTel = v) : '')
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
<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>
|
||||||
|
|
||||||
|
|
@ -10,10 +13,13 @@ defineProps<{
|
||||||
v-if="!hideIcon"
|
v-if="!hideIcon"
|
||||||
id="btn-add"
|
id="btn-add"
|
||||||
padding="sm"
|
padding="sm"
|
||||||
icon="mdi-plus"
|
:icon="icon ? undefined : '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"
|
||||||
|
|
@ -29,10 +35,12 @@ defineProps<{
|
||||||
fab
|
fab
|
||||||
id="btn-add"
|
id="btn-add"
|
||||||
padding="sm"
|
padding="sm"
|
||||||
icon="mdi-plus"
|
:icon="icon ? undefined : '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>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import { ref } from 'vue';
|
||||||
import MainButton from './MainButton.vue';
|
import MainButton from './MainButton.vue';
|
||||||
|
|
||||||
defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: 'click', v: MouseEvent): void;
|
(e: 'click', v: MouseEvent): void;
|
||||||
|
(e: 'fileSelected', v: File[]): void;
|
||||||
}>();
|
}>();
|
||||||
defineProps<{
|
defineProps<{
|
||||||
iconOnly?: boolean;
|
iconOnly?: boolean;
|
||||||
|
|
@ -10,15 +12,29 @@ 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) => $emit('click', e)"
|
@click="(e) => (importFile ? triggerFileInput() : $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)"
|
||||||
|
|
@ -26,4 +42,13 @@ defineProps<{
|
||||||
>
|
>
|
||||||
{{ 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>
|
||||||
|
|
|
||||||
32
src/components/button/PasteButton.vue
Normal file
32
src/components/button/PasteButton.vue
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
<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>
|
||||||
|
|
@ -14,3 +14,4 @@ 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';
|
||||||
|
|
|
||||||
189
src/components/shared/AdvanceSearch.vue
Normal file
189
src/components/shared/AdvanceSearch.vue
Normal file
|
|
@ -0,0 +1,189 @@
|
||||||
|
<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">
|
||||||
|
<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>
|
||||||
|
|
@ -25,7 +25,11 @@ withDefaults(
|
||||||
alt="Image"
|
alt="Image"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="data.length > 3" class="avatar remaining-count">
|
<div
|
||||||
|
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 }}
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ 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;
|
||||||
}>(),
|
}>(),
|
||||||
|
|
@ -31,6 +32,7 @@ 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;
|
||||||
|
|
@ -172,6 +174,27 @@ 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
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ 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)[];
|
||||||
}>(),
|
}>(),
|
||||||
|
|
@ -82,7 +83,7 @@ watch(
|
||||||
:hide-selected
|
:hide-selected
|
||||||
hide-bottom-space
|
hide-bottom-space
|
||||||
:fill-input="fillInput && !!model"
|
:fill-input="fillInput && !!model"
|
||||||
:hide-dropdown-icon="readonly"
|
:hide-dropdown-icon="readonly || hideDropdownIcon"
|
||||||
input-debounce="500"
|
input-debounce="500"
|
||||||
:option-value="
|
:option-value="
|
||||||
typeof props.optionValue === 'string' ? props.optionValue : 'value'
|
typeof props.optionValue === 'string' ? props.optionValue : 'value'
|
||||||
|
|
@ -103,6 +104,11 @@ 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>
|
||||||
|
|
|
||||||
|
|
@ -65,6 +65,8 @@ onMounted(async () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
await getSelectedOption();
|
await getSelectedOption();
|
||||||
|
|
||||||
|
valueOption.value = selectOptions.value.find((v) => v.id === value.value);
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,7 @@ 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;
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -60,7 +60,7 @@ export default {
|
||||||
branchStatus: 'Branch Status',
|
branchStatus: 'Branch Status',
|
||||||
success: 'Success',
|
success: 'Success',
|
||||||
taxNo: 'Legal Person',
|
taxNo: 'Legal Person',
|
||||||
contactName: 'Contact Name',
|
contactName: 'Contact Person',
|
||||||
image: 'Image of ',
|
image: 'Image of ',
|
||||||
apply: 'Apply',
|
apply: 'Apply',
|
||||||
licenseNumber: 'License number',
|
licenseNumber: 'License number',
|
||||||
|
|
@ -154,6 +154,12 @@ 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',
|
||||||
},
|
},
|
||||||
|
|
||||||
menu: {
|
menu: {
|
||||||
|
|
@ -248,7 +254,8 @@ export default {
|
||||||
|
|
||||||
manual: {
|
manual: {
|
||||||
title: 'Manual',
|
title: 'Manual',
|
||||||
usage: 'การใช้งาน',
|
usage: 'Usage',
|
||||||
|
troubleshooting: 'Troubleshooting',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -377,7 +384,7 @@ export default {
|
||||||
branchLabel: 'Branch',
|
branchLabel: 'Branch',
|
||||||
branchHQLabel: 'Headoffice',
|
branchHQLabel: 'Headoffice',
|
||||||
taxNo: 'Legal Person',
|
taxNo: 'Legal Person',
|
||||||
contactName: 'Contact Name',
|
contactName: 'Contact Person',
|
||||||
},
|
},
|
||||||
page: {
|
page: {
|
||||||
captionManage: 'Manage',
|
captionManage: 'Manage',
|
||||||
|
|
@ -398,8 +405,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 Name',
|
contactName: 'Contact Person',
|
||||||
contactTelephone: 'Contact Telephone',
|
contactTelephone: 'Contact Number',
|
||||||
branchName: 'Branch Name',
|
branchName: 'Branch Name',
|
||||||
branchNameEN: 'Branch Name (EN)',
|
branchNameEN: 'Branch Name (EN)',
|
||||||
servicePointName: 'Service Point Name',
|
servicePointName: 'Service Point Name',
|
||||||
|
|
@ -464,6 +471,8 @@ export default {
|
||||||
normal: 'Normal',
|
normal: 'Normal',
|
||||||
canceled: 'Canceled',
|
canceled: 'Canceled',
|
||||||
blacklist: 'Black list',
|
blacklist: 'Black list',
|
||||||
|
contactName: 'Contact Person',
|
||||||
|
contactTel: 'Contact Number',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
customer: {
|
customer: {
|
||||||
|
|
@ -477,7 +486,6 @@ 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: 'Natrual Person',
|
employerNaturalPerson: 'Natrual Person',
|
||||||
|
|
@ -499,15 +507,12 @@ export default {
|
||||||
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',
|
||||||
|
|
@ -515,7 +520,6 @@ 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',
|
||||||
|
|
@ -523,7 +527,6 @@ 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',
|
||||||
|
|
@ -561,7 +564,7 @@ export default {
|
||||||
jobPosition: 'Job Position',
|
jobPosition: 'Job Position',
|
||||||
address: 'Address',
|
address: 'Address',
|
||||||
workPlace: 'Workplace',
|
workPlace: 'Workplace',
|
||||||
contactName: 'Contact Name',
|
contactName: 'Contact Person',
|
||||||
contactPhone: 'Contact Phone',
|
contactPhone: 'Contact Phone',
|
||||||
totalEmployee: 'Total Employee',
|
totalEmployee: 'Total Employee',
|
||||||
officeTel: 'Headoffice Telephone',
|
officeTel: 'Headoffice Telephone',
|
||||||
|
|
@ -799,7 +802,7 @@ export default {
|
||||||
employee: 'Employee',
|
employee: 'Employee',
|
||||||
employeeName: 'Full Name',
|
employeeName: 'Full Name',
|
||||||
workName: 'Work Name',
|
workName: 'Work Name',
|
||||||
contactName: 'Contact Name',
|
contactName: 'Contact Person',
|
||||||
documentReceivePoint: 'Document Drop-Off Point"',
|
documentReceivePoint: 'Document Drop-Off Point"',
|
||||||
dueDate: 'Quotation Due Date',
|
dueDate: 'Quotation Due Date',
|
||||||
specialCondition: 'Special Conditions',
|
specialCondition: 'Special Conditions',
|
||||||
|
|
@ -917,8 +920,8 @@ export default {
|
||||||
code: 'Agencies Code',
|
code: 'Agencies Code',
|
||||||
group: 'Agencies Group',
|
group: 'Agencies Group',
|
||||||
name: 'Agencies Name',
|
name: 'Agencies Name',
|
||||||
contactName: 'Contact Name',
|
contactName: 'Contact Person',
|
||||||
contactTel: 'Contact Telephone',
|
contactTel: 'Contact Number',
|
||||||
bankInfo: 'Bank Information',
|
bankInfo: 'Bank Information',
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -941,8 +944,9 @@ 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',
|
salesRepresentative: 'Sales Representative',
|
||||||
|
|
||||||
|
dataOffice: 'Employment Office District',
|
||||||
ref: 'Reference',
|
ref: 'Reference',
|
||||||
action: {
|
action: {
|
||||||
title: 'Action',
|
title: 'Action',
|
||||||
|
|
@ -1006,7 +1010,7 @@ export default {
|
||||||
issueBranch: 'Issue Branch',
|
issueBranch: 'Issue Branch',
|
||||||
issueDate: 'Issue Date',
|
issueDate: 'Issue Date',
|
||||||
madeBy: 'Made By',
|
madeBy: 'Made By',
|
||||||
contactName: 'Contact Name',
|
contactName: 'Contact Person',
|
||||||
workOrderCode: 'Work Order Code',
|
workOrderCode: 'Work Order Code',
|
||||||
workOrderName: 'Work Order Name',
|
workOrderName: 'Work Order Name',
|
||||||
telephone: 'Telephone',
|
telephone: 'Telephone',
|
||||||
|
|
@ -1065,6 +1069,10 @@ 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',
|
||||||
|
|
@ -1485,4 +1493,19 @@ 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',
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -154,6 +154,12 @@ export default {
|
||||||
draw: 'วาด',
|
draw: 'วาด',
|
||||||
newUpload: 'อัปโหลดใหม่',
|
newUpload: 'อัปโหลดใหม่',
|
||||||
nativeLanguage: '{msg} ภาษาต้นทาง',
|
nativeLanguage: '{msg} ภาษาต้นทาง',
|
||||||
|
copy: 'คัดลอก',
|
||||||
|
paste: 'วาง',
|
||||||
|
period: 'ช่วงเวลา',
|
||||||
|
documentStatus: 'สถานะเอกสาร',
|
||||||
|
advanceSearch: 'ค้นหาขั้นสูง',
|
||||||
|
totalPeople: '{meg} คน',
|
||||||
},
|
},
|
||||||
|
|
||||||
menu: {
|
menu: {
|
||||||
|
|
@ -249,6 +255,7 @@ export default {
|
||||||
manual: {
|
manual: {
|
||||||
title: 'คู่มือ',
|
title: 'คู่มือ',
|
||||||
usage: 'การใช้งาน',
|
usage: 'การใช้งาน',
|
||||||
|
troubleshooting: 'การแก้ปัญหา',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -456,10 +463,12 @@ export default {
|
||||||
citizenId: 'เลขที่บัตรประชาชน',
|
citizenId: 'เลขที่บัตรประชาชน',
|
||||||
citizenIssue: 'วันที่ออกบัตร',
|
citizenIssue: 'วันที่ออกบัตร',
|
||||||
citizenExpire: 'วันที่หมดอายุ',
|
citizenExpire: 'วันที่หมดอายุ',
|
||||||
agencyStatus: 'สถานะการเป็นเอเจนซี่',
|
agencyStatus: 'สถานะเอเจนซี่',
|
||||||
normal: 'ปกติ',
|
normal: 'ปกติ',
|
||||||
canceled: 'ยกเลิก',
|
canceled: 'ยกเลิก',
|
||||||
blacklist: 'แบล็คลิสต์',
|
blacklist: 'แบล็คลิสต์',
|
||||||
|
contactName: 'ชื่อผู้ติดต่อ',
|
||||||
|
contactTel: 'เบอร์โทรศัพท์ผู้ติดต่อ',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
customer: {
|
customer: {
|
||||||
|
|
@ -932,6 +941,7 @@ export default {
|
||||||
nonLocalEmployee: 'พนักงานนอกพื้นที่',
|
nonLocalEmployee: 'พนักงานนอกพื้นที่',
|
||||||
noWorkflowTemplate: 'คุณไม่ได้เลือกแม่แบบขั้นตอนการทำงาน',
|
noWorkflowTemplate: 'คุณไม่ได้เลือกแม่แบบขั้นตอนการทำงาน',
|
||||||
salesRepresentative: 'พนักงานขาย',
|
salesRepresentative: 'พนักงานขาย',
|
||||||
|
dataOffice: 'สำนักงานเขตจัดหางาน',
|
||||||
ref: 'อ้างอิง',
|
ref: 'อ้างอิง',
|
||||||
action: {
|
action: {
|
||||||
title: 'จัดการ',
|
title: 'จัดการ',
|
||||||
|
|
@ -1050,6 +1060,9 @@ export default {
|
||||||
confirmDebitNoteAccept: 'ยืนยันการตอบรับใบเพิ่มหนี้',
|
confirmDebitNoteAccept: 'ยืนยันการตอบรับใบเพิ่มหนี้',
|
||||||
},
|
},
|
||||||
message: {
|
message: {
|
||||||
|
copy: 'คัดลอก',
|
||||||
|
warningPaste: 'คุณต้องการที่จะเเทนที่ข้อมูลที่คัดลอกมาใหม่ใช่หรือไม่',
|
||||||
|
warningCopyEmpty: 'คุณยังไม่ได้คัดลอกข้อมูล',
|
||||||
quotationAccept: 'เมื่อตอบรับเเล้วจะไม่สามารถแก้ไขได้อีก',
|
quotationAccept: 'เมื่อตอบรับเเล้วจะไม่สามารถแก้ไขได้อีก',
|
||||||
beingUse: '"{msg}" มีการใช้งานอยู่',
|
beingUse: '"{msg}" มีการใช้งานอยู่',
|
||||||
incompleteDataEntry: 'กรอกข้อมูลไม่ครบในหน้า {tap}',
|
incompleteDataEntry: 'กรอกข้อมูลไม่ครบในหน้า {tap}',
|
||||||
|
|
@ -1468,4 +1481,19 @@ export default {
|
||||||
type: 'ประเภท',
|
type: 'ประเภท',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
dateRange: {
|
||||||
|
today: 'วันนี้',
|
||||||
|
yesterday: 'เมื่อวานนี้',
|
||||||
|
thisWeek: 'สัปดาห์นี้',
|
||||||
|
lastWeek: 'สัปดาห์ที่แล้ว',
|
||||||
|
thisMonth: 'เดือนนี้',
|
||||||
|
lastMonth: 'เดือนที่แล้ว',
|
||||||
|
thisYear: 'ปีนี้',
|
||||||
|
lastYear: 'ปีที่แล้ว',
|
||||||
|
last7Days: '7 วันที่ผ่านมา',
|
||||||
|
last30Days: '30 วันที่ผ่านมา',
|
||||||
|
last90Days: '90 วันที่ผ่านมา',
|
||||||
|
customDateRange: 'กำหนดช่วงวันที่เอง',
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -215,6 +215,10 @@ function initMenu() {
|
||||||
label: 'usage',
|
label: 'usage',
|
||||||
route: '/manual',
|
route: '/manual',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: 'troubleshooting',
|
||||||
|
route: '/troubleshooting',
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
|
||||||
|
|
@ -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 } from 'vue';
|
import { onMounted, watch } from 'vue';
|
||||||
|
|
||||||
// NOTE: Components
|
// NOTE: Components
|
||||||
|
|
||||||
|
|
@ -10,22 +10,33 @@ import { onMounted } 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 } from 'vue-router';
|
||||||
|
|
||||||
// NOTE: Variable
|
// NOTE: Variable
|
||||||
|
const route = useRoute();
|
||||||
const manualStore = useManualStore();
|
const manualStore = useManualStore();
|
||||||
const navigatorStore = useNavigator();
|
const navigatorStore = useNavigator();
|
||||||
const { dataManual } = storeToRefs(manualStore);
|
const { dataManual, dataTroubleshooting } = 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 : [];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: true },
|
||||||
|
);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
@ -34,7 +45,7 @@ onMounted(async () => {
|
||||||
>
|
>
|
||||||
<section class="scroll q-gutter-y-sm">
|
<section class="scroll q-gutter-y-sm">
|
||||||
<q-expansion-item
|
<q-expansion-item
|
||||||
v-for="v in dataManual"
|
v-for="v in $route.name === 'Manual' ? dataManual : dataTroubleshooting"
|
||||||
: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"
|
||||||
|
|
@ -58,7 +69,11 @@ onMounted(async () => {
|
||||||
clickable
|
clickable
|
||||||
dense
|
dense
|
||||||
class="dot items-center rounded q-my-xs"
|
class="dot items-center rounded q-my-xs"
|
||||||
:to="`/manual/${v.category}/${x.name}`"
|
:to="
|
||||||
|
$route.name === 'Manual'
|
||||||
|
? `/manual/${v.category}/${x.name}`
|
||||||
|
: `/troubleshooting/${v.category}/${x.name}`
|
||||||
|
"
|
||||||
>
|
>
|
||||||
<Icon
|
<Icon
|
||||||
v-if="!!x.icon"
|
v-if="!!x.icon"
|
||||||
|
|
|
||||||
|
|
@ -56,14 +56,28 @@ onUnmounted(() => {
|
||||||
|
|
||||||
async function getContent() {
|
async function getContent() {
|
||||||
if (!category.value || !page.value) return;
|
if (!category.value || !page.value) return;
|
||||||
const res = await manualStore.getManualByPage({
|
|
||||||
category: category.value,
|
if (ROUTE.name === 'ManualView') {
|
||||||
pageName: page.value,
|
const res = await manualStore.getManualByPage({
|
||||||
});
|
category: category.value,
|
||||||
if (res && res.ok) {
|
pageName: page.value,
|
||||||
const text = await res.text();
|
});
|
||||||
content.value = text;
|
if (res && res.ok) {
|
||||||
contentParsed.value = md.parse(text, {});
|
const text = await res.text();
|
||||||
|
content.value = text;
|
||||||
|
contentParsed.value = md.parse(text, {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (ROUTE.name === 'TroubleshootingView') {
|
||||||
|
const res = await manualStore.getTroubleshootingByPage({
|
||||||
|
category: category.value,
|
||||||
|
pageName: page.value,
|
||||||
|
});
|
||||||
|
if (res && res.ok) {
|
||||||
|
const text = await res.text();
|
||||||
|
content.value = text;
|
||||||
|
contentParsed.value = md.parse(text, {});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -184,7 +198,9 @@ async function scrollTo(id: string) {
|
||||||
md.render(
|
md.render(
|
||||||
content.replaceAll(
|
content.replaceAll(
|
||||||
'assets/',
|
'assets/',
|
||||||
`${baseUrl}/manual/${category}/assets/`,
|
$route.name === 'ManualView'
|
||||||
|
? `${baseUrl}/manual/${category}/assets/`
|
||||||
|
: `${baseUrl}/troubleshooting/${category}/assets/`,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
"
|
"
|
||||||
|
|
@ -315,4 +331,20 @@ async function scrollTo(id: string) {
|
||||||
.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>
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ 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 { QSelect, QTableProps, QTableSlots } from 'quasar';
|
import type { 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';
|
||||||
|
|
@ -52,6 +52,7 @@ 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();
|
||||||
|
|
@ -72,7 +73,6 @@ 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,6 +175,8 @@ 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();
|
||||||
|
|
@ -715,12 +717,20 @@ 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, () => {
|
watch([inputSearch, searchDate], () => {
|
||||||
fetchList({ tree: true, query: inputSearch.value, withHead: true });
|
fetchList({
|
||||||
|
tree: true,
|
||||||
|
query: inputSearch.value,
|
||||||
|
withHead: true,
|
||||||
|
startDate: searchDate.value[0],
|
||||||
|
endDate: searchDate.value[1],
|
||||||
|
});
|
||||||
currentSubBranch.value = undefined;
|
currentSubBranch.value = undefined;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -1170,26 +1180,49 @@ watch(currentHq, () => {
|
||||||
<template v-slot:prepend>
|
<template v-slot:prepend>
|
||||||
<q-icon name="mdi-magnify" />
|
<q-icon name="mdi-magnify" />
|
||||||
</template>
|
</template>
|
||||||
<template v-if="$q.screen.lt.md" v-slot:append>
|
<template v-slot:append>
|
||||||
<span class="row">
|
<q-separator vertical inset class="q-mr-xs" />
|
||||||
<q-separator vertical />
|
|
||||||
<q-btn
|
<AdvanceSearch
|
||||||
icon="mdi-filter-variant"
|
v-model="searchDate"
|
||||||
unelevated
|
:active="$q.screen.lt.md && statusFilter !== 'all'"
|
||||||
class="q-ml-sm"
|
>
|
||||||
padding="4px"
|
<div
|
||||||
size="sm"
|
v-if="$q.screen.lt.md"
|
||||||
rounded
|
class="q-mt-sm text-weight-medium"
|
||||||
@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',
|
||||||
|
},
|
||||||
|
]"
|
||||||
/>
|
/>
|
||||||
</span>
|
</AdvanceSearch>
|
||||||
</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-show="$q.screen.gt.sm"
|
v-if="$q.screen.gt.sm"
|
||||||
ref="refFilter"
|
|
||||||
v-model="statusFilter"
|
v-model="statusFilter"
|
||||||
outlined
|
outlined
|
||||||
dense
|
dense
|
||||||
|
|
|
||||||
|
|
@ -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 { QSelect, useQuasar, type QTableProps } from 'quasar';
|
import { useQuasar, type QTableProps } from 'quasar';
|
||||||
import { dialog, baseUrl } from 'stores/utils';
|
import { dialog, baseUrl, setPrefixName } 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,6 +49,7 @@ 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();
|
||||||
|
|
@ -73,7 +74,6 @@ 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,12 +93,14 @@ const currentUser = ref<User>();
|
||||||
const userCode = ref<string>();
|
const userCode = ref<string>();
|
||||||
|
|
||||||
const statusToggle = ref(true);
|
const statusToggle = ref(true);
|
||||||
const agencyFile = ref<File[]>([]);
|
const userFile = ref<File[]>([]);
|
||||||
const agencyFileList = ref<{ name: string; url: string }[]>([]);
|
const userFileList = 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[] }>();
|
||||||
|
|
@ -124,7 +126,7 @@ const defaultFormData = {
|
||||||
streetEN: '',
|
streetEN: '',
|
||||||
street: '',
|
street: '',
|
||||||
trainingPlace: null,
|
trainingPlace: null,
|
||||||
importNationality: null,
|
importNationality: [],
|
||||||
sourceNationality: null,
|
sourceNationality: null,
|
||||||
licenseExpireDate: null,
|
licenseExpireDate: null,
|
||||||
licenseIssueDate: null,
|
licenseIssueDate: null,
|
||||||
|
|
@ -151,8 +153,10 @@ const defaultFormData = {
|
||||||
citizenExpire: null,
|
citizenExpire: null,
|
||||||
citizenIssue: null,
|
citizenIssue: null,
|
||||||
citizenId: '',
|
citizenId: '',
|
||||||
|
contactName: '',
|
||||||
|
contactTel: '',
|
||||||
remark: '',
|
remark: '',
|
||||||
agencyStatus: null,
|
agencyStatus: '',
|
||||||
};
|
};
|
||||||
|
|
||||||
const formData = ref<UserCreate>({
|
const formData = ref<UserCreate>({
|
||||||
|
|
@ -174,7 +178,7 @@ const formData = ref<UserCreate>({
|
||||||
streetEN: '',
|
streetEN: '',
|
||||||
street: '',
|
street: '',
|
||||||
trainingPlace: null,
|
trainingPlace: null,
|
||||||
importNationality: null,
|
importNationality: [],
|
||||||
sourceNationality: null,
|
sourceNationality: null,
|
||||||
licenseExpireDate: null,
|
licenseExpireDate: null,
|
||||||
licenseIssueDate: null,
|
licenseIssueDate: null,
|
||||||
|
|
@ -201,8 +205,10 @@ const formData = ref<UserCreate>({
|
||||||
citizenExpire: null,
|
citizenExpire: null,
|
||||||
citizenIssue: null,
|
citizenIssue: null,
|
||||||
citizenId: '',
|
citizenId: '',
|
||||||
|
contactName: '',
|
||||||
|
contactTel: '',
|
||||||
remark: '',
|
remark: '',
|
||||||
agencyStatus: null,
|
agencyStatus: '',
|
||||||
});
|
});
|
||||||
|
|
||||||
const fieldSelectedOption = ref<{ label: string; value: string }[]>([
|
const fieldSelectedOption = ref<{ label: string; value: string }[]>([
|
||||||
|
|
@ -331,7 +337,7 @@ function onClose(excludeDialog?: boolean) {
|
||||||
urlProfile.value = '';
|
urlProfile.value = '';
|
||||||
profileFileImg.value = null;
|
profileFileImg.value = null;
|
||||||
infoDrawerEdit.value = false;
|
infoDrawerEdit.value = false;
|
||||||
agencyFile.value = [];
|
userFile.value = [];
|
||||||
isEdit.value = false;
|
isEdit.value = false;
|
||||||
statusToggle.value = true;
|
statusToggle.value = true;
|
||||||
isImageEdit.value = false;
|
isImageEdit.value = false;
|
||||||
|
|
@ -340,6 +346,8 @@ 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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -360,12 +368,10 @@ 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) {
|
||||||
agencyFileList.value = result;
|
userFileList.value = result;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (userStore.userOption.hqOpts.length !== 0 && !id) {
|
if (userStore.userOption.hqOpts.length !== 0 && !id) {
|
||||||
|
|
@ -429,10 +435,9 @@ async function onSubmit(excludeDialog?: boolean) {
|
||||||
|
|
||||||
await userStore.editById(currentUser.value.id, formDataEdit);
|
await userStore.editById(currentUser.value.id, formDataEdit);
|
||||||
|
|
||||||
if (currentUser.value.id && formDataEdit.userType === 'AGENCY') {
|
if (userFile.value) {
|
||||||
if (!agencyFile.value) return;
|
|
||||||
const payload: UserAttachmentCreate = {
|
const payload: UserAttachmentCreate = {
|
||||||
file: agencyFile.value,
|
file: userFile.value,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (payload?.file) {
|
if (payload?.file) {
|
||||||
|
|
@ -461,11 +466,9 @@ async function onSubmit(excludeDialog?: boolean) {
|
||||||
onCreateImageList.value,
|
onCreateImageList.value,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (result && formData.value.userType === 'AGENCY') {
|
if (userFile.value && result) {
|
||||||
if (!agencyFile.value) return;
|
|
||||||
|
|
||||||
const payload: UserAttachmentCreate = {
|
const payload: UserAttachmentCreate = {
|
||||||
file: agencyFile.value,
|
file: userFile.value,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (payload?.file) {
|
if (payload?.file) {
|
||||||
|
|
@ -552,6 +555,7 @@ async function triggerChangeStatus(id: string, status: string) {
|
||||||
async function assignFormData(idEdit: string) {
|
async function assignFormData(idEdit: string) {
|
||||||
if (!userData.value) return;
|
if (!userData.value) return;
|
||||||
const foundUser = userData.value.result.find((user) => user.id === idEdit);
|
const foundUser = userData.value.result.find((user) => user.id === idEdit);
|
||||||
|
console.log(foundUser);
|
||||||
|
|
||||||
if (foundUser) {
|
if (foundUser) {
|
||||||
currentUser.value = foundUser;
|
currentUser.value = foundUser;
|
||||||
|
|
@ -573,7 +577,10 @@ async function assignFormData(idEdit: string) {
|
||||||
street: foundUser.street,
|
street: foundUser.street,
|
||||||
streetEN: foundUser.streetEN,
|
streetEN: foundUser.streetEN,
|
||||||
trainingPlace: foundUser.trainingPlace,
|
trainingPlace: foundUser.trainingPlace,
|
||||||
importNationality: foundUser.importNationality,
|
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,
|
||||||
|
|
@ -593,6 +600,8 @@ 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)) ||
|
||||||
|
|
@ -669,6 +678,8 @@ 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) {
|
||||||
|
|
@ -743,11 +754,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 = null;
|
formData.value.importNationality = [];
|
||||||
formData.value.trainingPlace = null;
|
formData.value.trainingPlace = null;
|
||||||
formData.value.checkpoint = null;
|
formData.value.checkpoint = null;
|
||||||
formData.value.checkpointEN = null;
|
formData.value.checkpointEN = null;
|
||||||
agencyFile.value = [];
|
userFile.value = [];
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -758,7 +769,7 @@ watch(
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
watch([inputSearch, statusFilter, pageSize], async () => {
|
watch([inputSearch, statusFilter, pageSize, searchDate], async () => {
|
||||||
if (userData.value) userData.value.result = [];
|
if (userData.value) userData.value.result = [];
|
||||||
currentPage.value = 1;
|
currentPage.value = 1;
|
||||||
|
|
||||||
|
|
@ -880,26 +891,45 @@ watch(
|
||||||
<template #prepend>
|
<template #prepend>
|
||||||
<q-icon name="mdi-magnify" />
|
<q-icon name="mdi-magnify" />
|
||||||
</template>
|
</template>
|
||||||
<template v-if="$q.screen.lt.md" v-slot:append>
|
<template v-slot:append>
|
||||||
<span class="row">
|
<q-separator vertical inset class="q-mr-xs" />
|
||||||
<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
|
||||||
|
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',
|
||||||
|
},
|
||||||
|
]"
|
||||||
/>
|
/>
|
||||||
</span>
|
</AdvanceSearch>
|
||||||
</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-show="$q.screen.gt.sm"
|
v-if="$q.screen.gt.sm"
|
||||||
ref="refFilter"
|
|
||||||
v-model="statusFilter"
|
v-model="statusFilter"
|
||||||
outlined
|
outlined
|
||||||
dense
|
dense
|
||||||
|
|
@ -1559,7 +1589,18 @@ 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="`${locale === 'eng' ? `${formData.firstNameEN} ${formData.lastNameEN}` : `${formData.firstName} ${formData.lastName}`}`"
|
:title="
|
||||||
|
setPrefixName(
|
||||||
|
{
|
||||||
|
namePrefix: formData.namePrefix,
|
||||||
|
firstName: formData.firstName,
|
||||||
|
lastName: formData.lastName,
|
||||||
|
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(
|
||||||
|
|
@ -1744,12 +1785,15 @@ 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"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|
@ -1789,8 +1833,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-file="agencyFile"
|
v-model:user-file="userFile"
|
||||||
v-model:agency-file-list="agencyFileList"
|
v-model:user-file-list="userFileList"
|
||||||
v-model:user-id="currentUser.id"
|
v-model:user-id="currentUser.id"
|
||||||
v-model:remark="formData.remark"
|
v-model:remark="formData.remark"
|
||||||
v-model:agency-status="formData.agencyStatus"
|
v-model:agency-status="formData.agencyStatus"
|
||||||
|
|
@ -1837,7 +1881,18 @@ watch(
|
||||||
}[formData.gender]
|
}[formData.gender]
|
||||||
"
|
"
|
||||||
:toggleTitle="$t('status.title')"
|
:toggleTitle="$t('status.title')"
|
||||||
:title="`${locale === 'eng' ? `${formData.firstNameEN} ${formData.lastNameEN}` : `${formData.firstName} ${formData.lastName}`}`"
|
:title="
|
||||||
|
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',
|
||||||
|
|
@ -1948,6 +2003,7 @@ 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'"
|
||||||
|
|
@ -1966,6 +2022,8 @@ 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
|
||||||
|
|
@ -2003,7 +2061,8 @@ watch(
|
||||||
v-model:checkpoint="formData.checkpoint"
|
v-model:checkpoint="formData.checkpoint"
|
||||||
v-model:agency-status="formData.agencyStatus"
|
v-model:agency-status="formData.agencyStatus"
|
||||||
v-model:remark="formData.remark"
|
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>
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, watch, onMounted, computed } from 'vue';
|
import { ref, watch, onMounted, computed } from 'vue';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import { QSelect, useQuasar } from 'quasar';
|
import { useQuasar } from 'quasar';
|
||||||
import { useRoute, useRouter } from 'vue-router';
|
import { useRoute, useRouter } from 'vue-router';
|
||||||
import { getUserId, getRole } from 'src/services/keycloak';
|
import { getUserId, getRole } from 'src/services/keycloak';
|
||||||
import { baseUrl, waitAll } from 'src/stores/utils';
|
import { baseUrl, setPrefixName, waitAll } from 'src/stores/utils';
|
||||||
import { dateFormat } from 'src/utils/datetime';
|
import { dateFormat } from 'src/utils/datetime';
|
||||||
import { dialogCheckData } from 'stores/utils';
|
import { dialogCheckData } from 'stores/utils';
|
||||||
|
|
||||||
|
|
@ -86,6 +86,7 @@ import { nextTick } from 'vue';
|
||||||
import FormEmployeeVisa from 'components/03_customer-management/FormEmployeeVisa.vue';
|
import FormEmployeeVisa from 'components/03_customer-management/FormEmployeeVisa.vue';
|
||||||
import PaginationPageSize from 'src/components/PaginationPageSize.vue';
|
import PaginationPageSize from 'src/components/PaginationPageSize.vue';
|
||||||
import { AddButton } from 'components/button';
|
import { AddButton } from 'components/button';
|
||||||
|
import AdvanceSearch from 'src/components/shared/AdvanceSearch.vue';
|
||||||
|
|
||||||
const { t, locale } = useI18n();
|
const { t, locale } = useI18n();
|
||||||
const $q = useQuasar();
|
const $q = useQuasar();
|
||||||
|
|
@ -101,7 +102,6 @@ const employeeFormStore = useEmployeeForm();
|
||||||
const optionStore = useOptionStore();
|
const optionStore = useOptionStore();
|
||||||
const ocrStore = useOcrStore();
|
const ocrStore = useOcrStore();
|
||||||
|
|
||||||
const refFilter = ref<InstanceType<typeof QSelect>>();
|
|
||||||
const mrz = ref<Awaited<ReturnType<typeof parseResultMRZ>>>();
|
const mrz = ref<Awaited<ReturnType<typeof parseResultMRZ>>>();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
|
|
@ -142,6 +142,14 @@ async function init() {
|
||||||
|
|
||||||
gridView.value = $q.screen.lt.md ? true : false;
|
gridView.value = $q.screen.lt.md ? true : false;
|
||||||
|
|
||||||
|
if (route.query.tab === 'customer') {
|
||||||
|
currentTab.value = 'employer';
|
||||||
|
if (route.query.id) openSpecificCustomer(route.query.id as string);
|
||||||
|
} else if (route.query.tab === 'employee') {
|
||||||
|
currentTab.value = 'employee';
|
||||||
|
if (route.query.id) openSpecificEmployee(route.query.id as string);
|
||||||
|
}
|
||||||
|
|
||||||
if (route.name === 'CustomerManagement') await fetchListCustomer(true);
|
if (route.name === 'CustomerManagement') await fetchListCustomer(true);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
|
|
@ -181,6 +189,7 @@ const statsCustomerType = ref<CustomerStats>({
|
||||||
});
|
});
|
||||||
|
|
||||||
// NOTE: Page State
|
// NOTE: Page State
|
||||||
|
const searchDate = ref<string[]>([]);
|
||||||
const currentTab = ref<'employer' | 'employee'>('employer');
|
const currentTab = ref<'employer' | 'employee'>('employer');
|
||||||
const inputSearch = ref('');
|
const inputSearch = ref('');
|
||||||
const currentStatus = ref<Status | 'All'>('All');
|
const currentStatus = ref<Status | 'All'>('All');
|
||||||
|
|
@ -218,8 +227,16 @@ const dialogEmployeeImageUpload = ref<InstanceType<typeof ImageUploadDialog>>();
|
||||||
const imageList = ref<{ selectedImage: string; list: string[] }>();
|
const imageList = ref<{ selectedImage: string; list: string[] }>();
|
||||||
watch(() => route.name, init);
|
watch(() => route.name, init);
|
||||||
watch(
|
watch(
|
||||||
[currentTab, currentStatus, inputSearch, customerTypeSelected, pageSize],
|
[
|
||||||
async ([tabName]) => {
|
currentTab,
|
||||||
|
currentStatus,
|
||||||
|
inputSearch,
|
||||||
|
customerTypeSelected,
|
||||||
|
pageSize,
|
||||||
|
searchDate,
|
||||||
|
],
|
||||||
|
async ([tabName], [oldTabName]) => {
|
||||||
|
// if (tabName !== oldTabName) searchDate.value = [];
|
||||||
if (tabName === 'employer') {
|
if (tabName === 'employer') {
|
||||||
currentPageCustomer.value = 1;
|
currentPageCustomer.value = 1;
|
||||||
currentBtnOpen.value = [];
|
currentBtnOpen.value = [];
|
||||||
|
|
@ -309,6 +326,8 @@ async function fetchListCustomer(fetchStats = false, mobileFetch?: boolean) {
|
||||||
? 'ACTIVE'
|
? 'ACTIVE'
|
||||||
: 'INACTIVE',
|
: 'INACTIVE',
|
||||||
query: inputSearch.value,
|
query: inputSearch.value,
|
||||||
|
startDate: searchDate.value[0],
|
||||||
|
endDate: searchDate.value[1],
|
||||||
customerType: (
|
customerType: (
|
||||||
{
|
{
|
||||||
all: undefined,
|
all: undefined,
|
||||||
|
|
@ -362,6 +381,8 @@ async function fetchListEmployee(opt?: {
|
||||||
query: inputSearch.value,
|
query: inputSearch.value,
|
||||||
passport: true,
|
passport: true,
|
||||||
visa: true,
|
visa: true,
|
||||||
|
startDate: searchDate.value[0],
|
||||||
|
endDate: searchDate.value[1],
|
||||||
});
|
});
|
||||||
if (resultListEmployee) {
|
if (resultListEmployee) {
|
||||||
maxPageEmployee.value = Math.ceil(
|
maxPageEmployee.value = Math.ceil(
|
||||||
|
|
@ -490,6 +511,30 @@ async function fetchImageList(
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function openSpecificCustomer(id: string) {
|
||||||
|
await customerFormStore.assignFormData(id);
|
||||||
|
await fetchImageList(
|
||||||
|
id,
|
||||||
|
customerFormData.value.selectedImage || '',
|
||||||
|
'customer',
|
||||||
|
);
|
||||||
|
customerFormState.value.branchIndex = -1;
|
||||||
|
customerFormState.value.drawerModal = true;
|
||||||
|
customerFormState.value.editCustomerId = id;
|
||||||
|
customerFormState.value.dialogType = 'info';
|
||||||
|
}
|
||||||
|
|
||||||
|
async function openSpecificEmployee(id: string) {
|
||||||
|
await employeeFormStore.assignFormDataEmployee(id);
|
||||||
|
await fetchImageList(
|
||||||
|
id,
|
||||||
|
currentFromDataEmployee.value.selectedImage || '',
|
||||||
|
'employee',
|
||||||
|
);
|
||||||
|
employeeFormState.value.dialogType = 'info';
|
||||||
|
employeeFormState.value.drawerModal = true;
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: When in employee form, if select address same as customer then auto fill
|
// TODO: When in employee form, if select address same as customer then auto fill
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
|
|
@ -743,26 +788,43 @@ const emptyCreateDialog = ref(false);
|
||||||
<template #prepend>
|
<template #prepend>
|
||||||
<q-icon name="mdi-magnify" />
|
<q-icon name="mdi-magnify" />
|
||||||
</template>
|
</template>
|
||||||
<template v-if="$q.screen.lt.md" v-slot:append>
|
<template v-slot:append>
|
||||||
<span class="row">
|
<q-separator vertical inset class="q-mr-xs" />
|
||||||
<q-separator vertical />
|
<AdvanceSearch
|
||||||
<q-btn
|
v-model="searchDate"
|
||||||
icon="mdi-filter-variant"
|
:active="$q.screen.lt.md && currentStatus !== '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"
|
||||||
|
id="select-status"
|
||||||
|
for="select-status"
|
||||||
|
v-model="currentStatus"
|
||||||
|
outlined
|
||||||
|
dense
|
||||||
|
autocomplete="off"
|
||||||
|
option-value="value"
|
||||||
|
option-label="label"
|
||||||
|
map-options
|
||||||
|
emit-value
|
||||||
|
:options="[
|
||||||
|
{ label: $t('general.all'), value: 'All' },
|
||||||
|
{ label: $t('status.ACTIVE'), value: 'ACTIVE' },
|
||||||
|
{ label: $t('status.INACTIVE'), value: 'INACTIVE' },
|
||||||
|
]"
|
||||||
/>
|
/>
|
||||||
</span>
|
</AdvanceSearch>
|
||||||
</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-show="$q.screen.gt.sm"
|
v-if="$q.screen.gt.sm"
|
||||||
ref="refFilter"
|
|
||||||
id="select-status"
|
id="select-status"
|
||||||
for="select-status"
|
for="select-status"
|
||||||
v-model="currentStatus"
|
v-model="currentStatus"
|
||||||
|
|
@ -2004,7 +2066,16 @@ const emptyCreateDialog = ref(false);
|
||||||
"
|
"
|
||||||
:title="
|
:title="
|
||||||
customerFormData.customerType === 'PERS'
|
customerFormData.customerType === 'PERS'
|
||||||
? `${customerFormData.customerBranch[0]?.firstName} ${customerFormData.customerBranch[0]?.lastName}`
|
? setPrefixName(
|
||||||
|
{
|
||||||
|
namePrefix: customerFormData.customerBranch[0]?.namePrefix,
|
||||||
|
firstName: customerFormData.customerBranch[0]?.firstName,
|
||||||
|
lastName: customerFormData.customerBranch[0]?.lastName,
|
||||||
|
firstNameEN: customerFormData.customerBranch[0]?.firstNameEN,
|
||||||
|
lastNameEN: customerFormData.customerBranch[0]?.lastNameEN,
|
||||||
|
},
|
||||||
|
{ locale },
|
||||||
|
)
|
||||||
: customerFormData.customerBranch[0]?.registerName
|
: customerFormData.customerBranch[0]?.registerName
|
||||||
"
|
"
|
||||||
:caption="
|
:caption="
|
||||||
|
|
@ -2113,16 +2184,16 @@ const emptyCreateDialog = ref(false);
|
||||||
id="form-basic-info-customer"
|
id="form-basic-info-customer"
|
||||||
:onCreate="customerFormState.dialogType === 'create'"
|
:onCreate="customerFormState.dialogType === 'create'"
|
||||||
@edit="
|
@edit="
|
||||||
(customerFormState.dialogType = 'edit'),
|
((customerFormState.dialogType = 'edit'),
|
||||||
(customerFormState.readonly = false)
|
(customerFormState.readonly = false))
|
||||||
"
|
"
|
||||||
@cancel="() => customerFormUndo(false)"
|
@cancel="() => customerFormUndo(false)"
|
||||||
@delete="
|
@delete="
|
||||||
customerFormState.editCustomerId &&
|
customerFormState.editCustomerId &&
|
||||||
deleteCustomerById(
|
deleteCustomerById(
|
||||||
customerFormState.editCustomerId,
|
customerFormState.editCustomerId,
|
||||||
async () => await fetchListCustomer(true, $q.screen.xs),
|
async () => await fetchListCustomer(true, $q.screen.xs),
|
||||||
)
|
)
|
||||||
"
|
"
|
||||||
:customer-type="customerFormData.customerType"
|
:customer-type="customerFormData.customerType"
|
||||||
v-model:registered-branch-id="customerFormData.registeredBranchId"
|
v-model:registered-branch-id="customerFormData.registeredBranchId"
|
||||||
|
|
@ -2452,6 +2523,20 @@ const emptyCreateDialog = ref(false);
|
||||||
"
|
"
|
||||||
:toggleTitle="$t('status.title')"
|
:toggleTitle="$t('status.title')"
|
||||||
hideFade
|
hideFade
|
||||||
|
:title="
|
||||||
|
currentFromDataEmployee
|
||||||
|
? setPrefixName(
|
||||||
|
{
|
||||||
|
namePrefix: currentFromDataEmployee.namePrefix,
|
||||||
|
firstName: currentFromDataEmployee.firstName,
|
||||||
|
lastName: currentFromDataEmployee.lastName,
|
||||||
|
firstNameEN: currentFromDataEmployee.firstNameEN,
|
||||||
|
lastNameEN: currentFromDataEmployee.lastNameEN,
|
||||||
|
},
|
||||||
|
{ locale },
|
||||||
|
)
|
||||||
|
: '-'
|
||||||
|
"
|
||||||
@view="
|
@view="
|
||||||
() => {
|
() => {
|
||||||
employeeFormState.imageDialog = true;
|
employeeFormState.imageDialog = true;
|
||||||
|
|
@ -4052,7 +4137,17 @@ const emptyCreateDialog = ref(false);
|
||||||
"
|
"
|
||||||
:title="
|
:title="
|
||||||
customerFormData.customerType === 'PERS'
|
customerFormData.customerType === 'PERS'
|
||||||
? `${customerFormData.customerBranch[0]?.firstName} ${customerFormData.customerBranch[0]?.lastName}`
|
? setPrefixName(
|
||||||
|
{
|
||||||
|
namePrefix: customerFormData.customerBranch[0]?.namePrefix,
|
||||||
|
firstName: customerFormData.customerBranch[0]?.firstName,
|
||||||
|
lastName: customerFormData.customerBranch[0]?.lastName,
|
||||||
|
firstNameEN:
|
||||||
|
customerFormData.customerBranch[0]?.firstNameEN,
|
||||||
|
lastNameEN: customerFormData.customerBranch[0]?.lastNameEN,
|
||||||
|
},
|
||||||
|
{ locale },
|
||||||
|
)
|
||||||
: customerFormData.customerBranch[0]?.registerName
|
: customerFormData.customerBranch[0]?.registerName
|
||||||
"
|
"
|
||||||
:caption="
|
:caption="
|
||||||
|
|
@ -4166,16 +4261,16 @@ const emptyCreateDialog = ref(false);
|
||||||
id="form-basic-info-customer"
|
id="form-basic-info-customer"
|
||||||
:onCreate="customerFormState.dialogType === 'create'"
|
:onCreate="customerFormState.dialogType === 'create'"
|
||||||
@edit="
|
@edit="
|
||||||
(customerFormState.dialogType = 'edit'),
|
((customerFormState.dialogType = 'edit'),
|
||||||
(customerFormState.readonly = false)
|
(customerFormState.readonly = false))
|
||||||
"
|
"
|
||||||
@cancel="() => customerFormUndo(false)"
|
@cancel="() => customerFormUndo(false)"
|
||||||
@delete="
|
@delete="
|
||||||
customerFormState.editCustomerId &&
|
customerFormState.editCustomerId &&
|
||||||
deleteCustomerById(
|
deleteCustomerById(
|
||||||
customerFormState.editCustomerId,
|
customerFormState.editCustomerId,
|
||||||
async () => await fetchListCustomer(true, $q.screen.xs),
|
async () => await fetchListCustomer(true, $q.screen.xs),
|
||||||
)
|
)
|
||||||
"
|
"
|
||||||
:customer-type="customerFormData.customerType"
|
:customer-type="customerFormData.customerType"
|
||||||
v-model:registered-branch-id="customerFormData.registeredBranchId"
|
v-model:registered-branch-id="customerFormData.registeredBranchId"
|
||||||
|
|
@ -4475,9 +4570,16 @@ const emptyCreateDialog = ref(false);
|
||||||
fallback-cover="/images/employee-banner.png"
|
fallback-cover="/images/employee-banner.png"
|
||||||
:title="
|
:title="
|
||||||
employeeFormState.currentEmployee
|
employeeFormState.currentEmployee
|
||||||
? $i18n.locale === 'eng'
|
? setPrefixName(
|
||||||
? `${employeeFormState.currentEmployee.firstNameEN} ${employeeFormState.currentEmployee.lastNameEN}`
|
{
|
||||||
: `${employeeFormState.currentEmployee.firstName} ${employeeFormState.currentEmployee.lastName}`
|
namePrefix: employeeFormState.currentEmployee.namePrefix,
|
||||||
|
firstName: employeeFormState.currentEmployee.firstName,
|
||||||
|
lastName: employeeFormState.currentEmployee.lastName,
|
||||||
|
firstNameEN: employeeFormState.currentEmployee.firstNameEN,
|
||||||
|
lastNameEN: employeeFormState.currentEmployee.lastNameEN,
|
||||||
|
},
|
||||||
|
{ locale },
|
||||||
|
)
|
||||||
: '-'
|
: '-'
|
||||||
"
|
"
|
||||||
:caption="currentFromDataEmployee.code"
|
:caption="currentFromDataEmployee.code"
|
||||||
|
|
|
||||||
|
|
@ -60,6 +60,7 @@ async function addStep() {
|
||||||
flowData.value.step.push({
|
flowData.value.step.push({
|
||||||
responsibleInstitution: [],
|
responsibleInstitution: [],
|
||||||
responsiblePersonId: [],
|
responsiblePersonId: [],
|
||||||
|
responsibleGroup: [],
|
||||||
value: [],
|
value: [],
|
||||||
detail: '',
|
detail: '',
|
||||||
name: '',
|
name: '',
|
||||||
|
|
@ -166,6 +167,7 @@ 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"
|
||||||
|
|
|
||||||
|
|
@ -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 { QSelect, QTableProps } from 'quasar';
|
import { QTableProps } from 'quasar';
|
||||||
import { storeToRefs } from 'pinia';
|
import { storeToRefs } from 'pinia';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
|
|
||||||
|
|
@ -22,6 +22,7 @@ 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();
|
||||||
|
|
@ -45,6 +46,7 @@ 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')[]>([
|
||||||
|
|
@ -68,7 +70,6 @@ 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',
|
||||||
|
|
@ -102,6 +103,7 @@ 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: '',
|
||||||
|
|
@ -206,7 +208,7 @@ async function submit() {
|
||||||
...formDataWorkflow.value,
|
...formDataWorkflow.value,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
await workflowStore.creatWorkflowTemplate({
|
await workflowStore.createWorkflowTemplate({
|
||||||
registeredBranchId: registeredBranchId.value,
|
registeredBranchId: registeredBranchId.value,
|
||||||
...formDataWorkflow.value,
|
...formDataWorkflow.value,
|
||||||
});
|
});
|
||||||
|
|
@ -222,7 +224,11 @@ 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] = { name: s.name, responsiblePerson: [] };
|
userInTable.value[i] = {
|
||||||
|
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,
|
||||||
|
|
@ -236,12 +242,16 @@ 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),
|
||||||
|
|
@ -282,6 +292,8 @@ 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 =
|
||||||
|
|
@ -311,11 +323,14 @@ watch(
|
||||||
fetchWorkflowList();
|
fetchWorkflowList();
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
watch([() => pageState.inputSearch, workflowPageSize], () => {
|
watch(
|
||||||
workflowData.value = [];
|
[() => pageState.inputSearch, workflowPageSize, () => pageState.searchDate],
|
||||||
workflowPage.value = 1;
|
() => {
|
||||||
fetchWorkflowList();
|
workflowData.value = [];
|
||||||
});
|
workflowPage.value = 1;
|
||||||
|
fetchWorkflowList();
|
||||||
|
},
|
||||||
|
);
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<FloatingActionButton
|
<FloatingActionButton
|
||||||
|
|
@ -392,26 +407,44 @@ watch([() => pageState.inputSearch, workflowPageSize], () => {
|
||||||
<template #prepend>
|
<template #prepend>
|
||||||
<q-icon name="mdi-magnify" />
|
<q-icon name="mdi-magnify" />
|
||||||
</template>
|
</template>
|
||||||
<template v-if="$q.screen.lt.md" v-slot:append>
|
<template v-slot:append>
|
||||||
<span class="row">
|
<q-separator vertical inset class="q-mr-xs" />
|
||||||
<q-separator vertical />
|
<AdvanceSearch
|
||||||
<q-btn
|
v-model="pageState.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
|
||||||
|
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',
|
||||||
|
},
|
||||||
|
]"
|
||||||
/>
|
/>
|
||||||
</span>
|
</AdvanceSearch>
|
||||||
</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-show="$q.screen.gt.sm"
|
v-if="$q.screen.gt.sm"
|
||||||
ref="refFilter"
|
|
||||||
v-model="statusFilter"
|
v-model="statusFilter"
|
||||||
outlined
|
outlined
|
||||||
dense
|
dense
|
||||||
|
|
@ -509,12 +542,12 @@ watch([() => pageState.inputSearch, workflowPageSize], () => {
|
||||||
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"
|
v-if="pageState.total !== 0 || pageState.searchDate.length > 0"
|
||||||
:not-found="!!pageState.inputSearch"
|
:not-found="!!pageState.inputSearch"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<CreateButton
|
<CreateButton
|
||||||
v-if="pageState.total === 0"
|
v-if="pageState.total === 0 && pageState.searchDate.length === 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') }"
|
||||||
|
|
|
||||||
|
|
@ -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 { QSelect, useQuasar, type QTableProps } from 'quasar';
|
import { 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,6 +33,7 @@ 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';
|
||||||
|
|
@ -40,7 +41,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 } from 'stores/utils';
|
import { formatNumberDecimal, isRoleInclude, notify } from 'stores/utils';
|
||||||
const { getWorkflowTemplate } = useWorkflowTemplate();
|
const { getWorkflowTemplate } = useWorkflowTemplate();
|
||||||
|
|
||||||
import { Status } from 'stores/types';
|
import { Status } from 'stores/types';
|
||||||
|
|
@ -67,6 +68,8 @@ 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();
|
||||||
|
|
@ -96,10 +99,13 @@ const {
|
||||||
createWork,
|
createWork,
|
||||||
editWork,
|
editWork,
|
||||||
deleteWork,
|
deleteWork,
|
||||||
|
|
||||||
|
importProduct,
|
||||||
} = 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;
|
||||||
|
|
@ -161,8 +167,6 @@ 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 }>();
|
||||||
|
|
@ -520,6 +524,7 @@ 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);
|
||||||
|
|
@ -615,6 +620,8 @@ 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) {
|
||||||
|
|
@ -675,6 +682,8 @@ 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) {
|
||||||
|
|
@ -720,6 +729,8 @@ 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) {
|
||||||
|
|
@ -1590,6 +1601,7 @@ 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 &&
|
||||||
|
|
@ -1745,7 +1757,7 @@ watch(currentStatus, async () => {
|
||||||
flowStore.rotate();
|
flowStore.rotate();
|
||||||
});
|
});
|
||||||
|
|
||||||
watch(inputSearch, async () => {
|
watch([inputSearch, () => searchDate.value], async () => {
|
||||||
if (productMode.value === 'group') {
|
if (productMode.value === 'group') {
|
||||||
productGroup.value = [];
|
productGroup.value = [];
|
||||||
currentPageGroup.value = 1;
|
currentPageGroup.value = 1;
|
||||||
|
|
@ -1754,7 +1766,7 @@ watch(inputSearch, async () => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
watch(inputSearchProductAndService, async () => {
|
watch([inputSearchProductAndService, () => searchDate.value], async () => {
|
||||||
product.value = [];
|
product.value = [];
|
||||||
service.value = [];
|
service.value = [];
|
||||||
currentPageServiceAndProduct.value = 1;
|
currentPageServiceAndProduct.value = 1;
|
||||||
|
|
@ -1831,6 +1843,51 @@ 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;
|
||||||
|
}
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => formService.value.attributes.workflowId,
|
() => formService.value.attributes.workflowId,
|
||||||
async (a, b) => {
|
async (a, b) => {
|
||||||
|
|
@ -1948,19 +2005,34 @@ watch(
|
||||||
<template v-slot:prepend>
|
<template v-slot:prepend>
|
||||||
<q-icon name="mdi-magnify" />
|
<q-icon name="mdi-magnify" />
|
||||||
</template>
|
</template>
|
||||||
<template v-if="$q.screen.lt.md" v-slot:append>
|
<template v-slot:append>
|
||||||
<span class="row">
|
<q-separator vertical inset class="q-mr-xs" />
|
||||||
<q-separator vertical />
|
<AdvanceSearch
|
||||||
<q-btn
|
v-model="searchDate"
|
||||||
icon="mdi-filter-variant"
|
:active="$q.screen.lt.md && currentStatus !== 'All'"
|
||||||
unelevated
|
>
|
||||||
class="q-ml-sm"
|
<div class="q-mt-sm text-weight-medium">
|
||||||
padding="4px"
|
{{ $t('general.status') }}
|
||||||
size="sm"
|
</div>
|
||||||
rounded
|
<q-select
|
||||||
@click="refFilterGroup?.showPopup"
|
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',
|
||||||
|
},
|
||||||
|
]"
|
||||||
/>
|
/>
|
||||||
</span>
|
</AdvanceSearch>
|
||||||
</template>
|
</template>
|
||||||
</q-input>
|
</q-input>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -2115,26 +2187,44 @@ watch(
|
||||||
<template v-slot:prepend>
|
<template v-slot:prepend>
|
||||||
<q-icon name="mdi-magnify" />
|
<q-icon name="mdi-magnify" />
|
||||||
</template>
|
</template>
|
||||||
<template v-if="$q.screen.lt.md" v-slot:append>
|
<template v-slot:append>
|
||||||
<span class="row">
|
<q-separator vertical inset class="q-mr-xs" />
|
||||||
<q-separator vertical />
|
<AdvanceSearch
|
||||||
<q-btn
|
v-model="searchDate"
|
||||||
icon="mdi-filter-variant"
|
:active="$q.screen.lt.md && currentStatus !== '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="refFilterGroup?.showPopup"
|
{{ $t('general.status') }}
|
||||||
|
</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',
|
||||||
|
},
|
||||||
|
]"
|
||||||
/>
|
/>
|
||||||
</span>
|
</AdvanceSearch>
|
||||||
</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
|
||||||
|
|
@ -2155,7 +2245,6 @@ watch(
|
||||||
},
|
},
|
||||||
]"
|
]"
|
||||||
></q-select>
|
></q-select>
|
||||||
|
|
||||||
<q-select
|
<q-select
|
||||||
v-if="modeView === false"
|
v-if="modeView === false"
|
||||||
id="select-field"
|
id="select-field"
|
||||||
|
|
@ -2178,7 +2267,6 @@ watch(
|
||||||
multiple
|
multiple
|
||||||
dense
|
dense
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<q-btn-toggle
|
<q-btn-toggle
|
||||||
v-model="modeView"
|
v-model="modeView"
|
||||||
id="btn-mode"
|
id="btn-mode"
|
||||||
|
|
@ -2618,26 +2706,65 @@ watch(
|
||||||
<template v-slot:prepend>
|
<template v-slot:prepend>
|
||||||
<q-icon name="mdi-magnify" />
|
<q-icon name="mdi-magnify" />
|
||||||
</template>
|
</template>
|
||||||
<template v-if="$q.screen.lt.md" v-slot:append>
|
<template v-slot:append>
|
||||||
<span class="row">
|
<q-separator vertical inset class="q-mr-xs" />
|
||||||
<q-separator vertical />
|
<AdvanceSearch
|
||||||
<q-btn
|
v-model="searchDate"
|
||||||
icon="mdi-filter-variant"
|
:active="$q.screen.lt.md && currentStatus !== '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="refFilterProductService?.showPopup"
|
{{ $t('general.status') }}
|
||||||
|
</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()"
|
||||||
/>
|
/>
|
||||||
</span>
|
</AdvanceSearch>
|
||||||
</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(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
</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
|
||||||
|
|
@ -2659,7 +2786,6 @@ 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"
|
||||||
|
|
@ -2694,7 +2820,6 @@ watch(
|
||||||
multiple
|
multiple
|
||||||
dense
|
dense
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<q-btn-toggle
|
<q-btn-toggle
|
||||||
v-model="modeView"
|
v-model="modeView"
|
||||||
id="btn-mode"
|
id="btn-mode"
|
||||||
|
|
@ -3127,8 +3252,14 @@ watch(
|
||||||
"
|
"
|
||||||
/>
|
/>
|
||||||
<KebabAction
|
<KebabAction
|
||||||
|
: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') {
|
||||||
|
|
@ -4422,6 +4553,7 @@ 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>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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 { QSelect, QTableProps } from 'quasar';
|
import { 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';
|
||||||
|
|
@ -17,6 +17,7 @@ import { Property } from 'src/stores/property/types';
|
||||||
import { dialog, toCamelCase } 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();
|
||||||
|
|
@ -38,7 +39,6 @@ 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,6 +118,7 @@ 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) {
|
||||||
|
|
@ -134,6 +135,8 @@ 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 =
|
||||||
|
|
@ -317,11 +320,14 @@ watch(
|
||||||
fetchPropertyList();
|
fetchPropertyList();
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
watch([() => pageState.inputSearch, propertyPageSize], () => {
|
watch(
|
||||||
propertyData.value = [];
|
[() => pageState.inputSearch, propertyPageSize, () => pageState.searchDate],
|
||||||
propertyPage.value = 1;
|
() => {
|
||||||
fetchPropertyList();
|
propertyData.value = [];
|
||||||
});
|
propertyPage.value = 1;
|
||||||
|
fetchPropertyList();
|
||||||
|
},
|
||||||
|
);
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<FloatingActionButton
|
<FloatingActionButton
|
||||||
|
|
@ -398,26 +404,44 @@ watch([() => pageState.inputSearch, propertyPageSize], () => {
|
||||||
<template #prepend>
|
<template #prepend>
|
||||||
<q-icon name="mdi-magnify" />
|
<q-icon name="mdi-magnify" />
|
||||||
</template>
|
</template>
|
||||||
<template v-if="$q.screen.lt.md" v-slot:append>
|
<template v-slot:append>
|
||||||
<span class="row">
|
<q-separator vertical inset class="q-mr-xs" />
|
||||||
<q-separator vertical />
|
<AdvanceSearch
|
||||||
<q-btn
|
v-model="pageState.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
|
||||||
|
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',
|
||||||
|
},
|
||||||
|
]"
|
||||||
/>
|
/>
|
||||||
</span>
|
</AdvanceSearch>
|
||||||
</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-show="$q.screen.gt.sm"
|
v-if="$q.screen.gt.sm"
|
||||||
ref="refFilter"
|
|
||||||
v-model="statusFilter"
|
v-model="statusFilter"
|
||||||
outlined
|
outlined
|
||||||
dense
|
dense
|
||||||
|
|
@ -512,11 +536,11 @@ watch([() => pageState.inputSearch, propertyPageSize], () => {
|
||||||
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"
|
v-if="pageState.total !== 0 || pageState.searchDate.length > 0"
|
||||||
:not-found="!!pageState.inputSearch"
|
:not-found="!!pageState.inputSearch"
|
||||||
/>
|
/>
|
||||||
<CreateButton
|
<CreateButton
|
||||||
v-if="pageState.total === 0"
|
v-if="pageState.total === 0 && pageState.searchDate.length === 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') }"
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ import { useI18n } from 'vue-i18n';
|
||||||
// NOTE: Import stores
|
// NOTE: Import stores
|
||||||
import useCustomerStore from 'stores/customer';
|
import useCustomerStore from 'stores/customer';
|
||||||
import { useQuotationStore } from 'src/stores/quotations';
|
import { useQuotationStore } from 'src/stores/quotations';
|
||||||
import { dialog, isRoleInclude, notify } from 'stores/utils';
|
import { dialog, isRoleInclude, notify, setPrefixName } from 'stores/utils';
|
||||||
import { useNavigator } from 'src/stores/navigator';
|
import { useNavigator } from 'src/stores/navigator';
|
||||||
import useFlowStore from 'src/stores/flow';
|
import useFlowStore from 'src/stores/flow';
|
||||||
import useMyBranch from 'stores/my-branch';
|
import useMyBranch from 'stores/my-branch';
|
||||||
|
|
@ -49,6 +49,7 @@ import { Quotation } from 'src/stores/quotations/types';
|
||||||
import TableQuotation from 'src/components/05_quotation/TableQuotation.vue';
|
import TableQuotation from 'src/components/05_quotation/TableQuotation.vue';
|
||||||
import PaginationPageSize from 'src/components/PaginationPageSize.vue';
|
import PaginationPageSize from 'src/components/PaginationPageSize.vue';
|
||||||
import { DialogContainer, DialogHeader } from 'src/components/dialog';
|
import { DialogContainer, DialogHeader } from 'src/components/dialog';
|
||||||
|
import AdvanceSearch from 'src/components/shared/AdvanceSearch.vue';
|
||||||
|
|
||||||
const { t, locale } = useI18n();
|
const { t, locale } = useI18n();
|
||||||
const $q = useQuasar();
|
const $q = useQuasar();
|
||||||
|
|
@ -109,6 +110,7 @@ const pageState = reactive({
|
||||||
quotationModal: false,
|
quotationModal: false,
|
||||||
employeeModal: false,
|
employeeModal: false,
|
||||||
receiptModal: false,
|
receiptModal: false,
|
||||||
|
searchDate: [],
|
||||||
});
|
});
|
||||||
|
|
||||||
pageState.fieldSelected = [...columnQuotation.map((v) => v.name)];
|
pageState.fieldSelected = [...columnQuotation.map((v) => v.name)];
|
||||||
|
|
@ -327,6 +329,8 @@ async function fetchQuotationList(mobileFetch?: boolean) {
|
||||||
: 'Issued',
|
: 'Issued',
|
||||||
query: pageState.inputSearch,
|
query: pageState.inputSearch,
|
||||||
urgentFirst: true,
|
urgentFirst: true,
|
||||||
|
startDate: pageState.searchDate[0],
|
||||||
|
endDate: pageState.searchDate[1],
|
||||||
});
|
});
|
||||||
|
|
||||||
if (ret) {
|
if (ret) {
|
||||||
|
|
@ -350,7 +354,12 @@ async function fetchQuotationList(mobileFetch?: boolean) {
|
||||||
}
|
}
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
[() => pageState.currentTab, () => pageState.inputSearch, quotationPageSize],
|
[
|
||||||
|
() => pageState.currentTab,
|
||||||
|
() => pageState.inputSearch,
|
||||||
|
() => pageState.searchDate,
|
||||||
|
quotationPageSize,
|
||||||
|
],
|
||||||
() => {
|
() => {
|
||||||
quotationPage.value = 1;
|
quotationPage.value = 1;
|
||||||
quotationData.value = [];
|
quotationData.value = [];
|
||||||
|
|
@ -517,6 +526,10 @@ async function storeDataLocal(id: string) {
|
||||||
<template #prepend>
|
<template #prepend>
|
||||||
<q-icon name="mdi-magnify" />
|
<q-icon name="mdi-magnify" />
|
||||||
</template>
|
</template>
|
||||||
|
<template v-slot:append>
|
||||||
|
<q-separator vertical inset class="q-mr-xs" />
|
||||||
|
<AdvanceSearch v-model="pageState.searchDate" />
|
||||||
|
</template>
|
||||||
</q-input>
|
</q-input>
|
||||||
|
|
||||||
<div class="row col-md-5 justify-end" style="white-space: nowrap">
|
<div class="row col-md-5 justify-end" style="white-space: nowrap">
|
||||||
|
|
@ -1008,7 +1021,16 @@ async function storeDataLocal(id: string) {
|
||||||
"
|
"
|
||||||
:title="
|
:title="
|
||||||
customerFormData.customerType === 'PERS'
|
customerFormData.customerType === 'PERS'
|
||||||
? `${customerFormData.customerBranch[0]?.firstName} ${customerFormData.customerBranch[0]?.lastName}`
|
? setPrefixName(
|
||||||
|
{
|
||||||
|
namePrefix: customerFormData.customerBranch[0]?.namePrefix,
|
||||||
|
firstName: customerFormData.customerBranch[0]?.firstName,
|
||||||
|
lastName: customerFormData.customerBranch[0]?.lastName,
|
||||||
|
firstNameEN: customerFormData.customerBranch[0]?.firstNameEN,
|
||||||
|
lastNameEN: customerFormData.customerBranch[0]?.lastNameEN,
|
||||||
|
},
|
||||||
|
{ locale },
|
||||||
|
)
|
||||||
: customerFormData.customerBranch[0]?.registerName
|
: customerFormData.customerBranch[0]?.registerName
|
||||||
"
|
"
|
||||||
:caption="
|
:caption="
|
||||||
|
|
@ -1117,13 +1139,13 @@ async function storeDataLocal(id: string) {
|
||||||
id="form-basic-info-customer"
|
id="form-basic-info-customer"
|
||||||
:onCreate="customerFormState.dialogType === 'create'"
|
:onCreate="customerFormState.dialogType === 'create'"
|
||||||
@edit="
|
@edit="
|
||||||
(customerFormState.dialogType = 'edit'),
|
((customerFormState.dialogType = 'edit'),
|
||||||
(customerFormState.readonly = false)
|
(customerFormState.readonly = false))
|
||||||
"
|
"
|
||||||
@cancel="() => customerFormUndo(false)"
|
@cancel="() => customerFormUndo(false)"
|
||||||
@delete="
|
@delete="
|
||||||
customerFormState.editCustomerId &&
|
customerFormState.editCustomerId &&
|
||||||
deleteCustomerById(customerFormState.editCustomerId)
|
deleteCustomerById(customerFormState.editCustomerId)
|
||||||
"
|
"
|
||||||
:customer-type="customerFormData.customerType"
|
:customer-type="customerFormData.customerType"
|
||||||
v-model:registered-branch-id="customerFormData.registeredBranchId"
|
v-model:registered-branch-id="customerFormData.registeredBranchId"
|
||||||
|
|
|
||||||
|
|
@ -1865,7 +1865,7 @@ function covertToNode() {
|
||||||
name: selectedWorker.map(
|
name: selectedWorker.map(
|
||||||
(v, i) =>
|
(v, i) =>
|
||||||
`${i + 1}. ` +
|
`${i + 1}. ` +
|
||||||
`${v.namePrefix}. ${v.firstNameEN} ${v.lastNameEN}`.toUpperCase(),
|
`${v.employeePassport.length !== 0 ? v.employeePassport[0].number + '_' : ''} ${v.namePrefix}.${v.firstNameEN ? `${v.firstNameEN} ${v.lastNameEN}` : `${v.firstName} ${v.lastName}`} `.toUpperCase(),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import { reactive, ref, watch, onMounted } from 'vue';
|
import { reactive, ref, watch, onMounted } from 'vue';
|
||||||
import { baseUrl, notify } from 'src/stores/utils';
|
import { baseUrl, notify, setPrefixName } from 'src/stores/utils';
|
||||||
|
|
||||||
// NOTE: Import stores
|
// NOTE: Import stores
|
||||||
import { dialog } from 'stores/utils';
|
import { dialog } from 'stores/utils';
|
||||||
|
|
@ -296,6 +296,42 @@ watch(
|
||||||
function setCurrentBranchId() {
|
function setCurrentBranchId() {
|
||||||
employeeFormState.value.currentBranchId = props.customerBranchId;
|
employeeFormState.value.currentBranchId = props.customerBranchId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
},
|
||||||
|
);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
@ -700,6 +736,20 @@ function setCurrentBranchId() {
|
||||||
"
|
"
|
||||||
:toggleTitle="$t('status.title')"
|
:toggleTitle="$t('status.title')"
|
||||||
hideFade
|
hideFade
|
||||||
|
:title="
|
||||||
|
currentFromDataEmployee
|
||||||
|
? setPrefixName(
|
||||||
|
{
|
||||||
|
namePrefix: currentFromDataEmployee.namePrefix,
|
||||||
|
firstName: currentFromDataEmployee.firstName,
|
||||||
|
lastName: currentFromDataEmployee.lastName,
|
||||||
|
firstNameEN: currentFromDataEmployee.firstNameEN,
|
||||||
|
lastNameEN: currentFromDataEmployee.lastNameEN,
|
||||||
|
},
|
||||||
|
{ locale },
|
||||||
|
)
|
||||||
|
: '-'
|
||||||
|
"
|
||||||
@view="
|
@view="
|
||||||
() => {
|
() => {
|
||||||
employeeFormState.imageDialog = true;
|
employeeFormState.imageDialog = true;
|
||||||
|
|
@ -996,6 +1046,9 @@ function setCurrentBranchId() {
|
||||||
title="form.field.basicInformation"
|
title="form.field.basicInformation"
|
||||||
:readonly="!employeeFormState.isEmployeeEdit"
|
:readonly="!employeeFormState.isEmployeeEdit"
|
||||||
v-model:customer-branch-id="employeeFormState.currentBranchId"
|
v-model:customer-branch-id="employeeFormState.currentBranchId"
|
||||||
|
v-model:current-customer-branch="
|
||||||
|
employeeFormState.currentCustomerBranch
|
||||||
|
"
|
||||||
v-model:employee-id="employeeFormState.currentEmployeeCode"
|
v-model:employee-id="employeeFormState.currentEmployeeCode"
|
||||||
v-model:nrc-no="currentFromDataEmployee.nrcNo"
|
v-model:nrc-no="currentFromDataEmployee.nrcNo"
|
||||||
v-model:code="currentFromDataEmployee.code"
|
v-model:code="currentFromDataEmployee.code"
|
||||||
|
|
|
||||||
|
|
@ -600,7 +600,7 @@ function print() {
|
||||||
details?.worker.map(
|
details?.worker.map(
|
||||||
(v, i) =>
|
(v, i) =>
|
||||||
`${i + 1}. ` +
|
`${i + 1}. ` +
|
||||||
`${v.namePrefix}. ${v.firstNameEN} ${v.lastNameEN}`.toUpperCase(),
|
`${v.namePrefix}. ${v.firstNameEN ? `${v.firstNameEN} ${v.lastNameEN}` : `${v.firstName} ${v.lastName}`} `.toUpperCase(),
|
||||||
) || [],
|
) || [],
|
||||||
},
|
},
|
||||||
}) || '-'
|
}) || '-'
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ import {
|
||||||
import AddressForm from 'src/components/form/AddressForm.vue';
|
import AddressForm from 'src/components/form/AddressForm.vue';
|
||||||
import ImageUploadDialog from 'src/components/ImageUploadDialog.vue';
|
import ImageUploadDialog from 'src/components/ImageUploadDialog.vue';
|
||||||
import { BankBook } from 'src/stores/branch/types';
|
import { BankBook } from 'src/stores/branch/types';
|
||||||
|
import QrCodeUploadDialog from 'src/components/QrCodeUploadDialog.vue';
|
||||||
|
|
||||||
const institutionStore = useInstitution();
|
const institutionStore = useInstitution();
|
||||||
|
|
||||||
|
|
@ -29,10 +30,14 @@ const drawerModel = defineModel<boolean>('drawerModel', {
|
||||||
required: true,
|
required: true,
|
||||||
default: false,
|
default: false,
|
||||||
});
|
});
|
||||||
const onCreateImageList = defineModel<{
|
const imageListOnCreate = defineModel<{
|
||||||
selectedImage: string;
|
selectedImage: string;
|
||||||
list: { url: string; imgFile: File | null; name: string }[];
|
list: { url: string; imgFile: File | null; name: string }[];
|
||||||
}>('onCreateImageList', { default: { selectedImage: '', list: [] } });
|
}>('imageListOnCreate', { default: { selectedImage: '', list: [] } });
|
||||||
|
const deletesStatusQrCodeBankImag = defineModel<number[]>(
|
||||||
|
'deletesStatusQrCodeBankImag',
|
||||||
|
{ default: [] },
|
||||||
|
);
|
||||||
|
|
||||||
const imageState = reactive({
|
const imageState = reactive({
|
||||||
imageDialog: false,
|
imageDialog: false,
|
||||||
|
|
@ -45,6 +50,14 @@ const imageState = reactive({
|
||||||
const imageFile = ref<File | null>(null);
|
const imageFile = ref<File | null>(null);
|
||||||
const imageList = ref<{ selectedImage: string; list: string[] }>();
|
const imageList = ref<{ selectedImage: string; list: string[] }>();
|
||||||
|
|
||||||
|
const qrCodeDialog = ref(false);
|
||||||
|
const qrCodeImageUrl = ref<string>('');
|
||||||
|
const currentIndexQrCodeBank = ref<number>(-1);
|
||||||
|
const statusQrCodeFile = ref<File | undefined>(undefined);
|
||||||
|
const refQrCodeUpload = ref();
|
||||||
|
const statusQrCodeUrl = ref<string>('');
|
||||||
|
const statusDeletesQrCode = ref<boolean>(false);
|
||||||
|
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
readonly?: boolean;
|
readonly?: boolean;
|
||||||
|
|
@ -159,10 +172,39 @@ async function submitImage(name: string) {
|
||||||
function clearImageState() {
|
function clearImageState() {
|
||||||
imageState.imageDialog = false;
|
imageState.imageDialog = false;
|
||||||
imageFile.value = null;
|
imageFile.value = null;
|
||||||
onCreateImageList.value = { selectedImage: '', list: [] };
|
imageListOnCreate.value = { selectedImage: '', list: [] };
|
||||||
imageState.refreshImageState = false;
|
imageState.refreshImageState = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function triggerEditQrCodeBank(opts?: { save?: boolean }) {
|
||||||
|
if (opts?.save === undefined) {
|
||||||
|
qrCodeDialog.value = true;
|
||||||
|
statusDeletesQrCode.value = false;
|
||||||
|
statusQrCodeUrl.value =
|
||||||
|
formBankBook.value[currentIndexQrCodeBank.value].bankUrl || '';
|
||||||
|
statusDeletesQrCode.value = false;
|
||||||
|
} else {
|
||||||
|
formBankBook.value[currentIndexQrCodeBank.value].bankUrl =
|
||||||
|
statusQrCodeUrl.value;
|
||||||
|
|
||||||
|
formBankBook.value[currentIndexQrCodeBank.value].bankQr =
|
||||||
|
statusQrCodeFile.value;
|
||||||
|
|
||||||
|
if (statusDeletesQrCode.value === true) {
|
||||||
|
deletesStatusQrCodeBankImag.value.push(currentIndexQrCodeBank.value);
|
||||||
|
}
|
||||||
|
if (statusDeletesQrCode.value === false) {
|
||||||
|
deletesStatusQrCodeBankImag.value =
|
||||||
|
deletesStatusQrCodeBankImag.value.filter(
|
||||||
|
(item) => item !== currentIndexQrCodeBank.value,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
currentIndexQrCodeBank.value = -1;
|
||||||
|
statusDeletesQrCode.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => imageFile.value,
|
() => imageFile.value,
|
||||||
() => {
|
() => {
|
||||||
|
|
@ -177,7 +219,6 @@ watch(
|
||||||
imageList.value
|
imageList.value
|
||||||
? (imageList.value.selectedImage = data.value.selectedImage || '')
|
? (imageList.value.selectedImage = data.value.selectedImage || '')
|
||||||
: '';
|
: '';
|
||||||
console.log(imageState.imageUrl);
|
|
||||||
imageState.refreshImageState = false;
|
imageState.refreshImageState = false;
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
@ -327,8 +368,19 @@ watch(
|
||||||
title="agencies.bankInfo"
|
title="agencies.bankInfo"
|
||||||
class="q-pt-xl"
|
class="q-pt-xl"
|
||||||
dense
|
dense
|
||||||
single
|
|
||||||
v-model:bank-book-list="formBankBook"
|
v-model:bank-book-list="formBankBook"
|
||||||
|
@view-qr="
|
||||||
|
(i) => {
|
||||||
|
currentIndexQrCodeBank = i;
|
||||||
|
triggerEditQrCodeBank();
|
||||||
|
}
|
||||||
|
"
|
||||||
|
@edit-qr="
|
||||||
|
(i) => {
|
||||||
|
currentIndexQrCodeBank = i;
|
||||||
|
refQrCodeUpload && refQrCodeUpload.browse();
|
||||||
|
}
|
||||||
|
"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -518,9 +570,20 @@ watch(
|
||||||
title="agencies.bankInfo"
|
title="agencies.bankInfo"
|
||||||
class="q-pt-xl"
|
class="q-pt-xl"
|
||||||
dense
|
dense
|
||||||
single
|
|
||||||
:readonly
|
:readonly
|
||||||
v-model:bank-book-list="formBankBook"
|
v-model:bank-book-list="formBankBook"
|
||||||
|
@view-qr="
|
||||||
|
(i) => {
|
||||||
|
currentIndexQrCodeBank = i;
|
||||||
|
triggerEditQrCodeBank();
|
||||||
|
}
|
||||||
|
"
|
||||||
|
@edit-qr="
|
||||||
|
(i) => {
|
||||||
|
currentIndexQrCodeBank = i;
|
||||||
|
refQrCodeUpload && refQrCodeUpload.browse();
|
||||||
|
}
|
||||||
|
"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -531,7 +594,7 @@ watch(
|
||||||
<ImageUploadDialog
|
<ImageUploadDialog
|
||||||
v-model:dialog-state="imageState.imageDialog"
|
v-model:dialog-state="imageState.imageDialog"
|
||||||
v-model:file="imageFile"
|
v-model:file="imageFile"
|
||||||
v-model:on-create-data-list="onCreateImageList"
|
v-model:on-create-data-list="imageListOnCreate"
|
||||||
v-model:image-url="imageState.imageUrl"
|
v-model:image-url="imageState.imageUrl"
|
||||||
v-model:data-list="imageList"
|
v-model:data-list="imageList"
|
||||||
:on-create="model"
|
:on-create="model"
|
||||||
|
|
@ -561,5 +624,31 @@ watch(
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</ImageUploadDialog>
|
</ImageUploadDialog>
|
||||||
|
|
||||||
|
<QrCodeUploadDialog
|
||||||
|
ref="refQrCodeUpload"
|
||||||
|
v-model:dialog-state="qrCodeDialog"
|
||||||
|
v-model:file="statusQrCodeFile as File"
|
||||||
|
v-model:image-url="statusQrCodeUrl"
|
||||||
|
@save="
|
||||||
|
(_file) => {
|
||||||
|
qrCodeDialog = false;
|
||||||
|
if (currentIndexQrCodeBank !== -1) {
|
||||||
|
triggerEditQrCodeBank({ save: true });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"
|
||||||
|
@clear="statusDeletesQrCode = true"
|
||||||
|
clearButton
|
||||||
|
>
|
||||||
|
<template #error>
|
||||||
|
<div
|
||||||
|
class="full-width full-height flex items-center justify-center"
|
||||||
|
style="color: gray"
|
||||||
|
>
|
||||||
|
<q-icon size="15rem" name="mdi-qrcode" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</QrCodeUploadDialog>
|
||||||
</template>
|
</template>
|
||||||
<style scoped></style>
|
<style scoped></style>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { QSelect, QTableProps } from 'quasar';
|
import { QTableProps } from 'quasar';
|
||||||
import { dialog } from 'src/stores/utils';
|
import { dialog } from 'src/stores/utils';
|
||||||
import { onMounted, reactive, ref, watch } from 'vue';
|
import { onMounted, reactive, ref, watch } from 'vue';
|
||||||
import { storeToRefs } from 'pinia';
|
import { storeToRefs } from 'pinia';
|
||||||
|
|
@ -21,6 +21,7 @@ import FloatingActionButton from 'src/components/FloatingActionButton.vue';
|
||||||
import CreateButton from 'src/components/AddButton.vue';
|
import CreateButton from 'src/components/AddButton.vue';
|
||||||
import NoData from 'src/components/NoData.vue';
|
import NoData from 'src/components/NoData.vue';
|
||||||
import AgenciesDialog from './AgenciesDialog.vue';
|
import AgenciesDialog from './AgenciesDialog.vue';
|
||||||
|
import AdvanceSearch from 'src/components/shared/AdvanceSearch.vue';
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const $q = useQuasar();
|
const $q = useQuasar();
|
||||||
|
|
@ -78,8 +79,9 @@ const pageState = reactive({
|
||||||
addModal: false,
|
addModal: false,
|
||||||
viewDrawer: false,
|
viewDrawer: false,
|
||||||
isDrawerEdit: true,
|
isDrawerEdit: true,
|
||||||
|
searchDate: [],
|
||||||
});
|
});
|
||||||
|
const deletesStatusQrCodeBankImag = ref<number[]>([]);
|
||||||
const blankFormData: InstitutionPayload = {
|
const blankFormData: InstitutionPayload = {
|
||||||
group: '',
|
group: '',
|
||||||
code: '',
|
code: '',
|
||||||
|
|
@ -114,11 +116,10 @@ const blankFormData: InstitutionPayload = {
|
||||||
};
|
};
|
||||||
|
|
||||||
const statusFilter = ref<'all' | 'statusACTIVE' | 'statusINACTIVE'>('all');
|
const statusFilter = ref<'all' | 'statusACTIVE' | 'statusINACTIVE'>('all');
|
||||||
const refFilter = ref<InstanceType<typeof QSelect>>();
|
|
||||||
const refAgenciesDialog = ref();
|
const refAgenciesDialog = ref();
|
||||||
const formData = ref<InstitutionPayload>(structuredClone(blankFormData));
|
const formData = ref<InstitutionPayload>(structuredClone(blankFormData));
|
||||||
const currAgenciesData = ref<Institution>();
|
const currAgenciesData = ref<Institution>();
|
||||||
const onCreateImageList = ref<{
|
const imageListOnCreate = ref<{
|
||||||
selectedImage: string;
|
selectedImage: string;
|
||||||
list: { url: string; imgFile: File | null; name: string }[];
|
list: { url: string; imgFile: File | null; name: string }[];
|
||||||
}>({ selectedImage: '', list: [] });
|
}>({ selectedImage: '', list: [] });
|
||||||
|
|
@ -176,16 +177,15 @@ function assignFormData(data: Institution) {
|
||||||
contactEmail: data.contactEmail,
|
contactEmail: data.contactEmail,
|
||||||
contactName: data.contactName,
|
contactName: data.contactName,
|
||||||
contactTel: data.contactTel,
|
contactTel: data.contactTel,
|
||||||
bank: [
|
bank: data.bank.map((v) => ({
|
||||||
{
|
bankName: v.bankName,
|
||||||
bankName: data.bank[0]?.bankName,
|
accountNumber: v.accountNumber,
|
||||||
accountNumber: data.bank[0]?.accountNumber,
|
bankBranch: v.bankBranch,
|
||||||
bankBranch: data.bank[0]?.bankBranch,
|
accountName: v.accountName,
|
||||||
accountName: data.bank[0]?.accountName,
|
accountType: v.accountType,
|
||||||
accountType: data.bank[0]?.accountType,
|
currentlyUse: v.currentlyUse,
|
||||||
currentlyUse: data.bank[0]?.currentlyUse,
|
bankUrl: `${baseUrl}/institution/${data.id}/bank-qr/${v.id}?ts=${Date.now()}`,
|
||||||
},
|
})),
|
||||||
],
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -211,14 +211,10 @@ async function submit(opt?: { selectedImage: string }) {
|
||||||
provinceId: formData.value.provinceId,
|
provinceId: formData.value.provinceId,
|
||||||
status: formData.value.status,
|
status: formData.value.status,
|
||||||
bank: formData.value.bank.map((v) => ({
|
bank: formData.value.bank.map((v) => ({
|
||||||
bankName: v.bankName,
|
...v,
|
||||||
accountNumber: v.accountNumber,
|
|
||||||
bankBranch: v.bankBranch,
|
|
||||||
accountName: v.accountName,
|
|
||||||
accountType: v.accountType,
|
|
||||||
currentlyUse: v.currentlyUse,
|
|
||||||
})),
|
})),
|
||||||
};
|
};
|
||||||
|
console.log('payload', payload);
|
||||||
if (
|
if (
|
||||||
(pageState.isDrawerEdit && currAgenciesData.value?.id) ||
|
(pageState.isDrawerEdit && currAgenciesData.value?.id) ||
|
||||||
(opt?.selectedImage && currAgenciesData.value?.id)
|
(opt?.selectedImage && currAgenciesData.value?.id)
|
||||||
|
|
@ -229,6 +225,7 @@ async function submit(opt?: { selectedImage: string }) {
|
||||||
id: currAgenciesData.value.id,
|
id: currAgenciesData.value.id,
|
||||||
selectedImage: opt?.selectedImage || undefined,
|
selectedImage: opt?.selectedImage || undefined,
|
||||||
}),
|
}),
|
||||||
|
{ indexDeleteQrCodeBank: deletesStatusQrCodeBankImag.value },
|
||||||
);
|
);
|
||||||
|
|
||||||
if (ret) {
|
if (ret) {
|
||||||
|
|
@ -248,7 +245,7 @@ async function submit(opt?: { selectedImage: string }) {
|
||||||
...payload,
|
...payload,
|
||||||
code: formData.value.group || '',
|
code: formData.value.group || '',
|
||||||
},
|
},
|
||||||
onCreateImageList.value,
|
imageListOnCreate.value,
|
||||||
);
|
);
|
||||||
|
|
||||||
await fetchData($q.screen.xs);
|
await fetchData($q.screen.xs);
|
||||||
|
|
@ -288,6 +285,8 @@ async function fetchData(mobileFetch?: boolean) {
|
||||||
: statusFilter.value === 'statusACTIVE'
|
: statusFilter.value === 'statusACTIVE'
|
||||||
? 'ACTIVE'
|
? 'ACTIVE'
|
||||||
: 'INACTIVE',
|
: 'INACTIVE',
|
||||||
|
startDate: pageState.searchDate[0],
|
||||||
|
endDate: pageState.searchDate[1],
|
||||||
});
|
});
|
||||||
|
|
||||||
if (ret) {
|
if (ret) {
|
||||||
|
|
@ -357,7 +356,7 @@ onMounted(async () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => [pageState.inputSearch, statusFilter.value],
|
() => [pageState.inputSearch, statusFilter.value, pageState.searchDate],
|
||||||
() => {
|
() => {
|
||||||
page.value = 1;
|
page.value = 1;
|
||||||
data.value = [];
|
data.value = [];
|
||||||
|
|
@ -440,26 +439,44 @@ watch(
|
||||||
<template #prepend>
|
<template #prepend>
|
||||||
<q-icon name="mdi-magnify" />
|
<q-icon name="mdi-magnify" />
|
||||||
</template>
|
</template>
|
||||||
<template v-if="$q.screen.lt.md" v-slot:append>
|
<template v-slot:append>
|
||||||
<span class="row">
|
<q-separator vertical inset class="q-mr-xs" />
|
||||||
<q-separator vertical />
|
<AdvanceSearch
|
||||||
<q-btn
|
v-model="pageState.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
|
||||||
|
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',
|
||||||
|
},
|
||||||
|
]"
|
||||||
/>
|
/>
|
||||||
</span>
|
</AdvanceSearch>
|
||||||
</template>
|
</template>
|
||||||
</q-input>
|
</q-input>
|
||||||
|
|
||||||
<div class="row col-md-5 justify-end" style="white-space: nowrap">
|
<div class="row col-md-5 justify-end" style="white-space: nowrap">
|
||||||
<q-select
|
<q-select
|
||||||
v-show="$q.screen.gt.sm"
|
v-if="$q.screen.gt.sm"
|
||||||
ref="refFilter"
|
|
||||||
v-model="statusFilter"
|
v-model="statusFilter"
|
||||||
outlined
|
outlined
|
||||||
dense
|
dense
|
||||||
|
|
@ -960,7 +977,8 @@ watch(
|
||||||
v-model:drawer-model="pageState.viewDrawer"
|
v-model:drawer-model="pageState.viewDrawer"
|
||||||
v-model:data="formData"
|
v-model:data="formData"
|
||||||
v-model:form-bank-book="formData.bank"
|
v-model:form-bank-book="formData.bank"
|
||||||
v-model:on-create-image-list="onCreateImageList"
|
v-model:image-list-on-create="imageListOnCreate"
|
||||||
|
v-model:deletes-status-qr-code-bank-imag="deletesStatusQrCodeBankImag"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
import { computed, onMounted, reactive, ref, watch } from 'vue';
|
import { computed, onMounted, reactive, ref, watch } from 'vue';
|
||||||
import { storeToRefs } from 'pinia';
|
import { storeToRefs } from 'pinia';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import { QSelect, useQuasar } from 'quasar';
|
import { useQuasar } from 'quasar';
|
||||||
|
|
||||||
// NOTE: Components
|
// NOTE: Components
|
||||||
import StatCardComponent from 'src/components/StatCardComponent.vue';
|
import StatCardComponent from 'src/components/StatCardComponent.vue';
|
||||||
|
|
@ -24,6 +24,9 @@ import { RequestData, RequestDataStatus } from 'src/stores/request-list/types';
|
||||||
import { dialogWarningClose } from 'src/stores/utils';
|
import { dialogWarningClose } from 'src/stores/utils';
|
||||||
import { CancelButton, SaveButton } from 'src/components/button';
|
import { CancelButton, SaveButton } from 'src/components/button';
|
||||||
import { getRole } from 'src/services/keycloak';
|
import { getRole } from 'src/services/keycloak';
|
||||||
|
import FloatingActionButton from 'src/components/FloatingActionButton.vue';
|
||||||
|
import RequestListAction from './RequestListAction .vue';
|
||||||
|
import AdvanceSearch from 'src/components/shared/AdvanceSearch.vue';
|
||||||
|
|
||||||
const $q = useQuasar();
|
const $q = useQuasar();
|
||||||
const navigatorStore = useNavigator();
|
const navigatorStore = useNavigator();
|
||||||
|
|
@ -32,7 +35,7 @@ const requestListStore = useRequestList();
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const { data, stats, page, pageMax, pageSize } = storeToRefs(requestListStore);
|
const { data, stats, page, pageMax, pageSize } = storeToRefs(requestListStore);
|
||||||
|
|
||||||
const refFilter = ref<InstanceType<typeof QSelect>>();
|
const requestListActionData = ref<RequestData[]>();
|
||||||
|
|
||||||
// NOTE: Variable
|
// NOTE: Variable
|
||||||
const pageState = reactive({
|
const pageState = reactive({
|
||||||
|
|
@ -45,6 +48,8 @@ const pageState = reactive({
|
||||||
rejectCancelDialog: false,
|
rejectCancelDialog: false,
|
||||||
rejectCancelReason: '',
|
rejectCancelReason: '',
|
||||||
requestId: '',
|
requestId: '',
|
||||||
|
requestListActionDialog: false,
|
||||||
|
searchDate: [],
|
||||||
});
|
});
|
||||||
|
|
||||||
const fieldSelectedOption = computed(() => {
|
const fieldSelectedOption = computed(() => {
|
||||||
|
|
@ -60,6 +65,8 @@ async function fetchList(opts?: { rotateFlowId?: boolean }) {
|
||||||
query: pageState.inputSearch,
|
query: pageState.inputSearch,
|
||||||
page: page.value,
|
page: page.value,
|
||||||
pageSize: pageSize.value,
|
pageSize: pageSize.value,
|
||||||
|
startDate: pageState.searchDate[0],
|
||||||
|
endDate: pageState.searchDate[1],
|
||||||
requestDataStatus:
|
requestDataStatus:
|
||||||
pageState.statusFilter === 'None' ? undefined : pageState.statusFilter,
|
pageState.statusFilter === 'None' ? undefined : pageState.statusFilter,
|
||||||
// responsibleOnly: true,
|
// responsibleOnly: true,
|
||||||
|
|
@ -131,6 +138,32 @@ async function submitRejectCancel() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function openRequestListDialog() {
|
||||||
|
const ret = await requestListStore.getRequestDataList({
|
||||||
|
page: 1,
|
||||||
|
pageSize: 999,
|
||||||
|
incomplete: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (ret) {
|
||||||
|
requestListActionData.value = ret.result;
|
||||||
|
}
|
||||||
|
|
||||||
|
pageState.requestListActionDialog = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function submitRequestListAction(data: {
|
||||||
|
form: { responsibleUserLocal: boolean; responsibleUserId: string };
|
||||||
|
selected: RequestData[];
|
||||||
|
}) {
|
||||||
|
const res = await requestListStore.updateMessenger(
|
||||||
|
data.selected.map((v) => v.id),
|
||||||
|
data.form.responsibleUserId,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (res) pageState.requestListActionDialog = false;
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
pageState.gridView = $q.screen.lt.md ? true : false;
|
pageState.gridView = $q.screen.lt.md ? true : false;
|
||||||
navigatorStore.current.title = 'requestList.title';
|
navigatorStore.current.title = 'requestList.title';
|
||||||
|
|
@ -140,13 +173,27 @@ onMounted(async () => {
|
||||||
await fetchList({ rotateFlowId: true });
|
await fetchList({ rotateFlowId: true });
|
||||||
});
|
});
|
||||||
|
|
||||||
watch([() => pageState.inputSearch, () => pageState.statusFilter], () => {
|
watch(
|
||||||
page.value = 1;
|
[
|
||||||
data.value = [];
|
() => pageState.inputSearch,
|
||||||
fetchList({ rotateFlowId: true });
|
() => pageState.statusFilter,
|
||||||
});
|
() => pageState.searchDate,
|
||||||
|
],
|
||||||
|
() => {
|
||||||
|
page.value = 1;
|
||||||
|
data.value = [];
|
||||||
|
fetchList({ rotateFlowId: true });
|
||||||
|
},
|
||||||
|
);
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
|
<FloatingActionButton
|
||||||
|
hide-icon
|
||||||
|
style="z-index: 999"
|
||||||
|
icon="mdi-account-outline"
|
||||||
|
@click.stop="openRequestListDialog"
|
||||||
|
></FloatingActionButton>
|
||||||
|
|
||||||
<div class="column full-height no-wrap">
|
<div class="column full-height no-wrap">
|
||||||
<!-- SEC: stat -->
|
<!-- SEC: stat -->
|
||||||
<section class="text-body-2 q-mb-xs flex items-center">
|
<section class="text-body-2 q-mb-xs flex items-center">
|
||||||
|
|
@ -239,26 +286,62 @@ watch([() => pageState.inputSearch, () => pageState.statusFilter], () => {
|
||||||
<template #prepend>
|
<template #prepend>
|
||||||
<q-icon name="mdi-magnify" />
|
<q-icon name="mdi-magnify" />
|
||||||
</template>
|
</template>
|
||||||
<template v-if="$q.screen.lt.md" v-slot:append>
|
<template v-slot:append>
|
||||||
<span class="row">
|
<q-separator vertical inset class="q-mr-xs" />
|
||||||
<q-separator vertical />
|
<AdvanceSearch
|
||||||
<q-btn
|
v-model="pageState.searchDate"
|
||||||
icon="mdi-filter-variant"
|
:active="$q.screen.lt.md && pageState.statusFilter !== 'None'"
|
||||||
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="pageState.statusFilter"
|
||||||
|
outlined
|
||||||
|
dense
|
||||||
|
option-value="value"
|
||||||
|
option-label="label"
|
||||||
|
map-options
|
||||||
|
emit-value
|
||||||
|
:for="'field-select-status'"
|
||||||
|
:options="[
|
||||||
|
{
|
||||||
|
label: $t('general.all'),
|
||||||
|
value: 'None',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: $t('requestList.status.Pending'),
|
||||||
|
value: RequestDataStatus.Pending,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: $t('requestList.status.Ready'),
|
||||||
|
value: RequestDataStatus.Ready,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: $t('requestList.status.InProgress'),
|
||||||
|
value: RequestDataStatus.InProgress,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: $t('requestList.status.Completed'),
|
||||||
|
value: RequestDataStatus.Completed,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: $t('requestList.status.Canceled'),
|
||||||
|
value: RequestDataStatus.Canceled,
|
||||||
|
},
|
||||||
|
]"
|
||||||
/>
|
/>
|
||||||
</span>
|
</AdvanceSearch>
|
||||||
</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-show="$q.screen.gt.sm"
|
v-if="$q.screen.gt.sm"
|
||||||
ref="refFilter"
|
|
||||||
v-model="pageState.statusFilter"
|
v-model="pageState.statusFilter"
|
||||||
outlined
|
outlined
|
||||||
dense
|
dense
|
||||||
|
|
@ -485,6 +568,13 @@ watch([() => pageState.inputSearch, () => pageState.statusFilter], () => {
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
</DialogFormContainer>
|
</DialogFormContainer>
|
||||||
|
|
||||||
|
<RequestListAction
|
||||||
|
v-if="requestListActionData"
|
||||||
|
v-model="pageState.requestListActionDialog"
|
||||||
|
:request-list="requestListActionData"
|
||||||
|
@submit="submitRequestListAction"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<style></style>
|
<style></style>
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ const props = defineProps<{
|
||||||
readonly?: boolean;
|
readonly?: boolean;
|
||||||
step: Step;
|
step: Step;
|
||||||
responsibleAreaDistrictId?: string;
|
responsibleAreaDistrictId?: string;
|
||||||
|
defaultMessenger?: string;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
|
|
@ -85,7 +86,8 @@ function assignToForm() {
|
||||||
companyDuty: attributesForm.value.companyDuty ?? false,
|
companyDuty: attributesForm.value.companyDuty ?? false,
|
||||||
companyDutyCost: attributesForm.value.companyDutyCost ?? 30,
|
companyDutyCost: attributesForm.value.companyDutyCost ?? 30,
|
||||||
responsibleUserLocal: attributesForm.value.responsibleUserLocal ?? true,
|
responsibleUserLocal: attributesForm.value.responsibleUserLocal ?? true,
|
||||||
responsibleUserId: attributesForm.value.responsibleUserId ?? '',
|
responsibleUserId:
|
||||||
|
attributesForm.value.responsibleUserId || props.defaultMessenger,
|
||||||
individualDuty: attributesForm.value.individualDuty ?? false,
|
individualDuty: attributesForm.value.individualDuty ?? false,
|
||||||
individualDutyCost: attributesForm.value.individualDutyCost ?? 10,
|
individualDutyCost: attributesForm.value.individualDutyCost ?? 10,
|
||||||
}),
|
}),
|
||||||
|
|
|
||||||
238
src/pages/08_request-list/RequestListAction .vue
Normal file
238
src/pages/08_request-list/RequestListAction .vue
Normal file
|
|
@ -0,0 +1,238 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { reactive, ref, watch } from 'vue';
|
||||||
|
import { RequestData } from 'src/stores/request-list';
|
||||||
|
import { DialogContainer, DialogHeader } from 'src/components/dialog';
|
||||||
|
import {
|
||||||
|
BackButton,
|
||||||
|
CancelButton,
|
||||||
|
MainButton,
|
||||||
|
SaveButton,
|
||||||
|
} from 'src/components/button';
|
||||||
|
import FormResponsibleUser from './FormResponsibleUser.vue';
|
||||||
|
import FormGroupHead from './FormGroupHead.vue';
|
||||||
|
import TableRequestList from './TableRequestList.vue';
|
||||||
|
import { column } from './constants';
|
||||||
|
import useAddressStore from 'src/stores/address';
|
||||||
|
|
||||||
|
defineProps<{
|
||||||
|
requestList: RequestData[];
|
||||||
|
}>();
|
||||||
|
|
||||||
|
defineEmits<{
|
||||||
|
(
|
||||||
|
e: 'submit',
|
||||||
|
data: {
|
||||||
|
form: { responsibleUserLocal: boolean; responsibleUserId: string };
|
||||||
|
selected: RequestData[];
|
||||||
|
},
|
||||||
|
): void;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
enum Step {
|
||||||
|
RequestList = 1,
|
||||||
|
Configure = 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
const open = defineModel<boolean>({ default: false });
|
||||||
|
const step = ref<Step>(Step.RequestList);
|
||||||
|
const selected = ref<RequestData[]>([]);
|
||||||
|
const listSameArea = ref<string[]>([]);
|
||||||
|
const form = reactive({
|
||||||
|
responsibleUserLocal: false,
|
||||||
|
responsibleUserId: '',
|
||||||
|
});
|
||||||
|
|
||||||
|
function reset() {
|
||||||
|
step.value = Step.RequestList;
|
||||||
|
selected.value = [];
|
||||||
|
form.responsibleUserLocal = false;
|
||||||
|
form.responsibleUserId = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function prev() {
|
||||||
|
step.value = Step.RequestList;
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => selected.value,
|
||||||
|
async () => {
|
||||||
|
if (selected.value.length === 1) {
|
||||||
|
const districtId = selected.value[0].quotation.customerBranch.districtId;
|
||||||
|
const ret = await useAddressStore().listSameOfficeArea(districtId);
|
||||||
|
if (ret) listSameArea.value = ret;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<DialogContainer v-model="open" :onOpen="reset">
|
||||||
|
<template #header>
|
||||||
|
<DialogHeader :title="$t('requestList.action.title')" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<div class="surface-0 q-pa-md">
|
||||||
|
<div class="stepper-wrapper">
|
||||||
|
<div class="stepper">
|
||||||
|
<template
|
||||||
|
v-for="(label, i) in [
|
||||||
|
$t('menu.product'),
|
||||||
|
$t('requestList.action.configure'),
|
||||||
|
]"
|
||||||
|
:key="i"
|
||||||
|
>
|
||||||
|
<span class="step" :class="{ ['step__active']: step > i }">
|
||||||
|
<span class="step-outer"><span class="step-inner" /></span>
|
||||||
|
<span class="step-label">{{ label }}</span>
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="step-connector"
|
||||||
|
:class="{ ['step-connector__active']: step > i + 1 }"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="surface-1 q-pa-md col full-width scroll">
|
||||||
|
<TableRequestList
|
||||||
|
v-if="step === Step.RequestList"
|
||||||
|
v-model:selected="selected"
|
||||||
|
hide-action
|
||||||
|
hide-view
|
||||||
|
checkable
|
||||||
|
:list-same-area="listSameArea"
|
||||||
|
:columns="column"
|
||||||
|
:rows="requestList"
|
||||||
|
:visible-columns="[...column.map((col) => col.name)]"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<template v-if="step === Step.Configure">
|
||||||
|
<q-expansion-item
|
||||||
|
dense
|
||||||
|
class="overflow-hidden bordered full-width"
|
||||||
|
switch-toggle-side
|
||||||
|
style="border-radius: var(--radius-2)"
|
||||||
|
expand-icon="mdi-chevron-down-circle"
|
||||||
|
header-class="surface-1 q-py-sm text-medium text-body1"
|
||||||
|
default-opened
|
||||||
|
>
|
||||||
|
<template #header>
|
||||||
|
<span>
|
||||||
|
{{ $t('requestList.employeeMessenger') }}
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
<FormGroupHead>
|
||||||
|
{{
|
||||||
|
$t('general.select', { msg: $t('requestList.employeeMessenger') })
|
||||||
|
}}
|
||||||
|
</FormGroupHead>
|
||||||
|
|
||||||
|
<FormResponsibleUser
|
||||||
|
:district-id="listSameArea[0]"
|
||||||
|
v-model:responsible-user-id="form.responsibleUserId"
|
||||||
|
v-model:responsible-user-local="form.responsibleUserLocal"
|
||||||
|
/>
|
||||||
|
</q-expansion-item>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<template #footer>
|
||||||
|
<div class="q-gutter-x-xs q-ml-auto">
|
||||||
|
<CancelButton
|
||||||
|
v-if="step === Step.RequestList"
|
||||||
|
id="btn-cancel"
|
||||||
|
outlined
|
||||||
|
@click="
|
||||||
|
reset();
|
||||||
|
open = false;
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
<BackButton
|
||||||
|
v-if="step === Step.Configure"
|
||||||
|
id="btn-back"
|
||||||
|
outlined
|
||||||
|
@click="prev"
|
||||||
|
/>
|
||||||
|
<MainButton
|
||||||
|
icon="mdi-check"
|
||||||
|
color="207 96% 32%"
|
||||||
|
solid
|
||||||
|
id="btn-next"
|
||||||
|
v-if="step === Step.RequestList"
|
||||||
|
@click="step = Step.Configure"
|
||||||
|
>
|
||||||
|
{{ $t('general.next') }}
|
||||||
|
</MainButton>
|
||||||
|
<SaveButton
|
||||||
|
v-if="step === Step.Configure"
|
||||||
|
id="btn-save"
|
||||||
|
solid
|
||||||
|
@click="$emit('submit', { form, selected })"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</DialogContainer>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.stepper {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
margin-inline: 25%;
|
||||||
|
|
||||||
|
& > .step {
|
||||||
|
--__color: var(--gray-5);
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
position: relative;
|
||||||
|
gap: 0.25rem;
|
||||||
|
|
||||||
|
&.step__active {
|
||||||
|
--__color: var(--brand-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
& > .step-label {
|
||||||
|
position: absolute;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--__color);
|
||||||
|
white-space: nowrap;
|
||||||
|
top: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
& > .step-outer {
|
||||||
|
display: inline-flex;
|
||||||
|
border: 2px solid var(--__color);
|
||||||
|
border-radius: 50%;
|
||||||
|
width: 1.5rem;
|
||||||
|
height: 1.5rem;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
& > .step-inner {
|
||||||
|
display: inline-block;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: var(--__color);
|
||||||
|
width: 0.7rem;
|
||||||
|
height: 0.7rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
& > .step-connector {
|
||||||
|
display: block;
|
||||||
|
border-bottom: 2px solid var(--gray-5);
|
||||||
|
flex-grow: 1;
|
||||||
|
|
||||||
|
&.step-connector__active {
|
||||||
|
border-color: var(--brand-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -54,6 +54,7 @@ import { Invoice } from 'src/stores/payment/types';
|
||||||
import { CreatedBy } from 'src/stores/types';
|
import { CreatedBy } from 'src/stores/types';
|
||||||
import { getUserId } from 'src/services/keycloak';
|
import { getUserId } from 'src/services/keycloak';
|
||||||
import { QuotationFull } from 'src/stores/quotations/types';
|
import { QuotationFull } from 'src/stores/quotations/types';
|
||||||
|
import useUserStore from 'src/stores/user';
|
||||||
|
|
||||||
const { locale, t } = useI18n();
|
const { locale, t } = useI18n();
|
||||||
|
|
||||||
|
|
@ -62,7 +63,9 @@ const route = useRoute();
|
||||||
const optionStore = useOptionStore();
|
const optionStore = useOptionStore();
|
||||||
const requestListStore = useRequestList();
|
const requestListStore = useRequestList();
|
||||||
const flowTemplateStore = useWorkflowTemplate();
|
const flowTemplateStore = useWorkflowTemplate();
|
||||||
|
const userStore = useUserStore();
|
||||||
|
|
||||||
|
const currentUserGroup = ref<string[]>([]);
|
||||||
const workList = ref<RequestWork[]>([]);
|
const workList = ref<RequestWork[]>([]);
|
||||||
const statusFile = ref<Attributes>({
|
const statusFile = ref<Attributes>({
|
||||||
customer: {},
|
customer: {},
|
||||||
|
|
@ -158,6 +161,10 @@ onMounted(async () => {
|
||||||
initTheme();
|
initTheme();
|
||||||
initLang();
|
initLang();
|
||||||
|
|
||||||
|
const result = await userStore.fetchUserGroup();
|
||||||
|
|
||||||
|
currentUserGroup.value = result.map((v) => v.name);
|
||||||
|
|
||||||
// get data
|
// get data
|
||||||
await getData();
|
await getData();
|
||||||
});
|
});
|
||||||
|
|
@ -283,26 +290,38 @@ async function triggerViewFile(opt: {
|
||||||
if (!opt.download) window.open(url, '_blank');
|
if (!opt.download) window.open(url, '_blank');
|
||||||
}
|
}
|
||||||
|
|
||||||
const responsiblePersonList = computed(() => {
|
const responsibleList = computed(() => {
|
||||||
const temp = workList.value?.reduce<Record<string, CreatedBy[]>>(
|
const temp = workList.value?.reduce<
|
||||||
(acc, curr: RequestWork) => {
|
Record<string, { user: CreatedBy[]; group: string[] }>
|
||||||
curr.productService.service?.workflow?.step.forEach((v) => {
|
>((acc, curr: RequestWork) => {
|
||||||
const key = v.order.toString();
|
curr.productService.service?.workflow?.step.forEach((v) => {
|
||||||
|
const key = v.order.toString();
|
||||||
|
const responsibleGroup = (
|
||||||
|
v.responsibleGroup as unknown as { group: string }[]
|
||||||
|
).map((v) => v.group);
|
||||||
|
|
||||||
if (!acc[key]) acc[key] = v.responsiblePerson.map((v) => v.user);
|
if (!acc[key]) {
|
||||||
|
acc[key] = {
|
||||||
|
user: v.responsiblePerson.map((v) => v.user),
|
||||||
|
group: responsibleGroup,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const current = acc[key];
|
const current = acc[key];
|
||||||
|
|
||||||
v.responsiblePerson.forEach((lhs) => {
|
v.responsiblePerson.forEach((lhs) => {
|
||||||
if (current.find((rhs) => rhs.id === lhs.userId)) return;
|
if (current.user.find((rhs) => rhs.id === lhs.userId)) return;
|
||||||
current.push(lhs.user);
|
current.user.push(lhs.user);
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return acc;
|
responsibleGroup.forEach((lhs) => {
|
||||||
},
|
if (current.group.find((rhs) => rhs === lhs)) return;
|
||||||
{},
|
current.group.push(lhs);
|
||||||
);
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
|
||||||
return temp || {};
|
return temp || {};
|
||||||
});
|
});
|
||||||
|
|
@ -438,6 +457,24 @@ async function submitRejectCancel() {
|
||||||
pageState.rejectCancelDialog = false;
|
pageState.rejectCancelDialog = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function toCustomer(customer: RequestData['quotation']['customerBranch']) {
|
||||||
|
const url = new URL(
|
||||||
|
`/customer-management?tab=customer&id=${customer.customerId}`,
|
||||||
|
window.location.origin,
|
||||||
|
);
|
||||||
|
|
||||||
|
window.open(url.toString(), '_blank');
|
||||||
|
}
|
||||||
|
|
||||||
|
function toEmployee(employee: RequestData['employee']) {
|
||||||
|
const url = new URL(
|
||||||
|
`/customer-management?tab=employee&id=${employee.id}`,
|
||||||
|
window.location.origin,
|
||||||
|
);
|
||||||
|
|
||||||
|
window.open(url.toString(), '_blank');
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div class="column surface-0 fullscreen" v-if="data">
|
<div class="column surface-0 fullscreen" v-if="data">
|
||||||
|
|
@ -478,11 +515,11 @@ async function submitRejectCancel() {
|
||||||
<span class="app-text-muted">{{ $t('flow.responsiblePerson') }}</span>
|
<span class="app-text-muted">{{ $t('flow.responsiblePerson') }}</span>
|
||||||
<span>
|
<span>
|
||||||
<template
|
<template
|
||||||
v-if="responsiblePersonList[pageState.currentStep]?.length"
|
v-if="responsibleList[pageState.currentStep]?.user.length"
|
||||||
>
|
>
|
||||||
<AvatarGroup
|
<AvatarGroup
|
||||||
:data="
|
:data="[
|
||||||
(responsiblePersonList[pageState.currentStep] || []).map(
|
...(responsibleList[pageState.currentStep].user || []).map(
|
||||||
(v) => ({
|
(v) => ({
|
||||||
name:
|
name:
|
||||||
$i18n.locale === 'eng'
|
$i18n.locale === 'eng'
|
||||||
|
|
@ -494,8 +531,12 @@ async function submitRejectCancel() {
|
||||||
: `/no-img-female.png`
|
: `/no-img-female.png`
|
||||||
: `${baseUrl}/user/${v.id}/profile-image/${v.selectedImage}`,
|
: `${baseUrl}/user/${v.id}/profile-image/${v.selectedImage}`,
|
||||||
}),
|
}),
|
||||||
)
|
),
|
||||||
"
|
...responsibleList[pageState.currentStep].group.map((g) => ({
|
||||||
|
name: `${$t('general.group')} ${g}`,
|
||||||
|
imgUrl: '/img-group.png',
|
||||||
|
})),
|
||||||
|
]"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>-</template>
|
<template v-else>-</template>
|
||||||
|
|
@ -701,6 +742,7 @@ async function submitRejectCancel() {
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<DataDisplay
|
<DataDisplay
|
||||||
|
clickable
|
||||||
class="col"
|
class="col"
|
||||||
icon="mdi-account-settings-outline"
|
icon="mdi-account-settings-outline"
|
||||||
:label="$t('customer.employer')"
|
:label="$t('customer.employer')"
|
||||||
|
|
@ -710,8 +752,10 @@ async function submitRejectCancel() {
|
||||||
noCode: true,
|
noCode: true,
|
||||||
}) || '-'
|
}) || '-'
|
||||||
"
|
"
|
||||||
|
@label-click="toCustomer(data.quotation.customerBranch)"
|
||||||
/>
|
/>
|
||||||
<DataDisplay
|
<DataDisplay
|
||||||
|
clickable
|
||||||
class="col"
|
class="col"
|
||||||
icon="mdi-account-settings-outline"
|
icon="mdi-account-settings-outline"
|
||||||
:label="$t('customer.employee')"
|
:label="$t('customer.employee')"
|
||||||
|
|
@ -720,6 +764,7 @@ async function submitRejectCancel() {
|
||||||
locale: $i18n.locale,
|
locale: $i18n.locale,
|
||||||
}) || '-'
|
}) || '-'
|
||||||
"
|
"
|
||||||
|
@label-click="toEmployee(data.employee)"
|
||||||
/>
|
/>
|
||||||
<DataDisplay
|
<DataDisplay
|
||||||
class="col"
|
class="col"
|
||||||
|
|
@ -776,11 +821,15 @@ async function submitRejectCancel() {
|
||||||
:cancel="data.requestDataStatus === RequestDataStatus.Canceled"
|
:cancel="data.requestDataStatus === RequestDataStatus.Canceled"
|
||||||
:readonly="
|
:readonly="
|
||||||
data.requestDataStatus === RequestDataStatus.Canceled ||
|
data.requestDataStatus === RequestDataStatus.Canceled ||
|
||||||
(responsiblePersonList &&
|
(responsibleList &&
|
||||||
!!responsiblePersonList[pageState.currentStep]?.length &&
|
!responsibleList[pageState.currentStep]?.user.find(
|
||||||
!responsiblePersonList[pageState.currentStep]?.find(
|
|
||||||
(v) => v.id === getUserId(),
|
(v) => v.id === getUserId(),
|
||||||
))
|
) &&
|
||||||
|
!responsibleList[pageState.currentStep]?.group.some((v) =>
|
||||||
|
currentUserGroup.includes(v),
|
||||||
|
)) ||
|
||||||
|
(!!responsibleList[pageState.currentStep]?.user?.length &&
|
||||||
|
!!responsibleList[pageState.currentStep]?.user?.length)
|
||||||
"
|
"
|
||||||
:order-able="value._messengerExpansion"
|
:order-able="value._messengerExpansion"
|
||||||
:installment-info="getInstallmentInfo()"
|
:installment-info="getInstallmentInfo()"
|
||||||
|
|
@ -873,6 +922,11 @@ async function submitRejectCancel() {
|
||||||
:readonly="
|
:readonly="
|
||||||
data.requestDataStatus === RequestDataStatus.Canceled
|
data.requestDataStatus === RequestDataStatus.Canceled
|
||||||
"
|
"
|
||||||
|
:default-messenger="
|
||||||
|
value.stepStatus[pageState.currentStep - 1]
|
||||||
|
? undefined
|
||||||
|
: data.defaultMessengerId
|
||||||
|
"
|
||||||
:step="{
|
:step="{
|
||||||
step: pageState.currentStep,
|
step: pageState.currentStep,
|
||||||
requestWorkId: value.id || '',
|
requestWorkId: value.id || '',
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ import useOptionStore from 'src/stores/options';
|
||||||
|
|
||||||
import KebabAction from 'src/components/shared/KebabAction.vue';
|
import KebabAction from 'src/components/shared/KebabAction.vue';
|
||||||
import { CreatedBy } from 'src/stores/types';
|
import { CreatedBy } from 'src/stores/types';
|
||||||
|
import { dateFormatJS } from 'src/utils/datetime';
|
||||||
|
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
|
|
@ -21,6 +22,9 @@ const props = withDefaults(
|
||||||
grid?: boolean;
|
grid?: boolean;
|
||||||
visibleColumns?: string[];
|
visibleColumns?: string[];
|
||||||
hideAction?: boolean;
|
hideAction?: boolean;
|
||||||
|
hideView?: boolean;
|
||||||
|
checkable?: boolean;
|
||||||
|
listSameArea?: string[];
|
||||||
}>(),
|
}>(),
|
||||||
{
|
{
|
||||||
row: () => [],
|
row: () => [],
|
||||||
|
|
@ -36,9 +40,16 @@ defineEmits<{
|
||||||
(e: 'rejectCancel', data: RequestData): void;
|
(e: 'rejectCancel', data: RequestData): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
function responsiblePerson(quotation: QuotationFull): CreatedBy[] | undefined {
|
const selected = defineModel<RequestData[]>('selected');
|
||||||
|
|
||||||
|
function responsiblePerson(quotation: QuotationFull) {
|
||||||
const productServiceList = quotation.productServiceList;
|
const productServiceList = quotation.productServiceList;
|
||||||
const tempPerson: CreatedBy[] = [];
|
const tempPerson: CreatedBy[] = [];
|
||||||
|
const tempGroup: {
|
||||||
|
group: string;
|
||||||
|
id: string;
|
||||||
|
workflowTemplateStepId: string;
|
||||||
|
}[] = [];
|
||||||
|
|
||||||
for (const v of productServiceList) {
|
for (const v of productServiceList) {
|
||||||
const tempStep = v.service?.workflow?.step;
|
const tempStep = v.service?.workflow?.step;
|
||||||
|
|
@ -49,7 +60,17 @@ function responsiblePerson(quotation: QuotationFull): CreatedBy[] | undefined {
|
||||||
tempPerson.push(rhs.user);
|
tempPerson.push(rhs.user);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return tempPerson;
|
tempStep.forEach((lhs) => {
|
||||||
|
const newGroup = lhs.responsibleGroup as unknown as {
|
||||||
|
group: string;
|
||||||
|
id: string;
|
||||||
|
workflowTemplateStepId: string;
|
||||||
|
}[];
|
||||||
|
for (const rhs of newGroup) {
|
||||||
|
tempGroup.push(rhs);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return { user: tempPerson, group: tempGroup };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -92,10 +113,39 @@ function getEmployeeName(
|
||||||
return (
|
return (
|
||||||
{
|
{
|
||||||
['eng']: `${useOptionStore().mapOption(employee?.namePrefix || '')} ${employee?.firstNameEN} ${employee?.lastNameEN}`,
|
['eng']: `${useOptionStore().mapOption(employee?.namePrefix || '')} ${employee?.firstNameEN} ${employee?.lastNameEN}`,
|
||||||
['tha']: `${useOptionStore().mapOption(employee?.namePrefix || '')} ${employee?.firstName} ${employee?.lastName}`,
|
['tha']: `${useOptionStore().mapOption(employee?.namePrefix || '')} ${employee?.firstName || employee?.firstNameEN} ${employee?.lastName || employee?.lastNameEN}`,
|
||||||
}[opts?.locale || 'eng'] || '-'
|
}[opts?.locale || 'eng'] || '-'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function toCustomer(customer: RequestData['quotation']['customerBranch']) {
|
||||||
|
const url = new URL(
|
||||||
|
`/customer-management?tab=customer&id=${customer.customerId}`,
|
||||||
|
window.location.origin,
|
||||||
|
);
|
||||||
|
|
||||||
|
window.open(url.toString(), '_blank');
|
||||||
|
}
|
||||||
|
|
||||||
|
function toEmployee(employee: RequestData['employee']) {
|
||||||
|
const url = new URL(
|
||||||
|
`/customer-management?tab=employee&id=${employee.id}`,
|
||||||
|
window.location.origin,
|
||||||
|
);
|
||||||
|
|
||||||
|
window.open(url.toString(), '_blank');
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleCheckAll() {
|
||||||
|
const filteredRows = props.rows.filter((row) =>
|
||||||
|
props.listSameArea?.includes(row.quotation.customerBranch.districtId),
|
||||||
|
);
|
||||||
|
if (selected.value.length === filteredRows.length) {
|
||||||
|
selected.value = [];
|
||||||
|
} else {
|
||||||
|
selected.value = filteredRows;
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<q-table
|
<q-table
|
||||||
|
|
@ -106,12 +156,36 @@ function getEmployeeName(
|
||||||
card-container-class="q-col-gutter-sm"
|
card-container-class="q-col-gutter-sm"
|
||||||
:rows-per-page-options="[0]"
|
:rows-per-page-options="[0]"
|
||||||
class="full-width"
|
class="full-width"
|
||||||
|
selection="multiple"
|
||||||
|
v-model:selected="selected"
|
||||||
|
:selected-rows-label="
|
||||||
|
(n) =>
|
||||||
|
$t('general.selected', {
|
||||||
|
number: n,
|
||||||
|
msg: $t('general.list'),
|
||||||
|
})
|
||||||
|
"
|
||||||
|
:no-data-label="$t('general.noDataTable')"
|
||||||
>
|
>
|
||||||
<template v-slot:header="props">
|
<template v-slot:header="props">
|
||||||
<q-tr
|
<q-tr
|
||||||
style="background-color: hsla(var(--info-bg) / 0.07)"
|
style="background-color: hsla(var(--info-bg) / 0.07)"
|
||||||
:props="props"
|
:props="props"
|
||||||
>
|
>
|
||||||
|
<q-th v-if="checkable">
|
||||||
|
<q-checkbox
|
||||||
|
v-if="selected.length > 0"
|
||||||
|
:model-value="
|
||||||
|
selected.length ===
|
||||||
|
rows.filter((row) =>
|
||||||
|
listSameArea?.includes(row.quotation.customerBranch.districtId),
|
||||||
|
).length
|
||||||
|
"
|
||||||
|
size="sm"
|
||||||
|
@click="handleCheckAll"
|
||||||
|
/>
|
||||||
|
<div v-else style="width: 35px; height: 35px"></div>
|
||||||
|
</q-th>
|
||||||
<q-th v-for="col in props.cols" :key="col.name" :props="props">
|
<q-th v-for="col in props.cols" :key="col.name" :props="props">
|
||||||
{{ col.label && $t(col.label) }}
|
{{ col.label && $t(col.label) }}
|
||||||
</q-th>
|
</q-th>
|
||||||
|
|
@ -125,9 +199,30 @@ function getEmployeeName(
|
||||||
} & Omit<Parameters<QTableSlots['body']>[0], 'row'>"
|
} & Omit<Parameters<QTableSlots['body']>[0], 'row'>"
|
||||||
>
|
>
|
||||||
<q-tr
|
<q-tr
|
||||||
:class="{ urgent: props.row.quotation.urgent, dark: $q.dark.isActive }"
|
:class="{
|
||||||
|
urgent: props.row.quotation.urgent,
|
||||||
|
dark: $q.dark.isActive,
|
||||||
|
'disabled-row':
|
||||||
|
selected &&
|
||||||
|
selected.length > 0 &&
|
||||||
|
!listSameArea.includes(
|
||||||
|
props.row.quotation.customerBranch.districtId,
|
||||||
|
),
|
||||||
|
}"
|
||||||
class="text-center"
|
class="text-center"
|
||||||
>
|
>
|
||||||
|
<q-td v-if="checkable">
|
||||||
|
<q-checkbox
|
||||||
|
:disable="
|
||||||
|
selected.length > 0 &&
|
||||||
|
!listSameArea.includes(
|
||||||
|
props.row.quotation.customerBranch.districtId,
|
||||||
|
)
|
||||||
|
"
|
||||||
|
v-model="props.selected"
|
||||||
|
size="sm"
|
||||||
|
/>
|
||||||
|
</q-td>
|
||||||
<q-td v-if="visibleColumns.includes('order')">
|
<q-td v-if="visibleColumns.includes('order')">
|
||||||
{{ props.rowIndex + 1 }}
|
{{ props.rowIndex + 1 }}
|
||||||
</q-td>
|
</q-td>
|
||||||
|
|
@ -138,21 +233,50 @@ function getEmployeeName(
|
||||||
</div>
|
</div>
|
||||||
</q-td>
|
</q-td>
|
||||||
<q-td v-if="visibleColumns.includes('employer')" class="text-left">
|
<q-td v-if="visibleColumns.includes('employer')" class="text-left">
|
||||||
{{
|
<span
|
||||||
getCustomerName(props.row, {
|
class="link"
|
||||||
noCode: true,
|
@click="toCustomer(props.row.quotation.customerBranch)"
|
||||||
locale: $i18n.locale,
|
>
|
||||||
}) || '-'
|
{{
|
||||||
}}
|
getCustomerName(props.row, {
|
||||||
|
noCode: true,
|
||||||
|
locale: $i18n.locale,
|
||||||
|
}) || '-'
|
||||||
|
}}
|
||||||
|
</span>
|
||||||
</q-td>
|
</q-td>
|
||||||
<q-td v-if="visibleColumns.includes('employee')" class="text-left">
|
<q-td v-if="visibleColumns.includes('employee')" class="text-left">
|
||||||
{{ getEmployeeName(props.row, { locale: $i18n.locale }) || '-' }}
|
<span class="link" @click="toEmployee(props.row.employee)">
|
||||||
|
{{ getEmployeeName(props.row, { locale: $i18n.locale }) || '-' }}
|
||||||
|
</span>
|
||||||
</q-td>
|
</q-td>
|
||||||
|
|
||||||
|
<q-td
|
||||||
|
v-if="visibleColumns.includes('employeePassport')"
|
||||||
|
class="text-left"
|
||||||
|
>
|
||||||
|
{{
|
||||||
|
props.row.employee.employeePassport.length !== 0
|
||||||
|
? props.row.employee.employeePassport[0].number
|
||||||
|
: '-'
|
||||||
|
}}
|
||||||
|
</q-td>
|
||||||
|
<q-td v-if="visibleColumns.includes('dataOffice')" class="text-left">
|
||||||
|
{{
|
||||||
|
$i18n.locale === 'eng'
|
||||||
|
? props.row.dataOffice.nameEN
|
||||||
|
: props.row.dataOffice.name
|
||||||
|
}}
|
||||||
|
</q-td>
|
||||||
|
<q-td v-if="visibleColumns.includes('createdAt')" class="text-left">
|
||||||
|
{{ dateFormatJS({ date: props.row.createdAt }) }}
|
||||||
|
</q-td>
|
||||||
|
|
||||||
<q-td v-if="visibleColumns.includes('quotationCode')">
|
<q-td v-if="visibleColumns.includes('quotationCode')">
|
||||||
{{ props.row.quotation.code || '-' }}
|
{{ props.row.quotation.code || '-' }}
|
||||||
</q-td>
|
</q-td>
|
||||||
<q-td v-if="visibleColumns.includes('responsiblePerson')">
|
<q-td v-if="visibleColumns.includes('responsiblePerson')">
|
||||||
<AvatarGroup
|
<!-- <AvatarGroup
|
||||||
:data="
|
:data="
|
||||||
responsiblePerson(props.row.quotation)?.map((v) => {
|
responsiblePerson(props.row.quotation)?.map((v) => {
|
||||||
return {
|
return {
|
||||||
|
|
@ -168,7 +292,26 @@ function getEmployeeName(
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
"
|
"
|
||||||
/>
|
/> -->
|
||||||
|
<AvatarGroup
|
||||||
|
:data="[
|
||||||
|
...responsiblePerson(props.row.quotation).user.map((v) => ({
|
||||||
|
name:
|
||||||
|
$i18n.locale === 'eng'
|
||||||
|
? `${v.firstNameEN} ${v.lastNameEN}`
|
||||||
|
: `${v.firstName} ${v.lastName}`,
|
||||||
|
imgUrl: !v.selectedImage
|
||||||
|
? v.gender === 'male'
|
||||||
|
? `/no-img-man.png`
|
||||||
|
: `/no-img-female.png`
|
||||||
|
: `${baseUrl}/user/${v.id}/profile-image/${v.selectedImage}`,
|
||||||
|
})),
|
||||||
|
...responsiblePerson(props.row.quotation).group.map((g) => ({
|
||||||
|
name: `${$t('general.group')} ${g.group}`,
|
||||||
|
imgUrl: '/img-group.png',
|
||||||
|
})),
|
||||||
|
]"
|
||||||
|
></AvatarGroup>
|
||||||
</q-td>
|
</q-td>
|
||||||
<q-td v-if="visibleColumns.includes('status')">
|
<q-td v-if="visibleColumns.includes('status')">
|
||||||
<BadgeComponent
|
<BadgeComponent
|
||||||
|
|
@ -215,6 +358,7 @@ function getEmployeeName(
|
||||||
</q-td>
|
</q-td>
|
||||||
<q-td class="text-right">
|
<q-td class="text-right">
|
||||||
<q-btn
|
<q-btn
|
||||||
|
v-if="!hideView"
|
||||||
:id="`btn-eye-${props.row.code}`"
|
:id="`btn-eye-${props.row.code}`"
|
||||||
icon="mdi-eye-outline"
|
icon="mdi-eye-outline"
|
||||||
size="sm"
|
size="sm"
|
||||||
|
|
@ -313,22 +457,29 @@ function getEmployeeName(
|
||||||
</div>
|
</div>
|
||||||
<div class="col-8">
|
<div class="col-8">
|
||||||
<AvatarGroup
|
<AvatarGroup
|
||||||
v-if="(responsiblePerson(props.row.quotation) ?? []).length > 0"
|
v-if="
|
||||||
:data="
|
(responsiblePerson(props.row.quotation).user ?? []).length >
|
||||||
responsiblePerson(props.row.quotation)?.map((v) => {
|
0 ||
|
||||||
return {
|
(responsiblePerson(props.row.quotation).group ?? []).length >
|
||||||
name:
|
0
|
||||||
$i18n.locale === 'eng'
|
|
||||||
? `${v.firstNameEN} ${v.lastNameEN}`
|
|
||||||
: `${v.firstName} ${v.lastName}`,
|
|
||||||
imgUrl: !v.selectedImage
|
|
||||||
? v.gender === 'male'
|
|
||||||
? `/no-img-man.png`
|
|
||||||
: `/no-img-female.png`
|
|
||||||
: `${baseUrl}/user/${v.id}/profile-image/${v.selectedImage}`,
|
|
||||||
};
|
|
||||||
})
|
|
||||||
"
|
"
|
||||||
|
:data="[
|
||||||
|
...responsiblePerson(props.row.quotation).user.map((v) => ({
|
||||||
|
name:
|
||||||
|
$i18n.locale === 'eng'
|
||||||
|
? `${v.firstNameEN} ${v.lastNameEN}`
|
||||||
|
: `${v.firstName} ${v.lastName}`,
|
||||||
|
imgUrl: !v.selectedImage
|
||||||
|
? v.gender === 'male'
|
||||||
|
? `/no-img-man.png`
|
||||||
|
: `/no-img-female.png`
|
||||||
|
: `${baseUrl}/user/${v.id}/profile-image/${v.selectedImage}`,
|
||||||
|
})),
|
||||||
|
...responsiblePerson(props.row.quotation).group.map((g) => ({
|
||||||
|
name: `${$t('general.group')} ${g.group}`,
|
||||||
|
imgUrl: '/img-group.png',
|
||||||
|
})),
|
||||||
|
]"
|
||||||
/>
|
/>
|
||||||
<span v-else>-</span>
|
<span v-else>-</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -406,4 +557,15 @@ function getEmployeeName(
|
||||||
background: var(--red-8);
|
background: var(--red-8);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.link {
|
||||||
|
color: hsl(var(--info-bg));
|
||||||
|
text-decoration: underline;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.disabled-row {
|
||||||
|
opacity: 0.3;
|
||||||
|
filter: grayscale(1);
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,24 @@ export const column = [
|
||||||
label: 'customer.employee',
|
label: 'customer.employee',
|
||||||
field: 'employee',
|
field: 'employee',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'employeePassport',
|
||||||
|
align: 'center',
|
||||||
|
label: 'customerEmployee.form.passportNo',
|
||||||
|
field: 'employeePassport',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'dataOffice',
|
||||||
|
align: 'center',
|
||||||
|
label: 'requestList.dataOffice',
|
||||||
|
field: 'dataOffice',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'createdAt',
|
||||||
|
align: 'center',
|
||||||
|
label: 'general.createdAt',
|
||||||
|
field: 'createdAt',
|
||||||
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
name: 'quotationCode',
|
name: 'quotationCode',
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,7 @@ import useFlowStore from 'src/stores/flow';
|
||||||
import { pageTabs, column, pageTabsReceive } from './constants';
|
import { pageTabs, column, pageTabsReceive } from './constants';
|
||||||
import { dialogWarningClose, isRoleInclude } from 'src/stores/utils';
|
import { dialogWarningClose, isRoleInclude } from 'src/stores/utils';
|
||||||
import { PaginationResult } from 'src/types';
|
import { PaginationResult } from 'src/types';
|
||||||
|
import AdvanceSearch from 'src/components/shared/AdvanceSearch.vue';
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const $q = useQuasar();
|
const $q = useQuasar();
|
||||||
|
|
@ -48,6 +49,7 @@ const pageState = reactive({
|
||||||
isMessenger: isRoleInclude(['messenger']),
|
isMessenger: isRoleInclude(['messenger']),
|
||||||
receiveDialog: false,
|
receiveDialog: false,
|
||||||
isReceiveScan: false,
|
isReceiveScan: false,
|
||||||
|
searchDate: [],
|
||||||
});
|
});
|
||||||
|
|
||||||
const taskOrderList = ref<TaskOrder[]>([]);
|
const taskOrderList = ref<TaskOrder[]>([]);
|
||||||
|
|
@ -69,6 +71,8 @@ async function fetchTaskOrderList(opts?: { page?: number; pageSize?: number }) {
|
||||||
pageSize: opts?.pageSize || pageSize.value,
|
pageSize: opts?.pageSize || pageSize.value,
|
||||||
query: pageState.inputSearch === '' ? undefined : pageState.inputSearch,
|
query: pageState.inputSearch === '' ? undefined : pageState.inputSearch,
|
||||||
userTaskStatus: pageState.currentTab as UserTaskStatus,
|
userTaskStatus: pageState.currentTab as UserTaskStatus,
|
||||||
|
startDate: pageState.searchDate[0],
|
||||||
|
endDate: pageState.searchDate[1],
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
res = await taskOrderStore.getTaskOrderList({
|
res = await taskOrderStore.getTaskOrderList({
|
||||||
|
|
@ -76,6 +80,8 @@ async function fetchTaskOrderList(opts?: { page?: number; pageSize?: number }) {
|
||||||
pageSize: opts?.pageSize || pageSize.value,
|
pageSize: opts?.pageSize || pageSize.value,
|
||||||
query: pageState.inputSearch === '' ? undefined : pageState.inputSearch,
|
query: pageState.inputSearch === '' ? undefined : pageState.inputSearch,
|
||||||
taskOrderStatus: pageState.currentTab as TaskOrderStatus | undefined,
|
taskOrderStatus: pageState.currentTab as TaskOrderStatus | undefined,
|
||||||
|
startDate: pageState.searchDate[0],
|
||||||
|
endDate: pageState.searchDate[1],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (res) {
|
if (res) {
|
||||||
|
|
@ -157,6 +163,7 @@ watch(
|
||||||
() => pageState.inputSearch,
|
() => pageState.inputSearch,
|
||||||
() => pageSize.value,
|
() => pageSize.value,
|
||||||
() => pageState.statusFilter,
|
() => pageState.statusFilter,
|
||||||
|
() => pageState.searchDate,
|
||||||
],
|
],
|
||||||
() => {
|
() => {
|
||||||
fetchTaskOrderList();
|
fetchTaskOrderList();
|
||||||
|
|
@ -299,6 +306,10 @@ watch(
|
||||||
<template #prepend>
|
<template #prepend>
|
||||||
<q-icon name="mdi-magnify" />
|
<q-icon name="mdi-magnify" />
|
||||||
</template>
|
</template>
|
||||||
|
<template v-slot:append>
|
||||||
|
<q-separator vertical inset class="q-mr-xs" />
|
||||||
|
<AdvanceSearch v-model="pageState.searchDate" />
|
||||||
|
</template>
|
||||||
</q-input>
|
</q-input>
|
||||||
|
|
||||||
<div class="row col-md-5 justify-end" style="white-space: nowrap">
|
<div class="row col-md-5 justify-end" style="white-space: nowrap">
|
||||||
|
|
|
||||||
|
|
@ -160,7 +160,12 @@ const emit = defineEmits<{
|
||||||
</q-tooltip>
|
</q-tooltip>
|
||||||
</div>
|
</div>
|
||||||
<div class="text-caption app-text-muted">
|
<div class="text-caption app-text-muted">
|
||||||
{{ props.row.code || '-' }}
|
{{
|
||||||
|
(props.row.taskOrderStatus === TaskOrderStatus.Complete &&
|
||||||
|
props.row.codeProductReceived
|
||||||
|
? props.row.codeProductReceived
|
||||||
|
: props.row.code) || '-'
|
||||||
|
}}
|
||||||
</div>
|
</div>
|
||||||
</q-td>
|
</q-td>
|
||||||
<q-td v-if="visibleColumns.includes('issueBranch')">
|
<q-td v-if="visibleColumns.includes('issueBranch')">
|
||||||
|
|
|
||||||
|
|
@ -227,6 +227,12 @@ export const productColumn = [
|
||||||
label: 'taskOrder.productList',
|
label: 'taskOrder.productList',
|
||||||
field: 'productList',
|
field: 'productList',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'status',
|
||||||
|
align: 'center',
|
||||||
|
label: 'general.status',
|
||||||
|
field: 'status',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'amountOfEmployee',
|
name: 'amountOfEmployee',
|
||||||
align: 'center',
|
align: 'center',
|
||||||
|
|
|
||||||
|
|
@ -294,7 +294,7 @@ function closeAble() {
|
||||||
:branch="branch"
|
:branch="branch"
|
||||||
:institution="data.institution"
|
:institution="data.institution"
|
||||||
:details="{
|
:details="{
|
||||||
code: data.code,
|
code: data.codeProductReceived ?? data.code,
|
||||||
name: data.taskName,
|
name: data.taskName,
|
||||||
contactName: data.contactName,
|
contactName: data.contactName,
|
||||||
contactTel: data.contactTel,
|
contactTel: data.contactTel,
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,8 @@ import { baseUrl, formatNumberDecimal, commaInput } from 'src/stores/utils';
|
||||||
import { precisionRound } from 'src/utils/arithmetic';
|
import { precisionRound } from 'src/utils/arithmetic';
|
||||||
import { useConfigStore } from 'stores/config';
|
import { useConfigStore } from 'stores/config';
|
||||||
import { storeToRefs } from 'pinia';
|
import { storeToRefs } from 'pinia';
|
||||||
|
import BadgeComponent from 'src/components/BadgeComponent.vue';
|
||||||
|
import { TaskStatus } from 'src/stores/task-order/types';
|
||||||
|
|
||||||
const currentBtnOpen = ref<boolean[]>([]);
|
const currentBtnOpen = ref<boolean[]>([]);
|
||||||
const configStore = useConfigStore();
|
const configStore = useConfigStore();
|
||||||
|
|
@ -30,7 +32,10 @@ const props = defineProps<{
|
||||||
readonly?: boolean;
|
readonly?: boolean;
|
||||||
agentPrice?: boolean;
|
agentPrice?: boolean;
|
||||||
taskList: {
|
taskList: {
|
||||||
product: RequestWork['productService']['product'];
|
product: RequestWork['productService']['product'] & {
|
||||||
|
taskStatus?: TaskStatus;
|
||||||
|
totalNotStatusComplete?: number;
|
||||||
|
};
|
||||||
list: RequestWork[];
|
list: RequestWork[];
|
||||||
}[];
|
}[];
|
||||||
creditNote?: boolean;
|
creditNote?: boolean;
|
||||||
|
|
@ -111,6 +116,26 @@ function calcPrice(
|
||||||
|
|
||||||
return precisionRound(priceNoVat * amount + rawVatTotal);
|
return precisionRound(priceNoVat * amount + rawVatTotal);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function taskOrderStatus(value: TaskStatus) {
|
||||||
|
if ([TaskStatus.Pending].includes(value)) {
|
||||||
|
return '--blue-6-hsl';
|
||||||
|
}
|
||||||
|
if ([TaskStatus.InProgress, TaskStatus.Validate].includes(value)) {
|
||||||
|
return '--orange-5-hsl';
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
[
|
||||||
|
TaskStatus.Canceled,
|
||||||
|
TaskStatus.Restart,
|
||||||
|
TaskStatus.Redo,
|
||||||
|
TaskStatus.Failed,
|
||||||
|
].includes(value)
|
||||||
|
) {
|
||||||
|
return '--red-5-hsl';
|
||||||
|
}
|
||||||
|
return '--green-8-hsl';
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<q-expansion-item
|
<q-expansion-item
|
||||||
|
|
@ -144,7 +169,8 @@ function calcPrice(
|
||||||
(v) =>
|
(v) =>
|
||||||
v.name !== 'discount' &&
|
v.name !== 'discount' &&
|
||||||
v.name !== 'priceBeforeVat' &&
|
v.name !== 'priceBeforeVat' &&
|
||||||
v.name !== 'vat',
|
v.name !== 'vat' &&
|
||||||
|
v.name !== 'status',
|
||||||
)
|
)
|
||||||
: productColumn
|
: productColumn
|
||||||
"
|
"
|
||||||
|
|
@ -173,7 +199,10 @@ function calcPrice(
|
||||||
<template
|
<template
|
||||||
v-slot:body="props: {
|
v-slot:body="props: {
|
||||||
row: {
|
row: {
|
||||||
product: RequestWork['productService']['product'];
|
product: RequestWork['productService']['product'] & {
|
||||||
|
taskStatus?: TaskStatus;
|
||||||
|
totalNotStatusComplete?: number;
|
||||||
|
};
|
||||||
list: RequestWork[];
|
list: RequestWork[];
|
||||||
};
|
};
|
||||||
} & Omit<Parameters<QTableSlots['body']>[0], 'row'>"
|
} & Omit<Parameters<QTableSlots['body']>[0], 'row'>"
|
||||||
|
|
@ -203,6 +232,14 @@ function calcPrice(
|
||||||
</q-avatar>
|
</q-avatar>
|
||||||
{{ props.row.product.name }}
|
{{ props.row.product.name }}
|
||||||
</q-td>
|
</q-td>
|
||||||
|
<q-td class="text-left" v-if="!creditNote">
|
||||||
|
<BadgeComponent
|
||||||
|
hide-icon
|
||||||
|
:hsla-color="taskOrderStatus(props.row.product.taskStatus)"
|
||||||
|
:title="`${$t(`taskOrder.status.${props.row.product.taskStatus}`)} ${!!props.row.product.totalNotStatusComplete ? $t('general.totalPeople', { meg: props.row.product.totalNotStatusComplete }) : ''}`"
|
||||||
|
/>
|
||||||
|
</q-td>
|
||||||
|
|
||||||
<q-td>
|
<q-td>
|
||||||
{{ props.row.list.length }}
|
{{ props.row.list.length }}
|
||||||
</q-td>
|
</q-td>
|
||||||
|
|
|
||||||
|
|
@ -280,7 +280,10 @@ let taskListGroup = computed(() => {
|
||||||
|
|
||||||
const cacheData = currentFormData.value.taskList.reduce<
|
const cacheData = currentFormData.value.taskList.reduce<
|
||||||
{
|
{
|
||||||
product: RequestWork['productService']['product'];
|
product: RequestWork['productService']['product'] & {
|
||||||
|
taskStatus?: TaskStatus;
|
||||||
|
totalNotStatusComplete?: number;
|
||||||
|
};
|
||||||
list: (RequestWork & {
|
list: (RequestWork & {
|
||||||
_template?: {
|
_template?: {
|
||||||
id: string;
|
id: string;
|
||||||
|
|
@ -289,15 +292,15 @@ let taskListGroup = computed(() => {
|
||||||
step: number;
|
step: number;
|
||||||
responsibleInstitution: (string | { group: string })[];
|
responsibleInstitution: (string | { group: string })[];
|
||||||
} | null;
|
} | null;
|
||||||
|
taskStatus?: TaskStatus;
|
||||||
|
failedComment?: string;
|
||||||
|
failedType?: string;
|
||||||
})[];
|
})[];
|
||||||
}[]
|
}[]
|
||||||
>((acc, curr) => {
|
>((acc, curr) => {
|
||||||
if (
|
const isNotComplete =
|
||||||
fullTaskOrder.value?.taskOrderStatus === TaskOrderStatus.Complete &&
|
fullTaskOrder.value?.taskOrderStatus === TaskOrderStatus.Complete &&
|
||||||
curr.taskStatus !== TaskStatus.Complete
|
curr.taskStatus !== TaskStatus.Complete;
|
||||||
) {
|
|
||||||
return acc;
|
|
||||||
}
|
|
||||||
|
|
||||||
const task = curr.requestWorkStep;
|
const task = curr.requestWorkStep;
|
||||||
const step = curr.step;
|
const step = curr.step;
|
||||||
|
|
@ -308,9 +311,18 @@ let taskListGroup = computed(() => {
|
||||||
let exist = acc.find(
|
let exist = acc.find(
|
||||||
(item) => task.requestWork.productService.productId == item.product.id,
|
(item) => task.requestWork.productService.productId == item.product.id,
|
||||||
);
|
);
|
||||||
const record = Object.assign(task.requestWork, {
|
|
||||||
_template: getTemplateData(task.requestWork, step),
|
const record = Object.assign(
|
||||||
});
|
{
|
||||||
|
...task.requestWork,
|
||||||
|
taskStatus: curr.taskStatus,
|
||||||
|
failedComment: curr.failedComment || '',
|
||||||
|
failedType: curr.failedType || '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
_template: getTemplateData(task.requestWork, step),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
const template = getTemplateData(task.requestWork, step);
|
const template = getTemplateData(task.requestWork, step);
|
||||||
|
|
||||||
|
|
@ -323,10 +335,18 @@ let taskListGroup = computed(() => {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (exist) {
|
if (exist) {
|
||||||
exist.list.push(task.requestWork);
|
exist.list.push(record);
|
||||||
|
if (isNotComplete) {
|
||||||
|
exist.product.totalNotStatusComplete =
|
||||||
|
(exist.product.totalNotStatusComplete || undefined) + 1;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
acc.push({
|
acc.push({
|
||||||
product: task.requestWork.productService.product,
|
product: {
|
||||||
|
...task.requestWork.productService.product,
|
||||||
|
taskStatus: curr.taskStatus || TaskStatus.Pending,
|
||||||
|
totalNotStatusComplete: isNotComplete ? 1 : undefined,
|
||||||
|
},
|
||||||
list: [record],
|
list: [record],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -897,9 +917,14 @@ watch(
|
||||||
v-model:registered-branch-id="currentFormData.registeredBranchId"
|
v-model:registered-branch-id="currentFormData.registeredBranchId"
|
||||||
v-model:institution-id="currentFormData.institutionId"
|
v-model:institution-id="currentFormData.institutionId"
|
||||||
v-model:task-name="currentFormData.taskName"
|
v-model:task-name="currentFormData.taskName"
|
||||||
v-model:code="currentFormData.code"
|
|
||||||
v-model:contact-name="currentFormData.contactName"
|
v-model:contact-name="currentFormData.contactName"
|
||||||
v-model:contact-tel="currentFormData.contactTel"
|
v-model:contact-tel="currentFormData.contactTel"
|
||||||
|
:code="
|
||||||
|
view === TaskOrderStatus.Complete &&
|
||||||
|
currentFormData.codeProductReceived
|
||||||
|
? currentFormData.codeProductReceived
|
||||||
|
: currentFormData.code
|
||||||
|
"
|
||||||
:task-list-group="
|
:task-list-group="
|
||||||
taskListGroup.length === 0 && state.mode === 'create'
|
taskListGroup.length === 0 && state.mode === 'create'
|
||||||
"
|
"
|
||||||
|
|
@ -985,6 +1010,7 @@ watch(
|
||||||
"
|
"
|
||||||
/>
|
/>
|
||||||
<!-- TODO: blind remark, urgent -->
|
<!-- TODO: blind remark, urgent -->
|
||||||
|
{{ console.log(taskListGroup) }}
|
||||||
<RemarkExpansion
|
<RemarkExpansion
|
||||||
v-if="
|
v-if="
|
||||||
view === TaskOrderStatus.Pending ||
|
view === TaskOrderStatus.Pending ||
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
// NOTE: Library
|
// NOTE: Library
|
||||||
import { computed, onMounted, reactive, ref, watch } from 'vue';
|
import { computed, onMounted, reactive, ref, watch } from 'vue';
|
||||||
import { storeToRefs } from 'pinia';
|
import { storeToRefs } from 'pinia';
|
||||||
import { QSelect, useQuasar } from 'quasar';
|
import { useQuasar } from 'quasar';
|
||||||
|
|
||||||
// NOTE: Components
|
// NOTE: Components
|
||||||
import StatCardComponent from 'src/components/StatCardComponent.vue';
|
import StatCardComponent from 'src/components/StatCardComponent.vue';
|
||||||
|
|
@ -18,13 +18,13 @@ import { columns, hslaColors } from './constants';
|
||||||
import useFlowStore from 'src/stores/flow';
|
import useFlowStore from 'src/stores/flow';
|
||||||
import { useInvoice } from 'src/stores/payment';
|
import { useInvoice } from 'src/stores/payment';
|
||||||
import { Invoice, PaymentDataStatus } from 'src/stores/payment/types';
|
import { Invoice, PaymentDataStatus } from 'src/stores/payment/types';
|
||||||
|
import AdvanceSearch from 'src/components/shared/AdvanceSearch.vue';
|
||||||
|
|
||||||
const $q = useQuasar();
|
const $q = useQuasar();
|
||||||
const navigatorStore = useNavigator();
|
const navigatorStore = useNavigator();
|
||||||
const flowStore = useFlowStore();
|
const flowStore = useFlowStore();
|
||||||
const invoiceStore = useInvoice();
|
const invoiceStore = useInvoice();
|
||||||
const { data, stats, page, pageMax, pageSize } = storeToRefs(invoiceStore);
|
const { data, stats, page, pageMax, pageSize } = storeToRefs(invoiceStore);
|
||||||
const refFilter = ref<InstanceType<typeof QSelect>>();
|
|
||||||
|
|
||||||
// NOTE: Variable
|
// NOTE: Variable
|
||||||
const pageState = reactive({
|
const pageState = reactive({
|
||||||
|
|
@ -34,6 +34,7 @@ const pageState = reactive({
|
||||||
fieldSelected: [...columns.map((v) => v.name)],
|
fieldSelected: [...columns.map((v) => v.name)],
|
||||||
gridView: false,
|
gridView: false,
|
||||||
total: 0,
|
total: 0,
|
||||||
|
searchDate: [],
|
||||||
});
|
});
|
||||||
|
|
||||||
const fieldSelectedOption = computed(() => {
|
const fieldSelectedOption = computed(() => {
|
||||||
|
|
@ -56,6 +57,8 @@ async function fetchList(opts?: { rotateFlowId?: boolean }) {
|
||||||
: undefined,
|
: undefined,
|
||||||
quotationOnly: true,
|
quotationOnly: true,
|
||||||
debitNoteOnly: false,
|
debitNoteOnly: false,
|
||||||
|
startDate: pageState.searchDate[0],
|
||||||
|
endDate: pageState.searchDate[1],
|
||||||
});
|
});
|
||||||
if (ret) {
|
if (ret) {
|
||||||
data.value = $q.screen.xs ? [...data.value, ...ret.result] : ret.result;
|
data.value = $q.screen.xs ? [...data.value, ...ret.result] : ret.result;
|
||||||
|
|
@ -89,8 +92,6 @@ function triggerView(opts: { quotationId: string }) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function viewDocExample(quotationId: string, codeInvoice: string) {
|
function viewDocExample(quotationId: string, codeInvoice: string) {
|
||||||
console.log(codeInvoice);
|
|
||||||
|
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
'quotation-preview',
|
'quotation-preview',
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
|
|
@ -124,6 +125,7 @@ watch(
|
||||||
() => pageState.inputSearch,
|
() => pageState.inputSearch,
|
||||||
() => pageState.statusFilter,
|
() => pageState.statusFilter,
|
||||||
() => pageSize.value,
|
() => pageSize.value,
|
||||||
|
() => pageState.searchDate,
|
||||||
],
|
],
|
||||||
() => {
|
() => {
|
||||||
page.value = 1;
|
page.value = 1;
|
||||||
|
|
@ -207,26 +209,50 @@ watch(
|
||||||
<template #prepend>
|
<template #prepend>
|
||||||
<q-icon name="mdi-magnify" />
|
<q-icon name="mdi-magnify" />
|
||||||
</template>
|
</template>
|
||||||
<template v-if="$q.screen.lt.md" v-slot:append>
|
<template v-slot:append>
|
||||||
<span class="row">
|
<q-separator vertical inset class="q-mr-xs" />
|
||||||
<q-separator vertical />
|
<AdvanceSearch
|
||||||
<q-btn
|
v-model="pageState.searchDate"
|
||||||
icon="mdi-filter-variant"
|
:active="$q.screen.lt.md && pageState.statusFilter !== 'None'"
|
||||||
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="pageState.statusFilter"
|
||||||
|
outlined
|
||||||
|
dense
|
||||||
|
option-value="value"
|
||||||
|
option-label="label"
|
||||||
|
map-options
|
||||||
|
emit-value
|
||||||
|
:for="'field-select-status'"
|
||||||
|
:options="[
|
||||||
|
{
|
||||||
|
label: $t('general.all'),
|
||||||
|
value: 'None',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: $t('invoice.status.PaymentWait'),
|
||||||
|
value: PaymentDataStatus.Wait,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: $t('invoice.status.PaymentSuccess'),
|
||||||
|
value: PaymentDataStatus.Success,
|
||||||
|
},
|
||||||
|
]"
|
||||||
/>
|
/>
|
||||||
</span>
|
</AdvanceSearch>
|
||||||
</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-show="$q.screen.gt.sm"
|
v-if="$q.screen.gt.sm"
|
||||||
ref="refFilter"
|
|
||||||
v-model="pageState.statusFilter"
|
v-model="pageState.statusFilter"
|
||||||
outlined
|
outlined
|
||||||
dense
|
dense
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ import { pageTabs, columns, hslaColors } from './constants';
|
||||||
import { CreditNoteStatus, useCreditNote } from 'src/stores/credit-note';
|
import { CreditNoteStatus, useCreditNote } from 'src/stores/credit-note';
|
||||||
import TableCreditNote from './TableCreditNote.vue';
|
import TableCreditNote from './TableCreditNote.vue';
|
||||||
import { dialogWarningClose } from 'src/stores/utils';
|
import { dialogWarningClose } from 'src/stores/utils';
|
||||||
|
import AdvanceSearch from 'src/components/shared/AdvanceSearch.vue';
|
||||||
|
|
||||||
const $q = useQuasar();
|
const $q = useQuasar();
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|
@ -46,6 +47,7 @@ const pageState = reactive({
|
||||||
total: 0,
|
total: 0,
|
||||||
|
|
||||||
creditDialog: false,
|
creditDialog: false,
|
||||||
|
searchDate: [],
|
||||||
});
|
});
|
||||||
|
|
||||||
const fieldSelectedOption = computed(() => {
|
const fieldSelectedOption = computed(() => {
|
||||||
|
|
@ -64,6 +66,8 @@ async function getList(opts?: { page?: number; pageSize?: number }) {
|
||||||
pageSize: opts?.pageSize || pageSize.value,
|
pageSize: opts?.pageSize || pageSize.value,
|
||||||
query: pageState.inputSearch === '' ? undefined : pageState.inputSearch,
|
query: pageState.inputSearch === '' ? undefined : pageState.inputSearch,
|
||||||
creditNoteStatus: pageState.currentTab as CreditNoteStatus | undefined,
|
creditNoteStatus: pageState.currentTab as CreditNoteStatus | undefined,
|
||||||
|
startDate: pageState.searchDate[0],
|
||||||
|
endDate: pageState.searchDate[1],
|
||||||
});
|
});
|
||||||
|
|
||||||
if (res) {
|
if (res) {
|
||||||
|
|
@ -133,6 +137,7 @@ watch(
|
||||||
() => pageState.inputSearch,
|
() => pageState.inputSearch,
|
||||||
() => pageSize.value,
|
() => pageSize.value,
|
||||||
() => pageState.statusFilter,
|
() => pageState.statusFilter,
|
||||||
|
() => pageState.searchDate,
|
||||||
],
|
],
|
||||||
() => {
|
() => {
|
||||||
getList();
|
getList();
|
||||||
|
|
@ -228,6 +233,10 @@ watch(
|
||||||
<template #prepend>
|
<template #prepend>
|
||||||
<q-icon name="mdi-magnify" />
|
<q-icon name="mdi-magnify" />
|
||||||
</template>
|
</template>
|
||||||
|
<template v-slot:append>
|
||||||
|
<q-separator vertical inset class="q-mr-xs" />
|
||||||
|
<AdvanceSearch v-model="pageState.searchDate" />
|
||||||
|
</template>
|
||||||
</q-input>
|
</q-input>
|
||||||
|
|
||||||
<div class="row col-md-5 justify-end" style="white-space: nowrap">
|
<div class="row col-md-5 justify-end" style="white-space: nowrap">
|
||||||
|
|
|
||||||
|
|
@ -248,6 +248,7 @@ function calcPricePerUnit(product: RequestWork['productService']['product']) {
|
||||||
function calcPrice(
|
function calcPrice(
|
||||||
product: RequestWork['productService']['product'],
|
product: RequestWork['productService']['product'],
|
||||||
amount: number,
|
amount: number,
|
||||||
|
vat: number = 0,
|
||||||
) {
|
) {
|
||||||
const pricePerUnit = agentPrice.value ? product.agentPrice : product.price;
|
const pricePerUnit = agentPrice.value ? product.agentPrice : product.price;
|
||||||
|
|
||||||
|
|
@ -256,7 +257,8 @@ function calcPrice(
|
||||||
: pricePerUnit;
|
: pricePerUnit;
|
||||||
const priceDiscountNoVat = priceNoVat * amount - 0;
|
const priceDiscountNoVat = priceNoVat * amount - 0;
|
||||||
|
|
||||||
const rawVatTotal = priceDiscountNoVat * (config.value?.vat || 0.07);
|
const rawVatTotal =
|
||||||
|
vat === 0 ? 0 : priceDiscountNoVat * (config.value?.vat || 0.07);
|
||||||
|
|
||||||
return precisionRound(priceNoVat * amount + rawVatTotal);
|
return precisionRound(priceNoVat * amount + rawVatTotal);
|
||||||
}
|
}
|
||||||
|
|
@ -346,7 +348,7 @@ function closeAble() {
|
||||||
<td style="text-align: center">
|
<td style="text-align: center">
|
||||||
{{
|
{{
|
||||||
formatNumberDecimal(
|
formatNumberDecimal(
|
||||||
calcPrice(v.product.product, v.list.length),
|
calcPrice(v.product.product, v.list.length, v.product.vat),
|
||||||
2,
|
2,
|
||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
|
|
@ -431,7 +433,7 @@ function closeAble() {
|
||||||
class="column set-width bg-color full-height"
|
class="column set-width bg-color full-height"
|
||||||
style="padding: 12px"
|
style="padding: 12px"
|
||||||
>
|
>
|
||||||
({{ ThaiBahtText(summaryPrice.finalPrice) }})
|
({{ ThaiBahtText(precisionRound(summaryPrice.finalPrice)) }})
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="row text-right border-5 items-center"
|
class="row text-right border-5 items-center"
|
||||||
|
|
@ -494,7 +496,7 @@ function closeAble() {
|
||||||
details?.worker.map(
|
details?.worker.map(
|
||||||
(v, i) =>
|
(v, i) =>
|
||||||
`${i + 1}. ` +
|
`${i + 1}. ` +
|
||||||
`${v.namePrefix}. ${v.firstNameEN} ${v.lastNameEN}`.toUpperCase(),
|
`${v.namePrefix}. ${v.firstNameEN ? `${v.firstNameEN} ${v.lastNameEN}` : `${v.firstName} ${v.lastName}`} `.toUpperCase(),
|
||||||
) || [],
|
) || [],
|
||||||
},
|
},
|
||||||
}) || '-'
|
}) || '-'
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ import { pageTabs, columns, hslaColors } from './constants';
|
||||||
import { DebitNoteStatus, useDebitNote } from 'src/stores/debit-note';
|
import { DebitNoteStatus, useDebitNote } from 'src/stores/debit-note';
|
||||||
import { dialogWarningClose } from 'src/stores/utils';
|
import { dialogWarningClose } from 'src/stores/utils';
|
||||||
import { useQuasar } from 'quasar';
|
import { useQuasar } from 'quasar';
|
||||||
|
import AdvanceSearch from 'src/components/shared/AdvanceSearch.vue';
|
||||||
|
|
||||||
const $q = useQuasar();
|
const $q = useQuasar();
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|
@ -46,6 +47,7 @@ const pageState = reactive({
|
||||||
total: 0,
|
total: 0,
|
||||||
|
|
||||||
debitDialog: false,
|
debitDialog: false,
|
||||||
|
searchDate: [],
|
||||||
});
|
});
|
||||||
|
|
||||||
const fieldSelectedOption = computed(() => {
|
const fieldSelectedOption = computed(() => {
|
||||||
|
|
@ -68,6 +70,8 @@ async function getList(opts?: { page?: number; pageSize?: number }) {
|
||||||
? undefined
|
? undefined
|
||||||
: pageState.currentTab) as DebitNoteStatus,
|
: pageState.currentTab) as DebitNoteStatus,
|
||||||
includeRegisteredBranch: true,
|
includeRegisteredBranch: true,
|
||||||
|
startDate: pageState.searchDate[0],
|
||||||
|
endDate: pageState.searchDate[1],
|
||||||
});
|
});
|
||||||
|
|
||||||
if (res) {
|
if (res) {
|
||||||
|
|
@ -149,6 +153,7 @@ watch(
|
||||||
() => pageState.inputSearch,
|
() => pageState.inputSearch,
|
||||||
() => pageSize.value,
|
() => pageSize.value,
|
||||||
() => pageState.statusFilter,
|
() => pageState.statusFilter,
|
||||||
|
() => pageState.searchDate,
|
||||||
],
|
],
|
||||||
() => getList(),
|
() => getList(),
|
||||||
);
|
);
|
||||||
|
|
@ -256,6 +261,10 @@ watch(
|
||||||
<template #prepend>
|
<template #prepend>
|
||||||
<q-icon name="mdi-magnify" />
|
<q-icon name="mdi-magnify" />
|
||||||
</template>
|
</template>
|
||||||
|
<template v-slot:append>
|
||||||
|
<q-separator vertical inset class="q-mr-xs" />
|
||||||
|
<AdvanceSearch v-model="pageState.searchDate" />
|
||||||
|
</template>
|
||||||
</q-input>
|
</q-input>
|
||||||
|
|
||||||
<div class="row col-md-5 justify-end" style="white-space: nowrap">
|
<div class="row col-md-5 justify-end" style="white-space: nowrap">
|
||||||
|
|
|
||||||
|
|
@ -501,7 +501,7 @@ function print() {
|
||||||
details?.worker.map(
|
details?.worker.map(
|
||||||
(v, i) =>
|
(v, i) =>
|
||||||
`${i + 1}. ` +
|
`${i + 1}. ` +
|
||||||
`${v.namePrefix}. ${v.firstNameEN} ${v.lastNameEN}`.toUpperCase(),
|
`${v.namePrefix}. ${v.firstNameEN ? `${v.firstNameEN} ${v.lastNameEN}` : `${v.firstName} ${v.lastName}`} `.toUpperCase(),
|
||||||
) || [],
|
) || [],
|
||||||
},
|
},
|
||||||
}) || '-'
|
}) || '-'
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,8 @@ import { columns, hslaColors } from './constants';
|
||||||
import useFlowStore from 'src/stores/flow';
|
import useFlowStore from 'src/stores/flow';
|
||||||
import { usePayment, useReceipt } from 'src/stores/payment';
|
import { usePayment, useReceipt } from 'src/stores/payment';
|
||||||
import { PaymentDataStatus } from 'src/stores/payment/types';
|
import { PaymentDataStatus } from 'src/stores/payment/types';
|
||||||
import { QSelect, useQuasar } from 'quasar';
|
import { useQuasar } from 'quasar';
|
||||||
|
import AdvanceSearch from 'src/components/shared/AdvanceSearch.vue';
|
||||||
|
|
||||||
const $q = useQuasar();
|
const $q = useQuasar();
|
||||||
const navigatorStore = useNavigator();
|
const navigatorStore = useNavigator();
|
||||||
|
|
@ -26,7 +27,6 @@ const receiptStore = useReceipt();
|
||||||
const { data, page, pageMax, pageSize } = storeToRefs(receiptStore);
|
const { data, page, pageMax, pageSize } = storeToRefs(receiptStore);
|
||||||
|
|
||||||
// NOTE: Variable
|
// NOTE: Variable
|
||||||
const refFilter = ref<InstanceType<typeof QSelect>>();
|
|
||||||
|
|
||||||
const pageState = reactive({
|
const pageState = reactive({
|
||||||
hideStat: false,
|
hideStat: false,
|
||||||
|
|
@ -35,6 +35,7 @@ const pageState = reactive({
|
||||||
fieldSelected: [...columns.map((v) => v.name)],
|
fieldSelected: [...columns.map((v) => v.name)],
|
||||||
gridView: false,
|
gridView: false,
|
||||||
total: 0,
|
total: 0,
|
||||||
|
searchDate: [],
|
||||||
});
|
});
|
||||||
|
|
||||||
const fieldSelectedOption = computed(() => {
|
const fieldSelectedOption = computed(() => {
|
||||||
|
|
@ -49,6 +50,8 @@ async function fetchList(opts?: { rotateFlowId?: boolean }) {
|
||||||
page: page.value,
|
page: page.value,
|
||||||
pageSize: pageSize.value,
|
pageSize: pageSize.value,
|
||||||
query: pageState.inputSearch,
|
query: pageState.inputSearch,
|
||||||
|
startDate: pageState.searchDate[0],
|
||||||
|
endDate: pageState.searchDate[1],
|
||||||
});
|
});
|
||||||
if (ret) {
|
if (ret) {
|
||||||
data.value = $q.screen.xs ? [...data.value, ...ret.result] : ret.result;
|
data.value = $q.screen.xs ? [...data.value, ...ret.result] : ret.result;
|
||||||
|
|
@ -95,6 +98,7 @@ watch(
|
||||||
() => pageState.inputSearch,
|
() => pageState.inputSearch,
|
||||||
() => pageState.statusFilter,
|
() => pageState.statusFilter,
|
||||||
() => pageSize.value,
|
() => pageSize.value,
|
||||||
|
() => pageState.searchDate,
|
||||||
],
|
],
|
||||||
() => {
|
() => {
|
||||||
page.value = 1;
|
page.value = 1;
|
||||||
|
|
@ -172,25 +176,43 @@ watch(
|
||||||
<template #prepend>
|
<template #prepend>
|
||||||
<q-icon name="mdi-magnify" />
|
<q-icon name="mdi-magnify" />
|
||||||
</template>
|
</template>
|
||||||
<template v-if="$q.screen.lt.md" v-slot:append>
|
<template v-slot:append>
|
||||||
<span class="row">
|
<q-separator vertical inset class="q-mr-xs" />
|
||||||
<q-separator vertical />
|
<AdvanceSearch
|
||||||
<q-btn
|
v-model="pageState.searchDate"
|
||||||
icon="mdi-filter-variant"
|
:active="$q.screen.lt.md && pageState.statusFilter !== 'None'"
|
||||||
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"
|
||||||
|
ref="refFilter"
|
||||||
|
v-model="pageState.statusFilter"
|
||||||
|
outlined
|
||||||
|
dense
|
||||||
|
option-value="value"
|
||||||
|
option-label="label"
|
||||||
|
map-options
|
||||||
|
emit-value
|
||||||
|
:for="'field-select-status'"
|
||||||
|
:options="[
|
||||||
|
{
|
||||||
|
label: $t('general.all'),
|
||||||
|
value: 'None',
|
||||||
|
},
|
||||||
|
]"
|
||||||
/>
|
/>
|
||||||
</span>
|
</AdvanceSearch>
|
||||||
</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-show="$q.screen.gt.sm"
|
v-if="$q.screen.gt.sm"
|
||||||
ref="refFilter"
|
ref="refFilter"
|
||||||
v-model="pageState.statusFilter"
|
v-model="pageState.statusFilter"
|
||||||
outlined
|
outlined
|
||||||
|
|
|
||||||
|
|
@ -155,6 +155,16 @@ const routes: RouteRecordRaw[] = [
|
||||||
name: 'ManualView',
|
name: 'ManualView',
|
||||||
component: () => import('pages/00_manual/ViewPage.vue'),
|
component: () => import('pages/00_manual/ViewPage.vue'),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/troubleshooting',
|
||||||
|
name: 'Troubleshooting',
|
||||||
|
component: () => import('pages/00_manual/MainPage.vue'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/troubleshooting/:category/:page',
|
||||||
|
name: 'TroubleshootingView',
|
||||||
|
component: () => import('pages/00_manual/ViewPage.vue'),
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -102,12 +102,25 @@ const useAddressStore = defineStore('api-address', () => {
|
||||||
return subDistrict.value[districtId];
|
return subDistrict.value[districtId];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function listSameOfficeArea(districtId: string) {
|
||||||
|
const res = await api.post<string[]>(
|
||||||
|
`/employment-office/list-same-office-area`,
|
||||||
|
{ districtId: districtId },
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!res) return false;
|
||||||
|
|
||||||
|
return res.data;
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
fetchOffice,
|
fetchOffice,
|
||||||
fetchOfficeById,
|
fetchOfficeById,
|
||||||
fetchProvince,
|
fetchProvince,
|
||||||
fetchDistrictByProvinceId,
|
fetchDistrictByProvinceId,
|
||||||
fetchSubDistrictByProvinceId,
|
fetchSubDistrictByProvinceId,
|
||||||
|
|
||||||
|
listSameOfficeArea,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,8 @@ const useBranchStore = defineStore('api-branch', () => {
|
||||||
withHead?: boolean;
|
withHead?: boolean;
|
||||||
activeOnly?: boolean;
|
activeOnly?: boolean;
|
||||||
headOfficeId?: string;
|
headOfficeId?: string;
|
||||||
|
startDate?: string;
|
||||||
|
endDate?: string;
|
||||||
},
|
},
|
||||||
Data extends Pagination<Branch[]>,
|
Data extends Pagination<Branch[]>,
|
||||||
>(opts?: Options): Promise<Data | false> {
|
>(opts?: Options): Promise<Data | false> {
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,8 @@ export async function getCreditNoteList(params?: {
|
||||||
pageSize?: number;
|
pageSize?: number;
|
||||||
query?: string;
|
query?: string;
|
||||||
creditNoteStatus?: Status;
|
creditNoteStatus?: Status;
|
||||||
|
startDate?: string;
|
||||||
|
endDate?: string;
|
||||||
}) {
|
}) {
|
||||||
const res = await api.get<PaginationResult<Data>>(`/${ENDPOINT}`, {
|
const res = await api.get<PaginationResult<Data>>(`/${ENDPOINT}`, {
|
||||||
params,
|
params,
|
||||||
|
|
|
||||||
|
|
@ -113,6 +113,8 @@ const useCustomerStore = defineStore('api-customer', () => {
|
||||||
includeBranch?: boolean;
|
includeBranch?: boolean;
|
||||||
status?: 'CREATED' | 'ACTIVE' | 'INACTIVE';
|
status?: 'CREATED' | 'ACTIVE' | 'INACTIVE';
|
||||||
customerType?: CustomerType;
|
customerType?: CustomerType;
|
||||||
|
startDate?: string;
|
||||||
|
endDate?: string;
|
||||||
},
|
},
|
||||||
Data extends Pagination<
|
Data extends Pagination<
|
||||||
(Customer &
|
(Customer &
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,8 @@ export async function getDebitNoteList(params?: {
|
||||||
query?: string;
|
query?: string;
|
||||||
status?: Status;
|
status?: Status;
|
||||||
includeRegisteredBranch?: boolean;
|
includeRegisteredBranch?: boolean;
|
||||||
|
startDate?: string;
|
||||||
|
endDate?: string;
|
||||||
}) {
|
}) {
|
||||||
const res = await api.get<PaginationResult<Data>>(`/${ENDPOINT}`, {
|
const res = await api.get<PaginationResult<Data>>(`/${ENDPOINT}`, {
|
||||||
params,
|
params,
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,8 @@ const useEmployeeStore = defineStore('api-employee', () => {
|
||||||
customerId?: string;
|
customerId?: string;
|
||||||
customerBranchId?: string;
|
customerBranchId?: string;
|
||||||
activeOnly?: boolean;
|
activeOnly?: boolean;
|
||||||
|
startDate?: string;
|
||||||
|
endDate?: string;
|
||||||
payload?: { passport?: string[] };
|
payload?: { passport?: string[] };
|
||||||
}) {
|
}) {
|
||||||
const { payload, ...params } = opts || {};
|
const { payload, ...params } = opts || {};
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,9 @@ export const useInstitution = defineStore('institution-store', () => {
|
||||||
group?: string;
|
group?: string;
|
||||||
status?: Status;
|
status?: Status;
|
||||||
payload?: { group?: string[] };
|
payload?: { group?: string[] };
|
||||||
|
startDate?: string;
|
||||||
|
endDate?: string;
|
||||||
|
activeOnly?: boolean;
|
||||||
}) {
|
}) {
|
||||||
const { payload, ...params } = opts || {};
|
const { payload, ...params } = opts || {};
|
||||||
|
|
||||||
|
|
@ -72,18 +75,67 @@ export const useInstitution = defineStore('institution-store', () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (res.data.bank && data.bank.length > 0) {
|
||||||
|
for (let i = 0; i < data.bank?.length; i++) {
|
||||||
|
if (data.bank[i].bankQr) {
|
||||||
|
await api
|
||||||
|
.put(
|
||||||
|
`/institution/${res.data.id}/bank-qr/${res.data.bank[i].id}`,
|
||||||
|
data.bank[i].bankQr,
|
||||||
|
{
|
||||||
|
headers: { 'Content-Type': data.bank[i].bankQr?.type },
|
||||||
|
onUploadProgress: (e) => console.log(e),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.catch((e) => console.error(e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (res.status < 400) {
|
if (res.status < 400) {
|
||||||
return res.data;
|
return res.data;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function editInstitution(data: InstitutionPayload & { id: string }) {
|
async function editInstitution(
|
||||||
|
data: InstitutionPayload & { id: string },
|
||||||
|
opts?: { indexDeleteQrCodeBank?: number[] },
|
||||||
|
) {
|
||||||
const res = await api.put(`/institution/${data.id}`, {
|
const res = await api.put(`/institution/${data.id}`, {
|
||||||
...data,
|
...data,
|
||||||
id: undefined,
|
id: undefined,
|
||||||
group: undefined,
|
group: undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (!!res.data.bank && !!data.bank.length) {
|
||||||
|
for (let i = 0; i < data.bank?.length; i++) {
|
||||||
|
if (data.bank[i].bankQr) {
|
||||||
|
console.log(i);
|
||||||
|
console.log(data.bank[i].bankQr);
|
||||||
|
await api
|
||||||
|
.put(
|
||||||
|
`/institution/${res.data.id}/bank-qr/${res.data.bank[i].id}`,
|
||||||
|
data.bank[i].bankQr,
|
||||||
|
{
|
||||||
|
headers: { 'Content-Type': data.bank[i].bankQr?.type },
|
||||||
|
onUploadProgress: (e) => console.log(e),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.catch((e) => console.error(e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opts.indexDeleteQrCodeBank && opts.indexDeleteQrCodeBank.length > 0) {
|
||||||
|
console.log('delete');
|
||||||
|
opts.indexDeleteQrCodeBank.forEach(async (i) => {
|
||||||
|
await api
|
||||||
|
.delete(`/institution/${res.data.id}/bank-qr/${res.data.bank[i].id}`)
|
||||||
|
.catch((e) => console.error(e));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (res.status < 400) {
|
if (res.status < 400) {
|
||||||
return res.data;
|
return res.data;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,10 +5,11 @@ import { getToken } from 'src/services/keycloak';
|
||||||
import { Manual } from './types';
|
import { Manual } from './types';
|
||||||
import { baseUrl } from '../utils';
|
import { baseUrl } from '../utils';
|
||||||
|
|
||||||
const ENDPOINT = 'manual';
|
const MANUAL_ENDPOINT = 'manual';
|
||||||
|
const TROUBLESHOOTING_ENDPOINT = 'troubleshooting';
|
||||||
|
|
||||||
export async function getManual() {
|
export async function getManual() {
|
||||||
const res = await api.get<Manual[]>(`/${ENDPOINT}`);
|
const res = await api.get<Manual[]>(`/${MANUAL_ENDPOINT}`);
|
||||||
if (res.status < 400) {
|
if (res.status < 400) {
|
||||||
return res.data;
|
return res.data;
|
||||||
}
|
}
|
||||||
|
|
@ -20,7 +21,28 @@ export async function getManualByPage(opt: {
|
||||||
pageName: string;
|
pageName: string;
|
||||||
}) {
|
}) {
|
||||||
const res = await fetch(
|
const res = await fetch(
|
||||||
`${baseUrl}/${ENDPOINT}/${opt.category}/page/${opt.pageName}`,
|
`${baseUrl}/${MANUAL_ENDPOINT}/${opt.category}/page/${opt.pageName}`,
|
||||||
|
);
|
||||||
|
if (res.status < 400) {
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getTroubleshooting() {
|
||||||
|
const res = await api.get<Manual[]>(`/${TROUBLESHOOTING_ENDPOINT}`);
|
||||||
|
if (res.status < 400) {
|
||||||
|
return res.data;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getTroubleshootingByPage(opt: {
|
||||||
|
category: string;
|
||||||
|
pageName: string;
|
||||||
|
}) {
|
||||||
|
const res = await fetch(
|
||||||
|
`${baseUrl}/${TROUBLESHOOTING_ENDPOINT}/${opt.category}/page/${opt.pageName}`,
|
||||||
);
|
);
|
||||||
if (res.status < 400) {
|
if (res.status < 400) {
|
||||||
return res;
|
return res;
|
||||||
|
|
@ -30,11 +52,15 @@ export async function getManualByPage(opt: {
|
||||||
|
|
||||||
export const useManualStore = defineStore('manual-store', () => {
|
export const useManualStore = defineStore('manual-store', () => {
|
||||||
const dataManual = ref<Manual[]>([]);
|
const dataManual = ref<Manual[]>([]);
|
||||||
|
const dataTroubleshooting = ref<Manual[]>([]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
getManual,
|
getManual,
|
||||||
getManualByPage,
|
getManualByPage,
|
||||||
|
getTroubleshooting,
|
||||||
|
getTroubleshootingByPage,
|
||||||
|
|
||||||
dataManual,
|
dataManual,
|
||||||
|
dataTroubleshooting,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -101,6 +101,8 @@ export const useReceipt = defineStore('receipt-store', () => {
|
||||||
debitNoteId?: string;
|
debitNoteId?: string;
|
||||||
debitNoteOnly?: boolean;
|
debitNoteOnly?: boolean;
|
||||||
quotationOnly?: boolean;
|
quotationOnly?: boolean;
|
||||||
|
startDate?: string;
|
||||||
|
endDate?: string;
|
||||||
}) {
|
}) {
|
||||||
const res = await api.get<PaginationResult<Receipt>>('/receipt', {
|
const res = await api.get<PaginationResult<Receipt>>('/receipt', {
|
||||||
params: opts,
|
params: opts,
|
||||||
|
|
@ -162,6 +164,8 @@ export const useInvoice = defineStore('invoice-store', () => {
|
||||||
debitNoteOnly?: boolean;
|
debitNoteOnly?: boolean;
|
||||||
quotationId?: string;
|
quotationId?: string;
|
||||||
debitNoteId?: string;
|
debitNoteId?: string;
|
||||||
|
startDate?: string;
|
||||||
|
endDate?: string;
|
||||||
}) {
|
}) {
|
||||||
const res = await api.get<PaginationResult<Invoice>>('/invoice', {
|
const res = await api.get<PaginationResult<Invoice>>('/invoice', {
|
||||||
params,
|
params,
|
||||||
|
|
|
||||||
|
|
@ -56,6 +56,8 @@ const useProductServiceStore = defineStore('api-product-service', () => {
|
||||||
query?: string;
|
query?: string;
|
||||||
status?: 'CREATED' | 'ACTIVE' | 'INACTIVE';
|
status?: 'CREATED' | 'ACTIVE' | 'INACTIVE';
|
||||||
activeOnly?: boolean;
|
activeOnly?: boolean;
|
||||||
|
startDate?: string;
|
||||||
|
endDate?: string;
|
||||||
}) {
|
}) {
|
||||||
const params = new URLSearchParams();
|
const params = new URLSearchParams();
|
||||||
|
|
||||||
|
|
@ -142,6 +144,8 @@ const useProductServiceStore = defineStore('api-product-service', () => {
|
||||||
orderField?: string;
|
orderField?: string;
|
||||||
activeOnly?: boolean;
|
activeOnly?: boolean;
|
||||||
orderBy?: 'asc' | 'desc';
|
orderBy?: 'asc' | 'desc';
|
||||||
|
startDate?: string;
|
||||||
|
endDate?: string;
|
||||||
}) {
|
}) {
|
||||||
const params = new URLSearchParams();
|
const params = new URLSearchParams();
|
||||||
|
|
||||||
|
|
@ -190,6 +194,23 @@ const useProductServiceStore = defineStore('api-product-service', () => {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function importProduct(
|
||||||
|
productGroupId: string,
|
||||||
|
files: File[],
|
||||||
|
fetch: (...args: unknown[]) => void,
|
||||||
|
) {
|
||||||
|
const importTasks = files.map((f) => {
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('file', f);
|
||||||
|
return api.post('/product/import-product', formData, {
|
||||||
|
params: { productGroupId },
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
await Promise.all(importTasks);
|
||||||
|
fetch?.();
|
||||||
|
}
|
||||||
|
|
||||||
async function fetchListProductById(productId: string) {
|
async function fetchListProductById(productId: string) {
|
||||||
const res = await api.get<Product>(`/product/${productId}`);
|
const res = await api.get<Product>(`/product/${productId}`);
|
||||||
|
|
||||||
|
|
@ -249,6 +270,8 @@ const useProductServiceStore = defineStore('api-product-service', () => {
|
||||||
productGroupId?: string;
|
productGroupId?: string;
|
||||||
status?: string;
|
status?: string;
|
||||||
fullDetail?: boolean;
|
fullDetail?: boolean;
|
||||||
|
startDate?: string;
|
||||||
|
endDate?: string;
|
||||||
}) {
|
}) {
|
||||||
const params = new URLSearchParams();
|
const params = new URLSearchParams();
|
||||||
|
|
||||||
|
|
@ -543,6 +566,8 @@ const useProductServiceStore = defineStore('api-product-service', () => {
|
||||||
fetchImageListById,
|
fetchImageListById,
|
||||||
addImageList,
|
addImageList,
|
||||||
deleteImageByName,
|
deleteImageByName,
|
||||||
|
|
||||||
|
importProduct,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,8 @@ export const useProperty = defineStore('property-store', () => {
|
||||||
query?: string;
|
query?: string;
|
||||||
status?: Status;
|
status?: Status;
|
||||||
activeOnly?: boolean;
|
activeOnly?: boolean;
|
||||||
|
startDate?: string;
|
||||||
|
endDate?: string;
|
||||||
}) {
|
}) {
|
||||||
const res = await api.get<PaginationResult<Property>>('/property', {
|
const res = await api.get<PaginationResult<Property>>('/property', {
|
||||||
params,
|
params,
|
||||||
|
|
|
||||||
|
|
@ -76,6 +76,8 @@ export const useQuotationStore = defineStore('quotation-store', () => {
|
||||||
includeRegisteredBranch?: boolean;
|
includeRegisteredBranch?: boolean;
|
||||||
forDebitNote?: boolean;
|
forDebitNote?: boolean;
|
||||||
cancelIncludeDebitNote?: boolean;
|
cancelIncludeDebitNote?: boolean;
|
||||||
|
startDate?: string;
|
||||||
|
endDate?: string;
|
||||||
}) {
|
}) {
|
||||||
const res = await api.get<PaginationResult<Quotation>>('/quotation', {
|
const res = await api.get<PaginationResult<Quotation>>('/quotation', {
|
||||||
params,
|
params,
|
||||||
|
|
|
||||||
|
|
@ -209,6 +209,9 @@ export const useRequestList = defineStore('request-list', () => {
|
||||||
requestDataStatus?: RequestDataStatus;
|
requestDataStatus?: RequestDataStatus;
|
||||||
responsibleOnly?: boolean;
|
responsibleOnly?: boolean;
|
||||||
quotationId?: string;
|
quotationId?: string;
|
||||||
|
incomplete?: boolean;
|
||||||
|
startDate?: string;
|
||||||
|
endDate?: string;
|
||||||
}) {
|
}) {
|
||||||
const res = await api.get<PaginationResult<RequestData>>('/request-data', {
|
const res = await api.get<PaginationResult<RequestData>>('/request-data', {
|
||||||
params,
|
params,
|
||||||
|
|
@ -325,6 +328,20 @@ export const useRequestList = defineStore('request-list', () => {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function updateMessenger(
|
||||||
|
requestDataId: string[],
|
||||||
|
defaultMessengerId: string,
|
||||||
|
) {
|
||||||
|
const res = await api.post('/request-data/update-messenger', {
|
||||||
|
requestDataId,
|
||||||
|
defaultMessengerId,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.status < 400) return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
data,
|
data,
|
||||||
page,
|
page,
|
||||||
|
|
@ -350,6 +367,8 @@ export const useRequestList = defineStore('request-list', () => {
|
||||||
|
|
||||||
rejectRequest,
|
rejectRequest,
|
||||||
rejectRequestWork,
|
rejectRequestWork,
|
||||||
|
|
||||||
|
updateMessenger,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,8 @@ export type RequestData = {
|
||||||
rejectRequestCancel?: boolean;
|
rejectRequestCancel?: boolean;
|
||||||
rejectRequestCancelReason?: string;
|
rejectRequestCancelReason?: string;
|
||||||
|
|
||||||
|
defaultMessengerId?: string;
|
||||||
|
|
||||||
quotation: QuotationFull & {
|
quotation: QuotationFull & {
|
||||||
debitNoteQuotationId: string;
|
debitNoteQuotationId: string;
|
||||||
isDebitNote: boolean;
|
isDebitNote: boolean;
|
||||||
|
|
@ -26,6 +28,7 @@ export type RequestData = {
|
||||||
|
|
||||||
requestWork: RequestWork[];
|
requestWork: RequestWork[];
|
||||||
requestDataStatus: RequestDataStatus;
|
requestDataStatus: RequestDataStatus;
|
||||||
|
dataOffice: { name: string; nameEN: string };
|
||||||
};
|
};
|
||||||
|
|
||||||
export enum RequestDataStatus {
|
export enum RequestDataStatus {
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,8 @@ export const useTaskOrderStore = defineStore('taskorder-store', () => {
|
||||||
query?: string;
|
query?: string;
|
||||||
taskOrderStatus?: TaskOrderStatus;
|
taskOrderStatus?: TaskOrderStatus;
|
||||||
assignedUserId?: boolean;
|
assignedUserId?: boolean;
|
||||||
|
startDate?: string;
|
||||||
|
endDate?: string;
|
||||||
}) {
|
}) {
|
||||||
const res = await api.get<PaginationResult<TaskOrder>>('/task-order', {
|
const res = await api.get<PaginationResult<TaskOrder>>('/task-order', {
|
||||||
params,
|
params,
|
||||||
|
|
@ -161,6 +163,8 @@ export const useTaskOrderStore = defineStore('taskorder-store', () => {
|
||||||
pageSize?: number;
|
pageSize?: number;
|
||||||
query?: string;
|
query?: string;
|
||||||
userTaskStatus?: UserTaskStatus;
|
userTaskStatus?: UserTaskStatus;
|
||||||
|
startDate?: string;
|
||||||
|
endDate?: string;
|
||||||
}) {
|
}) {
|
||||||
const res = await api.get<PaginationResult<TaskOrder>>('/user-task-order', {
|
const res = await api.get<PaginationResult<TaskOrder>>('/user-task-order', {
|
||||||
params,
|
params,
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,7 @@ export interface TaskOrder {
|
||||||
contactName: string;
|
contactName: string;
|
||||||
taskOrderStatus: TaskOrderStatus;
|
taskOrderStatus: TaskOrderStatus;
|
||||||
taskName: string;
|
taskName: string;
|
||||||
|
codeProductReceived?: string;
|
||||||
code: string;
|
code: string;
|
||||||
id: string;
|
id: string;
|
||||||
userTask: UserTask[];
|
userTask: UserTask[];
|
||||||
|
|
@ -164,6 +165,8 @@ export interface TaskOrderPayload {
|
||||||
requestWorkId: string;
|
requestWorkId: string;
|
||||||
requestWorkStep?: Task;
|
requestWorkStep?: Task;
|
||||||
taskStatus?: TaskStatus;
|
taskStatus?: TaskStatus;
|
||||||
|
failedType?: string;
|
||||||
|
failedComment?: string;
|
||||||
}[];
|
}[];
|
||||||
taskProduct?: {
|
taskProduct?: {
|
||||||
productId: string;
|
productId: string;
|
||||||
|
|
@ -177,6 +180,7 @@ export interface TaskOrderPayload {
|
||||||
registeredBranchId?: string;
|
registeredBranchId?: string;
|
||||||
id?: string;
|
id?: string;
|
||||||
code?: string;
|
code?: string;
|
||||||
|
codeProductReceived?: string;
|
||||||
remark?: string;
|
remark?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ import axios from 'axios';
|
||||||
import useBranchStore from '../branch';
|
import useBranchStore from '../branch';
|
||||||
import { Branch } from '../branch/types';
|
import { Branch } from '../branch/types';
|
||||||
import { getSignature, setSignature } from './signature';
|
import { getSignature, setSignature } from './signature';
|
||||||
|
import { getUserId } from 'src/services/keycloak';
|
||||||
|
|
||||||
const branchStore = useBranchStore();
|
const branchStore = useBranchStore();
|
||||||
|
|
||||||
|
|
@ -38,6 +39,14 @@ const useUserStore = defineStore('api-user', () => {
|
||||||
|
|
||||||
const data = ref<Pagination<User[]>>();
|
const data = ref<Pagination<User[]>>();
|
||||||
|
|
||||||
|
async function fetchUserGroup(id?: string) {
|
||||||
|
return await api
|
||||||
|
.get<
|
||||||
|
{ id: string; name: string; path: string }[]
|
||||||
|
>(`/user/${id || getUserId()}/group`)
|
||||||
|
.then((res) => res.data);
|
||||||
|
}
|
||||||
|
|
||||||
async function fetchHqOption() {
|
async function fetchHqOption() {
|
||||||
if (userOption.value.hqOpts.length === 0) {
|
if (userOption.value.hqOpts.length === 0) {
|
||||||
const res = await branchStore.fetchList({
|
const res = await branchStore.fetchList({
|
||||||
|
|
@ -168,6 +177,8 @@ const useUserStore = defineStore('api-user', () => {
|
||||||
status?: Status;
|
status?: Status;
|
||||||
responsibleDistrictId?: string;
|
responsibleDistrictId?: string;
|
||||||
activeBranchOnly?: boolean;
|
activeBranchOnly?: boolean;
|
||||||
|
startDate?: string;
|
||||||
|
endDate?: string;
|
||||||
}) {
|
}) {
|
||||||
const res = await api.get<Pagination<User[]>>('/user', { params: opts });
|
const res = await api.get<Pagination<User[]>>('/user', { params: opts });
|
||||||
|
|
||||||
|
|
@ -332,6 +343,8 @@ const useUserStore = defineStore('api-user', () => {
|
||||||
setSignature,
|
setSignature,
|
||||||
|
|
||||||
typeStats,
|
typeStats,
|
||||||
|
|
||||||
|
fetchUserGroup,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ export type User = {
|
||||||
createdBy: string;
|
createdBy: string;
|
||||||
status: Status;
|
status: Status;
|
||||||
trainingPlace: string | null;
|
trainingPlace: string | null;
|
||||||
importNationality: string | null;
|
importNationality: [] | null;
|
||||||
sourceNationality: string | null;
|
sourceNationality: string | null;
|
||||||
licenseExpireDate: Date | null;
|
licenseExpireDate: Date | null;
|
||||||
licenseIssueDate: Date | null;
|
licenseIssueDate: Date | null;
|
||||||
|
|
@ -57,7 +57,8 @@ export type User = {
|
||||||
citizenIssue?: Date | null;
|
citizenIssue?: Date | null;
|
||||||
citizenId: string;
|
citizenId: string;
|
||||||
branch: Branch[];
|
branch: Branch[];
|
||||||
|
contactName?: string;
|
||||||
|
contactTel?: string;
|
||||||
remark?: string;
|
remark?: string;
|
||||||
agencyStatus?: AgencyStatus;
|
agencyStatus?: AgencyStatus;
|
||||||
};
|
};
|
||||||
|
|
@ -81,7 +82,7 @@ export type UserCreate = {
|
||||||
streetEN: string;
|
streetEN: string;
|
||||||
street: string;
|
street: string;
|
||||||
trainingPlace?: string | null;
|
trainingPlace?: string | null;
|
||||||
importNationality?: string | null;
|
importNationality?: string[] | null;
|
||||||
sourceNationality?: string | null;
|
sourceNationality?: string | null;
|
||||||
licenseExpireDate?: Date | null;
|
licenseExpireDate?: Date | null;
|
||||||
licenseIssueDate?: Date | null;
|
licenseIssueDate?: Date | null;
|
||||||
|
|
@ -108,7 +109,8 @@ export type UserCreate = {
|
||||||
citizenExpire?: Date | null;
|
citizenExpire?: Date | null;
|
||||||
citizenIssue?: Date | null;
|
citizenIssue?: Date | null;
|
||||||
citizenId: string;
|
citizenId: string;
|
||||||
|
contactName?: string;
|
||||||
|
contactTel?: string;
|
||||||
remark?: string;
|
remark?: string;
|
||||||
agencyStatus?: AgencyStatus | string;
|
agencyStatus?: AgencyStatus | string;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -610,6 +610,26 @@ export function getEmployeeName(
|
||||||
}[opts?.locale || 'eng'];
|
}[opts?.locale || 'eng'];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function setPrefixName(
|
||||||
|
record: {
|
||||||
|
namePrefix: string;
|
||||||
|
firstName: string;
|
||||||
|
lastName: string;
|
||||||
|
firstNameEN: string;
|
||||||
|
lastNameEN: string;
|
||||||
|
},
|
||||||
|
opts?: {
|
||||||
|
locale?: string;
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
const data = record;
|
||||||
|
|
||||||
|
return {
|
||||||
|
['eng']: `${typeof data.namePrefix === 'string' ? useOptionStore().mapOption(data.namePrefix) : ''} ${data.firstNameEN} ${data.lastNameEN}`,
|
||||||
|
['tha']: `${typeof data.namePrefix === 'string' ? useOptionStore().mapOption(data.namePrefix) : ''} ${data.firstName} ${data.lastName}`,
|
||||||
|
}[opts?.locale || 'eng'];
|
||||||
|
}
|
||||||
|
|
||||||
export function toCamelCase(text: string): string {
|
export function toCamelCase(text: string): string {
|
||||||
return text
|
return text
|
||||||
.replace(/[^a-zA-Z0-9]+(.)/g, (match, chr) => chr.toUpperCase())
|
.replace(/[^a-zA-Z0-9]+(.)/g, (match, chr) => chr.toUpperCase())
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import { ref } from 'vue';
|
||||||
import { defineStore } from 'pinia';
|
import { defineStore } from 'pinia';
|
||||||
import { api } from 'src/boot/axios';
|
import { api } from 'src/boot/axios';
|
||||||
import { PaginationResult } from 'src/types';
|
import { PaginationResult } from 'src/types';
|
||||||
import { WorkflowTemplate, WorkflowTemplatePayload } from './types';
|
import { Group, WorkflowTemplate, WorkflowTemplatePayload } from './types';
|
||||||
import { Status } from '../types';
|
import { Status } from '../types';
|
||||||
|
|
||||||
export const useWorkflowTemplate = defineStore('workflow-store', () => {
|
export const useWorkflowTemplate = defineStore('workflow-store', () => {
|
||||||
|
|
@ -25,6 +25,8 @@ export const useWorkflowTemplate = defineStore('workflow-store', () => {
|
||||||
query?: string;
|
query?: string;
|
||||||
status?: Status;
|
status?: Status;
|
||||||
activeOnly?: boolean;
|
activeOnly?: boolean;
|
||||||
|
startDate?: string;
|
||||||
|
endDate?: string;
|
||||||
}) {
|
}) {
|
||||||
const res = await api.get<PaginationResult<WorkflowTemplate>>(
|
const res = await api.get<PaginationResult<WorkflowTemplate>>(
|
||||||
'/workflow-template',
|
'/workflow-template',
|
||||||
|
|
@ -36,7 +38,7 @@ export const useWorkflowTemplate = defineStore('workflow-store', () => {
|
||||||
return res.data;
|
return res.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function creatWorkflowTemplate(data: WorkflowTemplatePayload) {
|
async function createWorkflowTemplate(data: WorkflowTemplatePayload) {
|
||||||
const res = await api.post<WorkflowTemplate>('/workflow-template', data);
|
const res = await api.post<WorkflowTemplate>('/workflow-template', data);
|
||||||
if (res.status >= 400) return null;
|
if (res.status >= 400) return null;
|
||||||
return res;
|
return res;
|
||||||
|
|
@ -62,6 +64,12 @@ export const useWorkflowTemplate = defineStore('workflow-store', () => {
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function getGroupList() {
|
||||||
|
const res = await api.get<Group[]>('/keycloak/group');
|
||||||
|
if (res.status >= 400) return null;
|
||||||
|
return res.data;
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
data,
|
data,
|
||||||
page,
|
page,
|
||||||
|
|
@ -70,8 +78,9 @@ export const useWorkflowTemplate = defineStore('workflow-store', () => {
|
||||||
|
|
||||||
getWorkflowTemplate,
|
getWorkflowTemplate,
|
||||||
getWorkflowTemplateList,
|
getWorkflowTemplateList,
|
||||||
creatWorkflowTemplate,
|
createWorkflowTemplate,
|
||||||
editWorkflowTemplate,
|
editWorkflowTemplate,
|
||||||
deleteWorkflowTemplate,
|
deleteWorkflowTemplate,
|
||||||
|
getGroupList,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ export type WorkflowStep = {
|
||||||
user: CreatedBy;
|
user: CreatedBy;
|
||||||
}[];
|
}[];
|
||||||
responsibleInstitution: (string | { group: string })[];
|
responsibleInstitution: (string | { group: string })[];
|
||||||
|
responsibleGroup: string[];
|
||||||
attributes: WorkFlowAttributes;
|
attributes: WorkFlowAttributes;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -49,6 +50,7 @@ export type WorkflowTemplatePayload = {
|
||||||
|
|
||||||
export type WorkflowUserInTable = {
|
export type WorkflowUserInTable = {
|
||||||
name: string;
|
name: string;
|
||||||
|
responsibleGroup: string[];
|
||||||
responsiblePerson: {
|
responsiblePerson: {
|
||||||
id: string;
|
id: string;
|
||||||
selectedImage?: string;
|
selectedImage?: string;
|
||||||
|
|
@ -73,6 +75,15 @@ export type WorkFlowPayloadStep = {
|
||||||
value?: string[];
|
value?: string[];
|
||||||
responsiblePersonId?: string[];
|
responsiblePersonId?: string[];
|
||||||
responsibleInstitution?: string[];
|
responsibleInstitution?: string[];
|
||||||
|
responsibleGroup?: string[];
|
||||||
attributes: WorkFlowAttributes;
|
attributes: WorkFlowAttributes;
|
||||||
messengerByArea?: boolean;
|
messengerByArea?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type Group = {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
path: string;
|
||||||
|
subGroupCount: number;
|
||||||
|
subGroups: Group[];
|
||||||
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
import { RequestWork } from 'src/stores/request-list';
|
import { RequestWork } from 'src/stores/request-list';
|
||||||
|
import { TaskStatus } from 'src/stores/task-order/types';
|
||||||
import { formatNumberDecimal } from 'src/stores/utils';
|
import { formatNumberDecimal } from 'src/stores/utils';
|
||||||
|
import { i18n } from 'src/boot/i18n';
|
||||||
|
|
||||||
const templates = {
|
const templates = {
|
||||||
'quotation-labor': {
|
'quotation-labor': {
|
||||||
|
|
@ -46,7 +48,12 @@ const templates = {
|
||||||
converter: (context?: {
|
converter: (context?: {
|
||||||
items?: {
|
items?: {
|
||||||
product: RequestWork['productService']['product'];
|
product: RequestWork['productService']['product'];
|
||||||
list: RequestWork[];
|
list: (RequestWork & {
|
||||||
|
taskStatus?: TaskStatus;
|
||||||
|
failedComment?: string;
|
||||||
|
failedType?: string;
|
||||||
|
codeRequest?: string;
|
||||||
|
})[];
|
||||||
}[];
|
}[];
|
||||||
itemsDiscount?: {
|
itemsDiscount?: {
|
||||||
productId: string;
|
productId: string;
|
||||||
|
|
@ -67,8 +74,9 @@ const templates = {
|
||||||
const branch = v.request.quotation.customerBranch;
|
const branch = v.request.quotation.customerBranch;
|
||||||
return (
|
return (
|
||||||
`${i + 1}. ` +
|
`${i + 1}. ` +
|
||||||
|
` ${v.request.code}_${branch.customer.customerType === 'PERS' ? `นายจ้าง ${branch.namePrefix}. ${branch.firstNameEN} ${branch.lastNameEN} `.toUpperCase() : branch.registerName}_` +
|
||||||
`${employee.namePrefix}. ${employee.firstNameEN} ${employee.lastNameEN} `.toUpperCase() +
|
`${employee.namePrefix}. ${employee.firstNameEN} ${employee.lastNameEN} `.toUpperCase() +
|
||||||
`(${branch.customer.customerType === 'PERS' ? `นายจ้าง ${branch.namePrefix}. ${branch.firstNameEN} ${branch.lastNameEN} `.toUpperCase() : branch.registerName})`
|
`${!!v.failedType && v.failedType !== 'other' ? `${i18n.global.t(`taskOrder.${v.failedType}`)}` : !!v.failedComment ? v.failedComment : ''}`
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
return [
|
return [
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue