mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-09 03:18:44 +00:00
Internal Users: Refresh user list on create (#10296)
* Clean up * Refactor * Internal Users: Refresh user list on create
This commit is contained in:
parent
8d73259561
commit
350bae9be0
5 changed files with 151 additions and 293 deletions
34
ui/litellm-dashboard/package-lock.json
generated
34
ui/litellm-dashboard/package-lock.json
generated
|
|
@ -12,6 +12,7 @@
|
|||
"@headlessui/tailwindcss": "^0.2.0",
|
||||
"@heroicons/react": "^1.0.6",
|
||||
"@remixicon/react": "^4.1.1",
|
||||
"@tanstack/react-pacer": "^0.2.0",
|
||||
"@tanstack/react-query": "^5.64.1",
|
||||
"@tanstack/react-table": "^8.20.6",
|
||||
"@tremor/react": "^3.13.3",
|
||||
|
|
@ -772,6 +773,19 @@
|
|||
"tailwindcss": ">=3.0.0 || >= 3.0.0-alpha.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@tanstack/pacer": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@tanstack/pacer/-/pacer-0.2.0.tgz",
|
||||
"integrity": "sha512-fUJs3NpSwtAL/tfq8kuYdgvm9HbbJvHsOG6aHY2dFDfff0NBFNwjvyGreWZZRPs2zgoIbr4nOk+rRV7aQgmf+A==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/tannerlinsley"
|
||||
}
|
||||
},
|
||||
"node_modules/@tanstack/query-core": {
|
||||
"version": "5.64.1",
|
||||
"resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.64.1.tgz",
|
||||
|
|
@ -781,6 +795,26 @@
|
|||
"url": "https://github.com/sponsors/tannerlinsley"
|
||||
}
|
||||
},
|
||||
"node_modules/@tanstack/react-pacer": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@tanstack/react-pacer/-/react-pacer-0.2.0.tgz",
|
||||
"integrity": "sha512-KU5GtjkKSeNdYCilen5Dc+Pu/6BPQbsQshKrUUjrg7URyJIiGBCz6ZZFre1QjDz/aeUeqUJWMWSm+2Dsh64v+w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@tanstack/pacer": "0.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/tannerlinsley"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=16.8",
|
||||
"react-dom": ">=16.8"
|
||||
}
|
||||
},
|
||||
"node_modules/@tanstack/react-query": {
|
||||
"version": "5.64.1",
|
||||
"resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.64.1.tgz",
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@
|
|||
"@headlessui/tailwindcss": "^0.2.0",
|
||||
"@heroicons/react": "^1.0.6",
|
||||
"@remixicon/react": "^4.1.1",
|
||||
"@tanstack/react-pacer": "^0.2.0",
|
||||
"@tanstack/react-query": "^5.64.1",
|
||||
"@tanstack/react-table": "^8.20.6",
|
||||
"@tremor/react": "^3.13.3",
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ const { Option } = Select;
|
|||
import { Tooltip } from "antd";
|
||||
import { InfoCircleOutlined } from '@ant-design/icons';
|
||||
import { getModelDisplayName } from "./key_team_helpers/fetch_available_models_team_key";
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
|
||||
interface CreateuserProps {
|
||||
userID: string;
|
||||
|
|
@ -50,6 +51,7 @@ const Createuser: React.FC<CreateuserProps> = ({
|
|||
onUserCreated,
|
||||
isEmbedded = false,
|
||||
}) => {
|
||||
const queryClient = useQueryClient();
|
||||
const [uiSettings, setUISettings] = useState<UISettings | null>(null);
|
||||
const [form] = Form.useForm();
|
||||
const [isModalVisible, setIsModalVisible] = useState(false);
|
||||
|
|
@ -130,6 +132,7 @@ const Createuser: React.FC<CreateuserProps> = ({
|
|||
}
|
||||
console.log("formValues in create user:", formValues);
|
||||
const response = await userCreateCall(accessToken, null, formValues);
|
||||
await queryClient.invalidateQueries({ queryKey: ['userList'] })
|
||||
console.log("user create Response:", response);
|
||||
setApiuser(true);
|
||||
const user_id = response.data?.user_id || response.user_id;
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import { all_admin_roles } from "@/utils/roles";
|
|||
import { message } from "antd";
|
||||
import { TagNewRequest, TagUpdateRequest, TagDeleteRequest, TagInfoRequest, TagListResponse, TagInfoResponse } from "./tag_management/types";
|
||||
import { Team } from "./key_team_helpers/key_list";
|
||||
import { UserInfo } from "./view_users/types";
|
||||
|
||||
const isLocal = process.env.NODE_ENV === "development";
|
||||
export const proxyBaseUrl = isLocal ? "http://localhost:4000" : null;
|
||||
|
|
@ -670,6 +671,13 @@ export const teamDeleteCall = async (accessToken: String, teamID: String) => {
|
|||
}
|
||||
};
|
||||
|
||||
export type UserListResponse = {
|
||||
page: number,
|
||||
page_size: number,
|
||||
total: number,
|
||||
total_pages: number,
|
||||
users: UserInfo[]
|
||||
}
|
||||
|
||||
export const userListCall = async (
|
||||
accessToken: String,
|
||||
|
|
@ -748,7 +756,7 @@ export const userListCall = async (
|
|||
throw new Error("Network response was not ok");
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
const data = await response.json() as UserListResponse;
|
||||
console.log("/user/list API Response:", data);
|
||||
return data;
|
||||
// Handle success - you might want to update some state or UI based on the created key
|
||||
|
|
@ -2914,7 +2922,7 @@ export const getPossibleUserRoles = async (accessToken: String) => {
|
|||
const errorData = await response.text();
|
||||
throw new Error("Network response was not ok");
|
||||
}
|
||||
const data = await response.json();
|
||||
const data = await response.json() as Record<string, Record<string, string>>;
|
||||
console.log("response from user/available_role", data);
|
||||
return data;
|
||||
// Handle success - you might want to update some state or UI based on the created key
|
||||
|
|
@ -3600,7 +3608,10 @@ export const userUpdateUserCall = async (
|
|||
throw new Error("Network response was not ok");
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
const data = await response.json() as {
|
||||
user_id: string;
|
||||
data: UserInfo;
|
||||
};
|
||||
console.log("API Response:", data);
|
||||
//message.success("User role updated");
|
||||
return data;
|
||||
|
|
|
|||
|
|
@ -1,58 +1,36 @@
|
|||
import React, { useState, useEffect, useCallback, useRef } from "react";
|
||||
import {
|
||||
Card,
|
||||
Title,
|
||||
Subtitle,
|
||||
Table,
|
||||
TableHead,
|
||||
TableHeaderCell,
|
||||
TableRow,
|
||||
TableCell,
|
||||
TableBody,
|
||||
Tab,
|
||||
Text,
|
||||
TabGroup,
|
||||
TabList,
|
||||
TabPanels,
|
||||
Metric,
|
||||
Grid,
|
||||
TabPanel,
|
||||
Select,
|
||||
SelectItem,
|
||||
Dialog,
|
||||
DialogPanel,
|
||||
Icon,
|
||||
TextInput,
|
||||
NumberInput,
|
||||
} from "@tremor/react";
|
||||
|
||||
import { message } from "antd";
|
||||
import { Modal } from "antd";
|
||||
|
||||
import {
|
||||
userInfoCall,
|
||||
userUpdateUserCall,
|
||||
getPossibleUserRoles,
|
||||
userListCall,
|
||||
UserListResponse,
|
||||
} from "./networking";
|
||||
import { Badge, BadgeDelta, Button } from "@tremor/react";
|
||||
import RequestAccess from "./request_model_access";
|
||||
import { Button } from "@tremor/react";
|
||||
import CreateUser from "./create_user_button";
|
||||
import EditUserModal from "./edit_user";
|
||||
import Paragraph from "antd/es/skeleton/Paragraph";
|
||||
import {
|
||||
PencilAltIcon,
|
||||
InformationCircleIcon,
|
||||
TrashIcon,
|
||||
} from "@heroicons/react/outline";
|
||||
|
||||
import { userDeleteCall } from "./networking";
|
||||
import { columns } from "./view_users/columns";
|
||||
import { UserDataTable } from "./view_users/table";
|
||||
import { UserInfo } from "./view_users/types";
|
||||
import BulkCreateUsers from "./bulk_create_users_button";
|
||||
import SSOSettings from "./SSOSettings";
|
||||
import debounce from "lodash/debounce";
|
||||
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { updateExistingKeys } from "@/utils/dataUtils";
|
||||
import { useDebouncedState } from '@tanstack/react-pacer/debouncer'
|
||||
|
||||
interface ViewUserDashboardProps {
|
||||
accessToken: string | null;
|
||||
|
|
@ -64,22 +42,6 @@ interface ViewUserDashboardProps {
|
|||
setKeys: React.Dispatch<React.SetStateAction<Object[] | null>>;
|
||||
}
|
||||
|
||||
interface UserListResponse {
|
||||
users: any[] | null;
|
||||
total: number;
|
||||
page: number;
|
||||
page_size: number;
|
||||
total_pages: number;
|
||||
}
|
||||
|
||||
interface CreateuserProps {
|
||||
userID: string;
|
||||
accessToken: string;
|
||||
teams: any[];
|
||||
possibleUIRoles: Record<string, Record<string, string>>;
|
||||
onUserCreated: () => Promise<void>;
|
||||
}
|
||||
|
||||
interface FilterState {
|
||||
email: string;
|
||||
user_id: string;
|
||||
|
|
@ -99,144 +61,75 @@ if (isLocal != true) {
|
|||
console.log = function() {};
|
||||
}
|
||||
|
||||
const DEFAULT_PAGE_SIZE = 25;
|
||||
|
||||
const initialFilters: FilterState = {
|
||||
email: "",
|
||||
user_id: "",
|
||||
user_role: "",
|
||||
sso_user_id: "",
|
||||
team: "",
|
||||
model: "",
|
||||
min_spend: null,
|
||||
max_spend: null,
|
||||
sort_by: "created_at",
|
||||
sort_order: "desc"
|
||||
}
|
||||
|
||||
const ViewUserDashboard: React.FC<ViewUserDashboardProps> = ({
|
||||
accessToken,
|
||||
token,
|
||||
keys,
|
||||
userRole,
|
||||
userID,
|
||||
teams,
|
||||
setKeys,
|
||||
}) => {
|
||||
const [userListResponse, setUserListResponse] = useState<UserListResponse | null>(null);
|
||||
const [endUsers, setEndUsers] = useState<null | any[]>(null);
|
||||
const queryClient = useQueryClient();
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [openDialogId, setOpenDialogId] = React.useState<null | number>(null);
|
||||
const [selectedItem, setSelectedItem] = useState<null | any>(null);
|
||||
const [editModalVisible, setEditModalVisible] = useState(false);
|
||||
const [selectedUser, setSelectedUser] = useState<UserInfo | null>(null);
|
||||
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
|
||||
const [userToDelete, setUserToDelete] = useState<string | null>(null);
|
||||
const [possibleUIRoles, setPossibleUIRoles] = useState<
|
||||
Record<string, Record<string, string>>
|
||||
>({});
|
||||
const defaultPageSize = 25;
|
||||
const [searchTerm, setSearchTerm] = useState("");
|
||||
const [activeTab, setActiveTab] = useState("users");
|
||||
const [filters, setFilters] = useState<FilterState>({
|
||||
email: "",
|
||||
user_id: "",
|
||||
user_role: "",
|
||||
sso_user_id: "",
|
||||
team: "",
|
||||
model: "",
|
||||
min_spend: null,
|
||||
max_spend: null,
|
||||
sort_by: "created_at",
|
||||
sort_order: "desc"
|
||||
});
|
||||
const [filters, setFilters] = useState<FilterState>(initialFilters);
|
||||
const [debouncedFilters, setDebouncedFilters, debouncer] = useDebouncedState(filters, { wait: 300 })
|
||||
const [showFilters, setShowFilters] = useState(false);
|
||||
const [showColumnDropdown, setShowColumnDropdown] = useState(false);
|
||||
const [selectedFilter, setSelectedFilter] = useState("Email");
|
||||
const filtersRef = useRef(null);
|
||||
const lastSearchTimestamp = useRef(0);
|
||||
|
||||
// check if window is not undefined
|
||||
if (typeof window !== "undefined") {
|
||||
window.addEventListener("beforeunload", function () {
|
||||
// Clear session storage
|
||||
sessionStorage.clear();
|
||||
});
|
||||
}
|
||||
|
||||
const handleDelete = (userId: string) => {
|
||||
setUserToDelete(userId);
|
||||
setIsDeleteModalOpen(true);
|
||||
};
|
||||
|
||||
const handleFilterChange = (key: keyof FilterState, value: string | number | null) => {
|
||||
const newFilters = { ...filters, [key]: value };
|
||||
setFilters(newFilters);
|
||||
console.log("called from handleFilterChange - newFilters:", JSON.stringify(newFilters));
|
||||
debouncedSearch(newFilters);
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
debouncer.cancel()
|
||||
}
|
||||
}, [debouncer])
|
||||
|
||||
const updateFilters = (update: Partial<FilterState>) => {
|
||||
setFilters((previousFilters) => {
|
||||
const newFilters = {...previousFilters, ...update };
|
||||
setDebouncedFilters(newFilters);
|
||||
return newFilters;
|
||||
})
|
||||
};
|
||||
|
||||
const handleSortChange = (sortBy: string, sortOrder: 'asc' | 'desc') => {
|
||||
const newFilters = {
|
||||
...filters,
|
||||
sort_by: sortBy,
|
||||
sort_order: sortOrder
|
||||
};
|
||||
setFilters(newFilters);
|
||||
debouncedSearch(newFilters);
|
||||
updateFilters({ sort_by: sortBy, sort_order: sortOrder });
|
||||
};
|
||||
|
||||
// Create a debounced version of the search function
|
||||
const debouncedSearch = useCallback(
|
||||
debounce(async (filters: FilterState) => {
|
||||
if (!accessToken || !token || !userRole || !userID) {
|
||||
return;
|
||||
}
|
||||
|
||||
const currentTimestamp = Date.now();
|
||||
lastSearchTimestamp.current = currentTimestamp;
|
||||
|
||||
try {
|
||||
// Make the API call using userListCall with all filter parameters
|
||||
const data = await userListCall(
|
||||
accessToken,
|
||||
filters.user_id ? [filters.user_id] : null,
|
||||
1, // Reset to first page when searching
|
||||
defaultPageSize,
|
||||
filters.email || null,
|
||||
filters.user_role || null,
|
||||
filters.team || null,
|
||||
filters.sso_user_id || null,
|
||||
filters.sort_by,
|
||||
filters.sort_order
|
||||
);
|
||||
|
||||
// Only update state if this is the most recent search
|
||||
if (currentTimestamp === lastSearchTimestamp.current) {
|
||||
if (data) {
|
||||
setUserListResponse(data);
|
||||
console.log("called from debouncedSearch filters:", JSON.stringify(filters));
|
||||
console.log("called from debouncedSearch data:", JSON.stringify(data));
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error searching users:", error);
|
||||
}
|
||||
}, 300),
|
||||
[accessToken, token, userRole, userID]
|
||||
);
|
||||
|
||||
// Cleanup the debounced function on component unmount
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
debouncedSearch.cancel();
|
||||
};
|
||||
}, [debouncedSearch]);
|
||||
|
||||
const handleSearch = (value: string) => {
|
||||
setSearchTerm(value);
|
||||
if (value === "") {
|
||||
refreshUserData(); // Reset to original data when search is cleared
|
||||
} else {
|
||||
debouncedSearch(filters);
|
||||
}
|
||||
};
|
||||
|
||||
const confirmDelete = async () => {
|
||||
if (userToDelete && accessToken) {
|
||||
try {
|
||||
await userDeleteCall(accessToken, [userToDelete]);
|
||||
message.success("User deleted successfully");
|
||||
|
||||
// Update the user list after deletion
|
||||
if (userListResponse) {
|
||||
const updatedUserData = userListResponse.users?.filter(user => user.user_id !== userToDelete);
|
||||
setUserListResponse({ ...userListResponse, users: updatedUserData || [] });
|
||||
}
|
||||
queryClient.setQueriesData<UserListResponse>({ queryKey: ['userList'] }, (previousData) => {
|
||||
if (previousData === undefined) return previousData;
|
||||
const updatedUsers = previousData.users.filter(user => user.user_id !== userToDelete);
|
||||
return { ...previousData, users: updatedUsers };
|
||||
})
|
||||
|
||||
message.success("User deleted successfully");
|
||||
} catch (error) {
|
||||
console.error("Error deleting user:", error);
|
||||
message.error("Failed to delete user");
|
||||
|
|
@ -264,141 +157,68 @@ const ViewUserDashboard: React.FC<ViewUserDashboardProps> = ({
|
|||
}
|
||||
|
||||
try {
|
||||
await userUpdateUserCall(accessToken, editedUser, null);
|
||||
const response = await userUpdateUserCall(accessToken, editedUser, null);
|
||||
queryClient.setQueriesData<UserListResponse>({ queryKey: ['userList'] }, (previousData) => {
|
||||
if (previousData === undefined) return previousData;
|
||||
const updatedUsers = previousData.users.map(user => {
|
||||
if (user.user_id === response.data.user_id) {
|
||||
return updateExistingKeys(user, response.data);
|
||||
}
|
||||
return user;
|
||||
});
|
||||
|
||||
return { ...previousData, users: updatedUsers };
|
||||
})
|
||||
|
||||
message.success(`User ${editedUser.user_id} updated successfully`);
|
||||
} catch (error) {
|
||||
console.error("There was an error updating the user", error);
|
||||
}
|
||||
if (userListResponse) {
|
||||
const updatedUserData = userListResponse.users?.map((user) =>
|
||||
user.user_id === editedUser.user_id ? editedUser : user
|
||||
);
|
||||
setUserListResponse({ ...userListResponse, users: updatedUserData || [] });
|
||||
}
|
||||
setSelectedUser(null);
|
||||
setEditModalVisible(false);
|
||||
// Close the modal
|
||||
};
|
||||
|
||||
const refreshUserData = async () => {
|
||||
console.log("called from refreshUserData");
|
||||
if (!accessToken || !token || !userRole || !userID) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const userDataResponse = await userInfoCall(
|
||||
accessToken,
|
||||
null,
|
||||
userRole,
|
||||
true,
|
||||
currentPage,
|
||||
defaultPageSize
|
||||
);
|
||||
|
||||
// Update session storage with new data
|
||||
sessionStorage.setItem(
|
||||
`userList_${currentPage}`,
|
||||
JSON.stringify(userDataResponse)
|
||||
);
|
||||
console.log("called from refreshUserData");
|
||||
setUserListResponse(userDataResponse);
|
||||
} catch (error) {
|
||||
console.error("Error refreshing user data:", error);
|
||||
}
|
||||
};
|
||||
|
||||
const handlePageChange = async (newPage: number) => {
|
||||
if (!accessToken || !token || !userRole || !userID) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const userDataResponse = await userListCall(
|
||||
accessToken,
|
||||
filters.user_id ? [filters.user_id] : null,
|
||||
newPage,
|
||||
defaultPageSize,
|
||||
filters.email || null,
|
||||
filters.user_role || null,
|
||||
filters.team || null,
|
||||
filters.sso_user_id || null,
|
||||
filters.sort_by,
|
||||
filters.sort_order
|
||||
);
|
||||
|
||||
// Update session storage with new data
|
||||
sessionStorage.setItem(
|
||||
`userList_${newPage}`,
|
||||
JSON.stringify(userDataResponse)
|
||||
);
|
||||
|
||||
setUserListResponse(userDataResponse);
|
||||
setCurrentPage(newPage);
|
||||
} catch (error) {
|
||||
console.error("Error changing page:", error);
|
||||
}
|
||||
setCurrentPage(newPage);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!accessToken || !token || !userRole || !userID) {
|
||||
return;
|
||||
}
|
||||
const fetchData = async () => {
|
||||
try {
|
||||
// Check session storage first
|
||||
const cachedUserData = sessionStorage.getItem(`userList_${currentPage}`);
|
||||
if (cachedUserData) {
|
||||
const parsedData = JSON.parse(cachedUserData);
|
||||
setUserListResponse(parsedData);
|
||||
console.log("called from useEffect");
|
||||
} else {
|
||||
// Fetch from API using userListCall with current filters
|
||||
const userDataResponse = await userListCall(
|
||||
accessToken,
|
||||
filters.user_id ? [filters.user_id] : null,
|
||||
currentPage,
|
||||
defaultPageSize,
|
||||
filters.email || null,
|
||||
filters.user_role || null,
|
||||
filters.team || null,
|
||||
filters.sso_user_id || null,
|
||||
filters.sort_by,
|
||||
filters.sort_order
|
||||
);
|
||||
const userListQuery = useQuery({
|
||||
queryKey: ['userList', { debouncedFilter: debouncedFilters, currentPage }],
|
||||
queryFn: async () => {
|
||||
if (!accessToken) throw new Error('Access token required');
|
||||
|
||||
// Store in session storage
|
||||
sessionStorage.setItem(
|
||||
`userList_${currentPage}`,
|
||||
JSON.stringify(userDataResponse)
|
||||
);
|
||||
return await userListCall(
|
||||
accessToken,
|
||||
debouncedFilters.user_id ? [debouncedFilters.user_id] : null,
|
||||
currentPage,
|
||||
DEFAULT_PAGE_SIZE,
|
||||
debouncedFilters.email || null,
|
||||
debouncedFilters.user_role || null,
|
||||
debouncedFilters.team || null,
|
||||
debouncedFilters.sso_user_id || null,
|
||||
debouncedFilters.sort_by,
|
||||
debouncedFilters.sort_order
|
||||
);
|
||||
},
|
||||
enabled: Boolean(accessToken && token && userRole && userID),
|
||||
placeholderData: (previousData) => previousData
|
||||
});
|
||||
const userListResponse = userListQuery.data
|
||||
|
||||
setUserListResponse(userDataResponse);
|
||||
console.log("called from useEffect 2");
|
||||
}
|
||||
const userRolesQuery = useQuery<Record<string, Record<string, string>>>({
|
||||
queryKey: ['userRoles'],
|
||||
initialData: () => ({}),
|
||||
queryFn: async () => {
|
||||
if (!accessToken) throw new Error('Access token required');
|
||||
return await getPossibleUserRoles(accessToken);
|
||||
},
|
||||
enabled: Boolean(accessToken && token && userRole && userID),
|
||||
});
|
||||
const possibleUIRoles = userRolesQuery.data
|
||||
|
||||
// Fetch roles if not cached
|
||||
const cachedRoles = sessionStorage.getItem('possibleUserRoles');
|
||||
if (cachedRoles) {
|
||||
setPossibleUIRoles(JSON.parse(cachedRoles));
|
||||
} else {
|
||||
const availableUserRoles = await getPossibleUserRoles(accessToken);
|
||||
sessionStorage.setItem('possibleUserRoles', JSON.stringify(availableUserRoles));
|
||||
setPossibleUIRoles(availableUserRoles);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("There was an error fetching the model data", error);
|
||||
}
|
||||
};
|
||||
|
||||
if (accessToken && token && userRole && userID) {
|
||||
fetchData();
|
||||
}
|
||||
|
||||
}, [accessToken, token, userRole, userID]);
|
||||
|
||||
if (!userListResponse) {
|
||||
return <div>Loading...</div>;
|
||||
if (userListQuery.isLoading) {
|
||||
return <div>Loading...</div>
|
||||
}
|
||||
|
||||
if (!accessToken || !token || !userRole || !userID) {
|
||||
|
|
@ -448,7 +268,7 @@ const ViewUserDashboard: React.FC<ViewUserDashboardProps> = ({
|
|||
placeholder="Search by email..."
|
||||
className="w-full px-3 py-2 pl-8 border rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
||||
value={filters.email}
|
||||
onChange={(e) => handleFilterChange('email', e.target.value)}
|
||||
onChange={(e) => updateFilters({ email: e.target.value })}
|
||||
/>
|
||||
<svg
|
||||
className="absolute left-2.5 top-2.5 h-4 w-4 text-gray-500"
|
||||
|
|
@ -493,18 +313,7 @@ const ViewUserDashboard: React.FC<ViewUserDashboardProps> = ({
|
|||
<button
|
||||
className="px-3 py-2 text-sm border rounded-md hover:bg-gray-50 flex items-center gap-2"
|
||||
onClick={() => {
|
||||
setFilters({
|
||||
email: "",
|
||||
user_id: "",
|
||||
user_role: "",
|
||||
team: "",
|
||||
sso_user_id: "",
|
||||
model: "",
|
||||
min_spend: null,
|
||||
max_spend: null,
|
||||
sort_by: "created_at",
|
||||
sort_order: "desc"
|
||||
});
|
||||
updateFilters(initialFilters);
|
||||
}}
|
||||
>
|
||||
<svg
|
||||
|
|
@ -534,7 +343,7 @@ const ViewUserDashboard: React.FC<ViewUserDashboardProps> = ({
|
|||
placeholder="Filter by User ID"
|
||||
className="w-full px-3 py-2 pl-8 border rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
||||
value={filters.user_id}
|
||||
onChange={(e) => handleFilterChange('user_id', e.target.value)}
|
||||
onChange={(e) => updateFilters({ user_id : e.target.value })}
|
||||
/>
|
||||
<svg
|
||||
className="absolute left-2.5 top-2.5 h-4 w-4 text-gray-500"
|
||||
|
|
@ -555,7 +364,7 @@ const ViewUserDashboard: React.FC<ViewUserDashboardProps> = ({
|
|||
<div className="w-64">
|
||||
<Select
|
||||
value={filters.user_role}
|
||||
onValueChange={(value) => handleFilterChange('user_role', value)}
|
||||
onValueChange={(value) => updateFilters({ user_role: value })}
|
||||
placeholder="Select Role"
|
||||
>
|
||||
{Object.entries(possibleUIRoles).map(([key, value]) => (
|
||||
|
|
@ -570,7 +379,7 @@ const ViewUserDashboard: React.FC<ViewUserDashboardProps> = ({
|
|||
<div className="w-64">
|
||||
<Select
|
||||
value={filters.team}
|
||||
onValueChange={(value) => handleFilterChange('team', value)}
|
||||
onValueChange={(value) => updateFilters({ team: value })}
|
||||
placeholder="Select Team"
|
||||
>
|
||||
{teams?.map((team) => (
|
||||
|
|
@ -588,7 +397,7 @@ const ViewUserDashboard: React.FC<ViewUserDashboardProps> = ({
|
|||
placeholder="Filter by SSO ID"
|
||||
className="w-full px-3 py-2 pl-8 border rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
||||
value={filters.sso_user_id}
|
||||
onChange={(e) => handleFilterChange('sso_user_id', e.target.value)}
|
||||
onChange={(e) => updateFilters({ sso_user_id : e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -641,9 +450,9 @@ const ViewUserDashboard: React.FC<ViewUserDashboardProps> = ({
|
|||
</div>
|
||||
|
||||
<UserDataTable
|
||||
data={userListResponse?.users || []}
|
||||
data={userListQuery.data?.users || []}
|
||||
columns={tableColumns}
|
||||
isLoading={!userListResponse}
|
||||
isLoading={userListQuery.isLoading}
|
||||
accessToken={accessToken}
|
||||
userRole={userRole}
|
||||
onSortChange={handleSortChange}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue