fix
This commit is contained in:
@@ -118,6 +118,7 @@ export function Assigned({ courses }: AssignedProps) {
|
|||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
options={courses}
|
options={courses}
|
||||||
error={fieldState.error}
|
error={fieldState.error}
|
||||||
|
readOnly
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<ErrorMessage
|
<ErrorMessage
|
||||||
@@ -142,6 +143,7 @@ export function Assigned({ courses }: AssignedProps) {
|
|||||||
<InputGroup>
|
<InputGroup>
|
||||||
<InputGroupInput
|
<InputGroupInput
|
||||||
className="pointer-events-none"
|
className="pointer-events-none"
|
||||||
|
tabIndex={-1}
|
||||||
readOnly
|
readOnly
|
||||||
value={currency.format(unit_price)}
|
value={currency.format(unit_price)}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -20,8 +20,7 @@ import { CoursePicker } from '../_.$orgid.enrollments.add/course-picker'
|
|||||||
import { MAX_ITEMS, type Course } from '../_.$orgid.enrollments.add/data'
|
import { MAX_ITEMS, type Course } from '../_.$orgid.enrollments.add/data'
|
||||||
|
|
||||||
const emptyRow = {
|
const emptyRow = {
|
||||||
course: undefined,
|
course: undefined
|
||||||
quantity: undefined
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type BulkProps = {
|
type BulkProps = {
|
||||||
@@ -54,8 +53,15 @@ export function Bulk({ courses }: BulkProps) {
|
|||||||
resolver: zodResolver(formSchema),
|
resolver: zodResolver(formSchema),
|
||||||
defaultValues: { items: [emptyRow] }
|
defaultValues: { items: [emptyRow] }
|
||||||
})
|
})
|
||||||
const { formState, control, handleSubmit, register, setValue, getValues } =
|
const {
|
||||||
form
|
formState,
|
||||||
|
control,
|
||||||
|
handleSubmit,
|
||||||
|
register,
|
||||||
|
setValue,
|
||||||
|
getValues,
|
||||||
|
setFocus
|
||||||
|
} = form
|
||||||
const { fields, remove, append } = useFieldArray({
|
const { fields, remove, append } = useFieldArray({
|
||||||
control,
|
control,
|
||||||
name: 'items'
|
name: 'items'
|
||||||
@@ -96,15 +102,19 @@ export function Bulk({ courses }: BulkProps) {
|
|||||||
control={control}
|
control={control}
|
||||||
name={`items.${index}.course`}
|
name={`items.${index}.course`}
|
||||||
render={({
|
render={({
|
||||||
field: { name, value, onChange },
|
field: { name, value, onChange, ref },
|
||||||
fieldState
|
fieldState
|
||||||
}) => (
|
}) => (
|
||||||
<div className="grid gap-1">
|
<div className="grid gap-1">
|
||||||
<CoursePicker
|
<CoursePicker
|
||||||
|
ref={ref}
|
||||||
|
name={name}
|
||||||
|
autoFocus={index === 0}
|
||||||
value={value}
|
value={value}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
options={courses}
|
options={courses}
|
||||||
error={fieldState.error}
|
error={fieldState.error}
|
||||||
|
readOnly
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<ErrorMessage
|
<ErrorMessage
|
||||||
@@ -139,6 +149,7 @@ export function Bulk({ courses }: BulkProps) {
|
|||||||
<InputGroupAddon align="inline-end">
|
<InputGroupAddon align="inline-end">
|
||||||
<InputGroupButton
|
<InputGroupButton
|
||||||
type="button"
|
type="button"
|
||||||
|
tabIndex={-1}
|
||||||
size="icon-xs"
|
size="icon-xs"
|
||||||
className="border cursor-pointer"
|
className="border cursor-pointer"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@@ -157,6 +168,7 @@ export function Bulk({ courses }: BulkProps) {
|
|||||||
<InputGroupAddon align="inline-end">
|
<InputGroupAddon align="inline-end">
|
||||||
<InputGroupButton
|
<InputGroupButton
|
||||||
type="button"
|
type="button"
|
||||||
|
tabIndex={-1}
|
||||||
size="icon-xs"
|
size="icon-xs"
|
||||||
className="border cursor-pointer"
|
className="border cursor-pointer"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@@ -172,6 +184,7 @@ export function Bulk({ courses }: BulkProps) {
|
|||||||
|
|
||||||
<InputGroup>
|
<InputGroup>
|
||||||
<InputGroupInput
|
<InputGroupInput
|
||||||
|
tabIndex={-1}
|
||||||
className="pointer-events-none"
|
className="pointer-events-none"
|
||||||
readOnly
|
readOnly
|
||||||
value={currency.format(course?.unit_price || 0)}
|
value={currency.format(course?.unit_price || 0)}
|
||||||
@@ -180,6 +193,7 @@ export function Bulk({ courses }: BulkProps) {
|
|||||||
|
|
||||||
<InputGroup>
|
<InputGroup>
|
||||||
<InputGroupInput
|
<InputGroupInput
|
||||||
|
tabIndex={-1}
|
||||||
className="pointer-events-none"
|
className="pointer-events-none"
|
||||||
readOnly
|
readOnly
|
||||||
value={currency.format(
|
value={currency.format(
|
||||||
@@ -207,8 +221,13 @@ export function Bulk({ courses }: BulkProps) {
|
|||||||
|
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
// @ts-ignore
|
onClick={() => {
|
||||||
onClick={() => append(emptyRow)}
|
// @ts-ignore
|
||||||
|
append(emptyRow, { shouldFocus: false })
|
||||||
|
queueMicrotask(() => {
|
||||||
|
setFocus(`items.${fields.length}.course`)
|
||||||
|
})
|
||||||
|
}}
|
||||||
className="cursor-pointer"
|
className="cursor-pointer"
|
||||||
disabled={fields.length == MAX_ITEMS}
|
disabled={fields.length == MAX_ITEMS}
|
||||||
variant="outline"
|
variant="outline"
|
||||||
|
|||||||
@@ -1,4 +1,10 @@
|
|||||||
import { use, useState, useMemo } from 'react'
|
import {
|
||||||
|
use,
|
||||||
|
useState,
|
||||||
|
useMemo,
|
||||||
|
forwardRef,
|
||||||
|
type InputHTMLAttributes
|
||||||
|
} from 'react'
|
||||||
import { useToggle } from 'ahooks'
|
import { useToggle } from 'ahooks'
|
||||||
import Fuse from 'fuse.js'
|
import Fuse from 'fuse.js'
|
||||||
import {
|
import {
|
||||||
@@ -32,124 +38,129 @@ import {
|
|||||||
|
|
||||||
import { type Course } from './data'
|
import { type Course } from './data'
|
||||||
|
|
||||||
interface CoursePickerProps {
|
interface CoursePickerProps extends Omit<
|
||||||
|
InputHTMLAttributes<HTMLInputElement>,
|
||||||
|
'value' | 'onChange'
|
||||||
|
> {
|
||||||
value?: Course
|
value?: Course
|
||||||
options: Promise<{ hits: any[] }>
|
options: Promise<{ hits: any[] }>
|
||||||
onChange?: (value: any) => void
|
onChange?: (value: any) => void
|
||||||
error?: any
|
error?: any
|
||||||
}
|
}
|
||||||
|
|
||||||
export function CoursePicker({
|
export const CoursePicker = forwardRef<HTMLInputElement, CoursePickerProps>(
|
||||||
value,
|
({ value, onChange, options, error, ...props }, ref) => {
|
||||||
onChange,
|
const { hits } = use(options)
|
||||||
options,
|
const [search, setSearch] = useState<string>('')
|
||||||
error
|
const [open, { set }] = useToggle()
|
||||||
}: CoursePickerProps) {
|
const [sort, { toggle }] = useToggle('a-z', 'z-a')
|
||||||
const { hits } = use(options)
|
const fuse = useMemo(() => {
|
||||||
const [search, setSearch] = useState<string>('')
|
return new Fuse(hits, {
|
||||||
const [open, { set }] = useToggle()
|
keys: ['name'],
|
||||||
const [sort, { toggle }] = useToggle('a-z', 'z-a')
|
threshold: 0.3,
|
||||||
const fuse = useMemo(() => {
|
includeMatches: true
|
||||||
return new Fuse(hits, {
|
})
|
||||||
keys: ['name'],
|
}, [hits])
|
||||||
threshold: 0.3,
|
|
||||||
includeMatches: true
|
|
||||||
})
|
|
||||||
}, [hits])
|
|
||||||
|
|
||||||
const filtered = useMemo(() => {
|
const filtered = useMemo(() => {
|
||||||
const results = !search ? hits : fuse.search(search).map(({ item }) => item)
|
const results = !search
|
||||||
|
? hits
|
||||||
|
: fuse.search(search).map(({ item }) => item)
|
||||||
|
|
||||||
return results.sort((a, b) => {
|
return results.sort((a, b) => {
|
||||||
const comparison = a.name.localeCompare(b.name)
|
const comparison = a.name.localeCompare(b.name)
|
||||||
return sort === 'a-z' ? comparison : -comparison
|
return sort === 'a-z' ? comparison : -comparison
|
||||||
})
|
})
|
||||||
}, [search, fuse, hits, sort])
|
}, [search, fuse, hits, sort])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Popover open={open} onOpenChange={set}>
|
<Popover open={open} onOpenChange={set}>
|
||||||
<PopoverTrigger asChild>
|
<PopoverTrigger asChild>
|
||||||
<InputGroup>
|
<InputGroup>
|
||||||
<InputGroupInput
|
<InputGroupInput
|
||||||
readOnly
|
ref={ref}
|
||||||
placeholder="Curso"
|
placeholder="Curso"
|
||||||
value={value?.name || ''}
|
value={value?.name || ''}
|
||||||
aria-invalid={!!error}
|
aria-invalid={!!error}
|
||||||
/>
|
{...props}
|
||||||
<InputGroupAddon>
|
/>
|
||||||
<BookIcon />
|
|
||||||
</InputGroupAddon>
|
|
||||||
<InputGroupAddon align="inline-end">
|
|
||||||
<ChevronsUpDownIcon />
|
|
||||||
</InputGroupAddon>
|
|
||||||
</InputGroup>
|
|
||||||
</PopoverTrigger>
|
|
||||||
|
|
||||||
<PopoverContent className="lg:w-84 p-0" align="start">
|
<InputGroupAddon>
|
||||||
<Command shouldFilter={false}>
|
<BookIcon />
|
||||||
<div className="flex">
|
</InputGroupAddon>
|
||||||
<div className="flex-1">
|
|
||||||
<CommandInput
|
<InputGroupAddon align="inline-end">
|
||||||
placeholder="Curso"
|
<ChevronsUpDownIcon />
|
||||||
autoComplete="off"
|
</InputGroupAddon>
|
||||||
onValueChange={setSearch}
|
</InputGroup>
|
||||||
/>
|
</PopoverTrigger>
|
||||||
|
|
||||||
|
<PopoverContent className="lg:w-84 p-0" align="start">
|
||||||
|
<Command shouldFilter={false}>
|
||||||
|
<div className="flex">
|
||||||
|
<div className="flex-1">
|
||||||
|
<CommandInput
|
||||||
|
placeholder="Curso"
|
||||||
|
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"
|
||||||
|
onClick={toggle}
|
||||||
|
>
|
||||||
|
{sort == 'a-z' ? <ArrowDownAZIcon /> : <ArrowUpAZIcon />}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="border-b flex items-center justify-end">
|
{/* Force rerender to reset the scroll position */}
|
||||||
<Button
|
<CommandList key={sort}>
|
||||||
variant="link"
|
<CommandEmpty>Nenhum resultado encontrado.</CommandEmpty>
|
||||||
size="icon-sm"
|
<CommandGroup>
|
||||||
tabIndex={-1}
|
{filtered
|
||||||
className="cursor-pointer text-muted-foreground"
|
.filter(
|
||||||
onClick={toggle}
|
({ metadata__unit_price = 0 }) => metadata__unit_price > 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
|
|
||||||
}) => (
|
|
||||||
<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>
|
|
||||||
)
|
)
|
||||||
)}
|
.map(
|
||||||
</CommandGroup>
|
({
|
||||||
</CommandList>
|
id,
|
||||||
</Command>
|
name,
|
||||||
</PopoverContent>
|
access_period,
|
||||||
</Popover>
|
metadata__unit_price: unit_price
|
||||||
)
|
}) => (
|
||||||
}
|
<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>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|||||||
@@ -263,6 +263,7 @@ export default function Route({
|
|||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
options={courses}
|
options={courses}
|
||||||
error={fieldState.error}
|
error={fieldState.error}
|
||||||
|
readOnly
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<ErrorMessage
|
<ErrorMessage
|
||||||
|
|||||||
Reference in New Issue
Block a user