183 lines
7.8 KiB
JavaScript
183 lines
7.8 KiB
JavaScript
// RMLWorkorderAdmin.js
|
||
Vue.component('r-m-l-workorder-admin', {
|
||
template: `
|
||
<tt-card>
|
||
<assign-company-modal
|
||
v-if="assignModalWorkorderId"
|
||
:workorder-id="assignModalWorkorderId"
|
||
@close="assignModalWorkorderId = null; $refs.table.$refs.table.refreshTable()"
|
||
/>
|
||
|
||
<documentation-viewer-modal
|
||
v-if="docsModalWorkorderId"
|
||
:workorder-id="docsModalWorkorderId"
|
||
@close="docsModalWorkorderId = null"
|
||
/>
|
||
|
||
<tt-table-crud
|
||
ref="table"
|
||
@assign="assignModalWorkorderId = $event.id"
|
||
@view_docs="docsModalWorkorderId = $event.id"
|
||
:crud-config="crudConfig"
|
||
>
|
||
<template v-slot:preorderinfo="{ row }">
|
||
<div v-html="row.preorderInfo" class="small"></div>
|
||
</template>
|
||
|
||
<template v-slot:status="{ row }">
|
||
<traffic-light :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:deadlinedate="{ row }">
|
||
{{ formatDate(row.deadlineDate) }}
|
||
</template>
|
||
|
||
<template v-slot:appointmentdate="{ row }">
|
||
{{ formatDate(row.appointmentDate) }}
|
||
</template>
|
||
|
||
</tt-table-crud>
|
||
</tt-card>
|
||
`,
|
||
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');
|
||
}
|
||
}
|
||
});
|
||
|
||
Vue.component('traffic-light', {
|
||
props: ['deadline', 'status'],
|
||
computed: {
|
||
lightInfo() {
|
||
if (['completed', 'new'].includes(this.status)) return { color: '#cccccc', title: 'Status irrelevant für Dringlichkeit' };
|
||
const now = moment();
|
||
const deadlineDate = moment.unix(this.deadline);
|
||
if (!deadlineDate.isValid()) return { color: '#cccccc', title: 'Keine Deadline gesetzt' };
|
||
|
||
if (deadlineDate.isBefore(now)) return { color: '#dc3545', title: 'Deadline überschritten' };
|
||
const daysLeft = deadlineDate.diff(now, 'days');
|
||
if (daysLeft <= 7) return { color: '#dc3545', title: 'Dringend: Weniger als 1 Woche' };
|
||
if (daysLeft <= 21) return { color: '#ffc107', title: 'Mittel: Weniger als 3 Wochen' };
|
||
return { color: '#28a745', title: 'Im Plan: Mehr als 3 Wochen' };
|
||
}
|
||
},
|
||
template: `<span :style="{ color: lightInfo.color, fontSize: '1.2em' }" class="mr-2" :title="lightInfo.title">●</span>`
|
||
});
|
||
|
||
Vue.component('assign-company-modal', {
|
||
props: ['workorderId'],
|
||
template: `
|
||
<tt-modal :show="true" title="Firma zuweisen" @submit="submit" @update:show="$emit('close')">
|
||
<tt-select label="Firma" :options="companies" v-model="selectedCompanyId" sm row required />
|
||
</tt-modal>
|
||
`,
|
||
data() { return { companies: [], selectedCompanyId: null } },
|
||
async mounted() {
|
||
const response = await axios.get(`${window.TT_CONFIG.BASE_PATH}/RMLWorkorderAdmin/getCompanies`);
|
||
this.companies = response.data;
|
||
},
|
||
methods: {
|
||
async submit() {
|
||
if (!this.selectedCompanyId) return window.notify('error', 'Bitte eine Firma auswählen.');
|
||
try {
|
||
const response = await axios.post(`${window.TT_CONFIG.BASE_PATH}/RMLWorkorderAdmin/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.');
|
||
}
|
||
} catch (e) {
|
||
window.notify('error', 'Ein Netzwerkfehler ist aufgetreten.');
|
||
}
|
||
}
|
||
}
|
||
});
|
||
|
||
Vue.component('documentation-viewer-modal', {
|
||
props: ['workorderId'],
|
||
template: `
|
||
<tt-modal :show="true" :title="'Dokumentation für Auftrag #' + workorderId" :save="false" :delete="false" @update:show="$emit('close')">
|
||
<div class="card">
|
||
<div class="card-header">
|
||
<h5>Hochgeladene Dokumente</h5>
|
||
</div>
|
||
<div v-if="loading" class="card-body text-center"><i class="fas fa-spinner fa-spin fa-2x"></i></div>
|
||
<div v-else-if="!docs.length" class="card-body text-center text-muted">Keine Dokumente vorhanden.</div>
|
||
<ul v-else class="list-group list-group-flush">
|
||
<li v-for="doc in docs" :key="doc.id" class="list-group-item">
|
||
<a :href="'/File/download?id=' + doc.fileId" target="_blank">
|
||
<i class="fas fa-file-download mr-2"></i> {{ doc.fileName }}
|
||
</a>
|
||
<div class="text-muted small mt-1">
|
||
<strong>Typ:</strong> {{ getDocTypeText(doc.documentType) }} <br/>
|
||
<strong>Beschreibung:</strong> {{ doc.description || '-' }} <br/>
|
||
<strong>Hochgeladen von:</strong> {{ doc.userName }} am {{ formatDate(doc.create) }}
|
||
</div>
|
||
</li>
|
||
</ul>
|
||
</div>
|
||
</tt-modal>
|
||
`,
|
||
data() {
|
||
return { loading: false, docs: [] }
|
||
},
|
||
methods: {
|
||
async fetchDocs() {
|
||
this.loading = true;
|
||
const response = await axios.get(`${window.TT_CONFIG.BASE_PATH}/RMLWorkorderAdmin/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();
|
||
}
|
||
}); |