// 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: ` `, 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: `
Benötigte Dokumente
Neues Dokument hochladen
`, 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: `
Hochgeladene Dokumente
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(); } });