Last active
June 4, 2025 11:42
-
-
Save izakfilmalter/56e32c19d24dffb66f538a1809abb4da to your computer and use it in GitHub Desktop.
Zero + bazza/ui filters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import type { FiltersState } from '@/components/data-table-filter/core/types' | |
| import { filtersSchema } from '@/components/data-table-filter/core/types' | |
| import type { FilterKeys } from '@/data/globalState' | |
| import { noOp } from '@/shared/things' | |
| import { useZero } from '@/shared/zero/useZero' | |
| import type { ZSchema } from '@/shared/zero/zeroSchema.mjs' | |
| import { escapeLike, type Query, type TTL } from '@rocicorp/zero' | |
| import { useQuery } from '@rocicorp/zero/react' | |
| import { getTime } from 'date-fns/fp' | |
| import { Array, Boolean, Option, pipe, String } from 'effect' | |
| import { parseAsJson, useQueryState } from 'nuqs' | |
| import { useCallback, useState } from 'react' | |
| export function useFilterQuery< | |
| TSchema extends ZSchema, | |
| TTable extends keyof TSchema['tables'] & string, | |
| TReturn, | |
| >(params: { | |
| query: (z: ReturnType<typeof useZero>) => Query<TSchema, TTable, TReturn> | |
| filterKey: FilterKeys | |
| pageSize?: number | |
| enabled?: boolean | |
| ttl?: TTL | |
| filterTtl?: TTL | |
| getAll?: boolean | |
| }) { | |
| const { | |
| query, | |
| filterKey, | |
| pageSize = 50, | |
| enabled = true, | |
| ttl = '1d', | |
| filterTtl = 'none', | |
| getAll = false, | |
| } = params | |
| const z = useZero() | |
| const [limit, setLimit] = useState(pageSize) | |
| const [urlFilters] = useQueryState<FiltersState>( | |
| filterKey, | |
| parseAsJson(filtersSchema.parse).withDefault([]), | |
| ) | |
| const [result, info] = useQuery( | |
| getQuery({ | |
| urlFilters, | |
| query: query(z), | |
| getAll, | |
| limit, | |
| }), | |
| { | |
| enabled, | |
| ttl: pipe( | |
| urlFilters, | |
| Array.match({ | |
| onEmpty: () => ttl, | |
| onNonEmpty: () => filterTtl, | |
| }), | |
| ), | |
| }, | |
| ) | |
| const nextPage = useCallback(() => { | |
| setLimit((x) => x + pageSize) | |
| }, [pageSize]) | |
| return { result, info, nextPage, pageSize, limit } | |
| } | |
| const getQuery = < | |
| TSchema extends ZSchema, | |
| TTable extends keyof TSchema['tables'] & string, | |
| TReturn, | |
| >(params: { | |
| urlFilters: FiltersState | |
| query: Query<TSchema, TTable, TReturn> | |
| getAll: boolean | |
| limit: number | |
| }) => { | |
| const { urlFilters, getAll, limit } = params | |
| let { query } = params | |
| pipe( | |
| urlFilters, | |
| Array.map((x) => { | |
| switch (x.type) { | |
| case 'option': | |
| query = query.where((q) => | |
| q.exists(x.columnId as Parameters<typeof q.exists>[0], (q) => | |
| q.where( | |
| // @ts-ignore | |
| 'id', | |
| pipe( | |
| x.operator === 'is', | |
| Boolean.match({ | |
| onFalse: () => 'NOT IN' as const, | |
| onTrue: () => 'IN' as const, | |
| }), | |
| ), | |
| x.values, | |
| ), | |
| ), | |
| ) | |
| return | |
| case 'multiOption': | |
| query = query.where((q) => | |
| q.exists(x.columnId as Parameters<typeof q.exists>[0], (q) => | |
| q.where( | |
| // @ts-ignore | |
| 'id', | |
| pipe( | |
| x.operator === 'include', | |
| Boolean.match({ | |
| onFalse: () => 'NOT IN' as const, | |
| onTrue: () => 'IN' as const, | |
| }), | |
| ), | |
| x.values, | |
| ), | |
| ), | |
| ) | |
| return | |
| case 'text': | |
| // Doing it this way because we don't want to filter for emp | |
| pipe( | |
| x.values as Array<string>, | |
| Array.head, | |
| Option.filter(String.isNonEmpty), | |
| Option.match({ | |
| onNone: noOp, | |
| onSome: (y) => { | |
| query = query.where( | |
| // @ts-ignore | |
| x.columnId, | |
| pipe( | |
| x.operator === 'contains', | |
| Boolean.match({ | |
| onFalse: () => 'NOT ILIKE' as const, | |
| onTrue: () => 'ILIKE' as const, | |
| }), | |
| ), | |
| `%${escapeLike(y)}%`, | |
| ) | |
| }, | |
| }), | |
| ) | |
| return | |
| case 'date': | |
| const startDate = pipe( | |
| x.values as Array<Date>, | |
| Array.head, | |
| Option.getOrElse(() => new Date()), | |
| getTime, | |
| ) | |
| const endDate = pipe( | |
| x.values as Array<Date>, | |
| Array.last, | |
| Option.getOrElse(() => new Date()), | |
| getTime, | |
| ) | |
| query = query.where((q) => | |
| pipe( | |
| x.operator === 'is between', | |
| Boolean.match({ | |
| onFalse: () => | |
| q.or( | |
| // @ts-ignore | |
| q.cmp(x.columnId, '>', endDate), | |
| // @ts-ignore | |
| q.cmp(x.columnId, '<', startDate), | |
| ), | |
| onTrue: () => | |
| q.and( | |
| // @ts-ignore | |
| q.cmp(x.columnId, '>=', startDate), | |
| // @ts-ignore | |
| q.cmp(x.columnId, '<=', endDate), | |
| ), | |
| }), | |
| ), | |
| ) | |
| return | |
| case 'number': | |
| const firstValue = pipe( | |
| x.values as Array<number>, | |
| Array.head, | |
| Option.getOrElse(() => 0), | |
| ) | |
| const secondValue = pipe( | |
| x.values as Array<number>, | |
| Array.last, | |
| Option.getOrElse(() => 0), | |
| ) | |
| switch (x.operator) { | |
| case 'is': | |
| // @ts-ignore | |
| query = query.where(x.columnId, '=', firstValue) | |
| return | |
| case 'is not': | |
| // @ts-ignore | |
| query = query.where(x.columnId, '!=', firstValue) | |
| return | |
| case 'is greater than': | |
| // @ts-ignore | |
| query = query.where(x.columnId, '>', firstValue) | |
| return | |
| case 'is greater than or equal to': | |
| // @ts-ignore | |
| query = query.where(x.columnId, '>=', firstValue) | |
| return | |
| case 'is less than': | |
| // @ts-ignore | |
| query = query.where(x.columnId, '<', firstValue) | |
| return | |
| case 'is less than or equal to': | |
| // @ts-ignore | |
| query = query.where(x.columnId, '<=', firstValue) | |
| case 'is between': | |
| // @ts-ignore | |
| query = query.where(x.columnId, '>=', firstValue) | |
| // @ts-ignore | |
| query = query.where(x.columnId, '<=', secondValue) | |
| return | |
| case 'is not between': | |
| // @ts-ignore | |
| query = query.where(x.columnId, '>', firstValue) | |
| // @ts-ignore | |
| query = query.where(x.columnId, '<', secondValue) | |
| return | |
| default: | |
| return | |
| } | |
| return | |
| default: | |
| return | |
| } | |
| }), | |
| ) | |
| if (!getAll) { | |
| query = query.limit(limit) | |
| } | |
| return query | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment