Vue.component("UserEdit", { template: `

Berechtigungen


{{ groupName }}

Mitarbeiter-spezifische Felder

Projekt- & Netzwerkzugriff

Passwort & API Key

{{ Math.round(bulkProgress) }}%
Benutzer {{ bulkCurrentRow + 1 }} von {{ bulkTotalRows }} wird erstellt...

Hinweis: Die Berechtigungen, Zugriffe und allgemeinen Einstellungen (Firma, 2FA, etc.) werden aus dem Hauptformular übernommen. Tragen Sie hier nur die individuellen Benutzerdaten wie Name und E-Mail ein.

`, components: { 'collapsible-selection-list': { props: ['title', 'items', 'lookup', 'collapsible'], data: () => ({ collapsed: true }), computed: { selectedItems() { if (!this.items || !this.lookup) return []; const lookupMap = new Map(this.lookup.map(i => [i.value, i.text])); return this.items.map(id => ({ id, text: lookupMap.get(id) || `ID: ${id}` })); } }, template: `
{{ title }} ({{ items.length }})
` } }, data() { return { user: JSON.parse(JSON.stringify(window.TT_CONFIG.USER_DATA)), // Deep copy initialPermissions: {}, lookups: window.TT_CONFIG.LOOKUPS, permissionsConfig: window.TT_CONFIG.PERMISSIONS_CONFIG, password: { new: '', repeat: '' }, passwordFieldType: 'password', selectedTemplate: null, userToLoad: null, isSaving: false, isToggling: false, collapsedSections: { permissions: false, employeeSpecific: true, projects: true, security: true, }, showBulkCreateModal: false, isBulkSaving: false, bulkUsers: [], bulkProgress: 0, bulkCurrentRow: 0, bulkTotalRows: 0, bulkCreateConfig: { fields: { username: { type: 'input', label: 'Username' }, name: { type: 'input', label: 'Name' }, email: { type: 'input', label: 'Email', inputType: 'email' }, mobile: { type: 'input', label: 'Handy Nr.' } }, validateForm(formData) { if (!formData.username || !formData.name || !formData.email) { window.notify('error', 'Username, Name und Email sind Pflichtfelder.'); return false; } return true; } } } }, computed: { isNewUser() { return !this.user.id; }, templateOptions() { const options = this.lookups.permissionTemplates.map(t => ({ value: t.id, text: t.name })); options.unshift({ value: null, text: 'Vorlage auswählen...' }); return options; }, permissionChangesTooltip() { if (this.isNewUser) return "Ein neuer Benutzer wird erstellt."; const added = []; const removed = []; for (const key in this.user.permissions) { const initial = !!this.initialPermissions[key]; const current = !!this.user.permissions[key]; if (initial !== current) { const permissionLabel = this.findPermissionLabel(key); if (!permissionLabel || /^\d+$/.test(permissionLabel)) continue; if (current) { added.push(`- ${permissionLabel}`); } else { removed.push(`- ${permissionLabel}`); } } } let tooltipText = ''; if (added.length > 0) { tooltipText += 'Hinzugefügt:\n' + added.join('\n'); } if (removed.length > 0) { if (tooltipText.length > 0) { tooltipText += '\n\n'; // Two line breaks } tooltipText += 'Entfernt:\n' + removed.join('\n'); } return tooltipText || 'Keine Berechtigungsänderungen'; } }, methods: { toggleSection(section) { this.collapsedSections[section] = !this.collapsedSections[section]; }, getChevronClass(section) { return this.collapsedSections[section] ? 'fas fa-chevron-down' : 'fas fa-chevron-up'; }, findPermissionLabel(key) { for (const group in this.permissionsConfig) { if (this.permissionsConfig[group][key]) { return this.permissionsConfig[group][key]; } } return key; }, applyTemplate(templateId) { if (!templateId) return; const template = this.lookups.permissionTemplates.find(t => t.id == templateId); template.permissions = typeof template.permissions === 'string' ? JSON.parse(template.permissions) : template.permissions; if (template) { const newPermissions = { ...this.user.permissions }; for (const key in template.permissions) { newPermissions[key] = template.permissions[key]; } this.user.permissions = newPermissions; window.notify('success', `Vorlage "${template.name}" angewendet.`); } setTimeout(() => { this.selectedTemplate = null; this.$refs.templateSelect.selected = null this.$refs.templateSelect.searchQuery = ''; }, 250); }, async loadDataFromUser(userId) { if(!userId) return; try { const response = await axios.get(`/User/getUserDataForTemplate?id=${userId}`); const dataToApply = response.data; // Apply Permissions const newPermissions = { ...this.user.permissions }; for (const key in newPermissions) { newPermissions[key] = dataToApply.permissions[key] === 'true'; } this.user.permissions = newPermissions; // Apply other fields this.$set(this.user, 'preorder_networks', dataToApply.preorder_networks || []); this.$set(this.user, 'constructionconsent_projects', dataToApply.constructionconsent_projects || []); this.$set(this.user, 'vodia_identity_domain', dataToApply.vodia_identity_domain || ''); this.$set(this.user, 'vodia_identity_default', dataToApply.vodia_identity_default || ''); const selectedUser = this.lookups.users.find(u => u.value === userId); window.notify('success', `Daten von "${selectedUser.text}" geladen.`); } catch (error) { window.notify('error', 'Daten konnten nicht geladen werden.'); } finally { this.userToLoad = null; // Reset autocomplete } }, saveUser() { this.isSaving = true; if (this.isNewUser && !this.user.username) { window.notify('error', 'Benutzername ist ein Pflichtfeld.'); this.isSaving = false; return; } if (this.isNewUser && !this.password.new) { window.notify('error', 'Bitte Passwort angeben.'); this.isSaving = false; return; } if (this.password.new && this.password.new !== this.password.repeat) { window.notify('error', 'Die Passwörter stimmen nicht überein!'); this.isSaving = false; return; } const formData = new FormData(); // Append standard fields const fields = ['id', 'username', 'name', 'email', 'mobile', 'address_id', 'employee_number', 'project_api_key', 'vodia_identity_domain', 'vodia_identity_username', 'vodia_identity_default']; fields.forEach(field => formData.append(field, this.user[field] || '')); if (this.isNewUser) { formData.delete('id'); } // Append booleans as 'true'/'false' strings formData.append('active', this.user.active ? 'true' : 'false'); formData.append('twofactorrequired', this.user.twofactorrequired ? 'true' : 'false'); if (this.password.new) { formData.append('password', this.password.new); formData.append('password2', this.password.repeat); } // Append ONLY TRUE permissions to mimic checkbox form behavior for (const key in this.user.permissions) { if (this.user.permissions[key]) { if (key.startsWith('can')) { formData.append(`can[${key.replace('can', '')}]`, 'true'); } else { formData.append(key, 'true'); } } } (this.user.preorder_networks || []).forEach(val => formData.append('preorder_networks[]', val)); (this.user.constructionconsent_projects || []).forEach(val => formData.append('constructionconsent_projects[]', val)); axios.post(window.TT_CONFIG.SAVE_URL, formData) .then(response => { // A successful save redirects. The final URL will be different from the POST URL. const wasRedirected = response.request.responseURL && !response.request.responseURL.endsWith(window.TT_CONFIG.SAVE_URL); if (response.status === 200 && wasRedirected) { window.notify('success', 'Benutzer erfolgreich gespeichert.'); setTimeout(() => window.location.href = '/User', 150); } else if (response.data && response.data.success === false) { // Explicit JSON error from backend const errorMsg = response.data.errors ? Object.values(response.data.errors).join('
') : response.data.message; window.notify('error', errorMsg || 'Fehler beim Speichern des Benutzers.'); } else { // Fallback window.notify('error', 'Eine unerwartete Antwort vom Server wurde empfangen.'); } }) .catch(error => { const errorMsg = error.response?.data?.errors ? Object.values(error.response.data.errors).join('
') : (error.response?.data?.message || 'Fehler beim Speichern des Benutzers.'); window.notify('error', errorMsg); console.error(error); }) .finally(() => { this.isSaving = false; }); }, async generateApiKey() { try { await axios.post(window.TT_CONFIG.API_KEY_URL, new URLSearchParams({id: this.user.id})); window.notify('success', 'Neuer API Key wurde generiert. Seite wird neu geladen...'); setTimeout(() => window.location.reload(), 1500); } catch (error) { window.notify('error', 'API Key konnte nicht generiert werden.'); } }, generatePassword(returnOnly = false) { const length = 14; const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; let retVal = ""; for (let i = 0, n = charset.length; i < length; ++i) { retVal += charset.charAt(Math.floor(Math.random() * n)); } if(returnOnly) { return retVal; } this.password.new = retVal; this.password.repeat = retVal; window.notify('info', 'Neues Passwort generiert.'); }, togglePasswordVisibility() { this.passwordFieldType = this.passwordFieldType === 'password' ? 'text' : 'password'; }, removeItem(arrayName, valueToRemove) { const index = this.user[arrayName].indexOf(valueToRemove); if (index > -1) { this.user[arrayName].splice(index, 1); } }, async handleBulkCreate() { if (this.bulkUsers.length === 0) { window.notify('error', 'Bitte fügen Sie mindestens einen Benutzer hinzu.'); return; } this.isBulkSaving = true; this.bulkTotalRows = this.bulkUsers.length; this.bulkCurrentRow = 0; this.bulkProgress = 0; const createdUsersForCSV = []; const failedUsers = []; for (const [index, user] of this.bulkUsers.entries()) { this.bulkCurrentRow = index; this.bulkProgress = ((index + 1) / this.bulkTotalRows) * 100; const password = this.generatePassword(true); const formData = new FormData(); // Append base data from main form formData.append('active', this.user.active ? 'true' : 'false'); formData.append('twofactorrequired', this.user.twofactorrequired ? 'true' : 'false'); for (const key in this.user.permissions) { if (this.user.permissions[key]) { if (key.startsWith('can')) { formData.append(`can[${key.replace('can', '')}]`, 'true'); } else { formData.append(key, 'true'); } } } const baseFields = ['address_id', 'employee_number', 'project_api_key', 'vodia_identity_domain', 'vodia_identity_username', 'vodia_identity_default']; baseFields.forEach(field => formData.append(field, this.user[field] || '')); (this.user.preorder_networks || []).forEach(val => formData.append('preorder_networks[]', val)); (this.user.constructionconsent_projects || []).forEach(val => formData.append('constructionconsent_projects[]', val)); // Append user-specific data from loop formData.append('username', user.username); formData.append('name', user.name); formData.append('email', user.email); formData.append('mobile', user.mobile || ''); formData.append('password', password); formData.append('password2', password); try { const response = await axios.post(window.TT_CONFIG.SAVE_URL, formData); const wasRedirected = response.request.responseURL && !response.request.responseURL.endsWith(window.TT_CONFIG.SAVE_URL); if (response.status === 200 && wasRedirected) { createdUsersForCSV.push({ ...user, password: password }); } else { const errorMsg = response.data.errors ? Object.values(response.data.errors).join(', ') : (response.data.message || 'Unbekannter Serverfehler'); failedUsers.push({ ...user, error: errorMsg }); } } catch (error) { const errorMsg = error.response?.data?.errors ? Object.values(error.response.data.errors).join(', ') : (error.response?.data?.message || error.message); failedUsers.push({ ...user, error: errorMsg }); } } this.isBulkSaving = false; if (createdUsersForCSV.length > 0) { this.downloadCSV(createdUsersForCSV); window.notify('success', `${createdUsersForCSV.length} von ${this.bulkTotalRows} Benutzern erfolgreich erstellt.`); } if (failedUsers.length > 0) { let errorMessage = `${failedUsers.length} Benutzer konnten nicht erstellt werden:\n` + failedUsers.map(u => `- ${u.username}: ${u.error}`).join('\n'); alert(errorMessage); } if (createdUsersForCSV.length > 0 && failedUsers.length === 0) { this.showBulkCreateModal = false; setTimeout(() => window.location.href = '/User', 500); } }, downloadCSV(data) { if (!data || data.length === 0) return; const headers = ['Username', 'Password', 'Name', 'Email']; const rows = data.map(user => [ `"${user.username}"`, `"${user.password}"`, `"${user.name}"`, `"${user.email}"` ]); let csvContent = headers.join(";") + "\n" + rows.map(e => e.join(";")).join("\n"); const blob = new Blob([`\uFEFF${csvContent}`], { type: 'text/csv;charset=utf-8;' }); const link = document.createElement("a"); const url = URL.createObjectURL(blob); link.setAttribute("href", url); link.setAttribute("download", "new_users_credentials.csv"); document.body.appendChild(link); link.click(); document.body.removeChild(link); } }, created() { const permissions = {}; Object.values(this.permissionsConfig).forEach(group => { Object.keys(group).forEach(key => { permissions[key] = this.user.permissions[key] === 'true' || this.user.permissions[key] === true; }); }); this.user.permissions = permissions; this.initialPermissions = JSON.parse(JSON.stringify(permissions)); // Deep copy for change tracking this.user.active = this.user.active == 1 || this.isNewUser; this.user.twofactorrequired = this.user.twofactorrequired == 1 || this.isNewUser; // Set default collapse state for new users if (this.isNewUser) { this.collapsedSections = { permissions: false, employeeSpecific: false, projects: false, security: false, }; } } });