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/cypress/local-only/tests/accessibility/fileDocument/petitioner.cy.ts b/cypress/local-only/tests/accessibility/fileDocument/petitioner.cy.ts index 0febe92ae6a..3c89f145926 100644 --- a/cypress/local-only/tests/accessibility/fileDocument/petitioner.cy.ts +++ b/cypress/local-only/tests/accessibility/fileDocument/petitioner.cy.ts @@ -17,18 +17,16 @@ describe('File Document Page - Petitioner Accessibility', () => { it('should be free of a11y issues on step 2', () => { loginAsPetitioner(); cy.visit('/case-detail/101-19/file-a-document'); - cy.get('#document-type').click({ force: true }); - cy.get('#document-type input') - .first() - .type('Motion for Leave to File Out of Time'); - cy.get('#document-type #react-select-2-option-0').click({ force: true }); - cy.get('#secondary-doc-secondary-document-type').click({ force: true }); - cy.get('#secondary-doc-secondary-document-type input') - .first() - .type('Motion for Continuance'); - cy.get( - '#secondary-doc-secondary-document-type #react-select-3-option-0', - ).click({ force: true }); + cy.get('[data-testid="document-type"]').click(); + cy.get('[data-testid="document-type"]') + .contains('Motion for Leave to File Out of Time') + .click(); + + cy.get('[data-testid="secondary-doc-secondary-document-type"]').click(); + cy.get('[data-testid="secondary-doc-secondary-document-type"]') + .contains('Motion for Continuance') + .click(); + cy.get('#submit-document').click(); cy.get('#primaryDocument-certificateOfService-label').click(); cy.get('#primaryDocument-service-date-picker').should('exist'); 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/cases/CalendaredCase.ts b/shared/src/business/entities/cases/CalendaredCase.ts index a27eccc6b7a..ad9fd3cc4b1 100644 --- a/shared/src/business/entities/cases/CalendaredCase.ts +++ b/shared/src/business/entities/cases/CalendaredCase.ts @@ -7,7 +7,7 @@ import { IrsPractitioner } from '../IrsPractitioner'; import { JoiValidationConstants } from '../JoiValidationConstants'; import { JoiValidationEntity } from '../JoiValidationEntity'; import { PrivatePractitioner } from '../PrivatePractitioner'; -import { setPretrialMemorandumFiler } from '../../utilities/getFormattedTrialSessionDetails'; +import { setPretrialMemorandumFiler } from '../../utilities/trialSession/getFormattedTrialSessionDetails'; import joi from 'joi'; export class CalendaredCase extends JoiValidationEntity { 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/entities/trialSessions/TrialSessionsPageValidation.ts b/shared/src/business/entities/trialSessions/TrialSessionsPageValidation.ts new file mode 100644 index 00000000000..a9780187182 --- /dev/null +++ b/shared/src/business/entities/trialSessions/TrialSessionsPageValidation.ts @@ -0,0 +1,39 @@ +import { JoiValidationConstants } from '@shared/business/entities/JoiValidationConstants'; +import { JoiValidationEntity } from '../JoiValidationEntity'; +import joi from 'joi'; + +export class TrialSessionsPageValidation extends JoiValidationEntity { + public endDate: string; + public startDate: string; + + constructor(rawProps) { + super('TrialSessionsPageValidation'); + this.endDate = rawProps.endDate; + this.startDate = rawProps.startDate; + } + + getValidationRules() { + return { + endDate: joi + .alternatives() + .conditional('startDate', { + is: JoiValidationConstants.ISO_DATE.exist().not(null), + otherwise: JoiValidationConstants.ISO_DATE, + then: JoiValidationConstants.ISO_DATE.min( + joi.ref('startDate'), + ).description( + 'The end date search filter must be of valid date format and greater than or equal to the start date', + ), + }) + .messages({ + '*': 'Enter date in format MM/DD/YYYY.', + 'date.min': 'End date cannot be prior to start date.', + }), + startDate: JoiValidationConstants.ISO_DATE.description( + 'The start date to search by, which cannot be greater than the current date, and is required when there is an end date provided', + ).messages({ + '*': 'Enter date in format MM/DD/YYYY.', + }), + }; + } +} diff --git a/shared/src/business/test/createTestApplicationContext.ts b/shared/src/business/test/createTestApplicationContext.ts index f241dc7842a..66c14dda380 100644 --- a/shared/src/business/test/createTestApplicationContext.ts +++ b/shared/src/business/test/createTestApplicationContext.ts @@ -40,7 +40,7 @@ import { compareCasesByDocketNumber, formatCaseForTrialSession, getFormattedTrialSessionDetails, -} from '@shared/business/utilities/getFormattedTrialSessionDetails'; +} from '@shared/business/utilities/trialSession/getFormattedTrialSessionDetails'; import { compareISODateStrings, compareStrings, 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/business/utilities/getFormattedTrialSessionDetails.compareCasesByDocketNumber.test.ts b/shared/src/business/utilities/trialSession/getFormattedTrialSessionDetails.compareCasesByDocketNumber.test.ts similarity index 95% rename from shared/src/business/utilities/getFormattedTrialSessionDetails.compareCasesByDocketNumber.test.ts rename to shared/src/business/utilities/trialSession/getFormattedTrialSessionDetails.compareCasesByDocketNumber.test.ts index 0610dc7c634..74617e3f580 100644 --- a/shared/src/business/utilities/getFormattedTrialSessionDetails.compareCasesByDocketNumber.test.ts +++ b/shared/src/business/utilities/trialSession/getFormattedTrialSessionDetails.compareCasesByDocketNumber.test.ts @@ -1,4 +1,3 @@ -import { applicationContext } from '../test/createTestApplicationContext'; import { compareCasesByDocketNumber, compareCasesByDocketNumberFactory, @@ -58,7 +57,6 @@ describe('getFormattedTrialSessionDetails.compareCasesByDocketNumberFactory', () it('101-19 should come before 102-19', () => { const result = compareCasesByDocketNumberFactory({ allCases: [], - applicationContext, })( { docketNumber: '101-19', @@ -79,7 +77,6 @@ describe('getFormattedTrialSessionDetails.compareCasesByDocketNumberFactory', () it('190-07 should come before 102-19', () => { const result = compareCasesByDocketNumberFactory({ allCases: [], - applicationContext, })( { docketNumber: '190-07', @@ -94,7 +91,6 @@ describe('getFormattedTrialSessionDetails.compareCasesByDocketNumberFactory', () it('102-19 should equal 102-19', () => { const result = compareCasesByDocketNumberFactory({ allCases: [], - applicationContext, })( { docketNumber: '102-19', @@ -115,7 +111,6 @@ describe('getFormattedTrialSessionDetails.compareCasesByDocketNumberFactory', () it('103-19 should come after 102-19', () => { const result = compareCasesByDocketNumberFactory({ allCases: [], - applicationContext, })( { docketNumber: '103-19', @@ -163,7 +158,6 @@ describe('getFormattedTrialSessionDetails.compareCasesByDocketNumberFactory', () leadDocketNumber: '101-20', }, ], - applicationContext, }), ); @@ -207,7 +201,6 @@ describe('getFormattedTrialSessionDetails.compareCasesByDocketNumberFactory', () cases.sort( compareCasesByDocketNumberFactory({ allCases: [], - applicationContext, }), ); diff --git a/shared/src/business/utilities/getFormattedTrialSessionDetails.setPretrialMemorandumFilter.test.ts b/shared/src/business/utilities/trialSession/getFormattedTrialSessionDetails.setPretrialMemorandumFilter.test.ts similarity index 92% rename from shared/src/business/utilities/getFormattedTrialSessionDetails.setPretrialMemorandumFilter.test.ts rename to shared/src/business/utilities/trialSession/getFormattedTrialSessionDetails.setPretrialMemorandumFilter.test.ts index 46f7514f401..06ae4b60978 100644 --- a/shared/src/business/utilities/getFormattedTrialSessionDetails.setPretrialMemorandumFilter.test.ts +++ b/shared/src/business/utilities/trialSession/getFormattedTrialSessionDetails.setPretrialMemorandumFilter.test.ts @@ -1,6 +1,6 @@ -import { MOCK_CASE } from '../../test/mockCase'; -import { PARTIES_CODES } from '../entities/EntityConstants'; -import { applicationContext } from '../test/createTestApplicationContext'; +import { MOCK_CASE } from '../../../test/mockCase'; +import { PARTIES_CODES } from '../../entities/EntityConstants'; +import { applicationContext } from '../../test/createTestApplicationContext'; import { setPretrialMemorandumFiler } from './getFormattedTrialSessionDetails'; describe('getFormattedTrialSessionDetails', () => { @@ -35,7 +35,6 @@ describe('getFormattedTrialSessionDetails', () => { }; const result = setPretrialMemorandumFiler({ - applicationContext, caseItem: mockCase, }); @@ -55,7 +54,6 @@ describe('getFormattedTrialSessionDetails', () => { }; const result = setPretrialMemorandumFiler({ - applicationContext, caseItem: mockCase, }); @@ -75,7 +73,6 @@ describe('getFormattedTrialSessionDetails', () => { }; const result = setPretrialMemorandumFiler({ - applicationContext, caseItem: mockCase, }); @@ -100,7 +97,6 @@ describe('getFormattedTrialSessionDetails', () => { }; const result = setPretrialMemorandumFiler({ - applicationContext, caseItem: mockCase, }); @@ -126,7 +122,6 @@ describe('getFormattedTrialSessionDetails', () => { }; const result = setPretrialMemorandumFiler({ - applicationContext, caseItem: mockCase, }); @@ -139,7 +134,6 @@ describe('getFormattedTrialSessionDetails', () => { .getCaseByDocketNumber.mockReturnValue(MOCK_CASE); const result = setPretrialMemorandumFiler({ - applicationContext, caseItem: MOCK_CASE, }); @@ -158,7 +152,6 @@ describe('getFormattedTrialSessionDetails', () => { }; const result = setPretrialMemorandumFiler({ - applicationContext, caseItem: MOCK_CASE, }); diff --git a/shared/src/business/utilities/getFormattedTrialSessionDetails.test.ts b/shared/src/business/utilities/trialSession/getFormattedTrialSessionDetails.test.ts similarity index 98% rename from shared/src/business/utilities/getFormattedTrialSessionDetails.test.ts rename to shared/src/business/utilities/trialSession/getFormattedTrialSessionDetails.test.ts index 63481ff20e0..dd6abb8ffc1 100644 --- a/shared/src/business/utilities/getFormattedTrialSessionDetails.test.ts +++ b/shared/src/business/utilities/trialSession/getFormattedTrialSessionDetails.test.ts @@ -1,8 +1,8 @@ -import { DOCKET_NUMBER_SUFFIXES } from '../entities/EntityConstants'; -import { MOCK_CASE } from '../../../../shared/src/test/mockCase'; +import { DOCKET_NUMBER_SUFFIXES } from '../../entities/EntityConstants'; +import { MOCK_CASE } from '../../../test/mockCase'; import { MOCK_TRIAL_REGULAR } from '@shared/test/mockTrial'; import { TrialSessionState } from '@web-client/presenter/state/trialSessionState'; -import { applicationContext } from '../test/createTestApplicationContext'; +import { applicationContext } from '../../test/createTestApplicationContext'; import { formatCaseForTrialSession, getFormattedTrialSessionDetails, @@ -19,9 +19,11 @@ describe('getFormattedTrialSessionDetails', () => { beforeEach(() => { TRIAL_SESSION = { ...MOCK_TRIAL_REGULAR, + calendaredCases: [], caseOrder: [], city: 'Hartford', courtReporter: 'Test Court Reporter', + eligibleCases: [], estimatedEndDate: '2040-11-25T15:00:00.000Z', irsCalendarAdministrator: 'Test Calendar Admin', postalCode: '12345', diff --git a/shared/src/business/utilities/getFormattedTrialSessionDetails.ts b/shared/src/business/utilities/trialSession/getFormattedTrialSessionDetails.ts similarity index 96% rename from shared/src/business/utilities/getFormattedTrialSessionDetails.ts rename to shared/src/business/utilities/trialSession/getFormattedTrialSessionDetails.ts index 4b63484ba01..84999be50b4 100644 --- a/shared/src/business/utilities/getFormattedTrialSessionDetails.ts +++ b/shared/src/business/utilities/trialSession/getFormattedTrialSessionDetails.ts @@ -5,9 +5,9 @@ import { import { DOCKET_NUMBER_SUFFIXES, PARTIES_CODES, -} from '../entities/EntityConstants'; -import { FORMATS } from './DateHandler'; -import { RawEligibleCase } from '../entities/cases/EligibleCase'; +} from '../../entities/EntityConstants'; +import { FORMATS } from '../DateHandler'; +import { RawEligibleCase } from '../../entities/cases/EligibleCase'; import { RawIrsCalendarAdministratorInfo } from '@shared/business/entities/trialSessions/IrsCalendarAdministratorInfo'; import { compact, partition } from 'lodash'; @@ -243,13 +243,14 @@ export const getFormattedTrialSessionDetails = ({ .formatDateString(trialSession.startDate, 'MONTH_DAY_YEAR'); let [hour, min] = trialSession.startTime!.split(':'); - let startTimeExtension = +hour >= 12 ? 'pm' : 'am'; + let hourNumber = +hour; + let startTimeExtension = hourNumber >= 12 ? 'pm' : 'am'; - if (+hour > 12) { - hour = +hour - 12; + if (hourNumber > 12) { + hourNumber = hourNumber - 12; } - const formattedStartTime = `${hour}:${min} ${startTimeExtension}`; + const formattedStartTime = `${hourNumber}:${min} ${startTimeExtension}`; const formattedJudge = (trialSession.judge && trialSession.judge.name) || 'Not assigned'; diff --git a/shared/src/business/utilities/trialSession/trialCitiesGroupedByState.ts b/shared/src/business/utilities/trialSession/trialCitiesGroupedByState.ts new file mode 100644 index 00000000000..6ad2ab0aa00 --- /dev/null +++ b/shared/src/business/utilities/trialSession/trialCitiesGroupedByState.ts @@ -0,0 +1,38 @@ +import { TRIAL_CITIES } from '@shared/business/entities/EntityConstants'; +import { sortBy } from 'lodash'; + +export const getTrialCitiesGroupedByState = (): { + label: string; + options: { + label: string; + value: string; + }[]; +}[] => { + const trialCities = sortBy(TRIAL_CITIES.ALL, ['state', 'city']); + const states = trialCities.reduce( + (listOfStates, cityStatePair) => { + const existingState = listOfStates.find( + trialState => trialState.label === cityStatePair.state, + ); + const cityOption = { + label: `${cityStatePair.city}, ${cityStatePair.state}`, + value: `${cityStatePair.city}, ${cityStatePair.state}`, + }; + if (existingState) { + existingState.options.push(cityOption); + } else { + listOfStates.push({ + label: cityStatePair.state, + options: [cityOption], + }); + } + return listOfStates; + }, + [] as { + label: string; + options: { label: string; value: string }[]; + }[], + ); + + return states; +}; 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/generateTrialCalendarPdfInteractor.ts b/web-api/src/business/useCases/trialSessions/generateTrialCalendarPdfInteractor.ts index 11f87c57ebd..2a692339365 100644 --- a/web-api/src/business/useCases/trialSessions/generateTrialCalendarPdfInteractor.ts +++ b/web-api/src/business/useCases/trialSessions/generateTrialCalendarPdfInteractor.ts @@ -1,7 +1,7 @@ import { NotFoundError } from '@web-api/errors/errors'; import { ServerApplicationContext } from '@web-api/applicationContext'; import { compact } from 'lodash'; -import { compareCasesByDocketNumberFactory } from '../../../../../shared/src/business/utilities/getFormattedTrialSessionDetails'; +import { compareCasesByDocketNumberFactory } from '../../../../../shared/src/business/utilities/trialSession/getFormattedTrialSessionDetails'; import { formatDateString } from '@shared/business/utilities/DateHandler'; import { saveFileAndGenerateUrl } from '../../useCaseHelper/saveFileAndGenerateUrl'; 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/src/getUtilities.ts b/web-api/src/getUtilities.ts index 69f45fee431..2e61d38bfc8 100644 --- a/web-api/src/getUtilities.ts +++ b/web-api/src/getUtilities.ts @@ -16,7 +16,7 @@ import { combineTwoPdfs } from '../../shared/src/business/utilities/documentGene import { compareCasesByDocketNumber, getFormattedTrialSessionDetails, -} from '../../shared/src/business/utilities/getFormattedTrialSessionDetails'; +} from '../../shared/src/business/utilities/trialSession/getFormattedTrialSessionDetails'; import { compareISODateStrings, compareStrings, 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..cc92e39c707 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, @@ -39,30 +36,22 @@ export const docketClerkViewsTrialSessionsTab = ( state: cerebralTest.getState(), }); - const legacyJudge = helper.trialSessionJudges.find( - judge => judge.role === 'legacyJudge', + const legacyJudge = helper.trialSessionJudgeOptions.find( + option => option.value.name === 'Fieri', ); - 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/applicationContext.ts b/web-client/src/applicationContext.ts index 59f66f550a3..7d8f2db04d2 100644 --- a/web-client/src/applicationContext.ts +++ b/web-client/src/applicationContext.ts @@ -77,7 +77,7 @@ import { compareCasesByDocketNumber, formatCaseForTrialSession, getFormattedTrialSessionDetails, -} from '../../shared/src/business/utilities/getFormattedTrialSessionDetails'; +} from '../../shared/src/business/utilities/trialSession/getFormattedTrialSessionDetails'; import { compareISODateStrings, compareStrings, diff --git a/web-client/src/applicationContextPublic.ts b/web-client/src/applicationContextPublic.ts index 6eaa6b508d1..aef367de660 100644 --- a/web-client/src/applicationContextPublic.ts +++ b/web-client/src/applicationContextPublic.ts @@ -42,7 +42,7 @@ import { } from '../../shared/src/sharedAppContext'; import { User } from '../../shared/src/business/entities/User'; import { casePublicSearchInteractor } from '../../shared/src/proxies/casePublicSearchProxy'; -import { compareCasesByDocketNumber } from '../../shared/src/business/utilities/getFormattedTrialSessionDetails'; +import { compareCasesByDocketNumber } from '../../shared/src/business/utilities/trialSession/getFormattedTrialSessionDetails'; import { confirmSignUpInteractor } from '@shared/proxies/auth/confirmSignUpProxy'; import { createISODateString, 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/actions/setCaseAction.ts b/web-client/src/presenter/actions/setCaseAction.ts index 09099cc2b03..1d2a31ebe5f 100644 --- a/web-client/src/presenter/actions/setCaseAction.ts +++ b/web-client/src/presenter/actions/setCaseAction.ts @@ -1,4 +1,4 @@ -import { compareCasesByDocketNumber } from '@shared/business/utilities/getFormattedTrialSessionDetails'; +import { compareCasesByDocketNumber } from '@shared/business/utilities/trialSession/getFormattedTrialSessionDetails'; import { state } from '@web-client/presenter/app.cerebral'; export const setCaseAction = ({ 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/customCaseReportHelper.ts b/web-client/src/presenter/computeds/customCaseReportHelper.ts index bcc2710ef4e..16f0429ce59 100644 --- a/web-client/src/presenter/computeds/customCaseReportHelper.ts +++ b/web-client/src/presenter/computeds/customCaseReportHelper.ts @@ -3,7 +3,7 @@ import { CASE_TYPES, CHIEF_JUDGE, CUSTOM_CASE_REPORT_PAGE_SIZE, - TRIAL_CITIES, + CaseType, } from '@shared/business/entities/EntityConstants'; import { Case } from '@shared/business/entities/cases/Case'; import { @@ -13,17 +13,15 @@ import { import { ClientApplicationContext } from '@web-client/applicationContext'; import { FORMATS } from '@shared/business/utilities/DateHandler'; import { Get } from 'cerebral'; -import { InputOption } from '@web-client/ustc-ui/Utils/types'; -import { sortBy } from 'lodash'; +import { getTrialCitiesGroupedByState } from '@shared/business/utilities/trialSession/trialCitiesGroupedByState'; import { state } from '@web-client/presenter/app.cerebral'; export const customCaseReportHelper = ( get: Get, applicationContext: ClientApplicationContext, ): { - activeTrialCities: InputOption[]; - caseStatuses: InputOption[]; - caseTypes: InputOption[]; + caseStatuses: { label: string; value: string }[]; + caseTypes: { label: string; value: CaseType }[]; cases: (CaseInventory & { inConsolidatedGroup: boolean; consolidatedIconTooltipText: string; @@ -31,11 +29,16 @@ export const customCaseReportHelper = ( isLeadCase: boolean; })[]; clearFiltersIsDisabled: boolean; - judges: InputOption[]; + judges: { label: string; value: string }[]; pageCount: number; - searchableTrialCities: InputOption[]; today: string; - trialCitiesByState: InputOption[]; + trialCitiesByState: { + label: string; + options: { + label: string; + value: string; + }[]; + }[]; } => { const caseStatuses = Object.values(CASE_STATUS_TYPES).map(status => ({ label: status, @@ -91,43 +94,16 @@ export const customCaseReportHelper = ( const today = applicationContext.getUtilities().formatNow(FORMATS.YYYYMMDD); - const trialCities = sortBy(TRIAL_CITIES.ALL, ['state', 'city']); - - const searchableTrialCities: InputOption[] = []; - - 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], - }); - } - searchableTrialCities.push(cityOption); - return listOfStates; - }, - [], - ); + const trialCitiesByState = getTrialCitiesGroupedByState(); return { - activeTrialCities: states, caseStatuses, caseTypes, cases: reportData, clearFiltersIsDisabled, judges, pageCount, - searchableTrialCities, today, - trialCitiesByState: states, + trialCitiesByState, }; }; 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..f257df9f7ef 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 { FormattedTrialSessionDetailsType } from '@shared/business/utilities/trialSession/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/internalTypesHelper.ts b/web-client/src/presenter/computeds/internalTypesHelper.ts index 78f0f735f57..9eb91b3fb9e 100644 --- a/web-client/src/presenter/computeds/internalTypesHelper.ts +++ b/web-client/src/presenter/computeds/internalTypesHelper.ts @@ -52,7 +52,16 @@ export const getSortFunction = searchText => { export const internalTypesHelper = ( get: Get, applicationContext: ClientApplicationContext, -): any => { +): { + internalDocumentTypesForSelectSorted: (DocumentTypeBase & { + label: string; + value: string; + })[]; + internalDocumentTypesForSelectWithLegacySorted: (DocumentTypeBase & { + label: string; + value: string; + })[]; +} => { const { INTERNAL_CATEGORY_MAP, LEGACY_DOCUMENT_TYPES, LODGED_EVENT_CODE } = applicationContext.getConstants(); const searchText = get(state.screenMetadata.searchText) || ''; diff --git a/web-client/src/presenter/computeds/trialSessionWorkingCopyHelper.ts b/web-client/src/presenter/computeds/trialSessionWorkingCopyHelper.ts index b0d86664eed..24a18508d4b 100644 --- a/web-client/src/presenter/computeds/trialSessionWorkingCopyHelper.ts +++ b/web-client/src/presenter/computeds/trialSessionWorkingCopyHelper.ts @@ -2,7 +2,7 @@ import { ClientApplicationContext } from '@web-client/applicationContext'; import { FormattedTrialSessionCase, compareCasesByDocketNumber, -} from '@shared/business/utilities/getFormattedTrialSessionDetails'; +} from '@shared/business/utilities/trialSession/getFormattedTrialSessionDetails'; import { Get } from 'cerebral'; import { TRIAL_STATUS_TYPES } from '@shared/business/entities/EntityConstants'; import { TrialSessionState } from '@web-client/presenter/state/trialSessionState'; diff --git a/web-client/src/presenter/computeds/trialSessionsHelper.test.ts b/web-client/src/presenter/computeds/trialSessionsHelper.test.ts index 025c74c8e6c..01e0e7e8c28 100644 --- a/web-client/src/presenter/computeds/trialSessionsHelper.test.ts +++ b/web-client/src/presenter/computeds/trialSessionsHelper.test.ts @@ -1,8 +1,30 @@ -import { ROLES } from '../../../../shared/src/business/entities/EntityConstants'; -import { docketClerk1User, judgeUser } from '@shared/test/mockUsers'; +import { + FORMATS, + calculateISODate, + formatNow, +} from '@shared/business/utilities/DateHandler'; +import { + 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, + judgeColvin, + judgeUser, + legacyJudgeUser, +} 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 +32,687 @@ 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', + }; + trialSessionsPageState.trialSessions = [trialSession1, trialSession2]; + }); + 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', - }, - }, + judges: [judgeUser, judgeColvin], 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', - }, - }, + judges: [judgeUser, judgeColvin], 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', - }, - }, + judges: [judgeUser, judgeColvin], 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', - }, - }, + judges: [judgeUser, judgeColvin], 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('trialSessionJudgeOptions', () => { + 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', - }, - }, + judges: [judgeUser, judgeColvin], permissions: getUserPermissions(docketClerk1User), + trialSessionsPage: trialSessionsPageState, }, }); - expect(result.showSessionStatus).toEqual(true); + expect(result.trialSessionJudgeOptions[2]).toEqual({ + label: 'Unassigned', + value: { name: 'Unassigned', userId: 'unassigned' }, + }); }); - it('should NOT show the Session Status column for `new` sessions', () => { + it('should not show the `unassigned` judge filter when on the calendared tab', () => { + trialSessionsPageState.filters.currentTab = 'calendared'; + const result = runCompute(trialSessionsHelper, { state: { - currentViewMetadata: { - trialSessions: { - tab: 'new', - }, - }, + judges: [judgeUser, judgeColvin], permissions: getUserPermissions(docketClerk1User), + trialSessionsPage: trialSessionsPageState, }, }); - expect(result.showSessionStatus).toEqual(false); + expect(result.trialSessionJudgeOptions.length).toEqual(2); }); + }); - 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', - }, - }, + legacyAndCurrentJudges: [judgeUser, legacyJudgeUser], permissions: getUserPermissions(docketClerk1User), + trialSessionsPage: trialSessionsPageState, }, }); - expect(result.showSessionStatus).toEqual(false); + expect(result.trialSessionJudgeOptions).toEqual([ + { + label: 'Sotomayor', + value: { + name: 'Sotomayor', + userId: '43b00e5f-b78c-476c-820e-5d6ed1d58828', + }, + }, + { + label: 'Legacy Judge Ginsburg', + value: { + name: 'Legacy Judge Ginsburg', + userId: 'dc67e189-cf3e-4ca3-a33f-91db111ec270', + }, + }, + ]); }); - 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: [judgeUser], + legacyAndCurrentJudges: [legacyJudgeUser], permissions: getUserPermissions(docketClerk1User), + trialSessionsPage: trialSessionsPageState, }, }); - expect(result.showSessionStatus).toEqual(false); + expect(result.trialSessionJudgeOptions).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + label: judgeUser.name, + value: expect.objectContaining({ + name: judgeUser.name, + userId: judgeUser.userId, + }), + }), + ]), + ); + + expect(result.trialSessionJudgeOptions).not.toEqual( + expect.arrayContaining([ + expect.objectContaining({ + label: legacyJudgeUser.name, + value: expect.objectContaining({ + name: legacyJudgeUser.name, + userId: legacyJudgeUser.userId, + }), + }), + ]), + ); }); - }); - 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: [judgeUser], + legacyAndCurrentJudges: [legacyJudgeUser], permissions: getUserPermissions(docketClerk1User), + trialSessionsPage: trialSessionsPageState, }, }); - expect(result.additionalColumnsShown).toEqual(0); + expect(result.trialSessionJudgeOptions).toEqual([ + { + label: 'Sotomayor', + value: { + name: 'Sotomayor', + userId: '43b00e5f-b78c-476c-820e-5d6ed1d58828', + }, + }, + ]); }); + }); - 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', () => { + trialSessionsPageState.filters.currentTab = 'new'; + const judges = [judgeUser, judgeColvin]; const result = runCompute(trialSessionsHelper, { state: { - currentViewMetadata: { - trialSessions: { - tab: 'closed', - }, - }, + judges, 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), + judges: [judgeUser], + 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 = '43b00e5f-b78c-476c-820e-5d6ed1d58828'; + trialSession2.judge!.userId = 'd17b07dc-6455-447e-bea3-f91d12ac5a6'; + trialSessionsPageState.trialSessions = [trialSession1, trialSession2]; + trialSessionsPageState.filters.judges = { + 'd17b07dc-6455-447e-bea3-f91d12ac5a6': { + name: 'Colvin', + userId: 'd17b07dc-6455-447e-bea3-f91d12ac5a6', }, - permissions: getUserPermissions(docketClerk1User), - }, + }; + + const result = runCompute(trialSessionsHelper, { + state: { + judges: [judgeUser, judgeColvin], + permissions: getUserPermissions(docketClerk1User), + trialSessionsPage: trialSessionsPageState, + }, + }); + + const trialSessionsOnly = + result.trialSessionRows.filter(isTrialSessionRow); + expect(trialSessionsOnly.length).toEqual(1); }); - expect(result.additionalColumnsShown).toEqual(1); - }); - }); + // NOTE: This test passes, but I am unable to find when the userId for a judge would be set to unassigned + 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.judges = { + 'd17b07dc-6455-447e-bea3-f91d12ac5a6a': { + name: 'Colvin', + userId: '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: { + judges: [], + 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 empty', () => { + trialSessionsPageState.trialSessions = [trialSession1, trialSession2]; + trialSessionsPageState.filters.judges = {}; + const result = runCompute(trialSessionsHelper, { + state: { + judges: [judgeUser, judgeColvin], + permissions: getUserPermissions(docketClerk1User), + trialSessionsPage: trialSessionsPageState, + }, + }); - it('should NOT show the `unassigned` judge filter for `open` sessions', () => { - const result = runCompute(trialSessionsHelper, { - state: { - currentViewMetadata: { - trialSessions: { - tab: 'open', - }, + const trialSessionsOnly = + result.trialSessionRows.filter(isTrialSessionRow); + expect(trialSessionsOnly.length).toEqual(2); + }); + + 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]; + + const result = runCompute(trialSessionsHelper, { + state: { + judges: [judgeUser], + 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'; + + const result = runCompute(trialSessionsHelper, { + state: { + judges: [judgeUser], + permissions: getUserPermissions(docketClerk1User), + trialSessionsPage: trialSessionsPageState, + }, + }); - it('should NOT show the `unassigned` judge filter for `closed` sessions', () => { - const result = runCompute(trialSessionsHelper, { - state: { - currentViewMetadata: { - trialSessions: { - tab: 'close', - }, + const trialSessionsOnly = + result.trialSessionRows.filter(isTrialSessionRow); + + expect(trialSessionsOnly[0].trialSessionId).toEqual( + trialSession1.trialSessionId, + ); + }); + + 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'; + + const result = runCompute(trialSessionsHelper, { + state: { + judges: [judgeUser], + 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 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: { + judges: [judgeUser], + permissions: getUserPermissions(docketClerk1User), + trialSessionsPage: trialSessionsPageState, + }, + }); + + const trialSessionsOnly = + result.trialSessionRows.filter(isTrialSessionRow); + expect(trialSessionsOnly.length).toEqual(1); + expect(trialSessionsOnly[0].trialSessionId).toEqual( + trialSession1.trialSessionId, + ); + }); - it('should NOT show the `unassigned` judge filter for `all` sessions', () => { - const result = runCompute(trialSessionsHelper, { - state: { - currentViewMetadata: { - trialSessions: { - tab: 'all', - }, + 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: { + judges: [judgeUser], + legacyAndCurrentJudges: [legacyJudgeUser], + 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).toBeFalsy(); - }); - }); + 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: { + judges: [judgeUser], + permissions: getUserPermissions(docketClerk1User), + trialSessionsPage: trialSessionsPageState, + }, + }); + + const trialSessionsOnly = + result.trialSessionRows.filter(isTrialSessionRow); + expect(trialSessionsOnly.length).toEqual(1); + expect(trialSessionsOnly[0].trialSessionId).toEqual( + trialSession2.trialSessionId, + ); + }); - describe('trialSessionJudges', () => { - it('returns only non-legacy judges when state.currentViewMetadata.trialSessions.tab is Open', () => { - const result = runCompute(trialSessionsHelper, { - state: { - currentViewMetadata: { - trialSessions: { - tab: 'open', - }, + 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]: SESSION_TYPES.regular, + }), + (trialSessionsPageState.trialSessions = [ + trialSession1, + trialSession2, + ]); + + const result = runCompute(trialSessionsHelper, { + state: { + judges: [judgeUser], + 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 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': 'Honolulu, Hawaii', + }; + trialSessionsPageState.trialSessions = [trialSession1, trialSession2]; + + const result = runCompute(trialSessionsHelper, { + state: { + judges: [judgeUser], + 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', - }, + 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: { + judges: [judgeUser], + 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 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 set userIsAssignedToSession false for all sessions if there is no associated judgeUser', () => { + trialSessionsPageState.trialSessions = [trialSession1, trialSession2]; - it('returns all current and legacy judges when state.currentViewMetadata.trialSessions.tab is Closed', () => { - const result = runCompute(trialSessionsHelper, { - state: { - currentViewMetadata: { - trialSessions: { - tab: 'closed', - }, + const result = runCompute(trialSessionsHelper, { + state: { + judgeUser: {}, + judges: [judgeUser], + 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); + trialSessionsOnly.forEach(t => { + expect(t.userIsAssignedToSession).toEqual(false); + }); }); - 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 true for all sessions the judge user is assigned to', () => { + trialSession1.judge!.userId = '1'; + trialSession2.judge!.userId = '2'; + trialSessionsPageState.trialSessions = [trialSession1, trialSession2]; - it('returns all current and legacy judges when state.currentViewMetadata.trialSessions.tab is All', () => { - const result = runCompute(trialSessionsHelper, { - state: { - currentViewMetadata: { - trialSessions: { - tab: 'all', + const result = runCompute(trialSessionsHelper, { + state: { + judgeUser: { + userId: '1', }, + judges: [judgeUser], + 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); + trialSessionsOnly.forEach(t => { + if (t.trialSessionId === trialSession1.trialSessionId) { + expect(t.userIsAssignedToSession).toEqual(true); + } else { + expect(t.userIsAssignedToSession).toEqual(false); + } + }); }); - 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 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: { + judges: [judgeUser], + 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); + }); }); - }); - 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', - }, + 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: { + judges: [judgeUser], + permissions: getUserPermissions(docketClerk1User), + trialSessionsPage: trialSessionsPageState, }, - permissions: getUserPermissions(docketClerk1User), - }, + }); + + 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(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('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: { + judges: [judgeUser], + permissions: getUserPermissions(docketClerk1User), + trialSessionsPage: trialSessionsPageState, }, - permissions: getUserPermissions(judgeUser), - }, + }); + + const trialSessionWeeks = + result.trialSessionRows.filter(isTrialSessionWeek); + expect(trialSessionWeeks).toEqual([ + { + formattedSessionWeekStartDate: 'September 2, 2024', + sessionWeekStartDate: '2024-09-02T04:00:00.000+00:00', + }, + ]); }); - expect(result.showNewTrialSession).toEqual(false); + 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: { + judges: [judgeUser], + 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..cb898634d97 100644 --- a/web-client/src/presenter/computeds/trialSessionsHelper.ts +++ b/web-client/src/presenter/computeds/trialSessionsHelper.ts @@ -1,37 +1,346 @@ +import { + FORMATS, + createDateAtStartOfWeekEST, + createISODateString, + formatDateString, + subtractISODates, +} from '@shared/business/utilities/DateHandler'; import { Get } from 'cerebral'; +import { RawUser } from '@shared/business/entities/User'; +import { + SESSION_STATUS_TYPES, + SESSION_TYPES, + TrialSessionTypes, +} 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 { TrialSessionsPageValidation } from '@shared/business/entities/trialSessions/TrialSessionsPageValidation'; +import { getTrialCitiesGroupedByState } from '@shared/business/utilities/trialSession/trialCitiesGroupedByState'; 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; + trialSessionJudgeOptions: { + label: string; + value: { name: string; userId: string }; + }[]; + trialSessionRows: (TrialSessionRow | TrialSessionWeek)[]; + sessionTypeOptions: { label: string; value: TrialSessionTypes }[]; + trialCitiesByState: { + label: string; + options: { label: string; value: string }[]; + }[]; + trialSessionsCount: number; + endDateErrorMessage?: string; + startDateErrorMessage?: string; + 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 judges = get(state.judges); + + const pageSize = 100; - const showCurrentJudgesOnly = isNewTab || isOpenTab; + const showCurrentJudgesOnly = + filters.currentTab === 'new' || + filters.sessionStatus === SESSION_STATUS_TYPES.open; - let trialSessionJudges; + let trialSessionJudges: { name: string; userId: string }[]; if (showCurrentJudgesOnly) { - trialSessionJudges = get(state.judges); + trialSessionJudges = 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 showUnassignedJudgeFilter = filters.currentTab === 'new'; + if (showUnassignedJudgeFilter) { + trialSessionJudgeOptions.push({ + label: 'Unassigned', + value: { name: 'Unassigned', userId: 'unassigned' }, + }); + } + + const states = getTrialCitiesGroupedByState(); + + const { endDateErrorMessage, startDateErrorMessage } = + validateTrialSessionDateRange({ + endDate: filters.endDate, + startDate: filters.startDate, + }); + + 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, + endDateErrorMessage, + isResetFiltersDisabled: !userHasSelectedAFilter, + sessionTypeOptions, showNewTrialSession: permissions.CREATE_TRIAL_SESSION, - showNoticeIssued: isOpenTab, - showSessionStatus: isAllTab, - showUnassignedJudgeFilter: isNewTab, - trialSessionJudges, + showNoticeIssued: filters.currentTab === 'calendared', + showSessionStatus: filters.currentTab === 'calendared', + startDateErrorMessage, + 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; +} + +function validateTrialSessionDateRange({ + endDate, + startDate, +}: { + startDate: string; + endDate: string; +}): { startDateErrorMessage?: string; endDateErrorMessage?: string } { + const formattedEndDate = endDate + ? createISODateString(endDate, FORMATS.MMDDYYYY) + : undefined; + + const formattedStartDate = startDate + ? createISODateString(startDate, FORMATS.MMDDYYYY) + : undefined; + + const errors = new TrialSessionsPageValidation({ + endDate: formattedEndDate, + startDate: formattedStartDate, + }).getFormattedValidationErrors(); + + return { + endDateErrorMessage: errors?.endDate, + startDateErrorMessage: errors?.startDate, + }; +} diff --git a/web-client/src/presenter/presenter.ts b/web-client/src/presenter/presenter.ts index 2372cf1b20e..725df48df61 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,7 +954,7 @@ export const presenterSequences = { gotoTrialSessionPlanningReportSequence as unknown as Function, gotoTrialSessionWorkingCopySequence: gotoTrialSessionWorkingCopySequence as unknown as Function, - gotoTrialSessionsSequence: gotoTrialSessionsSequence as unknown as Function, + gotoTrialSessionsSequence, gotoUpdatedPetitionFlowSequence, gotoUploadCorrespondenceDocumentSequence: gotoUploadCorrespondenceDocumentSequence as unknown as Function, @@ -1193,6 +1196,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 +1278,7 @@ export const presenterSequences = { setSelectedMessagesSequence, setTrialSessionCalendarErrorSequence, setTrialSessionCalendarSequence, + setTrialSessionsFiltersSequence, setViewerCorrespondenceToDisplaySequence: setViewerCorrespondenceToDisplaySequence as unknown as Function, setViewerDocumentToDisplaySequence: @@ -1647,7 +1652,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/custom.scss b/web-client/src/styles/custom.scss index b3c54edd1f6..de248eec1eb 100644 --- a/web-client/src/styles/custom.scss +++ b/web-client/src/styles/custom.scss @@ -2407,4 +2407,20 @@ button.change-scanner-button { .stepper-line-height { line-height: 18px; +} + +.visibility-hidden { + visibility: hidden; +} + +.column-width-sm { + width: 150px +} + +.column-width-md { + width: 200px +} + +.column-width-lg { + width: 300px } \ No newline at end of file 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/test/createClientTestApplicationContext.ts b/web-client/src/test/createClientTestApplicationContext.ts index ccb6d15fb57..890ef12385b 100644 --- a/web-client/src/test/createClientTestApplicationContext.ts +++ b/web-client/src/test/createClientTestApplicationContext.ts @@ -37,7 +37,7 @@ import { compareCasesByDocketNumber, formatCaseForTrialSession, getFormattedTrialSessionDetails, -} from '@shared/business/utilities/getFormattedTrialSessionDetails'; +} from '@shared/business/utilities/trialSession/getFormattedTrialSessionDetails'; import { compareISODateStrings, compareStrings, diff --git a/web-client/src/ustc-ui/DateInput/DateRangePickerComponent.tsx b/web-client/src/ustc-ui/DateInput/DateRangePickerComponent.tsx index b8c9054075a..caa4b7e043d 100644 --- a/web-client/src/ustc-ui/DateInput/DateRangePickerComponent.tsx +++ b/web-client/src/ustc-ui/DateInput/DateRangePickerComponent.tsx @@ -1,24 +1,26 @@ import { FormGroup } from '../FormGroup/FormGroup'; -import React, { useEffect, useRef } from 'react'; +import React, { ChangeEvent, useEffect, useRef } from 'react'; import classNames from 'classnames'; import datePicker from '../../../../node_modules/@uswds/uswds/packages/usa-date-picker/src'; import dateRangePicker from '../../../../node_modules/@uswds/uswds/packages/usa-date-range-picker/src'; export const DateRangePickerComponent = ({ endDateErrorText, - endLabel, + endLabel = 'End date', endName, endPickerCls, endValue, formGroupCls, maxDate, omitFormGroupClass, + onBlurEnd, + onBlurStart, onChangeEnd, onChangeStart, rangePickerCls, showDateHint = false, startDateErrorText, - startLabel, + startLabel = 'Start date', startName, startPickerCls, startValue, @@ -31,8 +33,10 @@ export const DateRangePickerComponent = ({ endValue: string; formGroupCls?: string; rangePickerCls?: string; - onChangeEnd: (event: React.ChangeEvent) => void; - onChangeStart: (event: React.ChangeEvent) => void; + onBlurEnd?: (event: React.ChangeEvent) => void; + onBlurStart?: (event: React.ChangeEvent) => void; + onChangeEnd?: (event: React.ChangeEvent) => void; + onChangeStart?: (event: React.ChangeEvent) => void; startDateErrorText?: string; startPickerCls?: string; startLabel?: string | React.ReactNode; @@ -120,15 +124,34 @@ export const DateRangePickerComponent = ({ `${endName}-date-end`, ); if (dateEndInput) { - dateEndInput.addEventListener('change', onChangeEnd); - dateEndInput.addEventListener('input', onChangeEnd); + if (onChangeEnd) { + dateEndInput.addEventListener('change', event => { + onChangeEnd(event as unknown as ChangeEvent); + }); + + dateEndInput.addEventListener('input', event => { + onChangeEnd(event as unknown as ChangeEvent); + }); + } + if (onBlurEnd) { + dateEndInput.addEventListener('blur', onBlurEnd); + } } const dateStartInput = window.document.getElementById( `${startName}-date-start`, ); if (dateStartInput) { - dateStartInput.addEventListener('change', onChangeStart); - dateStartInput.addEventListener('input', onChangeStart); + if (onChangeStart) { + dateStartInput.addEventListener('change', event => { + onChangeStart(event as unknown as ChangeEvent); + }); + dateStartInput.addEventListener('input', event => { + onChangeStart(event as unknown as ChangeEvent); + }); + } + if (onBlurStart) { + dateStartInput.addEventListener('blur', onBlurStart); + } } } }, [startDateInputRef, endDateInputRef]); @@ -154,7 +177,7 @@ export const DateRangePickerComponent = ({ htmlFor={`${startName}-date-start`} id={`${startName}-date-start-label`} > - {startLabel || 'Start date'}{' '} + {startLabel}{' '} {showDateHint && MM/DD/YYYY}
@@ -171,7 +194,6 @@ export const DateRangePickerComponent = ({
-
- {endLabel || 'End date'}{' '} + {endLabel}{' '} {showDateHint && MM/DD/YYYY}
diff --git a/web-client/src/ustc-ui/Icon/LeftChevron.tsx b/web-client/src/ustc-ui/Icon/LeftChevron.tsx new file mode 100644 index 00000000000..7d3f7f2e35f --- /dev/null +++ b/web-client/src/ustc-ui/Icon/LeftChevron.tsx @@ -0,0 +1,20 @@ +import React from 'react'; +import classNames from 'classnames'; + +export function LeftChevron({ + className, + ...props +}: { className?: string } & React.SVGProps) { + return ( + + ); +} diff --git a/web-client/src/ustc-ui/Icon/RightChevron.tsx b/web-client/src/ustc-ui/Icon/RightChevron.tsx new file mode 100644 index 00000000000..86a9eb1f696 --- /dev/null +++ b/web-client/src/ustc-ui/Icon/RightChevron.tsx @@ -0,0 +1,20 @@ +import React from 'react'; +import classNames from 'classnames'; + +export function RightChevron({ + className, + ...props +}: { className?: string } & React.SVGProps) { + return ( + + ); +} diff --git a/web-client/src/ustc-ui/Pagination/Paginator.tsx b/web-client/src/ustc-ui/Pagination/Paginator.tsx index 1beb6346bc7..3a5436fc97a 100644 --- a/web-client/src/ustc-ui/Pagination/Paginator.tsx +++ b/web-client/src/ustc-ui/Pagination/Paginator.tsx @@ -1,75 +1,289 @@ -import { Button } from '@web-client/ustc-ui/Button/Button'; +import { LeftChevron } from '@web-client/ustc-ui/Icon/LeftChevron'; +import { RightChevron } from '@web-client/ustc-ui/Icon/RightChevron'; import React from 'react'; import classNames from 'classnames'; +/* +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, + showSinglePage = false, totalPages, }: { - totalPages: number; currentPageIndex: number; - onPageChange: (currentPage: number) => void; + totalPages: number; + showSinglePage?: boolean; + onPageChange: (selectedPage: number) => any; }) => { - let currentPage = currentPageIndex + 1; + if (totalPages === 0) { + return; + } + const numberOfPaginatorSlots = showSinglePage ? 1 : 7; + const sevenDisplayedSlots: React.JSX.Element[] = []; + + for (let slotNumber = 0; slotNumber < numberOfPaginatorSlots; slotNumber++) { + if (slotNumber >= totalPages) { + continue; + } + const slotComponent = getSlotComponent({ + currentPageIndex, + numberOfPaginatorSlots, + 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; isHidden: boolean }) => { + return ( + <> +
  • + +
  • + + ); +}; + +const PageEllipsis = () => { + return ( + <> +
  • + … +
  • + + ); +}; + +function getSlotComponent({ + currentPageIndex, + numberOfPaginatorSlots, + onPageChange, + slotNumber, + totalPages, +}: { + currentPageIndex: number; + onPageChange: (selectedPage: number) => any; + slotNumber: number; + totalPages: number; + numberOfPaginatorSlots: number; +}) { + const isHidingPreviousOptions = + currentPageIndex > 3 && totalPages > numberOfPaginatorSlots; + const isHidingFutureOptions = + totalPages - currentPageIndex > 4 && totalPages > numberOfPaginatorSlots; + if (slotNumber === 0) { + if (numberOfPaginatorSlots === 1) { + return ( + { + onPageChange(selectedPage); + }} + /> + ); + } + 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/Select/SelectSearch.tsx b/web-client/src/ustc-ui/Select/SelectSearch.tsx index f4ecbb70191..c0e42d614d1 100644 --- a/web-client/src/ustc-ui/Select/SelectSearch.tsx +++ b/web-client/src/ustc-ui/Select/SelectSearch.tsx @@ -1,92 +1,22 @@ -/* eslint-disable import/named */ -import { getSortedOptions } from '../../ustc-ui/Utils/selectSearchHelper'; -import React, { useState } from 'react'; -import ReactSelect, { - ActionMeta, - GroupBase, - OptionsOrGroups, -} from 'react-select'; +import React from 'react'; +import ReactSelect, { GroupBase, Props } from 'react-select'; import classNames from 'classnames'; -export const SelectSearch = ({ - className, - disabled, - id, - isClearable = true, - name, - onChange, - onInputChange, - options, - placeholder = '- Select -', - searchableOptions, - value, - ...props -}: { - className?: string; - disabled?: boolean; - id?: string; - isClearable?: boolean; - name?: string; - onChange: (newValue: any, actionMeta: ActionMeta) => void; - onInputChange?: Function; - options?: OptionsOrGroups> | undefined; - placeholder?: string; - searchableOptions?: OptionsOrGroups> | undefined; - value?: any; -}) => { - const [inputText, setInputText] = useState(''); - const [selectOptions, setSelectOptions] = useState(options); - - function handleOnInputChange(newInputText, { action }) { - if (action === 'input-change') { - setInputText(newInputText); - if (newInputText === '') { - setSelectOptions(options); - } else if (searchableOptions) { - setSelectOptions(searchableOptions); - } - } - if (typeof onInputChange === 'function') { - onInputChange(newInputText, action); - } - } - - function resetOptions() { - setInputText(''); - setSelectOptions(options); - } - - let sortedOptions = getSortedOptions(selectOptions, inputText); - - const aria = { - 'aria-describedby': props['aria-describedby'], - 'aria-disabled': disabled || props['aria-disabled'], - 'aria-label': props['aria-label'], - 'aria-labelledby': props['aria-labelledby'], - }; - +export function SelectSearch< + Option, + IsMulti extends boolean = false, + Group extends GroupBase