Refactorización de wizards para consistencia, reusabilidad y mantenibilidad
This commit is contained in:
@@ -23,43 +23,41 @@ export function WizardControls({
|
|||||||
const isLast = idx >= Wizard.steps.length - 1
|
const isLast = idx >= Wizard.steps.length - 1
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex-none border-t bg-white p-6">
|
<div className="flex items-center justify-between">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex-1">
|
||||||
<div className="flex-1">
|
{wizard.errorMessage && (
|
||||||
{wizard.errorMessage && (
|
<span className="text-destructive text-sm font-medium">
|
||||||
<span className="text-destructive text-sm font-medium">
|
{wizard.errorMessage}
|
||||||
{wizard.errorMessage}
|
</span>
|
||||||
</span>
|
)}
|
||||||
)}
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex gap-4">
|
<div className="flex gap-4">
|
||||||
|
<Button
|
||||||
|
variant="secondary"
|
||||||
|
onClick={() => methods.prev()}
|
||||||
|
disabled={idx === 0 || wizard.isLoading}
|
||||||
|
>
|
||||||
|
Anterior
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
{!isLast ? (
|
||||||
<Button
|
<Button
|
||||||
variant="secondary"
|
onClick={() => methods.next()}
|
||||||
onClick={() => methods.prev()}
|
disabled={
|
||||||
disabled={idx === 0 || wizard.isLoading}
|
wizard.isLoading ||
|
||||||
|
(idx === 0 && !canContinueDesdeMetodo) ||
|
||||||
|
(idx === 1 && !canContinueDesdeBasicos) ||
|
||||||
|
(idx === 2 && !canContinueDesdeConfig)
|
||||||
|
}
|
||||||
>
|
>
|
||||||
Anterior
|
Siguiente
|
||||||
</Button>
|
</Button>
|
||||||
|
) : (
|
||||||
{!isLast ? (
|
<Button onClick={onCreate} disabled={wizard.isLoading}>
|
||||||
<Button
|
{wizard.isLoading ? 'Creando...' : 'Crear Asignatura'}
|
||||||
onClick={() => methods.next()}
|
</Button>
|
||||||
disabled={
|
)}
|
||||||
wizard.isLoading ||
|
|
||||||
(idx === 0 && !canContinueDesdeMetodo) ||
|
|
||||||
(idx === 1 && !canContinueDesdeBasicos) ||
|
|
||||||
(idx === 2 && !canContinueDesdeConfig)
|
|
||||||
}
|
|
||||||
>
|
|
||||||
Siguiente
|
|
||||||
</Button>
|
|
||||||
) : (
|
|
||||||
<Button onClick={onCreate} disabled={wizard.isLoading}>
|
|
||||||
{wizard.isLoading ? 'Creando...' : 'Crear Asignatura'}
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
41
src/components/wizard/StepWithTooltip.tsx
Normal file
41
src/components/wizard/StepWithTooltip.tsx
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
import { useState } from 'react'
|
||||||
|
|
||||||
|
import {
|
||||||
|
Tooltip,
|
||||||
|
TooltipContent,
|
||||||
|
TooltipProvider,
|
||||||
|
TooltipTrigger,
|
||||||
|
} from '@/components/ui/tooltip'
|
||||||
|
|
||||||
|
export function StepWithTooltip({
|
||||||
|
title,
|
||||||
|
desc,
|
||||||
|
}: {
|
||||||
|
title: string
|
||||||
|
desc: string
|
||||||
|
}) {
|
||||||
|
const [isOpen, setIsOpen] = useState(false)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TooltipProvider delayDuration={0}>
|
||||||
|
<Tooltip open={isOpen} onOpenChange={setIsOpen}>
|
||||||
|
<TooltipTrigger asChild>
|
||||||
|
<span
|
||||||
|
className="cursor-help decoration-dotted underline-offset-4 hover:underline"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation()
|
||||||
|
setIsOpen((prev) => !prev)
|
||||||
|
}}
|
||||||
|
onMouseEnter={() => setIsOpen(true)}
|
||||||
|
onMouseLeave={() => setIsOpen(false)}
|
||||||
|
>
|
||||||
|
{title}
|
||||||
|
</span>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent className="max-w-50 text-xs">
|
||||||
|
<p>{desc}</p>
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
</TooltipProvider>
|
||||||
|
)
|
||||||
|
}
|
||||||
52
src/components/wizard/WizardLayout.tsx
Normal file
52
src/components/wizard/WizardLayout.tsx
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
import * as Icons from 'lucide-react'
|
||||||
|
|
||||||
|
import { CardHeader, CardTitle } from '@/components/ui/card'
|
||||||
|
import { Dialog, DialogContent } from '@/components/ui/dialog'
|
||||||
|
|
||||||
|
export function WizardLayout({
|
||||||
|
title,
|
||||||
|
onClose,
|
||||||
|
headerSlot,
|
||||||
|
footerSlot,
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
|
title: string
|
||||||
|
onClose: () => void
|
||||||
|
headerSlot?: React.ReactNode
|
||||||
|
footerSlot?: React.ReactNode
|
||||||
|
children: React.ReactNode
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<Dialog open={true} onOpenChange={(open) => !open && onClose()}>
|
||||||
|
<DialogContent
|
||||||
|
className="flex h-[90vh] w-[calc(100%-2rem)] flex-col gap-0 overflow-hidden p-0 sm:max-w-4xl"
|
||||||
|
onInteractOutside={(e) => {
|
||||||
|
e.preventDefault()
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="z-10 flex-none border-b bg-white">
|
||||||
|
<CardHeader className="flex flex-row items-center justify-between gap-4 p-6 pb-4">
|
||||||
|
<CardTitle>{title}</CardTitle>
|
||||||
|
<button
|
||||||
|
onClick={onClose}
|
||||||
|
className="ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground rounded-sm opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-none disabled:pointer-events-none"
|
||||||
|
>
|
||||||
|
<Icons.X className="h-4 w-4" />
|
||||||
|
<span className="sr-only">Cerrar</span>
|
||||||
|
</button>
|
||||||
|
</CardHeader>
|
||||||
|
|
||||||
|
{headerSlot ? <div className="px-6 pb-6">{headerSlot}</div> : null}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex-1 overflow-y-auto bg-gray-50/30 p-6">
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{footerSlot ? (
|
||||||
|
<div className="flex-none border-t bg-white p-6">{footerSlot}</div>
|
||||||
|
) : null}
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
)
|
||||||
|
}
|
||||||
59
src/components/wizard/WizardResponsiveHeader.tsx
Normal file
59
src/components/wizard/WizardResponsiveHeader.tsx
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
import { CircularProgress } from '@/components/CircularProgress'
|
||||||
|
import { StepWithTooltip } from '@/components/wizard/StepWithTooltip'
|
||||||
|
|
||||||
|
export function WizardResponsiveHeader({
|
||||||
|
wizard,
|
||||||
|
methods,
|
||||||
|
}: {
|
||||||
|
wizard: any
|
||||||
|
methods: any
|
||||||
|
}) {
|
||||||
|
const idx = wizard.utils.getIndex(methods.current.id)
|
||||||
|
const totalSteps = wizard.steps.length
|
||||||
|
const currentIndex = idx + 1
|
||||||
|
const hasNextStep = idx < totalSteps - 1
|
||||||
|
const nextStep = wizard.steps[currentIndex]
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="block sm:hidden">
|
||||||
|
<div className="flex items-center gap-5">
|
||||||
|
<CircularProgress current={currentIndex} total={totalSteps} />
|
||||||
|
<div className="flex flex-col justify-center">
|
||||||
|
<h2 className="text-lg font-bold text-slate-900">
|
||||||
|
<StepWithTooltip
|
||||||
|
title={methods.current.title}
|
||||||
|
desc={methods.current.description}
|
||||||
|
/>
|
||||||
|
</h2>
|
||||||
|
{hasNextStep && nextStep ? (
|
||||||
|
<p className="text-sm text-slate-400">
|
||||||
|
Siguiente: {nextStep.title}
|
||||||
|
</p>
|
||||||
|
) : (
|
||||||
|
<p className="text-sm font-medium text-green-500">
|
||||||
|
¡Último paso!
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="hidden sm:block">
|
||||||
|
<wizard.Stepper.Navigation className="border-border/60 rounded-xl border bg-slate-50 p-2">
|
||||||
|
{wizard.steps.map((step: any) => (
|
||||||
|
<wizard.Stepper.Step
|
||||||
|
key={step.id}
|
||||||
|
of={step.id}
|
||||||
|
className="whitespace-nowrap"
|
||||||
|
>
|
||||||
|
<wizard.Stepper.Title>
|
||||||
|
<StepWithTooltip title={step.title} desc={step.description} />
|
||||||
|
</wizard.Stepper.Title>
|
||||||
|
</wizard.Stepper.Step>
|
||||||
|
))}
|
||||||
|
</wizard.Stepper.Navigation>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import { useNavigate } from '@tanstack/react-router'
|
import { useNavigate } from '@tanstack/react-router'
|
||||||
|
import * as Icons from 'lucide-react'
|
||||||
|
|
||||||
import { useNuevaAsignaturaWizard } from './hooks/useNuevaAsignaturaWizard'
|
import { useNuevaAsignaturaWizard } from './hooks/useNuevaAsignaturaWizard'
|
||||||
|
|
||||||
@@ -6,13 +7,20 @@ import { PasoBasicosForm } from '@/components/asignaturas/wizard/PasoBasicosForm
|
|||||||
import { PasoConfiguracionPanel } from '@/components/asignaturas/wizard/PasoConfiguracionPanel'
|
import { PasoConfiguracionPanel } from '@/components/asignaturas/wizard/PasoConfiguracionPanel'
|
||||||
import { PasoMetodoCardGroup } from '@/components/asignaturas/wizard/PasoMetodoCardGroup'
|
import { PasoMetodoCardGroup } from '@/components/asignaturas/wizard/PasoMetodoCardGroup'
|
||||||
import { PasoResumenCard } from '@/components/asignaturas/wizard/PasoResumenCard'
|
import { PasoResumenCard } from '@/components/asignaturas/wizard/PasoResumenCard'
|
||||||
import { VistaSinPermisos } from '@/components/asignaturas/wizard/VistaSinPermisos'
|
|
||||||
import { WizardControls } from '@/components/asignaturas/wizard/WizardControls'
|
import { WizardControls } from '@/components/asignaturas/wizard/WizardControls'
|
||||||
import { WizardHeader } from '@/components/asignaturas/wizard/WizardHeader'
|
|
||||||
import { defineStepper } from '@/components/stepper'
|
import { defineStepper } from '@/components/stepper'
|
||||||
import { Dialog, DialogContent } from '@/components/ui/dialog'
|
import { Button } from '@/components/ui/button'
|
||||||
|
import {
|
||||||
|
Card,
|
||||||
|
CardContent,
|
||||||
|
CardDescription,
|
||||||
|
CardHeader,
|
||||||
|
CardTitle,
|
||||||
|
} from '@/components/ui/card'
|
||||||
|
import { WizardLayout } from '@/components/wizard/WizardLayout'
|
||||||
|
import { WizardResponsiveHeader } from '@/components/wizard/WizardResponsiveHeader'
|
||||||
|
|
||||||
const auth_get_current_user_role = () => 'JEFE_CARRERA' as const
|
const auth_get_current_user_role = (): string => 'JEFE_CARRERA'
|
||||||
|
|
||||||
const Wizard = defineStepper(
|
const Wizard = defineStepper(
|
||||||
{
|
{
|
||||||
@@ -55,85 +63,90 @@ export function NuevaAsignaturaModalContainer({ planId }: { planId: string }) {
|
|||||||
navigate({ to: `/planes/${planId}/asignaturas`, resetScroll: false })
|
navigate({ to: `/planes/${planId}/asignaturas`, resetScroll: false })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (role !== 'JEFE_CARRERA') {
|
||||||
|
return (
|
||||||
|
<WizardLayout title="Nueva Asignatura" onClose={handleClose}>
|
||||||
|
<Card className="border-destructive/40">
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle className="text-destructive flex items-center gap-2">
|
||||||
|
<Icons.ShieldAlert className="h-5 w-5" />
|
||||||
|
Sin permisos
|
||||||
|
</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
Solo el Jefe de Carrera puede crear asignaturas.
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="flex justify-end">
|
||||||
|
<Button variant="secondary" onClick={handleClose}>
|
||||||
|
Volver
|
||||||
|
</Button>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</WizardLayout>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={true} onOpenChange={(open) => !open && handleClose()}>
|
<Wizard.Stepper.Provider
|
||||||
<DialogContent
|
initialStep={Wizard.utils.getFirst().id}
|
||||||
className="flex h-[90vh] w-[calc(100%-2rem)] flex-col gap-0 overflow-hidden p-0 sm:max-w-4xl"
|
className="flex h-full flex-col"
|
||||||
onInteractOutside={(e) => e.preventDefault()}
|
>
|
||||||
>
|
{({ methods }) => {
|
||||||
{role !== 'JEFE_CARRERA' ? (
|
const idx = Wizard.utils.getIndex(methods.current.id)
|
||||||
<VistaSinPermisos onClose={handleClose} />
|
|
||||||
) : (
|
return (
|
||||||
<Wizard.Stepper.Provider
|
<WizardLayout
|
||||||
initialStep={Wizard.utils.getFirst().id}
|
title="Nueva Asignatura"
|
||||||
className="flex h-full flex-col"
|
onClose={handleClose}
|
||||||
|
headerSlot={
|
||||||
|
<WizardResponsiveHeader wizard={Wizard} methods={methods} />
|
||||||
|
}
|
||||||
|
footerSlot={
|
||||||
|
<Wizard.Stepper.Controls>
|
||||||
|
<WizardControls
|
||||||
|
Wizard={Wizard}
|
||||||
|
methods={methods}
|
||||||
|
wizard={wizard}
|
||||||
|
canContinueDesdeMetodo={canContinueDesdeMetodo}
|
||||||
|
canContinueDesdeBasicos={canContinueDesdeBasicos}
|
||||||
|
canContinueDesdeConfig={canContinueDesdeConfig}
|
||||||
|
onCreate={() => crearAsignatura(handleClose)}
|
||||||
|
/>
|
||||||
|
</Wizard.Stepper.Controls>
|
||||||
|
}
|
||||||
>
|
>
|
||||||
{({ methods }) => {
|
<div className="mx-auto max-w-3xl">
|
||||||
const currentIndex = Wizard.utils.getIndex(methods.current.id) + 1
|
{idx === 0 && (
|
||||||
const totalSteps = Wizard.steps.length
|
<Wizard.Stepper.Panel>
|
||||||
const nextStep = Wizard.steps[currentIndex] ?? {
|
<PasoMetodoCardGroup wizard={wizard} onChange={setWizard} />
|
||||||
title: '',
|
</Wizard.Stepper.Panel>
|
||||||
description: '',
|
)}
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
{idx === 1 && (
|
||||||
<>
|
<Wizard.Stepper.Panel>
|
||||||
<WizardHeader
|
<PasoBasicosForm wizard={wizard} onChange={setWizard} />
|
||||||
title="Nueva Asignatura"
|
</Wizard.Stepper.Panel>
|
||||||
Wizard={Wizard}
|
)}
|
||||||
methods={{ ...methods, onClose: handleClose }}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div className="flex-1 overflow-y-auto bg-gray-50/30 p-6">
|
{idx === 2 && (
|
||||||
<div className="mx-auto max-w-3xl">
|
<Wizard.Stepper.Panel>
|
||||||
{Wizard.utils.getIndex(methods.current.id) === 0 && (
|
<PasoConfiguracionPanel
|
||||||
<Wizard.Stepper.Panel>
|
|
||||||
<PasoMetodoCardGroup
|
|
||||||
wizard={wizard}
|
|
||||||
onChange={setWizard}
|
|
||||||
/>
|
|
||||||
</Wizard.Stepper.Panel>
|
|
||||||
)}
|
|
||||||
{Wizard.utils.getIndex(methods.current.id) === 1 && (
|
|
||||||
<Wizard.Stepper.Panel>
|
|
||||||
<PasoBasicosForm
|
|
||||||
wizard={wizard}
|
|
||||||
onChange={setWizard}
|
|
||||||
/>
|
|
||||||
</Wizard.Stepper.Panel>
|
|
||||||
)}
|
|
||||||
{Wizard.utils.getIndex(methods.current.id) === 2 && (
|
|
||||||
<Wizard.Stepper.Panel>
|
|
||||||
<PasoConfiguracionPanel
|
|
||||||
wizard={wizard}
|
|
||||||
onChange={setWizard}
|
|
||||||
onGenerarIA={simularGeneracionIA}
|
|
||||||
/>
|
|
||||||
</Wizard.Stepper.Panel>
|
|
||||||
)}
|
|
||||||
{Wizard.utils.getIndex(methods.current.id) === 3 && (
|
|
||||||
<Wizard.Stepper.Panel>
|
|
||||||
<PasoResumenCard wizard={wizard} />
|
|
||||||
</Wizard.Stepper.Panel>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<WizardControls
|
|
||||||
Wizard={Wizard}
|
|
||||||
methods={methods}
|
|
||||||
wizard={wizard}
|
wizard={wizard}
|
||||||
canContinueDesdeMetodo={canContinueDesdeMetodo}
|
onChange={setWizard}
|
||||||
canContinueDesdeBasicos={canContinueDesdeBasicos}
|
onGenerarIA={simularGeneracionIA}
|
||||||
canContinueDesdeConfig={canContinueDesdeConfig}
|
|
||||||
onCreate={() => crearAsignatura(handleClose)}
|
|
||||||
/>
|
/>
|
||||||
</>
|
</Wizard.Stepper.Panel>
|
||||||
)
|
)}
|
||||||
}}
|
|
||||||
</Wizard.Stepper.Provider>
|
{idx === 3 && (
|
||||||
)}
|
<Wizard.Stepper.Panel>
|
||||||
</DialogContent>
|
<PasoResumenCard wizard={wizard} />
|
||||||
</Dialog>
|
</Wizard.Stepper.Panel>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</WizardLayout>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
</Wizard.Stepper.Provider>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import { PasoDetallesPanel } from '@/components/planes/wizard/PasoDetallesPanel/
|
|||||||
import { PasoModoCardGroup } from '@/components/planes/wizard/PasoModoCardGroup'
|
import { PasoModoCardGroup } from '@/components/planes/wizard/PasoModoCardGroup'
|
||||||
import { PasoResumenCard } from '@/components/planes/wizard/PasoResumenCard'
|
import { PasoResumenCard } from '@/components/planes/wizard/PasoResumenCard'
|
||||||
import { WizardControls } from '@/components/planes/wizard/WizardControls'
|
import { WizardControls } from '@/components/planes/wizard/WizardControls'
|
||||||
import { WizardHeader } from '@/components/planes/wizard/WizardHeader'
|
|
||||||
import { defineStepper } from '@/components/stepper'
|
import { defineStepper } from '@/components/stepper'
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
@@ -19,16 +18,12 @@ import {
|
|||||||
CardHeader,
|
CardHeader,
|
||||||
CardTitle,
|
CardTitle,
|
||||||
} from '@/components/ui/card'
|
} from '@/components/ui/card'
|
||||||
import {
|
import { WizardLayout } from '@/components/wizard/WizardLayout'
|
||||||
Dialog,
|
import { WizardResponsiveHeader } from '@/components/wizard/WizardResponsiveHeader'
|
||||||
DialogContent,
|
|
||||||
DialogHeader,
|
|
||||||
DialogTitle,
|
|
||||||
} from '@/components/ui/dialog'
|
|
||||||
// import { useGeneratePlanAI } from '@/data/hooks/usePlans'
|
// import { useGeneratePlanAI } from '@/data/hooks/usePlans'
|
||||||
|
|
||||||
// Mock de permisos/rol
|
// Mock de permisos/rol
|
||||||
const auth_get_current_user_role = () => 'JEFE_CARRERA' as const
|
const auth_get_current_user_role = (): string => 'JEFE_CARRERA'
|
||||||
|
|
||||||
const Wizard = defineStepper(
|
const Wizard = defineStepper(
|
||||||
{
|
{
|
||||||
@@ -64,136 +59,97 @@ export default function NuevoPlanModalContainer() {
|
|||||||
|
|
||||||
// Crear plan: ahora la lógica vive en WizardControls
|
// Crear plan: ahora la lógica vive en WizardControls
|
||||||
|
|
||||||
|
if (role !== 'JEFE_CARRERA') {
|
||||||
|
return (
|
||||||
|
<WizardLayout title="Nuevo plan de estudios" onClose={handleClose}>
|
||||||
|
<Card className="border-destructive/40">
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle className="flex items-center gap-2">
|
||||||
|
<Icons.ShieldAlert className="text-destructive h-5 w-5" />
|
||||||
|
Sin permisos
|
||||||
|
</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
No tienes permisos para crear planes de estudio.
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="flex justify-end">
|
||||||
|
<button
|
||||||
|
className="ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground rounded-md border px-3 py-2 text-sm opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-none"
|
||||||
|
onClick={handleClose}
|
||||||
|
>
|
||||||
|
Volver
|
||||||
|
</button>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</WizardLayout>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={true} onOpenChange={(open) => !open && handleClose()}>
|
<Wizard.Stepper.Provider
|
||||||
<DialogContent
|
initialStep={Wizard.utils.getFirst().id}
|
||||||
className="flex h-[90vh] w-[calc(100%-2rem)] flex-col gap-0 overflow-hidden p-0 sm:max-w-4xl"
|
className="flex h-full flex-col"
|
||||||
onInteractOutside={(e) => {
|
>
|
||||||
e.preventDefault()
|
{({ methods }) => {
|
||||||
}}
|
const idx = Wizard.utils.getIndex(methods.current.id)
|
||||||
>
|
|
||||||
{role !== 'JEFE_CARRERA' ? (
|
return (
|
||||||
<>
|
<WizardLayout
|
||||||
<DialogHeader className="flex-none border-b p-6">
|
title="Nuevo plan de estudios"
|
||||||
<DialogTitle>Nuevo plan de estudios</DialogTitle>
|
onClose={handleClose}
|
||||||
</DialogHeader>
|
headerSlot={
|
||||||
<div className="flex-1 p-6">
|
<WizardResponsiveHeader wizard={Wizard} methods={methods} />
|
||||||
<Card className="border-destructive/40">
|
}
|
||||||
<CardHeader>
|
footerSlot={
|
||||||
<CardTitle className="flex items-center gap-2">
|
<Wizard.Stepper.Controls>
|
||||||
<Icons.ShieldAlert className="text-destructive h-5 w-5" />
|
<WizardControls
|
||||||
Sin permisos
|
errorMessage={wizard.errorMessage}
|
||||||
</CardTitle>
|
onPrev={() => methods.prev()}
|
||||||
<CardDescription>
|
onNext={() => methods.next()}
|
||||||
No tienes permisos para crear planes de estudio.
|
disablePrev={idx === 0 || wizard.isLoading}
|
||||||
</CardDescription>
|
disableNext={
|
||||||
</CardHeader>
|
wizard.isLoading ||
|
||||||
<CardContent className="flex justify-end">
|
(idx === 0 && !canContinueDesdeModo) ||
|
||||||
<button
|
(idx === 1 && !canContinueDesdeBasicos) ||
|
||||||
className="ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground rounded-md border px-3 py-2 text-sm opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-none"
|
(idx === 2 && !canContinueDesdeDetalles)
|
||||||
onClick={handleClose}
|
}
|
||||||
>
|
disableCreate={wizard.isLoading}
|
||||||
Volver
|
isLastStep={idx >= Wizard.steps.length - 1}
|
||||||
</button>
|
wizard={wizard}
|
||||||
</CardContent>
|
setWizard={setWizard}
|
||||||
</Card>
|
/>
|
||||||
</div>
|
</Wizard.Stepper.Controls>
|
||||||
</>
|
}
|
||||||
) : (
|
|
||||||
<Wizard.Stepper.Provider
|
|
||||||
initialStep={Wizard.utils.getFirst().id}
|
|
||||||
className="flex h-full flex-col"
|
|
||||||
>
|
>
|
||||||
{({ methods }) => {
|
<div className="mx-auto max-w-3xl">
|
||||||
const currentIndex = Wizard.utils.getIndex(methods.current.id) + 1
|
{idx === 0 && (
|
||||||
const totalSteps = Wizard.steps.length
|
<Wizard.Stepper.Panel>
|
||||||
const nextStep = Wizard.steps[currentIndex] ?? {
|
<PasoModoCardGroup wizard={wizard} onChange={setWizard} />
|
||||||
title: '',
|
</Wizard.Stepper.Panel>
|
||||||
description: '',
|
)}
|
||||||
}
|
{idx === 1 && (
|
||||||
|
<Wizard.Stepper.Panel>
|
||||||
return (
|
<PasoBasicosForm wizard={wizard} onChange={setWizard} />
|
||||||
<>
|
</Wizard.Stepper.Panel>
|
||||||
<WizardHeader
|
)}
|
||||||
currentIndex={currentIndex}
|
{idx === 2 && (
|
||||||
totalSteps={totalSteps}
|
<Wizard.Stepper.Panel>
|
||||||
currentTitle={methods.current.title}
|
<PasoDetallesPanel
|
||||||
currentDescription={methods.current.description}
|
wizard={wizard}
|
||||||
nextTitle={nextStep.title}
|
onChange={setWizard}
|
||||||
onClose={handleClose}
|
isLoading={wizard.isLoading}
|
||||||
Wizard={Wizard}
|
|
||||||
/>
|
/>
|
||||||
|
</Wizard.Stepper.Panel>
|
||||||
<div className="flex-1 overflow-y-auto bg-gray-50/30 p-6">
|
)}
|
||||||
<div className="mx-auto max-w-3xl">
|
{idx === 3 && (
|
||||||
{Wizard.utils.getIndex(methods.current.id) === 0 && (
|
<Wizard.Stepper.Panel>
|
||||||
<Wizard.Stepper.Panel>
|
<PasoResumenCard wizard={wizard} />
|
||||||
<PasoModoCardGroup
|
</Wizard.Stepper.Panel>
|
||||||
wizard={wizard}
|
)}
|
||||||
onChange={setWizard}
|
</div>
|
||||||
/>
|
</WizardLayout>
|
||||||
</Wizard.Stepper.Panel>
|
)
|
||||||
)}
|
}}
|
||||||
{Wizard.utils.getIndex(methods.current.id) === 1 && (
|
</Wizard.Stepper.Provider>
|
||||||
<Wizard.Stepper.Panel>
|
|
||||||
<PasoBasicosForm
|
|
||||||
wizard={wizard}
|
|
||||||
onChange={setWizard}
|
|
||||||
/>
|
|
||||||
</Wizard.Stepper.Panel>
|
|
||||||
)}
|
|
||||||
{Wizard.utils.getIndex(methods.current.id) === 2 && (
|
|
||||||
<Wizard.Stepper.Panel>
|
|
||||||
<PasoDetallesPanel
|
|
||||||
wizard={wizard}
|
|
||||||
onChange={setWizard}
|
|
||||||
isLoading={wizard.isLoading}
|
|
||||||
/>
|
|
||||||
</Wizard.Stepper.Panel>
|
|
||||||
)}
|
|
||||||
{Wizard.utils.getIndex(methods.current.id) === 3 && (
|
|
||||||
<Wizard.Stepper.Panel>
|
|
||||||
<PasoResumenCard wizard={wizard} />
|
|
||||||
</Wizard.Stepper.Panel>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex-none border-t bg-white p-6">
|
|
||||||
<Wizard.Stepper.Controls>
|
|
||||||
<WizardControls
|
|
||||||
errorMessage={wizard.errorMessage}
|
|
||||||
onPrev={() => methods.prev()}
|
|
||||||
onNext={() => methods.next()}
|
|
||||||
disablePrev={
|
|
||||||
Wizard.utils.getIndex(methods.current.id) === 0 ||
|
|
||||||
wizard.isLoading
|
|
||||||
}
|
|
||||||
disableNext={
|
|
||||||
wizard.isLoading ||
|
|
||||||
(Wizard.utils.getIndex(methods.current.id) === 0 &&
|
|
||||||
!canContinueDesdeModo) ||
|
|
||||||
(Wizard.utils.getIndex(methods.current.id) === 1 &&
|
|
||||||
!canContinueDesdeBasicos) ||
|
|
||||||
(Wizard.utils.getIndex(methods.current.id) === 2 &&
|
|
||||||
!canContinueDesdeDetalles)
|
|
||||||
}
|
|
||||||
disableCreate={wizard.isLoading}
|
|
||||||
isLastStep={
|
|
||||||
Wizard.utils.getIndex(methods.current.id) >=
|
|
||||||
Wizard.steps.length - 1
|
|
||||||
}
|
|
||||||
wizard={wizard}
|
|
||||||
setWizard={setWizard}
|
|
||||||
/>
|
|
||||||
</Wizard.Stepper.Controls>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
</Wizard.Stepper.Provider>
|
|
||||||
)}
|
|
||||||
</DialogContent>
|
|
||||||
</Dialog>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user