Compare commits
4 Commits
feature/Pd
...
6e2b3d72f1
| Author | SHA1 | Date | |
|---|---|---|---|
| 6e2b3d72f1 | |||
| 0c5c3f935b | |||
| 8da08b6bf1 | |||
| 1fe8f2b6a8 |
@@ -30,7 +30,7 @@ export function useDeleteCarreraDialog(carreraId: string, onDeleted?: () => void
|
|||||||
|
|
||||||
const dialog = (
|
const dialog = (
|
||||||
<Dialog open={open} onOpenChange={setOpen}>
|
<Dialog open={open} onOpenChange={setOpen}>
|
||||||
<DialogContent>
|
<DialogContent className="bg-white">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle className="font-mono" >¿Eliminar carrera?</DialogTitle>
|
<DialogTitle className="font-mono" >¿Eliminar carrera?</DialogTitle>
|
||||||
<DialogDescription>
|
<DialogDescription>
|
||||||
|
|||||||
@@ -11,9 +11,11 @@ import { supabase, useSupabaseAuth } from "@/auth/supabase"
|
|||||||
import { Field } from "./Field"
|
import { Field } from "./Field"
|
||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "../ui/tabs"
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "../ui/tabs"
|
||||||
import { asignaturaKeys } from "./planQueries"
|
import { asignaturaKeys } from "./planQueries"
|
||||||
|
import { useRouter } from "@tanstack/react-router"
|
||||||
|
|
||||||
export function AddAsignaturaButton({ planId, onAdded }: { planId: string; onAdded?: () => void }) {
|
export function AddAsignaturaButton({ planId, onAdded }: { planId: string; onAdded?: () => void }) {
|
||||||
const qc = useQueryClient()
|
const qc = useQueryClient()
|
||||||
|
const router = useRouter()
|
||||||
const supabaseAuth = useSupabaseAuth()
|
const supabaseAuth = useSupabaseAuth()
|
||||||
const [open, setOpen] = useState(false)
|
const [open, setOpen] = useState(false)
|
||||||
const [saving, setSaving] = useState(false)
|
const [saving, setSaving] = useState(false)
|
||||||
@@ -45,7 +47,13 @@ export function AddAsignaturaButton({ planId, onAdded }: { planId: string; onAdd
|
|||||||
objetivos: toNull(f.objetivos),
|
objetivos: toNull(f.objetivos),
|
||||||
contenidos: [], bibliografia: [], criterios_evaluacion: null,
|
contenidos: [], bibliografia: [], criterios_evaluacion: null,
|
||||||
}
|
}
|
||||||
const { error } = await supabase.from("asignaturas").insert([payload])
|
const { error,data } = await supabase.from("asignaturas").insert([payload]).select().single()
|
||||||
|
console.log(data);
|
||||||
|
router.invalidate()
|
||||||
|
router.navigate({
|
||||||
|
to: "/asignatura/$asignaturaId",
|
||||||
|
params: { asignaturaId: data.id },
|
||||||
|
})
|
||||||
setSaving(false)
|
setSaving(false)
|
||||||
if (error) { alert(error.message); return }
|
if (error) { alert(error.message); return }
|
||||||
setOpen(false)
|
setOpen(false)
|
||||||
@@ -64,8 +72,17 @@ export function AddAsignaturaButton({ planId, onAdded }: { planId: string; onAdd
|
|||||||
body: JSON.stringify({ planEstudiosId: planId, prompt: iaPrompt, semestre: iaSemestre.trim() ? Number(iaSemestre) : undefined, insert: true, uuid: supabaseAuth.user?.id }),
|
body: JSON.stringify({ planEstudiosId: planId, prompt: iaPrompt, semestre: iaSemestre.trim() ? Number(iaSemestre) : undefined, insert: true, uuid: supabaseAuth.user?.id }),
|
||||||
})
|
})
|
||||||
if (!res.ok) throw new Error(await res.text())
|
if (!res.ok) throw new Error(await res.text())
|
||||||
|
const data = await res.json()
|
||||||
|
console.log("Asignatura generada:", data)
|
||||||
|
const asignaturaId = data.asignaturaId || data.insertResult?.id
|
||||||
|
if (!asignaturaId) throw new Error("No se recibió el ID de la asignatura generada")
|
||||||
confetti({ particleCount: 120, spread: 80, origin: { y: 0.6 } })
|
confetti({ particleCount: 120, spread: 80, origin: { y: 0.6 } })
|
||||||
setOpen(false)
|
setOpen(false)
|
||||||
|
router.invalidate()
|
||||||
|
router.navigate({
|
||||||
|
to: "/asignatura/$asignaturaId",
|
||||||
|
params: { asignaturaId },
|
||||||
|
})
|
||||||
onAdded?.()
|
onAdded?.()
|
||||||
// qc.invalidateQueries({ queryKey: asignaturaKeys.preview(planId) })
|
// qc.invalidateQueries({ queryKey: asignaturaKeys.preview(planId) })
|
||||||
// qc.invalidateQueries({ queryKey: asignaturaKeys.count(planId) })
|
// qc.invalidateQueries({ queryKey: asignaturaKeys.count(planId) })
|
||||||
|
|||||||
@@ -104,7 +104,7 @@ export function CreatePlanDialog({ open, onOpenChange }: { open: boolean; onOpen
|
|||||||
setDbFiles((data || []).map((file: any) => ({
|
setDbFiles((data || []).map((file: any) => ({
|
||||||
id: file.documentos_id,
|
id: file.documentos_id,
|
||||||
titulo: file.titulo_archivo,
|
titulo: file.titulo_archivo,
|
||||||
s3_file_path: file.titulo_archivo,
|
s3_file_path: `prueba-referencias/documento_${file.documentos_id}.pdf`,
|
||||||
fecha_subida: file.fecha_subida,
|
fecha_subida: file.fecha_subida,
|
||||||
tags: file.tags || [],
|
tags: file.tags || [],
|
||||||
})));
|
})));
|
||||||
@@ -118,35 +118,35 @@ export function CreatePlanDialog({ open, onOpenChange }: { open: boolean; onOpen
|
|||||||
|
|
||||||
const isSelected = useCallback((path: string) => selectedFiles.includes(path), [selectedFiles]);
|
const isSelected = useCallback((path: string) => selectedFiles.includes(path), [selectedFiles]);
|
||||||
|
|
||||||
const toggleSelected = useCallback((path: string) => {
|
const toggleSelected = useCallback((id: string) => {
|
||||||
setSelectedFiles(prev => prev.includes(path) ? prev.filter(p => p !== path) : [...prev, path]);
|
setSelectedFiles(prev => prev.includes(id) ? prev.filter(p => p !== id) : [...prev, id]);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const replaceSelection = useCallback((path: string) => {
|
const replaceSelection = useCallback((id: string) => {
|
||||||
setSelectedFiles([path]);
|
setSelectedFiles([id]);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const rangeSelect = useCallback((start: number, end: number) => {
|
const rangeSelect = useCallback((start: number, end: number) => {
|
||||||
const [s, e] = start < end ? [start, end] : [end, start];
|
const [s, e] = start < end ? [start, end] : [end, start];
|
||||||
const paths = dbFiles.slice(s, e + 1).map(f => f.s3_file_path);
|
const ids = dbFiles.slice(s, e + 1).map(f => f.id);
|
||||||
setSelectedFiles(prev => Array.from(new Set([...prev, ...paths])));
|
setSelectedFiles(prev => Array.from(new Set([...prev, ...ids])));
|
||||||
}, [dbFiles]);
|
}, [dbFiles]);
|
||||||
|
|
||||||
const handleCardClick = useCallback((e: React.MouseEvent, index: number, file: { s3_file_path: string }) => {
|
const handleCardClick = useCallback((e: React.MouseEvent, index: number, file: { id: string }) => {
|
||||||
const path = file.s3_file_path;
|
const id = file.id;
|
||||||
|
|
||||||
if (e.shiftKey && lastSelectedIndex !== null) {
|
if (e.shiftKey && lastSelectedIndex !== null) {
|
||||||
rangeSelect(lastSelectedIndex, index);
|
rangeSelect(lastSelectedIndex, index);
|
||||||
} else if (e.metaKey || e.ctrlKey) {
|
} else if (e.metaKey || e.ctrlKey) {
|
||||||
toggleSelected(path);
|
toggleSelected(id);
|
||||||
setLastSelectedIndex(index);
|
setLastSelectedIndex(index);
|
||||||
} else {
|
} else {
|
||||||
if (isSelected(path) && selectedFiles.length === 1) {
|
if (isSelected(id) && selectedFiles.length === 1) {
|
||||||
// si ya es el único seleccionado, des-selecciona
|
// si ya es el único seleccionado, des-selecciona
|
||||||
setSelectedFiles([]);
|
setSelectedFiles([]);
|
||||||
setLastSelectedIndex(null);
|
setLastSelectedIndex(null);
|
||||||
} else {
|
} else {
|
||||||
replaceSelection(path);
|
replaceSelection(id);
|
||||||
setLastSelectedIndex(index);
|
setLastSelectedIndex(index);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -167,7 +167,7 @@ export function CreatePlanDialog({ open, onOpenChange }: { open: boolean; onOpen
|
|||||||
prompt: prompt,
|
prompt: prompt,
|
||||||
insert: true,
|
insert: true,
|
||||||
files: selectedFiles,
|
files: selectedFiles,
|
||||||
uuid: auth.user?.id,
|
created_by: auth.user?.id,
|
||||||
})
|
})
|
||||||
const newId = (res as any)?.id || (res as any)?.plan?.id || (res as any)?.data?.id
|
const newId = (res as any)?.id || (res as any)?.plan?.id || (res as any)?.data?.id
|
||||||
if (newId) {
|
if (newId) {
|
||||||
@@ -261,7 +261,7 @@ export function CreatePlanDialog({ open, onOpenChange }: { open: boolean; onOpen
|
|||||||
<div role="grid" className="grid gap-4 xs:grid-cols-2 sm:grid-cols-2 md:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-5">
|
<div role="grid" className="grid gap-4 xs:grid-cols-2 sm:grid-cols-2 md:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-5">
|
||||||
{dbFiles.map((file, index) => {
|
{dbFiles.map((file, index) => {
|
||||||
const ext = fileExt(file.titulo);
|
const ext = fileExt(file.titulo);
|
||||||
const selected = isSelected(file.s3_file_path);
|
const selected = isSelected(file.id);
|
||||||
console.log(file);
|
console.log(file);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -126,6 +126,17 @@ function RouteComponent() {
|
|||||||
const [detail, setDetail] = useState<CarreraRow | null>(null)
|
const [detail, setDetail] = useState<CarreraRow | null>(null)
|
||||||
const [editCarrera, setEditCarrera] = useState<CarreraRow | null>(null)
|
const [editCarrera, setEditCarrera] = useState<CarreraRow | null>(null)
|
||||||
const [createOpen, setCreateOpen] = useState(false)
|
const [createOpen, setCreateOpen] = useState(false)
|
||||||
|
const [deleteTarget, setDeleteTarget] = useState<CarreraRow | null>(null)
|
||||||
|
|
||||||
|
// ✅ Se declara UNA SOLA VEZ
|
||||||
|
const { setOpen: setDeleteOpen, dialog: deleteDialog } = useDeleteCarreraDialog(
|
||||||
|
deleteTarget?.id ?? "",
|
||||||
|
async () => {
|
||||||
|
await qc.invalidateQueries({ queryKey: carrerasKeys.root })
|
||||||
|
router.invalidate()
|
||||||
|
// setDeleteTarget(null)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
const filtered = useMemo(() => {
|
const filtered = useMemo(() => {
|
||||||
const term = q.trim().toLowerCase()
|
const term = q.trim().toLowerCase()
|
||||||
@@ -198,10 +209,7 @@ function RouteComponent() {
|
|||||||
const border = tint(fac?.color, 0.28)
|
const border = tint(fac?.color, 0.28)
|
||||||
const chip = tint(fac?.color, 0.1)
|
const chip = tint(fac?.color, 0.1)
|
||||||
const IconComp = (fac?.icon && (Icons as any)[fac.icon]) || Icons.Building2
|
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 (
|
return (
|
||||||
<ContextMenu key={c.id}>
|
<ContextMenu key={c.id}>
|
||||||
<ContextMenuTrigger onClick={(e) => openContextMenu(e)}>
|
<ContextMenuTrigger onClick={(e) => openContextMenu(e)}>
|
||||||
@@ -233,11 +241,14 @@ function RouteComponent() {
|
|||||||
<ContextMenuItem onClick={() => setEditCarrera(c)}>
|
<ContextMenuItem onClick={() => setEditCarrera(c)}>
|
||||||
<Icons.Pencil className="w-4 h-4 mr-2" /> Editar
|
<Icons.Pencil className="w-4 h-4 mr-2" /> Editar
|
||||||
</ContextMenuItem>
|
</ContextMenuItem>
|
||||||
<ContextMenuItem onClick={() => setDeleteOpen(true)}>
|
<ContextMenuItem onClick={() => {
|
||||||
|
setDeleteTarget(c)
|
||||||
|
setDeleteOpen(true)
|
||||||
|
}}>
|
||||||
<Icons.Trash className="w-4 h-4 mr-2" /> Eliminar
|
<Icons.Trash className="w-4 h-4 mr-2" /> Eliminar
|
||||||
</ContextMenuItem>
|
</ContextMenuItem>
|
||||||
</ContextMenuContent>
|
</ContextMenuContent>
|
||||||
{deleteDialog}
|
|
||||||
</ContextMenu>
|
</ContextMenu>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
@@ -247,6 +258,8 @@ function RouteComponent() {
|
|||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
|
{deleteDialog}
|
||||||
|
|
||||||
{/* Crear / Editar */}
|
{/* Crear / Editar */}
|
||||||
<CarreraFormDialog
|
<CarreraFormDialog
|
||||||
open={createOpen}
|
open={createOpen}
|
||||||
|
|||||||
@@ -49,7 +49,10 @@ export const Route = createFileRoute("/_authenticated/plan/$planId")({
|
|||||||
// ...existing code...
|
// ...existing code...
|
||||||
function RouteComponent() {
|
function RouteComponent() {
|
||||||
const qc = useQueryClient()
|
const qc = useQueryClient()
|
||||||
const { plan, asignaturas: asignaturasPreview } = Route.useLoaderData() as LoaderData
|
//const { plan, asignaturas: asignaturasPreview } = Route.useLoaderData() as LoaderData
|
||||||
|
const { plan } = Route.useLoaderData() as LoaderData
|
||||||
|
|
||||||
|
const { data: asignaturasPreview } = useSuspenseQuery(asignaturasPreviewOptions(plan.id))
|
||||||
const auth = useSupabaseAuth()
|
const auth = useSupabaseAuth()
|
||||||
const asignaturasCount = asignaturasPreview.length
|
const asignaturasCount = asignaturasPreview.length
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user