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>
|
||||
defineProps<{
|
||||
data: {
|
||||
no: number;
|
||||
refNo: string;
|
||||
fullName: string;
|
||||
birthDate: string;
|
||||
age: string;
|
||||
nationality: string;
|
||||
docExpireDate: string;
|
||||
};
|
||||
color?: 'male' | 'female' | 'none';
|
||||
import { QTableProps } from 'quasar';
|
||||
import TableComponents from 'src/components/TableComponents.vue';
|
||||
|
||||
defineEmits<{
|
||||
(e: 'delete'): void;
|
||||
}>();
|
||||
|
||||
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>
|
||||
<template>
|
||||
<div
|
||||
class="surface-1 rounded row bordered worker-item items-center"
|
||||
:class="{
|
||||
'worker-item__male': color === 'male',
|
||||
'worker-item__female': color === 'female',
|
||||
}"
|
||||
style="padding-block: var(--size-2)"
|
||||
>
|
||||
<div class="col-1 text-center">{{ data.no }}</div>
|
||||
<div class="col-2 text-center">{{ data.refNo }}</div>
|
||||
<div class="col-3">{{ data.fullName }}</div>
|
||||
<div class="col-1">{{ data.birthDate }}</div>
|
||||
<div class="col-1 text-center">{{ data.age }}</div>
|
||||
<div class="col-1 text-center">{{ data.nationality }}</div>
|
||||
<div class="col-2 text-center">{{ data.docExpireDate }}</div>
|
||||
<div class="col-1 text-center">
|
||||
<q-btn
|
||||
id="btn-delete-work"
|
||||
icon="mdi-trash-can-outline"
|
||||
dense
|
||||
flat
|
||||
round
|
||||
size="12px"
|
||||
color="negative"
|
||||
@click="$emit('delete')"
|
||||
/>
|
||||
<div>
|
||||
<TableComponents
|
||||
flat
|
||||
bordered
|
||||
button-delete
|
||||
img-column="employeeName"
|
||||
:columns
|
||||
:rows
|
||||
hidePagination
|
||||
@delete="$emit('delete')"
|
||||
>
|
||||
<template v-slot:img-column="{ props }">
|
||||
<q-avatar class="q-mr-sm" size="md">
|
||||
<q-img :src="props.row.imgUrl"></q-img>
|
||||
<div
|
||||
class="absolute-bottom-right"
|
||||
style="
|
||||
border-radius: 50%;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
z-index: 2;
|
||||
background: var(--surface-1);
|
||||
"
|
||||
>
|
||||
<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>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,11 @@
|
|||
<script lang="ts" setup>
|
||||
import { onMounted, ref } from 'vue';
|
||||
import { onMounted, reactive, ref } from 'vue';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { productTreeDecoration } from './constants';
|
||||
import {
|
||||
productTreeDecoration,
|
||||
pageTabs,
|
||||
fieldSelectedOption,
|
||||
} from './constants';
|
||||
import useProductServiceStore from 'src/stores/product-service';
|
||||
import {
|
||||
ProductGroup,
|
||||
|
|
@ -29,10 +33,24 @@ import {
|
|||
|
||||
import { CustomerBranchCreate } from 'stores/customer/types';
|
||||
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 TreeView from 'src/components/shared/TreeView.vue';
|
||||
import { AddButton } from 'src/components/button';
|
||||
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 {
|
||||
useCustomerForm,
|
||||
|
|
@ -114,6 +132,7 @@ const nodes = ref([
|
|||
},
|
||||
]);
|
||||
const productServiceStore = useProductServiceStore();
|
||||
const router = useRouter();
|
||||
|
||||
type ProductGroupId = string;
|
||||
|
||||
|
|
@ -129,14 +148,91 @@ const selectedGroup = ref<ProductGroup | null>(null);
|
|||
const selectedGroupSub = ref<'product' | 'service' | null>(null);
|
||||
const selectedProductServiceId = ref('');
|
||||
|
||||
onMounted(async () => {
|
||||
const ret = await productServiceStore.fetchListProductService({
|
||||
page: 1,
|
||||
pageSize: 9999,
|
||||
});
|
||||
if (ret) productGroup.value = ret.result;
|
||||
const selectedEmployee = ref<Employee[]>([]);
|
||||
|
||||
const pageState = reactive({
|
||||
hideStat: false,
|
||||
inputSearch: '',
|
||||
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(
|
||||
groupId: string,
|
||||
opts?: { force?: false; page?: number; pageSize?: number },
|
||||
|
|
@ -186,116 +282,450 @@ function convertToTree() {
|
|||
// TODO: convert product or service into selectable tree
|
||||
// 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>
|
||||
|
||||
<template>
|
||||
<div v-for="item in productGroup" class="row items-center">
|
||||
{{ item.name }}
|
||||
{{ item.code }}
|
||||
<AddButton icon-only @click="selectedGroup = item" />
|
||||
</div>
|
||||
<div class="column full-height no-wrap">
|
||||
<!-- SEC: stat -->
|
||||
<section class="text-body-2 q-mb-xs flex items-center">
|
||||
{{ $t('general.dataSum') }}
|
||||
<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">
|
||||
<MainButton
|
||||
icon="mdi-check"
|
||||
color="var(--blue-6-hsl)"
|
||||
@click="getAllProduct(selectedGroup.id)"
|
||||
>
|
||||
Product
|
||||
</MainButton>
|
||||
<MainButton
|
||||
icon="mdi-check"
|
||||
color="var(--blue-6-hsl)"
|
||||
@click="getAllService(selectedGroup.id)"
|
||||
>
|
||||
Service
|
||||
</MainButton>
|
||||
|
||||
<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>
|
||||
<transition name="slide">
|
||||
<div v-if="!pageState.hideStat" class="scroll q-mb-md">
|
||||
<div style="display: inline-block">
|
||||
<StatCardComponent
|
||||
labelI18n
|
||||
:branch="
|
||||
pageState.currentTab === 'all'
|
||||
? statData
|
||||
: statData.filter((i) =>
|
||||
i.label.split('.').pop()?.includes(pageState.currentTab),
|
||||
)
|
||||
"
|
||||
:dark="$q.dark.isActive"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</ImageUploadDialog>
|
||||
</transition>
|
||||
|
||||
<button @click="isOpen = !isOpen">Open</button>
|
||||
<button @click="imageUploadDialog?.browse()">Click Me</button>
|
||||
<div
|
||||
style="
|
||||
height: 100vh;
|
||||
background: green;
|
||||
width: 100%;
|
||||
color: white;
|
||||
font-weight: 600;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
"
|
||||
id="aaa"
|
||||
>
|
||||
My AAA
|
||||
<!-- SEC: header content -->
|
||||
<header class="col surface-1 rounded">
|
||||
<div class="column full-height">
|
||||
<section
|
||||
class="row surface-3 justify-between full-width items-center bordered-b"
|
||||
style="z-index: 1"
|
||||
>
|
||||
<div class="row q-py-sm q-px-md justify-between full-width">
|
||||
<q-input
|
||||
for="input-search"
|
||||
outlined
|
||||
dense
|
||||
:label="$t('general.search')"
|
||||
class="q-mr-md col-12 col-md-3"
|
||||
:bg-color="$q.dark.isActive ? 'dark' : 'white'"
|
||||
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
|
||||
style="
|
||||
height: 100vh;
|
||||
background: red;
|
||||
width: 100%;
|
||||
color: white;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-weight: 600;
|
||||
"
|
||||
id="my-anchor"
|
||||
|
||||
<!-- SEC: Dialog -->
|
||||
<!-- add quotation -->
|
||||
<DialogForm
|
||||
:title="$t('general.add', { text: $t('quotation.title') })"
|
||||
v-model:modal="pageState.addModal"
|
||||
:submit-label="$t('general.add', { text: $t('quotation.title') })"
|
||||
submit-icon="mdi-check"
|
||||
height="auto"
|
||||
width="60vw"
|
||||
>
|
||||
My Menu
|
||||
</div> -->
|
||||
<QuotationForm v-model:dialog-state="dialog" readonly />
|
||||
<header class="q-mx-lg q-mt-lg">
|
||||
<ProfileBanner
|
||||
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
|
||||
:model-value="test"
|
||||
|
|
@ -559,6 +989,10 @@ function convertToTree() {
|
|||
</div>
|
||||
</div>
|
||||
</DialogForm>
|
||||
|
||||
<ProductServiceForm
|
||||
v-model="pageState.productServiceModal"
|
||||
></ProductServiceForm>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue