feat: task order (#141)
* feat: task order => routes * feat: Page * refactor: pagination * refactor: taskOrder => table, card and constants * feat: add structure select request list comp * fix: re-export type * refactor: edit path route of task order * feat: trigger task order * refactor: edit type task statss * feat: table select request list * feat: i18n * refactor: quasar expansion chevron color * refactor: type * refactor: state btn status done * feat: task order => order view layout * feat: task order => remark expansion * fix: task order => rename attachment to additional file * feat: upload file section optional layout * feat: task order => additional file expansion * feat: task order => payment expansion * feat: conditionally add urgent * feat: send id together with link * refactor: edit type * feat: new form.ts * refactor: edit url * refactor: edit id trigger * feat: select institution component * feat: task order code i18n * feat: task order => document expansion form * feat: fallback address on null * refactor: add type for table * feat: add filter parameter * refactor: edit name routes * refactor: add type of task order payload * refactor: by value form * refactor: responsive quotation form info * refactor: submit form * refactor: add i18n * refactor: status canceled * refactor: handle task status * refactor: handle mode view * refactor: addtaskstatus * refactor: i18n & constants * refactor: table employee * refactor: select ready request work * refactor: handle save form * refactor: edit layout btn * feat: undo() * cleanup delete import * feat: closetab * refactor: handle readonly * fix: body edit * refactor: handle readonly uploadfile * feat: import manage attachment * refactor: quotation/task-order => type * refactor: select ready request work * refactor: i18n & constants * chore: clean duplicate i18n * refactor: type according to backend relation * refactor: edit base url * feat: upload file * feat: fetch file list * feat: get url file * refactor: set default opened * refactor: type * feat: removefile * feat: task order => select product * feat: add parameter only active branch is selectable * refactor: add i18n * feat: set layout * feat: add info product expansion * refactor: new info messenger * refactor: add slot name value * refactor: add i18n * refactor: edit type task status * refactor: use date format * refactor: value can null * refactor: add i18n * cleanup * feat: productlistinput * refactor: edit i18n * refactor: edit redo * refactor: add slot * feat: task order => i18n * refactor: task order => constant * refactor: taskOrder => status type and index * feat: taskOrder => ReceiveDialog * refactor: wording * refactor: table employee due date * refactor: receive task i18n * feat: trigger receive & task stat in receive page * refactor: receive dialog task in cart i18n * fix: remove task-order/receive/add * feat: receivetabletaskorder * refactor: fetch task on receive dialog * feat: add separate api get user task * refactor: receive fetch (messenger) * refactor: edit layout table * refactor: task order i18n & constant * refactor: task order change tab and stat (messenger and !messenger) * fix: task order status display & receive badge color (card) * refactor: trigger receive view * fix: add receive task condition * feat: total count * feat: prepare information * fix: i18n error task order not found * refacor: value * feat: select worker * refactor: status i18n & constant * refactor: table employee props (check box, step) * fix: order => select ready task * refactor: order => toggle status * refactor: receive => receive dialog * feat: featch value * refactor: task status display components * refactor: status active can is null * feat: update status tab * refactor: data display * refactor: i18n & fullTaskOrder variable * refactor: task receive view * refactor: add type responsible user * refactor: set group messenger * cleanup: * refactor: i18n / clone full task order / service => workflow type * refactor: receive view * refactor: show info messenger * refactor: handle flow step * refactor: receive view => opacity when pending * feat: add workflow template name and step name * feat: display workflow data on table * feat: add template step identifier * fix: edit does not change workflow id if changed * feat: detect if same template and step * refactor: handle template * refactor: add slot name product * refactor: map step in list product * refactor: bind data messenger list group * refactor: change endpoint name * chore: add helper package * feat: changetaskstatus * refactor: update type * refactor: set color btn * refactor: add step * refactor: add resposible institution * feat: disabled * refactor: map responsible institution * fix: order view => readonly * chore: clean * refactor: edit url api * refactor: edit name type * refactor: add slots action * refactor: add type row * refactor: add opts of task status * refactor: add select status * refactor: handle btn * refactor: add btn change task status * refactor: edit i18n redo th * refactor: sort status opts * feat: receive & order banner img * refactor: fetch status after submit * refactor: handle create only * refactor: task order status type Accept (messenger only) * feat: receive messenger profile * refactor: receive toggle status (display only) * fix: document expansion readonly * feat: confirmsendingbtn * refactor: constant and task order status * feat: receive task list count * refactor: post or get * refactor: define props institution group * refactor: fetch status after submit * refactor: handle create * refactor: handle query * refactor: update endpoint to support accept multiple order * refactor: change function name * feat: receive => functional accept task order * feat: task status count * feat: receive stat card count * refactor: order messenger profile * refactor: edit value to be task status * refactor: handle status of type order * refactor: use componet task status * refactor: handle show btn saving status * refactor: order => task status * refactor: edit selectStatus => changeStatus * refactor: edit @click btn confirmssending * refactor: add i18n * refactor: add function get template data * refactor: add change status * refactor: handle type receive * feat: order => auto change tab by status * refactor: fetch task after change status * feat: fail remark dialog * refactor: display step order (table employee) * refactor: fail remark dialog * refactor: order => open ready request dialog map selected * refactor: task list type & change status param * refactor: table task order, td background when selected * refactor: order => change status param * refactor: order => selectedEmployee variable type * refactor: task status component => shield btn * refactor: receive => change status * refactor: order => step btn waiting * fix: step btn waiting condition * refactor: filter selectable task (Failed) * refactor: find index condition on check * refactor: no request list available * refactor: fail btn no-wrap * refactor: fail dialog readonly * fix: reset state on open dialog * fix: wrong title position * refactor: hide task status drop down icon * fix: handle check condition * refactor: add userTask type and status * feat: submit task order function * refactor: table employee checkbox display condition * refactor: main layout * fix: task order validate i18n * refactor: table task order add submit status * refactor: status list * refactor: info product => user task status * feat: receive => submit task & step * refactor: i18n * feat: complete task oder function * refactor: task status component no action props * refactor: info messenger status * refactor: receive and order view * refactor: order complete view * refactor: order => complete color and title * refactor: calc price on table * refactor: quotation table i18n + product image * refactor: remove urgent checkbox * refactor: task status color * feat: calc summary price * fix: data is not available * feat: add doc view structure * refactor: format address text * feat: fetch document data from api * fix: value is null * fix: regression cannot edit package * feat: add document view for task order * feat: add view document button * feat: update type add discount * feat: readonly on cancel * feat: add discount from relation * refactor: add taskProduct on submit order * refactor: order => task product discount * refactor: order => date, task status count, view example * refactor: receive date * refactor: receive task status count --------- Co-authored-by: puriphatt <puriphat@frappet.com> Co-authored-by: nwpptrs <jay02499@gmail.com> Co-authored-by: Thanaphon Frappet <thanaphon@frappet.com> Co-authored-by: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Co-authored-by: oat_dev <nattapon@frappet.com>
This commit is contained in:
parent
cd0831bac1
commit
9eff614dbd
56 changed files with 6981 additions and 361 deletions
299
src/pages/09_task-order/receive_view/FailRemarkDialog.vue
Normal file
299
src/pages/09_task-order/receive_view/FailRemarkDialog.vue
Normal file
|
|
@ -0,0 +1,299 @@
|
|||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import { Lang } from 'src/utils/ui';
|
||||
|
||||
import { TaskStatus } from 'src/stores/task-order/types';
|
||||
import { RequestData, RequestWork } from 'src/stores/request-list';
|
||||
import useOptionStore from 'src/stores/options';
|
||||
|
||||
import { CancelButton, SaveButton } from 'src/components/button';
|
||||
import SelectInput from 'src/components/shared/SelectInput.vue';
|
||||
import DialogFormContainer from 'src/components/dialog/DialogFormContainer.vue';
|
||||
import DialogHeader from 'src/components/dialog/DialogHeader.vue';
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'submit'): void;
|
||||
(e: 'close'): void;
|
||||
}>();
|
||||
|
||||
const open = defineModel<boolean>('open', { default: false });
|
||||
const taskStatusList = defineModel<
|
||||
{
|
||||
code?: string;
|
||||
requestWorkId: string;
|
||||
step: number;
|
||||
failedComment?: string;
|
||||
failedType?: string;
|
||||
}[]
|
||||
>('setTaskStatusList', { default: [] });
|
||||
|
||||
const failedType = ref<string>('');
|
||||
const failedComment = ref<string>('');
|
||||
|
||||
const props = defineProps<{
|
||||
readonly?: boolean;
|
||||
failTaskOption: (RequestWork & {
|
||||
_template?: {
|
||||
id: string;
|
||||
templateName: string;
|
||||
templateStepName: string;
|
||||
step: number;
|
||||
} | null;
|
||||
taskStatus?: TaskStatus;
|
||||
})[];
|
||||
}>();
|
||||
|
||||
function onDialogOpen() {
|
||||
failedType.value = taskStatusList.value[0].failedType || '';
|
||||
failedComment.value = taskStatusList.value[0].failedComment || '';
|
||||
}
|
||||
|
||||
function onDialogClose() {
|
||||
emit('close');
|
||||
}
|
||||
|
||||
function getEmployeeName(
|
||||
record?: RequestData,
|
||||
opts?: {
|
||||
locale?: string;
|
||||
},
|
||||
) {
|
||||
if (!record) return;
|
||||
const employee = record.employee;
|
||||
|
||||
return (
|
||||
{
|
||||
[Lang.English]: `${useOptionStore().mapOption(employee.namePrefix)} ${employee.firstNameEN} ${employee.lastNameEN}`,
|
||||
[Lang.Thai]: `${useOptionStore().mapOption(employee.namePrefix)} ${employee.firstName} ${employee.lastName}`,
|
||||
}[opts?.locale || Lang.English] || '-'
|
||||
);
|
||||
}
|
||||
|
||||
function submit() {
|
||||
taskStatusList.value.forEach((v) => {
|
||||
v.failedComment = failedComment.value;
|
||||
v.failedType = failedType.value;
|
||||
});
|
||||
emit('submit');
|
||||
open.value = false;
|
||||
}
|
||||
|
||||
function tooltip(id: string) {
|
||||
const currTask = props.failTaskOption.find((t) => t.id === id);
|
||||
return currTask;
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<DialogFormContainer
|
||||
v-on:open="onDialogOpen"
|
||||
v-on:close="onDialogClose"
|
||||
v-model="open"
|
||||
height="330px"
|
||||
width="60vw"
|
||||
>
|
||||
<template #header>
|
||||
<DialogHeader :title="$t('general.remark')" />
|
||||
</template>
|
||||
|
||||
<main class="q-py-sm q-px-md scroll">
|
||||
<q-select
|
||||
:readonly
|
||||
dense
|
||||
outlined
|
||||
multiple
|
||||
use-chips
|
||||
:model-value="taskStatusList"
|
||||
:label="$t('taskOrder.failTaskOrderCode')"
|
||||
hide-dropdown-icon
|
||||
class="q-mb-sm"
|
||||
:options="
|
||||
failTaskOption.filter(
|
||||
(v) => v.taskStatus !== TaskStatus.Success && TaskStatus.Complete,
|
||||
)
|
||||
"
|
||||
@remove="(v) => taskStatusList.splice(v.index, 1)"
|
||||
@add="
|
||||
(v) => {
|
||||
const index = taskStatusList.findIndex(
|
||||
(item) => item.requestWorkId === v.value.id,
|
||||
);
|
||||
if (index !== -1) {
|
||||
taskStatusList.splice(index, 1);
|
||||
return;
|
||||
}
|
||||
taskStatusList.push({
|
||||
code: `${v.value.productService.product.code}-${v.value.request.code}`,
|
||||
requestWorkId: v.value.id,
|
||||
step: v.value._template.step,
|
||||
});
|
||||
}
|
||||
"
|
||||
>
|
||||
<template #selected-item="scope">
|
||||
<q-chip
|
||||
dense
|
||||
:removable="!readonly"
|
||||
@remove="scope.removeAtIndex(scope.index)"
|
||||
>
|
||||
{{ taskStatusList[scope.index].code }}
|
||||
<q-tooltip :delay="300">
|
||||
<section class="column">
|
||||
<div>
|
||||
{{ $t('productService.title') }}
|
||||
</div>
|
||||
<div class="text-caption">
|
||||
{{
|
||||
tooltip(scope.opt.requestWorkId)?.productService.product
|
||||
.name
|
||||
}}
|
||||
({{
|
||||
tooltip(scope.opt.requestWorkId)?.productService.product
|
||||
.code
|
||||
}})
|
||||
</div>
|
||||
|
||||
<div class="q-pt-xs">
|
||||
{{ $t('quotation.employeeName') }}
|
||||
</div>
|
||||
<div class="text-caption">
|
||||
{{
|
||||
getEmployeeName(tooltip(scope.opt.requestWorkId)?.request, {
|
||||
locale: $i18n.locale,
|
||||
})
|
||||
}}
|
||||
({{ tooltip(scope.opt.requestWorkId)?.request.code }})
|
||||
</div>
|
||||
</section>
|
||||
</q-tooltip>
|
||||
</q-chip>
|
||||
</template>
|
||||
|
||||
<template #option="scope">
|
||||
<q-item
|
||||
clickable
|
||||
v-bind="scope.itemProps"
|
||||
class="row items-start col-12 no-padding"
|
||||
:active="
|
||||
scope.opt.id === taskStatusList[scope.index]?.requestWorkId
|
||||
"
|
||||
active-class="text-brand1"
|
||||
>
|
||||
<div class="q-ma-sm">
|
||||
<q-icon
|
||||
name="mdi-shopping-outline"
|
||||
style="color: var(--teal-10)"
|
||||
/>
|
||||
</div>
|
||||
<div class="q-mt-sm">
|
||||
<div>
|
||||
<span>
|
||||
<span style="font-weight: 600">
|
||||
{{ $t('productService.title') }}:
|
||||
</span>
|
||||
{{ scope.opt.productService.product.name }}
|
||||
({{ scope.opt.productService.product.code }})
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="text-caption app-text-muted-2 q-mb-xs">
|
||||
{{ $t('quotation.employeeName') }}:
|
||||
{{
|
||||
getEmployeeName(scope.opt.request, { locale: $i18n.locale })
|
||||
}}
|
||||
({{ scope.opt.request.code }})
|
||||
</div>
|
||||
</div>
|
||||
<!-- <section class="column">
|
||||
<div class="text-caption app-text-muted">
|
||||
{{ $t('productService.title') }}
|
||||
</div>
|
||||
<div>
|
||||
{{ scope.opt.productService.product.name }} ({{
|
||||
scope.opt.productService.product.code
|
||||
}})
|
||||
</div>
|
||||
|
||||
<div class="text-caption app-text-muted q-pt-xs">
|
||||
{{ $t('quotation.employeeName') }}
|
||||
</div>
|
||||
<div>
|
||||
{{
|
||||
getEmployeeName(scope.opt.request, { locale: $i18n.locale })
|
||||
}}
|
||||
({{ scope.opt.request.code }})
|
||||
</div>
|
||||
</section> -->
|
||||
</q-item>
|
||||
<q-separator class="q-mx-sm" />
|
||||
</template>
|
||||
</q-select>
|
||||
|
||||
<SelectInput
|
||||
:readonly
|
||||
:option="[
|
||||
{
|
||||
label: $t('taskOrder.documentSubmitFailed'),
|
||||
value: 'documentSubmitFailed',
|
||||
},
|
||||
{
|
||||
label: $t('taskOrder.taskNotFullyCompleted'),
|
||||
value: 'taskNotFullyCompleted',
|
||||
},
|
||||
{
|
||||
label: $t('general.other'),
|
||||
value: 'other',
|
||||
},
|
||||
]"
|
||||
:label="$t('taskOrder.describeIssue')"
|
||||
v-model="failedType"
|
||||
>
|
||||
<template #option="{ opt, scope }">
|
||||
<q-item
|
||||
class="items-center"
|
||||
v-bind="scope.itemProps"
|
||||
:class="{ 'bordered-t': opt.value === 'other' }"
|
||||
>
|
||||
{{ opt.label }}
|
||||
</q-item>
|
||||
</template>
|
||||
</SelectInput>
|
||||
|
||||
<div v-if="failedType === 'other'" class="q-mt-sm rounded">
|
||||
<q-editor
|
||||
dense
|
||||
flat
|
||||
v-model="failedComment"
|
||||
min-height="5rem"
|
||||
class="q-mt-sm q-mb-xs"
|
||||
:toolbar="[['left', 'center', 'justify']]"
|
||||
:toolbar-color="$q.dark.isActive ? 'white' : ''"
|
||||
:toolbar-toggle-color="'primary'"
|
||||
style="
|
||||
cursor: auto;
|
||||
color: var(--foreground);
|
||||
border-color: var(--surface-3);
|
||||
"
|
||||
:style="`width: ${$q.screen.gt.xs ? '100%' : '63vw'}`"
|
||||
/>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<template #footer v-if="!readonly">
|
||||
<CancelButton class="q-ml-auto" outlined @click="$emit('close')" />
|
||||
<SaveButton class="q-ml-sm" solid @click="submit" />
|
||||
</template>
|
||||
</DialogFormContainer>
|
||||
</template>
|
||||
<style scoped>
|
||||
.q-editor__toolbar {
|
||||
border-bottom: none !important;
|
||||
}
|
||||
|
||||
:deep(.q-editor__toolbar.row.no-wrap.scroll-x) {
|
||||
background-color: var(--surface-2) !important;
|
||||
}
|
||||
|
||||
:deep(.q-editor__toolbar) {
|
||||
border-color: var(--surface-3) !important;
|
||||
}
|
||||
</style>
|
||||
774
src/pages/09_task-order/receive_view/MainPage.vue
Normal file
774
src/pages/09_task-order/receive_view/MainPage.vue
Normal file
|
|
@ -0,0 +1,774 @@
|
|||
<script lang="ts" setup>
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { computed, onMounted, ref, watch } from 'vue';
|
||||
import { getUserId } from 'src/services/keycloak';
|
||||
|
||||
// NOTE: Import Components
|
||||
import { SaveButton } from 'src/components/button';
|
||||
import { StateButton } from 'components/button';
|
||||
import InfoMessengerExpansion from '../expansion/receive/InfoMessengerExpansion.vue';
|
||||
import InfoProductExpansion from '../expansion/receive/InfoProductExpansion.vue';
|
||||
import FormGroupHead from 'src/pages/08_request-list/FormGroupHead.vue';
|
||||
import TableEmployee from '../TableEmployee.vue';
|
||||
import TaskStatusComponent from '../TaskStatusComponent.vue';
|
||||
import FailRemarkDialog from './FailRemarkDialog.vue';
|
||||
|
||||
// NOTE: Import Types and Store
|
||||
import { dateFormatJS, dateFormat } from 'src/utils/datetime';
|
||||
import { useTaskOrderForm } from '../form';
|
||||
import { initLang, initTheme, Lang } from 'src/utils/ui';
|
||||
import { RequestWork } from 'src/stores/request-list';
|
||||
import { baseUrl, dialogWarningClose } from 'src/stores/utils';
|
||||
import {
|
||||
TaskOrder,
|
||||
TaskOrderStatus,
|
||||
TaskStatus,
|
||||
UserTaskStatus,
|
||||
} from 'src/stores/task-order/types';
|
||||
import useOptionStore from 'src/stores/options';
|
||||
import { useTaskOrderStore } from 'src/stores/task-order';
|
||||
|
||||
const route = useRoute();
|
||||
const taskOrderFormStore = useTaskOrderForm();
|
||||
|
||||
const { currentFormData, state, fullTaskOrder } =
|
||||
storeToRefs(taskOrderFormStore);
|
||||
|
||||
const statusTabForm = ref<
|
||||
{
|
||||
title: string;
|
||||
status: 'done' | 'doing' | 'waiting';
|
||||
handler: () => void;
|
||||
active?: () => boolean;
|
||||
}[]
|
||||
>([
|
||||
{
|
||||
title: 'receive',
|
||||
status: 'doing',
|
||||
active: () => view.value === UserTaskStatus.Pending,
|
||||
handler: () => (
|
||||
(view.value = UserTaskStatus.Pending), console.log(view.value)
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'sendTaskOrder',
|
||||
status: 'waiting',
|
||||
active: () => view.value === UserTaskStatus.Submit,
|
||||
handler: () => (
|
||||
(view.value = UserTaskStatus.Submit), console.log(view.value)
|
||||
),
|
||||
},
|
||||
]);
|
||||
const failedDialog = ref(false);
|
||||
const taskStatusRecords = ref<
|
||||
{
|
||||
requestWorkId: string;
|
||||
step: number;
|
||||
failedComment?: string;
|
||||
failedType?: string;
|
||||
code?: string;
|
||||
}[]
|
||||
>([]);
|
||||
const selectedEmployee = ref<
|
||||
(RequestWork & {
|
||||
_template?: {
|
||||
id: string;
|
||||
templateName: string;
|
||||
templateStepName: string;
|
||||
step: number;
|
||||
} | null;
|
||||
})[][]
|
||||
>([]);
|
||||
|
||||
const view = ref<UserTaskStatus>(UserTaskStatus.Pending);
|
||||
const TAB_STATUS = ['Pending', 'Accept', 'Submit'];
|
||||
|
||||
function getStatus(
|
||||
status: UserTaskStatus,
|
||||
doneIndex: number,
|
||||
doingIndex: number,
|
||||
) {
|
||||
return TAB_STATUS.findIndex((v) => v === status) >= doneIndex
|
||||
? 'done'
|
||||
: TAB_STATUS.findIndex((v) => v === status) >= doingIndex
|
||||
? 'doing'
|
||||
: 'waiting';
|
||||
}
|
||||
|
||||
async function fetchStatus() {
|
||||
if (!fullTaskOrder.value) return;
|
||||
statusTabForm.value = [
|
||||
{
|
||||
title: 'receive',
|
||||
status: getStatus(
|
||||
fullTaskOrder.value?.userTask[0]?.userTaskStatus ||
|
||||
UserTaskStatus.Pending,
|
||||
2,
|
||||
-1,
|
||||
),
|
||||
active: () => view.value === UserTaskStatus.Pending,
|
||||
handler: () => (
|
||||
(view.value = UserTaskStatus.Pending), console.log(view.value)
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'sendTaskOrder',
|
||||
status: getStatus(
|
||||
fullTaskOrder.value?.userTask[0]?.userTaskStatus ||
|
||||
UserTaskStatus.Pending,
|
||||
2,
|
||||
2,
|
||||
),
|
||||
active: () => view.value === UserTaskStatus.Submit,
|
||||
handler: () => (
|
||||
(view.value = UserTaskStatus.Submit), console.log(view.value)
|
||||
),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
function getTemplateData(
|
||||
requestWork: RequestWork,
|
||||
targetStep: number,
|
||||
): {
|
||||
id: string;
|
||||
templateName: string;
|
||||
templateStepName: string;
|
||||
step: number;
|
||||
} | null {
|
||||
const flow = requestWork.productService.service?.workflow;
|
||||
if (!flow) return null;
|
||||
const step = flow.step.find((v) => v.order === targetStep);
|
||||
if (!step) return null;
|
||||
return {
|
||||
id: step.id,
|
||||
templateName: flow.name,
|
||||
templateStepName: step.name || '-',
|
||||
step: step.order,
|
||||
};
|
||||
}
|
||||
|
||||
let taskListGroup = computed(() => {
|
||||
const cacheData = currentFormData.value.taskList.reduce<
|
||||
{
|
||||
product: RequestWork['productService']['product'];
|
||||
list: (RequestWork & {
|
||||
_template?: {
|
||||
id: string;
|
||||
templateName: string;
|
||||
templateStepName: string;
|
||||
step: number;
|
||||
} | null;
|
||||
taskStatus?: TaskStatus;
|
||||
})[];
|
||||
}[]
|
||||
>((acc, curr) => {
|
||||
const taskStatus = curr.taskStatus;
|
||||
const task = curr.requestWorkStep;
|
||||
const step = curr.step;
|
||||
|
||||
if (!task) return acc;
|
||||
|
||||
if (task.requestWork) {
|
||||
let exist = acc.find(
|
||||
(item) => task.requestWork.productService.productId == item.product.id,
|
||||
);
|
||||
const record = Object.assign(task.requestWork, {
|
||||
_template: getTemplateData(task.requestWork, step),
|
||||
taskStatus: taskStatus ?? TaskStatus.Pending,
|
||||
});
|
||||
|
||||
if (exist) {
|
||||
exist.list.push(task.requestWork);
|
||||
} else {
|
||||
acc.push({
|
||||
product: task.requestWork.productService.product,
|
||||
list: [record],
|
||||
});
|
||||
if (selectedEmployee.value.length < acc.length) {
|
||||
selectedEmployee.value.push([]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
return cacheData;
|
||||
});
|
||||
|
||||
// NOTE: Function
|
||||
async function sendTask() {
|
||||
if (!fullTaskOrder.value) return;
|
||||
if (
|
||||
!fullTaskOrder.value.taskList.every(
|
||||
(t) =>
|
||||
t.taskStatus === TaskStatus.Success ||
|
||||
t.taskStatus === TaskStatus.Failed,
|
||||
)
|
||||
)
|
||||
return;
|
||||
await useTaskOrderStore().submitTaskOrder(fullTaskOrder.value.id);
|
||||
await taskOrderFormStore.assignFormData(
|
||||
fullTaskOrder.value.id,
|
||||
'info',
|
||||
getUserId(),
|
||||
);
|
||||
await fetchStatus();
|
||||
view.value = UserTaskStatus.Submit;
|
||||
}
|
||||
|
||||
function getMessengerName(
|
||||
record: TaskOrder,
|
||||
opts?: {
|
||||
gender?: boolean;
|
||||
url?: boolean;
|
||||
locale?: string;
|
||||
},
|
||||
) {
|
||||
const user = record.taskList[0].requestWorkStep.responsibleUser;
|
||||
|
||||
if (!user) return '-';
|
||||
|
||||
const url = `${baseUrl}/user/${user.id}/profile-image/${user.selectedImage}`;
|
||||
|
||||
return opts?.gender
|
||||
? user.gender
|
||||
: opts?.url
|
||||
? url
|
||||
: {
|
||||
[Lang.English]: `${useOptionStore().mapOption(user.namePrefix as string)} ${user.firstNameEN} ${user.lastNameEN}`,
|
||||
[Lang.Thai]: `${useOptionStore().mapOption(user.namePrefix as string)} ${user.firstName} ${user.lastName}`,
|
||||
}[opts?.locale || Lang.English] || '-';
|
||||
}
|
||||
|
||||
function handleChangeStatus(
|
||||
records: {
|
||||
data: (RequestWork & {
|
||||
_template?: {
|
||||
id: string;
|
||||
templateName: string;
|
||||
templateStepName: string;
|
||||
step: number;
|
||||
} | null;
|
||||
})[];
|
||||
status: TaskStatus;
|
||||
},
|
||||
index: number,
|
||||
) {
|
||||
const { data, status } = records;
|
||||
|
||||
if (data.length === 0) return;
|
||||
|
||||
taskStatusRecords.value = data.map((v) => ({
|
||||
code: `${v.productService.product.code}-${v.request.code}`,
|
||||
requestWorkId: v.id || '',
|
||||
step: v._template?.step || 0,
|
||||
}));
|
||||
|
||||
if (status === TaskStatus.Failed) {
|
||||
failedDialog.value = true;
|
||||
return;
|
||||
}
|
||||
|
||||
taskOrderFormStore.changeStatus(taskStatusRecords.value, status, () => {
|
||||
if (!currentFormData.value.id) return;
|
||||
selectedEmployee.value[index] = [];
|
||||
taskOrderFormStore.assignFormData(
|
||||
currentFormData.value.id,
|
||||
'info',
|
||||
getUserId(),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
function taskStatusCount(index: number, id: string) {
|
||||
const task = fullTaskOrder.value?.taskList.filter(
|
||||
(t) => t.requestWorkStep.requestWork.productService.productId === id,
|
||||
);
|
||||
if (index === 1) {
|
||||
return task?.filter((t) => t.taskStatus === TaskStatus.InProgress).length;
|
||||
} else if (index === 2) {
|
||||
return task?.filter(
|
||||
(t) =>
|
||||
t.taskStatus === TaskStatus.Success ||
|
||||
t.taskStatus === TaskStatus.Complete ||
|
||||
t.taskStatus === TaskStatus.Validate,
|
||||
).length;
|
||||
} else {
|
||||
return task?.filter(
|
||||
(t) =>
|
||||
t.taskStatus === TaskStatus.Canceled ||
|
||||
t.taskStatus === TaskStatus.Redo ||
|
||||
t.taskStatus === TaskStatus.Failed,
|
||||
).length;
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
initTheme();
|
||||
initLang();
|
||||
|
||||
const currentId = route.params['id'];
|
||||
state.value.mode = 'info';
|
||||
|
||||
if (currentId !== undefined && typeof currentId === 'string') {
|
||||
await taskOrderFormStore.assignFormData(
|
||||
currentId,
|
||||
state.value.mode,
|
||||
getUserId(),
|
||||
);
|
||||
await fetchStatus();
|
||||
}
|
||||
});
|
||||
|
||||
watch([currentFormData.value.taskStatus], () => {
|
||||
fetchStatus();
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
<div v-if="fullTaskOrder" class="column surface-0 fullscreen">
|
||||
<div class="color-bar" :class="{ dark: $q.dark.isActive }">
|
||||
<div class="pink-segment"></div>
|
||||
<div class="light-pink-segment"></div>
|
||||
<div class="gray-segment"></div>
|
||||
</div>
|
||||
|
||||
<!-- SEC: Header -->
|
||||
<header
|
||||
class="row q-px-md q-py-sm items-center full justify-between relative-position"
|
||||
>
|
||||
<section class="banner" :class="{ dark: $q.dark.isActive }"></section>
|
||||
|
||||
<div style="flex: 1" class="row items-center">
|
||||
<RouterLink to="/task-order">
|
||||
<q-img src="/icons/favicon-512x512.png" width="3rem" />
|
||||
</RouterLink>
|
||||
<span class="column text-h6 text-bold q-ml-md">
|
||||
{{ $t('taskOrder.receive') }}
|
||||
<!-- {{ code || '' }} -->
|
||||
<span class="text-caption text-regular app-text-muted">
|
||||
{{
|
||||
$t('quotation.processOn', {
|
||||
msg: dateFormatJS({
|
||||
date: fullTaskOrder
|
||||
? fullTaskOrder.createdAt
|
||||
: new Date(Date.now()),
|
||||
dayStyle: 'numeric',
|
||||
monthStyle: 'long',
|
||||
locale: $i18n.locale === 'tha' ? 'th-Th' : 'en-US',
|
||||
}),
|
||||
})
|
||||
}}
|
||||
{{
|
||||
dateFormat(
|
||||
fullTaskOrder ? fullTaskOrder.createdAt : new Date(Date.now()),
|
||||
false,
|
||||
true,
|
||||
)
|
||||
}}
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- SEC: Body -->
|
||||
<article
|
||||
class="col full-width q-pa-md"
|
||||
style="flex-grow: 1; overflow-y: auto"
|
||||
>
|
||||
<section class="col-sm col-12">
|
||||
<div class="col q-gutter-y-md">
|
||||
<nav
|
||||
class="surface-1 q-pa-sm row no-wrap full-width scroll rounded"
|
||||
style="gap: 10px"
|
||||
>
|
||||
<!-- TODO: replace step and status -->
|
||||
<StateButton
|
||||
v-for="i in statusTabForm"
|
||||
:key="i.title"
|
||||
:label="$t(`taskOrder.${i.title}`)"
|
||||
:statusActive="i.active?.()"
|
||||
:statusDone="i.status === 'done'"
|
||||
:status-waiting="i.status === 'waiting'"
|
||||
@click="i.handler()"
|
||||
/>
|
||||
</nav>
|
||||
|
||||
<article
|
||||
v-if="
|
||||
fullTaskOrder.taskOrderStatus !== TaskOrderStatus.Pending &&
|
||||
view !== UserTaskStatus.Submit
|
||||
"
|
||||
class="row items-center surface-1 q-pa-md rounded gradient-stat"
|
||||
>
|
||||
<span
|
||||
class="row col rounded q-px-sm q-py-md info"
|
||||
style="border: 1px solid hsl(var(--info-bg))"
|
||||
>
|
||||
{{ $t('taskOrder.allProduct') }}
|
||||
<span class="q-ml-auto">{{ fullTaskOrder.taskList.length }}</span>
|
||||
</span>
|
||||
<span
|
||||
class="row col rounded q-px-sm q-py-md q-mx-md positive"
|
||||
style="border: 1px solid hsl(var(--positive-bg))"
|
||||
>
|
||||
{{ $t('taskOrder.alreadySentTask') }}
|
||||
<span class="q-ml-auto">
|
||||
{{
|
||||
fullTaskOrder.taskList.filter(
|
||||
(t) =>
|
||||
t.taskStatus === TaskStatus.Complete ||
|
||||
t.taskStatus === TaskStatus.Success ||
|
||||
t.taskStatus === TaskStatus.Validate ||
|
||||
t.taskStatus === TaskStatus.Redo ||
|
||||
t.taskStatus === TaskStatus.Failed,
|
||||
).length
|
||||
}}
|
||||
</span>
|
||||
</span>
|
||||
<span
|
||||
class="row col rounded q-px-sm q-py-md warning"
|
||||
style="border: 1px solid hsl(var(--warning-bg))"
|
||||
>
|
||||
{{ $t('taskOrder.status.Pending') }}
|
||||
<span class="q-ml-auto">
|
||||
{{
|
||||
fullTaskOrder.taskList.filter(
|
||||
(t) => t.taskStatus === TaskStatus.InProgress,
|
||||
).length
|
||||
}}
|
||||
</span>
|
||||
</span>
|
||||
</article>
|
||||
|
||||
<InfoMessengerExpansion
|
||||
:gender="getMessengerName(fullTaskOrder, { gender: true })"
|
||||
:contact-url="getMessengerName(fullTaskOrder, { url: true })"
|
||||
:contact-name="
|
||||
getMessengerName(fullTaskOrder, { locale: $i18n.locale })
|
||||
"
|
||||
:contact-tel="
|
||||
fullTaskOrder.taskList[0].requestWorkStep.responsibleUser
|
||||
?.telephoneNo
|
||||
"
|
||||
:email="
|
||||
fullTaskOrder.taskList[0].requestWorkStep.responsibleUser?.email
|
||||
"
|
||||
:status="
|
||||
fullTaskOrder.userTask[0]?.userTaskStatus ||
|
||||
UserTaskStatus.Pending
|
||||
"
|
||||
/>
|
||||
<InfoProductExpansion
|
||||
:code="currentFormData.code"
|
||||
:task-name="currentFormData.taskName"
|
||||
:issue-branch="currentFormData.registeredBranchId"
|
||||
:agencies="
|
||||
$i18n.locale === 'eng'
|
||||
? fullTaskOrder.institution?.nameEN
|
||||
: fullTaskOrder.institution?.name
|
||||
"
|
||||
:made-by="
|
||||
$i18n.locale === 'eng'
|
||||
? `${fullTaskOrder?.createdBy.firstNameEN} ${fullTaskOrder?.createdBy.lastNameEN}`
|
||||
: `${fullTaskOrder?.createdBy.firstName} ${fullTaskOrder?.createdBy.lastName}`
|
||||
"
|
||||
:contact-tel="currentFormData.contactTel"
|
||||
:contact-name="currentFormData.contactName"
|
||||
:userTaskStatus="
|
||||
fullTaskOrder?.userTask[0]?.userTaskStatus ||
|
||||
UserTaskStatus.Pending
|
||||
"
|
||||
>
|
||||
<div
|
||||
v-for="({ product, list }, i) in taskListGroup"
|
||||
:key="product.id"
|
||||
class="bordered-b"
|
||||
>
|
||||
<q-expansion-item
|
||||
dense
|
||||
class="overflow-hidden"
|
||||
switch-toggle-side
|
||||
style="border-radius: var(--radius-2)"
|
||||
expand-icon="mdi-chevron-down-circle"
|
||||
header-class="q-py-sm text-medium text-body items-center rounded q-mx-md q-my-sm"
|
||||
>
|
||||
<template #header>
|
||||
<q-avatar class="q-mr-md" size="md">
|
||||
<q-img
|
||||
class="text-center"
|
||||
:ratio="1"
|
||||
:src="`${baseUrl}/product/${product.id}/image/${product.selectedImage}`"
|
||||
>
|
||||
<template #error>
|
||||
<q-icon
|
||||
class="full-width full-height"
|
||||
name="mdi-shopping-outline"
|
||||
:style="`color: var(--teal-10); background: hsla(var(--teal-${$q.dark.isActive ? '8' : '10'}-hsl)/0.15)`"
|
||||
/>
|
||||
</template>
|
||||
</q-img>
|
||||
</q-avatar>
|
||||
<span>
|
||||
{{ product.name }}
|
||||
<div class="app-text-muted text-caption">
|
||||
{{ product.code }}
|
||||
</div>
|
||||
</span>
|
||||
<span
|
||||
v-if="
|
||||
fullTaskOrder.taskOrderStatus === TaskOrderStatus.Pending
|
||||
"
|
||||
class="q-ml-auto"
|
||||
>
|
||||
<div
|
||||
class="rounded q-px-sm row items-center"
|
||||
style="background: hsl(var(--text-mute) / 0.15)"
|
||||
>
|
||||
<q-icon
|
||||
name="mdi-account-group-outline"
|
||||
size="xs"
|
||||
class="q-pr-sm"
|
||||
/>
|
||||
{{ list.length }}
|
||||
</div>
|
||||
</span>
|
||||
<span v-else class="q-ml-auto row items-center q-gutter-x-sm">
|
||||
<div
|
||||
v-for="v in 3"
|
||||
:key="v"
|
||||
class="rounded q-px-sm row items-center"
|
||||
style="background: hsl(var(--text-mute) / 0.1)"
|
||||
:style="`color: hsl(var(--${
|
||||
v === 1 ? 'warning' : v === 2 ? 'positive' : 'negative'
|
||||
}-bg))`"
|
||||
>
|
||||
<q-icon
|
||||
name="mdi-account-group-outline"
|
||||
size="xs"
|
||||
class="q-pr-sm"
|
||||
/>
|
||||
|
||||
{{ taskStatusCount(v, product.id) }}
|
||||
</div>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<div>
|
||||
<FormGroupHead>
|
||||
{{ $t('quotation.employeeList') }}
|
||||
</FormGroupHead>
|
||||
<div class="q-pa-md full-width">
|
||||
<TableEmployee
|
||||
:checkbox-on="
|
||||
fullTaskOrder.taskOrderStatus !==
|
||||
TaskOrderStatus.Pending &&
|
||||
fullTaskOrder.userTask.every(
|
||||
(v) => v.userTaskStatus !== UserTaskStatus.Submit,
|
||||
)
|
||||
"
|
||||
:check-all="
|
||||
fullTaskOrder.taskOrderStatus !==
|
||||
TaskOrderStatus.Pending &&
|
||||
fullTaskOrder.userTask.every(
|
||||
(v) => v.userTaskStatus !== UserTaskStatus.Submit,
|
||||
)
|
||||
"
|
||||
step-on
|
||||
:rows="list"
|
||||
@change-all-status="(v) => handleChangeStatus(v, i)"
|
||||
v-model:selected-employee="selectedEmployee[i]"
|
||||
>
|
||||
<template #append="{ props: subProps }">
|
||||
<TaskStatusComponent
|
||||
type="receive"
|
||||
:readonly="
|
||||
fullTaskOrder.taskOrderStatus ===
|
||||
TaskOrderStatus.Pending
|
||||
"
|
||||
:status="subProps.row.taskStatus"
|
||||
@click-failed="
|
||||
() => {
|
||||
taskStatusRecords = [
|
||||
{
|
||||
code: `${subProps.row.productService.product.code}-${subProps.row.request.code}`,
|
||||
requestWorkId: subProps.row.id || '',
|
||||
step: subProps.row._template?.step || 0,
|
||||
failedComment:
|
||||
fullTaskOrder?.taskList[subProps.rowIndex]
|
||||
.failedComment || '',
|
||||
failedType:
|
||||
fullTaskOrder?.taskList[subProps.rowIndex]
|
||||
.failedType || '',
|
||||
},
|
||||
];
|
||||
failedDialog = true;
|
||||
}
|
||||
"
|
||||
@change-status="
|
||||
(status) => {
|
||||
handleChangeStatus(
|
||||
{
|
||||
data: [subProps.row],
|
||||
status: status,
|
||||
},
|
||||
i,
|
||||
);
|
||||
}
|
||||
"
|
||||
/>
|
||||
</template>
|
||||
</TableEmployee>
|
||||
</div>
|
||||
</div>
|
||||
</q-expansion-item>
|
||||
|
||||
<FailRemarkDialog
|
||||
:fail-task-option="list"
|
||||
v-model:open="failedDialog"
|
||||
v-model:set-task-status-list="taskStatusRecords"
|
||||
@submit="
|
||||
async () => {
|
||||
await taskOrderFormStore.changeStatus(
|
||||
taskStatusRecords,
|
||||
TaskStatus.Failed,
|
||||
() => {
|
||||
if (!currentFormData.id) return;
|
||||
taskOrderFormStore.assignFormData(
|
||||
currentFormData.id,
|
||||
'info',
|
||||
getUserId(),
|
||||
);
|
||||
},
|
||||
);
|
||||
selectedEmployee = [];
|
||||
}
|
||||
"
|
||||
@close="
|
||||
() => {
|
||||
failedDialog = false;
|
||||
taskStatusRecords = [];
|
||||
}
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
</InfoProductExpansion>
|
||||
</div>
|
||||
</section>
|
||||
</article>
|
||||
|
||||
<!-- SEC: footer -->
|
||||
<footer
|
||||
v-if="fullTaskOrder.taskOrderStatus !== TaskOrderStatus.Pending"
|
||||
class="surface-1 q-pa-md full-width"
|
||||
>
|
||||
<nav class="row justify-end">
|
||||
<SaveButton
|
||||
:disabled="
|
||||
!fullTaskOrder.taskList.every(
|
||||
(t) =>
|
||||
t.taskStatus === TaskStatus.Success ||
|
||||
t.taskStatus === TaskStatus.Failed,
|
||||
)
|
||||
"
|
||||
@click="
|
||||
dialogWarningClose($t, {
|
||||
message: $t('dialog.message.confirmSending'),
|
||||
action: async () => {
|
||||
await sendTask();
|
||||
},
|
||||
cancel: () => {},
|
||||
})
|
||||
"
|
||||
:label="$t('taskOrder.sentTask')"
|
||||
icon="mdi-check"
|
||||
solid
|
||||
/>
|
||||
</nav>
|
||||
</footer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.color-bar {
|
||||
width: 100%;
|
||||
height: 1vh;
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
rgb(214, 51, 108) 0%,
|
||||
rgba(255, 255, 255, 1) 77%,
|
||||
rgba(204, 204, 204, 1) 100%
|
||||
);
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.color-bar.dark {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.pink-segment {
|
||||
background-color: var(--pink-7);
|
||||
flex-grow: 4;
|
||||
}
|
||||
|
||||
.light-pink-segment {
|
||||
background-color: hsla(var(--pink-7-hsl) / 0.2);
|
||||
flex-grow: 0.5;
|
||||
}
|
||||
|
||||
.gray-segment {
|
||||
background-color: #ccc;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.pink-segment,
|
||||
.light-pink-segment,
|
||||
.gray-segment {
|
||||
transform: skewX(-60deg);
|
||||
}
|
||||
|
||||
.gradient-stat {
|
||||
& .info {
|
||||
background-image: linear-gradient(
|
||||
to right,
|
||||
hsl(var(--info-bg) / 0.15),
|
||||
var(--surface-1)
|
||||
);
|
||||
}
|
||||
|
||||
& .positive {
|
||||
background-image: linear-gradient(
|
||||
to right,
|
||||
hsl(var(--positive-bg) / 0.15),
|
||||
var(--surface-1)
|
||||
);
|
||||
}
|
||||
|
||||
& .warning {
|
||||
background-image: linear-gradient(
|
||||
to right,
|
||||
hsl(var(--warning-bg) / 0.15),
|
||||
var(--surface-1)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
.banner {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: url('/images/building-banner.png');
|
||||
background-repeat: no-repeat;
|
||||
background-size: cover;
|
||||
z-index: -1;
|
||||
|
||||
&.dark {
|
||||
filter: invert(100%);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
250
src/pages/09_task-order/receive_view/ReceiveDialog.vue
Normal file
250
src/pages/09_task-order/receive_view/ReceiveDialog.vue
Normal file
|
|
@ -0,0 +1,250 @@
|
|||
<script lang="ts" setup>
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
import DialogFormContainer from 'src/components/dialog/DialogFormContainer.vue';
|
||||
import DialogHeader from 'src/components/dialog/DialogHeader.vue';
|
||||
import TableTaskOrder from '../TableTaskOrder.vue';
|
||||
import FormGroupHead from 'src/pages/08_request-list/FormGroupHead.vue';
|
||||
import TableEmployee from '../TableEmployee.vue';
|
||||
import { SaveButton, CancelButton } from 'src/components/button';
|
||||
import TaskStatusComponent from '../TaskStatusComponent.vue';
|
||||
|
||||
import { baseUrl } from 'src/stores/utils';
|
||||
import { TaskOrder, UserTaskStatus } from 'src/stores/task-order/types';
|
||||
import { useTaskOrderStore } from 'src/stores/task-order';
|
||||
import { RequestWork } from 'src/stores/request-list';
|
||||
|
||||
const taskOrderStore = useTaskOrderStore();
|
||||
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
scan: boolean;
|
||||
}>(),
|
||||
{
|
||||
scan: false,
|
||||
},
|
||||
);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'submit', data: TaskOrder[]): void;
|
||||
}>();
|
||||
|
||||
const open = defineModel<boolean>('open', { default: false });
|
||||
|
||||
const rows = ref<TaskOrder[]>([]);
|
||||
const selectedTask = ref<TaskOrder[]>([]);
|
||||
const currTaskOrder = ref<(TaskOrder | null)[]>([]);
|
||||
|
||||
let taskListGroup = computed(() => {
|
||||
let acc: {
|
||||
taskId: string;
|
||||
list: {
|
||||
product: RequestWork['productService']['product'];
|
||||
list: RequestWork[];
|
||||
}[];
|
||||
}[] = [];
|
||||
|
||||
let list = [];
|
||||
|
||||
currTaskOrder.value.forEach((v, i) => {
|
||||
if (!v) {
|
||||
acc[i] = {
|
||||
taskId: '',
|
||||
list: [],
|
||||
};
|
||||
return;
|
||||
}
|
||||
|
||||
list = v.taskList.reduce<
|
||||
{
|
||||
product: RequestWork['productService']['product'];
|
||||
list: RequestWork[];
|
||||
}[]
|
||||
>((acc2, curr2) => {
|
||||
const task = curr2.requestWorkStep;
|
||||
if (!task) return acc2;
|
||||
if (task.requestWork) {
|
||||
let exist = acc2.find(
|
||||
(item) =>
|
||||
task.requestWork.productService.productId == item.product.id,
|
||||
);
|
||||
if (exist) {
|
||||
exist.list.push(task.requestWork);
|
||||
} else {
|
||||
acc2.push({
|
||||
product: task.requestWork.productService.product,
|
||||
list: [task.requestWork],
|
||||
});
|
||||
}
|
||||
}
|
||||
return acc2;
|
||||
}, []);
|
||||
|
||||
acc[i] = {
|
||||
taskId: v.id,
|
||||
list: list,
|
||||
};
|
||||
});
|
||||
return acc;
|
||||
});
|
||||
|
||||
async function fetchTaskOrderList() {
|
||||
const res = await taskOrderStore.getUserTaskOrderList({
|
||||
pageSize: 999,
|
||||
userTaskStatus: UserTaskStatus.Pending,
|
||||
});
|
||||
|
||||
if (res) {
|
||||
rows.value = res.result;
|
||||
}
|
||||
}
|
||||
|
||||
async function onDialogOpen() {
|
||||
await fetchTaskOrderList();
|
||||
currTaskOrder.value = Array(rows.value.length).fill(null);
|
||||
}
|
||||
|
||||
function submit() {
|
||||
emit('submit', selectedTask.value);
|
||||
clearState();
|
||||
}
|
||||
|
||||
function close() {
|
||||
open.value = false;
|
||||
clearState();
|
||||
}
|
||||
|
||||
function clearState() {
|
||||
selectedTask.value = [];
|
||||
currTaskOrder.value = [];
|
||||
}
|
||||
|
||||
async function handleSubRow(index: number, data: TaskOrder) {
|
||||
const ret = await taskOrderStore.getTaskOrderById(data.id);
|
||||
if (ret) {
|
||||
currTaskOrder.value[index] = ret;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<DialogFormContainer
|
||||
v-model="open"
|
||||
v-on:open="onDialogOpen"
|
||||
v-on:close="close"
|
||||
>
|
||||
<template #header>
|
||||
<DialogHeader
|
||||
:title="$t(`taskOrder.${scan ? 'receiveScan' : 'receiveCustom'}`)"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<section class="q-pa-sm full-width scroll">
|
||||
<TableTaskOrder
|
||||
receive
|
||||
:rows="rows"
|
||||
:selection="'multiple'"
|
||||
v-model:selected-task="selectedTask"
|
||||
@click-sub-row="handleSubRow"
|
||||
>
|
||||
<template #subRow="{ props }">
|
||||
<FormGroupHead>
|
||||
{{ $t('productService.title') }}
|
||||
</FormGroupHead>
|
||||
<div
|
||||
v-if="taskListGroup?.[props.rowIndex]?.list.length === 0"
|
||||
class="app-text-muted q-pt-xs"
|
||||
>
|
||||
{{ $t('productService.product.noProduct') }}
|
||||
</div>
|
||||
|
||||
<template v-if="taskListGroup">
|
||||
<div
|
||||
v-for="{ product, list } in taskListGroup?.[props.rowIndex]?.list"
|
||||
:key="product.id"
|
||||
:class="{ 'bordered-b': taskListGroup.length > 1 }"
|
||||
>
|
||||
<q-expansion-item
|
||||
dense
|
||||
class="overflow-hidden surface-1"
|
||||
switch-toggle-side
|
||||
style="border-radius: var(--radius-2)"
|
||||
expand-icon="mdi-chevron-down-circle"
|
||||
header-class="q-py-sm text-medium text-body items-center q-mb-sm surface-1"
|
||||
header-style="margin: 0"
|
||||
>
|
||||
<template #header>
|
||||
<q-avatar class="q-mr-md" size="md">
|
||||
<q-img
|
||||
class="text-center"
|
||||
:ratio="1"
|
||||
:src="`${baseUrl}/product/${product.id}/image/${product.selectedImage}`"
|
||||
>
|
||||
<template #error>
|
||||
<q-icon
|
||||
class="full-width full-height"
|
||||
name="mdi-shopping-outline"
|
||||
:style="`color: var(--teal-10); background: hsla(var(--teal-${$q.dark.isActive ? '8' : '10'}-hsl)/0.15)`"
|
||||
/>
|
||||
</template>
|
||||
</q-img>
|
||||
</q-avatar>
|
||||
<span>
|
||||
{{ product.name }}
|
||||
<div class="app-text-muted text-caption">
|
||||
{{ product.code }}
|
||||
</div>
|
||||
</span>
|
||||
<span class="q-ml-auto">
|
||||
<div
|
||||
class="rounded q-px-sm row items-center"
|
||||
style="background: hsl(var(--text-mute) / 0.15)"
|
||||
>
|
||||
<q-icon
|
||||
name="mdi-account-group-outline"
|
||||
size="xs"
|
||||
class="q-pr-sm"
|
||||
/>
|
||||
{{ list.length }}
|
||||
</div>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<div>
|
||||
<FormGroupHead>
|
||||
{{ $t('quotation.employeeList') }}
|
||||
</FormGroupHead>
|
||||
<div class="q-pa-md full-width surface-1">
|
||||
<TableEmployee step-on :rows="list">
|
||||
<template #append="{ props: subProps }">
|
||||
<TaskStatusComponent
|
||||
readonly
|
||||
:status="
|
||||
currTaskOrder[props.rowIndex]?.taskList[
|
||||
subProps.rowIndex
|
||||
].taskStatus
|
||||
"
|
||||
/>
|
||||
</template>
|
||||
</TableEmployee>
|
||||
</div>
|
||||
</div>
|
||||
</q-expansion-item>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
</TableTaskOrder>
|
||||
</section>
|
||||
|
||||
<template #footer>
|
||||
<CancelButton class="q-ml-auto" outlined @click="close" />
|
||||
<SaveButton
|
||||
:label="$t('taskOrder.receiveTask')"
|
||||
class="q-ml-sm"
|
||||
icon="mdi-check"
|
||||
solid
|
||||
@click="submit"
|
||||
/>
|
||||
</template>
|
||||
</DialogFormContainer>
|
||||
</template>
|
||||
<style scoped></style>
|
||||
Loading…
Add table
Add a link
Reference in a new issue