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: `
{{ 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: `
`,
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
}
}
})