feat: add context menu functionality and delete buttons for plans and carreras; update dependencies
This commit is contained in:
@@ -14,6 +14,8 @@ import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, D
|
||||
import { Select, SelectTrigger, SelectValue, SelectContent, SelectItem } from "@/components/ui/select"
|
||||
import { Accordion, AccordionItem, AccordionTrigger, AccordionContent } from "@/components/ui/accordion"
|
||||
import { Switch } from "@/components/ui/switch"
|
||||
import { ContextMenu, ContextMenuContent, ContextMenuItem, ContextMenuTrigger } from "@/components/ui/context-menu"
|
||||
import { useDeleteCarreraDialog } from "@/components/carreras/DeleteCarreras"
|
||||
|
||||
/* -------------------- Tipos -------------------- */
|
||||
type FacultadLite = { id: string; nombre: string; color?: string | null; icon?: string | null }
|
||||
@@ -27,7 +29,7 @@ export type CarreraRow = {
|
||||
}
|
||||
|
||||
/* -------------------- Query Keys & Fetchers -------------------- */
|
||||
const carrerasKeys = {
|
||||
export const carrerasKeys = {
|
||||
root: ["carreras"] as const,
|
||||
list: () => [...carrerasKeys.root, "list"] as const,
|
||||
}
|
||||
@@ -115,11 +117,10 @@ const tint = (hex?: string | null, a = 0.18) => {
|
||||
}
|
||||
const StatusPill = ({ active }: { active: boolean }) => (
|
||||
<span
|
||||
className={`text-[10px] px-2 py-0.5 rounded-full border ${
|
||||
active
|
||||
? "bg-emerald-50 text-emerald-700 border-emerald-200"
|
||||
: "bg-neutral-100 text-neutral-700 border-neutral-200"
|
||||
}`}
|
||||
className={`text-[10px] px-2 py-0.5 rounded-full border ${active
|
||||
? "bg-emerald-50 text-emerald-700 border-emerald-200"
|
||||
: "bg-neutral-100 text-neutral-700 border-neutral-200"
|
||||
}`}
|
||||
>
|
||||
{active ? "Activa" : "Inactiva"}
|
||||
</span>
|
||||
@@ -212,36 +213,47 @@ function RouteComponent() {
|
||||
const border = tint(fac?.color, 0.28)
|
||||
const chip = tint(fac?.color, 0.1)
|
||||
const IconComp = (fac?.icon && (Icons as any)[fac.icon]) || Icons.Building2
|
||||
const { setOpen: setDeleteOpen, dialog: deleteDialog } = useDeleteCarreraDialog(c.id, async () => {
|
||||
await qc.invalidateQueries({ queryKey: carrerasKeys.root })
|
||||
router.invalidate()
|
||||
})
|
||||
return (
|
||||
<article
|
||||
key={c.id}
|
||||
className="group relative overflow-hidden rounded-3xl bg-white shadow-sm ring-1 transition-all hover:shadow-md hover:-translate-y-0.5"
|
||||
style={{ borderColor: border, background: `linear-gradient(180deg, ${chip}, transparent)` }}
|
||||
>
|
||||
<div className="p-5 h-44 flex flex-col justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="inline-flex items-center justify-center rounded-2xl border px-2.5 py-2 bg-white/70" style={{ borderColor: border }}>
|
||||
<IconComp className="w-6 h-6" />
|
||||
</span>
|
||||
<div className="min-w-0">
|
||||
<div className="font-semibold truncate">{c.nombre}</div>
|
||||
<div className="text-xs text-neutral-600 truncate">{fac?.nombre ?? "—"} · {c.semestres} semestres</div>
|
||||
</div>
|
||||
</div>
|
||||
<ContextMenu key={c.id}>
|
||||
<ContextMenuTrigger onClick={(e) => openContextMenu(e)}>
|
||||
<article
|
||||
className="group relative overflow-hidden rounded-3xl bg-white shadow-sm ring-1 transition-all hover:shadow-md hover:-translate-y-0.5"
|
||||
style={{ borderColor: border, background: `linear-gradient(180deg, ${chip}, transparent)` }}
|
||||
>
|
||||
<div className="p-5 h-44 flex flex-col justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="inline-flex items-center justify-center rounded-2xl border px-2.5 py-2 bg-white/70" style={{ borderColor: border }}>
|
||||
<IconComp className="w-6 h-6" />
|
||||
</span>
|
||||
<div className="min-w-0">
|
||||
<div className="font-semibold truncate">{c.nombre}</div>
|
||||
<div className="text-xs text-neutral-600 truncate">{fac?.nombre ?? "—"} · {c.semestres} semestres</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-2 flex items-center justify-between">
|
||||
<StatusPill active={c.activo} />
|
||||
<div className="flex items-center gap-1.5">
|
||||
<Button variant="ghost" size="sm" onClick={() => setDetail(c)}>
|
||||
<Icons.Eye className="w-4 h-4 mr-1" /> Ver
|
||||
</Button>
|
||||
<Button variant="ghost" size="sm" onClick={() => setEditCarrera(c)}>
|
||||
<Icons.Pencil className="w-4 h-4 mr-1" /> Editar
|
||||
</Button>
|
||||
<div className="mt-2 flex items-center justify-between">
|
||||
<StatusPill active={c.activo} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
</article>
|
||||
</ContextMenuTrigger>
|
||||
<ContextMenuContent>
|
||||
<ContextMenuItem onClick={() => setDetail(c)}>
|
||||
<Icons.Eye className="w-4 h-4 mr-2" /> Ver
|
||||
</ContextMenuItem>
|
||||
<ContextMenuItem onClick={() => setEditCarrera(c)}>
|
||||
<Icons.Pencil className="w-4 h-4 mr-2" /> Editar
|
||||
</ContextMenuItem>
|
||||
<ContextMenuItem onClick={() => setDeleteOpen(true)}>
|
||||
<Icons.Trash className="w-4 h-4 mr-2" /> Eliminar
|
||||
</ContextMenuItem>
|
||||
</ContextMenuContent>
|
||||
{deleteDialog}
|
||||
</ContextMenu>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
@@ -287,6 +299,22 @@ function RouteComponent() {
|
||||
)
|
||||
}
|
||||
|
||||
function openContextMenu(e: React.MouseEvent) {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
// Simulate right click by opening context menu
|
||||
const trigger = e.currentTarget
|
||||
if (!(trigger instanceof HTMLElement)) return
|
||||
const event = new window.MouseEvent("contextmenu", {
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
view: window,
|
||||
clientX: e.clientX,
|
||||
clientY: e.clientY,
|
||||
})
|
||||
trigger.dispatchEvent(event)
|
||||
}
|
||||
|
||||
/* -------------------- Form crear/editar -------------------- */
|
||||
function CarreraFormDialog({
|
||||
open,
|
||||
|
||||
@@ -18,6 +18,7 @@ import confetti from "canvas-confetti"
|
||||
import { Textarea } from "@/components/ui/textarea"
|
||||
import { AuroraButton } from "@/components/effect/aurora-button"
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
|
||||
import { DeletePlanButton } from "@/components/planes/DeletePlan"
|
||||
|
||||
type LoaderData = { planId: string }
|
||||
|
||||
@@ -94,6 +95,7 @@ function RouteComponent() {
|
||||
<div className='flex gap-2'>
|
||||
<EditPlanButton plan={plan} />
|
||||
<AdjustAIButton plan={plan} />
|
||||
<DeletePlanButton planId={plan.id} />
|
||||
</div>
|
||||
</div>
|
||||
</CardHeader>
|
||||
|
||||
Reference in New Issue
Block a user