From bad89107e7bef92adfb895b2394a44a3d29a24fa Mon Sep 17 00:00:00 2001 From: Andrew Date: Wed, 23 Feb 2022 06:03:59 -0800 Subject: [PATCH 001/133] added files for admin unit page --- src/client/app/actions/unit.ts | 0 src/server/routes/unit.js | 1 + 2 files changed, 1 insertion(+) create mode 100644 src/client/app/actions/unit.ts create mode 100644 src/server/routes/unit.js diff --git a/src/client/app/actions/unit.ts b/src/client/app/actions/unit.ts new file mode 100644 index 000000000..e69de29bb diff --git a/src/server/routes/unit.js b/src/server/routes/unit.js new file mode 100644 index 000000000..145c2c3c4 --- /dev/null +++ b/src/server/routes/unit.js @@ -0,0 +1 @@ +const express = require('express'); \ No newline at end of file From b573378d3aec4ea89d336d5c36b6c487d5e5998a Mon Sep 17 00:00:00 2001 From: Andrew Date: Fri, 25 Feb 2022 13:52:24 -0800 Subject: [PATCH 002/133] added front-end starter code for admin units page --- .../app/components/HeaderButtonsComponent.tsx | 6 ++ src/client/app/components/RouteComponent.tsx | 3 + .../components/unit/UnitsDetailContainer.tsx | 87 +++++++++++++++++++ src/client/app/translations/data.json | 14 +++ src/server/app.js | 2 +- src/server/routes/unit.js | 44 +++++++++- 6 files changed, 154 insertions(+), 2 deletions(-) create mode 100644 src/client/app/components/unit/UnitsDetailContainer.tsx diff --git a/src/client/app/components/HeaderButtonsComponent.tsx b/src/client/app/components/HeaderButtonsComponent.tsx index 3cf766b6c..50b1ec73f 100644 --- a/src/client/app/components/HeaderButtonsComponent.tsx +++ b/src/client/app/components/HeaderButtonsComponent.tsx @@ -44,6 +44,7 @@ export default class HeaderButtonsComponent extends React.Component + diff --git a/src/client/app/components/RouteComponent.tsx b/src/client/app/components/RouteComponent.tsx index e3c35e409..7b82f26ec 100644 --- a/src/client/app/components/RouteComponent.tsx +++ b/src/client/app/components/RouteComponent.tsx @@ -36,6 +36,7 @@ import UploadCSVContainer from '../containers/csv/UploadCSVContainer'; import { UserRole } from '../types/items'; import { hasPermissions } from '../utils/hasPermissions'; import queryString = require('query-string'); +import UnitsDetailContainer from './unit/UnitsDetailContainer'; interface RouteProps { barStacking: boolean; @@ -260,6 +261,8 @@ export default class RouteComponent extends React.Component { this.requireAuth()}/> this.requireAuth()}/> this.requireAuth( []}/>)}/> + this.requireAuth()}/> + this.requireAuth()}/> diff --git a/src/client/app/components/unit/UnitsDetailContainer.tsx b/src/client/app/components/unit/UnitsDetailContainer.tsx new file mode 100644 index 000000000..fe80cc433 --- /dev/null +++ b/src/client/app/components/unit/UnitsDetailContainer.tsx @@ -0,0 +1,87 @@ +import * as React from 'react'; +import { Table, Button } from 'reactstrap'; +import { FormattedMessage } from 'react-intl'; +import { hasToken } from '../../utils/token'; +import HeaderContainer from '../../containers/HeaderContainer'; +import FooterContainer from '../../containers/FooterContainer'; +import {Link} from 'react-router-dom'; +import TooltipHelpContainerAlternative from '../../containers/TooltipHelpContainerAlternative'; +import TooltipMarkerComponent from '../TooltipMarkerComponent'; +import { removeUnsavedChanges } from '../../actions/unsavedWarning'; +import store from '../../index'; +import UnsavedWarningContainer from '../../containers/UnsavedWarningContainer'; + + +export default class UnitsDetailContainer extends React.Component { + constructor(props: any) { + super(props); + } + + + public render() { + const titleStyle: React.CSSProperties = { + textAlign: 'center' + }; + + const tableStyle: React.CSSProperties = { + marginLeft: '5%', + marginRight: '5%' + }; + + const buttonContainerStyle: React.CSSProperties = { + minWidth: '150px', + width: '10%', + marginLeft: '40%', + marginRight: '40%' + }; + + const tooltipStyle = { + display: 'inline-block', + fontSize: '50%' + }; + return ( +
+ + +
+

+
+
+

+
+ + + + + + + + + + + + + + + + + + + +
+ {/* + Need to implement addUnit route later + */} + + + +
+
+
+ +
+ ); + } +} \ No newline at end of file diff --git a/src/client/app/translations/data.json b/src/client/app/translations/data.json index 004c6b19e..fb766a6f8 100644 --- a/src/client/app/translations/data.json +++ b/src/client/app/translations/data.json @@ -26,6 +26,7 @@ "create.group": "Create a Group", "create.map": "Create a Map", "create.user": "Create a User", + "create.unit": "Create a Unit", "csv": "CSV", "csv.file": "CSV File", "csv.common.param.gzip": "Gzip", @@ -225,6 +226,19 @@ "toggle.link": "Toggle chart link", "total": "total", "undefined": "undefined", + "units": "Units", + "unit" : "Unit", + "unit.id": "ID", + "unit.name": "Unit Name", + "unit.identifier": "Identifier", + "unit.represent": "Unit Represent", + "unit.sec_in_rate": "Sec in Rate", + "unit.type_of_unit": "Type of Unit", + "unit.suffix": "Suffix", + "unit.displayable": "Displayable", + "unit.preffered_display": "Preffered Display", + "unit.note": "Note", + "unit.remove": "Remove", "unused.groups": "Unused Groups", "unused.meters": "Unused Meters", "unsaved.warning": "You have unsaved change(s). Are you sure you want to leave?", diff --git a/src/server/app.js b/src/server/app.js index e5d3e0ea8..c3c9eb08f 100644 --- a/src/server/app.js +++ b/src/server/app.js @@ -80,7 +80,7 @@ app.use(express.static(path.join(__dirname, '..', 'client', 'public'))); const router = express.Router(); -router.get(/^(\/)(login|admin|groups|createGroup|editGroup|graph|meters|editMeter|maps|calibration|users|csv)?$/, (req, res) => { +router.get(/^(\/)(login|admin|groups|createGroup|editGroup|graph|meters|editMeter|maps|calibration|users|csv|units|addUnit)?$/, (req, res) => { fs.readFile(path.resolve(__dirname, '..', 'client', 'index.html'), (err, html) => { const subdir = config.subdir || '/'; let htmlPlusData = html.toString().replace('SUBDIR', subdir); diff --git a/src/server/routes/unit.js b/src/server/routes/unit.js index 145c2c3c4..e219abb79 100644 --- a/src/server/routes/unit.js +++ b/src/server/routes/unit.js @@ -1 +1,43 @@ -const express = require('express'); \ No newline at end of file +const express = require('express'); +const { getConnection } = require('../db'); +const { Unit } = require('../models/Unit'); +const User = require('../models/User'); +const { isTokenAuthorized } = require('../util/userRoles') +const adminAuthenticator = require('./authenticator').adminAuthMiddleware; + +const router = express.Router(); +router.use(optionalAuthenticator); + +function formatUnitForResponse(unit){ + const formattedUnit = { + id: unit.id, + name: unit.name, + identifier: unit.identifier, + unitRepresent: unit.unitRepresent, + secInRate: unit.secInRate, + typeOfUnit: unit.typeOfUnit, + unitIndex: unit.unitIndex, + suffix: unit.suffix, + displayable: unit.displayable, + preferredDisplay: unit.preferredDisplay, + note: unit.note + }; + return formattedUnit; +} + +router.get('/', async(req,res) => { + try{ + const conn = getConnection(); + let query; + const token = req.headers.token || req.body.token || req.query.token; + if (req.hasValidAuthToken && (await isTokenAuthorized(token, User.role.ADMIN))) { + query = Unit.getAll; // only admins can see disabled maps; + } else { + query = Unit.getDisplayable; + } + const rows = await query(conn); + res.json(rows.map(row => formatUnitForResponse(row))); + }catch(err){ + log.error(`Error while performing GET all units query: ${err}`, err); + } +}) \ No newline at end of file From ef1263aec1e4d2e8b500e5c08deb3f6ea9b5d48c Mon Sep 17 00:00:00 2001 From: Andrew Date: Wed, 2 Mar 2022 13:57:35 -0800 Subject: [PATCH 003/133] added necessary files for displaying units --- .vscode/settings.json | 2 + src/client/app/actions/unit.ts | 20 +++++++ src/client/app/components/RouteComponent.tsx | 2 +- .../app/components/unit/UnitViewComponent.tsx | 6 ++ ...Container.tsx => UnitsDetailComponent.tsx} | 56 ++++++++++++----- .../app/containers/unit/UnitViewContainer.tsx | 18 ++++++ .../containers/unit/UnitsDetailContainer.tsx | 60 +++++++++++++++++++ src/client/app/types/redux/actions.ts | 5 +- src/client/app/types/redux/state.ts | 2 + src/client/app/types/redux/unit.ts | 60 +++++++++++++++++++ src/client/app/utils/api/UnitsApi.ts | 15 +++++ src/client/app/utils/api/index.ts | 5 +- src/server/app.js | 2 + src/server/routes/unit.js | 16 +---- 14 files changed, 238 insertions(+), 31 deletions(-) create mode 100644 .vscode/settings.json create mode 100644 src/client/app/components/unit/UnitViewComponent.tsx rename src/client/app/components/unit/{UnitsDetailContainer.tsx => UnitsDetailComponent.tsx} (56%) create mode 100644 src/client/app/containers/unit/UnitViewContainer.tsx create mode 100644 src/client/app/containers/unit/UnitsDetailContainer.tsx create mode 100644 src/client/app/types/redux/unit.ts create mode 100644 src/client/app/utils/api/UnitsApi.ts diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 000000000..7a73a41bf --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,2 @@ +{ +} \ No newline at end of file diff --git a/src/client/app/actions/unit.ts b/src/client/app/actions/unit.ts index e69de29bb..4f10d82cc 100644 --- a/src/client/app/actions/unit.ts +++ b/src/client/app/actions/unit.ts @@ -0,0 +1,20 @@ +import {ActionType, Dispatch, GetState, Thunk} from '../types/redux/actions'; +import * as t from '../types/redux/unit' +import {unitsApi} from '../utils/api'; +import {State} from '../types/redux/state'; + +function requestUnitsDetails(): t.RequestUnitsDetailsAction { + return { type: ActionType.RequestUnitsDetails} +} + +function receiveUnitsDetails(data: t.UnitData[]): t.ReceiveUnitsDetailsAction{ + return {type: ActionType.ReceiveUnitsDetails, data} +} + +export function fetchUnitsDetails(): Thunk{ + return async (dispatch: Dispatch) => { + dispatch(requestUnitsDetails()); + const unitsDetails = await unitsApi.details(); + dispatch(receiveUnitsDetails(unitsDetails)); + } +} \ No newline at end of file diff --git a/src/client/app/components/RouteComponent.tsx b/src/client/app/components/RouteComponent.tsx index 7b82f26ec..358c5e814 100644 --- a/src/client/app/components/RouteComponent.tsx +++ b/src/client/app/components/RouteComponent.tsx @@ -36,7 +36,7 @@ import UploadCSVContainer from '../containers/csv/UploadCSVContainer'; import { UserRole } from '../types/items'; import { hasPermissions } from '../utils/hasPermissions'; import queryString = require('query-string'); -import UnitsDetailContainer from './unit/UnitsDetailContainer'; +import UnitsDetailContainer from '../containers/unit/UnitsDetailContainer'; interface RouteProps { barStacking: boolean; diff --git a/src/client/app/components/unit/UnitViewComponent.tsx b/src/client/app/components/unit/UnitViewComponent.tsx new file mode 100644 index 000000000..e0fbd8b8b --- /dev/null +++ b/src/client/app/components/unit/UnitViewComponent.tsx @@ -0,0 +1,6 @@ +import * as React from 'react'; + +interface UnitViewProps{ + id: number; + +} \ No newline at end of file diff --git a/src/client/app/components/unit/UnitsDetailContainer.tsx b/src/client/app/components/unit/UnitsDetailComponent.tsx similarity index 56% rename from src/client/app/components/unit/UnitsDetailContainer.tsx rename to src/client/app/components/unit/UnitsDetailComponent.tsx index fe80cc433..0bd94a4e5 100644 --- a/src/client/app/components/unit/UnitsDetailContainer.tsx +++ b/src/client/app/components/unit/UnitsDetailComponent.tsx @@ -1,5 +1,6 @@ import * as React from 'react'; import { Table, Button } from 'reactstrap'; +import { UnitData } from '../../types/redux/unit' import { FormattedMessage } from 'react-intl'; import { hasToken } from '../../utils/token'; import HeaderContainer from '../../containers/HeaderContainer'; @@ -11,12 +12,22 @@ import { removeUnsavedChanges } from '../../actions/unsavedWarning'; import store from '../../index'; import UnsavedWarningContainer from '../../containers/UnsavedWarningContainer'; +interface UnitsDetailProps{ + units: UnitData[]; + unsavedChanges: boolean; + fetchUnitsDetails(): Promise; +} -export default class UnitsDetailContainer extends React.Component { + +export default class UnitsDetailContainer extends React.Component { constructor(props: any) { super(props); } + public componentWillMount() { + this.props.fetchUnitsDetails(); + } + public render() { const titleStyle: React.CSSProperties = { @@ -42,7 +53,6 @@ export default class UnitsDetailContainer extends React.Component { return (
-

@@ -64,23 +74,39 @@ export default class UnitsDetailContainer extends React.Component { - - - {/* - Need to implement addUnit route later - */} - - - - - + + {this.props.units.map(unit => ( + + {unit.id} + {unit.name} + {unit.identifier} + {unit.unitRepresent} + {unit.secInRate} + {unit.typeOfUnit} + {unit.suffix} + {unit.displayable} + {unit.preferredDisplay} + {unit.note} + Button will go here I swear + + ))} + + + {/* + Need to implement addUnit route later + */} + + + + + +

-
); } diff --git a/src/client/app/containers/unit/UnitViewContainer.tsx b/src/client/app/containers/unit/UnitViewContainer.tsx new file mode 100644 index 000000000..993326322 --- /dev/null +++ b/src/client/app/containers/unit/UnitViewContainer.tsx @@ -0,0 +1,18 @@ +import * as React from 'react'; +import {State} from '../../types/redux/state' +import UnitViewComponent from '../../components/unit/UnitViewComponent.tsx' +import {Dispatch} from '../../types/redux/actions' +import { connect } from 'react-redux'; + +function mapStateToProps(state: State, ownProps: {id: number}){ + let unit = state.units.byUnitID[ownProps.id]; + if(state.units.editedUnits[ownProps.id]){ + unit = state.units.editedUnits[ownProps.id]; + } + return{ + unit, + isEdited: state.units.editedUnits[ownProps.id] !== undefined, + } +} + +export default connect(mapStateToProps)(UnitViewComponent); \ No newline at end of file diff --git a/src/client/app/containers/unit/UnitsDetailContainer.tsx b/src/client/app/containers/unit/UnitsDetailContainer.tsx new file mode 100644 index 000000000..9c98def89 --- /dev/null +++ b/src/client/app/containers/unit/UnitsDetailContainer.tsx @@ -0,0 +1,60 @@ +import * as React from 'react'; +import * as _ from 'lodash'; +import { State } from '../../types/redux/state' +import {UnitData} from '../../types/redux/unit' +import UnitDetailComponent from '../../components/unit/UnitsDetailComponent' +import {fetchUnitsDetails} from '../../actions/unit' +import {Dispatch} from '../../types/redux/actions'; +import { connect } from 'react-redux'; +import { unitsApi } from '../../utils/api'; +import HeaderContainer from '../HeaderContainer'; +import FooterContainer from '../FooterContainer'; + + +// function mapStateToProps(state: State){ +// return { +// units: Object.keys(state.units.byUnitID) +// .map(key => parseInt(key)) +// .filter(key => !isNaN(key)), +// unsavedChanges: Object.keys(state.units.editedUnits).length > 0 +// }; +// } + +// function mapDispatchToProps(dispatch: Dispatch){ +// return { +// fetchUnitsDetails: () => dispatch(fetchUnitsDetails()) +// } +// } + +// export default connect(mapStateToProps, mapDispatchToProps)(UnitDetailComponent); + +interface UnitDisplayContainerState{ + units: UnitData[], + history: UnitData[][] +} + +export default class UnitsDetailContainer extends React.Component<{} ,UnitDisplayContainerState> { + async componentDidMount() { + const units = await this.fetchUnits(); + this.setState({ units, history: [_.cloneDeep(units)]}) + } + + state: UnitDisplayContainerState = { + units: [], + history: [] + } + + private async fetchUnits() { + return await unitsApi.details(); + } + + public render () { + return ( +
+ + + +
+ ) + } +} \ No newline at end of file diff --git a/src/client/app/types/redux/actions.ts b/src/client/app/types/redux/actions.ts index 405c9c2c8..9607e39c7 100644 --- a/src/client/app/types/redux/actions.ts +++ b/src/client/app/types/redux/actions.ts @@ -111,7 +111,10 @@ export enum ActionType { ConfirmEditedMap = 'CONFIRM_EDITED_MAP', SetCalibration = 'SET_CALIBRATION', ResetCalibration = 'RESET_CALIBRATION', - IncrementCounter = 'INCREMENT_COUNTER' + IncrementCounter = 'INCREMENT_COUNTER', + + ReceiveUnitsDetails = 'RECEIVE_UNITS_DETAILS', + RequestUnitsDetails = 'REQUEST_UNITS_DETAISL' } /** diff --git a/src/client/app/types/redux/state.ts b/src/client/app/types/redux/state.ts index 7d48021e5..b9754944b 100644 --- a/src/client/app/types/redux/state.ts +++ b/src/client/app/types/redux/state.ts @@ -14,6 +14,7 @@ import { VersionState } from './version'; import {MapState} from './map'; import { CurrentUserState } from './currentUser'; import { UnsavedWarningState } from './unsavedWarning'; +import { UnitState } from './unit'; export interface State { meters: MetersState; @@ -30,4 +31,5 @@ export interface State { version: VersionState; currentUser: CurrentUserState; unsavedWarning: UnsavedWarningState; + units: UnitState; } diff --git a/src/client/app/types/redux/unit.ts b/src/client/app/types/redux/unit.ts new file mode 100644 index 000000000..d627798ad --- /dev/null +++ b/src/client/app/types/redux/unit.ts @@ -0,0 +1,60 @@ +import { ActionType } from "./actions"; + +export interface ReceiveUnitsDetailsAction{ + type: ActionType.ReceiveUnitsDetails; + data: UnitData[]; +} + +export interface RequestUnitsDetailsAction{ + type: ActionType.RequestUnitsDetails; +} + +/** + * @param {*} id This unit's ID. + * @param {*} name This unit's name used internally and by the admin. + * @param {*} identifier This unit's identifier displayed to the user. + * @param {*} unitRepresent Tells how the data is fetched for readings (only need for meter type unit). + * @param {*} secInRate The number of seconds in the unit associated with flow (rate) units. + * @param {*} typeOfUnit This unit's type. Can be meter, unit, or suffix. + * @param {*} unitIndex The unique number for row/column index in conversion table for this unit. + * @param {*} suffix This unit's suffix. + * @param {*} displayable Can be none, all, or admin. Restrict the type of user that can see this unit. + * @param {*} preferredDisplay True if this unit is always displayed. If not, the user needs to ask to see (for future enhancement). + * @param {*} note Note about this unit. + */ + +export interface UnitData{ + id: number; + name: string; + identifier: string; + unitRepresent: string; + secInRate: number; + typeOfUnit: string; + unitIndex: number; + suffix: string; + displayable: boolean; + preferredDisplay: boolean; + note: string; +} + +export interface UnitMetadata{ + id: number; + name: string; + displayable: boolean; + note?: string; +} + +interface UnitMetadataByID{ + [unitID: number]: UnitMetadata; +} + +export interface UnitState { + isLoading: boolean; + byUnitID: UnitMetadataByID; + selectedUnit: number; + editedUnits: UnitMetadataByID; +} + +export type UnitsAction = + | RequestUnitsDetailsAction + | ReceiveUnitsDetailsAction \ No newline at end of file diff --git a/src/client/app/utils/api/UnitsApi.ts b/src/client/app/utils/api/UnitsApi.ts new file mode 100644 index 000000000..739d3cd52 --- /dev/null +++ b/src/client/app/utils/api/UnitsApi.ts @@ -0,0 +1,15 @@ +import ApiBackend from "./ApiBackend"; +import {UnitData} from '../../types/redux/unit' + + +export default class UnitsApi { + private readonly backend: ApiBackend; + + constructor(backend: ApiBackend){ + this.backend = backend; + } + + public async details(): Promise{ + return await this.backend.doGetRequest('/api/units/'); + } +} \ No newline at end of file diff --git a/src/client/app/utils/api/index.ts b/src/client/app/utils/api/index.ts index 32d694f0e..c95697c1e 100644 --- a/src/client/app/utils/api/index.ts +++ b/src/client/app/utils/api/index.ts @@ -15,6 +15,7 @@ import UsersApi from './UsersApi'; import VersionApi from './VersionApi'; import MapsApi from './MapsApi'; import LogsApi from './LogsApi'; +import UnitsApi from './UnitsApi'; const apiBackend = new ApiBackend(); @@ -29,6 +30,7 @@ const usersApi = new UsersApi(apiBackend); const mapsApi = new MapsApi(apiBackend); const logsApi = new LogsApi(apiBackend); const versionApi = new VersionApi(apiBackend); +const unitsApi = new UnitsApi(apiBackend); export { groupsApi, @@ -40,5 +42,6 @@ export { logsApi, usersApi, versionApi, - uploadCSVApi + uploadCSVApi, + unitsApi }; diff --git a/src/server/app.js b/src/server/app.js index c3c9eb08f..b2e5ee0a4 100644 --- a/src/server/app.js +++ b/src/server/app.js @@ -30,6 +30,7 @@ const maps = require('./routes/maps'); const logs = require('./routes/logs'); const obvius = require('./routes/obvius'); const csv = require('./routes/csv'); +const unit = require('./routes/unit'); // Limit the rate of overall requests to OED // Note that the rate limit may make the automatic test return the value of 429. In that case, the limiters below need to be increased. @@ -76,6 +77,7 @@ app.use('/api/logs', logs); app.use('/api/timezones', timezones); app.use('/api/obvius', obvius); app.use('/api/csv', csv); +// app.use('/api/units', unit); app.use(express.static(path.join(__dirname, '..', 'client', 'public'))); const router = express.Router(); diff --git a/src/server/routes/unit.js b/src/server/routes/unit.js index e219abb79..63e28c280 100644 --- a/src/server/routes/unit.js +++ b/src/server/routes/unit.js @@ -1,12 +1,9 @@ const express = require('express'); const { getConnection } = require('../db'); const { Unit } = require('../models/Unit'); -const User = require('../models/User'); -const { isTokenAuthorized } = require('../util/userRoles') -const adminAuthenticator = require('./authenticator').adminAuthMiddleware; const router = express.Router(); -router.use(optionalAuthenticator); +//help function formatUnitForResponse(unit){ const formattedUnit = { @@ -28,15 +25,8 @@ function formatUnitForResponse(unit){ router.get('/', async(req,res) => { try{ const conn = getConnection(); - let query; - const token = req.headers.token || req.body.token || req.query.token; - if (req.hasValidAuthToken && (await isTokenAuthorized(token, User.role.ADMIN))) { - query = Unit.getAll; // only admins can see disabled maps; - } else { - query = Unit.getDisplayable; - } - const rows = await query(conn); - res.json(rows.map(row => formatUnitForResponse(row))); + let query = Unit.getAll; + res.json(query.map(row => formatUnitForResponse(query))); }catch(err){ log.error(`Error while performing GET all units query: ${err}`, err); } From 4bd99700e19e93b9453b76812f4f9016f8eee2b2 Mon Sep 17 00:00:00 2001 From: Andrew Date: Fri, 4 Mar 2022 14:19:42 -0800 Subject: [PATCH 004/133] finished unit display page, added page and form for adding units --- src/client/app/actions/unit.ts | 3 +- src/client/app/components/RouteComponent.tsx | 3 +- .../components/unit/CreateUnitComponent.tsx | 109 +++++++++++++++ .../app/components/unit/UnitViewComponent.tsx | 6 - .../components/unit/UnitsDetailComponent.tsx | 10 +- .../containers/unit/CreateUnitContainer.tsx | 61 +++++++++ .../containers/unit/UnitsDetailContainer.tsx | 4 - src/client/app/types/redux/unit.ts | 2 +- src/server/app.js | 2 +- src/server/routes/unit.js | 125 +++++++++++++++++- 10 files changed, 298 insertions(+), 27 deletions(-) create mode 100644 src/client/app/components/unit/CreateUnitComponent.tsx delete mode 100644 src/client/app/components/unit/UnitViewComponent.tsx create mode 100644 src/client/app/containers/unit/CreateUnitContainer.tsx diff --git a/src/client/app/actions/unit.ts b/src/client/app/actions/unit.ts index 4f10d82cc..9650643f1 100644 --- a/src/client/app/actions/unit.ts +++ b/src/client/app/actions/unit.ts @@ -1,7 +1,6 @@ -import {ActionType, Dispatch, GetState, Thunk} from '../types/redux/actions'; +import {ActionType, Dispatch, Thunk} from '../types/redux/actions'; import * as t from '../types/redux/unit' import {unitsApi} from '../utils/api'; -import {State} from '../types/redux/state'; function requestUnitsDetails(): t.RequestUnitsDetailsAction { return { type: ActionType.RequestUnitsDetails} diff --git a/src/client/app/components/RouteComponent.tsx b/src/client/app/components/RouteComponent.tsx index 358c5e814..e988c6eb6 100644 --- a/src/client/app/components/RouteComponent.tsx +++ b/src/client/app/components/RouteComponent.tsx @@ -37,6 +37,7 @@ import { UserRole } from '../types/items'; import { hasPermissions } from '../utils/hasPermissions'; import queryString = require('query-string'); import UnitsDetailContainer from '../containers/unit/UnitsDetailContainer'; +import CreateUnitContainter from '../containers/unit/CreateUnitContainer'; interface RouteProps { barStacking: boolean; @@ -262,7 +263,7 @@ export default class RouteComponent extends React.Component { this.requireAuth()}/> this.requireAuth( []}/>)}/> this.requireAuth()}/> - this.requireAuth()}/> + this.requireAuth()}/> diff --git a/src/client/app/components/unit/CreateUnitComponent.tsx b/src/client/app/components/unit/CreateUnitComponent.tsx new file mode 100644 index 000000000..98e803d39 --- /dev/null +++ b/src/client/app/components/unit/CreateUnitComponent.tsx @@ -0,0 +1,109 @@ +import * as React from 'react'; +import {Alert, Button, Input} from 'reactstrap'; +import { FormattedMessage } from 'react-intl'; + + +// TODO: Add props for state handlers from container +interface CreateUnitFormProps{ + name: string, + identifier: string, + unitRepresent: string, + secInRate: number, + typeOfUnit: string, + unitIndex: null, + suffix: string, + displayable: string, + preferredDisplay: boolean, + note: string, + submitNewUnit: () => void; + handleNameChange: (val: string) => void; +} + +export default class CreateUnitComponent extends React.Component{ + constructor(props: any){ + super(props); + } + + public render() { + const formInputStyle: React.CSSProperties = { + paddingBottom: '5px' + } + const titleStyle: React.CSSProperties = { + textAlign: 'center' + }; + + const tableStyle: React.CSSProperties = { + marginLeft: '25%', + marginRight: '25%', + width: '50%' + }; + return( +
+ {/* add create.unit text */} +

Create New Unit

+
+
{ e.preventDefault(); this.props.submitNewUnit(); }}> +
+ {/* need name formatted message */} +
+ this.props.handleNameChange(target.value)} required value={this.props.name} /> +
+
+ {/* need identfier formatted message */} +
+ this.props.handleIdentifierChange(target.value)} required value={this.props.identifier} /> +
+
+ {/* need name formatted message */} +
+ this.props.handleUnitRepresentChange(target.value)} required value={this.props.name}> + + + + + +
+
+ {/* need name formatted message */} +
+ this.props.handleSecInRateChange(target.value)} required value={this.props.secInRate} /> +
+
+ {/* need name formatted message */} +
+ this.props.handleTypeOfUnitChange(target.value)} required value={this.props.name}> + + + + +
+
+ {/* need name formatted message */} +
+ this.props.handleSuffixChange(target.value)} required value={this.props.suffix} /> +
+
+
+ this.props.handleDisplayableChange(target.value)} required value={this.props.displayable}> + + + + +
+
+ {/* need identfier formatted message */} + + this.props.handlePreferredDisplayChange(target.value)} required value={this.props.preferredDisplay.toString()} /> +
+
+ {/* need name formatted message */} +
+ this.props.handleNoteChange(target.value)} required value={this.props.note} /> +
+
+
+
+ ); + } + +} \ No newline at end of file diff --git a/src/client/app/components/unit/UnitViewComponent.tsx b/src/client/app/components/unit/UnitViewComponent.tsx deleted file mode 100644 index e0fbd8b8b..000000000 --- a/src/client/app/components/unit/UnitViewComponent.tsx +++ /dev/null @@ -1,6 +0,0 @@ -import * as React from 'react'; - -interface UnitViewProps{ - id: number; - -} \ No newline at end of file diff --git a/src/client/app/components/unit/UnitsDetailComponent.tsx b/src/client/app/components/unit/UnitsDetailComponent.tsx index 0bd94a4e5..6ed4660b9 100644 --- a/src/client/app/components/unit/UnitsDetailComponent.tsx +++ b/src/client/app/components/unit/UnitsDetailComponent.tsx @@ -2,14 +2,8 @@ import * as React from 'react'; import { Table, Button } from 'reactstrap'; import { UnitData } from '../../types/redux/unit' import { FormattedMessage } from 'react-intl'; -import { hasToken } from '../../utils/token'; -import HeaderContainer from '../../containers/HeaderContainer'; -import FooterContainer from '../../containers/FooterContainer'; import {Link} from 'react-router-dom'; -import TooltipHelpContainerAlternative from '../../containers/TooltipHelpContainerAlternative'; -import TooltipMarkerComponent from '../TooltipMarkerComponent'; -import { removeUnsavedChanges } from '../../actions/unsavedWarning'; -import store from '../../index'; + import UnsavedWarningContainer from '../../containers/UnsavedWarningContainer'; interface UnitsDetailProps{ @@ -20,7 +14,7 @@ interface UnitsDetailProps{ export default class UnitsDetailContainer extends React.Component { - constructor(props: any) { + constructor(props: UnitsDetailProps) { super(props); } diff --git a/src/client/app/containers/unit/CreateUnitContainer.tsx b/src/client/app/containers/unit/CreateUnitContainer.tsx new file mode 100644 index 000000000..c2983119e --- /dev/null +++ b/src/client/app/containers/unit/CreateUnitContainer.tsx @@ -0,0 +1,61 @@ +import * as React from 'react'; +import HeaderContainer from '../HeaderContainer'; +import FooterContainer from '../FooterContainer'; +import CreateUnitComponent from '../../components/unit/CreateUnitComponent'; +import { showSuccessNotification, showErrorNotification } from '../../utils/notifications'; +import { unitsApi } from 'utils/api'; +import { UnitData } from 'types/redux/unit'; + +export default class CreateUnitContainter extends React.Component<{}, {}> { + constructor(props: {}){ + super(props); + //TODO: create rest of hanlder functions, bind them, and pass to CreateUnitComponent + this.handleNameChange = this.handleNameChange.bind(this); + this.submitNewUnit = this.submitNewUnit.bind(this); + } + + state = { + name: '', + identifier: '', + unitRepresent: '', + secInRate: 3600, + typeOfUnit: '', + unitIndex: null, + suffix: '', + displayable: '', + preferredDisplay: false, + note: '' + } + + //TODO: create rest of hanlder functions, bind them, and pass to CreateUnitComponent + private handleNameChange = (newName: string) => { + this.setState({ name : newName}); + } + + private submitNewUnit = async () => { + + }; + public render() { + return ( +
+ + + +
+ ); + } +} \ No newline at end of file diff --git a/src/client/app/containers/unit/UnitsDetailContainer.tsx b/src/client/app/containers/unit/UnitsDetailContainer.tsx index 9c98def89..1f3f55d9c 100644 --- a/src/client/app/containers/unit/UnitsDetailContainer.tsx +++ b/src/client/app/containers/unit/UnitsDetailContainer.tsx @@ -1,11 +1,7 @@ import * as React from 'react'; import * as _ from 'lodash'; -import { State } from '../../types/redux/state' import {UnitData} from '../../types/redux/unit' import UnitDetailComponent from '../../components/unit/UnitsDetailComponent' -import {fetchUnitsDetails} from '../../actions/unit' -import {Dispatch} from '../../types/redux/actions'; -import { connect } from 'react-redux'; import { unitsApi } from '../../utils/api'; import HeaderContainer from '../HeaderContainer'; import FooterContainer from '../FooterContainer'; diff --git a/src/client/app/types/redux/unit.ts b/src/client/app/types/redux/unit.ts index d627798ad..942b9a76d 100644 --- a/src/client/app/types/redux/unit.ts +++ b/src/client/app/types/redux/unit.ts @@ -32,7 +32,7 @@ export interface UnitData{ typeOfUnit: string; unitIndex: number; suffix: string; - displayable: boolean; + displayable: string; preferredDisplay: boolean; note: string; } diff --git a/src/server/app.js b/src/server/app.js index b2e5ee0a4..5e0a89ea9 100644 --- a/src/server/app.js +++ b/src/server/app.js @@ -77,7 +77,7 @@ app.use('/api/logs', logs); app.use('/api/timezones', timezones); app.use('/api/obvius', obvius); app.use('/api/csv', csv); -// app.use('/api/units', unit); +app.use('/api/units', unit); app.use(express.static(path.join(__dirname, '..', 'client', 'public'))); const router = express.Router(); diff --git a/src/server/routes/unit.js b/src/server/routes/unit.js index 63e28c280..058305b67 100644 --- a/src/server/routes/unit.js +++ b/src/server/routes/unit.js @@ -1,6 +1,10 @@ const express = require('express'); +const Unit = require('../models/Unit'); const { getConnection } = require('../db'); -const { Unit } = require('../models/Unit'); +const { log } = require('../log'); +const validate = require('jsonschema').validate; +const adminAuthenticator = require('./authenticator').adminAuthMiddleware; + const router = express.Router(); //help @@ -25,9 +29,122 @@ function formatUnitForResponse(unit){ router.get('/', async(req,res) => { try{ const conn = getConnection(); - let query = Unit.getAll; - res.json(query.map(row => formatUnitForResponse(query))); + let query; + query = Unit.getAll; + const rows = await query(conn); + + res.json([{ + id: 1, + name: "unit.name", + identifier: "unit.identifier", + unitRepresent: "unit.unitRepresent", + secInRate: 2, + typeOfUnit: "unit.typeOfUnit", + unitIndex: 3, + suffix: "unit.suffix", + displayable: "true", + preferredDisplay: false, + note: "unit.note" + }, { + id: 4, + name: "unit.name", + identifier: "unit.identifier", + unitRepresent: "unit.unitRepresent", + secInRate: 5, + typeOfUnit: "unit.typeOfUnit", + unitIndex: 6, + suffix: "unit.suffix", + displayable: "true", + preferredDisplay: false, + note: "unit.note" + }]); + console.log(rows); }catch(err){ log.error(`Error while performing GET all units query: ${err}`, err); } -}) \ No newline at end of file +}) + +router.post('/addUnit', adminAuthenticator('create unit'), async (req,res) => { + const validUnit = { + type: 'object', + required: ['name', 'identifier', 'unitRepresent', 'typeOfUnit', 'displayable', 'preferredDisplay'], + properties: { + name: { + type: 'string', + minLength: 1 + }, + identifier: { + type: 'string', + minLength: 1 + }, + // not sure if you need to check based on enum + unitRepresent: { + type: 'string', + minLength: 1 + }, + secInRate: { + type: 'number', + }, + typeOfUnit: { + type: 'string', + minLength: 1 + }, + // not sure how to handle unit index and type for unique values + unitIndex: { + oneOf: [ + { type: 'number'}, + { type: 'null' } + ] + }, + suffix: { + type: 'string', + minLength: 1 + }, + displayable: { + type: 'string', + minLength: 1 + }, + preferredDisplay: { + type: 'bool' + }, + note: { + oneOf: [ + {type: 'string'}, + { type: 'null'} + ] + } + } + }; + const validationResult = validate(req.body, validUnit); + if(!validationResult.valid){ + log.error(`Invalid input for mapAPI. ${validationResult.error}`); + res.sendStatus(400); + }else{ + const conn = getConnection(); + try{ + await conn.tx(async t => { + const note = (req.body.note) ? '' : req.body.note; + const newUnit = new Unit( + undefined, + req.body.name, + req.body.identifier, + req.body.unitRepresent, + req.body.secInRate, + req.body.typeOfUnit, + req.body.unitIndex, + req.body.suffix, + req.body.displayable, + req.body.preferredDisplay, + note + ); + await newUnit.insert(t); + }); + res.sendStatus(200); + }catch(err){ + log.error(`Error while inserting new unit ${err}`, err); + res.sendStatus(500); + } + } +}); + +module.exports = router; \ No newline at end of file From 21147ad333f3b99da542e65dd1e749caa364b1bf Mon Sep 17 00:00:00 2001 From: Justin Le Date: Wed, 9 Mar 2022 12:11:55 -0800 Subject: [PATCH 005/133] added ES and FR translation messages for unit page --- src/client/app/translations/data.js | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/client/app/translations/data.js b/src/client/app/translations/data.js index 3c19354b9..bfee3f087 100644 --- a/src/client/app/translations/data.js +++ b/src/client/app/translations/data.js @@ -301,6 +301,7 @@ const localeData = { "create.group": "Créer un Groupe", "create.map": "(need French) Create a Map", "create.user": "(Need French) Create a User", + "create.unit": "(Need French) Create a Unit", "csv": "CSV", "csv.file": "(Need French) CSV File", "csv.common.param.gzip": "(Need French) Gzip", @@ -500,6 +501,19 @@ const localeData = { "toggle.link": "Bascule du lien du diagramme", "total": "total", "undefined": "(Need French) undefined", + "units": "(Need French) Units", + "unit" : "(Need French) Unit", + "unit.id": "(Need French) ID", + "unit.name": "(Need French) Unit Name", + "unit.identifier": "(Need French) Identifier", + "unit.represent": "(Need French) Unit Represent", + "unit.sec_in_rate": "(Need French) Sec in Rate", + "unit.type_of_unit": "(Need French) Type of Unit", + "unit.suffix": "(Need French) Suffix", + "unit.displayable": "(Need French) Displayable", + "unit.preffered_display": "(Need French) Preffered Display", + "unit.note": "(Need French) Note", + "unit.remove": "(Need French) Remove", "unused.groups": "Groupes non utilisés", "unused.meters": "Mètres non utilisés", "unsaved.warning": "(Need French) You have unsaved change(s). Are you sure you want to leave?", @@ -555,6 +569,7 @@ const localeData = { "create.group": "Crear un grupo", "create.map": "Crear una mapa", "create.user": "Crear un usario", + "create.unit": "(Need Spanish) Create a Unit", "csv": "CSV", "csv.file": "Archivo de CSV", "csv.common.param.gzip": "Gzip", @@ -754,6 +769,19 @@ const localeData = { "toggle.link": "Alternar enlace de gráfico", "total": "Total", "undefined": "indefinido", + "units": "(Need Spanish) Units", + "unit" : "(Need Spanish) Unit", + "unit.id": "(Need Spanish) ID", + "unit.name": "(Need Spanish) Unit Name", + "unit.identifier": "(Need Spanish) Identifier", + "unit.represent": "(Need Spanish) Unit Represent", + "unit.sec_in_rate": "(Need Spanish) Sec in Rate", + "unit.type_of_unit": "(Need Spanish) Type of Unit", + "unit.suffix": "(Need Spanish) Suffix", + "unit.displayable": "(Need Spanish) Displayable", + "unit.preffered_display": "(Need Spanish) Preffered Display", + "unit.note": "(Need Spanish) Note", + "unit.remove": "(Need Spanish) Remove", "unused.groups": "Grupos no utilizados", "unused.meters": "Metros no utilizados", "unsaved.warning": "Tienes cambios sin guardar. ¿Estás seguro que quieres irte?", From 301addca3b5a5ebd8f507471d2af383bbdda15d4 Mon Sep 17 00:00:00 2001 From: Andrew Date: Wed, 9 Mar 2022 12:19:05 -0800 Subject: [PATCH 006/133] edited UnitsDetailContainer --- .../components/unit/CreateUnitComponent.tsx | 10 +- .../containers/unit/CreateUnitContainer.tsx | 45 +++++++++ .../containers/unit/UnitsDetailContainer.tsx | 96 +++++++++++-------- src/server/routes/unit.js | 16 ++-- 4 files changed, 115 insertions(+), 52 deletions(-) diff --git a/src/client/app/components/unit/CreateUnitComponent.tsx b/src/client/app/components/unit/CreateUnitComponent.tsx index 98e803d39..4271fe6d5 100644 --- a/src/client/app/components/unit/CreateUnitComponent.tsx +++ b/src/client/app/components/unit/CreateUnitComponent.tsx @@ -17,6 +17,14 @@ interface CreateUnitFormProps{ note: string, submitNewUnit: () => void; handleNameChange: (val: string) => void; + handleIdentifierChange: (val : string) => void; + handleUnitRepresentChange: (val : string) => void; + handleSecInRateChange: (val : number) => void; + handleTypeOfUnitChange: (val : string) => void; + handleSuffixChange: (val : string) => void; + handleDisplayableChange: (val : string) => void; + handlePreferredDisplayChange: (val : boolean) => void; + handleNoteChange: (val : string) => void; } export default class CreateUnitComponent extends React.Component{ @@ -66,7 +74,7 @@ export default class CreateUnitComponent extends React.Component {/* need name formatted message */}
- this.props.handleSecInRateChange(target.value)} required value={this.props.secInRate} /> + this.props.handleSecInRateChange(parseInt(target.value))} required value={this.props.secInRate} />
{/* need name formatted message */} diff --git a/src/client/app/containers/unit/CreateUnitContainer.tsx b/src/client/app/containers/unit/CreateUnitContainer.tsx index c2983119e..d886aed51 100644 --- a/src/client/app/containers/unit/CreateUnitContainer.tsx +++ b/src/client/app/containers/unit/CreateUnitContainer.tsx @@ -12,6 +12,14 @@ export default class CreateUnitContainter extends React.Component<{}, {}> { //TODO: create rest of hanlder functions, bind them, and pass to CreateUnitComponent this.handleNameChange = this.handleNameChange.bind(this); this.submitNewUnit = this.submitNewUnit.bind(this); + this.handleIdentifierChange = this.handleIdentifierChange.bind(this); + this.handleUnitRepresentChange = this.handleUnitRepresentChange.bind(this); + this.handleSecInRateChange = this.handleSecInRateChange.bind(this); + this.handleTypeOfUnitChange = this.handleTypeOfUnitChange.bind(this); + this.handleSuffixChange = this.handleSuffixChange.bind(this); + this.handleDisplayableChange = this.handleDisplayableChange.bind(this); + this.handlePreferredDisplayChange = this.handlePreferredDisplayChange.bind(this); + this.handleNoteChange = this.handleNoteChange.bind(this); } state = { @@ -32,6 +40,35 @@ export default class CreateUnitContainter extends React.Component<{}, {}> { this.setState({ name : newName}); } + private handleIdentifierChange = (newIdentifier: string) => { + this.setState({ identifier : newIdentifier}); + } + + private handleUnitRepresentChange = (newIdentifier: string) => { + this.setState({ identifier : newIdentifier}); + } + + + private handleSecInRateChange = (newUnitRepresent: number) => { + this.setState({ unitRepresent : newUnitRepresent}); + } + + private handleTypeOfUnitChange = (newTypeOfUnit: string) => { + this.setState({ typeOfUnit : newTypeOfUnit}); + } + private handleSuffixChange = (newsuffix: string) => { + this.setState({ suffix : newsuffix}) + } + private handleDisplayableChange = (newDisplayable: string) => { + this.setState({ displayable : newDisplayable}); + } + private handlePreferredDisplayChange = (newPreferredDisplay: boolean) => { + this.setState({ preferredDisplay : newPreferredDisplay}); + } + private handleNoteChange = (newNote: string) => { + this.setState({ note : newNote}); + } + private submitNewUnit = async () => { }; @@ -53,6 +90,14 @@ export default class CreateUnitContainter extends React.Component<{}, {}> { note= {this.state.note} submitNewUnit= {this.submitNewUnit} handleNameChange= {this.handleNameChange} + handleIdentifierChange = {this.handleIdentifierChange} + handleUnitRepresentChange = {this.handleUnitRepresentChange} + handleSecInRateChange = {this.handleSecInRateChange} + handleTypeOfUnitChange = {this.handleTypeOfUnitChange} + handleSuffixChange = {this.handleSuffixChange} + handleDisplayableChange = {this.handleDisplayableChange} + handlePreferredDisplayChange = {this.handlePreferredDisplayChange} + handleNoteChange = {this.handleNoteChange} />
diff --git a/src/client/app/containers/unit/UnitsDetailContainer.tsx b/src/client/app/containers/unit/UnitsDetailContainer.tsx index 1f3f55d9c..9c8b94580 100644 --- a/src/client/app/containers/unit/UnitsDetailContainer.tsx +++ b/src/client/app/containers/unit/UnitsDetailContainer.tsx @@ -1,56 +1,70 @@ import * as React from 'react'; import * as _ from 'lodash'; import {UnitData} from '../../types/redux/unit' -import UnitDetailComponent from '../../components/unit/UnitsDetailComponent' +import UnitsDetailComponent from '../../components/unit/UnitsDetailComponent' +import { connect } from 'react-redux'; +import { State } from '../../types/redux/state'; +import {Dispatch} from '../../types/redux/actions'; import { unitsApi } from '../../utils/api'; +import { fetchUnitsDetails } from 'actions/unit'; import HeaderContainer from '../HeaderContainer'; import FooterContainer from '../FooterContainer'; +import { isRoleAdmin } from 'utils/hasPermissions'; -// function mapStateToProps(state: State){ -// return { -// units: Object.keys(state.units.byUnitID) -// .map(key => parseInt(key)) -// .filter(key => !isNaN(key)), -// unsavedChanges: Object.keys(state.units.editedUnits).length > 0 -// }; -// } - -// function mapDispatchToProps(dispatch: Dispatch){ -// return { -// fetchUnitsDetails: () => dispatch(fetchUnitsDetails()) -// } -// } - -// export default connect(mapStateToProps, mapDispatchToProps)(UnitDetailComponent); +function mapStateToProps(state: State, ownProps: { id: number}){ + let unit = JSON.parse(JSON.stringify(state.units.byUnitID[ownProps.id])); + if(state.units.editedUnits[ownProps.id]){ + unit = JSON.parse(JSON.stringify(state.units.editedUnits[ownProps.id])) + } -interface UnitDisplayContainerState{ - units: UnitData[], - history: UnitData[][] + const currentUser = state.currentUser.profile; + let loggedInAsAdmin = false; + if(currentUser !== null){ + loggedInAsAdmin = isRoleAdmin(currentUser.role); + } + return { + unit, + isEdited: state.units.editedUnits[ownProps.id] !== undefined, + loggedInAsAdmin + }; } -export default class UnitsDetailContainer extends React.Component<{} ,UnitDisplayContainerState> { - async componentDidMount() { - const units = await this.fetchUnits(); - this.setState({ units, history: [_.cloneDeep(units)]}) +function mapDispatchToProps(dispatch: Dispatch){ + return { + fetchUnitsDetails: () => dispatch(fetchUnitsDetails()) } +} - state: UnitDisplayContainerState = { - units: [], - history: [] - } +export default connect(mapStateToProps, mapDispatchToProps)(UnitsDetailComponent); - private async fetchUnits() { - return await unitsApi.details(); - } +// interface UnitDisplayContainerState{ +// units: UnitData[], +// history: UnitData[][] +// } - public render () { - return ( -
- - - -
- ) - } -} \ No newline at end of file +// export default class UnitsDetailContainer extends React.Component<{} ,UnitDisplayContainerState> { +// async componentDidMount() { +// const units = await this.fetchUnits(); +// this.setState({ units, history: [_.cloneDeep(units)]}) +// } + +// state: UnitDisplayContainerState = { +// units: [], +// history: [] +// } + +// private async fetchUnits() { +// return await unitsApi.details(); +// } + +// public render () { +// return ( +//
+// +// +// +//
+// ) +// } +// } \ No newline at end of file diff --git a/src/server/routes/unit.js b/src/server/routes/unit.js index 058305b67..c42ec16e5 100644 --- a/src/server/routes/unit.js +++ b/src/server/routes/unit.js @@ -80,21 +80,16 @@ router.post('/addUnit', adminAuthenticator('create unit'), async (req,res) => { // not sure if you need to check based on enum unitRepresent: { type: 'string', - minLength: 1 + minLength: 1, + enum: Object.values(Unit.unitRepresentType) }, secInRate: { type: 'number', }, typeOfUnit: { type: 'string', - minLength: 1 - }, - // not sure how to handle unit index and type for unique values - unitIndex: { - oneOf: [ - { type: 'number'}, - { type: 'null' } - ] + minLength: 1, + enum: Object.values(Unit.unitType) }, suffix: { type: 'string', @@ -102,7 +97,8 @@ router.post('/addUnit', adminAuthenticator('create unit'), async (req,res) => { }, displayable: { type: 'string', - minLength: 1 + minLength: 1, + enum: Object.values(Unit.displayableType) }, preferredDisplay: { type: 'bool' From 069df2f398f5a39ebcb7f7846381a3a92160eb5c Mon Sep 17 00:00:00 2001 From: Sagar Prasad Date: Wed, 9 Mar 2022 14:04:16 -0800 Subject: [PATCH 007/133] edited units details component, container, etc --- .../app/components/unit/UnitViewComponent.tsx | 54 +++++++++++++++++++ .../components/unit/UnitsDetailComponent.tsx | 20 ++----- .../containers/unit/CreateUnitContainer.tsx | 1 + .../app/containers/unit/UnitViewContainer.tsx | 30 ++++++++--- .../containers/unit/UnitsDetailContainer.tsx | 20 ++++--- src/client/app/reducers/index.ts | 4 +- src/client/app/reducers/units.ts | 32 +++++++++++ src/client/app/types/redux/unit.ts | 11 ++-- 8 files changed, 133 insertions(+), 39 deletions(-) create mode 100644 src/client/app/components/unit/UnitViewComponent.tsx create mode 100644 src/client/app/reducers/units.ts diff --git a/src/client/app/components/unit/UnitViewComponent.tsx b/src/client/app/components/unit/UnitViewComponent.tsx new file mode 100644 index 000000000..3ad4ac8b3 --- /dev/null +++ b/src/client/app/components/unit/UnitViewComponent.tsx @@ -0,0 +1,54 @@ +import * as React from 'react'; +import {Button} from 'reactstrap'; +import { UnitData } from '../../types/redux/unit'; +import { FormattedMessage, injectIntl, WrappedComponentProps } from 'react-intl'; + + + +interface UnitViewProps { + id: number; + unit: UnitData; + isEdited: boolean; + isSubmitting: boolean; + loggedInAsAdmin: boolean; + + //editUnitDetails(unit: UnitMetadata): EditUnitDetailsAction; +} + +type UnitViewPropsWithIntl = UnitViewProps & WrappedComponentProps; + +class UnitViewComponent extends React.Component { + constructor(props: UnitViewPropsWithIntl){ + super(props); + } + public render() { + const loggedInAsAdmin = this.props.loggedInAsAdmin; + return ( + + {loggedInAsAdmin && {this.props.unit.id} {this.formatStatus()} } + {loggedInAsAdmin && {this.props.unit.name} {this.formatStatus()} } + {loggedInAsAdmin && {this.props.unit.identifier} {this.formatStatus()} } + {loggedInAsAdmin && {this.props.unit.unitRepresent} {this.formatStatus()} } + {loggedInAsAdmin && {this.props.unit.secInRate} {this.formatStatus()} } + {loggedInAsAdmin && {this.props.unit.typeOfUnit} {this.formatStatus()} } + {loggedInAsAdmin && {this.props.unit.suffix} {this.formatStatus()} } + {loggedInAsAdmin && {this.props.unit.displayable} {this.formatStatus()} } + {loggedInAsAdmin && {this.props.unit.preferredDisplay} {this.formatStatus()} } + {loggedInAsAdmin && {this.props.unit.note} {this.formatStatus()} } + + ); + } + + private formatStatus(): string { + if (this.props.isSubmitting) { + return '(' + this.props.intl.formatMessage({id: 'submitting'}) + ')'; + } + + if (this.props.isEdited) { + return this.props.intl.formatMessage({id: 'edited'}); + } + + return ''; + } +} +export default injectIntl(UnitViewComponent); \ No newline at end of file diff --git a/src/client/app/components/unit/UnitsDetailComponent.tsx b/src/client/app/components/unit/UnitsDetailComponent.tsx index 6ed4660b9..11af4b217 100644 --- a/src/client/app/components/unit/UnitsDetailComponent.tsx +++ b/src/client/app/components/unit/UnitsDetailComponent.tsx @@ -3,11 +3,12 @@ import { Table, Button } from 'reactstrap'; import { UnitData } from '../../types/redux/unit' import { FormattedMessage } from 'react-intl'; import {Link} from 'react-router-dom'; +import UnitViewContainer from '../../containers/unit/UnitViewContainer'; import UnsavedWarningContainer from '../../containers/UnsavedWarningContainer'; interface UnitsDetailProps{ - units: UnitData[]; + units: number[]; unsavedChanges: boolean; fetchUnitsDetails(): Promise; } @@ -70,21 +71,8 @@ export default class UnitsDetailContainer extends React.Component - {this.props.units.map(unit => ( - - {unit.id} - {unit.name} - {unit.identifier} - {unit.unitRepresent} - {unit.secInRate} - {unit.typeOfUnit} - {unit.suffix} - {unit.displayable} - {unit.preferredDisplay} - {unit.note} - Button will go here I swear - - ))} + {this.props.units.map(unitID => + ( ))} {/* diff --git a/src/client/app/containers/unit/CreateUnitContainer.tsx b/src/client/app/containers/unit/CreateUnitContainer.tsx index d886aed51..ac345cf04 100644 --- a/src/client/app/containers/unit/CreateUnitContainer.tsx +++ b/src/client/app/containers/unit/CreateUnitContainer.tsx @@ -1,4 +1,5 @@ import * as React from 'react'; +import { fetchUnitsDetails } from '../../actions/unit'; import HeaderContainer from '../HeaderContainer'; import FooterContainer from '../FooterContainer'; import CreateUnitComponent from '../../components/unit/CreateUnitComponent'; diff --git a/src/client/app/containers/unit/UnitViewContainer.tsx b/src/client/app/containers/unit/UnitViewContainer.tsx index 993326322..e8a669edd 100644 --- a/src/client/app/containers/unit/UnitViewContainer.tsx +++ b/src/client/app/containers/unit/UnitViewContainer.tsx @@ -1,18 +1,36 @@ import * as React from 'react'; -import {State} from '../../types/redux/state' -import UnitViewComponent from '../../components/unit/UnitViewComponent.tsx' -import {Dispatch} from '../../types/redux/actions' +import {State} from '../../types/redux/state'; +import UnitViewComponent from '../../components/unit/UnitViewComponent'; +import {Dispatch} from '../../types/redux/actions'; import { connect } from 'react-redux'; +import { logToServer } from '../../actions/logs'; +import { UnitMetadata } from 'types/redux/unit'; +import { isRoleAdmin } from '../../utils/hasPermissions'; function mapStateToProps(state: State, ownProps: {id: number}){ - let unit = state.units.byUnitID[ownProps.id]; + let unit = JSON.parse(JSON.stringify(state.units.byUnitID[ownProps.id])); if(state.units.editedUnits[ownProps.id]){ - unit = state.units.editedUnits[ownProps.id]; + unit = JSON.parse(JSON.stringify(state.units.editedUnits[ownProps.id])); } + + const currentUser = state.currentUser.profile; + let loggedInAsAdmin = false; + if (currentUser !== null) { + loggedInAsAdmin = isRoleAdmin(currentUser.role); + } + return{ unit, isEdited: state.units.editedUnits[ownProps.id] !== undefined, + isSubmitting: state.meters.submitting.indexOf(ownProps.id) !== -1, + loggedInAsAdmin } } +function mapDispatchToProps(dispatch: Dispatch) { + return { + // editMeterDetails: (meter: UnitMetadata) => dispatch(editUnitDetails(unit)), + log: (level: string, message: string) => dispatch(logToServer(level, message)) + }; +} -export default connect(mapStateToProps)(UnitViewComponent); \ No newline at end of file +export default connect(mapStateToProps, mapDispatchToProps)(UnitViewComponent); \ No newline at end of file diff --git a/src/client/app/containers/unit/UnitsDetailContainer.tsx b/src/client/app/containers/unit/UnitsDetailContainer.tsx index 9c8b94580..01adb2a9d 100644 --- a/src/client/app/containers/unit/UnitsDetailContainer.tsx +++ b/src/client/app/containers/unit/UnitsDetailContainer.tsx @@ -6,27 +6,25 @@ import { connect } from 'react-redux'; import { State } from '../../types/redux/state'; import {Dispatch} from '../../types/redux/actions'; import { unitsApi } from '../../utils/api'; -import { fetchUnitsDetails } from 'actions/unit'; +import { fetchUnitsDetails } from '../../actions/unit'; import HeaderContainer from '../HeaderContainer'; import FooterContainer from '../FooterContainer'; -import { isRoleAdmin } from 'utils/hasPermissions'; +import { isRoleAdmin } from '../../utils/hasPermissions'; -function mapStateToProps(state: State, ownProps: { id: number}){ - let unit = JSON.parse(JSON.stringify(state.units.byUnitID[ownProps.id])); - if(state.units.editedUnits[ownProps.id]){ - unit = JSON.parse(JSON.stringify(state.units.editedUnits[ownProps.id])) - } - +function mapStateToProps(state: State){ const currentUser = state.currentUser.profile; let loggedInAsAdmin = false; if(currentUser !== null){ loggedInAsAdmin = isRoleAdmin(currentUser.role); } + return { - unit, - isEdited: state.units.editedUnits[ownProps.id] !== undefined, - loggedInAsAdmin + loggedInAsAdmin, + units: Object.keys(state.units.byUnitID) + .map(key => parseInt(key)) + .filter(key => !isNaN(key)), + unsavedChanges: Object.keys(state.units.editedUnits).length > 0 }; } diff --git a/src/client/app/reducers/index.ts b/src/client/app/reducers/index.ts index 17ec7d1bb..35475df33 100644 --- a/src/client/app/reducers/index.ts +++ b/src/client/app/reducers/index.ts @@ -15,6 +15,7 @@ import admin from './admin'; import version from './version'; import currentUser from './currentUser'; import unsavedWarning from './unsavedWarning'; +import units from './units'; export default combineReducers({ meters, @@ -30,5 +31,6 @@ export default combineReducers({ admin, version, currentUser, - unsavedWarning + unsavedWarning, + units }); diff --git a/src/client/app/reducers/units.ts b/src/client/app/reducers/units.ts new file mode 100644 index 000000000..5e412918b --- /dev/null +++ b/src/client/app/reducers/units.ts @@ -0,0 +1,32 @@ + import * as _ from 'lodash'; + import { UnitsAction, UnitState } from '../types/redux/unit'; + import { ActionType } from '../types/redux/actions'; + + const defaultState: UnitState = { + isLoading: false, + byUnitID: {}, + selectedUnits: [], + editedUnits: {}, + submitting: [] + }; + + export default function units(state = defaultState, action: UnitsAction) { + let submitting; + let editedUnits; + switch (action.type) { + case ActionType.RequestUnitsDetails: + return { + ...state, + isFetching: true + }; + case ActionType.ReceiveUnitsDetails: + return { + ...state, + isFetching: false, + byunitID: _.keyBy(action.data, unit => unit.id) + }; + default: + return state; + } + } + \ No newline at end of file diff --git a/src/client/app/types/redux/unit.ts b/src/client/app/types/redux/unit.ts index 942b9a76d..3565e1554 100644 --- a/src/client/app/types/redux/unit.ts +++ b/src/client/app/types/redux/unit.ts @@ -44,15 +44,16 @@ export interface UnitMetadata{ note?: string; } -interface UnitMetadataByID{ - [unitID: number]: UnitMetadata; +interface UnitDataByID{ + [unitID: number]: UnitData; } export interface UnitState { isLoading: boolean; - byUnitID: UnitMetadataByID; - selectedUnit: number; - editedUnits: UnitMetadataByID; + byUnitID: UnitDataByID; + selectedUnits: number[]; + editedUnits: UnitDataByID; + submitting: number[]; } export type UnitsAction = From 0c9eb3bab47236967cd9acd88f7c30aef52c4694 Mon Sep 17 00:00:00 2001 From: Justin Le Date: Fri, 11 Mar 2022 09:02:12 -0800 Subject: [PATCH 008/133] added header and footer to UnitDetailComponent --- src/client/app/components/unit/UnitsDetailComponent.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/client/app/components/unit/UnitsDetailComponent.tsx b/src/client/app/components/unit/UnitsDetailComponent.tsx index 11af4b217..57e1e47bd 100644 --- a/src/client/app/components/unit/UnitsDetailComponent.tsx +++ b/src/client/app/components/unit/UnitsDetailComponent.tsx @@ -3,6 +3,8 @@ import { Table, Button } from 'reactstrap'; import { UnitData } from '../../types/redux/unit' import { FormattedMessage } from 'react-intl'; import {Link} from 'react-router-dom'; +import HeaderContainer from '../../containers/HeaderContainer'; +import FooterContainer from '../../containers/FooterContainer'; import UnitViewContainer from '../../containers/unit/UnitViewContainer'; import UnsavedWarningContainer from '../../containers/UnsavedWarningContainer'; @@ -48,6 +50,7 @@ export default class UnitsDetailContainer extends React.Component +

@@ -89,6 +92,7 @@ export default class UnitsDetailContainer extends React.Component

+ ); } From 722107497be6d028a7b00f7e12601f7e1eba24f2 Mon Sep 17 00:00:00 2001 From: Sagar Prasad Date: Fri, 11 Mar 2022 12:22:07 -0800 Subject: [PATCH 009/133] testing --- .../containers/unit/UnitsDetailContainer.tsx | 2 +- src/server/routes/unit.js | 51 ++++++++++--------- 2 files changed, 27 insertions(+), 26 deletions(-) diff --git a/src/client/app/containers/unit/UnitsDetailContainer.tsx b/src/client/app/containers/unit/UnitsDetailContainer.tsx index 01adb2a9d..82888906f 100644 --- a/src/client/app/containers/unit/UnitsDetailContainer.tsx +++ b/src/client/app/containers/unit/UnitsDetailContainer.tsx @@ -31,7 +31,7 @@ function mapStateToProps(state: State){ function mapDispatchToProps(dispatch: Dispatch){ return { fetchUnitsDetails: () => dispatch(fetchUnitsDetails()) - } + }; } export default connect(mapStateToProps, mapDispatchToProps)(UnitsDetailComponent); diff --git a/src/server/routes/unit.js b/src/server/routes/unit.js index c42ec16e5..da699cb05 100644 --- a/src/server/routes/unit.js +++ b/src/server/routes/unit.js @@ -33,31 +33,32 @@ router.get('/', async(req,res) => { query = Unit.getAll; const rows = await query(conn); - res.json([{ - id: 1, - name: "unit.name", - identifier: "unit.identifier", - unitRepresent: "unit.unitRepresent", - secInRate: 2, - typeOfUnit: "unit.typeOfUnit", - unitIndex: 3, - suffix: "unit.suffix", - displayable: "true", - preferredDisplay: false, - note: "unit.note" - }, { - id: 4, - name: "unit.name", - identifier: "unit.identifier", - unitRepresent: "unit.unitRepresent", - secInRate: 5, - typeOfUnit: "unit.typeOfUnit", - unitIndex: 6, - suffix: "unit.suffix", - displayable: "true", - preferredDisplay: false, - note: "unit.note" - }]); + // res.json([{ + // id: 1, + // name: "unit.name", + // identifier: "unit.identifier", + // unitRepresent: "unit.unitRepresent", + // secInRate: 2, + // typeOfUnit: "unit.typeOfUnit", + // unitIndex: 3, + // suffix: "unit.suffix", + // displayable: "true", + // preferredDisplay: false, + // note: "unit.note" + // }, { + // id: 4, + // name: "unit.name", + // identifier: "unit.identifier", + // unitRepresent: "unit.unitRepresent", + // secInRate: 5, + // typeOfUnit: "unit.typeOfUnit", + // unitIndex: 6, + // suffix: "unit.suffix", + // displayable: "true", + // preferredDisplay: false, + // note: "unit.note" + // }]); + res.json(rows.map(row => formatUnitForResponse(row))); console.log(rows); }catch(err){ log.error(`Error while performing GET all units query: ${err}`, err); From 3a9d9724c0bbeb8a6022818b46d59ecc2d65527a Mon Sep 17 00:00:00 2001 From: Sagar Prasad Date: Fri, 11 Mar 2022 13:10:16 -0800 Subject: [PATCH 010/133] IDK --- src/client/app/actions/unit.ts | 3 ++- src/client/app/components/unit/UnitsDetailComponent.tsx | 3 --- src/client/app/containers/unit/UnitViewContainer.tsx | 2 +- src/client/app/reducers/units.ts | 2 +- src/client/app/types/redux/unit.ts | 3 ++- 5 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/client/app/actions/unit.ts b/src/client/app/actions/unit.ts index 9650643f1..a65758302 100644 --- a/src/client/app/actions/unit.ts +++ b/src/client/app/actions/unit.ts @@ -1,12 +1,13 @@ import {ActionType, Dispatch, Thunk} from '../types/redux/actions'; import * as t from '../types/redux/unit' import {unitsApi} from '../utils/api'; +import { NamedIDItem } from '../types/items'; function requestUnitsDetails(): t.RequestUnitsDetailsAction { return { type: ActionType.RequestUnitsDetails} } -function receiveUnitsDetails(data: t.UnitData[]): t.ReceiveUnitsDetailsAction{ +function receiveUnitsDetails(data: NamedIDItem[]): t.ReceiveUnitsDetailsAction{ return {type: ActionType.ReceiveUnitsDetails, data} } diff --git a/src/client/app/components/unit/UnitsDetailComponent.tsx b/src/client/app/components/unit/UnitsDetailComponent.tsx index 57e1e47bd..1a5360177 100644 --- a/src/client/app/components/unit/UnitsDetailComponent.tsx +++ b/src/client/app/components/unit/UnitsDetailComponent.tsx @@ -78,9 +78,6 @@ export default class UnitsDetailContainer extends React.Component ))} - {/* - Need to implement addUnit route later - */}