Files
thetool/public/js/pages/WorkorderMphAdmin/WorkorderMphAdmin.js
2025-12-03 14:08:44 +00:00

245 lines
12 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// WorkorderMphAdmin.js
Vue.component('workorder-mph-admin', {
template: `
<tt-card>
<tt-table-crud ref="table" :crud-config="crudConfig">
<template v-slot:hausnummerinfo="{ row }">
<div class="small">
<div><strong>Adresse:</strong> {{ row.street }} {{ row.hausnummer }}<template v-if="row.stiege">/{{ row.stiege }}</template></div>
<div><strong>Ort:</strong> {{ row.plz }} {{ row.city }}</div>
</div>
</template>
<template v-slot:status="{ row }">
<traffic-light-mph :deadline="row.deadlineDate" :status="row.status"/>
<i :class="getStatusColumn(row.status).icon" :title="getStatusColumn(row.status).text"></i>
<span class="ml-2">{{ getStatusColumn(row.status).text }}</span>
</template>
<template v-slot:companyname="{ row }">
<div class="d-flex justify-content-between align-items-center">
<div class="flex-grow-1">
<div v-if="editingWorkorderId === row.id">
<div v-if="companiesLoading" class="spinner-border spinner-border-sm"></div>
<tt-select v-else :options="companies" :value="row.companyId"
@input="assignCompany(row, $event)" @blur="editingWorkorderId = null"
@keydown.esc.native="editingWorkorderId = null"
placeholder="Firma zuweisen..." sm no-form-group/>
</div>
<div v-else-if="row.status === 'new'">
<tt-select :options="companies" :value="row.companyId"
@input="assignCompany(row, $event)" @focus="loadCompanies"
placeholder="Firma zuweisen..." sm no-form-group/>
</div>
<div v-else><span>{{ row.companyName || 'N/A' }}</span></div>
</div>
<div style="display: grid; grid-template-columns: repeat(2, auto); gap: 0px; padding-left: 8px;">
<tt-button v-if="!['completed', 'new'].includes(row.status)" icon="fas fa-edit"
@click="startCompanyEdit(row)" additional-class="btn-link workorder-mph-button"
title="Zuweisung ändern"/>
<tt-button v-if="!['completed', 'cancelled'].includes(row.status)" icon="fas fa-ban text-danger"
@click="cancelWorkorderModalData = row" additional-class="btn-link workorder-mph-button"
title="Auftrag stornieren"/>
</div>
</div>
</template>
<template v-slot:additionalinfo="{ row }">
<div v-if="editingAdditionalInfoId === row.id">
<tt-textarea v-model="tempAdditionalInfo" @keydown.esc.native="cancelEdit" rows="3" no-form-group sm ref="editTextarea"/>
<div class="mt-2 d-flex justify-content-end">
<tt-button text="Abbrechen" @click="cancelEdit" sm additional-class="btn-secondary mr-2"/>
<tt-button text="Speichern" @click="updateAdditionalInfo(row)" sm additional-class="btn-success"/>
</div>
</div>
<div v-else class="d-flex align-items-start">
<span style="white-space: pre-wrap; max-width: 250px; display: inline-block;">{{ row.additionalInfo || '-' }}</span>
<tt-button icon="fas fa-edit" @click="startAdditionalInfoEdit(row)"
additional-class="btn-link btn-sm p-0 ml-2" title="Zusatz-Info bearbeiten"/>
</div>
</template>
<template v-slot:deadlinedate="{ row }">
<div v-if="editingDeadlineId === row.id">
<tt-date-picker :value="row.deadlineDate" :date-range="false"
@input="updateDeadline(row, $event)" @blur="editingDeadlineId = null"
sm no-form-group/>
</div>
<div v-else class="d-flex align-items-center">
<span>{{ formatDate(row.deadlineDate) }}</span>
<tt-button icon="fas fa-edit" @click="editingDeadlineId = row.id"
additional-class="btn-link btn-sm p-0 ml-2" title="Deadline ändern"/>
</div>
</template>
<template v-slot:appointmentdate="{ row }">{{ formatDate(row.appointmentDate, true) }}</template>
<template v-slot:expandedRow="{ row }">
<workorder-mph-data-provider :workorder-mph-id="row.id" v-slot="{ docs, journals, refresh }">
<div class="workorder-mph-expanded-wrapper">
<div class="row g-2">
<!-- Left Column (1/4): Docs Checkbox, Journal, Review -->
<div class="col-xl-3 col-lg-4">
<div class="mph-details-stack">
<checkbox-documentation :workorder-mph-id="parseInt(row.id)" :is-admin="true"/>
<workorder-mph-journal :journals="journals" :workorder-mph-id="row.id" :is-admin="true" @refresh="refresh"/>
<workorder-mph-admin-review :docs="docs" :workorder-mph-id="row.id" @refresh="refresh"/>
</div>
</div>
<!-- Right Column (3/4): Wohneinheiten, Documents -->
<div class="col-xl-9 col-lg-8">
<div class="mph-details-stack">
<wohneinheit-status-manager :workorder-mph-id="parseInt(row.id)" :is-admin="true"/>
<workorder-mph-documents :docs="docs" :workorder-mph-id="row.id" :is-admin="true" @refresh="refresh"/>
</div>
</div>
</div>
</div>
</workorder-mph-data-provider>
</template>
</tt-table-crud>
<tt-modal v-if="cancelWorkorderModalData" :show.sync="cancelWorkorderModalData"
title="Auftrag stornieren" @submit="cancelWorkorder">
<p>Soll der Auftrag <strong>#{{ cancelWorkorderModalData.id }}</strong> wirklich storniert werden?</p>
<tt-textarea label="Grund (optional)" v-model="cancelWorkorderModalData.reason" sm row/>
</tt-modal>
</tt-card>
`,
data() {
return {
window,
editingWorkorderId: null,
editingDeadlineId: null,
editingAdditionalInfoId: null,
tempAdditionalInfo: '',
companies: [],
companiesLoading: false,
cancelWorkorderModalData: null,
crudConfig: {
...window.TT_CONFIG.CRUD_CONFIG,
selectable: false,
expandable: true,
customRowClass: (row) => {
if (['completed', 'new', 'cancelled', 'archived'].includes(row.status)) return 'tt-mph-workorder-irrelevant';
const deadlineDate = moment.unix(row.deadlineDate);
if (!deadlineDate.isValid()) return 'tt-mph-workorder-irrelevant';
const daysLeft = deadlineDate.diff(moment(), 'days');
if (daysLeft <= 7) return 'tt-mph-workorder-urgent';
if (daysLeft <= 21) return 'tt-mph-workorder-medium';
return 'tt-mph-workorder-ontrack';
}
}
}
},
methods: {
getStatusColumn(status) {
const column = this.crudConfig.columns.find(c => c.key === 'status');
return column.table.filterOptions.find(opt => opt.value === status) || {};
},
formatDate(timestamp, withTime = false) {
if (!timestamp) return '';
return window.moment.unix(timestamp).format(withTime ? 'DD.MM.YYYY HH:mm' : 'DD.MM.YYYY');
},
async loadCompanies() {
if (this.companies.length > 0) return;
this.companiesLoading = true;
try {
const { data } = await axios.get(`${window.TT_CONFIG.BASE_PATH}/WorkorderMphAdmin/getCompanies`);
this.companies = data;
} catch (e) {
window.notify('error', 'Firmenliste konnte nicht geladen werden.');
} finally {
this.companiesLoading = false;
}
},
async startCompanyEdit(row) {
await this.loadCompanies();
this.editingWorkorderId = row.id;
},
async assignCompany(workorder, companyId) {
if (!companyId) {
this.editingWorkorderId = null;
return;
}
const { data } = await axios.post(`${window.TT_CONFIG.BASE_PATH}/WorkorderMphAdmin/assignWorkorder`, {
workorderId: workorder.id,
companyId: companyId
});
if (data.success) {
window.notify('success', data.message);
this.$refs.table.$refs.table.refreshTable();
} else {
window.notify('error', data.message || 'Ein Fehler ist aufgetreten.');
}
this.editingWorkorderId = null;
},
async updateDeadline(workorder, newDate) {
if (!newDate) {
this.editingDeadlineId = null;
return;
}
try {
const { data } = await axios.post(`${window.TT_CONFIG.BASE_PATH}/WorkorderMphAdmin/updateDeadline`, {
workorderId: workorder.id,
deadlineDate: newDate
});
if (data.success) {
window.notify('success', data.message);
this.$refs.table.$refs.table.refreshTable();
} else {
window.notify('error', data.message || 'Ein Fehler ist aufgetreten.');
}
} catch (e) {
window.notify('error', 'Netzwerkfehler.');
} finally {
this.editingDeadlineId = null;
}
},
startAdditionalInfoEdit(row) {
this.editingAdditionalInfoId = row.id;
this.tempAdditionalInfo = row.additionalInfo || '';
this.$nextTick(() => this.$refs.editTextarea?.$el.querySelector('textarea').focus());
},
cancelEdit() {
this.editingAdditionalInfoId = null;
this.tempAdditionalInfo = '';
},
async updateAdditionalInfo(row) {
if (row.additionalInfo === this.tempAdditionalInfo) {
this.cancelEdit();
return;
}
try {
const { data } = await axios.post(`${window.TT_CONFIG.BASE_PATH}/WorkorderMphAdmin/updateAdditionalInfo`, {
workorderMphId: row.id,
additionalInfo: this.tempAdditionalInfo
});
if (data.success) {
window.notify('success', data.message);
row.additionalInfo = data.newInfo;
} else {
window.notify('error', data.message || 'Update fehlgeschlagen.');
}
} catch (e) {
window.notify('error', 'Netzwerkfehler.');
} finally {
this.cancelEdit();
}
},
async cancelWorkorder() {
const { id, reason } = this.cancelWorkorderModalData;
const { data } = await axios.post(`${window.TT_CONFIG.BASE_PATH}/WorkorderMphAdmin/cancelWorkorder`, {
workorderId: id,
reason: reason
});
if (data.success) {
window.notify('success', data.message);
this.$refs.table.$refs.table.refreshTable();
this.cancelWorkorderModalData = null;
} else {
window.notify('error', data.message || 'Stornierung fehlgeschlagen.');
}
}
}
});