refactor: quotation
This commit is contained in:
parent
57b5392bfe
commit
f2d9507668
3 changed files with 935 additions and 148 deletions
269
src/components/05_quotation/ProductItem.vue
Normal file
269
src/components/05_quotation/ProductItem.vue
Normal file
|
|
@ -0,0 +1,269 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { QTableProps } from 'quasar';
|
||||||
|
import TableComponents from 'src/components/TableComponents.vue';
|
||||||
|
|
||||||
|
const calTax = defineModel('calTax', { default: false });
|
||||||
|
const priceData = defineModel<{
|
||||||
|
productList: number;
|
||||||
|
discount: number;
|
||||||
|
tax: number;
|
||||||
|
totalPrice: number;
|
||||||
|
}>('priceData', {
|
||||||
|
default: { productList: 0, discount: 0, tax: 0, totalPrice: 0 },
|
||||||
|
});
|
||||||
|
|
||||||
|
defineEmits<{
|
||||||
|
(e: 'delete'): void;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
withDefaults(
|
||||||
|
defineProps<{
|
||||||
|
rows: {
|
||||||
|
code: string;
|
||||||
|
list: string;
|
||||||
|
type: string;
|
||||||
|
amount: number;
|
||||||
|
pricePerUnit: number;
|
||||||
|
discount: number;
|
||||||
|
tax: number;
|
||||||
|
sumPrice: number;
|
||||||
|
}[];
|
||||||
|
}>(),
|
||||||
|
{
|
||||||
|
rows: () => [],
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
name: 'order',
|
||||||
|
align: 'center',
|
||||||
|
label: 'general.order',
|
||||||
|
field: 'order',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'code',
|
||||||
|
align: 'center',
|
||||||
|
label: 'productService.group.code',
|
||||||
|
field: 'code',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'list',
|
||||||
|
align: 'left',
|
||||||
|
label: 'productService.service.list',
|
||||||
|
field: 'list',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'amount',
|
||||||
|
align: 'center',
|
||||||
|
label: 'general.amount',
|
||||||
|
field: 'amount',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'pricePerUnit',
|
||||||
|
align: 'center',
|
||||||
|
label: 'quotation.pricePerUnit',
|
||||||
|
field: 'pricePerUnit',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'discount',
|
||||||
|
align: 'center',
|
||||||
|
label: 'general.discount',
|
||||||
|
field: 'discount',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'tax',
|
||||||
|
align: 'center',
|
||||||
|
label: 'quotation.tax',
|
||||||
|
field: 'tax',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'sumPrice',
|
||||||
|
align: 'center',
|
||||||
|
label: 'quotation.sumPrice',
|
||||||
|
field: 'sumPrice',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'action',
|
||||||
|
align: 'left',
|
||||||
|
label: '',
|
||||||
|
field: 'action',
|
||||||
|
},
|
||||||
|
] satisfies QTableProps['columns'];
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<div class="column">
|
||||||
|
<TableComponents
|
||||||
|
flat
|
||||||
|
bordered
|
||||||
|
hidePagination
|
||||||
|
button-delete
|
||||||
|
:columns="columns"
|
||||||
|
:rows="rows"
|
||||||
|
imgColumn="list"
|
||||||
|
:customColumn="['amount', 'pricePerUnit', 'discount', 'tax', 'sumPrice']"
|
||||||
|
@delete="$emit('delete')"
|
||||||
|
>
|
||||||
|
<template v-slot:img-column="{ props }">
|
||||||
|
<q-avatar class="q-mr-sm" size="md">
|
||||||
|
<q-icon
|
||||||
|
class="full-width full-height"
|
||||||
|
:name="
|
||||||
|
props.row.type === 'product'
|
||||||
|
? 'mdi-shopping-outline'
|
||||||
|
: 'mdi-server-outline'
|
||||||
|
"
|
||||||
|
:style="`color: var(--${props.row.type === 'product' ? 'teal-10' : 'orange-5'}); background: ${
|
||||||
|
props.row.type === 'product'
|
||||||
|
? `hsla(var(--teal-${$q.dark.isActive ? '8' : '10'}-hsl)/0.15)`
|
||||||
|
: `hsla(var(--orange-${$q.dark.isActive ? '6' : '5'}-hsl)/0.15)`
|
||||||
|
}`"
|
||||||
|
/>
|
||||||
|
</q-avatar>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template v-slot:body-cell-amount="{ props }">
|
||||||
|
<q-td align="center">
|
||||||
|
<q-input
|
||||||
|
dense
|
||||||
|
outlined
|
||||||
|
type="number"
|
||||||
|
style="width: 70px"
|
||||||
|
min="0"
|
||||||
|
:modelValue="props.row.amount"
|
||||||
|
@update:modelValue="(v) => (props.row = v)"
|
||||||
|
/>
|
||||||
|
</q-td>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template v-slot:body-cell-pricePerUnit="{ props }">
|
||||||
|
<q-td align="center">
|
||||||
|
<q-input
|
||||||
|
dense
|
||||||
|
outlined
|
||||||
|
type="number"
|
||||||
|
style="width: 70px"
|
||||||
|
:modelValue="props.row.pricePerUnit"
|
||||||
|
min="0"
|
||||||
|
@update:modelValue="(v) => (props.row = v)"
|
||||||
|
/>
|
||||||
|
</q-td>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template v-slot:body-cell-discount="{ props }">
|
||||||
|
<q-td align="center">
|
||||||
|
<q-input
|
||||||
|
dense
|
||||||
|
min="0"
|
||||||
|
outlined
|
||||||
|
type="number"
|
||||||
|
style="width: 70px"
|
||||||
|
:modelValue="props.row.discount"
|
||||||
|
@update:modelValue="(v) => (props.row = v)"
|
||||||
|
/>
|
||||||
|
</q-td>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template v-slot:body-cell-tax="{ props }">
|
||||||
|
<q-td align="center">
|
||||||
|
<q-input
|
||||||
|
dense
|
||||||
|
min="0"
|
||||||
|
outlined
|
||||||
|
type="number"
|
||||||
|
style="width: 70px"
|
||||||
|
:modelValue="props.row.tax"
|
||||||
|
@update:modelValue="(v) => (props.row = v)"
|
||||||
|
>
|
||||||
|
<template v-slot:append>
|
||||||
|
<span class="text-caption no-padding">%</span>
|
||||||
|
</template>
|
||||||
|
</q-input>
|
||||||
|
</q-td>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template v-slot:body-cell-sumPrice="{ props }">
|
||||||
|
<q-td align="center">
|
||||||
|
<q-input
|
||||||
|
dense
|
||||||
|
min="0"
|
||||||
|
outlined
|
||||||
|
type="number"
|
||||||
|
style="width: 70px"
|
||||||
|
:modelValue="props.row.sumPrice"
|
||||||
|
@update:modelValue="(v) => (props.row = v)"
|
||||||
|
/>
|
||||||
|
</q-td>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #button>
|
||||||
|
<q-btn
|
||||||
|
icon="mdi-monitor"
|
||||||
|
size="sm"
|
||||||
|
class="rounded q-mr-xs"
|
||||||
|
padding="4px 8px"
|
||||||
|
dense
|
||||||
|
flat
|
||||||
|
style="
|
||||||
|
background-color: hsla(var(--positive-bg) / 0.1);
|
||||||
|
color: hsl(var(--positive-bg));
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</TableComponents>
|
||||||
|
<q-checkbox
|
||||||
|
v-model="calTax"
|
||||||
|
class="q-pt-md"
|
||||||
|
size="xs"
|
||||||
|
:label="$t('quotation.calTax')"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="column q-ml-auto text-caption app-text-muted"
|
||||||
|
style="width: 15vw"
|
||||||
|
>
|
||||||
|
<div class="row">
|
||||||
|
{{ $t('quotation.allProduct') }}
|
||||||
|
<span class="q-ml-auto">{{ priceData.productList }} ฿</span>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
{{ $t('general.discount') }}
|
||||||
|
<span class="q-ml-auto">{{ priceData.discount }} ฿</span>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
{{ $t('quotation.tax') }}
|
||||||
|
<span class="q-ml-auto">{{ priceData.tax }} ฿</span>
|
||||||
|
</div>
|
||||||
|
<q-separator spaced="md" />
|
||||||
|
<div class="row text-bold text-body2" style="color: var(--foreground)">
|
||||||
|
Total Price
|
||||||
|
<span class="q-ml-auto">{{ priceData.totalPrice }} ฿</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.worker-item {
|
||||||
|
--side-color: var(--brand-1);
|
||||||
|
position: relative;
|
||||||
|
overflow-x: hidden;
|
||||||
|
|
||||||
|
&.worker-item__female {
|
||||||
|
--side-color: hsl(var(--gender-female));
|
||||||
|
}
|
||||||
|
|
||||||
|
&.worker-item__male {
|
||||||
|
--side-color: hsl(var(--gender-male));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.worker-item::before {
|
||||||
|
position: absolute;
|
||||||
|
content: ' ';
|
||||||
|
left: 0;
|
||||||
|
width: 7px;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: var(--side-color);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -1,44 +1,128 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
defineProps<{
|
import { QTableProps } from 'quasar';
|
||||||
data: {
|
import TableComponents from 'src/components/TableComponents.vue';
|
||||||
no: number;
|
|
||||||
refNo: string;
|
defineEmits<{
|
||||||
fullName: string;
|
(e: 'delete'): void;
|
||||||
birthDate: string;
|
|
||||||
age: string;
|
|
||||||
nationality: string;
|
|
||||||
docExpireDate: string;
|
|
||||||
};
|
|
||||||
color?: 'male' | 'female' | 'none';
|
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
withDefaults(
|
||||||
|
defineProps<{
|
||||||
|
employeeAmount: number;
|
||||||
|
rows: {
|
||||||
|
foreignRefNo: string;
|
||||||
|
employeeName: string;
|
||||||
|
birthDate: string;
|
||||||
|
gender: string;
|
||||||
|
age: string;
|
||||||
|
nationality: string;
|
||||||
|
documentExpireDate: string;
|
||||||
|
imgUrl: string;
|
||||||
|
status: string;
|
||||||
|
}[];
|
||||||
|
}>(),
|
||||||
|
{
|
||||||
|
rows: () => [],
|
||||||
|
employeeAmount: 0,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
name: 'order',
|
||||||
|
align: 'center',
|
||||||
|
label: 'general.order',
|
||||||
|
field: 'order',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'foreignRefNo',
|
||||||
|
align: 'center',
|
||||||
|
label: 'quotation.foreignRefNo',
|
||||||
|
field: 'foreignRefNo',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'employeeName',
|
||||||
|
align: 'left',
|
||||||
|
label: 'quotation.employeeName',
|
||||||
|
field: 'employeeName',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'birthDate',
|
||||||
|
align: 'left',
|
||||||
|
label: 'general.birthDate',
|
||||||
|
field: 'birthDate',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'age',
|
||||||
|
align: 'left',
|
||||||
|
label: 'general.age',
|
||||||
|
field: 'age',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'nationality',
|
||||||
|
align: 'left',
|
||||||
|
label: 'general.nationality',
|
||||||
|
field: 'nationality',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'documentExpireDate',
|
||||||
|
align: 'left',
|
||||||
|
label: 'quotation.documentExpireDate',
|
||||||
|
field: 'documentExpireDate',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'action',
|
||||||
|
align: 'left',
|
||||||
|
label: '',
|
||||||
|
field: 'action',
|
||||||
|
},
|
||||||
|
] satisfies QTableProps['columns'];
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div>
|
||||||
class="surface-1 rounded row bordered worker-item items-center"
|
<TableComponents
|
||||||
:class="{
|
flat
|
||||||
'worker-item__male': color === 'male',
|
bordered
|
||||||
'worker-item__female': color === 'female',
|
button-delete
|
||||||
}"
|
img-column="employeeName"
|
||||||
style="padding-block: var(--size-2)"
|
:columns
|
||||||
>
|
:rows
|
||||||
<div class="col-1 text-center">{{ data.no }}</div>
|
hidePagination
|
||||||
<div class="col-2 text-center">{{ data.refNo }}</div>
|
@delete="$emit('delete')"
|
||||||
<div class="col-3">{{ data.fullName }}</div>
|
>
|
||||||
<div class="col-1">{{ data.birthDate }}</div>
|
<template v-slot:img-column="{ props }">
|
||||||
<div class="col-1 text-center">{{ data.age }}</div>
|
<q-avatar class="q-mr-sm" size="md">
|
||||||
<div class="col-1 text-center">{{ data.nationality }}</div>
|
<q-img :src="props.row.imgUrl"></q-img>
|
||||||
<div class="col-2 text-center">{{ data.docExpireDate }}</div>
|
<div
|
||||||
<div class="col-1 text-center">
|
class="absolute-bottom-right"
|
||||||
<q-btn
|
style="
|
||||||
id="btn-delete-work"
|
border-radius: 50%;
|
||||||
icon="mdi-trash-can-outline"
|
width: 10px;
|
||||||
dense
|
height: 10px;
|
||||||
flat
|
z-index: 2;
|
||||||
round
|
background: var(--surface-1);
|
||||||
size="12px"
|
"
|
||||||
color="negative"
|
>
|
||||||
@click="$emit('delete')"
|
<div
|
||||||
/>
|
class="absolute-center"
|
||||||
|
style="border-radius: 50%; width: 8px; height: 8px"
|
||||||
|
:style="`background: hsl(var(${props.row.status !== 'INACTIVE' ? '--positive-bg' : '--text-mute'}))`"
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
</q-avatar>
|
||||||
|
</template>
|
||||||
|
</TableComponents>
|
||||||
|
|
||||||
|
<div class="row q-pt-md items-center">
|
||||||
|
<span class="q-ml-auto">
|
||||||
|
{{ $t('general.numberOf', { msg: $t('quotation.employee') }) }}
|
||||||
|
</span>
|
||||||
|
<div
|
||||||
|
class="surface-tab bordered rounded flex items-center justify-center q-mx-md"
|
||||||
|
style="width: 30px; height: 30px"
|
||||||
|
>
|
||||||
|
{{ employeeAmount }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,11 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { onMounted, ref } from 'vue';
|
import { onMounted, reactive, ref } from 'vue';
|
||||||
import { storeToRefs } from 'pinia';
|
import { storeToRefs } from 'pinia';
|
||||||
import { productTreeDecoration } from './constants';
|
import {
|
||||||
|
productTreeDecoration,
|
||||||
|
pageTabs,
|
||||||
|
fieldSelectedOption,
|
||||||
|
} from './constants';
|
||||||
import useProductServiceStore from 'src/stores/product-service';
|
import useProductServiceStore from 'src/stores/product-service';
|
||||||
import {
|
import {
|
||||||
ProductGroup,
|
ProductGroup,
|
||||||
|
|
@ -29,10 +33,24 @@ import {
|
||||||
|
|
||||||
import { CustomerBranchCreate } from 'stores/customer/types';
|
import { CustomerBranchCreate } from 'stores/customer/types';
|
||||||
import DialogForm from 'components/DialogForm.vue';
|
import DialogForm from 'components/DialogForm.vue';
|
||||||
|
import useFlowStore from 'src/stores/flow';
|
||||||
|
import { Employee } from 'src/stores/employee/types';
|
||||||
|
|
||||||
import QuotationForm from './QuotationForm.vue';
|
import QuotationForm from './QuotationForm.vue';
|
||||||
import TreeView from 'src/components/shared/TreeView.vue';
|
import TreeView from 'src/components/shared/TreeView.vue';
|
||||||
import { AddButton } from 'src/components/button';
|
|
||||||
import MainButton from 'src/components/button/MainButton.vue';
|
import MainButton from 'src/components/button/MainButton.vue';
|
||||||
|
import { AddButton } from 'src/components/button';
|
||||||
|
import StatCardComponent from 'src/components/StatCardComponent.vue';
|
||||||
|
import CreateButton from 'src/components/AddButton.vue';
|
||||||
|
import PaginationComponent from 'src/components/PaginationComponent.vue';
|
||||||
|
import QuotationCard from 'src/components/05_quotation/QuotationCard.vue';
|
||||||
|
import FormAbout from 'src/components/05_quotation/FormAbout.vue';
|
||||||
|
import SelectZone from 'src/components/shared/SelectZone.vue';
|
||||||
|
import PersonCard from 'src/components/shared/PersonCard.vue';
|
||||||
|
import { useRouter } from 'vue-router';
|
||||||
|
import ProductServiceForm from './ProductServiceForm.vue';
|
||||||
|
|
||||||
|
const flowStore = useFlowStore();
|
||||||
|
|
||||||
import {
|
import {
|
||||||
useCustomerForm,
|
useCustomerForm,
|
||||||
|
|
@ -114,6 +132,7 @@ const nodes = ref([
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
const productServiceStore = useProductServiceStore();
|
const productServiceStore = useProductServiceStore();
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
type ProductGroupId = string;
|
type ProductGroupId = string;
|
||||||
|
|
||||||
|
|
@ -129,14 +148,91 @@ const selectedGroup = ref<ProductGroup | null>(null);
|
||||||
const selectedGroupSub = ref<'product' | 'service' | null>(null);
|
const selectedGroupSub = ref<'product' | 'service' | null>(null);
|
||||||
const selectedProductServiceId = ref('');
|
const selectedProductServiceId = ref('');
|
||||||
|
|
||||||
onMounted(async () => {
|
const selectedEmployee = ref<Employee[]>([]);
|
||||||
const ret = await productServiceStore.fetchListProductService({
|
|
||||||
page: 1,
|
const pageState = reactive({
|
||||||
pageSize: 9999,
|
hideStat: false,
|
||||||
});
|
inputSearch: '',
|
||||||
if (ret) productGroup.value = ret.result;
|
statusFilter: 'all',
|
||||||
|
fieldSelected: [],
|
||||||
|
gridView: false,
|
||||||
|
|
||||||
|
currentTab: 'all',
|
||||||
|
addModal: false,
|
||||||
|
quotationModal: false,
|
||||||
|
employeeModal: false,
|
||||||
|
productServiceModal: false,
|
||||||
|
|
||||||
|
currentMaxPage: 1,
|
||||||
|
currentPage: 1,
|
||||||
|
pageSize: 30,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const statData = ref<
|
||||||
|
{
|
||||||
|
icon: string;
|
||||||
|
count: number;
|
||||||
|
label: string;
|
||||||
|
color:
|
||||||
|
| 'pink'
|
||||||
|
| 'purple'
|
||||||
|
| 'green'
|
||||||
|
| 'orange'
|
||||||
|
| 'cyan'
|
||||||
|
| 'yellow'
|
||||||
|
| 'red'
|
||||||
|
| 'magenta'
|
||||||
|
| 'blue'
|
||||||
|
| 'lime'
|
||||||
|
| 'light-purple';
|
||||||
|
}[]
|
||||||
|
>([
|
||||||
|
{
|
||||||
|
icon: 'mdi-cash',
|
||||||
|
count: 0,
|
||||||
|
label: 'quotation.type.fullAmountCash',
|
||||||
|
color: 'red',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: 'mdi-hand-coin-outline',
|
||||||
|
count: 0,
|
||||||
|
label: 'quotation.type.installmentsCash',
|
||||||
|
color: 'blue',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: 'mdi-receipt-text-outline',
|
||||||
|
count: 0,
|
||||||
|
label: 'quotation.type.fullAmountBill',
|
||||||
|
color: 'lime',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: 'mdi-receipt-text-send-outline',
|
||||||
|
count: 0,
|
||||||
|
label: 'quotation.type.installmentsBill',
|
||||||
|
color: 'light-purple',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
function triggerAddQuotationDialog() {
|
||||||
|
pageState.addModal = true;
|
||||||
|
// TODO: form and state controll
|
||||||
|
}
|
||||||
|
|
||||||
|
function triggerQuotationDialog() {
|
||||||
|
window.open('/quotation/add-quotation', '_blank');
|
||||||
|
// TODO: form and state controll
|
||||||
|
}
|
||||||
|
|
||||||
|
function triggerSelectEmployeeDialog() {
|
||||||
|
pageState.employeeModal = true;
|
||||||
|
// TODO: form and state controll
|
||||||
|
}
|
||||||
|
|
||||||
|
function triggerProductServiceDialog() {
|
||||||
|
pageState.productServiceModal = true;
|
||||||
|
// TODO: form and state controll
|
||||||
|
}
|
||||||
|
|
||||||
async function getAllProduct(
|
async function getAllProduct(
|
||||||
groupId: string,
|
groupId: string,
|
||||||
opts?: { force?: false; page?: number; pageSize?: number },
|
opts?: { force?: false; page?: number; pageSize?: number },
|
||||||
|
|
@ -186,116 +282,450 @@ function convertToTree() {
|
||||||
// TODO: convert product or service into selectable tree
|
// TODO: convert product or service into selectable tree
|
||||||
// NOTE: this is meant to be used inside getService() and getProduct() before return and after return
|
// NOTE: this is meant to be used inside getService() and getProduct() before return and after return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
const ret = await productServiceStore.fetchListProductService({
|
||||||
|
page: 1,
|
||||||
|
pageSize: 9999,
|
||||||
|
});
|
||||||
|
if (ret) productGroup.value = ret.result;
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div v-for="item in productGroup" class="row items-center">
|
<div class="column full-height no-wrap">
|
||||||
{{ item.name }}
|
<!-- SEC: stat -->
|
||||||
{{ item.code }}
|
<section class="text-body-2 q-mb-xs flex items-center">
|
||||||
<AddButton icon-only @click="selectedGroup = item" />
|
{{ $t('general.dataSum') }}
|
||||||
</div>
|
<q-badge
|
||||||
|
rounded
|
||||||
|
class="q-ml-sm"
|
||||||
|
style="
|
||||||
|
background-color: hsla(var(--info-bg) / 0.15);
|
||||||
|
color: hsl(var(--info-bg));
|
||||||
|
"
|
||||||
|
>
|
||||||
|
{{ '0' }}
|
||||||
|
</q-badge>
|
||||||
|
<q-btn
|
||||||
|
class="q-ml-sm"
|
||||||
|
icon="mdi-pin-outline"
|
||||||
|
color="primary"
|
||||||
|
size="sm"
|
||||||
|
flat
|
||||||
|
dense
|
||||||
|
rounded
|
||||||
|
@click="pageState.hideStat = !pageState.hideStat"
|
||||||
|
:style="pageState.hideStat ? 'rotate: 90deg' : ''"
|
||||||
|
style="transition: 0.1s ease-in-out"
|
||||||
|
/>
|
||||||
|
<q-btn
|
||||||
|
class="q-ml-sm"
|
||||||
|
label="add"
|
||||||
|
color="primary"
|
||||||
|
size="sm"
|
||||||
|
flat
|
||||||
|
dense
|
||||||
|
@click="triggerSelectEmployeeDialog"
|
||||||
|
:style="pageState.hideStat ? 'rotate: 90deg' : ''"
|
||||||
|
style="transition: 0.1s ease-in-out"
|
||||||
|
/>
|
||||||
|
<q-btn
|
||||||
|
class="q-ml-sm"
|
||||||
|
label="quo"
|
||||||
|
color="primary"
|
||||||
|
size="sm"
|
||||||
|
flat
|
||||||
|
dense
|
||||||
|
@click="triggerQuotationDialog"
|
||||||
|
:style="pageState.hideStat ? 'rotate: 90deg' : ''"
|
||||||
|
style="transition: 0.1s ease-in-out"
|
||||||
|
/>
|
||||||
|
<q-btn
|
||||||
|
class="q-ml-sm"
|
||||||
|
label="proserv"
|
||||||
|
color="primary"
|
||||||
|
size="sm"
|
||||||
|
flat
|
||||||
|
dense
|
||||||
|
@click="triggerProductServiceDialog"
|
||||||
|
:style="pageState.hideStat ? 'rotate: 90deg' : ''"
|
||||||
|
style="transition: 0.1s ease-in-out"
|
||||||
|
/>
|
||||||
|
</section>
|
||||||
|
|
||||||
<template v-if="selectedGroup">
|
<transition name="slide">
|
||||||
<MainButton
|
<div v-if="!pageState.hideStat" class="scroll q-mb-md">
|
||||||
icon="mdi-check"
|
<div style="display: inline-block">
|
||||||
color="var(--blue-6-hsl)"
|
<StatCardComponent
|
||||||
@click="getAllProduct(selectedGroup.id)"
|
labelI18n
|
||||||
>
|
:branch="
|
||||||
Product
|
pageState.currentTab === 'all'
|
||||||
</MainButton>
|
? statData
|
||||||
<MainButton
|
: statData.filter((i) =>
|
||||||
icon="mdi-check"
|
i.label.split('.').pop()?.includes(pageState.currentTab),
|
||||||
color="var(--blue-6-hsl)"
|
)
|
||||||
@click="getAllService(selectedGroup.id)"
|
"
|
||||||
>
|
:dark="$q.dark.isActive"
|
||||||
Service
|
/>
|
||||||
</MainButton>
|
</div>
|
||||||
|
|
||||||
<div
|
|
||||||
v-if="selectedGroupSub === 'product' && productList[selectedGroup.id]"
|
|
||||||
v-for="item in productList[selectedGroup.id]"
|
|
||||||
class="row items-center"
|
|
||||||
>
|
|
||||||
{{ item.name }}
|
|
||||||
{{ item.code }}
|
|
||||||
<AddButton icon-only @click="getProduct(item.id)" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
|
||||||
v-if="selectedGroupSub === 'service' && serviceList[selectedGroup.id]"
|
|
||||||
v-for="item in serviceList[selectedGroup.id]"
|
|
||||||
class="row items-center"
|
|
||||||
>
|
|
||||||
{{ item.name }}
|
|
||||||
{{ item.code }}
|
|
||||||
<AddButton icon-only @click="getService(item.id)" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<template
|
|
||||||
v-if="selectedGroupSub === 'service' && service[selectedProductServiceId]"
|
|
||||||
>
|
|
||||||
{{ service[selectedProductServiceId] }}
|
|
||||||
</template>
|
|
||||||
<template
|
|
||||||
v-if="selectedGroupSub === 'product' && product[selectedProductServiceId]"
|
|
||||||
>
|
|
||||||
{{ product[selectedProductServiceId] }}
|
|
||||||
</template>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<div class="surface-1 rounded full-height q-pa-md">
|
|
||||||
<TreeView
|
|
||||||
:decoration="productTreeDecoration"
|
|
||||||
v-model:nodes="nodes"
|
|
||||||
expandable
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<!-- <ImageUploadDialog
|
|
||||||
v-model:dialog-state="isOpen"
|
|
||||||
v-model:file="file"
|
|
||||||
ref="imageUploadDialog"
|
|
||||||
clear-button
|
|
||||||
>
|
|
||||||
<template #error>
|
|
||||||
<div style="display: flex; align-items: center; justify-content: center">
|
|
||||||
<h1>Fallback</h1>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</transition>
|
||||||
</ImageUploadDialog>
|
|
||||||
|
|
||||||
<button @click="isOpen = !isOpen">Open</button>
|
<!-- SEC: header content -->
|
||||||
<button @click="imageUploadDialog?.browse()">Click Me</button>
|
<header class="col surface-1 rounded">
|
||||||
<div
|
<div class="column full-height">
|
||||||
style="
|
<section
|
||||||
height: 100vh;
|
class="row surface-3 justify-between full-width items-center bordered-b"
|
||||||
background: green;
|
style="z-index: 1"
|
||||||
width: 100%;
|
>
|
||||||
color: white;
|
<div class="row q-py-sm q-px-md justify-between full-width">
|
||||||
font-weight: 600;
|
<q-input
|
||||||
display: flex;
|
for="input-search"
|
||||||
align-items: center;
|
outlined
|
||||||
justify-content: center;
|
dense
|
||||||
"
|
:label="$t('general.search')"
|
||||||
id="aaa"
|
class="q-mr-md col-12 col-md-3"
|
||||||
>
|
:bg-color="$q.dark.isActive ? 'dark' : 'white'"
|
||||||
My AAA
|
v-model="pageState.inputSearch"
|
||||||
|
debounce="200"
|
||||||
|
>
|
||||||
|
<template #prepend>
|
||||||
|
<q-icon name="mdi-magnify" />
|
||||||
|
</template>
|
||||||
|
</q-input>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="row col-12 col-md-5"
|
||||||
|
:class="{ 'q-pt-xs': $q.screen.lt.md }"
|
||||||
|
style="white-space: nowrap"
|
||||||
|
>
|
||||||
|
<q-select
|
||||||
|
v-model="pageState.statusFilter"
|
||||||
|
outlined
|
||||||
|
dense
|
||||||
|
option-value="value"
|
||||||
|
option-label="label"
|
||||||
|
class="col"
|
||||||
|
:class="{ 'offset-md-5': pageState.gridView }"
|
||||||
|
map-options
|
||||||
|
emit-value
|
||||||
|
:for="'field-select-status'"
|
||||||
|
:hide-dropdown-icon="$q.screen.lt.sm"
|
||||||
|
:options="[
|
||||||
|
{ label: $t('general.all'), value: 'all' },
|
||||||
|
{ label: $t('general.active'), value: 'active' },
|
||||||
|
{ label: $t('general.inactive'), value: 'inactive' },
|
||||||
|
]"
|
||||||
|
></q-select>
|
||||||
|
|
||||||
|
<q-select
|
||||||
|
v-if="!pageState.gridView"
|
||||||
|
id="select-field"
|
||||||
|
for="select-field"
|
||||||
|
class="col q-ml-sm"
|
||||||
|
:options="
|
||||||
|
fieldSelectedOption.map((v) => ({
|
||||||
|
...v,
|
||||||
|
label: $t(v.label),
|
||||||
|
}))
|
||||||
|
"
|
||||||
|
:display-value="$t('general.displayField')"
|
||||||
|
:hide-dropdown-icon="$q.screen.lt.sm"
|
||||||
|
v-model="pageState.fieldSelected"
|
||||||
|
option-label="label"
|
||||||
|
option-value="value"
|
||||||
|
map-options
|
||||||
|
emit-value
|
||||||
|
outlined
|
||||||
|
multiple
|
||||||
|
dense
|
||||||
|
/>
|
||||||
|
|
||||||
|
<q-btn-toggle
|
||||||
|
id="btn-mode"
|
||||||
|
v-model="pageState.gridView"
|
||||||
|
dense
|
||||||
|
class="no-shadow bordered rounded surface-1 q-ml-sm"
|
||||||
|
:toggle-color="$q.dark.isActive ? 'grey-9' : 'grey-2'"
|
||||||
|
size="xs"
|
||||||
|
:options="[
|
||||||
|
{ value: true, slot: 'folder' },
|
||||||
|
{ value: false, slot: 'list' },
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
<template v-slot:folder>
|
||||||
|
<q-icon
|
||||||
|
name="mdi-view-grid-outline"
|
||||||
|
size="16px"
|
||||||
|
class="q-px-sm q-py-xs rounded"
|
||||||
|
:style="{
|
||||||
|
color: $q.dark.isActive
|
||||||
|
? pageState.gridView
|
||||||
|
? '#C9D3DB '
|
||||||
|
: '#787B7C'
|
||||||
|
: pageState.gridView
|
||||||
|
? '#787B7C'
|
||||||
|
: '#C9D3DB',
|
||||||
|
}"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<template v-slot:list>
|
||||||
|
<q-icon
|
||||||
|
name="mdi-format-list-bulleted"
|
||||||
|
class="q-px-sm q-py-xs rounded"
|
||||||
|
size="16px"
|
||||||
|
:style="{
|
||||||
|
color: $q.dark.isActive
|
||||||
|
? pageState.gridView === false
|
||||||
|
? '#C9D3DB'
|
||||||
|
: '#787B7C'
|
||||||
|
: pageState.gridView === false
|
||||||
|
? '#787B7C'
|
||||||
|
: '#C9D3DB',
|
||||||
|
}"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</q-btn-toggle>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<nav class="surface-2 bordered-b q-px-md full-width">
|
||||||
|
<q-tabs
|
||||||
|
inline-label
|
||||||
|
mobile-arrows
|
||||||
|
dense
|
||||||
|
v-model="pageState.currentTab"
|
||||||
|
align="left"
|
||||||
|
class="full-width"
|
||||||
|
active-color="info"
|
||||||
|
>
|
||||||
|
<q-tab
|
||||||
|
v-for="tab in pageTabs"
|
||||||
|
:name="tab"
|
||||||
|
:key="tab"
|
||||||
|
@click="
|
||||||
|
async () => {
|
||||||
|
pageState.currentPage = 1;
|
||||||
|
pageState.currentTab = tab;
|
||||||
|
pageState.inputSearch = '';
|
||||||
|
pageState.statusFilter = 'all';
|
||||||
|
|
||||||
|
flowStore.rotate();
|
||||||
|
}
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="row text-capitalize"
|
||||||
|
:class="
|
||||||
|
pageState.currentTab === tab ? 'text-bold' : 'app-text-muted'
|
||||||
|
"
|
||||||
|
>
|
||||||
|
{{ $t(`quotation.type.${tab}`) }}
|
||||||
|
</div>
|
||||||
|
</q-tab>
|
||||||
|
</q-tabs>
|
||||||
|
</nav>
|
||||||
|
<!-- SEC: body content -->
|
||||||
|
<body
|
||||||
|
v-if="true"
|
||||||
|
class="col surface-2 flex items-center justify-center"
|
||||||
|
>
|
||||||
|
<CreateButton
|
||||||
|
@click="triggerAddQuotationDialog"
|
||||||
|
label="general.add"
|
||||||
|
:i18n-args="{ text: $t('quotation.title') }"
|
||||||
|
/>
|
||||||
|
</body>
|
||||||
|
<body v-else class="col q-pa-md surface-2 scroll">
|
||||||
|
<div class="row q-col-gutter-md">
|
||||||
|
<div v-for="n in 5" :key="n" class="col-md-4 col-12">
|
||||||
|
<QuotationCard
|
||||||
|
:type="
|
||||||
|
pageState.currentTab !== 'all'
|
||||||
|
? pageState.currentTab
|
||||||
|
: 'fullAmountCash'
|
||||||
|
"
|
||||||
|
code="QT240120S0002"
|
||||||
|
title="ชื่อใบเสนอราคา"
|
||||||
|
date="20/01/2024 16:00:01"
|
||||||
|
:amount="2"
|
||||||
|
customer-name="เลด้า สวนลุม"
|
||||||
|
reporter="สตีเฟ่น สมัคร"
|
||||||
|
:total-price="1500"
|
||||||
|
@view="console.log('view')"
|
||||||
|
@edit="console.log('edit')"
|
||||||
|
@link="console.log('link')"
|
||||||
|
@upload="console.log('upload')"
|
||||||
|
@delete="console.log('delete')"
|
||||||
|
@change-status="console.log('change')"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
<!-- SEC: footer content -->
|
||||||
|
<footer
|
||||||
|
class="row justify-between items-center q-px-md q-py-sm surface-2"
|
||||||
|
v-if="pageState.currentMaxPage > 0"
|
||||||
|
>
|
||||||
|
<div class="col-4">
|
||||||
|
<div class="row items-center no-wrap">
|
||||||
|
<div class="app-text-muted q-mr-sm" v-if="$q.screen.gt.sm">
|
||||||
|
{{ $t('general.recordPerPage') }}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<q-btn-dropdown
|
||||||
|
dense
|
||||||
|
unelevated
|
||||||
|
:label="pageState.pageSize"
|
||||||
|
class="bordered q-pl-md"
|
||||||
|
>
|
||||||
|
<q-list>
|
||||||
|
<q-item
|
||||||
|
v-for="v in [10, 30, 50, 100, 500, 1000]"
|
||||||
|
:key="v"
|
||||||
|
clickable
|
||||||
|
v-close-popup
|
||||||
|
@click="
|
||||||
|
async () => {
|
||||||
|
pageState.pageSize = v;
|
||||||
|
}
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<q-item-section>
|
||||||
|
<q-item-label>{{ v }}</q-item-label>
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
</q-list>
|
||||||
|
</q-btn-dropdown>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-4 row justify-center app-text-muted">
|
||||||
|
{{
|
||||||
|
$t('general.recordsPage', {
|
||||||
|
resultcurrentPage: 0,
|
||||||
|
total: 0,
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
</div>
|
||||||
|
<nav class="col-4 row justify-end">
|
||||||
|
<PaginationComponent
|
||||||
|
v-model:current-page="pageState.currentPage"
|
||||||
|
v-model:max-page="pageState.currentMaxPage"
|
||||||
|
/>
|
||||||
|
<!-- :fetch-data="async () => await fetchUserList()" -->
|
||||||
|
</nav>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
</div>
|
</div>
|
||||||
<div
|
|
||||||
style="
|
<!-- SEC: Dialog -->
|
||||||
height: 100vh;
|
<!-- add quotation -->
|
||||||
background: red;
|
<DialogForm
|
||||||
width: 100%;
|
:title="$t('general.add', { text: $t('quotation.title') })"
|
||||||
color: white;
|
v-model:modal="pageState.addModal"
|
||||||
display: flex;
|
:submit-label="$t('general.add', { text: $t('quotation.title') })"
|
||||||
align-items: center;
|
submit-icon="mdi-check"
|
||||||
justify-content: center;
|
height="auto"
|
||||||
font-weight: 600;
|
width="60vw"
|
||||||
"
|
|
||||||
id="my-anchor"
|
|
||||||
>
|
>
|
||||||
My Menu
|
<header class="q-mx-lg q-mt-lg">
|
||||||
</div> -->
|
<ProfileBanner
|
||||||
<QuotationForm v-model:dialog-state="dialog" readonly />
|
img="/images/quotation-bg-avatar.png"
|
||||||
|
fallback-cover="/images/quotation-banner.png"
|
||||||
|
bg-color="var(--surface-1)"
|
||||||
|
:title="$t('general.itemNo', { msg: $t('quotation.title') })"
|
||||||
|
hideActive
|
||||||
|
readonly
|
||||||
|
noImageAction
|
||||||
|
hideFade
|
||||||
|
/>
|
||||||
|
</header>
|
||||||
|
<section class="col surface-1 q-ma-lg rounded bordered row scroll">
|
||||||
|
<div
|
||||||
|
class="col-12 q-px-md q-py-lg"
|
||||||
|
id="customer-form-content"
|
||||||
|
style="height: 100%; max-height: 100%; overflow-y: auto"
|
||||||
|
>
|
||||||
|
<FormAbout prefixId="zxc" />
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<template #footer>
|
||||||
|
<q-btn
|
||||||
|
style="
|
||||||
|
border-radius: var(--radius-2);
|
||||||
|
padding-block: var(--size-1);
|
||||||
|
padding-inline: var(--size-2);
|
||||||
|
"
|
||||||
|
color="negative"
|
||||||
|
outline
|
||||||
|
:label="$t('general.add', { text: $t('quotation.newCustomer') })"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</DialogForm>
|
||||||
|
|
||||||
|
<!-- add employee -->
|
||||||
|
<DialogForm
|
||||||
|
:title="$t('general.select', { msg: $t('quotation.employeeList') })"
|
||||||
|
v-model:modal="pageState.employeeModal"
|
||||||
|
:submit-label="$t('general.select', { msg: $t('quotation.employee') })"
|
||||||
|
submit-icon="mdi-check"
|
||||||
|
height="75vh"
|
||||||
|
>
|
||||||
|
<section class="col row scroll">
|
||||||
|
<SelectZone
|
||||||
|
v-model:selected-item="selectedEmployee"
|
||||||
|
:items="[
|
||||||
|
{
|
||||||
|
name: 'มิเคล่า สุวรรณดี',
|
||||||
|
gender: 'female',
|
||||||
|
telephoneNo: '0621249602',
|
||||||
|
code: 'CORP000000-1-01-240001',
|
||||||
|
birthDate: '16 ปี 11 เดือน 5 วัน',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'มิลิเซนต์ สุวรรณดี',
|
||||||
|
gender: 'female',
|
||||||
|
telephoneNo: '0621249666',
|
||||||
|
code: 'CORP000000-1-01-240002',
|
||||||
|
birthDate: '19 ปี 2 เดือน 2 วัน',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'ไรคาร์ด พวงศรี',
|
||||||
|
gender: 'male',
|
||||||
|
telephoneNo: '0621249777',
|
||||||
|
code: 'CORP000000-1-01-240003',
|
||||||
|
birthDate: '39 ปี 5 เดือน 2 วัน',
|
||||||
|
},
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
<template #top><AddButton icon-only /></template>
|
||||||
|
<template #data="{ item }">
|
||||||
|
<PersonCard
|
||||||
|
noAction
|
||||||
|
prefixId="asda"
|
||||||
|
class="full-width"
|
||||||
|
:data="{
|
||||||
|
name: item.name,
|
||||||
|
code: item.code,
|
||||||
|
female: item.gender === 'female',
|
||||||
|
male: item.gender === 'male',
|
||||||
|
img: 'images/employee-avatar.png',
|
||||||
|
detail: [
|
||||||
|
{ icon: 'mdi-phone-outline', value: item.telephoneNo },
|
||||||
|
{ icon: 'mdi-clock-outline', value: item.birthDate },
|
||||||
|
],
|
||||||
|
}"
|
||||||
|
></PersonCard>
|
||||||
|
</template>
|
||||||
|
</SelectZone>
|
||||||
|
</section>
|
||||||
|
</DialogForm>
|
||||||
|
|
||||||
<DialogContainer
|
<DialogContainer
|
||||||
:model-value="test"
|
:model-value="test"
|
||||||
|
|
@ -559,6 +989,10 @@ function convertToTree() {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</DialogForm>
|
</DialogForm>
|
||||||
|
|
||||||
|
<ProductServiceForm
|
||||||
|
v-model="pageState.productServiceModal"
|
||||||
|
></ProductServiceForm>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped></style>
|
<style scoped></style>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue