diff --git a/components/webfield/AreaChairConsole.js b/components/webfield/AreaChairConsole.js
index 341adc160..1d0a691cc 100644
--- a/components/webfield/AreaChairConsole.js
+++ b/components/webfield/AreaChairConsole.js
@@ -4,7 +4,6 @@ import { useContext, useEffect, useState } from 'react'
import { useRouter } from 'next/router'
import WebFieldContext from '../WebFieldContext'
import BasicHeader from './BasicHeader'
-import { Tab, TabList, TabPanel, TabPanels, Tabs } from '../Tabs'
import Table from '../Table'
import ErrorDisplay from '../ErrorDisplay'
import NoteSummary from './NoteSummary'
@@ -31,6 +30,7 @@ import LoadingSpinner from '../LoadingSpinner'
import ConsoleTaskList from './ConsoleTaskList'
import { getProfileLink } from '../../lib/webfield-utils'
import { formatProfileContent } from '../../lib/edge-utils'
+import ConsoleTabs from './ConsoleTabs'
const SelectAllCheckBox = ({ selectedNoteIds, setSelectedNoteIds, allNoteIds }) => {
const allNotesSelected = selectedNoteIds.length === allNoteIds?.length
@@ -181,9 +181,7 @@ const AreaChairConsole = ({ appContext }) => {
const { setBannerContent } = appContext
const [acConsoleData, setAcConsoleData] = useState({})
const [selectedNoteIds, setSelectedNoteIds] = useState([])
- const [activeTabId, setActiveTabId] = useState(
- decodeURIComponent(window.location.hash) || `#assigned-${pluralizeString(submissionName)}`
- )
+ const [activeTabId, setActiveTabId] = useState(null)
const [sacLinkText, setSacLinkText] = useState('')
const edgeBrowserUrl = proposedAssignmentTitle
@@ -789,19 +787,6 @@ const AreaChairConsole = ({ appContext }) => {
getSACLinkText()
}, [acConsoleData.sacProfiles])
- useEffect(() => {
- const validTabIds = [
- `#assigned-${pluralizeString(submissionName ?? '').toLowerCase()}`,
- ...(secondaryAreaChairName ? [`#${secondaryAreaChairUrlFormat}-assignments`] : []),
- `#${areaChairUrlFormat}-tasks`,
- ]
- if (!validTabIds.includes(activeTabId)) {
- setActiveTabId(`#assigned-${pluralizeString(submissionName ?? '').toLowerCase()}`)
- return
- }
- router.replace(activeTabId)
- }, [activeTabId])
-
const missingConfig = Object.entries({
header,
group,
@@ -834,62 +819,30 @@ const AreaChairConsole = ({ appContext }) => {
title={header?.title}
instructions={`${headerInstructions}${sacLinkText}`}
/>
-
-
-
-
- setActiveTabId(`#assigned-${pluralizeString(submissionName).toLowerCase()}`)
- }
- >
- Assigned {pluralizeString(submissionName)}
-
- {secondaryAreaChairName && (
- setActiveTabId(`#${secondaryAreaChairUrlFormat}-assignments`)}
- >
- {getSingularRoleName(prettyField(secondaryAreaChairName))} Assignments
-
- )}
- setActiveTabId(`#${areaChairUrlFormat}-tasks`)}
- >
- {getSingularRoleName(prettyField(areaChairName))} Tasks
-
-
-
-
-
- {activeTabId === `#assigned-${pluralizeString(submissionName).toLowerCase()}` &&
- renderTable()}
-
- {secondaryAreaChairName && (
-
- {activeTabId === `#${secondaryAreaChairUrlFormat}-assignments` &&
- renderTripletACTable()}
-
- )}
-
- {activeTabId === `#${areaChairUrlFormat}-tasks` && (
-
- )}
-
-
-
+ ,
+ visible: true,
+ },
+ ]}
+ updateActiveTabId={setActiveTabId}
+ />
>
)
}
diff --git a/components/webfield/ConsoleTabs.js b/components/webfield/ConsoleTabs.js
new file mode 100644
index 000000000..95a104189
--- /dev/null
+++ b/components/webfield/ConsoleTabs.js
@@ -0,0 +1,61 @@
+import { useEffect, useState } from 'react'
+import { useRouter } from 'next/router'
+import { Tab, TabList, TabPanel, TabPanels, Tabs } from '../Tabs'
+
+const ConsoleTabs = ({ defaultActiveTabId, tabs = [], updateActiveTabId }) => {
+ const validTabIds = tabs.flatMap((tab) => (tab.visible ? tab.id : []))
+ const [activeTabId, setActiveTabId] = useState(
+ decodeURIComponent(window.location.hash.substring(1)) ||
+ defaultActiveTabId ||
+ validTabIds[0]
+ )
+ const router = useRouter()
+
+ useEffect(() => {
+ if (!validTabIds.includes(activeTabId)) {
+ setActiveTabId(defaultActiveTabId)
+ updateActiveTabId?.(`#${defaultActiveTabId}`)
+ return
+ }
+ updateActiveTabId?.(`#${activeTabId}`)
+ router.replace(`#${activeTabId}`).catch((e) => {
+ if (!e.cancelled) {
+ throw e
+ }
+ })
+ }, [activeTabId])
+
+ return (
+
+
+ {tabs.map((tab) => {
+ const { id, label, visible } = tab
+ if (!visible) return null
+ return (
+ setActiveTabId(id)}
+ >
+ {label}
+
+ )
+ })}
+
+
+ {tabs.map((tab) => {
+ const { id, content, visible } = tab
+ if (!visible || activeTabId !== `${id}`) return null
+ return (
+
+ {content}
+
+ )
+ })}
+
+
+ )
+}
+
+export default ConsoleTabs
diff --git a/components/webfield/EthicsChairConsole.js b/components/webfield/EthicsChairConsole.js
index f1199211a..1c333ea1b 100644
--- a/components/webfield/EthicsChairConsole.js
+++ b/components/webfield/EthicsChairConsole.js
@@ -1,9 +1,8 @@
/* globals promptError: false */
-import { useContext, useEffect, useState } from 'react'
+import { useContext, useEffect } from 'react'
import { useRouter } from 'next/router'
import useUser from '../../hooks/useUser'
import useQuery from '../../hooks/useQuery'
-import { Tab, TabList, TabPanel, TabPanels, Tabs } from '../Tabs'
import { referrerLink, venueHomepageLink } from '../../lib/banner-links'
import WebFieldContext from '../WebFieldContext'
import BasicHeader from './BasicHeader'
@@ -12,6 +11,7 @@ import EthicsChairOverview from './EthicsChairConsole/EthicsChairOverview'
import PaperStatus from './EthicsChairConsole/EthicsChairPaperStatus'
import EthicsChairTasks from './EthicsChairConsole/EthicsChairTasks'
import { getRoleHashFragment } from '../../lib/utils'
+import ConsoleTabs from './ConsoleTabs'
const EthicsChairConsole = ({ appContext }) => {
const {
@@ -31,17 +31,9 @@ const EthicsChairConsole = ({ appContext }) => {
const { setBannerContent } = appContext
const router = useRouter()
const query = useQuery()
- const [activeTabId, setActiveTabId] = useState(
- decodeURIComponent(window.location.hash) || '#overview'
- )
const { user, userLoading } = useUser()
const ethicsChairsUrlFormat = getRoleHashFragment(ethicsChairsName)
- const validTabIds = [
- '#overview',
- `#${submissionName.toLowerCase()}-status`,
- `#${ethicsChairsUrlFormat}-tasks`,
- ]
useEffect(() => {
if (!query) return
@@ -53,14 +45,6 @@ const EthicsChairConsole = ({ appContext }) => {
}
}, [query, venueId])
- useEffect(() => {
- if (!validTabIds.includes(activeTabId)) {
- setActiveTabId(validTabIds[0])
- return
- }
- router.replace(activeTabId)
- }, [activeTabId])
-
const missingConfig = Object.entries({
header,
entity: group,
@@ -85,45 +69,29 @@ const EthicsChairConsole = ({ appContext }) => {
return (
<>
-
-
- setActiveTabId('#overview')}
- >
- Overview
-
- setActiveTabId(`#${submissionName.toLowerCase()}-status`)}
- >
- {submissionName} Status
-
- setActiveTabId(`#${ethicsChairsUrlFormat}-tasks`)}
- >
- Ethics Chair Tasks
-
-
-
-
-
-
-
-
- {activeTabId === `#${submissionName.toLowerCase()}-status` && }
-
-
- {activeTabId === `#${ethicsChairsUrlFormat}-tasks` && }
-
-
-
+ ,
+ visible: true,
+ },
+ {
+ id: `${submissionName.toLowerCase()}-status`,
+ label: `${submissionName} Status`,
+ content: ,
+ visible: true,
+ },
+ {
+ id: `${ethicsChairsUrlFormat}-tasks`,
+ label: 'Ethics Chair Tasks',
+ content: ,
+ visible: true,
+ },
+ ]}
+ />
>
)
}
diff --git a/components/webfield/ProgramChairConsole.js b/components/webfield/ProgramChairConsole.js
index a760fd064..4c95e51e4 100644
--- a/components/webfield/ProgramChairConsole.js
+++ b/components/webfield/ProgramChairConsole.js
@@ -1,10 +1,8 @@
/* globals promptError: false */
import { useContext, useEffect, useState } from 'react'
-import { useRouter } from 'next/router'
import groupBy from 'lodash/groupBy'
import useUser from '../../hooks/useUser'
import useQuery from '../../hooks/useQuery'
-import { Tab, TabList, TabPanel, TabPanels, Tabs } from '../Tabs'
import { referrerLink, venueHomepageLink } from '../../lib/banner-links'
import api from '../../lib/api-client'
import WebFieldContext from '../WebFieldContext'
@@ -29,6 +27,7 @@ import ReviewerStatusTab from './ProgramChairConsole/ReviewerStatus'
import ErrorDisplay from '../ErrorDisplay'
import RejectedWithdrawnPapers from './ProgramChairConsole/RejectedWithdrawnPapers'
import { formatProfileContent } from '../../lib/edge-utils'
+import ConsoleTabs from './ConsoleTabs'
const ProgramChairConsole = ({ appContext, extraTabs = [] }) => {
const {
@@ -88,11 +87,7 @@ const ProgramChairConsole = ({ appContext, extraTabs = [] }) => {
} = useContext(WebFieldContext)
const { setBannerContent } = appContext
const { user, accessToken, userLoading } = useUser()
- const router = useRouter()
const query = useQuery()
- const [activeTabId, setActiveTabId] = useState(
- decodeURIComponent(window.location.hash) || '#venue-configuration'
- )
const [pcConsoleData, setPcConsoleData] = useState({})
const [isLoadingData, setIsLoadingData] = useState(false)
@@ -1034,31 +1029,6 @@ const ProgramChairConsole = ({ appContext, extraTabs = [] }) => {
loadData()
}, [user, userLoading, group])
- useEffect(() => {
- const validTabIds = [
- '#venue-configuration',
- `#${submissionName.toLowerCase()}-status`,
- `#${reviewerUrlFormat}-status`,
- `#${areaChairUrlFormat}-status`,
- `#${seniorAreaChairUrlFormat}-status`,
- '#deskrejectwithdrawn-status',
- ]
-
- if (submissionContentFields.length > 0) {
- submissionContentFields.forEach((fieldAttrs) => validTabIds.push(`#${fieldAttrs.field}`))
- }
-
- if (extraTabs.length > 0) {
- extraTabs.forEach((tabAttrs) => validTabIds.push(`#${tabAttrs.tabId}`))
- }
-
- if (!validTabIds.includes(activeTabId)) {
- setActiveTabId('#venue-configuration')
- return
- }
- router.replace(activeTabId)
- }, [activeTabId])
-
const missingConfig = Object.entries({
header,
entity: group,
@@ -1087,146 +1057,93 @@ const ProgramChairConsole = ({ appContext, extraTabs = [] }) => {
return (
<>
-
-
- setActiveTabId('#venue-configuration')}
- >
- Overview
-
- setActiveTabId(`#${submissionName.toLowerCase()}-status`)}
- >
- {submissionName} Status
-
- setActiveTabId(`#${reviewerUrlFormat}-status`)}
- >
- {getSingularRoleName(prettyField(reviewerName))} Status
-
- {areaChairsId && (
- setActiveTabId(`#${areaChairUrlFormat}-status`)}
- >
- {getSingularRoleName(prettyField(areaChairName))} Status
-
- )}
- {seniorAreaChairsId && (
- setActiveTabId(`#${seniorAreaChairUrlFormat}-status`)}
- >
- {getSingularRoleName(prettyField(seniorAreaChairName))} Status
-
- )}
- {(withdrawnVenueId || deskRejectedVenueId) && (
- setActiveTabId('#deskrejectwithdrawn-status')}
- >
- Desk Rejected/Withdrawn {pluralizeString(submissionName)}
-
- )}
- {submissionContentFields.length > 0 &&
- submissionContentFields.map((fieldAttrs) => (
- setActiveTabId(`#${fieldAttrs.field}`)}
- >
- {prettyField(fieldAttrs.field)}
-
- ))}
- {extraTabs.length > 0 &&
- extraTabs.map((tabAttrs) => (
- setActiveTabId(`#${tabAttrs.tabId}`)}
- >
- {tabAttrs.tabName}
-
- ))}
-
-
-
-
-
-
-
- {activeTabId === `#${submissionName.toLowerCase()}-status` && (
+ ,
+ visible: true,
+ },
+ {
+ id: `${submissionName.toLowerCase()}-status`,
+ label: `${submissionName} Status`,
+ content: (
- )}
-
-
-
-
- {areaChairsId && activeTabId === `#${areaChairUrlFormat}-status` && (
-
+ ),
+ visible: true,
+ },
+ {
+ id: `${reviewerUrlFormat}-status`,
+ label: `${getSingularRoleName(prettyField(reviewerName))} Status`,
+ content: (
+
+ ),
+ visible: true,
+ },
+ {
+ id: `${areaChairUrlFormat}-status`,
+ label: `${getSingularRoleName(prettyField(areaChairName))} Status`,
+ content: (
-
- )}
- {seniorAreaChairsId && activeTabId === `#${seniorAreaChairUrlFormat}-status` && (
-
+ ),
+ visible: areaChairsId,
+ },
+ {
+ id: `${seniorAreaChairUrlFormat}-status`,
+ label: `${getSingularRoleName(prettyField(seniorAreaChairName))} Status`,
+ content: (
-
- )}
-
- {activeTabId === '#deskrejectwithdrawn-status' && (
-
- )}
-
- {submissionContentFields.length > 0 &&
- submissionContentFields.map((fieldAttrs) => (
-
- {activeTabId === `#${fieldAttrs.field}` && (
+ ),
+ visible: seniorAreaChairsId,
+ },
+ {
+ id: 'deskrejectwithdrawn-status',
+ label: `Desk Rejected/Withdrawn ${pluralizeString(submissionName)}`,
+ content: ,
+ visible: withdrawnVenueId || deskRejectedVenueId,
+ },
+ ...(submissionContentFields.length > 0
+ ? submissionContentFields.map((fieldAttrs) => ({
+ id: fieldAttrs.field,
+ label: prettyField(fieldAttrs.field),
+ content: (
- )}
-
- ))}
- {extraTabs.length > 0 &&
- extraTabs.map((tabAttrs) => (
-
- {activeTabId === `#${tabAttrs.tabId}` && tabAttrs.renderTab()}
-
- ))}
-
-
+ ),
+ visible: true,
+ }))
+ : []),
+ ...(extraTabs.length > 0
+ ? extraTabs.map((tabAttrs) => ({
+ id: tabAttrs.tabId,
+ label: tabAttrs.tabName,
+ content: tabAttrs.renderTab(),
+ visible: true,
+ }))
+ : []),
+ ]}
+ />
>
)
}
diff --git a/components/webfield/ProgramChairConsole/ReviewerStatus.js b/components/webfield/ProgramChairConsole/ReviewerStatus.js
index 286807573..464d2f7b3 100644
--- a/components/webfield/ProgramChairConsole/ReviewerStatus.js
+++ b/components/webfield/ProgramChairConsole/ReviewerStatus.js
@@ -292,7 +292,6 @@ const ReviewerStatusTab = ({
pcConsoleData,
loadReviewMetaReviewData,
loadRegistrationNoteMap,
- showContent,
}) => {
const [reviewerStatusTabData, setReviewerStatusTabData] = useState({})
const {
@@ -456,7 +455,7 @@ const ReviewerStatusTab = ({
}
useEffect(() => {
- if (!pcConsoleData.reviewers || !showContent) return
+ if (!pcConsoleData.reviewers) return
if (!pcConsoleData.registrationNoteMap) {
loadRegistrationNoteMap()
} else {
@@ -466,7 +465,6 @@ const ReviewerStatusTab = ({
pcConsoleData.reviewers,
pcConsoleData.noteNumberReviewMetaReviewMap,
pcConsoleData.registrationNoteMap,
- showContent,
])
useEffect(() => {
diff --git a/components/webfield/ReviewerConsole.js b/components/webfield/ReviewerConsole.js
index 33a4eb723..b183f6015 100644
--- a/components/webfield/ReviewerConsole.js
+++ b/components/webfield/ReviewerConsole.js
@@ -5,7 +5,6 @@ import Link from 'next/link'
import { chunk } from 'lodash'
import api from '../../lib/api-client'
import Table from '../Table'
-import { Tab, TabList, TabPanel, TabPanels, Tabs } from '../Tabs'
import WebFieldContext from '../WebFieldContext'
import BasicHeader from './BasicHeader'
import { ReviewerConsoleNoteReviewStatus } from './NoteReviewStatus'
@@ -28,6 +27,7 @@ import ReviewerConsoleMenuBar from './ReviewerConsoleMenuBar'
import LoadingSpinner from '../LoadingSpinner'
import ConsoleTaskList from './ConsoleTaskList'
import { getProfileLink } from '../../lib/webfield-utils'
+import ConsoleTabs from './ConsoleTabs'
const AreaChairInfo = ({ areaChairName, areaChairIds }) => (
@@ -284,10 +284,7 @@ const ReviewerConsole = ({ appContext }) => {
const { setBannerContent } = appContext
const [reviewerConsoleData, setReviewerConsoleData] = useState({})
const [enablePaperRanking, setEnablePaperRanking] = useState(true)
- const [activeTabId, setActiveTabId] = useState(
- decodeURIComponent(window.location.hash) ||
- `#assigned-${pluralizeString(submissionName ?? '').toLowerCase()}`
- )
+ const [activeTabId, setActiveTabId] = useState(null)
const paperRankingId = `${venueId}/${reviewerName}/-/Paper_Ranking`
const reviewerUrlFormat = reviewerName ? getRoleHashFragment(reviewerName) : null
@@ -603,20 +600,6 @@ const ReviewerConsole = ({ appContext }) => {
}
}, [reviewerConsoleData.notes])
- useEffect(() => {
- if (user && !userLoading) {
- const validTabIds = [
- `#assigned-${pluralizeString(submissionName ?? '').toLowerCase()}`,
- `#${reviewerUrlFormat}-tasks`,
- ]
- if (!validTabIds.includes(activeTabId)) {
- setActiveTabId(`#assigned-${pluralizeString(submissionName ?? '').toLowerCase()}`)
- return
- }
- router.replace(activeTabId)
- }
- }, [activeTabId, user, userLoading])
-
const missingConfig = Object.entries({
header,
group,
@@ -651,48 +634,31 @@ const ReviewerConsole = ({ appContext }) => {
customLoad={reviewerConsoleData.customLoad}
submissionName={submissionName}
/>
-
-
-
- setActiveTabId(`#assigned-${pluralizeString(submissionName).toLowerCase()}`)
- }
- >
- Assigned {pluralizeString(submissionName)}
-
- setActiveTabId(`#${reviewerUrlFormat}-tasks`)}
- >
- {getSingularRoleName(prettyField(reviewerName))} Tasks
-
-
-
-
-
- {activeTabId === `#assigned-${pluralizeString(submissionName).toLowerCase()}` &&
- renderTable()}
-
-
-
- {activeTabId === `#${reviewerUrlFormat}-tasks` && (
+
- )}
-
-
-
+ ),
+ visible: true,
+ },
+ ]}
+ updateActiveTabId={setActiveTabId}
+ />
>
)
}
diff --git a/components/webfield/SeniorAreaChairConsole.js b/components/webfield/SeniorAreaChairConsole.js
index 9615d289a..fc9db079f 100644
--- a/components/webfield/SeniorAreaChairConsole.js
+++ b/components/webfield/SeniorAreaChairConsole.js
@@ -2,7 +2,6 @@
import { useContext, useEffect, useState } from 'react'
import { useRouter } from 'next/router'
import WebFieldContext from '../WebFieldContext'
-import { Tab, TabList, TabPanel, TabPanels, Tabs } from '../Tabs'
import BasicHeader from './BasicHeader'
import AreaChairStatus from './SeniorAreaChairConsole/AreaChairStatus'
import PaperStatus from './SeniorAreaChairConsole/PaperStatus'
@@ -25,6 +24,7 @@ import {
} from '../../lib/utils'
import { formatProfileContent } from '../../lib/edge-utils'
import RejectedWithdrawnPapers from './ProgramChairConsole/RejectedWithdrawnPapers'
+import ConsoleTabs from './ConsoleTabs'
const SeniorAreaChairConsole = ({ appContext }) => {
const {
@@ -68,9 +68,6 @@ const SeniorAreaChairConsole = ({ appContext }) => {
const [isLoadingData, setIsLoadingData] = useState(false)
const router = useRouter()
const query = useQuery()
- const [activeTabId, setActiveTabId] = useState(
- decodeURIComponent(window.location.hash) || `#${submissionName ?? ''.toLowerCase()}-status`
- )
const seniorAreaChairUrlFormat = getRoleHashFragment(seniorAreaChairName)
const areaChairUrlFormat = getRoleHashFragment(areaChairName)
@@ -657,21 +654,6 @@ const SeniorAreaChairConsole = ({ appContext }) => {
loadData()
}, [user, userLoading, group])
- useEffect(() => {
- // if (!activeTabId) return
- const validTabIds = [
- `#${(submissionName ?? '').toLowerCase()}-status`,
- `#${areaChairUrlFormat}-status`,
- '#deskrejectwithdrawn-status',
- `#${seniorAreaChairUrlFormat}-tasks`,
- ]
- if (!validTabIds.includes(activeTabId)) {
- setActiveTabId(`#${(submissionName ?? '').toLowerCase()}-status`)
- return
- }
- router.replace(activeTabId)
- }, [activeTabId])
-
const missingConfig = Object.entries({
header,
entity: group,
@@ -693,72 +675,43 @@ const SeniorAreaChairConsole = ({ appContext }) => {
return (
<>
-
-
-
- setActiveTabId(`#${submissionName.toLowerCase()}-status`)}
- >
- {submissionName} Status
-
- setActiveTabId(`#${areaChairUrlFormat}-status`)}
- hidden={!assignmentInvitation}
- >
- {getSingularRoleName(prettyField(areaChairName))} Status
-
- {(withdrawnVenueId || deskRejectedVenueId) && (
- setActiveTabId('#deskrejectwithdrawn-status')}
- >
- Desk Rejected/Withdrawn {pluralizeString(submissionName)}
-
- )}
- setActiveTabId(`#${seniorAreaChairUrlFormat}-tasks`)}
- >
- {getSingularRoleName(prettyField(seniorAreaChairName))} Tasks
-
-
-
-
-
- {activeTabId === `#${submissionName.toLowerCase()}-status` && (
-
- )}
-
- {activeTabId === `#${areaChairUrlFormat}-status` && (
-
+ ,
+ visible: true,
+ },
+ {
+ id: `${areaChairUrlFormat}-status`,
+ label: `${getSingularRoleName(prettyField(areaChairName))} Status`,
+ content: (
-
- )}
- {activeTabId === '#deskrejectwithdrawn-status' && (
-
+ ),
+ visible: assignmentInvitation,
+ },
+ {
+ id: 'deskrejectwithdrawn-status',
+ label: `Desk Rejected/Withdrawn ${pluralizeString(submissionName)}`,
+ content: (
-
- )}
-
- {activeTabId === `#${seniorAreaChairUrlFormat}-tasks` && (
-
-
-
- )}
-
-
+ ),
+ visible: withdrawnVenueId || deskRejectedVenueId,
+ },
+ {
+ id: `${seniorAreaChairUrlFormat}-tasks`,
+ label: `${getSingularRoleName(prettyField(seniorAreaChairName))} Tasks`,
+ content:
,
+ visible: true,
+ },
+ ]}
+ />
>
)
}
diff --git a/lib/utils.js b/lib/utils.js
index d69802d56..ce99fbccb 100644
--- a/lib/utils.js
+++ b/lib/utils.js
@@ -662,7 +662,7 @@ export function pluralizeString(word) {
* @param {string} roleName - role name to convert
*/
export function getSingularRoleName(roleName) {
- return roleName.endsWith('s') ? roleName.slice(0, -1) : roleName
+ return roleName?.endsWith('s') ? roleName.slice(0, -1) : roleName
}
/**
diff --git a/unitTests/AreaChairConsole.test.js b/unitTests/AreaChairConsole.test.js
index 34f67a4af..931b3f695 100644
--- a/unitTests/AreaChairConsole.test.js
+++ b/unitTests/AreaChairConsole.test.js
@@ -12,10 +12,12 @@ let noteReviewStatusProps
jest.mock('next/router', () => ({
useRouter: () => ({
- replace: (params) => {
+ replace: jest.fn((params) => {
routerParams = params
- return jest.fn()
- },
+ return {
+ catch: jest.fn(),
+ }
+ }),
}),
}))
jest.mock('../hooks/useUser', () => () => useUserReturnValue)
@@ -45,25 +47,6 @@ beforeEach(() => {
})
describe('AreaChairConsole', () => {
- test('default to assigned papers tab when window.location does not contain any hash', async () => {
- const providerProps = { value: { submissionName: 'Submissions' } }
- renderWithWebFieldContext(
-
,
- providerProps
- )
- expect(routerParams).toEqual('#assigned-submissions')
- })
-
- test('default to assigned papers tab when window.location.hash does not match any tab', async () => {
- window.location.hash = '#some-unknown-tab'
- const providerProps = { value: { submissionName: 'Submissions' } }
- renderWithWebFieldContext(
-
,
- providerProps
- )
- expect(routerParams).toEqual('#assigned-submissions')
- })
-
test('show error when config is not complete', async () => {
const providerProps = { value: { areaChairName: undefined } }
const { rerender } = renderWithWebFieldContext(
diff --git a/unitTests/ConsoleTabs.test.js b/unitTests/ConsoleTabs.test.js
new file mode 100644
index 000000000..e51c48d12
--- /dev/null
+++ b/unitTests/ConsoleTabs.test.js
@@ -0,0 +1,110 @@
+import '@testing-library/jest-dom'
+import { render, screen } from '@testing-library/react'
+import userEvent from '@testing-library/user-event'
+import ConsoleTabs from '../components/webfield/ConsoleTabs'
+
+let routerParams
+
+jest.mock('next/router', () => ({
+ useRouter: () => ({
+ replace: jest.fn((params) => {
+ routerParams = params
+ return {
+ catch: jest.fn(),
+ }
+ }),
+ }),
+}))
+
+beforeEach(() => {
+ routerParams = null
+ window.location.hash = ''
+})
+
+describe('ConsoleTabs', () => {
+ test('render first tab by default', () => {
+ const tabs = [
+ { id: 'tab1Id', label: 'Tab One', visible: true, content:
tab 1 content },
+ { id: 'tab2Id', label: 'Tab Two', visible: true, content:
tab 2 content },
+ { id: 'tab3Id', label: 'Tab Three', visible: true, content:
tab 3 content },
+ ]
+ render(
)
+
+ expect(screen.getAllByRole('tab').length).toBe(3)
+ expect(screen.getAllByRole('tabpanel').length).toBe(1)
+ expect(screen.getByText('tab 1 content')).toBeInTheDocument()
+ })
+
+ test('render specified default tab in properties', () => {
+ const tabs = [
+ { id: 'tab1Id', label: 'Tab One', visible: true, content:
tab 1 content },
+ { id: 'tab2Id', label: 'Tab Two', visible: true, content:
tab 2 content },
+ { id: 'tab3Id', label: 'Tab Three', visible: true, content:
tab 3 content },
+ ]
+ render(
)
+
+ expect(screen.getAllByRole('tab').length).toBe(3)
+ expect(screen.getAllByRole('tabpanel').length).toBe(1)
+ expect(screen.getByText('tab 3 content')).toBeInTheDocument()
+ })
+
+ test('render tab specified in url', () => {
+ window.location.hash = '#tab2Id' // higher priority than defaultActiveTabId
+ const tabs = [
+ { id: 'tab1Id', label: 'Tab One', visible: true, content:
tab 1 content },
+ { id: 'tab2Id', label: 'Tab Two', visible: true, content:
tab 2 content },
+ { id: 'tab3Id', label: 'Tab Three', visible: true, content:
tab 3 content },
+ ]
+ render(
)
+
+ expect(screen.getAllByRole('tab').length).toBe(3)
+ expect(screen.getAllByRole('tabpanel').length).toBe(1)
+ expect(screen.getByText('tab 2 content')).toBeInTheDocument()
+ })
+
+ test('render only visible tabs', () => {
+ const tabs = [
+ { id: 'tab1Id', label: 'Tab One', visible: false, content:
tab 1 content }, // first tab is invisible
+ { id: 'tab2Id', label: 'Tab Two', visible: true, content:
tab 2 content }, // tab 2 becomes first tab
+ { id: 'tab3Id', label: 'Tab Three', visible: true, content:
tab 3 content },
+ ]
+ render(
)
+
+ expect(screen.getAllByRole('tab').length).toBe(2)
+ expect(screen.getAllByRole('tabpanel').length).toBe(1)
+ expect(screen.getByText('tab 2 content')).toBeInTheDocument()
+ })
+
+ test('handle invalid hash in url', () => {
+ window.location.hash = '#invalidTabId'
+ const tabs = [
+ { id: 'tab1Id', label: 'Tab One', visible: true, content:
tab 1 content },
+ { id: 'tab2Id', label: 'Tab Two', visible: true, content:
tab 2 content },
+ { id: 'tab3Id', label: 'Tab Three', visible: true, content:
tab 3 content },
+ ]
+ render(
)
+
+ expect(screen.getAllByRole('tab').length).toBe(3)
+ expect(screen.getAllByRole('tabpanel').length).toBe(1)
+ expect(screen.getByText('tab 1 content')).toBeInTheDocument()
+ })
+
+ test('switch tab and pass active tab id to parent', async () => {
+ const tabs = [
+ { id: 'tab1Id', label: 'Tab One', visible: true, content:
tab 1 content },
+ { id: 'tab2Id', label: 'Tab Two', visible: true, content:
tab 2 content },
+ { id: 'tab3Id', label: 'Tab Three', visible: true, content:
tab 3 content },
+ ]
+ render(
)
+
+ expect(screen.getByText('tab 1 content')).toBeInTheDocument()
+
+ await userEvent.click(screen.getByText('Tab Two'))
+ expect(screen.getByText('tab 2 content')).toBeInTheDocument()
+ expect(routerParams).toBe('#tab2Id')
+
+ await userEvent.click(screen.getByText('Tab Three'))
+ expect(screen.getByText('tab 3 content')).toBeInTheDocument()
+ expect(routerParams).toBe('#tab3Id')
+ })
+})
diff --git a/unitTests/ReviewerConsole.test.js b/unitTests/ReviewerConsole.test.js
index 0e5930eb2..7701f8962 100644
--- a/unitTests/ReviewerConsole.test.js
+++ b/unitTests/ReviewerConsole.test.js
@@ -11,10 +11,12 @@ let noteReviewStatusProps
jest.mock('next/router', () => ({
useRouter: () => ({
- replace: (params) => {
+ replace: jest.fn((params) => {
routerParams = params
- return jest.fn()
- },
+ return {
+ catch: jest.fn(),
+ }
+ }),
}),
}))
jest.mock('../hooks/useUser', () => () => useUserReturnValue)
diff --git a/unitTests/SeniorAreaChairConsole.test.js b/unitTests/SeniorAreaChairConsole.test.js
index 19f5e81d2..ee20b99ab 100644
--- a/unitTests/SeniorAreaChairConsole.test.js
+++ b/unitTests/SeniorAreaChairConsole.test.js
@@ -1,7 +1,5 @@
-import { screen, waitFor } from '@testing-library/react'
+import { screen } from '@testing-library/react'
import '@testing-library/jest-dom'
-import userEvent from '@testing-library/user-event'
-import api from '../lib/api-client'
import { reRenderWithWebFieldContext, renderWithWebFieldContext } from './util'
import SeniorAreaChairConsole from '../components/webfield/SeniorAreaChairConsole'
@@ -14,10 +12,12 @@ let sacTasksProps
jest.mock('nanoid', () => ({ nanoid: () => 'some id' }))
jest.mock('next/router', () => ({
useRouter: () => ({
- replace: (params) => {
+ replace: jest.fn((params) => {
routerParams = params
- return jest.fn()
- },
+ return {
+ catch: jest.fn(),
+ }
+ }),
}),
}))
jest.mock('../hooks/useUser', () => () => useUserReturnValue)
@@ -53,25 +53,6 @@ beforeEach(() => {
})
describe('SeniorAreaChairConsole', () => {
- test('default to paper status tab when window.location does not contain any hash', async () => {
- const providerProps = { value: { submissionName: 'Submission' } }
- renderWithWebFieldContext(
-
,
- providerProps
- )
- expect(routerParams).toEqual('#submission-status')
- })
-
- test('default to assigned papers tab when window.location.hash does not match any tab', async () => {
- window.location.hash = '#some-unknown-tab'
- const providerProps = { value: { submissionName: 'Submission' } }
- renderWithWebFieldContext(
-
,
- providerProps
- )
- expect(routerParams).toEqual('#submission-status')
- })
-
test('show error message based on sac name when config is not complete', async () => {
const providerProps = { value: { seniorAreaChairName: undefined } }
const { rerender } = renderWithWebFieldContext(