Add components for SelectField and TextLinkButton
This commit is contained in:
parent
e539f9201b
commit
c8b8435b69
3 changed files with 101 additions and 51 deletions
25
frontend/src/components/basics/SelectField.tsx
Normal file
25
frontend/src/components/basics/SelectField.tsx
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
type SelectFieldProps = {
|
||||||
|
value: string;
|
||||||
|
onChange: (value: string) => void;
|
||||||
|
options: { value: string; label: string }[];
|
||||||
|
className?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SelectField - A dropdown styled consistently with input fields
|
||||||
|
*/
|
||||||
|
export default function SelectField({value, onChange, options, className = ''}: SelectFieldProps) {
|
||||||
|
return (
|
||||||
|
<select
|
||||||
|
value={value}
|
||||||
|
onChange={(e) => onChange(e.target.value)}
|
||||||
|
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) => (
|
||||||
|
<option key={option.value} value={option.value}>
|
||||||
|
{option.label}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
);
|
||||||
|
}
|
||||||
20
frontend/src/components/basics/TextLinkButton.tsx
Normal file
20
frontend/src/components/basics/TextLinkButton.tsx
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
type TextLinkButtonProps = {
|
||||||
|
text: string;
|
||||||
|
onClick: () => void;
|
||||||
|
className?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TextLinkButton - A button styled as a hyperlink
|
||||||
|
*/
|
||||||
|
export default function TextLinkButton({text, onClick, className = ''}: TextLinkButtonProps) {
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={onClick}
|
||||||
|
className={`text-blue-600 hover:text-blue-800 hover:underline transition-colors bg-transparent border-none p-0 cursor-pointer ${className}`}
|
||||||
|
>
|
||||||
|
{text}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import {useEffect, useState} from "react";
|
import {useEffect, useState} from "react";
|
||||||
import {createUser, fetchCurrentUser, updateUser} from "../../api/points/UserPoint";
|
import {createUser, fetchCurrentUser, updateUser} from "../../api/points/UserPoint";
|
||||||
import type {UserDto} from "../../api/dtos/UserDto.ts"; // @todo add model and mapper!
|
import type {UserDto} from "../../api/dtos/UserDto.ts";
|
||||||
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";
|
||||||
|
|
@ -14,12 +14,14 @@ 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.tsx";
|
||||||
import {getRecipeListUrl} from "../../routes.ts";
|
import {getRecipeListUrl} from "../../routes.ts";
|
||||||
|
import TextLinkButton from "../basics/TextLinkButton.tsx";
|
||||||
|
import SelectField from "../basics/SelectField.tsx";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* UserManagementPage
|
* UserManagementPage
|
||||||
* -------------------
|
* -------------------
|
||||||
* Displays a two-column layout:
|
* Displays a two-column layout:
|
||||||
* - Left: list of all users
|
* - Left: list of all users (wider on desktop)
|
||||||
* - Right: edit form for selected or new user
|
* - Right: edit form for selected or new user
|
||||||
*
|
*
|
||||||
* Allows:
|
* Allows:
|
||||||
|
|
@ -55,6 +57,7 @@ export default function UserManagementPage() {
|
||||||
setSelectedUser(me);
|
setSelectedUser(me);
|
||||||
//}
|
//}
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Handles selecting a user from the list */
|
/** Handles selecting a user from the list */
|
||||||
const handleSelectUser = (user: UserDto) => {
|
const handleSelectUser = (user: UserDto) => {
|
||||||
setSelectedUser({...user});
|
setSelectedUser({...user});
|
||||||
|
|
@ -98,6 +101,11 @@ export default function UserManagementPage() {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const roleOptions = [
|
||||||
|
{value: "user", label: "Benutzer"},
|
||||||
|
{value: "admin", label: "Administrator"}
|
||||||
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PageContainer>
|
<PageContainer>
|
||||||
<ContentBackground>
|
<ContentBackground>
|
||||||
|
|
@ -120,17 +128,17 @@ export default function UserManagementPage() {
|
||||||
</StickyHeader>
|
</StickyHeader>
|
||||||
|
|
||||||
<ContentBody>
|
<ContentBody>
|
||||||
<div className="flex flex-col md:flex-row gap-6">
|
<div className="flex flex-col md:flex-row gap-0">
|
||||||
{/* User List @todo selector component!*/}
|
{/* User List - Wider on desktop, attached to left */}
|
||||||
<div className="md:w-1/3 border-r border-gray-300">
|
<div className="md:w-1/2 lg:w-2/5 border-r border-gray-300 pr-6">
|
||||||
<h2>Benutzer</h2>
|
<h2>Benutzer</h2>
|
||||||
<ul>
|
<ul className="space-y-1">
|
||||||
{users.map((user) => (
|
{users.map((user) => (
|
||||||
<li
|
<li
|
||||||
key={user.id ?? user.userName}
|
key={user.id ?? user.userName}
|
||||||
className={clsx(
|
className={clsx(
|
||||||
"p-2 hover:bg-gray-200",
|
"p-3 rounded cursor-pointer hover:bg-gray-200 transition-colors",
|
||||||
selectedUser?.id === user.id && "bg-gray-300 font-semibold"
|
selectedUser?.id === user.id && "bg-blue-100 hover:bg-blue-200 font-semibold"
|
||||||
)}
|
)}
|
||||||
onClick={() => handleSelectUser(user)}
|
onClick={() => handleSelectUser(user)}
|
||||||
>
|
>
|
||||||
|
|
@ -141,20 +149,13 @@ export default function UserManagementPage() {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Edit Form */}
|
{/* Edit Form */}
|
||||||
<div className="md:w-2/3">
|
<div className="md:w-1/2 lg:w-3/5 pl-6">
|
||||||
{selectedUser ? (
|
{selectedUser ? (
|
||||||
<div>
|
<div>
|
||||||
<h2>
|
<h2>
|
||||||
{selectedUser.id ? "Benutzer bearbeiten" : "Neuen Benutzer anlegen"}
|
{selectedUser.id ? "Benutzer bearbeiten" : "Neuen Benutzer anlegen"}
|
||||||
</h2>
|
</h2>
|
||||||
<form
|
<div className="flex flex-col gap-3 max-w-md">
|
||||||
onSubmit={(e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
handleSave();
|
|
||||||
}}
|
|
||||||
className="flex flex-col gap-3 max-w-md"
|
|
||||||
>
|
|
||||||
{/* @todo create component for laben and input field combination */}
|
|
||||||
<label>Benutzername</label>
|
<label>Benutzername</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
|
|
@ -164,6 +165,7 @@ export default function UserManagementPage() {
|
||||||
setSelectedUser({...selectedUser, userName: e.target.value})
|
setSelectedUser({...selectedUser, userName: e.target.value})
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<label>Vorname</label>
|
<label>Vorname</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
|
|
@ -173,6 +175,7 @@ export default function UserManagementPage() {
|
||||||
setSelectedUser({...selectedUser, firstName: e.target.value})
|
setSelectedUser({...selectedUser, firstName: e.target.value})
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<label>Nachname</label>
|
<label>Nachname</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
|
|
@ -182,6 +185,7 @@ export default function UserManagementPage() {
|
||||||
setSelectedUser({...selectedUser, lastName: e.target.value})
|
setSelectedUser({...selectedUser, lastName: e.target.value})
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<label>E-Mail</label>
|
<label>E-Mail</label>
|
||||||
<input
|
<input
|
||||||
type="email"
|
type="email"
|
||||||
|
|
@ -192,46 +196,47 @@ export default function UserManagementPage() {
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{isAdmin && (<label>Benutzergruppe</label>)}
|
|
||||||
{isAdmin && (
|
{isAdmin && (
|
||||||
// @todo style
|
<>
|
||||||
<select
|
<label>Benutzergruppe</label>
|
||||||
className="input-field"
|
<SelectField
|
||||||
value={selectedUser.role ?? "user"}
|
value={selectedUser.role ?? "user"}
|
||||||
onChange={(e) =>
|
onChange={(value) =>
|
||||||
setSelectedUser({...selectedUser, role: e.target.value})
|
setSelectedUser({...selectedUser, role: value})
|
||||||
}
|
}
|
||||||
>
|
options={roleOptions}
|
||||||
<option value="user">Benutzer</option>
|
/>
|
||||||
<option value="admin">Administrator</option>
|
</>
|
||||||
</select>
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Show password field only when creating new user */}
|
{/* Show password field only when creating new user */}
|
||||||
{!selectedUser.id && (<label>Passwort</label>)}
|
|
||||||
{!selectedUser.id && (
|
{!selectedUser.id && (
|
||||||
|
<>
|
||||||
|
<label>Passwort</label>
|
||||||
<PasswordField
|
<PasswordField
|
||||||
onPasswordChanged={setPassword}
|
onPasswordChanged={setPassword}
|
||||||
onKeyDown={() => {
|
onKeyDown={() => {
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
|
||||||
{!selectedUser.id && (<label>Passwort bestätigen</label>)}
|
<label>Passwort bestätigen</label>
|
||||||
{!selectedUser.id && (
|
|
||||||
<PasswordField
|
<PasswordField
|
||||||
placeholder="Passwort bestätigen"
|
placeholder="Passwort bestätigen"
|
||||||
onPasswordChanged={setConfirmPassword}
|
onPasswordChanged={setConfirmPassword}
|
||||||
onKeyDown={() => {
|
onKeyDown={() => {
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Change password button for existing user */}
|
{/* Change password link for existing user */}
|
||||||
{selectedUser.id && (
|
{selectedUser.id && (
|
||||||
<Button
|
<div className="mt-2 text-right">
|
||||||
|
<TextLinkButton
|
||||||
text="Passwort ändern"
|
text="Passwort ändern"
|
||||||
onClick={openPasswordModal}
|
onClick={openPasswordModal}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<ButtonGroupLayout>
|
<ButtonGroupLayout>
|
||||||
|
|
@ -245,7 +250,7 @@ export default function UserManagementPage() {
|
||||||
onClick={loadData}
|
onClick={loadData}
|
||||||
/>
|
/>
|
||||||
</ButtonGroupLayout>
|
</ButtonGroupLayout>
|
||||||
</form>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<p className="text-gray-600">Bitte einen Benutzer auswählen.</p>
|
<p className="text-gray-600">Bitte einen Benutzer auswählen.</p>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue