sidebar fix
This commit is contained in:
3
user/web/.gitignore
vendored
Normal file
3
user/web/.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
dist
|
||||
node_modules
|
||||
|
||||
@ -24,7 +24,7 @@ import {
|
||||
} from '@tabler/icons-react';
|
||||
import { notifications } from '@mantine/notifications';
|
||||
import { modals } from '@mantine/modals';
|
||||
import UserForm from './UserForm';
|
||||
import UserSidebar from './UserSidebar';
|
||||
import { userService } from '../services/userService';
|
||||
import { User, UserStatus, UserRole, ListUsersRequest } from '../types/user';
|
||||
|
||||
@ -36,7 +36,7 @@ const UserManagement: React.FC = () => {
|
||||
const [roleFilter, setRoleFilter] = useState<string | null>(null);
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [totalUsers, setTotalUsers] = useState(0);
|
||||
const [userFormOpened, setUserFormOpened] = useState(false);
|
||||
const [userSidebarOpened, setUserSidebarOpened] = useState(false);
|
||||
const [editingUser, setEditingUser] = useState<User | null>(null);
|
||||
|
||||
const pageSize = 10;
|
||||
@ -76,12 +76,12 @@ const UserManagement: React.FC = () => {
|
||||
|
||||
const handleCreateUser = () => {
|
||||
setEditingUser(null);
|
||||
setUserFormOpened(true);
|
||||
setUserSidebarOpened(true);
|
||||
};
|
||||
|
||||
const handleEditUser = (user: User) => {
|
||||
setEditingUser(user);
|
||||
setUserFormOpened(true);
|
||||
setUserSidebarOpened(true);
|
||||
};
|
||||
|
||||
const handleDeleteUser = (user: User) => {
|
||||
@ -115,12 +115,12 @@ const UserManagement: React.FC = () => {
|
||||
});
|
||||
};
|
||||
|
||||
const handleUserFormSuccess = () => {
|
||||
const handleUserSidebarSuccess = () => {
|
||||
loadUsers();
|
||||
};
|
||||
|
||||
const handleUserFormClose = () => {
|
||||
setUserFormOpened(false);
|
||||
const handleUserSidebarClose = () => {
|
||||
setUserSidebarOpened(false);
|
||||
setEditingUser(null);
|
||||
};
|
||||
|
||||
@ -149,7 +149,13 @@ const UserManagement: React.FC = () => {
|
||||
return (
|
||||
<>
|
||||
{/* Main user management interface */}
|
||||
<Stack gap="md">
|
||||
<Stack
|
||||
gap="md"
|
||||
style={{
|
||||
transition: 'margin-right 0.3s ease',
|
||||
marginRight: userSidebarOpened ? '400px' : '0',
|
||||
}}
|
||||
>
|
||||
<Group justify="space-between">
|
||||
<Button leftSection={<IconPlus size={16} />} onClick={handleCreateUser}>
|
||||
Add User
|
||||
@ -296,11 +302,11 @@ const UserManagement: React.FC = () => {
|
||||
</Paper>
|
||||
</Stack>
|
||||
|
||||
{/* User form modal */}
|
||||
<UserForm
|
||||
opened={userFormOpened}
|
||||
onClose={handleUserFormClose}
|
||||
onSuccess={handleUserFormSuccess}
|
||||
{/* User sidebar */}
|
||||
<UserSidebar
|
||||
opened={userSidebarOpened}
|
||||
onClose={handleUserSidebarClose}
|
||||
onSuccess={handleUserSidebarSuccess}
|
||||
editUser={editingUser}
|
||||
/>
|
||||
</>
|
||||
|
||||
255
user/web/src/components/UserSidebar.tsx
Normal file
255
user/web/src/components/UserSidebar.tsx
Normal file
@ -0,0 +1,255 @@
|
||||
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 { userService } from '../services/userService';
|
||||
import { User, CreateUserRequest, UpdateUserRequest, UserRole, UserStatus } from '../types/user';
|
||||
|
||||
interface UserSidebarProps {
|
||||
opened: boolean;
|
||||
onClose: () => void;
|
||||
onSuccess: () => void;
|
||||
editUser?: User | null;
|
||||
}
|
||||
|
||||
const UserSidebar: React.FC<UserSidebarProps> = ({
|
||||
opened,
|
||||
onClose,
|
||||
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,
|
||||
},
|
||||
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),
|
||||
},
|
||||
});
|
||||
|
||||
// Update form values when editUser changes
|
||||
useEffect(() => {
|
||||
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,
|
||||
});
|
||||
} 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',
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
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>
|
||||
);
|
||||
};
|
||||
|
||||
export default UserSidebar;
|
||||
Reference in New Issue
Block a user