import type { Route } from './+types/route' import { formatCNPJ } from '@brazilian-utils/brazilian-utils' import { BuildingIcon, CalendarIcon, CheckIcon, PlusCircleIcon } from 'lucide-react' import { MeiliSearchFilterBuilder } from 'meilisearch-helper' import { pick } from 'ramda' import { Suspense, useState } from 'react' import { Await, Outlet, useSearchParams } from 'react-router' import { cloudflareContext } from '@repo/auth/context' import { Abbr } from '@repo/ui/components/abbr' import { DataTable, DataTableViewOptions } from '@repo/ui/components/data-table' import { ExportMenu } from '@repo/ui/components/export-menu' import { FacetedFilter } from '@repo/ui/components/faceted-filter' import { RangeCalendarFilter } from '@repo/ui/components/range-calendar-filter' import { SearchFilter } from '@repo/ui/components/search-filter' import { SearchForm } from '@repo/ui/components/search-form' import { Skeleton } from '@repo/ui/components/skeleton' import { Avatar, AvatarFallback } from '@repo/ui/components/ui/avatar' import { Badge } from '@repo/ui/components/ui/badge' import { Button } from '@repo/ui/components/ui/button' import { CommandItem } from '@repo/ui/components/ui/command' import { Kbd } from '@repo/ui/components/ui/kbd' import { Separator } from '@repo/ui/components/ui/separator' import { Spinner } from '@repo/ui/components/ui/spinner' import { cn, initials } from '@repo/ui/lib/utils' import { headers, sortings, statuses } from '@repo/ui/routes/enrollments/data' import { createSearch } from '@repo/util/meili' import { columns, type Enrollment } from './columns' export function meta({}: Route.MetaArgs) { return [{ title: 'Matrículas' }] } export async function loader({ context, request }: Route.LoaderArgs) { const cloudflare = context.get(cloudflareContext) const { searchParams } = new URL(request.url) const query = searchParams.get('q') || '' const org = searchParams.get('org') || '' const from = searchParams.get('from') const to = searchParams.get('to') const sort = searchParams.get('sort') || 'created_at:desc' const status = searchParams.get('status') const page = Number(searchParams.get('p')) + 1 const hitsPerPage = Number(searchParams.get('perPage')) || 25 let builder = new MeiliSearchFilterBuilder().where('is_test', 'exists', false) if (status) { builder = builder.where('status', 'in', status.split(',')) } if (org) { const { id } = JSON.parse(org) builder = builder.where('org_id', '=', id) } if (from && to) { const [field, from_] = from.split(':') builder = builder.where(field, 'between', [from_, to]) } const enrollments = createSearch({ index: 'betaeducacao-prod-enrollments', filter: builder.build(), sort: [sort], query, page, hitsPerPage, env: cloudflare.env }) return { enrollments } } const formatted = new Intl.DateTimeFormat('en-CA', { year: 'numeric', month: '2-digit', day: '2-digit' }) export default function Route({ loaderData: { enrollments } }: Route.ComponentProps) { const [searchParams, setSearchParams] = useSearchParams() const [selectedRows, setSelectedRows] = useState([]) const status = searchParams.get('status') const dateRange = useRangeParams() const org = searchParams.has('org') ? JSON.parse(searchParams.get('org') || '') : null const onSearch = async (search: string) => { const params = new URLSearchParams({ q: search }) const r = await fetch(`/orgs.json?${params.toString()}`) const { hits } = (await r.json()) as { hits: any[] } return hits } return ( }>

Matrículas

{({ hits, page = 1, hitsPerPage, totalHits }) => (
{selectedRows.length ? ( <>
) : ( <>
Digite / para pesquisar } onChange={(value) => setSearchParams((searchParams) => { searchParams.set('q', String(value)) searchParams.delete('p') return searchParams }) } />
{ setSearchParams((searchParams) => { searchParams.delete('status') searchParams.delete('p') if (statuses.length) { searchParams.set('status', statuses.join(',')) } return searchParams }) }} options={Object.entries(statuses).map( ([key, value]) => ({ value: key, ...value }) )} /> ({ value, label }) )} onChange={(props) => { setSearchParams((searchParams) => { if (!props) { searchParams.delete('from') searchParams.delete('to') return searchParams } const { rangeField, dateRange } = props searchParams.set( 'from', `${rangeField}:${formatted.format(dateRange?.from)}` ) searchParams.set( 'to', formatted.format(dateRange?.to) ) return searchParams }) }} /> { setSearchParams((searchParams) => { if (value) { searchParams.set( 'org', JSON.stringify(pick(['id', 'name', 'q'], value)) ) } else { searchParams.delete('org') } return searchParams }) }} render={({ id, name, cnpj, onSelect }) => (
{initials(name)}
  • {name}
  • {formatCNPJ(cnpj)}
)} > {({ loading }) => ( )}
)}
)}
) } function useRangeParams() { const [searchParams] = useSearchParams() const [from, to] = [searchParams.get('from'), searchParams.get('to')] if (!from || !to) { return {} } const [rangeField, from_] = from.split(':') return { rangeField, dateRange: { from: new Date(from_), to: new Date(to) } } }