import { useEffect, useMemo, useState, useDeferredValue } from "react"; import { useLocalSearchParams } from "expo-router"; import { View, Text } from "react-native"; import { UserAccountsTable } from "@/components/features/users/UserAccountsTable"; import { UserModal } from "@/components/features/users/UserModal"; import { User } from "@/store/models/User.model"; import { LoadingSpinner } from "@/components/ui/LoadingSpinner"; import { useDeleteUserMutation, useGetUsersQuery, useLockUserMutation, useUpdateUserMutation, useDeleteUsersMutation, useCreateUserMutation, } from "@/store/api/usersApi"; import { BulkDeleteUserModal } from "@/components/features/users/BulkDeleteUserModal"; import { LockUserModal } from "@/components/features/users/LockUserModal"; import { DeleteUserModal } from "@/components/features/users/DeleteUserModal"; import { NoData } from "@/components/ui/NoData"; import { TableSortDirection } from "@/constants/tableSortDirectionEnum"; import { RiskEnum } from "@/constants/riskEnum"; import { useAuthContext } from "@/auth/AuthContext"; import { OperationMode } from "@/constants/operationModeEnum"; import { useAppSelector } from "@/store/hooks"; import { selectIsMobile } from "@/store/screen/screenSizeSlice"; import { AppSnackbar } from "@/components/ui/AppSnackbar"; export const UsersPage = () => { const params = useLocalSearchParams<{ q?: string }>(); const { user } = useAuthContext(); const [page, setPage] = useState(0); const [itemsPerPage, setItemsPerPage] = useState(25); const [searchQuery, setSearchQuery] = useState(""); const [selectedRisks, setSelectedRisks] = useState([]); const [sortColumn, setSortColumn] = useState(null); const [sortDirection, setSortDirection] = useState( TableSortDirection.ASCENDING ); const isMobile = useAppSelector(selectIsMobile); const deferredSearch = useDeferredValue(searchQuery); const [debouncedSearch, setDebouncedSearch] = useState(deferredSearch); useEffect(() => { const handler = setTimeout( () => setDebouncedSearch(deferredSearch.trim()), 450 ); return () => clearTimeout(handler); }, [deferredSearch]); // Initialize search query from route param useEffect(() => { const qParam = (params.q ?? "").toString().trim(); if (qParam && qParam !== searchQuery) { setSearchQuery(qParam); setPage(0); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [params.q]); const queryArgs = useMemo(() => { const allowedSorts = ["name", "email", "title", "risk"] as const; const sortBy = (allowedSorts as readonly string[]).includes( sortColumn || "" ) ? (sortColumn as (typeof allowedSorts)[number]) : undefined; const sortDir = sortDirection; const risks = selectedRisks.length ? selectedRisks : undefined; const searchParam = debouncedSearch && debouncedSearch.length >= 2 ? debouncedSearch : undefined; return { roleType: "user" as const, page: page + 1, limit: itemsPerPage, search: searchParam, risks, sortBy, sortDir, }; }, [ page, itemsPerPage, debouncedSearch, selectedRisks, sortColumn, sortDirection, ]); const { data: usersPage, isLoading, error, refetch, isFetching, } = useGetUsersQuery(queryArgs, { refetchOnMountOrArgChange: true, refetchOnFocus: true, refetchOnReconnect: true, }); const users = usersPage?.items ?? []; const totalItems = usersPage?.total ?? 0; const from = users.length ? page * itemsPerPage + 1 : 0; const to = users.length ? Math.min(from + users.length - 1, totalItems) : 0; const [deleteUser, { isLoading: isDeleting }] = useDeleteUserMutation(); const [lockUser, { isLoading: isLocking }] = useLockUserMutation(); const [updateUser, { isLoading: isUpdating }] = useUpdateUserMutation(); const [createUser, { isLoading: isCreating }] = useCreateUserMutation(); const [bulkDeleteUsers, { isLoading: isBulkDeleting }] = useDeleteUsersMutation(); const [userModalVisible, setUserModalVisible] = useState(false); const [userModalMode, setUserModalMode] = useState( OperationMode.ADD ); const [deleteModalVisible, setDeleteModalVisible] = useState(false); const [bulkDeleteModalVisible, setBulkDeleteModalVisible] = useState(false); const [lockModalVisible, setLockModalVisible] = useState(false); const [currentUser, setCurrentUser] = useState(null); const [selectedUsersIds, setSelectedUsersIds] = useState([]); const [selectedUsers, setSelectedUsers] = useState([]); const [snackbarVisible, setSnackbarVisible] = useState(false); const [snackbarMessage, setSnackbarMessage] = useState(""); const showSnackbar = (message: string) => { setSnackbarMessage(message); setSnackbarVisible(true); }; const handleEdit = (user: User) => { setCurrentUser(user); setUserModalMode(OperationMode.EDIT); setUserModalVisible(true); }; const handleAddUser = () => { setCurrentUser(null); setUserModalMode(OperationMode.ADD); setUserModalVisible(true); }; const openDeleteModal = (user: User) => { setCurrentUser(user); setDeleteModalVisible(true); }; const openLockModal = (user: User) => { setCurrentUser(user); setLockModalVisible(true); }; const handleDeleteUser = async (user: User) => { try { await deleteUser(user.id).unwrap(); if (selectedUsersIds.includes(user.id)) { setSelectedUsersIds(selectedUsersIds.filter((id) => id !== user.id)); } refetch(); showSnackbar( `${user.firstName} ${user.lastName} has been deleted successfully` ); setDeleteModalVisible(false); } catch (error) { console.error( `Error deleting user ${user.firstName} ${user.lastName}:`, error ); showSnackbar(`Error deleting ${user.firstName} ${user.lastName}`); } }; const handleLockUser = async (user: User) => { const action = user.isLocked ? "unlock" : "lock"; try { await lockUser(user.id).unwrap(); refetch(); showSnackbar( `${user.firstName} ${user.lastName} has been ${action}ed successfully` ); setLockModalVisible(false); } catch (error) { console.error( `Error ${action}ing user ${user.firstName} ${user.lastName}:`, error ); showSnackbar(`Error ${action}ing ${user.firstName} ${user.lastName}`); } }; const handleBulkDeleteUsers = (users: User[]) => { if (users.length === 0) return; setSelectedUsersIds(users.map((user) => user.id)); setSelectedUsers(users); setBulkDeleteModalVisible(true); }; const confirmBulkDelete = async () => { if (selectedUsersIds.length === 0) return; try { const userIds = selectedUsersIds; const count = selectedUsersIds.length; await bulkDeleteUsers(userIds).unwrap(); refetch(); showSnackbar(`${count} users deleted successfully`); setBulkDeleteModalVisible(false); setSelectedUsersIds([]); } catch (error: any) { console.error(`Error deleting multiple users:`, error); const errorMessage = error?.data?.error || error?.data?.details || "Error deleting multiple users"; showSnackbar(errorMessage); } }; const handleSubmitUser = async (payload: Partial) => { try { if (userModalMode === OperationMode.EDIT) { await updateUser(payload as User).unwrap(); showSnackbar( `${payload.firstName} ${payload.lastName} updated successfully` ); } else { const res = await createUser(payload).unwrap(); showSnackbar( `${res.firstName} ${res.lastName} added successfully${ !res.isActivated ? " and invitation sent" : "" }` ); } setUserModalVisible(false); refetch(); } catch (error) { console.error(`Error saving user:`, error); showSnackbar(`Error saving user`); } }; useEffect(() => { // Refetch when auth user state changes (e.g., after activation/login) refetch(); }, [user?.id, user?.isActivated, refetch]); return ( <> Users manager {isLoading ? ( Loading users... ) : error ? ( ) : ( { setSearchQuery(q); setPage(0); }} onSelectRisks={(risks) => { setSelectedRisks(risks); setPage(0); }} onSort={(column) => { if (sortColumn === column) { setSortDirection((d) => d === TableSortDirection.ASCENDING ? TableSortDirection.DESCENDING : TableSortDirection.ASCENDING ); } else { setSortColumn(column); setSortDirection(TableSortDirection.ASCENDING); } setPage(0); }} onItemsPerPageChange={(value) => { setItemsPerPage(value); setPage(0); }} onPageChange={(newPage) => setPage(newPage)} /> )} setUserModalVisible(false)} onSubmit={handleSubmitUser} isLoading={isUpdating || isCreating} /> setDeleteModalVisible(false)} onDelete={handleDeleteUser} isLoading={isDeleting} /> setBulkDeleteModalVisible(false)} onDelete={confirmBulkDelete} isLoading={isBulkDeleting} /> setLockModalVisible(false)} onLock={handleLockUser} isLoading={isLocking} /> setSnackbarVisible(false)} action={{ label: "Close", onPress: () => setSnackbarVisible(false) }} > {snackbarMessage} ); };