Enum for user role - somehow user page is currently unresponsive. Probably because of previous layout changes

This commit is contained in:
araemer 2025-11-30 08:42:07 +01:00
parent bd6ee25910
commit e6fd6d7d6f
5 changed files with 94 additions and 21 deletions

View file

@ -1,9 +1,10 @@
import { AbstractDto } from "./AbstractDto.ts"; import {AbstractDto} from "./AbstractDto";
import {UserRole} from "../enums/UserRole";
export class UserDto extends AbstractDto { export interface UserDto extends AbstractDto {
firstName?: string; firstName?: string;
lastName?: string; lastName?: string;
userName!: string; userName: string;
email!: string; email: string;
role?: string; role?: UserRole;
} }

View file

@ -0,0 +1,52 @@
/**
* User roles - Frontend version
* Content should match backend enum exactly
* However, we cannot use a basic enum here as we're using erasableSyntaxOnly.
*/
export const UserRole = {
USER: "user",
ADMIN: "admin",
} as const;
// Create a type from the values
export type UserRole = typeof UserRole[keyof typeof UserRole];
/**
* Helper functions for UserRole in frontend
*/
export const UserRoleHelper = {
/**
* Get display name for role (German)
*/
getDisplayName(role: UserRole): string {
const displayNames: Record<UserRole, string> = {
[UserRole.USER]: "Benutzer",
[UserRole.ADMIN]: "Administrator",
};
return displayNames[role];
},
/**
* Get all roles with display names for dropdowns
*/
getRoleOptions(): Array<{ value: UserRole; label: string }> {
return Object.values(UserRole).map((role) => ({
value: role,
label: this.getDisplayName(role),
}));
},
/**
* Check if a string is a valid role
*/
isValidRole(value: string): value is UserRole {
return Object.values(UserRole).includes(value as UserRole);
},
/**
* Get all role values
*/
getAllRoles(): UserRole[] {
return Object.values(UserRole);
},
};

View file

@ -1,18 +1,40 @@
type SelectFieldProps = { type SelectFieldProps<T extends string> = {
value: string; value: T;
onChange: (value: string) => void; onChange: (value: T) => void;
options: { value: string; label: string }[]; options: { value: T; label: string }[];
className?: string; className?: string;
}; };
/** /**
* SelectField - A dropdown styled consistently with input fields * SelectField - A dropdown styled consistently with input fields
* Generic component that works with any string-based type (including string literals and enums)
*
* @example
* // With UserRole type
* <SelectField<UserRole>
* value={user.role}
* onChange={(role) => setUser({...user, role})}
* options={UserRoleHelper.getRoleOptions()}
* />
*
* @example
* // With plain strings
* <SelectField<string>
* value={status}
* onChange={setStatus}
* options={[{value: "active", label: "Active"}, {value: "inactive", label: "Inactive"}]}
* />
*/ */
export default function SelectField({value, onChange, options, className = ''}: SelectFieldProps) { export default function SelectField<T extends string>({
value,
onChange,
options,
className = ''
}: SelectFieldProps<T>) {
return ( return (
<select <select
value={value} value={value}
onChange={(e) => onChange(e.target.value)} onChange={(e) => onChange(e.target.value as T)}
className={`p-2 w-full border rounded-md bg-white border-gray-600 hover:border-blue-800 transition-colors text-gray-600 focus:outline-none focus:border-blue-900 cursor-pointer ${className}`} className={`p-2 w-full border rounded-md bg-white border-gray-600 hover:border-blue-800 transition-colors text-gray-600 focus:outline-none focus:border-blue-900 cursor-pointer ${className}`}
> >
{options.map((option) => ( {options.map((option) => (

View file

@ -6,6 +6,7 @@ import PasswordField from "../basics/PasswordField";
import ButtonGroupLayout from "../basics/ButtonGroupLayout"; import ButtonGroupLayout from "../basics/ButtonGroupLayout";
import TextLinkButton from "../basics/TextLinkButton"; import TextLinkButton from "../basics/TextLinkButton";
import SelectField from "../basics/SelectField"; import SelectField from "../basics/SelectField";
import {UserRole, UserRoleHelper} from "../../api/enums/UserRole.ts";
type UserEditFormProps = { type UserEditFormProps = {
user: UserDto; user: UserDto;
@ -52,10 +53,7 @@ export default function UserEditForm({
}; };
// @todo API string // @todo API string
const roleOptions = [ const roleOptions = UserRoleHelper.getRoleOptions();
{value: "user", label: "Benutzer"},
{value: "admin", label: "Administrator"},
];
return ( return (
<div className="px-6 py-2"> <div className="px-6 py-2">
@ -104,8 +102,8 @@ export default function UserEditForm({
{isAdmin && ( {isAdmin && (
<> <>
<label>Benutzergruppe</label> <label>Benutzergruppe</label>
<SelectField <SelectField<UserRole>
value={editedUser.role ?? "user"} value={editedUser.role ?? UserRole.USER}
onChange={(value) => onChange={(value) =>
setEditedUser({...editedUser, role: value}) setEditedUser({...editedUser, role: value})
} }

View file

@ -17,6 +17,7 @@ import type {UserListResponse} from "../../api/dtos/UserListResponse";
import UserList from "./UserList"; import UserList from "./UserList";
import UserEditForm from "./UserEditForm"; import UserEditForm from "./UserEditForm";
import ErrorPopup from "../basics/ErrorPopup"; import ErrorPopup from "../basics/ErrorPopup";
import {UserRole} from "../../api/enums/UserRole.ts";
/** /**
* UserManagementPage * UserManagementPage
@ -39,8 +40,7 @@ export default function UserManagementPage() {
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
//@todo API enum //@todo API enum
const adminRole: string = "admin"; const isAdmin = currentUser?.role === UserRole.ADMIN;
const isAdmin = currentUser?.role === adminRole;
// Load current user and user list (if admin) // Load current user and user list (if admin)
useEffect(() => { useEffect(() => {
@ -52,7 +52,7 @@ export default function UserManagementPage() {
const me = await fetchCurrentUser(); const me = await fetchCurrentUser();
setCurrentUser(me); setCurrentUser(me);
if (me.role === adminRole) { if (me.role === UserRole.ADMIN) {
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
@ -140,7 +140,7 @@ export default function UserManagementPage() {
firstName: "", firstName: "",
lastName: "", lastName: "",
email: "", email: "",
role: "user", role: UserRole.USER,
}); });
}; };