// RMLWorkorder.js
// =================================================================================
// Main Component - Switches between Admin and Company View
// =================================================================================
Vue.component('r-m-l-workorder', {
template: `
`,
data() { return { window: window } }
});
// =================================================================================
// RML Admin View
// =================================================================================
Vue.component('rml-workorder-admin-view', {
template: `
`,
data() {
return {
assignModalWorkorderId: null,
docsModalWorkorderId: null,
crudConfig: {
...window.TT_CONFIG.CRUD_CONFIG,
additionalActions: [
{
"key": "assign",
"title": "Firma zuweisen",
"class": "fas fa-user-plus text-primary",
"condition": (row) => row.status === 'new',
},
{
"key": "view_docs",
"title": "Dokumentation ansehen",
"class": "fas fa-folder-open text-info",
"condition": (row) => ['documented', 'completed'].includes(row.status),
},
]
}
}
},
methods: {
getStatusColumn(status) {
const column = this.crudConfig.columns.find(c => c.key === 'status');
return column.table.filterOptions.find(opt => opt.value === status) || {};
},
formatDate(timestamp) {
if (!timestamp) return '–';
return window.moment.unix(timestamp).format('DD.MM.YYYY');
}
}
});
// =================================================================================
// RML Company View
// =================================================================================
Vue.component('rml-workorder-company-view', {
template: `
{{ getStatusColumn(row.status).text }}
{{ formatDate(row.deadlineDate) }}
{{ formatDate(row.appointmentDate) }}
`,
data() {
return {
scheduleModalWorkorderId: null,
documentModalWorkorder: null,
crudConfig: {
...window.TT_CONFIG.CRUD_CONFIG,
additionalActions: [
{
"key": "schedule",
"title": "Termin festlegen",
"class": "fas fa-calendar-plus text-primary",
"condition": (row) => row.status === 'assigned',
},
{
"key": "document",
"title": "Dokumentieren & Abschließen",
"class": "fas fa-camera text-success",
"condition": (row) => ['assigned', 'scheduled', 'documented'].includes(row.status),
},
]
}
}
},
methods: {
getStatusColumn(status) {
const column = this.crudConfig.columns.find(c => c.key === 'status');
return column.table.filterOptions.find(opt => opt.value === status) || {};
},
formatDate(timestamp) {
if (!timestamp) return '–';
return window.moment.unix(timestamp).format('DD.MM.YYYY');
}
}
});
// =================================================================================
// Modals and Helper Components
// =================================================================================
// Traffic Light Component
Vue.component('traffic-light', {
props: ['deadline', 'status'],
computed: {
lightColor() {
if (this.status === 'completed') return '#cccccc'; // Grey for completed
const now = moment();
const deadlineDate = moment.unix(this.deadline);
if (!deadlineDate.isValid()) return '#cccccc'; // Grey for invalid date
if (deadlineDate.isBefore(now)) return '#dc3545'; // Red for overdue
if (deadlineDate.isBefore(now.clone().add(1, 'weeks'))) return '#dc3545'; // Red
if (deadlineDate.isBefore(now.clone().add(3, 'weeks'))) return '#ffc107'; // Yellow
return '#28a745'; // Green
}
},
template: `● `
});
// Modal for RML Admin to assign a company
Vue.component('assign-company-modal', {
props: ['workorderId'],
template: `
`,
data() { return { companies: [], selectedCompanyId: null } },
async mounted() {
const response = await axios.get(`${window.TT_CONFIG.BASE_PATH}/RMLWorkorder/getCompanies`);
this.companies = response.data;
},
methods: {
async submit() {
if (!this.selectedCompanyId) return window.notify('error', 'Bitte eine Firma auswählen.');
const response = await axios.post(`${window.TT_CONFIG.BASE_PATH}/RMLWorkorder/assignWorkorder`, {
workorderId: this.workorderId,
companyId: this.selectedCompanyId
});
if(response.data.success) {
window.notify('success', response.data.message);
this.$emit('close');
} else {
window.notify('error', response.data.message || 'Ein Fehler ist aufgetreten.');
}
}
}
});
// Modal for Company to schedule an appointment
Vue.component('schedule-appointment-modal', {
props: ['workorderId'],
template: `
`,
data() { return { appointmentDate: null } },
methods: {
async submit() {
if (!this.appointmentDate) return window.notify('error', 'Bitte ein Datum auswählen.');
const response = await axios.post(`${window.TT_CONFIG.BASE_PATH}/RMLWorkorder/scheduleAppointment`, {
workorderId: this.workorderId,
appointmentDate: this.appointmentDate
});
if(response.data.success) {
window.notify('success', response.data.message);
this.$emit('close');
} else {
window.notify('error', response.data.message || 'Ein Fehler ist aufgetreten.');
}
}
}
});
// Documentation Upload Modal for Companies
Vue.component('documentation-modal', {
props: ['workorder'],
template: `
Schließen
`,
data() {
return {
uploading: false,
viewerKey: 0,
uploadedFiles: [],
uploadData: {
file: null,
documentType: 'photo_before',
description: ''
},
requiredDocTypes: [
{ value: 'photo_before', text: 'Foto: Zustand vorher' },
{ value: 'photo_during', text: 'Foto: Während der Arbeit' },
{ value: 'photo_after', text: 'Foto: Zustand nachher' },
{ value: 'measurement_protocol', text: 'Messprotokoll (z.B. OTDR)' },
{ value: 'customer_signature', text: 'Unterschriebenes Arbeitsprotokoll' },
]
}
},
computed: {
canComplete() {
// Check if at least one of each required document type is uploaded.
return this.requiredDocTypes.every(docType => this.isUploaded(docType.value));
}
},
methods: {
isUploaded(docType) {
return this.uploadedFiles.some(file => file.documentType === docType);
},
handleFileUpload(event) {
this.uploadData.file = event.target.files[0];
},
async uploadFile() {
if(!this.uploadData.file) return window.notify('error', 'Bitte eine Datei auswählen.');
this.uploading = true;
const formData = new FormData();
formData.append('file', this.uploadData.file);
formData.append('workorderId', this.workorder.id);
formData.append('documentType', this.uploadData.documentType);
formData.append('description', this.uploadData.description);
const response = await axios.post(`${window.TT_CONFIG.BASE_PATH}/RMLWorkorder/uploadDocumentation`, formData);
if(response.data.success) {
window.notify('success', `Datei "${response.data.fileName}" wurde hochgeladen.`);
this.$refs.fileInput.value = ''; // Clear file input
this.uploadData.file = null;
this.uploadData.description = '';
this.viewerKey++; // Refresh the viewer
} else {
window.notify('error', response.data.error || 'Upload fehlgeschlagen.');
}
this.uploading = false;
},
async completeWorkorder() {
if(!confirm('Möchten Sie diesen Auftrag wirklich abschließen? Diese Aktion kann nicht rückgängig gemacht werden.')) return;
const response = await axios.post(`${window.TT_CONFIG.BASE_PATH}/RMLWorkorder/completeWorkorder`, { workorderId: this.workorder.id });
if(response.data.success) {
window.notify('success', response.data.message);
this.$emit('close');
} else {
window.notify('error', response.data.message || 'Ein Fehler ist aufgetreten.');
}
}
},
async mounted() {
const response = await axios.get(`${window.TT_CONFIG.BASE_PATH}/RMLWorkorder/getDocumentation`, { params: { workorderId: this.workorder.id }});
this.uploadedFiles = response.data;
}
});
// Read-only viewer for documentation, used by both Admins and Companies
Vue.component('documentation-viewer-modal', {
props: ['workorderId'],
template: `
Keine Dokumente vorhanden.
`,
data() {
return { loading: false, docs: [] }
},
methods: {
async fetchDocs() {
this.loading = true;
const response = await axios.get(`${window.TT_CONFIG.BASE_PATH}/RMLWorkorder/getDocumentation`, { params: { workorderId: this.workorderId }});
this.docs = response.data;
this.loading = false;
},
formatDate(timestamp) {
return window.moment.unix(timestamp).format('DD.MM.YYYY HH:mm');
},
getDocTypeText(type) {
const types = [
{ value: 'photo_before', text: 'Foto: Zustand vorher' },
{ value: 'photo_during', text: 'Foto: Während der Arbeit' },
{ value: 'photo_after', text: 'Foto: Zustand nachher' },
{ value: 'measurement_protocol', text: 'Messprotokoll (z.B. OTDR)' },
{ value: 'customer_signature', text: 'Unterschriebenes Arbeitsprotokoll' },
];
return types.find(t => t.value === type)?.text || type;
}
},
mounted() {
this.fetchDocs();
}
});