add certs page
This commit is contained in:
@@ -54,6 +54,7 @@ app.include_router(orgs.add, prefix='/orgs')
|
|||||||
app.include_router(orgs.address, prefix='/orgs')
|
app.include_router(orgs.address, prefix='/orgs')
|
||||||
app.include_router(orgs.admins, prefix='/orgs')
|
app.include_router(orgs.admins, prefix='/orgs')
|
||||||
app.include_router(orgs.billing, prefix='/orgs')
|
app.include_router(orgs.billing, prefix='/orgs')
|
||||||
|
app.include_router(orgs.certs, prefix='/orgs')
|
||||||
app.include_router(orgs.custom_pricing, prefix='/orgs')
|
app.include_router(orgs.custom_pricing, prefix='/orgs')
|
||||||
app.include_router(orgs.scheduled, prefix='/orgs')
|
app.include_router(orgs.scheduled, prefix='/orgs')
|
||||||
app.include_router(orgs.submissions, prefix='/orgs')
|
app.include_router(orgs.submissions, prefix='/orgs')
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ from .address import router as address
|
|||||||
from .admins import router as admins
|
from .admins import router as admins
|
||||||
from .billing import router as billing
|
from .billing import router as billing
|
||||||
from .custom_pricing import router as custom_pricing
|
from .custom_pricing import router as custom_pricing
|
||||||
|
from .enrollments.certs import router as certs
|
||||||
from .enrollments.scheduled import router as scheduled
|
from .enrollments.scheduled import router as scheduled
|
||||||
from .enrollments.submissions import router as submissions
|
from .enrollments.submissions import router as submissions
|
||||||
from .seats import router as seats
|
from .seats import router as seats
|
||||||
@@ -23,6 +24,7 @@ __all__ = [
|
|||||||
'admins',
|
'admins',
|
||||||
'billing',
|
'billing',
|
||||||
'custom_pricing',
|
'custom_pricing',
|
||||||
|
'certs',
|
||||||
'scheduled',
|
'scheduled',
|
||||||
'submissions',
|
'submissions',
|
||||||
'seats',
|
'seats',
|
||||||
|
|||||||
29
api.saladeaula.digital/app/routes/orgs/enrollments/certs.py
Normal file
29
api.saladeaula.digital/app/routes/orgs/enrollments/certs.py
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
from datetime import date
|
||||||
|
from typing import Annotated
|
||||||
|
|
||||||
|
from aws_lambda_powertools import Logger
|
||||||
|
from aws_lambda_powertools.event_handler.api_gateway import Router
|
||||||
|
from aws_lambda_powertools.event_handler.openapi.params import Query
|
||||||
|
from layercake.dynamodb import DynamoDBPersistenceLayer, KeyPair
|
||||||
|
|
||||||
|
from boto3clients import dynamodb_client
|
||||||
|
from config import ENROLLMENT_TABLE
|
||||||
|
|
||||||
|
logger = Logger(__name__)
|
||||||
|
router = Router()
|
||||||
|
dyn = DynamoDBPersistenceLayer(ENROLLMENT_TABLE, dynamodb_client)
|
||||||
|
|
||||||
|
|
||||||
|
@router.get('/<org_id>/enrollments/certs')
|
||||||
|
def certs(
|
||||||
|
org_id: str,
|
||||||
|
month: Annotated[date, Query()],
|
||||||
|
):
|
||||||
|
year_month = month.strftime('%Y-%m')
|
||||||
|
|
||||||
|
return dyn.collection.query(
|
||||||
|
KeyPair(
|
||||||
|
f'CERT_REPORTING#ORG#{org_id}',
|
||||||
|
f'MONTH#{year_month}#ENROLLMENT',
|
||||||
|
),
|
||||||
|
)
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
from http import HTTPStatus
|
from http import HTTPStatus
|
||||||
from typing import Annotated, cast
|
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
|
||||||
@@ -11,7 +11,6 @@ from pydantic import FutureDatetime
|
|||||||
from api_gateway import JSONResponse
|
from api_gateway import JSONResponse
|
||||||
from boto3clients import dynamodb_client
|
from boto3clients import dynamodb_client
|
||||||
from config import ENROLLMENT_TABLE
|
from config import ENROLLMENT_TABLE
|
||||||
from routes.orgs import billing
|
|
||||||
|
|
||||||
from ...enrollments.enroll import Context, Enrollment, Org, Subscription, enroll_now
|
from ...enrollments.enroll import Context, Enrollment, Org, Subscription, enroll_now
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
import {
|
import {
|
||||||
BookCopyIcon,
|
BookCopyIcon,
|
||||||
CalendarClockIcon,
|
CalendarClockIcon,
|
||||||
|
FileBadgeIcon,
|
||||||
// FileBadgeIcon,
|
// FileBadgeIcon,
|
||||||
GraduationCap,
|
GraduationCap,
|
||||||
LayoutDashboardIcon,
|
LayoutDashboardIcon,
|
||||||
@@ -65,11 +66,11 @@ const data = {
|
|||||||
url: '/enrollments',
|
url: '/enrollments',
|
||||||
icon: GraduationCap
|
icon: GraduationCap
|
||||||
},
|
},
|
||||||
// {
|
{
|
||||||
// title: 'Certificações',
|
title: 'Certificações',
|
||||||
// url: '/certs',
|
url: '/certs',
|
||||||
// icon: FileBadgeIcon
|
icon: FileBadgeIcon
|
||||||
// },
|
},
|
||||||
{
|
{
|
||||||
title: 'Agendamentos',
|
title: 'Agendamentos',
|
||||||
url: '/scheduled',
|
url: '/scheduled',
|
||||||
|
|||||||
@@ -76,7 +76,6 @@ export default function Route({
|
|||||||
const search = searchParams.get('s') as string
|
const search = searchParams.get('s') as string
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
|
||||||
<Suspense fallback={<Skeleton />}>
|
<Suspense fallback={<Skeleton />}>
|
||||||
<div className="space-y-0.5 mb-8">
|
<div className="space-y-0.5 mb-8">
|
||||||
<h1 className="text-2xl font-bold tracking-tight">
|
<h1 className="text-2xl font-bold tracking-tight">
|
||||||
@@ -105,8 +104,8 @@ export default function Route({
|
|||||||
defaultValue={search || ''}
|
defaultValue={search || ''}
|
||||||
placeholder={
|
placeholder={
|
||||||
<>
|
<>
|
||||||
Digite <Kbd className="border font-mono">/</Kbd>{' '}
|
Digite <Kbd className="border font-mono">/</Kbd> para
|
||||||
para pesquisar
|
pesquisar
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
onChange={(value) =>
|
onChange={(value) =>
|
||||||
@@ -142,7 +141,6 @@ export default function Route({
|
|||||||
}}
|
}}
|
||||||
</Await>
|
</Await>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
</>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -217,7 +215,7 @@ function List({ items, search }) {
|
|||||||
{charges.length ? (
|
{charges.length ? (
|
||||||
<>
|
<>
|
||||||
<TableHeader>
|
<TableHeader>
|
||||||
<TableRow className="bg-muted-foreground/10 pointer-events-none">
|
<TableRow className=" pointer-events-none">
|
||||||
<TableHead>Colaborador</TableHead>
|
<TableHead>Colaborador</TableHead>
|
||||||
<TableHead>Curso</TableHead>
|
<TableHead>Curso</TableHead>
|
||||||
<TableHead>Matriculado por</TableHead>
|
<TableHead>Matriculado por</TableHead>
|
||||||
|
|||||||
@@ -1,12 +1,46 @@
|
|||||||
import type { Route } from './+types/route'
|
import type { Route } from './+types/route'
|
||||||
|
|
||||||
|
import { Suspense } from 'react'
|
||||||
|
import { Await } from 'react-router'
|
||||||
|
|
||||||
|
import { DateTime } from '@repo/ui/components/datetime'
|
||||||
|
import { Skeleton } from '@repo/ui/components/skeleton'
|
||||||
|
import { Card, CardContent } from '@repo/ui/components/ui/card'
|
||||||
|
import {
|
||||||
|
Table,
|
||||||
|
TableBody,
|
||||||
|
TableCell,
|
||||||
|
TableHead,
|
||||||
|
TableHeader,
|
||||||
|
TableRow
|
||||||
|
} from '@repo/ui/components/ui/table'
|
||||||
|
import { request as req } from '@repo/util/request'
|
||||||
|
|
||||||
export function meta({}) {
|
export function meta({}) {
|
||||||
return [{ title: 'Certificações' }]
|
return [{ title: 'Certificações' }]
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Route({}: Route.ComponentProps) {
|
export async function loader({ context, request, params }: Route.LoaderArgs) {
|
||||||
|
const { searchParams } = new URL(request.url)
|
||||||
|
|
||||||
|
const month =
|
||||||
|
searchParams.get('month') || new Date().toISOString().slice(0, 10)
|
||||||
|
const reporting = req({
|
||||||
|
url: `/orgs/${params.orgid}/enrollments/certs?month=${month}`,
|
||||||
|
context,
|
||||||
|
request
|
||||||
|
}).then((r) => r.json())
|
||||||
|
|
||||||
|
return {
|
||||||
|
reporting
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Route({
|
||||||
|
loaderData: { reporting }
|
||||||
|
}: Route.ComponentProps) {
|
||||||
return (
|
return (
|
||||||
<>
|
<Suspense fallback={<Skeleton />}>
|
||||||
<div className="space-y-0.5 mb-8">
|
<div className="space-y-0.5 mb-8">
|
||||||
<h1 className="text-2xl font-bold tracking-tight">
|
<h1 className="text-2xl font-bold tracking-tight">
|
||||||
Gerenciar certificações
|
Gerenciar certificações
|
||||||
@@ -16,6 +50,57 @@ export default function Route({}: Route.ComponentProps) {
|
|||||||
prazos e renovações com facilidade.
|
prazos e renovações com facilidade.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<Card>
|
||||||
|
<CardContent>
|
||||||
|
<Table>
|
||||||
|
<TableHeader>
|
||||||
|
<TableRow className=" pointer-events-none">
|
||||||
|
<TableHead>Colaborador</TableHead>
|
||||||
|
<TableHead>Curso</TableHead>
|
||||||
|
<TableHead>Matriculado em</TableHead>
|
||||||
|
<TableHead>Concluído em</TableHead>
|
||||||
|
<TableHead>Cert. válido até</TableHead>
|
||||||
|
</TableRow>
|
||||||
|
</TableHeader>
|
||||||
|
<TableBody>
|
||||||
|
<Await resolve={reporting}>
|
||||||
|
{({ items = [] }) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{items.map(
|
||||||
|
({
|
||||||
|
course,
|
||||||
|
user,
|
||||||
|
enrolled_at,
|
||||||
|
completed_at,
|
||||||
|
expires_at
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<TableRow>
|
||||||
|
<TableCell>{user.name}</TableCell>
|
||||||
|
<TableCell>{course.name}</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
<DateTime>{enrolled_at}</DateTime>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
<DateTime>{completed_at}</DateTime>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
<DateTime>{expires_at}</DateTime>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
}}
|
||||||
|
</Await>
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</Suspense>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -190,7 +190,10 @@ export default function Route({
|
|||||||
<Button size="sm" variant="outline" asChild>
|
<Button size="sm" variant="outline" asChild>
|
||||||
<NavLink to="../enrollments/seats">
|
<NavLink to="../enrollments/seats">
|
||||||
{({ isPending }) => (
|
{({ isPending }) => (
|
||||||
<>{isPending ? <Spinner /> : <PlusIcon />} Matricular</>
|
<>
|
||||||
|
{isPending ? <Spinner /> : <PlusIcon />}
|
||||||
|
<span className="max-lg:hidden">Matricular</span>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
</NavLink>
|
</NavLink>
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
Reference in New Issue
Block a user