diff --git a/components/webfield/ARRSeniorAreaChairConsole.js b/components/webfield/ARRSeniorAreaChairConsole.js new file mode 100644 index 000000000..cb88b5463 --- /dev/null +++ b/components/webfield/ARRSeniorAreaChairConsole.js @@ -0,0 +1,794 @@ +/* globals promptError: false */ +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' +import SeniorAreaChairTasks from './SeniorAreaChairConsole/SeniorAreaChairTasks' +import ErrorDisplay from '../ErrorDisplay' +import useUser from '../../hooks/useUser' +import useQuery from '../../hooks/useQuery' +import api from '../../lib/api-client' +import { referrerLink, venueHomepageLink } from '../../lib/banner-links' +import { + getIndentifierFromGroup, + getNumberFromGroup, + getProfileName, + prettyId, + parseNumberField, + prettyField, + getSingularRoleName, + getRoleHashFragment, + pluralizeString, +} from '../../lib/utils' +import { formatProfileContent } from '../../lib/edge-utils' +import RejectedWithdrawnPapers from './ProgramChairConsole/RejectedWithdrawnPapers' + +const ARRSeniorAreaChairConsole = ({ appContext }) => { + const { + header, + entity: group, + venueId, + assignmentInvitation, + assignmentLabel, + submissionId, + submissionName, + reviewersId, + reviewerName, + reviewerAssignmentId = `${reviewersId}/-/Assignment`, + anonReviewerName, + areaChairsId, + areaChairName = 'Area_Chairs', + areaChairAssignmentId = `${areaChairsId}/-/Assignment`, + anonAreaChairName, + secondaryAreaChairName, + secondaryAnonAreaChairName, + seniorAreaChairName = 'Senior_Area_Chairs', + reviewRatingName, + reviewConfidenceName, + officialReviewName, + officialMetaReviewName = 'Meta_Review', + decisionName = 'Decision', + preliminaryDecisionName, + metaReviewRecommendationName = 'recommendation', + additionalMetaReviewFields = [], + edgeBrowserDeployedUrl, + customStageInvitations, + withdrawnVenueId, + deskRejectedVenueId, + filterFunction, + preferredEmailInvitationId, + ithenticateInvitationId, + perPaperInvitationSuffixes + } = useContext(WebFieldContext) + const { setBannerContent } = appContext + const { user, accessToken, userLoading } = useUser() + const [sacConsoleData, setSacConsoleData] = useState({}) + const [isLoadingData, setIsLoadingData] = useState(false) + const router = useRouter() + const query = useQuery() + const [activeTabId, setActiveTabId] = useState( + window.location.hash || `#${submissionName ?? ''.toLowerCase()}-status` + ) + + const seniorAreaChairUrlFormat = getRoleHashFragment(seniorAreaChairName) + const areaChairUrlFormat = getRoleHashFragment(areaChairName) + + const loadData = async () => { + if (isLoadingData) return + + setIsLoadingData(true) + try { + // #region getSubmissions + const notesP = submissionId + ? api + .getAll( + '/groups', + { + member: user.id, + prefix: `${venueId}/${submissionName}.*`, + select: 'id', + domain: group.domain, + }, + { accessToken } + ) + .then((groups) => { + const noteNumbers = groups.flatMap((p) => + p.id.endsWith(`/${seniorAreaChairName}`) + ? getNumberFromGroup(p.id, submissionName) + : [] + ) + if (!noteNumbers.length) return [] + return api + .getAll( + '/notes', + { + invitation: submissionId, + details: 'replies', + select: 'id,number,forum,content,details,invitations,readers', + sort: 'number:asc', + domain: group.domain, + }, + { accessToken } + ) + .then((notes) => + notes.filter( + (note) => + // eslint-disable-next-line no-new-func + ((filterFunction && Function('note', filterFunction)(note)) ?? true) && + noteNumbers.includes(note.number) + ) + ) + }) + : Promise.resolve([]) + // #endregion + + // #region getInvitations + + const invitationsP = api.getAll( + '/invitations', + { + ids: [reviewerAssignmentId, areaChairAssignmentId], + }, + { accessToken } + ) + + // #endregion + + // #region getGroups (per paper groups) + const perPaperGroupResultsP = api.get( + '/groups', + { + prefix: `${venueId}/${submissionName}.*`, + stream: true, + select: 'id,members', + domain: group.domain, + }, + { accessToken } + ) + // #endregion + + // #region getAssignedACEdges + const assignmentsP = assignmentInvitation + ? api.getAll( + '/edges', + { + invitation: assignmentInvitation, + label: assignmentLabel, + tail: user.profile.id, + domain: group.domain, + }, + { accessToken } + ) + : Promise.resolve([]) + // #endregion + + // #region get ithenticate edges + const ithenticateEdgesP = ithenticateInvitationId + ? api + .getAll( + '/edges', + { + invitation: ithenticateInvitationId, + groupBy: 'id', + }, + { accessToken, resultsKey: 'groupedEdges' } + ) + .then((result) => result.map((p) => p.values[0])) + : Promise.resolve([]) + // #endregion + + const [notes, invitations, perPaperGroupResults, assignmentEdges, ithenticateEdges] = + await Promise.all([ + notesP, + invitationsP, + perPaperGroupResultsP, + assignmentsP, + ithenticateEdgesP, + ]) + const assignedAreaChairIds = assignmentEdges.map((p) => p.head) + + // #region get perPaperInvitations + const perPaperInvitations = (perPaperInvitationSuffixes && notes.length > 0) ? await api.getAll( + '/invitations', + { + ids: perPaperInvitationSuffixes.map((suffix) => + notes.map((note) => `${venueId}/${submissionName}${note.number}/-/${suffix}`) + ).flat(), + }, + { accessToken } + ) + : [] + // Use note.number to map to the correct perPaperInvitation by checking ${submissionName}${note.number}/ string + const perPaperInvitationForumsMap = new Map() + notes.forEach((note) => { + const noteNumber = note.number + const notePerPaperInvitations = perPaperInvitations.filter((invitation) => + invitation.id.includes(`${submissionName}${noteNumber}/`) + ).map((invitation) => ({'suffix': invitation.id.split('/-/')[1], 'forum': invitation.edit.note.forum})) + perPaperInvitationForumsMap.set(noteNumber, notePerPaperInvitations) + } + ) + console.log(perPaperInvitationForumsMap) + + // #endregion + + // #region categorize result of per paper groups + let reviewerGroups = [] + const anonReviewerGroups = {} + let areaChairGroups = [] + const anonAreaChairGroups = {} + const secondaryAreaChairGroups = [] + const secondaryAnonAreaChairGroups = {} + const seniorAreaChairGroups = [] + let allGroupMembers = [] + perPaperGroupResults.groups?.forEach((p) => { + const number = getNumberFromGroup(p.id, submissionName) + if (p.id.endsWith(`/${reviewerName}`)) { + reviewerGroups.push({ + noteNumber: getNumberFromGroup(p.id, submissionName), + ...p, + }) + p.members.forEach((member) => { + if (!(number in anonReviewerGroups)) anonReviewerGroups[number] = {} + if (!(member in anonReviewerGroups[number]) && member.includes(anonReviewerName)) { + anonReviewerGroups[number][member] = member + } + }) + } else if (p.id.includes(`/${anonReviewerName}`)) { + if (!(number in anonReviewerGroups)) anonReviewerGroups[number] = {} + if (p.members.length) anonReviewerGroups[number][p.id] = p.members[0] + allGroupMembers = allGroupMembers.concat(p.members) + } else if (p.id.endsWith(`/${areaChairName}`)) { + areaChairGroups.push({ + noteNumber: getNumberFromGroup(p.id, submissionName), + ...p, + }) + p.members.forEach((member) => { + if (!(number in anonAreaChairGroups)) anonAreaChairGroups[number] = {} + if ( + !(member in anonAreaChairGroups[number]) && + member.includes(`/${anonAreaChairName}`) + ) { + anonAreaChairGroups[number][member] = member + } + }) + } else if (p.id.includes(`/${anonAreaChairName}`)) { + if (!(number in anonAreaChairGroups)) anonAreaChairGroups[number] = {} + if (p.members.length) anonAreaChairGroups[number][p.id] = p.members[0] + allGroupMembers = allGroupMembers.concat(p.members) + } else if (p.id.endsWith(seniorAreaChairName)) { + seniorAreaChairGroups.push(p) + } else if (secondaryAreaChairName && p.id.endsWith(`/${secondaryAreaChairName}`)) { + secondaryAreaChairGroups.push({ + noteNumber: getNumberFromGroup(p.id, submissionName), + ...p, + }) + p.members.forEach((member) => { + if (!(number in secondaryAnonAreaChairGroups)) + secondaryAnonAreaChairGroups[number] = {} + if ( + !(member in secondaryAnonAreaChairGroups[number]) && + member.includes(`/${secondaryAnonAreaChairName}`) + ) { + secondaryAnonAreaChairGroups[number][member] = member + } + }) + } else if (secondaryAreaChairName && p.id.includes(`/${secondaryAnonAreaChairName}`)) { + if (!(number in secondaryAnonAreaChairGroups)) + secondaryAnonAreaChairGroups[number] = {} + if (p.members.length) secondaryAnonAreaChairGroups[number][p.id] = p.members[0] + allGroupMembers = allGroupMembers.concat(p.members) + } + }) + + reviewerGroups = reviewerGroups.map((reviewerGroup) => { + const paperAnonReviewerGroups = anonReviewerGroups[reviewerGroup.noteNumber] || {} + return { + ...reviewerGroup, + members: reviewerGroup.members.flatMap((member) => { + let deanonymizedGroup = paperAnonReviewerGroups[member] + let anonymizedGroup = member + if (!deanonymizedGroup) { + deanonymizedGroup = member + anonymizedGroup = Object.keys(paperAnonReviewerGroups).find( + (key) => paperAnonReviewerGroups[key] === member + ) + } + if (!anonymizedGroup) return [] + return { + reviewerProfileId: deanonymizedGroup, + anonymizedGroup, + anonymousId: getIndentifierFromGroup(anonymizedGroup, anonReviewerName), + } + }), + } + }) + + areaChairGroups = areaChairGroups.map((areaChairGroup) => { + const paperAnonAreaChairGroups = anonAreaChairGroups[areaChairGroup.noteNumber] + return { + ...areaChairGroup, + members: areaChairGroup.members.flatMap((member) => { + let deanonymizedGroup = paperAnonAreaChairGroups?.[member] + let anonymizedGroup = member + if (!deanonymizedGroup) { + deanonymizedGroup = member + anonymizedGroup = Object.keys(paperAnonAreaChairGroups).find( + (key) => paperAnonAreaChairGroups[key] === member + ) + } + if (secondaryAreaChairName && member.endsWith(`/${secondaryAreaChairName}`)) { + return [] + } + return { + areaChairProfileId: deanonymizedGroup, + anonymizedGroup, + anonymousId: anonymizedGroup + ? getIndentifierFromGroup(anonymizedGroup, anonAreaChairName) + : null, + } + }), + secondaries: areaChairGroup.members.flatMap((member) => { + if (!secondaryAreaChairName || !member.endsWith(`/${secondaryAreaChairName}`)) { + return [] + } + + const acGroupNoteNum = areaChairGroup.noteNumber + const secondaryAreaChairGroup = secondaryAreaChairGroups.find( + (p) => p.noteNumber === acGroupNoteNum + ) + if (!secondaryAreaChairGroup) return [] + + return secondaryAreaChairGroup.members.map((secondaryMember) => ({ + areaChairProfileId: + secondaryAnonAreaChairGroups[acGroupNoteNum]?.[secondaryMember] ?? + secondaryMember, + anonymizedGroup: secondaryMember, + anonymousId: getIndentifierFromGroup( + secondaryMember, + secondaryAnonAreaChairName + ), + })) + }), + } + }) + // #endregion + + // #region get all profiles + const allIds = [...new Set(assignedAreaChairIds.concat(allGroupMembers))] + const ids = allIds.filter((p) => p.startsWith('~')) + const emails = allIds.filter((p) => p.match(/.+@.+/)) + const getProfilesByIdsP = ids.length + ? api.post( + '/profiles/search', + { + ids, + }, + { accessToken } + ) + : Promise.resolve([]) + const getProfilesByEmailsP = emails.length + ? api.post( + '/profiles/search', + { + emails, + }, + { accessToken } + ) + : Promise.resolve([]) + const profileResults = await Promise.all([getProfilesByIdsP, getProfilesByEmailsP]) + const allProfiles = (profileResults[0].profiles ?? []) + .concat(profileResults[1].profiles ?? []) + .map((profile) => ({ + ...profile, + preferredName: getProfileName(profile), + title: formatProfileContent(profile.content).title, + })) + + const allProfilesMap = new Map() + allProfiles.forEach((profile) => { + const usernames = profile.content.names.flatMap((p) => p.username ?? []) + usernames.concat(profile.email ?? []).forEach((key) => { + allProfilesMap.set(key, profile) + }) + }) + // #endregion + + const seniorAreaChairGroupByNumber = {} + const assignedNoteNumbers = [] + seniorAreaChairGroups.forEach((p) => { + const number = getNumberFromGroup(p.id, submissionName) + assignedNoteNumbers.push(number) + seniorAreaChairGroupByNumber[number] = p.id + }) + + const assignedNotes = notes.flatMap((p) => + assignedNoteNumbers.includes(p.number) ? p : [] + ) + + setSacConsoleData({ + assignedAreaChairIds, + areaChairGroups, + allProfilesMap, + assignmentInvitations: invitations, + notes: assignedNotes.flatMap((note) => { + if ([withdrawnVenueId, deskRejectedVenueId].includes(note.content?.venueid?.value)) + return [] + const assignedReviewers = + reviewerGroups?.find((p) => p.noteNumber === note.number)?.members ?? [] + const assignedAreaChairs = + areaChairGroups?.find((p) => p.noteNumber === note.number)?.members ?? [] + const secondaryAreaChairs = + areaChairGroups?.find((p) => p.noteNumber === note.number)?.secondaries ?? [] + const perPaperInvitationForums = perPaperInvitationForumsMap.get(note.number) ?? [] + const officialReviews = + note.details.replies + .filter((p) => { + const officialReviewInvitationId = `${venueId}/${submissionName}${note.number}/-/${officialReviewName}` + return p.invitations.includes(officialReviewInvitationId) + }) + ?.map((review) => { + const reviewValue = review.content?.review?.value + return { + ...review, + anonymousId: getIndentifierFromGroup(review.signatures[0], anonReviewerName), + confidence: parseNumberField(review.content?.[reviewConfidenceName]?.value), + ...Object.fromEntries( + (Array.isArray(reviewRatingName) + ? reviewRatingName + : [reviewRatingName] + ).map((ratingName) => { + const displayRatingName = + typeof ratingName === 'object' + ? Object.keys(ratingName)[0] + : ratingName + const ratingValue = + typeof ratingName === 'object' + ? Object.values(ratingName)[0] + .map((r) => review.content?.[r]?.value) + .find((s) => s !== undefined) + : review.content?.[ratingName]?.value + return [[displayRatingName], parseNumberField(ratingValue)] + }) + ), + reviewLength: reviewValue?.length, + forum: review.forum, + id: review.id, + } + }) ?? [] + + const ratings = Object.fromEntries( + (Array.isArray(reviewRatingName) ? reviewRatingName : [reviewRatingName]).map( + (ratingName) => { + const ratingDisplayName = + typeof ratingName === 'object' ? Object.keys(ratingName)[0] : ratingName + const ratingValues = officialReviews.map((p) => p[ratingDisplayName]) + const validRatingValues = ratingValues.filter((p) => p !== null) + const ratingAvg = validRatingValues.length + ? ( + validRatingValues.reduce((sum, curr) => sum + curr, 0) / + validRatingValues.length + ).toFixed(2) + : 'N/A' + const ratingMin = validRatingValues.length + ? Math.min(...validRatingValues) + : 'N/A' + const ratingMax = validRatingValues.length + ? Math.max(...validRatingValues) + : 'N/A' + return [ratingDisplayName, { ratingAvg, ratingMin, ratingMax }] + } + ) + ) + + const confidences = officialReviews.map((p) => p?.confidence) + const validConfidences = confidences.filter((p) => p !== null) + const confidenceAvg = validConfidences.length + ? ( + validConfidences.reduce((sum, curr) => sum + curr, 0) / validConfidences.length + ).toFixed(2) + : 'N/A' + const confidenceMin = validConfidences.length ? Math.min(...validConfidences) : 'N/A' + const confidenceMax = validConfidences.length ? Math.max(...validConfidences) : 'N/A' + + const customStageInvitationIds = customStageInvitations + ? customStageInvitations.map((p) => `/-/${p.name}`) + : [] + const customStageReviews = note.details.replies.filter((p) => + p.invitations.some((q) => customStageInvitationIds.some((r) => q.includes(r))) + ) + + const metaReviews = note.details.replies + .filter((p) => { + const officialMetaReviewInvitationId = `${venueId}/${submissionName}${note.number}/-/${officialMetaReviewName}` + return p.invitations.includes(officialMetaReviewInvitationId) + }) + .map((metaReview) => ({ + ...metaReview, + anonId: getIndentifierFromGroup(metaReview.signatures[0], anonAreaChairName), + })) + .map((metaReview) => { + const metaReviewAgreement = customStageReviews.find( + (p) => p.replyto === metaReview.id || p.forum === metaReview.forum + ) + const metaReviewAgreementConfig = metaReviewAgreement + ? customStageInvitations.find((p) => + metaReviewAgreement.invitations.some((q) => q.includes(`/-/${p.name}`)) + ) + : null + const metaReviewAgreementValue = + metaReviewAgreement?.content?.[metaReviewAgreementConfig?.displayField]?.value + return { + [metaReviewRecommendationName]: + metaReview?.content?.[metaReviewRecommendationName]?.value, + ...metaReview, + ...additionalMetaReviewFields?.reduce((prev, curr) => { + const additionalMetaReviewFieldValue = metaReview?.content?.[curr]?.value + return { + ...prev, + [curr]: { + value: additionalMetaReviewFieldValue, + searchValue: additionalMetaReviewFieldValue ?? 'N/A', + }, + } + }, {}), + metaReviewAgreement: metaReviewAgreement + ? { + searchValue: metaReviewAgreementValue, + name: prettyId(metaReviewAgreementConfig.name), + value: metaReviewAgreementValue, + id: metaReviewAgreement.id, + forum: metaReviewAgreement.forum, + } + : { + searchValue: 'N/A', + }, + } + }) + + const decisionInvitationId = `${venueId}/${submissionName}${note.number}/-/${decisionName}` + const decisionNote = note.details.replies.find((p) => + p.invitations.includes(decisionInvitationId) + ) + const decision = decisionNote?.content?.decision + ? decisionNote.content.decision?.value + : 'No Decision' + + let preliminaryDecision = null + if (preliminaryDecisionName) { + const prelimDecisionId = `${venueId}/${submissionName}${note.number}/-/${preliminaryDecisionName}` + const prelimDecisionNote = note.details.replies.find((p) => + p.invitations.includes(prelimDecisionId) + ) + if (prelimDecisionNote) { + preliminaryDecision = { + id: prelimDecisionNote.id, + recommendation: prelimDecisionNote.content?.recommendation?.value, + confidence: prelimDecisionNote.content?.confidence?.value, + discussionNeeded: + prelimDecisionNote.content?.discussion_with_SAC_needed?.value, + } + } + } + + return { + noteNumber: note.number, + note: { + ...note, + ...(ithenticateInvitationId && { + ithenticateWeight: + ithenticateEdges.find((p) => p.head === note.id)?.weight ?? 'N/A', + }), + perPaperInvitationForums + }, + reviewers: assignedReviewers?.map((reviewer) => { + const profile = allProfilesMap.get(reviewer.reviewerProfileId) + return { + ...reviewer, + type: 'profile', + profile, + hasReview: officialReviews.some((p) => p.anonymousId === reviewer.anonymousId), + noteNumber: note.number, + preferredId: reviewer.reviewerProfileId, + preferredName: profile ? getProfileName(profile) : reviewer.reviewerProfileId, + } + }), + officialReviews, + reviewProgressData: { + reviewers: assignedReviewers.map((reviewer) => ({ + id: reviewer.reviewerProfileId, + profile: allProfilesMap.get(reviewer.reviewerProfileId), + })), + numReviewersAssigned: assignedReviewers.length, + numReviewsDone: officialReviews.length, + ratings, + confidenceAvg, + confidenceMax, + confidenceMin, + replyCount: note.details.replies?.length ?? 0, + }, + metaReviewData: { + numAreaChairsAssigned: assignedAreaChairs.length, + areaChairs: assignedAreaChairs.map((areaChair) => { + const profile = allProfilesMap.get(areaChair.areaChairProfileId) + return { + ...areaChair, + noteNumber: note.number, + preferredId: profile ? profile.id : areaChair.areaChairProfileId, + preferredName: profile + ? getProfileName(profile) + : areaChair.areaChairProfileId, + title: profile?.title, + } + }), + secondaryAreaChairs: secondaryAreaChairs.map((areaChair) => { + const profile = allProfilesMap.get(areaChair.areaChairProfileId) + return { + ...areaChair, + preferredName: profile + ? getProfileName(profile) + : areaChair.areaChairProfileId, + } + }), + numMetaReviewsDone: metaReviews.length, + metaReviews, + metaReviewsSearchValue: metaReviews?.length + ? metaReviews.map((p) => p[metaReviewRecommendationName]).join(' ') + : 'N/A', + metaReviewAgreementSearchValue: metaReviews + .map((p) => p.metaReviewAgreement?.searchValue) + .join(' '), + ...additionalMetaReviewFields?.reduce((prev, curr) => { + const additionalMetaReviewValues = metaReviews.map((p) => p[curr]?.searchValue) + return { + ...prev, + [`${curr}SearchValue`]: additionalMetaReviewValues.join(' '), + } + }, {}), + }, + decision, + preliminaryDecision, + messageSignature: seniorAreaChairGroupByNumber[note.number], + ithenticateEdge: ithenticateEdges.find((p) => p.head === note.id), + } + }), + withdrawnNotes: assignedNotes.flatMap((note) => { + if (note.content?.venueid?.value === withdrawnVenueId) return note + return [] + }), + deskRejectedNotes: assignedNotes.flatMap((note) => { + if (note.content?.venueid?.value === deskRejectedVenueId) return note + return [] + }) + }) + } catch (error) { + promptError(`loading data: ${error.message}`) + } + setIsLoadingData(false) + } + + useEffect(() => { + if (!query) return + + if (query.referrer) { + setBannerContent(referrerLink(query.referrer)) + } else { + setBannerContent(venueHomepageLink(venueId)) + } + }, [query, venueId]) + + useEffect(() => { + if (userLoading || !user || !group || !venueId) return + 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, + venueId, + submissionName, + reviewerName, + anonReviewerName, + officialReviewName, + }) + .filter(([key, value]) => value === undefined) + .map((p) => p[0]) + if (missingConfig.length > 0) { + const errorMessage = `${prettyField( + seniorAreaChairName + )} Console is missing required properties: ${missingConfig.join(', ')}` + return + } + + 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` && ( + + + + )} + {activeTabId === '#deskrejectwithdrawn-status' && ( + + + + )} + + {activeTabId === `#${seniorAreaChairUrlFormat}-tasks` && ( + + + + )} + + + + ) +} + +export default ARRSeniorAreaChairConsole diff --git a/components/webfield/NoteSummary.js b/components/webfield/NoteSummary.js index b38484ec4..abe986649 100644 --- a/components/webfield/NoteSummary.js +++ b/components/webfield/NoteSummary.js @@ -1,7 +1,7 @@ /* globals promptError: false */ import isEqual from 'lodash/isEqual' import { useState } from 'react' -import { forumDate, getNotePdfUrl } from '../../lib/utils' +import { forumDate, getNotePdfUrl, prettyId } from '../../lib/utils' import Collapse from '../Collapse' import Icon from '../Icon' import NoteContent, { NoteContentV2 } from '../NoteContent' @@ -81,6 +81,8 @@ const NoteSummary = ({ setIsLoadingReportLink(false) } + const perPaperInvitationForums = note?.perPaperInvitationForums + return (

@@ -187,6 +189,23 @@ const NoteSummary = ({ /> )} + + {perPaperInvitationForums && ( +
+ {perPaperInvitationForums.map((invitation) => ( + + Edit {prettyId(invitation.suffix)} + + ))} +
+ ) + } +

) }