feat(05): quotation product form

This commit is contained in:
puriphatt 2024-09-27 16:11:16 +07:00
parent 368e8ec588
commit 57b5392bfe
3 changed files with 423 additions and 11 deletions

BIN
public/images/finding.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

View file

@ -1,11 +1,11 @@
<script setup lang="ts">
import { Icon } from '@iconify/vue';
import { computed } from 'vue';
import { computed, ref } from 'vue';
type Node = {
[key: string]: any;
opened?: boolean;
selected?: boolean;
checked?: boolean;
bg?: string;
fg?: string;
icon?: string;
@ -19,6 +19,8 @@ type Props = {
expandable?: boolean;
hideCheckBox?: boolean;
iconSize?: string;
selectable?: boolean;
selectedNode?: Node[];
decoration?: {
level?: number;
bg?: string;
@ -30,7 +32,10 @@ type Props = {
const props = defineProps<Props>();
const nodes = defineModel<Node[]>('nodes', { required: true });
const emits = defineEmits<{ (e: 'checked'): void }>();
const emits = defineEmits<{
(e: 'checked'): void;
(e: 'select', node: Node): void;
}>();
const dec = props.decoration?.find((v) => v.level === (props.level || 0));
const maxLevel = computed(() =>
@ -42,15 +47,15 @@ const maxLevel = computed(() =>
function recursiveDeselect(node: Node) {
if (node.children) {
node.children.forEach((v) => {
v.selected = false;
v.checked = false;
recursiveDeselect(v);
});
}
}
function toggleCheck(node: Node) {
node.selected = !node.selected;
if (node.selected === false) recursiveDeselect(node);
if (node.selected === true) emits('checked');
node.checked = !node.checked;
if (node.checked === false) recursiveDeselect(node);
if (node.checked === true) emits('checked');
}
function toggleExpand(node: Node) {
@ -84,7 +89,10 @@ function toggleExpand(node: Node) {
<template v-else>
<div
class="item__content row items-center no-wrap"
@click="toggleExpand(node)"
:class="{ active: selectedNode?.includes(node) }"
@click="
() => (selectable ? $emit('select', node) : toggleExpand(node))
"
>
<div
v-if="level !== maxLevel"
@ -95,6 +103,7 @@ function toggleExpand(node: Node) {
name="mdi-chevron-down-circle"
size="sm"
:style="`transform: rotate(${node.opened ? '180deg' : '0'}); transition: transform 0.3s ease;`"
@click.stop="toggleExpand(node)"
/>
</div>
@ -105,7 +114,7 @@ function toggleExpand(node: Node) {
>
<input
type="checkbox"
v-model="node.selected"
v-model="node.checked"
@click="toggleCheck(node)"
/>
</label>
@ -135,22 +144,25 @@ function toggleExpand(node: Node) {
</div>
</div>
</template>
<q-separator v-if="!level" spaced="md"></q-separator>
<q-separator v-if="!level" class="q-mt-sm"></q-separator>
<transition name="slide">
<div v-if="node.opened && node.children && node.children.length > 0">
<TreeView
:iconSize
:hideCheckBox
:selectable
:selectedNode
class="item__children"
v-if="node.children"
v-model:nodes="node.children"
@checked="
() => {
node.selected = true;
node.checked = true;
$emit('checked');
}
"
@select="(v) => $emit('select', v)"
:level="(level || 0) + 1"
:expandable
:decoration
@ -170,6 +182,7 @@ function toggleExpand(node: Node) {
& .tree-item {
& .item__content {
border: solid 1px transparent;
padding: 0.1rem 0.5rem;
&:hover {
@ -198,6 +211,12 @@ function toggleExpand(node: Node) {
font-size: 80%;
color: hsla(var(--text-mute-2));
}
& .active {
border-radius: var(--radius-2);
border: solid 1px hsl(var(--info-bg));
background: hsla(var(--info-bg) / 0.1);
}
}
}

View file

@ -0,0 +1,393 @@
<script lang="ts" setup>
import DialogForm from 'src/components/DialogForm.vue';
import { ref, watch } from 'vue';
import TreeView from 'src/components/shared/TreeView.vue';
type Node = {
[key: string]: any;
opened?: boolean;
checked?: boolean;
bg?: string;
fg?: string;
icon?: string;
children?: Node[];
};
const model = defineModel<boolean>();
const inputSearch = defineModel<string>('inputSearch');
const splitterModel = ref(20);
const subSplitterModel = ref(100);
const currentTab = ref('1');
const currentSelectedType = ref<'group' | 'type' | 'work' | 'product' | ''>('');
const currentSelectedNode = ref<Node[]>([]);
const nodes = ref([
{
title: 'กลุ่มสินค้าและบริการที่ 1',
subtitle: 'TG01000000001',
selected: false,
type: 'group',
children: [
{
title:
'บริการค่าบริการและค่าดำเนินงานยื่นแบบคำร้องขอนำเข้า MOU (Demand)',
subtitle: 'TG01000000001',
selected: false,
type: 'type',
children: [
{
title: 'งาน 1',
subtitle: 'TG01000000001',
selected: false,
type: 'work',
children: [
{
title: 'สินค้า 1',
subtitle: 'TG01000000001',
selected: false,
type: 'product',
},
{
title: 'สินค้า 2',
subtitle: 'TG01000000001',
selected: false,
type: 'product',
},
{
title: 'สินค้า 3',
subtitle: 'TG01000000001',
selected: false,
type: 'product',
},
],
},
],
},
],
},
{
title: 'กลุ่มสินค้าและบริการที่ 2',
subtitle: 'TG01000000001',
selected: false,
type: 'group',
children: [
{
title:
'บริการค่าบริการและค่าดำเนินงานยื่นแบบคำร้องขอนำเข้า MOU (Demand)',
subtitle: 'TG01000000001',
selected: false,
type: 'type',
children: [
{
title: 'งาน 1',
subtitle: 'TG01000000001',
selected: false,
type: 'work',
children: [
{
title: 'สินค้า 1',
subtitle: 'TG01000000001',
selected: false,
type: 'product',
},
{
title: 'สินค้า 2',
subtitle: 'TG01000000001',
selected: false,
type: 'product',
},
{
title: 'สินค้า 3',
subtitle: 'TG01000000001',
selected: false,
type: 'product',
},
],
},
],
},
],
},
]);
function triggerInfo() {
if (subSplitterModel.value === 100) {
subSplitterModel.value = 60;
} else {
subSplitterModel.value = 100;
}
}
function toggleSelected(node: Node) {
if (currentSelectedNode.value.includes(node)) {
currentSelectedNode.value = [];
} else {
currentSelectedNode.value = [];
currentSelectedNode.value.push(node);
}
}
watch(
() => currentSelectedNode.value,
(v) => {
if (v.length > 0) {
currentSelectedType.value = v[0].type;
} else {
currentSelectedType.value = '';
}
},
);
</script>
<template>
<div>
<DialogForm
v-model:modal="model"
:title="$t('general.list', { msg: $t('productService.title') })"
:submit-label="$t('general.select', { msg: $t('productService.title') })"
submit-icon="mdi-check"
>
<q-splitter
v-model="splitterModel"
:limits="[0, 30]"
class="col"
before-class="overflow-hidden"
after-class="overflow-hidden"
>
<!-- SEC: tree component -->
<template v-slot:before>
<section class="column full-height">
<header
class="row no-wrap full-width bordered-b surface-3 items-center q-px-md q-py-sm"
:style="`min-height: ${$q.screen.gt.sm ? '57px' : '100.8px'}`"
>
<span class="col ellipsis-2-lines">
{{ $t('productService.group.title') }}
</span>
</header>
<body class="surface-1 col">
<!-- TODO: tree component -->
waiting for tree
</body>
</section>
</template>
<template v-slot:after>
<section class="column full-height no-wrap">
<header
class="row items-center q-py-sm q-px-md justify-between full-width surface-3 bordered-b"
>
<q-input
for="input-search"
outlined
dense
:label="$t('general.search')"
class="q-mr-md col-12 col-md-4"
:bg-color="$q.dark.isActive ? 'dark' : 'white'"
v-model="inputSearch"
debounce="200"
>
<template v-slot:prepend>
<q-icon name="mdi-magnify" />
</template>
</q-input>
<div>
<q-btn
padding="4px"
color="info"
flat
rounded
icon="mdi-information-outline"
@click="triggerInfo"
/>
</div>
</header>
<body class="col">
<q-splitter
disable
v-model="subSplitterModel"
:limits="[0, 100]"
before-class="overflow-hidden"
after-class="overflow-hidden"
style="height: 100%"
>
<!-- SEC: tree view body -->
<template v-slot:before>
<section class="full-height column">
<span
v-if="false"
class="col column items-center justify-center app-text-muted"
>
<q-avatar color="input-border" class="q-mb-md">
<q-icon name="mdi-shopping" />
</q-avatar>
{{
$t('general.select', {
msg: $t('general.list', {
msg: $t('productService.title'),
}),
})
}}
</span>
<body>
<TreeView
class="full-width q-pt-sm"
v-model:nodes="nodes"
expandable
selectable
:selected-node="currentSelectedNode"
@select="toggleSelected"
:decoration="[
{
level: 0,
icon: 'mdi-folder-outline',
bg: 'hsla(var(--pink-6-hsl)/0.1)',
fg: 'var(--pink-6)',
},
{
level: 1,
icon: 'mdi-server-outline',
bg: 'hsla(var(--orange-5-hsl)/0.1)',
fg: 'var(--orange-5)',
},
{
level: 2,
icon: 'mdi-briefcase-outline',
bg: 'hsla(var(--violet-11-hsl)/0.1)',
fg: 'var(--violet-11)',
},
{
level: 3,
icon: 'mdi-shopping-outline',
bg: 'hsla(var(--teal-10-hsl)/0.1)',
fg: 'var(--teal-10)',
},
]"
/>
</body>
</section>
</template>
<!-- SEC: info -->
<template v-slot:after>
<section class="column no-wrap surface-1 full-height">
<span
v-if="currentSelectedType === ''"
class="col column items-center justify-center app-text-muted"
>
<q-img
:ratio="1"
width="20vw"
src="/images/finding.png"
></q-img>
{{
$t('general.select', {
msg: $t('general.list', {
msg: $t('general.for', {
msg: $t('general.viewDetail'),
}),
}),
})
}}
</span>
<body v-else class="col column no-wrap">
<q-tabs
inline-label
mobile-arrows
dense
v-model="currentTab"
align="left"
class="full-width bordered-b"
active-color="info"
>
<q-tab name="1" @click="currentTab = '1'">
<div
class="row text-capitalize"
:class="
currentTab === 'detail'
? 'text-bold'
: 'app-text-muted'
"
>
{{ $t('general.detail') }}
</div>
</q-tab>
<q-tab name="2" @click="currentTab = '2'">
<div
class="row text-capitalize"
:class="
currentTab === 'remark'
? 'text-bold'
: 'app-text-muted'
"
>
{{
currentSelectedType !== 'work'
? $t('general.remark')
: $t('productService.service.properties')
}}
</div>
</q-tab>
</q-tabs>
<div class="q-pa-md column">
<span v-if="currentTab === '1'">
<span class="app-text-muted">
{{
$t('general.detail', {
msg:
currentSelectedType === 'group'
? $t('productService.group.title')
: currentSelectedType === 'type'
? $t('productService.type.title')
: currentSelectedType === 'work'
? $t('productService.service.title2')
: $t('productService.product.title'),
})
}}
</span>
<aside class="surface-3 q-pa-md">-</aside>
</span>
<span v-if="currentTab === '2'">
<span class="app-text-muted">
{{
currentSelectedType !== 'work'
? $t('general.remark', {
msg:
currentSelectedType === 'group'
? $t('productService.group.title')
: currentSelectedType === 'type'
? $t('productService.type.title')
: $t('productService.product.title'),
})
: `${$t('productService.service.properties')}${$t('productService.service.title2')}`
}}
</span>
<aside
v-if="currentSelectedType !== 'work'"
class="surface-3 q-pa-md"
>
-
</aside>
<aside v-else class="q-pt-md row q-gutter-sm">
<div class="bordered q-py-xs q-px-sm rounded">
นทกบรการ_Name
</div>
</aside>
</span>
</div>
</body>
</section>
</template>
</q-splitter>
</body>
</section>
</template>
</q-splitter>
</DialogForm>
</div>
</template>