jws-frontend/src/pages/09_task-order/TableTaskOrder.vue
Aif 75d5c7dfe8
Some checks failed
Spell Check / Spell Check with Typos (push) Failing after 5s
feat: unique id attributes to UI components
2025-11-11 11:01:36 +07:00

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>