jws-frontend/src/pages/08_request-list/RequestListView.vue
puriphatt a3c51f5f52
Some checks failed
Spell Check / Spell Check with Typos (push) Failing after 5s
fix: request list hide btn manage messenger condition
2025-08-20 17:48:32 +07:00

1073 lines
35 KiB
Vue

<script setup lang="ts">
// NOTE: Library
import { onMounted, reactive, ref, watch, computed, nextTick } from 'vue';
import { useI18n } from 'vue-i18n';
// NOTE: Components
import DataDisplay from 'components/08_request-list/DataDisplay.vue';
import DocumentExpansion from './DocumentExpansion.vue';
import FormExpansion from './FormExpansion.vue';
import PropertiesExpansion from './PropertiesExpansion.vue';
import FormGroupHead from './FormGroupHead.vue';
import AvatarGroup from 'src/components/shared/AvatarGroup.vue';
import { StateButton } from 'components/button';
import { CancelButton, MainButton, SaveButton } from 'components/button';
import DutyExpansion from './DutyExpansion.vue';
import MessengerExpansion from './MessengerExpansion.vue';
import RequestAction from './RequestAction.vue';
import DialogFormContainer from 'src/components/dialog/DialogFormContainer.vue';
import DialogHeader from 'src/components/dialog/DialogHeader.vue';
import FormCancel from './FormCancel.vue';
// NOTE: Store
import {
baseUrl,
dialog,
getEmployeeName,
getCustomerName,
dialogWarningClose,
canAccess,
} from 'src/stores/utils';
import { dateFormatJS } from 'src/utils/datetime';
import { useRequestList } from 'src/stores/request-list';
import {
RequestData,
RequestWork,
Attributes,
DocStatus,
Step,
RequestWorkStatus,
RequestDataStatus,
} from 'src/stores/request-list/types';
import useOptionStore from 'src/stores/options';
import ProductExpansion from './ProductExpansion.vue';
import { useRoute } from 'vue-router';
import { useWorkflowTemplate } from 'src/stores/workflow-template';
import { WorkflowTemplate } from 'src/stores/workflow-template/types';
import { initLang, initTheme } from 'src/utils/ui';
import {
EmployeePassportPayload,
EmployeeVisaPayload,
} from 'stores/employee/types';
import { PropVariant } from 'src/stores/product-service/types';
import { Invoice } from 'src/stores/payment/types';
import { CreatedBy } from 'src/stores/types';
import { getUserId } from 'src/services/keycloak';
import { QuotationFull } from 'src/stores/quotations/types';
import useUserStore from 'src/stores/user';
const { locale, t } = useI18n();
// NOTE: Variable
const route = useRoute();
const optionStore = useOptionStore();
const requestListStore = useRequestList();
const flowTemplateStore = useWorkflowTemplate();
const userStore = useUserStore();
const currentUserGroup = ref<string[]>([]);
const workList = ref<RequestWork[]>([]);
const statusFile = ref<Attributes>({
customer: {},
employee: {},
});
const refDocumentExpansion = ref<InstanceType<typeof DocumentExpansion>[]>([]);
const data = ref<RequestData>();
const flow = ref<WorkflowTemplate>();
const productsList = computed(() =>
workList.value
?.filter((v) =>
v.productService.work?.attributes.workflowStep?.[
pageState.currentStep - 1
]?.productsId.includes(v.productService.productId),
)
.map((v) => {
const _props =
v.productService.work?.attributes?.workflowStep[
pageState.currentStep - 1
]?.attributes?.properties;
return Object.assign(v, {
_documentExpansion: _props.some(
(v: PropVariant) => v.fieldName === 'documentCheck',
),
_formExpansion: _props.some(
(v: PropVariant) => v.fieldName === 'designForm',
),
_dutyExpansion: _props.some((v: PropVariant) => v.fieldName === 'duty'),
_messengerExpansion: _props.some(
(v: PropVariant) => v.fieldName === 'messenger',
),
});
})
.sort(
(lhs, rhs) =>
lhs.productService.installmentNo - rhs.productService.installmentNo,
),
);
const pageState = reactive({
hideMetaData: false,
currentStep: 1,
requestActionDialog: false,
rejectCancelDialog: false,
rejectCancelReason: '',
requestWorkId: '',
});
// NOTE: Function
async function fetchRequestWorkList(opts: { requestDataId: string }) {
const res = await requestListStore.getRequestWorkList({
requestDataId: opts.requestDataId,
pageSize: 9999,
});
if (res) {
workList.value = res.result;
}
}
async function getData() {
const current = route.params['requestListId'];
if (typeof current === 'string') {
const res = await requestListStore.getRequestData(current);
if (res) {
data.value = res;
await fetchRequestWorkList({ requestDataId: current });
await getFlow();
}
}
}
async function getFlow() {
if (!workList.value) return;
const attr = workList.value.find((v) => !!v.productService.work?.attributes)
?.productService.work?.attributes;
if (attr && Object.hasOwn(attr, 'workflowId')) {
const workflowId = attr['workflowId'];
const res = await flowTemplateStore.getWorkflowTemplate(workflowId);
if (res) flow.value = res;
}
}
onMounted(async () => {
initTheme();
initLang();
const result = await userStore.fetchUserGroup();
currentUserGroup.value = result.map((v) => v.name);
// get data
await getData();
});
watch(() => route.params['requestListId'], getData);
async function triggerChangeStatusWork(step: Step, responsibleUserId?: string) {
if (step.workStatus === 'Ready' && !responsibleUserId) {
dialog({
color: 'warning',
icon: 'mdi-alert',
title: t('form.warning.title'),
actionText: t('dialog.action.ok'),
message: t('dialog.message.warningSelectDeliveryStaff'),
action: async () => {
await nextTick();
return;
},
});
return;
}
if (step.workStatus === 'RejectCancel') {
pageState.rejectCancelDialog = true;
pageState.rejectCancelReason = '';
pageState.requestWorkId = step.requestWorkId;
return;
}
const res = await requestListStore.editStatusRequestWork(step);
if (res) {
const indexWork = workList.value?.findIndex(
(v) => v.id === step.requestWorkId,
);
if (indexWork === -1 || indexWork === undefined) return;
if (workList.value === undefined) return;
const indexStep = workList.value[indexWork].stepStatus.findIndex(
(v) => v.step === step.step,
);
if (indexStep === -1) {
workList.value[indexWork].stepStatus.push(res);
}
if (indexStep !== -1) {
workList.value[indexWork].stepStatus[indexStep].workStatus =
res.workStatus;
}
}
await nextTick();
}
async function triggerChangeStatusFile(opt: {
index: number;
id: string;
documentType: string;
status: DocStatus;
type: 'customer' | 'employee';
}) {
if (!workList.value) return;
const workItem = workList.value[opt.index];
if (!workItem || !workItem.attributes) {
if (workItem) {
workItem.attributes = { customer: {}, employee: {} };
} else {
return;
}
}
const attributes = workItem.attributes;
statusFile.value = attributes;
if (!statusFile.value[opt.type]) {
statusFile.value[opt.type] = {};
}
statusFile.value[opt.type]![opt.documentType] = opt.status;
const res = await requestListStore.editRequestWork({
id: opt.id,
attributes: statusFile.value,
});
if (res) {
workList.value[opt.index].attributes = res.attributes;
}
}
async function triggerUpload(opt: {
id: string;
type: 'customer' | 'employee';
group: string;
file: File;
form?: EmployeePassportPayload | EmployeeVisaPayload;
}) {
const newName = `${opt.group}-${Date.now()}-${opt.file.name}`;
const res = await requestListStore.uploadAttachmentRequest({
...opt,
name: newName,
});
return !!res;
}
async function triggerViewFile(opt: {
id: string;
fileName: string;
type: 'customer' | 'employee';
group: string;
download?: boolean;
}) {
let url;
url = await requestListStore.viewAttachmentRequest({
id: opt.id,
name: opt.fileName,
type: opt.type,
group: opt.group,
download: opt.download,
});
if (!opt.download) window.open(url, '_blank');
}
const responsibleList = computed(() => {
const temp = workList.value?.reduce<
Record<string, { user: CreatedBy[]; group: string[] }>
>((acc, curr: RequestWork) => {
curr.productService.service?.workflow?.step.forEach((v) => {
const key = v.order.toString();
const responsibleGroup = (
v.responsibleGroup as unknown as { group: string }[]
).map((v) => v.group);
if (!acc[key]) {
acc[key] = {
user: v.responsiblePerson.map((v) => v.user),
group: responsibleGroup,
};
}
const current = acc[key];
v.responsiblePerson.forEach((lhs) => {
if (current.user.find((rhs) => rhs.id === lhs.userId)) return;
current.user.push(lhs.user);
});
responsibleGroup.forEach((lhs) => {
if (current.group.find((rhs) => rhs === lhs)) return;
current.group.push(lhs);
});
});
return acc;
}, {});
return temp || {};
});
function getInstallmentInfo() {
if (
data.value?.quotation.payCondition === 'Full' ||
data.value?.quotation.payCondition === 'BillFull'
) {
return undefined;
}
const total = data.value?.quotation.paySplitCount || 0;
const paid = data.value?.quotation.invoice?.reduce((a, c) => {
if (c.payment?.paymentStatus === 'PaymentSuccess') {
a += c.installments.length || 0;
}
return a;
}, 0);
return { total, paid };
}
function isInstallmentPaySuccess(installmentNo: number) {
if (
data.value?.quotation.payCondition === 'Full' ||
data.value?.quotation.payCondition === 'BillFull'
) {
return true;
}
const invoice = data.value?.quotation.invoice?.find((lhs) => {
return lhs.installments?.some((rhs) => rhs.no === installmentNo);
});
return !!(invoice?.payment?.paymentStatus === 'PaymentSuccess');
}
function goToQuotation(
quotation: QuotationFull,
opt?: { tab?: string; id?: string; amount?: number },
) {
const { tab, id, amount } = opt || {};
const url = new URL('/quotation/view', window.location.origin);
if (tab) {
url.searchParams.append('tab', tab);
}
if (id) {
url.searchParams.append('id', id);
}
if (amount) {
url.searchParams.append('amount', amount.toString());
}
localStorage.setItem(
'new-quotation',
JSON.stringify({
customerBranchId: quotation.customerBranchId,
agentPrice: quotation.agentPrice,
statusDialog: 'info',
quotationId: opt && opt.id ? opt.id : quotation.id,
}),
);
window.open(url.toString(), '_blank');
}
function goToDebitNote(opt?: { tab?: string; id?: string }) {
const { tab, id } = opt || {};
const url = new URL(
`/debit-note/${id}?mode=info&tab=${tab}`,
window.location.origin,
);
window.open(url.toString(), '_blank');
}
function closeTab() {
dialogWarningClose(t, {
message: t('dialog.message.close'),
action: () => {
window.close();
},
cancel: () => {},
});
}
function openRequestAction() {
pageState.requestActionDialog = true;
}
async function submitRequestAction(data: {
form: { responsibleUserLocal: boolean; responsibleUserId: string };
selected: { _work: RequestWork }[];
}) {
const requestWorksId = data.selected.map((s) => s._work.id);
const res = await requestListStore.actionRequestWork(
route.params['requestListId'] as string,
pageState.currentStep,
requestWorksId.map((v) => ({
responsibleUserId: data.form.responsibleUserId,
responsibleUserLocal: data.form.responsibleUserLocal,
requestWorkId: v!,
})),
);
if (res) {
await getData();
pageState.requestActionDialog = false;
}
}
async function submitRejectCancel() {
const current = route.params['requestListId'];
if (typeof current !== 'string') return;
const res = await requestListStore.rejectRequestWork(
current,
pageState.requestWorkId,
{
reason: pageState.rejectCancelReason,
},
);
if (res) {
const indexWork = workList.value?.findIndex(
(v) => v.id === pageState.requestWorkId,
);
workList.value[indexWork].rejectRequestCancel = true;
workList.value[indexWork].rejectRequestCancelReason =
pageState.rejectCancelReason;
pageState.rejectCancelDialog = false;
}
}
function toCustomer(customer: RequestData['quotation']['customerBranch']) {
if (!canAccess('customer', 'view')) return;
const url = new URL(
`/customer-management?tab=customer&id=${customer.customerId}`,
window.location.origin,
);
window.open(url.toString(), '_blank');
}
function toEmployee(employee: RequestData['employee']) {
if (!canAccess('customer', 'view')) return;
const url = new URL(
`/customer-management?tab=employee&id=${employee.id}`,
window.location.origin,
);
window.open(url.toString(), '_blank');
}
</script>
<template>
<div class="column surface-0 fullscreen" v-if="data">
<!-- SEC: Header -->
<header class="row q-px-md q-py-sm items-center full justify-between">
<div style="flex: 1" class="row items-center">
<RouterLink to="/request-list">
<q-img src="/icons/favicon-512x512.png" width="3rem" />
</RouterLink>
<span class="column text-h6 text-bold q-ml-md">
{{ $t('requestList.title') }}
{{ data.code || '' }}
<span class="text-caption text-regular app-text-muted">
{{
$t('quotation.processOn', {
msg: dateFormatJS({ date: data.createdAt }),
})
}}
</span>
</span>
</div>
<div class="row q-gutter-x-xl q-mr-xl">
<div class="column">
<span class="app-text-muted">
{{ $t('requestList.salesRepresentative') }}
</span>
<span>
{{ optionStore.mapOption(data.quotation.createdBy.namePrefix) }}
{{
$i18n.locale === 'eng'
? `${data.quotation.createdBy.firstNameEN} ${data.quotation.createdBy.lastNameEN}`
: `${data.quotation.createdBy.firstName} ${data.quotation.createdBy.lastName}`
}}
</span>
</div>
<div class="column">
<span class="app-text-muted">{{ $t('flow.responsiblePerson') }}</span>
<span>
<template
v-if="responsibleList[pageState.currentStep]?.user.length"
>
<AvatarGroup
:data="[
...(responsibleList[pageState.currentStep].user || []).map(
(v) => ({
name:
$i18n.locale === 'eng'
? `${v.firstNameEN} ${v.lastNameEN}`
: `${v.firstName} ${v.lastName}`,
imgUrl: !v.selectedImage
? v.gender === 'male'
? `/no-img-man.png`
: `/no-img-female.png`
: `${baseUrl}/user/${v.id}/profile-image/${v.selectedImage}`,
}),
),
...responsibleList[pageState.currentStep].group.map((g) => ({
name: `${$t('general.group')} ${g}`,
imgUrl: '/img-group.png',
})),
]"
/>
</template>
<template v-else>-</template>
</span>
</div>
</div>
</header>
<!-- SEC: Body -->
<main 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" :key="pageState.currentStep">
<!-- step -->
<nav
v-if="flow"
class="surface-1 q-pa-sm row no-wrap full-width scroll rounded"
style="gap: 10px"
>
<span
v-if="
workList?.every((v) =>
v.productService.work?.attributes?.workflowStep?.every(
(s: any) => {
s.attributes?.properties.length === 0;
},
),
)
"
class="app-text-muted q-py-sm"
>
{{ $t('requestList.noWorkflowTemplate') }}
</span>
<template v-for="(value, i) in flow.step" :key="value.id">
<StateButton
@click="() => (pageState.currentStep = value.order)"
:status-waiting="
!workList
?.filter((v) => {
return v.productService.work?.attributes.workflowStep?.[
i - 1
]?.productsId.includes(v.productService.productId);
})
.every((v) => {
const status = v.stepStatus.find(
({ step }) => step === i,
)?.workStatus;
return (
status === RequestWorkStatus.Completed ||
status === RequestWorkStatus.Ended ||
status === RequestWorkStatus.Canceled
);
})
"
:status-done="
workList
?.filter((v) => {
return v.productService.work?.attributes.workflowStep?.[
i
]?.productsId.includes(v.productService.productId);
})
.every((v) => {
const status = v.stepStatus.find(
({ step }) => step === i + 1,
)?.workStatus;
return (
status === RequestWorkStatus.Completed ||
status === RequestWorkStatus.Ended ||
status === RequestWorkStatus.Canceled ||
(data?.requestDataStatus === 'Canceled' && !status)
);
})
"
:status-active="pageState.currentStep === value.order"
:label="value.name"
/>
<!-- 'quotation-status-active': value.active?.(), -->
<!-- @click="'waiting' !== 'waiting' && value.handler()" -->
</template>
</nav>
<!-- meta data -->
<article class="surface-1 rounded">
<div
class="text-weight-bold row items-center no-wrap q-pa-sm"
style="gap: 16px"
>
<q-img src="/images/quotation-avatar.png" width="42px" />
<span class="ellipsis" style="font-size: 18px">
{{ data.quotation.workName || '-' }}
</span>
<q-btn
class="q-ml-sm"
icon="mdi-pin-outline"
color="primary"
size="sm"
flat
dense
rounded
@click="pageState.hideMetaData = !pageState.hideMetaData"
:style="pageState.hideMetaData ? 'rotate: 90deg' : ''"
style="transition: 0.1s ease-in-out"
/>
</div>
<transition name="slide">
<section
v-if="!pageState.hideMetaData"
class=""
:class="{ row: $q.screen.gt.sm, column: $q.screen.lt.md }"
>
<FormGroupHead class="col-12">
{{ $t('requestList.ref') }}
</FormGroupHead>
<div
class="col-12 q-pa-sm"
:class="{
row: $q.screen.gt.sm,
'column q-gutter-y-sm': $q.screen.lt.md,
}"
>
<DataDisplay
clickable
class="col"
icon="mdi-file-document-outline"
:label="$t('requestList.quotationCode')"
:value="data.quotation.code || '-'"
@label-click="
data.quotation.isDebitNote
? goToDebitNote({ id: data.quotation.id, tab: 'title' })
: goToQuotation(data.quotation)
"
/>
<DataDisplay
clickable
class="col"
icon="mdi-file-document-outline"
tooltip
:label="$t('requestList.invoiceCode')"
:value="
data.quotation?.invoice?.map((i: Invoice) => i.code)
"
@label-click="
(value: string, index: number | null) => {
if (!data || index === null) return;
data.quotation.isDebitNote
? goToDebitNote({
id: data.quotation.id,
tab: 'payment',
})
: goToQuotation(data.quotation, {
tab: 'invoice',
id: data.quotation.id,
amount: data.quotation.invoice?.[index]?.amount,
});
}
"
/>
<DataDisplay
clickable
class="col"
icon="mdi-file-document-outline"
tooltip
:label="$t('requestList.receiptCode')"
:value="
data.quotation?.invoice?.flatMap(
(i: Invoice) => i.payment?.code || [],
)
"
@click="
data.quotation.isDebitNote
? goToDebitNote({
id: data.quotation.id,
tab: 'receipt',
})
: goToQuotation(data.quotation, { tab: 'receipt' })
"
/>
<DataDisplay
v-if="data.quotation.isDebitNote"
clickable
class="col"
icon="mdi-file-document-outline"
:label="$t('requestList.referenceNo')"
:value="data.quotation.debitNoteQuotation.code || '-'"
@label-click="
goToQuotation(data.quotation, {
id: data.quotation.debitNoteQuotationId,
})
"
/>
<div v-if="$q.screen.gt.sm" class="col"></div>
</div>
<FormGroupHead class="col-12">
{{ $t('quotation.employee') }}
</FormGroupHead>
<div
class="col-12 q-pa-sm"
:class="{
row: $q.screen.gt.sm,
'column q-gutter-y-sm': $q.screen.lt.md,
}"
>
<DataDisplay
:clickable="canAccess('customer', 'view')"
class="col"
icon="mdi-account-settings-outline"
:label="$t('customer.employer')"
:value="
getCustomerName(data.quotation.customerBranch, {
locale: locale,
noCode: true,
}) || '-'
"
@label-click="toCustomer(data.quotation.customerBranch)"
/>
<DataDisplay
:clickable="canAccess('customer', 'view')"
class="col"
icon="mdi-account-settings-outline"
:label="$t('customer.employee')"
:value="
getEmployeeName(data.employee, {
locale: $i18n.locale,
}) || '-'
"
@label-click="toEmployee(data.employee)"
/>
<DataDisplay
class="col"
icon="mdi-passport"
:label="$t('customerEmployee.form.passportNo')"
:value="data.employee.employeePassport?.[0]?.number || '-'"
/>
<div v-if="$q.screen.gt.sm" class="col"></div>
</div>
<FormGroupHead class="col-12">
{{ $t('general.contactName') }}
</FormGroupHead>
<div
class="col-12 q-pa-sm"
:class="{
row: $q.screen.gt.sm,
'column q-gutter-y-sm': $q.screen.lt.md,
}"
>
<DataDisplay
class="col"
icon="mdi-account-settings-outline"
:label="$t('quotation.actor')"
:value="
getEmployeeName(data.quotation?.createdBy, {
locale: $i18n.locale,
}) || '-'
"
/>
<DataDisplay
class="col"
icon="mdi-account-settings-outline"
:label="$t('quotation.contactName')"
:value="data.quotation?.contactName || '-'"
/>
<DataDisplay
class="col"
icon="mdi-telephone-outline"
:label="$t('general.telephone')"
:value="data.quotation.contactTel || '-'"
/>
<div v-if="$q.screen.gt.sm" class="col"></div>
</div>
</section>
</transition>
</article>
<!-- product -->
<template
v-for="(value, index) in productsList.map((v) =>
Object.assign(v, {
_readonly:
data.requestDataStatus === RequestDataStatus.Canceled ||
(responsibleList &&
(!!responsibleList[pageState.currentStep]?.user?.length ||
!!responsibleList[pageState.currentStep]?.group
?.length) &&
!responsibleList[pageState.currentStep]?.user.find(
(v) => v.id === getUserId(),
) &&
!responsibleList[pageState.currentStep]?.group.some((v) =>
currentUserGroup.includes(v),
)),
}),
)"
:key="value"
>
<ProductExpansion
:request-cancel="value.customerRequestCancel"
:request-cancel-reason="value.customerRequestCancelReason"
:reject-request-cancel="value.rejectRequestCancel"
:reject-request-cancel-reason="value.rejectRequestCancelReason"
:cancel="data.requestDataStatus === RequestDataStatus.Canceled"
:readonly="value._readonly"
:order-able="value._messengerExpansion"
:installment-info="getInstallmentInfo()"
:pay-success="
isInstallmentPaySuccess(value.productService.installmentNo)
"
:status="
value.stepStatus?.find((v) => v.step === pageState.currentStep)
"
:installment-no="value.productService.installmentNo"
:pay-condition="data?.quotation.payCondition"
:img-url="`/product/${value.productService.productId}/image/${value.productService.product.selectedImage}`"
:name="value.productService.product.name"
:code="value.productService.product.code"
:product="value.productService.product"
@change-status="
(v) => {
triggerChangeStatusWork(
{
workStatus: v.requestWorkStatus,
step:
v.step === undefined
? pageState.currentStep
: v.step.step,
requestWorkId: value.id || '',
},
value.stepStatus?.[pageState.currentStep - 1]
?.responsibleUserId ?? data.defaultMessengerId,
);
}
"
>
<template v-slot="{ product }">
<section
class="column surface-1 q-px-sm bordered-t q-pb-sm q-gutter-y-sm"
>
<DocumentExpansion
v-if="value._documentExpansion"
:readonly="value._readonly"
ref="refDocumentExpansion"
:attributes="value.attributes"
@change-status="
(opt) => {
triggerChangeStatusFile({
index,
id: value.id || '',
documentType: opt.key || '',
status: opt.status,
type: opt.type || 'customer',
});
}
"
@view-doc="
(opt) => {
triggerViewFile({
id: opt.id,
fileName: opt.data.fileName,
type: opt.type,
group: opt.data.documentType,
});
}
"
@upload="
async (opt, done) => {
await triggerUpload({ ...opt });
await done(opt.type || 'customer');
}
"
@download="
(opt) => {
triggerViewFile({
id: opt.id,
fileName: opt.data.fileName,
type: opt.type,
group: opt.data.documentType,
download: true,
});
}
"
:current-id="{
customer: value.request.quotation.customerBranchId,
employee: value.request.employeeId,
}"
:listDocument="product?.document"
/>
<MessengerExpansion
v-if="value._messengerExpansion"
:readonly="value._readonly"
:default-messenger="
value.stepStatus[pageState.currentStep - 1]
? undefined
: data.defaultMessengerId
"
:step="{
step: pageState.currentStep,
requestWorkId: value.id || '',
}"
:id="value.id"
:attributes-form="
value.stepStatus[pageState.currentStep - 1]
"
:responsible-area-district-id="
data.quotation.customerBranch.districtId
"
@update-attributes="
(opt) => {
value.stepStatus[pageState.currentStep - 1] = opt;
}
"
/>
<DutyExpansion
v-if="value._dutyExpansion"
:readonly="value._readonly"
:step="{
step: pageState.currentStep,
requestWorkId: value.id || '',
}"
:id="value.id"
:attributes-form="
value.stepStatus?.[pageState.currentStep - 1]
"
/>
<FormExpansion
v-if="value._formExpansion"
:request-work-id="value.id"
:readonly="value._readonly"
:step="{
step: pageState.currentStep,
requestWorkId: value.id || '',
}"
:id="value.id"
:attributes-form="
value.stepStatus?.[pageState.currentStep - 1]
"
/>
<PropertiesExpansion
:request-list-data="data"
:id="value.id"
:readonly="value._readonly"
:properties-to-show="
value.productService.work?.attributes.workflowStep[
pageState.currentStep - 1
].attributes.properties
"
:attributes="value.attributes"
/>
</section>
</template>
</ProductExpansion>
</template>
</div>
</section>
</main>
<!-- SEC: Footer -->
<footer
class="surface-1 q-pa-md full-width row justify-end"
style="gap: var(--size-2)"
>
<CancelButton
outlined
@click="closeTab()"
:label="$t('dialog.action.close')"
/>
<MainButton
v-if="
productsList.some((v) => v._messengerExpansion) &&
!(
data.requestDataStatus === RequestDataStatus.Completed ||
data.requestDataStatus === RequestDataStatus.Canceled ||
(responsibleList &&
!responsibleList[pageState.currentStep]?.user.find(
(v) => v.id === getUserId(),
) &&
!responsibleList[pageState.currentStep]?.group.some((v) =>
currentUserGroup.includes(v),
)) ||
(!!responsibleList[pageState.currentStep]?.user?.length &&
!!responsibleList[pageState.currentStep]?.user?.length)
)
"
solid
icon="mdi-account-outline"
color="207 96% 32%"
@click="openRequestAction"
>
{{
$t('general.manage', { text: $t('requestList.employeeMessenger') })
}}
</MainButton>
</footer>
</div>
<RequestAction
v-model="pageState.requestActionDialog"
:work="workList"
:responsible-district-id="data?.quotation.customerBranch.districtId || ''"
@submit="submitRequestAction"
/>
<DialogFormContainer
width="60vw"
height="40vh"
v-model="pageState.rejectCancelDialog"
@submit="() => submitRejectCancel()"
>
<template #header>
<DialogHeader :title="$t('requestList.status.work.RejectCancel')" />
</template>
<section class="col q-pa-md scroll">
<FormCancel v-model:reason="pageState.rejectCancelReason" />
</section>
<template #footer>
<CancelButton
class="q-ml-auto"
outlined
@click="() => (pageState.rejectCancelDialog = false)"
/>
<SaveButton
label="ยืนยัน"
class="q-ml-sm"
icon="mdi-check"
solid
type="submit"
/>
</template>
</DialogFormContainer>
</template>
<style scoped></style>