ADM-001: Medios y Secciones (fundacional) #15
@@ -15,6 +15,13 @@ import {
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from '@/components/ui/form'
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@/components/ui/select'
|
||||
import { TIPO_MEDIO_OPTIONS } from '../tipoMedio'
|
||||
import type { MedioDetail } from '../types'
|
||||
|
||||
@@ -136,22 +143,24 @@ export function MedioForm({ initialData, isPending, error, onSubmit }: MedioForm
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Tipo</FormLabel>
|
||||
<FormControl>
|
||||
<select
|
||||
{...field}
|
||||
value={field.value ?? ''}
|
||||
<Select
|
||||
value={field.value ? String(field.value) : ''}
|
||||
onValueChange={(v) => field.onChange(v === '' ? '' : Number(v))}
|
||||
disabled={isPending}
|
||||
aria-label="Tipo"
|
||||
className="flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50"
|
||||
>
|
||||
<option value="">Seleccioná un tipo</option>
|
||||
{TIPO_MEDIO_OPTIONS.map((o) => (
|
||||
<option key={o.value} value={o.value}>
|
||||
{o.label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<FormControl>
|
||||
<SelectTrigger className="w-full" aria-label="Tipo">
|
||||
<SelectValue placeholder="Seleccioná un tipo" />
|
||||
</SelectTrigger>
|
||||
</FormControl>
|
||||
<SelectContent>
|
||||
{TIPO_MEDIO_OPTIONS.map((o) => (
|
||||
<SelectItem key={o.value} value={String(o.value)}>
|
||||
{o.label}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
|
||||
@@ -5,6 +5,13 @@ import { Input } from '@/components/ui/input'
|
||||
import { Skeleton } from '@/components/ui/skeleton'
|
||||
import { CanPerform } from '@/components/auth/CanPerform'
|
||||
import { useDebouncedValue } from '@/hooks/useDebouncedValue'
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@/components/ui/select'
|
||||
import { MediosTable } from '../components/MediosTable'
|
||||
import { useMediosList } from '../hooks/useMediosList'
|
||||
import { TIPO_MEDIO_OPTIONS } from '../tipoMedio'
|
||||
@@ -70,28 +77,36 @@ export function MediosListPage() {
|
||||
aria-label="Buscar medios"
|
||||
/>
|
||||
|
||||
<select
|
||||
aria-label="Tipo"
|
||||
onChange={(e) => handleTipoChange(e.target.value)}
|
||||
className="flex h-9 rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
|
||||
<Select
|
||||
value={tipo !== undefined ? String(tipo) : '__all__'}
|
||||
onValueChange={(v) => handleTipoChange(v === '__all__' ? '' : v)}
|
||||
>
|
||||
<option value="">Todos los tipos</option>
|
||||
<SelectTrigger className="h-9 w-40" aria-label="Tipo">
|
||||
<SelectValue placeholder="Todos los tipos" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="__all__">Todos los tipos</SelectItem>
|
||||
{TIPO_MEDIO_OPTIONS.map((o) => (
|
||||
<option key={o.value} value={o.value}>
|
||||
<SelectItem key={o.value} value={String(o.value)}>
|
||||
{o.label}
|
||||
</option>
|
||||
</SelectItem>
|
||||
))}
|
||||
</select>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
|
||||
<select
|
||||
aria-label="Estado"
|
||||
onChange={(e) => handleActivoChange(e.target.value)}
|
||||
className="flex h-9 rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
|
||||
<Select
|
||||
value={activo !== undefined ? String(activo) : '__all__'}
|
||||
onValueChange={(v) => handleActivoChange(v === '__all__' ? '' : v)}
|
||||
>
|
||||
<option value="">Todos</option>
|
||||
<option value="true">Activos</option>
|
||||
<option value="false">Inactivos</option>
|
||||
</select>
|
||||
<SelectTrigger className="h-9 w-32" aria-label="Estado">
|
||||
<SelectValue placeholder="Todos" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="__all__">Todos</SelectItem>
|
||||
<SelectItem value="true">Activos</SelectItem>
|
||||
<SelectItem value="false">Inactivos</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
{isLoading ? (
|
||||
|
||||
@@ -6,6 +6,13 @@ import {
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from '@/components/ui/card'
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@/components/ui/select'
|
||||
import { useRoles } from '../../roles/hooks/useRoles'
|
||||
import { RolPermisosEditor } from '../components/RolPermisosEditor'
|
||||
|
||||
@@ -28,28 +35,28 @@ export function RolPermisosPage() {
|
||||
<CardContent className="space-y-6">
|
||||
{/* Selector de rol */}
|
||||
<div className="flex flex-col gap-1 max-w-xs">
|
||||
<label
|
||||
htmlFor="rol-selector"
|
||||
className="text-sm font-medium text-foreground"
|
||||
>
|
||||
<label className="text-sm font-medium text-foreground">
|
||||
Rol
|
||||
</label>
|
||||
{loadingRoles ? (
|
||||
<p className="text-sm text-muted-foreground">Cargando roles...</p>
|
||||
) : (
|
||||
<select
|
||||
id="rol-selector"
|
||||
value={selectedRol ?? ''}
|
||||
onChange={(e) => setSelectedRol(e.target.value || null)}
|
||||
className="flex h-9 w-full rounded-md border border-input bg-background px-3 py-1 text-sm shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50"
|
||||
<Select
|
||||
value={selectedRol ?? '__none__'}
|
||||
onValueChange={(v) => setSelectedRol(v === '__none__' ? null : v)}
|
||||
>
|
||||
<option value="">— Seleccioná un rol —</option>
|
||||
<SelectTrigger className="w-full" aria-label="Rol">
|
||||
<SelectValue placeholder="— Seleccioná un rol —" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="__none__">— Seleccioná un rol —</SelectItem>
|
||||
{rolesActivos.map((r) => (
|
||||
<option key={r.codigo} value={r.codigo}>
|
||||
<SelectItem key={r.codigo} value={r.codigo}>
|
||||
{r.nombre} ({r.codigo})
|
||||
</option>
|
||||
</SelectItem>
|
||||
))}
|
||||
</select>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
)}
|
||||
</div>
|
||||
|
||||
|
||||
@@ -15,6 +15,13 @@ import {
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from '@/components/ui/form'
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@/components/ui/select'
|
||||
import { useMediosList } from '@/features/medios/hooks/useMediosList'
|
||||
import { TIPO_SECCION_OPTIONS } from '../tipoSeccion'
|
||||
import type { SeccionDetail, TipoSeccion } from '../types'
|
||||
@@ -100,22 +107,24 @@ export function SeccionForm({ initialData, isPending, error, onSubmit }: Seccion
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Medio</FormLabel>
|
||||
<FormControl>
|
||||
<select
|
||||
{...field}
|
||||
value={field.value ?? ''}
|
||||
<Select
|
||||
value={field.value ? String(field.value) : ''}
|
||||
onValueChange={(v) => field.onChange(Number(v))}
|
||||
disabled={isPending || isEdit}
|
||||
aria-label="Medio"
|
||||
className="flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50"
|
||||
>
|
||||
<option value="">Seleccioná un medio</option>
|
||||
{medios.map((m) => (
|
||||
<option key={m.id} value={m.id}>
|
||||
{m.nombre}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<FormControl>
|
||||
<SelectTrigger className="w-full" aria-label="Medio">
|
||||
<SelectValue placeholder="Seleccioná un medio" />
|
||||
</SelectTrigger>
|
||||
</FormControl>
|
||||
<SelectContent>
|
||||
{medios.map((m) => (
|
||||
<SelectItem key={m.id} value={String(m.id)}>
|
||||
{m.nombre}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
@@ -165,22 +174,24 @@ export function SeccionForm({ initialData, isPending, error, onSubmit }: Seccion
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Tipo</FormLabel>
|
||||
<FormControl>
|
||||
<select
|
||||
{...field}
|
||||
<Select
|
||||
value={field.value ?? ''}
|
||||
onValueChange={(v) => field.onChange(v as TipoSeccion)}
|
||||
disabled={isPending}
|
||||
aria-label="Tipo de sección"
|
||||
className="flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50"
|
||||
>
|
||||
<option value="">Seleccioná un tipo</option>
|
||||
{TIPO_SECCION_OPTIONS.map((o) => (
|
||||
<option key={o.value} value={o.value}>
|
||||
{o.label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<FormControl>
|
||||
<SelectTrigger className="w-full" aria-label="Tipo de sección">
|
||||
<SelectValue placeholder="Seleccioná un tipo" />
|
||||
</SelectTrigger>
|
||||
</FormControl>
|
||||
<SelectContent>
|
||||
{TIPO_SECCION_OPTIONS.map((o) => (
|
||||
<SelectItem key={o.value} value={o.value}>
|
||||
{o.label}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { useDebouncedValue } from '@/hooks/useDebouncedValue'
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@/components/ui/select'
|
||||
import { useMediosList } from '@/features/medios/hooks/useMediosList'
|
||||
import { TIPO_SECCION_OPTIONS } from '../tipoSeccion'
|
||||
import type { TipoSeccion } from '../types'
|
||||
@@ -40,51 +47,56 @@ export function SeccionesFilters({
|
||||
aria-label="Buscar secciones"
|
||||
/>
|
||||
|
||||
<select
|
||||
aria-label="Medio"
|
||||
onChange={(e) => {
|
||||
const v = e.target.value
|
||||
onMedioIdChange(v === '' ? undefined : Number(v))
|
||||
}}
|
||||
className="flex h-9 rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
|
||||
<Select
|
||||
defaultValue="__all__"
|
||||
onValueChange={(v) => onMedioIdChange(v === '__all__' ? undefined : Number(v))}
|
||||
>
|
||||
<option value="">Todos los medios</option>
|
||||
<SelectTrigger className="h-9 w-44" aria-label="Medio">
|
||||
<SelectValue placeholder="Todos los medios" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="__all__">Todos los medios</SelectItem>
|
||||
{medios.map((m) => (
|
||||
<option key={m.id} value={m.id}>
|
||||
<SelectItem key={m.id} value={String(m.id)}>
|
||||
{m.nombre}
|
||||
</option>
|
||||
</SelectItem>
|
||||
))}
|
||||
</select>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
|
||||
<select
|
||||
aria-label="Tipo de sección"
|
||||
onChange={(e) => {
|
||||
const v = e.target.value
|
||||
onTipoChange(v === '' ? undefined : (v as TipoSeccion))
|
||||
}}
|
||||
className="flex h-9 rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
|
||||
<Select
|
||||
defaultValue="__all__"
|
||||
onValueChange={(v) => onTipoChange(v === '__all__' ? undefined : (v as TipoSeccion))}
|
||||
>
|
||||
<option value="">Todos los tipos</option>
|
||||
<SelectTrigger className="h-9 w-44" aria-label="Tipo de sección">
|
||||
<SelectValue placeholder="Todos los tipos" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="__all__">Todos los tipos</SelectItem>
|
||||
{TIPO_SECCION_OPTIONS.map((o) => (
|
||||
<option key={o.value} value={o.value}>
|
||||
<SelectItem key={o.value} value={o.value}>
|
||||
{o.label}
|
||||
</option>
|
||||
</SelectItem>
|
||||
))}
|
||||
</select>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
|
||||
<select
|
||||
aria-label="Estado"
|
||||
onChange={(e) => {
|
||||
const v = e.target.value
|
||||
if (v === '') onActivoChange(undefined)
|
||||
<Select
|
||||
defaultValue="__all__"
|
||||
onValueChange={(v) => {
|
||||
if (v === '__all__') onActivoChange(undefined)
|
||||
else onActivoChange(v === 'true')
|
||||
}}
|
||||
className="flex h-9 rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
|
||||
>
|
||||
<option value="">Todos</option>
|
||||
<option value="true">Activos</option>
|
||||
<option value="false">Inactivos</option>
|
||||
</select>
|
||||
<SelectTrigger className="h-9 w-32" aria-label="Estado">
|
||||
<SelectValue placeholder="Todos" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="__all__">Todos</SelectItem>
|
||||
<SelectItem value="true">Activos</SelectItem>
|
||||
<SelectItem value="false">Inactivos</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -14,6 +14,13 @@ import {
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from '@/components/ui/form'
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@/components/ui/select'
|
||||
import { useCreateUser } from '../hooks/useCreateUser'
|
||||
import { useRolesForSelect } from '../hooks/useRolesForSelect'
|
||||
import type { CreatedUserDto } from '../api/createUser'
|
||||
@@ -202,23 +209,26 @@ export function UserForm({ onSuccess }: UserFormProps) {
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Rol</FormLabel>
|
||||
<FormControl>
|
||||
<select
|
||||
{...field}
|
||||
disabled={disabled || rolesError}
|
||||
aria-label="Rol"
|
||||
className="flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50"
|
||||
<Select
|
||||
value={field.value}
|
||||
onValueChange={field.onChange}
|
||||
disabled={disabled || !!rolesError}
|
||||
>
|
||||
<option value="">
|
||||
{rolesLoading ? 'Cargando roles...' : 'Seleccioná un rol'}
|
||||
</option>
|
||||
{rolOptions.map((r) => (
|
||||
<option key={r.codigo} value={r.codigo}>
|
||||
{r.nombre}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<FormControl>
|
||||
<SelectTrigger className="w-full" aria-label="Rol">
|
||||
<SelectValue
|
||||
placeholder={rolesLoading ? 'Cargando roles...' : 'Seleccioná un rol'}
|
||||
/>
|
||||
</SelectTrigger>
|
||||
</FormControl>
|
||||
<SelectContent>
|
||||
{rolOptions.map((r) => (
|
||||
<SelectItem key={r.codigo} value={r.codigo}>
|
||||
{r.nombre}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { useDebouncedValue } from '@/hooks/useDebouncedValue'
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@/components/ui/select'
|
||||
|
||||
interface UsersFiltersProps {
|
||||
onRolChange: (rol: string) => void
|
||||
@@ -38,32 +45,39 @@ export function UsersFilters({ onRolChange, onActivoChange, onSearchChange }: Us
|
||||
/>
|
||||
|
||||
{/* Rol select */}
|
||||
<select
|
||||
aria-label="Rol"
|
||||
onChange={(e) => onRolChange(e.target.value)}
|
||||
className="flex h-9 rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
|
||||
<Select
|
||||
defaultValue="__all__"
|
||||
onValueChange={(v) => onRolChange(v === '__all__' ? '' : v)}
|
||||
>
|
||||
<SelectTrigger className="h-9 w-40" aria-label="Rol">
|
||||
<SelectValue placeholder="Todos los roles" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{ROL_OPTIONS.map((r) => (
|
||||
<option key={r.value} value={r.value}>
|
||||
<SelectItem key={r.value || '__all__'} value={r.value || '__all__'}>
|
||||
{r.label}
|
||||
</option>
|
||||
</SelectItem>
|
||||
))}
|
||||
</select>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
|
||||
{/* Activo filter */}
|
||||
<select
|
||||
aria-label="Estado"
|
||||
onChange={(e) => {
|
||||
const v = e.target.value
|
||||
if (v === '') onActivoChange(undefined)
|
||||
<Select
|
||||
defaultValue="__all__"
|
||||
onValueChange={(v) => {
|
||||
if (v === '__all__') onActivoChange(undefined)
|
||||
else onActivoChange(v === 'true')
|
||||
}}
|
||||
className="flex h-9 rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
|
||||
>
|
||||
<option value="">Todos</option>
|
||||
<option value="true">Activos</option>
|
||||
<option value="false">Inactivos</option>
|
||||
</select>
|
||||
<SelectTrigger className="h-9 w-32" aria-label="Estado">
|
||||
<SelectValue placeholder="Todos" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="__all__">Todos</SelectItem>
|
||||
<SelectItem value="true">Activos</SelectItem>
|
||||
<SelectItem value="false">Inactivos</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -17,6 +17,13 @@ import {
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from '@/components/ui/form'
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@/components/ui/select'
|
||||
import { useUser } from '../hooks/useUser'
|
||||
import { useUpdateUser } from '../hooks/useUpdateUser'
|
||||
import { ResetPasswordModal } from '../components/ResetPasswordModal'
|
||||
@@ -199,18 +206,22 @@ export function UserEditPage() {
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Rol</FormLabel>
|
||||
<FormControl>
|
||||
<select
|
||||
{...field}
|
||||
<Select
|
||||
value={field.value}
|
||||
onValueChange={field.onChange}
|
||||
disabled={isPending}
|
||||
aria-label="Rol"
|
||||
className="flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50"
|
||||
>
|
||||
<option value="admin">Admin</option>
|
||||
<option value="cajero">Cajero</option>
|
||||
<option value="reportes">Reportes</option>
|
||||
</select>
|
||||
<FormControl>
|
||||
<SelectTrigger className="w-full" aria-label="Rol">
|
||||
<SelectValue placeholder="Seleccioná un rol" />
|
||||
</SelectTrigger>
|
||||
</FormControl>
|
||||
<SelectContent>
|
||||
<SelectItem value="admin">Admin</SelectItem>
|
||||
<SelectItem value="cajero">Cajero</SelectItem>
|
||||
<SelectItem value="reportes">Reportes</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
|
||||
@@ -61,7 +61,11 @@ describe('CreateMedioPage', () => {
|
||||
|
||||
await userEvent.type(screen.getByLabelText(/código/i), 'RAD01')
|
||||
await userEvent.type(screen.getByLabelText(/nombre/i), 'Radio AM')
|
||||
await userEvent.selectOptions(screen.getByLabelText(/tipo/i), '2')
|
||||
|
||||
// Open Radix Select trigger and pick "Radio" (value=2)
|
||||
await userEvent.click(screen.getByRole('combobox', { name: /tipo/i }))
|
||||
await userEvent.click(screen.getByRole('option', { name: /^radio$/i }))
|
||||
|
||||
await userEvent.click(screen.getByRole('button', { name: /crear medio/i }))
|
||||
|
||||
await waitFor(() => expect(mockNavigate).toHaveBeenCalledWith('/admin/medios'))
|
||||
@@ -81,7 +85,11 @@ describe('CreateMedioPage', () => {
|
||||
|
||||
await userEvent.type(screen.getByLabelText(/código/i), 'DUP01')
|
||||
await userEvent.type(screen.getByLabelText(/nombre/i), 'Duplicado')
|
||||
await userEvent.selectOptions(screen.getByLabelText(/tipo/i), '1')
|
||||
|
||||
// Open Radix Select trigger and pick "Diario" (value=1)
|
||||
await userEvent.click(screen.getByRole('combobox', { name: /tipo/i }))
|
||||
await userEvent.click(screen.getByRole('option', { name: /^diario$/i }))
|
||||
|
||||
await userEvent.click(screen.getByRole('button', { name: /crear medio/i }))
|
||||
|
||||
await waitFor(() =>
|
||||
|
||||
@@ -79,7 +79,11 @@ describe('MedioForm — create mode', () => {
|
||||
|
||||
await userEvent.type(screen.getByLabelText(/código/i), 'DIA99')
|
||||
await userEvent.type(screen.getByLabelText(/nombre/i), 'Mi Diario')
|
||||
await userEvent.selectOptions(screen.getByLabelText(/tipo/i), '1')
|
||||
|
||||
// Open the Radix Select trigger and pick option
|
||||
await userEvent.click(screen.getByRole('combobox', { name: /tipo/i }))
|
||||
await userEvent.click(screen.getByRole('option', { name: /diario/i }))
|
||||
|
||||
await userEvent.click(screen.getByRole('button', { name: /crear medio/i }))
|
||||
|
||||
await waitFor(() => {
|
||||
|
||||
@@ -144,8 +144,9 @@ describe('MediosListPage', () => {
|
||||
|
||||
await waitFor(() => expect(requests.length).toBeGreaterThan(0))
|
||||
|
||||
const tipoSelect = screen.getByRole('combobox', { name: /tipo/i })
|
||||
await userEvent.selectOptions(tipoSelect, '1')
|
||||
// Open the Radix Select trigger and pick "Diario" (value=1)
|
||||
await userEvent.click(screen.getByRole('combobox', { name: /tipo/i }))
|
||||
await userEvent.click(screen.getByRole('option', { name: /^diario$/i }))
|
||||
|
||||
await waitFor(() => {
|
||||
const filtered = requests.find((u) => u.includes('tipo=1'))
|
||||
|
||||
@@ -88,15 +88,25 @@ describe('SeccionForm — create mode', () => {
|
||||
const onSubmit = vi.fn()
|
||||
renderForm({ onSubmit })
|
||||
|
||||
// Wait for medios to load
|
||||
// Open Medio trigger, wait for medios to load, then pick one
|
||||
const medioTrigger = screen.getByRole('combobox', { name: /medio/i })
|
||||
await userEvent.click(medioTrigger)
|
||||
await waitFor(() =>
|
||||
expect(screen.getByRole('option', { name: 'Diario El Día' })).toBeInTheDocument(),
|
||||
)
|
||||
await userEvent.click(screen.getByRole('option', { name: 'Diario El Día' }))
|
||||
|
||||
await userEvent.selectOptions(screen.getByLabelText(/medio/i), '1')
|
||||
await userEvent.type(screen.getByLabelText(/código/i), 'CLAS99')
|
||||
await userEvent.type(screen.getByLabelText(/nombre/i), 'Mi Sección')
|
||||
await userEvent.selectOptions(screen.getByLabelText(/tipo de sección/i), 'clasificados')
|
||||
|
||||
// Open Tipo trigger and pick Clasificados
|
||||
const tipoTrigger = screen.getByRole('combobox', { name: /tipo de sección/i })
|
||||
await userEvent.click(tipoTrigger)
|
||||
await waitFor(() =>
|
||||
expect(screen.getByRole('option', { name: 'Clasificados' })).toBeInTheDocument(),
|
||||
)
|
||||
await userEvent.click(screen.getByRole('option', { name: 'Clasificados' }))
|
||||
|
||||
await userEvent.click(screen.getByRole('button', { name: /crear sección/i }))
|
||||
|
||||
await waitFor(() => {
|
||||
@@ -117,8 +127,9 @@ describe('SeccionForm — edit mode', () => {
|
||||
renderForm({ initialData: sampleSeccion })
|
||||
const codigoInput = screen.getByLabelText(/código/i) as HTMLInputElement
|
||||
expect(codigoInput.disabled).toBe(true)
|
||||
const medioSelect = screen.getByLabelText(/medio/i) as HTMLSelectElement
|
||||
expect(medioSelect.disabled).toBe(true)
|
||||
// Radix Select trigger is a <button> — check it's disabled
|
||||
const medioTrigger = screen.getByRole('combobox', { name: /medio/i })
|
||||
expect(medioTrigger).toBeDisabled()
|
||||
})
|
||||
|
||||
it('pre-fills form with initialData values', async () => {
|
||||
|
||||
@@ -56,6 +56,10 @@ describe('SeccionesFilters', () => {
|
||||
it('loads medios options from API', async () => {
|
||||
renderFilters()
|
||||
|
||||
// Open the Medio Radix Select to see options in the portal
|
||||
const medioTrigger = await screen.findByRole('combobox', { name: /medio/i })
|
||||
await userEvent.click(medioTrigger)
|
||||
|
||||
await waitFor(() =>
|
||||
expect(screen.getByRole('option', { name: 'Diario El Día' })).toBeInTheDocument(),
|
||||
)
|
||||
@@ -66,34 +70,55 @@ describe('SeccionesFilters', () => {
|
||||
it('calls onMedioIdChange when medio is selected', async () => {
|
||||
const handlers = renderFilters()
|
||||
|
||||
// Open Medio trigger, wait for options to load, then pick one
|
||||
const medioTrigger = await screen.findByRole('combobox', { name: /medio/i })
|
||||
await userEvent.click(medioTrigger)
|
||||
|
||||
await waitFor(() =>
|
||||
expect(screen.getByRole('option', { name: 'Diario El Día' })).toBeInTheDocument(),
|
||||
)
|
||||
|
||||
await userEvent.selectOptions(screen.getByLabelText(/medio/i), '1')
|
||||
await userEvent.click(screen.getByRole('option', { name: 'Diario El Día' }))
|
||||
expect(handlers.onMedioIdChange).toHaveBeenCalledWith(1)
|
||||
})
|
||||
|
||||
it('calls onTipoChange when tipo is selected', async () => {
|
||||
const handlers = renderFilters()
|
||||
|
||||
// Open Tipo de sección trigger and pick an option
|
||||
const tipoTrigger = screen.getByRole('combobox', { name: /tipo de sección/i })
|
||||
await userEvent.click(tipoTrigger)
|
||||
|
||||
await waitFor(() =>
|
||||
expect(screen.getByRole('option', { name: 'Clasificados' })).toBeInTheDocument(),
|
||||
)
|
||||
|
||||
await userEvent.selectOptions(screen.getByLabelText(/tipo de sección/i), 'clasificados')
|
||||
await userEvent.click(screen.getByRole('option', { name: 'Clasificados' }))
|
||||
expect(handlers.onTipoChange).toHaveBeenCalledWith('clasificados')
|
||||
})
|
||||
|
||||
it('calls onActivoChange when estado is selected', async () => {
|
||||
const handlers = renderFilters()
|
||||
await userEvent.selectOptions(screen.getByLabelText(/estado/i), 'true')
|
||||
|
||||
// Open Estado trigger and pick Activos
|
||||
const estadoTrigger = screen.getByRole('combobox', { name: /estado/i })
|
||||
await userEvent.click(estadoTrigger)
|
||||
|
||||
await waitFor(() =>
|
||||
expect(screen.getByRole('option', { name: 'Activos' })).toBeInTheDocument(),
|
||||
)
|
||||
|
||||
await userEvent.click(screen.getByRole('option', { name: 'Activos' }))
|
||||
expect(handlers.onActivoChange).toHaveBeenCalledWith(true)
|
||||
})
|
||||
|
||||
it('renders all tipo options', async () => {
|
||||
renderFilters()
|
||||
|
||||
// Open Tipo de sección trigger to see options in portal
|
||||
const tipoTrigger = screen.getByRole('combobox', { name: /tipo de sección/i })
|
||||
await userEvent.click(tipoTrigger)
|
||||
|
||||
await waitFor(() =>
|
||||
expect(screen.getByRole('option', { name: 'Clasificados' })).toBeInTheDocument(),
|
||||
)
|
||||
|
||||
@@ -107,6 +107,13 @@ describe('UserEditPage', () => {
|
||||
await userEvent.clear(nombreInput)
|
||||
await userEvent.type(nombreInput, 'Pedro')
|
||||
|
||||
// Confirm the rol Select has a value by opening and re-selecting the prefilled value
|
||||
// (Radix Select in jsdom needs interaction to register the item text in context)
|
||||
const rolTrigger = screen.getByRole('combobox', { name: /rol/i })
|
||||
await userEvent.click(rolTrigger)
|
||||
await waitFor(() => expect(screen.getByRole('option', { name: /cajero/i })).toBeInTheDocument())
|
||||
await userEvent.click(screen.getByRole('option', { name: /cajero/i }))
|
||||
|
||||
// Submit
|
||||
await userEvent.click(screen.getByRole('button', { name: /guardar|actualizar|save/i }))
|
||||
|
||||
@@ -129,6 +136,12 @@ describe('UserEditPage', () => {
|
||||
|
||||
await waitFor(() => expect(screen.getByDisplayValue('Juan')).toBeInTheDocument())
|
||||
|
||||
// Confirm rol by opening and re-selecting the prefilled value
|
||||
const rolTrigger = screen.getByRole('combobox', { name: /rol/i })
|
||||
await userEvent.click(rolTrigger)
|
||||
await waitFor(() => expect(screen.getByRole('option', { name: /cajero/i })).toBeInTheDocument())
|
||||
await userEvent.click(screen.getByRole('option', { name: /cajero/i }))
|
||||
|
||||
await userEvent.click(screen.getByRole('button', { name: /guardar|actualizar|save/i }))
|
||||
|
||||
await waitFor(() =>
|
||||
|
||||
@@ -109,6 +109,10 @@ describe('UserForm — roles dropdown integration', () => {
|
||||
)
|
||||
renderForm()
|
||||
|
||||
// Open the Radix Select trigger to see options in the portal
|
||||
const rolTrigger = screen.getByRole('combobox', { name: /rol/i })
|
||||
await userEvent.click(rolTrigger)
|
||||
|
||||
// Wait for roles to load — active options should appear.
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
@@ -132,15 +136,21 @@ describe('UserForm — roles dropdown integration', () => {
|
||||
const user = userEvent.setup()
|
||||
renderForm(onSuccess)
|
||||
|
||||
// Open trigger and wait for Cajero option to load
|
||||
const rolTrigger = screen.getByRole('combobox', { name: /rol/i })
|
||||
await user.click(rolTrigger)
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByRole('option', { name: 'Cajero' })).toBeInTheDocument()
|
||||
})
|
||||
|
||||
// Pick Cajero option
|
||||
await user.click(screen.getByRole('option', { name: 'Cajero' }))
|
||||
|
||||
await user.type(screen.getByLabelText(/usuario/i), 'jdoe123')
|
||||
await user.type(screen.getByLabelText(/^contraseña$/i), 'Secret12')
|
||||
await user.type(screen.getByLabelText(/nombre/i), 'Juan')
|
||||
await user.type(screen.getByLabelText(/apellido/i), 'Doe')
|
||||
await user.selectOptions(screen.getByLabelText(/rol/i), 'cajero')
|
||||
|
||||
await user.click(screen.getByRole('button', { name: /crear usuario/i }))
|
||||
|
||||
@@ -176,15 +186,20 @@ describe('UserForm — backend error display', () => {
|
||||
const user = userEvent.setup()
|
||||
renderForm()
|
||||
|
||||
// Open trigger, wait for options, pick Cajero
|
||||
const rolTrigger = screen.getByRole('combobox', { name: /rol/i })
|
||||
await user.click(rolTrigger)
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByRole('option', { name: 'Cajero' })).toBeInTheDocument()
|
||||
})
|
||||
|
||||
await user.click(screen.getByRole('option', { name: 'Cajero' }))
|
||||
|
||||
await user.type(screen.getByLabelText(/usuario/i), 'existing')
|
||||
await user.type(screen.getByLabelText(/^contraseña$/i), 'Secret12')
|
||||
await user.type(screen.getByLabelText(/nombre/i), 'Juan')
|
||||
await user.type(screen.getByLabelText(/apellido/i), 'Doe')
|
||||
await user.selectOptions(screen.getByLabelText(/rol/i), 'cajero')
|
||||
|
||||
await user.click(screen.getByRole('button', { name: /crear usuario/i }))
|
||||
|
||||
|
||||
@@ -171,8 +171,10 @@ describe('UsersListPage', () => {
|
||||
|
||||
await waitFor(() => expect(requests.length).toBeGreaterThan(0))
|
||||
|
||||
const rolSelect = screen.getByRole('combobox', { name: /rol/i })
|
||||
await userEvent.selectOptions(rolSelect, 'admin')
|
||||
// Open the Radix Select trigger and pick "Admin"
|
||||
const rolTrigger = screen.getByRole('combobox', { name: /rol/i })
|
||||
await userEvent.click(rolTrigger)
|
||||
await userEvent.click(screen.getByRole('option', { name: /^admin$/i }))
|
||||
|
||||
await waitFor(() => {
|
||||
const filtered = requests.find((u) => u.includes('rol=admin'))
|
||||
|
||||
@@ -1 +1,10 @@
|
||||
import '@testing-library/jest-dom'
|
||||
|
||||
// Radix UI primitives use pointer capture APIs not available in jsdom.
|
||||
// Provide no-op stubs so Radix Select/etc. work in unit tests.
|
||||
if (typeof window !== 'undefined') {
|
||||
window.HTMLElement.prototype.hasPointerCapture = () => false
|
||||
window.HTMLElement.prototype.setPointerCapture = () => {}
|
||||
window.HTMLElement.prototype.releasePointerCapture = () => {}
|
||||
window.HTMLElement.prototype.scrollIntoView = () => {}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user