diff --git a/backend/src/app/dto/parcelDescription.dto.ts b/backend/src/app/dto/parcelDescription.dto.ts index 5ee021c9..b2a49902 100644 --- a/backend/src/app/dto/parcelDescription.dto.ts +++ b/backend/src/app/dto/parcelDescription.dto.ts @@ -24,7 +24,7 @@ export type ParcelDescriptionTypeValue = @ObjectType() export class ParcelDescriptionDto { constructor( - id: number | null, + id: string | null, descriptionType: ParcelDescriptionTypeValue | null, idPinNumber: string | null, dateNoted: Date | null, @@ -32,7 +32,7 @@ export class ParcelDescriptionDto { userAction: string | null, srAction: string | null, ) { - this.id = id ? id : 0; + this.id = id ? id : ''; this.descriptionType = descriptionType ? descriptionType : ParcelDescriptionType.Unknown; @@ -44,7 +44,7 @@ export class ParcelDescriptionDto { } @Field() @IsInt() - id: number; + id: string; @Field() @IsString() diff --git a/frontend/src/app/features/details/SaveSiteDetailsSlice.ts b/frontend/src/app/features/details/SaveSiteDetailsSlice.ts index edf571d3..333798df 100644 --- a/frontend/src/app/features/details/SaveSiteDetailsSlice.ts +++ b/frontend/src/app/features/details/SaveSiteDetailsSlice.ts @@ -18,7 +18,7 @@ const initialState: SaveSiteDetails = { siteParticipantData: null, documentsData: null, landHistoriesData: null, - subDivisionsData: null, + parcelDescriptionsData: null, profilesData: null, siteAssociationsData: null, siteId: '', @@ -52,7 +52,7 @@ const siteDetailsSlice = createSlice({ newState.siteParticipantData = null; newState.documentsData = null; newState.landHistoriesData = null; - newState.subDivisionsData = null; + newState.parcelDescriptionsData = null; newState.profilesData = null; newState.siteAssociationsData = null; newState.siteId = ''; @@ -109,11 +109,11 @@ const siteDetailsSlice = createSlice({ newState.landHistoriesData = action.payload; return newState; }, - setupSubDivisionsDataForSaving: (state, action) => { + setupParcelDescriptionsDataForSaving: (state, action) => { const newState = { ...state, }; - newState.subDivisionsData = action.payload; + newState.parcelDescriptionsData = action.payload; return newState; }, setupSiteAssociationDataForSaving: (state, action) => { @@ -178,7 +178,7 @@ export const getSiteDetailsToBeSaved = (state: any) => { UserActionEnum.updated, UserActionEnum.deleted, ]), - subDivisions: state.siteDetails.subDivisions, + parcelDescriptions: state.siteDetails.parcelDescriptionsData, landHistories: state.siteDetails.landHistoriesData, profiles: state.siteDetails.profilesData && @@ -214,7 +214,7 @@ export const { setupLandHistoriesDataForSaving, setupSiteAssociationDataForSaving, setupSiteParticipantDataForSaving, - setupSubDivisionsDataForSaving, + setupParcelDescriptionsDataForSaving, setupSiteSummaryForSaving, setupSiteDisclosureDataForSaving, } = siteDetailsSlice.actions; diff --git a/frontend/src/app/features/details/dto/SiteDetailsMode.ts b/frontend/src/app/features/details/dto/SiteDetailsMode.ts index a3bacb9a..016f405a 100644 --- a/frontend/src/app/features/details/dto/SiteDetailsMode.ts +++ b/frontend/src/app/features/details/dto/SiteDetailsMode.ts @@ -18,7 +18,7 @@ export interface SaveSiteDetails { notationData: any; siteParticipantData: any; siteAssociationsData: any; - subDivisionsData: any; + parcelDescriptionsData: any; landHistoriesData: any; documentsData: any; profilesData: any; diff --git a/frontend/src/app/features/details/parcelDescriptions/ParcelDescriptionTable.tsx b/frontend/src/app/features/details/parcelDescriptions/ParcelDescriptionTable.tsx index 7841e58e..b387b38c 100644 --- a/frontend/src/app/features/details/parcelDescriptions/ParcelDescriptionTable.tsx +++ b/frontend/src/app/features/details/parcelDescriptions/ParcelDescriptionTable.tsx @@ -2,6 +2,7 @@ import React from 'react'; import Table from '../../../components/table/Table'; import { RequestStatus } from '../../../helpers/requests/status'; import { TableColumn } from '../../../components/table/TableColumn'; +import { SiteDetailsMode } from '../dto/SiteDetailsMode'; interface IParcelDescriptionTable { requestStatus: RequestStatus; @@ -14,6 +15,7 @@ interface IParcelDescriptionTable { resultsPerPage: number | undefined; handleTableSortChange: (column: TableColumn, descending: boolean) => void; showPageOptions: boolean; + viewMode: SiteDetailsMode; tableChangeHandler: (event: any) => void; } @@ -28,6 +30,7 @@ const ParcelDescriptionTable: React.FC = ({ resultsPerPage, handleTableSortChange, showPageOptions, + viewMode, tableChangeHandler, }) => { return ( @@ -42,9 +45,9 @@ const ParcelDescriptionTable: React.FC = ({ changeResultsPerPage={handleChangeResultsPerPage} currentPage={currentPage} resultsPerPage={resultsPerPage} - allowRowsSelect={false} + allowRowsSelect={viewMode == SiteDetailsMode.EditMode} changeHandler={tableChangeHandler} - editMode={false} + editMode={viewMode === SiteDetailsMode.EditMode} idColumnName="id" sortHandler={handleTableSortChange} > diff --git a/frontend/src/app/features/details/parcelDescriptions/parcelDescriptions.test.tsx b/frontend/src/app/features/details/parcelDescriptions/parcelDescriptions.test.tsx index 64b883a0..f06d03c8 100644 --- a/frontend/src/app/features/details/parcelDescriptions/parcelDescriptions.test.tsx +++ b/frontend/src/app/features/details/parcelDescriptions/parcelDescriptions.test.tsx @@ -9,13 +9,21 @@ import { RequestStatus } from '../../../helpers/requests/status'; import { IParcelDescriptionsState } from './parcelDescriptionsInterfaces'; import thunk from 'redux-thunk'; import { initialParcelDescriptionsState } from './parcelDescriptionsSlice'; +import { SiteDetailsMode } from '../dto/SiteDetailsMode'; +import { ParcelDescriptionType } from './parcelDescriptionsConfig'; +import { IChangeType } from '../../../components/common/IChangeType'; +import { act } from '@testing-library/react'; jest.mock('react-router-dom', () => ({ ...jest.requireActual('react-router-dom'), useParams: () => ({ id: '1' }), })); -type TestState = { parcelDescriptions: IParcelDescriptionsState }; +type TestState = { + sites: { siteDetailsMode: string }; + siteDetails: { saveRequestStatus: RequestStatus }; + parcelDescriptions: IParcelDescriptionsState; +}; describe('Parcel Descriptions Component', () => { let mockStore: MockStoreCreator; @@ -47,43 +55,59 @@ describe('Parcel Descriptions Component', () => { resultsPerPage = 5; totalResults = 10; testState = { + sites: { + siteDetailsMode: SiteDetailsMode.ViewOnlyMode, + }, + siteDetails: { + saveRequestStatus: RequestStatus.idle, + }, parcelDescriptions: { siteId: siteId, data: [ { - id: 11, - descriptionType: 'Parcel ID', - idPinNumber: '123456', - dateNoted: '2023-06-15T00:00:00Z', + id: '11', + descriptionType: ParcelDescriptionType.CrownLandPIN, + idPinNumber: '123456789', + dateNoted: '2023-06-15T00:00:00', landDescription: 'first land description', + srAction: '', + userAction: '', }, { - id: 12, - descriptionType: 'Crown Land PIN', - idPinNumber: '654321', - dateNoted: '2023-06-16T00:00:00Z', + id: '12', + descriptionType: ParcelDescriptionType.CrownLandPIN, + idPinNumber: '987654321', + dateNoted: '2023-06-16T00:00:00', landDescription: 'second land description', + srAction: '', + userAction: '', }, { - id: 13, - descriptionType: 'Crown Land File Number', - idPinNumber: 'ax213456', - dateNoted: '2023-06-17T00:00:00Z', + id: '13', + descriptionType: ParcelDescriptionType.CrownLandFileNumber, + idPinNumber: 'ax12345', + dateNoted: '2023-06-17T00:00:00', landDescription: 'third land description', + srAction: '', + userAction: '', }, { - id: 14, - descriptionType: 'Parcel ID', - idPinNumber: '789012', - dateNoted: '2023-06-18T00:00:00Z', + id: '14', + descriptionType: ParcelDescriptionType.ParcelID, + idPinNumber: '789012345', + dateNoted: '2023-06-18T00:00:00', landDescription: 'fourth land description', + srAction: '', + userAction: '', }, { - id: 15, - descriptionType: 'Crown Land PIN', - idPinNumber: '210987', - dateNoted: '2023-06-19T00:00:00Z', + id: '15', + descriptionType: ParcelDescriptionType.CrownLandPIN, + idPinNumber: '4321098765', + dateNoted: '2023-06-19T00:00:00', landDescription: 'fifth land description', + srAction: '', + userAction: '', }, ], requestStatus: RequestStatus.idle, @@ -582,4 +606,287 @@ describe('Parcel Descriptions Component', () => { ); }); }); + + describe('when editing', () => { + beforeEach(() => { + jest.useFakeTimers(); + testState.sites.siteDetailsMode = SiteDetailsMode.EditMode; + store = mockStore(testState); + }); + + afterEach(() => { + jest.runOnlyPendingTimers(); + jest.useRealTimers(); + }); + + describe('when editing the description type', () => { + it('sets up the parcel description for saving, and tracks the action', async () => { + render( + + + , + ); + + // The first row on the table. + const descriptionTypeInputs = + await screen.findAllByDisplayValue('Crown Land PIN'); + + fireEvent.change(descriptionTypeInputs[0], { + target: { value: 'Crown Land File Number' }, + }); + + jest.runAllTimers(); + + let actions = store.getActions(); + expect(actions).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + type: 'siteDetails/setupParcelDescriptionsDataForSaving', + payload: expect.arrayContaining([ + expect.objectContaining({ + descriptionType: 'Crown Land File Number', + apiAction: 'updated', + }), + ]), + }), + expect.objectContaining({ + type: 'sites/trackChanges', + payload: expect.objectContaining({ + changeType: IChangeType.Modified, + label: 'Parcel Descriptions: 11', + }), + }), + ]), + ); + }); + + describe('when selecting Crown Land File Number', () => { + it('truncates the value in id/pin/number', async () => { + render( + + + , + ); + + const descriptionTypeInputs = + await screen.findAllByDisplayValue('Crown Land PIN'); + + fireEvent.change(descriptionTypeInputs[0], { + target: { value: 'Crown Land File Number' }, + }); + + // the first id/pin/number should have been truncated from 123456789 to 1234567. + expect( + await screen.findByDisplayValue('1234567'), + ).toBeInTheDocument(); + expect( + screen.queryByDisplayValue('123456789'), + ).not.toBeInTheDocument(); + }); + }); + }); + + describe('when editing the id/pin/number', () => { + it('sets up the parcel description for saving, and tracks the action', () => { + render( + + + , + ); + + const descriptionTypeInput = screen.getByDisplayValue('123456789'); + + fireEvent.change(descriptionTypeInput, { + target: { value: '192837465' }, + }); + + jest.runAllTimers(); + + let actions = store.getActions(); + expect(actions).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + type: 'siteDetails/setupParcelDescriptionsDataForSaving', + payload: expect.arrayContaining([ + expect.objectContaining({ + idPinNumber: '192837465', + apiAction: 'updated', + }), + ]), + }), + expect.objectContaining({ + type: 'sites/trackChanges', + payload: expect.objectContaining({ + changeType: IChangeType.Modified, + label: 'Parcel Descriptions: 11', + }), + }), + ]), + ); + }); + + describe('when entering an id/pin/number that is too long', () => { + describe('and the row is a crown land PIN or parcel ID', () => { + it('does not update the input value', async () => { + render( + + + , + ); + + // The first row is a crown land PIN + const idPinNumberInput = screen.getByDisplayValue('123456789'); + + fireEvent.change(idPinNumberInput, { + target: { value: '1234567890' }, + }); + + // This is a crown land PIN, so the tenth digit should be disregarded. + expect( + await screen.findByDisplayValue('123456789'), + ).toBeInTheDocument(); + expect( + screen.queryByDisplayValue('1234567890'), + ).not.toBeInTheDocument(); + }); + }); + + describe('and the row is a crown land File Number', () => { + it('does not update the input value', async () => { + render( + + + , + ); + + // The third row is a crown land file number. + const idPinNumberInput = screen.getByDisplayValue('ax12345'); + + fireEvent.change(idPinNumberInput, { + target: { value: 'ax123456' }, + }); + + // This is a crown land PIN, so the tenth digit should be disregarded. + expect( + await screen.findByDisplayValue('ax12345'), + ).toBeInTheDocument(); + expect( + screen.queryByDisplayValue('ax123456'), + ).not.toBeInTheDocument(); + }); + }); + }); + }); + + describe('when editing the date noted', () => { + const originalConsoleError = console.error; + + beforeAll(() => { + console.error = (msg: any) => { + // Suppress react printing an error about wrapping updates in act(...) + // this is an issue caused by rsuite's date picker component + // performing updates asynchronously. I have thoroughly tested that + // everything works as expected during these tests. + if ( + !msg.toString().includes('inside a test was not wrapped in act') + ) { + originalConsoleError(msg); + } + }; + }); + + afterAll(() => { + console.error = originalConsoleError; + }); + + describe('when inputting a new date', () => { + it('sets up the parcel description for saving, and tracks the action', async () => { + await act(async () => { + render( + + + , + ); + }); + + // Get the first date picker input (Jun 15, 2023). + let dateNotedInput = screen.getByDisplayValue('Jun 15, 2023'); + await act(async () => { + fireEvent.click(dateNotedInput); + }); + let targetDate = screen.getByText('11'); + await act(async () => { + // Click on Jun 11, 2023 + fireEvent.click(targetDate); + }); + + let actions = store.getActions(); + expect(actions).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + type: 'siteDetails/setupParcelDescriptionsDataForSaving', + payload: expect.arrayContaining([ + expect.objectContaining({ + // Match only the date without the timezone. This is necessary + // because the timezone will necessarily be in the test + // environment's timezone which will be different in our + // development and CI environments. + dateNoted: expect.stringMatching(/^2023-06-11.*$/), + apiAction: 'updated', + }), + ]), + }), + expect.objectContaining({ + type: 'sites/trackChanges', + payload: expect.objectContaining({ + changeType: IChangeType.Modified, + label: expect.stringMatching(/^Parcel Descriptions: 11$/), + }), + }), + ]), + ); + }); + }); + + describe('when clearing the date', () => { + it('sets up the parcel description for saving, and tracks the action', async () => { + await act(async () => { + render( + + + , + ); + }); + + // Get the first (Parcel Description 11) date clear button. + let clearButton = screen.getAllByLabelText('Clear')[0]; + await act(async () => { + fireEvent.click(clearButton); + }); + + let actions = store.getActions(); + expect(actions).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + type: 'siteDetails/setupParcelDescriptionsDataForSaving', + payload: expect.arrayContaining([ + expect.objectContaining({ + dateNoted: expect.stringMatching(/^$/), + apiAction: 'updated', + }), + ]), + }), + expect.objectContaining({ + type: 'sites/trackChanges', + payload: expect.objectContaining({ + changeType: IChangeType.Modified, + label: expect.stringMatching(/^Parcel Descriptions: 11$/), + }), + }), + ]), + ); + }); + }); + }); + }); }); diff --git a/frontend/src/app/features/details/parcelDescriptions/parcelDescriptions.tsx b/frontend/src/app/features/details/parcelDescriptions/parcelDescriptions.tsx index 8bd0b20c..ef9bcb4e 100644 --- a/frontend/src/app/features/details/parcelDescriptions/parcelDescriptions.tsx +++ b/frontend/src/app/features/details/parcelDescriptions/parcelDescriptions.tsx @@ -16,18 +16,55 @@ import { updateSortByDir, updateSortByInputValue, } from './parcelDescriptionsSlice'; -import { columns } from './parcelDescriptionsConfig'; +import { + getParcelDescriptionsTableColumns, + ParcelDescriptionType, +} from './parcelDescriptionsConfig'; import { useParams } from 'react-router-dom'; import ParcelDescriptionTable from './ParcelDescriptionTable'; import { IFetchParcelDescriptionsParams, IParcelDescriptionDto, + IParcelDescriptionSaveDto, } from './parcelDescriptionsInterfaces'; import { RequestStatus } from '../../../helpers/requests/status'; +import { + resetSiteDetails, + siteDetailsMode, + trackChanges, +} from '../../site/dto/SiteSlice'; +import { + ChangeTracker, + IChangeType, +} from '../../../components/common/IChangeType'; +import { + saveRequestStatus, + setupParcelDescriptionsDataForSaving, +} from '../SaveSiteDetailsSlice'; +import { UserActionEnum } from '../../../common/userActionEnum'; +import { SiteDetailsMode } from '../dto/SiteDetailsMode'; +import { SRApprovalStatusEnum } from '../../../common/srApprovalStatusEnum'; + +type ParcelDescriptionsChangeEvent = { + property: 'descriptionType' | 'idPinNumber' | 'dateNoted'; + value: + | string + | boolean + | Date + | null + | IParcelDescriptionDto[] + | ParcelDescriptionType; + row?: IParcelDescriptionDto; + selected?: boolean; +}; const ParcelDescriptions = () => { const dispatch = useDispatch(); const reduxState = useSelector(parcelDescriptions); + const viewMode: SiteDetailsMode = useSelector(siteDetailsMode); + const saveSiteDetailsRequestStatus: RequestStatus = + useSelector(saveRequestStatus); + const resetDetails = useSelector(resetSiteDetails); const { id } = useParams(); const siteId = Number(id); @@ -46,7 +83,7 @@ const ParcelDescriptions = () => { dispatch(fetchParcelDescriptions(fetchParams)); } - const [data, setData] = React.useState( + const [dbRows, setDbRows] = React.useState( reduxState.data, ); const [currentPage, setCurrentPage] = React.useState( @@ -71,6 +108,12 @@ const ParcelDescriptions = () => { const [requestStatus, setRequestStatus] = React.useState( reduxState.requestStatus, ); + const [updatedRows, setUpdatedRows] = React.useState< + IParcelDescriptionSaveDto[] + >([]); + const [mergedRows, setMergedRows] = React.useState( + [], + ); const handleSelectPage = (newPage: number) => { if (newPage !== currentPage) { @@ -103,10 +146,6 @@ const ParcelDescriptions = () => { } }; - const tableChangeHandler = () => { - // To Be Implemented As Part Of EDIT functionality - }; - const handleSortInputChange = ( graphQLPropertyName: any, newSortByInputValue: string | [Date, Date], @@ -171,6 +210,106 @@ const ParcelDescriptions = () => { } }; + const handleRowUpdate = (newRow: IParcelDescriptionDto) => { + let rowExistsInMemory = false; + const newUpdatedRows: IParcelDescriptionSaveDto[] = updatedRows.map( + (originalRow) => { + // Try to replace the existing row with the updated row. + if (newRow.id === originalRow.id) { + rowExistsInMemory = true; + return { + ...newRow, + apiAction: UserActionEnum.updated, + srAction: SRApprovalStatusEnum.Pending, + } as IParcelDescriptionSaveDto; + } else { + return originalRow; + } + }, + ); + if (!rowExistsInMemory) { + // Add the newly edited row to the array of edited rows. + newUpdatedRows.push({ + ...newRow, + apiAction: UserActionEnum.updated, + } as IParcelDescriptionSaveDto); + } + + setUpdatedRows(newUpdatedRows); + dispatch( + trackChanges( + new ChangeTracker( + IChangeType.Modified, + `Parcel Descriptions: ${newRow.id}`, + ).toPlainObject(), + ), + ); + }; + + const idPinNumberIsValid = ( + newIdPinNumber: string, + descriptionType: ParcelDescriptionType, + ): boolean => { + switch (descriptionType) { + case ParcelDescriptionType.ParcelID: + case ParcelDescriptionType.CrownLandPIN: + return newIdPinNumber.length <= 9; + case ParcelDescriptionType.CrownLandFileNumber: + return newIdPinNumber.length <= 7; + } + }; + + const handleTableChange = (event: ParcelDescriptionsChangeEvent) => { + if (event.row !== undefined) { + let newRow: IParcelDescriptionDto; + switch (event.property) { + case 'descriptionType': + // There is an edge case where the user enters a 9 digit idPinNumber + // then changes the description type to Crown Land File Number, which + // only allows an input length of 7. Truncate the value. + const value = event.value as ParcelDescriptionType; + if (value === ParcelDescriptionType.CrownLandFileNumber) { + event.row.idPinNumber = event.row.idPinNumber.slice(0, 7); + } + newRow = { + ...event.row, + descriptionType: value, + }; + handleRowUpdate(newRow); + break; + case 'idPinNumber': + if ( + idPinNumberIsValid( + event.value as string, + event.row?.descriptionType as ParcelDescriptionType, + ) + ) { + newRow = { + ...event.row, + idPinNumber: event.value as string, + }; + handleRowUpdate(newRow); + } + break; + case 'dateNoted': + let newDateString: string; + if (event.value === null) { + // This happens when clearing the date widget. + newDateString = ''; + } else { + const newDate = event.value as Date; + newDateString = newDate.toISOString(); + } + newRow = { + ...event.row, + dateNoted: newDateString, + }; + handleRowUpdate(newRow); + break; + } + } + }; + const fetchNewParcelDescriptions = ({ newPage, newPageSize, @@ -196,9 +335,39 @@ const ParcelDescriptions = () => { dispatch(fetchParcelDescriptions(fetchParams)); }; + React.useEffect(() => { + dispatch(setupParcelDescriptionsDataForSaving([...updatedRows])); + }, [updatedRows]); + + React.useEffect(() => { + if (viewMode == SiteDetailsMode.EditMode) { + // Merge rows from DB with user edited rows upon the update of either. + const newMergedRows = dbRows.map((dbRow) => { + const match = updatedRows.find((updatedRow) => { + return updatedRow.id === dbRow.id; + }); + if (match) { + return match as IParcelDescriptionDto; + } else { + return dbRow; + } + }); + setMergedRows(newMergedRows); + } else { + setMergedRows(dbRows); + } + }, [dbRows, updatedRows, viewMode]); + + React.useEffect(() => { + if (resetDetails) { + fetchNewParcelDescriptions({}); + setUpdatedRows([]); + } + }, [resetDetails, saveSiteDetailsRequestStatus]); + React.useEffect(() => { // Update local state with redux state. - setData(reduxState.data); + setDbRows(reduxState.data); setCurrentPage(reduxState.currentPage); setRequestStatus(reduxState.requestStatus); setResultsPerPage(reduxState.resultsPerPage); @@ -237,15 +406,16 @@ const ParcelDescriptions = () => { diff --git a/frontend/src/app/features/details/parcelDescriptions/parcelDescriptionsConfig.ts b/frontend/src/app/features/details/parcelDescriptions/parcelDescriptionsConfig.ts index c20acf2e..78e4bba4 100644 --- a/frontend/src/app/features/details/parcelDescriptions/parcelDescriptionsConfig.ts +++ b/frontend/src/app/features/details/parcelDescriptions/parcelDescriptionsConfig.ts @@ -1,5 +1,12 @@ import { FormFieldType } from '../../../components/input-controls/IFormField'; -import { TableColumn } from '../../../components/table/TableColumn'; +import { ColumnSize, TableColumn } from '../../../components/table/TableColumn'; +import { SiteDetailsMode } from '../dto/SiteDetailsMode'; + +export enum ParcelDescriptionType { + ParcelID = 'Parcel ID', + CrownLandPIN = 'Crown Land PIN', + CrownLandFileNumber = 'Crown Land File Number', +} export const columns: TableColumn[] = [ { @@ -7,87 +14,95 @@ export const columns: TableColumn[] = [ displayName: 'Description Type', active: true, graphQLPropertyName: 'descriptionType', - groupId: 1, - disabled: true, - isDefault: true, - sortOrder: 1, - isChecked: true, + columnSize: ColumnSize.Default, displayType: { - type: FormFieldType.Label, + type: FormFieldType.DropDown, label: 'DescriptionType', graphQLPropertyName: 'descriptionType', - value: '', - customLabelCss: 'custom-lbl-text', - customInputTextCss: 'custom-input-text', + options: [ + { + key: ParcelDescriptionType.ParcelID, + value: ParcelDescriptionType.ParcelID, + }, + { + key: ParcelDescriptionType.CrownLandPIN, + value: ParcelDescriptionType.CrownLandPIN, + }, + { + key: ParcelDescriptionType.CrownLandFileNumber, + value: ParcelDescriptionType.CrownLandFileNumber, + }, + ], tableMode: true, - stickyCol: false, }, - stickyCol: true, }, { id: 2, displayName: 'ID/PIN/Number', active: true, graphQLPropertyName: 'idPinNumber', - groupId: 1, - disabled: true, - isDefault: true, - sortOrder: 1, - isChecked: true, + columnSize: ColumnSize.Default, displayType: { - type: FormFieldType.Label, + type: FormFieldType.Text, label: 'ID/PIN/Number', graphQLPropertyName: 'idPinNumber', value: '', - customLabelCss: 'custom-lbl-text', - customInputTextCss: 'custom-input-text', tableMode: true, - stickyCol: false, }, - stickyCol: true, }, { id: 3, displayName: 'Date Noted', active: true, graphQLPropertyName: 'dateNoted', - groupId: 1, - disabled: true, - isDefault: true, - sortOrder: 1, - isChecked: true, + columnSize: ColumnSize.Default, displayType: { - type: FormFieldType.Label, + type: FormFieldType.Date, label: 'Date Noted', graphQLPropertyName: 'dateNoted', value: '', - customLabelCss: 'custom-lbl-text', - customInputTextCss: 'custom-input-text', tableMode: true, - stickyCol: false, }, - stickyCol: true, }, { id: 4, displayName: 'Land Description', active: true, graphQLPropertyName: 'landDescription', - groupId: 1, - disabled: true, - isDefault: true, - sortOrder: 1, - isChecked: true, + columnSize: ColumnSize.Triple, displayType: { - type: FormFieldType.Label, + type: FormFieldType.Text, label: 'Land Description', graphQLPropertyName: 'landDescription', value: '', - customLabelCss: 'custom-lbl-text', - customInputTextCss: 'custom-input-text', tableMode: true, - stickyCol: false, + isDisabled: true, }, - stickyCol: true, }, ]; + +const SRColumn: TableColumn = { + id: 4, + displayName: 'SR', + active: true, + graphQLPropertyName: 'srAction', + columnSize: ColumnSize.Default, + displayType: { + type: FormFieldType.Checkbox, + label: 'SR', + placeholder: '', + graphQLPropertyName: 'srAction', + value: false, + tableMode: true, + }, +}; + +export const getParcelDescriptionsTableColumns = ( + viewMode: SiteDetailsMode, +): TableColumn[] => { + if (viewMode === SiteDetailsMode.SRMode) { + return [...columns, SRColumn]; + } else { + return columns; + } +}; diff --git a/frontend/src/app/features/details/parcelDescriptions/parcelDescriptionsInterfaces.tsx b/frontend/src/app/features/details/parcelDescriptions/parcelDescriptionsInterfaces.tsx index f2d2c161..233a9488 100644 --- a/frontend/src/app/features/details/parcelDescriptions/parcelDescriptionsInterfaces.tsx +++ b/frontend/src/app/features/details/parcelDescriptions/parcelDescriptionsInterfaces.tsx @@ -1,12 +1,20 @@ import { RequestStatus } from '../../../helpers/requests/status'; +import { ParcelDescriptionType } from './parcelDescriptionsConfig'; export interface IParcelDescriptionDto { - id: number; - descriptionType: string; + id: string; + descriptionType: ParcelDescriptionType; idPinNumber: string; dateNoted: string; landDescription: string; + srAction: string; + userAction: string; } + +export interface IParcelDescriptionSaveDto extends IParcelDescriptionDto { + apiAction: string; +} + export interface IParcelDescriptionResponseDto { page: number; pageSize: number; diff --git a/frontend/src/app/features/details/srUpdates/srUpdates.tsx b/frontend/src/app/features/details/srUpdates/srUpdates.tsx index 2b177e69..28e48261 100644 --- a/frontend/src/app/features/details/srUpdates/srUpdates.tsx +++ b/frontend/src/app/features/details/srUpdates/srUpdates.tsx @@ -810,6 +810,7 @@ const SRUpdates = () => { handleChangeResultsPerPage={handleChange} currentPage={1} resultsPerPage={undefined} + viewMode={SiteDetailsMode.ViewOnlyMode} handleTableSortChange={handleChange} /> diff --git a/frontend/src/app/features/site/graphql/ParcelDescriptions.ts b/frontend/src/app/features/site/graphql/ParcelDescriptions.ts index 369c7f0f..e88cb9c5 100644 --- a/frontend/src/app/features/site/graphql/ParcelDescriptions.ts +++ b/frontend/src/app/features/site/graphql/ParcelDescriptions.ts @@ -32,6 +32,8 @@ export const graphQLParcelDescriptionBySiteId = () => { idPinNumber dateNoted landDescription + srAction + userAction } } }