From 0d3550ec869b7ec3a390adae09aafbd092f4118d Mon Sep 17 00:00:00 2001 From: Leopoldadmin Date: Fri, 3 Jul 2026 20:11:04 +0000 Subject: [PATCH] Problem 1+5: Dynamic roles dropdown via useRoles(), delete button with ConfirmDialog, fixed create form --- frontend/src/pages/SettingsUsers.tsx | 140 +++++++++++++++++++++++---- 1 file changed, 120 insertions(+), 20 deletions(-) diff --git a/frontend/src/pages/SettingsUsers.tsx b/frontend/src/pages/SettingsUsers.tsx index aea6f60..d7ee116 100644 --- a/frontend/src/pages/SettingsUsers.tsx +++ b/frontend/src/pages/SettingsUsers.tsx @@ -1,6 +1,6 @@ -import React, { useState } from 'react'; +import React, { useState, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; -import { useUsers, useCreateUser, useUpdateUser, useDeleteUser } from '@/api/hooks'; +import { useUsers, useCreateUser, useUpdateUser, useDeleteUser, useRoles } from '@/api/hooks'; import { Button } from '@/components/ui/Button'; import { Card } from '@/components/ui/Card'; import { Badge } from '@/components/ui/Badge'; @@ -13,52 +13,107 @@ import { Modal } from '@/components/ui/Modal'; import { ConfirmDialog } from '@/components/ui/ConfirmDialog'; import { useToast } from '@/components/ui/Toast'; -const ROLES = [ +const LEGACY_ROLES = [ { value: 'admin', label: 'Administrator' }, { value: 'manager', label: 'Manager' }, { value: 'user', label: 'Mitarbeiter' }, { value: 'guest', label: 'Gast' }, + { value: 'viewer', label: 'Viewer' }, ]; export function SettingsUsersPage() { const { t } = useTranslation(); const toast = useToast(); const { data, isLoading } = useUsers(1, 100); + const { data: rolesData } = useRoles(); const createUserMutation = useCreateUser(); const updateUserMutation = useUpdateUser(); const deleteUserMutation = useDeleteUser(); const [inviteOpen, setInviteOpen] = useState(false); const [inviteEmail, setInviteEmail] = useState(''); - const [inviteRole, setInviteRole] = useState('user'); + const [inviteName, setInviteName] = useState(''); + const [invitePassword, setInvitePassword] = useState(''); + const [inviteRoleId, setInviteRoleId] = useState(''); const [confirmDeactivate, setConfirmDeactivate] = useState(null); + const [confirmDelete, setConfirmDelete] = useState(null); const users = data?.items ?? []; + const customRoles = rolesData?.items ?? []; + + const roleOptions = useMemo(() => { + const options = [...LEGACY_ROLES]; + for (const role of customRoles) { + options.push({ value: `role_id:${role.id}`, label: role.name }); + } + return options; + }, [customRoles]); + + const roleLabelMap = useMemo(() => { + const map = new Map(); + for (const opt of roleOptions) { + map.set(opt.value, opt.label); + } + return map; + }, [roleOptions]); + + const getRoleValueForUser = (user: any): string => { + if (user.role_id) { + const key = `role_id:${user.role_id}`; + if (roleLabelMap.has(key)) return key; + } + if (user.role) { + const legacy = LEGACY_ROLES.find((r) => r.value === user.role); + if (legacy) return legacy.value; + } + return 'viewer'; + }; const handleInvite = async () => { - if (!inviteEmail.trim()) { + if (!inviteEmail.trim() || !inviteName.trim() || !invitePassword.trim()) { toast.error(t('validation.required')); return; } + if (invitePassword.length < 8) { + toast.error(t('auth.passwordTooShort')); + return; + } try { - await createUserMutation.mutateAsync({ + const payload: any = { email: inviteEmail.trim(), - role: inviteRole, - first_name: '', - last_name: '', - }); + name: inviteName.trim(), + password: invitePassword, + is_active: true, + }; + if (inviteRoleId.startsWith('role_id:')) { + payload.role_id = inviteRoleId.substring('role_id:'.length); + payload.role = 'viewer'; + } else { + payload.role = inviteRoleId || 'viewer'; + } + await createUserMutation.mutateAsync(payload); toast.success(t('settings.userInvited')); setInviteEmail(''); - setInviteRole('user'); + setInviteName(''); + setInvitePassword(''); + setInviteRoleId(''); setInviteOpen(false); } catch (err: any) { toast.error(err.message || t('common.error')); } }; - const handleRoleChange = async (userId: string, newRole: string) => { + const handleRoleChange = async (userId: string, newRoleValue: string) => { try { - await updateUserMutation.mutateAsync({ id: userId, data: { role: newRole } }); + const data: any = {}; + if (newRoleValue.startsWith('role_id:')) { + data.role_id = newRoleValue.substring('role_id:'.length); + data.role = 'viewer'; + } else { + data.role = newRoleValue; + data.role_id = null; + } + await updateUserMutation.mutateAsync({ id: userId, data }); toast.success(t('settings.assignRole') + ' — OK'); } catch (err: any) { toast.error(err.message || t('common.error')); @@ -81,6 +136,17 @@ export function SettingsUsersPage() { } }; + const handleDeleteUser = async () => { + if (!confirmDelete) return; + try { + await deleteUserMutation.mutateAsync(confirmDelete.id); + toast.success(t('settings.userDeleted')); + setConfirmDelete(null); + } catch (err: any) { + toast.error(err.message || t('common.error')); + } + }; + if (isLoading) { return (
@@ -115,13 +181,13 @@ export function SettingsUsersPage() {

- {user.first_name} {user.last_name} + {user.name}

{user.email}

@@ -131,8 +197,8 @@ export function SettingsUsersPage() {
setInviteName(e.target.value)} + placeholder={t('settings.name')} + data-testid="invite-name" + /> + setInvitePassword(e.target.value)} + placeholder="********" + data-testid="invite-password" + />