feat: reverse tree convert

This commit is contained in:
Methapon Metanipat 2024-10-08 17:32:28 +07:00
parent d5c8ce85b6
commit 13cf2da9f9
3 changed files with 147 additions and 33 deletions

View file

@ -19,6 +19,7 @@ import { CustomerBranchCreate } from 'stores/customer/types';
import { Employee } from 'src/stores/employee/types';
// NOTE: Import Components
import TreeView from 'src/components/shared/TreeView.vue';
import QuotationCard from 'src/components/05_quotation/QuotationCard.vue';
import PaginationComponent from 'src/components/PaginationComponent.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 SelectZone from 'src/components/shared/SelectZone.vue';
import PersonCard from 'src/components/shared/PersonCard.vue';
import ProductServiceForm from './ProductServiceForm.vue';
import CreateButton from 'src/components/AddButton.vue';
import ItemCard from 'src/components/ItemCard.vue';
import DialogForm from 'components/DialogForm.vue';
@ -34,11 +34,6 @@ import { AddButton } from 'src/components/button';
import SideMenu from 'components/SideMenu.vue';
import { dialogCreateCustomerItem } from 'src/pages/03_customer-management/constant';
import {
ProductGroup,
Product,
Service,
} from 'src/stores/product-service/types';
import { SaveButton } from 'components/button';
@ -54,6 +49,7 @@ import {
useEmployeeForm,
} from 'src/pages/03_customer-management/form';
import QuotationView from './QuotationView.vue';
import { quotationProductTree } from './utils';
const quotationFormStore = useQuotationForm();
const customerFormStore = useCustomerForm();
@ -633,6 +629,38 @@ watch(() => pageState.currentTab, fetchQuotationList);
/>
</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>
<!-- SEC: footer content -->
@ -1056,33 +1084,6 @@ watch(() => pageState.currentTab, fetchQuotationList);
</div>
</div>
</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>
<style scoped></style>

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

View file

@ -149,6 +149,13 @@ type ServiceRelation = {
createdByUserId: string;
updatedAt: string;
updatedByUserId: string;
work: (WorkRelation & {
productOnWork: {
order: number;
product: ProductRelation;
}[];
})[];
};
export type QuotationStats = {