'use client' import type { CellContext, ColumnDef } from '@tanstack/react-table' import { useRequest, useToggle } from 'ahooks' import { CircleXIcon, EllipsisVerticalIcon, FileBadgeIcon, HelpCircleIcon, LockOpenIcon } from 'lucide-react' import type { ComponentProps, MouseEvent } from 'react' import { toast } from 'sonner' import { Abbr } from '@repo/ui/components/abbr' import { DataTableColumnHeader } from '@repo/ui/components/data-table' import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger } from '@repo/ui/components/ui/alert-dialog' 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 { Checkbox } from '@repo/ui/components/ui/checkbox' import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger } from '@repo/ui/components/ui/dropdown-menu' import { Progress } from '@repo/ui/components/ui/progress' import { Spinner } from '@repo/ui/components/ui/spinner' import { cn, initials } from '@repo/ui/lib/utils' import { labels, statuses } from './data' // This type is used to define the shape of our data. // You can use a Zod schema here if you want. type Course = { id: string name: string } export type Enrollment = { id: string name: string course: Course status: string progress: string created_at: string } const formatted = new Intl.DateTimeFormat('pt-BR', { day: '2-digit', month: '2-digit', year: 'numeric', hour: '2-digit', minute: '2-digit' }) export const columns: ColumnDef[] = [ { id: 'select', header: ({ table }) => ( table.toggleAllPageRowsSelected(!!value)} className="cursor-pointer" aria-label="Selecionar tudo" /> ), cell: ({ row }) => ( row.toggleSelected(!!value)} className="cursor-pointer" aria-label="Selecionar linha" /> ) }, { accessorKey: 'user', header: 'Colaborador', enableHiding: false, cell: ({ row }) => { const user = row.getValue('user') as { name: string; email: string } return (
{initials(user.name)}
  • {user.name}
  • {user.email}
) } }, { accessorKey: 'course', header: 'Curso', enableHiding: false, cell: ({ row }) => { const { name } = row.getValue('course') as { name: string } return {name} } }, { accessorKey: 'status', header: 'Status', enableHiding: false, cell: ({ row }) => { const s = row.getValue('status') as string const status = labels[s] ?? s const { icon: Icon, color } = statuses?.[s] ?? { icon: HelpCircleIcon } return ( {status} ) } }, { accessorKey: 'progress', header: 'Progresso', enableHiding: false, cell: ({ row }) => { const progress = row.getValue('progress') return (
{String(progress)}%
) } }, { accessorKey: 'created_at', header: ({ column }) => , meta: { title: 'Cadastrado em' }, enableSorting: true, enableHiding: true, cell: cellDate }, { accessorKey: 'started_at', header: ({ column }) => , meta: { title: 'Iniciado em' }, enableSorting: true, enableHiding: true, cell: cellDate }, { accessorKey: 'completed_at', header: ({ column }) => , meta: { title: 'Concluído em' }, enableSorting: true, enableHiding: true, cell: cellDate }, { accessorKey: 'failed_at', header: ({ column }) => , meta: { title: 'Reprovado em' }, enableSorting: true, enableHiding: true, cell: cellDate }, { accessorKey: 'canceled_at', header: ({ column }) => , meta: { title: 'Cancelado em' }, enableSorting: true, enableHiding: true, cell: cellDate }, { id: 'actions', cell: ({ row }) => } ] function cellDate({ row: { original }, cell: { column } }: CellContext) { const accessorKey = column.columnDef.accessorKey as keyof TData const value = original?.[accessorKey] if (value) { return formatted.format(new Date(value as string)) } return <> } async function getEnrollment(id: string) { const r = await fetch(`/~/api/enrollments/${id}`, { method: 'GET' }) await new Promise((r) => setTimeout(r, 150)) return (await r.json()) as { cancel_policy?: any lock?: { hash: string } } } function ActionMenu({ row }: { row: any }) { const [open, { set: setOpen }] = useToggle(false) const cert = row.original?.cert const { data, loading, run, refresh } = useRequest(getEnrollment, { manual: true }) const onSuccess = () => { refresh() setOpen(false) } return (
{ setOpen(open) if (data) return run(row.id) }} > setOpen(false)} /> {loading ? ( ) : ( <> )}
) } type ItemProps = ComponentProps & { id: string onSuccess?: () => void } function DownloadItem({ id, onSuccess, ...props }: ItemProps) { const [loading, { set }] = useToggle(false) const download = async (e: Event) => { e.preventDefault() set(true) const r = await fetch(`/~/api/enrollments/${id}/download`, { method: 'GET' }) if (r.ok) { const { presigned_url } = (await r.json()) as { presigned_url: string } window.open(presigned_url, '_blank') set(false) onSuccess?.() } } return ( {loading ? : } Baixar certificado ) } function RemoveDedupItem({ id, lock, onSuccess, ...props }: ItemProps & { lock?: { hash: string; ttl?: number } }) { const [loading, { set }] = useToggle(false) const [open, { set: setOpen }] = useToggle(false) const cancel = async (e: MouseEvent) => { e.preventDefault() set(true) if (!lock) return null const r = await fetch(`/~/api/enrollments/${id}/dedupwindow`, { method: 'DELETE', body: JSON.stringify({ lock_hash: lock.hash }), headers: new Headers({ 'Content-Type': 'application/json' }) }) if (r.ok) { toast.info('A proteção contra duplicação foi removida') setOpen(false) onSuccess?.() } } const getDaysRemaining = () => { if (!lock?.ttl) return null return new Date(lock.ttl * 1000).toLocaleString('pt-BR', { hour: '2-digit', minute: '2-digit', day: '2-digit', month: '2-digit', year: '2-digit' }) } const daysRemaining = getDaysRemaining() return ( e.preventDefault()} disabled={!lock} {...props} > Remover proteção Tem certeza absoluta? Esta ação não pode ser desfeita. Isso{' '} remove a proteção contra duplicação permanentemente {' '} desta matrícula. {daysRemaining && ( Proteção contra duplicação válida até {daysRemaining} )} Cancelar ) } function CancelItem({ id, cancelPolicy, lock, onSuccess, ...props }: ItemProps & { cancelPolicy?: object lock?: { hash: string } }) { const [loading, { set }] = useToggle(false) const [open, { set: setOpen }] = useToggle(false) const cancel = async (e: MouseEvent) => { e.preventDefault() set(true) const r = await fetch(`/~/api/enrollments/${id}/cancel`, { method: 'POST', headers: new Headers({ 'Content-Type': 'application/json' }) }) if (r.ok) { toast.info('A matrícula foi cancelada') setOpen(false) onSuccess?.() } } return ( e.preventDefault()} disabled={!cancelPolicy} {...props} > Cancelar Tem certeza absoluta? Esta ação não pode ser desfeita. Isso{' '} cancelar permanentemente a matrícula {' '} deste colaborador. Cancelar ) }