├── src ├── App.css ├── index.js ├── reducers │ ├── index.js │ ├── techReducer.js │ └── logReducer.js ├── components │ ├── layout │ │ ├── Preloader.js │ │ ├── AddBtn.js │ │ └── SearchBar.js │ ├── techs │ │ ├── TechSelectOptions.js │ │ ├── TechItem.js │ │ ├── TechListModal.js │ │ └── AddTechModal.js │ └── logs │ │ ├── Logs.js │ │ ├── LogItem.js │ │ ├── AddLogModal.js │ │ └── EditLogModal.js ├── store.js ├── actions │ ├── types.js │ ├── techActions.js │ └── logActions.js └── App.js ├── public ├── favicon.ico ├── manifest.json └── index.html ├── .gitignore ├── README.md ├── db.json └── package.json /src/App.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bradtraversy/it-logger/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | ReactDOM.render(, document.getElementById('root')); 6 | -------------------------------------------------------------------------------- /src/reducers/index.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | import logReducer from './logReducer'; 3 | import techReducer from './techReducer'; 4 | 5 | export default combineReducers({ 6 | log: logReducer, 7 | tech: techReducer 8 | }); 9 | -------------------------------------------------------------------------------- /src/components/layout/Preloader.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const Preloader = () => { 4 | return ( 5 |
6 |
7 |
8 | ); 9 | }; 10 | 11 | export default Preloader; 12 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": ".", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /src/store.js: -------------------------------------------------------------------------------- 1 | import { createStore, applyMiddleware } from 'redux'; 2 | import { composeWithDevTools } from 'redux-devtools-extension'; 3 | import thunk from 'redux-thunk'; 4 | import rootReducer from './reducers'; 5 | 6 | const initialState = {}; 7 | 8 | const middleware = [thunk]; 9 | 10 | const store = createStore( 11 | rootReducer, 12 | initialState, 13 | composeWithDevTools(applyMiddleware(...middleware)) 14 | ); 15 | 16 | export default store; 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ITLogger 2 | 3 | > React app to track IT department tasks and issues. Uses JSON-Server as a mock backend. This is part of my React course on Udemy 4 | 5 | ## Usage 6 | 7 | ### `npm install` 8 | 9 | ### `npm run dev` 10 | 11 | Runs the app with the backend JSON-Server
12 | Open [http://localhost:3000](http://localhost:3000) 13 | Server [http://localhost:5000](http://localhost:5000) 14 | db.json contains data 15 | 16 | ### `npm run build` 17 | 18 | Builds the app for production to the `build` folder.
19 | -------------------------------------------------------------------------------- /src/actions/types.js: -------------------------------------------------------------------------------- 1 | export const GET_LOGS = 'GET_LOGS'; 2 | export const ADD_LOG = 'ADD_LOG'; 3 | export const DELETE_LOG = 'DELETE_LOG'; 4 | export const SET_CURRENT = 'SET_CURRENT'; 5 | export const CLEAR_CURRENT = 'CLEAR_CURRENT'; 6 | export const UPDATE_LOG = 'UPDATE_LOG'; 7 | export const CLEAR_LOGS = 'CLEAR_LOGS'; 8 | export const SET_LOADING = 'SET_LOADING'; 9 | export const LOGS_ERROR = 'LOGS_ERROR'; 10 | export const SEARCH_LOGS = 'SEARCH_LOGS'; 11 | export const GET_TECHS = 'GET_TECHS'; 12 | export const ADD_TECH = 'ADD_TECH'; 13 | export const DELETE_TECH = 'DELETE_TECH'; 14 | export const TECHS_ERROR = 'TECHS_ERROR'; 15 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 14 | IT Logger 15 | 16 | 17 | 18 |
19 | 20 | 21 | -------------------------------------------------------------------------------- /src/components/layout/AddBtn.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const AddBtn = () => { 4 | return ( 5 |
6 | 10 | add 11 | 12 | 27 |
28 | ); 29 | }; 30 | 31 | export default AddBtn; 32 | -------------------------------------------------------------------------------- /src/components/techs/TechSelectOptions.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react'; 2 | import { connect } from 'react-redux'; 3 | import PropTypes from 'prop-types'; 4 | import { getTechs } from '../../actions/techActions'; 5 | 6 | const TechSelectOptions = ({ getTechs, tech: { techs, loading } }) => { 7 | useEffect(() => { 8 | getTechs(); 9 | // eslint-disable-next-line 10 | }, []); 11 | 12 | return ( 13 | !loading && 14 | techs !== null && 15 | techs.map(t => ( 16 | 19 | )) 20 | ); 21 | }; 22 | 23 | TechSelectOptions.propTypes = { 24 | tech: PropTypes.object.isRequired, 25 | getTechs: PropTypes.func.isRequired 26 | }; 27 | 28 | const mapStateToProps = state => ({ 29 | tech: state.tech 30 | }); 31 | 32 | export default connect( 33 | mapStateToProps, 34 | { getTechs } 35 | )(TechSelectOptions); 36 | -------------------------------------------------------------------------------- /src/components/techs/TechItem.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { connect } from 'react-redux'; 3 | import PropTypes from 'prop-types'; 4 | import { deleteTech } from '../../actions/techActions'; 5 | import M from 'materialize-css/dist/js/materialize.min.js'; 6 | 7 | const TechItem = ({ tech: { id, firstName, lastName }, deleteTech }) => { 8 | const onDelete = () => { 9 | deleteTech(id); 10 | M.toast({ html: 'Technician deleted' }); 11 | }; 12 | 13 | return ( 14 |
  • 15 |
    16 | {firstName} {lastName} 17 | 18 | delete 19 | 20 |
    21 |
  • 22 | ); 23 | }; 24 | 25 | TechItem.propTypes = { 26 | tech: PropTypes.object.isRequired, 27 | deleteTech: PropTypes.func.isRequired 28 | }; 29 | 30 | export default connect( 31 | null, 32 | { deleteTech } 33 | )(TechItem); 34 | -------------------------------------------------------------------------------- /src/components/techs/TechListModal.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react'; 2 | import { connect } from 'react-redux'; 3 | import PropTypes from 'prop-types'; 4 | import TechItem from './TechItem'; 5 | import { getTechs } from '../../actions/techActions'; 6 | 7 | const TechListModal = ({ getTechs, tech: { techs, loading } }) => { 8 | useEffect(() => { 9 | getTechs(); 10 | // eslint-disable-next-line 11 | }, []); 12 | 13 | return ( 14 |
    15 |
    16 |

    Technician List

    17 |
      18 | {!loading && 19 | techs !== null && 20 | techs.map(tech => )} 21 |
    22 |
    23 |
    24 | ); 25 | }; 26 | 27 | TechListModal.propTypes = { 28 | tech: PropTypes.object.isRequired, 29 | getTechs: PropTypes.func.isRequired 30 | }; 31 | 32 | const mapStateToProps = state => ({ 33 | tech: state.tech 34 | }); 35 | 36 | export default connect( 37 | mapStateToProps, 38 | { getTechs } 39 | )(TechListModal); 40 | -------------------------------------------------------------------------------- /db.json: -------------------------------------------------------------------------------- 1 | { 2 | "logs": [ 3 | { 4 | "message": "Changed network card in server 007", 5 | "attention": false, 6 | "tech": "Sam Smith", 7 | "date": "2019-05-31T15:46:10.179Z", 8 | "id": 1 9 | }, 10 | { 11 | "id": 2, 12 | "message": "Fixed hard drive on workstation 002", 13 | "attention": false, 14 | "tech": "John Doe", 15 | "date": "2019-05-31T16:18:04.245Z" 16 | }, 17 | { 18 | "message": "1122 wireless down", 19 | "attention": true, 20 | "tech": "Sara Wilson", 21 | "date": "2019-05-31T15:46:48.690Z", 22 | "id": 3 23 | }, 24 | { 25 | "id": 4, 26 | "message": "Workstation 026 is up and running", 27 | "attention": false, 28 | "tech": "Sara Wilson", 29 | "date": "2019-05-31T19:57:35.544Z" 30 | } 31 | ], 32 | "techs": [ 33 | { 34 | "id": 1, 35 | "firstName": "John", 36 | "lastName": "Doe" 37 | }, 38 | { 39 | "id": 2, 40 | "firstName": "Sam", 41 | "lastName": "Smith" 42 | }, 43 | { 44 | "id": 3, 45 | "firstName": "Sara", 46 | "lastName": "Wilson" 47 | } 48 | ] 49 | } -------------------------------------------------------------------------------- /src/reducers/techReducer.js: -------------------------------------------------------------------------------- 1 | import { 2 | GET_TECHS, 3 | ADD_TECH, 4 | DELETE_TECH, 5 | SET_LOADING, 6 | TECHS_ERROR 7 | } from '../actions/types'; 8 | 9 | const initialState = { 10 | techs: null, 11 | loading: false, 12 | error: null 13 | }; 14 | 15 | export default (state = initialState, action) => { 16 | switch (action.type) { 17 | case GET_TECHS: 18 | return { 19 | ...state, 20 | techs: action.payload, 21 | loading: false 22 | }; 23 | case ADD_TECH: 24 | return { 25 | ...state, 26 | techs: [...state.techs, action.payload], 27 | loading: false 28 | }; 29 | case DELETE_TECH: 30 | return { 31 | ...state, 32 | techs: state.techs.filter(tech => tech.id !== action.payload), 33 | loading: false 34 | }; 35 | case SET_LOADING: 36 | return { 37 | ...state, 38 | loading: true 39 | }; 40 | case TECHS_ERROR: 41 | console.error(action.payload); 42 | return { 43 | ...state, 44 | error: action.payload, 45 | loading: false 46 | }; 47 | default: 48 | return state; 49 | } 50 | }; 51 | -------------------------------------------------------------------------------- /src/components/logs/Logs.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react'; 2 | import { connect } from 'react-redux'; 3 | import LogItem from './LogItem'; 4 | import Preloader from '../layout/Preloader'; 5 | import PropTypes from 'prop-types'; 6 | import { getLogs } from '../../actions/logActions'; 7 | 8 | const Logs = ({ log: { logs, loading }, getLogs }) => { 9 | useEffect(() => { 10 | getLogs(); 11 | // eslint-disable-next-line 12 | }, []); 13 | 14 | if (loading || logs === null) { 15 | return ; 16 | } 17 | 18 | return ( 19 |
      20 |
    • 21 |

      System Logs

      22 |
    • 23 | {!loading && logs.length === 0 ? ( 24 |

      No logs to show...

      25 | ) : ( 26 | logs.map(log => ) 27 | )} 28 |
    29 | ); 30 | }; 31 | 32 | Logs.propTypes = { 33 | log: PropTypes.object.isRequired, 34 | getLogs: PropTypes.func.isRequired 35 | }; 36 | 37 | const mapStateToProps = state => ({ 38 | log: state.log 39 | }); 40 | 41 | export default connect( 42 | mapStateToProps, 43 | { getLogs } 44 | )(Logs); 45 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import React, { Fragment, useEffect } from 'react'; 2 | import SearchBar from './components/layout/SearchBar'; 3 | import Logs from './components/logs/Logs'; 4 | import AddBtn from './components/layout/AddBtn'; 5 | import AddLogModal from './components/logs/AddLogModal'; 6 | import EditLogModal from './components/logs/EditLogModal'; 7 | import AddTechModal from './components/techs/AddTechModal'; 8 | import TechListModal from './components/techs/TechListModal'; 9 | import { Provider } from 'react-redux'; 10 | import store from './store'; 11 | 12 | import 'materialize-css/dist/css/materialize.min.css'; 13 | import M from 'materialize-css/dist/js/materialize.min.js'; 14 | import './App.css'; 15 | 16 | const App = () => { 17 | useEffect(() => { 18 | // Init Materialize JS 19 | M.AutoInit(); 20 | }); 21 | return ( 22 | 23 | 24 | 25 |
    26 | 27 | 28 | 29 | 30 | 31 | 32 |
    33 |
    34 |
    35 | ); 36 | }; 37 | 38 | export default App; 39 | -------------------------------------------------------------------------------- /src/components/layout/SearchBar.js: -------------------------------------------------------------------------------- 1 | import React, { useRef } from 'react'; 2 | import { connect } from 'react-redux'; 3 | import PropTypes from 'prop-types'; 4 | import { searchLogs } from '../../actions/logActions'; 5 | 6 | const SearchBar = ({ searchLogs }) => { 7 | const text = useRef(''); 8 | 9 | const onChange = e => { 10 | searchLogs(text.current.value); 11 | }; 12 | 13 | return ( 14 | 33 | ); 34 | }; 35 | 36 | SearchBar.propTypes = { 37 | searchLogs: PropTypes.func.isRequired 38 | }; 39 | 40 | export default connect( 41 | null, 42 | { searchLogs } 43 | )(SearchBar); 44 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "it-logger", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "materialize-css": "^1.0.0", 7 | "moment": "^2.24.0", 8 | "react": "^16.8.6", 9 | "react-dom": "^16.8.6", 10 | "react-moment": "^0.9.2", 11 | "react-redux": "^7.0.3", 12 | "react-scripts": "3.0.1", 13 | "redux": "^4.0.1", 14 | "redux-devtools-extension": "^2.13.8", 15 | "redux-thunk": "^2.3.0" 16 | }, 17 | "scripts": { 18 | "start": "react-scripts start", 19 | "json-server": "json-server --watch db.json --port 5000", 20 | "dev": "concurrently \"npm start\" \"npm run json-server\"", 21 | "build": "react-scripts build", 22 | "test": "react-scripts test", 23 | "eject": "react-scripts eject" 24 | }, 25 | "eslintConfig": { 26 | "extends": "react-app" 27 | }, 28 | "browserslist": { 29 | "production": [ 30 | ">0.2%", 31 | "not dead", 32 | "not op_mini all" 33 | ], 34 | "development": [ 35 | "last 1 chrome version", 36 | "last 1 firefox version", 37 | "last 1 safari version" 38 | ] 39 | }, 40 | "devDependencies": { 41 | "concurrently": "^4.1.0", 42 | "json-server": "^0.15.0" 43 | }, 44 | "proxy": "http://localhost:5000" 45 | } 46 | -------------------------------------------------------------------------------- /src/components/logs/LogItem.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Moment from 'react-moment'; 3 | import { connect } from 'react-redux'; 4 | import PropTypes from 'prop-types'; 5 | import { deleteLog, setCurrent } from '../../actions/logActions'; 6 | 7 | import M from 'materialize-css/dist/js/materialize.min.js'; 8 | 9 | const LogItem = ({ log, deleteLog, setCurrent }) => { 10 | const onDelete = () => { 11 | deleteLog(log.id); 12 | M.toast({ html: 'Log Deleted' }); 13 | }; 14 | 15 | return ( 16 |
  • 17 |
    18 | setCurrent(log)} 24 | > 25 | {log.message} 26 | 27 |
    28 | 29 | ID #{log.id} last updated by{' '} 30 | {log.tech} on{' '} 31 | {log.date} 32 | 33 | 34 | delete 35 | 36 |
    37 |
  • 38 | ); 39 | }; 40 | 41 | LogItem.propTypes = { 42 | log: PropTypes.object.isRequired, 43 | deleteLog: PropTypes.func.isRequired, 44 | setCurrent: PropTypes.func.isRequired 45 | }; 46 | 47 | export default connect( 48 | null, 49 | { deleteLog, setCurrent } 50 | )(LogItem); 51 | -------------------------------------------------------------------------------- /src/actions/techActions.js: -------------------------------------------------------------------------------- 1 | import { 2 | GET_TECHS, 3 | ADD_TECH, 4 | DELETE_TECH, 5 | SET_LOADING, 6 | TECHS_ERROR 7 | } from './types'; 8 | 9 | // Get techs from server 10 | export const getTechs = () => async dispatch => { 11 | try { 12 | setLoading(); 13 | 14 | const res = await fetch('/techs'); 15 | const data = await res.json(); 16 | 17 | dispatch({ 18 | type: GET_TECHS, 19 | payload: data 20 | }); 21 | } catch (err) { 22 | dispatch({ 23 | type: TECHS_ERROR, 24 | payload: err.response.statusText 25 | }); 26 | } 27 | }; 28 | 29 | // Add technician to server 30 | export const addTech = tech => async dispatch => { 31 | try { 32 | setLoading(); 33 | 34 | const res = await fetch('/techs', { 35 | method: 'POST', 36 | body: JSON.stringify(tech), 37 | headers: { 38 | 'Content-Type': 'application/json' 39 | } 40 | }); 41 | const data = await res.json(); 42 | 43 | dispatch({ 44 | type: ADD_TECH, 45 | payload: data 46 | }); 47 | } catch (err) { 48 | dispatch({ 49 | type: TECHS_ERROR, 50 | payload: err.response.statusText 51 | }); 52 | } 53 | }; 54 | 55 | export const deleteTech = id => async dispatch => { 56 | try { 57 | setLoading(); 58 | 59 | await fetch(`/techs/${id}`, { 60 | method: 'DELETE' 61 | }); 62 | 63 | dispatch({ 64 | type: DELETE_TECH, 65 | payload: id 66 | }); 67 | } catch (err) { 68 | dispatch({ 69 | type: TECHS_ERROR, 70 | payload: err.response.statusText 71 | }); 72 | } 73 | }; 74 | 75 | // Set loading to true 76 | export const setLoading = () => { 77 | return { 78 | type: SET_LOADING 79 | }; 80 | }; 81 | -------------------------------------------------------------------------------- /src/reducers/logReducer.js: -------------------------------------------------------------------------------- 1 | import { 2 | GET_LOGS, 3 | SET_LOADING, 4 | LOGS_ERROR, 5 | ADD_LOG, 6 | DELETE_LOG, 7 | UPDATE_LOG, 8 | SEARCH_LOGS, 9 | SET_CURRENT, 10 | CLEAR_CURRENT 11 | } from '../actions/types'; 12 | 13 | const initialState = { 14 | logs: null, 15 | current: null, 16 | loading: false, 17 | error: null 18 | }; 19 | 20 | export default (state = initialState, action) => { 21 | switch (action.type) { 22 | case GET_LOGS: 23 | return { 24 | ...state, 25 | logs: action.payload, 26 | loading: false 27 | }; 28 | case ADD_LOG: 29 | return { 30 | ...state, 31 | logs: [...state.logs, action.payload], 32 | loading: false 33 | }; 34 | case DELETE_LOG: 35 | return { 36 | ...state, 37 | logs: state.logs.filter(log => log.id !== action.payload), 38 | loading: false 39 | }; 40 | case UPDATE_LOG: 41 | return { 42 | ...state, 43 | logs: state.logs.map(log => 44 | log.id === action.payload.id ? action.payload : log 45 | ) 46 | }; 47 | case SEARCH_LOGS: 48 | return { 49 | ...state, 50 | logs: action.payload 51 | }; 52 | case SET_CURRENT: 53 | return { 54 | ...state, 55 | current: action.payload 56 | }; 57 | case CLEAR_CURRENT: 58 | return { 59 | ...state, 60 | current: null 61 | }; 62 | case SET_LOADING: 63 | return { 64 | ...state, 65 | loading: true 66 | }; 67 | case LOGS_ERROR: 68 | console.error(action.payload); 69 | return { 70 | ...state, 71 | error: action.payload 72 | }; 73 | default: 74 | return state; 75 | } 76 | }; 77 | -------------------------------------------------------------------------------- /src/components/techs/AddTechModal.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { connect } from 'react-redux'; 3 | import PropTypes from 'prop-types'; 4 | import { addTech } from '../../actions/techActions'; 5 | import M from 'materialize-css/dist/js/materialize.min.js'; 6 | 7 | const AddTechModal = ({ addTech }) => { 8 | const [firstName, setFirstName] = useState(''); 9 | const [lastName, setLastName] = useState(''); 10 | 11 | const onSubmit = () => { 12 | if (firstName === '' || lastName === '') { 13 | M.toast({ html: 'Please enter the first and last name' }); 14 | } else { 15 | addTech({ 16 | firstName, 17 | lastName 18 | }); 19 | 20 | M.toast({ html: `${firstName} ${lastName} was added as a tech` }); 21 | 22 | // Clear Fields 23 | setFirstName(''); 24 | setLastName(''); 25 | } 26 | }; 27 | 28 | return ( 29 |
    30 |
    31 |

    New Technician

    32 |
    33 |
    34 | setFirstName(e.target.value)} 39 | /> 40 | 43 |
    44 |
    45 | 46 |
    47 |
    48 | setLastName(e.target.value)} 53 | /> 54 | 57 |
    58 |
    59 |
    60 |
    61 | 66 | Enter 67 | 68 |
    69 |
    70 | ); 71 | }; 72 | 73 | AddTechModal.propTypes = { 74 | addTech: PropTypes.func.isRequired 75 | }; 76 | 77 | export default connect( 78 | null, 79 | { addTech } 80 | )(AddTechModal); 81 | -------------------------------------------------------------------------------- /src/components/logs/AddLogModal.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import TechSelectOptions from '../techs/TechSelectOptions'; 3 | import { connect } from 'react-redux'; 4 | import PropTypes from 'prop-types'; 5 | import { addLog } from '../../actions/logActions'; 6 | import M from 'materialize-css/dist/js/materialize.min.js'; 7 | 8 | const AddLogModal = ({ addLog }) => { 9 | const [message, setMessage] = useState(''); 10 | const [attention, setAttention] = useState(false); 11 | const [tech, setTech] = useState(''); 12 | 13 | const onSubmit = () => { 14 | if (message === '' || tech === '') { 15 | M.toast({ html: 'Please enter a message and tech' }); 16 | } else { 17 | const newLog = { 18 | message, 19 | attention, 20 | tech, 21 | date: new Date() 22 | }; 23 | 24 | addLog(newLog); 25 | 26 | M.toast({ html: `Log added by ${tech}` }); 27 | 28 | // Clear Fields 29 | setMessage(''); 30 | setTech(''); 31 | setAttention(false); 32 | } 33 | }; 34 | 35 | return ( 36 |
    37 |
    38 |

    Enter System Log

    39 |
    40 |
    41 | setMessage(e.target.value)} 46 | /> 47 | 50 |
    51 |
    52 | 53 |
    54 |
    55 | 66 |
    67 |
    68 | 69 |
    70 |
    71 |

    72 | 82 |

    83 |
    84 |
    85 |
    86 |
    87 | 92 | Enter 93 | 94 |
    95 |
    96 | ); 97 | }; 98 | 99 | AddLogModal.propTypes = { 100 | addLog: PropTypes.func.isRequired 101 | }; 102 | 103 | const modalStyle = { 104 | width: '75%', 105 | height: '75%' 106 | }; 107 | 108 | export default connect( 109 | null, 110 | { addLog } 111 | )(AddLogModal); 112 | -------------------------------------------------------------------------------- /src/actions/logActions.js: -------------------------------------------------------------------------------- 1 | import { 2 | GET_LOGS, 3 | SET_LOADING, 4 | LOGS_ERROR, 5 | ADD_LOG, 6 | DELETE_LOG, 7 | UPDATE_LOG, 8 | SEARCH_LOGS, 9 | SET_CURRENT, 10 | CLEAR_CURRENT 11 | } from './types'; 12 | 13 | // export const getLogs = () => { 14 | // return async dispatch => { 15 | // setLoading(); 16 | 17 | // const res = await fetch('/logs'); 18 | // const data = await res.json(); 19 | 20 | // dispatch({ 21 | // type: GET_LOGS, 22 | // payload: data 23 | // }); 24 | // }; 25 | // }; 26 | 27 | // Get logs from server 28 | export const getLogs = () => async dispatch => { 29 | try { 30 | setLoading(); 31 | 32 | const res = await fetch('/logs'); 33 | const data = await res.json(); 34 | 35 | dispatch({ 36 | type: GET_LOGS, 37 | payload: data 38 | }); 39 | } catch (err) { 40 | dispatch({ 41 | type: LOGS_ERROR, 42 | payload: err.response.statusText 43 | }); 44 | } 45 | }; 46 | 47 | // Add new log 48 | export const addLog = log => async dispatch => { 49 | try { 50 | setLoading(); 51 | 52 | const res = await fetch('/logs', { 53 | method: 'POST', 54 | body: JSON.stringify(log), 55 | headers: { 56 | 'Content-Type': 'application/json' 57 | } 58 | }); 59 | const data = await res.json(); 60 | 61 | dispatch({ 62 | type: ADD_LOG, 63 | payload: data 64 | }); 65 | } catch (err) { 66 | dispatch({ 67 | type: LOGS_ERROR, 68 | payload: err.response.statusText 69 | }); 70 | } 71 | }; 72 | 73 | // Delete log from server 74 | export const deleteLog = id => async dispatch => { 75 | try { 76 | setLoading(); 77 | 78 | await fetch(`/logs/${id}`, { 79 | method: 'DELETE' 80 | }); 81 | 82 | dispatch({ 83 | type: DELETE_LOG, 84 | payload: id 85 | }); 86 | } catch (err) { 87 | dispatch({ 88 | type: LOGS_ERROR, 89 | payload: err.response.statusText 90 | }); 91 | } 92 | }; 93 | 94 | // Update log on server 95 | export const updateLog = log => async dispatch => { 96 | try { 97 | setLoading(); 98 | 99 | const res = await fetch(`/logs/${log.id}`, { 100 | method: 'PUT', 101 | body: JSON.stringify(log), 102 | headers: { 103 | 'Content-Type': 'application/json' 104 | } 105 | }); 106 | 107 | const data = await res.json(); 108 | 109 | dispatch({ 110 | type: UPDATE_LOG, 111 | payload: data 112 | }); 113 | } catch (err) { 114 | dispatch({ 115 | type: LOGS_ERROR, 116 | payload: err.response.statusText 117 | }); 118 | } 119 | }; 120 | 121 | // Search server logs 122 | export const searchLogs = text => async dispatch => { 123 | try { 124 | setLoading(); 125 | 126 | const res = await fetch(`/logs?q=${text}`); 127 | const data = await res.json(); 128 | 129 | dispatch({ 130 | type: SEARCH_LOGS, 131 | payload: data 132 | }); 133 | } catch (err) { 134 | dispatch({ 135 | type: LOGS_ERROR, 136 | payload: err.response.statusText 137 | }); 138 | } 139 | }; 140 | 141 | // Set current log 142 | export const setCurrent = log => { 143 | return { 144 | type: SET_CURRENT, 145 | payload: log 146 | }; 147 | }; 148 | 149 | // Clear current log 150 | export const clearCurrent = () => { 151 | return { 152 | type: CLEAR_CURRENT 153 | }; 154 | }; 155 | 156 | // Set loading to true 157 | export const setLoading = () => { 158 | return { 159 | type: SET_LOADING 160 | }; 161 | }; 162 | -------------------------------------------------------------------------------- /src/components/logs/EditLogModal.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | import TechSelectOptions from '../techs/TechSelectOptions'; 3 | import { connect } from 'react-redux'; 4 | import PropTypes from 'prop-types'; 5 | import M from 'materialize-css/dist/js/materialize.min.js'; 6 | import { updateLog } from '../../actions/logActions'; 7 | 8 | const EditLogModal = ({ current, updateLog }) => { 9 | const [message, setMessage] = useState(''); 10 | const [attention, setAttention] = useState(false); 11 | const [tech, setTech] = useState(''); 12 | 13 | useEffect(() => { 14 | if (current) { 15 | setMessage(current.message); 16 | setAttention(current.attention); 17 | setTech(current.tech); 18 | } 19 | }, [current]); 20 | 21 | const onSubmit = () => { 22 | if (message === '' || tech === '') { 23 | M.toast({ html: 'Please enter a message and tech' }); 24 | } else { 25 | const updLog = { 26 | id: current.id, 27 | message, 28 | attention, 29 | tech, 30 | date: new Date() 31 | }; 32 | 33 | updateLog(updLog); 34 | M.toast({ html: `Log updated by ${tech}` }); 35 | 36 | // Clear Fields 37 | setMessage(''); 38 | setTech(''); 39 | setAttention(false); 40 | } 41 | }; 42 | 43 | return ( 44 |
    45 |
    46 |

    Enter System Log

    47 |
    48 |
    49 | setMessage(e.target.value)} 54 | /> 55 |
    56 |
    57 | 58 |
    59 |
    60 | 71 |
    72 |
    73 | 74 |
    75 |
    76 |

    77 | 87 |

    88 |
    89 |
    90 |
    91 |
    92 | 97 | Enter 98 | 99 |
    100 |
    101 | ); 102 | }; 103 | 104 | const modalStyle = { 105 | width: '75%', 106 | height: '75%' 107 | }; 108 | 109 | EditLogModal.propTypes = { 110 | current: PropTypes.object, 111 | updateLog: PropTypes.func.isRequired 112 | }; 113 | 114 | const mapStateToProps = state => ({ 115 | current: state.log.current 116 | }); 117 | 118 | export default connect( 119 | mapStateToProps, 120 | { updateLog } 121 | )(EditLogModal); 122 | --------------------------------------------------------------------------------