460 lines
13 KiB
TypeScript
460 lines
13 KiB
TypeScript
import React, { useState } from 'react';
|
|
import {
|
|
Grid,
|
|
Paper,
|
|
Typography,
|
|
Box,
|
|
Button,
|
|
Chip,
|
|
IconButton,
|
|
TextField,
|
|
InputAdornment,
|
|
FormControl,
|
|
InputLabel,
|
|
Select,
|
|
MenuItem,
|
|
Dialog,
|
|
DialogTitle,
|
|
DialogContent,
|
|
DialogActions,
|
|
Avatar,
|
|
Alert,
|
|
Card,
|
|
CardContent,
|
|
} from '@mui/material';
|
|
import {
|
|
Search,
|
|
Add,
|
|
Edit,
|
|
Delete,
|
|
} from '@mui/icons-material';
|
|
import { DataGrid } from '@mui/x-data-grid';
|
|
import { ROLES } from '../../components/Auth/AuthContext';
|
|
|
|
const mockUsers = [
|
|
{
|
|
id: 1,
|
|
username: 'admin',
|
|
email: 'admin@company.com',
|
|
name: 'System Administrator',
|
|
role: 'ADMIN',
|
|
department: 'IT',
|
|
lastLogin: '2024-01-15 10:30',
|
|
status: 'active',
|
|
avatar: null,
|
|
created: '2024-01-01',
|
|
permissions: ['All Permissions']
|
|
},
|
|
{
|
|
id: 2,
|
|
username: 'john.smith',
|
|
email: 'john.smith@company.com',
|
|
name: 'John Smith',
|
|
role: 'IT_STAFF',
|
|
department: 'Infrastructure',
|
|
lastLogin: '2024-01-15 09:45',
|
|
status: 'active',
|
|
avatar: null,
|
|
created: '2024-01-02',
|
|
permissions: ['Incident Management', 'Problem Management', 'Change Management']
|
|
},
|
|
{
|
|
id: 3,
|
|
username: 'sarah.johnson',
|
|
email: 'sarah.johnson@company.com',
|
|
name: 'Sarah Johnson',
|
|
role: 'MANAGER',
|
|
department: 'IT Operations',
|
|
lastLogin: '2024-01-15 08:20',
|
|
status: 'active',
|
|
avatar: null,
|
|
created: '2024-01-03',
|
|
permissions: ['Reporting', 'Management', 'Approval']
|
|
},
|
|
{
|
|
id: 4,
|
|
username: 'mike.davis',
|
|
email: 'mike.davis@company.com',
|
|
name: 'Mike Davis',
|
|
role: 'IT_STAFF',
|
|
department: 'Applications',
|
|
lastLogin: '2024-01-14 16:30',
|
|
status: 'active',
|
|
avatar: null,
|
|
created: '2024-01-04',
|
|
permissions: ['Incident Management', 'Problem Management']
|
|
},
|
|
{
|
|
id: 5,
|
|
username: 'lisa.wilson',
|
|
email: 'lisa.wilson@company.com',
|
|
name: 'Lisa Wilson',
|
|
role: 'END_USER',
|
|
department: 'HR',
|
|
lastLogin: '2024-01-15 11:15',
|
|
status: 'active',
|
|
avatar: null,
|
|
created: '2024-01-05',
|
|
permissions: ['Self-Service', 'Knowledge Base']
|
|
},
|
|
{
|
|
id: 6,
|
|
username: 'auditor1',
|
|
email: 'auditor1@company.com',
|
|
name: 'Audit Specialist',
|
|
role: 'AUDITOR',
|
|
department: 'Compliance',
|
|
lastLogin: '2024-01-14 14:20',
|
|
status: 'active',
|
|
avatar: null,
|
|
created: '2024-01-06',
|
|
permissions: ['Audit', 'Compliance', 'Reporting']
|
|
}
|
|
];
|
|
|
|
const UserManagement: React.FC = () => {
|
|
const [users, setUsers] = useState(mockUsers);
|
|
const [searchTerm, setSearchTerm] = useState('');
|
|
const [roleFilter, setRoleFilter] = useState('All');
|
|
const [statusFilter, setStatusFilter] = useState('All');
|
|
const [selectedUser, setSelectedUser] = useState<any>(null);
|
|
const [userDialogOpen, setUserDialogOpen] = useState<boolean>(false);
|
|
const [newUser, setNewUser] = useState({
|
|
username: '',
|
|
email: '',
|
|
name: '',
|
|
role: 'END_USER',
|
|
department: '',
|
|
status: 'active'
|
|
});
|
|
|
|
const columns = [
|
|
{ field: 'id', headerName: 'ID', width: 80 },
|
|
{
|
|
field: 'avatar',
|
|
headerName: 'Avatar',
|
|
width: 80,
|
|
renderCell: (params: any) => (
|
|
<Avatar sx={{ width: 32, height: 32, bgcolor: 'primary.main' }}>
|
|
{params.row.name.charAt(0)}
|
|
</Avatar>
|
|
),
|
|
},
|
|
{ field: 'name', headerName: 'Name', width: 200 },
|
|
{ field: 'username', headerName: 'Username', width: 150 },
|
|
{ field: 'email', headerName: 'Email', width: 250 },
|
|
{
|
|
field: 'role',
|
|
headerName: 'Role',
|
|
width: 120,
|
|
renderCell: (params: any) => (
|
|
<Chip
|
|
label={ROLES[params.row.role as keyof typeof ROLES]?.name || params.row.role}
|
|
size="small"
|
|
sx={{
|
|
bgcolor: ROLES[params.row.role as keyof typeof ROLES]?.color || 'default',
|
|
color: 'white'
|
|
}}
|
|
/>
|
|
),
|
|
},
|
|
{ field: 'department', headerName: 'Department', width: 150 },
|
|
{
|
|
field: 'status',
|
|
headerName: 'Status',
|
|
width: 100,
|
|
renderCell: (params: any) => (
|
|
<Chip
|
|
label={params.row.status}
|
|
size="small"
|
|
color={params.row.status === 'active' ? 'success' : 'error'}
|
|
/>
|
|
),
|
|
},
|
|
{ field: 'lastLogin', headerName: 'Last Login', width: 150 },
|
|
{
|
|
field: 'actions',
|
|
headerName: 'Actions',
|
|
width: 120,
|
|
renderCell: (params: any) => (
|
|
<Box>
|
|
<IconButton size="small" onClick={() => handleEditUser(params.row)}>
|
|
<Edit />
|
|
</IconButton>
|
|
<IconButton size="small" onClick={() => handleDeleteUser(params.row.id)}>
|
|
<Delete />
|
|
</IconButton>
|
|
</Box>
|
|
),
|
|
},
|
|
];
|
|
|
|
const handleEditUser = (user: any) => {
|
|
setSelectedUser(user);
|
|
setNewUser(user);
|
|
setUserDialogOpen(true);
|
|
};
|
|
|
|
const handleDeleteUser = (userId: any) => {
|
|
if (window.confirm('Are you sure you want to delete this user?')) {
|
|
setUsers(users.filter(u => u.id !== userId));
|
|
}
|
|
};
|
|
|
|
const handleCreateUser = () => {
|
|
setSelectedUser(null);
|
|
setNewUser({
|
|
username: '',
|
|
email: '',
|
|
name: '',
|
|
role: 'END_USER',
|
|
department: '',
|
|
status: 'active'
|
|
});
|
|
setUserDialogOpen(true);
|
|
};
|
|
|
|
const handleSaveUser = () => {
|
|
if (selectedUser) {
|
|
// Update existing user
|
|
setUsers(users.map(u => u.id === selectedUser.id ? {
|
|
...u,
|
|
...newUser,
|
|
id: selectedUser.id
|
|
} : u));
|
|
} else {
|
|
// Create new user
|
|
const newId = Math.max(...users.map(u => u.id)) + 1;
|
|
setUsers([...users, {
|
|
...newUser,
|
|
id: newId,
|
|
lastLogin: 'Never',
|
|
avatar: null,
|
|
created: new Date().toISOString().split('T')[0],
|
|
permissions: ROLES[newUser.role as keyof typeof ROLES]?.permissions || []
|
|
}]);
|
|
}
|
|
setUserDialogOpen(false);
|
|
};
|
|
|
|
const getRoleStats = () => {
|
|
const stats: { [key: string]: number } = {};
|
|
Object.keys(ROLES).forEach(role => {
|
|
stats[role] = users.filter(u => u.role === role).length;
|
|
});
|
|
return stats;
|
|
};
|
|
|
|
const filteredUsers = users.filter(user => {
|
|
const matchesSearch = user.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
|
user.username.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
|
user.email.toLowerCase().includes(searchTerm.toLowerCase());
|
|
const matchesRole = roleFilter === 'All' || user.role === roleFilter;
|
|
const matchesStatus = statusFilter === 'All' || user.status === statusFilter;
|
|
return matchesSearch && matchesRole && matchesStatus;
|
|
});
|
|
|
|
const roleStats = getRoleStats();
|
|
|
|
return (
|
|
<Box>
|
|
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 3 }}>
|
|
<Typography variant="h4" gutterBottom>
|
|
User Management
|
|
</Typography>
|
|
<Button
|
|
variant="contained"
|
|
startIcon={<Add />}
|
|
onClick={handleCreateUser}
|
|
>
|
|
Add User
|
|
</Button>
|
|
</Box>
|
|
|
|
{/* Role Statistics */}
|
|
<Grid container spacing={3} sx={{ mb: 3 }}>
|
|
{Object.entries(roleStats).map(([role, count]) => (
|
|
<Grid item xs={12} sm={6} md={2.4} key={role}>
|
|
<Card>
|
|
<CardContent sx={{ textAlign: 'center' }}>
|
|
<Typography variant="h4" sx={{ color: ROLES[role as keyof typeof ROLES]?.color }}>
|
|
{count as number}
|
|
</Typography>
|
|
<Typography variant="body2" color="text.secondary">
|
|
{ROLES[role as keyof typeof ROLES]?.name}
|
|
</Typography>
|
|
</CardContent>
|
|
</Card>
|
|
</Grid>
|
|
))}
|
|
</Grid>
|
|
|
|
{/* Filters */}
|
|
<Paper sx={{ p: 2, mb: 3 }}>
|
|
<Grid container spacing={2} alignItems="center">
|
|
<Grid item xs={12} md={4}>
|
|
<TextField
|
|
fullWidth
|
|
placeholder="Search users..."
|
|
value={searchTerm}
|
|
onChange={(e) => setSearchTerm(e.target.value)}
|
|
InputProps={{
|
|
startAdornment: (
|
|
<InputAdornment position="start">
|
|
<Search />
|
|
</InputAdornment>
|
|
),
|
|
}}
|
|
/>
|
|
</Grid>
|
|
<Grid item xs={12} md={3}>
|
|
<FormControl fullWidth>
|
|
<InputLabel>Role</InputLabel>
|
|
<Select
|
|
value={roleFilter}
|
|
label="Role"
|
|
onChange={(e) => setRoleFilter(e.target.value)}
|
|
>
|
|
<MenuItem value="All">All Roles</MenuItem>
|
|
{Object.entries(ROLES).map(([key, role]) => (
|
|
<MenuItem key={key} value={key}>{role.name}</MenuItem>
|
|
))}
|
|
</Select>
|
|
</FormControl>
|
|
</Grid>
|
|
<Grid item xs={12} md={3}>
|
|
<FormControl fullWidth>
|
|
<InputLabel>Status</InputLabel>
|
|
<Select
|
|
value={statusFilter}
|
|
label="Status"
|
|
onChange={(e) => setStatusFilter(e.target.value)}
|
|
>
|
|
<MenuItem value="All">All Status</MenuItem>
|
|
<MenuItem value="active">Active</MenuItem>
|
|
<MenuItem value="inactive">Inactive</MenuItem>
|
|
</Select>
|
|
</FormControl>
|
|
</Grid>
|
|
</Grid>
|
|
</Paper>
|
|
|
|
{/* Users Table */}
|
|
<Paper sx={{ p: 2 }}>
|
|
<DataGrid
|
|
rows={filteredUsers}
|
|
columns={columns}
|
|
pageSize={10}
|
|
rowsPerPageOptions={[10, 25, 50]}
|
|
checkboxSelection
|
|
disableSelectionOnClick
|
|
sx={{ height: 400 }}
|
|
/>
|
|
</Paper>
|
|
|
|
{/* User Dialog */}
|
|
<Dialog open={userDialogOpen} onClose={() => setUserDialogOpen(false)} maxWidth="md" fullWidth>
|
|
<DialogTitle>
|
|
{selectedUser ? 'Edit User' : 'Create New User'}
|
|
</DialogTitle>
|
|
<DialogContent>
|
|
<Grid container spacing={2} sx={{ mt: 1 }}>
|
|
<Grid item xs={12} md={6}>
|
|
<TextField
|
|
fullWidth
|
|
label="Username"
|
|
value={newUser.username}
|
|
onChange={(e) => setNewUser(prev => ({ ...prev, username: e.target.value }))}
|
|
required
|
|
/>
|
|
</Grid>
|
|
<Grid item xs={12} md={6}>
|
|
<TextField
|
|
fullWidth
|
|
label="Email"
|
|
type="email"
|
|
value={newUser.email}
|
|
onChange={(e) => setNewUser(prev => ({ ...prev, email: e.target.value }))}
|
|
required
|
|
/>
|
|
</Grid>
|
|
<Grid item xs={12} md={6}>
|
|
<TextField
|
|
fullWidth
|
|
label="Full Name"
|
|
value={newUser.name}
|
|
onChange={(e) => setNewUser(prev => ({ ...prev, name: e.target.value }))}
|
|
required
|
|
/>
|
|
</Grid>
|
|
<Grid item xs={12} md={6}>
|
|
<TextField
|
|
fullWidth
|
|
label="Department"
|
|
value={newUser.department}
|
|
onChange={(e) => setNewUser(prev => ({ ...prev, department: e.target.value }))}
|
|
/>
|
|
</Grid>
|
|
<Grid item xs={12} md={6}>
|
|
<FormControl fullWidth required>
|
|
<InputLabel>Role</InputLabel>
|
|
<Select
|
|
value={newUser.role}
|
|
label="Role"
|
|
onChange={(e) => setNewUser(prev => ({ ...prev, role: e.target.value }))}
|
|
>
|
|
{Object.entries(ROLES).map(([key, role]) => (
|
|
<MenuItem key={key} value={key}>
|
|
<Box sx={{ display: 'flex', alignItems: 'center' }}>
|
|
<Box
|
|
sx={{
|
|
width: 12,
|
|
height: 12,
|
|
borderRadius: '50%',
|
|
bgcolor: role.color,
|
|
mr: 1,
|
|
}}
|
|
/>
|
|
{role.name}
|
|
</Box>
|
|
</MenuItem>
|
|
))}
|
|
</Select>
|
|
</FormControl>
|
|
</Grid>
|
|
<Grid item xs={12} md={6}>
|
|
<FormControl fullWidth>
|
|
<InputLabel>Status</InputLabel>
|
|
<Select
|
|
value={newUser.status}
|
|
label="Status"
|
|
onChange={(e) => setNewUser(prev => ({ ...prev, status: e.target.value }))}
|
|
>
|
|
<MenuItem value="active">Active</MenuItem>
|
|
<MenuItem value="inactive">Inactive</MenuItem>
|
|
</Select>
|
|
</FormControl>
|
|
</Grid>
|
|
<Grid item xs={12}>
|
|
<Alert severity="info">
|
|
<Typography variant="subtitle2">Role Permissions:</Typography>
|
|
<Typography variant="body2">
|
|
{ROLES[newUser.role as keyof typeof ROLES]?.permissions.join(', ') || 'No permissions'}
|
|
</Typography>
|
|
</Alert>
|
|
</Grid>
|
|
</Grid>
|
|
</DialogContent>
|
|
<DialogActions>
|
|
<Button onClick={() => setUserDialogOpen(false)}>Cancel</Button>
|
|
<Button variant="contained" onClick={handleSaveUser}>
|
|
{selectedUser ? 'Update' : 'Create'}
|
|
</Button>
|
|
</DialogActions>
|
|
</Dialog>
|
|
</Box>
|
|
);
|
|
}
|
|
|
|
export default UserManagement;
|