import { useState, useEffect, useCallback, useRef } from "react"; import { useAlert } from "../context/AlertContext"; import apiFetch from "../utils/api"; import useDebounce from "./useDebounce"; const API_BASE = "/api/admin"; interface PaginationData { total: number; page: number; per_page: number; total_pages: number; } interface UseListDataOptions { dataKey?: string; search?: string; sort?: string; order?: string; page?: number; perPage?: number; extraParams?: Record; errorMsg?: string; } export default function useListData( endpoint: string, options: UseListDataOptions = {}, ) { const { dataKey, search = "", sort, order, page = 1, perPage = 25, extraParams = {}, errorMsg = "Nepodařilo se načíst data", } = options; const alert = useAlert(); const [items, setItems] = useState([]); const [loading, setLoading] = useState(true); const [initialLoad, setInitialLoad] = useState(true); const [pagination, setPagination] = useState(null); const abortRef = useRef(null); const mountedRef = useRef(true); const debouncedSearch = useDebounce(search, 300); const extraParamsKey = Object.entries(extraParams) .sort((a, b) => a[0].localeCompare(b[0])) .map(([k, v]) => `${k}=${v}`) .join("&"); const fetchData = useCallback(async () => { if (abortRef.current) abortRef.current.abort(); const controller = new AbortController(); abortRef.current = controller; try { const params = new URLSearchParams({ page: String(page), per_page: String(perPage), }); if (debouncedSearch) params.set("search", debouncedSearch); if (sort) params.set("sort", sort); if (order) params.set("order", order); Object.entries(extraParams).forEach(([k, v]) => { if (v) params.set(k, v); }); const url = endpoint.startsWith("/") ? `${endpoint}?${params}` : `${API_BASE}/${endpoint}?${params}`; const response = await apiFetch(url, { signal: controller.signal }); if (response.status === 401) { window.location.href = "/login"; return; } const result = await response.json(); if (result.success) { const data = dataKey ? result.data[dataKey] : Array.isArray(result.data) ? result.data : result.data?.items || []; setItems(data || []); const pag = result.pagination || (!Array.isArray(result.data) && result.data?.pagination) || null; setPagination( pag || { total: data?.length ?? 0, page, per_page: perPage, total_pages: 1, }, ); } else { alert.error(result.error || errorMsg); } } catch (err: unknown) { if (err instanceof Error && err.name === "AbortError") return; if (!mountedRef.current) return; alert.error(errorMsg); } finally { if (!mountedRef.current) return; setLoading(false); setInitialLoad(false); } }, [ endpoint, debouncedSearch, sort, order, page, perPage, dataKey, extraParamsKey, ]); useEffect(() => { mountedRef.current = true; fetchData(); return () => { mountedRef.current = false; if (abortRef.current) abortRef.current.abort(); }; }, [fetchData]); return { items, setItems, loading, initialLoad, pagination, refetch: fetchData, }; }