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: `
Loading...
{{ text }}`, async created() { const cacheKey = `${this.reference}_${this.value}_${this.autocomplete}`; const cached = JSON.parse(localStorage.getItem(cacheKey) || 'null'); if (cached && Date.now() - cached.timestamp < 1800000) { this.text = cached.text; this.loading = false; return; } 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); if (this.reference === 'WarehouseArticle') this.text = `${data.articleNumber} | ${data.title}`; else this.text = this.autocomplete ? data[0]?.text : data.name ?? data.title ?? data.text ?? '[E] Key not found'; localStorage.setItem(cacheKey, JSON.stringify({ text: this.text, timestamp: Date.now() })); 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, editingGroupName: null, tempGroupName: '', editingCell: { groupName: null, index: null, key: null }, ctrlPressed: false, draggingItem: null, dragOverItem: null, draggingGroup: null } }, template: `
{{ field.label }} Aktionen
`, methods: { handleKeyDown(event) { if (event.key === 'Control' || event.key === 'Meta') this.ctrlPressed = true; }, handleKeyUp(event) { if (event.key === 'Control' || event.key === 'Meta') this.ctrlPressed = false; }, onDragStart(position, event) { this.draggingItem = position; this.draggingGroup = null; event.dataTransfer.setData('text/plain', ''); event.dataTransfer.effectAllowed = 'move'; }, onGroupDragStart(groupName, event) { this.draggingGroup = groupName; this.draggingItem = null; event.dataTransfer.setData('text/plain', ''); event.dataTransfer.effectAllowed = 'move'; }, onDrop(targetItemOrGroup) { if (this.draggingItem) { const draggedIndex = this.positions.indexOf(this.draggingItem); const itemToMove = this.positions.splice(draggedIndex, 1)[0]; let targetIndex, newGroup; if (typeof targetItemOrGroup === 'string') { newGroup = targetItemOrGroup; const firstItemOfGroup = this.positions.find(p => (p._group || 'Keine Gruppe') === newGroup); targetIndex = firstItemOfGroup ? this.positions.indexOf(firstItemOfGroup) : this.positions.length; } else { newGroup = targetItemOrGroup._group || 'Keine Gruppe'; targetIndex = this.positions.indexOf(targetItemOrGroup); } this.$set(itemToMove, '_group', newGroup); this.positions.splice(targetIndex, 0, itemToMove); this.$emit('input', this.positions); } else if (this.draggingGroup) { const targetGroupName = typeof targetItemOrGroup === 'string' ? targetItemOrGroup : (targetItemOrGroup._group || 'Keine Gruppe'); if (this.draggingGroup === targetGroupName) return this.cleanupDrag(); const groupOrder = [...this.allGroups]; const fromIndex = groupOrder.indexOf(this.draggingGroup); const [movedGroup] = groupOrder.splice(fromIndex, 1); const toIndex = groupOrder.indexOf(targetGroupName); groupOrder.splice(toIndex, 0, movedGroup); const newPositions = groupOrder.flatMap(groupName => this.positions.filter(p => (p._group || 'Keine Gruppe') === groupName) ); this.$emit('input', newPositions); } this.cleanupDrag(); }, onDragEnd() { this.cleanupDrag(); }, cleanupDrag() { this.draggingItem = null; this.dragOverItem = null; this.draggingGroup = null; }, 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)) { 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]) { this.$set(this.formData, key + '_text', this.$refs['autocomplete-' + key][0].displayValue); this.$delete(this.formData, key); } if ((typeof field.showCondition === 'function' && field.showCondition(this.formData) === true || !field.showCondition) && field.type === 'input-article' && field.emitDisplayValue && (isNaN(this.formData[key]) || !this.formData[key]) && this.$refs['article-' + key][0]) { console.log(this.$refs['article-' + key][0].$refs.autocomplete); this.$set(this.formData, key + '_text', this.$refs['article-' + key][0].$refs.autocomplete.displayValue); this.$delete(this.formData, key); } if (field.emitDisplayValue && this.formData[key] !== null && this.formData[key] !== undefined) { this.$delete(this.formData, key + '_text'); } } }, 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 = ''; }, startGroupEdit(groupName) { this.editingGroupName = groupName; this.tempGroupName = groupName; }, saveGroupName(oldGroupName) { if (this.tempGroupName.trim() === '') { window.notify('error', 'Gruppenname darf nicht leer sein'); return; } if (this.tempGroupName !== oldGroupName) { this.positions.forEach(position => { if (position._group === oldGroupName) { this.$set(position, '_group', this.tempGroupName); } }); this.$emit('input', this.positions); } this.editingGroupName = null; this.tempGroupName = ''; }, cancelGroupEdit() { this.editingGroupName = null; this.tempGroupName = ''; }, deleteGroup(groupName) { if (confirm(`Möchten Sie die Gruppe "${groupName}" wirklich löschen? Alle Einträge werden in "Keine Gruppe" verschoben.`)) { this.positions = this.positions.filter(position => { if (position._group === groupName) { if (Object.keys(position).length === 1) return false; this.$set(position, '_group', 'Keine Gruppe'); } return true; }); this.$emit('input', this.positions); } }, async editEntry(position) { const index = this.positions.indexOf(position); if (index === -1) return; this.selectedIndex = index; for (const [key, field] of Object.entries(this.config.fields)) { if (field.type === 'autocomplete' && field.emitDisplayValue) { await this.$nextTick(); if (this.positions[index][key + '_text'] && this.$refs['autocomplete-' + key][0]) { this.$refs['autocomplete-' + key][0].displayValue = this.positions[index][key + '_text']; this.$set(this.formData, key, this.positions[index][key]); } } } this.formData = {...this.positions[index]}; this.$nextTick(() => { this.$refs.formContainer.scrollIntoView({ behavior: 'smooth', block: 'start' }); }); }, deleteEntry(position) { const index = this.positions.indexOf(position); if (index > -1) { 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; }, startCellEdit(groupName, index, key, field) { if (!field.editableInTable || !(field.type === 'input' || field.type === 'textarea')) return; this.editingCell = { groupName, index, key }; this.$nextTick(() => { const inputElement = this.$el.querySelector(`.editable-cell input[type="${field.type}"], .editable-cell textarea`); if (inputElement) inputElement.focus(); }); }, saveCellEdit(position, key) { this.editingCell = { groupName: null, index: null, key: null }; this.$emit('input', this.positions); }, isCellEditing(groupName, index, key) { const currentGroup = (this.positions[index] && this.positions[index]._group) || 'Keine Gruppe'; return this.editingCell.groupName === currentGroup && this.editingCell.index === index && this.editingCell.key === key; } }, 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(); window.addEventListener('keydown', this.handleKeyDown); window.addEventListener('keyup', this.handleKeyUp); window.addEventListener('blur', () => { this.ctrlPressed = false; }); }, beforeDestroy() { window.removeEventListener('keydown', this.handleKeyDown); window.removeEventListener('keyup', this.handleKeyUp); }, computed: { groupedPositions() { const groups = {}; if (this.positions.some(p => !p._group && Object.keys(p).length > 1)) { groups['Keine Gruppe'] = []; } const groupNames = [...new Set(this.positions.map(p => p._group).filter(Boolean))]; groupNames.forEach(name => groups[name] = []); for (const position of this.positions) { if (Object.keys(position).length === 1 && position._group) continue; const groupName = position._group || 'Keine Gruppe'; if (!groups[groupName]) groups[groupName] = []; groups[groupName].push(position); } return groups; }, positionsToRender() { return this.groupMode ? this.groupedPositions : {'': this.positions}; }, allGroups() { const groupOrder = []; const seenGroups = new Set(); for (const pos of this.positions) { const groupName = pos._group || 'Keine Gruppe'; if (!seenGroups.has(groupName)) { seenGroups.add(groupName); groupOrder.push(groupName); } } return groupOrder; } }, watch: { value: { handler(newValue) { this.positions = newValue; }, deep: true } } })