feat: add workflow template fields (#63)

* feat: add type detail

* feat: option => agencies type

* feat: agencies i18n

* fix: workflow => add institution type

* refactor: map option specific category, key

* feat: select menu with search components

* feat: workflow => add field step description & agencies

* fix: workflow => table page

* refactor: workflow => floating dialog submit

---------

Co-authored-by: Methapon Metanipat <methapon@frappet.com>
This commit is contained in:
puriphatt 2024-11-07 18:09:00 +07:00 committed by GitHub
parent 55f6a5f84e
commit 9708258e7a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 708 additions and 251 deletions

View file

@ -958,6 +958,45 @@
"label": "Processing Fee",
"value": "processingFee"
}
],
"agenciesType": [
{
"label": "Agency",
"value": "AGE"
},
{
"label": "Department of Employment",
"value": "DOE"
},
{
"label": "Dhipaya Insurance",
"value": "INS"
},
{
"label": "Embassy",
"value": "EMB"
},
{
"label": "Government Agencies",
"value": "GA"
},
{
"label": "Hospital",
"value": "HOS"
},
{
"label": "Immigration Bureau",
"value": "IMB"
},
{
"label": "Immigration Checkpoint",
"value": "IMC"
},
{
"label": "Ministry of Labour",
"value": "MOL"
}
]
},
@ -1919,6 +1958,45 @@
"label": "ค่าดำเนินงาน",
"value": "processingFee"
}
],
"agenciesType": [
{
"label": "เอเจนซี่ / หน่วยงาน",
"value": "AGE"
},
{
"label": "จัดหางานพื้นที่",
"value": "DOE"
},
{
"label": "ทิพยประกันภัย",
"value": "INS"
},
{
"label": "สถานทูต",
"value": "EMB"
},
{
"label": "หน่วยงานราชการ",
"value": "GA"
},
{
"label": "โรงพยาบาล",
"value": "HOS"
},
{
"label": "สำนักงานตรวจคนเข้าเมือง",
"value": "IMB"
},
{
"label": "ด่านตรวจคนเข้าเมือง",
"value": "IMC"
},
{
"label": "กรมแรงงาน",
"value": "MOL"
}
]
}
}

View file

@ -13,18 +13,18 @@ import {
} from 'src/stores/workflow-template/types';
import { User } from 'src/stores/user/types';
import SelectMenuWithSearch from '../shared/SelectMenuWithSearch.vue';
import SelectInput from '../shared/SelectInput.vue';
import ToggleButton from 'src/components/button/ToggleButton.vue';
import { DeleteButton } from '../button';
import NoData from '../NoData.vue';
const props = defineProps<{
defineProps<{
readonly?: boolean;
onDrawer?: boolean;
}>();
const userStore = useUserStore();
const optionStore = useOptionStore();
const modelByArea = ref<boolean>(false);
const userInTable = defineModel<WorkflowUserInTable[]>('userInTable', {
@ -40,6 +40,7 @@ const flowData = defineModel<WorkflowTemplatePayload>('flowData', {
},
});
const options = ref(optionStore.globalOption?.agenciesType);
const role = ref<string[]>([]);
const userList = ref<User[]>([]);
const responsiblePersonSearch = ref('');
@ -56,6 +57,12 @@ const columns = [
label: 'flow.responsiblePerson',
field: 'responsiblePerson',
},
{
name: 'responsiblePerson',
align: 'center',
label: 'general.agencies',
field: 'responsiblePerson',
},
{
name: 'action',
align: 'right',
@ -112,6 +119,31 @@ function selectResponsiblePerson(stepIndex: number, responsiblePerson: User) {
}
}
function selectItem(
val: Record<string, unknown>,
responsibleInstitution?: string[],
) {
if (!responsibleInstitution) return;
const existIndex = responsibleInstitution.findIndex((p) => p === val.value);
if (existIndex === -1) {
responsibleInstitution.push(val.value as string);
} else {
responsibleInstitution.splice(Number(existIndex), 1);
}
}
function optionSearch(val: string | null) {
if (val === '') {
options.value = optionStore.globalOption?.agenciesType;
return;
}
const needle = val ? val.toLowerCase() : '';
options.value = optionStore.globalOption?.agenciesType.filter(
(v: { label: string }) => v.label.toLowerCase().indexOf(needle) > -1,
);
}
defineEmits<{
(e: 'moveUp'): void;
(e: 'moveDown'): void;
@ -163,21 +195,25 @@ onMounted(async () => {
</span>
</section>
<section id="form-flow-template" class="col-12 row">
<section id="form-flow-template" class="col-12 row q-col-gutter-sm">
<SelectInput
v-if="role.includes('system')"
:readonly
v-model="registerBranchId"
v-if="role.includes('system')"
class="col-12 q-pb-sm"
class="col-2"
:option="userStore.userOption.hqOpts"
:label="$t('branch.form.code')"
:rules="[
(val: string) =>
(role.includes('system') && !!val) || $t('form.error.required'),
]"
/>
<q-input
:readonly
bg-color="transparent"
outlined
dense
class="col-12"
class="col"
id="input-flow-template-name"
v-model="flowData.name"
hide-bottom-space
@ -200,208 +236,220 @@ onMounted(async () => {
{{ $t(`flow.processStep`) }}
</section>
<section class="col-12">
<q-table
id="form-flow-step"
flat
bordered
hide-pagination
class="full-width"
:columns
:rows="flowData.step"
:no-data-label="$t('general.noDataTable')"
:pagination="{
rowsPerPage: 0,
}"
>
<template #header="{ cols }">
<q-tr style="background-color: hsla(var(--info-bg) / 0.07)">
<q-th v-for="v in cols" :key="v">
{{ v.label && $t(v.label, { msg: $t('flow.step') }) }}
</q-th>
</q-tr>
</template>
<section
v-if="flowData.step.length === 0"
class="col-12 surface-2 rounded bordered column items-center justify-center q-pa-md"
>
<NoData class="col" />
</section>
<template #body="props">
<q-tr>
<q-td style="width: 60%">
<section class="row items-center">
<q-btn
:id="`btn-up-${props.rowIndex}`"
:for="`btn-up-${props.rowIndex}`"
icon="mdi-arrow-up"
size="sm"
dense
flat
round
:disable="props.rowIndex === 0"
style="color: hsl(var(--text-mute-2))"
@click.stop="
moveItemUp(flowData.step, props.rowIndex);
moveItemUp(userInTable, props.rowIndex);
"
/>
<q-btn
:id="`btn-down-${props.rowIndex}`"
:for="`btn-down-${props.rowIndex}`"
icon="mdi-arrow-down"
size="sm"
dense
flat
round
class="q-mx-sm"
:disable="props.rowIndex === flowData.step.length - 1"
style="color: hsl(var(--text-mute-2))"
@click.stop="
moveItemDown(flowData.step, props.rowIndex);
moveItemDown(userInTable, props.rowIndex);
"
/>
<q-avatar
size="md"
class="q-mx-lg"
style="background-color: var(--surface-tab)"
>
{{ props.rowIndex + 1 }}
</q-avatar>
<q-input
dense
outlined
:readonly
:id="`input-flow-step-name-${props.rowIndex}`"
:for="`input-flow-step-name-${props.rowIndex}`"
class="col"
:placeholder="$t('general.no', { msg: $t('flow.step') })"
v-model="props.row.name"
hide-bottom-space
:rules="[(val: string) => !!val || $t('form.error.required')]"
/>
</section>
</q-td>
<q-td>
<q-field @click.stop dense outlined :readonly>
<span
v-if="props.row.responsiblePersonId.length === 0"
class="app-text-muted row items-center col"
>
{{ $t('general.no', { msg: $t('flow.responsiblePerson') }) }}
<q-icon name="mdi-chevron-down" class="q-ml-auto" />
</span>
<div v-else>
<div class="row items-center no-wrap">
<q-avatar class="q-ml-sm" size="md">
<q-img
class="text-center"
:ratio="1"
:src="`${baseUrl}/user/${userInTable[props.rowIndex]?.responsiblePerson[0]?.id}/profile-image/${userInTable[props.rowIndex]?.responsiblePerson[0]?.selectedImage}`"
>
<template #error>
<div
class="no-padding full-width full-height flex items-center justify-center"
:style="`${userInTable[props.rowIndex]?.responsiblePerson[0].gender ? 'background: white' : 'background: linear-gradient(135deg,rgba(43, 137, 223, 1) 0%, rgba(230, 51, 81, 1) 100%);'}`"
<section v-else class="col-12 q-gutter-y-md">
<template v-for="(step, index) in flowData.step" :key="index">
<div class="bordered rounded">
<q-expansion-item
for="item-up"
id="item-up"
dense
switch-toggle-side
default-opened
expand-icon="mdi-chevron-down-circle"
header-class="expansion-rounded surface-2"
header-style="border-top-left-radius: var(--radius-2); border-top-right-radius: var(--radius-2)"
>
<template v-slot:header>
<div class="column full-width">
<div class="row items-center q-py-sm full-width" @click.stop>
<q-btn
v-if="!readonly"
id="btn-work-up-product"
for="btn-work-up-product"
icon="mdi-arrow-up"
dense
flat
round
:disable="index === 0"
style="color: hsl(var(--text-mute-2))"
@click.stop="
moveItemUp(flowData.step, index);
moveItemUp(userInTable, index);
"
/>
<q-btn
v-if="!readonly"
id="btn-work-down-product"
for="btn-work-down-product"
icon="mdi-arrow-down"
dense
flat
round
class="q-mx-sm"
:disable="index === flowData.step.length - 1"
style="color: hsl(var(--text-mute-2))"
@click.stop="
moveItemDown(flowData.step, index);
moveItemDown(userInTable, index);
"
/>
<q-input
:bg-color="readonly ? 'transparent' : ''"
:prefix="`${$t('flow.stepNo')} ${index + 1}: `"
dense
outlined
:readonly
:id="`input-flow-step-name-${index}`"
:for="`input-flow-step-name-${index}`"
class="col q-ml-md"
:placeholder="$t('general.no', { msg: $t('flow.step') })"
v-model="step.name"
hide-bottom-space
:rules="[
(val: string) => !!val || $t('form.error.required'),
]"
/>
<!-- <div
:for="`select-work-name-${index + 1}`"
class="col q-py-sm q-px-md"
style="background-color: var(--surface-1); z-index: 2"
@click="() => (readonly ? '' : fetchListOfWork())"
>
<span class="text-body2" style="color: var(--foreground)">
{{ $t('productService.service.work') }} {{ index + 1 }} :
<span class="app-text-muted-2">
{{
workName
? workName
: $t('productService.service.workName')
}}
</span>
</span>
<q-menu
v-if="!readonly"
fit
ref="refMenu"
self="top left"
anchor="bottom left"
>
<q-item>
<div
class="full-width flex items-center justify-between"
>
{{ $t('productService.service.workName') }}
<q-btn
dense
unelevated
class="bordered q-px-sm text-capitalize"
style="
border-radius: var(--radius-2);
color: hsl(var(--info-bg));
"
@click.stop="
() => {
refMenu.hide();
$emit('manageWorkName');
}
"
>
<q-img
v-if="
userInTable[props.rowIndex]
?.responsiblePerson[0]?.gender
"
:src="
userInTable[props.rowIndex]
?.responsiblePerson[0]?.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(userInTable[props.rowIndex]?.responsiblePerson[0]?.namePrefix || '')} ${
$i18n.locale === 'eng'
? userInTable[props.rowIndex]
?.responsiblePerson[0]?.firstNameEN
: userInTable[props.rowIndex]
?.responsiblePerson[0]?.firstName
} ${
$i18n.locale === 'eng'
? userInTable[props.rowIndex]
?.responsiblePerson[0]?.lastNameEN
: userInTable[props.rowIndex]
?.responsiblePerson[0]?.lastName
}`
}}
</span>
<span class="text-caption app-text-muted">
{{
userInTable[props.rowIndex]?.responsiblePerson[0]
?.code
}}
</span>
</div>
<span
class="q-pl-md"
v-if="props.row.responsiblePersonId.length > 1"
>
({{ props.row.responsiblePersonId.length }})
</span>
</div>
</div>
<q-menu v-if="!readonly" :offset="[0, 4]">
<q-list>
<q-item>
<q-input
for="input-search"
outlined
dense
:label="$t('general.search')"
class="col responsible-search"
:bg-color="$q.dark.isActive ? 'dark' : 'white'"
v-model="responsiblePersonSearch"
debounce="200"
<q-icon name="mdi-cog" size="xs" class="q-mr-sm" />
{{ $t('general.manage') }}
</q-btn>
</div>
</q-item>
<q-item
@click="workName = item.name"
clickable
v-for="(item, index) in workNameItems"
:key="index"
>
<template #prepend>
<q-icon name="mdi-magnify" />
</template>
</q-input>
</q-item>
<span class="text-caption app-text-muted-2 q-px-md">
{{ $t('general.people') }}
</span>
<div class="full-width flex items-center">
<q-icon
v-if="workName === item.name"
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"
/>
{{ item.name }}
</div>
</q-item>
</q-menu>
</div> -->
<q-btn
v-if="!readonly"
id="btn-delete-work"
icon="mdi-trash-can-outline"
dense
flat
round
padding="0"
class="q-ml-md"
color="negative"
@click="deleteItem(flowData.step, index)"
>
<q-tooltip>{{ $t('general.delete') }}</q-tooltip>
</q-btn>
</div>
</div>
</template>
<q-item
v-for="(person, i) in userList"
:key="i"
clickable
class="column"
@click.stop="
selectResponsiblePerson(props.rowIndex, person)
"
<section class="q-px-md surface-2 q-py-sm">
<div
:class="{ 'surface-1 rounded bordered': readonly }"
style="border: 1px solid transparent"
>
<div class="q-col-gutter-sm row">
<q-input
:bg-color="readonly ? 'transparent' : ''"
:readonly
v-model="step.detail"
class="col-12"
type="textarea"
dense
outlined
:label="$t('general.detail')"
/>
<q-field
:bg-color="readonly ? 'transparent' : ''"
v-if="step.responsiblePersonId"
stack-label
:label="
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"
>
<div class="row items-center no-wrap">
<q-checkbox
size="xs"
:model-value="
props.row.responsiblePersonId.includes(person.id)
"
@click.stop="
selectResponsiblePerson(props.rowIndex, person)
"
/>
{{
$t('general.no', { msg: $t('flow.responsiblePerson') })
}}
<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"
@ -431,7 +479,10 @@ onMounted(async () => {
</template>
</q-img>
</q-avatar>
<div class="column q-pl-md">
<div
class="column q-pl-md"
style="color: var(--foreground)"
>
<span>
{{
`${optionStore.mapOption(person.namePrefix || '')} ${
@ -450,53 +501,217 @@ onMounted(async () => {
</span>
</div>
</div>
</q-item>
</div>
<q-menu v-if="!readonly" :offset="[0, 4]">
<q-list>
<q-item>
<q-input
for="input-search"
outlined
dense
:label="$t('general.search')"
class="col responsible-search"
:bg-color="$q.dark.isActive ? 'dark' : 'white'"
v-model="responsiblePersonSearch"
debounce="200"
>
<template #prepend>
<q-icon name="mdi-magnify" />
</template>
</q-input>
</q-item>
<span class="text-caption app-text-muted-2 q-px-md">
{{ $t('general.people') }}
</span>
<span class="text-caption app-text-muted-2 q-px-md">
{{ $t('general.group') }}
</span>
<q-item clickable class="column">
<div class="row items-center">
<q-checkbox :model-value="false" size="xs"></q-checkbox>
<q-avatar class="q-ml-sm" size="md">
<q-img :src="`/images/employee-avatar.png`" />
</q-avatar>
<div class="column q-pl-md">
<span>กลมคาโมมายด (Mocking)</span>
</div>
</div>
</q-item>
<q-item
v-for="(person, i) in userList"
dense
:key="i"
clickable
class="column"
@click.stop="selectResponsiblePerson(index, person)"
>
<div class="row items-center no-wrap">
<q-checkbox
size="xs"
:model-value="
step.responsiblePersonId.includes(person.id)
"
@click.stop="
selectResponsiblePerson(index, person)
"
/>
<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">
<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>
</q-item>
<span class="text-caption app-text-muted-2 q-px-md">
{{ $t('general.area') }}
</span>
<q-item clickable class="column">
<div class="row items-center">
<span class="text-caption app-text-muted-2 q-px-md">
{{ $t('general.group') }}
</span>
<q-item clickable class="column">
<div class="row items-center">
<q-checkbox
:model-value="false"
size="xs"
></q-checkbox>
<q-avatar class="q-ml-sm" size="md">
<q-img :src="`/images/employee-avatar.png`" />
</q-avatar>
<div class="column q-pl-md">
<span>กล 1 (Mocking)</span>
</div>
</div>
</q-item>
<span class="text-caption app-text-muted-2 q-px-md">
{{ $t('general.area') }}
</span>
<q-item
clickable
@click="modelByArea = !modelByArea"
class="column"
>
<div class="row items-center">
<q-checkbox
v-model="modelByArea"
size="xs"
></q-checkbox>
<div class="column q-pl-md">
<span>{{ $t('general.byArea') }}</span>
</div>
</div>
</q-item>
</q-list>
</q-menu>
</q-field>
<q-select
:bg-color="readonly ? 'transparent' : ''"
:readonly
outlined
dense
v-model="step.responsibleInstitution"
multiple
:options="options"
option-label="label"
option-value="value"
emit-value
:label="
$t('general.select', { msg: $t('general.agencies') })
"
class="col-6"
:hide-dropdown-icon="readonly"
>
<template v-slot:selected-item="scope">
<q-chip
dense
removable
@remove="scope.removeAtIndex(scope.index)"
>
<span class="text-caption">
{{ optionStore.mapOption(scope.opt, 'agenciesType') }}
</span>
</q-chip>
</template>
<template v-slot:option></template>
<SelectMenuWithSearch
v-if="!readonly"
:title="
$t('general.select', { msg: $t('general.agencies') })
"
:option="options"
width="353.66px"
@search="(v) => optionSearch(v as string)"
@select="
(v) => selectItem(v, step.responsibleInstitution)
"
@before-show="
options = optionStore.globalOption?.agenciesType
"
>
<template #prepend>
<q-separator></q-separator>
</template>
<template
#option="{ opt }"
v-if="step.responsibleInstitution"
>
<q-checkbox
v-model="modelByArea"
:model-value="
step.responsibleInstitution.some(
(v: string) => v === opt.value,
)
"
class="q-pr-sm"
size="xs"
></q-checkbox>
<div class="column q-pl-md">
<span>{{ $t('general.byArea') }}</span>
</div>
</div>
</q-item>
</q-list>
</q-menu>
</q-field>
</q-td>
<q-td style="width: 10%">
<DeleteButton
v-if="!readonly"
icon-only
class="q-ml-auto"
@click="deleteItem(flowData.step, props.rowIndex)"
/>
</q-td>
</q-tr>
</template>
</q-table>
@click="selectItem(opt, step.responsibleInstitution)"
/>
<span
:class="{
'app-text-info': step.responsibleInstitution.some(
(v: string) => v === opt.value,
),
}"
>
{{ opt.label }}
</span>
</template>
</SelectMenuWithSearch>
</q-select>
</div>
</div>
</section>
</q-expansion-item>
</div>
</template>
</section>
</div>
</template>
@ -506,4 +721,21 @@ onMounted(async () => {
height: 36px;
font-size: 12px;
}
:deep(i.q-icon.mdi.mdi-chevron-down-circle.q-expansion-item__toggle-icon) {
color: hsl(var(--text-mute));
}
:deep(
i.q-icon.mdi.mdi-chevron-down-circle.q-expansion-item__toggle-icon.q-expansion-item__toggle-icon--rotated
) {
color: var(--brand-1);
}
:deep(
.q-item.q-item-type.row.no-wrap.q-item--dense.q-item--clickable.q-link.cursor-pointer.q-focusable.q-hoverable.expansion-rounded.surface-2
.q-focus-helper
) {
visibility: hidden;
}
</style>

View file

@ -0,0 +1,105 @@
<script lang="ts" setup>
import { ref } from 'vue';
const props = withDefaults(
defineProps<{
readonly?: boolean;
title?: string;
width?: string;
offset?: number[];
option: Record<string, unknown>[];
optionLabel?: string;
}>(),
{
readonly: false,
option: () => [],
optionLabel: 'label',
offset: () => [0, 12],
},
);
const inputSearch = ref('');
defineEmits<{
(e: 'search', value: string | number | null): void;
(e: 'select', value: Record<string, unknown>): void;
(e: 'hide'): void;
(e: 'show'): void;
(e: 'beforeHide'): void;
(e: 'beforeShow'): void;
}>();
</script>
<template>
<q-menu
v-if="!readonly"
:offset
class="bordered"
:style="`width: ${width}`"
@show="$emit('show')"
@hide="$emit('hide')"
@before-show="
() => {
inputSearch = '';
$emit('beforeShow');
}
"
@before-hide="$emit('beforeHide')"
>
<div
v-if="title"
class="column no-padding"
style="padding: 16px !important"
>
<span class="text-weight-bold q-pb-sm">
{{ title }}
</span>
<q-input
for="input-search"
outlined
dense
:label="$t('general.search')"
class="col responsible-search"
:bg-color="$q.dark.isActive ? 'dark' : 'white'"
v-model="inputSearch"
debounce="200"
@update:model-value="(val) => $emit('search', val)"
>
<template #prepend>
<q-icon name="mdi-magnify" />
</template>
</q-input>
</div>
<template v-if="$slots.prepend">
<slot name="prepend"></slot>
</template>
<span v-if="option.length === 0">
<q-item dense class="flex items-center app-text-muted">
{{ $t('general.noData') }}
</q-item>
</span>
<template v-if="option.length > 0">
<q-item
v-for="(opt, i) in option"
dense
:key="i"
class="flex items-center"
clickable
@click.stop="$emit('select', opt)"
>
<template v-if="$slots.option">
<slot name="option" :opt="opt"></slot>
</template>
<span v-if="!$slots.option" class="row items-center">
{{ opt.label }}
</span>
</q-item>
</template>
</q-menu>
</template>
<style scoped></style>

View file

@ -120,6 +120,7 @@ export default {
area: 'Area by area',
byArea: 'By area',
company: 'Company',
agencies: 'Agencies',
form: 'Form',
designForm: 'design a form',
},

View file

@ -120,6 +120,7 @@ export default {
area: 'เขตพื้นที่',
byArea: 'ตามเขตพื้นที่',
company: 'บริษัท',
agencies: 'หน่วยงาน',
form: 'แบบฟอร์ม',
designForm: 'ออกแบบฟอร์ม',
},

View file

@ -52,8 +52,10 @@ defineEmits<{
async function addStep() {
flowData.value.step.push({
responsibleInstitution: [],
responsiblePersonId: [],
value: [],
detail: '',
name: '',
});
@ -71,6 +73,7 @@ async function addStep() {
v-model:modal="model"
:submit="() => $emit('submit')"
:close="() => $emit('close')"
hide-footer
>
<div
class="col surface-1 rounded bordered scroll row relative-position"
@ -79,6 +82,18 @@ async function addStep() {
'q-mx-md q-my-sm': !$q.screen.gt.sm,
}"
>
<div
class="rounded row"
style="position: absolute; z-index: 999; right: 0; top: 0"
:class="{
'q-py-md q-px-lg': $q.screen.gt.sm,
'q-py-sm q-px-lg': !$q.screen.gt.sm,
}"
>
<div class="surface-1 row rounded">
<SaveButton id="btn-info-basic-save" icon-only type="submit" />
</div>
</div>
<section
class="col"
style="height: 100%; max-height: 100; overflow-y: auto"

View file

@ -211,9 +211,10 @@ function assignFormData(workflowData: WorkflowTemplate) {
return {
id: s.id,
name: s.name,
// type: s.type,
detail: s.detail,
value: s.value || [],
responsiblePersonId: s.responsiblePerson.map((p) => p.userId),
responsibleInstitution: s.responsibleInstitution,
};
}),
};
@ -395,6 +396,7 @@ watch(() => pageState.inputSearch, fetchWorkflowList);
hide-pagination
:columns="columns"
:rows="workflowData"
:rows-per-page-options="[0]"
>
<template #header="{ cols }">
<q-tr style="background-color: hsla(var(--info-bg) / 0.07)">
@ -428,7 +430,6 @@ watch(() => pageState.inputSearch, fetchWorkflowList);
'status-active': props.row.status !== 'INACTIVE',
'status-inactive': props.row.status === 'INACTIVE',
}"
:props="props"
:style="
props.rowIndex % 2 !== 0
? $q.dark.isActive
@ -552,3 +553,16 @@ watch(() => pageState.inputSearch, fetchWorkflowList);
v-model:register-branch-id="registeredBranchId"
/>
</template>
<style scoped>
.status-active {
--_branch-status-color: var(--green-6-hsl);
}
.status-inactive {
--_branch-status-color: var(--stone-5-hsl);
--_branch-badge-bg: var(--stone-5-hsl);
filter: grayscale(0.5);
opacity: 0.5;
}
</style>

View file

@ -7,7 +7,13 @@ const useOptionStore = defineStore('optionStore', () => {
const globalOption = ref();
const rawOption = ref();
function mapOption(value: string) {
function mapOption(value: string, categoryKey?: string) {
if (categoryKey) {
const option = globalOption.value[categoryKey].find(
(opt: { value: string }) => opt.value === value,
);
if (option) return option.label;
}
for (const category in globalOption.value) {
if (globalOption.value.hasOwnProperty(category)) {
const option = globalOption.value[category].find(

View file

@ -4,6 +4,7 @@ export type WorkflowStep = {
id: string;
order: number;
name: string;
detail?: string;
type: string | null;
// NOTE: for list like type only
@ -14,11 +15,13 @@ export type WorkflowStep = {
userId: string;
user: CreatedBy;
}[];
responsibleInstitution: string[];
};
export type WorkflowTemplate = {
id: string;
name: string;
detail: string | null;
registeredBranchId: string;
step: WorkflowStep[];
status: Status;
@ -36,8 +39,10 @@ export type WorkflowTemplatePayload = {
step: {
name: string;
type?: string | null;
detail?: string | null;
value?: string[];
responsiblePersonId?: string[];
responsibleInstitution?: string[];
}[];
};