Files
thetool/public/js/pages/ConstructionConsentSignTablet/ConstructionConsentSignTablet.js
2025-05-15 14:02:29 +02:00

349 lines
14 KiB
JavaScript

Vue.component('construction-consent-signature-pad', {
props: {
ownerId: {type: Number, required: true},
ownerName: {type: String, required: true}
},
data() {
return {
window: window,
signaturePad: null,
consent: null,
signatureDate: new Date().toLocaleDateString(),
notes: '',
penWidth: 2,
penColor: '#000000',
screenOrientation: 'landscape'
}
},
//language=Vue
template: `
<tt-modal class="signatureModal" :show="true" :delete="false" :save="false" @update:show="$emit('close')" :title="'Unterschrift für Bauvorhaben'">
<div class="signature-container">
<div class="signature-header">
<h4>Einwilligung für {{ ownerName }}</h4>
<p>Datum: {{ signatureDate }}</p>
</div>
<div class="signature-area" :class="screenOrientation">
<canvas id="consent-signature-pad" width="800" height="300"></canvas>
</div>
<div class="notes-area mb-3">
<tt-input v-model="notes" label="Unterschrieben von...." placeholder="Name oder i.V."></tt-input>
</div>
<div class="signature-actions">
<button class="btn btn-primary" @click="submit()">Speichern</button>
<button class="btn btn-outline-secondary" @click="signaturePad.clear()">Zurücksetzen</button>
<button class="btn btn-outline-danger" @click="$emit('close')">Abbrechen</button>
</div>
</div>
</tt-modal>
`,
methods: {
async submit() {
try {
if (this.signaturePad.isEmpty()) {
this.window.notify('error', 'Bitte eine Unterschrift hinzufügen');
return;
}
const data = this.signaturePad.toDataURL();
const response = await axios.post(window.TT_CONFIG['BASE_PATH'] + '/ConstructionConsent/sign', {
owner_id: this.ownerId,
signature: data,
signature_name: this.notes,
signature_date: this.signatureDate
});
if (response.data.success) {
this.window.notify('success', response.data.message || 'Unterschrift erfolgreich gespeichert');
this.$emit('close', true); // Pass true to indicate successful signing
} else {
this.window.notify('error',
response.data.errors ? Object.values(response.data.errors).join('<br>') : response.data.message || 'Ein Fehler ist aufgetreten');
}
} catch (error) {
console.error('Error submitting signature:', error);
this.window.notify('error', 'Ein Fehler ist beim Speichern der Unterschrift aufgetreten');
}
},
updatePenSettings() {
this.signaturePad.penColor = this.penColor;
this.signaturePad.penWidth = this.penWidth;
},
toggleOrientation() {
this.screenOrientation = this.screenOrientation === 'landscape' ? 'portrait' : 'landscape';
this.$nextTick(() => {
this.resizeCanvas();
});
},
resizeCanvas() {
const canvas = document.getElementById('consent-signature-pad');
const container = canvas.parentElement;
if (this.screenOrientation === 'landscape') {
canvas.width = container.offsetWidth - 20;
canvas.height = 300;
} else {
canvas.width = Math.min(container.offsetWidth - 20, 500);
canvas.height = 500;
}
// Important: maintain drawing after resize
const data = this.signaturePad.toData();
this.signaturePad.clear();
if (data) {
this.signaturePad.fromData(data);
}
}
},
mounted() {
this.$nextTick(() => {
const canvas = document.getElementById('consent-signature-pad');
this.signaturePad = new SignaturePad(canvas, {
penColor: this.penColor,
penWidth: this.penWidth,
velocityFilterWeight: 0.5 // Better for tablet input
});
// Initial canvas sizing
this.resizeCanvas();
// Resize on window change
window.addEventListener('resize', this.resizeCanvas);
// Handle tablet touch events better
canvas.addEventListener('touchstart', function(event) {
if (event.cancelable) {
event.preventDefault();
}
}, {passive: false});
});
},
beforeDestroy() {
window.removeEventListener('resize', this.resizeCanvas);
}
});
// Main Component for Construction Consent Owner Signing
Vue.component('construction-consent-sign-tablet', {
data() {
return {
window: window,
searchName: '',
searchFilter: {
consent_status: '',
owner_result: ''
},
results: [],
loading: false,
showSignaturePad: false,
selectedConsent: null,
filterOptions: {
consent_status: [
{text: 'Alle Status', value: ''},
{text: 'Offen', value: 'open'},
{text: 'In Bearbeitung', value: 'in_progress'},
{text: 'Abgeschlossen', value: 'completed'}
],
owner_result: [
{text: 'Alle Ergebnisse', value: ''},
{text: 'Nicht signiert', value: ''},
{text: 'Zugestimmt', value: 'accepted'},
{text: 'Abgelehnt', value: 'rejected'}
]
},
tableConfig: {
key: 'ConstructionConsentTable',
tableHeader: 'Bauvorhaben Eigentümer',
defaultPageSize: 10,
headers: [
{text: 'Bauvorhaben', key: 'consent_name', sortable: false, filter: false, class: 'text-nowrap', priority: 9},
{text: 'Projekt', key: 'project_name', sortable: false,filter: false, class: 'text-nowrap', priority: 7},
{text: 'Eigentümer', key: 'owner_name', sortable: false,filter: false, class: 'text-nowrap', priority: 6},
{text: 'Adresse', key: 'owner_address', sortable: false,filter: false, class: '', priority: 5},
{text: 'Eigentümer Status', key: 'owner_status', sortable: false,filter: false, class: 'text-nowrap', priority: 3},
{text: 'Eigentümer Ergebnis', key: 'owner_result', sortable: false,filter: false, class: 'text-nowrap', priority: 2},
{text: 'Aktionen', key: 'actions', sortable: false,filter: false, class: 'text-nowrap text-center', priority: 1}
],
}
}
},
//language=Vue
template: `
<div class="consent-signing-container">
<tt-card>
<div class="search-area p-3">
<div style="display: grid; grid-template-columns: 4fr 1fr; grid-gap: 16px; justify-content: space-between; align-items: center;">
<tt-input
v-model="searchName"
label="Eigentümer suchen"
placeholder="Name eingeben..."
></tt-input>
<tt-button
style="margin-top: 10px"
@click="searchOwners"
text="Suchen"
additionalClass="btn-primary"
icon="fas fa-search"
:loading="loading"
></tt-button>
</div>
</div>
</tt-card>
<tt-card v-if="results.length > 0" class="mt-3">
<tt-table
:data="results"
:config="tableConfig"
excel-export
>
<template v-slot:owner_status="{ row }">
<span class="badge" :class="getOwnerStatusBadgeClass(row.owner_status)">
{{ getOwnerStatusText(row.owner_status) }}
</span>
</template>
<template v-slot:owner_result="{ row }">
<span class="badge" :class="getOwnerResultBadgeClass(row.owner_result)">
{{ getOwnerResultText(row.owner_result) }}
</span>
</template>
<template v-slot:actions="{ row }">
<div class="btn-group btn-group-sm">
<tt-button
@click="openSignaturePad(row)"
text="Unterschreiben"
:sm="true"
additionalClass="btn-primary"
icon="fas fa-signature"
:disabled="row.owner_result === 'accepted'"
></tt-button>
<a :href="window.TT_CONFIG['BASE_PATH'] + '/ConstructionConsent/view/?id=' + row.consent_id"
class="btn btn-sm btn-info"
target="_blank"
title="Ansehen">
<i class="far fa-eye"></i>
</a>
<a :href="window.TT_CONFIG['BASE_PATH'] + '/ConstructionConsent/Download/?open=true&owner_id=' + row.owner_id"
class="btn btn-sm btn-info"
target="_blank"
title="PDF">
<i class="fas fa-file-pdf"></i>
</a>
</div>
</template>
</tt-table>
</tt-card>
<tt-card v-else-if="results.length === 0 && !loading" class="mt-3">
<div class="alert alert-info">
Keine Ergebnisse gefunden. Bitte versuchen Sie eine andere Suche.
</div>
</tt-card>
<construction-consent-signature-pad
v-if="showSignaturePad"
:owner-id="selectedConsent.owner_id"
:owner-name="selectedConsent.owner_name"
@close="handleSignatureClose"
></construction-consent-signature-pad>
</div>
`,
methods: {
async searchOwners() {
this.loading = true;
try {
const response = await axios.post(
window.TT_CONFIG['BASE_PATH'] + '/ConstructionConsent/searchTablet',
{
name: this.searchName,
filter: this.searchFilter
}
);
if (response.data.success) {
this.results = response.data.results;
} else {
this.window.notify('error', response.data.message || 'Fehler bei der Suche');
this.results = [];
}
} catch (error) {
console.error('Error searching owners:', error);
this.window.notify('error', 'Ein Fehler ist bei der Suche aufgetreten');
this.results = [];
} finally {
this.loading = false;
}
},
openSignaturePad(consent) {
this.selectedConsent = consent;
this.showSignaturePad = true;
},
handleSignatureClose(success) {
this.showSignaturePad = false;
if (success) {
// Refresh the data after successful signing
this.searchOwners();
}
},
getStatusBadgeClass(status) {
switch (status) {
case 'open': return 'badge-primary';
case 'in_progress': return 'badge-warning';
case 'completed': return 'badge-success';
default: return 'badge-secondary';
}
},
getStatusText(status) {
switch (status) {
case 'open': return 'Offen';
case 'in_progress': return 'In Bearbeitung';
case 'completed': return 'Abgeschlossen';
default: return status;
}
},
getOwnerStatusBadgeClass(status) {
switch (status) {
case 'pending': return 'badge-warning';
case 'contacted': return 'badge-info';
case 'signed': return 'badge-success';
case 'returned': return 'badge-success';
default: return 'badge-secondary';
}
},
getOwnerStatusText(status) {
switch (status) {
case 'pending': return 'Ausstehend';
case 'contacted': return 'Kontaktiert';
case 'signed': return 'Unterschrieben';
case 'returned': return 'Zurückgegeben';
default: return status;
}
},
getOwnerResultBadgeClass(result) {
if (!result) return 'badge-primary';
switch (result) {
case 'accepted': return 'badge-success';
case 'rejected': return 'badge-danger';
case 'denied': return 'badge-danger';
default: return 'badge-secondary';
}
},
getOwnerResultText(result) {
if (!result) return 'Nicht signiert';
switch (result) {
case 'accepted': return 'Zugestimmt';
case 'rejected': return 'Abgelehnt';
default: return result;
}
}
},
mounted() {
// Initialize with empty search to show all results
this.searchOwners();
}
});