import type { Route } from './+types/route' import { ErrorMessage } from '@hookform/error-message' import { zodResolver } from '@hookform/resolvers/zod' import { useRequest, useToggle } from 'ahooks' import { CircleQuestionMarkIcon, CopyIcon, CopyPlusIcon, EllipsisIcon, PlusIcon, Trash2Icon } from 'lucide-react' import { Fragment, use, useEffect, type ReactNode } from 'react' import { Controller, useFieldArray, useForm } from 'react-hook-form' import { Link, redirect, useFetcher, useParams } from 'react-router' import { cloudflareContext } from '@repo/auth/context' import { DateTime } from '@repo/ui/components/datetime' import { Breadcrumb, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator } from '@repo/ui/components/ui/breadcrumb' import { Button } from '@repo/ui/components/ui/button' import { Card, CardAction, CardContent, CardDescription, CardHeader, CardTitle } from '@repo/ui/components/ui/card' import { Command, CommandEmpty, CommandGroup, CommandItem, CommandList } from '@repo/ui/components/ui/command' import { HoverCard, HoverCardContent, HoverCardTrigger } from '@repo/ui/components/ui/hover-card' import { Input } from '@repo/ui/components/ui/input' import { Kbd } from '@repo/ui/components/ui/kbd' import { Label } from '@repo/ui/components/ui/label' import { Popover, PopoverContent, PopoverTrigger } from '@repo/ui/components/ui/popover' import { Separator } from '@repo/ui/components/ui/separator' import { Spinner } from '@repo/ui/components/ui/spinner' import { useIsMobile } from '@repo/ui/hooks/use-mobile' import { createSearch } from '@repo/util/meili' import { HttpMethod, request as req } from '@repo/util/request' import { workspaceContext } from '@/middleware/workspace' import { cn } from '@repo/ui/lib/utils' import { CoursePicker } from './course-picker' import { formSchema, MAX_ITEMS, type Enrolled, type Schema, type User } from './data' import { ScheduledForInput } from './scheduled-for' import { UserPicker } from './user-picker' const emptyRow = { user: undefined, course: undefined, scheduled_for: undefined } export function meta({}: Route.MetaArgs) { return [{ title: 'Adicionar matrícula' }] } export async function loader({ params, context, request }: Route.LoaderArgs) { const { subscription } = context.get(workspaceContext) // If there's no subscription for the org, redirect to checkout if (!subscription) { throw redirect('../enrollments/buy') } const url = new URL(request.url) const submissionId = url.searchParams.get('submission') const cloudflare = context.get(cloudflareContext) const courses = createSearch({ index: 'saladeaula_courses', sort: ['created_at:desc'], filter: 'unlisted = false', hitsPerPage: 100, env: cloudflare.env }) const submission: Promise<{ enrolled: Enrolled[] }> = submissionId ? req({ url: `/orgs/${params.orgid}/enrollments/submissions/${submissionId}`, context, request }).then((r) => r.json()) : Promise.resolve({ enrolled: [] }) return { courses, submission } } export async function action({ params, request, context }: Route.ActionArgs) { const { orgid: org_id } = params const { subscription } = context.get(workspaceContext) const body = (await request.json()) as object const r = await req({ url: `enrollments`, headers: new Headers({ 'Content-Type': 'application/json' }), method: HttpMethod.POST, body: JSON.stringify({ org_id, subscription, ...body }), request, context }) const data = (await r.json()) as { sk: string } return redirect(`/${org_id}/enrollments/${data.sk}/submitted`) } export default function Route({ loaderData: { courses, submission } }: Route.ComponentProps) { const { orgid } = useParams() const { enrolled } = use(submission) const fetcher = useFetcher() const form = useForm({ resolver: zodResolver(formSchema), defaultValues: { enrollments: [emptyRow] } }) const { formState, control, handleSubmit, getValues, setValue } = form const { fields, insert, remove, append } = useFieldArray({ control, name: 'enrollments' }) const onSubmit = async (data: Schema) => { await fetcher.submit(JSON.stringify(data), { method: 'post', encType: 'application/json' }) } 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 } const duplicateRow = (index: number, times: number = 1) => { if (fields.length + times > MAX_ITEMS) { return null } const { user, ...rest } = getValues(`enrollments.${index}`) Array.from({ length: times }, (_, i) => { // @ts-ignore insert(index + 1 + i, rest) }) } useEffect(() => { if (enrolled.length === 0) { return } setValue( 'enrollments', enrolled .filter(({ status }) => status === 'fail') .map(({ input_record: { course, user } }) => ({ user, course: { ...course, unit_price: 0 } })) ) }, [enrolled, setValue]) return (
Matrículas Adicionar matrículas
Adicionar matrículas Siga os passos abaixo para adicionar novas matrículas.
{/* Header */} <> Colaborador Curso Matricular em

Escolha a data em que o colaborador será matriculado no curso.

Você poderá acompanhar as matrículas em{' '} Agendamentos

{/**/} {/* Rows */} <> {fields.map((field, index) => ( {/* Separator only for mobile */} {index >= 1 &&
} (
(

{message}

)} />
)} /> (
(

{message}

)} />
)} /> ( )} /> {/* Action */}
))}
) } function DuplicateRowMultipleTimes({ index, duplicateRow }: { index: number duplicateRow: (index: number, times: number) => void }) { const [open, { toggle, set }] = useToggle() const isMobile = useIsMobile() return (
{ e.stopPropagation() e.preventDefault() const formData = new FormData(e.currentTarget) const times = parseInt(formData.get('quantity') as string) duplicateRow(index, times) set(false) }} >

Duplicar várias vezes

Duplique o curso desta linha na quantidade desejada para agilizar o preenchimento.

) } function ActionMenu() { const { orgid } = useParams() const [open, { set }] = useToggle() const { data, runAsync, loading } = useRequest( async () => { const r = await fetch(`/~/api/orgs/${orgid}/enrollments/submissions`, { method: 'GET' }) return (await r.json()) as { items: { sk: string }[] } }, { manual: true } ) return ( { set(open) if (open && !data) { await runAsync() } }} >
Envios recentes
{loading && ( )} {data?.items?.map(({ sk }, index) => ( {sk} ))} {data?.items?.length === 0 && ( Nenhum envio ainda )}
) } export function Cell({ children, className }: { children?: ReactNode className?: string }) { return (
{children}
) }