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 ProfileBanner from 'components/ProfileBanner.vue';
|
||||||
import SideMenu from 'components/SideMenu.vue';
|
import SideMenu from 'components/SideMenu.vue';
|
||||||
import ImageUploadDialog from 'components/ImageUploadDialog.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 KebabAction from 'src/components/shared/KebabAction.vue';
|
||||||
import {
|
import {
|
||||||
EditButton,
|
EditButton,
|
||||||
|
|
@ -161,6 +162,7 @@ const imageDialog = ref(false);
|
||||||
const currentNode = ref<ProductGroup & { type: string }>();
|
const currentNode = ref<ProductGroup & { type: string }>();
|
||||||
const expandedTree = ref<string[]>([]);
|
const expandedTree = ref<string[]>([]);
|
||||||
const editByTree = ref<'group' | 'type' | undefined>();
|
const editByTree = ref<'group' | 'type' | undefined>();
|
||||||
|
const formProductDocument = ref<string[]>([]);
|
||||||
|
|
||||||
const treeProductTypeAndGroup = computed(
|
const treeProductTypeAndGroup = computed(
|
||||||
() =>
|
() =>
|
||||||
|
|
@ -1016,7 +1018,10 @@ async function assignFormDataProduct(data: Product) {
|
||||||
expenseType: data.expenseType,
|
expenseType: data.expenseType,
|
||||||
vatIncluded: data.vatIncluded,
|
vatIncluded: data.vatIncluded,
|
||||||
selectedImage: data.selectedImage,
|
selectedImage: data.selectedImage,
|
||||||
|
document: data.document,
|
||||||
};
|
};
|
||||||
|
if (prevProduct.value.document)
|
||||||
|
formProductDocument.value = prevProduct.value.document;
|
||||||
formDataProduct.value = { ...prevProduct.value };
|
formDataProduct.value = { ...prevProduct.value };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1160,7 +1165,7 @@ async function submitProduct(notClose = false) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const res = await createProduct(
|
const res = await createProduct(
|
||||||
formDataProduct.value,
|
{ ...formDataProduct.value, document: formProductDocument.value },
|
||||||
onCreateImageList.value,
|
onCreateImageList.value,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -1176,6 +1181,7 @@ async function submitProduct(notClose = false) {
|
||||||
await editProduct(currentIdProduct.value, {
|
await editProduct(currentIdProduct.value, {
|
||||||
...formDataProduct.value,
|
...formDataProduct.value,
|
||||||
status: statusToggle.value ? 'ACTIVE' : 'INACTIVE',
|
status: statusToggle.value ? 'ACTIVE' : 'INACTIVE',
|
||||||
|
document: formProductDocument.value,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
totalProduct.value = totalProduct.value + 1;
|
totalProduct.value = totalProduct.value + 1;
|
||||||
|
|
@ -3493,6 +3499,7 @@ watch(
|
||||||
:close="
|
:close="
|
||||||
() => {
|
() => {
|
||||||
dialogProduct = false;
|
dialogProduct = false;
|
||||||
|
formProductDocument = [];
|
||||||
onCreateImageList = { selectedImage: '', list: [] };
|
onCreateImageList = { selectedImage: '', list: [] };
|
||||||
flowStore.rotate();
|
flowStore.rotate();
|
||||||
}
|
}
|
||||||
|
|
@ -3559,7 +3566,7 @@ watch(
|
||||||
>
|
>
|
||||||
<div class="q-py-md q-pl-md q-pr-sm">
|
<div class="q-py-md q-pl-md q-pr-sm">
|
||||||
<q-item
|
<q-item
|
||||||
v-for="v in 2"
|
v-for="v in 3"
|
||||||
:key="v"
|
:key="v"
|
||||||
dense
|
dense
|
||||||
clickable
|
clickable
|
||||||
|
|
@ -3573,7 +3580,11 @@ watch(
|
||||||
{{
|
{{
|
||||||
v === 1
|
v === 1
|
||||||
? $t('form.field.basicInformation')
|
? $t('form.field.basicInformation')
|
||||||
: $t('productService.product.priceInformation')
|
: v === 2
|
||||||
|
? $t('productService.product.priceInformation')
|
||||||
|
: $t('general.information', {
|
||||||
|
msg: $t('general.attachment'),
|
||||||
|
})
|
||||||
}}
|
}}
|
||||||
</span>
|
</span>
|
||||||
</q-item>
|
</q-item>
|
||||||
|
|
@ -3616,6 +3627,10 @@ watch(
|
||||||
v-model:calc-vat="formDataProduct.calcVat"
|
v-model:calc-vat="formDataProduct.calcVat"
|
||||||
dense
|
dense
|
||||||
/>
|
/>
|
||||||
|
<FormDocument
|
||||||
|
v-if="productTab === 3"
|
||||||
|
v-model:attachment="formProductDocument"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</DialogForm>
|
</DialogForm>
|
||||||
|
|
@ -3631,6 +3646,7 @@ watch(
|
||||||
() => {
|
() => {
|
||||||
infoProductEdit = false;
|
infoProductEdit = false;
|
||||||
dialogProductEdit = false;
|
dialogProductEdit = false;
|
||||||
|
formProductDocument = [];
|
||||||
flowStore.rotate();
|
flowStore.rotate();
|
||||||
}
|
}
|
||||||
"
|
"
|
||||||
|
|
@ -3715,6 +3731,8 @@ watch(
|
||||||
@click="
|
@click="
|
||||||
() => {
|
() => {
|
||||||
formDataProduct = { ...prevProduct };
|
formDataProduct = { ...prevProduct };
|
||||||
|
if (prevProduct.document)
|
||||||
|
formProductDocument = prevProduct.document;
|
||||||
infoProductEdit = false;
|
infoProductEdit = false;
|
||||||
}
|
}
|
||||||
"
|
"
|
||||||
|
|
@ -3748,7 +3766,7 @@ watch(
|
||||||
>
|
>
|
||||||
<div class="q-py-md q-pl-md q-pr-sm">
|
<div class="q-py-md q-pl-md q-pr-sm">
|
||||||
<q-item
|
<q-item
|
||||||
v-for="v in 2"
|
v-for="v in 3"
|
||||||
:key="v"
|
:key="v"
|
||||||
dense
|
dense
|
||||||
clickable
|
clickable
|
||||||
|
|
@ -3762,7 +3780,11 @@ watch(
|
||||||
{{
|
{{
|
||||||
v === 1
|
v === 1
|
||||||
? $t('form.field.basicInformation')
|
? $t('form.field.basicInformation')
|
||||||
: $t('productService.product.priceInformation')
|
: v === 2
|
||||||
|
? $t('productService.product.priceInformation')
|
||||||
|
: $t('general.information', {
|
||||||
|
msg: $t('general.attachment'),
|
||||||
|
})
|
||||||
}}
|
}}
|
||||||
</span>
|
</span>
|
||||||
</q-item>
|
</q-item>
|
||||||
|
|
@ -3801,6 +3823,11 @@ watch(
|
||||||
dense
|
dense
|
||||||
:priceDisplay="priceDisplay"
|
:priceDisplay="priceDisplay"
|
||||||
/>
|
/>
|
||||||
|
<FormDocument
|
||||||
|
v-if="productTab === 3"
|
||||||
|
:readonly="!infoProductEdit"
|
||||||
|
v-model:attachment="formProductDocument"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</DialogForm>
|
</DialogForm>
|
||||||
|
|
|
||||||
|
|
@ -169,6 +169,7 @@ export interface Product {
|
||||||
id: string;
|
id: string;
|
||||||
imageUrl: string;
|
imageUrl: string;
|
||||||
installmentNo: number;
|
installmentNo: number;
|
||||||
|
document?: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ProductCreate {
|
export interface ProductCreate {
|
||||||
|
|
@ -186,6 +187,7 @@ export interface ProductCreate {
|
||||||
detail: string;
|
detail: string;
|
||||||
name: string;
|
name: string;
|
||||||
code: string;
|
code: string;
|
||||||
|
document?: string[];
|
||||||
image?: File;
|
image?: File;
|
||||||
status?: Status;
|
status?: Status;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue