-
This commit is contained in:
@ -1,29 +1,14 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import {
|
||||
Button,
|
||||
Group,
|
||||
Paper,
|
||||
Text,
|
||||
Table,
|
||||
import {
|
||||
DataTable,
|
||||
TableColumn,
|
||||
Badge,
|
||||
ActionIcon,
|
||||
TextInput,
|
||||
Select,
|
||||
Pagination,
|
||||
Stack,
|
||||
LoadingOverlay,
|
||||
Avatar,
|
||||
} from '@mantine/core';
|
||||
import {
|
||||
IconPlus,
|
||||
IconEdit,
|
||||
IconTrash,
|
||||
IconSearch,
|
||||
IconUser,
|
||||
IconMail,
|
||||
} from '@tabler/icons-react';
|
||||
import { notifications } from '@mantine/notifications';
|
||||
import { modals } from '@mantine/modals';
|
||||
Group,
|
||||
Text,
|
||||
Stack
|
||||
} from '@skybridge/web-components';
|
||||
import { Avatar } from '@mantine/core';
|
||||
import { IconUser, IconMail } from '@tabler/icons-react';
|
||||
import UserSidebar from './UserSidebar';
|
||||
import { userService } from '../services/userService';
|
||||
import { User, UserStatus, UserRole, ListUsersRequest } from '../types/user';
|
||||
@ -31,9 +16,7 @@ import { User, UserStatus, UserRole, ListUsersRequest } from '../types/user';
|
||||
const UserManagement: React.FC = () => {
|
||||
const [users, setUsers] = useState<User[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [searchTerm, setSearchTerm] = useState('');
|
||||
const [statusFilter, setStatusFilter] = useState<string | null>(null);
|
||||
const [roleFilter, setRoleFilter] = useState<string | null>(null);
|
||||
const [filters, setFilters] = useState({});
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [totalUsers, setTotalUsers] = useState(0);
|
||||
const [userSidebarOpened, setUserSidebarOpened] = useState(false);
|
||||
@ -41,13 +24,13 @@ const UserManagement: React.FC = () => {
|
||||
|
||||
const pageSize = 10;
|
||||
|
||||
const loadUsers = async (page: number = currentPage) => {
|
||||
const loadUsers = async (page: number = currentPage, newFilters = filters) => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const request: ListUsersRequest = {
|
||||
search: searchTerm || undefined,
|
||||
status: statusFilter as UserStatus || undefined,
|
||||
role: roleFilter as UserRole || undefined,
|
||||
search: newFilters.search || undefined,
|
||||
status: newFilters.status as UserStatus || undefined,
|
||||
role: newFilters.role as UserRole || undefined,
|
||||
limit: pageSize,
|
||||
offset: (page - 1) * pageSize,
|
||||
order_by: 'created_at',
|
||||
@ -60,95 +43,108 @@ const UserManagement: React.FC = () => {
|
||||
setCurrentPage(page);
|
||||
} catch (error) {
|
||||
console.error('Failed to load users:', error);
|
||||
notifications.show({
|
||||
title: 'Error',
|
||||
message: 'Failed to load users',
|
||||
color: 'red',
|
||||
});
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
loadUsers(1);
|
||||
}, [searchTerm, statusFilter, roleFilter]);
|
||||
loadUsers(1, filters);
|
||||
}, [filters]);
|
||||
|
||||
const handleCreateUser = () => {
|
||||
const handleAdd = () => {
|
||||
setEditingUser(null);
|
||||
setUserSidebarOpened(true);
|
||||
};
|
||||
|
||||
const handleEditUser = (user: User) => {
|
||||
const handleEdit = (user: User) => {
|
||||
setEditingUser(user);
|
||||
setUserSidebarOpened(true);
|
||||
};
|
||||
|
||||
const handleDeleteUser = (user: User) => {
|
||||
modals.openConfirmModal({
|
||||
title: 'Delete User',
|
||||
children: (
|
||||
<Text size="sm">
|
||||
Are you sure you want to delete {user.first_name} {user.last_name}? This action cannot be undone.
|
||||
</Text>
|
||||
),
|
||||
labels: { confirm: 'Delete', cancel: 'Cancel' },
|
||||
confirmProps: { color: 'red' },
|
||||
onConfirm: async () => {
|
||||
try {
|
||||
await userService.deleteUser(user.id);
|
||||
notifications.show({
|
||||
title: 'Success',
|
||||
message: 'User deleted successfully',
|
||||
color: 'green',
|
||||
});
|
||||
loadUsers();
|
||||
} catch (error) {
|
||||
console.error('Failed to delete user:', error);
|
||||
notifications.show({
|
||||
title: 'Error',
|
||||
message: 'Failed to delete user',
|
||||
color: 'red',
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const handleUserSidebarSuccess = () => {
|
||||
const handleDelete = async (user: User) => {
|
||||
await userService.deleteUser(user.id);
|
||||
loadUsers();
|
||||
};
|
||||
|
||||
const handleUserSidebarClose = () => {
|
||||
const handleSuccess = () => {
|
||||
setUserSidebarOpened(false);
|
||||
setEditingUser(null);
|
||||
loadUsers();
|
||||
};
|
||||
|
||||
const getStatusColor = (status: UserStatus): string => {
|
||||
switch (status) {
|
||||
case 'active': return 'green';
|
||||
case 'inactive': return 'gray';
|
||||
case 'suspended': return 'red';
|
||||
case 'pending': return 'yellow';
|
||||
default: return 'gray';
|
||||
}
|
||||
const handleFiltersChange = (newFilters) => {
|
||||
setFilters(newFilters);
|
||||
setCurrentPage(1);
|
||||
};
|
||||
|
||||
const getRoleColor = (role: UserRole): string => {
|
||||
switch (role) {
|
||||
case 'admin': return 'red';
|
||||
case 'moderator': return 'orange';
|
||||
case 'user': return 'blue';
|
||||
case 'viewer': return 'gray';
|
||||
default: return 'gray';
|
||||
}
|
||||
};
|
||||
|
||||
const totalPages = Math.ceil(totalUsers / pageSize);
|
||||
const columns: TableColumn[] = [
|
||||
{
|
||||
key: 'user',
|
||||
label: 'User',
|
||||
render: (_, user: User) => (
|
||||
<Group gap="sm">
|
||||
<Avatar
|
||||
src={user.avatar || null}
|
||||
radius="sm"
|
||||
size={32}
|
||||
>
|
||||
<IconUser size={16} />
|
||||
</Avatar>
|
||||
<div>
|
||||
<Text size="sm" fw={500}>
|
||||
{user.display_name || `${user.first_name} ${user.last_name}`}
|
||||
</Text>
|
||||
<Text size="xs" c="dimmed">
|
||||
{user.first_name} {user.last_name}
|
||||
</Text>
|
||||
</div>
|
||||
</Group>
|
||||
)
|
||||
},
|
||||
{
|
||||
key: 'email',
|
||||
label: 'Email',
|
||||
render: (value) => (
|
||||
<Group gap="xs">
|
||||
<IconMail size={14} />
|
||||
<Text size="sm">{value}</Text>
|
||||
</Group>
|
||||
)
|
||||
},
|
||||
{
|
||||
key: 'role',
|
||||
label: 'Role',
|
||||
render: (value) => {
|
||||
const roleColors = {
|
||||
admin: 'red',
|
||||
moderator: 'orange',
|
||||
user: 'blue',
|
||||
viewer: 'gray'
|
||||
};
|
||||
return (
|
||||
<Badge color={roleColors[value] || 'blue'} size="sm">
|
||||
{value}
|
||||
</Badge>
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
key: 'status',
|
||||
label: 'Status'
|
||||
},
|
||||
{
|
||||
key: 'created_at',
|
||||
label: 'Created',
|
||||
render: (value) => (
|
||||
<Text size="sm">
|
||||
{new Date(value).toLocaleDateString()}
|
||||
</Text>
|
||||
)
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Main user management interface */}
|
||||
<Stack
|
||||
gap="md"
|
||||
style={{
|
||||
@ -156,160 +152,32 @@ const UserManagement: React.FC = () => {
|
||||
marginRight: userSidebarOpened ? '400px' : '0',
|
||||
}}
|
||||
>
|
||||
<Group justify="space-between">
|
||||
<Button leftSection={<IconPlus size={16} />} onClick={handleCreateUser}>
|
||||
Add User
|
||||
</Button>
|
||||
</Group>
|
||||
|
||||
<Paper p="md" withBorder>
|
||||
<Group justify="space-between" mb="md">
|
||||
<Group gap="sm">
|
||||
<TextInput
|
||||
placeholder="Search users..."
|
||||
leftSection={<IconSearch size={16} />}
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.currentTarget.value)}
|
||||
style={{ minWidth: 250 }}
|
||||
/>
|
||||
<Select
|
||||
placeholder="Filter by status"
|
||||
data={[
|
||||
{ value: 'active', label: 'Active' },
|
||||
{ value: 'inactive', label: 'Inactive' },
|
||||
{ value: 'suspended', label: 'Suspended' },
|
||||
{ value: 'pending', label: 'Pending' },
|
||||
]}
|
||||
value={statusFilter}
|
||||
onChange={setStatusFilter}
|
||||
clearable
|
||||
/>
|
||||
<Select
|
||||
placeholder="Filter by role"
|
||||
data={[
|
||||
{ value: 'admin', label: 'Admin' },
|
||||
{ value: 'moderator', label: 'Moderator' },
|
||||
{ value: 'user', label: 'User' },
|
||||
{ value: 'viewer', label: 'Viewer' },
|
||||
]}
|
||||
value={roleFilter}
|
||||
onChange={setRoleFilter}
|
||||
clearable
|
||||
/>
|
||||
</Group>
|
||||
<Text size="sm" c="dimmed">
|
||||
{totalUsers} users found
|
||||
</Text>
|
||||
</Group>
|
||||
|
||||
<div style={{ position: 'relative' }}>
|
||||
<LoadingOverlay visible={loading} />
|
||||
<Table striped highlightOnHover>
|
||||
<Table.Thead>
|
||||
<Table.Tr>
|
||||
<Table.Th>User</Table.Th>
|
||||
<Table.Th>Email</Table.Th>
|
||||
<Table.Th>Role</Table.Th>
|
||||
<Table.Th>Status</Table.Th>
|
||||
<Table.Th>Created</Table.Th>
|
||||
<Table.Th>Actions</Table.Th>
|
||||
</Table.Tr>
|
||||
</Table.Thead>
|
||||
<Table.Tbody>
|
||||
{users.map((user) => (
|
||||
<Table.Tr key={user.id}>
|
||||
<Table.Td>
|
||||
<Group gap="sm">
|
||||
<Avatar
|
||||
src={user.avatar || null}
|
||||
radius="sm"
|
||||
size={32}
|
||||
>
|
||||
<IconUser size={16} />
|
||||
</Avatar>
|
||||
<div>
|
||||
<Text size="sm" fw={500}>
|
||||
{user.display_name || `${user.first_name} ${user.last_name}`}
|
||||
</Text>
|
||||
<Text size="xs" c="dimmed">
|
||||
{user.first_name} {user.last_name}
|
||||
</Text>
|
||||
</div>
|
||||
</Group>
|
||||
</Table.Td>
|
||||
<Table.Td>
|
||||
<Group gap="xs">
|
||||
<IconMail size={14} />
|
||||
<Text size="sm">{user.email}</Text>
|
||||
</Group>
|
||||
</Table.Td>
|
||||
<Table.Td>
|
||||
<Badge color={getRoleColor(user.role)} variant="light">
|
||||
{user.role}
|
||||
</Badge>
|
||||
</Table.Td>
|
||||
<Table.Td>
|
||||
<Badge color={getStatusColor(user.status)} variant="light">
|
||||
{user.status}
|
||||
</Badge>
|
||||
</Table.Td>
|
||||
<Table.Td>
|
||||
<Text size="sm">
|
||||
{new Date(user.created_at).toLocaleDateString()}
|
||||
</Text>
|
||||
</Table.Td>
|
||||
<Table.Td>
|
||||
<Group gap="xs">
|
||||
<ActionIcon
|
||||
variant="light"
|
||||
size="sm"
|
||||
onClick={() => handleEditUser(user)}
|
||||
>
|
||||
<IconEdit size={14} />
|
||||
</ActionIcon>
|
||||
<ActionIcon
|
||||
variant="light"
|
||||
color="red"
|
||||
size="sm"
|
||||
onClick={() => handleDeleteUser(user)}
|
||||
>
|
||||
<IconTrash size={14} />
|
||||
</ActionIcon>
|
||||
</Group>
|
||||
</Table.Td>
|
||||
</Table.Tr>
|
||||
))}
|
||||
</Table.Tbody>
|
||||
</Table>
|
||||
|
||||
{users.length === 0 && !loading && (
|
||||
<Text ta="center" py="xl" c="dimmed">
|
||||
No users found
|
||||
</Text>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{totalPages > 1 && (
|
||||
<Group justify="center" mt="md">
|
||||
<Pagination
|
||||
value={currentPage}
|
||||
onChange={loadUsers}
|
||||
total={totalPages}
|
||||
size="sm"
|
||||
/>
|
||||
</Group>
|
||||
)}
|
||||
</Paper>
|
||||
</Stack>
|
||||
|
||||
{/* User sidebar */}
|
||||
<DataTable
|
||||
data={users}
|
||||
columns={columns}
|
||||
loading={loading}
|
||||
title="User Management"
|
||||
total={totalUsers}
|
||||
page={currentPage}
|
||||
pageSize={pageSize}
|
||||
onPageChange={loadUsers}
|
||||
searchable
|
||||
filters={filters}
|
||||
onFiltersChange={handleFiltersChange}
|
||||
onAdd={handleAdd}
|
||||
onEdit={handleEdit}
|
||||
onDelete={handleDelete}
|
||||
onRefresh={() => loadUsers()}
|
||||
emptyMessage="No users found"
|
||||
/>
|
||||
|
||||
<UserSidebar
|
||||
opened={userSidebarOpened}
|
||||
onClose={handleUserSidebarClose}
|
||||
onSuccess={handleUserSidebarSuccess}
|
||||
onClose={() => setUserSidebarOpened(false)}
|
||||
onSuccess={handleSuccess}
|
||||
editUser={editingUser}
|
||||
/>
|
||||
</>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@ -1,21 +1,10 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import {
|
||||
Paper,
|
||||
TextInput,
|
||||
Select,
|
||||
Button,
|
||||
Group,
|
||||
Stack,
|
||||
Title,
|
||||
ActionIcon,
|
||||
ScrollArea,
|
||||
Box,
|
||||
} from '@mantine/core';
|
||||
import { IconX } from '@tabler/icons-react';
|
||||
import { useForm } from '@mantine/form';
|
||||
import { notifications } from '@mantine/notifications';
|
||||
import React from 'react';
|
||||
import {
|
||||
FormSidebar,
|
||||
FormField
|
||||
} from '@skybridge/web-components';
|
||||
import { userService } from '../services/userService';
|
||||
import { User, CreateUserRequest, UpdateUserRequest, UserRole, UserStatus } from '../types/user';
|
||||
import { User } from '../types/user';
|
||||
|
||||
interface UserSidebarProps {
|
||||
opened: boolean;
|
||||
@ -30,225 +19,91 @@ const UserSidebar: React.FC<UserSidebarProps> = ({
|
||||
onSuccess,
|
||||
editUser,
|
||||
}) => {
|
||||
const isEditing = !!editUser;
|
||||
|
||||
const form = useForm({
|
||||
initialValues: {
|
||||
email: '',
|
||||
first_name: '',
|
||||
last_name: '',
|
||||
display_name: '',
|
||||
avatar: '',
|
||||
role: 'user' as UserRole,
|
||||
status: 'pending' as UserStatus,
|
||||
const fields: FormField[] = [
|
||||
{
|
||||
name: 'first_name',
|
||||
label: 'First Name',
|
||||
type: 'text',
|
||||
required: true,
|
||||
placeholder: 'Enter first name',
|
||||
},
|
||||
validate: {
|
||||
email: (value) => (/^\S+@\S+$/.test(value) ? null : 'Invalid email'),
|
||||
first_name: (value) => (value.trim().length < 1 ? 'First name is required' : null),
|
||||
last_name: (value) => (value.trim().length < 1 ? 'Last name is required' : null),
|
||||
{
|
||||
name: 'last_name',
|
||||
label: 'Last Name',
|
||||
type: 'text',
|
||||
required: true,
|
||||
placeholder: 'Enter last name',
|
||||
},
|
||||
});
|
||||
{
|
||||
name: 'display_name',
|
||||
label: 'Display Name',
|
||||
type: 'text',
|
||||
required: false,
|
||||
placeholder: 'Enter display name (optional)',
|
||||
},
|
||||
{
|
||||
name: 'email',
|
||||
label: 'Email',
|
||||
type: 'email',
|
||||
required: true,
|
||||
placeholder: 'Enter email address',
|
||||
validation: { email: true },
|
||||
},
|
||||
{
|
||||
name: 'avatar',
|
||||
label: 'Avatar URL',
|
||||
type: 'text',
|
||||
required: false,
|
||||
placeholder: 'Enter avatar URL (optional)',
|
||||
},
|
||||
{
|
||||
name: 'role',
|
||||
label: 'Role',
|
||||
type: 'select',
|
||||
required: true,
|
||||
options: [
|
||||
{ value: 'admin', label: 'Admin' },
|
||||
{ value: 'moderator', label: 'Moderator' },
|
||||
{ value: 'user', label: 'User' },
|
||||
{ value: 'viewer', label: 'Viewer' },
|
||||
],
|
||||
defaultValue: 'user',
|
||||
},
|
||||
{
|
||||
name: 'status',
|
||||
label: 'Status',
|
||||
type: 'select',
|
||||
required: true,
|
||||
options: [
|
||||
{ value: 'active', label: 'Active' },
|
||||
{ value: 'inactive', label: 'Inactive' },
|
||||
{ value: 'suspended', label: 'Suspended' },
|
||||
{ value: 'pending', label: 'Pending' },
|
||||
],
|
||||
defaultValue: 'pending',
|
||||
},
|
||||
];
|
||||
|
||||
// Update form values when editUser changes
|
||||
useEffect(() => {
|
||||
const handleSubmit = async (values: any) => {
|
||||
if (editUser) {
|
||||
form.setValues({
|
||||
email: editUser.email || '',
|
||||
first_name: editUser.first_name || '',
|
||||
last_name: editUser.last_name || '',
|
||||
display_name: editUser.display_name || '',
|
||||
avatar: editUser.avatar || '',
|
||||
role: editUser.role || 'user' as UserRole,
|
||||
status: editUser.status || 'pending' as UserStatus,
|
||||
});
|
||||
await userService.updateUser(editUser.id, values);
|
||||
} else {
|
||||
// Reset to default values when not editing
|
||||
form.setValues({
|
||||
email: '',
|
||||
first_name: '',
|
||||
last_name: '',
|
||||
display_name: '',
|
||||
avatar: '',
|
||||
role: 'user' as UserRole,
|
||||
status: 'pending' as UserStatus,
|
||||
});
|
||||
}
|
||||
}, [editUser, opened]);
|
||||
|
||||
const handleSubmit = async (values: typeof form.values) => {
|
||||
try {
|
||||
if (isEditing && editUser) {
|
||||
const updateRequest: UpdateUserRequest = {
|
||||
email: values.email !== editUser.email ? values.email : undefined,
|
||||
first_name: values.first_name !== editUser.first_name ? values.first_name : undefined,
|
||||
last_name: values.last_name !== editUser.last_name ? values.last_name : undefined,
|
||||
display_name: values.display_name !== editUser.display_name ? values.display_name : undefined,
|
||||
avatar: values.avatar !== editUser.avatar ? values.avatar : undefined,
|
||||
role: values.role !== editUser.role ? values.role : undefined,
|
||||
status: values.status !== editUser.status ? values.status : undefined,
|
||||
};
|
||||
|
||||
// Only send fields that have changed
|
||||
const hasChanges = Object.values(updateRequest).some(value => value !== undefined);
|
||||
if (!hasChanges) {
|
||||
notifications.show({
|
||||
title: 'No Changes',
|
||||
message: 'No changes detected',
|
||||
color: 'blue',
|
||||
});
|
||||
onClose();
|
||||
return;
|
||||
}
|
||||
|
||||
await userService.updateUser(editUser.id, updateRequest);
|
||||
notifications.show({
|
||||
title: 'Success',
|
||||
message: 'User updated successfully',
|
||||
color: 'green',
|
||||
});
|
||||
} else {
|
||||
const createRequest: CreateUserRequest = {
|
||||
email: values.email,
|
||||
first_name: values.first_name,
|
||||
last_name: values.last_name,
|
||||
display_name: values.display_name || undefined,
|
||||
avatar: values.avatar || undefined,
|
||||
role: values.role,
|
||||
status: values.status,
|
||||
};
|
||||
|
||||
await userService.createUser(createRequest);
|
||||
notifications.show({
|
||||
title: 'Success',
|
||||
message: 'User created successfully',
|
||||
color: 'green',
|
||||
});
|
||||
}
|
||||
|
||||
onSuccess();
|
||||
onClose();
|
||||
form.reset();
|
||||
} catch (error: any) {
|
||||
console.error('Failed to save user:', error);
|
||||
notifications.show({
|
||||
title: 'Error',
|
||||
message: error.message || 'Failed to save user',
|
||||
color: 'red',
|
||||
});
|
||||
await userService.createUser(values);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Paper
|
||||
style={{
|
||||
position: 'fixed',
|
||||
top: 60, // Below header
|
||||
right: opened ? 0 : '-400px',
|
||||
bottom: 0,
|
||||
width: '400px',
|
||||
zIndex: 1000,
|
||||
borderRadius: 0,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
borderLeft: '1px solid var(--mantine-color-gray-3)',
|
||||
backgroundColor: 'var(--mantine-color-body)',
|
||||
transition: 'right 0.3s ease',
|
||||
}}
|
||||
>
|
||||
{/* Header */}
|
||||
<Group justify="space-between" p="md" style={{ borderBottom: '1px solid var(--mantine-color-gray-3)' }}>
|
||||
<Title order={4}>
|
||||
{isEditing ? 'Edit User' : 'Create User'}
|
||||
</Title>
|
||||
<ActionIcon
|
||||
variant="subtle"
|
||||
color="gray"
|
||||
onClick={onClose}
|
||||
>
|
||||
<IconX size={18} />
|
||||
</ActionIcon>
|
||||
</Group>
|
||||
|
||||
{/* Content */}
|
||||
<ScrollArea style={{ flex: 1 }}>
|
||||
<Box p="md">
|
||||
<form onSubmit={form.onSubmit(handleSubmit)}>
|
||||
<Stack gap="md">
|
||||
<Group grow>
|
||||
<TextInput
|
||||
label="First Name"
|
||||
placeholder="Enter first name"
|
||||
required
|
||||
{...form.getInputProps('first_name')}
|
||||
/>
|
||||
<TextInput
|
||||
label="Last Name"
|
||||
placeholder="Enter last name"
|
||||
required
|
||||
{...form.getInputProps('last_name')}
|
||||
/>
|
||||
</Group>
|
||||
|
||||
<TextInput
|
||||
label="Display Name"
|
||||
placeholder="Enter display name (optional)"
|
||||
{...form.getInputProps('display_name')}
|
||||
/>
|
||||
|
||||
<TextInput
|
||||
label="Email"
|
||||
placeholder="Enter email address"
|
||||
required
|
||||
type="email"
|
||||
{...form.getInputProps('email')}
|
||||
/>
|
||||
|
||||
<TextInput
|
||||
label="Avatar URL"
|
||||
placeholder="Enter avatar URL (optional)"
|
||||
{...form.getInputProps('avatar')}
|
||||
/>
|
||||
|
||||
<Group grow>
|
||||
<Select
|
||||
label="Role"
|
||||
placeholder="Select role"
|
||||
required
|
||||
data={[
|
||||
{ value: 'admin', label: 'Admin' },
|
||||
{ value: 'moderator', label: 'Moderator' },
|
||||
{ value: 'user', label: 'User' },
|
||||
{ value: 'viewer', label: 'Viewer' },
|
||||
]}
|
||||
{...form.getInputProps('role')}
|
||||
/>
|
||||
<Select
|
||||
label="Status"
|
||||
placeholder="Select status"
|
||||
required
|
||||
data={[
|
||||
{ value: 'active', label: 'Active' },
|
||||
{ value: 'inactive', label: 'Inactive' },
|
||||
{ value: 'suspended', label: 'Suspended' },
|
||||
{ value: 'pending', label: 'Pending' },
|
||||
]}
|
||||
{...form.getInputProps('status')}
|
||||
/>
|
||||
</Group>
|
||||
|
||||
<Group justify="flex-end" mt="xl">
|
||||
<Button variant="light" onClick={onClose}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button type="submit">
|
||||
{isEditing ? 'Update' : 'Create'} User
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</form>
|
||||
</Box>
|
||||
</ScrollArea>
|
||||
</Paper>
|
||||
<FormSidebar
|
||||
opened={opened}
|
||||
onClose={onClose}
|
||||
onSuccess={onSuccess}
|
||||
title="User"
|
||||
editMode={!!editUser}
|
||||
editItem={editUser}
|
||||
fields={fields}
|
||||
onSubmit={handleSubmit}
|
||||
width={400}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user