diff --git a/cypress/local-only/support/pages/public/advanced-search.ts b/cypress/local-only/support/pages/public/advanced-search.ts index 76207e6712b..0369a188e06 100644 --- a/cypress/local-only/support/pages/public/advanced-search.ts +++ b/cypress/local-only/support/pages/public/advanced-search.ts @@ -82,7 +82,7 @@ export const searchForOrderByJudge = judge => { }; export const publicHeader = () => { - return cy.get('h1.header-welcome-public'); + return cy.get('div.header-welcome-public'); }; export const petitionHyperlink = () => { diff --git a/cypress/local-only/tests/integration/public/trialSessions/trial-sessions.cy.ts b/cypress/local-only/tests/integration/public/trialSessions/trial-sessions.cy.ts new file mode 100644 index 00000000000..802004d58b5 --- /dev/null +++ b/cypress/local-only/tests/integration/public/trialSessions/trial-sessions.cy.ts @@ -0,0 +1,68 @@ +import { selectTypeaheadInput } from '../../../../../helpers/components/typeAhead/select-typeahead-input'; + +describe('Public Trial Sessions', () => { + beforeEach(() => { + cy.visit('/trial-sessions'); + }); + + it('should display table filters correctly', () => { + cy.get('[data-testid="proceeding-type-filter"]').should('be.visible'); + cy.get('[data-testid="session-type-filter"]').should('be.visible'); + cy.get('[data-testid="location-filter"]').should('be.visible'); + cy.get('[data-testid="judge-filter"]').should('be.visible'); + + cy.get('[data-testid="remote-proceedings-card"]').should('be.visible'); + cy.get('[data-testid="remote-proceedings-card"]') + .find('a') + .should('have.length', 2); + + cy.get('[data-testid="trial-sessions-reset-filters-button"]').should( + 'be.disabled', + ); + }); + + it('should enable the reset filters button if there are any filters aplied', () => { + cy.get('[data-testid="trial-sessions-reset-filters-button"]').should( + 'be.disabled', + ); + + cy.get('[data-testid="In Person-proceeding-label"]').click(); + + cy.get('[data-testid="trial-sessions-reset-filters-button"]').should( + 'be.enabled', + ); + }); + + it('should display Pill Button for every dropdown filter selected', () => { + const SESSION_TYPE = 'Regular'; + selectTypeaheadInput('sessionTypes-filter-select', SESSION_TYPE); + cy.get(`[data-testid="sessionTypes-${SESSION_TYPE}-pill-button"]`); + + const LOCATION = 'Mobile, Alabama'; + selectTypeaheadInput('locations-filter-select', LOCATION); + cy.get(`[data-testid="locations-${LOCATION}-pill-button"]`); + + const JUDGE = 'Buch'; + selectTypeaheadInput('judges-filter-select', JUDGE); + cy.get(`[data-testid="judges-${JUDGE}-pill-button"]`); + + cy.get(`[data-testid="sessionTypes-${SESSION_TYPE}-pill-button"]`) + .find('button') + .click(); + cy.get(`[data-testid="sessionTypes-${SESSION_TYPE}-pill-button"]`).should( + 'not.exist', + ); + + cy.get(`[data-testid="locations-${LOCATION}-pill-button"]`) + .find('button') + .click(); + cy.get(`[data-testid="locations-${LOCATION}-pill-button"]`).should( + 'not.exist', + ); + + cy.get(`[data-testid="judges-${JUDGE}-pill-button"]`) + .find('button') + .click(); + cy.get(`[data-testid="judges-${JUDGE}-pill-button"]`).should('not.exist'); + }); +}); diff --git a/shared/src/proxies/trialSessions/getPublicTrialSessionsProxy.ts b/shared/src/proxies/trialSessions/getPublicTrialSessionsProxy.ts new file mode 100644 index 00000000000..7ff64758ee4 --- /dev/null +++ b/shared/src/proxies/trialSessions/getPublicTrialSessionsProxy.ts @@ -0,0 +1,11 @@ +import { TrialSessionInfoDTO } from '@shared/business/dto/trialSessions/TrialSessionInfoDTO'; +import { get } from '@shared/proxies/requests'; + +export const getPublicTrialSessionsInteractor = ( + applicationContext, +): Promise => { + return get({ + applicationContext, + endpoint: '/public-api/trial-sessions', + }); +}; diff --git a/shared/src/proxies/users/getPublicUsersInSectionProxy.ts b/shared/src/proxies/users/getPublicUsersInSectionProxy.ts new file mode 100644 index 00000000000..e82b5ff483d --- /dev/null +++ b/shared/src/proxies/users/getPublicUsersInSectionProxy.ts @@ -0,0 +1,12 @@ +import { RawUser } from '@shared/business/entities/User'; +import { get } from '../requests'; + +export const getPublicUsersInSectionInteractor = ( + applicationContext, + { section }: { section: string }, +): Promise => { + return get({ + applicationContext, + endpoint: `/public-api/sections/${section}/users`, + }); +}; diff --git a/web-api/src/app-public.ts b/web-api/src/app-public.ts index ee9a13732a0..47710694db2 100644 --- a/web-api/src/app-public.ts +++ b/web-api/src/app-public.ts @@ -83,6 +83,8 @@ import { getPublicCaseExistsLambda } from './lambdas/public-api/getPublicCaseExi import { getPublicCaseLambda } from './lambdas/public-api/getPublicCaseLambda'; import { getPublicDocumentDownloadUrlLambda } from './lambdas/public-api/getPublicDocumentDownloadUrlLambda'; import { getPublicJudgesLambda } from './lambdas/public-api/getPublicJudgesLambda'; +import { getPublicTrialSessionsLambda } from '@web-api/lambdas/trialSessions/getPublicTrialSessionsLambda'; +import { getUsersInSectionLambda } from '@web-api/lambdas/users/getUsersInSectionLambda'; import { ipLimiter } from './middleware/ipLimiter'; import { opinionPublicSearchLambda } from './lambdas/public-api/opinionPublicSearchLambda'; import { orderPublicSearchLambda } from './lambdas/public-api/orderPublicSearchLambda'; @@ -178,6 +180,17 @@ app.get('/public-api/judges', lambdaWrapper(getPublicJudgesLambda)); ); } +{ + app.get( + '/public-api/trial-sessions', + lambdaWrapper(getPublicTrialSessionsLambda), + ); + app.get( + '/public-api/sections/:section/users', + lambdaWrapper(getUsersInSectionLambda), + ); +} + /** * Feature flags */ diff --git a/web-api/src/business/useCases/trialSessions/getPublicTrialSessionsInteractor.test.ts b/web-api/src/business/useCases/trialSessions/getPublicTrialSessionsInteractor.test.ts new file mode 100644 index 00000000000..d8bab02cb18 --- /dev/null +++ b/web-api/src/business/useCases/trialSessions/getPublicTrialSessionsInteractor.test.ts @@ -0,0 +1,79 @@ +import { applicationContext } from '../../../../../shared/src/business/test/createTestApplicationContext'; +import { getPublicTrialSessionsInteractor } from '@web-api/business/useCases/trialSessions/getPublicTrialSessionsInteractor'; + +describe('getPublicTrialSessionsInteractor', () => { + beforeEach(() => { + applicationContext + .getPersistenceGateway() + .getTrialSessions.mockReturnValue(MOCK_TRIAL_SESSIONS); + }); + + it('should return open trial sessions', async () => { + const result = await getPublicTrialSessionsInteractor(applicationContext); + expect(result.every(session => session.sessionStatus === 'Open')).toBe( + true, + ); + }); +}); + +const MOCK_TRIAL_SESSIONS = [ + { + caseOrder: [], + createdAt: '2019-11-02T05:00:00.000Z', + gsi1pk: 'trial-session-catalog', + isCalendared: true, + judge: { name: 'Cohen', userId: 'dabbad04-18d0-43ec-bafb-654e83405416' }, + maxCases: 30, + pk: 'trial-session|0d943468-bc2e-4631-84e3-b084cf5b1fbb', + proceedingType: 'In Person', + sessionStatus: 'Open', + sessionType: 'Special', + sk: 'trial-session|0d943468-bc2e-4631-84e3-b084cf5b1fbb', + startDate: '2019-12-02T05:00:00.000Z', + startTime: '21:00', + status: 'Closed', + term: 'Fall', + termYear: '2019', + trialLocation: 'Denver, Colorado', + trialSessionId: '0d943468-bc2e-4631-84e3-b084cf5b1fbb', + }, + { + caseOrder: [], + createdAt: '2020-10-25T05:00:00.000Z', + gsi1pk: 'trial-session-catalog', + isCalendared: true, + judge: { name: 'Colvin', userId: 'dabbad00-18d0-43ec-bafb-654e83405416' }, + maxCases: 100, + pk: 'trial-session|111ac21b-99f9-4321-98c8-b95db00af96b', + proceedingType: 'Remote', + sessionScope: 'Standalone Remote', + sessionStatus: 'Open', + sessionType: 'Special', + sk: 'trial-session|111ac21b-99f9-4321-98c8-b95db00af96b', + startDate: '2020-11-25T05:00:00.000Z', + startTime: '13:00', + term: 'Fall', + termYear: '2020', + trialLocation: 'Standalone Remote', + trialSessionId: '111ac21b-99f9-4321-98c8-b95db00af96b', + }, + { + caseOrder: [], + createdAt: '2020-10-02T05:00:00.000Z', + gsi1pk: 'trial-session-catalog', + isCalendared: false, + judge: { name: 'Cohen', userId: 'dabbad04-18d0-43ec-bafb-654e83405416' }, + maxCases: 8, + pk: 'trial-session|149159ca-f4a1-4b2b-bc24-bd1fbe6defdc', + proceedingType: 'In Person', + sessionStatus: 'New', + sessionType: 'Regular', + sk: 'trial-session|149159ca-f4a1-4b2b-bc24-bd1fbe6defdc', + startDate: '2020-12-02T05:00:00.000Z', + startTime: '09:00', + term: 'Fall', + termYear: '2020', + trialLocation: 'Birmingham, Alabama', + trialSessionId: '149159ca-f4a1-4b2b-bc24-bd1fbe6defdc', + }, +]; diff --git a/web-api/src/business/useCases/trialSessions/getPublicTrialSessionsInteractor.ts b/web-api/src/business/useCases/trialSessions/getPublicTrialSessionsInteractor.ts new file mode 100644 index 00000000000..6ead2b3a4a8 --- /dev/null +++ b/web-api/src/business/useCases/trialSessions/getPublicTrialSessionsInteractor.ts @@ -0,0 +1,18 @@ +import { ServerApplicationContext } from '@web-api/applicationContext'; +import { TrialSession } from '@shared/business/entities/trialSessions/TrialSession'; +import { TrialSessionInfoDTO } from '../../../../../shared/src/business/dto/trialSessions/TrialSessionInfoDTO'; + +export const getPublicTrialSessionsInteractor = async ( + applicationContext: ServerApplicationContext, +): Promise => { + const trialSessions = await applicationContext + .getPersistenceGateway() + .getTrialSessions({ + applicationContext, + }); + + return trialSessions + .map(t => new TrialSession(t).toRawObject()) + .map(trialSession => new TrialSessionInfoDTO(trialSession)) + .filter(trialSession => trialSession.sessionStatus === 'Open'); +}; diff --git a/web-api/src/business/useCases/user/getUsersInSectionInteractor.ts b/web-api/src/business/useCases/user/getUsersInSectionInteractor.ts index bc3092a70f3..65c676b9ea8 100644 --- a/web-api/src/business/useCases/user/getUsersInSectionInteractor.ts +++ b/web-api/src/business/useCases/user/getUsersInSectionInteractor.ts @@ -23,7 +23,7 @@ export const getUsersInSectionInteractor = async ( rolePermission = ROLE_PERMISSIONS.GET_USERS_IN_SECTION; } - if (!isAuthorized(authorizedUser, rolePermission)) { + if (!!authorizedUser && !isAuthorized(authorizedUser, rolePermission)) { throw new UnauthorizedError('Unauthorized'); } diff --git a/web-api/src/lambdas/trialSessions/getPublicTrialSessionsLambda.ts b/web-api/src/lambdas/trialSessions/getPublicTrialSessionsLambda.ts new file mode 100644 index 00000000000..b925224510c --- /dev/null +++ b/web-api/src/lambdas/trialSessions/getPublicTrialSessionsLambda.ts @@ -0,0 +1,7 @@ +import { genericHandler } from '@web-api/genericHandler'; +import { getPublicTrialSessionsInteractor } from '@web-api/business/useCases/trialSessions/getPublicTrialSessionsInteractor'; + +export const getPublicTrialSessionsLambda = event => + genericHandler(event, async ({ applicationContext }) => { + return await getPublicTrialSessionsInteractor(applicationContext); + }); diff --git a/web-client/src/appPublic.tsx b/web-client/src/appPublic.tsx index 2abd1ae7174..fac0c591bad 100644 --- a/web-client/src/appPublic.tsx +++ b/web-client/src/appPublic.tsx @@ -37,6 +37,8 @@ import { faArrowAltCircleLeft as faArrowAltCircleLeftRegular } from '@fortawesom import { faExclamation } from '@fortawesome/free-solid-svg-icons/faExclamation'; import { faExclamationCircle } from '@fortawesome/free-solid-svg-icons/faExclamationCircle'; import { faExclamationTriangle } from '@fortawesome/free-solid-svg-icons/faExclamationTriangle'; +import { faLink } from '@fortawesome/free-solid-svg-icons/faLink'; +import { faTimes } from '@fortawesome/free-solid-svg-icons/faTimes'; import { faTimesCircle as faTimesCircleRegular } from '@fortawesome/free-regular-svg-icons/faTimesCircle'; import { faUser } from '@fortawesome/free-regular-svg-icons/faUser'; import { isFunction, mapValues } from 'lodash'; @@ -66,6 +68,8 @@ const appPublic = { faFileAltSolid, faLock, faLongArrowAltUp, + faLink, + faTimes, faPrint, faFilePdf, faSearch, diff --git a/web-client/src/applicationContextPublic.ts b/web-client/src/applicationContextPublic.ts index aef367de660..6508131e9c7 100644 --- a/web-client/src/applicationContextPublic.ts +++ b/web-client/src/applicationContextPublic.ts @@ -72,6 +72,8 @@ import { getPublicCaseInteractor } from '../../shared/src/proxies/getPublicCaseP import { getPublicJudgesInteractor } from '../../shared/src/proxies/public/getPublicJudgesProxy'; import { getPublicPractitionerByBarNumberInteractor } from '@shared/proxies/public/getPublicPractitionerByBarNumberProxy'; import { getPublicPractitionersByNameInteractor } from '@shared/proxies/public/getPublicPractitionersByNameProxy'; +import { getPublicTrialSessionsInteractor } from '@shared/proxies/trialSessions/getPublicTrialSessionsProxy'; +import { getPublicUsersInSectionInteractor } from '@shared/proxies/users/getPublicUsersInSectionProxy'; import { getSealedDocketEntryTooltip } from '../../shared/src/business/utilities/getSealedDocketEntryTooltip'; import { getTodaysOpinionsInteractor } from '../../shared/src/proxies/public/getTodaysOpinionsProxy'; import { getTodaysOrdersInteractor } from '../../shared/src/proxies/public/getTodaysOrdersProxy'; @@ -115,6 +117,8 @@ const allUseCases = { getPublicJudgesInteractor, getTodaysOpinionsInteractor, getTodaysOrdersInteractor, + getTrialSessionsInteractor: getPublicTrialSessionsInteractor, + getUsersInSectionInteractor: getPublicUsersInSectionInteractor, opinionPublicSearchInteractor, orderPublicSearchInteractor, removeItemInteractor, diff --git a/web-client/src/presenter/actions/TrialSession/setTimeStampAction.test.ts b/web-client/src/presenter/actions/TrialSession/setTimeStampAction.test.ts new file mode 100644 index 00000000000..101dbf86ec7 --- /dev/null +++ b/web-client/src/presenter/actions/TrialSession/setTimeStampAction.test.ts @@ -0,0 +1,14 @@ +import { DateTime } from 'luxon'; +import { runAction } from '@web-client/presenter/test.cerebral'; +import { setTimeStampAction } from '@web-client/presenter/actions/TrialSession/setTimeStampAction'; + +describe('setTimeStampAction', () => { + it('should set time stamp', async () => { + const propertyName = 'FetchedTrialSessions'; + const result = await runAction(setTimeStampAction({ propertyName }), {}); + + const expectedDate = DateTime.now().setZone('America/New_York').toISODate(); + + expect(result.state[propertyName].toISODate()).toEqual(expectedDate); + }); +}); diff --git a/web-client/src/presenter/actions/TrialSession/setTimeStampAction.ts b/web-client/src/presenter/actions/TrialSession/setTimeStampAction.ts new file mode 100644 index 00000000000..bc1c9848e29 --- /dev/null +++ b/web-client/src/presenter/actions/TrialSession/setTimeStampAction.ts @@ -0,0 +1,8 @@ +import { DateTime } from 'luxon'; +import { state } from '@web-client/presenter/app.cerebral'; + +export const setTimeStampAction = + ({ propertyName }: { propertyName: string }) => + ({ store }: ActionProps) => { + store.set(state[propertyName], DateTime.now().setZone('America/New_York')); + }; diff --git a/web-client/src/presenter/actions/resetPublicTrialSessionDataAction.test.ts b/web-client/src/presenter/actions/resetPublicTrialSessionDataAction.test.ts new file mode 100644 index 00000000000..43d00566f31 --- /dev/null +++ b/web-client/src/presenter/actions/resetPublicTrialSessionDataAction.test.ts @@ -0,0 +1,18 @@ +import { PublicClientState } from '@web-client/presenter/state-public'; +import { resetPublicTrialSessionDataAction } from '@web-client/presenter/actions/resetPublicTrialSessionDataAction'; +import { runAction } from '@web-client/presenter/test.cerebral'; + +describe('resetPublicTrialSessionDataAction', () => { + it('should reset trial session data', async () => { + const result = await runAction( + resetPublicTrialSessionDataAction, + { + state: { + publicTrialSessionData: { id: 123 }, + }, + }, + ); + + expect(result.state.publicTrialSessionData).toEqual({}); + }); +}); diff --git a/web-client/src/presenter/actions/resetPublicTrialSessionDataAction.ts b/web-client/src/presenter/actions/resetPublicTrialSessionDataAction.ts new file mode 100644 index 00000000000..f3282342c1d --- /dev/null +++ b/web-client/src/presenter/actions/resetPublicTrialSessionDataAction.ts @@ -0,0 +1,5 @@ +import { state } from '@web-client/presenter/app-public.cerebral'; + +export const resetPublicTrialSessionDataAction = ({ store }: ActionProps) => { + store.set(state.publicTrialSessionData, {}); +}; diff --git a/web-client/src/presenter/computeds/Public/publicTrialSessionHelper.test.ts b/web-client/src/presenter/computeds/Public/publicTrialSessionHelper.test.ts new file mode 100644 index 00000000000..98e4c7d2e25 --- /dev/null +++ b/web-client/src/presenter/computeds/Public/publicTrialSessionHelper.test.ts @@ -0,0 +1,476 @@ +import { DateTime } from 'luxon'; +import { TrialSessionInfoDTO } from '@shared/business/dto/trialSessions/TrialSessionInfoDTO'; +import { publicTrialSessionsHelper } from '@web-client/presenter/computeds/Public/publicTrialSessionsHelper'; +import { runCompute } from '@web-client/presenter/test.cerebral'; + +describe('publicTrialSessionsHelper', () => { + const TEST_TIME = DateTime.fromObject({ + day: 22, + hour: 0, + minute: 0, + month: 10, + second: 0, + year: 2024, + }); + + it('should generate the correct "fetchedDateString" string', () => { + const { fetchedDateString } = runCompute(publicTrialSessionsHelper, { + state: { + FetchedTrialSessions: TEST_TIME, + publicTrialSessionData: {}, + }, + }); + + expect(fetchedDateString).toBe('10/22/24 12:00 AM Eastern'); + }); + + it('should return the "sessiontTypeOptions" value correctly', () => { + const { sessionTypeOptions } = runCompute(publicTrialSessionsHelper, { + state: { + FetchedTrialSessions: TEST_TIME, + publicTrialSessionData: {}, + }, + }); + + expect(sessionTypeOptions).toEqual([ + { + label: 'Regular', + value: 'Regular', + }, + { + label: 'Small', + value: 'Small', + }, + { + label: 'Hybrid', + value: 'Hybrid', + }, + { + label: 'Hybrid-S', + value: 'Hybrid-S', + }, + { + label: 'Special', + value: 'Special', + }, + { + label: 'Motion/Hearing', + value: 'Motion/Hearing', + }, + ]); + }); + + it('should return the "trialCitiesByState" value correctly', () => { + const { trialCitiesByState } = runCompute(publicTrialSessionsHelper, { + state: { + FetchedTrialSessions: TEST_TIME, + publicTrialSessionData: {}, + }, + }); + + expect(trialCitiesByState).toBeDefined(); + }); + + it('should return the "trialSessionJudgeOptions" value correctly', () => { + const { trialSessionJudgeOptions } = runCompute(publicTrialSessionsHelper, { + state: { + FetchedTrialSessions: TEST_TIME, + judges: [ + { name: 'TEST_JUDGE_1', userId: '1' }, + { name: 'TEST_JUDGE_2', userId: '2' }, + { name: 'TEST_JUDGE_3', userId: '3' }, + { name: 'TEST_JUDGE_4', userId: '4' }, + ], + publicTrialSessionData: {}, + }, + }); + + expect(trialSessionJudgeOptions).toEqual([ + { + label: 'TEST_JUDGE_1', + value: 'TEST_JUDGE_1', + }, + { + label: 'TEST_JUDGE_2', + value: 'TEST_JUDGE_2', + }, + { + label: 'TEST_JUDGE_3', + value: 'TEST_JUDGE_3', + }, + { + label: 'TEST_JUDGE_4', + value: 'TEST_JUDGE_4', + }, + ]); + }); + + describe('filtersHaveBeenModified', () => { + it('should return "false" if there is no filters modified', () => { + const { filtersHaveBeenModified } = runCompute( + publicTrialSessionsHelper, + { + state: { + FetchedTrialSessions: TEST_TIME, + publicTrialSessionData: {}, + }, + }, + ); + + expect(filtersHaveBeenModified).toEqual(false); + }); + + it('should return "true" when the "proceedingTypes" is not default', () => { + const { filtersHaveBeenModified } = runCompute( + publicTrialSessionsHelper, + { + state: { + FetchedTrialSessions: TEST_TIME, + publicTrialSessionData: { + proceedingType: 'SOME_OPTION', + }, + }, + }, + ); + + expect(filtersHaveBeenModified).toEqual(true); + }); + + it('should return "true" when the "judges" is not default', () => { + const { filtersHaveBeenModified } = runCompute( + publicTrialSessionsHelper, + { + state: { + FetchedTrialSessions: TEST_TIME, + publicTrialSessionData: { + judges: { + TEST_JUDGE: 'TEST_JUDGE', + }, + }, + }, + }, + ); + + expect(filtersHaveBeenModified).toEqual(true); + }); + + it('should return "true" when the "locations" is not default', () => { + const { filtersHaveBeenModified } = runCompute( + publicTrialSessionsHelper, + { + state: { + FetchedTrialSessions: TEST_TIME, + publicTrialSessionData: { + locations: { + TEST_LOCATION: 'TEST_LOCATION', + }, + }, + }, + }, + ); + + expect(filtersHaveBeenModified).toEqual(true); + }); + + it('should return "true" when the "sessionTypes" is not default', () => { + const { filtersHaveBeenModified } = runCompute( + publicTrialSessionsHelper, + { + state: { + FetchedTrialSessions: TEST_TIME, + publicTrialSessionData: { + sessionTypes: { + TEST_SESSION_TYPE: 'TEST_SESSION_TYPE', + }, + }, + }, + }, + ); + + expect(filtersHaveBeenModified).toEqual(true); + }); + }); + + describe('trialSessionRows', () => { + function createTrialSessionObject(overrides: { + [key: string]: any; + }): TrialSessionInfoDTO { + return { + isCalendared: true, + judge: { + name: 'Ashford', + userId: 'dabbad01-18d0-43ec-bafb-654e83405416', + }, + proceedingType: 'In Person', + sessionScope: 'Standalone Remote', + sessionStatus: 'Open', + sessionType: 'Regular', + startDate: '2020-11-25T05:00:00.000Z', + term: 'Fall', + termYear: '2020', + trialLocation: 'Birmingham, Alabama', + ...overrides, + }; + } + + it('should return all the trialSessions if there are no filter', () => { + const TEST_TRIAL_SESSIONS: TrialSessionInfoDTO[] = [ + createTrialSessionObject({ proceedingType: 'Remote' }), + createTrialSessionObject({ sessionType: 'Small' }), + createTrialSessionObject({ trialLocation: 'Mobile, Alabama' }), + createTrialSessionObject({ + judge: { + name: 'Buch', + userId: 'dabbad01-18d0-43ec-bafb-654e83405416', + }, + }), + ]; + + const { trialSessionsCount } = runCompute(publicTrialSessionsHelper, { + state: { + FetchedTrialSessions: TEST_TIME, + publicTrialSessionData: {}, + trialSessionsPage: { + trialSessions: TEST_TRIAL_SESSIONS, + }, + }, + }); + + expect(trialSessionsCount).toEqual(4); + }); + + it('should return all the trialSessions that meet the proceedingType filter', () => { + const TEST_PROCEEDING_TYPE = 'TEST_PROCEEDING_TYPE'; + const TEST_TRIAL_SESSIONS: TrialSessionInfoDTO[] = [ + createTrialSessionObject({ proceedingType: TEST_PROCEEDING_TYPE }), + createTrialSessionObject({ sessionType: 'Small' }), + createTrialSessionObject({ trialLocation: 'Mobile, Alabama' }), + createTrialSessionObject({ + judge: { + name: 'Buch', + userId: 'dabbad01-18d0-43ec-bafb-654e83405416', + }, + }), + ]; + + const { trialSessionRows, trialSessionsCount } = runCompute( + publicTrialSessionsHelper, + { + state: { + FetchedTrialSessions: TEST_TIME, + publicTrialSessionData: { + proceedingType: TEST_PROCEEDING_TYPE, + }, + trialSessionsPage: { + trialSessions: TEST_TRIAL_SESSIONS, + }, + }, + }, + ); + + expect(trialSessionsCount).toEqual(1); + expect(trialSessionRows).toEqual([ + { + formattedSessionWeekStartDate: 'November 23, 2020', + sessionWeekStartDate: '2020-11-23T05:00:00.000+00:00', + }, + { + alertMessageForNOTT: '', + formattedEstimatedEndDate: '', + formattedNoticeIssuedDate: '', + formattedStartDate: '11/25/20', + judge: { + name: 'Ashford', + userId: 'dabbad01-18d0-43ec-bafb-654e83405416', + }, + proceedingType: 'TEST_PROCEEDING_TYPE', + sessionStatus: 'Open', + sessionType: 'Regular', + showAlertForNOTTReminder: false, + startDate: '2020-11-25T05:00:00.000Z', + swingSession: false, + trialLocation: 'Birmingham, Alabama', + trialSessionId: '', + userIsAssignedToSession: false, + }, + ]); + }); + + it('should return all the trialSessions that meet the judge filter', () => { + const TEST_JUDGE_NAME = 'TEST_JUDGE_NAME'; + const TEST_TRIAL_SESSIONS: TrialSessionInfoDTO[] = [ + createTrialSessionObject({ proceedingType: 'Remote' }), + createTrialSessionObject({ sessionType: 'Small' }), + createTrialSessionObject({ trialLocation: 'Mobile, Alabama' }), + createTrialSessionObject({ + judge: { + name: TEST_JUDGE_NAME, + userId: 'dabbad01-18d0-43ec-bafb-654e83405416', + }, + }), + ]; + + const { trialSessionRows, trialSessionsCount } = runCompute( + publicTrialSessionsHelper, + { + state: { + FetchedTrialSessions: TEST_TIME, + publicTrialSessionData: { + judges: { + [TEST_JUDGE_NAME]: TEST_JUDGE_NAME, + }, + }, + trialSessionsPage: { + trialSessions: TEST_TRIAL_SESSIONS, + }, + }, + }, + ); + + expect(trialSessionsCount).toEqual(1); + expect(trialSessionRows).toEqual([ + { + formattedSessionWeekStartDate: 'November 23, 2020', + sessionWeekStartDate: '2020-11-23T05:00:00.000+00:00', + }, + { + alertMessageForNOTT: '', + formattedEstimatedEndDate: '', + formattedNoticeIssuedDate: '', + formattedStartDate: '11/25/20', + judge: { + name: TEST_JUDGE_NAME, + userId: 'dabbad01-18d0-43ec-bafb-654e83405416', + }, + proceedingType: 'In Person', + sessionStatus: 'Open', + sessionType: 'Regular', + showAlertForNOTTReminder: false, + startDate: '2020-11-25T05:00:00.000Z', + swingSession: false, + trialLocation: 'Birmingham, Alabama', + trialSessionId: '', + userIsAssignedToSession: false, + }, + ]); + }); + + it('should return all the trialSessions that meet the location filter', () => { + const TEST_LOCATION = 'TEST_LOCATION'; + const TEST_TRIAL_SESSIONS: TrialSessionInfoDTO[] = [ + createTrialSessionObject({ proceedingType: 'Remote' }), + createTrialSessionObject({ sessionType: 'Small' }), + createTrialSessionObject({ trialLocation: TEST_LOCATION }), + createTrialSessionObject({ + judge: { + name: 'Buch', + userId: 'dabbad01-18d0-43ec-bafb-654e83405416', + }, + }), + ]; + + const { trialSessionRows, trialSessionsCount } = runCompute( + publicTrialSessionsHelper, + { + state: { + FetchedTrialSessions: TEST_TIME, + publicTrialSessionData: { + locations: { + [TEST_LOCATION]: TEST_LOCATION, + }, + }, + trialSessionsPage: { + trialSessions: TEST_TRIAL_SESSIONS, + }, + }, + }, + ); + + expect(trialSessionsCount).toEqual(1); + expect(trialSessionRows).toEqual([ + { + formattedSessionWeekStartDate: 'November 23, 2020', + sessionWeekStartDate: '2020-11-23T05:00:00.000+00:00', + }, + { + alertMessageForNOTT: '', + formattedEstimatedEndDate: '', + formattedNoticeIssuedDate: '', + formattedStartDate: '11/25/20', + judge: { + name: 'Ashford', + userId: 'dabbad01-18d0-43ec-bafb-654e83405416', + }, + proceedingType: 'In Person', + sessionStatus: 'Open', + sessionType: 'Regular', + showAlertForNOTTReminder: false, + startDate: '2020-11-25T05:00:00.000Z', + swingSession: false, + trialLocation: TEST_LOCATION, + trialSessionId: '', + userIsAssignedToSession: false, + }, + ]); + }); + + it('should return all the trialSessions that meet the sessionType filter', () => { + const TEST_SESSION_TYPE = 'TEST_SESSION_TYPE'; + const TEST_TRIAL_SESSIONS: TrialSessionInfoDTO[] = [ + createTrialSessionObject({ proceedingType: 'Remote' }), + createTrialSessionObject({ sessionType: TEST_SESSION_TYPE }), + createTrialSessionObject({ trialLocation: 'Mobile, Alabama' }), + createTrialSessionObject({ + judge: { + name: 'Buch', + userId: 'dabbad01-18d0-43ec-bafb-654e83405416', + }, + }), + ]; + + const { trialSessionRows, trialSessionsCount } = runCompute( + publicTrialSessionsHelper, + { + state: { + FetchedTrialSessions: TEST_TIME, + publicTrialSessionData: { + sessionTypes: { + [TEST_SESSION_TYPE]: TEST_SESSION_TYPE, + }, + }, + trialSessionsPage: { + trialSessions: TEST_TRIAL_SESSIONS, + }, + }, + }, + ); + + expect(trialSessionsCount).toEqual(1); + expect(trialSessionRows).toEqual([ + { + formattedSessionWeekStartDate: 'November 23, 2020', + sessionWeekStartDate: '2020-11-23T05:00:00.000+00:00', + }, + { + alertMessageForNOTT: '', + formattedEstimatedEndDate: '', + formattedNoticeIssuedDate: '', + formattedStartDate: '11/25/20', + judge: { + name: 'Ashford', + userId: 'dabbad01-18d0-43ec-bafb-654e83405416', + }, + proceedingType: 'In Person', + sessionStatus: 'Open', + sessionType: TEST_SESSION_TYPE, + showAlertForNOTTReminder: false, + startDate: '2020-11-25T05:00:00.000Z', + swingSession: false, + trialLocation: 'Birmingham, Alabama', + trialSessionId: '', + userIsAssignedToSession: false, + }, + ]); + }); + }); +}); diff --git a/web-client/src/presenter/computeds/Public/publicTrialSessionsHelper.ts b/web-client/src/presenter/computeds/Public/publicTrialSessionsHelper.ts new file mode 100644 index 00000000000..9279565dfd9 --- /dev/null +++ b/web-client/src/presenter/computeds/Public/publicTrialSessionsHelper.ts @@ -0,0 +1,175 @@ +import { Get } from 'cerebral'; +import { + SESSION_TYPES, + TRIAL_SESSION_SCOPE_TYPES, +} from '@shared/business/entities/EntityConstants'; +import { + TrialSessionRow, + TrialSessionWeek, + formatTrialSessions, + isTrialSessionWeek, +} from '@web-client/presenter/computeds/trialSessionsHelper'; +import { getTrialCitiesGroupedByState } from '@shared/business/utilities/trialSession/trialCitiesGroupedByState'; +import { state } from '@web-client/presenter/app-public.cerebral'; + +export type PublicTrialSessionsHelperResults = { + fetchedDateString: string; + sessionTypeOptions: { + label: string; + value: string; + }[]; + trialCitiesByState: { + label: string; + options: { label: string; value: string }[]; + }[]; + trialSessionJudgeOptions: { + label: string; + value: string; + }[]; + filtersHaveBeenModified: boolean; + totalPages: number; + trialSessionsCount: number; + trialSessionRows: (TrialSessionRow | TrialSessionWeek)[]; + groupedTrialsSessions: { + header: TrialSessionWeek; + rows: TrialSessionRow[]; + }[]; +}; + +function areAnyFiltersModified( + proceedingType: string, + judges: { [key: string]: string }, + locations: { [key: string]: string }, + sessionTypes: { [key: string]: string }, +): boolean { + const proceedingTypeModified = proceedingType !== 'All'; + const judgesModified = Object.values(judges).filter(j => !!j).length; + const locationsModified = Object.values(locations).filter(l => !!l).length; + const sessionTypesModified = Object.values(sessionTypes).filter( + st => !!st, + ).length; + + return ( + !!proceedingTypeModified || + !!judgesModified || + !!locationsModified || + !!sessionTypesModified + ); +} + +function groupTrialSessions( + trialSessions: (TrialSessionRow | TrialSessionWeek)[], +): { header: TrialSessionWeek; rows: TrialSessionRow[] }[] { + const groupedTrialSessions: { + header: TrialSessionWeek; + rows: TrialSessionRow[]; + }[] = []; + + let counter = -1; + trialSessions.forEach(tsRow => { + if (isTrialSessionWeek(tsRow)) { + groupedTrialSessions.push({ + header: tsRow, + rows: [] as TrialSessionRow[], + }); + counter += 1; + } else { + groupedTrialSessions[counter].rows.push(tsRow); + } + }); + + return groupedTrialSessions; +} + +const PAGE_SIZE = 100; + +export const publicTrialSessionsHelper = ( + get: Get, +): PublicTrialSessionsHelperResults => { + const fetchedTrialSessions = get(state['FetchedTrialSessions']); + const trialSessionJudges = get(state.judges) || []; + const { + judges = {}, + locations = {}, + pageNumber = 0, + proceedingType = 'All', + sessionTypes = {}, + } = get(state.publicTrialSessionData); + + const trialSessions = get(state.trialSessionsPage.trialSessions) || []; + const fetchedDateString = fetchedTrialSessions.toFormat( + "MM/dd/yy hh:mm a 'Eastern'", + ); + + const sessionTypeOptions = Object.values(SESSION_TYPES).map(sessionType => ({ + label: sessionType, + value: sessionType, + })); + + const standaloneRemoteOption = { + label: TRIAL_SESSION_SCOPE_TYPES.standaloneRemote, + options: [ + { + label: TRIAL_SESSION_SCOPE_TYPES.standaloneRemote, + value: TRIAL_SESSION_SCOPE_TYPES.standaloneRemote, + }, + ], + }; + const trialCitiesByState = [ + standaloneRemoteOption, + ...getTrialCitiesGroupedByState(), + ]; + + const trialSessionJudgeOptions = trialSessionJudges.map( + trialSessionJudge => ({ + label: trialSessionJudge.name, + value: trialSessionJudge.name, + }), + ); + + const filtersHaveBeenModified = areAnyFiltersModified( + proceedingType, + judges, + locations, + sessionTypes, + ); + + const filteredTrialSessions = trialSessions + .filter( + ts => proceedingType === 'All' || ts.proceedingType === proceedingType, + ) + .filter(ts => !Object.entries(judges).length || judges[ts.judge?.name!]) + .filter( + ts => !Object.entries(locations).length || locations[ts.trialLocation!], + ) + .filter( + ts => + !Object.entries(sessionTypes).length || sessionTypes[ts.sessionType!], + ) + .sort((sessionA, sessionB) => { + return sessionA.startDate.localeCompare(sessionB.startDate); + }); + + const paginatedTrialSessions = filteredTrialSessions.slice( + pageNumber * PAGE_SIZE, + pageNumber * PAGE_SIZE + PAGE_SIZE, + ); + + const trialSessionRows = formatTrialSessions({ + trialSessions: paginatedTrialSessions, + }); + + const groupedTrialsSessions = groupTrialSessions(trialSessionRows); + + return { + fetchedDateString, + filtersHaveBeenModified, + groupedTrialsSessions, + sessionTypeOptions, + totalPages: Math.ceil(filteredTrialSessions.length / PAGE_SIZE), + trialCitiesByState, + trialSessionJudgeOptions, + trialSessionRows, + trialSessionsCount: filteredTrialSessions.length, + }; +}; diff --git a/web-client/src/presenter/computeds/trialSessionsHelper.ts b/web-client/src/presenter/computeds/trialSessionsHelper.ts index 07a74df56e9..f4d0a67c675 100644 --- a/web-client/src/presenter/computeds/trialSessionsHelper.ts +++ b/web-client/src/presenter/computeds/trialSessionsHelper.ts @@ -225,7 +225,7 @@ const filterAndSortTrialSessions = ({ }); }; -const formatTrialSessions = ({ +export const formatTrialSessions = ({ judgeAssociatedToUser, trialSessions, }: { @@ -322,7 +322,7 @@ export const thirtyDaysBeforeTrial = (startDate?: string): string => { return formatDateString(thirtyDaysBeforeTrialIso, FORMATS.MMDDYY); }; -type TrialSessionRow = { +export type TrialSessionRow = { trialSessionId: string; showAlertForNOTTReminder: boolean; alertMessageForNOTT: string; @@ -342,7 +342,7 @@ export function isTrialSessionRow(item: any): item is TrialSessionRow { return !!item?.trialSessionId; } -type TrialSessionWeek = { +export type TrialSessionWeek = { sessionWeekStartDate: string; formattedSessionWeekStartDate: string; }; diff --git a/web-client/src/presenter/presenter-public.ts b/web-client/src/presenter/presenter-public.ts index 4f6527448ba..beba89178d1 100644 --- a/web-client/src/presenter/presenter-public.ts +++ b/web-client/src/presenter/presenter-public.ts @@ -9,6 +9,7 @@ import { clearPdfPreviewUrlSequence } from './sequences/clearPdfPreviewUrlSequen import { closeModalAndNavigateToMaintenanceSequence } from './sequences/closeModalAndNavigateToMaintenanceSequence'; import { confirmSignUpSequence } from '@web-client/presenter/sequences/Login/confirmSignUpSequence'; import { dismissModalSequence } from './sequences/dismissModalSequence'; +import { displayProgressSpinnerSequence } from '@web-client/presenter/sequences/displayProgressSpinnerSequence'; import { goToCreatePetitionerAccountSequence } from '@web-client/presenter/sequences/Public/goToCreatePetitionerAccountSequence'; import { gotoContactSequence } from './sequences/gotoContactSequence'; import { gotoHealthCheckSequence } from './sequences/gotoHealthCheckSequence'; @@ -18,6 +19,7 @@ import { gotoPublicCaseDetailSequence } from './sequences/Public/gotoPublicCaseD import { gotoPublicEmailVerificationInstructionsSequence } from './sequences/gotoPublicEmailVerificationInstructionsSequence'; import { gotoPublicPrintableDocketRecordSequence } from './sequences/Public/gotoPublicPrintableDocketRecordSequence'; import { gotoPublicSearchSequence } from './sequences/Public/gotoPublicSearchSequence'; +import { gotoPublicTrialSessionsSequence } from '@web-client/presenter/sequences/Public/gotoPublicTrialSessionsSequence'; import { gotoTodaysOpinionsSequence } from './sequences/Public/gotoTodaysOpinionsSequence'; import { gotoTodaysOrdersSequence } from './sequences/Public/gotoTodaysOrdersSequence'; import { initialPublicState } from './state-public'; @@ -33,6 +35,7 @@ import { persistFormsOnReloadSequence } from './sequences/persistFormsOnReloadSe import { redirectToCreatePetitionerAccountSequence } from '@web-client/presenter/sequences/redirectToCreatePetitionerAccountSequence'; import { redirectToDashboardSequence } from '@web-client/presenter/sequences/redirectToDashboardSequence'; import { redirectToLoginSequence } from '@web-client/presenter/sequences/Public/redirectToLoginSequence'; +import { resetPublicTrialSessionDataSequence } from '@web-client/presenter/sequences/resetPublicTrialSessionDataSequence'; import { setCurrentPageErrorSequence } from './sequences/setCurrentPageErrorSequence'; import { showMaintenancePageDecorator } from './utilities/showMaintenancePageDecorator'; import { showMoreResultsSequence } from './sequences/showMoreResultsSequence'; @@ -70,6 +73,7 @@ export const presenterSequences = { closeModalAndNavigateToMaintenanceSequence, confirmSignUpSequence, dismissModalSequence, + displayProgressSpinnerSequence, goToCreatePetitionerAccountSequence, gotoContactSequence: showMaintenancePageDecorator(gotoContactSequence), gotoHealthCheckSequence: showMaintenancePageDecorator( @@ -87,6 +91,7 @@ export const presenterSequences = { gotoPublicSearchSequence: showMaintenancePageDecorator( gotoPublicSearchSequence, ), + gotoPublicTrialSessionsSequence, gotoTodaysOpinionsSequence: showMaintenancePageDecorator( gotoTodaysOpinionsSequence, ), @@ -105,6 +110,7 @@ export const presenterSequences = { redirectToCreatePetitionerAccountSequence, redirectToDashboardSequence, redirectToLoginSequence, + resetPublicTrialSessionDataSequence, showMoreResultsSequence, sortTodaysOrdersSequence, submitLoginSequence, diff --git a/web-client/src/presenter/presenter.ts b/web-client/src/presenter/presenter.ts index 10afd5a766e..920a3060c86 100644 --- a/web-client/src/presenter/presenter.ts +++ b/web-client/src/presenter/presenter.ts @@ -113,6 +113,7 @@ import { dismissAddEditCaseWorksheetModalSequence } from '@web-client/presenter/ import { dismissAlertSequence } from './sequences/dismissAlertSequence'; import { dismissModalSequence } from './sequences/dismissModalSequence'; import { dismissThirtyDayTrialAlertSequence } from './sequences/dismissThirtyDayTrialAlertSequence'; +import { displayProgressSpinnerSequence } from '@web-client/presenter/sequences/displayProgressSpinnerSequence'; import { downloadCsvFileSequence } from '@web-client/presenter/sequences/downloadCsvFileSequence'; import { editCorrespondenceDocumentSequence } from './sequences/editCorrespondenceDocumentSequence'; import { editUnsignedDraftDocumentSequence } from '@web-client/presenter/sequences/editUnsignedDraftDocumentSequence'; @@ -786,6 +787,7 @@ export const presenterSequences = { dismissModalSequence: dismissModalSequence as unknown as Function, dismissThirtyDayTrialAlertSequence: dismissThirtyDayTrialAlertSequence as unknown as Function, + displayProgressSpinnerSequence, downloadCsvFileSequence: downloadCsvFileSequence as unknown as Function, editCorrespondenceDocumentSequence: editCorrespondenceDocumentSequence as unknown as Function, diff --git a/web-client/src/presenter/sequences/Public/gotoPublicTrialSessionsSequence.ts b/web-client/src/presenter/sequences/Public/gotoPublicTrialSessionsSequence.ts new file mode 100644 index 00000000000..72d9ed65ca0 --- /dev/null +++ b/web-client/src/presenter/sequences/Public/gotoPublicTrialSessionsSequence.ts @@ -0,0 +1,28 @@ +import { clearErrorAlertsAction } from '@web-client/presenter/actions/clearErrorAlertsAction'; +import { closeMobileMenuAction } from '@web-client/presenter/actions/closeMobileMenuAction'; +import { getTrialSessionsAction } from '@web-client/presenter/actions/TrialSession/getTrialSessionsAction'; +import { getUsersInSectionAction } from '@web-client/presenter/actions/getUsersInSectionAction'; +import { parallel } from 'cerebral'; +import { resetTrialSessionsFiltersAction } from '@web-client/presenter/actions/TrialSession/resetTrialSessionsFiltersAction'; +import { setAllAndCurrentJudgesAction } from '@web-client/presenter/actions/setAllAndCurrentJudgesAction'; +import { setTimeStampAction } from '@web-client/presenter/actions/TrialSession/setTimeStampAction'; +import { setTrialSessionsFiltersAction } from '@web-client/presenter/actions/TrialSession/setTrialSessionsFiltersAction'; +import { setTrialSessionsPageAction } from '@web-client/presenter/actions/TrialSession/setTrialSessionsPageAction'; +import { setupCurrentPageAction } from '@web-client/presenter/actions/setupCurrentPageAction'; + +export const gotoPublicTrialSessionsSequence = [ + setupCurrentPageAction('Interstitial'), + resetTrialSessionsFiltersAction, + closeMobileMenuAction, + clearErrorAlertsAction, + setTrialSessionsFiltersAction, + parallel([ + [getTrialSessionsAction, setTrialSessionsPageAction], + [ + getUsersInSectionAction({ section: 'judge' }), + setAllAndCurrentJudgesAction, + ], + ]), + setTimeStampAction({ propertyName: 'FetchedTrialSessions' }), + setupCurrentPageAction('PublicTrialSessions'), +] as unknown as () => void; diff --git a/web-client/src/presenter/sequences/displayProgressSpinnerSequence.ts b/web-client/src/presenter/sequences/displayProgressSpinnerSequence.ts new file mode 100644 index 00000000000..48a1f0fe4af --- /dev/null +++ b/web-client/src/presenter/sequences/displayProgressSpinnerSequence.ts @@ -0,0 +1,8 @@ +import { showProgressSequenceDecorator } from '@web-client/presenter/utilities/showProgressSequenceDecorator'; + +export const displayProgressSpinnerSequence = showProgressSequenceDecorator([ + async ({ props }: ActionProps<{ timeInSeconds: number }>) => + await new Promise(resolve => + setTimeout(() => resolve(null), props.timeInSeconds * 1000), + ), +]) as unknown as (props: { timeInSeconds: number }) => void; diff --git a/web-client/src/presenter/sequences/resetPublicTrialSessionDataSequence.ts b/web-client/src/presenter/sequences/resetPublicTrialSessionDataSequence.ts new file mode 100644 index 00000000000..d0dd4f666ca --- /dev/null +++ b/web-client/src/presenter/sequences/resetPublicTrialSessionDataSequence.ts @@ -0,0 +1,5 @@ +import { resetPublicTrialSessionDataAction } from '@web-client/presenter/actions/resetPublicTrialSessionDataAction'; + +export const resetPublicTrialSessionDataSequence = [ + resetPublicTrialSessionDataAction, +] as unknown as () => void; diff --git a/web-client/src/presenter/sequences/updateFormValueSequence.ts b/web-client/src/presenter/sequences/updateFormValueSequence.ts index 64c5aff587f..1a932854a4b 100644 --- a/web-client/src/presenter/sequences/updateFormValueSequence.ts +++ b/web-client/src/presenter/sequences/updateFormValueSequence.ts @@ -3,7 +3,7 @@ import { setFormValueAction } from '../actions/setFormValueAction'; export const updateFormValueSequence = [ setFormValueAction, ] as unknown as (props: { - index: number; + index?: number; root?: string; key: string; value: any; diff --git a/web-client/src/presenter/state-public.ts b/web-client/src/presenter/state-public.ts index ea97c476498..8f3dfe207a9 100644 --- a/web-client/src/presenter/state-public.ts +++ b/web-client/src/presenter/state-public.ts @@ -1,4 +1,6 @@ import { PUBLIC_DOCKET_RECORD_FILTER_OPTIONS } from '../../../shared/src/business/entities/EntityConstants'; +import { RawUser } from '@shared/business/entities/User'; +import { TrialSessionInfoDTO } from '@shared/business/dto/trialSessions/TrialSessionInfoDTO'; import { advancedDocumentSearchHelper } from './computeds/AdvancedSearch/advancedDocumentSearchHelper'; import { advancedSearchHelper } from './computeds/AdvancedSearch/advancedSearchHelper'; import { caseSearchByNameHelper } from './computeds/AdvancedSearch/CaseSearchByNameHelper'; @@ -10,6 +12,7 @@ import { practitionerSearchHelper } from '@web-client/presenter/computeds/Advanc import { publicAlertHelper } from './computeds/Public/publicAlertHelper'; import { publicCaseDetailHeaderHelper } from './computeds/Public/publicCaseDetailHeaderHelper'; import { publicCaseDetailHelper } from './computeds/Public/publicCaseDetailHelper'; +import { publicTrialSessionsHelper } from '@web-client/presenter/computeds/Public/publicTrialSessionsHelper'; import { templateHelper } from './computeds/templateHelper'; import { todaysOpinionsHelper } from './computeds/Public/todaysOpinionsHelper'; import { todaysOrdersHelper } from './computeds/Public/todaysOrdersHelper'; @@ -30,6 +33,9 @@ const computeds = { publicCaseDetailHelper: publicCaseDetailHelper as unknown as ReturnType< typeof publicCaseDetailHelper >, + publicTrialSessionsHelper: publicTrialSessionsHelper as unknown as ReturnType< + typeof publicTrialSessionsHelper + >, templateHelper, todaysOpinionsHelper, todaysOrdersHelper, @@ -58,12 +64,20 @@ export const baseState = { }, isPublic: true, isTerminalUser: false, + judges: [] as RawUser[], modal: {}, progressIndicator: { // used for the spinner that shows when waiting for network responses waitingForResponse: false, waitingForResponseRequests: 0, }, + publicTrialSessionData: {} as { + judges?: { [key: string]: string }; + locations?: { [key: string]: string }; + sessionTypes?: { [key: string]: string }; + pageNumber?: number; + proceedingType?: string; + }, sessionMetadata: { docketRecordFilter: PUBLIC_DOCKET_RECORD_FILTER_OPTIONS.allDocuments, docketRecordSort: {}, @@ -75,6 +89,9 @@ export const baseState = { results: [], totalCount: 0, }, + trialSessionsPage: { trialSessions: [] } as { + trialSessions: TrialSessionInfoDTO[]; + }, user: {}, validationErrors: {}, }; diff --git a/web-client/src/presenter/test.cerebral.ts b/web-client/src/presenter/test.cerebral.ts index 467360e2828..77f54f79436 100644 --- a/web-client/src/presenter/test.cerebral.ts +++ b/web-client/src/presenter/test.cerebral.ts @@ -10,8 +10,9 @@ type FakeRunComputeType = ( ) => T; export const runCompute = cerebralRunCompute as unknown as FakeRunComputeType; -type FakeRunActionType = ( +type FakeRunActionType = ( action: (actionProps: any) => Promise | T, fixtures: { modules?: { presenter: any }; props?: any; state?: any }, -) => { state: ClientState; props: any; output: T }; +) => { state: U; props: any; output: T }; + export const runAction = cerebralRunAction as unknown as FakeRunActionType; diff --git a/web-client/src/routerPublic.ts b/web-client/src/routerPublic.ts index fc905dae6fc..aa33003be81 100644 --- a/web-client/src/routerPublic.ts +++ b/web-client/src/routerPublic.ts @@ -84,6 +84,11 @@ const router = { return app.getSequence('redirectToLoginSequence')(); }); + route('/trial-sessions', () => { + setPageTitle('Trial sessions'); + return app.getSequence('gotoPublicTrialSessionsSequence')(); + }); + route('..', () => { setPageTitle('Error'); return app.getSequence('notFoundErrorSequence')({ diff --git a/web-client/src/styles/custom.scss b/web-client/src/styles/custom.scss index affdc483334..03b6dad56c4 100644 --- a/web-client/src/styles/custom.scss +++ b/web-client/src/styles/custom.scss @@ -2358,3 +2358,8 @@ button.change-scanner-button { // This has higher importance than uwds padding-0 padding: 0; } + +.double-border { + border-bottom: 1px solid !important; + margin-bottom: 5px !important; +} \ No newline at end of file diff --git a/web-client/src/styles/public.scss b/web-client/src/styles/public.scss index ae19d70b0d9..2939e3e9a27 100644 --- a/web-client/src/styles/public.scss +++ b/web-client/src/styles/public.scss @@ -22,7 +22,13 @@ } .header-welcome-public { + padding-bottom: .5rem; + margin-top: auto; + margin-bottom: auto; margin-left: 10px; + font-family: "Noto Serif", serif; + font-size: 26px; + font-weight: 700; line-height: 2em; @media only screen and (max-width: $medium-screen) { diff --git a/web-client/src/ustc-ui/Button/PillButton.tsx b/web-client/src/ustc-ui/Button/PillButton.tsx index adeb64671ec..a4fcbd2d7f0 100644 --- a/web-client/src/ustc-ui/Button/PillButton.tsx +++ b/web-client/src/ustc-ui/Button/PillButton.tsx @@ -3,12 +3,17 @@ import React from 'react'; interface PillButtonProps { text: string; + 'data-testid'?: string; onRemove: () => void; } -export const PillButton = ({ onRemove, text }: PillButtonProps) => { +export const PillButton = ({ + 'data-testid': dataTestId, + onRemove, + text, +}: PillButtonProps) => { return ( - + {text} + +
+ + + +
+ + + ); +} + +function MobilePublicTrialsSessions({ + displayProgressSpinnerSequence, + publicTrialSessionData, + publicTrialSessionsHelper, + resetPublicTrialSessionDataSequence, + updateFormValueSequence, +}: TrialsSessionsUiParams) { + const { + judges = {}, + locations = {}, + sessionTypes = {}, + } = publicTrialSessionData; + + const [isOpen, setIsOpen] = useState(false); + + const publicTrialsSessionUpdateFormValueSequence = ( + ...args: Parameters + ) => { + if (displayProgressSpinnerSequence) + displayProgressSpinnerSequence({ timeInSeconds: 0.25 }); + updateFormValueSequence(...args); + updateFormValueSequence({ + key: 'pageNumber', + root: ROOT, + value: 0, + }); + }; + + return ( + +
+ +
+ +
+ + setIsOpen(!isOpen)}> + + {''} + + + + {isOpen && ( + <> + + + + )} + +
+ {Object.entries( + sessionTypes as { + [key: string]: string; + }, + ).map(([sessionTypeKey, sessionTypeLabel]) => ( + { + publicTrialsSessionUpdateFormValueSequence({ + key: `sessionTypes.${sessionTypeKey}`, + root: ROOT, + value: undefined, + }); + }} + /> + ))} + + {Object.entries( + locations as { + [key: string]: string; + }, + ).map(([sessionTypeKey, sessionTypeLabel]) => ( + { + publicTrialsSessionUpdateFormValueSequence({ + key: `locations.${sessionTypeKey}`, + root: ROOT, + value: undefined, + }); + }} + /> + ))} + + {Object.entries( + judges as { + [key: string]: string; + }, + ).map(([sessionTypeKey, sessionTypeLabel]) => ( + { + publicTrialsSessionUpdateFormValueSequence({ + key: `judges.${sessionTypeKey}`, + root: ROOT, + value: undefined, + }); + }} + /> + ))} +
+ + + +
+
+ ); +} + +function FetchedTimeMessage({ fetchedDateString }) { + return ( +
Information on this page is current as of {fetchedDateString}.
+ ); +} + +function TablePagination({ + children, + pageNumber, + totalPages, + updateFormValueSequence, +}) { + const paginatorTop = useRef(null); + if (totalPages <= 1) return children; + return ( + <> +
+
+ { + updateFormValueSequence({ + key: 'pageNumber', + root: ROOT, + value: selectedPage, + }); + focusPaginatorTop(paginatorTop); + }} + /> +
+
+
+ + {children} +
+
+ { + updateFormValueSequence({ + key: 'pageNumber', + root: ROOT, + value: selectedPage, + }); + focusPaginatorTop(paginatorTop); + }} + /> +
+
+
+ + ); +} diff --git a/web-client/src/views/Public/TrialsSessions/PublicTrialSessionsFilters.tsx b/web-client/src/views/Public/TrialsSessions/PublicTrialSessionsFilters.tsx new file mode 100644 index 00000000000..7d49209ae0c --- /dev/null +++ b/web-client/src/views/Public/TrialsSessions/PublicTrialSessionsFilters.tsx @@ -0,0 +1,207 @@ +import { FormGroup } from '@web-client/ustc-ui/FormGroup/FormGroup'; +import { NonPhone, Phone } from '@web-client/ustc-ui/Responsive/Responsive'; +import { PillButton } from '@web-client/ustc-ui/Button/PillButton'; +import { Select } from '@web-client/ustc-ui/Select/Selects'; +import { SelectSearch } from '@web-client/ustc-ui/Select/SelectSearch'; +import { TRIAL_SESSION_PROCEEDING_TYPES } from '@shared/business/entities/EntityConstants'; +import { props as cerebralProps } from 'cerebral'; +import { connect } from '@web-client/presenter/shared.cerebral'; +import { sequences, state } from '@web-client/presenter/app-public.cerebral'; +import React from 'react'; + +type PublicTrialSessionsFiltersProps = { + ROOT: string; +}; + +const props = cerebralProps as unknown as PublicTrialSessionsFiltersProps; + +const PublicTrialSessionsFiltersDeps = { + displayProgressSpinnerSequence: sequences.displayProgressSpinnerSequence, + publicTrialSessionData: state[props.ROOT], + publicTrialSessionsHelper: state.publicTrialSessionsHelper, + updateFormValueSequence: sequences.updateFormValueSequence, +}; + +export const PublicTrialSessionsFilters = connect< + PublicTrialSessionsFiltersProps, + typeof PublicTrialSessionsFiltersDeps +>( + PublicTrialSessionsFiltersDeps, + function ({ + displayProgressSpinnerSequence, + publicTrialSessionData, + publicTrialSessionsHelper, + ROOT, + updateFormValueSequence, + }) { + const PROCEEDING_TYPES = Object.entries({ + all: 'All', + ...TRIAL_SESSION_PROCEEDING_TYPES, + }); + + const { + judges = {}, + locations = {}, + proceedingType = 'All', + sessionTypes = {}, + } = publicTrialSessionData; + + const handleUpdateFormValue = (key: string, value: string | undefined) => { + displayProgressSpinnerSequence({ timeInSeconds: 0.25 }); + updateFormValueSequence({ key, root: ROOT, value }); + updateFormValueSequence({ + key: 'pageNumber', + root: ROOT, + value: 0, + }); + }; + + function proceedingTypeRadioOption(key: string, value: string) { + return ( +
+ { + handleUpdateFormValue(e.target.name, e.target.value); + }} + /> + +
+ ); + } + + return ( + <> + +
+ + Proceeding type + + {PROCEEDING_TYPES.map(([key, value]) => + proceedingTypeRadioOption(key, value), + )} +
+
+ +
+
+
+ +
+
+ +
+
+ +
+
+
+ + ); + }, +); + +function FilterSelect({ label, name, onChange, options, selectedValues }) { + return ( + <> +
+ + + + option && onChange(`${name}.${option.value}`, option.label) + } + />{' '} + + +