Customer Relation Manager - Front end
React, React-table, Redux, Redux-toolkit, TailwindCSS, Vite, React-router, Eslint, PrettierDescription
In this project i use TailwindCSS, because need more customization in the UI, is fast and also very flexible.
- Includes Login page for Auth, React-big-calendar- React-table
- Redux-toolkit for state management.
Key takeaways
There are multiple purposes of this project is can connect this front end crm with an e-commerce, backend, apis, services
Store
Combines all reducers and apply async-await fuction in replace of Thunks.
View code
1export const storeSlice = createSlice({ 2 name: "storeData", 3 initialState: { 4 // Pages 5 actualPage: "dashboard", 6 7 // Modals 8 isProductModalOpen: false, 9 isModalActionOpen: false, 10 isNeWProduct: false, 11 isModalFeatureOpen: false, 12 13 // Tables 14 infoData: [], 15 infoColumns: [], 16 selectedRecord: [], 17 showSelectedrow: false, 18 dataList: [], 19 }, 20 reducers: { 21 // Pages 22 onChangeView: (state, payload) => {}, 23 24 // Modals 25 onOpenProductModal: (state, { payload }) => { 26 state.isNeWProduct 27 ? (state.isProductModalOpen = true) 28 : (state.isProductModalOpen = true); 29 // state.selectedRecord = [{title: '',category: '',description: '',image:'',price:''}] 30 }, 31 onCloseProductModal: (state) => { 32 state.isProductModalOpen = false; 33 state.selectedRecord = []; 34 }, 35 36 onOpenDeleteModal: (state) => { 37 state.isModalActionOpen = true; 38 }, 39 40 onCloseDeleteModal: (state) => { 41 state.isModalActionOpen = false; 42 }, 43 44 onOpenFeatureModal: (state) => { 45 state.isModalFeatureOpen = true; 46 }, 47 48 onCloseFeatureModal: (state) => { 49 state.isModalFeatureOpen = false; 50 }, 51 52 // Tables 53 onLoadTable: (state, { payload }) => { 54 switch (payload.page) { 55 case "dashboard": 56 state.actualPage = payload.page; 57 break; 58 59 case "customers": 60 state.actualPage = payload.page; 61 state.dataList = payload.data; 62 state.infoData = payload.data; 63 state.infoColumns = Object.keys(payload.data[0]) 64 .filter( 65 (key) => 66 // Exceptions 67 key !== "name" && key !== "address" && key !== "__v" 68 ) 69 .map((key) => { 70 return { Header: key, accessor: key }; 71 }); 72 break; 73 74 case "orders": 75 state.actualPage = payload.page; 76 state.dataList = payload.data; 77 state.infoData = payload.data; 78 state.infoColumns = Object.keys(payload.data[0]) 79 .filter( 80 (key) => 81 // Exceptions 82 key !== "products" && key !== "__v" 83 ) 84 .map((key) => { 85 return { Header: key, accessor: key }; 86 }); 87 88 break; 89 90 case "products": 91 state.actualPage = payload.page; 92 state.dataList = payload.data; 93 state.infoData = payload.data; 94 state.infoColumns = Object.keys(payload.data[0]) 95 .filter( 96 (key) => key !== "rating" /* Exceptions && key !== "price" */ 97 ) 98 .map((key) => { 99 return { Header: key, accessor: key }; 100 }); 101 102 break; 103 104 default: 105 break; 106 } 107 }, 108 109 onSelectRecord: (state, { payload }) => { 110 state.selectedRecord = payload; 111 console.log(state.selectedRecord); 112 }, 113 114 // CRUD Table 115 onAddNewProduct: (state, { payload }) => { 116 state.infoData.push(payload); 117 state.selectedRecord = []; 118 state.isProductModalOpen = false; 119 }, 120 onUpdateProduct: (state, { payload }) => { 121 state.infoData = state.infoData.map((product) => { 122 if (product.id === payload.id) { 123 return payload; 124 } 125 return product; 126 }); 127 state.isProductModalOpen = false; 128 }, 129 130 onDeleteProduct: (state, { payload }) => { 131 state.infoData = state.infoData.filter( 132 (product) => product.id !== state.selectedRecord.id 133 ); 134 state.selectedRecord = null; 135 state.isModalActionOpen = false; 136 }, 137 138 // Sidebar 139 140 onClickSidebar: (state, { payload }) => { 141 state.actualPage = payload; 142 }, 143 }, // Reducers End 144}); 145 146// Action creators are generated for each case reducer function 147export const { 148 // Pages 149 onChangeView, 150 151 // Modals 152 onOpenProductModal, 153 onCloseProductModal, 154 onOpenFeatureModal, 155 156 onOpenDeleteModal, 157 onCloseDeleteModal, 158 159 // Table 160 onLoadTable, 161 onSelectRecord, 162 163 // CRUD Table 164 onAddNewProduct, 165 onUpdateProduct, 166 onDeleteProduct, 167 onCloseFeatureModal, 168 169 // Sidebar 170 onClickSidebar, 171} = storeSlice.actions; 172
Components
This is an SPA aplication, then i use a logic of container, and reused the same container following the dry principle, if you check the code, you can see that i try to reused all components, also the table is the same for all the views, the modal is the same, only change the custom form for each modal-view.
View code
1export const Dashboard = () => { 2 const { actualPage } = useSelector((state) => state.storeData); 3 4 const showFabAddNew = () => { 5 switch (actualPage) { 6 case "dashboard": 7 break; 8 case "customers": 9 return <FabAddNew />; 10 case "products": 11 return <FabAddNew />; 12 case "orders": 13 return <FabAddNew />; 14 15 default: 16 break; 17 } 18 }; 19 20 const showContainer = () => { 21 switch (actualPage) { 22 case "dashboard": 23 return <DashboardPage />; 24 case "customers": 25 return <CustomersPage />; 26 case "products": 27 return <ProductsPage />; 28 case "orders": 29 return <OrdersPage />; 30 31 default: 32 break; 33 } 34 }; 35 36 return ( 37 <> 38 <Sidebar /> 39 <div className="ml-auto mb-6 lg:w-[75%] xl:w-[80%] 2xl:w-[85%]"> 40 <Navbar /> 41 <div className="p-2 pt-2 2xl:container"> 42 <div className="flex flex-col border-gray-300 rounded-xl"> 43 {showContainer()} 44 </div> 45 </div> 46 </div> 47 <CrudTableModal /> 48 {showFabAddNew()} 49 <ModalConfirmation /> 50 <FeatureModal /> 51 </> 52 ); 53}; 54