feat: add product document list (#64)
* feat: product => document select
This commit is contained in:
parent
e501706e88
commit
f3fdaac2b1
3 changed files with 226 additions and 5 deletions
192
src/components/04_product-service/FormDocument.vue
Normal file
192
src/components/04_product-service/FormDocument.vue
Normal file
|
|
@ -0,0 +1,192 @@
|
|||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import SelectMenuWithSearch from '../shared/SelectMenuWithSearch.vue';
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
defineProps<{
|
||||
readonly?: boolean;
|
||||
}>();
|
||||
|
||||
const attachment = defineModel<string[]>('attachment', { default: [] });
|
||||
|
||||
const mapName = (val: string): string => {
|
||||
const name = options.value.find((v) => v.value === val);
|
||||
return name ? name.label : '';
|
||||
};
|
||||
|
||||
const objectOptions = [
|
||||
{
|
||||
label: 'customerEmployee.fileType.passport',
|
||||
value: 'passport',
|
||||
},
|
||||
{
|
||||
label: 'customerEmployee.fileType.visa',
|
||||
value: 'visa',
|
||||
},
|
||||
{
|
||||
label: 'customerEmployee.fileType.tm6',
|
||||
value: 'tm6',
|
||||
},
|
||||
{
|
||||
label: 'customerEmployee.fileType.workPermit',
|
||||
value: 'workPermit',
|
||||
},
|
||||
{
|
||||
label: 'customerEmployee.fileType.noticeJobEmployment',
|
||||
value: 'noticeJobEmployment',
|
||||
},
|
||||
{
|
||||
label: 'customerEmployee.fileType.noticeJobEntry',
|
||||
value: 'noticeJobEntry',
|
||||
},
|
||||
{
|
||||
label: 'customerEmployee.fileType.historyJob',
|
||||
value: 'historyJob',
|
||||
},
|
||||
{
|
||||
label: 'customerEmployee.fileType.acceptJob',
|
||||
value: 'acceptJob',
|
||||
},
|
||||
];
|
||||
|
||||
const options = ref(objectOptions);
|
||||
|
||||
function selectAttachment(val: Record<string, unknown>) {
|
||||
const existIndex = attachment.value.findIndex((p) => p === val.value);
|
||||
|
||||
if (existIndex === -1) {
|
||||
attachment.value.push(val.value as string);
|
||||
} else {
|
||||
attachment.value.splice(Number(existIndex), 1);
|
||||
}
|
||||
}
|
||||
|
||||
function selectAll() {
|
||||
if (attachment.value.length === options.value.length) {
|
||||
attachment.value = [];
|
||||
return;
|
||||
}
|
||||
|
||||
options.value.forEach((opt) => {
|
||||
const existItem = attachment.value.some((v) => v === opt.value);
|
||||
|
||||
if (!existItem) selectAttachment(opt);
|
||||
});
|
||||
}
|
||||
|
||||
function optionSearch(val: string | null) {
|
||||
if (val === '') {
|
||||
options.value = objectOptions;
|
||||
return;
|
||||
}
|
||||
|
||||
const needle = val ? val.toLowerCase() : '';
|
||||
options.value = objectOptions.filter(
|
||||
(v) => t(v.label).toLowerCase().indexOf(needle) > -1,
|
||||
);
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<div class="row col-12">
|
||||
<div class="col-12 q-pb-sm row items-center">
|
||||
<q-icon
|
||||
flat
|
||||
size="xs"
|
||||
class="q-pa-sm rounded q-mr-sm"
|
||||
color="info"
|
||||
name="mdi-file-outline"
|
||||
style="background-color: var(--surface-3)"
|
||||
/>
|
||||
<span class="text-body1 text-weight-bold">
|
||||
{{ $t('general.information', { msg: $t('general.attachment') }) }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="col-12 row q-col-gutter-sm">
|
||||
<q-select
|
||||
:readonly
|
||||
outlined
|
||||
dense
|
||||
v-model="attachment"
|
||||
multiple
|
||||
:options="options"
|
||||
use-chips
|
||||
option-label="label"
|
||||
option-value="value"
|
||||
emit-value
|
||||
:label="$t('general.select', { msg: $t('general.attachment') })"
|
||||
class="col"
|
||||
:hide-dropdown-icon="readonly"
|
||||
>
|
||||
<template v-slot:selected-item="scope">
|
||||
<q-chip
|
||||
:removable="!readonly"
|
||||
@remove="scope.removeAtIndex(scope.index)"
|
||||
>
|
||||
{{ $t(mapName(scope.opt)) }}
|
||||
</q-chip>
|
||||
</template>
|
||||
<template v-slot:option></template>
|
||||
|
||||
<SelectMenuWithSearch
|
||||
v-if="!readonly"
|
||||
:title="$t('general.select', { msg: $t('general.attachment') })"
|
||||
:option="options"
|
||||
width="353.66px"
|
||||
@search="(v) => optionSearch(v as string)"
|
||||
@select="(v) => selectAttachment(v)"
|
||||
>
|
||||
<template #prepend>
|
||||
<q-item
|
||||
dense
|
||||
clickable
|
||||
class="bordered-t flex items-center app-text-muted"
|
||||
style="padding: 0px 16px"
|
||||
>
|
||||
<q-icon name="mdi-plus" class="q-pa-sm q-mr-sm" />
|
||||
{{ $t('general.add', { text: $t('general.attachment') }) }}
|
||||
</q-item>
|
||||
<q-item
|
||||
dense
|
||||
clickable
|
||||
class="flex items-center"
|
||||
style="
|
||||
padding: 0px 16px;
|
||||
background-color: hsla(var(--info-bg) / 0.1);
|
||||
"
|
||||
@click="selectAll()"
|
||||
>
|
||||
<q-checkbox
|
||||
:model-value="
|
||||
options.length > 0 && attachment.length === options.length
|
||||
"
|
||||
class="q-pr-sm"
|
||||
size="xs"
|
||||
@click="selectAll()"
|
||||
/>
|
||||
{{ $t('general.selectAll') }}
|
||||
</q-item>
|
||||
</template>
|
||||
<template #option="{ opt }">
|
||||
<q-checkbox
|
||||
:model-value="attachment.some((v) => v === opt.value)"
|
||||
class="q-pr-sm"
|
||||
size="xs"
|
||||
@click="selectAttachment(opt)"
|
||||
/>
|
||||
<span
|
||||
:class="{
|
||||
'app-text-info': attachment.some((v) => v === opt.value),
|
||||
}"
|
||||
>
|
||||
{{ $t(opt.label as string) }}
|
||||
</span>
|
||||
</template>
|
||||
</SelectMenuWithSearch>
|
||||
</q-select>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<style></style>
|
||||
|
|
@ -27,6 +27,7 @@ import DialogForm from 'components/DialogForm.vue';
|
|||
import ProfileBanner from 'components/ProfileBanner.vue';
|
||||
import SideMenu from 'components/SideMenu.vue';
|
||||
import ImageUploadDialog from 'components/ImageUploadDialog.vue';
|
||||
import FormDocument from 'src/components/04_product-service/FormDocument.vue';
|
||||
import KebabAction from 'src/components/shared/KebabAction.vue';
|
||||
import {
|
||||
EditButton,
|
||||
|
|
@ -161,6 +162,7 @@ const imageDialog = ref(false);
|
|||
const currentNode = ref<ProductGroup & { type: string }>();
|
||||
const expandedTree = ref<string[]>([]);
|
||||
const editByTree = ref<'group' | 'type' | undefined>();
|
||||
const formProductDocument = ref<string[]>([]);
|
||||
|
||||
const treeProductTypeAndGroup = computed(
|
||||
() =>
|
||||
|
|
@ -1016,7 +1018,10 @@ async function assignFormDataProduct(data: Product) {
|
|||
expenseType: data.expenseType,
|
||||
vatIncluded: data.vatIncluded,
|
||||
selectedImage: data.selectedImage,
|
||||
document: data.document,
|
||||
};
|
||||
if (prevProduct.value.document)
|
||||
formProductDocument.value = prevProduct.value.document;
|
||||
formDataProduct.value = { ...prevProduct.value };
|
||||
}
|
||||
|
||||
|
|
@ -1160,7 +1165,7 @@ async function submitProduct(notClose = false) {
|
|||
return;
|
||||
}
|
||||
const res = await createProduct(
|
||||
formDataProduct.value,
|
||||
{ ...formDataProduct.value, document: formProductDocument.value },
|
||||
onCreateImageList.value,
|
||||
);
|
||||
|
||||
|
|
@ -1176,6 +1181,7 @@ async function submitProduct(notClose = false) {
|
|||
await editProduct(currentIdProduct.value, {
|
||||
...formDataProduct.value,
|
||||
status: statusToggle.value ? 'ACTIVE' : 'INACTIVE',
|
||||
document: formProductDocument.value,
|
||||
});
|
||||
}
|
||||
totalProduct.value = totalProduct.value + 1;
|
||||
|
|
@ -3493,6 +3499,7 @@ watch(
|
|||
:close="
|
||||
() => {
|
||||
dialogProduct = false;
|
||||
formProductDocument = [];
|
||||
onCreateImageList = { selectedImage: '', list: [] };
|
||||
flowStore.rotate();
|
||||
}
|
||||
|
|
@ -3559,7 +3566,7 @@ watch(
|
|||
>
|
||||
<div class="q-py-md q-pl-md q-pr-sm">
|
||||
<q-item
|
||||
v-for="v in 2"
|
||||
v-for="v in 3"
|
||||
:key="v"
|
||||
dense
|
||||
clickable
|
||||
|
|
@ -3573,7 +3580,11 @@ watch(
|
|||
{{
|
||||
v === 1
|
||||
? $t('form.field.basicInformation')
|
||||
: $t('productService.product.priceInformation')
|
||||
: v === 2
|
||||
? $t('productService.product.priceInformation')
|
||||
: $t('general.information', {
|
||||
msg: $t('general.attachment'),
|
||||
})
|
||||
}}
|
||||
</span>
|
||||
</q-item>
|
||||
|
|
@ -3616,6 +3627,10 @@ watch(
|
|||
v-model:calc-vat="formDataProduct.calcVat"
|
||||
dense
|
||||
/>
|
||||
<FormDocument
|
||||
v-if="productTab === 3"
|
||||
v-model:attachment="formProductDocument"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</DialogForm>
|
||||
|
|
@ -3631,6 +3646,7 @@ watch(
|
|||
() => {
|
||||
infoProductEdit = false;
|
||||
dialogProductEdit = false;
|
||||
formProductDocument = [];
|
||||
flowStore.rotate();
|
||||
}
|
||||
"
|
||||
|
|
@ -3715,6 +3731,8 @@ watch(
|
|||
@click="
|
||||
() => {
|
||||
formDataProduct = { ...prevProduct };
|
||||
if (prevProduct.document)
|
||||
formProductDocument = prevProduct.document;
|
||||
infoProductEdit = false;
|
||||
}
|
||||
"
|
||||
|
|
@ -3748,7 +3766,7 @@ watch(
|
|||
>
|
||||
<div class="q-py-md q-pl-md q-pr-sm">
|
||||
<q-item
|
||||
v-for="v in 2"
|
||||
v-for="v in 3"
|
||||
:key="v"
|
||||
dense
|
||||
clickable
|
||||
|
|
@ -3762,7 +3780,11 @@ watch(
|
|||
{{
|
||||
v === 1
|
||||
? $t('form.field.basicInformation')
|
||||
: $t('productService.product.priceInformation')
|
||||
: v === 2
|
||||
? $t('productService.product.priceInformation')
|
||||
: $t('general.information', {
|
||||
msg: $t('general.attachment'),
|
||||
})
|
||||
}}
|
||||
</span>
|
||||
</q-item>
|
||||
|
|
@ -3801,6 +3823,11 @@ watch(
|
|||
dense
|
||||
:priceDisplay="priceDisplay"
|
||||
/>
|
||||
<FormDocument
|
||||
v-if="productTab === 3"
|
||||
:readonly="!infoProductEdit"
|
||||
v-model:attachment="formProductDocument"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</DialogForm>
|
||||
|
|
|
|||
|
|
@ -169,6 +169,7 @@ export interface Product {
|
|||
id: string;
|
||||
imageUrl: string;
|
||||
installmentNo: number;
|
||||
document?: string[];
|
||||
}
|
||||
|
||||
export interface ProductCreate {
|
||||
|
|
@ -186,6 +187,7 @@ export interface ProductCreate {
|
|||
detail: string;
|
||||
name: string;
|
||||
code: string;
|
||||
document?: string[];
|
||||
image?: File;
|
||||
status?: Status;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue