feat(web): banner y disabled de secciones de medio inactivo — issue #16

This commit is contained in:
2026-04-17 11:46:09 -03:00
parent 455954fa98
commit 4fb25356a3
5 changed files with 53 additions and 6 deletions

View File

@@ -18,9 +18,10 @@ interface DeactivateSeccionModalProps {
seccionId: number
seccionNombre: string
activo: boolean
disabled?: boolean
}
export function DeactivateSeccionModal({ seccionId, seccionNombre, activo }: DeactivateSeccionModalProps) {
export function DeactivateSeccionModal({ seccionId, seccionNombre, activo, disabled = false }: DeactivateSeccionModalProps) {
const [open, setOpen] = useState(false)
const { mutate: deactivate, isPending: deactivating } = useDeactivateSeccion()
const { mutate: reactivate, isPending: reactivating } = useReactivateSeccion()
@@ -38,7 +39,7 @@ export function DeactivateSeccionModal({ seccionId, seccionNombre, activo }: Dea
return (
<AlertDialog open={open} onOpenChange={setOpen}>
<AlertDialogTrigger asChild>
<Button variant="outline" size="sm">
<Button variant="outline" size="sm" disabled={disabled}>
{activo ? 'Desactivar' : 'Reactivar'}
</Button>
</AlertDialogTrigger>

View File

@@ -0,0 +1,19 @@
import { AlertTriangle } from 'lucide-react'
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'
interface MedioInactivoBannerProps {
medioNombre: string
}
export function MedioInactivoBanner({ medioNombre }: MedioInactivoBannerProps) {
return (
<Alert variant="destructive" className="mb-4">
<AlertTriangle className="h-4 w-4" />
<AlertTitle>Medio desactivado</AlertTitle>
<AlertDescription>
El medio &quot;{medioNombre}&quot; está desactivado. Las operaciones de edición, activación y
desactivación de sus secciones están bloqueadas hasta que se reactive el medio.
</AlertDescription>
</Alert>
)
}

View File

@@ -11,9 +11,10 @@ import { DeactivateSeccionModal } from './DeactivateSeccionModal'
interface SeccionesTableProps {
rows: SeccionListItem[]
medioInactivo?: boolean
}
export function SeccionesTable({ rows }: SeccionesTableProps) {
export function SeccionesTable({ rows, medioInactivo = false }: SeccionesTableProps) {
const navigate = useNavigate()
const columns = useMemo<ColumnDef<SeccionListItem>[]>(
@@ -73,6 +74,7 @@ export function SeccionesTable({ rows }: SeccionesTableProps) {
<Button
variant="outline"
size="sm"
disabled={medioInactivo}
onClick={() => navigate(`/admin/secciones/${row.original.id}/edit`)}
>
Editar
@@ -81,6 +83,7 @@ export function SeccionesTable({ rows }: SeccionesTableProps) {
seccionId={row.original.id}
seccionNombre={row.original.nombre}
activo={row.original.activo}
disabled={medioInactivo}
/>
</CanPerform>
</div>
@@ -88,7 +91,7 @@ export function SeccionesTable({ rows }: SeccionesTableProps) {
meta: { priority: 'high' },
},
],
[navigate],
[navigate, medioInactivo],
)
return (

View File

@@ -4,7 +4,9 @@ import { Button } from '@/components/ui/button'
import { CanPerform } from '@/components/auth/CanPerform'
import { useSeccion } from '../hooks/useSeccion'
import { DeactivateSeccionModal } from '../components/DeactivateSeccionModal'
import { MedioInactivoBanner } from '../components/MedioInactivoBanner'
import { tipoSeccionLabel } from '../tipoSeccion'
import { useMedio } from '../../medios/hooks/useMedio'
function formatDate(iso: string | null): string {
if (!iso) return '—'
@@ -21,6 +23,8 @@ export function SeccionDetailPage() {
const navigate = useNavigate()
const { data: seccion, isLoading } = useSeccion(seccionId)
const { data: medio } = useMedio(seccion?.medioId ?? 0)
const medioInactivo = medio?.activo === false
if (isLoading) {
return (
@@ -79,10 +83,15 @@ export function SeccionDetailPage() {
</div>
</div>
{medioInactivo && medio && (
<MedioInactivoBanner medioNombre={medio.nombre} />
)}
<CanPerform permission="administracion:secciones:gestionar">
<div className="flex flex-wrap gap-2">
<Button
variant="outline"
disabled={medioInactivo}
onClick={() => navigate(`/admin/secciones/${seccionId}/edit`)}
>
Editar
@@ -91,6 +100,7 @@ export function SeccionDetailPage() {
seccionId={seccionId}
seccionNombre={seccion.nombre}
activo={seccion.activo}
disabled={medioInactivo}
/>
</div>
</CanPerform>

View File

@@ -5,7 +5,9 @@ import { Skeleton } from '@/components/ui/skeleton'
import { CanPerform } from '@/components/auth/CanPerform'
import { SeccionesTable } from '../components/SeccionesTable'
import { SeccionesFilters } from '../components/SeccionesFilters'
import { MedioInactivoBanner } from '../components/MedioInactivoBanner'
import { useSeccionesList } from '../hooks/useSeccionesList'
import { useMedio } from '../../medios/hooks/useMedio'
import type { TipoSeccion } from '../types'
export function SeccionesListPage() {
@@ -28,6 +30,10 @@ export function SeccionesListPage() {
const { data, isLoading } = useSeccionesList(query)
// Fetch parent medio only when filtering by a single medioId
const { data: filteredMedio } = useMedio(medioId ?? 0)
const medioInactivo = medioId !== undefined && filteredMedio?.activo === false
const handleMedioIdChange = useCallback((value: number | undefined) => {
setMedioId(value)
setPage(1)
@@ -57,12 +63,20 @@ export function SeccionesListPage() {
<div className="flex items-center justify-between">
<h1 className="text-xl font-semibold">Secciones</h1>
<CanPerform permission="administracion:secciones:gestionar">
<Button onClick={() => navigate('/admin/secciones/nuevo')} size="sm">
<Button
onClick={() => navigate('/admin/secciones/nuevo')}
size="sm"
disabled={medioInactivo}
>
Nueva sección
</Button>
</CanPerform>
</div>
{medioInactivo && filteredMedio && (
<MedioInactivoBanner medioNombre={filteredMedio.nombre} />
)}
<SeccionesFilters
onMedioIdChange={handleMedioIdChange}
onTipoChange={handleTipoChange}
@@ -77,7 +91,7 @@ export function SeccionesListPage() {
))}
</div>
) : (
<SeccionesTable rows={data?.items ?? []} />
<SeccionesTable rows={data?.items ?? []} medioInactivo={medioInactivo} />
)}
{/* Pagination */}