add download cert
This commit is contained in:
@@ -12,6 +12,7 @@ logger = Logger(__name__)
|
|||||||
tracer = Tracer()
|
tracer = Tracer()
|
||||||
urls = [
|
urls = [
|
||||||
'https://bcs7fgb9og.execute-api.sa-east-1.amazonaws.com/health',
|
'https://bcs7fgb9og.execute-api.sa-east-1.amazonaws.com/health',
|
||||||
|
# 'https://api.saladeaula.digital/health',
|
||||||
'https://id.saladeaula.digital/health',
|
'https://id.saladeaula.digital/health',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -64,9 +64,7 @@ def put_course(course_id: str):
|
|||||||
event = router.current_event
|
event = router.current_event
|
||||||
|
|
||||||
if not event.decoded_body:
|
if not event.decoded_body:
|
||||||
raise BadRequestError('Invalid request body')
|
raise BadRequestError('Invalid request body') now_ = now()
|
||||||
|
|
||||||
now_ = now()
|
|
||||||
body = parse(
|
body = parse(
|
||||||
event.headers,
|
event.headers,
|
||||||
BytesIO(event.decoded_body.encode()),
|
BytesIO(event.decoded_body.encode()),
|
||||||
|
|||||||
@@ -1,6 +1,13 @@
|
|||||||
|
from typing import Annotated
|
||||||
|
|
||||||
from aws_lambda_powertools import Logger
|
from aws_lambda_powertools import Logger
|
||||||
from aws_lambda_powertools.event_handler.api_gateway import Router
|
from aws_lambda_powertools.event_handler.api_gateway import Router
|
||||||
from layercake.dynamodb import DynamoDBPersistenceLayer
|
from aws_lambda_powertools.event_handler.exceptions import (
|
||||||
|
BadRequestError,
|
||||||
|
)
|
||||||
|
from aws_lambda_powertools.event_handler.openapi.params import Body
|
||||||
|
from layercake.dateutils import now
|
||||||
|
from layercake.dynamodb import DynamoDBPersistenceLayer, KeyPair
|
||||||
|
|
||||||
from boto3clients import dynamodb_client
|
from boto3clients import dynamodb_client
|
||||||
from config import ENROLLMENT_TABLE
|
from config import ENROLLMENT_TABLE
|
||||||
@@ -10,5 +17,78 @@ router = Router()
|
|||||||
dyn = DynamoDBPersistenceLayer(ENROLLMENT_TABLE, dynamodb_client)
|
dyn = DynamoDBPersistenceLayer(ENROLLMENT_TABLE, dynamodb_client)
|
||||||
|
|
||||||
|
|
||||||
@router.patch('/')
|
class CancelPolicyConflictError(BadRequestError):
|
||||||
def cancel(): ...
|
def __init__(self, *_):
|
||||||
|
super().__init__('Cancellation policy not found')
|
||||||
|
|
||||||
|
|
||||||
|
class SlotConflictError(BadRequestError):
|
||||||
|
def __init__(self, *_):
|
||||||
|
super().__init__('Slot not found')
|
||||||
|
|
||||||
|
|
||||||
|
@router.post('/<enrollment_id>/cancel')
|
||||||
|
def cancel(
|
||||||
|
enrollment_id: str,
|
||||||
|
lock_hash: Annotated[str | None, Body(embed=True)] = None,
|
||||||
|
):
|
||||||
|
now_ = now()
|
||||||
|
|
||||||
|
with dyn.transact_writer() as transact:
|
||||||
|
transact.update(
|
||||||
|
key=KeyPair(enrollment_id, '0'),
|
||||||
|
cond_expr='#status = pending',
|
||||||
|
update_expr='SET #status = :canceled, \
|
||||||
|
canceled_at = :now, \
|
||||||
|
updated_at = :now',
|
||||||
|
expr_attr_names={
|
||||||
|
':status': 'status',
|
||||||
|
},
|
||||||
|
expr_attr_values={
|
||||||
|
':pending': 'PENDING',
|
||||||
|
':canceled': 'CANCELED',
|
||||||
|
':true': True,
|
||||||
|
':now': now_,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
transact.put(
|
||||||
|
item={
|
||||||
|
'id': enrollment_id,
|
||||||
|
'sk': 'CANCELED_BY',
|
||||||
|
'canceled_by': {},
|
||||||
|
'created_at': now_,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
transact.delete(
|
||||||
|
key=KeyPair(
|
||||||
|
pk=enrollment_id,
|
||||||
|
sk='CANCEL_POLICY',
|
||||||
|
),
|
||||||
|
cond_expr='attribute_exists(sk)',
|
||||||
|
exc_cls=CancelPolicyConflictError,
|
||||||
|
)
|
||||||
|
# Remove reminders and policies that no longer apply
|
||||||
|
transact.delete(
|
||||||
|
key=KeyPair(
|
||||||
|
pk=enrollment_id,
|
||||||
|
sk='SCHEDULE#REMINDER_NO_ACCESS_AFTER_3_DAYS',
|
||||||
|
)
|
||||||
|
)
|
||||||
|
transact.delete(
|
||||||
|
key=KeyPair(
|
||||||
|
pk=enrollment_id,
|
||||||
|
sk='SCHEDULE#REMINDER_ACCESS_PERIOD_BEFORE_30_DAYS',
|
||||||
|
)
|
||||||
|
)
|
||||||
|
transact.delete(
|
||||||
|
key=KeyPair(
|
||||||
|
pk=enrollment_id,
|
||||||
|
sk='METADATA#PARENT_SLOT',
|
||||||
|
),
|
||||||
|
cond_expr='attribute_exists(sk)',
|
||||||
|
exc_cls=SlotConflictError,
|
||||||
|
)
|
||||||
|
|
||||||
|
if lock_hash:
|
||||||
|
transact.delete(key=KeyPair(enrollment_id, 'LOCK'))
|
||||||
|
transact.delete(key=KeyPair('LOCK', lock_hash))
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ router = Router()
|
|||||||
dyn = DynamoDBPersistenceLayer(ENROLLMENT_TABLE, dynamodb_client)
|
dyn = DynamoDBPersistenceLayer(ENROLLMENT_TABLE, dynamodb_client)
|
||||||
|
|
||||||
|
|
||||||
@router.patch('/<enrollment_id>/dedupwindow', compress=True)
|
@router.delete('/<enrollment_id>/dedupwindow', compress=True)
|
||||||
def dedup_window(
|
def dedup_window(
|
||||||
enrollment_id: str,
|
enrollment_id: str,
|
||||||
lock_hash: Annotated[str, Body(embed=True)],
|
lock_hash: Annotated[str, Body(embed=True)],
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
GraduationCap,
|
GraduationCap,
|
||||||
LayoutDashboard,
|
LayoutDashboard,
|
||||||
ShieldUserIcon,
|
ShieldUserIcon,
|
||||||
|
UploadIcon,
|
||||||
UsersIcon
|
UsersIcon
|
||||||
} from 'lucide-react'
|
} from 'lucide-react'
|
||||||
|
|
||||||
@@ -31,7 +32,9 @@ const data = {
|
|||||||
title: 'Histórico de compras',
|
title: 'Histórico de compras',
|
||||||
url: '/orders',
|
url: '/orders',
|
||||||
icon: DollarSign
|
icon: DollarSign
|
||||||
},
|
}
|
||||||
|
],
|
||||||
|
navUser: [
|
||||||
{
|
{
|
||||||
title: 'Colaboradores',
|
title: 'Colaboradores',
|
||||||
url: '/users',
|
url: '/users',
|
||||||
@@ -41,9 +44,14 @@ const data = {
|
|||||||
title: 'Gestores',
|
title: 'Gestores',
|
||||||
url: '/admins',
|
url: '/admins',
|
||||||
icon: ShieldUserIcon
|
icon: ShieldUserIcon
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Importações',
|
||||||
|
url: '/batch',
|
||||||
|
icon: UploadIcon
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
navContent: [
|
navEnrollment: [
|
||||||
{
|
{
|
||||||
title: 'Matrículas',
|
title: 'Matrículas',
|
||||||
url: '/enrollments',
|
url: '/enrollments',
|
||||||
|
|||||||
@@ -25,7 +25,8 @@ export function NavMain({
|
|||||||
}: {
|
}: {
|
||||||
data: {
|
data: {
|
||||||
navMain: NavItem[]
|
navMain: NavItem[]
|
||||||
navContent: NavItem[]
|
navUser: NavItem[]
|
||||||
|
navEnrollment: NavItem[]
|
||||||
}
|
}
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
@@ -43,8 +44,23 @@ export function NavMain({
|
|||||||
<SidebarGroup>
|
<SidebarGroup>
|
||||||
<SidebarGroupContent>
|
<SidebarGroupContent>
|
||||||
<SidebarMenu>
|
<SidebarMenu>
|
||||||
<SidebarGroupLabel>Gestão de matrículas</SidebarGroupLabel>
|
<SidebarGroupLabel className="uppercase">
|
||||||
{data.navContent.map((props, idx) => (
|
Colaboradores
|
||||||
|
</SidebarGroupLabel>
|
||||||
|
{data.navUser.map((props, idx) => (
|
||||||
|
<SidebarMenuItemLink key={idx} {...props} />
|
||||||
|
))}
|
||||||
|
</SidebarMenu>
|
||||||
|
</SidebarGroupContent>
|
||||||
|
</SidebarGroup>
|
||||||
|
|
||||||
|
<SidebarGroup>
|
||||||
|
<SidebarGroupContent>
|
||||||
|
<SidebarMenu>
|
||||||
|
<SidebarGroupLabel className="uppercase">
|
||||||
|
Gestão de matrículas
|
||||||
|
</SidebarGroupLabel>
|
||||||
|
{data.navEnrollment.map((props, idx) => (
|
||||||
<SidebarMenuItemLink key={idx} {...props} />
|
<SidebarMenuItemLink key={idx} {...props} />
|
||||||
))}
|
))}
|
||||||
</SidebarMenu>
|
</SidebarMenu>
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
import type { Route } from './+types'
|
||||||
|
|
||||||
|
import { Suspense } from 'react'
|
||||||
|
import { Await } from 'react-router'
|
||||||
|
|
||||||
|
import { Skeleton } from '@repo/ui/components/skeleton'
|
||||||
|
import { request as req } from '@repo/util/request'
|
||||||
|
|
||||||
|
export function meta({}: Route.MetaArgs) {
|
||||||
|
return [{ title: ' Importações de colaboradores' }]
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function loader({ context, request, params }: Route.LoaderArgs) {
|
||||||
|
const data = req({
|
||||||
|
url: `/orgs/${params.orgid}/enrollments/scheduled`,
|
||||||
|
context,
|
||||||
|
request
|
||||||
|
}).then((r) => r.json())
|
||||||
|
|
||||||
|
return {
|
||||||
|
data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Route({ loaderData: { data } }) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="space-y-0.5 mb-8">
|
||||||
|
<h1 className="text-2xl font-bold tracking-tight">
|
||||||
|
Importações de colaboradores
|
||||||
|
</h1>
|
||||||
|
<p className="text-muted-foreground">
|
||||||
|
Acompanhe suas importações de colaboradores, faça novas importações
|
||||||
|
sempre que precisar e finalize o processo com rapidez.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Suspense fallback={<Skeleton />}>
|
||||||
|
<Await resolve={data}>
|
||||||
|
{(resolved) => <>...{console.log(resolved)}</>}
|
||||||
|
</Await>
|
||||||
|
</Suspense>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
import type { Route } from './+types'
|
||||||
|
|
||||||
|
import { useNavigate } from 'react-router'
|
||||||
|
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogDescription,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle
|
||||||
|
} from '@repo/ui/components/ui/dialog'
|
||||||
|
import { request as req } from '@repo/util/request'
|
||||||
|
|
||||||
|
export async function loader({ params, request, context }: Route.LoaderArgs) {
|
||||||
|
const { id } = params
|
||||||
|
const r = await req({
|
||||||
|
url: `/enrollments/${id}`,
|
||||||
|
request,
|
||||||
|
context
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!r.ok) {
|
||||||
|
throw new Response(null, { status: r.status })
|
||||||
|
}
|
||||||
|
|
||||||
|
const enrollment = await r.json()
|
||||||
|
return { data: enrollment }
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function UserModal({ loaderData }: Route.ComponentProps) {
|
||||||
|
const navigate = useNavigate()
|
||||||
|
const { enrollment } = loaderData
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog
|
||||||
|
open={true}
|
||||||
|
onOpenChange={(open) => {
|
||||||
|
if (!open) navigate('/enrollments') // Volta pra listagem ao fechar
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<DialogContent>
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>...</DialogTitle>
|
||||||
|
<DialogDescription>Detalhes do usuário</DialogDescription>
|
||||||
|
</DialogHeader>
|
||||||
|
|
||||||
|
<div className="space-y-4">
|
||||||
|
...
|
||||||
|
{/* Mais informações... */}
|
||||||
|
</div>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,16 +1,46 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import type { CellContext, ColumnDef } from '@tanstack/react-table'
|
import type { CellContext, ColumnDef } from '@tanstack/react-table'
|
||||||
import { HelpCircleIcon } from 'lucide-react'
|
import { useToggle } from 'ahooks'
|
||||||
|
import {
|
||||||
|
CircleXIcon,
|
||||||
|
EllipsisVerticalIcon,
|
||||||
|
FileBadgeIcon,
|
||||||
|
HelpCircleIcon,
|
||||||
|
LockOpenIcon
|
||||||
|
} from 'lucide-react'
|
||||||
|
import { NavLink, useParams } from 'react-router'
|
||||||
|
import { toast } from 'sonner'
|
||||||
|
|
||||||
|
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 { Avatar, AvatarFallback } from '@repo/ui/components/ui/avatar'
|
||||||
import { Badge } from '@repo/ui/components/ui/badge'
|
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 { 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 { Progress } from '@repo/ui/components/ui/progress'
|
||||||
|
import { Spinner } from '@repo/ui/components/ui/spinner'
|
||||||
import { cn, initials } from '@repo/ui/lib/utils'
|
import { cn, initials } from '@repo/ui/lib/utils'
|
||||||
|
|
||||||
import { Abbr } from '@/components/abbr'
|
import { Abbr } from '@/components/abbr'
|
||||||
import { DataTableColumnHeader } from '@/components/data-table/column-header'
|
import { DataTableColumnHeader } from '@/components/data-table/column-header'
|
||||||
|
import { useDataTable } from '@/components/data-table/data-table'
|
||||||
import { labels, statuses } from './data'
|
import { labels, statuses } from './data'
|
||||||
|
|
||||||
// This type is used to define the shape of our data.
|
// This type is used to define the shape of our data.
|
||||||
@@ -167,6 +197,10 @@ export const columns: ColumnDef<Enrollment>[] = [
|
|||||||
enableSorting: true,
|
enableSorting: true,
|
||||||
enableHiding: true,
|
enableHiding: true,
|
||||||
cell: cellDate
|
cell: cellDate
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'actions',
|
||||||
|
cell: ({ row }) => <ActionMenu row={row} />
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -183,3 +217,159 @@ function cellDate<TData>({
|
|||||||
|
|
||||||
return <></>
|
return <></>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function ActionMenu({ row }: { row: any }) {
|
||||||
|
const cert = row.original?.cert
|
||||||
|
const progress = row.getValue('progress')
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex justify-end items-center">
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger asChild>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
className="data-[state=open]:bg-muted text-muted-foreground cursor-pointer"
|
||||||
|
size="icon-sm"
|
||||||
|
>
|
||||||
|
<EllipsisVerticalIcon />
|
||||||
|
<span className="sr-only">Abrir menu</span>
|
||||||
|
</Button>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent align="end" className="w-46 *:cursor-pointer">
|
||||||
|
<DownloadItem id={row.id} disabled={!cert} />
|
||||||
|
<DropdownMenuSeparator />
|
||||||
|
<RemoveDedupItem id={row.id} disabled={progress > 0} />
|
||||||
|
<CancelItem id={row.id} disabled={progress > 0} />
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function DownloadItem({ id, ...props }: { id: string }) {
|
||||||
|
const [loading, { set }] = useToggle(false)
|
||||||
|
|
||||||
|
const download = async (e) => {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DropdownMenuItem onSelect={download} {...props}>
|
||||||
|
{loading ? <Spinner /> : <FileBadgeIcon />} Baixar certificado
|
||||||
|
</DropdownMenuItem>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function RemoveDedupItem({ id, ...props }: { id: string }) {
|
||||||
|
const [loading, { set }] = useToggle(false)
|
||||||
|
const { orgid } = useParams()
|
||||||
|
const { table } = useDataTable<Enrollment>()
|
||||||
|
|
||||||
|
const cancel = async (e) => {
|
||||||
|
e.preventDefault()
|
||||||
|
set(true)
|
||||||
|
|
||||||
|
const r = await fetch(`/~/api/enrollments/${orgid}/dedupwindow`, {
|
||||||
|
method: 'DELETE'
|
||||||
|
})
|
||||||
|
|
||||||
|
if (r.ok) {
|
||||||
|
toast.info('A proteção contra duplicação foi removida')
|
||||||
|
// @ts-ignore
|
||||||
|
table.options.meta?.removeRow?.(id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AlertDialog>
|
||||||
|
<AlertDialogTrigger asChild>
|
||||||
|
<DropdownMenuItem
|
||||||
|
variant="destructive"
|
||||||
|
onSelect={(e) => e.preventDefault()}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<LockOpenIcon /> Remover proteção
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</AlertDialogTrigger>
|
||||||
|
<AlertDialogContent>
|
||||||
|
<AlertDialogHeader>
|
||||||
|
<AlertDialogTitle>Tem certeza absoluta?</AlertDialogTitle>
|
||||||
|
<AlertDialogDescription>
|
||||||
|
Esta ação não pode ser desfeita. Isso remove a proteção contra
|
||||||
|
duplicação permanentemente desta matrícula.
|
||||||
|
</AlertDialogDescription>
|
||||||
|
</AlertDialogHeader>
|
||||||
|
<AlertDialogFooter className="*:cursor-pointer">
|
||||||
|
<AlertDialogCancel>Cancelar</AlertDialogCancel>
|
||||||
|
<AlertDialogAction asChild>
|
||||||
|
<Button onClick={cancel} disabled={loading} variant="destructive">
|
||||||
|
{loading ? <Spinner /> : null} Continuar
|
||||||
|
</Button>
|
||||||
|
</AlertDialogAction>
|
||||||
|
</AlertDialogFooter>
|
||||||
|
</AlertDialogContent>
|
||||||
|
</AlertDialog>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function CancelItem({ id, ...props }: { id: string }) {
|
||||||
|
const [loading, { set }] = useToggle(false)
|
||||||
|
const { orgid } = useParams()
|
||||||
|
const { table } = useDataTable<Enrollment>()
|
||||||
|
|
||||||
|
const cancel = async (e) => {
|
||||||
|
e.preventDefault()
|
||||||
|
set(true)
|
||||||
|
|
||||||
|
const r = await fetch(`/~/api/enrollments/${orgid}/cancel`, {
|
||||||
|
method: 'PATCH'
|
||||||
|
})
|
||||||
|
|
||||||
|
if (r.ok) {
|
||||||
|
toast.info('A matrícula foi cancelada')
|
||||||
|
// @ts-ignore
|
||||||
|
table.options.meta?.removeRow?.(id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AlertDialog>
|
||||||
|
<AlertDialogTrigger asChild>
|
||||||
|
<DropdownMenuItem
|
||||||
|
variant="destructive"
|
||||||
|
onSelect={(e) => e.preventDefault()}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<CircleXIcon /> Cancelar
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</AlertDialogTrigger>
|
||||||
|
<AlertDialogContent>
|
||||||
|
<AlertDialogHeader>
|
||||||
|
<AlertDialogTitle>Tem certeza absoluta?</AlertDialogTitle>
|
||||||
|
<AlertDialogDescription>
|
||||||
|
Esta ação não pode ser desfeita. Isso cancelar permanentemente a
|
||||||
|
matrícula deste colaborador.
|
||||||
|
</AlertDialogDescription>
|
||||||
|
</AlertDialogHeader>
|
||||||
|
<AlertDialogFooter className="*:cursor-pointer">
|
||||||
|
<AlertDialogCancel>Cancelar</AlertDialogCancel>
|
||||||
|
<AlertDialogAction asChild>
|
||||||
|
<Button onClick={cancel} disabled={loading} variant="destructive">
|
||||||
|
{loading ? <Spinner /> : null} Continuar
|
||||||
|
</Button>
|
||||||
|
</AlertDialogAction>
|
||||||
|
</AlertDialogFooter>
|
||||||
|
</AlertDialogContent>
|
||||||
|
</AlertDialog>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import {
|
|||||||
} from 'lucide-react'
|
} from 'lucide-react'
|
||||||
import { MeiliSearchFilterBuilder } from 'meilisearch-helper'
|
import { MeiliSearchFilterBuilder } from 'meilisearch-helper'
|
||||||
import { Suspense, useState } from 'react'
|
import { Suspense, useState } from 'react'
|
||||||
import { Await, Link, useParams, useSearchParams } from 'react-router'
|
import { Await, Link, Outlet, useParams, useSearchParams } from 'react-router'
|
||||||
import type { BookType } from 'xlsx'
|
import type { BookType } from 'xlsx'
|
||||||
import * as XLSX from 'xlsx'
|
import * as XLSX from 'xlsx'
|
||||||
|
|
||||||
@@ -221,6 +221,8 @@ export default function Route({ loaderData: { data } }) {
|
|||||||
</DataTable>
|
</DataTable>
|
||||||
)}
|
)}
|
||||||
</Await>
|
</Await>
|
||||||
|
|
||||||
|
<Outlet />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import { formatCPF } from '@brazilian-utils/brazilian-utils'
|
import { formatCPF } from '@brazilian-utils/brazilian-utils'
|
||||||
import { type ColumnDef, type RowData } from '@tanstack/react-table'
|
import { type ColumnDef } from '@tanstack/react-table'
|
||||||
import { useToggle } from 'ahooks'
|
import { useToggle } from 'ahooks'
|
||||||
import {
|
import {
|
||||||
EllipsisVerticalIcon,
|
EllipsisVerticalIcon,
|
||||||
|
|||||||
@@ -52,14 +52,14 @@ def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool:
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Schedule the event to add `access_expired` after the access period ends
|
# Schedule the event to add `access_expired` after the access period ends
|
||||||
transact.put(
|
# transact.put(
|
||||||
item={
|
# item={
|
||||||
'id': enrollment_id,
|
# 'id': enrollment_id,
|
||||||
'sk': 'SCHEDULE#SET_ACCESS_EXPIRED',
|
# 'sk': 'SCHEDULE#SET_ACCESS_EXPIRED',
|
||||||
'created_at': now_,
|
# 'created_at': now_,
|
||||||
'ttl': ttl(start_dt=now_, days=access_period),
|
# 'ttl': ttl(start_dt=now_, days=access_period),
|
||||||
},
|
# },
|
||||||
)
|
# )
|
||||||
|
|
||||||
transact.put(
|
transact.put(
|
||||||
item={
|
item={
|
||||||
|
|||||||
Reference in New Issue
Block a user