diff --git a/application/UserEdit/UserEditController.php b/application/UserEdit/UserEditController.php
new file mode 100644
index 000000000..c49d9f1f9
--- /dev/null
+++ b/application/UserEdit/UserEditController.php
@@ -0,0 +1,170 @@
+needlogin = true;
+ $this->user = new User();
+ $this->user->loadMe();
+ $this->layout()->set('me', $this->user);
+
+ if (!$this->user->isAdmin()) {
+ $this->redirect("Dashboard");
+ }
+
+ // if post then set postData
+ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
+ $this->postData = json_decode(file_get_contents('php://input'), true);
+ }
+ }
+
+ protected function indexAction() {
+ $id = $this->request->id;
+ if (!is_numeric($id) || $id <= 0) throw new Exception("User ID is required.", 400);
+
+ $user = new User($id);
+ if (!$user->id) throw new Exception("User not found.", 404);
+
+ $preorderNetworks = $user->getFlag("preorder_networks")->value();
+ $consentProjects = $user->getFlag("constructionConsent_projects")->value();
+
+ $userData = $user->toArray();
+ $userData['permissions'] = (array)$user->permissions->data;
+ $userData['preorder_networks'] = $preorderNetworks ? json_decode($preorderNetworks, true) : [];
+ $userData['constructionconsent_projects'] = $consentProjects ? json_decode($consentProjects, true) : [];
+ $userData['employee_number'] = $user->getFlag("employee_number")->value();
+ $userData['project_api_key'] = $user->getFlag("project_api_key")->value();
+ $userData['vodia_identity_domain'] = $user->getFlag("vodia_identity_domain")->value();
+ $userData['vodia_identity_username'] = $user->getFlag("vodia_identity_username")->value();
+ $userData['vodia_identity_default'] = $user->getFlag("vodia_identity_default")->value();
+
+
+ $JS_VARIABLES = [
+ "USER_DATA" => $userData,
+ "LOOKUPS" => [
+ "addresses" => array_map(fn($addr) => ['value' => $addr->id, 'text' => ($addr->company) ? $addr->company : $addr->getFullName()], AddressModel::getAll()),
+ "networks" => array_map(fn($net) => ['value' => $net->id, 'text' => $net->name], NetworkModel::getAll()),
+ "consentProjects" => array_map(fn($proj) => ['value' => $proj->id, 'text' => $proj->name], ConstructionConsentProject::getAll()),
+ "permissionTemplates" => UserPermissionTemplateModel::getAll([], null, 0, ['key' => 'name', 'order' => 'asc']),
+ "users" => array_map(fn($u) => ['value' => $u->id, 'text' => $u->name], UserModel::search(['active' => 1])),
+ ],
+ "PERMISSIONS_CONFIG" => $this->getPermissionsConfig(),
+ "SAVE_URL" => self::getUrl("User", "save"),
+ "API_KEY_URL" => self::getUrl("User", "generateApikey"),
+ ];
+
+ Helper::renderVue($this, "UserEdit", "Benutzer bearbeiten: " . $user->name, $JS_VARIABLES);
+ }
+
+ protected function getUserDataForTemplateAction() {
+ $id = $this->request->id;
+ if (!$id) self::sendError("User ID is required.");
+ $user = new User($id);
+ if (!$user->id) self::sendError("User not found.");
+
+ $preorderNetworks = $user->getFlag("preorder_networks")->value();
+ $consentProjects = $user->getFlag("constructionConsent_projects")->value();
+
+ self::returnJson([
+ 'permissions' => (array)$user->permissions->data,
+ 'preorder_networks' => $preorderNetworks ? json_decode($preorderNetworks, true) : [],
+ 'constructionconsent_projects' => $consentProjects ? json_decode($consentProjects, true) : [],
+ 'vodia_identity_domain' => $user->getFlag("vodia_identity_domain")->value(),
+ 'vodia_identity_default' => $user->getFlag("vodia_identity_default")->value(),
+ ]);
+ }
+
+ protected function managePermissionTemplatesAction() {
+ Helper::renderVue($this, "UserPermissionTemplate", "Berechtigungsvorlagen", ["PERMISSIONS_CONFIG" => $this->getPermissionsConfig()]);
+ }
+
+ protected function getPermissionTemplatesAction() {
+ self::returnJson(array_map(
+ function ($perm) {
+ $perm = (array)$perm;
+ $perm['permissions'] = json_decode($perm['permissions'], true) ?: [];
+ return $perm;
+ }, UserPermissionTemplateModel::getAll([], null, 0, ['key' => 'name', 'order' => 'asc'])
+ ));
+ }
+
+ protected function savePermissionTemplateAction() {
+ if (empty($this->postData['name'])) self::sendError("Template name is required.");
+
+ $data = [
+ 'name' => $this->postData['name'],
+ 'permissions' => json_encode($this->postData['permissions'] ?? []),
+ ];
+
+ if (empty($this->postData['id'])) {
+ $data += ['createBy' => $this->user->id, 'create' => time()];
+ $id = UserPermissionTemplateModel::create($data);
+ self::returnJson(['success' => true, 'message' => 'Vorlage erstellt.', 'id' => $id]);
+ }
+
+ $template = UserPermissionTemplateModel::get($this->postData['id']);
+ $data += [
+ 'id' => $this->postData['id'],
+ 'create' => $template->create,
+ 'createBy' => $template->createBy,
+ ];
+
+ UserPermissionTemplateModel::update($data);
+ self::returnJson(['success' => true, 'message' => 'Vorlage gespeichert.']);
+ }
+
+ protected function deletePermissionTemplateAction() {
+ $post = json_decode(file_get_contents('php://input'), true);
+ if (empty($post['id'])) self::sendError("Template ID is required.");
+ UserPermissionTemplateModel::delete($post['id']);
+ self::returnJson(['success' => true, 'message' => 'Vorlage gelöscht.']);
+ }
+
+ private function getPermissionsConfig(): array {
+ return [
+ 'Rollen' => [
+ 'admin' => 'Administrator',
+ 'employee' => TT_SYSOWNER_NAME_HTML . ' Mitarbeiter',
+ 'technician' => 'Techniker',
+ ],
+ 'Preorder' => [
+ 'preorderfront' => 'Frontdesk (Semi-Readonly)',
+ 'preorderlogistics' => 'Logistikpartner',
+ 'preorderaddressreporting' => 'Address Reporting API User',
+ 'preorderreadonly' => 'Readonly',
+ 'canPreorder' => 'Modul: Vorbestellung',
+ 'canPreorderpricing' => 'Modul: Bepreisung',
+ 'canPreorderpricingReadonly' => 'Modul: Bepreisung (Readonly)',
+ 'canPreorderbilling' => 'Modul: Verrechnung',
+ 'canPreorderbillingReadonly' => 'Modul: Verrechnung (Readonly)',
+ ],
+ 'Module' => [
+ 'canBuilding' => 'Objekte & Anschlüsse',
+ 'canPipework' => 'Tiefbau',
+ 'canLinework' => 'Leitungsbau',
+ 'canPatching' => 'Patching',
+ 'canFilestore' => 'Filestore (Netzbau)',
+ 'canCpeprovisioning' => 'CPE Provisioning',
+ 'canCpeshipping' => 'CPE Versand',
+ 'canVoipnumbering' => 'VOIP Nummernverwaltung',
+ 'canOrder' => 'Bestellung',
+ 'canBilling' => 'Verrechnung',
+ ],
+ 'Lager' => [
+ 'canWarehouseAdmin' => 'Lager-Admin',
+ 'canWarehouseUser' => 'Lager-User',
+ 'canWarehouseEShop' => 'Energie Steiermark Shop',
+ ],
+ 'Zusatzberechtigungen' => [
+ 'canFibu' => 'Buchhaltung',
+ 'canStatistics' => 'Statistiken',
+ 'canADBExtended' => 'Address-DB erweitert',
+ 'canAssetAdmin' => 'Anlagen-Admin',
+ 'canRMLAdmin' => 'RML-Workorder-Admin',
+ 'canRMLCompany' => 'RML-Workorder-Firma',
+ ]
+ ];
+ }
+}
\ No newline at end of file
diff --git a/application/UserPermissionTemplate/UserPermissionTemplateModel.php b/application/UserPermissionTemplate/UserPermissionTemplateModel.php
new file mode 100644
index 000000000..66c71b318
--- /dev/null
+++ b/application/UserPermissionTemplate/UserPermissionTemplateModel.php
@@ -0,0 +1,20 @@
+getEnvironment() == "thetool") {
+ if (!$this->hasTable('UserPermissionTemplate')) {
+ $table = $this->table('UserPermissionTemplate', ['id' => false, 'primary_key' => ['id']]);
+ $table->addColumn('id', 'integer', ['identity' => true, 'signed' => false])
+ ->addColumn('name', 'string', ['limit' => 255, 'null' => false])
+ ->addColumn('permissions', 'text', ['null' => false])
+ ->addColumn('create', 'integer', ['null' => false])
+ ->addColumn('createBy', 'integer', ['null' => false])
+ ->addIndex(['name'], ['unique' => true])
+ ->create();
+ }
+ }
+ }
+
+ public function down(): void
+ {
+ if ($this->getEnvironment() == "thetool") {
+ if ($this->hasTable('UserPermissionTemplate')) {
+ $this->table('UserPermissionTemplate')->drop()->save();
+ }
+ }
+ }
+}
diff --git a/public/js/pages/UserEdit/UserEdit.css b/public/js/pages/UserEdit/UserEdit.css
new file mode 100644
index 000000000..c19304306
--- /dev/null
+++ b/public/js/pages/UserEdit/UserEdit.css
@@ -0,0 +1,166 @@
+.user-edit-container {
+ padding-bottom: 60px; /* Space for the bottom save button */
+}
+
+.user-edit-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 1rem;
+ margin-top: 1.5rem;
+ padding: 0.5rem;
+}
+
+.user-edit-header.sticky-header {
+ position: sticky;
+ top: 60px; /* Adjust based on your main header height */
+ background-color: #f8f9fa; /* Match card background */
+ z-index: 10;
+ padding: 0.75rem 1rem;
+ border-bottom: 1px solid #dee2e6;
+ margin: -1.25rem -1.25rem 1rem -1.25rem; /* Make it span the card width */
+}
+
+.user-edit-header.collapsible {
+ cursor: pointer;
+ border-bottom: 1px solid #dee2e6;
+ padding-bottom: 0.75rem;
+ transition: background-color 0.2s;
+}
+.user-edit-header.collapsible:hover {
+ background-color: #f1f3f5;
+}
+
+.user-edit-header h3 {
+ margin: 0;
+}
+
+.user-form-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
+ gap: 0.5rem 1.5rem;
+}
+
+.user-form-grid-half {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
+ gap: 1rem;
+}
+
+.user-form-grid-toggles {
+ display: flex;
+ gap: 2rem;
+ margin-top: 1rem;
+}
+
+.user-form-grid-toggles .form-group {
+ display: flex;
+ align-items: center;
+ gap: 0.75rem;
+ margin-bottom: 0;
+}
+
+.permission-template-section {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
+ gap: 1rem;
+ align-items: end;
+}
+
+.permissions-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
+ gap: 1.5rem 2rem;
+}
+
+.permission-group {
+ border: 1px solid #dee2e6;
+ border-radius: 0.3rem;
+ padding: 1rem;
+ background-color: #fff;
+}
+
+.permission-group h5 {
+ font-size: 1.1rem;
+ margin-bottom: 1rem;
+ color: #0056b3;
+}
+
+.form-check-label {
+ user-select: none;
+}
+
+.password-generation-grid {
+ display: grid;
+ grid-template-columns: 1fr auto auto;
+ gap: 0.5rem;
+ align-items: end;
+}
+
+.selected-items-viewer {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
+ gap: 1.5rem;
+ margin-top: 1.5rem;
+}
+
+.selection-list-container {
+ border: 1px solid #e9ecef;
+ padding: 0.75rem;
+ border-radius: 0.25rem;
+}
+
+.selection-list {
+ list-style-type: none;
+ padding-left: 0;
+ margin-top: 0.5rem;
+ max-height: 200px;
+ overflow-y: auto;
+}
+.selection-list li {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 0.25rem 0.5rem;
+ border-radius: 0.2rem;
+}
+.selection-list li:hover {
+ background-color: #f8f9fa;
+}
+.selection-list li .fa-times-circle {
+ color: #dc3545;
+ cursor: pointer;
+ opacity: 0.7;
+}
+.selection-list li .fa-times-circle:hover {
+ opacity: 1;
+}
+
+.user-edit-footer {
+ position: fixed;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ padding: 0.75rem;
+ background-color: #ffffff;
+ border-top: 1px solid #dee2e6;
+ display: flex;
+ justify-content: flex-end;
+ z-index: 1000;
+}
+
+/* Slide-fade transition */
+.slide-fade-enter-active, .slide-fade-leave-active {
+ transition: all .3s ease;
+}
+.slide-fade-enter, .slide-fade-leave-to {
+ transform: translateY(-10px);
+ opacity: 0;
+}
+.slide-fade-fast-enter-active, .slide-fade-fast-leave-active {
+ transition: all .2s ease;
+}
+.slide-fade-fast-enter, .slide-fade-fast-leave-to {
+ transform: translateY(-5px);
+ opacity: 0;
+}
\ No newline at end of file
diff --git a/public/js/pages/UserEdit/UserEdit.js b/public/js/pages/UserEdit/UserEdit.js
new file mode 100644
index 000000000..302ac1f41
--- /dev/null
+++ b/public/js/pages/UserEdit/UserEdit.js
@@ -0,0 +1,350 @@
+Vue.component("UserEdit", {
+ template: `
+ Allgemeine Informationen
+ Berechtigungen
+
+
+
+ Mitarbeiter-spezifische Felder
+
+ Projekt- & Netzwerkzugriff
+
+ Passwort & API Key
+
+