186 lines
5.2 KiB
TypeScript
186 lines
5.2 KiB
TypeScript
import { useToggle } from 'ahooks'
|
|
import Fuse from 'fuse.js'
|
|
import {
|
|
ArrowDownAZIcon,
|
|
ArrowUpAZIcon,
|
|
BookIcon,
|
|
CheckIcon,
|
|
ChevronsUpDownIcon
|
|
} from 'lucide-react'
|
|
import {
|
|
forwardRef,
|
|
use,
|
|
useMemo,
|
|
useState,
|
|
type InputHTMLAttributes
|
|
} from 'react'
|
|
|
|
import { Button } from '@repo/ui/components/ui/button'
|
|
import {
|
|
Command,
|
|
CommandEmpty,
|
|
CommandGroup,
|
|
CommandInput,
|
|
CommandItem,
|
|
CommandList
|
|
} from '@repo/ui/components/ui/command'
|
|
import {
|
|
InputGroup,
|
|
InputGroupAddon,
|
|
InputGroupInput
|
|
} from '@repo/ui/components/ui/input-group'
|
|
import {
|
|
Popover,
|
|
PopoverContent,
|
|
PopoverTrigger
|
|
} from '@repo/ui/components/ui/popover'
|
|
import { cn } from '@repo/ui/lib/utils'
|
|
|
|
import { type Course } from './data'
|
|
|
|
interface CoursePickerProps extends Omit<
|
|
InputHTMLAttributes<HTMLInputElement>,
|
|
'value' | 'onChange'
|
|
> {
|
|
value?: Course
|
|
options: Promise<{ hits: any[] }>
|
|
onChange?: (value: any) => void
|
|
error?: any
|
|
}
|
|
|
|
const normalize = (value: string) =>
|
|
value
|
|
.toLowerCase()
|
|
.normalize('NFD')
|
|
.replace(/[\u0300-\u036f]/g, '')
|
|
.replace(/[^a-z0-9]/g, '')
|
|
|
|
export const CoursePicker = forwardRef<HTMLInputElement, CoursePickerProps>(
|
|
({ value, onChange, options, error, ...props }, ref) => {
|
|
const { hits } = use(options)
|
|
const [search, setSearch] = useState<string>('')
|
|
const [open, { set }] = useToggle()
|
|
const [sort, { toggle }] = useToggle('a-z', 'z-a')
|
|
const fuse = useMemo(() => {
|
|
return new Fuse(hits, {
|
|
keys: ['name'],
|
|
threshold: 0.3,
|
|
includeMatches: true,
|
|
getFn: (obj, path) => {
|
|
const value = obj[path as keyof typeof obj]
|
|
return typeof value === 'string' ? normalize(value) : value
|
|
}
|
|
})
|
|
}, [hits])
|
|
|
|
const filtered = useMemo(() => {
|
|
if (!search) {
|
|
return [...hits].sort((a, b) => {
|
|
const comparison = a.name.localeCompare(b.name)
|
|
return sort === 'a-z' ? comparison : -comparison
|
|
})
|
|
}
|
|
|
|
return fuse.search(search).map(({ item, matches }) => ({
|
|
...item,
|
|
matches
|
|
}))
|
|
}, [search, fuse, hits, sort])
|
|
|
|
return (
|
|
<Popover open={open} onOpenChange={set}>
|
|
<PopoverTrigger asChild>
|
|
<InputGroup>
|
|
<InputGroupInput
|
|
ref={ref}
|
|
placeholder="Curso"
|
|
value={value?.name || ''}
|
|
aria-invalid={!!error}
|
|
{...props}
|
|
/>
|
|
|
|
<InputGroupAddon>
|
|
<BookIcon />
|
|
</InputGroupAddon>
|
|
|
|
<InputGroupAddon align="inline-end">
|
|
<ChevronsUpDownIcon />
|
|
</InputGroupAddon>
|
|
</InputGroup>
|
|
</PopoverTrigger>
|
|
|
|
<PopoverContent className="lg:w-84 p-0" align="start">
|
|
<Command shouldFilter={false}>
|
|
<div className="flex">
|
|
<div className="flex-1">
|
|
<CommandInput
|
|
placeholder="Digite para pesquisar"
|
|
autoComplete="off"
|
|
onValueChange={setSearch}
|
|
/>
|
|
</div>
|
|
<div className="border-b flex items-center justify-end">
|
|
<Button
|
|
variant="link"
|
|
size="icon-sm"
|
|
tabIndex={-1}
|
|
className="cursor-pointer text-muted-foreground hover:text-accent-foreground"
|
|
onClick={toggle}
|
|
disabled={search.length > 0}
|
|
>
|
|
{sort == 'a-z' ? <ArrowDownAZIcon /> : <ArrowUpAZIcon />}
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Force rerender to reset the scroll position */}
|
|
<CommandList key={sort}>
|
|
<CommandEmpty>Nenhum resultado encontrado.</CommandEmpty>
|
|
<CommandGroup>
|
|
{filtered
|
|
.filter(
|
|
({ metadata__unit_price = 0 }) => metadata__unit_price > 0
|
|
)
|
|
.map(
|
|
({
|
|
id,
|
|
name,
|
|
access_period,
|
|
metadata__unit_price: unit_price,
|
|
matches
|
|
}) => {
|
|
return (
|
|
<CommandItem
|
|
key={id}
|
|
value={id}
|
|
className="cursor-pointer"
|
|
onSelect={() => {
|
|
onChange?.({
|
|
id,
|
|
name,
|
|
access_period: Number(access_period),
|
|
unit_price: Number(unit_price)
|
|
})
|
|
set(false)
|
|
}}
|
|
>
|
|
{name}
|
|
<CheckIcon
|
|
className={cn(
|
|
'ml-auto',
|
|
value?.id === id ? 'opacity-100' : 'opacity-0'
|
|
)}
|
|
/>
|
|
</CommandItem>
|
|
)
|
|
}
|
|
)}
|
|
</CommandGroup>
|
|
</CommandList>
|
|
</Command>
|
|
</PopoverContent>
|
|
</Popover>
|
|
)
|
|
}
|
|
)
|