334 lines
13 KiB
Vue
334 lines
13 KiB
Vue
<template>
|
|
<section class="h-screen-main w-full px-4 flex flex-col justify-between items-start">
|
|
<!-- Upload Content -->
|
|
<div class="w-full h-[calc(100%_-_64px)]">
|
|
<!-- File name -->
|
|
<!-- cursor-pointer -->
|
|
<div class="h-12 pl-1 border-b border-neutral-300 flex items-center">
|
|
<InputText type="text" v-model="fileName" id="fileNameInput" class="rounded !border-transparent !font-bold !text-base !py-1 min-w-[1px] w-auto" @input="onInput" @blur="onBlur" maxlength="200" title="Rename"/>
|
|
</div>
|
|
<!-- Upload notification -->
|
|
<div class="flex justify-start items-center space-x-2 ml-2 py-2">
|
|
<span class="material-symbols-outlined text-neutral-700 cursor-pointer" v-tooltip.right="tooltipUpload">info</span>
|
|
<span class="w-px h-7 bg-neutral-300"></span>
|
|
<!-- Upload text -->
|
|
<div>
|
|
<div v-if="!isDisabled"></div>
|
|
<div v-else-if="isInform" class="flex justify-start items-center space-x-2 duration-700">
|
|
<span class="material-symbols-outlined text-primary">notifications</span>
|
|
<p class="text-primary text-sm">
|
|
Please verify the label for each column before uploading.
|
|
</p>
|
|
</div>
|
|
<div v-else class="flex justify-start items-center space-x-2 duration-700">
|
|
<span class="material-symbols-outlined material-fill text-danger">warning</span>
|
|
<p class="text-danger text-sm">
|
|
Need to select
|
|
<span v-for="(item, index) in informData" :key="index">{{ item.label }}<span v-if="index !== informData.length - 1">, </span></span>.
|
|
</p>
|
|
<div v-if="repeatedData.length !== 0" class="duration-700">
|
|
<p v-if="repeatedData.length === 1" class="text-danger text-sm">
|
|
{{repeatedData[0].label}} has been assigned.
|
|
</p>
|
|
<p v-else class="text-danger text-sm">
|
|
<span v-for="(item, index) in repeatedData" :key="index">{{ item.label }}<span v-if="index !== repeatedData.length - 1">, </span></span>
|
|
have been assigned.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<!-- Upload table -->
|
|
<div class="overflow-y-auto overflow-x-auto scrollbar max-h-[calc(100%_-_97px)]">
|
|
<table class="text-sm w-full border-separate border-spacing-0">
|
|
<thead class="sticky top-0 bg-neutral-10">
|
|
<tr>
|
|
<td v-for="(item, index) in uploadDetail?.columns" :key="index" class="border border-neutral-500 p-2">{{ item }}</td>
|
|
</tr>
|
|
<tr>
|
|
<td v-for="(item, index) in uploadDetail?.columns" :key="index" class="px-2 py-1 bg-neutral-300 border border-neutral-500">
|
|
<Dropdown
|
|
v-model="selectedColumns[index]"
|
|
:options="columnType"
|
|
optionLabel="name"
|
|
placeholder="Not Assigned"
|
|
class="w-full !border-neutral-500"
|
|
:data-type="item"
|
|
:inputId="index.toString()"
|
|
:inputClass="selectedColumns[index]?.color"
|
|
inputClass="!text-sm">
|
|
<template #option="slotProps">
|
|
<div :class="slotProps.option.color" class="text-sm">
|
|
<span>{{ slotProps.option.name }}</span>
|
|
</div>
|
|
</template>
|
|
</Dropdown>
|
|
</td>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr v-for="(item, index) in uploadDetail?.data" :key="index">
|
|
<td v-for="(itemDetail, key) in item" :key="key" class="border border-neutral-500 p-2 truncate break-keep">{{ itemDetail }}</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
<!-- Upload button -->
|
|
<div class="w-full text-right space-x-4 px-8 py-4">
|
|
<button type="button" class="btn btn-sm btn-neutral" @click="cancel">Cancel</button>
|
|
<button type="button" class="btn btn-sm btn-neutral" @click="reset">Reset</button>
|
|
<button type="button" class="btn btn-sm" @click="submit" :disabled="isDisabled" :class="isDisabled ? 'btn-disable' : 'btn-neutral'">Upload</button>
|
|
</div>
|
|
</section>
|
|
</template>
|
|
<script>
|
|
import { storeToRefs } from 'pinia';
|
|
import LoadingStore from '@/stores/loading.js';
|
|
import FilesStore from '@/stores/files.js';
|
|
import { uploadFailedFirst, uploadSuccess, uploadConfirm } from '@/module/alertModal.js'
|
|
|
|
export default {
|
|
setup() {
|
|
const loadingStore = LoadingStore();
|
|
const filesStore = FilesStore();
|
|
const { isLoading } = storeToRefs(loadingStore);
|
|
const { uploadDetail, uploadId, uploadFileName } = storeToRefs(filesStore);
|
|
|
|
return { isLoading, filesStore, uploadDetail, uploadId, uploadFileName }
|
|
},
|
|
data() {
|
|
return {
|
|
tooltipUpload: {
|
|
value: `1. Case ID: A unique identifier for each case.
|
|
2. Activity: A process step executed by either a system (automated) or humans (manual).
|
|
3. Activity Instance ID: A unique identifier for a single occurrence of an activity.
|
|
4. Timestamp: The time of occurrence of a particular event, such as the start or end of an activity.
|
|
5. Status: Activity status, such as Start or Complete.
|
|
6. Attribute: A property that can be associated with a case to provide additional information about that case.`,
|
|
// 暫時沒有 Resource
|
|
// 7. Resource: A resource refers to any entity that is required to carry out a business process. This can include people, equipment, software, or any other type of asset.
|
|
class: '!max-w-[400px] !text-[10px] !opacity-80',
|
|
autoHide: false,
|
|
},
|
|
columnType: [
|
|
{ name: 'Case ID*', code: 'case_id', color: '!text-secondary', value: '', label: 'Case ID', required: true },
|
|
{ name: 'Timestamp*', code: 'timestamp', color: '!text-secondary', value: '', label: 'Timestamp', required: true },
|
|
{ name: 'Status*', code: 'status', color: '!text-secondary', value: '', label: 'Status', required: true },
|
|
{ name: 'Activity*', code: 'name', color: '!text-secondary', value: '', label: 'Activity', required: true },
|
|
{ name: 'Activity Instance ID*', code: 'instance', color: '!text-secondary', value: '', label: 'Activity Instance ID', required: true },
|
|
{ name: 'Case Attribute', code: 'case_attributes', color: '!text-primary', value: '', label: 'Case Attribute', required: false },
|
|
// { name: 'Resource', code: '', color: '', value: '', label: 'Resource', required: false }, // 現階段沒有,未來可能有
|
|
{ name: 'Not Assigned', code: '', color: '!text-neutral-700', value: '', label: 'Not Assigned', required: false },
|
|
],
|
|
selectedColumns: [],
|
|
isInform: true, // true: 藍字提示; false: 紅字提示
|
|
informData: [], // 紅字提示,尚未選擇的 type
|
|
repeatedData: [], // 紅字提示,重複選擇的 type
|
|
fileName: this.uploadFileName,
|
|
};
|
|
},
|
|
computed: {
|
|
isDisabled: function() {
|
|
// 1. 長度一樣,強制每一個都要選
|
|
// 2. 不為 null undefind
|
|
let hasValue = !this.selectedColumns.includes(undefined);
|
|
let result = !(this.selectedColumns.length === this.uploadDetail?.columns.length && this.informData.length === 0 && this.repeatedData.length === 0 && hasValue) ? true : false;
|
|
return result
|
|
},
|
|
},
|
|
watch: {
|
|
selectedColumns: {
|
|
deep: true, // 監聽陣列內部的變化
|
|
handler(newVal, oldVal) {
|
|
this.updateValidationData(newVal);
|
|
},
|
|
}
|
|
},
|
|
methods: {
|
|
uploadFailedFirst,
|
|
uploadSuccess,
|
|
uploadConfirm,
|
|
/**
|
|
* Rename 離開 input 的行為
|
|
* @param {Event} e
|
|
*/
|
|
onBlur(e) {
|
|
const baseWidth = 20;
|
|
|
|
if(e.target.value === '') {
|
|
e.target.value = this.uploadFileName;
|
|
const textWidth = this.getTextWidth(e.target.value, e.target);
|
|
e.target.style.width = baseWidth + textWidth + 'px';
|
|
}else if(e.target.value !== e.target.value.trim()) {
|
|
e.target.value = e.target.value.trim();
|
|
const textWidth = this.getTextWidth(e.target.value, e.target);
|
|
e.target.style.width = baseWidth + textWidth + 'px';
|
|
}
|
|
},
|
|
/**
|
|
* Rename 輸入 input 的行為
|
|
* @param {Event} e
|
|
*/
|
|
onInput(e) {
|
|
const baseWidth = 20;
|
|
const textWidth = this.getTextWidth(e.target.value, e.target);
|
|
|
|
e.target.style.width = baseWidth + textWidth + 'px';
|
|
},
|
|
/**
|
|
* input 寬度隨著 value 響應式改變
|
|
* @param {String} text
|
|
* @param {Event} e
|
|
*/
|
|
getTextWidth(text, e) {
|
|
// 替換空格為不斷行的空格
|
|
const processedText = text.replace(/ /g, '\u00a0');
|
|
const hiddenSpan = document.createElement('span');
|
|
let width;
|
|
|
|
hiddenSpan.innerHTML = processedText;
|
|
hiddenSpan.style.font = window.getComputedStyle(e).font;
|
|
hiddenSpan.style.visibility = 'hidden';
|
|
document.body.appendChild(hiddenSpan);
|
|
width = hiddenSpan.getBoundingClientRect().width;
|
|
document.body.removeChild(hiddenSpan);
|
|
|
|
return width;
|
|
},
|
|
/**
|
|
* 驗證,根據新的 selectedColumns 更新 isInform、informData 和 repeatedData
|
|
* @param {Array} data
|
|
*/
|
|
updateValidationData(data) {
|
|
const nameOccurrences = {};
|
|
const noSortedRepeatedData = []; // 未排序的重複選擇的 data
|
|
let selectedData = [] // 已經選擇的 data
|
|
|
|
this.informData = []; // 尚未選擇的 data
|
|
this.repeatedData = []; // 重複選擇的 data
|
|
|
|
data.forEach(item => {
|
|
const { name, code } = item;
|
|
|
|
if(nameOccurrences[name]) {
|
|
// 'Not Assigned'、'Case Attribute' 不列入驗證
|
|
if(!code || code === 'case_attributes') return;
|
|
nameOccurrences[name]++;
|
|
// 重複的選項只出現一次
|
|
nameOccurrences[name] === 2 ? noSortedRepeatedData.push(item) : false;
|
|
// 要按照選單的順序排序
|
|
this.repeatedData = this.columnType.filter(column => noSortedRepeatedData.includes(column));
|
|
}else {
|
|
nameOccurrences[name] = 1;
|
|
selectedData.push(name);
|
|
this.informData = this.columnType.filter(item => item.required ? !selectedData.includes(item.name) : false);
|
|
}
|
|
});
|
|
this.isInform = (this.informData.length === 0 && this.repeatedData.length === 0) ? true : false;
|
|
},
|
|
/**
|
|
* Reset Button
|
|
*/
|
|
reset() {
|
|
// 路徑不列入歷史紀錄
|
|
this.selectedColumns = [];
|
|
},
|
|
/**
|
|
* Cancel Button
|
|
*/
|
|
cancel() {
|
|
// 路徑不列入歷史紀錄
|
|
this.$router.push({name: 'Files', replace: true});
|
|
},
|
|
/**
|
|
* Upload Button
|
|
*/
|
|
async submit() {
|
|
// Post API Data
|
|
let fetchData = {
|
|
timestamp: '',
|
|
case_id: '',
|
|
name: '',
|
|
instance: '',
|
|
status: '',
|
|
case_attributes: []
|
|
};
|
|
// 給值
|
|
let haveValueData = this.selectedColumns.map((column, i) => {
|
|
if (column && this.uploadDetail.columns[i]) {
|
|
return {
|
|
name: column.name,
|
|
code: column.code,
|
|
color: column.color,
|
|
value: this.uploadDetail.columns[i]
|
|
}
|
|
}
|
|
});
|
|
|
|
// 取得欲更改的檔名,
|
|
this.uploadFileName = this.fileName;
|
|
// 設定第二階段上傳的 data
|
|
haveValueData.forEach(column => {
|
|
switch (column.code) {
|
|
case 'timestamp':
|
|
fetchData.timestamp = column.value;
|
|
break;
|
|
case 'case_id':
|
|
fetchData.case_id = column.value;
|
|
break;
|
|
case 'name':
|
|
fetchData.name = column.value;
|
|
break;
|
|
case 'instance':
|
|
fetchData.instance = column.value;
|
|
break;
|
|
case 'status':
|
|
fetchData.status = column.value;
|
|
break;
|
|
case 'case_attributes':
|
|
fetchData.case_attributes.push(column.value);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
});
|
|
this.uploadConfirm(fetchData);
|
|
},
|
|
},
|
|
mounted() {
|
|
// 只監聽第一次
|
|
let unwatch = this.$watch('fileName', (newValue) => {
|
|
if (newValue) {
|
|
const inputElement = document.getElementById('fileNameInput');
|
|
const baseWidth = 20;
|
|
const textWidth = this.getTextWidth(this.fileName, inputElement);
|
|
inputElement.style.width = baseWidth + textWidth + 'px';
|
|
}
|
|
},
|
|
{ immediate: true }
|
|
);
|
|
this.isInform = true; // 初始為藍字提示
|
|
this.showEdit = true;
|
|
if(this.uploadId) this.filesStore.getUploadDetail();
|
|
unwatch();
|
|
this.isLoading = false;
|
|
},
|
|
beforeUnmount() {
|
|
// 離開頁面要刪 uploadID
|
|
this.uploadId = null;
|
|
this.uploadFileName = null;
|
|
},
|
|
beforeRouteEnter(to, from, next){
|
|
// 要有 uploadID 才能進來
|
|
next(vm => {
|
|
if(vm.uploadId == null) {
|
|
vm.$router.push({name: 'Files', replace: true});
|
|
vm.$toast.default('Please upload your file.', {position: 'bottom'});
|
|
return
|
|
}
|
|
})
|
|
},
|
|
}
|
|
</script>
|