| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165 |
- "use client";
- import React from "react";
- import { adminListUsers } from "@/lib/frontend/apiClient";
- function normalizeQuery(query) {
- const q =
- typeof query?.q === "string" && query.q.trim() ? query.q.trim() : null;
- const role =
- typeof query?.role === "string" && query.role.trim()
- ? query.role.trim()
- : null;
- const branchId =
- typeof query?.branchId === "string" && query.branchId.trim()
- ? query.branchId.trim()
- : null;
- return { q, role, branchId };
- }
- function buildKey({ q, role, branchId, limit }) {
- return `${q || ""}|${role || ""}|${branchId || ""}|${String(limit)}`;
- }
- /**
- * useAdminUsersQuery
- *
- * - Loads first page on query changes.
- * - Supports cursor-based "load more".
- * - Includes race protection to avoid stale appends.
- */
- export function useAdminUsersQuery({ query, limit = 50 }) {
- const normalized = React.useMemo(() => {
- return normalizeQuery(query);
- }, [query?.q, query?.role, query?.branchId]);
- const key = React.useMemo(() => {
- return buildKey({ ...normalized, limit });
- }, [normalized.q, normalized.role, normalized.branchId, limit]);
- const [status, setStatus] = React.useState("loading"); // loading|success|error
- const [items, setItems] = React.useState([]);
- const [nextCursor, setNextCursor] = React.useState(null);
- const [error, setError] = React.useState(null);
- const [isLoadingMore, setIsLoadingMore] = React.useState(false);
- const [loadMoreError, setLoadMoreError] = React.useState(null);
- const [refreshTick, setRefreshTick] = React.useState(0);
- const keyRef = React.useRef(key);
- React.useEffect(() => {
- keyRef.current = key;
- }, [key]);
- const firstReqIdRef = React.useRef(0);
- const moreReqIdRef = React.useRef(0);
- const refresh = React.useCallback(() => {
- setRefreshTick((n) => n + 1);
- }, []);
- const runFirstPage = React.useCallback(async () => {
- const reqId = ++firstReqIdRef.current;
- setStatus("loading");
- setError(null);
- setItems([]);
- setNextCursor(null);
- // Reset load-more state on first page load
- setIsLoadingMore(false);
- setLoadMoreError(null);
- try {
- const res = await adminListUsers({
- q: normalized.q,
- role: normalized.role,
- branchId: normalized.branchId,
- limit,
- cursor: null,
- });
- if (reqId !== firstReqIdRef.current) return;
- const list = Array.isArray(res?.items) ? res.items : [];
- const next =
- typeof res?.nextCursor === "string" && res.nextCursor.trim()
- ? res.nextCursor
- : null;
- setItems(list);
- setNextCursor(next);
- setStatus("success");
- } catch (err) {
- if (reqId !== firstReqIdRef.current) return;
- setError(err);
- setStatus("error");
- }
- }, [normalized.q, normalized.role, normalized.branchId, limit]);
- React.useEffect(() => {
- runFirstPage();
- }, [runFirstPage, key, refreshTick]);
- const loadMore = React.useCallback(async () => {
- if (!nextCursor) return;
- if (isLoadingMore) return;
- const baseKey = keyRef.current;
- const reqId = ++moreReqIdRef.current;
- setIsLoadingMore(true);
- setLoadMoreError(null);
- try {
- const res = await adminListUsers({
- q: normalized.q,
- role: normalized.role,
- branchId: normalized.branchId,
- limit,
- cursor: nextCursor,
- });
- // Ignore stale
- if (reqId !== moreReqIdRef.current) return;
- if (keyRef.current !== baseKey) return;
- const more = Array.isArray(res?.items) ? res.items : [];
- const next =
- typeof res?.nextCursor === "string" && res.nextCursor.trim()
- ? res.nextCursor
- : null;
- setItems((prev) => [...prev, ...more]);
- setNextCursor(next);
- } catch (err) {
- setLoadMoreError(err);
- } finally {
- if (reqId === moreReqIdRef.current) {
- setIsLoadingMore(false);
- }
- }
- }, [
- nextCursor,
- isLoadingMore,
- normalized.q,
- normalized.role,
- normalized.branchId,
- limit,
- ]);
- return {
- status,
- items,
- nextCursor,
- error,
- refresh,
- loadMore,
- isLoadingMore,
- loadMoreError,
- };
- }
|