add search filter

This commit is contained in:
2025-12-18 17:12:23 -03:00
parent d95981a6eb
commit 453327982d
15 changed files with 688 additions and 247 deletions

View File

@@ -1,33 +1,25 @@
import type { ComponentPropsWithoutRef } from 'react'
type DateTimeProps = {
children: string
children: string | Date
options?: Intl.DateTimeFormatOptions
locale?: string
} & ComponentPropsWithoutRef<'time'>
}
export function DateTime({
children,
options,
locale = 'pt-BR',
...props
}: DateTimeProps) {
locale = 'pt-BR'
}: DateTimeProps): string {
const optionsInit: Intl.DateTimeFormatOptions = {
day: '2-digit',
month: '2-digit',
year: 'numeric',
hour: '2-digit',
minute: '2-digit'
year: 'numeric'
}
const date = children instanceof Date ? children : new Date(children)
const datetime = new Intl.DateTimeFormat(locale, {
...optionsInit,
...options
})
return (
<time dateTime={children} {...props}>
{datetime.format(new Date(children))}
</time>
)
return datetime.format(date)
}

View File

@@ -75,7 +75,7 @@ export function RangeCalendarFilter({
<PopoverTrigger asChild>
<Button variant="ghost" size="sm" className="cursor-pointer h-full">
<Icon /> {title}
{dateRange && (
{dateRange ? (
<>
<Separator orientation="vertical" className="mx-0.5 h-4" />
<div className="gap-1 flex">
@@ -93,11 +93,11 @@ export function RangeCalendarFilter({
</Badge>
</div>
</>
)}
) : null}
</Button>
</PopoverTrigger>
{dateRange && (
{dateRange ? (
<>
<Separator orientation="vertical" className="h-4" />
<DropdownMenu>
@@ -128,7 +128,7 @@ export function RangeCalendarFilter({
</DropdownMenuContent>
</DropdownMenu>
</>
)}
) : null}
</div>
<PopoverContent className="w-full p-0" align="start">

View File

@@ -0,0 +1,137 @@
import {
ReactElement,
ComponentProps,
ReactNode,
useState,
useRef
} from 'react'
import { useBoolean, useRequest, useToggle } from 'ahooks'
import { cn } from '../lib/utils'
import { Popover, PopoverContent, PopoverTrigger } from './ui/popover'
import {
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandList,
CommandItem
} from './ui/command'
import { Button } from './ui/button'
import { Separator } from './ui/separator'
interface SearchFilterProps<T> {
defaultValue?: string
onValueChange?: (value: string) => void
requestOptions?: object
placeholder?: string
onChange: (value: T | undefined) => void
onSearch: (search: string) => Promise<T[]>
align: 'start' | 'center' | 'end'
children: ({ loading }: { loading: boolean }) => ReactNode
render: (
item: T & {
onSelect: () => void
onClose: () => void
}
) => ReactElement<ComponentProps<typeof CommandItem>>
}
export function SearchFilter<T>({
defaultValue,
onValueChange,
placeholder,
align,
onSearch,
onChange,
children,
render,
requestOptions = {}
}: SearchFilterProps<T>) {
const inputRef = useRef<HTMLInputElement>(null)
const [open, { toggle, set }] = useToggle()
const [value, setValue] = useState(defaultValue)
const [searched, { setTrue, setFalse }] = useBoolean()
const {
data = [],
loading,
runAsync,
mutate
} = useRequest(onSearch, {
manual: true,
debounceWait: 300,
defaultParams: [''],
onSuccess: setTrue,
...requestOptions
})
return (
<Popover open={open} onOpenChange={toggle}>
<PopoverTrigger asChild>{children({ loading })}</PopoverTrigger>
<PopoverContent className="lg:w-84 p-0" align={align}>
<Command
shouldFilter={false}
className={cn(
!searched && '**:data-[slot=command-input-wrapper]:border-b-0'
)}
>
<CommandInput
ref={inputRef}
placeholder={placeholder}
autoComplete="off"
value={value}
onValueChange={async (value) => {
onValueChange?.(value)
setValue(value)
await runAsync(value)
}}
/>
{searched ? (
<CommandList>
<CommandEmpty>Nenhum resultado encontrado.</CommandEmpty>
<CommandGroup>
{data.map((item) => {
return render({
...item,
onClose: () => set(false),
onSelect: () => {
onChange?.({ ...item, q: value })
}
})
})}
</CommandGroup>
</CommandList>
) : null}
</Command>
{data.length ? (
<>
<Separator orientation="horizontal" />
<div className="p-1">
<Button
size="sm"
variant="ghost"
onClick={() => {
setValue('')
onChange?.(undefined)
setFalse()
mutate([])
if (inputRef.current) {
inputRef.current.focus()
}
}}
className="w-full cursor-pointer"
>
Limpar
</Button>
</div>
</>
) : null}
</PopoverContent>
</Popover>
)
}