Files
thetool/public/js/pages/WorkorderMphCompany/WorkorderMphCompany.js
2025-12-13 21:27:43 +00:00

267 lines
13 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.
// WorkorderMphCompany.js
Vue.component('workorder-mph-company', {
template: `
<tt-card>
<tt-table-crud ref="table" :crud-config="crudConfig">
<template v-slot:hausnummerinfo="{ row }">
<span class="small">{{ row.street }} {{ row.hausnummer }}<template v-if="row.stiege">/{{ row.stiege }}</template>, {{ row.plz }} {{ row.city }}</span>
</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: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 v-if="!['completed', 'cancelled', 'documented'].includes(row.status)"
icon="fas fa-edit" @click="startAdditionalInfoEdit(row)"
additional-class="btn-link btn-sm p-0 ml-2" title="Notiz bearbeiten"/>
</div>
</template>
<template v-slot:deadlinedate="{ row }">{{ formatDate(row.deadlineDate) }}</template>
<template v-slot:appointmentdate="{ row }">
<div v-if="!row.appointmentDate && canSchedule(row)">
<tt-date-picker placeholder="Termin festlegen..." :date-range="false"
@input="scheduleAppointment(row, $event)" sm no-form-group
:additional-props="{ timePicker: true, timePicker24Hour: true, locale: { format: 'DD.MM.YYYY HH:mm' }, drops: 'up' }"/>
</div>
<div v-else-if="row.appointmentDate" class="d-flex align-items-center">
<span>{{ formatDate(row.appointmentDate, true) }}</span>
<tt-button v-if="canSchedule(row)"
icon="fas fa-edit" @click="openRescheduleModal(row)"
additional-class="btn-link btn-sm p-0 ml-2" title="Termin ändern"/>
</div>
<span v-else></span>
</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">
<!-- Action Buttons -->
<div class="mb-3" v-if="!['completed', 'cancelled', 'documented'].includes(row.status)">
<div class="btn-group" role="group">
<tt-button v-if="row.status === 'scheduled'" text="Arbeit beginnen"
@click="startWork(row)" icon="fas fa-play" additional-class="btn-success"/>
<tt-button v-if="row.status === 'in_progress'" text="Auftrag abschließen"
@click="openCompleteModal(row)" icon="fas fa-check-double"
additional-class="btn-success"/>
</div>
</div>
<div class="row g-2">
<!-- Left Column (1/4): Docs Checkbox, Journal -->
<div class="col-xl-3 col-lg-4">
<div class="mph-details-stack">
<checkbox-documentation :workorder-mph-id="parseInt(row.id)" :is-admin="false" @refresh="refresh"/>
<workorder-mph-journal :journals="journals" :workorder-mph-id="row.id" :is-admin="false" @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="false" @refresh="refresh"/>
<workorder-mph-documents :docs="docs" :workorder-mph-id="row.id" :is-admin="false" @refresh="refresh"/>
</div>
</div>
</div>
</div>
</workorder-mph-data-provider>
</template>
</tt-table-crud>
<tt-modal v-if="rescheduleModalData" :show.sync="rescheduleModalData"
title="Termin verschieben" @submit="rescheduleAppointment">
<p>Aktueller Termin: <strong>{{ formatDate(rescheduleModalData.currentDate, true) }}</strong></p>
<tt-date-picker label="Neuer Termin" v-model="rescheduleModalData.newDate"
:date-range="false" sm row required
:additional-props="{ timePicker: true, timePicker24Hour: true, locale: { format: 'DD.MM.YYYY HH:mm' }, singleDatePicker: true }"/>
<tt-textarea label="Grund" v-model="rescheduleModalData.reason" sm row required/>
</tt-modal>
<tt-modal v-if="completeModalData" :show.sync="completeModalData"
title="Auftrag abschließen" @submit="completeWorkorder" :delete="false">
<p>Möchten Sie diesen Auftrag wirklich abschließen und zur Prüfung einreichen?</p>
<div class="alert alert-warning small">
<i class="fas fa-exclamation-triangle mr-2"></i>
Bitte stellen Sie sicher, dass alle Wohneinheiten dokumentiert sind und alle erforderlichen Dokumente hochgeladen wurden.
</div>
</tt-modal>
</tt-card>
`,
data() {
return {
window,
editingAdditionalInfoId: null,
tempAdditionalInfo: '',
rescheduleModalData: null,
completeModalData: 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');
},
canSchedule(row) {
return ['assigned', 'scheduled', 'in_progress'].includes(row.status);
},
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}/WorkorderMphCompany/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 scheduleAppointment(row, newDate) {
if (!newDate) return;
const hour = parseInt(moment.unix(newDate).format('H'));
if (hour >= 23 || hour < 1) {
window.notify('error', 'Bitte geben Sie eine Uhrzeit zwischen 01:00 und 22:59 an!');
this.$refs.table.$refs.table.refreshTable();
return;
}
try {
const { data } = await axios.post(`${window.TT_CONFIG.BASE_PATH}/WorkorderMphCompany/scheduleAppointment`, {
workorderId: row.id,
appointmentDate: 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.');
}
},
openRescheduleModal(row) {
this.rescheduleModalData = {
workorderId: row.id,
currentDate: row.appointmentDate,
newDate: null,
reason: ''
};
},
async rescheduleAppointment() {
if (!this.rescheduleModalData.newDate || !this.rescheduleModalData.reason) {
return window.notify('error', 'Bitte füllen Sie alle Felder aus.');
}
const hour = parseInt(moment.unix(this.rescheduleModalData.newDate).format('H'));
if (hour >= 23 || hour < 1) {
return window.notify('error', 'Bitte geben Sie eine Uhrzeit zwischen 01:00 und 22:59 an!');
}
try {
const { data } = await axios.post(`${window.TT_CONFIG.BASE_PATH}/WorkorderMphCompany/rescheduleAppointment`, {
workorderId: this.rescheduleModalData.workorderId,
appointmentDate: this.rescheduleModalData.newDate,
reason: this.rescheduleModalData.reason
});
if (data.success) {
window.notify('success', data.message);
this.$refs.table.$refs.table.refreshTable();
this.rescheduleModalData = null;
} else {
window.notify('error', data.message || 'Ein Fehler ist aufgetreten.');
}
} catch (e) {
window.notify('error', 'Netzwerkfehler.');
}
},
async startWork(row) {
if (!confirm(`Möchten Sie mit der Arbeit an Auftrag #${row.id} beginnen?`)) return;
try {
const { data } = await axios.post(`${window.TT_CONFIG.BASE_PATH}/WorkorderMphCompany/startWork`, {
workorderId: row.id
});
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.');
}
},
openCompleteModal(row) {
this.completeModalData = { workorderId: row.id };
},
async completeWorkorder() {
try {
const { data } = await axios.post(`${window.TT_CONFIG.BASE_PATH}/WorkorderMphCompany/completeWorkorder`, {
workorderId: this.completeModalData.workorderId
});
if (data.success) {
window.notify('success', data.message);
this.$refs.table.$refs.table.refreshTable();
this.completeModalData = null;
} else {
window.notify('error', data.message || 'Abschluss fehlgeschlagen.');
}
} catch (e) {
window.notify('error', 'Ein Netzwerkfehler ist aufgetreten.');
}
}
}
});