- Enhanced bulk import error messaging and input file handling.

- Improved calculation view and tree view display.
- Added draft clearing for container rates and corrected indentation issues.
- Various frontend usability and styling refinements.
This commit is contained in:
Jan 2025-09-29 18:46:55 +02:00
parent d997178d00
commit 1fe4d700aa
8 changed files with 89 additions and 28 deletions

View file

@ -63,7 +63,7 @@ export default {
expanded: {
type: Boolean,
default: false
}
},
},
data() {
return {
@ -79,12 +79,11 @@ export default {
},
displayKey() {
if (this.keyName === 'root') return 'JSON'
return Array.isArray(this.parentData) ? `[${this.keyName}]` : this.keyName
return this.keyName
},
keyClass() {
return {
'array-index': Array.isArray(this.parentData),
'object-key': !Array.isArray(this.parentData),
'object-key': this.keyName !== 'root',
'root-key': this.keyName === 'root'
}
},

View file

@ -26,13 +26,13 @@
<div class="bulk-operation-caption">validity period</div>
<div class="bulk-operation-data">
<div class="period-select-container">
<dropdown :options="periods"
emptyText="No property set available"
class="period-select"
placeholder="Select a property set"
v-model="selectedPeriod"
:disabled="!showValidityPeriod"
></dropdown>
<dropdown :options="periods"
emptyText="No property set available"
class="period-select"
placeholder="Select a property set"
v-model="selectedPeriod"
:disabled="!showValidityPeriod"
></dropdown>
</div>
</div>
@ -77,11 +77,11 @@
</box>
<div class="bulk-operation-box-status-container">
<div class="bulk-operation-status">
<div class="bulk-operation-status">
<div class="bulk-operation-header">History</div>
<div v-if="this.bulkOperationStore.getBulkOperations.length === 0" class="empty-container">No recent bulk operations</div>
<bulk-operation v-else v-for="bulk in this.bulkOperationStore.getBulkOperations" :key="bulk.id" :operation="bulk" @download="fetchFile"></bulk-operation>
</div>
</div>
</div>
</div>
@ -112,6 +112,7 @@ export default {
importDataset: "NODE",
selectedFileName: null,
selectedFile: null,
fileBlob: null,
uploading: false,
processId: null,
}
@ -126,7 +127,7 @@ export default {
async isSelected(newVal) {
if(newVal === true)
await this.validityPeriodStore.loadPeriods();
await this.bulkOperationStore.manageStatus();
await this.bulkOperationStore.manageStatus();
}
},
computed: {
@ -166,7 +167,7 @@ export default {
}
},
methods: {
buildDate(date) {
buildDate(date) {
return `${date[0]}-${date[1].toString().padStart(2, '0')}-${date[2].toString().padStart(2, '0')} ${date[3].toString().padStart(2, '0')}:${date[4].toString().padStart(2, '0')}:${date[5].toString().padStart(2, '0')}`
},
async fetchFile(id) {
@ -183,22 +184,60 @@ export default {
await this.bulkOperationStore.scheduleDownload(this.exportDataset, isCurrent ? null : this.selectedPeriod);
}
},
inputFile(event) {
async inputFile(event) {
const file = event.target.files[0];
if (file) {
this.selectedFile = file;
this.selectedFileName = file.name;
try {
// Datei sofort in den RAM laden als Blob
this.fileBlob = await this.readFileAsBlob(file);
// File-Objekt mit dem Blob erstellen, das den originalen Namen und Typ behält
this.selectedFile = new File([this.fileBlob], file.name, { type: file.type });
this.selectedFileName = file.name;
logger.info(`File loaded into memory: ${file.name} (${(file.size / 1024).toFixed(2)} KB)`);
} catch (error) {
logger.error('Error reading file:', error);
this.selectedFile = null;
this.selectedFileName = null;
this.fileBlob = null;
}
} else {
this.selectedFile = null;
this.selectedFileName = null;
this.fileBlob = null;
}
// Input zurücksetzen, damit dieselbe Datei erneut ausgewählt werden kann
event.target.value = '';
},
readFileAsBlob(file) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = (e) => {
// ArrayBuffer in Blob konvertieren
const blob = new Blob([e.target.result], { type: file.type });
resolve(blob);
};
reader.onerror = (error) => {
reject(error);
};
// Datei als ArrayBuffer lesen
reader.readAsArrayBuffer(file);
});
},
async uploadFile() {
if (!this.selectedFile)
return;
await this.bulkOperationStore.scheduleUpload(this.importDataset, this.selectedFile);
const file = this.selectedFile;
this.selectedFile = null;
this.selectedFileName = null;
await this.bulkOperationStore.scheduleUpload(this.importDataset, file);
}
}
}

View file

@ -1,15 +1,20 @@
<template>
<div>
<h2 class="page-header">Dump of calculation #{{ $route.params.id }}</h2>
<box>
<div class="container">
<div>
<basic-button variant="secondary" :show-icon="false" @click="expand = !expand">{{ expand === true ? 'Collapse all items' : 'Expand all items' }}</basic-button>
</div>
<json-tree-viewer :data="dump" :all-expanded="expand"></json-tree-viewer>
</div>
<div class="container">
<div>
<basic-button variant="secondary" :show-icon="false" @click="expand = !expand">
{{ expand === true ? 'Collapse all items' : 'Expand all items' }}
</basic-button>
</div>
<json-tree-viewer :data="dump" :all-expanded="expand"></json-tree-viewer>
</div>
</box>
</div>
</template>

View file

@ -97,7 +97,7 @@ export default {
if (ids.length === 1) {
this.$router.push({name: "edit", params: {id: new UrlSafeBase64().encodeIds([ids[0]])}});
} else {
} else if(ids.length !== 0) {
this.$router.push({name: "bulk", params: {ids: new UrlSafeBase64().encodeIds(ids)}});
}

View file

@ -1,5 +1,16 @@
package de.avatic.lcc.dto.bulk;
public enum BulkFileType {
CONTAINER_RATE, COUNTRY_MATRIX, MATERIAL, PACKAGING, NODE
CONTAINER_RATE("container rate"), COUNTRY_MATRIX("kilometer rate"), MATERIAL("material"), PACKAGING("packaging"), NODE("node");
private final String fileType;
BulkFileType(String fileType) {
this.fileType = fileType;
}
public String getFileType() {
return fileType;
}
}

View file

@ -224,6 +224,12 @@ public class ContainerRateRepository {
nodeId, nodeId, TransportType.SEA.name(), TransportType.RAIL.name()));
}
@Transactional
public void clearDraft() {
var sql = "DELETE FROM container_rate WHERE container_rate.validity_period_id = (SELECT id FROM validity_period WHERE state = ?)";
jdbcTemplate.update(sql, ValidityPeriodState.DRAFT.name());
}
private static class ContainerRateMapper implements RowMapper<ContainerRate> {

View file

@ -59,7 +59,7 @@ public class BulkImportService {
Sheet sheet = workbook.getSheet(BulkFileTypes.valueOf(type.name()).getSheetName());
if(sheet == null)
throw new ExcelValidationError("Provided file does not contain a sheet named " + BulkFileTypes.valueOf(type.name()).getSheetName());
throw new ExcelValidationError("Unable to import " + op.getFileType().getFileType() + ", because \"" + BulkFileTypes.valueOf(type.name()).getSheetName() + "\" sheet is missing. Please use correct template.");
switch (type) {

View file

@ -20,6 +20,7 @@ public class ContainerRateImportService {
public void processContainerRates(List<ContainerRate> containerRates) {
Integer periodId = validityPeriodRepository.getDraftPeriodId();
containerRateRepository.clearDraft();
containerRates.forEach(rate -> processContainerRate(rate,periodId));
}