@@ -410,7 +410,6 @@ |
def update_users(self): |
request_json = er2_api.load_json(self._request.body) |
updated_users = request_json["updatedUsers"] |
- logger.info("Inside of Update_Users in services.py, request body: {}".format(updated_users)) |
|
try: |
conn = self.get_connection() |
@@ -419,7 +418,7 @@ |
# For each updated user coming in, we will update their role, state and status |
for updated_user_id in updated_users: |
updated_user_role = updated_users[updated_user_id]["role"] |
- updated_user_state = updated_users[updated_user_id]["state"] |
+ updated_user_states = updated_users[updated_user_id]["states"] |
updated_user_status = updated_users[updated_user_id]["status"] |
|
# With updated_user: id, role, state and status, we will update the related info in the participant table |
@@ -429,8 +428,6 @@ |
SET |
fk_d_participant_type_id = |
(SELECT id FROM d_participant_type WHERE participant_type = %s), |
- fk_d_state_id = |
- (SELECT id FROM d_state WHERE state_abbreviation = %s), |
fk_d_participant_status = |
(SELECT id FROM d_participant_status WHERE status = %s) |
WHERE |
@@ -438,11 +435,22 @@ |
|
cursor.execute(update_participant_query, ( |
updated_user_role, |
- updated_user_state, |
updated_user_status, |
updated_user_id |
)) |
|
+ delete_participant_states_query = f""" |
+ DELETE FROM participant_state WHERE fk_participant_id = '{updated_user_id}'""" |
+ |
+ cursor.execute(delete_participant_states_query) |
+ |
+ |
+ for state in updated_user_states: |
+ insert_state_query = f"""INSERT INTO participant_state(fk_participant_id, fk_state_id) |
+ VALUES (%s, (SELECT id FROM d_state WHERE state_abbreviation = %s))""" |
+ cursor.execute(insert_state_query, (updated_user_id, state)) |
+ |
+ |
except Exception as e: |
logger.error(e) |
|
@@ -1225,20 +1233,40 @@ |
fname, |
email, |
participant_type, |
- d_state.state_name, |
- d_state.state_abbreviation, |
status |
FROM [participant] |
LEFT JOIN d_participant_type |
ON participant.fk_d_participant_type_id = d_participant_type.id |
- LEFT JOIN d_state |
- ON participant.fk_d_state_id = d_state.id |
LEFT JOIN d_participant_status |
ON fk_d_participant_status = d_participant_status.id {}""".format(where_clause) |
|
cursor.execute(query) |
users = cursor.fetchall() |
|
+ user_state_query = f""" |
+ SELECT fk_participant_id as user_id, state_abbreviation |
+ FROM participant_state |
+ left join |
+ participant ON participant_state.fk_participant_id = participant.id |
+ left join |
+ d_state ON participant_state.fk_state_id = d_state.id |
+ WHERE state_abbreviation != '' |
+ """ |
+ |
+ cursor.execute(user_state_query) |
+ user_states = cursor.fetchall() |
+ logger.info(json.dumps(user_states)) |
+ |
+ for u_s_entry in user_states: |
+ user = next((user for user in users if user["id"] == u_s_entry['user_id']), None) |
+ if 'states' in user: |
+ user['states'].append(u_s_entry['state_abbreviation']) |
+ else: |
+ user['states'] = [u_s_entry['state_abbreviation']] |
+ |
+ logger.info(user) |
+ |
+ |
return er2_api.respond({'users': users}) |
except Exception as e: |
logger.info(e) |
@@ -1281,8 +1309,8 @@ |
user = {} |
|
if len(rows) == 0: |
- insert_query = f"""INSERT INTO participant (id, lname, fname, email, fk_d_participant_status, fk_d_participant_type_id, fk_d_state_id) |
- VALUES('{user_token}', '{user_lName}', '{user_fName}', '{user_email}', 1, 1, 53)""" |
+ insert_query = f"""INSERT INTO participant (id, lname, fname, email, fk_d_participant_status, fk_d_participant_type_id) |
+ VALUES('{user_token}', '{user_lName}', '{user_fName}', '{user_email}', 1, 1)""" |
logger.info(insert_query) |
cursor.execute(insert_query) |
user = {'id': user_token, 'firstName': user_fName, |
@@ -1,5 +1,5 @@ |
import * as types from '../constants/action_types' |
-import { RoleType, StateType, StatusType } from '../reducers/users' |
+import { RoleType, StatusType } from '../reducers/users' |
|
export const setFetchingUsers = fetchingUsers => ({ type: types.SET_FETCHING_USERS, payload: fetchingUsers }) |
export const setFetchingUserData = fetchingUserData => ({ type: types.SET_FETCHING_USER_DATA, payload: fetchingUserData }) |
@@ -35,12 +35,12 @@ |
return response.json() |
}).then((result) => { |
if (fetchError) { |
- console.log('Something went wrong when attempting to fetch user role list') |
+ console.error('Something went wrong when attempting to fetch user role list') |
} else { |
const userRoles: RoleType[] = result.participant_types.map(pt => ({ id: pt.id, role: pt.participant_type })) |
dispatch({ type: types.SET_USER_ROLES, payload: { userRoles } }) |
} |
- }).catch(ret => console.log(ret.message, ret.stack)) |
+ }).catch(ret => console.error(ret.message, ret.stack)) |
} |
|
export const getUsersFromDB = () => (dispatch, getState) => { |
@@ -60,7 +60,7 @@ |
}) |
.then((result) => { |
if (fetchError) { |
- console.log('Something went wrong when attempting to fetch users') |
+ console.error('Something went wrong when attempting to fetch users') |
} else { |
const users = result.users.map(user => { |
return { |
@@ -69,15 +69,14 @@ |
lastName: user.lname, |
email: user.email, |
role: user.participant_type, |
- state: user.state_abbreviation, |
+ states: user.states, |
status: user.status, |
} |
}) |
- |
dispatch(setUsers(users)) |
} |
}) |
- .catch(ret => console.log(ret.message, ret.stack)) |
+ .catch(ret => console.error(ret.message, ret.stack)) |
} |
|
export const getUserDataFromDB = idTokenParsed => (dispatch, getState) => { |
@@ -103,19 +102,18 @@ |
}) |
.then((result) => { |
if (fetchError) { |
- console.log('Something went wrong when attempting to fetch user data') |
+ console.error('Something went wrong when attempting to fetch user data') |
} else { |
const userData = result.user |
dispatch(setUserData(userData)) |
dispatch(setFetchingUserData(false)) |
} |
}) |
- .catch(ret => console.log(ret.message, ret.stack)) |
+ .catch(ret => console.error(ret.message, ret.stack)) |
} |
|
|
export const getStatesFromDB = () => (dispatch, getState) => { |
- console.log('getStatesFromDB') |
let fetchError |
fetch( |
`/${getState().meta.name}/api/v1/get_states/?token=${ |
@@ -129,17 +127,17 @@ |
return response.json() |
}).then((result) => { |
if (fetchError) { |
- console.log('Something went wrong when attempting to fetch states list') |
+ console.error('Something went wrong when attempting to fetch states list') |
} else { |
- const states: StateType[] = result.states.map(state => ({ id: state.id, name: state.state_name, abbreviation: state.state_abbreviation })) |
+ const states: string[] = result.states.map(state => (state.state_abbreviation)) |
dispatch({ type: types.SET_STATES, payload: { states } }) |
} |
- }).catch(ret => console.log(ret.message, ret.stack)) |
+ }).catch(ret => console.error(ret.message, ret.stack)) |
} |
|
|
export const getStatusFromDB = () => (dispatch, getState) => { |
- console.log('getStatusFromDB') |
+ console.debug('getStatusFromDB') |
let fetchError |
fetch( |
`/${getState().meta.name}/api/v1/get_status/?token=${ |
@@ -153,16 +151,16 @@ |
return response.json() |
}).then((result) => { |
if (fetchError) { |
- console.log('Something went wrong when attempting to fetch statuses list') |
+ console.error('Something went wrong when attempting to fetch statuses list') |
} else { |
const status: StatusType[] = result.status.map(status => ({ id: status.id, status: status.status })) |
dispatch({ type: types.SET_STATUS, payload: { status } }) |
} |
- }).catch(ret => console.log(ret.message, ret.stack)) |
+ }).catch(ret => console.error(ret.message, ret.stack)) |
} |
|
export const saveUsers = updatedUsers => (dispatch, getState) => { |
- console.log('save updated users info in actions ====>', updatedUsers) |
+ console.debug('save updated users info in actions ====>', updatedUsers) |
|
let fetchError |
fetch( |
@@ -182,11 +180,11 @@ |
}) |
.then((result) => { |
if (fetchError) { |
- console.log('Something went wrong when updating users in db') |
+ console.error('Something went wrong when updating users in db') |
} else { |
- console.log('Successfully updated users info in database') |
+ console.debug('Successfully updated users info in database') |
} |
}) |
- .catch(ret => console.log(ret.message, ret.stack)) |
+ .catch(ret => console.error(ret.message, ret.stack)) |
} |
|
@@ -1,7 +1,7 @@ |
/* eslint-disable no-use-before-define */ |
// @flow |
import React, {useState, useEffect} from "react" |
-import { useSelector, connect } from 'react-redux' |
+import { useSelector, connect, useDispatch } from 'react-redux' |
import { |
FormControl, |
MenuItem, |
@@ -20,10 +20,14 @@ |
} from '../actions' |
import Autocomplete from '@material-ui/lab/Autocomplete/Autocomplete' |
import { makeStyles } from "@material-ui/core/styles" |
+import WindowState, { windowStates } from '@owsi/catena/er2_map_userlayers/js/components/window_state' |
+import { setTabbedPanelWindowState } from '@owsi/catena/er2_map_userlayers/js/actions/tabbed_panel' |
import { primaryDarkColor, contrastTextColor, gray } from "../styles/colors.js" |
+import { RootStateType } from '../types' |
|
interface Props { |
- saveUsers: Function |
+ saveUsers: Function, |
+ height: number |
} |
|
const useTableStyles = makeStyles({ |
@@ -38,13 +42,6 @@ |
fontSize: "18px", |
backgroundColor: primaryDarkColor, |
color: contrastTextColor, |
- // "&.MuiTableSortLabel-root": { |
- // padding: "0px 0px 0px 15px", |
- // color: contrastTextColor, |
- // "& .MuiTableSortLabel-icon": { |
- // color: contrastTextColor, |
- // }, |
- // }, |
}, |
tableRow: { |
"&:hover": { |
@@ -67,17 +64,20 @@ |
const spinnerClasses = useSpinnerStyles() |
|
const [usersUpdated, setUsersUpdated] = React.useState({}) |
- const users = useSelector(state => state.users.users) |
- const states = useSelector(state => state.users.states) |
- const statuses = useSelector(state => state.users.status) |
- const roles = useSelector(state => state.users.userRoles) |
+ const users = useSelector((state: RootStateType) => state.users.users) |
+ const states = useSelector((state: RootStateType) => state.users.states) |
+ const statuses = useSelector((state: RootStateType) => state.users.status) |
+ const roles = useSelector((state: RootStateType) => state.users.userRoles) |
+ const tabbedPanel = useSelector((state: RootStateType) => state.tabbedPanel) |
|
const [sortBy, setSortBy] = React.useState("firstName") |
- const [sortOrder, setSortOrder] = React.useState("asc") |
+ const [sortOrder, setSortOrder] = React.useState<'asc' | 'desc'>("asc") |
const [usersSorted, setUsersSorted] = React.useState([]) |
const [usersFiltered, setUsersFiltered] = React.useState([]) |
const [usersFilterText, setUsersFilterText] = React.useState("") |
|
+ const dispatch = useDispatch() |
+ |
// Map through users info to create component-level state that is in the form: id: {firstName: '', lastName: '', role: '', state: '', status: ''} |
React.useEffect(() => { |
// If users info is stored as array of objects (necessary in order to sort table): |
@@ -95,6 +95,12 @@ |
}, [users, sortBy, sortOrder]) |
|
React.useEffect(() => { |
+ if(tabbedPanel.windowState !== windowStates.maximized){ |
+ dispatch(setTabbedPanelWindowState(windowStates.maximized)) |
+ } |
+ }, []) |
+ |
+ React.useEffect(() => { |
// if the project filter text is empty, we just want to display all, sorted users; otherwise, filter users based on text entry (for firstName, lastName or role) |
if (usersFilterText !== "") { |
const filteredUsers = usersSorted.filter( |
@@ -131,11 +137,11 @@ |
}, [usersUpdated]) |
|
const headers = [ |
- { text: "First Name", sortable: true, key: "firstName" }, |
- { text: "Last Name", sortable: true, key: "lastName" }, |
- { text: "Role", sortable: true, key: "role" }, |
- { text: "State", sortable: true, key: "state" }, |
- { text: "Status", sortable: true, key: "status" }, |
+ { text: "First Name", sortable: true, key: "firstName", width: '15%' }, |
+ { text: "Last Name", sortable: true, key: "lastName", width: '15%' }, |
+ { text: "Role", sortable: true, key: "role", width: '15%' }, |
+ { text: "State(s)", sortable: true, key: "state", width: '40%' }, |
+ { text: "Status", sortable: true, key: "status", width: '15%' }, |
] |
|
const handleChange = (event, userID, dropdownType) => { |
@@ -146,7 +152,16 @@ |
setUsersSorted(copyOfSortedUsers) |
setUsersFiltered(copyOfSortedUsers) |
setUsersUpdated({...usersUpdated, [userID]: copyOfSortedUsers[usersInfoIndex]}) |
- }; |
+ } |
+ |
+ const handleStateChange = (userID, newStates) => { |
+ let usersInfoIndex = usersSorted.findIndex(user => user.id === userID) |
+ let copyOfSortedUsers = usersSorted.slice() |
+ copyOfSortedUsers[usersInfoIndex]['states'] = newStates |
+ setUsersSorted(copyOfSortedUsers) |
+ setUsersFiltered(copyOfSortedUsers) |
+ setUsersUpdated({...usersUpdated, [userID]: copyOfSortedUsers[usersInfoIndex]}) |
+ } |
|
const onChangeFilterUsers = (evt) => { |
setUsersFilterText(evt.target.value) |
@@ -229,12 +244,8 @@ |
|
const displayTable = () => { |
let usersDisplayed = usersFilterText === "" ? usersSorted : usersFiltered |
- |
return ( |
- <div> |
- <h2> |
- Click on the respective cell to update a user's: Role, State or Status. |
- </h2> |
+ <div style={{height: props.height}}> |
<div key="toolBar" className={'filterUsers'}> |
<TextField |
label="Filter Users" |
@@ -249,12 +260,13 @@ |
size="small" |
/> |
</div> |
- <TableContainer style={{ maxHeight: 800 }}> |
+ <TableContainer> |
<Table id="ManageUsers" stickyHeader> |
<TableHead> |
<TableRow> |
{headers.map((header) => ( |
<TableCell |
+ style={{width: header.width}} |
key={"header-" + header.text} |
className={tableClasses.headerCell} |
onClick={() => |
@@ -266,15 +278,6 @@ |
padding: "0px 0px 0px 15px", |
color: contrastTextColor, |
}} |
- // Below styling works in this sandbox: https://codesandbox.io/s/enhancedtable-material-demo-forked-3go1p?file=/demo.js to make the active arrow a specific color... however, doesn't work here... IDK |
- // sx={{ |
- // "&.MuiTableSortLabel-root": { |
- // color: contrastTextColor, |
- // "& .MuiTableSortLabel-icon": { |
- // color: contrastTextColor, |
- // }, |
- // }, |
- // }} |
active={sortBy === header.key} |
direction={sortOrder} |
onClick={() => |
@@ -322,13 +325,13 @@ |
|
{/* State */} |
<TableCell className={tableClasses.bodyCell}> |
- <FormControl style={{ minWidth: '100px' }} > |
+ <FormControl style={{ width: '100%' }} > |
<Autocomplete |
- value={user.state} |
+ multiple |
+ value={user.states} |
id="state-name-select" |
options={states} |
- onChange={(e, newValue) => handleChange(newValue.abbreviation, user.id, 'state')} |
- getOptionSelected={(o, value) => o.abbreviation === value} |
+ onChange={(e, newStates) => handleStateChange(user.id, newStates)} |
ChipProps={{ |
size: 'small', |
}} |
@@ -340,7 +343,9 @@ |
} |
}} |
renderInput={(params) => |
- <TextField {...params}/> |
+ <TextField |
+ {...params} |
+ /> |
} |
/> |
</FormControl> |
@@ -374,15 +379,10 @@ |
|
const displayLoading = () => { |
return ( |
- <div> |
- <h2> |
- To update a user's: role, status or state click on their respective cell and select from the dropdown. |
- </h2> |
<div className={spinnerClasses.center}> |
<i className="fa fa-cog fa-spin fa-3x fa-fw" /> |
<span className="sr-only">Loading...</span> |
</div> |
- </div> |
) |
} |
|
@@ -55,6 +55,7 @@ |
faFilePdf, faFilter, faBars, faTrash, faEdit, faCog, faCogs, faDownload, faUsers, |
} from '@fortawesome/free-solid-svg-icons' |
import { setFetchingUserData } from '../actions' |
+import ManageUsersCB from '../components/ManageUsersCB' |
|
library.add(faFilePdf, faFilter, faBars, faTrash, faEdit, faCog, faCogs, faDownload, faUsers) |
|
@@ -210,6 +211,9 @@ |
indicator: { |
backgroundColor: '#CAFE48', |
}, |
+ root: { |
+ paddingLeft: 60, |
+ }, |
}, |
MuiTab: { |
root: { |
@@ -248,14 +252,21 @@ |
const activeMapToolbarPanel = theprops.mapToolbar.items.filter( |
item => item.active, |
) |
- const isMapToolbarVisible = activeMapToolbarPanel.length > 0 |
+ |
const mapVisible = [ |
routes.MANAGE_PROJECTS, |
routes.SETTINGS, |
].some(route => parsedLoc.path.startsWith(route)) |
+ const usersVisible = location.pathname === routes.USERS |
+ let isMapToolbarVisible = activeMapToolbarPanel.length > 0 |
+ if(usersVisible){ |
+ isMapToolbarVisible = undefined |
+ } |
|
- const tocVisible = location.pathname === routes.MANAGE_PROJECTS |
- || location.pathname === routes.SETTINGS |
+ const tocVisible = location.pathname === routes.MANAGE_PROJECTS || |
+ location.pathname === routes.USERS || |
+ location.pathname === routes.SETTINGS |
+ |
|
const dispatch = useDispatch() |
const { token, actions } = theprops |
@@ -329,6 +340,19 @@ |
}, [theprops.serverState]) |
|
useEffect(() => { |
+ if(location.pathname === routes.USERS){ |
+ if(theprops.tabbedPanel.windowState !== windowStates.maximized){ |
+ theprops.actions.setTabbedPanelWindowState(windowStates.maximized) |
+ } |
+ } |
+ if(location.pathname === routes.MANAGE_PROJECTS){ |
+ if(theprops.tabbedPanel.windowState !== windowStates.opened){ |
+ theprops.actions.setTabbedPanelWindowState(windowStates.opened) |
+ } |
+ } |
+ }, [location.pathname]) |
+ |
+ useEffect(() => { |
theprops.actions.getUsersFromDB() |
theprops.actions.setDefaultBaseLayer('USGS Imagery Topo') |
}, []) |
@@ -553,20 +577,7 @@ |
</div> |
) |
} |
- /* Don't do this for now since the token won't be available when the user isn't logged in. |
- if (!theprops.token.value) { |
- // Render loading page until the server state is available |
- return ( |
- <div className={classes.root}> |
- {renderHeader()} |
- <div className={classes.loading}> |
- <i className="fa fa-cog fa-spin fa-3x fa-fw" /> |
- <span className="sr-only">Loading...</span> |
- </div> |
- </div> |
- ) |
- } |
-*/ |
+ |
const renderCatenaMap = () => ( |
<CatenaMap |
analysisPanelClosed={theprops.tabbedPanel.windowState === windowStates.minimized} |
@@ -623,6 +634,7 @@ |
) |
|
const renderTabbedPanel = () => { |
+ console.log({isMapToolbarVisible}) |
const selectedProjects = theprops.manageProjects.projects.filter(p => p.selected === true) |
return ( |
<React.Fragment> |
@@ -634,7 +646,7 @@ |
type="south" |
windowState={theprops.tabbedPanel.windowState} |
/> |
- {theprops.tabbedPanel.windowState !== windowStates.minimized && ( |
+ {theprops.tabbedPanel.windowState !== windowStates.minimized && parsedLoc.path === routes.MANAGE_PROJECTS && ( |
<TabbedPanel |
heightOffset={theprops.theme.analysisPanelOffset} |
maptoolbarVisible={isMapToolbarVisible} |
@@ -648,11 +660,24 @@ |
<MonitoringStationSP |
key="Monitoring Stations" |
classes={theprops.classes} |
- project={selectedProjects[0]} |
- /> |
+ project={selectedProjects[0]} /> |
<DocumentsSP key="Documents" project={selectedProjects[0]} /> |
</TabbedPanel> |
)} |
+ {theprops.tabbedPanel.windowState !== windowStates.minimized && parsedLoc.path === routes.USERS && ( |
+ <TabbedPanel |
+ heightOffset={theprops.theme.analysisPanelOffset} |
+ maptoolbarVisible={isMapToolbarVisible} |
+ onResize={theprops.actions.onResizeAnalysisPanel} |
+ tocVisible={tocVisible} |
+ tabbedPanel={theprops.tabbedPanel} |
+ theme={theprops.classes} |
+ onSetTabbedPanelKey={theprops.actions.onSetTabbedPanelKey} |
+ > |
+ <ManageUsers key="Users"/> |
+ |
+ </TabbedPanel> |
+ )} |
</React.Fragment> |
) |
} |
@@ -673,11 +698,8 @@ |
const renderManageUsersCB = () => ( |
<Route |
path={routes.USERS} |
- render={() => ( |
- <ManageUsers |
- // serverState={theprops.serverState} |
- // theme={theprops.classes} |
- /> |
+ render={() => ( |
+ <ManageUsersCB theme={theprops.classes} /> |
)} |
/> |
) |
@@ -705,12 +727,12 @@ |
|
const renderContextBarItems = () => { |
const isManageProjectsVisible = location.pathname === routes.MANAGE_PROJECTS && catenaMap.current |
- |
const isManageUsersVisible = location.pathname === routes.USERS |
|
if (isManageProjectsVisible) { |
return renderManageProjectsCB() |
- } else if (isManageUsersVisible) { |
+ } |
+ if(isManageUsersVisible) { |
return renderManageUsersCB() |
} |
|
@@ -722,22 +744,6 @@ |
<Redirect to={routes.INSTRUCTIONS} />) |
} |
|
- /* Don't do this for now since the token will be undefined if the user isn't logged in. |
- if (!theprops.token.value) { |
- // Render loading page until the server state is available |
- return ( |
- <div className={theprops.classes.root}> |
- {renderHeader()} |
- {renderContextBar()} |
- <div className={theprops.classes.loading}> |
- <i className="fa fa-cog fa-spin fa-3x fa-fw" /> |
- <span className="sr-only">Loading...</span> |
- </div> |
- </div> |
- ) |
- } |
- */ |
- |
const theMuiTheme = customMuiTheme({ colors, overrides }) |
|
return ( |
@@ -745,18 +751,17 @@ |
<div className={classes.root}> |
{renderHeader()} |
{renderContextBar()} |
+ {/* Rendering the Settings */} |
+ {renderSettings()} |
{renderBreadcrumbs(theprops.classes)} |
{renderInstructions()} |
{/* Rendering the Maps and the tabbed panels */} |
{mapVisible && renderCatenaMap()} |
{mapVisible && catenaMap.current && renderUserContainerLayer()} |
- {mapVisible && renderTabbedPanel()} |
- |
{/* Rendering the Context Bar Items */} |
{renderContextBarItems()} |
+ {(mapVisible || usersVisible) && renderTabbedPanel()} |
|
- {/* Rendering the Settings */} |
- {renderSettings()} |
</div> |
</MuiThemeProvider> |
) |