Merge branch 'ConstructionConsent/add-signature' into 'master'
added new signature action See merge request fronk/thetool!1340
This commit is contained in:
@@ -679,4 +679,85 @@ FROM ConstructionConsent
|
||||
return $where;
|
||||
}
|
||||
|
||||
public static function searchConstructionConsentBasedOnName($name, $filter = []) {
|
||||
$items = [];
|
||||
$db = FronkDB::singleton();
|
||||
|
||||
$searchName = trim($name);
|
||||
if (empty($searchName)) {
|
||||
// return $items;
|
||||
}
|
||||
|
||||
$searchTerms = explode(' ', $searchName);
|
||||
$searchConditions = [];
|
||||
|
||||
foreach ($searchTerms as $term) {
|
||||
$escapedTerm = $db->escape($term);
|
||||
$searchConditions[] = "(cco.firstname LIKE '%$escapedTerm%' OR
|
||||
cco.lastname LIKE '%$escapedTerm%' OR
|
||||
cc.name LIKE '%$escapedTerm%' OR
|
||||
ccp.name LIKE '%$escapedTerm%')";
|
||||
}
|
||||
|
||||
if (!empty($searchConditions)) {
|
||||
$intelligentSearchWhere = "(" . implode(' AND ', $searchConditions) . ")";
|
||||
if (isset($filter['add-where'])) {
|
||||
$filter['add-where'] .= " AND $intelligentSearchWhere";
|
||||
} else {
|
||||
$filter['add-where'] = "AND $intelligentSearchWhere";
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($filter['add-where'])) {
|
||||
$filter['add-where'] .= " AND cco.firstname IS NOT NULL";
|
||||
} else {
|
||||
$filter['add-where'] = "AND cco.firstname IS NOT NULL";
|
||||
}
|
||||
|
||||
$where = self::getSqlFilter($filter);
|
||||
|
||||
$sql = "SELECT cc.id AS consent_id, cc.name AS consent_name, cc.status AS consent_status,
|
||||
cc.result AS consent_result, cc.ez, cc.kg, cc.gst, cc.gstnr,
|
||||
ccp.id AS project_id, ccp.name AS project_name,
|
||||
cco.id AS owner_id, cco.firstname, cco.lastname, cco.street, cco.zip, cco.city,
|
||||
cco.email, cco.phone, cco.status AS owner_status, cco.result AS owner_result,
|
||||
vh.strasse AS address_street, vh.hausnummer AS address_number,
|
||||
vh.plz AS address_zip, vh.ortschaft AS address_city
|
||||
FROM ConstructionConsent cc
|
||||
LEFT JOIN ConstructionConsentProject ccp ON cc.constructionconsentproject_id = ccp.id
|
||||
LEFT JOIN ConstructionConsentOwner cco ON cc.id = cco.constructionconsent_id
|
||||
LEFT JOIN `".ADDRESSDB_DBNAME."`.view_hausnummer vh ON cc.adb_hausnummer_id = vh.hausnummer_id
|
||||
WHERE $where
|
||||
ORDER BY cc.edit DESC, cco.lastname ASC, cco.firstname ASC";
|
||||
|
||||
$res = $db->query($sql);
|
||||
while ($data = $res->fetch_assoc()) {
|
||||
$items[] = [
|
||||
'consent_id' => $data['consent_id'],
|
||||
'consent_name' => $data['consent_name'],
|
||||
'consent_status' => $data['consent_status'],
|
||||
'consent_result' => $data['consent_result'],
|
||||
'project_id' => $data['project_id'],
|
||||
'project_name' => $data['project_name'],
|
||||
'owner_id' => $data['owner_id'],
|
||||
'owner_name' => trim($data['firstname'] . ' ' . $data['lastname']),
|
||||
'owner_address' => $data['street'] . ', ' . $data['zip'] . ' ' . $data['city'],
|
||||
'owner_contact' => $data['email'] . ($data['phone'] ? ' / ' . $data['phone'] : ''),
|
||||
'owner_status' => $data['owner_status'],
|
||||
'owner_result' => $data['owner_result'],
|
||||
'property_info' => 'EZ:' . $data['ez'] . ' KG:' . $data['kg'] . ' GST:' . $data['gst'] . ($data['gstnr'] ? '-' . $data['gstnr'] : ''),
|
||||
'address' => $data['address_street'] . ' ' . $data['address_number'] . ', ' . $data['address_zip'] . ' ' . $data['address_city'],
|
||||
];
|
||||
}
|
||||
|
||||
return $items;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -168,6 +168,7 @@ class ConstructionConsentController extends mfBaseController {
|
||||
protected function downloadAction()
|
||||
{
|
||||
$owner_id = $this->request->owner_id;
|
||||
$open = $this->request->open;
|
||||
|
||||
if (!is_numeric($owner_id) || $owner_id < 1 || !($owner = new ConstructionConsentOwner($owner_id))->id) {
|
||||
$this->layout()->setFlash("Besitzer nicht gefunden", "error");
|
||||
@@ -184,7 +185,7 @@ class ConstructionConsentController extends mfBaseController {
|
||||
$this->redirect("ConstructionConsent", "View", ["id" => $cc->id]);
|
||||
}
|
||||
|
||||
$this->sendPdfResponse($filename, "Zustimmungserklärung-{$cc->id}-{$owner_id}.pdf");
|
||||
$this->sendPdfResponse($filename, "Zustimmungserklärung-{$cc->id}-{$owner_id}.pdf", $open === 'true');
|
||||
}
|
||||
|
||||
protected function downloadMultipleAction()
|
||||
@@ -224,10 +225,12 @@ class ConstructionConsentController extends mfBaseController {
|
||||
$this->sendPdfResponse($filename, "Zustimmungserklärungen-".date('Y-m-d').".pdf");
|
||||
}
|
||||
|
||||
private function sendPdfResponse(string $filename, string $downloadName): void
|
||||
private function sendPdfResponse(string $filename, string $downloadName, bool $open = false): void
|
||||
{
|
||||
header('Content-Type: ' . mime_content_type($filename));
|
||||
header('Content-disposition: attachment; filename="' . rawurlencode($downloadName) . '"');
|
||||
if ($open === true) header('Content-disposition: inline; filename="' . rawurlencode($downloadName) . '"');
|
||||
else header('Content-disposition: attachment; filename="' . rawurlencode($downloadName) . '"');
|
||||
|
||||
header('Content-Length: ' . filesize($filename));
|
||||
readfile($filename);
|
||||
exit;
|
||||
@@ -1278,4 +1281,84 @@ class ConstructionConsentController extends mfBaseController {
|
||||
fclose($output);
|
||||
exit();
|
||||
}
|
||||
|
||||
protected function searchTabletAction() {
|
||||
$name = '';
|
||||
$filter = [];
|
||||
|
||||
// Handle POST data - support both form data and JSON input
|
||||
if($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$postData = [];
|
||||
|
||||
// Check for JSON input (Axios sends JSON)
|
||||
$inputJSON = file_get_contents('php://input');
|
||||
$jsonData = json_decode($inputJSON, true);
|
||||
|
||||
if($jsonData !== null) {
|
||||
$postData = $jsonData;
|
||||
} else {
|
||||
$postData = $_POST;
|
||||
}
|
||||
|
||||
if(isset($postData['name'])) {
|
||||
$name = $postData['name'];
|
||||
}
|
||||
|
||||
if(isset($postData['filter']) && is_array($postData['filter'])) {
|
||||
$filter = $postData['filter'];
|
||||
}
|
||||
}
|
||||
|
||||
$results = ConstructionConsent::searchConstructionConsentBasedOnName($name, $filter);
|
||||
|
||||
self::returnJson([
|
||||
'success' => true,
|
||||
'count' => count($results),
|
||||
'results' => $results
|
||||
]);
|
||||
}
|
||||
|
||||
protected function signTabletAction() {
|
||||
$JSGlobals = ["BASE_PATH" => self::getUrl(""),
|
||||
"DASHBOARD_URL" => self::getUrl("Dashboard"),
|
||||
"MFAPPNAME" => MFAPPNAME_SLUG,
|
||||
"PAGE_TITLE" => "Zustimmungserklärungen unterzeichnen",
|
||||
"PATH" => [
|
||||
["text" => MFAPPNAME_SLUG, "href" => self::getUrl("Dashboard")],
|
||||
["text" => "Zustimmungserklärungen unterzeichnen", "href" => self::getUrl("ConstructionConsent", "SignTablet")],
|
||||
],
|
||||
"IS_ADMIN" => $this->me->is("Admin"),
|
||||
];
|
||||
|
||||
$this->layout()->set("vueViewName", "ConstructionConsentSignTablet");
|
||||
$this->layout()->set("JSGlobals", $JSGlobals);
|
||||
$this->layout()->setTemplate("VueViews/Vue");
|
||||
}
|
||||
|
||||
protected function signAction() {
|
||||
$POST = json_decode(file_get_contents('php://input'), true);
|
||||
|
||||
$owner_id = $POST['owner_id'] ?? null;
|
||||
$signature = $POST['signature'] ?? null;
|
||||
$signature_name = $POST['signature_name'] ?? null;
|
||||
$signature_date = $POST['signature_date'] ?? null;
|
||||
|
||||
if(!$owner_id || !$signature) {
|
||||
self::sendError("Invalid request");
|
||||
}
|
||||
|
||||
$owner = new ConstructionConsentOwner($owner_id);
|
||||
if(!$owner->id) self::sendError("Owner not found");
|
||||
|
||||
if($signature_name) $owner->signature_name = $signature_name;
|
||||
if($signature_date) $owner->signature_date = date("U", strtotime($signature_date));
|
||||
if($signature) $owner->signature = $signature;
|
||||
|
||||
$owner->status = 'returned';
|
||||
$owner->result = 'accepted';
|
||||
|
||||
if(!$owner->save()) self::sendError("Error saving signature");
|
||||
self::returnJson(["success" => true]);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
use Phinx\Migration\AbstractMigration;
|
||||
|
||||
final class ConstrConsOwnerAddSignature extends AbstractMigration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
if($this->getEnvironment() == "addressdb") {
|
||||
$ConstructionConsentOwner = $this->table("ConstructionConsentOwner");
|
||||
$ConstructionConsentOwner->addColumn("signature", "text", ["null" => true]);
|
||||
$ConstructionConsentOwner->addColumn("signature_name", "string", ["limit" => 255, "null" => true]);
|
||||
$ConstructionConsentOwner->addColumn("signature_date", "integer", ["null" => true]);
|
||||
|
||||
$ConstructionConsentOwner->update();
|
||||
}
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
if($this->getEnvironment() == "addressdb") {
|
||||
$ConstructionConsentOwner = $this->table("ConstructionConsentOwner");
|
||||
$ConstructionConsentOwner->removeColumn("signature");
|
||||
$ConstructionConsentOwner->removeColumn("signature_name");
|
||||
$ConstructionConsentOwner->removeColumn("signature_date");
|
||||
|
||||
$ConstructionConsentOwner->update();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
.consent-signing-container {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.signatureModal .modal-dialog {
|
||||
max-width: 90%;
|
||||
}
|
||||
|
||||
.signature-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.signature-header {
|
||||
text-align: center;
|
||||
margin-bottom: 20px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.signature-options {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.signature-area {
|
||||
width: 100%;
|
||||
margin-bottom: 20px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 5px;
|
||||
overflow: hidden;
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
|
||||
.signature-area.landscape {
|
||||
height: 300px;
|
||||
}
|
||||
|
||||
.signature-area.portrait {
|
||||
height: 500px;
|
||||
}
|
||||
|
||||
.signature-actions {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 10px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.notes-area {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* Touch-friendly styling for tablets */
|
||||
@media (max-width: 1024px) {
|
||||
.btn, .form-control, select {
|
||||
height: 46px;
|
||||
padding: 10px 15px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.btn-sm {
|
||||
height: 38px;
|
||||
padding: 8px 12px;
|
||||
}
|
||||
|
||||
.table th, .table td {
|
||||
padding: 12px 8px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
input[type=range] {
|
||||
height: 30px;
|
||||
}
|
||||
|
||||
input[type=color] {
|
||||
height: 30px;
|
||||
width: 50px;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,349 @@
|
||||
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();
|
||||
}
|
||||
});
|
||||
File diff suppressed because one or more lines are too long
@@ -162,7 +162,7 @@ Vue.component('tt-table', {
|
||||
<tt-input v-if="column.filter === 'search' && !disableFiltering" v-model="filters[column.key]" sm/>
|
||||
<tt-icon-select v-else-if="column.filter === 'iconSelect' && !disableFiltering" :options="column.filterOptions" v-model="filters[column.key]" sm :multiple="column.filterOptions.length > 2 && this.ssr === true"/>
|
||||
<tt-number-range v-else-if="column.filter === 'numberRange' && !disableFiltering" :returnText="!ssr" v-model="filters[column.key]" sm/>
|
||||
<tt-select v-else-if="column.filter === 'select' && !disableFiltering" :options="[{text: 'Alle', value: undefined}, ...column.filterOptions]" v-model="filters[column.key]" sm multiple/>
|
||||
<tt-select v-else-if="column.filter === 'select' && !disableFiltering" :options="[...column.filterOptions]" v-model="filters[column.key]" sm multiple/>
|
||||
<tt-autocomplete v-else-if="column.filter === 'autocomplete' && !disableFiltering" :api-url="column.filterOptions" v-model="filters[column.key]" sm/>
|
||||
<tt-date-picker v-else-if="(column.filter === 'date' || column.filter === 'datepicker') && !disableFiltering" v-model="filters[column.key]" sm/>
|
||||
<!-- @formatter:on -->
|
||||
@@ -559,6 +559,25 @@ Vue.component('tt-table', {
|
||||
}
|
||||
|
||||
}, watch: {
|
||||
data: {
|
||||
handler: function () {
|
||||
// we need to refresh the table if the prop data changes
|
||||
this.loading = true;
|
||||
this.rawRows = this.data;
|
||||
this.pagination = {
|
||||
page: Math.max(this.pagination.page, 1),
|
||||
per_page: this.pagination?.per_page ? parseInt(this.pagination.per_page) : 10,
|
||||
total_rows: this.rawRows.length || 0,
|
||||
total_pages: this.rawRows.length / this.pagination?.per_page,
|
||||
filtered_available: this.rawRows.length
|
||||
}
|
||||
this.loading = false;
|
||||
this.isInitialised = true;
|
||||
this.$nextTick(() => {
|
||||
this.handleResponsiveColumns()
|
||||
})
|
||||
}
|
||||
},
|
||||
filters: {
|
||||
handler: function () {
|
||||
if (!this.isInitialised) return;
|
||||
|
||||
Reference in New Issue
Block a user