import { useEffect, useState, useCallback } from "react"; import { useSearchParams } from "react-router-dom"; import { fetchStandards, fetchCategories } from "../api/standards"; import { useDebounce } from "../hooks/useDebounce"; import StandardCard from "../components/StandardCard"; import StandardModal from "../components/StandardModal"; import "./Standards.css"; const PAGE_SIZE = 18; export default function Standards() { const [searchParams, setSearchParams] = useSearchParams(); const [query, setQuery] = useState(searchParams.get("q") || ""); 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 debouncedQuery = useDebounce(query, 300); useEffect(() => { fetchCategories().then(setCategories).catch(() => {}); }, []); 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); } catch { setError("Could not load standards. Is the server running?"); } finally { setLoading(false); } }, []); useEffect(() => { setPage(1); const params = {}; if (debouncedQuery) params.q = debouncedQuery; if (category) params.category = category; setSearchParams(params, { replace: true }); load(debouncedQuery, category, 1); }, [debouncedQuery, category, load, setSearchParams]); const handlePageChange = (pg) => { setPage(pg); load(debouncedQuery, category, pg); window.scrollTo({ top: 0, behavior: "smooth" }); }; return (

Find an IS Standard

Search by standard number, title, material, or keyword.

e.preventDefault()} >
setQuery(e.target.value)} placeholder="e.g. Ordinary Portland Cement, IS 269, aggregates…" aria-label="Search standards" /> {query && ( )}
{error &&
{error}
} {!error && meta && (

{loading ? "Searching…" : `${meta.total} standard${meta.total !== 1 ? "s" : ""} found`} {meta.total > 0 && ` — page ${meta.page} of ${meta.totalPages}`}

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

No standards found

Try a different keyword or clear the category filter.

)} {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 ( ); }