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:
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user