add cancel schedule

This commit is contained in:
2025-12-15 16:38:40 -03:00
parent 2774545d09
commit 62b5340b20
11 changed files with 238 additions and 85 deletions

View File

@@ -25,6 +25,7 @@ import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger
} from '@repo/ui/components/ui/dropdown-menu'
import { Spinner } from '@repo/ui/components/ui/spinner'
@@ -138,6 +139,7 @@ function ActionMenu({ id }: { id: string }) {
)}
</NavLink>
</DropdownMenuItem>
<DropdownMenuSeparator />
<RevokeItem id={id} />
</DropdownMenuContent>
</DropdownMenu>

View File

@@ -194,7 +194,7 @@ function RemoveDedupItem({
})
if (r.ok) {
toast.info('A proteção contra duplicação foi removida')
toast.info('A proteção contra duplicação foi removida.')
setOpen(false)
onSuccess?.()
}
@@ -279,7 +279,7 @@ function CancelItem({
})
if (r.ok) {
toast.info('A matrícula foi cancelada')
toast.info('A matrícula foi cancelada.')
setOpen(false)
onSuccess?.()
}

View File

@@ -1,12 +1,17 @@
import type { Route } from './+types/route'
import type { MouseEvent } from 'react'
import { useRequest, useToggle } from 'ahooks'
import {
BanIcon,
CalendarIcon,
CircleXIcon,
EllipsisIcon,
PlusIcon,
RocketIcon,
UserIcon
} from 'lucide-react'
import { toast } from 'sonner'
import { DateTime } from 'luxon'
import { Fragment, Suspense } from 'react'
import { Await } from 'react-router'
@@ -37,6 +42,27 @@ import { Link } from 'react-router'
import { Avatar, AvatarFallback } from '@repo/ui/components/ui/avatar'
import { initials } from '@repo/ui/lib/utils'
import { Abbr } from '@repo/ui/components/abbr'
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger
} from '@repo/ui/components/ui/dropdown-menu'
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
AlertDialogTrigger
} from '@repo/ui/components/ui/alert-dialog'
import { Spinner } from '@repo/ui/components/ui/spinner'
import { useParams } from 'react-router'
import { useRevalidator } from 'react-router'
export function meta({}: Route.MetaArgs) {
return [{ title: 'Matrículas agendadas' }]
@@ -100,7 +126,10 @@ export default function Route({
return (
<div className="space-y-5 lg:max-w-4xl mx-auto">
{scheduled.map(([run_at, items], index) => (
<div className="grid lg:grid-cols-5 gap-2.5" key={index}>
<div
className="grid grid-cols-1 lg:grid-cols-5 gap-2.5"
key={index}
>
<div>
{DateTime.fromISO(run_at)
.setLocale('pt-BR')
@@ -112,7 +141,7 @@ export default function Route({
<ItemGroup>
{items.map(
(
{ user, course, created_by, scheduled_at },
{ sk, user, course, created_by, scheduled_at },
index
) => (
<Fragment key={index}>
@@ -133,7 +162,7 @@ export default function Route({
<div className="mt-1">
<ul
className="lg:flex gap-2.5 text-muted-foreground text-sm
*:flex *:gap-1 *:items-center"
*:flex *:gap-1 *:items-center"
>
<li>
<CalendarIcon className="size-3.5" />{' '}
@@ -148,11 +177,9 @@ export default function Route({
</ul>
</div>
</ItemContent>
{/*<ItemActions>
<Button variant="ghost" size="icon-sm">
<EllipsisIcon />
</Button>
</ItemActions>*/}
<ItemActions className="self-start">
<ActionMenu sk={sk} />
</ItemActions>
</Item>
{index !== items.length - 1 && <ItemSeparator />}
@@ -172,6 +199,93 @@ export default function Route({
)
}
function ActionMenu({ sk }: { sk: string }) {
const { revalidate } = useRevalidator()
const onSuccess = () => {
revalidate()
}
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" size="icon-sm" className="cursor-pointer">
<EllipsisIcon />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="*:cursor-pointer w-42">
<DropdownMenuItem disabled>
<RocketIcon /> Matricular agora
</DropdownMenuItem>
<DropdownMenuSeparator />
<CancelItem sk={sk} onSuccess={onSuccess} />
</DropdownMenuContent>
</DropdownMenu>
)
}
function CancelItem({ sk, onSuccess }: { sk: string; onSuccess?: () => void }) {
const { orgid } = useParams()
const [open, { set: setOpen }] = useToggle(false)
const { runAsync, loading } = useRequest(
async () => {
const [scheduled_for, lock_hash] = sk.split('#')
return await fetch(`/~/api/orgs/${orgid}/enrollments/scheduled`, {
method: 'DELETE',
headers: new Headers({ 'Content-Type': 'application/json' }),
body: JSON.stringify({ scheduled_for, lock_hash })
})
},
{ manual: true }
)
const cancel = async (e: MouseEvent<HTMLButtonElement>) => {
e.preventDefault()
const r = await runAsync()
if (r.ok) {
toast.info('O agendamento foi cancelada.')
onSuccess?.()
}
setOpen(false)
}
return (
<AlertDialog open={open} onOpenChange={setOpen}>
<AlertDialogTrigger asChild>
<DropdownMenuItem
variant="destructive"
onSelect={(e) => e.preventDefault()}
>
<CircleXIcon /> Cancelar
</DropdownMenuItem>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Tem certeza absoluta?</AlertDialogTitle>
<AlertDialogDescription>
Esta ação não pode ser desfeita. Isso{' '}
<span className="font-bold">
cancela permanentemente o agendamento
</span>{' '}
desta matrícula.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter className="*:cursor-pointer">
<AlertDialogAction asChild>
<Button onClick={cancel} disabled={loading} variant="destructive">
{loading ? <Spinner /> : null} Continuar
</Button>
</AlertDialogAction>
<AlertDialogCancel>Cancelar</AlertDialogCancel>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
)
}
function grouping(items) {
const newItems = Object.entries(
items.reduce((acc, item) => {

View File

@@ -27,6 +27,7 @@ import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger
} from '@repo/ui/components/ui/dropdown-menu'
import { Spinner } from '@repo/ui/components/ui/spinner'
@@ -74,6 +75,7 @@ function ActionMenu({ row }: { row: any }) {
)}
</NavLink>
</DropdownMenuItem>
<DropdownMenuSeparator />
<UnlinkItem id={row.id} />
</DropdownMenuContent>
</DropdownMenu>