@arqel-dev/ui 0.8.1 → 0.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/pages.d.ts CHANGED
@@ -29,6 +29,19 @@ declare function ArqelEditPage<TRecord extends RecordType = RecordType>(): JSX.E
29
29
  * `<ResourceIndex>`. Apps can override per-resource by registering
30
30
  * their own page component at `Pages/Arqel/{Slug}/Index.tsx` (the
31
31
  * lookup falls through to user pages first inside `createArqelApp`).
32
+ *
33
+ * Wires the standard table interactions — search / filter / sort /
34
+ * pagination — into Inertia partial `router.get` visits so the
35
+ * server can re-run TableQueryBuilder with the new query string.
36
+ *
37
+ * Query-string contract (matches `Arqel\Table\TableQueryBuilder`):
38
+ * - `?search=foo` global search
39
+ * - `?sort=column&direction=asc|desc`
40
+ * - `?page=N&per_page=M`
41
+ * - `?filter[name]=value` one entry per filter
42
+ *
43
+ * `search` is debounced (300ms) so typing doesn't hammer the
44
+ * backend; the rest fire immediately.
32
45
  */
33
46
 
34
47
  declare function ArqelIndexPage<TRecord extends RecordType = RecordType>(): JSX.Element;
package/dist/pages.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { clsx } from 'clsx';
2
2
  import { twMerge } from 'tailwind-merge';
3
- import { useRef, useMemo, useCallback, useId, useState } from 'react';
3
+ import { useMemo, useState, useRef, useEffect, useCallback, useId } from 'react';
4
4
  import { cva } from 'class-variance-authority';
5
5
  import { Slot } from 'radix-ui';
6
6
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
@@ -1566,10 +1566,123 @@ var ArqelIndexPage_exports = {};
1566
1566
  __export(ArqelIndexPage_exports, {
1567
1567
  default: () => ArqelIndexPage
1568
1568
  });
1569
+ function pruneEmptyFilters(values) {
1570
+ const out = {};
1571
+ for (const [k, v] of Object.entries(values)) {
1572
+ if (v === void 0 || v === null || v === "") continue;
1573
+ if (Array.isArray(v) && v.length === 0) continue;
1574
+ out[k] = v;
1575
+ }
1576
+ return out;
1577
+ }
1578
+ function buildQuery(params) {
1579
+ const data = {};
1580
+ const filters = pruneEmptyFilters(params.filters);
1581
+ if (Object.keys(filters).length > 0) data["filter"] = filters;
1582
+ if (params.search) data["search"] = params.search;
1583
+ if (params.sort?.column) {
1584
+ data["sort"] = params.sort.column;
1585
+ if (params.sort.direction) data["direction"] = params.sort.direction;
1586
+ }
1587
+ if (params.page && params.page > 1) data["page"] = params.page;
1588
+ if (params.perPage) data["per_page"] = params.perPage;
1589
+ return data;
1590
+ }
1569
1591
  function ArqelIndexPage() {
1570
1592
  const page = usePage();
1571
1593
  const props = page.props;
1572
- return /* @__PURE__ */ jsx(ResourceIndex, { ...props });
1594
+ const initialFilters = useMemo(() => {
1595
+ if (typeof window === "undefined") return {};
1596
+ const sp = new URLSearchParams(window.location.search);
1597
+ const fromUrl = {};
1598
+ for (const [key, value] of sp.entries()) {
1599
+ const m = key.match(/^filter\[([^\]]+)\]$/);
1600
+ if (m?.[1]) fromUrl[m[1]] = value;
1601
+ }
1602
+ return fromUrl;
1603
+ }, []);
1604
+ const [filterValues, setFilterValues] = useState(initialFilters);
1605
+ const [searchValue, setSearchValue] = useState(props.search ?? "");
1606
+ const searchDebounceRef = useRef(null);
1607
+ const visit = (data) => {
1608
+ const url = typeof window !== "undefined" ? window.location.pathname : page.url.split("?")[0];
1609
+ router.get(url ?? "/", data, {
1610
+ preserveState: true,
1611
+ preserveScroll: true,
1612
+ replace: true
1613
+ });
1614
+ };
1615
+ const currentSort = props.sort ?? null;
1616
+ const handleFilterChange = (name, value) => {
1617
+ const nextFilters = { ...filterValues };
1618
+ if (value === void 0 || value === null || value === "") {
1619
+ delete nextFilters[name];
1620
+ } else {
1621
+ nextFilters[name] = value;
1622
+ }
1623
+ setFilterValues(nextFilters);
1624
+ visit(buildQuery({ filters: nextFilters, search: searchValue, sort: currentSort }));
1625
+ };
1626
+ const handleClearFilters = () => {
1627
+ setFilterValues({});
1628
+ visit(buildQuery({ filters: {}, search: searchValue, sort: currentSort }));
1629
+ };
1630
+ const handleSearchChange = (value) => {
1631
+ setSearchValue(value);
1632
+ if (searchDebounceRef.current) clearTimeout(searchDebounceRef.current);
1633
+ searchDebounceRef.current = setTimeout(() => {
1634
+ visit(buildQuery({ filters: filterValues, search: value, sort: currentSort }));
1635
+ }, 300);
1636
+ };
1637
+ useEffect(
1638
+ () => () => {
1639
+ if (searchDebounceRef.current) clearTimeout(searchDebounceRef.current);
1640
+ },
1641
+ []
1642
+ );
1643
+ const handleSortChange = (column, direction) => {
1644
+ visit(
1645
+ buildQuery({
1646
+ filters: filterValues,
1647
+ search: searchValue,
1648
+ sort: { column, direction }
1649
+ })
1650
+ );
1651
+ };
1652
+ const handlePageChange = (pageNum) => {
1653
+ visit(
1654
+ buildQuery({
1655
+ filters: filterValues,
1656
+ search: searchValue,
1657
+ sort: currentSort,
1658
+ page: pageNum
1659
+ })
1660
+ );
1661
+ };
1662
+ const handlePerPageChange = (perPage) => {
1663
+ visit(
1664
+ buildQuery({
1665
+ filters: filterValues,
1666
+ search: searchValue,
1667
+ sort: currentSort,
1668
+ perPage
1669
+ })
1670
+ );
1671
+ };
1672
+ return /* @__PURE__ */ jsx(
1673
+ ResourceIndex,
1674
+ {
1675
+ ...props,
1676
+ filterValues,
1677
+ onFilterChange: handleFilterChange,
1678
+ onClearFilters: handleClearFilters,
1679
+ onSearchChange: handleSearchChange,
1680
+ onSortChange: handleSortChange,
1681
+ onPageChange: handlePageChange,
1682
+ onPerPageChange: handlePerPageChange,
1683
+ search: searchValue
1684
+ }
1685
+ );
1573
1686
  }
1574
1687
  var init_ArqelIndexPage = __esm({
1575
1688
  "src/pages/ArqelIndexPage.tsx"() {