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", "label": "Processing Fee",
"value": "processingFee" "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": "ค่าดำเนินงาน", "label": "ค่าดำเนินงาน",
"value": "processingFee" "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'; } from 'src/stores/workflow-template/types';
import { User } from 'src/stores/user/types'; import { User } from 'src/stores/user/types';
import SelectMenuWithSearch from '../shared/SelectMenuWithSearch.vue';
import SelectInput from '../shared/SelectInput.vue'; import SelectInput from '../shared/SelectInput.vue';
import ToggleButton from 'src/components/button/ToggleButton.vue'; import ToggleButton from 'src/components/button/ToggleButton.vue';
import { DeleteButton } from '../button'; import NoData from '../NoData.vue';
const props = defineProps<{ defineProps<{
readonly?: boolean; readonly?: boolean;
onDrawer?: boolean; onDrawer?: boolean;
}>(); }>();
const userStore = useUserStore(); const userStore = useUserStore();
const optionStore = useOptionStore(); const optionStore = useOptionStore();
const modelByArea = ref<boolean>(false); const modelByArea = ref<boolean>(false);
const userInTable = defineModel<WorkflowUserInTable[]>('userInTable', { 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 role = ref<string[]>([]);
const userList = ref<User[]>([]); const userList = ref<User[]>([]);
const responsiblePersonSearch = ref(''); const responsiblePersonSearch = ref('');
@ -56,6 +57,12 @@ const columns = [
label: 'flow.responsiblePerson', label: 'flow.responsiblePerson',
field: 'responsiblePerson', field: 'responsiblePerson',
}, },
{
name: 'responsiblePerson',
align: 'center',
label: 'general.agencies',
field: 'responsiblePerson',
},
{ {
name: 'action', name: 'action',
align: 'right', 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<{ defineEmits<{
(e: 'moveUp'): void; (e: 'moveUp'): void;
(e: 'moveDown'): void; (e: 'moveDown'): void;
@ -163,21 +195,25 @@ onMounted(async () => {
</span> </span>
</section> </section>
<section id="form-flow-template" class="col-12 row"> <section id="form-flow-template" class="col-12 row q-col-gutter-sm">
<SelectInput <SelectInput
v-if="role.includes('system')"
:readonly :readonly
v-model="registerBranchId" v-model="registerBranchId"
v-if="role.includes('system')" class="col-2"
class="col-12 q-pb-sm"
:option="userStore.userOption.hqOpts" :option="userStore.userOption.hqOpts"
:label="$t('branch.form.code')" :label="$t('branch.form.code')"
:rules="[
(val: string) =>
(role.includes('system') && !!val) || $t('form.error.required'),
]"
/> />
<q-input <q-input
:readonly :readonly
bg-color="transparent" bg-color="transparent"
outlined outlined
dense dense
class="col-12" class="col"
id="input-flow-template-name" id="input-flow-template-name"
v-model="flowData.name" v-model="flowData.name"
hide-bottom-space hide-bottom-space
@ -200,208 +236,220 @@ onMounted(async () => {
{{ $t(`flow.processStep`) }} {{ $t(`flow.processStep`) }}
</section> </section>
<section class="col-12"> <section
<q-table v-if="flowData.step.length === 0"
id="form-flow-step" class="col-12 surface-2 rounded bordered column items-center justify-center q-pa-md"
flat >
bordered <NoData class="col" />
hide-pagination </section>
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>
<template #body="props"> <section v-else class="col-12 q-gutter-y-md">
<q-tr> <template v-for="(step, index) in flowData.step" :key="index">
<q-td style="width: 60%"> <div class="bordered rounded">
<section class="row items-center"> <q-expansion-item
<q-btn for="item-up"
:id="`btn-up-${props.rowIndex}`" id="item-up"
:for="`btn-up-${props.rowIndex}`" dense
icon="mdi-arrow-up" switch-toggle-side
size="sm" default-opened
dense expand-icon="mdi-chevron-down-circle"
flat header-class="expansion-rounded surface-2"
round header-style="border-top-left-radius: var(--radius-2); border-top-right-radius: var(--radius-2)"
:disable="props.rowIndex === 0" >
style="color: hsl(var(--text-mute-2))" <template v-slot:header>
@click.stop=" <div class="column full-width">
moveItemUp(flowData.step, props.rowIndex); <div class="row items-center q-py-sm full-width" @click.stop>
moveItemUp(userInTable, props.rowIndex); <q-btn
" v-if="!readonly"
/> id="btn-work-up-product"
<q-btn for="btn-work-up-product"
:id="`btn-down-${props.rowIndex}`" icon="mdi-arrow-up"
:for="`btn-down-${props.rowIndex}`" dense
icon="mdi-arrow-down" flat
size="sm" round
dense :disable="index === 0"
flat style="color: hsl(var(--text-mute-2))"
round @click.stop="
class="q-mx-sm" moveItemUp(flowData.step, index);
:disable="props.rowIndex === flowData.step.length - 1" moveItemUp(userInTable, index);
style="color: hsl(var(--text-mute-2))" "
@click.stop=" />
moveItemDown(flowData.step, props.rowIndex); <q-btn
moveItemDown(userInTable, props.rowIndex); v-if="!readonly"
" id="btn-work-down-product"
/> for="btn-work-down-product"
icon="mdi-arrow-down"
<q-avatar dense
size="md" flat
class="q-mx-lg" round
style="background-color: var(--surface-tab)" class="q-mx-sm"
> :disable="index === flowData.step.length - 1"
{{ props.rowIndex + 1 }} style="color: hsl(var(--text-mute-2))"
</q-avatar> @click.stop="
moveItemDown(flowData.step, index);
<q-input moveItemDown(userInTable, index);
dense "
outlined />
:readonly <q-input
:id="`input-flow-step-name-${props.rowIndex}`" :bg-color="readonly ? 'transparent' : ''"
:for="`input-flow-step-name-${props.rowIndex}`" :prefix="`${$t('flow.stepNo')} ${index + 1}: `"
class="col" dense
:placeholder="$t('general.no', { msg: $t('flow.step') })" outlined
v-model="props.row.name" :readonly
hide-bottom-space :id="`input-flow-step-name-${index}`"
:rules="[(val: string) => !!val || $t('form.error.required')]" :for="`input-flow-step-name-${index}`"
/> class="col q-ml-md"
</section> :placeholder="$t('general.no', { msg: $t('flow.step') })"
</q-td> v-model="step.name"
hide-bottom-space
<q-td> :rules="[
<q-field @click.stop dense outlined :readonly> (val: string) => !!val || $t('form.error.required'),
<span ]"
v-if="props.row.responsiblePersonId.length === 0" />
class="app-text-muted row items-center col" <!-- <div
> :for="`select-work-name-${index + 1}`"
{{ $t('general.no', { msg: $t('flow.responsiblePerson') }) }} class="col q-py-sm q-px-md"
<q-icon name="mdi-chevron-down" class="q-ml-auto" /> style="background-color: var(--surface-1); z-index: 2"
</span> @click="() => (readonly ? '' : fetchListOfWork())"
<div v-else> >
<div class="row items-center no-wrap"> <span class="text-body2" style="color: var(--foreground)">
<q-avatar class="q-ml-sm" size="md"> {{ $t('productService.service.work') }} {{ index + 1 }} :
<q-img <span class="app-text-muted-2">
class="text-center" {{
:ratio="1" workName
:src="`${baseUrl}/user/${userInTable[props.rowIndex]?.responsiblePerson[0]?.id}/profile-image/${userInTable[props.rowIndex]?.responsiblePerson[0]?.selectedImage}`" ? workName
> : $t('productService.service.workName')
<template #error> }}
<div </span>
class="no-padding full-width full-height flex items-center justify-center" </span>
: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%);'}`" <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 <q-icon name="mdi-cog" size="xs" class="q-mr-sm" />
v-if=" {{ $t('general.manage') }}
userInTable[props.rowIndex] </q-btn>
?.responsiblePerson[0]?.gender </div>
" </q-item>
:src=" <q-item
userInTable[props.rowIndex] @click="workName = item.name"
?.responsiblePerson[0]?.gender === 'male' clickable
? '/no-img-man.png' v-for="(item, index) in workNameItems"
: '/no-img-female.png' :key="index"
"
/>
<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"
> >
<template #prepend> <div class="full-width flex items-center">
<q-icon name="mdi-magnify" /> <q-icon
</template> v-if="workName === item.name"
</q-input> name="mdi-checkbox-marked"
</q-item> size="xs"
<span class="text-caption app-text-muted-2 q-px-md"> color="primary"
{{ $t('general.people') }} class="q-mr-sm"
</span> />
<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 <section class="q-px-md surface-2 q-py-sm">
v-for="(person, i) in userList" <div
:key="i" :class="{ 'surface-1 rounded bordered': readonly }"
clickable style="border: 1px solid transparent"
class="column" >
@click.stop=" <div class="q-col-gutter-sm row">
selectResponsiblePerson(props.rowIndex, person) <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 $t('general.no', { msg: $t('flow.responsiblePerson') })
size="xs" }}
:model-value=" <q-icon
props.row.responsiblePersonId.includes(person.id) v-if="!readonly"
" name="mdi-menu-down"
@click.stop=" size="sm"
selectResponsiblePerson(props.rowIndex, person) 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-avatar class="q-ml-sm" size="md">
<q-img <q-img
class="text-center" class="text-center"
@ -431,7 +479,10 @@ onMounted(async () => {
</template> </template>
</q-img> </q-img>
</q-avatar> </q-avatar>
<div class="column q-pl-md"> <div
class="column q-pl-md"
style="color: var(--foreground)"
>
<span> <span>
{{ {{
`${optionStore.mapOption(person.namePrefix || '')} ${ `${optionStore.mapOption(person.namePrefix || '')} ${
@ -450,53 +501,217 @@ onMounted(async () => {
</span> </span>
</div> </div>
</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"> <q-item
{{ $t('general.group') }} v-for="(person, i) in userList"
</span> dense
<q-item clickable class="column"> :key="i"
<div class="row items-center"> clickable
<q-checkbox :model-value="false" size="xs"></q-checkbox> class="column"
<q-avatar class="q-ml-sm" size="md"> @click.stop="selectResponsiblePerson(index, person)"
<q-img :src="`/images/employee-avatar.png`" /> >
</q-avatar> <div class="row items-center no-wrap">
<div class="column q-pl-md"> <q-checkbox
<span>กลมคาโมมายด (Mocking)</span> size="xs"
</div> :model-value="
</div> step.responsiblePersonId.includes(person.id)
</q-item> "
@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"> <span class="text-caption app-text-muted-2 q-px-md">
{{ $t('general.area') }} {{ $t('general.group') }}
</span> </span>
<q-item clickable class="column"> <q-item clickable class="column">
<div class="row items-center"> <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 <q-checkbox
v-model="modelByArea" :model-value="
step.responsibleInstitution.some(
(v: string) => v === opt.value,
)
"
class="q-pr-sm"
size="xs" size="xs"
></q-checkbox> @click="selectItem(opt, step.responsibleInstitution)"
<div class="column q-pl-md"> />
<span>{{ $t('general.byArea') }}</span> <span
</div> :class="{
</div> 'app-text-info': step.responsibleInstitution.some(
</q-item> (v: string) => v === opt.value,
</q-list> ),
</q-menu> }"
</q-field> >
</q-td> {{ opt.label }}
</span>
<q-td style="width: 10%"> </template>
<DeleteButton </SelectMenuWithSearch>
v-if="!readonly" </q-select>
icon-only </div>
class="q-ml-auto" </div>
@click="deleteItem(flowData.step, props.rowIndex)" </section>
/> </q-expansion-item>
</q-td> </div>
</q-tr> </template>
</template>
</q-table>
</section> </section>
</div> </div>
</template> </template>
@ -506,4 +721,21 @@ onMounted(async () => {
height: 36px; height: 36px;
font-size: 12px; 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> </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', area: 'Area by area',
byArea: 'By area', byArea: 'By area',
company: 'Company', company: 'Company',
agencies: 'Agencies',
form: 'Form', form: 'Form',
designForm: 'design a form', designForm: 'design a form',
}, },

View file

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

View file

@ -52,8 +52,10 @@ defineEmits<{
async function addStep() { async function addStep() {
flowData.value.step.push({ flowData.value.step.push({
responsibleInstitution: [],
responsiblePersonId: [], responsiblePersonId: [],
value: [], value: [],
detail: '',
name: '', name: '',
}); });
@ -71,6 +73,7 @@ async function addStep() {
v-model:modal="model" v-model:modal="model"
:submit="() => $emit('submit')" :submit="() => $emit('submit')"
:close="() => $emit('close')" :close="() => $emit('close')"
hide-footer
> >
<div <div
class="col surface-1 rounded bordered scroll row relative-position" 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, '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 <section
class="col" class="col"
style="height: 100%; max-height: 100; overflow-y: auto" style="height: 100%; max-height: 100; overflow-y: auto"

View file

@ -211,9 +211,10 @@ function assignFormData(workflowData: WorkflowTemplate) {
return { return {
id: s.id, id: s.id,
name: s.name, name: s.name,
// type: s.type, detail: s.detail,
value: s.value || [], value: s.value || [],
responsiblePersonId: s.responsiblePerson.map((p) => p.userId), responsiblePersonId: s.responsiblePerson.map((p) => p.userId),
responsibleInstitution: s.responsibleInstitution,
}; };
}), }),
}; };
@ -395,6 +396,7 @@ watch(() => pageState.inputSearch, fetchWorkflowList);
hide-pagination hide-pagination
:columns="columns" :columns="columns"
:rows="workflowData" :rows="workflowData"
:rows-per-page-options="[0]"
> >
<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)">
@ -428,7 +430,6 @@ watch(() => pageState.inputSearch, fetchWorkflowList);
'status-active': props.row.status !== 'INACTIVE', 'status-active': props.row.status !== 'INACTIVE',
'status-inactive': props.row.status === 'INACTIVE', 'status-inactive': props.row.status === 'INACTIVE',
}" }"
:props="props"
:style=" :style="
props.rowIndex % 2 !== 0 props.rowIndex % 2 !== 0
? $q.dark.isActive ? $q.dark.isActive
@ -552,3 +553,16 @@ watch(() => pageState.inputSearch, fetchWorkflowList);
v-model:register-branch-id="registeredBranchId" v-model:register-branch-id="registeredBranchId"
/> />
</template> </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 globalOption = ref();
const rawOption = 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) { for (const category in globalOption.value) {
if (globalOption.value.hasOwnProperty(category)) { if (globalOption.value.hasOwnProperty(category)) {
const option = globalOption.value[category].find( const option = globalOption.value[category].find(

View file

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