import { useEffect, useState, useCallback } from "react"; import { useSearchParams } from "react-router-dom"; import { useTranslation } from "react-i18next"; import { fetchStandards, fetchCategories } from "../api/standards"; import StandardCard from "../components/StandardCard"; import StandardModal from "../components/StandardModal"; import { useDebounce } from "../hooks/useDebounce"; import "./Standards.css"; const PAGE_SIZE = 18; export default function Standards() { const { t } = useTranslation(); const [searchParams, setSearchParams] = useSearchParams(); const [query, setQuery] = useState(searchParams.get("q") || ""); const debouncedQuery = useDebounce(query, 350); const [category, setCategory] = useState(searchParams.get("category") || ""); const [page, setPage] = useState(1); const [results, setResults] = useState([]); const [meta, setMeta] = useState(null); const [categories, setCategories] = useState([]); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [selected, setSelected] = useState(null); const load = useCallback(async (q, cat, pg) => { setLoading(true); setError(null); try { const data = await fetchStandards({ q, category: cat, page: pg, limit: PAGE_SIZE }); setResults(data.data); setMeta(data.meta); setPage(pg); } catch { setError(t("standards.serverError")); } finally { setLoading(false); } }, [t]); useEffect(() => { fetchCategories().then(setCategories).catch(() => {}); load(debouncedQuery, category, 1); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); useEffect(() => { load(debouncedQuery, category, 1); // eslint-disable-next-line react-hooks/exhaustive-deps }, [debouncedQuery]); useEffect(() => { const params = {}; if (debouncedQuery) params.q = debouncedQuery; if (category) params.category = category; setSearchParams(params, { replace: true }); }, [debouncedQuery, category, setSearchParams]); const handleCategoryChange = (value) => { setCategory(value); load(debouncedQuery, value, 1); }; const handlePageChange = (pg) => { load(debouncedQuery, category, pg); window.scrollTo({ top: 0, behavior: "smooth" }); }; const handleClearSearch = () => { setQuery(""); setCategory(""); load("", "", 1); }; return (

{t("standards.heading")}

{t("standards.lead")}

e.preventDefault()}>
setQuery(e.target.value)} placeholder={t("standards.searchPlaceholder")} aria-label={t("standards.searchLabel")} /> {query && ( )}
{error &&
{error}
} {!error && meta && (

{loading ? t("standards.searching") : t("standards.found", { count: meta.total })} {!loading && meta.total > 0 && ` — ${t("standards.page", { page: meta.page, total: meta.totalPages })}`}

)} {loading && results.length === 0 && (
{Array.from({ length: 6 }).map((_, i) => ( )} {!loading && results.length === 0 && !error && (

{t("standards.noResultsTitle")}

{t("standards.noResultsSub")}

)} {results.length > 0 && (
{results.map((s) => ( setSelected(s)} /> ))}
)} {meta && meta.totalPages > 1 && ( )}
{selected && ( setSelected(null)} /> )}
); } function buildPageRange(current, total) { if (total <= 7) return Array.from({ length: total }, (_, i) => i + 1); const pages = new Set([1, total, current, current - 1, current + 1].filter(p => p >= 1 && p <= total)); const sorted = [...pages].sort((a, b) => a - b); const result = []; for (let i = 0; i < sorted.length; i++) { if (i > 0 && sorted[i] - sorted[i - 1] > 1) result.push("…"); result.push(sorted[i]); } return result; } function SearchIcon() { return ( ); }