Просмотр исходного кода

RHL-012 feat(api): implement useAdminUsersQuery for managing user data with pagination and query normalization

codeUWE 1 месяц назад
Родитель
Сommit
030540473d
1 измененных файлов с 165 добавлено и 0 удалено
  1. 165 0
      lib/frontend/admin/users/useAdminUsersQuery.js

+ 165 - 0
lib/frontend/admin/users/useAdminUsersQuery.js

@@ -0,0 +1,165 @@
+"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,
+	};
+}