diff --git a/10409_validation_delete_me.md b/10409_validation_delete_me.md new file mode 100644 index 00000000000..44ba212c975 --- /dev/null +++ b/10409_validation_delete_me.md @@ -0,0 +1,26 @@ +Problem statement: How can we detect when making a validation changes to entities, we do not create invalid data in the database? + +- Pull all validation rules into a single file, if they change in any way, a validation sweep must be run on the database +- Continue validating before we return inside all of our interactors but instead of exploding just silently log which entities are now invalid. +- On each deployment validate all data in the database. + + +Assumptions +- Not all business rules can or should live inside of the persistence layer. + - For example when a trialSession is calendared it should have a sessionType +- What is and what is not a valid entity can only be expressed inside of functions. + - Is it possible to tell if a function has been modified between different git commits? + - If it is a pure function that does not modify any external state + + +shared/src/business/entities/contacts/Contact.ts getValidationRules depends on instance.countryType + + +Thoughts +- If validation rules are expressed inside of functions it is impossible to tell if a function has changed its behavior from one release to another. If it is impossible to tell if validation rules have changed then maybe defensively checking if they have changed, and running a validation sweep on the DB is not the correct approach +- Another option is better warnings to devs about making validation changes. +- Another option is continue validating, but only log invalid values instead of throwing errors to reduce application impact. + + +Resources +- Validate at input time. Validate again before you put it in the database. And have database constraints to prevent bad input. And you can bet in spite of all that, bad data will still get into your database, so validate it again when you use it. \ No newline at end of file diff --git a/shared/src/business/dto/trialSessions/TrialSessionInfoDTO.ts b/shared/src/business/dto/trialSessions/TrialSessionInfoDTO.ts index f3291519721..fca87992417 100644 --- a/shared/src/business/dto/trialSessions/TrialSessionInfoDTO.ts +++ b/shared/src/business/dto/trialSessions/TrialSessionInfoDTO.ts @@ -21,17 +21,11 @@ export class TrialSessionInfoDTO { public sessionStatus: string; public swingSession?: boolean; public dismissedAlertForNOTT?: boolean; - public isStartDateWithinNOTTReminderRange?: boolean; - public thirtyDaysBeforeTrialFormatted?: string; constructor(rawTrialSession: RawTrialSession) { this.estimatedEndDate = rawTrialSession.estimatedEndDate; this.isCalendared = rawTrialSession.isCalendared; this.judge = rawTrialSession.judge; - this.isStartDateWithinNOTTReminderRange = - rawTrialSession.isStartDateWithinNOTTReminderRange; - this.thirtyDaysBeforeTrialFormatted = - rawTrialSession.thirtyDaysBeforeTrialFormatted; this.proceedingType = rawTrialSession.proceedingType; this.sessionType = rawTrialSession.sessionType; this.startDate = rawTrialSession.startDate; diff --git a/shared/src/business/entities/trialSessions/TrialSession.noticeOfTrialReminder.test.ts b/shared/src/business/entities/trialSessions/TrialSession.noticeOfTrialReminder.test.ts index 3e0ff04614b..da856eb91c8 100644 --- a/shared/src/business/entities/trialSessions/TrialSession.noticeOfTrialReminder.test.ts +++ b/shared/src/business/entities/trialSessions/TrialSession.noticeOfTrialReminder.test.ts @@ -20,17 +20,22 @@ describe('TrialSession entity', () => { { daysFromToday: 34, expectedOutput: true }, { daysFromToday: 35, expectedOutput: false }, ]; - it('should set isStartDateWithinNOTTReminderRange to false when the trial session is not calendared', () => { + it('should return false when the trial session is not calendared', () => { const trialSession = new TrialSession({ ...MOCK_TRIAL_REGULAR, isCalendared: false, }); - expect(trialSession.isStartDateWithinNOTTReminderRange).toBe(false); + expect( + TrialSession.isStartDateWithinNOTTReminderRange({ + isCalendared: trialSession.isCalendared, + startDate: trialSession.startDate, + }), + ).toBe(false); }); tests.forEach(({ daysFromToday, expectedOutput }) => { - it(`should set isStartDateWithinNOTTReminderRange to ${expectedOutput} when the trial session is calendared and the start date is ${daysFromToday} days from today`, () => { + it(`should return ${expectedOutput} when the trial session is calendared and the start date is ${daysFromToday} days from today`, () => { const thirtyDaysFromToday = today.plus({ ['days']: daysFromToday }); const trialSession = new TrialSession({ @@ -39,9 +44,12 @@ describe('TrialSession entity', () => { startDate: thirtyDaysFromToday, }); - expect(trialSession.isStartDateWithinNOTTReminderRange).toBe( - expectedOutput, - ); + expect( + TrialSession.isStartDateWithinNOTTReminderRange({ + isCalendared: trialSession.isCalendared, + startDate: trialSession.startDate, + }), + ).toBe(expectedOutput); }); }); }); diff --git a/shared/src/business/entities/trialSessions/TrialSession.thirtyDaysBeforeTrialFormatted.test.ts b/shared/src/business/entities/trialSessions/TrialSession.thirtyDaysBeforeTrialFormatted.test.ts deleted file mode 100644 index 4e4c6d00d6e..00000000000 --- a/shared/src/business/entities/trialSessions/TrialSession.thirtyDaysBeforeTrialFormatted.test.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { MOCK_TRIAL_REGULAR } from '../../../test/mockTrial'; -import { TrialSession } from './TrialSession'; - -describe('TrialSession entity', () => { - describe('thirtyDaysBeforeTrialFormatted', () => { - // this is how the court was calculating the duration days between dates - // https://www.timeanddate.com/date/durationresult.html?m1=5&d1=17&y1=2023&m2=6&d2=15&y2=2023&ti=on - it("should set thirtyDaysBeforeTrialFormatted to 30 days prior to the trial's startDate, inclusive of the startDate, when the trial session is calendared", () => { - const trialSession = new TrialSession({ - ...MOCK_TRIAL_REGULAR, - isCalendared: true, - startDate: '2023-06-15', - }); - - expect(trialSession.thirtyDaysBeforeTrialFormatted).toBe('05/17/23'); - }); - }); -}); diff --git a/shared/src/business/entities/trialSessions/TrialSession.ts b/shared/src/business/entities/trialSessions/TrialSession.ts index 570050287da..bba85f21107 100644 --- a/shared/src/business/entities/trialSessions/TrialSession.ts +++ b/shared/src/business/entities/trialSessions/TrialSession.ts @@ -87,7 +87,6 @@ export class TrialSession extends JoiValidationEntity { public irsCalendarAdministratorInfo?: RawIrsCalendarAdministratorInfo; public isCalendared: boolean; public isClosed?: boolean; - public isStartDateWithinNOTTReminderRange?: boolean; public joinPhoneNumber?: string; public judge?: TJudge; public maxCases?: number; @@ -107,7 +106,6 @@ export class TrialSession extends JoiValidationEntity { public swingSessionId?: string; public term: string; public termYear: string; - public thirtyDaysBeforeTrialFormatted?: string; public trialClerk?: TTrialClerk; public trialLocation?: string; public trialSessionId?: string; @@ -198,12 +196,6 @@ export class TrialSession extends JoiValidationEntity { }; } - if (rawSession.isCalendared && rawSession.startDate) { - this.setNoticeOfTrialReminderAlert(); - } else { - this.isStartDateWithinNOTTReminderRange = false; - } - if (rawSession.trialClerk && rawSession.trialClerk.name) { this.trialClerk = { name: rawSession.trialClerk.name, @@ -216,6 +208,33 @@ export class TrialSession extends JoiValidationEntity { return sessionScope === TRIAL_SESSION_SCOPE_TYPES.standaloneRemote; } + static isStartDateWithinNOTTReminderRange({ + isCalendared, + startDate, + }: { + isCalendared?: boolean; + startDate?: string; + }): boolean { + if (!isCalendared || !startDate) { + return false; + } + + const formattedStartDate = formatDateString(startDate, FORMATS.MMDDYY); + const trialStartDateString = prepareDateFromString( + formattedStartDate, + FORMATS.MMDDYY, + ); + + return isTodayWithinGivenInterval({ + intervalEndDate: trialStartDateString.minus({ + ['days']: 24, // luxon's interval end date is not inclusive + }), + intervalStartDate: trialStartDateString.minus({ + ['days']: 34, + }), + }); + } + static validationRules = { COMMON: { address1: JoiValidationConstants.STRING.max(100).allow('').optional(), @@ -406,32 +425,6 @@ export class TrialSession extends JoiValidationEntity { return skPrefix; } - setNoticeOfTrialReminderAlert() { - const formattedStartDate = formatDateString(this.startDate, FORMATS.MMDDYY); - const trialStartDateString: any = prepareDateFromString( - formattedStartDate, - FORMATS.MMDDYY, - ); - - this.isStartDateWithinNOTTReminderRange = isTodayWithinGivenInterval({ - intervalEndDate: trialStartDateString.minus({ - ['days']: 24, // luxon's interval end date is not inclusive - }), - intervalStartDate: trialStartDateString.minus({ - ['days']: 34, - }), - }); - - const thirtyDaysBeforeTrialInclusive: any = trialStartDateString.minus({ - ['days']: 29, - }); - - this.thirtyDaysBeforeTrialFormatted = formatDateString( - thirtyDaysBeforeTrialInclusive, - FORMATS.MMDDYY, - ); - } - setAsCalendared() { this.isCalendared = true; this.sessionStatus = SESSION_STATUS_TYPES.open; diff --git a/shared/src/business/utilities/DateHandler.ts b/shared/src/business/utilities/DateHandler.ts index cc4641bec08..a62f1a23a2e 100644 --- a/shared/src/business/utilities/DateHandler.ts +++ b/shared/src/business/utilities/DateHandler.ts @@ -233,7 +233,7 @@ export const createISODateStringFromObject = options => { * @returns {string} a formatted date string */ export const formatDateString = ( - dateString: string, + dateString: string | undefined, formatArg: TimeFormatNames | TimeFormats = FORMATS.ISO, ): string => { if (!dateString) return ''; diff --git a/shared/src/proxies/trialSessions/getTrialSessionsProxy.ts b/shared/src/proxies/trialSessions/getTrialSessionsProxy.ts index b561ff5be22..56456ad8c74 100644 --- a/shared/src/proxies/trialSessions/getTrialSessionsProxy.ts +++ b/shared/src/proxies/trialSessions/getTrialSessionsProxy.ts @@ -1,3 +1,4 @@ +import { TrialSessionInfoDTO } from '@shared/business/dto/trialSessions/TrialSessionInfoDTO'; import { get } from '../requests'; /** @@ -6,7 +7,9 @@ import { get } from '../requests'; * @param {object} applicationContext the application context * @returns {Promise<*>} the promise of the api call */ -export const getTrialSessionsInteractor = applicationContext => { +export const getTrialSessionsInteractor = ( + applicationContext, +): Promise => { return get({ applicationContext, endpoint: '/trial-sessions', diff --git a/web-api/src/business/useCases/trialSessions/getTrialSessionsInteractor.test.ts b/web-api/src/business/useCases/trialSessions/getTrialSessionsInteractor.test.ts index 60e283bdd13..a6784a3fc35 100644 --- a/web-api/src/business/useCases/trialSessions/getTrialSessionsInteractor.test.ts +++ b/web-api/src/business/useCases/trialSessions/getTrialSessionsInteractor.test.ts @@ -10,7 +10,6 @@ import { mockPetitionerUser, mockPetitionsClerkUser, } from '@shared/test/mockAuthUsers'; -import { omit } from 'lodash'; describe('getTrialSessionsInteractor', () => { it('should throw an unauthorized error when the user does not have permission to view trial sessions', async () => { @@ -19,19 +18,7 @@ describe('getTrialSessionsInteractor', () => { ).rejects.toThrow(new UnauthorizedError('Unauthorized')); }); - it('should throw an error when the entity returned from persistence is invalid', async () => { - applicationContext - .getPersistenceGateway() - .getTrialSessions.mockResolvedValue([ - omit(MOCK_TRIAL_INPERSON, 'maxCases'), - ]); - - await expect( - getTrialSessionsInteractor(applicationContext, mockPetitionsClerkUser), - ).rejects.toThrow('The TrialSession entity was invalid.'); - }); - - it('should return a list of validated trial sessions', async () => { + it('should return a list of trial sessions', async () => { applicationContext .getPersistenceGateway() .getTrialSessions.mockResolvedValue([ diff --git a/web-api/src/business/useCases/trialSessions/getTrialSessionsInteractor.ts b/web-api/src/business/useCases/trialSessions/getTrialSessionsInteractor.ts index 854d6a181f6..54491f7e430 100644 --- a/web-api/src/business/useCases/trialSessions/getTrialSessionsInteractor.ts +++ b/web-api/src/business/useCases/trialSessions/getTrialSessionsInteractor.ts @@ -3,7 +3,7 @@ import { isAuthorized, } from '../../../../../shared/src/authorization/authorizationClientService'; import { ServerApplicationContext } from '@web-api/applicationContext'; -import { TrialSession } from '../../../../../shared/src/business/entities/trialSessions/TrialSession'; +import { TrialSession } from '@shared/business/entities/trialSessions/TrialSession'; import { TrialSessionInfoDTO } from '../../../../../shared/src/business/dto/trialSessions/TrialSessionInfoDTO'; import { UnauthorizedError } from '@web-api/errors/errors'; import { UnknownAuthUser } from '@shared/business/entities/authUser/AuthUser'; @@ -22,9 +22,7 @@ export const getTrialSessionsInteractor = async ( applicationContext, }); - const validatedSessions = TrialSession.validateRawCollection(trialSessions); - - return validatedSessions.map( - trialSession => new TrialSessionInfoDTO(trialSession), - ); + return trialSessions + .map(t => new TrialSession(t).toRawObject()) + .map(trialSession => new TrialSessionInfoDTO(trialSession)); }; diff --git a/web-api/storage/fixtures/seed/efcms-local.json b/web-api/storage/fixtures/seed/efcms-local.json index eb09b76e742..4175e7a6c73 100644 --- a/web-api/storage/fixtures/seed/efcms-local.json +++ b/web-api/storage/fixtures/seed/efcms-local.json @@ -50345,7 +50345,7 @@ { "caseOrder": [], "gsi1pk": "trial-session-catalog", - "sessionStatus": "New", + "sessionStatus": "Open", "trialLocation": "Denver, Colorado", "proceedingType": "In Person", "createdAt": "2019-11-02T05:00:00.000Z", @@ -50362,7 +50362,7 @@ "startDate": "2019-12-02T05:00:00.000Z", "maxCases": 30, "trialSessionId": "0d943468-bc2e-4631-84e3-b084cf5b1fbb", - "isCalendared": false, + "isCalendared": true, "status": "Closed" }, { diff --git a/web-client/integration-tests/dismissNOTTReminder.test.ts b/web-client/integration-tests/dismissNOTTReminder.test.ts index 8314bc5e827..33c6ed0dcf2 100644 --- a/web-client/integration-tests/dismissNOTTReminder.test.ts +++ b/web-client/integration-tests/dismissNOTTReminder.test.ts @@ -2,7 +2,10 @@ import { SESSION_TYPES } from '../../shared/src/business/entities/EntityConstant import { docketClerkCreatesATrialSession } from './journey/docketClerkCreatesATrialSession'; import { docketClerkViewsTrialSessionsTab } from './journey/docketClerkViewsTrialSessionsTab'; import { formattedTrialSessionDetails } from '../src/presenter/computeds/formattedTrialSessionDetails'; -import { formattedTrialSessions } from '../src/presenter/computeds/formattedTrialSessions'; +import { + isTrialSessionRow, + trialSessionsHelper as trialSessionsHelperComputed, +} from '@web-client/presenter/computeds/trialSessionsHelper'; import { loginAs, setupTest } from './helpers'; import { petitionsClerkSetsATrialSessionsSchedule } from './journey/petitionsClerkSetsATrialSessionsSchedule'; import { petitionsClerkViewsNewTrialSession } from './journey/petitionsClerkViewsNewTrialSession'; @@ -55,38 +58,27 @@ describe('Dismiss NOTT reminder on calendared trial session within 30-35 day ran loginAs(cerebralTest, 'docketclerk@example.com'); it('should see the NOTT reminder icon on the trial session list', async () => { await cerebralTest.runSequence('gotoTrialSessionsSequence'); + await cerebralTest.runSequence('setTrialSessionsFiltersSequence', { + currentTab: 'calendared', + }); expect(cerebralTest.getState('currentPage')).toEqual('TrialSessions'); - const trialSessionFormatted: any = runCompute( - withAppContextDecorator(formattedTrialSessions), - { - state: cerebralTest.getState(), - }, + const trialSessionsHelper = withAppContextDecorator( + trialSessionsHelperComputed, ); - - const filteredSessions: any[] = - trialSessionFormatted.filteredTrialSessions['Open']; - - let foundSession; - filteredSessions.some(trialSession => { - trialSession.sessions.some(session => { - if ( - session.trialSessionId === cerebralTest.lastCreatedTrialSessionId - ) { - foundSession = session; - return true; - } - }); - if (foundSession) { - return true; - } + const helper = runCompute(trialSessionsHelper, { + state: cerebralTest.getState(), }); + const foundSession = helper.trialSessionRows + .filter(isTrialSessionRow) + .find(t => t.trialSessionId === cerebralTest.lastCreatedTrialSessionId); + expect(foundSession).toBeDefined(); - expect(foundSession.showAlertForNOTTReminder).toEqual(true); - expect(foundSession.alertMessageForNOTT).toEqual( - `The 30-day notice is due by ${foundSession.thirtyDaysBeforeTrialFormatted}`, + expect(foundSession?.showAlertForNOTTReminder).toEqual(true); + expect(foundSession?.alertMessageForNOTT).toContain( + 'The 30-day notice is due by', ); }); @@ -104,8 +96,11 @@ describe('Dismiss NOTT reminder on calendared trial session within 30-35 day ran }, ); - expect(trialSessionDetailsFormatted.alertMessageForNOTT).toEqual( - `30-day trial notices are due by ${trialSessionDetailsFormatted.thirtyDaysBeforeTrialFormatted}. Have notices been served?`, + expect(trialSessionDetailsFormatted.alertMessageForNOTT).toContain( + '30-day trial notices are due by', + ); + expect(trialSessionDetailsFormatted.alertMessageForNOTT).toContain( + 'Have notices been served?', ); let trialSessionDetailsHelperComputed: any = runCompute( @@ -124,16 +119,9 @@ describe('Dismiss NOTT reminder on calendared trial session within 30-35 day ran describe('Petitions clerk views calendared trial session in trial session list', () => { loginAs(cerebralTest, 'petitionsclerk@example.com'); it('should go to the created trial session', async () => { - await cerebralTest.runSequence('gotoTrialSessionsSequence', { - query: { - status: 'Open', - }, - }); + await cerebralTest.runSequence('gotoTrialSessionsSequence'); expect(cerebralTest.getState('currentPage')).toEqual('TrialSessions'); - expect( - cerebralTest.getState('screenMetadata.trialSessionFilters.status'), - ).toEqual('Open'); }); it('should see the alert banner in the latest trial session and can clear it', async () => { @@ -148,8 +136,11 @@ describe('Dismiss NOTT reminder on calendared trial session within 30-35 day ran }, ); - expect(trialSessionDetailsFormatted.alertMessageForNOTT).toEqual( - `30-day trial notices are due by ${trialSessionDetailsFormatted.thirtyDaysBeforeTrialFormatted}. Have notices been served?`, + expect(trialSessionDetailsFormatted.alertMessageForNOTT).toContain( + '30-day trial notices are due by', + ); + expect(trialSessionDetailsFormatted.alertMessageForNOTT).toContain( + 'Have notices been served?', ); let trialSessionDetailsHelperComputed: any = runCompute( diff --git a/web-client/integration-tests/docketClerkViewsTrialSessionTabs.test.ts b/web-client/integration-tests/docketClerkViewsTrialSessionTabs.test.ts index bc4a582c250..92e0e24de72 100644 --- a/web-client/integration-tests/docketClerkViewsTrialSessionTabs.test.ts +++ b/web-client/integration-tests/docketClerkViewsTrialSessionTabs.test.ts @@ -47,7 +47,10 @@ describe('Docket Clerk Views Trial Session Tabs', () => { docketClerkCreatesATrialSession(cerebralTest, overrides); docketClerkViewsTrialSessionList(cerebralTest); // Trial Session should exist in New tab - docketClerkViewsTrialSessionsTab(cerebralTest, { tab: 'New' }); + docketClerkViewsTrialSessionsTab(cerebralTest, { + sessionStatus: 'All', + tab: 'new', + }); for (let i = 0; i < caseCount; i++) { const id = i + 1; @@ -65,15 +68,24 @@ describe('Docket Clerk Views Trial Session Tabs', () => { petitionsClerkSetsATrialSessionsSchedule(cerebralTest); loginAs(cerebralTest, 'docketclerk@example.com'); - // Trial Session should exist in Open tab - docketClerkViewsTrialSessionsTab(cerebralTest, { tab: 'Open' }); + // Trial Session should exist in Open + docketClerkViewsTrialSessionsTab(cerebralTest, { + sessionStatus: 'Open', + tab: 'calendared', + }); loginAs(cerebralTest, 'petitionsclerk@example.com'); petitionsClerkManuallyRemovesCaseFromTrial(cerebralTest); loginAs(cerebralTest, 'docketclerk@example.com'); - // Trial Session should exist in Closed tab - docketClerkViewsTrialSessionsTab(cerebralTest, { tab: 'Closed' }); - // Trial Session should exist in All tab - docketClerkViewsTrialSessionsTab(cerebralTest, { tab: 'All' }); + // Trial Session should exist in Closed + docketClerkViewsTrialSessionsTab(cerebralTest, { + sessionStatus: 'Closed', + tab: 'calendared', + }); + // Trial Session should exist in All + docketClerkViewsTrialSessionsTab(cerebralTest, { + sessionStatus: 'All', + tab: 'calendared', + }); }); diff --git a/web-client/integration-tests/journey/docketClerkClosesStandaloneRemoteTrialSession.ts b/web-client/integration-tests/journey/docketClerkClosesStandaloneRemoteTrialSession.ts index 1048584bf6d..2bedb3e3b6f 100644 --- a/web-client/integration-tests/journey/docketClerkClosesStandaloneRemoteTrialSession.ts +++ b/web-client/integration-tests/journey/docketClerkClosesStandaloneRemoteTrialSession.ts @@ -1,12 +1,4 @@ -import { formattedTrialSessions as formattedTrialSessionsComputed } from '../../src/presenter/computeds/formattedTrialSessions'; -import { runCompute } from '@web-client/presenter/test.cerebral'; -import { withAppContextDecorator } from '../../src/withAppContext'; - -const formattedTrialSessions = withAppContextDecorator( - formattedTrialSessionsComputed, -); - -const status = 'Closed'; +import { SESSION_STATUS_TYPES } from '@shared/business/entities/EntityConstants'; export const docketClerkClosesStandaloneRemoteTrialSession = cerebralTest => { return it('Docket Clerk closes the trial session', async () => { @@ -17,25 +9,13 @@ export const docketClerkClosesStandaloneRemoteTrialSession = cerebralTest => { await cerebralTest.runSequence('closeTrialSessionSequence'); - const formatted = runCompute(formattedTrialSessions, { - state: cerebralTest.getState(), - }); - - const filteredSessions = formatted.filteredTrialSessions[status]; - - let foundSession; - filteredSessions.some(trialSession => { - trialSession.sessions.some(session => { - if (session.trialSessionId === cerebralTest.lastCreatedTrialSessionId) { - foundSession = session; - return true; - } - }); - if (foundSession) { - return true; - } - }); + const { trialSessions } = cerebralTest.getState().trialSessionsPage; + expect(trialSessions.length).toBeGreaterThan(0); + const foundSession = trialSessions.find( + t => t.trialSessionId === cerebralTest.lastCreatedTrialSessionId, + ); expect(foundSession).toBeTruthy(); + expect(foundSession.sessionStatus).toEqual(SESSION_STATUS_TYPES.closed); }); }; diff --git a/web-client/integration-tests/journey/docketClerkVerifiesSessionIsNotClosed.ts b/web-client/integration-tests/journey/docketClerkVerifiesSessionIsNotClosed.ts index 32ba9d3a597..9bbd194db78 100644 --- a/web-client/integration-tests/journey/docketClerkVerifiesSessionIsNotClosed.ts +++ b/web-client/integration-tests/journey/docketClerkVerifiesSessionIsNotClosed.ts @@ -1,45 +1,15 @@ -import { formattedTrialSessions as formattedTrialSessionsComputed } from '../../src/presenter/computeds/formattedTrialSessions'; -import { runCompute } from '@web-client/presenter/test.cerebral'; -import { withAppContextDecorator } from '../../src/withAppContext'; - -const formattedTrialSessions = withAppContextDecorator( - formattedTrialSessionsComputed, -); - -const status = 'Closed'; +import { SESSION_STATUS_TYPES } from '@shared/business/entities/EntityConstants'; export const docketClerkVerifiesSessionIsNotClosed = cerebralTest => { return it('Docket Clerk verifies session is not closed', async () => { - await cerebralTest.runSequence('gotoTrialSessionsSequence', { - query: { - status, - }, - }); + await cerebralTest.runSequence('gotoTrialSessionsSequence'); expect(cerebralTest.getState('currentPage')).toEqual('TrialSessions'); - expect( - cerebralTest.getState('screenMetadata.trialSessionFilters.status'), - ).toEqual(status); - - const formatted = runCompute(formattedTrialSessions, { - state: cerebralTest.getState(), - }); - - const filteredSessions = formatted.filteredTrialSessions[status]; - - let foundSession; - filteredSessions.some(trialSession => { - trialSession.sessions.some(session => { - if (session.trialSessionId === cerebralTest.lastCreatedTrialSessionId) { - foundSession = session; - return true; - } - }); - if (foundSession) { - return true; - } - }); - expect(foundSession).toBeFalsy(); + const { trialSessions } = cerebralTest.getState().trialSessionsPage; + const foundSession = trialSessions.find( + t => t.trialSessionId === cerebralTest.lastCreatedTrialSessionId, + ); + expect(foundSession.sessionStatus).not.toEqual(SESSION_STATUS_TYPES.closed); }); }; diff --git a/web-client/integration-tests/journey/docketClerkViewsTrialSessionList.ts b/web-client/integration-tests/journey/docketClerkViewsTrialSessionList.ts index 8c480767d8d..a72b777ca71 100644 --- a/web-client/integration-tests/journey/docketClerkViewsTrialSessionList.ts +++ b/web-client/integration-tests/journey/docketClerkViewsTrialSessionList.ts @@ -1,11 +1,4 @@ import { find } from 'lodash'; -import { formattedTrialSessions as formattedTrialSessionsComputed } from '../../src/presenter/computeds/formattedTrialSessions'; -import { runCompute } from '@web-client/presenter/test.cerebral'; -import { withAppContextDecorator } from '../../src/withAppContext'; - -const formattedTrialSessions = withAppContextDecorator( - formattedTrialSessionsComputed, -); export const docketClerkViewsTrialSessionList = ( cerebralTest, @@ -15,12 +8,11 @@ export const docketClerkViewsTrialSessionList = ( await cerebralTest.runSequence('gotoTrialSessionsSequence'); expect(cerebralTest.getState('currentPage')).toEqual('TrialSessions'); - const formatted = runCompute(formattedTrialSessions, { - state: cerebralTest.getState(), - }); - expect(formatted.formattedSessions.length).toBeGreaterThan(0); + const { trialSessions } = cerebralTest.getState().trialSessionsPage; + + expect(trialSessions.length).toBeGreaterThan(0); - const trialSession = find(formatted.sessionsByTerm, { + const trialSession = find(trialSessions, { trialSessionId: cerebralTest.lastCreatedTrialSessionId, }); diff --git a/web-client/integration-tests/journey/docketClerkViewsTrialSessionsTab.ts b/web-client/integration-tests/journey/docketClerkViewsTrialSessionsTab.ts index a4ff7c04e2e..7b71a99491f 100644 --- a/web-client/integration-tests/journey/docketClerkViewsTrialSessionsTab.ts +++ b/web-client/integration-tests/journey/docketClerkViewsTrialSessionsTab.ts @@ -1,35 +1,32 @@ -import { formattedTrialSessions as formattedTrialSessionsComputed } from '../../src/presenter/computeds/formattedTrialSessions'; +import { SESSION_STATUS_TYPES } from '@shared/business/entities/EntityConstants'; +import { + isTrialSessionRow, + trialSessionsHelper as trialSessionsHelperComputed, +} from '@web-client/presenter/computeds/trialSessionsHelper'; import { runCompute } from '@web-client/presenter/test.cerebral'; -import { trialSessionsHelper as trialSessionsHelperComputed } from '../../src/presenter/computeds/trialSessionsHelper'; import { withAppContextDecorator } from '../../src/withAppContext'; -const formattedTrialSessions = withAppContextDecorator( - formattedTrialSessionsComputed, -); - export const docketClerkViewsTrialSessionsTab = ( cerebralTest: any, - overrides: { tab?: string } = { tab: undefined }, + overrides: { + tab?: 'calendared' | 'new'; + sessionStatus?: 'Closed' | 'Open' | 'All'; + } = { + sessionStatus: 'Open', + tab: 'calendared', + }, ) => { - const status = overrides.tab || 'Open'; - return it(`Docket clerk views ${status} Trial Sessions tab`, async () => { - // resetting view metadata to counteract the fact that state is not being reset on login as it would be outside of a test - cerebralTest.setState('currentViewMetadata.trialSessions.tab', undefined); - - await cerebralTest.runSequence('gotoTrialSessionsSequence', { - query: { - status, - }, + const { tab } = overrides; + return it(`Docket clerk views ${tab} Trial Sessions tab`, async () => { + await cerebralTest.runSequence('gotoTrialSessionsSequence'); + await cerebralTest.runSequence('setTrialSessionsFiltersSequence', { + currentTab: tab, + }); + await cerebralTest.runSequence('setTrialSessionsFiltersSequence', { + sessionStatus: overrides.sessionStatus, }); expect(cerebralTest.getState('currentPage')).toEqual('TrialSessions'); - expect( - cerebralTest.getState('screenMetadata.trialSessionFilters.status'), - ).toEqual(status); - - const formatted = runCompute(formattedTrialSessions, { - state: cerebralTest.getState(), - }); const trialSessionsHelper = withAppContextDecorator( trialSessionsHelperComputed, @@ -43,26 +40,18 @@ export const docketClerkViewsTrialSessionsTab = ( judge => judge.role === 'legacyJudge', ); - if (status === 'Closed' || status === 'All') { - expect(legacyJudge).toBeTruthy(); - } else { + if ( + tab === 'new' || + overrides.sessionStatus === SESSION_STATUS_TYPES.open + ) { expect(legacyJudge).toBeFalsy(); + } else { + expect(legacyJudge).toBeTruthy(); } - const filteredSessions = formatted.filteredTrialSessions[status]; - - let foundSession; - filteredSessions.some(trialSession => { - trialSession.sessions.some(session => { - if (session.trialSessionId === cerebralTest.trialSessionId) { - foundSession = session; - return true; - } - }); - if (foundSession) { - return true; - } - }); + const foundSession = helper.trialSessionRows + .filter(isTrialSessionRow) + .find(t => t.trialSessionId === cerebralTest.trialSessionId); expect(foundSession).toBeTruthy(); }); diff --git a/web-client/integration-tests/serveNOTTsFromReminder.test.ts b/web-client/integration-tests/serveNOTTsFromReminder.test.ts index 3f380a9b8c8..298b9496e83 100644 --- a/web-client/integration-tests/serveNOTTsFromReminder.test.ts +++ b/web-client/integration-tests/serveNOTTsFromReminder.test.ts @@ -107,15 +107,18 @@ describe('Serve NOTTs from reminder on calendared trial session detail page', () trialSessionId: cerebralTest.trialSessionId, }); - const trialSessionDetailsFormatted: any = runCompute( + const trialSessionDetailsFormatted = runCompute( withAppContextDecorator(formattedTrialSessionDetails), { state: cerebralTest.getState(), }, ); - expect(trialSessionDetailsFormatted.alertMessageForNOTT).toEqual( - `30-day trial notices are due by ${trialSessionDetailsFormatted.thirtyDaysBeforeTrialFormatted}. Have notices been served?`, + expect(trialSessionDetailsFormatted.alertMessageForNOTT).toContain( + '30-day trial notices are due by', + ); + expect(trialSessionDetailsFormatted.alertMessageForNOTT).toContain( + 'Have notices been served?', ); }); diff --git a/web-client/src/presenter/actions/Login/resetToBaseStateAction.test.ts b/web-client/src/presenter/actions/Login/resetToBaseStateAction.test.ts new file mode 100644 index 00000000000..0c175f35fee --- /dev/null +++ b/web-client/src/presenter/actions/Login/resetToBaseStateAction.test.ts @@ -0,0 +1,28 @@ +import { baseState } from '@web-client/presenter/state'; +import { resetToBaseStateAction } from '@web-client/presenter/actions/Login/resetToBaseStateAction'; +import { runAction } from '@web-client/presenter/test.cerebral'; + +describe('resetToBaseStateAction', () => { + it('should reset state to baseState except for a few state slices', async () => { + const stateThatShouldNotBeReset = { + featureFlags: {}, + header: {}, + idleLogoutState: {}, + idleStatus: {}, + lastIdleAction: {}, + maintenanceMode: {}, + }; + + const result = await runAction(resetToBaseStateAction, { + state: { + ...stateThatShouldNotBeReset, + trialSessionsPage: { stuff: 'this should be reset' }, + }, + }); + + expect(result.state).toEqual({ + ...baseState, + ...stateThatShouldNotBeReset, + }); + }); +}); diff --git a/web-client/src/presenter/actions/Login/resetToBaseStateAction.ts b/web-client/src/presenter/actions/Login/resetToBaseStateAction.ts new file mode 100644 index 00000000000..061feac1fe6 --- /dev/null +++ b/web-client/src/presenter/actions/Login/resetToBaseStateAction.ts @@ -0,0 +1,18 @@ +import { baseState } from '@web-client/presenter/state'; +import { cloneDeep } from 'lodash'; +import { state } from '@web-client/presenter/app.cerebral'; + +export const resetToBaseStateAction = ({ store }: ActionProps) => { + Object.entries(cloneDeep(baseState)).forEach(([key, value]) => { + const stateSlicesToPersist = [ + 'maintenanceMode', + 'featureFlags', + 'idleLogoutState', + 'idleStatus', + 'lastIdleAction', + 'header', + ]; + if (stateSlicesToPersist.includes(key)) return; + store.set(state[key], value); + }); +}; diff --git a/web-client/src/presenter/actions/TrialSession/resetTrialSessionsFiltersAction.test.ts b/web-client/src/presenter/actions/TrialSession/resetTrialSessionsFiltersAction.test.ts new file mode 100644 index 00000000000..bd3bd8e4239 --- /dev/null +++ b/web-client/src/presenter/actions/TrialSession/resetTrialSessionsFiltersAction.test.ts @@ -0,0 +1,15 @@ +import { initialTrialSessionPageState } from '@web-client/presenter/state/trialSessionsPageState'; +import { resetTrialSessionsFiltersAction } from '@web-client/presenter/actions/TrialSession/resetTrialSessionsFiltersAction'; +import { runAction } from '@web-client/presenter/test.cerebral'; + +describe('resetTrialSessionsFiltersAction', () => { + it('should reset the trialSessions filters', async () => { + const result = await runAction(resetTrialSessionsFiltersAction, { + state: {}, + }); + + expect(result.state.trialSessionsPage.filters).toEqual( + initialTrialSessionPageState.filters, + ); + }); +}); diff --git a/web-client/src/presenter/actions/TrialSession/resetTrialSessionsFiltersAction.ts b/web-client/src/presenter/actions/TrialSession/resetTrialSessionsFiltersAction.ts new file mode 100644 index 00000000000..ae70b2b33e2 --- /dev/null +++ b/web-client/src/presenter/actions/TrialSession/resetTrialSessionsFiltersAction.ts @@ -0,0 +1,17 @@ +import { ResetTrialSessionsFiltersSequence } from '@web-client/presenter/sequences/resetTrialSessionsFiltersSequence'; +import { cloneDeep } from 'lodash'; +import { initialTrialSessionPageState } from '@web-client/presenter/state/trialSessionsPageState'; +import { state } from '@web-client/presenter/app.cerebral'; + +export const resetTrialSessionsFiltersAction = ({ + props, + store, +}: ActionProps) => { + store.set( + state.trialSessionsPage.filters, + cloneDeep(initialTrialSessionPageState.filters), + ); + if (props?.currentTab) { + store.set(state.trialSessionsPage.filters.currentTab, props.currentTab); + } +}; diff --git a/web-client/src/presenter/actions/TrialSession/setActiveTrialSessionsTabAction.test.ts b/web-client/src/presenter/actions/TrialSession/setActiveTrialSessionsTabAction.test.ts index b1358480693..c1a64879083 100644 --- a/web-client/src/presenter/actions/TrialSession/setActiveTrialSessionsTabAction.test.ts +++ b/web-client/src/presenter/actions/TrialSession/setActiveTrialSessionsTabAction.test.ts @@ -1,27 +1,12 @@ -import { TRIAL_SESSION_SCOPE_TYPES } from '@shared/business/entities/EntityConstants'; import { runAction } from '@web-client/presenter/test.cerebral'; import { setActiveTrialSessionsTabAction } from '@web-client/presenter/actions/TrialSession/setActiveTrialSessionsTabAction'; describe('setActiveTrialSessionsTabAction', () => { - it('sets state.currentViewMetadata.tab to new when props.sessionScope location-based', async () => { + it('sets the trial sessions page tab to new after creating a trial session so the user can see their newly created trial sesison', async () => { const result = await runAction(setActiveTrialSessionsTabAction, { - props: { - sessionScope: TRIAL_SESSION_SCOPE_TYPES.locationBased, - }, state: {}, }); - expect(result.state.currentViewMetadata.trialSessions.tab).toEqual('new'); - }); - - it('sets state.currentViewMetadata.tab to open when props.sessionScope is not location-based', async () => { - const result = await runAction(setActiveTrialSessionsTabAction, { - props: { - sessionScope: TRIAL_SESSION_SCOPE_TYPES.standaloneRemote, - }, - state: {}, - }); - - expect(result.state.currentViewMetadata.trialSessions.tab).toEqual('open'); + expect(result.state.trialSessionsPage.filters.currentTab).toEqual('new'); }); }); diff --git a/web-client/src/presenter/actions/TrialSession/setActiveTrialSessionsTabAction.ts b/web-client/src/presenter/actions/TrialSession/setActiveTrialSessionsTabAction.ts index 6c1da46e62a..0d66660a724 100644 --- a/web-client/src/presenter/actions/TrialSession/setActiveTrialSessionsTabAction.ts +++ b/web-client/src/presenter/actions/TrialSession/setActiveTrialSessionsTabAction.ts @@ -1,17 +1,8 @@ -import { - TRIAL_SESSION_SCOPE_TYPES, - TrialSessionScope, -} from '@shared/business/entities/EntityConstants'; +import { TrialSessionScope } from '@shared/business/entities/EntityConstants'; import { state } from '@web-client/presenter/app.cerebral'; export const setActiveTrialSessionsTabAction = ({ - props, store, }: ActionProps<{ sessionScope: TrialSessionScope }>) => { - const activeTab = - props.sessionScope === TRIAL_SESSION_SCOPE_TYPES.locationBased - ? 'new' - : 'open'; - - store.set(state.currentViewMetadata.trialSessions.tab, activeTab); + store.set(state.trialSessionsPage.filters.currentTab, 'new'); }; diff --git a/web-client/src/presenter/actions/TrialSession/setTrialSessionsFiltersAction.test.ts b/web-client/src/presenter/actions/TrialSession/setTrialSessionsFiltersAction.test.ts index dc70b6861ae..ff73e9884f1 100644 --- a/web-client/src/presenter/actions/TrialSession/setTrialSessionsFiltersAction.test.ts +++ b/web-client/src/presenter/actions/TrialSession/setTrialSessionsFiltersAction.test.ts @@ -1,23 +1,16 @@ -import { presenter } from '../../presenter-mock'; import { runAction } from '@web-client/presenter/test.cerebral'; import { setTrialSessionsFiltersAction } from './setTrialSessionsFiltersAction'; describe('setTrialSessionsFiltersAction', () => { it('call the use case to get the eligible cases', async () => { const result = await runAction(setTrialSessionsFiltersAction, { - modules: { - presenter, - }, props: { - query: { - trialLocation: 'Baton Rouge, Louisiana', - trialSessionId: '123', - }, + trialLocation: 'Baton Rouge, Louisiana', }, - state: { screenMetadata: {} }, - }); - expect(result.state.screenMetadata.trialSessionFilters).toEqual({ - trialLocation: 'Baton Rouge, Louisiana', }); + + expect(result.state.trialSessionsPage.filters.trialLocations).toEqual( + 'Baton Rouge, Louisiana', + ); }); }); diff --git a/web-client/src/presenter/actions/TrialSession/setTrialSessionsFiltersAction.ts b/web-client/src/presenter/actions/TrialSession/setTrialSessionsFiltersAction.ts index d392240b603..1e32cb67da6 100644 --- a/web-client/src/presenter/actions/TrialSession/setTrialSessionsFiltersAction.ts +++ b/web-client/src/presenter/actions/TrialSession/setTrialSessionsFiltersAction.ts @@ -1,17 +1,97 @@ -import { pick } from 'lodash'; +import { + TrialSessionProceedingType, + TrialSessionTypes, +} from '@shared/business/entities/EntityConstants'; import { state } from '@web-client/presenter/app.cerebral'; -/** - * sets the state.screenMetadata.trialSessionFilters - * @param {object} providers the providers object - * @param {object} providers.props the cerebral props object containing the props.query - * @param {object} providers.store the cerebral store used for setting the state.screenMetadata.trialSessionFilters - */ + +export type SetTrialSessionsFilters = Partial<{ + currentTab: 'calendared' | 'new'; + judges: { + action: 'add' | 'remove'; + judge: { name: string; userId: string }; + }; + proceedingType: TrialSessionProceedingType | 'All'; + sessionStatus: string; + sessionTypes: { action: 'add' | 'remove'; sessionType: TrialSessionTypes }; + trialLocations: { + action: 'add' | 'remove'; + trialLocation: string; + }; + pageNumber: number; + startDate: string; + endDate: string; +}>; + export const setTrialSessionsFiltersAction = ({ + get, props, store, -}: ActionProps) => { - store.set( - state.screenMetadata.trialSessionFilters, - pick(props.query, ['trialLocation', 'judge', 'sessionType', 'status']), - ); +}: ActionProps) => { + const currentFilters = get(state.trialSessionsPage.filters); + + if (props.currentTab) { + store.set(state.trialSessionsPage.filters.currentTab, props.currentTab); + } + + if (props.judges) { + const { action, judge } = props.judges; + if (action === 'add') { + currentFilters.judges[judge.userId] = judge; + } else { + delete currentFilters.judges[judge.userId]; + } + + store.set(state.trialSessionsPage.filters.judges, currentFilters.judges); + } + + if (props.proceedingType) { + store.set( + state.trialSessionsPage.filters.proceedingType, + props.proceedingType, + ); + } + + if (props.sessionStatus) { + store.set( + state.trialSessionsPage.filters.sessionStatus, + props.sessionStatus, + ); + } + + if (props.sessionTypes) { + const { action, sessionType } = props.sessionTypes; + if (action === 'add') { + currentFilters.sessionTypes[sessionType] = sessionType; + } else { + delete currentFilters.sessionTypes[sessionType]; + } + + store.set( + state.trialSessionsPage.filters.sessionTypes, + currentFilters.sessionTypes, + ); + } + + if (props.trialLocations) { + const { action, trialLocation } = props.trialLocations; + if (action === 'add') { + currentFilters.trialLocations[trialLocation] = trialLocation; + } else { + delete currentFilters.trialLocations[trialLocation]; + } + + store.set( + state.trialSessionsPage.filters.trialLocations, + currentFilters.trialLocations, + ); + } + + if (props.startDate || props.startDate === '') { + store.set(state.trialSessionsPage.filters.startDate, props.startDate); + } + if (props.endDate || props.endDate === '') { + store.set(state.trialSessionsPage.filters.endDate, props.endDate); + } + + store.set(state.trialSessionsPage.filters.pageNumber, props.pageNumber || 0); // Always reset page number to 0 }; diff --git a/web-client/src/presenter/actions/TrialSession/setTrialSessionsPageAction.ts b/web-client/src/presenter/actions/TrialSession/setTrialSessionsPageAction.ts new file mode 100644 index 00000000000..72f2ee896e1 --- /dev/null +++ b/web-client/src/presenter/actions/TrialSession/setTrialSessionsPageAction.ts @@ -0,0 +1,5 @@ +import { state } from '@web-client/presenter/app.cerebral'; + +export const setTrialSessionsPageAction = ({ props, store }: ActionProps) => { + store.set(state.trialSessionsPage.trialSessions, props.trialSessions); +}; diff --git a/web-client/src/presenter/actions/clearLoginFormAction.test.ts b/web-client/src/presenter/actions/clearLoginFormAction.test.ts deleted file mode 100644 index 4a84513be8d..00000000000 --- a/web-client/src/presenter/actions/clearLoginFormAction.test.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { clearLoginFormAction } from './clearLoginFormAction'; -import { runAction } from '@web-client/presenter/test.cerebral'; - -describe('clearLoginFormAction', () => { - it('should reset the form state', async () => { - const result = await runAction(clearLoginFormAction, { - state: { - form: { - name: 'Joe', - nature: 'Exotic', - }, - }, - }); - - expect(result.state.form).toMatchObject({ - email: '', - }); - }); -}); diff --git a/web-client/src/presenter/actions/clearLoginFormAction.ts b/web-client/src/presenter/actions/clearLoginFormAction.ts deleted file mode 100644 index 676d1fe3370..00000000000 --- a/web-client/src/presenter/actions/clearLoginFormAction.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { state } from '@web-client/presenter/app.cerebral'; - -/** - * resets the form. - * state.form is used throughout the app for storing html form values - * @param {object} providers the providers object - * @param {object} providers.store the cerebral store object used for setting the form - */ -export const clearLoginFormAction = ({ store }: ActionProps) => { - store.set(state.form, { - email: '', - }); -}; diff --git a/web-client/src/presenter/computeds/TrialSession/addTrialSessionInformationHelper.test.ts b/web-client/src/presenter/computeds/TrialSession/addTrialSessionInformationHelper.test.ts index dbc6603e246..c863f19b0d2 100644 --- a/web-client/src/presenter/computeds/TrialSession/addTrialSessionInformationHelper.test.ts +++ b/web-client/src/presenter/computeds/TrialSession/addTrialSessionInformationHelper.test.ts @@ -1,13 +1,46 @@ import { + SESSION_STATUS_TYPES, TRIAL_SESSION_PROCEEDING_TYPES, TRIAL_SESSION_SCOPE_TYPES, } from '../../../../../shared/src/business/entities/EntityConstants'; +import { TrialSessionInfoDTO } from '@shared/business/dto/trialSessions/TrialSessionInfoDTO'; import { addTrialSessionInformationHelper as addTrialSessionInformationHelperComputed } from './addTrialSessionInformationHelper'; import { applicationContextForClient as applicationContext } from '@web-client/test/createClientTestApplicationContext'; +import { docketClerk1User } from '@shared/test/mockUsers'; import { runCompute } from '@web-client/presenter/test.cerebral'; import { withAppContextDecorator } from '../../../withAppContext'; describe('addTrialSessionInformationHelper', () => { + let trialSession1: TrialSessionInfoDTO; + let trialSession2: TrialSessionInfoDTO; + beforeEach(() => { + trialSession1 = { + isCalendared: true, + judge: { name: 'howdy', userId: '1' }, + proceedingType: 'Remote', + sessionScope: TRIAL_SESSION_SCOPE_TYPES.locationBased, + sessionStatus: 'Open', + sessionType: 'Regular', + startDate: '2022-03-01T21:00:00.000Z', + term: 'Winter', + termYear: '2022', + trialLocation: 'Boise', + trialSessionId: '43bc50b8-8b0b-47db-817b-a666af7a703e', + }; + trialSession2 = { + isCalendared: true, + judge: { name: 'howdy', userId: '2' }, + proceedingType: 'Remote', + sessionScope: TRIAL_SESSION_SCOPE_TYPES.locationBased, + sessionStatus: 'Open', + sessionType: 'Regular', + startDate: '2022-03-01T21:00:00.000Z', + term: 'Winter', + termYear: '2022', + trialLocation: 'Boise', + trialSessionId: '933ac8d9-68f0-4bfa-b7be-99c465c6799e', + }; + }); const addTrialSessionInformationHelper = withAppContextDecorator( addTrialSessionInformationHelperComputed, { @@ -171,4 +204,202 @@ describe('addTrialSessionInformationHelper', () => { expect(result.sessionTypes).toEqual(['Special', 'Motion/Hearing']); }); }); + + describe('showSwingSessionList', () => { + it('should show the swing session options list when the user has selected that their trial session is part of a swing session', () => { + const result = runCompute(addTrialSessionInformationHelper, { + state: { + form: { swingSession: true }, + user: docketClerk1User, + }, + }); + + expect(result.showSwingSessionList).toEqual(true); + }); + + it('should not show the swing session options list when the user has not selected that their trial session is part of a swing session', () => { + const result = runCompute(addTrialSessionInformationHelper, { + state: { + form: { swingSession: false }, + user: docketClerk1User, + }, + }); + + expect(result.showSwingSessionList).toEqual(false); + }); + }); + + describe('showSwingSessionOption', () => { + it('should show the option to associate the current trial session with another swing session when there are valid swing session options', () => { + const term = 'Fall'; + const termYear = '2020'; + trialSession1.term = term; + trialSession1.termYear = termYear; + trialSession1.sessionStatus = SESSION_STATUS_TYPES.open; + + const result = runCompute(addTrialSessionInformationHelper, { + state: { + form: { swingSession: true, term, termYear }, + trialSession: { + trialSessionId: '74f24014-2cf1-4e97-b80a-40f970d5376d', + }, + trialSessions: [trialSession1], + user: docketClerk1User, + }, + }); + + expect(result.showSwingSessionOption).toEqual(true); + }); + + it('should not show the option to associate the current trial session with another swing session when there are no valid swing session options', () => { + const term = 'Fall'; + const termYear = '2020'; + + const result = runCompute(addTrialSessionInformationHelper, { + state: { + form: { swingSession: true, term, termYear }, + trialSession: { + trialSessionId: '74f24014-2cf1-4e97-b80a-40f970d5376d', + }, + trialSessions: [], + user: docketClerk1User, + }, + }); + + expect(result.showSwingSessionOption).toEqual(false); + }); + }); + + describe('swingSessions', () => { + describe('valid swing sessions', () => { + it('should show only trial sessions in the same term year as the current trial session', () => { + const term = 'Fall'; + const termYear = '2020'; + trialSession1.term = term; + trialSession1.termYear = termYear; + trialSession1.sessionStatus = SESSION_STATUS_TYPES.open; + trialSession2.term = term; + trialSession2.termYear = '2021'; + trialSession2.sessionStatus = SESSION_STATUS_TYPES.open; + + const result = runCompute(addTrialSessionInformationHelper, { + state: { + form: { swingSession: true, term, termYear }, + trialSession: { + trialSessionId: '74f24014-2cf1-4e97-b80a-40f970d5376d', + }, + trialSessions: [trialSession1, trialSession2], + user: docketClerk1User, + }, + }); + + expect(result.swingSessions[0].trialSessionId).toEqual( + trialSession1.trialSessionId, + ); + expect(result.swingSessions.length).toEqual(1); + }); + + it('should show only trial sessions in the same term as the current trial session', () => { + const term = 'Fall'; + const termYear = '2020'; + trialSession1.term = term; + trialSession1.termYear = termYear; + trialSession1.sessionStatus = SESSION_STATUS_TYPES.open; + trialSession2.term = 'Summer'; + trialSession2.termYear = termYear; + trialSession2.sessionStatus = SESSION_STATUS_TYPES.open; + + const result = runCompute(addTrialSessionInformationHelper, { + state: { + form: { swingSession: true, term, termYear }, + trialSession: { + trialSessionId: '74f24014-2cf1-4e97-b80a-40f970d5376d', + }, + trialSessions: [trialSession1, trialSession2], + user: docketClerk1User, + }, + }); + + expect(result.swingSessions[0].trialSessionId).toEqual( + trialSession1.trialSessionId, + ); + expect(result.swingSessions.length).toEqual(1); + }); + + it('should not show closed trial sessions as valid swing sessions', () => { + const term = 'Fall'; + const termYear = '2020'; + trialSession1.term = term; + trialSession1.termYear = termYear; + trialSession1.sessionStatus = SESSION_STATUS_TYPES.closed; + + const result = runCompute(addTrialSessionInformationHelper, { + state: { + form: { swingSession: true, term, termYear }, + trialSession: { + trialSessionId: '74f24014-2cf1-4e97-b80a-40f970d5376d', + }, + trialSessions: [trialSession1], + user: docketClerk1User, + }, + }); + + expect(result.swingSessions.length).toEqual(0); + }); + + it('should not show the current trial session as a valid trialSession option to create a swing session with', () => { + const term = 'Fall'; + const termYear = '2020'; + trialSession1.term = term; + trialSession1.termYear = termYear; + trialSession1.sessionStatus = SESSION_STATUS_TYPES.open; + + const result = runCompute(addTrialSessionInformationHelper, { + state: { + form: { swingSession: true, term, termYear }, + trialSession: { + trialSessionId: trialSession1.trialSessionId, + }, + trialSessions: [trialSession1, trialSession2], + user: docketClerk1User, + }, + }); + + expect(result.swingSessions.length).toEqual(0); + }); + }); + + describe('sorting', () => { + it('should sort swing session options by trial location', () => { + const term = 'Fall'; + const termYear = '2020'; + trialSession1.term = term; + trialSession1.termYear = termYear; + trialSession1.sessionStatus = SESSION_STATUS_TYPES.open; + trialSession1.trialLocation = 'San Diego, California'; + trialSession2.term = term; + trialSession2.termYear = termYear; + trialSession2.sessionStatus = SESSION_STATUS_TYPES.open; + trialSession2.trialLocation = 'Birmingham, Alabama'; + + const result = runCompute(addTrialSessionInformationHelper, { + state: { + form: { swingSession: true, term, termYear }, + trialSession: { + trialSessionId: '74f24014-2cf1-4e97-b80a-40f970d5376d', + }, + trialSessions: [trialSession1, trialSession2], + user: docketClerk1User, + }, + }); + + expect(result.swingSessions[0].trialSessionId).toEqual( + trialSession2.trialSessionId, + ); + expect(result.swingSessions[1].trialSessionId).toEqual( + trialSession1.trialSessionId, + ); + }); + }); + }); }); diff --git a/web-client/src/presenter/computeds/TrialSession/addTrialSessionInformationHelper.ts b/web-client/src/presenter/computeds/TrialSession/addTrialSessionInformationHelper.ts index 053875fd7db..60c91ea3f86 100644 --- a/web-client/src/presenter/computeds/TrialSession/addTrialSessionInformationHelper.ts +++ b/web-client/src/presenter/computeds/TrialSession/addTrialSessionInformationHelper.ts @@ -2,8 +2,11 @@ import { AuthUser } from '@shared/business/entities/authUser/AuthUser'; import { ClientApplicationContext } from '@web-client/applicationContext'; import { FORMATS } from '../../../../../shared/src/business/utilities/DateHandler'; import { Get } from 'cerebral'; +import { SESSION_STATUS_TYPES } from '@shared/business/entities/EntityConstants'; import { TrialSession } from '../../../../../shared/src/business/entities/trialSessions/TrialSession'; +import { TrialSessionInfoDTO } from '@shared/business/dto/trialSessions/TrialSessionInfoDTO'; import { state } from '@web-client/presenter/app.cerebral'; +import { trialSessionOptionText } from '@web-client/presenter/computeds/addToTrialSessionModalHelper'; export const addTrialSessionInformationHelper = ( get: Get, @@ -14,11 +17,17 @@ export const addTrialSessionInformationHelper = ( sessionTypes: string[]; title: string; today: string; + swingSessions: { trialSessionId: string; swingSessionText: string }[]; + showSwingSessionList: boolean; + showSwingSessionOption: boolean; } => { const { SESSION_TYPES, TRIAL_SESSION_PROCEEDING_TYPES } = applicationContext.getConstants(); - const { proceedingType, sessionScope } = get(state.form); + const trialSessions: TrialSessionInfoDTO[] = get(state.trialSessions) || []; + const selectedTerm = get(state.form.term); + const selectedTermYear = get(state.form.termYear); + const currentTrialSessionId = get(state.trialSession.trialSessionId); const isStandaloneSession = TrialSession.isStandaloneRemote(sessionScope); @@ -49,10 +58,42 @@ export const addTrialSessionInformationHelper = ( }; const today = applicationContext.getUtilities().formatNow(FORMATS.YYYYMMDD); + const validSwingSessions: { + trialSessionId: string; + swingSessionText: string; + }[] = trialSessions + .filter(trialSession => trialSession.termYear === selectedTermYear) + .filter(trialSession => trialSession.term === selectedTerm) + .filter( + trialSession => + trialSession.sessionStatus !== SESSION_STATUS_TYPES.closed, + ) + .filter( + trialSession => trialSession.trialSessionId !== currentTrialSessionId, + ) + .sort((sessionA, sessionB) => { + const aTrialLocation = sessionA.trialLocation || ''; + const bTrialLocation = sessionB.trialLocation || ''; + if (aTrialLocation === bTrialLocation) { + return sessionA.startDate.localeCompare(sessionB.startDate); + } + return aTrialLocation.localeCompare(bTrialLocation); + }) + .map(trialSession => { + const swingSessionText = trialSessionOptionText(trialSession); + return { + swingSessionText, + trialSessionId: trialSession.trialSessionId || '', + }; + }); + return { displayRemoteProceedingForm, isStandaloneSession, sessionTypes: getSessionTypes(get(state.user)), + showSwingSessionList: get(state.form.swingSession), + showSwingSessionOption: validSwingSessions.length > 0, + swingSessions: validSwingSessions, title, today, }; diff --git a/web-client/src/presenter/computeds/addToTrialSessionModalHelper.ts b/web-client/src/presenter/computeds/addToTrialSessionModalHelper.ts index 48a9a421be7..76aa660d3db 100644 --- a/web-client/src/presenter/computeds/addToTrialSessionModalHelper.ts +++ b/web-client/src/presenter/computeds/addToTrialSessionModalHelper.ts @@ -9,28 +9,41 @@ export const formatTrialSessionDisplayOptions = ( trialSession.startDateFormatted = applicationContext .getUtilities() .formatDateString(trialSession.startDate, 'MMDDYY'); - switch (trialSession.sessionType) { - case 'Regular': - case 'Small': - case 'Hybrid': - trialSession.sessionTypeFormatted = trialSession.sessionType.charAt(0); - break; - case 'Hybrid-S': - trialSession.sessionTypeFormatted = 'HS'; - break; - case 'Special': - trialSession.sessionTypeFormatted = 'SP'; - break; - case 'Motion/Hearing': - trialSession.sessionTypeFormatted = 'M/H'; - break; - } - trialSession.optionText = `${trialSession.trialLocation} ${trialSession.startDateFormatted} (${trialSession.sessionTypeFormatted})`; + trialSession.optionText = trialSessionOptionText(trialSession); return trialSession; }); }; +export const trialSessionOptionText = (trialSession: { + trialLocation?: string; + startDate: string; + sessionType: string; +}): string => { + const startDateFormatted = formatDateString( + trialSession.startDate, + FORMATS.MMDDYY, + ); + let sessionTypeFormatted: string = ''; + switch (trialSession.sessionType) { + case 'Regular': + case 'Small': + case 'Hybrid': + sessionTypeFormatted = trialSession.sessionType.charAt(0); + break; + case 'Hybrid-S': + sessionTypeFormatted = 'HS'; + break; + case 'Special': + sessionTypeFormatted = 'SP'; + break; + case 'Motion/Hearing': + sessionTypeFormatted = 'M/H'; + break; + } + return `${trialSession.trialLocation} ${startDateFormatted} (${sessionTypeFormatted})`; +}; + export const trialSessionsModalHelper = ({ applicationContext, excludedTrialSessionIds, @@ -129,6 +142,10 @@ export const trialSessionsModalHelper = ({ }; import { ClientApplicationContext } from '@web-client/applicationContext'; +import { + FORMATS, + formatDateString, +} from '@shared/business/utilities/DateHandler'; import { Get } from 'cerebral'; export const addToTrialSessionModalHelper = ( get: Get, diff --git a/web-client/src/presenter/computeds/formattedTrialSessionDetails.test.ts b/web-client/src/presenter/computeds/formattedTrialSessionDetails.test.ts index 5f7ac902ce3..cd255d525bb 100644 --- a/web-client/src/presenter/computeds/formattedTrialSessionDetails.test.ts +++ b/web-client/src/presenter/computeds/formattedTrialSessionDetails.test.ts @@ -1,3 +1,8 @@ +import { + FORMATS, + calculateISODate, + formatNow, +} from '@shared/business/utilities/DateHandler'; import { HYBRID_SESSION_TYPES, SESSION_STATUS_GROUPS, @@ -522,12 +527,12 @@ describe('formattedTrialSessionDetails', () => { }); describe('NOTT reminder', () => { - it('should set showAlertForNOTTReminder to true when the alert has not been previously dismissed and isStartDateWithinNOTTReminderRange is true', () => { + it('should set showAlertForNOTTReminder to true when the alert has not been previously dismissed and start date is within NOTT reminder range', () => { mockTrialSession = { ...TRIAL_SESSION, dismissedAlertForNOTT: false, - isStartDateWithinNOTTReminderRange: true, - thirtyDaysBeforeTrialFormatted: '2/2/2022', + isCalendared: true, + startDate: calculateISODate({ howMuch: 29, units: 'days' }), }; const result: any = runCompute(formattedTrialSessionDetails, { @@ -541,7 +546,7 @@ describe('formattedTrialSessionDetails', () => { expect(result.showAlertForNOTTReminder).toBe(true); expect(result.alertMessageForNOTT).toEqual( - '30-day trial notices are due by 2/2/2022. Have notices been served?', + `30-day trial notices are due by ${formatNow(FORMATS.MMDDYY)}. Have notices been served?`, ); }); @@ -549,7 +554,8 @@ describe('formattedTrialSessionDetails', () => { mockTrialSession = { ...TRIAL_SESSION, dismissedAlertForNOTT: true, - isStartDateWithinNOTTReminderRange: true, + isCalendared: true, + startDate: calculateISODate({ howMuch: 30, units: 'days' }), }; const result: any = runCompute(formattedTrialSessionDetails, { @@ -565,11 +571,12 @@ describe('formattedTrialSessionDetails', () => { expect(result.alertMessageForNOTT).toBeUndefined(); }); - it('should set showAlertForNOTTReminder to false when isStartDateWithinNOTTReminderRange is false', () => { + it('should set showAlertForNOTTReminder to false when start date is within NOTT reminder range', () => { mockTrialSession = { ...TRIAL_SESSION, dismissedAlertForNOTT: true, - isStartDateWithinNOTTReminderRange: false, + isCalendared: true, + startDate: calculateISODate({ howMuch: 60, units: 'days' }), }; const result: any = runCompute(formattedTrialSessionDetails, { diff --git a/web-client/src/presenter/computeds/formattedTrialSessionDetails.ts b/web-client/src/presenter/computeds/formattedTrialSessionDetails.ts index f4f17cbfb68..ff5d83d8434 100644 --- a/web-client/src/presenter/computeds/formattedTrialSessionDetails.ts +++ b/web-client/src/presenter/computeds/formattedTrialSessionDetails.ts @@ -1,8 +1,10 @@ import { ClientApplicationContext } from '@web-client/applicationContext'; import { FormattedTrialSessionDetailsType } from '@shared/business/utilities/getFormattedTrialSessionDetails'; import { Get } from 'cerebral'; +import { TrialSession } from '@shared/business/entities/trialSessions/TrialSession'; import { isEmpty, isEqual } from 'lodash'; import { state } from '@web-client/presenter/app.cerebral'; +import { thirtyDaysBeforeTrial } from '@web-client/presenter/computeds/trialSessionsHelper'; type FormatTrialSessionHelperType = FormattedTrialSessionDetailsType & { alertMessageForNOTT?: string; @@ -58,11 +60,14 @@ export const formattedTrialSessionDetails = ( showAlertForNOTTReminder = !formattedTrialSession.dismissedAlertForNOTT && - !!formattedTrialSession.isStartDateWithinNOTTReminderRange && + TrialSession.isStartDateWithinNOTTReminderRange({ + isCalendared: formattedTrialSession.isCalendared, + startDate: formattedTrialSession.startDate, + }) && formattedTrialSession.sessionStatus !== SESSION_STATUS_TYPES.closed; if (showAlertForNOTTReminder) { - alertMessageForNOTT = `30-day trial notices are due by ${formattedTrialSession.thirtyDaysBeforeTrialFormatted}. Have notices been served?`; + alertMessageForNOTT = `30-day trial notices are due by ${thirtyDaysBeforeTrial(formattedTrialSession.startDate)}. Have notices been served?`; } if (formattedTrialSession.chambersPhoneNumber) { diff --git a/web-client/src/presenter/computeds/formattedTrialSessions.filterFormattedSessionsByStatus.test.ts b/web-client/src/presenter/computeds/formattedTrialSessions.filterFormattedSessionsByStatus.test.ts deleted file mode 100644 index 192a278a4e7..00000000000 --- a/web-client/src/presenter/computeds/formattedTrialSessions.filterFormattedSessionsByStatus.test.ts +++ /dev/null @@ -1,81 +0,0 @@ -import { filterFormattedSessionsByStatus } from './formattedTrialSessions'; - -describe('formattedTrialSessions filterFormattedSessionsByStatus', () => { - let TRIAL_SESSIONS_LIST: any[] = []; - let trialTerms; - - beforeEach(() => { - TRIAL_SESSIONS_LIST = [ - { - caseOrder: [], - judge: { name: '1', userId: '1' }, - sessionStatus: 'Open', - startDate: '2019-11-25T15:00:00.000Z', - swingSession: true, - trialLocation: 'Hartford, Connecticut', - }, - { - caseOrder: [], - judge: { name: '2', userId: '2' }, - sessionStatus: 'New', - startDate: '2019-11-25T15:00:00.000Z', - swingSession: true, - trialClerk: { name: '10', userId: '10' }, - trialLocation: 'Knoxville, TN', - }, - { - caseOrder: [], - judge: { name: '3', userId: '3' }, - noticeIssuedDate: '2019-07-25T15:00:00.000Z', - sessionStatus: 'Closed', - startDate: '2019-11-27T15:00:00.000Z', - swingSession: true, - trialLocation: 'Jacksonville, FL', - }, - ]; - - trialTerms = [ - { - dateFormatted: 'October 1, 2022', - sessions: [TRIAL_SESSIONS_LIST[0]], - }, - { - dateFormatted: 'November 1, 2022', - sessions: [TRIAL_SESSIONS_LIST[1]], - }, - { - dateFormatted: 'December 1, 2022', - sessions: [TRIAL_SESSIONS_LIST[2]], - }, - ]; - }); - - it('filters closed cases when all trial session cases are inactive', () => { - const results = filterFormattedSessionsByStatus(trialTerms); - expect(results.Closed.length).toEqual(1); - }); - - it('filters open trial sessions', () => { - const results = filterFormattedSessionsByStatus(trialTerms); - expect(results.Open.length).toEqual(1); - }); - - it('filters new trial sessions', () => { - const results = filterFormattedSessionsByStatus(trialTerms); - expect(results.New.length).toEqual(1); - }); - - it('filters all trial sessions (returns everything) with the sessionStatus on the session', () => { - const results = filterFormattedSessionsByStatus(trialTerms); - - const getSessionCount = trialTermsList => { - let count = 0; - trialTermsList.forEach(term => (count += term.sessions.length)); - return count; - }; - - expect(results.All.length).toEqual(trialTerms.length); - expect(getSessionCount(results.All)).toEqual(getSessionCount(trialTerms)); - expect(results.All[0].sessions[0]).toHaveProperty('sessionStatus'); - }); -}); diff --git a/web-client/src/presenter/computeds/formattedTrialSessions.formatSession.test.ts b/web-client/src/presenter/computeds/formattedTrialSessions.formatSession.test.ts deleted file mode 100644 index b028396fd82..00000000000 --- a/web-client/src/presenter/computeds/formattedTrialSessions.formatSession.test.ts +++ /dev/null @@ -1,101 +0,0 @@ -import { - SESSION_TYPES, - TRIAL_SESSION_PROCEEDING_TYPES, -} from '../../../../shared/src/business/entities/EntityConstants'; -import { applicationContext } from '../../applicationContext'; -import { formatSession } from './formattedTrialSessions'; - -describe('formattedTrialSessions formatSession', () => { - const mockTrialSessions = [ - { - caseOrder: [], - isCalendared: true, - judge: { name: '3', userId: '3' }, - noticeIssuedDate: '2019-07-25T15:00:00.000Z', - proceedingType: TRIAL_SESSION_PROCEEDING_TYPES.inPerson, - sessionType: SESSION_TYPES.regular, - startDate: '2019-11-27T15:00:00.000Z', - swingSession: true, - term: 'Winter', - trialLocation: 'Jacksonville, FL', - }, - { - caseOrder: [], - estimatedEndDate: '2045-02-17T15:00:00.000Z', - judge: { name: '6', userId: '6' }, - proceedingType: TRIAL_SESSION_PROCEEDING_TYPES.inPerson, - sessionType: SESSION_TYPES.regular, - startDate: '2044-02-17T15:00:00.000Z', - swingSession: false, - term: 'Spring', - trialLocation: 'Jacksonville, FL', - }, - ]; - - it('formats trial sessions correctly selecting startOfWeek and formatting start date, startOfWeekSortable, and formattedNoticeIssued', () => { - const result = formatSession(mockTrialSessions[0], applicationContext); - expect(result).toMatchObject({ - formattedNoticeIssuedDate: '07/25/2019', - formattedStartDate: '11/27/19', - judge: { name: '3', userId: '3' }, - startDate: '2019-11-27T15:00:00.000Z', - startOfWeek: 'November 25, 2019', - startOfWeekSortable: '20191125', - }); - }); - - it('should format start date and estimated end date as "MM/DD/YYYY"', () => { - const result = formatSession(mockTrialSessions[1], applicationContext); - expect(result).toMatchObject({ - formattedEstimatedEndDate: '02/17/45', - formattedStartDate: '02/17/44', - }); - }); - - describe('NOTT reminder', () => { - it('should set showAlertForNOTTReminder to true when the alert has not been previously dismissed and isStartDateWithinNOTTReminderRange is true', () => { - const session = formatSession( - { - ...mockTrialSessions[0], - dismissedAlertForNOTT: false, - isStartDateWithinNOTTReminderRange: true, - thirtyDaysBeforeTrialFormatted: '2/2/2022', - }, - applicationContext, - ); - - expect(session.showAlertForNOTTReminder).toBe(true); - expect(session.alertMessageForNOTT).toEqual( - 'The 30-day notice is due by 2/2/2022', - ); - }); - - it('should set showAlertForNOTTReminder to false when the alert has been previously dismissed', () => { - const session = formatSession( - { - ...mockTrialSessions[0], - dismissedAlertForNOTT: true, - isStartDateWithinNOTTReminderRange: true, - }, - applicationContext, - ); - - expect(session.showAlertForNOTTReminder).toBe(false); - expect(session.alertMessageForNOTT).toBeUndefined(); - }); - - it('should set showAlertForNOTTReminder to false when isStartDateWithinNOTTReminderRange is false', () => { - const session = formatSession( - { - ...mockTrialSessions[0], - dismissedAlertForNOTT: true, - isStartDateWithinNOTTReminderRange: false, - }, - applicationContext, - ); - - expect(session.showAlertForNOTTReminder).toBe(false); - expect(session.alertMessageForNOTT).toBeUndefined(); - }); - }); -}); diff --git a/web-client/src/presenter/computeds/formattedTrialSessions.test.ts b/web-client/src/presenter/computeds/formattedTrialSessions.test.ts deleted file mode 100644 index c30830cd766..00000000000 --- a/web-client/src/presenter/computeds/formattedTrialSessions.test.ts +++ /dev/null @@ -1,654 +0,0 @@ -import { - FORMATS, - formatNow, - prepareDateFromString, -} from '../../../../shared/src/business/utilities/DateHandler'; -import { applicationContextForClient as applicationContext } from '@web-client/test/createClientTestApplicationContext'; -import { formatTrialSessionDisplayOptions } from './addToTrialSessionModalHelper'; -import { formattedTrialSessions as formattedTrialSessionsComputed } from './formattedTrialSessions'; -import { judgeUser, petitionsClerkUser } from '@shared/test/mockUsers'; -import { runCompute } from '@web-client/presenter/test.cerebral'; -import { withAppContextDecorator } from '../../withAppContext'; -jest.mock('./addToTrialSessionModalHelper.ts'); - -const { - SESSION_TYPES, - TRIAL_SESSION_PROCEEDING_TYPES, - USER_ROLES: ROLES, -} = applicationContext.getConstants(); - -const formattedTrialSessions = withAppContextDecorator( - formattedTrialSessionsComputed, - { - ...applicationContext, - }, -); - -const getStartOfWeek = date => { - return prepareDateFromString(date).startOf('week').toFormat('DDD'); -}; - -let nextYear; - -const testTrialClerkUser = { - role: ROLES.trialClerk, - userId: '10', -}; - -const baseState = { - constants: { USER_ROLES: ROLES }, - judgeUser, -}; - -let TRIAL_SESSIONS_LIST: any[] = []; - -describe('formattedTrialSessions', () => { - beforeAll(() => { - nextYear = (parseInt(formatNow(FORMATS.YEAR)) + 1).toString(); - }); - - beforeEach(() => { - TRIAL_SESSIONS_LIST = [ - { - caseOrder: [], - isCalendared: true, - judge: { name: judgeUser.name, userId: judgeUser.userId }, - proceedingType: TRIAL_SESSION_PROCEEDING_TYPES.inPerson, - sessionStatus: 'Open', - sessionType: SESSION_TYPES.regular, - startDate: '2019-11-25T15:00:00.000Z', - swingSession: true, - term: 'Fall', - termYear: '2019', - trialLocation: 'Hartford, Connecticut', - trialSessionId: '1', - }, - { - caseOrder: [], - isCalendared: false, - judge: { name: '2', userId: '2' }, - proceedingType: TRIAL_SESSION_PROCEEDING_TYPES.remote, - sessionStatus: 'New', - sessionType: SESSION_TYPES.small, - startDate: '2019-11-25T15:00:00.000Z', - swingSession: true, - term: 'Winter', - trialClerk: { name: '10', userId: '10' }, - trialLocation: 'Knoxville, TN', - trialSessionId: '2', - }, - { - caseOrder: [], - isCalendared: false, - judge: { name: '3', userId: '3' }, - noticeIssuedDate: '2019-07-25T15:00:00.000Z', - proceedingType: TRIAL_SESSION_PROCEEDING_TYPES.inPerson, - sessionStatus: 'New', - sessionType: SESSION_TYPES.regular, - startDate: '2019-11-27T15:00:00.000Z', - swingSession: true, - term: 'Winter', - trialLocation: 'Jacksonville, FL', - trialSessionId: '3', - }, - { - caseOrder: [], - isCalendared: false, - judge: { name: '55', userId: '55' }, - noticeIssuedDate: '2019-07-25T15:00:00.000Z', - proceedingType: TRIAL_SESSION_PROCEEDING_TYPES.inPerson, - sessionStatus: 'New', - sessionType: SESSION_TYPES.regular, - startDate: '2019-10-27T15:00:00.000Z', - swingSession: true, - term: 'Winter', - trialLocation: 'Jacksonville, FL', - trialSessionId: '5', - }, - { - caseOrder: [], - isCalendared: false, - judge: { name: '88', userId: '88' }, - noticeIssuedDate: '2020-07-26T15:00:00.000Z', - proceedingType: TRIAL_SESSION_PROCEEDING_TYPES.inPerson, - sessionStatus: 'New', - sessionType: SESSION_TYPES.regular, - startDate: '2020-11-26T15:00:00.000Z', - swingSession: true, - term: 'Winter', - trialLocation: 'Jacksonville, FL', - trialSessionId: '8', - }, - { - caseOrder: [], - isCalendared: true, - judge: { name: '4', userId: '4' }, - proceedingType: TRIAL_SESSION_PROCEEDING_TYPES.inPerson, - sessionStatus: 'Open', - sessionType: SESSION_TYPES.hybrid, - startDate: '2019-11-27T15:00:00.000Z', - swingSession: true, - term: 'Summer', - trialLocation: 'Memphis, TN', - trialSessionId: '4', - }, - { - caseOrder: [], - isCalendared: true, - judge: { name: '5', userId: '5' }, - proceedingType: TRIAL_SESSION_PROCEEDING_TYPES.remote, - sessionStatus: 'Open', - sessionType: SESSION_TYPES.hybrid, - startDate: '2019-11-25T15:00:00.000Z', - swingSession: false, - term: 'Spring', - termYear: '2019', - trialLocation: 'Anchorage, AK', - }, - { - caseOrder: [], - estimatedEndDate: '2045-02-17T15:00:00.000Z', - isCalendared: true, - judge: { name: '6', userId: '6' }, - proceedingType: TRIAL_SESSION_PROCEEDING_TYPES.inPerson, - sessionStatus: 'Open', - sessionType: SESSION_TYPES.regular, - startDate: `${nextYear}-02-17T15:00:00.000Z`, - swingSession: false, - term: 'Spring', - trialLocation: 'Jacksonville, FL', - }, - ]; - - formatTrialSessionDisplayOptions.mockImplementation(session => session); - }); - - it('does not error if user is undefined', () => { - let error; - try { - runCompute(formattedTrialSessions, { - state: { - ...baseState, - trialSessions: TRIAL_SESSIONS_LIST, - user: judgeUser, - }, - }); - } catch (err) { - error = err; - } - expect(error).toBeUndefined(); - }); - - it('groups trial sessions into arrays according to session weeks', () => { - const result = runCompute(formattedTrialSessions, { - state: { - ...baseState, - trialSessions: TRIAL_SESSIONS_LIST, - user: judgeUser, - }, - }); - - expect(result.filteredTrialSessions).toBeDefined(); - expect(result.formattedSessions.length).toBe(4); - expect(result.formattedSessions[0].dateFormatted).toEqual( - 'November 25, 2019', - ); - expect(result.formattedSessions[1].dateFormatted).toEqual( - getStartOfWeek(result.formattedSessions[1].sessions[0].startDate), - ); - }); - - it('should filter trial sessions by judge', () => { - const result = runCompute(formattedTrialSessions, { - state: { - ...baseState, - screenMetadata: { - trialSessionFilters: { judge: { userId: judgeUser.userId } }, - }, - trialSessions: TRIAL_SESSIONS_LIST, - user: judgeUser, - }, - }); - expect(result.formattedSessions.length).toBe(1); - }); - - it('should double filter trial sessions', () => { - const result = runCompute(formattedTrialSessions, { - state: { - ...baseState, - screenMetadata: { - trialSessionFilters: { - proceedingType: TRIAL_SESSION_PROCEEDING_TYPES.inPerson, - sessionType: SESSION_TYPES.regular, - }, - }, - trialSessions: TRIAL_SESSIONS_LIST, - user: judgeUser, - }, - }); - const flattenedSessions = result.formattedSessions.flatMap( - week => week.sessions, - ); - expect(flattenedSessions.length).toBe(5); - }); - - it('returns all trial sessions if judge userId trial session filter is an empty string', () => { - const result = runCompute(formattedTrialSessions, { - state: { - ...baseState, - screenMetadata: { trialSessionFilters: { judge: { userId: '' } } }, - trialSessions: TRIAL_SESSIONS_LIST, - user: judgeUser, - }, - }); - expect(result.formattedSessions.length).toBe(4); - }); - - it('does NOT return the unassigned judge filter on trial sessions tabs other than "new"', () => { - let result = runCompute(formattedTrialSessions, { - state: { - ...baseState, - currentViewMetadata: { - trialSessions: { - tab: 'open', - }, - }, - screenMetadata: { - trialSessionFilters: { judge: { userId: 'unassigned' } }, - }, - trialSessions: TRIAL_SESSIONS_LIST, - user: judgeUser, - }, - }); - - expect(result.formattedSessions.length).toBe(4); - }); - - it('shows swing session option only if matching term and term year is found', () => { - let form = { - term: 'Winter', - termYear: '2019', - }; - let result = runCompute(formattedTrialSessions, { - state: { - ...baseState, - form, - trialSessions: TRIAL_SESSIONS_LIST, - user: judgeUser, - }, - }); - expect(result.sessionsByTerm.length).toEqual(0); - expect(result.showSwingSessionOption).toBeFalsy(); - - form.term = 'Spring'; - result = runCompute(formattedTrialSessions, { - state: { - ...baseState, - form, - trialSessions: TRIAL_SESSIONS_LIST, - user: judgeUser, - }, - }); - expect(result.sessionsByTerm.length).toEqual(1); - expect(result.showSwingSessionOption).toBeTruthy(); - - form.termYear = '2011'; // similar term but not a matching year - result = runCompute(formattedTrialSessions, { - state: { - ...baseState, - form, - trialSessions: TRIAL_SESSIONS_LIST, - user: judgeUser, - }, - }); - expect(result.sessionsByTerm.length).toEqual(0); - expect(result.showSwingSessionOption).toBeFalsy(); - }); - - it('returns sessionsByTerm with only sessions in that term sorted chronologically if form.term is set', () => { - const result = runCompute(formattedTrialSessions, { - state: { - ...baseState, - form: { - term: 'Winter', - }, - trialSessions: TRIAL_SESSIONS_LIST, - user: judgeUser, - }, - }); - - expect(result.sessionsByTerm).toEqual([ - { - caseOrder: [], - formattedEstimatedEndDate: '', - formattedNoticeIssuedDate: '07/25/2019', - formattedStartDate: '10/27/19', - isCalendared: false, - judge: { name: '55', userId: '55' }, - noticeIssuedDate: '2019-07-25T15:00:00.000Z', - proceedingType: 'In Person', - sessionStatus: 'New', - sessionType: SESSION_TYPES.regular, - showAlertForNOTTReminder: undefined, - startDate: '2019-10-27T15:00:00.000Z', - startOfWeek: 'October 21, 2019', - startOfWeekSortable: '20191021', - swingSession: true, - term: 'Winter', - trialLocation: 'Jacksonville, FL', - trialSessionId: '5', - userIsAssignedToSession: false, - }, - { - caseOrder: [], - formattedEstimatedEndDate: '', - formattedNoticeIssuedDate: '07/25/2019', - formattedStartDate: '11/27/19', - isCalendared: false, - judge: { name: '3', userId: '3' }, - noticeIssuedDate: '2019-07-25T15:00:00.000Z', - proceedingType: 'In Person', - sessionStatus: 'New', - sessionType: SESSION_TYPES.regular, - showAlertForNOTTReminder: undefined, - startDate: '2019-11-27T15:00:00.000Z', - startOfWeek: 'November 25, 2019', - startOfWeekSortable: '20191125', - swingSession: true, - term: 'Winter', - trialLocation: 'Jacksonville, FL', - trialSessionId: '3', - userIsAssignedToSession: false, - }, - { - caseOrder: [], - formattedEstimatedEndDate: '', - formattedNoticeIssuedDate: '07/26/2020', - formattedStartDate: '11/26/20', - isCalendared: false, - judge: { name: '88', userId: '88' }, - noticeIssuedDate: '2020-07-26T15:00:00.000Z', - proceedingType: 'In Person', - sessionStatus: 'New', - sessionType: SESSION_TYPES.regular, - showAlertForNOTTReminder: undefined, - startDate: '2020-11-26T15:00:00.000Z', - startOfWeek: 'November 23, 2020', - startOfWeekSortable: '20201123', - swingSession: true, - term: 'Winter', - trialLocation: 'Jacksonville, FL', - trialSessionId: '8', - userIsAssignedToSession: false, - }, - { - caseOrder: [], - formattedEstimatedEndDate: '', - formattedNoticeIssuedDate: '', - formattedStartDate: '11/25/19', - isCalendared: false, - judge: { name: '2', userId: '2' }, - proceedingType: 'Remote', - sessionStatus: 'New', - sessionType: SESSION_TYPES.small, - showAlertForNOTTReminder: undefined, - startDate: '2019-11-25T15:00:00.000Z', - startOfWeek: 'November 25, 2019', - startOfWeekSortable: '20191125', - swingSession: true, - term: 'Winter', - trialClerk: { name: '10', userId: '10' }, - trialLocation: 'Knoxville, TN', - trialSessionId: '2', - userIsAssignedToSession: false, - }, - ]); - }); - - it('makes a call to format display text on sessionsByTerm', () => { - runCompute(formattedTrialSessions, { - state: { - ...baseState, - form: { - term: 'Winter', - }, - trialSessions: TRIAL_SESSIONS_LIST, - user: judgeUser, - }, - }); - - expect(formatTrialSessionDisplayOptions).toHaveBeenCalled(); - }); - - it('removes the current trial session from the sessionsByTerm when state.trialSession.trialSessionId is defined', () => { - const { sessionsByTerm } = runCompute(formattedTrialSessions, { - state: { - ...baseState, - form: { - term: 'Winter', - }, - trialSession: { trialSessionId: TRIAL_SESSIONS_LIST[1].trialSessionId }, - trialSessions: TRIAL_SESSIONS_LIST, - user: judgeUser, - }, - }); - - expect( - sessionsByTerm.find( - session => - session.trialSessionId === TRIAL_SESSIONS_LIST[1].trialSessionId, - ), - ).toBeUndefined(); - }); - - it('sets userIsAssignedToSession false for all sessions if there is no associated judgeUser', () => { - const result = runCompute(formattedTrialSessions, { - state: { - ...baseState, - judgeUser: undefined, - trialSessions: TRIAL_SESSIONS_LIST, - user: petitionsClerkUser, - }, - }); - - expect(result.formattedSessions[0]).toMatchObject({ - dateFormatted: 'November 25, 2019', - sessions: [ - { - judge: { name: '5', userId: '5' }, - userIsAssignedToSession: false, - }, - { - judge: { name: judgeUser.name, userId: judgeUser.userId }, - userIsAssignedToSession: false, - }, - { - judge: { name: '2', userId: '2' }, - userIsAssignedToSession: false, - }, - { - judge: { name: '3', userId: '3' }, - userIsAssignedToSession: false, - }, - { - judge: { name: '4', userId: '4' }, - userIsAssignedToSession: false, - }, - ], - }); - - expect(result.formattedSessions[1]).toMatchObject({ - dateFormatted: getStartOfWeek( - result.formattedSessions[1].sessions[0].startDate, - ), - sessions: [ - { - judge: { name: '55', userId: '55' }, - userIsAssignedToSession: false, - }, - ], - }); - }); - - it('sets userIsAssignedToSession true for sessions the judge user is assigned to', () => { - const result = runCompute(formattedTrialSessions, { - state: { - ...baseState, - trialSessions: TRIAL_SESSIONS_LIST, - user: judgeUser, - }, - }); - expect(result.formattedSessions).toMatchObject([ - { - dateFormatted: 'November 25, 2019', - sessions: [ - { - judge: { name: '5', userId: '5' }, - userIsAssignedToSession: false, - }, - { - judge: { name: judgeUser.name, userId: judgeUser.userId }, - userIsAssignedToSession: true, - }, - { - judge: { name: '2', userId: '2' }, - userIsAssignedToSession: false, - }, - { - judge: { name: '3', userId: '3' }, - userIsAssignedToSession: false, - }, - { - judge: { name: '4', userId: '4' }, - userIsAssignedToSession: false, - }, - ], - }, - { - dateFormatted: getStartOfWeek( - result.formattedSessions[1].sessions[0].startDate, - ), - sessions: [ - { - judge: { name: '55', userId: '55' }, - userIsAssignedToSession: false, - }, - ], - }, - { - dateFormatted: 'November 23, 2020', - sessions: [ - { - judge: { name: '88', userId: '88' }, - userIsAssignedToSession: false, - }, - ], - }, - { - dateFormatted: 'February 17, 2025', - sessions: [ - { - caseOrder: [], - estimatedEndDate: '2045-02-17T15:00:00.000Z', - formattedEstimatedEndDate: '02/17/45', - formattedNoticeIssuedDate: '', - formattedStartDate: '02/17/25', - isCalendared: true, - judge: { name: '6', userId: '6' }, - proceedingType: 'In Person', - sessionStatus: 'Open', - sessionType: SESSION_TYPES.regular, - showAlertForNOTTReminder: undefined, - startDate: '2025-02-17T15:00:00.000Z', - startOfWeek: 'February 17, 2025', - startOfWeekSortable: '20250217', - swingSession: false, - term: 'Spring', - trialLocation: 'Jacksonville, FL', - userIsAssignedToSession: false, - }, - ], - }, - ]); - }); - - it('sets userIsAssignedToSession true for sessions the current trial clerk user is assigned to', () => { - const result = runCompute(formattedTrialSessions, { - state: { - ...baseState, - judgeUser: undefined, - trialSessions: TRIAL_SESSIONS_LIST, - user: testTrialClerkUser, - }, - }); - - expect(result.formattedSessions[0]).toMatchObject({ - dateFormatted: 'November 25, 2019', - sessions: [ - { - judge: { name: '5', userId: '5' }, - userIsAssignedToSession: false, - }, - { - judge: { name: judgeUser.name, userId: judgeUser.userId }, - userIsAssignedToSession: false, - }, - { - trialClerk: { name: '10', userId: '10' }, - userIsAssignedToSession: true, - }, - { - judge: { name: '3', userId: '3' }, - userIsAssignedToSession: false, - }, - { - judge: { name: '4', userId: '4' }, - userIsAssignedToSession: false, - }, - ], - }); - - expect(result.formattedSessions[1]).toMatchObject({ - dateFormatted: getStartOfWeek( - result.formattedSessions[1].sessions[0].startDate, - ), - sessions: [ - { - judge: { name: '55', userId: '55' }, - userIsAssignedToSession: false, - }, - ], - }); - }); - - it('sets userIsAssignedToSession false if the current user and session have no associated judge', () => { - const startDate = `${nextYear}-02-17T15:00:00.000Z`; - const result = runCompute(formattedTrialSessions, { - state: { - ...baseState, - judgeUser: undefined, - trialSessions: [ - { - caseOrder: [], - judge: undefined, - sessionStatus: 'Open', - startDate, - swingSession: false, - trialLocation: 'Jacksonville, FL', - }, - ], - user: petitionsClerkUser, - }, - }); - expect(result.formattedSessions).toMatchObject([ - { - dateFormatted: getStartOfWeek(startDate), - sessions: [ - { - userIsAssignedToSession: false, - }, - ], - }, - ]); - }); -}); diff --git a/web-client/src/presenter/computeds/formattedTrialSessions.ts b/web-client/src/presenter/computeds/formattedTrialSessions.ts deleted file mode 100644 index e33ec8d5669..00000000000 --- a/web-client/src/presenter/computeds/formattedTrialSessions.ts +++ /dev/null @@ -1,258 +0,0 @@ -import { ClientApplicationContext } from '@web-client/applicationContext'; -import { Get } from 'cerebral'; -import { RawTrialSession } from '@shared/business/entities/trialSessions/TrialSession'; -import { createDateAtStartOfWeekEST } from '../../../../shared/src/business/utilities/DateHandler'; -import { - filter, - find, - flatMap, - groupBy, - identity, - omit, - orderBy, - pickBy, -} from 'lodash'; -import { formatTrialSessionDisplayOptions } from './addToTrialSessionModalHelper'; -import { state } from '@web-client/presenter/app.cerebral'; - -export const formatSession = (session, applicationContext) => { - const { DATE_FORMATS } = applicationContext.getConstants(); - - session.startOfWeek = createDateAtStartOfWeekEST( - session.startDate, - DATE_FORMATS.MONTH_DAY_YEAR, - ); - - session.startOfWeekSortable = createDateAtStartOfWeekEST( - session.startDate, - DATE_FORMATS.YYYYMMDD_NUMERIC, - ); - - session.formattedStartDate = applicationContext - .getUtilities() - .formatDateString(session.startDate, DATE_FORMATS.MMDDYY); - - session.formattedEstimatedEndDate = applicationContext - .getUtilities() - .formatDateString(session.estimatedEndDate, DATE_FORMATS.MMDDYY); - - session.formattedNoticeIssuedDate = applicationContext - .getUtilities() - .formatDateString(session.noticeIssuedDate, DATE_FORMATS.MMDDYYYY); - - session.showAlertForNOTTReminder = - !session.dismissedAlertForNOTT && - session.isStartDateWithinNOTTReminderRange; - - if (session.showAlertForNOTTReminder) { - session.alertMessageForNOTT = `The 30-day notice is due by ${session.thirtyDaysBeforeTrialFormatted}`; - } - - return session; -}; - -export const sessionSorter = (sessionList, dateSort = 'asc') => { - return orderBy( - sessionList, - ['startDate', 'trialLocation'], - [dateSort, 'asc'], - ); -}; - -export const filterFormattedSessionsByStatus = trialTerms => { - const sessionSort = { - All: 'desc', - Closed: 'desc', - New: 'asc', - Open: 'asc', - }; - - const filteredbyStatusType = { - All: [], - Closed: [], - New: [], - Open: [], - }; - - const initTermIndex = (trialTerm, filtered) => { - let termIndex = filtered.findIndex( - term => term.dateFormatted === trialTerm.dateFormatted, - ); - - if (termIndex === -1) { - filtered.push({ - dateFormatted: trialTerm.dateFormatted, - sessions: [], - startOfWeekSortable: trialTerm.startOfWeekSortable, - }); - termIndex = filtered.length - 1; - } - - return termIndex; - }; - - trialTerms.forEach(trialTerm => { - trialTerm.sessions.forEach(session => { - const termIndex = initTermIndex( - trialTerm, - filteredbyStatusType[session.sessionStatus], - ); - - if (!session.judge) { - session.judge = { - name: 'Unassigned', - userId: 'unassigned', - }; - } - // Add session status to filtered session - filteredbyStatusType[session.sessionStatus][termIndex].sessions.push( - session, - ); - - // Push to all - const allTermIndex = initTermIndex(trialTerm, filteredbyStatusType.All); - filteredbyStatusType.All[allTermIndex].sessions.push(session); - }); - }); - - for (let [status, entryTrialTerms] of Object.entries(filteredbyStatusType)) { - filteredbyStatusType[status] = orderBy( - entryTrialTerms, - ['startOfWeekSortable'], - [sessionSort[status]], - ); - entryTrialTerms.forEach(trialTerm => { - trialTerm.sessions = sessionSorter(trialTerm.sessions, [ - sessionSort[status], - ]); - }); - } - - return filteredbyStatusType; -}; - -const sortSessionsByTerm = ({ - applicationContext, - currentTrialSessionId, - selectedTerm, - selectedTermYear, - sessions, -}: { - applicationContext: ClientApplicationContext; - selectedTermYear: string; - selectedTerm: string; - sessions: RawTrialSession[]; - currentTrialSessionId?: string; -}) => { - const sessionsByTermOrderedByTrialLocation = orderBy( - sessions.filter( - session => - session.term === selectedTerm && session.termYear == selectedTermYear, - ), - 'trialLocation', - ); - - const sessionsGroupedByTrialLocation = groupBy( - sessionsByTermOrderedByTrialLocation, - 'trialLocation', - ); - - const sessionsOrderedChronologically = flatMap( - sessionsGroupedByTrialLocation, - group => { - return orderBy(group, 'startDate', 'asc'); - }, - ); - - const sessionsByTermFormatted = formatTrialSessionDisplayOptions( - sessionsOrderedChronologically, - applicationContext, - ); - - if (currentTrialSessionId) { - return sessionsByTermFormatted.filter( - session => session.trialSessionId !== currentTrialSessionId, - ); - } - - return sessionsByTermFormatted; -}; - -export const formattedTrialSessions = ( - get: Get, - applicationContext: ClientApplicationContext, -): any => { - const judgeId = get(state.judgeUser.userId); - const currentTrialSessionId = get(state.trialSession.trialSessionId); - const currentUser = get(state.user); - - const trialSessionFilters = pickBy( - omit(get(state.screenMetadata.trialSessionFilters), 'status'), - identity, - ); - const judgeFilter = get( - state.screenMetadata.trialSessionFilters.judge.userId, - ); - - const tab = get(state.currentViewMetadata.trialSessions.tab); - - if (!judgeFilter || (tab !== 'new' && judgeFilter === 'unassigned')) { - delete trialSessionFilters.judge; - } - - const sessions = filter(get(state.trialSessions), trialSessionFilters); - - const formattedSessions = []; - sessions.forEach(session => { - const isJudgeUserAssigned = !!( - session.judge?.userId === judgeId && judgeId - ); - const isTrialClerkUserAssigned = - session.trialClerk?.userId === currentUser.userId; - - session.userIsAssignedToSession = - isJudgeUserAssigned || isTrialClerkUserAssigned; - - const formattedSession = formatSession(session, applicationContext); - - let sessionWeek = find(formattedSessions, { - startOfWeekSortable: formattedSession.startOfWeekSortable, - }); - - if (!sessionWeek) { - sessionWeek = { - dateFormatted: formattedSession.startOfWeek, - sessions: [], - startOfWeekSortable: formattedSession.startOfWeekSortable, - }; - formattedSessions.push(sessionWeek); - } - sessionWeek.sessions.push(session); - }); - - formattedSessions.forEach( - week => (week.sessions = sessionSorter(week.sessions)), - ); - - const selectedTerm = get(state.form.term); - let sessionsByTerm: any[] = []; - - if (selectedTerm) { - const selectedTermYear = get(state.form.termYear); - sessionsByTerm = sortSessionsByTerm({ - applicationContext, - currentTrialSessionId, - selectedTerm, - selectedTermYear, - sessions, - }); - } - - return { - filteredTrialSessions: filterFormattedSessionsByStatus(formattedSessions), - formattedSessions, - sessionsByTerm, - showSwingSessionList: get(state.form.swingSession), - showSwingSessionOption: sessionsByTerm.length > 0, - }; -}; diff --git a/web-client/src/presenter/computeds/trialSessionsHelper.test.ts b/web-client/src/presenter/computeds/trialSessionsHelper.test.ts index 025c74c8e6c..f4da90fbc14 100644 --- a/web-client/src/presenter/computeds/trialSessionsHelper.test.ts +++ b/web-client/src/presenter/computeds/trialSessionsHelper.test.ts @@ -1,8 +1,26 @@ -import { ROLES } from '../../../../shared/src/business/entities/EntityConstants'; +import { + FORMATS, + calculateISODate, + formatNow, +} from '@shared/business/utilities/DateHandler'; +import { + ROLES, + SESSION_STATUS_TYPES, + SESSION_TYPES, + TRIAL_SESSION_PROCEEDING_TYPES, + TRIAL_SESSION_SCOPE_TYPES, +} from '../../../../shared/src/business/entities/EntityConstants'; +import { TrialSessionInfoDTO } from '@shared/business/dto/trialSessions/TrialSessionInfoDTO'; +import { cloneDeep } from 'lodash'; import { docketClerk1User, judgeUser } from '@shared/test/mockUsers'; import { getUserPermissions } from '@shared/authorization/getUserPermissions'; +import { initialTrialSessionPageState } from '../state/trialSessionsPageState'; +import { + isTrialSessionRow, + isTrialSessionWeek, + trialSessionsHelper as trialSessionsHelperComputed, +} from './trialSessionsHelper'; import { runCompute } from '@web-client/presenter/test.cerebral'; -import { trialSessionsHelper as trialSessionsHelperComputed } from './trialSessionsHelper'; import { withAppContextDecorator } from '../../withAppContext'; const trialSessionsHelper = withAppContextDecorator( @@ -10,433 +28,615 @@ const trialSessionsHelper = withAppContextDecorator( ); describe('trialSessionsHelper', () => { + let trialSessionsPageState: typeof initialTrialSessionPageState; + let trialSession1: TrialSessionInfoDTO; + let trialSession2: TrialSessionInfoDTO; + beforeEach(() => { + trialSessionsPageState = cloneDeep(initialTrialSessionPageState); + trialSession1 = { + isCalendared: true, + judge: { name: 'howdy', userId: '1' }, + proceedingType: 'Remote', + sessionScope: TRIAL_SESSION_SCOPE_TYPES.locationBased, + sessionStatus: 'Open', + sessionType: 'Regular', + startDate: '2022-03-01T21:00:00.000Z', + term: 'Winter', + termYear: '2022', + trialLocation: 'Boise', + trialSessionId: '294038', + }; + trialSession2 = { + isCalendared: true, + judge: { name: 'howdy', userId: '2' }, + proceedingType: 'Remote', + sessionScope: TRIAL_SESSION_SCOPE_TYPES.locationBased, + sessionStatus: 'Open', + sessionType: 'Regular', + startDate: '2022-03-01T21:00:00.000Z', + term: 'Winter', + termYear: '2022', + trialLocation: 'Boise', + trialSessionId: '392810', + }; + }); + describe('showNoticeIssued', () => { - it('should show the Notice Issued column for `open` sessions', () => { + it('should show the Notice Issued column when on the calendared tab', () => { + trialSessionsPageState.filters.currentTab = 'calendared'; + const result = runCompute(trialSessionsHelper, { state: { - currentViewMetadata: { - trialSessions: { - tab: 'open', - }, - }, permissions: getUserPermissions(docketClerk1User), + trialSessionsPage: trialSessionsPageState, }, }); expect(result.showNoticeIssued).toEqual(true); }); - it('should NOT show the Notice Issued column for `new` sessions', () => { + it('should NOT show the Notice Issued column when on the new tab', () => { + trialSessionsPageState.filters.currentTab = 'new'; const result = runCompute(trialSessionsHelper, { state: { - currentViewMetadata: { - trialSessions: { - tab: 'new', - }, - }, permissions: getUserPermissions(docketClerk1User), + trialSessionsPage: trialSessionsPageState, }, }); expect(result.showNoticeIssued).toEqual(false); }); + }); + + describe('showSessionStatus', () => { + it('should show the Session Status column when on the `calendared` tab', () => { + trialSessionsPageState.filters.currentTab = 'calendared'; - it('should NOT show the Notice Issued column for `closed` sessions', () => { const result = runCompute(trialSessionsHelper, { state: { - currentViewMetadata: { - trialSessions: { - tab: 'closed', - }, - }, permissions: getUserPermissions(docketClerk1User), + trialSessionsPage: trialSessionsPageState, }, }); - expect(result.showNoticeIssued).toEqual(false); + expect(result.showSessionStatus).toEqual(true); }); - it('should NOT show the Notice Issued column for `all` sessions', () => { + it('should NOT show the Session Status column when on the `new` tab', () => { + trialSessionsPageState.filters.currentTab = 'new'; + const result = runCompute(trialSessionsHelper, { state: { - currentViewMetadata: { - trialSessions: { - tab: 'all', - }, - }, permissions: getUserPermissions(docketClerk1User), + trialSessionsPage: trialSessionsPageState, }, }); - expect(result.showNoticeIssued).toEqual(false); + expect(result.showSessionStatus).toEqual(false); }); }); - describe('showSessionStatus', () => { - it('should show the Session Status column for `all` sessions', () => { + describe('showUnassignedJudgeFilter', () => { + it('should show the `unassigned` judge filter when on the new tab', () => { + trialSessionsPageState.filters.currentTab = 'new'; + const result = runCompute(trialSessionsHelper, { state: { - currentViewMetadata: { - trialSessions: { - tab: 'all', - }, - }, permissions: getUserPermissions(docketClerk1User), + trialSessionsPage: trialSessionsPageState, }, }); - expect(result.showSessionStatus).toEqual(true); + expect(result.showUnassignedJudgeFilter).toBeTruthy(); }); - it('should NOT show the Session Status column for `new` sessions', () => { + it('should show the `unassigned` judge filter when on the calendared tab', () => { + trialSessionsPageState.filters.currentTab = 'calendared'; + const result = runCompute(trialSessionsHelper, { state: { - currentViewMetadata: { - trialSessions: { - tab: 'new', - }, - }, permissions: getUserPermissions(docketClerk1User), + trialSessionsPage: trialSessionsPageState, }, }); - expect(result.showSessionStatus).toEqual(false); + expect(result.showUnassignedJudgeFilter).toEqual(false); }); + }); - it('should NOT show the Session Status column for `open` sessions', () => { + describe('trialSessionJudges', () => { + it('returns all current and legacy judges when the session status is closed', () => { + trialSessionsPageState.filters.currentTab = 'calendared'; + trialSessionsPageState.filters.sessionStatus = + SESSION_STATUS_TYPES.closed; const result = runCompute(trialSessionsHelper, { state: { - currentViewMetadata: { - trialSessions: { - tab: 'open', - }, - }, + judges: [ + { name: 'I am not a legacy judge part 2', role: ROLES.judge }, + ], + legacyAndCurrentJudges: [ + { name: 'I am not a legacy judge', role: ROLES.judge }, + { name: 'I am a legacy judge', role: ROLES.legacyJudge }, + ], permissions: getUserPermissions(docketClerk1User), + trialSessionsPage: trialSessionsPageState, }, }); - expect(result.showSessionStatus).toEqual(false); + expect(result.trialSessionJudges).toEqual([ + { name: 'I am not a legacy judge', role: ROLES.judge }, + { name: 'I am a legacy judge', role: ROLES.legacyJudge }, + ]); }); - it('should NOT show the Session Status column for `closed` sessions', () => { + it('returns only current judges when the current tab is new', () => { + trialSessionsPageState.filters.currentTab = 'new'; const result = runCompute(trialSessionsHelper, { state: { - currentViewMetadata: { - trialSessions: { - tab: 'closed', - }, - }, + judges: [ + { name: 'I am not a legacy judge part 2', role: ROLES.judge }, + ], + legacyAndCurrentJudges: [ + { name: 'I am not a legacy judge', role: ROLES.judge }, + { name: 'I am a legacy judge', role: ROLES.legacyJudge }, + ], permissions: getUserPermissions(docketClerk1User), + trialSessionsPage: trialSessionsPageState, }, }); - expect(result.showSessionStatus).toEqual(false); + expect(result.trialSessionJudges).toEqual([ + { name: 'I am not a legacy judge part 2', role: ROLES.judge }, + ]); }); - }); - describe('additionalColumnsShown', () => { - it('should show 0 additional table columns for `new` sessions', () => { + it('returns only current judges when the session status is open', () => { + trialSessionsPageState.filters.sessionStatus = SESSION_STATUS_TYPES.open; const result = runCompute(trialSessionsHelper, { state: { - currentViewMetadata: { - trialSessions: { - tab: 'new', - }, - }, + judges: [ + { name: 'I am not a legacy judge part 2', role: ROLES.judge }, + ], + legacyAndCurrentJudges: [ + { name: 'I am not a legacy judge', role: ROLES.judge }, + { name: 'I am a legacy judge', role: ROLES.legacyJudge }, + ], permissions: getUserPermissions(docketClerk1User), + trialSessionsPage: trialSessionsPageState, }, }); - expect(result.additionalColumnsShown).toEqual(0); + expect(result.trialSessionJudges).toEqual([ + { name: 'I am not a legacy judge part 2', role: ROLES.judge }, + ]); }); + }); - it('should show 0 additional table columns for `closed` sessions', () => { + describe('showNewTrialSession', () => { + it('should return showNewTrialSession as true when current user has CREATE_TRIAL_SESSION permission', () => { const result = runCompute(trialSessionsHelper, { state: { - currentViewMetadata: { - trialSessions: { - tab: 'closed', - }, - }, permissions: getUserPermissions(docketClerk1User), + trialSessionsPage: trialSessionsPageState, }, }); - expect(result.additionalColumnsShown).toEqual(0); + expect(result.showNewTrialSession).toEqual(true); }); - it('should show 1 additional table column for `open` sessions', () => { + it('should return showNewTrialSession as false when current user does not have CREATE_TRIAL_SESSION permission', () => { const result = runCompute(trialSessionsHelper, { state: { - currentViewMetadata: { - trialSessions: { - tab: 'open', - }, - }, - permissions: getUserPermissions(docketClerk1User), + permissions: getUserPermissions(judgeUser), + trialSessionsPage: trialSessionsPageState, }, }); - expect(result.additionalColumnsShown).toEqual(1); + expect(result.showNewTrialSession).toEqual(false); }); + }); - it('should show 1 additional table column for `all` sessions', () => { - const result = runCompute(trialSessionsHelper, { - state: { - currentViewMetadata: { - trialSessions: { - tab: 'all', - }, + describe('trialSessionRows', () => { + describe('filters', () => { + it('should filter trial sessions by judge', () => { + trialSession1.judge!.userId = '1'; + trialSession2.judge!.userId = '2'; + trialSessionsPageState.trialSessions = [trialSession1, trialSession2]; + trialSessionsPageState.filters.judgeId = '1'; + + const result = runCompute(trialSessionsHelper, { + state: { + permissions: getUserPermissions(docketClerk1User), + trialSessionsPage: trialSessionsPageState, }, - permissions: getUserPermissions(docketClerk1User), - }, + }); + + const trialSessionsOnly = + result.trialSessionRows.filter(isTrialSessionRow); + expect(trialSessionsOnly.length).toEqual(1); }); - expect(result.additionalColumnsShown).toEqual(1); - }); - }); + it('should only show trial sessions who do not have a judge when the judge filter is "unassigned"', () => { + trialSession1.judge = undefined; + trialSession2.judge!.userId = '2'; + trialSessionsPageState.trialSessions = [trialSession1, trialSession2]; + trialSessionsPageState.filters.judgeId = 'unassigned'; - describe('showUnassignedJudgeFilter', () => { - it('should show the `unassigned` judge filter for `new` sessions', () => { - const result = runCompute(trialSessionsHelper, { - state: { - currentViewMetadata: { - trialSessions: { - tab: 'new', - }, + const result = runCompute(trialSessionsHelper, { + state: { + permissions: getUserPermissions(docketClerk1User), + trialSessionsPage: trialSessionsPageState, }, - permissions: getUserPermissions(docketClerk1User), - }, + }); + + const trialSessionsOnly = + result.trialSessionRows.filter(isTrialSessionRow); + expect(trialSessionsOnly.length).toEqual(1); + expect(trialSessionsOnly[0].trialSessionId).toEqual( + trialSession1.trialSessionId, + ); }); - expect(result.showUnassignedJudgeFilter).toBeTruthy(); - }); + it('should not filter trial sessions by judge when judge filter is All', () => { + trialSessionsPageState.trialSessions = [trialSession1, trialSession2]; + trialSessionsPageState.filters.judgeId = 'All'; - it('should NOT show the `unassigned` judge filter for `open` sessions', () => { - const result = runCompute(trialSessionsHelper, { - state: { - currentViewMetadata: { - trialSessions: { - tab: 'open', - }, + const result = runCompute(trialSessionsHelper, { + state: { + permissions: getUserPermissions(docketClerk1User), + trialSessionsPage: trialSessionsPageState, }, - permissions: getUserPermissions(docketClerk1User), - }, + }); + + const trialSessionsOnly = + result.trialSessionRows.filter(isTrialSessionRow); + expect(trialSessionsOnly.length).toEqual(2); }); - expect(result.showUnassignedJudgeFilter).toBeFalsy(); - }); + it('should show open and closed trial sessions when the current tab is calendared', () => { + trialSession1.isCalendared = false; + trialSession2.isCalendared = true; + trialSession2.sessionStatus = SESSION_STATUS_TYPES.open; + trialSessionsPageState.trialSessions = [trialSession1, trialSession2]; - it('should NOT show the `unassigned` judge filter for `closed` sessions', () => { - const result = runCompute(trialSessionsHelper, { - state: { - currentViewMetadata: { - trialSessions: { - tab: 'close', - }, + const result = runCompute(trialSessionsHelper, { + state: { + permissions: getUserPermissions(docketClerk1User), + trialSessionsPage: trialSessionsPageState, }, - permissions: getUserPermissions(docketClerk1User), - }, + }); + + const trialSessionsOnly = + result.trialSessionRows.filter(isTrialSessionRow); + + expect(trialSessionsOnly[0].trialSessionId).toEqual( + trialSession2.trialSessionId, + ); }); - expect(result.showUnassignedJudgeFilter).toBeFalsy(); - }); + it('should show remote proceeding types when proceeding type is remote', () => { + trialSession1.proceedingType = TRIAL_SESSION_PROCEEDING_TYPES.remote; + trialSession2.proceedingType = TRIAL_SESSION_PROCEEDING_TYPES.inPerson; + trialSessionsPageState.trialSessions = [trialSession1, trialSession2]; + trialSessionsPageState.filters.proceedingType = 'Remote'; - it('should NOT show the `unassigned` judge filter for `all` sessions', () => { - const result = runCompute(trialSessionsHelper, { - state: { - currentViewMetadata: { - trialSessions: { - tab: 'all', - }, + const result = runCompute(trialSessionsHelper, { + state: { + permissions: getUserPermissions(docketClerk1User), + trialSessionsPage: trialSessionsPageState, }, - permissions: getUserPermissions(docketClerk1User), - }, + }); + + const trialSessionsOnly = + result.trialSessionRows.filter(isTrialSessionRow); + + expect(trialSessionsOnly[0].trialSessionId).toEqual( + trialSession1.trialSessionId, + ); }); - expect(result.showUnassignedJudgeFilter).toBeFalsy(); - }); - }); + it('should show in person proceeding types when proceeding type is in person', () => { + trialSession1.proceedingType = TRIAL_SESSION_PROCEEDING_TYPES.remote; + trialSession2.proceedingType = TRIAL_SESSION_PROCEEDING_TYPES.inPerson; + trialSessionsPageState.trialSessions = [trialSession1, trialSession2]; + trialSessionsPageState.filters.proceedingType = 'In Person'; - describe('trialSessionJudges', () => { - it('returns only non-legacy judges when state.currentViewMetadata.trialSessions.tab is Open', () => { - const result = runCompute(trialSessionsHelper, { - state: { - currentViewMetadata: { - trialSessions: { - tab: 'open', - }, + const result = runCompute(trialSessionsHelper, { + state: { + permissions: getUserPermissions(docketClerk1User), + trialSessionsPage: trialSessionsPageState, }, - judges: [ - { name: 'I am not a legacy judge part 2', role: ROLES.judge }, - ], - legacyAndCurrentJudges: [ - { name: 'I am not a legacy judge', role: ROLES.judge }, - { name: 'I am a legacy judge', role: ROLES.legacyJudge }, - ], - permissions: getUserPermissions(docketClerk1User), - }, + }); + + const trialSessionsOnly = + result.trialSessionRows.filter(isTrialSessionRow); + + expect(trialSessionsOnly[0].trialSessionId).toEqual( + trialSession2.trialSessionId, + ); }); - expect(result.trialSessionJudges).toMatchObject( - expect.arrayContaining([ - expect.objectContaining({ - name: 'I am not a legacy judge part 2', - role: ROLES.judge, - }), - ]), - ); - expect(result.trialSessionJudges).not.toMatchObject( - expect.arrayContaining([ - expect.objectContaining({ - name: 'I am a legacy judge', - role: ROLES.legacyJudge, - }), - ]), - ); - }); + it('should show open trial sessions when session status filter is open', () => { + trialSession1.sessionStatus = SESSION_STATUS_TYPES.open; + trialSession1.isCalendared = true; + trialSession2.sessionStatus = SESSION_STATUS_TYPES.closed; + trialSession2.isCalendared = true; + trialSessionsPageState.filters.sessionStatus = + SESSION_STATUS_TYPES.open; + trialSessionsPageState.trialSessions = [trialSession1, trialSession2]; + + const result = runCompute(trialSessionsHelper, { + state: { + permissions: getUserPermissions(docketClerk1User), + trialSessionsPage: trialSessionsPageState, + }, + }); + + const trialSessionsOnly = + result.trialSessionRows.filter(isTrialSessionRow); + expect(trialSessionsOnly.length).toEqual(1); + expect(trialSessionsOnly[0].trialSessionId).toEqual( + trialSession1.trialSessionId, + ); + }); - it('returns only non-legacy judges when state.currentViewMetadata.trialSessions.tab is New', () => { - const result = runCompute(trialSessionsHelper, { - state: { - currentViewMetadata: { - trialSessions: { - tab: 'new', - }, + it('should show closed trial sessions when session status filter is closed', () => { + trialSession1.sessionStatus = SESSION_STATUS_TYPES.closed; + trialSession1.isCalendared = true; + trialSession2.sessionStatus = SESSION_STATUS_TYPES.open; + trialSession2.isCalendared = true; + trialSessionsPageState.filters.sessionStatus = + SESSION_STATUS_TYPES.closed; + trialSessionsPageState.trialSessions = [trialSession1, trialSession2]; + + const result = runCompute(trialSessionsHelper, { + state: { + permissions: getUserPermissions(docketClerk1User), + trialSessionsPage: trialSessionsPageState, }, - judges: [ - { name: 'I am not a legacy judge part 2', role: ROLES.judge }, - ], - legacyAndCurrentJudges: [ - { name: 'I am not a legacy judge', role: ROLES.judge }, - { name: 'I am a legacy judge', role: ROLES.legacyJudge }, - ], - permissions: getUserPermissions(docketClerk1User), - }, + }); + + const trialSessionsOnly = + result.trialSessionRows.filter(isTrialSessionRow); + expect(trialSessionsOnly.length).toEqual(1); + expect(trialSessionsOnly[0].trialSessionId).toEqual( + trialSession1.trialSessionId, + ); }); - expect(result.trialSessionJudges).toMatchObject( - expect.arrayContaining([ - expect.objectContaining({ - name: 'I am not a legacy judge part 2', - role: ROLES.judge, - }), - ]), - ); - expect(result.trialSessionJudges).not.toMatchObject( - expect.arrayContaining([ - expect.objectContaining({ - name: 'I am a legacy judge', - role: ROLES.legacyJudge, - }), - ]), - ); - }); + it('should ignore session status filter when the current tab is new', () => { + trialSession1.sessionStatus = SESSION_STATUS_TYPES.closed; + trialSession1.isCalendared = true; + trialSession2.sessionStatus = SESSION_STATUS_TYPES.new; + trialSession2.isCalendared = false; + trialSessionsPageState.filters.sessionStatus = + SESSION_STATUS_TYPES.closed; + trialSessionsPageState.filters.currentTab = 'new'; + trialSessionsPageState.trialSessions = [trialSession1, trialSession2]; + + const result = runCompute(trialSessionsHelper, { + state: { + permissions: getUserPermissions(docketClerk1User), + trialSessionsPage: trialSessionsPageState, + }, + }); + + const trialSessionsOnly = + result.trialSessionRows.filter(isTrialSessionRow); + expect(trialSessionsOnly.length).toEqual(1); + expect(trialSessionsOnly[0].trialSessionId).toEqual( + trialSession2.trialSessionId, + ); + }); - it('returns all current and legacy judges when state.currentViewMetadata.trialSessions.tab is Closed', () => { - const result = runCompute(trialSessionsHelper, { - state: { - currentViewMetadata: { - trialSessions: { - tab: 'closed', - }, + it('should show regular trial sessions when session type filter is regular', () => { + trialSession1.sessionType = SESSION_TYPES.regular; + trialSession2.sessionType = SESSION_TYPES.hybridSmall; + trialSessionsPageState.filters.sessionTypes = SESSION_TYPES.regular; + trialSessionsPageState.trialSessions = [trialSession1, trialSession2]; + + const result = runCompute(trialSessionsHelper, { + state: { + permissions: getUserPermissions(docketClerk1User), + trialSessionsPage: trialSessionsPageState, }, - judges: [ - { name: 'I am not a legacy judge part 2', role: ROLES.judge }, - ], - legacyAndCurrentJudges: [ - { name: 'I am not a legacy judge', role: ROLES.judge }, - { name: 'I am a legacy judge', role: ROLES.legacyJudge }, - ], - permissions: getUserPermissions(docketClerk1User), - }, + }); + + const trialSessionsOnly = + result.trialSessionRows.filter(isTrialSessionRow); + expect(trialSessionsOnly.length).toEqual(1); + expect(trialSessionsOnly[0].trialSessionId).toEqual( + trialSession1.trialSessionId, + ); }); - expect(result.trialSessionJudges).toMatchObject( - expect.arrayContaining([ - expect.objectContaining({ - name: 'I am not a legacy judge', - role: ROLES.judge, - }), - ]), - ); - expect(result.trialSessionJudges).toMatchObject( - expect.arrayContaining([ - expect.objectContaining({ - name: 'I am a legacy judge', - role: ROLES.legacyJudge, - }), - ]), - ); + it('should show trial sessions in honolulu when trial sessions when trial location filter is honolulu', () => { + trialSession1.trialLocation = 'Honolulu, Hawaii'; + trialSession2.trialLocation = 'Jacksonville, Florida'; + trialSessionsPageState.filters.trialLocations = 'Honolulu, Hawaii'; + trialSessionsPageState.trialSessions = [trialSession1, trialSession2]; + + const result = runCompute(trialSessionsHelper, { + state: { + permissions: getUserPermissions(docketClerk1User), + trialSessionsPage: trialSessionsPageState, + }, + }); + + const trialSessionsOnly = + result.trialSessionRows.filter(isTrialSessionRow); + expect(trialSessionsOnly.length).toEqual(1); + expect(trialSessionsOnly[0].trialSessionId).toEqual( + trialSession1.trialSessionId, + ); + }); }); - it('returns all current and legacy judges when state.currentViewMetadata.trialSessions.tab is All', () => { - const result = runCompute(trialSessionsHelper, { - state: { - currentViewMetadata: { - trialSessions: { - tab: 'all', - }, + describe('formatting', () => { + it('should format trialSessions startDate, endDate, noticeIssuedDate', () => { + trialSession1.noticeIssuedDate = '2020-05-03T21:00:00.000Z'; + trialSession1.startDate = '2020-05-03T21:00:00.000Z'; + trialSession1.estimatedEndDate = '2020-05-03T21:00:00.000Z'; + trialSessionsPageState.trialSessions = [trialSession1]; + + const result = runCompute(trialSessionsHelper, { + state: { + permissions: getUserPermissions(docketClerk1User), + trialSessionsPage: trialSessionsPageState, }, - judges: [ - { name: 'I am not a legacy judge part 2', role: ROLES.judge }, - ], - legacyAndCurrentJudges: [ - { name: 'I am not a legacy judge', role: ROLES.judge }, - { name: 'I am a legacy judge', role: ROLES.legacyJudge }, - ], - permissions: getUserPermissions(docketClerk1User), - }, + }); + + const trialSessionsOnly = + result.trialSessionRows.filter(isTrialSessionRow); + expect(trialSessionsOnly[0]).toMatchObject({ + formattedEstimatedEndDate: '05/03/20', + formattedNoticeIssuedDate: '05/03/2020', + formattedStartDate: '05/03/20', + }); }); - expect(result.trialSessionJudges).toMatchObject( - expect.arrayContaining([ - expect.objectContaining({ - name: 'I am not a legacy judge', - role: ROLES.judge, - }), - ]), - ); - expect(result.trialSessionJudges).toMatchObject( - expect.arrayContaining([ - expect.objectContaining({ - name: 'I am a legacy judge', - role: ROLES.legacyJudge, - }), - ]), - ); - }); - }); + it('should set userIsAssignedToSession false for all sessions if there is no associated judgeUser', () => { + trialSessionsPageState.trialSessions = [trialSession1, trialSession2]; - describe('showNewTrialSession', () => { - it('should return showNewTrialSession as true when current user has CREATE_TRIAL_SESSION permission', () => { - const result = runCompute(trialSessionsHelper, { - state: { - currentViewMetadata: { - trialSessions: { - tab: 'open', + const result = runCompute(trialSessionsHelper, { + state: { + judgeUser: {}, + permissions: getUserPermissions(docketClerk1User), + trialSessionsPage: trialSessionsPageState, + }, + }); + + const trialSessionsOnly = + result.trialSessionRows.filter(isTrialSessionRow); + trialSessionsOnly.forEach(t => { + expect(t.userIsAssignedToSession).toEqual(false); + }); + }); + + it('should set userIsAssignedToSession true for all sessions the judge user is assigned to', () => { + trialSession1.judge!.userId = '1'; + trialSession2.judge!.userId = '2'; + trialSessionsPageState.trialSessions = [trialSession1, trialSession2]; + + const result = runCompute(trialSessionsHelper, { + state: { + judgeUser: { + userId: '1', }, + permissions: getUserPermissions(docketClerk1User), + trialSessionsPage: trialSessionsPageState, }, - permissions: getUserPermissions(docketClerk1User), - }, + }); + + const trialSessionsOnly = + result.trialSessionRows.filter(isTrialSessionRow); + trialSessionsOnly.forEach(t => { + if (t.trialSessionId === trialSession1.trialSessionId) { + expect(t.userIsAssignedToSession).toEqual(true); + } else { + expect(t.userIsAssignedToSession).toEqual(false); + } + }); }); - expect(result.showNewTrialSession).toEqual(true); + it('should show an alertMessage for NOTT reminders when the user has not dismissed the alert and the start day is within the reminder range', () => { + trialSession1.dismissedAlertForNOTT = false; + trialSession1.isCalendared = true; + trialSession1.startDate = calculateISODate({ + howMuch: 29, + units: 'days', + }); + trialSessionsPageState.trialSessions = [trialSession1]; + + const result = runCompute(trialSessionsHelper, { + state: { + permissions: getUserPermissions(docketClerk1User), + trialSessionsPage: trialSessionsPageState, + }, + }); + + const trialSessionsOnly = + result.trialSessionRows.filter(isTrialSessionRow); + expect(trialSessionsOnly[0].alertMessageForNOTT).toEqual( + `The 30-day notice is due by ${formatNow(FORMATS.MMDDYY)}`, + ); + expect(trialSessionsOnly[0].showAlertForNOTTReminder).toEqual(true); + }); }); - it('should return showNewTrialSession as false when current user does not have CREATE_TRIAL_SESSION permission', () => { - const result = runCompute(trialSessionsHelper, { - state: { - currentViewMetadata: { - trialSessions: { - tab: 'open', - }, + describe('sorting', () => { + it('should order trial sessions by start date from oldest to newest', () => { + trialSession1.startDate = '2022-03-01T21:00:00.000Z'; + trialSession2.startDate = '2020-03-01T21:00:00.000Z'; + trialSessionsPageState.trialSessions = [trialSession1, trialSession2]; + + const result = runCompute(trialSessionsHelper, { + state: { + permissions: getUserPermissions(docketClerk1User), + trialSessionsPage: trialSessionsPageState, }, - permissions: getUserPermissions(judgeUser), - }, + }); + + const trialSessionsOnly = + result.trialSessionRows.filter(isTrialSessionRow); + expect(trialSessionsOnly.length).toEqual(2); + expect(trialSessionsOnly[0].trialSessionId).toEqual( + trialSession2.trialSessionId, + ); + expect(trialSessionsOnly[1].trialSessionId).toEqual( + trialSession1.trialSessionId, + ); }); + }); - expect(result.showNewTrialSession).toEqual(false); + describe('trial session weeks', () => { + it('should insert one trialSessionWeek row when two trial sessions are within the same week(week starts on Monday EST)', () => { + trialSession1.startDate = '2024-09-03T21:00:00.000Z'; // A Tuesday + trialSession2.startDate = '2024-09-05T21:00:00.000Z'; // A Thursday + trialSessionsPageState.trialSessions = [trialSession1, trialSession2]; + + const result = runCompute(trialSessionsHelper, { + state: { + permissions: getUserPermissions(docketClerk1User), + trialSessionsPage: trialSessionsPageState, + }, + }); + + const trialSessionWeeks = + result.trialSessionRows.filter(isTrialSessionWeek); + expect(trialSessionWeeks).toEqual([ + { + formattedSessionWeekStartDate: 'September 2, 2024', + sessionWeekStartDate: '2024-09-02T04:00:00.000+00:00', + }, + ]); + }); + + it('should insert two trialSessionWeek rows when two trial sessions are not within the same week(week starts on Monday EST)', () => { + trialSession1.startDate = '2024-09-03T21:00:00.000Z'; // A Tuesday + trialSession2.startDate = '2024-09-12T21:00:00.000Z'; // A Thursday next week + trialSessionsPageState.trialSessions = [trialSession1, trialSession2]; + + const result = runCompute(trialSessionsHelper, { + state: { + permissions: getUserPermissions(docketClerk1User), + trialSessionsPage: trialSessionsPageState, + }, + }); + + const trialSessionWeeks = + result.trialSessionRows.filter(isTrialSessionWeek); + expect(trialSessionWeeks).toEqual([ + { + formattedSessionWeekStartDate: 'September 2, 2024', + sessionWeekStartDate: '2024-09-02T04:00:00.000+00:00', + }, + { + formattedSessionWeekStartDate: 'September 9, 2024', + sessionWeekStartDate: '2024-09-09T04:00:00.000+00:00', + }, + ]); + }); }); }); }); diff --git a/web-client/src/presenter/computeds/trialSessionsHelper.ts b/web-client/src/presenter/computeds/trialSessionsHelper.ts index 41a8fe8752e..184bbcd7ea4 100644 --- a/web-client/src/presenter/computeds/trialSessionsHelper.ts +++ b/web-client/src/presenter/computeds/trialSessionsHelper.ts @@ -1,37 +1,321 @@ +import { + FORMATS, + createDateAtStartOfWeekEST, + createISODateString, + formatDateString, + subtractISODates, +} from '@shared/business/utilities/DateHandler'; import { Get } from 'cerebral'; +import { InputOption } from '@web-client/ustc-ui/Utils/types'; +import { RawUser } from '@shared/business/entities/User'; +import { + SESSION_STATUS_TYPES, + SESSION_TYPES, + TRIAL_CITIES, +} from '@shared/business/entities/EntityConstants'; +import { TrialSession } from '@shared/business/entities/trialSessions/TrialSession'; +import { TrialSessionInfoDTO } from '@shared/business/dto/trialSessions/TrialSessionInfoDTO'; +import { + TrialSessionsFilters, + initialTrialSessionPageState, +} from '@web-client/presenter/state/trialSessionsPageState'; +import { sortBy } from 'lodash'; import { state } from '@web-client/presenter/app.cerebral'; -export const trialSessionsHelper = (get: Get): any => { +export const trialSessionsHelper = ( + get: Get, +): { + isResetFiltersDisabled: boolean; + showNewTrialSession: boolean; + showNoticeIssued: boolean; + showSessionStatus: boolean; + showUnassignedJudgeFilter: boolean; + trialSessionJudgeOptions: InputOption<{ name: string; userId: string }>[]; + trialSessionRows: (TrialSessionRow | TrialSessionWeek)[]; + sessionTypeOptions: InputOption[]; + searchableTrialLocationOptions: InputOption[]; + trialCitiesByState: InputOption[]; + trialSessionsCount: number; + totalPages: number; +} => { const permissions = get(state.permissions)!; - const status = get(state.screenMetadata.trialSessionFilters.status); - const tab = - get(state.currentViewMetadata.trialSessions.tab) || - (status && status.toLowerCase()); - - const isNewTab = tab === 'new'; - const isOpenTab = tab === 'open' || tab === undefined; - const isAllTab = tab === 'all'; - - let additionalColumnsShown = 0; - if (isOpenTab || isAllTab) { - additionalColumnsShown = 1; - } + const trialSessions = get(state.trialSessionsPage.trialSessions); + const filters = get(state.trialSessionsPage.filters); + const judge = get(state.judgeUser); + + const pageSize = 1; - const showCurrentJudgesOnly = isNewTab || isOpenTab; + const showCurrentJudgesOnly = + filters.currentTab === 'new' || + filters.sessionStatus === SESSION_STATUS_TYPES.open; - let trialSessionJudges; + let trialSessionJudges: { name: string; userId: string }[]; // 10409 TODO BUG. The judge options is not updating correctly. Showing legacy when it should not. if (showCurrentJudgesOnly) { trialSessionJudges = get(state.judges); } else { trialSessionJudges = get(state.legacyAndCurrentJudges); } + const userHasSelectedAFilter = + filters.proceedingType !== + initialTrialSessionPageState.filters.proceedingType || + filters.sessionStatus !== + initialTrialSessionPageState.filters.sessionStatus || + Object.keys(filters.judges).length > 0 || + Object.keys(filters.sessionTypes).length > 0 || + Object.keys(filters.trialLocations).length > 0 || + !!filters.startDate || + !!filters.endDate; + + const sessionTypeOptions = Object.values(SESSION_TYPES).map(sessionType => ({ + label: sessionType, + value: sessionType, + })); + + const trialSessionJudgeOptions = trialSessionJudges.map( + trialSessionJudge => ({ + label: trialSessionJudge.name, + value: { name: trialSessionJudge.name, userId: trialSessionJudge.userId }, + }), + ); + const trialCities = sortBy(TRIAL_CITIES.ALL, ['state', 'city']); + const searchableTrialLocationOptions: InputOption[] = []; + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const states: InputOption[] = trialCities.reduce( + (listOfStates: InputOption[], cityStatePair) => { + const existingState = listOfStates.find( + trialState => trialState.label === cityStatePair.state, + ); + const cityOption: InputOption = { + label: `${cityStatePair.city}, ${cityStatePair.state}`, + value: `${cityStatePair.city}, ${cityStatePair.state}`, + }; + if (existingState) { + existingState.options?.push(cityOption); + } else { + listOfStates.push({ + label: cityStatePair.state, + options: [cityOption], + }); + } + searchableTrialLocationOptions.push(cityOption); + return listOfStates; + }, + [], + ); + + const filteredTrialSessions = filterAndSortTrialSessions({ + filters, + trialSessions, + }); + const trialSessionPage = filteredTrialSessions.slice( + filters.pageNumber * pageSize, + filters.pageNumber * pageSize + pageSize, + ); + const trialSessionRows = formatTrialSessions({ + judgeAssociatedToUser: judge, + trialSessions: trialSessionPage, + }); + return { - additionalColumnsShown, + isResetFiltersDisabled: !userHasSelectedAFilter, + searchableTrialLocationOptions, + sessionTypeOptions, showNewTrialSession: permissions.CREATE_TRIAL_SESSION, - showNoticeIssued: isOpenTab, - showSessionStatus: isAllTab, - showUnassignedJudgeFilter: isNewTab, - trialSessionJudges, + showNoticeIssued: filters.currentTab === 'calendared', + showSessionStatus: filters.currentTab === 'calendared', + showUnassignedJudgeFilter: filters.currentTab === 'new', + totalPages: Math.ceil(filteredTrialSessions.length / pageSize), + trialCitiesByState: states, + trialSessionJudgeOptions, + trialSessionRows, + trialSessionsCount: filteredTrialSessions.length, }; }; + +const filterAndSortTrialSessions = ({ + filters, + trialSessions, +}: { + trialSessions: TrialSessionInfoDTO[]; + filters: TrialSessionsFilters; +}): TrialSessionInfoDTO[] => { + return trialSessions + .filter(trialSession => { + const isCalendaredFilter = filters.currentTab === 'calendared'; + return trialSession.isCalendared === isCalendaredFilter; + }) + .filter(trialSession => { + const selectedJudges = Object.values(filters.judges); + if (selectedJudges.length === 0) return true; + const trialSessionHasJudge = selectedJudges.some(judgeFilter => { + if (judgeFilter.userId === 'unassigned') { + return !trialSession.judge?.userId; + } + return judgeFilter.userId === trialSession.judge?.userId; + }); + + return trialSessionHasJudge; + }) + .filter(trialSession => { + if (filters.proceedingType === 'All') return true; + return trialSession.proceedingType === filters.proceedingType; + }) + .filter(trialSession => { + if (filters.currentTab === 'new') return true; + if (filters.sessionStatus === 'All') return true; + return filters.sessionStatus === trialSession.sessionStatus; + }) + .filter(trialSession => { + if (Object.values(filters.sessionTypes).length === 0) return true; + return !!filters.sessionTypes[trialSession.sessionType]; + }) + .filter(trialSession => { + if (Object.values(filters.trialLocations).length === 0) return true; + return !!filters.trialLocations[trialSession.trialLocation || '']; + }) + .filter(trialSession => { + if (!filters.startDate) return true; + const filterIsoStartDate = createISODateString( + filters.startDate, + FORMATS.MMDDYYYY, + ); + return trialSession.startDate >= filterIsoStartDate; + }) + .filter(trialSession => { + if (!filters.endDate) return true; + const filterIsoEndDate = createISODateString( + filters.endDate, + FORMATS.MMDDYYYY, + ); + return trialSession.startDate <= filterIsoEndDate; + }) + .sort((sessionA, sessionB) => { + return sessionA.startDate.localeCompare(sessionB.startDate); + }); +}; + +const formatTrialSessions = ({ + judgeAssociatedToUser, + trialSessions, +}: { + trialSessions: TrialSessionInfoDTO[]; + judgeAssociatedToUser?: RawUser; +}): (TrialSessionRow | TrialSessionWeek)[] => { + const trialSessionRows: TrialSessionRow[] = trialSessions.map( + trialSession => { + const showAlertForNOTTReminder = + !trialSession.dismissedAlertForNOTT && + TrialSession.isStartDateWithinNOTTReminderRange({ + isCalendared: trialSession.isCalendared, + startDate: trialSession.startDate, + }); + + const alertMessageForNOTT = showAlertForNOTTReminder + ? `The 30-day notice is due by ${thirtyDaysBeforeTrial(trialSession.startDate)}` + : ''; + const formattedEstimatedEndDate = formatDateString( + trialSession.estimatedEndDate, + FORMATS.MMDDYY, + ); + const formattedNoticeIssuedDate = formatDateString( + trialSession.noticeIssuedDate, + FORMATS.MMDDYYYY, + ); + const formattedStartDate = formatDateString( + trialSession.startDate, + FORMATS.MMDDYY, + ); + const isJudgeUserAssigned = !!( + trialSession.judge?.userId === judgeAssociatedToUser?.userId && + judgeAssociatedToUser?.userId + ); + /* TODO 10409: There may be a bug in userIsAssignedToSession to session as the previous formatted needed a trialClerk to compute userIsAssignedToSession. + Look at how formattedTrialSessions.ts calculates userIsAssignedToSession for reference + */ + const userIsAssignedToSession = isJudgeUserAssigned; + + return { + alertMessageForNOTT, + formattedEstimatedEndDate, + formattedNoticeIssuedDate, + formattedStartDate, + judge: trialSession.judge, + proceedingType: trialSession.proceedingType, + sessionStatus: trialSession.sessionStatus, + sessionType: trialSession.sessionType, + showAlertForNOTTReminder, + startDate: trialSession.startDate, + swingSession: !!trialSession.swingSession, + trialLocation: trialSession.trialLocation || '', + trialSessionId: trialSession.trialSessionId || '', + userIsAssignedToSession, + }; + }, + ); + + const trialSessionWithStartWeeks: (TrialSessionRow | TrialSessionWeek)[] = []; + + let lastSessionWeek: TrialSessionWeek = { + formattedSessionWeekStartDate: '', + sessionWeekStartDate: '', + }; + trialSessionRows.forEach(trialSession => { + const trialSessionStartOfWeek = createDateAtStartOfWeekEST( + trialSession.startDate, + FORMATS.ISO, + ); + if (lastSessionWeek.sessionWeekStartDate < trialSessionStartOfWeek) { + const formattedSessionWeekStartDate = createDateAtStartOfWeekEST( + trialSession.startDate, + FORMATS.MONTH_DAY_YEAR, + ); + + lastSessionWeek = { + formattedSessionWeekStartDate, + sessionWeekStartDate: trialSessionStartOfWeek, + }; + + trialSessionWithStartWeeks.push(lastSessionWeek); + } + trialSessionWithStartWeeks.push(trialSession); + }); + + return trialSessionWithStartWeeks; +}; + +export const thirtyDaysBeforeTrial = (startDate?: string): string => { + if (!startDate) return ''; + const thirtyDaysBeforeTrialIso = subtractISODates(startDate, { day: 29 }); + + return formatDateString(thirtyDaysBeforeTrialIso, FORMATS.MMDDYY); +}; + +type TrialSessionRow = { + trialSessionId: string; + showAlertForNOTTReminder: boolean; + alertMessageForNOTT: string; + formattedStartDate: string; //MM/DD/YYYY + formattedEstimatedEndDate: string; + swingSession: boolean; + userIsAssignedToSession: boolean; + trialLocation: string; + proceedingType: string; + startDate: string; // ISO format + sessionType: string; + judge?: { name: string; userId: string }; + formattedNoticeIssuedDate: string; + sessionStatus: string; +}; +export function isTrialSessionRow(item: any): item is TrialSessionRow { + return !!item?.trialSessionId; +} + +type TrialSessionWeek = { + sessionWeekStartDate: string; + formattedSessionWeekStartDate: string; +}; +export function isTrialSessionWeek(item: any): item is TrialSessionWeek { + return !!item?.sessionWeekStartDate; +} diff --git a/web-client/src/presenter/presenter.ts b/web-client/src/presenter/presenter.ts index 2372cf1b20e..7e309c474a7 100644 --- a/web-client/src/presenter/presenter.ts +++ b/web-client/src/presenter/presenter.ts @@ -67,6 +67,7 @@ import { clearPreferredTrialCitySequence } from './sequences/clearPreferredTrial import { clearSelectedWorkItemsSequence } from './sequences/clearSelectedWorkItemsSequence'; import { clearStatusReportOrderFormSequence } from './sequences/StatusReportOrder/clearStatusReportOrderFormSequence'; import { clearViewerDocumentToDisplaySequence } from './sequences/clearViewerDocumentToDisplaySequence'; +import { cloneDeep } from 'lodash'; import { closeModalAndNavigateBackSequence } from './sequences/closeModalAndNavigateBackSequence'; import { closeModalAndNavigateSequence } from './sequences/closeModalAndNavigateSequence'; import { closeModalAndNavigateToMaintenanceSequence } from './sequences/closeModalAndNavigateToMaintenanceSequence'; @@ -352,6 +353,7 @@ import { resetHeaderAccordionsSequence } from './sequences/resetHeaderAccordions import { resetIdleTimerSequence } from './sequences/resetIdleTimerSequence'; import { resetPasswordSequence } from '@web-client/presenter/sequences/Login/resetPasswordSequence'; import { resetSecondaryAddressSequence } from './sequences/resetSecondaryAddressSequence'; +import { resetTrialSessionsFiltersSequence } from '@web-client/presenter/sequences/resetTrialSessionsFiltersSequence'; import { retryAsyncRequestSequence } from './sequences/retryAsyncRequestSequence'; import { reviewCaseAssociationRequestSequence } from './sequences/reviewCaseAssociationRequestSequence'; import { reviewExternalDocumentInformationSequence } from './sequences/reviewExternalDocumentInformationSequence'; @@ -406,6 +408,7 @@ import { setSelectedDocumentsForDownloadSequence } from './sequences/setSelected import { setSelectedMessagesSequence } from './sequences/setSelectedMessagesSequence'; import { setTrialSessionCalendarErrorSequence } from '@web-client/presenter/sequences/setTrialSessionCalendarErrorSequence'; import { setTrialSessionCalendarSequence } from './sequences/setTrialSessionCalendarSequence'; +import { setTrialSessionsFiltersSequence } from '@web-client/presenter/sequences/setTrialSessionsFiltersSequence'; import { setViewerCorrespondenceToDisplaySequence } from './sequences/setViewerCorrespondenceToDisplaySequence'; import { setViewerDocumentToDisplaySequence } from './sequences/setViewerDocumentToDisplaySequence'; import { setViewerDraftDocumentToDisplaySequence } from './sequences/setViewerDraftDocumentToDisplaySequence'; @@ -951,8 +954,9 @@ export const presenterSequences = { gotoTrialSessionPlanningReportSequence as unknown as Function, gotoTrialSessionWorkingCopySequence: gotoTrialSessionWorkingCopySequence as unknown as Function, - gotoTrialSessionsSequence: gotoTrialSessionsSequence as unknown as Function, - gotoUpdatedPetitionFlowSequence, + gotoTrialSessionsSequence, + gotoUpdatedPetitionFlowSequence: + gotoUpdatedPetitionFlowSequence as unknown as Function, gotoUploadCorrespondenceDocumentSequence: gotoUploadCorrespondenceDocumentSequence as unknown as Function, gotoUploadCourtIssuedDocumentSequence: @@ -1193,6 +1197,7 @@ export const presenterSequences = { resetIdleTimerSequence: resetIdleTimerSequence as unknown as Function, resetPasswordSequence, resetSecondaryAddressSequence, + resetTrialSessionsFiltersSequence, retryAsyncRequestSequence: retryAsyncRequestSequence as unknown as Function, reviewCaseAssociationRequestSequence: reviewCaseAssociationRequestSequence as unknown as Function, @@ -1274,6 +1279,7 @@ export const presenterSequences = { setSelectedMessagesSequence, setTrialSessionCalendarErrorSequence, setTrialSessionCalendarSequence, + setTrialSessionsFiltersSequence, setViewerCorrespondenceToDisplaySequence: setViewerCorrespondenceToDisplaySequence as unknown as Function, setViewerDocumentToDisplaySequence: @@ -1647,7 +1653,7 @@ export const presenter = { ], providers: {} as { applicationContext: ClientApplicationContext; router: {} }, sequences: presenterSequences, - state: initialState, + state: cloneDeep(initialState), }; export type Sequences = typeof presenterSequences; diff --git a/web-client/src/presenter/sequences/gotoTrialSessionsSequence.ts b/web-client/src/presenter/sequences/gotoTrialSessionsSequence.ts index 5dd87fa401d..1e1fd2e0a17 100644 --- a/web-client/src/presenter/sequences/gotoTrialSessionsSequence.ts +++ b/web-client/src/presenter/sequences/gotoTrialSessionsSequence.ts @@ -1,34 +1,35 @@ +import { TrialSessionsFilters } from '@web-client/presenter/state/trialSessionsPageState'; import { clearErrorAlertsAction } from '../actions/clearErrorAlertsAction'; -import { clearScreenMetadataAction } from '../actions/clearScreenMetadataAction'; import { closeMobileMenuAction } from '../actions/closeMobileMenuAction'; import { getJudgeForCurrentUserAction } from '../actions/getJudgeForCurrentUserAction'; import { getNotificationsAction } from '../actions/getNotificationsAction'; import { getTrialSessionsAction } from '../actions/TrialSession/getTrialSessionsAction'; import { getUsersInSectionAction } from '../actions/getUsersInSectionAction'; import { parallel } from 'cerebral/factories'; +import { resetTrialSessionsFiltersAction } from '@web-client/presenter/actions/TrialSession/resetTrialSessionsFiltersAction'; import { setAllAndCurrentJudgesAction } from '../actions/setAllAndCurrentJudgesAction'; import { setJudgeUserAction } from '../actions/setJudgeUserAction'; import { setNotificationsAction } from '../actions/setNotificationsAction'; -import { setTrialSessionsAction } from '../actions/TrialSession/setTrialSessionsAction'; -import { setTrialSessionsFiltersAction } from '../actions/TrialSession/setTrialSessionsFiltersAction'; +import { setTrialSessionsFiltersAction } from '@web-client/presenter/actions/TrialSession/setTrialSessionsFiltersAction'; +import { setTrialSessionsPageAction } from '@web-client/presenter/actions/TrialSession/setTrialSessionsPageAction'; import { setupCurrentPageAction } from '../actions/setupCurrentPageAction'; import { startWebSocketConnectionSequenceDecorator } from '../utilities/startWebSocketConnectionSequenceDecorator'; export const gotoTrialSessionsSequence = startWebSocketConnectionSequenceDecorator([ setupCurrentPageAction('Interstitial'), - clearScreenMetadataAction, + resetTrialSessionsFiltersAction, closeMobileMenuAction, clearErrorAlertsAction, + setTrialSessionsFiltersAction, parallel([ [getJudgeForCurrentUserAction, setJudgeUserAction], [getNotificationsAction, setNotificationsAction], - [getTrialSessionsAction, setTrialSessionsAction], + [getTrialSessionsAction, setTrialSessionsPageAction], [ getUsersInSectionAction({ section: 'judge' }), setAllAndCurrentJudgesAction, ], ]), - setTrialSessionsFiltersAction, setupCurrentPageAction('TrialSessions'), - ]); + ]) as unknown as (props: ActionProps>) => void; diff --git a/web-client/src/presenter/sequences/resetTrialSessionsFiltersSequence.ts b/web-client/src/presenter/sequences/resetTrialSessionsFiltersSequence.ts new file mode 100644 index 00000000000..f9ac27dac2f --- /dev/null +++ b/web-client/src/presenter/sequences/resetTrialSessionsFiltersSequence.ts @@ -0,0 +1,11 @@ +import { resetTrialSessionsFiltersAction } from '@web-client/presenter/actions/TrialSession/resetTrialSessionsFiltersAction'; + +export const resetTrialSessionsFiltersSequence = [ + resetTrialSessionsFiltersAction, +] as unknown as (props?: ResetTrialSessionsFiltersSequence) => void; + +export type ResetTrialSessionsFiltersSequence = + | undefined + | { + currentTab: 'calendared' | 'new'; + }; diff --git a/web-client/src/presenter/sequences/setTrialSessionsFiltersSequence.ts b/web-client/src/presenter/sequences/setTrialSessionsFiltersSequence.ts new file mode 100644 index 00000000000..9016d6c9974 --- /dev/null +++ b/web-client/src/presenter/sequences/setTrialSessionsFiltersSequence.ts @@ -0,0 +1,8 @@ +import { + SetTrialSessionsFilters, + setTrialSessionsFiltersAction, +} from '@web-client/presenter/actions/TrialSession/setTrialSessionsFiltersAction'; + +export const setTrialSessionsFiltersSequence = [ + setTrialSessionsFiltersAction, +] as unknown as (props: SetTrialSessionsFilters) => void; diff --git a/web-client/src/presenter/sequences/signOutSequence.ts b/web-client/src/presenter/sequences/signOutSequence.ts index 709b012240b..c6fa7d2f221 100644 --- a/web-client/src/presenter/sequences/signOutSequence.ts +++ b/web-client/src/presenter/sequences/signOutSequence.ts @@ -1,6 +1,5 @@ import { broadcastLogoutAction } from '../actions/broadcastLogoutAction'; import { clearAlertsAction } from '../actions/clearAlertsAction'; -import { clearLoginFormAction } from '../actions/clearLoginFormAction'; import { clearLogoutTypeAction } from '@web-client/presenter/actions/clearLogoutTypeAction'; import { clearMaintenanceModeAction } from '../actions/clearMaintenanceModeAction'; import { clearUserAction } from '../actions/clearUserAction'; @@ -17,7 +16,6 @@ export const signOutSequence = [ clearAlertsAction, clearUserAction, clearMaintenanceModeAction, - clearLoginFormAction, clearLogoutTypeAction, resetIdleTimerAction, ]; diff --git a/web-client/src/presenter/state.ts b/web-client/src/presenter/state.ts index 643ef2b6fee..d4e2e6187ac 100644 --- a/web-client/src/presenter/state.ts +++ b/web-client/src/presenter/state.ts @@ -85,7 +85,6 @@ import { formattedMessageDetail } from './computeds/formattedMessageDetail'; import { formattedMessages } from './computeds/formattedMessages'; import { formattedPendingItemsHelper } from './computeds/formattedPendingItems'; import { formattedTrialSessionDetails } from './computeds/formattedTrialSessionDetails'; -import { formattedTrialSessions } from './computeds/formattedTrialSessions'; import { formattedWorkQueue } from './computeds/formattedWorkQueue'; import { getAllIrsPractitionersForSelectHelper } from '@web-client/presenter/computeds/TrialSession/getAllIrsPractitionersForSelectHelper'; import { getConstants } from '../getConstants'; @@ -94,6 +93,7 @@ import { headerHelper } from './computeds/headerHelper'; import { initialBlockedCaseReportFilter } from '@web-client/presenter/state/blockedCasesReportState'; import { initialCustomCaseReportState } from './customCaseReportState'; import { initialPendingReportsState } from '@web-client/presenter/state/pendingReportState'; +import { initialTrialSessionPageState } from '@web-client/presenter/state/trialSessionsPageState'; import { initialTrialSessionState } from '@web-client/presenter/state/trialSessionState'; import { initialTrialSessionWorkingCopyState } from '@web-client/presenter/state/trialSessionWorkingCopyState'; import { internalPetitionPartiesHelper } from './computeds/internalPetitionPartiesHelper'; @@ -380,9 +380,6 @@ export const computeds = { formattedTrialSessionDetails as unknown as ReturnType< typeof formattedTrialSessionDetails >, - formattedTrialSessions: formattedTrialSessions as unknown as ReturnType< - typeof formattedTrialSessions - >, formattedWorkQueue: formattedWorkQueue as unknown as ReturnType< typeof formattedWorkQueue >, @@ -850,6 +847,8 @@ export const baseState = { name: '', }, trialSessionWorkingCopy: cloneDeep(initialTrialSessionWorkingCopyState), + trialSessions: [] as any[], // Sometimes trialSessions, sometimes TrialSessionInfoDTO, sometimes ad-hoc trial sessions + trialSessionsPage: cloneDeep(initialTrialSessionPageState), user: cloneDeep(emptyUserState), userContactEditProgress: {} as { inProgress?: boolean }, users: [] as RawUser[], diff --git a/web-client/src/presenter/state/trialSessionsPageState.ts b/web-client/src/presenter/state/trialSessionsPageState.ts new file mode 100644 index 00000000000..a198a815870 --- /dev/null +++ b/web-client/src/presenter/state/trialSessionsPageState.ts @@ -0,0 +1,35 @@ +import { + SESSION_STATUS_TYPES, + TrialSessionProceedingType, + TrialSessionTypes, +} from '@shared/business/entities/EntityConstants'; +import { TrialSessionInfoDTO } from '@shared/business/dto/trialSessions/TrialSessionInfoDTO'; + +const filters: TrialSessionsFilters = { + currentTab: 'calendared' as 'calendared' | 'new', + endDate: '', + judges: {}, + pageNumber: 0, + proceedingType: 'All' as TrialSessionProceedingType, + sessionStatus: SESSION_STATUS_TYPES.open, + sessionTypes: {}, + startDate: '', + trialLocations: {}, +}; + +export const initialTrialSessionPageState = { + filters, + trialSessions: [] as TrialSessionInfoDTO[], +}; + +export type TrialSessionsFilters = { + currentTab: 'calendared' | 'new'; + endDate: string; + pageNumber: number; + judges: Record; + proceedingType: TrialSessionProceedingType | 'All'; + sessionStatus: string; + sessionTypes: Record; + startDate: string; + trialLocations: Record; +}; diff --git a/web-client/src/router.ts b/web-client/src/router.ts index 78e8cf7ac8a..4b6788787bf 100644 --- a/web-client/src/router.ts +++ b/web-client/src/router.ts @@ -1,5 +1,4 @@ /* eslint-disable max-lines */ -import { forEach, set } from 'lodash'; import { setPageTitle } from './presenter/utilities/setPageTitle'; import qs from 'qs'; import route from 'riot-route'; @@ -1148,14 +1147,9 @@ const router = { ifHasAccess( { app, permissionToCheck: ROLE_PERMISSIONS.TRIAL_SESSIONS }, () => { - const trialSessionFilter = {}; - forEach(route.query(), (value, key) => { - set(trialSessionFilter, key, value); - }); + const queryParams = route.query(); setPageTitle('Trial sessions'); - return app.getSequence('gotoTrialSessionsSequence')({ - query: trialSessionFilter, - }); + return app.getSequence('gotoTrialSessionsSequence')(queryParams); }, ), ); diff --git a/web-client/src/styles/_index.scss b/web-client/src/styles/_index.scss index 0c6c375a3af..ca82403b7a6 100644 --- a/web-client/src/styles/_index.scss +++ b/web-client/src/styles/_index.scss @@ -13,3 +13,4 @@ @forward './tables'; @forward './tabs'; @forward './typography'; +@forward './utility-classes'; diff --git a/web-client/src/styles/utility-classes.scss b/web-client/src/styles/utility-classes.scss new file mode 100644 index 00000000000..4b19f6728d6 --- /dev/null +++ b/web-client/src/styles/utility-classes.scss @@ -0,0 +1,14 @@ +@use '../uswds' as *; +@use '../variables' as *; + +.gap-1 { + gap: 8px; +} + +.gap-2 { + gap: 16px; +} + +.gap-3 { + gap: 24px; +} \ No newline at end of file diff --git a/web-client/src/ustc-ui/DateInput/DateRangePickerComponent.tsx b/web-client/src/ustc-ui/DateInput/DateRangePickerComponent.tsx index b8c9054075a..184ccfc756c 100644 --- a/web-client/src/ustc-ui/DateInput/DateRangePickerComponent.tsx +++ b/web-client/src/ustc-ui/DateInput/DateRangePickerComponent.tsx @@ -154,7 +154,7 @@ export const DateRangePickerComponent = ({ htmlFor={`${startName}-date-start`} id={`${startName}-date-start-label`} > - {startLabel || 'Start date'}{' '} + {startLabel}{' '} {showDateHint && MM/DD/YYYY}
@@ -171,7 +171,6 @@ export const DateRangePickerComponent = ({
-
- {endLabel || 'End date'}{' '} + {endLabel}{' '} {showDateHint && MM/DD/YYYY}
diff --git a/web-client/src/ustc-ui/Pagination/Paginator.tsx b/web-client/src/ustc-ui/Pagination/Paginator.tsx index 1beb6346bc7..1dea748b15d 100644 --- a/web-client/src/ustc-ui/Pagination/Paginator.tsx +++ b/web-client/src/ustc-ui/Pagination/Paginator.tsx @@ -1,75 +1,261 @@ -import { Button } from '@web-client/ustc-ui/Button/Button'; import React from 'react'; import classNames from 'classnames'; +const numberOfPaginatorSlots = 7; + +/* +This component is based off of USWDS implementation of a paginator: https://designsystem.digital.gov/components/pagination/ +The totalPages and selected page work similarly to counting arrays. TotalPages is similar to array.length and currentPageIndex is 0 based indexing. +totalPages could be 20 but the maximum value currentPageIndex could be is 19 and the lowest pages is 0. +*/ + export const Paginator = ({ currentPageIndex, onPageChange, totalPages, }: { - totalPages: number; currentPageIndex: number; - onPageChange: (currentPage: number) => void; + totalPages: number; + onPageChange: (selectedPage: number) => any; }) => { - let currentPage = currentPageIndex + 1; + if (totalPages === 0) { + return; + } + const sevenDisplayedSlots: React.JSX.Element[] = []; + + for (let slotNumber = 0; slotNumber < numberOfPaginatorSlots; slotNumber++) { + if (slotNumber >= totalPages) { + continue; + } + const slotComponent = getSlotComponent({ + currentPageIndex, + onPageChange, + slotNumber, + totalPages, + }); + sevenDisplayedSlots.push(slotComponent); + } + + return ( + <> + + + ); +}; - const nextDisabled = currentPage >= totalPages; - const previousDisabled = currentPage <= 1; +Paginator.displayName = 'Paginator'; +const PageButton = (props: { + pageNumber: number; + selected: boolean; + onClick: (selectedPage: number) => void; +}) => { return ( - + + Previous + + + ); }; -Paginator.displayName = 'Paginator'; +const NextPage = (props: { onNextClick: Function }) => { + return ( + <> +
  • + +
  • + + ); +}; + +const PageEllipsis = () => { + return ( + <> +
  • + +
  • + + ); +}; + +function getSlotComponent({ + currentPageIndex, + onPageChange, + slotNumber, + totalPages, +}: { + currentPageIndex: number; + onPageChange: (selectedPage: number) => any; + slotNumber: number; + totalPages: number; +}) { + const isHidingPreviousOptions = + currentPageIndex > 3 && totalPages > numberOfPaginatorSlots; + const isHidingFutureOptions = + totalPages - currentPageIndex > 4 && totalPages > numberOfPaginatorSlots; + if (slotNumber === 0) { + return ( + { + onPageChange(selectedPage); + }} + /> + ); + } + if (slotNumber === 1) { + if (isHidingPreviousOptions) { + return ; + } else { + return ( + { + onPageChange(selectedPage); + }} + /> + ); + } + } + if (slotNumber === 2 || slotNumber === 3 || slotNumber === 4) { + if (!isHidingPreviousOptions) { + return ( + { + onPageChange(selectedPage); + }} + /> + ); + } + if (!isHidingFutureOptions) { + return ( + { + onPageChange(selectedPage); + }} + /> + ); + } + return ( + { + onPageChange(selectedPage); + }} + /> + ); + } + if (slotNumber === 5) { + if (isHidingFutureOptions) { + return ; + } else { + const subtractor = totalPages >= 7 ? 2 : 1; + return ( + { + onPageChange(selectedPage); + }} + /> + ); + } + } + if (slotNumber === 6) { + return ( + { + onPageChange(selectedPage); + }} + /> + ); + } + + return <>; +} diff --git a/web-client/src/ustc-ui/Tabs/Tabs.tsx b/web-client/src/ustc-ui/Tabs/Tabs.tsx index 10d09f3a8dc..52c2726101d 100644 --- a/web-client/src/ustc-ui/Tabs/Tabs.tsx +++ b/web-client/src/ustc-ui/Tabs/Tabs.tsx @@ -82,7 +82,7 @@ export function Tab(properties: { const HeadingElement = ({ children, level }) => { return React.createElement( `h${level}`, - { ariaHidden: 'false', className: 'sr-only' }, + { 'aria-hidden': 'false', className: 'sr-only' }, children, ); }; diff --git a/web-client/src/ustc-ui/Utils/types.ts b/web-client/src/ustc-ui/Utils/types.ts index 300cea8b962..d9c0cd43583 100644 --- a/web-client/src/ustc-ui/Utils/types.ts +++ b/web-client/src/ustc-ui/Utils/types.ts @@ -4,9 +4,9 @@ import { GetCasesByStatusAndByJudgeResponse } from '@web-api/business/useCases/j import { JudgeActivityReportFilters } from '@web-api/business/useCases/judgeActivityReport/getCountOfCaseDocumentsFiledByJudgesInteractor'; import { TrialSessionReturnType } from '@web-api/business/useCases/judgeActivityReport/getTrialSessionsForJudgeActivityReportInteractor'; -export type InputOption = { +export type InputOption = { label: string; - value?: string; + value?: T; options?: InputOption[]; }; diff --git a/web-client/src/views/TrialSessions/SessionInformationForm.tsx b/web-client/src/views/TrialSessions/SessionInformationForm.tsx index ac5a4006c38..e556aa7eeb3 100644 --- a/web-client/src/views/TrialSessions/SessionInformationForm.tsx +++ b/web-client/src/views/TrialSessions/SessionInformationForm.tsx @@ -15,7 +15,6 @@ const sessionInformationDeps = { form: state.form, formatAndUpdateDateFromDatePickerSequence: sequences.formatAndUpdateDateFromDatePickerSequence, - formattedTrialSessions: state.formattedTrialSessions, updateTrialSessionFormDataSequence: sequences.updateTrialSessionFormDataSequence, user: state.user, @@ -34,7 +33,6 @@ export const SessionInformationForm = connect< DATE_FORMATS, form, formatAndUpdateDateFromDatePickerSequence, - formattedTrialSessions, TRIAL_SESSION_SCOPE_TYPES, updateTrialSessionFormDataSequence, validateTrialSessionSequence, @@ -223,7 +221,7 @@ export const SessionInformationForm = connect<
    - {formattedTrialSessions.showSwingSessionOption && + {addTrialSessionInformationHelper.showSwingSessionOption && !addTrialSessionInformationHelper.isStandaloneSession && ( <>
    @@ -249,7 +247,7 @@ export const SessionInformationForm = connect<
    - {formattedTrialSessions.showSwingSessionList && ( + {addTrialSessionInformationHelper.showSwingSessionList && ( )} diff --git a/web-client/src/views/TrialSessions/TrialSessions.tsx b/web-client/src/views/TrialSessions/TrialSessions.tsx index fc7f48debd4..9615094d2c6 100644 --- a/web-client/src/views/TrialSessions/TrialSessions.tsx +++ b/web-client/src/views/TrialSessions/TrialSessions.tsx @@ -1,6 +1,14 @@ import { BigHeader } from '../BigHeader'; import { Button } from '../../ustc-ui/Button/Button'; +import { DateRangePickerComponent } from '@web-client/ustc-ui/DateInput/DateRangePickerComponent'; import { ErrorNotification } from '../ErrorNotification'; +import { PillButton } from '@web-client/ustc-ui/Button/PillButton'; +import { + SESSION_STATUS_TYPES, + TRIAL_SESSION_PROCEEDING_TYPES, + TrialSessionProceedingType, +} from '@shared/business/entities/EntityConstants'; +import { SelectSearch } from '@web-client/ustc-ui/Select/SelectSearch'; import { SuccessNotification } from '../SuccessNotification'; import { Tab, Tabs } from '../../ustc-ui/Tabs/Tabs'; import { TrialSessionsTable } from './TrialSessionsTable'; @@ -11,15 +19,16 @@ import React from 'react'; export const TrialSessions = connect( { - defaultTab: state.screenMetadata.trialSessionFilters.status, openTrialSessionPlanningModalSequence: sequences.openTrialSessionPlanningModalSequence, - showNewTrialSession: state.trialSessionsHelper.showNewTrialSession, + resetTrialSessionsFiltersSequence: + sequences.resetTrialSessionsFiltersSequence, + trialSessionsHelper: state.trialSessionsHelper, }, function TrialSessions({ - defaultTab, openTrialSessionPlanningModalSequence, - showNewTrialSession, + resetTrialSessionsFiltersSequence, + trialSessionsHelper, }) { return ( <> @@ -27,12 +36,14 @@ export const TrialSessions = connect(
    - { + resetTrialSessionsFiltersSequence({ currentTab: tabName }); + }} >
    + {trialSessionsHelper.showNewTrialSession && ( + + )}
    - {showNewTrialSession && ( - - )} - {showNewTrialSession && ( + {trialSessionsHelper.showNewTrialSession && ( - + + )} - - - - - - - + +
    @@ -95,4 +93,327 @@ export const TrialSessions = connect( }, ); +const TrialSessionFilters = connect( + { + resetTrialSessionsFiltersSequence: + sequences.resetTrialSessionsFiltersSequence, + setTrialSessionsFiltersSequence: sequences.setTrialSessionsFiltersSequence, + trialSessionsHelper: state.trialSessionsHelper, + trialSessionsPage: state.trialSessionsPage, + }, + function TrialSessionFilters({ + resetTrialSessionsFiltersSequence, + setTrialSessionsFiltersSequence, + trialSessionsHelper, + trialSessionsPage, + }) { + return ( + <> +
    + {trialSessionsHelper.showSessionStatus && ( +
    + Session Status +
    + { + setTrialSessionsFiltersSequence({ + sessionStatus: e.target.value, + }); + }} + /> + + { + setTrialSessionsFiltersSequence({ + sessionStatus: e.target.value, + }); + }} + /> + + { + setTrialSessionsFiltersSequence({ + sessionStatus: e.target.value, + }); + }} + /> + +
    +
    + )} +
    + Proceeding Type +
    + { + setTrialSessionsFiltersSequence({ + proceedingType: e.target.value as 'All', + }); + }} + /> + + { + setTrialSessionsFiltersSequence({ + proceedingType: e.target + .value as TrialSessionProceedingType, + }); + }} + /> + + { + setTrialSessionsFiltersSequence({ + proceedingType: e.target + .value as TrialSessionProceedingType, + }); + }} + /> + +
    +
    + + Last start date{' '} + (optional) + + } + endName="trialSessionStartDate" + endValue={trialSessionsPage.filters.endDate} + formGroupCls="margin-bottom-0" + maxDate={''} + rangePickerCls={'display-flex flex-align-end'} + startDateErrorText={''} + startLabel={ + + First start date{' '} + (optional) + + } + startName="trialSessionEndDate" + startPickerCls="padding-right-2" + startValue={trialSessionsPage.filters.startDate} + onChangeEnd={e => { + setTrialSessionsFiltersSequence({ + endDate: e.target.value, + }); + }} + onChangeStart={e => { + setTrialSessionsFiltersSequence({ + startDate: e.target.value, + }); + }} + /> +
    +
    +
    +
    + + { + setTrialSessionsFiltersSequence({ + sessionTypes: { + action: 'add', + sessionType: sessionType.value, + }, + }); + }} + /> +
    +
    + {Object.values(trialSessionsPage.filters.sessionTypes).map( + sessionType => ( + { + setTrialSessionsFiltersSequence({ + sessionTypes: { + action: 'remove', + sessionType, + }, + }); + }} + /> + ), + )} +
    +
    +
    +
    + + { + setTrialSessionsFiltersSequence({ + trialLocations: { + action: 'add', + trialLocation: location.value, + }, + }); + }} + /> +
    +
    + {Object.values(trialSessionsPage.filters.trialLocations).map( + location => ( + { + setTrialSessionsFiltersSequence({ + trialLocations: { + action: 'remove', + trialLocation: location, + }, + }); + }} + /> + ), + )} +
    +
    +
    +
    + + { + setTrialSessionsFiltersSequence({ + judges: { + action: 'add', + judge: inputValue.value, + }, + }); + }} + /> +
    +
    + {Object.values(trialSessionsPage.filters.judges).map(judge => ( + { + setTrialSessionsFiltersSequence({ + judges: { + action: 'remove', + judge, + }, + }); + }} + /> + ))} +
    +
    +
    + + + ); + }, +); + TrialSessions.displayName = 'TrialSessions'; diff --git a/web-client/src/views/TrialSessions/TrialSessionsSummary.tsx b/web-client/src/views/TrialSessions/TrialSessionsSummary.tsx index e587f21740c..14fdf373f5d 100644 --- a/web-client/src/views/TrialSessions/TrialSessionsSummary.tsx +++ b/web-client/src/views/TrialSessions/TrialSessionsSummary.tsx @@ -24,7 +24,7 @@ export const TrialSessionsSummary = connect( link className="margin-left-205" data-testid="view-all-trial-sessions-button" - href={`/trial-sessions?judge[userId]=${trialSessionsSummaryHelper.judgeUserId}`} + href={`/trial-sessions?judgeId=${trialSessionsSummaryHelper.judgeUserId}`} > View All diff --git a/web-client/src/views/TrialSessions/TrialSessionsTable.tsx b/web-client/src/views/TrialSessions/TrialSessionsTable.tsx index 8e2875d6591..01c41afaa9f 100644 --- a/web-client/src/views/TrialSessions/TrialSessionsTable.tsx +++ b/web-client/src/views/TrialSessions/TrialSessionsTable.tsx @@ -1,113 +1,46 @@ -import { BindedSelect } from '../../ustc-ui/BindedSelect/BindedSelect'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { - SESSION_TYPES, - TRIAL_SESSION_PROCEEDING_TYPES, -} from '../../../../shared/src/business/entities/EntityConstants'; -import { TrialCityOptions } from '../TrialCityOptions'; +import { Paginator } from '@web-client/ustc-ui/Pagination/Paginator'; import { connect } from '@web-client/presenter/shared.cerebral'; -import { props } from 'cerebral'; -import { state } from '@web-client/presenter/app.cerebral'; +import { isTrialSessionWeek } from '@web-client/presenter/computeds/trialSessionsHelper'; +import { sequences, state } from '@web-client/presenter/app.cerebral'; import React from 'react'; export const TrialSessionsTable = connect( { - formattedTrialSessions: - state.formattedTrialSessions.filteredTrialSessions[props.filter], + setTrialSessionsFiltersSequence: sequences.setTrialSessionsFiltersSequence, trialSessionsHelper: state.trialSessionsHelper, + trialSessionsPage: state.trialSessionsPage, }, function TrialSessionsTable({ - formattedTrialSessions, + setTrialSessionsFiltersSequence, trialSessionsHelper, - }: { - formattedTrialSessions: any[]; - trialSessionsHelper: { - additionalColumnsShown: number; - showNoticeIssued: boolean; - showSessionStatus: boolean; - showUnassignedJudgeFilter: boolean; - trialSessionJudges: any[]; - }; + trialSessionsPage, }) { return ( - -
    - - - - - - - - {Object.values(TRIAL_SESSION_PROCEEDING_TYPES).map( - proceedingType => ( - - ), - )} - - - - {Object.values(SESSION_TYPES).map(sessionType => ( - - ))} - - - - {trialSessionsHelper.trialSessionJudges.map(judge => ( - - ))} - - {trialSessionsHelper.showUnassignedJudgeFilter && ( - - )} - + <> +
    +
    +
    + { + setTrialSessionsFiltersSequence({ pageNumber: selectedPage }); + }} + /> +
    +
    + Count:{' '} + + {trialSessionsHelper.trialSessionsCount} + +
    @@ -122,78 +55,78 @@ export const TrialSessionsTable = connect( {trialSessionsHelper.showSessionStatus && } - {formattedTrialSessions.map(trialDate => ( - - - - - - - {trialDate.sessions.map(item => ( - - - - - + + - - - - - {trialSessionsHelper.showNoticeIssued && ( - - )} - {trialSessionsHelper.showSessionStatus && ( - - )} - ))} - - ))} + ); + } + return ( + + + + + + + + + + {trialSessionsHelper.showNoticeIssued && ( + + )} + {trialSessionsHelper.showSessionStatus && ( + + )} + + + ); + })}
    Session Status
    -

    - {'Week of '} - {trialDate.dateFormatted} -

    -
    - {item.showAlertForNOTTReminder && ( - - )} - {item.formattedStartDate} - {item.formattedEstimatedEndDate} - {item.swingSession && ( - - )} + {trialSessionsHelper.trialSessionRows.map(row => { + if (isTrialSessionWeek(row)) { + return ( +
    +

    + {'Week of '} + {row.formattedSessionWeekStartDate} +

    - - {item.trialLocation} - - {item.proceedingType}{item.sessionType}{item.judge && item.judge.name}{item.formattedNoticeIssuedDate}{item.sessionStatus}
    + {row.showAlertForNOTTReminder && ( + + )} + {row.formattedStartDate} + {row.formattedEstimatedEndDate} + {row.swingSession && ( + + )} + + + {row.trialLocation} + + {row.proceedingType}{row.sessionType}{row.judge && row.judge.name}{row.formattedNoticeIssuedDate}{row.sessionStatus}
    - {formattedTrialSessions.length === 0 && ( -

    There are no trial sessions.

    + {trialSessionsHelper.trialSessionRows.length === 0 && ( +

    There are no trial sessions for the selected filters.

    )} - + ); }, );