"use client"; import { Plus, X } from "lucide-react"; import { cn } from "@/lib/utils"; import type { Property, FilterGroup, FilterNode, FilterRule, FilterOp, Sort } from "@/lib/databases/types"; import { isGroup } from "@/lib/databases/types"; import { useDb } from "./hooks"; import { Sel, inputCls } from "./meta"; const OPS: Record = { text: [["contains", "contains"], ["not_contains", "does not contain"], ["eq", "is"], ["neq", "is not"], ["starts_with", "starts with"], ["ends_with", "ends with"], ["empty", "is empty"], ["not_empty", "is not empty"]], number: [["eq", "="], ["neq", "≠"], ["gt", ">"], ["gte", "≥"], ["lt", "<"], ["lte", "≤"], ["empty", "is empty"], ["not_empty", "is not empty"]], date: [["on", "is"], ["before", "is before"], ["after", "is after"], ["empty", "is empty"], ["not_empty", "is not empty"]], checkbox: [["eq", "is"]], select: [["eq", "is"], ["neq", "is not"], ["empty", "is empty"], ["not_empty", "is not empty"]], multi: [["contains", "contains"], ["not_contains", "does not contain"], ["empty", "is empty"], ["not_empty", "is not empty"]], file: [["empty", "is empty"], ["not_empty", "is not empty"]], }; const kind = (p?: Property) => !p ? "text" : ["number", "rollup"].includes(p.type) ? "number" : ["date", "created_time", "last_edited_time"].includes(p.type) ? "date" : p.type === "checkbox" ? "checkbox" : ["select", "status"].includes(p.type) ? "select" : p.type === "multi_select" ? "multi" : p.type === "relation" ? "multi" : p.type === "file" ? "file" : "text"; export const describeFilter = (f: FilterGroup | null | undefined): number => !f ? 0 : (f.and ?? f.or ?? []).reduce((n, x) => n + (isGroup(x) ? describeFilter(x) : 1), 0); export function FilterBuilder() { const ctx = useDb(); const f: FilterGroup = ctx.view.config.filter ?? { and: [] }; const setF = (g: FilterGroup) => ctx.patchView({ filter: (g.and ?? g.or ?? []).length ? g : null }); return
Filters{describeFilter(f) > 0 && }
; } function Group({ g, onChange, depth, onRemove }: { g: FilterGroup; onChange: (g: FilterGroup) => void; depth: number; onRemove?: () => void }) { const ctx = useDb(); const mode: "and" | "or" = g.or ? "or" : "and"; const items = (g.or ?? g.and ?? []) as FilterNode[]; const setItems = (n: FilterNode[]) => onChange({ [mode]: n } as FilterGroup); const first = ctx.props[0]; return (
0 && "rounded-md border border-line bg-bg-sunk/50 p-2")}> {items.map((it, i) => (
{i === 0 ? "Where" : i === 1 ? onChange({ [e.target.value]: items } as FilterGroup)} className="h-7 w-14 px-1 text-[12px]"> : mode}
{isGroup(it) ?
setItems(items.map((x, j) => (j === i ? ng : x)))} onRemove={() => setItems(items.filter((_, j) => j !== i))} />
: setItems(items.map((x, j) => (j === i ? nr : x)))} onRemove={() => setItems(items.filter((_, j) => j !== i))} />}
))}
{depth === 0 && } {onRemove && }
); } function Rule({ r, onChange, onRemove }: { r: FilterRule; onChange: (r: FilterRule) => void; onRemove: () => void }) { const ctx = useDb(); const p = ctx.props.find((x) => x.id === r.property); const k = kind(p); const ops = OPS[k]; const noValue = r.op === "empty" || r.op === "not_empty"; return (
{ const np = ctx.props.find((x) => x.id === e.target.value); const nk = kind(np); onChange({ property: e.target.value, op: OPS[nk][0][0], value: nk === "checkbox" ? true : "" }); }} className="h-7 max-w-[9rem] text-[12px]">{ctx.props.map((x) => )} onChange({ ...r, op: e.target.value as FilterOp })} className="h-7 text-[12px]">{ops.map(([v, l]) => )} {!noValue && (k === "checkbox" ? onChange({ ...r, value: e.target.value === "true" })} className="h-7 text-[12px]"> : k === "select" || (k === "multi" && p?.type === "multi_select") ? onChange({ ...r, value: e.target.value })} className="h-7 max-w-[9rem] text-[12px]">{(p?.config.options ?? []).map((o) => )} : k === "date" ? onChange({ ...r, value: e.target.value })} className={cn(inputCls, "h-7 w-36 text-[12px]")} /> : onChange({ ...r, value: k === "number" ? e.target.value : e.target.value })} placeholder="value" className={cn(inputCls, "h-7 w-32 text-[12px]")} inputMode={k === "number" ? "decimal" : undefined} />)}
); } export function SortBuilder() { const ctx = useDb(); const sorts = ctx.view.config.sorts ?? []; const set = (s: Sort[]) => ctx.patchView({ sorts: s }); const avail = ctx.props.filter((p) => !sorts.some((s) => s.property === p.id)); return (
Sort
{sorts.map((s, i) =>
set(sorts.map((x, j) => (j === i ? { ...x, property: e.target.value } : x)))} className="h-7 min-w-0 flex-1 text-[12px]">{ctx.props.map((p) => )} set(sorts.map((x, j) => (j === i ? { ...x, dir: e.target.value as any } : x)))} className="h-7 text-[12px]">
)} {avail.length > 0 && } {sorts.length === 0 && avail.length === 0 &&
No properties.
}
); }