Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions backend/tests/frontend/settings-tab.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// tests/frontend/settings-tab.test.ts
// resolveSettingsTab returns the requested tab if available, otherwise the
// first available tab. Runs the real helper (the frontend has no test runner).

import { describe, it, expect } from 'vitest'
import { resolveSettingsTab } from '../../../frontend/app/lib/helpers/settingsTab.js'

describe('resolveSettingsTab', () => {
const available = ['users', 'notifications', 'security'] as const

it('returns the requested tab when it is available', () => {
expect(resolveSettingsTab('notifications', available)).toBe('notifications')
expect(resolveSettingsTab('security', available)).toBe('security')
expect(resolveSettingsTab('users', available)).toBe('users')
})

it('returns available[0] when requested tab is not in available (no permission)', () => {
expect(resolveSettingsTab('users', ['notifications', 'security'])).toBe('notifications')
})

it('returns available[0] when requested tab does not exist', () => {
expect(resolveSettingsTab('nonexistent', available)).toBe('users')
})

it('returns available[0] when requested is null', () => {
expect(resolveSettingsTab(null, available)).toBe('users')
})

it('returns available[0] when requested is empty string', () => {
expect(resolveSettingsTab('', available)).toBe('users')
})
})
3 changes: 0 additions & 3 deletions docs/TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -297,9 +297,6 @@ preview.
volviera a ejecutar (`UPDATE users SET is_active = 0 WHERE username =
'demo'`). `setup:local` no la ejecuta (es anterior al baseline); no
aplicarla a mano. _Comprobado el 2026-10-05._
- [ ] **Configuración: `?tab=` de una pestaña sin permiso deja el contenido en
blanco** — p. ej. recepción con `?tab=users`; debería caer en la primera
pestaña disponible. _Visto el 2026-10-05._
- [ ] **Errores del backend sin traducir** — `messages/{es,en}/errors.json`
tiene ~200 textos en `codes` que nadie usa: la UI muestra el `error` del
backend, en español. `app/lib/helpers/errorCodeMessage.ts` ya los traduce
Expand Down
10 changes: 7 additions & 3 deletions frontend/app/components/profile/SettingsPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { departmentsApi } from '@/app/lib/departments'
import { formatDepartmentName } from '@/app/lib/logbooks/hooks/useDepartments'
import { cn } from '@/app/lib/helpers/utils'
import { canRunNotificationCheckRole, isAdminRole } from '@/app/lib/helpers/utils'
import { resolveSettingsTab } from '@/app/lib/helpers/settingsTab'
import { toast } from 'react-hot-toast'
import {
FiUsers,
Expand Down Expand Up @@ -138,8 +139,12 @@ export function SettingsPanel() {
[t, showDemoTab]
)

const activeTab =
(searchParams.get('tab') as SettingsTab) || (isUserAdmin ? 'users' : 'notifications')
const availableTabs = tabs.filter((tab) => !tab.adminOnly || isUserAdmin)

const activeTab = resolveSettingsTab(
searchParams.get('tab'),
availableTabs.map((t) => t.id)
) as SettingsTab

const [users, setUsers] = useState<User[]>([])
const [loading, setLoading] = useState(false)
Expand All @@ -150,7 +155,6 @@ export function SettingsPanel() {
const [isDropdownOpen, setIsDropdownOpen] = useState(false)
const dropdownRef = useRef<HTMLDivElement>(null)

const availableTabs = tabs.filter((tab) => !tab.adminOnly || isUserAdmin)
const activeTabConfig = availableTabs.find((t) => t.id === activeTab) || availableTabs[0]

useEffect(() => {
Expand Down
12 changes: 12 additions & 0 deletions frontend/app/lib/helpers/settingsTab.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// frontend/app/lib/helpers/settingsTab.ts

/**
* Resolves the active settings tab from URL parameter.
* Returns the requested tab if it's available, otherwise the first available tab.
*/
export function resolveSettingsTab(requested: string | null, available: readonly string[]): string {
if (requested && available.includes(requested)) {
return requested
}
return available[0]
}
Loading