diff --git a/apps/admin.saladeaula.digital/app/routes/_.$orgid.checkout/assigned.tsx b/apps/admin.saladeaula.digital/app/routes/_.$orgid.checkout/assigned.tsx new file mode 100644 index 0000000..e931e77 --- /dev/null +++ b/apps/admin.saladeaula.digital/app/routes/_.$orgid.checkout/assigned.tsx @@ -0,0 +1,176 @@ +import { Fragment } from 'react' +import { Trash2Icon, PlusIcon } from 'lucide-react' +import { useForm, useFieldArray, Controller, useWatch } from 'react-hook-form' +import { useParams } from 'react-router' +import { ErrorMessage } from '@hookform/error-message' +import { zodResolver } from '@hookform/resolvers/zod' + +import { Form } from '@repo/ui/components/ui/form' +import { InputGroup, InputGroupInput } from '@repo/ui/components/ui/input-group' +import { Button } from '@repo/ui/components/ui/button' +import { Separator } from '@repo/ui/components/ui/separator' + +import { + MAX_ITEMS, + type Course, + type User +} from '../_.$orgid.enrollments.add/data' +import { ScheduledForInput } from '../_.$orgid.enrollments.add/scheduled-for' +import { Cell } from '../_.$orgid.enrollments.add/route' +import { CoursePicker } from '../_.$orgid.enrollments.add/course-picker' +import { UserPicker } from '../_.$orgid.enrollments.add/user-picker' + +const emptyRow = { + user: undefined, + course: undefined, + scheduled_for: undefined +} + +type AssignedProps = { + courses: Promise<{ hits: Course[] }> +} + +export function Assigned({ courses }: AssignedProps) { + const { orgid } = useParams() + const form = useForm({ + // resolver: zodResolver(formSchema), + defaultValues: { enrollments: [emptyRow] } + }) + const { formState, control, handleSubmit, getValues, watch } = form + const { fields, insert, remove, append } = useFieldArray({ + control, + name: 'enrollments' + }) + const items = useWatch({ + control, + name: 'enrollments' + }) + + const onSearch = async (search: string) => { + const params = new URLSearchParams({ q: search }) + const r = await fetch(`/${orgid}/users.json?${params.toString()}`) + const { hits } = (await r.json()) as { hits: User[] } + return hits + } + + return ( +
+
+ {/* Header */} + <> + Colaborador + Curso + Matricular em + Valor unit. + {/**/} + + + {/* Rows */} + {fields.map((field, index) => { + const course = items?.[index]?.course + return ( + + ( +
+ + + ( +

{message}

+ )} + /> +
+ )} + /> + + ( +
+ + + ( +

{message}

+ )} + /> +
+ )} + /> + + ( + + )} + /> + + + {/* + R$ + */} + + + + + +
+ ) + })} +
+ + + + + + + + ) +} + +const currency = new Intl.NumberFormat('pt-BR', { + style: 'currency', + currency: 'BRL' +}) diff --git a/apps/admin.saladeaula.digital/app/routes/_.$orgid.checkout/bulk.tsx b/apps/admin.saladeaula.digital/app/routes/_.$orgid.checkout/bulk.tsx new file mode 100644 index 0000000..1c794c2 --- /dev/null +++ b/apps/admin.saladeaula.digital/app/routes/_.$orgid.checkout/bulk.tsx @@ -0,0 +1,162 @@ +import { Fragment } from 'react' +import { PlusIcon, Trash2Icon } from 'lucide-react' +import { useForm, useFieldArray, Controller, useWatch } from 'react-hook-form' +import { ErrorMessage } from '@hookform/error-message' +import { zodResolver } from '@hookform/resolvers/zod' +import { z } from 'zod' + +import { Form } from '@repo/ui/components/ui/form' +import { Input } from '@repo/ui/components/ui/input' +import { Button } from '@repo/ui/components/ui/button' +import { InputGroup, InputGroupInput } from '@repo/ui/components/ui/input-group' +import { Separator } from '@repo/ui/components/ui/separator' + +import { Cell } from '../_.$orgid.enrollments.add/route' +import { CoursePicker } from '../_.$orgid.enrollments.add/course-picker' +import { MAX_ITEMS, type Course } from '../_.$orgid.enrollments.add/data' + +const emptyRow = { + course: undefined, + quantity: undefined +} + +type BulkProps = { + courses: Promise<{ hits: Course[] }> +} + +const item = z.object({ + course: z + .object( + { + id: z.string(), + name: z.string(), + access_period: z.number(), + unit_price: z.number() + }, + { error: 'Escolha um curso' } + ) + .required(), + quantity: z.number() +}) + +const formSchema = z.object({ + items: z.array(item).min(1).max(MAX_ITEMS) +}) + +type Schema = z.infer + +export function Bulk({ courses }: BulkProps) { + const form = useForm({ + resolver: zodResolver(formSchema), + defaultValues: { items: [emptyRow] } + }) + const { formState, control, handleSubmit } = form + const { fields, remove, append } = useFieldArray({ + control, + name: 'items' + }) + const items = useWatch({ + control, + name: 'items' + }) + + const onSubmit = async (data: Schema) => { + console.log(data) + } + + return ( +
+ +
+ {/* Header */} + <> + Curso + Quantidade + Valor unit. + {/**/} + + + {/* Rows */} + {fields.map((field, index) => { + const course = items?.[index]?.course + + return ( + + ( +
+ + + ( +

{message}

+ )} + /> +
+ )} + /> + + + + {/* + R$ + */} + + + + + +
+ ) + })} +
+ + + + + + + + + ) +} + +const currency = new Intl.NumberFormat('pt-BR', { + style: 'currency', + currency: 'BRL' +}) diff --git a/apps/admin.saladeaula.digital/app/routes/_.$orgid.checkout/route.tsx b/apps/admin.saladeaula.digital/app/routes/_.$orgid.checkout/route.tsx index 22a00c1..58a9941 100644 --- a/apps/admin.saladeaula.digital/app/routes/_.$orgid.checkout/route.tsx +++ b/apps/admin.saladeaula.digital/app/routes/_.$orgid.checkout/route.tsx @@ -1,15 +1,83 @@ -import { Card, CardContent } from '@repo/ui/components/ui/card' import type { Route } from './+types/route' +import { useToggle } from 'ahooks' + +import { + Card, + CardContent, + CardHeader, + CardDescription, + CardTitle +} from '@repo/ui/components/ui/card' +import { Switch } from '@repo/ui/components/ui/switch' +import { createSearch } from '@repo/util/meili' +import { cloudflareContext } from '@repo/auth/context' +import { Label } from '@repo/ui/components/ui/label' + +import { Assigned } from './assigned' +import { Bulk } from './bulk' + export function meta({}: Route.MetaArgs) { return [{ title: '' }] } -export default function Route() { +export async function loader({ params, context, request }: Route.LoaderArgs) { + const cloudflare = context.get(cloudflareContext) + const courses = createSearch({ + index: 'saladeaula_courses', + sort: ['created_at:desc'], + filter: 'unlisted NOT EXISTS', + hitsPerPage: 100, + env: cloudflare.env + }) + + return { courses } +} + +export default function Route({ + loaderData: { courses } +}: Route.ComponentProps) { + const [state, { toggle }] = useToggle('bulk', 'assigned') + return (
- ,,, + + Comprar matrículas + + Siga os passos abaixo para comprar novas matrículas. + + + + + + + {state == 'assigned' ? ( + + ) : ( + + )} +
) diff --git a/apps/admin.saladeaula.digital/app/routes/_.$orgid.enrollments.add/course-picker.tsx b/apps/admin.saladeaula.digital/app/routes/_.$orgid.enrollments.add/course-picker.tsx new file mode 100644 index 0000000..5cdca5e --- /dev/null +++ b/apps/admin.saladeaula.digital/app/routes/_.$orgid.enrollments.add/course-picker.tsx @@ -0,0 +1,155 @@ +import { use, useState, useMemo } from 'react' +import { useToggle } from 'ahooks' +import Fuse from 'fuse.js' +import { + ChevronsUpDownIcon, + CheckIcon, + BookIcon, + ArrowDownAZIcon, + ArrowUpAZIcon +} from 'lucide-react' + +import { cn } from '@repo/ui/lib/utils' +import { Button } from '@repo/ui/components/ui/button' +import { + InputGroup, + InputGroupAddon, + InputGroupInput +} from '@repo/ui/components/ui/input-group' +import { + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList +} from '@repo/ui/components/ui/command' +import { + Popover, + PopoverContent, + PopoverTrigger +} from '@repo/ui/components/ui/popover' + +import { type Course } from './data' + +interface CoursePickerProps { + value?: Course + options: Promise<{ hits: any[] }> + onChange?: (value: any) => void + error?: any +} + +export function CoursePicker({ + value, + onChange, + options, + error +}: CoursePickerProps) { + const { hits } = use(options) + const [search, setSearch] = useState('') + 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 + }) + }, [hits]) + + const filtered = useMemo(() => { + const results = !search ? hits : fuse.search(search).map(({ item }) => item) + + return results.sort((a, b) => { + const comparison = a.name.localeCompare(b.name) + return sort === 'a-z' ? comparison : -comparison + }) + }, [search, fuse, hits, sort]) + + return ( + + + + + + + + + + + + + + + +
+
+ +
+
+ +
+
+ {/* Force rerender to reset the scroll position */} + + Nenhum resultado encontrado. + + {filtered + .filter( + ({ metadata__unit_price = 0 }) => metadata__unit_price > 0 + ) + .map( + ({ + id, + name, + access_period, + metadata__unit_price: unit_price + }) => ( + { + onChange?.({ + id, + name, + access_period: Number(access_period), + unit_price: Number(unit_price) + }) + set(false) + }} + > + {name} + + + ) + )} + + +
+
+
+ ) +} diff --git a/apps/admin.saladeaula.digital/app/routes/_.$orgid.enrollments.add/data.ts b/apps/admin.saladeaula.digital/app/routes/_.$orgid.enrollments.add/data.ts index dbfb0c7..a0af187 100644 --- a/apps/admin.saladeaula.digital/app/routes/_.$orgid.enrollments.add/data.ts +++ b/apps/admin.saladeaula.digital/app/routes/_.$orgid.enrollments.add/data.ts @@ -41,3 +41,22 @@ export const formSchema = z.object({ }) export type Schema = z.infer + +export type User = { + id: string + name: string + email: string + cpf: string +} + +export type Course = { + id: string + name: string + access_period: number + metadata__unit_price?: number +} + +export type Enrolled = { + status: 'fail' | 'success' + input_record: { user: any; course: any } +} diff --git a/apps/admin.saladeaula.digital/app/routes/_.$orgid.enrollments.add/route.tsx b/apps/admin.saladeaula.digital/app/routes/_.$orgid.enrollments.add/route.tsx index 3400b12..54fbcc9 100644 --- a/apps/admin.saladeaula.digital/app/routes/_.$orgid.enrollments.add/route.tsx +++ b/apps/admin.saladeaula.digital/app/routes/_.$orgid.enrollments.add/route.tsx @@ -1,41 +1,24 @@ import type { Route } from './+types/route' -import Fuse from 'fuse.js' +import { Fragment, use, useEffect, type ReactNode } from 'react' import { useRequest, useToggle } from 'ahooks' import { ErrorMessage } from '@hookform/error-message' import { - CalendarIcon, CopyIcon, CopyPlusIcon, Trash2Icon, PlusIcon, - XIcon, - ChevronsUpDownIcon, - CheckIcon, - BookIcon, - ArrowDownAZIcon, - ArrowUpAZIcon, - AlertTriangleIcon, - UserIcon, EllipsisIcon } from 'lucide-react' import { redirect, Link, useParams, useFetcher } from 'react-router' import { Controller, useFieldArray, useForm } from 'react-hook-form' -import { Fragment, use, useEffect, useMemo, useState } from 'react' -import { format } from 'date-fns' -import { ptBR } from 'react-day-picker/locale' import { zodResolver } from '@hookform/resolvers/zod' -import { formatCPF } from '@brazilian-utils/brazilian-utils' import { pick } from 'ramda' -import { DateTime } from '@repo/ui/components/datetime' -import { Avatar, AvatarFallback } from '@repo/ui/components/ui/avatar' -import { Abbr } from '@repo/ui/components/abbr' import { Command, CommandEmpty, CommandGroup, - CommandInput, CommandItem, CommandList } from '@repo/ui/components/ui/command' @@ -47,11 +30,6 @@ import { BreadcrumbPage, BreadcrumbSeparator } from '@repo/ui/components/ui/breadcrumb' -import { - InputGroup, - InputGroupAddon, - InputGroupInput -} from '@repo/ui/components/ui/input-group' import { Card, CardAction, @@ -60,6 +38,7 @@ import { CardHeader, CardTitle } from '@repo/ui/components/ui/card' +import { DateTime } from '@repo/ui/components/datetime' import { Spinner } from '@repo/ui/components/ui/spinner' import { Input } from '@repo/ui/components/ui/input' import { Button } from '@repo/ui/components/ui/button' @@ -70,25 +49,33 @@ import { PopoverTrigger } from '@repo/ui/components/ui/popover' import { Label } from '@repo/ui/components/ui/label' -import { Calendar } from '@repo/ui/components/ui/calendar' import { createSearch } from '@repo/util/meili' -import { initials, cn } from '@repo/ui/lib/utils' import { HttpMethod, request as req } from '@repo/util/request' import { useIsMobile } from '@repo/ui/hooks/use-mobile' import { cloudflareContext } from '@repo/auth/context' -import { SearchFilter } from '@repo/ui/components/search-filter' -import { formSchema, type Schema, MAX_ITEMS } from './data' +import { + MAX_ITEMS, + formSchema, + type Schema, + type Course, + type User, + type Enrolled +} from './data' +import { ScheduledForInput } from './scheduled-for' +import { CoursePicker } from './course-picker' +import { UserPicker } from './user-picker' + +const emptyRow = { + user: undefined, + course: undefined, + scheduled_for: undefined +} export function meta({}: Route.MetaArgs) { return [{ title: 'Adicionar matrícula' }] } -type Enrolled = { - status: 'fail' | 'success' - input_record: { user: any; course: any } -} - export async function loader({ params, context, request }: Route.LoaderArgs) { const url = new URL(request.url) const submissionId = url.searchParams.get('submission') @@ -129,12 +116,6 @@ export async function action({ params, request, context }: Route.ActionArgs) { return redirect(`/${params.orgid}/enrollments/${data.sk}/submitted`) } -const emptyRow = { - user: undefined, - course: undefined, - scheduled_for: undefined -} - export default function Route({ loaderData: { courses, submission } }: Route.ComponentProps) { @@ -161,7 +142,7 @@ export default function Route({ const onSearch = async (search: string) => { const params = new URLSearchParams({ q: search }) const r = await fetch(`/${orgid}/users.json?${params.toString()}`) - const { hits } = (await r.json()) as { hits: any[] } + const { hits } = (await r.json()) as { hits: User[] } return hits } @@ -228,16 +209,10 @@ export default function Route({
{/* Header */} <> -
- Colaborador -
-
- Curso -
-
- Matriculado em -
-
{/**/}
+ Colaborador + Curso + Matricular em + {/**/} {/* Rows */} @@ -255,109 +230,13 @@ export default function Route({ fieldState }) => (
- ( - { - onSelect() - onClose() - }} - > -
- - - {initials(name)} - - + fieldState={fieldState} + /> -
    -
  • - {name} -
  • -
  • - {email} -
  • - - {cpf ? ( -
  • - {formatCPF(cpf)} -
  • - ) : ( -
  • - - Inelegível -
  • - )} -
-
- - -
- )} - > - {({ loading }) => ( - - - - - - - {value && ( - - - - )} - - {loading && ( - - - - )} - - )} -
(
- + - onChange?: (value: any) => void - error?: any -} - -function FacetedFilter({ - value, - onChange, - options, - error -}: FacetedFilterProps) { - const [search, setSearch] = useState('') - const [open, { set }] = useToggle() - const [sort, { toggle }] = useToggle('a-z', 'z-a') - const { hits } = use(options) - const fuse = useMemo(() => { - return new Fuse(hits, { - keys: ['name'], - threshold: 0.3, - includeMatches: true - }) - }, [hits]) - - const filtered = useMemo(() => { - const results = !search ? hits : fuse.search(search).map(({ item }) => item) - - return results.sort((a, b) => { - const comparison = a.name.localeCompare(b.name) - return sort === 'a-z' ? comparison : -comparison - }) - }, [search, fuse, hits, sort]) - - return ( - - - - - - - - - - - - - - - -
-
- -
-
- -
-
- {/* Force rerender to reset the scroll position */} - - Nenhum resultado encontrado. - - {filtered - .filter( - ({ metadata__unit_price = 0 }) => metadata__unit_price > 0 - ) - .map( - ({ - id, - name, - access_period, - metadata__unit_price: unit_price - }) => ( - { - onChange?.({ - id, - name, - access_period: Number(access_period), - unit_price: Number(unit_price) - }) - set(false) - }} - > - {name} - - - ) - )} - - -
-
-
- ) -} - -interface ScheduledForInputProps { - value?: Date - onChange?: (value: Date | undefined) => void -} - -function ScheduledForInput({ value, onChange }: ScheduledForInputProps) { - const today = new Date() - const tomorrow = new Date() - tomorrow.setDate(today.getDate() + 1) - const [open, { set }] = useToggle() - const [selected, setDate] = useState(value) - const display = selected ? format(selected, 'dd/MM/yyyy') : '' - - return ( - - - - - - - - - - {selected && ( - - - - )} - - - - - { - setDate(date) - onChange?.(date) - set(false) - }} - disabled={{ before: tomorrow }} - startMonth={new Date(today.getFullYear(), 0)} - endMonth={new Date(today.getFullYear() + 3, 11)} - captionLayout="dropdown" - locale={ptBR} - /> - - - ) -} - function DuplicateRowMultipleTimes({ index, duplicateRow @@ -747,7 +431,7 @@ function ActionMenu() { const r = await fetch(`/~/api/orgs/${orgid}/enrollments/submissions`, { method: 'GET' }) - return await r.json() + return (await r.json()) as { items: { sk: string }[] } }, { manual: true } ) @@ -810,3 +494,11 @@ function ActionMenu() { ) } + +export function Cell({ children }: { children?: ReactNode }) { + return ( +
+ {children} +
+ ) +} diff --git a/apps/admin.saladeaula.digital/app/routes/_.$orgid.enrollments.add/scheduled-for.tsx b/apps/admin.saladeaula.digital/app/routes/_.$orgid.enrollments.add/scheduled-for.tsx new file mode 100644 index 0000000..fa1b600 --- /dev/null +++ b/apps/admin.saladeaula.digital/app/routes/_.$orgid.enrollments.add/scheduled-for.tsx @@ -0,0 +1,86 @@ +import { CalendarIcon, XIcon } from 'lucide-react' + +import { useState } from 'react' +import { useToggle } from 'ahooks' +import { format } from 'date-fns' +import { ptBR } from 'react-day-picker/locale' + +import { Button } from '@repo/ui/components/ui/button' +import { Calendar } from '@repo/ui/components/ui/calendar' +import { + InputGroup, + InputGroupAddon, + InputGroupInput +} from '@repo/ui/components/ui/input-group' +import { + Popover, + PopoverContent, + PopoverTrigger +} from '@repo/ui/components/ui/popover' + +interface ScheduledForInputProps { + value?: Date + onChange?: (value: Date | undefined) => void +} + +export function ScheduledForInput({ value, onChange }: ScheduledForInputProps) { + const today = new Date() + const tomorrow = new Date() + tomorrow.setDate(today.getDate() + 1) + const [open, { set }] = useToggle() + const [selected, setDate] = useState(value) + const display = selected ? format(selected, 'dd/MM/yyyy') : '' + + return ( + + + + + + + + + + {selected && ( + + + + )} + + + + + { + setDate(date) + onChange?.(date) + set(false) + }} + disabled={{ before: tomorrow }} + startMonth={new Date(today.getFullYear(), 0)} + endMonth={new Date(today.getFullYear() + 3, 11)} + captionLayout="dropdown" + locale={ptBR} + /> + + + ) +} diff --git a/apps/admin.saladeaula.digital/app/routes/_.$orgid.enrollments.add/user-picker.tsx b/apps/admin.saladeaula.digital/app/routes/_.$orgid.enrollments.add/user-picker.tsx new file mode 100644 index 0000000..d056f83 --- /dev/null +++ b/apps/admin.saladeaula.digital/app/routes/_.$orgid.enrollments.add/user-picker.tsx @@ -0,0 +1,126 @@ +import type { ControllerFieldState } from 'react-hook-form' +import { XIcon, CheckIcon, AlertTriangleIcon, UserIcon } from 'lucide-react' +import { formatCPF } from '@brazilian-utils/brazilian-utils' + +import { cn, initials } from '@repo/ui/lib/utils' +import { Button } from '@repo/ui/components/ui/button' +import { Avatar, AvatarFallback } from '@repo/ui/components/ui/avatar' +import { Abbr } from '@repo/ui/components/abbr' +import { Spinner } from '@repo/ui/components/ui/spinner' +import { + InputGroup, + InputGroupAddon, + InputGroupInput +} from '@repo/ui/components/ui/input-group' +import { CommandItem } from '@repo/ui/components/ui/command' +import { SearchFilter } from '@repo/ui/components/search-filter' + +import type { User } from './data' + +interface UserPickerProps { + value?: User + onChange: (value: User | null) => void + onSearch: (search: string) => Promise + fieldState: ControllerFieldState +} + +export function UserPicker({ + value, + onChange, + onSearch, + fieldState +}: UserPickerProps) { + return ( + ( + { + onSelect() + onClose() + }} + > +
+ + + {initials(name)} + + + +
    +
  • + {name} +
  • +
  • + {email} +
  • + + {cpf ? ( +
  • + {formatCPF(cpf)} +
  • + ) : ( +
  • + + Inelegível +
  • + )} +
+
+ + +
+ )} + > + {({ loading }) => ( + + + + + + + {value && ( + + + + )} + + {loading && ( + + + + )} + + )} +
+ ) +} diff --git a/apps/studio.saladeaula.digital/app/routes/edit.tsx b/apps/studio.saladeaula.digital/app/routes/edit.tsx index 293d5a1..96bbe9f 100644 --- a/apps/studio.saladeaula.digital/app/routes/edit.tsx +++ b/apps/studio.saladeaula.digital/app/routes/edit.tsx @@ -162,7 +162,6 @@ export default function Component({ function Editing() { const course = useAsyncValue() as Course const fetcher = useFetcher() - const form = useForm({ resolver: zodResolver(formSchema), defaultValues: { diff --git a/enrollments-events/app/docuseal.py b/enrollments-events/app/docuseal.py index 50240c6..7a60f8f 100644 --- a/enrollments-events/app/docuseal.py +++ b/enrollments-events/app/docuseal.py @@ -32,7 +32,7 @@ def create_submission_from_pdf( submitters: list[Submitter], email_message: EmailMessage, **kwargs, -): +) -> dict: r = requests.post( url=f'{DOCUSEAL_API}/api/submissions/pdf', json={ @@ -52,4 +52,4 @@ def create_submission_from_pdf( ) r.raise_for_status() - return True + return r.json() diff --git a/enrollments-events/app/events/ask_to_sign.py b/enrollments-events/app/events/ask_to_sign.py index 1513675..17e8a2e 100644 --- a/enrollments-events/app/events/ask_to_sign.py +++ b/enrollments-events/app/events/ask_to_sign.py @@ -36,7 +36,7 @@ def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool: file_bytes = _get_file_bytes(new_image['cert']['s3_uri']) file_base64 = base64.b64encode(file_bytes) - create_submission_from_pdf( + r = create_submission_from_pdf( filename=new_image['id'], file=file_base64.decode('utf-8'), email_message={ @@ -52,6 +52,7 @@ def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool: }, ], ) + logger.debug(r) return True diff --git a/packages/ui/src/components/search-filter.tsx b/packages/ui/src/components/search-filter.tsx index ff5f49c..60a06c3 100644 --- a/packages/ui/src/components/search-filter.tsx +++ b/packages/ui/src/components/search-filter.tsx @@ -25,7 +25,7 @@ interface SearchFilterProps { onValueChange?: (value: string) => void requestOptions?: object placeholder?: string - onChange: (value: T | undefined) => void + onChange: (value: T | null) => void onSearch: (search: string) => Promise align: 'start' | 'center' | 'end' children: ({ loading }: { loading: boolean }) => ReactNode @@ -117,7 +117,7 @@ export function SearchFilter({ variant="ghost" onClick={() => { setValue('') - onChange?.(undefined) + onChange?.(null) setFalse() mutate([])