Layout changes
This commit is contained in:
parent
e1eeef8d8a
commit
bd6ee25910
4 changed files with 350 additions and 192 deletions
41
frontend/src/components/basics/ErrorPopup.tsx
Normal file
41
frontend/src/components/basics/ErrorPopup.tsx
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
import {AlertCircle, X} from "lucide-react";
|
||||||
|
|
||||||
|
type ErrorPopupProps = {
|
||||||
|
message: string;
|
||||||
|
onClose: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ErrorPopup - Displays error messages in a modal overlay
|
||||||
|
*/
|
||||||
|
export default function ErrorPopup({message, onClose}: ErrorPopupProps) {
|
||||||
|
return (
|
||||||
|
<div className="fixed inset-0 flex items-center justify-center bg-black/40 z-50">
|
||||||
|
<div className="bg-white rounded-lg p-6 w-96 shadow-lg">
|
||||||
|
<div className="flex items-start justify-between mb-4">
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<AlertCircle className="text-red-600" size={24}/>
|
||||||
|
<h3 className="text-lg font-semibold text-red-600">Fehler</h3>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
onClick={onClose}
|
||||||
|
className="text-gray-400 hover:text-gray-600 transition-colors"
|
||||||
|
>
|
||||||
|
<X size={20}/>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p className="text-gray-700 mb-4">{message}</p>
|
||||||
|
|
||||||
|
<div className="flex justify-end">
|
||||||
|
<button
|
||||||
|
onClick={onClose}
|
||||||
|
className="px-4 py-2 bg-red-600 text-white rounded hover:bg-red-700 transition-colors"
|
||||||
|
>
|
||||||
|
Schließen
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
160
frontend/src/components/users/UserEditForm.tsx
Normal file
160
frontend/src/components/users/UserEditForm.tsx
Normal file
|
|
@ -0,0 +1,160 @@
|
||||||
|
import {useState} from "react";
|
||||||
|
import type {UserDto} from "../../api/dtos/UserDto";
|
||||||
|
import Button from "../basics/Button";
|
||||||
|
import {ButtonType} from "../basics/BasicButtonDefinitions";
|
||||||
|
import PasswordField from "../basics/PasswordField";
|
||||||
|
import ButtonGroupLayout from "../basics/ButtonGroupLayout";
|
||||||
|
import TextLinkButton from "../basics/TextLinkButton";
|
||||||
|
import SelectField from "../basics/SelectField";
|
||||||
|
|
||||||
|
type UserEditFormProps = {
|
||||||
|
user: UserDto;
|
||||||
|
isAdmin: boolean;
|
||||||
|
isSaving: boolean;
|
||||||
|
onSave: (user: UserDto, password?: string) => Promise<void>;
|
||||||
|
onCancel: () => void;
|
||||||
|
onOpenPasswordModal: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* UserEditForm - Form for creating/editing a user
|
||||||
|
*/
|
||||||
|
export default function UserEditForm({
|
||||||
|
user,
|
||||||
|
isAdmin,
|
||||||
|
isSaving,
|
||||||
|
onSave,
|
||||||
|
onCancel,
|
||||||
|
onOpenPasswordModal,
|
||||||
|
}: UserEditFormProps) {
|
||||||
|
const [editedUser, setEditedUser] = useState<UserDto>({...user});
|
||||||
|
const [password, setPassword] = useState("");
|
||||||
|
const [confirmPassword, setConfirmPassword] = useState("");
|
||||||
|
const [passwordError, setPasswordError] = useState("");
|
||||||
|
|
||||||
|
const handleSave = async () => {
|
||||||
|
if (!editedUser.id) {
|
||||||
|
// New user - validate passwords
|
||||||
|
if (confirmPassword !== password) {
|
||||||
|
setPasswordError("Passwörter stimmen nicht überein");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!password || password.length === 0) {
|
||||||
|
setPasswordError("Passwort ist erforderlich");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setPasswordError("");
|
||||||
|
await onSave(editedUser, password);
|
||||||
|
} else {
|
||||||
|
// Existing user
|
||||||
|
await onSave(editedUser);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// @todo API string
|
||||||
|
const roleOptions = [
|
||||||
|
{value: "user", label: "Benutzer"},
|
||||||
|
{value: "admin", label: "Administrator"},
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="px-6 py-2">
|
||||||
|
<h2>{editedUser.id ? "Benutzer bearbeiten" : "Neuen Benutzer anlegen"}</h2>
|
||||||
|
<div className="flex flex-col gap-3 max-w-md">
|
||||||
|
<label>Benutzername</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="Benutzername"
|
||||||
|
value={editedUser.userName}
|
||||||
|
onChange={(e) =>
|
||||||
|
setEditedUser({...editedUser, userName: e.target.value})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<label>Vorname</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="Vorname"
|
||||||
|
value={editedUser.firstName ?? ""}
|
||||||
|
onChange={(e) =>
|
||||||
|
setEditedUser({...editedUser, firstName: e.target.value})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<label>Nachname</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="Nachname"
|
||||||
|
value={editedUser.lastName ?? ""}
|
||||||
|
onChange={(e) =>
|
||||||
|
setEditedUser({...editedUser, lastName: e.target.value})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<label>E-Mail</label>
|
||||||
|
<input
|
||||||
|
type="email"
|
||||||
|
placeholder="E-Mail"
|
||||||
|
value={editedUser.email ?? ""}
|
||||||
|
onChange={(e) =>
|
||||||
|
setEditedUser({...editedUser, email: e.target.value})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{isAdmin && (
|
||||||
|
<>
|
||||||
|
<label>Benutzergruppe</label>
|
||||||
|
<SelectField
|
||||||
|
value={editedUser.role ?? "user"}
|
||||||
|
onChange={(value) =>
|
||||||
|
setEditedUser({...editedUser, role: value})
|
||||||
|
}
|
||||||
|
options={roleOptions}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Show password field only when creating new user */}
|
||||||
|
{!editedUser.id && (
|
||||||
|
<>
|
||||||
|
<label>Passwort</label>
|
||||||
|
<PasswordField
|
||||||
|
onPasswordChanged={setPassword}
|
||||||
|
onKeyDown={() => {
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<label>Passwort bestätigen</label>
|
||||||
|
<PasswordField
|
||||||
|
placeholder="Passwort bestätigen"
|
||||||
|
onPasswordChanged={setConfirmPassword}
|
||||||
|
onKeyDown={() => {
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{passwordError && <p className="error-text">{passwordError}</p>}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Change password link for existing user */}
|
||||||
|
{editedUser.id && (
|
||||||
|
<div className="mt-2 text-right">
|
||||||
|
<TextLinkButton
|
||||||
|
text="Passwort ändern"
|
||||||
|
onClick={onOpenPasswordModal}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<ButtonGroupLayout>
|
||||||
|
<Button
|
||||||
|
text={isSaving ? "Speichern..." : "Speichern"}
|
||||||
|
onClick={handleSave}
|
||||||
|
buttonType={ButtonType.PrimaryButton}
|
||||||
|
disabled={isSaving}
|
||||||
|
/>
|
||||||
|
<Button text="Abbrechen" onClick={onCancel} disabled={isSaving}/>
|
||||||
|
</ButtonGroupLayout>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
48
frontend/src/components/users/UserList.tsx
Normal file
48
frontend/src/components/users/UserList.tsx
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
import clsx from "clsx";
|
||||||
|
import type {UserDto} from "../../api/dtos/UserDto";
|
||||||
|
|
||||||
|
type UserListProps = {
|
||||||
|
users: UserDto[];
|
||||||
|
selectedUser: UserDto | null;
|
||||||
|
onSelectUser: (user: UserDto) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* UserList - Displays a list of users with selection
|
||||||
|
*/
|
||||||
|
export default function UserList({users, selectedUser, onSelectUser}: UserListProps) {
|
||||||
|
const formatUserName = (user: UserDto) => {
|
||||||
|
const parts = [];
|
||||||
|
if (user.lastName) parts.push(user.lastName);
|
||||||
|
if (user.firstName) parts.push(user.firstName);
|
||||||
|
return parts.length > 0 ? parts.join(", ") : "";
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="border-r border-gray-300 h-full">
|
||||||
|
<h2 className="px-4 py-2">Benutzer</h2>
|
||||||
|
<ul>
|
||||||
|
{users.map((user) => (
|
||||||
|
<li
|
||||||
|
key={user.id ?? user.userName}
|
||||||
|
className={clsx(
|
||||||
|
"px-4 py-3 cursor-pointer hover:bg-gray-200 transition-colors border-b border-gray-300",
|
||||||
|
selectedUser?.id === user.id && "bg-blue-100 hover:bg-blue-200"
|
||||||
|
)}
|
||||||
|
onClick={() => onSelectUser(user)}
|
||||||
|
>
|
||||||
|
<div className={clsx(
|
||||||
|
"text-base",
|
||||||
|
selectedUser?.id === user.id && "font-semibold"
|
||||||
|
)}>
|
||||||
|
{formatUserName(user)}
|
||||||
|
</div>
|
||||||
|
<div className="text-sm text-gray-600 mt-1">
|
||||||
|
{user.userName}
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -1,29 +1,28 @@
|
||||||
import {useEffect, useState} from "react";
|
import {useEffect, useState} from "react";
|
||||||
import {createUser, fetchAllUsers, fetchCurrentUser, updateUser} from "../../api/points/UserPoint";
|
import {createUser, fetchAllUsers, fetchCurrentUser, updateUser,} from "../../api/points/UserPoint";
|
||||||
import type {UserDto} from "../../api/dtos/UserDto.ts";
|
import type {UserDto} from "../../api/dtos/UserDto";
|
||||||
import ContentBackground from "../basics/ContentBackground";
|
import ContentBackground from "../basics/ContentBackground";
|
||||||
import ContentBody from "../basics/ContentBody";
|
import ContentBody from "../basics/ContentBody";
|
||||||
import StickyHeader from "../basics/StickyHeader";
|
import StickyHeader from "../basics/StickyHeader";
|
||||||
import Button from "../basics/Button";
|
import Button from "../basics/Button";
|
||||||
import {ButtonType} from "../basics/BasicButtonDefinitions";
|
import {ButtonType} from "../basics/BasicButtonDefinitions";
|
||||||
import clsx from "clsx";
|
import PageContainer from "../basics/PageContainer";
|
||||||
import PageContainer from "../basics/PageContainer.tsx";
|
import {ChangePasswordModal} from "./ChangePasswordModal";
|
||||||
import {ChangePasswordModal} from "./ChangePasswordModal.tsx";
|
import ButtonGroupLayout from "../basics/ButtonGroupLayout";
|
||||||
import PasswordField from "../basics/PasswordField.tsx";
|
|
||||||
import ButtonGroupLayout from "../basics/ButtonGroupLayout.tsx";
|
|
||||||
import {Plus, X} from "lucide-react";
|
import {Plus, X} from "lucide-react";
|
||||||
import ButtonLink from "../basics/ButtonLink.tsx";
|
import ButtonLink from "../basics/ButtonLink";
|
||||||
import {getRecipeListUrl} from "../../routes.ts";
|
import {getRecipeListUrl} from "../../routes";
|
||||||
import TextLinkButton from "../basics/TextLinkButton.tsx";
|
import type {CreateUserResponse} from "../../api/dtos/CreateUserResponse";
|
||||||
import SelectField from "../basics/SelectField.tsx";
|
import type {UserListResponse} from "../../api/dtos/UserListResponse";
|
||||||
import type {CreateUserResponse} from "../../api/dtos/CreateUserResponse.ts";
|
import UserList from "./UserList";
|
||||||
import type {UserListResponse} from "../../api/dtos/UserListResponse.ts";
|
import UserEditForm from "./UserEditForm";
|
||||||
|
import ErrorPopup from "../basics/ErrorPopup";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* UserManagementPage
|
* UserManagementPage
|
||||||
* -------------------
|
* -------------------
|
||||||
* Displays a two-column layout:
|
* Displays a two-column layout:
|
||||||
* - Left: list of all users (wider on desktop)
|
* - Left: list of all users
|
||||||
* - Right: edit form for selected or new user
|
* - Right: edit form for selected or new user
|
||||||
*
|
*
|
||||||
* Allows:
|
* Allows:
|
||||||
|
|
@ -36,43 +35,49 @@ export default function UserManagementPage() {
|
||||||
const [selectedUser, setSelectedUser] = useState<UserDto | null>(null);
|
const [selectedUser, setSelectedUser] = useState<UserDto | null>(null);
|
||||||
const [currentUser, setCurrentUser] = useState<UserDto | null>(null);
|
const [currentUser, setCurrentUser] = useState<UserDto | null>(null);
|
||||||
const [isPasswordModalOpen, setIsPasswordModalOpen] = useState(false);
|
const [isPasswordModalOpen, setIsPasswordModalOpen] = useState(false);
|
||||||
const [password, setPassword] = useState("");
|
|
||||||
const [confirmPassword, setConfirmPassword] = useState("");
|
|
||||||
const [passwordError, setPasswordError] = useState("");
|
|
||||||
const [isSaving, setIsSaving] = useState(false);
|
const [isSaving, setIsSaving] = useState(false);
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
|
||||||
const isAdmin = currentUser?.role === "admin";
|
//@todo API enum
|
||||||
|
const adminRole: string = "admin";
|
||||||
|
const isAdmin = currentUser?.role === adminRole;
|
||||||
|
|
||||||
// Load current user and user list (if admin)
|
// Load current user and user list (if admin)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadData().catch(console.error);
|
loadData();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const loadData = async () => {
|
const loadData = async () => {
|
||||||
const me = await fetchCurrentUser();
|
try {
|
||||||
setCurrentUser(me);
|
const me = await fetchCurrentUser();
|
||||||
|
setCurrentUser(me);
|
||||||
|
|
||||||
if (me.role === "admin") {
|
if (me.role === adminRole) {
|
||||||
const userResponse: UserListResponse = await fetchAllUsers();
|
const userResponse: UserListResponse = await fetchAllUsers();
|
||||||
|
|
||||||
// Sort users alphabetically by last name, then first name
|
// Sort users alphabetically by last name, then first name
|
||||||
const sortedUsers = userResponse.valueList.sort((a, b) => {
|
const sortedUsers = userResponse.valueList.sort((a, b) => {
|
||||||
// Compare last names first
|
const lastNameCompare = (a.lastName || "").localeCompare(
|
||||||
const lastNameCompare = (a.lastName || "").localeCompare(b.lastName || "");
|
b.lastName || ""
|
||||||
if (lastNameCompare !== 0) {
|
);
|
||||||
return lastNameCompare;
|
if (lastNameCompare !== 0) {
|
||||||
}
|
return lastNameCompare;
|
||||||
// If last names are equal, compare first names
|
}
|
||||||
return (a.firstName || "").localeCompare(b.firstName || "");
|
return (a.firstName || "").localeCompare(b.firstName || "");
|
||||||
});
|
});
|
||||||
|
|
||||||
setUsers(sortedUsers);
|
setUsers(sortedUsers);
|
||||||
|
setSelectedUser(me);
|
||||||
// Pre-select current user (admin's own profile)
|
} else {
|
||||||
setSelectedUser(me);
|
setUsers([me]);
|
||||||
} else {
|
setSelectedUser(me);
|
||||||
setUsers([me]);
|
}
|
||||||
setSelectedUser(me);
|
} catch (err) {
|
||||||
|
setError(
|
||||||
|
err instanceof Error
|
||||||
|
? err.message
|
||||||
|
: "Fehler beim Laden der Benutzerdaten"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -82,30 +87,42 @@ export default function UserManagementPage() {
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Handles saving user (create or update) */
|
/** Handles saving user (create or update) */
|
||||||
const handleSave = async () => {
|
const handleSave = async (user: UserDto, password?: string) => {
|
||||||
if (!selectedUser) return;
|
|
||||||
setIsSaving(true);
|
setIsSaving(true);
|
||||||
|
setError(null);
|
||||||
try {
|
try {
|
||||||
if (!selectedUser.id) {
|
if (!user.id) {
|
||||||
// New user - check passwords and save
|
// New user
|
||||||
if (confirmPassword !== password) {
|
const response: CreateUserResponse = await createUser({
|
||||||
setPasswordError("Passwords do not match");
|
userData: user,
|
||||||
return;
|
password: password || "",
|
||||||
}
|
});
|
||||||
const response: CreateUserResponse = await createUser({userData: selectedUser, password});
|
|
||||||
const userDto = response.userData;
|
const userDto = response.userData;
|
||||||
if (userDto) {
|
if (userDto) {
|
||||||
// add user to list and slect
|
const newUsers = [...users, userDto].sort((a, b) => {
|
||||||
setUsers([...users, userDto]);
|
const lastNameCompare = (a.lastName || "").localeCompare(
|
||||||
|
b.lastName || ""
|
||||||
|
);
|
||||||
|
if (lastNameCompare !== 0) {
|
||||||
|
return lastNameCompare;
|
||||||
|
}
|
||||||
|
return (a.firstName || "").localeCompare(b.firstName || "");
|
||||||
|
});
|
||||||
|
setUsers(newUsers);
|
||||||
setSelectedUser(userDto);
|
setSelectedUser(userDto);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// existing user - update
|
// Existing user
|
||||||
const updated = await updateUser(selectedUser);
|
const updated = await updateUser(user);
|
||||||
// update user data in user list and select correct user
|
setUsers(users.map((u) => (u.id === updated.id ? updated : u)));
|
||||||
setUsers(users.map(u => (u.id === updated.id ? updated : u)));
|
|
||||||
setSelectedUser(updated);
|
setSelectedUser(updated);
|
||||||
}
|
}
|
||||||
|
} catch (err) {
|
||||||
|
setError(
|
||||||
|
err instanceof Error
|
||||||
|
? err.message
|
||||||
|
: "Fehler beim Speichern der Benutzerdaten"
|
||||||
|
);
|
||||||
} finally {
|
} finally {
|
||||||
setIsSaving(false);
|
setIsSaving(false);
|
||||||
}
|
}
|
||||||
|
|
@ -113,8 +130,6 @@ export default function UserManagementPage() {
|
||||||
|
|
||||||
/** Opens password modal */
|
/** Opens password modal */
|
||||||
const openPasswordModal = () => {
|
const openPasswordModal = () => {
|
||||||
setPassword("");
|
|
||||||
setConfirmPassword("");
|
|
||||||
setIsPasswordModalOpen(true);
|
setIsPasswordModalOpen(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -129,16 +144,10 @@ export default function UserManagementPage() {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// @todo API enum!
|
|
||||||
const roleOptions = [
|
|
||||||
{value: "user", label: "Benutzer"},
|
|
||||||
{value: "admin", label: "Administrator"}
|
|
||||||
];
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PageContainer>
|
<PageContainer>
|
||||||
<ContentBackground>
|
<ContentBackground>
|
||||||
<StickyHeader className={"flex justify-between items-center"}>
|
<StickyHeader className="flex justify-between items-center">
|
||||||
<h1>Benutzerverwaltung</h1>
|
<h1>Benutzerverwaltung</h1>
|
||||||
<ButtonGroupLayout>
|
<ButtonGroupLayout>
|
||||||
{isAdmin && (
|
{isAdmin && (
|
||||||
|
|
@ -149,141 +158,36 @@ export default function UserManagementPage() {
|
||||||
buttonType={ButtonType.PrimaryButton}
|
buttonType={ButtonType.PrimaryButton}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<ButtonLink
|
<ButtonLink icon={X} to={getRecipeListUrl()}/>
|
||||||
icon={X}
|
|
||||||
to={getRecipeListUrl()}
|
|
||||||
/>
|
|
||||||
</ButtonGroupLayout>
|
</ButtonGroupLayout>
|
||||||
</StickyHeader>
|
</StickyHeader>
|
||||||
|
|
||||||
<ContentBody>
|
<ContentBody className="p-0">
|
||||||
<div className="flex flex-col md:flex-row gap-0">
|
<div className="flex flex-col md:flex-row min-h-[500px]">
|
||||||
{/* User List - Wider on desktop, attached to left */}
|
{/* User List - Left side, no padding */}
|
||||||
<div className="md:w-1/2 lg:w-2/5 border-r border-gray-300 pr-6">
|
<div className="md:w-2/5 lg:w-1/3">
|
||||||
<h2>Benutzer</h2>
|
<UserList
|
||||||
<ul className="space-y-1">
|
users={users}
|
||||||
{users.map((user) => (
|
selectedUser={selectedUser}
|
||||||
<li
|
onSelectUser={handleSelectUser}
|
||||||
key={user.id ?? user.userName}
|
/>
|
||||||
className={clsx(
|
|
||||||
"p-3 rounded cursor-pointer hover:bg-gray-200 transition-colors",
|
|
||||||
selectedUser?.id === user.id && "bg-blue-100 hover:bg-blue-200 font-semibold"
|
|
||||||
)}
|
|
||||||
onClick={() => handleSelectUser(user)}
|
|
||||||
>
|
|
||||||
{user.lastName}, {user.firstName} ({user.userName})
|
|
||||||
</li>
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Edit Form */}
|
{/* Edit Form - Right side, takes remaining space */}
|
||||||
<div className="md:w-1/2 lg:w-3/5 pl-6">
|
<div className="md:w-3/5 lg:w-2/3">
|
||||||
{selectedUser ? (
|
{selectedUser ? (
|
||||||
<div>
|
<UserEditForm
|
||||||
<h2>
|
user={selectedUser}
|
||||||
{selectedUser.id ? "Benutzer bearbeiten" : "Neuen Benutzer anlegen"}
|
isAdmin={isAdmin}
|
||||||
</h2>
|
isSaving={isSaving}
|
||||||
<div className="flex flex-col gap-3 max-w-md">
|
onSave={handleSave}
|
||||||
<label>Benutzername</label>
|
onCancel={loadData}
|
||||||
<input
|
onOpenPasswordModal={openPasswordModal}
|
||||||
type="text"
|
/>
|
||||||
placeholder="Benutzername"
|
|
||||||
value={selectedUser.userName}
|
|
||||||
onChange={(e) =>
|
|
||||||
setSelectedUser({...selectedUser, userName: e.target.value})
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<label>Vorname</label>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
placeholder="Vorname"
|
|
||||||
value={selectedUser.firstName ?? ""}
|
|
||||||
onChange={(e) =>
|
|
||||||
setSelectedUser({...selectedUser, firstName: e.target.value})
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<label>Nachname</label>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
placeholder="Nachname"
|
|
||||||
value={selectedUser.lastName ?? ""}
|
|
||||||
onChange={(e) =>
|
|
||||||
setSelectedUser({...selectedUser, lastName: e.target.value})
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<label>E-Mail</label>
|
|
||||||
<input
|
|
||||||
type="email"
|
|
||||||
placeholder="E-Mail"
|
|
||||||
value={selectedUser.email ?? ""}
|
|
||||||
onChange={(e) =>
|
|
||||||
setSelectedUser({...selectedUser, email: e.target.value})
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{isAdmin && (
|
|
||||||
<>
|
|
||||||
<label>Benutzergruppe</label>
|
|
||||||
<SelectField
|
|
||||||
value={selectedUser.role ?? "user"}
|
|
||||||
onChange={(value) =>
|
|
||||||
setSelectedUser({...selectedUser, role: value})
|
|
||||||
}
|
|
||||||
options={roleOptions}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Show password field only when creating new user */}
|
|
||||||
{!selectedUser.id && (
|
|
||||||
<>
|
|
||||||
<label>Passwort</label>
|
|
||||||
<PasswordField
|
|
||||||
onPasswordChanged={setPassword}
|
|
||||||
onKeyDown={() => {
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<label>Passwort bestätigen</label>
|
|
||||||
<PasswordField
|
|
||||||
placeholder="Passwort bestätigen"
|
|
||||||
onPasswordChanged={setConfirmPassword}
|
|
||||||
onKeyDown={() => {
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
{passwordError && <p className="error-text mb-2">{passwordError}</p>}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Change password link for existing user */}
|
|
||||||
{selectedUser.id && (
|
|
||||||
<div className="mt-2 text-right">
|
|
||||||
<TextLinkButton
|
|
||||||
text="Passwort ändern"
|
|
||||||
onClick={openPasswordModal}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<ButtonGroupLayout>
|
|
||||||
<Button
|
|
||||||
text={isSaving ? "Speichern..." : "Speichern"}
|
|
||||||
onClick={handleSave}
|
|
||||||
buttonType={ButtonType.PrimaryButton}
|
|
||||||
/>
|
|
||||||
<Button
|
|
||||||
text={"Abbrechen"}
|
|
||||||
onClick={loadData}
|
|
||||||
/>
|
|
||||||
</ButtonGroupLayout>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
) : (
|
) : (
|
||||||
<p className="text-gray-600">Bitte einen Benutzer auswählen.</p>
|
<div className="px-6 py-4">
|
||||||
|
<p className="text-gray-600">Bitte einen Benutzer auswählen.</p>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -296,6 +200,11 @@ export default function UserManagementPage() {
|
||||||
onClose={() => setIsPasswordModalOpen(false)}
|
onClose={() => setIsPasswordModalOpen(false)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Error Popup */}
|
||||||
|
{error && (
|
||||||
|
<ErrorPopup message={error} onClose={() => setError(null)}/>
|
||||||
|
)}
|
||||||
</ContentBackground>
|
</ContentBackground>
|
||||||
</PageContainer>
|
</PageContainer>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue