diff --git a/package.json b/package.json index 07b580b..ae92a60 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,8 @@ "@types/react-router-dom": "^5.1.7", "@types/styled-components": "^5.1.10", "@types/yup": "^0.29.11", + "ag-grid-community": "30.0.6", + "ag-grid-react": "30.0.6", "formik": "^2.2.9", "react": "^17.0.2", "react-dom": "^17.0.2", diff --git a/public/index.html b/public/index.html index e6cc647..9b05d89 100644 --- a/public/index.html +++ b/public/index.html @@ -24,7 +24,7 @@ work correctly both with client-side routing and a non-root public URL. Learn how to configure a non-root public URL by running `npm run build`. --> - Customer List + Customer Management diff --git a/src/App.tsx b/src/App.tsx index 9b36496..28f0e67 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -2,6 +2,7 @@ import * as React from "react"; import { StyledMain, StyledHeader, StyledHeaderText } from "./StyledApp"; import { BrowserRouter as Router, Switch, Route } from "react-router-dom"; import Home from "./views/Home"; +import { AddCustomerForm } from "./components/AddCustomer/AddCustomerForm"; const App: React.FC = () => { return ( @@ -11,9 +12,12 @@ const App: React.FC = () => { - + + + + diff --git a/src/components/AddCustomer/AddCustomerForm.tsx b/src/components/AddCustomer/AddCustomerForm.tsx index 52ebf9e..1878f71 100644 --- a/src/components/AddCustomer/AddCustomerForm.tsx +++ b/src/components/AddCustomer/AddCustomerForm.tsx @@ -1,25 +1,72 @@ import { Field, Formik, FormikHelpers } from "formik"; import * as React from "react"; -import { ICustomer, Customer } from "../../types/types"; +import { ICustomer, Customer, CustomerState } from "../../types/types"; import { StyledForm, StyledInput, StyledLabel, StyledAddButton, + StyledCancelButton, + StyledError, } from "./StyledAddCustomerForm"; +import { addCustomer } from "../../redux/actions/customerActions"; +import { Dispatch } from "redux"; +import { shallowEqual, useDispatch, useSelector } from "react-redux"; +import { useHistory } from "react-router-dom"; +import * as Yup from "yup"; -type Props = { - saveCustomer: (customer: ICustomer | any) => void; -}; +const CustomerSchema = Yup.object().shape({ + firstName: Yup.string() + .min(2, "Too Short!") + .max(50, "Too Long!") + .required("Required"), + lastName: Yup.string() + .min(2, "Too Short!") + .max(50, "Too Long!") + .required("Required"), + phoneNumber: Yup.string() + .matches(/^(\(0[1-9]\)|0[1-9])?( ?-?[0-9]){10,10}$/, "Invalid phone number") + .required("Required"), + birthday: Yup.date().required("Required"), +}); + +export const AddCustomerForm: React.FC = () => { + let history = useHistory(); + const dispatch: Dispatch = useDispatch(); + + const customers: ICustomer[] = useSelector( + (state: CustomerState) => state.customers, + shallowEqual + ); + + const saveCustomer = React.useCallback( + (customer: ICustomer | any) => { + let isCustomerFound = customers.find(cust => cust.firstName === customer.firstName && cust.lastName === customer.lastName && cust.phoneNumber === customer.phoneNumber) + if(!isCustomerFound) { + dispatch(addCustomer(customer)); + history.push("/"); + } else { + alert('Customer already exist') + } + + + }, + [dispatch] + ); + + const cancel = () => { + history.push("/"); + }; -export const AddCustomerForm: React.FC = ({ saveCustomer }) => { return ( @@ -28,34 +75,76 @@ export const AddCustomerForm: React.FC = ({ saveCustomer }) => { setSubmitting(false); }} > - - First Name - - - Last Name - - - Phone Number - - - Add Customer - + {({ errors, touched }) => ( + + + First Name*{" "} + {errors.firstName && touched.firstName ? ( + {errors.firstName} + ) : null} + + + + + Last Name*{" "} + {errors.lastName && touched.lastName ? ( + {errors.lastName} + ) : null} + + + + + Phone Number*{" "} + {errors.phoneNumber && touched.phoneNumber ? ( + {errors.phoneNumber} + ) : null} + + + + + + Birthday*{" "} + {errors.birthday && touched.birthday ? ( + {errors.birthday} + ) : null} + + + + + Add Customer + Cancel + + )} ); }; diff --git a/src/components/AddCustomer/StyledAddCustomerForm.ts b/src/components/AddCustomer/StyledAddCustomerForm.ts index a7a4bb9..fd33141 100644 --- a/src/components/AddCustomer/StyledAddCustomerForm.ts +++ b/src/components/AddCustomer/StyledAddCustomerForm.ts @@ -30,3 +30,20 @@ export const StyledAddButton = styled.button` border-radius: 4px; font-size: 16px; `; + +export const StyledCancelButton = styled.button` + padding: 1rem; + background-color: gray; + color: white; + border: none; + border-radius: 4px; + font-size: 16px; + cursor: pointer; + margin-top: 5px +`; + +export const StyledError = styled.div` + float: right; + font-size: 14px; + color: red; +`; diff --git a/src/components/Customer/Customer.tsx b/src/components/Customer/Customer.tsx index 8fd093a..f8e84d7 100644 --- a/src/components/Customer/Customer.tsx +++ b/src/components/Customer/Customer.tsx @@ -30,6 +30,9 @@ export const Customer: React.FC = ({ customer, removeCustomer }) => { Phone number: {customer.phoneNumber} + + Birthday: {customer.birthday} + deleteCustomer(customer)}> Delete diff --git a/src/components/Customer/StyledCustomer.ts b/src/components/Customer/StyledCustomer.ts index f6e8885..079073a 100644 --- a/src/components/Customer/StyledCustomer.ts +++ b/src/components/Customer/StyledCustomer.ts @@ -19,7 +19,8 @@ export const StyledCustomerInfo = styled.p` `; export const StyledCustomerDelete = styled.button` - padding: 1rem; + padding: 6px; + height: 30px; background-color: #dc1616; color: white; border: none; diff --git a/src/components/Grid/ButtonCellRenderer.tsx b/src/components/Grid/ButtonCellRenderer.tsx new file mode 100644 index 0000000..d297772 --- /dev/null +++ b/src/components/Grid/ButtonCellRenderer.tsx @@ -0,0 +1,21 @@ +import React from "react"; +import { StyledCustomerDelete } from "../Customer/StyledCustomer"; +import { Dispatch } from "redux"; +import { useDispatch } from "react-redux"; +import { ICustomer } from "../../types/types"; +import { removeCustomer } from "../../redux/actions/customerActions"; + +export const ButtonCellRenderer: React.FC = (params) => { + const dispatch: Dispatch = useDispatch(); + + const deleteCustomer = React.useCallback( + (customer: ICustomer) => dispatch(removeCustomer(customer)), + [dispatch, removeCustomer] + ); + + return ( + deleteCustomer(params.data)}> + Delete + + ); +}; diff --git a/src/redux/reducers/customerReducers.tsx b/src/redux/reducers/customerReducers.tsx index d6d837e..d0c93f9 100644 --- a/src/redux/reducers/customerReducers.tsx +++ b/src/redux/reducers/customerReducers.tsx @@ -8,18 +8,21 @@ export const initialState: CustomerState = { firstName: "Charles", lastName: "Babbage", phoneNumber: "0412 123 123", + birthday: "1992-10-05", }, { id: 2, firstName: "Alan", lastName: "Turing", phoneNumber: "(03) 9599 1234", + birthday: "1990-11-02", }, { id: 3, firstName: "Ada", lastName: "Lovelace", phoneNumber: "+61 423 345 567", + birthday: "1984-04-09", }, ], }; @@ -35,6 +38,7 @@ export const customerReducer = ( firstName: action.customer.firstName, lastName: action.customer.lastName, phoneNumber: action.customer.phoneNumber, + birthday: action.customer.birthday, }; return { ...state, diff --git a/src/types/types.ts b/src/types/types.ts index 83e2415..3c4cde2 100644 --- a/src/types/types.ts +++ b/src/types/types.ts @@ -2,6 +2,7 @@ export interface Customer { firstName: string; lastName: string; phoneNumber: string; + birthday: string; } export interface ICustomer extends Customer { diff --git a/src/views/Home.tsx b/src/views/Home.tsx index 8911b39..8d1d1f3 100644 --- a/src/views/Home.tsx +++ b/src/views/Home.tsx @@ -1,34 +1,72 @@ import * as React from "react"; -import { useSelector, shallowEqual, useDispatch } from "react-redux"; -import { Customer } from "../components/Customer/Customer"; -import { AddCustomerForm } from "../components/AddCustomer/AddCustomerForm"; -import { Dispatch } from "redux"; +import { useSelector, shallowEqual } from "react-redux"; import { CustomerState, ICustomer } from "../types/types"; -import { addCustomer, removeCustomer } from "../redux/actions/customerActions"; +import styled from "styled-components"; +import { Link } from "react-router-dom"; +import { AgGridReact } from "ag-grid-react"; +import { ButtonCellRenderer } from "../components/Grid/ButtonCellRenderer" +import "ag-grid-community/styles/ag-grid.css"; +import "ag-grid-community/styles/ag-theme-alpine.css"; +import { ColDef } from "ag-grid-community"; + +const StyledAddCustomerButton = styled(Link)` + padding: 1rem; + background-color: rgb(4, 121, 205); + color: white; + border: none; + border-radius: 4px; + font-size: 16px; + width: 100%; + text-align: center; + text-decoration: auto; + margin-bottom: 5px; +`; + +const filterConfig = { + filter: true, + floatingFilter: true, +}; const Home: React.FC = () => { - const customers: readonly ICustomer[] = useSelector( + const gridRef = React.useRef>(null); + + const customers: ICustomer[] = useSelector( (state: CustomerState) => state.customers, shallowEqual ); - const dispatch: Dispatch = useDispatch(); + const colDefs: ColDef[] = [ + { field: "firstName", ...filterConfig, sortable: true }, + { field: "lastName", ...filterConfig, sortable: true }, + { field: "phoneNumber", ...filterConfig, sortable: true }, + { field: "birthday", ...filterConfig, sortable: true }, + { field: "", cellRenderer: ButtonCellRenderer} + ]; - const saveCustomer = React.useCallback( - (customer: ICustomer) => dispatch(addCustomer(customer)), - [dispatch] - ); + const onFirstDataRendered = React.useCallback((params) => { + gridRef?.current?.api.sizeColumnsToFit(); + }, []); return ( <> - - {customers.map((customer: ICustomer) => ( - - ))} +

Customer List

+ + New Customer + +
+ + ref={gridRef} + rowData={customers} + columnDefs={colDefs} + onFirstDataRendered={onFirstDataRendered} + > +
); };