diff --git a/src/App.jsx b/src/App.jsx index bc3e8cb..2322788 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -29,7 +29,8 @@ const FAQ = React.lazy(() => import(/* webpackChunkName: 'FAQ' */ './js/pages/FA const Footer = React.lazy(() => import(/* webpackChunkName: 'Footer' */ './js/components/Navigation/Footer')); const Header = React.lazy(() => import(/* webpackChunkName: 'Header' */ './js/components/Navigation/Header')); const PageNotFound = React.lazy(() => import(/* webpackChunkName: 'PageNotFound' */ './js/pages/PageNotFound')); -const TeamMembers = React.lazy(() => import(/* webpackChunkName: 'FAQ' */ './js/pages/TeamMembers')); +const TeamMembers = React.lazy(() => import(/* webpackChunkName: 'TeamMembers' */ './js/pages/TeamMembers')); +const Teams = React.lazy(() => import(/* webpackChunkName: 'Teams' */ './js/pages/Teams')); // There are just too many "prop spreadings" in the use of Route, if someone can figure out an alternative... /* eslint-disable react/jsx-props-no-spreading */ @@ -275,7 +276,9 @@ class App extends Component { + + diff --git a/src/js/actions/PersonActions.js b/src/js/actions/PersonActions.js index 319118f..95fe672 100644 --- a/src/js/actions/PersonActions.js +++ b/src/js/actions/PersonActions.js @@ -1,6 +1,17 @@ import Dispatcher from '../common/dispatcher/Dispatcher'; export default { + personListRetrieve (searchText = '') { + // console.log('PersonActions, personListRetrieve searchText:', searchText); + if (searchText) { + Dispatcher.loadEndpoint('person-list-retrieve', { + searchText, + }); + } else { + Dispatcher.loadEndpoint('person-list-retrieve'); + } + }, + personRetrieve (personId = '') { // console.log('PersonActions, personRetrieve personId:', personId); if (personId) { @@ -11,4 +22,13 @@ export default { Dispatcher.loadEndpoint('person-retrieve'); } }, + + personSave (personId = '', incomingData = {}) { + // console.log('PersonActions, personSave personId:', personId, ', incomingData:', incomingData); + const data = { + personId, + ...incomingData, + }; + Dispatcher.loadEndpoint('person-save', data); + }, }; diff --git a/src/js/actions/TeamActions.js b/src/js/actions/TeamActions.js index ddbadbf..c04673c 100644 --- a/src/js/actions/TeamActions.js +++ b/src/js/actions/TeamActions.js @@ -1,6 +1,20 @@ import Dispatcher from '../common/dispatcher/Dispatcher'; +import PersonStore from '../stores/PersonStore'; // eslint-disable-line import/no-cycle +import TeamStore from '../stores/TeamStore'; // eslint-disable-line import/no-cycle export default { + addPersonToTeam (personId, teamId) { + console.log('TeamActions, addPersonToTeam personId:', personId, ', teamId:', teamId); + const teamMemberName = PersonStore.getFullNamePreferred(personId) || ''; + const data = { + personId, + teamId, + teamMemberName, + teamName: TeamStore.getTeamById(teamId).teamName || '', + }; + Dispatcher.loadEndpoint('add-person-to-team', data); + }, + teamRetrieve (teamId = '') { // console.log('TeamActions, teamRetrieve teamId:', teamId); if (teamId) { @@ -11,4 +25,18 @@ export default { Dispatcher.loadEndpoint('team-retrieve'); } }, + + teamListRetrieve () { + // console.log('TeamActions, teamListRetrieve'); + Dispatcher.loadEndpoint('team-list-retrieve'); + }, + + teamSave (teamId = '', incomingData = {}) { + // console.log('PersonActions, teamSave teamId:', teamId, ', incomingData:', incomingData); + const data = { + teamId, + ...incomingData, + }; + Dispatcher.loadEndpoint('team-save', data); + }, }; diff --git a/src/js/common/utils/prepareDataPackageFromAppObservableStore.js b/src/js/common/utils/prepareDataPackageFromAppObservableStore.js new file mode 100644 index 0000000..a354106 --- /dev/null +++ b/src/js/common/utils/prepareDataPackageFromAppObservableStore.js @@ -0,0 +1,19 @@ +import AppObservableStore from '../../stores/AppObservableStore'; + +export default function prepareDataPackageFromAppObservableStore (acceptedVariables) { + // Extract data from AppObservableStore and put into data object, with Changed variable, to be sent to server + const data = {}; + for (let i = 0; i < acceptedVariables.length; i++) { + // Ex/ emailPersonalToBeSaved and emailPersonalChanged + // console.log(`prepareData: ${acceptedVariables[i]}ToBeSaved:`, AppObservableStore.getGlobalVariableState(`${acceptedVariables[i]}ToBeSaved`)); + if (AppObservableStore.getGlobalVariableState(`${acceptedVariables[i]}ToBeSaved`) && + AppObservableStore.getGlobalVariableState(`${acceptedVariables[i]}Changed`)) { + // If the value has changed, add it to the data dictionary + if (AppObservableStore.getGlobalVariableState(`${acceptedVariables[i]}Changed`) === true) { + data[`${acceptedVariables[i]}Changed`] = true; + data[`${acceptedVariables[i]}ToBeSaved`] = AppObservableStore.getGlobalVariableState(`${acceptedVariables[i]}ToBeSaved`); + } + } + } + return data; +} diff --git a/src/js/components/AddPerson/AddPersonForm.jsx b/src/js/components/AddPerson/AddPersonForm.jsx new file mode 100644 index 0000000..d70a5f6 --- /dev/null +++ b/src/js/components/AddPerson/AddPersonForm.jsx @@ -0,0 +1,173 @@ +import { Button, FormControl, TextField } from '@mui/material'; +import { withStyles } from '@mui/styles'; +import React from 'react'; +import styled from 'styled-components'; +import PropTypes from 'prop-types'; +import AppObservableStore, { messageService } from '../../stores/AppObservableStore'; +import PersonActions from '../../actions/PersonActions'; +import PersonStore from '../../stores/PersonStore'; +import { renderLog } from '../../common/utils/logging'; +import prepareDataPackageFromAppObservableStore from '../../common/utils/prepareDataPackageFromAppObservableStore'; + + +const AddPersonForm = ({ classes }) => { // classes, teamId + renderLog('AddPersonForm'); // Set LOG_RENDER_EVENTS to log all renders + const [emailPersonal, setEmailPersonal] = React.useState(''); + const [firstName, setFirstName] = React.useState(''); + const [lastName, setLastName] = React.useState(''); + const [teamId, setTeamId] = React.useState(-1); + + const onAppObservableStoreChange = () => { + setTeamId(AppObservableStore.getGlobalVariableState('addPersonDrawerTeamId')); + }; + + const saveNewPersonSuccessful = () => { + AppObservableStore.setGlobalVariableState('addPersonDrawerOpen', false); + AppObservableStore.setGlobalVariableState('addPersonDrawerTeamId', -1); + AppObservableStore.setGlobalVariableState('emailPersonalChanged', false); + AppObservableStore.setGlobalVariableState('emailPersonalToBeSaved', ''); + AppObservableStore.setGlobalVariableState('firstNameChanged', false); + AppObservableStore.setGlobalVariableState('firstNameToBeSaved', ''); + AppObservableStore.setGlobalVariableState('lastNameChanged', false); + AppObservableStore.setGlobalVariableState('lastNameToBeSaved', ''); + }; + + const onPersonStoreChange = () => { + const mostRecentPersonChanged = PersonStore.getMostRecentPersonChanged(); + // console.log('AddPersonForm onPersonStoreChange mostRecentPersonChanged:', mostRecentPersonChanged); + // TODO: Figure out why firstName, lastName, and emailPersonal are not being updated + // console.log('firstName:', firstName, ', lastName:', lastName, ', emailPersonal:', emailPersonal); + // console.log('emailPersonalToBeSaved:', AppObservableStore.getGlobalVariableState('emailPersonalToBeSaved')); + if (mostRecentPersonChanged.emailPersonal === AppObservableStore.getGlobalVariableState('emailPersonalToBeSaved')) { + saveNewPersonSuccessful(); + } + }; + + const saveNewPerson = () => { + const acceptedVariables = ['emailPersonal', 'firstName', 'lastName']; + const data = prepareDataPackageFromAppObservableStore(acceptedVariables); + // console.log('saveNewPerson data:', data); + PersonActions.personSave('-1', data); + }; + + const updateEmailPersonal = (event) => { + if (event.target.name === 'emailPersonalToBeSaved') { + const newEmailPersonal = event.target.value; + AppObservableStore.setGlobalVariableState('emailPersonalChanged', true); + AppObservableStore.setGlobalVariableState('emailPersonalToBeSaved', newEmailPersonal); + console.log('updateEmailPersonal:', newEmailPersonal); + setEmailPersonal(newEmailPersonal); + } + }; + + const updateFirstName = (event) => { + if (event.target.name === 'firstNameToBeSaved') { + const newFirstName = event.target.value; + AppObservableStore.setGlobalVariableState('firstNameChanged', true); + AppObservableStore.setGlobalVariableState('firstNameToBeSaved', newFirstName); + console.log('updateFirstName:', newFirstName); + setFirstName(newFirstName); + } + }; + + const updateLastName = (event) => { + if (event.target.name === 'lastNameToBeSaved') { + const newLastName = event.target.value; + AppObservableStore.setGlobalVariableState('lastNameChanged', true); + AppObservableStore.setGlobalVariableState('lastNameToBeSaved', newLastName); + console.log('updateLastName:', newLastName); + setLastName(newLastName); + } + }; + + React.useEffect(() => { + const appStateSubscription = messageService.getMessage().subscribe(() => onAppObservableStoreChange()); + onAppObservableStoreChange(); + const personStoreListener = PersonStore.addListener(onPersonStoreChange); + onPersonStoreChange(); + // console.log('Initial load emailPersonalToBeSaved:', AppObservableStore.getGlobalVariableState('emailPersonalToBeSaved')); + if (AppObservableStore.getGlobalVariableState('emailPersonalToBeSaved')) { + setEmailPersonal(AppObservableStore.getGlobalVariableState('emailPersonalToBeSaved')); + } + if (AppObservableStore.getGlobalVariableState('firstNameToBeSaved')) { + setFirstName(AppObservableStore.getGlobalVariableState('firstNameToBeSaved')); + } + if (AppObservableStore.getGlobalVariableState('lastNameToBeSaved')) { + setLastName(AppObservableStore.getGlobalVariableState('lastNameToBeSaved')); + } + + return () => { + appStateSubscription.unsubscribe(); + personStoreListener.remove(); + }; + }, []); + + return ( + + + + + + + + + ); +}; +AddPersonForm.propTypes = { + classes: PropTypes.object.isRequired, +}; + +const styles = (theme) => ({ + formControl: { + width: '100%', + }, + saveNewPersonButton: { + width: 300, + [theme.breakpoints.down('md')]: { + width: '100%', + }, + }, +}); + +const AddPersonFormWrapper = styled('div')` +`; + +export default withStyles(styles)(AddPersonForm); diff --git a/src/js/components/AddTeam/AddTeamForm.jsx b/src/js/components/AddTeam/AddTeamForm.jsx new file mode 100644 index 0000000..5a396cb --- /dev/null +++ b/src/js/components/AddTeam/AddTeamForm.jsx @@ -0,0 +1,113 @@ +import { Button, FormControl, TextField } from '@mui/material'; +import { withStyles } from '@mui/styles'; +import React from 'react'; +import styled from 'styled-components'; +import PropTypes from 'prop-types'; +import AppObservableStore, { messageService } from '../../stores/AppObservableStore'; +import TeamActions from '../../actions/TeamActions'; +import TeamStore from '../../stores/TeamStore'; +import { renderLog } from '../../common/utils/logging'; +import prepareDataPackageFromAppObservableStore from '../../common/utils/prepareDataPackageFromAppObservableStore'; + + +const AddTeamForm = ({ classes }) => { // classes, teamId + renderLog('AddTeamForm'); // Set LOG_RENDER_EVENTS to log all renders + const [teamName, setTeamName] = React.useState(''); + + const onAppObservableStoreChange = () => { + }; + + const saveNewTeamSuccessful = () => { + AppObservableStore.setGlobalVariableState('addTeamDrawerOpen', false); + AppObservableStore.setGlobalVariableState('teamNameChanged', false); + AppObservableStore.setGlobalVariableState('teamNameToBeSaved', ''); + }; + + const onTeamStoreChange = () => { + const mostRecentTeamChanged = TeamStore.getMostRecentTeamChanged(); + console.log('AddTeamForm onTeamStoreChange mostRecentTeamChanged:', mostRecentTeamChanged); + // TODO: Figure out why teamName is not being updated locally + // console.log('teamName:', teamName); + if (mostRecentTeamChanged.teamName === AppObservableStore.getGlobalVariableState('teamNameToBeSaved')) { + saveNewTeamSuccessful(); + } + }; + + const saveNewTeam = () => { + const acceptedVariables = ['teamName']; + const data = prepareDataPackageFromAppObservableStore(acceptedVariables); + // console.log('saveNewTeam data:', data); + TeamActions.teamSave('-1', data); + }; + + const updateTeamName = (event) => { + if (event.target.name === 'teamNameToBeSaved') { + const newTeamName = event.target.value; + AppObservableStore.setGlobalVariableState('teamNameChanged', true); + AppObservableStore.setGlobalVariableState('teamNameToBeSaved', newTeamName); + // console.log('updateTeamName:', newTeamName); + setTeamName(newTeamName); + } + }; + + React.useEffect(() => { + const appStateSubscription = messageService.getMessage().subscribe(() => onAppObservableStoreChange()); + onAppObservableStoreChange(); + const teamStoreListener = TeamStore.addListener(onTeamStoreChange); + onTeamStoreChange(); + if (AppObservableStore.getGlobalVariableState('teamNameToBeSaved')) { + setTeamName(AppObservableStore.getGlobalVariableState('teamNameToBeSaved')); + } + + return () => { + appStateSubscription.unsubscribe(); + teamStoreListener.remove(); + }; + }, []); + + return ( + + + + + + + ); +}; +AddTeamForm.propTypes = { + classes: PropTypes.object.isRequired, +}; + +const styles = (theme) => ({ + formControl: { + width: '100%', + }, + saveNewTeamButton: { + width: 300, + [theme.breakpoints.down('md')]: { + width: '100%', + }, + }, +}); + +const AddTeamFormWrapper = styled('div')` +`; + +export default withStyles(styles)(AddTeamForm); diff --git a/src/js/components/Drawers/AddPersonDrawer.jsx b/src/js/components/Drawers/AddPersonDrawer.jsx index f2045af..33ae443 100644 --- a/src/js/components/Drawers/AddPersonDrawer.jsx +++ b/src/js/components/Drawers/AddPersonDrawer.jsx @@ -5,18 +5,15 @@ import { withStyles } from '@mui/styles'; import DrawerTemplateA from './DrawerTemplateA'; import AppObservableStore, { messageService } from '../../stores/AppObservableStore'; import PersonStore from '../../stores/PersonStore'; -// import TeamActions from '../actions/TeamActions'; import TeamStore from '../../stores/TeamStore'; -import SearchBar2024 from '../../common/components/Search/SearchBar2024'; import { renderLog } from '../../common/utils/logging'; +import AddPersonDrawerMainContent from './AddPersonDrawerMainContent'; const AddPersonDrawer = ({ classes }) => { // classes, teamId renderLog('AddPersonDrawer'); // Set LOG_RENDER_EVENTS to log all renders - const [mainContentJsx, setMainContentJsx] = React.useState(<>); const [headerTitleJsx, setHeaderTitleJsx] = React.useState(<>); const [headerFixedJsx, setHeaderFixedJsx] = React.useState(<>); - const [searchText, setSearchText] = React.useState(''); const [teamId, setTeamId] = React.useState(-1); const onAppObservableStoreChange = () => { @@ -34,14 +31,6 @@ const AddPersonDrawer = ({ classes }) => { // classes, teamId onRetrieveTeamChange(); }; - const searchFunction = (incomingSearchText) => { - setSearchText(incomingSearchText); - }; - - const clearFunction = () => { - setSearchText(''); - } - React.useEffect(() => { const appStateSubscription = messageService.getMessage().subscribe(() => onAppObservableStoreChange()); onAppObservableStoreChange(); @@ -51,18 +40,6 @@ const AddPersonDrawer = ({ classes }) => { // classes, teamId onTeamStoreChange(); setHeaderTitleJsx(<>Add Team Member); - setMainContentJsx( - - - - - - ); return () => { appStateSubscription.unsubscribe(); @@ -73,8 +50,9 @@ const AddPersonDrawer = ({ classes }) => { // classes, teamId return ( } headerTitleJsx={headerTitleJsx} headerFixedJsx={headerFixedJsx} /> diff --git a/src/js/components/Drawers/AddPersonDrawerMainContent.jsx b/src/js/components/Drawers/AddPersonDrawerMainContent.jsx new file mode 100644 index 0000000..b6e4fbe --- /dev/null +++ b/src/js/components/Drawers/AddPersonDrawerMainContent.jsx @@ -0,0 +1,133 @@ +import React from 'react'; +import styled from 'styled-components'; +import PropTypes from 'prop-types'; +import { withStyles } from '@mui/styles'; +import AppObservableStore, { messageService } from '../../stores/AppObservableStore'; +import PersonActions from '../../actions/PersonActions'; +import TeamActions from '../../actions/TeamActions'; +import PersonStore from '../../stores/PersonStore'; +import TeamStore from '../../stores/TeamStore'; +import apiCalming from '../../common/utils/apiCalming'; +import SearchBar2024 from '../../common/components/Search/SearchBar2024'; +import { renderLog } from '../../common/utils/logging'; +import AddPersonForm from '../AddPerson/AddPersonForm'; + + +const AddPersonDrawerMainContent = ({ classes }) => { // classes, teamId + renderLog('AddPersonDrawerMainContent'); // Set LOG_RENDER_EVENTS to log all renders + const [searchText, setSearchText] = React.useState(''); + const [personSearchResultsList, setPersonSearchResultsList] = React.useState([]); + const [teamId, setTeamId] = React.useState(-1); + + const onAppObservableStoreChange = () => { + setTeamId(AppObservableStore.getGlobalVariableState('addPersonDrawerTeamId')); + }; + + const onPersonStoreChange = () => { + const personSearchResultsListTemp = PersonStore.getSearchResults(); + // console.log('AddPersonDrawerMainContent personSearchResultsList:', personSearchResultsListTemp); + setPersonSearchResultsList(personSearchResultsListTemp); + }; + + const onTeamStoreChange = () => { + }; + + const searchFunction = (incomingSearchText) => { + let searchingJustStarted = false; + if (searchText.length === 0 && incomingSearchText.length > 0) { + searchingJustStarted = true; + } + const isSearching = (incomingSearchText && incomingSearchText.length > 0); + if (apiCalming(`addPersonToTeamSearch-${teamId}-${incomingSearchText}`, 60000)) { // Only once per 60 seconds + PersonActions.personListRetrieve(); + } + setSearchText(incomingSearchText); + }; + + const clearFunction = () => { + setSearchText(''); + }; + + React.useEffect(() => { + const appStateSubscription = messageService.getMessage().subscribe(() => onAppObservableStoreChange()); + onAppObservableStoreChange(); + const personStoreListener = PersonStore.addListener(onPersonStoreChange); + onPersonStoreChange(); + const teamStoreListener = TeamStore.addListener(onTeamStoreChange); + onTeamStoreChange(); + + return () => { + appStateSubscription.unsubscribe(); + personStoreListener.remove(); + teamStoreListener.remove(); + }; + }, []); + + React.useEffect(() => { + }, [personSearchResultsList]); + const personSearchResultsListTemp = PersonStore.getSearchResults(); + + return ( + + + + + + {(personSearchResultsListTemp && personSearchResultsListTemp.length > 0) && ( + + Search Results: + + {personSearchResultsListTemp.map((person, index) => ( + + {person.firstName} + {' '} + {person.lastName} + {' '} + TeamActions.addPersonToTeam(person.id, teamId)}>add + + ))} + + + )} + + ); +}; +AddPersonDrawerMainContent.propTypes = { + classes: PropTypes.object.isRequired, +}; + +const styles = () => ({ +}); + +const AddPersonDrawerMainContentWrapper = styled('div')` +`; + +const AddPersonToTeamLinkStyle = styled('span')` + text-decoration:underline; + color:#206DB3; /* primary500 */ + cursor:pointer; +`; + +const PersonSearchResultsItem = styled('div')` +`; + +const PersonSearchResultsList = styled('div')` +`; + +const PersonSearchResultsTitle = styled('div')` +`; + +const PersonSearchResultsWrapper = styled('div')` + margin-top: 16px; +`; + +const SearchBarWrapper = styled('div')` + margin-bottom: 16px; +`; + +export default withStyles(styles)(AddPersonDrawerMainContent); diff --git a/src/js/components/Drawers/AddTeamDrawer.jsx b/src/js/components/Drawers/AddTeamDrawer.jsx new file mode 100644 index 0000000..995835f --- /dev/null +++ b/src/js/components/Drawers/AddTeamDrawer.jsx @@ -0,0 +1,94 @@ +import React from 'react'; +import styled from 'styled-components'; +import PropTypes from 'prop-types'; +import { withStyles } from '@mui/styles'; +import DrawerTemplateA from './DrawerTemplateA'; +import { messageService } from '../../stores/AppObservableStore'; +// import TeamActions from '../actions/TeamActions'; +import TeamStore from '../../stores/TeamStore'; +import SearchBar2024 from '../../common/components/Search/SearchBar2024'; +import { renderLog } from '../../common/utils/logging'; +import AddTeamForm from '../AddTeam/AddTeamForm'; + + +const AddTeamDrawer = ({ classes }) => { // classes, teamId + renderLog('AddTeamDrawer'); // Set LOG_RENDER_EVENTS to log all renders + const [mainContentJsx, setMainContentJsx] = React.useState(<>); + const [headerTitleJsx, setHeaderTitleJsx] = React.useState(<>); + const [headerFixedJsx, setHeaderFixedJsx] = React.useState(<>); + const [searchText, setSearchText] = React.useState(''); + + const onAppObservableStoreChange = () => { + }; + + const onRetrieveTeamChange = () => { + }; + + const onTeamStoreChange = () => { + onRetrieveTeamChange(); + }; + + const searchFunction = (incomingSearchText) => { + setSearchText(incomingSearchText); + }; + + const clearFunction = () => { + setSearchText(''); + }; + + React.useEffect(() => { + const appStateSubscription = messageService.getMessage().subscribe(() => onAppObservableStoreChange()); + onAppObservableStoreChange(); + const personStoreListener = TeamStore.addListener(onTeamStoreChange); + onTeamStoreChange(); + const teamStoreListener = TeamStore.addListener(onTeamStoreChange); + onTeamStoreChange(); + + setHeaderTitleJsx(<>Add Team); + const mainContentJsxTemp = ( + + + + + + + ); + setMainContentJsx(mainContentJsxTemp); + + return () => { + appStateSubscription.unsubscribe(); + personStoreListener.remove(); + teamStoreListener.remove(); + }; + }, []); + + return ( + + ); +}; +AddTeamDrawer.propTypes = { + classes: PropTypes.object.isRequired, +}; + +const styles = () => ({ +}); + +const AddTeamDrawerWrapper = styled('div')` +`; + +const SearchBarWrapper = styled('div')` + margin-bottom: 16px; +`; + +export default withStyles(styles)(AddTeamDrawer); diff --git a/src/js/components/Drawers/DrawerTemplateA.jsx b/src/js/components/Drawers/DrawerTemplateA.jsx index 6dc711b..01d2218 100644 --- a/src/js/components/Drawers/DrawerTemplateA.jsx +++ b/src/js/components/Drawers/DrawerTemplateA.jsx @@ -11,7 +11,7 @@ import { hasIPhoneNotch } from '../../common/utils/cordovaUtils'; import { renderLog } from '../../common/utils/logging'; -const DrawerTemplateA = ({ classes, drawerOpenGlobalVariableName, headerFixedJsx, headerTitleJsx, mainContentJsx }) => { // classes, teamId +const DrawerTemplateA = ({ classes, drawerId, drawerOpenGlobalVariableName, headerFixedJsx, headerTitleJsx, mainContentJsx }) => { // classes, teamId renderLog('DrawerTemplateA'); // Set LOG_RENDER_EVENTS to log all renders const [drawerOpen, setDrawerOpen] = React.useState(false); const [scrolledDown, setScrolledDown] = React.useState(false); @@ -54,7 +54,7 @@ const DrawerTemplateA = ({ classes, drawerOpenGlobalVariableName, headerFixedJsx anchor="right" classes={{ paper: classes.drawer }} direction="left" - id="addPersonDrawer" + id={drawerId} onClose={() => AppObservableStore.setGlobalVariableState(drawerOpenGlobalVariableName, false)} open={drawerOpen} > @@ -66,7 +66,7 @@ const DrawerTemplateA = ({ classes, drawerOpenGlobalVariableName, headerFixedJsx AppObservableStore.setGlobalVariableState(drawerOpenGlobalVariableName, false)} size="large" > @@ -76,7 +76,7 @@ const DrawerTemplateA = ({ classes, drawerOpenGlobalVariableName, headerFixedJsx - + {headerFixedJsx} @@ -89,6 +89,7 @@ const DrawerTemplateA = ({ classes, drawerOpenGlobalVariableName, headerFixedJsx }; DrawerTemplateA.propTypes = { classes: PropTypes.object.isRequired, + drawerId: PropTypes.string, drawerOpenGlobalVariableName: PropTypes.string, mainContentJsx: PropTypes.object, headerTitleJsx: PropTypes.object, diff --git a/src/js/components/Drawers/Drawers.jsx b/src/js/components/Drawers/Drawers.jsx index 130aab1..0c3ce87 100644 --- a/src/js/components/Drawers/Drawers.jsx +++ b/src/js/components/Drawers/Drawers.jsx @@ -3,6 +3,7 @@ import styled from 'styled-components'; import PropTypes from 'prop-types'; import { withStyles } from '@mui/styles'; import AddPersonDrawer from './AddPersonDrawer'; +import AddTeamDrawer from './AddTeamDrawer'; import { messageService } from '../../stores/AppObservableStore'; import { renderLog } from '../../common/utils/logging'; @@ -25,6 +26,7 @@ const Drawers = () => { // classes, teamId return ( }> + ); }; diff --git a/src/js/config-template.js b/src/js/config-template.js index 13acb39..6c0cf75 100644 --- a/src/js/config-template.js +++ b/src/js/config-template.js @@ -8,6 +8,7 @@ module.exports = { PORT: 'localhost:4000', // Don't add 'http...' here. Live server: 'WeVote.US', Quality: 'quality.WeVote.US', developers: 'localhost:4000' IMAGE_PATH_FOR_CORDOVA: 'https://wevote.us', // If you are not working with Cordova, you don't need to change this SECURE_CERTIFICATE_INSTALLED: false, + WECONNECT_URL_FOR_SEO: 'https://wevoteconnect.org', /////////////////////////////////////////////// // Keep both configuration blocks below, but only uncomment one of them at a time. @@ -22,9 +23,9 @@ module.exports = { // WECONNECT_SERVER_API_ROOT_URL: 'https://weconnectserver.org/apis/v1/', // WECONNECT_SERVER_API_CDN_ROOT_URL: 'https://cdn.weconnectserver.org/apis/v1/', - // For when we need to connect to the WeVoteServer (Python) APIs + // For when we need to connect to the WeVote WebApp front end WE_VOTE_URL_PROTOCOL: 'http://', // 'http://' for local dev (if not using SSL), or 'https://' for live server - WE_VOTE_HOSTNAME: 'localhost:8000', // Don't add 'http...' here. Live server: 'WeVote.US', Quality: 'quality.WeVote.US', developers: 'localhost:3000' + WE_VOTE_HOSTNAME: 'localhost:3000', // Don't add 'http...' here. Live server: 'WeVote.US', Quality: 'quality.WeVote.US', developers: 'localhost:3000' /////////////////////////////////////////////// // Keep both configuration blocks below, but only uncomment one of them at a time. diff --git a/src/js/pages/TeamMembers.jsx b/src/js/pages/TeamMembers.jsx index 3bda7d4..db82077 100644 --- a/src/js/pages/TeamMembers.jsx +++ b/src/js/pages/TeamMembers.jsx @@ -1,6 +1,7 @@ import { Button } from '@mui/material'; import React from 'react'; import { Helmet } from 'react-helmet-async'; +import { Link } from 'react-router-dom'; import styled from 'styled-components'; import PropTypes from 'prop-types'; import { withStyles } from '@mui/styles'; @@ -10,6 +11,7 @@ import TeamActions from '../actions/TeamActions'; import TeamStore from '../stores/TeamStore'; import { PageContentContainer } from '../components/Style/pageLayoutStyles'; import webAppConfig from '../config'; +import apiCalming from '../common/utils/apiCalming'; import { renderLog } from '../common/utils/logging'; @@ -32,7 +34,11 @@ const TeamMembers = ({ classes, match }) => { // classes, teamId }; const onPersonStoreChange = () => { + const { params } = match; onRetrieveTeamChange(); + if (apiCalming(`teamRetrieve-${params.teamId}`, 1000)) { + TeamActions.teamRetrieve(params.teamId); + } }; const onTeamStoreChange = () => { @@ -40,8 +46,9 @@ const TeamMembers = ({ classes, match }) => { // classes, teamId }; const addTeamMemberClick = () => { + const { params } = match; AppObservableStore.setGlobalVariableState('addPersonDrawerOpen', true); - AppObservableStore.setGlobalVariableState('addPersonDrawerTeamId', true); + AppObservableStore.setGlobalVariableState('addPersonDrawerTeamId', params.teamId); }; React.useEffect(() => { @@ -54,7 +61,9 @@ const TeamMembers = ({ classes, match }) => { // classes, teamId const teamStoreListener = TeamStore.addListener(onTeamStoreChange); onTeamStoreChange(); - TeamActions.teamRetrieve(params.teamId); + if (apiCalming(`teamRetrieve-${params.teamId}`, 1000)) { + TeamActions.teamRetrieve(params.teamId); + } return () => { appStateSubscription.unsubscribe(); @@ -78,7 +87,9 @@ const TeamMembers = ({ classes, match }) => { // classes, teamId
Team Members ( {teamMemberCount} - ) + ) - + {' '} + home
+ {teamList.map((team) => ( +
+ + {team.teamName} + +
+ ))} + {pigsCanFly && ( + Teams will fly here + )} + + + ); +}; +Teams.propTypes = { + classes: PropTypes.object.isRequired, + match: PropTypes.object.isRequired, +}; + +const styles = (theme) => ({ + ballotButtonIconRoot: { + marginRight: 8, + }, + addTeamButtonRoot: { + width: 100, + [theme.breakpoints.down('md')]: { + width: '100%', + }, + }, +}); + +const SearchBarWrapper = styled('div')` + // margin-top: 14px; + // margin-bottom: 8px; + width: 100%; +`; + +export default withStyles(styles)(Teams); diff --git a/src/js/stores/AppObservableStore.js b/src/js/stores/AppObservableStore.js index 3a83939..0c18de6 100644 --- a/src/js/stores/AppObservableStore.js +++ b/src/js/stores/AppObservableStore.js @@ -23,6 +23,7 @@ const nonFluxState = { activityTidbitWeVoteIdForDrawer: '', addPersonDrawerOpen: false, addPersonDrawerTeamId: -1, // Team ID used when adding a new person + addTeamDrawerOpen: false, blockChallengeRedirectOnSignIn: false, // When signing in from the header, don't mark a challenge as supported challengeParticipantNameWithHighestRankByChallengeWeVoteId: {}, // Key is challengeWeVoteId, value is name for voter with the highest rank for that challenge challengeParticipantRankOfVoterByChallengeWeVoteId: {}, // Key is challengeWeVoteId, value is rank of voter for that challenge @@ -34,6 +35,10 @@ const nonFluxState = { chosenSiteLogoUrl: '', chosenWebsiteName: '', currentPathname: '', + emailPersonalChanged: false, + emailPersonalToBeSaved: '', + firstNameChanged: false, + firstNameToBeSaved: '', getStartedMode: '', getVoterGuideSettingsDashboardEditMode: '', googleAnalyticsEnabled: false, @@ -42,6 +47,8 @@ const nonFluxState = { hideOrganizationModalPositions: false, hideWeVoteLogo: false, hostname: '', + lastNameChanged: false, + lastNameToBeSaved: '', observableUpdateCounter: 0, openReplayEnabled: false, openReplayPending: false, @@ -82,6 +89,8 @@ const nonFluxState = { siteConfigurationHasBeenRetrieved: false, siteOwnerOrganizationWeVoteId: '', storeSignInStartFullUrl: false, + teamNameChanged: false, + teamNameToBeSaved: '', viewingOrganizationVoterGuide: false, voterBallotItemsRetrieveHasBeenCalled: false, voterExternalIdHasBeenSavedOnce: {}, // Dict with externalVoterId and membershipOrganizationWeVoteId as keys, and true/false as value diff --git a/src/js/stores/PersonStore.js b/src/js/stores/PersonStore.js index b10ef47..68865ed 100644 --- a/src/js/stores/PersonStore.js +++ b/src/js/stores/PersonStore.js @@ -7,21 +7,43 @@ class PersonStore extends ReduceStore { getInitialState () { return { allCachedPeopleDict: {}, // This is a dictionary key: personId, value: person dict - person: { // The person who is signed in + mostRecentPersonIdSaved: -1, + mostRecentPersonSaved: { firstName: '', lastName: '', - personDeviceId: '', + personId: '', }, + searchResults: [], }; } - getFirstName () { - return this.getState().person.firstName || ''; + getFirstName (personId) { + const person = this.getPersonById(personId); + return person.firstName || ''; } - getFirstPlusLastName () { - const storedFirstName = this.getFirstName(); - const storedLastName = this.getLastName(); + getFullNamePreferred (personId) { + const person = this.getPersonById(personId); + let fullName = ''; + if (person.id >= 0) { + if (person.firstNamePreferred) { + fullName += person.firstNamePreferred; + } else if (person.firstName) { + fullName += person.firstName; + } + if (fullName.length > 0 && person.lastName) { + fullName += ' '; + } + if (person.lastName) { + fullName += person.lastName; + } + } + return fullName; + } + + getFirstPlusLastName (personId) { + const storedFirstName = this.getFirstName(personId); + const storedLastName = this.getLastName(personId); let displayName = ''; if (storedFirstName && String(storedFirstName) !== '') { displayName = storedFirstName; @@ -35,12 +57,17 @@ class PersonStore extends ReduceStore { return displayName; } - getLastName () { - return this.getState().person.lastName || ''; + getLastName (personId) { + const person = this.getPersonById(personId); + return person.lastName || ''; } - getPerson () { - return this.getState().person; + getMostRecentPersonChanged () { + // console.log('PersonStore getMostRecentPersonChanged Id:', this.getState().mostRecentPersonIdSaved); + if (this.getState().mostRecentPersonIdSaved !== -1) { + return this.getPersonById(this.getState().mostRecentPersonIdSaved); + } + return {}; } getPersonById (personId) { @@ -53,32 +80,77 @@ class PersonStore extends ReduceStore { return this.getState().person.personDeviceId || Cookies.get('personDeviceId'); } - getStateCode () { - // This defaults to state_code_from_ip_address but is overridden by the address the voter defaults to, or enters in text_for_map_search - return this.getState().person.stateCode || ''; + getStateCode (personId) { + const person = this.getPersonById(personId); + return person.stateCode || ''; + } + + getSearchResults () { + // console.log('PersonStore getSearchResults:', this.getState().searchResults); + return this.getState().searchResults || []; } reduce (state, action) { - let { allCachedPeopleDict } = state; + const { allCachedPeopleDict } = state; + let personId = -1; let revisedState = state; + let searchResults = []; let teamId = -1; let teamMemberList = []; switch (action.type) { - case 'team-retrieve': + case 'person-list-retrieve': + if (!action.res.success) { + console.log('PersonStore ', action.type, ' FAILED action.res:', action.res); + return state; + } + revisedState = state; + // console.log('PersonStore person-list-retrieve personList:', action.res.personList); + if (action.res.isSearching && action.res.isSearching === true) { + // console.log('PersonStore isSearching:', action.res.isSearching); + searchResults = action.res.personList; + // console.log('PersonStore searchResults:', searchResults); + revisedState = { + ...revisedState, + searchResults, + }; + } + // console.log('PersonStore revisedState:', revisedState); + return revisedState; + + case 'person-save': if (!action.res.success) { console.log('PersonStore ', action.type, ' FAILED action.res:', action.res); return state; } - teamId = action.res.teamId || -1; - teamMemberList = action.res.teamMemberList || []; revisedState = state; + personId = action.res.personId || -1; + + if (personId >= 0) { + // console.log('PersonStore person-save personId:', personId); + allCachedPeopleDict[personId] = action.res; + revisedState = { + ...revisedState, + allCachedPeopleDict, + mostRecentPersonIdSaved: personId, + }; + } else { + console.log('PersonStore person-save MISSING personId:', personId); + } + return revisedState; - // console.log('PersonStore team-retrieve start allCachedPeopleDict:', allCachedPeopleDict); - if (!allCachedPeopleDict) { - allCachedPeopleDict = {}; + case 'team-retrieve': + case 'team-save': + if (!action.res.success) { + console.log('PersonStore ', action.type, ' FAILED action.res:', action.res); + return state; } - if (teamId > 0) { + revisedState = state; + teamId = action.res.teamId || -1; + + // console.log('PersonStore ', action.type, ' start allCachedPeopleDict:', allCachedPeopleDict); + if (teamId >= 0 && action.res.teamMemberList) { + teamMemberList = action.res.teamMemberList || []; teamMemberList.forEach((person) => { // console.log('PersonStore team-retrieve adding person:', person); if (person && (person.id >= 0) && !arrayContains(person.id, allCachedPeopleDict)) { diff --git a/src/js/stores/TeamStore.js b/src/js/stores/TeamStore.js index 921729d..ee55c92 100644 --- a/src/js/stores/TeamStore.js +++ b/src/js/stores/TeamStore.js @@ -1,6 +1,5 @@ import { ReduceStore } from 'flux/utils'; import Dispatcher from '../common/dispatcher/Dispatcher'; -import Cookies from '../common/utils/js-cookie/Cookies'; import PersonStore from './PersonStore'; import arrayContains from '../common/utils/arrayContains'; @@ -9,22 +8,43 @@ class TeamStore extends ReduceStore { return { allCachedTeamsDict: {}, // This is a dictionary key: teamId, value: team dict allCachedTeamMembersDict: {}, // This is a dictionary key: teamId, value: list of personIds in the team - // team: { - // teamName: '', - // description: '', - // personDeviceId: '', - // statusActive: false, - // }, + mostRecentTeamIdSaved: -1, + mostRecentTeamMemberIdSaved: -1, + mostRecentTeamSaved: { + teamName: '', + teamId: '', + }, }; } + getMostRecentTeamChanged () { + console.log('TeamStore getMostRecentTeamChanged Id:', this.getState().mostRecentTeamIdSaved); + if (this.getState().mostRecentTeamIdSaved !== -1) { + return this.getTeamById(this.getState().mostRecentTeamIdSaved); + } + return {}; + } + getTeamById (teamId) { const { allCachedTeamsDict } = this.getState(); + console.log('TeamStore getTeamById:', teamId, ', allCachedTeamsDict:', allCachedTeamsDict); return allCachedTeamsDict[teamId] || {}; } - getTeamDeviceId () { - return this.getState().person.personDeviceId || Cookies.get('personDeviceId'); + getTeamList () { + const { allCachedTeamsDict } = this.getState(); + const teamMemberListRaw = Object.values(allCachedTeamsDict); + + const teamList = []; + let teamFiltered; + let teamRaw; + for (let i = 0; i < teamMemberListRaw.length; i++) { + teamRaw = teamMemberListRaw[i]; + // console.log('TeamStore getTeamMemberList person:', person); + teamFiltered = teamRaw; + teamList.push(teamFiltered); + } + return teamList; } getTeamMemberList (teamId) { @@ -42,16 +62,71 @@ class TeamStore extends ReduceStore { } reduce (state, action) { - const { allCachedTeamMembersDict } = state; + const { allCachedTeamMembersDict, allCachedTeamsDict } = state; + let personId = -1; let revisedState = state; let teamId = -1; + let teamList = []; let teamMemberList = []; let teamMemberIdList = []; switch (action.type) { - case 'clearEmailAddressStatus': - // console.log('VoterStore clearEmailAddressStatus'); - return { ...state, emailAddressStatus: {} }; + case 'add-person-to-team': + if (!action.res.success) { + console.log('TeamStore ', action.type, ' FAILED action.res:', action.res); + return state; + } + revisedState = state; + personId = action.res.personId || -1; + teamId = action.res.teamId || -1; + if (personId >= 0 && teamId >= 0) { + console.log('TeamStore add-person-to-team personId: ', personId, ', teamId:', teamId); + // Start with existing teamMemberList + teamMemberIdList = allCachedTeamMembersDict[teamId] || []; + // Check if personId is already in teamMemberListAdd personId to teamMemberList + if (!arrayContains(personId, teamMemberIdList)) { + teamMemberIdList.push(personId); + allCachedTeamMembersDict[teamId] = teamMemberIdList; + revisedState = { + ...revisedState, + allCachedTeamMembersDict, + mostRecentTeamMemberIdSaved: personId, + }; + } + } else { + console.log('TeamStore add-person-to-team MISSING personId: ', personId, ' OR teamId:', teamId); + } + + return revisedState; + + case 'team-list-retrieve': + if (!action.res.success) { + console.log('TeamStore ', action.type, ' FAILED action.res:', action.res); + return state; + } + teamList = action.res.teamList || []; + revisedState = state; + teamList.forEach((team) => { + if (team && (team.id >= 0)) { + allCachedTeamsDict[team.id] = team; + if (team.teamMemberList) { + teamMemberList = team.teamMemberList || []; + teamMemberList.forEach((person) => { + if (person && (person.id >= 0) && !arrayContains(person.id, teamMemberIdList)) { + teamMemberIdList.push(person.id); + } + }); + allCachedTeamMembersDict[teamId] = teamMemberIdList; + } + } + }); + + revisedState = { + ...revisedState, + allCachedTeamMembersDict, + allCachedTeamsDict, + }; + return revisedState; case 'team-retrieve': if (!action.res.success) { @@ -59,27 +134,70 @@ class TeamStore extends ReduceStore { return state; } teamId = action.res.teamId || -1; - teamMemberList = action.res.teamMemberList || []; teamMemberIdList = []; revisedState = state; // console.log('OrganizationStore issueDescriptionsRetrieve issueList:', issueList); if (teamId > 0) { - teamMemberList.forEach((person) => { - if (person && (person.id >= 0) && !arrayContains(person.id, teamMemberIdList)) { - teamMemberIdList.push(person.id); - } - }); - allCachedTeamMembersDict[teamId] = teamMemberIdList; + allCachedTeamsDict[teamId] = action.res; + if (action.res.teamMemberList) { + // If missing teamMemberList do not alter data in the store + teamMemberList = action.res.teamMemberList || []; + teamMemberList.forEach((person) => { + if (person && (person.id >= 0) && !arrayContains(person.id, teamMemberIdList)) { + teamMemberIdList.push(person.id); + } + }); + allCachedTeamMembersDict[teamId] = teamMemberIdList; + revisedState = { + ...revisedState, + allCachedTeamMembersDict, + }; + } // console.log('allCachedTeamMembersDict:', allCachedTeamMembersDict); // console.log('allCachedOrganizationsDict:', allCachedOrganizationsDict); revisedState = { ...revisedState, - allCachedTeamMembersDict, + allCachedTeamsDict, }; } return revisedState; + case 'team-save': + if (!action.res.success) { + console.log('TeamStore ', action.type, ' FAILED action.res:', action.res); + return state; + } + revisedState = state; + teamId = action.res.teamId || -1; + if (teamId >= 0) { + console.log('TeamStore team-save teamId:', teamId); + allCachedTeamsDict[teamId] = action.res; + if (action.res.teamMemberList) { + // If missing teamMemberList do not alter data in the store + teamMemberList = action.res.teamMemberList || []; + teamMemberList.forEach((person) => { + if (person && (person.id >= 0) && !arrayContains(person.id, teamMemberIdList)) { + teamMemberIdList.push(person.id); + } + }); + allCachedTeamMembersDict[teamId] = teamMemberIdList; + revisedState = { + ...revisedState, + allCachedTeamMembersDict, + }; + } + revisedState = { + ...revisedState, + allCachedTeamsDict, + mostRecentTeamIdSaved: teamId, + }; + } else { + console.log('TeamStore person-save MISSING teamId:', teamId); + } + + return revisedState; + default: return state; }