jws-frontend/src/pages/08_request-list/TableRequestList.vue
2025-03-05 16:58:59 +07:00

359 lines
11 KiB
Vue

<script setup lang="ts">
import { QTable, QTableProps, QTableSlots } from 'quasar';
import { baseUrl } from 'src/stores/utils';
import QuotationCard from 'src/components/05_quotation/QuotationCard.vue';
import BadgeComponent from 'src/components/BadgeComponent.vue';
import AvatarGroup from 'src/components/shared/AvatarGroup.vue';
import { RequestData } from 'src/stores/request-list/types';
import { RequestDataStatus } from 'src/stores/request-list/types';
import { QuotationFull } from 'src/stores/quotations/types';
import useOptionStore from 'src/stores/options';
import KebabAction from 'src/components/shared/KebabAction.vue';
import { CreatedBy } from 'src/stores/types';
const props = withDefaults(
defineProps<{
rows: QTableProps['rows'];
columns: QTableProps['columns'];
grid?: boolean;
visibleColumns?: string[];
}>(),
{
row: () => [],
column: () => [],
grid: false,
visibleColumns: () => [],
},
);
defineEmits<{
(e: 'view', data: RequestData): void;
(e: 'delete', data: RequestData): void;
}>();
function responsiblePerson(quotation: QuotationFull): CreatedBy[] | undefined {
const productServiceList = quotation.productServiceList;
const tempPerson: CreatedBy[] = [];
for (const v of productServiceList) {
const tempStep = v.service?.workflow?.step;
if (tempStep) {
tempStep.forEach((lhs) => {
for (const rhs of lhs.responsiblePerson) {
tempPerson.push(rhs.user);
}
});
return tempPerson;
}
}
return undefined;
}
function getCustomerName(
record: RequestData,
opts?: {
locale?: string;
noCode?: boolean;
},
) {
const customer = record.quotation.customerBranch;
return (
{
['CORP']: {
['eng']: customer.registerNameEN,
['tha']: customer.registerName,
}[opts?.locale || 'eng'],
['PERS']:
{
['eng']: `${useOptionStore().mapOption(customer?.namePrefix || '')} ${customer?.firstNameEN} ${customer?.lastNameEN}`,
['tha']: `${useOptionStore().mapOption(customer?.namePrefix || '')} ${customer?.firstName} ${customer?.lastName}`,
}[opts?.locale || 'eng'] || '-',
}[customer.customer.customerType] +
(opts?.noCode ? '' : ' ' + `(${customer.code})`)
);
}
function getEmployeeName(
record: RequestData,
opts?: {
locale?: string;
},
) {
const employee = record.employee;
return (
{
['eng']: `${useOptionStore().mapOption(employee?.namePrefix || '')} ${employee?.firstNameEN} ${employee?.lastNameEN}`,
['tha']: `${useOptionStore().mapOption(employee?.namePrefix || '')} ${employee?.firstName} ${employee?.lastName}`,
}[opts?.locale || 'eng'] || '-'
);
}
</script>
<template>
<q-table
v-bind="props"
bordered
flat
hide-pagination
card-container-class="q-col-gutter-sm"
:rows-per-page-options="[0]"
class="full-width"
>
<template v-slot:header="props">
<q-tr
style="background-color: hsla(var(--info-bg) / 0.07)"
:props="props"
>
<q-th v-for="col in props.cols" :key="col.name" :props="props">
{{ col.label && $t(col.label) }}
</q-th>
<q-th></q-th>
</q-tr>
</template>
<template
v-slot:body="props: {
row: RequestData;
} & Omit<Parameters<QTableSlots['body']>[0], 'row'>"
>
<q-tr
:class="{ urgent: props.row.quotation.urgent, dark: $q.dark.isActive }"
class="text-center"
>
<q-td v-if="visibleColumns.includes('order')">
{{ props.rowIndex + 1 }}
</q-td>
<q-td v-if="visibleColumns.includes('requestList')" class="text-left">
{{ props.row.quotation.workName || '-' }}
<div class="text-caption app-text-muted">
{{ props.row.code || '-' }}
</div>
</q-td>
<q-td v-if="visibleColumns.includes('employer')" class="text-left">
{{
getCustomerName(props.row, {
noCode: true,
locale: $i18n.locale,
}) || '-'
}}
</q-td>
<q-td v-if="visibleColumns.includes('employee')" class="text-left">
{{ getEmployeeName(props.row, { locale: $i18n.locale }) || '-' }}
</q-td>
<q-td v-if="visibleColumns.includes('quotationCode')">
{{ props.row.quotation.code || '-' }}
</q-td>
<q-td v-if="visibleColumns.includes('responsiblePerson')">
<AvatarGroup
:data="
responsiblePerson(props.row.quotation)?.map((v) => {
return {
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}`,
};
})
"
/>
</q-td>
<q-td v-if="visibleColumns.includes('status')">
<BadgeComponent
:hsla-color="
{
[RequestDataStatus.Pending]: '--orange-5-hsl',
[RequestDataStatus.Ready]: '--yellow-6-hsl',
[RequestDataStatus.InProgress]: '--blue-6-hsl',
[RequestDataStatus.Completed]: '--green-8-hsl',
[RequestDataStatus.Canceled]: '--red-5-hsl',
}[props.row.requestDataStatus]
"
:title="
$t(`requestList.status.${props.row.requestDataStatus}`) || '-'
"
/>
<BadgeComponent
v-if="
props.row.customerRequestCancel &&
props.row.requestDataStatus !== RequestDataStatus.Canceled
"
:hsla-color="'--red-5-hsl'"
class="q-ml-sm"
:title="$t(`requestList.status.CancelRequested`) || '-'"
>
<template #append>
<q-tooltip>
{{
props.row.customerRequestCancelReason ||
$t('general.noReason')
}}
</q-tooltip>
</template>
</BadgeComponent>
</q-td>
<q-td class="text-right">
<q-btn
:id="`btn-eye-${props.row.code}`"
icon="mdi-eye-outline"
size="sm"
dense
round
flat
@click.stop="$emit('view', props.row)"
/>
<KebabAction
:id-name="`btn-kebab-${props.row.code}`"
hide-edit
hide-toggle
hide-view
hide-delete
use-cancel
:disable-cancel="
props.row.requestDataStatus === RequestDataStatus.Canceled ||
props.row.requestDataStatus === RequestDataStatus.Completed
"
@cancel="$emit('delete', props.row)"
/>
</q-td>
</q-tr>
</template>
<template
v-slot:item="props: {
row: RequestData;
} & Omit<Parameters<QTableSlots['body']>[0], 'row'>"
>
<div class="col-md-4 col-sm-6 col-12">
<QuotationCard
hide-preview
hide-kebab-view
hide-kebab-edit
hide-kebab-delete
use-cancel
:disable-cancel="
props.row.requestDataStatus === RequestDataStatus.Canceled ||
props.row.requestDataStatus === RequestDataStatus.Completed
"
:badge-color="
{
[RequestDataStatus.Pending]: '--orange-5-hsl',
[RequestDataStatus.Ready]: '--yellow-6-hsl',
[RequestDataStatus.InProgress]: '--blue-6-hsl',
[RequestDataStatus.Completed]: '--green-8-hsl',
[RequestDataStatus.Canceled]: '--red-5-hsl',
}[props.row.requestDataStatus]
"
:urgent="props.row.quotation.urgent"
:code="props.row.code"
:title="props.row.quotation.workName"
:status="$t(`requestList.status.${props.row.requestDataStatus}`)"
:custom-data="[
{
label: $t('customer.employer'),
value:
getCustomerName(props.row, {
noCode: true,
locale: $i18n.locale,
}) || '-',
},
{
label: $t('customer.employee'),
value:
getEmployeeName(props.row, { locale: $i18n.locale }) || '-',
},
{
label: $t('requestList.quotationCode'),
value: props.row.quotation.code || '-',
},
{
label: $t('flow.responsiblePerson'),
value: '',
slotName: 'responsiblePerson',
},
]"
@view="$emit('view', props.row)"
@cancel="$emit('delete', props.row)"
>
<template v-slot:responsiblePerson="{ props: subProps }">
<div class="col-4 app-text-muted q-pr-sm">
{{ subProps.label }}
</div>
<div class="col-8">
<AvatarGroup
v-if="(responsiblePerson(props.row.quotation) ?? []).length > 0"
:data="
responsiblePerson(props.row.quotation)?.map((v) => {
return {
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}`,
};
})
"
/>
<span v-else>-</span>
</div>
</template>
</QuotationCard>
</div>
</template>
</q-table>
</template>
<style scoped>
:deep(tr:nth-child(2n)) {
background: #f9fafc;
&.dark {
background: hsl(var(--gray-11-hsl) / 0.2);
}
}
.q-table tr.urgent {
background: hsla(var(--red-6-hsl) / 0.03);
}
.q-table tr.urgent td:first-child {
&::after {
content: ' ';
display: block;
position: absolute;
left: 0;
top: 15%;
bottom: 15%;
background: var(--red-8);
width: 4px;
border-radius: 99rem;
animation: blink 1s infinite;
}
}
@keyframes blink {
0% {
background: var(--red-8);
}
50% {
background: var(--red-3);
}
100% {
background: var(--red-8);
}
}
</style>