feat: Replace individual action icons with dropdown menu in users table
Some checks failed
ParentFlow CI/CD Pipeline / Backend Tests (push) Has been cancelled
ParentFlow CI/CD Pipeline / Frontend Tests (push) Has been cancelled
ParentFlow CI/CD Pipeline / Security Scanning (push) Has been cancelled
ParentFlow CI/CD Pipeline / Build Docker Images (map[context:maternal-app/maternal-app-backend dockerfile:Dockerfile.production name:backend]) (push) Has been cancelled
ParentFlow CI/CD Pipeline / Build Docker Images (map[context:maternal-web dockerfile:Dockerfile.production name:frontend]) (push) Has been cancelled
ParentFlow CI/CD Pipeline / Deploy to Development (push) Has been cancelled
ParentFlow CI/CD Pipeline / Deploy to Production (push) Has been cancelled
CI/CD Pipeline / Lint and Test (push) Has been cancelled
CI/CD Pipeline / E2E Tests (push) Has been cancelled
CI/CD Pipeline / Build Application (push) Has been cancelled

Improved UX by consolidating user actions into a single dropdown menu:
- Added MoreVert icon button to open actions menu
- Menu includes: View Details, Edit User, Activate/Deactivate, Delete User
- Delete action shown in error color for visual distinction
- Cleaner table layout with single action button per row

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Andrei
2025-10-07 16:03:35 +00:00
parent ab23e978a2
commit e4dbf30dbb

View File

@@ -27,6 +27,10 @@ import {
Grid, Grid,
Switch, Switch,
FormControlLabel, FormControlLabel,
Menu,
MenuItem,
ListItemIcon,
ListItemText,
} from '@mui/material'; } from '@mui/material';
import { import {
Search, Search,
@@ -36,6 +40,7 @@ import {
PersonAdd, PersonAdd,
Block, Block,
CheckCircle, CheckCircle,
MoreVert,
} from '@mui/icons-material'; } from '@mui/icons-material';
import AdminLayout from '@/components/AdminLayout'; import AdminLayout from '@/components/AdminLayout';
import apiClient from '@/lib/api-client'; import apiClient from '@/lib/api-client';
@@ -68,6 +73,8 @@ export default function UsersPage() {
const [selectedUser, setSelectedUser] = useState<User | null>(null); const [selectedUser, setSelectedUser] = useState<User | null>(null);
const [viewDialogOpen, setViewDialogOpen] = useState(false); const [viewDialogOpen, setViewDialogOpen] = useState(false);
const [editDialogOpen, setEditDialogOpen] = useState(false); const [editDialogOpen, setEditDialogOpen] = useState(false);
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
const [menuUser, setMenuUser] = useState<User | null>(null);
useEffect(() => { useEffect(() => {
fetchUsers(); fetchUsers();
@@ -92,14 +99,26 @@ export default function UsersPage() {
} }
}; };
const handleOpenMenu = (event: React.MouseEvent<HTMLElement>, user: User) => {
setAnchorEl(event.currentTarget);
setMenuUser(user);
};
const handleCloseMenu = () => {
setAnchorEl(null);
setMenuUser(null);
};
const handleViewUser = (user: User) => { const handleViewUser = (user: User) => {
setSelectedUser(user); setSelectedUser(user);
setViewDialogOpen(true); setViewDialogOpen(true);
handleCloseMenu();
}; };
const handleEditUser = (user: User) => { const handleEditUser = (user: User) => {
setSelectedUser(user); setSelectedUser(user);
setEditDialogOpen(true); setEditDialogOpen(true);
handleCloseMenu();
}; };
const handleToggleUserStatus = async (user: User) => { const handleToggleUserStatus = async (user: User) => {
@@ -108,6 +127,7 @@ export default function UsersPage() {
emailVerified: !user.emailVerified, emailVerified: !user.emailVerified,
}); });
fetchUsers(); fetchUsers();
handleCloseMenu();
} catch (error) { } catch (error) {
console.error('Failed to update user status:', error); console.error('Failed to update user status:', error);
} }
@@ -118,6 +138,7 @@ export default function UsersPage() {
try { try {
await apiClient.delete(`/admin/users/${userId}`); await apiClient.delete(`/admin/users/${userId}`);
fetchUsers(); fetchUsers();
handleCloseMenu();
} catch (error) { } catch (error) {
console.error('Failed to delete user:', error); console.error('Failed to delete user:', error);
} }
@@ -270,32 +291,10 @@ export default function UsersPage() {
<TableCell align="right"> <TableCell align="right">
<IconButton <IconButton
size="small" size="small"
onClick={() => handleViewUser(user)} onClick={(e) => handleOpenMenu(e, user)}
title="View" title="Actions"
> >
<Visibility /> <MoreVert />
</IconButton>
<IconButton
size="small"
onClick={() => handleEditUser(user)}
title="Edit"
>
<Edit />
</IconButton>
<IconButton
size="small"
onClick={() => handleToggleUserStatus(user)}
title={user.emailVerified ? 'Deactivate' : 'Activate'}
>
{user.emailVerified ? <Block /> : <CheckCircle />}
</IconButton>
<IconButton
size="small"
onClick={() => handleDeleteUser(user.id)}
title="Delete"
color="error"
>
<Delete />
</IconButton> </IconButton>
</TableCell> </TableCell>
</TableRow> </TableRow>
@@ -316,6 +315,43 @@ export default function UsersPage() {
/> />
</TableContainer> </TableContainer>
{/* Actions Menu */}
<Menu
anchorEl={anchorEl}
open={Boolean(anchorEl)}
onClose={handleCloseMenu}
>
<MenuItem onClick={() => menuUser && handleViewUser(menuUser)}>
<ListItemIcon>
<Visibility fontSize="small" />
</ListItemIcon>
<ListItemText>View Details</ListItemText>
</MenuItem>
<MenuItem onClick={() => menuUser && handleEditUser(menuUser)}>
<ListItemIcon>
<Edit fontSize="small" />
</ListItemIcon>
<ListItemText>Edit User</ListItemText>
</MenuItem>
<MenuItem onClick={() => menuUser && handleToggleUserStatus(menuUser)}>
<ListItemIcon>
{menuUser?.emailVerified ? <Block fontSize="small" /> : <CheckCircle fontSize="small" />}
</ListItemIcon>
<ListItemText>
{menuUser?.emailVerified ? 'Deactivate' : 'Activate'}
</ListItemText>
</MenuItem>
<MenuItem
onClick={() => menuUser && handleDeleteUser(menuUser.id)}
sx={{ color: 'error.main' }}
>
<ListItemIcon>
<Delete fontSize="small" color="error" />
</ListItemIcon>
<ListItemText>Delete User</ListItemText>
</MenuItem>
</Menu>
{/* View User Dialog */} {/* View User Dialog */}
<Dialog <Dialog
open={viewDialogOpen} open={viewDialogOpen}