Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tim add Materials List page #1401

Merged
merged 20 commits into from
Oct 19, 2023
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .eslintignore
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ src/components/App.jsx
src/components/AutoReload/**
src/components/AutoUpdate/**
src/components/Badge/**
src/components/BMDashboard/**
src/components/common/**
src/components/ForcePasswordUpdate/**
src/components/Header/**
Expand Down
31 changes: 31 additions & 0 deletions src/actions/bmdashboard/materialsActions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import axios from "axios";

import { ENDPOINTS } from "utils/URL";
import { SET_MATERIALS } from "constants/bmdashboard/materialsConstants";
import { GET_ERRORS } from "constants/errors";

export const fetchAllMaterials = () => {
return async dispatch => {
axios.get(ENDPOINTS.BM_MATERIALS_LIST)
.then(res => {
dispatch(setMaterials(res.data))
})
.catch(err => {
dispatch(setErrors(err))
})
}
}

export const setMaterials = payload => {
return {
type: SET_MATERIALS,
payload
}
}

export const setErrors = payload => {
return {
type: GET_ERRORS,
payload
}
}
10 changes: 6 additions & 4 deletions src/components/BMDashboard/BMDashboard.jsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
export const BMDashboard = () => {
return <div style={{ textAlign: 'center', marginTop: '5rem' }}>
<h2>Building and Inventory Management Dashboard</h2>
</div>
export function BMDashboard() {
return (
<div style={{ textAlign: 'center', marginTop: '5rem' }}>
<h2>Building and Inventory Management Dashboard</h2>
</div>
);
}

export default BMDashboard;
117 changes: 58 additions & 59 deletions src/components/BMDashboard/Login/BMLogin.jsx
Original file line number Diff line number Diff line change
@@ -1,129 +1,128 @@
import { useEffect, useState } from 'react';
import { connect } from 'react-redux';
import { Redirect, withRouter } from 'react-router-dom';
import { Redirect } from 'react-router-dom';
import { Form, FormGroup, FormText, Input, Label, Button, FormFeedback } from 'reactstrap';
import Joi from 'joi';

import { loginBMUser } from 'actions/authActions';

const BMLogin = (props) => {
const { dispatch, auth, history } = props
function BMLogin(props) {
const { dispatch, auth, history, location } = props;
// state
const [enteredEmail, setEnteredEmail] = useState("")
const [enterPassword, setEnteredPassword] = useState("")
const [validationError, setValidationError] = useState(null)
const [hasAccess, setHasAccess] = useState(false)
const [enteredEmail, setEnteredEmail] = useState('');
const [enterPassword, setEnteredPassword] = useState('');
const [validationError, setValidationError] = useState(null);
const [hasAccess, setHasAccess] = useState(false);

// push to dashboard if user is authenticated
useEffect(() => {
if(auth.user.access && auth.user.access.canAccessBMPortal) {
history.push('/bmdashboard')
if (auth.user.access && auth.user.access.canAccessBMPortal) {
history.push('/bmdashboard');
}
}, []);
useEffect(() => {
if(hasAccess) {
if (hasAccess) {
history.push('/bmdashboard');
}
},[hasAccess])
}, [hasAccess]);

// Note: email input type="text" to validate with Joi
const schema = Joi.object({
email: Joi.string()
.email()
.required(),
password: Joi.string()
.min(8)
})
password: Joi.string().min(8),
});

const handleChange = ({ target }) => {
// clears validationError only if error input is being edited
if(validationError && target.name === validationError.label) {
setValidationError(null)
}
if(target.name === "email") {
setEnteredEmail(target.value)
if (validationError && target.name === validationError.label) {
setValidationError(null);
}
else {
setEnteredPassword(target.value)
if (target.name === 'email') {
setEnteredEmail(target.value);
} else {
setEnteredPassword(target.value);
}
}
};

// submit login
const handleSubmit = async (e) => {
e.preventDefault()
const handleSubmit = async e => {
e.preventDefault();
// client side error validation
// Note: Joi by default stops validation on first error
const validate = schema.validate({ email: enteredEmail, password: enterPassword })
if(validate.error) {
return setValidationError({
const validate = schema.validate({ email: enteredEmail, password: enterPassword });
if (validate.error) {
return setValidationError({
label: validate.error.details[0].context.label,
message: validate.error.details[0].message
})
message: validate.error.details[0].message,
});
}
const res = await dispatch(loginBMUser({ email: enteredEmail, password: enterPassword }));
// server side error validation
if(res.statusText !== "OK") {
if(res.status === 422) {
return setValidationError({
if (res.statusText !== 'OK') {
if (res.status === 422) {
return setValidationError({
label: res.data.label,
message: res.data.message,
})
});
}
return alert(res.data.message)
// TODO: add additional error handling
return setValidationError({
label: '',
message: '',
});
}
// initiate push to BM Dashboard if validated (ie received token)
setHasAccess(!!res.data.token)
}
return setHasAccess(!!res.data.token);
};

// push Dashboard if not authenticated
if(!auth.isAuthenticated) {
return <Redirect to={{ pathname: '/login', state: { from: props.location } }} />
if (!auth.isAuthenticated) {
return <Redirect to={{ pathname: '/login', state: { from: location } }} />;
}

return (
<div className='container mt-5'>
<div className="container mt-5">
<h2>Log In To Building Management Dashboard</h2>
<Form onSubmit={handleSubmit}>
<FormText>Enter your current user credentials to access the Building Management Dashboard</FormText>
<FormText>
Enter your current user credentials to access the Building Management Dashboard
</FormText>
<FormGroup>
<Label for="email">Email</Label>
<Input
id="email"
name="email"
type="text"
invalid={validationError && validationError.label === "email"}
invalid={validationError && validationError.label === 'email'}
onChange={handleChange}
/>
{ validationError && validationError.label === "email" && (
<FormFeedback>
{validationError.message}
</FormFeedback>)
}
{validationError && validationError.label === 'email' && (
<FormFeedback>{validationError.message}</FormFeedback>
)}
</FormGroup>
<FormGroup>
<Label for="password">Password</Label>
<Input
id="password"
name="password"
type="password"
invalid={validationError && validationError.label === "password"}
invalid={validationError && validationError.label === 'password'}
onChange={handleChange}
/>
{ validationError && validationError.label === "password" && (
<FormFeedback>
{validationError.message}
</FormFeedback>)
}
{validationError && validationError.label === 'password' && (
<FormFeedback>{validationError.message}</FormFeedback>
)}
</FormGroup>
<Button disabled={!enteredEmail || !enterPassword}>
Submit
</Button>
<Button disabled={!enteredEmail || !enterPassword}>Submit</Button>
</Form>
</div>)
</div>
);
}

const mapStateToProps = state => ({
auth: state.auth
auth: state.auth,
});

export default connect (mapStateToProps)(BMLogin);
export default connect(mapStateToProps)(BMLogin);
4 changes: 3 additions & 1 deletion src/components/BMDashboard/Login/index.js
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
export { default } from './BMLogin';
import BMLogin from './BMLogin';

export default BMLogin;
47 changes: 47 additions & 0 deletions src/components/BMDashboard/MaterialsList/MaterialsList.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
.materials_list_container {
width: 100%;
max-width: 1536px;
margin: 1rem auto;
padding: 0 1rem;
}

.materials_table_container {
overflow-x: scroll;
}

.materials_list_container section {
margin-top: 2rem;
}

.materials_list_container table {
text-align: center;
min-width: 1024px;
overflow: scroll;
font-size: small;
}

.materials_list_container th {
height: 2rem;
}

.materials_cell button {
cursor: pointer;
}

.materials_cell svg {
width: 20px;
height: 20px;
margin-right: 6px;
color: dimgray;
}

.materials_cell svg:hover {
color: black;
}

.select_input {
display: flex;
align-items: end;
width: 300px;
column-gap: 1rem;
}
79 changes: 79 additions & 0 deletions src/components/BMDashboard/MaterialsList/MaterialsList.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/* eslint-disable consistent-return */
import { useState, useEffect } from 'react';
import { connect } from 'react-redux';

import { fetchAllMaterials } from 'actions/bmdashboard/materialsActions';
import BMError from '../shared/BMError';
import SelectForm from './SelectForm';
import MaterialsTable from './MaterialsTable';
import './MaterialsList.css';

export function MaterialsList(props) {
// console.log('materials props: ', props);
// props & state
const { materials, errors, dispatch } = props;
const [filteredMaterials, setFilteredMaterials] = useState(materials);
const [selectedProject, setSelectedProject] = useState('all');
const [isError, setIsError] = useState(false);
const [error, setError] = useState({ status: '', message: '' });

// dispatch materials fetch action
// response is mapped to materials or errors in redux store
useEffect(() => {
dispatch(fetchAllMaterials());
}, []);

// filter materials data by project
useEffect(() => {
if (selectedProject === 'all') {
return setFilteredMaterials(materials);
}
const filterMaterials = materials.filter(mat => mat.project.projectName === selectedProject);
setFilteredMaterials(filterMaterials);
}, [selectedProject]);

// error handling
useEffect(() => {
if (Object.entries(errors).length) {
setIsError(true);
// no response object if server is offline
if (!errors.response) {
return setError({
status: 503,
message: 'The server is temporarily offline. Please try again later.',
});
}
setError({
status: errors.response.status,
message: errors.response.statusText,
});
}
}, [errors]);

if (isError) {
return (
<main className="materials_list_container">
<h2>Materials List</h2>
<BMError error={error} />
</main>
);
}

return (
<main className="materials_list_container">
<h3>Materials</h3>
<section>
<SelectForm materials={materials} setSelectedProject={setSelectedProject} />
<MaterialsTable filteredMaterials={filteredMaterials} />
</section>
</main>
);
}

const mapStateToProps = state => ({
// auth: state.auth,
materials: state.materials,
errors: state.errors,
});

export default connect(mapStateToProps)(MaterialsList);
Loading
Loading