feat: add reject request cancellation functionality and UI components
Some checks failed
Spell Check / Spell Check with Typos (push) Failing after 9s

This commit is contained in:
puriphatt 2025-03-13 09:47:12 +07:00
parent e1d1ee7d8b
commit c1adc9dee3
2 changed files with 99 additions and 14 deletions

View file

@ -20,6 +20,8 @@ const props = defineProps<{
imgUrl?: string; imgUrl?: string;
requestCancel?: boolean; requestCancel?: boolean;
requestCancelReason?: string; requestCancelReason?: string;
rejectRequestCancel?: boolean;
rejectRequestCancelReason?: string;
installmentInfo?: { installmentInfo?: {
total: number; total: number;
paid?: number; paid?: number;
@ -37,7 +39,9 @@ function changeableStatus(currentStatus?: RequestWorkStatus) {
case RequestWorkStatus.Ready: case RequestWorkStatus.Ready:
case RequestWorkStatus.Waiting: case RequestWorkStatus.Waiting:
case RequestWorkStatus.InProgress: case RequestWorkStatus.InProgress:
return [RequestWorkStatus.Canceled]; return props.requestCancel && !props.rejectRequestCancel
? [RequestWorkStatus.Canceled, RequestWorkStatus.RejectCancel]
: [RequestWorkStatus.Canceled];
case RequestWorkStatus.Validate: case RequestWorkStatus.Validate:
case RequestWorkStatus.Ended: case RequestWorkStatus.Ended:
case RequestWorkStatus.Completed: case RequestWorkStatus.Completed:
@ -45,12 +49,15 @@ function changeableStatus(currentStatus?: RequestWorkStatus) {
return []; return [];
default: default:
if (props.readonly) return []; if (props.readonly) return [];
const statuses = [
RequestWorkStatus.Ready,
RequestWorkStatus.Ended,
RequestWorkStatus.Canceled,
];
if (props.orderAble) { if (props.orderAble) {
return [ return props.requestCancel && !props.rejectRequestCancel
RequestWorkStatus.Ready, ? [...statuses, RequestWorkStatus.RejectCancel]
RequestWorkStatus.Ended, : statuses;
RequestWorkStatus.Canceled,
];
} else { } else {
return [RequestWorkStatus.Ended, RequestWorkStatus.Canceled]; return [RequestWorkStatus.Ended, RequestWorkStatus.Canceled];
} }
@ -103,11 +110,19 @@ function changeableStatus(currentStatus?: RequestWorkStatus) {
v-if="requestCancel && !cancel" v-if="requestCancel && !cancel"
:hsla-color="'--red-5-hsl'" :hsla-color="'--red-5-hsl'"
class="q-ml-sm" class="q-ml-sm"
:title="$t(`requestList.status.CancelRequested`) || '-'" :title="
rejectRequestCancel
? $t('requestList.status.RejectedCancel') || '-'
: $t(`requestList.status.CancelRequested`) || '-'
"
> >
<template #append> <template #append>
<q-tooltip> <q-tooltip>
{{ requestCancelReason || $t('general.noReason') }} {{
rejectRequestCancel
? rejectRequestCancelReason || $t('general.noReason')
: requestCancelReason || $t('general.noReason')
}}
</q-tooltip> </q-tooltip>
</template> </template>
</BadgeComponent> </BadgeComponent>
@ -171,10 +186,11 @@ function changeableStatus(currentStatus?: RequestWorkStatus) {
clickable clickable
v-close-popup v-close-popup
class="row items-center" class="row items-center"
:class="{ 'bordered-t': value === 'RejectCancel' }"
@click=" @click="
$emit('changeStatus', { $emit('changeStatus', {
step: status, step: status,
requestWorkStatus: value, requestWorkStatus: value as RequestWorkStatus,
}) })
" "
> >

View file

@ -11,10 +11,13 @@ import PropertiesExpansion from './PropertiesExpansion.vue';
import FormGroupHead from './FormGroupHead.vue'; import FormGroupHead from './FormGroupHead.vue';
import AvatarGroup from 'src/components/shared/AvatarGroup.vue'; import AvatarGroup from 'src/components/shared/AvatarGroup.vue';
import { StateButton } from 'components/button'; import { StateButton } from 'components/button';
import { CancelButton, MainButton } from 'components/button'; import { CancelButton, MainButton, SaveButton } from 'components/button';
import DutyExpansion from './DutyExpansion.vue'; import DutyExpansion from './DutyExpansion.vue';
import MessengerExpansion from './MessengerExpansion.vue'; import MessengerExpansion from './MessengerExpansion.vue';
import RequestAction from './RequestAction.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 // NOTE: Store
import { import {
@ -104,6 +107,9 @@ const pageState = reactive({
hideMetaData: false, hideMetaData: false,
currentStep: 1, currentStep: 1,
requestActionDialog: false, requestActionDialog: false,
rejectCancelDialog: false,
rejectCancelReason: '',
requestWorkId: '',
}); });
// NOTE: Function // NOTE: Function
@ -173,6 +179,13 @@ async function triggerChangeStatusWork(step: Step, responsibleUserId?: string) {
}); });
return; return;
} }
if (step.workStatus === 'RejectCancel') {
pageState.rejectCancelDialog = true;
pageState.requestWorkId = step.requestWorkId;
return;
}
const res = await requestListStore.editStatusRequestWork(step); const res = await requestListStore.editStatusRequestWork(step);
if (res) { if (res) {
const indexWork = workList.value?.findIndex( const indexWork = workList.value?.findIndex(
@ -400,6 +413,30 @@ async function submitRequestAction(data: {
pageState.requestActionDialog = false; 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;
}
}
</script> </script>
<template> <template>
<div class="column surface-0 fullscreen" v-if="data"> <div class="column surface-0 fullscreen" v-if="data">
@ -599,8 +636,8 @@ async function submitRequestAction(data: {
data.quotation?.invoice?.map((i: Invoice) => i.code) data.quotation?.invoice?.map((i: Invoice) => i.code)
" "
@label-click=" @label-click="
(_: string, i: number) => { (value: string, index: number | null) => {
if (!data) return; if (!data || index === null) return;
data.quotation.isDebitNote data.quotation.isDebitNote
? goToDebitNote({ ? goToDebitNote({
@ -609,8 +646,8 @@ async function submitRequestAction(data: {
}) })
: goToQuotation(data.quotation, { : goToQuotation(data.quotation, {
tab: 'invoice', tab: 'invoice',
id: data.quotation.invoice?.[i]?.id, id: data.quotation.invoice?.[index]?.id,
amount: data.quotation.invoice?.[i]?.amount, amount: data.quotation.invoice?.[index]?.amount,
}); });
} }
" "
@ -733,6 +770,8 @@ async function submitRequestAction(data: {
<ProductExpansion <ProductExpansion
:request-cancel="value.customerRequestCancel" :request-cancel="value.customerRequestCancel"
:request-cancel-reason="value.customerRequestCancelReason" :request-cancel-reason="value.customerRequestCancelReason"
:reject-request-cancel="value.rejectRequestCancel"
:reject-request-cancel-reason="value.rejectRequestCancelReason"
:cancel="data.requestDataStatus === RequestDataStatus.Canceled" :cancel="data.requestDataStatus === RequestDataStatus.Canceled"
:readonly=" :readonly="
data.requestDataStatus === RequestDataStatus.Canceled || data.requestDataStatus === RequestDataStatus.Canceled ||
@ -929,5 +968,35 @@ async function submitRequestAction(data: {
:responsible-district-id="data?.quotation.customerBranch.districtId || ''" :responsible-district-id="data?.quotation.customerBranch.districtId || ''"
@submit="submitRequestAction" @submit="submitRequestAction"
/> />
<DialogFormContainer
width="60vw"
height="50%"
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> </template>
<style scoped></style> <style scoped></style>