Skip to content

Instantly share code, notes, and snippets.

@izakfilmalter
Last active June 4, 2025 11:42
Show Gist options
  • Select an option

  • Save izakfilmalter/56e32c19d24dffb66f538a1809abb4da to your computer and use it in GitHub Desktop.

Select an option

Save izakfilmalter/56e32c19d24dffb66f538a1809abb4da to your computer and use it in GitHub Desktop.
Zero + bazza/ui filters
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