Se añadió el botón de eliminar asignatura, y se borra adecuadamente
This commit is contained in:
@@ -114,7 +114,7 @@ export function DetailDialog({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={open} onOpenChange={(o) => !o && onClose()}>
|
<Dialog open={open} onOpenChange={(o) => !o && onClose()}>
|
||||||
<DialogContent className="max-w-3xl">
|
<DialogContent className="max-w-fit">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle className="font-mono">{row?.titulo_archivo ?? "(Sin título)"}</DialogTitle>
|
<DialogTitle className="font-mono">{row?.titulo_archivo ?? "(Sin título)"}</DialogTitle>
|
||||||
<DialogDescription>{row?.descripcion || "Sin descripción"}</DialogDescription>
|
<DialogDescription>{row?.descripcion || "Sin descripción"}</DialogDescription>
|
||||||
@@ -134,13 +134,13 @@ export function DetailDialog({
|
|||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
{/* Tags
|
||||||
{row.tags?.length ? (
|
{row.tags?.length ? (
|
||||||
<div className="text-xs text-neutral-600">
|
<div className="text-xs text-neutral-600">
|
||||||
<span className="font-medium">Tags: </span>
|
<span className="font-medium">Tags: </span>
|
||||||
{row.tags.join(", ")}
|
{row.tags.join(", ")}
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null} */}
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<Label className="text-xs text-neutral-600">Instrucciones</Label>
|
<Label className="text-xs text-neutral-600">Instrucciones</Label>
|
||||||
|
|||||||
@@ -312,6 +312,7 @@ export function CreatePlanDialog({ open, onOpenChange }: { open: boolean; onOpen
|
|||||||
) : (
|
) : (
|
||||||
<p className="text-xs text-neutral-500">Fecha desconocida</p>
|
<p className="text-xs text-neutral-500">Fecha desconocida</p>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{file.tags && file.tags.length > 0 && (
|
{file.tags && file.tags.length > 0 && (
|
||||||
<div className="mt-2 flex flex-wrap gap-1.5">
|
<div className="mt-2 flex flex-wrap gap-1.5">
|
||||||
{file.tags.map((tag, i) => (
|
{file.tags.map((tag, i) => (
|
||||||
|
|||||||
@@ -167,6 +167,7 @@ function RouteComponent() {
|
|||||||
<p className="text-sm text-neutral-700 line-clamp-3">{r.descripcion}</p>
|
<p className="text-sm text-neutral-700 line-clamp-3">{r.descripcion}</p>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Tags
|
||||||
{r.tags && r.tags.length > 0 && (
|
{r.tags && r.tags.length > 0 && (
|
||||||
<div className="flex flex-wrap gap-1.5">
|
<div className="flex flex-wrap gap-1.5">
|
||||||
{r.tags.map((t, i) => (
|
{r.tags.map((t, i) => (
|
||||||
@@ -175,7 +176,7 @@ function RouteComponent() {
|
|||||||
</span>
|
</span>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)} */}
|
||||||
|
|
||||||
<div className="mt-auto flex items-center justify-between gap-2">
|
<div className="mt-auto flex items-center justify-between gap-2">
|
||||||
<Button variant="ghost" size="sm" onClick={() => setViewing(r)}>
|
<Button variant="ghost" size="sm" onClick={() => setViewing(r)}>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
// routes/_authenticated/asignatura/$asignaturaId.tsx
|
// routes/_authenticated/asignatura/$asignaturaId.tsx
|
||||||
|
import { useQueryClient } from "@tanstack/react-query";
|
||||||
import { createFileRoute, Link, useRouter } from "@tanstack/react-router"
|
import { createFileRoute, Link, useRouter } from "@tanstack/react-router"
|
||||||
import * as Icons from "lucide-react"
|
import * as Icons from "lucide-react"
|
||||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react"
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react"
|
||||||
@@ -165,6 +166,7 @@ function Page() {
|
|||||||
</Button>
|
</Button>
|
||||||
<EditAsignaturaButton asignatura={a} onUpdate={setA} />
|
<EditAsignaturaButton asignatura={a} onUpdate={setA} />
|
||||||
<MejorarAIButton asignaturaId={a.id} onApply={(nuevo) => setA(nuevo)} />
|
<MejorarAIButton asignaturaId={a.id} onApply={(nuevo) => setA(nuevo)} />
|
||||||
|
<BorrarAsignaturaButton asignatura_id={a.id} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -191,7 +193,7 @@ function Page() {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Syllabus */}
|
{/* Syllabus */}
|
||||||
{unidades.length > 0 && (
|
|
||||||
<Section id="syllabus" title="Programa / Contenidos" icon={Icons.ListTree}>
|
<Section id="syllabus" title="Programa / Contenidos" icon={Icons.ListTree}>
|
||||||
<div className="flex items-center gap-2 mb-2">
|
<div className="flex items-center gap-2 mb-2">
|
||||||
<div className="relative flex-1">
|
<div className="relative flex-1">
|
||||||
@@ -285,7 +287,7 @@ function Page() {
|
|||||||
)
|
)
|
||||||
})()}
|
})()}
|
||||||
</Section>
|
</Section>
|
||||||
)}
|
|
||||||
|
|
||||||
|
|
||||||
{/* Bibliografía */}
|
{/* Bibliografía */}
|
||||||
@@ -578,6 +580,51 @@ function MejorarAIButton({ asignaturaId, onApply }: {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function BorrarAsignaturaButton({ asignatura_id, onDeleted }: { asignatura_id: string; onDeleted?: () => void }) {
|
||||||
|
const [confirm, setConfirm] = useState(false)
|
||||||
|
const [loading, setLoading] = useState(false)
|
||||||
|
const router = useRouter()
|
||||||
|
const queryClient = useQueryClient()
|
||||||
|
|
||||||
|
async function handleDelete() {
|
||||||
|
setLoading(true)
|
||||||
|
try {
|
||||||
|
const { error, status, statusText } = await supabase.from("asignaturas").delete().eq("id", asignatura_id)
|
||||||
|
console.log({ status, statusText });
|
||||||
|
|
||||||
|
|
||||||
|
if (error) throw error
|
||||||
|
setConfirm(false)
|
||||||
|
queryClient.invalidateQueries({ queryKey: ["asignaturas"] })
|
||||||
|
if (onDeleted) onDeleted()
|
||||||
|
router.navigate({ to: "/asignaturas", search: {
|
||||||
|
q: "", // Término de búsqueda vacío
|
||||||
|
planId: "", // ID del plan (vacío si no aplica)
|
||||||
|
carreraId: "", // ID de la carrera (vacío si no aplica)
|
||||||
|
facultadId: "", // ID de la facultad (vacío si no aplica)
|
||||||
|
f: "", // Filtro vacío
|
||||||
|
}})
|
||||||
|
} catch (e: any) {
|
||||||
|
alert(e?.message || "Error al eliminar la asignatura")
|
||||||
|
} finally {
|
||||||
|
setLoading(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return confirm ? (
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<Button variant="destructive" onClick={handleDelete} disabled={loading}>
|
||||||
|
{loading ? "Eliminando…" : "Confirmar eliminación"}
|
||||||
|
</Button>
|
||||||
|
<Button variant="outline" onClick={() => setConfirm(false)} disabled={loading}>Cancelar</Button>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<Button variant="outline" onClick={() => setConfirm(true)}>
|
||||||
|
Eliminar asignatura
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
function Field({ label, children }: { label: string; children: React.ReactNode }) {
|
function Field({ label, children }: { label: string; children: React.ReactNode }) {
|
||||||
return (
|
return (
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
@@ -632,9 +679,9 @@ export function EditContenidosButton({
|
|||||||
}
|
}
|
||||||
return { title, temas }
|
return { title, temas }
|
||||||
})
|
})
|
||||||
return entries.length ? entries : [{ title: "Unidad 1", temas: [] }]
|
return entries.length ? entries : [{ title: "", temas: [] }]
|
||||||
} catch {
|
} catch {
|
||||||
return [{ title: "Unidad 1", temas: [] }]
|
return [{ title: "", temas: [] }]
|
||||||
}
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
@@ -650,7 +697,7 @@ export function EditContenidosButton({
|
|||||||
.forEach((t, i) => {
|
.forEach((t, i) => {
|
||||||
sub[String(i + 1)] = t
|
sub[String(i + 1)] = t
|
||||||
})
|
})
|
||||||
out[k] = { titulo: (u.title || "").trim() || `Unidad ${k}`, subtemas: sub }
|
out[k] = { titulo: (u.title || "").trim(), subtemas: sub }
|
||||||
})
|
})
|
||||||
return out
|
return out
|
||||||
}, [])
|
}, [])
|
||||||
@@ -669,7 +716,7 @@ export function EditContenidosButton({
|
|||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
return {
|
return {
|
||||||
title: (u.title || "").trim() || `Unidad ${idx + 1}`,
|
title: (u.title || "").trim(),
|
||||||
temas,
|
temas,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -839,7 +886,7 @@ export function EditContenidosButton({
|
|||||||
<Button
|
<Button
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
setUnits((prev) => [...prev, { title: `Unidad ${prev.length + 1}`, temas: [] }])
|
setUnits((prev) => [...prev, { title: "", temas: [] }])
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Icons.Plus className="w-4 h-4 mr-2" /> Agregar unidad
|
<Icons.Plus className="w-4 h-4 mr-2" /> Agregar unidad
|
||||||
@@ -850,7 +897,7 @@ export function EditContenidosButton({
|
|||||||
|
|
||||||
<DialogFooter className="px-6 pb-5">
|
<DialogFooter className="px-6 pb-5">
|
||||||
<Button variant="outline" onClick={cancel}>Cancelar</Button>
|
<Button variant="outline" onClick={cancel}>Cancelar</Button>
|
||||||
<Button onClick={save} disabled={saving}>
|
<Button onClick={save} disabled={saving || !hasChanges || units.some(u => !u.title.trim())}>
|
||||||
{saving ? (
|
{saving ? (
|
||||||
<span className="inline-flex items-center gap-2">
|
<span className="inline-flex items-center gap-2">
|
||||||
<Icons.Loader2 className="h-4 w-4 animate-spin" /> Guardando…
|
<Icons.Loader2 className="h-4 w-4 animate-spin" /> Guardando…
|
||||||
|
|||||||
@@ -115,7 +115,7 @@ function RouteComponent() {
|
|||||||
id: role.id,
|
id: role.id,
|
||||||
label: role.label,
|
label: role.label,
|
||||||
Icon: (Icons as any)[role.icono] || Icons.Cpu, // Icono por defecto si no está definido
|
Icon: (Icons as any)[role.icono] || Icons.Cpu, // Icono por defecto si no está definido
|
||||||
className: role.nombre_clase || "bg-gray-500 text-white", // Clase por defecto si no está definida
|
className: /* role.nombre_clase || */ "bg-gray-500 text-white", // Clase por defecto si no está definida
|
||||||
};
|
};
|
||||||
return acc;
|
return acc;
|
||||||
}, {} as Record<string, { id: string; label: string; Icon: React.ComponentType<React.SVGProps<SVGSVGElement>>; className: string }>);
|
}, {} as Record<string, { id: string; label: string; Icon: React.ComponentType<React.SVGProps<SVGSVGElement>>; className: string }>);
|
||||||
|
|||||||
Reference in New Issue
Block a user