add reason hover card

This commit is contained in:
2026-01-26 15:52:33 -03:00
parent 631087ac9b
commit 96a8ee8775
4 changed files with 62 additions and 24 deletions

View File

@@ -21,6 +21,11 @@ import {
CardHeader, CardHeader,
CardTitle CardTitle
} from '@repo/ui/components/ui/card' } from '@repo/ui/components/ui/card'
import {
HoverCard,
HoverCardContent,
HoverCardTrigger
} from '@repo/ui/components/ui/hover-card'
import { import {
Popover, Popover,
PopoverContent, PopoverContent,
@@ -54,8 +59,8 @@ export function Enrollments({
<CardHeader> <CardHeader>
<CardTitle className="text-xl">Matrículas relacionadas</CardTitle> <CardTitle className="text-xl">Matrículas relacionadas</CardTitle>
<CardDescription> <CardDescription>
Acompanhe o status e os detalhes de todas as matrículas relacionadas a Acompanhe os detalhes de todas as matrículas relacionadas a esta
esta compra. compra.
</CardDescription> </CardDescription>
<CardAction> <CardAction>
@@ -64,18 +69,17 @@ export function Enrollments({
</CardHeader> </CardHeader>
<CardContent> <CardContent>
<Table className="pointer-events-none"> <Table>
<TableHeader> <TableHeader className="pointer-events-none">
<TableRow> <TableRow>
<TableHead>Colaborador</TableHead> <TableHead>Colaborador</TableHead>
<TableHead>Curso</TableHead> <TableHead>Curso</TableHead>
<TableHead>Status</TableHead> <TableHead>Status</TableHead>
<TableHead>Executada em</TableHead> <TableHead>Executada em</TableHead>
{/*<TableHead>Agendada em</TableHead>*/}
<TableHead>Revogada em</TableHead> <TableHead>Revogada em</TableHead>
</TableRow> </TableRow>
</TableHeader> </TableHeader>
<TableBody> <TableBody className="[&_tr]:hover:bg-transparent">
{enrollments.map( {enrollments.map(
( (
{ {
@@ -83,11 +87,13 @@ export function Enrollments({
course, course,
status, status,
executed_at, executed_at,
// scheduled_at, rollback_at,
rollback_at reason: reason_
}, },
idx idx
) => { ) => {
const friendlyReason = reason_ ? reason(reason_) : null
return ( return (
<TableRow key={idx}> <TableRow key={idx}>
<TableCell> <TableCell>
@@ -112,18 +118,28 @@ export function Enrollments({
<Abbr>{course.name}</Abbr> <Abbr>{course.name}</Abbr>
</TableCell> </TableCell>
<TableCell> <TableCell>
<Status status={status} /> {friendlyReason ? (
<HoverCard openDelay={10} closeDelay={100}>
<HoverCardTrigger>
<Status status={status} />
</HoverCardTrigger>
<HoverCardContent align="end" className="text-sm">
<p className="flex gap-1">
<HelpCircleIcon className="size-4.5 mt-px" />{' '}
{friendlyReason}
</p>
</HoverCardContent>
</HoverCard>
) : (
<Status status={status} />
)}
</TableCell> </TableCell>
<TableCell> <TableCell>
{executed_at ? ( {executed_at ? (
<DateTime options={dtOptions}>{executed_at}</DateTime> <DateTime options={dtOptions}>{executed_at}</DateTime>
) : null} ) : null}
</TableCell> </TableCell>
{/*<TableCell>
{scheduled_at ? (
<DateTime options={dtOptions}>{scheduled_at}</DateTime>
) : null}
</TableCell>*/}
<TableCell> <TableCell>
{rollback_at ? ( {rollback_at ? (
<DateTime options={dtOptions}>{rollback_at}</DateTime> <DateTime options={dtOptions}>{rollback_at}</DateTime>
@@ -140,6 +156,17 @@ export function Enrollments({
) )
} }
const reasons: Record<string, string> = {
DEDUPLICATION: 'Matrícula ou agendamento já existentes.',
CANCELLATION: 'Cancelamento da matrícula.',
UNSCHEDULED: 'Cancelamento do agendamento da matrícula.',
DEADLINE: 'Data do agendamento é anterior ao dia atual.'
} as const
const reason = (reason_: string): string | null => {
return reason_ in reasons ? reasons[reason_] : null
}
const statuses: Record<string, { icon: LucideIcon; color?: string }> = { const statuses: Record<string, { icon: LucideIcon; color?: string }> = {
PENDING: { PENDING: {
icon: CircleDashedIcon, icon: CircleDashedIcon,
@@ -206,9 +233,9 @@ function SeatsMenu({ seats: seats_ }: { seats: Seat[] }) {
</div> </div>
<ul className="text-sm space-y-1.5"> <ul className="text-sm space-y-1.5">
{seats.map(({ course, quantity }) => { {seats.map(({ course, quantity }, idx) => {
return ( return (
<li> <li key={idx}>
{quantity}x {course.name} {quantity}x {course.name}
</li> </li>
) )

View File

@@ -110,6 +110,7 @@ type Item = {
type User = { type User = {
id: string id: string
name: string name: string
email: string
} }
type Invoice = { type Invoice = {
@@ -137,12 +138,13 @@ type Course = {
} }
export type Enrollment = { export type Enrollment = {
status: 'PENDING' | 'EXECUTED' | 'ROLLBACK' status: 'PENDING' | 'EXECUTED' | 'ROLLBACK' | 'SCHEDULED'
user: { id: string; name: string; email: string } user: User
course: Course course: Course
executed_at?: string executed_at?: string
rollback_at?: string rollback_at?: string
scheduled_at?: string scheduled_at?: string
reason?: string
} }
export type Seat = { export type Seat = {
@@ -213,7 +215,7 @@ export default function Route({ loaderData: { order } }: Route.ComponentProps) {
useEffect(() => { useEffect(() => {
reset() reset()
}, []) }, [])
console.log(seats)
return ( return (
<div className="space-y-2.5"> <div className="space-y-2.5">
<Breadcrumb> <Breadcrumb>

View File

@@ -42,7 +42,7 @@ def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool:
expr_attr_values={ expr_attr_values={
':rollback': 'ROLLBACK', ':rollback': 'ROLLBACK',
':scheduled': 'SCHEDULED', ':scheduled': 'SCHEDULED',
':reason': 'CANCELLATION', ':reason': 'UNSCHEDULED',
':now': now_, ':now': now_,
}, },
table_name=ORDER_TABLE, table_name=ORDER_TABLE,

View File

@@ -19,7 +19,7 @@ from layercake.dynamodb import (
TransactKey, TransactKey,
) )
from layercake.strutils import md5_hash from layercake.strutils import md5_hash
from pydantic import UUID4, BaseModel, BeforeValidator, Field, FutureDate from pydantic import UUID4, BaseModel, BeforeValidator, Field
from boto3clients import dynamodb_client from boto3clients import dynamodb_client
from config import ( from config import (
@@ -42,6 +42,9 @@ class DeduplicationConflictError(Exception): ...
class EnrollmentConflictError(Exception): ... class EnrollmentConflictError(Exception): ...
class DeadlineExceededError(Exception): ...
class User(BaseModel): class User(BaseModel):
id: str id: str
name: str name: str
@@ -200,9 +203,12 @@ def _get_courses(ids: set[str]) -> tuple[Course, ...]:
def _friendly_reason(reason: str) -> str: def _friendly_reason(reason: str) -> str:
if reason == 'DeduplicationConflictError': reasons = {
return 'DEDUPLICATION' 'DeduplicationConflictError': 'DEDUPLICATION',
return 'CONFLICT' 'DeadlineExceededError': 'DEADLINE',
}
return reasons.get(reason, 'CONFLICT')
CreatedBy = TypedDict('CreatedBy', {'user_id': str, 'name': str}) CreatedBy = TypedDict('CreatedBy', {'user_id': str, 'name': str})
@@ -341,6 +347,9 @@ def _enroll_later(enrollment: Enrollment, context: Context) -> None:
scheduled_for = _date_to_midnight(enrollment.scheduled_for) # type: ignore scheduled_for = _date_to_midnight(enrollment.scheduled_for) # type: ignore
lock_hash = md5_hash(f'{user.id}{course.id}') lock_hash = md5_hash(f'{user.id}{course.id}')
if now_ > scheduled_for:
raise DeadlineExceededError('Deadline exceeded')
with dyn.transact_writer(table_name=ENROLLMENT_TABLE) as transact: with dyn.transact_writer(table_name=ENROLLMENT_TABLE) as transact:
pk = f'SCHEDULED#ORG#{org.id}' pk = f'SCHEDULED#ORG#{org.id}'
sk = f'{scheduled_for.isoformat()}#{lock_hash}' sk = f'{scheduled_for.isoformat()}#{lock_hash}'