add error

This commit is contained in:
2025-12-11 12:14:17 -03:00
parent 2edc7a353f
commit f728b23d3a
5 changed files with 97 additions and 33 deletions

View File

@@ -1,8 +1,7 @@
import { useRequest, useToggle } from 'ahooks'
import { debounce } from 'lodash'
import { CheckIcon, UserIcon } from 'lucide-react'
import { initials, cn } from '@repo/ui/lib/utils'
import { formatCPF } from '@brazilian-utils/brazilian-utils'
import { Avatar, AvatarFallback } from '@repo/ui/components/ui/avatar'
import { Abbr } from '@repo/ui/components/abbr'
import {
@@ -10,7 +9,7 @@ import {
InputGroupAddon,
InputGroupInput
} from '@repo/ui/components/ui/input-group'
import { initials, cn } from '@repo/ui/lib/utils'
import {
Popover,
PopoverContent,
@@ -31,19 +30,25 @@ interface AsyncComboboxProps {
title: string
onChange: (props: any) => void
onSearch: (search: string) => Promise<any[]>
error?: any
}
export function AsyncCombobox({
title,
value,
onSearch,
onChange
onChange,
error
}: AsyncComboboxProps) {
const [open, { set }] = useToggle()
const {
data = [],
loading,
runAsync
} = useRequest(onSearch, { manual: true, defaultParams: [''] })
} = useRequest(onSearch, {
manual: true,
debounceWait: 300,
defaultParams: ['']
})
return (
<Popover open={open} onOpenChange={set}>
@@ -55,6 +60,7 @@ export function AsyncCombobox({
placeholder={title}
className="cursor-pointer"
autoComplete="off"
aria-invalid={!!error}
/>
<InputGroupAddon>
<UserIcon />
@@ -73,11 +79,12 @@ export function AsyncCombobox({
<CommandInput
placeholder={title}
autoComplete="off"
onValueChange={debounce(runAsync, 300)}
onValueChange={runAsync}
/>
<CommandList>
<CommandEmpty>Nenhum resultado encontrado.</CommandEmpty>
<CommandGroup>
{data.map(({ id, name, email, cpf }) => (
<CommandItem

View File

@@ -2,20 +2,26 @@ import { z } from 'zod'
export const enrollment = z.object({
user: z
.object({
id: z.string(),
name: z.string(),
email: z.string(),
cpf: z.string()
})
.object(
{
id: z.string(),
name: z.string(),
email: z.string(),
cpf: z.string()
},
{ error: 'Escolhe um colaborador' }
)
.required(),
course: z
.object({
id: z.string(),
name: z.string(),
access_period: z.number(),
unit_price: z.number()
})
.object(
{
id: z.string(),
name: z.string(),
access_period: z.number(),
unit_price: z.number()
},
{ error: 'Escolha um curso' }
)
.required(),
deduplication_window: z
.object({

View File

@@ -1,6 +1,7 @@
import type { Route } from './+types/route'
import { useRequest, useToggle } from 'ahooks'
import { useToggle } from 'ahooks'
import { ErrorMessage } from '@hookform/error-message'
import {
CalendarIcon,
CopyIcon,
@@ -187,25 +188,55 @@ export default function Route({
<Controller
control={control}
name={`enrollments.${index}.user`}
render={({ field: { value, onChange } }) => (
<AsyncCombobox
value={value}
title="Colaborador"
onChange={onChange}
onSearch={onSearch}
/>
render={({
field: { name, value, onChange },
fieldState
}) => (
<div className="grid gap-1">
<AsyncCombobox
value={value}
title="Colaborador"
onChange={onChange}
onSearch={onSearch}
error={fieldState.error}
/>
<ErrorMessage
errors={formState.errors}
name={name}
render={({ message }) => (
<p className="text-destructive text-sm">
{message}
</p>
)}
/>
</div>
)}
/>
<Controller
control={control}
name={`enrollments.${index}.course`}
render={({ field: { value, onChange } }) => (
<FacetedFilter
value={value}
onChange={onChange}
options={courses}
/>
render={({
field: { name, value, onChange },
fieldState
}) => (
<div className="grid gap-1">
<FacetedFilter
value={value}
onChange={onChange}
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
options: Promise<{ hits: any[] }>
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 [open, { set }] = useToggle()
const { hits } = use(options)
@@ -326,6 +363,7 @@ function FacetedFilter({ value, onChange, options }: FacetedFilterProps) {
readOnly
placeholder="Curso"
value={value?.name || ''}
aria-invalid={!!error}
/>
<InputGroupAddon>
<BookIcon />