initial commit

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
BOHA
2026-03-23 08:46:51 +01:00
commit 4608494a3f
130 changed files with 40361 additions and 0 deletions

View File

@@ -0,0 +1,91 @@
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<string, string>
errorMsg?: string
}
export default function useListData<T = unknown>(
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<T[]>([])
const [loading, setLoading] = useState(true)
const [initialLoad, setInitialLoad] = useState(true)
const [pagination, setPagination] = useState<PaginationData | null>(null)
const abortRef = useRef<AbortController | null>(null)
const debouncedSearch = useDebounce(search, 300)
const fetchData = useCallback(async () => {
if (abortRef.current) abortRef.current.abort()
const controller = new AbortController()
abortRef.current = controller
setLoading(true)
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) 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
alert.error(errorMsg)
} finally {
setLoading(false)
setInitialLoad(false)
}
}, [endpoint, debouncedSearch, sort, order, page, perPage, dataKey, JSON.stringify(extraParams)]) // eslint-disable-line react-hooks/exhaustive-deps
useEffect(() => {
fetchData()
return () => {
if (abortRef.current) abortRef.current.abort()
}
}, [fetchData])
return { items, setItems, loading, initialLoad, pagination, refetch: fetchData }
}