Files
thetool/public/plugins/vue/tt-components/tt-position-manager.js
2025-03-24 13:10:42 +01:00

270 lines
12 KiB
JavaScript

Vue.component('tt-resolver', {
props: {
value: { type: [Number, String], required: true },
reference: { type: String, required: true },
autocomplete: { type: Boolean, default: false }
},
data: () => ({
loading: true,
text: ''
}),
template: `
<div v-if="loading" class="d-flex justify-content-center align-items-center">
<div class="spinner-border spinner-border-sm text-primary" role="status">
<span class="sr-only">Loading...</span>
</div>
</div>
<span v-else>{{ text }}</span>
`,
async created() {
const endpoint = this.autocomplete
? `${window.TT_CONFIG["BASE_PATH"]}${this.reference}?searchedID=${this.value}`
: `${window.TT_CONFIG["BASE_PATH"]}/${this.reference}/getById?id=${this.value}`;
const { data } = await axios.get(endpoint);
this.text = this.autocomplete ? data[0]?.text : data.name ?? data.title ?? data.text ?? '[E] Key not found';
this.loading = false;
}
});
Vue.component('tt-positions-manager',
{
props: {
value: {type: [Array, String], required: false},
config: {type: Object, required: true},
groupMode: {type: Boolean, default: false},
submitLoading: {type: Boolean, default: false}
},
data() {
return {
window: window,
positions: this.value,
formData: {},
groupName: '',
selectedIndex: null,
}
},
template: `
<div class="positions-manager">
<template v-if="config['header']">
<h4 class="text-center">{{ config["header"] }}</h4>
</template>
<div class="form-container">
<template v-for="(field, key) in config.fields">
<template v-if="typeof field.showCondition === 'function' ? field.showCondition(formData) : true">
<slot :name="key" v-bind:field="field" v-bind:value="formData[key]">
<tt-input
v-if="field.type === 'input'"
:label="field.label"
v-model="formData[key]"
@input="$emit('updateField-' + key, $event)"
sm
:type="field.inputType || 'text'"
/>
<tt-autocomplete
v-else-if="field.type === 'autocomplete'"
:label="field.label"
:emit-display-value="field.emitDisplayValue || false"
v-model="formData[key]"
@input="$emit('updateField-' + key, $event)"
:ref="'autocomplete-' + key"
:api-url="window.TT_CONFIG['BASE_PATH'] + field.apiUrl"
sm
/>
<tt-textarea
v-else-if="field.type === 'textarea'"
:label="field.label"
v-model="formData[key]"
@input="$emit('updateField-' + key, $event)"
sm
/>
<tt-checkbox
v-else-if="field.type === 'checkbox'"
:label="field.label"
@input="$emit('updateField-' + key, $event)"
sm
v-model="formData[key]"
/>
<tt-select
v-else-if="field.type === 'select'"
:label="field.label"
@input="$emit('updateField-' + key, $event)"
sm
v-model="formData[key]"
:options="field.options"
/>
</slot>
</template>
</template>
<div class="button-wrapper">
<tt-button @click="saveEntry"
sm
:loading="submitLoading"
:additional-class="selectedIndex === null ? 'btn-primary' : 'btn-success'"
:text="selectedIndex === null ? 'Hinzufügen' : 'Aktualisieren'"/>
</div>
</div>
<div class="form-container" v-if="groupMode">
<tt-input label="Gruppenname" v-model="groupName" sm/>
<tt-button @click="addGroup" sm text="Gruppe hinzufügen" additional-class="btn-primary"/>
</div>
<table class="table table-striped table-sm">
<thead>
<tr>
<th v-for="field in config['fields']">{{ field.label }}</th>
<th style="text-align: right;padding-right: 24px">Aktionen</th>
</tr>
</thead>
<tbody>
<template v-for="(group, groupName) in positionsToRender">
<tr v-if="groupMode">
<td colspan="100%">
<h4 style="text-align: center;">{{ groupName }}</h4>
</td>
</tr>
<tr v-for="(position, index) in group" :key="groupMode ? groupName + index : index">
<td v-for="(field, key) in config.fields">
<tt-resolver
:key="index + key + position[key]"
v-if="position[key] && (field.customFieldReference || field.type === 'autocomplete')"
:autocomplete="!field.customFieldReference && field.type === 'autocomplete'"
:reference="field.customFieldReference || field.apiUrl"
:value="position[key]"
/>
<span v-else-if="field.type === 'checkbox'">{{ position[key] ? 'Ja' : 'Nein' }}</span>
<span v-else>{{ formatFieldValue(position[key] ?? position[key + '_text'], field) }}</span>
</td>
<td class="d-flex justify-content-end">
<select v-if="groupMode" v-model="position._group" @change="$set(position, '_group', $event.target.value)">
<option v-for="group in allGroups" :value="group">{{ group }}</option>
</select>
<tt-button @click="editEntry(index)" sm additional-class="btn-primary" icon="fa fa-edit"/>
<tt-button @click="deleteEntry(index)" sm additional-class="btn-danger" icon="fa fa-trash"/>
</td>
</tr>
</template>
</tbody>
</table>
</div>
`,
methods: {
updateField(key, value) {
this.$set(this.formData, key, value);
},
defaultValidateForm(formData) {
for (const field of this.config["validateFormOptions"]) {
if (!(formData[field.key] || formData[field.key + '_text'])) {
window.notify('error', field.message);
return false;
}
}
return true;
},
checkEmitDisplayValueAutocomplete() {
for (const [key, field] of Object.entries(this.config.fields)) {
console.log("HI");
if ((typeof field.showCondition === 'function' && field.showCondition(this.formData) === true || !field.showCondition) && field.type === 'autocomplete' && field.emitDisplayValue && (isNaN(this.formData[key]) || !this.formData[key]) && this.$refs['autocomplete-' + key][0]) {
console.log("hi");
this.$set(this.formData, key + '_text', this.$refs['autocomplete-' + key][0].displayValue);
this.$delete(this.formData, key);
}
}
},
async saveEntry() {
this.checkEmitDisplayValueAutocomplete();
if (this.config.hasOwnProperty('validateFormOptions') && !this.defaultValidateForm(this.formData)) return;
else if (this.config.validateForm && !await this.config.validateForm(this.formData)) return;
if (this.selectedIndex === null) this.positions.push(this.formData);
else this.$set(this.positions, this.selectedIndex, this.formData);
if (this.config.customOrdering) {
this.positions.sort((a, b) => a[this.config.customOrdering] - b[this.config.customOrdering]);
}
this.$emit('input', this.positions);
this.resetForm();
},
addGroup() {
this.positions.push({_group: this.groupName});
this.groupName = '';
},
editEntry(index) {
this.selectedIndex = index;
this.formData = {...this.positions[index]};
},
deleteEntry(index) {
this.positions.splice(index, 1);
this.$emit('input', this.positions);
},
resetForm() {
this.formData = {};
for (const [key, field] of Object.entries(this.config.fields)) {
if (
typeof field.showCondition === 'function' && field.showCondition(this.formData) === true &&
field.type === 'autocomplete' && field.emitDisplayValue && this.$refs['autocomplete-' + key][0]) {
this.$refs['autocomplete-' + key][0].displayValue = '';
}
if (field.inputType === 'date') {
this.$set(this.formData, key, moment().format('YYYY-MM-DD'));
}
}
this.selectedIndex = null;
},
formatFieldValue(value, field) {
if (field.formatter) return field.formatter(value);
if (field.inputType === 'date') return moment(value).format('DD.MM.YYYY');
return value;
},
},
//TODO: cleanup
created() {
if (this.config["customMethods"]) Object.assign(this, this.config.customMethods);
if (!this.positions) this.positions = [];
if (typeof this.positions === 'string') {
try {
this.positions = JSON.parse(this.positions);
} catch (e) {
console.error(e);
this.positions = [];
}
}
},
mounted() {
this.resetForm();
},
computed: {
groupedPositions() {
const groups = {};
for (const position of this.positions) {
const group = position._group ?? 'Keine Gruppe';
if (!groups[group]) groups[group] = [];
if (Object.keys(position).length !== 1) groups[group].push(position);
}
return groups;
},
positionsToRender() {
return this.groupMode ? this.groupedPositions : {'': this.positions};
},
allGroups() {
return Object.keys(this.groupedPositions);
}
},
watch: {
value: {
handler() {
this.positions = this.value;
},
deep: true
}
}
});