245 lines
8.9 KiB
JavaScript
245 lines
8.9 KiB
JavaScript
Vue.component('csv-import-modal', {
|
|
props: {
|
|
show: { type: Boolean, required: true },
|
|
title: { type: String, required: true },
|
|
apiUrl: { type: String, required: true },
|
|
showNetworkAreas: { type: Boolean, default: false },
|
|
networkAreas: { type: Array, default: () => [] }
|
|
},
|
|
data() {
|
|
return {
|
|
selectedFile: null,
|
|
loading: false,
|
|
errorMessage: null,
|
|
fileInputKey: Date.now(),
|
|
selectedNetworkArea: null
|
|
};
|
|
},
|
|
methods: {
|
|
handleFileChange(event) {
|
|
const file = event.target.files[0];
|
|
this.resetInputState();
|
|
if (!file) return;
|
|
|
|
const fileNameLower = file.name.toLowerCase();
|
|
const allowedTypes = ['text/csv', 'application/vnd.ms-excel'];
|
|
if (!fileNameLower.endsWith('.csv') && !allowedTypes.includes(file.type)) {
|
|
this.errorMessage = 'Bitte wählen Sie eine gültige CSV-Datei aus (.csv).';
|
|
this.resetFileInputVisuals();
|
|
return;
|
|
}
|
|
this.selectedFile = file;
|
|
},
|
|
|
|
readFileAsString() {
|
|
return new Promise((resolve, reject) => {
|
|
if (!this.selectedFile) {
|
|
return reject(new Error("Keine Datei ausgewählt."));
|
|
}
|
|
const reader = new FileReader();
|
|
reader.onload = (e) => resolve(e.target.result);
|
|
reader.onerror = () => reject(new Error("Fehler beim Lesen der Datei."));
|
|
reader.readAsText(this.selectedFile, 'UTF-8');
|
|
});
|
|
},
|
|
|
|
parseCSV(csvText) {
|
|
if (!csvText || typeof csvText !== 'string') return [];
|
|
const lines = csvText.trim().split(/\r?\n/);
|
|
if (lines.length < 2) return [];
|
|
|
|
const headers = lines[0].split(';').map(h => this.cleanValue(h));
|
|
const data = [];
|
|
|
|
for (let i = 1; i < lines.length; i++) {
|
|
const line = lines[i].trim();
|
|
if (!line) continue;
|
|
|
|
const values = line.split(';');
|
|
if (values.length !== headers.length) continue;
|
|
|
|
const entry = {};
|
|
headers.forEach((header, j) => {
|
|
entry[header] = this.cleanValue(values[j]);
|
|
});
|
|
data.push(entry);
|
|
}
|
|
return data;
|
|
},
|
|
|
|
cleanValue(value) {
|
|
if (typeof value !== 'string') return value;
|
|
let cleaned = value.trim();
|
|
if (cleaned.startsWith('"') && cleaned.endsWith('"')) {
|
|
cleaned = cleaned.slice(1, -1).replace(/""/g, '"');
|
|
}
|
|
return cleaned;
|
|
},
|
|
|
|
async submit() {
|
|
if (!this.selectedFile) {
|
|
this.errorMessage = 'Bitte wählen Sie zuerst eine Datei aus.';
|
|
return;
|
|
}
|
|
if (this.showNetworkAreas && !this.selectedNetworkArea) {
|
|
this.errorMessage = 'Bitte wählen Sie einen Netzbereich aus.';
|
|
return;
|
|
}
|
|
this.errorMessage = null;
|
|
this.loading = true;
|
|
|
|
try {
|
|
const csvString = await this.readFileAsString();
|
|
const parsedData = this.parseCSV(csvString);
|
|
|
|
const payload = { data: parsedData };
|
|
if (this.showNetworkAreas) {
|
|
payload.networkAreaId = this.selectedNetworkArea;
|
|
}
|
|
|
|
const response = await axios.post(this.apiUrl, payload);
|
|
window.notify(response.data.success ? 'success' : 'warning', response.data.message || 'Import erfolgreich.');
|
|
this.$emit('close', true);
|
|
|
|
} catch (error) {
|
|
let backendMsg = error.response?.data?.message || '';
|
|
let detailsMsg = '';
|
|
if (error.response?.data?.errors) {
|
|
detailsMsg = Object.values(error.response.data.errors).flat().join(', ');
|
|
}
|
|
this.errorMessage = `Importfehler: ${error.message || 'Unbekannter Fehler.'}${backendMsg ? ' Server: ' + backendMsg : ''}${detailsMsg ? ' Details: ' + detailsMsg : ''}`;
|
|
window.notify('error', this.errorMessage);
|
|
} finally {
|
|
this.loading = false;
|
|
}
|
|
},
|
|
|
|
closeModal() {
|
|
if (!this.loading) {
|
|
this.$emit('close', false);
|
|
}
|
|
},
|
|
|
|
resetFileInputVisuals() {
|
|
this.fileInputKey = Date.now();
|
|
},
|
|
|
|
resetInputState() {
|
|
this.selectedFile = null;
|
|
this.errorMessage = null;
|
|
},
|
|
|
|
resetAll() {
|
|
this.resetInputState();
|
|
this.resetFileInputVisuals();
|
|
this.selectedNetworkArea = null;
|
|
}
|
|
},
|
|
watch: {
|
|
show(newVal) {
|
|
if (!newVal) {
|
|
this.resetAll();
|
|
}
|
|
}
|
|
},
|
|
template: `
|
|
<tt-modal
|
|
:show="show"
|
|
@update:show="closeModal"
|
|
:title="title"
|
|
@submit="submit"
|
|
:save-loading="loading"
|
|
save-text="Importieren"
|
|
:delete="false"
|
|
>
|
|
<tt-loader :absolute="false" v-if="loading"/>
|
|
<template v-else>
|
|
<div v-if="showNetworkAreas" class="form-group" style="margin: 10px 0">
|
|
<tt-select
|
|
label="Netzbereich auswählen"
|
|
v-model="selectedNetworkArea"
|
|
:options="networkAreas"
|
|
:required="true"
|
|
:disabled="loading || !networkAreas || networkAreas.length === 0"
|
|
sm
|
|
row
|
|
/>
|
|
<small v-if="!networkAreas || networkAreas.length === 0" class="form-text text-danger">
|
|
Keine Netzbereiche verfügbar.
|
|
</small>
|
|
</div>
|
|
|
|
<div class="form-group" style="margin: 10px 0">
|
|
<label>CSV-Datei auswählen (Trennzeichen: Semikolon ';')</label>
|
|
<input
|
|
type="file"
|
|
class="form-control"
|
|
accept=".csv, text/csv, application/vnd.ms-excel"
|
|
@change="handleFileChange"
|
|
:key="fileInputKey"
|
|
:disabled="loading"
|
|
/>
|
|
<small v-if="selectedFile" class="form-text text-muted">
|
|
Ausgewählt: {{ selectedFile.name }}
|
|
</small>
|
|
</div>
|
|
|
|
<div v-if="errorMessage" class="alert alert-danger mt-2" role="alert">
|
|
{{ errorMessage }}
|
|
</div>
|
|
</template>
|
|
</tt-modal>
|
|
`
|
|
});
|
|
|
|
Vue.component('a-d-b-rimo-fcp', {
|
|
template: `
|
|
<tt-card>
|
|
<tt-table-crud ref="table">
|
|
<template #table-top-buttons>
|
|
<div style="display: flex; gap: 10px;">
|
|
<tt-button icon="fas fa-upload" text="FCPs Importieren" additional-class="btn-outline-success" @click="showImportFCPModal = true" />
|
|
<tt-button icon="fas fa-upload" text="Locations Importieren" additional-class="btn-outline-success" @click="showImportLocationsModal = true" />
|
|
</div>
|
|
</template>
|
|
</tt-table-crud>
|
|
|
|
<csv-import-modal
|
|
v-if="showImportFCPModal"
|
|
:show="showImportFCPModal"
|
|
:show-network-areas="true"
|
|
:network-areas="networkAreas"
|
|
title="FCPs Importieren"
|
|
:api-url="fcpApiUrl"
|
|
@close="handleModalClose"
|
|
/>
|
|
|
|
<csv-import-modal
|
|
v-if="showImportLocationsModal"
|
|
:show="showImportLocationsModal"
|
|
:show-network-areas="true"
|
|
:network-areas="networkAreas"
|
|
title="Locations Importieren"
|
|
:api-url="locationsApiUrl"
|
|
@close="handleModalClose"
|
|
/>
|
|
</tt-card>
|
|
`,
|
|
data() {
|
|
return {
|
|
showImportFCPModal: false,
|
|
showImportLocationsModal: false,
|
|
networkAreas: window.TT_CONFIG?.CRUD_CONFIG?.columns?.find(col => col.key === 'netzgebiet_id')?.modal?.items || [],
|
|
fcpApiUrl: window.TT_CONFIG['BASE_PATH'] + '/ADBRimoFcp/ImportFCPs',
|
|
locationsApiUrl: window.TT_CONFIG['BASE_PATH'] + '/ADBRimoFcp/ImportLocations'
|
|
}
|
|
},
|
|
methods: {
|
|
handleModalClose(importWasSuccessful) {
|
|
this.showImportFCPModal = false;
|
|
this.showImportLocationsModal = false;
|
|
|
|
if (importWasSuccessful) this.$refs.table.$refs.table.refreshTable();
|
|
}
|
|
}
|
|
}); |