feat: reverse tree convert
This commit is contained in:
parent
d5c8ce85b6
commit
13cf2da9f9
3 changed files with 147 additions and 33 deletions
|
|
@ -19,6 +19,7 @@ import { CustomerBranchCreate } from 'stores/customer/types';
|
||||||
import { Employee } from 'src/stores/employee/types';
|
import { Employee } from 'src/stores/employee/types';
|
||||||
|
|
||||||
// NOTE: Import Components
|
// NOTE: Import Components
|
||||||
|
import TreeView from 'src/components/shared/TreeView.vue';
|
||||||
import QuotationCard from 'src/components/05_quotation/QuotationCard.vue';
|
import QuotationCard from 'src/components/05_quotation/QuotationCard.vue';
|
||||||
import PaginationComponent from 'src/components/PaginationComponent.vue';
|
import PaginationComponent from 'src/components/PaginationComponent.vue';
|
||||||
import StatCardComponent from 'src/components/StatCardComponent.vue';
|
import StatCardComponent from 'src/components/StatCardComponent.vue';
|
||||||
|
|
@ -26,7 +27,6 @@ import ButtonAddComponent from 'components/ButtonAddCompoent.vue';
|
||||||
import FormAbout from 'src/components/05_quotation/FormAbout.vue';
|
import FormAbout from 'src/components/05_quotation/FormAbout.vue';
|
||||||
import SelectZone from 'src/components/shared/SelectZone.vue';
|
import SelectZone from 'src/components/shared/SelectZone.vue';
|
||||||
import PersonCard from 'src/components/shared/PersonCard.vue';
|
import PersonCard from 'src/components/shared/PersonCard.vue';
|
||||||
import ProductServiceForm from './ProductServiceForm.vue';
|
|
||||||
import CreateButton from 'src/components/AddButton.vue';
|
import CreateButton from 'src/components/AddButton.vue';
|
||||||
import ItemCard from 'src/components/ItemCard.vue';
|
import ItemCard from 'src/components/ItemCard.vue';
|
||||||
import DialogForm from 'components/DialogForm.vue';
|
import DialogForm from 'components/DialogForm.vue';
|
||||||
|
|
@ -34,11 +34,6 @@ import { AddButton } from 'src/components/button';
|
||||||
import SideMenu from 'components/SideMenu.vue';
|
import SideMenu from 'components/SideMenu.vue';
|
||||||
|
|
||||||
import { dialogCreateCustomerItem } from 'src/pages/03_customer-management/constant';
|
import { dialogCreateCustomerItem } from 'src/pages/03_customer-management/constant';
|
||||||
import {
|
|
||||||
ProductGroup,
|
|
||||||
Product,
|
|
||||||
Service,
|
|
||||||
} from 'src/stores/product-service/types';
|
|
||||||
|
|
||||||
import { SaveButton } from 'components/button';
|
import { SaveButton } from 'components/button';
|
||||||
|
|
||||||
|
|
@ -54,6 +49,7 @@ import {
|
||||||
useEmployeeForm,
|
useEmployeeForm,
|
||||||
} from 'src/pages/03_customer-management/form';
|
} from 'src/pages/03_customer-management/form';
|
||||||
import QuotationView from './QuotationView.vue';
|
import QuotationView from './QuotationView.vue';
|
||||||
|
import { quotationProductTree } from './utils';
|
||||||
|
|
||||||
const quotationFormStore = useQuotationForm();
|
const quotationFormStore = useQuotationForm();
|
||||||
const customerFormStore = useCustomerForm();
|
const customerFormStore = useCustomerForm();
|
||||||
|
|
@ -633,6 +629,38 @@ watch(() => pageState.currentTab, fetchQuotationList);
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
(
|
||||||
|
<TreeView
|
||||||
|
v-if="quotationFormData.productServiceList"
|
||||||
|
class="full-width q-py-sm q-px-md"
|
||||||
|
:nodes="quotationProductTree(quotationFormData?.productServiceList)"
|
||||||
|
expandable
|
||||||
|
selectable
|
||||||
|
:selected-node="[]"
|
||||||
|
@select=""
|
||||||
|
@delete=""
|
||||||
|
:decoration="[
|
||||||
|
{
|
||||||
|
level: 0,
|
||||||
|
icon: 'mdi-server-outline',
|
||||||
|
bg: 'hsla(var(--orange-5-hsl)/0.1)',
|
||||||
|
fg: 'var(--orange-5)',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
level: 1,
|
||||||
|
icon: 'mdi-briefcase-outline',
|
||||||
|
bg: 'hsla(var(--violet-11-hsl)/0.1)',
|
||||||
|
fg: 'var(--violet-11)',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
level: 2,
|
||||||
|
icon: 'mdi-shopping-outline',
|
||||||
|
bg: 'hsla(var(--teal-10-hsl)/0.1)',
|
||||||
|
fg: 'var(--teal-10)',
|
||||||
|
},
|
||||||
|
]"
|
||||||
|
/>
|
||||||
|
)
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
<!-- SEC: footer content -->
|
<!-- SEC: footer content -->
|
||||||
|
|
@ -1056,33 +1084,6 @@ watch(() => pageState.currentTab, fetchQuotationList);
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</DialogForm>
|
</DialogForm>
|
||||||
|
|
||||||
<ProductServiceForm
|
|
||||||
v-model="pageState.productServiceModal"
|
|
||||||
v-model:product-group="productGroup"
|
|
||||||
v-model:product-list="productList"
|
|
||||||
v-model:service-list="serviceList"
|
|
||||||
@select-group="
|
|
||||||
async (id) => {
|
|
||||||
await getAllService(id);
|
|
||||||
await getAllProduct(id);
|
|
||||||
}
|
|
||||||
"
|
|
||||||
@submit="
|
|
||||||
(node) => {
|
|
||||||
const nodeRecursive = (
|
|
||||||
v: (typeof node)[number],
|
|
||||||
): typeof node | (typeof node)[number] => {
|
|
||||||
if (v.checked && v.children) return v.children.flatMap(nodeRecursive);
|
|
||||||
if (v.checked) return v;
|
|
||||||
return [];
|
|
||||||
};
|
|
||||||
|
|
||||||
const filtered = node.flatMap(nodeRecursive);
|
|
||||||
console.log(JSON.parse(JSON.stringify(filtered.map((v) => v.value))));
|
|
||||||
}
|
|
||||||
"
|
|
||||||
></ProductServiceForm>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped></style>
|
<style scoped></style>
|
||||||
|
|
|
||||||
106
src/pages/05_quotation/utils.ts
Normal file
106
src/pages/05_quotation/utils.ts
Normal file
|
|
@ -0,0 +1,106 @@
|
||||||
|
import { QuotationFull } from 'src/stores/quotations/types';
|
||||||
|
|
||||||
|
export type ProductTree = {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
subtitle: string;
|
||||||
|
checked: boolean;
|
||||||
|
opened: boolean;
|
||||||
|
icon: string;
|
||||||
|
fg: string;
|
||||||
|
bg: string;
|
||||||
|
value?: {
|
||||||
|
vat: number;
|
||||||
|
pricePerUnit: number;
|
||||||
|
discount: number;
|
||||||
|
amount: number;
|
||||||
|
serviceId?: string;
|
||||||
|
service?: QuotationFull['productServiceList'][number]['service'];
|
||||||
|
workId: string;
|
||||||
|
work?: QuotationFull['productServiceList'][number]['work'];
|
||||||
|
productId: string;
|
||||||
|
product: QuotationFull['productServiceList'][number]['product'];
|
||||||
|
};
|
||||||
|
children?: ProductTree;
|
||||||
|
}[];
|
||||||
|
|
||||||
|
export function quotationProductTree(
|
||||||
|
list: {
|
||||||
|
service?: QuotationFull['productServiceList'][number]['service'];
|
||||||
|
work?: QuotationFull['productServiceList'][number]['work'];
|
||||||
|
product: QuotationFull['productServiceList'][number]['product'];
|
||||||
|
}[],
|
||||||
|
): ProductTree {
|
||||||
|
const ret: ProductTree = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < list.length; i++) {
|
||||||
|
const current = list[i];
|
||||||
|
|
||||||
|
if (ret.find((v) => v.id === current.service?.id)) continue;
|
||||||
|
|
||||||
|
if (current.service && current.work) {
|
||||||
|
const service = current.service;
|
||||||
|
|
||||||
|
ret.push({
|
||||||
|
id: service.id,
|
||||||
|
title: service.name,
|
||||||
|
subtitle: service.code,
|
||||||
|
opened: service.work.length > 0,
|
||||||
|
checked: true,
|
||||||
|
children: service.work.flatMap((work) => {
|
||||||
|
const mapper = (
|
||||||
|
relation: (typeof work)['productOnWork'][number],
|
||||||
|
) => ({
|
||||||
|
id: relation.product.id,
|
||||||
|
title: relation.product.name,
|
||||||
|
subtitle: relation.product.code,
|
||||||
|
opened: true,
|
||||||
|
checked: !!list.find(
|
||||||
|
(v) =>
|
||||||
|
v.service?.id === service.id &&
|
||||||
|
v.work?.id === work.id &&
|
||||||
|
v.product.id === relation.product.id,
|
||||||
|
),
|
||||||
|
icon: 'mdi-shopping-outline',
|
||||||
|
bg: 'hsla(var(--teal-10-hsl)/0.1)',
|
||||||
|
fg: 'var(--teal-10)',
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!!work.name) {
|
||||||
|
return {
|
||||||
|
id: work.id,
|
||||||
|
title: work.name,
|
||||||
|
subtitle: ' ',
|
||||||
|
opened: true,
|
||||||
|
checked: !!list.find(
|
||||||
|
(v) => v.service?.id === service.id && v.work?.id === work.id,
|
||||||
|
),
|
||||||
|
children: work.productOnWork.map(mapper),
|
||||||
|
icon: 'mdi-briefcase-outline',
|
||||||
|
bg: 'hsla(var(--violet-11-hsl)/0.1)',
|
||||||
|
fg: 'var(--violet-11)',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return work.productOnWork.map(mapper);
|
||||||
|
}),
|
||||||
|
icon: 'mdi-server-outline',
|
||||||
|
bg: 'hsla(var(--orange-5-hsl)/0.1)',
|
||||||
|
fg: 'var(--orange-5)',
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
ret.push({
|
||||||
|
id: current.product.id,
|
||||||
|
title: current.product.name,
|
||||||
|
subtitle: current.product.code,
|
||||||
|
opened: true,
|
||||||
|
checked: true,
|
||||||
|
icon: 'mdi-shopping-outline',
|
||||||
|
bg: 'hsla(var(--teal-10-hsl)/0.1)',
|
||||||
|
fg: 'var(--teal-10)',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
@ -149,6 +149,13 @@ type ServiceRelation = {
|
||||||
createdByUserId: string;
|
createdByUserId: string;
|
||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
updatedByUserId: string;
|
updatedByUserId: string;
|
||||||
|
|
||||||
|
work: (WorkRelation & {
|
||||||
|
productOnWork: {
|
||||||
|
order: number;
|
||||||
|
product: ProductRelation;
|
||||||
|
}[];
|
||||||
|
})[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type QuotationStats = {
|
export type QuotationStats = {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue