feat: add product document list (#64)

* feat: product => document select
This commit is contained in:
Methapon Metanipat 2024-11-08 10:22:50 +07:00 committed by GitHub
parent e501706e88
commit f3fdaac2b1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 226 additions and 5 deletions

View 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>

View file

@ -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>

View file

@ -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;
}