383 lines
11 KiB
Vue
383 lines
11 KiB
Vue
<script lang="ts" setup>
|
|
import { ref } from 'vue';
|
|
import { QTableProps, QTableSlots } from 'quasar';
|
|
|
|
import { canAccess } from 'src/stores/utils';
|
|
|
|
import QuotationCard from 'src/components/05_quotation/QuotationCard.vue';
|
|
import BadgeComponent from 'src/components/BadgeComponent.vue';
|
|
import KebabAction from 'src/components/shared/KebabAction.vue';
|
|
|
|
import useOptionStore from 'src/stores/options';
|
|
import { column } from './constants';
|
|
import { dateFormatJS } from 'src/utils/datetime';
|
|
import { TaskOrder, TaskOrderStatus } from 'src/stores/task-order/types';
|
|
import { Lang } from 'src/utils/ui';
|
|
|
|
const optionStore = useOptionStore();
|
|
const selectedTask = defineModel<TaskOrder[]>('selectedTask', { default: [] });
|
|
const props = withDefaults(
|
|
defineProps<{
|
|
rows: QTableProps['rows'];
|
|
grid?: boolean;
|
|
visibleColumns?: string[];
|
|
selection?: 'single' | 'multiple' | 'none';
|
|
receive?: boolean;
|
|
}>(),
|
|
{
|
|
rows: () => [],
|
|
grid: false,
|
|
receive: false,
|
|
selection: 'none',
|
|
visibleColumns: () => [
|
|
'createdAt',
|
|
'order',
|
|
'taskName',
|
|
'issueBranch',
|
|
'institution',
|
|
'createdBy',
|
|
'contactTel',
|
|
'contactName',
|
|
'taskStatus',
|
|
],
|
|
},
|
|
);
|
|
|
|
const currentBtnOpen = ref<boolean[]>([]);
|
|
|
|
function taskOrderStatus(value: TaskOrderStatus, type: 'status' | 'color') {
|
|
return {
|
|
[TaskOrderStatus.Pending]: {
|
|
status: props.receive ? 'taskOrder.taskInCart' : 'taskOrder.title',
|
|
color: '--blue-6-hsl',
|
|
},
|
|
[TaskOrderStatus.InProgress]: {
|
|
status: 'taskOrder.inProgress',
|
|
color: props.receive ? '--blue-6-hsl' : '--orange-5-hsl',
|
|
},
|
|
[TaskOrderStatus.Validate]: {
|
|
status: 'taskOrder.inProgress',
|
|
color: props.receive ? '--blue-6-hsl' : '--orange-5-hsl',
|
|
},
|
|
[TaskOrderStatus.Complete]: {
|
|
status: props.receive ? 'taskOrder.sentTask' : 'taskOrder.goodReceipt',
|
|
color: props.receive ? '--blue-6-hsl' : '--green-8-hsl',
|
|
},
|
|
[TaskOrderStatus.Accept]: {
|
|
status: 'taskOrder.receiveTask',
|
|
color: '--blue-6-hsl',
|
|
},
|
|
[TaskOrderStatus.Submit]: {
|
|
status: 'taskOrder.sentTask',
|
|
color: '--blue-6-hsl',
|
|
},
|
|
[TaskOrderStatus.Restart]: {
|
|
status: 'taskOrder.status.Restart',
|
|
color: '--red-5-hsl',
|
|
},
|
|
}[value][type];
|
|
}
|
|
|
|
function getCreatedByName(
|
|
record: TaskOrder,
|
|
opts?: {
|
|
locale?: string;
|
|
},
|
|
) {
|
|
const _user = record.createdBy;
|
|
switch (opts?.locale) {
|
|
case Lang.English:
|
|
return `${optionStore.mapOption(_user?.namePrefix) || ''} ${_user?.firstNameEN} ${_user?.lastNameEN}`;
|
|
default:
|
|
return `${optionStore.mapOption(_user?.namePrefix) || ''} ${_user?.firstName} ${_user?.lastName}`;
|
|
}
|
|
}
|
|
|
|
function openList(index: number, data: TaskOrder) {
|
|
if (!currentBtnOpen.value[index]) {
|
|
// currentBtnOpen.value.map((v, i) => {
|
|
// if (i !== index) {
|
|
// currentBtnOpen.value[i] = false;
|
|
// }
|
|
// });
|
|
emit('clickSubRow', index, data);
|
|
}
|
|
currentBtnOpen.value[index] = !currentBtnOpen.value[index];
|
|
}
|
|
|
|
const emit = defineEmits<{
|
|
(e: 'view', data: TaskOrder): void;
|
|
(e: 'edit', data: TaskOrder): void;
|
|
(e: 'delete', data: TaskOrder): void;
|
|
(e: 'clickSubRow', index: number, data: TaskOrder): void;
|
|
}>();
|
|
</script>
|
|
<template>
|
|
<q-table
|
|
id="table-task-order"
|
|
v-bind="props"
|
|
:columns="column"
|
|
bordered
|
|
flat
|
|
hide-pagination
|
|
card-container-class="q-col-gutter-sm"
|
|
:rows-per-page-options="[0]"
|
|
class="full-width"
|
|
:no-data-label="$t('general.noDataTable')"
|
|
row-key="id"
|
|
v-model:selected="selectedTask"
|
|
hide-selected-banner
|
|
>
|
|
<template v-slot:header="props">
|
|
<q-tr
|
|
style="background-color: hsla(var(--info-bg) / 0.07)"
|
|
:props="props"
|
|
>
|
|
<q-th v-if="selection !== 'none'">
|
|
<q-checkbox
|
|
id="checkbox-select-all-task"
|
|
v-model="props.selected"
|
|
size="sm"
|
|
/>
|
|
</q-th>
|
|
<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: TaskOrder;
|
|
} & Omit<Parameters<QTableSlots['body']>[0], 'row'>"
|
|
>
|
|
<q-tr class="text-center" :class="{ urgent: props.row.urgent }">
|
|
<q-td v-if="selection !== 'none'">
|
|
<q-checkbox
|
|
:id="`checkbox-task-${props.row.code}`"
|
|
v-model="props.selected"
|
|
size="sm"
|
|
/>
|
|
</q-td>
|
|
<q-td v-if="visibleColumns.includes('order')">
|
|
{{ props.rowIndex + 1 }}
|
|
</q-td>
|
|
<q-td
|
|
v-if="visibleColumns.includes('taskName')"
|
|
class="text-left"
|
|
:id="`task-name-${props.row.code}`"
|
|
>
|
|
<div :id="`task-name-div-${props.row.code}`">
|
|
{{ props.row.taskName || '-' }}
|
|
<q-tooltip :delay="300">
|
|
{{ props.row.taskName || '-' }}
|
|
</q-tooltip>
|
|
</div>
|
|
<div class="text-caption app-text-muted">
|
|
{{
|
|
(props.row.taskOrderStatus === TaskOrderStatus.Complete &&
|
|
props.row.codeProductReceived
|
|
? props.row.codeProductReceived
|
|
: props.row.code) || '-'
|
|
}}
|
|
</div>
|
|
</q-td>
|
|
<q-td
|
|
v-if="visibleColumns.includes('issueBranch')"
|
|
:id="`task-issue-branch-${props.row.code}`"
|
|
>
|
|
{{
|
|
$i18n.locale === 'eng'
|
|
? props.row.registeredBranch.nameEN || '-'
|
|
: props.row.registeredBranch.name || '-'
|
|
}}
|
|
</q-td>
|
|
<q-td
|
|
v-if="visibleColumns.includes('institution')"
|
|
:id="`task-institution-${props.row.code}`"
|
|
>
|
|
{{
|
|
$i18n.locale === 'eng'
|
|
? props.row.institution.nameEN || '-'
|
|
: props.row.institution.name || '-'
|
|
}}
|
|
</q-td>
|
|
<q-td
|
|
v-if="visibleColumns.includes('createdAt')"
|
|
:id="`task-created-at-${props.row.code}`"
|
|
>
|
|
{{ dateFormatJS({ date: props.row.createdAt }) || '-' }}
|
|
{{ dateFormatJS({ date: props.row.createdAt, timeOnly: true }) }}
|
|
</q-td>
|
|
<q-td
|
|
v-if="visibleColumns.includes('createdBy')"
|
|
class="text-left"
|
|
:id="`task-created-by-${props.row.code}`"
|
|
>
|
|
{{ getCreatedByName(props.row, $i18n) }}
|
|
</q-td>
|
|
<q-td
|
|
v-if="visibleColumns.includes('contactTel')"
|
|
:id="`task-contact-tel-${props.row.code}`"
|
|
>
|
|
{{ props.row.contactTel || '-' }}
|
|
</q-td>
|
|
<q-td
|
|
v-if="visibleColumns.includes('contactName')"
|
|
class="text-left"
|
|
:id="`task-contact-name-${props.row.code}`"
|
|
>
|
|
{{ props.row.contactName || '-' }}
|
|
</q-td>
|
|
<q-td v-if="visibleColumns.includes('taskStatus')">
|
|
<BadgeComponent
|
|
hide-icon
|
|
:hsla-color="taskOrderStatus(props.row.taskOrderStatus, 'color')"
|
|
:title="$t(taskOrderStatus(props.row.taskOrderStatus, 'status'))"
|
|
:id="`badge-task-status-${props.row.code}`"
|
|
/>
|
|
</q-td>
|
|
<q-td v-if="selection === 'none'">
|
|
<q-btn
|
|
:id="`btn-view-task-${props.row.code}`"
|
|
icon="mdi-eye-outline"
|
|
size="sm"
|
|
dense
|
|
round
|
|
flat
|
|
@click.stop="$emit('view', props.row)"
|
|
/>
|
|
<KebabAction
|
|
v-if="
|
|
!receive &&
|
|
props.row.taskOrderStatus === TaskOrderStatus.Pending &&
|
|
canAccess('taskOrder', 'edit')
|
|
"
|
|
:hide-delete="!canAccess('taskOrder', 'create')"
|
|
:idName="`btn-kebab-${props.row.code}`"
|
|
status="'ACTIVE'"
|
|
hide-toggle
|
|
@view="$emit('view', props.row)"
|
|
@edit="$emit('edit', props.row)"
|
|
@delete="$emit('delete', props.row)"
|
|
/>
|
|
</q-td>
|
|
<q-td v-else>
|
|
<q-btn
|
|
dense
|
|
flat
|
|
:id="`btn-sub-row-${props.row.code}`"
|
|
class="rounded"
|
|
@click.stop="
|
|
() => {
|
|
openList(props.rowIndex, props.row);
|
|
}
|
|
"
|
|
>
|
|
<div class="row items-center no-wrap">
|
|
<q-icon name="mdi-account-group-outline" />
|
|
<q-icon
|
|
class="btn-arrow-right"
|
|
:class="{
|
|
active: currentBtnOpen[props.rowIndex],
|
|
}"
|
|
size="xs"
|
|
:name="`mdi-chevron-${currentBtnOpen[props.rowIndex] ? 'down' : 'up'}`"
|
|
/>
|
|
</div>
|
|
</q-btn>
|
|
</q-td>
|
|
</q-tr>
|
|
|
|
<q-tr v-show="currentBtnOpen[props.rowIndex]" :props="props">
|
|
<q-td colspan="100%" style="padding: 16px">
|
|
<slot name="subRow" :props="props"></slot>
|
|
</q-td>
|
|
</q-tr>
|
|
</template>
|
|
|
|
<template v-slot:item="props">
|
|
<div class="col-md-4 col-sm-6 col-12">
|
|
<!-- TODO: status -->
|
|
<QuotationCard
|
|
:status="$t(taskOrderStatus(props.row.taskOrderStatus, 'status'))"
|
|
:badge-color="taskOrderStatus(props.row.taskOrderStatus, 'color')"
|
|
hide-preview
|
|
:hideKebabDelete="!canAccess('taskOrder', 'create')"
|
|
:hide-action="
|
|
receive ||
|
|
props.row.taskOrderStatus !== TaskOrderStatus.Pending ||
|
|
!canAccess('taskOrder', 'edit')
|
|
"
|
|
:code="props.row.code"
|
|
:title="props.row.taskName"
|
|
:custom-data="[
|
|
{
|
|
label: $t('taskOrder.issueBranch'),
|
|
value:
|
|
$i18n.locale === 'eng'
|
|
? props.row.registeredBranch.nameEN || '-'
|
|
: props.row.registeredBranch.name || '-',
|
|
},
|
|
{
|
|
label: $t('general.agencies'),
|
|
value:
|
|
$i18n.locale === 'eng'
|
|
? props.row.institution.nameEN
|
|
: props.row.institution.name,
|
|
},
|
|
{
|
|
label: $t('taskOrder.issueDate'),
|
|
value: `${dateFormatJS({ date: props.row.createdAt })} ${dateFormatJS(
|
|
{
|
|
date: props.row.createdAt,
|
|
timeOnly: true,
|
|
},
|
|
)}`,
|
|
},
|
|
{
|
|
label: $t('taskOrder.madeBy'),
|
|
value: getCreatedByName(props.row, $i18n),
|
|
},
|
|
{
|
|
label: $t('general.telephone'),
|
|
value: props.row.contactTel,
|
|
},
|
|
{
|
|
label: $t('taskOrder.contactName'),
|
|
value: props.row.contactName,
|
|
},
|
|
]"
|
|
@view="$emit('view', props.row)"
|
|
@edit="$emit('edit', props.row)"
|
|
@delete="$emit('delete', props.row)"
|
|
/>
|
|
</div>
|
|
</template>
|
|
</q-table>
|
|
</template>
|
|
<style scoped>
|
|
:deep(.q-table tbody td:after) {
|
|
background: transparent;
|
|
}
|
|
|
|
.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;
|
|
}
|
|
}
|
|
</style>
|