Files
thetool/public/plugins/vue/tt-components/tt-table-crud.js
2025-07-23 20:44:25 +02:00

203 lines
12 KiB
JavaScript

Vue.component('tt-table-crud', {
//language=Vue
template: `
<div>
<tt-table :fetch-url="window['TT_CONFIG']['TABLE_URL']" :config="tableConfig"
small ssr ref="table">
<template v-slot:top-buttons>
<button type="button" class="btn btn-primary" @click="openCrudModal" v-if="crudConfig.createText !== false">
<i class="fas fa-plus"></i>
{{crudConfig.createText || 'Erstellen'}}
</button>
<slot name="table-top-buttons"></slot>
</template>
<template v-for="column in crudConfig.columns"
:slot="column.key.toLowerCase()"
slot-scope="{row}">
<slot v-if="$scopedSlots[column.key.toLowerCase()] && column.modal?.type !== 'select'"
:name="column.key.toLowerCase()" :row="row">
</slot>
<span v-else-if="column.modal?.type === 'select' && column.modal.items.find(item => item.value == row[column.key] + '')">{{column.modal.items.find(item => item.value == row[column.key]).text}}</span>
<span v-else-if="column.modal?.type === 'select' && row[column.key] !== null && row[column.key] !== undefined"
:data-all-items="JSON.stringify(column.modal.items)"
>Select not found for column {{column.key}} and value {{row[column.key]}} {{typeof row[column.key]}}</span>
</template>
<template v-slot:actions="{ row }">
<!-- calculate min width 1 + number of actions * 19 -->
<div style="display: flex; justify-content: space-around; align-items: center;"
:style="{minWidth: calculateMinActionsWidth(row)}">
<a v-if="!crudConfig.editCondition || crudConfig.editCondition(row)"
style="cursor: pointer;" @click="openCrudModal(row)"><i class="far fa-edit text-primary"
title="Editieren"></i></a>
<!-- v-for action crudConfig.additionalActions -> v-if action.condition(row) || true , action.class for icon class, action.title for title, action.key for emitting event -->
<a v-for="action in crudConfig.additionalActions"
v-if="!action.condition || action.condition(row)"
style="cursor: pointer;" @click="$emit(action.key, row)">
<i :class="action.class" :title="action.title"></i>
</a>
</div>
</template>
<template v-if="$scopedSlots.expandedRow" v-slot:expandedRow="{ row }">
<slot name="expandedRow" :row="row"/>
</template>
</tt-table>
<tt-modal :show.sync="crudModal"
:title="crudConfig.createText || 'Erstellen'"
@submit="submitCrudModal"
@delete="deleteCrudModal"
@close="resetCrudModalData">
<template v-for="column in modalConfig.headers.filter(column => column.visible !== false)">
<!-- @formatter:off -->
<!-- <slot v-if="$scopedSlots[column.key.toLowerCase() + '-modal'] && column.type !== false && 1 < 0" :name="column.key.toLowerCase() + '-modal'" slot-scope="{crudModalData}"></slot>-->
<slot :name="column.key.toLowerCase() + '-modal'" :crudModalData="crudModalData">
<tt-input v-show="crudModalColumnVisibility[column.key]" v-if="column.type === 'text'" v-model="crudModalData[column.key]" :label="column.text" sm row/>
<tt-input v-show="crudModalColumnVisibility[column.key]" v-else-if="column.type === 'number'" v-model="crudModalData[column.key]" :label="column.text" type="number" sm row/>
<tt-textarea v-show="crudModalColumnVisibility[column.key]" v-else-if="column.type === 'textarea'" v-model="crudModalData[column.key]" :label="column.text" sm row/>
<tt-select v-show="crudModalColumnVisibility[column.key]" v-else-if="column.type === 'select'" v-model="crudModalData[column.key]" :label="column.text" :options="column.items" sm row/>
<tt-autocomplete v-show="crudModalColumnVisibility[column.key]" v-else-if="column.type === 'autocomplete'" v-model="crudModalData[column.key]" :label="column.text" :api-url="column.apiUrl" :items="typeof column.items === 'string' ? [] : column.items" sm row :return-text="column.returnText" />
<tt-date-picker v-show="crudModalColumnVisibility[column.key]" v-else-if="column.type === 'datepicker'" v-model="crudModalData[column.key]" :label="column.text" sm row :date-range="false" :ref="column.key.toLowerCase() + '-modal-input'"/>
<tt-icon-select v-show="crudModalColumnVisibility[column.key]" v-else-if="column.type === 'icon-select'" v-model="crudModalData[column.key]" :options="column.items" :label="column.text" sm row/>
<tt-checkbox v-show="crudModalColumnVisibility[column.key]" v-else-if="column.type === 'checkbox'" v-model="crudModalData[column.key]" :label="column.text" sm row/>
<tt-positions-manager v-show="crudModalColumnVisibility[column.key]" v-else-if="column.type === 'positions-manager'" v-model="crudModalData[column.key]" :config="column.config" sm row/>
</slot>
<!-- @formatter:on -->
</template>
<slot name="modal-prepend" :crudModalData="crudModalData"></slot>
</tt-modal>
</div>
`, props: {
crudConfig: {type: Object, required: false, default: () => (window['TT_CONFIG']['CRUD_CONFIG'])},
emitEdit: {type: Boolean, required: false, default: false}
}, data() {
return {
crudModal: false, crudModalData: {}, crudModalColumnVisibility: {}, crudModalColumnVisibilityCheck: {}, window: window
}
}, methods: {
openCrudModal(row = null) {
if (this.emitEdit) {
this.$emit('edit', row)
return
}
this.crudModalData = row ? JSON.parse(JSON.stringify(row)) : {};
this.crudModal = true;
}, resetCrudModalData() {
this.crudModalData = {};
this.crudModal = false;
}, async submitCrudModal() {
delete this.crudModalData.isTrusted;
for (const column of this.modalConfig.headers) {
if (column.type === 'autocomplete' && this.crudModalData[column.key] === '') {
delete this.crudModalData[column.key];
}
}
const response = await axios.post(this.crudModalData.id ? window['TT_CONFIG']['UPDATE_URL'] : window['TT_CONFIG']['CREATE_URL'],
this.crudModalData);
if (response.data.success) {
this.$refs.table.refreshTable();
this.resetCrudModalData();
this.window.notify('success', response.data.message || 'Erfolgreich gespeichert');
} else {
this.window.notify('error',
response.data.errors ? Object.values(response.data.errors).join('<br>') : response.data.message || 'Ein Fehler ist aufgetreten');
}
}, async deleteCrudModal() {
const response = await axios.post(window['TT_CONFIG']['DELETE_URL'], {id: this.crudModalData.id});
if (response.data.success) {
this.$refs.table.refreshTable();
this.resetCrudModalData();
}
this.window.notify(response.data.success ? 'success' : 'error', response.data.message);
},
async checkCrudModalColumnVisibility(val, oldVal) {
const crudModalColumnVisibility = {}
for (const column of this.modalConfig.headers) {
if (!column?.visible?.reference || typeof column.visible.reference !== 'string') {
crudModalColumnVisibility[column.key] = true
continue;
}
const localId = this.crudModalData[column.visible.use.split('=')[0]]
if (this.crudModalColumnVisibilityCheck[column.key] &&
this.crudModalColumnVisibilityCheck[column.key][column.visible.reference] === localId) {
crudModalColumnVisibility[column.key] = this.crudModalColumnVisibilityCheck[column.key]['visibility']
continue;
}
if (!localId) {
crudModalColumnVisibility[column.key] = false
continue;
}
const reference = column.visible.reference
let referenceData = await axios.get(window['TT_CONFIG']['BASE_PATH'] + `/${reference}/getById?id=${localId}`)
referenceData = referenceData.data
// noinspection EqualityComparisonWithCoercionJS
crudModalColumnVisibility[column.key] = referenceData[column.visible.key] == column.visible.value
this.crudModalColumnVisibilityCheck[column.key] = {
[column.visible.reference]: localId,
visibility: crudModalColumnVisibility[column.key]
}
}
this.$set(this, 'crudModalColumnVisibility', crudModalColumnVisibility)
},
calculateMinActionsWidth(row) {
// Base width for the edit action
let minWidth = 19;
// Check additional actions
if (this.crudConfig && this.crudConfig.additionalActions) {
this.crudConfig.additionalActions.forEach(action => {
// Add width if action is visible (no condition or condition is true)
if (!action.condition || action.condition(row)) {
minWidth += 19;
}
});
}
return `${minWidth}px`;
}
}, computed: {
tableConfig() {
return {
key: this.crudConfig.key,
tableHeader: this.crudConfig.tableHeader,
headers: this.crudConfig.columns.filter(column => column.table !== false).map(column => {
return {text: column.text, key: column.key, ...column.table, filterOptions: column?.table?.filterOptions ?? column?.modal?.items ?? [], priority: column.priority}
})
}
}, modalConfig() {
return {
key: this.crudConfig.key, headers: this.crudConfig.columns.filter(column => column.modal !== false).map(column => {
const type = column.modal?.type || "text"
return {text: column.text, key: column.key, type, ...column.modal}
}),
}
}
}, watch: {
crudModalData: {
handler: async function (val, oldVal) {
if (!val) return
await this.checkCrudModalColumnVisibility(val, oldVal)
}, deep: true
}
}
})