fix(04): service tree view

This commit is contained in:
puriphatt 2024-09-25 17:47:01 +07:00
parent 55a6e03bcf
commit 76a08ac368
3 changed files with 159 additions and 14 deletions

View file

@ -5,17 +5,21 @@ import { moveItemUp, moveItemDown, deleteItem, dialog } from 'stores/utils';
import NoData from 'components/NoData.vue'; import NoData from 'components/NoData.vue';
import WorkManagementComponent from './WorkManagementComponent.vue'; import WorkManagementComponent from './WorkManagementComponent.vue';
import AddButton from '../button/AddButton.vue'; import AddButton from '../button/AddButton.vue';
import { WorkItems } from 'stores/product-service/types'; import { ServiceCreate, WorkItems } from 'stores/product-service/types';
import TreeView from '../shared/TreeView.vue';
import { ref } from 'vue';
const { t } = useI18n(); const { t } = useI18n();
const workItems = defineModel<WorkItems[]>('workItems', { default: [] }); const workItems = defineModel<WorkItems[]>('workItems', { default: [] });
defineProps<{ const props = defineProps<{
service?: ServiceCreate;
dense?: boolean; dense?: boolean;
outlined?: boolean; outlined?: boolean;
readonly?: boolean; readonly?: boolean;
separator?: boolean; separator?: boolean;
treeView?: boolean;
priceDisplay?: { priceDisplay?: {
price: boolean; price: boolean;
@ -30,6 +34,22 @@ defineEmits<{
(e: 'workProperties', index: number): void; (e: 'workProperties', index: number): void;
}>(); }>();
const nodes = ref([
{
title: props.service?.name,
subtitle: props.service?.code,
selected: false,
children: workItems.value.map((v) => ({
title: v.name,
subtitle: ' ',
children: v.product.map((x) => ({
title: x.name,
subtitle: x.code,
})),
})),
},
]);
function addWork() { function addWork() {
workItems.value.push({ workItems.value.push({
id: '', id: '',
@ -75,7 +95,7 @@ function confirmDelete(items: unknown[], index: number) {
/> />
</div> </div>
<div v-if="workItems.length > 0" class="q-gutter-y-md row"> <div v-if="workItems.length > 0 && !treeView" class="q-gutter-y-md row">
<WorkManagementComponent <WorkManagementComponent
class="col-12" class="col-12"
v-for="(work, index) in workItems" v-for="(work, index) in workItems"
@ -101,6 +121,39 @@ function confirmDelete(items: unknown[], index: number) {
<div class="col-12" style="height: 12px"></div> <div class="col-12" style="height: 12px"></div>
</div> </div>
<div
v-else-if="workItems.length > 0 && treeView"
class="row rounded bordered surface-2 col q-pa-md"
>
<TreeView
expandable
hideCheckBox
icon-size="2.5rem"
class="full-width"
v-model:nodes="nodes"
: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)',
},
]"
/>
</div>
<div <div
v-else v-else
class="col row rounded bordered surface-2 justify-center items-center" class="col row rounded bordered surface-2 justify-center items-center"

View file

@ -1,5 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import { Icon } from '@iconify/vue'; import { Icon } from '@iconify/vue';
import { computed } from 'vue';
type Node = { type Node = {
[key: string]: any; [key: string]: any;
@ -13,6 +14,8 @@ type Props = {
keyTitle?: string; keyTitle?: string;
keySubtitle?: string; keySubtitle?: string;
expandable?: boolean; expandable?: boolean;
hideCheckBox?: boolean;
iconSize?: string;
decoration?: { decoration?: {
level?: number; level?: number;
bg?: string; bg?: string;
@ -27,6 +30,11 @@ const nodes = defineModel<Node[]>('nodes', { required: true });
const emits = defineEmits<{ (e: 'checked'): void }>(); const emits = defineEmits<{ (e: 'checked'): void }>();
const dec = props.decoration?.find((v) => v.level === (props.level || 0)); const dec = props.decoration?.find((v) => v.level === (props.level || 0));
const maxLevel = computed(() =>
props.decoration?.reduce((max, v) => {
return v.level && v.level > max ? v.level : max;
}, 0),
);
function recursiveDeselect(node: Node) { function recursiveDeselect(node: Node) {
if (node.children) { if (node.children) {
@ -48,8 +56,22 @@ function toggleExpand(node: Node) {
</script> </script>
<template> <template>
<div class="tree-container"> <div
<div v-for="(node, i) in nodes" class="tree-item" :key="i"> class="tree-container"
:class="{
'q-pl-lg': level && level > 0,
'last-children': level && level === maxLevel,
}"
>
<div
v-for="(node, i) in nodes"
class="tree-item"
:class="{
'q-pt-sm': level !== 0 && level !== maxLevel && i !== 0,
'q-pt-xs': level === maxLevel && i === 0,
}"
:key="i"
>
<slot <slot
v-if="$slots['item']" v-if="$slots['item']"
name="item" name="item"
@ -61,7 +83,23 @@ function toggleExpand(node: Node) {
class="item__content row items-center no-wrap" class="item__content row items-center no-wrap"
@click="toggleExpand(node)" @click="toggleExpand(node)"
> >
<label class="flex items-center item__checkbox" @click.stop> <div
v-if="level !== maxLevel"
class="q-mr-md"
style="color: var(--stone-4)"
>
<q-icon
name="mdi-chevron-down-circle"
size="sm"
:style="`transform: rotate(${node.opened ? '180deg' : '0'})`"
/>
</div>
<label
v-if="!hideCheckBox"
class="flex items-center item__checkbox"
@click.stop
>
<input <input
type="checkbox" type="checkbox"
v-model="node.selected" v-model="node.selected"
@ -71,9 +109,17 @@ function toggleExpand(node: Node) {
<div <div
class="item__icon flex items-center justify-center" class="item__icon flex items-center justify-center"
:style="`background: ${dec?.bg}; color: ${dec?.fg}`" :style="`background: ${dec?.bg}; color: ${dec?.fg}; height: ${iconSize}; width: ${iconSize}`"
> >
<Icon v-if="dec && dec.icon" :icon="dec.icon" /> <div
:style="`height: calc(${iconSize} - 40%); width: calc(${iconSize} - 40%)`"
>
<Icon
v-if="dec && dec.icon"
:icon="dec.icon"
class="full-width full-height"
/>
</div>
</div> </div>
<div class="column"> <div class="column">
@ -86,14 +132,13 @@ function toggleExpand(node: Node) {
</div> </div>
</div> </div>
</template> </template>
<q-separator v-if="!level"></q-separator> <q-separator v-if="!level" spaced="md"></q-separator>
<transition name="slide"> <transition name="slide">
<div <div v-if="node.opened && node.children && node.children.length > 0">
class="q-pl-lg q-pt-sm"
v-if="node.opened && node.children && node.children.length > 0"
>
<TreeView <TreeView
:iconSize
:hideCheckBox
class="item__children" class="item__children"
v-if="node.children" v-if="node.children"
v-model:nodes="node.children" v-model:nodes="node.children"
@ -153,6 +198,10 @@ function toggleExpand(node: Node) {
} }
} }
.last-children {
margin-left: 50px;
}
.slide-enter-active { .slide-enter-active {
transition: all 0.1s ease-out; transition: all 0.1s ease-out;
} }

View file

@ -209,6 +209,8 @@ const dialogService = ref(false);
const dialogProductEdit = ref(false); const dialogProductEdit = ref(false);
const dialogServiceEdit = ref(false); const dialogServiceEdit = ref(false);
const serviceTreeView = ref(false);
const statusToggle = ref(false); const statusToggle = ref(false);
const profileSubmit = ref(false); const profileSubmit = ref(false);
const infoProductEdit = ref(false); const infoProductEdit = ref(false);
@ -4019,10 +4021,49 @@ watch(
id="group-form" id="group-form"
> >
<div <div
class="surface-1 rounded q-my-md q-mx-lg row" class="surface-1 rounded q-my-md q-mx-lg row items-center"
style="position: absolute; z-index: 999; top: 0; right: 0" style="position: absolute; z-index: 999; top: 0; right: 0"
v-if="actionDisplay && !currentNoAction" v-if="actionDisplay && !currentNoAction"
> >
<div class="bordered rounded q-mr-md" v-if="serviceTab === 2">
<q-btn
icon="mdi-file-tree-outline"
flat
square
:class="{
' surface-3': serviceTreeView,
'app-text-muted-2': serviceTreeView,
'app-text-muted': !serviceTreeView,
}"
size="sm"
padding="6px 10px"
title="Tree"
style="
border-top-left-radius: var(--radius-2);
border-bottom-left-radius: var(--radius-2);
"
@click="serviceTreeView = true"
/>
<q-btn
icon="mdi-view-list-outline"
flat
square
:class="{
' surface-3': !serviceTreeView,
'app-text-muted-2': !serviceTreeView,
'app-text-muted': serviceTreeView,
}"
size="sm"
padding="6px 10px"
title="List"
style="
border-top-right-radius: var(--radius-2);
border-bottom-right-radius: var(--radius-2);
"
@click="serviceTreeView = false"
/>
</div>
<UndoButton <UndoButton
v-if="infoServiceEdit" v-if="infoServiceEdit"
id="btn-info-basic-undo" id="btn-info-basic-undo"
@ -4104,6 +4145,8 @@ watch(
<FormServiceWork <FormServiceWork
v-if="serviceTab === 2" v-if="serviceTab === 2"
:service="formDataProductService"
:tree-view="serviceTreeView"
:readonly="!infoServiceEdit" :readonly="!infoServiceEdit"
v-model:work-items="workItems" v-model:work-items="workItems"
dense dense