mirror of
https://github.com/johndoe6345789/metabuilder.git
synced 2026-04-24 13:54:57 +00:00
285 lines
6.6 KiB
TypeScript
285 lines
6.6 KiB
TypeScript
'use client'
|
|
|
|
/**
|
|
* useUsers Hook
|
|
*
|
|
* Manages user list state, pagination, search, and filtering for the admin users page.
|
|
* Integrates with /api/v1/{tenant}/{package}/users endpoints
|
|
*
|
|
* @example
|
|
* const { users, loading, error, pagination, handlers } = useUsers()
|
|
*
|
|
* // Fetch users with pagination
|
|
* useEffect(() => {
|
|
* handlers.fetchUsers(1, 10)
|
|
* }, [])
|
|
*
|
|
* // Search with debouncing
|
|
* const handleSearch = (term) => {
|
|
* handlers.searchUsers(term)
|
|
* }
|
|
*
|
|
* // Change page
|
|
* const handlePageChange = (page) => {
|
|
* handlers.changePage(page)
|
|
* }
|
|
*/
|
|
|
|
import { useCallback, useEffect, useRef, useState } from 'react'
|
|
import type { User } from '@/lib/level-types'
|
|
|
|
interface PaginationState {
|
|
page: number
|
|
limit: number
|
|
total: number
|
|
totalPages: number
|
|
}
|
|
|
|
interface UseUsersState {
|
|
users: User[]
|
|
loading: boolean
|
|
error: string | null
|
|
pagination: PaginationState
|
|
search: string
|
|
roleFilter: string | null
|
|
refetching: boolean
|
|
}
|
|
|
|
interface UseUsersHandlers {
|
|
fetchUsers: (page?: number, limit?: number, search?: string, role?: string | null) => Promise<void>
|
|
refetchUsers: () => Promise<void>
|
|
searchUsers: (term: string) => Promise<void>
|
|
filterByRole: (role: string | null) => Promise<void>
|
|
changePage: (newPage: number) => Promise<void>
|
|
changeLimit: (newLimit: number) => Promise<void>
|
|
reset: () => void
|
|
}
|
|
|
|
interface UseUsersReturn extends UseUsersState {
|
|
handlers: UseUsersHandlers
|
|
}
|
|
|
|
// Debounce timer for search
|
|
let searchTimeout: NodeJS.Timeout | null = null
|
|
|
|
/**
|
|
* Hook for managing user list state, pagination, and filtering
|
|
*/
|
|
export function useUsers(options?: { baseUrl?: string }): UseUsersReturn {
|
|
const baseUrl = options?.baseUrl ?? ''
|
|
const [state, setState] = useState<UseUsersState>({
|
|
users: [],
|
|
loading: false,
|
|
error: null,
|
|
pagination: {
|
|
page: 1,
|
|
limit: 10,
|
|
total: 0,
|
|
totalPages: 0,
|
|
},
|
|
search: '',
|
|
roleFilter: null,
|
|
refetching: false,
|
|
})
|
|
|
|
// Keep track of current filters for refetch
|
|
const currentFiltersRef = useRef({
|
|
page: 1,
|
|
limit: 10,
|
|
search: '',
|
|
role: null as string | null,
|
|
})
|
|
|
|
/**
|
|
* Build query string for API request
|
|
*/
|
|
const buildQueryString = useCallback((
|
|
page: number,
|
|
limit: number,
|
|
search?: string,
|
|
role?: string | null
|
|
): string => {
|
|
const params = new URLSearchParams()
|
|
|
|
params.set('skip', String((page - 1) * limit))
|
|
params.set('take', String(limit))
|
|
|
|
if (search && search.trim()) {
|
|
params.set('search', search.trim())
|
|
}
|
|
|
|
if (role) {
|
|
params.set('role', role)
|
|
}
|
|
|
|
return `?${params.toString()}`
|
|
}, [])
|
|
|
|
/**
|
|
* Fetch users from API
|
|
* Supports pagination, search, and role filtering
|
|
*/
|
|
const fetchUsers = useCallback(
|
|
async (page = 1, limit = 10, search = '', role: string | null = null) => {
|
|
setState((prev) => ({ ...prev, loading: true, error: null }))
|
|
|
|
try {
|
|
// Store current filters for refetch
|
|
currentFiltersRef.current = { page, limit, search, role }
|
|
|
|
// Build query string
|
|
const queryString = buildQueryString(page, limit, search, role)
|
|
|
|
// Make API request
|
|
const response = await fetch(`${baseUrl}/api/v1/default/user_manager/users${queryString}`)
|
|
|
|
if (!response.ok) {
|
|
const errorData = await response.json().catch(() => ({}))
|
|
throw new Error(
|
|
errorData.error?.message ||
|
|
`HTTP ${response.status}: ${response.statusText}`
|
|
)
|
|
}
|
|
|
|
const data = await response.json()
|
|
|
|
// Extract pagination metadata from response
|
|
const total = data.meta?.total ?? 0
|
|
const totalPages = Math.ceil(total / limit)
|
|
|
|
setState((prev) => ({
|
|
...prev,
|
|
users: data.data ?? [],
|
|
pagination: {
|
|
page,
|
|
limit,
|
|
total,
|
|
totalPages,
|
|
},
|
|
search,
|
|
roleFilter: role,
|
|
loading: false,
|
|
}))
|
|
} catch (err) {
|
|
const message = err instanceof Error ? err.message : 'Failed to fetch users'
|
|
setState((prev) => ({
|
|
...prev,
|
|
error: message,
|
|
loading: false,
|
|
}))
|
|
}
|
|
},
|
|
[buildQueryString]
|
|
)
|
|
|
|
/**
|
|
* Refetch users with current filters
|
|
*/
|
|
const refetchUsers = useCallback(async () => {
|
|
setState((prev) => ({ ...prev, refetching: true }))
|
|
|
|
try {
|
|
const { page, limit, search, role } = currentFiltersRef.current
|
|
await fetchUsers(page, limit, search, role)
|
|
} finally {
|
|
setState((prev) => ({ ...prev, refetching: false }))
|
|
}
|
|
}, [fetchUsers])
|
|
|
|
/**
|
|
* Search users by username or email (debounced)
|
|
*/
|
|
const searchUsers = useCallback(
|
|
async (term: string) => {
|
|
// Clear previous timeout
|
|
if (searchTimeout) {
|
|
clearTimeout(searchTimeout)
|
|
}
|
|
|
|
// Update search state immediately (for input field)
|
|
setState((prev) => ({ ...prev, search: term }))
|
|
|
|
// Debounce API call (300ms)
|
|
searchTimeout = setTimeout(async () => {
|
|
const { page, limit, role } = currentFiltersRef.current
|
|
await fetchUsers(1, limit, term, role) // Reset to page 1 on search
|
|
}, 300)
|
|
},
|
|
[fetchUsers]
|
|
)
|
|
|
|
/**
|
|
* Filter by user role and reset to page 1
|
|
*/
|
|
const filterByRole = useCallback(
|
|
async (role: string | null) => {
|
|
const { limit, search } = currentFiltersRef.current
|
|
await fetchUsers(1, limit, search, role)
|
|
},
|
|
[fetchUsers]
|
|
)
|
|
|
|
/**
|
|
* Change current page
|
|
*/
|
|
const changePage = useCallback(
|
|
async (newPage: number) => {
|
|
const { limit, search, role } = currentFiltersRef.current
|
|
await fetchUsers(newPage, limit, search, role)
|
|
},
|
|
[fetchUsers]
|
|
)
|
|
|
|
/**
|
|
* Change items per page (reset to page 1)
|
|
*/
|
|
const changeLimit = useCallback(
|
|
async (newLimit: number) => {
|
|
const { search, role } = currentFiltersRef.current
|
|
await fetchUsers(1, newLimit, search, role)
|
|
},
|
|
[fetchUsers]
|
|
)
|
|
|
|
/**
|
|
* Reset all filters and fetch initial list
|
|
*/
|
|
const reset = useCallback(() => {
|
|
setState({
|
|
users: [],
|
|
loading: false,
|
|
error: null,
|
|
pagination: {
|
|
page: 1,
|
|
limit: 10,
|
|
total: 0,
|
|
totalPages: 0,
|
|
},
|
|
search: '',
|
|
roleFilter: null,
|
|
refetching: false,
|
|
})
|
|
currentFiltersRef.current = {
|
|
page: 1,
|
|
limit: 10,
|
|
search: '',
|
|
role: null,
|
|
}
|
|
}, [])
|
|
|
|
return {
|
|
...state,
|
|
handlers: {
|
|
fetchUsers,
|
|
refetchUsers,
|
|
searchUsers,
|
|
filterByRole,
|
|
changePage,
|
|
changeLimit,
|
|
reset,
|
|
},
|
|
}
|
|
}
|
|
|
|
export default useUsers
|