user tab enhanced and user search bug fixed
This commit is contained in:
parent
f022cbf899
commit
891096a184
@ -67,13 +67,21 @@ export function UserRoleManager() {
|
||||
const [updating, setUpdating] = useState(false);
|
||||
const [message, setMessage] = useState<{ type: 'success' | 'error'; text: string } | null>(null);
|
||||
|
||||
// Users with elevated roles
|
||||
const [elevatedUsers, setElevatedUsers] = useState<UserWithRole[]>([]);
|
||||
// Users list with filtering and pagination
|
||||
const [users, setUsers] = useState<UserWithRole[]>([]);
|
||||
const [loadingUsers, setLoadingUsers] = useState(false);
|
||||
const [roleStats, setRoleStats] = useState({ admins: 0, management: 0, users: 0 });
|
||||
|
||||
// Ref for search container (click outside to close)
|
||||
// Pagination and filtering
|
||||
const [roleFilter, setRoleFilter] = useState<'ELEVATED' | 'ALL' | 'ADMIN' | 'MANAGEMENT' | 'USER'>('ELEVATED');
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [totalPages, setTotalPages] = useState(1);
|
||||
const [totalUsers, setTotalUsers] = useState(0);
|
||||
const limit = 10;
|
||||
|
||||
// Refs for search container and user list
|
||||
const searchContainerRef = useRef<HTMLDivElement>(null);
|
||||
const userListRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
// Search users from Okta
|
||||
const searchUsers = useCallback(
|
||||
@ -134,7 +142,7 @@ export function UserRoleManager() {
|
||||
|
||||
try {
|
||||
// Call backend to assign role (will create user if doesn't exist)
|
||||
const response = await userApi.assignRole(selectedUser.email, selectedRole);
|
||||
await userApi.assignRole(selectedUser.email, selectedRole);
|
||||
|
||||
setMessage({
|
||||
type: 'success',
|
||||
@ -146,8 +154,8 @@ export function UserRoleManager() {
|
||||
setSearchQuery('');
|
||||
setSelectedRole('USER');
|
||||
|
||||
// Refresh the elevated users list
|
||||
await fetchElevatedUsers();
|
||||
// Refresh the users list
|
||||
await fetchUsers();
|
||||
await fetchRoleStatistics();
|
||||
} catch (error: any) {
|
||||
console.error('Role assignment failed:', error);
|
||||
@ -160,26 +168,39 @@ export function UserRoleManager() {
|
||||
}
|
||||
};
|
||||
|
||||
// Fetch users with ADMIN and MANAGEMENT roles
|
||||
const fetchElevatedUsers = async () => {
|
||||
// Fetch users with filtering and pagination
|
||||
const fetchUsers = async (page: number = currentPage) => {
|
||||
setLoadingUsers(true);
|
||||
try {
|
||||
const [adminResponse, managementResponse] = await Promise.all([
|
||||
userApi.getUsersByRole('ADMIN'),
|
||||
userApi.getUsersByRole('MANAGEMENT')
|
||||
]);
|
||||
const response = await userApi.getUsersByRole(roleFilter, page, limit);
|
||||
|
||||
console.log('Admin response:', adminResponse);
|
||||
console.log('Management response:', managementResponse);
|
||||
console.log('Users response:', response);
|
||||
|
||||
// Backend returns { success: true, data: { users: [...], summary: {...} } }
|
||||
const admins = adminResponse.data?.data?.users || [];
|
||||
const managers = managementResponse.data?.data?.users || [];
|
||||
// Backend returns { success: true, data: { users: [...], pagination: {...}, summary: {...} } }
|
||||
const usersData = response.data?.data?.users || [];
|
||||
const paginationData = response.data?.data?.pagination;
|
||||
const summaryData = response.data?.data?.summary;
|
||||
|
||||
console.log('Parsed admins:', admins);
|
||||
console.log('Parsed managers:', managers);
|
||||
console.log('Parsed users:', usersData);
|
||||
console.log('Pagination:', paginationData);
|
||||
console.log('Summary:', summaryData);
|
||||
|
||||
setElevatedUsers([...admins, ...managers]);
|
||||
setUsers(usersData);
|
||||
|
||||
if (paginationData) {
|
||||
setCurrentPage(paginationData.currentPage);
|
||||
setTotalPages(paginationData.totalPages);
|
||||
setTotalUsers(paginationData.totalUsers);
|
||||
}
|
||||
|
||||
// Update summary stats if available
|
||||
if (summaryData) {
|
||||
setRoleStats({
|
||||
admins: summaryData.ADMIN || 0,
|
||||
management: summaryData.MANAGEMENT || 0,
|
||||
users: summaryData.USER || 0
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch users:', error);
|
||||
} finally {
|
||||
@ -207,11 +228,39 @@ export function UserRoleManager() {
|
||||
}
|
||||
};
|
||||
|
||||
// Load data on mount
|
||||
// Load data on mount and when filter changes
|
||||
useEffect(() => {
|
||||
fetchElevatedUsers();
|
||||
fetchUsers(1); // Reset to page 1 when filter changes
|
||||
fetchRoleStatistics();
|
||||
}, []);
|
||||
}, [roleFilter]);
|
||||
|
||||
// Handle filter change
|
||||
const handleFilterChange = (value: string) => {
|
||||
setRoleFilter(value as any);
|
||||
setCurrentPage(1);
|
||||
};
|
||||
|
||||
// Handle page change
|
||||
const handlePageChange = (page: number) => {
|
||||
fetchUsers(page);
|
||||
};
|
||||
|
||||
// Handle statistics card click - filter and scroll to user list
|
||||
const handleStatCardClick = (filter: 'ADMIN' | 'MANAGEMENT' | 'USER') => {
|
||||
setRoleFilter(filter);
|
||||
setCurrentPage(1);
|
||||
|
||||
// Immediate scroll without waiting for data load
|
||||
requestAnimationFrame(() => {
|
||||
const element = userListRef.current;
|
||||
if (element) {
|
||||
element.scrollIntoView({
|
||||
behavior: 'smooth',
|
||||
block: 'start'
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// Handle click outside to close search results
|
||||
useEffect(() => {
|
||||
@ -256,13 +305,21 @@ export function UserRoleManager() {
|
||||
<div className="space-y-6">
|
||||
{/* Statistics Cards */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 sm:gap-6">
|
||||
<Card className="shadow-lg border-0 bg-gradient-to-br from-yellow-50 to-yellow-100/50 hover:shadow-xl transition-all rounded-xl" data-testid="admin-count-card">
|
||||
<Card
|
||||
className={`border-2 bg-gradient-to-br from-yellow-50 to-yellow-100/50 hover:shadow-lg transition-all rounded-xl cursor-pointer ${
|
||||
roleFilter === 'ADMIN' ? 'border-yellow-400 shadow-lg' : 'border-transparent shadow-md'
|
||||
}`}
|
||||
data-testid="admin-count-card"
|
||||
onClick={() => handleStatCardClick('ADMIN')}
|
||||
>
|
||||
<CardContent className="p-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-xs font-semibold text-gray-600 uppercase tracking-wide">Administrators</p>
|
||||
<p className="text-3xl font-bold text-gray-900 mt-2" data-testid="admin-count">{roleStats.admins}</p>
|
||||
<p className="text-xs text-gray-500 mt-1">Full system access</p>
|
||||
<p className="text-xs text-yellow-700 mt-1 font-semibold">
|
||||
{roleFilter === 'ADMIN' ? '✓ Viewing' : 'Click to view'}
|
||||
</p>
|
||||
</div>
|
||||
<div className="p-3 bg-gradient-to-br from-yellow-400 to-yellow-500 rounded-xl shadow-md">
|
||||
<Crown className="w-6 h-6 text-slate-900" />
|
||||
@ -271,13 +328,21 @@ export function UserRoleManager() {
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card className="shadow-lg border-0 bg-gradient-to-br from-blue-50 to-blue-100/50 hover:shadow-xl transition-all rounded-xl" data-testid="management-count-card">
|
||||
<Card
|
||||
className={`border-2 bg-gradient-to-br from-blue-50 to-blue-100/50 hover:shadow-lg transition-all rounded-xl cursor-pointer ${
|
||||
roleFilter === 'MANAGEMENT' ? 'border-blue-400 shadow-lg' : 'border-transparent shadow-md'
|
||||
}`}
|
||||
data-testid="management-count-card"
|
||||
onClick={() => handleStatCardClick('MANAGEMENT')}
|
||||
>
|
||||
<CardContent className="p-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-xs font-semibold text-gray-600 uppercase tracking-wide">Management</p>
|
||||
<p className="text-3xl font-bold text-gray-900 mt-2" data-testid="management-count">{roleStats.management}</p>
|
||||
<p className="text-xs text-gray-500 mt-1">Read all data access</p>
|
||||
<p className="text-xs text-blue-700 mt-1 font-semibold">
|
||||
{roleFilter === 'MANAGEMENT' ? '✓ Viewing' : 'Click to view'}
|
||||
</p>
|
||||
</div>
|
||||
<div className="p-3 bg-gradient-to-br from-blue-400 to-blue-500 rounded-xl shadow-md">
|
||||
<Users className="w-6 h-6 text-slate-900" />
|
||||
@ -286,13 +351,21 @@ export function UserRoleManager() {
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card className="shadow-lg border-0 bg-gradient-to-br from-gray-50 to-gray-100/50 hover:shadow-xl transition-all rounded-xl" data-testid="user-count-card">
|
||||
<Card
|
||||
className={`border-2 bg-gradient-to-br from-gray-50 to-gray-100/50 hover:shadow-lg transition-all rounded-xl cursor-pointer ${
|
||||
roleFilter === 'USER' ? 'border-gray-400 shadow-lg' : 'border-transparent shadow-md'
|
||||
}`}
|
||||
data-testid="user-count-card"
|
||||
onClick={() => handleStatCardClick('USER')}
|
||||
>
|
||||
<CardContent className="p-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-xs font-semibold text-gray-600 uppercase tracking-wide">Regular Users</p>
|
||||
<p className="text-3xl font-bold text-gray-900 mt-2" data-testid="user-count">{roleStats.users}</p>
|
||||
<p className="text-xs text-gray-500 mt-1">Standard access</p>
|
||||
<p className="text-xs text-gray-700 mt-1 font-semibold">
|
||||
{roleFilter === 'USER' ? '✓ Viewing' : 'Click to view'}
|
||||
</p>
|
||||
</div>
|
||||
<div className="p-3 bg-gradient-to-br from-gray-400 to-gray-500 rounded-xl shadow-md">
|
||||
<UserIcon className="w-6 h-6 text-white" />
|
||||
@ -328,7 +401,7 @@ export function UserRoleManager() {
|
||||
placeholder="Type name or email address..."
|
||||
value={searchQuery}
|
||||
onChange={handleSearchChange}
|
||||
className="pl-10 pr-10 h-12 border-2 rounded-lg focus:border-purple-500 focus:ring-2 focus:ring-purple-200 transition-all"
|
||||
className="pl-10 pr-10 h-12 border rounded-lg border-gray-300 focus:border-purple-500 focus:ring-2 focus:ring-purple-200 transition-all"
|
||||
data-testid="user-search-input"
|
||||
/>
|
||||
{searching && (
|
||||
@ -339,13 +412,13 @@ export function UserRoleManager() {
|
||||
|
||||
{/* Search Results Dropdown */}
|
||||
{searchResults.length > 0 && (
|
||||
<div className="border-2 border-purple-200 rounded-lg shadow-lg bg-white max-h-60 overflow-y-auto">
|
||||
<div className="border border-purple-200 rounded-lg shadow-lg bg-white max-h-60 overflow-y-auto">
|
||||
<div className="sticky top-0 bg-purple-50 px-4 py-2 border-b border-purple-100">
|
||||
<p className="text-xs font-semibold text-purple-700">
|
||||
{searchResults.length} user{searchResults.length > 1 ? 's' : ''} found
|
||||
</p>
|
||||
</div>
|
||||
<div className="p-2">
|
||||
<div className="p-3">
|
||||
{searchResults.map((user) => (
|
||||
<button
|
||||
key={user.userId}
|
||||
@ -407,25 +480,25 @@ export function UserRoleManager() {
|
||||
<label className="text-sm font-medium text-gray-700">Select Role</label>
|
||||
<Select value={selectedRole} onValueChange={(value: any) => setSelectedRole(value)}>
|
||||
<SelectTrigger
|
||||
className="h-12 border-2 rounded-lg focus:border-purple-500 focus:ring-2 focus:ring-purple-200 transition-all"
|
||||
className="h-12 border border-gray-300 py-2 rounded-lg focus:border-purple-500 focus:ring-1 focus:ring-purple-200 transition-all"
|
||||
data-testid="role-select"
|
||||
>
|
||||
<SelectValue placeholder="Select role" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="USER">
|
||||
<SelectContent className="rounded-lg">
|
||||
<SelectItem value="USER" className="p-3 rounded-lg my-1">
|
||||
<div className="flex items-center gap-2">
|
||||
<UserIcon className="w-4 h-4 text-gray-600" />
|
||||
<span>User - Regular access</span>
|
||||
</div>
|
||||
</SelectItem>
|
||||
<SelectItem value="MANAGEMENT">
|
||||
<SelectItem value="MANAGEMENT" className="p-3 rounded-lg my-1">
|
||||
<div className="flex items-center gap-2">
|
||||
<Users className="w-4 h-4 text-blue-600" />
|
||||
<span>Management - Read all data</span>
|
||||
</div>
|
||||
</SelectItem>
|
||||
<SelectItem value="ADMIN">
|
||||
<SelectItem value="ADMIN" className="p-3 rounded-lg my-1">
|
||||
<div className="flex items-center gap-2">
|
||||
<Crown className="w-4 h-4 text-yellow-600" />
|
||||
<span>Administrator - Full access</span>
|
||||
@ -477,24 +550,61 @@ export function UserRoleManager() {
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Elevated Users List */}
|
||||
<Card className="shadow-lg border">
|
||||
<CardHeader className="border-b pb-4">
|
||||
<div className="flex items-center justify-between">
|
||||
{/* Users List with Filter and Pagination */}
|
||||
<div ref={userListRef}>
|
||||
<Card className="shadow-lg border">
|
||||
<CardHeader className="border-b pb-4">
|
||||
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-4">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="p-3 bg-gradient-to-br from-indigo-500 to-indigo-600 rounded-lg shadow-md">
|
||||
<Shield className="w-5 h-5 text-white" />
|
||||
</div>
|
||||
<div>
|
||||
<CardTitle className="text-lg font-semibold">Users with Elevated Roles</CardTitle>
|
||||
<CardTitle className="text-lg font-semibold">User Management</CardTitle>
|
||||
<CardDescription className="text-sm">
|
||||
Administrators and Management team members
|
||||
View and manage user roles ({totalUsers} {roleFilter !== 'ALL' && roleFilter !== 'ELEVATED' ? roleFilter.toLowerCase() : ''} users)
|
||||
</CardDescription>
|
||||
</div>
|
||||
</div>
|
||||
<Badge variant="outline" className="text-sm">
|
||||
{elevatedUsers.length} user{elevatedUsers.length !== 1 ? 's' : ''}
|
||||
</Badge>
|
||||
<div className="flex items-center gap-3">
|
||||
<Select value={roleFilter} onValueChange={handleFilterChange}>
|
||||
<SelectTrigger className="w-[200px] h-10 border rounded-lg border-gray-300">
|
||||
<SelectValue placeholder="Filter by role" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="ELEVATED">
|
||||
<div className="flex items-center gap-2">
|
||||
<Shield className="w-4 h-4 text-purple-600" />
|
||||
<span>Elevated ({roleStats.admins + roleStats.management})</span>
|
||||
</div>
|
||||
</SelectItem>
|
||||
<SelectItem value="ADMIN">
|
||||
<div className="flex items-center gap-2">
|
||||
<Crown className="w-4 h-4 text-yellow-600" />
|
||||
<span>Admins ({roleStats.admins})</span>
|
||||
</div>
|
||||
</SelectItem>
|
||||
<SelectItem value="MANAGEMENT">
|
||||
<div className="flex items-center gap-2">
|
||||
<Users className="w-4 h-4 text-blue-600" />
|
||||
<span>Management ({roleStats.management})</span>
|
||||
</div>
|
||||
</SelectItem>
|
||||
<SelectItem value="USER">
|
||||
<div className="flex items-center gap-2">
|
||||
<UserIcon className="w-4 h-4 text-gray-600" />
|
||||
<span>Users ({roleStats.users})</span>
|
||||
</div>
|
||||
</SelectItem>
|
||||
<SelectItem value="ALL">
|
||||
<div className="flex items-center gap-2">
|
||||
<Users className="w-4 h-4 text-gray-600" />
|
||||
<span>All Users ({roleStats.admins + roleStats.management + roleStats.users})</span>
|
||||
</div>
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="pt-6">
|
||||
@ -503,47 +613,114 @@ export function UserRoleManager() {
|
||||
<Loader2 className="w-6 h-6 animate-spin text-purple-500 mb-2" />
|
||||
<p className="text-sm text-gray-500">Loading users...</p>
|
||||
</div>
|
||||
) : elevatedUsers.length === 0 ? (
|
||||
) : users.length === 0 ? (
|
||||
<div className="text-center py-8">
|
||||
<div className="w-12 h-12 rounded-full bg-gray-100 flex items-center justify-center mx-auto mb-3">
|
||||
<Users className="w-6 h-6 text-gray-400" />
|
||||
</div>
|
||||
<p className="font-medium text-gray-700">No elevated users found</p>
|
||||
<p className="text-sm text-gray-500 mt-1">Assign ADMIN or MANAGEMENT roles to see users here</p>
|
||||
<p className="font-medium text-gray-700">No users found</p>
|
||||
<p className="text-sm text-gray-500 mt-1">
|
||||
{roleFilter === 'ELEVATED'
|
||||
? 'Assign ADMIN or MANAGEMENT roles to see users here'
|
||||
: 'No users match the selected filter'
|
||||
}
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-2 max-h-96 overflow-y-auto pr-2" data-testid="elevated-users-list">
|
||||
{elevatedUsers.map((user) => (
|
||||
<div
|
||||
key={user.userId}
|
||||
className="border-2 border-gray-100 hover:border-purple-200 hover:shadow-md transition-all rounded-lg bg-white p-4"
|
||||
data-testid={`elevated-user-${user.email}`}
|
||||
>
|
||||
<div className="flex items-center justify-between gap-4">
|
||||
<div className="flex items-center gap-3 flex-1 min-w-0">
|
||||
<div className={`w-10 h-10 rounded-lg ${getRoleBadgeColor(user.role)} flex items-center justify-center shadow-sm`}>
|
||||
{getRoleIcon(user.role)}
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="font-semibold text-gray-900 truncate">{user.displayName}</p>
|
||||
<p className="text-sm text-gray-600 truncate">{user.email}</p>
|
||||
{user.department && (
|
||||
<p className="text-xs text-gray-500 mt-1 truncate">
|
||||
{user.department}{user.designation ? ` • ${user.designation}` : ''}
|
||||
</p>
|
||||
)}
|
||||
<>
|
||||
<div className="space-y-2" data-testid="users-list">
|
||||
{users.map((user) => (
|
||||
<div
|
||||
key={user.userId}
|
||||
className="border-2 border-gray-100 hover:border-purple-200 hover:shadow-md transition-all rounded-lg bg-white p-4"
|
||||
data-testid={`user-${user.email}`}
|
||||
>
|
||||
<div className="flex items-center justify-between gap-4">
|
||||
<div className="flex items-center gap-3 flex-1 min-w-0">
|
||||
<div className={`w-10 h-10 rounded-lg ${getRoleBadgeColor(user.role)} flex items-center justify-center shadow-sm`}>
|
||||
{getRoleIcon(user.role)}
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="font-semibold text-gray-900 truncate">{user.displayName}</p>
|
||||
<p className="text-sm text-gray-600 truncate">{user.email}</p>
|
||||
{user.department && (
|
||||
<p className="text-xs text-gray-500 mt-1 truncate">
|
||||
{user.department}{user.designation ? ` • ${user.designation}` : ''}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<Badge className={`${getRoleBadgeColor(user.role)} shrink-0`} data-testid={`role-badge-${user.role}`}>
|
||||
{user.role}
|
||||
</Badge>
|
||||
</div>
|
||||
<Badge className={`${getRoleBadgeColor(user.role)} shrink-0`} data-testid={`role-badge-${user.role}`}>
|
||||
{user.role}
|
||||
</Badge>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Pagination Controls */}
|
||||
{totalPages > 1 && (
|
||||
<div className="flex items-center justify-between pt-4 border-t">
|
||||
<div className="text-sm text-gray-600">
|
||||
Showing {((currentPage - 1) * limit) + 1} to {Math.min(currentPage * limit, totalUsers)} of {totalUsers} users
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => handlePageChange(currentPage - 1)}
|
||||
disabled={currentPage === 1}
|
||||
data-testid="prev-page-button"
|
||||
>
|
||||
Previous
|
||||
</Button>
|
||||
<div className="flex items-center gap-1">
|
||||
{Array.from({ length: Math.min(5, totalPages) }, (_, i) => {
|
||||
let pageNum;
|
||||
if (totalPages <= 5) {
|
||||
pageNum = i + 1;
|
||||
} else if (currentPage <= 3) {
|
||||
pageNum = i + 1;
|
||||
} else if (currentPage >= totalPages - 2) {
|
||||
pageNum = totalPages - 4 + i;
|
||||
} else {
|
||||
pageNum = currentPage - 2 + i;
|
||||
}
|
||||
return (
|
||||
<Button
|
||||
key={pageNum}
|
||||
variant={currentPage === pageNum ? "default" : "outline"}
|
||||
size="sm"
|
||||
onClick={() => handlePageChange(pageNum)}
|
||||
className={`w-9 h-9 p-0 ${
|
||||
currentPage === pageNum
|
||||
? 'bg-purple-500 hover:bg-purple-600'
|
||||
: ''
|
||||
}`}
|
||||
data-testid={`page-${pageNum}-button`}
|
||||
>
|
||||
{pageNum}
|
||||
</Button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => handlePageChange(currentPage + 1)}
|
||||
disabled={currentPage === totalPages}
|
||||
data-testid="next-page-button"
|
||||
>
|
||||
Next
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -189,7 +189,9 @@ export function AddApproverModal({
|
||||
// If user was NOT selected via @ search, validate against Okta
|
||||
if (!selectedUser || selectedUser.email.toLowerCase() !== emailToAdd) {
|
||||
try {
|
||||
const searchOktaResults = await searchUsers(emailToAdd, 1);
|
||||
const response = await searchUsers(emailToAdd, 1);
|
||||
// Backend returns { success: true, data: [...users] }
|
||||
const searchOktaResults = response.data?.data || [];
|
||||
|
||||
if (searchOktaResults.length === 0) {
|
||||
// User not found in Okta
|
||||
@ -310,7 +312,9 @@ export function AddApproverModal({
|
||||
searchTimer.current = setTimeout(async () => {
|
||||
try {
|
||||
const term = value.slice(1); // Remove @ prefix
|
||||
const results = await searchUsers(term, 10);
|
||||
const response = await searchUsers(term, 10);
|
||||
// Backend returns { success: true, data: [...users] }
|
||||
const results = response.data?.data || [];
|
||||
setSearchResults(results);
|
||||
} catch (error) {
|
||||
console.error('Search failed:', error);
|
||||
@ -352,7 +356,7 @@ export function AddApproverModal({
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={handleClose}>
|
||||
<DialogContent className="sm:max-w-md max-h-[90vh] flex flex-col p-0">
|
||||
<DialogContent className="sm:max-w-md min-h-[60vh] max-h-[90vh] flex flex-col p-0">
|
||||
<button
|
||||
onClick={handleClose}
|
||||
className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none z-50"
|
||||
|
||||
@ -116,7 +116,9 @@ export function AddSpectatorModal({
|
||||
// If user was NOT selected via @ search, validate against Okta
|
||||
if (!selectedUser || selectedUser.email.toLowerCase() !== emailToAdd) {
|
||||
try {
|
||||
const searchOktaResults = await searchUsers(emailToAdd, 1);
|
||||
const response = await searchUsers(emailToAdd, 1);
|
||||
// Backend returns { success: true, data: [...users] }
|
||||
const searchOktaResults = response.data?.data || [];
|
||||
|
||||
if (searchOktaResults.length === 0) {
|
||||
// User not found in Okta
|
||||
@ -223,7 +225,9 @@ export function AddSpectatorModal({
|
||||
searchTimer.current = setTimeout(async () => {
|
||||
try {
|
||||
const term = value.slice(1); // Remove @ prefix
|
||||
const results = await searchUsers(term, 10);
|
||||
const response = await searchUsers(term, 10);
|
||||
// Backend returns { success: true, data: [...users] }
|
||||
const results = response.data?.data || [];
|
||||
setSearchResults(results);
|
||||
} catch (error) {
|
||||
console.error('Search failed:', error);
|
||||
@ -265,7 +269,7 @@ export function AddSpectatorModal({
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={handleClose}>
|
||||
<DialogContent className="sm:max-w-md max-h-[90vh] flex flex-col p-0">
|
||||
<DialogContent className="sm:max-w-md min-h-[60vh] max-h-[90vh] flex flex-col p-0">
|
||||
<button
|
||||
onClick={handleClose}
|
||||
className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none z-50"
|
||||
|
||||
@ -475,7 +475,9 @@ export function CreateRequest({ onBack, onSubmit, requestId: propRequestId, isEd
|
||||
|
||||
// Search for the user by email in Okta directory
|
||||
try {
|
||||
const searchResults = await searchUsers(approver.email, 1);
|
||||
const response = await searchUsers(approver.email, 1);
|
||||
// Backend returns { success: true, data: [...users] }
|
||||
const searchResults = response.data?.data || [];
|
||||
|
||||
if (searchResults.length === 0) {
|
||||
// User NOT found in Okta directory
|
||||
@ -700,7 +702,9 @@ export function CreateRequest({ onBack, onSubmit, requestId: propRequestId, isEd
|
||||
|
||||
// Search for user in Okta directory
|
||||
try {
|
||||
const searchResults = await searchUsers(emailInput, 1);
|
||||
const response = await searchUsers(emailInput, 1);
|
||||
// Backend returns { success: true, data: [...users] }
|
||||
const searchResults = response.data?.data || [];
|
||||
|
||||
if (searchResults.length === 0) {
|
||||
// User NOT found in Okta directory
|
||||
@ -1699,7 +1703,9 @@ export function CreateRequest({ onBack, onSubmit, requestId: propRequestId, isEd
|
||||
searchTimers.current[index] = setTimeout(async () => {
|
||||
try {
|
||||
const term = value.slice(1); // remove leading '@'
|
||||
const results = await searchUsers(term, 10);
|
||||
const response = await searchUsers(term, 10);
|
||||
// Backend returns { success: true, data: [...users] }
|
||||
const results = response.data?.data || [];
|
||||
setUserSearchResults(prev => ({ ...prev, [index]: results }));
|
||||
} catch (err) {
|
||||
setUserSearchResults(prev => ({ ...prev, [index]: [] }));
|
||||
@ -2098,8 +2104,10 @@ export function CreateRequest({ onBack, onSubmit, requestId: propRequestId, isEd
|
||||
spectatorTimer.current = setTimeout(async () => {
|
||||
try {
|
||||
const term = value.slice(1);
|
||||
const res = await searchUsers(term, 10);
|
||||
setSpectatorSearchResults(res);
|
||||
const response = await searchUsers(term, 10);
|
||||
// Backend returns { success: true, data: [...users] }
|
||||
const results = response.data?.data || [];
|
||||
setSpectatorSearchResults(results);
|
||||
} catch {
|
||||
setSpectatorSearchResults([]);
|
||||
} finally {
|
||||
|
||||
@ -49,10 +49,16 @@ export async function updateUserRole(userId: string, role: 'USER' | 'MANAGEMENT'
|
||||
}
|
||||
|
||||
/**
|
||||
* Get users by role
|
||||
* Get users by role (with pagination)
|
||||
*/
|
||||
export async function getUsersByRole(role: 'USER' | 'MANAGEMENT' | 'ADMIN') {
|
||||
return await apiClient.get('/admin/users/by-role', { params: { role } });
|
||||
export async function getUsersByRole(
|
||||
role?: 'USER' | 'MANAGEMENT' | 'ADMIN' | 'ALL' | 'ELEVATED',
|
||||
page: number = 1,
|
||||
limit: number = 10
|
||||
) {
|
||||
return await apiClient.get('/admin/users/by-role', {
|
||||
params: { role: role || 'ELEVATED', page, limit }
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Loading…
Reference in New Issue
Block a user