feat: workflow template properties (#70)
* feat: add i18n * refactor/feat: workflow attributes type * refactor: workflow => gray stat card * refactor: select menu with search => separator * feat: workflow => workflow step properties * fix: workflow type * fix: dialog properties component => model data * fix: form flow => prevent toggle expansion with keyboard * refactor: workflow step data & change status * fix: form flow properties btn * refactor: side menu => hide sub index * feat: workflow => avatar & status on table * refactor: workflow => drawer id and dialog id * feat: workflow => card * fix: agencies => format address
This commit is contained in:
parent
8a2a010776
commit
42e2f2b21d
12 changed files with 1257 additions and 225 deletions
|
|
@ -1,7 +1,8 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { onMounted, ref, watch } from 'vue';
|
import { onMounted, ref, watch } from 'vue';
|
||||||
import { QTableProps } from 'quasar';
|
import { Icon } from '@iconify/vue/dist/iconify.js';
|
||||||
import { moveItemUp, moveItemDown, deleteItem } from 'src/stores/utils';
|
import { moveItemUp, moveItemDown, deleteItem } from 'src/stores/utils';
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
|
|
||||||
import useUserStore from 'src/stores/user';
|
import useUserStore from 'src/stores/user';
|
||||||
import useOptionStore from 'src/stores/options';
|
import useOptionStore from 'src/stores/options';
|
||||||
|
|
@ -10,6 +11,7 @@ import { getRole } from 'src/services/keycloak';
|
||||||
import {
|
import {
|
||||||
WorkflowUserInTable,
|
WorkflowUserInTable,
|
||||||
WorkflowTemplatePayload,
|
WorkflowTemplatePayload,
|
||||||
|
WorkFlowPayloadStep,
|
||||||
} from 'src/stores/workflow-template/types';
|
} from 'src/stores/workflow-template/types';
|
||||||
import { User } from 'src/stores/user/types';
|
import { User } from 'src/stores/user/types';
|
||||||
|
|
||||||
|
|
@ -23,6 +25,7 @@ defineProps<{
|
||||||
onDrawer?: boolean;
|
onDrawer?: boolean;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
const optionStore = useOptionStore();
|
const optionStore = useOptionStore();
|
||||||
const modelByArea = ref<boolean>(false);
|
const modelByArea = ref<boolean>(false);
|
||||||
|
|
@ -40,36 +43,15 @@ const flowData = defineModel<WorkflowTemplatePayload>('flowData', {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const options = ref(optionStore.globalOption?.agenciesType);
|
const objectOptions = [
|
||||||
|
...(optionStore.globalOption?.agenciesType || []),
|
||||||
|
{ label: t('flow.customer'), value: 'customer' },
|
||||||
|
{ label: t('flow.officer'), value: 'officer' },
|
||||||
|
];
|
||||||
|
const options = ref(objectOptions);
|
||||||
const role = ref<string[]>([]);
|
const role = ref<string[]>([]);
|
||||||
const userList = ref<User[]>([]);
|
const userList = ref<User[]>([]);
|
||||||
const responsiblePersonSearch = ref('');
|
const responsiblePersonSearch = ref('');
|
||||||
const columns = [
|
|
||||||
{
|
|
||||||
name: 'detail',
|
|
||||||
align: 'center',
|
|
||||||
label: 'general.detail',
|
|
||||||
field: 'detail',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'responsiblePerson',
|
|
||||||
align: 'center',
|
|
||||||
label: 'flow.responsiblePerson',
|
|
||||||
field: 'responsiblePerson',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'responsiblePerson',
|
|
||||||
align: 'center',
|
|
||||||
label: 'general.agencies',
|
|
||||||
field: 'responsiblePerson',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'action',
|
|
||||||
align: 'right',
|
|
||||||
label: '',
|
|
||||||
field: 'action',
|
|
||||||
},
|
|
||||||
] satisfies QTableProps['columns'];
|
|
||||||
|
|
||||||
async function getUserList(opts?: { query: string }) {
|
async function getUserList(opts?: { query: string }) {
|
||||||
const resUser = await userStore.fetchList({
|
const resUser = await userStore.fetchList({
|
||||||
|
|
@ -134,12 +116,12 @@ function selectItem(
|
||||||
|
|
||||||
function optionSearch(val: string | null) {
|
function optionSearch(val: string | null) {
|
||||||
if (val === '') {
|
if (val === '') {
|
||||||
options.value = optionStore.globalOption?.agenciesType;
|
options.value = objectOptions;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const needle = val ? val.toLowerCase() : '';
|
const needle = val ? val.toLowerCase() : '';
|
||||||
options.value = optionStore.globalOption?.agenciesType.filter(
|
options.value = objectOptions.filter(
|
||||||
(v: { label: string }) => v.label.toLowerCase().indexOf(needle) > -1,
|
(v: { label: string }) => v.label.toLowerCase().indexOf(needle) > -1,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -148,6 +130,7 @@ defineEmits<{
|
||||||
(e: 'moveUp'): void;
|
(e: 'moveUp'): void;
|
||||||
(e: 'moveDown'): void;
|
(e: 'moveDown'): void;
|
||||||
(e: 'changeStatus'): void;
|
(e: 'changeStatus'): void;
|
||||||
|
(e: 'triggerProperties', step: WorkFlowPayloadStep): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
|
|
@ -165,6 +148,7 @@ onMounted(async () => {
|
||||||
<template>
|
<template>
|
||||||
<div class="row col-12">
|
<div class="row col-12">
|
||||||
<section
|
<section
|
||||||
|
:id="`form-flow-template-${onDrawer ? 'drawer' : 'dialog'}`"
|
||||||
class="col-12 q-pb-sm text-weight-bold text-body1 row items-center"
|
class="col-12 q-pb-sm text-weight-bold text-body1 row items-center"
|
||||||
>
|
>
|
||||||
<q-icon
|
<q-icon
|
||||||
|
|
@ -172,7 +156,7 @@ onMounted(async () => {
|
||||||
size="xs"
|
size="xs"
|
||||||
class="q-pa-sm rounded q-mr-xs"
|
class="q-pa-sm rounded q-mr-xs"
|
||||||
color="info"
|
color="info"
|
||||||
name="mdi-office-building-outline"
|
name="mdi-cogs"
|
||||||
style="background-color: var(--surface-3)"
|
style="background-color: var(--surface-3)"
|
||||||
/>
|
/>
|
||||||
{{ $t(`general.name`, { msg: $t('flow.title') }) }}
|
{{ $t(`general.name`, { msg: $t('flow.title') }) }}
|
||||||
|
|
@ -195,7 +179,7 @@ onMounted(async () => {
|
||||||
</span>
|
</span>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section id="form-flow-template" class="col-12 row q-col-gutter-sm">
|
<section class="col-12 row q-col-gutter-sm">
|
||||||
<SelectInput
|
<SelectInput
|
||||||
v-if="role.includes('system')"
|
v-if="role.includes('system')"
|
||||||
:readonly
|
:readonly
|
||||||
|
|
@ -222,15 +206,17 @@ onMounted(async () => {
|
||||||
/>
|
/>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<!-- SEC: Step -->
|
||||||
<section
|
<section
|
||||||
class="col-12 q-pb-sm q-pt-lg text-weight-bold text-body1 row items-center"
|
:id="`form-flow-step-${onDrawer ? 'drawer' : 'dialog'}`"
|
||||||
|
class="col-12 q-pb-sm q-pt-xl text-weight-bold text-body1 row items-center"
|
||||||
>
|
>
|
||||||
<q-icon
|
<q-icon
|
||||||
flat
|
flat
|
||||||
size="xs"
|
size="xs"
|
||||||
class="q-pa-sm rounded q-mr-xs"
|
class="q-pa-sm rounded q-mr-xs"
|
||||||
color="info"
|
color="info"
|
||||||
name="mdi-office-building-outline"
|
name="mdi-note-edit-outline"
|
||||||
style="background-color: var(--surface-3)"
|
style="background-color: var(--surface-3)"
|
||||||
/>
|
/>
|
||||||
{{ $t(`flow.processStep`) }}
|
{{ $t(`flow.processStep`) }}
|
||||||
|
|
@ -238,7 +224,7 @@ onMounted(async () => {
|
||||||
|
|
||||||
<section
|
<section
|
||||||
v-if="flowData.step.length === 0"
|
v-if="flowData.step.length === 0"
|
||||||
class="col-12 surface-2 rounded bordered column items-center justify-center q-pa-md"
|
class="col-12 surface-2 rounded bordered column items-center justify-center q-pa-xl"
|
||||||
>
|
>
|
||||||
<NoData class="col" />
|
<NoData class="col" />
|
||||||
</section>
|
</section>
|
||||||
|
|
@ -257,8 +243,8 @@ onMounted(async () => {
|
||||||
header-style="border-top-left-radius: var(--radius-2); border-top-right-radius: var(--radius-2)"
|
header-style="border-top-left-radius: var(--radius-2); border-top-right-radius: var(--radius-2)"
|
||||||
>
|
>
|
||||||
<template v-slot:header>
|
<template v-slot:header>
|
||||||
<div class="column full-width">
|
<div class="column full-width" @keyup.stop @click.stop>
|
||||||
<div class="row items-center q-py-sm full-width" @click.stop>
|
<div class="row items-center q-py-sm full-width">
|
||||||
<q-btn
|
<q-btn
|
||||||
v-if="!readonly"
|
v-if="!readonly"
|
||||||
id="btn-work-up-product"
|
id="btn-work-up-product"
|
||||||
|
|
@ -296,8 +282,8 @@ onMounted(async () => {
|
||||||
dense
|
dense
|
||||||
outlined
|
outlined
|
||||||
:readonly
|
:readonly
|
||||||
:id="`input-flow-step-name-${index}`"
|
:id="`input-flow-step-name-${index}-${onDrawer ? 'drawer' : 'dialog'}`"
|
||||||
:for="`input-flow-step-name-${index}`"
|
:for="`input-flow-step-name-${index}-${onDrawer ? 'drawer' : 'dialog'}`"
|
||||||
class="col q-ml-md"
|
class="col q-ml-md"
|
||||||
:placeholder="$t('general.no', { msg: $t('flow.step') })"
|
:placeholder="$t('general.no', { msg: $t('flow.step') })"
|
||||||
v-model="step.name"
|
v-model="step.name"
|
||||||
|
|
@ -403,105 +389,151 @@ onMounted(async () => {
|
||||||
:class="{ 'surface-1 rounded bordered': readonly }"
|
:class="{ 'surface-1 rounded bordered': readonly }"
|
||||||
style="border: 1px solid transparent"
|
style="border: 1px solid transparent"
|
||||||
>
|
>
|
||||||
<div class="q-col-gutter-sm row">
|
<div class="row q-col-gutter-sm">
|
||||||
<q-input
|
<q-input
|
||||||
:bg-color="readonly ? 'transparent' : ''"
|
:bg-color="readonly ? 'transparent' : ''"
|
||||||
:readonly
|
:readonly
|
||||||
v-model="step.detail"
|
v-model="step.detail"
|
||||||
class="col-12"
|
class="col-6"
|
||||||
type="textarea"
|
type="textarea"
|
||||||
dense
|
dense
|
||||||
outlined
|
outlined
|
||||||
:label="$t('general.detail')"
|
:label="$t('general.detail')"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<q-field
|
<div class="col-6">
|
||||||
:bg-color="readonly ? 'transparent' : ''"
|
<div
|
||||||
v-if="step.responsiblePersonId"
|
class="surface-1 rounded bordered full-height"
|
||||||
stack-label
|
style="padding-inline: 12px"
|
||||||
:label="
|
:style="readonly ? 'border: 1px solid transparent;' : ''"
|
||||||
step.responsiblePersonId.length > 0
|
|
||||||
? $t('flow.responsiblePerson')
|
|
||||||
: undefined
|
|
||||||
"
|
|
||||||
class="col-6"
|
|
||||||
@click.stop
|
|
||||||
dense
|
|
||||||
outlined
|
|
||||||
:readonly
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
v-if="step.responsiblePersonId.length === 0"
|
|
||||||
class="app-text-muted row items-center col"
|
|
||||||
>
|
>
|
||||||
{{
|
<section
|
||||||
$t('general.no', { msg: $t('flow.responsiblePerson') })
|
class="row items-center q-pt-xs justify-between relative-position"
|
||||||
}}
|
|
||||||
<q-icon
|
|
||||||
v-if="!readonly"
|
|
||||||
name="mdi-menu-down"
|
|
||||||
size="sm"
|
|
||||||
class="q-ml-auto"
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
<div v-else>
|
|
||||||
<div
|
|
||||||
class="row items-center no-wrap"
|
|
||||||
v-for="person in userInTable[index]?.responsiblePerson"
|
|
||||||
:key="person.id"
|
|
||||||
>
|
>
|
||||||
<q-avatar class="q-ml-sm" size="md">
|
|
||||||
<q-img
|
|
||||||
class="text-center"
|
|
||||||
:ratio="1"
|
|
||||||
:src="`${baseUrl}/user/${person.id}/profile-image/${person.selectedImage}`"
|
|
||||||
>
|
|
||||||
<template #error>
|
|
||||||
<div
|
|
||||||
class="no-padding full-width full-height flex items-center justify-center"
|
|
||||||
:style="`${person.gender ? 'background: white' : 'background: linear-gradient(135deg,rgba(43, 137, 223, 1) 0%, rgba(230, 51, 81, 1) 100%);'}`"
|
|
||||||
>
|
|
||||||
<q-img
|
|
||||||
v-if="person.gender"
|
|
||||||
:src="
|
|
||||||
person.gender === 'male'
|
|
||||||
? '/no-img-man.png'
|
|
||||||
: '/no-img-female.png'
|
|
||||||
"
|
|
||||||
/>
|
|
||||||
<q-icon
|
|
||||||
v-else
|
|
||||||
size="sm"
|
|
||||||
name="mdi-account-outline"
|
|
||||||
style="color: white"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</q-img>
|
|
||||||
</q-avatar>
|
|
||||||
<div
|
<div
|
||||||
class="column q-pl-md"
|
class="app-text-muted-2"
|
||||||
style="color: var(--foreground)"
|
:style="
|
||||||
|
step.attributes?.properties &&
|
||||||
|
step.attributes?.properties.length > 0
|
||||||
|
? 'font-size: 10px'
|
||||||
|
: ''
|
||||||
|
"
|
||||||
>
|
>
|
||||||
<span>
|
{{ $t('general.properties') }}
|
||||||
{{
|
</div>
|
||||||
`${optionStore.mapOption(person.namePrefix || '')} ${
|
<q-btn
|
||||||
$i18n.locale === 'eng'
|
v-if="!readonly"
|
||||||
? person.firstNameEN
|
id="btn-add-work-product"
|
||||||
: person.firstName
|
class="text-capitalize rounded absolute-top-right"
|
||||||
} ${
|
flat
|
||||||
$i18n.locale === 'eng'
|
dense
|
||||||
? person.lastNameEN
|
padding="4px 8px"
|
||||||
: person.lastName
|
style="color: hsl(var(--info-bg)); top: 4px"
|
||||||
}`
|
@click.stop="$emit('triggerProperties', step)"
|
||||||
}}
|
>
|
||||||
</span>
|
<Icon
|
||||||
<span class="text-caption app-text-muted">
|
icon="basil:settings-adjust-solid"
|
||||||
{{ person.code }}
|
width="20.08px"
|
||||||
</span>
|
style="color: hsl(var(--info-bg))"
|
||||||
|
/>
|
||||||
|
</q-btn>
|
||||||
|
</section>
|
||||||
|
<section class="row q-gutter-sm q-pb-sm scroll">
|
||||||
|
<span
|
||||||
|
v-for="(att, i) in step.attributes?.properties"
|
||||||
|
:key="i"
|
||||||
|
class="surface-2 bordered rounded q-px-xs"
|
||||||
|
>
|
||||||
|
{{ optionStore.mapOption(att.fieldName ?? '') }}
|
||||||
|
</span>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- RESPONSIBLE-PERSON -->
|
||||||
|
<q-select
|
||||||
|
v-if="step.responsiblePersonId"
|
||||||
|
:bg-color="readonly ? 'transparent' : ''"
|
||||||
|
:readonly
|
||||||
|
outlined
|
||||||
|
dense
|
||||||
|
v-model="step.responsiblePersonId"
|
||||||
|
multiple
|
||||||
|
:options="[1, 2, 3]"
|
||||||
|
hide-bottom-space
|
||||||
|
option-label="label"
|
||||||
|
option-value="value"
|
||||||
|
emit-value
|
||||||
|
:label="$t('flow.responsiblePerson')"
|
||||||
|
class="col-6"
|
||||||
|
:hide-dropdown-icon="readonly"
|
||||||
|
>
|
||||||
|
<template v-slot:selected-item="scope">
|
||||||
|
<div class="column full-width">
|
||||||
|
<div
|
||||||
|
class="row items-center no-wrap"
|
||||||
|
v-for="person in userInTable[
|
||||||
|
index
|
||||||
|
]?.responsiblePerson.filter(
|
||||||
|
(p) => p.id === scope.opt,
|
||||||
|
)"
|
||||||
|
:key="person.id"
|
||||||
|
>
|
||||||
|
<q-avatar class="q-ml-sm" size="md">
|
||||||
|
<q-img
|
||||||
|
class="text-center"
|
||||||
|
:ratio="1"
|
||||||
|
:src="`${baseUrl}/user/${person.id}/profile-image/${person.selectedImage}`"
|
||||||
|
>
|
||||||
|
<template #error>
|
||||||
|
<div
|
||||||
|
class="no-padding full-width full-height flex items-center justify-center"
|
||||||
|
:style="`${person.gender ? 'background: white' : 'background: linear-gradient(135deg,rgba(43, 137, 223, 1) 0%, rgba(230, 51, 81, 1) 100%);'}`"
|
||||||
|
>
|
||||||
|
<q-img
|
||||||
|
v-if="person.gender"
|
||||||
|
:src="
|
||||||
|
person.gender === 'male'
|
||||||
|
? '/no-img-man.png'
|
||||||
|
: '/no-img-female.png'
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
<q-icon
|
||||||
|
v-else
|
||||||
|
size="sm"
|
||||||
|
name="mdi-account-outline"
|
||||||
|
style="color: white"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</q-img>
|
||||||
|
</q-avatar>
|
||||||
|
<div
|
||||||
|
class="column q-pl-md"
|
||||||
|
style="color: var(--foreground)"
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
{{
|
||||||
|
`${optionStore.mapOption(person.namePrefix || '')} ${
|
||||||
|
$i18n.locale === 'eng'
|
||||||
|
? person.firstNameEN
|
||||||
|
: person.firstName
|
||||||
|
} ${
|
||||||
|
$i18n.locale === 'eng'
|
||||||
|
? person.lastNameEN
|
||||||
|
: person.lastName
|
||||||
|
}`
|
||||||
|
}}
|
||||||
|
</span>
|
||||||
|
<span class="text-caption app-text-muted">
|
||||||
|
{{ person.code }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</template>
|
||||||
|
|
||||||
|
<template v-slot:option></template>
|
||||||
<q-menu v-if="!readonly" :offset="[0, 4]">
|
<q-menu v-if="!readonly" :offset="[0, 4]">
|
||||||
<q-list>
|
<q-list>
|
||||||
<q-item>
|
<q-item>
|
||||||
|
|
@ -524,6 +556,12 @@ onMounted(async () => {
|
||||||
{{ $t('general.people') }}
|
{{ $t('general.people') }}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
|
<q-item
|
||||||
|
v-if="userList.length === 0"
|
||||||
|
class="app-text-muted q-px-lg"
|
||||||
|
>
|
||||||
|
{{ $t('general.noData') }}
|
||||||
|
</q-item>
|
||||||
<q-item
|
<q-item
|
||||||
v-for="(person, i) in userList"
|
v-for="(person, i) in userList"
|
||||||
dense
|
dense
|
||||||
|
|
@ -630,8 +668,9 @@ onMounted(async () => {
|
||||||
</q-item>
|
</q-item>
|
||||||
</q-list>
|
</q-list>
|
||||||
</q-menu>
|
</q-menu>
|
||||||
</q-field>
|
</q-select>
|
||||||
|
|
||||||
|
<!-- RESPONSIBLE-AGENCIES, RESPONSIBLE-INSTITUTION -->
|
||||||
<q-select
|
<q-select
|
||||||
:bg-color="readonly ? 'transparent' : ''"
|
:bg-color="readonly ? 'transparent' : ''"
|
||||||
:readonly
|
:readonly
|
||||||
|
|
@ -640,6 +679,7 @@ onMounted(async () => {
|
||||||
v-model="step.responsibleInstitution"
|
v-model="step.responsibleInstitution"
|
||||||
multiple
|
multiple
|
||||||
:options="options"
|
:options="options"
|
||||||
|
hide-bottom-space
|
||||||
option-label="label"
|
option-label="label"
|
||||||
option-value="value"
|
option-value="value"
|
||||||
emit-value
|
emit-value
|
||||||
|
|
@ -656,7 +696,16 @@ onMounted(async () => {
|
||||||
@remove="scope.removeAtIndex(scope.index)"
|
@remove="scope.removeAtIndex(scope.index)"
|
||||||
>
|
>
|
||||||
<span class="text-caption">
|
<span class="text-caption">
|
||||||
{{ optionStore.mapOption(scope.opt, 'agenciesType') }}
|
{{
|
||||||
|
scope.opt === 'customer'
|
||||||
|
? $t('flow.customer')
|
||||||
|
: scope.opt === 'officer'
|
||||||
|
? $t('flow.officer')
|
||||||
|
: optionStore.mapOption(
|
||||||
|
scope.opt,
|
||||||
|
'agenciesType',
|
||||||
|
)
|
||||||
|
}}
|
||||||
</span>
|
</span>
|
||||||
</q-chip>
|
</q-chip>
|
||||||
</template>
|
</template>
|
||||||
|
|
@ -668,13 +717,21 @@ onMounted(async () => {
|
||||||
$t('general.select', { msg: $t('general.agencies') })
|
$t('general.select', { msg: $t('general.agencies') })
|
||||||
"
|
"
|
||||||
:option="options"
|
:option="options"
|
||||||
|
:separator-index="[9]"
|
||||||
width="353.66px"
|
width="353.66px"
|
||||||
@search="(v) => optionSearch(v as string)"
|
@search="(v) => optionSearch(v as string)"
|
||||||
@select="
|
@select="
|
||||||
(v) => selectItem(v, step.responsibleInstitution)
|
(v) => selectItem(v, step.responsibleInstitution)
|
||||||
"
|
"
|
||||||
@before-show="
|
@before-show="
|
||||||
options = optionStore.globalOption?.agenciesType
|
() => {
|
||||||
|
objectOptions = [
|
||||||
|
...(optionStore.globalOption?.agenciesType || []),
|
||||||
|
{ label: t('flow.customer'), value: 'customer' },
|
||||||
|
{ label: t('flow.officer'), value: 'officer' },
|
||||||
|
];
|
||||||
|
options = objectOptions;
|
||||||
|
}
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
<template #prepend>
|
<template #prepend>
|
||||||
|
|
@ -722,6 +779,15 @@ onMounted(async () => {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:deep(
|
||||||
|
.q-item__section.column.q-item__section--side.justify-center.q-item__section--avatar.q-focusable.relative-position.cursor-pointer
|
||||||
|
) {
|
||||||
|
justify-content: start !important;
|
||||||
|
padding-right: 8px !important;
|
||||||
|
padding-top: 16px;
|
||||||
|
min-width: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
:deep(i.q-icon.mdi.mdi-chevron-down-circle.q-expansion-item__toggle-icon) {
|
:deep(i.q-icon.mdi.mdi-chevron-down-circle.q-expansion-item__toggle-icon) {
|
||||||
color: hsl(var(--text-mute));
|
color: hsl(var(--text-mute));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ type Menu = {
|
||||||
anchor: string;
|
anchor: string;
|
||||||
name: string;
|
name: string;
|
||||||
sub?: boolean;
|
sub?: boolean;
|
||||||
|
hideSubIndex?: boolean;
|
||||||
tab?: string;
|
tab?: string;
|
||||||
useBtn?: boolean;
|
useBtn?: boolean;
|
||||||
};
|
};
|
||||||
|
|
@ -98,10 +99,10 @@ onUnmounted(() => {
|
||||||
class="row no-wrap items-center"
|
class="row no-wrap items-center"
|
||||||
:class="{ 'app-text-muted': v.sub && activeMenu !== v.anchor }"
|
:class="{ 'app-text-muted': v.sub && activeMenu !== v.anchor }"
|
||||||
>
|
>
|
||||||
<div v-if="v.sub" class="circle-2"></div>
|
<div v-if="v.sub" class="circle-2 q-mr-md"></div>
|
||||||
<div
|
<div
|
||||||
v-if="v.sub"
|
v-if="v.sub && !v.hideSubIndex"
|
||||||
class="surface-tab circle flex justify-center q-mx-md"
|
class="surface-tab circle flex justify-center q-mr-md"
|
||||||
>
|
>
|
||||||
{{ menu.filter((v) => v.sub === true).indexOf(v) + 1 }}
|
{{ menu.filter((v) => v.sub === true).indexOf(v) + 1 }}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,8 @@ const props = withDefaults(
|
||||||
| 'blue'
|
| 'blue'
|
||||||
| 'lime'
|
| 'lime'
|
||||||
| 'light-purple'
|
| 'light-purple'
|
||||||
| 'light-green';
|
| 'light-green'
|
||||||
|
| 'gray';
|
||||||
}[];
|
}[];
|
||||||
dark?: boolean;
|
dark?: boolean;
|
||||||
textSize?: string;
|
textSize?: string;
|
||||||
|
|
@ -143,6 +144,10 @@ const props = withDefaults(
|
||||||
--_color: var(--blue-6-hsl);
|
--_color: var(--blue-6-hsl);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.stat-card__gray {
|
||||||
|
--_color: var(--gray-6-hsl);
|
||||||
|
}
|
||||||
|
|
||||||
.dark .stat-card__purple {
|
.dark .stat-card__purple {
|
||||||
--_color: var(--violet-10-hsl);
|
--_color: var(--violet-10-hsl);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
688
src/components/dialog/DialogProperties.vue
Normal file
688
src/components/dialog/DialogProperties.vue
Normal file
|
|
@ -0,0 +1,688 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, watch } from 'vue';
|
||||||
|
import { moveItemUp, moveItemDown, dialog, deleteItem } from 'stores/utils';
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
|
|
||||||
|
import useOptionStore from 'src/stores/options';
|
||||||
|
import { Option } from 'stores/options/types';
|
||||||
|
|
||||||
|
import NoData from '../NoData.vue';
|
||||||
|
import DialogForm from '../DialogForm.vue';
|
||||||
|
import { WorkFlowPayloadStep } from 'src/stores/workflow-template/types';
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
const optionStore = useOptionStore();
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
stepIndex?: number;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const model = defineModel<boolean>({ required: true, default: false });
|
||||||
|
const dataStep = defineModel<WorkFlowPayloadStep[]>('dataStep', {
|
||||||
|
required: true,
|
||||||
|
default: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
const tempStep = ref<WorkFlowPayloadStep[]>([]);
|
||||||
|
const propertiesOption = ref();
|
||||||
|
const typeOption = [
|
||||||
|
{
|
||||||
|
label: 'Text',
|
||||||
|
value: 'string',
|
||||||
|
color: 'var(--pink-6-hsl)',
|
||||||
|
icon: 'mdi-alpha-t',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Number',
|
||||||
|
value: 'number',
|
||||||
|
color: 'var(--purple-11-hsl)',
|
||||||
|
icon: 'mdi-numeric',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Date',
|
||||||
|
value: 'date',
|
||||||
|
color: 'var(--green-9-hsl)',
|
||||||
|
icon: 'mdi-calendar-blank-outline',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Selection',
|
||||||
|
value: 'array',
|
||||||
|
color: 'var(--indigo-7-hsl)',
|
||||||
|
icon: 'mdi-code-array',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
function submit() {
|
||||||
|
dataStep.value = JSON.parse(JSON.stringify(tempStep.value));
|
||||||
|
|
||||||
|
model.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function close() {
|
||||||
|
tempStep.value = [];
|
||||||
|
model.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function manageProperties(
|
||||||
|
stepIndex: number,
|
||||||
|
property: string,
|
||||||
|
propertyType?: 'date' | 'array' | 'string' | 'number',
|
||||||
|
) {
|
||||||
|
if (property === 'all' && propertiesOption.value) {
|
||||||
|
if (
|
||||||
|
tempStep.value[stepIndex].attributes.properties.length ===
|
||||||
|
propertiesOption.value.length
|
||||||
|
) {
|
||||||
|
tempStep.value[stepIndex].attributes.properties = [];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const ops of propertiesOption.value) {
|
||||||
|
if (
|
||||||
|
tempStep.value[stepIndex].attributes.properties.some(
|
||||||
|
(prop) => prop.fieldName === ops.value,
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ops.type === 'date') {
|
||||||
|
tempStep.value[stepIndex].attributes.properties.push({
|
||||||
|
type: ops.type,
|
||||||
|
fieldName: ops.value,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ops.type === 'array') {
|
||||||
|
tempStep.value[stepIndex].attributes.properties.push({
|
||||||
|
type: ops.type,
|
||||||
|
fieldName: ops.value,
|
||||||
|
options: [],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ops.type === 'string') {
|
||||||
|
tempStep.value[stepIndex].attributes.properties.push({
|
||||||
|
type: ops.type,
|
||||||
|
fieldName: ops.value,
|
||||||
|
isPhoneNumber: false,
|
||||||
|
phoneNumberLength: 10,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ops.type === 'number') {
|
||||||
|
tempStep.value[stepIndex].attributes.properties.push({
|
||||||
|
type: ops.type,
|
||||||
|
fieldName: ops.value,
|
||||||
|
comma: false,
|
||||||
|
decimal: false,
|
||||||
|
decimalPlace: 2,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tempStep.value[stepIndex].attributes.properties) {
|
||||||
|
const propStep = tempStep.value[stepIndex].attributes.properties.findIndex(
|
||||||
|
(prop) => prop.fieldName === property,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (propStep !== -1) {
|
||||||
|
tempStep.value[stepIndex].attributes.properties.splice(propStep, 1);
|
||||||
|
} else {
|
||||||
|
if (propertyType === 'date') {
|
||||||
|
tempStep.value[stepIndex].attributes.properties.push({
|
||||||
|
type: propertyType,
|
||||||
|
fieldName: property,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (propertyType === 'array') {
|
||||||
|
tempStep.value[stepIndex].attributes.properties.push({
|
||||||
|
type: propertyType,
|
||||||
|
fieldName: property,
|
||||||
|
options: [],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (propertyType === 'string') {
|
||||||
|
tempStep.value[stepIndex].attributes.properties.push({
|
||||||
|
type: propertyType,
|
||||||
|
fieldName: property,
|
||||||
|
isPhoneNumber: false,
|
||||||
|
phoneNumberLength: 10,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (propertyType === 'number') {
|
||||||
|
tempStep.value[stepIndex].attributes.properties.push({
|
||||||
|
type: propertyType,
|
||||||
|
fieldName: property,
|
||||||
|
comma: false,
|
||||||
|
decimal: false,
|
||||||
|
decimalPlace: 2,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function changeType(fieldName: string, stepIndex: number) {
|
||||||
|
if (!propertiesOption.value) return;
|
||||||
|
|
||||||
|
const defaultPropType = propertiesOption.value.find(
|
||||||
|
(op: { value: string }) => op.value === fieldName,
|
||||||
|
)?.type;
|
||||||
|
|
||||||
|
if (!defaultPropType) return;
|
||||||
|
|
||||||
|
const idx = tempStep.value[stepIndex].attributes.properties.findIndex(
|
||||||
|
(p) => p.fieldName === fieldName,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!idx) return;
|
||||||
|
|
||||||
|
if (defaultPropType === 'date') {
|
||||||
|
tempStep.value[stepIndex].attributes.properties.push({
|
||||||
|
type: defaultPropType,
|
||||||
|
fieldName,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (defaultPropType === 'array') {
|
||||||
|
tempStep.value[stepIndex].attributes.properties.push({
|
||||||
|
type: defaultPropType,
|
||||||
|
fieldName,
|
||||||
|
options: [],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (defaultPropType === 'string') {
|
||||||
|
tempStep.value[stepIndex].attributes.properties.push({
|
||||||
|
type: defaultPropType,
|
||||||
|
fieldName,
|
||||||
|
isPhoneNumber: false,
|
||||||
|
phoneNumberLength: 10,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (defaultPropType === 'number') {
|
||||||
|
tempStep.value[stepIndex].attributes.properties.push({
|
||||||
|
type: defaultPropType,
|
||||||
|
fieldName,
|
||||||
|
comma: false,
|
||||||
|
decimal: false,
|
||||||
|
decimalPlace: 2,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function shouldShowItem(opt: Option, stepIndex: number) {
|
||||||
|
if (tempStep.value[stepIndex].attributes.properties) {
|
||||||
|
const properties = new Set(
|
||||||
|
tempStep.value[stepIndex].attributes.properties.map((p) => p.fieldName),
|
||||||
|
);
|
||||||
|
return !!opt && !properties.has(opt.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function confirmDelete(items: unknown[], index: number) {
|
||||||
|
dialog({
|
||||||
|
color: 'negative',
|
||||||
|
icon: 'mdi-alert',
|
||||||
|
title: t('dialog.title.confirmDelete'),
|
||||||
|
actionText: t('general.delete'),
|
||||||
|
message: t('dialog.message.confirmDelete'),
|
||||||
|
action: async () => {
|
||||||
|
deleteItem(items, index);
|
||||||
|
},
|
||||||
|
cancel: () => {},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => model.value,
|
||||||
|
() => {
|
||||||
|
if (model.value) {
|
||||||
|
propertiesOption.value = optionStore.globalOption?.servicePropertiesField;
|
||||||
|
tempStep.value = JSON.parse(JSON.stringify(dataStep.value));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<DialogForm
|
||||||
|
no-address
|
||||||
|
no-app-box
|
||||||
|
height="60vh"
|
||||||
|
width="75%"
|
||||||
|
:title="$t('general.properties')"
|
||||||
|
v-model:modal="model"
|
||||||
|
:submit="submit"
|
||||||
|
:close="close"
|
||||||
|
>
|
||||||
|
<div class="column">
|
||||||
|
<section
|
||||||
|
v-for="(step, stepIndex) in dataStep"
|
||||||
|
:key="stepIndex"
|
||||||
|
class="column"
|
||||||
|
>
|
||||||
|
<template
|
||||||
|
v-if="
|
||||||
|
(props.stepIndex !== undefined && stepIndex === props.stepIndex) ||
|
||||||
|
props.stepIndex === undefined
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="row items-center q-py-sm bordered-b"
|
||||||
|
:class="{
|
||||||
|
'q-px-lg': $q.screen.gt.sm,
|
||||||
|
'q-px-md ': !$q.screen.gt.sm,
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
{{ $t('flow.stepNo', { msg: (props.stepIndex || stepIndex) + 1 }) }}
|
||||||
|
<span class="app-text-muted">: {{ step.name }}</span>
|
||||||
|
<q-btn-dropdown
|
||||||
|
unelevated
|
||||||
|
no-icon-animation
|
||||||
|
size="sm"
|
||||||
|
padding="0 0"
|
||||||
|
class="rounded q-ml-md"
|
||||||
|
dropdown-icon="mdi-plus"
|
||||||
|
style="color: hsl(var(--text-mute))"
|
||||||
|
>
|
||||||
|
<q-list dense v-if="propertiesOption">
|
||||||
|
<q-item
|
||||||
|
for="list-all"
|
||||||
|
id="list-all"
|
||||||
|
clickable
|
||||||
|
@click="manageProperties(stepIndex, 'all')"
|
||||||
|
>
|
||||||
|
<div class="full-width flex items-center">
|
||||||
|
<q-icon
|
||||||
|
v-if="
|
||||||
|
tempStep[stepIndex].attributes.properties.length ===
|
||||||
|
propertiesOption.length
|
||||||
|
"
|
||||||
|
name="mdi-checkbox-marked"
|
||||||
|
size="xs"
|
||||||
|
class="q-mr-sm"
|
||||||
|
color="primary"
|
||||||
|
/>
|
||||||
|
<q-icon
|
||||||
|
v-else
|
||||||
|
name="mdi-checkbox-blank-outline"
|
||||||
|
size="xs"
|
||||||
|
class="q-mr-sm"
|
||||||
|
style="color: hsl(var(--text-mute))"
|
||||||
|
/>
|
||||||
|
{{ $t('general.selectAll') }}
|
||||||
|
</div>
|
||||||
|
</q-item>
|
||||||
|
<q-separator />
|
||||||
|
<q-item
|
||||||
|
v-for="(ops, index) in propertiesOption"
|
||||||
|
clickable
|
||||||
|
:key="index"
|
||||||
|
@click="manageProperties(stepIndex, ops.value, ops.type)"
|
||||||
|
:for="`list-${ops.value}`"
|
||||||
|
:id="`list-${ops.value}`"
|
||||||
|
>
|
||||||
|
<div class="full-width flex items-center no-wrap">
|
||||||
|
<q-icon
|
||||||
|
v-if="
|
||||||
|
tempStep[stepIndex].attributes.properties.some(
|
||||||
|
(p) => p.fieldName === ops.value,
|
||||||
|
)
|
||||||
|
"
|
||||||
|
name="mdi-checkbox-marked"
|
||||||
|
size="xs"
|
||||||
|
color="primary"
|
||||||
|
class="q-mr-sm"
|
||||||
|
/>
|
||||||
|
<q-icon
|
||||||
|
v-else
|
||||||
|
name="mdi-checkbox-blank-outline"
|
||||||
|
size="xs"
|
||||||
|
style="color: hsl(var(--text-mute))"
|
||||||
|
class="q-mr-sm"
|
||||||
|
/>
|
||||||
|
{{ ops.label }}
|
||||||
|
</div>
|
||||||
|
</q-item>
|
||||||
|
</q-list>
|
||||||
|
</q-btn-dropdown>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-if="tempStep[stepIndex].attributes.properties.length === 0"
|
||||||
|
class="row surface-1 rounded bordered items-center justify-center col"
|
||||||
|
:class="{
|
||||||
|
'q-ma-lg': $q.screen.gt.sm,
|
||||||
|
'q-ma-md': !$q.screen.gt.sm,
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<NoData useField />
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="tempStep[stepIndex].attributes.properties.length > 0"
|
||||||
|
class="q-py-sm"
|
||||||
|
:class="{
|
||||||
|
'q-px-lg': $q.screen.gt.sm,
|
||||||
|
'q-px-md': !$q.screen.gt.sm,
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
v-for="(prop, propIndex) in tempStep[stepIndex].attributes
|
||||||
|
.properties"
|
||||||
|
:key="propIndex"
|
||||||
|
class="bordered surface-1 rounded q-py-sm q-px-md row items-start q-my-sm"
|
||||||
|
>
|
||||||
|
<div class="col-md col-12 row items-center">
|
||||||
|
<q-btn
|
||||||
|
id="btn-move-up-product"
|
||||||
|
icon="mdi-arrow-up"
|
||||||
|
dense
|
||||||
|
flat
|
||||||
|
round
|
||||||
|
:disable="propIndex === 0"
|
||||||
|
:size="$q.screen.xs ? 'xs' : ''"
|
||||||
|
style="color: hsl(var(--text-mute-2))"
|
||||||
|
@click="moveItemUp(tempStep, propIndex)"
|
||||||
|
/>
|
||||||
|
<q-btn
|
||||||
|
id="btn-move-down-product"
|
||||||
|
icon="mdi-arrow-down"
|
||||||
|
dense
|
||||||
|
flat
|
||||||
|
round
|
||||||
|
:size="$q.screen.xs ? 'xs' : ''"
|
||||||
|
:disable="propIndex === tempStep.length - 1"
|
||||||
|
style="color: hsl(var(--text-mute-2))"
|
||||||
|
@click="moveItemDown(tempStep, propIndex)"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<q-avatar
|
||||||
|
:size="$q.screen.xs ? 'sm' : 'md'"
|
||||||
|
class="q-mx-lg"
|
||||||
|
style="background-color: var(--surface-3)"
|
||||||
|
>
|
||||||
|
{{ propIndex + 1 }}
|
||||||
|
</q-avatar>
|
||||||
|
|
||||||
|
<!-- field name -->
|
||||||
|
<q-select
|
||||||
|
dense
|
||||||
|
outlined
|
||||||
|
emit-value
|
||||||
|
map-options
|
||||||
|
hide-bottom-space
|
||||||
|
for="input-properties-name"
|
||||||
|
class="col-md col-12 q-mr-md"
|
||||||
|
:class="{ 'q-my-sm': $q.screen.lt.md }"
|
||||||
|
:label="$t('productService.service.propertiesName')"
|
||||||
|
option-label="label"
|
||||||
|
option-value="value"
|
||||||
|
:options="propertiesOption"
|
||||||
|
v-model="prop.fieldName"
|
||||||
|
@update:model-value="(v) => changeType(v, stepIndex)"
|
||||||
|
>
|
||||||
|
<template v-slot:option="scope">
|
||||||
|
<q-item
|
||||||
|
v-if="scope.opt && shouldShowItem(scope.opt, stepIndex)"
|
||||||
|
v-bind="scope.itemProps"
|
||||||
|
class="row items-center col-12"
|
||||||
|
>
|
||||||
|
{{ scope.opt.label }}
|
||||||
|
</q-item>
|
||||||
|
</template>
|
||||||
|
</q-select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- type -->
|
||||||
|
<div class="col-md col-12">
|
||||||
|
<q-select
|
||||||
|
dense
|
||||||
|
outlined
|
||||||
|
emit-value
|
||||||
|
map-options
|
||||||
|
hide-bottom-space
|
||||||
|
for="input-properties-type"
|
||||||
|
id="input-properties-type"
|
||||||
|
:label="$t('general.type')"
|
||||||
|
option-value="value"
|
||||||
|
@update:model-value="
|
||||||
|
(t: 'string' | 'number' | 'date' | 'array') => {
|
||||||
|
if (!tempStep) return;
|
||||||
|
if (t === 'date') {
|
||||||
|
tempStep[stepIndex].attributes.properties[propIndex] = {
|
||||||
|
type: t,
|
||||||
|
fieldName: prop.fieldName,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (t === 'array') {
|
||||||
|
tempStep[stepIndex].attributes.properties[propIndex] = {
|
||||||
|
type: t,
|
||||||
|
fieldName: prop.fieldName,
|
||||||
|
options: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (t === 'string') {
|
||||||
|
tempStep[stepIndex].attributes.properties[propIndex] = {
|
||||||
|
type: t,
|
||||||
|
fieldName: prop.fieldName,
|
||||||
|
isPhoneNumber: false,
|
||||||
|
phoneNumberLength: 10,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (t === 'number') {
|
||||||
|
tempStep[stepIndex].attributes.properties[propIndex] = {
|
||||||
|
type: t,
|
||||||
|
fieldName: prop.fieldName,
|
||||||
|
comma: false,
|
||||||
|
decimal: false,
|
||||||
|
decimalPlace: 2,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"
|
||||||
|
:options="typeOption"
|
||||||
|
v-model="prop.type"
|
||||||
|
>
|
||||||
|
<template v-slot:option="scope">
|
||||||
|
<q-item
|
||||||
|
v-if="scope.opt"
|
||||||
|
v-bind="scope.itemProps"
|
||||||
|
class="row items-center col-12"
|
||||||
|
:id="`type-${scope.itemProps}`"
|
||||||
|
>
|
||||||
|
<q-avatar
|
||||||
|
size="sm"
|
||||||
|
class="q-mr-md"
|
||||||
|
:style="`background-color: hsla(${scope.opt.color}/0.2)`"
|
||||||
|
>
|
||||||
|
<q-icon
|
||||||
|
size="20px"
|
||||||
|
:name="scope.opt.icon"
|
||||||
|
:style="`color: hsl(${scope.opt.color})`"
|
||||||
|
/>
|
||||||
|
</q-avatar>
|
||||||
|
{{ scope.opt.label }}
|
||||||
|
</q-item>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template v-slot:selected-item="scope">
|
||||||
|
<div v-if="scope.opt" class="row items-center col-12">
|
||||||
|
<q-avatar
|
||||||
|
size="xs"
|
||||||
|
class="q-mr-sm"
|
||||||
|
:style="`background-color: hsla(${scope.opt.color}/0.2)`"
|
||||||
|
>
|
||||||
|
<q-icon
|
||||||
|
size="14px"
|
||||||
|
:name="scope.opt.icon"
|
||||||
|
:style="`color: hsl(${scope.opt.color})`"
|
||||||
|
/>
|
||||||
|
</q-avatar>
|
||||||
|
{{ scope.opt.label }}
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</q-select>
|
||||||
|
|
||||||
|
<div v-if="prop.type !== 'date' && prop.type">
|
||||||
|
<q-item class="no-padding" style="font-size: 11px">
|
||||||
|
<q-item-section
|
||||||
|
class="column q-py-sm"
|
||||||
|
:style="{ 'padding-left: 12px': $q.screen.gt.xs }"
|
||||||
|
>
|
||||||
|
<span class="app-text-muted-2">
|
||||||
|
{{ $t('general.additional') }}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<div v-if="prop.type === 'string'" class="q-gutter-y-sm">
|
||||||
|
<div class="row items-center">
|
||||||
|
<div class="col-7 surface-3 rounded q-mr-sm q-py-xs">
|
||||||
|
<q-checkbox
|
||||||
|
:for="`checkbox-is-phone-number-${prop.fieldName}`"
|
||||||
|
v-if="prop.type === 'string'"
|
||||||
|
v-model="prop.isPhoneNumber"
|
||||||
|
size="xs"
|
||||||
|
/>
|
||||||
|
{{ $t('general.telephone') }}
|
||||||
|
</div>
|
||||||
|
<q-input
|
||||||
|
v-if="prop.type === 'string'"
|
||||||
|
:for="`input-max-length-${prop.fieldName}`"
|
||||||
|
v-model="prop.phoneNumberLength"
|
||||||
|
input-class="text-caption"
|
||||||
|
class="col additional-label"
|
||||||
|
dense
|
||||||
|
outlined
|
||||||
|
:label="$t('form.maxLength')"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="prop.type === 'number'" class="q-gutter-y-sm">
|
||||||
|
<div class="row items-center">
|
||||||
|
<div class="col-md-4 col-12 surface-3 rounded">
|
||||||
|
<q-checkbox
|
||||||
|
v-model="prop.comma"
|
||||||
|
size="xs"
|
||||||
|
class="q-py-xs"
|
||||||
|
:for="`checkbox-is-comma-${prop.fieldName}`"
|
||||||
|
/>
|
||||||
|
{{ $t('form.useComma') }}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="col-md-4 col-7 surface-3 rounded"
|
||||||
|
:class="{
|
||||||
|
'q-mx-sm': $q.screen.gt.sm,
|
||||||
|
'q-mr-sm q-mt-xs': $q.screen.lt.md,
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<q-checkbox
|
||||||
|
v-model="prop.decimal"
|
||||||
|
size="xs"
|
||||||
|
class="q-py-xs"
|
||||||
|
:for="`checkbox-is-decimal-${prop.fieldName}`"
|
||||||
|
/>
|
||||||
|
{{ $t('form.decimal') }}
|
||||||
|
</div>
|
||||||
|
<q-input
|
||||||
|
:for="`input-decimal-place-${prop.fieldName}`"
|
||||||
|
v-model="prop.decimalPlace"
|
||||||
|
class="col additional-label"
|
||||||
|
:class="{ 'q-mt-xs': $q.screen.lt.md }"
|
||||||
|
input-class="text-caption"
|
||||||
|
dense
|
||||||
|
outlined
|
||||||
|
:label="$t('form.decimalPlace')"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="prop.type === 'array'" class="q-gutter-y-sm">
|
||||||
|
<div
|
||||||
|
class="row items-center justify-between"
|
||||||
|
v-for="(_, i) in prop.options"
|
||||||
|
:key="i"
|
||||||
|
>
|
||||||
|
<div class="col rounded">
|
||||||
|
<q-input
|
||||||
|
v-model="prop.options[i]"
|
||||||
|
:for="`input-selection-${prop.fieldName}-${i}`"
|
||||||
|
class="col additional-label"
|
||||||
|
dense
|
||||||
|
outlined
|
||||||
|
input-class="text-caption"
|
||||||
|
:label="$t('form.selection')"
|
||||||
|
:rules="[
|
||||||
|
(val) => !!val || $t('form.error.required'),
|
||||||
|
]"
|
||||||
|
hide-bottom-space
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="col-1 q-pl-sm">
|
||||||
|
<q-btn
|
||||||
|
:id="`btn-delete-selection-${prop.fieldName}-${i}`"
|
||||||
|
:for="`btn-delete-selection-${prop.fieldName}-${i}`"
|
||||||
|
@click="
|
||||||
|
() => {
|
||||||
|
prop.options.splice(i, 1);
|
||||||
|
}
|
||||||
|
"
|
||||||
|
dense
|
||||||
|
flat
|
||||||
|
icon="mdi-trash-can-outline"
|
||||||
|
class="bordered"
|
||||||
|
text-color="negative"
|
||||||
|
style="border-radius: 6px"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<q-btn
|
||||||
|
:for="`btn-add-selection-${prop.fieldName}`"
|
||||||
|
:id="`btn-add-selection-${prop.fieldName}`"
|
||||||
|
@click="
|
||||||
|
() => {
|
||||||
|
prop.options.push('');
|
||||||
|
}
|
||||||
|
"
|
||||||
|
dense
|
||||||
|
flat
|
||||||
|
icon="mdi-plus"
|
||||||
|
class="bordered col-11"
|
||||||
|
text-color="grey"
|
||||||
|
style="border-radius: 6px"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<q-btn
|
||||||
|
id="btn-delete-work-product"
|
||||||
|
icon="mdi-trash-can-outline"
|
||||||
|
dense
|
||||||
|
flat
|
||||||
|
round
|
||||||
|
color="negative"
|
||||||
|
class="q-ml-sm"
|
||||||
|
@click="confirmDelete(tempStep, propIndex)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</DialogForm>
|
||||||
|
</template>
|
||||||
|
<style scoped></style>
|
||||||
|
|
@ -11,12 +11,14 @@ const props = withDefaults(
|
||||||
|
|
||||||
option: Record<string, unknown>[];
|
option: Record<string, unknown>[];
|
||||||
optionLabel?: string;
|
optionLabel?: string;
|
||||||
|
separatorIndex?: number[];
|
||||||
}>(),
|
}>(),
|
||||||
{
|
{
|
||||||
readonly: false,
|
readonly: false,
|
||||||
option: () => [],
|
option: () => [],
|
||||||
optionLabel: 'label',
|
optionLabel: 'label',
|
||||||
offset: () => [0, 12],
|
offset: () => [0, 12],
|
||||||
|
separatorIndex: () => [],
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -88,6 +90,7 @@ defineEmits<{
|
||||||
dense
|
dense
|
||||||
:key="i"
|
:key="i"
|
||||||
class="flex items-center"
|
class="flex items-center"
|
||||||
|
:class="{ 'bordered-t': separatorIndex.includes(i) }"
|
||||||
clickable
|
clickable
|
||||||
@click.stop="$emit('select', opt)"
|
@click.stop="$emit('select', opt)"
|
||||||
>
|
>
|
||||||
|
|
@ -100,6 +103,10 @@ defineEmits<{
|
||||||
</span>
|
</span>
|
||||||
</q-item>
|
</q-item>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<template v-if="$slots.append">
|
||||||
|
<slot name="append"></slot>
|
||||||
|
</template>
|
||||||
</q-menu>
|
</q-menu>
|
||||||
</template>
|
</template>
|
||||||
<style scoped></style>
|
<style scoped></style>
|
||||||
|
|
|
||||||
|
|
@ -122,6 +122,7 @@ export default {
|
||||||
company: 'Company',
|
company: 'Company',
|
||||||
agencies: 'Agencies',
|
agencies: 'Agencies',
|
||||||
form: 'Form',
|
form: 'Form',
|
||||||
|
properties: 'Properties',
|
||||||
designForm: 'Design Form',
|
designForm: 'Design Form',
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -818,6 +819,8 @@ export default {
|
||||||
step: 'Step',
|
step: 'Step',
|
||||||
stepNo: 'Step no {msg}',
|
stepNo: 'Step no {msg}',
|
||||||
responsiblePerson: 'Responsible Person',
|
responsiblePerson: 'Responsible Person',
|
||||||
|
customer: 'Customer',
|
||||||
|
officer: 'Officer',
|
||||||
},
|
},
|
||||||
|
|
||||||
agencies: {
|
agencies: {
|
||||||
|
|
|
||||||
|
|
@ -123,6 +123,7 @@ export default {
|
||||||
agencies: 'หน่วยงาน',
|
agencies: 'หน่วยงาน',
|
||||||
form: 'แบบฟอร์ม',
|
form: 'แบบฟอร์ม',
|
||||||
designForm: 'ออกแบบฟอร์ม',
|
designForm: 'ออกแบบฟอร์ม',
|
||||||
|
properties: 'คุณสมบัติ',
|
||||||
},
|
},
|
||||||
|
|
||||||
menu: {
|
menu: {
|
||||||
|
|
@ -811,6 +812,8 @@ export default {
|
||||||
step: 'ขั้นตอน',
|
step: 'ขั้นตอน',
|
||||||
stepNo: 'ขั้นตอนที่ {msg}',
|
stepNo: 'ขั้นตอนที่ {msg}',
|
||||||
responsiblePerson: 'ผู้รับผิดชอบ',
|
responsiblePerson: 'ผู้รับผิดชอบ',
|
||||||
|
customer: 'ลูกค้า',
|
||||||
|
officer: 'เจ้าหน้าที่',
|
||||||
},
|
},
|
||||||
|
|
||||||
agencies: {
|
agencies: {
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,13 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { nextTick } from 'vue';
|
import { nextTick, reactive, ref } from 'vue';
|
||||||
import {
|
import {
|
||||||
WorkflowUserInTable,
|
WorkflowUserInTable,
|
||||||
WorkflowTemplatePayload,
|
WorkflowTemplatePayload,
|
||||||
|
WorkFlowPayloadStep,
|
||||||
} from 'src/stores/workflow-template/types';
|
} from 'src/stores/workflow-template/types';
|
||||||
|
|
||||||
import FormFlow from 'src/components/04_flow-management/FormFlow.vue';
|
import FormFlow from 'src/components/04_flow-management/FormFlow.vue';
|
||||||
|
import DialogProperties from 'src/components/dialog/DialogProperties.vue';
|
||||||
import DialogForm from 'src/components/DialogForm.vue';
|
import DialogForm from 'src/components/DialogForm.vue';
|
||||||
import SideMenu from 'src/components/SideMenu.vue';
|
import SideMenu from 'src/components/SideMenu.vue';
|
||||||
import DrawerInfo from 'src/components/DrawerInfo.vue';
|
import DrawerInfo from 'src/components/DrawerInfo.vue';
|
||||||
|
|
@ -33,6 +35,10 @@ const userInTable = defineModel<WorkflowUserInTable[]>('userInTable', {
|
||||||
default: [],
|
default: [],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const pageState = reactive({ propertiesModal: false });
|
||||||
|
|
||||||
|
const currStep = ref<WorkFlowPayloadStep[]>([]);
|
||||||
|
|
||||||
withDefaults(
|
withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
readonly?: boolean;
|
readonly?: boolean;
|
||||||
|
|
@ -57,14 +63,21 @@ async function addStep() {
|
||||||
value: [],
|
value: [],
|
||||||
detail: '',
|
detail: '',
|
||||||
name: '',
|
name: '',
|
||||||
|
attributes: { properties: [] },
|
||||||
});
|
});
|
||||||
|
|
||||||
await nextTick();
|
await nextTick();
|
||||||
const scrollTarget = document.getElementById(
|
const scrollTarget = document.getElementById(
|
||||||
`input-flow-step-name-${flowData.value.step.length - 1}`,
|
`input-flow-step-name-${flowData.value.step.length - 1}-${model.value ? 'dialog' : 'drawer'}`,
|
||||||
);
|
);
|
||||||
if (scrollTarget)
|
if (scrollTarget) {
|
||||||
scrollTarget.scrollIntoView({ behavior: 'instant', inline: 'center' });
|
scrollTarget.scrollIntoView({ behavior: 'instant', inline: 'start' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function triggerPropertiesDialog(step: WorkFlowPayloadStep) {
|
||||||
|
currStep.value = flowData.value.step.filter((s) => s === step);
|
||||||
|
pageState.propertiesModal = true;
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
|
|
@ -104,22 +117,28 @@ async function addStep() {
|
||||||
:menu="[
|
:menu="[
|
||||||
{
|
{
|
||||||
name: $t('general.name', { msg: $t('flow.template') }),
|
name: $t('general.name', { msg: $t('flow.template') }),
|
||||||
anchor: 'form-flow-template',
|
anchor: 'form-flow-template-dialog',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: $t('flow.processStep'),
|
name: $t('flow.processStep'),
|
||||||
anchor: 'form-flow-step',
|
anchor: 'form-flow-step-dialog',
|
||||||
useBtn: true,
|
useBtn: true,
|
||||||
},
|
},
|
||||||
|
...flowData.step.map((_s, i) => ({
|
||||||
|
name: $t('flow.stepNo', { msg: i + 1 }),
|
||||||
|
anchor: `input-flow-step-name-${i}-dialog`,
|
||||||
|
sub: true,
|
||||||
|
hideSubIndex: true,
|
||||||
|
})),
|
||||||
]"
|
]"
|
||||||
background="transparent"
|
background="transparent"
|
||||||
:active="{
|
:active="{
|
||||||
background: 'hsla(var(--blue-6-hsl) / .2)',
|
background: 'hsla(var(--blue-6-hsl) / .2)',
|
||||||
foreground: 'var(--blue-6)',
|
foreground: 'var(--blue-6)',
|
||||||
}"
|
}"
|
||||||
scroll-element="#flow-form"
|
scroll-element="#flow-form-dialog"
|
||||||
>
|
>
|
||||||
<template v-slot:btn-form-flow-step>
|
<template v-slot:btn-form-flow-step-dialog>
|
||||||
<q-btn
|
<q-btn
|
||||||
dense
|
dense
|
||||||
flat
|
flat
|
||||||
|
|
@ -144,11 +163,12 @@ async function addStep() {
|
||||||
'q-py-md q-px-lg': !$q.screen.gt.sm,
|
'q-py-md q-px-lg': !$q.screen.gt.sm,
|
||||||
}"
|
}"
|
||||||
style="height: 100%; max-height: 100%; overflow-y: auto"
|
style="height: 100%; max-height: 100%; overflow-y: auto"
|
||||||
id="flow-form"
|
id="flow-form-dialog"
|
||||||
>
|
>
|
||||||
<FormFlow
|
<FormFlow
|
||||||
v-model:flow-data="flowData"
|
v-model:flow-data="flowData"
|
||||||
v-model:register-branch-id="registerBranchId"
|
v-model:register-branch-id="registerBranchId"
|
||||||
|
@trigger-properties="triggerPropertiesDialog"
|
||||||
/>
|
/>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -241,22 +261,28 @@ async function addStep() {
|
||||||
:menu="[
|
:menu="[
|
||||||
{
|
{
|
||||||
name: $t('general.name', { msg: $t('flow.template') }),
|
name: $t('general.name', { msg: $t('flow.template') }),
|
||||||
anchor: 'form-flow-template',
|
anchor: 'form-flow-template-drawer',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: $t('flow.processStep'),
|
name: $t('flow.processStep'),
|
||||||
anchor: 'form-flow-step',
|
anchor: 'form-flow-step-drawer',
|
||||||
useBtn: true,
|
useBtn: true,
|
||||||
},
|
},
|
||||||
|
...flowData.step.map((_s, i) => ({
|
||||||
|
name: $t('flow.stepNo', { msg: i + 1 }),
|
||||||
|
anchor: `input-flow-step-name-${i}-drawer`,
|
||||||
|
sub: true,
|
||||||
|
hideSubIndex: true,
|
||||||
|
})),
|
||||||
]"
|
]"
|
||||||
background="transparent"
|
background="transparent"
|
||||||
:active="{
|
:active="{
|
||||||
background: 'hsla(var(--blue-6-hsl) / .2)',
|
background: 'hsla(var(--blue-6-hsl) / .2)',
|
||||||
foreground: 'var(--blue-6)',
|
foreground: 'var(--blue-6)',
|
||||||
}"
|
}"
|
||||||
scroll-element="#flow-form"
|
scroll-element="#flow-form-drawer"
|
||||||
>
|
>
|
||||||
<template v-slot:btn-form-flow-step>
|
<template v-slot:btn-form-flow-step-drawer>
|
||||||
<q-btn
|
<q-btn
|
||||||
dense
|
dense
|
||||||
flat
|
flat
|
||||||
|
|
@ -281,20 +307,27 @@ async function addStep() {
|
||||||
'q-py-md q-px-lg': !$q.screen.gt.sm,
|
'q-py-md q-px-lg': !$q.screen.gt.sm,
|
||||||
}"
|
}"
|
||||||
style="height: 100%; max-height: 100%; overflow-y: auto"
|
style="height: 100%; max-height: 100%; overflow-y: auto"
|
||||||
id="flow-form"
|
id="flow-form-drawer"
|
||||||
>
|
>
|
||||||
<FormFlow
|
<FormFlow
|
||||||
:readonly
|
:readonly
|
||||||
@change-status="$emit('changeStatus')"
|
|
||||||
onDrawer
|
onDrawer
|
||||||
v-model:user-in-table="userInTable"
|
v-model:user-in-table="userInTable"
|
||||||
v-model:flow-data="flowData"
|
v-model:flow-data="flowData"
|
||||||
v-model:register-branch-id="registerBranchId"
|
v-model:register-branch-id="registerBranchId"
|
||||||
|
@change-status="$emit('changeStatus')"
|
||||||
|
@trigger-properties="triggerPropertiesDialog"
|
||||||
/>
|
/>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</DrawerInfo>
|
</DrawerInfo>
|
||||||
|
|
||||||
|
<DialogProperties
|
||||||
|
v-model="pageState.propertiesModal"
|
||||||
|
v-model:data-step="flowData.step"
|
||||||
|
:step-index="flowData.step.indexOf(currStep[0])"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
<style scoped></style>
|
<style scoped></style>
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,27 @@ const pageState = reactive({
|
||||||
isDrawerEdit: true,
|
isDrawerEdit: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const fieldSelected = ref<('order' | 'name' | 'step')[]>([
|
||||||
|
'order',
|
||||||
|
'name',
|
||||||
|
'step',
|
||||||
|
]);
|
||||||
|
const fieldSelectedOption = ref<{ label: string; value: string }[]>([
|
||||||
|
{
|
||||||
|
label: 'general.order',
|
||||||
|
value: 'order',
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
label: 'general.name',
|
||||||
|
value: 'name',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'flow.processStep',
|
||||||
|
value: 'step',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
const currWorkflowData = ref<WorkflowTemplate>();
|
const currWorkflowData = ref<WorkflowTemplate>();
|
||||||
const formDataWorkflow = ref<WorkflowTemplatePayload>({
|
const formDataWorkflow = ref<WorkflowTemplatePayload>({
|
||||||
status: 'CREATED',
|
status: 'CREATED',
|
||||||
|
|
@ -68,15 +89,9 @@ const columns = [
|
||||||
{
|
{
|
||||||
name: 'step',
|
name: 'step',
|
||||||
align: 'center',
|
align: 'center',
|
||||||
label: 'general.numberOf',
|
label: 'flow.processStep',
|
||||||
field: 'step',
|
field: 'step',
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: 'action',
|
|
||||||
align: 'center',
|
|
||||||
label: '',
|
|
||||||
field: 'action',
|
|
||||||
},
|
|
||||||
] satisfies QTableProps['columns'];
|
] satisfies QTableProps['columns'];
|
||||||
|
|
||||||
function triggerDialog(type: 'add' | 'edit' | 'view') {
|
function triggerDialog(type: 'add' | 'edit' | 'view') {
|
||||||
|
|
@ -132,6 +147,7 @@ async function changeStatus(id?: string, regisBId?: string) {
|
||||||
if (currWorkflowData.value) {
|
if (currWorkflowData.value) {
|
||||||
currWorkflowData.value.status = res.data.status;
|
currWorkflowData.value.status = res.data.status;
|
||||||
}
|
}
|
||||||
|
await fetchWorkflowList();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -189,35 +205,40 @@ async function submit() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function assignFormData(workflowData: WorkflowTemplate) {
|
function assignFormData(workflowData: WorkflowTemplate) {
|
||||||
currWorkflowData.value = workflowData;
|
currWorkflowData.value = JSON.parse(JSON.stringify(workflowData));
|
||||||
formDataWorkflow.value = {
|
formDataWorkflow.value = JSON.parse(
|
||||||
status: workflowData.status,
|
JSON.stringify({
|
||||||
name: workflowData.name,
|
status: workflowData.status,
|
||||||
step: workflowData.step.map((s, i) => {
|
name: workflowData.name,
|
||||||
userInTable.value[i] = { name: s.name, responsiblePerson: [] };
|
step: workflowData.step.map((s, i) => {
|
||||||
s.responsiblePerson.forEach((p) => {
|
userInTable.value[i] = { name: s.name, responsiblePerson: [] };
|
||||||
userInTable.value[i].responsiblePerson.push({
|
s.responsiblePerson.forEach((p) => {
|
||||||
id: p.user.id,
|
userInTable.value[i].responsiblePerson.push({
|
||||||
selectedImage: p.user.selectedImage,
|
id: p.user.id,
|
||||||
gender: p.user.gender,
|
selectedImage: p.user.selectedImage,
|
||||||
namePrefix: p.user.namePrefix,
|
gender: p.user.gender,
|
||||||
firstName: p.user.firstName,
|
namePrefix: p.user.namePrefix,
|
||||||
lastName: p.user.lastName,
|
firstName: p.user.firstName,
|
||||||
firstNameEN: p.user.firstNameEN,
|
lastName: p.user.lastName,
|
||||||
lastNameEN: p.user.lastNameEN,
|
firstNameEN: p.user.firstNameEN,
|
||||||
code: p.user.code,
|
lastNameEN: p.user.lastNameEN,
|
||||||
|
code: p.user.code,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
return {
|
||||||
return {
|
id: s.id,
|
||||||
id: s.id,
|
name: s.name,
|
||||||
name: s.name,
|
detail: s.detail,
|
||||||
detail: s.detail,
|
value: s.value.length > 0 ? JSON.parse(JSON.stringify(s.value)) : [],
|
||||||
value: s.value || [],
|
responsiblePersonId: s.responsiblePerson.map((p) => p.userId),
|
||||||
responsiblePersonId: s.responsiblePerson.map((p) => p.userId),
|
responsibleInstitution: JSON.parse(
|
||||||
responsibleInstitution: s.responsibleInstitution,
|
JSON.stringify(s.responsibleInstitution),
|
||||||
};
|
),
|
||||||
|
attributes: JSON.parse(JSON.stringify(s.attributes)),
|
||||||
|
};
|
||||||
|
}),
|
||||||
}),
|
}),
|
||||||
};
|
);
|
||||||
registeredBranchId.value = workflowData.registeredBranchId;
|
registeredBranchId.value = workflowData.registeredBranchId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -309,10 +330,10 @@ watch(() => pageState.inputSearch, fetchWorkflowList);
|
||||||
labelI18n
|
labelI18n
|
||||||
:branch="[
|
:branch="[
|
||||||
{
|
{
|
||||||
icon: 'mdi-folder-outline',
|
icon: 'mdi-cogs',
|
||||||
count: pageState.total,
|
count: pageState.total,
|
||||||
label: 'flow.title',
|
label: 'flow.title',
|
||||||
color: 'red',
|
color: 'gray',
|
||||||
},
|
},
|
||||||
]"
|
]"
|
||||||
:dark="$q.dark.isActive"
|
:dark="$q.dark.isActive"
|
||||||
|
|
@ -345,7 +366,7 @@ watch(() => pageState.inputSearch, fetchWorkflowList);
|
||||||
</q-input>
|
</q-input>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="row col-12 col-md-3 justify-end"
|
class="row col-12 col-md-5 justify-end"
|
||||||
:class="{ 'q-pt-xs': $q.screen.lt.md }"
|
:class="{ 'q-pt-xs': $q.screen.lt.md }"
|
||||||
style="white-space: nowrap"
|
style="white-space: nowrap"
|
||||||
>
|
>
|
||||||
|
|
@ -356,7 +377,6 @@ watch(() => pageState.inputSearch, fetchWorkflowList);
|
||||||
option-value="value"
|
option-value="value"
|
||||||
option-label="label"
|
option-label="label"
|
||||||
class="col"
|
class="col"
|
||||||
:class="{ 'offset-md-5': pageState.gridView }"
|
|
||||||
map-options
|
map-options
|
||||||
emit-value
|
emit-value
|
||||||
:for="'field-select-status'"
|
:for="'field-select-status'"
|
||||||
|
|
@ -367,6 +387,73 @@ watch(() => pageState.inputSearch, fetchWorkflowList);
|
||||||
{ label: $t('general.inactive'), value: 'statusINACTIVE' },
|
{ label: $t('general.inactive'), value: 'statusINACTIVE' },
|
||||||
]"
|
]"
|
||||||
/>
|
/>
|
||||||
|
<q-select
|
||||||
|
id="select-field"
|
||||||
|
for="select-field"
|
||||||
|
class="col q-ml-sm"
|
||||||
|
:options="
|
||||||
|
fieldSelectedOption.map((v) => ({
|
||||||
|
...v,
|
||||||
|
label: $t(v.label),
|
||||||
|
}))
|
||||||
|
"
|
||||||
|
:display-value="$t('general.displayField')"
|
||||||
|
:hide-dropdown-icon="$q.screen.lt.sm"
|
||||||
|
v-model="fieldSelected"
|
||||||
|
option-label="label"
|
||||||
|
option-value="value"
|
||||||
|
autocomplete="off"
|
||||||
|
map-options
|
||||||
|
emit-value
|
||||||
|
outlined
|
||||||
|
multiple
|
||||||
|
dense
|
||||||
|
/>
|
||||||
|
<q-btn-toggle
|
||||||
|
id="btn-mode"
|
||||||
|
v-model="pageState.gridView"
|
||||||
|
dense
|
||||||
|
class="no-shadow bordered rounded surface-1 q-ml-sm"
|
||||||
|
:toggle-color="$q.dark.isActive ? 'grey-9' : 'grey-2'"
|
||||||
|
size="xs"
|
||||||
|
:options="[
|
||||||
|
{ value: true, slot: 'folder' },
|
||||||
|
{ value: false, slot: 'list' },
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
<template v-slot:folder>
|
||||||
|
<q-icon
|
||||||
|
name="mdi-view-grid-outline"
|
||||||
|
size="16px"
|
||||||
|
class="q-px-sm q-py-xs rounded"
|
||||||
|
:style="{
|
||||||
|
color: $q.dark.isActive
|
||||||
|
? pageState.gridView
|
||||||
|
? '#C9D3DB '
|
||||||
|
: '#787B7C'
|
||||||
|
: pageState.gridView
|
||||||
|
? '#787B7C'
|
||||||
|
: '#C9D3DB',
|
||||||
|
}"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<template v-slot:list>
|
||||||
|
<q-icon
|
||||||
|
name="mdi-format-list-bulleted"
|
||||||
|
class="q-px-sm q-py-xs rounded"
|
||||||
|
size="16px"
|
||||||
|
:style="{
|
||||||
|
color: $q.dark.isActive
|
||||||
|
? pageState.gridView === false
|
||||||
|
? '#C9D3DB'
|
||||||
|
: '#787B7C'
|
||||||
|
: pageState.gridView === false
|
||||||
|
? '#787B7C'
|
||||||
|
: '#C9D3DB',
|
||||||
|
}"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</q-btn-toggle>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
@ -393,10 +480,15 @@ watch(() => pageState.inputSearch, fetchWorkflowList);
|
||||||
<q-table
|
<q-table
|
||||||
flat
|
flat
|
||||||
bordered
|
bordered
|
||||||
hide-pagination
|
:grid="pageState.gridView"
|
||||||
:columns="columns"
|
|
||||||
:rows="workflowData"
|
:rows="workflowData"
|
||||||
|
:columns="columns"
|
||||||
|
class="full-width"
|
||||||
|
card-container-class="q-col-gutter-md"
|
||||||
|
row-key="name"
|
||||||
:rows-per-page-options="[0]"
|
:rows-per-page-options="[0]"
|
||||||
|
hide-pagination
|
||||||
|
:visible-columns="fieldSelected"
|
||||||
>
|
>
|
||||||
<template #header="{ cols }">
|
<template #header="{ cols }">
|
||||||
<q-tr style="background-color: hsla(var(--info-bg) / 0.07)">
|
<q-tr style="background-color: hsla(var(--info-bg) / 0.07)">
|
||||||
|
|
@ -420,6 +512,7 @@ watch(() => pageState.inputSearch, fetchWorkflowList);
|
||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
</q-th>
|
</q-th>
|
||||||
|
<q-th auto-width />
|
||||||
</q-tr>
|
</q-tr>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
@ -438,25 +531,57 @@ watch(() => pageState.inputSearch, fetchWorkflowList);
|
||||||
: ''
|
: ''
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
<q-td class="text-center">{{ props.rowIndex + 1 }}</q-td>
|
<q-td
|
||||||
<q-td>{{ props.row.name }}</q-td>
|
v-if="fieldSelected.includes('order')"
|
||||||
<q-td class="text-right">{{ props.row.step.length }}</q-td>
|
class="text-center"
|
||||||
<q-td class="row items-center justify-end">
|
>
|
||||||
<div class="row">
|
{{ props.rowIndex + 1 }}
|
||||||
<q-btn
|
<!-- {{ (currentPage - 1) * pageSize + props.rowIndex + 1 }} -->
|
||||||
icon="mdi-eye-outline"
|
</q-td>
|
||||||
size="sm"
|
<q-td v-if="fieldSelected.includes('name')">
|
||||||
dense
|
<section class="row items-center">
|
||||||
round
|
<q-avatar
|
||||||
flat
|
class="q-mr-sm"
|
||||||
@click.stop="
|
size="md"
|
||||||
() => {
|
style="
|
||||||
assignFormData(props.row);
|
color: var(--gray-6);
|
||||||
triggerDialog('view');
|
background: hsla(var(--gray-6-hsl) / 0.1);
|
||||||
}
|
|
||||||
"
|
"
|
||||||
/>
|
>
|
||||||
</div>
|
<q-icon name="mdi-cogs" />
|
||||||
|
|
||||||
|
<q-badge
|
||||||
|
class="absolute-bottom-right no-padding"
|
||||||
|
style="
|
||||||
|
border-radius: 50%;
|
||||||
|
min-width: 8px;
|
||||||
|
min-height: 8px;
|
||||||
|
"
|
||||||
|
:style="{
|
||||||
|
background: `var(--${props.row.status === 'INACTIVE' ? 'stone-5' : 'green-6'})`,
|
||||||
|
}"
|
||||||
|
></q-badge>
|
||||||
|
</q-avatar>
|
||||||
|
{{ props.row.name }}
|
||||||
|
</section>
|
||||||
|
</q-td>
|
||||||
|
<q-td v-if="fieldSelected.includes('step')" class="text-right">
|
||||||
|
{{ props.row.step.length }}
|
||||||
|
</q-td>
|
||||||
|
<q-td style="width: 20%" class="text-right">
|
||||||
|
<q-btn
|
||||||
|
icon="mdi-eye-outline"
|
||||||
|
size="sm"
|
||||||
|
dense
|
||||||
|
round
|
||||||
|
flat
|
||||||
|
@click.stop="
|
||||||
|
() => {
|
||||||
|
assignFormData(props.row);
|
||||||
|
triggerDialog('view');
|
||||||
|
}
|
||||||
|
"
|
||||||
|
/>
|
||||||
<KebabAction
|
<KebabAction
|
||||||
:id-name="props.row.id"
|
:id-name="props.row.id"
|
||||||
:status="props.row.status"
|
:status="props.row.status"
|
||||||
|
|
@ -478,6 +603,76 @@ watch(() => pageState.inputSearch, fetchWorkflowList);
|
||||||
</q-td>
|
</q-td>
|
||||||
</q-tr>
|
</q-tr>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<template v-slot:item="props">
|
||||||
|
<section class="col-12 col-md-4 column">
|
||||||
|
<div
|
||||||
|
class="surface-1 rounded bordered row no-wrap col"
|
||||||
|
style="overflow: hidden"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="col-md-2 col-3 flex items-center justify-center"
|
||||||
|
style="
|
||||||
|
color: var(--gray-6);
|
||||||
|
background: hsla(var(--gray-6-hsl) / 0.1);
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<q-icon name="mdi-cogs" size="md" />
|
||||||
|
</div>
|
||||||
|
<article class="row q-pa-sm q-gutter-y-sm col">
|
||||||
|
<div
|
||||||
|
v-if="fieldSelected.includes('name')"
|
||||||
|
class="text-weight-bold col-12 ellipsis-2-lines"
|
||||||
|
>
|
||||||
|
{{ props.row.name }}
|
||||||
|
|
||||||
|
<q-tooltip>
|
||||||
|
{{ props.row.name }}
|
||||||
|
</q-tooltip>
|
||||||
|
</div>
|
||||||
|
<div v-if="fieldSelected.includes('step')" class="self-end">
|
||||||
|
<div class="bordered rounded q-px-sm">
|
||||||
|
<q-icon name="mdi-note-edit-outline" class="q-pr-sm" />
|
||||||
|
{{ props.row.step.length }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
<nav class="q-pa-sm row items-center self-start">
|
||||||
|
<q-btn
|
||||||
|
icon="mdi-eye-outline"
|
||||||
|
size="sm"
|
||||||
|
dense
|
||||||
|
round
|
||||||
|
flat
|
||||||
|
@click.stop="
|
||||||
|
() => {
|
||||||
|
assignFormData(props.row);
|
||||||
|
triggerDialog('view');
|
||||||
|
}
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
<KebabAction
|
||||||
|
:id-name="props.row.id"
|
||||||
|
:status="props.row.status"
|
||||||
|
@view="
|
||||||
|
() => {
|
||||||
|
assignFormData(props.row);
|
||||||
|
triggerDialog('view');
|
||||||
|
}
|
||||||
|
"
|
||||||
|
@edit="
|
||||||
|
() => {
|
||||||
|
assignFormData(props.row);
|
||||||
|
triggerDialog('edit');
|
||||||
|
}
|
||||||
|
"
|
||||||
|
@delete="() => deleteWorkflow(props.row.id)"
|
||||||
|
@change-status="() => triggerChangeStatus(props.row)"
|
||||||
|
/>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</template>
|
||||||
</q-table>
|
</q-table>
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -72,7 +72,6 @@ const columns = [
|
||||||
const pageState = reactive({
|
const pageState = reactive({
|
||||||
hideStat: false,
|
hideStat: false,
|
||||||
inputSearch: '',
|
inputSearch: '',
|
||||||
fieldSelected: [],
|
|
||||||
gridView: false,
|
gridView: false,
|
||||||
total: 0,
|
total: 0,
|
||||||
|
|
||||||
|
|
@ -683,19 +682,37 @@ watch(
|
||||||
</span>
|
</span>
|
||||||
<span class="col">
|
<span class="col">
|
||||||
{{
|
{{
|
||||||
$i18n.locale === 'eng'
|
formatAddress({
|
||||||
? `${props.row.addressEN}, ${props.row.mooEN && `${$t('form.moo')} ${props.row.mooEN},`} ${props.row.soiEN && `${$t('form.soi')} ${props.row.soiEN},`} ${props.row.moo && `${props.row.streetEN} Rd,`} ${props.row.subDistrict?.nameEN}, ${props.row.district?.nameEN}, ${props.row.province?.nameEN} ${props.row.subDistrict?.zipCode}` ||
|
address: props.row.address,
|
||||||
'-'
|
addressEN: props.row.addressEN,
|
||||||
: `${props.row.address}, ${props.row.moo && `${$t('form.moo')} ${props.row.moo},`} ${props.row.soi && `${$t('form.soi')} ${props.row.soi},`} ${props.row.street && `${$t('form.road')} ${props.row.street},`} ${props.row.subDistrict?.name}, ${props.row.district?.name}, ${props.row.province?.name} ${props.row.subDistrict?.zipCode}` ||
|
moo: props.row.moo,
|
||||||
'-'
|
mooEN: props.row.mooEN,
|
||||||
|
soi: props.row.soi,
|
||||||
|
soiEN: props.row.soiEN,
|
||||||
|
street: props.row.street,
|
||||||
|
streetEN: props.row.streetEN,
|
||||||
|
province: props.row.province,
|
||||||
|
district: props.row.district,
|
||||||
|
subDistrict: props.row.subDistrict,
|
||||||
|
en: $i18n.locale === 'eng',
|
||||||
|
})
|
||||||
}}
|
}}
|
||||||
<q-tooltip>
|
<q-tooltip>
|
||||||
{{
|
{{
|
||||||
$i18n.locale === 'eng'
|
formatAddress({
|
||||||
? `${props.row.addressEN}, ${props.row.mooEN && `${$t('form.moo')} ${props.row.mooEN},`} ${props.row.soiEN && `${$t('form.soi')} ${props.row.soiEN},`} ${props.row.moo && `${props.row.streetEN} Rd,`} ${props.row.subDistrict?.nameEN}, ${props.row.district?.nameEN}, ${props.row.province?.nameEN} ${props.row.subDistrict?.zipCode}` ||
|
address: props.row.address,
|
||||||
'-'
|
addressEN: props.row.addressEN,
|
||||||
: `${props.row.address}, ${props.row.moo && `${$t('form.moo')} ${props.row.moo},`} ${props.row.soi && `${$t('form.soi')} ${props.row.soi},`} ${props.row.street && `${$t('form.road')} ${props.row.street},`} ${props.row.subDistrict?.name}, ${props.row.district?.name}, ${props.row.province?.name} ${props.row.subDistrict?.zipCode}` ||
|
moo: props.row.moo,
|
||||||
'-'
|
mooEN: props.row.mooEN,
|
||||||
|
soi: props.row.soi,
|
||||||
|
soiEN: props.row.soiEN,
|
||||||
|
street: props.row.street,
|
||||||
|
streetEN: props.row.streetEN,
|
||||||
|
province: props.row.province,
|
||||||
|
district: props.row.district,
|
||||||
|
subDistrict: props.row.subDistrict,
|
||||||
|
en: $i18n.locale === 'eng',
|
||||||
|
})
|
||||||
}}
|
}}
|
||||||
</q-tooltip>
|
</q-tooltip>
|
||||||
</span>
|
</span>
|
||||||
|
|
|
||||||
|
|
@ -81,14 +81,14 @@ export interface Attributes {
|
||||||
}[];
|
}[];
|
||||||
}
|
}
|
||||||
|
|
||||||
type PropString = {
|
export type PropString = {
|
||||||
type: 'string';
|
type: 'string';
|
||||||
fieldName: string;
|
fieldName: string;
|
||||||
isPhoneNumber: boolean;
|
isPhoneNumber: boolean;
|
||||||
phoneNumberLength: number;
|
phoneNumberLength: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
type PropNumber = {
|
export type PropNumber = {
|
||||||
type: 'number';
|
type: 'number';
|
||||||
fieldName: string;
|
fieldName: string;
|
||||||
comma: boolean;
|
comma: boolean;
|
||||||
|
|
@ -96,12 +96,12 @@ type PropNumber = {
|
||||||
decimalPlace: number;
|
decimalPlace: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
type PropDate = {
|
export type PropDate = {
|
||||||
type: 'date';
|
type: 'date';
|
||||||
fieldName: string;
|
fieldName: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
type PropOptions = {
|
export type PropOptions = {
|
||||||
type: 'array';
|
type: 'array';
|
||||||
fieldName: string;
|
fieldName: string;
|
||||||
options: string[];
|
options: string[];
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,9 @@
|
||||||
|
import {
|
||||||
|
PropDate,
|
||||||
|
PropNumber,
|
||||||
|
PropOptions,
|
||||||
|
PropString,
|
||||||
|
} from '../product-service/types';
|
||||||
import { CreatedBy, Status } from '../types';
|
import { CreatedBy, Status } from '../types';
|
||||||
|
|
||||||
export type WorkflowStep = {
|
export type WorkflowStep = {
|
||||||
|
|
@ -16,6 +22,7 @@ export type WorkflowStep = {
|
||||||
user: CreatedBy;
|
user: CreatedBy;
|
||||||
}[];
|
}[];
|
||||||
responsibleInstitution: string[];
|
responsibleInstitution: string[];
|
||||||
|
attributes: WorkFlowAttributes;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type WorkflowTemplate = {
|
export type WorkflowTemplate = {
|
||||||
|
|
@ -36,14 +43,7 @@ export type WorkflowTemplatePayload = {
|
||||||
registeredBranchId?: string;
|
registeredBranchId?: string;
|
||||||
status: Status;
|
status: Status;
|
||||||
name: string;
|
name: string;
|
||||||
step: {
|
step: WorkFlowPayloadStep[];
|
||||||
name: string;
|
|
||||||
type?: string | null;
|
|
||||||
detail?: string | null;
|
|
||||||
value?: string[];
|
|
||||||
responsiblePersonId?: string[];
|
|
||||||
responsibleInstitution?: string[];
|
|
||||||
}[];
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type WorkflowUserInTable = {
|
export type WorkflowUserInTable = {
|
||||||
|
|
@ -60,3 +60,17 @@ export type WorkflowUserInTable = {
|
||||||
code: string;
|
code: string;
|
||||||
}[];
|
}[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type WorkFlowAttributes = {
|
||||||
|
properties: (PropString | PropNumber | PropDate | PropOptions)[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type WorkFlowPayloadStep = {
|
||||||
|
name: string;
|
||||||
|
type?: string | null;
|
||||||
|
detail?: string | null;
|
||||||
|
value?: string[];
|
||||||
|
responsiblePersonId?: string[];
|
||||||
|
responsibleInstitution?: string[];
|
||||||
|
attributes: WorkFlowAttributes;
|
||||||
|
};
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue