feat(05): quotation product form
This commit is contained in:
parent
368e8ec588
commit
57b5392bfe
3 changed files with 423 additions and 11 deletions
BIN
public/images/finding.png
Normal file
BIN
public/images/finding.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 49 KiB |
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
393
src/pages/05_quotation/ProductServiceForm.vue
Normal file
393
src/pages/05_quotation/ProductServiceForm.vue
Normal 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>
|
||||
Loading…
Add table
Add a link
Reference in a new issue