287 lines
10 KiB
TypeScript
287 lines
10 KiB
TypeScript
import type { Route } from './+types/route'
|
|
|
|
import {
|
|
AlertCircleIcon,
|
|
CheckCircle2Icon,
|
|
ClockIcon,
|
|
CalendarIcon,
|
|
UserIcon,
|
|
BanIcon,
|
|
PlusIcon,
|
|
EllipsisIcon,
|
|
RotateCcw
|
|
} from 'lucide-react'
|
|
import { Link, useParams } from 'react-router'
|
|
import { Suspense } from 'react'
|
|
import { DateTime as LuxonDateTime } from 'luxon'
|
|
|
|
import {
|
|
Empty,
|
|
EmptyContent,
|
|
EmptyDescription,
|
|
EmptyHeader,
|
|
EmptyMedia,
|
|
EmptyTitle
|
|
} from '@repo/ui/components/ui/empty'
|
|
import {
|
|
Card,
|
|
CardContent,
|
|
CardDescription,
|
|
CardFooter,
|
|
CardHeader,
|
|
CardTitle
|
|
} from '@repo/ui/components/ui/card'
|
|
import {
|
|
Breadcrumb,
|
|
BreadcrumbItem,
|
|
BreadcrumbLink,
|
|
BreadcrumbList,
|
|
BreadcrumbPage,
|
|
BreadcrumbSeparator
|
|
} from '@repo/ui/components/ui/breadcrumb'
|
|
import {
|
|
Alert,
|
|
AlertDescription,
|
|
AlertTitle
|
|
} from '@repo/ui/components/ui/alert'
|
|
import { request as req } from '@repo/util/request'
|
|
import { Skeleton } from '@repo/ui/components/skeleton'
|
|
import { Await } from 'react-router'
|
|
import { Abbr } from '@repo/ui/components/abbr'
|
|
import { Button } from '@repo/ui/components/ui/button'
|
|
import {
|
|
DropdownMenu,
|
|
DropdownMenuContent,
|
|
DropdownMenuItem,
|
|
DropdownMenuTrigger
|
|
} from '@repo/ui/components/ui/dropdown-menu'
|
|
import { DateTime } from '@repo/ui/components/datetime'
|
|
|
|
export function meta({}: Route.MetaArgs) {
|
|
return [{ title: 'Relatório de matrículas' }]
|
|
}
|
|
|
|
export async function loader({ context, request, params }: Route.LoaderArgs) {
|
|
const { orgid, id } = params
|
|
const submission = req({
|
|
url: `/orgs/${orgid}/enrollments/submissions/${id}`,
|
|
context,
|
|
request
|
|
}).then((r) => r.json())
|
|
|
|
return {
|
|
submission
|
|
}
|
|
}
|
|
|
|
export default function Route({
|
|
loaderData: { submission }
|
|
}: Route.ComponentProps) {
|
|
const { id } = useParams()
|
|
|
|
return (
|
|
<Suspense fallback={<Skeleton />}>
|
|
<div className="space-y-2.5">
|
|
<Breadcrumb>
|
|
<BreadcrumbList>
|
|
<BreadcrumbItem>
|
|
<BreadcrumbLink asChild>
|
|
<Link to="../enrollments">Matrículas</Link>
|
|
</BreadcrumbLink>
|
|
</BreadcrumbItem>
|
|
<BreadcrumbSeparator />
|
|
<BreadcrumbItem>
|
|
<BreadcrumbLink asChild>
|
|
<Link to="../enrollments/add">Adicionar matrículas</Link>
|
|
</BreadcrumbLink>
|
|
</BreadcrumbItem>
|
|
<BreadcrumbSeparator />
|
|
<BreadcrumbItem>
|
|
<BreadcrumbPage>Relatório de matrículas</BreadcrumbPage>
|
|
</BreadcrumbItem>
|
|
</BreadcrumbList>
|
|
</Breadcrumb>
|
|
|
|
<Await resolve={submission} errorElement={<NotFound />}>
|
|
{({ enrolled, scheduled, sk, created_by }) => {
|
|
const succeed = enrolled?.filter(
|
|
({ status }) => status === 'success'
|
|
)
|
|
const failed = enrolled?.filter(({ status }) => status === 'fail')
|
|
|
|
return (
|
|
<div className="lg:max-w-4xl mx-auto space-y-2.5">
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle className="text-2xl">
|
|
Relatório de matrículas
|
|
</CardTitle>
|
|
<CardDescription>
|
|
Resumo detalhado do processamento das matrículas enviadas.
|
|
</CardDescription>
|
|
</CardHeader>
|
|
|
|
<CardContent className="space-y-4 text-base">
|
|
<>
|
|
{succeed?.length > 0 && (
|
|
<Alert className="text-green-500 *:data-[slot=alert-description]:text-green-500/90">
|
|
<CheckCircle2Icon />
|
|
<AlertTitle>
|
|
Matrículas adicionadas com sucesso.
|
|
</AlertTitle>
|
|
<AlertDescription>
|
|
<ul className="list-decimal list-inside">
|
|
{succeed.map(({ output }, index) => (
|
|
<li className="space-x-1" key={index}>
|
|
<Abbr>{output.user.name}</Abbr>
|
|
<span>—</span>
|
|
<Abbr>{output.course.name}</Abbr>
|
|
</li>
|
|
))}
|
|
</ul>
|
|
</AlertDescription>
|
|
</Alert>
|
|
)}
|
|
|
|
{failed?.length > 0 && (
|
|
<Alert variant="destructive">
|
|
<AlertCircleIcon />
|
|
<AlertTitle>Matrículas não processadas.</AlertTitle>
|
|
<AlertDescription>
|
|
<ul className="list-decimal list-inside">
|
|
{failed.map(({ input_record }, index) => (
|
|
<li className="space-x-1" key={index}>
|
|
<Abbr>{input_record.user.name}</Abbr>
|
|
<span>—</span>
|
|
<Abbr>{input_record.course.name}</Abbr>
|
|
</li>
|
|
))}
|
|
</ul>
|
|
</AlertDescription>
|
|
|
|
<DropdownMenu>
|
|
<DropdownMenuTrigger asChild>
|
|
<Button
|
|
className="absolute right-2.5 top-2.5 text-accent-foreground cursor-pointer"
|
|
variant="ghost"
|
|
size="icon-sm"
|
|
>
|
|
<EllipsisIcon />
|
|
</Button>
|
|
</DropdownMenuTrigger>
|
|
<DropdownMenuContent align="end" className="w-42">
|
|
<DropdownMenuItem
|
|
className="cursor-pointer"
|
|
asChild
|
|
>
|
|
<Link
|
|
to={`../enrollments/add?submission=${id}`}
|
|
>
|
|
<RotateCcw /> Restaurar dados
|
|
</Link>
|
|
</DropdownMenuItem>
|
|
</DropdownMenuContent>
|
|
</DropdownMenu>
|
|
</Alert>
|
|
)}
|
|
|
|
{scheduled?.length && (
|
|
<Alert>
|
|
<ClockIcon />
|
|
<AlertTitle>
|
|
Matrículas agendadas. Serão processadas na data
|
|
definida.
|
|
</AlertTitle>
|
|
<AlertDescription>
|
|
<ul className="list-decimal list-inside">
|
|
{scheduled.map(
|
|
({ output, input_record, status }, index) =>
|
|
status === 'success' ? (
|
|
<li className="space-x-1" key={index}>
|
|
<span className=" text-green-500">
|
|
Agendado para{' '}
|
|
<DateTime>
|
|
{LuxonDateTime.fromISO(
|
|
output.scheduled_for,
|
|
{ zone: 'America/Sao_Paulo' }
|
|
).toJSDate()}
|
|
{}
|
|
</DateTime>
|
|
</span>
|
|
<span>—</span>
|
|
<Abbr>{output.user.name}</Abbr>
|
|
<span>—</span>
|
|
<Abbr>{output.course.name}</Abbr>
|
|
</li>
|
|
) : (
|
|
<li className="space-x-1" key={index}>
|
|
<span className=" text-red-500">
|
|
Agendado anteriormente
|
|
</span>
|
|
<span>—</span>
|
|
<Abbr>{input_record.user.name}</Abbr>
|
|
<span>—</span>
|
|
<Abbr>{input_record.course.name}</Abbr>
|
|
</li>
|
|
)
|
|
)}
|
|
</ul>
|
|
</AlertDescription>
|
|
</Alert>
|
|
)}
|
|
</>
|
|
</CardContent>
|
|
|
|
<CardFooter>
|
|
<ul
|
|
className="lg:flex gap-2.5 text-muted-foreground text-sm
|
|
*:flex *:gap-1 *:items-center"
|
|
>
|
|
<li>
|
|
<CalendarIcon className="size-3.5" />
|
|
<DateTime
|
|
options={{ hour: '2-digit', minute: '2-digit' }}
|
|
>
|
|
{sk}
|
|
</DateTime>
|
|
</li>
|
|
<li>
|
|
<UserIcon className="size-3.5" /> {created_by.name}
|
|
</li>
|
|
</ul>
|
|
</CardFooter>
|
|
</Card>
|
|
</div>
|
|
)
|
|
}}
|
|
</Await>
|
|
</div>
|
|
</Suspense>
|
|
)
|
|
}
|
|
|
|
function NotFound() {
|
|
return (
|
|
<>
|
|
<Empty className="border border-dashed">
|
|
<EmptyHeader>
|
|
<EmptyMedia variant="icon">
|
|
<BanIcon />
|
|
</EmptyMedia>
|
|
<EmptyTitle>Nenhum relatório aqui</EmptyTitle>
|
|
<EmptyDescription>
|
|
Matricule colaboradores de forma rápida e acompanhe seu progresso.
|
|
</EmptyDescription>
|
|
</EmptyHeader>
|
|
<EmptyContent>
|
|
<Button asChild>
|
|
<Link to="../enrollments/add">
|
|
<PlusIcon /> Matricular
|
|
</Link>
|
|
</Button>
|
|
</EmptyContent>
|
|
</Empty>
|
|
</>
|
|
)
|
|
}
|