update
This commit is contained in:
@@ -0,0 +1,34 @@
|
|||||||
|
import { z } from 'zod'
|
||||||
|
|
||||||
|
export const enrollment = z.object({
|
||||||
|
user: z
|
||||||
|
.object({
|
||||||
|
id: z.string(),
|
||||||
|
name: z.string(),
|
||||||
|
email: z.string(),
|
||||||
|
cpf: z.string()
|
||||||
|
})
|
||||||
|
.required(),
|
||||||
|
course: z
|
||||||
|
.object({
|
||||||
|
id: z.string(),
|
||||||
|
name: z.string(),
|
||||||
|
access_period: z.number(),
|
||||||
|
unit_price: z.number()
|
||||||
|
})
|
||||||
|
.required(),
|
||||||
|
deduplication_window: z
|
||||||
|
.object({
|
||||||
|
offset_days: z.number()
|
||||||
|
})
|
||||||
|
.optional(),
|
||||||
|
scheduled_for: z.date().optional()
|
||||||
|
})
|
||||||
|
|
||||||
|
export const formSchema = z.object({
|
||||||
|
enrollments: z.array(enrollment).min(1).max(100)
|
||||||
|
})
|
||||||
|
|
||||||
|
export type Schema = z.infer<typeof formSchema>
|
||||||
|
|
||||||
|
export const MAX_ITEMS = 100
|
||||||
@@ -3,21 +3,21 @@ import type { Route } from './+types/route'
|
|||||||
import { useToggle } from 'ahooks'
|
import { useToggle } from 'ahooks'
|
||||||
import {
|
import {
|
||||||
CalendarIcon,
|
CalendarIcon,
|
||||||
SearchIcon,
|
|
||||||
CopyIcon,
|
CopyIcon,
|
||||||
CopyPlusIcon,
|
CopyPlusIcon,
|
||||||
Trash2Icon,
|
Trash2Icon,
|
||||||
PlusIcon,
|
PlusIcon,
|
||||||
XIcon,
|
XIcon,
|
||||||
ChevronsUpDownIcon,
|
ChevronsUpDownIcon,
|
||||||
CheckIcon
|
CheckIcon,
|
||||||
|
BookIcon,
|
||||||
|
UserIcon
|
||||||
} from 'lucide-react'
|
} from 'lucide-react'
|
||||||
import { Link } from 'react-router'
|
import { Link } from 'react-router'
|
||||||
import { Controller, useFieldArray, useForm } from 'react-hook-form'
|
import { Controller, useFieldArray, useForm } from 'react-hook-form'
|
||||||
import { Fragment, use, useMemo, useState } from 'react'
|
import { Fragment, use, useMemo, useState } from 'react'
|
||||||
import { format } from 'date-fns'
|
import { format } from 'date-fns'
|
||||||
import { ptBR } from 'react-day-picker/locale'
|
import { ptBR } from 'react-day-picker/locale'
|
||||||
import { z } from 'zod'
|
|
||||||
import { zodResolver } from '@hookform/resolvers/zod'
|
import { zodResolver } from '@hookform/resolvers/zod'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -39,8 +39,7 @@ import {
|
|||||||
import {
|
import {
|
||||||
InputGroup,
|
InputGroup,
|
||||||
InputGroupAddon,
|
InputGroupAddon,
|
||||||
InputGroupInput,
|
InputGroupInput
|
||||||
InputGroupText
|
|
||||||
} from '@repo/ui/components/ui/input-group'
|
} from '@repo/ui/components/ui/input-group'
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
@@ -61,43 +60,11 @@ import {
|
|||||||
import { Label } from '@repo/ui/components/ui/label'
|
import { Label } from '@repo/ui/components/ui/label'
|
||||||
import { Calendar } from '@repo/ui/components/ui/calendar'
|
import { Calendar } from '@repo/ui/components/ui/calendar'
|
||||||
import { createSearch } from '@repo/util/meili'
|
import { createSearch } from '@repo/util/meili'
|
||||||
import { Await } from 'react-router'
|
|
||||||
import { cn } from '@repo/ui/lib/utils'
|
import { cn } from '@repo/ui/lib/utils'
|
||||||
import Fuse from 'fuse.js'
|
import Fuse from 'fuse.js'
|
||||||
import { useIsMobile } from '@repo/ui/hooks/use-mobile'
|
import { useIsMobile } from '@repo/ui/hooks/use-mobile'
|
||||||
|
|
||||||
const enrollment = z.object({
|
import { formSchema, type Schema, MAX_ITEMS } from './data'
|
||||||
user: z
|
|
||||||
.object({
|
|
||||||
id: z.string(),
|
|
||||||
name: z.string(),
|
|
||||||
email: z.string(),
|
|
||||||
cpf: z.string()
|
|
||||||
})
|
|
||||||
.required(),
|
|
||||||
course: z
|
|
||||||
.object({
|
|
||||||
id: z.string(),
|
|
||||||
name: z.string(),
|
|
||||||
access_period: z.number(),
|
|
||||||
unit_price: z.number()
|
|
||||||
})
|
|
||||||
.required(),
|
|
||||||
deduplication_window: z
|
|
||||||
.object({
|
|
||||||
offset_days: z.number()
|
|
||||||
})
|
|
||||||
.optional(),
|
|
||||||
scheduled_for: z.date().optional().nullable()
|
|
||||||
})
|
|
||||||
|
|
||||||
const formSchema = z.object({
|
|
||||||
enrollments: z.array(enrollment).min(1).max(100)
|
|
||||||
})
|
|
||||||
|
|
||||||
type Schema = z.infer<typeof formSchema>
|
|
||||||
|
|
||||||
const MAX_ITEMS = 100
|
|
||||||
|
|
||||||
export function meta({}: Route.MetaArgs) {
|
export function meta({}: Route.MetaArgs) {
|
||||||
return [{ title: 'Adicionar matrícula' }]
|
return [{ title: 'Adicionar matrícula' }]
|
||||||
@@ -125,7 +92,9 @@ export default function Route({
|
|||||||
const form = useForm({
|
const form = useForm({
|
||||||
resolver: zodResolver(formSchema),
|
resolver: zodResolver(formSchema),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
enrollments: [{ user: undefined, course: undefined, scheduled_for: null }]
|
enrollments: [
|
||||||
|
{ user: undefined, course: undefined, scheduled_for: undefined }
|
||||||
|
]
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
const { formState, control, handleSubmit, getValues } = form
|
const { formState, control, handleSubmit, getValues } = form
|
||||||
@@ -203,9 +172,9 @@ export default function Route({
|
|||||||
{index >= 1 && <div className="h-2.5 lg:hidden"></div>}
|
{index >= 1 && <div className="h-2.5 lg:hidden"></div>}
|
||||||
|
|
||||||
<InputGroup>
|
<InputGroup>
|
||||||
<InputGroupInput placeholder="Search..." />
|
<InputGroupInput placeholder="Colaborador" />
|
||||||
<InputGroupAddon>
|
<InputGroupAddon>
|
||||||
<SearchIcon />
|
<UserIcon />
|
||||||
</InputGroupAddon>
|
</InputGroupAddon>
|
||||||
</InputGroup>
|
</InputGroup>
|
||||||
|
|
||||||
@@ -266,9 +235,11 @@ export default function Route({
|
|||||||
type="button"
|
type="button"
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
append({
|
append({
|
||||||
|
// @ts-ignore
|
||||||
user: undefined,
|
user: undefined,
|
||||||
|
// @ts-ignore
|
||||||
course: undefined,
|
course: undefined,
|
||||||
scheduled_for: null
|
scheduled_for: undefined
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
className="cursor-pointer"
|
className="cursor-pointer"
|
||||||
@@ -333,7 +304,14 @@ function FacetedFilter({ value, onChange, options }: FacetedFilterProps) {
|
|||||||
<Popover open={open} onOpenChange={set}>
|
<Popover open={open} onOpenChange={set}>
|
||||||
<PopoverTrigger asChild>
|
<PopoverTrigger asChild>
|
||||||
<InputGroup>
|
<InputGroup>
|
||||||
<InputGroupInput readOnly value={value?.name || ''} />
|
<InputGroupInput
|
||||||
|
readOnly
|
||||||
|
placeholder="Curso"
|
||||||
|
value={value?.name || ''}
|
||||||
|
/>
|
||||||
|
<InputGroupAddon>
|
||||||
|
<BookIcon />
|
||||||
|
</InputGroupAddon>
|
||||||
<InputGroupAddon align="inline-end">
|
<InputGroupAddon align="inline-end">
|
||||||
<ChevronsUpDownIcon />
|
<ChevronsUpDownIcon />
|
||||||
</InputGroupAddon>
|
</InputGroupAddon>
|
||||||
@@ -393,21 +371,28 @@ function FacetedFilter({ value, onChange, options }: FacetedFilterProps) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function ScheduledForInput({ value, onChange }) {
|
interface ScheduledForInputProps {
|
||||||
|
value?: Date
|
||||||
|
onChange?: (value: any) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
function ScheduledForInput({ value, onChange }: ScheduledForInputProps) {
|
||||||
const today = new Date()
|
const today = new Date()
|
||||||
const tomorrow = new Date()
|
const tomorrow = new Date()
|
||||||
tomorrow.setDate(today.getDate() + 1)
|
tomorrow.setDate(today.getDate() + 1)
|
||||||
const [open, { set }] = useToggle()
|
const [open, { set }] = useToggle()
|
||||||
const [selected, setDate] = useState<Date | undefined>(value)
|
const [selected, setDate] = useState<Date | undefined>(value)
|
||||||
const displayValue = !selected
|
const displayValue = selected ? format(selected, 'dd/MM/yyyy') : ''
|
||||||
? 'Imediatamente'
|
|
||||||
: format(selected, 'dd/MM/yyyy')
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Popover open={open} onOpenChange={set}>
|
<Popover open={open} onOpenChange={set}>
|
||||||
<PopoverTrigger asChild>
|
<PopoverTrigger asChild>
|
||||||
<InputGroup>
|
<InputGroup>
|
||||||
<InputGroupInput readOnly type="search" value={displayValue} />
|
<InputGroupInput
|
||||||
|
readOnly
|
||||||
|
placeholder="Imediatamente"
|
||||||
|
value={displayValue}
|
||||||
|
/>
|
||||||
<InputGroupAddon>
|
<InputGroupAddon>
|
||||||
<CalendarIcon />
|
<CalendarIcon />
|
||||||
</InputGroupAddon>
|
</InputGroupAddon>
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ export default function Component({
|
|||||||
loaderData: { data }
|
loaderData: { data }
|
||||||
}: Route.ComponentProps) {
|
}: Route.ComponentProps) {
|
||||||
const [searchParams, setSearchParams] = useSearchParams()
|
const [searchParams, setSearchParams] = useSearchParams()
|
||||||
const s = searchParams.get('s') as string
|
const search = searchParams.get('s') as string
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container className="space-y-4">
|
<Container className="space-y-4">
|
||||||
@@ -145,14 +145,16 @@ export default function Component({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Await resolve={data}>
|
<Await resolve={data}>
|
||||||
{({ hits = [] }) => <List s={s} hits={hits as Enrollment[]} />}
|
{({ hits = [] }) => (
|
||||||
|
<List search={search} hits={hits as Enrollment[]} />
|
||||||
|
)}
|
||||||
</Await>
|
</Await>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
</Container>
|
</Container>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function List({ s, hits = [] }: { s: string; hits: Enrollment[] }) {
|
function List({ search, hits = [] }: { search: string; hits: Enrollment[] }) {
|
||||||
const fuse = useMemo(() => {
|
const fuse = useMemo(() => {
|
||||||
return new Fuse(hits, {
|
return new Fuse(hits, {
|
||||||
keys: ['course.name'],
|
keys: ['course.name'],
|
||||||
@@ -162,12 +164,12 @@ function List({ s, hits = [] }: { s: string; hits: Enrollment[] }) {
|
|||||||
}, [hits])
|
}, [hits])
|
||||||
|
|
||||||
const filtered = useMemo(() => {
|
const filtered = useMemo(() => {
|
||||||
if (!s) {
|
if (!search) {
|
||||||
return hits
|
return hits
|
||||||
}
|
}
|
||||||
|
|
||||||
return fuse.search(s).map(({ item }) => item)
|
return fuse.search(search).map(({ item }) => item)
|
||||||
}, [s, fuse, hits])
|
}, [search, fuse, hits])
|
||||||
|
|
||||||
if (filtered.length === 0) {
|
if (filtered.length === 0) {
|
||||||
return (
|
return (
|
||||||
@@ -176,7 +178,7 @@ function List({ s, hits = [] }: { s: string; hits: Enrollment[] }) {
|
|||||||
<EmptyMedia variant="icon">
|
<EmptyMedia variant="icon">
|
||||||
<BanIcon />
|
<BanIcon />
|
||||||
</EmptyMedia>
|
</EmptyMedia>
|
||||||
{s ? (
|
{search ? (
|
||||||
<>
|
<>
|
||||||
<EmptyTitle>Nada encontrado</EmptyTitle>
|
<EmptyTitle>Nada encontrado</EmptyTitle>
|
||||||
<EmptyDescription>
|
<EmptyDescription>
|
||||||
|
|||||||
Reference in New Issue
Block a user