diff --git a/app/src/ui/components/table/DataTable.tsx b/app/src/ui/components/table/DataTable.tsx index 2ed4b05..2ce2a42 100644 --- a/app/src/ui/components/table/DataTable.tsx +++ b/app/src/ui/components/table/DataTable.tsx @@ -104,15 +104,17 @@ export function DataTable = Record diff --git a/app/src/ui/hooks/use-search.ts b/app/src/ui/hooks/use-search.ts index eabd58c..915b201 100644 --- a/app/src/ui/hooks/use-search.ts +++ b/app/src/ui/hooks/use-search.ts @@ -1,32 +1,41 @@ -import { decodeSearch, encodeSearch, mergeObject, parseDecode } from "core/utils"; +import { decodeSearch, encodeSearch, mergeObject } from "core/utils"; import { isEqual, transform } from "lodash-es"; import { useLocation, useSearch as useWouterSearch } from "wouter"; -import { type s, parse, cloneSchema } from "core/object/schema"; +import { type s, parse } from "core/object/schema"; +import { useEffect, useState } from "react"; + +export type UseSearchOptions = { + defaultValue?: Partial>; + beforeEncode?: (search: Partial>) => object; +}; -// @todo: migrate to Typebox export function useSearch( - _schema: Schema, - defaultValue?: Partial>, + schema: Schema, + options?: UseSearchOptions, ) { - const schema = cloneSchema(_schema as any) as s.TSchema; const searchString = useWouterSearch(); const [location, navigate] = useLocation(); - const initial = searchString.length > 0 ? decodeSearch(searchString) : (defaultValue ?? {}); - const value = parse(schema, initial, { - withDefaults: true, - clone: true, - }) as s.StaticCoerced; + const [value, setValue] = useState>( + options?.defaultValue ?? ({} as any), + ); + const _defaults = mergeObject( + // @ts-ignore + schema.template({ withOptional: true }), + options?.defaultValue ?? {}, + ); - // @ts-ignore - const _defaults = mergeObject(schema.template({ withOptional: true }), defaultValue ?? {}); + useEffect(() => { + const initial = + searchString.length > 0 ? decodeSearch(searchString) : (options?.defaultValue ?? {}); + const v = parse(schema, Object.assign({}, _defaults, initial)) as any; + setValue(v); + }, [searchString, JSON.stringify(options?.defaultValue), location]); function set>>(update: Update): void { - // @ts-ignore - if (schema.validate(update).valid) { - const search = getWithoutDefaults(mergeObject(value, update), _defaults); - const encoded = encodeSearch(search, { encode: false }); - navigate(location + (encoded.length > 0 ? "?" + encoded : "")); - } + const search = getWithoutDefaults(Object.assign({}, value, update), _defaults); + const prepared = options?.beforeEncode?.(search) ?? search; + const encoded = encodeSearch(prepared, { encode: false }); + navigate(location + (encoded.length > 0 ? "?" + encoded : "")); } return { diff --git a/app/src/ui/routes/data/data.$entity.$id.tsx b/app/src/ui/routes/data/data.$entity.$id.tsx index b73c1b5..be8bc34 100644 --- a/app/src/ui/routes/data/data.$entity.$id.tsx +++ b/app/src/ui/routes/data/data.$entity.$id.tsx @@ -257,15 +257,20 @@ function EntityDetailInner({ }) { const other = relation.other(entity); const [navigate] = useNavigate(); - - const search = { + const [search, setSearch] = useState({ select: other.entity.getSelect(undefined, "table"), + sort: other.entity.getDefaultSort(), limit: 10, offset: 0, - }; + }); + // @todo: add custom key for invalidation - const $q = useApiQuery((api) => - api.data.readManyByReference(entity.name, id, other.reference, search), + const $q = useApiQuery( + (api) => api.data.readManyByReference(entity.name, id, other.reference, search), + { + keepPreviousData: true, + revalidateOnFocus: true, + }, ); function handleClickRow(row: Record) { @@ -300,11 +305,17 @@ function EntityDetailInner({ select={search.select} data={$q.data ?? null} entity={other.entity} + sort={search.sort} onClickRow={handleClickRow} onClickNew={handleClickNew} - page={1} + page={Math.floor(search.offset / search.limit) + 1} total={$q.data?.body?.meta?.count ?? 1} - /*onClickPage={handleClickPage}*/ + onClickPage={(page) => { + setSearch((s) => ({ + ...s, + offset: (page - 1) * s.limit, + })); + }} /> ); diff --git a/app/src/ui/routes/data/data.$entity.index.tsx b/app/src/ui/routes/data/data.$entity.index.tsx index ee9befc..dfb3ede 100644 --- a/app/src/ui/routes/data/data.$entity.index.tsx +++ b/app/src/ui/routes/data/data.$entity.index.tsx @@ -35,8 +35,19 @@ export function DataEntityList({ params }) { useBrowserTitle(["Data", entity?.label ?? params.entity]); const [navigate] = useNavigate(); const search = useSearch(searchSchema, { - select: entity.getSelect(undefined, "table"), - sort: entity.getDefaultSort(), + defaultValue: { + select: entity.getSelect(undefined, "table"), + sort: entity.getDefaultSort(), + }, + beforeEncode: (v) => { + if ("sort" in v && v.sort) { + return { + ...v, + sort: `${v.sort.dir === "asc" ? "" : "-"}${v.sort.by}`, + }; + } + return v; + }, }); const $q = useApiQuery(