feat: implement user management system

- Add role field to UserProfile (superadmin/admin/trainer)
- Add role-based permission classes
- Create UserManagementViewSet with CRUD and password change
- Add API types and components for user management
- Create users management page in settings
- Only superadmins can manage users
This commit is contained in:
Andrej Spielmann
2026-03-26 16:42:08 +01:00
parent 7611533718
commit 28222d634d
19 changed files with 1960 additions and 7 deletions
+160
View File
@@ -0,0 +1,160 @@
"use client"
import { useState } from "react"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog"
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select"
import { ICreateUserInput, IUpdateUserInput, IUser } from "@/lib/api"
interface UserFormProps {
open: boolean
onOpenChange: (open: boolean) => void
onSubmit: (data: ICreateUserInput | IUpdateUserInput) => Promise<void>
user?: IUser
mode: 'create' | 'edit'
}
const roles = [
{ value: 'superadmin', label: 'Super Admin' },
{ value: 'admin', label: 'Admin' },
{ value: 'trainer', label: 'Trainer' },
]
export function UserForm({ open, onOpenChange, onSubmit, user, mode }: UserFormProps) {
const [loading, setLoading] = useState(false)
const [formData, setFormData] = useState<ICreateUserInput | IUpdateUserInput>({
username: user?.username || '',
email: user?.email || '',
first_name: user?.first_name || '',
last_name: user?.last_name || '',
password: '',
role: (user?.role as any) || 'trainer',
})
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
setLoading(true)
try {
await onSubmit(formData)
onOpenChange(false)
} finally {
setLoading(false)
}
}
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="sm:max-w-[500px]">
<DialogHeader>
<DialogTitle>
{mode === 'create' ? 'Neuer Benutzer' : 'Benutzer bearbeiten'}
</DialogTitle>
<DialogDescription>
{mode === 'create'
? 'Erstelle einen neuen Benutzer mit Rolle'
: 'Bearbeite die Benutzerdaten'}
</DialogDescription>
</DialogHeader>
<form onSubmit={handleSubmit} className="space-y-4">
<div className="grid grid-cols-2 gap-4">
<div className="space-y-2">
<Label htmlFor="firstName">Vorname</Label>
<Input
id="firstName"
value={formData.first_name}
onChange={(e) => setFormData({ ...formData, first_name: e.target.value })}
required
/>
</div>
<div className="space-y-2">
<Label htmlFor="lastName">Nachname</Label>
<Input
id="lastName"
value={formData.last_name}
onChange={(e) => setFormData({ ...formData, last_name: e.target.value })}
required
/>
</div>
</div>
<div className="space-y-2">
<Label htmlFor="username">Username</Label>
<Input
id="username"
value={formData.username}
onChange={(e) => setFormData({ ...formData, username: e.target.value })}
required
/>
</div>
<div className="space-y-2">
<Label htmlFor="email">E-Mail</Label>
<Input
id="email"
type="email"
value={formData.email}
onChange={(e) => setFormData({ ...formData, email: e.target.value })}
required
/>
</div>
{mode === 'create' && (
<div className="space-y-2">
<Label htmlFor="password">Passwort</Label>
<Input
id="password"
type="password"
value={formData.password}
onChange={(e) => setFormData({ ...formData, password: e.target.value })}
required={mode === 'create'}
/>
</div>
)}
<div className="space-y-2">
<Label htmlFor="role">Rolle</Label>
<Select
value={formData.role}
onValueChange={(value) => setFormData({ ...formData, role: value as any })}
>
<SelectTrigger>
<SelectValue placeholder="Rolle auswählen" />
</SelectTrigger>
<SelectContent>
{roles.map((role) => (
<SelectItem key={role.value} value={role.value}>
{role.label}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div className="flex justify-end gap-2 pt-4">
<Button type="button" variant="outline" onClick={() => onOpenChange(false)}>
Abbrechen
</Button>
<Button type="submit" disabled={loading}>
{loading ? 'Speichern...' : 'Speichern'}
</Button>
</div>
</form>
</DialogContent>
</Dialog>
)
}
+21
View File
@@ -265,6 +265,27 @@ export interface IUser {
last_name?: string
club_id?: number | null
club_name?: string | null
is_active?: boolean
role?: string
date_joined?: string
}
export interface ICreateUserInput {
username: string
email: string
first_name: string
last_name: string
password: string
role: 'superadmin' | 'admin' | 'trainer'
}
export interface IUpdateUserInput {
username?: string
email?: string
first_name?: string
last_name?: string
is_active?: boolean
role?: 'superadmin' | 'admin' | 'trainer'
}
export interface IAuthResponse {