├── .gitignore ├── package-lock.json ├── package.json ├── public ├── favicon.png └── index.html └── src ├── App.js ├── assets └── styles │ ├── global │ ├── _fonts.scss │ └── _reset.scss │ └── index.scss ├── components ├── Editable.js ├── ReactForm.js └── ReadOnly.js └── index.js /.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 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "17", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^5.16.5", 7 | "@testing-library/react": "^13.3.0", 8 | "@testing-library/user-event": "^13.5.0", 9 | "bootstrap": "^5.2.0", 10 | "react": "^18.2.0", 11 | "react-bootstrap": "^2.5.0", 12 | "react-dom": "^18.2.0", 13 | "react-icons": "^4.4.0", 14 | "react-scripts": "5.0.1", 15 | "sass": "^1.54.4", 16 | "uuid": "^8.3.2", 17 | "web-vitals": "^2.1.4" 18 | }, 19 | "scripts": { 20 | "start": "react-scripts start", 21 | "build": "react-scripts build", 22 | "test": "react-scripts test", 23 | "eject": "react-scripts eject" 24 | }, 25 | "eslintConfig": { 26 | "extends": [ 27 | "react-app", 28 | "react-app/jest" 29 | ] 30 | }, 31 | "browserslist": { 32 | "production": [ 33 | ">0.2%", 34 | "not dead", 35 | "not op_mini all" 36 | ], 37 | "development": [ 38 | "last 1 chrome version", 39 | "last 1 firefox version", 40 | "last 1 safari version" 41 | ] 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /public/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/parham-ab/react-crud-table/c8010dc55de4353a93559a6cacb1898106e174ae/public/favicon.png -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | 12 | CRUD Table 13 | 14 | 15 | 16 |
17 | 18 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | // components 2 | import ReactForm from "./components/ReactForm"; 3 | 4 | const App = () => { 5 | return ; 6 | }; 7 | 8 | export default App; -------------------------------------------------------------------------------- /src/assets/styles/global/_fonts.scss: -------------------------------------------------------------------------------- 1 | @import url("https://fonts.googleapis.com/css2?family=Quicksand&family=Rubik:ital,wght@0,300;1,300&display=swap"); -------------------------------------------------------------------------------- /src/assets/styles/global/_reset.scss: -------------------------------------------------------------------------------- 1 | // CSS Reset 2 | *, 3 | *::before, 4 | *::after { 5 | margin: 0; 6 | padding: 0; 7 | border: none; 8 | outline: none; 9 | box-sizing: border-box; 10 | text-decoration: none; 11 | user-select: none; 12 | } 13 | a { 14 | color: inherit; 15 | } 16 | body { 17 | font-family: "Quicksand", sans-serif !important; 18 | font-family: "Rubik", sans-serif !important; 19 | } 20 | // bootstrap 21 | textarea:hover, 22 | textarea:active, 23 | textarea:focus, 24 | input:hover, 25 | input:active, 26 | input:focus, 27 | select:hover, 28 | select:active, 29 | select:focus, 30 | button:hover, 31 | button:active, 32 | button:focus, 33 | label:focus, 34 | .btn:active, 35 | .btn.active { 36 | outline: 0px !important; 37 | -webkit-appearance: none; 38 | box-shadow: none !important; 39 | } 40 | @import "~bootstrap/scss/bootstrap"; 41 | // scroll bar 42 | ::-webkit-scrollbar { 43 | width: 9px; 44 | } 45 | ::-webkit-scrollbar-track { 46 | background-color: transparent; 47 | } 48 | ::-webkit-scrollbar-thumb { 49 | background-color: rgb(158, 158, 158); 50 | border-radius: 5px; 51 | } 52 | ::-webkit-scrollbar-thumb:hover { 53 | background-color: rgb(187, 187, 187); 54 | } 55 | ::-webkit-scrollbar-thumb:active { 56 | background-color: rgb(207, 207, 207); 57 | } 58 | svg { 59 | font-size: 23px; 60 | cursor: pointer; 61 | } -------------------------------------------------------------------------------- /src/assets/styles/index.scss: -------------------------------------------------------------------------------- 1 | @import "global/reset"; 2 | @import "global/fonts"; 3 | -------------------------------------------------------------------------------- /src/components/Editable.js: -------------------------------------------------------------------------------- 1 | // bootstrap 2 | import { Form } from "react-bootstrap"; 3 | // icons 4 | import { FaSave } from "react-icons/fa"; 5 | import { MdCancel } from "react-icons/md"; 6 | 7 | const Editable = ({ 8 | handleEditFormChange, 9 | editFormData, 10 | handleCancelClick, 11 | }) => { 12 | return ( 13 | 14 | 15 | 16 | 27 | 28 | 29 | 30 | 31 | 41 | 42 | 43 | 44 | 45 | 55 | 56 | 57 | 58 | 59 | 70 | 71 | 72 | 73 | 74 | 84 | 85 | 86 | 87 | 90 | 91 | 92 | 93 | 94 | 95 | ); 96 | }; 97 | 98 | export default Editable; 99 | -------------------------------------------------------------------------------- /src/components/ReactForm.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useRef, useState } from "react"; 2 | // bootstrap components 3 | import { Button, Table } from "react-bootstrap"; 4 | import Form from "react-bootstrap/Form"; 5 | // uuid 6 | import { v4 as uuidv4 } from "uuid"; 7 | // components 8 | import ReadOnly from "./ReadOnly"; 9 | import Editable from "./Editable"; 10 | 11 | const ReactForm = () => { 12 | // refs 13 | const priorityVal = useRef(); 14 | const nameVal = useRef(); 15 | const emailVal = useRef(); 16 | const addressVal = useRef(); 17 | const phoneNoVal = useRef(); 18 | // states 19 | const [data, setData] = useState([]); 20 | const [addFormData, setAddFormData] = useState({ 21 | priority: "", 22 | fullName: "", 23 | address: "", 24 | phoneNo: "", 25 | email: "", 26 | }); 27 | const [editFormData, setEditFormData] = useState({ 28 | priority: "", 29 | fullName: "", 30 | address: "", 31 | phoneNo: "", 32 | email: "", 33 | }); 34 | const [refVals, setRefVals] = useState([ 35 | priorityVal, 36 | nameVal, 37 | emailVal, 38 | addressVal, 39 | phoneNoVal, 40 | ]); 41 | 42 | const [editDataId, setEditDataId] = useState(null); 43 | // onchange handler of inputs 44 | const handleAddFormChange = (e) => { 45 | e.preventDefault(); 46 | setAddFormData({ ...addFormData, [e.target.name]: e.target.value }); 47 | }; 48 | // edit forms 49 | const handleEditFormChange = (e) => { 50 | e.preventDefault(); 51 | setEditFormData({ ...editFormData, [e.target.name]: e.target.value }); 52 | }; 53 | // add new value to the table 54 | const handleAddFormSubmit = (e) => { 55 | e.preventDefault(); 56 | const newContact = { 57 | id: uuidv4(), 58 | priority: addFormData.priority, 59 | fullName: addFormData.fullName, 60 | address: addFormData.address, 61 | phoneNo: addFormData.phoneNo, 62 | email: addFormData.email, 63 | }; 64 | const newData = [...data, newContact]; 65 | setData(newData); 66 | localStorage.setItem("crud-table-data", JSON.stringify(newData)); 67 | // clear inputs 68 | refVals.forEach((items) => { 69 | items.current.value = ""; 70 | }); 71 | }; 72 | // submit edited forms 73 | const handleEditFormSubmit = (e) => { 74 | e.preventDefault(); 75 | const editedData = { 76 | id: editDataId, 77 | priority: editFormData.priority, 78 | fullName: editFormData.fullName, 79 | address: editFormData.address, 80 | phoneNo: editFormData.phoneNo, 81 | email: editFormData.email, 82 | }; 83 | const newData = [...data]; 84 | const index = data.findIndex((item) => item.id === editDataId); 85 | newData[index] = editedData; 86 | setData(newData); 87 | localStorage.setItem("crud-table-data", JSON.stringify(newData)); 88 | setEditDataId(null); 89 | }; 90 | // edit button 91 | const handleEditClick = (e, data) => { 92 | e.preventDefault(); 93 | setEditDataId(data.id); 94 | const formValues = { 95 | priority: data.priority, 96 | fullName: data.fullName, 97 | address: data.address, 98 | phoneNo: data.phoneNo, 99 | email: data.email, 100 | }; 101 | setEditFormData(formValues); 102 | }; 103 | // cancel button 104 | const handleCancelClick = () => { 105 | setEditDataId(null); 106 | }; 107 | // delete 108 | const handleDeleteClick = (contactId) => { 109 | const newData = [...data]; 110 | const index = data.findIndex((contact) => contact.id === contactId); 111 | newData.splice(index, 1); 112 | setData(newData); 113 | localStorage.setItem("crud-table-data", JSON.stringify(newData)); 114 | }; 115 | // load data from localStorage per each change 116 | useEffect(() => { 117 | const savedData = localStorage.getItem("crud-table-data"); 118 | const parsedData = JSON.parse(savedData); 119 | parsedData !== null && setData(parsedData); 120 | }, []); 121 | // reorder tables based on priority 122 | const [reorderedData, setReOrderedData] = useState([]); 123 | useEffect(() => { 124 | const sortedData = data.sort((a, b) => a.priority - b.priority); 125 | setReOrderedData(sortedData); 126 | }, [data]); 127 | 128 | return ( 129 | <> 130 |
131 | {reorderedData.length > 0 && ( 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | {reorderedData.map((item) => ( 145 | 146 | {editDataId === item.id ? ( 147 | 152 | ) : ( 153 | 158 | )} 159 | 160 | ))} 161 | 162 |
PriorityNameAddressPhone NumberEmailActions
163 | )} 164 |
165 | {/* add new data */} 166 |
170 |
171 | 172 | 182 | 191 | 200 | 210 | 219 | 220 | 223 |
224 |
225 | 226 | ); 227 | }; 228 | 229 | export default ReactForm; 230 | -------------------------------------------------------------------------------- /src/components/ReadOnly.js: -------------------------------------------------------------------------------- 1 | // icons 2 | import { FaRegTrashAlt } from "react-icons/fa"; 3 | import { FiEdit2 } from "react-icons/fi"; 4 | 5 | const ReadOnly = ({ data, handleEditClick, handleDeleteClick }) => { 6 | return ( 7 | 8 | {data.priority} 9 | {data.fullName} 10 | {data.address} 11 | {data.phoneNo} 12 | {data.email} 13 | 14 | handleEditClick(e, data)} 17 | className="text-warning" 18 | > 19 | 20 | 21 | handleDeleteClick(data.id)} 24 | className="text-danger" 25 | > 26 | 27 | 28 | 29 | 30 | ); 31 | }; 32 | 33 | export default ReadOnly; 34 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom/client"; 3 | import App from "./App"; 4 | // styles 5 | import "./assets/styles/index.scss"; 6 | 7 | const root = ReactDOM.createRoot(document.getElementById("root")); 8 | root.render(); --------------------------------------------------------------------------------