From 1242c96a48ac06f02d34e5742eb43a69ca6c1411 Mon Sep 17 00:00:00 2001 From: jay2452 Date: Tue, 7 Jul 2020 23:58:26 +0530 Subject: [PATCH 1/6] project setup with webpack, react and babel --- .babelrc | 4 ++++ .gitignore | 3 +++ package.json | 44 ++++++++++++++++++++++++++++++++++++++++++++ src/App.jsx | 10 ++++++++++ src/index.css | 0 src/index.html | 15 +++++++++++++++ src/index.js | 16 ++++++++++++++++ src/theme.js | 7 +++++++ webpack.config.js | 41 +++++++++++++++++++++++++++++++++++++++++ 9 files changed, 140 insertions(+) create mode 100644 .babelrc create mode 100644 .gitignore create mode 100644 package.json create mode 100644 src/App.jsx create mode 100644 src/index.css create mode 100644 src/index.html create mode 100644 src/index.js create mode 100644 src/theme.js create mode 100644 webpack.config.js diff --git a/.babelrc b/.babelrc new file mode 100644 index 00000000..14a6811b --- /dev/null +++ b/.babelrc @@ -0,0 +1,4 @@ +{ + "presets": [ "@babel/env", "@babel/react" ], + "plugins": [ "@babel/plugin-proposal-class-properties" ] +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..2e2d9d30 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +node_modules +package-lock.json +/dist \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 00000000..f8a3a79d --- /dev/null +++ b/package.json @@ -0,0 +1,44 @@ +{ + "name": "system-test", + "version": "1.0.0", + "description": "Sureify - Front end system test", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1", + "start": "babel-node ./node_modules/webpack-dev-server/bin/webpack-dev-server --open" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/jay2452/system-test.git" + }, + "author": "", + "license": "ISC", + "bugs": { + "url": "https://github.com/jay2452/system-test/issues" + }, + "homepage": "https://github.com/jay2452/system-test#readme", + "devDependencies": { + "@babel/core": "7.6.4", + "@babel/node": "7.6.3", + "@babel/plugin-proposal-class-properties": "^7.5.5", + "@babel/preset-env": "7.6.3", + "@babel/preset-react": "7.6.3", + "babel-loader": "8.0.6", + "css-loader": "3.2.0", + "file-loader": "^4.2.0", + "html-webpack-plugin": "3.2.0", + "node-sass": "^4.14.1", + "path": "0.12.7", + "sass-loader": "8.0.0", + "style-loader": "1.0.0", + "webpack": "4.41.2", + "webpack-cli": "3.3.9", + "webpack-dev-server": "^3.11.0" + }, + "dependencies": { + "@material-ui/core": "^4.9.3", + "@material-ui/icons": "^4.9.1", + "react": "16.11.0", + "react-dom": "16.11.0" + } +} diff --git a/src/App.jsx b/src/App.jsx new file mode 100644 index 00000000..3eeb8bc0 --- /dev/null +++ b/src/App.jsx @@ -0,0 +1,10 @@ +import React from'react'; + + +const App = props => { + return ( +
Todo App
+ ) +}; + +export default App; \ No newline at end of file diff --git a/src/index.css b/src/index.css new file mode 100644 index 00000000..e69de29b diff --git a/src/index.html b/src/index.html new file mode 100644 index 00000000..bcc65d2a --- /dev/null +++ b/src/index.html @@ -0,0 +1,15 @@ + + + + + + + + + +
+ +
+ + + \ No newline at end of file diff --git a/src/index.js b/src/index.js new file mode 100644 index 00000000..019804dd --- /dev/null +++ b/src/index.js @@ -0,0 +1,16 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import CssBaseline from '@material-ui/core/CssBaseline'; +import { ThemeProvider } from "@material-ui/core"; +import App from './App.jsx'; + +import theme from './theme'; + + +ReactDOM.render( + + + + + , document.getElementById('root') +); \ No newline at end of file diff --git a/src/theme.js b/src/theme.js new file mode 100644 index 00000000..eec2f3bc --- /dev/null +++ b/src/theme.js @@ -0,0 +1,7 @@ +import { createMuiTheme } from "@material-ui/core"; + +const theme = createMuiTheme({ + +}); + +export default theme; \ No newline at end of file diff --git a/webpack.config.js b/webpack.config.js new file mode 100644 index 00000000..5de38d23 --- /dev/null +++ b/webpack.config.js @@ -0,0 +1,41 @@ +// old// const path = require('path');// const HtmlWebpackPlugin = require('html-webpack-plugin'); +// new +import path from 'path'; +import HtmlWebpackPlugin from 'html-webpack-plugin'; +module.exports = { + entry: path.join(__dirname,'src','index.js'), + output: { + path: path.join(__dirname,'build'), + filename: 'index.bundle.js' + }, + mode: process.env.NODE_ENV || 'development', + resolve: { + modules: [path.resolve(__dirname, 'src'), 'node_modules'] + }, + devServer: { + contentBase: path.join(__dirname,'src') + }, + module: { + rules: [{ + // this is so that we can compile any React, + // ES6 and above into normal ES5 syntax + test: /\.(js|jsx)$/, + // we do not want anything from node_modules to be compiled + exclude: /node_modules/, + use: ['babel-loader'] + },{ + test: /\.(css|scss)$/, + use: [ + "style-loader", // creates style nodes from JS strings + "css-loader", // translates CSS into CommonJS + "sass-loader" // compiles Sass to CSS, using Node Sass by default + ] + },{ + test: /\.(jpg|jpeg|png|gif|mp3|svg)$/, + loaders: ['file-loader'] + }] + }, + plugins: [ + new HtmlWebpackPlugin({template: path.join(__dirname,'src','index.html') }) + ] +}; From c06b33312202a292a609a852a15f4e407f39ab11 Mon Sep 17 00:00:00 2001 From: jay2452 Date: Wed, 8 Jul 2020 04:04:05 +0530 Subject: [PATCH 2/6] adding component and containers --- package.json | 1 + src/App.jsx | 21 +++++- src/Components/AddTaskButton/index.js | 28 ++++++++ src/Components/Dropdown/index.js | 33 +++++++++ src/Components/TodoForm/index.js | 100 ++++++++++++++++++++++++++ src/Containers/TodoList/index.js | 71 ++++++++++++++++++ src/Containers/TodoTabs/TabPanel.js | 32 +++++++++ src/Containers/TodoTabs/index.js | 52 ++++++++++++++ src/index.html | 3 +- src/theme.js | 8 ++- 10 files changed, 346 insertions(+), 3 deletions(-) create mode 100644 src/Components/AddTaskButton/index.js create mode 100644 src/Components/Dropdown/index.js create mode 100644 src/Components/TodoForm/index.js create mode 100644 src/Containers/TodoList/index.js create mode 100644 src/Containers/TodoTabs/TabPanel.js create mode 100644 src/Containers/TodoTabs/index.js diff --git a/package.json b/package.json index f8a3a79d..46c44734 100644 --- a/package.json +++ b/package.json @@ -38,6 +38,7 @@ "dependencies": { "@material-ui/core": "^4.9.3", "@material-ui/icons": "^4.9.1", + "material-table": "^1.64.0", "react": "16.11.0", "react-dom": "16.11.0" } diff --git a/src/App.jsx b/src/App.jsx index 3eeb8bc0..f6677977 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,9 +1,28 @@ import React from'react'; +import AddTaskButton from './Components/AddTaskButton'; +import TodoForm from './Components/TodoForm'; +import TodoTabs from './Containers/TodoTabs'; const App = props => { + const [isFormOpen, setIsFormOpen] = React.useState(false); + const [isEditMode, setIsEditMode] = React.useState(true); + + const toggleOpenForm = React.useCallback(() => { + setIsFormOpen(!isFormOpen); + }, [isFormOpen]); + return ( -
Todo App
+
+ {/* render tabs */} + + + +
) }; diff --git a/src/Components/AddTaskButton/index.js b/src/Components/AddTaskButton/index.js new file mode 100644 index 00000000..49a91db7 --- /dev/null +++ b/src/Components/AddTaskButton/index.js @@ -0,0 +1,28 @@ +import React from "react"; + +import { makeStyles } from '@material-ui/core/styles'; +import IconButton from '@material-ui/core/IconButton'; +import AddCircleOutlinedIcon from '@material-ui/icons/AddCircleOutlined'; + +const useStyles = makeStyles(theme => ({ + addtaskButton: { + position: 'absolute', + bottom: 0, + right: 0 + } +})); + +const AddTaskButton = props => { + + const classes = useStyles(); + + return ( +
+ + + +
+ ); +}; + +export default AddTaskButton; \ No newline at end of file diff --git a/src/Components/Dropdown/index.js b/src/Components/Dropdown/index.js new file mode 100644 index 00000000..4f1ce31a --- /dev/null +++ b/src/Components/Dropdown/index.js @@ -0,0 +1,33 @@ +import React from 'react'; +import FormControl from '@material-ui/core/FormControl'; +import Select from '@material-ui/core/Select'; +import MenuItem from '@material-ui/core/MenuItem'; +import InputLabel from '@material-ui/core/InputLabel'; + +const Dropdown = props => { + + const { data, valueColumn, displayColumn, handleChange, isDisabled, selectedValue, displayLabel } = props; + + return ( + + {displayLabel} + + + ) +}; + +export default Dropdown; \ No newline at end of file diff --git a/src/Components/TodoForm/index.js b/src/Components/TodoForm/index.js new file mode 100644 index 00000000..d9e7408f --- /dev/null +++ b/src/Components/TodoForm/index.js @@ -0,0 +1,100 @@ +import React from "react"; + +import { makeStyles } from '@material-ui/core/styles'; +import Button from '@material-ui/core/Button'; +import TextField from '@material-ui/core/TextField'; +import Dialog from '@material-ui/core/Dialog'; +import DialogActions from '@material-ui/core/DialogActions'; +import DialogContent from '@material-ui/core/DialogContent'; +import DialogTitle from '@material-ui/core/DialogTitle'; +import Dropdown from "../Dropdown"; + + + +const useStyles = makeStyles(theme => ({ + root: { + + }, + formControl: { + + }, + dueDate: { + marginLeft: theme.spacing(1), + marginRight: theme.spacing(1), + width: 200, + }, +})); + +const priorities = ['low', 'medium', 'high'].map(item => ({value: item, label: item})); + +const TodoForm = props => { + + const classes = useStyles(); + const {isFormOpen, isEditMode} = props; + return ( + + Add / Edit Todo + + + +

+
+ + + + + +
+
+ + + + +
+ ) +}; + +export default TodoForm; \ No newline at end of file diff --git a/src/Containers/TodoList/index.js b/src/Containers/TodoList/index.js new file mode 100644 index 00000000..e68f8f89 --- /dev/null +++ b/src/Containers/TodoList/index.js @@ -0,0 +1,71 @@ +import React from 'react'; +import MaterialTable from "material-table"; +import Dropdown from '../../Components/Dropdown'; +import AddCircleOutlinedIcon from '@material-ui/icons/AddCircleOutlined'; +const groupByFields = ["createdAt", "dueDate", "priority"].map(item => ({value: item, label: item})); + +const TodoList = props => { + //Summary | Priority | Created On | Due Date | Actions + const [state, setState] = React.useState({ + columns: [ + { title: 'Summary', field: 'summary' }, + { title: 'Priority', field: 'priority' }, + { title: 'Created On', field: 'createdOn', type: 'datetime' }, + { + title: 'Due Date', + field: 'dueDate', + type: 'datetime' + } + ], + data: [{ + summary: "test 1", + priority: "High", + createdOn: new Date(), + dueDate: new Date() + 12 + }], + }); + + return ( + +
+ + alert("Done") + },{ + icon: 'edit', + tooltip: 'Edit User', + onClick: (event, rowData) => alert('You are editing ' + rowData.name) + }, + { + icon: 'delete', + tooltip: 'Delete User', + onClick: (event, rowData) => confirm('You want to delete ' + rowData.name) + } + ]} + /> +
+ ); +}; + +export default TodoList; \ No newline at end of file diff --git a/src/Containers/TodoTabs/TabPanel.js b/src/Containers/TodoTabs/TabPanel.js new file mode 100644 index 00000000..e0f314dd --- /dev/null +++ b/src/Containers/TodoTabs/TabPanel.js @@ -0,0 +1,32 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import Typography from '@material-ui/core/Typography'; +import Box from '@material-ui/core/Box'; + +function TabPanel(props) { + const { children, value, index, ...other } = props; + + return ( + + ); + } + + TabPanel.propTypes = { + children: PropTypes.node, + index: PropTypes.any.isRequired, + value: PropTypes.any.isRequired, + }; + +export default TabPanel; \ No newline at end of file diff --git a/src/Containers/TodoTabs/index.js b/src/Containers/TodoTabs/index.js new file mode 100644 index 00000000..3ac7bae9 --- /dev/null +++ b/src/Containers/TodoTabs/index.js @@ -0,0 +1,52 @@ +import React from 'react'; +import { makeStyles } from '@material-ui/core/styles'; +import Paper from '@material-ui/core/Paper'; +import Tabs from '@material-ui/core/Tabs'; +import Tab from '@material-ui/core/Tab'; + +import TabPanel from './TabPanel'; +import TodoList from '../TodoList'; + +const useStyles = makeStyles({ + root: { + flexGrow: 1, + }, +}); + +const TodoTabs = (props) => { + const classes = useStyles(); + const [value, setValue] = React.useState(0); + + const handleChange = (event, newValue) => { + setValue(newValue); + }; + + return ( + + + + + + + + + + + + + + + + + + + ); +} + +export default TodoTabs; diff --git a/src/index.html b/src/index.html index bcc65d2a..bfc9b657 100644 --- a/src/index.html +++ b/src/index.html @@ -2,7 +2,8 @@ - + Todo Application + diff --git a/src/theme.js b/src/theme.js index eec2f3bc..2324afe0 100644 --- a/src/theme.js +++ b/src/theme.js @@ -1,7 +1,13 @@ import { createMuiTheme } from "@material-ui/core"; const theme = createMuiTheme({ - + overrides: { + MuiFormControl: { + root:{ + minWidth: '120px' + } + } + } }); export default theme; \ No newline at end of file From accb94aa7a0a33fda69f1c836a5a24eae7bd52e2 Mon Sep 17 00:00:00 2001 From: jay2452 Date: Wed, 8 Jul 2020 10:14:12 +0530 Subject: [PATCH 3/6] table rendering --- package.json | 4 +- src/Components/Dropdown/index.js | 2 +- src/Components/TodoForm/index.js | 54 ++++++-- src/Containers/TodoList/Table.js | 42 +++++++ src/Containers/TodoList/index.js | 117 ++++++++++-------- src/Containers/TodoTabs/index.js | 10 +- src/index.js | 9 +- .../actionCreator/todoListActionCreator.js | 8 ++ src/store/reducers/index.js | 7 ++ src/store/reducers/todoReducer.js | 23 ++++ src/store/staticData/todoListData.js | 3 + 11 files changed, 210 insertions(+), 69 deletions(-) create mode 100644 src/Containers/TodoList/Table.js create mode 100644 src/store/actionCreator/todoListActionCreator.js create mode 100644 src/store/reducers/index.js create mode 100644 src/store/reducers/todoReducer.js create mode 100644 src/store/staticData/todoListData.js diff --git a/package.json b/package.json index 46c44734..7313953e 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,8 @@ "@material-ui/icons": "^4.9.1", "material-table": "^1.64.0", "react": "16.11.0", - "react-dom": "16.11.0" + "react-dom": "16.11.0", + "react-redux": "7.1.1", + "redux": "4.0.4" } } diff --git a/src/Components/Dropdown/index.js b/src/Components/Dropdown/index.js index 4f1ce31a..459ccceb 100644 --- a/src/Components/Dropdown/index.js +++ b/src/Components/Dropdown/index.js @@ -16,7 +16,7 @@ const Dropdown = props => { id="todoform-dropdown-select" value={selectedValue} disabled={isDisabled} - // onChange={props.handleChange} + onChange={handleChange} > { data.map((item, i) => ( diff --git a/src/Components/TodoForm/index.js b/src/Components/TodoForm/index.js index d9e7408f..396cdb0c 100644 --- a/src/Components/TodoForm/index.js +++ b/src/Components/TodoForm/index.js @@ -1,5 +1,5 @@ import React from "react"; - +import { connect } from 'react-redux'; import { makeStyles } from '@material-ui/core/styles'; import Button from '@material-ui/core/Button'; import TextField from '@material-ui/core/TextField'; @@ -31,6 +31,28 @@ const TodoForm = props => { const classes = useStyles(); const {isFormOpen, isEditMode} = props; + + const [priority, setPriority] = React.useState('low'); + const [summary, setSummary] = React.useState(''); + const [description, setDescription] = React.useState(''); + const [dueDate, setDueDate] = React.useState(new Date()); + + const handlePriority = (e) => { + setPriority(e.target.value); + } + + const handleSaveTodo = () => { + const values = { + summary, + description, + dueDate, + priority, + createdOn: new Date() + }; + props.saveTodoItem(values); + props.handleClose(); + } + return ( { autoFocus margin="dense" id="todo-title" - label="Title" + label="Summary" type="text" fullWidth autoFocus disabled={!isEditMode} + value={summary} + onChange={(e) => { + setSummary(e.target.value); + }} /> { rowsMax={4} fullWidth disabled={!isEditMode} - // value={value} - // onChange={handleChange} + value={description} + onChange={(e) => setDescription(e.target.value)} />

@@ -65,22 +91,23 @@ const TodoForm = props => { isDisabled={!isEditMode} valueColumn="value" displayColumn="label" - selectedValue={priorities[0].value} + selectedValue={priority} data={priorities} displayLabel="Priority" - // handleChange + handleChange={handlePriority} /> setDueDate(e.target.value)} />
@@ -89,7 +116,7 @@ const TodoForm = props => { - @@ -97,4 +124,13 @@ const TodoForm = props => { ) }; -export default TodoForm; \ No newline at end of file +const mapDispatchToProps = dispatch => { + return { + saveTodoItem: (values) => dispatch({ + type: "ADD_TODO_LIST", + values + }) + } +} + +export default connect(null, mapDispatchToProps)(TodoForm); \ No newline at end of file diff --git a/src/Containers/TodoList/Table.js b/src/Containers/TodoList/Table.js new file mode 100644 index 00000000..a64b5156 --- /dev/null +++ b/src/Containers/TodoList/Table.js @@ -0,0 +1,42 @@ +import React from 'react'; +import MaterialTable from "material-table"; + + +const Table = props => { + const { columns, data} = props; + return ( +
+ alert("Done") + }, { + icon: 'edit', + tooltip: 'Edit User', + onClick: (event, rowData) => alert('You are editing ' + rowData.name) + }, + { + icon: 'delete', + tooltip: 'Delete User', + onClick: (event, rowData) => confirm('You want to delete ' + rowData.name) + } + ]} + /> +
+ ) +}; + +export default Table; \ No newline at end of file diff --git a/src/Containers/TodoList/index.js b/src/Containers/TodoList/index.js index e68f8f89..6b733c2d 100644 --- a/src/Containers/TodoList/index.js +++ b/src/Containers/TodoList/index.js @@ -1,71 +1,84 @@ import React from 'react'; -import MaterialTable from "material-table"; + import Dropdown from '../../Components/Dropdown'; -import AddCircleOutlinedIcon from '@material-ui/icons/AddCircleOutlined'; -const groupByFields = ["createdAt", "dueDate", "priority"].map(item => ({value: item, label: item})); +import { connect } from 'react-redux'; +// import AddCircleOutlinedIcon from '@material-ui/icons/AddCircleOutlined'; +import Table from './Table'; +const groupByFields = ['none', "createdAt", "dueDate", "priority"].map(item => ({ value: item, label: item })); const TodoList = props => { //Summary | Priority | Created On | Due Date | Actions - const [state, setState] = React.useState({ - columns: [ - { title: 'Summary', field: 'summary' }, - { title: 'Priority', field: 'priority' }, - { title: 'Created On', field: 'createdOn', type: 'datetime' }, - { - title: 'Due Date', - field: 'dueDate', - type: 'datetime' + const [columns, setColumns] = React.useState([ + { title: 'Summary', field: 'summary' }, + { title: 'Priority', field: 'priority' }, + { title: 'Created On', field: 'createdOn', type: 'datetime' }, + { + title: 'Due Date', + field: 'dueDate', + type: 'datetime' + } + ]); + const [currentGrouping, setCurrentGrouping] = React.useState('none'); + const tabView = props.tabView; + const [tableComp, setTableComp] = React.useState(null); + + React.useEffect(() => { + let viewData = null; + if(tabView === "all"){ + viewData = props.todoData; + + }else if(tabView === "completed"){ + viewData = props.todoData.filter(record => record.status === "completed"); + + }else if(tabView === "pending"){ + viewData = props.todoData.filter(record => record.status === "pending"); + } + console.log("data ::", viewData); + setTableComp(); + }, [tabView, props.todoData.length, columns]) + + const handleGrouping = (e) => { + const value = e.target.value; + setCurrentGrouping(value); + const updatedColumns = columns.map(col => { + if (col.field === value) { + col.tableData.groupOrder = 0; + } else { + delete col.tableData.groupOrder; } - ], - data: [{ - summary: "test 1", - priority: "High", - createdOn: new Date(), - dueDate: new Date() + 12 - }], - }); + return col; + }); + setColumns(updatedColumns); + } return ( -
- + - alert("Done") - },{ - icon: 'edit', - tooltip: 'Edit User', - onClick: (event, rowData) => alert('You are editing ' + rowData.name) - }, - { - icon: 'delete', - tooltip: 'Delete User', - onClick: (event, rowData) => confirm('You want to delete ' + rowData.name) - } - ]} - /> + {tableComp}
); }; -export default TodoList; \ No newline at end of file + +const mapStatetoProps = state => { + return { + todoData: state.todos.data + } +} +const options = { + areOwnPropsEqual: () => false +} + +export default connect(mapStatetoProps, undefined, undefined, options)(TodoList); \ No newline at end of file diff --git a/src/Containers/TodoTabs/index.js b/src/Containers/TodoTabs/index.js index 3ac7bae9..537fd9e8 100644 --- a/src/Containers/TodoTabs/index.js +++ b/src/Containers/TodoTabs/index.js @@ -20,7 +20,6 @@ const TodoTabs = (props) => { const handleChange = (event, newValue) => { setValue(newValue); }; - return ( @@ -37,16 +36,17 @@ const TodoTabs = (props) => { - + - + - + ); } -export default TodoTabs; + +export default (TodoTabs); diff --git a/src/index.js b/src/index.js index 019804dd..0b463b82 100644 --- a/src/index.js +++ b/src/index.js @@ -2,15 +2,22 @@ import React from 'react'; import ReactDOM from 'react-dom'; import CssBaseline from '@material-ui/core/CssBaseline'; import { ThemeProvider } from "@material-ui/core"; -import App from './App.jsx'; +import { Provider } from 'react-redux' +import { createStore, applyMiddleware } from 'redux'; + +import App from './App.jsx'; import theme from './theme'; +import rootReducer from './store/reducers'; +const store = createStore(rootReducer); ReactDOM.render( + + , document.getElementById('root') ); \ No newline at end of file diff --git a/src/store/actionCreator/todoListActionCreator.js b/src/store/actionCreator/todoListActionCreator.js new file mode 100644 index 00000000..d8945a4c --- /dev/null +++ b/src/store/actionCreator/todoListActionCreator.js @@ -0,0 +1,8 @@ +import reducers from "../reducers" + + +export const addItemToTodoList = (values) => { + return reducer => { + + } +} \ No newline at end of file diff --git a/src/store/reducers/index.js b/src/store/reducers/index.js new file mode 100644 index 00000000..6bbd2bda --- /dev/null +++ b/src/store/reducers/index.js @@ -0,0 +1,7 @@ +import { combineReducers } from 'redux'; +import todoReducer from "./todoReducer"; + + +export default combineReducers({ + todos: todoReducer, +}) \ No newline at end of file diff --git a/src/store/reducers/todoReducer.js b/src/store/reducers/todoReducer.js new file mode 100644 index 00000000..bf1ac865 --- /dev/null +++ b/src/store/reducers/todoReducer.js @@ -0,0 +1,23 @@ +import todoListData from '../staticData/todoListData'; + +const initialState = { + data: todoListData +}; + + +export default function(state = initialState, action){ + const updatedState = {...state}; + switch(action.type){ + case "GET_TODO_LIST": + + break; + + case "ADD_TODO_LIST": + const fieldValues = action.values; + fieldValues.id = updatedState.data.length + 1; + fieldValues.status = "pending"; + updatedState.data.push(fieldValues); + break; + } + return updatedState; +} \ No newline at end of file diff --git a/src/store/staticData/todoListData.js b/src/store/staticData/todoListData.js new file mode 100644 index 00000000..350bf307 --- /dev/null +++ b/src/store/staticData/todoListData.js @@ -0,0 +1,3 @@ +const todoListData = []; + +export default todoListData; \ No newline at end of file From 2708071f7d8eeb027b15bec51d313d890f8b5786 Mon Sep 17 00:00:00 2001 From: jay2452 Date: Thu, 9 Jul 2020 04:14:59 +0530 Subject: [PATCH 4/6] functionality development --- package.json | 3 +- src/App.jsx | 24 ++-- src/Components/AddTaskButton/index.js | 10 +- src/Components/Alert/index.js | 39 ++++++ src/Components/Backdrop/index.js | 27 ++++ src/Components/InputSearchBox/index.js | 59 +++++++++ src/Components/TodoForm/index.js | 105 +++++++++++++--- src/Containers/TodoList/Table.js | 48 ++++++- src/Containers/TodoList/index.js | 119 ++++++++++++++---- src/config.js | 42 +++++++ src/index.js | 4 +- .../actionCreator/todoListActionCreator.js | 8 -- src/store/actionCreators/todoActions.js | 91 ++++++++++++++ src/store/reducers/index.js | 3 +- src/store/reducers/todoReducer.js | 119 ++++++++++++++++-- src/store/reducers/uiReducer.js | 30 +++++ src/store/staticData/todoListData.js | 32 ++++- src/utils/debounce.js | 11 ++ 18 files changed, 695 insertions(+), 79 deletions(-) create mode 100644 src/Components/Alert/index.js create mode 100644 src/Components/Backdrop/index.js create mode 100644 src/Components/InputSearchBox/index.js create mode 100644 src/config.js delete mode 100644 src/store/actionCreator/todoListActionCreator.js create mode 100644 src/store/actionCreators/todoActions.js create mode 100644 src/store/reducers/uiReducer.js create mode 100644 src/utils/debounce.js diff --git a/package.json b/package.json index 7313953e..3019b715 100644 --- a/package.json +++ b/package.json @@ -42,6 +42,7 @@ "react": "16.11.0", "react-dom": "16.11.0", "react-redux": "7.1.1", - "redux": "4.0.4" + "redux": "4.0.4", + "redux-thunk": "2.3.0" } } diff --git a/src/App.jsx b/src/App.jsx index f6677977..f3f8d282 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,27 +1,37 @@ -import React from'react'; +import React, { useReducer } from'react'; import AddTaskButton from './Components/AddTaskButton'; import TodoForm from './Components/TodoForm'; import TodoTabs from './Containers/TodoTabs'; +import Alert from './Components/Alert'; +import { useSelector, useDispatch } from 'react-redux'; +import InputSearchBox from './Components/InputSearchBox'; +import * as todoActionCreators from './store/actionCreators/todoActions'; +import CustomBackdrop from './Components/Backdrop'; const App = props => { - const [isFormOpen, setIsFormOpen] = React.useState(false); - const [isEditMode, setIsEditMode] = React.useState(true); + // const [isFormOpen, setIsFormOpen] = React.useState(false); + const {isFormOpen} = useSelector(state => state.todos); + const dispatch = useDispatch(); - const toggleOpenForm = React.useCallback(() => { - setIsFormOpen(!isFormOpen); - }, [isFormOpen]); + // const [isEditMode, setIsEditMode] = React.useState(true); + const toggleOpenForm = () => dispatch(todoActionCreators.openTodoForm(null, true)); + + const handleSearch = value => dispatch(todoActionCreators.searchTodoItems(value)); return (
{/* render tabs */} + + +
) }; diff --git a/src/Components/AddTaskButton/index.js b/src/Components/AddTaskButton/index.js index 49a91db7..cd87ae48 100644 --- a/src/Components/AddTaskButton/index.js +++ b/src/Components/AddTaskButton/index.js @@ -1,8 +1,8 @@ import React from "react"; import { makeStyles } from '@material-ui/core/styles'; -import IconButton from '@material-ui/core/IconButton'; -import AddCircleOutlinedIcon from '@material-ui/icons/AddCircleOutlined'; +import Fab from '@material-ui/core/Fab'; +import AddIcon from '@material-ui/icons/Add'; const useStyles = makeStyles(theme => ({ addtaskButton: { @@ -18,9 +18,9 @@ const AddTaskButton = props => { return (
- - - + + +
); }; diff --git a/src/Components/Alert/index.js b/src/Components/Alert/index.js new file mode 100644 index 00000000..4def4a0f --- /dev/null +++ b/src/Components/Alert/index.js @@ -0,0 +1,39 @@ +import React from 'react'; +import Button from '@material-ui/core/Button'; +import Dialog from '@material-ui/core/Dialog'; +import DialogActions from '@material-ui/core/DialogActions'; +import DialogContent from '@material-ui/core/DialogContent'; +import DialogContentText from '@material-ui/core/DialogContentText'; +import DialogTitle from '@material-ui/core/DialogTitle'; +import { useSelector } from 'react-redux'; + +export default function Alert(props) { + + const { isAlertOpen, yesAction, noAction, alertTitle, alertMessage } = useSelector(state => state.ui); + + return ( +
+ + {alertTitle} + + + {alertMessage} + + + + + + + +
+ ); +} diff --git a/src/Components/Backdrop/index.js b/src/Components/Backdrop/index.js new file mode 100644 index 00000000..f7ef4203 --- /dev/null +++ b/src/Components/Backdrop/index.js @@ -0,0 +1,27 @@ +import React from 'react'; + +import Backdrop from '@material-ui/core/Backdrop'; +import CircularProgress from '@material-ui/core/CircularProgress'; +import { makeStyles } from '@material-ui/core/styles'; +import { useDispatch, useSelector } from 'react-redux'; +import { toggleBackdrop } from '../../store/actionCreators/todoActions'; + +const useStyles = makeStyles((theme) => ({ + backdrop: { + zIndex: theme.zIndex.drawer + 1, + color: '#fff', + }, +})); + +const CustomBackdrop = props => { + const classes = useStyles(); + // const dispatch = useDispatch(); + const { isBackdropOpen } = useSelector(state => state.ui); + return ( + + + + ) +}; + +export default CustomBackdrop; \ No newline at end of file diff --git a/src/Components/InputSearchBox/index.js b/src/Components/InputSearchBox/index.js new file mode 100644 index 00000000..69c85062 --- /dev/null +++ b/src/Components/InputSearchBox/index.js @@ -0,0 +1,59 @@ +import React from 'react'; +import clsx from 'clsx'; +import TextField from '@material-ui/core/TextField'; +import InputAdornment from '@material-ui/core/InputAdornment'; +import { makeStyles } from '@material-ui/core/styles'; +import SearchIcon from '@material-ui/icons/Search'; +import debounce from '../../utils/debounce'; + + +const useStyles = makeStyles((theme) => ({ + root: { + display: 'flex', + flexWrap: 'wrap', + }, + margin: { + margin: theme.spacing(1), + }, + withoutLabel: { + marginTop: theme.spacing(3), + }, + textField: { + width: '25ch', + }, +})); + +(function () { + const keysPressed = {}; + document.addEventListener('keydown', (event) => { + keysPressed[event.key] = true; + if (keysPressed['Control'] && keysPressed['Shift'] && event.key == 'F') { + const node = document.getElementById('global-searchbox'); + if (node) { + node.focus(); + } + } + }); + + document.addEventListener('keyup', (event) => { + delete keysPressed[event.key]; + }); +})(); + +const InputSearchBox = props => { + const classes = useStyles(); + const debouncedSearch = debounce(value => props.handleSearch(value), 200) + return ( + , + }} + onChange={({ target: { value } }) => debouncedSearch(value)} + /> + ) +}; +export default InputSearchBox; \ No newline at end of file diff --git a/src/Components/TodoForm/index.js b/src/Components/TodoForm/index.js index 396cdb0c..39107b76 100644 --- a/src/Components/TodoForm/index.js +++ b/src/Components/TodoForm/index.js @@ -1,5 +1,5 @@ import React from "react"; -import { connect } from 'react-redux'; +import { connect, useSelector } from 'react-redux'; import { makeStyles } from '@material-ui/core/styles'; import Button from '@material-ui/core/Button'; import TextField from '@material-ui/core/TextField'; @@ -8,7 +8,8 @@ import DialogActions from '@material-ui/core/DialogActions'; import DialogContent from '@material-ui/core/DialogContent'; import DialogTitle from '@material-ui/core/DialogTitle'; import Dropdown from "../Dropdown"; - +import * as todoActionCreators from "../../store/actionCreators/todoActions"; +import { priorities } from '../../config'; const useStyles = makeStyles(theme => ({ @@ -25,18 +26,34 @@ const useStyles = makeStyles(theme => ({ }, })); -const priorities = ['low', 'medium', 'high'].map(item => ({value: item, label: item})); const TodoForm = props => { const classes = useStyles(); const {isFormOpen, isEditMode} = props; - const [priority, setPriority] = React.useState('low'); + const selectedTodoItem = props.selectedTodoItem || {}; + + const [priority, setPriority] = React.useState(priorities[0].value); const [summary, setSummary] = React.useState(''); + const [summaryError, setSummaryError ] = React.useState(false); const [description, setDescription] = React.useState(''); - const [dueDate, setDueDate] = React.useState(new Date()); - + const [descriptionError, setDescriptionError] = React.useState(false); + const [dueDate, setDueDate] = React.useState(new Date().toISOString().substr(0, 19)); + + React.useEffect(() => { + setPriority(selectedTodoItem.priority || 'low'); + setSummary(selectedTodoItem.summary || ''); + setDescription(selectedTodoItem.description || ''); + if(selectedTodoItem.dueDate && selectedTodoItem.dueDate.toISOString){ + setDueDate(selectedTodoItem.dueDate.toISOString().substr(0, 19)); + }else{ + setDueDate(selectedTodoItem.dueDate || ''); + } + + // setIsEditMode(false); + }, [selectedTodoItem.priority, selectedTodoItem.summary, selectedTodoItem.description, selectedTodoItem.dueDate]) + const handlePriority = (e) => { setPriority(e.target.value); } @@ -49,8 +66,10 @@ const TodoForm = props => { priority, createdOn: new Date() }; - props.saveTodoItem(values); - props.handleClose(); + if(confirm("Are you sure ?")){ + props.saveTodoItem(values); + props.handleClose(); + } } return ( @@ -70,19 +89,43 @@ const TodoForm = props => { autoFocus disabled={!isEditMode} value={summary} + inputProps={{ + maxLength: 140, + minLength: 10 + }} + required + error={summaryError} onChange={(e) => { setSummary(e.target.value); + if(e.target.value.length < 10){ + setSummaryError(true) + }else{ + setSummaryError(false) + } }} /> setDescription(e.target.value)} + inputProps={{ + minLength: 10, + maxLength: 500 + }} + onChange={(e) => { + setDescription(e.target.value); + if(e.target.value.length < 10){ + setDescriptionError(true); + }else{ + setDescriptionError(false); + } + }} />

@@ -101,7 +144,8 @@ const TodoForm = props => { id="todo-duedate" label="Due Date" type="datetime-local" - defaultValue={dueDate} + value={dueDate} + required disabled={!isEditMode} className={classes.dueDate} InputLabelProps={{ @@ -110,27 +154,54 @@ const TodoForm = props => { onChange={e => setDueDate(e.target.value)} /> + { + selectedTodoItem.createdOn && isEditMode === false ? ( + + ): null + } + { + selectedTodoItem.status && isEditMode === false ? ( +
+
+ Status: {selectedTodoItem.status} +
+ ): null + } +
- ) }; - +const mapStateToProps = state => { + return { + selectedTodoItem: state.todos.selectedTodoItem, + isEditMode: state.todos.isEditMode + } +} const mapDispatchToProps = dispatch => { return { - saveTodoItem: (values) => dispatch({ - type: "ADD_TODO_LIST", - values - }) + saveTodoItem: values => dispatch(todoActionCreators.saveTodoItem(values)) } } -export default connect(null, mapDispatchToProps)(TodoForm); \ No newline at end of file +export default connect(mapStateToProps, mapDispatchToProps)(TodoForm); \ No newline at end of file diff --git a/src/Containers/TodoList/Table.js b/src/Containers/TodoList/Table.js index a64b5156..c98de8aa 100644 --- a/src/Containers/TodoList/Table.js +++ b/src/Containers/TodoList/Table.js @@ -1,9 +1,13 @@ import React from 'react'; -import MaterialTable from "material-table"; +import MaterialTable, { MTableBodyRow } from "material-table"; const Table = props => { - const { columns, data} = props; + const { columns, data, handleDeleteTodo, handleRowClick, + handleEditTodo, + toggleCompleteTask, + onSelectionChange + } = props; return (
{ showTitle: false, toolbar: false, actionsColumnIndex: Infinity, - grouping: true + grouping: true, + selection: true, + rowStyle: (row, index) => { + if(row.status === "completed"){ + return { + "textDecoration" : "line-through" + } + }else{ + return { + "textDecoration" : "none" + } + } + } + }} + onRowClick={handleRowClick} + components={{ + Groupbar: props => { + return null + }, + Row: props => { + if(props.data.status === "pending"){ + props.actions[0].tooltip = "Mark as complete"; + props.actions[0].icon = "check_circle_outline"; + }else { + props.actions[0].tooltip = "Re Open"; + props.actions[0].icon = "check_circle"; + } + return + } }} actions={[ { icon: 'assignment_turned_in', tooltip: 'Mark as complete', - onClick: (e, rowData) => alert("Done") + onClick: toggleCompleteTask, + position: "row" }, { icon: 'edit', tooltip: 'Edit User', - onClick: (event, rowData) => alert('You are editing ' + rowData.name) + onClick: handleEditTodo, + position: "row" }, { icon: 'delete', tooltip: 'Delete User', - onClick: (event, rowData) => confirm('You want to delete ' + rowData.name) + onClick: handleDeleteTodo, + position: "row" } ]} + onSelectionChange={onSelectionChange} />
) diff --git a/src/Containers/TodoList/index.js b/src/Containers/TodoList/index.js index 6b733c2d..ebcbf082 100644 --- a/src/Containers/TodoList/index.js +++ b/src/Containers/TodoList/index.js @@ -1,26 +1,47 @@ import React from 'react'; - import Dropdown from '../../Components/Dropdown'; import { connect } from 'react-redux'; -// import AddCircleOutlinedIcon from '@material-ui/icons/AddCircleOutlined'; import Table from './Table'; -const groupByFields = ['none', "createdAt", "dueDate", "priority"].map(item => ({ value: item, label: item })); +import * as todoActionreators from '../../store/actionCreators/todoActions'; +import { groupByFields, gridColumns } from '../../config'; +import { Button } from '@material-ui/core'; const TodoList = props => { - //Summary | Priority | Created On | Due Date | Actions - const [columns, setColumns] = React.useState([ - { title: 'Summary', field: 'summary' }, - { title: 'Priority', field: 'priority' }, - { title: 'Created On', field: 'createdOn', type: 'datetime' }, - { - title: 'Due Date', - field: 'dueDate', - type: 'datetime' - } - ]); + const [columns, setColumns] = React.useState(gridColumns); const [currentGrouping, setCurrentGrouping] = React.useState('none'); const tabView = props.tabView; const [tableComp, setTableComp] = React.useState(null); + const [selectedRows, setSelectedRows] = React.useState([]); + + const handleDeleteTodo = (event, rowData) => { + props.toggleAlertBox({ + title: rowData.summary, + alertMessage: "Do you want to delete this task?", + yesAction: () => { + props.deleteTodo(rowData.id); + props.toggleAlertBox(); + }, + noAction: () => { + props.toggleAlertBox(); + } + }); + } + + const handleRowClick = (e, rowData) => { + props.openTodoForm(rowData.id, false); + }; + + const handleEditTodo = (e, rowData) => { + props.openTodoForm(rowData.id, true); + } + + const toggleCompleteTask = (e, rowData) => { + props.toggleTaskStatus(rowData.id); + } + + const onSelectionChange = (rows) => { + setSelectedRows(rows.map(row => row.id)); + } React.useEffect(() => { let viewData = null; @@ -33,12 +54,19 @@ const TodoList = props => { }else if(tabView === "pending"){ viewData = props.todoData.filter(record => record.status === "pending"); } - console.log("data ::", viewData); - setTableComp(
); - }, [tabView, props.todoData.length, columns]) + // console.log("data ::", viewData); + setTableComp( +
+ ); + }, [props.todoData.length, props.lastEditTimestamp, columns]) const handleGrouping = (e) => { const value = e.target.value; @@ -57,6 +85,11 @@ const TodoList = props => { return (
+
{ displayLabel="Group By" handleChange={handleGrouping} /> + + + + + + +
+ {tableComp}
); @@ -74,11 +137,23 @@ const TodoList = props => { const mapStatetoProps = state => { return { - todoData: state.todos.data + todoData: state.todos.filteredData, + lastEditTimestamp: state.todos.lastEditTimestamp, + isAlertBoxOpen: state.ui.isAlertOpen + } +} +const mapDispatchToProps = dispatch => { + return { + deleteTodo: (todoId) => dispatch(todoActionreators.deleteTodo(todoId)), + toggleAlertBox: (info) => dispatch(todoActionreators.toggleAlertBox(info)), + openTodoForm: (id, isEditable) => dispatch(todoActionreators.openTodoForm(id, isEditable)), + toggleTaskStatus: (id) => dispatch(todoActionreators.toggleTaskStatus(id)), + markListAsDone: (ids) => dispatch(todoActionreators.markListAsDone(ids)), + markListAsPending: ids => dispatch(todoActionreators.markListAsPending(ids)) } } const options = { areOwnPropsEqual: () => false } -export default connect(mapStatetoProps, undefined, undefined, options)(TodoList); \ No newline at end of file +export default connect(mapStatetoProps, mapDispatchToProps, undefined, options)(TodoList); \ No newline at end of file diff --git a/src/config.js b/src/config.js new file mode 100644 index 00000000..d32783a2 --- /dev/null +++ b/src/config.js @@ -0,0 +1,42 @@ +export const groupByFields = [{ + value: 'none', + label: 'None' +}, { + value: 'createdOn', + label: 'Created On' +}, { + value: "dueDate", + label: 'Due Date' +}, { + value: "priority", + label: 'Priority' +}]; + +export const priorities = [{ + value: 'none', + label: 'None' +},{ + value: 'low', + label: "Low" +}, { + value: "medium", + label: "Medium" +}, { + value: "high", + label: "High" +}]; + +export const searchableFields = ["summary", "description"]; +export const sortableFields = ["summary", "priority", "dueDate", "createdOn"]; +export const gridColumns = [ + { title: 'Id', field: 'id', hidden: true }, + { title: 'Summary', field: 'summary', sorting: sortableFields.includes('summary') ? true : false }, + { title: 'Priority', field: 'priority', sorting: sortableFields.includes('priority') ? true : false }, + { title: 'Created On', field: 'createdOn', type: 'datetime', sorting: sortableFields.includes('createdOn') ? true : false }, + { + title: 'Due Date', + field: 'dueDate', + type: 'datetime', + sorting: sortableFields.includes('dueDate') ? true : false + } +] \ No newline at end of file diff --git a/src/index.js b/src/index.js index 0b463b82..b9c9f46d 100644 --- a/src/index.js +++ b/src/index.js @@ -5,12 +5,12 @@ import { ThemeProvider } from "@material-ui/core"; import { Provider } from 'react-redux' import { createStore, applyMiddleware } from 'redux'; - +import thunk from 'redux-thunk'; import App from './App.jsx'; import theme from './theme'; import rootReducer from './store/reducers'; -const store = createStore(rootReducer); +const store = createStore(rootReducer, applyMiddleware(thunk)); ReactDOM.render( diff --git a/src/store/actionCreator/todoListActionCreator.js b/src/store/actionCreator/todoListActionCreator.js deleted file mode 100644 index d8945a4c..00000000 --- a/src/store/actionCreator/todoListActionCreator.js +++ /dev/null @@ -1,8 +0,0 @@ -import reducers from "../reducers" - - -export const addItemToTodoList = (values) => { - return reducer => { - - } -} \ No newline at end of file diff --git a/src/store/actionCreators/todoActions.js b/src/store/actionCreators/todoActions.js new file mode 100644 index 00000000..da1415fe --- /dev/null +++ b/src/store/actionCreators/todoActions.js @@ -0,0 +1,91 @@ +export const searchTodoItems = value => { + return { + type: "SEARCH_TODO_ITEMS", + values: { + searchText: value + } + } +} + +export const deleteTodo = id => { + return dispatch => { + dispatch(toggleBackdrop()); + setTimeout(() => { + dispatch({ + type: "DELETE_TODO", + values: { + id + } + }); + dispatch(toggleBackdrop()); + }, 500) + + } +} + +export const toggleAlertBox = info => { + return { + type: "TOGGLE_ALERT_BOX", + values: { + ...info + } + } +} + +export const openTodoForm = (id, isEditable) => { + return { + type: "TOGGLE_TODO_FORM", + values: { + id, + isEditable + } + } +} + +export const toggleTaskStatus = id => { + return { + type: "TOGGLE_COMPLETE", + values: { + id + } + } +} + +export const saveTodoItem = values => { + return dispatch => { + dispatch(toggleBackdrop()); + setTimeout(() => { + dispatch({ + type: "ADD_TODO_LIST", + values : { + ...values + } + }) + dispatch(toggleBackdrop()); + }, 500) + } +} + +export const toggleBackdrop = () => { + return { + type: "TOGGLE_BACKDROP" + } +} + +export const markListAsDone = (ids) => { + return { + type: "MARK_LIST_DONE", + values: { + ids + } + } +} + +export const markListAsPending = (ids) => { + return { + type: "MARK_LIST_PENDING", + values: { + ids + } + } +} \ No newline at end of file diff --git a/src/store/reducers/index.js b/src/store/reducers/index.js index 6bbd2bda..20cc9223 100644 --- a/src/store/reducers/index.js +++ b/src/store/reducers/index.js @@ -1,7 +1,8 @@ import { combineReducers } from 'redux'; import todoReducer from "./todoReducer"; - +import uiReducer from './uiReducer'; export default combineReducers({ todos: todoReducer, + ui: uiReducer }) \ No newline at end of file diff --git a/src/store/reducers/todoReducer.js b/src/store/reducers/todoReducer.js index bf1ac865..cdd558da 100644 --- a/src/store/reducers/todoReducer.js +++ b/src/store/reducers/todoReducer.js @@ -1,22 +1,123 @@ import todoListData from '../staticData/todoListData'; +import { searchableFields } from '../../config'; + const initialState = { - data: todoListData + data: todoListData, + isFormOpen: false, + isEditMode: true, + selectedTodoId: null, + selectedTodoItem: {}, + lastEditTimestamp: null, + filteredData: todoListData }; -export default function(state = initialState, action){ - const updatedState = {...state}; - switch(action.type){ - case "GET_TODO_LIST": +export default function (state = initialState, action) { + const updatedState = { ...state }; + switch (action.type) { + case "GET_TODO_LIST": + + break; + + case "TOGGLE_COMPLETE": + const id = action.values.id; + const item = updatedState.data.find(item => item.id === id); + if (item.status === "pending") item.status = "completed"; + else if (item.status === "completed") item.status = "pending"; + updatedState.lastEditTimestamp = new Date().getTime(); break; - case "ADD_TODO_LIST": + case "ADD_TODO_LIST": const fieldValues = action.values; - fieldValues.id = updatedState.data.length + 1; - fieldValues.status = "pending"; - updatedState.data.push(fieldValues); + if (updatedState.selectedTodoId > 0 || fieldValues.id > 0) { + const id = updatedState.selectedTodoId || fieldValues.id; + const record = updatedState.data.find(item => item.id === id); + record.summary = fieldValues.summary; + record.description = fieldValues.description; + record.priority = fieldValues.priority; + record.dueDate = fieldValues.dueDate; + } else { + fieldValues.id = updatedState.data.length + 1; + fieldValues.status = "pending"; + updatedState.data.push(fieldValues); + } + updatedState.lastEditTimestamp = new Date().getTime(); + break; + case "DELETE_TODO": + const todoList = updatedState.data.filter((item => item.id !== action.values.id)); + const filteredTodoList = updatedState.filteredData.filter((item => item.id !== action.values.id)) + updatedState.data = todoList; + updatedState.filteredData = filteredTodoList; + break; + + case "TOGGLE_TODO_FORM": + updatedState.isFormOpen = !updatedState.isFormOpen; + const values = action.values; + + if (updatedState.isFormOpen && action.values && Object.keys(action.values).length > 0) { + updatedState.selectedTodoId = action.values.id; + if (values.isEditable) { + updatedState.isEditMode = true; + } else { + updatedState.isEditMode = false; + } + updatedState.selectedTodoItem = updatedState.data.find(item => item.id === action.values.id); + } + break; + case "SEARCH_TODO_ITEMS": + const searchText = action.values.searchText; + if (searchText === "") { + updatedState.filteredData = updatedState.data; + } else { + updatedState.filteredData = updatedState.data.filter(item => { + for (let field of searchableFields) { + if (item[field].includes(searchText)) { + return item; + } + } + }); + } + + break; + case "MARK_LIST_DONE": + const ids = action.values.ids; + updatedState.data.forEach(data => { + ids.forEach(id => { + if (data.id === id) { + data.status = "completed"; + } + }) + }); + updatedState.filteredData.forEach(data => { + ids.forEach(id => { + if (data.id === id) { + data.status = "completed"; + } + }) + }); + updatedState.lastEditTimestamp = new Date().getTime(); + break; + case "MARK_LIST_PENDING": + { + const ids = action.values.ids; + updatedState.data.forEach(data => { + ids.forEach(id => { + if (data.id === id) { + data.status = "pending"; + } + }) + }) + updatedState.filteredData.forEach(data => { + ids.forEach(id => { + if (data.id === id) { + data.status = "pending"; + } + }) + }) + updatedState.lastEditTimestamp = new Date().getTime(); + } break; } return updatedState; diff --git a/src/store/reducers/uiReducer.js b/src/store/reducers/uiReducer.js new file mode 100644 index 00000000..185160f8 --- /dev/null +++ b/src/store/reducers/uiReducer.js @@ -0,0 +1,30 @@ +const initialState = { + isAlertOpen: false, + alertTitle: "Alert", + yesAction: undefined, + noAction: undefined, + alertMessage: "Message !!", + isBackdropOpen: false +} + + +export default function (state = initialState, action) { + const updatedState = {...state}; + switch (action.type) { + case "TOGGLE_ALERT_BOX": + updatedState.isAlertOpen = !updatedState.isAlertOpen; + if(action.values){ + updatedState.alertTitle = action.values.title; + updatedState.yesAction = action.values.yesAction; + updatedState.noAction = action.values.noAction; + updatedState.alertMessage = action.values.alertMessage; + } + break; + case "TOGGLE_BACKDROP": + updatedState.isBackdropOpen = !updatedState.isBackdropOpen; + break; + default: + break; + } + return updatedState; +} \ No newline at end of file diff --git a/src/store/staticData/todoListData.js b/src/store/staticData/todoListData.js index 350bf307..e33c7d05 100644 --- a/src/store/staticData/todoListData.js +++ b/src/store/staticData/todoListData.js @@ -1,3 +1,33 @@ -const todoListData = []; +const todoListData = [{ + id: 1, summary: "todo 1", description: "description for todo 1", status: "pending", + priority: "high", + createdOn: new Date(2020, 4, 2), + dueDate: new Date(2020, 4, 6) +},{ + id: 2, summary: "todo 2", description: "description for todo 2", status: "completed", + priority: "medium", + createdOn: new Date(2020, 4, 3), + dueDate: new Date(2020, 4, 7) +},{ + id: 3, summary: "todo 3", description: "description for todo 3", status: "pending", + priority: "high", + createdOn: new Date(2020, 5, 1), + dueDate: new Date(2020, 1, 6) +},{ + id: 4, summary: "todo 4", description: "description for todo 4", status: "completed", + priority: "low", + createdOn: new Date(2019, 4, 2), + dueDate: new Date(2019, 5, 6) +},{ + id: 5, summary: "todo 5", description: "description for todo 5", status: "pending", + priority: "high", + createdOn: new Date(2020, 4, 2), + dueDate: new Date(2020, 4, 6) +},{ + id: 6, summary: "todo 6", description: "description for todo 6", status: "completed", + priority: "medium", + createdOn: new Date(2020, 4, 2), + dueDate: new Date(2020, 4, 6) +}]; export default todoListData; \ No newline at end of file diff --git a/src/utils/debounce.js b/src/utils/debounce.js new file mode 100644 index 00000000..41a72076 --- /dev/null +++ b/src/utils/debounce.js @@ -0,0 +1,11 @@ +export default function debounce(fn, delay){ + let timer; + return function(){ + const args = arguments; + const context = this; + clearTimeout(timer); + timer = setTimeout(() => { + fn.apply(context, args); + }, delay); + } +}; \ No newline at end of file From 688b203464773d4b5fe2a58fb91f30d046c7883f Mon Sep 17 00:00:00 2001 From: jay2452 Date: Thu, 9 Jul 2020 13:53:47 +0530 Subject: [PATCH 5/6] UI changes, added columns config, simulate api call for get all lists --- src/App.jsx | 35 ++++++++++- src/Components/AddTaskButton/index.js | 2 +- src/Components/InputSearchBox/index.js | 4 +- src/Containers/TodoList/index.js | 79 +++++++++++++++---------- src/Containers/TodoTabs/index.js | 1 - src/config.js | 4 +- src/store/actionCreators/todoActions.js | 17 ++++++ src/store/reducers/todoReducer.js | 12 ++-- src/store/staticData/todoListData.js | 12 ++-- 9 files changed, 118 insertions(+), 48 deletions(-) diff --git a/src/App.jsx b/src/App.jsx index f3f8d282..b23b5259 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -8,10 +8,11 @@ import InputSearchBox from './Components/InputSearchBox'; import * as todoActionCreators from './store/actionCreators/todoActions'; import CustomBackdrop from './Components/Backdrop'; +import { Typography } from '@material-ui/core'; const App = props => { // const [isFormOpen, setIsFormOpen] = React.useState(false); - const {isFormOpen} = useSelector(state => state.todos); + const {isFormOpen, data, isDataLoaded} = useSelector(state => state.todos); const dispatch = useDispatch(); // const [isEditMode, setIsEditMode] = React.useState(true); @@ -19,10 +20,40 @@ const App = props => { const handleSearch = value => dispatch(todoActionCreators.searchTodoItems(value)); + React.useEffect(() => { + if(isDataLoaded === false){ + dispatch(todoActionCreators.getAllTodos()) + } + }, []); + + if(isDataLoaded === false) return ; return (
{/* render tabs */} - +
+ +
+
+ My Tasks +
+ + + Better to do, than to say + +
+
{ return (
- +
diff --git a/src/Components/InputSearchBox/index.js b/src/Components/InputSearchBox/index.js index 69c85062..d0e79033 100644 --- a/src/Components/InputSearchBox/index.js +++ b/src/Components/InputSearchBox/index.js @@ -45,9 +45,9 @@ const InputSearchBox = props => { const debouncedSearch = debounce(value => props.handleSearch(value), 200) return ( , diff --git a/src/Containers/TodoList/index.js b/src/Containers/TodoList/index.js index ebcbf082..c17c4b85 100644 --- a/src/Containers/TodoList/index.js +++ b/src/Containers/TodoList/index.js @@ -4,7 +4,7 @@ import { connect } from 'react-redux'; import Table from './Table'; import * as todoActionreators from '../../store/actionCreators/todoActions'; import { groupByFields, gridColumns } from '../../config'; -import { Button } from '@material-ui/core'; +import { Button, Tooltip } from '@material-ui/core'; const TodoList = props => { const [columns, setColumns] = React.useState(gridColumns); @@ -85,11 +85,15 @@ const TodoList = props => { return (
+
+ +
{ displayLabel="Group By" handleChange={handleGrouping} /> +
- - - - - +
+ + + + + + + + + + + + + + + + + +
{tableComp} diff --git a/src/Containers/TodoTabs/index.js b/src/Containers/TodoTabs/index.js index 537fd9e8..d2cf2adc 100644 --- a/src/Containers/TodoTabs/index.js +++ b/src/Containers/TodoTabs/index.js @@ -28,7 +28,6 @@ const TodoTabs = (props) => { onChange={handleChange} indicatorColor="primary" textColor="primary" - centered > diff --git a/src/config.js b/src/config.js index d32783a2..4467a1df 100644 --- a/src/config.js +++ b/src/config.js @@ -32,7 +32,9 @@ export const gridColumns = [ { title: 'Id', field: 'id', hidden: true }, { title: 'Summary', field: 'summary', sorting: sortableFields.includes('summary') ? true : false }, { title: 'Priority', field: 'priority', sorting: sortableFields.includes('priority') ? true : false }, - { title: 'Created On', field: 'createdOn', type: 'datetime', sorting: sortableFields.includes('createdOn') ? true : false }, + { title: 'Created On', + defaultSort: 'desc', + field: 'createdOn', type: 'datetime', sorting: sortableFields.includes('createdOn') ? true : false }, { title: 'Due Date', field: 'dueDate', diff --git a/src/store/actionCreators/todoActions.js b/src/store/actionCreators/todoActions.js index da1415fe..cdfdaa29 100644 --- a/src/store/actionCreators/todoActions.js +++ b/src/store/actionCreators/todoActions.js @@ -1,3 +1,20 @@ +import todoListData from '../staticData/todoListData'; + +export const getAllTodos = () => { + return dispatch => { + dispatch(toggleBackdrop()); + setTimeout(() => { + dispatch({ + type: "GET_TODO_LIST", + values: { + data: todoListData + } + }); + dispatch(toggleBackdrop()); + }, 1000) + } +} + export const searchTodoItems = value => { return { type: "SEARCH_TODO_ITEMS", diff --git a/src/store/reducers/todoReducer.js b/src/store/reducers/todoReducer.js index cdd558da..37648222 100644 --- a/src/store/reducers/todoReducer.js +++ b/src/store/reducers/todoReducer.js @@ -1,15 +1,15 @@ -import todoListData from '../staticData/todoListData'; import { searchableFields } from '../../config'; const initialState = { - data: todoListData, + isDataLoaded: false, + data: [], isFormOpen: false, isEditMode: true, selectedTodoId: null, selectedTodoItem: {}, lastEditTimestamp: null, - filteredData: todoListData + filteredData: [] }; @@ -17,7 +17,9 @@ export default function (state = initialState, action) { const updatedState = { ...state }; switch (action.type) { case "GET_TODO_LIST": - + updatedState.data = action.values.data; + updatedState.filteredData = action.values.data; + updatedState.isDataLoaded = true; break; case "TOGGLE_COMPLETE": @@ -73,7 +75,7 @@ export default function (state = initialState, action) { } else { updatedState.filteredData = updatedState.data.filter(item => { for (let field of searchableFields) { - if (item[field].includes(searchText)) { + if (item[field] && item[field].toLowerCase().includes(searchText.toLowerCase())) { return item; } } diff --git a/src/store/staticData/todoListData.js b/src/store/staticData/todoListData.js index e33c7d05..a04ba26e 100644 --- a/src/store/staticData/todoListData.js +++ b/src/store/staticData/todoListData.js @@ -1,30 +1,30 @@ const todoListData = [{ - id: 1, summary: "todo 1", description: "description for todo 1", status: "pending", + id: 1, summary: "Start Sureify Project", description: "description for todo 1", status: "pending", priority: "high", createdOn: new Date(2020, 4, 2), dueDate: new Date(2020, 4, 6) },{ - id: 2, summary: "todo 2", description: "description for todo 2", status: "completed", + id: 2, summary: "Use React, Redux, Webpack", description: "description for todo 2", status: "completed", priority: "medium", createdOn: new Date(2020, 4, 3), dueDate: new Date(2020, 4, 7) },{ - id: 3, summary: "todo 3", description: "description for todo 3", status: "pending", + id: 3, summary: "Include Material UI", description: "description for todo 3", status: "pending", priority: "high", createdOn: new Date(2020, 5, 1), dueDate: new Date(2020, 1, 6) },{ - id: 4, summary: "todo 4", description: "description for todo 4", status: "completed", + id: 4, summary: "Develop some components", description: "description for todo 4", status: "completed", priority: "low", createdOn: new Date(2019, 4, 2), dueDate: new Date(2019, 5, 6) },{ - id: 5, summary: "todo 5", description: "description for todo 5", status: "pending", + id: 5, summary: "Test all the features", description: "description for todo 5", status: "pending", priority: "high", createdOn: new Date(2020, 4, 2), dueDate: new Date(2020, 4, 6) },{ - id: 6, summary: "todo 6", description: "description for todo 6", status: "completed", + id: 6, summary: "Take bouns points", description: "description for todo 6", status: "completed", priority: "medium", createdOn: new Date(2020, 4, 2), dueDate: new Date(2020, 4, 6) From 19335a2eca6be35df57d99c0a96957c99649e867 Mon Sep 17 00:00:00 2001 From: jay2452 Date: Thu, 9 Jul 2020 14:32:35 +0530 Subject: [PATCH 6/6] Ui updates --- src/App.jsx | 4 ++-- src/Components/AddTaskButton/index.js | 4 ++-- src/Components/TodoForm/index.js | 3 ++- src/Containers/TodoList/Table.js | 10 +++++----- src/config.js | 1 + src/store/staticData/todoListData.js | 2 +- src/theme.js | 18 ++++++++++++++++++ 7 files changed, 31 insertions(+), 11 deletions(-) diff --git a/src/App.jsx b/src/App.jsx index b23b5259..ef2b3cec 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -36,7 +36,7 @@ const App = props => { }}>
{ Better to do, than to say diff --git a/src/Components/AddTaskButton/index.js b/src/Components/AddTaskButton/index.js index 44115236..176aea8b 100644 --- a/src/Components/AddTaskButton/index.js +++ b/src/Components/AddTaskButton/index.js @@ -7,8 +7,8 @@ import AddIcon from '@material-ui/icons/Add'; const useStyles = makeStyles(theme => ({ addtaskButton: { position: 'absolute', - bottom: 0, - right: 0 + bottom: 10, + right: 10 } })); diff --git a/src/Components/TodoForm/index.js b/src/Components/TodoForm/index.js index 39107b76..cecf8089 100644 --- a/src/Components/TodoForm/index.js +++ b/src/Components/TodoForm/index.js @@ -77,7 +77,7 @@ const TodoForm = props => { maxWidth="md" onClose={props.handleClose} > - Add / Edit Todo + Add Task { error={descriptionError} multiline required + rows={2} rowsMax={4} fullWidth disabled={!isEditMode} diff --git a/src/Containers/TodoList/Table.js b/src/Containers/TodoList/Table.js index c98de8aa..bf6b81c5 100644 --- a/src/Containers/TodoList/Table.js +++ b/src/Containers/TodoList/Table.js @@ -1,6 +1,6 @@ import React from 'react'; import MaterialTable, { MTableBodyRow } from "material-table"; - +import EditOutlinedIcon from '@material-ui/icons/EditOutlined'; const Table = props => { const { columns, data, handleDeleteTodo, handleRowClick, @@ -57,14 +57,14 @@ const Table = props => { onClick: toggleCompleteTask, position: "row" }, { - icon: 'edit', - tooltip: 'Edit User', + icon: 'edit_outline', + tooltip: 'Edit', onClick: handleEditTodo, position: "row" }, { - icon: 'delete', - tooltip: 'Delete User', + icon: 'delete_outline', + tooltip: 'Delete', onClick: handleDeleteTodo, position: "row" } diff --git a/src/config.js b/src/config.js index 4467a1df..af5c834d 100644 --- a/src/config.js +++ b/src/config.js @@ -34,6 +34,7 @@ export const gridColumns = [ { title: 'Priority', field: 'priority', sorting: sortableFields.includes('priority') ? true : false }, { title: 'Created On', defaultSort: 'desc', + format: "%d%m%Y - %hh:%mm", field: 'createdOn', type: 'datetime', sorting: sortableFields.includes('createdOn') ? true : false }, { title: 'Due Date', diff --git a/src/store/staticData/todoListData.js b/src/store/staticData/todoListData.js index a04ba26e..e9db4eb6 100644 --- a/src/store/staticData/todoListData.js +++ b/src/store/staticData/todoListData.js @@ -4,7 +4,7 @@ const todoListData = [{ createdOn: new Date(2020, 4, 2), dueDate: new Date(2020, 4, 6) },{ - id: 2, summary: "Use React, Redux, Webpack", description: "description for todo 2", status: "completed", + id: 2, summary: "Use React, Redux, Webpack", description: "description for todo 2", status: "pending", priority: "medium", createdOn: new Date(2020, 4, 3), dueDate: new Date(2020, 4, 7) diff --git a/src/theme.js b/src/theme.js index 2324afe0..b92b1e57 100644 --- a/src/theme.js +++ b/src/theme.js @@ -1,11 +1,29 @@ import { createMuiTheme } from "@material-ui/core"; const theme = createMuiTheme({ + palette:{ + primary: { + main: '#03a9f4' + }, + secondary:{ + main: '#03a9f4' + } + }, overrides: { MuiFormControl: { root:{ minWidth: '120px' } + }, + MuiIconButton: { + root: { + color: '#03a9f4' + } + }, + MuiDialogTitle: { + root:{ + padding: "16px 24px 0 24px" + } } } });