Vue.component("UserEdit", {
template: `
{{ 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,
};
}
}
});