jws-frontend/src/components/03_customer-management/HistoryEditComponent.vue

458 lines
11 KiB
Vue
Raw Normal View History

2024-06-27 10:23:54 +00:00
<script setup lang="ts">
import { useI18n } from 'vue-i18n';
import { QTableColumn } from 'quasar';
2024-06-28 08:10:14 +00:00
import { EmployeeHistory, NewEmployeeHistory } from 'src/stores/employee/types';
import { dateFormat } from 'src/utils/datetime';
import NoData from '../NoData.vue';
import { computed, onMounted, ref, watch } from 'vue';
import useOptionStore from 'src/stores/options';
2024-06-27 10:23:54 +00:00
const { t } = useI18n();
2024-06-28 08:10:14 +00:00
const optionStore = useOptionStore();
const historyList = defineModel<EmployeeHistory[]>('historyList', {
required: true,
});
2024-06-27 10:23:54 +00:00
const columns: QTableColumn[] = [
{
2024-06-28 08:10:14 +00:00
name: 'updatedAt',
2024-06-27 10:23:54 +00:00
label: t('time'),
2024-06-28 08:10:14 +00:00
field: 'updatedAt',
2024-06-27 10:23:54 +00:00
align: 'left',
headerStyle: 'font-weight: bold',
},
{
name: 'editBy',
align: 'center',
label: t('editBy'),
field: 'editBy',
headerStyle: 'font-weight: bold',
},
{
name: 'history',
align: 'center',
label: '',
field: 'history',
},
{
name: 'valueAfter',
align: 'center',
label: t('valueAfter'),
field: 'valueAfter',
headerStyle: 'font-weight: bold',
},
{
name: 'valueBefore',
align: 'center',
label: t('valueBefore'),
field: 'valueBefore',
headerStyle: 'font-weight: bold',
},
];
2024-06-28 08:10:14 +00:00
const currentDate = ref();
const currentData = ref();
const currentIndex = ref(0);
const formatList = ref<NewEmployeeHistory[]>([]);
const fieldName = [
{
name: 'customerBranchId',
title: 'formDialogTitleInformation',
i18n: 'formDialogEmployerID',
},
{
name: 'nrcNo',
title: 'formDialogTitleInformation',
i18n: 'formDialogEmployeeNRCNo',
},
{
name: 'firstName',
title: 'personalInfo',
i18n: 'formDialogInputFirstName',
},
{
name: 'firstNameEN',
title: 'personalInfo',
i18n: 'formDialogInputFirstNameEN',
},
{ name: 'lastName', title: 'personalInfo', i18n: 'formDialogInputLastName' },
{
name: 'lastNameEN',
title: 'personalInfo',
i18n: 'formDialogInputLastNameEN',
},
{
name: 'dateOfBirth',
title: 'personalInfo',
i18n: 'formDialogInputBirthDate',
},
{ name: 'gender', title: 'personalInfo', i18n: 'formDialogInputGender' },
{
name: 'nationality',
title: 'personalInfo',
i18n: 'formDialogInputNationality',
},
{
name: 'address',
title: 'formDialogTitlePersonnelAddress',
i18n: 'formDialogTitleAddressPure',
},
{
name: 'addressEN',
title: 'formDialogTitlePersonnelAddress',
i18n: 'formDialogTitleAddressPureEN',
},
{
name: 'provinceId',
title: 'formDialogTitlePersonnelAddress',
i18n: 'province',
},
{
name: 'districtId',
title: 'formDialogTitlePersonnelAddress',
i18n: 'district',
},
{
name: 'subDistrictId',
title: 'formDialogTitlePersonnelAddress',
i18n: 'subDistrict',
},
{
name: 'passportType',
title: 'formDialogTitlePassport',
i18n: 'formDialogInputPassportType',
},
{
name: 'passportNumber',
title: 'formDialogTitlePassport',
i18n: 'formDialogInputPassportNo',
},
{
name: 'previousPassportReference',
title: 'formDialogTitlePassport',
i18n: 'formDialogInputPassportRef',
},
{
name: 'passportIssuingPlace',
title: 'formDialogTitlePassport',
i18n: 'formDialogInputWPassportPlace',
},
{
name: 'passportIssuingCountry',
title: 'formDialogTitlePassport',
i18n: 'formDialogInputPassportCountry',
},
{
name: 'passportIssueDate',
title: 'formDialogTitlePassport',
i18n: 'formDialogInputPassportIssuance',
},
{
name: 'passportExpiryDate',
title: 'formDialogTitlePassport',
i18n: 'formDialogInputPassportExpire',
},
{
name: 'visaType',
title: 'formDialogTitleVisa',
i18n: 'formDialogInputVisaType',
},
{
name: 'visaNumber',
title: 'formDialogTitleVisa',
i18n: 'formDialogInputVisaNo',
},
{
name: 'visaIssueDate',
title: 'formDialogTitleVisa',
i18n: 'formDialogInputVisaIssuance',
},
{
name: 'visaExpiryDate',
title: 'formDialogTitleVisa',
i18n: 'formDialogInputVisaExpire',
},
{
name: 'visaIssuingPlace',
title: 'formDialogTitleVisa',
i18n: 'formDialogInputVisaPlace',
},
{
name: 'visaStayUntilDate',
title: 'formDialogTitleVisa',
i18n: 'formDialogInputVisaStayUntil',
},
2024-06-27 10:23:54 +00:00
{
2024-06-28 08:10:14 +00:00
name: 'tm6Number',
title: 'formDialogTitleVisa',
i18n: 'formDialogInputVisaTM6',
2024-06-27 10:23:54 +00:00
},
{
2024-06-28 08:10:14 +00:00
name: 'entryDate',
title: 'formDialogTitleVisa',
i18n: 'formDialogInputVisaEnter',
2024-06-27 10:23:54 +00:00
},
];
2024-06-28 08:10:14 +00:00
function isValidDate(dateString: string): boolean {
if (typeof dateString !== 'string' || dateString.length < 24) {
return false;
}
const date = new Date(dateString);
return !isNaN(date.getTime()) && dateString === date.toISOString();
}
function mapName(field: string): { title: string; i18n: string } {
const fieldData = fieldName.find((item) => item.name === field);
if (fieldData) {
return { title: fieldData.title, i18n: fieldData.i18n };
}
return { title: '-', i18n: '-' };
}
async function groupEmployeeHistory(
historyList: EmployeeHistory[],
): Promise<NewEmployeeHistory[]> {
const grouped = historyList.reduce((acc, curr) => {
const updatedAt = new Date(curr.updatedAt);
const dateKey = `${updatedAt.getFullYear()}-${updatedAt.getMonth() + 1}-${updatedAt.getDate()}`;
const existingEntry = acc.find((entry) => entry.date === dateKey);
if (existingEntry) {
const existingData = existingEntry.data.find(
(data) => data.updatedAt.getTime() === updatedAt.getTime(),
);
if (existingData) {
existingData.history.push({
valueAfter: curr.valueAfter,
valueBefore: curr.valueBefore,
field: curr.field,
});
} else {
existingEntry.data.push({
masterId: curr.masterId,
updatedBy: curr.updatedBy,
updatedByUserId: curr.updatedByUserId,
timestamp: curr.timestamp,
updatedAt: updatedAt,
id: curr.id,
history: [
{
valueAfter: curr.valueAfter,
valueBefore: curr.valueBefore,
field: curr.field,
},
],
});
}
} else {
acc.push({
date: dateKey,
data: [
{
masterId: curr.masterId,
updatedBy: curr.updatedBy,
updatedByUserId: curr.updatedByUserId,
timestamp: curr.timestamp,
updatedAt: updatedAt,
id: curr.id,
history: [
{
valueAfter: curr.valueAfter,
valueBefore: curr.valueBefore,
field: curr.field,
},
],
},
],
});
}
return acc;
}, [] as NewEmployeeHistory[]);
return grouped;
}
onMounted(async () => {
const newList = await groupEmployeeHistory(historyList.value);
formatList.value = newList;
currentDate.value = formatList.value[currentIndex.value].date;
currentData.value = formatList.value[currentIndex.value].data;
});
watch(
() => currentIndex.value,
(i) => {
currentDate.value = formatList.value[i].date;
currentData.value = formatList.value[i].data;
},
);
2024-06-27 10:23:54 +00:00
</script>
2024-06-27 09:21:43 +07:00
<template>
2024-06-27 10:23:54 +00:00
<div class="full-width">
<div
class="row full-width justify-center q-py-sm header-border"
style="background: hsla(var(--info-bg) / 0.1)"
>
<div class="surface-1 q-py-sm q-px-sm row items-center">
2024-06-28 08:10:14 +00:00
<q-btn
flat
:disable="currentIndex === formatList.length - 1"
color="info"
padding="0"
icon="mdi-chevron-left"
@click="currentIndex++"
/>
2024-06-27 10:23:54 +00:00
<span class="text-weight-medium q-px-xl">
2024-06-28 08:10:14 +00:00
{{ dateFormat(currentDate) }}
2024-06-27 10:23:54 +00:00
</span>
2024-06-28 08:10:14 +00:00
<q-btn
flat
:disable="currentIndex === 0"
color="info"
padding="0"
icon="mdi-chevron-right"
@click="currentIndex--"
/>
2024-06-27 10:23:54 +00:00
</div>
</div>
<q-table
2024-06-28 08:10:14 +00:00
v-if="currentData?.length > 0"
2024-06-27 10:23:54 +00:00
flat
class="table-border"
table-header-class="surface-2"
2024-06-28 08:10:14 +00:00
:rows="currentData"
2024-06-27 10:23:54 +00:00
:columns="columns"
row-key="name"
>
<template v-slot:body="props">
<q-tr
:props="props"
2024-06-28 08:10:14 +00:00
:style="`background-color: ${props.rowIndex % 2 === 0 ? '' : 'var(--surface-2)'}`"
2024-06-27 10:23:54 +00:00
>
2024-06-28 08:10:14 +00:00
<q-td key="updatedAt" :props="props">
{{ dateFormat(props.row.updatedAt, false, true, true) }}
2024-06-27 10:23:54 +00:00
</q-td>
<q-td key="editBy" :props="props">
<div class="row items-center no-wrap">
2024-06-28 08:10:14 +00:00
<q-avatar class="surface-tab">
<img v-if="false" src="https://cdn.quasar.dev/img/avatar.png" />
<q-icon v-else name="mdi-account"></q-icon>
2024-06-27 10:23:54 +00:00
</q-avatar>
<div class="column q-pl-md items-start">
<span class="text-weight-bold">
2024-06-28 08:10:14 +00:00
{{ props.row.editBy ?? '-' }}
2024-06-27 10:23:54 +00:00
</span>
2024-06-28 08:10:14 +00:00
<!-- <span class="text-caption">กบรหาร</span> -->
2024-06-27 10:23:54 +00:00
</div>
</div>
</q-td>
<q-td key="history" :props="props">
<q-stepper vertical flat>
<q-step
2024-06-28 08:10:14 +00:00
v-for="(item, index) in props.row.history"
2024-06-27 10:23:54 +00:00
:key="index"
:name="1"
2024-06-28 08:10:14 +00:00
:title="$t(mapName(item.field).title)"
:caption="$t(mapName(item.field).i18n)"
2024-06-27 10:23:54 +00:00
:icon="`mdi-numeric-${props.row.history.length - index}`"
2024-06-28 08:10:14 +00:00
>
asd
</q-step>
2024-06-27 10:23:54 +00:00
</q-stepper>
</q-td>
<q-td key="valueBefore" :props="props">
<div
2024-06-28 08:10:14 +00:00
v-for="(i, index) in props.row.history"
2024-06-27 10:23:54 +00:00
:key="index"
class="q-py-md"
>
2024-06-28 08:10:14 +00:00
{{
isValidDate(i.valueBefore) === true
? dateFormat(i.valueBefore)
: optionStore.mapOption(i.valueBefore)
}}
2024-06-27 10:23:54 +00:00
</div>
</q-td>
<q-td key="valueAfter" :props="props">
<div
2024-06-28 08:10:14 +00:00
v-for="(i, index) in props.row.history"
2024-06-27 10:23:54 +00:00
:key="index"
class="q-py-md"
>
2024-06-28 08:10:14 +00:00
{{
isValidDate(i.valueAfter) === true
? dateFormat(i.valueAfter)
: optionStore.mapOption(i.valueAfter)
}}
2024-06-27 10:23:54 +00:00
</div>
</q-td>
</q-tr>
</template>
</q-table>
2024-06-28 08:10:14 +00:00
<div v-else class="table-border flex items-center justify-center q-py-lg">
<NoData />
</div>
2024-06-27 09:21:43 +07:00
</div>
</template>
2024-06-27 10:23:54 +00:00
<style lang="scss" scoped>
.header-border {
border: 1px solid var(--border-color);
border-top-left-radius: var(--radius-2);
border-top-right-radius: var(--radius-2);
}
.table-border {
border-bottom: 1px solid var(--border-color);
border-left: 1px solid var(--border-color);
border-right: 1px solid var(--border-color);
border-top-left-radius: 0 !important;
border-top-right-radius: 0 !important;
border-bottom-left-radius: var(--radius-2);
border-bottom-right-radius: var(--radius-2);
}
:deep(.q-stepper.q-stepper--vertical.q-stepper--flat) {
background-color: transparent;
padding: 0;
text-align: left;
}
:deep(.q-stepper__caption) {
color: hsl(var(--text-mute-2)) !important;
}
:deep(.q-stepper__title) {
color: hsl(var(--info-bg)) !important;
}
:deep(i.q-icon.mdi) {
font-size: 18px !important;
}
:deep(.q-stepper__dot.row.flex-center.q-stepper__line.relative-position) {
color: hsl(var(--info-bg)) !important;
}
</style>