add error
This commit is contained in:
@@ -1,8 +1,7 @@
|
|||||||
import { useRequest, useToggle } from 'ahooks'
|
import { useRequest, useToggle } from 'ahooks'
|
||||||
import { debounce } from 'lodash'
|
|
||||||
import { CheckIcon, UserIcon } from 'lucide-react'
|
import { CheckIcon, UserIcon } from 'lucide-react'
|
||||||
import { initials, cn } from '@repo/ui/lib/utils'
|
|
||||||
import { formatCPF } from '@brazilian-utils/brazilian-utils'
|
import { formatCPF } from '@brazilian-utils/brazilian-utils'
|
||||||
|
|
||||||
import { Avatar, AvatarFallback } from '@repo/ui/components/ui/avatar'
|
import { Avatar, AvatarFallback } from '@repo/ui/components/ui/avatar'
|
||||||
import { Abbr } from '@repo/ui/components/abbr'
|
import { Abbr } from '@repo/ui/components/abbr'
|
||||||
import {
|
import {
|
||||||
@@ -10,7 +9,7 @@ import {
|
|||||||
InputGroupAddon,
|
InputGroupAddon,
|
||||||
InputGroupInput
|
InputGroupInput
|
||||||
} from '@repo/ui/components/ui/input-group'
|
} from '@repo/ui/components/ui/input-group'
|
||||||
|
import { initials, cn } from '@repo/ui/lib/utils'
|
||||||
import {
|
import {
|
||||||
Popover,
|
Popover,
|
||||||
PopoverContent,
|
PopoverContent,
|
||||||
@@ -31,19 +30,25 @@ interface AsyncComboboxProps {
|
|||||||
title: string
|
title: string
|
||||||
onChange: (props: any) => void
|
onChange: (props: any) => void
|
||||||
onSearch: (search: string) => Promise<any[]>
|
onSearch: (search: string) => Promise<any[]>
|
||||||
|
error?: any
|
||||||
}
|
}
|
||||||
export function AsyncCombobox({
|
export function AsyncCombobox({
|
||||||
title,
|
title,
|
||||||
value,
|
value,
|
||||||
onSearch,
|
onSearch,
|
||||||
onChange
|
onChange,
|
||||||
|
error
|
||||||
}: AsyncComboboxProps) {
|
}: AsyncComboboxProps) {
|
||||||
const [open, { set }] = useToggle()
|
const [open, { set }] = useToggle()
|
||||||
const {
|
const {
|
||||||
data = [],
|
data = [],
|
||||||
loading,
|
loading,
|
||||||
runAsync
|
runAsync
|
||||||
} = useRequest(onSearch, { manual: true, defaultParams: [''] })
|
} = useRequest(onSearch, {
|
||||||
|
manual: true,
|
||||||
|
debounceWait: 300,
|
||||||
|
defaultParams: ['']
|
||||||
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Popover open={open} onOpenChange={set}>
|
<Popover open={open} onOpenChange={set}>
|
||||||
@@ -55,6 +60,7 @@ export function AsyncCombobox({
|
|||||||
placeholder={title}
|
placeholder={title}
|
||||||
className="cursor-pointer"
|
className="cursor-pointer"
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
|
aria-invalid={!!error}
|
||||||
/>
|
/>
|
||||||
<InputGroupAddon>
|
<InputGroupAddon>
|
||||||
<UserIcon />
|
<UserIcon />
|
||||||
@@ -73,11 +79,12 @@ export function AsyncCombobox({
|
|||||||
<CommandInput
|
<CommandInput
|
||||||
placeholder={title}
|
placeholder={title}
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
onValueChange={debounce(runAsync, 300)}
|
onValueChange={runAsync}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<CommandList>
|
<CommandList>
|
||||||
<CommandEmpty>Nenhum resultado encontrado.</CommandEmpty>
|
<CommandEmpty>Nenhum resultado encontrado.</CommandEmpty>
|
||||||
|
|
||||||
<CommandGroup>
|
<CommandGroup>
|
||||||
{data.map(({ id, name, email, cpf }) => (
|
{data.map(({ id, name, email, cpf }) => (
|
||||||
<CommandItem
|
<CommandItem
|
||||||
|
|||||||
@@ -2,20 +2,26 @@ import { z } from 'zod'
|
|||||||
|
|
||||||
export const enrollment = z.object({
|
export const enrollment = z.object({
|
||||||
user: z
|
user: z
|
||||||
.object({
|
.object(
|
||||||
|
{
|
||||||
id: z.string(),
|
id: z.string(),
|
||||||
name: z.string(),
|
name: z.string(),
|
||||||
email: z.string(),
|
email: z.string(),
|
||||||
cpf: z.string()
|
cpf: z.string()
|
||||||
})
|
},
|
||||||
|
{ error: 'Escolhe um colaborador' }
|
||||||
|
)
|
||||||
.required(),
|
.required(),
|
||||||
course: z
|
course: z
|
||||||
.object({
|
.object(
|
||||||
|
{
|
||||||
id: z.string(),
|
id: z.string(),
|
||||||
name: z.string(),
|
name: z.string(),
|
||||||
access_period: z.number(),
|
access_period: z.number(),
|
||||||
unit_price: z.number()
|
unit_price: z.number()
|
||||||
})
|
},
|
||||||
|
{ error: 'Escolha um curso' }
|
||||||
|
)
|
||||||
.required(),
|
.required(),
|
||||||
deduplication_window: z
|
deduplication_window: z
|
||||||
.object({
|
.object({
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import type { Route } from './+types/route'
|
import type { Route } from './+types/route'
|
||||||
|
|
||||||
import { useRequest, useToggle } from 'ahooks'
|
import { useToggle } from 'ahooks'
|
||||||
|
import { ErrorMessage } from '@hookform/error-message'
|
||||||
import {
|
import {
|
||||||
CalendarIcon,
|
CalendarIcon,
|
||||||
CopyIcon,
|
CopyIcon,
|
||||||
@@ -187,25 +188,55 @@ export default function Route({
|
|||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
name={`enrollments.${index}.user`}
|
name={`enrollments.${index}.user`}
|
||||||
render={({ field: { value, onChange } }) => (
|
render={({
|
||||||
|
field: { name, value, onChange },
|
||||||
|
fieldState
|
||||||
|
}) => (
|
||||||
|
<div className="grid gap-1">
|
||||||
<AsyncCombobox
|
<AsyncCombobox
|
||||||
value={value}
|
value={value}
|
||||||
title="Colaborador"
|
title="Colaborador"
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
onSearch={onSearch}
|
onSearch={onSearch}
|
||||||
|
error={fieldState.error}
|
||||||
/>
|
/>
|
||||||
|
<ErrorMessage
|
||||||
|
errors={formState.errors}
|
||||||
|
name={name}
|
||||||
|
render={({ message }) => (
|
||||||
|
<p className="text-destructive text-sm">
|
||||||
|
{message}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
name={`enrollments.${index}.course`}
|
name={`enrollments.${index}.course`}
|
||||||
render={({ field: { value, onChange } }) => (
|
render={({
|
||||||
|
field: { name, value, onChange },
|
||||||
|
fieldState
|
||||||
|
}) => (
|
||||||
|
<div className="grid gap-1">
|
||||||
<FacetedFilter
|
<FacetedFilter
|
||||||
value={value}
|
value={value}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
options={courses}
|
options={courses}
|
||||||
|
error={fieldState.error}
|
||||||
/>
|
/>
|
||||||
|
<ErrorMessage
|
||||||
|
errors={formState.errors}
|
||||||
|
name={name}
|
||||||
|
render={({ message }) => (
|
||||||
|
<p className="text-destructive text-sm">
|
||||||
|
{message}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -296,9 +327,15 @@ interface FacetedFilterProps {
|
|||||||
value?: Course
|
value?: Course
|
||||||
options: Promise<{ hits: any[] }>
|
options: Promise<{ hits: any[] }>
|
||||||
onChange?: (value: any) => void
|
onChange?: (value: any) => void
|
||||||
|
error?: any
|
||||||
}
|
}
|
||||||
|
|
||||||
function FacetedFilter({ value, onChange, options }: FacetedFilterProps) {
|
function FacetedFilter({
|
||||||
|
value,
|
||||||
|
onChange,
|
||||||
|
options,
|
||||||
|
error
|
||||||
|
}: FacetedFilterProps) {
|
||||||
const [search, setSearch] = useState<string>('')
|
const [search, setSearch] = useState<string>('')
|
||||||
const [open, { set }] = useToggle()
|
const [open, { set }] = useToggle()
|
||||||
const { hits } = use(options)
|
const { hits } = use(options)
|
||||||
@@ -326,6 +363,7 @@ function FacetedFilter({ value, onChange, options }: FacetedFilterProps) {
|
|||||||
readOnly
|
readOnly
|
||||||
placeholder="Curso"
|
placeholder="Curso"
|
||||||
value={value?.name || ''}
|
value={value?.name || ''}
|
||||||
|
aria-invalid={!!error}
|
||||||
/>
|
/>
|
||||||
<InputGroupAddon>
|
<InputGroupAddon>
|
||||||
<BookIcon />
|
<BookIcon />
|
||||||
|
|||||||
12
package-lock.json
generated
12
package-lock.json
generated
@@ -1913,6 +1913,17 @@
|
|||||||
"integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==",
|
"integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@hookform/error-message": {
|
||||||
|
"version": "2.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@hookform/error-message/-/error-message-2.0.1.tgz",
|
||||||
|
"integrity": "sha512-U410sAr92xgxT1idlu9WWOVjndxLdgPUHEB8Schr27C9eh7/xUnITWpCMF93s+lGiG++D4JnbSnrb5A21AdSNg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">=16.8.0",
|
||||||
|
"react-dom": ">=16.8.0",
|
||||||
|
"react-hook-form": "^7.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@hookform/resolvers": {
|
"node_modules/@hookform/resolvers": {
|
||||||
"version": "5.2.2",
|
"version": "5.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-5.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-5.2.2.tgz",
|
||||||
@@ -7326,6 +7337,7 @@
|
|||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@brazilian-utils/brazilian-utils": "^1.1.0",
|
"@brazilian-utils/brazilian-utils": "^1.1.0",
|
||||||
|
"@hookform/error-message": "^2.0.1",
|
||||||
"@hookform/resolvers": "^5.2.2",
|
"@hookform/resolvers": "^5.2.2",
|
||||||
"@radix-ui/react-alert-dialog": "^1.1.15",
|
"@radix-ui/react-alert-dialog": "^1.1.15",
|
||||||
"@radix-ui/react-avatar": "^1.1.11",
|
"@radix-ui/react-avatar": "^1.1.11",
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@brazilian-utils/brazilian-utils": "^1.1.0",
|
"@brazilian-utils/brazilian-utils": "^1.1.0",
|
||||||
|
"@hookform/error-message": "^2.0.1",
|
||||||
"@hookform/resolvers": "^5.2.2",
|
"@hookform/resolvers": "^5.2.2",
|
||||||
"@radix-ui/react-alert-dialog": "^1.1.15",
|
"@radix-ui/react-alert-dialog": "^1.1.15",
|
||||||
"@radix-ui/react-avatar": "^1.1.11",
|
"@radix-ui/react-avatar": "^1.1.11",
|
||||||
|
|||||||
Reference in New Issue
Block a user