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,117 +236,235 @@ 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
hide-pagination
class="full-width"
:columns
:rows="flowData.step"
:no-data-label="$t('general.noDataTable')"
:pagination="{
rowsPerPage: 0,
}"
> >
<template #header="{ cols }"> <NoData class="col" />
<q-tr style="background-color: hsla(var(--info-bg) / 0.07)"> </section>
<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
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 <q-btn
:id="`btn-up-${props.rowIndex}`" v-if="!readonly"
:for="`btn-up-${props.rowIndex}`" id="btn-work-up-product"
for="btn-work-up-product"
icon="mdi-arrow-up" icon="mdi-arrow-up"
size="sm"
dense dense
flat flat
round round
:disable="props.rowIndex === 0" :disable="index === 0"
style="color: hsl(var(--text-mute-2))" style="color: hsl(var(--text-mute-2))"
@click.stop=" @click.stop="
moveItemUp(flowData.step, props.rowIndex); moveItemUp(flowData.step, index);
moveItemUp(userInTable, props.rowIndex); moveItemUp(userInTable, index);
" "
/> />
<q-btn <q-btn
:id="`btn-down-${props.rowIndex}`" v-if="!readonly"
:for="`btn-down-${props.rowIndex}`" id="btn-work-down-product"
for="btn-work-down-product"
icon="mdi-arrow-down" icon="mdi-arrow-down"
size="sm"
dense dense
flat flat
round round
class="q-mx-sm" class="q-mx-sm"
:disable="props.rowIndex === flowData.step.length - 1" :disable="index === flowData.step.length - 1"
style="color: hsl(var(--text-mute-2))" style="color: hsl(var(--text-mute-2))"
@click.stop=" @click.stop="
moveItemDown(flowData.step, props.rowIndex); moveItemDown(flowData.step, index);
moveItemDown(userInTable, props.rowIndex); moveItemDown(userInTable, index);
" "
/> />
<q-avatar
size="md"
class="q-mx-lg"
style="background-color: var(--surface-tab)"
>
{{ props.rowIndex + 1 }}
</q-avatar>
<q-input <q-input
:bg-color="readonly ? 'transparent' : ''"
:prefix="`${$t('flow.stepNo')} ${index + 1}: `"
dense dense
outlined outlined
:readonly :readonly
:id="`input-flow-step-name-${props.rowIndex}`" :id="`input-flow-step-name-${index}`"
:for="`input-flow-step-name-${props.rowIndex}`" :for="`input-flow-step-name-${index}`"
class="col" class="col q-ml-md"
:placeholder="$t('general.no', { msg: $t('flow.step') })" :placeholder="$t('general.no', { msg: $t('flow.step') })"
v-model="props.row.name" v-model="step.name"
hide-bottom-space hide-bottom-space
:rules="[(val: string) => !!val || $t('form.error.required')]" :rules="[
(val: string) => !!val || $t('form.error.required'),
]"
/> />
</section> <!-- <div
</q-td> :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-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"
>
<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-td> <section class="q-px-md surface-2 q-py-sm">
<q-field @click.stop dense outlined :readonly> <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 <span
v-if="props.row.responsiblePersonId.length === 0" v-if="step.responsiblePersonId.length === 0"
class="app-text-muted row items-center col" 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" /> $t('general.no', { msg: $t('flow.responsiblePerson') })
}}
<q-icon
v-if="!readonly"
name="mdi-menu-down"
size="sm"
class="q-ml-auto"
/>
</span> </span>
<div v-else> <div v-else>
<div class="row items-center no-wrap"> <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"
:ratio="1" :ratio="1"
:src="`${baseUrl}/user/${userInTable[props.rowIndex]?.responsiblePerson[0]?.id}/profile-image/${userInTable[props.rowIndex]?.responsiblePerson[0]?.selectedImage}`" :src="`${baseUrl}/user/${person.id}/profile-image/${person.selectedImage}`"
> >
<template #error> <template #error>
<div <div
class="no-padding full-width full-height flex items-center justify-center" 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%);'}`" :style="`${person.gender ? 'background: white' : 'background: linear-gradient(135deg,rgba(43, 137, 223, 1) 0%, rgba(230, 51, 81, 1) 100%);'}`"
> >
<q-img <q-img
v-if=" v-if="person.gender"
userInTable[props.rowIndex]
?.responsiblePerson[0]?.gender
"
:src=" :src="
userInTable[props.rowIndex] person.gender === 'male'
?.responsiblePerson[0]?.gender === 'male'
? '/no-img-man.png' ? '/no-img-man.png'
: '/no-img-female.png' : '/no-img-female.png'
" "
@ -331,34 +485,21 @@ onMounted(async () => {
> >
<span> <span>
{{ {{
`${optionStore.mapOption(userInTable[props.rowIndex]?.responsiblePerson[0]?.namePrefix || '')} ${ `${optionStore.mapOption(person.namePrefix || '')} ${
$i18n.locale === 'eng' $i18n.locale === 'eng'
? userInTable[props.rowIndex] ? person.firstNameEN
?.responsiblePerson[0]?.firstNameEN : person.firstName
: userInTable[props.rowIndex]
?.responsiblePerson[0]?.firstName
} ${ } ${
$i18n.locale === 'eng' $i18n.locale === 'eng'
? userInTable[props.rowIndex] ? person.lastNameEN
?.responsiblePerson[0]?.lastNameEN : person.lastName
: userInTable[props.rowIndex]
?.responsiblePerson[0]?.lastName
}` }`
}} }}
</span> </span>
<span class="text-caption app-text-muted"> <span class="text-caption app-text-muted">
{{ {{ person.code }}
userInTable[props.rowIndex]?.responsiblePerson[0]
?.code
}}
</span> </span>
</div> </div>
<span
class="q-pl-md"
v-if="props.row.responsiblePersonId.length > 1"
>
({{ props.row.responsiblePersonId.length }})
</span>
</div> </div>
</div> </div>
<q-menu v-if="!readonly" :offset="[0, 4]"> <q-menu v-if="!readonly" :offset="[0, 4]">
@ -385,21 +526,20 @@ onMounted(async () => {
<q-item <q-item
v-for="(person, i) in userList" v-for="(person, i) in userList"
dense
:key="i" :key="i"
clickable clickable
class="column" class="column"
@click.stop=" @click.stop="selectResponsiblePerson(index, person)"
selectResponsiblePerson(props.rowIndex, person)
"
> >
<div class="row items-center no-wrap"> <div class="row items-center no-wrap">
<q-checkbox <q-checkbox
size="xs" size="xs"
:model-value=" :model-value="
props.row.responsiblePersonId.includes(person.id) step.responsiblePersonId.includes(person.id)
" "
@click.stop=" @click.stop="
selectResponsiblePerson(props.rowIndex, person) selectResponsiblePerson(index, person)
" "
/> />
<q-avatar class="q-ml-sm" size="md"> <q-avatar class="q-ml-sm" size="md">
@ -457,12 +597,15 @@ onMounted(async () => {
</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-checkbox
:model-value="false"
size="xs"
></q-checkbox>
<q-avatar class="q-ml-sm" size="md"> <q-avatar class="q-ml-sm" size="md">
<q-img :src="`/images/employee-avatar.png`" /> <q-img :src="`/images/employee-avatar.png`" />
</q-avatar> </q-avatar>
<div class="column q-pl-md"> <div class="column q-pl-md">
<span>กลคาโมมายด (Mocking)</span> <span>กล 1 (Mocking)</span>
</div> </div>
</div> </div>
</q-item> </q-item>
@ -470,7 +613,11 @@ onMounted(async () => {
<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.area') }}
</span> </span>
<q-item clickable class="column"> <q-item
clickable
@click="modelByArea = !modelByArea"
class="column"
>
<div class="row items-center"> <div class="row items-center">
<q-checkbox <q-checkbox
v-model="modelByArea" v-model="modelByArea"
@ -484,19 +631,87 @@ onMounted(async () => {
</q-list> </q-list>
</q-menu> </q-menu>
</q-field> </q-field>
</q-td>
<q-td style="width: 10%"> <q-select
<DeleteButton :bg-color="readonly ? 'transparent' : ''"
v-if="!readonly" :readonly
icon-only outlined
class="q-ml-auto" dense
@click="deleteItem(flowData.step, props.rowIndex)" v-model="step.responsibleInstitution"
/> multiple
</q-td> :options="options"
</q-tr> 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
:model-value="
step.responsibleInstitution.some(
(v: string) => v === opt.value,
)
"
class="q-pr-sm"
size="xs"
@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> </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[];
}[]; }[];
}; };