init
376 files changed, +53044 -0
This diff is large and was cut short. Clone the repository to see all of it.
+33-0.gitignore
| @@ -0,0 +1,33 @@ | ||
| 1 | +node_modules | |
| 2 | +package-lock.json | |
| 3 | +yarn.lock | |
| 4 | +build | |
| 5 | + | |
| 6 | +# dependencies | |
| 7 | +Client/node_modules | |
| 8 | +server/node_modules | |
| 9 | +server/uploads | |
| 10 | +server/.env | |
| 11 | +Client/.env | |
| 12 | + | |
| 13 | +/.pnp | |
| 14 | +.pnp.js | |
| 15 | + | |
| 16 | +# testing | |
| 17 | +/coverage | |
| 18 | + | |
| 19 | +# production | |
| 20 | +/build | |
| 21 | + | |
| 22 | +*.bat | |
| 23 | + | |
| 24 | +# misc | |
| 25 | +.DS_Store | |
| 26 | +.env.local | |
| 27 | +.env.development.local | |
| 28 | +.env.test.local | |
| 29 | +.env.production.local | |
| 30 | + | |
| 31 | +npm-debug.log* | |
| 32 | +yarn-debug.log* | |
| 33 | +yarn-error.log* |
+2-0Client/.gitattributes
| @@ -0,0 +1,2 @@ | ||
| 1 | +# Auto detect text files and perform LF normalization | |
| 2 | +* text=auto |
+2-0Client/.vscode/settings.json
| @@ -0,0 +1,2 @@ | ||
| 1 | +{ | |
| 2 | +} |
+8-0Client/jsconfig.json
| @@ -0,0 +1,8 @@ | ||
| 1 | +{ | |
| 2 | + "compilerOptions": { | |
| 3 | + "baseUrl": "src", | |
| 4 | + "paths": { | |
| 5 | + "*": ["src/*"] | |
| 6 | + } | |
| 7 | + } | |
| 8 | +} |
+92-0Client/package.json
| @@ -0,0 +1,92 @@ | ||
| 1 | +{ | |
| 2 | + "name": "real-estate", | |
| 3 | + "version": "1.0.1", | |
| 4 | + "private": true, | |
| 5 | + "dependencies": { | |
| 6 | + "@chakra-ui/icons": "^1.1.5", | |
| 7 | + "@chakra-ui/react": "1.8.8", | |
| 8 | + "@chakra-ui/system": "^1.12.1", | |
| 9 | + "@chakra-ui/theme-tools": "^1.3.6", | |
| 10 | + "@emotion/cache": "^11.7.1", | |
| 11 | + "@emotion/react": "^11.4.1", | |
| 12 | + "@emotion/styled": "^11.3.0", | |
| 13 | + "@fullcalendar/core": "^6.1.8", | |
| 14 | + "@fullcalendar/react": "^6.1.8", | |
| 15 | + "@fullcalendar/resource": "^6.1.8", | |
| 16 | + "@fullcalendar/resource-timeline": "^6.1.8", | |
| 17 | + "@fullcalendar/scrollgrid": "^6.1.8", | |
| 18 | + "@hypertheme-editor/chakra-ui": "^0.1.5", | |
| 19 | + "@react-pdf/renderer": "^3.4.4", | |
| 20 | + "@reduxjs/toolkit": "^2.0.1", | |
| 21 | + "@stripe/react-stripe-js": "^2.3.0", | |
| 22 | + "@stripe/stripe-js": "^2.1.6", | |
| 23 | + "apexcharts": "^4.0.0", | |
| 24 | + "axios": "^1.4.0", | |
| 25 | + "chakra-ui-autocomplete": "^1.4.5", | |
| 26 | + "dayjs": "^1.11.10", | |
| 27 | + "exceljs": "^4.4.0", | |
| 28 | + "formik": "^2.4.2", | |
| 29 | + "framer-motion": "^4.1.17", | |
| 30 | + "fullcalendar": "^6.1.8", | |
| 31 | + "html2pdf.js": "^0.10.2", | |
| 32 | + "jwt-decode": "^3.1.2", | |
| 33 | + "moment": "^2.29.4", | |
| 34 | + "papaparse": "^5.4.1", | |
| 35 | + "react": "17.0.2", | |
| 36 | + "react-apexcharts": "^1.4.1", | |
| 37 | + "react-beautiful-dnd": "^13.1.1", | |
| 38 | + "react-calendar": "^3.7.0", | |
| 39 | + "react-custom-scrollbars-2": "^4.2.1", | |
| 40 | + "react-datepicker": "^4.16.0", | |
| 41 | + "react-dom": "17.0.2", | |
| 42 | + "react-dropzone": "^12.0.4", | |
| 43 | + "react-email-editor": "^1.7.11", | |
| 44 | + "react-icons": "^4.3.1", | |
| 45 | + "react-redux": "^7.2.9", | |
| 46 | + "react-router-dom": "^6.14.1", | |
| 47 | + "react-scripts": "^5.0.0", | |
| 48 | + "react-signature-canvas": "^1.0.6", | |
| 49 | + "react-table": "^7.7.0", | |
| 50 | + "react-toastify": "^9.1.3", | |
| 51 | + "redux": "^5.0.1", | |
| 52 | + "redux-persist": "^6.0.0", | |
| 53 | + "redux-thunk": "^3.1.0", | |
| 54 | + "stylis-plugin-rtl": "2.0.2", | |
| 55 | + "xlsx": "^0.18.5", | |
| 56 | + "yup": "^1.2.0" | |
| 57 | + }, | |
| 58 | + "scripts": { | |
| 59 | + "start": "react-scripts start", | |
| 60 | + "build": "react-scripts build", | |
| 61 | + "test": "react-scripts test", | |
| 62 | + "eject": "react-scripts eject", | |
| 63 | + "predeploy": "npm run build", | |
| 64 | + "deploy": "gh-pages -d build", | |
| 65 | + "sitemap": "babel-node ./sitemap-builder.js" | |
| 66 | + }, | |
| 67 | + "resolutions": { | |
| 68 | + "react-error-overlay": "6.0.9" | |
| 69 | + }, | |
| 70 | + "eslintConfig": { | |
| 71 | + "extends": [ | |
| 72 | + "react-app", | |
| 73 | + "react-app/jest" | |
| 74 | + ] | |
| 75 | + }, | |
| 76 | + "browserslist": { | |
| 77 | + "production": [ | |
| 78 | + ">0.2%", | |
| 79 | + "not dead", | |
| 80 | + "not op_mini all" | |
| 81 | + ], | |
| 82 | + "development": [ | |
| 83 | + "last 1 chrome version", | |
| 84 | + "last 1 firefox version", | |
| 85 | + "last 1 safari version" | |
| 86 | + ] | |
| 87 | + }, | |
| 88 | + "devDependencies": { | |
| 89 | + "@babel/plugin-proposal-private-property-in-object": "^7.21.11", | |
| 90 | + "gh-pages": "^3.2.3" | |
| 91 | + } | |
| 92 | +} |
+0-0Client/public/favicon.ico
Binary file changed.
+0-0Client/public/favicon1.ico
Binary file changed.
+22-0Client/public/index.html
| @@ -0,0 +1,22 @@ | ||
| 1 | + | |
| 2 | + | |
| 3 | +<!DOCTYPE html> | |
| 4 | +<html lang="en"> | |
| 5 | + | |
| 6 | +<head> | |
| 7 | + <meta charset="utf-8" /> | |
| 8 | + <link rel="icon" href="%PUBLIC_URL%/favicon.ico" /> | |
| 9 | + <meta name="viewport" content="width=device-width, initial-scale=1" /> | |
| 10 | + <meta name="theme-color" content="#000000" /> | |
| 11 | + <title itemprop="name">Real E-state</title> | |
| 12 | + <!-- Manifest and apple icon --> | |
| 13 | + <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" /> | |
| 14 | + <link rel="manifest" href="%PUBLIC_URL%/manifest.json" /> | |
| 15 | +</head> | |
| 16 | + | |
| 17 | +<body> | |
| 18 | + <noscript>You need to enable JavaScript to run this app.</noscript> | |
| 19 | + <div id="root"></div> | |
| 20 | +</body> | |
| 21 | + | |
| 22 | +</html> |
+15-0Client/public/manifest.json
| @@ -0,0 +1,15 @@ | ||
| 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 | +} |
+3-0Client/public/robots.txt
| @@ -0,0 +1,3 @@ | ||
| 1 | +User-Agent: * | |
| 2 | +Disallow: | |
| 3 | +Sitemap: https://horizon-ui.com |
+270-0Client/src/assets/css/App.css
| @@ -0,0 +1,270 @@ | ||
| 1 | +@import url("https://fonts.googleapis.com/css2?family=DM+Sans:ital,wght@0,400;0,500;0,700;1,400;1,500;1,700&display=swap"); | |
| 2 | + | |
| 3 | +body { | |
| 4 | + font-family: "DM Sans", sans-serif; | |
| 5 | +} | |
| 6 | + | |
| 7 | +option { | |
| 8 | + color: black; | |
| 9 | +} | |
| 10 | + | |
| 11 | +* { | |
| 12 | + letter-spacing: 0.3px; | |
| 13 | + outline: none; | |
| 14 | +} | |
| 15 | + | |
| 16 | +::-webkit-scrollbar { | |
| 17 | + height: 4px !important; | |
| 18 | + width: 7px; | |
| 19 | +} | |
| 20 | + | |
| 21 | +::-webkit-scrollbar-thumb { | |
| 22 | + background: #b9b9b9; | |
| 23 | + border-radius: 20px; | |
| 24 | +} | |
| 25 | + | |
| 26 | +.css-70fard, | |
| 27 | +.apexcharts-toolbar { | |
| 28 | + z-index: 0 !important; | |
| 29 | +} | |
| 30 | + | |
| 31 | +.fc-bg-event { | |
| 32 | + opacity: 1 !important; | |
| 33 | +} | |
| 34 | + | |
| 35 | +.p0 { | |
| 36 | + margin-top: 0; | |
| 37 | +} | |
| 38 | + | |
| 39 | +/* TABLE */ | |
| 40 | +.table-container { | |
| 41 | + min-height: 23vh; | |
| 42 | + max-height: 23vh; | |
| 43 | + overflow-y: auto; | |
| 44 | +} | |
| 45 | + | |
| 46 | +.table-container thead { | |
| 47 | + border-bottom: 1px solid black; | |
| 48 | + box-shadow: -2px 0px 4px -2px #5c5d5efa; | |
| 49 | + position: sticky; | |
| 50 | + top: 0; | |
| 51 | + height: 25px; | |
| 52 | + background: #ffffff; | |
| 53 | +} | |
| 54 | + | |
| 55 | +.table-container-property { | |
| 56 | + min-height: auto; | |
| 57 | + max-height: 68vh; | |
| 58 | + overflow-y: auto; | |
| 59 | +} | |
| 60 | + | |
| 61 | +.css-efk3t2 { | |
| 62 | + max-height: 15rem !important; | |
| 63 | + overflow-y: scroll !important; | |
| 64 | +} | |
| 65 | + | |
| 66 | +.table-container-property thead { | |
| 67 | + position: sticky; | |
| 68 | + top: 0; | |
| 69 | + height: 25px; | |
| 70 | + background: #ffffff; | |
| 71 | + border-bottom: 1px solid black; | |
| 72 | + box-shadow: -2px 0px 4px -2px #5c5d5efa; | |
| 73 | +} | |
| 74 | + | |
| 75 | +.table-fix-container { | |
| 76 | + min-height: 63vh; | |
| 77 | + max-height: 63vh; | |
| 78 | + overflow-y: auto; | |
| 79 | +} | |
| 80 | + | |
| 81 | +.small-table-fix-container { | |
| 82 | + min-height: 15vh; | |
| 83 | + max-height: 63vh; | |
| 84 | + overflow-y: auto; | |
| 85 | +} | |
| 86 | + | |
| 87 | +.table-fix-container thead { | |
| 88 | + border-bottom: 1px solid black; | |
| 89 | + box-shadow: -2px 0px 4px -2px #5c5d5efa; | |
| 90 | + position: sticky; | |
| 91 | + top: 0; | |
| 92 | + height: 25px; | |
| 93 | + background: #ffffff; | |
| 94 | +} | |
| 95 | + | |
| 96 | +.importTable { | |
| 97 | + min-height: auto; | |
| 98 | + max-height: 82vh; | |
| 99 | + overflow-y: auto; | |
| 100 | +} | |
| 101 | + | |
| 102 | +.tableData { | |
| 103 | + padding: 10px !important; | |
| 104 | +} | |
| 105 | + | |
| 106 | +/* .leadRow td { | |
| 107 | + border-bottom: 1px solid #e2e8f0; | |
| 108 | +} */ | |
| 109 | + | |
| 110 | +.datePickerBorder { | |
| 111 | + border: 1px solid #e2e8f0; | |
| 112 | + padding: 0 5px; | |
| 113 | + border-radius: 5px; | |
| 114 | + margin: 0 10px !important; | |
| 115 | +} | |
| 116 | + | |
| 117 | +.imageCard { | |
| 118 | + position: relative; | |
| 119 | + overflow: hidden; | |
| 120 | + margin-right: 12px; | |
| 121 | + /* height: 200px; | |
| 122 | + width: 400px; */ | |
| 123 | +} | |
| 124 | + | |
| 125 | +.imageContent { | |
| 126 | + content: ""; | |
| 127 | + position: absolute; | |
| 128 | + top: 0; | |
| 129 | + left: 0; | |
| 130 | + background-color: #00000096; | |
| 131 | + height: 100%; | |
| 132 | + width: 100%; | |
| 133 | + display: flex; | |
| 134 | + justify-content: center; | |
| 135 | + align-items: center; | |
| 136 | + opacity: 0; | |
| 137 | +} | |
| 138 | + | |
| 139 | +.imageCard:hover .imageContent { | |
| 140 | + opacity: 1; | |
| 141 | +} | |
| 142 | + | |
| 143 | +.EditDelete { | |
| 144 | + display: none; | |
| 145 | +} | |
| 146 | + | |
| 147 | +.CustomFieldName { | |
| 148 | + border-bottom: 1px solid rgba(128, 128, 128, 0.705); | |
| 149 | +} | |
| 150 | + | |
| 151 | +.CustomFieldName:hover .EditDelete { | |
| 152 | + display: block; | |
| 153 | +} | |
| 154 | + | |
| 155 | +.pending { | |
| 156 | + background-color: #fbf4dd !important; | |
| 157 | + color: #a37f08 !important; | |
| 158 | + border: 1px solid #a37f08 !important; | |
| 159 | +} | |
| 160 | + | |
| 161 | +.onHold { | |
| 162 | + background-color: #ffeeeb !important; | |
| 163 | + color: #db5436 !important; | |
| 164 | + border: 1px solid #db5436 !important; | |
| 165 | +} | |
| 166 | + | |
| 167 | +.completed { | |
| 168 | + background-color: #eaf9e6 !important; | |
| 169 | + color: #4d8f3a !important; | |
| 170 | + border: 1px solid #4d8f3a !important; | |
| 171 | +} | |
| 172 | + | |
| 173 | +.toDo { | |
| 174 | + background-color: #ebf5ff !important; | |
| 175 | + color: #1f7eeb !important; | |
| 176 | + border: 1px solid #1f7eeb !important; | |
| 177 | +} | |
| 178 | + | |
| 179 | +.inProgress { | |
| 180 | + background-color: #eee7fc !important; | |
| 181 | + color: #7038db !important; | |
| 182 | + border: 1px solid #7038db !important; | |
| 183 | +} | |
| 184 | + | |
| 185 | +.chakra-checkbox[disabled] .chakra-checkbox__control { | |
| 186 | + background-color: #dcdee0 !important; | |
| 187 | + border: 2px solid #d1d1d1; | |
| 188 | +} | |
| 189 | + | |
| 190 | +/* Invoice.css */ | |
| 191 | +.invoice-container { | |
| 192 | + width: 100%; | |
| 193 | + margin: 20px auto; | |
| 194 | + padding: 20px; | |
| 195 | + border: 1px solid #000; | |
| 196 | + font-family: Arial, sans-serif; | |
| 197 | + font-size: 14px; | |
| 198 | +} | |
| 199 | +.invoice-container2 { | |
| 200 | + width: 50%; | |
| 201 | + margin: 20px auto; | |
| 202 | + padding: 20px; | |
| 203 | + border: 1px solid #000; | |
| 204 | + font-family: Arial, sans-serif; | |
| 205 | + font-size: 14px; | |
| 206 | +} | |
| 207 | + | |
| 208 | +.invoice-header { | |
| 209 | + display: flex; | |
| 210 | + justify-content: space-between; | |
| 211 | + align-items: center; | |
| 212 | +} | |
| 213 | + | |
| 214 | +.logo { | |
| 215 | + font-weight: bold; | |
| 216 | + font-size: 24px; | |
| 217 | + color: orange; | |
| 218 | +} | |
| 219 | + | |
| 220 | +.invoice-details { | |
| 221 | + text-align: right; | |
| 222 | +} | |
| 223 | + | |
| 224 | +.address-section { | |
| 225 | + margin-top: 20px; | |
| 226 | + display: flex; | |
| 227 | + justify-content: space-between; | |
| 228 | +} | |
| 229 | + | |
| 230 | +.address { | |
| 231 | + width: 45%; | |
| 232 | + margin-top: 20px; | |
| 233 | +} | |
| 234 | + | |
| 235 | +.invoice-info { | |
| 236 | + margin-top: 20px; | |
| 237 | +} | |
| 238 | + | |
| 239 | +.invoice-table { | |
| 240 | + width: 100%; | |
| 241 | + border-collapse: collapse; | |
| 242 | + margin-top: 20px; | |
| 243 | +} | |
| 244 | + | |
| 245 | +.invoice-table th, | |
| 246 | +.invoice-table td { | |
| 247 | + border: 1px solid #000; | |
| 248 | + padding: 5px; | |
| 249 | + text-align: center; | |
| 250 | +} | |
| 251 | + | |
| 252 | +.totals { | |
| 253 | + margin-top: 20px; | |
| 254 | + display: flex; | |
| 255 | + justify-content: end; | |
| 256 | +} | |
| 257 | + | |
| 258 | +.totals > div { | |
| 259 | + margin-bottom: 5px; | |
| 260 | +} | |
| 261 | + | |
| 262 | +.net-amount { | |
| 263 | + font-weight: bold; | |
| 264 | +} | |
| 265 | + | |
| 266 | +.footer { | |
| 267 | + margin-top: 20px; | |
| 268 | + text-align: center; | |
| 269 | + font-style: italic; | |
| 270 | +} |
+3-0Client/src/assets/css/Contact.css
| @@ -0,0 +1,3 @@ | ||
| 1 | +form { | |
| 2 | + width: 100%; | |
| 3 | +} |
+157-0Client/src/assets/css/MiniCalendar.css
| @@ -0,0 +1,157 @@ | ||
| 1 | +@import url("https://fonts.googleapis.com/css2?family=DM+Sans:ital,wght@0,400;0,500;0,700;1,400;1,500;1,700&display=swap"); | |
| 2 | +/* LIGHT MODE + GENERAL */ | |
| 3 | + | |
| 4 | +.react-calendar { | |
| 5 | + border: unset; | |
| 6 | + background-color: transparent; | |
| 7 | + font-family: "DM Sans", sans-serif; | |
| 8 | +} | |
| 9 | +.react-calendar__navigation__prev2-button { | |
| 10 | + display: none; | |
| 11 | +} | |
| 12 | +.react-calendar__navigation__next2-button { | |
| 13 | + display: none; | |
| 14 | +} | |
| 15 | +.react-calendar__navigation { | |
| 16 | + align-items: center; | |
| 17 | +} | |
| 18 | +abbr[title] { | |
| 19 | + border-bottom: none; | |
| 20 | + -webkit-text-decoration: unset; | |
| 21 | + text-decoration: unset; | |
| 22 | + -webkit-text-decoration: unset; | |
| 23 | + -webkit-text-decoration: unset; | |
| 24 | + text-decoration: unset !important; | |
| 25 | +} | |
| 26 | +.react-calendar__navigation__prev-button { | |
| 27 | + background-color: #4318ff !important; | |
| 28 | + border-radius: 10px; | |
| 29 | + min-width: 34px !important; | |
| 30 | + height: 34px !important; | |
| 31 | + color: white; | |
| 32 | +} | |
| 33 | +.react-calendar__navigation__next-button { | |
| 34 | + background-color: #4318ff !important; | |
| 35 | + border-radius: 10px; | |
| 36 | + min-width: 34px !important; | |
| 37 | + width: 34px !important; | |
| 38 | + height: 34px !important; | |
| 39 | + color: white; | |
| 40 | +} | |
| 41 | +.react-calendar__navigation__label { | |
| 42 | + font-weight: 700 !important; | |
| 43 | + font-size: 18px; | |
| 44 | +} | |
| 45 | +.react-calendar__navigation__label:hover, | |
| 46 | +.react-calendar__navigation__label:focus { | |
| 47 | + background-color: unset !important; | |
| 48 | + border-radius: 10px; | |
| 49 | +} | |
| 50 | +.react-calendar__tile { | |
| 51 | + font-size: 12px; | |
| 52 | + display: flex; | |
| 53 | + align-items: center; | |
| 54 | + justify-content: center; | |
| 55 | + padding: 0px !important; | |
| 56 | + height: 34px !important; | |
| 57 | + color: #1b2559; | |
| 58 | +} | |
| 59 | +.react-calendar__month-view__weekdays { | |
| 60 | + background-color: #f4f7fe; | |
| 61 | + border-radius: 10px; | |
| 62 | + margin-bottom: 6px; | |
| 63 | +} | |
| 64 | +.react-calendar__tile--now, | |
| 65 | +.react-calendar__tile--now:enabled:hover, | |
| 66 | +.react-calendar__tile--now:enabled:focus { | |
| 67 | + background-color: #f4f7fe; | |
| 68 | + border-radius: 10px; | |
| 69 | +} | |
| 70 | +.react-calendar__month-view__days__day--neighboringMonth { | |
| 71 | + color: #b0bbd5; | |
| 72 | +} | |
| 73 | +.react-calendar__tile--active, | |
| 74 | +.react-calendar__tile--active:enabled:hover, | |
| 75 | +.react-calendar__tile--active:enabled:focus { | |
| 76 | + background: #4318ff; | |
| 77 | + border-radius: 10px; | |
| 78 | + color: white; | |
| 79 | +} | |
| 80 | +.react-calendar__tile--range, | |
| 81 | +.react-calendar__tile--range:enabled:hover, | |
| 82 | +.react-calendar__tile--range:enabled:focus { | |
| 83 | + background: #f4f7fe; | |
| 84 | + color: #4318ff; | |
| 85 | + border-radius: 0px; | |
| 86 | +} | |
| 87 | +.react-calendar__tile--rangeStart, | |
| 88 | +.react-calendar__tile--rangeStart:enabled:hover, | |
| 89 | +.react-calendar__tile--rangeStart:enabled:focus { | |
| 90 | + background: #4318ff; | |
| 91 | + border-top-left-radius: 10px; | |
| 92 | + border-bottom-left-radius: 10px; | |
| 93 | + color: white; | |
| 94 | +} | |
| 95 | +.react-calendar__tile--rangeEnd, | |
| 96 | +.react-calendar__tile--rangeEnd:enabled:hover, | |
| 97 | +.react-calendar__tile--rangeEnd:enabled:focus { | |
| 98 | + background: #4318ff; | |
| 99 | + border-top-right-radius: 10px; | |
| 100 | + border-bottom-right-radius: 10px; | |
| 101 | + color: white; | |
| 102 | +} | |
| 103 | + | |
| 104 | +/* DARK MODE */ | |
| 105 | + | |
| 106 | +body.chakra-ui-dark .react-calendar { | |
| 107 | + border-radius: 30px; | |
| 108 | +} | |
| 109 | +body.chakra-ui-dark .react-calendar__navigation__prev-button { | |
| 110 | + background-color: #7551ff !important; | |
| 111 | +} | |
| 112 | +body.chakra-ui-dark .react-calendar__navigation__next-button { | |
| 113 | + background-color: #7551ff !important; | |
| 114 | + color: white; | |
| 115 | +} | |
| 116 | +body.chakra-ui-dark .react-calendar__tile { | |
| 117 | + color: white; | |
| 118 | +} | |
| 119 | +body.chakra-ui-dark .react-calendar__tile:enabled:hover, | |
| 120 | +body.chakra-ui-dark .react-calendar__tile:enabled:focus { | |
| 121 | + background-color: rgba(255, 255, 255, 0.1); | |
| 122 | +} | |
| 123 | +body.chakra-ui-dark .react-calendar__month-view__weekdays { | |
| 124 | + background-color: rgba(255, 255, 255, 0.1); | |
| 125 | +} | |
| 126 | +body.chakra-ui-dark .react-calendar__tile--now, | |
| 127 | +body.chakra-ui-dark .react-calendar__tile--now:enabled:hover, | |
| 128 | +body.chakra-ui-dark .react-calendar__tile--now:enabled:focus { | |
| 129 | + background-color: rgba(255, 255, 255, 0.1); | |
| 130 | +} | |
| 131 | +body.chakra-ui-dark .react-calendar__month-view__days__day--neighboringMonth { | |
| 132 | + color: #b0bbd5; | |
| 133 | +} | |
| 134 | +body.chakra-ui-dark .react-calendar__tile--active, | |
| 135 | +body.chakra-ui-dark .react-calendar__tile--active:enabled:hover, | |
| 136 | +body.chakra-ui-dark .react-calendar__tile--active:enabled:focus { | |
| 137 | + background: #7551ff; | |
| 138 | + color: white; | |
| 139 | +} | |
| 140 | +body.chakra-ui-dark .react-calendar__tile--range, | |
| 141 | +body.chakra-ui-dark .react-calendar__tile--range:enabled:hover, | |
| 142 | +body.chakra-ui-dark .react-calendar__tile--range:enabled:focus { | |
| 143 | + background: rgba(255, 255, 255, 0.1); | |
| 144 | + color: #7551ff; | |
| 145 | +} | |
| 146 | +body.chakra-ui-dark .react-calendar__tile--rangeStart, | |
| 147 | +body.chakra-ui-dark .react-calendar__tile--rangeStart:enabled:hover, | |
| 148 | +body.chakra-ui-dark .react-calendar__tile--rangeStart:enabled:focus { | |
| 149 | + background: #7551ff; | |
| 150 | + color: white; | |
| 151 | +} | |
| 152 | +body.chakra-ui-dark .react-calendar__tile--rangeEnd, | |
| 153 | +body.chakra-ui-dark .react-calendar__tile--rangeEnd:enabled:hover, | |
| 154 | +body.chakra-ui-dark .react-calendar__tile--rangeEnd:enabled:focus { | |
| 155 | + background: #7551ff; | |
| 156 | + color: white; | |
| 157 | +} |
+0-0Client/src/assets/img/Announcement.png
Binary file changed.
+0-0Client/src/assets/img/AnnouncementWinter.png
Binary file changed.
+0-0Client/src/assets/img/american-express.png
Binary file changed.
+0-0Client/src/assets/img/auth/PNG-02.png
Binary file changed.
+0-0Client/src/assets/img/auth/banner.png
Binary file changed.
+0-0Client/src/assets/img/fileImage/csv.png
Binary file changed.
+0-0Client/src/assets/img/fileImage/file.png
Binary file changed.
+0-0Client/src/assets/img/fileImage/jpg.png
Binary file changed.
+0-0Client/src/assets/img/fileImage/pdf.png
Binary file changed.
+0-0Client/src/assets/img/fileImage/png.png
Binary file changed.
+0-0Client/src/assets/img/fileImage/xls.png
Binary file changed.
+0-0Client/src/assets/img/fileImage/xlsx.png
Binary file changed.
+0-0Client/src/assets/img/layout/logoWhite.png
Binary file changed.
+0-0Client/src/assets/img/masterCard.png
Binary file changed.
+0-0Client/src/assets/img/pay.avif
Binary file changed.
+0-0Client/src/assets/img/visa.png
Binary file changed.
+58-0Client/src/components/Dropzone.js
| @@ -0,0 +1,58 @@ | ||
| 1 | +// Chakra imports | |
| 2 | +import { Button, Flex, Input, useColorModeValue } from "@chakra-ui/react"; | |
| 3 | +// Assets | |
| 4 | +import React from "react"; | |
| 5 | +import { useDropzone } from "react-dropzone"; | |
| 6 | + | |
| 7 | +function Dropzone(props) { | |
| 8 | + const { content, ...rest } = props; | |
| 9 | + const { getRootProps, getInputProps } = useDropzone({ | |
| 10 | + // multiple: true, // Set to false if you only want to allow selecting one file | |
| 11 | + multiple: props.hasOwnProperty('isMultipleAllow') ? props.isMultipleAllow : true, // assign false in props.isMultipleAllow if you only want to allow selecting one file | |
| 12 | + onDrop: (acceptedFiles) => { | |
| 13 | + if (props.img === "img") { | |
| 14 | + const imageFiles = acceptedFiles.filter((file) => { | |
| 15 | + return file.type.startsWith("image/"); | |
| 16 | + }) | |
| 17 | + if (imageFiles.length > 0) { | |
| 18 | + props.onFileSelect(imageFiles); | |
| 19 | + } | |
| 20 | + } | |
| 21 | + else if (props.csv === "csv") { | |
| 22 | + const excelFiles = acceptedFiles.filter((file) => { | |
| 23 | + return ["text/csv", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"]?.includes(file?.type) | |
| 24 | + }) | |
| 25 | + if (excelFiles.length > 0) { | |
| 26 | + props.onFileSelect(excelFiles); | |
| 27 | + } | |
| 28 | + } | |
| 29 | + else if (acceptedFiles.length > 0) { | |
| 30 | + props.onFileSelect(acceptedFiles); | |
| 31 | + } | |
| 32 | + | |
| 33 | + }, | |
| 34 | + }); | |
| 35 | + | |
| 36 | + const bg = useColorModeValue("gray.100", "navy.700"); | |
| 37 | + const borderColor = useColorModeValue("secondaryGray.100", "whiteAlpha.100"); | |
| 38 | + return ( | |
| 39 | + <Flex | |
| 40 | + align='center' | |
| 41 | + justify='center' | |
| 42 | + bg={bg} | |
| 43 | + border='1px dashed' | |
| 44 | + borderColor={borderColor} | |
| 45 | + borderRadius='16px' | |
| 46 | + w='100%' | |
| 47 | + h='max-content' | |
| 48 | + minH='100%' | |
| 49 | + cursor='pointer' | |
| 50 | + {...getRootProps({ className: "dropzone" })} | |
| 51 | + {...rest}> | |
| 52 | + <Input type="file" variant='main' multiple {...getInputProps()} /> | |
| 53 | + <Button variant='no-effects'>{content}</Button> | |
| 54 | + </Flex> | |
| 55 | + ); | |
| 56 | +} | |
| 57 | + | |
| 58 | +export default Dropzone; |
+91-0Client/src/components/FolderTreeView/folderTreeView.js
| @@ -0,0 +1,91 @@ | ||
| 1 | +import { DeleteIcon, DownloadIcon, LinkIcon, ViewIcon } from '@chakra-ui/icons'; | |
| 2 | +import { Collapse, Flex, Icon, IconButton, List, ListIcon, ListItem, Menu, MenuButton, MenuItem, MenuList, Text } from '@chakra-ui/react'; | |
| 3 | +import React, { useState } from 'react' | |
| 4 | +import { FcOpenedFolder } from 'react-icons/fc'; | |
| 5 | +import { FiChevronDown, FiChevronRight, FiFile } from 'react-icons/fi'; | |
| 6 | +import { IoIosContact } from 'react-icons/io'; | |
| 7 | +import { MdLeaderboard } from 'react-icons/md'; | |
| 8 | +import { useNavigate } from 'react-router-dom'; | |
| 9 | +import Delete from 'views/admin/document/component/Delete'; | |
| 10 | +import LinkModel from 'views/admin/document/component/LinkModel'; | |
| 11 | +import { CiMenuKebab } from "react-icons/ci"; | |
| 12 | + | |
| 13 | +const FolderTreeView = ({ data, deleteFile, item, download, name, isFile, children, setLinkDocument, from }) => { | |
| 14 | + const [isOpen, setIsOpen] = useState(false); | |
| 15 | + // const user = localStorage.getItem('user'); | |
| 16 | + const [deleteModel, setDelete] = useState(false); | |
| 17 | + const [linkModel, setLinkModel] = useState(false); | |
| 18 | + const [id, setId] = useState(false); | |
| 19 | + const navigate = useNavigate() | |
| 20 | + | |
| 21 | + const handleToggle = () => { | |
| 22 | + setIsOpen(!isOpen); | |
| 23 | + }; | |
| 24 | + const isFolder = !isFile && !!children; | |
| 25 | + | |
| 26 | + const handleClick = (data) => { | |
| 27 | + download(data) | |
| 28 | + } | |
| 29 | + | |
| 30 | + const deletedata = (data) => { | |
| 31 | + setDelete(true) | |
| 32 | + setId(data) | |
| 33 | + } | |
| 34 | + | |
| 35 | + const handleLinkClick = (data) => { | |
| 36 | + setLinkModel(true) | |
| 37 | + setId(data) | |
| 38 | + } | |
| 39 | + const user = JSON.parse(localStorage.getItem("user")) | |
| 40 | + | |
| 41 | + function isImageUrl(url) { | |
| 42 | + const imageExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.bmp']; | |
| 43 | + const urlExtension = url.substring(url.lastIndexOf('.')).toLowerCase(); | |
| 44 | + return imageExtensions.includes(urlExtension); | |
| 45 | + } | |
| 46 | + return ( | |
| 47 | + <List width={'100%'} > | |
| 48 | + <ListItem alignItems={'center'} display={'flex'} > | |
| 49 | + <Text alignItems={'center'} justifyContent={'space-between'} display={'flex'} width={'100%'} onClick={isFolder ? handleToggle : undefined} _hover={{ cursor: 'pointer', textDecoration: 'none' }}> | |
| 50 | + <Flex width={'70%'} alignItems={'center'}> | |
| 51 | + <ListIcon as={isFile ? FiFile : FcOpenedFolder} /> | |
| 52 | + {isFolder && (isOpen ? <FiChevronDown /> : <FiChevronRight />)} | |
| 53 | + {name} | |
| 54 | + </Flex> | |
| 55 | + {item?.createByName ? <Text>({item?.createByName}) </Text> : null} | |
| 56 | + {!isFolder && | |
| 57 | + <Flex justifyContent={'right'} width={'100%'} > | |
| 58 | + <Menu isLazy > | |
| 59 | + <MenuButton><CiMenuKebab /></MenuButton> | |
| 60 | + <MenuList position={'absolute'} right={-5} pl={'0.5em'} minW={'fit-content'} > | |
| 61 | + {!from && data?.linkContact ? | |
| 62 | + <MenuItem pr={2} w={'180px'} onClick={() => navigate(`/contactView/${data?.linkContact}`)} icon={<IoIosContact fontSize={15} />}>Linked Contact</MenuItem> | |
| 63 | + : !from && data?.linkLead && <MenuItem pr={2} w={'180px'} onClick={() => navigate(`/leadView/${data?.linkLead}`)} icon={<MdLeaderboard fontSize={15} />}>Linked Lead</MenuItem> | |
| 64 | + } | |
| 65 | + {!from && <MenuItem pr={10} alignItems={'start'} color={'blue'} onClick={() => handleLinkClick(data?._id)} icon={<LinkIcon fontSize={15} />}>Link</MenuItem>} | |
| 66 | + {isImageUrl(data?.img) && <MenuItem alignItems={'start'} pr={10} color={'green'} onClick={() => window.open(data?.img)} icon={<ViewIcon fontSize={15} />}>View</MenuItem>} | |
| 67 | + <MenuItem pr={10} alignItems={'start'} onClick={() => handleClick(data?._id)} icon={<DownloadIcon fontSize={15} />}>Download</MenuItem> | |
| 68 | + {!from && <MenuItem alignItems={'start'} pr={10} color={'red'} onClick={() => deletedata(data?._id)} icon={<DeleteIcon fontSize={15} />}>Delete</MenuItem>} | |
| 69 | + </MenuList> | |
| 70 | + </Menu> | |
| 71 | + </Flex> | |
| 72 | + } | |
| 73 | + <Delete isOpen={deleteModel} onClose={setDelete} method='one' deleteFile={deleteFile} id={id} /> | |
| 74 | + <LinkModel isOpen={linkModel} setLinkDocument={setLinkDocument} onClose={setLinkModel} id={id} /> | |
| 75 | + </Text> | |
| 76 | + </ListItem> | |
| 77 | + { | |
| 78 | + isFolder && ( | |
| 79 | + <Collapse in={isOpen} animateOpacity> | |
| 80 | + <List styleType="disc" ml={4}> | |
| 81 | + {children} | |
| 82 | + </List> | |
| 83 | + </Collapse> | |
| 84 | + ) | |
| 85 | + } | |
| 86 | + </List> | |
| 87 | + ); | |
| 88 | +}; | |
| 89 | + | |
| 90 | + | |
| 91 | +export default FolderTreeView |
+44-0Client/src/components/PDF/index.js
| @@ -0,0 +1,44 @@ | ||
| 1 | +import React from 'react'; | |
| 2 | +import { PDFDownloadLink, Document, Page, Text, View, StyleSheet } from '@react-pdf/renderer'; | |
| 3 | + | |
| 4 | +// Create styles | |
| 5 | +const styles = StyleSheet.create({ | |
| 6 | + page: { | |
| 7 | + flexDirection: 'column', | |
| 8 | + backgroundColor: '#E4E4E4', | |
| 9 | + padding: 20, | |
| 10 | + }, | |
| 11 | + section: { | |
| 12 | + margin: 10, | |
| 13 | + padding: 10, | |
| 14 | + flexGrow: 1, | |
| 15 | + }, | |
| 16 | + header: { | |
| 17 | + fontSize: 20, | |
| 18 | + marginBottom: 20, | |
| 19 | + }, | |
| 20 | + text: { | |
| 21 | + fontSize: 12, | |
| 22 | + marginBottom: 10, | |
| 23 | + }, | |
| 24 | +}); | |
| 25 | + | |
| 26 | +// Create Document Component | |
| 27 | +const MyDocument = () => ( | |
| 28 | + <Document> | |
| 29 | + <Page size="A4" style={styles.page}> | |
| 30 | + <View style={styles.section}> | |
| 31 | + <Text style={styles.header}>Hello World</Text> | |
| 32 | + <Text style={styles.text}> | |
| 33 | + This is an example of a simple PDF document created using @react-pdf/renderer. | |
| 34 | + </Text> | |
| 35 | + <Text style={styles.text}> | |
| 36 | + You can add more text, images, and other elements to your PDF document. | |
| 37 | + </Text> | |
| 38 | + </View> | |
| 39 | + </Page> | |
| 40 | + </Document> | |
| 41 | +); | |
| 42 | + | |
| 43 | + | |
| 44 | +export default MyDocument; |
+94-0Client/src/components/calender/viewCalender.js
| @@ -0,0 +1,94 @@ | ||
| 1 | +import { CloseIcon, DeleteIcon, EditIcon, ViewIcon } from '@chakra-ui/icons' | |
| 2 | +import { DrawerFooter, Flex, Grid, GridItem, IconButton, Modal, ModalBody, ModalContent, ModalHeader, ModalOverlay, Text } from '@chakra-ui/react' | |
| 3 | +import Spinner from "components/spinner/Spinner" | |
| 4 | +import moment from 'moment' | |
| 5 | +import { useEffect, useState } from 'react' | |
| 6 | +import { BiLink } from 'react-icons/bi' | |
| 7 | +import { Link } from 'react-router-dom' | |
| 8 | +import { getApi } from 'services/api' | |
| 9 | +import { useNavigate } from 'react-router-dom'; | |
| 10 | + | |
| 11 | +const ViewCalender = (props) => { | |
| 12 | + const { onClose, isOpen, info, fetchData, setAction, action, access, contactAccess, leadAccess } = props | |
| 13 | + const [data, setData] = useState() | |
| 14 | + const [edit, setEdit] = useState(false); | |
| 15 | + const [deleteModel, setDelete] = useState(false); | |
| 16 | + const user = JSON.parse(localStorage.getItem("user")) | |
| 17 | + const [isLoding, setIsLoding] = useState(false) | |
| 18 | + const navigate = useNavigate() | |
| 19 | + | |
| 20 | + const fetchViewData = async () => { | |
| 21 | + if (info?.event?.groupId === "task") { | |
| 22 | + setIsLoding(true) | |
| 23 | + let result = await getApi('api/task/view/', info?.event ? info?.event?.id : info); | |
| 24 | + setData(result?.data); | |
| 25 | + setIsLoding(false) | |
| 26 | + } else if (info?.event?.groupId === "call") { | |
| 27 | + setIsLoding(true) | |
| 28 | + let result = await getApi('api/call/view/', info?.event ? info?.event?.id : info); | |
| 29 | + setData(result?.data); | |
| 30 | + setIsLoding(false) | |
| 31 | + | |
| 32 | + } else if (info?.event?.groupId === "meeting") { | |
| 33 | + setIsLoding(true) | |
| 34 | + let result = await getApi('api/meeting/view/', info?.event ? info?.event?.id : info); | |
| 35 | + setData(result?.data); | |
| 36 | + setIsLoding(false) | |
| 37 | + | |
| 38 | + } else if (info?.event?.groupId === "email") { | |
| 39 | + setIsLoding(true) | |
| 40 | + let result = await getApi('api/email/view/', info?.event ? info?.event?.id : info); | |
| 41 | + setData(result?.data); | |
| 42 | + setIsLoding(false) | |
| 43 | + } | |
| 44 | + } | |
| 45 | + | |
| 46 | + useEffect(() => { | |
| 47 | + fetchViewData() | |
| 48 | + }, [action, info]) | |
| 49 | + | |
| 50 | + const handleViewOpen = () => { | |
| 51 | + if (info?.event) { | |
| 52 | + navigate(`/view/${info?.event?.id}`) | |
| 53 | + } | |
| 54 | + else { | |
| 55 | + navigate(`/view/${info}`) | |
| 56 | + } | |
| 57 | + } | |
| 58 | + return ( | |
| 59 | + <div> <Modal isOpen={isOpen} size={'md'} isCentered> | |
| 60 | + <ModalOverlay /> | |
| 61 | + <ModalContent> | |
| 62 | + <ModalHeader justifyContent='space-between' display='flex' > | |
| 63 | + <span style={{ textTransform: "capitalize" }}>{info?.event?.groupId}</span> | |
| 64 | + <IconButton onClick={() => onClose(false)} icon={<CloseIcon />} /> | |
| 65 | + </ModalHeader> | |
| 66 | + {isLoding ? | |
| 67 | + <Flex justifyContent={'center'} alignItems={'center'} mb={30} width="100%" > | |
| 68 | + <Spinner /> | |
| 69 | + </Flex> : <> | |
| 70 | + | |
| 71 | + <ModalBody> | |
| 72 | + <Grid templateColumns="repeat(12, 1fr)" gap={3} > | |
| 73 | + {data && Object.keys(data).length > 0 && Object.keys(data)?.map((item) => | |
| 74 | + <GridItem colSpan={{ base: 12, md: 6 }} > | |
| 75 | + <Text fontSize="sm" fontWeight="bold" color={'blackAlpha.900'}> {item}</Text> | |
| 76 | + <Text>{data[item] ? data[item] : ' - '}</Text> | |
| 77 | + </GridItem> | |
| 78 | + )} | |
| 79 | + </Grid> | |
| 80 | + | |
| 81 | + </ModalBody> | |
| 82 | + <DrawerFooter> | |
| 83 | + {access?.view || user?.role === "superAdmin" && <IconButton variant='outline' colorScheme={'green'} onClick={() => handleViewOpen()} borderRadius="10px" size="md" icon={<ViewIcon />} />} | |
| 84 | + {access?.update || user?.role === "superAdmin" && <IconButton variant='outline' onClick={() => setEdit(true)} ml={3} borderRadius="10px" size="md" icon={<EditIcon />} />} | |
| 85 | + {access?.delete || user?.role === "superAdmin" && <IconButton colorScheme='red' onClick={() => setDelete(true)} ml={3} borderRadius="10px" size="md" icon={<DeleteIcon />} />} | |
| 86 | + | |
| 87 | + </DrawerFooter> | |
| 88 | + </>} | |
| 89 | + </ModalContent> | |
| 90 | + </Modal></div> | |
| 91 | + ) | |
| 92 | +} | |
| 93 | + | |
| 94 | +export default ViewCalender |
+12-0Client/src/components/card/Card.js
| @@ -0,0 +1,12 @@ | ||
| 1 | +import { Box, useStyleConfig } from "@chakra-ui/react"; | |
| 2 | +function Card(props) { | |
| 3 | + const { variant, children, ...rest } = props; | |
| 4 | + const styles = useStyleConfig("Card", { variant }); | |
| 5 | + | |
| 6 | + return ( | |
| 7 | + <Box __css={styles} height={'100%'} {...rest}> | |
| 8 | + {children} | |
| 9 | + </Box> | |
| 10 | + ); | |
| 11 | +} | |
| 12 | +export default Card; |
+65-0Client/src/components/card/MiniStatistics.js
| @@ -0,0 +1,65 @@ | ||
| 1 | +// Chakra imports | |
| 2 | +// Chakra imports | |
| 3 | +import { | |
| 4 | + Flex, | |
| 5 | + Stat, | |
| 6 | + StatLabel, | |
| 7 | + StatNumber, | |
| 8 | + useColorModeValue, | |
| 9 | + Text, | |
| 10 | +} from "@chakra-ui/react"; | |
| 11 | +// Custom components | |
| 12 | +import Card from "components/card/Card.js"; | |
| 13 | +import CountUpComponent from "components/countUpComponent/countUpComponent"; | |
| 14 | +// Custom icons | |
| 15 | +import React from "react"; | |
| 16 | + | |
| 17 | +export default function Default(props) { | |
| 18 | + const { startContent, endContent, name, growth, value } = props; | |
| 19 | + const textColor = useColorModeValue("secondaryGray.900", "white"); | |
| 20 | + const textColorSecondary = "secondaryGray.600"; | |
| 21 | + | |
| 22 | + return ( | |
| 23 | + <Card cursor={'pointer'} py='15px' onClick={props.onClick} > | |
| 24 | + <Flex | |
| 25 | + my='auto' | |
| 26 | + h='100%' | |
| 27 | + align={{ base: "center", xl: "start" }} | |
| 28 | + justify={{ base: "center", xl: "center" }}> | |
| 29 | + {startContent} | |
| 30 | + | |
| 31 | + <Stat my='auto' ms={startContent ? "18px" : "0px"}> | |
| 32 | + <StatLabel | |
| 33 | + lineHeight='100%' | |
| 34 | + color={textColorSecondary} | |
| 35 | + fontSize={{ | |
| 36 | + base: props.fontsize ? props.fontsize : "sm", | |
| 37 | + }}> | |
| 38 | + {name} | |
| 39 | + </StatLabel> | |
| 40 | + <StatNumber | |
| 41 | + color={textColor} | |
| 42 | + fontSize={{ | |
| 43 | + base: "2xl", | |
| 44 | + }}> | |
| 45 | + <CountUpComponent targetNumber={value} /> | |
| 46 | + {/* {value} */} | |
| 47 | + </StatNumber> | |
| 48 | + {growth ? ( | |
| 49 | + <Flex align='center'> | |
| 50 | + <Text color='green.500' fontSize='xs' fontWeight='700' me='5px'> | |
| 51 | + {growth} | |
| 52 | + </Text> | |
| 53 | + <Text color='secondaryGray.600' fontSize='xs' fontWeight='400'> | |
| 54 | + since last month | |
| 55 | + </Text> | |
| 56 | + </Flex> | |
| 57 | + ) : null} | |
| 58 | + </Stat> | |
| 59 | + <Flex ms='auto' w='max-content'> | |
| 60 | + {endContent} | |
| 61 | + </Flex> | |
| 62 | + </Flex> | |
| 63 | + </Card> | |
| 64 | + ); | |
| 65 | +} |
+72-0Client/src/components/charts/LineChart.js
| @@ -0,0 +1,72 @@ | ||
| 1 | +import React, { useEffect, useState } from "react"; | |
| 2 | +import ReactApexChart from "react-apexcharts"; | |
| 3 | +import { HasAccess } from "../../redux/accessUtils"; | |
| 4 | + | |
| 5 | +const ApexChart = (props) => { | |
| 6 | + const { data } = props; | |
| 7 | + | |
| 8 | + let permissions = [] | |
| 9 | + let permissionsLength = [] | |
| 10 | + | |
| 11 | + data?.forEach(item => { | |
| 12 | + if (item.name) { | |
| 13 | + permissions.push(item.name); | |
| 14 | + permissionsLength.push(item.length); | |
| 15 | + } | |
| 16 | + }); | |
| 17 | + | |
| 18 | + const state = { | |
| 19 | + series: [ | |
| 20 | + { | |
| 21 | + name: 'Data', | |
| 22 | + data: permissionsLength?.map((item) => item) | |
| 23 | + } | |
| 24 | + ], | |
| 25 | + options: { | |
| 26 | + chart: { | |
| 27 | + height: 350, | |
| 28 | + type: 'bar', | |
| 29 | + }, | |
| 30 | + plotOptions: { | |
| 31 | + bar: { | |
| 32 | + borderRadius: 10, | |
| 33 | + columnWidth: '40%', | |
| 34 | + } | |
| 35 | + }, | |
| 36 | + stroke: { | |
| 37 | + width: 2 | |
| 38 | + }, | |
| 39 | + grid: { | |
| 40 | + row: { | |
| 41 | + colors: ['#fff', '#f2f2f2'] | |
| 42 | + } | |
| 43 | + }, | |
| 44 | + xaxis: { | |
| 45 | + categories: permissions?.map((item) => item), | |
| 46 | + tickPlacement: 'on' | |
| 47 | + }, | |
| 48 | + | |
| 49 | + fill: { | |
| 50 | + type: 'gradient', | |
| 51 | + gradient: { | |
| 52 | + shade: 'light', | |
| 53 | + type: "horizontal", | |
| 54 | + shadeIntensity: 0.25, | |
| 55 | + inverseColors: true, | |
| 56 | + opacityFrom: 0.85, | |
| 57 | + opacityTo: 0.85, | |
| 58 | + stops: [50, 0, 100] | |
| 59 | + }, | |
| 60 | + } | |
| 61 | + }, | |
| 62 | + }; | |
| 63 | + return ( | |
| 64 | + <div id="chart"> | |
| 65 | + <ReactApexChart options={state.options} series={state.series} type="bar" height={350} /> | |
| 66 | + </div> | |
| 67 | + ); | |
| 68 | +}; | |
| 69 | + | |
| 70 | +export default ApexChart; | |
| 71 | + | |
| 72 | + |
+88-0Client/src/components/charts/PieChart.js
| @@ -0,0 +1,88 @@ | ||
| 1 | +import React, { useState } from "react"; | |
| 2 | +import ReactApexChart from "react-apexcharts"; | |
| 3 | + | |
| 4 | +const ApexChart = (props) => { | |
| 5 | + const { leadData } = props; | |
| 6 | + | |
| 7 | + let activeLength = leadData && leadData.length > 0 ? leadData?.filter(lead => lead?.leadStatus === "active")?.length : 0; | |
| 8 | + let pendingLength = leadData && leadData.length > 0 ? leadData?.filter(lead => lead?.leadStatus === "pending")?.length : 0; | |
| 9 | + let soldLength = leadData && leadData.length > 0 ? leadData?.filter(lead => lead?.leadStatus === "sold")?.length : 0; | |
| 10 | + | |
| 11 | + const series = [activeLength, pendingLength, soldLength]; | |
| 12 | + const scaledSeries = series?.map(value => { | |
| 13 | + if (leadData?.length === 0) { | |
| 14 | + return NaN; | |
| 15 | + } else { | |
| 16 | + return value === 0 ? NaN : ((value * 100) / leadData?.length); | |
| 17 | + } | |
| 18 | +}); | |
| 19 | + | |
| 20 | + | |
| 21 | + const options = { | |
| 22 | + chart: { | |
| 23 | + type: 'radialBar', | |
| 24 | + width: 330, | |
| 25 | + }, | |
| 26 | + plotOptions: { | |
| 27 | + radialBar: { | |
| 28 | + size: undefined, | |
| 29 | + inverseOrder: true, | |
| 30 | + hollow: { | |
| 31 | + margin: 40, | |
| 32 | + size: '48%', | |
| 33 | + background: 'transparent', | |
| 34 | + }, | |
| 35 | + dataLabels: { | |
| 36 | + name: { | |
| 37 | + fontSize: '22px', | |
| 38 | + }, | |
| 39 | + value: { | |
| 40 | + fontSize: '16px', | |
| 41 | + }, | |
| 42 | + total: { | |
| 43 | + show: true, | |
| 44 | + label: 'Total', | |
| 45 | + color: '#1F7EEB', | |
| 46 | + formatter: function () { | |
| 47 | + return leadData?.length || 0; | |
| 48 | + } | |
| 49 | + }, | |
| 50 | + value: { | |
| 51 | + show: true, | |
| 52 | + formatter: function (val) { | |
| 53 | + return (val / 100 * leadData.length).toFixed(0); | |
| 54 | + } | |
| 55 | + } | |
| 56 | + }, | |
| 57 | + track: { | |
| 58 | + show: true, | |
| 59 | + }, | |
| 60 | + startAngle: -180, | |
| 61 | + endAngle: 180, | |
| 62 | + hover: { | |
| 63 | + size: undefined, | |
| 64 | + sizeOffset: 3, | |
| 65 | + colors: ["#ff5959", "#ECC94B", "#01B574"], // Add hover effect colors | |
| 66 | + } | |
| 67 | + }, | |
| 68 | + }, | |
| 69 | + stroke: { | |
| 70 | + lineCap: 'round' | |
| 71 | + }, | |
| 72 | + colors: ["#25BE87", "#ECC94B", "#ff5959"], | |
| 73 | + labels: ['Active', 'Pending', 'Sold'], | |
| 74 | + legend: { | |
| 75 | + show: true, | |
| 76 | + floating: true, | |
| 77 | + position: 'bottom', | |
| 78 | + }, | |
| 79 | + } | |
| 80 | + | |
| 81 | + return ( | |
| 82 | + <div> | |
| 83 | + <ReactApexChart key={leadData?.length} options={options} series={scaledSeries} type="radialBar" height={320} /> | |
| 84 | + </div> | |
| 85 | + ); | |
| 86 | +}; | |
| 87 | + | |
| 88 | +export default ApexChart; |
+532-0Client/src/components/checkTable/checktable.js
| @@ -0,0 +1,532 @@ | ||
| 1 | +import { useMemo, useState, useEffect } from 'react'; | |
| 2 | +import { Box, Flex, Table, Tbody, Td, Text, Th, Thead, Tr, Button, HStack, Tag, TagLabel, Menu, MenuButton, MenuDivider, MenuItem, MenuList, Grid, GridItem, Checkbox, Modal, ModalBody, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalOverlay, TagCloseButton } from '@chakra-ui/react'; | |
| 3 | +import { useColorModeValue } from '@chakra-ui/system'; | |
| 4 | +import { BsColumnsGap } from "react-icons/bs"; | |
| 5 | +import { FaSort, FaSortDown, FaSortUp } from 'react-icons/fa'; | |
| 6 | +import { SearchIcon, DeleteIcon, AddIcon } from "@chakra-ui/icons"; | |
| 7 | +import { useGlobalFilter, usePagination, useSortBy, useTable } from 'react-table'; | |
| 8 | +import * as XLSX from "xlsx"; | |
| 9 | +import Card from 'components/card/Card'; | |
| 10 | +import CountUpComponent from 'components/countUpComponent/countUpComponent'; | |
| 11 | +import Pagination from 'components/pagination/Pagination'; | |
| 12 | +import Spinner from 'components/spinner/Spinner'; | |
| 13 | +import CustomSearchInput from "../search/search"; | |
| 14 | +import AdvanceSearchUsingCustomFields from "../search/advanceSearch"; | |
| 15 | +import DataNotFound from "../notFoundData"; | |
| 16 | +import moment from 'moment'; | |
| 17 | +import { useSelector, useDispatch } from 'react-redux'; | |
| 18 | +import { getSearchData, setGetTagValues, setSearchValue } from '../../redux/slices/advanceSearchSlice' | |
| 19 | + | |
| 20 | +const CommonCheckTable = (props) => { | |
| 21 | + const { isLoding, title, columnData, size, dataColumn, setSearchedDataOut, state, allData, ManageGrid, deleteMany, tableCustomFields, access, selectedColumns, setSelectedColumns, onOpen, setDelete, selectedValues, setSelectedValues, setIsImport, checkBox, AdvanceSearch, searchDisplay, setSearchDisplay, BackButton, searchboxOutside, setGetTagValuesOutside, setSearchboxOutside, selectType, customSearch } = props; | |
| 22 | + const { dataLength } = props; | |
| 23 | + const { handleSearchType } = props; | |
| 24 | + const textColor = useColorModeValue("secondaryGray.900", "white"); | |
| 25 | + const borderColor = useColorModeValue("gray.200", "whiteAlpha.100"); | |
| 26 | + const [displaySearchData, setDisplaySearchData] = useState(false); | |
| 27 | + const [searchedData, setSearchedData] = useState([]); | |
| 28 | + const columns = useMemo(() => dataColumn, [dataColumn]); | |
| 29 | + const [tempSelectedColumns, setTempSelectedColumns] = useState(dataColumn); // State to track changes | |
| 30 | + | |
| 31 | + const searchedDataOut = useSelector((state) => state?.advanceSearchData?.searchResult) | |
| 32 | + const searchValue = useSelector((state) => state?.advanceSearchData?.searchValue) | |
| 33 | + const getTagValues = useSelector((state) => state?.advanceSearchData?.getTagValues) | |
| 34 | + const data = useMemo(() => (AdvanceSearch ? searchDisplay : displaySearchData) ? (AdvanceSearch ? searchedDataOut : searchedData) : allData, [(AdvanceSearch ? searchDisplay : displaySearchData) ? (AdvanceSearch ? searchedDataOut : searchedData) : allData]); | |
| 35 | + const [manageColumns, setManageColumns] = useState(false); | |
| 36 | + const [csvColumns, setCsvColumns] = useState([]); | |
| 37 | + const [searchbox, setSearchbox] = useState(''); | |
| 38 | + const [advaceSearch, setAdvaceSearch] = useState(false); | |
| 39 | + const [column, setColumn] = useState(''); | |
| 40 | + const [gopageValue, setGopageValue] = useState(); | |
| 41 | + | |
| 42 | + const dispatch = useDispatch(); | |
| 43 | + | |
| 44 | + const tableInstance = useTable( | |
| 45 | + { | |
| 46 | + columns, | |
| 47 | + data, | |
| 48 | + initialState: { pageIndex: 0 } | |
| 49 | + }, | |
| 50 | + useGlobalFilter, | |
| 51 | + useSortBy, | |
| 52 | + usePagination | |
| 53 | + ); | |
| 54 | + | |
| 55 | + const { | |
| 56 | + getTableProps, | |
| 57 | + getTableBodyProps, | |
| 58 | + headerGroups, | |
| 59 | + prepareRow, | |
| 60 | + page, | |
| 61 | + canPreviousPage, | |
| 62 | + canNextPage, | |
| 63 | + pageOptions, | |
| 64 | + pageCount, | |
| 65 | + gotoPage, | |
| 66 | + nextPage, | |
| 67 | + previousPage, | |
| 68 | + setPageSize, | |
| 69 | + state: { pageIndex, pageSize } | |
| 70 | + } = tableInstance; | |
| 71 | + | |
| 72 | + if (pageOptions && pageOptions?.length > 0 && pageOptions?.length < gopageValue) { | |
| 73 | + setGopageValue(pageOptions.length) | |
| 74 | + } | |
| 75 | + | |
| 76 | + const handleSearch = (results) => { | |
| 77 | + AdvanceSearch && dispatch(getSearchData({ searchData: results, type: handleSearchType })) | |
| 78 | + AdvanceSearch ? setSearchedDataOut(results) : setSearchedData(results); | |
| 79 | + }; | |
| 80 | + | |
| 81 | + const handleAdvanceSearch = (values) => { | |
| 82 | + dispatch(setSearchValue(values)) | |
| 83 | + const searchResult = AdvanceSearch ? dispatch(getSearchData({ values: values, allData: allData, type: title })) : allData?.filter(item => { | |
| 84 | + return tableCustomFields.every(field => { | |
| 85 | + const fieldValue = values[field.name]; | |
| 86 | + const itemValue = item[field.name]; | |
| 87 | + | |
| 88 | + if (field.type === 'select') { | |
| 89 | + return !fieldValue || itemValue === fieldValue; | |
| 90 | + } | |
| 91 | + else if (field.type === 'number') { | |
| 92 | + return ( | |
| 93 | + [null, undefined, ''].includes(fieldValue) || | |
| 94 | + (itemValue !== undefined && | |
| 95 | + itemValue.toString().includes(fieldValue.toString())) | |
| 96 | + ); | |
| 97 | + } | |
| 98 | + else if (field.type === 'date') { | |
| 99 | + const fromDate = values[`from${field.name}`]; | |
| 100 | + const toDate = values[`to${field.name}`]; | |
| 101 | + | |
| 102 | + if (!fromDate && !toDate) { | |
| 103 | + return true; // No date range specified | |
| 104 | + } | |
| 105 | + | |
| 106 | + const timeItemDate = new Date(itemValue); | |
| 107 | + const timeMomentDate = moment(timeItemDate).format('YYYY-MM-DD'); | |
| 108 | + | |
| 109 | + return ( | |
| 110 | + (!fromDate || (timeMomentDate >= fromDate)) && | |
| 111 | + (!toDate || (timeMomentDate <= toDate)) | |
| 112 | + ); | |
| 113 | + } | |
| 114 | + else { | |
| 115 | + // Default case for text, email | |
| 116 | + return !fieldValue || itemValue?.toLowerCase()?.includes(fieldValue?.toLowerCase()); | |
| 117 | + } | |
| 118 | + }); | |
| 119 | + }); | |
| 120 | + | |
| 121 | + const getValue = tableCustomFields.reduce((result, field) => { | |
| 122 | + if (field.type === 'date') { | |
| 123 | + const fromDate = values[`from${field.name}`]; | |
| 124 | + const toDate = values[`to${field.name}`]; | |
| 125 | + | |
| 126 | + if (fromDate || toDate) { | |
| 127 | + result.push({ | |
| 128 | + name: [`from${field.name}`, `to${field.name}`], | |
| 129 | + value: `From: ${fromDate} To: ${toDate}` | |
| 130 | + }) | |
| 131 | + } | |
| 132 | + } else if (values[field.name]) { | |
| 133 | + result.push({ | |
| 134 | + name: [field.name], | |
| 135 | + value: values[field.name] | |
| 136 | + }) | |
| 137 | + } | |
| 138 | + | |
| 139 | + return result; | |
| 140 | + }, []); | |
| 141 | + dispatch(setGetTagValues(getValue)) | |
| 142 | + setSearchedData(searchResult); | |
| 143 | + setDisplaySearchData(true); | |
| 144 | + setAdvaceSearch(false); | |
| 145 | + if (setSearchbox) { | |
| 146 | + setSearchbox(''); | |
| 147 | + } | |
| 148 | + } | |
| 149 | + | |
| 150 | + | |
| 151 | + const handleClear = () => { | |
| 152 | + setSearchDisplay && setSearchDisplay(false) | |
| 153 | + setDisplaySearchData && setDisplaySearchData(false) | |
| 154 | + if (searchboxOutside) { | |
| 155 | + setSearchboxOutside('') | |
| 156 | + } else { | |
| 157 | + setSearchbox(''); | |
| 158 | + } | |
| 159 | + dispatch(setGetTagValues([])) | |
| 160 | + if (props?.getTagValuesOutSide) { | |
| 161 | + setGetTagValuesOutside([]); | |
| 162 | + } | |
| 163 | + setGopageValue(1); | |
| 164 | + }; | |
| 165 | + | |
| 166 | + const handleClick = () => { | |
| 167 | + onOpen(); | |
| 168 | + }; | |
| 169 | + | |
| 170 | + const findStatus = () => { | |
| 171 | + const searchResult = allData?.filter( | |
| 172 | + (item) => | |
| 173 | + (!state || (item?.status && item?.status?.toLowerCase().includes(state?.toLowerCase()))) | |
| 174 | + ) | |
| 175 | + let getValue = [state || undefined].filter(value => value); | |
| 176 | + | |
| 177 | + dispatch(setGetTagValues(getValue)) | |
| 178 | + AdvanceSearch ? setSearchedDataOut && setSearchedDataOut(searchResult) : setSearchedData && setSearchedData(searchResult); | |
| 179 | + AdvanceSearch ? setSearchDisplay && setSearchDisplay(true) : setDisplaySearchData && setDisplaySearchData(searchResult); | |
| 180 | + setDisplaySearchData(true) | |
| 181 | + setAdvaceSearch(false) | |
| 182 | + } | |
| 183 | + | |
| 184 | + useEffect(() => { | |
| 185 | + state && findStatus() | |
| 186 | + }, [state, allData]); | |
| 187 | + | |
| 188 | + let isColumnSelected; | |
| 189 | + const toggleColumnVisibility = (columnKey) => { | |
| 190 | + setColumn(columnKey); | |
| 191 | + isColumnSelected = tempSelectedColumns?.some((column) => column?.accessor === columnKey); | |
| 192 | + if (isColumnSelected) { | |
| 193 | + const updatedColumns = tempSelectedColumns?.filter((column) => column?.accessor !== columnKey); | |
| 194 | + setTempSelectedColumns(updatedColumns); | |
| 195 | + } else { | |
| 196 | + const columnToAdd = columnData?.find((column) => column?.accessor === columnKey); | |
| 197 | + setTempSelectedColumns([...tempSelectedColumns, columnToAdd]); | |
| 198 | + } | |
| 199 | + }; | |
| 200 | + | |
| 201 | + const handleCheckboxChange = (event, value) => { | |
| 202 | + if (selectType === "single") { | |
| 203 | + if (event.target.checked) { | |
| 204 | + setSelectedValues && setSelectedValues(value); | |
| 205 | + } else { | |
| 206 | + setSelectedValues(); | |
| 207 | + } | |
| 208 | + } else if (event.target.checked) { | |
| 209 | + setSelectedValues && setSelectedValues((prevSelectedValues) => [...prevSelectedValues, value]); | |
| 210 | + } else { | |
| 211 | + setSelectedValues && setSelectedValues((prevSelectedValues) => | |
| 212 | + prevSelectedValues.filter((selectedValue) => selectedValue !== value) | |
| 213 | + ); | |
| 214 | + } | |
| 215 | + | |
| 216 | + }; | |
| 217 | + | |
| 218 | + const handleColumnClear = () => { | |
| 219 | + isColumnSelected = selectedColumns?.some((selectedColumn) => selectedColumn?.accessor === column?.accessor) | |
| 220 | + setTempSelectedColumns(columnData); | |
| 221 | + setManageColumns(!manageColumns ? !manageColumns : false) | |
| 222 | + }; | |
| 223 | + | |
| 224 | + | |
| 225 | + const handleExportLeads = (extension) => { | |
| 226 | + selectedValues && selectedValues?.length > 0 | |
| 227 | + ? downloadCsvOrExcel(extension, selectedValues) | |
| 228 | + : downloadCsvOrExcel(extension); | |
| 229 | + }; | |
| 230 | + | |
| 231 | + const downloadCsvOrExcel = async (extension, selectedIds) => { | |
| 232 | + try { | |
| 233 | + if (selectedIds && selectedIds?.length > 0) { | |
| 234 | + const selectedRecordsWithSpecificFileds = allData?.filter((rec) => selectedIds.includes(rec._id))?.map((rec) => { | |
| 235 | + const selectedFieldsData = {}; | |
| 236 | + csvColumns?.forEach((property) => { | |
| 237 | + selectedFieldsData[property.accessor] = rec[property.accessor]; | |
| 238 | + }); | |
| 239 | + return selectedFieldsData; | |
| 240 | + }); | |
| 241 | + convertJsonToCsvOrExcel(selectedRecordsWithSpecificFileds, csvColumns, title || 'data', extension); | |
| 242 | + } else { | |
| 243 | + const AllRecordsWithSpecificFileds = allData?.map((rec) => { | |
| 244 | + const selectedFieldsData = {}; | |
| 245 | + csvColumns?.forEach((property) => { | |
| 246 | + selectedFieldsData[property?.accessor] = rec[property?.accessor]; | |
| 247 | + }); | |
| 248 | + return selectedFieldsData; | |
| 249 | + }); | |
| 250 | + convertJsonToCsvOrExcel(AllRecordsWithSpecificFileds, csvColumns, title || 'data', extension); | |
| 251 | + } | |
| 252 | + } catch (e) { | |
| 253 | + console.error(e); | |
| 254 | + } | |
| 255 | + }; | |
| 256 | + | |
| 257 | + const convertJsonToCsvOrExcel = (jsonArray, csvColumns, fileName, extension) => { | |
| 258 | + const csvHeader = csvColumns?.map((col) => col?.Header); | |
| 259 | + | |
| 260 | + const csvContent = [ | |
| 261 | + csvHeader, | |
| 262 | + ...jsonArray?.map((row) => csvColumns?.map((col) => row[col?.accessor])) | |
| 263 | + ]; | |
| 264 | + | |
| 265 | + const ws = XLSX.utils.aoa_to_sheet(csvContent); | |
| 266 | + const wb = XLSX.utils.book_new(); | |
| 267 | + XLSX.utils.book_append_sheet(wb, ws, 'Sheet 1'); | |
| 268 | + XLSX.writeFile(wb, `${fileName}.${extension}`); // .csv, .xlsx | |
| 269 | + setSelectedValues([]) | |
| 270 | + }; | |
| 271 | + | |
| 272 | + const handleRemove = (name) => { | |
| 273 | + const filter = (getTagValues || []).filter((item) => { | |
| 274 | + if (Array.isArray(name?.name)) { | |
| 275 | + return name.name?.toString() !== item.name?.toString(); | |
| 276 | + } | |
| 277 | + }); | |
| 278 | + | |
| 279 | + let updatedSearchValue = { ...searchValue }; | |
| 280 | + for (let key in updatedSearchValue) { | |
| 281 | + if (updatedSearchValue.hasOwnProperty(key)) { | |
| 282 | + if (name.name.includes(key)) { | |
| 283 | + delete updatedSearchValue[key]; | |
| 284 | + } | |
| 285 | + if (updatedSearchValue[key] === "") { | |
| 286 | + delete updatedSearchValue[key]; | |
| 287 | + } | |
| 288 | + } | |
| 289 | + } | |
| 290 | + | |
| 291 | + handleAdvanceSearch(updatedSearchValue) | |
| 292 | + | |
| 293 | + dispatch(setGetTagValues(filter)) | |
| 294 | + if (filter?.length === 0) { | |
| 295 | + handleClear(); | |
| 296 | + } | |
| 297 | + } | |
| 298 | + | |
| 299 | + useEffect(() => { | |
| 300 | + AdvanceSearch ? setSearchedDataOut && setSearchedDataOut(data) : setSearchedData && setSearchedData(data); | |
| 301 | + }, []); | |
| 302 | + | |
| 303 | + useEffect(() => { | |
| 304 | + setTempSelectedColumns(dataColumn); | |
| 305 | + }, [dataColumn]); | |
| 306 | + | |
| 307 | + useEffect(() => { | |
| 308 | + if (selectedColumns) { | |
| 309 | + let tempCsvColumns = selectedColumns?.filter((col) => col?.Header !== '#' && col?.Header !== 'Action')?.map((field) => ({ Header: field?.Header, accessor: field?.accessor })); | |
| 310 | + setCsvColumns([...tempCsvColumns]) | |
| 311 | + } | |
| 312 | + }, [selectedColumns]); | |
| 313 | + return ( | |
| 314 | + <> | |
| 315 | + <Card | |
| 316 | + direction="column" | |
| 317 | + w="100%" | |
| 318 | + overflowX={{ sm: "scroll", lg: "hidden" }} | |
| 319 | + > | |
| 320 | + <Grid templateColumns="repeat(12, 1fr)" gap={2}> | |
| 321 | + <GridItem colSpan={{ base: 12, md: 8 }} display={"flex"} alignItems={"center"}> | |
| 322 | + <Flex alignItems={"center"} flexWrap={"wrap"}> | |
| 323 | + { | |
| 324 | + title && | |
| 325 | + <Text | |
| 326 | + color={'secondaryGray.900'} | |
| 327 | + fontSize="22px" | |
| 328 | + fontWeight="700" | |
| 329 | + lineHeight="100%" | |
| 330 | + textTransform={'capitalize'} | |
| 331 | + > | |
| 332 | + {title} (<CountUpComponent key={data?.length} targetNumber={dataLength || data?.length} />) | |
| 333 | + </Text> | |
| 334 | + } | |
| 335 | + {customSearch !== false && <CustomSearchInput setSearchbox={setSearchboxOutside ? setSearchboxOutside : setSearchbox} setDisplaySearchData={setSearchboxOutside ? props.setSearchDisplay : setDisplaySearchData} searchbox={searchboxOutside ? searchboxOutside : searchbox} allData={allData} dataColumn={columns} onSearch={handleSearch} setGetTagValues={props.setGetTagValuesOutside ? props.setGetTagValuesOutside : setGetTagValues} setGopageValue={setGopageValue} />} | |
| 336 | + { | |
| 337 | + AdvanceSearch ? AdvanceSearch : AdvanceSearch !== false && | |
| 338 | + <Button variant="outline" colorScheme='brand' leftIcon={<SearchIcon />} mt={{ sm: "5px", md: "0" }} size="sm" onClick={() => setAdvaceSearch(true)}>Advance Search</Button> | |
| 339 | + } | |
| 340 | + {(searchDisplay || displaySearchData) ? <Button variant="outline" colorScheme='red' size="sm" ms={2} onClick={() => handleClear()}>Clear</Button> : ""} | |
| 341 | + {(selectedValues?.length > 0 && access?.delete && !deleteMany) && <DeleteIcon cursor={"pointer"} onClick={() => setDelete(true)} color={'red'} ms={2} />} | |
| 342 | + </Flex> | |
| 343 | + </GridItem> | |
| 344 | + {/* Advance filter */} | |
| 345 | + <AdvanceSearchUsingCustomFields | |
| 346 | + setAdvaceSearch={setAdvaceSearch} | |
| 347 | + setGetTagValues={setGetTagValues} | |
| 348 | + isLoding={isLoding} | |
| 349 | + allData={allData} | |
| 350 | + setDisplaySearchData={setDisplaySearchData} | |
| 351 | + setSearchedData={setSearchedData} | |
| 352 | + advaceSearch={advaceSearch} | |
| 353 | + tableCustomFields={tableCustomFields} | |
| 354 | + setSearchbox={setSearchbox} | |
| 355 | + handleAdvanceSearch={handleAdvanceSearch} | |
| 356 | + /> | |
| 357 | + <GridItem colSpan={{ base: 12, md: 4 }} display={"flex"} justifyContent={"end"} alignItems={"center"} textAlign={"right"}> | |
| 358 | + {ManageGrid !== false && | |
| 359 | + <Menu isLazy > | |
| 360 | + <MenuButton p={4}> | |
| 361 | + <BsColumnsGap /> | |
| 362 | + </MenuButton> | |
| 363 | + <MenuList minW={'fit-content'} transform={"translate(1670px, 60px)"} zIndex={2} > | |
| 364 | + <MenuItem onClick={() => setManageColumns(true)} width={"165px"}> Manage Columns | |
| 365 | + </MenuItem> | |
| 366 | + {typeof setIsImport === "function" && <MenuItem width={"165px"} onClick={() => setIsImport(true)}> Import {title} | |
| 367 | + </MenuItem>} | |
| 368 | + { | |
| 369 | + allData && allData?.length > 0 && | |
| 370 | + <> | |
| 371 | + <MenuDivider /> | |
| 372 | + <MenuItem width={"165px"} onClick={() => handleExportLeads('csv')}>{selectedValues && selectedValues?.length > 0 ? 'Export Selected Data as CSV' : 'Export as CSV'}</MenuItem> | |
| 373 | + <MenuItem width={"165px"} onClick={() => handleExportLeads('xlsx')}>{selectedValues && selectedValues?.length > 0 ? 'Export Selected Data as Excel' : 'Export as Excel'}</MenuItem> | |
| 374 | + </> | |
| 375 | + } | |
| 376 | + </MenuList> | |
| 377 | + </Menu>} | |
| 378 | + {(access?.create || access === true) && <Button onClick={() => handleClick()} size="sm" variant="brand" leftIcon={<AddIcon />}>Add New</Button>} | |
| 379 | + {BackButton && BackButton} | |
| 380 | + </GridItem> | |
| 381 | + <HStack spacing={4} mb={2}> | |
| 382 | + {(getTagValues || []).map((item) => ( | |
| 383 | + <Tag | |
| 384 | + size={"md"} | |
| 385 | + p={2} | |
| 386 | + key={item.value} | |
| 387 | + borderRadius='full' | |
| 388 | + variant='solid' | |
| 389 | + colorScheme="gray" | |
| 390 | + > | |
| 391 | + <TagLabel>{item.value}</TagLabel> | |
| 392 | + <TagCloseButton onClick={() => handleRemove(item)} /> | |
| 393 | + </Tag> | |
| 394 | + ))} | |
| 395 | + </HStack> | |
| 396 | + </Grid> | |
| 397 | + <Box overflowY={"auto"} className={size ? `small-table-fix-container` : `table-fix-container`}> | |
| 398 | + <Table {...getTableProps()} variant="simple" color="gray.500" mb="24px"> | |
| 399 | + <Thead zIndex={1}> | |
| 400 | + {headerGroups?.map((headerGroup, index) => ( | |
| 401 | + <Tr {...headerGroup.getHeaderGroupProps()} key={index}> | |
| 402 | + {headerGroup.headers?.map((column, index) => ( | |
| 403 | + <Th | |
| 404 | + {...column.getHeaderProps(column.isSortable !== false && column.getSortByToggleProps())} | |
| 405 | + pe="10px" | |
| 406 | + key={index} | |
| 407 | + borderColor={borderColor} | |
| 408 | + > | |
| 409 | + <Flex | |
| 410 | + align="center" | |
| 411 | + justifyContent={column.center ? "center" : "start"} | |
| 412 | + fontSize={{ sm: "14px", lg: "16px" }} | |
| 413 | + color="secondaryGray.900" | |
| 414 | + > | |
| 415 | + <span style={{ textTransform: "capitalize", marginRight: "8px" }}> | |
| 416 | + {column.render("Header")} | |
| 417 | + </span> | |
| 418 | + {column.isSortable !== false && ( | |
| 419 | + <span> | |
| 420 | + {column.isSorted ? (column.isSortedDesc ? <FaSortDown /> : <FaSortUp />) : <FaSort />} | |
| 421 | + </span> | |
| 422 | + )} | |
| 423 | + </Flex> | |
| 424 | + </Th> | |
| 425 | + ))} | |
| 426 | + </Tr> | |
| 427 | + ))} | |
| 428 | + </Thead> | |
| 429 | + <Tbody {...getTableBodyProps()}> | |
| 430 | + {isLoding ? | |
| 431 | + <Tr> | |
| 432 | + <Td colSpan={columns?.length}> | |
| 433 | + <Flex justifyContent={'center'} alignItems={'center'} width="100%" color={textColor} fontSize="sm" fontWeight="700"> | |
| 434 | + <Spinner /> | |
| 435 | + </Flex> | |
| 436 | + </Td> | |
| 437 | + </Tr> | |
| 438 | + : data?.length === 0 ? ( | |
| 439 | + <Tr> | |
| 440 | + <Td colSpan={columns.length}> | |
| 441 | + <Text textAlign={'center'} width="100%" color={textColor} fontSize="sm" fontWeight="700"> | |
| 442 | + <DataNotFound /> | |
| 443 | + </Text> | |
| 444 | + </Td> | |
| 445 | + </Tr> | |
| 446 | + ) : page?.map((row, i) => { | |
| 447 | + prepareRow(row); | |
| 448 | + return ( | |
| 449 | + <Tr {...row?.getRowProps()}> | |
| 450 | + {row?.cells?.map((cell, index) => { | |
| 451 | + let data = ""; | |
| 452 | + columnData?.forEach((item) => { | |
| 453 | + if (cell?.column.Header === item.Header) { | |
| 454 | + if (item.cell && typeof item.cell === 'function') { | |
| 455 | + data = ( | |
| 456 | + <Flex Flex align="center" justifyContent={item?.Header === 'Action' && 'center'}> | |
| 457 | + <Text color={textColor} fontSize="sm" fontWeight="700" > | |
| 458 | + {item.cell(cell) === ' ' ? '-' : item.cell(cell)} | |
| 459 | + </Text> | |
| 460 | + </Flex> | |
| 461 | + ); | |
| 462 | + } | |
| 463 | + else { | |
| 464 | + data = ( | |
| 465 | + <Flex align="center" > | |
| 466 | + {(item.Header === "#" && (checkBox || checkBox === undefined)) && <Checkbox colorScheme="brandScheme" value={selectedValues} isChecked={selectedValues?.includes(cell?.value)} onChange={(event) => handleCheckboxChange(event, cell?.value)} me="10px" />} | |
| 467 | + | |
| 468 | + <Text color={textColor} fontSize="sm" fontWeight="700"> | |
| 469 | + {item.Header === "#" ? cell?.row?.index + 1 : cell?.value ? cell?.value : '-'} | |
| 470 | + </Text> | |
| 471 | + </Flex> | |
| 472 | + ); | |
| 473 | + } | |
| 474 | + } | |
| 475 | + }); | |
| 476 | + return ( | |
| 477 | + <Td | |
| 478 | + {...cell?.getCellProps()} | |
| 479 | + key={index} | |
| 480 | + fontSize={{ sm: "14px" }} | |
| 481 | + minW={{ sm: "150px", md: "200px", lg: "auto" }} | |
| 482 | + borderColor="transparent" | |
| 483 | + > | |
| 484 | + {data} | |
| 485 | + </Td> | |
| 486 | + ); | |
| 487 | + })} | |
| 488 | + </Tr> | |
| 489 | + ); | |
| 490 | + })} | |
| 491 | + </Tbody> | |
| 492 | + </Table> | |
| 493 | + </Box> | |
| 494 | + {data?.length > 5 && <Pagination gotoPage={gotoPage} gopageValue={gopageValue} setGopageValue={setGopageValue} pageCount={pageCount} canPreviousPage={canPreviousPage} previousPage={previousPage} canNextPage={canNextPage} pageOptions={pageOptions} setPageSize={setPageSize} nextPage={nextPage} | |
| 495 | + pageSize={pageSize} pageIndex={pageIndex} dataLength={15} />} | |
| 496 | + | |
| 497 | + {/* Manage Columns */} | |
| 498 | + <Modal onClose={() => { setManageColumns(false); }} isOpen={manageColumns} isCentered> | |
| 499 | + <ModalOverlay /> | |
| 500 | + <ModalContent> | |
| 501 | + <ModalHeader>Manage Columns</ModalHeader> | |
| 502 | + <ModalCloseButton onClick={() => { setManageColumns(false); }} /> | |
| 503 | + <ModalBody> | |
| 504 | + <div> | |
| 505 | + {columnData?.map((column) => ( | |
| 506 | + <Text display={"flex"} key={column?.accessor} py={2}> | |
| 507 | + <Checkbox | |
| 508 | + defaultChecked={selectedColumns?.some((selectedColumn) => selectedColumn?.accessor === column?.accessor)} | |
| 509 | + onChange={() => toggleColumnVisibility(column?.accessor)} | |
| 510 | + pe={2} | |
| 511 | + /> | |
| 512 | + {column?.Header} | |
| 513 | + </Text> | |
| 514 | + ))} | |
| 515 | + </div> | |
| 516 | + </ModalBody> | |
| 517 | + <ModalFooter> | |
| 518 | + <Button colorScheme='brand' mr={2} onClick={() => { | |
| 519 | + setSelectedColumns([...tempSelectedColumns]); | |
| 520 | + setManageColumns(false); | |
| 521 | + }} disabled={isLoding ? true : false} size='sm'>{isLoding ? <Spinner /> : 'Save'}</Button> | |
| 522 | + <Button variant='outline' colorScheme="red" size='sm' onClick={() => handleColumnClear()}>Close</Button> | |
| 523 | + </ModalFooter> | |
| 524 | + </ModalContent> | |
| 525 | + </Modal> | |
| 526 | + | |
| 527 | + </Card> | |
| 528 | + </> | |
| 529 | + ); | |
| 530 | +} | |
| 531 | + | |
| 532 | +export default CommonCheckTable |
+38-0Client/src/components/commonDeleteModel/index.js
| @@ -0,0 +1,38 @@ | ||
| 1 | +import { Button, Modal, ModalBody, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalOverlay } from '@chakra-ui/react'; | |
| 2 | +import Spinner from 'components/spinner/Spinner'; | |
| 3 | +import { useState } from 'react'; | |
| 4 | +import { useNavigate, useParams } from 'react-router-dom'; | |
| 5 | + | |
| 6 | +const CommonDeleteModel = (props) => { | |
| 7 | + const { isOpen, onClose, type, handleDeleteData, ids, selectedValues } = props | |
| 8 | + const [isLoding, setIsLoding] = useState(false) | |
| 9 | + | |
| 10 | + const handleDelete = () => { | |
| 11 | + handleDeleteData(ids, selectedValues) | |
| 12 | + } | |
| 13 | + | |
| 14 | + const handleClose = () => { | |
| 15 | + onClose() | |
| 16 | + } | |
| 17 | + | |
| 18 | + return ( | |
| 19 | + <div> | |
| 20 | + <Modal onClose={onClose} isOpen={isOpen} isCentered> | |
| 21 | + <ModalOverlay /> | |
| 22 | + <ModalContent> | |
| 23 | + <ModalHeader>Delete {`${type}`}</ModalHeader> | |
| 24 | + <ModalCloseButton /> | |
| 25 | + <ModalBody> | |
| 26 | + Are You Sure To Delete selected {`${type}`} ? | |
| 27 | + </ModalBody> | |
| 28 | + <ModalFooter> | |
| 29 | + <Button colorScheme="red" size="sm" mr={2} onClick={handleDelete} disabled={isLoding ? true : false} >{isLoding ? <Spinner /> : 'Yes'}</Button> | |
| 30 | + <Button variant="outline" size="sm" onClick={handleClose}>No</Button> | |
| 31 | + </ModalFooter> | |
| 32 | + </ModalContent> | |
| 33 | + </Modal> | |
| 34 | + </div> | |
| 35 | + ) | |
| 36 | +} | |
| 37 | + | |
| 38 | +export default CommonDeleteModel |
+71-0Client/src/components/commonFileUpload/index.js
| @@ -0,0 +1,71 @@ | ||
| 1 | +// Chakra imports | |
| 2 | +import { | |
| 3 | + Box, Flex, | |
| 4 | + Grid, | |
| 5 | + GridItem, | |
| 6 | + Icon, | |
| 7 | + Text, | |
| 8 | + useColorModeValue | |
| 9 | +} from "@chakra-ui/react"; | |
| 10 | +// Custom components | |
| 11 | +// Assets | |
| 12 | +import Dropzone from "components/Dropzone"; | |
| 13 | +import { MdUpload } from "react-icons/md"; | |
| 14 | + | |
| 15 | +export default function CommonFileUpload(props) { | |
| 16 | + // const { used, total, count, ...rest } = props; | |
| 17 | + const { count, text, onFileSelect } = props; | |
| 18 | + // Chakra Color Mode | |
| 19 | + const textColorPrimary = useColorModeValue("secondaryGray.900", "white"); | |
| 20 | + const brandColor = useColorModeValue("brand.500", "white"); | |
| 21 | + const textColorSecondary = "gray.400"; | |
| 22 | + return ( | |
| 23 | + <Grid templateColumns="repeat(12, 1fr)" gap={2}> | |
| 24 | + <GridItem colSpan={{ base: 12 }} > | |
| 25 | + <Dropzone | |
| 26 | + w={{ base: "100%" }} | |
| 27 | + me='36px' | |
| 28 | + minH={200} | |
| 29 | + img={text === 'Property Photos' ? 'img' : ''} | |
| 30 | + csv={'csv'} | |
| 31 | + height={'100%'} | |
| 32 | + onFileSelect={onFileSelect} | |
| 33 | + isMultipleAllow={false} | |
| 34 | + content={ | |
| 35 | + <Box> | |
| 36 | + <Icon as={MdUpload} w='80px' h='80px' color={brandColor} /> | |
| 37 | + <Flex justify='center' mx='auto' mb='12px'> | |
| 38 | + <Text fontSize='xl' fontWeight='700' color={brandColor}> | |
| 39 | + Upload File | |
| 40 | + </Text> | |
| 41 | + </Flex> | |
| 42 | + {count > 0 && <Text fontSize='sm' fontWeight='500' color='secondaryGray.500'> | |
| 43 | + Selected Files : {count} | |
| 44 | + </Text>} | |
| 45 | + </Box> | |
| 46 | + } | |
| 47 | + /> | |
| 48 | + </GridItem> | |
| 49 | + <GridItem colSpan={{ base: 12 }} > | |
| 50 | + <Flex direction='column' > | |
| 51 | + <Text | |
| 52 | + color={textColorPrimary} | |
| 53 | + fontWeight='bold' | |
| 54 | + textAlign='start' | |
| 55 | + fontSize='2xl' | |
| 56 | + mt={{ base: "20px" }}> | |
| 57 | + Upload {text} | |
| 58 | + </Text> | |
| 59 | + <Text | |
| 60 | + color={textColorSecondary} | |
| 61 | + fontSize='md' | |
| 62 | + my={{ base: "auto" }} | |
| 63 | + mx='auto' | |
| 64 | + textAlign='start'> | |
| 65 | + Accepted File Type (csv, xlsx file - 15MB max) only 1 file | |
| 66 | + </Text> | |
| 67 | + </Flex> | |
| 68 | + </GridItem> | |
| 69 | + </Grid> | |
| 70 | + ); | |
| 71 | +} |
+103-0Client/src/components/commonTableModel/AccountModel.js
| @@ -0,0 +1,103 @@ | ||
| 1 | +import { Button, Flex, Modal, ModalBody, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalOverlay } from '@chakra-ui/react' | |
| 2 | +import React, { useEffect, useState } from 'react' | |
| 3 | +import Spinner from 'components/spinner/Spinner' | |
| 4 | +import { GiClick } from "react-icons/gi"; | |
| 5 | +import CommonCheckTable from 'components/reactTable/checktable'; | |
| 6 | +import { useDispatch } from 'react-redux'; | |
| 7 | +import { getApi } from 'services/api'; | |
| 8 | +import { fetchAccountData } from '../../redux/slices/accountSlice'; | |
| 9 | +import { toast } from 'react-toastify'; | |
| 10 | + | |
| 11 | +const AccountModel = (props) => { | |
| 12 | + const { onClose, isOpen, fieldName, setFieldValue, data, type, billingState, billingCity, billingPostalCode, billingCountry, billingStreet } = props | |
| 13 | + const title = "Account"; | |
| 14 | + const dispatch = useDispatch(); | |
| 15 | + // const [data, setData] = useState([]); | |
| 16 | + | |
| 17 | + const [isLoding, setIsLoding] = useState(false); | |
| 18 | + const [leadData, setLeadData] = useState([]); | |
| 19 | + const [selectedValues, setSelectedValues] = useState([]); | |
| 20 | + const accountData = data?.length > 0 && data?.find((item) => item?._id === selectedValues) | |
| 21 | + const handleSubmit = async () => { | |
| 22 | + try { | |
| 23 | + setIsLoding(true) | |
| 24 | + setFieldValue(fieldName, selectedValues) | |
| 25 | + // if (type === "quotes") { | |
| 26 | + // setFieldValue(billingStreet, accountData?.billingStreet) | |
| 27 | + // setFieldValue(billingState, accountData?.billingState) | |
| 28 | + // setFieldValue(billingCity, selectedValues?.billingCity) | |
| 29 | + // setFieldValue(billingPostalCode, selectedValues?.billingPostalcode) | |
| 30 | + // setFieldValue(billingCountry, selectedValues?.billingCountry) | |
| 31 | + // } | |
| 32 | + onClose() | |
| 33 | + } | |
| 34 | + catch (e) { | |
| 35 | + console.log(e) | |
| 36 | + } | |
| 37 | + finally { | |
| 38 | + setIsLoding(false) | |
| 39 | + } | |
| 40 | + } | |
| 41 | + const tableColumns = [ | |
| 42 | + { Header: "#", accessor: "_id", isSortable: false, width: 10 }, | |
| 43 | + { Header: 'Account Name', accessor: 'name' }, | |
| 44 | + { Header: 'Office Phone', accessor: 'officePhone', }, | |
| 45 | + { Header: 'Fax', accessor: 'fax', }, | |
| 46 | + { Header: 'Email Address', accessor: 'emailAddress', }, | |
| 47 | + ]; | |
| 48 | + | |
| 49 | + // const fetchData = async () => { | |
| 50 | + // setIsLoding(true) | |
| 51 | + // const result = await dispatch(fetchAccountData()) | |
| 52 | + | |
| 53 | + // if (result.payload.status === 200) { | |
| 54 | + // setData(result?.payload?.data); | |
| 55 | + // } else { | |
| 56 | + // toast.error("Failed to fetch data", "error"); | |
| 57 | + // } | |
| 58 | + // setIsLoding(false) | |
| 59 | + // } | |
| 60 | + | |
| 61 | + // useEffect(() => { | |
| 62 | + // fetchData() | |
| 63 | + // }, []) | |
| 64 | + | |
| 65 | + return ( | |
| 66 | + <Modal onClose={onClose} size='full' isOpen={isOpen} > | |
| 67 | + <ModalOverlay /> | |
| 68 | + <ModalContent> | |
| 69 | + <ModalHeader>Select Account</ModalHeader> | |
| 70 | + <ModalCloseButton /> | |
| 71 | + <ModalBody> | |
| 72 | + {isLoding ? | |
| 73 | + <Flex justifyContent={'center'} alignItems={'center'} width="100%" > | |
| 74 | + <Spinner /> | |
| 75 | + </Flex> : | |
| 76 | + <CommonCheckTable | |
| 77 | + title={title} | |
| 78 | + isLoding={isLoding} | |
| 79 | + columnData={tableColumns ?? []} | |
| 80 | + // dataColumn={columns ?? []} | |
| 81 | + allData={data ?? []} | |
| 82 | + tableData={data} | |
| 83 | + AdvanceSearch={() => ""} | |
| 84 | + ManageGrid={false} | |
| 85 | + deleteMany={false} | |
| 86 | + selectedValues={selectedValues} | |
| 87 | + setSelectedValues={setSelectedValues} | |
| 88 | + selectType="single" | |
| 89 | + customSearch={false} | |
| 90 | + /> | |
| 91 | + } | |
| 92 | + </ModalBody> | |
| 93 | + <ModalFooter> | |
| 94 | + <Button variant='brand' size='sm' me={2} disabled={isLoding ? true : false} leftIcon={<GiClick />} onClick={handleSubmit}> {isLoding ? <Spinner /> : 'Select'}</Button> | |
| 95 | + <Button variant='outline' size='sm' colorScheme='red' onClick={() => onClose()}>Close</Button> | |
| 96 | + </ModalFooter> | |
| 97 | + </ModalContent> | |
| 98 | + </Modal> | |
| 99 | + | |
| 100 | + ) | |
| 101 | +} | |
| 102 | + | |
| 103 | +export default AccountModel |
+255-0Client/src/components/commonTableModel/Contact.js
| @@ -0,0 +1,255 @@ | ||
| 1 | +import { | |
| 2 | + Box, Checkbox, | |
| 3 | + Flex, Table, | |
| 4 | + Tbody, | |
| 5 | + Td, | |
| 6 | + Text, | |
| 7 | + Th, | |
| 8 | + Thead, | |
| 9 | + Tr, | |
| 10 | + useColorModeValue | |
| 11 | +} from "@chakra-ui/react"; | |
| 12 | +import { useMemo, useState } from "react"; | |
| 13 | +import { | |
| 14 | + useGlobalFilter, | |
| 15 | + usePagination, | |
| 16 | + useSortBy, | |
| 17 | + useTable, | |
| 18 | +} from "react-table"; | |
| 19 | +import { GrFormNext, GrFormPrevious } from "react-icons/gr"; | |
| 20 | + | |
| 21 | +// Custom components | |
| 22 | +import Card from "components/card/Card"; | |
| 23 | +import { Link } from "react-router-dom"; | |
| 24 | +import CountUpComponent from "components/countUpComponent/countUpComponent"; | |
| 25 | +import Pagination from "components/pagination/Pagination"; | |
| 26 | +import { FaSort, FaSortDown, FaSortUp } from "react-icons/fa"; | |
| 27 | + | |
| 28 | +export default function ContactTable(props) { | |
| 29 | + const { columnsData, tableData, title, type, selectedValues, setSelectedValues } = props; | |
| 30 | + | |
| 31 | + const columns = useMemo(() => columnsData, [columnsData]); | |
| 32 | + const data = useMemo(() => tableData, [tableData]); | |
| 33 | + const user = JSON.parse(localStorage.getItem("user")) | |
| 34 | + const [gopageValue, setGopageValue] = useState() | |
| 35 | + | |
| 36 | + const tableInstance = useTable( | |
| 37 | + { | |
| 38 | + columns, data, | |
| 39 | + initialState: { pageIndex: 0 } | |
| 40 | + }, | |
| 41 | + useGlobalFilter, | |
| 42 | + useSortBy, | |
| 43 | + usePagination | |
| 44 | + ); | |
| 45 | + | |
| 46 | + const { | |
| 47 | + getTableProps, | |
| 48 | + getTableBodyProps, | |
| 49 | + headerGroups, | |
| 50 | + prepareRow, | |
| 51 | + page, | |
| 52 | + canPreviousPage, | |
| 53 | + canNextPage, | |
| 54 | + pageOptions, | |
| 55 | + pageCount, | |
| 56 | + gotoPage, | |
| 57 | + nextPage, | |
| 58 | + previousPage, | |
| 59 | + setPageSize, | |
| 60 | + state: { pageIndex, pageSize } | |
| 61 | + } = tableInstance; | |
| 62 | + | |
| 63 | + if (pageOptions.length < gopageValue) { | |
| 64 | + setGopageValue(pageOptions.length) | |
| 65 | + } | |
| 66 | + | |
| 67 | + const textColor = useColorModeValue("gray.500", "white"); | |
| 68 | + const borderColor = useColorModeValue("gray.200", "whiteAlpha.100"); | |
| 69 | + | |
| 70 | + const handleCheckboxChange = (event, value) => { | |
| 71 | + if (type === "multi") { | |
| 72 | + if (event.target.checked) { | |
| 73 | + setSelectedValues((prevSelectedValues) => [...prevSelectedValues, value]); | |
| 74 | + } else { | |
| 75 | + setSelectedValues((prevSelectedValues) => | |
| 76 | + prevSelectedValues.filter((selectedValue) => selectedValue !== value) | |
| 77 | + ); | |
| 78 | + } | |
| 79 | + } else { | |
| 80 | + if (event.target.checked) { | |
| 81 | + setSelectedValues(value); | |
| 82 | + } else { | |
| 83 | + setSelectedValues(null); | |
| 84 | + } | |
| 85 | + } | |
| 86 | + }; | |
| 87 | + | |
| 88 | + | |
| 89 | + return ( | |
| 90 | + <Card | |
| 91 | + direction='column' | |
| 92 | + w='100%' | |
| 93 | + px='0px' | |
| 94 | + style={{ border: '1px solid gray.200' }} | |
| 95 | + overflowX={{ sm: "scroll", lg: "hidden" }}> | |
| 96 | + <Flex px='25px' justify='space-between' mb='20px' align='center'> | |
| 97 | + <Text | |
| 98 | + color={textColor} | |
| 99 | + fontSize='22px' | |
| 100 | + fontWeight='700' | |
| 101 | + lineHeight='100%'> | |
| 102 | + {title} (<CountUpComponent key={data?.length} targetNumber={data?.length} />) | |
| 103 | + </Text> | |
| 104 | + </Flex> | |
| 105 | + <Box overflowY={'auto'} className="table-container-property" > | |
| 106 | + <Table {...getTableProps()} variant='simple' color='gray.500' mb='24px'> | |
| 107 | + <Thead zIndex={1}> | |
| 108 | + {headerGroups?.map((headerGroup, index) => ( | |
| 109 | + <Tr {...headerGroup.getHeaderGroupProps()} key={index}> | |
| 110 | + {headerGroup.headers.map((column, index) => ( | |
| 111 | + | |
| 112 | + <Th | |
| 113 | + {...column.getHeaderProps(column.isSortable !== false && column.getSortByToggleProps())} | |
| 114 | + pe="10px" | |
| 115 | + key={index} | |
| 116 | + borderColor={borderColor} | |
| 117 | + > | |
| 118 | + <Flex | |
| 119 | + align="center" | |
| 120 | + justifyContent={column.center ? "center" : "start"} | |
| 121 | + fontSize={{ sm: "14px", lg: "16px" }} | |
| 122 | + color=" secondaryGray.900" | |
| 123 | + > | |
| 124 | + <span style={{ textTransform: "capitalize", marginRight: "8px" }}> | |
| 125 | + {column.render("Header")} | |
| 126 | + </span> | |
| 127 | + | |
| 128 | + {column.isSortable !== false && ( | |
| 129 | + <span> | |
| 130 | + {column.isSorted ? (column.isSortedDesc ? <FaSortDown /> : <FaSortUp />) : <FaSort />} | |
| 131 | + </span> | |
| 132 | + )} | |
| 133 | + </Flex> | |
| 134 | + </Th> | |
| 135 | + ))} | |
| 136 | + </Tr> | |
| 137 | + ))} | |
| 138 | + </Thead> | |
| 139 | + <Tbody {...getTableBodyProps()}> | |
| 140 | + {data?.length === 0 && ( | |
| 141 | + <Tr> | |
| 142 | + <Td colSpan={columns.length}> | |
| 143 | + <Text textAlign={'center'} width="100%" color={textColor} fontSize="sm" fontWeight="700"> | |
| 144 | + -- No Data Found -- | |
| 145 | + </Text> | |
| 146 | + </Td> | |
| 147 | + </Tr> | |
| 148 | + )} | |
| 149 | + {page?.map((row, index) => { | |
| 150 | + prepareRow(row); | |
| 151 | + return ( | |
| 152 | + <Tr {...row?.getRowProps()} key={index}> | |
| 153 | + {row?.cells?.map((cell, index) => { | |
| 154 | + let data = ""; | |
| 155 | + if (cell?.column.Header === "#") { | |
| 156 | + data = ( | |
| 157 | + <Flex align="center"> | |
| 158 | + {type === "multi" ? <Checkbox colorScheme="brandScheme" value={selectedValues} isChecked={selectedValues.includes(cell?.value)} onChange={(event) => handleCheckboxChange(event, cell?.value)} me="10px" /> : | |
| 159 | + <Checkbox colorScheme="brandScheme" value={selectedValues} isChecked={selectedValues === cell?.value} onChange={(event) => handleCheckboxChange(event, cell?.value)} me="10px" />} | |
| 160 | + <Text color={textColor} fontSize="sm" fontWeight="700"> | |
| 161 | + {cell?.row?.index + 1} | |
| 162 | + </Text> | |
| 163 | + </Flex> | |
| 164 | + ); | |
| 165 | + } else if (cell?.column.Header === "title") { | |
| 166 | + data = ( | |
| 167 | + <Text | |
| 168 | + me="10px" | |
| 169 | + color={textColor} | |
| 170 | + fontSize="sm" | |
| 171 | + fontWeight="700" | |
| 172 | + > | |
| 173 | + {cell?.value} | |
| 174 | + </Text> | |
| 175 | + ); | |
| 176 | + } else if (cell?.column.Header === "first Name") { | |
| 177 | + data = ( | |
| 178 | + // <Link to={`/contactView/${cell?.row?.original._id}`}> | |
| 179 | + <Text | |
| 180 | + me="10px" | |
| 181 | + // sx={{ '&:hover': { color: 'blue.500', textDecoration: 'underline' } }} | |
| 182 | + color={textColor} | |
| 183 | + fontSize="sm" | |
| 184 | + fontWeight="700" | |
| 185 | + > | |
| 186 | + {cell?.value} | |
| 187 | + </Text> | |
| 188 | + // </Link> | |
| 189 | + ); | |
| 190 | + } else if (cell?.column.Header === "last Name") { | |
| 191 | + data = ( | |
| 192 | + <Text | |
| 193 | + me="10px" | |
| 194 | + color={textColor} | |
| 195 | + fontSize="sm" | |
| 196 | + fontWeight="700" | |
| 197 | + > | |
| 198 | + {cell?.value} | |
| 199 | + </Text> | |
| 200 | + ); | |
| 201 | + } else if (cell?.column.Header === "phone Number") { | |
| 202 | + data = ( | |
| 203 | + <Text fontSize="sm" fontWeight="700" | |
| 204 | + color={textColor}> | |
| 205 | + {cell?.value} | |
| 206 | + </Text> | |
| 207 | + ); | |
| 208 | + } else if (cell?.column.Header === "Email Address") { | |
| 209 | + data = ( | |
| 210 | + <Text fontSize="sm" fontWeight="700" | |
| 211 | + color={textColor}> | |
| 212 | + {cell?.value} | |
| 213 | + </Text> | |
| 214 | + ); | |
| 215 | + } else if (cell?.column.Header === "physical Address") { | |
| 216 | + data = ( | |
| 217 | + <Text color={textColor} fontSize="sm" fontWeight="700"> | |
| 218 | + {cell?.value} | |
| 219 | + </Text> | |
| 220 | + ); | |
| 221 | + } else if (cell?.column.Header === "mailing Address") { | |
| 222 | + data = ( | |
| 223 | + <Text color={textColor} fontSize="sm" fontWeight="700"> | |
| 224 | + {cell?.value} | |
| 225 | + </Text> | |
| 226 | + ); | |
| 227 | + } else if (cell?.column.Header === "Contact Method") { | |
| 228 | + data = ( | |
| 229 | + <Text color={textColor} fontSize="sm" fontWeight="700"> | |
| 230 | + {cell?.value} | |
| 231 | + </Text> | |
| 232 | + ); | |
| 233 | + } | |
| 234 | + return ( | |
| 235 | + <Td | |
| 236 | + {...cell?.getCellProps()} | |
| 237 | + key={index} | |
| 238 | + fontSize={{ sm: "14px" }} | |
| 239 | + minW={{ sm: "150px", md: "200px", lg: "auto" }} | |
| 240 | + borderColor='transparent'> | |
| 241 | + {data} | |
| 242 | + </Td> | |
| 243 | + ); | |
| 244 | + })} | |
| 245 | + </Tr> | |
| 246 | + ); | |
| 247 | + })} | |
| 248 | + </Tbody> | |
| 249 | + </Table> | |
| 250 | + </Box> | |
| 251 | + {data?.length > 5 && <Pagination gotoPage={gotoPage} gopageValue={gopageValue} setGopageValue={setGopageValue} pageCount={pageCount} canPreviousPage={canPreviousPage} previousPage={previousPage} canNextPage={canNextPage} pageOptions={pageOptions} setPageSize={setPageSize} nextPage={nextPage} pageSize={pageSize} pageIndex={pageIndex} />} | |
| 252 | + | |
| 253 | + </Card> | |
| 254 | + ); | |
| 255 | +} |
+91-0Client/src/components/commonTableModel/ContactModel.js
| @@ -0,0 +1,91 @@ | ||
| 1 | +import { Button, Flex, Modal, ModalBody, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalOverlay } from '@chakra-ui/react' | |
| 2 | +import React, { useEffect, useState } from 'react' | |
| 3 | +import ContactTable from './Contact.js' | |
| 4 | +import Spinner from 'components/spinner/Spinner' | |
| 5 | +import { GiClick } from "react-icons/gi"; | |
| 6 | +import CommonCheckTable from 'components/reactTable/checktable.js'; | |
| 7 | +import { fetchContactCustomFiled } from '../../redux/slices/contactCustomFiledSlice.js'; | |
| 8 | +import { fetchContactData } from '../../redux/slices/contactSlice.js'; | |
| 9 | +import { useDispatch } from 'react-redux'; | |
| 10 | + | |
| 11 | +const ContactModel = (props) => { | |
| 12 | + const { onClose, isOpen, fieldName, setFieldValue, data } = props | |
| 13 | + const [selectedValues, setSelectedValues] = useState(); | |
| 14 | + const [contactData, setContactData] = useState([]); | |
| 15 | + const [isLoding, setIsLoding] = useState(false) | |
| 16 | + const [columns, setColumns] = useState([]); | |
| 17 | + const dispatch = useDispatch(); | |
| 18 | + | |
| 19 | + const handleSubmit = async () => { | |
| 20 | + try { | |
| 21 | + setIsLoding(true) | |
| 22 | + setFieldValue(fieldName, selectedValues) | |
| 23 | + onClose() | |
| 24 | + } | |
| 25 | + catch (e) { | |
| 26 | + console.log(e) | |
| 27 | + } | |
| 28 | + finally { | |
| 29 | + setIsLoding(false) | |
| 30 | + } | |
| 31 | + } | |
| 32 | + const fetchCustomDataFields = async () => { | |
| 33 | + setIsLoding(true); | |
| 34 | + const result = await dispatch(fetchContactCustomFiled()); | |
| 35 | + setContactData(result?.payload?.data); | |
| 36 | + | |
| 37 | + const tempTableColumns = [ | |
| 38 | + { Header: "#", accessor: "_id", isSortable: false, width: 10 }, | |
| 39 | + ...(result?.payload?.data?.[0]?.fields || []) | |
| 40 | + .filter(field => field?.isTableField === true) | |
| 41 | + .map(field => ({ Header: field?.label, accessor: field?.name })) | |
| 42 | + ]; | |
| 43 | + | |
| 44 | + setColumns(tempTableColumns); | |
| 45 | + setIsLoding(false); | |
| 46 | + }; | |
| 47 | + useEffect(async () => { | |
| 48 | + await dispatch(fetchContactData()) | |
| 49 | + fetchCustomDataFields(); | |
| 50 | + }, []); | |
| 51 | + return ( | |
| 52 | + <Modal onClose={onClose} size='full' isOpen={isOpen} > | |
| 53 | + <ModalOverlay /> | |
| 54 | + <ModalContent> | |
| 55 | + <ModalHeader>Select Contact</ModalHeader> | |
| 56 | + <ModalCloseButton /> | |
| 57 | + <ModalBody> | |
| 58 | + {isLoding ? | |
| 59 | + <Flex justifyContent={'center'} alignItems={'center'} width="100%" > | |
| 60 | + <Spinner /> | |
| 61 | + </Flex> | |
| 62 | + : | |
| 63 | + <CommonCheckTable | |
| 64 | + title={'Contacts'} | |
| 65 | + isLoding={isLoding} | |
| 66 | + columnData={columns ?? []} | |
| 67 | + // dataColumn={columns ?? []} | |
| 68 | + allData={data ?? []} | |
| 69 | + tableData={data} | |
| 70 | + tableCustomFields={contactData?.[0]?.fields?.filter((field) => field?.isTableField === true) || []} | |
| 71 | + AdvanceSearch={() => ""} | |
| 72 | + ManageGrid={false} | |
| 73 | + deleteMany={false} | |
| 74 | + selectedValues={selectedValues} | |
| 75 | + setSelectedValues={setSelectedValues} | |
| 76 | + selectType="single" | |
| 77 | + customSearch={false} | |
| 78 | + /> | |
| 79 | + } | |
| 80 | + </ModalBody> | |
| 81 | + <ModalFooter> | |
| 82 | + <Button variant='brand' size='sm' me={2} onClick={handleSubmit} disabled={isLoding ? true : false} leftIcon={<GiClick />}> {isLoding ? <Spinner /> : 'Select'}</Button> | |
| 83 | + <Button variant='outline' size='sm' colorScheme='red' onClick={() => onClose()}>Close</Button> | |
| 84 | + </ModalFooter> | |
| 85 | + </ModalContent> | |
| 86 | + </Modal> | |
| 87 | + | |
| 88 | + ) | |
| 89 | +} | |
| 90 | + | |
| 91 | +export default ContactModel |
+247-0Client/src/components/commonTableModel/Lead.js
| @@ -0,0 +1,247 @@ | ||
| 1 | +import { | |
| 2 | + Box, Checkbox, | |
| 3 | + Flex, Table, | |
| 4 | + Tbody, | |
| 5 | + Td, | |
| 6 | + Text, | |
| 7 | + Th, | |
| 8 | + Thead, | |
| 9 | + Tr, | |
| 10 | + useColorModeValue | |
| 11 | +} from "@chakra-ui/react"; | |
| 12 | +import { useMemo } from "react"; | |
| 13 | +import { | |
| 14 | + useGlobalFilter, | |
| 15 | + usePagination, | |
| 16 | + useSortBy, | |
| 17 | + useTable, | |
| 18 | +} from "react-table"; | |
| 19 | + | |
| 20 | +// Custom components | |
| 21 | +import Card from "components/card/Card"; | |
| 22 | +import CountUpComponent from "components/countUpComponent/countUpComponent"; | |
| 23 | +import { useState } from "react"; | |
| 24 | +import { Link } from "react-router-dom"; | |
| 25 | +import Pagination from "components/pagination/Pagination"; | |
| 26 | +import { FaSort, FaSortDown, FaSortUp } from "react-icons/fa"; | |
| 27 | + | |
| 28 | +export default function LeadTable(props) { | |
| 29 | + const { columnsData, tableData, title, type, selectedValues, setSelectedValues } = props; | |
| 30 | + | |
| 31 | + const columns = useMemo(() => columnsData, [columnsData]); | |
| 32 | + const data = useMemo(() => tableData, [tableData]); | |
| 33 | + const user = JSON.parse(localStorage.getItem("user")) | |
| 34 | + const [gopageValue, setGopageValue] = useState() | |
| 35 | + | |
| 36 | + const tableInstance = useTable( | |
| 37 | + { | |
| 38 | + columns, data, | |
| 39 | + initialState: { pageIndex: 0 } | |
| 40 | + }, | |
| 41 | + useGlobalFilter, | |
| 42 | + useSortBy, | |
| 43 | + usePagination | |
| 44 | + ); | |
| 45 | + | |
| 46 | + const { | |
| 47 | + getTableProps, | |
| 48 | + getTableBodyProps, | |
| 49 | + headerGroups, | |
| 50 | + prepareRow, | |
| 51 | + page, | |
| 52 | + canPreviousPage, | |
| 53 | + canNextPage, | |
| 54 | + pageOptions, | |
| 55 | + pageCount, | |
| 56 | + gotoPage, | |
| 57 | + nextPage, | |
| 58 | + previousPage, | |
| 59 | + setPageSize, | |
| 60 | + state: { pageIndex, pageSize } | |
| 61 | + } = tableInstance; | |
| 62 | + | |
| 63 | + if (pageOptions.length < gopageValue) { | |
| 64 | + setGopageValue(pageOptions.length) | |
| 65 | + } | |
| 66 | + | |
| 67 | + const textColor = useColorModeValue("gray.500", "white"); | |
| 68 | + const borderColor = useColorModeValue("gray.200", "whiteAlpha.100"); | |
| 69 | + | |
| 70 | + const handleCheckboxChange = (event, value) => { | |
| 71 | + if (type === "multi") { | |
| 72 | + if (event.target.checked) { | |
| 73 | + setSelectedValues((prevSelectedValues) => [...prevSelectedValues, value]); | |
| 74 | + } else { | |
| 75 | + setSelectedValues((prevSelectedValues) => | |
| 76 | + prevSelectedValues.filter((selectedValue) => selectedValue !== value) | |
| 77 | + ); | |
| 78 | + } | |
| 79 | + } else { | |
| 80 | + if (event.target.checked) { | |
| 81 | + setSelectedValues(value); | |
| 82 | + } else { | |
| 83 | + setSelectedValues(null); | |
| 84 | + } | |
| 85 | + } | |
| 86 | + | |
| 87 | + }; | |
| 88 | + | |
| 89 | + | |
| 90 | + return ( | |
| 91 | + <Card | |
| 92 | + direction='column' | |
| 93 | + w='100%' | |
| 94 | + px='0px' | |
| 95 | + style={{ border: '1px solid gray.200' }} | |
| 96 | + overflowX={{ sm: "scroll", lg: "hidden" }}> | |
| 97 | + <Flex px='25px' justify='space-between' mb='20px' align='center'> | |
| 98 | + <Text | |
| 99 | + color={textColor} | |
| 100 | + fontSize='22px' | |
| 101 | + fontWeight='700' | |
| 102 | + lineHeight='100%'> | |
| 103 | + {title} (<CountUpComponent key={data?.length} targetNumber={data?.length} />) | |
| 104 | + </Text> | |
| 105 | + </Flex> | |
| 106 | + <Box overflowY={'auto'} className="table-container-property" > | |
| 107 | + <Table {...getTableProps()} variant='simple' color='gray.500' mb='24px'> | |
| 108 | + <Thead zIndex={1}> | |
| 109 | + {headerGroups?.map((headerGroup, index) => ( | |
| 110 | + <Tr {...headerGroup.getHeaderGroupProps()} key={index}> | |
| 111 | + {headerGroup.headers.map((column, index) => ( | |
| 112 | + <Th | |
| 113 | + {...column.getHeaderProps(column.isSortable !== false && column.getSortByToggleProps())} | |
| 114 | + pe="10px" | |
| 115 | + key={index} | |
| 116 | + borderColor={borderColor} | |
| 117 | + > | |
| 118 | + <Flex | |
| 119 | + align="center" | |
| 120 | + justifyContent={column.center ? "center" : "start"} | |
| 121 | + fontSize={{ sm: "14px", lg: "16px" }} | |
| 122 | + color=" secondaryGray.900" | |
| 123 | + > | |
| 124 | + <span style={{ textTransform: "capitalize", marginRight: "8px" }}> | |
| 125 | + {column.render("Header")} | |
| 126 | + </span> | |
| 127 | + | |
| 128 | + {column.isSortable !== false && ( | |
| 129 | + <span> | |
| 130 | + {column.isSorted ? (column.isSortedDesc ? <FaSortDown /> : <FaSortUp />) : <FaSort />} | |
| 131 | + </span> | |
| 132 | + )} | |
| 133 | + </Flex> | |
| 134 | + </Th> | |
| 135 | + ))} | |
| 136 | + </Tr> | |
| 137 | + ))} | |
| 138 | + </Thead> | |
| 139 | + <Tbody {...getTableBodyProps()}> | |
| 140 | + {data?.length === 0 && ( | |
| 141 | + <Tr> | |
| 142 | + <Td colSpan={columns.length}> | |
| 143 | + <Text textAlign={'center'} width="100%" color={textColor} fontSize="sm" fontWeight="700"> | |
| 144 | + -- No Data Found -- | |
| 145 | + </Text> | |
| 146 | + </Td> | |
| 147 | + </Tr> | |
| 148 | + )} | |
| 149 | + {page?.map((row, index) => { | |
| 150 | + prepareRow(row); | |
| 151 | + return ( | |
| 152 | + <Tr {...row?.getRowProps()} key={index}> | |
| 153 | + {row?.cells?.map((cell, index) => { | |
| 154 | + let data = ""; | |
| 155 | + if (cell?.column.Header === "#") { | |
| 156 | + data = ( | |
| 157 | + <Flex align="center"> | |
| 158 | + {type === "multi" ? <Checkbox colorScheme="brandScheme" value={selectedValues} isChecked={selectedValues.includes(cell?.value)} onChange={(event) => handleCheckboxChange(event, cell?.value)} me="10px" /> : | |
| 159 | + <Checkbox colorScheme="brandScheme" value={selectedValues} isChecked={selectedValues === cell?.value} onChange={(event) => handleCheckboxChange(event, cell?.value)} me="10px" />} | |
| 160 | + <Text color={textColor} fontSize="sm" fontWeight="700"> | |
| 161 | + {cell?.row?.index + 1} | |
| 162 | + </Text> | |
| 163 | + </Flex> | |
| 164 | + ); | |
| 165 | + } else if (cell?.column.Header === "Lead Name") { | |
| 166 | + data = ( | |
| 167 | + // <Link to={user?.role !== 'superAdmin' ? `/leadView/${cell?.row?.values._id}` : `/admin/leadView/${cell?.row?.values._id}`}> | |
| 168 | + <Text | |
| 169 | + me="10px" | |
| 170 | + // sx={{ '&:hover': { color: 'blue.500', textDecoration: 'underline' } }} | |
| 171 | + color={textColor} | |
| 172 | + fontSize="sm" | |
| 173 | + fontWeight="700" | |
| 174 | + > | |
| 175 | + {cell?.value} | |
| 176 | + </Text> | |
| 177 | + // </Link> | |
| 178 | + ); | |
| 179 | + } else if (cell?.column.Header === "Lead Email") { | |
| 180 | + data = ( | |
| 181 | + <Text | |
| 182 | + me="10px" | |
| 183 | + color={textColor} | |
| 184 | + fontSize="sm" | |
| 185 | + fontWeight="700" | |
| 186 | + > | |
| 187 | + {cell?.value} | |
| 188 | + </Text> | |
| 189 | + ); | |
| 190 | + } else if (cell?.column.Header === "Lead PhoneNumber") { | |
| 191 | + data = ( | |
| 192 | + <Text | |
| 193 | + me="10px" | |
| 194 | + color={textColor} | |
| 195 | + fontSize="sm" | |
| 196 | + fontWeight="700" | |
| 197 | + > | |
| 198 | + {cell?.value} | |
| 199 | + </Text> | |
| 200 | + ); | |
| 201 | + } else if (cell?.column.Header === "Lead Address") { | |
| 202 | + data = ( | |
| 203 | + <Text color={textColor} fontSize="sm" fontWeight="700"> | |
| 204 | + {cell?.value} | |
| 205 | + </Text> | |
| 206 | + ); | |
| 207 | + } else if (cell?.column.Header === "Lead Status") { | |
| 208 | + data = ( | |
| 209 | + <Text color={textColor} fontSize="sm" fontWeight="700"> | |
| 210 | + {cell?.value} | |
| 211 | + </Text> | |
| 212 | + ); | |
| 213 | + } else if (cell?.column.Header === "Lead Owner") { | |
| 214 | + data = ( | |
| 215 | + <Text color={textColor} fontSize="sm" fontWeight="700"> | |
| 216 | + {cell?.value} | |
| 217 | + </Text> | |
| 218 | + ); | |
| 219 | + } else if (cell?.column.Header === "Lead Score") { | |
| 220 | + data = ( | |
| 221 | + <Text color={textColor} fontSize="sm" fontWeight="700"> | |
| 222 | + {cell?.value} | |
| 223 | + </Text> | |
| 224 | + ); | |
| 225 | + } | |
| 226 | + return ( | |
| 227 | + <Td | |
| 228 | + {...cell?.getCellProps()} | |
| 229 | + key={index} | |
| 230 | + fontSize={{ sm: "14px" }} | |
| 231 | + minW={{ sm: "150px", md: "200px", lg: "auto" }} | |
| 232 | + borderColor='transparent'> | |
| 233 | + {data} | |
| 234 | + </Td> | |
| 235 | + ); | |
| 236 | + })} | |
| 237 | + </Tr> | |
| 238 | + ); | |
| 239 | + })} | |
| 240 | + </Tbody> | |
| 241 | + </Table> | |
| 242 | + </Box> | |
| 243 | + {data?.length > 5 && <Pagination gotoPage={gotoPage} gopageValue={gopageValue} setGopageValue={setGopageValue} pageCount={pageCount} canPreviousPage={canPreviousPage} previousPage={previousPage} canNextPage={canNextPage} pageOptions={pageOptions} setPageSize={setPageSize} nextPage={nextPage} pageSize={pageSize} pageIndex={pageIndex} />} | |
| 244 | + | |
| 245 | + </Card> | |
| 246 | + ); | |
| 247 | +} |
+99-0Client/src/components/commonTableModel/LeadModel.js
| @@ -0,0 +1,99 @@ | ||
| 1 | +import { Button, Flex, Modal, ModalBody, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalOverlay } from '@chakra-ui/react' | |
| 2 | +import React, { useEffect, useState } from 'react' | |
| 3 | +import Spinner from 'components/spinner/Spinner' | |
| 4 | +import { GiClick } from "react-icons/gi"; | |
| 5 | +import CommonCheckTable from 'components/reactTable/checktable'; | |
| 6 | +import { fetchLeadCustomFiled } from '../../redux/slices/leadCustomFiledSlice'; | |
| 7 | +import { useDispatch } from 'react-redux'; | |
| 8 | +import { fetchLeadData } from '../../redux/slices/leadSlice'; | |
| 9 | + | |
| 10 | +const ContactModel = (props) => { | |
| 11 | + const { onClose, isOpen, fieldName, setFieldValue, data } = props | |
| 12 | + const title = "Leads"; | |
| 13 | + const dispatch = useDispatch(); | |
| 14 | + | |
| 15 | + const [isLoding, setIsLoding] = useState(false); | |
| 16 | + const [columns, setColumns] = useState([]); | |
| 17 | + const [leadData, setLeadData] = useState([]); | |
| 18 | + const [selectedValues, setSelectedValues] = useState([]); | |
| 19 | + | |
| 20 | + const handleSubmit = async () => { | |
| 21 | + try { | |
| 22 | + setIsLoding(true) | |
| 23 | + setFieldValue(fieldName, selectedValues) | |
| 24 | + onClose() | |
| 25 | + } | |
| 26 | + catch (e) { | |
| 27 | + console.log(e) | |
| 28 | + } | |
| 29 | + finally { | |
| 30 | + setIsLoding(false) | |
| 31 | + } | |
| 32 | + } | |
| 33 | + | |
| 34 | + const fetchCustomDataFields = async () => { | |
| 35 | + setIsLoding(true); | |
| 36 | + | |
| 37 | + const result = await dispatch(fetchLeadCustomFiled()); | |
| 38 | + setLeadData(result?.payload?.data); | |
| 39 | + | |
| 40 | + const tempTableColumns = [ | |
| 41 | + { Header: "#", accessor: "_id", isSortable: false, width: 10 }, | |
| 42 | + { | |
| 43 | + Header: "Status", accessor: "leadStatus", isSortable: true, center: true, | |
| 44 | + cell: ({ row }) => ( | |
| 45 | + row.original.leadStatus | |
| 46 | + ) | |
| 47 | + }, | |
| 48 | + ...(result?.payload?.data?.[0]?.fields?.filter((field) => field?.isTableField === true)?.map((field) => (field?.name !== "leadStatus" && { Header: field?.label, accessor: field?.name })) || []), | |
| 49 | + ]; | |
| 50 | + | |
| 51 | + setColumns(tempTableColumns); | |
| 52 | + setIsLoding(false); | |
| 53 | + } | |
| 54 | + | |
| 55 | + useEffect(() => { | |
| 56 | + dispatch(fetchLeadData()) | |
| 57 | + fetchCustomDataFields(); | |
| 58 | + }, []) | |
| 59 | + | |
| 60 | + return ( | |
| 61 | + <Modal onClose={onClose} size='full' isOpen={isOpen} > | |
| 62 | + <ModalOverlay /> | |
| 63 | + <ModalContent> | |
| 64 | + <ModalHeader>Select Lead</ModalHeader> | |
| 65 | + <ModalCloseButton /> | |
| 66 | + <ModalBody> | |
| 67 | + {isLoding ? | |
| 68 | + <Flex justifyContent={'center'} alignItems={'center'} width="100%" > | |
| 69 | + <Spinner /> | |
| 70 | + </Flex> : | |
| 71 | + <CommonCheckTable | |
| 72 | + title={title} | |
| 73 | + isLoding={isLoding} | |
| 74 | + columnData={columns ?? []} | |
| 75 | + // dataColumn={columns ?? []} | |
| 76 | + allData={data ?? []} | |
| 77 | + tableData={data} | |
| 78 | + tableCustomFields={leadData?.[0]?.fields?.filter((field) => field?.isTableField === true) || []} | |
| 79 | + AdvanceSearch={() => ""} | |
| 80 | + ManageGrid={false} | |
| 81 | + deleteMany={false} | |
| 82 | + selectedValues={selectedValues} | |
| 83 | + setSelectedValues={setSelectedValues} | |
| 84 | + selectType="single" | |
| 85 | + customSearch={false} | |
| 86 | + /> | |
| 87 | + } | |
| 88 | + </ModalBody> | |
| 89 | + <ModalFooter> | |
| 90 | + <Button variant='brand' size='sm' me={2} disabled={isLoding ? true : false} leftIcon={<GiClick />} onClick={handleSubmit}> {isLoding ? <Spinner /> : 'Select'}</Button> | |
| 91 | + <Button variant='outline' size='sm' colorScheme='red' onClick={() => onClose()}>Close</Button> | |
| 92 | + </ModalFooter> | |
| 93 | + </ModalContent> | |
| 94 | + </Modal> | |
| 95 | + | |
| 96 | + ) | |
| 97 | +} | |
| 98 | + | |
| 99 | +export default ContactModel |
+65-0Client/src/components/commonTableModel/MultiContactModel.js
| @@ -0,0 +1,65 @@ | ||
| 1 | +import { Button, Flex, Modal, ModalBody, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalOverlay } from '@chakra-ui/react' | |
| 2 | +import React, { useState } from 'react' | |
| 3 | +import ContactTable from './Contact.js' | |
| 4 | +import Spinner from 'components/spinner/Spinner' | |
| 5 | +import { GiClick } from "react-icons/gi"; | |
| 6 | + | |
| 7 | +const MultiContactModel = (props) => { | |
| 8 | + const { onClose, isOpen, fieldName, setFieldValue,data } = props | |
| 9 | + const [selectedValues, setSelectedValues] = useState([]); | |
| 10 | + const [isLoding, setIsLoding] = useState(false) | |
| 11 | + | |
| 12 | + const columns = [ | |
| 13 | + { Header: "#", accessor: "_id", isSortable: false, width: 10 }, | |
| 14 | + { Header: 'title', accessor: 'title' }, | |
| 15 | + { Header: "first Name", accessor: "firstName" }, | |
| 16 | + { Header: "last Name", accessor: "lastName" }, | |
| 17 | + { Header: "phone Number", accessor: "phoneNumber" }, | |
| 18 | + { Header: "Email Address", accessor: "email" }, | |
| 19 | + { Header: "physical Address", accessor: "physicalAddress" }, | |
| 20 | + { Header: "mailing Address", accessor: "mailingAddress" }, | |
| 21 | + { Header: "Contact Method", accessor: "preferredContactMethod" }, | |
| 22 | + ]; | |
| 23 | + | |
| 24 | + const user = JSON.parse(localStorage.getItem("user")) | |
| 25 | + | |
| 26 | + const uniqueValues = [...new Set(selectedValues)]; | |
| 27 | + | |
| 28 | + const handleSubmit = async () => { | |
| 29 | + try { | |
| 30 | + setIsLoding(true) | |
| 31 | + setFieldValue(fieldName, uniqueValues) | |
| 32 | + onClose() | |
| 33 | + } | |
| 34 | + catch (e) { | |
| 35 | + console.log(e) | |
| 36 | + } | |
| 37 | + finally { | |
| 38 | + setIsLoding(false) | |
| 39 | + } | |
| 40 | + } | |
| 41 | + | |
| 42 | + | |
| 43 | + return ( | |
| 44 | + <Modal onClose={onClose} size='full' isOpen={isOpen} > | |
| 45 | + <ModalOverlay /> | |
| 46 | + <ModalContent> | |
| 47 | + <ModalHeader>Select Contact</ModalHeader> | |
| 48 | + <ModalCloseButton /> | |
| 49 | + <ModalBody> | |
| 50 | + {isLoding ? | |
| 51 | + <Flex justifyContent={'center'} alignItems={'center'} width="100%" > | |
| 52 | + <Spinner /> | |
| 53 | + </Flex> : <ContactTable tableData={data} type='multi' selectedValues={selectedValues} setSelectedValues={setSelectedValues} columnsData={columns} title="Contact" />} | |
| 54 | + </ModalBody> | |
| 55 | + <ModalFooter> | |
| 56 | + <Button variant='brand' onClick={handleSubmit} disabled={isLoding ? true : false} leftIcon={<GiClick />}> {isLoding ? <Spinner /> : 'Select'}</Button> | |
| 57 | + <Button onClick={() => onClose()}>Close</Button> | |
| 58 | + </ModalFooter> | |
| 59 | + </ModalContent> | |
| 60 | + </Modal> | |
| 61 | + | |
| 62 | + ) | |
| 63 | +} | |
| 64 | + | |
| 65 | +export default MultiContactModel |
+63-0Client/src/components/commonTableModel/MultiLeadModel.js
| @@ -0,0 +1,63 @@ | ||
| 1 | +import { Button, Flex, Modal, ModalBody, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalOverlay } from '@chakra-ui/react' | |
| 2 | +import React, { useState } from 'react' | |
| 3 | +import LeadTable from './Lead' | |
| 4 | +import Spinner from 'components/spinner/Spinner' | |
| 5 | +import { GiClick } from "react-icons/gi"; | |
| 6 | + | |
| 7 | +const MultiLeadModel = (props) => { | |
| 8 | + const { onClose, isOpen, fieldName, setFieldValue,data } = props | |
| 9 | + const [selectedValues, setSelectedValues] = useState([]); | |
| 10 | + const [isLoding, setIsLoding] = useState(false) | |
| 11 | + | |
| 12 | + const columns = [ | |
| 13 | + { Header: "#", accessor: "_id", isSortable: false, width: 10 }, | |
| 14 | + { Header: 'Lead Name', accessor: 'leadName', width: 20 }, | |
| 15 | + { Header: "Lead Email", accessor: "leadEmail", }, | |
| 16 | + { Header: "Lead PhoneNumber", accessor: "leadPhoneNumber", }, | |
| 17 | + { Header: "Lead Address", accessor: "leadAddress", }, | |
| 18 | + { Header: "Lead Status", accessor: "leadStatus", }, | |
| 19 | + { Header: "Lead Owner", accessor: "leadOwner", }, | |
| 20 | + { Header: "Lead Score", accessor: "leadScore", }, | |
| 21 | + ]; | |
| 22 | + | |
| 23 | + const user = JSON.parse(localStorage.getItem("user")) | |
| 24 | + | |
| 25 | + const uniqueValues = [...new Set(selectedValues)]; | |
| 26 | + | |
| 27 | + const handleSubmit = async () => { | |
| 28 | + try { | |
| 29 | + setIsLoding(true) | |
| 30 | + setFieldValue(fieldName, uniqueValues) | |
| 31 | + onClose() | |
| 32 | + } | |
| 33 | + catch (e) { | |
| 34 | + console.log(e) | |
| 35 | + } | |
| 36 | + finally { | |
| 37 | + setIsLoding(false) | |
| 38 | + } | |
| 39 | + } | |
| 40 | + | |
| 41 | + return ( | |
| 42 | + <Modal onClose={onClose} size='full' isOpen={isOpen} > | |
| 43 | + <ModalOverlay /> | |
| 44 | + <ModalContent> | |
| 45 | + <ModalHeader>Select Lead</ModalHeader> | |
| 46 | + <ModalCloseButton /> | |
| 47 | + <ModalBody> | |
| 48 | + {isLoding ? | |
| 49 | + <Flex justifyContent={'center'} alignItems={'center'} width="100%" > | |
| 50 | + <Spinner /> | |
| 51 | + </Flex> : <LeadTable tableData={data} type='multi' selectedValues={selectedValues} setSelectedValues={setSelectedValues} columnsData={columns} title="Lead" />} | |
| 52 | + </ModalBody> | |
| 53 | + <ModalFooter> | |
| 54 | + <Button variant='brand' onClick={handleSubmit} disabled={isLoding ? true : false} leftIcon={<GiClick />}> {isLoding ? <Spinner /> : 'Select'}</Button> | |
| 55 | + <Button onClick={() => onClose()}>Close</Button> | |
| 56 | + </ModalFooter> | |
| 57 | + </ModalContent> | |
| 58 | + </Modal> | |
| 59 | + | |
| 60 | + ) | |
| 61 | +} | |
| 62 | + | |
| 63 | +export default MultiLeadModel |
+114-0Client/src/components/commonTableModel/OpprtunityModel.js
| @@ -0,0 +1,114 @@ | ||
| 1 | +import { Button, Flex, Modal, ModalBody, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalOverlay } from '@chakra-ui/react' | |
| 2 | +import React, { useEffect, useState } from 'react' | |
| 3 | +import Spinner from 'components/spinner/Spinner' | |
| 4 | +import { GiClick } from "react-icons/gi"; | |
| 5 | +import CommonCheckTable from 'components/reactTable/checktable'; | |
| 6 | +import { useDispatch } from 'react-redux'; | |
| 7 | +import { getApi } from 'services/api'; | |
| 8 | +import { fetchAccountData } from '../../redux/slices/accountSlice'; | |
| 9 | +import { toast } from 'react-toastify'; | |
| 10 | +import moment from 'moment'; | |
| 11 | + | |
| 12 | +const OpprtunityModel = (props) => { | |
| 13 | + const { onClose, isOpen, fieldName, setFieldValue, data } = props | |
| 14 | + const title = "Opprtunities"; | |
| 15 | + const dispatch = useDispatch(); | |
| 16 | + // const [data, setData] = useState([]); | |
| 17 | + | |
| 18 | + const [isLoding, setIsLoding] = useState(false); | |
| 19 | + const [selectedValues, setSelectedValues] = useState([]); | |
| 20 | + | |
| 21 | + const handleSubmit = async () => { | |
| 22 | + try { | |
| 23 | + setIsLoding(true) | |
| 24 | + setFieldValue(fieldName, selectedValues) | |
| 25 | + onClose() | |
| 26 | + } | |
| 27 | + catch (e) { | |
| 28 | + console.log(e) | |
| 29 | + } | |
| 30 | + finally { | |
| 31 | + setIsLoding(false) | |
| 32 | + } | |
| 33 | + } | |
| 34 | + const tableColumns = [ | |
| 35 | + { Header: "#", accessor: "_id", isSortable: false, width: 10 }, | |
| 36 | + { | |
| 37 | + Header: 'Opportunity Name', accessor: 'opportunityName', | |
| 38 | + }, | |
| 39 | + { | |
| 40 | + Header: 'Account Name', accessor: 'accountName' | |
| 41 | + }, | |
| 42 | + { | |
| 43 | + Header: 'Opportunity Amount', accessor: 'opportunityAmount', | |
| 44 | + }, | |
| 45 | + { | |
| 46 | + Header: 'Expected Close Date', accessor: 'expectedCloseDate', cell: (cell) => ( | |
| 47 | + <div>{moment(cell?.value).format("YYYY-MM-DD")}</div> | |
| 48 | + ) | |
| 49 | + }, | |
| 50 | + { | |
| 51 | + Header: 'Sales Stage', accessor: 'salesStage', | |
| 52 | + }, | |
| 53 | + | |
| 54 | + ]; | |
| 55 | + | |
| 56 | + const [columns, setColumns] = useState([...tableColumns]); | |
| 57 | + const [selectedColumns, setSelectedColumns] = useState([...tableColumns]); | |
| 58 | + const dataColumn = tableColumns?.filter(item => selectedColumns?.find(colum => colum?.Header === item.Header)) | |
| 59 | + | |
| 60 | + // const fetchData = async () => { | |
| 61 | + // setIsLoding(true) | |
| 62 | + // const result = await dispatch(fetchAccountData()) | |
| 63 | + | |
| 64 | + // if (result.payload.status === 200) { | |
| 65 | + // setData(result?.payload?.data); | |
| 66 | + // } else { | |
| 67 | + // toast.error("Failed to fetch data", "error"); | |
| 68 | + // } | |
| 69 | + // setIsLoding(false) | |
| 70 | + // } | |
| 71 | + | |
| 72 | + // useEffect(() => { | |
| 73 | + // fetchData() | |
| 74 | + // }, []) | |
| 75 | + | |
| 76 | + return ( | |
| 77 | + <Modal onClose={onClose} size='full' isOpen={isOpen} > | |
| 78 | + <ModalOverlay /> | |
| 79 | + <ModalContent> | |
| 80 | + <ModalHeader>Select Opportunity</ModalHeader> | |
| 81 | + <ModalCloseButton /> | |
| 82 | + <ModalBody> | |
| 83 | + {isLoding ? | |
| 84 | + <Flex justifyContent={'center'} alignItems={'center'} width="100%" > | |
| 85 | + <Spinner /> | |
| 86 | + </Flex> : | |
| 87 | + <CommonCheckTable | |
| 88 | + title={title} | |
| 89 | + isLoding={isLoding} | |
| 90 | + columnData={columns ?? []} | |
| 91 | + // dataColumn={columns ?? []} | |
| 92 | + allData={data ?? []} | |
| 93 | + tableData={data} | |
| 94 | + AdvanceSearch={() => ""} | |
| 95 | + ManageGrid={false} | |
| 96 | + deleteMany={false} | |
| 97 | + selectedValues={selectedValues} | |
| 98 | + setSelectedValues={setSelectedValues} | |
| 99 | + selectType="single" | |
| 100 | + customSearch={false} | |
| 101 | + /> | |
| 102 | + } | |
| 103 | + </ModalBody> | |
| 104 | + <ModalFooter> | |
| 105 | + <Button variant='brand' size='sm' me={2} disabled={isLoding ? true : false} leftIcon={<GiClick />} onClick={handleSubmit}> {isLoding ? <Spinner /> : 'Select'}</Button> | |
| 106 | + <Button variant='outline' size='sm' colorScheme='red' onClick={() => onClose()}>Close</Button> | |
| 107 | + </ModalFooter> | |
| 108 | + </ModalContent> | |
| 109 | + </Modal> | |
| 110 | + | |
| 111 | + ) | |
| 112 | +} | |
| 113 | + | |
| 114 | +export default OpprtunityModel |
+95-0Client/src/components/commonTableModel/UserModel.js
| @@ -0,0 +1,95 @@ | ||
| 1 | +import { Button, Flex, Modal, ModalBody, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalOverlay } from '@chakra-ui/react' | |
| 2 | +import React, { useEffect, useState } from 'react' | |
| 3 | +import Spinner from 'components/spinner/Spinner' | |
| 4 | +import { GiClick } from "react-icons/gi"; | |
| 5 | +import CommonCheckTable from 'components/reactTable/checktable'; | |
| 6 | +import { fetchLeadCustomFiled } from '../../redux/slices/leadCustomFiledSlice'; | |
| 7 | +import { useDispatch } from 'react-redux'; | |
| 8 | +import { getApi } from 'services/api'; | |
| 9 | + | |
| 10 | +const UserModel = (props) => { | |
| 11 | + const { onClose, isOpen, fieldName, setFieldValue, data, isLoding, setIsLoding } = props | |
| 12 | + const title = "Users"; | |
| 13 | + const dispatch = useDispatch(); | |
| 14 | + // const [data, setData] = useState([]); | |
| 15 | + | |
| 16 | + // const [isLoding, setIsLoding] = useState(false); | |
| 17 | + const [leadData, setLeadData] = useState([]); | |
| 18 | + const [selectedValues, setSelectedValues] = useState([]); | |
| 19 | + | |
| 20 | + const handleSubmit = async () => { | |
| 21 | + try { | |
| 22 | + setIsLoding(true) | |
| 23 | + setFieldValue(fieldName, selectedValues) | |
| 24 | + onClose() | |
| 25 | + } | |
| 26 | + catch (e) { | |
| 27 | + console.log(e) | |
| 28 | + } | |
| 29 | + finally { | |
| 30 | + setIsLoding(false) | |
| 31 | + } | |
| 32 | + } | |
| 33 | + const tableColumns = [ | |
| 34 | + { Header: "#", accessor: "_id", isSortable: false, width: 10 }, | |
| 35 | + { | |
| 36 | + Header: 'email Id', accessor: 'username', | |
| 37 | + }, | |
| 38 | + { Header: "first Name", accessor: "firstName", }, | |
| 39 | + { Header: "last Name", accessor: "lastName", }, | |
| 40 | + { Header: "role", accessor: "role", }, | |
| 41 | + ]; | |
| 42 | + | |
| 43 | + // const [columns, setColumns] = useState([...tableColumns]); | |
| 44 | + // const [selectedColumns, setSelectedColumns] = useState([...tableColumns]); | |
| 45 | + // const dataColumn = tableColumns?.filter(item => selectedColumns?.find(colum => colum?.Header === item.Header)) | |
| 46 | + | |
| 47 | + // const fetchData = async () => { | |
| 48 | + // setIsLoding(true) | |
| 49 | + // let result = await getApi('api/user/'); | |
| 50 | + // setData(result?.data?.user); | |
| 51 | + // setIsLoding(false) | |
| 52 | + // } | |
| 53 | + // useEffect(() => { | |
| 54 | + // fetchData() | |
| 55 | + // }, []) | |
| 56 | + | |
| 57 | + return ( | |
| 58 | + <Modal onClose={onClose} size='full' isOpen={isOpen} > | |
| 59 | + <ModalOverlay /> | |
| 60 | + <ModalContent> | |
| 61 | + <ModalHeader>Select User</ModalHeader> | |
| 62 | + <ModalCloseButton /> | |
| 63 | + <ModalBody> | |
| 64 | + {isLoding ? | |
| 65 | + <Flex justifyContent={'center'} alignItems={'center'} width="100%" > | |
| 66 | + <Spinner /> | |
| 67 | + </Flex> : | |
| 68 | + <CommonCheckTable | |
| 69 | + title={title} | |
| 70 | + isLoding={isLoding} | |
| 71 | + columnData={tableColumns ?? []} | |
| 72 | + // dataColumn={columns ?? []} | |
| 73 | + allData={data ?? []} | |
| 74 | + tableData={data} | |
| 75 | + AdvanceSearch={() => ""} | |
| 76 | + ManageGrid={false} | |
| 77 | + deleteMany={false} | |
| 78 | + selectedValues={selectedValues} | |
| 79 | + setSelectedValues={setSelectedValues} | |
| 80 | + selectType="single" | |
| 81 | + customSearch={false} | |
| 82 | + /> | |
| 83 | + } | |
| 84 | + </ModalBody> | |
| 85 | + <ModalFooter> | |
| 86 | + <Button variant='brand' size='sm' me={2} disabled={isLoding ? true : false} leftIcon={<GiClick />} onClick={handleSubmit}> {isLoding ? <Spinner /> : 'Select'}</Button> | |
| 87 | + <Button variant='outline' size='sm' colorScheme='red' onClick={() => onClose()}>Close</Button> | |
| 88 | + </ModalFooter> | |
| 89 | + </ModalContent> | |
| 90 | + </Modal> | |
| 91 | + | |
| 92 | + ) | |
| 93 | +} | |
| 94 | + | |
| 95 | +export default UserModel |
+21-0Client/src/components/countUpComponent/countUpComponent.js
| @@ -0,0 +1,21 @@ | ||
| 1 | +import React, { useState, useEffect } from 'react'; | |
| 2 | + | |
| 3 | +function CountUpComponent({ targetNumber }) { | |
| 4 | + const [count, setCount] = useState(0); | |
| 5 | + | |
| 6 | + useEffect(() => { | |
| 7 | + const interval = setInterval(() => { | |
| 8 | + if (count < targetNumber) { | |
| 9 | + setCount(prevCount => prevCount + 1); | |
| 10 | + } | |
| 11 | + }, targetNumber > 500 ? 0 : 20); // Change the interval duration as needed (in milliseconds) | |
| 12 | + | |
| 13 | + return () => { | |
| 14 | + clearInterval(interval); | |
| 15 | + }; | |
| 16 | + }, [count, targetNumber]); | |
| 17 | + | |
| 18 | + return <span>{typeof targetNumber === "number" ? count : targetNumber}</span> | |
| 19 | +} | |
| 20 | + | |
| 21 | +export default CountUpComponent; |
+45-0Client/src/components/fields/InputField.js
| @@ -0,0 +1,45 @@ | ||
| 1 | +// Chakra imports | |
| 2 | +import { | |
| 3 | + Flex, | |
| 4 | + FormLabel, | |
| 5 | + Input, | |
| 6 | + Text, | |
| 7 | + useColorModeValue, | |
| 8 | +} from "@chakra-ui/react"; | |
| 9 | +// Custom components | |
| 10 | +import React from "react"; | |
| 11 | + | |
| 12 | +export default function Default(props) { | |
| 13 | + const { id, label, extra, placeholder, type, mb, ...rest } = props; | |
| 14 | + // Chakra Color Mode | |
| 15 | + const textColorPrimary = useColorModeValue("secondaryGray.900", "white"); | |
| 16 | + | |
| 17 | + return ( | |
| 18 | + <Flex direction='column' mb={mb ? mb : "30px"}> | |
| 19 | + <FormLabel | |
| 20 | + display='flex' | |
| 21 | + ms='10px' | |
| 22 | + htmlFor={id} | |
| 23 | + fontSize='sm' | |
| 24 | + color={textColorPrimary} | |
| 25 | + fontWeight='bold' | |
| 26 | + _hover={{ cursor: "pointer" }}> | |
| 27 | + {label} | |
| 28 | + <Text fontSize='sm' fontWeight='400' ms='2px'> | |
| 29 | + {extra} | |
| 30 | + </Text> | |
| 31 | + </FormLabel> | |
| 32 | + <Input | |
| 33 | + {...rest} | |
| 34 | + type={type} | |
| 35 | + id={id} | |
| 36 | + fontWeight='500' | |
| 37 | + variant='main' | |
| 38 | + placeholder={placeholder} | |
| 39 | + _placeholder={{ fontWeight: "400", color: "secondaryGray.600" }} | |
| 40 | + h='44px' | |
| 41 | + maxh='44px' | |
| 42 | + /> | |
| 43 | + </Flex> | |
| 44 | + ); | |
| 45 | +} |
+106-0Client/src/components/fields/SwitchField.js
| @@ -0,0 +1,106 @@ | ||
| 1 | +// Chakra imports | |
| 2 | +import { | |
| 3 | + Box, | |
| 4 | + Flex, | |
| 5 | + FormLabel, | |
| 6 | + Switch, | |
| 7 | + Text, | |
| 8 | + useColorModeValue | |
| 9 | +} from "@chakra-ui/react"; | |
| 10 | +// Custom components | |
| 11 | +import React from "react"; | |
| 12 | + | |
| 13 | +export default function Default(props) { | |
| 14 | + const { | |
| 15 | + id, | |
| 16 | + label, | |
| 17 | + isChecked, | |
| 18 | + onChange, | |
| 19 | + desc, | |
| 20 | + textWidth, | |
| 21 | + reversed, | |
| 22 | + fontSize, | |
| 23 | + ...rest | |
| 24 | + } = props; | |
| 25 | + let [checked, setChecked] = React.useState(isChecked); | |
| 26 | + const textColorPrimary = useColorModeValue("secondaryGray.900", "white"); | |
| 27 | + return ( | |
| 28 | + <Box w="100%" fontWeight="500" {...rest}> | |
| 29 | + {reversed ? ( | |
| 30 | + <Flex align="center" borderRadius="16px"> | |
| 31 | + {isChecked ? ( | |
| 32 | + <Switch | |
| 33 | + isChecked={checked} | |
| 34 | + id={id} | |
| 35 | + variant="main" | |
| 36 | + colorScheme="brandScheme" | |
| 37 | + size="md" | |
| 38 | + onChange={() => setChecked(!checked)} | |
| 39 | + /> | |
| 40 | + ) : ( | |
| 41 | + <Switch | |
| 42 | + id={id} | |
| 43 | + variant="main" | |
| 44 | + colorScheme="brandScheme" | |
| 45 | + size="md" | |
| 46 | + /> | |
| 47 | + )} | |
| 48 | + <FormLabel | |
| 49 | + ms="15px" | |
| 50 | + htmlFor={id} | |
| 51 | + _hover={{ cursor: "pointer" }} | |
| 52 | + direction="column" | |
| 53 | + mb="0px" | |
| 54 | + maxW={textWidth ? textWidth : "75%"} | |
| 55 | + > | |
| 56 | + <Text color={textColorPrimary} fontSize="md" fontWeight="500"> | |
| 57 | + {label} | |
| 58 | + </Text> | |
| 59 | + <Text | |
| 60 | + color="secondaryGray.600" | |
| 61 | + fontSize={fontSize ? fontSize : "md"} | |
| 62 | + > | |
| 63 | + {desc} | |
| 64 | + </Text> | |
| 65 | + </FormLabel> | |
| 66 | + </Flex> | |
| 67 | + ) : ( | |
| 68 | + <Flex justify="space-between" align="center" borderRadius="16px"> | |
| 69 | + <FormLabel | |
| 70 | + htmlFor={id} | |
| 71 | + _hover={{ cursor: "pointer" }} | |
| 72 | + direction="column" | |
| 73 | + maxW={textWidth ? textWidth : "75%"} | |
| 74 | + > | |
| 75 | + <Text color={textColorPrimary} fontSize="md" fontWeight="500"> | |
| 76 | + {label} | |
| 77 | + </Text> | |
| 78 | + <Text | |
| 79 | + color="secondaryGray.600" | |
| 80 | + fontSize={fontSize ? fontSize : "md"} | |
| 81 | + > | |
| 82 | + {desc} | |
| 83 | + </Text> | |
| 84 | + </FormLabel> | |
| 85 | + {isChecked && onChange ? ( | |
| 86 | + <Switch | |
| 87 | + isChecked={isChecked} | |
| 88 | + id={id} | |
| 89 | + variant="main" | |
| 90 | + colorScheme="brandScheme" | |
| 91 | + size="md" | |
| 92 | + onChange={onChange} | |
| 93 | + /> | |
| 94 | + ) : ( | |
| 95 | + <Switch | |
| 96 | + id={id} | |
| 97 | + variant="main" | |
| 98 | + colorScheme="brandScheme" | |
| 99 | + size="md" | |
| 100 | + /> | |
| 101 | + )} | |
| 102 | + </Flex> | |
| 103 | + )} | |
| 104 | + </Box> | |
| 105 | + ); | |
| 106 | +} |
+40-0Client/src/components/fixedPlugin/FixedPlugin.js
| @@ -0,0 +1,40 @@ | ||
| 1 | +// Chakra Imports | |
| 2 | +import { Button, Icon, useColorMode } from "@chakra-ui/react"; | |
| 3 | +// Custom Icons | |
| 4 | +import { IoMdMoon, IoMdSunny } from "react-icons/io"; | |
| 5 | +import React from "react"; | |
| 6 | + | |
| 7 | +export default function FixedPlugin(props) { | |
| 8 | + const { ...rest } = props; | |
| 9 | + const { colorMode, toggleColorMode } = useColorMode(); | |
| 10 | + let bgButton = "linear-gradient(135deg, #868CFF 0%, #4318FF 100%)"; | |
| 11 | + | |
| 12 | + return ( | |
| 13 | + <Button | |
| 14 | + {...rest} | |
| 15 | + h='60px' | |
| 16 | + w='60px' | |
| 17 | + zIndex='99' | |
| 18 | + bg={bgButton} | |
| 19 | + position='fixed' | |
| 20 | + variant='no-effects' | |
| 21 | + left={document.documentElement.dir === "rtl" ? "35px" : ""} | |
| 22 | + right={document.documentElement.dir === "rtl" ? "" : "35px"} | |
| 23 | + bottom='30px' | |
| 24 | + border='1px solid' | |
| 25 | + borderColor='#6A53FF' | |
| 26 | + borderRadius='50px' | |
| 27 | + onClick={toggleColorMode} | |
| 28 | + display='flex' | |
| 29 | + p='0px' | |
| 30 | + align='center' | |
| 31 | + justify='center'> | |
| 32 | + <Icon | |
| 33 | + h='24px' | |
| 34 | + w='24px' | |
| 35 | + color='white' | |
| 36 | + as={colorMode === "light" ? IoMdMoon : IoMdSunny} | |
| 37 | + /> | |
| 38 | + </Button> | |
| 39 | + ); | |
| 40 | +} |
+16-0Client/src/components/icons/IconBox.js
| @@ -0,0 +1,16 @@ | ||
| 1 | +import React from "react"; | |
| 2 | +import { Flex } from "@chakra-ui/react"; | |
| 3 | + | |
| 4 | +export default function IconBox(props) { | |
| 5 | + const { icon, ...rest } = props; | |
| 6 | + | |
| 7 | + return ( | |
| 8 | + <Flex | |
| 9 | + alignItems={"center"} | |
| 10 | + justifyContent={"center"} | |
| 11 | + borderRadius={"50%"} | |
| 12 | + {...rest}> | |
| 13 | + {icon} | |
| 14 | + </Flex> | |
| 15 | + ); | |
| 16 | +} |
+639-0Client/src/components/icons/Icons.js
| @@ -0,0 +1,639 @@ | ||
| 1 | +import { createIcon } from "@chakra-ui/icons"; | |
| 2 | + | |
| 3 | +export const AdobexdLogo = createIcon({ | |
| 4 | + displayName: "AdobexdLogo", | |
| 5 | + viewBox: "0 0 24 24", | |
| 6 | + path: ( | |
| 7 | + <g clipPath='url(#clip0)'> | |
| 8 | + <path | |
| 9 | + d='M4.042 0h12.916A3.539 3.539 0 0120.5 3.542v12.416a3.539 3.539 0 01-3.542 3.542H4.042A3.539 3.539 0 01.5 15.958V3.542A3.539 3.539 0 014.042 0z' | |
| 10 | + fill='#470137' | |
| 11 | + /> | |
| 12 | + <path | |
| 13 | + d='M11.017 5.124l-2.5 4.125 2.666 4.375a.143.143 0 01.017.1c-.008.034-.042.009-.092.017H9.2c-.133 0-.225-.008-.283-.092-.175-.35-.359-.691-.534-1.041a33.608 33.608 0 00-.566-1.05c-.2-.359-.4-.717-.6-1.084H7.2c-.175.359-.367.717-.558 1.075-.192.359-.384.717-.567 1.067-.192.35-.383.708-.575 1.05-.033.083-.1.092-.192.092H3.475c-.033 0-.058.016-.058-.025a.116.116 0 01.016-.092l2.592-4.25L3.5 5.116c-.025-.033-.033-.067-.017-.083.017-.025.05-.034.084-.034h1.891c.042 0 .084.009.117.017a.236.236 0 01.083.075c.159.358.342.717.534 1.075.2.358.391.708.6 1.058.2.35.383.7.558 1.059h.017c.175-.367.358-.725.541-1.075.184-.35.375-.7.567-1.05.192-.35.375-.709.558-1.05.009-.034.025-.067.05-.084a.159.159 0 01.109-.016h1.758a.077.077 0 01.091.058c.009.008-.008.042-.024.058zM14.866 13.917a4.144 4.144 0 01-1.791-.375 2.906 2.906 0 01-1.259-1.134c-.308-.508-.458-1.141-.458-1.9a3.423 3.423 0 011.783-3.05c.584-.325 1.284-.483 2.109-.483.041 0 .1 0 .175.008.075.009.158.009.258.017V4.367c0-.059.025-.092.083-.092h1.692c.042-.008.075.025.083.058v7.95c0 .15.009.317.017.5.017.175.025.342.033.484 0 .058-.025.108-.083.133a6.365 6.365 0 01-1.358.4 7.35 7.35 0 01-1.284.117zm.817-1.667V8.583c-.075-.016-.15-.033-.225-.041-.092-.009-.183-.017-.275-.017-.325 0-.65.067-.942.217-.283.141-.525.35-.708.616-.183.267-.275.625-.275 1.059-.008.291.042.583.142.858.083.225.208.425.375.592.158.15.35.266.566.333.225.075.459.108.692.108.125 0 .242-.008.35-.016a.73.73 0 00.3-.042z' | |
| 14 | + fill='#FF61F6' | |
| 15 | + /> | |
| 16 | + </g> | |
| 17 | + ), | |
| 18 | +}); | |
| 19 | +export const AppleLogo = createIcon({ | |
| 20 | + displayName: "AppleLogo", | |
| 21 | + viewBox: "0 0 15 18", | |
| 22 | + path: ( | |
| 23 | + <g width='15' height='18' fill='none' xmlns='http://www.w3.org/2000/svg'> | |
| 24 | + <g clipPath='url(#Apple_logo_black 1__a)'> | |
| 25 | + <path | |
| 26 | + d='M12.507 17.28c-.966.947-2.031.8-3.046.353-1.08-.456-2.067-.484-3.207 0-1.42.619-2.173.439-3.029-.353C-1.602 12.258-.89 4.608 4.597 4.32c1.33.072 2.263.743 3.046.8 1.166-.24 2.28-.926 3.528-.836 1.498.122 2.619.72 3.367 1.795-3.082 1.872-2.352 5.976.479 7.128-.566 1.503-1.293 2.988-2.512 4.086l.002-.013ZM7.537 4.266C7.392 2.034 9.183.198 11.242.018c.283 2.574-2.316 4.5-3.705 4.248Z' | |
| 27 | + fill='currentColor' | |
| 28 | + /> | |
| 29 | + </g> | |
| 30 | + <defs> | |
| 31 | + <clipPath id='Apple_logo_black 1__a'> | |
| 32 | + <path fill='currentColor' d='M0 0h15v18H0z' /> | |
| 33 | + </clipPath> | |
| 34 | + </defs> | |
| 35 | + </g> | |
| 36 | + ), | |
| 37 | +}); | |
| 38 | +export const AndroidLogo = createIcon({ | |
| 39 | + displayName: "AndroidLogo", | |
| 40 | + viewBox: "0 0 16 18", | |
| 41 | + path: ( | |
| 42 | + <g width='16' height='18' fill='none' xmlns='http://www.w3.org/2000/svg'> | |
| 43 | + <path | |
| 44 | + d='M4.9.051a.571.571 0 0 0-.263.066.518.518 0 0 0-.214.715l.595 1.029C3.584 2.71 2.634 4.16 2.634 5.815v.307a1.525 1.525 0 0 0-1.132-.494C.672 5.628 0 6.27 0 7.064v4.426c0 .793.673 1.437 1.502 1.437.452 0 .857-.192 1.132-.495v.62c0 .836.715 1.52 1.588 1.52h.404v1.969c0 .793.672 1.436 1.501 1.436.829 0 1.502-.643 1.502-1.436v-1.97h.697v1.97c0 .793.673 1.436 1.502 1.436.828 0 1.501-.643 1.501-1.436v-1.97h.403c.874 0 1.588-.683 1.588-1.519v-.62c.276.303.68.495 1.133.495.829 0 1.502-.644 1.502-1.437V7.064c0-.793-.673-1.436-1.502-1.436-.452 0-.857.191-1.133.494v-.307c0-1.655-.952-3.104-2.385-3.953l.596-1.03a.518.518 0 0 0-.214-.715.571.571 0 0 0-.748.205L9.945 1.4a5.906 5.906 0 0 0-1.968-.333c-.692 0-1.357.117-1.967.332L5.386.322a.556.556 0 0 0-.487-.27Z' | |
| 45 | + fill='#fffcurrentColor' | |
| 46 | + /> | |
| 47 | + <path | |
| 48 | + d='M1.501 6.002c-.618 0-1.11.47-1.11 1.062v4.426c0 .592.492 1.063 1.11 1.063.618 0 1.11-.471 1.11-1.063V7.064c0-.591-.492-1.062-1.11-1.062Zm12.952 0c-.619 0-1.11.47-1.11 1.062v4.426c0 .592.491 1.063 1.11 1.063.618 0 1.11-.471 1.11-1.063V7.064c0-.591-.492-1.062-1.11-1.062Zm-8.327 5.05c-.618 0-1.11.471-1.11 1.063v4.426c0 .591.492 1.062 1.11 1.062.618 0 1.11-.47 1.11-1.062v-4.426c0-.592-.492-1.063-1.11-1.063Zm3.7 0c-.618 0-1.11.471-1.11 1.063v4.426c0 .591.492 1.062 1.11 1.062.619 0 1.111-.47 1.111-1.062v-4.426c0-.592-.492-1.063-1.11-1.063Z' | |
| 49 | + fill='currentColor' | |
| 50 | + /> | |
| 51 | + <path | |
| 52 | + d='M4.9.426a.16.16 0 0 0-.074.02.141.141 0 0 0-.061.204l.782 1.352c-1.505.75-2.522 2.174-2.524 3.808h9.905c-.003-1.634-1.02-3.058-2.524-3.808L11.186.65c.044-.076.018-.163-.061-.205a.16.16 0 0 0-.075-.02.157.157 0 0 0-.14.079l-.792 1.367a5.483 5.483 0 0 0-2.143-.43c-.767 0-1.494.154-2.142.43L5.04.504A.157.157 0 0 0 4.9.426ZM3.024 6.184v6.868c0 .635.534 1.146 1.198 1.146h7.51c.663 0 1.197-.511 1.197-1.146V6.184H3.023Z' | |
| 53 | + fill='currentColor' | |
| 54 | + /> | |
| 55 | + <path | |
| 56 | + d='M5.69 3.285a.407.407 0 0 0-.413.395c0 .216.187.395.414.395a.407.407 0 0 0 .413-.395.407.407 0 0 0-.413-.395Zm4.572 0a.407.407 0 0 0-.414.395c0 .216.188.395.414.395a.407.407 0 0 0 .413-.395.407.407 0 0 0-.413-.395Z' | |
| 57 | + fill='currentColor' | |
| 58 | + /> | |
| 59 | + </g> | |
| 60 | + ), | |
| 61 | +}); | |
| 62 | +export const WindowsLogo = createIcon({ | |
| 63 | + displayName: "WindowsLogo", | |
| 64 | + viewBox: "0 0 19 18", | |
| 65 | + path: ( | |
| 66 | + <g width='19' height='18' fill='none' xmlns='http://www.w3.org/2000/svg'> | |
| 67 | + <path | |
| 68 | + d='m0 2.537 7.705-.994.004 7.04-7.702.042L0 2.537Zm7.701 6.858.006 7.047L.006 15.44V9.348l7.695.047Zm.935-7.982L18.852 0v8.494l-10.216.077V1.413ZM18.854 9.46l-.002 8.456L8.636 16.55l-.015-7.106 10.233.016Z' | |
| 69 | + fill='currentColor' | |
| 70 | + /> | |
| 71 | + </g> | |
| 72 | + ), | |
| 73 | +}); | |
| 74 | + | |
| 75 | +export const AtlassianLogo = createIcon({ | |
| 76 | + displayName: "AtlassianLogo", | |
| 77 | + viewBox: "0 0 24 24", | |
| 78 | + path: ( | |
| 79 | + <g> | |
| 80 | + <path | |
| 81 | + d='M6.427 9.239a.57.57 0 00-.798-.108.591.591 0 00-.167.209l-4.9 9.803a.586.586 0 00.524.847h6.827a.562.562 0 00.523-.324c1.474-3.043.58-7.67-2.01-10.427z' | |
| 82 | + fill='#2684FF' | |
| 83 | + /> | |
| 84 | + <path | |
| 85 | + d='M10.028.318a12.932 12.932 0 00-.755 12.765l3.292 6.583a.586.586 0 00.523.324h6.826a.583.583 0 00.586-.585c0-.091-.02-.18-.06-.262L11.024.315a.552.552 0 00-.997 0v.003z' | |
| 86 | + fill='#2684FF' | |
| 87 | + /> | |
| 88 | + </g> | |
| 89 | + ), | |
| 90 | +}); | |
| 91 | + | |
| 92 | +export const CartIcon = createIcon({ | |
| 93 | + displayName: "CartIcon", | |
| 94 | + viewBox: "0 0 24 24", | |
| 95 | + path: ( | |
| 96 | + <path | |
| 97 | + fill='currentColor' | |
| 98 | + d='M7.984 19.937a1.406 1.406 0 100-2.812 1.406 1.406 0 000 2.812zM17.828 19.937a1.406 1.406 0 100-2.812 1.406 1.406 0 000 2.812zM20.324 5.558a1.051 1.051 0 00-.815-.386H6.134l-.27-1.528a.703.703 0 00-.692-.581H2.359a.703.703 0 000 1.406h2.223L6.59 15.841a.703.703 0 00.692.581h11.25a.703.703 0 100-1.406H7.871l-.248-1.406h10.62a1.057 1.057 0 001.035-.848l1.266-6.328a1.055 1.055 0 00-.22-.876z' | |
| 99 | + /> | |
| 100 | + ), | |
| 101 | +}); | |
| 102 | + | |
| 103 | +export const ClockIcon = createIcon({ | |
| 104 | + displayName: "ClockIcon", | |
| 105 | + viewBox: "0 0 24 24", | |
| 106 | + path: ( | |
| 107 | + <g> | |
| 108 | + <g> | |
| 109 | + <rect fill='none' height='24' width='24' /> | |
| 110 | + </g> | |
| 111 | + <g> | |
| 112 | + <g> | |
| 113 | + <g> | |
| 114 | + <path | |
| 115 | + fill='currentColor' | |
| 116 | + d='M12,2C6.5,2,2,6.5,2,12s4.5,10,10,10s10-4.5,10-10S17.5,2,12,2z M16.2,16.2L11,13V7h1.5v5.2l4.5,2.7L16.2,16.2z' | |
| 117 | + /> | |
| 118 | + </g> | |
| 119 | + </g> | |
| 120 | + </g> | |
| 121 | + </g> | |
| 122 | + ), | |
| 123 | +}); | |
| 124 | + | |
| 125 | +export const CreditIcon = createIcon({ | |
| 126 | + displayName: "CreditIcon", | |
| 127 | + viewBox: "0 0 24 24", | |
| 128 | + path: ( | |
| 129 | + <path | |
| 130 | + fill='currentColor' | |
| 131 | + d='M1.406 17.023a2.461 2.461 0 002.461 2.46h14.766a2.461 2.461 0 002.46-2.46v-6.768H1.407v6.768zm2.9-3.34a1.318 1.318 0 011.319-1.318h2.11a1.318 1.318 0 011.318 1.318v.879a1.318 1.318 0 01-1.319 1.318H5.625a1.318 1.318 0 01-1.318-1.318v-.879zM18.633 4.014H3.867a2.46 2.46 0 00-2.46 2.461v1.143h19.687V6.475a2.46 2.46 0 00-2.461-2.46z' | |
| 132 | + /> | |
| 133 | + ), | |
| 134 | +}); | |
| 135 | + | |
| 136 | +export const DashboardLogo = createIcon({ | |
| 137 | + displayName: "DashboardLogo", | |
| 138 | + viewBox: "0 0 1000 257", | |
| 139 | + path: ( | |
| 140 | + <g width='998' height='257' viewBox='0 0 998 257' fill='none'> | |
| 141 | + <g clipPath='url(#clip0)'> | |
| 142 | + <path | |
| 143 | + d='M388.5 115.302C406.112 115.302 413.966 126.726 416.584 136.96L441.336 127.916C436.576 109.352 419.44 89.836 388.262 89.836C354.942 89.836 329 115.54 329 151.24C329 186.464 354.942 212.882 388.976 212.882C419.44 212.882 436.814 193.128 442.288 175.04L418.012 166.234C415.394 175.04 407.064 187.654 388.976 187.654C371.602 187.654 356.608 174.564 356.608 151.24C356.608 127.916 371.602 115.302 388.5 115.302Z' | |
| 144 | + fill='currentColor' | |
| 145 | + /> | |
| 146 | + <path | |
| 147 | + d='M484.894 141.244C485.37 126.488 493.7 115.064 508.932 115.064C526.306 115.064 532.732 126.488 532.732 140.768V209.312H560.34V136.008C560.34 110.542 546.536 90.074 517.976 90.074C505.838 90.074 492.748 94.358 484.894 104.592V37H457.286V209.312H484.894V141.244Z' | |
| 148 | + fill='currentColor' | |
| 149 | + /> | |
| 150 | + <path | |
| 151 | + d='M577.29 177.896C577.29 196.222 592.284 212.882 616.56 212.882C635.362 212.882 646.786 203.362 652.26 194.556C652.26 203.838 653.212 208.598 653.45 209.312H679.154C678.916 208.122 677.726 201.22 677.726 190.748V133.152C677.726 110.066 664.16 89.836 628.46 89.836C599.9 89.836 581.812 107.686 579.67 127.678L604.898 133.39C606.088 121.728 614.18 112.446 628.698 112.446C643.93 112.446 650.594 120.3 650.594 130.058C650.594 134.104 648.69 137.436 642.026 138.388L612.276 142.91C592.522 145.766 577.29 157.19 577.29 177.896ZM621.796 191.224C610.848 191.224 604.898 184.084 604.898 176.23C604.898 166.71 611.8 161.95 620.368 160.522L650.594 156V161.236C650.594 183.846 637.266 191.224 621.796 191.224Z' | |
| 152 | + fill='currentColor' | |
| 153 | + /> | |
| 154 | + <path | |
| 155 | + d='M810.108 93.406H773.456L728.95 140.292V37H701.58V209.312H728.95V176.944L743.23 161.95L777.264 209.312H811.06L762.508 142.434L810.108 93.406Z' | |
| 156 | + fill='currentColor' | |
| 157 | + /> | |
| 158 | + <path | |
| 159 | + d='M889.349 92.692C888.159 92.454 885.303 91.978 881.971 91.978C866.739 91.978 853.887 99.356 848.413 111.97V93.406H821.519V209.312H849.127V154.096C849.127 132.438 858.885 120.062 880.305 120.062C883.161 120.062 886.255 120.3 889.349 120.776V92.692Z' | |
| 160 | + fill='currentColor' | |
| 161 | + /> | |
| 162 | + <path | |
| 163 | + d='M895.968 177.896C895.968 196.222 910.962 212.882 935.238 212.882C954.04 212.882 965.464 203.362 970.938 194.556C970.938 203.838 971.89 208.598 972.128 209.312H997.832C997.594 208.122 996.404 201.22 996.404 190.748V133.152C996.404 110.066 982.838 89.836 947.138 89.836C918.578 89.836 900.49 107.686 898.348 127.678L923.576 133.39C924.766 121.728 932.858 112.446 947.376 112.446C962.608 112.446 969.272 120.3 969.272 130.058C969.272 134.104 967.368 137.436 960.704 138.388L930.954 142.91C911.2 145.766 895.968 157.19 895.968 177.896ZM940.474 191.224C929.526 191.224 923.576 184.084 923.576 176.23C923.576 166.71 930.478 161.95 939.046 160.522L969.272 156V161.236C969.272 183.846 955.944 191.224 940.474 191.224Z' | |
| 164 | + fill='currentColor' | |
| 165 | + /> | |
| 166 | + <rect width='257' height='257' rx='128.5' fill='#4FD1C5' /> | |
| 167 | + <path | |
| 168 | + d='M69.5584 133.985L157.15 46.9959C158.787 45.3708 161.42 47.3484 160.315 49.3729L127.714 109.125C126.987 110.457 127.951 112.083 129.47 112.083H185.809C187.624 112.083 188.501 114.306 187.174 115.545L88.4456 207.687C86.6753 209.339 84.0405 207.011 85.4617 205.051L132.197 140.578C133.156 139.256 132.211 137.404 130.578 137.404H70.9677C69.1826 137.404 68.2917 135.243 69.5584 133.985Z' | |
| 169 | + fill='white' | |
| 170 | + /> | |
| 171 | + </g> | |
| 172 | + <defs> | |
| 173 | + <linearGradient | |
| 174 | + id='paint0_linear' | |
| 175 | + x1='128.5' | |
| 176 | + y1='0' | |
| 177 | + x2='128.5' | |
| 178 | + y2='257' | |
| 179 | + gradientUnits='userSpaceOnUse'> | |
| 180 | + <stop stop-color='#7BCBD4' /> | |
| 181 | + <stop offset='1' stop-color='#29C6B7' /> | |
| 182 | + </linearGradient> | |
| 183 | + <clipPath id='clip0'> | |
| 184 | + <rect width='997.832' height='257' fill='white' /> | |
| 185 | + </clipPath> | |
| 186 | + </defs> | |
| 187 | + </g> | |
| 188 | + ), | |
| 189 | +}); | |
| 190 | + | |
| 191 | +export const DashboardLogoWhite = createIcon({ | |
| 192 | + displayName: "DashboardLogo", | |
| 193 | + viewBox: "0 0 163.5 42", | |
| 194 | + path: ( | |
| 195 | + <g fill='none'> | |
| 196 | + <path | |
| 197 | + d='M63.452 19.292c2.877 0 4.16 1.911 4.587 3.623l4.043-1.513c-.777-3.106-3.576-6.371-8.668-6.371-5.443 0-9.68 4.3-9.68 10.274 0 5.893 4.237 10.313 9.796 10.313 4.976 0 7.813-3.305 8.707-6.331l-3.965-1.473c-.427 1.473-1.788 3.583-4.742 3.583-2.837 0-5.286-2.19-5.286-6.092 0-3.903 2.449-6.013 5.208-6.013zM79.196 23.632c.078-2.469 1.438-4.38 3.926-4.38 2.838 0 3.887 1.911 3.887 4.3v11.47h4.51V22.755c0-4.26-2.255-7.685-6.92-7.685-1.982 0-4.12.716-5.403 2.429V6.19h-4.509v28.831h4.51V23.632zM94.287 29.765c0 3.066 2.449 5.853 6.414 5.853 3.071 0 4.936-1.592 5.83-3.066 0 1.553.156 2.35.195 2.47h4.198c-.039-.2-.233-1.355-.233-3.107v-9.637c0-3.862-2.216-7.247-8.047-7.247-4.664 0-7.619 2.986-7.968 6.332l4.12.955c.194-1.951 1.516-3.504 3.887-3.504 2.488 0 3.576 1.314 3.576 2.947 0 .677-.311 1.234-1.399 1.393l-4.859.757c-3.226.478-5.714 2.39-5.714 5.854zm7.269 2.23c-1.788 0-2.76-1.195-2.76-2.509 0-1.593 1.127-2.39 2.527-2.628l4.936-.757v.876c0 3.783-2.176 5.018-4.703 5.018zM132.312 15.628h-5.986l-7.269 7.845V6.191h-4.47v28.83h4.47v-5.416l2.332-2.508 5.559 7.924h5.52l-7.93-11.19 7.774-8.203zM145.254 15.509a6.3 6.3 0 00-1.205-.12c-2.487 0-4.587 1.235-5.481 3.345v-3.106h-4.392v19.393h4.509v-9.238c0-3.624 1.594-5.695 5.092-5.695.467 0 .972.04 1.477.12v-4.7zM146.335 29.765c0 3.066 2.449 5.853 6.414 5.853 3.071 0 4.937-1.592 5.831-3.066 0 1.553.156 2.35.194 2.47h4.199c-.039-.2-.234-1.355-.234-3.107v-9.637c0-3.862-2.215-7.247-8.046-7.247-4.665 0-7.619 2.986-7.969 6.332l4.121.955c.194-1.951 1.516-3.504 3.887-3.504 2.488 0 3.576 1.314 3.576 2.947 0 .677-.311 1.234-1.399 1.393l-4.859.757c-3.227.478-5.715 2.39-5.715 5.854zm7.269 2.23c-1.788 0-2.759-1.195-2.759-2.509 0-1.593 1.127-2.39 2.526-2.628l4.937-.757v.876c0 3.783-2.177 5.018-4.704 5.018zM41.975 21.5C41.975 9.626 32.578 0 20.987 0 9.398 0 0 9.626 0 21.5S9.396 43 20.988 43c11.59 0 20.987-9.626 20.987-21.5z' | |
| 198 | + fill='#fff' | |
| 199 | + /> | |
| 200 | + <path | |
| 201 | + d='M11.36 22.418L25.668 7.863c.267-.272.697.06.517.398l-5.325 9.997c-.119.223.039.495.287.495h9.202c.296 0 .44.372.223.58L14.446 34.748c-.29.277-.72-.113-.488-.44l7.633-10.788c.157-.221.003-.531-.264-.531H11.59c-.292 0-.437-.362-.23-.572z' | |
| 202 | + fill='#3BCBBE' | |
| 203 | + /> | |
| 204 | + </g> | |
| 205 | + ), | |
| 206 | +}); | |
| 207 | + | |
| 208 | +export const DocumentIcon = createIcon({ | |
| 209 | + displayName: "DocumentIcon", | |
| 210 | + viewBox: "0 0 24 24", | |
| 211 | + path: ( | |
| 212 | + <g> | |
| 213 | + <path | |
| 214 | + fill='currentColor' | |
| 215 | + d='M18.809 10.344h-6.153a2.11 2.11 0 01-2.11-2.11V2.083a.176.176 0 00-.175-.176H6.328A2.812 2.812 0 003.516 4.72v14.063a2.812 2.812 0 002.812 2.812h9.844a2.812 2.812 0 002.812-2.813V10.52a.176.176 0 00-.175-.176z' | |
| 216 | + /> | |
| 217 | + <path | |
| 218 | + fill='currentColor' | |
| 219 | + d='M18.423 8.789l-6.32-6.32a.088.088 0 00-.15.062v5.705a.703.703 0 00.703.703h5.705a.088.088 0 00.062-.15z' | |
| 220 | + /> | |
| 221 | + </g> | |
| 222 | + ), | |
| 223 | +}); | |
| 224 | + | |
| 225 | +export const GlobeIcon = createIcon({ | |
| 226 | + displayName: "GlobeIcon", | |
| 227 | + viewBox: "0 0 24 24", | |
| 228 | + path: ( | |
| 229 | + <g> | |
| 230 | + <path | |
| 231 | + stroke='currentColor' | |
| 232 | + fill='transparent' | |
| 233 | + d='M11.25 2.109a9.14 9.14 0 100 18.281 9.14 9.14 0 000-18.281z' | |
| 234 | + stroke-width='.75' | |
| 235 | + stroke-miterlimit='10' | |
| 236 | + /> | |
| 237 | + <path | |
| 238 | + stroke='currentColor' | |
| 239 | + fill='transparent' | |
| 240 | + d='M11.25 2.109C8.698 2.109 6.3 6.2 6.3 11.249c0 5.048 2.4 9.14 4.951 9.14 2.552 0 4.951-4.092 4.951-9.14 0-5.048-2.399-9.14-4.95-9.14z' | |
| 241 | + stroke-width='.75' | |
| 242 | + stroke-miterlimit='10' | |
| 243 | + /> | |
| 244 | + <path | |
| 245 | + stroke='currentColor' | |
| 246 | + fill='transparent' | |
| 247 | + d='M5.156 5.156C6.836 6.349 8.952 7.06 11.25 7.06c2.298 0 4.414-.711 6.094-1.904' | |
| 248 | + /> | |
| 249 | + <path | |
| 250 | + stroke='currentColor' | |
| 251 | + fill='transparent' | |
| 252 | + d='M5.156 5.156C6.836 6.349 8.952 7.06 11.25 7.06c2.298 0 4.414-.711 6.094-1.904M17.344 17.344c-1.68-1.193-3.796-1.904-6.094-1.904-2.298 0-4.413.711-6.094 1.904' | |
| 253 | + stroke-width='.75' | |
| 254 | + stroke-linecap='round' | |
| 255 | + stroke-linejoin='round' | |
| 256 | + /> | |
| 257 | + <path | |
| 258 | + stroke='currentColor' | |
| 259 | + fill='transparent' | |
| 260 | + d='M11.25 2.109v18.28M20.39 11.249H2.11' | |
| 261 | + stroke-width='.75' | |
| 262 | + stroke-miterlimit='10' | |
| 263 | + /> | |
| 264 | + </g> | |
| 265 | + ), | |
| 266 | +}); | |
| 267 | + | |
| 268 | +export const HelpIcon = createIcon({ | |
| 269 | + displayName: "HelpIcon", | |
| 270 | + viewBox: "0 0 24 24", | |
| 271 | + path: ( | |
| 272 | + <path | |
| 273 | + fill='currentColor' | |
| 274 | + d='M11.25 0C5.04 0 0 5.04 0 11.25S5.04 22.5 11.25 22.5 22.5 17.46 22.5 11.25 17.46 0 11.25 0zm-.352 17.813a1.172 1.172 0 110-2.344 1.172 1.172 0 010 2.344zm1.96-5.977c-.95.637-1.08 1.222-1.08 1.758a.82.82 0 11-1.641 0c0-1.284.59-2.305 1.806-3.121 1.13-.758 1.768-1.239 1.768-2.295 0-.718-.41-1.264-1.26-1.668-.199-.095-.644-.187-1.191-.18-.687.008-1.22.172-1.63.503-.775.623-.84 1.302-.84 1.312a.821.821 0 11-1.642-.08c.007-.142.106-1.425 1.452-2.507.698-.562 1.585-.854 2.636-.866.745-.01 1.444.117 1.918.34 1.418.672 2.198 1.79 2.198 3.146 0 1.982-1.325 2.872-2.494 3.658z' | |
| 275 | + /> | |
| 276 | + ), | |
| 277 | +}); | |
| 278 | + | |
| 279 | +export const HomeIcon = createIcon({ | |
| 280 | + displayName: "HomeIcon", | |
| 281 | + viewBox: "0 0 24 24", | |
| 282 | + path: ( | |
| 283 | + <g> | |
| 284 | + <path | |
| 285 | + fill='currentColor' | |
| 286 | + d='M11.494 4.951a.351.351 0 00-.486 0l-8.09 7.729a.352.352 0 00-.109.254v7.254a1.406 1.406 0 001.405 1.406h4.223a.703.703 0 00.704-.703v-5.976a.351.351 0 01.351-.352h3.516a.351.351 0 01.351.352v5.976a.703.703 0 00.704.703h4.22a1.407 1.407 0 001.407-1.406v-7.254a.35.35 0 00-.108-.254L11.494 4.95z' | |
| 287 | + /> | |
| 288 | + <path | |
| 289 | + fill='currentColor' | |
| 290 | + d='M21.574 11.23l-3.287-3.144V3.314a.703.703 0 00-.703-.703h-2.11a.703.703 0 00-.703.703V4.72l-2.545-2.434c-.239-.24-.593-.378-.976-.378-.38 0-.734.138-.972.379L.93 11.23a.717.717 0 00-.058.983.703.703 0 001.018.046l9.119-8.713a.352.352 0 01.486 0l9.12 8.713a.703.703 0 00.992-.019c.27-.28.248-.74-.033-1.01z' | |
| 291 | + /> | |
| 292 | + </g> | |
| 293 | + ), | |
| 294 | +}); | |
| 295 | + | |
| 296 | +export const InvisionLogo = createIcon({ | |
| 297 | + displayName: "InvisionLogo", | |
| 298 | + viewBox: "0 0 24 24", | |
| 299 | + path: ( | |
| 300 | + <g clipPath='url(#clip0)'> | |
| 301 | + <path | |
| 302 | + d='M18.687 0H2.313A1.813 1.813 0 00.5 1.811v16.374C.5 19.188 1.312 20 2.313 20h16.374a1.813 1.813 0 001.813-1.813V1.812A1.813 1.813 0 0018.687 0z' | |
| 303 | + fill='#DC395F' | |
| 304 | + /> | |
| 305 | + <path | |
| 306 | + d='M7.184 6.293c.665 0 1.222-.522 1.222-1.204 0-.683-.557-1.204-1.222-1.204-.665 0-1.222.521-1.222 1.204 0 .682.557 1.204 1.222 1.204zM4.65 12.739a4.134 4.134 0 00-.108.905c0 1.06.575 1.764 1.797 1.764 1.013 0 1.834-.602 2.426-1.573l-.361 1.449h2.012l1.15-4.612c.287-1.168.844-1.774 1.689-1.774.665 0 1.078.413 1.078 1.096 0 .197-.018.413-.09.646l-.593 2.12c-.09.306-.126.611-.126.899 0 1.006.593 1.742 1.833 1.742 1.06 0 1.904-.682 2.371-2.317l-.79-.305c-.395 1.095-.737 1.293-1.006 1.293-.27 0-.414-.18-.414-.538 0-.162.037-.342.09-.558l.575-2.065c.144-.485.198-.915.198-1.31 0-1.546-.934-2.352-2.066-2.352-1.06 0-2.138.956-2.677 1.962l.395-1.806H8.962L8.53 8.996h1.438l-.885 3.544c-.695 1.545-1.972 1.57-2.132 1.534-.264-.06-.432-.159-.432-.5 0-.198.036-.482.126-.823l1.348-5.346H4.579l-.431 1.591h1.419L4.65 12.74' | |
| 307 | + fill='#fff' | |
| 308 | + /> | |
| 309 | + </g> | |
| 310 | + ), | |
| 311 | +}); | |
| 312 | + | |
| 313 | +export const JiraLogo = createIcon({ | |
| 314 | + displayName: "JiraLogo", | |
| 315 | + viewBox: "0 0 24 24", | |
| 316 | + path: ( | |
| 317 | + <g clipPath='url(#clip0)'> | |
| 318 | + <path | |
| 319 | + d='M20.26 10.42l-8.863-8.93-.86-.865-6.67 6.722L.814 10.42a.827.827 0 000 1.162l6.096 6.14 3.627 3.654 6.67-6.722.105-.104 2.947-2.964a.824.824 0 000-1.166zm-9.722 3.649L7.493 11l3.045-3.068L13.582 11l-3.044 3.068z' | |
| 320 | + fill='#2684FF' | |
| 321 | + /> | |
| 322 | + <path | |
| 323 | + d='M10.537 7.932a5.184 5.184 0 01-1.502-3.637A5.185 5.185 0 0110.515.65L3.852 7.36l3.626 3.654 3.06-3.082z' | |
| 324 | + fill='url(#paint0_linear)' | |
| 325 | + /> | |
| 326 | + <path | |
| 327 | + d='M13.59 10.992l-3.053 3.076a5.186 5.186 0 011.502 3.653c0 1.37-.54 2.683-1.502 3.652l6.682-6.728-3.63-3.653z' | |
| 328 | + fill='url(#paint1_linear)' | |
| 329 | + /> | |
| 330 | + </g> | |
| 331 | + ), | |
| 332 | +}); | |
| 333 | + | |
| 334 | +export const MastercardIcon = createIcon({ | |
| 335 | + displayName: "MastercardIcon", | |
| 336 | + viewBox: "0 0 24 24", | |
| 337 | + path: ( | |
| 338 | + <svg | |
| 339 | + width='24' | |
| 340 | + height='20' | |
| 341 | + viewBox='0 0 21 15' | |
| 342 | + fill='none' | |
| 343 | + xmlns='http://www.w3.org/2000/svg'> | |
| 344 | + <circle cx='6.63158' cy='7.49999' r='6.63158' fill='#EB001B' /> | |
| 345 | + <circle cx='14.3686' cy='7.49999' r='6.63158' fill='#F79E1B' /> | |
| 346 | + </svg> | |
| 347 | + ), | |
| 348 | +}); | |
| 349 | + | |
| 350 | +export const PayPalIcon = createIcon({ | |
| 351 | + displayName: "PayPalIcon", | |
| 352 | + viewBox: "0 0 24 24", | |
| 353 | + path: ( | |
| 354 | + <g> | |
| 355 | + <path | |
| 356 | + fill='currentColor' | |
| 357 | + d='M16.179 2.532C15.142 1.357 13.265.854 10.863.854H3.895a.997.997 0 00-.986.834L.007 19.945a.595.595 0 00.591.686H4.9l1.08-6.8-.033.215a.993.993 0 01.983-.834h2.044c4.017 0 7.16-1.619 8.08-6.3.027-.14.07-.406.07-.406.262-1.732-.001-2.907-.945-3.974z' | |
| 358 | + /> | |
| 359 | + <path | |
| 360 | + fill='currentColor' | |
| 361 | + d='M18.213 7.393c-.998 4.61-4.184 7.048-9.24 7.048H7.142L5.773 23.1h2.973a.87.87 0 00.862-.731l.035-.184.684-4.297.044-.237a.87.87 0 01.86-.731h.544c3.514 0 6.264-1.416 7.068-5.51.322-1.644.166-3.021-.63-4.017z' | |
| 362 | + /> | |
| 363 | + </g> | |
| 364 | + ), | |
| 365 | +}); | |
| 366 | + | |
| 367 | +export const PersonIcon = createIcon({ | |
| 368 | + displayName: "PersonIcon", | |
| 369 | + viewBox: "0 0 24 24", | |
| 370 | + path: ( | |
| 371 | + <path | |
| 372 | + fill='currentColor' | |
| 373 | + d='M14.618 3.338c-.855-.924-2.05-1.432-3.368-1.432-1.325 0-2.524.505-3.375 1.423-.86.928-1.28 2.188-1.181 3.55.195 2.686 2.239 4.87 4.556 4.87s4.358-2.184 4.556-4.87c.1-1.349-.322-2.607-1.188-3.541zM18.984 21.592H3.515a1.363 1.363 0 01-1.063-.489 1.552 1.552 0 01-.316-1.279c.371-2.058 1.529-3.786 3.348-5 1.616-1.076 3.664-1.67 5.766-1.67s4.15.594 5.765 1.67c1.82 1.214 2.977 2.942 3.348 5 .085.471-.03.937-.315 1.279a1.362 1.362 0 01-1.064.49z' | |
| 374 | + /> | |
| 375 | + ), | |
| 376 | +}); | |
| 377 | + | |
| 378 | +export const ProfileIcon = createIcon({ | |
| 379 | + displayName: "ProfileIcon", | |
| 380 | + viewBox: "0 0 24 24", | |
| 381 | + path: ( | |
| 382 | + <g> | |
| 383 | + <path d='M0 0h24v24H0V0z' fill='transparent' /> | |
| 384 | + <path | |
| 385 | + fill='currentColor' | |
| 386 | + d='M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v1c0 .55.45 1 1 1h14c.55 0 1-.45 1-1v-1c0-2.66-5.33-4-8-4z' | |
| 387 | + /> | |
| 388 | + </g> | |
| 389 | + ), | |
| 390 | +}); | |
| 391 | + | |
| 392 | +export const RocketIcon = createIcon({ | |
| 393 | + displayName: "RocketIcon", | |
| 394 | + viewBox: "0 0 24 24", | |
| 395 | + path: ( | |
| 396 | + <g> | |
| 397 | + <path | |
| 398 | + fill='currentColor' | |
| 399 | + d='M20.99 2.182a.209.209 0 00-.156-.16c-2.574-.63-8.521 1.613-11.743 4.833a13.93 13.93 0 00-1.566 1.85c-.994-.087-1.987-.014-2.834.355-2.39 1.052-3.085 3.796-3.279 4.976a.424.424 0 00.464.492l3.837-.423c.002.29.02.578.052.866.02.2.108.386.25.527l1.486 1.482a.86.86 0 00.528.25c.286.033.573.05.86.053l-.42 3.832a.424.424 0 00.492.464c1.178-.19 3.927-.885 4.972-3.274.37-.847.445-1.836.36-2.824a13.96 13.96 0 001.855-1.567c3.232-3.216 5.462-9.03 4.842-11.732zm-8.067 7.896a2.11 2.11 0 112.983-2.984 2.11 2.11 0 01-2.983 2.984z' | |
| 400 | + /> | |
| 401 | + <path | |
| 402 | + fill='currentColor' | |
| 403 | + d='M7.4 18.054c-.24.241-.627.335-1.092.416-1.044.178-1.967-.725-1.779-1.78.072-.401.283-.962.415-1.094a.192.192 0 00-.16-.328 2.636 2.636 0 00-1.544.753c-1.033 1.034-1.13 4.87-1.13 4.87s3.838-.097 4.872-1.13c.417-.417.682-.961.752-1.546.017-.184-.207-.293-.334-.16z' | |
| 404 | + /> | |
| 405 | + </g> | |
| 406 | + ), | |
| 407 | +}); | |
| 408 | + | |
| 409 | +export const SettingsIcon = createIcon({ | |
| 410 | + displayName: "SettingsIcon", | |
| 411 | + viewBox: "0 0 24 24", | |
| 412 | + // path can also be an array of elements, if you have multiple paths, lines, shapes, etc. | |
| 413 | + path: ( | |
| 414 | + <g> | |
| 415 | + <path d='M0,0h24v24H0V0z' fill='none' /> | |
| 416 | + <path | |
| 417 | + fill='currentColor' | |
| 418 | + d='M19.14,12.94c0.04-0.3,0.06-0.61,0.06-0.94c0-0.32-0.02-0.64-0.07-0.94l2.03-1.58c0.18-0.14,0.23-0.41,0.12-0.61 l-1.92-3.32c-0.12-0.22-0.37-0.29-0.59-0.22l-2.39,0.96c-0.5-0.38-1.03-0.7-1.62-0.94L14.4,2.81c-0.04-0.24-0.24-0.41-0.48-0.41 h-3.84c-0.24,0-0.43,0.17-0.47,0.41L9.25,5.35C8.66,5.59,8.12,5.92,7.63,6.29L5.24,5.33c-0.22-0.08-0.47,0-0.59,0.22L2.74,8.87 C2.62,9.08,2.66,9.34,2.86,9.48l2.03,1.58C4.84,11.36,4.8,11.69,4.8,12s0.02,0.64,0.07,0.94l-2.03,1.58 c-0.18,0.14-0.23,0.41-0.12,0.61l1.92,3.32c0.12,0.22,0.37,0.29,0.59,0.22l2.39-0.96c0.5,0.38,1.03,0.7,1.62,0.94l0.36,2.54 c0.05,0.24,0.24,0.41,0.48,0.41h3.84c0.24,0,0.44-0.17,0.47-0.41l0.36-2.54c0.59-0.24,1.13-0.56,1.62-0.94l2.39,0.96 c0.22,0.08,0.47,0,0.59-0.22l1.92-3.32c0.12-0.22,0.07-0.47-0.12-0.61L19.14,12.94z M12,15.6c-1.98,0-3.6-1.62-3.6-3.6 s1.62-3.6,3.6-3.6s3.6,1.62,3.6,3.6S13.98,15.6,12,15.6z' | |
| 419 | + /> | |
| 420 | + </g> | |
| 421 | + ), | |
| 422 | +}); | |
| 423 | + | |
| 424 | +export const SlackLogo = createIcon({ | |
| 425 | + displayName: "SlackLogo", | |
| 426 | + viewBox: "0 0 24 24", | |
| 427 | + path: ( | |
| 428 | + <g clipPath='url(#clip0)' fill-rule='evenodd' clip-rule='evenodd'> | |
| 429 | + <path | |
| 430 | + d='M7.832.5c-1.105 0-2 .919-1.999 2.05 0 1.13.895 2.048 2 2.05h2V2.55c.001-1.13-.894-2.048-2-2.05zm0 5.467H2.5c-1.106 0-2.001.918-2 2.05-.002 1.13.894 2.048 2 2.05h5.332c1.106-.001 2.002-.919 2-2.05.002-1.132-.894-2.05-2-2.05z' | |
| 431 | + fill='#36C5F0' | |
| 432 | + /> | |
| 433 | + <path | |
| 434 | + d='M20.5 8.016c0-1.13-.895-2.048-2-2.05-1.106.002-2.002.92-2 2.05v2.05h2c1.105 0 2-.918 2-2.05zm-5.334 0V2.55c.001-1.13-.893-2.048-2-2.05-1.105 0-2 .919-2 2.05v5.466c-.001 1.131.894 2.049 2 2.05 1.106 0 2.001-.918 2-2.05z' | |
| 435 | + fill='#2EB67D' | |
| 436 | + /> | |
| 437 | + <path | |
| 438 | + d='M13.166 21c1.106 0 2.001-.919 2-2.05.001-1.13-.894-2.048-2-2.05h-2v2.05c-.001 1.13.894 2.048 2 2.05zm0-5.467h5.333c1.106-.001 2.002-.92 2-2.05.002-1.131-.893-2.049-1.999-2.05h-5.333c-1.106 0-2.001.918-2 2.05-.001 1.13.893 2.049 1.999 2.05z' | |
| 439 | + fill='#ECB22E' | |
| 440 | + /> | |
| 441 | + <path | |
| 442 | + d='M.5 13.483c-.001 1.13.895 2.049 2 2.05 1.106-.001 2.001-.92 2-2.05v-2.05h-2c-1.105.001-2.001.919-2 2.05zm5.333 0v5.467c-.001 1.13.894 2.048 2 2.05 1.105 0 2-.919 2-2.05v-5.465c.002-1.131-.894-2.05-2-2.05-1.106 0-2 .917-2 2.048z' | |
| 443 | + fill='#E01E5A' | |
| 444 | + /> | |
| 445 | + </g> | |
| 446 | + ), | |
| 447 | +}); | |
| 448 | + | |
| 449 | +export const SpotifyLogo = createIcon({ | |
| 450 | + displayName: "SpotifyLogo", | |
| 451 | + viewBox: "0 0 24 24", | |
| 452 | + path: ( | |
| 453 | + <g clipPath='url(#clip0)'> | |
| 454 | + <path | |
| 455 | + d='M10.5 0C4.977 0 .5 4.477.5 10s4.477 10 10 10 10-4.477 10-10c0-5.522-4.477-10-10-10zm4.586 14.422a.623.623 0 01-.857.208c-2.348-1.435-5.304-1.76-8.785-.964a.623.623 0 11-.277-1.216c3.808-.87 7.076-.495 9.712 1.115.294.181.387.564.207.857zm1.223-2.722a.78.78 0 01-1.072.257c-2.687-1.652-6.786-2.13-9.965-1.166a.78.78 0 01-.973-.519.781.781 0 01.52-.972c3.632-1.102 8.147-.569 11.233 1.329a.78.78 0 01.258 1.072zm.106-2.835C13.19 6.95 7.875 6.775 4.797 7.708a.935.935 0 11-.543-1.79c3.533-1.072 9.404-.865 13.115 1.338a.935.935 0 01-.954 1.609z' | |
| 456 | + fill='#2EBD59' | |
| 457 | + /> | |
| 458 | + </g> | |
| 459 | + ), | |
| 460 | +}); | |
| 461 | + | |
| 462 | +export const HorizonLogo = createIcon({ | |
| 463 | + displayName: "horizonLogo", | |
| 464 | + viewBox: "0 0 179 20", | |
| 465 | + path: ( | |
| 466 | + <g width='179' height='20' fill='none' xmlns='http://www.w3.org/2000/svg'> | |
| 467 | + <path | |
| 468 | + d='M16.42.748V19h-4.446v-7.514H5.058V19H.612V.748h4.446v7.15h6.916V.748h4.446Zm11.842 18.434c-1.716 0-3.294-.399-4.732-1.196a9.092 9.092 0 0 1-3.406-3.328c-.832-1.439-1.248-3.05-1.248-4.836 0-1.785.416-3.389 1.248-4.81a9.092 9.092 0 0 1 3.406-3.328C24.968.887 26.546.488 28.262.488c1.716 0 3.284.399 4.706 1.196a8.665 8.665 0 0 1 3.38 3.328c.832 1.421 1.248 3.025 1.248 4.81 0 1.785-.416 3.397-1.248 4.836a8.901 8.901 0 0 1-3.38 3.328c-1.422.797-2.99 1.196-4.706 1.196Zm0-4.056c1.456 0 2.617-.485 3.484-1.456.884-.97 1.326-2.253 1.326-3.848 0-1.612-.442-2.895-1.326-3.848-.867-.97-2.028-1.456-3.484-1.456-1.474 0-2.652.477-3.536 1.43-.867.953-1.3 2.245-1.3 3.874 0 1.612.433 2.903 1.3 3.874.884.953 2.062 1.43 3.536 1.43ZM49.377 19l-3.796-6.89h-1.066V19h-4.446V.748h7.462c1.439 0 2.66.251 3.666.754 1.023.503 1.785 1.196 2.288 2.08.503.867.754 1.837.754 2.912 0 1.213-.347 2.297-1.04 3.25-.676.953-1.681 1.63-3.016 2.028L54.395 19h-5.018ZM44.515 8.964h2.756c.815 0 1.421-.2 1.82-.598.416-.399.624-.962.624-1.69 0-.693-.208-1.24-.624-1.638-.399-.399-1.005-.598-1.82-.598h-2.756v4.524ZM61.476.748V19H57.03V.748h4.446ZM69.43 15.36h7.852V19H64.386v-3.38l7.8-11.232h-7.8V.748h12.896v3.38L69.43 15.36Zm19.388 3.822c-1.716 0-3.293-.399-4.732-1.196a9.093 9.093 0 0 1-3.406-3.328c-.832-1.439-1.248-3.05-1.248-4.836 0-1.785.416-3.389 1.248-4.81a9.093 9.093 0 0 1 3.406-3.328C85.525.887 87.102.488 88.818.488c1.716 0 3.285.399 4.706 1.196a8.664 8.664 0 0 1 3.38 3.328c.832 1.421 1.248 3.025 1.248 4.81 0 1.785-.416 3.397-1.248 4.836a8.9 8.9 0 0 1-3.38 3.328c-1.421.797-2.99 1.196-4.706 1.196Zm0-4.056c1.456 0 2.618-.485 3.484-1.456.884-.97 1.326-2.253 1.326-3.848 0-1.612-.442-2.895-1.326-3.848-.866-.97-2.028-1.456-3.484-1.456-1.473 0-2.652.477-3.536 1.43-.866.953-1.3 2.245-1.3 3.874 0 1.612.434 2.903 1.3 3.874.884.953 2.063 1.43 3.536 1.43ZM116.954 19h-4.446l-7.436-11.258V19h-4.446V.748h4.446l7.436 11.31V.748h4.446V19ZM136.32.878v1.924h-7.878v6.11h6.396v1.924h-6.396V19h-2.366V.878h10.244ZM148.72 19l-4.316-7.41h-2.86V19h-2.366V.878h5.85c1.369 0 2.522.234 3.458.702.953.468 1.664 1.1 2.132 1.898.468.797.702 1.707.702 2.73 0 1.248-.364 2.349-1.092 3.302-.711.953-1.786 1.586-3.224 1.898l4.55 7.592h-2.834Zm-7.176-9.308h3.484c1.282 0 2.244-.312 2.886-.936.641-.641.962-1.49.962-2.548 0-1.075-.321-1.907-.962-2.496-.624-.59-1.586-.884-2.886-.884h-3.484v6.864Zm15.818-6.89V8.86h6.604v1.95h-6.604v6.24h7.384V19h-9.75V.852h9.75v1.95h-7.384Zm13.33 0V8.86h6.604v1.95h-6.604v6.24h7.384V19h-9.75V.852h9.75v1.95h-7.384Z' | |
| 469 | + fill='currentColor' | |
| 470 | + /> | |
| 471 | + </g> | |
| 472 | + ), | |
| 473 | +}); | |
| 474 | + | |
| 475 | +export const SupportIcon = createIcon({ | |
| 476 | + // Doesn't display the full icon without w and h being specified | |
| 477 | + displayName: "BuildIcon", | |
| 478 | + viewBox: "0 0 24 24", | |
| 479 | + path: ( | |
| 480 | + <path | |
| 481 | + fill='currentColor' | |
| 482 | + d='M20.885 5.547a.703.703 0 00-1.122-.176l-2.7 2.702a.708.708 0 01-.995 0l-1.167-1.169a.702.702 0 010-.994l2.689-2.69a.704.704 0 00-.21-1.138c-2.031-.908-4.566-.435-6.164 1.152-1.358 1.348-1.763 3.455-1.11 5.78a.698.698 0 01-.197.703L2.593 16.4a2.82 2.82 0 103.981 3.983l6.754-7.332a.699.699 0 01.693-.2 7.885 7.885 0 002.03.279c1.469 0 2.757-.475 3.686-1.39 1.72-1.695 1.983-4.57 1.148-6.192zM4.623 19.901a1.407 1.407 0 11-.305-2.797 1.407 1.407 0 01.305 2.797z' | |
| 483 | + /> | |
| 484 | + ), | |
| 485 | +}); | |
| 486 | + | |
| 487 | +export const StatsIcon = createIcon({ | |
| 488 | + displayName: "StatsIcon", | |
| 489 | + viewBox: "0 0 24 24", | |
| 490 | + path: ( | |
| 491 | + <path | |
| 492 | + fill='currentColor' | |
| 493 | + d='M4.57 22.297H3.164a1.055 1.055 0 01-1.055-1.054v-6.328a1.055 1.055 0 011.055-1.055H4.57a1.055 1.055 0 011.055 1.055v6.328a1.055 1.055 0 01-1.055 1.054zM14.414 22.296h-1.406a1.055 1.055 0 01-1.055-1.055V10.695a1.055 1.055 0 011.055-1.055h1.406a1.055 1.055 0 011.055 1.055V21.24a1.055 1.055 0 01-1.055 1.055zM19.336 22.297H17.93a1.055 1.055 0 01-1.055-1.055V5.773A1.055 1.055 0 0117.93 4.72h1.406a1.055 1.055 0 011.055 1.054v15.47a1.055 1.055 0 01-1.055 1.054zM9.492 22.297H8.086a1.055 1.055 0 01-1.055-1.055V2.257a1.055 1.055 0 011.055-1.054h1.406a1.055 1.055 0 011.055 1.054v18.985a1.055 1.055 0 01-1.055 1.055z' | |
| 494 | + /> | |
| 495 | + ), | |
| 496 | +}); | |
| 497 | + | |
| 498 | +export const WalletIcon = createIcon({ | |
| 499 | + displayName: "WalletIcon", | |
| 500 | + viewBox: "0 0 24 24", | |
| 501 | + path: ( | |
| 502 | + <g> | |
| 503 | + <path | |
| 504 | + fill='currentColor' | |
| 505 | + d='M4.447 4.818h14.062c.164 0 .328.01.491.031a2.9 2.9 0 00-3.406-2.441L4.03 4.382h-.013a2.9 2.9 0 00-1.805 1.149 3.848 3.848 0 012.236-.713zM18.51 5.875H4.446a2.816 2.816 0 00-2.813 2.812v8.438a2.816 2.816 0 002.813 2.812h14.062a2.815 2.815 0 002.813-2.812V8.687a2.815 2.815 0 00-2.813-2.812zm-2.088 8.437a1.406 1.406 0 110-2.811 1.406 1.406 0 010 2.811z' | |
| 506 | + /> | |
| 507 | + <path | |
| 508 | + fill='currentColor' | |
| 509 | + d='M1.656 11.651V7.28c0-.952.528-2.549 2.358-2.895 1.553-.291 3.091-.291 3.091-.291s1.011.703.176.703-.813 1.077 0 1.077 0 1.032 0 1.032L4.007 10.62l-2.35 1.032z' | |
| 510 | + /> | |
| 511 | + </g> | |
| 512 | + ), | |
| 513 | +}); | |
| 514 | + | |
| 515 | +export const VisaIcon = createIcon({ | |
| 516 | + displayName: "VisaIcon", | |
| 517 | + viewBox: "0 0 24 24", | |
| 518 | + path: ( | |
| 519 | + <svg | |
| 520 | + width='26' | |
| 521 | + height='20' | |
| 522 | + viewBox='0 0 30 9' | |
| 523 | + fill='none' | |
| 524 | + xmlns='http://www.w3.org/2000/svg'> | |
| 525 | + <path | |
| 526 | + fill-rule='evenodd' | |
| 527 | + clip-rule='evenodd' | |
| 528 | + d='M12.6708 2.96349C12.6568 4.15055 13.653 4.81298 14.4034 5.20684C15.1744 5.61099 15.4333 5.87013 15.4304 6.23147C15.4245 6.78459 14.8153 7.02862 14.2452 7.03812C13.2505 7.05475 12.6723 6.74889 12.2125 6.51753L11.8542 8.32341C12.3155 8.55247 13.1696 8.75217 14.0554 8.76087C16.1345 8.76087 17.4947 7.65543 17.5021 5.94145C17.5102 3.76625 14.7087 3.64579 14.7278 2.67348C14.7344 2.37871 14.9956 2.06408 15.568 1.98406C15.8512 1.94368 16.6332 1.91274 17.5198 2.35252L17.8677 0.605286C17.391 0.418253 16.7782 0.239136 16.0153 0.239136C14.0583 0.239136 12.6819 1.35962 12.6708 2.96349ZM21.2115 0.389687C20.8319 0.389687 20.5118 0.628245 20.3691 0.99433L17.3991 8.63249H19.4767L19.8901 7.40183H22.429L22.6689 8.63249H24.5L22.9021 0.389687H21.2115ZM21.5021 2.61641L22.1016 5.7116H20.4596L21.5021 2.61641ZM10.1518 0.389687L8.51418 8.63249H10.4939L12.1308 0.389687H10.1518ZM7.22303 0.389687L5.16233 6.00003L4.32878 1.22966C4.23097 0.697187 3.84472 0.389687 3.41579 0.389687H0.0471011L0 0.629037C0.691574 0.790671 1.47729 1.0514 1.95326 1.33033C2.24457 1.50067 2.32775 1.64964 2.42336 2.05458L4.00214 8.63249H6.0945L9.3021 0.389687H7.22303Z' | |
| 529 | + fill='currentColor' | |
| 530 | + /> | |
| 531 | + </svg> | |
| 532 | + ), | |
| 533 | +}); | |
| 534 | + | |
| 535 | +export const BitcoinLogo = createIcon({ | |
| 536 | + displayName: "BitcoinLogo", | |
| 537 | + viewBox: "0 0 67 14", | |
| 538 | + path: ( | |
| 539 | + <svg width='67' height='14' fill='none' xmlns='http://www.w3.org/2000/svg'> | |
| 540 | + <path | |
| 541 | + d='M13.779 8.694A6.993 6.993 0 1 1 .209 5.308a6.992 6.992 0 1 1 13.57 3.386Z' | |
| 542 | + fill='#F7931A' | |
| 543 | + /> | |
| 544 | + <path | |
| 545 | + d='M10.078 6.004c.14-.932-.57-1.433-1.539-1.767l.315-1.261-.768-.192-.306 1.229c-.202-.05-.409-.098-.615-.145l.309-1.237-.768-.191-.314 1.26a25.67 25.67 0 0 1-.49-.115v-.004l-1.058-.264-.204.82s.57.13.558.139c.31.077.367.283.357.446L5.197 6.16c.021.005.05.013.08.025l-.081-.02-.502 2.014c-.038.094-.135.236-.352.182.008.011-.558-.14-.558-.14l-.38.88.998.249.547.141-.317 1.276.766.192.315-1.263c.21.057.412.11.611.16l-.313 1.256.767.191.318-1.274c1.309.248 2.293.148 2.707-1.036.334-.954-.016-1.504-.705-1.863.501-.116.88-.446.98-1.127Zm-1.754 2.46c-.237.954-1.842.438-2.362.31l.422-1.692c.52.13 2.188.388 1.94 1.382Zm.238-2.474c-.217.867-1.552.427-1.986.318l.383-1.533c.433.108 1.828.31 1.603 1.215Zm11.936-1.754c.568 0 1.058.102 1.47.302.413.201.755.473 1.029.815.27.343.471.744.6 1.204.13.462.195.958.195 1.489 0 .815-.15 1.586-.451 2.312a5.995 5.995 0 0 1-1.23 1.896 5.786 5.786 0 0 1-1.842 1.274 5.617 5.617 0 0 1-2.3.47c-.107 0-.295-.003-.56-.008a8.166 8.166 0 0 1-.91-.08 10.687 10.687 0 0 1-1.09-.213 5.458 5.458 0 0 1-1.089-.39L17.383.428l2.744-.425-1.097 4.57a4.19 4.19 0 0 1 .707-.247c.237-.06.49-.09.761-.09Zm-2.3 7.584c.412 0 .802-.101 1.167-.301.367-.2.684-.47.949-.806a4.07 4.07 0 0 0 .628-1.143 3.9 3.9 0 0 0 .23-1.33c0-.567-.094-1.009-.283-1.329-.189-.318-.538-.478-1.045-.478-.166 0-.38.032-.645.088a1.61 1.61 0 0 0-.718.373l-1.168 4.854c.07.012.133.024.186.035a1.918 1.918 0 0 0 .38.037h.318Zm8.439 1.932h-2.62l2.212-9.318h2.638l-2.23 9.317Zm1.275-10.454c-.366 0-.698-.109-.992-.327-.297-.217-.443-.551-.443-1.001 0-.248.05-.48.15-.7A1.87 1.87 0 0 1 27.6.323c.219-.094.452-.14.7-.14.366 0 .695.108.99.326.295.22.443.554.443 1.002 0 .248-.05.481-.15.7a1.844 1.844 0 0 1-.974.947c-.219.096-.45.141-.698.141Zm3.252-1.186 2.744-.425-.674 2.746h2.94l-.531 2.16H32.72l-.778 3.26a3.69 3.69 0 0 0-.124.761c-.013.237.017.44.088.612a.77.77 0 0 0 .38.398c.184.095.445.143.788.143.283 0 .558-.027.825-.08.264-.053.533-.126.804-.222l.196 2.02c-.355.13-.738.242-1.151.337a6.62 6.62 0 0 1-1.47.14c-.814 0-1.445-.12-1.895-.361-.449-.243-.766-.574-.956-.994-.187-.418-.271-.9-.248-1.442a9.351 9.351 0 0 1 .248-1.72l1.736-7.333Zm4.894 7.918c0-.802.13-1.559.39-2.267A5.58 5.58 0 0 1 37.563 5.9a5.215 5.215 0 0 1 1.762-1.257c.689-.306 1.46-.46 2.31-.46.531 0 1.006.05 1.424.15.42.102.8.234 1.143.399l-.904 2.056a8.454 8.454 0 0 0-.734-.257 3.194 3.194 0 0 0-.93-.116c-.874 0-1.566.302-2.08.903-.513.602-.77 1.412-.77 2.428 0 .602.129 1.09.39 1.462.259.372.738.558 1.433.558a4.841 4.841 0 0 0 1.842-.372l.196 2.108a9.48 9.48 0 0 1-1.098.347c-.402.098-.88.149-1.435.149-.732 0-1.352-.107-1.859-.319a3.54 3.54 0 0 1-1.256-.859 3.24 3.24 0 0 1-.717-1.268 5.205 5.205 0 0 1-.223-1.522Zm11.641 3.969c-.626 0-1.17-.096-1.63-.284a3.009 3.009 0 0 1-1.141-.797 3.443 3.443 0 0 1-.682-1.214 4.903 4.903 0 0 1-.229-1.548c0-.71.114-1.42.345-2.128a6.032 6.032 0 0 1 1.018-1.912 5.441 5.441 0 0 1 1.646-1.393c.648-.359 1.392-.54 2.23-.54.613 0 1.154.096 1.62.285a3.03 3.03 0 0 1 1.152.797c.299.343.527.746.68 1.214.154.465.23.983.23 1.55a7.06 7.06 0 0 1-.336 2.126 6.143 6.143 0 0 1-.991 1.913 5.198 5.198 0 0 1-1.637 1.39c-.655.36-1.414.54-2.275.54Zm1.31-7.582c-.39 0-.733.112-1.027.335a2.894 2.894 0 0 0-.743.851 4.202 4.202 0 0 0-.453 1.125 4.87 4.87 0 0 0-.149 1.16c0 .591.095 1.052.283 1.383.19.33.53.496 1.027.496.39 0 .731-.112 1.027-.337.293-.224.542-.508.743-.85.2-.342.352-.718.453-1.126.099-.406.15-.794.15-1.16 0-.59-.096-1.051-.285-1.382-.189-.33-.531-.495-1.027-.495Zm6.784 7.335h-2.622l2.212-9.318h2.64l-2.23 9.317Zm1.273-10.454c-.365 0-.697-.109-.99-.327-.296-.217-.444-.551-.444-1.001 0-.248.051-.48.151-.7.1-.218.233-.406.399-.566.164-.16.358-.286.575-.382.218-.094.45-.14.698-.14.367 0 .698.108.992.326.295.22.444.554.444 1.002 0 .248-.053.481-.152.7a1.837 1.837 0 0 1-.972.947 1.73 1.73 0 0 1-.7.141Zm2.845 1.559c.2-.058.422-.128.663-.204a9.764 9.764 0 0 1 1.824-.371c.372-.043.795-.063 1.265-.063 1.382 0 2.335.402 2.86 1.205.526.804.617 1.902.276 3.295l-1.205 5.032h-2.638l1.168-4.926c.072-.307.127-.604.169-.895a2.12 2.12 0 0 0-.01-.761.911.911 0 0 0-.326-.532c-.173-.136-.434-.204-.789-.204a5.32 5.32 0 0 0-1.044.108l-1.717 7.21h-2.639l2.143-8.894Z' | |
| 546 | + fill='#fff' | |
| 547 | + /> | |
| 548 | + </svg> | |
| 549 | + ), | |
| 550 | +}); | |
| 551 | + | |
| 552 | +export const EthereumLogoOutline = createIcon({ | |
| 553 | + displayName: "EthereumLogoOutline", | |
| 554 | + viewBox: "0 0 82 82", | |
| 555 | + path: ( | |
| 556 | + <svg width='82' height='82' fill='none' xmlns='http://www.w3.org/2000/svg'> | |
| 557 | + <path | |
| 558 | + fill-rule='evenodd' | |
| 559 | + clip-rule='evenodd' | |
| 560 | + d='M41 82c22.644 0 41-18.356 41-41S63.644 0 41 0 0 18.356 0 41s18.356 41 41 41Zm-.1-33.852-12.625-7.146 12.627 17.054L53.54 41.002 40.9 48.148Zm-12.436-9.44 12.632-20.082 12.628 20.088-12.63 7.147-12.63-7.154Z' | |
| 561 | + fill='currentColor' | |
| 562 | + /> | |
| 563 | + </svg> | |
| 564 | + ), | |
| 565 | +}); | |
| 566 | + | |
| 567 | +export const DashCurveUp = createIcon({ | |
| 568 | + displayName: "DashCurveUp", | |
| 569 | + viewBox: "0 0 134 22", | |
| 570 | + path: ( | |
| 571 | + <g width='134' height='22' fill='none' xmlns='http://www.w3.org/2000/svg'> | |
| 572 | + <path | |
| 573 | + d='M1 21v0C41.269-4.626 92.731-4.626 133 21v0' | |
| 574 | + stroke='currentColor' | |
| 575 | + stroke-width='2' | |
| 576 | + stroke-linecap='round' | |
| 577 | + stroke-linejoin='round' | |
| 578 | + stroke-dasharray='6 6' | |
| 579 | + /> | |
| 580 | + </g> | |
| 581 | + ), | |
| 582 | +}); | |
| 583 | +export const DashCurveDown = createIcon({ | |
| 584 | + displayName: "DashCurveDown", | |
| 585 | + viewBox: "0 0 134 22", | |
| 586 | + path: ( | |
| 587 | + <g width='134' height='22' fill='none' xmlns='http://www.w3.org/2000/svg'> | |
| 588 | + <path | |
| 589 | + d='M1 1v0c40.269 25.626 91.731 25.626 132 0v0' | |
| 590 | + stroke='currentColor' | |
| 591 | + stroke-width='2' | |
| 592 | + stroke-linecap='round' | |
| 593 | + stroke-linejoin='round' | |
| 594 | + stroke-dasharray='6 6' | |
| 595 | + /> | |
| 596 | + </g> | |
| 597 | + ), | |
| 598 | +}); | |
| 599 | + | |
| 600 | +export const ButtonLeft = createIcon({ | |
| 601 | + displayName: "ButtonLeft", | |
| 602 | + viewBox: "0 0 44 29", | |
| 603 | + path: ( | |
| 604 | + <g width='44' height='29' fill='none' xmlns='http://www.w3.org/2000/svg'> | |
| 605 | + <path | |
| 606 | + d='M1 9.024A5 5 0 0 1 5.606 4.04l32-2.531A5 5 0 0 1 43 6.493v16.014a5 5 0 0 1-5.394 4.984l-32-2.53A5 5 0 0 1 1 19.975V9.024Z' | |
| 607 | + stroke='currentColor' | |
| 608 | + stroke-width='2' | |
| 609 | + /> | |
| 610 | + </g> | |
| 611 | + ), | |
| 612 | +}); | |
| 613 | + | |
| 614 | +export const ButtonRight = createIcon({ | |
| 615 | + displayName: "ButtonRight", | |
| 616 | + viewBox: "0 0 44 29", | |
| 617 | + path: ( | |
| 618 | + <svg width='44' height='29' fill='none' xmlns='http://www.w3.org/2000/svg'> | |
| 619 | + <path | |
| 620 | + d='M43 9.024a5 5 0 0 0-4.606-4.984l-32-2.531A5 5 0 0 0 1 6.493v16.014a5 5 0 0 0 5.394 4.984l32-2.53A5 5 0 0 0 43 19.975V9.024Z' | |
| 621 | + stroke='currentColor' | |
| 622 | + stroke-width='2' | |
| 623 | + /> | |
| 624 | + </svg> | |
| 625 | + ), | |
| 626 | +}); | |
| 627 | + | |
| 628 | +export const Fingerprint = createIcon({ | |
| 629 | + displayName: "ButtonRight", | |
| 630 | + viewBox: "0 0 48 48", | |
| 631 | + path: ( | |
| 632 | + <g xmlns='http://www.w3.org/2000/svg' height='48' width='48'> | |
| 633 | + <path | |
| 634 | + fill='currentColor' | |
| 635 | + d='M24.15 3.7Q27.45 3.7 30.625 4.5Q33.8 5.3 36.7 6.8Q37.15 7.05 37.225 7.425Q37.3 7.8 37.15 8.1Q36.95 8.4 36.6 8.575Q36.25 8.75 35.85 8.5Q33.2 7.1 30.2 6.375Q27.2 5.65 24.15 5.65Q21.05 5.65 18.1 6.35Q15.15 7.05 12.45 8.5Q11.95 8.75 11.6 8.65Q11.25 8.55 11.1 8.25Q10.9 8.05 10.975 7.6Q11.05 7.15 11.4 6.95Q14.3 5.3 17.575 4.5Q20.85 3.7 24.15 3.7ZM24.15 8.65Q29.5 8.65 34.25 10.975Q39 13.3 42.1 17.65Q42.4 18.1 42.325 18.475Q42.25 18.85 41.95 19.1Q41.65 19.35 41.25 19.325Q40.85 19.3 40.6 18.85Q37.8 14.9 33.425 12.775Q29.05 10.65 24.15 10.6Q19.2 10.6 14.875 12.725Q10.55 14.85 7.8 18.9Q7.45 19.35 7.05 19.425Q6.65 19.5 6.35 19.3Q6 19.15 5.925 18.775Q5.85 18.4 6 18.05Q9.05 13.6 13.85 11.125Q18.65 8.65 24.15 8.65ZM24.15 18.55Q28.8 18.55 32.125 21.725Q35.45 24.9 35.45 29.45Q35.45 29.95 35.225 30.275Q35 30.6 34.5 30.6Q33.95 30.6 33.7 30.275Q33.45 29.95 33.45 29.45Q33.45 25.65 30.725 23.05Q28 20.45 24.15 20.45Q20.3 20.45 17.6 23.15Q14.9 25.85 14.9 29.65Q14.9 33.65 16.15 36.625Q17.4 39.6 20.25 42.55Q20.6 42.9 20.6 43.25Q20.6 43.6 20.4 43.85Q20.15 44.2 19.625 44.175Q19.1 44.15 18.75 43.8Q15.9 40.8 14.425 37.35Q12.95 33.9 12.95 29.65Q12.95 25.05 16.2 21.8Q19.45 18.55 24.15 18.55ZM24.15 28.45Q24.6 28.45 24.85 28.75Q25.1 29.05 25.1 29.5Q25.1 33.9 27.975 36.2Q30.85 38.5 34.85 38.5Q35.35 38.5 36 38.4Q36.65 38.3 37.2 38.3Q37.6 38.3 37.875 38.55Q38.15 38.8 38.1 39.1Q38.1 39.5 37.925 39.725Q37.75 39.95 37.45 40.05Q36.75 40.3 36.025 40.35Q35.3 40.4 34.85 40.4Q30 40.4 26.6 37.575Q23.2 34.75 23.2 29.5Q23.2 29.05 23.45 28.75Q23.7 28.45 24.15 28.45ZM24.15 23.5Q26.7 23.5 28.5 25.225Q30.3 26.95 30.3 29.45Q30.3 31.2 31.525 32.4Q32.75 33.6 34.5 33.6Q36.2 33.6 37.4 32.4Q38.6 31.2 38.6 29.45Q38.6 23.6 34.35 19.625Q30.1 15.65 24.1 15.65Q18.1 15.65 13.9 19.725Q9.7 23.8 9.7 29.7Q9.7 31.55 9.975 33.35Q10.25 35.15 10.9 36.85Q11.05 37.35 10.925 37.65Q10.8 37.95 10.45 38.1Q10.05 38.25 9.65 38.15Q9.25 38.05 9.05 37.5Q8.55 36.05 8.175 34.025Q7.8 32 7.8 29.65Q7.8 23.1 12.625 18.325Q17.45 13.55 24.15 13.55Q30.95 13.55 35.75 18.175Q40.55 22.8 40.55 29.45Q40.55 31.95 38.8 33.75Q37.05 35.55 34.5 35.55Q31.9 35.55 30.1 33.825Q28.3 32.1 28.3 29.65Q28.3 27.9 27.1 26.65Q25.9 25.4 24.15 25.4Q22.45 25.4 21.225 26.65Q20 27.9 20 29.65Q20 34.75 22.9 38.225Q25.8 41.7 31.1 42.9Q31.55 42.95 31.725 43.275Q31.9 43.6 31.8 43.95Q31.75 44.35 31.45 44.55Q31.15 44.75 30.55 44.65Q24.7 43.45 21.375 39.375Q18.05 35.3 18.05 29.65Q18.05 27.1 19.825 25.3Q21.6 23.5 24.15 23.5Z' | |
| 636 | + /> | |
| 637 | + </g> | |
| 638 | + ), | |
| 639 | +}); |
+12-0Client/src/components/notFoundData/index.js
| @@ -0,0 +1,12 @@ | ||
| 1 | +import { Image, Box } from '@chakra-ui/react' | |
| 2 | +import React from 'react' | |
| 3 | + | |
| 4 | +const DataNotFound = () => { | |
| 5 | + return ( | |
| 6 | + <Box display={'flex'} justifyContent={'center'} alignItems={'center'}> | |
| 7 | + -- No Data Found -- | |
| 8 | + </Box> | |
| 9 | + ) | |
| 10 | +} | |
| 11 | + | |
| 12 | +export default DataNotFound |
+105-0Client/src/components/pagination/Pagination.js
| @@ -0,0 +1,105 @@ | ||
| 1 | +import { ArrowLeftIcon, ArrowRightIcon, ChevronLeftIcon, ChevronRightIcon } from '@chakra-ui/icons'; | |
| 2 | +import { Flex, IconButton, NumberDecrementStepper, NumberIncrementStepper, NumberInput, NumberInputField, NumberInputStepper, Select, Text, Tooltip } from '@chakra-ui/react'; | |
| 3 | +import React from 'react' | |
| 4 | +import { useEffect } from 'react'; | |
| 5 | + | |
| 6 | +const Pagination = (props) => { | |
| 7 | + const { gotoPage, gopageValue, setGopageValue, pageCount, canPreviousPage, previousPage, canNextPage, pageOptions, setPageSize, nextPage, pageSize, pageIndex } = props | |
| 8 | + | |
| 9 | + useEffect(() => { | |
| 10 | + setGopageValue(1) | |
| 11 | + }, []) | |
| 12 | + | |
| 13 | + return ( | |
| 14 | + | |
| 15 | + <Flex justifyContent={pageOptions?.length !== 1 ? "space-between" : "end"} mt={2} alignItems="center" > | |
| 16 | + {pageOptions?.length !== 1 && <Flex> | |
| 17 | + <Tooltip label="First Page"> | |
| 18 | + <IconButton | |
| 19 | + onClick={() => { gotoPage(0); setGopageValue(1) }} | |
| 20 | + isDisabled={!canPreviousPage} | |
| 21 | + icon={<ArrowLeftIcon h={3} w={3} />} | |
| 22 | + mr={4} | |
| 23 | + /> | |
| 24 | + </Tooltip> | |
| 25 | + <Tooltip label="Previous Page"> | |
| 26 | + <IconButton | |
| 27 | + onClick={() => { previousPage(); setGopageValue((pre) => pre - 1) }} | |
| 28 | + isDisabled={!canPreviousPage} | |
| 29 | + icon={<ChevronLeftIcon h={6} w={6} />} | |
| 30 | + /> | |
| 31 | + </Tooltip> | |
| 32 | + </Flex>} | |
| 33 | + | |
| 34 | + <Flex alignItems="center"> | |
| 35 | + {pageOptions?.length !== 1 && <> | |
| 36 | + <Text flexShrink="0" mr={8}> | |
| 37 | + Page{" "} | |
| 38 | + <Text fontWeight="bold" as="span"> | |
| 39 | + {pageIndex + 1} | |
| 40 | + </Text>{" "} | |
| 41 | + of{" "} | |
| 42 | + <Text fontWeight="bold" as="span"> | |
| 43 | + {pageOptions?.length} | |
| 44 | + </Text> | |
| 45 | + </Text> | |
| 46 | + <Text flexShrink="0">Go to page:</Text>{" "} | |
| 47 | + <NumberInput | |
| 48 | + ml={2} | |
| 49 | + mr={8} | |
| 50 | + w={28} | |
| 51 | + min={1} | |
| 52 | + max={pageOptions?.length} | |
| 53 | + value={gopageValue} | |
| 54 | + onChange={(value) => { | |
| 55 | + const page = value ? value - 1 : 0; | |
| 56 | + gotoPage(page); | |
| 57 | + setGopageValue(value) | |
| 58 | + }} | |
| 59 | + defaultValue={pageIndex + 1} | |
| 60 | + > | |
| 61 | + <NumberInputField /> | |
| 62 | + <NumberInputStepper> | |
| 63 | + <NumberIncrementStepper /> | |
| 64 | + <NumberDecrementStepper /> | |
| 65 | + </NumberInputStepper> | |
| 66 | + </NumberInput> | |
| 67 | + </>} | |
| 68 | + <Select | |
| 69 | + w={32} | |
| 70 | + value={pageSize} | |
| 71 | + onChange={(e) => { | |
| 72 | + setPageSize(Number(e.target.value)); | |
| 73 | + }} | |
| 74 | + > | |
| 75 | + {[5, 10, 20, 30, 40, 50].map((pageSize) => ( | |
| 76 | + <option key={pageSize} value={pageSize}> | |
| 77 | + Show {pageSize} | |
| 78 | + </option> | |
| 79 | + ))} | |
| 80 | + </Select> | |
| 81 | + </Flex> | |
| 82 | + | |
| 83 | + {pageOptions?.length !== 1 && | |
| 84 | + <Flex> | |
| 85 | + <Tooltip label="Next Page"> | |
| 86 | + <IconButton | |
| 87 | + onClick={() => { nextPage(); setGopageValue((pre) => pre + 1) }} | |
| 88 | + isDisabled={!canNextPage} | |
| 89 | + icon={<ChevronRightIcon h={6} w={6} />} | |
| 90 | + /> | |
| 91 | + </Tooltip> | |
| 92 | + <Tooltip label="Last Page"> | |
| 93 | + <IconButton | |
| 94 | + onClick={() => { gotoPage(pageCount - 1); setGopageValue(pageCount) }} | |
| 95 | + isDisabled={!canNextPage} | |
| 96 | + icon={<ArrowRightIcon h={3} w={3} />} | |
| 97 | + ml={4} | |
| 98 | + /> | |
| 99 | + </Tooltip> | |
| 100 | + </Flex>} | |
| 101 | + </Flex> | |
| 102 | + ) | |
| 103 | +} | |
| 104 | + | |
| 105 | +export default Pagination |
+588-0Client/src/components/reactTable/checktable.js
| @@ -0,0 +1,588 @@ | ||
| 1 | +import { useMemo, useState, useEffect } from 'react'; | |
| 2 | +import { Box, Flex, Table, Tbody, Td, Text, Th, Thead, Tr, Button, HStack, Tag, TagLabel, Menu, MenuButton, MenuDivider, MenuItem, MenuList, Grid, GridItem, Checkbox, Modal, ModalBody, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalOverlay, TagCloseButton } from '@chakra-ui/react'; | |
| 3 | +import { useColorModeValue } from '@chakra-ui/system'; | |
| 4 | +import { BsColumnsGap } from "react-icons/bs"; | |
| 5 | +import { FaSort, FaSortDown, FaSortUp } from 'react-icons/fa'; | |
| 6 | +import { SearchIcon, DeleteIcon, AddIcon } from "@chakra-ui/icons"; | |
| 7 | +import { useGlobalFilter, usePagination, useSortBy, useTable } from 'react-table'; | |
| 8 | +import Card from 'components/card/Card'; | |
| 9 | +import CountUpComponent from 'components/countUpComponent/countUpComponent'; | |
| 10 | +import Pagination from 'components/pagination/Pagination'; | |
| 11 | +import Spinner from 'components/spinner/Spinner'; | |
| 12 | +import CustomSearchInput from "../search/search"; | |
| 13 | +import AdvanceSearchUsingCustomFields from "../search/advanceSearch"; | |
| 14 | +import DataNotFound from "../notFoundData"; | |
| 15 | +import moment from 'moment'; | |
| 16 | +import { useSelector, useDispatch } from 'react-redux'; | |
| 17 | +import { getSearchData, setGetTagValues, setSearchValue } from '../../redux/slices/advanceSearchSlice' | |
| 18 | +import { commonUtils } from 'utils/utils'; | |
| 19 | + | |
| 20 | +const CommonCheckTable = (props) => { | |
| 21 | + const { | |
| 22 | + isLoding, | |
| 23 | + title, | |
| 24 | + columnData, | |
| 25 | + size, | |
| 26 | + // dataColumn, | |
| 27 | + setSearchedDataOut, | |
| 28 | + state, | |
| 29 | + allData, | |
| 30 | + ManageGrid, | |
| 31 | + deleteMany, | |
| 32 | + tableCustomFields, | |
| 33 | + access, | |
| 34 | + // selectedColumns, | |
| 35 | + // setSelectedColumns, | |
| 36 | + onOpen, | |
| 37 | + setDelete, | |
| 38 | + selectedValues, | |
| 39 | + setSelectedValues, | |
| 40 | + setIsImport, | |
| 41 | + checkBox, | |
| 42 | + AdvanceSearch, | |
| 43 | + searchDisplay, | |
| 44 | + setSearchDisplay, | |
| 45 | + BackButton, | |
| 46 | + searchboxOutside, | |
| 47 | + setGetTagValuesOutside, | |
| 48 | + setSearchboxOutside, | |
| 49 | + selectType, | |
| 50 | + customSearch, | |
| 51 | + } = props; | |
| 52 | + const { dataLength } = props; | |
| 53 | + const { handleSearchType } = props; | |
| 54 | + | |
| 55 | + const textColor = useColorModeValue("secondaryGray.900", "white"); | |
| 56 | + const borderColor = useColorModeValue("gray.200", "whiteAlpha.100"); | |
| 57 | + | |
| 58 | + const [displaySearchData, setDisplaySearchData] = useState(false); | |
| 59 | + const [searchedData, setSearchedData] = useState([]); | |
| 60 | + | |
| 61 | + const [columns, setColumns] = useState(columnData || []); | |
| 62 | + const [tempSelectedColumns, setTempSelectedColumns] = useState(columns || []); | |
| 63 | + | |
| 64 | + const searchedDataOut = useSelector((state) => state?.advanceSearchData?.searchResult) | |
| 65 | + const searchValue = useSelector((state) => state?.advanceSearchData?.searchValue) | |
| 66 | + const getTagValues = useSelector((state) => state?.advanceSearchData?.getTagValues) | |
| 67 | + const data = useMemo(() => (AdvanceSearch ? searchDisplay : displaySearchData) ? (AdvanceSearch ? searchedDataOut : searchedData) : allData, [searchDisplay, displaySearchData, AdvanceSearch, searchedDataOut, searchedData, allData]); | |
| 68 | + | |
| 69 | + const [manageColumnsModel, setManageColumnsModel] = useState(false); | |
| 70 | + const [csvColumns, setCsvColumns] = useState([]); | |
| 71 | + const [searchbox, setSearchbox] = useState(''); | |
| 72 | + const [advaceSearch, setAdvaceSearch] = useState(false); | |
| 73 | + // const [column, setColumn] = useState(''); | |
| 74 | + const [gopageValue, setGopageValue] = useState(); | |
| 75 | + | |
| 76 | + const dispatch = useDispatch(); | |
| 77 | + | |
| 78 | + const tableInstance = useTable( | |
| 79 | + { | |
| 80 | + columns, | |
| 81 | + data, | |
| 82 | + initialState: { pageIndex: 0 } | |
| 83 | + }, | |
| 84 | + useGlobalFilter, | |
| 85 | + useSortBy, | |
| 86 | + usePagination | |
| 87 | + ); | |
| 88 | + | |
| 89 | + const { | |
| 90 | + getTableProps, | |
| 91 | + getTableBodyProps, | |
| 92 | + headerGroups, | |
| 93 | + prepareRow, | |
| 94 | + page, | |
| 95 | + canPreviousPage, | |
| 96 | + canNextPage, | |
| 97 | + pageOptions, | |
| 98 | + pageCount, | |
| 99 | + gotoPage, | |
| 100 | + nextPage, | |
| 101 | + previousPage, | |
| 102 | + setPageSize, | |
| 103 | + state: { pageIndex, pageSize } | |
| 104 | + } = tableInstance; | |
| 105 | + | |
| 106 | + if (pageOptions && pageOptions?.length > 0 && pageOptions?.length < gopageValue) { | |
| 107 | + setGopageValue(pageOptions.length) | |
| 108 | + } | |
| 109 | + | |
| 110 | + const handleSearch = (results) => { | |
| 111 | + AdvanceSearch && dispatch(getSearchData({ searchData: (results || []), type: handleSearchType })) | |
| 112 | + AdvanceSearch ? setSearchedDataOut(results || []) : setSearchedData(results || []); | |
| 113 | + }; | |
| 114 | + | |
| 115 | + const handleAdvanceSearch = (values) => { | |
| 116 | + dispatch(setSearchValue(values)) | |
| 117 | + const searchResult = AdvanceSearch ? dispatch(getSearchData({ values: values, allData: allData, type: title })) : allData?.filter(item => { | |
| 118 | + return tableCustomFields.every(field => { | |
| 119 | + const fieldValue = values[field.name]; | |
| 120 | + const itemValue = item[field.name]; | |
| 121 | + | |
| 122 | + if (field.type === 'select') { | |
| 123 | + return !fieldValue || itemValue === fieldValue; | |
| 124 | + } | |
| 125 | + else if (field.type === 'number') { | |
| 126 | + return ( | |
| 127 | + [null, undefined, ''].includes(fieldValue) || | |
| 128 | + (itemValue !== undefined && | |
| 129 | + itemValue.toString().includes(fieldValue.toString())) | |
| 130 | + ); | |
| 131 | + } | |
| 132 | + else if (field.type === 'date') { | |
| 133 | + const fromDate = values[`from${field.name}`]; | |
| 134 | + const toDate = values[`to${field.name}`]; | |
| 135 | + | |
| 136 | + if (!fromDate && !toDate) { | |
| 137 | + return true; // No date range specified | |
| 138 | + } | |
| 139 | + | |
| 140 | + const timeItemDate = new Date(itemValue); | |
| 141 | + const timeMomentDate = moment(timeItemDate).format('YYYY-MM-DD'); | |
| 142 | + | |
| 143 | + return ( | |
| 144 | + (!fromDate || (timeMomentDate >= fromDate)) && | |
| 145 | + (!toDate || (timeMomentDate <= toDate)) | |
| 146 | + ); | |
| 147 | + } | |
| 148 | + else { | |
| 149 | + // Default case for text, email | |
| 150 | + return !fieldValue || itemValue?.toLowerCase()?.includes(fieldValue?.toLowerCase()); | |
| 151 | + } | |
| 152 | + }); | |
| 153 | + }); | |
| 154 | + | |
| 155 | + const getValue = tableCustomFields.reduce((result, field) => { | |
| 156 | + if (field.type === 'date') { | |
| 157 | + const fromDate = values[`from${field.name}`]; | |
| 158 | + const toDate = values[`to${field.name}`]; | |
| 159 | + | |
| 160 | + if (fromDate || toDate) { | |
| 161 | + result.push({ | |
| 162 | + name: [`from${field.name}`, `to${field.name}`], | |
| 163 | + value: `From: ${fromDate} To: ${toDate}` | |
| 164 | + }) | |
| 165 | + } | |
| 166 | + } else if (values[field.name]) { | |
| 167 | + result.push({ | |
| 168 | + name: [field.name], | |
| 169 | + value: values[field.name] | |
| 170 | + }) | |
| 171 | + } | |
| 172 | + | |
| 173 | + return result; | |
| 174 | + }, []); | |
| 175 | + dispatch(setGetTagValues(getValue)) | |
| 176 | + setSearchedData(searchResult); | |
| 177 | + setDisplaySearchData(true); | |
| 178 | + setAdvaceSearch(false); | |
| 179 | + if (setSearchbox) { | |
| 180 | + setSearchbox(''); | |
| 181 | + } | |
| 182 | + } | |
| 183 | + | |
| 184 | + | |
| 185 | + const handleClear = () => { | |
| 186 | + setSearchDisplay && setSearchDisplay(false) | |
| 187 | + setDisplaySearchData && setDisplaySearchData(false) | |
| 188 | + if (searchboxOutside) { | |
| 189 | + setSearchboxOutside('') | |
| 190 | + } else { | |
| 191 | + setSearchbox(''); | |
| 192 | + } | |
| 193 | + dispatch(setGetTagValues([])) | |
| 194 | + if (props?.getTagValuesOutSide) { | |
| 195 | + setGetTagValuesOutside([]); | |
| 196 | + } | |
| 197 | + setGopageValue(1); | |
| 198 | + }; | |
| 199 | + | |
| 200 | + const handleClick = () => { | |
| 201 | + onOpen(); | |
| 202 | + }; | |
| 203 | + | |
| 204 | + const findStatus = () => { | |
| 205 | + const searchResult = allData?.filter( | |
| 206 | + (item) => | |
| 207 | + (!state || (item?.status && item?.status?.toLowerCase().includes(state?.toLowerCase()))) | |
| 208 | + ) | |
| 209 | + let getValue = [state || undefined].filter(value => value); | |
| 210 | + | |
| 211 | + dispatch(setGetTagValues(getValue)) | |
| 212 | + AdvanceSearch ? setSearchedDataOut && setSearchedDataOut(searchResult) : setSearchedData && setSearchedData(searchResult); | |
| 213 | + AdvanceSearch ? setSearchDisplay && setSearchDisplay(true) : setDisplaySearchData && setDisplaySearchData(searchResult); | |
| 214 | + setDisplaySearchData(true) | |
| 215 | + setAdvaceSearch(false) | |
| 216 | + } | |
| 217 | + | |
| 218 | + useEffect(() => { | |
| 219 | + state && findStatus() | |
| 220 | + }, [state, allData]); | |
| 221 | + | |
| 222 | + const toggleColumnVisibility = (columnKey) => { | |
| 223 | + let updatedColumns; | |
| 224 | + | |
| 225 | + if (tempSelectedColumns?.some((column) => column?.accessor === columnKey)) { | |
| 226 | + updatedColumns = tempSelectedColumns?.filter((column) => column?.accessor !== columnKey); | |
| 227 | + } else { | |
| 228 | + const columnToAdd = columnData?.find((column) => column?.accessor === columnKey); | |
| 229 | + updatedColumns = [...tempSelectedColumns, columnToAdd]; | |
| 230 | + } | |
| 231 | + | |
| 232 | + const orderedColumns = columnData?.filter(column => updatedColumns.some(updatedColumn => updatedColumn?.accessor === column?.accessor)); | |
| 233 | + setTempSelectedColumns(orderedColumns); | |
| 234 | + | |
| 235 | + }; | |
| 236 | + | |
| 237 | + const handleCheckboxChange = (event, value) => { | |
| 238 | + if (selectType === "single") { | |
| 239 | + if (event.target.checked) { | |
| 240 | + setSelectedValues && setSelectedValues(value); | |
| 241 | + } else { | |
| 242 | + setSelectedValues(); | |
| 243 | + } | |
| 244 | + } else if (event.target.checked) { | |
| 245 | + setSelectedValues && setSelectedValues((prevSelectedValues) => [...prevSelectedValues, value]); | |
| 246 | + } else { | |
| 247 | + setSelectedValues && setSelectedValues((prevSelectedValues) => | |
| 248 | + prevSelectedValues.filter((selectedValue) => selectedValue !== value) | |
| 249 | + ); | |
| 250 | + } | |
| 251 | + | |
| 252 | + }; | |
| 253 | + | |
| 254 | + const handleColumnClose = () => { | |
| 255 | + setManageColumnsModel(!manageColumnsModel) | |
| 256 | + }; | |
| 257 | + | |
| 258 | + | |
| 259 | + const handleExportLeads = (extension) => { | |
| 260 | + selectedValues && selectedValues?.length > 0 | |
| 261 | + ? downloadCsvOrExcel(extension, selectedValues) | |
| 262 | + : downloadCsvOrExcel(extension); | |
| 263 | + }; | |
| 264 | + | |
| 265 | + const downloadCsvOrExcel = async (extension, selectedIds) => { | |
| 266 | + try { | |
| 267 | + if (selectedIds && selectedIds?.length > 0) { | |
| 268 | + const selectedRecordsWithSpecificFileds = allData?.filter((rec) => selectedIds.includes(rec._id))?.map((rec) => { | |
| 269 | + const selectedFieldsData = {}; | |
| 270 | + csvColumns?.forEach((property) => { | |
| 271 | + selectedFieldsData[property.accessor] = rec[property.accessor]; | |
| 272 | + }); | |
| 273 | + return selectedFieldsData; | |
| 274 | + }); | |
| 275 | + commonUtils.convertJsonToCsvOrExcel({ | |
| 276 | + jsonArray: selectedRecordsWithSpecificFileds, | |
| 277 | + csvColumns: csvColumns, | |
| 278 | + fileName: title || 'data', | |
| 279 | + extension: extension | |
| 280 | + }); | |
| 281 | + } else { | |
| 282 | + const AllRecordsWithSpecificFileds = allData?.map((rec) => { | |
| 283 | + const selectedFieldsData = {}; | |
| 284 | + csvColumns?.forEach((property) => { | |
| 285 | + selectedFieldsData[property?.accessor] = rec[property?.accessor]; | |
| 286 | + }); | |
| 287 | + return selectedFieldsData; | |
| 288 | + }); | |
| 289 | + commonUtils.convertJsonToCsvOrExcel({ | |
| 290 | + jsonArray: AllRecordsWithSpecificFileds, | |
| 291 | + csvColumns: csvColumns, | |
| 292 | + fileName: title || 'data', | |
| 293 | + extension: extension | |
| 294 | + }); | |
| 295 | + } | |
| 296 | + setSelectedValues([]) | |
| 297 | + } catch (e) { | |
| 298 | + console.error(e); | |
| 299 | + } | |
| 300 | + }; | |
| 301 | + | |
| 302 | + const handleRemoveFromTag = (name) => { | |
| 303 | + const filter = (getTagValues || []).filter((item) => { | |
| 304 | + if (Array.isArray(name?.name)) { | |
| 305 | + return name.name?.toString() !== item.name?.toString(); | |
| 306 | + } | |
| 307 | + }); | |
| 308 | + | |
| 309 | + let updatedSearchValue = { ...searchValue }; | |
| 310 | + for (let key in updatedSearchValue) { | |
| 311 | + if (updatedSearchValue.hasOwnProperty(key)) { | |
| 312 | + if (name.name.includes(key)) { | |
| 313 | + delete updatedSearchValue[key]; | |
| 314 | + } | |
| 315 | + if (updatedSearchValue[key] === "") { | |
| 316 | + delete updatedSearchValue[key]; | |
| 317 | + } | |
| 318 | + } | |
| 319 | + } | |
| 320 | + | |
| 321 | + handleAdvanceSearch(updatedSearchValue) | |
| 322 | + | |
| 323 | + dispatch(setGetTagValues(filter)) | |
| 324 | + if (filter?.length === 0) { | |
| 325 | + handleClear(); | |
| 326 | + } | |
| 327 | + } | |
| 328 | + | |
| 329 | + useEffect(() => { | |
| 330 | + AdvanceSearch ? setSearchedDataOut && setSearchedDataOut(data) : setSearchedData && setSearchedData(data); | |
| 331 | + }, []); | |
| 332 | + | |
| 333 | + useEffect(() => { | |
| 334 | + setColumns(columnData); | |
| 335 | + }, [columnData]); | |
| 336 | + | |
| 337 | + useEffect(() => { | |
| 338 | + if (columns) { | |
| 339 | + let tempCsvColumns = columns?.filter((col) => col?.Header !== '#' && col?.Header !== 'Action')?.map((field) => ({ Header: field?.Header, accessor: field?.accessor })); | |
| 340 | + setCsvColumns([...tempCsvColumns]) | |
| 341 | + } | |
| 342 | + }, [columns]); | |
| 343 | + | |
| 344 | + return ( | |
| 345 | + <> | |
| 346 | + <Card | |
| 347 | + direction="column" | |
| 348 | + w="100%" | |
| 349 | + overflowX={{ sm: "scroll", lg: "hidden" }} | |
| 350 | + > | |
| 351 | + <Grid templateColumns="repeat(12, 1fr)" gap={2}> | |
| 352 | + <GridItem colSpan={{ base: 12, md: 8 }} display={"flex"} alignItems={"center"}> | |
| 353 | + <Flex alignItems={"center"} flexWrap={"wrap"}> | |
| 354 | + { | |
| 355 | + title && | |
| 356 | + <Text | |
| 357 | + color={'secondaryGray.900'} | |
| 358 | + fontSize="22px" | |
| 359 | + fontWeight="700" | |
| 360 | + lineHeight="100%" | |
| 361 | + textTransform={'capitalize'} | |
| 362 | + > | |
| 363 | + {title} (<CountUpComponent key={data?.length} targetNumber={dataLength || data?.length} />) | |
| 364 | + </Text> | |
| 365 | + } | |
| 366 | + {customSearch !== false && <CustomSearchInput setSearchbox={setSearchboxOutside ? setSearchboxOutside : setSearchbox} setDisplaySearchData={setSearchboxOutside ? props.setSearchDisplay : setDisplaySearchData} searchbox={searchboxOutside ? searchboxOutside : searchbox} allData={allData} dataColumn={columns} onSearch={handleSearch} setGetTagValues={props.setGetTagValuesOutside ? props.setGetTagValuesOutside : setGetTagValues} setGopageValue={setGopageValue} />} | |
| 367 | + { | |
| 368 | + AdvanceSearch ? AdvanceSearch : AdvanceSearch !== false && | |
| 369 | + <Button variant="outline" colorScheme='brand' leftIcon={<SearchIcon />} mt={{ sm: "5px", md: "0" }} size="sm" onClick={() => setAdvaceSearch(true)}>Advance Search</Button> | |
| 370 | + } | |
| 371 | + {(searchDisplay || displaySearchData) ? <Button variant="outline" colorScheme='red' size="sm" ms={2} onClick={() => handleClear()}>Clear</Button> : ""} | |
| 372 | + {(selectedValues?.length > 0 && access?.delete && !deleteMany) && <DeleteIcon cursor={"pointer"} onClick={() => setDelete(true)} color={'red'} ms={2} />} | |
| 373 | + </Flex> | |
| 374 | + </GridItem> | |
| 375 | + {/* Advance filter */} | |
| 376 | + <AdvanceSearchUsingCustomFields | |
| 377 | + setAdvaceSearch={setAdvaceSearch} | |
| 378 | + setGetTagValues={setGetTagValues} | |
| 379 | + isLoding={isLoding} | |
| 380 | + allData={allData} | |
| 381 | + setDisplaySearchData={setDisplaySearchData} | |
| 382 | + setSearchedData={setSearchedData} | |
| 383 | + advaceSearch={advaceSearch} | |
| 384 | + tableCustomFields={tableCustomFields} | |
| 385 | + setSearchbox={setSearchbox} | |
| 386 | + handleAdvanceSearch={handleAdvanceSearch} | |
| 387 | + /> | |
| 388 | + <GridItem colSpan={{ base: 12, md: 4 }} display={"flex"} justifyContent={"end"} alignItems={"center"} textAlign={"right"}> | |
| 389 | + {ManageGrid !== false && | |
| 390 | + <Menu isLazy > | |
| 391 | + <MenuButton p={4}> | |
| 392 | + <BsColumnsGap /> | |
| 393 | + </MenuButton> | |
| 394 | + <MenuList minW={'fit-content'} transform={"translate(1670px, 60px)"} zIndex={2} > | |
| 395 | + <MenuItem onClick={() => setManageColumnsModel(true)} width={"165px"}> Manage Columns | |
| 396 | + </MenuItem> | |
| 397 | + {typeof setIsImport === "function" && <MenuItem width={"165px"} onClick={() => setIsImport(true)}> Import {title} | |
| 398 | + </MenuItem>} | |
| 399 | + { | |
| 400 | + allData && allData?.length > 0 && | |
| 401 | + <> | |
| 402 | + <MenuDivider /> | |
| 403 | + <MenuItem width={"165px"} onClick={() => handleExportLeads('csv')}>{selectedValues && selectedValues?.length > 0 ? 'Export Selected Data as CSV' : 'Export as CSV'}</MenuItem> | |
| 404 | + <MenuItem width={"165px"} onClick={() => handleExportLeads('xlsx')}>{selectedValues && selectedValues?.length > 0 ? 'Export Selected Data as Excel' : 'Export as Excel'}</MenuItem> | |
| 405 | + </> | |
| 406 | + } | |
| 407 | + </MenuList> | |
| 408 | + </Menu>} | |
| 409 | + {(access?.create || access === true) && <Button onClick={() => handleClick()} size="sm" variant="brand" leftIcon={<AddIcon />}>Add New</Button>} | |
| 410 | + {BackButton && BackButton} | |
| 411 | + </GridItem> | |
| 412 | + <HStack spacing={4} mb={2}> | |
| 413 | + {(getTagValues || []).map((item) => ( | |
| 414 | + <Tag | |
| 415 | + size={"md"} | |
| 416 | + p={2} | |
| 417 | + key={item.value} | |
| 418 | + borderRadius='full' | |
| 419 | + variant='solid' | |
| 420 | + colorScheme="gray" | |
| 421 | + > | |
| 422 | + <TagLabel>{item.value}</TagLabel> | |
| 423 | + <TagCloseButton onClick={() => handleRemoveFromTag(item)} /> | |
| 424 | + </Tag> | |
| 425 | + ))} | |
| 426 | + </HStack> | |
| 427 | + </Grid> | |
| 428 | + <Box overflowY={"auto"} className={size ? `small-table-fix-container` : `table-fix-container`}> | |
| 429 | + <Table {...getTableProps()} variant="simple" color="gray.500" mb="24px"> | |
| 430 | + <Thead zIndex={1}> | |
| 431 | + {headerGroups?.map((headerGroup, index) => ( | |
| 432 | + <Tr {...headerGroup.getHeaderGroupProps()} key={index}> | |
| 433 | + {headerGroup.headers?.map((column, index) => ( | |
| 434 | + <Th | |
| 435 | + {...column.getHeaderProps(column.isSortable !== false && column.getSortByToggleProps())} | |
| 436 | + pe="10px" | |
| 437 | + key={index} | |
| 438 | + borderColor={borderColor} | |
| 439 | + > | |
| 440 | + <Flex | |
| 441 | + align="center" | |
| 442 | + justifyContent={column.center ? "center" : "start"} | |
| 443 | + fontSize={{ sm: "14px", lg: "16px" }} | |
| 444 | + color="secondaryGray.900" | |
| 445 | + > | |
| 446 | + <span style={{ textTransform: "capitalize", marginRight: "8px" }}> | |
| 447 | + {column.render("Header")} | |
| 448 | + </span> | |
| 449 | + {column.isSortable !== false && ( | |
| 450 | + <span> | |
| 451 | + {column.isSorted ? (column.isSortedDesc ? <FaSortDown /> : <FaSortUp />) : <FaSort />} | |
| 452 | + </span> | |
| 453 | + )} | |
| 454 | + </Flex> | |
| 455 | + </Th> | |
| 456 | + ))} | |
| 457 | + </Tr> | |
| 458 | + ))} | |
| 459 | + </Thead> | |
| 460 | + <Tbody {...getTableBodyProps()}> | |
| 461 | + {isLoding ? | |
| 462 | + <Tr> | |
| 463 | + <Td colSpan={columns?.length}> | |
| 464 | + <Flex justifyContent={'center'} alignItems={'center'} width="100%" color={textColor} fontSize="sm" fontWeight="700"> | |
| 465 | + <Spinner /> | |
| 466 | + </Flex> | |
| 467 | + </Td> | |
| 468 | + </Tr> | |
| 469 | + : data && data?.length === 0 || data === undefined ? ( | |
| 470 | + <Tr> | |
| 471 | + <Td colSpan={columns.length}> | |
| 472 | + <Text textAlign={'center'} width="100%" color={textColor} fontSize="sm" fontWeight="700"> | |
| 473 | + <DataNotFound /> | |
| 474 | + </Text> | |
| 475 | + </Td> | |
| 476 | + </Tr> | |
| 477 | + ) : page?.map((row, i) => { | |
| 478 | + prepareRow(row); | |
| 479 | + return ( | |
| 480 | + <Tr {...row?.getRowProps()}> | |
| 481 | + {row?.cells?.map((cell, index) => { | |
| 482 | + let data = ""; | |
| 483 | + columnData?.forEach((item) => { | |
| 484 | + if (cell?.column.Header === item.Header) { | |
| 485 | + if (item.cell && typeof item.cell === 'function') { | |
| 486 | + data = ( | |
| 487 | + <Flex Flex align="center" justifyContent={item?.Header === 'Action' && 'center'}> | |
| 488 | + <Text color={textColor} fontSize="sm" fontWeight="700" > | |
| 489 | + {item.cell(cell) === ' ' ? '-' : item.cell(cell)} | |
| 490 | + </Text> | |
| 491 | + </Flex> | |
| 492 | + ); | |
| 493 | + } | |
| 494 | + else { | |
| 495 | + data = ( | |
| 496 | + <Flex align="center" > | |
| 497 | + {item.Header === | |
| 498 | + "#" && | |
| 499 | + (checkBox || checkBox === undefined) && ( | |
| 500 | + <Checkbox | |
| 501 | + colorScheme="brandScheme" | |
| 502 | + value={selectedValues} | |
| 503 | + isChecked={selectedValues?.includes(cell?.value)} | |
| 504 | + onChange={(event) => handleCheckboxChange(event, cell?.value)} | |
| 505 | + me="10px" | |
| 506 | + /> | |
| 507 | + )} | |
| 508 | + | |
| 509 | + <Text color={textColor} fontSize="sm" fontWeight="700"> | |
| 510 | + {item.Header === "#" ? cell?.row?.index + 1 : cell?.value ? cell?.value : '-'} | |
| 511 | + </Text> | |
| 512 | + </Flex> | |
| 513 | + ); | |
| 514 | + } | |
| 515 | + } | |
| 516 | + }); | |
| 517 | + return ( | |
| 518 | + <Td | |
| 519 | + {...cell?.getCellProps()} | |
| 520 | + key={index} | |
| 521 | + fontSize={{ sm: "14px" }} | |
| 522 | + minW={{ sm: "150px", md: "200px", lg: "auto" }} | |
| 523 | + borderColor="transparent" | |
| 524 | + > | |
| 525 | + {data} | |
| 526 | + </Td> | |
| 527 | + ); | |
| 528 | + })} | |
| 529 | + </Tr> | |
| 530 | + ); | |
| 531 | + })} | |
| 532 | + </Tbody> | |
| 533 | + </Table> | |
| 534 | + </Box> | |
| 535 | + {data?.length > 5 && <Pagination gotoPage={gotoPage} gopageValue={gopageValue} setGopageValue={setGopageValue} pageCount={pageCount} canPreviousPage={canPreviousPage} previousPage={previousPage} canNextPage={canNextPage} pageOptions={pageOptions} setPageSize={setPageSize} nextPage={nextPage} | |
| 536 | + pageSize={pageSize} pageIndex={pageIndex} dataLength={15} />} | |
| 537 | + | |
| 538 | + {/* Manage Columns */} | |
| 539 | + <Modal onClose={() => { setManageColumnsModel(false); }} isOpen={manageColumnsModel} isCentered> | |
| 540 | + <ModalOverlay /> | |
| 541 | + <ModalContent> | |
| 542 | + <ModalHeader>Manage Columns</ModalHeader> | |
| 543 | + <ModalCloseButton onClick={() => { setManageColumnsModel(false); }} /> | |
| 544 | + <ModalBody> | |
| 545 | + <div> | |
| 546 | + {columnData?.map((column) => ( | |
| 547 | + <Text display={"flex"} key={column?.accessor} py={2}> | |
| 548 | + <Checkbox | |
| 549 | + defaultChecked={columns?.some((item) => item?.accessor === column?.accessor)} | |
| 550 | + onChange={() => toggleColumnVisibility(column?.accessor)} | |
| 551 | + pe={2} | |
| 552 | + /> | |
| 553 | + {column?.Header} | |
| 554 | + </Text> | |
| 555 | + ))} | |
| 556 | + </div> | |
| 557 | + </ModalBody> | |
| 558 | + <ModalFooter> | |
| 559 | + <Button | |
| 560 | + colorScheme='brand' | |
| 561 | + mr={2} | |
| 562 | + onClick={() => { | |
| 563 | + setColumns([...tempSelectedColumns]); | |
| 564 | + setManageColumnsModel(false); | |
| 565 | + }} | |
| 566 | + disabled={isLoding ? true : false} | |
| 567 | + size='sm' | |
| 568 | + > | |
| 569 | + {isLoding ? <Spinner /> : 'Save'} | |
| 570 | + </Button> | |
| 571 | + <Button | |
| 572 | + variant='outline' | |
| 573 | + colorScheme="red" | |
| 574 | + size='sm' | |
| 575 | + onClick={() => handleColumnClose()} | |
| 576 | + > | |
| 577 | + Close | |
| 578 | + </Button> | |
| 579 | + </ModalFooter> | |
| 580 | + </ModalContent> | |
| 581 | + </Modal> | |
| 582 | + | |
| 583 | + </Card> | |
| 584 | + </> | |
| 585 | + ); | |
| 586 | +} | |
| 587 | + | |
| 588 | +export default CommonCheckTable |
+13-0Client/src/components/rtlProvider/RtlProvider.js
| @@ -0,0 +1,13 @@ | ||
| 1 | +import { CacheProvider } from "@emotion/react"; | |
| 2 | +import createCache from "@emotion/cache"; | |
| 3 | +import rtl from "stylis-plugin-rtl"; | |
| 4 | +// NB: A unique `key` is important for it to work! | |
| 5 | +const options = { | |
| 6 | + rtl: { key: "css-ar", stylisPlugins: [rtl] }, | |
| 7 | + ltr: { key: "css-en" }, | |
| 8 | +}; | |
| 9 | +export function RtlProvider({ children }) { | |
| 10 | + const dir = document.documentElement.dir === "ar" ? "rtl" : "ltr"; | |
| 11 | + const cache = createCache(options[dir]); | |
| 12 | + return <CacheProvider value={cache} children={children} />; | |
| 13 | +} |
+38-0Client/src/components/scrollbar/Scrollbar.js
| @@ -0,0 +1,38 @@ | ||
| 1 | +import { Box } from "@chakra-ui/react"; | |
| 2 | + | |
| 3 | +import React from "react"; | |
| 4 | + | |
| 5 | +export const renderTrack = ({ style, ...props }) => { | |
| 6 | + const trackStyle = { | |
| 7 | + position: "absolute", | |
| 8 | + maxWidth: "100%", | |
| 9 | + width: 6, | |
| 10 | + transition: "opacity 200ms ease 0s", | |
| 11 | + opacity: 0, | |
| 12 | + background: "transparent", | |
| 13 | + bottom: 2, | |
| 14 | + top: 2, | |
| 15 | + borderRadius: 3, | |
| 16 | + right: 0, | |
| 17 | + }; | |
| 18 | + return <div style={{ ...style, ...trackStyle }} {...props} />; | |
| 19 | +}; | |
| 20 | +export const renderThumb = ({ style, ...props }) => { | |
| 21 | + const thumbStyle = { | |
| 22 | + borderRadius: 15, | |
| 23 | + background: "rgba(222, 222, 222, .1)", | |
| 24 | + }; | |
| 25 | + return <div style={{ ...style, ...thumbStyle }} {...props} />; | |
| 26 | +}; | |
| 27 | +export const renderView = ({ style, ...props }) => { | |
| 28 | + const viewStyle = { | |
| 29 | + marginBottom: 10, | |
| 30 | + }; | |
| 31 | + return ( | |
| 32 | + <Box | |
| 33 | + me={{ base: "0px !important", lg: "-16px !important" }} | |
| 34 | + style={{ ...style, ...viewStyle }} | |
| 35 | + {...props} | |
| 36 | + /> | |
| 37 | + ); | |
| 38 | +}; |
+210-0Client/src/components/search/advanceSearch.js
| @@ -0,0 +1,210 @@ | ||
| 1 | +import React from 'react'; | |
| 2 | +import moment from 'moment'; | |
| 3 | +import { useFormik } from 'formik'; | |
| 4 | +import { Box, Button, Flex, FormLabel, Grid, GridItem, Input, InputGroup, InputLeftElement, Modal, ModalBody, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalOverlay, Select, Spinner, Text } from '@chakra-ui/react'; | |
| 5 | + | |
| 6 | +const AdvanceSearch = ({ handleAdvanceSearch, setAdvaceSearch, search, advaceSearch, isLoding, allData, setDisplaySearchData, setSearchedData, setGetTagValues, setSearchClear, tableCustomFields, setSearchbox }) => { | |
| 7 | + | |
| 8 | + const initialFieldValues = Object.fromEntries( | |
| 9 | + (tableCustomFields || []).flatMap(field => { | |
| 10 | + if (field.type === 'date') { | |
| 11 | + return [ | |
| 12 | + [`from${field.name}`, ''], | |
| 13 | + [`to${field.name}`, ''] | |
| 14 | + ]; | |
| 15 | + } else { | |
| 16 | + return [[field?.name, '']]; | |
| 17 | + } | |
| 18 | + }) | |
| 19 | + ); | |
| 20 | + | |
| 21 | + const initialValues = { | |
| 22 | + ...initialFieldValues | |
| 23 | + }; | |
| 24 | + | |
| 25 | + const formik = useFormik({ | |
| 26 | + initialValues: initialValues, | |
| 27 | + enableReinitialize: true, | |
| 28 | + onSubmit: (values, { resetForm }) => { | |
| 29 | + handleAdvanceSearch(values) | |
| 30 | + resetForm(); | |
| 31 | + } | |
| 32 | + }) | |
| 33 | + // const formik = useFormik({ | |
| 34 | + // initialValues: initialValues, | |
| 35 | + // enableReinitialize: true, | |
| 36 | + // onSubmit: (values, { resetForm }) => { | |
| 37 | + // const searchResult = allData?.filter(item => { | |
| 38 | + // return tableCustomFields.every(field => { | |
| 39 | + // const fieldValue = values[field.name]; | |
| 40 | + // const itemValue = item[field.name]; | |
| 41 | + | |
| 42 | + // if (field.type === 'select') { | |
| 43 | + // return !fieldValue || itemValue === fieldValue; | |
| 44 | + // } | |
| 45 | + // else if (field.type === 'number') { | |
| 46 | + // // return ( | |
| 47 | + // // [null, undefined, ''].includes(fieldValue) || | |
| 48 | + // // (itemValue !== undefined && | |
| 49 | + // // (parseInt(itemValue, 10) >= parseInt(fieldValue, 10) || 0)) | |
| 50 | + // // ); | |
| 51 | + // // return ( | |
| 52 | + // // [null, undefined, ''].includes(fieldValue) || | |
| 53 | + // // (itemValue !== undefined && | |
| 54 | + // // (parseInt(itemValue, 10) === parseInt(fieldValue, 10))) | |
| 55 | + // // ); | |
| 56 | + // return ( | |
| 57 | + // [null, undefined, ''].includes(fieldValue) || | |
| 58 | + // (itemValue !== undefined && | |
| 59 | + // itemValue.toString().includes(fieldValue.toString())) | |
| 60 | + // ); | |
| 61 | + // } | |
| 62 | + // else if (field.type === 'date') { | |
| 63 | + // const fromDate = values[`from${field.name}`]; | |
| 64 | + // const toDate = values[`to${field.name}`]; | |
| 65 | + | |
| 66 | + // if (!fromDate && !toDate) { | |
| 67 | + // return true; // No date range specified | |
| 68 | + // } | |
| 69 | + | |
| 70 | + // const timeItemDate = new Date(itemValue); | |
| 71 | + // const timeMomentDate = moment(timeItemDate).format('YYYY-MM-DD'); | |
| 72 | + | |
| 73 | + // return ( | |
| 74 | + // (!fromDate || (timeMomentDate >= fromDate)) && | |
| 75 | + // (!toDate || (timeMomentDate <= toDate)) | |
| 76 | + // ); | |
| 77 | + // } | |
| 78 | + // else { | |
| 79 | + // // Default case for text, email | |
| 80 | + // return !fieldValue || itemValue?.toLowerCase()?.includes(fieldValue?.toLowerCase()); | |
| 81 | + // } | |
| 82 | + // }); | |
| 83 | + // }); | |
| 84 | + | |
| 85 | + // // let getValue = tableCustomFields.map(field => values[field.name]).filter(value => value); | |
| 86 | + // const getValue = tableCustomFields.map(field => { | |
| 87 | + // if (field.type === 'date') { | |
| 88 | + // const fromDate = values[`from${field.name}`]; | |
| 89 | + // const toDate = values[`to${field.name}`]; | |
| 90 | + | |
| 91 | + // return (fromDate || toDate) && `From: ${fromDate} To: ${toDate}`; | |
| 92 | + // } else { | |
| 93 | + // return values[field.name]; | |
| 94 | + // } | |
| 95 | + // }).filter(value => value); | |
| 96 | + | |
| 97 | + // setGetTagValues(getValue); | |
| 98 | + // setSearchedData(searchResult); | |
| 99 | + // setDisplaySearchData(true); | |
| 100 | + // setAdvaceSearch(false); | |
| 101 | + // resetForm(); | |
| 102 | + // if (setSearchbox) { | |
| 103 | + // setSearchbox(''); | |
| 104 | + // } | |
| 105 | + // } | |
| 106 | + // }) | |
| 107 | + | |
| 108 | + const { errors, touched, values, handleBlur, handleChange, handleSubmit, setFieldValue, resetForm } = formik; | |
| 109 | + | |
| 110 | + return ( | |
| 111 | + <Modal onClose={() => { setAdvaceSearch(false); resetForm() }} isOpen={advaceSearch} isCentered> | |
| 112 | + <ModalOverlay /> | |
| 113 | + <ModalContent> | |
| 114 | + <ModalHeader>Advance Search</ModalHeader> | |
| 115 | + <ModalCloseButton onClick={() => { setAdvaceSearch(false); resetForm(); }} /> | |
| 116 | + <ModalBody> | |
| 117 | + <Grid templateColumns="repeat(12, 1fr)" mb={3} gap={2}> | |
| 118 | + { | |
| 119 | + tableCustomFields?.map((field) => ( | |
| 120 | + <GridItem colSpan={{ base: 12, sm: (field.type === 'date' ? 12 : 6) }} key={field?.name}> | |
| 121 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='600' color={"#000"} mb="0" mt={2} htmlFor={field?.name}> | |
| 122 | + {field?.label} | |
| 123 | + </FormLabel> | |
| 124 | + {field.type === 'select' ? | |
| 125 | + <Select | |
| 126 | + fontSize='sm' | |
| 127 | + id={field.name} | |
| 128 | + name={field.name} | |
| 129 | + onChange={handleChange} | |
| 130 | + onBlur={handleBlur} | |
| 131 | + value={values?.[field?.name]} | |
| 132 | + fontWeight='500' | |
| 133 | + // borderColor={errors?.[field?.name] && touched?.[field?.name] ? "red.300" : null} | |
| 134 | + > | |
| 135 | + <option value="">Select {field?.label}</option> | |
| 136 | + {field.options.map(option => ( | |
| 137 | + <option key={option?._id} value={option?.value}> | |
| 138 | + {option?.name} | |
| 139 | + </option> | |
| 140 | + ))} | |
| 141 | + </Select> | |
| 142 | + : field.type === 'date' ? ( | |
| 143 | + <> | |
| 144 | + <Flex justifyContent="space-between"> | |
| 145 | + <Box w="49%"> | |
| 146 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='600' color={"#000"} mb="0" > | |
| 147 | + From | |
| 148 | + </FormLabel> | |
| 149 | + <Input | |
| 150 | + fontSize='sm' | |
| 151 | + onChange={handleChange} | |
| 152 | + onBlur={handleBlur} | |
| 153 | + value={values[`from${field.name}`]} | |
| 154 | + type="date" | |
| 155 | + name={`from${field.name}`} | |
| 156 | + fontWeight='500' | |
| 157 | + /> | |
| 158 | + </Box> | |
| 159 | + <Box w="49%"> | |
| 160 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='600' color={"#000"} mb="0"> | |
| 161 | + To | |
| 162 | + </FormLabel> | |
| 163 | + <Input | |
| 164 | + fontSize='sm' | |
| 165 | + onChange={handleChange} | |
| 166 | + onBlur={handleBlur} | |
| 167 | + value={values[`to${field.name}`]} | |
| 168 | + type="date" | |
| 169 | + min={values[`from${field.name}`]} | |
| 170 | + name={`to${field.name}`} | |
| 171 | + fontWeight='500' | |
| 172 | + /> | |
| 173 | + </Box> | |
| 174 | + </Flex> | |
| 175 | + {/* <Text mb='10px' color={'red'}> {errors.fromLeadScore && touched.fromLeadScore && errors.fromLeadScore}</Text> */} | |
| 176 | + </> | |
| 177 | + ) : <InputGroup> | |
| 178 | + {/* {field.type === 'tel' && <InputLeftElement | |
| 179 | + pointerEvents="none" | |
| 180 | + children={<PhoneIcon color="gray.300" borderRadius="16px" />} | |
| 181 | + />} */} | |
| 182 | + < Input | |
| 183 | + fontSize='sm' | |
| 184 | + type={field.type} | |
| 185 | + id={field.name} | |
| 186 | + name={field.name} | |
| 187 | + onChange={handleChange} | |
| 188 | + onBlur={handleBlur} | |
| 189 | + value={values[field.name]} | |
| 190 | + fontWeight='500' | |
| 191 | + placeholder={`Enter ${field.label}`} | |
| 192 | + // borderColor={errors?.[field?.name] && touched?.[field?.name] ? "red.300" : null} | |
| 193 | + /> | |
| 194 | + </InputGroup> | |
| 195 | + } | |
| 196 | + </GridItem> | |
| 197 | + )) | |
| 198 | + } | |
| 199 | + </Grid> | |
| 200 | + </ModalBody> | |
| 201 | + <ModalFooter> | |
| 202 | + <Button variant="outline" colorScheme='green' size="sm" mr={2} onClick={handleSubmit} disabled={isLoding ? true : false} >{isLoding ? <Spinner /> : 'Search'}</Button> | |
| 203 | + <Button colorScheme="red" size="sm" onClick={() => resetForm()}>Clear</Button> | |
| 204 | + </ModalFooter> | |
| 205 | + </ModalContent> | |
| 206 | + </Modal> | |
| 207 | + ) | |
| 208 | +} | |
| 209 | + | |
| 210 | +export default AdvanceSearch |
+56-0Client/src/components/search/search.js
| @@ -0,0 +1,56 @@ | ||
| 1 | +import React, { useState } from 'react'; | |
| 2 | +import { InputGroup, InputLeftElement, Input } from '@chakra-ui/react'; | |
| 3 | +import { SearchIcon } from '@chakra-ui/icons'; | |
| 4 | + | |
| 5 | +const CustomSearchInput = ({ allData, setSearchbox, setDisplaySearchData, searchbox, dataColumn, onSearch, setGetTagValues, setGopageValue }) => { | |
| 6 | + | |
| 7 | + const handleInputChange = (e) => { | |
| 8 | + const searchTerm = e.target.value?.toLowerCase(); | |
| 9 | + | |
| 10 | + const results = allData?.filter((item) => { | |
| 11 | + // Check if any of the specified columns contains the search term | |
| 12 | + return dataColumn?.some((column) => { | |
| 13 | + const columnValue = item[column?.accessor]; | |
| 14 | + | |
| 15 | + return ( | |
| 16 | + columnValue && | |
| 17 | + typeof columnValue === 'string' ? | |
| 18 | + columnValue?.toLowerCase()?.includes(searchTerm) : typeof columnValue === 'number' && columnValue?.toString()?.includes(searchTerm) | |
| 19 | + ); | |
| 20 | + }); | |
| 21 | + }); | |
| 22 | + | |
| 23 | + setSearchbox(searchTerm ? searchTerm : ''); | |
| 24 | + setDisplaySearchData(e.target.value === "" ? false : true) | |
| 25 | + onSearch(results); | |
| 26 | + setGetTagValues([]); | |
| 27 | + | |
| 28 | + if (e.target.value === "" && setGopageValue) { | |
| 29 | + setGopageValue(1); | |
| 30 | + } | |
| 31 | + }; | |
| 32 | + | |
| 33 | + return ( | |
| 34 | + <InputGroup width={{ sm: "100%", md: "30%" }} mx={{ sm: 0, md: 3 }} my={{ sm: "8px", md: "0" }} > | |
| 35 | + <InputLeftElement | |
| 36 | + size="sm" | |
| 37 | + top="-3px" | |
| 38 | + pointerEvents="none" | |
| 39 | + zIndex='0' | |
| 40 | + children={<SearchIcon color="gray.300" borderRadius="16px" />} | |
| 41 | + /> | |
| 42 | + <Input | |
| 43 | + type="text" | |
| 44 | + size="sm" | |
| 45 | + fontSize="sm" | |
| 46 | + value={searchbox} | |
| 47 | + onChange={handleInputChange} | |
| 48 | + fontWeight="500" | |
| 49 | + placeholder="Search..." | |
| 50 | + borderRadius="16px" | |
| 51 | + /> | |
| 52 | + </InputGroup> | |
| 53 | + ); | |
| 54 | +}; | |
| 55 | + | |
| 56 | +export default CustomSearchInput; |
+14-0Client/src/components/separator/Separator.jsx
| @@ -0,0 +1,14 @@ | ||
| 1 | +import { Flex } from "@chakra-ui/react"; | |
| 2 | +import React from "react"; | |
| 3 | + | |
| 4 | +const HSeparator = (props) => { | |
| 5 | + const { variant, children, ...rest } = props; | |
| 6 | + return <Flex h='1px' w='100%' bg='rgba(135, 140, 189, 0.3)' {...rest}></Flex>; | |
| 7 | +}; | |
| 8 | + | |
| 9 | +const VSeparator = (props) => { | |
| 10 | + const { variant, children, ...rest } = props; | |
| 11 | + return <Flex w='1px' bg='rgba(135, 140, 189, 0.3)' {...rest}></Flex>; | |
| 12 | +}; | |
| 13 | + | |
| 14 | +export { HSeparator, VSeparator }; |
+130-0Client/src/components/sidebar/Sidebar.js
| @@ -0,0 +1,130 @@ | ||
| 1 | +import React, { useState } from "react"; | |
| 2 | + | |
| 3 | +// chakra imports | |
| 4 | +import { | |
| 5 | + Box, | |
| 6 | + Flex, | |
| 7 | + Drawer, | |
| 8 | + DrawerBody, | |
| 9 | + Icon, | |
| 10 | + useColorModeValue, | |
| 11 | + DrawerOverlay, | |
| 12 | + useDisclosure, | |
| 13 | + DrawerContent, | |
| 14 | + DrawerCloseButton, | |
| 15 | +} from "@chakra-ui/react"; | |
| 16 | +import Content from "components/sidebar/components/Content"; | |
| 17 | +import { | |
| 18 | + renderThumb, | |
| 19 | + renderTrack, | |
| 20 | + renderView, | |
| 21 | +} from "components/scrollbar/Scrollbar"; | |
| 22 | +import { Scrollbars } from "react-custom-scrollbars-2"; | |
| 23 | +import PropTypes from "prop-types"; | |
| 24 | + | |
| 25 | +// Assets | |
| 26 | +import { IoMenuOutline } from "react-icons/io5"; | |
| 27 | + | |
| 28 | +function Sidebar(props) { | |
| 29 | + const { routes, setOpenSidebar, openSidebar, largeLogo } = props; | |
| 30 | + | |
| 31 | + let variantChange = "0.2s linear"; | |
| 32 | + let shadow = useColorModeValue( | |
| 33 | + "14px 17px 40px 4px rgba(112, 144, 176, 0.08)", | |
| 34 | + "unset" | |
| 35 | + ); | |
| 36 | + // Chakra Color Mode | |
| 37 | + let sidebarBg = useColorModeValue("white", "navy.800"); | |
| 38 | + let sidebarMargins = "0px"; | |
| 39 | + const { isOpen, onOpen, onClose } = useDisclosure(); | |
| 40 | + // SIDEBAR | |
| 41 | + return ( | |
| 42 | + <Box display={{ sm: "none", xl: "block" }} w="100%" position='fixed' minH='100%'> | |
| 43 | + <Box | |
| 44 | + bg={sidebarBg} | |
| 45 | + transition={variantChange} | |
| 46 | + // w='280px' | |
| 47 | + w={openSidebar ? '280px' : "80px"} | |
| 48 | + h='100vh' | |
| 49 | + m={sidebarMargins} | |
| 50 | + minH='100%' | |
| 51 | + overflowX='hidden' | |
| 52 | + boxShadow={shadow}> | |
| 53 | + <Scrollbars | |
| 54 | + autoHide | |
| 55 | + renderTrackVertical={renderTrack} | |
| 56 | + renderThumbVertical={renderThumb} | |
| 57 | + renderView={renderView}> | |
| 58 | + <Content routes={routes} largeLogo={largeLogo} openSidebar={openSidebar} setOpenSidebar={setOpenSidebar} /> | |
| 59 | + </Scrollbars> | |
| 60 | + </Box> | |
| 61 | + </Box> | |
| 62 | + ); | |
| 63 | +} | |
| 64 | + | |
| 65 | +// FUNCTIONS | |
| 66 | +export function SidebarResponsive(props) { | |
| 67 | + let sidebarBackgroundColor = useColorModeValue("white", "navy.800"); | |
| 68 | + let menuColor = useColorModeValue("gray.400", "white"); | |
| 69 | + // // SIDEBAR | |
| 70 | + const { isOpen, onOpen, onClose } = useDisclosure(); | |
| 71 | + const btnRef = React.useRef(); | |
| 72 | + const { routes, setOpenSidebar, openSidebar } = props; | |
| 73 | + // let isWindows = navigator.platform.startsWith("Win"); | |
| 74 | + // BRAND | |
| 75 | + const handlesidebarClose = () => { | |
| 76 | + // setOpenSidebar(false) | |
| 77 | + onClose(); | |
| 78 | + } | |
| 79 | + return ( | |
| 80 | + <Flex display={{ sm: "flex", xl: "none" }} alignItems='center'> | |
| 81 | + <Flex | |
| 82 | + ref={btnRef} | |
| 83 | + w='max-content' h='max-content' onClick={() => { onOpen(); setOpenSidebar(true) }}> | |
| 84 | + <Icon | |
| 85 | + as={IoMenuOutline} | |
| 86 | + color={menuColor} | |
| 87 | + my='auto' | |
| 88 | + w='20px' | |
| 89 | + h='20px' | |
| 90 | + me='10px' | |
| 91 | + _hover={{ cursor: "pointer" }} | |
| 92 | + /> | |
| 93 | + </Flex> | |
| 94 | + | |
| 95 | + <Drawer | |
| 96 | + isOpen={isOpen} | |
| 97 | + onClose={handlesidebarClose} | |
| 98 | + placement={document.documentElement.dir === "rtl" ? "right" : "left"} | |
| 99 | + finalFocusRef={btnRef}> | |
| 100 | + <DrawerOverlay /> | |
| 101 | + <DrawerContent boxShadow={'xl'} w='285px' maxW='285px' bg={sidebarBackgroundColor}> | |
| 102 | + <DrawerCloseButton | |
| 103 | + zIndex='3' | |
| 104 | + onClose={handlesidebarClose} | |
| 105 | + _focus={{ boxShadow: "none" }} | |
| 106 | + _hover={{ boxShadow: "none" }} | |
| 107 | + /> | |
| 108 | + <DrawerBody maxW='285px' px='0rem' pb='0'> | |
| 109 | + <Scrollbars | |
| 110 | + autoHide | |
| 111 | + renderTrackVertical={renderTrack} | |
| 112 | + renderThumbVertical={renderThumb} | |
| 113 | + renderView={renderView}> | |
| 114 | + <Content from={"modal"} routes={routes} openSidebar={openSidebar} setOpenSidebar={setOpenSidebar} /> | |
| 115 | + </Scrollbars> | |
| 116 | + </DrawerBody> | |
| 117 | + </DrawerContent> | |
| 118 | + </Drawer> | |
| 119 | + </Flex> | |
| 120 | + ); | |
| 121 | +} | |
| 122 | +// PROPS | |
| 123 | + | |
| 124 | +Sidebar.propTypes = { | |
| 125 | + logoText: PropTypes.string, | |
| 126 | + routes: PropTypes.arrayOf(PropTypes.object), | |
| 127 | + variant: PropTypes.string, | |
| 128 | +}; | |
| 129 | + | |
| 130 | +export default Sidebar; |
+42-0Client/src/components/sidebar/components/Brand.js
| @@ -0,0 +1,42 @@ | ||
| 1 | + | |
| 2 | +// Chakra imports | |
| 3 | +import { Flex, Heading, Image, useColorModeValue } from "@chakra-ui/react"; | |
| 4 | + | |
| 5 | +// Custom components | |
| 6 | +import { HSeparator } from "components/separator/Separator"; | |
| 7 | +import { useEffect } from "react"; | |
| 8 | +import { useDispatch, useSelector } from "react-redux"; | |
| 9 | +import { fetchImage } from "../../../redux/slices/imageSlice"; | |
| 10 | + | |
| 11 | +export function SidebarBrand(props) { | |
| 12 | + const { setOpenSidebar, openSidebar, from, largeLogo } = props; | |
| 13 | + | |
| 14 | + // Chakra color mode | |
| 15 | + let logoColor = useColorModeValue("navy.700", "white"); | |
| 16 | + | |
| 17 | + return ( | |
| 18 | + <Flex align='center' direction='column' style={{ | |
| 19 | + position: "sticky", | |
| 20 | + top: "0", | |
| 21 | + left: "0", | |
| 22 | + background: "#fff", | |
| 23 | + | |
| 24 | + }}> | |
| 25 | + <Flex> | |
| 26 | + {largeLogo && (largeLogo[0]?.logoLgImg || largeLogo[0]?.logoSmImg) ? <Image | |
| 27 | + style={{ width: "100%", height: '52px' }} | |
| 28 | + src={openSidebar === true ? largeLogo[0]?.logoLgImg : largeLogo[0]?.logoSmImg} // Set the source path of your image | |
| 29 | + alt="Logo" // Set the alt text for accessibility | |
| 30 | + cursor="pointer" | |
| 31 | + onClick={() => !from && setOpenSidebar(!openSidebar)} | |
| 32 | + userSelect="none" | |
| 33 | + my={2} | |
| 34 | + /> : <Heading my={4} | |
| 35 | + cursor={"pointer"} onClick={() => !from && setOpenSidebar(!openSidebar)} userSelect={"none"}>{openSidebar === true ? "Prolink" : "Pr"}</Heading>} | |
| 36 | + </Flex> | |
| 37 | + | |
| 38 | + </Flex> | |
| 39 | + ); | |
| 40 | +} | |
| 41 | + | |
| 42 | +export default SidebarBrand; |
+44-0Client/src/components/sidebar/components/Content.js
| @@ -0,0 +1,44 @@ | ||
| 1 | +// chakra imports | |
| 2 | +import { Box, Flex, Stack, Text } from "@chakra-ui/react"; | |
| 3 | +// Custom components | |
| 4 | +import Brand from "components/sidebar/components/Brand"; | |
| 5 | +import Links from "components/sidebar/components/Links"; | |
| 6 | +import SidebarCard from "components/sidebar/components/SidebarCard"; | |
| 7 | +import React from "react"; | |
| 8 | +import { HiOutlineChevronDoubleRight, HiOutlineChevronLeft, HiOutlineChevronRight } from "react-icons/hi"; | |
| 9 | + | |
| 10 | +// FUNCTIONS | |
| 11 | + | |
| 12 | +function SidebarContent(props) { | |
| 13 | + const { routes, setOpenSidebar, openSidebar, from, largeLogo } = props; | |
| 14 | + | |
| 15 | + // SIDEBAR | |
| 16 | + return ( | |
| 17 | + <Flex direction='column' height='100%' | |
| 18 | + borderRadius='30px'> | |
| 19 | + <Brand from={from} largeLogo={largeLogo} | |
| 20 | + openSidebar={openSidebar} setOpenSidebar={setOpenSidebar} | |
| 21 | + /> | |
| 22 | + <Stack direction='column' mb='auto' pt={2} | |
| 23 | + > | |
| 24 | + | |
| 25 | + <Box | |
| 26 | + > | |
| 27 | + <Links routes={routes} key={routes} | |
| 28 | + openSidebar={openSidebar} setOpenSidebar={setOpenSidebar} | |
| 29 | + | |
| 30 | + /> | |
| 31 | + </Box> | |
| 32 | + </Stack> | |
| 33 | + | |
| 34 | + {/* <Box | |
| 35 | + mt='60px' | |
| 36 | + mb='40px' | |
| 37 | + borderRadius='30px'> | |
| 38 | + <SidebarCard /> | |
| 39 | + </Box> */} | |
| 40 | + </Flex > | |
| 41 | + ); | |
| 42 | +} | |
| 43 | + | |
| 44 | +export default SidebarContent; |
+175-0Client/src/components/sidebar/components/Links.js
| @@ -0,0 +1,175 @@ | ||
| 1 | +/* eslint-disable */ | |
| 2 | +import { NavLink, useLocation } from "react-router-dom"; | |
| 3 | +// chakra imports | |
| 4 | +import { AbsoluteCenter, Box, Divider, Flex, HStack, Text, Tooltip, useColorModeValue, useDisclosure } from "@chakra-ui/react"; | |
| 5 | + | |
| 6 | +export function SidebarLinks(props) { | |
| 7 | + // Chakra color mode | |
| 8 | + let location = useLocation(); | |
| 9 | + let activeColor = useColorModeValue("brand.600", "white"); | |
| 10 | + let inactiveColor = useColorModeValue( | |
| 11 | + "secondaryGray.600", | |
| 12 | + "secondaryGray.600" | |
| 13 | + ); | |
| 14 | + let activeIcon = useColorModeValue("brand.600", "white"); | |
| 15 | + let textColor = useColorModeValue("secondaryGray.500", "white"); | |
| 16 | + let brandColor = useColorModeValue("brand.500", "brand.400"); | |
| 17 | + let sidebarBgColor = useColorModeValue("gray.200", "brand.200"); | |
| 18 | + const { isOpen, onOpen, onClose } = useDisclosure(); | |
| 19 | + | |
| 20 | + const user = JSON.parse(localStorage.getItem("user")) | |
| 21 | + | |
| 22 | + const { routes, setOpenSidebar, openSidebar } = props; | |
| 23 | + | |
| 24 | + // verifies if routeName is the one active (in browser input) | |
| 25 | + const activeRoute = (routeName) => { | |
| 26 | + return location.pathname === routeName; | |
| 27 | + }; | |
| 28 | + | |
| 29 | + | |
| 30 | + // this function creates the links from the secondary accordions (for example auth -> sign-in -> default) | |
| 31 | + const createLinks = (routes) => { | |
| 32 | + | |
| 33 | + return routes?.map((route, index) => { | |
| 34 | + if (route?.category) { | |
| 35 | + return ( | |
| 36 | + <> | |
| 37 | + <Text | |
| 38 | + fontSize={"md"} | |
| 39 | + color={activeColor} | |
| 40 | + fontWeight='bold' | |
| 41 | + mx='auto' | |
| 42 | + ps={{ | |
| 43 | + sm: "10px", | |
| 44 | + xl: "16px", | |
| 45 | + }} | |
| 46 | + pt='18px' | |
| 47 | + pb='10px' | |
| 48 | + key={index}> | |
| 49 | + {route?.name} | |
| 50 | + </Text> | |
| 51 | + {createLinks(route?.items)} | |
| 52 | + </> | |
| 53 | + ); | |
| 54 | + } else if (!route?.under && user?.role && route?.layout?.includes(`/${user?.role}`)) { | |
| 55 | + return ( | |
| 56 | + <NavLink key={index} to={route.path}> | |
| 57 | + {route?.separator && | |
| 58 | + <Box position='relative' | |
| 59 | + margin='20px 0' | |
| 60 | + > | |
| 61 | + <Divider /> | |
| 62 | + <AbsoluteCenter textTransform={'capitalize'} bg='white' width={'max-content'} padding='0 10px' textAlign={'center'}> | |
| 63 | + {route?.separator} | |
| 64 | + </AbsoluteCenter> | |
| 65 | + </Box> | |
| 66 | + } | |
| 67 | + { | |
| 68 | + route.icon ? ( | |
| 69 | + <Box backgroundColor={activeRoute(route.path.toLowerCase()) | |
| 70 | + ? sidebarBgColor | |
| 71 | + : ""} | |
| 72 | + ps={"25px"} pb={"6px"} pt={"10px"}> | |
| 73 | + | |
| 74 | + <HStack | |
| 75 | + spacing={activeRoute(route.path.toLowerCase()) ? "22px" : "26px"} | |
| 76 | + py='5px' | |
| 77 | + > | |
| 78 | + {openSidebar === true ? | |
| 79 | + <Flex w='100%' alignItems='center' justifyContent='center' | |
| 80 | + // onClick={() => setOpenSidebar(!openSidebar)} | |
| 81 | + > | |
| 82 | + <Box | |
| 83 | + color={ | |
| 84 | + activeRoute(route.path.toLowerCase()) | |
| 85 | + ? activeIcon | |
| 86 | + : textColor | |
| 87 | + } | |
| 88 | + me='18px' > | |
| 89 | + {route.icon} | |
| 90 | + </Box> | |
| 91 | + <Text | |
| 92 | + me='auto' | |
| 93 | + pb={"3px"} | |
| 94 | + textOverflow={"ellipsis"} | |
| 95 | + textTransform={'capitalize'} | |
| 96 | + overflowX="hidden" | |
| 97 | + whiteSpace='nowrap' | |
| 98 | + width="190px" | |
| 99 | + color={ | |
| 100 | + activeRoute(route.path.toLowerCase()) | |
| 101 | + ? activeColor | |
| 102 | + : textColor | |
| 103 | + } | |
| 104 | + fontWeight={ | |
| 105 | + activeRoute(route.path.toLowerCase()) | |
| 106 | + ? "bold" | |
| 107 | + : "normal" | |
| 108 | + }> | |
| 109 | + <Tooltip hasArrow label={route.name}> | |
| 110 | + {route.name} | |
| 111 | + </Tooltip> | |
| 112 | + </Text> | |
| 113 | + </Flex> | |
| 114 | + : | |
| 115 | + <Flex w='100%' alignItems='center' justifyContent='center' | |
| 116 | + // onClick={() => setOpenSidebar(!openSidebar)} | |
| 117 | + > | |
| 118 | + <Box | |
| 119 | + color={ | |
| 120 | + activeRoute(route.path.toLowerCase()) | |
| 121 | + ? activeIcon | |
| 122 | + : textColor | |
| 123 | + } | |
| 124 | + me='18px' > | |
| 125 | + {route.icon} | |
| 126 | + </Box> | |
| 127 | + </Flex>} | |
| 128 | + <Box | |
| 129 | + // h='36px' | |
| 130 | + w='4px' | |
| 131 | + bg={ | |
| 132 | + activeRoute(route.path.toLowerCase()) | |
| 133 | + ? brandColor | |
| 134 | + : brandColor | |
| 135 | + } | |
| 136 | + borderRadius='5px' | |
| 137 | + /> | |
| 138 | + </HStack> | |
| 139 | + </Box> | |
| 140 | + ) : ( | |
| 141 | + <Box> | |
| 142 | + <HStack | |
| 143 | + spacing={ | |
| 144 | + activeRoute(route.path.toLowerCase()) ? "22px" : "26px" | |
| 145 | + } | |
| 146 | + py='5px' | |
| 147 | + ps='10px'> | |
| 148 | + <Text | |
| 149 | + me='auto' | |
| 150 | + color={ | |
| 151 | + activeRoute(route.path.toLowerCase()) | |
| 152 | + ? activeColor | |
| 153 | + : inactiveColor | |
| 154 | + } | |
| 155 | + fontWeight={ | |
| 156 | + activeRoute(route.path.toLowerCase()) ? "bold" : "normal" | |
| 157 | + }> | |
| 158 | + {route.name} | |
| 159 | + </Text> | |
| 160 | + <Box h='36px' w='4px' bg='brand.400' borderRadius='5px' /> | |
| 161 | + </HStack> | |
| 162 | + </Box> | |
| 163 | + ) | |
| 164 | + } | |
| 165 | + </NavLink> | |
| 166 | + ); | |
| 167 | + } | |
| 168 | + | |
| 169 | + }); | |
| 170 | + }; | |
| 171 | + // BRAND | |
| 172 | + return createLinks(routes); | |
| 173 | +} | |
| 174 | + | |
| 175 | +export default SidebarLinks; |
+84-0Client/src/components/sidebar/components/SidebarCard.js
| @@ -0,0 +1,84 @@ | ||
| 1 | +import { | |
| 2 | + Button, | |
| 3 | + Flex, | |
| 4 | + Image, | |
| 5 | + Link, | |
| 6 | + Text, | |
| 7 | + useColorModeValue, | |
| 8 | +} from "@chakra-ui/react"; | |
| 9 | +import logoWhite from "assets/img/layout/logoWhite.png"; | |
| 10 | +import React from "react"; | |
| 11 | + | |
| 12 | +export default function SidebarDocs() { | |
| 13 | + const bgColor = "linear-gradient(135deg, #868CFF 0%, #4318FF 100%)"; | |
| 14 | + const borderColor = useColorModeValue("white", "navy.800"); | |
| 15 | + return ( | |
| 16 | + <Flex | |
| 17 | + justify='center' | |
| 18 | + direction='column' | |
| 19 | + align='center' | |
| 20 | + bg={bgColor} | |
| 21 | + borderRadius='30px' | |
| 22 | + position='relative'> | |
| 23 | + <Flex | |
| 24 | + border='5px solid' | |
| 25 | + borderColor={borderColor} | |
| 26 | + bg='linear-gradient(135deg, #868CFF 0%, #4318FF 100%)' | |
| 27 | + borderRadius='50%' | |
| 28 | + w='94px' | |
| 29 | + h='94px' | |
| 30 | + align='center' | |
| 31 | + justify='center' | |
| 32 | + mx='auto' | |
| 33 | + position='absolute' | |
| 34 | + left='50%' | |
| 35 | + top='-47px' | |
| 36 | + transform='translate(-50%, 0%)'> | |
| 37 | + <Image src={logoWhite} w='40px' h='40px' /> | |
| 38 | + </Flex> | |
| 39 | + <Flex | |
| 40 | + direction='column' | |
| 41 | + mb='12px' | |
| 42 | + align='center' | |
| 43 | + justify='center' | |
| 44 | + px='15px' | |
| 45 | + pt='55px'> | |
| 46 | + <Text | |
| 47 | + fontSize={{ base: "lg", xl: "18px" }} | |
| 48 | + color='white' | |
| 49 | + fontWeight='bold' | |
| 50 | + lineHeight='150%' | |
| 51 | + textAlign='center' | |
| 52 | + px='10px' | |
| 53 | + mt="10px" | |
| 54 | + mb='6px'> | |
| 55 | + Upgrade to PRO | |
| 56 | + </Text> | |
| 57 | + <Text | |
| 58 | + fontSize='14px' | |
| 59 | + color={"white"} | |
| 60 | + fontWeight='500' | |
| 61 | + px='10px' | |
| 62 | + mb='6px' | |
| 63 | + textAlign='center'> | |
| 64 | + Improve your development process and start doing more with Horizon UI | |
| 65 | + PRO! | |
| 66 | + </Text> | |
| 67 | + </Flex> | |
| 68 | + <Link href='https://horizon-ui.com/pro?ref=horizon-chakra-free'> | |
| 69 | + <Button | |
| 70 | + bg='whiteAlpha.300' | |
| 71 | + _hover={{ bg: "whiteAlpha.200" }} | |
| 72 | + _active={{ bg: "whiteAlpha.100" }} | |
| 73 | + mb={{ sm: "16px", xl: "24px" }} | |
| 74 | + color={"white"} | |
| 75 | + fontWeight='regular' | |
| 76 | + fontSize='sm' | |
| 77 | + minW='185px' | |
| 78 | + mx='auto'> | |
| 79 | + Upgrade to PRO | |
| 80 | + </Button> | |
| 81 | + </Link> | |
| 82 | + </Flex> | |
| 83 | + ); | |
| 84 | +} |
+4-0Client/src/components/spinner/Spinner.js
| @@ -0,0 +1,4 @@ | ||
| 1 | +import './style.css'; | |
| 2 | +const Spinner = () => <div className="loader"></div>; | |
| 3 | + | |
| 4 | +export default Spinner; |
+18-0Client/src/components/spinner/style.css
| @@ -0,0 +1,18 @@ | ||
| 1 | +.loader { | |
| 2 | + border: 5px solid #f3f3f3; | |
| 3 | + border-top: 5px solid #3442df; | |
| 4 | + width: 30px; | |
| 5 | + border-radius: 50%; | |
| 6 | + height: 30px; | |
| 7 | + animation: spin 1s linear infinite; | |
| 8 | +} | |
| 9 | + | |
| 10 | +@keyframes spin { | |
| 11 | + 0% { | |
| 12 | + transform: rotate(0deg); | |
| 13 | + } | |
| 14 | + | |
| 15 | + 100% { | |
| 16 | + transform: rotate(360deg); | |
| 17 | + } | |
| 18 | +} |
+3-0Client/src/constant.js
| @@ -0,0 +1,3 @@ | ||
| 1 | +export const constant = { | |
| 2 | + baseUrl: process.env.REACT_APP_BASE_URL | |
| 3 | +} |
+31-0Client/src/contexts/RoleContext.js
| @@ -0,0 +1,31 @@ | ||
| 1 | +import React, { createContext, useContext, useEffect, useState } from 'react'; | |
| 2 | +import { getApi } from 'services/api'; | |
| 3 | + | |
| 4 | +// Create context | |
| 5 | +const RoleContext = createContext(); | |
| 6 | + | |
| 7 | +// Create provider | |
| 8 | +export const RoleProvider = ({ children }) => { | |
| 9 | + const [roleData, setRoleData] = useState(null); | |
| 10 | + | |
| 11 | + const user = JSON.parse(localStorage.getItem('user')) | |
| 12 | + | |
| 13 | + // Fetch user data from API | |
| 14 | + useEffect(async () => { | |
| 15 | + // Call API here | |
| 16 | + // Example using fetch: | |
| 17 | + if (user) { | |
| 18 | + const response = await getApi(`api/user/view/${user?._id}`) | |
| 19 | + setRoleData(response?.data?.roles) | |
| 20 | + } | |
| 21 | + }, []); | |
| 22 | + | |
| 23 | + return ( | |
| 24 | + <RoleContext.Provider value={roleData}> | |
| 25 | + {children} | |
| 26 | + </RoleContext.Provider> | |
| 27 | + ); | |
| 28 | +}; | |
| 29 | + | |
| 30 | +// Custom hook to access user data | |
| 31 | +export const useRole = () => useContext(RoleContext); |
+3-0Client/src/contexts/SidebarContext.js
| @@ -0,0 +1,3 @@ | ||
| 1 | +import { createContext } from "react"; | |
| 2 | + | |
| 3 | +export const SidebarContext = createContext(); |
+57-0Client/src/index.js
| @@ -0,0 +1,57 @@ | ||
| 1 | +import React, { useEffect, useState } from 'react'; | |
| 2 | +import ReactDOM from 'react-dom'; | |
| 3 | +import 'assets/css/App.css'; | |
| 4 | +import { BrowserRouter as Router, Routes, Route, useNavigate } from 'react-router-dom'; | |
| 5 | +import AuthLayout from './layouts/auth'; | |
| 6 | +import AdminLayout from 'layouts/admin'; | |
| 7 | +import UserLayout from 'layouts/user'; | |
| 8 | +import { ChakraProvider } from '@chakra-ui/react'; | |
| 9 | +import theme from 'theme/theme'; | |
| 10 | +import { ThemeEditorProvider } from '@hypertheme-editor/chakra-ui'; | |
| 11 | +import { ToastContainer } from 'react-toastify'; | |
| 12 | +import 'react-toastify/dist/ReactToastify.css'; | |
| 13 | +import { Provider } from 'react-redux'; | |
| 14 | +import { store, persistor } from './redux/store'; | |
| 15 | +import { PersistGate } from 'redux-persist/integration/react'; | |
| 16 | + | |
| 17 | +function App() { | |
| 18 | + const token = localStorage.getItem("token") || sessionStorage.getItem("token"); | |
| 19 | + | |
| 20 | + const user = JSON.parse(localStorage.getItem("user")) | |
| 21 | + useNavigate() | |
| 22 | + | |
| 23 | + return ( | |
| 24 | + <> | |
| 25 | + <ToastContainer /> | |
| 26 | + <Routes> | |
| 27 | + {token && user?.role ? ( | |
| 28 | + user?.role === 'user' ? | |
| 29 | + <Route path="/*" element={<UserLayout />} /> | |
| 30 | + : user?.role === 'superAdmin' ? | |
| 31 | + <Route path="/*" element={<AdminLayout />} /> | |
| 32 | + : '' | |
| 33 | + ) : ( | |
| 34 | + <Route path="/*" element={<AuthLayout />} /> | |
| 35 | + )} | |
| 36 | + </Routes> | |
| 37 | + </> | |
| 38 | + ); | |
| 39 | +} | |
| 40 | + | |
| 41 | +ReactDOM.render( | |
| 42 | + <Provider store={store}> | |
| 43 | + <PersistGate loading={null} persistor={persistor}> | |
| 44 | + <ChakraProvider theme={theme}> | |
| 45 | + <React.StrictMode> | |
| 46 | + <ThemeEditorProvider> | |
| 47 | + <Router> | |
| 48 | + <App /> | |
| 49 | + </Router> | |
| 50 | + </ThemeEditorProvider> | |
| 51 | + </React.StrictMode> | |
| 52 | + </ChakraProvider> | |
| 53 | + </PersistGate> | |
| 54 | + </Provider> | |
| 55 | + , document.getElementById('root') | |
| 56 | +); | |
| 57 | + |
+344-0Client/src/layouts/admin/index.js
| @@ -0,0 +1,344 @@ | ||
| 1 | +// Chakra imports | |
| 2 | +import { Portal, Box, useDisclosure, Flex, Icon } from '@chakra-ui/react'; | |
| 3 | +import Footer from 'components/footer/FooterAdmin.js'; | |
| 4 | +// Layout components | |
| 5 | +import Navbar from 'components/navbar/NavbarAdmin.js'; | |
| 6 | +import Sidebar from 'components/sidebar/Sidebar.js'; | |
| 7 | +import Spinner from 'components/spinner/Spinner'; | |
| 8 | +import { SidebarContext } from 'contexts/SidebarContext'; | |
| 9 | +import React, { Suspense, useEffect } from 'react'; | |
| 10 | +import { useState } from 'react'; | |
| 11 | +import { Navigate, Route, Routes } from 'react-router-dom'; | |
| 12 | +import { ROLE_PATH } from '../../roles'; | |
| 13 | +import newRoutes from 'routes.js'; | |
| 14 | +import { useDispatch, useSelector } from 'react-redux'; | |
| 15 | +import { fetchImage } from '../../redux/slices/imageSlice'; | |
| 16 | +import { getApi } from 'services/api'; | |
| 17 | +import { MdHome, MdLock } from 'react-icons/md'; | |
| 18 | +import DynamicPage from 'views/admin/dynamicPage'; | |
| 19 | +import DynamicPageview from 'views/admin/dynamicPage/DynamicPageview'; | |
| 20 | +import { fetchRouteData } from '../../redux/slices/routeSlice'; | |
| 21 | +import { LuChevronRightCircle } from 'react-icons/lu'; | |
| 22 | +import { fetchRoles } from '../../redux/slices/roleSlice'; | |
| 23 | +import { fetchModules } from '../../redux/slices/moduleSlice'; | |
| 24 | + | |
| 25 | +const MainDashboard = React.lazy(() => import("views/admin/default")); | |
| 26 | + | |
| 27 | +// Custom Chakra theme | |
| 28 | +export default function Dashboard(props) { | |
| 29 | + const { ...rest } = props; | |
| 30 | + // states and functions | |
| 31 | + const [fixed] = useState(false); | |
| 32 | + const [toggleSidebar, setToggleSidebar] = useState(false); | |
| 33 | + const [openSidebar, setOpenSidebar] = useState(false) | |
| 34 | + // const user = JSON.parse(localStorage.getItem("user")) | |
| 35 | + const userId = JSON.parse(localStorage.getItem("user"))?._id; | |
| 36 | + | |
| 37 | + // let routes = newRoutes; | |
| 38 | + const [routes, setRoutes] = useState(newRoutes) | |
| 39 | + const route = useSelector((state) => state?.route?.data) | |
| 40 | + const modules = useSelector((state) => state?.modules?.data) | |
| 41 | + const dispatch = useDispatch(); | |
| 42 | + | |
| 43 | + const pathName = (name) => { | |
| 44 | + return `/${name?.toLowerCase()?.replace(/ /g, '-')}`; | |
| 45 | + } | |
| 46 | + | |
| 47 | + const getRoute = () => { | |
| 48 | + return window.location.pathname !== '/admin/full-screen-maps'; | |
| 49 | + }; | |
| 50 | + | |
| 51 | + const dynamicRoute = () => { | |
| 52 | + let apiData = [] | |
| 53 | + | |
| 54 | + route && route?.length > 0 && route?.map((item, i) => { | |
| 55 | + let rec = routes.find(route => route?.name === item?.moduleName) | |
| 56 | + if (!routes.some(route => route?.name === item?.moduleName)) { | |
| 57 | + | |
| 58 | + const newRoute = [{ | |
| 59 | + name: item?.moduleName, | |
| 60 | + layout: [ROLE_PATH.superAdmin], | |
| 61 | + path: pathName(item.moduleName), | |
| 62 | + icon: item?.icon ? ( | |
| 63 | + <img src={item?.icon} width="20px" height="20px" alt="icon" /> | |
| 64 | + ) : ( | |
| 65 | + <Icon as={LuChevronRightCircle} width="20px" height="20px" color="inherit" /> | |
| 66 | + ), | |
| 67 | + component: DynamicPage, | |
| 68 | + }, | |
| 69 | + { | |
| 70 | + name: item?.moduleName, | |
| 71 | + layout: [ROLE_PATH.superAdmin], | |
| 72 | + under: item?.moduleName, | |
| 73 | + parentName: item?.moduleName, | |
| 74 | + path: `${pathName(item.moduleName)}/:id`, | |
| 75 | + icon: item?.icon ? ( | |
| 76 | + <img src={item?.icon} width="20px" height="20px" alt="icon" /> | |
| 77 | + ) : ( | |
| 78 | + <Icon as={LuChevronRightCircle} width="20px" height="20px" color="inherit" /> | |
| 79 | + ), | |
| 80 | + component: DynamicPageview, | |
| 81 | + } | |
| 82 | + ] | |
| 83 | + setRoutes((pre) => [...pre, ...newRoute]) | |
| 84 | + } else if (routes.some(route => route?.name === item?.moduleName) && rec.icon?.props?.src !== item?.icon) { | |
| 85 | + | |
| 86 | + const updatedData = routes?.map(i => { | |
| 87 | + if (i.name === item?.moduleName) { | |
| 88 | + return { ...i, icon: <img src={item?.icon} width="20px" height="20px" alt="icon" /> }; | |
| 89 | + } | |
| 90 | + return i; | |
| 91 | + }); | |
| 92 | + setRoutes(updatedData) | |
| 93 | + } | |
| 94 | + if (routes.find(route => route?.name !== item?.moduleName)) { | |
| 95 | + | |
| 96 | + if (!newRoutes.find(route => route?.name?.toLowerCase() === item?.moduleName?.toLowerCase())) { | |
| 97 | + | |
| 98 | + const newRoute = [{ | |
| 99 | + name: item?.moduleName, | |
| 100 | + layout: [ROLE_PATH.superAdmin], | |
| 101 | + path: pathName(item.moduleName), | |
| 102 | + icon: item?.icon ? ( | |
| 103 | + <img src={item?.icon} width="20px" height="20px" alt="icon" /> | |
| 104 | + ) : ( | |
| 105 | + <Icon as={LuChevronRightCircle} width="20px" height="20px" color="inherit" /> | |
| 106 | + ), | |
| 107 | + component: DynamicPage, | |
| 108 | + }, | |
| 109 | + { | |
| 110 | + name: item?.moduleName, | |
| 111 | + layout: [ROLE_PATH.superAdmin], | |
| 112 | + under: item?.moduleName, | |
| 113 | + parentName: item?.moduleName, | |
| 114 | + path: `${pathName(item.moduleName)}/:id`, | |
| 115 | + icon: item?.icon ? ( | |
| 116 | + <img src={item?.icon} width="20px" height="20px" alt="icon" /> | |
| 117 | + ) : ( | |
| 118 | + <Icon as={LuChevronRightCircle} width="20px" height="20px" color="inherit" /> | |
| 119 | + ), | |
| 120 | + component: DynamicPageview, | |
| 121 | + } | |
| 122 | + ] | |
| 123 | + | |
| 124 | + apiData.push(...newRoute) | |
| 125 | + } | |
| 126 | + } | |
| 127 | + | |
| 128 | + }); | |
| 129 | + | |
| 130 | + let filterData = [...newRoutes, ...apiData] | |
| 131 | + | |
| 132 | + const activeModel = modules?.filter(module => module?.isActive)?.map(module => module?.moduleName); | |
| 133 | + | |
| 134 | + const activeRoutes = filterData?.filter( | |
| 135 | + (data) => | |
| 136 | + activeModel?.includes(data?.name) || | |
| 137 | + activeModel?.includes(data?.parentName) || | |
| 138 | + !modules?.some( | |
| 139 | + (module) => | |
| 140 | + module?.moduleName === data?.name || | |
| 141 | + module?.moduleName === data?.parentName | |
| 142 | + ) | |
| 143 | + ); | |
| 144 | + | |
| 145 | + setRoutes(activeRoutes) | |
| 146 | + | |
| 147 | + }; | |
| 148 | + | |
| 149 | + const getActiveRoute = (routes) => { | |
| 150 | + let activeRoute = 'Prolink'; | |
| 151 | + for (let i = 0; i < routes.length; i++) { | |
| 152 | + if (routes[i].collapse) { | |
| 153 | + let collapseActiveRoute = getActiveRoute(routes[i].items); | |
| 154 | + if (collapseActiveRoute !== activeRoute) { | |
| 155 | + return collapseActiveRoute; | |
| 156 | + } | |
| 157 | + } else if (routes[i].category) { | |
| 158 | + let categoryActiveRoute = getActiveRoute(routes[i].items); | |
| 159 | + if (categoryActiveRoute !== activeRoute) { | |
| 160 | + return categoryActiveRoute; | |
| 161 | + } | |
| 162 | + } else { | |
| 163 | + if (window.location.href.indexOf(routes[i].path.replace("/:id", "")) !== -1) { | |
| 164 | + return routes[i].name; | |
| 165 | + } | |
| 166 | + } | |
| 167 | + } | |
| 168 | + return activeRoute; | |
| 169 | + }; | |
| 170 | + | |
| 171 | + | |
| 172 | + useEffect(() => { | |
| 173 | + dynamicRoute(); | |
| 174 | + }, [route, modules]); | |
| 175 | + | |
| 176 | + useEffect(async () => { | |
| 177 | + if (window.location.pathname === "/default") { | |
| 178 | + await dispatch(fetchRouteData()); | |
| 179 | + await dispatch(fetchImage()); | |
| 180 | + } | |
| 181 | + await dispatch(fetchModules()) | |
| 182 | + }, []); | |
| 183 | + | |
| 184 | + const largeLogo = useSelector((state) => state?.images?.images?.filter(item => item?.isActive === true)); | |
| 185 | + | |
| 186 | + const under = (routes) => { | |
| 187 | + let activeRoute = false | |
| 188 | + for (let i = 0; i < routes.length; i++) { | |
| 189 | + if (routes[i].collapse) { | |
| 190 | + let collapseActiveRoute = getActiveRoute(routes[i].items); | |
| 191 | + if (collapseActiveRoute !== activeRoute) { | |
| 192 | + return collapseActiveRoute; | |
| 193 | + } | |
| 194 | + } else if (routes[i].category) { | |
| 195 | + let categoryActiveRoute = getActiveRoute(routes[i].items); | |
| 196 | + if (categoryActiveRoute !== activeRoute) { | |
| 197 | + return categoryActiveRoute; | |
| 198 | + } | |
| 199 | + } else { | |
| 200 | + if (window.location.href.indexOf(routes[i].path.replace("/:id", "")) !== -1) { | |
| 201 | + return routes[i]; | |
| 202 | + } | |
| 203 | + } | |
| 204 | + } | |
| 205 | + return activeRoute; | |
| 206 | + }; | |
| 207 | + | |
| 208 | + const getActiveNavbar = (routes) => { | |
| 209 | + let activeNavbar = false; | |
| 210 | + for (let i = 0; i < routes.length; i++) { | |
| 211 | + if (routes[i].collapse) { | |
| 212 | + let collapseActiveNavbar = getActiveNavbar(routes[i].items); | |
| 213 | + if (collapseActiveNavbar !== activeNavbar) { | |
| 214 | + return collapseActiveNavbar; | |
| 215 | + } | |
| 216 | + } else if (routes[i].category) { | |
| 217 | + let categoryActiveNavbar = getActiveNavbar(routes[i].items); | |
| 218 | + if (categoryActiveNavbar !== activeNavbar) { | |
| 219 | + return categoryActiveNavbar; | |
| 220 | + } | |
| 221 | + } else { | |
| 222 | + if (window.location.href.indexOf(routes[i].path) !== -1) { | |
| 223 | + return routes[i].secondary; | |
| 224 | + } | |
| 225 | + } | |
| 226 | + } | |
| 227 | + return activeNavbar; | |
| 228 | + }; | |
| 229 | + const getActiveNavbarText = (routes) => { | |
| 230 | + let activeNavbar = false; | |
| 231 | + for (let i = 0; i < routes.length; i++) { | |
| 232 | + if (routes[i].collapse) { | |
| 233 | + let collapseActiveNavbar = getActiveNavbarText(routes[i].items); | |
| 234 | + if (collapseActiveNavbar !== activeNavbar) { | |
| 235 | + return collapseActiveNavbar; | |
| 236 | + } | |
| 237 | + } else if (routes[i].category) { | |
| 238 | + let categoryActiveNavbar = getActiveNavbarText(routes[i].items); | |
| 239 | + if (categoryActiveNavbar !== activeNavbar) { | |
| 240 | + return categoryActiveNavbar; | |
| 241 | + } | |
| 242 | + } else { | |
| 243 | + if (window.location.href.indexOf(routes[i].path) !== -1) { | |
| 244 | + return routes[i].messageNavbar; | |
| 245 | + } | |
| 246 | + } | |
| 247 | + } | |
| 248 | + return activeNavbar; | |
| 249 | + }; | |
| 250 | + | |
| 251 | + const getRoutes = (routes) => { | |
| 252 | + return routes.map((prop, key) => { | |
| 253 | + // if (!prop.under && prop.layout === '/superAdmin') { | |
| 254 | + if (!prop.under && prop.layout?.includes(ROLE_PATH.superAdmin)) { | |
| 255 | + return <Route path={prop.path} element={<prop.component />} key={key} />; | |
| 256 | + } else if (prop.under) { | |
| 257 | + return <Route path={prop.path} element={<prop.component />} key={key} /> | |
| 258 | + } | |
| 259 | + if (prop.collapse) { | |
| 260 | + return getRoutes(prop.items); | |
| 261 | + } | |
| 262 | + if (prop.category) { | |
| 263 | + return getRoutes(prop.items); | |
| 264 | + } else { | |
| 265 | + return null; | |
| 266 | + } | |
| 267 | + }); | |
| 268 | + }; | |
| 269 | + | |
| 270 | + useEffect(() => { | |
| 271 | + if (window.location.pathname === "/default") { | |
| 272 | + dispatch(fetchRoles(userId)) | |
| 273 | + } | |
| 274 | + }, [userId]); | |
| 275 | + | |
| 276 | + document.documentElement.dir = 'ltr'; | |
| 277 | + const { onOpen } = useDisclosure(); | |
| 278 | + document.documentElement.dir = 'ltr'; | |
| 279 | + return ( | |
| 280 | + <Box> | |
| 281 | + <Box> | |
| 282 | + <SidebarContext.Provider | |
| 283 | + value={{ | |
| 284 | + toggleSidebar, | |
| 285 | + setToggleSidebar | |
| 286 | + }}> | |
| 287 | + <Sidebar routes={routes} largeLogo={largeLogo} display='none' {...rest} openSidebar={openSidebar} setOpenSidebar={setOpenSidebar} /> | |
| 288 | + <Box | |
| 289 | + float='right' | |
| 290 | + minHeight='100vh' | |
| 291 | + height='100%' | |
| 292 | + | |
| 293 | + overflow='auto' | |
| 294 | + position='relative' | |
| 295 | + maxHeight='100%' | |
| 296 | + // w={{ base: '100%', xl: 'calc( 100% - 290px )' }} | |
| 297 | + w={{ base: '100%', xl: openSidebar === true ? 'calc( 100% - 300px )' : 'calc( 100% - 88px )' }} | |
| 298 | + maxWidth={{ base: '100%', xl: openSidebar === true ? 'calc( 100% - 300px )' : 'calc( 100% - 88px )' }} | |
| 299 | + transition='all 0.33s cubic-bezier(0.685, 0.0473, 0.346, 1)' | |
| 300 | + transitionDuration='.2s, .2s, .35s' | |
| 301 | + transitionProperty='top, bottom, width' | |
| 302 | + transitionTimingFunction='linear, linear, ease'> | |
| 303 | + <Portal > | |
| 304 | + <Box className="header"> | |
| 305 | + <Navbar | |
| 306 | + onOpen={onOpen} | |
| 307 | + logoText={'Horizon UI Dashboard PRO'} | |
| 308 | + brandText={getActiveRoute(routes)} | |
| 309 | + secondary={getActiveNavbar(routes)} | |
| 310 | + message={getActiveNavbarText(routes)} | |
| 311 | + routes={routes} | |
| 312 | + fixed={fixed} | |
| 313 | + under={under(routes)} | |
| 314 | + largeLogo={largeLogo} | |
| 315 | + openSidebar={openSidebar} setOpenSidebar={setOpenSidebar} | |
| 316 | + {...rest} | |
| 317 | + /> | |
| 318 | + </Box> | |
| 319 | + </Portal> | |
| 320 | + <Box pt={{ base: "150px", md: "95px", xl: "95px" }}> | |
| 321 | + {getRoute() ? ( | |
| 322 | + <Box mx='auto' pe='20px' minH='84vh' pt='50px' style={{ padding: openSidebar ? '8px 20px 8px 0px' : '8px 20px' }}> | |
| 323 | + <Suspense fallback={ | |
| 324 | + <Flex justifyContent={'center'} alignItems={'center'} width="100%" > | |
| 325 | + <Spinner /> | |
| 326 | + </Flex> | |
| 327 | + }> | |
| 328 | + <Routes> | |
| 329 | + {getRoutes(routes)} | |
| 330 | + <Route path="/*" element={<Navigate to="/default" />} /> | |
| 331 | + </Routes> | |
| 332 | + </Suspense> | |
| 333 | + </Box> | |
| 334 | + ) : null} | |
| 335 | + </Box> | |
| 336 | + <Box> | |
| 337 | + <Footer /> | |
| 338 | + </Box> | |
| 339 | + </Box> | |
| 340 | + </SidebarContext.Provider> | |
| 341 | + </Box> | |
| 342 | + </Box> | |
| 343 | + ); | |
| 344 | +} |
+77-0Client/src/layouts/auth/Default.js
| @@ -0,0 +1,77 @@ | ||
| 1 | +// Chakra imports | |
| 2 | +import { Box, Flex } from "@chakra-ui/react"; | |
| 3 | +import Footer from "components/footer/FooterAuth"; | |
| 4 | +import FixedPlugin from "components/fixedPlugin/FixedPlugin"; | |
| 5 | +import { Link } from "react-router-dom"; | |
| 6 | +// Custom components | |
| 7 | +// Assets | |
| 8 | + | |
| 9 | +function AuthIllustration(props) { | |
| 10 | + const { children, illustrationBackground } = props; | |
| 11 | + // Chakra color mode | |
| 12 | + return ( | |
| 13 | + <Flex h='max-content'> | |
| 14 | + | |
| 15 | + <Flex | |
| 16 | + h={{ | |
| 17 | + sm: "initial", | |
| 18 | + md: "unset", | |
| 19 | + lg: "100vh", | |
| 20 | + xl: "97vh", | |
| 21 | + }} | |
| 22 | + w='100%' | |
| 23 | + maxW={{ md: "66%", lg: "1313px" }} | |
| 24 | + mx='auto' | |
| 25 | + pt={{ sm: "50px", md: "0px" }} | |
| 26 | + px={{ lg: "30px", xl: "0px" }} | |
| 27 | + ps={{ xl: "70px" }} | |
| 28 | + justifyContent='center' | |
| 29 | + direction='column'> | |
| 30 | + {children} | |
| 31 | + <Box | |
| 32 | + display={{ base: "none", md: "flex" }} | |
| 33 | + h='100%' | |
| 34 | + minH='100vh' | |
| 35 | + w={{ lg: "50vw", "2xl": "44vw" }} | |
| 36 | + borderBottomLeftRadius={{ lg: "120px", xl: "200px" }} | |
| 37 | + justifyContent='center' | |
| 38 | + position='absolute' | |
| 39 | + flexDirection={'column'} | |
| 40 | + alignItems={'center'} | |
| 41 | + // bg={'black'} | |
| 42 | + overflow={'hidden'} | |
| 43 | + right='0px'> | |
| 44 | + <Link to="https://prolinkinfotech.com/" target="_blank" style={{ height: '50%', width: '50%' }} > | |
| 45 | + <Flex | |
| 46 | + bg={`url(${illustrationBackground})`} | |
| 47 | + justify='center' | |
| 48 | + align='center' | |
| 49 | + w='100%' | |
| 50 | + h='100%' | |
| 51 | + bgSize='cover' | |
| 52 | + bgPosition='50%' | |
| 53 | + /> | |
| 54 | + </Link> | |
| 55 | + {/* <Box | |
| 56 | + border={'1px solid'} | |
| 57 | + p={4} | |
| 58 | + borderRadius={10} | |
| 59 | + > | |
| 60 | + Hi | |
| 61 | + </Box> */} | |
| 62 | + </Box> | |
| 63 | + <Footer /> | |
| 64 | + </Flex> | |
| 65 | + {/* CHANGE THEME COLOR BUTTON LIGHT-DARK */} | |
| 66 | + {/* <FixedPlugin /> */} | |
| 67 | + </Flex> | |
| 68 | + ); | |
| 69 | +} | |
| 70 | +// PROPS | |
| 71 | + | |
| 72 | +// AuthIllustration.propTypes = { | |
| 73 | +// illustrationBackground: PropTypes.string, | |
| 74 | +// image: PropTypes.any, | |
| 75 | +// }; | |
| 76 | + | |
| 77 | +export default AuthIllustration; |
+82-0Client/src/layouts/auth/index.js
| @@ -0,0 +1,82 @@ | ||
| 1 | +import { Suspense, useState } from "react"; | |
| 2 | +import { Navigate, Route, Routes } from "react-router-dom"; | |
| 3 | +import routes from "routes.js"; | |
| 4 | + | |
| 5 | +// Chakra imports | |
| 6 | +import { Box, Flex, useColorModeValue } from "@chakra-ui/react"; | |
| 7 | + | |
| 8 | +// Layout components | |
| 9 | +import { SidebarContext } from "contexts/SidebarContext"; | |
| 10 | +import Spinner from "components/spinner/Spinner"; | |
| 11 | + | |
| 12 | +// Custom Chakra theme | |
| 13 | +export default function Auth({ setIsLogin }) { | |
| 14 | + | |
| 15 | + // states and functions | |
| 16 | + const [toggleSidebar, setToggleSidebar] = useState(false); | |
| 17 | + // functions for changing the states from components | |
| 18 | + const getRoute = () => { | |
| 19 | + return window.location.pathname !== "/auth/full-screen-maps"; | |
| 20 | + }; | |
| 21 | + const getRoutes = (routes) => { | |
| 22 | + return routes.map((prop, key) => { | |
| 23 | + if (prop.layout === "/auth") { | |
| 24 | + return ( | |
| 25 | + <Route | |
| 26 | + path={prop.layout + prop.path} | |
| 27 | + element={<prop.component />} | |
| 28 | + key={key} | |
| 29 | + /> | |
| 30 | + ); | |
| 31 | + } | |
| 32 | + if (prop.collapse) { | |
| 33 | + return getRoutes(prop.items); | |
| 34 | + } | |
| 35 | + if (prop.category) { | |
| 36 | + return getRoutes(prop.items); | |
| 37 | + } else { | |
| 38 | + return null; | |
| 39 | + } | |
| 40 | + }); | |
| 41 | + }; | |
| 42 | + | |
| 43 | + const authBg = useColorModeValue("white", "navy.900"); | |
| 44 | + document.documentElement.dir = "ltr"; | |
| 45 | + return ( | |
| 46 | + <Box> | |
| 47 | + <SidebarContext.Provider | |
| 48 | + value={{ | |
| 49 | + toggleSidebar, | |
| 50 | + setToggleSidebar, | |
| 51 | + }}> | |
| 52 | + <Box | |
| 53 | + bg={authBg} | |
| 54 | + float='right' | |
| 55 | + minHeight='100vh' | |
| 56 | + height='100%' | |
| 57 | + position='relative' | |
| 58 | + w='100%' | |
| 59 | + transition='all 0.33s cubic-bezier(0.685, 0.0473, 0.346, 1)' | |
| 60 | + transitionDuration='.2s, .2s, .35s' | |
| 61 | + transitionProperty='top, bottom, width' | |
| 62 | + transitionTimingFunction='linear, linear, ease' | |
| 63 | + > | |
| 64 | + {getRoute() ? ( | |
| 65 | + <Box mx='auto' minH='100vh' > | |
| 66 | + <Suspense fallback={ | |
| 67 | + <Flex justifyContent={'center'} alignItems={'center'} width="100%" height={'100vh'}> | |
| 68 | + <Spinner /> | |
| 69 | + </Flex> | |
| 70 | + }> | |
| 71 | + <Routes> | |
| 72 | + {getRoutes(routes)} | |
| 73 | + <Route path="/*" element={<Navigate to="/auth/sign-in" />} /> | |
| 74 | + </Routes> | |
| 75 | + </Suspense> | |
| 76 | + </Box> | |
| 77 | + ) : null} | |
| 78 | + </Box> | |
| 79 | + </SidebarContext.Provider> | |
| 80 | + </Box> | |
| 81 | + ); | |
| 82 | +} |
+339-0Client/src/layouts/user/index.js
| @@ -0,0 +1,339 @@ | ||
| 1 | +// Chakra imports | |
| 2 | +import { Portal, Box, useDisclosure, Text, Button, Link, Flex, Icon } from '@chakra-ui/react'; | |
| 3 | +import Footer from 'components/footer/FooterAdmin.js'; | |
| 4 | +// Layout components | |
| 5 | +import Navbar from 'components/navbar/NavbarAdmin.js'; | |
| 6 | +import Sidebar from 'components/sidebar/Sidebar.js'; | |
| 7 | +import { SidebarContext } from 'contexts/SidebarContext'; | |
| 8 | +import React, { Suspense, useEffect, useState } from 'react'; | |
| 9 | +import { Navigate, Route, Routes } from 'react-router-dom'; | |
| 10 | +import { ROLE_PATH } from '../../roles'; | |
| 11 | +import newRoute from 'routes.js'; | |
| 12 | +import { MdHome, MdLock } from 'react-icons/md'; | |
| 13 | +import Spinner from 'components/spinner/Spinner'; | |
| 14 | +import { useDispatch, useSelector } from 'react-redux'; | |
| 15 | +import { fetchImage } from '../../redux/slices/imageSlice'; | |
| 16 | +import { getApi } from 'services/api'; | |
| 17 | +import DynamicPage from 'views/admin/dynamicPage'; | |
| 18 | +import { LuChevronRightCircle } from 'react-icons/lu'; | |
| 19 | +import { FaCalendarAlt } from 'react-icons/fa'; | |
| 20 | +import { fetchModules } from '../../redux/slices/moduleSlice'; | |
| 21 | + | |
| 22 | +const MainDashboard = React.lazy(() => import("views/admin/default")); | |
| 23 | +const SignInCentered = React.lazy(() => import("views/auth/signIn")); | |
| 24 | +const Calender = React.lazy(() => import("views/admin/calender")); | |
| 25 | +const UserView = React.lazy(() => import("views/admin/users/View")); | |
| 26 | + | |
| 27 | +// Custom Chakra theme | |
| 28 | +export default function User(props) { | |
| 29 | + const { ...rest } = props; | |
| 30 | + // states and functions | |
| 31 | + const [fixed] = useState(false); | |
| 32 | + const [toggleSidebar, setToggleSidebar] = useState(false); | |
| 33 | + const [route, setRoute] = useState(); | |
| 34 | + const [openSidebar, setOpenSidebar] = useState(true) | |
| 35 | + const user = JSON.parse(localStorage.getItem("user")) | |
| 36 | + const modules = useSelector((state) => state?.modules?.data) | |
| 37 | + // functions for changing the states from components | |
| 38 | + const getRoute = () => { | |
| 39 | + return window.location.pathname !== '/admin/full-screen-maps'; | |
| 40 | + }; | |
| 41 | + | |
| 42 | + | |
| 43 | + const fetchRoute = async () => { | |
| 44 | + let response = await getApi("api/route/"); | |
| 45 | + setRoute(response?.data); | |
| 46 | + }; | |
| 47 | + | |
| 48 | + const pathName = (name) => { | |
| 49 | + return `/${name.toLowerCase().replace(/ /g, '-')}`; | |
| 50 | + } | |
| 51 | + | |
| 52 | + | |
| 53 | + useEffect(() => { | |
| 54 | + fetchRoute(); | |
| 55 | + }, []); | |
| 56 | + | |
| 57 | + const layoutName = user?.roles?.map(item => `/${item.roleName}`) | |
| 58 | + | |
| 59 | + const filterAccess = (rolesData) => { | |
| 60 | + return rolesData?.map(role => { | |
| 61 | + role.access = role?.access?.filter(access => access.view); | |
| 62 | + return role; | |
| 63 | + }); | |
| 64 | + }; | |
| 65 | + | |
| 66 | + // Example usage: | |
| 67 | + const updatedRolesData = filterAccess(user?.roles); | |
| 68 | + let access = [] | |
| 69 | + updatedRolesData?.map((item) => { | |
| 70 | + item?.access?.map((data) => access.push(data)) | |
| 71 | + }) | |
| 72 | + | |
| 73 | + let mergedPermissions = {}; | |
| 74 | + | |
| 75 | + access.forEach((permission) => { | |
| 76 | + const { title, ...rest } = permission; | |
| 77 | + | |
| 78 | + if (!mergedPermissions[title]) { | |
| 79 | + mergedPermissions[title] = { ...rest }; | |
| 80 | + } else { | |
| 81 | + // Merge with priority to true values | |
| 82 | + Object.keys(rest).forEach((key) => { | |
| 83 | + if (mergedPermissions[title][key] !== true) { | |
| 84 | + mergedPermissions[title][key] = rest[key]; | |
| 85 | + } | |
| 86 | + }); | |
| 87 | + } | |
| 88 | + }); | |
| 89 | + | |
| 90 | + let routes = | |
| 91 | + [ | |
| 92 | + { | |
| 93 | + name: "Dashboard", | |
| 94 | + layout: [ROLE_PATH.user], | |
| 95 | + path: "/default", | |
| 96 | + icon: <Icon as={MdHome} width='20px' height='20px' color='inherit' />, | |
| 97 | + component: MainDashboard, | |
| 98 | + }, { | |
| 99 | + name: "Sign In", | |
| 100 | + layout: "/auth", | |
| 101 | + path: "/sign-in", | |
| 102 | + icon: <Icon as={MdLock} width='20px' height='20px' color='inherit' />, | |
| 103 | + component: SignInCentered, | |
| 104 | + }, | |
| 105 | + { | |
| 106 | + name: "Calender", | |
| 107 | + layout: [ROLE_PATH.user], | |
| 108 | + path: "/calender", | |
| 109 | + icon: <Icon as={FaCalendarAlt} width='20px' height='20px' color='inherit' />, | |
| 110 | + component: Calender, | |
| 111 | + }, | |
| 112 | + { | |
| 113 | + name: "User View", | |
| 114 | + layout: [ROLE_PATH.superAdmin, ROLE_PATH.user], | |
| 115 | + parentName: "Email", | |
| 116 | + under: "user", | |
| 117 | + path: "/userView/:id", | |
| 118 | + component: UserView, | |
| 119 | + }, | |
| 120 | + ] | |
| 121 | + | |
| 122 | + route?.map((item, i) => { | |
| 123 | + if (!newRoute.some(route => route.name === item.moduleName)) { | |
| 124 | + return ( | |
| 125 | + newRoute.push({ | |
| 126 | + name: item?.moduleName, | |
| 127 | + layout: [ROLE_PATH.user], | |
| 128 | + path: pathName(item.moduleName), | |
| 129 | + icon: <Icon as={LuChevronRightCircle} width='20px' height='20px' color='inherit' />, | |
| 130 | + component: DynamicPage, | |
| 131 | + }) | |
| 132 | + ) | |
| 133 | + } | |
| 134 | + }) | |
| 135 | + const accessRoute = newRoute?.filter(item => Object.keys(mergedPermissions)?.find(data => (data?.toLowerCase() === item?.name?.toLowerCase()) || (data?.toLowerCase() === item.parentName?.toLowerCase()))) | |
| 136 | + | |
| 137 | + // routes.push(...accessRoute) | |
| 138 | + let filterData = [...accessRoute] | |
| 139 | + | |
| 140 | + const activeModel = modules?.filter(module => module?.isActive)?.map(module => module?.moduleName); | |
| 141 | + | |
| 142 | + const activeRoutes = filterData?.filter( | |
| 143 | + (data) => | |
| 144 | + activeModel?.includes(data?.name) || | |
| 145 | + activeModel?.includes(data?.parentName) || | |
| 146 | + !modules?.some( | |
| 147 | + (module) => | |
| 148 | + module?.moduleName === data?.name || | |
| 149 | + module?.moduleName === data?.parentName | |
| 150 | + ) | |
| 151 | + ); | |
| 152 | + routes.push(...activeRoutes) | |
| 153 | + | |
| 154 | + const getActiveRoute = (routes) => { | |
| 155 | + let activeRoute = 'Prolink'; | |
| 156 | + for (let i = 0; i < routes.length; i++) { | |
| 157 | + if (routes[i].collapse) { | |
| 158 | + let collapseActiveRoute = getActiveRoute(routes[i].items); | |
| 159 | + if (collapseActiveRoute !== activeRoute) { | |
| 160 | + return collapseActiveRoute; | |
| 161 | + } | |
| 162 | + } else if (routes[i].category) { | |
| 163 | + let categoryActiveRoute = getActiveRoute(routes[i].items); | |
| 164 | + if (categoryActiveRoute !== activeRoute) { | |
| 165 | + return categoryActiveRoute; | |
| 166 | + } | |
| 167 | + } else { | |
| 168 | + if (window.location.href.indexOf(routes[i].path.replace("/:id", "")) !== -1) { | |
| 169 | + return routes[i].name; | |
| 170 | + } | |
| 171 | + } | |
| 172 | + } | |
| 173 | + return activeRoute; | |
| 174 | + }; | |
| 175 | + const under = (routes) => { | |
| 176 | + let activeRoute = false | |
| 177 | + for (let i = 0; i < routes?.length; i++) { | |
| 178 | + if (routes[i]?.collapse) { | |
| 179 | + let collapseActiveRoute = getActiveRoute(routes[i]?.items); | |
| 180 | + if (collapseActiveRoute !== activeRoute) { | |
| 181 | + return collapseActiveRoute; | |
| 182 | + } | |
| 183 | + } else if (routes[i]?.category) { | |
| 184 | + let categoryActiveRoute = getActiveRoute(routes[i]?.items); | |
| 185 | + if (categoryActiveRoute !== activeRoute) { | |
| 186 | + return categoryActiveRoute; | |
| 187 | + } | |
| 188 | + } else { | |
| 189 | + if (window.location.href?.indexOf(routes[i]?.path?.replace("/:id", "")) !== -1) { | |
| 190 | + return routes[i]; | |
| 191 | + } | |
| 192 | + } | |
| 193 | + } | |
| 194 | + return activeRoute; | |
| 195 | + }; | |
| 196 | + | |
| 197 | + const getActiveNavbar = (routes) => { | |
| 198 | + let activeNavbar = false; | |
| 199 | + for (let i = 0; i < routes.length; i++) { | |
| 200 | + if (routes[i].collapse) { | |
| 201 | + let collapseActiveNavbar = getActiveNavbar(routes[i].items); | |
| 202 | + if (collapseActiveNavbar !== activeNavbar) { | |
| 203 | + return collapseActiveNavbar; | |
| 204 | + } | |
| 205 | + } else if (routes[i].category) { | |
| 206 | + let categoryActiveNavbar = getActiveNavbar(routes[i].items); | |
| 207 | + if (categoryActiveNavbar !== activeNavbar) { | |
| 208 | + return categoryActiveNavbar; | |
| 209 | + } | |
| 210 | + } else { | |
| 211 | + if (window.location.href.indexOf(routes[i].path) !== -1) { | |
| 212 | + return routes[i].secondary; | |
| 213 | + } | |
| 214 | + } | |
| 215 | + } | |
| 216 | + return activeNavbar; | |
| 217 | + }; | |
| 218 | + const getActiveNavbarText = (routes) => { | |
| 219 | + let activeNavbar = false; | |
| 220 | + for (let i = 0; i < routes.length; i++) { | |
| 221 | + if (routes[i].collapse) { | |
| 222 | + let collapseActiveNavbar = getActiveNavbarText(routes[i].items); | |
| 223 | + if (collapseActiveNavbar !== activeNavbar) { | |
| 224 | + return collapseActiveNavbar; | |
| 225 | + } | |
| 226 | + } else if (routes[i].category) { | |
| 227 | + let categoryActiveNavbar = getActiveNavbarText(routes[i].items); | |
| 228 | + if (categoryActiveNavbar !== activeNavbar) { | |
| 229 | + return categoryActiveNavbar; | |
| 230 | + } | |
| 231 | + } else { | |
| 232 | + if (window.location.href.indexOf(routes[i].path) !== -1) { | |
| 233 | + return routes[i].messageNavbar; | |
| 234 | + } | |
| 235 | + } | |
| 236 | + } | |
| 237 | + return activeNavbar; | |
| 238 | + }; | |
| 239 | + | |
| 240 | + | |
| 241 | + const getRoutes = (routes) => { | |
| 242 | + return routes?.map((prop, key) => { | |
| 243 | + // if (!prop.under && prop.layout === '/admin') { | |
| 244 | + if (!prop?.under && prop?.layout !== '/auth') { | |
| 245 | + return <Route path={prop?.path} element={prop && <prop.component />} key={key} />; | |
| 246 | + } else if (prop?.under) { | |
| 247 | + return <Route path={prop?.path} element={prop && <prop.component />} key={key} /> | |
| 248 | + } | |
| 249 | + if (prop?.collapse) { | |
| 250 | + return getRoutes(prop?.items); | |
| 251 | + } | |
| 252 | + if (prop?.category) { | |
| 253 | + return getRoutes(prop?.items); | |
| 254 | + } else { | |
| 255 | + return null; | |
| 256 | + } | |
| 257 | + }); | |
| 258 | + }; | |
| 259 | + document.documentElement.dir = 'ltr'; | |
| 260 | + const { onOpen } = useDisclosure(); | |
| 261 | + document.documentElement.dir = 'ltr'; | |
| 262 | + | |
| 263 | + const dispatch = useDispatch(); | |
| 264 | + | |
| 265 | + useEffect(() => { | |
| 266 | + // Dispatch the fetchRoles action on component mount | |
| 267 | + dispatch(fetchImage()); | |
| 268 | + dispatch(fetchModules()) | |
| 269 | + | |
| 270 | + }, [dispatch]); | |
| 271 | + | |
| 272 | + const largeLogo = useSelector((state) => state?.images?.images?.filter(item => item?.isActive === true)); | |
| 273 | + | |
| 274 | + | |
| 275 | + return ( | |
| 276 | + <Box> | |
| 277 | + <Box> | |
| 278 | + <SidebarContext.Provider | |
| 279 | + value={{ | |
| 280 | + toggleSidebar, | |
| 281 | + setToggleSidebar | |
| 282 | + }}> | |
| 283 | + <Sidebar routes={routes} display='none' {...rest} openSidebar={openSidebar} setOpenSidebar={setOpenSidebar} largeLogo={largeLogo} /> | |
| 284 | + <Box | |
| 285 | + float='right' | |
| 286 | + minHeight='100vh' | |
| 287 | + height='100%' | |
| 288 | + overflow='auto' | |
| 289 | + position='relative' | |
| 290 | + maxHeight='100%' | |
| 291 | + // w={{ base: '100%', xl: 'calc( 100% - 290px )' }} | |
| 292 | + w={{ base: '100%', xl: openSidebar === true ? 'calc( 100% - 300px )' : 'calc( 100% - 88px )' }} | |
| 293 | + maxWidth={{ base: '100%', xl: openSidebar === true ? 'calc( 100% - 300px )' : 'calc( 100% - 88px )' }} | |
| 294 | + transition='all 0.33s cubic-bezier(0.685, 0.0473, 0.346, 1)' | |
| 295 | + transitionDuration='.2s, .2s, .35s' | |
| 296 | + transitionProperty='top, bottom, width' | |
| 297 | + transitionTimingFunction='linear, linear, ease'> | |
| 298 | + <Portal> | |
| 299 | + <Box className='header'> | |
| 300 | + <Navbar | |
| 301 | + onOpen={onOpen} | |
| 302 | + logoText={'Horizon UI Dashboard PRO'} | |
| 303 | + brandText={getActiveRoute(routes)} | |
| 304 | + secondary={getActiveNavbar(routes)} | |
| 305 | + message={getActiveNavbarText(routes)} | |
| 306 | + fixed={fixed} | |
| 307 | + routes={routes} | |
| 308 | + under={under(routes)} | |
| 309 | + largeLogo={largeLogo} | |
| 310 | + openSidebar={openSidebar} setOpenSidebar={setOpenSidebar} | |
| 311 | + {...rest} | |
| 312 | + /> | |
| 313 | + </Box> | |
| 314 | + </Portal> | |
| 315 | + <Box pt={{ base: "150px", md: "95px", xl: "95px" }}> | |
| 316 | + {getRoute() ? ( | |
| 317 | + <Box mx='auto' pe='20px' minH='84vh' pt='50px' style={{ padding: openSidebar ? '8px 20px 8px 20px' : '8px 20px' }}> | |
| 318 | + <Suspense fallback={ | |
| 319 | + <Flex justifyContent={'center'} alignItems={'center'} width="100%" > | |
| 320 | + <Spinner /> | |
| 321 | + </Flex> | |
| 322 | + }> | |
| 323 | + <Routes> | |
| 324 | + {getRoutes(routes)} | |
| 325 | + <Route path="/*" element={<Navigate to="/default" />} /> | |
| 326 | + </Routes> | |
| 327 | + </Suspense> | |
| 328 | + </Box> | |
| 329 | + ) : null} | |
| 330 | + </Box> | |
| 331 | + <Box> | |
| 332 | + <Footer /> | |
| 333 | + </Box> | |
| 334 | + </Box> | |
| 335 | + </SidebarContext.Provider> | |
| 336 | + </Box> | |
| 337 | + </Box> | |
| 338 | + ); | |
| 339 | +} |
+62-0Client/src/redux/accessUtils.js
| @@ -0,0 +1,62 @@ | ||
| 1 | +import { useEffect } from "react"; | |
| 2 | +import { useDispatch, useSelector } from "react-redux"; | |
| 3 | +import { fetchRoles } from "./slices/roleSlice"; | |
| 4 | + | |
| 5 | +export const HasAccess = (actions) => { | |
| 6 | + | |
| 7 | + const user = JSON.parse(localStorage.getItem('user')) | |
| 8 | + | |
| 9 | + const dispatch = useDispatch(); | |
| 10 | + | |
| 11 | + useEffect(() => { | |
| 12 | + // Dispatch the fetchRoles action on component mount | |
| 13 | + if (window.location.pathname === "/default") { | |
| 14 | + dispatch(fetchRoles(user?._id)); | |
| 15 | + } | |
| 16 | + }, [dispatch]); | |
| 17 | + | |
| 18 | + const roles = useSelector((state) => state?.roles?.roles); | |
| 19 | + | |
| 20 | + const rolesToCheck = roles?.map(item => item.roleName) | |
| 21 | + const mergedPermissions = {}; | |
| 22 | + const superAdminPermission = { | |
| 23 | + "create": true, | |
| 24 | + "update": true, | |
| 25 | + "delete": true, | |
| 26 | + "view": true, | |
| 27 | + "import": true, | |
| 28 | + "export": true, | |
| 29 | + }; | |
| 30 | + | |
| 31 | + actions?.forEach(action => { | |
| 32 | + const access = rolesToCheck.map(roleToCheck => { | |
| 33 | + const role = roles.find(r => r.roleName === roleToCheck); | |
| 34 | + return role?.access?.find(a => a.title === action); | |
| 35 | + }); | |
| 36 | + | |
| 37 | + access?.forEach(permission => { | |
| 38 | + // Check if permission is defined | |
| 39 | + if (permission) { | |
| 40 | + // Destructure permission only if it's defined | |
| 41 | + const { title, ...rest } = permission; | |
| 42 | + | |
| 43 | + if (!mergedPermissions[title]) { | |
| 44 | + mergedPermissions[title] = { ...rest }; | |
| 45 | + } else { | |
| 46 | + // Merge with priority to true values | |
| 47 | + Object.keys(rest).forEach(key => { | |
| 48 | + if (mergedPermissions[title][key] !== true) { | |
| 49 | + mergedPermissions[title][key] = rest[key]; | |
| 50 | + } | |
| 51 | + }); | |
| 52 | + } | |
| 53 | + } | |
| 54 | + }); | |
| 55 | + }); | |
| 56 | + | |
| 57 | + | |
| 58 | + // Return permissions for each action | |
| 59 | + return actions.map(action => ( | |
| 60 | + user?.role === "superAdmin" ? superAdminPermission : mergedPermissions[action] | |
| 61 | + )); | |
| 62 | +}; |
+40-0Client/src/redux/slices/accountSlice.js
| @@ -0,0 +1,40 @@ | ||
| 1 | +import { createAsyncThunk, createSlice } from '@reduxjs/toolkit'; | |
| 2 | +import { getApi } from '../../services/api' | |
| 3 | + | |
| 4 | +export const fetchAccountData = createAsyncThunk('fetchAccountData', async () => { | |
| 5 | + const user = JSON.parse(localStorage.getItem("user")); | |
| 6 | + try { | |
| 7 | + const response = await getApi(user.role === 'superAdmin' ? 'api/account/' : `api/account/?createBy=${user._id}`); | |
| 8 | + return response; | |
| 9 | + } catch (error) { | |
| 10 | + throw error; | |
| 11 | + } | |
| 12 | +}); | |
| 13 | + | |
| 14 | + | |
| 15 | +const accountSlice = createSlice({ | |
| 16 | + name: 'accountData', | |
| 17 | + initialState: { | |
| 18 | + data: [], | |
| 19 | + isLoading: false, | |
| 20 | + error: "", | |
| 21 | + }, | |
| 22 | + extraReducers: (builder) => { | |
| 23 | + builder | |
| 24 | + .addCase(fetchAccountData.pending, (state) => { | |
| 25 | + state.isLoading = true; | |
| 26 | + }) | |
| 27 | + .addCase(fetchAccountData.fulfilled, (state, action) => { | |
| 28 | + state.isLoading = false; | |
| 29 | + state.data = action.payload; | |
| 30 | + state.error = ""; | |
| 31 | + }) | |
| 32 | + .addCase(fetchAccountData.rejected, (state, action) => { | |
| 33 | + state.isLoading = false; | |
| 34 | + state.data = []; | |
| 35 | + state.error = action.error.message; | |
| 36 | + }); | |
| 37 | + }, | |
| 38 | +}); | |
| 39 | + | |
| 40 | +export default accountSlice.reducer; |
+163-0Client/src/redux/slices/advanceSearchSlice.js
| @@ -0,0 +1,163 @@ | ||
| 1 | +import { createSlice } from '@reduxjs/toolkit'; | |
| 2 | +import moment from 'moment'; | |
| 3 | + | |
| 4 | +const initialState = { | |
| 5 | + searchValue: {}, | |
| 6 | + getTagValues: [], | |
| 7 | + searchResult: [] | |
| 8 | +}; | |
| 9 | + | |
| 10 | +const advanceSearchSlice = createSlice({ | |
| 11 | + name: 'advanceSearchValue', | |
| 12 | + initialState, | |
| 13 | + reducers: { | |
| 14 | + setSearchValue(state, action) { | |
| 15 | + state.searchValue = action.payload; | |
| 16 | + }, | |
| 17 | + setGetTagValues(state, action) { | |
| 18 | + state.getTagValues = action.payload; | |
| 19 | + }, | |
| 20 | + getSearchData(state, action) { | |
| 21 | + switch (action.payload.type) { | |
| 22 | + case 'Tasks': | |
| 23 | + state.searchResult = action.payload.allData?.filter(item => { | |
| 24 | + return ((!action.payload.values?.title || (item?.title && item?.title.toLowerCase().includes(action.payload.values?.title?.toLowerCase()))) && | |
| 25 | + (!action.payload.values.status || (item?.status && item?.status.toLowerCase().includes(action.payload.values.status?.toLowerCase()))) && | |
| 26 | + (!action.payload.values?.category || (item?.category && item?.category.toLowerCase().includes(action.payload.values?.category?.toLowerCase()))) && | |
| 27 | + (!action.payload.values?.start || (item?.start && item?.start.toLowerCase().includes(action.payload.values?.start?.toLowerCase()))) && | |
| 28 | + (!action.payload.values?.end || (item?.end && item?.end.toString().includes(action.payload.values?.end))) && | |
| 29 | + (!action.payload.values?.assignToName || (item?.assignToName && item?.assignToName.toLowerCase().includes(action.payload.values?.assignToName?.toLowerCase()))) && | |
| 30 | + ([null, undefined, ''].includes(action.payload.values?.fromLeadScore) || [null, undefined, ''].includes(action.payload.values?.toLeadScore) || | |
| 31 | + ((item?.leadScore || item?.leadScore === 0) && | |
| 32 | + (parseInt(item?.leadScore, 10) >= parseInt(action.payload.values.fromLeadScore, 10) || 0) && | |
| 33 | + (parseInt(item?.leadScore, 10) <= parseInt(action.payload.values.toLeadScore, 10) || 0))) | |
| 34 | + ); | |
| 35 | + }); | |
| 36 | + break; | |
| 37 | + case 'TasksSearch': | |
| 38 | + state.searchResult = action.payload.searchData; | |
| 39 | + break; | |
| 40 | + case 'Meeting': | |
| 41 | + state.searchResult = action.payload.allData?.filter( | |
| 42 | + (item) => { | |
| 43 | + const itemDate = new Date(item.dateTime); | |
| 44 | + const momentDate = moment(itemDate).format('YYYY-MM-DD'); | |
| 45 | + const timeItemDate = new Date(item.timestamp); | |
| 46 | + const timeMomentDate = moment(timeItemDate).format('YYYY-MM-DD'); | |
| 47 | + return ( | |
| 48 | + (!action?.payload?.values?.agenda || (item?.agenda && item?.agenda.toLowerCase().includes(action?.payload?.values?.agenda?.toLowerCase()))) && | |
| 49 | + (!action?.payload?.values?.createBy || (item?.createBy && item?.createBy.toLowerCase().includes(action?.payload?.values?.createBy?.toLowerCase()))) && | |
| 50 | + (!action?.payload?.values?.startDate || (momentDate >= action?.payload?.values?.startDate)) && | |
| 51 | + (!action?.payload?.values?.endDate || (momentDate <= action?.payload?.values?.endDate)) && | |
| 52 | + (!action?.payload?.values?.timeStartDate || (timeMomentDate >= action?.payload?.values?.timeStartDate)) && | |
| 53 | + (!action?.payload?.values?.timeEndDate || (timeMomentDate <= action?.payload?.values?.timeEndDate))) | |
| 54 | + } | |
| 55 | + ) | |
| 56 | + break; | |
| 57 | + case 'MeetingSearch': | |
| 58 | + state.searchResult = action.payload.searchData; | |
| 59 | + break; | |
| 60 | + case 'Calls': | |
| 61 | + state.searchResult = action.payload.allData?.filter( | |
| 62 | + (item) => | |
| 63 | + (!action.payload.values?.senderName || (item?.senderName && item?.senderName.toLowerCase().includes(action.payload.values?.senderName?.toLowerCase()))) && | |
| 64 | + (!action.payload.values?.realetedTo || (action.payload.values.realetedTo === "contact" ? item.createBy : item.createByLead)) && | |
| 65 | + (!action.payload.values?.createByName || (item?.createByName && item?.createByName.toLowerCase().includes(action.payload.values?.createByName?.toLowerCase()))) | |
| 66 | + ) | |
| 67 | + break; | |
| 68 | + case 'CallsSearch': | |
| 69 | + state.searchResult = action.payload.searchData; | |
| 70 | + break; | |
| 71 | + case 'Leads': | |
| 72 | + state.searchResult = action.payload.allData?.filter( | |
| 73 | + (item) => | |
| 74 | + (!action.payload.values?.leadStatus || (item?.leadStatus && item?.leadStatus.toLowerCase().includes(action.payload.values?.leadStatus?.toLowerCase()))) | |
| 75 | + ) | |
| 76 | + break; | |
| 77 | + case 'Email': | |
| 78 | + state.searchResult = action.payload.allData?.filter( | |
| 79 | + (item) => | |
| 80 | + (!action.payload.values?.senderName || (item?.senderName && item?.senderName.toLowerCase().includes(action.payload.values?.senderName?.toLowerCase()))) && | |
| 81 | + (!action.payload.values?.realetedTo || (action.payload.values.realetedTo === "contact" ? item.createBy : item.createByLead)) && | |
| 82 | + (!action.payload.values?.createByName || (item?.createByName && item?.createByName.toLowerCase().includes(action.payload.values?.createByName?.toLowerCase()))) | |
| 83 | + ) | |
| 84 | + break; | |
| 85 | + case 'EmailSearch': | |
| 86 | + state.searchResult = action.payload.searchData; | |
| 87 | + break; | |
| 88 | + case 'Users': | |
| 89 | + state.searchResult = action?.payload?.allData?.filter( | |
| 90 | + (item) => | |
| 91 | + (!action.payload?.values?.firstName || (item?.firstName && item?.firstName.toLowerCase().includes(action.payload?.values?.firstName?.toLowerCase()))) && | |
| 92 | + (!action.payload?.values?.username || (item?.username && item?.username.toLowerCase().includes(action.payload?.values?.username?.toLowerCase()))) && | |
| 93 | + (!action.payload?.values?.lastName || (item?.lastName && item?.lastName.toLowerCase().includes(action.payload?.values?.lastName?.toLowerCase()))) | |
| 94 | + ) | |
| 95 | + break; | |
| 96 | + case 'UsersSearch': | |
| 97 | + state.searchResult = action.payload.searchData; | |
| 98 | + break; | |
| 99 | + case 'Opprtunity': | |
| 100 | + state.searchResult = action?.payload?.allData?.filter( | |
| 101 | + (item) => | |
| 102 | + (!action.payload?.values?.opportunityName || (item?.opportunityName && item?.opportunityName.toLowerCase().includes(action.payload?.values?.opportunityName?.toLowerCase()))) && | |
| 103 | + (!action.payload?.values?.accountName2 || (item?.accountName2 && item?.accountName2.toLowerCase().includes(action.payload?.values?.accountName2?.toLowerCase()))) && | |
| 104 | + (!action.payload?.values?.opportunityAmount || (item?.opportunityAmount && item?.opportunityAmount.toLowerCase().includes(action.payload?.values?.opportunityAmount?.toLowerCase()))) && | |
| 105 | + (!action.payload?.values?.expectedCloseDate || (item?.expectedCloseDate && item?.expectedCloseDate.toLowerCase().includes(action.payload?.values?.expectedCloseDate?.toLowerCase()))) && | |
| 106 | + (!action.payload?.values?.salesStage || (item?.salesStage && item?.salesStage.toLowerCase().includes(action.payload?.values?.salesStage?.toLowerCase()))) | |
| 107 | + ) | |
| 108 | + break; | |
| 109 | + case 'OpprtunitySearch': | |
| 110 | + state.searchResult = action.payload.searchData; | |
| 111 | + break; | |
| 112 | + case 'Account': | |
| 113 | + state.searchResult = action?.payload?.allData?.filter( | |
| 114 | + (item) => | |
| 115 | + (!action.payload?.values?.name || (item?.name && item?.name.toLowerCase().includes(action.payload?.values?.name?.toLowerCase()))) && | |
| 116 | + (!action.payload?.values?.officePhone || (item?.officePhone && item?.officePhone.toString().toLowerCase().includes(action.payload?.values?.officePhone?.toString().toLowerCase()))) && | |
| 117 | + (!action.payload?.values?.fax || (item?.fax && item?.fax.toString().toLowerCase().includes(action.payload?.values?.fax?.toString().toLowerCase()))) && | |
| 118 | + (!action.payload?.values?.emailAddress || (item?.emailAddress && item?.emailAddress.toLowerCase().includes(action.payload?.values?.emailAddress?.toLowerCase()))) | |
| 119 | + ) | |
| 120 | + break; | |
| 121 | + case 'AccountSearch': | |
| 122 | + state.searchResult = action.payload.searchData; | |
| 123 | + break; | |
| 124 | + case 'quotes': | |
| 125 | + state.searchResult = action?.payload?.allData?.filter( | |
| 126 | + (item) => | |
| 127 | + (!action.payload?.values?.quoteNumber || (item?.quoteNumber && item?.quoteNumber?.toString().toLowerCase().includes(action.payload?.values?.quoteNumber?.toString().toLowerCase()))) && | |
| 128 | + (!action.payload?.values?.title || (item?.title && item?.title.toString().toLowerCase().includes(action.payload?.values?.title?.toString().toLowerCase()))) && | |
| 129 | + (!action.payload?.values?.quoteStage || (item?.quoteStage && item?.quoteStage.toString().toLowerCase().includes(action.payload?.values?.quoteStage?.toString().toLowerCase()))) && | |
| 130 | + (!action.payload?.values?.contactName || (item?.contactName && item?.contactName.toLowerCase().includes(action.payload?.values?.contactName?.toLowerCase()))) && | |
| 131 | + (!action.payload?.values?.accountName || (item?.accountName && item?.accountName.toLowerCase().includes(action.payload?.values?.accountName?.toLowerCase()))) && | |
| 132 | + (!action.payload?.values?.grandTotal || (item?.grandTotal && item?.grandTotal?.toString().toLowerCase().includes(action.payload?.values?.grandTotal?.toString().toLowerCase()))) && | |
| 133 | + (!action.payload?.values?.validUntil || (item?.validUntil && item?.validUntil.toLowerCase().includes(action.payload?.values?.validUntil?.toLowerCase()))) | |
| 134 | + ) | |
| 135 | + break; | |
| 136 | + case 'QuotesSearch': | |
| 137 | + state.searchResult = action.payload.searchData; | |
| 138 | + break; | |
| 139 | + case 'invoice': | |
| 140 | + state.searchResult = action?.payload?.allData?.filter( | |
| 141 | + (item) => | |
| 142 | + (!action.payload?.values?.invoiceNumber || (item?.invoiceNumber && item?.invoiceNumber?.toString().toLowerCase().includes(action.payload?.values?.invoiceNumber?.toString().toLowerCase()))) && | |
| 143 | + (!action.payload?.values?.title || (item?.title && item?.title.toString().toLowerCase().includes(action.payload?.values?.title?.toString().toLowerCase()))) && | |
| 144 | + (!action.payload?.values?.status || (item?.status && item?.status.toString().toLowerCase().includes(action.payload?.values?.status?.toString().toLowerCase()))) && | |
| 145 | + (!action.payload?.values?.contactName || (item?.contactName && item?.contactName.toLowerCase().includes(action.payload?.values?.contactName?.toLowerCase()))) && | |
| 146 | + (!action.payload?.values?.accountName || (item?.accountName && item?.accountName.toLowerCase().includes(action.payload?.values?.accountName?.toLowerCase()))) && | |
| 147 | + (!action.payload?.values?.grandTotal || (item?.grandTotal && item?.grandTotal?.toString().toLowerCase().includes(action.payload?.values?.grandTotal?.toString().toLowerCase()))) | |
| 148 | + ) | |
| 149 | + break; | |
| 150 | + case 'InvoiceSearch': | |
| 151 | + state.searchResult = action.payload.searchData; | |
| 152 | + break; | |
| 153 | + case 'template': | |
| 154 | + state.searchResult = action.payload.searchData; | |
| 155 | + break; | |
| 156 | + default: | |
| 157 | + } | |
| 158 | + }, | |
| 159 | + }, | |
| 160 | +}); | |
| 161 | + | |
| 162 | +export const { setSearchValue, setGetTagValues, getSearchData } = advanceSearchSlice.actions; | |
| 163 | +export default advanceSearchSlice.reducer; |
+40-0Client/src/redux/slices/contactCustomFiledSlice.js
| @@ -0,0 +1,40 @@ | ||
| 1 | +import { createAsyncThunk, createSlice } from '@reduxjs/toolkit'; | |
| 2 | +import { getApi } from '../../services/api' | |
| 3 | + | |
| 4 | +export const fetchContactCustomFiled = createAsyncThunk('fetchContactCustomFiled', async () => { | |
| 5 | + const user = JSON.parse(localStorage.getItem("user")); | |
| 6 | + try { | |
| 7 | + const response = await getApi(`api/custom-field/?moduleName=Contacts`); | |
| 8 | + return response; | |
| 9 | + } catch (error) { | |
| 10 | + throw error; | |
| 11 | + } | |
| 12 | +}); | |
| 13 | + | |
| 14 | + | |
| 15 | +const contactCustomFiledSlice = createSlice({ | |
| 16 | + name: 'contactCustomFiledData', | |
| 17 | + initialState: { | |
| 18 | + data: [], | |
| 19 | + isLoading: false, | |
| 20 | + error: "", | |
| 21 | + }, | |
| 22 | + extraReducers: (builder) => { | |
| 23 | + builder | |
| 24 | + .addCase(fetchContactCustomFiled.pending, (state) => { | |
| 25 | + state.isLoading = true; | |
| 26 | + }) | |
| 27 | + .addCase(fetchContactCustomFiled.fulfilled, (state, action) => { | |
| 28 | + state.isLoading = false; | |
| 29 | + state.data = action.payload; | |
| 30 | + state.error = ""; | |
| 31 | + }) | |
| 32 | + .addCase(fetchContactCustomFiled.rejected, (state, action) => { | |
| 33 | + state.isLoading = false; | |
| 34 | + state.data = []; | |
| 35 | + state.error = action.error.message; | |
| 36 | + }); | |
| 37 | + }, | |
| 38 | +}); | |
| 39 | + | |
| 40 | +export default contactCustomFiledSlice.reducer; |
+41-0Client/src/redux/slices/contactSlice.js
| @@ -0,0 +1,41 @@ | ||
| 1 | + | |
| 2 | +import { createAsyncThunk, createSlice } from '@reduxjs/toolkit'; | |
| 3 | +import { getApi } from 'services/api'; | |
| 4 | + | |
| 5 | +export const fetchContactData = createAsyncThunk('fetchContactData', async () => { | |
| 6 | + const user = JSON.parse(localStorage.getItem("user")); | |
| 7 | + try { | |
| 8 | + const response = await getApi(user.role === 'superAdmin' ? 'api/contact/' : `api/contact/?createBy=${user._id}`); | |
| 9 | + return response.data; | |
| 10 | + } catch (error) { | |
| 11 | + throw error; | |
| 12 | + } | |
| 13 | +}); | |
| 14 | + | |
| 15 | + | |
| 16 | +const getContactSlice = createSlice({ | |
| 17 | + name: 'contactData', | |
| 18 | + initialState: { | |
| 19 | + data: [], | |
| 20 | + isLoading: false, | |
| 21 | + error: "", | |
| 22 | + }, | |
| 23 | + extraReducers: (builder) => { | |
| 24 | + builder | |
| 25 | + .addCase(fetchContactData.pending, (state) => { | |
| 26 | + state.isLoading = true; | |
| 27 | + }) | |
| 28 | + .addCase(fetchContactData.fulfilled, (state, action) => { | |
| 29 | + state.isLoading = false; | |
| 30 | + state.data = action.payload; | |
| 31 | + state.error = ""; | |
| 32 | + }) | |
| 33 | + .addCase(fetchContactData.rejected, (state, action) => { | |
| 34 | + state.isLoading = false; | |
| 35 | + state.data = []; | |
| 36 | + state.error = action.error.message; | |
| 37 | + }); | |
| 38 | + }, | |
| 39 | +}); | |
| 40 | + | |
| 41 | +export default getContactSlice.reducer; |
+40-0Client/src/redux/slices/emailTempSlice.js
| @@ -0,0 +1,40 @@ | ||
| 1 | +import { createAsyncThunk, createSlice } from '@reduxjs/toolkit'; | |
| 2 | +import { getApi } from '../../services/api' | |
| 3 | + | |
| 4 | +export const fetchEmailTempData = createAsyncThunk('fetchEmailTempData', async () => { | |
| 5 | + const user = JSON.parse(localStorage.getItem("user")); | |
| 6 | + try { | |
| 7 | + const response = await getApi(user.role === 'superAdmin' ? 'api/email-temp/' : `api/email-temp/?createBy=${user._id}`); | |
| 8 | + return response; | |
| 9 | + } catch (error) { | |
| 10 | + throw error; | |
| 11 | + } | |
| 12 | +}); | |
| 13 | + | |
| 14 | + | |
| 15 | +const emailTempSlice = createSlice({ | |
| 16 | + name: 'emailTempData', | |
| 17 | + initialState: { | |
| 18 | + data: [], | |
| 19 | + isLoading: false, | |
| 20 | + error: "", | |
| 21 | + }, | |
| 22 | + extraReducers: (builder) => { | |
| 23 | + builder | |
| 24 | + .addCase(fetchEmailTempData.pending, (state) => { | |
| 25 | + state.isLoading = true; | |
| 26 | + }) | |
| 27 | + .addCase(fetchEmailTempData.fulfilled, (state, action) => { | |
| 28 | + state.isLoading = false; | |
| 29 | + state.data = action.payload; | |
| 30 | + state.error = ""; | |
| 31 | + }) | |
| 32 | + .addCase(fetchEmailTempData.rejected, (state, action) => { | |
| 33 | + state.isLoading = false; | |
| 34 | + state.data = []; | |
| 35 | + state.error = action.error.message; | |
| 36 | + }); | |
| 37 | + }, | |
| 38 | +}); | |
| 39 | + | |
| 40 | +export default emailTempSlice.reducer; |
+39-0Client/src/redux/slices/emailsSlice.js
| @@ -0,0 +1,39 @@ | ||
| 1 | +import { createAsyncThunk, createSlice } from '@reduxjs/toolkit'; | |
| 2 | +import { getApi } from '../../services/api' | |
| 3 | + | |
| 4 | +export const fetchEmailsData = createAsyncThunk('fetchEmailsData', async () => { | |
| 5 | + const user = JSON.parse(localStorage.getItem("user")); | |
| 6 | + try { | |
| 7 | + const response = await getApi(user.role === 'superAdmin' ? 'api/email/' : `api/email/?sender=${user._id}`); | |
| 8 | + return response; | |
| 9 | + } catch (error) { | |
| 10 | + throw error; | |
| 11 | + } | |
| 12 | +}); | |
| 13 | + | |
| 14 | +const emailsSlice = createSlice({ | |
| 15 | + name: 'emailsData', | |
| 16 | + initialState: { | |
| 17 | + data: [], | |
| 18 | + isLoading: false, | |
| 19 | + error: "", | |
| 20 | + }, | |
| 21 | + extraReducers: (builder) => { | |
| 22 | + builder | |
| 23 | + .addCase(fetchEmailsData.pending, (state) => { | |
| 24 | + state.isLoading = true; | |
| 25 | + }) | |
| 26 | + .addCase(fetchEmailsData.fulfilled, (state, action) => { | |
| 27 | + state.isLoading = false; | |
| 28 | + state.data = action.payload; | |
| 29 | + state.error = ""; | |
| 30 | + }) | |
| 31 | + .addCase(fetchEmailsData.rejected, (state, action) => { | |
| 32 | + state.isLoading = false; | |
| 33 | + state.data = []; | |
| 34 | + state.error = action.error.message; | |
| 35 | + }); | |
| 36 | + }, | |
| 37 | +}); | |
| 38 | + | |
| 39 | +export default emailsSlice.reducer; |
+44-0Client/src/redux/slices/imageSlice.js
| @@ -0,0 +1,44 @@ | ||
| 1 | +import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'; | |
| 2 | +import { getApi } from 'services/api'; | |
| 3 | + | |
| 4 | +const initialState = { | |
| 5 | + images: [], | |
| 6 | + status: 'idle', | |
| 7 | + error: null, | |
| 8 | +}; | |
| 9 | + | |
| 10 | +// Create an asynchronous thunk | |
| 11 | +export const fetchImage = createAsyncThunk('images/fetchImage', async (active, { dispatch, getState }) => { | |
| 12 | + dispatch(fetchImage.pending()); | |
| 13 | + try { | |
| 14 | + const response = await getApi(`api/images/${active ? active : ""}`); | |
| 15 | + dispatch(fetchImage.fulfilled(response.data)); | |
| 16 | + return response.data; | |
| 17 | + } catch (error) { | |
| 18 | + dispatch(fetchImage.rejected(error)); | |
| 19 | + throw error; | |
| 20 | + } | |
| 21 | +}); | |
| 22 | +const imageSlice = createSlice({ | |
| 23 | + name: 'images', | |
| 24 | + initialState, | |
| 25 | + reducers: {}, | |
| 26 | + extraReducers: (builder) => { | |
| 27 | + builder | |
| 28 | + .addCase(fetchImage.pending, (state) => { | |
| 29 | + state.status = 'loading'; | |
| 30 | + }) | |
| 31 | + .addCase(fetchImage.fulfilled, (state, action) => { | |
| 32 | + state.status = 'succeeded'; | |
| 33 | + state.images = action.payload; | |
| 34 | + state.error = null; // Reset error on successful fetch | |
| 35 | + }) | |
| 36 | + .addCase(fetchImage.rejected, (state, action) => { | |
| 37 | + state.status = 'failed'; | |
| 38 | + state.error = action.error.message; | |
| 39 | + }); | |
| 40 | + }, | |
| 41 | +}); | |
| 42 | + | |
| 43 | +export default imageSlice.reducer; | |
| 44 | + |
+40-0Client/src/redux/slices/invoicesSlice.js
| @@ -0,0 +1,40 @@ | ||
| 1 | +import { createAsyncThunk, createSlice } from '@reduxjs/toolkit'; | |
| 2 | +import { getApi } from '../../services/api' | |
| 3 | + | |
| 4 | +export const fetchInvoicesData = createAsyncThunk('fetchInvoicesData', async () => { | |
| 5 | + const user = JSON.parse(localStorage.getItem("user")); | |
| 6 | + try { | |
| 7 | + const response = await getApi(user.role === 'superAdmin' ? 'api/invoices/' : `api/invoices/?createBy=${user._id}`); | |
| 8 | + return response; | |
| 9 | + } catch (error) { | |
| 10 | + throw error; | |
| 11 | + } | |
| 12 | +}); | |
| 13 | + | |
| 14 | + | |
| 15 | +const invoicesSlice = createSlice({ | |
| 16 | + name: 'invoiceData', | |
| 17 | + initialState: { | |
| 18 | + data: [], | |
| 19 | + isLoading: false, | |
| 20 | + error: "", | |
| 21 | + }, | |
| 22 | + extraReducers: (builder) => { | |
| 23 | + builder | |
| 24 | + .addCase(fetchInvoicesData.pending, (state) => { | |
| 25 | + state.isLoading = true; | |
| 26 | + }) | |
| 27 | + .addCase(fetchInvoicesData.fulfilled, (state, action) => { | |
| 28 | + state.isLoading = false; | |
| 29 | + state.data = action.payload; | |
| 30 | + state.error = ""; | |
| 31 | + }) | |
| 32 | + .addCase(fetchInvoicesData.rejected, (state, action) => { | |
| 33 | + state.isLoading = false; | |
| 34 | + state.data = []; | |
| 35 | + state.error = action.error.message; | |
| 36 | + }); | |
| 37 | + }, | |
| 38 | +}); | |
| 39 | + | |
| 40 | +export default invoicesSlice.reducer; |
+40-0Client/src/redux/slices/leadCustomFiledSlice.js
| @@ -0,0 +1,40 @@ | ||
| 1 | +import { createAsyncThunk, createSlice } from '@reduxjs/toolkit'; | |
| 2 | +import { getApi } from '../../services/api' | |
| 3 | + | |
| 4 | +export const fetchLeadCustomFiled = createAsyncThunk('fetchLeadCustomFiled', async () => { | |
| 5 | + const user = JSON.parse(localStorage.getItem("user")); | |
| 6 | + try { | |
| 7 | + const response = await getApi(`api/custom-field/?moduleName=Leads`); | |
| 8 | + return response; | |
| 9 | + } catch (error) { | |
| 10 | + throw error; | |
| 11 | + } | |
| 12 | +}); | |
| 13 | + | |
| 14 | + | |
| 15 | +const leadCustomFiledSlice = createSlice({ | |
| 16 | + name: 'leadCustomFiledData', | |
| 17 | + initialState: { | |
| 18 | + data: [], | |
| 19 | + isLoading: false, | |
| 20 | + error: "", | |
| 21 | + }, | |
| 22 | + extraReducers: (builder) => { | |
| 23 | + builder | |
| 24 | + .addCase(fetchLeadCustomFiled.pending, (state) => { | |
| 25 | + state.isLoading = true; | |
| 26 | + }) | |
| 27 | + .addCase(fetchLeadCustomFiled.fulfilled, (state, action) => { | |
| 28 | + state.isLoading = false; | |
| 29 | + state.data = action.payload; | |
| 30 | + state.error = ""; | |
| 31 | + }) | |
| 32 | + .addCase(fetchLeadCustomFiled.rejected, (state, action) => { | |
| 33 | + state.isLoading = false; | |
| 34 | + state.data = []; | |
| 35 | + state.error = action.error.message; | |
| 36 | + }); | |
| 37 | + }, | |
| 38 | +}); | |
| 39 | + | |
| 40 | +export default leadCustomFiledSlice.reducer; |
+40-0Client/src/redux/slices/leadSlice.js
| @@ -0,0 +1,40 @@ | ||
| 1 | +import { createAsyncThunk, createSlice } from '@reduxjs/toolkit'; | |
| 2 | +import { getApi } from '../../services/api' | |
| 3 | + | |
| 4 | +export const fetchLeadData = createAsyncThunk('fetchLeadData', async () => { | |
| 5 | + const user = JSON.parse(localStorage.getItem("user")); | |
| 6 | + try { | |
| 7 | + const response = await getApi(user.role === 'superAdmin' ? 'api/lead/' : `api/lead/?createBy=${user._id}`); | |
| 8 | + return response.data; | |
| 9 | + } catch (error) { | |
| 10 | + throw error; | |
| 11 | + } | |
| 12 | +}); | |
| 13 | + | |
| 14 | + | |
| 15 | +const leadSlice = createSlice({ | |
| 16 | + name: 'leadData', | |
| 17 | + initialState: { | |
| 18 | + data: [], | |
| 19 | + isLoading: false, | |
| 20 | + error: "", | |
| 21 | + }, | |
| 22 | + extraReducers: (builder) => { | |
| 23 | + builder | |
| 24 | + .addCase(fetchLeadData.pending, (state) => { | |
| 25 | + state.isLoading = true; | |
| 26 | + }) | |
| 27 | + .addCase(fetchLeadData.fulfilled, (state, action) => { | |
| 28 | + state.isLoading = false; | |
| 29 | + state.data = action.payload; | |
| 30 | + state.error = ""; | |
| 31 | + }) | |
| 32 | + .addCase(fetchLeadData.rejected, (state, action) => { | |
| 33 | + state.isLoading = false; | |
| 34 | + state.data = []; | |
| 35 | + state.error = action.error.message; | |
| 36 | + }); | |
| 37 | + }, | |
| 38 | +}); | |
| 39 | + | |
| 40 | +export default leadSlice.reducer; |
+24-0Client/src/redux/slices/localSlice.js
| @@ -0,0 +1,24 @@ | ||
| 1 | +import { createSlice } from '@reduxjs/toolkit'; | |
| 2 | + | |
| 3 | +const initialState = { | |
| 4 | + user: JSON.parse(window.localStorage.getItem('user')), | |
| 5 | +}; | |
| 6 | + | |
| 7 | +const localSlice = createSlice({ | |
| 8 | + name: 'user', | |
| 9 | + initialState, | |
| 10 | + reducers: { | |
| 11 | + setUser: (state, action) => { | |
| 12 | + state.user = action.payload; | |
| 13 | + // You can also update localStorage here if needed | |
| 14 | + }, | |
| 15 | + clearUser: (state) => { | |
| 16 | + state.user = null; | |
| 17 | + // You can also update localStorage here if needed | |
| 18 | + }, | |
| 19 | + }, | |
| 20 | +}); | |
| 21 | + | |
| 22 | +export const { setUser, clearUser } = localSlice.actions; | |
| 23 | + | |
| 24 | +export default localSlice.reducer; |
+39-0Client/src/redux/slices/meetingSlice.js
| @@ -0,0 +1,39 @@ | ||
| 1 | +import { createAsyncThunk, createSlice } from '@reduxjs/toolkit'; | |
| 2 | +import { getApi } from '../../services/api' | |
| 3 | + | |
| 4 | +export const fetchMeetingData = createAsyncThunk('fetchMeetingData', async () => { | |
| 5 | + const user = JSON.parse(localStorage.getItem("user")); | |
| 6 | + try { | |
| 7 | + const response = await getApi(user.role === 'superAdmin' ? 'api/meeting' : `api/meeting/?createBy=${user._id}`); | |
| 8 | + return response; | |
| 9 | + } catch (error) { | |
| 10 | + throw error; | |
| 11 | + } | |
| 12 | +}); | |
| 13 | + | |
| 14 | +const meetingSlice = createSlice({ | |
| 15 | + name: 'meetingData', | |
| 16 | + initialState: { | |
| 17 | + data: [], | |
| 18 | + isLoading: false, | |
| 19 | + error: "", | |
| 20 | + }, | |
| 21 | + extraReducers: (builder) => { | |
| 22 | + builder | |
| 23 | + .addCase(fetchMeetingData.pending, (state) => { | |
| 24 | + state.isLoading = true; | |
| 25 | + }) | |
| 26 | + .addCase(fetchMeetingData.fulfilled, (state, action) => { | |
| 27 | + state.isLoading = false; | |
| 28 | + state.data = action.payload; | |
| 29 | + state.error = ""; | |
| 30 | + }) | |
| 31 | + .addCase(fetchMeetingData.rejected, (state, action) => { | |
| 32 | + state.isLoading = false; | |
| 33 | + state.data = []; | |
| 34 | + state.error = action.error.message; | |
| 35 | + }); | |
| 36 | + }, | |
| 37 | +}); | |
| 38 | + | |
| 39 | +export default meetingSlice.reducer; |
+42-0Client/src/redux/slices/moduleSlice.js
| @@ -0,0 +1,42 @@ | ||
| 1 | +import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'; | |
| 2 | +import { getApi } from 'services/api'; | |
| 3 | + | |
| 4 | +// Assume you have an initialState for roles | |
| 5 | +const initialState = { | |
| 6 | + data: [], | |
| 7 | + status: 'idle', | |
| 8 | + error: null, | |
| 9 | +}; | |
| 10 | + | |
| 11 | +// Create an asynchronous thunk | |
| 12 | +export const fetchModules = createAsyncThunk('modules', async () => { | |
| 13 | + try { | |
| 14 | + const response = await getApi(`api/modules`); | |
| 15 | + return response.data; | |
| 16 | + } catch (error) { | |
| 17 | + throw error; | |
| 18 | + } | |
| 19 | +}); | |
| 20 | + | |
| 21 | +// Create a slice with reducers and the initial state | |
| 22 | +const moduleSlice = createSlice({ | |
| 23 | + name: 'modules', | |
| 24 | + initialState, | |
| 25 | + reducers: {}, | |
| 26 | + extraReducers: (builder) => { | |
| 27 | + builder | |
| 28 | + .addCase(fetchModules.pending, (state) => { | |
| 29 | + state.status = 'loading'; | |
| 30 | + }) | |
| 31 | + .addCase(fetchModules.fulfilled, (state, action) => { | |
| 32 | + state.status = 'succeeded'; | |
| 33 | + state.data = action.payload; | |
| 34 | + }) | |
| 35 | + .addCase(fetchModules.rejected, (state, action) => { | |
| 36 | + state.status = 'failed'; | |
| 37 | + state.error = action.error.message; | |
| 38 | + }); | |
| 39 | + }, | |
| 40 | +}); | |
| 41 | + | |
| 42 | +export default moduleSlice.reducer; |
+40-0Client/src/redux/slices/opportunitySlice.js
| @@ -0,0 +1,40 @@ | ||
| 1 | +import { createAsyncThunk, createSlice } from '@reduxjs/toolkit'; | |
| 2 | +import { getApi } from '../../services/api' | |
| 3 | + | |
| 4 | +export const fetchOpportunityData = createAsyncThunk('fetchOpportunityData', async () => { | |
| 5 | + const user = JSON.parse(localStorage.getItem("user")); | |
| 6 | + try { | |
| 7 | + const response = await getApi(user.role === 'superAdmin' ? 'api/opportunity/' : `api/opportunity/?createBy=${user._id}`); | |
| 8 | + return response; | |
| 9 | + } catch (error) { | |
| 10 | + throw error; | |
| 11 | + } | |
| 12 | +}); | |
| 13 | + | |
| 14 | + | |
| 15 | +const opportunitySlice = createSlice({ | |
| 16 | + name: 'opportunityData', | |
| 17 | + initialState: { | |
| 18 | + data: [], | |
| 19 | + isLoading: false, | |
| 20 | + error: "", | |
| 21 | + }, | |
| 22 | + extraReducers: (builder) => { | |
| 23 | + builder | |
| 24 | + .addCase(fetchOpportunityData.pending, (state) => { | |
| 25 | + state.isLoading = true; | |
| 26 | + }) | |
| 27 | + .addCase(fetchOpportunityData.fulfilled, (state, action) => { | |
| 28 | + state.isLoading = false; | |
| 29 | + state.data = action.payload; | |
| 30 | + state.error = ""; | |
| 31 | + }) | |
| 32 | + .addCase(fetchOpportunityData.rejected, (state, action) => { | |
| 33 | + state.isLoading = false; | |
| 34 | + state.data = []; | |
| 35 | + state.error = action.error.message; | |
| 36 | + }); | |
| 37 | + }, | |
| 38 | +}); | |
| 39 | + | |
| 40 | +export default opportunitySlice.reducer; |
+39-0Client/src/redux/slices/phoneCallSlice.js
| @@ -0,0 +1,39 @@ | ||
| 1 | +import { createAsyncThunk, createSlice } from '@reduxjs/toolkit'; | |
| 2 | +import { getApi } from '../../services/api' | |
| 3 | + | |
| 4 | +export const fetchPhoneCallData = createAsyncThunk('fetchPhoneCallData', async () => { | |
| 5 | + const user = JSON.parse(localStorage.getItem("user")); | |
| 6 | + try { | |
| 7 | + const response = await getApi(user.role === 'superAdmin' ? 'api/phoneCall' : `api/phoneCall?sender=${user._id}`); | |
| 8 | + return response; | |
| 9 | + } catch (error) { | |
| 10 | + throw error; | |
| 11 | + } | |
| 12 | +}); | |
| 13 | + | |
| 14 | +const phoneCallSlice = createSlice({ | |
| 15 | + name: 'phoneCallData', | |
| 16 | + initialState: { | |
| 17 | + data: [], | |
| 18 | + isLoading: false, | |
| 19 | + error: "", | |
| 20 | + }, | |
| 21 | + extraReducers: (builder) => { | |
| 22 | + builder | |
| 23 | + .addCase(fetchPhoneCallData.pending, (state) => { | |
| 24 | + state.isLoading = true; | |
| 25 | + }) | |
| 26 | + .addCase(fetchPhoneCallData.fulfilled, (state, action) => { | |
| 27 | + state.isLoading = false; | |
| 28 | + state.data = action.payload; | |
| 29 | + state.error = ""; | |
| 30 | + }) | |
| 31 | + .addCase(fetchPhoneCallData.rejected, (state, action) => { | |
| 32 | + state.isLoading = false; | |
| 33 | + state.data = []; | |
| 34 | + state.error = action.error.message; | |
| 35 | + }); | |
| 36 | + }, | |
| 37 | +}); | |
| 38 | + | |
| 39 | +export default phoneCallSlice.reducer; |
+40-0Client/src/redux/slices/propertyCustomFiledSlice.js
| @@ -0,0 +1,40 @@ | ||
| 1 | +import { createAsyncThunk, createSlice } from '@reduxjs/toolkit'; | |
| 2 | +import { getApi } from '../../services/api' | |
| 3 | + | |
| 4 | +export const fetchPropertyCustomFiled = createAsyncThunk('fetchLeadData', async () => { | |
| 5 | + const user = JSON.parse(localStorage.getItem("user")); | |
| 6 | + try { | |
| 7 | + const response = await getApi(`api/custom-field/?moduleName=Properties`); | |
| 8 | + return response; | |
| 9 | + } catch (error) { | |
| 10 | + throw error; | |
| 11 | + } | |
| 12 | +}); | |
| 13 | + | |
| 14 | + | |
| 15 | +const propertyCustomFiledSlice = createSlice({ | |
| 16 | + name: 'propertyCustomFiledData', | |
| 17 | + initialState: { | |
| 18 | + data: [], | |
| 19 | + isLoading: false, | |
| 20 | + error: "", | |
| 21 | + }, | |
| 22 | + extraReducers: (builder) => { | |
| 23 | + builder | |
| 24 | + .addCase(fetchPropertyCustomFiled.pending, (state) => { | |
| 25 | + state.isLoading = true; | |
| 26 | + }) | |
| 27 | + .addCase(fetchPropertyCustomFiled.fulfilled, (state, action) => { | |
| 28 | + state.isLoading = false; | |
| 29 | + state.data = action.payload; | |
| 30 | + state.error = ""; | |
| 31 | + }) | |
| 32 | + .addCase(fetchPropertyCustomFiled.rejected, (state, action) => { | |
| 33 | + state.isLoading = false; | |
| 34 | + state.data = []; | |
| 35 | + state.error = action.error.message; | |
| 36 | + }); | |
| 37 | + }, | |
| 38 | +}); | |
| 39 | + | |
| 40 | +export default propertyCustomFiledSlice.reducer; |
+39-0Client/src/redux/slices/propertySlice.js
| @@ -0,0 +1,39 @@ | ||
| 1 | +import { createAsyncThunk, createSlice } from '@reduxjs/toolkit'; | |
| 2 | +import { getApi } from '../../services/api' | |
| 3 | + | |
| 4 | +export const fetchPropertyData = createAsyncThunk('fetchPropertyData', async () => { | |
| 5 | + const user = JSON.parse(localStorage.getItem("user")); | |
| 6 | + try { | |
| 7 | + const response = await getApi(user.role === 'superAdmin' ? 'api/property/' : `api/property/?createBy=${user._id}`); | |
| 8 | + return response.data; | |
| 9 | + } catch (error) { | |
| 10 | + throw error; | |
| 11 | + } | |
| 12 | +}); | |
| 13 | + | |
| 14 | +const propertySlice = createSlice({ | |
| 15 | + name: 'propertyData', | |
| 16 | + initialState: { | |
| 17 | + data: [], | |
| 18 | + isLoading: false, | |
| 19 | + error: "", | |
| 20 | + }, | |
| 21 | + extraReducers: (builder) => { | |
| 22 | + builder | |
| 23 | + .addCase(fetchPropertyData.pending, (state) => { | |
| 24 | + state.isLoading = true; | |
| 25 | + }) | |
| 26 | + .addCase(fetchPropertyData.fulfilled, (state, action) => { | |
| 27 | + state.isLoading = false; | |
| 28 | + state.data = action.payload; | |
| 29 | + state.error = ""; | |
| 30 | + }) | |
| 31 | + .addCase(fetchPropertyData.rejected, (state, action) => { | |
| 32 | + state.isLoading = false; | |
| 33 | + state.data = []; | |
| 34 | + state.error = action.error.message; | |
| 35 | + }); | |
| 36 | + }, | |
| 37 | +}); | |
| 38 | + | |
| 39 | +export default propertySlice.reducer; |
+40-0Client/src/redux/slices/quotesSlice.js
| @@ -0,0 +1,40 @@ | ||
| 1 | +import { createAsyncThunk, createSlice } from '@reduxjs/toolkit'; | |
| 2 | +import { getApi } from '../../services/api' | |
| 3 | + | |
| 4 | +export const fetchQuotesData = createAsyncThunk('fetchQuotesData', async () => { | |
| 5 | + const user = JSON.parse(localStorage.getItem("user")); | |
| 6 | + try { | |
| 7 | + const response = await getApi(user.role === 'superAdmin' ? 'api/quotes/' : `api/quotes/?createBy=${user._id}`); | |
| 8 | + return response; | |
| 9 | + } catch (error) { | |
| 10 | + throw error; | |
| 11 | + } | |
| 12 | +}); | |
| 13 | + | |
| 14 | + | |
| 15 | +const quotesSlice = createSlice({ | |
| 16 | + name: 'quotesData', | |
| 17 | + initialState: { | |
| 18 | + data: [], | |
| 19 | + isLoading: false, | |
| 20 | + error: "", | |
| 21 | + }, | |
| 22 | + extraReducers: (builder) => { | |
| 23 | + builder | |
| 24 | + .addCase(fetchQuotesData.pending, (state) => { | |
| 25 | + state.isLoading = true; | |
| 26 | + }) | |
| 27 | + .addCase(fetchQuotesData.fulfilled, (state, action) => { | |
| 28 | + state.isLoading = false; | |
| 29 | + state.data = action.payload; | |
| 30 | + state.error = ""; | |
| 31 | + }) | |
| 32 | + .addCase(fetchQuotesData.rejected, (state, action) => { | |
| 33 | + state.isLoading = false; | |
| 34 | + state.data = []; | |
| 35 | + state.error = action.error.message; | |
| 36 | + }); | |
| 37 | + }, | |
| 38 | +}); | |
| 39 | + | |
| 40 | +export default quotesSlice.reducer; |
+44-0Client/src/redux/slices/roleSlice.js
| @@ -0,0 +1,44 @@ | ||
| 1 | +import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'; | |
| 2 | +import { getApi } from 'services/api'; | |
| 3 | + | |
| 4 | +// Assume you have an initialState for roles | |
| 5 | +const initialState = { | |
| 6 | + roles: [], | |
| 7 | + user: {}, // Initial empty array for roles | |
| 8 | + status: 'idle', // Possible statuses: 'idle', 'loading', 'succeeded', 'failed' | |
| 9 | + error: null, | |
| 10 | +}; | |
| 11 | + | |
| 12 | +// Create an asynchronous thunk | |
| 13 | +export const fetchRoles = createAsyncThunk('roles/fetchRoles', async (userId) => { | |
| 14 | + try { | |
| 15 | + const response = await getApi(`api/user/view/${userId}`); | |
| 16 | + return response.data; | |
| 17 | + } catch (error) { | |
| 18 | + throw error; | |
| 19 | + } | |
| 20 | +}); | |
| 21 | + | |
| 22 | +// Create a slice with reducers and the initial state | |
| 23 | +const rolesSlice = createSlice({ | |
| 24 | + name: 'roles', | |
| 25 | + initialState, | |
| 26 | + reducers: {}, | |
| 27 | + extraReducers: (builder) => { | |
| 28 | + builder | |
| 29 | + .addCase(fetchRoles.pending, (state) => { | |
| 30 | + state.status = 'loading'; | |
| 31 | + }) | |
| 32 | + .addCase(fetchRoles.fulfilled, (state, action) => { | |
| 33 | + state.status = 'succeeded'; | |
| 34 | + state.roles = action?.payload?.roles || []; // Set roles in the state | |
| 35 | + state.user = action.payload; // Set roles in the state | |
| 36 | + }) | |
| 37 | + .addCase(fetchRoles.rejected, (state, action) => { | |
| 38 | + state.status = 'failed'; | |
| 39 | + state.error = action.error.message; | |
| 40 | + }); | |
| 41 | + }, | |
| 42 | +}); | |
| 43 | + | |
| 44 | +export default rolesSlice.reducer; |
+42-0Client/src/redux/slices/routeSlice.js
| @@ -0,0 +1,42 @@ | ||
| 1 | + | |
| 2 | +import { createAsyncThunk, createSlice } from '@reduxjs/toolkit'; | |
| 3 | +import axios from 'axios'; | |
| 4 | +import { constant } from '../../constant'; | |
| 5 | +import { getApi } from 'services/api'; | |
| 6 | + | |
| 7 | +export const fetchRouteData = createAsyncThunk('fetchRouteData', async () => { | |
| 8 | + try { | |
| 9 | + const response = await getApi(`api/route/`); | |
| 10 | + return response.data; | |
| 11 | +} catch (error) { | |
| 12 | + throw error; | |
| 13 | +} | |
| 14 | +}); | |
| 15 | + | |
| 16 | + | |
| 17 | +const routeSlice = createSlice({ | |
| 18 | + name: 'routeData', | |
| 19 | + initialState: { | |
| 20 | + data: [], | |
| 21 | + isLoading: false, | |
| 22 | + error: "", | |
| 23 | + }, | |
| 24 | + extraReducers: (builder) => { | |
| 25 | + builder | |
| 26 | + .addCase(fetchRouteData.pending, (state) => { | |
| 27 | + state.isLoading = true; | |
| 28 | + }) | |
| 29 | + .addCase(fetchRouteData.fulfilled, (state, action) => { | |
| 30 | + state.isLoading = false; | |
| 31 | + state.data = action.payload; | |
| 32 | + state.error = ""; | |
| 33 | + }) | |
| 34 | + .addCase(fetchRouteData.rejected, (state, action) => { | |
| 35 | + state.isLoading = false; | |
| 36 | + state.data = []; | |
| 37 | + state.error = action.error.message; | |
| 38 | + }); | |
| 39 | + }, | |
| 40 | +}); | |
| 41 | + | |
| 42 | +export default routeSlice.reducer; |
+39-0Client/src/redux/slices/taskSlice.js
| @@ -0,0 +1,39 @@ | ||
| 1 | +import { createAsyncThunk, createSlice } from '@reduxjs/toolkit'; | |
| 2 | +import { getApi } from '../../services/api' | |
| 3 | + | |
| 4 | +export const fetchTaskData = createAsyncThunk('fetchTaskData', async () => { | |
| 5 | + const user = JSON.parse(localStorage.getItem("user")); | |
| 6 | + try { | |
| 7 | + const response = await getApi(user.role === 'superAdmin' ? `api/task` : `api/task/?createBy=${user._id}`); | |
| 8 | + return response; | |
| 9 | + } catch (error) { | |
| 10 | + throw error; | |
| 11 | + } | |
| 12 | +}); | |
| 13 | + | |
| 14 | +const taskSlice = createSlice({ | |
| 15 | + name: 'taskData', | |
| 16 | + initialState: { | |
| 17 | + data: [], | |
| 18 | + isLoading: false, | |
| 19 | + error: "", | |
| 20 | + }, | |
| 21 | + extraReducers: (builder) => { | |
| 22 | + builder | |
| 23 | + .addCase(fetchTaskData.pending, (state) => { | |
| 24 | + state.isLoading = true; | |
| 25 | + }) | |
| 26 | + .addCase(fetchTaskData.fulfilled, (state, action) => { | |
| 27 | + state.isLoading = false; | |
| 28 | + state.data = action.payload; | |
| 29 | + state.error = ""; | |
| 30 | + }) | |
| 31 | + .addCase(fetchTaskData.rejected, (state, action) => { | |
| 32 | + state.isLoading = false; | |
| 33 | + state.data = []; | |
| 34 | + state.error = action.error.message; | |
| 35 | + }); | |
| 36 | + }, | |
| 37 | +}); | |
| 38 | + | |
| 39 | +export default taskSlice.reducer; |
+78-0Client/src/redux/store.js
| @@ -0,0 +1,78 @@ | ||
| 1 | +import { configureStore } from '@reduxjs/toolkit'; | |
| 2 | +import { persistStore, persistReducer } from 'redux-persist'; | |
| 3 | +import storage from 'redux-persist/lib/storage'; | |
| 4 | +import roleReducer from './slices/roleSlice'; | |
| 5 | +import imageReducer from './slices/imageSlice'; | |
| 6 | +import userReducer from './slices/localSlice'; | |
| 7 | +import routeReducer from './slices/routeSlice'; | |
| 8 | +import advanceSearchSlice from './slices/advanceSearchSlice'; | |
| 9 | +import leadSlice from './slices/leadSlice' | |
| 10 | +import propertyCustomFiledSlice from './slices/propertyCustomFiledSlice'; | |
| 11 | +import propertySlice from './slices/propertySlice'; | |
| 12 | +import contactSlice from './slices/contactSlice'; | |
| 13 | +import contactCustomFiledSlice from './slices/contactCustomFiledSlice'; | |
| 14 | +import leadCustomFiledSlice from './slices/leadCustomFiledSlice'; | |
| 15 | +import taskSlice from './slices/taskSlice'; | |
| 16 | +import meetingSlice from './slices/meetingSlice'; | |
| 17 | +import emailsSlice from './slices/emailsSlice'; | |
| 18 | +import emailTempSlice from './slices/emailTempSlice'; | |
| 19 | +import opportunitySlice from './slices/opportunitySlice'; | |
| 20 | +import moduleSlice from './slices/moduleSlice'; | |
| 21 | +import accountSlice from './slices/accountSlice'; | |
| 22 | +import quotesSlice from './slices/quotesSlice'; | |
| 23 | +import invoicesSlice from './slices/invoicesSlice'; | |
| 24 | + | |
| 25 | +const middleware = (getDefaultMiddleware) => { | |
| 26 | + return getDefaultMiddleware({ | |
| 27 | + serializableCheck: false, | |
| 28 | + }); | |
| 29 | +}; | |
| 30 | + | |
| 31 | +const userPersistConfig = { | |
| 32 | + key: 'userDetails', | |
| 33 | + storage, | |
| 34 | +}; | |
| 35 | +const routePersistConfig = { | |
| 36 | + key: 'route', | |
| 37 | + storage, | |
| 38 | +}; | |
| 39 | +const imagesPersistConfig = { | |
| 40 | + key: 'image', | |
| 41 | + storage, | |
| 42 | +}; | |
| 43 | +const leadPersistConfig = { | |
| 44 | + key: 'lead', | |
| 45 | + storage, | |
| 46 | +}; | |
| 47 | +const contactPersistConfig = { | |
| 48 | + key: 'contact', | |
| 49 | + storage, | |
| 50 | +}; | |
| 51 | + | |
| 52 | +export const store = configureStore({ | |
| 53 | + reducer: { | |
| 54 | + roles: persistReducer(userPersistConfig, roleReducer), | |
| 55 | + modules: moduleSlice, | |
| 56 | + images: persistReducer(imagesPersistConfig, imageReducer), | |
| 57 | + user: userReducer, | |
| 58 | + route: persistReducer(routePersistConfig, routeReducer), | |
| 59 | + advanceSearchData: advanceSearchSlice, | |
| 60 | + leadData: persistReducer(leadPersistConfig, leadSlice), | |
| 61 | + contactData: persistReducer(contactPersistConfig, contactSlice), | |
| 62 | + propertyCustomFiled: propertyCustomFiledSlice, | |
| 63 | + contactCustomFiled: contactCustomFiledSlice, | |
| 64 | + leadCustomFiled: leadCustomFiledSlice, | |
| 65 | + propertyData: propertySlice, | |
| 66 | + taskData: taskSlice, | |
| 67 | + meetingData: meetingSlice, | |
| 68 | + emailsData: emailsSlice, | |
| 69 | + emailTempData: emailTempSlice, | |
| 70 | + opportunityData: opportunitySlice, | |
| 71 | + accountData: accountSlice, | |
| 72 | + quotesData: quotesSlice, | |
| 73 | + invoicesData: invoicesSlice, | |
| 74 | + }, | |
| 75 | + middleware, | |
| 76 | +}); | |
| 77 | + | |
| 78 | +export const persistor = persistStore(store); |
+19-0Client/src/roles.js
| @@ -0,0 +1,19 @@ | ||
| 1 | +export const ROLE_PATH = { | |
| 2 | + user: "/user", | |
| 3 | + admin: "/admin", | |
| 4 | + superAdmin: "/superAdmin", | |
| 5 | + manager: "/manager", | |
| 6 | + teamleader: "/teamleader", | |
| 7 | + executive: "/executive", | |
| 8 | + telecaller: "/telecaller" | |
| 9 | +} | |
| 10 | + | |
| 11 | +export const ROLE = { | |
| 12 | + admin: "admin", | |
| 13 | + user: "user", | |
| 14 | + manager: "manager", | |
| 15 | + teamleader: "teamleader", | |
| 16 | + telecaller: "telecaller", | |
| 17 | + executive: "executive", | |
| 18 | +} | |
| 19 | + |
+531-0Client/src/routes.js
| @@ -0,0 +1,531 @@ | ||
| 1 | + | |
| 2 | +import { Icon } from "@chakra-ui/react"; | |
| 3 | +import { HiUsers } from "react-icons/hi"; | |
| 4 | +import { | |
| 5 | + MdContacts, | |
| 6 | + MdHome, | |
| 7 | + MdInsertChartOutlined, | |
| 8 | + MdLeaderboard, | |
| 9 | + MdLock | |
| 10 | +} from "react-icons/md"; | |
| 11 | +// icon | |
| 12 | +import React from "react"; | |
| 13 | +import { AiFillFolderOpen, AiOutlineMail } from "react-icons/ai"; | |
| 14 | +import { FaCalendarAlt, FaRupeeSign, FaTasks, FaWpforms } from "react-icons/fa"; | |
| 15 | +import { LuBuilding2 } from "react-icons/lu"; | |
| 16 | +import { PiPhoneCallBold } from "react-icons/pi"; | |
| 17 | +import { FaCreativeCommonsBy } from "react-icons/fa"; | |
| 18 | +import { SiGooglemeet } from "react-icons/si"; | |
| 19 | +import { ROLE_PATH } from "./roles"; | |
| 20 | +import ChangeImage from "views/admin/image"; | |
| 21 | +import Validation from "views/admin/validation"; | |
| 22 | +import CustomField from "views/admin/customField"; | |
| 23 | +import TableField from "views/admin/tableField"; | |
| 24 | +import activeDeactiveModule from "views/admin/activeDeactiveModule"; | |
| 25 | +import { TbExchange, TbTableColumn } from "react-icons/tb"; | |
| 26 | +import { GrValidate } from "react-icons/gr"; | |
| 27 | +import { VscFileSubmodule } from "react-icons/vsc"; | |
| 28 | +import { HiTemplate } from "react-icons/hi"; | |
| 29 | +import { TbBulb } from "react-icons/tb"; | |
| 30 | +import { BsBlockquoteRight } from "react-icons/bs"; | |
| 31 | +import { RiAccountCircleFill } from "react-icons/ri"; | |
| 32 | +import { TbInvoice } from "react-icons/tb"; | |
| 33 | +import { TbFileInvoice } from "react-icons/tb"; | |
| 34 | +// Admin Imports | |
| 35 | +const MainDashboard = React.lazy(() => import("views/admin/default")); | |
| 36 | + | |
| 37 | +// My component | |
| 38 | +const Contact = React.lazy(() => import('views/admin/contact')); | |
| 39 | +const ContactView = React.lazy(() => import('views/admin/contact/View')); | |
| 40 | +const ContactImport = React.lazy(() => import("views/admin/contact/components/ContactImport")); | |
| 41 | + | |
| 42 | +const Quotes = React.lazy(() => import('views/admin/quotes')); | |
| 43 | +const QuotesView = React.lazy(() => import('views/admin/quotes/View')); | |
| 44 | +const QuotesImport = React.lazy(() => import("views/admin/quotes/components/QuotesImport")); | |
| 45 | + | |
| 46 | +const Invoices = React.lazy(() => import('views/admin/invoice')); | |
| 47 | +const InvoicesView = React.lazy(() => import('views/admin/invoice/View')); | |
| 48 | +const InvoicesImport = React.lazy(() => import("views/admin/invoice/components/InvoiceImport")); | |
| 49 | + | |
| 50 | +const User = React.lazy(() => import("views/admin/users")); | |
| 51 | +const UserView = React.lazy(() => import("views/admin/users/View")); | |
| 52 | + | |
| 53 | +const Property = React.lazy(() => import("views/admin/property")); | |
| 54 | +const PropertyView = React.lazy(() => import("views/admin/property/View")); | |
| 55 | +const PropertyImport = React.lazy(() => import("views/admin/property/components/PropertyImport")) | |
| 56 | + | |
| 57 | +const Lead = React.lazy(() => import("views/admin/lead")); | |
| 58 | +const LeadView = React.lazy(() => import("views/admin/lead/View")); | |
| 59 | +const LeadImport = React.lazy(() => import("views/admin/lead/components/LeadImport")); | |
| 60 | + | |
| 61 | +const Communication = React.lazy(() => import("views/admin/communication")); | |
| 62 | + | |
| 63 | +const Task = React.lazy(() => import("views/admin/task")); | |
| 64 | +const TaskView = React.lazy(() => import("views/admin/task/components/taskView")); | |
| 65 | +const Calender = React.lazy(() => import("views/admin/calender")); | |
| 66 | +const Payments = React.lazy(() => import("views/admin/payments")); | |
| 67 | +const Role = React.lazy(() => import("views/admin/role")); | |
| 68 | + | |
| 69 | +const Document = React.lazy(() => import("views/admin/document")); | |
| 70 | + | |
| 71 | +const EmailHistory = React.lazy(() => import("views/admin/emailHistory")); | |
| 72 | +const EmailHistoryView = React.lazy(() => import("views/admin/emailHistory/View")); | |
| 73 | + | |
| 74 | +const Meeting = React.lazy(() => import("views/admin/meeting")); | |
| 75 | +const MettingView = React.lazy(() => import("views/admin/meeting/View")); | |
| 76 | + | |
| 77 | +const PhoneCall = React.lazy(() => import("views/admin/phoneCall")); | |
| 78 | +const PhoneCallView = React.lazy(() => import("views/admin/phoneCall/View")); | |
| 79 | + | |
| 80 | +const Report = React.lazy(() => import("views/admin/reports")); | |
| 81 | +const EmailTemplate = React.lazy(() => import("views/admin/emailTemplate")); | |
| 82 | +const AddEdit = React.lazy(() => import("views/admin/emailTemplate/AddEdit")); | |
| 83 | +const templateView = React.lazy(() => import("views/admin/emailTemplate/view.js")); | |
| 84 | + | |
| 85 | +// Auth Imports | |
| 86 | +const SignInCentered = React.lazy(() => import("views/auth/signIn")); | |
| 87 | +// admin setting | |
| 88 | +const AdminSetting = React.lazy(() => import("views/admin/adminSetting")); | |
| 89 | +const validation = React.lazy(() => import("views/admin/validation")); | |
| 90 | +const module = React.lazy(() => import("views/admin/moduleName")); | |
| 91 | +const Opportunities = React.lazy(() => import("views/admin/opportunities")); | |
| 92 | +const OpportunitiesView = React.lazy(() => import("views/admin/opportunities/View")); | |
| 93 | +const OpportunitiesImport = React.lazy(() => import("views/admin/opportunities/components/OpprtunityImport")); | |
| 94 | +const Account = React.lazy(() => import("views/admin/account")); | |
| 95 | +const AccountView = React.lazy(() => import("views/admin/account/View")); | |
| 96 | +const AccountImport = React.lazy(() => import("views/admin/account/components/AccountImport")); | |
| 97 | + | |
| 98 | +const routes = [ | |
| 99 | + // ========================== Dashboard ========================== | |
| 100 | + { | |
| 101 | + name: "Dashboard", | |
| 102 | + layout: [ROLE_PATH.superAdmin, ROLE_PATH.user], | |
| 103 | + path: "/default", | |
| 104 | + icon: <Icon as={MdHome} width='20px' height='20px' color='inherit' />, | |
| 105 | + component: MainDashboard, | |
| 106 | + }, | |
| 107 | + // ========================== Admin Layout ========================== | |
| 108 | + // ------------- lead Routes ------------------------ | |
| 109 | + { | |
| 110 | + name: "Leads", | |
| 111 | + layout: [ROLE_PATH.superAdmin, ROLE_PATH.user], | |
| 112 | + path: "/lead", | |
| 113 | + icon: <Icon as={MdLeaderboard} width='20px' height='20px' color='inherit' />, | |
| 114 | + component: Lead, | |
| 115 | + }, | |
| 116 | + { | |
| 117 | + name: "Leads", | |
| 118 | + layout: [ROLE_PATH.superAdmin, ROLE_PATH.user], | |
| 119 | + under: "lead", | |
| 120 | + parentName: "Leads", | |
| 121 | + path: "/leadView/:id", | |
| 122 | + component: LeadView, | |
| 123 | + }, | |
| 124 | + { | |
| 125 | + name: "Lead Import", | |
| 126 | + layout: [ROLE_PATH.superAdmin, ROLE_PATH.user], | |
| 127 | + under: "lead", | |
| 128 | + parentName: "Leads", | |
| 129 | + path: "/leadImport", | |
| 130 | + component: LeadImport, | |
| 131 | + }, | |
| 132 | + // --------------- contact Routes -------------------- | |
| 133 | + { | |
| 134 | + name: "Contacts", | |
| 135 | + layout: [ROLE_PATH.superAdmin, ROLE_PATH.user], | |
| 136 | + path: "/contacts", | |
| 137 | + icon: <Icon as={MdContacts} width='20px' height='20px' color='inherit' />, | |
| 138 | + component: Contact, | |
| 139 | + }, | |
| 140 | + { | |
| 141 | + name: "Contacts", | |
| 142 | + layout: [ROLE_PATH.superAdmin, ROLE_PATH.user], | |
| 143 | + under: "contacts", | |
| 144 | + parentName: "Contacts", | |
| 145 | + path: "/contactView/:id", | |
| 146 | + component: ContactView, | |
| 147 | + }, | |
| 148 | + { | |
| 149 | + name: "Contact Import", | |
| 150 | + layout: [ROLE_PATH.superAdmin, ROLE_PATH.user], | |
| 151 | + both: true, | |
| 152 | + under: "contacts", | |
| 153 | + parentName: "Contacts", | |
| 154 | + path: "/contactImport", | |
| 155 | + component: ContactImport, | |
| 156 | + }, | |
| 157 | + // ------------- Property Routes ------------------------ | |
| 158 | + { | |
| 159 | + name: "Properties", | |
| 160 | + layout: [ROLE_PATH.superAdmin, ROLE_PATH.user], | |
| 161 | + path: "/properties", | |
| 162 | + icon: <Icon as={LuBuilding2} width='20px' height='20px' color='inherit' />, | |
| 163 | + component: Property, | |
| 164 | + }, | |
| 165 | + { | |
| 166 | + name: "Properties", | |
| 167 | + layout: [ROLE_PATH.superAdmin, ROLE_PATH.user], | |
| 168 | + parentName: "Properties", | |
| 169 | + under: "properties", | |
| 170 | + path: "/propertyView/:id", | |
| 171 | + component: PropertyView, | |
| 172 | + }, | |
| 173 | + { | |
| 174 | + name: "Property Import", | |
| 175 | + layout: [ROLE_PATH.superAdmin, ROLE_PATH.user], | |
| 176 | + both: true, | |
| 177 | + under: "properties", | |
| 178 | + parentName: "Properties", | |
| 179 | + path: "/propertyImport", | |
| 180 | + component: PropertyImport, | |
| 181 | + }, | |
| 182 | + | |
| 183 | + // -----------------------------Opportunities------------------------------------- | |
| 184 | + { | |
| 185 | + name: "Opportunities", | |
| 186 | + layout: [ROLE_PATH.superAdmin, ROLE_PATH.user], | |
| 187 | + path: "/opportunities", | |
| 188 | + icon: <Icon as={TbBulb} width='20px' height='20px' color='inherit' />, | |
| 189 | + component: Opportunities, | |
| 190 | + }, | |
| 191 | + { | |
| 192 | + name: "Opportunities", | |
| 193 | + layout: [ROLE_PATH.superAdmin, ROLE_PATH.user], | |
| 194 | + path: "/opportunitiesView/:id", | |
| 195 | + under: "opportunities", | |
| 196 | + parentName: "Opportunities", | |
| 197 | + icon: <Icon as={TbBulb} width='20px' height='20px' color='inherit' />, | |
| 198 | + component: OpportunitiesView, | |
| 199 | + }, | |
| 200 | + { | |
| 201 | + name: "Opportunities", | |
| 202 | + layout: [ROLE_PATH.superAdmin, ROLE_PATH.user], | |
| 203 | + path: "/opprotunitiesImport", | |
| 204 | + under: "opportunities", | |
| 205 | + parentName: "Opportunities", | |
| 206 | + icon: <Icon as={TbBulb} width='20px' height='20px' color='inherit' />, | |
| 207 | + component: OpportunitiesImport, | |
| 208 | + }, | |
| 209 | + // -----------------------------Account------------------------------------- | |
| 210 | + { | |
| 211 | + name: "Account", | |
| 212 | + layout: [ROLE_PATH.superAdmin, ROLE_PATH.user], | |
| 213 | + path: "/account", | |
| 214 | + icon: <Icon as={RiAccountCircleFill} width='20px' height='20px' color='inherit' />, | |
| 215 | + component: Account, | |
| 216 | + }, | |
| 217 | + { | |
| 218 | + name: "Account", | |
| 219 | + layout: [ROLE_PATH.superAdmin, ROLE_PATH.user], | |
| 220 | + path: "/accountView/:id", | |
| 221 | + under: "account", | |
| 222 | + parentName: "Account", | |
| 223 | + icon: <Icon as={RiAccountCircleFill} width='20px' height='20px' color='inherit' />, | |
| 224 | + component: AccountView, | |
| 225 | + }, | |
| 226 | + { | |
| 227 | + name: "Account", | |
| 228 | + layout: [ROLE_PATH.superAdmin, ROLE_PATH.user], | |
| 229 | + path: "/accountImport", | |
| 230 | + under: "account", | |
| 231 | + parentName: "Account", | |
| 232 | + icon: <Icon as={RiAccountCircleFill} width='20px' height='20px' color='inherit' />, | |
| 233 | + component: AccountImport, | |
| 234 | + }, | |
| 235 | + // --------------- Quotes Routes -------------------- | |
| 236 | + { | |
| 237 | + name: "Quotes", | |
| 238 | + layout: [ROLE_PATH.superAdmin, ROLE_PATH.user], | |
| 239 | + path: "/quotes", | |
| 240 | + icon: <Icon as={BsBlockquoteRight} width='20px' height='20px' color='inherit' />, | |
| 241 | + component: Quotes, | |
| 242 | + }, | |
| 243 | + { | |
| 244 | + name: "Quotes", | |
| 245 | + layout: [ROLE_PATH.superAdmin, ROLE_PATH.user], | |
| 246 | + under: "quotes", | |
| 247 | + parentName: "Quotes", | |
| 248 | + path: "/quotesView/:id", | |
| 249 | + component: QuotesView, | |
| 250 | + }, | |
| 251 | + { | |
| 252 | + name: "Quotes Import", | |
| 253 | + layout: [ROLE_PATH.superAdmin, ROLE_PATH.user], | |
| 254 | + both: true, | |
| 255 | + under: "quotes", | |
| 256 | + parentName: "Quotes", | |
| 257 | + path: "/quotesImport", | |
| 258 | + component: QuotesImport, | |
| 259 | + }, | |
| 260 | + // --------------- Invoices Routes -------------------- | |
| 261 | + { | |
| 262 | + | |
| 263 | + name: "Invoices", | |
| 264 | + layout: [ROLE_PATH.superAdmin, ROLE_PATH.user], | |
| 265 | + path: "/invoices", | |
| 266 | + icon: <Icon as={TbFileInvoice} width='20px' height='20px' color='inherit' />, | |
| 267 | + component: Invoices, | |
| 268 | + }, | |
| 269 | + { | |
| 270 | + name: "Invoices", | |
| 271 | + layout: [ROLE_PATH.superAdmin, ROLE_PATH.user], | |
| 272 | + under: "invoices", | |
| 273 | + parentName: "Invoices", | |
| 274 | + path: "/invoicesView/:id", | |
| 275 | + component: InvoicesView, | |
| 276 | + }, | |
| 277 | + { | |
| 278 | + name: "Invoices Import", | |
| 279 | + layout: [ROLE_PATH.superAdmin, ROLE_PATH.user], | |
| 280 | + both: true, | |
| 281 | + under: "invoices", | |
| 282 | + parentName: "Invoices", | |
| 283 | + path: "/invoicesImport", | |
| 284 | + component: InvoicesImport, | |
| 285 | + }, | |
| 286 | + | |
| 287 | + // // ------------- Communication Integration Routes ------------------------ | |
| 288 | + // { | |
| 289 | + // name: "Communication Integration", | |
| 290 | + // layout: [ROLE_PATH.admin, ROLE_PATH.user], | |
| 291 | + | |
| 292 | + // path: "/communication-integration", | |
| 293 | + // icon: <Icon as={GiSatelliteCommunication} width='20px' height='20px' color='inherit' />, | |
| 294 | + // component: Communication, | |
| 295 | + // }, | |
| 296 | + // ------------- Task Routes ------------------------ | |
| 297 | + { | |
| 298 | + name: "Tasks", | |
| 299 | + layout: [ROLE_PATH.superAdmin, ROLE_PATH.user], | |
| 300 | + path: "/task", | |
| 301 | + icon: <Icon as={FaTasks} width='20px' height='20px' color='inherit' />, | |
| 302 | + component: Task, | |
| 303 | + }, | |
| 304 | + { | |
| 305 | + name: "Tasks", | |
| 306 | + layout: [ROLE_PATH.superAdmin, ROLE_PATH.user], | |
| 307 | + under: "task", | |
| 308 | + parentName: "Tasks", | |
| 309 | + path: "/view/:id", | |
| 310 | + component: TaskView, | |
| 311 | + }, | |
| 312 | + // ------------- Meeting Routes ------------------------ | |
| 313 | + { | |
| 314 | + name: "Meetings", | |
| 315 | + layout: [ROLE_PATH.superAdmin, ROLE_PATH.user], | |
| 316 | + path: "/metting", | |
| 317 | + icon: <Icon as={SiGooglemeet} width='20px' height='20px' color='inherit' />, | |
| 318 | + component: Meeting, | |
| 319 | + }, | |
| 320 | + { | |
| 321 | + name: "Meetings ", | |
| 322 | + layout: [ROLE_PATH.superAdmin, ROLE_PATH.user], | |
| 323 | + under: "Meetings", | |
| 324 | + parentName: "Meetings", | |
| 325 | + path: "/metting/:id", | |
| 326 | + component: MettingView, | |
| 327 | + }, | |
| 328 | + // ------------- Phone Routes ------------------------ | |
| 329 | + { | |
| 330 | + name: "Calls", | |
| 331 | + layout: [ROLE_PATH.superAdmin, ROLE_PATH.user], | |
| 332 | + path: "/phone-call", | |
| 333 | + icon: <Icon as={PiPhoneCallBold} width='20px' height='20px' color='inherit' />, | |
| 334 | + component: PhoneCall, | |
| 335 | + }, | |
| 336 | + { | |
| 337 | + name: "Calls", | |
| 338 | + layout: [ROLE_PATH.superAdmin, ROLE_PATH.user], | |
| 339 | + under: "phone-call", | |
| 340 | + parentName: "Calls", | |
| 341 | + path: "/phone-call/:id", | |
| 342 | + component: PhoneCallView, | |
| 343 | + }, | |
| 344 | + // ------------- Email Routes------------------------ | |
| 345 | + { | |
| 346 | + // separator: 'History', | |
| 347 | + name: "Emails", | |
| 348 | + layout: [ROLE_PATH.superAdmin, ROLE_PATH.user], | |
| 349 | + path: "/email", | |
| 350 | + icon: <Icon as={AiOutlineMail} width='20px' height='20px' color='inherit' />, | |
| 351 | + component: EmailHistory, | |
| 352 | + }, | |
| 353 | + { | |
| 354 | + name: "Emails ", | |
| 355 | + layout: [ROLE_PATH.superAdmin, ROLE_PATH.user], | |
| 356 | + under: "Emails", | |
| 357 | + parentName: "Emails", | |
| 358 | + path: "/Email/:id", | |
| 359 | + component: EmailHistoryView, | |
| 360 | + }, | |
| 361 | + // -----------------------------Email Template------------------------------------- | |
| 362 | + { | |
| 363 | + name: "Email Template", | |
| 364 | + layout: [ROLE_PATH.superAdmin, ROLE_PATH.user], | |
| 365 | + path: "/email-template", | |
| 366 | + icon: <Icon as={HiTemplate} width='20px' height='20px' color='inherit' />, | |
| 367 | + component: EmailTemplate, | |
| 368 | + }, | |
| 369 | + { | |
| 370 | + name: "Add Email Template", | |
| 371 | + layout: [ROLE_PATH.superAdmin, ROLE_PATH.user], | |
| 372 | + under: "email-template", | |
| 373 | + parentName: "Email Template", | |
| 374 | + path: "/email-template/email-template-addEdit", | |
| 375 | + icon: <Icon as={HiTemplate} width='20px' height='20px' color='inherit' />, | |
| 376 | + component: AddEdit, | |
| 377 | + }, | |
| 378 | + { | |
| 379 | + name: "Email Template", | |
| 380 | + layout: [ROLE_PATH.superAdmin, ROLE_PATH.user], | |
| 381 | + under: "email-template", | |
| 382 | + parentName: "Email Template", | |
| 383 | + path: "/email-template/:id", | |
| 384 | + icon: <Icon as={HiTemplate} width='20px' height='20px' color='inherit' />, | |
| 385 | + component: templateView, | |
| 386 | + }, | |
| 387 | + // ------------- Calender Routes ------------------------ | |
| 388 | + { | |
| 389 | + name: "Calender", | |
| 390 | + layout: [ROLE_PATH.superAdmin, ROLE_PATH.user], | |
| 391 | + path: "/calender", | |
| 392 | + icon: <Icon as={FaCalendarAlt} width='20px' height='20px' color='inherit' />, | |
| 393 | + component: Calender, | |
| 394 | + }, | |
| 395 | + // ------------- Payments Routes ------------------------ | |
| 396 | + { | |
| 397 | + name: "Payments", | |
| 398 | + layout: [ROLE_PATH.superAdmin, ROLE_PATH.user], | |
| 399 | + path: "/payments", | |
| 400 | + icon: <Icon as={FaRupeeSign} width='20px' height='20px' color='inherit' />, | |
| 401 | + component: Payments, | |
| 402 | + }, | |
| 403 | + | |
| 404 | + // -----------------------------Admin setting------------------------------------- | |
| 405 | + { | |
| 406 | + name: "Admin Setting", | |
| 407 | + layout: [ROLE_PATH.superAdmin, ROLE_PATH.user], | |
| 408 | + // parentName: "admin", | |
| 409 | + under: "admin", | |
| 410 | + path: "/admin-setting", | |
| 411 | + component: AdminSetting, | |
| 412 | + }, | |
| 413 | + { | |
| 414 | + name: "Roles", | |
| 415 | + layout: [ROLE_PATH.superAdmin, ROLE_PATH.user], | |
| 416 | + path: "/role", | |
| 417 | + under: "role", | |
| 418 | + icon: <Icon as={FaCreativeCommonsBy} width='20px' height='20px' color='inherit' />, | |
| 419 | + component: Role, | |
| 420 | + }, | |
| 421 | + { | |
| 422 | + name: "Custom Fields", | |
| 423 | + layout: [ROLE_PATH.superAdmin, ROLE_PATH.user], | |
| 424 | + path: "/custom-Fields", | |
| 425 | + under: "customField", | |
| 426 | + icon: <Icon as={FaWpforms} width='20px' height='20px' color='inherit' />, | |
| 427 | + component: CustomField, | |
| 428 | + }, | |
| 429 | + { | |
| 430 | + name: "Change Images", | |
| 431 | + layout: [ROLE_PATH.superAdmin, ROLE_PATH.user], | |
| 432 | + path: "/change-images", | |
| 433 | + under: "image", | |
| 434 | + icon: <Icon as={TbExchange} width='20px' height='20px' color='inherit' />, | |
| 435 | + component: ChangeImage, | |
| 436 | + }, | |
| 437 | + { | |
| 438 | + name: "Validation", | |
| 439 | + layout: [ROLE_PATH.superAdmin, ROLE_PATH.user], | |
| 440 | + path: "/validations", | |
| 441 | + under: "Validation", | |
| 442 | + icon: <Icon as={GrValidate} width='20px' height='20px' color='inherit' />, | |
| 443 | + component: Validation, | |
| 444 | + }, | |
| 445 | + { | |
| 446 | + name: "Table Fields", | |
| 447 | + layout: [ROLE_PATH.superAdmin, ROLE_PATH.user], | |
| 448 | + path: "/table-field", | |
| 449 | + under: "tableField", | |
| 450 | + icon: <Icon as={TbTableColumn} width='20px' height='20px' color='inherit' />, | |
| 451 | + component: TableField, | |
| 452 | + }, | |
| 453 | + { | |
| 454 | + name: "Active Deactive Module", | |
| 455 | + layout: [ROLE_PATH.superAdmin, ROLE_PATH.user], | |
| 456 | + path: "/active-deactive-module", | |
| 457 | + under: "activeDeactiveModule", | |
| 458 | + icon: <Icon as={TbTableColumn} width='20px' height='20px' color='inherit' />, | |
| 459 | + component: activeDeactiveModule, | |
| 460 | + }, | |
| 461 | + { | |
| 462 | + name: "Module", | |
| 463 | + layout: [ROLE_PATH.superAdmin, ROLE_PATH.user], | |
| 464 | + path: "/module", | |
| 465 | + under: "module", | |
| 466 | + icon: <Icon as={VscFileSubmodule} width='20px' height='20px' color='inherit' />, | |
| 467 | + component: module, | |
| 468 | + }, | |
| 469 | + // // ------------- Text message Routes ------------------------ | |
| 470 | + // { | |
| 471 | + // name: "Text Msg", | |
| 472 | + // layout: [ROLE_PATH.admin, ROLE_PATH.user], | |
| 473 | + // | |
| 474 | + // path: "/text-msg", | |
| 475 | + // icon: <Icon as={MdOutlineMessage} width='20px' height='20px' color='inherit' />, | |
| 476 | + // component: TextMsg, | |
| 477 | + // }, | |
| 478 | + // { | |
| 479 | + // name: "Text Msg View", | |
| 480 | + // layout: [ROLE_PATH.admin, ROLE_PATH.user], | |
| 481 | + // | |
| 482 | + // under: "text-msg", | |
| 483 | + // path: text-msg/:id", | |
| 484 | + // component: TextMsgView, | |
| 485 | + // }, | |
| 486 | + // ------------- Document Routes ------------------------ | |
| 487 | + { | |
| 488 | + name: "Documents", | |
| 489 | + layout: [ROLE_PATH.superAdmin, ROLE_PATH.user], | |
| 490 | + path: "/documents", | |
| 491 | + icon: <Icon as={AiFillFolderOpen} width='20px' height='20px' color='inherit' />, | |
| 492 | + component: Document, | |
| 493 | + }, | |
| 494 | + // ----------------- Reporting Layout ----------------- | |
| 495 | + { | |
| 496 | + name: "Reporting and Analytics", | |
| 497 | + layout: [ROLE_PATH.superAdmin, ROLE_PATH.user], | |
| 498 | + path: "/reporting-analytics", | |
| 499 | + icon: <Icon as={MdInsertChartOutlined} width='20px' height='20px' color='inherit' />, | |
| 500 | + component: Report, | |
| 501 | + }, | |
| 502 | + // ------------- user Routes ------------------------ | |
| 503 | + { | |
| 504 | + name: "Users", | |
| 505 | + layout: [ROLE_PATH.superAdmin, ROLE_PATH.user], | |
| 506 | + path: "/user", | |
| 507 | + under: "user", | |
| 508 | + icon: <Icon as={HiUsers} width='20px' height='20px' color='inherit' />, | |
| 509 | + component: User, | |
| 510 | + }, | |
| 511 | + { | |
| 512 | + name: "User View", | |
| 513 | + layout: [ROLE_PATH.superAdmin, ROLE_PATH.user], | |
| 514 | + parentName: "Email", | |
| 515 | + under: "user", | |
| 516 | + path: "/userView/:id", | |
| 517 | + component: UserView, | |
| 518 | + }, | |
| 519 | + // ========================== User layout ========================== | |
| 520 | + | |
| 521 | + // ========================== auth layout ========================== | |
| 522 | + { | |
| 523 | + name: "Sign In", | |
| 524 | + layout: "/auth", | |
| 525 | + path: "/sign-in", | |
| 526 | + icon: <Icon as={MdLock} width='20px' height='20px' color='inherit' />, | |
| 527 | + component: SignInCentered, | |
| 528 | + }, | |
| 529 | +]; | |
| 530 | + | |
| 531 | +export default routes; |
+12-0Client/src/schema/accountSchema.js
| @@ -0,0 +1,12 @@ | ||
| 1 | +import * as yup from 'yup' | |
| 2 | + | |
| 3 | +export const accountSchema = yup.object({ | |
| 4 | + name: yup.string().required("Account Name Is required"), | |
| 5 | + officePhone: yup.string().matches(/^\d{10}$/, 'Office Number must be exactly 10 digits').notRequired(), | |
| 6 | + alternatePhone: yup.string().matches(/^\d{10}$/, 'Office Number must be exactly 10 digits').notRequired(), | |
| 7 | + website: yup.string().url('Enter a valid URL').notRequired(), | |
| 8 | + emailAddress: yup.string().email('Enter a valid Email Address').notRequired(), | |
| 9 | + nonPrimaryEmail: yup.string().email('Enter a valid Email Address').notRequired(), | |
| 10 | + shippingPostalcode: yup.string().matches(/^\d{6}$/, 'Shipping Postal Code must be exactly 6 digits').notRequired(), | |
| 11 | + billingPostalcode: yup.string().matches(/^\d{6}$/, 'Billing Postal Code must be exactly 6 digits').notRequired(), | |
| 12 | +}) |
+30-0Client/src/schema/addFiledSchema.js
| @@ -0,0 +1,30 @@ | ||
| 1 | +import * as yup from 'yup' | |
| 2 | + | |
| 3 | +// export const addFiledSchema = yup.object({ | |
| 4 | +// name: yup.string().min(2).required('Name is required'), | |
| 5 | +// label: yup.string().min(2).required('Label is required'), | |
| 6 | +// type: yup.string().required('Type is required'), | |
| 7 | +// }) | |
| 8 | + | |
| 9 | +const nameValidation = yup | |
| 10 | + .string() | |
| 11 | + .min(2) | |
| 12 | + .matches(/^[A-Za-z0-9_-]+$/, 'Name can only contain letters, numbers, underscores, and dashes') | |
| 13 | + .required('Name is required'); | |
| 14 | + | |
| 15 | +// Custom validation function for the label field | |
| 16 | +const labelValidation = yup | |
| 17 | + .string() | |
| 18 | + .min(2) | |
| 19 | + .test('first-letter-capital', 'First letter of label must be capital', value => { | |
| 20 | + return /^[A-Z]/.test(value); | |
| 21 | + }) | |
| 22 | + .required('Label is required'); | |
| 23 | + | |
| 24 | +// Define the schema using the custom validations | |
| 25 | +export const addFiledSchema = yup.object({ | |
| 26 | + name: nameValidation, | |
| 27 | + label: labelValidation, | |
| 28 | + type: yup.string().required('Type is required') | |
| 29 | + // belongsTo: yup.string().required('Belongs To is required'), | |
| 30 | +}); |
+51-0Client/src/schema/contactSchema.js
| @@ -0,0 +1,51 @@ | ||
| 1 | +import * as yup from 'yup' | |
| 2 | + | |
| 3 | +export const contactSchema = yup.object({ | |
| 4 | + // 1. Basic Information | |
| 5 | + firstName: yup.string().min(2).required('First Name is required'), | |
| 6 | + lastName: yup.string().min(2).required('Last Name is required'), | |
| 7 | + title: yup.string().required('Title is required'), | |
| 8 | + email: yup.string().email().required('Email is required'), | |
| 9 | + phoneNumber: yup.number().min(1000000000, 'Phone number is invalid').max(999999999999, 'Phone number is invalid').required('Phonenumber is Required'), | |
| 10 | + mobileNumber: yup.number().min(1000000000, 'Phone number is invalid').max(999999999999, 'Phone number is invalid').notRequired(), | |
| 11 | + physicalAddress: yup.string().required('Physical address is required'), | |
| 12 | + mailingAddress: yup.string(), | |
| 13 | + preferredContactMethod: yup.string().required('Preferred contact method is required'), | |
| 14 | + // 2.Lead Source Information | |
| 15 | + leadSource: yup.string(), | |
| 16 | + referralSource: yup.string(), | |
| 17 | + campaignSource: yup.string(), | |
| 18 | + // 3. Status and Classifications | |
| 19 | + leadStatus: yup.string(), | |
| 20 | + leadRating: yup.number(), | |
| 21 | + leadConversionProbability: yup.string(), | |
| 22 | + // 5. History: | |
| 23 | + emailHistory: yup.string(), | |
| 24 | + phoneCallHistory: yup.string(), | |
| 25 | + meetingHistory: yup.string(), | |
| 26 | + notesandComments: yup.string(), | |
| 27 | + // 6. Tags or Categories | |
| 28 | + tagsOrLabelsForcategorizingcontacts: yup.string(), | |
| 29 | + // 7. Important Dates: | |
| 30 | + birthday: yup.date(), | |
| 31 | + anniversary: yup.date(), | |
| 32 | + keyMilestones: yup.string(), | |
| 33 | + // 8. Additional Personal Information | |
| 34 | + dob: yup.string(), | |
| 35 | + gender: yup.string(), | |
| 36 | + occupation: yup.string(), | |
| 37 | + interestsOrHobbies: yup.string(), | |
| 38 | + // 9. Preferred Communication Preferences: | |
| 39 | + communicationFrequency: yup.string(), | |
| 40 | + preferences: yup.string(), | |
| 41 | + // 10. Social Media Profiles: | |
| 42 | + linkedInProfile: yup.string(), | |
| 43 | + facebookProfile: yup.string(), | |
| 44 | + twitterHandle: yup.string(), | |
| 45 | + otherProfiles: yup.string(), | |
| 46 | + // 11. Lead Assignment and Team Collaboration: | |
| 47 | + agentOrTeamMember: yup.string(), | |
| 48 | + internalNotesOrComments: yup.string(), | |
| 49 | + createBy: yup.string(), | |
| 50 | + // 12. Custom Fields: | |
| 51 | +}) |
+10-0Client/src/schema/document.js
| @@ -0,0 +1,10 @@ | ||
| 1 | +import * as yup from 'yup' | |
| 2 | + | |
| 3 | + | |
| 4 | +// Define the yup schema for the main document | |
| 5 | +export const documentSchema = yup.object().shape({ | |
| 6 | + folderName: yup.string().required('Folder Name is required'), | |
| 7 | + filename: yup.string().min(2, 'File Name Must Be At Least 2 Characters'), | |
| 8 | + createBy: yup.string().required() | |
| 9 | +}); | |
| 10 | + |
+22-0Client/src/schema/emailSchema.js
| @@ -0,0 +1,22 @@ | ||
| 1 | +import * as yup from 'yup' | |
| 2 | + | |
| 3 | +export const emailSchema = yup.object({ | |
| 4 | + sender: yup.string().required("Sender Is required"), | |
| 5 | + recipient: yup.string().email().required("Recipient Is required"), | |
| 6 | + cc: yup.string().email(), | |
| 7 | + bcc: yup.string().email(), | |
| 8 | + relatedToContact: yup.string(), | |
| 9 | + relatedToLead: yup.string(), | |
| 10 | + subject: yup.string().required("Subject Is required"), | |
| 11 | + message: yup.string(), | |
| 12 | + startDate: yup.date().required("Start Date Is required"), | |
| 13 | + createBy: yup.string(), | |
| 14 | + createByLead: yup.string(), | |
| 15 | +}).test('createBy-or-createByLead-required', 'Recipient Is required', function (value) { | |
| 16 | + if (!value.createBy && !value.createByLead) { | |
| 17 | + return this.createError({ | |
| 18 | + path: 'createBy', | |
| 19 | + message: 'Recipient Is required', | |
| 20 | + }); | |
| 21 | + } | |
| 22 | +}); |
+27-0Client/src/schema/index.js
| @@ -0,0 +1,27 @@ | ||
| 1 | +import { contactSchema } from './contactSchema.js'; | |
| 2 | +import { propertySchema } from './propertySchema.js'; | |
| 3 | +import { leadSchema } from './leadSchema.js'; | |
| 4 | +import { userSchema } from './userSchema.js'; | |
| 5 | +import { loginSchema } from './loginSchema.js'; | |
| 6 | +import { emailSchema } from './emailSchema.js'; | |
| 7 | +import { documentSchema } from './document.js'; | |
| 8 | +import { phoneCallSchema } from './phoneCallSchema.js'; | |
| 9 | +import { textMsgSchema } from './textMsgSchema.js'; | |
| 10 | +import { TaskSchema } from './taskSchema.js'; | |
| 11 | +import { MeetingSchema } from './meetingSchema.js'; | |
| 12 | +import { addFiledSchema } from './addFiledSchema.js' | |
| 13 | + | |
| 14 | +export { | |
| 15 | + addFiledSchema, | |
| 16 | + contactSchema, | |
| 17 | + propertySchema, | |
| 18 | + leadSchema, | |
| 19 | + userSchema, | |
| 20 | + loginSchema, | |
| 21 | + emailSchema, | |
| 22 | + documentSchema, | |
| 23 | + TaskSchema, | |
| 24 | + phoneCallSchema, | |
| 25 | + textMsgSchema, | |
| 26 | + MeetingSchema, | |
| 27 | +}; |
+7-0Client/src/schema/invoicesSchema.js
| @@ -0,0 +1,7 @@ | ||
| 1 | +import * as yup from 'yup' | |
| 2 | + | |
| 3 | +export const invoicesSchema = yup.object({ | |
| 4 | + title: yup.string().required("Title Is required"), | |
| 5 | + shippingPostalcode: yup.string().matches(/^\d{6}$/, 'Shipping Postal Code must be exactly 6 digits').notRequired(), | |
| 6 | + billingPostalcode: yup.string().matches(/^\d{6}$/, 'Billing Postal Code must be exactly 6 digits').notRequired(), | |
| 7 | +}) |
+34-0Client/src/schema/leadSchema.js
| @@ -0,0 +1,34 @@ | ||
| 1 | +import * as yup from 'yup' | |
| 2 | + | |
| 3 | +export const leadSchema = yup.object({ | |
| 4 | + // Lead Information: | |
| 5 | + leadName: yup.string().required("Lead Name Is required"), | |
| 6 | + leadEmail: yup.string().email().required("Lead Email Is required"), | |
| 7 | + leadPhoneNumber: yup.number().min(1000000000, 'Phone number is invalid').max(999999999999, 'Phone number is invalid').required("Lead Phone Number Is required"), | |
| 8 | + leadAddress: yup.string().required("Lead Address Is required"), | |
| 9 | + // Lead Source and Details: | |
| 10 | + leadSource: yup.string(), | |
| 11 | + leadStatus: yup.string(), | |
| 12 | + leadSourceDetails: yup.string(), | |
| 13 | + leadCampaign: yup.string(), | |
| 14 | + leadSourceChannel: yup.string(), | |
| 15 | + leadSourceMedium: yup.string(), | |
| 16 | + leadSourceCampaign: yup.string(), | |
| 17 | + leadSourceReferral: yup.string(), | |
| 18 | + // Lead Assignment and Ownership: | |
| 19 | + leadAssignedAgent: yup.string(), | |
| 20 | + leadOwner: yup.string(), | |
| 21 | + leadCommunicationPreferences: yup.string(), | |
| 22 | + // Lead Dates and Follow-up: | |
| 23 | + leadCreationDate: yup.date().required("Lead Creation Date Is required"), | |
| 24 | + leadConversionDate: yup.date().required("Lead Conversion Date Is required"), | |
| 25 | + leadFollowUpDate: yup.date().required("lead Follow Up Date Is required"), | |
| 26 | + leadFollowUpStatus: yup.string(), | |
| 27 | + // Lead Scoring and Nurturing: | |
| 28 | + leadScore: yup.number().required("Lead Score Is required").min(0, "Lead Score Is invalid"), | |
| 29 | + leadNurturingWorkflow: yup.string(), | |
| 30 | + leadEngagementLevel: yup.string(), | |
| 31 | + leadConversionRate: yup.number().required("lead Conversion Rate Is required"), | |
| 32 | + leadNurturingStage: yup.string(), | |
| 33 | + leadNextAction: yup.string(), | |
| 34 | +}) |
+6-0Client/src/schema/loginSchema.js
| @@ -0,0 +1,6 @@ | ||
| 1 | +import * as yup from 'yup' | |
| 2 | + | |
| 3 | +export const loginSchema = yup.object({ | |
| 4 | + username: yup.string().email().required("Email Is required"), | |
| 5 | + password: yup.string().required("Password Is required") | |
| 6 | +}) |
+13-0Client/src/schema/meetingSchema.js
| @@ -0,0 +1,13 @@ | ||
| 1 | +import * as yup from 'yup' | |
| 2 | + | |
| 3 | +export const MeetingSchema = yup.object({ | |
| 4 | + agenda: yup.string().required("Agenda Is required"), | |
| 5 | + attendes: yup.array().of(yup.string().trim()), | |
| 6 | + attendesLead: yup.array().of(yup.string().trim()), | |
| 7 | + location: yup.string(), | |
| 8 | + related: yup.string(), | |
| 9 | + dateTime: yup.string().required("Date Time Is required"), | |
| 10 | + notes: yup.string(), | |
| 11 | + createFor: yup.string(), | |
| 12 | + createdBy: yup.string(), | |
| 13 | +}) |
+5-0Client/src/schema/moduleAddSchema.js
| @@ -0,0 +1,5 @@ | ||
| 1 | +import * as yup from 'yup' | |
| 2 | + | |
| 3 | +export const moduleAddSchema = yup.object({ | |
| 4 | + moduleName: yup.string().min(2).required('Name is required'), | |
| 5 | +}) |
+16-0Client/src/schema/opprtunitiesSchema.js
| @@ -0,0 +1,16 @@ | ||
| 1 | +import * as yup from 'yup' | |
| 2 | + | |
| 3 | +export const opprtunitiesSchema = yup.object({ | |
| 4 | + opportunityName: yup.string().required("Opportunity Name Is required"), | |
| 5 | + type: yup.string(), | |
| 6 | + leadSource: yup.string(), | |
| 7 | + currency: yup.string(), | |
| 8 | + opportunityAmount: yup.string().required("Opportunity Amount Is required"), | |
| 9 | + amount: yup.string(), | |
| 10 | + expectedCloseDate: yup.string().required("Expected Close Date Is required"), | |
| 11 | + nextStep: yup.string(), | |
| 12 | + salesStage: yup.string().required("Sales Stage Is required"), | |
| 13 | + probability: yup.string(), | |
| 14 | + description: yup.string(), | |
| 15 | + createBy: yup.string(), | |
| 16 | +}) |
+19-0Client/src/schema/phoneCallSchema.js
| @@ -0,0 +1,19 @@ | ||
| 1 | +import * as yup from 'yup' | |
| 2 | + | |
| 3 | +export const phoneCallSchema = yup.object({ | |
| 4 | + sender: yup.string().required("Sender Is required"), | |
| 5 | + recipient: yup.string().required("Recipient Is required"), | |
| 6 | + callDuration: yup.string().required("Call Duration is required"), | |
| 7 | + callNotes: yup.string(), | |
| 8 | + createBy: yup.string(), | |
| 9 | + createByLead: yup.string(), | |
| 10 | + category: yup.string(), | |
| 11 | + startDate: yup.date().required("Start Date Is required"), | |
| 12 | +}).test('createBy-or-createByLead-required', 'Recipient Is required', function (value) { | |
| 13 | + if (!value.createBy && !value.createByLead) { | |
| 14 | + return this.createError({ | |
| 15 | + path: 'createBy', | |
| 16 | + message: 'Recipient Is required', | |
| 17 | + }); | |
| 18 | + } | |
| 19 | +}); |
+47-0Client/src/schema/propertySchema.js
| @@ -0,0 +1,47 @@ | ||
| 1 | +import * as yup from 'yup' | |
| 2 | +const currentYear = new Date().getFullYear() | |
| 3 | + | |
| 4 | +export const propertySchema = yup.object({ | |
| 5 | + // 1. basicPropertyInformation: | |
| 6 | + propertyType: yup.string().required("Property Type Is required"), | |
| 7 | + propertyAddress: yup.string().required("Property Address Is required"), | |
| 8 | + listingPrice: yup.string().required("Listing Price Is required"), | |
| 9 | + squareFootage: yup.string().required("Square Footage Is required"), | |
| 10 | + numberofBedrooms: yup.number().required("Number Of Bedrooms Is required"), | |
| 11 | + numberofBathrooms: yup.number().required("Number Of Bathrooms Is required"), | |
| 12 | + yearBuilt: yup.number().min(1000).max(currentYear).required("Year Built Is required"), | |
| 13 | + propertyDescription: yup.string().required("Property Description Is required"), | |
| 14 | + //2. Property Features and Amenities: | |
| 15 | + lotSize: yup.string(), | |
| 16 | + parkingAvailability: yup.string(), | |
| 17 | + appliancesIncluded: yup.string(), | |
| 18 | + heatingAndCoolingSystems: yup.string(), | |
| 19 | + flooringType: yup.string(), | |
| 20 | + exteriorFeatures: yup.string(), | |
| 21 | + communityAmenities: yup.string(), | |
| 22 | + //3. Media and Visuals: | |
| 23 | + propertyPhotos: yup.array(), | |
| 24 | + virtualToursOrVideos: yup.array(), | |
| 25 | + floorPlans: yup.array(), | |
| 26 | + propertyDocuments: yup.array(), | |
| 27 | + //4. Listing and Marketing Details: | |
| 28 | + listingStatus: yup.string(), | |
| 29 | + listingAgentOrTeam: yup.string(), | |
| 30 | + listingDate: yup.string(), | |
| 31 | + marketingDescription: yup.string(), | |
| 32 | + multipleListingService: yup.string(), | |
| 33 | + //5. Property History: | |
| 34 | + previousOwners: yup.number().min(0).notRequired(), | |
| 35 | + purchaseHistory: yup.string(), | |
| 36 | + //6. Financial Information: | |
| 37 | + propertyTaxes: yup.string(), | |
| 38 | + homeownersAssociation: yup.string(), | |
| 39 | + mortgageInformation: yup.string(), | |
| 40 | + //7. Contacts Associated with Property: | |
| 41 | + sellers: yup.string(), | |
| 42 | + buyers: yup.string(), | |
| 43 | + propertyManagers: yup.string(), | |
| 44 | + contractorsOrServiceProviders: yup.string(), | |
| 45 | + //8. Property Notes and Comments: | |
| 46 | + internalNotesOrComments: yup.string(), | |
| 47 | +}) |
+9-0Client/src/schema/quoteSchema.js
| @@ -0,0 +1,9 @@ | ||
| 1 | +import * as yup from 'yup' | |
| 2 | + | |
| 3 | +export const quoteSchema = yup.object({ | |
| 4 | + title: yup.string().required("Title Is required"), | |
| 5 | + quoteStage: yup.string().required("Quote Stage Is required"), | |
| 6 | + validUntil: yup.string().required("Valid Until Is required"), | |
| 7 | + shippingPostalcode: yup.string().matches(/^\d{6}$/, 'Shipping Postal Code must be exactly 6 digits').notRequired(), | |
| 8 | + billingPostalcode: yup.string().matches(/^\d{6}$/, 'Billing Postal Code must be exactly 6 digits').notRequired(), | |
| 9 | +}) |
+19-0Client/src/schema/taskSchema.js
| @@ -0,0 +1,19 @@ | ||
| 1 | +import * as yup from 'yup' | |
| 2 | + | |
| 3 | +export const TaskSchema = yup.object({ | |
| 4 | + title: yup.string().required("Title Is required"), | |
| 5 | + category: yup.string(), | |
| 6 | + description: yup.string(), | |
| 7 | + notes: yup.string(), | |
| 8 | + // assignTo: yup.string(), | |
| 9 | + // assignToLead: yup.string(), | |
| 10 | + reminder: yup.string(), | |
| 11 | + start: yup.string().required("Start Date Is required"), | |
| 12 | + end: yup.string(), | |
| 13 | + backgroundColor: yup.string(), | |
| 14 | + borderColor: yup.string(), | |
| 15 | + textColor: yup.string(), | |
| 16 | + display: yup.string(), | |
| 17 | + url: yup.string(), | |
| 18 | + createBy: yup.string(), | |
| 19 | +}) |
+8-0Client/src/schema/textMsgSchema.js
| @@ -0,0 +1,8 @@ | ||
| 1 | +import * as yup from 'yup' | |
| 2 | + | |
| 3 | +export const textMsgSchema = yup.object({ | |
| 4 | + sender: yup.string().min(1000000000, 'Phone number is invalid').max(999999999999, 'Phone number is invalid').required("Sender Is required"), | |
| 5 | + to: yup.string().min(1000000000, 'Phone number is invalid').max(999999999999, 'Phone number is invalid').required("To Is required"), | |
| 6 | + message: yup.string(), | |
| 7 | + createFor: yup.string().required("Create By Is required") | |
| 8 | +}) |
+9-0Client/src/schema/userSchema.js
| @@ -0,0 +1,9 @@ | ||
| 1 | +import * as yup from 'yup' | |
| 2 | + | |
| 3 | +export const userSchema = yup.object({ | |
| 4 | + firstName: yup.string().required("First Name Is required"), | |
| 5 | + lastName: yup.string(), | |
| 6 | + // phoneNumber: yup.string().required("Phone Number Is required").matches(/^\d{10}$/, "Phone Number must be exactly 10 digits"), | |
| 7 | + phoneNumber: yup.number().typeError("Invalid Phone Number").min(1000000000, 'Phone Number is invalid').max(999999999999, 'Phone Number is invalid').required('Phone Number is Required'), | |
| 8 | + username: yup.string().email("Email must be a valid email").required("Email Is required"), | |
| 9 | +}) |
+5-0Client/src/schema/validationAddSchema.js
| @@ -0,0 +1,5 @@ | ||
| 1 | +import * as yup from 'yup' | |
| 2 | + | |
| 3 | +export const validationAddSchema = yup.object({ | |
| 4 | + name: yup.string().min(2).required('Name is required'), | |
| 5 | +}) |
+97-0Client/src/services/api.js
| @@ -0,0 +1,97 @@ | ||
| 1 | +import axios from "axios" | |
| 2 | +import { constant } from "../constant" | |
| 3 | + | |
| 4 | + | |
| 5 | +export const postApi = async (path, data, login) => { | |
| 6 | + try { | |
| 7 | + let result = await axios.post(constant.baseUrl + path, data, { | |
| 8 | + headers: { | |
| 9 | + Authorization: localStorage.getItem("token") || sessionStorage.getItem("token") | |
| 10 | + } | |
| 11 | + }) | |
| 12 | + if (result.data?.token && result.data?.token !== null) { | |
| 13 | + if (login) { | |
| 14 | + localStorage.setItem('token', result.data?.token) | |
| 15 | + } else { | |
| 16 | + sessionStorage.setItem('token', result.data?.token) | |
| 17 | + } | |
| 18 | + localStorage.setItem('user', JSON.stringify(result.data?.user)) | |
| 19 | + } | |
| 20 | + return result | |
| 21 | + } catch (e) { | |
| 22 | + console.error(e) | |
| 23 | + return e | |
| 24 | + } | |
| 25 | +} | |
| 26 | +export const putApi = async (path, data, id) => { | |
| 27 | + try { | |
| 28 | + let result = await axios.put(constant.baseUrl + path, data, { | |
| 29 | + headers: { | |
| 30 | + Authorization: localStorage.getItem("token") || sessionStorage.getItem("token") | |
| 31 | + } | |
| 32 | + }) | |
| 33 | + return result | |
| 34 | + } catch (e) { | |
| 35 | + console.error(e) | |
| 36 | + return e | |
| 37 | + } | |
| 38 | +} | |
| 39 | + | |
| 40 | +export const deleteApi = async (path, param) => { | |
| 41 | + try { | |
| 42 | + let result = await axios.delete(constant.baseUrl + path + param, { | |
| 43 | + headers: { | |
| 44 | + Authorization: localStorage.getItem("token") || sessionStorage.getItem("token") | |
| 45 | + } | |
| 46 | + }) | |
| 47 | + if (result.data?.token && result.data?.token !== null) { | |
| 48 | + localStorage.setItem('token', result.data?.token) | |
| 49 | + } | |
| 50 | + return result | |
| 51 | + } catch (e) { | |
| 52 | + console.error(e) | |
| 53 | + return e | |
| 54 | + } | |
| 55 | +} | |
| 56 | + | |
| 57 | +export const deleteManyApi = async (path, data) => { | |
| 58 | + try { | |
| 59 | + let result = await axios.post(constant.baseUrl + path, data, { | |
| 60 | + headers: { | |
| 61 | + Authorization: localStorage.getItem("token") || sessionStorage.getItem("token") | |
| 62 | + } | |
| 63 | + }) | |
| 64 | + if (result.data?.token && result.data?.token !== null) { | |
| 65 | + localStorage.setItem('token', result.data?.token) | |
| 66 | + } | |
| 67 | + return result | |
| 68 | + } catch (e) { | |
| 69 | + console.error(e) | |
| 70 | + return e | |
| 71 | + } | |
| 72 | +} | |
| 73 | + | |
| 74 | +export const getApi = async (path, id) => { | |
| 75 | + try { | |
| 76 | + if (id) { | |
| 77 | + let result = await axios.get(constant.baseUrl + path + id, { | |
| 78 | + headers: { | |
| 79 | + Authorization: localStorage.getItem("token") || sessionStorage.getItem("token") | |
| 80 | + } | |
| 81 | + }) | |
| 82 | + return result | |
| 83 | + } | |
| 84 | + else { | |
| 85 | + let result = await axios.get(constant.baseUrl + path, { | |
| 86 | + headers: { | |
| 87 | + Authorization: localStorage.getItem("token") || sessionStorage.getItem("token") | |
| 88 | + } | |
| 89 | + }) | |
| 90 | + return result | |
| 91 | + } | |
| 92 | + } catch (e) { | |
| 93 | + console.error(e) | |
| 94 | + return e | |
| 95 | + } | |
| 96 | +} | |
| 97 | + |
+21-0Client/src/theme/additions/card/card.js
| @@ -0,0 +1,21 @@ | ||
| 1 | +import { mode } from "@chakra-ui/theme-tools"; | |
| 2 | +const Card = { | |
| 3 | + baseStyle: (props) => ({ | |
| 4 | + p: "20px", | |
| 5 | + display: "flex", | |
| 6 | + flexDirection: "column", | |
| 7 | + width: "100%", | |
| 8 | + position: "relative", | |
| 9 | + borderRadius: "20px", | |
| 10 | + minWidth: "0px", | |
| 11 | + wordWrap: "break-word", | |
| 12 | + bg: mode("#ffffff", "navy.800")(props), | |
| 13 | + backgroundClip: "border-box", | |
| 14 | + }), | |
| 15 | +}; | |
| 16 | + | |
| 17 | +export const CardComponent = { | |
| 18 | + components: { | |
| 19 | + Card, | |
| 20 | + }, | |
| 21 | +}; |
+32-0Client/src/theme/components/badge.js
| @@ -0,0 +1,32 @@ | ||
| 1 | +import { mode } from "@chakra-ui/theme-tools"; | |
| 2 | +export const badgeStyles = { | |
| 3 | + components: { | |
| 4 | + Badge: { | |
| 5 | + baseStyle: { | |
| 6 | + borderRadius: "10px", | |
| 7 | + lineHeight: "100%", | |
| 8 | + padding: "7px", | |
| 9 | + paddingLeft: "12px", | |
| 10 | + paddingRight: "12px", | |
| 11 | + }, | |
| 12 | + variants: { | |
| 13 | + outline: () => ({ | |
| 14 | + borderRadius: "16px", | |
| 15 | + }), | |
| 16 | + brand: (props) => ({ | |
| 17 | + bg: mode("brand.500", "brand.400")(props), | |
| 18 | + color: "white", | |
| 19 | + _focus: { | |
| 20 | + bg: mode("brand.500", "brand.400")(props), | |
| 21 | + }, | |
| 22 | + _active: { | |
| 23 | + bg: mode("brand.500", "brand.400")(props), | |
| 24 | + }, | |
| 25 | + _hover: { | |
| 26 | + bg: mode("brand.600", "brand.400")(props), | |
| 27 | + }, | |
| 28 | + }), | |
| 29 | + }, | |
| 30 | + }, | |
| 31 | + }, | |
| 32 | +}; |
+104-0Client/src/theme/components/button.js
| @@ -0,0 +1,104 @@ | ||
| 1 | +import { mode } from "@chakra-ui/theme-tools"; | |
| 2 | +export const buttonStyles = { | |
| 3 | + components: { | |
| 4 | + Button: { | |
| 5 | + baseStyle: { | |
| 6 | + borderRadius: "16px", | |
| 7 | + boxShadow: "45px 76px 113px 7px rgba(112, 144, 176, 0.08)", | |
| 8 | + transition: ".25s all ease", | |
| 9 | + boxSizing: "border-box", | |
| 10 | + _focus: { | |
| 11 | + boxShadow: "none", | |
| 12 | + }, | |
| 13 | + _active: { | |
| 14 | + boxShadow: "none", | |
| 15 | + }, | |
| 16 | + }, | |
| 17 | + variants: { | |
| 18 | + outline: () => ({ | |
| 19 | + borderRadius: "16px", | |
| 20 | + }), | |
| 21 | + brand: (props) => ({ | |
| 22 | + bg: mode("brand.500", "brand.400")(props), | |
| 23 | + color: "white", | |
| 24 | + _focus: { | |
| 25 | + bg: mode("brand.500", "brand.400")(props), | |
| 26 | + }, | |
| 27 | + _active: { | |
| 28 | + bg: mode("brand.500", "brand.400")(props), | |
| 29 | + }, | |
| 30 | + _hover: { | |
| 31 | + bg: mode("brand.600", "brand.400")(props), | |
| 32 | + }, | |
| 33 | + }), | |
| 34 | + darkBrand: (props) => ({ | |
| 35 | + bg: mode("brand.900", "brand.400")(props), | |
| 36 | + color: "white", | |
| 37 | + _focus: { | |
| 38 | + bg: mode("brand.900", "brand.400")(props), | |
| 39 | + }, | |
| 40 | + _active: { | |
| 41 | + bg: mode("brand.900", "brand.400")(props), | |
| 42 | + }, | |
| 43 | + _hover: { | |
| 44 | + bg: mode("brand.800", "brand.400")(props), | |
| 45 | + }, | |
| 46 | + }), | |
| 47 | + lightBrand: (props) => ({ | |
| 48 | + bg: mode("#F2EFFF", "whiteAlpha.100")(props), | |
| 49 | + color: mode("brand.500", "white")(props), | |
| 50 | + _focus: { | |
| 51 | + bg: mode("#F2EFFF", "whiteAlpha.100")(props), | |
| 52 | + }, | |
| 53 | + _active: { | |
| 54 | + bg: mode("secondaryGray.300", "whiteAlpha.100")(props), | |
| 55 | + }, | |
| 56 | + _hover: { | |
| 57 | + bg: mode("secondaryGray.400", "whiteAlpha.200")(props), | |
| 58 | + }, | |
| 59 | + }), | |
| 60 | + light: (props) => ({ | |
| 61 | + bg: mode("secondaryGray.400", "whiteAlpha.100")(props), | |
| 62 | + color: mode("secondaryGray.900", "white")(props), | |
| 63 | + _focus: { | |
| 64 | + bg: mode("secondaryGray.300", "whiteAlpha.100")(props), | |
| 65 | + }, | |
| 66 | + _active: { | |
| 67 | + bg: mode("secondaryGray.300", "whiteAlpha.100")(props), | |
| 68 | + }, | |
| 69 | + _hover: { | |
| 70 | + bg: mode("secondaryGray.400", "whiteAlpha.200")(props), | |
| 71 | + }, | |
| 72 | + }), | |
| 73 | + action: (props) => ({ | |
| 74 | + fontWeight: "500", | |
| 75 | + borderRadius: "50px", | |
| 76 | + bg: mode("secondaryGray.300", "brand.400")(props), | |
| 77 | + color: mode("brand.500", "white")(props), | |
| 78 | + _focus: { | |
| 79 | + bg: mode("secondaryGray.300", "brand.400")(props), | |
| 80 | + }, | |
| 81 | + _active: { bg: mode("secondaryGray.300", "brand.400")(props) }, | |
| 82 | + _hover: { | |
| 83 | + bg: mode("secondaryGray.200", "brand.400")(props), | |
| 84 | + }, | |
| 85 | + }), | |
| 86 | + setup: (props) => ({ | |
| 87 | + fontWeight: "500", | |
| 88 | + borderRadius: "50px", | |
| 89 | + bg: mode("transparent", "brand.400")(props), | |
| 90 | + border: mode("1px solid", "0px solid")(props), | |
| 91 | + borderColor: mode("secondaryGray.400", "transparent")(props), | |
| 92 | + color: mode("secondaryGray.900", "white")(props), | |
| 93 | + _focus: { | |
| 94 | + bg: mode("transparent", "brand.400")(props), | |
| 95 | + }, | |
| 96 | + _active: { bg: mode("transparent", "brand.400")(props) }, | |
| 97 | + _hover: { | |
| 98 | + bg: mode("secondaryGray.100", "brand.400")(props), | |
| 99 | + }, | |
| 100 | + }), | |
| 101 | + }, | |
| 102 | + }, | |
| 103 | + }, | |
| 104 | +}; |
+222-0Client/src/theme/components/input.js
| @@ -0,0 +1,222 @@ | ||
| 1 | +import { mode } from "@chakra-ui/theme-tools"; | |
| 2 | +export const inputStyles = { | |
| 3 | + components: { | |
| 4 | + Input: { | |
| 5 | + baseStyle: { | |
| 6 | + field: { | |
| 7 | + fontWeight: 400, | |
| 8 | + borderRadius: "8px", | |
| 9 | + }, | |
| 10 | + }, | |
| 11 | + | |
| 12 | + variants: { | |
| 13 | + main: (props) => ({ | |
| 14 | + field: { | |
| 15 | + bg: mode("transparent", "navy.800")(props), | |
| 16 | + border: "1px solid", | |
| 17 | + color: mode("secondaryGray.900", "white")(props), | |
| 18 | + borderColor: mode("secondaryGray.100", "whiteAlpha.100")(props), | |
| 19 | + borderRadius: "16px", | |
| 20 | + fontSize: "sm", | |
| 21 | + p: "20px", | |
| 22 | + _placeholder: { color: "secondaryGray.400" }, | |
| 23 | + }, | |
| 24 | + }), | |
| 25 | + auth: (props) => ({ | |
| 26 | + field: { | |
| 27 | + fontWeight: "500", | |
| 28 | + color: mode("navy.700", "white")(props), | |
| 29 | + bg: mode("transparent", "transparent")(props), | |
| 30 | + border: "1px solid", | |
| 31 | + borderColor: mode( | |
| 32 | + "secondaryGray.100", | |
| 33 | + "rgba(135, 140, 189, 0.3)" | |
| 34 | + )(props), | |
| 35 | + borderRadius: "16px", | |
| 36 | + _placeholder: { color: "secondaryGray.600", fontWeight: "400" }, | |
| 37 | + }, | |
| 38 | + }), | |
| 39 | + authSecondary: (props) => ({ | |
| 40 | + field: { | |
| 41 | + bg: "transparent", | |
| 42 | + border: "1px solid", | |
| 43 | + borderColor: "secondaryGray.100", | |
| 44 | + borderRadius: "16px", | |
| 45 | + _placeholder: { color: "secondaryGray.600" }, | |
| 46 | + }, | |
| 47 | + }), | |
| 48 | + search: (props) => ({ | |
| 49 | + field: { | |
| 50 | + border: "none", | |
| 51 | + py: "11px", | |
| 52 | + borderRadius: "inherit", | |
| 53 | + _placeholder: { color: "secondaryGray.600" }, | |
| 54 | + }, | |
| 55 | + }), | |
| 56 | + }, | |
| 57 | + }, | |
| 58 | + NumberInput: { | |
| 59 | + baseStyle: { | |
| 60 | + field: { | |
| 61 | + fontWeight: 400, | |
| 62 | + }, | |
| 63 | + }, | |
| 64 | + | |
| 65 | + variants: { | |
| 66 | + main: (props) => ({ | |
| 67 | + field: { | |
| 68 | + bg: "transparent", | |
| 69 | + border: "1px solid", | |
| 70 | + | |
| 71 | + borderColor: "secondaryGray.100", | |
| 72 | + borderRadius: "16px", | |
| 73 | + _placeholder: { color: "secondaryGray.600" }, | |
| 74 | + }, | |
| 75 | + }), | |
| 76 | + auth: (props) => ({ | |
| 77 | + field: { | |
| 78 | + bg: "transparent", | |
| 79 | + border: "1px solid", | |
| 80 | + | |
| 81 | + borderColor: "secondaryGray.100", | |
| 82 | + borderRadius: "16px", | |
| 83 | + _placeholder: { color: "secondaryGray.600" }, | |
| 84 | + }, | |
| 85 | + }), | |
| 86 | + authSecondary: (props) => ({ | |
| 87 | + field: { | |
| 88 | + bg: "transparent", | |
| 89 | + border: "1px solid", | |
| 90 | + | |
| 91 | + borderColor: "secondaryGray.100", | |
| 92 | + borderRadius: "16px", | |
| 93 | + _placeholder: { color: "secondaryGray.600" }, | |
| 94 | + }, | |
| 95 | + }), | |
| 96 | + search: (props) => ({ | |
| 97 | + field: { | |
| 98 | + border: "none", | |
| 99 | + py: "11px", | |
| 100 | + borderRadius: "inherit", | |
| 101 | + _placeholder: { color: "secondaryGray.600" }, | |
| 102 | + }, | |
| 103 | + }), | |
| 104 | + }, | |
| 105 | + }, | |
| 106 | + Select: { | |
| 107 | + baseStyle: { | |
| 108 | + field: { | |
| 109 | + fontWeight: 400, | |
| 110 | + }, | |
| 111 | + }, | |
| 112 | + | |
| 113 | + variants: { | |
| 114 | + main: (props) => ({ | |
| 115 | + field: { | |
| 116 | + bg: mode("transparent", "navy.800")(props), | |
| 117 | + border: "1px solid", | |
| 118 | + color: "secondaryGray.600", | |
| 119 | + borderColor: mode("secondaryGray.100", "whiteAlpha.100")(props), | |
| 120 | + borderRadius: "16px", | |
| 121 | + _placeholder: { color: "secondaryGray.600" }, | |
| 122 | + }, | |
| 123 | + icon: { | |
| 124 | + color: "secondaryGray.600", | |
| 125 | + }, | |
| 126 | + }), | |
| 127 | + mini: (props) => ({ | |
| 128 | + field: { | |
| 129 | + bg: mode("transparent", "navy.800")(props), | |
| 130 | + border: "0px solid transparent", | |
| 131 | + fontSize: "0px", | |
| 132 | + p: "10px", | |
| 133 | + _placeholder: { color: "secondaryGray.600" }, | |
| 134 | + }, | |
| 135 | + icon: { | |
| 136 | + color: "secondaryGray.600", | |
| 137 | + }, | |
| 138 | + }), | |
| 139 | + subtle: (props) => ({ | |
| 140 | + box: { | |
| 141 | + width: "unset", | |
| 142 | + }, | |
| 143 | + field: { | |
| 144 | + bg: "transparent", | |
| 145 | + border: "0px solid", | |
| 146 | + color: "secondaryGray.600", | |
| 147 | + borderColor: "transparent", | |
| 148 | + width: "max-content", | |
| 149 | + _placeholder: { color: "secondaryGray.600" }, | |
| 150 | + }, | |
| 151 | + icon: { | |
| 152 | + color: "secondaryGray.600", | |
| 153 | + }, | |
| 154 | + }), | |
| 155 | + transparent: (props) => ({ | |
| 156 | + field: { | |
| 157 | + bg: "transparent", | |
| 158 | + border: "0px solid", | |
| 159 | + width: "min-content", | |
| 160 | + color: mode("secondaryGray.600", "secondaryGray.600")(props), | |
| 161 | + borderColor: "transparent", | |
| 162 | + padding: "0px", | |
| 163 | + paddingLeft: "8px", | |
| 164 | + paddingRight: "20px", | |
| 165 | + fontWeight: "700", | |
| 166 | + fontSize: "14px", | |
| 167 | + _placeholder: { color: "secondaryGray.600" }, | |
| 168 | + }, | |
| 169 | + icon: { | |
| 170 | + transform: "none !important", | |
| 171 | + position: "unset !important", | |
| 172 | + width: "unset", | |
| 173 | + color: "secondaryGray.600", | |
| 174 | + right: "0px", | |
| 175 | + }, | |
| 176 | + }), | |
| 177 | + auth: (props) => ({ | |
| 178 | + field: { | |
| 179 | + bg: "transparent", | |
| 180 | + border: "1px solid", | |
| 181 | + | |
| 182 | + borderColor: "secondaryGray.100", | |
| 183 | + borderRadius: "16px", | |
| 184 | + _placeholder: { color: "secondaryGray.600" }, | |
| 185 | + }, | |
| 186 | + }), | |
| 187 | + authSecondary: (props) => ({ | |
| 188 | + field: { | |
| 189 | + bg: "transparent", | |
| 190 | + border: "1px solid", | |
| 191 | + | |
| 192 | + borderColor: "secondaryGray.100", | |
| 193 | + borderRadius: "16px", | |
| 194 | + _placeholder: { color: "secondaryGray.600" }, | |
| 195 | + }, | |
| 196 | + }), | |
| 197 | + search: (props) => ({ | |
| 198 | + field: { | |
| 199 | + border: "none", | |
| 200 | + py: "11px", | |
| 201 | + borderRadius: "inherit", | |
| 202 | + _placeholder: { color: "secondaryGray.600" }, | |
| 203 | + }, | |
| 204 | + }), | |
| 205 | + }, | |
| 206 | + }, | |
| 207 | + // PinInputField: { | |
| 208 | + // variants: { | |
| 209 | + // main: (props) => ({ | |
| 210 | + // field: { | |
| 211 | + // bg: "red !important", | |
| 212 | + // border: "1px solid", | |
| 213 | + // color: mode("secondaryGray.900", "white")(props), | |
| 214 | + // borderColor: mode("secondaryGray.100", "whiteAlpha.100")(props), | |
| 215 | + // borderRadius: "16px", | |
| 216 | + // _placeholder: { color: "secondaryGray.600" }, | |
| 217 | + // }, | |
| 218 | + // }), | |
| 219 | + // }, | |
| 220 | + // }, | |
| 221 | + }, | |
| 222 | +}; |
+26-0Client/src/theme/components/link.js
| @@ -0,0 +1,26 @@ | ||
| 1 | +export const linkStyles = { | |
| 2 | + components: { | |
| 3 | + | |
| 4 | + Link: { | |
| 5 | + baseStyle: { | |
| 6 | + textDecoration: "none", | |
| 7 | + boxShadow: "none", | |
| 8 | + _focus: { | |
| 9 | + boxShadow: "none" | |
| 10 | + }, | |
| 11 | + _active: { | |
| 12 | + boxShadow: "none" | |
| 13 | + }, | |
| 14 | + _hover: { | |
| 15 | + textDecoration: "none", | |
| 16 | + border: "none" | |
| 17 | + } | |
| 18 | + | |
| 19 | + }, | |
| 20 | + _hover: { | |
| 21 | + textDecoration: "none", | |
| 22 | + border: "none" | |
| 23 | + } | |
| 24 | + }, | |
| 25 | + }, | |
| 26 | + }; |
+43-0Client/src/theme/components/progress.js
| @@ -0,0 +1,43 @@ | ||
| 1 | +import { mode } from "@chakra-ui/theme-tools"; | |
| 2 | +export const progressStyles = { | |
| 3 | + components: { | |
| 4 | + Progress: { | |
| 5 | + baseStyle: { | |
| 6 | + field: { | |
| 7 | + fontWeight: 400, | |
| 8 | + w: "16px", | |
| 9 | + h: "16px", | |
| 10 | + borderRadius: "20px", | |
| 11 | + _checked: { transform: "translate(20px, 0px)" }, | |
| 12 | + }, | |
| 13 | + track: { | |
| 14 | + w: "40px", | |
| 15 | + h: "20px", | |
| 16 | + borderRadius: "20px", | |
| 17 | + _focus: { | |
| 18 | + boxShadow: "none", | |
| 19 | + }, | |
| 20 | + }, | |
| 21 | + }, | |
| 22 | + | |
| 23 | + variants: { | |
| 24 | + table: (props) => ({ | |
| 25 | + field: { | |
| 26 | + bg: "brand.500", | |
| 27 | + borderRadius: "16px", | |
| 28 | + fontSize: "sm", | |
| 29 | + }, | |
| 30 | + track: { | |
| 31 | + borderRadius: "20px", | |
| 32 | + bg: mode("blue.50", "whiteAlpha.50")(props), | |
| 33 | + h: "8px", | |
| 34 | + w: "54px", | |
| 35 | + }, | |
| 36 | + thumb: { | |
| 37 | + w: "250px", | |
| 38 | + }, | |
| 39 | + }), | |
| 40 | + }, | |
| 41 | + }, | |
| 42 | + }, | |
| 43 | +}; |
+23-0Client/src/theme/components/slider.js
| @@ -0,0 +1,23 @@ | ||
| 1 | +import { mode } from "@chakra-ui/theme-tools"; | |
| 2 | +export const sliderStyles = { | |
| 3 | + components: { | |
| 4 | + RangeSlider: { | |
| 5 | + // baseStyle: { | |
| 6 | + // thumb: { | |
| 7 | + // fontWeight: 400, | |
| 8 | + // }, | |
| 9 | + // track: { | |
| 10 | + // display: "flex", | |
| 11 | + // }, | |
| 12 | + // }, | |
| 13 | + | |
| 14 | + variants: { | |
| 15 | + main: (props) => ({ | |
| 16 | + thumb: { | |
| 17 | + bg: mode("brand.500", "brand.400")(props), | |
| 18 | + }, | |
| 19 | + }), | |
| 20 | + }, | |
| 21 | + }, | |
| 22 | + }, | |
| 23 | +}; |
+36-0Client/src/theme/components/switch.js
| @@ -0,0 +1,36 @@ | ||
| 1 | +import { mode } from "@chakra-ui/theme-tools"; | |
| 2 | +export const switchStyles = { | |
| 3 | + components: { | |
| 4 | + Switch: { | |
| 5 | + baseStyle: { | |
| 6 | + thumb: { | |
| 7 | + fontWeight: 400, | |
| 8 | + borderRadius: "50%", | |
| 9 | + w: "16px", | |
| 10 | + h: "16px", | |
| 11 | + _checked: { transform: "translate(20px, 0px)" }, | |
| 12 | + }, | |
| 13 | + track: { | |
| 14 | + display: "flex", | |
| 15 | + alignItems: "center", | |
| 16 | + boxSizing: "border-box", | |
| 17 | + w: "40px", | |
| 18 | + h: "20px", | |
| 19 | + p: "2px", | |
| 20 | + ps: "2px", | |
| 21 | + _focus: { | |
| 22 | + boxShadow: "none", | |
| 23 | + }, | |
| 24 | + }, | |
| 25 | + }, | |
| 26 | + | |
| 27 | + variants: { | |
| 28 | + main: (props) => ({ | |
| 29 | + track: { | |
| 30 | + bg: mode("gray.300", "navy.700")(props), | |
| 31 | + }, | |
| 32 | + }), | |
| 33 | + }, | |
| 34 | + }, | |
| 35 | + }, | |
| 36 | +}; |
+55-0Client/src/theme/components/textarea.js
| @@ -0,0 +1,55 @@ | ||
| 1 | +import { mode } from "@chakra-ui/theme-tools"; | |
| 2 | +export const textareaStyles = { | |
| 3 | + components: { | |
| 4 | + Textarea: { | |
| 5 | + baseStyle: { | |
| 6 | + field: { | |
| 7 | + fontWeight: 400, | |
| 8 | + borderRadius: "8px", | |
| 9 | + }, | |
| 10 | + }, | |
| 11 | + | |
| 12 | + variants: { | |
| 13 | + main: (props) => ({ | |
| 14 | + field: { | |
| 15 | + bg: mode("transparent", "navy.800")(props), | |
| 16 | + border: "1px solid !important", | |
| 17 | + color: mode("secondaryGray.900", "white")(props), | |
| 18 | + borderColor: mode("secondaryGray.100", "whiteAlpha.100")(props), | |
| 19 | + borderRadius: "16px", | |
| 20 | + fontSize: "sm", | |
| 21 | + p: "20px", | |
| 22 | + _placeholder: { color: "secondaryGray.400" }, | |
| 23 | + }, | |
| 24 | + }), | |
| 25 | + auth: (props) => ({ | |
| 26 | + field: { | |
| 27 | + bg: "white", | |
| 28 | + border: "1px solid", | |
| 29 | + borderColor: "secondaryGray.100", | |
| 30 | + borderRadius: "16px", | |
| 31 | + _placeholder: { color: "secondaryGray.600" }, | |
| 32 | + }, | |
| 33 | + }), | |
| 34 | + authSecondary: (props) => ({ | |
| 35 | + field: { | |
| 36 | + bg: "white", | |
| 37 | + border: "1px solid", | |
| 38 | + | |
| 39 | + borderColor: "secondaryGray.100", | |
| 40 | + borderRadius: "16px", | |
| 41 | + _placeholder: { color: "secondaryGray.600" }, | |
| 42 | + }, | |
| 43 | + }), | |
| 44 | + search: (props) => ({ | |
| 45 | + field: { | |
| 46 | + border: "none", | |
| 47 | + py: "11px", | |
| 48 | + borderRadius: "inherit", | |
| 49 | + _placeholder: { color: "secondaryGray.600" }, | |
| 50 | + }, | |
| 51 | + }), | |
| 52 | + }, | |
| 53 | + }, | |
| 54 | + }, | |
| 55 | +}; |
+12-0Client/src/theme/foundations/blur.js
| @@ -0,0 +1,12 @@ | ||
| 1 | +const blur = { | |
| 2 | + none: '0', | |
| 3 | + sm: '4px', | |
| 4 | + base: '8px', | |
| 5 | + md: '12px', | |
| 6 | + lg: '16px', | |
| 7 | + xl: '24px', | |
| 8 | + '2xl': '40px', | |
| 9 | + '3xl': '64px', | |
| 10 | +} | |
| 11 | + | |
| 12 | +export default blur |
+9-0Client/src/theme/foundations/borders.js
| @@ -0,0 +1,9 @@ | ||
| 1 | +const borders = { | |
| 2 | + none: 0, | |
| 3 | + '1px': '1px solid', | |
| 4 | + '2px': '2px solid', | |
| 5 | + '4px': '4px solid', | |
| 6 | + '8px': '8px solid', | |
| 7 | +} | |
| 8 | + | |
| 9 | +export default borders |
+14-0Client/src/theme/foundations/breakpoints.js
| @@ -0,0 +1,14 @@ | ||
| 1 | +import { createBreakpoints } from '@chakra-ui/theme-tools' | |
| 2 | + | |
| 3 | +export const breakpoints = createBreakpoints({ | |
| 4 | + base: '0em', | |
| 5 | + sm: '320px', | |
| 6 | + md: '768px', | |
| 7 | + lg: '960px', | |
| 8 | + xl: '1200px', | |
| 9 | + '2xl': '1600px', | |
| 10 | + '2sm': '380px', | |
| 11 | + '3xl': '1920px', | |
| 12 | +}) | |
| 13 | + | |
| 14 | +// export default breakpoints |
+284-0Client/src/theme/foundations/colors.js
| @@ -0,0 +1,284 @@ | ||
| 1 | +const colors = { | |
| 2 | + transparent: 'transparent', | |
| 3 | + current: 'currentColor', | |
| 4 | + black: '#000000', | |
| 5 | + white: '#FFFFFF', | |
| 6 | + whiteAlpha: { | |
| 7 | + 50: 'rgba(255, 255, 255, 0.04)', | |
| 8 | + 100: 'rgba(255, 255, 255, 0.06)', | |
| 9 | + 200: 'rgba(255, 255, 255, 0.08)', | |
| 10 | + 300: 'rgba(255, 255, 255, 0.16)', | |
| 11 | + 400: 'rgba(255, 255, 255, 0.24)', | |
| 12 | + 500: 'rgba(255, 255, 255, 0.36)', | |
| 13 | + 600: 'rgba(255, 255, 255, 0.48)', | |
| 14 | + 700: 'rgba(255, 255, 255, 0.64)', | |
| 15 | + 800: 'rgba(255, 255, 255, 0.80)', | |
| 16 | + 900: 'rgba(255, 255, 255, 0.92)', | |
| 17 | + }, | |
| 18 | + blackAlpha: { | |
| 19 | + 50: 'rgba(0, 0, 0, 0.04)', | |
| 20 | + 100: 'rgba(0, 0, 0, 0.06)', | |
| 21 | + 200: 'rgba(0, 0, 0, 0.08)', | |
| 22 | + 300: 'rgba(0, 0, 0, 0.16)', | |
| 23 | + 400: 'rgba(0, 0, 0, 0.24)', | |
| 24 | + 500: 'rgba(0, 0, 0, 0.36)', | |
| 25 | + 600: 'rgba(0, 0, 0, 0.48)', | |
| 26 | + 700: 'rgba(0, 0, 0, 0.64)', | |
| 27 | + 800: 'rgba(0, 0, 0, 0.80)', | |
| 28 | + 900: 'rgba(0, 0, 0, 0.92)', | |
| 29 | + }, | |
| 30 | + gray: { | |
| 31 | + 50: '#F7FAFC', | |
| 32 | + 100: '#FAFCFE', | |
| 33 | + 200: '#E2E8F0', | |
| 34 | + 300: '#CBD5E0', | |
| 35 | + 400: '#A0AEC0', | |
| 36 | + 500: '#718096', | |
| 37 | + 600: '#4A5568', | |
| 38 | + 700: '#2D3748', | |
| 39 | + 800: '#1A202C', | |
| 40 | + 900: '#171923', | |
| 41 | + }, | |
| 42 | + red: { | |
| 43 | + 50: '#fee9e7', | |
| 44 | + 100: '#fbbcb7', | |
| 45 | + 200: '#faa69f', | |
| 46 | + 300: '#f89086', | |
| 47 | + 400: '#f77a6e', | |
| 48 | + 500: 'rgba(244, 77, 62, 1)', | |
| 49 | + 600: '#f1200e', | |
| 50 | + 700: '#d91d0c', | |
| 51 | + 800: '#c11a0b', | |
| 52 | + 900: '#911308', | |
| 53 | + }, | |
| 54 | + orange: { | |
| 55 | + 50: '#FFFAF0', | |
| 56 | + 100: '#FFF6DA', | |
| 57 | + 200: '#FBD38D', | |
| 58 | + 300: '#F6AD55', | |
| 59 | + 400: '#ED8936', | |
| 60 | + 500: '#FFB547', | |
| 61 | + 600: '#C05621', | |
| 62 | + 700: '#9C4221', | |
| 63 | + 800: '#7B341E', | |
| 64 | + 900: '#652B19', | |
| 65 | + }, | |
| 66 | + yellow: { | |
| 67 | + 50: '#FFFFF0', | |
| 68 | + 100: '#FEFCBF', | |
| 69 | + 200: '#FAF089', | |
| 70 | + 300: '#F6E05E', | |
| 71 | + 400: '#ECC94B', | |
| 72 | + 500: '#D69E2E', | |
| 73 | + 600: '#B7791F', | |
| 74 | + 700: '#975A16', | |
| 75 | + 800: '#744210', | |
| 76 | + 900: '#5F370E', | |
| 77 | + }, | |
| 78 | + green: { | |
| 79 | + 50: '#b9f2dd', | |
| 80 | + 100: '#8ee9c8', | |
| 81 | + 200: '#79e5bd', | |
| 82 | + 300: '#63e1b3', | |
| 83 | + 400: '#4edda8', | |
| 84 | + 500: 'rgba(40, 208, 146, 1)', | |
| 85 | + 600: '#20a574', | |
| 86 | + 700: '#1c9065', | |
| 87 | + 800: '#187a56', | |
| 88 | + 900: '#0f5038', | |
| 89 | + }, | |
| 90 | + teal: { | |
| 91 | + 50: '#E6FFFA', | |
| 92 | + 100: '#B2F5EA', | |
| 93 | + 200: '#81E6D9', | |
| 94 | + 300: '#4FD1C5', | |
| 95 | + 400: '#38B2AC', | |
| 96 | + 500: '#319795', | |
| 97 | + 600: '#2C7A7B', | |
| 98 | + 700: '#285E61', | |
| 99 | + 800: '#234E52', | |
| 100 | + 900: '#1D4044', | |
| 101 | + }, | |
| 102 | + blue: { | |
| 103 | + 50: '#e0f9e8', | |
| 104 | + 100: '#b5f0c9', | |
| 105 | + 200: '#a0ecb9', | |
| 106 | + 300: '#8be8aa', | |
| 107 | + 400: '#76e49a', | |
| 108 | + 500: 'rgba(75, 219, 123, 1)', | |
| 109 | + 600: '#28ca5f', | |
| 110 | + 700: '#24b555', | |
| 111 | + 800: '#20a04b', | |
| 112 | + 900: '#177537', | |
| 113 | + }, | |
| 114 | + cyan: { | |
| 115 | + 50: '#EDFDFD', | |
| 116 | + 100: '#C4F1F9', | |
| 117 | + 200: '#9DECF9', | |
| 118 | + 300: '#76E4F7', | |
| 119 | + 400: '#0BC5EA', | |
| 120 | + 500: '#00B5D8', | |
| 121 | + 600: '#00A3C4', | |
| 122 | + 700: '#0987A0', | |
| 123 | + 800: '#086F83', | |
| 124 | + 900: '#065666', | |
| 125 | + }, | |
| 126 | + purple: { | |
| 127 | + 50: '#aee2c9', | |
| 128 | + 100: '#89d4b0', | |
| 129 | + 200: '#76cda4', | |
| 130 | + 300: '#63c697', | |
| 131 | + 400: '#51bf8b', | |
| 132 | + 500: 'rgba(59, 162, 113, 1)', | |
| 133 | + 600: '#2d7d57', | |
| 134 | + 700: '#276a4a', | |
| 135 | + 800: '#20573d', | |
| 136 | + 900: '#123223', | |
| 137 | + }, | |
| 138 | + pink: { | |
| 139 | + 50: '#ffffff', | |
| 140 | + 100: '#fbd7e9', | |
| 141 | + 200: '#f8c0dd', | |
| 142 | + 300: '#f6a8d0', | |
| 143 | + 400: '#f491c4', | |
| 144 | + 500: 'rgba(239, 99, 171, 1)', | |
| 145 | + 600: '#ea3592', | |
| 146 | + 700: '#e81e86', | |
| 147 | + 800: '#d61679', | |
| 148 | + 900: '#a8115f', | |
| 149 | + }, | |
| 150 | + linkedin: { | |
| 151 | + 50: '#E8F4F9', | |
| 152 | + 100: '#CFEDFB', | |
| 153 | + 200: '#9BDAF3', | |
| 154 | + 300: '#68C7EC', | |
| 155 | + 400: '#34B3E4', | |
| 156 | + 500: '#00A0DC', | |
| 157 | + 600: '#008CC9', | |
| 158 | + 700: '#0077B5', | |
| 159 | + 800: '#005E93', | |
| 160 | + 900: '#004471', | |
| 161 | + }, | |
| 162 | + facebook: { | |
| 163 | + 50: '#E8F4F9', | |
| 164 | + 100: '#D9DEE9', | |
| 165 | + 200: '#B7C2DA', | |
| 166 | + 300: '#6482C0', | |
| 167 | + 400: '#4267B2', | |
| 168 | + 500: '#385898', | |
| 169 | + 600: '#314E89', | |
| 170 | + 700: '#29487D', | |
| 171 | + 800: '#223B67', | |
| 172 | + 900: '#1E355B', | |
| 173 | + }, | |
| 174 | + messenger: { | |
| 175 | + 50: '#D0E6FF', | |
| 176 | + 100: '#B9DAFF', | |
| 177 | + 200: '#A2CDFF', | |
| 178 | + 300: '#7AB8FF', | |
| 179 | + 400: '#2E90FF', | |
| 180 | + 500: '#0078FF', | |
| 181 | + 600: '#0063D1', | |
| 182 | + 700: '#0052AC', | |
| 183 | + 800: '#003C7E', | |
| 184 | + 900: '#002C5C', | |
| 185 | + }, | |
| 186 | + whatsapp: { | |
| 187 | + 50: '#dffeec', | |
| 188 | + 100: '#b9f5d0', | |
| 189 | + 200: '#90edb3', | |
| 190 | + 300: '#65e495', | |
| 191 | + 400: '#3cdd78', | |
| 192 | + 500: '#22c35e', | |
| 193 | + 600: '#179848', | |
| 194 | + 700: '#0c6c33', | |
| 195 | + 800: '#01421c', | |
| 196 | + 900: '#001803', | |
| 197 | + }, | |
| 198 | + twitter: { | |
| 199 | + 50: '#E5F4FD', | |
| 200 | + 100: '#C8E9FB', | |
| 201 | + 200: '#A8DCFA', | |
| 202 | + 300: '#83CDF7', | |
| 203 | + 400: '#57BBF5', | |
| 204 | + 500: '#1DA1F2', | |
| 205 | + 600: '#1A94DA', | |
| 206 | + 700: '#1681BF', | |
| 207 | + 800: '#136B9E', | |
| 208 | + 900: '#0D4D71', | |
| 209 | + }, | |
| 210 | + telegram: { | |
| 211 | + 50: '#E3F2F9', | |
| 212 | + 100: '#C5E4F3', | |
| 213 | + 200: '#A2D4EC', | |
| 214 | + 300: '#7AC1E4', | |
| 215 | + 400: '#47A9DA', | |
| 216 | + 500: '#0088CC', | |
| 217 | + 600: '#007AB8', | |
| 218 | + 700: '#006BA1', | |
| 219 | + 800: '#005885', | |
| 220 | + 900: '#003F5E', | |
| 221 | + }, | |
| 222 | + brand: { | |
| 223 | + 50: '#bbb4f4', | |
| 224 | + 100: '#9387ed', | |
| 225 | + 200: '#7f71ea', | |
| 226 | + 300: '#6b5be6', | |
| 227 | + 400: '#5745e3', | |
| 228 | + 500: 'rgba(53, 32, 213, 1)', | |
| 229 | + 600: '#2a19a9', | |
| 230 | + 700: '#241692', | |
| 231 | + 800: '#1f137c', | |
| 232 | + 900: '#140c50', | |
| 233 | + }, | |
| 234 | + brandScheme: { | |
| 235 | + 50: '#7959f5', | |
| 236 | + 100: '#5229f2', | |
| 237 | + 200: '#3e11f0', | |
| 238 | + 300: '#370dda', | |
| 239 | + 400: '#310cc2', | |
| 240 | + 500: 'rgba(37, 9, 146, 1)', | |
| 241 | + 600: '#190662', | |
| 242 | + 700: '#13054a', | |
| 243 | + 800: '#0d0332', | |
| 244 | + 900: '#000002', | |
| 245 | + }, | |
| 246 | + brandTabs: { | |
| 247 | + 50: '#270bff', | |
| 248 | + 100: '#1800d7', | |
| 249 | + 200: '#1600be', | |
| 250 | + 300: '#1300a4', | |
| 251 | + 400: '#10008b', | |
| 252 | + 500: 'rgba(10, 0, 88, 1)', | |
| 253 | + 600: '#040025', | |
| 254 | + 700: '#01000b', | |
| 255 | + 800: '#000000', | |
| 256 | + 900: '#000000', | |
| 257 | + }, | |
| 258 | + secondaryGray: { | |
| 259 | + 50: '#ffffff', | |
| 260 | + 100: '#fbfbfc', | |
| 261 | + 200: '#ecedf1', | |
| 262 | + 300: '#dddfe7', | |
| 263 | + 400: '#ced2dc', | |
| 264 | + 500: 'rgba(176, 182, 199, 1)', | |
| 265 | + 600: '#929ab2', | |
| 266 | + 700: '#838da7', | |
| 267 | + 800: '#747f9d', | |
| 268 | + 900: '#5c6682', | |
| 269 | + }, | |
| 270 | + navy: { | |
| 271 | + 50: '#4bfad4', | |
| 272 | + 100: '#19f9c8', | |
| 273 | + 200: '#07f1be', | |
| 274 | + 300: '#06d8ab', | |
| 275 | + 400: '#05c097', | |
| 276 | + 500: 'rgba(4, 142, 112, 1)', | |
| 277 | + 600: '#035c49', | |
| 278 | + 700: '#024435', | |
| 279 | + 800: '#012b22', | |
| 280 | + 900: '#000000', | |
| 281 | + }, | |
| 282 | +} | |
| 283 | + | |
| 284 | +export default colors |
+27-0Client/src/theme/foundations/index.js
| @@ -0,0 +1,27 @@ | ||
| 1 | +import borders from './borders' | |
| 2 | +import breakpoints from './breakpoints' | |
| 3 | +import colors from './colors' | |
| 4 | +import radii from './radius' | |
| 5 | +import shadows from './shadows' | |
| 6 | +import sizes from './sizes' | |
| 7 | +import spacing from './spacing' | |
| 8 | +import transition from './transition' | |
| 9 | +import typography from './typography' | |
| 10 | +import zIndices from './z-index' | |
| 11 | +import blur from './blur' | |
| 12 | + | |
| 13 | +const foundations = { | |
| 14 | + breakpoints, | |
| 15 | + zIndices, | |
| 16 | + radii, | |
| 17 | + blur, | |
| 18 | + colors, | |
| 19 | + ...typography, | |
| 20 | + sizes, | |
| 21 | + shadows, | |
| 22 | + space: spacing, | |
| 23 | + borders, | |
| 24 | + transition, | |
| 25 | +} | |
| 26 | + | |
| 27 | +export default foundations |
+13-0Client/src/theme/foundations/radius.js
| @@ -0,0 +1,13 @@ | ||
| 1 | +const radii = { | |
| 2 | + none: '0', | |
| 3 | + sm: '0.125rem', | |
| 4 | + base: '0.25rem', | |
| 5 | + md: '0.375rem', | |
| 6 | + lg: '0.5rem', | |
| 7 | + xl: '0.75rem', | |
| 8 | + '2xl': '1rem', | |
| 9 | + '3xl': '1.5rem', | |
| 10 | + full: '9999px', | |
| 11 | +} | |
| 12 | + | |
| 13 | +export default radii |
+15-0Client/src/theme/foundations/shadows.js
| @@ -0,0 +1,15 @@ | ||
| 1 | +const shadows = { | |
| 2 | + xs: '0 0 0 1px rgba(0, 0, 0, 0.05)', | |
| 3 | + sm: '0 1px 2px 0 rgba(0, 0, 0, 0.05)', | |
| 4 | + base: '0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06)', | |
| 5 | + md: '0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)', | |
| 6 | + lg: '0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05)', | |
| 7 | + xl: '0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04)', | |
| 8 | + '2xl': '0 25px 50px -12px rgba(0, 0, 0, 0.25)', | |
| 9 | + outline: '0 0 0 3px rgba(66, 153, 225, 0.6)', | |
| 10 | + inner: 'inset 0 2px 4px 0 rgba(0,0,0,0.06)', | |
| 11 | + none: 'none', | |
| 12 | + 'dark-lg': 'rgba(0, 0, 0, 0.1) 0px 0px 0px 1px, rgba(0, 0, 0, 0.2) 0px 5px 10px, rgba(0, 0, 0, 0.4) 0px 15px 40px', | |
| 13 | +} | |
| 14 | + | |
| 15 | +export default shadows |
+60-0Client/src/theme/foundations/sizes.js
| @@ -0,0 +1,60 @@ | ||
| 1 | +const sizes = { | |
| 2 | + 1: '0.25rem', | |
| 3 | + 2: '0.5rem', | |
| 4 | + 3: '0.75rem', | |
| 5 | + 4: '1rem', | |
| 6 | + 5: '1.25rem', | |
| 7 | + 6: '1.5rem', | |
| 8 | + 7: '1.75rem', | |
| 9 | + 8: '2rem', | |
| 10 | + 9: '2.25rem', | |
| 11 | + 10: '2.5rem', | |
| 12 | + 12: '3rem', | |
| 13 | + 14: '3.5rem', | |
| 14 | + 16: '4rem', | |
| 15 | + 20: '5rem', | |
| 16 | + 24: '6rem', | |
| 17 | + 28: '7rem', | |
| 18 | + 32: '8rem', | |
| 19 | + 36: '9rem', | |
| 20 | + 40: '10rem', | |
| 21 | + 44: '11rem', | |
| 22 | + 48: '12rem', | |
| 23 | + 52: '13rem', | |
| 24 | + 56: '14rem', | |
| 25 | + 60: '15rem', | |
| 26 | + 64: '16rem', | |
| 27 | + 72: '18rem', | |
| 28 | + 80: '20rem', | |
| 29 | + 96: '24rem', | |
| 30 | + px: '1px', | |
| 31 | + '0.5': '0.125rem', | |
| 32 | + '1.5': '0.375rem', | |
| 33 | + '2.5': '0.625rem', | |
| 34 | + '3.5': '0.875rem', | |
| 35 | + max: 'max-content', | |
| 36 | + min: 'min-content', | |
| 37 | + full: '100%', | |
| 38 | + '3xs': '14rem', | |
| 39 | + '2xs': '16rem', | |
| 40 | + xs: '20rem', | |
| 41 | + sm: '24rem', | |
| 42 | + md: '28rem', | |
| 43 | + lg: '32rem', | |
| 44 | + xl: '36rem', | |
| 45 | + '2xl': '42rem', | |
| 46 | + '3xl': '48rem', | |
| 47 | + '4xl': '56rem', | |
| 48 | + '5xl': '64rem', | |
| 49 | + '6xl': '72rem', | |
| 50 | + '7xl': '80rem', | |
| 51 | + '8xl': '90rem', | |
| 52 | + container: { | |
| 53 | + sm: '640px', | |
| 54 | + md: '768px', | |
| 55 | + lg: '1024px', | |
| 56 | + xl: '1280px', | |
| 57 | + }, | |
| 58 | +} | |
| 59 | + | |
| 60 | +export default sizes |
+37-0Client/src/theme/foundations/spacing.js
| @@ -0,0 +1,37 @@ | ||
| 1 | +const spacing = { | |
| 2 | + 1: '0.25rem', | |
| 3 | + 2: '0.5rem', | |
| 4 | + 3: '0.75rem', | |
| 5 | + 4: '1rem', | |
| 6 | + 5: '1.25rem', | |
| 7 | + 6: '1.5rem', | |
| 8 | + 7: '1.75rem', | |
| 9 | + 8: '2rem', | |
| 10 | + 9: '2.25rem', | |
| 11 | + 10: '2.5rem', | |
| 12 | + 12: '3rem', | |
| 13 | + 14: '3.5rem', | |
| 14 | + 16: '4rem', | |
| 15 | + 20: '5rem', | |
| 16 | + 24: '6rem', | |
| 17 | + 28: '7rem', | |
| 18 | + 32: '8rem', | |
| 19 | + 36: '9rem', | |
| 20 | + 40: '10rem', | |
| 21 | + 44: '11rem', | |
| 22 | + 48: '12rem', | |
| 23 | + 52: '13rem', | |
| 24 | + 56: '14rem', | |
| 25 | + 60: '15rem', | |
| 26 | + 64: '16rem', | |
| 27 | + 72: '18rem', | |
| 28 | + 80: '20rem', | |
| 29 | + 96: '24rem', | |
| 30 | + px: '1px', | |
| 31 | + '0.5': '0.125rem', | |
| 32 | + '1.5': '0.375rem', | |
| 33 | + '2.5': '0.625rem', | |
| 34 | + '3.5': '0.875rem', | |
| 35 | +} | |
| 36 | + | |
| 37 | +export default spacing |
+25-0Client/src/theme/foundations/transition.js
| @@ -0,0 +1,25 @@ | ||
| 1 | +const transition = { | |
| 2 | + property: { | |
| 3 | + common: 'background-color, border-color, color, fill, stroke, opacity, box-shadow, transform', | |
| 4 | + colors: 'background-color, border-color, color, fill, stroke', | |
| 5 | + dimensions: 'width, height', | |
| 6 | + position: 'left, right, top, bottom', | |
| 7 | + background: 'background-color, background-image, background-position', | |
| 8 | + }, | |
| 9 | + easing: { | |
| 10 | + 'ease-in': 'cubic-bezier(0.4, 0, 1, 1)', | |
| 11 | + 'ease-out': 'cubic-bezier(0, 0, 0.2, 1)', | |
| 12 | + 'ease-in-out': 'cubic-bezier(0.4, 0, 0.2, 1)', | |
| 13 | + }, | |
| 14 | + duration: { | |
| 15 | + 'ultra-fast': '50ms', | |
| 16 | + faster: '100ms', | |
| 17 | + fast: '150ms', | |
| 18 | + normal: '200ms', | |
| 19 | + slow: '300ms', | |
| 20 | + slower: '400ms', | |
| 21 | + 'ultra-slow': '500ms', | |
| 22 | + }, | |
| 23 | +} | |
| 24 | + | |
| 25 | +export default transition |
+60-0Client/src/theme/foundations/typography.js
| @@ -0,0 +1,60 @@ | ||
| 1 | +const typography = { | |
| 2 | + letterSpacing: { | |
| 3 | + tighter: '-0.05em', | |
| 4 | + tight: '-0.025em', | |
| 5 | + normal: '0', | |
| 6 | + wide: '0.025em', | |
| 7 | + wider: '0.05em', | |
| 8 | + widest: '0.1em', | |
| 9 | + }, | |
| 10 | + lineHeights: { | |
| 11 | + 3: '.75rem', | |
| 12 | + 4: '1rem', | |
| 13 | + 5: '1.25rem', | |
| 14 | + 6: '1.5rem', | |
| 15 | + 7: '1.75rem', | |
| 16 | + 8: '2rem', | |
| 17 | + 9: '2.25rem', | |
| 18 | + 10: '2.5rem', | |
| 19 | + normal: 'normal', | |
| 20 | + none: '1', | |
| 21 | + shorter: '1.25', | |
| 22 | + short: '1.375', | |
| 23 | + base: '1.5', | |
| 24 | + tall: '1.625', | |
| 25 | + taller: '2', | |
| 26 | + }, | |
| 27 | + fontWeights: { | |
| 28 | + hairline: '100', | |
| 29 | + thin: '200', | |
| 30 | + light: '300', | |
| 31 | + normal: '400', | |
| 32 | + medium: '500', | |
| 33 | + semibold: '600', | |
| 34 | + bold: '700', | |
| 35 | + extrabold: '800', | |
| 36 | + black: '900', | |
| 37 | + }, | |
| 38 | + fonts: { | |
| 39 | + heading: '-apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"', | |
| 40 | + body: '-apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"', | |
| 41 | + mono: 'SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace', | |
| 42 | + }, | |
| 43 | + fontSizes: { | |
| 44 | + xs: '0.75rem', | |
| 45 | + sm: '0.875rem', | |
| 46 | + md: '1rem', | |
| 47 | + lg: '1.125rem', | |
| 48 | + xl: '1.25rem', | |
| 49 | + '2xl': '1.5rem', | |
| 50 | + '3xl': '1.875rem', | |
| 51 | + '4xl': '2.25rem', | |
| 52 | + '5xl': '3rem', | |
| 53 | + '6xl': '3.75rem', | |
| 54 | + '7xl': '4.5rem', | |
| 55 | + '8xl': '6rem', | |
| 56 | + '9xl': '8rem', | |
| 57 | + }, | |
| 58 | +} | |
| 59 | + | |
| 60 | +export default typography |
+17-0Client/src/theme/foundations/z-index.js
| @@ -0,0 +1,17 @@ | ||
| 1 | +const zIndices = { | |
| 2 | + hide: -1, | |
| 3 | + auto: 'auto', | |
| 4 | + base: 0, | |
| 5 | + docked: 10, | |
| 6 | + dropdown: 1000, | |
| 7 | + sticky: 1100, | |
| 8 | + banner: 1200, | |
| 9 | + overlay: 1300, | |
| 10 | + modal: 1400, | |
| 11 | + popover: 1500, | |
| 12 | + skipLink: 1600, | |
| 13 | + toast: 1700, | |
| 14 | + tooltip: 1800, | |
| 15 | +} | |
| 16 | + | |
| 17 | +export default zIndices |
+18-0Client/src/theme/index.js
| @@ -0,0 +1,18 @@ | ||
| 1 | +import { extendTheme } from '@chakra-ui/react' | |
| 2 | +import foundations from './foundations' | |
| 3 | + | |
| 4 | +const direction = 'ltr' | |
| 5 | + | |
| 6 | +const config = { | |
| 7 | + useSystemColorMode: false, | |
| 8 | + initialColorMode: 'light', | |
| 9 | + cssVarPrefix: 'chakra', | |
| 10 | +} | |
| 11 | + | |
| 12 | +export const theme = { | |
| 13 | + direction, | |
| 14 | + ...foundations, | |
| 15 | + config, | |
| 16 | +} | |
| 17 | + | |
| 18 | +export default extendTheme(theme) |
+107-0Client/src/theme/styles.js
| @@ -0,0 +1,107 @@ | ||
| 1 | +import { mode } from "@chakra-ui/theme-tools"; | |
| 2 | +export const globalStyles = { | |
| 3 | + colors: { | |
| 4 | + brand: { | |
| 5 | + 100: "#E9E3FF", | |
| 6 | + 200: "#422AFB", | |
| 7 | + 300: "#422AFB", | |
| 8 | + 400: "#7551FF", | |
| 9 | + 500: "#422AFB", | |
| 10 | + 600: "#3311DB", | |
| 11 | + 700: "#02044A", | |
| 12 | + 800: "#190793", | |
| 13 | + 900: "#11047A", | |
| 14 | + }, | |
| 15 | + brandScheme: { | |
| 16 | + 100: "#E9E3FF", | |
| 17 | + 200: "#7551FF", | |
| 18 | + 300: "#7551FF", | |
| 19 | + 400: "#7551FF", | |
| 20 | + 500: "#422AFB", | |
| 21 | + 600: "#3311DB", | |
| 22 | + 700: "#02044A", | |
| 23 | + 800: "#190793", | |
| 24 | + 900: "#02044A", | |
| 25 | + }, | |
| 26 | + brandTabs: { | |
| 27 | + 100: "#E9E3FF", | |
| 28 | + 200: "#422AFB", | |
| 29 | + 300: "#422AFB", | |
| 30 | + 400: "#422AFB", | |
| 31 | + 500: "#422AFB", | |
| 32 | + 600: "#3311DB", | |
| 33 | + 700: "#02044A", | |
| 34 | + 800: "#190793", | |
| 35 | + 900: "#02044A", | |
| 36 | + }, | |
| 37 | + secondaryGray: { | |
| 38 | + 100: "#E0E5F2", | |
| 39 | + 200: "#E1E9F8", | |
| 40 | + 300: "#F4F7FE", | |
| 41 | + 400: "#E9EDF7", | |
| 42 | + 500: "#8F9BBA", | |
| 43 | + 600: "#A3AED0", | |
| 44 | + 700: "#707EAE", | |
| 45 | + 800: "#707EAE", | |
| 46 | + 900: "#1B2559", | |
| 47 | + }, | |
| 48 | + red: { | |
| 49 | + 100: "#FEEFEE", | |
| 50 | + 300: "#eb7b74", | |
| 51 | + 500: "#EE5D50", | |
| 52 | + 600: "#E31A1A", | |
| 53 | + }, | |
| 54 | + blue: { | |
| 55 | + 50: "#EFF4FB", | |
| 56 | + 500: "#3965FF", | |
| 57 | + }, | |
| 58 | + orange: { | |
| 59 | + 100: "#FFF6DA", | |
| 60 | + 400: "#fde04ce8", | |
| 61 | + 500: "#FFB547", | |
| 62 | + }, | |
| 63 | + green: { | |
| 64 | + 100: "#E6FAF5", | |
| 65 | + 500: "#01B574", | |
| 66 | + }, | |
| 67 | + navy: { | |
| 68 | + 50: "#d0dcfb", | |
| 69 | + 100: "#aac0fe", | |
| 70 | + 200: "#a3b9f8", | |
| 71 | + 300: "#728fea", | |
| 72 | + 400: "#3652ba", | |
| 73 | + 500: "#1b3bbb", | |
| 74 | + 600: "#24388a", | |
| 75 | + 700: "#1B254B", | |
| 76 | + 800: "#111c44", | |
| 77 | + 900: "#0b1437", | |
| 78 | + }, | |
| 79 | + gray: { | |
| 80 | + 100: '#FAFCFE', | |
| 81 | + 200: '#E2E8F0', | |
| 82 | + 300: '#CBD5E0', | |
| 83 | + 400: '#A0AEC0', | |
| 84 | + 500: '#718096', | |
| 85 | + 600: '#4A5568', | |
| 86 | + 700: '#2D3748', | |
| 87 | + 800: '#1A202C', | |
| 88 | + 900: '#171923', | |
| 89 | + }, | |
| 90 | + }, | |
| 91 | + styles: { | |
| 92 | + global: (props) => ({ | |
| 93 | + body: { | |
| 94 | + overflowX: "hidden", | |
| 95 | + bg: mode("secondaryGray.300", "navy.900")(props), | |
| 96 | + fontFamily: "Inter,sans-serif", | |
| 97 | + letterSpacing: "-0.5px", | |
| 98 | + }, | |
| 99 | + input: { | |
| 100 | + color: "gray.700", | |
| 101 | + }, | |
| 102 | + html: { | |
| 103 | + fontFamily: "Inter,sans-serif", | |
| 104 | + }, | |
| 105 | + }), | |
| 106 | + }, | |
| 107 | +}; |
+25-0Client/src/theme/theme.js
| @@ -0,0 +1,25 @@ | ||
| 1 | +import { extendTheme } from "@chakra-ui/react"; | |
| 2 | +import { CardComponent } from "./additions/card/card"; | |
| 3 | +import { buttonStyles } from "./components/button"; | |
| 4 | +import { badgeStyles } from "./components/badge"; | |
| 5 | +import { inputStyles } from "./components/input"; | |
| 6 | +import { progressStyles } from "./components/progress"; | |
| 7 | +import { sliderStyles } from "./components/slider"; | |
| 8 | +import { textareaStyles } from "./components/textarea"; | |
| 9 | +import { switchStyles } from "./components/switch"; | |
| 10 | +import { linkStyles } from "./components/link"; | |
| 11 | +import { breakpoints } from "./foundations/breakpoints"; | |
| 12 | +import { globalStyles } from "./styles"; | |
| 13 | +export default extendTheme( | |
| 14 | + { breakpoints }, // Breakpoints | |
| 15 | + globalStyles, | |
| 16 | + badgeStyles, // badge styles | |
| 17 | + buttonStyles, // button styles | |
| 18 | + linkStyles, // link styles | |
| 19 | + progressStyles, // progress styles | |
| 20 | + sliderStyles, // slider styles | |
| 21 | + inputStyles, // input styles | |
| 22 | + textareaStyles, // textarea styles | |
| 23 | + switchStyles, // switch styles | |
| 24 | + CardComponent // card component | |
| 25 | +); |
+99-0Client/src/utils/addDocumentModal.js
| @@ -0,0 +1,99 @@ | ||
| 1 | +import { Box, Button, Modal, ModalBody, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalOverlay, FormLabel, Input, Text } from '@chakra-ui/react'; | |
| 2 | +import React, { useState } from 'react' | |
| 3 | +import { useFormik } from 'formik'; | |
| 4 | +import Spinner from 'components/spinner/Spinner'; | |
| 5 | +import { postApi } from 'services/api'; | |
| 6 | +import { documentSchema } from 'schema'; | |
| 7 | +import FormDataUse, { commonUtils } from './formDataUse'; | |
| 8 | +import Upload from '../views/admin/document/component/Upload' | |
| 9 | + | |
| 10 | +const AddDocumentModal = ({ setAddDocument, addDocument, linkId, from, setAction, fetchData }) => { | |
| 11 | + const [isLoding, setIsLoding] = useState(false) | |
| 12 | + const user = JSON.parse(localStorage.getItem("user")) | |
| 13 | + | |
| 14 | + const initialValues = { | |
| 15 | + folderName: '', | |
| 16 | + files: '', | |
| 17 | + filename: '', | |
| 18 | + linkLead: from === 'lead' && linkId, | |
| 19 | + linkContact: from === 'contact' && linkId, | |
| 20 | + createBy: user._id | |
| 21 | + }; | |
| 22 | + | |
| 23 | + const formik = useFormik({ | |
| 24 | + initialValues: initialValues, | |
| 25 | + validationSchema: documentSchema, | |
| 26 | + onSubmit: (values, { resetForm }) => { | |
| 27 | + addData(); | |
| 28 | + }, | |
| 29 | + }); | |
| 30 | + const { errors, touched, values, handleBlur, handleChange, handleSubmit, setFieldValue, resetForm } = formik | |
| 31 | + | |
| 32 | + | |
| 33 | + const addData = async () => { | |
| 34 | + try { | |
| 35 | + setIsLoding(true) | |
| 36 | + const payload = commonUtils.formData(values) | |
| 37 | + | |
| 38 | + let response = await postApi(from === 'lead' ? 'api/document/addDocumentLead' : 'api/document/addDocumentContact', payload); | |
| 39 | + if (response && response.status === 200) { | |
| 40 | + formik.resetForm(); | |
| 41 | + // setAction((pre) => !pre) | |
| 42 | + fetchData(2) | |
| 43 | + setAddDocument(false) | |
| 44 | + } | |
| 45 | + } catch (e) { | |
| 46 | + console.log(e); | |
| 47 | + } | |
| 48 | + finally { | |
| 49 | + setIsLoding(false) | |
| 50 | + } | |
| 51 | + }; | |
| 52 | + return ( | |
| 53 | + <Modal onClose={() => setAddDocument(false)} isOpen={addDocument} isCentered> | |
| 54 | + <ModalOverlay /> | |
| 55 | + <ModalContent maxWidth={"2xl"}> | |
| 56 | + <ModalHeader></ModalHeader> | |
| 57 | + <ModalCloseButton /> | |
| 58 | + <ModalBody> | |
| 59 | + <Box > | |
| 60 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='8px'> | |
| 61 | + Folder Name<Text color={"red"}>*</Text> | |
| 62 | + </FormLabel> | |
| 63 | + <Input | |
| 64 | + fontSize='sm' | |
| 65 | + onChange={handleChange} | |
| 66 | + value={values.folderName} | |
| 67 | + name="folderName" | |
| 68 | + placeholder='Enter Folder Name' | |
| 69 | + fontWeight='500' | |
| 70 | + borderColor={errors?.folderName && touched?.folderName ? "red.300" : null} | |
| 71 | + /> | |
| 72 | + <Text mb='10px' color={'red'}> {errors.folderName && touched.folderName && errors.folderName}</Text> | |
| 73 | + </Box> | |
| 74 | + <Box > | |
| 75 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='8px'> | |
| 76 | + File Name | |
| 77 | + </FormLabel> | |
| 78 | + <Input | |
| 79 | + fontSize='sm' | |
| 80 | + onChange={handleChange} | |
| 81 | + value={values.filename} | |
| 82 | + name="filename" | |
| 83 | + placeholder='Enter File Name' | |
| 84 | + fontWeight='500' | |
| 85 | + borderColor={errors?.filename && touched?.filename ? "red.300" : null} | |
| 86 | + /> | |
| 87 | + <Text mb='10px' color={'red'}> {errors.filename && touched.filename && errors.filename}</Text> | |
| 88 | + </Box> | |
| 89 | + <Upload count={values.files.length} onFileSelect={(file) => setFieldValue('files', file)} /> </ModalBody> | |
| 90 | + <ModalFooter> | |
| 91 | + <Button size="sm" variant='brand' mr={2} onClick={handleSubmit} disabled={isLoding ? true : false} >{isLoding ? <Spinner /> : 'Save'}</Button> | |
| 92 | + <Button size="sm" variant="outline" colorScheme="red" onClick={() => { setAddDocument(false); resetForm() }} >Close</Button> | |
| 93 | + </ModalFooter> | |
| 94 | + </ModalContent> | |
| 95 | + </Modal> | |
| 96 | + ) | |
| 97 | +} | |
| 98 | + | |
| 99 | +export default AddDocumentModal |
+274-0Client/src/utils/customForm.js
| @@ -0,0 +1,274 @@ | ||
| 1 | +import React from 'react'; | |
| 2 | +import { Grid, GridItem, Heading, FormLabel, Input, Text, InputLeftElement, InputGroup, Checkbox, Select, RadioGroup, Radio, HStack, Slider, SliderTrack, SliderFilledTrack, SliderThumb, Box } from '@chakra-ui/react'; // Assuming you are using Chakra UI | |
| 3 | +import { HSeparator } from 'components/separator/Separator'; | |
| 4 | +import { EmailIcon, PhoneIcon, StarIcon } from '@chakra-ui/icons'; | |
| 5 | + | |
| 6 | +const CustomForm = ({ moduleData, values, handleChange, handleBlur, errors, touched, setFieldValue }) => { | |
| 7 | + return ( | |
| 8 | + <Grid templateColumns="repeat(12, 1fr)" gap={3}> | |
| 9 | + {moduleData?.headings?.length > 0 ? | |
| 10 | + <> | |
| 11 | + { | |
| 12 | + moduleData?.headings?.map((item, ind) => ( | |
| 13 | + <> | |
| 14 | + <GridItem colSpan={{ base: 12 }} key={ind}> | |
| 15 | + {ind !== 0 && <HSeparator />} | |
| 16 | + <Heading as="h1" size="md" mt='10px'> | |
| 17 | + {ind + 1}. {item?.heading} | |
| 18 | + </Heading> | |
| 19 | + </GridItem> | |
| 20 | + { | |
| 21 | + moduleData?.fields?.filter((itm) => itm?.belongsTo === item?._id)?.map((field, index) => ( | |
| 22 | + <GridItem colSpan={{ base: 12, sm: 6 }} key={index}> | |
| 23 | + {field.type === 'check' ? '' : <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='8px' htmlFor={field.name}>{field.label} {field.validation && field.validation.find((validation) => validation.require) && ( | |
| 24 | + <span style={{ color: 'red' }}>*</span> | |
| 25 | + )}</FormLabel>} | |
| 26 | + {field.type === 'range' ? | |
| 27 | + <> | |
| 28 | + {values.leadRating || 0} | |
| 29 | + <Slider ml={2} aria-label='slider-ex-1' colorScheme='yellow' value={values.leadRating} min={field?.validation[1]?.value} max={field?.validation[2]?.value} step={.1} defaultValue={0} onChange={(value) => setFieldValue(field.name, value)} > | |
| 30 | + <SliderTrack> | |
| 31 | + <SliderFilledTrack /> | |
| 32 | + </SliderTrack> | |
| 33 | + <SliderThumb boxSize={6}> | |
| 34 | + <Box color='yellow.300' as={StarIcon} /> | |
| 35 | + </SliderThumb> | |
| 36 | + </Slider> | |
| 37 | + </> | |
| 38 | + : field.type === 'radio' ? | |
| 39 | + <RadioGroup | |
| 40 | + name={field.name} | |
| 41 | + value={values[field.name]} | |
| 42 | + onChange={(value) => setFieldValue(field.name, value)} | |
| 43 | + > | |
| 44 | + <HStack spacing="24px"> | |
| 45 | + {field.options.map(option => ( | |
| 46 | + <Radio key={option._id} value={option.value}> | |
| 47 | + {option.name} | |
| 48 | + </Radio> | |
| 49 | + ))} | |
| 50 | + </HStack> | |
| 51 | + </RadioGroup> : | |
| 52 | + field.type === 'select' ? <Select | |
| 53 | + fontSize='sm' | |
| 54 | + id={field.name} | |
| 55 | + name={field.name} | |
| 56 | + onChange={handleChange} | |
| 57 | + onBlur={handleBlur} | |
| 58 | + value={values[field.name]} | |
| 59 | + fontWeight='500' | |
| 60 | + borderColor={errors?.[field?.name] && touched?.[field?.name] ? "red.300" : null} | |
| 61 | + > | |
| 62 | + {field.options.map(option => ( | |
| 63 | + <option key={option._id} value={option.value}> | |
| 64 | + {option.name} | |
| 65 | + </option> | |
| 66 | + ))} | |
| 67 | + </Select> : field.type === 'check' ? <Checkbox | |
| 68 | + isChecked={values[field.name]} | |
| 69 | + onChange={() => setFieldValue(field.name, !values[field.name])} | |
| 70 | + > | |
| 71 | + {field.label} | |
| 72 | + </Checkbox> : | |
| 73 | + <> | |
| 74 | + <InputGroup> | |
| 75 | + {field.type === 'tel' ? <InputLeftElement | |
| 76 | + pointerEvents="none" | |
| 77 | + children={<PhoneIcon color="gray.300" borderRadius="16px" />} | |
| 78 | + /> : field.type === 'email' && <InputLeftElement | |
| 79 | + pointerEvents="none" | |
| 80 | + children={<EmailIcon color="gray.300" borderRadius="16px" />} | |
| 81 | + /> | |
| 82 | + } | |
| 83 | + <Input | |
| 84 | + fontSize='sm' | |
| 85 | + type={field.type} | |
| 86 | + id={field.name} | |
| 87 | + name={field.name} | |
| 88 | + onChange={handleChange} | |
| 89 | + onBlur={handleBlur} | |
| 90 | + value={values[field.name]} | |
| 91 | + fontWeight='500' | |
| 92 | + placeholder={`Enter ${field.label}`} | |
| 93 | + borderColor={errors?.[field?.name] && touched?.[field?.name] ? "red.300" : null} | |
| 94 | + /> | |
| 95 | + </InputGroup> | |
| 96 | + </>} | |
| 97 | + {touched[field?.name] && errors?.[field?.name] ? ( | |
| 98 | + <Text mb='10px' color={'red'} fontSize='sm' textTransform={'capitalize'}> {errors?.[field?.name]}</Text> | |
| 99 | + ) : null} | |
| 100 | + </GridItem> | |
| 101 | + )) | |
| 102 | + } | |
| 103 | + </> | |
| 104 | + )) | |
| 105 | + } | |
| 106 | + <> | |
| 107 | + {moduleData?.fields?.filter((itm) => !itm?.belongsTo)?.map((field, i) => ( | |
| 108 | + <GridItem colSpan={{ base: 12, sm: 6 }} key={i}> | |
| 109 | + {field.type === 'check' ? '' : <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='8px' htmlFor={field.name}>{field.label} {field.validation && field.validation.find((validation) => validation.require) && ( | |
| 110 | + <span style={{ color: 'red' }}>*</span> | |
| 111 | + )}</FormLabel>} | |
| 112 | + {field.type === 'range' ? | |
| 113 | + <> | |
| 114 | + {values.leadRating || 0} | |
| 115 | + <Slider ml={2} aria-label='slider-ex-1' colorScheme='yellow' min={field?.validation[1]?.value} max={field?.validation[2]?.value} step={.1} defaultValue={0} onChange={(value) => setFieldValue(field.name, value)} > | |
| 116 | + <SliderTrack> | |
| 117 | + <SliderFilledTrack /> | |
| 118 | + </SliderTrack> | |
| 119 | + <SliderThumb boxSize={6}> | |
| 120 | + <Box color='yellow.300' as={StarIcon} /> | |
| 121 | + </SliderThumb> | |
| 122 | + </Slider> | |
| 123 | + </> | |
| 124 | + : field.type === 'radio' ? | |
| 125 | + <RadioGroup | |
| 126 | + name={field.name} | |
| 127 | + value={values[field.name]} | |
| 128 | + onChange={(value) => setFieldValue(field.name, value)} | |
| 129 | + > | |
| 130 | + <HStack spacing="24px"> | |
| 131 | + {field.options.map(option => ( | |
| 132 | + <Radio key={option._id} value={option.value}> | |
| 133 | + {option.name} | |
| 134 | + </Radio> | |
| 135 | + ))} | |
| 136 | + </HStack> | |
| 137 | + </RadioGroup> : | |
| 138 | + field.type === 'select' ? <Select | |
| 139 | + fontSize='sm' | |
| 140 | + id={field.name} | |
| 141 | + name={field.name} | |
| 142 | + onChange={handleChange} | |
| 143 | + onBlur={handleBlur} | |
| 144 | + value={values[field.name]} | |
| 145 | + fontWeight='500' | |
| 146 | + borderColor={errors?.[field?.name] && touched?.[field?.name] ? "red.300" : null} | |
| 147 | + > | |
| 148 | + <option value="">Select {field.label}</option> | |
| 149 | + {field.options.map(option => ( | |
| 150 | + <option key={option._id} value={option.value}> | |
| 151 | + {option.name} | |
| 152 | + </option> | |
| 153 | + ))} | |
| 154 | + </Select> : field.type === 'check' ? <Checkbox | |
| 155 | + isChecked={values[field.name]} | |
| 156 | + onChange={() => setFieldValue(field.name, !values[field.name])} | |
| 157 | + > | |
| 158 | + {field.label} | |
| 159 | + </Checkbox> : | |
| 160 | + <> | |
| 161 | + <InputGroup> | |
| 162 | + {field.type === 'tel' && <InputLeftElement | |
| 163 | + pointerEvents="none" | |
| 164 | + children={<PhoneIcon color="gray.300" borderRadius="16px" />} | |
| 165 | + />} | |
| 166 | + <Input | |
| 167 | + fontSize='sm' | |
| 168 | + type={field.type} | |
| 169 | + id={field.name} | |
| 170 | + name={field.name} | |
| 171 | + onChange={handleChange} | |
| 172 | + onBlur={handleBlur} | |
| 173 | + value={values[field.name]} | |
| 174 | + fontWeight='500' | |
| 175 | + placeholder={`Enter ${field.label}`} | |
| 176 | + borderColor={errors?.[field?.name] && touched?.[field?.name] ? "red.300" : null} | |
| 177 | + /> | |
| 178 | + </InputGroup> | |
| 179 | + </>} | |
| 180 | + {touched[field?.name] && errors?.[field?.name] ? ( | |
| 181 | + <Text mb='10px' fontSize='sm' color={'red'}> {errors?.[field?.name]}</Text> | |
| 182 | + ) : null} | |
| 183 | + </GridItem> | |
| 184 | + ))} | |
| 185 | + </> | |
| 186 | + </> | |
| 187 | + : | |
| 188 | + <> | |
| 189 | + {moduleData?.fields?.filter((itm) => !itm?.belongsTo)?.map((field, index) => ( | |
| 190 | + <GridItem colSpan={{ base: 12, sm: 6 }} key={index}> | |
| 191 | + {field.type === 'check' ? '' : <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='8px' htmlFor={field.name}>{field.label} {field.validation && field.validation.find((validation) => validation.require) && ( | |
| 192 | + <span style={{ color: 'red' }}>*</span> | |
| 193 | + )}</FormLabel>} | |
| 194 | + {field.type === 'range' ? | |
| 195 | + <> | |
| 196 | + {values.leadRating || 0} | |
| 197 | + <Slider ml={2} aria-label='slider-ex-1' colorScheme='yellow' min={field?.validation[1]?.value} max={field?.validation[2]?.value} step={.1} defaultValue={0} onChange={(value) => setFieldValue(field.name, value)} > | |
| 198 | + <SliderTrack> | |
| 199 | + <SliderFilledTrack /> | |
| 200 | + </SliderTrack> | |
| 201 | + <SliderThumb boxSize={6}> | |
| 202 | + <Box color='yellow.300' as={StarIcon} /> | |
| 203 | + </SliderThumb> | |
| 204 | + </Slider> | |
| 205 | + </> | |
| 206 | + : field.type === 'radio' ? | |
| 207 | + <RadioGroup | |
| 208 | + name={field.name} | |
| 209 | + value={values[field.name]} | |
| 210 | + onChange={(value) => setFieldValue(field.name, value)} | |
| 211 | + > | |
| 212 | + <HStack spacing="24px"> | |
| 213 | + {field.options.map(option => ( | |
| 214 | + <Radio key={option._id} value={option.value}> | |
| 215 | + {option.name} | |
| 216 | + </Radio> | |
| 217 | + ))} | |
| 218 | + </HStack> | |
| 219 | + </RadioGroup> : | |
| 220 | + field.type === 'select' ? <Select | |
| 221 | + fontSize='sm' | |
| 222 | + id={field.name} | |
| 223 | + name={field.name} | |
| 224 | + onChange={handleChange} | |
| 225 | + onBlur={handleBlur} | |
| 226 | + value={values[field.name]} | |
| 227 | + fontWeight='500' | |
| 228 | + borderColor={errors?.[field?.name] && touched?.[field?.name] ? "red.300" : null} | |
| 229 | + > | |
| 230 | + <option value="">Select {field.label}</option> | |
| 231 | + {field.options.map(option => ( | |
| 232 | + <option key={option._id} value={option.value}> | |
| 233 | + {option.name} | |
| 234 | + </option> | |
| 235 | + ))} | |
| 236 | + </Select> : field.type === 'check' ? <Checkbox | |
| 237 | + isChecked={values[field.name]} | |
| 238 | + onChange={() => setFieldValue(field.name, !values[field.name])} | |
| 239 | + > | |
| 240 | + {field.label} | |
| 241 | + </Checkbox> : | |
| 242 | + <> | |
| 243 | + <InputGroup> | |
| 244 | + {field.type === 'tel' && <InputLeftElement | |
| 245 | + pointerEvents="none" | |
| 246 | + children={<PhoneIcon color="gray.300" borderRadius="16px" />} | |
| 247 | + />} | |
| 248 | + <Input | |
| 249 | + fontSize='sm' | |
| 250 | + type={field.type} | |
| 251 | + id={field.name} | |
| 252 | + name={field.name} | |
| 253 | + onChange={handleChange} | |
| 254 | + onBlur={handleBlur} | |
| 255 | + value={values[field.name]} | |
| 256 | + fontWeight='500' | |
| 257 | + placeholder={`Enter ${field.label}`} | |
| 258 | + borderColor={errors?.[field?.name] && touched?.[field?.name] ? "red.300" : null} | |
| 259 | + /> | |
| 260 | + </InputGroup> | |
| 261 | + </>} | |
| 262 | + {touched[field?.name] && errors?.[field?.name] ? ( | |
| 263 | + <Text mb='10px' fontSize='sm' color={'red'}> {errors?.[field?.name]}</Text> | |
| 264 | + ) : null} | |
| 265 | + </GridItem> | |
| 266 | + ))} | |
| 267 | + </> | |
| 268 | + } | |
| 269 | + | |
| 270 | + </Grid> | |
| 271 | + ); | |
| 272 | +}; | |
| 273 | + | |
| 274 | +export default CustomForm; |
+566-0Client/src/utils/customView.js
| @@ -0,0 +1,566 @@ | ||
| 1 | +// import React from 'react' | |
| 2 | +// import { Grid, GridItem, Box, Heading, Text } from '@chakra-ui/react' | |
| 3 | +// import Card from 'components/card/Card' | |
| 4 | +// import moment from 'moment' | |
| 5 | +// import { HSeparator } from "components/separator/Separator"; | |
| 6 | + | |
| 7 | +// const CustomView = ({ data, toCamelCase, fieldData }) => { | |
| 8 | + | |
| 9 | +// const headingLength = data?.headings?.length % 3 | |
| 10 | +// const headingLengthMd = data?.headings?.length % 2 | |
| 11 | +// const lastLength = data?.headings.length - headingLength | |
| 12 | +// const lastLengthMd = data?.headings.length - headingLengthMd | |
| 13 | + | |
| 14 | +// // Define a function to determine the colSpan value | |
| 15 | +// const getColSpanLg = (ind, lastLength, headingLength) => { | |
| 16 | +// if (ind < lastLength) { | |
| 17 | +// return (ind + 1) > lastLength ? 6 : 4; | |
| 18 | +// } else { | |
| 19 | +// return headingLength === 1 ? 12 : ((ind + 1) > lastLength ? 6 : 4); | |
| 20 | +// } | |
| 21 | +// }; | |
| 22 | +// const getColSpanMd = (ind, lastLengthMd, headingLengthMd) => { | |
| 23 | +// if (ind < lastLengthMd) { | |
| 24 | +// return (ind + 1) > lastLengthMd ? 4 : 6; | |
| 25 | +// } else { | |
| 26 | +// return headingLengthMd === 1 ? 12 : ((ind + 1) > lastLengthMd ? 4 : 6); | |
| 27 | +// } | |
| 28 | +// }; | |
| 29 | +// return ( | |
| 30 | +// <Grid templateColumns="repeat(12, 1fr)" gap={3}> | |
| 31 | +// {data?.headings?.length > 0 ? <> | |
| 32 | +// {data?.headings?.map((item, ind) => ( | |
| 33 | +// <> | |
| 34 | +// <GridItem colSpan={{ | |
| 35 | +// base: 12, md: getColSpanMd(ind, lastLengthMd, headingLengthMd), lg: getColSpanLg(ind, lastLength, headingLength) | |
| 36 | +// }} key={ind}> | |
| 37 | +// <Card> | |
| 38 | +// <Grid templateColumns="repeat(12, 1fr)" gap={3}> | |
| 39 | +// <GridItem colSpan={12}> | |
| 40 | +// <Heading as="h1" size="md" mb='10px'> | |
| 41 | +// {ind + 1}. {item?.heading} | |
| 42 | +// </Heading> | |
| 43 | +// <HSeparator /> | |
| 44 | +// </GridItem> | |
| 45 | +// { | |
| 46 | +// data?.fields?.filter((itm) => itm?.belongsTo === item?._id)?.map((field) => ( | |
| 47 | +// <GridItem colSpan={{ base: 12, md: 6 }} key={field?.name}> | |
| 48 | +// <Text color={'blackAlpha.900'} fontSize="sm" fontWeight="bold"> {field?.label}</Text> | |
| 49 | +// <Text color={'blackAlpha.900'} fontSize="sm" > {(fieldData && (fieldData[field?.name] !== undefined ? fieldData[field?.name] : "N/A")) || "N/A"}</Text> | |
| 50 | +// </GridItem> | |
| 51 | +// )) | |
| 52 | +// } | |
| 53 | +// </Grid> | |
| 54 | +// </Card> | |
| 55 | +// </GridItem> | |
| 56 | +// </>) | |
| 57 | +// )} | |
| 58 | +// </> : | |
| 59 | +// <GridItem colSpan={{ | |
| 60 | +// base: 12, | |
| 61 | +// }}> | |
| 62 | + | |
| 63 | +// <Card> | |
| 64 | +// <Grid templateColumns="repeat(12, 1fr)" gap={3}> | |
| 65 | +// <GridItem colSpan={12}> | |
| 66 | +// <Heading as="h1" size="md" mb='10px'> | |
| 67 | +// {data?.moduleName} view page | |
| 68 | +// </Heading> | |
| 69 | +// <HSeparator /> | |
| 70 | +// </GridItem> | |
| 71 | +// { | |
| 72 | +// data?.fields?.map((field) => ( | |
| 73 | +// <GridItem colSpan={{ base: 12, md: 6 }} key={field?.name}> | |
| 74 | +// <Text color={'blackAlpha.900'} fontSize="sm" fontWeight="bold"> {field?.label}</Text> | |
| 75 | +// <Text color={'blackAlpha.900'} fontSize="sm" > {(fieldData && (fieldData[field?.name] !== undefined ? fieldData[field?.name] : "N/A")) || "N/A"}</Text> | |
| 76 | +// </GridItem> | |
| 77 | +// )) | |
| 78 | +// } | |
| 79 | +// </Grid> | |
| 80 | +// </Card> | |
| 81 | +// </GridItem> | |
| 82 | +// } | |
| 83 | +// </Grid> | |
| 84 | +// ) | |
| 85 | +// } | |
| 86 | + | |
| 87 | +// export default CustomView | |
| 88 | +// import React, { useState } from 'react' | |
| 89 | +// import { Grid, GridItem, Box, Heading, Text, Input } from '@chakra-ui/react' | |
| 90 | +// import Card from 'components/card/Card' | |
| 91 | +// import moment from 'moment' | |
| 92 | +// import { HSeparator } from "components/separator/Separator"; | |
| 93 | +// import { putApi } from 'services/api'; | |
| 94 | +// import { useParams } from 'react-router-dom'; | |
| 95 | +// import { useFormik } from 'formik'; | |
| 96 | +// import * as yup from 'yup' | |
| 97 | +// import { generateValidationSchema } from 'utils'; | |
| 98 | + | |
| 99 | +// const CustomView = ({ data, toCamelCase, fieldData, moduleId, fetchData }) => { | |
| 100 | +// const [editableField, setEditableField] = useState(null); | |
| 101 | +// const [editableValue, setEditableValue] = useState(''); | |
| 102 | +// const param = useParams() | |
| 103 | + | |
| 104 | +// const handleDoubleClick = (fieldName, value) => { | |
| 105 | +// setEditableField(fieldName); | |
| 106 | +// setEditableValue(value); | |
| 107 | +// }; | |
| 108 | +// const handleChange = (event) => { | |
| 109 | +// setEditableValue(event.target.value); | |
| 110 | +// }; | |
| 111 | + | |
| 112 | +// const formik = useFormik({ | |
| 113 | +// initialValues: fieldData, | |
| 114 | +// enableReinitialize: true, | |
| 115 | +// validationSchema: yup.object().shape(generateValidationSchema(data?.fields)), | |
| 116 | +// onSubmit: (values, { resetForm }) => { | |
| 117 | +// EditData(); | |
| 118 | +// }, | |
| 119 | +// }); | |
| 120 | + | |
| 121 | + | |
| 122 | +// const EditData = async () => { | |
| 123 | +// let payload = { | |
| 124 | +// ...formik.values, | |
| 125 | +// moduleId: moduleId | |
| 126 | +// } | |
| 127 | +// payload[`${editableField}`] = editableValue | |
| 128 | +// try { | |
| 129 | +// // setIsLoding(true) | |
| 130 | +// let response = await putApi(`api/form/edit/${param.id}`, payload) | |
| 131 | +// if (response.status === 200) { | |
| 132 | +// setEditableField(null); | |
| 133 | +// fetchData() | |
| 134 | +// } | |
| 135 | +// } catch (e) { | |
| 136 | +// console.log(e); | |
| 137 | +// } | |
| 138 | +// finally { | |
| 139 | +// // setIsLoding(false) | |
| 140 | +// } | |
| 141 | +// }; | |
| 142 | + | |
| 143 | +// const handleBlur = (e) => { | |
| 144 | +// formik.handleSubmit() | |
| 145 | +// }; | |
| 146 | + | |
| 147 | +// const headingLength = data?.headings?.length % 3; | |
| 148 | +// const headingLengthMd = data?.headings?.length % 2; | |
| 149 | +// const lastLength = data?.headings.length - headingLength; | |
| 150 | +// const lastLengthMd = data?.headings.length - headingLengthMd; | |
| 151 | + | |
| 152 | +// // Define a function to determine the colSpan value | |
| 153 | +// const getColSpanLg = (ind, lastLength, headingLength) => { | |
| 154 | +// if (ind < lastLength) { | |
| 155 | +// return (ind + 1) > lastLength ? 6 : 4; | |
| 156 | +// } else { | |
| 157 | +// return headingLength === 1 ? 12 : ((ind + 1) > lastLength ? 6 : 4); | |
| 158 | +// } | |
| 159 | +// }; | |
| 160 | +// const getColSpanMd = (ind, lastLengthMd, headingLengthMd) => { | |
| 161 | +// if (ind < lastLengthMd) { | |
| 162 | +// return (ind + 1) > lastLengthMd ? 4 : 6; | |
| 163 | +// } else { | |
| 164 | +// return headingLengthMd === 1 ? 12 : ((ind + 1) > lastLengthMd ? 4 : 6); | |
| 165 | +// } | |
| 166 | +// }; | |
| 167 | + | |
| 168 | +// return ( | |
| 169 | +// <Grid templateColumns="repeat(12, 1fr)" gap={3}> | |
| 170 | +// {data?.headings?.length > 0 ? ( | |
| 171 | +// <> | |
| 172 | +// {data?.headings?.map((item, ind) => ( | |
| 173 | +// <GridItem colSpan={{ | |
| 174 | +// base: 12, md: getColSpanMd(ind, lastLengthMd, headingLengthMd), lg: getColSpanLg(ind, lastLength, headingLength) | |
| 175 | +// }} key={ind}> | |
| 176 | +// <Card> | |
| 177 | +// <Grid templateColumns="repeat(12, 1fr)" gap={3}> | |
| 178 | +// <GridItem colSpan={12}> | |
| 179 | +// <Heading as="h1" size="md" mb='10px'> | |
| 180 | +// {ind + 1}. {item?.heading} | |
| 181 | +// </Heading> | |
| 182 | +// <HSeparator /> | |
| 183 | +// </GridItem> | |
| 184 | +// { | |
| 185 | +// data?.fields?.filter((itm) => itm?.belongsTo === item?._id)?.map((field) => ( | |
| 186 | +// <GridItem colSpan={{ base: 12, md: 6 }} key={field?.name}> | |
| 187 | +// <Text color={'blackAlpha.900'} fontSize="sm" fontWeight="bold">{field?.label}</Text> | |
| 188 | +// {editableField === field?.name ? ( | |
| 189 | +// <Input | |
| 190 | +// value={editableValue} | |
| 191 | +// onChange={handleChange} | |
| 192 | +// onBlur={handleBlur} | |
| 193 | +// autoFocus | |
| 194 | +// borderColor={errors?.[field?.name] && touched?.[field?.name] ? "red.300" : null} | |
| 195 | +// /> | |
| 196 | +// ) : ( | |
| 197 | +// <Text | |
| 198 | +// color={'blackAlpha.900'} | |
| 199 | +// fontSize="sm" | |
| 200 | +// onDoubleClick={() => handleDoubleClick(field?.name, fieldData && (fieldData[field?.name] !== undefined ? fieldData[field?.name] : "N/A"))} | |
| 201 | +// > | |
| 202 | +// {(fieldData && (fieldData[field?.name] !== undefined ? fieldData[field?.name] : "N/A")) || "N/A"} | |
| 203 | +// </Text> | |
| 204 | +// )} | |
| 205 | +// </GridItem> | |
| 206 | +// )) | |
| 207 | +// } | |
| 208 | +// </Grid> | |
| 209 | +// </Card> | |
| 210 | +// </GridItem> | |
| 211 | +// ))} | |
| 212 | +// </> | |
| 213 | +// ) : ( | |
| 214 | +// <GridItem colSpan={{ base: 12 }}> | |
| 215 | +// <Card> | |
| 216 | +// <Grid templateColumns="repeat(12, 1fr)" gap={3}> | |
| 217 | +// <GridItem colSpan={12}> | |
| 218 | +// <Heading as="h1" size="md" mb='10px'> | |
| 219 | +// {data?.moduleName} view page | |
| 220 | +// </Heading> | |
| 221 | +// <HSeparator /> | |
| 222 | +// </GridItem> | |
| 223 | +// {data?.fields?.map((field) => ( | |
| 224 | +// <GridItem colSpan={{ base: 12, md: 6 }} key={field?.name}> | |
| 225 | +// <Text color={'blackAlpha.900'} fontSize="sm" fontWeight="bold">{field?.label}</Text> | |
| 226 | +// {editableField === field?.name ? ( | |
| 227 | +// <Input | |
| 228 | +// value={editableValue} | |
| 229 | +// onChange={handleChange} | |
| 230 | +// onBlur={handleBlur} | |
| 231 | +// autoFocus | |
| 232 | +// /> | |
| 233 | +// ) : ( | |
| 234 | +// <Text | |
| 235 | +// color={'blackAlpha.900'} | |
| 236 | +// fontSize="sm" | |
| 237 | +// onDoubleClick={() => handleDoubleClick(field?.name, fieldData && (fieldData[field?.name] !== undefined ? fieldData[field?.name] : "N/A"))} | |
| 238 | +// > | |
| 239 | +// {(fieldData && (fieldData[field?.name] !== undefined ? fieldData[field?.name] : "N/A")) || "N/A"} | |
| 240 | +// </Text> | |
| 241 | +// )} | |
| 242 | +// </GridItem> | |
| 243 | +// ))} | |
| 244 | +// </Grid> | |
| 245 | +// </Card> | |
| 246 | +// </GridItem> | |
| 247 | +// )} | |
| 248 | +// </Grid> | |
| 249 | +// ); | |
| 250 | +// }; | |
| 251 | + | |
| 252 | +// export default CustomView; | |
| 253 | + | |
| 254 | + | |
| 255 | +import React, { useState } from 'react'; | |
| 256 | +import { Grid, GridItem, Heading, Text, Input, FormControl, FormErrorMessage, Select } from '@chakra-ui/react'; | |
| 257 | +import Card from 'components/card/Card'; | |
| 258 | +import moment from 'moment'; | |
| 259 | +import { HSeparator } from "components/separator/Separator"; | |
| 260 | +import { putApi } from 'services/api'; | |
| 261 | +import { useParams } from 'react-router-dom'; | |
| 262 | +import { useFormik } from 'formik'; | |
| 263 | +import * as yup from 'yup'; | |
| 264 | +import { generateValidationSchema } from 'utils'; | |
| 265 | +import { toast } from 'react-toastify'; | |
| 266 | + | |
| 267 | +const CustomView = ({ data, toCamelCase, fieldData, moduleId, fetchData, editUrl, id }) => { | |
| 268 | + const param = useParams(); | |
| 269 | + const [editableField, setEditableField] = useState(null); | |
| 270 | + const [editableFieldName, setEditableFieldName] = useState(null); | |
| 271 | + | |
| 272 | + const formik = useFormik({ | |
| 273 | + initialValues: fieldData, | |
| 274 | + enableReinitialize: true, | |
| 275 | + validationSchema: yup.object().shape(generateValidationSchema(data?.fields)), | |
| 276 | + onSubmit: async (values) => { | |
| 277 | + let payload = { | |
| 278 | + ...values, | |
| 279 | + moduleId: moduleId | |
| 280 | + }; | |
| 281 | + try { | |
| 282 | + let response = await putApi(editUrl ? editUrl : `api/form/edit/${param.id}`, payload); | |
| 283 | + if (response.status === 200) { | |
| 284 | + setEditableField(null) | |
| 285 | + toast.success(`${editableFieldName} Update successfully`) | |
| 286 | + fetchData(); | |
| 287 | + } | |
| 288 | + } catch (e) { | |
| 289 | + console.log(e); | |
| 290 | + toast.success(`server error`) | |
| 291 | + } | |
| 292 | + }, | |
| 293 | + }); | |
| 294 | + | |
| 295 | + const handleDoubleClick = (fieldName, value, lable) => { | |
| 296 | + formik.setFieldValue(fieldName, value); | |
| 297 | + setEditableField(fieldName) | |
| 298 | + setEditableFieldName(lable) | |
| 299 | + }; | |
| 300 | + | |
| 301 | + const handleBlur = (e) => { | |
| 302 | + formik.handleSubmit(); | |
| 303 | + }; | |
| 304 | + | |
| 305 | + const headingLength = data?.headings?.length % 3; | |
| 306 | + const headingLengthMd = data?.headings?.length % 2; | |
| 307 | + const lastLength = data?.headings.length - headingLength; | |
| 308 | + const lastLengthMd = data?.headings.length - headingLengthMd; | |
| 309 | + | |
| 310 | + const getColSpanLg = (ind, lastLength, headingLength) => { | |
| 311 | + if (ind < lastLength) { | |
| 312 | + return (ind + 1) > lastLength ? 6 : 4; | |
| 313 | + } else { | |
| 314 | + return headingLength === 1 ? 12 : ((ind + 1) > lastLength ? 6 : 4); | |
| 315 | + } | |
| 316 | + }; | |
| 317 | + | |
| 318 | + const getColSpanMd = (ind, lastLengthMd, headingLengthMd) => { | |
| 319 | + if (ind < lastLengthMd) { | |
| 320 | + return (ind + 1) > lastLengthMd ? 4 : 6; | |
| 321 | + } else { | |
| 322 | + return headingLengthMd === 1 ? 12 : ((ind + 1) > lastLengthMd ? 4 : 6); | |
| 323 | + } | |
| 324 | + }; | |
| 325 | + | |
| 326 | + return ( | |
| 327 | + <Grid templateColumns="repeat(12, 1fr)" gap={3} id={id}> | |
| 328 | + {data?.headings?.length > 0 ? ( | |
| 329 | + <> | |
| 330 | + {data?.headings?.map((item, ind) => ( | |
| 331 | + <GridItem colSpan={{ | |
| 332 | + base: 12, md: getColSpanMd(ind, lastLengthMd, headingLengthMd), lg: getColSpanLg(ind, lastLength, headingLength) | |
| 333 | + }} key={ind}> | |
| 334 | + <Card> | |
| 335 | + <Grid templateColumns="repeat(12, 1fr)" gap={3}> | |
| 336 | + <GridItem colSpan={12}> | |
| 337 | + <Heading as="h1" size="md" mb='10px'> | |
| 338 | + {ind + 1}. {item?.heading} | |
| 339 | + </Heading> | |
| 340 | + <HSeparator /> | |
| 341 | + </GridItem> | |
| 342 | + { | |
| 343 | + data?.fields?.filter((itm) => itm?.belongsTo === item?._id)?.map((field) => ( | |
| 344 | + <GridItem colSpan={{ base: 12, md: 6 }} key={field?.name}> | |
| 345 | + <FormControl isInvalid={formik.errors[field?.name] && formik.touched[field?.name]}> | |
| 346 | + <Text color={'blackAlpha.900'} fontSize="sm" fontWeight="bold">{field?.label}</Text> | |
| 347 | + {editableField === field?.name || formik.errors[field?.name] ? ( | |
| 348 | + field?.type === "select" || field?.type === "radio" ? | |
| 349 | + <Select | |
| 350 | + fontSize='sm' | |
| 351 | + id={field?.name} | |
| 352 | + name={field?.name} | |
| 353 | + onChange={formik.handleChange} | |
| 354 | + onBlur={handleBlur} | |
| 355 | + value={formik.values[field?.name] || ''} | |
| 356 | + fontWeight='500' | |
| 357 | + borderColor={formik.errors[field?.name] && formik.touched[field?.name] ? "red.300" : null} | |
| 358 | + > | |
| 359 | + {field?.options?.map(option => ( | |
| 360 | + <option key={option._id} value={option.value}> | |
| 361 | + {option.name} | |
| 362 | + </option>))} | |
| 363 | + </Select> | |
| 364 | + : field?.type === "text" ? | |
| 365 | + <Input | |
| 366 | + value={formik.values[field?.name] || ''} | |
| 367 | + onChange={formik.handleChange} | |
| 368 | + onBlur={handleBlur} | |
| 369 | + autoFocus | |
| 370 | + borderColor={formik.errors[field?.name] && formik.touched[field?.name] ? "red.300" : null} | |
| 371 | + name={field?.name} | |
| 372 | + /> | |
| 373 | + : field?.type === "tel" ? | |
| 374 | + <Input | |
| 375 | + value={formik.values[field?.name] || ''} | |
| 376 | + onChange={formik.handleChange} | |
| 377 | + onBlur={handleBlur} | |
| 378 | + autoFocus | |
| 379 | + borderColor={formik.errors[field?.name] && formik.touched[field?.name] ? "red.300" : null} | |
| 380 | + name={field?.name} | |
| 381 | + /> | |
| 382 | + : field?.type === "email" ? | |
| 383 | + <Input | |
| 384 | + value={formik.values[field?.name] || ''} | |
| 385 | + onChange={formik.handleChange} | |
| 386 | + onBlur={handleBlur} | |
| 387 | + autoFocus | |
| 388 | + borderColor={formik.errors[field?.name] && formik.touched[field?.name] ? "red.300" : null} | |
| 389 | + name={field?.name} | |
| 390 | + /> : field?.type === "date" ? | |
| 391 | + <Input | |
| 392 | + value={formik.values[field?.name] || ''} | |
| 393 | + onChange={formik.handleChange} | |
| 394 | + onBlur={handleBlur} | |
| 395 | + autoFocus | |
| 396 | + type='date' | |
| 397 | + borderColor={formik.errors[field?.name] && formik.touched[field?.name] ? "red.300" : null} | |
| 398 | + name={field?.name} | |
| 399 | + /> : field?.type === "url" ? | |
| 400 | + <Input | |
| 401 | + value={formik.values[field?.name] || ''} | |
| 402 | + onChange={formik.handleChange} | |
| 403 | + onBlur={handleBlur} | |
| 404 | + autoFocus | |
| 405 | + type='url' | |
| 406 | + borderColor={formik.errors[field?.name] && formik.touched[field?.name] ? "red.300" : null} | |
| 407 | + name={field?.name} | |
| 408 | + /> | |
| 409 | + : field?.type === "range" ? | |
| 410 | + <Input | |
| 411 | + value={formik.values[field?.name] || ''} | |
| 412 | + onChange={formik.handleChange} | |
| 413 | + onBlur={handleBlur} | |
| 414 | + autoFocus | |
| 415 | + type='range' | |
| 416 | + borderColor={formik.errors[field?.name] && formik.touched[field?.name] ? "red.300" : null} | |
| 417 | + name={field?.name} | |
| 418 | + /> | |
| 419 | + : | |
| 420 | + <Input | |
| 421 | + value={formik.values[field?.name] || ''} | |
| 422 | + onChange={formik.handleChange} | |
| 423 | + onBlur={handleBlur} | |
| 424 | + autoFocus | |
| 425 | + borderColor={formik.errors[field?.name] && formik.touched[field?.name] ? "red.300" : null} | |
| 426 | + name={field?.name} | |
| 427 | + /> | |
| 428 | + | |
| 429 | + ) : ( | |
| 430 | + <Text | |
| 431 | + color={'blackAlpha.900'} | |
| 432 | + fontSize="sm" | |
| 433 | + onDoubleClick={() => handleDoubleClick(field?.name, fieldData && (fieldData[field?.name] !== undefined ? fieldData[field?.name] : "N/A"), field?.label)} | |
| 434 | + > | |
| 435 | + {(fieldData && (fieldData[field?.name] !== undefined ? fieldData[field?.name] : "N/A")) || "N/A"} | |
| 436 | + </Text> | |
| 437 | + )} | |
| 438 | + <FormErrorMessage>{formik.errors[field?.name]}</FormErrorMessage> | |
| 439 | + </FormControl> | |
| 440 | + </GridItem> | |
| 441 | + )) | |
| 442 | + } | |
| 443 | + </Grid> | |
| 444 | + </Card> | |
| 445 | + </GridItem> | |
| 446 | + ))} | |
| 447 | + </> | |
| 448 | + ) : ( | |
| 449 | + <GridItem colSpan={{ base: 12 }}> | |
| 450 | + <Card> | |
| 451 | + <Grid templateColumns="repeat(12, 1fr)" gap={3}> | |
| 452 | + <GridItem colSpan={12}> | |
| 453 | + <Heading as="h1" size="md" mb='10px'> | |
| 454 | + {data?.moduleName} view page | |
| 455 | + </Heading> | |
| 456 | + <HSeparator /> | |
| 457 | + </GridItem> | |
| 458 | + {data?.fields?.map((field) => ( | |
| 459 | + <GridItem colSpan={{ base: 12, md: 6 }} key={field?.name}> | |
| 460 | + <FormControl isInvalid={formik.errors[field?.name] && formik.touched[field?.name]}> | |
| 461 | + <Text color={'blackAlpha.900'} fontSize="sm" fontWeight="bold">{field?.label}</Text> | |
| 462 | + {editableField === field?.name || formik.errors[field?.name] ? ( | |
| 463 | + field?.type === "select" || field?.type === "radio" ? | |
| 464 | + <Select | |
| 465 | + fontSize='sm' | |
| 466 | + id={field?.name} | |
| 467 | + name={field?.name} | |
| 468 | + onChange={formik.handleChange} | |
| 469 | + onBlur={handleBlur} | |
| 470 | + value={formik.values[field?.name] || ''} | |
| 471 | + fontWeight='500' | |
| 472 | + borderColor={formik.errors[field?.name] && formik.touched[field?.name] ? "red.300" : null} | |
| 473 | + > | |
| 474 | + {field?.options?.map(option => ( | |
| 475 | + <option key={option._id} value={option.value}> | |
| 476 | + {option.name} | |
| 477 | + </option>))} | |
| 478 | + </Select> | |
| 479 | + : field?.type === "text" ? | |
| 480 | + <Input | |
| 481 | + value={formik.values[field?.name] || ''} | |
| 482 | + onChange={formik.handleChange} | |
| 483 | + onBlur={handleBlur} | |
| 484 | + autoFocus | |
| 485 | + borderColor={formik.errors[field?.name] && formik.touched[field?.name] ? "red.300" : null} | |
| 486 | + name={field?.name} | |
| 487 | + /> | |
| 488 | + : field?.type === "tel" ? | |
| 489 | + <Input | |
| 490 | + value={formik.values[field?.name] || ''} | |
| 491 | + onChange={formik.handleChange} | |
| 492 | + onBlur={handleBlur} | |
| 493 | + autoFocus | |
| 494 | + borderColor={formik.errors[field?.name] && formik.touched[field?.name] ? "red.300" : null} | |
| 495 | + name={field?.name} | |
| 496 | + /> | |
| 497 | + : field?.type === "email" ? | |
| 498 | + <Input | |
| 499 | + value={formik.values[field?.name] || ''} | |
| 500 | + onChange={formik.handleChange} | |
| 501 | + onBlur={handleBlur} | |
| 502 | + autoFocus | |
| 503 | + borderColor={formik.errors[field?.name] && formik.touched[field?.name] ? "red.300" : null} | |
| 504 | + name={field?.name} | |
| 505 | + /> : field?.type === "date" ? | |
| 506 | + <Input | |
| 507 | + value={formik.values[field?.name] || ''} | |
| 508 | + onChange={formik.handleChange} | |
| 509 | + onBlur={handleBlur} | |
| 510 | + autoFocus | |
| 511 | + type='date' | |
| 512 | + borderColor={formik.errors[field?.name] && formik.touched[field?.name] ? "red.300" : null} | |
| 513 | + name={field?.name} | |
| 514 | + /> : field?.type === "url" ? | |
| 515 | + <Input | |
| 516 | + value={formik.values[field?.name] || ''} | |
| 517 | + onChange={formik.handleChange} | |
| 518 | + onBlur={handleBlur} | |
| 519 | + autoFocus | |
| 520 | + type='url' | |
| 521 | + borderColor={formik.errors[field?.name] && formik.touched[field?.name] ? "red.300" : null} | |
| 522 | + name={field?.name} | |
| 523 | + /> | |
| 524 | + : field?.type === "range" ? | |
| 525 | + <Input | |
| 526 | + value={formik.values[field?.name] || ''} | |
| 527 | + onChange={formik.handleChange} | |
| 528 | + onBlur={handleBlur} | |
| 529 | + autoFocus | |
| 530 | + type='range' | |
| 531 | + borderColor={formik.errors[field?.name] && formik.touched[field?.name] ? "red.300" : null} | |
| 532 | + name={field?.name} | |
| 533 | + /> | |
| 534 | + : | |
| 535 | + <Input | |
| 536 | + value={formik.values[field?.name] || ''} | |
| 537 | + onChange={formik.handleChange} | |
| 538 | + onBlur={handleBlur} | |
| 539 | + autoFocus | |
| 540 | + borderColor={formik.errors[field?.name] && formik.touched[field?.name] ? "red.300" : null} | |
| 541 | + name={field?.name} | |
| 542 | + /> | |
| 543 | + | |
| 544 | + | |
| 545 | + ) : ( | |
| 546 | + <Text | |
| 547 | + color={'blackAlpha.900'} | |
| 548 | + fontSize="sm" | |
| 549 | + onDoubleClick={() => handleDoubleClick(field?.name, fieldData && (fieldData[field?.name] !== undefined ? fieldData[field?.name] : "N/A"), field?.label)} | |
| 550 | + > | |
| 551 | + {(fieldData && (fieldData[field?.name] !== undefined ? fieldData[field?.name] : "N/A")) || "N/A"} | |
| 552 | + </Text> | |
| 553 | + )} | |
| 554 | + <FormErrorMessage>{formik.errors[field?.name]}</FormErrorMessage> | |
| 555 | + </FormControl> | |
| 556 | + </GridItem> | |
| 557 | + ))} | |
| 558 | + </Grid> | |
| 559 | + </Card> | |
| 560 | + </GridItem> | |
| 561 | + )} | |
| 562 | + </Grid> | |
| 563 | + ); | |
| 564 | +}; | |
| 565 | + | |
| 566 | +export default CustomView; |
+43-0Client/src/utils/formDataUse.js
| @@ -0,0 +1,43 @@ | ||
| 1 | +import { useEffect } from 'react'; | |
| 2 | + | |
| 3 | +const FormDataUse = (values, formData) => { | |
| 4 | + | |
| 5 | + useEffect(() => { | |
| 6 | + // const appendFormData = (key) => { | |
| 7 | + // if (formData && values[key]) { | |
| 8 | + // formData.append(key, values[key]); | |
| 9 | + // } | |
| 10 | + // }; | |
| 11 | + | |
| 12 | + // // Append each value to formData | |
| 13 | + // appendFormData('folderName'); | |
| 14 | + // appendFormData('createBy'); | |
| 15 | + // appendFormData('filename'); | |
| 16 | + | |
| 17 | + Object.entries(values).forEach(([key, value]) => { | |
| 18 | + formData.append(key, value); | |
| 19 | + }); | |
| 20 | + | |
| 21 | + // Clean up function | |
| 22 | + return () => { | |
| 23 | + // Optionally, you can reset or clear the formData here if needed | |
| 24 | + }; | |
| 25 | + }, [values, formData]); | |
| 26 | +}; | |
| 27 | + | |
| 28 | +export default FormDataUse; | |
| 29 | + | |
| 30 | + | |
| 31 | +export const commonUtils = { | |
| 32 | + formData: (values) => { | |
| 33 | + const formData = new FormData(); | |
| 34 | + | |
| 35 | + Object.entries(values).forEach(([key, value]) => { | |
| 36 | + formData.append(key, value); | |
| 37 | + }); | |
| 38 | + values.files.forEach((file) => { | |
| 39 | + formData?.append('files', file); | |
| 40 | + }); | |
| 41 | + return formData; | |
| 42 | + } | |
| 43 | +} |
+129-0Client/src/utils/index.js
| @@ -0,0 +1,129 @@ | ||
| 1 | +import * as yup from 'yup' | |
| 2 | + | |
| 3 | +// export const generateValidationSchema = (fields) => { | |
| 4 | +// return fields.reduce((acc, field) => { | |
| 5 | +// acc[field.name] = field.validation.reduce((fieldAcc, rule) => { | |
| 6 | +// if (rule.require) { | |
| 7 | +// fieldAcc = fieldAcc.required(rule.message); | |
| 8 | +// } | |
| 9 | +// if (rule.min) { | |
| 10 | +// fieldAcc = fieldAcc.min(rule.value, rule.message); | |
| 11 | +// } | |
| 12 | +// if (rule.max) { | |
| 13 | +// fieldAcc = fieldAcc.max(rule.value, rule.message); | |
| 14 | +// } | |
| 15 | +// if (rule.match) { | |
| 16 | +// const data = new RegExp(rule.value) | |
| 17 | +// const fieldAcc1 = data.test(rule.value); | |
| 18 | + | |
| 19 | +// // fieldAcc = fieldAcc.matches(data, rule.message); | |
| 20 | +// if (!fieldAcc1) { | |
| 21 | +// // If the string doesn't match the regular expression, handle the error or validation failure | |
| 22 | +// console.error(rule.message); | |
| 23 | +// } | |
| 24 | +// } | |
| 25 | +// // if (rule.formikType) { | |
| 26 | +// // fieldAcc = yup.formikType() || yup.string() | |
| 27 | +// // } | |
| 28 | +// if (rule.formikType === 'date') { | |
| 29 | +// fieldAcc = fieldAcc.required(rule.message); | |
| 30 | +// } | |
| 31 | +// // if (rule.formikType === 'email') { | |
| 32 | +// // fieldAcc = fieldAcc.required(rule.message); | |
| 33 | +// // } | |
| 34 | +// return fieldAcc; | |
| 35 | +// }, yup.string()); | |
| 36 | + | |
| 37 | +// return acc; | |
| 38 | +// }, {}); | |
| 39 | +// }; | |
| 40 | + | |
| 41 | +export const generateValidationSchema = (fields) => { | |
| 42 | + return fields?.reduce((acc, field) => { | |
| 43 | + // let formikValObj = field?.validation?.find(obj => obj?.hasOwnProperty('formikType')); | |
| 44 | + | |
| 45 | + acc[field.name] = field.validation.reduce((fieldAcc, rule) => { | |
| 46 | + if (rule.require) { | |
| 47 | + fieldAcc = fieldAcc.required(rule.message); | |
| 48 | + } | |
| 49 | + if (rule.min) { | |
| 50 | + fieldAcc = fieldAcc.min(rule.value, rule.message); | |
| 51 | + } | |
| 52 | + if (rule.max) { | |
| 53 | + fieldAcc = fieldAcc.max(rule.value, rule.message); | |
| 54 | + } | |
| 55 | + if (rule.match) { | |
| 56 | + const regexPattern = rule?.value?.replace(/^\/|\/$/g, ''); // Remove leading and trailing slashes | |
| 57 | + const regex = new RegExp(regexPattern); | |
| 58 | + fieldAcc = fieldAcc.matches(regex, rule.message); | |
| 59 | + } | |
| 60 | + if (rule.formikType === 'date') { | |
| 61 | + fieldAcc = fieldAcc.required(rule.message); | |
| 62 | + } | |
| 63 | + // Add other formikType cases as needed | |
| 64 | + | |
| 65 | + return fieldAcc; | |
| 66 | + }, yup.string()); | |
| 67 | + // }, (formikValObj && formikValObj?.formikType) ? yup?.[formikValObj?.formikType]() : yup.string()); | |
| 68 | + | |
| 69 | + let fieldValidation; | |
| 70 | + let formikValidation = field?.validation?.find(obj => obj?.hasOwnProperty('formikType')); | |
| 71 | + let fieldFormikType = formikValidation?.formikType?.toLowerCase(); | |
| 72 | + | |
| 73 | + if (fieldFormikType === 'string') { | |
| 74 | + fieldValidation = yup.string() | |
| 75 | + } else if (fieldFormikType === 'email') { | |
| 76 | + fieldValidation = yup.string().email() | |
| 77 | + } else if (fieldFormikType === 'date') { | |
| 78 | + fieldValidation = yup.date() | |
| 79 | + } else if (fieldFormikType === 'number') { | |
| 80 | + fieldValidation = yup.number() | |
| 81 | + } else if (fieldFormikType === 'object') { | |
| 82 | + fieldValidation = yup.object() | |
| 83 | + } else if (fieldFormikType === 'array') { | |
| 84 | + fieldValidation = yup.array() | |
| 85 | + } else if (fieldFormikType === 'url') { | |
| 86 | + fieldValidation = yup.string().url() | |
| 87 | + } else if (fieldFormikType === 'boolean') { | |
| 88 | + fieldValidation = yup.boolean() | |
| 89 | + } else if (fieldFormikType === 'positive') { | |
| 90 | + fieldValidation = yup.number().positive() | |
| 91 | + } else if (fieldFormikType === "negative") { | |
| 92 | + fieldValidation = yup.number().negative() | |
| 93 | + } else if (fieldFormikType === "integer") { | |
| 94 | + fieldValidation = yup.number().integer() | |
| 95 | + } else { | |
| 96 | + fieldValidation = yup.string() | |
| 97 | + } | |
| 98 | + | |
| 99 | + if (field.validation && Array.isArray(field.validation)) { | |
| 100 | + field.validation.forEach((validationRule) => { | |
| 101 | + | |
| 102 | + if (validationRule.require) { | |
| 103 | + fieldValidation = fieldValidation.required(validationRule.message || 'This field is required'); | |
| 104 | + } | |
| 105 | + if (validationRule.min) { | |
| 106 | + fieldValidation = fieldValidation.min(validationRule.value, validationRule.message || (fieldFormikType === 'date' ? "Date is too small" : 'Value is too small')); | |
| 107 | + } | |
| 108 | + if (validationRule.max) { | |
| 109 | + fieldValidation = fieldValidation.max(validationRule.value, validationRule.message || (fieldFormikType === 'date' ? "Date is too large" : 'Value is too large')); | |
| 110 | + } | |
| 111 | + if (validationRule.match) { | |
| 112 | + fieldValidation = fieldValidation.matches( | |
| 113 | + new RegExp(validationRule.match), | |
| 114 | + validationRule.message || 'Value does not match the pattern' | |
| 115 | + ); | |
| 116 | + } | |
| 117 | + if (validationRule?.formikType && validationRule?.message) { | |
| 118 | + fieldValidation = fieldValidation.typeError(validationRule.message) | |
| 119 | + } | |
| 120 | + }); | |
| 121 | + } | |
| 122 | + | |
| 123 | + return { | |
| 124 | + ...acc, | |
| 125 | + [field.name]: fieldValidation, | |
| 126 | + }; | |
| 127 | + | |
| 128 | + }, {}) | |
| 129 | +}; |
+17-0Client/src/utils/utils.js
| @@ -0,0 +1,17 @@ | ||
| 1 | +import * as XLSX from "xlsx"; | |
| 2 | + | |
| 3 | +export const commonUtils = { | |
| 4 | + convertJsonToCsvOrExcel: ({ jsonArray, csvColumns, fileName, extension }) => { | |
| 5 | + const csvHeader = csvColumns?.length > 0 && csvColumns?.map((col) => col?.Header); | |
| 6 | + | |
| 7 | + const csvContent = [ | |
| 8 | + csvHeader, | |
| 9 | + ...jsonArray?.map((row) => csvColumns?.length > 0 && csvColumns?.map((col) => row[col?.accessor])) | |
| 10 | + ]; | |
| 11 | + | |
| 12 | + const ws = XLSX.utils.aoa_to_sheet(csvContent); | |
| 13 | + const wb = XLSX.utils.book_new(); | |
| 14 | + XLSX.utils.book_append_sheet(wb, ws, 'Sheet 1'); | |
| 15 | + XLSX.writeFile(wb, `${fileName}.${extension}`); // .csv, .xlsx | |
| 16 | + } | |
| 17 | +} |
+789-0Client/src/views/admin/account/AddEdit.js
| @@ -0,0 +1,789 @@ | ||
| 1 | +import { CloseIcon } from '@chakra-ui/icons'; | |
| 2 | +import { Button, Checkbox, Drawer, DrawerBody, DrawerContent, DrawerFooter, DrawerHeader, DrawerOverlay, Flex, FormLabel, Grid, GridItem, IconButton, Input, Select, Text } from '@chakra-ui/react'; | |
| 3 | +import Spinner from 'components/spinner/Spinner'; | |
| 4 | +import dayjs from 'dayjs'; | |
| 5 | +import { useFormik } from 'formik'; | |
| 6 | +import { useState, useEffect } from 'react'; | |
| 7 | +import { LiaMousePointerSolid } from 'react-icons/lia'; | |
| 8 | +import { postApi, getApi, putApi } from 'services/api'; | |
| 9 | +import { generateValidationSchema } from 'utils'; | |
| 10 | +import CustomForm from 'utils/customForm'; | |
| 11 | +import * as yup from 'yup' | |
| 12 | +import { accountSchema } from '../../../schema/accountSchema'; | |
| 13 | +import UserModel from 'components/commonTableModel/UserModel'; | |
| 14 | +import AccountModel from 'components/commonTableModel/AccountModel'; | |
| 15 | +import { toast } from 'react-toastify'; | |
| 16 | +import { useDispatch, useSelector } from 'react-redux'; | |
| 17 | +import { fetchAccountData } from '../../../redux/slices/accountSlice'; | |
| 18 | + | |
| 19 | +const AddEdit = (props) => { | |
| 20 | + const { isOpen, size, onClose, type, setAction, selectedId } = props; | |
| 21 | + const [isLoding, setIsLoding] = useState(false) | |
| 22 | + const [userModel, setUserModel] = useState(false) | |
| 23 | + const [accountModel, setAccountModel] = useState(false) | |
| 24 | + const [userData, setUserData] = useState([]); | |
| 25 | + const [accountDetails, setAccountDetails] = useState({}); | |
| 26 | + const dispatch = useDispatch(); | |
| 27 | + const user = JSON.parse(localStorage.getItem("user")) | |
| 28 | + | |
| 29 | + const accountList = useSelector((state) => state?.accountData?.data?.data) | |
| 30 | + // const { isLoding } = useSelector((state) => state?.accountData) | |
| 31 | + | |
| 32 | + const initialValues = { | |
| 33 | + name: type === "edit" ? accountDetails?.name : "", | |
| 34 | + officePhone: type === "edit" ? accountDetails?.officePhone : "", | |
| 35 | + alternatePhone: type === "edit" ? accountDetails?.alternatePhone : "", | |
| 36 | + assignUser: type === "edit" ? accountDetails?.assignUser : null, | |
| 37 | + website: type === "edit" ? accountDetails?.website : "", | |
| 38 | + fax: type === "edit" ? accountDetails?.fax : "", | |
| 39 | + ownership: type === "edit" ? accountDetails?.ownership : "", | |
| 40 | + emailAddress: type === "edit" ? accountDetails?.emailAddress : "", | |
| 41 | + nonPrimaryEmail: type === "edit" ? accountDetails?.nonPrimaryEmail : "", | |
| 42 | + billingStreet: type === "edit" ? accountDetails?.billingStreet : "", | |
| 43 | + billingStreet2: type === "edit" ? accountDetails?.billingStreet2 : "", | |
| 44 | + billingStreet3: type === "edit" ? accountDetails?.billingStreet3 : "", | |
| 45 | + billingStreet4: type === "edit" ? accountDetails?.billingStreet4 : "", | |
| 46 | + billingPostalcode: type === "edit" ? accountDetails?.billingPostalcode : "", | |
| 47 | + billingCity: type === "edit" ? accountDetails?.billingCity : "", | |
| 48 | + billingState: type === "edit" ? accountDetails?.billingState : "", | |
| 49 | + billingCountry: type === "edit" ? accountDetails?.billingCountry : "", | |
| 50 | + shippingStreet: type === "edit" ? accountDetails?.shippingStreet : "", | |
| 51 | + shippingStreet2: type === "edit" ? accountDetails?.shippingStreet2 : "", | |
| 52 | + shippingStreet3: type === "edit" ? accountDetails?.shippingStreet3 : "", | |
| 53 | + shippingStreet4: type === "edit" ? accountDetails?.shippingStreet4 : "", | |
| 54 | + shippingPostalcode: type === "edit" ? accountDetails?.shippingPostalcode : "", | |
| 55 | + shippingCity: type === "edit" ? accountDetails?.shippingCity : "", | |
| 56 | + shippingState: type === "edit" ? accountDetails?.shippingState : "", | |
| 57 | + shippingCountry: type === "edit" ? accountDetails?.shippingCountry : "", | |
| 58 | + description: type === "edit" ? accountDetails?.description : "", | |
| 59 | + type: type === "edit" ? accountDetails?.type : "", | |
| 60 | + industry: type === "edit" ? accountDetails?.industry : "", | |
| 61 | + annualRevenue: type === "edit" ? accountDetails?.annualRevenue : "", | |
| 62 | + rating: type === "edit" ? accountDetails?.rating : "", | |
| 63 | + SICCode: type === "edit" ? accountDetails?.SICCode : "", | |
| 64 | + emailOptOut: type === "edit" ? accountDetails?.emailOptOut : false, | |
| 65 | + invalidEmail: type === "edit" ? accountDetails?.invalidEmail : false, | |
| 66 | + memberOf: type === "edit" ? accountDetails?.memberOf : null, | |
| 67 | + createBy: JSON.parse(localStorage.getItem('user'))._id, | |
| 68 | + modifiedBy: JSON.parse(localStorage.getItem('user'))._id | |
| 69 | + }; | |
| 70 | + | |
| 71 | + | |
| 72 | + const addData = async (values) => { | |
| 73 | + try { | |
| 74 | + setIsLoding(true) | |
| 75 | + let response = await postApi('api/account/add', values) | |
| 76 | + if (response.status === 200) { | |
| 77 | + onClose(); | |
| 78 | + toast.success(`Account Save successfully`) | |
| 79 | + formik.resetForm(); | |
| 80 | + setAction((pre) => !pre) | |
| 81 | + } | |
| 82 | + } catch (e) { | |
| 83 | + console.log(e); | |
| 84 | + toast.error(`server error`) | |
| 85 | + } | |
| 86 | + finally { | |
| 87 | + setIsLoding(false) | |
| 88 | + } | |
| 89 | + }; | |
| 90 | + const editData = async (values) => { | |
| 91 | + try { | |
| 92 | + setIsLoding(true) | |
| 93 | + let response = await putApi(`api/account/edit/${selectedId}`, values) | |
| 94 | + if (response.status === 200) { | |
| 95 | + onClose(); | |
| 96 | + toast.success(`Account Update successfully`) | |
| 97 | + formik.resetForm(); | |
| 98 | + // setAction((pre) => !pre) | |
| 99 | + } | |
| 100 | + } catch (e) { | |
| 101 | + console.log(e); | |
| 102 | + toast.error(`server error`) | |
| 103 | + } | |
| 104 | + finally { | |
| 105 | + setIsLoding(false) | |
| 106 | + } | |
| 107 | + }; | |
| 108 | + | |
| 109 | + const handleCancel = () => { | |
| 110 | + formik.resetForm(); | |
| 111 | + onClose() | |
| 112 | + } | |
| 113 | + | |
| 114 | + const formik = useFormik({ | |
| 115 | + initialValues: initialValues, | |
| 116 | + validationSchema: accountSchema, | |
| 117 | + enableReinitialize: true, | |
| 118 | + onSubmit: (values, { resetForm }) => { | |
| 119 | + if (type === "add") { | |
| 120 | + addData(values) | |
| 121 | + } else { | |
| 122 | + const payload = { ...values, modifiedDate: new Date() } | |
| 123 | + editData(payload) | |
| 124 | + } | |
| 125 | + }, | |
| 126 | + }); | |
| 127 | + | |
| 128 | + const { errors, touched, values, handleBlur, handleChange, handleSubmit, setFieldValue, } = formik | |
| 129 | + | |
| 130 | + const fetchData = async () => { | |
| 131 | + setIsLoding(true) | |
| 132 | + let result = await getApi('api/user/'); | |
| 133 | + setUserData(result?.data?.user); | |
| 134 | + setIsLoding(false) | |
| 135 | + } | |
| 136 | + | |
| 137 | + | |
| 138 | + const fetchAccountDetails = async () => { | |
| 139 | + if (type === "edit") { | |
| 140 | + try { | |
| 141 | + setIsLoding(true) | |
| 142 | + let result = await getApi('api/account/view/', selectedId) | |
| 143 | + if (result?.status === 200) { | |
| 144 | + setAccountDetails(result?.data) | |
| 145 | + } | |
| 146 | + | |
| 147 | + } | |
| 148 | + catch (e) { | |
| 149 | + console.log(e); | |
| 150 | + } | |
| 151 | + finally { | |
| 152 | + setIsLoding(false) | |
| 153 | + } | |
| 154 | + } | |
| 155 | + } | |
| 156 | + | |
| 157 | + | |
| 158 | + useEffect(() => { | |
| 159 | + if (type === "edit") fetchAccountDetails(); | |
| 160 | + if (user.role === 'superAdmin') fetchData(); | |
| 161 | + }, [type, selectedId]) | |
| 162 | + | |
| 163 | + // useEffect(() => { | |
| 164 | + // if (accountList?.length === 0) dispatch(fetchAccountData()) | |
| 165 | + // }, []) | |
| 166 | + | |
| 167 | + return ( | |
| 168 | + <div> | |
| 169 | + {userModel && <UserModel onClose={() => setUserModel(false)} isOpen={userModel} fieldName={"assignUser"} setFieldValue={setFieldValue} data={userData} isLoding={isLoding} setIsLoding={setIsLoding} />} | |
| 170 | + {accountModel && <AccountModel onClose={() => setAccountModel(false)} isOpen={accountModel} fieldName={"memberOf"} setFieldValue={setFieldValue} data={accountList} isLoding={isLoding} setIsLoding={setIsLoding} />} | |
| 171 | + | |
| 172 | + <Drawer isOpen={isOpen} size={size}> | |
| 173 | + <DrawerOverlay /> | |
| 174 | + <DrawerContent> | |
| 175 | + <DrawerHeader alignItems={"center"} justifyContent='space-between' display='flex' > | |
| 176 | + {type === "add" ? "Add" : "Edit"} Account | |
| 177 | + <IconButton onClick={() => handleCancel()} icon={<CloseIcon />} /> | |
| 178 | + </DrawerHeader> | |
| 179 | + <DrawerBody> | |
| 180 | + <Grid templateColumns="repeat(12, 1fr)" gap={3}> | |
| 181 | + <GridItem colSpan={{ base: 12, md: 6 }}> | |
| 182 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='8px'> | |
| 183 | + Account Name<Text color={"red"}>*</Text> | |
| 184 | + </FormLabel> | |
| 185 | + <Input | |
| 186 | + fontSize='sm' | |
| 187 | + value={values.name} | |
| 188 | + name="name" | |
| 189 | + onChange={handleChange} | |
| 190 | + onBlur={handleBlur} | |
| 191 | + placeholder='Account Name' | |
| 192 | + fontWeight='500' | |
| 193 | + borderColor={errors.name && touched.name ? "red.300" : null} | |
| 194 | + /> | |
| 195 | + <Text mb='10px' fontSize='sm' color={'red'}> {errors.name && touched.name && errors.name}</Text> | |
| 196 | + </GridItem> | |
| 197 | + <GridItem colSpan={{ base: 12, md: 6 }}> | |
| 198 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='8px'> | |
| 199 | + Office Phone | |
| 200 | + </FormLabel> | |
| 201 | + <Input | |
| 202 | + fontSize='sm' | |
| 203 | + value={values.officePhone} | |
| 204 | + name="officePhone" | |
| 205 | + type="number" | |
| 206 | + onChange={handleChange} | |
| 207 | + onBlur={handleBlur} | |
| 208 | + placeholder='Office Phone' | |
| 209 | + fontWeight='500' | |
| 210 | + borderColor={errors.officePhone && touched.officePhone ? "red.300" : null} | |
| 211 | + /> | |
| 212 | + <Text mb='10px' fontSize='sm' color={'red'}> {errors.officePhone && touched.officePhone && errors.officePhone}</Text> | |
| 213 | + </GridItem> | |
| 214 | + <GridItem colSpan={{ base: 12, md: 6 }}> | |
| 215 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='8px'> | |
| 216 | + Alternate Phone | |
| 217 | + </FormLabel> | |
| 218 | + <Input | |
| 219 | + fontSize='sm' | |
| 220 | + value={values.alternatePhone} | |
| 221 | + name="alternatePhone" | |
| 222 | + type="number" | |
| 223 | + onChange={handleChange} | |
| 224 | + onBlur={handleBlur} | |
| 225 | + placeholder='Alternate Phone' | |
| 226 | + fontWeight='500' | |
| 227 | + borderColor={errors.alternatePhone && touched.alternatePhone ? "red.300" : null} | |
| 228 | + /> | |
| 229 | + <Text mb='10px' fontSize='sm' color={'red'}> {errors.alternatePhone && touched.alternatePhone && errors.alternatePhone}</Text> | |
| 230 | + </GridItem> | |
| 231 | + <GridItem colSpan={{ base: 12, md: 6 }}> | |
| 232 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='8px'> | |
| 233 | + Website | |
| 234 | + </FormLabel> | |
| 235 | + <Input | |
| 236 | + fontSize='sm' | |
| 237 | + value={values.website} | |
| 238 | + name="website" | |
| 239 | + type="url" | |
| 240 | + onChange={handleChange} | |
| 241 | + onBlur={handleBlur} | |
| 242 | + placeholder='Website URL' | |
| 243 | + fontWeight='500' | |
| 244 | + borderColor={errors.website && touched.website ? "red.300" : null} | |
| 245 | + /> | |
| 246 | + <Text mb='10px' fontSize='sm' color={'red'}> {errors.website && touched.website && errors.website}</Text> | |
| 247 | + </GridItem> | |
| 248 | + <GridItem colSpan={{ base: 12, md: 6 }}> | |
| 249 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='8px'> | |
| 250 | + Fax | |
| 251 | + </FormLabel> | |
| 252 | + <Input | |
| 253 | + fontSize='sm' | |
| 254 | + value={values.fax} | |
| 255 | + name="fax" | |
| 256 | + type="number" | |
| 257 | + onChange={handleChange} | |
| 258 | + onBlur={handleBlur} | |
| 259 | + placeholder='Fax' | |
| 260 | + fontWeight='500' | |
| 261 | + borderColor={errors.fax && touched.fax ? "red.300" : null} | |
| 262 | + /> | |
| 263 | + <Text mb='10px' fontSize='sm' color={'red'}> {errors.fax && touched.fax && errors.fax}</Text> | |
| 264 | + </GridItem> | |
| 265 | + <GridItem colSpan={{ base: 12, md: 6 }}> | |
| 266 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='8px'> | |
| 267 | + Ownership | |
| 268 | + </FormLabel> | |
| 269 | + <Input | |
| 270 | + fontSize='sm' | |
| 271 | + value={values.ownership} | |
| 272 | + name="ownership" | |
| 273 | + onChange={handleChange} | |
| 274 | + onBlur={handleBlur} | |
| 275 | + placeholder='ownership' | |
| 276 | + fontWeight='500' | |
| 277 | + borderColor={errors.ownership && touched.ownership ? "red.300" : null} | |
| 278 | + /> | |
| 279 | + <Text mb='10px' fontSize='sm' color={'red'}> {errors.ownership && touched.ownership && errors.ownership}</Text> | |
| 280 | + </GridItem> | |
| 281 | + <GridItem colSpan={{ base: 12, md: 6 }}> | |
| 282 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='8px'> | |
| 283 | + Email Address | |
| 284 | + </FormLabel> | |
| 285 | + <Input | |
| 286 | + fontSize='sm' | |
| 287 | + value={values.emailAddress} | |
| 288 | + name="emailAddress" | |
| 289 | + onChange={handleChange} | |
| 290 | + onBlur={handleBlur} | |
| 291 | + placeholder='Email Address' | |
| 292 | + fontWeight='500' | |
| 293 | + borderColor={errors.emailAddress && touched.emailAddress ? "red.300" : null} | |
| 294 | + /> | |
| 295 | + <Text mb='10px' fontSize='sm' color={'red'}> {errors.emailAddress && touched.emailAddress && errors.emailAddress}</Text> | |
| 296 | + </GridItem> | |
| 297 | + <GridItem colSpan={{ base: 12, md: 6 }}> | |
| 298 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='8px'> | |
| 299 | + Non Primary Email | |
| 300 | + </FormLabel> | |
| 301 | + <Input | |
| 302 | + fontSize='sm' | |
| 303 | + value={values.nonPrimaryEmail} | |
| 304 | + name="nonPrimaryEmail" | |
| 305 | + onChange={handleChange} | |
| 306 | + onBlur={handleBlur} | |
| 307 | + placeholder='Non Primary Email' | |
| 308 | + fontWeight='500' | |
| 309 | + borderColor={errors.nonPrimaryEmail && touched.nonPrimaryEmail ? "red.300" : null} | |
| 310 | + /> | |
| 311 | + <Text mb='10px' fontSize='sm' color={'red'}> {errors.nonPrimaryEmail && touched.nonPrimaryEmail && errors.nonPrimaryEmail}</Text> | |
| 312 | + </GridItem> | |
| 313 | + <GridItem colSpan={{ base: 12, md: 6 }}> | |
| 314 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='8px'> | |
| 315 | + Shipping Street | |
| 316 | + </FormLabel> | |
| 317 | + <Input | |
| 318 | + fontSize='sm' | |
| 319 | + value={values.shippingStreet} | |
| 320 | + name="shippingStreet" | |
| 321 | + onChange={handleChange} | |
| 322 | + onBlur={handleBlur} | |
| 323 | + placeholder='Shipping Street' | |
| 324 | + fontWeight='500' | |
| 325 | + borderColor={errors.shippingStreet && touched.shippingStreet ? "red.300" : null} | |
| 326 | + /> | |
| 327 | + <Text mb='10px' fontSize='sm' color={'red'}> {errors.shippingStreet && touched.shippingStreet && errors.shippingStreet}</Text> | |
| 328 | + </GridItem> | |
| 329 | + <GridItem colSpan={{ base: 12, md: 6 }}> | |
| 330 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='8px'> | |
| 331 | + Billing Street | |
| 332 | + </FormLabel> | |
| 333 | + <Input | |
| 334 | + fontSize='sm' | |
| 335 | + value={values.billingStreet} | |
| 336 | + name="billingStreet" | |
| 337 | + onChange={handleChange} | |
| 338 | + onBlur={handleBlur} | |
| 339 | + placeholder='Billing Street' | |
| 340 | + fontWeight='500' | |
| 341 | + borderColor={errors.billingStreet && touched.billingStreet ? "red.300" : null} | |
| 342 | + /> | |
| 343 | + <Text mb='10px' fontSize='sm' color={'red'}> {errors.billingStreet && touched.billingStreet && errors.billingStreet}</Text> | |
| 344 | + </GridItem> | |
| 345 | + <GridItem colSpan={{ base: 12, md: 6 }}> | |
| 346 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='8px'> | |
| 347 | + Shipping Street2 | |
| 348 | + </FormLabel> | |
| 349 | + <Input | |
| 350 | + fontSize='sm' | |
| 351 | + value={values.shippingStreet2} | |
| 352 | + name="shippingStreet2" | |
| 353 | + onChange={handleChange} | |
| 354 | + onBlur={handleBlur} | |
| 355 | + placeholder='Shipping Street2' | |
| 356 | + fontWeight='500' | |
| 357 | + borderColor={errors.shippingStreet2 && touched.shippingStreet2 ? "red.300" : null} | |
| 358 | + /> | |
| 359 | + <Text mb='10px' fontSize='sm' color={'red'}> {errors.shippingStreet2 && touched.shippingStreet2 && errors.shippingStreet2}</Text> | |
| 360 | + </GridItem> | |
| 361 | + <GridItem colSpan={{ base: 12, md: 6 }}> | |
| 362 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='8px'> | |
| 363 | + Billing Street2 | |
| 364 | + </FormLabel> | |
| 365 | + <Input | |
| 366 | + fontSize='sm' | |
| 367 | + value={values.billingStreet2} | |
| 368 | + name="billingStreet2" | |
| 369 | + onChange={handleChange} | |
| 370 | + onBlur={handleBlur} | |
| 371 | + placeholder='Billing Street2' | |
| 372 | + fontWeight='500' | |
| 373 | + borderColor={errors.billingStreet2 && touched.billingStreet2 ? "red.300" : null} | |
| 374 | + /> | |
| 375 | + <Text mb='10px' fontSize='sm' color={'red'}> {errors.billingStreet2 && touched.billingStreet2 && errors.billingStreet2}</Text> | |
| 376 | + </GridItem> | |
| 377 | + <GridItem colSpan={{ base: 12, md: 6 }}> | |
| 378 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='8px'> | |
| 379 | + Shipping Street3 | |
| 380 | + </FormLabel> | |
| 381 | + <Input | |
| 382 | + fontSize='sm' | |
| 383 | + value={values.shippingStreet3} | |
| 384 | + name="shippingStreet3" | |
| 385 | + onChange={handleChange} | |
| 386 | + onBlur={handleBlur} | |
| 387 | + placeholder='Shipping Street3' | |
| 388 | + fontWeight='500' | |
| 389 | + borderColor={errors.shippingStreet3 && touched.shippingStreet3 ? "red.300" : null} | |
| 390 | + /> | |
| 391 | + <Text mb='10px' fontSize='sm' color={'red'}> {errors.shippingStreet3 && touched.shippingStreet3 && errors.shippingStreet3}</Text> | |
| 392 | + </GridItem> | |
| 393 | + <GridItem colSpan={{ base: 12, md: 6 }}> | |
| 394 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='8px'> | |
| 395 | + Billing Street3 | |
| 396 | + </FormLabel> | |
| 397 | + <Input | |
| 398 | + fontSize='sm' | |
| 399 | + value={values.billingStreet3} | |
| 400 | + name="billingStreet3" | |
| 401 | + onChange={handleChange} | |
| 402 | + onBlur={handleBlur} | |
| 403 | + placeholder='Billing Street3' | |
| 404 | + fontWeight='500' | |
| 405 | + borderColor={errors.billingStreet3 && touched.billingStreet3 ? "red.300" : null} | |
| 406 | + /> | |
| 407 | + <Text mb='10px' fontSize='sm' color={'red'}> {errors.billingStreet3 && touched.billingStreet3 && errors.billingStreet3}</Text> | |
| 408 | + </GridItem> | |
| 409 | + <GridItem colSpan={{ base: 12, md: 6 }}> | |
| 410 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='8px'> | |
| 411 | + Shipping Street4 | |
| 412 | + </FormLabel> | |
| 413 | + <Input | |
| 414 | + fontSize='sm' | |
| 415 | + value={values.shippingStreet4} | |
| 416 | + name="shippingStreet4" | |
| 417 | + onChange={handleChange} | |
| 418 | + onBlur={handleBlur} | |
| 419 | + placeholder='Shipping Street4' | |
| 420 | + fontWeight='500' | |
| 421 | + borderColor={errors.shippingStreet4 && touched.shippingStreet4 ? "red.300" : null} | |
| 422 | + /> | |
| 423 | + <Text mb='10px' fontSize='sm' color={'red'}> {errors.shippingStreet4 && touched.shippingStreet4 && errors.shippingStreet4}</Text> | |
| 424 | + </GridItem> | |
| 425 | + <GridItem colSpan={{ base: 12, md: 6 }}> | |
| 426 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='8px'> | |
| 427 | + Billing Street4 | |
| 428 | + </FormLabel> | |
| 429 | + <Input | |
| 430 | + fontSize='sm' | |
| 431 | + value={values.billingStreet4} | |
| 432 | + name="billingStreet4" | |
| 433 | + onChange={handleChange} | |
| 434 | + onBlur={handleBlur} | |
| 435 | + placeholder='Billing Street4' | |
| 436 | + fontWeight='500' | |
| 437 | + borderColor={errors.billingStreet4 && touched.billingStreet4 ? "red.300" : null} | |
| 438 | + /> | |
| 439 | + <Text mb='10px' fontSize='sm' color={'red'}> {errors.billingStreet4 && touched.billingStreet4 && errors.billingStreet4}</Text> | |
| 440 | + </GridItem> | |
| 441 | + <GridItem colSpan={{ base: 12, md: 6 }}> | |
| 442 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='8px'> | |
| 443 | + Shipping Postal Code | |
| 444 | + </FormLabel> | |
| 445 | + <Input | |
| 446 | + fontSize='sm' | |
| 447 | + value={values.shippingPostalcode} | |
| 448 | + name="shippingPostalcode" | |
| 449 | + onChange={handleChange} | |
| 450 | + onBlur={handleBlur} | |
| 451 | + placeholder='Shipping Postal Code' | |
| 452 | + fontWeight='500' | |
| 453 | + borderColor={errors.shippingPostalcode && touched.shippingPostalcode ? "red.300" : null} | |
| 454 | + /> | |
| 455 | + <Text mb='10px' fontSize='sm' color={'red'}> {errors.shippingPostalcode && touched.shippingPostalcode && errors.shippingPostalcode}</Text> | |
| 456 | + </GridItem> | |
| 457 | + <GridItem colSpan={{ base: 12, md: 6 }}> | |
| 458 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='8px'> | |
| 459 | + Billing Postal Code | |
| 460 | + </FormLabel> | |
| 461 | + <Input | |
| 462 | + fontSize='sm' | |
| 463 | + value={values.billingPostalcode} | |
| 464 | + name="billingPostalcode" | |
| 465 | + onChange={handleChange} | |
| 466 | + onBlur={handleBlur} | |
| 467 | + placeholder='Billing Postal Code' | |
| 468 | + fontWeight='500' | |
| 469 | + borderColor={errors.billingPostalcode && touched.billingPostalcode ? "red.300" : null} | |
| 470 | + /> | |
| 471 | + <Text mb='10px' fontSize='sm' color={'red'}> {errors.billingPostalcode && touched.billingPostalcode && errors.billingPostalcode}</Text> | |
| 472 | + </GridItem> | |
| 473 | + <GridItem colSpan={{ base: 12, md: 6 }}> | |
| 474 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='8px'> | |
| 475 | + Shipping City | |
| 476 | + </FormLabel> | |
| 477 | + <Input | |
| 478 | + fontSize='sm' | |
| 479 | + value={values.shippingCity} | |
| 480 | + name="shippingCity" | |
| 481 | + onChange={handleChange} | |
| 482 | + onBlur={handleBlur} | |
| 483 | + placeholder='Shipping City' | |
| 484 | + fontWeight='500' | |
| 485 | + borderColor={errors.shippingCity && touched.shippingCity ? "red.300" : null} | |
| 486 | + /> | |
| 487 | + <Text mb='10px' fontSize='sm' color={'red'}> {errors.shippingCity && touched.shippingCity && errors.shippingCity}</Text> | |
| 488 | + </GridItem> | |
| 489 | + <GridItem colSpan={{ base: 12, md: 6 }}> | |
| 490 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='8px'> | |
| 491 | + Billing City | |
| 492 | + </FormLabel> | |
| 493 | + <Input | |
| 494 | + fontSize='sm' | |
| 495 | + value={values.billingCity} | |
| 496 | + name="billingCity" | |
| 497 | + onChange={handleChange} | |
| 498 | + onBlur={handleBlur} | |
| 499 | + placeholder='Billing City' | |
| 500 | + fontWeight='500' | |
| 501 | + borderColor={errors.billingCity && touched.billingCity ? "red.300" : null} | |
| 502 | + /> | |
| 503 | + <Text mb='10px' fontSize='sm' color={'red'}> {errors.billingCity && touched.billingCity && errors.billingCity}</Text> | |
| 504 | + </GridItem> | |
| 505 | + <GridItem colSpan={{ base: 12, md: 6 }}> | |
| 506 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='8px'> | |
| 507 | + Shipping State | |
| 508 | + </FormLabel> | |
| 509 | + <Input | |
| 510 | + fontSize='sm' | |
| 511 | + value={values.shippingState} | |
| 512 | + name="shippingState" | |
| 513 | + onChange={handleChange} | |
| 514 | + onBlur={handleBlur} | |
| 515 | + placeholder='Shipping State' | |
| 516 | + fontWeight='500' | |
| 517 | + borderColor={errors.shippingState && touched.shippingState ? "red.300" : null} | |
| 518 | + /> | |
| 519 | + <Text mb='10px' fontSize='sm' color={'red'}> {errors.shippingState && touched.shippingState && errors.shippingState}</Text> | |
| 520 | + </GridItem> | |
| 521 | + <GridItem colSpan={{ base: 12, md: 6 }}> | |
| 522 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='8px'> | |
| 523 | + Billing State | |
| 524 | + </FormLabel> | |
| 525 | + <Input | |
| 526 | + fontSize='sm' | |
| 527 | + value={values.billingState} | |
| 528 | + name="billingState" | |
| 529 | + onChange={handleChange} | |
| 530 | + onBlur={handleBlur} | |
| 531 | + placeholder='Billing State' | |
| 532 | + fontWeight='500' | |
| 533 | + borderColor={errors.billingState && touched.billingState ? "red.300" : null} | |
| 534 | + /> | |
| 535 | + <Text mb='10px' fontSize='sm' color={'red'}> {errors.billingState && touched.billingState && errors.billingState}</Text> | |
| 536 | + </GridItem> | |
| 537 | + <GridItem colSpan={{ base: 12, md: 6 }}> | |
| 538 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='8px'> | |
| 539 | + Shipping Country | |
| 540 | + </FormLabel> | |
| 541 | + <Input | |
| 542 | + fontSize='sm' | |
| 543 | + value={values.shippingCountry} | |
| 544 | + name="shippingCountry" | |
| 545 | + onChange={handleChange} | |
| 546 | + onBlur={handleBlur} | |
| 547 | + placeholder='Shipping Country' | |
| 548 | + fontWeight='500' | |
| 549 | + borderColor={errors.shippingCountry && touched.shippingCountry ? "red.300" : null} | |
| 550 | + /> | |
| 551 | + <Text mb='10px' fontSize='sm' color={'red'}> {errors.shippingCountry && touched.shippingCountry && errors.shippingCountry}</Text> | |
| 552 | + </GridItem> | |
| 553 | + <GridItem colSpan={{ base: 12, md: 6 }}> | |
| 554 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='8px'> | |
| 555 | + Billing Country | |
| 556 | + </FormLabel> | |
| 557 | + <Input | |
| 558 | + fontSize='sm' | |
| 559 | + value={values.billingCountry} | |
| 560 | + name="billingCountry" | |
| 561 | + onChange={handleChange} | |
| 562 | + onBlur={handleBlur} | |
| 563 | + placeholder='Billing Country' | |
| 564 | + fontWeight='500' | |
| 565 | + borderColor={errors.billingCountry && touched.billingCountry ? "red.300" : null} | |
| 566 | + /> | |
| 567 | + <Text mb='10px' fontSize='sm' color={'red'}> {errors.billingCountry && touched.billingCountry && errors.billingCountry}</Text> | |
| 568 | + </GridItem> | |
| 569 | + | |
| 570 | + | |
| 571 | + <GridItem colSpan={{ base: 12, md: 6 }}> | |
| 572 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='8px'> | |
| 573 | + Type | |
| 574 | + </FormLabel> | |
| 575 | + <Select | |
| 576 | + value={values.type} | |
| 577 | + name="type" | |
| 578 | + onChange={handleChange} | |
| 579 | + onBlur={handleBlur} | |
| 580 | + mb={errors.type && touched.type ? undefined : '10px'} | |
| 581 | + fontWeight='500' | |
| 582 | + placeholder={'Select Type'} | |
| 583 | + borderColor={errors.type && touched.type ? "red.300" : null} | |
| 584 | + > | |
| 585 | + <option value="Analyst">Analyst</option> | |
| 586 | + <option value="Competitor">Competitor </option> | |
| 587 | + <option value="Customer">Customer</option> | |
| 588 | + <option value="Integrator">Integrator</option> | |
| 589 | + <option value="Investor">Investor </option> | |
| 590 | + <option value="Partner">Partner</option> | |
| 591 | + <option value="Press">Press</option> | |
| 592 | + <option value="Prospect">Prospect</option> | |
| 593 | + <option value="Reseller">Reseller</option> | |
| 594 | + <option value="Other">Other</option> | |
| 595 | + </Select> | |
| 596 | + <Text mb='10px' fontSize='sm' color={'red'}> {errors.type && touched.type && errors.type}</Text> | |
| 597 | + </GridItem> | |
| 598 | + | |
| 599 | + <GridItem colSpan={{ base: 12, md: 6 }}> | |
| 600 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='8px'> | |
| 601 | + Industry | |
| 602 | + </FormLabel> | |
| 603 | + <Select | |
| 604 | + value={values.industry} | |
| 605 | + name="industry" | |
| 606 | + onChange={handleChange} | |
| 607 | + onBlur={handleBlur} | |
| 608 | + mb={errors.industry && touched.industry ? undefined : '10px'} | |
| 609 | + fontWeight='500' | |
| 610 | + placeholder={'Select Industry'} | |
| 611 | + borderColor={errors.industry && touched.industry ? "red.300" : null} | |
| 612 | + > | |
| 613 | + <option value="Apparel">Apparel</option> | |
| 614 | + <option value="Banking">Banking </option> | |
| 615 | + <option value="Biotechnology">Biotechnology</option> | |
| 616 | + <option value="Chemicals">Chemicals</option> | |
| 617 | + <option value="Communications">Communications </option> | |
| 618 | + <option value="Construction">Construction</option> | |
| 619 | + <option value="Consulting">Consulting</option> | |
| 620 | + <option value="Education">Education</option> | |
| 621 | + <option value="Electronics">Electronics</option> | |
| 622 | + <option value="Energy">Energy</option> | |
| 623 | + <option value="Engineering">Engineering</option> | |
| 624 | + <option value="Entertainment">Entertainment</option> | |
| 625 | + <option value="Finance">Finance</option> | |
| 626 | + <option value="Government">Government</option> | |
| 627 | + <option value="Healthcare">Healthcare</option> | |
| 628 | + <option value="Hospitality">Hospitality</option> | |
| 629 | + <option value="Insurance">Insurance</option> | |
| 630 | + <option value="Machinery">Machinery</option> | |
| 631 | + <option value="Manufacturing">Manufacturing</option> | |
| 632 | + <option value="Media">Media</option> | |
| 633 | + <option value="Not For Profit">Not For Profit</option> | |
| 634 | + <option value="Recreation">Recreation</option> | |
| 635 | + <option value="Retail">Retail</option> | |
| 636 | + <option value="Shipping">Shipping</option> | |
| 637 | + <option value="Technology">Technology</option> | |
| 638 | + <option value="Telecommunications">Telecommunications</option> | |
| 639 | + <option value="Transportation">Transportation</option> | |
| 640 | + <option value="Utilities">Utilities</option> | |
| 641 | + <option value="Other">Other</option> | |
| 642 | + </Select> | |
| 643 | + <Text mb='10px' fontSize='sm' color={'red'}> {errors.industry && touched.industry && errors.industry}</Text> | |
| 644 | + </GridItem> | |
| 645 | + <GridItem colSpan={{ base: 12, md: 6 }}> | |
| 646 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='8px'> | |
| 647 | + Description | |
| 648 | + </FormLabel> | |
| 649 | + <Input | |
| 650 | + fontSize='sm' | |
| 651 | + value={values.description} | |
| 652 | + name="description" | |
| 653 | + onChange={handleChange} | |
| 654 | + onBlur={handleBlur} | |
| 655 | + placeholder='Description' | |
| 656 | + fontWeight='500' | |
| 657 | + borderColor={errors.description && touched.description ? "red.300" : null} | |
| 658 | + /> | |
| 659 | + <Text mb='10px' fontSize='sm' color={'red'}> {errors.description && touched.description && errors.description}</Text> | |
| 660 | + </GridItem> | |
| 661 | + <GridItem colSpan={{ base: 12, md: 6 }}> | |
| 662 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='8px'> | |
| 663 | + Annual Revenue | |
| 664 | + </FormLabel> | |
| 665 | + <Input | |
| 666 | + fontSize='sm' | |
| 667 | + value={values.annualRevenue} | |
| 668 | + name="annualRevenue" | |
| 669 | + onChange={handleChange} | |
| 670 | + onBlur={handleBlur} | |
| 671 | + placeholder='annualRevenue' | |
| 672 | + fontWeight='500' | |
| 673 | + borderColor={errors.annualRevenue && touched.annualRevenue ? "red.300" : null} | |
| 674 | + /> | |
| 675 | + <Text mb='10px' fontSize='sm' color={'red'}> {errors.annualRevenue && touched.annualRevenue && errors.annualRevenue}</Text> | |
| 676 | + </GridItem> | |
| 677 | + <GridItem colSpan={{ base: 12, md: 6 }}> | |
| 678 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='8px'> | |
| 679 | + Rating | |
| 680 | + </FormLabel> | |
| 681 | + <Input | |
| 682 | + fontSize='sm' | |
| 683 | + value={values.rating} | |
| 684 | + name="rating" | |
| 685 | + onChange={handleChange} | |
| 686 | + onBlur={handleBlur} | |
| 687 | + placeholder='Rating' | |
| 688 | + fontWeight='500' | |
| 689 | + borderColor={errors.rating && touched.rating ? "red.300" : null} | |
| 690 | + /> | |
| 691 | + <Text mb='10px' fontSize='sm' color={'red'}> {errors.rating && touched.rating && errors.rating}</Text> | |
| 692 | + </GridItem> | |
| 693 | + <GridItem colSpan={{ base: 12, md: 6 }}> | |
| 694 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='8px'> | |
| 695 | + SIC Code | |
| 696 | + </FormLabel> | |
| 697 | + <Input | |
| 698 | + fontSize='sm' | |
| 699 | + value={values.SICCode} | |
| 700 | + name="SICCode" | |
| 701 | + onChange={handleChange} | |
| 702 | + onBlur={handleBlur} | |
| 703 | + placeholder='SIC Code' | |
| 704 | + fontWeight='500' | |
| 705 | + borderColor={errors.SICCode && touched.SICCode ? "red.300" : null} | |
| 706 | + /> | |
| 707 | + <Text mb='10px' fontSize='sm' color={'red'}> {errors.SICCode && touched.SICCode && errors.SICCode}</Text> | |
| 708 | + </GridItem> | |
| 709 | + { | |
| 710 | + user.role === 'superAdmin' && | |
| 711 | + <GridItem colSpan={{ base: 12, md: 6 }} > | |
| 712 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='8px'> | |
| 713 | + Assigned User | |
| 714 | + </FormLabel> | |
| 715 | + <Flex justifyContent={'space-between'}> | |
| 716 | + <Select | |
| 717 | + value={values.assignUser} | |
| 718 | + name="assignUser" | |
| 719 | + onChange={handleChange} | |
| 720 | + onBlur={handleBlur} | |
| 721 | + mb={errors.assignUser && touched.assignUser ? undefined : '10px'} | |
| 722 | + fontWeight='500' | |
| 723 | + placeholder={'Assign To'} | |
| 724 | + borderColor={errors.assignUser && touched.assignUser ? "red.300" : null} | |
| 725 | + > | |
| 726 | + {userData?.map((item) => { | |
| 727 | + return <option value={item._id} key={item._id}>{`${item?.firstName} ${item?.lastName}`}</option> | |
| 728 | + })} | |
| 729 | + </Select> | |
| 730 | + <IconButton onClick={() => setUserModel(true)} ml={2} fontSize='25px' icon={<LiaMousePointerSolid />} /> | |
| 731 | + </Flex> | |
| 732 | + </GridItem> | |
| 733 | + } | |
| 734 | + <GridItem colSpan={{ base: 12, md: 6 }}> | |
| 735 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='8px'> | |
| 736 | + Member Of | |
| 737 | + </FormLabel> | |
| 738 | + <Flex justifyContent={'space-between'}> | |
| 739 | + <Select | |
| 740 | + value={values.memberOf} | |
| 741 | + name="memberOf" | |
| 742 | + onChange={handleChange} | |
| 743 | + onBlur={handleBlur} | |
| 744 | + mb={errors.memberOf && touched.memberOf ? undefined : '10px'} | |
| 745 | + fontWeight='500' | |
| 746 | + placeholder={'Member Of'} | |
| 747 | + borderColor={errors.memberOf && touched.memberOf ? "red.300" : null} | |
| 748 | + > | |
| 749 | + {accountList?.length > 0 && accountList?.map((item) => { | |
| 750 | + return <option value={item._id} key={item._id}>{`${item?.name}`}</option> | |
| 751 | + })} | |
| 752 | + </Select> | |
| 753 | + <IconButton onClick={() => setAccountModel(true)} ml={2} fontSize='25px' icon={<LiaMousePointerSolid />} /> | |
| 754 | + </Flex> | |
| 755 | + <Text mb='10px' fontSize='sm' color={'red'}> {errors.memberOf && touched.memberOf && errors.memberOf}</Text> | |
| 756 | + </GridItem> | |
| 757 | + | |
| 758 | + <GridItem colSpan={{ base: 12, md: 6 }}> | |
| 759 | + <Checkbox isChecked={values?.emailOptOut} onChange={handleChange} name='emailOptOut' lable="Email Opt Out">Email Opt Out</Checkbox> | |
| 760 | + </GridItem> | |
| 761 | + <GridItem colSpan={{ base: 12, md: 6 }}> | |
| 762 | + <Checkbox isChecked={values?.invalidEmail} onChange={handleChange} name='invalidEmail' lable="Invalid Email">Invalid Email</Checkbox> | |
| 763 | + </GridItem> | |
| 764 | + | |
| 765 | + </Grid> | |
| 766 | + </DrawerBody> | |
| 767 | + <DrawerFooter> | |
| 768 | + <Button sx={{ textTransform: "capitalize" }} size="sm" disabled={isLoding ? true : false} variant="brand" type="submit" onClick={handleSubmit} > | |
| 769 | + {isLoding ? <Spinner /> : 'Save'} | |
| 770 | + </Button> | |
| 771 | + <Button | |
| 772 | + variant="outline" | |
| 773 | + colorScheme='red' size="sm" | |
| 774 | + sx={{ | |
| 775 | + marginLeft: 2, | |
| 776 | + textTransform: "capitalize", | |
| 777 | + }} | |
| 778 | + onClick={handleCancel} | |
| 779 | + > | |
| 780 | + Close | |
| 781 | + </Button> | |
| 782 | + </DrawerFooter> | |
| 783 | + </DrawerContent> | |
| 784 | + </Drawer> | |
| 785 | + </div> | |
| 786 | + ) | |
| 787 | +} | |
| 788 | + | |
| 789 | +export default AddEdit |
+877-0Client/src/views/admin/account/View.js
| @@ -0,0 +1,877 @@ | ||
| 1 | +import { Button, Grid, GridItem, Flex, IconButton, Text, Menu, MenuButton, MenuDivider, MenuItem, MenuList, useDisclosure, Box, Heading, Input, Select } from '@chakra-ui/react' | |
| 2 | +import { AddIcon, ChevronDownIcon, DeleteIcon, EditIcon } from "@chakra-ui/icons"; | |
| 3 | +import React from 'react' | |
| 4 | +import moment from 'moment' | |
| 5 | +import { Link, useNavigate, useParams } from 'react-router-dom' | |
| 6 | +import { BiLink } from 'react-icons/bi' | |
| 7 | +import { useEffect } from 'react' | |
| 8 | +import { useState } from 'react' | |
| 9 | +import Card from 'components/card/Card' | |
| 10 | +import { IoIosArrowBack } from "react-icons/io"; | |
| 11 | +import { HasAccess } from '../../../redux/accessUtils'; | |
| 12 | +import { HSeparator } from 'components/separator/Separator'; | |
| 13 | +import AddEdit from './AddEdit'; | |
| 14 | +import CommonDeleteModel from 'components/commonDeleteModel'; | |
| 15 | +import { deleteManyApi, putApi, getApi } from '../../../services/api'; | |
| 16 | +import { FaFilePdf } from "react-icons/fa"; | |
| 17 | +import html2pdf from "html2pdf.js"; | |
| 18 | +import { opprtunitiesSchema } from '../../../schema/opprtunitiesSchema'; | |
| 19 | +import { useFormik } from 'formik'; | |
| 20 | +import dayjs from 'dayjs'; | |
| 21 | +import { toast } from 'react-toastify'; | |
| 22 | +import { accountSchema } from '../../../schema/accountSchema'; | |
| 23 | + | |
| 24 | +const View = (props) => { | |
| 25 | + const params = useParams() | |
| 26 | + const { id } = params | |
| 27 | + const user = JSON.parse(localStorage.getItem("user")) | |
| 28 | + | |
| 29 | + const [permission, contactAccess, leadAccess] = HasAccess(['Account', 'Contacts', 'Leads']) | |
| 30 | + | |
| 31 | + const [data, setData] = useState() | |
| 32 | + const { onOpen, onClose } = useDisclosure() | |
| 33 | + const [edit, setEdit] = useState(false); | |
| 34 | + const [deleteModel, setDelete] = useState(false); | |
| 35 | + const [deleteManyModel, setDeleteManyModel] = useState(false); | |
| 36 | + const [loading, setLoading] = useState(false) | |
| 37 | + const navigate = useNavigate() | |
| 38 | + const [type, setType] = useState("") | |
| 39 | + const [editableField, setEditableField] = useState(null); | |
| 40 | + const [editableFieldName, setEditableFieldName] = useState(null); | |
| 41 | + const today = new Date().toISOString().split('T')[0]; | |
| 42 | + const todayTime = new Date().toISOString().split('.')[0]; | |
| 43 | + | |
| 44 | + const fetchViewData = async () => { | |
| 45 | + if (id) { | |
| 46 | + let result = await getApi('api/account/view/', id); | |
| 47 | + setData(result?.data); | |
| 48 | + } | |
| 49 | + } | |
| 50 | + const generatePDF = () => { | |
| 51 | + setLoading(true) | |
| 52 | + const element = document.getElementById("reports"); | |
| 53 | + const hideBtn = document.getElementById("hide-btn"); | |
| 54 | + | |
| 55 | + if (element) { | |
| 56 | + hideBtn.style.display = 'none'; | |
| 57 | + html2pdf() | |
| 58 | + .from(element) | |
| 59 | + .set({ | |
| 60 | + margin: [0, 0, 0, 0], | |
| 61 | + filename: `Account_Details_${moment().format("DD-MM-YYYY")}.pdf`, | |
| 62 | + image: { type: "jpeg", quality: 0.98 }, | |
| 63 | + html2canvas: { scale: 2, useCORS: true, allowTaint: true }, | |
| 64 | + jsPDF: { unit: "in", format: "letter", orientation: "portrait" }, | |
| 65 | + }) | |
| 66 | + .save().then(() => { | |
| 67 | + setLoading(false) | |
| 68 | + hideBtn.style.display = ''; | |
| 69 | + }) | |
| 70 | + // }, 500); | |
| 71 | + } else { | |
| 72 | + console.error("Element with ID 'reports' not found."); | |
| 73 | + setLoading(false) | |
| 74 | + } | |
| 75 | + }; | |
| 76 | + const handleDeleteAccount = async (ids) => { | |
| 77 | + try { | |
| 78 | + let response = await deleteManyApi('api/account/deleteMany', ids) | |
| 79 | + if (response.status === 200) { | |
| 80 | + navigate('/account') | |
| 81 | + toast.success(`Account Delete successfully`) | |
| 82 | + setDeleteManyModel(false) | |
| 83 | + } | |
| 84 | + } catch (error) { | |
| 85 | + console.log(error) | |
| 86 | + toast.error(`server error`) | |
| 87 | + | |
| 88 | + } | |
| 89 | + | |
| 90 | + } | |
| 91 | + | |
| 92 | + const initialValues = { | |
| 93 | + name: data?.name, | |
| 94 | + officePhone: data?.officePhone, | |
| 95 | + alternatePhone: data?.alternatePhone, | |
| 96 | + assignUser: data?.assignUser, | |
| 97 | + website: data?.website, | |
| 98 | + fax: data?.fax, | |
| 99 | + ownership: data?.ownership, | |
| 100 | + emailAddress: data?.emailAddress, | |
| 101 | + nonPrimaryEmail: data?.nonPrimaryEmail, | |
| 102 | + billingStreet: data?.billingStreet, | |
| 103 | + billingStreet2: data?.billingStreet2, | |
| 104 | + billingStreet3: data?.billingStreet3, | |
| 105 | + billingStreet4: data?.billingStreet4, | |
| 106 | + billingPostalcode: data?.billingPostalcode, | |
| 107 | + billingCity: data?.billingCity, | |
| 108 | + billingState: data?.billingState, | |
| 109 | + billingCountry: data?.billingCountry, | |
| 110 | + shippingStreet: data?.shippingStreet, | |
| 111 | + shippingStreet2: data?.shippingStreet2, | |
| 112 | + shippingStreet3: data?.shippingStreet3, | |
| 113 | + shippingStreet4: data?.shippingStreet4, | |
| 114 | + shippingPostalcode: data?.shippingPostalcode, | |
| 115 | + shippingCity: data?.shippingCity, | |
| 116 | + shippingState: data?.shippingState, | |
| 117 | + shippingCountry: data?.shippingCountry, | |
| 118 | + description: data?.description, | |
| 119 | + type: data?.type, | |
| 120 | + industry: data?.industry, | |
| 121 | + annualRevenue: data?.annualRevenue, | |
| 122 | + rating: data?.rating, | |
| 123 | + SICCode: data?.SICCode, | |
| 124 | + memberOf: data?.memberOf, | |
| 125 | + modifiedBy: JSON.parse(localStorage.getItem('user'))._id | |
| 126 | + } | |
| 127 | + | |
| 128 | + const formik = useFormik({ | |
| 129 | + initialValues: initialValues, | |
| 130 | + validationSchema: accountSchema, | |
| 131 | + enableReinitialize: true, | |
| 132 | + onSubmit: async (values, { resetForm }) => { | |
| 133 | + const payload = { | |
| 134 | + ...values, | |
| 135 | + modifiedDate: new Date() | |
| 136 | + } | |
| 137 | + let response = await putApi(`api/account/edit/${id}`, payload) | |
| 138 | + setEditableField(null); | |
| 139 | + if (response.status === 200) { | |
| 140 | + fetchViewData() | |
| 141 | + toast.success(`${editableFieldName} Update successfully`) | |
| 142 | + } else { | |
| 143 | + toast.error(`${editableFieldName} not Update`) | |
| 144 | + } | |
| 145 | + }, | |
| 146 | + }); | |
| 147 | + const handleDoubleClick = (fieldName, value, name) => { | |
| 148 | + formik.setFieldValue(fieldName, value); | |
| 149 | + setEditableField(fieldName) | |
| 150 | + setEditableFieldName(name) | |
| 151 | + }; | |
| 152 | + | |
| 153 | + const handleBlur = (e) => { | |
| 154 | + formik.handleSubmit(); | |
| 155 | + }; | |
| 156 | + useEffect(() => { | |
| 157 | + fetchViewData() | |
| 158 | + }, [id, edit]) | |
| 159 | + | |
| 160 | + return ( | |
| 161 | + <div> | |
| 162 | + <Grid templateColumns="repeat(4, 1fr)" gap={3} id="reports"> | |
| 163 | + <GridItem colSpan={{ base: 4 }}> | |
| 164 | + <Heading size="lg" m={3}> | |
| 165 | + {data?.name || " "} | |
| 166 | + </Heading> | |
| 167 | + </GridItem> | |
| 168 | + <GridItem colSpan={{ base: 4 }}> | |
| 169 | + <Card> | |
| 170 | + <Grid gap={4}> | |
| 171 | + <GridItem colSpan={2}> | |
| 172 | + <Box> | |
| 173 | + <Box display={"flex"} justifyContent={"space-between"} > | |
| 174 | + <Heading size="md" mb={3}> | |
| 175 | + Account Details | |
| 176 | + </Heading> | |
| 177 | + <Flex id="hide-btn" > | |
| 178 | + <Menu> | |
| 179 | + {(user.role === 'superAdmin' || permission?.create || permission?.update || permission?.delete) && <MenuButton variant="outline" colorScheme='blackAlpha' size="sm" va mr={2.5} as={Button} rightIcon={<ChevronDownIcon />}> | |
| 180 | + Actions | |
| 181 | + </MenuButton>} | |
| 182 | + <MenuDivider /> | |
| 183 | + <MenuList minWidth={2}> | |
| 184 | + {(user.role === 'superAdmin' || permission?.create) && <MenuItem onClick={() => { setEdit(true); setType("add"); formik.resetForm() } | |
| 185 | + } alignItems={'start'} color={'blue'} icon={<AddIcon />}>Add</MenuItem>} | |
| 186 | + {(user.role === 'superAdmin' || permission?.update) && <MenuItem onClick={() => { setEdit(true); setType("edit") }} alignItems={'start'} icon={<EditIcon />}>Edit</MenuItem>} | |
| 187 | + <MenuItem onClick={generatePDF} alignItems={"start"} icon={<FaFilePdf />} display={"flex"} style={{ alignItems: "center" }}>Print as PDF</MenuItem > | |
| 188 | + | |
| 189 | + {(user.role === 'superAdmin' || permission?.delete) && <> | |
| 190 | + <MenuDivider /> | |
| 191 | + <MenuItem alignItems={'start'} onClick={() => setDeleteManyModel(true)} color={'red'} icon={<DeleteIcon />}>Delete</MenuItem> | |
| 192 | + </>} | |
| 193 | + </MenuList> | |
| 194 | + </Menu> | |
| 195 | + <Button leftIcon={<IoIosArrowBack />} size='sm' variant="brand" onClick={() => navigate(-1)} > | |
| 196 | + Back | |
| 197 | + </Button> | |
| 198 | + </Flex> | |
| 199 | + </Box> | |
| 200 | + <HSeparator /> | |
| 201 | + </Box> | |
| 202 | + </GridItem> | |
| 203 | + <GridItem colSpan={{ base: 2, md: 1 }} > | |
| 204 | + <Text fontSize="sm" fontWeight="bold" color={'blackAlpha.900'}> Account Name </Text> | |
| 205 | + { | |
| 206 | + editableField === "name" ? | |
| 207 | + <> | |
| 208 | + <Input | |
| 209 | + id="text" | |
| 210 | + name="name" | |
| 211 | + type="text" | |
| 212 | + onChange={formik.handleChange} | |
| 213 | + onBlur={handleBlur} | |
| 214 | + value={formik.values.name} | |
| 215 | + borderColor={formik?.errors?.name && formik?.touched?.name ? "red.300" : null} | |
| 216 | + autoFocus | |
| 217 | + /> | |
| 218 | + <Text mb='10px' color={'red'}> {formik?.errors.name && formik?.touched.name && formik?.errors.name}</Text> | |
| 219 | + </> | |
| 220 | + : | |
| 221 | + <Text onDoubleClick={() => handleDoubleClick("name", data?.name, "Account Name")}>{data?.name ? data?.name : ' - '}</Text> | |
| 222 | + } | |
| 223 | + </GridItem> | |
| 224 | + <GridItem colSpan={{ base: 2, md: 1 }} > | |
| 225 | + <Text fontSize="sm" fontWeight="bold" color={'blackAlpha.900'}> Office Phone </Text> | |
| 226 | + { | |
| 227 | + editableField === "officePhone" ? | |
| 228 | + <> | |
| 229 | + <Input | |
| 230 | + name="officePhone" | |
| 231 | + type="number" | |
| 232 | + onChange={formik.handleChange} | |
| 233 | + onBlur={handleBlur} | |
| 234 | + value={formik.values.officePhone} | |
| 235 | + borderColor={formik?.errors?.officePhone && formik?.touched?.officePhone ? "red.300" : null} | |
| 236 | + autoFocus | |
| 237 | + /> | |
| 238 | + <Text mb='10px' color={'red'}> {formik?.errors.officePhone && formik?.touched.officePhone && formik?.errors.officePhone}</Text> | |
| 239 | + </> | |
| 240 | + : | |
| 241 | + <Text onDoubleClick={() => handleDoubleClick("officePhone", data?.officePhone, "Office Phone")}>{data?.officePhone ? data?.officePhone : ' - '}</Text> | |
| 242 | + } | |
| 243 | + </GridItem> | |
| 244 | + <GridItem colSpan={{ base: 2, md: 1 }} > | |
| 245 | + <Text fontSize="sm" fontWeight="bold" color={'blackAlpha.900'}>Alternate Phone</Text> | |
| 246 | + { | |
| 247 | + editableField === "alternatePhone" ? | |
| 248 | + <> | |
| 249 | + <Input | |
| 250 | + name="alternatePhone" | |
| 251 | + type="number" | |
| 252 | + onChange={formik.handleChange} | |
| 253 | + onBlur={handleBlur} | |
| 254 | + value={formik.values.alternatePhone} | |
| 255 | + borderColor={formik?.errors?.alternatePhone && formik?.touched?.alternatePhone ? "red.300" : null} | |
| 256 | + autoFocus | |
| 257 | + /> | |
| 258 | + <Text mb='10px' color={'red'}> {formik?.errors.alternatePhone && formik?.touched.alternatePhone && formik?.errors.alternatePhone}</Text> | |
| 259 | + </> | |
| 260 | + : | |
| 261 | + <Text onDoubleClick={() => handleDoubleClick("alternatePhone", data?.alternatePhone, "Alternate Phone")}>{data?.alternatePhone ? data?.alternatePhone : ' - '}</Text> | |
| 262 | + } | |
| 263 | + </GridItem> | |
| 264 | + <GridItem colSpan={{ base: 2, md: 1 }} > | |
| 265 | + <Text fontSize="sm" fontWeight="bold" color={'blackAlpha.900'}>Website</Text> | |
| 266 | + { | |
| 267 | + editableField === "website" ? | |
| 268 | + <> | |
| 269 | + <Input | |
| 270 | + name="website" | |
| 271 | + type="url" | |
| 272 | + onChange={formik.handleChange} | |
| 273 | + onBlur={handleBlur} | |
| 274 | + value={formik.values.website} | |
| 275 | + borderColor={formik?.errors?.website && formik?.touched?.website ? "red.300" : null} | |
| 276 | + autoFocus | |
| 277 | + /> | |
| 278 | + <Text mb='10px' color={'red'}> {formik?.errors.website && formik?.touched.website && formik?.errors.website}</Text> | |
| 279 | + </> | |
| 280 | + : | |
| 281 | + <Text onDoubleClick={() => handleDoubleClick("website", data?.website, "Website")}>{data?.website ? data?.website : ' - '}</Text> | |
| 282 | + } | |
| 283 | + </GridItem> | |
| 284 | + <GridItem colSpan={{ base: 2, md: 1 }} > | |
| 285 | + <Text fontSize="sm" fontWeight="bold" color={'blackAlpha.900'}>Fax</Text> | |
| 286 | + { | |
| 287 | + editableField === "fax" ? | |
| 288 | + <> | |
| 289 | + <Input | |
| 290 | + name="fax" | |
| 291 | + type="number" | |
| 292 | + onChange={formik.handleChange} | |
| 293 | + onBlur={handleBlur} | |
| 294 | + value={formik.values.fax} | |
| 295 | + borderColor={formik?.errors?.fax && formik?.touched?.fax ? "red.300" : null} | |
| 296 | + autoFocus | |
| 297 | + /> | |
| 298 | + <Text mb='10px' color={'red'}> {formik?.errors.fax && formik?.touched.fax && formik?.errors.fax}</Text> | |
| 299 | + </> | |
| 300 | + : | |
| 301 | + <Text onDoubleClick={() => handleDoubleClick("fax", data?.fax, "Fax")}>{data?.fax ? data?.fax : ' - '}</Text> | |
| 302 | + } | |
| 303 | + </GridItem> | |
| 304 | + <GridItem colSpan={{ base: 2, md: 1 }} > | |
| 305 | + <Text fontSize="sm" fontWeight="bold" color={'blackAlpha.900'}>Ownership</Text> | |
| 306 | + { | |
| 307 | + editableField === "ownership" ? | |
| 308 | + <> | |
| 309 | + <Input | |
| 310 | + name="ownership" | |
| 311 | + onChange={formik.handleChange} | |
| 312 | + onBlur={handleBlur} | |
| 313 | + value={formik.values.ownership} | |
| 314 | + borderColor={formik?.errors?.ownership && formik?.touched?.ownership ? "red.300" : null} | |
| 315 | + autoFocus | |
| 316 | + /> | |
| 317 | + <Text mb='10px' color={'red'}> {formik?.errors.ownership && formik?.touched.ownership && formik?.errors.ownership}</Text> | |
| 318 | + </> | |
| 319 | + : | |
| 320 | + <Text onDoubleClick={() => handleDoubleClick("ownership", data?.ownership, "Ownership")}>{data?.ownership ? data?.ownership : ' - '}</Text> | |
| 321 | + } | |
| 322 | + </GridItem> | |
| 323 | + <GridItem colSpan={{ base: 2, md: 1 }} > | |
| 324 | + <Text fontSize="sm" fontWeight="bold" color={'blackAlpha.900'}>Email Address</Text> | |
| 325 | + { | |
| 326 | + editableField === "emailAddress" ? | |
| 327 | + <> | |
| 328 | + <Input | |
| 329 | + name="emailAddress" | |
| 330 | + onChange={formik.handleChange} | |
| 331 | + onBlur={handleBlur} | |
| 332 | + value={formik.values.emailAddress} | |
| 333 | + borderColor={formik?.errors?.emailAddress && formik?.touched?.emailAddress ? "red.300" : null} | |
| 334 | + autoFocus | |
| 335 | + /> | |
| 336 | + <Text mb='10px' color={'red'}> {formik?.errors.emailAddress && formik?.touched.emailAddress && formik?.errors.emailAddress}</Text> | |
| 337 | + </> | |
| 338 | + : | |
| 339 | + <Text onDoubleClick={() => handleDoubleClick("emailAddress", data?.emailAddress, "Email Address")}>{data?.emailAddress ? data?.emailAddress : ' - '}</Text> | |
| 340 | + } | |
| 341 | + </GridItem> | |
| 342 | + <GridItem colSpan={{ base: 2, md: 1 }} > | |
| 343 | + <Text fontSize="sm" fontWeight="bold" color={'blackAlpha.900'}>Non Primary Email</Text> | |
| 344 | + { | |
| 345 | + editableField === "nonPrimaryEmail" ? | |
| 346 | + <> | |
| 347 | + <Input | |
| 348 | + name="nonPrimaryEmail" | |
| 349 | + onChange={formik.handleChange} | |
| 350 | + onBlur={handleBlur} | |
| 351 | + value={formik.values.nonPrimaryEmail} | |
| 352 | + borderColor={formik?.errors?.nonPrimaryEmail && formik?.touched?.nonPrimaryEmail ? "red.300" : null} | |
| 353 | + autoFocus | |
| 354 | + /> | |
| 355 | + <Text mb='10px' color={'red'}> {formik?.errors.nonPrimaryEmail && formik?.touched.nonPrimaryEmail && formik?.errors.nonPrimaryEmail}</Text> | |
| 356 | + </> | |
| 357 | + : | |
| 358 | + <Text onDoubleClick={() => handleDoubleClick("nonPrimaryEmail", data?.nonPrimaryEmail, "Non Primary Email")}>{data?.nonPrimaryEmail ? data?.nonPrimaryEmail : ' - '}</Text> | |
| 359 | + } | |
| 360 | + </GridItem> | |
| 361 | + <GridItem colSpan={{ base: 2, md: 1 }} > | |
| 362 | + <Text fontSize="sm" fontWeight="bold" color={'blackAlpha.900'}>Shipping Street</Text> | |
| 363 | + { | |
| 364 | + editableField === "shippingStreet" ? | |
| 365 | + <> | |
| 366 | + <Input | |
| 367 | + name="shippingStreet" | |
| 368 | + onChange={formik.handleChange} | |
| 369 | + onBlur={handleBlur} | |
| 370 | + value={formik.values.shippingStreet} | |
| 371 | + borderColor={formik?.errors?.shippingStreet && formik?.touched?.shippingStreet ? "red.300" : null} | |
| 372 | + autoFocus | |
| 373 | + /> | |
| 374 | + <Text mb='10px' color={'red'}> {formik?.errors.shippingStreet && formik?.touched.shippingStreet && formik?.errors.shippingStreet}</Text> | |
| 375 | + </> | |
| 376 | + : | |
| 377 | + <Text onDoubleClick={() => handleDoubleClick("shippingStreet", data?.shippingStreet, "Shipping Street")}>{data?.shippingStreet ? data?.shippingStreet : ' - '}</Text> | |
| 378 | + } | |
| 379 | + </GridItem> | |
| 380 | + <GridItem colSpan={{ base: 2, md: 1 }} > | |
| 381 | + <Text fontSize="sm" fontWeight="bold" color={'blackAlpha.900'}>Billing Street</Text> | |
| 382 | + { | |
| 383 | + editableField === "billingStreet" ? | |
| 384 | + <> | |
| 385 | + <Input | |
| 386 | + name="billingStreet" | |
| 387 | + onChange={formik.handleChange} | |
| 388 | + onBlur={handleBlur} | |
| 389 | + value={formik.values.billingStreet} | |
| 390 | + borderColor={formik?.errors?.billingStreet && formik?.touched?.billingStreet ? "red.300" : null} | |
| 391 | + autoFocus | |
| 392 | + /> | |
| 393 | + <Text mb='10px' color={'red'}> {formik?.errors.billingStreet && formik?.touched.billingStreet && formik?.errors.billingStreet}</Text> | |
| 394 | + </> | |
| 395 | + : | |
| 396 | + <Text onDoubleClick={() => handleDoubleClick("billingStreet", data?.billingStreet, "Billing Street")}>{data?.billingStreet ? data?.billingStreet : ' - '}</Text> | |
| 397 | + } | |
| 398 | + </GridItem> | |
| 399 | + <GridItem colSpan={{ base: 2, md: 1 }} > | |
| 400 | + <Text fontSize="sm" fontWeight="bold" color={'blackAlpha.900'}>Shipping Street2</Text> | |
| 401 | + { | |
| 402 | + editableField === "shippingStreet2" ? | |
| 403 | + <> | |
| 404 | + <Input | |
| 405 | + name="shippingStreet2" | |
| 406 | + onChange={formik.handleChange} | |
| 407 | + onBlur={handleBlur} | |
| 408 | + value={formik.values.shippingStreet2} | |
| 409 | + borderColor={formik?.errors?.shippingStreet2 && formik?.touched?.shippingStreet2 ? "red.300" : null} | |
| 410 | + autoFocus | |
| 411 | + /> | |
| 412 | + <Text mb='10px' color={'red'}> {formik?.errors.shippingStreet2 && formik?.touched.shippingStreet2 && formik?.errors.shippingStreet2}</Text> | |
| 413 | + </> | |
| 414 | + : | |
| 415 | + <Text onDoubleClick={() => handleDoubleClick("shippingStreet2", data?.shippingStreet2, "Shipping Street2")}>{data?.shippingStreet2 ? data?.shippingStreet2 : ' - '}</Text> | |
| 416 | + } | |
| 417 | + </GridItem> | |
| 418 | + <GridItem colSpan={{ base: 2, md: 1 }} > | |
| 419 | + <Text fontSize="sm" fontWeight="bold" color={'blackAlpha.900'}>Billing Street2</Text> | |
| 420 | + { | |
| 421 | + editableField === "billingStreet2" ? | |
| 422 | + <> | |
| 423 | + <Input | |
| 424 | + name="billingStreet2" | |
| 425 | + onChange={formik.handleChange} | |
| 426 | + onBlur={handleBlur} | |
| 427 | + value={formik.values.billingStreet2} | |
| 428 | + borderColor={formik?.errors?.billingStreet2 && formik?.touched?.billingStreet2 ? "red.300" : null} | |
| 429 | + autoFocus | |
| 430 | + /> | |
| 431 | + <Text mb='10px' color={'red'}> {formik?.errors.billingStreet2 && formik?.touched.billingStreet2 && formik?.errors.billingStreet2}</Text> | |
| 432 | + </> | |
| 433 | + : | |
| 434 | + <Text onDoubleClick={() => handleDoubleClick("billingStreet2", data?.billingStreet2, "Billing Street2")}>{data?.billingStreet2 ? data?.billingStreet2 : ' - '}</Text> | |
| 435 | + } | |
| 436 | + </GridItem> | |
| 437 | + <GridItem colSpan={{ base: 2, md: 1 }} > | |
| 438 | + <Text fontSize="sm" fontWeight="bold" color={'blackAlpha.900'}>Shipping Street3</Text> | |
| 439 | + { | |
| 440 | + editableField === "shippingStreet3" ? | |
| 441 | + <> | |
| 442 | + <Input | |
| 443 | + name="shippingStreet3" | |
| 444 | + onChange={formik.handleChange} | |
| 445 | + onBlur={handleBlur} | |
| 446 | + value={formik.values.shippingStreet3} | |
| 447 | + borderColor={formik?.errors?.shippingStreet3 && formik?.touched?.shippingStreet3 ? "red.300" : null} | |
| 448 | + autoFocus | |
| 449 | + /> | |
| 450 | + <Text mb='10px' color={'red'}> {formik?.errors.shippingStreet3 && formik?.touched.shippingStreet3 && formik?.errors.shippingStreet3}</Text> | |
| 451 | + </> | |
| 452 | + : | |
| 453 | + <Text onDoubleClick={() => handleDoubleClick("shippingStreet3", data?.shippingStreet3, "Shipping Street3")}>{data?.shippingStreet3 ? data?.shippingStreet3 : ' - '}</Text> | |
| 454 | + } | |
| 455 | + </GridItem> | |
| 456 | + <GridItem colSpan={{ base: 2, md: 1 }} > | |
| 457 | + <Text fontSize="sm" fontWeight="bold" color={'blackAlpha.900'}>Billing Street3</Text> | |
| 458 | + { | |
| 459 | + editableField === "billingStreet3" ? | |
| 460 | + <> | |
| 461 | + <Input | |
| 462 | + name="billingStreet3" | |
| 463 | + onChange={formik.handleChange} | |
| 464 | + onBlur={handleBlur} | |
| 465 | + value={formik.values.billingStreet3} | |
| 466 | + borderColor={formik?.errors?.billingStreet3 && formik?.touched?.billingStreet3 ? "red.300" : null} | |
| 467 | + autoFocus | |
| 468 | + /> | |
| 469 | + <Text mb='10px' color={'red'}> {formik?.errors.billingStreet3 && formik?.touched.billingStreet3 && formik?.errors.billingStreet3}</Text> | |
| 470 | + </> | |
| 471 | + : | |
| 472 | + <Text onDoubleClick={() => handleDoubleClick("billingStreet3", data?.billingStreet3, "Billing Street3")}>{data?.billingStreet3 ? data?.billingStreet3 : ' - '}</Text> | |
| 473 | + } | |
| 474 | + </GridItem> | |
| 475 | + <GridItem colSpan={{ base: 2, md: 1 }} > | |
| 476 | + <Text fontSize="sm" fontWeight="bold" color={'blackAlpha.900'}>Shipping Street4</Text> | |
| 477 | + { | |
| 478 | + editableField === "shippingStreet4" ? | |
| 479 | + <> | |
| 480 | + <Input | |
| 481 | + name="shippingStreet4" | |
| 482 | + onChange={formik.handleChange} | |
| 483 | + onBlur={handleBlur} | |
| 484 | + value={formik.values.shippingStreet4} | |
| 485 | + borderColor={formik?.errors?.shippingStreet4 && formik?.touched?.shippingStreet4 ? "red.300" : null} | |
| 486 | + autoFocus | |
| 487 | + /> | |
| 488 | + <Text mb='10px' color={'red'}> {formik?.errors.shippingStreet4 && formik?.touched.shippingStreet4 && formik?.errors.shippingStreet4}</Text> | |
| 489 | + </> | |
| 490 | + : | |
| 491 | + <Text onDoubleClick={() => handleDoubleClick("shippingStreet4", data?.shippingStreet4, "Shipping Street4")}>{data?.shippingStreet4 ? data?.shippingStreet4 : ' - '}</Text> | |
| 492 | + } | |
| 493 | + </GridItem> | |
| 494 | + <GridItem colSpan={{ base: 2, md: 1 }} > | |
| 495 | + <Text fontSize="sm" fontWeight="bold" color={'blackAlpha.900'}>Billing Street4</Text> | |
| 496 | + { | |
| 497 | + editableField === "billingStreet4" ? | |
| 498 | + <> | |
| 499 | + <Input | |
| 500 | + name="billingStreet4" | |
| 501 | + onChange={formik.handleChange} | |
| 502 | + onBlur={handleBlur} | |
| 503 | + value={formik.values.billingStreet4} | |
| 504 | + borderColor={formik?.errors?.billingStreet4 && formik?.touched?.billingStreet4 ? "red.300" : null} | |
| 505 | + autoFocus | |
| 506 | + /> | |
| 507 | + <Text mb='10px' color={'red'}> {formik?.errors.billingStreet4 && formik?.touched.billingStreet4 && formik?.errors.billingStreet4}</Text> | |
| 508 | + </> | |
| 509 | + : | |
| 510 | + <Text onDoubleClick={() => handleDoubleClick("billingStreet4", data?.billingStreet4, "Billing Street4")}>{data?.billingStreet4 ? data?.billingStreet4 : ' - '}</Text> | |
| 511 | + } | |
| 512 | + </GridItem> | |
| 513 | + <GridItem colSpan={{ base: 2, md: 1 }} > | |
| 514 | + <Text fontSize="sm" fontWeight="bold" color={'blackAlpha.900'}>Shipping Postal Code</Text> | |
| 515 | + { | |
| 516 | + editableField === "shippingPostalcode" ? | |
| 517 | + <> | |
| 518 | + <Input | |
| 519 | + name="shippingPostalcode" | |
| 520 | + onChange={formik.handleChange} | |
| 521 | + onBlur={handleBlur} | |
| 522 | + value={formik.values.shippingPostalcode} | |
| 523 | + borderColor={formik?.errors?.shippingPostalcode && formik?.touched?.shippingPostalcode ? "red.300" : null} | |
| 524 | + autoFocus | |
| 525 | + /> | |
| 526 | + <Text mb='10px' color={'red'}> {formik?.errors.shippingPostalcode && formik?.touched.shippingPostalcode && formik?.errors.shippingPostalcode}</Text> | |
| 527 | + </> | |
| 528 | + : | |
| 529 | + <Text onDoubleClick={() => handleDoubleClick("shippingPostalcode", data?.shippingPostalcode, "Shipping Postal Code")}>{data?.shippingPostalcode ? data?.shippingPostalcode : ' - '}</Text> | |
| 530 | + } | |
| 531 | + </GridItem> | |
| 532 | + <GridItem colSpan={{ base: 2, md: 1 }} > | |
| 533 | + <Text fontSize="sm" fontWeight="bold" color={'blackAlpha.900'}>Billing Postal Code</Text> | |
| 534 | + { | |
| 535 | + editableField === "billingPostalcode" ? | |
| 536 | + <> | |
| 537 | + <Input | |
| 538 | + name="billingPostalcode" | |
| 539 | + onChange={formik.handleChange} | |
| 540 | + onBlur={handleBlur} | |
| 541 | + value={formik.values.billingPostalcode} | |
| 542 | + borderColor={formik?.errors?.billingPostalcode && formik?.touched?.billingPostalcode ? "red.300" : null} | |
| 543 | + autoFocus | |
| 544 | + /> | |
| 545 | + <Text mb='10px' color={'red'}> {formik?.errors.billingPostalcode && formik?.touched.billingPostalcode && formik?.errors.billingPostalcode}</Text> | |
| 546 | + </> | |
| 547 | + : | |
| 548 | + <Text onDoubleClick={() => handleDoubleClick("billingPostalcode", data?.billingPostalcode, "Billing Postal Code")}>{data?.billingPostalcode ? data?.billingPostalcode : ' - '}</Text> | |
| 549 | + } | |
| 550 | + </GridItem> | |
| 551 | + <GridItem colSpan={{ base: 2, md: 1 }} > | |
| 552 | + <Text fontSize="sm" fontWeight="bold" color={'blackAlpha.900'}>Shipping City | |
| 553 | + </Text> | |
| 554 | + { | |
| 555 | + editableField === "shippingCity" ? | |
| 556 | + <> | |
| 557 | + <Input | |
| 558 | + name="shippingCity" | |
| 559 | + onChange={formik.handleChange} | |
| 560 | + onBlur={handleBlur} | |
| 561 | + value={formik.values.shippingCity} | |
| 562 | + borderColor={formik?.errors?.shippingCity && formik?.touched?.shippingCity ? "red.300" : null} | |
| 563 | + autoFocus | |
| 564 | + /> | |
| 565 | + <Text mb='10px' color={'red'}> {formik?.errors.shippingCity && formik?.touched.shippingCity && formik?.errors.shippingCity}</Text> | |
| 566 | + </> | |
| 567 | + : | |
| 568 | + <Text onDoubleClick={() => handleDoubleClick("shippingCity", data?.shippingCity, "Shipping City")}>{data?.shippingCity ? data?.shippingCity : ' - '}</Text> | |
| 569 | + } | |
| 570 | + </GridItem> | |
| 571 | + <GridItem colSpan={{ base: 2, md: 1 }} > | |
| 572 | + <Text fontSize="sm" fontWeight="bold" color={'blackAlpha.900'}>Billing City | |
| 573 | + </Text> | |
| 574 | + { | |
| 575 | + editableField === "billingCity" ? | |
| 576 | + <> | |
| 577 | + <Input | |
| 578 | + name="billingCity" | |
| 579 | + onChange={formik.handleChange} | |
| 580 | + onBlur={handleBlur} | |
| 581 | + value={formik.values.billingCity} | |
| 582 | + borderColor={formik?.errors?.billingCity && formik?.touched?.billingCity ? "red.300" : null} | |
| 583 | + autoFocus | |
| 584 | + /> | |
| 585 | + <Text mb='10px' color={'red'}> {formik?.errors.billingCity && formik?.touched.billingCity && formik?.errors.billingCity}</Text> | |
| 586 | + </> | |
| 587 | + : | |
| 588 | + <Text onDoubleClick={() => handleDoubleClick("billingCity", data?.billingCity, "Billing City")}>{data?.billingCity ? data?.billingCity : ' - '}</Text> | |
| 589 | + } | |
| 590 | + </GridItem> | |
| 591 | + <GridItem colSpan={{ base: 2, md: 1 }} > | |
| 592 | + <Text fontSize="sm" fontWeight="bold" color={'blackAlpha.900'}>Shipping State</Text> | |
| 593 | + { | |
| 594 | + editableField === "shippingState" ? | |
| 595 | + <> | |
| 596 | + <Input | |
| 597 | + name="shippingState" | |
| 598 | + onChange={formik.handleChange} | |
| 599 | + onBlur={handleBlur} | |
| 600 | + value={formik.values.shippingState} | |
| 601 | + borderColor={formik?.errors?.shippingState && formik?.touched?.shippingState ? "red.300" : null} | |
| 602 | + autoFocus | |
| 603 | + /> | |
| 604 | + <Text mb='10px' color={'red'}> {formik?.errors.shippingState && formik?.touched.shippingState && formik?.errors.shippingState}</Text> | |
| 605 | + </> | |
| 606 | + : | |
| 607 | + <Text onDoubleClick={() => handleDoubleClick("shippingState", data?.shippingState, "Shipping State")}>{data?.shippingState ? data?.shippingState : ' - '}</Text> | |
| 608 | + } | |
| 609 | + </GridItem> | |
| 610 | + <GridItem colSpan={{ base: 2, md: 1 }} > | |
| 611 | + <Text fontSize="sm" fontWeight="bold" color={'blackAlpha.900'}>Billing State</Text> | |
| 612 | + { | |
| 613 | + editableField === "billingState" ? | |
| 614 | + <> | |
| 615 | + <Input | |
| 616 | + name="billingState" | |
| 617 | + onChange={formik.handleChange} | |
| 618 | + onBlur={handleBlur} | |
| 619 | + value={formik.values.billingState} | |
| 620 | + borderColor={formik?.errors?.billingState && formik?.touched?.billingState ? "red.300" : null} | |
| 621 | + autoFocus | |
| 622 | + /> | |
| 623 | + <Text mb='10px' color={'red'}> {formik?.errors.billingState && formik?.touched.billingState && formik?.errors.billingState}</Text> | |
| 624 | + </> | |
| 625 | + : | |
| 626 | + <Text onDoubleClick={() => handleDoubleClick("billingState", data?.billingState, "Billing State")}>{data?.billingState ? data?.billingState : ' - '}</Text> | |
| 627 | + } | |
| 628 | + </GridItem> | |
| 629 | + <GridItem colSpan={{ base: 2, md: 1 }} > | |
| 630 | + <Text fontSize="sm" fontWeight="bold" color={'blackAlpha.900'}>Shipping Country</Text> | |
| 631 | + { | |
| 632 | + editableField === "shippingCountry" ? | |
| 633 | + <> | |
| 634 | + <Input | |
| 635 | + name="shippingCountry" | |
| 636 | + onChange={formik.handleChange} | |
| 637 | + onBlur={handleBlur} | |
| 638 | + value={formik.values.shippingCountry} | |
| 639 | + borderColor={formik?.errors?.shippingCountry && formik?.touched?.shippingCountry ? "red.300" : null} | |
| 640 | + autoFocus | |
| 641 | + /> | |
| 642 | + <Text mb='10px' color={'red'}> {formik?.errors.shippingCountry && formik?.touched.shippingCountry && formik?.errors.shippingCountry}</Text> | |
| 643 | + </> | |
| 644 | + : | |
| 645 | + <Text onDoubleClick={() => handleDoubleClick("shippingCountry", data?.shippingCountry, "Shipping Country")}>{data?.shippingCountry ? data?.shippingCountry : ' - '}</Text> | |
| 646 | + } | |
| 647 | + </GridItem> | |
| 648 | + <GridItem colSpan={{ base: 2, md: 1 }} > | |
| 649 | + <Text fontSize="sm" fontWeight="bold" color={'blackAlpha.900'}>Billing Country</Text> | |
| 650 | + { | |
| 651 | + editableField === "billingCountry" ? | |
| 652 | + <> | |
| 653 | + <Input | |
| 654 | + name="billingCountry" | |
| 655 | + onChange={formik.handleChange} | |
| 656 | + onBlur={handleBlur} | |
| 657 | + value={formik.values.billingCountry} | |
| 658 | + borderColor={formik?.errors?.billingCountry && formik?.touched?.billingCountry ? "red.300" : null} | |
| 659 | + autoFocus | |
| 660 | + /> | |
| 661 | + <Text mb='10px' color={'red'}> {formik?.errors.billingCountry && formik?.touched.billingCountry && formik?.errors.billingCountry}</Text> | |
| 662 | + </> | |
| 663 | + : | |
| 664 | + <Text onDoubleClick={() => handleDoubleClick("billingCountry", data?.billingCountry, "Billing Country")}>{data?.billingCountry ? data?.billingCountry : ' - '}</Text> | |
| 665 | + } | |
| 666 | + </GridItem> | |
| 667 | + <GridItem colSpan={{ base: 2, md: 1 }} > | |
| 668 | + <Text fontSize="sm" fontWeight="bold" color={'blackAlpha.900'}>Type</Text> | |
| 669 | + { | |
| 670 | + editableField === "type" ? | |
| 671 | + <> | |
| 672 | + <Select | |
| 673 | + value={formik?.values.type} | |
| 674 | + name="type" | |
| 675 | + onChange={formik?.handleChange} | |
| 676 | + onBlur={handleBlur} | |
| 677 | + fontWeight='500' | |
| 678 | + placeholder={'Select Type'} | |
| 679 | + borderColor={formik?.errors.type && formik?.touched.type ? "red.300" : null} | |
| 680 | + > | |
| 681 | + <option value="Analyst">Analyst</option> | |
| 682 | + <option value="Competitor">Competitor </option> | |
| 683 | + <option value="Customer">Customer</option> | |
| 684 | + <option value="Integrator">Integrator</option> | |
| 685 | + <option value="Investor">Investor </option> | |
| 686 | + <option value="Partner">Partner</option> | |
| 687 | + <option value="Press">Press</option> | |
| 688 | + <option value="Prospect">Prospect</option> | |
| 689 | + <option value="Reseller">Reseller</option> | |
| 690 | + <option value="Other">Other</option> | |
| 691 | + </Select> | |
| 692 | + <Text mb='10px' color={'red'}> {formik?.errors.type && formik?.touched.type && formik?.errors.type}</Text> | |
| 693 | + </> | |
| 694 | + : | |
| 695 | + <Text onDoubleClick={() => handleDoubleClick("type", data?.type, "Type")}>{data?.type ? data?.type : ' - '}</Text> | |
| 696 | + } | |
| 697 | + </GridItem> | |
| 698 | + <GridItem colSpan={{ base: 2, md: 1 }} > | |
| 699 | + <Text fontSize="sm" fontWeight="bold" color={'blackAlpha.900'}>Industry</Text> | |
| 700 | + { | |
| 701 | + editableField === "industry" ? | |
| 702 | + <> | |
| 703 | + <Select | |
| 704 | + value={formik?.values.industry} | |
| 705 | + name="industry" | |
| 706 | + onChange={formik?.handleChange} | |
| 707 | + onBlur={handleBlur} | |
| 708 | + fontWeight='500' | |
| 709 | + placeholder={'Select Industry'} | |
| 710 | + borderColor={formik?.errors.industry && formik?.touched.industry ? "red.300" : null} | |
| 711 | + > | |
| 712 | + <option value="Apparel">Apparel</option> | |
| 713 | + <option value="Banking">Banking </option> | |
| 714 | + <option value="Biotechnology">Biotechnology</option> | |
| 715 | + <option value="Chemicals">Chemicals</option> | |
| 716 | + <option value="Communications">Communications </option> | |
| 717 | + <option value="Construction">Construction</option> | |
| 718 | + <option value="Consulting">Consulting</option> | |
| 719 | + <option value="Education">Education</option> | |
| 720 | + <option value="Electronics">Electronics</option> | |
| 721 | + <option value="Energy">Energy</option> | |
| 722 | + <option value="Engineering">Engineering</option> | |
| 723 | + <option value="Entertainment">Entertainment</option> | |
| 724 | + <option value="Finance">Finance</option> | |
| 725 | + <option value="Government">Government</option> | |
| 726 | + <option value="Healthcare">Healthcare</option> | |
| 727 | + <option value="Hospitality">Hospitality</option> | |
| 728 | + <option value="Insurance">Insurance</option> | |
| 729 | + <option value="Machinery">Machinery</option> | |
| 730 | + <option value="Manufacturing">Manufacturing</option> | |
| 731 | + <option value="Media">Media</option> | |
| 732 | + <option value="Not For Profit">Not For Profit</option> | |
| 733 | + <option value="Recreation">Recreation</option> | |
| 734 | + <option value="Retail">Retail</option> | |
| 735 | + <option value="Shipping">Shipping</option> | |
| 736 | + <option value="Technology">Technology</option> | |
| 737 | + <option value="Telecommunications">Telecommunications</option> | |
| 738 | + <option value="Transportation">Transportation</option> | |
| 739 | + <option value="Utilities">Utilities</option> | |
| 740 | + <option value="Other">Other</option> | |
| 741 | + </Select> | |
| 742 | + <Text mb='10px' color={'red'}> {formik?.errors.industry && formik?.touched.industry && formik?.errors.industry}</Text> | |
| 743 | + </> | |
| 744 | + : | |
| 745 | + <Text onDoubleClick={() => handleDoubleClick("industry", data?.industry, "Industry")}>{data?.industry ? data?.industry : ' - '}</Text> | |
| 746 | + } | |
| 747 | + </GridItem> | |
| 748 | + <GridItem colSpan={{ base: 2, md: 1 }} > | |
| 749 | + <Text fontSize="sm" fontWeight="bold" color={'blackAlpha.900'}> Rating </Text> | |
| 750 | + <Text >{data?.rating ? data?.rating : ' - '}</Text> | |
| 751 | + </GridItem> | |
| 752 | + <GridItem colSpan={{ base: 2, md: 1 }} > | |
| 753 | + <Text fontSize="sm" fontWeight="bold" color={'blackAlpha.900'}>SIC Code</Text> | |
| 754 | + { | |
| 755 | + editableField === "SICCode" ? | |
| 756 | + <> | |
| 757 | + <Input | |
| 758 | + name="SICCode" | |
| 759 | + onChange={formik.handleChange} | |
| 760 | + onBlur={handleBlur} | |
| 761 | + value={formik.values.SICCode} | |
| 762 | + borderColor={formik?.errors?.SICCode && formik?.touched?.SICCode ? "red.300" : null} | |
| 763 | + autoFocus | |
| 764 | + /> | |
| 765 | + <Text mb='10px' color={'red'}> {formik?.errors.SICCode && formik?.touched.SICCode && formik?.errors.SICCode}</Text> | |
| 766 | + </> | |
| 767 | + : | |
| 768 | + <Text onDoubleClick={() => handleDoubleClick("SICCode", data?.SICCode, "SIC Code")}>{data?.SICCode ? data?.SICCode : ' - '}</Text> | |
| 769 | + } | |
| 770 | + </GridItem> | |
| 771 | + <GridItem colSpan={{ base: 2, md: 1 }} > | |
| 772 | + <Text fontSize="sm" fontWeight="bold" color={'blackAlpha.900'}> Assigned User </Text> | |
| 773 | + {/* <Text>{data?.assignUserName ? data?.assignUserName : ' - '}</Text> */} | |
| 774 | + { | |
| 775 | + data?.assignUser ? | |
| 776 | + <Link to={user.role === 'superAdmin' && `/userView/${data?.assignUser}`}> | |
| 777 | + <Text color={user.role === 'superAdmin' ? 'blue.500' : 'blackAlpha.900'} sx={{ '&:hover': { color: user.role === 'superAdmin' ? 'blue.500' : 'blackAlpha.900', textDecoration: user.role === 'superAdmin' ? 'underline' : 'none' } }} style={{ cursor: "pointer" }}>{data?.assignUserName ? data?.assignUserName : ' - '}</Text> | |
| 778 | + </Link> | |
| 779 | + : | |
| 780 | + <Text color={user.role === 'superAdmin' ? 'blue.500' : 'blackAlpha.900'} sx={{ '&:hover': { color: user.role === 'superAdmin' ? 'blue.500' : 'blackAlpha.900', textDecoration: user.role === 'superAdmin' ? 'underline' : 'none' } }}>{data?.assignUserName ? data?.assignUserName : ' - '}</Text> | |
| 781 | + | |
| 782 | + } | |
| 783 | + </GridItem> | |
| 784 | + <GridItem colSpan={{ base: 2, md: 1 }} > | |
| 785 | + <Text fontSize="sm" fontWeight="bold" color={'blackAlpha.900'}> Member Of </Text> | |
| 786 | + { | |
| 787 | + data?.memberOf ? | |
| 788 | + <Link to={`/accountView/${data?.memberOf}`}> | |
| 789 | + <Text color={permission?.view ? 'blue.500' : 'blackAlpha.900'} sx={{ '&:hover': { color: permission?.view ? 'blue.500' : 'blackAlpha.900', textDecoration: permission?.view ? 'underline' : 'none' } }} style={{ cursor: "pointer" }}>{data?.memberOfName ? data?.memberOfName : ' - '}</Text> | |
| 790 | + </Link> | |
| 791 | + : | |
| 792 | + <Text color={permission?.view ? 'blue.500' : 'blackAlpha.900'} sx={{ '&:hover': { color: permission?.view ? 'blue.500' : 'blackAlpha.900', textDecoration: permission?.view ? 'underline' : 'none' } }}>{data?.memberOfName ? data?.memberOfName : ' - '}</Text> | |
| 793 | + | |
| 794 | + } | |
| 795 | + </GridItem> | |
| 796 | + | |
| 797 | + {/* <GridItem colSpan={{ base: 2, md: 1 }} > | |
| 798 | + <Text fontSize="sm" fontWeight="bold" color={'blackAlpha.900'}> Task reminder </Text> | |
| 799 | + <Text>{data?.reminder ? data?.reminder : ' - '}</Text> | |
| 800 | + </GridItem> | |
| 801 | + <GridItem colSpan={{ base: 2, md: 1 }} > | |
| 802 | + <Text fontSize="sm" fontWeight="bold" color={'blackAlpha.900'}> Assign To </Text> | |
| 803 | + <Link to={data?.assignTo ? contactAccess?.view && `/contactView/${data?.assignTo}` : leadAccess?.view && `/leadView/${data?.assignToLead}`}> | |
| 804 | + <Text color={(data?.category === 'contact' && (contactAccess?.view || user?.role === 'superAdmin')) ? 'brand.600' : (leadAccess?.view || user?.role === 'superAdmin' && data?.category === 'lead') ? 'brand.600' : 'blackAlpha.900'} sx={{ '&:hover': { color: 'blue.500', textDecoration: 'underline' } }}>{data?.assignToName ? data?.assignToName : ' - '}</Text> | |
| 805 | + </Link> | |
| 806 | + </GridItem> | |
| 807 | + <GridItem colSpan={{ base: 2, md: 1 }} > | |
| 808 | + <Text fontSize="sm" fontWeight="bold" color={'blackAlpha.900'}> Task createBy </Text> | |
| 809 | + <Text>{data?.createByName ? data?.createByName : ' - '}</Text> | |
| 810 | + </GridItem> | |
| 811 | + <GridItem colSpan={{ base: 2 }} > | |
| 812 | + <Text fontSize="sm" fontWeight="bold" color={'blackAlpha.900'}> Task Description</Text> | |
| 813 | + { | |
| 814 | + editableField === "description" ? | |
| 815 | + <> | |
| 816 | + <Input | |
| 817 | + id="text" | |
| 818 | + name="description" | |
| 819 | + type="text" | |
| 820 | + onChange={formik.handleChange} | |
| 821 | + onBlur={handleBlur} | |
| 822 | + value={formik.values.description} | |
| 823 | + autoFocus | |
| 824 | + borderColor={formik?.errors?.description && formik?.touched?.description ? "red.300" : null} | |
| 825 | + /> | |
| 826 | + <Text mb='10px' color={'red'}> {formik?.errors.description && formik?.touched.description && formik?.errors.description}</Text> | |
| 827 | + </> | |
| 828 | + : | |
| 829 | + <Text onDoubleClick={() => handleDoubleClick("description", data?.description)}>{data?.description ? data?.description : ' - '}</Text> | |
| 830 | + } | |
| 831 | + </GridItem> | |
| 832 | + <GridItem colSpan={{ base: 2 }} > | |
| 833 | + <Text fontSize="sm" fontWeight="bold" color={'blackAlpha.900'}> Task notes </Text> | |
| 834 | + { | |
| 835 | + editableField === "notes" ? | |
| 836 | + <> | |
| 837 | + <Input | |
| 838 | + id="text" | |
| 839 | + name="notes" | |
| 840 | + type="text" | |
| 841 | + onChange={formik.handleChange} | |
| 842 | + onBlur={handleBlur} | |
| 843 | + value={formik.values.notes} | |
| 844 | + autoFocus | |
| 845 | + borderColor={formik?.errors?.notes && formik?.touched?.notes ? "red.300" : null} | |
| 846 | + /> | |
| 847 | + <Text mb='10px' color={'red'}> {formik?.errors.notes && formik?.touched.notes && formik?.errors.notes}</Text> | |
| 848 | + | |
| 849 | + </> | |
| 850 | + : | |
| 851 | + <Text onDoubleClick={() => handleDoubleClick("notes", data?.notes)}>{data?.notes ? data?.notes : ' - '}</Text> | |
| 852 | + } | |
| 853 | + </GridItem> */} | |
| 854 | + </Grid> | |
| 855 | + </Card> | |
| 856 | + </GridItem> | |
| 857 | + | |
| 858 | + </Grid> | |
| 859 | + { | |
| 860 | + (permission?.update || permission?.delete || user?.role === 'superAdmin') && <Card mt={3}> | |
| 861 | + <Grid templateColumns="repeat(6, 1fr)" gap={1}> | |
| 862 | + <GridItem colStart={6} > | |
| 863 | + <Flex justifyContent={"right"}> | |
| 864 | + {(permission?.update || user?.role === 'superAdmin') && <Button size="sm" onClick={() => { setEdit(true); setType("edit") }} leftIcon={<EditIcon />} mr={2.5} variant="outline" colorScheme="green">Edit</Button>} | |
| 865 | + {(permission?.delete || user?.role === 'superAdmin') && <Button size="sm" style={{ background: 'red.800' }} onClick={() => setDeleteManyModel(true)} leftIcon={<DeleteIcon />} colorScheme="red" >Delete</Button>} | |
| 866 | + </Flex> | |
| 867 | + </GridItem> | |
| 868 | + </Grid> | |
| 869 | + </Card> | |
| 870 | + } | |
| 871 | + <AddEdit isOpen={edit} size="lg" onClose={() => setEdit(false)} viewClose={onClose} selectedId={id?.event ? id?.event?._def?.extendedProps?._id : id} type={type} /> | |
| 872 | + <CommonDeleteModel isOpen={deleteManyModel} onClose={() => setDeleteManyModel(false)} type='Account' handleDeleteData={handleDeleteAccount} ids={[id]} /> | |
| 873 | + </div > | |
| 874 | + ) | |
| 875 | +} | |
| 876 | + | |
| 877 | +export default View |
+150-0Client/src/views/admin/account/components/AccountAdvanceSearch.js
| @@ -0,0 +1,150 @@ | ||
| 1 | +import React from 'react'; | |
| 2 | +import { useFormik } from "formik"; | |
| 3 | +import * as yup from "yup"; | |
| 4 | +import { Modal, ModalBody, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalOverlay, Grid, GridItem, Input, FormLabel, Select, Text, Button, } from '@chakra-ui/react'; | |
| 5 | +import Spinner from 'components/spinner/Spinner'; | |
| 6 | +import { setSearchValue, getSearchData, setGetTagValues } from '../../../../redux/slices/advanceSearchSlice'; | |
| 7 | +import { useDispatch } from 'react-redux'; | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | +const OpprtunityAdvanceSearch = (props) => { | |
| 12 | + const { state, allData, advanceSearch, setAdvanceSearch, isLoding, setSearchedData, setDisplaySearchData, setSearchClear, setSearchbox } = props; | |
| 13 | + | |
| 14 | + const dispatch = useDispatch(); | |
| 15 | + const initialValues = { | |
| 16 | + name: '', | |
| 17 | + officePhone: '', | |
| 18 | + fax: '', | |
| 19 | + emailAddress: '', | |
| 20 | + } | |
| 21 | + const validationSchema = yup.object({ | |
| 22 | + name: yup.string(), | |
| 23 | + officePhone: yup.string(), | |
| 24 | + fax: yup.string(), | |
| 25 | + emailAddress: yup.string() | |
| 26 | + }); | |
| 27 | + const formik = useFormik({ | |
| 28 | + initialValues: initialValues, | |
| 29 | + validationSchema: validationSchema, | |
| 30 | + onSubmit: (values, { resetForm }) => { | |
| 31 | + dispatch(setSearchValue(values)) | |
| 32 | + dispatch(getSearchData({ values: values, allData: allData, type: 'Account' })) | |
| 33 | + // const searchResult = allData?.filter( | |
| 34 | + // (item) => | |
| 35 | + // (!values?.senderName || (item?.senderName && item?.senderName.toLowerCase().includes(values?.senderName?.toLowerCase()))) && | |
| 36 | + // (!values?.realetedTo || (values.realetedTo === "contact" ? item.createBy : item.createByLead)) && | |
| 37 | + // (!values?.createByName || (item?.createByName && item?.createByName.toLowerCase().includes(values?.createByName?.toLowerCase()))) | |
| 38 | + // ) | |
| 39 | + // let getValue = [values.senderName, values?.realetedTo, values?.createByName].filter(value => value); | |
| 40 | + const getValue = [ | |
| 41 | + { | |
| 42 | + name: ["name"], | |
| 43 | + value: values.name | |
| 44 | + }, | |
| 45 | + { | |
| 46 | + name: ["officePhone"], | |
| 47 | + value: values.officePhone | |
| 48 | + }, | |
| 49 | + { | |
| 50 | + name: ["fax"], | |
| 51 | + value: values.fax | |
| 52 | + }, | |
| 53 | + { | |
| 54 | + name: ["emailAddress"], | |
| 55 | + value: values.emailAddress | |
| 56 | + }, | |
| 57 | + ] | |
| 58 | + dispatch(setGetTagValues(getValue.filter(item => item.value))) | |
| 59 | + setDisplaySearchData(true) | |
| 60 | + setAdvanceSearch(false) | |
| 61 | + resetForm(); | |
| 62 | + setSearchbox(''); | |
| 63 | + } | |
| 64 | + }) | |
| 65 | + | |
| 66 | + const { errors, touched, values, handleBlur, handleChange, handleSubmit, setFieldValue, resetForm, dirty } = formik; | |
| 67 | + | |
| 68 | + return ( | |
| 69 | + <> | |
| 70 | + <Modal onClose={() => { setAdvanceSearch(false); resetForm() }} isOpen={advanceSearch} isCentered> | |
| 71 | + <ModalOverlay /> | |
| 72 | + <ModalContent> | |
| 73 | + <ModalHeader>Advance Search</ModalHeader> | |
| 74 | + <ModalCloseButton onClick={() => { setAdvanceSearch(false); resetForm() }} /> | |
| 75 | + <ModalBody> | |
| 76 | + <Grid templateColumns="repeat(12, 1fr)" mb={3} gap={2}> | |
| 77 | + <GridItem colSpan={{ base: 12, md: 6 }}> | |
| 78 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='600' color={"#000"} mb="0" mt={2}> | |
| 79 | + Account Name | |
| 80 | + </FormLabel> | |
| 81 | + <Input | |
| 82 | + fontSize='sm' | |
| 83 | + onChange={handleChange} onBlur={handleBlur} | |
| 84 | + value={values?.name} | |
| 85 | + name="name" | |
| 86 | + placeholder='Enter Account Name' | |
| 87 | + fontWeight='500' | |
| 88 | + /> | |
| 89 | + <Text mb='10px' color={'red'}> {errors.name && touched.name && errors.name}</Text> | |
| 90 | + </GridItem> | |
| 91 | + <GridItem colSpan={{ base: 12, md: 6 }}> | |
| 92 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='600' color={"#000"} mb="0" mt={2}> | |
| 93 | + Office Phone | |
| 94 | + </FormLabel> | |
| 95 | + <Input | |
| 96 | + fontSize='sm' | |
| 97 | + onChange={handleChange} onBlur={handleBlur} | |
| 98 | + value={values?.officePhone} | |
| 99 | + name="officePhone" | |
| 100 | + type="number" | |
| 101 | + placeholder='Enter Office Phone' | |
| 102 | + fontWeight='500' | |
| 103 | + /> | |
| 104 | + <Text mb='10px' color={'red'}> {errors.officePhone && touched.officePhone && errors.officePhone}</Text> | |
| 105 | + </GridItem> | |
| 106 | + <GridItem colSpan={{ base: 12, md: 6 }}> | |
| 107 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='600' color={"#000"} mb="0" mt={2}> | |
| 108 | + Fax | |
| 109 | + </FormLabel> | |
| 110 | + <Input | |
| 111 | + fontSize='sm' | |
| 112 | + onChange={handleChange} onBlur={handleBlur} | |
| 113 | + value={values?.fax} | |
| 114 | + name="fax" | |
| 115 | + placeholder='Enter Fax' | |
| 116 | + type="number" | |
| 117 | + fontWeight='500' | |
| 118 | + /> | |
| 119 | + <Text mb='10px' color={'red'}> {errors.fax && touched.fax && errors.fax}</Text> | |
| 120 | + </GridItem> | |
| 121 | + <GridItem colSpan={{ base: 12, md: 6 }}> | |
| 122 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='600' color={"#000"} mb="0" mt={2}> | |
| 123 | + Email Address | |
| 124 | + </FormLabel> | |
| 125 | + <Input | |
| 126 | + fontSize='sm' | |
| 127 | + onChange={handleChange} onBlur={handleBlur} | |
| 128 | + value={values?.emailAddress} | |
| 129 | + name="emailAddress" | |
| 130 | + placeholder='Enter Email Address' | |
| 131 | + type="email" | |
| 132 | + fontWeight='500' | |
| 133 | + /> | |
| 134 | + <Text mb='10px' color={'red'}> {errors.emailAddress && touched.emailAddress && errors.emailAddress}</Text> | |
| 135 | + </GridItem> | |
| 136 | + | |
| 137 | + | |
| 138 | + </Grid> | |
| 139 | + </ModalBody> | |
| 140 | + <ModalFooter> | |
| 141 | + <Button size="sm" variant="brand" mr={2} onClick={handleSubmit} disabled={isLoding || !dirty ? true : false} >{isLoding ? <Spinner /> : 'Search'}</Button> | |
| 142 | + <Button size="sm" variant="outline" colorScheme="red" onClick={() => resetForm()}>Clear</Button> | |
| 143 | + </ModalFooter> | |
| 144 | + </ModalContent> | |
| 145 | + </Modal> | |
| 146 | + </> | |
| 147 | + ) | |
| 148 | +} | |
| 149 | + | |
| 150 | +export default OpprtunityAdvanceSearch |
+237-0Client/src/views/admin/account/components/AccountImport.js
| @@ -0,0 +1,237 @@ | ||
| 1 | +import React, { useState, useEffect } from 'react' | |
| 2 | +import { useLocation, useNavigate } from 'react-router-dom'; | |
| 3 | +import Papa from 'papaparse'; | |
| 4 | +import { | |
| 5 | + Flex, | |
| 6 | + useColorModeValue, | |
| 7 | + Select, | |
| 8 | + Button, | |
| 9 | + Grid, | |
| 10 | + GridItem, | |
| 11 | + Text | |
| 12 | +} from "@chakra-ui/react"; | |
| 13 | +import { useFormik } from "formik"; | |
| 14 | +import { postApi } from 'services/api'; | |
| 15 | +import { toast } from 'react-toastify'; | |
| 16 | +import moment from 'moment'; | |
| 17 | +import ExcelJS from 'exceljs'; | |
| 18 | +import Card from 'components/card/Card'; | |
| 19 | + | |
| 20 | +function AccountImport() { | |
| 21 | + | |
| 22 | + const location = useLocation(); | |
| 23 | + const { fileData, customFields } = location.state || {}; | |
| 24 | + const [importedFileFields, setImportedFileFields] = useState([]); | |
| 25 | + const [importedFileData, setImportedFileData] = useState([]); | |
| 26 | + const [isLoding, setIsLoding] = useState(false); | |
| 27 | + const navigate = useNavigate(); | |
| 28 | + const userId = JSON.parse(localStorage.getItem('user'))._id; | |
| 29 | + const [filterContact, setFilterContact] = useState([]); | |
| 30 | + | |
| 31 | + const columns = [ | |
| 32 | + { Header: 'Fields In Crm', accessor: 'crmFields' }, | |
| 33 | + { Header: 'Fields In File', accessor: 'fileFields' }, | |
| 34 | + ]; | |
| 35 | + | |
| 36 | + | |
| 37 | + const initialFieldValues = Object.fromEntries( | |
| 38 | + (customFields || []).map(field => [field?.name, '']) | |
| 39 | + ); | |
| 40 | + const initialValues = { | |
| 41 | + ...initialFieldValues | |
| 42 | + }; | |
| 43 | + | |
| 44 | + const fieldsInCrm = [ | |
| 45 | + ...customFields?.map((field) => ({ Header: field?.label, accessor: field?.name, type: field?.type, formikType: field?.validations?.find(obj => obj.hasOwnProperty('formikType')) })) | |
| 46 | + ]; | |
| 47 | + | |
| 48 | + const formik = useFormik({ | |
| 49 | + initialValues: initialValues, | |
| 50 | + onSubmit: (values, { resetForm }) => { | |
| 51 | + | |
| 52 | + const opportunityData = importedFileData?.map((item, ind) => { | |
| 53 | + const opportunity = { | |
| 54 | + createdDate: new Date(), | |
| 55 | + deleted: item[values.deleted || "deleted"] || false, | |
| 56 | + createBy: JSON.parse(localStorage.getItem('user'))._id, | |
| 57 | + modifiedBy: JSON.parse(localStorage.getItem('user'))._id, | |
| 58 | + }; | |
| 59 | + | |
| 60 | + fieldsInCrm?.forEach(field => { | |
| 61 | + const selectedField = values[field?.accessor]; | |
| 62 | + const fieldValue = item[selectedField] || ''; | |
| 63 | + | |
| 64 | + if (field?.type?.toLowerCase() === "date") { | |
| 65 | + opportunity[field?.accessor] = moment(fieldValue).isValid() ? fieldValue : ''; | |
| 66 | + } else if (field?.type?.toLowerCase() === "number" && ['positive', 'negative'].includes(field?.formikType?.toLowerCase())) { | |
| 67 | + opportunity[field?.accessor] = parseFloat(fieldValue) || ''; | |
| 68 | + } else if (field?.type?.toLowerCase() === "number") { | |
| 69 | + opportunity[field?.accessor] = parseInt(fieldValue, 10) || ''; | |
| 70 | + } else { | |
| 71 | + opportunity[field?.accessor] = fieldValue; | |
| 72 | + } | |
| 73 | + }); | |
| 74 | + | |
| 75 | + return opportunity; | |
| 76 | + }); | |
| 77 | + | |
| 78 | + AddData(opportunityData); | |
| 79 | + } | |
| 80 | + }); | |
| 81 | + | |
| 82 | + const { errors, touched, values, handleBlur, handleChange, handleSubmit, setFieldValue, resetForm } = formik | |
| 83 | + | |
| 84 | + const AddData = async (opportunity) => { | |
| 85 | + try { | |
| 86 | + setIsLoding(true); | |
| 87 | + let response = await postApi('api/account/addMany', opportunity) | |
| 88 | + if (response.status === 200) { | |
| 89 | + toast.success(`Accounts imported successfully`) | |
| 90 | + resetForm(); | |
| 91 | + navigate('/account'); | |
| 92 | + } | |
| 93 | + } catch (e) { | |
| 94 | + console.error(e); | |
| 95 | + toast.error(`Accounts import failed`) | |
| 96 | + resetForm(); | |
| 97 | + navigate('/account'); | |
| 98 | + } | |
| 99 | + finally { | |
| 100 | + setIsLoding(false) | |
| 101 | + } | |
| 102 | + }; | |
| 103 | + | |
| 104 | + const parseFileData = async (file) => { | |
| 105 | + const reader = new FileReader(); | |
| 106 | + const extension = file.name.split('.').pop().toLowerCase(); | |
| 107 | + | |
| 108 | + reader.onload = async ({ target }) => { | |
| 109 | + | |
| 110 | + if (extension === 'csv') { | |
| 111 | + const csv = Papa.parse(target.result, { | |
| 112 | + header: true, | |
| 113 | + }); | |
| 114 | + const parsedData = csv?.data; | |
| 115 | + | |
| 116 | + if (parsedData && parsedData.length > 0) { | |
| 117 | + setImportedFileData(parsedData); | |
| 118 | + const fileHeadingFields = Object.keys(parsedData[0]); | |
| 119 | + setImportedFileFields(fileHeadingFields); | |
| 120 | + } else { | |
| 121 | + toast.error("Empty or invalid CSV file"); | |
| 122 | + navigate("/account"); | |
| 123 | + } | |
| 124 | + | |
| 125 | + } else if (extension === 'xlsx') { | |
| 126 | + const data = new Uint8Array(target.result); | |
| 127 | + const workbook = new ExcelJS.Workbook(); | |
| 128 | + | |
| 129 | + await workbook.xlsx.load(data); | |
| 130 | + | |
| 131 | + const worksheet = workbook.getWorksheet(1); | |
| 132 | + const jsonData = []; | |
| 133 | + | |
| 134 | + // Iterate over rows and cells | |
| 135 | + worksheet.eachRow({ includeEmpty: true }, (row, rowNumber) => { | |
| 136 | + const rowData = {}; | |
| 137 | + row.eachCell({ includeEmpty: true }, (cell, colNumber) => { | |
| 138 | + rowData[worksheet.getCell(1, colNumber).value] = cell.value; | |
| 139 | + }); | |
| 140 | + jsonData.push(rowData); | |
| 141 | + }); | |
| 142 | + jsonData?.splice(0, 1); | |
| 143 | + setImportedFileData(jsonData); | |
| 144 | + | |
| 145 | + if (jsonData && jsonData.length > 0) { | |
| 146 | + const fileHeadingFields = Object.keys(jsonData[0]); | |
| 147 | + setImportedFileFields(fileHeadingFields); | |
| 148 | + } else { | |
| 149 | + toast.error("Empty or invalid XLSX file"); | |
| 150 | + navigate("/account"); | |
| 151 | + } | |
| 152 | + } | |
| 153 | + }; | |
| 154 | + | |
| 155 | + if (extension === 'csv') { | |
| 156 | + reader.readAsText(file); | |
| 157 | + } else if (extension === 'xlsx') { | |
| 158 | + const blob = new Blob([file]); | |
| 159 | + reader.readAsArrayBuffer(blob); | |
| 160 | + } | |
| 161 | + }; | |
| 162 | + | |
| 163 | + useEffect(() => { | |
| 164 | + if (fileData && fileData.length > 0) { | |
| 165 | + const firstFile = fileData[0]; | |
| 166 | + parseFileData(firstFile); | |
| 167 | + } | |
| 168 | + }, [fileData]); | |
| 169 | + | |
| 170 | + | |
| 171 | + useEffect(() => { | |
| 172 | + const filterContactData = importedFileFields?.filter(field => { | |
| 173 | + const result = fieldsInCrm?.find(data => field === data?.accessor || field === data?.Header); | |
| 174 | + if (result) { | |
| 175 | + setFieldValue(result?.accessor, field); | |
| 176 | + return true; | |
| 177 | + } | |
| 178 | + return false; | |
| 179 | + }); | |
| 180 | + setFilterContact(filterContactData); | |
| 181 | + }, [importedFileFields]); | |
| 182 | + | |
| 183 | + return ( | |
| 184 | + <> | |
| 185 | + <Card overflowY={"auto"} className="importTable"> | |
| 186 | + <Text color={"secondaryGray.900"} | |
| 187 | + fontSize="22px" | |
| 188 | + fontWeight="700" | |
| 189 | + mb='20px' | |
| 190 | + >Import Opprtunities</Text> | |
| 191 | + <Grid templateColumns="repeat(12, 1fr)" mb={3} pb={2} gap={1} borderBottom={'1px solid #e2e8f0'}> | |
| 192 | + { | |
| 193 | + columns.map((column, index) => ( | |
| 194 | + <GridItem key={index} colSpan={{ base: 6 }} fontWeight={'600'} fontSize={{ sm: "14px", lg: "14px" }} color="secondaryGray.900" style={{ textTransform: "uppercase" }}> | |
| 195 | + {column.Header} | |
| 196 | + </GridItem> | |
| 197 | + )) | |
| 198 | + } | |
| 199 | + </Grid> | |
| 200 | + <Grid templateColumns="repeat(12, 1fr)" mb={3} gap={1} overflowY={'auto'}> | |
| 201 | + { | |
| 202 | + fieldsInCrm?.map((item, index) => ( | |
| 203 | + <> | |
| 204 | + <GridItem colSpan={{ base: 6 }} key={item.id} mt='10px'> | |
| 205 | + {item.Header} | |
| 206 | + </GridItem> | |
| 207 | + <GridItem colSpan={{ base: 4 }}> | |
| 208 | + <Select | |
| 209 | + variant="flushed" | |
| 210 | + fontWeight='500' | |
| 211 | + isSearchable | |
| 212 | + value={values[item.accessor]} | |
| 213 | + name={item.accessor} | |
| 214 | + onChange={handleChange} | |
| 215 | + > | |
| 216 | + <option value=''> {filterContact ? filterContact.find((data) => (item.Header === data || item.accessor === data) && data) ? filterContact.find((data) => (item.Header === data || item.accessor === data) && data) : 'Select Field In File' : 'Select Field In File'}</option> | |
| 217 | + { | |
| 218 | + importedFileFields?.map(field => ( | |
| 219 | + <option value={field} key={field}>{field}</option> | |
| 220 | + )) | |
| 221 | + } | |
| 222 | + </Select> | |
| 223 | + </GridItem> | |
| 224 | + </> | |
| 225 | + )) | |
| 226 | + } | |
| 227 | + </Grid> | |
| 228 | + | |
| 229 | + <Flex Flex justifyContent={'end'} mt='5' > | |
| 230 | + <Button size="sm" onClick={() => handleSubmit()} variant="brand">Save</Button> | |
| 231 | + </Flex> | |
| 232 | + </Card> | |
| 233 | + </> | |
| 234 | + ) | |
| 235 | +} | |
| 236 | + | |
| 237 | +export default AccountImport |
+74-0Client/src/views/admin/account/components/ImportModel.js
| @@ -0,0 +1,74 @@ | ||
| 1 | +import { Button, Grid, GridItem, Modal, ModalBody, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalOverlay, Text } from '@chakra-ui/react' | |
| 2 | +import Spinner from 'components/spinner/Spinner' | |
| 3 | +import { useFormik } from 'formik' | |
| 4 | +import { useState } from 'react' | |
| 5 | +import { useNavigate } from 'react-router-dom' | |
| 6 | +import CommonFileUpload from 'components/commonFileUpload' | |
| 7 | + | |
| 8 | +const ImportModal = (props) => { | |
| 9 | + const { onClose, isOpen, text, customFields } = props | |
| 10 | + const [isLoding, setIsLoding] = useState(false) | |
| 11 | + const navigate = useNavigate(); | |
| 12 | + | |
| 13 | + const initialValues = { | |
| 14 | + accounts: '' | |
| 15 | + } | |
| 16 | + | |
| 17 | + const formik = useFormik({ | |
| 18 | + initialValues: initialValues, | |
| 19 | + onSubmit: (values, { resetForm }) => { | |
| 20 | + AddData() | |
| 21 | + resetForm(); | |
| 22 | + }, | |
| 23 | + }); | |
| 24 | + const { errors, touched, values, handleBlur, handleChange, handleSubmit, setFieldValue, resetForm } = formik | |
| 25 | + | |
| 26 | + const AddData = async () => { | |
| 27 | + try { | |
| 28 | + setIsLoding(true) | |
| 29 | + resetForm() | |
| 30 | + | |
| 31 | + if (values.accounts) { | |
| 32 | + onClose(); | |
| 33 | + navigate('/accountImport', { state: { fileData: values.accounts, customFields: customFields } }); | |
| 34 | + } | |
| 35 | + | |
| 36 | + } catch (e) { | |
| 37 | + console.log(e); | |
| 38 | + } | |
| 39 | + finally { | |
| 40 | + setIsLoding(false) | |
| 41 | + } | |
| 42 | + }; | |
| 43 | + | |
| 44 | + return ( | |
| 45 | + <Modal onClose={onClose} isOpen={isOpen} isCentered> | |
| 46 | + <ModalOverlay /> | |
| 47 | + <ModalContent> | |
| 48 | + <ModalHeader>Import Account</ModalHeader> | |
| 49 | + <ModalCloseButton /> | |
| 50 | + <ModalBody> | |
| 51 | + <Grid templateColumns="repeat(12, 1fr)" gap={3}> | |
| 52 | + <GridItem colSpan={{ base: 12 }}> | |
| 53 | + <CommonFileUpload count={values.accounts.length} onFileSelect={(file) => setFieldValue('accounts', file)} text={text} /> | |
| 54 | + <Text mb='10px' color={'red'}> {errors.accounts && touched.accounts && <>Please Select {text}</>}</Text> | |
| 55 | + </GridItem> | |
| 56 | + </Grid> | |
| 57 | + </ModalBody> | |
| 58 | + <ModalFooter> | |
| 59 | + <Button size="sm" variant='brand' onClick={handleSubmit} disabled={isLoding ? true : false} >{isLoding ? <Spinner /> : 'Save'}</Button> | |
| 60 | + <Button sx={{ | |
| 61 | + marginLeft: 2, | |
| 62 | + textTransform: "capitalize", | |
| 63 | + }} variant="outline" | |
| 64 | + colorScheme="red" size="sm" onClick={() => { | |
| 65 | + onClose() | |
| 66 | + formik.resetForm() | |
| 67 | + }}>Close</Button> | |
| 68 | + </ModalFooter> | |
| 69 | + </ModalContent> | |
| 70 | + </Modal> | |
| 71 | + ) | |
| 72 | +} | |
| 73 | + | |
| 74 | +export default ImportModal |
+470-0Client/src/views/admin/account/index.js
| @@ -0,0 +1,470 @@ | ||
| 1 | +import { useEffect, useState } from 'react'; | |
| 2 | +import { Link, useNavigate } from "react-router-dom"; | |
| 3 | +import { DeleteIcon, EditIcon, ViewIcon } from '@chakra-ui/icons'; | |
| 4 | +import { Button, Menu, MenuButton, MenuItem, MenuList, Text, useDisclosure } from '@chakra-ui/react'; | |
| 5 | +import { getApi, deleteManyApi } from 'services/api'; | |
| 6 | +import { HasAccess } from '../../../redux/accessUtils'; | |
| 7 | +import CommonCheckTable from '../../../components/reactTable/checktable'; | |
| 8 | +import { SearchIcon } from "@chakra-ui/icons"; | |
| 9 | +import { CiMenuKebab } from 'react-icons/ci'; | |
| 10 | +import moment from 'moment'; | |
| 11 | +import { MdLeaderboard } from 'react-icons/md'; | |
| 12 | +import { IoIosContact } from 'react-icons/io'; | |
| 13 | +import AddEdit from './AddEdit'; | |
| 14 | +import { useDispatch } from 'react-redux'; | |
| 15 | +import { fetchEmailsData } from '../../../redux/slices/emailsSlice'; | |
| 16 | +import { toast } from 'react-toastify'; | |
| 17 | +import AccountAdvanceSearch from './components/AccountAdvanceSearch'; | |
| 18 | +import { fetchAccountData } from '../../../redux/slices/accountSlice'; | |
| 19 | +import CommonDeleteModel from '../../../components/commonDeleteModel' | |
| 20 | +import ImportModal from './components/ImportModel'; | |
| 21 | + | |
| 22 | +const Index = (props) => { | |
| 23 | + const [action, setAction] = useState(false); | |
| 24 | + const { isOpen, onOpen, onClose } = useDisclosure(); | |
| 25 | + const [selectedValues, setSelectedValues] = useState([]); | |
| 26 | + const [advanceSearch, setAdvanceSearch] = useState(false); | |
| 27 | + const [getTagValuesOutSide, setGetTagValuesOutside] = useState([]); | |
| 28 | + const [searchboxOutside, setSearchboxOutside] = useState(''); | |
| 29 | + const user = JSON.parse(localStorage.getItem("user")); | |
| 30 | + const navigate = useNavigate(); | |
| 31 | + const dispatch = useDispatch(); | |
| 32 | + const [isLoding, setIsLoding] = useState(false); | |
| 33 | + const [data, setData] = useState([]); | |
| 34 | + const [displaySearchData, setDisplaySearchData] = useState(false); | |
| 35 | + const [searchedData, setSearchedData] = useState([]); | |
| 36 | + const [selectedId, setSelectedId] = useState(); | |
| 37 | + const [deleteModel, setDelete] = useState(false); | |
| 38 | + const [edit, setEdit] = useState(false); | |
| 39 | + const [type, setType] = useState("") | |
| 40 | + const [isImport, setIsImport] = useState(false); | |
| 41 | + | |
| 42 | + const [permission] = HasAccess(["Account"]); | |
| 43 | + | |
| 44 | + const actionHeader = { | |
| 45 | + Header: "Action", | |
| 46 | + accessor: "action", | |
| 47 | + isSortable: false, | |
| 48 | + center: true, | |
| 49 | + cell: ({ row, i }) => ( | |
| 50 | + <Text fontSize="md" fontWeight="900" textAlign={"center"}> | |
| 51 | + <Menu isLazy> | |
| 52 | + <MenuButton> | |
| 53 | + <CiMenuKebab /> | |
| 54 | + </MenuButton> | |
| 55 | + <MenuList | |
| 56 | + minW={"fit-content"} | |
| 57 | + transform={"translate(1520px, 173px);"} | |
| 58 | + > | |
| 59 | + {permission?.update && ( | |
| 60 | + <MenuItem | |
| 61 | + py={2.5} | |
| 62 | + icon={<EditIcon fontSize={15} mb={1} />} | |
| 63 | + onClick={() => { | |
| 64 | + setType("edit"); | |
| 65 | + onOpen(); | |
| 66 | + setSelectedId(row?.values?._id); | |
| 67 | + }} | |
| 68 | + > | |
| 69 | + Edit | |
| 70 | + </MenuItem> | |
| 71 | + )} | |
| 72 | + {permission?.view && ( | |
| 73 | + <MenuItem | |
| 74 | + py={2.5} | |
| 75 | + color={"green"} | |
| 76 | + icon={<ViewIcon mb={1} fontSize={15} />} | |
| 77 | + onClick={() => { | |
| 78 | + navigate(`/accountView/${row?.values?._id}`); | |
| 79 | + }} | |
| 80 | + > | |
| 81 | + View | |
| 82 | + </MenuItem> | |
| 83 | + )} | |
| 84 | + {permission?.delete && ( | |
| 85 | + <MenuItem | |
| 86 | + py={2.5} | |
| 87 | + color={"red"} | |
| 88 | + icon={<DeleteIcon fontSize={15} mb={1} />} | |
| 89 | + onClick={() => { | |
| 90 | + setDelete(true); | |
| 91 | + setSelectedValues([row?.values?._id]); | |
| 92 | + }} | |
| 93 | + > | |
| 94 | + Delete | |
| 95 | + </MenuItem> | |
| 96 | + )} | |
| 97 | + </MenuList> | |
| 98 | + </Menu> | |
| 99 | + </Text> | |
| 100 | + ), | |
| 101 | + }; | |
| 102 | + const tableColumns = [ | |
| 103 | + { Header: "#", accessor: "_id", isSortable: false, width: 10 }, | |
| 104 | + { | |
| 105 | + Header: 'Account Name', accessor: 'name', cell: (cell) => ( | |
| 106 | + <div className="selectOpt"> | |
| 107 | + <Text | |
| 108 | + onClick={() => handleViewOpen(cell?.row?.original._id)} | |
| 109 | + me="10px" | |
| 110 | + sx={{ '&:hover': { color: 'blue.500', textDecoration: 'underline' }, cursor: 'pointer' }} | |
| 111 | + color='brand.600' | |
| 112 | + fontSize="sm" | |
| 113 | + fontWeight="700" | |
| 114 | + > | |
| 115 | + {cell?.value} | |
| 116 | + </Text> | |
| 117 | + </div> | |
| 118 | + ) | |
| 119 | + }, | |
| 120 | + { | |
| 121 | + Header: 'Office Phone', accessor: 'officePhone', | |
| 122 | + }, | |
| 123 | + { | |
| 124 | + Header: 'Fax', accessor: 'fax', | |
| 125 | + }, | |
| 126 | + { | |
| 127 | + Header: 'Email Address', accessor: 'emailAddress', | |
| 128 | + }, | |
| 129 | + ...(permission?.update || permission?.view || permission?.delete ? [actionHeader] : []) | |
| 130 | + | |
| 131 | + ]; | |
| 132 | + const handleViewOpen = (id) => { | |
| 133 | + navigate(`/accountView/${id}`) | |
| 134 | + } | |
| 135 | + const customFields = [ | |
| 136 | + { | |
| 137 | + "name": "name", | |
| 138 | + "label": "Account Name", | |
| 139 | + "type": "text", | |
| 140 | + "fixed": false, | |
| 141 | + "isDefault": false, | |
| 142 | + "delete": false, | |
| 143 | + "belongsTo": null, | |
| 144 | + "backendType": "Mixed", | |
| 145 | + "isTableField": false, | |
| 146 | + "isView": false, | |
| 147 | + "options": [ | |
| 148 | + { | |
| 149 | + "name": "", | |
| 150 | + "value": "", | |
| 151 | + "_id": "6694b8112cf3cbec6b5286c0" | |
| 152 | + }, | |
| 153 | + { | |
| 154 | + "name": "", | |
| 155 | + "value": "", | |
| 156 | + "_id": "6694b8112cf3cbec6b5286c1" | |
| 157 | + } | |
| 158 | + ], | |
| 159 | + "validation": [ | |
| 160 | + { | |
| 161 | + "require": true, | |
| 162 | + "message": "Account Name is required", | |
| 163 | + "_id": "6694b8112cf3cbec6b5286c2" | |
| 164 | + }, | |
| 165 | + { | |
| 166 | + "min": false, | |
| 167 | + "value": "", | |
| 168 | + "message": "", | |
| 169 | + "_id": "6694b8112cf3cbec6b5286c3" | |
| 170 | + }, | |
| 171 | + { | |
| 172 | + "max": false, | |
| 173 | + "value": "", | |
| 174 | + "message": "", | |
| 175 | + "_id": "6694b8112cf3cbec6b5286c4" | |
| 176 | + }, | |
| 177 | + { | |
| 178 | + "value": "", | |
| 179 | + "message": "", | |
| 180 | + "match": false, | |
| 181 | + "_id": "6694b8112cf3cbec6b5286c5" | |
| 182 | + }, | |
| 183 | + { | |
| 184 | + "message": "", | |
| 185 | + "formikType": "", | |
| 186 | + "_id": "6694b8112cf3cbec6b5286c6" | |
| 187 | + } | |
| 188 | + ], | |
| 189 | + "_id": "6694b8112cf3cbec6b5286bf" | |
| 190 | + }, | |
| 191 | + { | |
| 192 | + "name": "officePhone", | |
| 193 | + "label": "Office Phone", | |
| 194 | + "type": "number", | |
| 195 | + "fixed": false, | |
| 196 | + "isDefault": false, | |
| 197 | + "delete": false, | |
| 198 | + "belongsTo": null, | |
| 199 | + "backendType": "Mixed", | |
| 200 | + "isTableField": false, | |
| 201 | + "isView": false, | |
| 202 | + "options": [ | |
| 203 | + { | |
| 204 | + "name": "", | |
| 205 | + "value": "", | |
| 206 | + "_id": "6694b8662cf3cbec6b5289fd" | |
| 207 | + }, | |
| 208 | + { | |
| 209 | + "name": "", | |
| 210 | + "value": "", | |
| 211 | + "_id": "6694b8662cf3cbec6b5289fe" | |
| 212 | + } | |
| 213 | + ], | |
| 214 | + "validation": [ | |
| 215 | + { | |
| 216 | + "require": false, | |
| 217 | + "message": "", | |
| 218 | + "_id": "6694b8662cf3cbec6b5289ff" | |
| 219 | + }, | |
| 220 | + { | |
| 221 | + "min": false, | |
| 222 | + "value": "", | |
| 223 | + "message": "", | |
| 224 | + "_id": "6694b8662cf3cbec6b528a00" | |
| 225 | + }, | |
| 226 | + { | |
| 227 | + "max": false, | |
| 228 | + "value": "", | |
| 229 | + "message": "", | |
| 230 | + "_id": "6694b8662cf3cbec6b528a01" | |
| 231 | + }, | |
| 232 | + { | |
| 233 | + "value": "", | |
| 234 | + "message": "", | |
| 235 | + "match": false, | |
| 236 | + "_id": "6694b8662cf3cbec6b528a02" | |
| 237 | + }, | |
| 238 | + { | |
| 239 | + "message": "", | |
| 240 | + "formikType": "", | |
| 241 | + "_id": "6694b8662cf3cbec6b528a03" | |
| 242 | + } | |
| 243 | + ], | |
| 244 | + "_id": "6694b8662cf3cbec6b5289fc" | |
| 245 | + }, | |
| 246 | + { | |
| 247 | + "name": "fax", | |
| 248 | + "label": "Fax", | |
| 249 | + "type": "number", | |
| 250 | + "fixed": false, | |
| 251 | + "isDefault": false, | |
| 252 | + "delete": false, | |
| 253 | + "belongsTo": null, | |
| 254 | + "backendType": "Mixed", | |
| 255 | + "isTableField": false, | |
| 256 | + "isView": false, | |
| 257 | + "options": [ | |
| 258 | + { | |
| 259 | + "name": "", | |
| 260 | + "value": "", | |
| 261 | + "_id": "6694b8742cf3cbec6b528d52" | |
| 262 | + }, | |
| 263 | + { | |
| 264 | + "name": "", | |
| 265 | + "value": "", | |
| 266 | + "_id": "6694b8742cf3cbec6b528d53" | |
| 267 | + } | |
| 268 | + ], | |
| 269 | + "validation": [ | |
| 270 | + { | |
| 271 | + "require": false, | |
| 272 | + "message": "", | |
| 273 | + "_id": "6694b8742cf3cbec6b528d54" | |
| 274 | + }, | |
| 275 | + { | |
| 276 | + "min": false, | |
| 277 | + "value": "", | |
| 278 | + "message": "", | |
| 279 | + "_id": "6694b8742cf3cbec6b528d55" | |
| 280 | + }, | |
| 281 | + { | |
| 282 | + "max": false, | |
| 283 | + "value": "", | |
| 284 | + "message": "", | |
| 285 | + "_id": "6694b8742cf3cbec6b528d56" | |
| 286 | + }, | |
| 287 | + { | |
| 288 | + "value": "", | |
| 289 | + "message": "", | |
| 290 | + "match": false, | |
| 291 | + "_id": "6694b8742cf3cbec6b528d57" | |
| 292 | + }, | |
| 293 | + { | |
| 294 | + "message": "", | |
| 295 | + "formikType": "", | |
| 296 | + "_id": "6694b8742cf3cbec6b528d58" | |
| 297 | + } | |
| 298 | + ], | |
| 299 | + "_id": "6694b8742cf3cbec6b528d51" | |
| 300 | + }, | |
| 301 | + { | |
| 302 | + "name": "emailAddress", | |
| 303 | + "label": "Email Address", | |
| 304 | + "type": "text", | |
| 305 | + "fixed": false, | |
| 306 | + "isDefault": false, | |
| 307 | + "delete": false, | |
| 308 | + "belongsTo": null, | |
| 309 | + "backendType": "Mixed", | |
| 310 | + "isTableField": false, | |
| 311 | + "isView": false, | |
| 312 | + "options": [ | |
| 313 | + { | |
| 314 | + "name": "", | |
| 315 | + "value": "", | |
| 316 | + "_id": "6694b8942cf3cbec6b5290bf" | |
| 317 | + }, | |
| 318 | + { | |
| 319 | + "name": "", | |
| 320 | + "value": "", | |
| 321 | + "_id": "6694b8942cf3cbec6b5290c0" | |
| 322 | + } | |
| 323 | + ], | |
| 324 | + "validation": [ | |
| 325 | + { | |
| 326 | + "require": false, | |
| 327 | + "message": "", | |
| 328 | + "_id": "6694b8942cf3cbec6b5290c1" | |
| 329 | + }, | |
| 330 | + { | |
| 331 | + "min": false, | |
| 332 | + "value": "", | |
| 333 | + "message": "", | |
| 334 | + "_id": "6694b8942cf3cbec6b5290c2" | |
| 335 | + }, | |
| 336 | + { | |
| 337 | + "max": false, | |
| 338 | + "value": "", | |
| 339 | + "message": "", | |
| 340 | + "_id": "6694b8942cf3cbec6b5290c3" | |
| 341 | + }, | |
| 342 | + { | |
| 343 | + "value": "", | |
| 344 | + "message": "", | |
| 345 | + "match": false, | |
| 346 | + "_id": "6694b8942cf3cbec6b5290c4" | |
| 347 | + }, | |
| 348 | + { | |
| 349 | + "message": "", | |
| 350 | + "formikType": "email", | |
| 351 | + "_id": "6694b8942cf3cbec6b5290c5" | |
| 352 | + } | |
| 353 | + ], | |
| 354 | + "_id": "6694b8942cf3cbec6b5290be" | |
| 355 | + } | |
| 356 | + ] | |
| 357 | + | |
| 358 | + const handleOpenAdd = () => { | |
| 359 | + onOpen(); | |
| 360 | + setType("add") | |
| 361 | + } | |
| 362 | + | |
| 363 | + const handleDelete = async (ids) => { | |
| 364 | + try { | |
| 365 | + setIsLoding(true); | |
| 366 | + let response = await deleteManyApi("api/account/deleteMany", ids); | |
| 367 | + if (response.status === 200) { | |
| 368 | + toast.success(`Account Delete successfully`) | |
| 369 | + setSelectedValues([]); | |
| 370 | + setDelete(false); | |
| 371 | + setAction((pre) => !pre); | |
| 372 | + } | |
| 373 | + } catch (error) { | |
| 374 | + console.log(error); | |
| 375 | + toast.error(`server error`) | |
| 376 | + | |
| 377 | + } finally { | |
| 378 | + setIsLoding(false); | |
| 379 | + } | |
| 380 | + }; | |
| 381 | + | |
| 382 | + const fetchData = async () => { | |
| 383 | + setIsLoding(true) | |
| 384 | + const result = await dispatch(fetchAccountData()) | |
| 385 | + | |
| 386 | + if (result.payload.status === 200) { | |
| 387 | + setData(result?.payload?.data); | |
| 388 | + } else { | |
| 389 | + toast.error("Failed to fetch data", "error"); | |
| 390 | + } | |
| 391 | + setIsLoding(false) | |
| 392 | + } | |
| 393 | + | |
| 394 | + // const [columns, setColumns] = useState([...tableColumns]); | |
| 395 | + // const [selectedColumns, setSelectedColumns] = useState([...tableColumns]); | |
| 396 | + // const dataColumn = tableColumns?.filter(item => selectedColumns?.find(colum => colum?.Header === item.Header)) | |
| 397 | + | |
| 398 | + | |
| 399 | + useEffect(() => { | |
| 400 | + fetchData(); | |
| 401 | + }, [action]) | |
| 402 | + | |
| 403 | + return ( | |
| 404 | + <div> | |
| 405 | + <CommonCheckTable | |
| 406 | + title={"Account"} | |
| 407 | + isLoding={isLoding} | |
| 408 | + columnData={tableColumns ?? []} | |
| 409 | + // dataColumn={dataColumn ?? []} | |
| 410 | + allData={data ?? []} | |
| 411 | + tableData={data} | |
| 412 | + searchDisplay={displaySearchData} | |
| 413 | + setSearchDisplay={setDisplaySearchData} | |
| 414 | + searchedDataOut={searchedData} | |
| 415 | + setSearchedDataOut={setSearchedData} | |
| 416 | + tableCustomFields={[]} | |
| 417 | + access={permission} | |
| 418 | + // action={action} | |
| 419 | + // setAction={setAction} | |
| 420 | + // selectedColumns={selectedColumns} | |
| 421 | + // setSelectedColumns={setSelectedColumns} | |
| 422 | + // isOpen={isOpen} | |
| 423 | + // onClose={onclose} | |
| 424 | + setIsImport={setIsImport} | |
| 425 | + onOpen={handleOpenAdd} | |
| 426 | + selectedValues={selectedValues} | |
| 427 | + setSelectedValues={setSelectedValues} | |
| 428 | + setDelete={setDelete} | |
| 429 | + deleteMany={false} | |
| 430 | + AdvanceSearch={ | |
| 431 | + <Button variant="outline" colorScheme='brand' leftIcon={<SearchIcon />} mt={{ sm: "5px", md: "0" }} size="sm" onClick={() => setAdvanceSearch(true)}>Advance Search</Button> | |
| 432 | + } | |
| 433 | + getTagValuesOutSide={getTagValuesOutSide} | |
| 434 | + searchboxOutside={searchboxOutside} | |
| 435 | + setGetTagValuesOutside={setGetTagValuesOutside} | |
| 436 | + setSearchboxOutside={setSearchboxOutside} | |
| 437 | + handleSearchType="AccountSearch" | |
| 438 | + /> | |
| 439 | + | |
| 440 | + <AccountAdvanceSearch | |
| 441 | + advanceSearch={advanceSearch} | |
| 442 | + setAdvanceSearch={setAdvanceSearch} | |
| 443 | + setSearchedData={setSearchedData} | |
| 444 | + setDisplaySearchData={setDisplaySearchData} | |
| 445 | + allData={data ?? []} | |
| 446 | + setAction={setAction} | |
| 447 | + setGetTagValues={setGetTagValuesOutside} | |
| 448 | + setSearchbox={setSearchboxOutside} | |
| 449 | + /> | |
| 450 | + | |
| 451 | + <AddEdit isOpen={isOpen} size={"lg"} onClose={onClose} setAction={setAction} type={type} selectedId={selectedId} /> | |
| 452 | + <CommonDeleteModel | |
| 453 | + isOpen={deleteModel} | |
| 454 | + onClose={() => setDelete(false)} | |
| 455 | + type="Account" | |
| 456 | + handleDeleteData={handleDelete} | |
| 457 | + ids={selectedValues} | |
| 458 | + /> | |
| 459 | + | |
| 460 | + <ImportModal | |
| 461 | + text="Account file" | |
| 462 | + isOpen={isImport} | |
| 463 | + onClose={setIsImport} | |
| 464 | + customFields={customFields} | |
| 465 | + /> | |
| 466 | + </div> | |
| 467 | + ) | |
| 468 | +} | |
| 469 | + | |
| 470 | +export default Index |
+142-0Client/src/views/admin/activeDeactiveModule/index.js
| @@ -0,0 +1,142 @@ | ||
| 1 | +import { useEffect, useState } from "react"; | |
| 2 | +import { | |
| 3 | + Box, | |
| 4 | + Button, | |
| 5 | + Flex, GridItem, | |
| 6 | + Text, Grid, useColorModeValue, | |
| 7 | + Switch | |
| 8 | +} from "@chakra-ui/react"; | |
| 9 | +import Card from "components/card/Card"; | |
| 10 | +import { getApi, putApi } from "services/api"; | |
| 11 | +import Spinner from "components/spinner/Spinner"; | |
| 12 | +import { useFormik } from "formik"; | |
| 13 | +import { HSeparator } from "components/separator/Separator"; | |
| 14 | +import { useNavigate } from "react-router-dom"; | |
| 15 | +import { IoIosArrowBack } from "react-icons/io"; | |
| 16 | +import { useDispatch, useSelector } from "react-redux"; | |
| 17 | +import { fetchModules } from "../../../redux/slices/moduleSlice"; | |
| 18 | + | |
| 19 | +const Index = () => { | |
| 20 | + const textColor = useColorModeValue("gray.500", "white"); | |
| 21 | + | |
| 22 | + const navigate = useNavigate(); | |
| 23 | + const dispatch = useDispatch() | |
| 24 | + const modules = useSelector((state) => state?.modules?.data) | |
| 25 | + | |
| 26 | + const [isLoading, setIsLoading] = useState(false); | |
| 27 | + const [initialValues, setInitialValues] = useState([]) | |
| 28 | + const [action, setAction] = useState(false); | |
| 29 | + | |
| 30 | + const formik = useFormik({ | |
| 31 | + initialValues: initialValues, | |
| 32 | + enableReinitialize: true, | |
| 33 | + onSubmit: (_values, { resetForm }) => { | |
| 34 | + updateData(); | |
| 35 | + resetForm() | |
| 36 | + }, | |
| 37 | + }); | |
| 38 | + const { values, dirty, handleSubmit, setFieldValue } = formik | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + const fetchData = async () => { | |
| 43 | + setIsLoading(true); | |
| 44 | + try { | |
| 45 | + dispatch(fetchModules()) | |
| 46 | + } catch (error) { | |
| 47 | + console.error(error); | |
| 48 | + } finally { | |
| 49 | + setIsLoading(false); | |
| 50 | + } | |
| 51 | + }; | |
| 52 | + const updateData = async () => { | |
| 53 | + try { | |
| 54 | + setIsLoading(true); | |
| 55 | + await putApi("api/modules/edit", values); | |
| 56 | + fetchData() | |
| 57 | + // setAction((pre) => !pre); | |
| 58 | + } catch (error) { | |
| 59 | + console.error(error); | |
| 60 | + } finally { | |
| 61 | + setIsLoading(false); | |
| 62 | + } | |
| 63 | + }; | |
| 64 | + // useEffect(() => { | |
| 65 | + // fetchData(); | |
| 66 | + // }, [action]); | |
| 67 | + | |
| 68 | + useEffect(() => { | |
| 69 | + setInitialValues(modules); | |
| 70 | + }, [modules]); | |
| 71 | + | |
| 72 | + return ( | |
| 73 | + <> | |
| 74 | + <Card> | |
| 75 | + <Flex justifyContent={"space-between"} alignItems={"center"} mb={5}> | |
| 76 | + <Box> | |
| 77 | + <Text color={"secondaryGray.900"} fontSize="22px" fontWeight="700"> | |
| 78 | + Active Deactive Module | |
| 79 | + </Text> | |
| 80 | + </Box> | |
| 81 | + <Button | |
| 82 | + onClick={() => navigate("/admin-setting")} | |
| 83 | + variant="brand" | |
| 84 | + size="sm" | |
| 85 | + leftIcon={<IoIosArrowBack />} | |
| 86 | + ml={2} | |
| 87 | + > | |
| 88 | + Back | |
| 89 | + </Button> | |
| 90 | + </Flex> | |
| 91 | + <HSeparator /> | |
| 92 | + <Flex | |
| 93 | + mt={5} | |
| 94 | + alignItems={"center"} | |
| 95 | + fontSize="sm" | |
| 96 | + > | |
| 97 | + {isLoading ? ( | |
| 98 | + <Flex | |
| 99 | + justifyContent={"center"} | |
| 100 | + alignItems={"center"} | |
| 101 | + width="100%" | |
| 102 | + color={textColor} | |
| 103 | + fontSize="sm" | |
| 104 | + fontWeight="700" | |
| 105 | + > | |
| 106 | + <Spinner /> | |
| 107 | + </Flex> | |
| 108 | + ) : ( | |
| 109 | + <Grid templateColumns="repeat(12, 1fr)" p={3} gap={3} > | |
| 110 | + {values?.length > 0 && values?.map((item, index) => ( | |
| 111 | + <> | |
| 112 | + <GridItem colSpan={6} > | |
| 113 | + <Text fontSize="15px" fontWeight="700"> {item?.moduleName} </Text> | |
| 114 | + </GridItem> | |
| 115 | + <GridItem colSpan={6} > | |
| 116 | + <Switch | |
| 117 | + onChange={(e) => setFieldValue(`[${index}].isActive`, e.target.checked)} | |
| 118 | + isChecked={item?.isActive} /> | |
| 119 | + </GridItem> | |
| 120 | + </> | |
| 121 | + ))} | |
| 122 | + | |
| 123 | + <GridItem colSpan={12} mt={3} > | |
| 124 | + <Button | |
| 125 | + colorScheme="brand" | |
| 126 | + onClick={handleSubmit} | |
| 127 | + disabled={!dirty} | |
| 128 | + size="sm" | |
| 129 | + > | |
| 130 | + Update | |
| 131 | + </Button> | |
| 132 | + </GridItem> | |
| 133 | + </Grid> | |
| 134 | + )} | |
| 135 | + </Flex> | |
| 136 | + | |
| 137 | + </Card> | |
| 138 | + </> | |
| 139 | + ); | |
| 140 | +}; | |
| 141 | + | |
| 142 | +export default Index; |
+132-0Client/src/views/admin/adminSetting/index.js
| @@ -0,0 +1,132 @@ | ||
| 1 | +import { Icon, SimpleGrid } from '@chakra-ui/react'; | |
| 2 | +import MiniStatistics from "components/card/MiniStatistics"; | |
| 3 | +import IconBox from 'components/icons/IconBox'; | |
| 4 | +import { FaCreativeCommonsBy, FaWpforms } from "react-icons/fa"; | |
| 5 | +import { HiUsers } from "react-icons/hi"; | |
| 6 | +import { TbExchange } from "react-icons/tb"; | |
| 7 | +import { useNavigate } from 'react-router-dom'; | |
| 8 | +import { GrValidate } from "react-icons/gr"; | |
| 9 | +import { TbTableColumn } from "react-icons/tb"; | |
| 10 | +import { VscFileSubmodule } from "react-icons/vsc"; | |
| 11 | +import { IoIosSwitch } from "react-icons/io"; | |
| 12 | + | |
| 13 | +const Index = () => { | |
| 14 | + const navigate = useNavigate(); | |
| 15 | + return ( | |
| 16 | + <div> | |
| 17 | + <SimpleGrid columns={{ base: 1, md: 2, lg: 4 }} gap="20px" mb="20px"> | |
| 18 | + <MiniStatistics | |
| 19 | + fontsize="md" | |
| 20 | + onClick={() => navigate("/user")} | |
| 21 | + startContent={ | |
| 22 | + <IconBox | |
| 23 | + w="56px" | |
| 24 | + h="56px" | |
| 25 | + bg="linear-gradient(90deg, #4481EB 0%, #04BEFE 100%)" | |
| 26 | + icon={<Icon w="28px" h="28px" as={HiUsers} color="white" />} | |
| 27 | + /> | |
| 28 | + } | |
| 29 | + name="Users" | |
| 30 | + /> | |
| 31 | + <MiniStatistics | |
| 32 | + fontsize="md" | |
| 33 | + onClick={() => navigate("/role")} | |
| 34 | + startContent={ | |
| 35 | + <IconBox | |
| 36 | + w="56px" | |
| 37 | + h="56px" | |
| 38 | + bg="linear-gradient(90deg, #4481EB 0%, #04BEFE 100%)" | |
| 39 | + icon={<Icon w="28px" h="28px" as={FaCreativeCommonsBy} color="white" />} | |
| 40 | + /> | |
| 41 | + | |
| 42 | + } | |
| 43 | + name="Roles" | |
| 44 | + /> | |
| 45 | + <MiniStatistics | |
| 46 | + fontsize="md" | |
| 47 | + onClick={() => navigate("/change-images")} | |
| 48 | + startContent={ | |
| 49 | + <IconBox | |
| 50 | + w="56px" | |
| 51 | + h="56px" | |
| 52 | + bg="linear-gradient(90deg, #4481EB 0%, #04BEFE 100%)" | |
| 53 | + icon={<Icon w="28px" h="28px" as={TbExchange} color="white" />} | |
| 54 | + /> | |
| 55 | + | |
| 56 | + } | |
| 57 | + name="Change Images" | |
| 58 | + /> | |
| 59 | + <MiniStatistics | |
| 60 | + fontsize="md" | |
| 61 | + onClick={() => navigate("/custom-Fields")} | |
| 62 | + startContent={ | |
| 63 | + <IconBox | |
| 64 | + w="56px" | |
| 65 | + h="56px" | |
| 66 | + bg="linear-gradient(90deg, #4481EB 0%, #04BEFE 100%)" | |
| 67 | + icon={<Icon w="28px" h="28px" as={FaWpforms} color="white" />} | |
| 68 | + /> | |
| 69 | + | |
| 70 | + } | |
| 71 | + name="Custom Fields" | |
| 72 | + /> | |
| 73 | + <MiniStatistics | |
| 74 | + fontsize="md" | |
| 75 | + onClick={() => navigate("/validations")} | |
| 76 | + startContent={ | |
| 77 | + <IconBox | |
| 78 | + w="56px" | |
| 79 | + h="56px" | |
| 80 | + bg="linear-gradient(90deg, #4481EB 0%, #04BEFE 100%)" | |
| 81 | + icon={<Icon w="28px" h="28px" as={GrValidate} color="white" />} | |
| 82 | + /> | |
| 83 | + | |
| 84 | + } | |
| 85 | + name="Validations" | |
| 86 | + | |
| 87 | + /> | |
| 88 | + <MiniStatistics | |
| 89 | + fontsize="md" | |
| 90 | + onClick={() => navigate("/table-field")} | |
| 91 | + startContent={ | |
| 92 | + <IconBox | |
| 93 | + w="56px" | |
| 94 | + h="56px" | |
| 95 | + bg="linear-gradient(90deg, #4481EB 0%, #04BEFE 100%)" | |
| 96 | + icon={<Icon w="28px" h="28px" as={TbTableColumn} color="white" />} | |
| 97 | + /> | |
| 98 | + } | |
| 99 | + name="Table Fields" | |
| 100 | + /> | |
| 101 | + <MiniStatistics | |
| 102 | + fontsize="md" | |
| 103 | + onClick={() => navigate("/module")} | |
| 104 | + startContent={ | |
| 105 | + <IconBox | |
| 106 | + w="56px" | |
| 107 | + h="56px" | |
| 108 | + bg="linear-gradient(90deg, #4481EB 0%, #04BEFE 100%)" | |
| 109 | + icon={<Icon w="28px" h="28px" as={VscFileSubmodule} color="white" />} | |
| 110 | + /> | |
| 111 | + } | |
| 112 | + name="Module" | |
| 113 | + /> | |
| 114 | + <MiniStatistics | |
| 115 | + fontsize="md" | |
| 116 | + onClick={() => navigate("/active-deactive-module")} | |
| 117 | + startContent={ | |
| 118 | + <IconBox | |
| 119 | + w="56px" | |
| 120 | + h="56px" | |
| 121 | + bg="linear-gradient(90deg, #4481EB 0%, #04BEFE 100%)" | |
| 122 | + icon={<Icon w="28px" h="28px" as={IoIosSwitch} color="white" />} | |
| 123 | + /> | |
| 124 | + } | |
| 125 | + name="Active Deactive Module" | |
| 126 | + /> | |
| 127 | + </SimpleGrid> | |
| 128 | + </div> | |
| 129 | + ) | |
| 130 | +} | |
| 131 | + | |
| 132 | +export default Index |
+128-0Client/src/views/admin/calender/components/calender.js
| @@ -0,0 +1,128 @@ | ||
| 1 | +import FullCalendar from '@fullcalendar/react'; | |
| 2 | +import Card from 'components/card/Card'; | |
| 3 | +import { useEffect, useState } from 'react' | |
| 4 | +import dayGridPlugin from "@fullcalendar/daygrid"; | |
| 5 | +import interactionPlugin from "@fullcalendar/interaction"; | |
| 6 | +import listPlugin from '@fullcalendar/list'; | |
| 7 | +import multiMonthPlugin from '@fullcalendar/multimonth'; | |
| 8 | +import timeGridPlugin from '@fullcalendar/timegrid'; | |
| 9 | +import { Flex, useBreakpointValue, useDisclosure } from '@chakra-ui/react'; | |
| 10 | +import EventView from 'views/admin/task/eventView'; | |
| 11 | +import MeetingView from 'views/admin/meeting/meetingView'; | |
| 12 | +import CallView from 'views/admin/phoneCall/callView'; | |
| 13 | +import { GoDotFill } from "react-icons/go"; | |
| 14 | +import EmailView from 'views/admin/emailHistory/emailView'; | |
| 15 | +import { HasAccess } from '../../../../redux/accessUtils'; | |
| 16 | +import AddEdit from 'views/admin/task/components/AddEdit' | |
| 17 | + | |
| 18 | +const Calender = (props) => { | |
| 19 | + const { data, fetchData } = props | |
| 20 | + const [eventView, setEventView] = useState(false) | |
| 21 | + const [meetingView, setMeetingView] = useState(false) | |
| 22 | + const [callView, setCallView] = useState(false) | |
| 23 | + const [emailView, setEmailView] = useState(false) | |
| 24 | + const [taskInfo, setTaskInfo] = useState() | |
| 25 | + const [meetingInfo, setMeetingInfo] = useState() | |
| 26 | + const [callInfo, setCallInfo] = useState() | |
| 27 | + const [emailInfo, setEmailInfo] = useState() | |
| 28 | + const [date, setDate] = useState() | |
| 29 | + const [taskAccess, meetingAccess, callAccess, emailAccess] = HasAccess(['Tasks', 'Meetings', 'Calls', 'Emails']); | |
| 30 | + // const { isOpen, onOpen, onClose } = useDisclosure() | |
| 31 | + const [taskModel, setTaskModel] = useState(false); | |
| 32 | + | |
| 33 | + const user = JSON.parse(localStorage.getItem("user")) | |
| 34 | + | |
| 35 | + const handleDateClick = (arg) => { | |
| 36 | + setTaskModel(true) | |
| 37 | + setDate(arg.dateStr) | |
| 38 | + } | |
| 39 | + | |
| 40 | + function handleEventClick(info) { | |
| 41 | + if (info.event.url) { | |
| 42 | + info.jsEvent.preventDefault(); | |
| 43 | + window.open(info.event.url); | |
| 44 | + } | |
| 45 | + | |
| 46 | + if (info?.event?.groupId === "task") { | |
| 47 | + setTaskInfo(info) | |
| 48 | + setEventView(true) | |
| 49 | + } else if (info?.event?.groupId === "call") { | |
| 50 | + setCallInfo(info) | |
| 51 | + setCallView(true) | |
| 52 | + } else if (info?.event?.groupId === "meeting") { | |
| 53 | + setMeetingInfo(info) | |
| 54 | + setMeetingView(true) | |
| 55 | + } else if (info?.event?.groupId === "email") { | |
| 56 | + setEmailInfo(info) | |
| 57 | + setEmailView(true) | |
| 58 | + } | |
| 59 | + } | |
| 60 | + const [currentView, setCurrentView] = useState(''); | |
| 61 | + | |
| 62 | + useEffect(() => { | |
| 63 | + setCurrentView('dayGridMonth'); | |
| 64 | + }, []); | |
| 65 | + return ( | |
| 66 | + <div> | |
| 67 | + <Card > | |
| 68 | + {(taskAccess?.view || user?.role === "superAdmin") && <AddEdit isOpen={taskModel} onClose={setTaskModel} fetchData={fetchData} userAction={'add'} />} | |
| 69 | + <EventView fetchData={fetchData} isOpen={eventView} onClose={setEventView} info={taskInfo} /> | |
| 70 | + <MeetingView fetchData={fetchData} isOpen={meetingView} onClose={setMeetingView} info={meetingInfo} /> | |
| 71 | + <CallView fetchData={fetchData} isOpen={callView} onClose={setCallView} info={callInfo} /> | |
| 72 | + <EmailView fetchData={fetchData} isOpen={emailView} onClose={setEmailView} info={emailInfo} /> | |
| 73 | + | |
| 74 | + <div style={{ display: "flex", justifyContent: "end" }}> | |
| 75 | + { | |
| 76 | + (callAccess?.create || user?.role === "superAdmin") && | |
| 77 | + <Flex alignItems={"center"} fontSize={"14px"} marginRight={"10px"}> | |
| 78 | + <GoDotFill color='green' fontSize={"18px"} /> Calls | |
| 79 | + </Flex> | |
| 80 | + } | |
| 81 | + { | |
| 82 | + (meetingAccess?.create || user?.role === "superAdmin") && | |
| 83 | + <Flex alignItems={"center"} fontSize={"14px"} marginRight={"10px"}> | |
| 84 | + <GoDotFill color='red' fontSize={"18px"} /> Meetings | |
| 85 | + </Flex> | |
| 86 | + } | |
| 87 | + { | |
| 88 | + (emailAccess?.create || user?.role === "superAdmin") && | |
| 89 | + <Flex alignItems={"center"} fontSize={"14px"}> | |
| 90 | + <GoDotFill color='blue' fontSize={"18px"} /> Emails | |
| 91 | + </Flex> | |
| 92 | + } | |
| 93 | + </div> | |
| 94 | + <FullCalendar | |
| 95 | + plugins={[dayGridPlugin, timeGridPlugin, listPlugin, interactionPlugin, multiMonthPlugin]} | |
| 96 | + initialView={currentView} | |
| 97 | + height="600px" | |
| 98 | + dateClick={handleDateClick} | |
| 99 | + events={data} | |
| 100 | + headerToolbar={{ | |
| 101 | + left: 'prev,next today', | |
| 102 | + center: 'title', | |
| 103 | + right: 'dayGridMonth,timeGridWeek,timeGridDay,listWeek,multiMonthFourMonth' | |
| 104 | + }} | |
| 105 | + eventClick={handleEventClick} | |
| 106 | + buttonText={{ | |
| 107 | + today: 'Today', | |
| 108 | + dayGridMonth: 'Month', | |
| 109 | + timeGridWeek: 'Week', | |
| 110 | + timeGridDay: 'Day', | |
| 111 | + listWeek: 'List', | |
| 112 | + }} | |
| 113 | + views={{ | |
| 114 | + multiMonthFourMonth: { | |
| 115 | + type: 'multiMonth', | |
| 116 | + buttonText: 'Multi Month', | |
| 117 | + duration: { months: useBreakpointValue({ base: 4, lg: 4, xl: 6 }) }, | |
| 118 | + } | |
| 119 | + }} | |
| 120 | + eventClassNames="custom-fullcalendar" | |
| 121 | + /> | |
| 122 | + | |
| 123 | + </Card> | |
| 124 | + </div> | |
| 125 | + ) | |
| 126 | +} | |
| 127 | + | |
| 128 | +export default Calender |
+29-0Client/src/views/admin/calender/index.js
| @@ -0,0 +1,29 @@ | ||
| 1 | +import { useEffect, useState } from "react"; | |
| 2 | +import { getApi } from "services/api"; | |
| 3 | +import Calender from './components/calender'; | |
| 4 | + | |
| 5 | + | |
| 6 | +const Index = () => { | |
| 7 | + | |
| 8 | + const [data, setData] = useState([]) | |
| 9 | + const user = JSON.parse(localStorage.getItem("user")) | |
| 10 | + | |
| 11 | + const fetchData = async () => { | |
| 12 | + let result = await getApi(user?.role === 'superAdmin' ? 'api/calendar/' : `api/calendar/?createBy=${user._id}`); | |
| 13 | + if (result?.status === 200) { | |
| 14 | + setData(result?.data) | |
| 15 | + } | |
| 16 | + } | |
| 17 | + | |
| 18 | + useEffect(() => { | |
| 19 | + fetchData(); | |
| 20 | + }, []) | |
| 21 | + | |
| 22 | + return ( | |
| 23 | + <> | |
| 24 | + <Calender fetchData={fetchData} data={data} /> | |
| 25 | + </> | |
| 26 | + ) | |
| 27 | +} | |
| 28 | + | |
| 29 | +export default Index |
+148-0Client/src/views/admin/communication/components/TextMsg.js
| @@ -0,0 +1,148 @@ | ||
| 1 | +import { Button, FormLabel, Grid, GridItem, Heading, Input, List, ListItem, Text, Textarea, useDisclosure } from '@chakra-ui/react'; | |
| 2 | +import Spinner from 'components/spinner/Spinner'; | |
| 3 | +import { useFormik } from 'formik'; | |
| 4 | +import { useEffect, useState } from 'react'; | |
| 5 | +import { MdOutlineMessage } from 'react-icons/md'; | |
| 6 | +import { toast } from 'react-toastify'; | |
| 7 | +import { textMsgSchema } from 'schema'; | |
| 8 | +import { getApi } from 'services/api'; | |
| 9 | +import { postApi } from 'services/api'; | |
| 10 | + | |
| 11 | +const TextMsg = () => { | |
| 12 | + | |
| 13 | + const [data, setData] = useState([]) | |
| 14 | + const user = JSON.parse(localStorage.getItem('user')) | |
| 15 | + const { isOpen, onOpen, onClose } = useDisclosure(); | |
| 16 | + const [isLoding, setIsLoding] = useState(false) | |
| 17 | + | |
| 18 | + const initialValues = { | |
| 19 | + sender: user?._id, | |
| 20 | + to: '', | |
| 21 | + message: '', | |
| 22 | + createFor: '', | |
| 23 | + }; | |
| 24 | + | |
| 25 | + const formik = useFormik({ | |
| 26 | + initialValues: initialValues, | |
| 27 | + validationSchema: textMsgSchema, | |
| 28 | + onSubmit: (values, { resetForm }) => { | |
| 29 | + AddData(); | |
| 30 | + }, | |
| 31 | + }); | |
| 32 | + | |
| 33 | + const { errors, touched, values, handleChange, handleSubmit, setFieldValue, } = formik | |
| 34 | + | |
| 35 | + if (touched.to && errors.createFor) { | |
| 36 | + toast.error('Please select an authorized to'); | |
| 37 | + formik.resetForm(); | |
| 38 | + | |
| 39 | + } | |
| 40 | + | |
| 41 | + const AddData = async () => { | |
| 42 | + try { | |
| 43 | + setIsLoding(true) | |
| 44 | + let response = await postApi('api/text-msg/add', values) | |
| 45 | + if (response.status === 200) { | |
| 46 | + toast.success(`Text Msg send successfully to ${values.to} `) | |
| 47 | + formik.resetForm(); | |
| 48 | + } | |
| 49 | + } catch (e) { | |
| 50 | + console.log(e); | |
| 51 | + } | |
| 52 | + finally { | |
| 53 | + setIsLoding(false) | |
| 54 | + } | |
| 55 | + }; | |
| 56 | + | |
| 57 | + const fetchData = async () => { | |
| 58 | + let result = await getApi(user.role === 'superAdmin' ? 'api/contact/' : `api/contact/?createBy=${user._id}`); | |
| 59 | + values.createFor = result?._id; | |
| 60 | + setData(result.data); | |
| 61 | + } | |
| 62 | + | |
| 63 | + useEffect(() => { | |
| 64 | + fetchData() | |
| 65 | + }, []); | |
| 66 | + | |
| 67 | + useEffect(() => { | |
| 68 | + data?.map((item) => { | |
| 69 | + let recipientExists = item?.phoneNumber === values.to | |
| 70 | + if (recipientExists) { | |
| 71 | + values.createFor = item._id | |
| 72 | + setFieldValue('createFor', item._id) | |
| 73 | + } | |
| 74 | + }) | |
| 75 | + }, [values]) | |
| 76 | + | |
| 77 | + return ( | |
| 78 | + <Grid templateColumns="repeat(12, 1fr)" gap={3}> | |
| 79 | + <GridItem colSpan={{ base: 12 }} > | |
| 80 | + <Heading as='h2' size='xl' noOfLines={1}> | |
| 81 | + Text Message | |
| 82 | + </Heading> | |
| 83 | + </GridItem> | |
| 84 | + <GridItem colSpan={{ base: 12 }}> | |
| 85 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='8px'> | |
| 86 | + To<Text color={"red"}>*</Text> | |
| 87 | + </FormLabel> | |
| 88 | + <Input | |
| 89 | + onFocus={onOpen} | |
| 90 | + fontSize='sm' | |
| 91 | + type='number' | |
| 92 | + onChange={handleChange} | |
| 93 | + onBlur={() => setTimeout(onClose, 200)} | |
| 94 | + value={values.to} | |
| 95 | + name="to" | |
| 96 | + placeholder='to' | |
| 97 | + fontWeight='500' | |
| 98 | + borderColor={errors?.to && touched?.to ? "red.300" : null} | |
| 99 | + /> | |
| 100 | + {isOpen && values?.to && ( | |
| 101 | + <List position={'relative'} border={'1px solid'} bg={'gray.100'} width={'100%'} borderRadius={'0px 0px 20px 20px'} lineHeight={1} > | |
| 102 | + {data?.filter((option) => { | |
| 103 | + if (option && option.phoneNumber && typeof option.phoneNumber === 'number') { | |
| 104 | + return option.phoneNumber.toString().includes(values.to.toString().toLowerCase()); | |
| 105 | + } | |
| 106 | + return false; | |
| 107 | + }).map((option, index) => ( | |
| 108 | + <ListItem p={3} borderBottom={'2px solid #efefef'} sx={{ '&:last-child': { borderBottom: 'none' } }} key={option?._id} cursor={'pointer'} | |
| 109 | + onClick={() => { | |
| 110 | + setFieldValue('createFor', option?._id) | |
| 111 | + setFieldValue('to', option.phoneNumber.toString()) | |
| 112 | + }} | |
| 113 | + > | |
| 114 | + {option.phoneNumber} | |
| 115 | + </ListItem> | |
| 116 | + ))} | |
| 117 | + </List> | |
| 118 | + )} | |
| 119 | + <Text mb='10px' color={'red'}> {errors.to && touched.to && errors.to}</Text> | |
| 120 | + </GridItem> | |
| 121 | + <GridItem colSpan={{ base: 12 }} > | |
| 122 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='8px'> | |
| 123 | + Message | |
| 124 | + </FormLabel> | |
| 125 | + <Textarea | |
| 126 | + height={'20vh'} | |
| 127 | + fontSize='sm' | |
| 128 | + resize={'none'} | |
| 129 | + onChange={handleChange} | |
| 130 | + value={values.message} | |
| 131 | + name="message" | |
| 132 | + placeholder='message' | |
| 133 | + fontWeight='500' | |
| 134 | + borderColor={errors?.message && touched?.message ? "red.300" : null} | |
| 135 | + /> | |
| 136 | + <Text mb='10px' color={'red'}> {errors.message && touched.message && errors.message}</Text> | |
| 137 | + </GridItem> | |
| 138 | + | |
| 139 | + <GridItem colSpan={{ base: 12 }} > | |
| 140 | + <Button variant='brand' size="sm" onClick={handleSubmit} disabled={isLoding ? true : false} leftIcon={<MdOutlineMessage />}> {isLoding ? <Spinner /> : 'Send Msg'}</Button> | |
| 141 | + <Button onClick={() => formik.resetForm()} size="sm">Clear</Button> | |
| 142 | + </GridItem> | |
| 143 | + | |
| 144 | + </Grid> | |
| 145 | + ) | |
| 146 | +} | |
| 147 | + | |
| 148 | +export default TextMsg |
+152-0Client/src/views/admin/communication/components/call.js
| @@ -0,0 +1,152 @@ | ||
| 1 | +import { Button, FormLabel, Grid, GridItem, Heading, Input, List, ListItem, Text, Textarea, useDisclosure } from '@chakra-ui/react'; | |
| 2 | +import Spinner from 'components/spinner/Spinner'; | |
| 3 | +import { useFormik } from 'formik'; | |
| 4 | +import { useEffect, useState } from 'react'; | |
| 5 | +import { BsFillTelephoneFill } from 'react-icons/bs'; | |
| 6 | +import { toast } from 'react-toastify'; | |
| 7 | +import { phoneCallSchema } from 'schema'; | |
| 8 | +import { getApi } from 'services/api'; | |
| 9 | +import { postApi } from 'services/api'; | |
| 10 | + | |
| 11 | +const Call = () => { | |
| 12 | + | |
| 13 | + const [data, setData] = useState([]) | |
| 14 | + const user = JSON.parse(localStorage.getItem('user')) | |
| 15 | + const { isOpen, onOpen, onClose } = useDisclosure(); | |
| 16 | + const [isLoding, setIsLoding] = useState(false) | |
| 17 | + | |
| 18 | + const initialValues = { | |
| 19 | + sender: user?._id, | |
| 20 | + recipient: '', | |
| 21 | + callDuration: '', | |
| 22 | + callNotes: '', | |
| 23 | + createBy: '', | |
| 24 | + }; | |
| 25 | + | |
| 26 | + const formik = useFormik({ | |
| 27 | + initialValues: initialValues, | |
| 28 | + validationSchema: phoneCallSchema, | |
| 29 | + onSubmit: (values, { resetForm }) => { | |
| 30 | + AddData(); | |
| 31 | + }, | |
| 32 | + }); | |
| 33 | + | |
| 34 | + const { errors, touched, values, handleBlur, handleChange, handleSubmit, setFieldValue, } = formik | |
| 35 | + | |
| 36 | + if (touched.recipient && errors.createBy) { | |
| 37 | + | |
| 38 | + toast.error('Please select an authorized recipient'); | |
| 39 | + formik.resetForm(); | |
| 40 | + | |
| 41 | + } | |
| 42 | + | |
| 43 | + const AddData = async () => { | |
| 44 | + try { | |
| 45 | + setIsLoding(true) | |
| 46 | + let response = await postApi('api/phoneCall/add', values) | |
| 47 | + if (response.status === 200) { | |
| 48 | + | |
| 49 | + toast.success(`Call ${values.recipient} successfully`) | |
| 50 | + formik.resetForm(); | |
| 51 | + } | |
| 52 | + } catch (e) { | |
| 53 | + console.error(e); | |
| 54 | + } | |
| 55 | + finally { | |
| 56 | + setIsLoding(false) | |
| 57 | + } | |
| 58 | + }; | |
| 59 | + | |
| 60 | + const fetchData = async () => { | |
| 61 | + | |
| 62 | + let result = await getApi(user.role === 'superAdmin' ? 'api/contact/' : `api/contact/?createBy=${user._id}`); | |
| 63 | + values.createBy = result?._id; | |
| 64 | + setData(result.data); | |
| 65 | + } | |
| 66 | + | |
| 67 | + useEffect(() => { | |
| 68 | + fetchData() | |
| 69 | + }, []); | |
| 70 | + | |
| 71 | + useEffect(() => { | |
| 72 | + data?.map((item) => { | |
| 73 | + let recipientExists = item?.phoneNumber === values.recipient | |
| 74 | + if (recipientExists) { | |
| 75 | + values.createBy = item._id | |
| 76 | + setFieldValue('createBy', item._id) | |
| 77 | + } | |
| 78 | + }) | |
| 79 | + }, [values]) | |
| 80 | + | |
| 81 | + return ( | |
| 82 | + <Grid templateColumns="repeat(12, 1fr)" gap={3}> | |
| 83 | + <GridItem colSpan={{ base: 12 }} > | |
| 84 | + <Heading as='h2' size='xl' noOfLines={1}> | |
| 85 | + Call | |
| 86 | + </Heading> | |
| 87 | + </GridItem> | |
| 88 | + <GridItem colSpan={{ base: 12 }}> | |
| 89 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='8px'> | |
| 90 | + To<Text color={"red"}>*</Text> | |
| 91 | + </FormLabel> | |
| 92 | + <Input | |
| 93 | + onFocus={onOpen} | |
| 94 | + fontSize='sm' | |
| 95 | + type='number' | |
| 96 | + onChange={handleChange} | |
| 97 | + onBlur={() => setTimeout(onClose, 200)} | |
| 98 | + value={values.recipient} | |
| 99 | + name="recipient" | |
| 100 | + placeholder='Recipient' | |
| 101 | + fontWeight='500' | |
| 102 | + borderColor={errors?.recipient && touched?.recipient ? "red.300" : null} | |
| 103 | + /> | |
| 104 | + {isOpen && values?.recipient && ( | |
| 105 | + <List position={'relative'} border={'1px solid'} bg={'gray.100'} width={'100%'} borderRadius={'0px 0px 20px 20px'} lineHeight={1} > | |
| 106 | + {data?.filter((option) => { | |
| 107 | + if (option && option.phoneNumber && typeof option.phoneNumber === 'number') { | |
| 108 | + return option.phoneNumber.toString().includes(values.recipient.toString().toLowerCase()); | |
| 109 | + } | |
| 110 | + return false; | |
| 111 | + }).map((option, index) => ( | |
| 112 | + <ListItem p={3} borderBottom={'2px solid #efefef'} sx={{ '&:last-child': { borderBottom: 'none' } }} key={option?._id} cursor={'pointer'} | |
| 113 | + onClick={() => { | |
| 114 | + setFieldValue('createBy', option?._id) | |
| 115 | + setFieldValue('recipient', option.phoneNumber.toString()) | |
| 116 | + }} | |
| 117 | + > | |
| 118 | + {option.phoneNumber} | |
| 119 | + </ListItem> | |
| 120 | + ))} | |
| 121 | + </List> | |
| 122 | + )} | |
| 123 | + <Text mb='10px' color={'red'}> {errors.recipient && touched.recipient && errors.recipient}</Text> | |
| 124 | + </GridItem> | |
| 125 | + <GridItem colSpan={{ base: 12 }} > | |
| 126 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='8px'> | |
| 127 | + Call Notes | |
| 128 | + </FormLabel> | |
| 129 | + <Textarea | |
| 130 | + fontSize='sm' | |
| 131 | + height={'20vh'} | |
| 132 | + resize={'none'} | |
| 133 | + onChange={handleChange} | |
| 134 | + value={values.callNotes} | |
| 135 | + name="callNotes" | |
| 136 | + placeholder='callNotes' | |
| 137 | + fontWeight='500' | |
| 138 | + borderColor={errors?.callNotes && touched?.callNotes ? "red.300" : null} | |
| 139 | + /> | |
| 140 | + <Text mb='10px' color={'red'}> {errors.callNotes && touched.callNotes && errors.callNotes}</Text> | |
| 141 | + </GridItem> | |
| 142 | + | |
| 143 | + <GridItem colSpan={{ base: 12 }} > | |
| 144 | + <Button variant='brand' disabled={isLoding ? true : false} size="sm" onClick={handleSubmit} leftIcon={<BsFillTelephoneFill />}>{isLoding ? <Spinner /> : 'Call'}</Button> | |
| 145 | + <Button onClick={() => formik.resetForm()} size="sm">Clear</Button> | |
| 146 | + </GridItem> | |
| 147 | + | |
| 148 | + </Grid> | |
| 149 | + ) | |
| 150 | +} | |
| 151 | + | |
| 152 | +export default Call |
+190-0Client/src/views/admin/communication/components/email.js
| @@ -0,0 +1,190 @@ | ||
| 1 | +import { Button, FormLabel, Grid, GridItem, Heading, Input, List, ListItem, Text, Textarea, useDisclosure } from '@chakra-ui/react'; | |
| 2 | +import Spinner from 'components/spinner/Spinner'; | |
| 3 | +import { useFormik } from 'formik'; | |
| 4 | +import { useEffect, useState } from 'react'; | |
| 5 | +import { BsFillSendFill } from 'react-icons/bs'; | |
| 6 | +import { toast } from 'react-toastify'; | |
| 7 | +import { emailSchema } from 'schema'; | |
| 8 | +import { getApi } from 'services/api'; | |
| 9 | +import { postApi } from 'services/api'; | |
| 10 | + | |
| 11 | +const Email = () => { | |
| 12 | + const [data, setData] = useState([]) | |
| 13 | + const user = JSON.parse(localStorage.getItem('user')) | |
| 14 | + const { isOpen, onOpen, onClose } = useDisclosure(); | |
| 15 | + const [isLoding, setIsLoding] = useState(false) | |
| 16 | + | |
| 17 | + const initialValues = { | |
| 18 | + sender: user?._id, | |
| 19 | + recipient: '', | |
| 20 | + cc: '', | |
| 21 | + bcc: '', | |
| 22 | + subject: '', | |
| 23 | + message: '', | |
| 24 | + createBy: '' | |
| 25 | + }; | |
| 26 | + | |
| 27 | + const formik = useFormik({ | |
| 28 | + initialValues: initialValues, | |
| 29 | + validationSchema: emailSchema, | |
| 30 | + onSubmit: (values, { resetForm }) => { | |
| 31 | + AddData(); | |
| 32 | + }, | |
| 33 | + }); | |
| 34 | + | |
| 35 | + const { errors, touched, values, handleBlur, handleChange, handleSubmit, setFieldValue, } = formik | |
| 36 | + | |
| 37 | + if (touched.recipient && errors.createBy) { | |
| 38 | + toast.error('Please select an authorized recipient'); | |
| 39 | + formik.resetForm(); | |
| 40 | + | |
| 41 | + } | |
| 42 | + | |
| 43 | + const AddData = async () => { | |
| 44 | + try { | |
| 45 | + setIsLoding(true) | |
| 46 | + let response = await postApi('api/email/add', values) | |
| 47 | + if (response.status === 200) { | |
| 48 | + toast.success('Email Send successfully') | |
| 49 | + formik.resetForm(); | |
| 50 | + } | |
| 51 | + } catch (e) { | |
| 52 | + console.log(e); | |
| 53 | + } | |
| 54 | + finally { | |
| 55 | + setIsLoding(false) | |
| 56 | + } | |
| 57 | + }; | |
| 58 | + | |
| 59 | + | |
| 60 | + const fetchData = async () => { | |
| 61 | + let result = await getApi(user.role === 'superAdmin' ? 'api/contact/' : `api/contact/?createBy=${user._id}`); | |
| 62 | + values.createBy = result?._id; | |
| 63 | + setData(result.data); | |
| 64 | + } | |
| 65 | + | |
| 66 | + useEffect(() => { | |
| 67 | + fetchData() | |
| 68 | + }, []); | |
| 69 | + | |
| 70 | + useEffect(() => { | |
| 71 | + data?.map((item) => { | |
| 72 | + let recipientExists = item.email === values.recipient | |
| 73 | + if (recipientExists) { | |
| 74 | + values.createBy = item._id | |
| 75 | + setFieldValue('createBy', item._id) | |
| 76 | + } | |
| 77 | + }) | |
| 78 | + }, [values]) | |
| 79 | + | |
| 80 | + return ( | |
| 81 | + <Grid templateColumns="repeat(12, 1fr)" gap={3}> | |
| 82 | + <GridItem colSpan={{ base: 12 }} > | |
| 83 | + <Heading as='h2' size='xl' noOfLines={1}> | |
| 84 | + Send Email | |
| 85 | + </Heading> | |
| 86 | + </GridItem> | |
| 87 | + <GridItem colSpan={{ base: 12 }} > | |
| 88 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='8px'> | |
| 89 | + To<Text color={"red"}>*</Text> | |
| 90 | + </FormLabel> | |
| 91 | + <Input | |
| 92 | + onFocus={onOpen} | |
| 93 | + fontSize='sm' | |
| 94 | + onChange={handleChange} | |
| 95 | + onBlur={() => setTimeout(onClose, 200)} | |
| 96 | + value={values.recipient} | |
| 97 | + name="recipient" | |
| 98 | + placeholder='Recipient' | |
| 99 | + fontWeight='500' | |
| 100 | + borderColor={errors?.recipient && touched?.recipient ? "red.300" : null} | |
| 101 | + /> | |
| 102 | + {isOpen && values?.recipient && ( | |
| 103 | + <List position={'relative'} border={'1px solid'} bg={'gray.100'} width={'100%'} borderRadius={'0px 0px 20px 20px'} lineHeight={1} > | |
| 104 | + {data?.filter((option) => option?.email?.includes(values?.recipient.toLowerCase())).map((option, index) => ( | |
| 105 | + <ListItem p={3} borderBottom={'2px solid #efefef'} sx={{ '&:last-child': { borderBottom: 'none' } }} key={option?._id} cursor={'pointer'} | |
| 106 | + onClick={() => { | |
| 107 | + setFieldValue('createBy', option?._id) | |
| 108 | + setFieldValue('recipient', option?.email) | |
| 109 | + }} | |
| 110 | + > | |
| 111 | + {option?.email} | |
| 112 | + </ListItem> | |
| 113 | + ))} | |
| 114 | + </List> | |
| 115 | + )} | |
| 116 | + <Text mb='10px' color={'red'}> {errors.recipient && touched.recipient && errors.recipient}</Text> | |
| 117 | + </GridItem> | |
| 118 | + <GridItem colSpan={{ base: 12 }} > | |
| 119 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='8px'> | |
| 120 | + Sc | |
| 121 | + </FormLabel> | |
| 122 | + <Input | |
| 123 | + fontSize='sm' | |
| 124 | + onChange={handleChange} | |
| 125 | + value={values.cc} | |
| 126 | + name="cc" | |
| 127 | + placeholder='cc' | |
| 128 | + fontWeight='500' | |
| 129 | + borderColor={errors?.cc && touched?.cc ? "red.300" : null} | |
| 130 | + /> | |
| 131 | + | |
| 132 | + <Text mb='10px' color={'red'}> {errors.cc && touched.cc && errors.cc}</Text> | |
| 133 | + </GridItem> | |
| 134 | + <GridItem colSpan={{ base: 12 }} > | |
| 135 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='8px'> | |
| 136 | + Bcc | |
| 137 | + </FormLabel> | |
| 138 | + <Input | |
| 139 | + fontSize='sm' | |
| 140 | + onChange={handleChange} | |
| 141 | + value={values.bcc} | |
| 142 | + name="bcc" | |
| 143 | + placeholder='bcc' | |
| 144 | + fontWeight='500' | |
| 145 | + borderColor={errors?.bcc && touched?.bcc ? "red.300" : null} | |
| 146 | + /> | |
| 147 | + <Text mb='10px' color={'red'}> {errors.bcc && touched.bcc && errors.bcc}</Text> | |
| 148 | + </GridItem> | |
| 149 | + <GridItem colSpan={{ base: 12 }} > | |
| 150 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='8px'> | |
| 151 | + Subject | |
| 152 | + </FormLabel> | |
| 153 | + <Input | |
| 154 | + fontSize='sm' | |
| 155 | + onChange={handleChange} | |
| 156 | + value={values.subject} | |
| 157 | + name="subject" | |
| 158 | + placeholder='Subject' | |
| 159 | + fontWeight='500' | |
| 160 | + borderColor={errors?.subject && touched?.subject ? "red.300" : null} | |
| 161 | + /> | |
| 162 | + <Text mb='10px' color={'red'}> {errors.subject && touched.subject && errors.subject}</Text> | |
| 163 | + </GridItem> | |
| 164 | + <GridItem colSpan={{ base: 12 }} > | |
| 165 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='8px'> | |
| 166 | + Message<Text color={"red"}>*</Text> | |
| 167 | + </FormLabel> | |
| 168 | + <Textarea | |
| 169 | + height={240} | |
| 170 | + resize={'none'} | |
| 171 | + fontSize='sm' | |
| 172 | + onChange={handleChange} | |
| 173 | + value={values.message} | |
| 174 | + name="message" | |
| 175 | + placeholder='Enter Message Hear' | |
| 176 | + fontWeight='500' | |
| 177 | + borderColor={errors?.message && touched?.message ? "red.300" : null} | |
| 178 | + /> | |
| 179 | + <Text mb='10px' color={'red'}> {errors.message && touched.message && errors.message}</Text> | |
| 180 | + </GridItem> | |
| 181 | + <GridItem colSpan={{ base: 12 }} > | |
| 182 | + <Button size="sm" variant='brand' onClick={handleSubmit} disabled={isLoding ? true : false} rightIcon={<BsFillSendFill />}> {isLoding ? <Spinner /> : 'Send'}</Button> | |
| 183 | + <Button size="sm" onClick={() => formik.resetForm()}>Clear</Button> | |
| 184 | + </GridItem> | |
| 185 | + | |
| 186 | + </Grid> | |
| 187 | + ) | |
| 188 | +} | |
| 189 | + | |
| 190 | +export default Email |
+40-0Client/src/views/admin/communication/index.js
| @@ -0,0 +1,40 @@ | ||
| 1 | +import { Tab, TabList, TabPanel, TabPanels, Tabs } from '@chakra-ui/react' | |
| 2 | +import Card from 'components/card/Card' | |
| 3 | +import React from 'react' | |
| 4 | +import Email from './components/email' | |
| 5 | +import Call from './components/call' | |
| 6 | +import TextMsg from './components/TextMsg' | |
| 7 | + | |
| 8 | +const Index = () => { | |
| 9 | + return ( | |
| 10 | + <div> | |
| 11 | + <Tabs > | |
| 12 | + <TabList sx={{ '& button:focus': { boxShadow: 'none', }, }}> | |
| 13 | + <Tab >Email</Tab> | |
| 14 | + <Tab>Call</Tab> | |
| 15 | + <Tab>Text Msg</Tab> | |
| 16 | + </TabList> | |
| 17 | + | |
| 18 | + <TabPanels> | |
| 19 | + <TabPanel> | |
| 20 | + <Card> | |
| 21 | + <Email /> | |
| 22 | + </Card> | |
| 23 | + </TabPanel> | |
| 24 | + <TabPanel> | |
| 25 | + <Card> | |
| 26 | + <Call /> | |
| 27 | + </Card> | |
| 28 | + </TabPanel> | |
| 29 | + <TabPanel> | |
| 30 | + <Card> | |
| 31 | + <TextMsg /> | |
| 32 | + </Card> | |
| 33 | + </TabPanel> | |
| 34 | + </TabPanels> | |
| 35 | + </Tabs> | |
| 36 | + </div> | |
| 37 | + ) | |
| 38 | +} | |
| 39 | + | |
| 40 | +export default Index |
+98-0Client/src/views/admin/contact/Add.js
| @@ -0,0 +1,98 @@ | ||
| 1 | +import { CloseIcon } from '@chakra-ui/icons'; | |
| 2 | +import { Button, Drawer, DrawerBody, DrawerContent, DrawerFooter, DrawerHeader, DrawerOverlay, IconButton } from '@chakra-ui/react'; | |
| 3 | +import Spinner from 'components/spinner/Spinner'; | |
| 4 | +import { useFormik } from 'formik'; | |
| 5 | +import { useState } from 'react'; | |
| 6 | +import { postApi } from 'services/api'; | |
| 7 | +import { generateValidationSchema } from 'utils'; | |
| 8 | +import CustomForm from 'utils/customForm'; | |
| 9 | +import * as yup from 'yup' | |
| 10 | + | |
| 11 | +const Add = (props) => { | |
| 12 | + const [isLoding, setIsLoding] = useState(false) | |
| 13 | + | |
| 14 | + const initialFieldValues = Object.fromEntries( | |
| 15 | + (props?.contactData?.fields || []).map(field => [field?.name, '']) | |
| 16 | + ); | |
| 17 | + const initialValues = { | |
| 18 | + ...initialFieldValues, | |
| 19 | + createBy: JSON.parse(localStorage.getItem('user'))._id | |
| 20 | + }; | |
| 21 | + | |
| 22 | + const formik = useFormik({ | |
| 23 | + initialValues: initialValues, | |
| 24 | + validationSchema: yup.object().shape(generateValidationSchema(props?.contactData?.fields)), | |
| 25 | + onSubmit: (values, { resetForm }) => { | |
| 26 | + AddData(); | |
| 27 | + resetForm(); | |
| 28 | + }, | |
| 29 | + }); | |
| 30 | + | |
| 31 | + const { errors, touched, values, handleBlur, handleChange, handleSubmit, setFieldValue, } = formik | |
| 32 | + | |
| 33 | + const AddData = async () => { | |
| 34 | + try { | |
| 35 | + setIsLoding(true) | |
| 36 | + let response = await postApi('api/form/add', { ...values, moduleId: props?.contactData?._id }) | |
| 37 | + if (response.status === 200) { | |
| 38 | + props.onClose(); | |
| 39 | + props.setAction((pre) => !pre) | |
| 40 | + } | |
| 41 | + } catch (e) { | |
| 42 | + console.log(e); | |
| 43 | + } | |
| 44 | + finally { | |
| 45 | + setIsLoding(false) | |
| 46 | + } | |
| 47 | + | |
| 48 | + }; | |
| 49 | + | |
| 50 | + const handleCancel = () => { | |
| 51 | + formik.resetForm(); | |
| 52 | + props.onClose() | |
| 53 | + } | |
| 54 | + | |
| 55 | + return ( | |
| 56 | + <div> | |
| 57 | + <Drawer isOpen={props.isOpen} size={props.size}> | |
| 58 | + <DrawerOverlay /> | |
| 59 | + <DrawerContent> | |
| 60 | + <DrawerHeader alignItems={"center"} justifyContent='space-between' display='flex' > | |
| 61 | + Add Contact | |
| 62 | + <IconButton onClick={props.onClose} icon={<CloseIcon />} /> | |
| 63 | + </DrawerHeader> | |
| 64 | + <DrawerBody> | |
| 65 | + <CustomForm moduleData={props.contactData} values={values} setFieldValue={setFieldValue} handleChange={handleChange} handleBlur={handleBlur} errors={errors} touched={touched} /> | |
| 66 | + </DrawerBody> | |
| 67 | + | |
| 68 | + | |
| 69 | + <DrawerFooter> | |
| 70 | + <Button | |
| 71 | + sx={{ textTransform: "capitalize" }} | |
| 72 | + variant="brand" | |
| 73 | + disabled={isLoding ? true : false} | |
| 74 | + type="submit" size="sm" | |
| 75 | + onClick={handleSubmit} | |
| 76 | + > | |
| 77 | + {isLoding ? <Spinner /> : 'Save'} | |
| 78 | + </Button> | |
| 79 | + <Button | |
| 80 | + variant="outline" | |
| 81 | + colorScheme='red' size="sm" | |
| 82 | + sx={{ | |
| 83 | + marginLeft: 2, | |
| 84 | + textTransform: "capitalize", | |
| 85 | + }} | |
| 86 | + onClick={handleCancel} | |
| 87 | + > | |
| 88 | + Close | |
| 89 | + </Button> | |
| 90 | + </DrawerFooter> | |
| 91 | + | |
| 92 | + </DrawerContent> | |
| 93 | + </Drawer> | |
| 94 | + </div> | |
| 95 | + ) | |
| 96 | +} | |
| 97 | + | |
| 98 | +export default Add |
+167-0Client/src/views/admin/contact/Edit.js
| @@ -0,0 +1,167 @@ | ||
| 1 | +import { CloseIcon } from '@chakra-ui/icons'; | |
| 2 | +import { Button, Drawer, DrawerBody, DrawerContent, DrawerFooter, DrawerHeader, DrawerOverlay, Flex, IconButton } from '@chakra-ui/react'; | |
| 3 | +import Spinner from 'components/spinner/Spinner'; | |
| 4 | +import { useFormik } from 'formik'; | |
| 5 | +import { useEffect, useState } from 'react'; | |
| 6 | +import { useParams } from 'react-router-dom'; | |
| 7 | +import { getApi, putApi } from 'services/api'; | |
| 8 | +import { generateValidationSchema } from '../../../utils'; | |
| 9 | +import * as yup from 'yup' | |
| 10 | +import CustomForm from 'utils/customForm'; | |
| 11 | + | |
| 12 | +const Edit = (props) => { | |
| 13 | + const { data } = props; | |
| 14 | + const [isLoding, setIsLoding] = useState(false) | |
| 15 | + const initialFieldValues = Object.fromEntries( | |
| 16 | + (props?.contactData?.fields || []).map(field => [field?.name, '']) | |
| 17 | + ); | |
| 18 | + | |
| 19 | + // lastName: "", | |
| 20 | + // title: "", | |
| 21 | + // email: "", | |
| 22 | + // phoneNumber: "", | |
| 23 | + // mobileNumber: "", | |
| 24 | + // physicalAddress: "", | |
| 25 | + // mailingAddress: "", | |
| 26 | + // preferredContactMethod: "", | |
| 27 | + // // 2.Lead Source Information | |
| 28 | + // leadSource: "", | |
| 29 | + // referralSource: "", | |
| 30 | + // campaignSource: "", | |
| 31 | + // // 3. Status and Classifications | |
| 32 | + // leadStatus: "", | |
| 33 | + // leadRating: "", | |
| 34 | + // leadConversionProbability: "", | |
| 35 | + // // 4. Property of Interest | |
| 36 | + // // 5. History: | |
| 37 | + // notesandComments: "", | |
| 38 | + // // 6. Tags or Categories | |
| 39 | + // tagsOrLabelsForcategorizingcontacts: "", | |
| 40 | + // // 7. Important Dates:: | |
| 41 | + // birthday: "", | |
| 42 | + // anniversary: "", | |
| 43 | + // keyMilestones: "", | |
| 44 | + // // 8. Additional Personal Information | |
| 45 | + // dob: "", | |
| 46 | + // gender: "", | |
| 47 | + // occupation: "", | |
| 48 | + // interestsOrHobbies: "", | |
| 49 | + // // 9. Preferred Communication Preferences: | |
| 50 | + // communicationFrequency: "", | |
| 51 | + // preferences: "", | |
| 52 | + // // 10. Social Media Profiles: | |
| 53 | + // linkedInProfile: "", | |
| 54 | + // facebookProfile: "", | |
| 55 | + // twitterHandle: "", | |
| 56 | + // otherProfiles: "", | |
| 57 | + // // 11. Lead Assignment and Team Collaboration: | |
| 58 | + // agentOrTeamMember: "", | |
| 59 | + // internalNotesOrComments: "", | |
| 60 | + // createBy: JSON.parse(localStorage.getItem('user'))._id, | |
| 61 | + // }); | |
| 62 | + const [initialValues, setInitialValues] = useState({ | |
| 63 | + ...initialFieldValues, | |
| 64 | + createBy: JSON.parse(localStorage.getItem('user'))._id | |
| 65 | + }); | |
| 66 | + const param = useParams() | |
| 67 | + | |
| 68 | + const formik = useFormik({ | |
| 69 | + initialValues: initialValues, | |
| 70 | + enableReinitialize: true, | |
| 71 | + validationSchema: yup.object().shape(generateValidationSchema(props?.contactData?.fields)), | |
| 72 | + onSubmit: (values, { resetForm }) => { | |
| 73 | + EditData(); | |
| 74 | + }, | |
| 75 | + }); | |
| 76 | + | |
| 77 | + const { errors, touched, values, handleBlur, handleChange, handleSubmit, setFieldValue, } = formik | |
| 78 | + | |
| 79 | + const EditData = async () => { | |
| 80 | + try { | |
| 81 | + setIsLoding(true) | |
| 82 | + let response = await putApi(`api/form/edit/${props?.selectedId || param.id}`, { ...values, moduleId: props?.moduleId }) | |
| 83 | + if (response.status === 200) { | |
| 84 | + props.onClose(); | |
| 85 | + props.setAction((pre) => !pre) | |
| 86 | + } | |
| 87 | + } catch (e) { | |
| 88 | + console.log(e); | |
| 89 | + } finally { | |
| 90 | + setIsLoding(false) | |
| 91 | + } | |
| 92 | + }; | |
| 93 | + | |
| 94 | + const handleClose = () => { | |
| 95 | + props.onClose(false) | |
| 96 | + props.setSelectedId && props?.setSelectedId() | |
| 97 | + } | |
| 98 | + let response | |
| 99 | + const fetchData = async () => { | |
| 100 | + if (data) { | |
| 101 | + setInitialValues((prev) => ({ ...prev, ...data })) | |
| 102 | + } else if (props?.selectedId || param.id) { | |
| 103 | + try { | |
| 104 | + setIsLoding(true) | |
| 105 | + response = await getApi('api/contact/view/', props?.selectedId ? props?.selectedId : param.id) | |
| 106 | + setInitialValues((prev) => ({ ...prev, ...response?.data?.contact })) | |
| 107 | + } catch (e) { | |
| 108 | + console.error(e) | |
| 109 | + } finally { | |
| 110 | + setIsLoding(false) | |
| 111 | + } | |
| 112 | + } | |
| 113 | + } | |
| 114 | + useEffect(() => { | |
| 115 | + fetchData() | |
| 116 | + }, [props?.selectedId, data]) | |
| 117 | + | |
| 118 | + return ( | |
| 119 | + <div> | |
| 120 | + <Drawer isOpen={props.isOpen} size={props.size}> | |
| 121 | + <DrawerOverlay /> | |
| 122 | + <DrawerContent> | |
| 123 | + <DrawerHeader alignItems={"center"} justifyContent='space-between' display='flex' > | |
| 124 | + Edit Contact | |
| 125 | + <IconButton onClick={handleClose} icon={<CloseIcon />} /> | |
| 126 | + </DrawerHeader> | |
| 127 | + <DrawerBody> | |
| 128 | + {isLoding ? | |
| 129 | + <Flex justifyContent={'center'} alignItems={'center'} width="100%" > | |
| 130 | + <Spinner /> | |
| 131 | + </Flex> | |
| 132 | + : | |
| 133 | + | |
| 134 | + <CustomForm moduleData={props.contactData} values={values} setFieldValue={setFieldValue} handleChange={handleChange} handleBlur={handleBlur} errors={errors} touched={touched} /> | |
| 135 | + | |
| 136 | + } | |
| 137 | + </DrawerBody> | |
| 138 | + | |
| 139 | + <DrawerFooter> | |
| 140 | + <Button | |
| 141 | + sx={{ textTransform: "capitalize" }} | |
| 142 | + variant="brand" | |
| 143 | + type="submit" size="sm" | |
| 144 | + disabled={isLoding ? true : false} | |
| 145 | + onClick={handleSubmit} | |
| 146 | + > | |
| 147 | + {isLoding ? <Spinner /> : 'Update'} | |
| 148 | + </Button> | |
| 149 | + <Button | |
| 150 | + variant="outline" | |
| 151 | + colorScheme='red' size="sm" | |
| 152 | + sx={{ | |
| 153 | + marginLeft: 2, | |
| 154 | + textTransform: "capitalize", | |
| 155 | + }} | |
| 156 | + onClick={handleClose} | |
| 157 | + > | |
| 158 | + Close | |
| 159 | + </Button> | |
| 160 | + </DrawerFooter> | |
| 161 | + </DrawerContent> | |
| 162 | + </Drawer> | |
| 163 | + </div> | |
| 164 | + ) | |
| 165 | +} | |
| 166 | + | |
| 167 | +export default Edit |
+807-0Client/src/views/admin/contact/View.js
| @@ -0,0 +1,807 @@ | ||
| 1 | +import { AddIcon, ChevronDownIcon, DeleteIcon, EditIcon } from "@chakra-ui/icons"; | |
| 2 | +import { Box, Button, Flex, Grid, GridItem, Heading, IconButton, Menu, MenuButton, MenuDivider, MenuItem, MenuList, Tab, TabList, TabPanel, TabPanels, Tabs, Text, VStack, useColorModeValue, useDisclosure } from "@chakra-ui/react"; | |
| 3 | +import FolderTreeView from 'components/FolderTreeView/folderTreeView'; | |
| 4 | +import Card from "components/card/Card"; | |
| 5 | +import { HSeparator } from "components/separator/Separator"; | |
| 6 | +import Spinner from "components/spinner/Spinner"; | |
| 7 | +import { constant } from "constant"; | |
| 8 | +import { useEffect, useState } from "react"; | |
| 9 | +import { BiLink, BiLogoLinkedin } from "react-icons/bi"; | |
| 10 | +import { BsTwitter } from "react-icons/bs"; | |
| 11 | +import { FaFacebook } from "react-icons/fa"; | |
| 12 | +import { IoIosArrowBack } from "react-icons/io"; | |
| 13 | +import { LuBuilding2 } from "react-icons/lu"; | |
| 14 | +import { Link, useNavigate, useParams } from "react-router-dom"; | |
| 15 | +import { toast } from "react-toastify"; | |
| 16 | +import { getApi } from "services/api"; | |
| 17 | +import AddEmailHistory from "../emailHistory/components/AddEmail"; | |
| 18 | +import AddMeeting from "../meeting/components/Addmeeting"; | |
| 19 | +import AddPhoneCall from "../phoneCall/components/AddPhoneCall"; | |
| 20 | +import Add from "./Add"; | |
| 21 | +import Edit from "./Edit"; | |
| 22 | +import PhoneCall from "./components/phonCall"; | |
| 23 | +import PropertyModel from "./components/propertyModel"; | |
| 24 | +import PropertyTable from "./components/propertyTable"; | |
| 25 | +import { HasAccess } from "../../../redux/accessUtils"; | |
| 26 | +import DataNotFound from "components/notFoundData"; | |
| 27 | +import CustomView from "utils/customView"; | |
| 28 | +import AddDocumentModal from "utils/addDocumentModal"; | |
| 29 | +import CommonDeleteModel from "components/commonDeleteModel"; | |
| 30 | +import { deleteApi } from "services/api"; | |
| 31 | +import CommonCheckTable from "components/reactTable/checktable"; | |
| 32 | +import moment from 'moment'; | |
| 33 | +import AddEdit from '../task/components/AddEdit' | |
| 34 | +import { useDispatch, useSelector } from "react-redux"; | |
| 35 | +import { fetchContactCustomFiled } from '../../../redux/slices/contactCustomFiledSlice'; | |
| 36 | +import { fetchPropertyCustomFiled } from "../../../redux/slices/propertyCustomFiledSlice"; | |
| 37 | +import html2pdf from "html2pdf.js"; | |
| 38 | +import { FaFilePdf } from "react-icons/fa"; | |
| 39 | +import AddEditQuotes from '../quotes/AddEdit' | |
| 40 | +import AddEditInvoice from '../invoice/AddEdit' | |
| 41 | +const View = () => { | |
| 42 | + | |
| 43 | + const param = useParams() | |
| 44 | + const textColor = useColorModeValue("gray.500", "white"); | |
| 45 | + | |
| 46 | + const user = JSON.parse(localStorage.getItem("user")); | |
| 47 | + const buttonbg = useColorModeValue("gray.200", "white"); | |
| 48 | + const [data, setData] = useState([]) | |
| 49 | + const [allData, setAllData] = useState([]); | |
| 50 | + // const [contactData, setContactData] = useState([]); | |
| 51 | + const { isOpen, onOpen, onClose } = useDisclosure() | |
| 52 | + const [edit, setEdit] = useState(false); | |
| 53 | + const [deleteModel, setDelete] = useState(false); | |
| 54 | + const [propertyModel, setPropertyModel] = useState(false); | |
| 55 | + const [isLoding, setIsLoding] = useState(false) | |
| 56 | + const [action, setAction] = useState(false) | |
| 57 | + | |
| 58 | + const [taskModel, setTaskModel] = useState(false); | |
| 59 | + const [addEmailHistory, setAddEmailHistory] = useState(false); | |
| 60 | + const [addPhoneCall, setAddPhoneCall] = useState(false); | |
| 61 | + const [addQuotes, setAddQuotes] = useState(false); | |
| 62 | + const [addInvoice, setAddInvoice] = useState(false); | |
| 63 | + const [addMeeting, setMeeting] = useState(false); | |
| 64 | + const [showEmail, setShowEmail] = useState(false); | |
| 65 | + const [showCall, setShowCall] = useState(false); | |
| 66 | + const [showTasks, setShowTasks] = useState(false); | |
| 67 | + const [showMeetings, setShowMeetings] = useState(false); | |
| 68 | + const [showQuotes, setShowQuotes] = useState(false); | |
| 69 | + const [showInvoices, setShowInvoices] = useState(false); | |
| 70 | + const [addDocument, setAddDocument] = useState(false); | |
| 71 | + | |
| 72 | + const [selectedTab, setSelectedTab] = useState(0); | |
| 73 | + const size = "lg"; | |
| 74 | + const navigate = useNavigate() | |
| 75 | + const dispatch = useDispatch() | |
| 76 | + | |
| 77 | + const contactData = useSelector((state) => state?.contactCustomFiled?.data?.data) | |
| 78 | + | |
| 79 | + const [propertyData, setPropertyData] = useState([]); | |
| 80 | + const [columns, setColumns] = useState([]); | |
| 81 | + const [permission, callAccess, emailAccess, taskAccess, meetingAccess, quotesAccess, invoicesAccess, accountAccess] = HasAccess(['Contacts', 'Calls', 'Emails', 'Tasks', 'Meetings', 'Quotes', 'Invoices', 'Account']); | |
| 82 | + const columnsDataColumns = [ | |
| 83 | + { Header: "sender", accessor: "senderName", }, | |
| 84 | + { | |
| 85 | + Header: "recipient", accessor: "createByName", cell: (cell) => ( | |
| 86 | + <Link to={`/Email/${cell?.row?.original?._id}`}> | |
| 87 | + <Text | |
| 88 | + me="10px" | |
| 89 | + sx={{ '&:hover': { color: 'blue.500', textDecoration: 'underline' } }} | |
| 90 | + color='brand.600' | |
| 91 | + fontSize="sm" | |
| 92 | + fontWeight="700" | |
| 93 | + > | |
| 94 | + {cell?.value || '-'} | |
| 95 | + </Text> | |
| 96 | + </Link>) | |
| 97 | + }, | |
| 98 | + { | |
| 99 | + Header: "time stamp", accessor: "timestamp", | |
| 100 | + cell: (cell) => ( | |
| 101 | + <div className="selectOpt"> | |
| 102 | + <Text color={textColor} fontSize='sm' fontWeight='700'> | |
| 103 | + {moment(cell?.value).fromNow()} | |
| 104 | + </Text> | |
| 105 | + </div> | |
| 106 | + ) | |
| 107 | + }, | |
| 108 | + { | |
| 109 | + Header: "Created", accessor: "createBy", | |
| 110 | + cell: (cell) => ( | |
| 111 | + <div className="selectOpt"> | |
| 112 | + <Text color={textColor} fontSize='sm' fontWeight='700'> | |
| 113 | + {moment(cell?.row?.values.timestamp).format('h:mma (DD/MM)')} | |
| 114 | + </Text> | |
| 115 | + </div> | |
| 116 | + ) | |
| 117 | + }, | |
| 118 | + ]; | |
| 119 | + const callColumns = [ | |
| 120 | + { Header: "sender", accessor: "senderName", }, | |
| 121 | + { | |
| 122 | + Header: "recipient", accessor: "createByName", cell: (cell) => ( | |
| 123 | + <Link to={`/phone-call/${cell?.row?.original?._id}`}> | |
| 124 | + <Text | |
| 125 | + me="10px" | |
| 126 | + sx={{ '&:hover': { color: 'blue.500', textDecoration: 'underline' } }} | |
| 127 | + color='brand.600' | |
| 128 | + fontSize="sm" | |
| 129 | + fontWeight="700" | |
| 130 | + > | |
| 131 | + {cell?.value || '-'} | |
| 132 | + </Text> | |
| 133 | + </Link>) | |
| 134 | + }, | |
| 135 | + { | |
| 136 | + Header: "time stamp", accessor: "timestamp", | |
| 137 | + cell: (cell) => ( | |
| 138 | + <div className="selectOpt"> | |
| 139 | + <Text color={textColor} fontSize='sm' fontWeight='700'> | |
| 140 | + {moment(cell?.value).fromNow()} | |
| 141 | + </Text> | |
| 142 | + </div> | |
| 143 | + ) | |
| 144 | + }, | |
| 145 | + { | |
| 146 | + Header: "Created", accessor: "createBy", | |
| 147 | + cell: (cell) => ( | |
| 148 | + <div className="selectOpt"> | |
| 149 | + <Text color={textColor} fontSize='sm' fontWeight='700'> | |
| 150 | + {moment(cell?.row?.values.timestamp).format('h:mma (DD/MM)')} | |
| 151 | + </Text> | |
| 152 | + </div> | |
| 153 | + ) | |
| 154 | + }, | |
| 155 | + ]; | |
| 156 | + | |
| 157 | + const PropertyColumn = [ | |
| 158 | + { Header: 'property Type', accessor: 'propertyType' }, | |
| 159 | + { Header: "property Address", accessor: "propertyAddress", }, | |
| 160 | + { Header: "listing Price", accessor: "listingPrice", }, | |
| 161 | + { Header: "square Footage", accessor: "squareFootage", }, | |
| 162 | + { Header: "year Built", accessor: "yearBuilt", }, | |
| 163 | + ]; | |
| 164 | + const fetchCustomDataFields = async () => { | |
| 165 | + setIsLoding(true); | |
| 166 | + const result = await dispatch(fetchPropertyCustomFiled()) | |
| 167 | + setPropertyData(result?.payload?.data); | |
| 168 | + | |
| 169 | + const tempTableColumns = [ | |
| 170 | + { Header: "#", accessor: "_id", isSortable: false, width: 10 }, | |
| 171 | + ...result?.payload?.data?.[0]?.fields?.filter((field) => field?.isTableField === true)?.map((field) => ({ Header: field?.label, accessor: field?.name })), | |
| 172 | + ]; | |
| 173 | + | |
| 174 | + setColumns(tempTableColumns); | |
| 175 | + setIsLoding(false); | |
| 176 | + } | |
| 177 | + const MeetingColumns = [ | |
| 178 | + { | |
| 179 | + Header: 'agenda', accessor: 'agenda', cell: (cell) => ( | |
| 180 | + <Link to={`/metting/${cell?.row?.original?._id}`}> | |
| 181 | + <Text | |
| 182 | + me="10px" | |
| 183 | + sx={{ '&:hover': { color: 'blue.500', textDecoration: 'underline' } }} | |
| 184 | + color='brand.600' | |
| 185 | + fontSize="sm" | |
| 186 | + fontWeight="700" | |
| 187 | + > | |
| 188 | + {cell?.value || '-'} | |
| 189 | + </Text> | |
| 190 | + </Link>) | |
| 191 | + }, | |
| 192 | + { Header: "date Time", accessor: "dateTime", }, | |
| 193 | + { | |
| 194 | + Header: "times tamp", accessor: "timestamp", | |
| 195 | + cell: (cell) => ( | |
| 196 | + <div className="selectOpt"> | |
| 197 | + <Text color={textColor} fontSize='sm' fontWeight='700'> | |
| 198 | + {moment(cell?.value).fromNow()} | |
| 199 | + </Text> | |
| 200 | + </div> | |
| 201 | + ) | |
| 202 | + }, | |
| 203 | + { Header: "create By", accessor: "createdByName", }, | |
| 204 | + ]; | |
| 205 | + const quotesColumns = [ | |
| 206 | + { Header: "Quote Number", accessor: "quoteNumber", isSortable: false, width: 10 }, | |
| 207 | + { | |
| 208 | + Header: 'Title', accessor: 'title', cell: (cell) => ( | |
| 209 | + <div className="selectOpt"> | |
| 210 | + <Text | |
| 211 | + onClick={() => navigate(`/quotesView/${cell?.row?.original._id}`)} | |
| 212 | + me="10px" | |
| 213 | + sx={{ '&:hover': { color: 'blue.500', textDecoration: 'underline' }, cursor: 'pointer' }} | |
| 214 | + color='brand.600' | |
| 215 | + fontSize="sm" | |
| 216 | + fontWeight="700" | |
| 217 | + > | |
| 218 | + {cell?.value} | |
| 219 | + </Text> | |
| 220 | + </div> | |
| 221 | + ) | |
| 222 | + }, | |
| 223 | + { Header: 'Quote Stage', accessor: 'quoteStage' }, | |
| 224 | + { | |
| 225 | + Header: 'Contact', accessor: 'contact', | |
| 226 | + cell: (cell) => ( | |
| 227 | + <Text | |
| 228 | + > | |
| 229 | + {cell?.row?.original?.contactName ? cell?.row?.original?.contactName : "-"} | |
| 230 | + </Text> | |
| 231 | + ) | |
| 232 | + }, | |
| 233 | + { | |
| 234 | + Header: 'Account', accessor: 'account', | |
| 235 | + cell: (cell) => ( | |
| 236 | + (user.role === 'superAdmin' || accountAccess?.view) ? | |
| 237 | + <div className="selectOpt"> | |
| 238 | + <Text | |
| 239 | + onClick={() => navigate(cell?.row?.original.account !== null && `/accountView/${cell?.row?.original.account}`)} | |
| 240 | + me="10px" | |
| 241 | + sx={{ '&:hover': { color: 'blue.500', textDecoration: 'underline' }, cursor: 'pointer' }} | |
| 242 | + color='brand.600' | |
| 243 | + fontSize="sm" | |
| 244 | + fontWeight="700" | |
| 245 | + > | |
| 246 | + {cell?.row?.original?.accountName ? cell?.row?.original?.accountName : "-"} | |
| 247 | + </Text> | |
| 248 | + </div> | |
| 249 | + : | |
| 250 | + <Text | |
| 251 | + > | |
| 252 | + {cell?.row?.original?.accountName ? cell?.row?.original?.accountName : "-"} | |
| 253 | + </Text> | |
| 254 | + ) | |
| 255 | + }, | |
| 256 | + { | |
| 257 | + Header: "Grand Total", | |
| 258 | + accessor: "grandTotal", | |
| 259 | + cell: (cell) => ( | |
| 260 | + <div className="selectOpt"> | |
| 261 | + <Text | |
| 262 | + > | |
| 263 | + {cell?.row?.original?.grandTotal ? `$${cell?.row?.original?.grandTotal}` : '-'} | |
| 264 | + </Text> | |
| 265 | + </div> | |
| 266 | + ) | |
| 267 | + }, | |
| 268 | + { Header: "valid Until", accessor: "validUntil" }, | |
| 269 | + ]; | |
| 270 | + const invoicesColumns = [ | |
| 271 | + { Header: "Invoice Number", accessor: "invoiceNumber", isSortable: false, width: 10 }, | |
| 272 | + { | |
| 273 | + Header: 'Title', accessor: 'title', cell: (cell) => ( | |
| 274 | + <div className="selectOpt"> | |
| 275 | + <Text | |
| 276 | + onClick={() => navigate(`/invoicesView/${cell?.row?.original._id}`)} | |
| 277 | + me="10px" | |
| 278 | + sx={{ '&:hover': { color: 'blue.500', textDecoration: 'underline' }, cursor: 'pointer' }} | |
| 279 | + color='brand.600' | |
| 280 | + fontSize="sm" | |
| 281 | + fontWeight="700" | |
| 282 | + > | |
| 283 | + {cell?.value} | |
| 284 | + </Text> | |
| 285 | + </div> | |
| 286 | + ) | |
| 287 | + }, | |
| 288 | + { | |
| 289 | + Header: 'Status', accessor: 'status', | |
| 290 | + }, | |
| 291 | + { | |
| 292 | + Header: 'Contact', accessor: 'contact', | |
| 293 | + cell: (cell) => ( | |
| 294 | + <Text | |
| 295 | + > | |
| 296 | + {cell?.row?.original?.contactName ? cell?.row?.original?.contactName : "-"} | |
| 297 | + </Text> | |
| 298 | + ) | |
| 299 | + }, | |
| 300 | + { | |
| 301 | + Header: 'Account', accessor: 'account', | |
| 302 | + cell: (cell) => ( | |
| 303 | + (user.role === 'superAdmin' || accountAccess?.view) ? | |
| 304 | + <div className="selectOpt"> | |
| 305 | + <Text | |
| 306 | + onClick={() => navigate(cell?.row?.original.account !== null && `/accountView/${cell?.row?.original.account}`)} | |
| 307 | + me="10px" | |
| 308 | + sx={{ '&:hover': { color: 'blue.500', textDecoration: 'underline' }, cursor: 'pointer' }} | |
| 309 | + color='brand.600' | |
| 310 | + fontSize="sm" | |
| 311 | + fontWeight="700" | |
| 312 | + > | |
| 313 | + {cell?.row?.original?.accountName ? cell?.row?.original?.accountName : "-"} | |
| 314 | + </Text> | |
| 315 | + </div> | |
| 316 | + : | |
| 317 | + <Text | |
| 318 | + > | |
| 319 | + {cell?.row?.original?.accountName ? cell?.row?.original?.accountName : "-"} | |
| 320 | + </Text> | |
| 321 | + ) | |
| 322 | + }, | |
| 323 | + { | |
| 324 | + Header: "Grand Total", | |
| 325 | + accessor: "grandTotal", | |
| 326 | + cell: (cell) => ( | |
| 327 | + <div className="selectOpt"> | |
| 328 | + <Text> | |
| 329 | + {cell?.row?.original?.grandTotal ? `$${cell?.row?.original?.grandTotal}` : '-'} | |
| 330 | + </Text> | |
| 331 | + </div> | |
| 332 | + ) | |
| 333 | + }, | |
| 334 | + ]; | |
| 335 | + | |
| 336 | + | |
| 337 | + | |
| 338 | + const taskColumns = [ | |
| 339 | + { | |
| 340 | + Header: "Title", accessor: "title", cell: (cell) => ( | |
| 341 | + <Link to={`/view/${cell?.row?.original?._id}`}> | |
| 342 | + <Text | |
| 343 | + me="10px" | |
| 344 | + sx={{ '&:hover': { color: 'blue.500', textDecoration: 'underline' } }} | |
| 345 | + color='brand.600' | |
| 346 | + fontSize="sm" | |
| 347 | + fontWeight="700" | |
| 348 | + > | |
| 349 | + {cell?.value || '-'} | |
| 350 | + </Text> | |
| 351 | + </Link>) | |
| 352 | + }, | |
| 353 | + { Header: "Category", accessor: "category", }, | |
| 354 | + { Header: "Assign To", accessor: "assignToName", }, | |
| 355 | + { Header: "Start Date", accessor: "start", }, | |
| 356 | + { Header: "End Date", accessor: "end", }, | |
| 357 | + ]; | |
| 358 | + | |
| 359 | + const handleTabChange = (index) => { | |
| 360 | + setSelectedTab(index); | |
| 361 | + }; | |
| 362 | + | |
| 363 | + const generatePDF = () => { | |
| 364 | + const element = document.getElementById("reports"); | |
| 365 | + if (element) { | |
| 366 | + element.style.display = 'block'; | |
| 367 | + element.style.width = '100%'; // Adjust width for mobile | |
| 368 | + element.style.height = 'auto'; | |
| 369 | + // setTimeout(() => { | |
| 370 | + html2pdf() | |
| 371 | + .from(element) | |
| 372 | + .set({ | |
| 373 | + margin: [0, 0, 0, 0], | |
| 374 | + filename: `Contact_Details_${moment().format("DD-MM-YYYY")}.pdf`, | |
| 375 | + image: { type: "jpeg", quality: 0.98 }, | |
| 376 | + html2canvas: { scale: 2, useCORS: true, allowTaint: true }, | |
| 377 | + jsPDF: { unit: "in", format: "letter", orientation: "portrait" }, | |
| 378 | + }) | |
| 379 | + .save().then(() => { | |
| 380 | + element.style.display = ''; | |
| 381 | + }) | |
| 382 | + // }, 500); | |
| 383 | + } else { | |
| 384 | + console.error("Element with ID 'reports' not found."); | |
| 385 | + } | |
| 386 | + }; | |
| 387 | + | |
| 388 | + const download = async (data) => { | |
| 389 | + if (data) { | |
| 390 | + let result = await getApi(`api/document/download/`, data) | |
| 391 | + if (result && result.status === 200) { | |
| 392 | + window.open(`${constant.baseUrl}api/document/download/${data}`) | |
| 393 | + toast.success('file Download successful') | |
| 394 | + } else if (result && result.response.status === 404) { | |
| 395 | + toast.error('file Not Found') | |
| 396 | + } | |
| 397 | + } | |
| 398 | + } | |
| 399 | + | |
| 400 | + const fetchData = async (i) => { | |
| 401 | + setIsLoding(true) | |
| 402 | + let response = await getApi('api/contact/view/', param.id) | |
| 403 | + setData(response.data?.contact); | |
| 404 | + setAllData(response?.data); | |
| 405 | + setIsLoding(false) | |
| 406 | + setSelectedTab(i) | |
| 407 | + } | |
| 408 | + | |
| 409 | + const handleDeleteContact = async (id) => { | |
| 410 | + try { | |
| 411 | + setIsLoding(true) | |
| 412 | + let response = await deleteApi('api/contact/delete/', id) | |
| 413 | + if (response.status === 200) { | |
| 414 | + setDelete(false) | |
| 415 | + setAction((pre) => !pre) | |
| 416 | + navigate('/contacts') | |
| 417 | + } | |
| 418 | + } catch (error) { | |
| 419 | + console.log(error) | |
| 420 | + } | |
| 421 | + finally { | |
| 422 | + setIsLoding(false) | |
| 423 | + } | |
| 424 | + } | |
| 425 | + | |
| 426 | + useEffect(() => { | |
| 427 | + fetchData() | |
| 428 | + }, [action]) | |
| 429 | + | |
| 430 | + useEffect(() => { | |
| 431 | + fetchCustomDataFields() | |
| 432 | + }, []) | |
| 433 | + | |
| 434 | + function toCamelCase(text) { | |
| 435 | + return text?.replace(/([a-z])([A-Z])/g, '$1 $2'); | |
| 436 | + } | |
| 437 | + | |
| 438 | + useEffect(() => { | |
| 439 | + dispatch(fetchContactCustomFiled()) | |
| 440 | + }, []) | |
| 441 | + | |
| 442 | + const firstValue = Object?.values(param)[0]; | |
| 443 | + const splitValue = firstValue?.split('/') | |
| 444 | + | |
| 445 | + return ( | |
| 446 | + <> | |
| 447 | + {isLoding ? | |
| 448 | + <Flex justifyContent={'center'} alignItems={'center'} width="100%" > | |
| 449 | + <Spinner /> | |
| 450 | + </Flex> : <> | |
| 451 | + <Heading size="lg" mt={0} m={3}> | |
| 452 | + {data?.fullName || ""} | |
| 453 | + </Heading> | |
| 454 | + <Tabs onChange={handleTabChange} index={selectedTab}> | |
| 455 | + <Grid templateColumns="repeat(12, 1fr)" mb={3} gap={1}> | |
| 456 | + <GridItem colSpan={{ base: 12, md: 6 }}> | |
| 457 | + <TabList sx={{ | |
| 458 | + width: '100%', | |
| 459 | + overflowX: 'auto', | |
| 460 | + border: "none", | |
| 461 | + '& button:focus': { boxShadow: 'none', }, | |
| 462 | + '& button': { | |
| 463 | + margin: { sm: "0 3px", md: "0 5px" }, padding: { sm: "5px", md: "8px" }, fontSize: { sm: "12px", md: "16px" }, border: '2px solid #8080803d', borderTopLeftRadius: "10px", borderTopRightRadius: "10px", borderBottom: 0 | |
| 464 | + }, | |
| 465 | + '& button[aria-selected="true"]': { | |
| 466 | + border: "2px solid brand.200", borderBottom: 0, zIndex: '0' | |
| 467 | + }, | |
| 468 | + }} > | |
| 469 | + <Tab >Information</Tab> | |
| 470 | + {(emailAccess?.view || callAccess?.view || taskAccess?.view || meetingAccess?.view) && <Tab> Communication</Tab>} | |
| 471 | + <Tab>Document</Tab> | |
| 472 | + </TabList> | |
| 473 | + | |
| 474 | + </GridItem> | |
| 475 | + <GridItem colSpan={{ base: 12, md: 6 }} mt={{ sm: "3px", md: "5px" }} > | |
| 476 | + <Flex justifyContent={"right"}> | |
| 477 | + <Menu> | |
| 478 | + {(user.role === 'superAdmin' || permission?.create || permission?.update || permission?.delete) && <MenuButton size="sm" variant="outline" colorScheme='blackAlpha' va mr={2.5} as={Button} rightIcon={<ChevronDownIcon />}> | |
| 479 | + Actions | |
| 480 | + </MenuButton>} | |
| 481 | + <MenuDivider /> | |
| 482 | + <MenuList minWidth={2} zIndex={"99"}> | |
| 483 | + {(user.role === 'superAdmin' || permission?.create) && <MenuItem alignItems={'start'} onClick={() => onOpen()} color={'blue'} icon={<AddIcon />}>Add</MenuItem>} | |
| 484 | + {(user.role === 'superAdmin' || permission?.update) && <MenuItem alignItems={'start'} onClick={() => setEdit(true)} icon={<EditIcon />}>Edit</MenuItem>} | |
| 485 | + <MenuItem onClick={generatePDF} alignItems={"start"} icon={<FaFilePdf />} display={"flex"} style={{ alignItems: "center" }}>Print as PDF</MenuItem > | |
| 486 | + | |
| 487 | + {(user.role === 'superAdmin' || permission?.delete) && | |
| 488 | + <> | |
| 489 | + <MenuDivider /> | |
| 490 | + <MenuItem alignItems={'start'} onClick={() => setDelete(true)} color={'red'} icon={<DeleteIcon />}>Delete</MenuItem> | |
| 491 | + </> | |
| 492 | + } | |
| 493 | + </MenuList> | |
| 494 | + </Menu> | |
| 495 | + <Link to="/contacts"> | |
| 496 | + <Button leftIcon={<IoIosArrowBack />} size="sm" variant="brand"> | |
| 497 | + Back | |
| 498 | + </Button> | |
| 499 | + </Link> | |
| 500 | + </Flex> | |
| 501 | + </GridItem> | |
| 502 | + </Grid> | |
| 503 | + <TabPanels> | |
| 504 | + <TabPanel pt={4} p={0}> | |
| 505 | + <CustomView data={contactData?.[0]} fieldData={data} toCamelCase={toCamelCase} moduleId={contactData?.[0]?._id} fetchData={fetchData} id="reports" /> | |
| 506 | + <GridItem colSpan={{ base: 12 }} mt={4}> | |
| 507 | + <Card > | |
| 508 | + <Grid templateColumns={{ base: "1fr" }} gap={4}> | |
| 509 | + <GridItem colSpan={2}> | |
| 510 | + <Box> | |
| 511 | + <Flex alignItems={'center'} mb={2} justifyContent={'space-between'}> | |
| 512 | + <Heading size="md"> | |
| 513 | + Property of Interest ({allData?.interestProperty?.interestProperty?.length}) | |
| 514 | + </Heading> | |
| 515 | + <Button onClick={() => setPropertyModel(true)} leftIcon={<LuBuilding2 />} size="sm" colorScheme="gray" bg={buttonbg}>Select Interested Property </Button> | |
| 516 | + </Flex> | |
| 517 | + </Box> | |
| 518 | + | |
| 519 | + <Grid templateColumns={'repeat(2, 1fr)'} gap={4}> | |
| 520 | + <GridItem colSpan={{ base: 2 }}> | |
| 521 | + {/* <PropertyTable fetchData={fetchData} columnsData={PropertyColumn} tableData={allData?.interestProperty?.interestProperty?.length > 0 ? allData?.interestProperty?.interestProperty : []} title={'Interested Property'} /> */} | |
| 522 | + <CommonCheckTable | |
| 523 | + isLoding={isLoding} | |
| 524 | + columnData={columns ?? []} | |
| 525 | + dataColumn={columns ?? []} | |
| 526 | + allData={allData?.interestProperty?.interestProperty || []} | |
| 527 | + tableData={allData?.interestProperty?.interestProperty || []} | |
| 528 | + tableCustomFields={propertyData?.[0]?.fields?.filter((field) => field?.isTableField === true) || []} | |
| 529 | + AdvanceSearch={() => ""} | |
| 530 | + ManageGrid={false} | |
| 531 | + deleteMany={false} | |
| 532 | + selectType="multiple" | |
| 533 | + customSearch={false} | |
| 534 | + checkBox={false} | |
| 535 | + /> | |
| 536 | + </GridItem> | |
| 537 | + </Grid> | |
| 538 | + </GridItem> | |
| 539 | + </Grid> | |
| 540 | + </Card> | |
| 541 | + </GridItem> | |
| 542 | + | |
| 543 | + </TabPanel> | |
| 544 | + <TabPanel pt={4} p={0}> | |
| 545 | + <GridItem colSpan={{ base: 12 }} > | |
| 546 | + <Grid templateColumns={{ base: "1fr" }} gap={4}> | |
| 547 | + <Grid templateColumns={'repeat(12, 1fr)'} gap={4}> | |
| 548 | + {emailAccess?.view && <GridItem colSpan={{ base: 12, md: 6 }}> | |
| 549 | + <Card overflow={'scroll'}> | |
| 550 | + <CommonCheckTable | |
| 551 | + title={"Email"} | |
| 552 | + isLoding={isLoding} | |
| 553 | + columnData={columnsDataColumns ?? []} | |
| 554 | + // dataColumn={columnsDataColumns ?? []} | |
| 555 | + allData={showEmail ? allData.EmailHistory : allData?.EmailHistory?.length > 0 ? [allData.EmailHistory[0]] : []} | |
| 556 | + tableData={showEmail ? allData.EmailHistory : allData?.EmailHistory?.length > 0 ? [allData.EmailHistory[0]] : []} | |
| 557 | + AdvanceSearch={false} | |
| 558 | + dataLength={allData?.EmailHistory?.length} | |
| 559 | + tableCustomFields={[]} | |
| 560 | + checkBox={false} | |
| 561 | + deleteMany={true} | |
| 562 | + ManageGrid={false} | |
| 563 | + onOpen={() => setAddEmailHistory(true)} | |
| 564 | + access={emailAccess} | |
| 565 | + /> | |
| 566 | + {allData?.EmailHistory?.length > 1 && | |
| 567 | + <div style={{ display: "flex", justifyContent: "end" }}> | |
| 568 | + <Button colorScheme="brand" variant="outline" size='sm' display="flex" justifyContant="end" onClick={() => showEmail ? setShowEmail(false) : setShowEmail(true)}>{showEmail ? "Show less" : "Show more"}</Button> | |
| 569 | + </div>} | |
| 570 | + </Card> | |
| 571 | + </GridItem>} | |
| 572 | + {callAccess?.view && <GridItem colSpan={{ base: 12, md: 6 }}> | |
| 573 | + <Card overflow={'scroll'}> | |
| 574 | + <CommonCheckTable | |
| 575 | + title={"Call"} | |
| 576 | + isLoding={isLoding} | |
| 577 | + columnData={callColumns ?? []} | |
| 578 | + // dataColumn={callColumns ?? []} | |
| 579 | + allData={showCall ? allData?.phoneCallHistory : allData?.phoneCallHistory?.length > 0 ? [allData?.phoneCallHistory[0]] : []} | |
| 580 | + tableData={showCall ? allData?.phoneCallHistory : allData?.phoneCallHistory?.length > 0 ? [allData?.phoneCallHistory[0]] : []} | |
| 581 | + AdvanceSearch={false} | |
| 582 | + tableCustomFields={[]} | |
| 583 | + dataLength={allData?.phoneCallHistory?.length} | |
| 584 | + checkBox={false} | |
| 585 | + deleteMany={true} | |
| 586 | + ManageGrid={false} | |
| 587 | + onOpen={() => setAddPhoneCall(true)} | |
| 588 | + access={callAccess} | |
| 589 | + /> | |
| 590 | + {allData?.phoneCallHistory?.length > 1 && <div style={{ display: "flex", justifyContent: "end" }}> | |
| 591 | + <Button colorScheme="brand" variant="outline" size='sm' display="flex" justifyContant="end" onClick={() => showCall ? setShowCall(false) : setShowCall(true)}>{showCall ? "Show less" : "Show more"}</Button> | |
| 592 | + </div>} | |
| 593 | + </Card> | |
| 594 | + </GridItem>} | |
| 595 | + {taskAccess?.view && <GridItem colSpan={{ base: 12, md: 6 }}> | |
| 596 | + <Card overflow={'scroll'}> | |
| 597 | + <CommonCheckTable | |
| 598 | + title={"Task"} | |
| 599 | + isLoding={isLoding} | |
| 600 | + columnData={taskColumns ?? []} | |
| 601 | + // dataColumn={taskColumns ?? []} | |
| 602 | + allData={showTasks ? allData?.task : allData?.task?.length > 0 ? [allData?.task[0]] : []} | |
| 603 | + tableData={showTasks ? allData?.task : allData?.task?.length > 0 ? [allData?.task[0]] : []} | |
| 604 | + AdvanceSearch={false} | |
| 605 | + dataLength={allData?.task?.length} | |
| 606 | + tableCustomFields={[]} | |
| 607 | + checkBox={false} | |
| 608 | + deleteMany={true} | |
| 609 | + ManageGrid={false} | |
| 610 | + onOpen={() => setTaskModel(true)} | |
| 611 | + access={taskAccess} | |
| 612 | + /> | |
| 613 | + {allData?.task?.length > 1 && <div style={{ display: "flex", justifyContent: "end" }}> | |
| 614 | + <Button colorScheme="brand" variant="outline" size='sm' display="flex" justifyContant="end" onClick={() => showTasks ? setShowTasks(false) : setShowTasks(true)}>{showTasks ? "Show less" : "Show more"}</Button> | |
| 615 | + </div>} | |
| 616 | + </Card> | |
| 617 | + </GridItem>} | |
| 618 | + {meetingAccess?.view && <GridItem colSpan={{ base: 12, md: 6 }}> | |
| 619 | + <Card overflow={'scroll'}> | |
| 620 | + <CommonCheckTable | |
| 621 | + title={"Meeting"} | |
| 622 | + isLoding={isLoding} | |
| 623 | + columnData={MeetingColumns ?? []} | |
| 624 | + // dataColumn={MeetingColumns ?? []} | |
| 625 | + dataLength={allData?.meetingHistory?.length} | |
| 626 | + allData={showMeetings ? allData?.meetingHistory : allData?.meetingHistory?.length > 0 ? [allData?.meetingHistory[0]] : []} | |
| 627 | + tableData={showMeetings ? allData?.meetingHistory : allData?.meetingHistory?.length > 0 ? [allData?.meetingHistory[0]] : []} | |
| 628 | + AdvanceSearch={false} | |
| 629 | + tableCustomFields={[]} | |
| 630 | + checkBox={false} | |
| 631 | + deleteMany={true} | |
| 632 | + ManageGrid={false} | |
| 633 | + onOpen={() => setMeeting(true)} | |
| 634 | + access={meetingAccess} | |
| 635 | + /> | |
| 636 | + {allData?.meetingHistory?.length > 1 && <div style={{ display: "flex", justifyContent: "end" }}> | |
| 637 | + <Button colorScheme="brand" size='sm' variant="outline" display="flex" justifyContant="end" onClick={() => showMeetings ? setShowMeetings(false) : setShowMeetings(true)}>{showMeetings ? "Show less" : "Show more"}</Button> | |
| 638 | + </div>} | |
| 639 | + </Card> | |
| 640 | + </GridItem>} | |
| 641 | + {quotesAccess?.view && <GridItem colSpan={{ base: 12, md: 6 }}> | |
| 642 | + <Card overflow={'scroll'}> | |
| 643 | + <CommonCheckTable | |
| 644 | + title={"Quotes"} | |
| 645 | + isLoding={isLoding} | |
| 646 | + columnData={quotesColumns ?? []} | |
| 647 | + // dataColumn={quotesColumns ?? []} | |
| 648 | + dataLength={allData?.quotes?.length} | |
| 649 | + allData={showQuotes ? allData?.quotes : allData?.quotes?.length > 0 ? [allData?.quotes[0]] : []} | |
| 650 | + tableData={showQuotes ? allData?.quotes : allData?.quotes?.length > 0 ? [allData?.quotes[0]] : []} | |
| 651 | + AdvanceSearch={false} | |
| 652 | + tableCustomFields={[]} | |
| 653 | + checkBox={false} | |
| 654 | + deleteMany={true} | |
| 655 | + ManageGrid={false} | |
| 656 | + onOpen={() => setAddQuotes(true)} | |
| 657 | + access={quotesAccess} | |
| 658 | + /> | |
| 659 | + {allData?.quotes?.length > 1 && <div style={{ display: "flex", justifyContent: "end" }}> | |
| 660 | + <Button colorScheme="brand" size='sm' variant="outline" display="flex" justifyContant="end" onClick={() => showQuotes ? setShowQuotes(false) : setShowQuotes(true)}>{showQuotes ? "Show less" : "Show more"}</Button> | |
| 661 | + </div>} | |
| 662 | + </Card> | |
| 663 | + </GridItem>} | |
| 664 | + {invoicesAccess?.view && <GridItem colSpan={{ base: 12, md: 6 }}> | |
| 665 | + <Card overflow={'scroll'}> | |
| 666 | + <CommonCheckTable | |
| 667 | + title={"Invoices"} | |
| 668 | + isLoding={isLoding} | |
| 669 | + columnData={invoicesColumns ?? []} | |
| 670 | + // dataColumn={invoicesColumns ?? []} | |
| 671 | + dataLength={allData?.invoice?.length} | |
| 672 | + allData={showInvoices ? allData?.invoice : allData?.invoice?.length > 0 ? [allData?.invoice[0]] : []} | |
| 673 | + tableData={showInvoices ? allData?.invoice : allData?.invoice?.length > 0 ? [allData?.invoice[0]] : []} | |
| 674 | + AdvanceSearch={false} | |
| 675 | + tableCustomFields={[]} | |
| 676 | + checkBox={false} | |
| 677 | + deleteMany={true} | |
| 678 | + ManageGrid={false} | |
| 679 | + onOpen={() => setAddInvoice(true)} | |
| 680 | + access={invoicesAccess} | |
| 681 | + /> | |
| 682 | + {allData?.invoice?.length > 1 && <div style={{ display: "flex", justifyContent: "end" }}> | |
| 683 | + <Button colorScheme="brand" size='sm' variant="outline" display="flex" justifyContant="end" onClick={() => showInvoices ? setShowInvoices(false) : setShowInvoices(true)}>{showInvoices ? "Show less" : "Show more"}</Button> | |
| 684 | + </div>} | |
| 685 | + </Card> | |
| 686 | + </GridItem>} | |
| 687 | + | |
| 688 | + </Grid> | |
| 689 | + </Grid> | |
| 690 | + </GridItem> | |
| 691 | + | |
| 692 | + </TabPanel> | |
| 693 | + <TabPanel pt={4} p={0}> | |
| 694 | + <GridItem colSpan={{ base: 12 }} > | |
| 695 | + <Card minH={'40vh'} > | |
| 696 | + <Flex alignItems={'center'} justifyContent={'space-between'} mb='2'> | |
| 697 | + <Heading size="md" mb={3}> | |
| 698 | + Documents | |
| 699 | + </Heading> | |
| 700 | + <Button leftIcon={<AddIcon />} size='sm' variant='brand' onClick={() => setAddDocument(true)}>Add Document</Button> | |
| 701 | + </Flex> | |
| 702 | + <HSeparator /> | |
| 703 | + <VStack mt={4} alignItems="flex-start"> | |
| 704 | + {allData?.Document?.length > 0 ? allData?.Document?.map((item) => ( | |
| 705 | + <FolderTreeView name={item.folderName} item={item}> | |
| 706 | + {item?.files?.map((file) => ( | |
| 707 | + <FolderTreeView download={download} data={file} name={file.fileName} isFile from="contact" /> | |
| 708 | + ))} | |
| 709 | + </FolderTreeView> | |
| 710 | + )) : | |
| 711 | + <Text textAlign={'center'} width="100%" color={textColor} fontSize="sm" fontWeight="700"> | |
| 712 | + <DataNotFound /> | |
| 713 | + </Text> | |
| 714 | + } | |
| 715 | + | |
| 716 | + </VStack> | |
| 717 | + </Card> | |
| 718 | + </GridItem> | |
| 719 | + </TabPanel> | |
| 720 | + | |
| 721 | + <TabPanel pt={4} p={0}> | |
| 722 | + | |
| 723 | + <GridItem colSpan={{ base: 12 }} > | |
| 724 | + <Card > | |
| 725 | + <Grid templateColumns={{ base: "1fr" }} gap={4}> | |
| 726 | + <GridItem colSpan={2}> | |
| 727 | + <Box> | |
| 728 | + <Heading size="md" mb={3}> | |
| 729 | + Social Media Profiles | |
| 730 | + </Heading> | |
| 731 | + <HSeparator /> | |
| 732 | + </Box> | |
| 733 | + </GridItem> | |
| 734 | + {data?.linkedInProfile || data?.facebookProfile || data?.twitterHandle || data?.otherProfiles ? | |
| 735 | + <Grid templateColumns={'repeat(12, 1fr)'} gap={4} my={3} flexWrap={'wrap'} display={'flex'} justifyContent={'center'}> | |
| 736 | + {data?.linkedInProfile && <GridItem textAlign={'center'} colSpan={{ base: 2, md: 1 }}> | |
| 737 | + <a target='_blank' href={data?.linkedInProfile}> | |
| 738 | + <IconButton colorScheme="brand" aria-label="Call Fred" borderRadius="10px" size="md" icon={<BiLogoLinkedin />} /> | |
| 739 | + </a> | |
| 740 | + <Text fontSize="sm" mt={2} fontWeight="bold" color={'blackAlpha.900'}> LinkedIn Profile </Text> | |
| 741 | + </GridItem>} | |
| 742 | + {data?.facebookProfile && <GridItem textAlign={'center'} colSpan={{ base: 2, md: 1 }}> | |
| 743 | + <a target='_blank' href={`https://www.facebook.com/${data.facebookProfile}`}> | |
| 744 | + <IconButton colorScheme="brand" aria-label="Call Fred" borderRadius="10px" size="md" icon={<FaFacebook />} /> | |
| 745 | + </a> | |
| 746 | + <Text fontSize="sm" mt={2} fontWeight="bold" color={'blackAlpha.900'}> Facebook Profile </Text> | |
| 747 | + </GridItem>} | |
| 748 | + {data?.linkedInProfile && <GridItem textAlign={'center'} colSpan={{ base: 2, md: 1 }}> | |
| 749 | + <a target='_blank' href={`https://www.facebook.com/${data.facebookProfile}`}> | |
| 750 | + <IconButton colorScheme="brand" aria-label="Call Fred" borderRadius="10px" size="md" icon={<BsTwitter />} /> | |
| 751 | + </a> | |
| 752 | + <Text fontSize="sm" mt={2} px={2} fontWeight="bold" color={'blackAlpha.900'}>Twitter Handle </Text> | |
| 753 | + </GridItem>} | |
| 754 | + | |
| 755 | + {data?.linkedInProfile && <GridItem textAlign={'center'} colSpan={{ base: 2, md: 1 }}> | |
| 756 | + <a target='_blank' href={data?.otherProfiles}> | |
| 757 | + <IconButton colorScheme="brand" aria-label="Call Fred" borderRadius="10px" size="md" icon={<BiLink />} /> | |
| 758 | + </a> | |
| 759 | + <Text fontSize="sm" mt={2} fontWeight="bold" color={'blackAlpha.900'}> Other Profiles </Text> | |
| 760 | + </GridItem>} | |
| 761 | + </Grid> | |
| 762 | + : | |
| 763 | + <Grid templateColumns={'repeat(2, 1fr)'} gap={4}> | |
| 764 | + <GridItem colSpan={{ base: 2 }} textAlign={'center'}> | |
| 765 | + <Text textAlign={'center'} width="100%" color={textColor} fontSize="sm" fontWeight="700"> <DataNotFound /></Text> | |
| 766 | + | |
| 767 | + </GridItem> | |
| 768 | + </Grid> | |
| 769 | + } | |
| 770 | + </Grid> | |
| 771 | + </Card> | |
| 772 | + </GridItem> | |
| 773 | + | |
| 774 | + </TabPanel> | |
| 775 | + </TabPanels> | |
| 776 | + </Tabs> | |
| 777 | + | |
| 778 | + | |
| 779 | + | |
| 780 | + {(user.role === 'superAdmin' || (permission?.update || permission?.delete)) && <Card mt={3}> | |
| 781 | + <Grid templateColumns="repeat(6, 1fr)" gap={1}> | |
| 782 | + <GridItem colStart={6} > | |
| 783 | + <Flex justifyContent={"right"}> | |
| 784 | + {permission?.update && <Button size="sm" onClick={() => setEdit(true)} leftIcon={<EditIcon />} mr={2.5} variant="outline" colorScheme="green">Edit</Button>} | |
| 785 | + {permission?.delete && <Button size="sm" style={{ background: 'red.800' }} onClick={() => setDelete(true)} leftIcon={<DeleteIcon />} colorScheme="red" >Delete</Button>} | |
| 786 | + </Flex> | |
| 787 | + </GridItem> | |
| 788 | + </Grid> | |
| 789 | + </Card>} | |
| 790 | + </>} | |
| 791 | + {isOpen && <Add isOpen={isOpen} size={size} onClose={onClose} contactData={contactData?.[0]} />} | |
| 792 | + <Edit isOpen={edit} contactData={contactData?.[0]} size={size} onClose={setEdit} setAction={setAction} moduleId={contactData?.[0]?._id} data={data} /> | |
| 793 | + <CommonDeleteModel isOpen={deleteModel} onClose={() => setDelete(false)} type='Contact' handleDeleteData={handleDeleteContact} ids={param.id} /> | |
| 794 | + <AddEmailHistory lead="false" contactEmail={allData?.contact?.email} fetchData={fetchData} isOpen={addEmailHistory} onClose={setAddEmailHistory} id={param.id} /> | |
| 795 | + <AddDocumentModal addDocument={addDocument} setAddDocument={setAddDocument} linkId={param.id} from="contact" setAction={setAction} fetchData={fetchData} /> | |
| 796 | + <AddMeeting fetchData={fetchData} leadContect={splitValue[0]} isOpen={addMeeting} onClose={setMeeting} from="contact" id={param.id} setAction={setAction} view={true} /> | |
| 797 | + <AddEdit isOpen={taskModel} fetchData={fetchData} leadContect={splitValue[0]} onClose={setTaskModel} id={param.id} userAction={'add'} view={true} /> | |
| 798 | + <AddPhoneCall viewData={allData} fetchData={fetchData} setAction={setAction} isOpen={addPhoneCall} onClose={setAddPhoneCall} data={data?.contact} id={param.id} cData={data} /> | |
| 799 | + <AddEditQuotes isOpen={addQuotes} size={"lg"} onClose={() => setAddQuotes(false)} setAction={setAction} type={"add"} contactId={param.id} /> | |
| 800 | + <AddEditInvoice isOpen={addInvoice} size={"lg"} onClose={() => setAddInvoice(false)} setAction={setAction} type={"add"} contactId={param.id} /> | |
| 801 | + <PropertyModel fetchData={fetchData} isOpen={propertyModel} onClose={setPropertyModel} id={param.id} interestProperty={data?.interestProperty} /> | |
| 802 | + | |
| 803 | + </> | |
| 804 | + ); | |
| 805 | +}; | |
| 806 | + | |
| 807 | +export default View; |
+236-0Client/src/views/admin/contact/components/ContactImport.js
| @@ -0,0 +1,236 @@ | ||
| 1 | +import React, { useState, useEffect } from 'react' | |
| 2 | +import { useLocation, useNavigate } from 'react-router-dom'; | |
| 3 | +import Papa from 'papaparse'; | |
| 4 | +import { | |
| 5 | + Flex, | |
| 6 | + useColorModeValue, | |
| 7 | + Select, | |
| 8 | + Button, | |
| 9 | + Grid, | |
| 10 | + GridItem, | |
| 11 | + Text | |
| 12 | +} from "@chakra-ui/react"; | |
| 13 | +import { useFormik } from "formik"; | |
| 14 | +import { postApi } from 'services/api'; | |
| 15 | +import { toast } from 'react-toastify'; | |
| 16 | +import moment from 'moment'; | |
| 17 | +import ExcelJS from 'exceljs'; | |
| 18 | +import Card from 'components/card/Card'; | |
| 19 | + | |
| 20 | +function ContactImport() { | |
| 21 | + | |
| 22 | + const location = useLocation(); | |
| 23 | + const { fileData, customFields } = location.state || {}; | |
| 24 | + const [importedFileFields, setImportedFileFields] = useState([]); | |
| 25 | + const [importedFileData, setImportedFileData] = useState([]); | |
| 26 | + const [isLoding, setIsLoding] = useState(false); | |
| 27 | + const navigate = useNavigate(); | |
| 28 | + const userId = JSON.parse(localStorage.getItem('user'))._id; | |
| 29 | + const [filterContact, setFilterContact] = useState([]); | |
| 30 | + | |
| 31 | + const columns = [ | |
| 32 | + { Header: 'Fields In Crm', accessor: 'crmFields' }, | |
| 33 | + { Header: 'Fields In File', accessor: 'fileFields' }, | |
| 34 | + ]; | |
| 35 | + | |
| 36 | + | |
| 37 | + const initialFieldValues = Object.fromEntries( | |
| 38 | + (customFields || []).map(field => [field?.name, '']) | |
| 39 | + ); | |
| 40 | + const initialValues = { | |
| 41 | + ...initialFieldValues | |
| 42 | + }; | |
| 43 | + | |
| 44 | + const fieldsInCrm = [ | |
| 45 | + ...customFields?.map((field) => ({ Header: field?.label, accessor: field?.name, type: field?.type, formikType: field?.validations?.find(obj => obj.hasOwnProperty('formikType')) })) | |
| 46 | + ]; | |
| 47 | + | |
| 48 | + const formik = useFormik({ | |
| 49 | + initialValues: initialValues, | |
| 50 | + onSubmit: (values, { resetForm }) => { | |
| 51 | + | |
| 52 | + const contactsData = importedFileData?.map((item, ind) => { | |
| 53 | + const contact = { | |
| 54 | + createdDate: new Date(), | |
| 55 | + deleted: item[values.deleted || "deleted"] || false, | |
| 56 | + createBy: JSON.parse(localStorage.getItem('user'))._id, | |
| 57 | + }; | |
| 58 | + | |
| 59 | + fieldsInCrm?.forEach(field => { | |
| 60 | + const selectedField = values[field?.accessor]; | |
| 61 | + const fieldValue = item[selectedField] || ''; | |
| 62 | + | |
| 63 | + if (field?.type?.toLowerCase() === "date") { | |
| 64 | + contact[field?.accessor] = moment(fieldValue).isValid() ? fieldValue : ''; | |
| 65 | + } else if (field?.type?.toLowerCase() === "number" && ['positive', 'negative'].includes(field?.formikType?.toLowerCase())) { | |
| 66 | + contact[field?.accessor] = parseFloat(fieldValue) || ''; | |
| 67 | + } else if (field?.type?.toLowerCase() === "number") { | |
| 68 | + contact[field?.accessor] = parseInt(fieldValue, 10) || ''; | |
| 69 | + } else { | |
| 70 | + contact[field?.accessor] = fieldValue; | |
| 71 | + } | |
| 72 | + }); | |
| 73 | + | |
| 74 | + return contact; | |
| 75 | + }); | |
| 76 | + | |
| 77 | + AddData(contactsData); | |
| 78 | + } | |
| 79 | + }); | |
| 80 | + | |
| 81 | + const { errors, touched, values, handleBlur, handleChange, handleSubmit, setFieldValue, resetForm } = formik | |
| 82 | + | |
| 83 | + const AddData = async (contacts) => { | |
| 84 | + try { | |
| 85 | + setIsLoding(true); | |
| 86 | + let response = await postApi('api/contact/addMany', contacts) | |
| 87 | + if (response.status === 200) { | |
| 88 | + toast.success(`Contacts imported successfully`) | |
| 89 | + resetForm(); | |
| 90 | + navigate('/contacts'); | |
| 91 | + } | |
| 92 | + } catch (e) { | |
| 93 | + console.error(e); | |
| 94 | + toast.error(`Contacts import failed`) | |
| 95 | + resetForm(); | |
| 96 | + navigate('/contacts'); | |
| 97 | + } | |
| 98 | + finally { | |
| 99 | + setIsLoding(false) | |
| 100 | + } | |
| 101 | + }; | |
| 102 | + | |
| 103 | + const parseFileData = async (file) => { | |
| 104 | + const reader = new FileReader(); | |
| 105 | + const extension = file.name.split('.').pop().toLowerCase(); | |
| 106 | + | |
| 107 | + reader.onload = async ({ target }) => { | |
| 108 | + | |
| 109 | + if (extension === 'csv') { | |
| 110 | + const csv = Papa.parse(target.result, { | |
| 111 | + header: true, | |
| 112 | + }); | |
| 113 | + const parsedData = csv?.data; | |
| 114 | + | |
| 115 | + if (parsedData && parsedData.length > 0) { | |
| 116 | + setImportedFileData(parsedData); | |
| 117 | + const fileHeadingFields = Object.keys(parsedData[0]); | |
| 118 | + setImportedFileFields(fileHeadingFields); | |
| 119 | + } else { | |
| 120 | + toast.error("Empty or invalid CSV file"); | |
| 121 | + navigate("/contacts"); | |
| 122 | + } | |
| 123 | + | |
| 124 | + } else if (extension === 'xlsx') { | |
| 125 | + const data = new Uint8Array(target.result); | |
| 126 | + const workbook = new ExcelJS.Workbook(); | |
| 127 | + | |
| 128 | + await workbook.xlsx.load(data); | |
| 129 | + | |
| 130 | + const worksheet = workbook.getWorksheet(1); | |
| 131 | + const jsonData = []; | |
| 132 | + | |
| 133 | + // Iterate over rows and cells | |
| 134 | + worksheet.eachRow({ includeEmpty: true }, (row, rowNumber) => { | |
| 135 | + const rowData = {}; | |
| 136 | + row.eachCell({ includeEmpty: true }, (cell, colNumber) => { | |
| 137 | + rowData[worksheet.getCell(1, colNumber).value] = cell.value; | |
| 138 | + }); | |
| 139 | + jsonData.push(rowData); | |
| 140 | + }); | |
| 141 | + jsonData?.splice(0, 1); | |
| 142 | + setImportedFileData(jsonData); | |
| 143 | + | |
| 144 | + if (jsonData && jsonData.length > 0) { | |
| 145 | + const fileHeadingFields = Object.keys(jsonData[0]); | |
| 146 | + setImportedFileFields(fileHeadingFields); | |
| 147 | + } else { | |
| 148 | + toast.error("Empty or invalid XLSX file"); | |
| 149 | + navigate("/contacts"); | |
| 150 | + } | |
| 151 | + } | |
| 152 | + }; | |
| 153 | + | |
| 154 | + if (extension === 'csv') { | |
| 155 | + reader.readAsText(file); | |
| 156 | + } else if (extension === 'xlsx') { | |
| 157 | + const blob = new Blob([file]); | |
| 158 | + reader.readAsArrayBuffer(blob); | |
| 159 | + } | |
| 160 | + }; | |
| 161 | + | |
| 162 | + useEffect(() => { | |
| 163 | + if (fileData && fileData.length > 0) { | |
| 164 | + const firstFile = fileData[0]; | |
| 165 | + parseFileData(firstFile); | |
| 166 | + } | |
| 167 | + }, [fileData]); | |
| 168 | + | |
| 169 | + | |
| 170 | + useEffect(() => { | |
| 171 | + const filterContactData = importedFileFields?.filter(field => { | |
| 172 | + const result = fieldsInCrm?.find(data => field === data?.accessor || field === data?.Header); | |
| 173 | + if (result) { | |
| 174 | + setFieldValue(result?.accessor, field); | |
| 175 | + return true; | |
| 176 | + } | |
| 177 | + return false; | |
| 178 | + }); | |
| 179 | + setFilterContact(filterContactData); | |
| 180 | + }, [importedFileFields]); | |
| 181 | + | |
| 182 | + return ( | |
| 183 | + <> | |
| 184 | + <Card overflowY={"auto"} className="importTable"> | |
| 185 | + <Text color={"secondaryGray.900"} | |
| 186 | + fontSize="22px" | |
| 187 | + fontWeight="700" | |
| 188 | + mb='20px' | |
| 189 | + >Import Contacts</Text> | |
| 190 | + <Grid templateColumns="repeat(12, 1fr)" mb={3} pb={2} gap={1} borderBottom={'1px solid #e2e8f0'}> | |
| 191 | + { | |
| 192 | + columns.map((column, index) => ( | |
| 193 | + <GridItem key={index} colSpan={{ base: 6 }} fontWeight={'600'} fontSize={{ sm: "14px", lg: "14px" }} color="secondaryGray.900" style={{ textTransform: "uppercase" }}> | |
| 194 | + {column.Header} | |
| 195 | + </GridItem> | |
| 196 | + )) | |
| 197 | + } | |
| 198 | + </Grid> | |
| 199 | + <Grid templateColumns="repeat(12, 1fr)" mb={3} gap={1} overflowY={'auto'}> | |
| 200 | + { | |
| 201 | + fieldsInCrm?.map((item, index) => ( | |
| 202 | + <> | |
| 203 | + <GridItem colSpan={{ base: 6 }} key={item.id} mt='10px'> | |
| 204 | + {item.Header} | |
| 205 | + </GridItem> | |
| 206 | + <GridItem colSpan={{ base: 4 }}> | |
| 207 | + <Select | |
| 208 | + variant="flushed" | |
| 209 | + fontWeight='500' | |
| 210 | + isSearchable | |
| 211 | + value={values[item.accessor]} | |
| 212 | + name={item.accessor} | |
| 213 | + onChange={handleChange} | |
| 214 | + > | |
| 215 | + <option value=''> {filterContact ? filterContact.find((data) => (item.Header === data || item.accessor === data) && data) ? filterContact.find((data) => (item.Header === data || item.accessor === data) && data) : 'Select Field In File' : 'Select Field In File'}</option> | |
| 216 | + { | |
| 217 | + importedFileFields?.map(field => ( | |
| 218 | + <option value={field} key={field}>{field}</option> | |
| 219 | + )) | |
| 220 | + } | |
| 221 | + </Select> | |
| 222 | + </GridItem> | |
| 223 | + </> | |
| 224 | + )) | |
| 225 | + } | |
| 226 | + </Grid> | |
| 227 | + | |
| 228 | + <Flex Flex justifyContent={'end'} mt='5' > | |
| 229 | + <Button size="sm" onClick={() => handleSubmit()} variant="brand">Save</Button> | |
| 230 | + </Flex> | |
| 231 | + </Card> | |
| 232 | + </> | |
| 233 | + ) | |
| 234 | +} | |
| 235 | + | |
| 236 | +export default ContactImport |
+74-0Client/src/views/admin/contact/components/ImportModel.js
| @@ -0,0 +1,74 @@ | ||
| 1 | +import { Button, Grid, GridItem, Modal, ModalBody, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalOverlay, Text } from '@chakra-ui/react' | |
| 2 | +import Spinner from 'components/spinner/Spinner' | |
| 3 | +import { useFormik } from 'formik' | |
| 4 | +import { useState } from 'react' | |
| 5 | +import { useNavigate } from 'react-router-dom' | |
| 6 | +import CommonFileUpload from 'components/commonFileUpload' | |
| 7 | + | |
| 8 | +const ImportModal = (props) => { | |
| 9 | + const { onClose, isOpen, text, customFields } = props | |
| 10 | + const [isLoding, setIsLoding] = useState(false) | |
| 11 | + const navigate = useNavigate(); | |
| 12 | + | |
| 13 | + const initialValues = { | |
| 14 | + contact: '' | |
| 15 | + } | |
| 16 | + | |
| 17 | + const formik = useFormik({ | |
| 18 | + initialValues: initialValues, | |
| 19 | + onSubmit: (values, { resetForm }) => { | |
| 20 | + AddData() | |
| 21 | + resetForm(); | |
| 22 | + }, | |
| 23 | + }); | |
| 24 | + const { errors, touched, values, handleBlur, handleChange, handleSubmit, setFieldValue, resetForm } = formik | |
| 25 | + | |
| 26 | + const AddData = async () => { | |
| 27 | + try { | |
| 28 | + setIsLoding(true) | |
| 29 | + resetForm() | |
| 30 | + | |
| 31 | + if (values.contact) { | |
| 32 | + onClose(); | |
| 33 | + navigate('/contactImport', { state: { fileData: values.contact, customFields: customFields } }); | |
| 34 | + } | |
| 35 | + | |
| 36 | + } catch (e) { | |
| 37 | + console.log(e); | |
| 38 | + } | |
| 39 | + finally { | |
| 40 | + setIsLoding(false) | |
| 41 | + } | |
| 42 | + }; | |
| 43 | + | |
| 44 | + return ( | |
| 45 | + <Modal onClose={onClose} isOpen={isOpen} isCentered> | |
| 46 | + <ModalOverlay /> | |
| 47 | + <ModalContent> | |
| 48 | + <ModalHeader>Import Contacts</ModalHeader> | |
| 49 | + <ModalCloseButton /> | |
| 50 | + <ModalBody> | |
| 51 | + <Grid templateColumns="repeat(12, 1fr)" gap={3}> | |
| 52 | + <GridItem colSpan={{ base: 12 }}> | |
| 53 | + <CommonFileUpload count={values.contact.length} onFileSelect={(file) => setFieldValue('contact', file)} text={text} /> | |
| 54 | + <Text mb='10px' color={'red'}> {errors.contact && touched.contact && <>Please Select {text}</>}</Text> | |
| 55 | + </GridItem> | |
| 56 | + </Grid> | |
| 57 | + </ModalBody> | |
| 58 | + <ModalFooter> | |
| 59 | + <Button size="sm" variant='brand' onClick={handleSubmit} disabled={isLoding ? true : false} >{isLoding ? <Spinner /> : 'Save'}</Button> | |
| 60 | + <Button sx={{ | |
| 61 | + marginLeft: 2, | |
| 62 | + textTransform: "capitalize", | |
| 63 | + }} variant="outline" | |
| 64 | + colorScheme="red" size="sm" onClick={() => { | |
| 65 | + onClose() | |
| 66 | + formik.resetForm() | |
| 67 | + }}>Close</Button> | |
| 68 | + </ModalFooter> | |
| 69 | + </ModalContent> | |
| 70 | + </Modal> | |
| 71 | + ) | |
| 72 | +} | |
| 73 | + | |
| 74 | +export default ImportModal |
+195-0Client/src/views/admin/contact/components/phonCall.js
| @@ -0,0 +1,195 @@ | ||
| 1 | +import { | |
| 2 | + Box, | |
| 3 | + Button, | |
| 4 | + Flex, | |
| 5 | + Heading, | |
| 6 | + Table, | |
| 7 | + Tbody, | |
| 8 | + Td, | |
| 9 | + Text, | |
| 10 | + Th, | |
| 11 | + Thead, | |
| 12 | + Tr, | |
| 13 | + useColorModeValue | |
| 14 | +} from "@chakra-ui/react"; | |
| 15 | +import moment from 'moment'; | |
| 16 | +import { useMemo, useState } from "react"; | |
| 17 | +import { | |
| 18 | + useGlobalFilter, | |
| 19 | + usePagination, | |
| 20 | + useSortBy, | |
| 21 | + useTable, | |
| 22 | +} from "react-table"; | |
| 23 | + | |
| 24 | +// Custom components | |
| 25 | +import CountUpComponent from "components/countUpComponent/countUpComponent"; | |
| 26 | +import Pagination from "components/pagination/Pagination"; | |
| 27 | +import { MdOutlineMessage } from "react-icons/md"; | |
| 28 | +import { Link, useNavigate, useParams } from "react-router-dom"; | |
| 29 | +import AddPhoneCall from "../../phoneCall/components/AddPhoneCall"; | |
| 30 | +import { AddIcon } from "@chakra-ui/icons"; | |
| 31 | +import DataNotFound from "components/notFoundData"; | |
| 32 | + | |
| 33 | +export default function PhoneCall(props) { | |
| 34 | + const { columnsData, tableData, title, fetchData, callAccess } = props; | |
| 35 | + | |
| 36 | + const columns = useMemo(() => columnsData, [columnsData]); | |
| 37 | + const data = useMemo(() => tableData, [tableData]); | |
| 38 | + const [addModel, setAddModel] = useState(false); | |
| 39 | + const user = JSON.parse(localStorage.getItem("user")) | |
| 40 | + const [gopageValue, setGopageValue] = useState() | |
| 41 | + const tableInstance = useTable( | |
| 42 | + { | |
| 43 | + columns, data, | |
| 44 | + initialState: { pageIndex: 0 } | |
| 45 | + }, | |
| 46 | + useGlobalFilter, | |
| 47 | + useSortBy, | |
| 48 | + usePagination | |
| 49 | + ); | |
| 50 | + | |
| 51 | + const { | |
| 52 | + getTableProps, | |
| 53 | + getTableBodyProps, | |
| 54 | + headerGroups, | |
| 55 | + prepareRow, | |
| 56 | + page, | |
| 57 | + canPreviousPage, | |
| 58 | + canNextPage, | |
| 59 | + pageOptions, | |
| 60 | + pageCount, | |
| 61 | + gotoPage, | |
| 62 | + nextPage, | |
| 63 | + previousPage, | |
| 64 | + setPageSize, | |
| 65 | + state: { pageIndex, pageSize } | |
| 66 | + } = tableInstance; | |
| 67 | + | |
| 68 | + if (pageOptions.length < gopageValue) { | |
| 69 | + setGopageValue(pageOptions.length) | |
| 70 | + } | |
| 71 | + const textColor = useColorModeValue("gray.500", "white"); | |
| 72 | + const borderColor = useColorModeValue("gray.200", "whiteAlpha.100"); | |
| 73 | + const buttonbg = useColorModeValue("gray.200", "white"); | |
| 74 | + const param = useParams() | |
| 75 | + const navigate = useNavigate() | |
| 76 | + return ( | |
| 77 | + <Box | |
| 78 | + direction='column' | |
| 79 | + w='100%' | |
| 80 | + px='0px' | |
| 81 | + style={{ border: '1px solid gray.200' }} | |
| 82 | + overflowX={{ sm: "scroll", lg: "hidden" }} | |
| 83 | + > | |
| 84 | + <Flex justify='space-between' mb='10px' align='center'> | |
| 85 | + <Heading size="md" mb={3}> | |
| 86 | + {title} (<CountUpComponent key={data?.length} targetNumber={data?.length} />) | |
| 87 | + </Heading> | |
| 88 | + {/* <Menu /> */} | |
| 89 | + {!props.text ? callAccess?.create && <Button onClick={() => setAddModel(true)} leftIcon={<AddIcon />} size="sm" colorScheme="gray" bg={buttonbg}>Add New</Button> : <Button onClick={() => navigate('/communication-integration')} size="sm" leftIcon={<MdOutlineMessage />} colorScheme="gray" >send text Msg</Button>} | |
| 90 | + <AddPhoneCall lead={props.lead} fetchData={fetchData} isOpen={addModel} onClose={setAddModel} id={param.id} /> | |
| 91 | + </Flex> | |
| 92 | + <Box overflowY={'auto'} className="table-container" > | |
| 93 | + <Table {...getTableProps()} variant='simple' color='gray.500' mb='24px'> | |
| 94 | + <Thead > | |
| 95 | + {headerGroups?.map((headerGroup, index) => ( | |
| 96 | + <Tr {...headerGroup.getHeaderGroupProps()} key={index}> | |
| 97 | + {headerGroup.headers.map((column, index) => ( | |
| 98 | + <Th | |
| 99 | + {...column.getHeaderProps(column.isSortable !== false && column.getSortByToggleProps())} | |
| 100 | + pe="10px" | |
| 101 | + key={index} | |
| 102 | + borderColor={borderColor} | |
| 103 | + > | |
| 104 | + <Flex | |
| 105 | + justifyContent={column.center ? "center" : "start"} | |
| 106 | + align="center" | |
| 107 | + fontSize={{ sm: "14px", lg: "16px" }} | |
| 108 | + color="secondaryGray.900" | |
| 109 | + > | |
| 110 | + <span style={{ textTransform: "capitalize", marginRight: "8px" }}> | |
| 111 | + {column.render("Header")} | |
| 112 | + </span> | |
| 113 | + </Flex> | |
| 114 | + </Th> | |
| 115 | + ))} | |
| 116 | + </Tr> | |
| 117 | + ))} | |
| 118 | + </Thead> | |
| 119 | + <Tbody {...getTableBodyProps()}> | |
| 120 | + {data?.length === 0 && ( | |
| 121 | + <Tr> | |
| 122 | + <Td colSpan={columns.length}> | |
| 123 | + <Text textAlign={'center'} width="100%" color={textColor} fontSize="sm" fontWeight="700"> | |
| 124 | + <DataNotFound /> | |
| 125 | + </Text> | |
| 126 | + </Td> | |
| 127 | + </Tr> | |
| 128 | + )} | |
| 129 | + {page?.map((row, index) => { | |
| 130 | + prepareRow(row); | |
| 131 | + return ( | |
| 132 | + <Tr {...row?.getRowProps()} key={index}> | |
| 133 | + {row?.cells.map((cell, index) => { | |
| 134 | + let data = ""; | |
| 135 | + if (cell?.column.Header === "sender") { | |
| 136 | + data = ( | |
| 137 | + <Flex align='center'> | |
| 138 | + <Text fontSize='sm' | |
| 139 | + color={textColor} | |
| 140 | + fontWeight='700'> | |
| 141 | + {cell?.value} | |
| 142 | + </Text> | |
| 143 | + </Flex> | |
| 144 | + ); | |
| 145 | + } else if (cell?.column.Header === "recipient") { | |
| 146 | + data = ( | |
| 147 | + <Flex align='center'> | |
| 148 | + <Link to={props.text ? `/text-msg/${cell?.row?.original._id}` : `/phone-call/${cell?.row?.original._id}`}> | |
| 149 | + <Text | |
| 150 | + me='10px' | |
| 151 | + sx={{ '&:hover': { color: 'blue.500', textDecoration: 'underline' } }} | |
| 152 | + color='brand.600' | |
| 153 | + fontSize='sm' | |
| 154 | + fontWeight='700'> | |
| 155 | + {cell?.value} | |
| 156 | + </Text> | |
| 157 | + </Link> | |
| 158 | + </Flex> | |
| 159 | + ); | |
| 160 | + } else if (cell?.column.Header === "Created") { | |
| 161 | + data = ( | |
| 162 | + <Text color={textColor} fontSize='sm' fontWeight='700'> | |
| 163 | + {moment(cell?.row?.values.timestamp).format('h:mma (DD/MM)')} | |
| 164 | + </Text> | |
| 165 | + ); | |
| 166 | + } else if (cell?.column.Header === "time stamp") { | |
| 167 | + data = ( | |
| 168 | + <Text color={textColor} fontSize='sm' fontWeight='700'> | |
| 169 | + {moment(cell?.value).fromNow()} | |
| 170 | + </Text> | |
| 171 | + ); | |
| 172 | + } | |
| 173 | + return ( | |
| 174 | + <Td | |
| 175 | + {...cell?.getCellProps()} | |
| 176 | + key={index} | |
| 177 | + fontSize={{ sm: "14px" }} | |
| 178 | + minW={{ sm: "150px", md: "200px", lg: "auto" }} | |
| 179 | + borderColor='transparent'> | |
| 180 | + {data} | |
| 181 | + </Td> | |
| 182 | + ); | |
| 183 | + })} | |
| 184 | + </Tr> | |
| 185 | + ); | |
| 186 | + })} | |
| 187 | + </Tbody> | |
| 188 | + </Table> | |
| 189 | + </Box> | |
| 190 | + | |
| 191 | + {data?.length > 5 && <Pagination gotoPage={gotoPage} gopageValue={gopageValue} setGopageValue={setGopageValue} pageCount={pageCount} canPreviousPage={canPreviousPage} previousPage={previousPage} canNextPage={canNextPage} pageOptions={pageOptions} setPageSize={setPageSize} nextPage={nextPage} pageSize={pageSize} pageIndex={pageIndex} />} | |
| 192 | + | |
| 193 | + </Box > | |
| 194 | + ); | |
| 195 | +} |
+105-0Client/src/views/admin/contact/components/propertyModel.js
| @@ -0,0 +1,105 @@ | ||
| 1 | +import { Button, Flex, Modal, ModalBody, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalOverlay } from '@chakra-ui/react' | |
| 2 | +import Spinner from 'components/spinner/Spinner' | |
| 3 | +import { useEffect, useState } from 'react' | |
| 4 | +import { postApi } from 'services/api' | |
| 5 | +import { fetchPropertyData } from '../../../../redux/slices/propertySlice' | |
| 6 | +import { useDispatch, useSelector } from 'react-redux' | |
| 7 | +import CommonCheckTable from 'components/reactTable/checktable' | |
| 8 | +import { fetchPropertyCustomFiled } from '../../../../redux/slices/propertyCustomFiledSlice' | |
| 9 | + | |
| 10 | +const PropertyModel = (props) => { | |
| 11 | + const { onClose, isOpen, fetchData, id, interestProperty } = props | |
| 12 | + const [selectedValues, setSelectedValues] = useState([]); | |
| 13 | + const [isLoding, setIsLoding] = useState(false) | |
| 14 | + // const [data, setData] = useState([]) | |
| 15 | + const [propertyData, setPropertyData] = useState([]); | |
| 16 | + const [columns, setColumns] = useState([]); | |
| 17 | + | |
| 18 | + const dispatch = useDispatch() | |
| 19 | + const data = useSelector((state) => state.propertyData.data) | |
| 20 | + | |
| 21 | + const fetchCustomDataFields = async () => { | |
| 22 | + setIsLoding(true); | |
| 23 | + const result = await dispatch(fetchPropertyCustomFiled()) | |
| 24 | + setPropertyData(result?.payload?.data); | |
| 25 | + | |
| 26 | + const tempTableColumns = [ | |
| 27 | + { Header: "#", accessor: "_id", isSortable: false, width: 10 }, | |
| 28 | + ...result?.payload?.data?.[0]?.fields?.filter((field) => field?.isTableField === true)?.map((field) => ({ Header: field?.label, accessor: field?.name })), | |
| 29 | + ]; | |
| 30 | + | |
| 31 | + setColumns(tempTableColumns); | |
| 32 | + setIsLoding(false); | |
| 33 | + } | |
| 34 | + const uniqueValues = [...new Set(selectedValues)]; | |
| 35 | + | |
| 36 | + const handleSubmit = async () => { | |
| 37 | + try { | |
| 38 | + setIsLoding(true) | |
| 39 | + let result = await postApi(`api/contact/add-property-interest/${id}`, uniqueValues); | |
| 40 | + if (result && result.status === 200) { | |
| 41 | + fetchData() | |
| 42 | + onClose() | |
| 43 | + } | |
| 44 | + } | |
| 45 | + catch (e) { | |
| 46 | + console.log(e) | |
| 47 | + } | |
| 48 | + finally { | |
| 49 | + setIsLoding(false) | |
| 50 | + } | |
| 51 | + } | |
| 52 | + useEffect(() => { | |
| 53 | + dispatch(fetchPropertyData()) | |
| 54 | + fetchCustomDataFields() | |
| 55 | + }, []) | |
| 56 | + | |
| 57 | + useEffect(() => { | |
| 58 | + interestProperty?.map((item) => setSelectedValues((prevSelectedValues) => [...prevSelectedValues, item])) | |
| 59 | + }, [interestProperty]) | |
| 60 | + | |
| 61 | + return ( | |
| 62 | + <Modal onClose={onClose} size='full' isOpen={isOpen} > | |
| 63 | + <ModalOverlay /> | |
| 64 | + <ModalContent> | |
| 65 | + <ModalHeader>Select Interested Property</ModalHeader> | |
| 66 | + <ModalCloseButton /> | |
| 67 | + <ModalBody> | |
| 68 | + {isLoding ? | |
| 69 | + <Flex justifyContent={'center'} alignItems={'center'} width="100%" > | |
| 70 | + <Spinner /> | |
| 71 | + </Flex> | |
| 72 | + : | |
| 73 | + <CommonCheckTable | |
| 74 | + title={'Properties'} | |
| 75 | + isLoding={isLoding} | |
| 76 | + columnData={columns ?? []} | |
| 77 | + // dataColumn={columns ?? []} | |
| 78 | + allData={data ?? []} | |
| 79 | + tableData={data} | |
| 80 | + tableCustomFields={propertyData?.[0]?.fields?.filter((field) => field?.isTableField === true) || []} | |
| 81 | + AdvanceSearch={() => ""} | |
| 82 | + ManageGrid={false} | |
| 83 | + deleteMany={false} | |
| 84 | + selectedValues={selectedValues} | |
| 85 | + setSelectedValues={setSelectedValues} | |
| 86 | + selectType="multiple" | |
| 87 | + customSearch={false} | |
| 88 | + /> | |
| 89 | + } | |
| 90 | + </ModalBody> | |
| 91 | + <ModalFooter> | |
| 92 | + <Button size="sm" variant='brand' onClick={handleSubmit} disabled={isLoding ? true : false}> {isLoding ? <Spinner /> : 'Save'}</Button> | |
| 93 | + <Button size="sm" sx={{ | |
| 94 | + marginLeft: 2, | |
| 95 | + textTransform: "capitalize", | |
| 96 | + }} variant="outline" | |
| 97 | + colorScheme="red" onClick={() => onClose()}>Close</Button> | |
| 98 | + </ModalFooter> | |
| 99 | + </ModalContent> | |
| 100 | + </Modal> | |
| 101 | + | |
| 102 | + ) | |
| 103 | +} | |
| 104 | + | |
| 105 | +export default PropertyModel |
+221-0Client/src/views/admin/contact/components/propertyTable.js
| @@ -0,0 +1,221 @@ | ||
| 1 | +import { | |
| 2 | + Box, Checkbox, | |
| 3 | + Flex, Table, | |
| 4 | + Tbody, | |
| 5 | + Td, | |
| 6 | + Text, | |
| 7 | + Th, | |
| 8 | + Thead, | |
| 9 | + Tr, | |
| 10 | + useColorModeValue | |
| 11 | +} from "@chakra-ui/react"; | |
| 12 | +import { useMemo } from "react"; | |
| 13 | +import { | |
| 14 | + useGlobalFilter, | |
| 15 | + usePagination, | |
| 16 | + useSortBy, | |
| 17 | + useTable, | |
| 18 | +} from "react-table"; | |
| 19 | + | |
| 20 | +// Custom components | |
| 21 | +import Card from "components/card/Card"; | |
| 22 | +import Pagination from "components/pagination/Pagination"; | |
| 23 | +import { useState } from "react"; | |
| 24 | +import { Link } from "react-router-dom"; | |
| 25 | +import DataNotFound from "components/notFoundData"; | |
| 26 | + | |
| 27 | +export default function PropertyTable(props) { | |
| 28 | + const { columnsData, tableData, title, selectedValues, setSelectedValues } = props; | |
| 29 | + | |
| 30 | + const columns = useMemo(() => columnsData, [columnsData]); | |
| 31 | + const data = useMemo(() => tableData, [tableData]); | |
| 32 | + const user = JSON.parse(localStorage.getItem("user")) | |
| 33 | + const [gopageValue, setGopageValue] = useState() | |
| 34 | + | |
| 35 | + const tableInstance = useTable( | |
| 36 | + { | |
| 37 | + columns, data, | |
| 38 | + initialState: { pageIndex: 0 } | |
| 39 | + }, | |
| 40 | + useGlobalFilter, | |
| 41 | + useSortBy, | |
| 42 | + usePagination | |
| 43 | + ); | |
| 44 | + | |
| 45 | + const { | |
| 46 | + getTableProps, | |
| 47 | + getTableBodyProps, | |
| 48 | + headerGroups, | |
| 49 | + prepareRow, | |
| 50 | + page, | |
| 51 | + canPreviousPage, | |
| 52 | + canNextPage, | |
| 53 | + pageOptions, | |
| 54 | + pageCount, | |
| 55 | + gotoPage, | |
| 56 | + nextPage, | |
| 57 | + previousPage, | |
| 58 | + setPageSize, | |
| 59 | + state: { pageIndex, pageSize } | |
| 60 | + } = tableInstance; | |
| 61 | + | |
| 62 | + if (pageOptions.length < gopageValue) { | |
| 63 | + setGopageValue(pageOptions.length) | |
| 64 | + } | |
| 65 | + | |
| 66 | + const textColor = useColorModeValue("gray.500", "white"); | |
| 67 | + const borderColor = useColorModeValue("gray.200", "whiteAlpha.100"); | |
| 68 | + | |
| 69 | + const handleCheckboxChange = (event, value) => { | |
| 70 | + if (event.target.checked) { | |
| 71 | + setSelectedValues((prevSelectedValues) => [...prevSelectedValues, value]); | |
| 72 | + } else { | |
| 73 | + setSelectedValues((prevSelectedValues) => | |
| 74 | + prevSelectedValues.filter((selectedValue) => selectedValue !== value) | |
| 75 | + ); | |
| 76 | + } | |
| 77 | + }; | |
| 78 | + | |
| 79 | + | |
| 80 | + return ( | |
| 81 | + <Card | |
| 82 | + direction='column' | |
| 83 | + w='100%' | |
| 84 | + px='0px' | |
| 85 | + style={{ border: '1px solid gray.200' } | |
| 86 | + } | |
| 87 | + overflowX={{ sm: "scroll", lg: "hidden" }}> | |
| 88 | + <Box overflowY={'auto'} className="table-container-property" > | |
| 89 | + <Table {...getTableProps()} variant='simple' color='gray.500' mb='24px'> | |
| 90 | + <Thead > | |
| 91 | + {headerGroups?.map((headerGroup, index) => ( | |
| 92 | + <Tr {...headerGroup.getHeaderGroupProps()} key={index}> | |
| 93 | + {headerGroup.headers.map((column, index) => ( | |
| 94 | + <Th | |
| 95 | + {...column.getHeaderProps(column.getSortByToggleProps())} | |
| 96 | + pe='10px' | |
| 97 | + key={index} | |
| 98 | + borderColor={borderColor}> | |
| 99 | + <Flex | |
| 100 | + justify='space-between' | |
| 101 | + align='center' | |
| 102 | + fontSize={{ sm: "14px", lg: "14px" }} | |
| 103 | + color="secondaryGray.900"> | |
| 104 | + {column.render("Header")} | |
| 105 | + </Flex> | |
| 106 | + </Th> | |
| 107 | + ))} | |
| 108 | + </Tr> | |
| 109 | + ))} | |
| 110 | + </Thead> | |
| 111 | + <Tbody {...getTableBodyProps()}> | |
| 112 | + {data?.length === 0 && ( | |
| 113 | + <Tr> | |
| 114 | + <Td colSpan={columns.length}> | |
| 115 | + <Text textAlign={'center'} width="100%" color={textColor} fontSize="sm" fontWeight="700"> | |
| 116 | + <DataNotFound /> | |
| 117 | + </Text> | |
| 118 | + </Td> | |
| 119 | + </Tr> | |
| 120 | + )} | |
| 121 | + {page?.map((row, index) => { | |
| 122 | + prepareRow(row); | |
| 123 | + return ( | |
| 124 | + <Tr {...row?.getRowProps()} key={index}> | |
| 125 | + {row?.cells?.map((cell, index) => { | |
| 126 | + let data = ""; | |
| 127 | + if (cell?.column.Header === "#") { | |
| 128 | + data = ( | |
| 129 | + <Flex align="center"> | |
| 130 | + <Checkbox colorScheme="brandScheme" value={selectedValues} isChecked={selectedValues.includes(cell?.value)} onChange={(event) => handleCheckboxChange(event, cell?.value)} me="10px" /> | |
| 131 | + <Text color={textColor} fontSize="sm" fontWeight="700"> | |
| 132 | + {cell?.row?.index + 1} | |
| 133 | + </Text> | |
| 134 | + </Flex> | |
| 135 | + ); | |
| 136 | + } else if (cell?.column.Header === "property Type") { | |
| 137 | + data = ( | |
| 138 | + <Link to={user?.role !== 'superAdmin' ? `/propertyView/${cell?.row?.original?._id}` : `/propertyView/${cell?.row?.original?._id}`}> | |
| 139 | + <Text | |
| 140 | + me="10px" | |
| 141 | + sx={{ '&:hover': { color: 'blue.500', textDecoration: 'underline' } }} | |
| 142 | + color='brand.600' | |
| 143 | + fontSize="sm" | |
| 144 | + fontWeight="700" | |
| 145 | + > | |
| 146 | + {cell?.value} | |
| 147 | + </Text> | |
| 148 | + </Link> | |
| 149 | + ); | |
| 150 | + } else if (cell?.column.Header === "property Address") { | |
| 151 | + data = ( | |
| 152 | + | |
| 153 | + <Text | |
| 154 | + me="10px" | |
| 155 | + color={textColor} | |
| 156 | + fontSize="sm" | |
| 157 | + fontWeight="700" | |
| 158 | + > | |
| 159 | + {cell?.value} | |
| 160 | + </Text> | |
| 161 | + | |
| 162 | + ); | |
| 163 | + } else if (cell?.column.Header === "listing Price") { | |
| 164 | + data = ( | |
| 165 | + <Text | |
| 166 | + me="10px" | |
| 167 | + color={textColor} | |
| 168 | + fontSize="sm" | |
| 169 | + fontWeight="700" | |
| 170 | + > | |
| 171 | + {cell?.value} | |
| 172 | + </Text> | |
| 173 | + ); | |
| 174 | + } else if (cell?.column.Header === "square Footage") { | |
| 175 | + data = ( | |
| 176 | + <Text color={textColor} fontSize="sm" fontWeight="700"> | |
| 177 | + {cell?.value} | |
| 178 | + </Text> | |
| 179 | + ); | |
| 180 | + } else if (cell?.column.Header === "year Built") { | |
| 181 | + data = ( | |
| 182 | + <Text color={textColor} fontSize="sm" fontWeight="700"> | |
| 183 | + {cell?.value} | |
| 184 | + </Text> | |
| 185 | + ); | |
| 186 | + } else if (cell?.column.Header === "number of Bedrooms") { | |
| 187 | + data = ( | |
| 188 | + <Text color={textColor} fontSize="sm" fontWeight="700"> | |
| 189 | + {cell?.value} | |
| 190 | + </Text> | |
| 191 | + ); | |
| 192 | + } else if (cell?.column.Header === "number of Bathrooms") { | |
| 193 | + data = ( | |
| 194 | + <Text color={textColor} fontSize="sm" fontWeight="700"> | |
| 195 | + {cell?.value} | |
| 196 | + </Text> | |
| 197 | + ); | |
| 198 | + } | |
| 199 | + return ( | |
| 200 | + <Td | |
| 201 | + {...cell?.getCellProps()} | |
| 202 | + key={index} | |
| 203 | + fontSize={{ sm: "14px" }} | |
| 204 | + minW={{ sm: "150px", md: "200px", lg: "auto" }} | |
| 205 | + borderColor='transparent'> | |
| 206 | + {data} | |
| 207 | + </Td> | |
| 208 | + ); | |
| 209 | + })} | |
| 210 | + </Tr> | |
| 211 | + ); | |
| 212 | + })} | |
| 213 | + </Tbody> | |
| 214 | + </Table> | |
| 215 | + </Box> | |
| 216 | + | |
| 217 | + {data?.length > 5 && <Pagination gotoPage={gotoPage} gopageValue={gopageValue} setGopageValue={setGopageValue} pageCount={pageCount} canPreviousPage={canPreviousPage} previousPage={previousPage} canNextPage={canNextPage} pageOptions={pageOptions} setPageSize={setPageSize} nextPage={nextPage} pageSize={pageSize} pageIndex={pageIndex} />} | |
| 218 | + | |
| 219 | + </Card > | |
| 220 | + ); | |
| 221 | +} |
+187-0Client/src/views/admin/contact/index.js
| @@ -0,0 +1,187 @@ | ||
| 1 | + | |
| 2 | +import { useEffect, useState } from 'react'; | |
| 3 | +import { useNavigate } from "react-router-dom"; | |
| 4 | +import { Grid, GridItem, Text, useDisclosure, Menu, MenuButton, MenuItem, MenuList } from '@chakra-ui/react'; | |
| 5 | +import { DeleteIcon, ViewIcon, EditIcon, EmailIcon, PhoneIcon } from "@chakra-ui/icons"; | |
| 6 | +import { CiMenuKebab } from "react-icons/ci"; | |
| 7 | +import { getApi } from 'services/api'; | |
| 8 | +import Add from "./Add"; | |
| 9 | +import Edit from './Edit'; | |
| 10 | +import AddEmailHistory from "../emailHistory/components/AddEmail"; | |
| 11 | +import AddPhoneCall from "../phoneCall/components/AddPhoneCall"; | |
| 12 | +import { HasAccess } from "../../../redux/accessUtils"; | |
| 13 | +import CommonCheckTable from "../../../components/reactTable/checktable"; | |
| 14 | +import ImportModal from "./components/ImportModel"; | |
| 15 | +import CommonDeleteModel from 'components/commonDeleteModel'; | |
| 16 | +import { deleteManyApi } from 'services/api'; | |
| 17 | +import { fetchContactData } from '../../../redux/slices/contactSlice'; | |
| 18 | +import { useDispatch, useSelector } from 'react-redux'; | |
| 19 | +import { fetchContactCustomFiled } from '../../../redux/slices/contactCustomFiledSlice'; | |
| 20 | +import { toast } from 'react-toastify'; | |
| 21 | + | |
| 22 | +const Index = () => { | |
| 23 | + const navigate = useNavigate(); | |
| 24 | + const [permission, emailAccess, callAccess] = HasAccess(['Contacts', 'Emails', 'Calls']); | |
| 25 | + const { isOpen, onOpen, onClose } = useDisclosure(); | |
| 26 | + const dispatch = useDispatch(); | |
| 27 | + | |
| 28 | + const [isLoding, setIsLoding] = useState(false); | |
| 29 | + const [action, setAction] = useState(false); | |
| 30 | + const [columns, setColumns] = useState([]); | |
| 31 | + const [contactData, setContactData] = useState([]); | |
| 32 | + const [edit, setEdit] = useState(false); | |
| 33 | + const [deleteModel, setDelete] = useState(false); | |
| 34 | + const [addPhoneCall, setAddPhoneCall] = useState(false); | |
| 35 | + const [callSelectedId, setCallSelectedId] = useState(); | |
| 36 | + const [addEmailHistory, setAddEmailHistory] = useState(false); | |
| 37 | + const [selectedId, setSelectedId] = useState(); | |
| 38 | + const [selectedValues, setSelectedValues] = useState([]); | |
| 39 | + const [isImportContact, setIsImport] = useState(false); | |
| 40 | + const [emailRec, setEmailRec] = useState(''); | |
| 41 | + const [phoneRec, setPhoneRec] = useState({}); | |
| 42 | + | |
| 43 | + const data = useSelector((state) => state?.contactData?.data) | |
| 44 | + | |
| 45 | + const handleOpenEmail = (id, dataContact) => { | |
| 46 | + if (id) { | |
| 47 | + setEmailRec(dataContact?.email); | |
| 48 | + setAddEmailHistory(true); | |
| 49 | + } | |
| 50 | + } | |
| 51 | + | |
| 52 | + const fetchCustomDataFields = async () => { | |
| 53 | + setIsLoding(true); | |
| 54 | + const result = await dispatch(fetchContactCustomFiled()); | |
| 55 | + if (result?.payload?.status === 200) { | |
| 56 | + setContactData(result?.payload?.data); | |
| 57 | + } else { | |
| 58 | + toast.error("Failed to fetch data", "error"); | |
| 59 | + } | |
| 60 | + const actionHeader = { | |
| 61 | + Header: "Action", accessor: "action", isSortable: false, center: true, | |
| 62 | + cell: ({ row }) => ( | |
| 63 | + <Text fontSize="md" fontWeight="900" textAlign={"center"} > | |
| 64 | + <Menu isLazy > | |
| 65 | + <MenuButton><CiMenuKebab /></MenuButton> | |
| 66 | + <MenuList minW={'fit-content'} transform={"translate(1520px, 173px);"}> | |
| 67 | + {permission?.update && | |
| 68 | + <MenuItem py={2.5} icon={<EditIcon fontSize={15} mb={1} />} onClick={() => { setEdit(true); setSelectedId(row?.values?._id); }}>Edit</MenuItem>} | |
| 69 | + {callAccess?.create && | |
| 70 | + <MenuItem py={2.5} width={"165px"} onClick={() => { setPhoneRec(row?.original); setAddPhoneCall(true); setCallSelectedId(row?.values?._id); }} icon={<PhoneIcon fontSize={15} mb={1} />}>Create Call</MenuItem>} | |
| 71 | + {emailAccess?.create && | |
| 72 | + <MenuItem py={2.5} width={"165px"} onClick={() => { | |
| 73 | + handleOpenEmail(row?.values?._id, row?.original); setSelectedId(row?.values?._id) | |
| 74 | + }} icon={<EmailIcon fontSize={15} mb={1} />}>Send Email</MenuItem>} | |
| 75 | + {permission?.view && | |
| 76 | + <MenuItem py={2.5} color={'green'} icon={<ViewIcon mb={1} fontSize={15} />} onClick={() => { navigate(`/contactView/${row?.values?._id}`) }}>View</MenuItem>} | |
| 77 | + {permission?.delete && | |
| 78 | + <MenuItem py={2.5} color={'red'} icon={<DeleteIcon fontSize={15} mb={1} />} onClick={() => { setDelete(true); setSelectedValues([row?.values?._id]); }}>Delete</MenuItem>} | |
| 79 | + </MenuList> | |
| 80 | + </Menu> | |
| 81 | + </Text> | |
| 82 | + ) | |
| 83 | + }; | |
| 84 | + | |
| 85 | + const tempTableColumns = [ | |
| 86 | + { Header: "#", accessor: "_id", isSortable: false, width: 10 }, | |
| 87 | + ...(result?.payload?.data && result.payload.data.length > 0 | |
| 88 | + ? result.payload.data[0]?.fields | |
| 89 | + ?.filter((field) => field?.isTableField === true && field?.isView) | |
| 90 | + ?.map( | |
| 91 | + (field) => ({ | |
| 92 | + Header: field?.label, | |
| 93 | + accessor: field?.name, | |
| 94 | + cell: (cell) => ( | |
| 95 | + <div className="selectOpt"> | |
| 96 | + <Text | |
| 97 | + onClick={() => { | |
| 98 | + navigate(`/contactView/${cell?.row?.original?._id}`); | |
| 99 | + }} | |
| 100 | + me="10px" | |
| 101 | + sx={{ '&:hover': { color: 'blue.500', textDecoration: 'underline' }, cursor: 'pointer' }} | |
| 102 | + color='brand.600' | |
| 103 | + fontSize="sm" | |
| 104 | + fontWeight="700" | |
| 105 | + > | |
| 106 | + {cell?.value || "-"} | |
| 107 | + </Text> | |
| 108 | + </div> | |
| 109 | + ), | |
| 110 | + }) | |
| 111 | + ) || [] | |
| 112 | + : []), | |
| 113 | + ...(result?.payload?.data?.[0]?.fields || []) // Check if fields is defined, if not, use empty array | |
| 114 | + .filter(field => field?.isTableField === true && !field?.isView) // Filter out fields where isTableField is true | |
| 115 | + .map(field => ({ Header: field?.label, accessor: field?.name })), | |
| 116 | + ...(permission?.update || permission?.view || permission?.delete ? [actionHeader] : []) | |
| 117 | + ]; | |
| 118 | + | |
| 119 | + setColumns(tempTableColumns); | |
| 120 | + setIsLoding(false); | |
| 121 | + }; | |
| 122 | + | |
| 123 | + const handleDeleteContact = async (ids) => { | |
| 124 | + try { | |
| 125 | + setIsLoding(true) | |
| 126 | + let response = await deleteManyApi('api/contact/deleteMany', ids) | |
| 127 | + if (response.status === 200) { | |
| 128 | + setSelectedValues([]) | |
| 129 | + setDelete(false) | |
| 130 | + setAction((pre) => !pre) | |
| 131 | + } | |
| 132 | + } catch (error) { | |
| 133 | + console.log(error) | |
| 134 | + } | |
| 135 | + finally { | |
| 136 | + setIsLoding(false) | |
| 137 | + } | |
| 138 | + } | |
| 139 | + | |
| 140 | + useEffect(async () => { | |
| 141 | + // fetchData(); | |
| 142 | + await dispatch(fetchContactData()) | |
| 143 | + fetchCustomDataFields(); | |
| 144 | + }, [action]); | |
| 145 | + | |
| 146 | + return ( | |
| 147 | + <div> | |
| 148 | + <Grid templateColumns="repeat(6, 1fr)" mb={3} gap={4}> | |
| 149 | + {!isLoding && | |
| 150 | + <GridItem colSpan={6}> | |
| 151 | + <CommonCheckTable | |
| 152 | + title={"Contacts"} | |
| 153 | + isLoding={isLoding} | |
| 154 | + columnData={columns ?? []} | |
| 155 | + // dataColumn={dataColumn ?? []} | |
| 156 | + allData={data ?? []} | |
| 157 | + tableData={data} | |
| 158 | + tableCustomFields={contactData?.[0]?.fields?.filter((field) => field?.isTableField === true) || []} | |
| 159 | + access={permission} | |
| 160 | + // action={action} | |
| 161 | + // setAction={setAction} | |
| 162 | + // selectedColumns={selectedColumns} | |
| 163 | + // setSelectedColumns={setSelectedColumns} | |
| 164 | + // isOpen={isOpen} | |
| 165 | + // onClose={onclose} | |
| 166 | + onOpen={onOpen} | |
| 167 | + selectedValues={selectedValues} | |
| 168 | + setSelectedValues={setSelectedValues} | |
| 169 | + setDelete={setDelete} | |
| 170 | + setIsImport={setIsImport} | |
| 171 | + /> | |
| 172 | + </GridItem> | |
| 173 | + } | |
| 174 | + </Grid> | |
| 175 | + | |
| 176 | + {isOpen && <Add isOpen={isOpen} size={"lg"} contactData={contactData[0]} onClose={onClose} setAction={setAction} action={action} />} | |
| 177 | + {edit && <Edit isOpen={edit} size={"lg"} contactData={contactData[0]} selectedId={selectedId} setSelectedId={setSelectedId} onClose={setEdit} setAction={setAction} moduleId={contactData?.[0]?._id} />} | |
| 178 | + {deleteModel && <CommonDeleteModel isOpen={deleteModel} onClose={() => setDelete(false)} type='Contacts' handleDeleteData={handleDeleteContact} ids={selectedValues} />} | |
| 179 | + {addEmailHistory && <AddEmailHistory fetchData={fetchContactData} isOpen={addEmailHistory} onClose={setAddEmailHistory} id={selectedId} contactEmail={emailRec} />} | |
| 180 | + {addPhoneCall && <AddPhoneCall fetchData={fetchContactData} isOpen={addPhoneCall} onClose={setAddPhoneCall} id={callSelectedId} cData={phoneRec} />} | |
| 181 | + {isImportContact && <ImportModal text='Contact file' isOpen={isImportContact} onClose={setIsImport} customFields={contactData?.[0]?.fields || []} />} | |
| 182 | + | |
| 183 | + </div> | |
| 184 | + ) | |
| 185 | +} | |
| 186 | + | |
| 187 | +export default Index |
+128-0Client/src/views/admin/customField/addEditHeading.js
| @@ -0,0 +1,128 @@ | ||
| 1 | +import { Button, FormLabel, Grid, GridItem, Input, Modal, ModalBody, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalOverlay, Spinner, Text } from '@chakra-ui/react' | |
| 2 | +import { useFormik } from 'formik' | |
| 3 | +import React, { useState } from 'react' | |
| 4 | +import { toast } from 'react-toastify' | |
| 5 | +import { postApi, putApi } from 'services/api' | |
| 6 | +import * as yup from 'yup' | |
| 7 | + | |
| 8 | +const AddEditHeading = (props) => { | |
| 9 | + | |
| 10 | + const { moduleId, filed, updateData, setUpdateData } = props; | |
| 11 | + | |
| 12 | + const [isLoding, setIsLoding] = useState(false) | |
| 13 | + | |
| 14 | + const handleClose = () => { | |
| 15 | + props.onClose(false); | |
| 16 | + setUpdateData({}); | |
| 17 | + } | |
| 18 | + | |
| 19 | + const headingSchema = yup.object({ | |
| 20 | + heading: yup.string().required('Heading is required') | |
| 21 | + }) | |
| 22 | + | |
| 23 | + | |
| 24 | + const initialValues = { | |
| 25 | + heading: updateData && updateData?.heading ? updateData?.heading : '', | |
| 26 | + }; | |
| 27 | + | |
| 28 | + const formik = useFormik({ | |
| 29 | + initialValues: initialValues, | |
| 30 | + validationSchema: headingSchema, | |
| 31 | + enableReinitialize: true, | |
| 32 | + onSubmit: (values, { resetForm }) => { | |
| 33 | + | |
| 34 | + if (updateData && updateData?.heading) { | |
| 35 | + editData(); | |
| 36 | + } else { | |
| 37 | + fetchAddData(); | |
| 38 | + } | |
| 39 | + | |
| 40 | + resetForm(); | |
| 41 | + }, | |
| 42 | + }); | |
| 43 | + | |
| 44 | + const { errors, touched, values, handleBlur, handleChange, handleSubmit, setFieldValue, resetForm } = formik | |
| 45 | + | |
| 46 | + const fetchAddData = async () => { | |
| 47 | + try { | |
| 48 | + const addDataPayload = { | |
| 49 | + moduleId: moduleId, | |
| 50 | + headings: [values] | |
| 51 | + } | |
| 52 | + | |
| 53 | + let response = await postApi('api/custom-field/add-heading', addDataPayload); | |
| 54 | + if (response.status === 200) { | |
| 55 | + props.onClose(false); | |
| 56 | + props.fetchData(); | |
| 57 | + } else { | |
| 58 | + toast.error(response.response.data) | |
| 59 | + } | |
| 60 | + } | |
| 61 | + catch { | |
| 62 | + } | |
| 63 | + finally { | |
| 64 | + } | |
| 65 | + }; | |
| 66 | + | |
| 67 | + const editData = async () => { | |
| 68 | + try { | |
| 69 | + const editDataPayload = { | |
| 70 | + moduleId: moduleId, | |
| 71 | + updatedHeading: values | |
| 72 | + } | |
| 73 | + | |
| 74 | + let response = await putApi(`api/custom-field/change-single-heading/${updateData?._id}`, editDataPayload); | |
| 75 | + if (response.status === 200) { | |
| 76 | + handleClose(); | |
| 77 | + props.fetchData(); | |
| 78 | + } | |
| 79 | + } | |
| 80 | + catch { | |
| 81 | + } | |
| 82 | + finally { | |
| 83 | + } | |
| 84 | + }; | |
| 85 | + | |
| 86 | + return ( | |
| 87 | + <div> | |
| 88 | + <Modal onClose={() => handleClose()} isOpen={props.isOpen} isCentered> | |
| 89 | + <ModalOverlay /> | |
| 90 | + <ModalContent maxWidth={"2xl"}> | |
| 91 | + <ModalHeader>{updateData && updateData?.heading ? 'Edit' : 'Add'} Heading</ModalHeader> | |
| 92 | + <ModalCloseButton /> | |
| 93 | + <ModalBody> | |
| 94 | + <> | |
| 95 | + <Grid templateColumns="repeat(12, 1fr)" gap={3}> | |
| 96 | + <GridItem colSpan={{ base: 12 }} > | |
| 97 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='8px'> | |
| 98 | + Heading<Text color={"red"}>*</Text> | |
| 99 | + </FormLabel> | |
| 100 | + <Input | |
| 101 | + fontSize='sm' | |
| 102 | + onChange={handleChange} onBlur={handleBlur} | |
| 103 | + value={values.heading} | |
| 104 | + name="heading" | |
| 105 | + placeholder={`Enter Heading`} | |
| 106 | + fontWeight='500' | |
| 107 | + borderColor={errors.heading && touched.heading ? "red.300" : null} | |
| 108 | + /> | |
| 109 | + <Text mb='10px' color={'red'}> {errors.heading && touched.heading && errors.heading}</Text> | |
| 110 | + </GridItem> | |
| 111 | + </Grid> | |
| 112 | + </> | |
| 113 | + </ModalBody> | |
| 114 | + <ModalFooter> | |
| 115 | + <Button colorScheme="brand" size="sm" mr={2} type='submit' disabled={isLoding ? true : false} onClick={handleSubmit} >{isLoding ? <Spinner /> : updateData && updateData?.heading ? 'Update' : 'Save'}</Button> | |
| 116 | + <Button variant="outline" | |
| 117 | + colorScheme='red' size="sm" | |
| 118 | + sx={{ | |
| 119 | + textTransform: "capitalize", | |
| 120 | + }} onClick={() => { handleClose(); resetForm() }}>Close</Button> | |
| 121 | + </ModalFooter> | |
| 122 | + </ModalContent> | |
| 123 | + </Modal> | |
| 124 | + </div> | |
| 125 | + ) | |
| 126 | +} | |
| 127 | + | |
| 128 | +export default AddEditHeading |
+667-0Client/src/views/admin/customField/addfield.js
| @@ -0,0 +1,667 @@ | ||
| 1 | +import { DeleteIcon } from '@chakra-ui/icons' | |
| 2 | +import { Button, Checkbox, Flex, FormLabel, Grid, GridItem, Heading, Input, Modal, ModalBody, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalOverlay, Select, Spinner, Text } from '@chakra-ui/react' | |
| 3 | +import { useFormik } from 'formik' | |
| 4 | +import React, { useState } from 'react' | |
| 5 | +import { toast } from 'react-toastify' | |
| 6 | +import { addFiledSchema } from 'schema' | |
| 7 | +import { postApi } from 'services/api' | |
| 8 | +import * as yup from 'yup' | |
| 9 | + | |
| 10 | +const Addfield = (props) => { | |
| 11 | + | |
| 12 | + const { moduleId, filed, headingsData, validations } = props; | |
| 13 | + | |
| 14 | + const [isLoding, setIsLoding] = useState(false) | |
| 15 | + const [validationType, setValidationType] = useState('') | |
| 16 | + | |
| 17 | + const handleClose = () => { | |
| 18 | + props.onClose(false); | |
| 19 | + } | |
| 20 | + const initialValues = { | |
| 21 | + label: "", | |
| 22 | + name: "", | |
| 23 | + type: "text", | |
| 24 | + delete: false, | |
| 25 | + fixed: false, | |
| 26 | + belongsTo: null, | |
| 27 | + options: [{ | |
| 28 | + name: '', | |
| 29 | + value: '' | |
| 30 | + }, { | |
| 31 | + name: '', | |
| 32 | + value: '' | |
| 33 | + }], | |
| 34 | + validate: false, | |
| 35 | + validation: [ | |
| 36 | + { | |
| 37 | + require: false, | |
| 38 | + message: "", | |
| 39 | + }, | |
| 40 | + { | |
| 41 | + min: false, | |
| 42 | + value: "", | |
| 43 | + message: "", | |
| 44 | + }, | |
| 45 | + { | |
| 46 | + max: false, | |
| 47 | + value: "", | |
| 48 | + message: "", | |
| 49 | + }, | |
| 50 | + { | |
| 51 | + match: false, | |
| 52 | + value: "", | |
| 53 | + message: "", | |
| 54 | + }, | |
| 55 | + { | |
| 56 | + types: false, | |
| 57 | + formikType: '', | |
| 58 | + message: "", | |
| 59 | + }, | |
| 60 | + ], | |
| 61 | + }; | |
| 62 | + | |
| 63 | + const formik = useFormik({ | |
| 64 | + initialValues: initialValues, | |
| 65 | + validationSchema: addFiledSchema, | |
| 66 | + validate: (values) => { | |
| 67 | + const errors = {}; | |
| 68 | + if (values?.validation && (values.validation[1]?.min || values?.type === 'range') && values.validation[1]?.value === '') { | |
| 69 | + errors.validation = errors.validation || []; | |
| 70 | + errors.validation[1] = errors.validation[1] || {}; | |
| 71 | + errors.validation[1].value = 'Value is required'; | |
| 72 | + } | |
| 73 | + | |
| 74 | + if (values?.validation && (values.validation[2]?.max || values?.type === 'range') && values.validation[2]?.value === '') { | |
| 75 | + errors.validation = errors.validation || []; | |
| 76 | + errors.validation[2] = errors.validation[2] || {}; | |
| 77 | + errors.validation[2].value = 'Value is required'; | |
| 78 | + } | |
| 79 | + if (values?.validation && values.validation[3]?.match && !values.validation[3]?.value) { | |
| 80 | + errors.validation = errors.validation || []; | |
| 81 | + errors.validation[3] = errors.validation[3] || {}; | |
| 82 | + errors.validation[3].value = 'Value is required'; | |
| 83 | + } | |
| 84 | + if (values?.validation && values.validation[3]?.match && !values.validation[3]?.message) { | |
| 85 | + errors.validation = errors.validation || []; | |
| 86 | + errors.validation[3] = errors.validation[3] || {}; | |
| 87 | + errors.validation[3].message = 'Meassage is required'; | |
| 88 | + } | |
| 89 | + if (values?.validation && values.validation[4]?.types && !values.validation[4]?.formikType) { | |
| 90 | + errors.validation = errors.validation || []; | |
| 91 | + errors.validation[4] = errors.validation[4] || {}; | |
| 92 | + errors.validation[4].formikType = 'FormikType is required'; | |
| 93 | + } | |
| 94 | + if (values?.options && (values?.type === 'radio' || values?.type === 'select')) { | |
| 95 | + | |
| 96 | + values?.options?.forEach((option, index) => { | |
| 97 | + if (!option.name) { | |
| 98 | + if (!errors.options) { | |
| 99 | + errors.options = []; | |
| 100 | + } | |
| 101 | + if (!errors.options[index]) { | |
| 102 | + errors.options[index] = {}; | |
| 103 | + } | |
| 104 | + errors.options[index].name = 'Name is required'; | |
| 105 | + } | |
| 106 | + if (!option.value) { | |
| 107 | + if (!errors.options) { | |
| 108 | + errors.options = []; | |
| 109 | + } | |
| 110 | + if (!errors.options[index]) { | |
| 111 | + errors.options[index] = {}; | |
| 112 | + } | |
| 113 | + errors.options[index].value = 'Value is required'; | |
| 114 | + } | |
| 115 | + }); | |
| 116 | + } | |
| 117 | + return errors; | |
| 118 | + }, | |
| 119 | + onSubmit: (values, { resetForm }) => { | |
| 120 | + fetchAddData() | |
| 121 | + | |
| 122 | + }, | |
| 123 | + }); | |
| 124 | + | |
| 125 | + const handleAddOption = () => { | |
| 126 | + formik.setFieldValue('options', [...formik.values.options, { name: '', value: '' }]); | |
| 127 | + }; | |
| 128 | + | |
| 129 | + const handleRemoveOption = (index) => { | |
| 130 | + const newOptions = [...formik.values.options]; | |
| 131 | + newOptions.splice(index, 1); | |
| 132 | + formik.setFieldValue('options', newOptions); | |
| 133 | + }; | |
| 134 | + | |
| 135 | + const { errors, touched, values, handleBlur, handleChange, handleSubmit, setFieldValue, resetForm } = formik | |
| 136 | + | |
| 137 | + const fetchAddData = async () => { | |
| 138 | + try { | |
| 139 | + const addDataPayload = { | |
| 140 | + moduleId: moduleId, | |
| 141 | + fields: [values] | |
| 142 | + } | |
| 143 | + | |
| 144 | + let response = await postApi('api/custom-field/add', addDataPayload); | |
| 145 | + if (response.status === 200) { | |
| 146 | + setValidationType('') | |
| 147 | + props.onClose() | |
| 148 | + resetForm() | |
| 149 | + props.fetchData() | |
| 150 | + } | |
| 151 | + if (!response.response.data?.success) { | |
| 152 | + toast.error(response.response.data?.message); | |
| 153 | + } | |
| 154 | + } | |
| 155 | + catch { | |
| 156 | + } | |
| 157 | + finally { | |
| 158 | + } | |
| 159 | + } | |
| 160 | + | |
| 161 | + return ( | |
| 162 | + <div> | |
| 163 | + <Modal onClose={props.onClose} isOpen={props.isOpen} isCentered> | |
| 164 | + <ModalOverlay /> | |
| 165 | + <ModalContent maxWidth={"2xl"}> | |
| 166 | + <ModalHeader>Add Field</ModalHeader> | |
| 167 | + <ModalCloseButton onClick={() => resetForm()} /> | |
| 168 | + <ModalBody> | |
| 169 | + <> | |
| 170 | + <Grid templateColumns="repeat(12, 1fr)" gap={3}> | |
| 171 | + <GridItem colSpan={{ base: 12, sm: 6, md: 4 }}> | |
| 172 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='8px'> | |
| 173 | + Label<Text color={"red"}>*</Text> | |
| 174 | + </FormLabel> | |
| 175 | + <Input | |
| 176 | + fontSize='sm' | |
| 177 | + onChange={handleChange} onBlur={handleBlur} | |
| 178 | + value={values.label} | |
| 179 | + name="label" | |
| 180 | + placeholder={`Enter Label`} | |
| 181 | + fontWeight='500' | |
| 182 | + borderColor={errors.label && touched.label ? "red.300" : null} | |
| 183 | + /> | |
| 184 | + <Text mb='10px' color={'red'}> {errors.label && touched.label && errors.label}</Text> | |
| 185 | + </GridItem> | |
| 186 | + <GridItem colSpan={{ base: 12, sm: 6, md: 4 }}> | |
| 187 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='8px'> | |
| 188 | + Name<Text color={"red"}>*</Text> | |
| 189 | + </FormLabel> | |
| 190 | + <Input | |
| 191 | + fontSize='sm' | |
| 192 | + onKeyDown={(e) => { | |
| 193 | + if (e.code === 'Space') { | |
| 194 | + e.preventDefault(); | |
| 195 | + } | |
| 196 | + }} | |
| 197 | + onChange={(e) => { | |
| 198 | + const newValue = e.target.value.replace(/\s/g, ''); | |
| 199 | + handleChange({ | |
| 200 | + target: { | |
| 201 | + name: 'name', | |
| 202 | + value: newValue, | |
| 203 | + }, | |
| 204 | + }); | |
| 205 | + }} | |
| 206 | + onBlur={handleBlur} | |
| 207 | + value={values.name} | |
| 208 | + name="name" | |
| 209 | + placeholder='Enter Name' | |
| 210 | + fontWeight='500' | |
| 211 | + borderColor={errors.name && touched.name ? "red.300" : null} | |
| 212 | + /> | |
| 213 | + <Text mb='10px' color={'red'}> {errors.name && touched.name && errors.name}</Text> | |
| 214 | + </GridItem> | |
| 215 | + <GridItem colSpan={{ base: 12, sm: 6, md: 4 }}> | |
| 216 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='8px'> | |
| 217 | + Type | |
| 218 | + </FormLabel> | |
| 219 | + <Select | |
| 220 | + value={values.type} | |
| 221 | + name="type" | |
| 222 | + onChange={(e) => { | |
| 223 | + setFieldValue('type', e.target.value) | |
| 224 | + setFieldValue(`validation[${4}].formikType`, e.target.value === 'date' ? 'date' : '') | |
| 225 | + setFieldValue(`validation[${4}].types`, e.target.value === 'date' ? true : false) | |
| 226 | + }} | |
| 227 | + fontWeight='500' | |
| 228 | + // placeholder={'Select Type'} | |
| 229 | + borderColor={errors.type && touched.type ? "red.300" : null} | |
| 230 | + > | |
| 231 | + <option value='text'>Text</option> | |
| 232 | + <option value='number'>Number</option> | |
| 233 | + <option value='radio'>Radio</option> | |
| 234 | + <option value='check'>Check</option> | |
| 235 | + <option value='date'>Date</option> | |
| 236 | + <option value='tel'>Tel</option> | |
| 237 | + <option value='range'>Range</option> | |
| 238 | + <option value='color'>Color</option> | |
| 239 | + <option value='email'>Email</option> | |
| 240 | + <option value='url'>Url</option> | |
| 241 | + <option value='select'>Dropdown</option> | |
| 242 | + </Select> | |
| 243 | + <Text mb='10px' color={'red'}> {errors.type && touched.type && errors.type}</Text> | |
| 244 | + </GridItem> | |
| 245 | + <GridItem colSpan={{ base: 12, sm: 6, md: 4 }}> | |
| 246 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='8px'> | |
| 247 | + Belongs To | |
| 248 | + {/* Belongs To */} | |
| 249 | + </FormLabel> | |
| 250 | + <Select | |
| 251 | + value={values.belongsTo} | |
| 252 | + name="belongsTo" | |
| 253 | + onChange={(e) => { | |
| 254 | + const selectedValue = e.target.value; | |
| 255 | + const newValue = selectedValue === "" ? null : selectedValue; | |
| 256 | + handleChange({ | |
| 257 | + target: { | |
| 258 | + name: "belongsTo", | |
| 259 | + value: newValue, | |
| 260 | + }, | |
| 261 | + }) | |
| 262 | + }} | |
| 263 | + onBlur={handleBlur} | |
| 264 | + fontWeight='500' | |
| 265 | + placeholder={'Select Heading'} | |
| 266 | + borderColor={errors.belongsTo && touched.belongsTo ? "red.300" : null} | |
| 267 | + isDisabled={headingsData?.length < 1} | |
| 268 | + > | |
| 269 | + { | |
| 270 | + headingsData?.map(item => ( | |
| 271 | + <option value={item?._id} key={item?._id}>{item?.heading}</option> | |
| 272 | + )) | |
| 273 | + } | |
| 274 | + </Select> | |
| 275 | + <Text mb='10px' color={'red'}> {errors.belongsTo && touched.belongsTo && errors.belongsTo}</Text> | |
| 276 | + </GridItem> | |
| 277 | + <GridItem colSpan={{ base: 12, sm: 6, md: 4 }}> | |
| 278 | + <Flex alignItems='center'> | |
| 279 | + <FormLabel ms='4px' fontSize='sm' fontWeight='500' mb={0}> | |
| 280 | + Fixed | |
| 281 | + </FormLabel> | |
| 282 | + <Checkbox colorScheme="brandScheme" me="10px" onChange={(e) => setFieldValue(`fixed`, e.target.checked)} /> | |
| 283 | + </Flex> | |
| 284 | + </GridItem> | |
| 285 | + {(values?.type === 'radio' || values?.type === 'select') ? <> | |
| 286 | + <GridItem colSpan={{ base: 12, sm: 12, md: 12 }} borderBottom={'1px solid rgba(128, 128, 128, 0.705)'}> | |
| 287 | + <Flex justifyContent={'space-between'} alignItems={'center'} mb={'8px'} > | |
| 288 | + <Heading as="h1" size="md" > | |
| 289 | + Option | |
| 290 | + </Heading> | |
| 291 | + <Button size="sm" variant="brand" onClick={() => handleAddOption()}>Add</Button> | |
| 292 | + </Flex> | |
| 293 | + </GridItem> | |
| 294 | + { | |
| 295 | + values?.options?.map((option, index) => ( | |
| 296 | + <> | |
| 297 | + <GridItem colSpan={{ base: 12, sm: 6, md: 5 }}> | |
| 298 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='8px'> | |
| 299 | + Name | |
| 300 | + </FormLabel> | |
| 301 | + <Input | |
| 302 | + fontSize='sm' | |
| 303 | + onChange={handleChange} onBlur={handleBlur} | |
| 304 | + value={values.options[index]?.name} | |
| 305 | + name={`options[${index}].name`} | |
| 306 | + placeholder='Enter Name' | |
| 307 | + fontWeight='500' | |
| 308 | + borderColor={errors?.options && touched?.options && errors?.options[index]?.name && touched?.options[index]?.name ? "red.300" : null} | |
| 309 | + /> | |
| 310 | + <Text mb='10px' color={'red'}> {errors?.options && touched?.options && touched?.options[index]?.name && errors?.options[index]?.name}</Text> | |
| 311 | + | |
| 312 | + </GridItem> | |
| 313 | + <GridItem colSpan={{ base: 12, sm: 6, md: 5 }}> | |
| 314 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='8px'> | |
| 315 | + Value | |
| 316 | + </FormLabel> | |
| 317 | + <Input | |
| 318 | + fontSize='sm' | |
| 319 | + onChange={handleChange} onBlur={handleBlur} | |
| 320 | + value={values.options[index]?.value} | |
| 321 | + name={`options[${index}].value`} | |
| 322 | + placeholder='Enter Value' | |
| 323 | + fontWeight='500' | |
| 324 | + borderColor={errors?.options && touched?.options && errors?.options[index]?.value && touched?.options[index]?.value ? "red.300" : null} | |
| 325 | + /> | |
| 326 | + <Text mb='10px' color={'red'}> {errors?.options && touched?.options && touched?.options[index]?.value && errors?.options[index]?.value}</Text> | |
| 327 | + </GridItem> | |
| 328 | + {values?.options?.length > 2 ? <GridItem colSpan={{ base: 12, sm: 6, md: 2 }}> | |
| 329 | + <DeleteIcon onClick={() => handleRemoveOption(index)} color={'red'} cursor={'pointer'} mt={'2.5rem'} ms={'60px'} /> | |
| 330 | + </GridItem> : <GridItem colSpan={{ base: 12, sm: 6, md: 2 }}> | |
| 331 | + <DeleteIcon color={'gray'} mt={'2.5rem'} ms={'60px'} /> | |
| 332 | + </GridItem>} | |
| 333 | + </> | |
| 334 | + )) | |
| 335 | + } | |
| 336 | + | |
| 337 | + </> : ''} | |
| 338 | + <GridItem colSpan={{ base: 12 }} borderBottom={'1px solid rgba(128, 128, 128, 0.705)'}> | |
| 339 | + <Flex alignItems="center" justifyContent={"space-between"} mb={'8px'}> | |
| 340 | + <Heading as="h1" size="md" > | |
| 341 | + Validation | |
| 342 | + </Heading> | |
| 343 | + <Flex> | |
| 344 | + {validations?.length > 0 && <Select | |
| 345 | + value={validationType} | |
| 346 | + name="validations" | |
| 347 | + onChange={(e) => { | |
| 348 | + setValidationType(e.target.value) | |
| 349 | + if (e.target.value) { | |
| 350 | + const validationData = validations?.filter(item => item._id === e.target.value) | |
| 351 | + const filterData = validationData?.length > 0 ? validationData[0]?.validations : values?.validation | |
| 352 | + setFieldValue('validation', filterData) | |
| 353 | + setFieldValue('validate', true) | |
| 354 | + } else { | |
| 355 | + setFieldValue('validate', false) | |
| 356 | + setFieldValue('validation', [ | |
| 357 | + { | |
| 358 | + require: false, | |
| 359 | + message: "", | |
| 360 | + }, | |
| 361 | + { | |
| 362 | + min: false, | |
| 363 | + value: "", | |
| 364 | + message: "", | |
| 365 | + }, | |
| 366 | + { | |
| 367 | + max: false, | |
| 368 | + value: "", | |
| 369 | + message: "", | |
| 370 | + }, | |
| 371 | + { | |
| 372 | + match: false, | |
| 373 | + value: "", | |
| 374 | + message: "", | |
| 375 | + }, | |
| 376 | + { | |
| 377 | + types: false, | |
| 378 | + formikType: '', | |
| 379 | + message: "", | |
| 380 | + },]) | |
| 381 | + } | |
| 382 | + | |
| 383 | + }} | |
| 384 | + me="10px" | |
| 385 | + fontWeight='500' | |
| 386 | + size='xs' | |
| 387 | + placeholder={'Select Validation'} | |
| 388 | + > | |
| 389 | + {validations?.map((item, index) => ( | |
| 390 | + <option key={index} value={item._id} >{item.name}</option> | |
| 391 | + ))} | |
| 392 | + </Select>} | |
| 393 | + <Checkbox colorScheme="brandScheme" me="10px" isChecked={(values?.type === 'range' || values.validate) ? true : false} onChange={(e) => setFieldValue(`validate`, e.target.checked)} /> | |
| 394 | + </Flex> | |
| 395 | + </Flex> | |
| 396 | + </GridItem> | |
| 397 | + {(values.validate === true || values?.type === 'range') ? | |
| 398 | + <> | |
| 399 | + | |
| 400 | + <GridItem colSpan={{ base: 12, sm: 6, md: 4 }} mt={8}> | |
| 401 | + <Flex> | |
| 402 | + <Checkbox colorScheme="brandScheme" me="10px" isChecked={values?.validation[0]?.require} | |
| 403 | + onChange={(e) => { | |
| 404 | + const isChecked = e.target.checked; | |
| 405 | + setFieldValue(`validation[${0}].require`, isChecked); | |
| 406 | + setFieldValue( | |
| 407 | + 'validation[0].message', | |
| 408 | + isChecked ? values?.validation[0]?.message : '' | |
| 409 | + ); | |
| 410 | + }} | |
| 411 | + /> | |
| 412 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb="0"> | |
| 413 | + Require | |
| 414 | + </FormLabel> | |
| 415 | + </Flex> | |
| 416 | + </GridItem> | |
| 417 | + <GridItem colSpan={{ base: 12, md: 8 }}> | |
| 418 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='8px'> | |
| 419 | + Message | |
| 420 | + </FormLabel> | |
| 421 | + <Input | |
| 422 | + disabled={values?.validation[0]?.require === true ? false : true} | |
| 423 | + fontSize='sm' | |
| 424 | + onChange={handleChange} onBlur={handleBlur} | |
| 425 | + value={values?.validation[0]?.require === true ? values?.validation[0]?.message : ''} | |
| 426 | + name={`validation[${0}].message`} | |
| 427 | + placeholder='Enter Require message' | |
| 428 | + fontWeight='500' | |
| 429 | + /> | |
| 430 | + </GridItem> | |
| 431 | + | |
| 432 | + {!(values?.type === 'radio' || values?.type === 'select' || values?.type === 'check') && | |
| 433 | + <> | |
| 434 | + <GridItem colSpan={{ base: 12, sm: 6, md: 4 }} mt={8}> | |
| 435 | + <Flex> | |
| 436 | + <Checkbox colorScheme="brandScheme" isChecked={(values?.validation[1]?.min || values?.type === 'range') ? true : false} name={`validation[${1}].min`} me="10px" onChange={(e) => { | |
| 437 | + const isChecked = e.target.checked; | |
| 438 | + setFieldValue(`validation[${1}].min`, isChecked); | |
| 439 | + setFieldValue( | |
| 440 | + 'validation[1].message', | |
| 441 | + (isChecked || values?.type === 'range') ? values?.validation[1]?.message : '' | |
| 442 | + ); | |
| 443 | + setFieldValue( | |
| 444 | + 'validation[1].value', | |
| 445 | + (isChecked || values?.type === 'range') ? values?.validation[1]?.value : '' | |
| 446 | + ); | |
| 447 | + }} /> | |
| 448 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb="0"> | |
| 449 | + Min | |
| 450 | + </FormLabel> | |
| 451 | + </Flex> | |
| 452 | + </GridItem> | |
| 453 | + <GridItem colSpan={{ base: 12, sm: 6, md: 4 }}> | |
| 454 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='8px'> | |
| 455 | + Value{(values.validation[1].min === true || values?.type === 'range') ? <Text color={"red"}>*</Text> : ''} | |
| 456 | + </FormLabel> | |
| 457 | + <Input | |
| 458 | + disabled={(values.validation[1].min === true || values?.type === 'range') ? false : true} | |
| 459 | + fontSize='sm' | |
| 460 | + type={values?.type === 'date' ? 'date' : 'number'} | |
| 461 | + onChange={handleChange} onBlur={handleBlur} | |
| 462 | + value={(values.validation[1].min === true || values?.type === 'range') ? values.validation[1].value : ''} | |
| 463 | + name={`validation[${1}].value`} | |
| 464 | + placeholder='Enter Min Value' | |
| 465 | + fontWeight='500' | |
| 466 | + borderColor={(values.validation[1].min === true || values?.type === 'range') && errors?.validation && touched?.validation && errors?.validation[1]?.value && touched?.validation[1]?.value ? "red.300" : null} | |
| 467 | + /> | |
| 468 | + {(values.validation[1].min === true || values?.type === 'range') && <Text mb='10px' color={'red'}> {errors?.validation && touched?.validation && touched?.validation[1]?.value && errors?.validation[1]?.value}</Text>} | |
| 469 | + </GridItem> | |
| 470 | + <GridItem colSpan={{ base: 12, sm: 6, md: 4 }}> | |
| 471 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='8px'> | |
| 472 | + Message | |
| 473 | + </FormLabel> | |
| 474 | + <Input | |
| 475 | + disabled={(values.validation[1].min === true || values?.type === 'range') ? false : true} | |
| 476 | + fontSize='sm' | |
| 477 | + onChange={handleChange} onBlur={handleBlur} | |
| 478 | + value={values.validation[1].min === true || values?.type === 'range' ? values.validation[1].message : ''} | |
| 479 | + name={`validation[${1}].message`} | |
| 480 | + placeholder='Enter Min message' | |
| 481 | + fontWeight='500' | |
| 482 | + /> | |
| 483 | + </GridItem> | |
| 484 | + <GridItem colSpan={{ base: 12, sm: 6, md: 4 }} mt={8}> | |
| 485 | + <Flex> | |
| 486 | + <Checkbox colorScheme="brandScheme" me="10px" isChecked={(values?.validation[2].max || values?.type === 'range') ? true : false} name={`validation[${2}].max`} onChange={(e) => { | |
| 487 | + const isChecked = e.target.checked; | |
| 488 | + setFieldValue(`validation[${2}].max`, isChecked); | |
| 489 | + setFieldValue( | |
| 490 | + 'validation[2].message', | |
| 491 | + (isChecked || values?.type === 'range') ? values?.validation[2]?.message : '' | |
| 492 | + ); | |
| 493 | + setFieldValue( | |
| 494 | + 'validation[2].value', | |
| 495 | + (isChecked || values?.type === 'range') ? values?.validation[2]?.value : '' | |
| 496 | + ); | |
| 497 | + }} /> | |
| 498 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb="0"> | |
| 499 | + Max | |
| 500 | + </FormLabel> | |
| 501 | + </Flex> | |
| 502 | + </GridItem> | |
| 503 | + <GridItem colSpan={{ base: 12, sm: 6, md: 4 }}> | |
| 504 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='8px'> | |
| 505 | + Value{(values.validation[2].max === true || values?.type === 'range') ? <Text color={"red"}>*</Text> : ''} | |
| 506 | + </FormLabel> | |
| 507 | + <Input | |
| 508 | + disabled={(values.validation[2].max === true || values?.type === 'range') ? false : true} | |
| 509 | + fontSize='sm' | |
| 510 | + type={values?.type === 'date' ? 'date' : 'number'} | |
| 511 | + onChange={handleChange} onBlur={handleBlur} | |
| 512 | + value={(values.validation[2].max === true || values?.type === 'range') ? values.validation[2].value : ''} | |
| 513 | + name={`validation[${2}].value`} | |
| 514 | + placeholder='Enter Max Value' | |
| 515 | + fontWeight='500' | |
| 516 | + borderColor={(values.validation[2].max === true || values?.type === 'range') && errors?.validation && touched?.validation && errors?.validation[2]?.value && touched?.validation[2]?.value ? "red.300" : null} | |
| 517 | + /> | |
| 518 | + {(values.validation[2].max === true || values?.type === 'range') && <Text mb='10px' color={'red'}> {errors?.validation && touched?.validation && touched?.validation[2]?.value && errors?.validation[2]?.value}</Text>} | |
| 519 | + </GridItem> | |
| 520 | + <GridItem colSpan={{ base: 12, sm: 6, md: 4 }}> | |
| 521 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='8px'> | |
| 522 | + Message | |
| 523 | + </FormLabel> | |
| 524 | + <Input | |
| 525 | + disabled={(values.validation[2].max === true || values?.type === 'range') ? false : true} | |
| 526 | + fontSize='sm' | |
| 527 | + onChange={handleChange} onBlur={handleBlur} | |
| 528 | + value={(values.validation[2].max === true || values?.type === 'range') ? values.validation[2].massage : ''} | |
| 529 | + name={`validation[${2}].message`} | |
| 530 | + placeholder='Enter Max Message' | |
| 531 | + fontWeight='500' | |
| 532 | + borderColor={errors.massage && touched.massage ? "red.300" : null} | |
| 533 | + /> | |
| 534 | + </GridItem> | |
| 535 | + | |
| 536 | + <GridItem colSpan={{ base: 12, sm: 6, md: 4 }} mt={8}> | |
| 537 | + <Flex> | |
| 538 | + <Checkbox colorScheme="brandScheme" me="10px" isChecked={values?.validation[3].match} name={`validation[${3}].match`} onChange={(e) => { | |
| 539 | + const isChecked = e.target.checked; | |
| 540 | + setFieldValue(`validation[${3}].match`, isChecked); | |
| 541 | + setFieldValue( | |
| 542 | + 'validation[3].message', | |
| 543 | + isChecked ? values?.validation[3]?.message : '' | |
| 544 | + ); | |
| 545 | + setFieldValue( | |
| 546 | + 'validation[3].value', | |
| 547 | + isChecked ? values?.validation[3]?.value : '' | |
| 548 | + ); | |
| 549 | + }} /> | |
| 550 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb="0"> | |
| 551 | + Match | |
| 552 | + </FormLabel> | |
| 553 | + </Flex> | |
| 554 | + </GridItem> | |
| 555 | + <GridItem colSpan={{ base: 12, sm: 6, md: 4 }}> | |
| 556 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='8px'> | |
| 557 | + Value{values.validation[3].match === true ? <Text color={"red"}>*</Text> : ''} | |
| 558 | + </FormLabel> | |
| 559 | + <Input | |
| 560 | + disabled={values.validation[3].match === true ? false : true} | |
| 561 | + fontSize='sm' | |
| 562 | + onChange={handleChange} onBlur={handleBlur} | |
| 563 | + value={values.validation[3].match === true ? values.validation[3].value : ''} | |
| 564 | + name={`validation[${3}].value`} | |
| 565 | + placeholder='Enter Match Value' | |
| 566 | + fontWeight='500' | |
| 567 | + borderColor={values.validation[3].match === true && errors?.validation && touched?.validation && errors?.validation[3]?.value && touched?.validation[3]?.value ? "red.300" : null} | |
| 568 | + /> | |
| 569 | + {values.validation[3].match === true && <Text mb='10px' color={'red'}> {errors?.validation && touched?.validation && touched?.validation[3]?.value && errors?.validation[3]?.value}</Text>} | |
| 570 | + </GridItem> | |
| 571 | + <GridItem colSpan={{ base: 12, sm: 6, md: 4 }}> | |
| 572 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='8px'> | |
| 573 | + Message{values.validation[3].match === true ? <Text color={"red"}>*</Text> : ''} | |
| 574 | + </FormLabel> | |
| 575 | + <Input | |
| 576 | + disabled={values.validation[3].match === true ? false : true} | |
| 577 | + fontSize='sm' | |
| 578 | + onChange={handleChange} onBlur={handleBlur} | |
| 579 | + value={values.validation[3].match === true ? values.validation[3].massage : ''} | |
| 580 | + name={`validation[${3}].message`} | |
| 581 | + placeholder='Enter Match Message' | |
| 582 | + fontWeight='500' | |
| 583 | + borderColor={values.validation[3].match === true && errors?.validation && touched?.validation && errors?.validation[3]?.message && touched?.validation[3]?.message ? "red.300" : null} | |
| 584 | + /> | |
| 585 | + {values.validation[3].match === true && <Text mb='10px' color={'red'}> {errors?.validation && touched?.validation && touched?.validation[3]?.message && errors?.validation[3]?.message}</Text>} | |
| 586 | + </GridItem> | |
| 587 | + | |
| 588 | + <GridItem colSpan={{ base: 12, sm: 6, md: 4 }} mt={8}> | |
| 589 | + <Flex> | |
| 590 | + <Checkbox colorScheme="brandScheme" disabled={values?.type === 'date' ? true : false} isChecked={(values?.validation[4]?.types === true || values?.validation[4].formikType) ? true : false} name={`validation[${4}].types`} me="10px" onChange={(e) => { | |
| 591 | + const isChecked = e.target.checked; | |
| 592 | + setFieldValue(`validation[${4}].types`, isChecked); | |
| 593 | + setFieldValue( | |
| 594 | + 'validation[4].formikType', | |
| 595 | + isChecked ? values?.validation[4]?.formikType : '' | |
| 596 | + ); | |
| 597 | + }} /> | |
| 598 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb="0"> | |
| 599 | + Formik Type | |
| 600 | + </FormLabel> | |
| 601 | + </Flex> | |
| 602 | + </GridItem> | |
| 603 | + | |
| 604 | + <GridItem colSpan={{ base: 12, sm: 6, md: 4 }}> | |
| 605 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='8px'> | |
| 606 | + FormikType{(values?.validation[4]?.types === true || values?.validation[4].formikType) ? <Text color={"red"}>*</Text> : ''} | |
| 607 | + </FormLabel> | |
| 608 | + <Select | |
| 609 | + disabled={(values?.validation[4]?.types === true || values?.validation[4].formikType) ? values?.type === 'date' ? true : false : true} | |
| 610 | + value={values?.validation[4]?.types === true || values?.validation[4].formikType ? values.validation[4].formikType : ''} | |
| 611 | + name={`validation[${4}].formikType`} | |
| 612 | + onChange={handleChange} | |
| 613 | + fontWeight='500' | |
| 614 | + placeholder={'Select Type'} | |
| 615 | + borderColor={(values?.validation[4]?.types === true || values?.validation[4].formikType) && errors?.validation && touched?.validation && errors?.validation[4]?.formikType && touched?.validation[4]?.formikType ? "red.300" : null} | |
| 616 | + > | |
| 617 | + <option value='string'>String </option> | |
| 618 | + <option value='email'>Email </option> | |
| 619 | + <option value='date'>Date </option> | |
| 620 | + <option value='number'>Number </option> | |
| 621 | + <option value='object'>Object </option> | |
| 622 | + <option value='array'>Array </option> | |
| 623 | + <option value='url'>Url </option> | |
| 624 | + <option value='boolean'>Boolean </option> | |
| 625 | + <option value='positive'>Positive </option> | |
| 626 | + <option value='negative'>Negative </option> | |
| 627 | + <option value='integer'>Integer </option> | |
| 628 | + </Select> | |
| 629 | + {(values?.validation[4]?.types === true || values?.validation[4].formikType) && <Text mb='10px' color={'red'}> {errors?.validation && touched?.validation && touched?.validation[4]?.formikType && errors?.validation[4]?.formikType}</Text>} | |
| 630 | + </GridItem> | |
| 631 | + <GridItem colSpan={{ base: 12, sm: 6, md: 4 }}> | |
| 632 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='8px'> | |
| 633 | + Message | |
| 634 | + </FormLabel> | |
| 635 | + <Input | |
| 636 | + disabled={(values?.validation[4]?.types === true || values?.validation[4].formikType) ? false : true} | |
| 637 | + fontSize='sm' | |
| 638 | + onChange={handleChange} onBlur={handleBlur} | |
| 639 | + value={(values?.validation[4]?.types === true || values?.validation[4].formikType) ? values.validation[3].massage : ''} | |
| 640 | + name={`validation[${4}].message`} | |
| 641 | + placeholder='Enter Formik Type Message' | |
| 642 | + fontWeight='500' | |
| 643 | + /> | |
| 644 | + </GridItem> | |
| 645 | + </> | |
| 646 | + } | |
| 647 | + </> | |
| 648 | + : | |
| 649 | + ""} | |
| 650 | + </Grid> | |
| 651 | + </> | |
| 652 | + </ModalBody> | |
| 653 | + <ModalFooter> | |
| 654 | + <Button colorScheme="brand" size="sm" mr={2} type='submit' disabled={isLoding ? true : false} onClick={handleSubmit} >{isLoding ? <Spinner /> : 'Save'}</Button> | |
| 655 | + <Button variant="outline" | |
| 656 | + colorScheme='red' size="sm" | |
| 657 | + sx={{ | |
| 658 | + textTransform: "capitalize", | |
| 659 | + }} onClick={() => { handleClose(); resetForm(); setValidationType('') }}>Close</Button> | |
| 660 | + </ModalFooter> | |
| 661 | + </ModalContent> | |
| 662 | + </Modal> | |
| 663 | + </div> | |
| 664 | + ) | |
| 665 | +} | |
| 666 | + | |
| 667 | +export default Addfield |
+705-0Client/src/views/admin/customField/editfield.js
| @@ -0,0 +1,705 @@ | ||
| 1 | +import { DeleteIcon } from '@chakra-ui/icons' | |
| 2 | +import { Button, Checkbox, Flex, FormLabel, Grid, GridItem, Heading, Input, Modal, ModalBody, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalOverlay, Select, Spinner, Text } from '@chakra-ui/react' | |
| 3 | +import { useFormik } from 'formik' | |
| 4 | +import React, { useEffect, useState } from 'react' | |
| 5 | +import { addFiledSchema } from 'schema' | |
| 6 | +import { putApi } from 'services/api' | |
| 7 | +import * as yup from 'yup' | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | +const EditField = (props) => { | |
| 12 | + | |
| 13 | + const { moduleId, filed, updateFiled, headingsData, validations } = props; | |
| 14 | + | |
| 15 | + const [isLoding, setIsLoding] = useState(false) | |
| 16 | + const [data, setData] = useState([]) | |
| 17 | + const [validationType, setValidationType] = useState('') | |
| 18 | + | |
| 19 | + const initialValues = { | |
| 20 | + label: updateFiled ? updateFiled?.label : '', | |
| 21 | + name: updateFiled ? updateFiled?.name : '', | |
| 22 | + type: updateFiled ? updateFiled?.type : '', | |
| 23 | + delete: updateFiled ? updateFiled?.delete : '', | |
| 24 | + fixed: updateFiled ? updateFiled?.fixed ? true : false : '', | |
| 25 | + belongsTo: updateFiled ? updateFiled?.belongsTo : null, | |
| 26 | + validate: updateFiled?.validation && (updateFiled?.validation[0]?.require || updateFiled?.validation[1]?.min || updateFiled?.validation[2]?.max || updateFiled?.validation[3]?.match || updateFiled?.validation[4]?.formikType) ? true : false, | |
| 27 | + options: updateFiled?.options ? updateFiled?.options : '', | |
| 28 | + validation: [ | |
| 29 | + { | |
| 30 | + require: updateFiled?.validation ? updateFiled?.validation[0]?.require : '', | |
| 31 | + message: updateFiled?.validation ? updateFiled?.validation[0]?.message : '', | |
| 32 | + }, | |
| 33 | + { | |
| 34 | + min: updateFiled?.validation ? updateFiled?.validation[1]?.min : '', | |
| 35 | + value: updateFiled?.validation ? updateFiled?.validation[1]?.value : '', | |
| 36 | + message: updateFiled?.validation ? updateFiled?.validation[1]?.message : '', | |
| 37 | + }, | |
| 38 | + { | |
| 39 | + max: updateFiled?.validation ? updateFiled?.validation[2]?.max : '', | |
| 40 | + value: updateFiled?.validation ? updateFiled?.validation[2]?.value : '', | |
| 41 | + message: updateFiled?.validation ? updateFiled?.validation[2]?.message : '', | |
| 42 | + }, | |
| 43 | + { | |
| 44 | + match: updateFiled?.validation ? updateFiled?.validation[3]?.match : '', | |
| 45 | + value: updateFiled?.validation ? updateFiled?.validation[3]?.value : '', | |
| 46 | + message: updateFiled?.validation ? updateFiled?.validation[3]?.message : '', | |
| 47 | + }, | |
| 48 | + { | |
| 49 | + types: updateFiled?.validation ? updateFiled?.validation[4]?.formikType ? true : false : '', | |
| 50 | + formikType: updateFiled?.validation ? updateFiled?.validation[4]?.formikType : '', | |
| 51 | + message: updateFiled?.validation ? updateFiled?.validation[4]?.message : '', | |
| 52 | + }, | |
| 53 | + ], | |
| 54 | + }; | |
| 55 | + | |
| 56 | + const handleClose = () => { | |
| 57 | + props.onClose(false); | |
| 58 | + } | |
| 59 | + | |
| 60 | + const handleAddOption = () => { | |
| 61 | + formik.setFieldValue('options', [...formik.values.options, { name: '', value: '' }]); | |
| 62 | + }; | |
| 63 | + | |
| 64 | + const handleRemoveOption = (index) => { | |
| 65 | + const newOptions = [...formik.values.options]; | |
| 66 | + newOptions.splice(index, 1); | |
| 67 | + formik.setFieldValue('options', newOptions); | |
| 68 | + }; | |
| 69 | + | |
| 70 | + const underHeadingSchema = yup.object({ | |
| 71 | + belongsTo: yup.string().required("Belongs to field is required") | |
| 72 | + }); | |
| 73 | + | |
| 74 | + const formik = useFormik({ | |
| 75 | + initialValues: initialValues, | |
| 76 | + validationSchema: addFiledSchema, | |
| 77 | + // validationSchema: headingsData?.length > 0 ? addFiledSchema.concat(underHeadingSchema) : addFiledSchema, | |
| 78 | + enableReinitialize: true, | |
| 79 | + validate: (values) => { | |
| 80 | + const errors = {}; | |
| 81 | + | |
| 82 | + if (values?.validation && (values.validation[1]?.min || values?.type === 'range') && values.validation[1]?.value === '') { | |
| 83 | + errors.validation = errors.validation || []; | |
| 84 | + errors.validation[1] = errors.validation[1] || {}; | |
| 85 | + errors.validation[1].value = 'Value is required'; | |
| 86 | + } | |
| 87 | + if (values?.validation && (values.validation[2]?.max || values?.type === 'range') && values.validation[2]?.value === '') { | |
| 88 | + errors.validation = errors.validation || []; | |
| 89 | + errors.validation[2] = errors.validation[2] || {}; | |
| 90 | + errors.validation[2].value = 'Value is required'; | |
| 91 | + } | |
| 92 | + if (values?.validation && values.validation[3]?.match && !values.validation[3]?.value) { | |
| 93 | + errors.validation = errors.validation || []; | |
| 94 | + errors.validation[3] = errors.validation[3] || {}; | |
| 95 | + errors.validation[3].value = 'Value is required'; | |
| 96 | + } | |
| 97 | + if (values?.validation && values.validation[3]?.match && !values.validation[3]?.message) { | |
| 98 | + errors.validation = errors.validation || []; | |
| 99 | + errors.validation[3] = errors.validation[3] || {}; | |
| 100 | + errors.validation[3].message = 'Meassage is required'; | |
| 101 | + } | |
| 102 | + if (values?.validation && values.validation[4]?.types && !values.validation[4]?.formikType) { | |
| 103 | + errors.validation = errors.validation || []; | |
| 104 | + errors.validation[4] = errors.validation[4] || {}; | |
| 105 | + errors.validation[4].formikType = 'FormikType is required'; | |
| 106 | + } | |
| 107 | + | |
| 108 | + if (values?.options && (values?.type === 'radio' || values?.type === 'select')) { | |
| 109 | + values?.options?.forEach((option, index) => { | |
| 110 | + if (!option.name) { | |
| 111 | + if (!errors.options) { | |
| 112 | + errors.options = []; | |
| 113 | + } | |
| 114 | + if (!errors.options[index]) { | |
| 115 | + errors.options[index] = {}; | |
| 116 | + } | |
| 117 | + errors.options[index].name = 'Name is required'; | |
| 118 | + } | |
| 119 | + if (!option.value) { | |
| 120 | + if (!errors.options) { | |
| 121 | + errors.options = []; | |
| 122 | + } | |
| 123 | + if (!errors.options[index]) { | |
| 124 | + errors.options[index] = {}; | |
| 125 | + } | |
| 126 | + errors.options[index].value = 'Value is required'; | |
| 127 | + } | |
| 128 | + }); | |
| 129 | + } | |
| 130 | + return errors; | |
| 131 | + }, | |
| 132 | + onSubmit: (values, { resetForm }) => { | |
| 133 | + EditData() | |
| 134 | + }, | |
| 135 | + }); | |
| 136 | + | |
| 137 | + const { errors, touched, values, handleBlur, handleChange, handleSubmit, setFieldValue, resetForm } = formik | |
| 138 | + const EditData = async () => { | |
| 139 | + try { | |
| 140 | + const editDataPayload = { | |
| 141 | + moduleId: moduleId, | |
| 142 | + updatedField: values | |
| 143 | + } | |
| 144 | + | |
| 145 | + let response = await putApi(`api/custom-field/change-single-field/${updateFiled?._id}`, editDataPayload); | |
| 146 | + if (response.status === 200) { | |
| 147 | + props.onClose() | |
| 148 | + props.fetchData() | |
| 149 | + } | |
| 150 | + } | |
| 151 | + catch { | |
| 152 | + } | |
| 153 | + finally { | |
| 154 | + } | |
| 155 | + } | |
| 156 | + | |
| 157 | + return ( | |
| 158 | + <div> | |
| 159 | + <Modal onClose={props.onClose} isOpen={props.isOpen} isCentered> | |
| 160 | + <ModalOverlay /> | |
| 161 | + <ModalContent maxWidth={"2xl"} height={"580px"}> | |
| 162 | + <ModalHeader>Edit Field</ModalHeader> | |
| 163 | + <ModalCloseButton /> | |
| 164 | + <ModalBody overflowY={"auto"} height={"400px"}> | |
| 165 | + <> | |
| 166 | + <Grid templateColumns="repeat(12, 1fr)" gap={3}> | |
| 167 | + <GridItem colSpan={{ base: 12, sm: 6, md: 4 }}> | |
| 168 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='8px'> | |
| 169 | + Label<Text color={"red"}>*</Text> | |
| 170 | + </FormLabel> | |
| 171 | + <Input | |
| 172 | + fontSize='sm' | |
| 173 | + onChange={handleChange} onBlur={handleBlur} | |
| 174 | + value={values.label} | |
| 175 | + name="label" | |
| 176 | + placeholder={`Enter Label`} | |
| 177 | + fontWeight='500' | |
| 178 | + borderColor={errors.label && touched.label ? "red.300" : null} | |
| 179 | + /> | |
| 180 | + <Text mb='10px' color={'red'}> {errors.label && touched.label && errors.label}</Text> | |
| 181 | + </GridItem> | |
| 182 | + <GridItem colSpan={{ base: 12, sm: 6, md: 4 }}> | |
| 183 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='8px'> | |
| 184 | + Name<Text color={"red"}>*</Text> | |
| 185 | + </FormLabel> | |
| 186 | + <Input | |
| 187 | + fontSize='sm' | |
| 188 | + onKeyDown={(e) => { | |
| 189 | + if (e.code === 'Space') { | |
| 190 | + e.preventDefault(); | |
| 191 | + } | |
| 192 | + }} | |
| 193 | + onChange={(e) => { | |
| 194 | + const newValue = e.target.value.replace(/\s/g, ''); | |
| 195 | + handleChange({ | |
| 196 | + target: { | |
| 197 | + name: 'name', | |
| 198 | + value: newValue, | |
| 199 | + }, | |
| 200 | + }); | |
| 201 | + }} | |
| 202 | + onBlur={handleBlur} | |
| 203 | + value={values.name} | |
| 204 | + name="name" | |
| 205 | + placeholder='Enter Name' | |
| 206 | + fontWeight='500' | |
| 207 | + borderColor={errors.name && touched.name ? "red.300" : null} | |
| 208 | + /> | |
| 209 | + <Text mb='10px' color={'red'}> {errors.name && touched.name && errors.name}</Text> | |
| 210 | + </GridItem> | |
| 211 | + <GridItem colSpan={{ base: 12, sm: 6, md: 4 }}> | |
| 212 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='8px'> | |
| 213 | + Type | |
| 214 | + </FormLabel> | |
| 215 | + <Select | |
| 216 | + value={values.type} | |
| 217 | + name="type" | |
| 218 | + onChange={handleChange} | |
| 219 | + fontWeight='500' | |
| 220 | + // placeholder={'Select Type'} | |
| 221 | + borderColor={errors.type && touched.type ? "red.300" : null} | |
| 222 | + > | |
| 223 | + <option value='text'>Text</option> | |
| 224 | + <option value='number'>Number</option> | |
| 225 | + <option value='radio'>Radio</option> | |
| 226 | + <option value='check'>Check</option> | |
| 227 | + <option value='date'>Date</option> | |
| 228 | + <option value='tel'>Tel</option> | |
| 229 | + <option value='range'>Range</option> | |
| 230 | + <option value='color'>Color</option> | |
| 231 | + <option value='email'>Email</option> | |
| 232 | + <option value='url'>Url</option> | |
| 233 | + <option value='select'>Select</option> | |
| 234 | + </Select> | |
| 235 | + <Text mb='10px' color={'red'}> {errors.type && touched.type && errors.type}</Text> | |
| 236 | + </GridItem> | |
| 237 | + <GridItem colSpan={{ base: 12, sm: 6, md: 4 }}> | |
| 238 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='8px'> | |
| 239 | + {/* Belongs To{headingsData?.length > 0 ? <Text color={"red"}>*</Text> : ''} */} | |
| 240 | + Belongs To | |
| 241 | + </FormLabel> | |
| 242 | + <Select | |
| 243 | + value={values.belongsTo} | |
| 244 | + name="belongsTo" | |
| 245 | + onChange={(e) => { | |
| 246 | + const selectedValue = e.target.value; | |
| 247 | + const newValue = selectedValue === "" ? null : selectedValue; | |
| 248 | + handleChange({ | |
| 249 | + target: { | |
| 250 | + name: "belongsTo", | |
| 251 | + value: newValue, | |
| 252 | + }, | |
| 253 | + }) | |
| 254 | + }} | |
| 255 | + onBlur={handleBlur} | |
| 256 | + fontWeight='500' | |
| 257 | + placeholder={'Select Heading'} | |
| 258 | + borderColor={errors.belongsTo && touched.belongsTo ? "red.300" : null} | |
| 259 | + isDisabled={headingsData?.length < 1} | |
| 260 | + > | |
| 261 | + { | |
| 262 | + headingsData?.map(item => ( | |
| 263 | + <option value={item?._id} key={item?._id}>{item?.heading}</option> | |
| 264 | + )) | |
| 265 | + } | |
| 266 | + </Select> | |
| 267 | + <Text mb='10px' color={'red'}> {errors.belongsTo && touched.belongsTo && errors.belongsTo}</Text> | |
| 268 | + </GridItem> | |
| 269 | + <GridItem colSpan={{ base: 12, sm: 6, md: 6 }}> | |
| 270 | + <Flex alignItems='center'> | |
| 271 | + | |
| 272 | + <FormLabel ms='4px' fontSize='sm' fontWeight='500' mb={0}> | |
| 273 | + Fixed | |
| 274 | + </FormLabel> | |
| 275 | + <Checkbox colorScheme="brandScheme" me="10px" isChecked={values?.fixed} onChange={(e) => setFieldValue(`fixed`, e.target.checked)} /> | |
| 276 | + </Flex> | |
| 277 | + </GridItem> | |
| 278 | + {/* <GridItem colSpan={{ base: 12, sm: 6, md: 4 }}> | |
| 279 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='8px'> | |
| 280 | + Validations | |
| 281 | + </FormLabel> | |
| 282 | + <Select | |
| 283 | + value={validationType} | |
| 284 | + name="validations" | |
| 285 | + // onChange={handleChange} | |
| 286 | + onChange={(e) => { | |
| 287 | + setValidationType(e.target.value) | |
| 288 | + if (e.target.value) { | |
| 289 | + const validationData = validations?.filter(item => item._id === e.target.value) | |
| 290 | + const filterData = validationData?.length > 0 ? validationData[0]?.validations : values?.validation | |
| 291 | + setFieldValue('validation', filterData) | |
| 292 | + } else { | |
| 293 | + setFieldValue('validation', [ | |
| 294 | + { | |
| 295 | + require: updateFiled?.validation ? updateFiled?.validation[0]?.require : '', | |
| 296 | + message: updateFiled?.validation ? updateFiled?.validation[0]?.message : '', | |
| 297 | + }, | |
| 298 | + { | |
| 299 | + min: updateFiled?.validation ? updateFiled?.validation[1]?.min : '', | |
| 300 | + value: updateFiled?.validation ? updateFiled?.validation[1]?.value : '', | |
| 301 | + message: updateFiled?.validation ? updateFiled?.validation[1]?.message : '', | |
| 302 | + }, | |
| 303 | + { | |
| 304 | + max: updateFiled?.validation ? updateFiled?.validation[2]?.max : '', | |
| 305 | + value: updateFiled?.validation ? updateFiled?.validation[2]?.value : '', | |
| 306 | + message: updateFiled?.validation ? updateFiled?.validation[2]?.message : '', | |
| 307 | + }, | |
| 308 | + { | |
| 309 | + match: updateFiled?.validation ? updateFiled?.validation[3]?.match : '', | |
| 310 | + value: updateFiled?.validation ? updateFiled?.validation[3]?.value : '', | |
| 311 | + message: updateFiled?.validation ? updateFiled?.validation[3]?.message : '', | |
| 312 | + }, | |
| 313 | + { | |
| 314 | + types: updateFiled?.validation ? updateFiled?.validation[4]?.formikType ? true : false : '', | |
| 315 | + formikType: updateFiled?.validation ? updateFiled?.validation[4]?.formikType : '', | |
| 316 | + message: updateFiled?.validation ? updateFiled?.validation[4]?.message : '', | |
| 317 | + }]) | |
| 318 | + } | |
| 319 | + | |
| 320 | + }} | |
| 321 | + fontWeight='500' | |
| 322 | + placeholder={'Select Validation'} | |
| 323 | + > | |
| 324 | + {validations?.map((item, index) => ( | |
| 325 | + <option key={index} value={item._id}>{item.name}</option> | |
| 326 | + ))} | |
| 327 | + </Select> | |
| 328 | + </GridItem> */} | |
| 329 | + {(values?.type === 'radio' || values?.type === 'select') ? <> | |
| 330 | + <GridItem colSpan={{ base: 12, sm: 12, md: 12 }} borderBottom={'1px solid rgba(128, 128, 128, 0.705)'}> | |
| 331 | + <Flex justifyContent={'space-between'} alignItems={'center'} mb={'8px'} > | |
| 332 | + <Heading as="h1" size="md" > | |
| 333 | + Option | |
| 334 | + </Heading> | |
| 335 | + <Button size="sm" variant="brand" onClick={() => handleAddOption()}>Add</Button> | |
| 336 | + </Flex> | |
| 337 | + </GridItem> | |
| 338 | + { | |
| 339 | + values?.options && values?.options?.map((option, index) => ( | |
| 340 | + <> | |
| 341 | + <GridItem colSpan={{ base: 12, sm: 6, md: 5 }}> | |
| 342 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='8px'> | |
| 343 | + Name | |
| 344 | + </FormLabel> | |
| 345 | + <Input | |
| 346 | + fontSize='sm' | |
| 347 | + onChange={handleChange} onBlur={handleBlur} | |
| 348 | + value={values.options[index]?.name} | |
| 349 | + name={`options[${index}].name`} | |
| 350 | + placeholder='Enter Name' | |
| 351 | + fontWeight='500' | |
| 352 | + borderColor={errors?.options && touched?.options && errors?.options[index]?.name && touched?.options[index]?.name ? "red.300" : null} | |
| 353 | + /> | |
| 354 | + <Text mb='10px' color={'red'}> {errors?.options && touched?.options && touched?.options[index]?.name && errors?.options[index]?.name}</Text> | |
| 355 | + </GridItem> | |
| 356 | + <GridItem colSpan={{ base: 12, sm: 6, md: 5 }}> | |
| 357 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='8px'> | |
| 358 | + Value | |
| 359 | + </FormLabel> | |
| 360 | + <Input | |
| 361 | + fontSize='sm' | |
| 362 | + onChange={handleChange} onBlur={handleBlur} | |
| 363 | + value={values.options[index]?.value} | |
| 364 | + name={`options[${index}].value`} | |
| 365 | + placeholder='Enter Value' | |
| 366 | + fontWeight='500' | |
| 367 | + borderColor={errors?.options && touched?.options && errors?.options[index]?.value && touched?.options[index]?.value ? "red.300" : null} | |
| 368 | + /> | |
| 369 | + <Text mb='10px' color={'red'}> {errors?.options && touched?.options && touched?.options[index]?.value && errors?.options[index]?.value}</Text> | |
| 370 | + </GridItem> | |
| 371 | + {values?.options?.length > 2 ? <GridItem colSpan={{ base: 12, sm: 6, md: 2 }}> | |
| 372 | + <DeleteIcon onClick={() => handleRemoveOption(index)} color={'red'} cursor={'pointer'} mt={'2.5rem'} ms={'60px'} /> | |
| 373 | + </GridItem> : <GridItem colSpan={{ base: 12, sm: 6, md: 2 }}> | |
| 374 | + <DeleteIcon color={'gray'} mt={'2.5rem'} ms={'60px'} /> | |
| 375 | + </GridItem>} | |
| 376 | + </> | |
| 377 | + )) | |
| 378 | + } | |
| 379 | + </> : ''} | |
| 380 | + <GridItem colSpan={{ base: 12 }} borderBottom={'1px solid rgba(128, 128, 128, 0.705)'}> | |
| 381 | + <Flex alignItems="center" justifyContent={"space-between"} mb={'8px'}> | |
| 382 | + <Heading as="h1" size="md" > | |
| 383 | + Validation | |
| 384 | + </Heading> | |
| 385 | + <Flex> | |
| 386 | + {validations?.length > 0 && <Select | |
| 387 | + value={validationType} | |
| 388 | + name="validations" | |
| 389 | + // onChange={handleChange} | |
| 390 | + onChange={(e) => { | |
| 391 | + setValidationType(e.target.value) | |
| 392 | + if (e.target.value) { | |
| 393 | + const validationData = validations?.filter(item => item._id === e.target.value) | |
| 394 | + const filterData = validationData?.length > 0 ? validationData[0]?.validations : values?.validation | |
| 395 | + setFieldValue('validation', filterData) | |
| 396 | + setFieldValue('validate', true) | |
| 397 | + } else { | |
| 398 | + setFieldValue('validation', [ | |
| 399 | + { | |
| 400 | + require: updateFiled?.validation ? updateFiled?.validation[0]?.require : '', | |
| 401 | + message: updateFiled?.validation ? updateFiled?.validation[0]?.message : '', | |
| 402 | + }, | |
| 403 | + { | |
| 404 | + min: updateFiled?.validation ? updateFiled?.validation[1]?.min : '', | |
| 405 | + value: updateFiled?.validation ? updateFiled?.validation[1]?.value : '', | |
| 406 | + message: updateFiled?.validation ? updateFiled?.validation[1]?.message : '', | |
| 407 | + }, | |
| 408 | + { | |
| 409 | + max: updateFiled?.validation ? updateFiled?.validation[2]?.max : '', | |
| 410 | + value: updateFiled?.validation ? updateFiled?.validation[2]?.value : '', | |
| 411 | + message: updateFiled?.validation ? updateFiled?.validation[2]?.message : '', | |
| 412 | + }, | |
| 413 | + { | |
| 414 | + match: updateFiled?.validation ? updateFiled?.validation[3]?.match : '', | |
| 415 | + value: updateFiled?.validation ? updateFiled?.validation[3]?.value : '', | |
| 416 | + message: updateFiled?.validation ? updateFiled?.validation[3]?.message : '', | |
| 417 | + }, | |
| 418 | + { | |
| 419 | + types: updateFiled?.validation ? updateFiled?.validation[4]?.formikType ? true : false : '', | |
| 420 | + formikType: updateFiled?.validation ? updateFiled?.validation[4]?.formikType : '', | |
| 421 | + message: updateFiled?.validation ? updateFiled?.validation[4]?.message : '', | |
| 422 | + }]) | |
| 423 | + } | |
| 424 | + | |
| 425 | + }} | |
| 426 | + fontWeight='500' | |
| 427 | + me="10px" | |
| 428 | + size='xs' | |
| 429 | + placeholder={'Select Validation'} | |
| 430 | + > | |
| 431 | + {validations?.map((item, index) => ( | |
| 432 | + <option key={index} value={item._id}>{item.name}</option> | |
| 433 | + ))} | |
| 434 | + </Select>} | |
| 435 | + <Checkbox isChecked={(values?.type === 'range' || values.validate) ? true : false} colorScheme="brandScheme" me="10px" onChange={(e) => { setFieldValue(`validate`, e.target.checked) }} /> | |
| 436 | + </Flex> | |
| 437 | + </Flex> | |
| 438 | + </GridItem> | |
| 439 | + {(values.validate === true || values?.type === 'range') ? | |
| 440 | + <> | |
| 441 | + <GridItem colSpan={{ base: 12, sm: 6, md: 4 }} mt={8}> | |
| 442 | + <Flex> | |
| 443 | + <Checkbox colorScheme="brandScheme" me="10px" isChecked={values?.validation[0]?.require} | |
| 444 | + onChange={(e) => { | |
| 445 | + const isChecked = e.target.checked; | |
| 446 | + setFieldValue(`validation[${0}].require`, isChecked); | |
| 447 | + setFieldValue( | |
| 448 | + 'validation[0].message', | |
| 449 | + isChecked ? values?.validation[0]?.message : '' | |
| 450 | + ); | |
| 451 | + }} | |
| 452 | + /> | |
| 453 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb="0"> | |
| 454 | + Require | |
| 455 | + </FormLabel> | |
| 456 | + </Flex> | |
| 457 | + </GridItem> | |
| 458 | + <GridItem colSpan={{ base: 12, md: 8 }}> | |
| 459 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='8px'> | |
| 460 | + Message | |
| 461 | + </FormLabel> | |
| 462 | + <Input | |
| 463 | + disabled={values?.validation[0]?.require === true ? false : true} | |
| 464 | + fontSize='sm' | |
| 465 | + onChange={handleChange} onBlur={handleBlur} | |
| 466 | + value={values.validation[0].require === true ? values.validation[0].message : ''} | |
| 467 | + name={`validation[${0}].message`} | |
| 468 | + placeholder='Enter Require message' | |
| 469 | + fontWeight='500' | |
| 470 | + /> | |
| 471 | + </GridItem> | |
| 472 | + {!(values?.type === 'radio' || values?.type === 'select' || values?.type === 'check') && <> | |
| 473 | + <GridItem colSpan={{ base: 12, sm: 6, md: 4 }} mt={8}> | |
| 474 | + <Flex> | |
| 475 | + <Checkbox colorScheme="brandScheme" name={`validation[${1}].min`} me="10px" isChecked={(values?.validation[1]?.min || values?.type === 'range') ? true : false} onChange={(e) => { | |
| 476 | + const isChecked = e.target.checked; | |
| 477 | + setFieldValue(`validation[${1}].min`, isChecked); | |
| 478 | + setFieldValue( | |
| 479 | + 'validation[1].message', | |
| 480 | + (isChecked || values?.type === 'range') ? values?.validation[1]?.message : '' | |
| 481 | + ); | |
| 482 | + setFieldValue( | |
| 483 | + 'validation[1].value', | |
| 484 | + (isChecked || values?.type === 'range') ? values?.validation[1]?.value : '' | |
| 485 | + ); | |
| 486 | + }} /> | |
| 487 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb="0"> | |
| 488 | + Min | |
| 489 | + </FormLabel> | |
| 490 | + </Flex> | |
| 491 | + </GridItem> | |
| 492 | + <GridItem colSpan={{ base: 12, sm: 6, md: 4 }}> | |
| 493 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='8px'> | |
| 494 | + Value{(values.validation[1].min === true || values?.type === 'range') ? <Text color={"red"}>*</Text> : ''} | |
| 495 | + </FormLabel> | |
| 496 | + <Input | |
| 497 | + disabled={(values.validation[1].min === true || values?.type === 'range') ? false : true} | |
| 498 | + fontSize='sm' | |
| 499 | + type={values?.type === 'date' ? 'date' : 'number'} | |
| 500 | + onChange={handleChange} onBlur={handleBlur} | |
| 501 | + value={(values.validation[1].min === true || values?.type === 'range') ? values.validation[1].value : ''} | |
| 502 | + name={`validation[${1}].value`} | |
| 503 | + placeholder='Enter Min Value' | |
| 504 | + fontWeight='500' | |
| 505 | + borderColor={(values.validation[1].min === true || values?.type === 'range') && errors?.validation && touched?.validation && errors?.validation[1]?.value && touched?.validation[1]?.value ? "red.300" : null} | |
| 506 | + /> | |
| 507 | + {(values.validation[1].min === true || values?.type === 'range') && <Text mb='10px' color={'red'}> {errors?.validation && touched?.validation && touched?.validation[1]?.value && errors?.validation[1]?.value}</Text>} | |
| 508 | + </GridItem> | |
| 509 | + <GridItem colSpan={{ base: 12, sm: 6, md: 4 }}> | |
| 510 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='8px'> | |
| 511 | + Message | |
| 512 | + </FormLabel> | |
| 513 | + <Input | |
| 514 | + disabled={(values.validation[1].min === true || values?.type === 'range') ? false : true} | |
| 515 | + fontSize='sm' | |
| 516 | + onChange={handleChange} onBlur={handleBlur} | |
| 517 | + value={(values.validation[1].min === true || values?.type === 'range') ? values.validation[1].message : ''} | |
| 518 | + name={`validation[${1}].message`} | |
| 519 | + placeholder='Enter Min message' | |
| 520 | + fontWeight='500' | |
| 521 | + /> | |
| 522 | + </GridItem> | |
| 523 | + <GridItem colSpan={{ base: 12, sm: 6, md: 4 }} mt={8}> | |
| 524 | + <Flex> | |
| 525 | + <Checkbox colorScheme="brandScheme" me="10px" isChecked={(values?.validation[2].max || values?.type === 'range') ? true : false} name={`validation[${2}].max`} onChange={(e) => { | |
| 526 | + const isChecked = e.target.checked; | |
| 527 | + setFieldValue(`validation[${2}].max`, isChecked); | |
| 528 | + setFieldValue( | |
| 529 | + 'validation[2].message', | |
| 530 | + (isChecked || values?.type === 'range') ? values?.validation[2]?.message : '' | |
| 531 | + ); | |
| 532 | + setFieldValue( | |
| 533 | + 'validation[2].value', | |
| 534 | + (isChecked || values?.type === 'range') ? values?.validation[2]?.value : '' | |
| 535 | + ); | |
| 536 | + }} /> | |
| 537 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb="0"> | |
| 538 | + Max | |
| 539 | + </FormLabel> | |
| 540 | + </Flex> | |
| 541 | + </GridItem> | |
| 542 | + <GridItem colSpan={{ base: 12, sm: 6, md: 4 }}> | |
| 543 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='8px'> | |
| 544 | + Value{(values.validation[2].max === true || values?.type === 'range') ? <Text color={"red"}>*</Text> : ''} | |
| 545 | + </FormLabel> | |
| 546 | + <Input | |
| 547 | + disabled={(values.validation[2].max === true || values?.type === 'range') ? false : true} | |
| 548 | + fontSize='sm' | |
| 549 | + type={values?.type === 'date' ? 'date' : 'number'} | |
| 550 | + onChange={handleChange} onBlur={handleBlur} | |
| 551 | + value={(values.validation[2].max === true || values?.type === 'range') ? values.validation[2].value : ''} | |
| 552 | + name={`validation[${2}].value`} | |
| 553 | + placeholder='Enter Max Value' | |
| 554 | + fontWeight='500' | |
| 555 | + borderColor={(values.validation[2].max === true || values?.type === 'range') && errors?.validation && touched?.validation && errors?.validation[2]?.value && touched?.validation[2]?.value ? "red.300" : null} | |
| 556 | + /> | |
| 557 | + {(values.validation[2].max === true || values?.type === 'range') && <Text mb='10px' color={'red'}> {errors?.validation && touched?.validation && touched?.validation[2]?.value && errors?.validation[2]?.value}</Text>} | |
| 558 | + </GridItem> | |
| 559 | + <GridItem colSpan={{ base: 12, sm: 6, md: 4 }}> | |
| 560 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='8px'> | |
| 561 | + Message | |
| 562 | + </FormLabel> | |
| 563 | + <Input | |
| 564 | + disabled={(values.validation[2].max === true || values?.type === 'range') ? false : true} | |
| 565 | + fontSize='sm' | |
| 566 | + onChange={handleChange} onBlur={handleBlur} | |
| 567 | + value={(values.validation[2].max === true || values?.type === 'range') ? values.validation[2].message : ''} | |
| 568 | + name={`validation[${2}].message`} | |
| 569 | + placeholder='Enter Max Message' | |
| 570 | + fontWeight='500' | |
| 571 | + borderColor={errors.massage && touched.massage ? "red.300" : null} | |
| 572 | + /> | |
| 573 | + </GridItem> | |
| 574 | + <GridItem colSpan={{ base: 12, sm: 6, md: 4 }} mt={8}> | |
| 575 | + <Flex> | |
| 576 | + <Checkbox colorScheme="brandScheme" isChecked={values?.validation[3]?.match} me="10px" name={`validation[${3}].match`} onChange={(e) => { | |
| 577 | + const isChecked = e.target.checked; | |
| 578 | + setFieldValue(`validation[${3}].match`, isChecked); | |
| 579 | + setFieldValue( | |
| 580 | + 'validation[3].message', | |
| 581 | + isChecked ? values?.validation[3]?.message : '' | |
| 582 | + ); | |
| 583 | + setFieldValue( | |
| 584 | + 'validation[3].value', | |
| 585 | + isChecked ? values?.validation[3]?.value : '' | |
| 586 | + ); | |
| 587 | + }} /> | |
| 588 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb="0"> | |
| 589 | + Match | |
| 590 | + </FormLabel> | |
| 591 | + </Flex> | |
| 592 | + </GridItem> | |
| 593 | + <GridItem colSpan={{ base: 12, sm: 6, md: 4 }}> | |
| 594 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='8px'> | |
| 595 | + Value{values.validation[3].match === true ? <Text color={"red"}>*</Text> : ''} | |
| 596 | + </FormLabel> | |
| 597 | + <Input | |
| 598 | + disabled={values.validation[3].match === true ? false : true} | |
| 599 | + fontSize='sm' | |
| 600 | + onChange={handleChange} onBlur={handleBlur} | |
| 601 | + value={values.validation[3].match === true ? values.validation[3].value : ''} | |
| 602 | + name={`validation[${3}].value`} | |
| 603 | + placeholder='Enter Match Value' | |
| 604 | + fontWeight='500' | |
| 605 | + borderColor={values.validation[3].match === true && errors?.validation && touched?.validation && errors?.validation[3]?.value && touched?.validation[3]?.value ? "red.300" : null} | |
| 606 | + /> | |
| 607 | + {values.validation[3].match === true && <Text mb='10px' color={'red'}> {errors?.validation && touched?.validation && touched?.validation[3]?.value && errors?.validation[3]?.value}</Text>} | |
| 608 | + </GridItem> | |
| 609 | + <GridItem colSpan={{ base: 12, sm: 6, md: 4 }}> | |
| 610 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='8px'> | |
| 611 | + Message{values.validation[3].match === true ? <Text color={"red"}>*</Text> : ''} | |
| 612 | + </FormLabel> | |
| 613 | + <Input | |
| 614 | + disabled={values.validation[3].match === true ? false : true} | |
| 615 | + fontSize='sm' | |
| 616 | + onChange={handleChange} onBlur={handleBlur} | |
| 617 | + value={values.validation[3].match === true ? values.validation[3].message : ''} | |
| 618 | + name={`validation[${3}].message`} | |
| 619 | + placeholder='Enter Match Message' | |
| 620 | + fontWeight='500' | |
| 621 | + borderColor={values.validation[3].match === true && errors?.validation && touched?.validation && errors?.validation[3]?.message && touched?.validation[3]?.message ? "red.300" : null} | |
| 622 | + /> | |
| 623 | + {values.validation[3].match === true && <Text mb='10px' color={'red'}> {errors?.validation && touched?.validation && touched?.validation[3]?.message && errors?.validation[3]?.message}</Text>} | |
| 624 | + </GridItem> | |
| 625 | + <GridItem colSpan={{ base: 12, sm: 6, md: 4 }} mt={8}> | |
| 626 | + <Flex> | |
| 627 | + <Checkbox colorScheme="brandScheme" disabled={values?.type === 'date' ? true : false} isChecked={values?.validation[4]?.types} name={`validation[${4}].types`} me="10px" onChange={(e) => { | |
| 628 | + const isChecked = e.target.checked; | |
| 629 | + setFieldValue(`validation[${4}].types`, isChecked); | |
| 630 | + setFieldValue( | |
| 631 | + 'validation[4].formikType', | |
| 632 | + isChecked ? values?.validation[4]?.formikType : '' | |
| 633 | + ); | |
| 634 | + setFieldValue( | |
| 635 | + 'validation[4].message', | |
| 636 | + isChecked ? values?.validation[4]?.message : '' | |
| 637 | + ); | |
| 638 | + }} /> | |
| 639 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb="0"> | |
| 640 | + Formik Type | |
| 641 | + </FormLabel> | |
| 642 | + </Flex> | |
| 643 | + </GridItem> | |
| 644 | + <GridItem colSpan={{ base: 12, sm: 6, md: 4 }}> | |
| 645 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='8px'> | |
| 646 | + FormikType{values.validation[4].types === true ? <Text color={"red"}>*</Text> : ''} | |
| 647 | + </FormLabel> | |
| 648 | + <Select | |
| 649 | + disabled={(values?.validation[4]?.types === true || values?.validation[4].formikType) ? values?.type === 'date' ? true : false : true} | |
| 650 | + value={values.validation[4].types === true ? values.validation[4].formikType : ''} | |
| 651 | + name={`validation[${4}].formikType`} | |
| 652 | + onChange={handleChange} | |
| 653 | + fontWeight='500' | |
| 654 | + placeholder={'Select Type'} | |
| 655 | + borderColor={values.validation[4].types === true && errors?.validation && touched?.validation && errors?.validation[4]?.formikType && touched?.validation[4]?.formikType ? "red.300" : null} | |
| 656 | + > | |
| 657 | + <option value='string'>String </option> | |
| 658 | + <option value='email'>Email </option> | |
| 659 | + <option value='date'>Date </option> | |
| 660 | + <option value='number'>Number </option> | |
| 661 | + <option value='object'>Object </option> | |
| 662 | + <option value='array'>Array </option> | |
| 663 | + <option value='url'>Url </option> | |
| 664 | + <option value='boolean'>Boolean </option> | |
| 665 | + <option value='positive'>Positive </option> | |
| 666 | + <option value='negative'>Negative </option> | |
| 667 | + <option value='integer'>Integer </option> | |
| 668 | + </Select> | |
| 669 | + {values.validation[4].types === true && <Text mb='10px' color={'red'}> {errors?.validation && touched?.validation && touched?.validation[4]?.formikType && errors?.validation[4]?.formikType}</Text>} | |
| 670 | + </GridItem> | |
| 671 | + <GridItem colSpan={{ base: 12, sm: 6, md: 4 }}> | |
| 672 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='8px'> | |
| 673 | + Message | |
| 674 | + </FormLabel> | |
| 675 | + <Input | |
| 676 | + disabled={values?.validation[4]?.types === true ? false : true} | |
| 677 | + fontSize='sm' | |
| 678 | + onChange={handleChange} onBlur={handleBlur} | |
| 679 | + value={values.validation[4].types === true ? values.validation[4].message : ''} | |
| 680 | + name={`validation[${4}].message`} | |
| 681 | + placeholder='Enter Formik Type Message' | |
| 682 | + fontWeight='500' | |
| 683 | + /> | |
| 684 | + </GridItem></>} | |
| 685 | + </> | |
| 686 | + : | |
| 687 | + ""} | |
| 688 | + </Grid> | |
| 689 | + </> | |
| 690 | + </ModalBody> | |
| 691 | + <ModalFooter> | |
| 692 | + <Button colorScheme="brand" size="sm" mr={2} type='submit' disabled={isLoding ? true : false} onClick={handleSubmit} >{isLoding ? <Spinner /> : 'Update'}</Button> | |
| 693 | + <Button variant="outline" | |
| 694 | + colorScheme='red' size="sm" | |
| 695 | + sx={{ | |
| 696 | + textTransform: "capitalize", | |
| 697 | + }} onClick={() => { handleClose(); resetForm() }}>Close</Button> | |
| 698 | + </ModalFooter> | |
| 699 | + </ModalContent> | |
| 700 | + </Modal> | |
| 701 | + </div> | |
| 702 | + ) | |
| 703 | +} | |
| 704 | + | |
| 705 | +export default EditField |
+419-0Client/src/views/admin/customField/index.js
| @@ -0,0 +1,419 @@ | ||
| 1 | + | |
| 2 | +import React, { useEffect, useState } from 'react' | |
| 3 | +import { AddIcon, ChevronDownIcon, DeleteIcon, EditIcon } from '@chakra-ui/icons'; | |
| 4 | +import { Box, Button, Flex, Menu, MenuButton, Checkbox, GridItem, Text, MenuItem, Grid, MenuList, useColorModeValue } from '@chakra-ui/react'; | |
| 5 | +import Card from 'components/card/Card' | |
| 6 | +import Addfield from './addfield' | |
| 7 | +import { getApi, putApi } from 'services/api'; | |
| 8 | +import EditField from './editfield'; | |
| 9 | +import { DragDropContext, Droppable, Draggable } from 'react-beautiful-dnd'; | |
| 10 | +import { BiGridVertical } from "react-icons/bi"; | |
| 11 | +import AddEditHeading from "./addEditHeading"; | |
| 12 | +import { useNavigate } from 'react-router-dom'; | |
| 13 | +import { IoIosArrowBack } from 'react-icons/io'; | |
| 14 | +import Spinner from "components/spinner/Spinner"; | |
| 15 | +import CommonDeleteModel from 'components/commonDeleteModel'; | |
| 16 | +import { deleteApi } from 'services/api'; | |
| 17 | +import { deleteManyApi } from 'services/api'; | |
| 18 | + | |
| 19 | +const CustomField = () => { | |
| 20 | + const [addFieldModel, setAddFieldModel] = useState(false); | |
| 21 | + const [moduleName, setModuleName] = useState('') | |
| 22 | + const [moduleId, setModuleId] = useState('') | |
| 23 | + const [data, setData] = useState([]) | |
| 24 | + const [dataFilter, setDataFilter] = useState([]) | |
| 25 | + const [fields, setFields] = useState([]) | |
| 26 | + const [editModal, setEditModal] = useState(false) | |
| 27 | + const [updateField, setUpdateField] = useState({}) | |
| 28 | + const [deleteMany, setDeleteMany] = useState(false) | |
| 29 | + const [selectedValues, setSelectedValues] = useState([]); | |
| 30 | + | |
| 31 | + const [addHeadingModel, setAddHeadingModel] = useState(false); | |
| 32 | + const [deleteManyHeadings, setDeleteManyHeadings] = useState(false); | |
| 33 | + const [editHeadingModal, setEditHeadingModal] = useState(false); | |
| 34 | + const [updateHeading, setUpdateHeading] = useState({}); | |
| 35 | + const [selectedHeadings, setSelectedHeadings] = useState([]); | |
| 36 | + const [headingId, setHeadingId] = useState(''); | |
| 37 | + const [heading, setHeading] = useState(''); | |
| 38 | + const [isLoding, setIsLoding] = useState(false) | |
| 39 | + const [method, setMethod] = useState('') | |
| 40 | + const [selectedId, setSelectedId] = useState('') | |
| 41 | + const [validations, setValidations] = useState([]) | |
| 42 | + const [isLoading, setIsLoading] = useState(false) | |
| 43 | + | |
| 44 | + const handleHeadigsCheckboxChange = (event, value) => { | |
| 45 | + if (event.target.checked) { | |
| 46 | + setSelectedHeadings((prevSelectedHeadings) => [...prevSelectedHeadings, value]); | |
| 47 | + } else { | |
| 48 | + setSelectedHeadings((prevSelectedHeadings) => | |
| 49 | + prevSelectedHeadings.filter((selectedHeading) => selectedHeading !== value) | |
| 50 | + ); | |
| 51 | + } | |
| 52 | + }; | |
| 53 | + | |
| 54 | + const handleHeadingsDragEnd = async (result) => { | |
| 55 | + if (!result.destination) { | |
| 56 | + return; | |
| 57 | + } | |
| 58 | + | |
| 59 | + const newData = [...data[0]?.headings]; | |
| 60 | + const [removed] = newData.splice(result.source.index, 1); | |
| 61 | + newData.splice(result.destination.index, 0, removed); | |
| 62 | + setData((prev) => [{ ...prev[0], headings: newData }]); | |
| 63 | + | |
| 64 | + const response = await putApi(`api/custom-field/change-headings/${moduleId}`, newData) | |
| 65 | + fetchData(); | |
| 66 | + }; | |
| 67 | + | |
| 68 | + const textColor = useColorModeValue("gray.500", "white"); | |
| 69 | + | |
| 70 | + const getValidationData = async () => { | |
| 71 | + const response = await getApi('api/validation') | |
| 72 | + setValidations(response.data) | |
| 73 | + } | |
| 74 | + | |
| 75 | + useEffect(() => { | |
| 76 | + getValidationData() | |
| 77 | + }, [moduleName]) | |
| 78 | + | |
| 79 | + const navigate = useNavigate() | |
| 80 | + | |
| 81 | + const handleCheckboxChange = (event, value) => { | |
| 82 | + if (event.target.checked) { | |
| 83 | + setSelectedValues((prevSelectedValues) => [...prevSelectedValues, value]); | |
| 84 | + } else { | |
| 85 | + setSelectedValues((prevSelectedValues) => | |
| 86 | + prevSelectedValues.filter((selectedValue) => selectedValue !== value) | |
| 87 | + ); | |
| 88 | + } | |
| 89 | + }; | |
| 90 | + | |
| 91 | + const handleDragEnd = async (result) => { | |
| 92 | + if (!result.destination) { | |
| 93 | + return; | |
| 94 | + } | |
| 95 | + | |
| 96 | + const newData = [...data[0]?.fields]; | |
| 97 | + const newFilterData = [...dataFilter]; | |
| 98 | + const [removed] = newData.splice(result.source.index, 1); | |
| 99 | + newData.splice(result.destination.index, 0, removed); | |
| 100 | + const [removed1] = newFilterData.splice(result.source.index, 1); | |
| 101 | + newFilterData.splice(result.destination.index, 0, removed1); | |
| 102 | + setData((prev) => [{ ...prev[0], fields: newData }]); | |
| 103 | + setDataFilter(newFilterData); | |
| 104 | + | |
| 105 | + const mergedArray = [...newFilterData, ...newData]; | |
| 106 | + | |
| 107 | + const unique = [...new Set(mergedArray.map(item => item))] | |
| 108 | + | |
| 109 | + await putApi(`api/custom-field/change-fields/${moduleId}`, unique); | |
| 110 | + fetchData(); | |
| 111 | + }; | |
| 112 | + | |
| 113 | + const fetchData = async () => { | |
| 114 | + setIsLoading(true) | |
| 115 | + let responseAllData = await getApi(`api/custom-field`); | |
| 116 | + setFields(responseAllData?.data); | |
| 117 | + if (moduleName) { | |
| 118 | + let response = await getApi(`api/custom-field/?moduleName=${moduleName}`); | |
| 119 | + const filterData = response?.data[0]?.fields?.filter(field => (headingId ? headingId : field?.belongsTo) === field?.belongsTo); | |
| 120 | + | |
| 121 | + setDataFilter(filterData); | |
| 122 | + setData(response?.data); | |
| 123 | + } else if (!moduleName) { | |
| 124 | + setData([]); | |
| 125 | + setDataFilter([]) | |
| 126 | + } | |
| 127 | + setIsLoading(false) | |
| 128 | + } | |
| 129 | + | |
| 130 | + const handleDeleteFiled = async (id, fieldsIds) => { | |
| 131 | + if (method === 'one') { | |
| 132 | + try { | |
| 133 | + setIsLoding(true) | |
| 134 | + const response = await deleteApi(`api/custom-field/delete/${id}?moduleId=`, moduleId) | |
| 135 | + if (response.status === 200) { | |
| 136 | + setDeleteMany(false) | |
| 137 | + fetchData() | |
| 138 | + } | |
| 139 | + } catch (error) { | |
| 140 | + console.log(error) | |
| 141 | + } | |
| 142 | + finally { | |
| 143 | + setIsLoding(false) | |
| 144 | + } | |
| 145 | + } else if (method === 'many') { | |
| 146 | + try { | |
| 147 | + setIsLoding(true) | |
| 148 | + const payload = { | |
| 149 | + moduleId: data[0]?._id, | |
| 150 | + fieldsIds: fieldsIds | |
| 151 | + } | |
| 152 | + let response = await deleteManyApi('api/custom-field/deleteMany', payload) | |
| 153 | + if (response.status === 200) { | |
| 154 | + setSelectedValues([]) | |
| 155 | + setDeleteMany(false) | |
| 156 | + fetchData() | |
| 157 | + } | |
| 158 | + } catch (error) { | |
| 159 | + console.log(error) | |
| 160 | + } | |
| 161 | + finally { | |
| 162 | + setIsLoding(false) | |
| 163 | + } | |
| 164 | + } | |
| 165 | + }; | |
| 166 | + | |
| 167 | + const handleDeleteHeading = async (id, headingsIds) => { | |
| 168 | + if (method === 'one') { | |
| 169 | + try { | |
| 170 | + setIsLoding(true) | |
| 171 | + const response = await deleteApi(`api/custom-field/delete-heading/${id}?moduleId=`, moduleId) | |
| 172 | + if (response.status === 200) { | |
| 173 | + setDeleteManyHeadings(false) | |
| 174 | + fetchData() | |
| 175 | + } | |
| 176 | + } catch (error) { | |
| 177 | + console.log(error) | |
| 178 | + } | |
| 179 | + finally { | |
| 180 | + setIsLoding(false) | |
| 181 | + } | |
| 182 | + } else if (method === 'many') { | |
| 183 | + try { | |
| 184 | + setIsLoding(true) | |
| 185 | + const payload = { | |
| 186 | + moduleId: data[0]?._id, | |
| 187 | + headingsIds: headingsIds | |
| 188 | + } | |
| 189 | + let response = await deleteManyApi('api/custom-field/deleteMany-headings', payload) | |
| 190 | + if (response.status === 200) { | |
| 191 | + setSelectedHeadings([]) | |
| 192 | + setDeleteManyHeadings(false) | |
| 193 | + fetchData() | |
| 194 | + } | |
| 195 | + } catch (error) { | |
| 196 | + console.log(error) | |
| 197 | + } | |
| 198 | + finally { | |
| 199 | + setIsLoding(false) | |
| 200 | + } | |
| 201 | + } | |
| 202 | + }; | |
| 203 | + | |
| 204 | + const handleOpenDeleteMany = (id, type) => { | |
| 205 | + setMethod(type) | |
| 206 | + setSelectedId(id); | |
| 207 | + setDeleteMany(true); | |
| 208 | + } | |
| 209 | + | |
| 210 | + const handleOpenDeleteModel = (id, type) => { | |
| 211 | + setMethod(type) | |
| 212 | + setSelectedId(id); | |
| 213 | + setDeleteManyHeadings(true); | |
| 214 | + } | |
| 215 | + | |
| 216 | + useEffect(() => { | |
| 217 | + if (fetchData) fetchData() | |
| 218 | + }, [moduleName, headingId]) | |
| 219 | + | |
| 220 | + return ( | |
| 221 | + <> | |
| 222 | + <Card minHeight='250px'> | |
| 223 | + <Flex justifyContent={'space-between'} alignItems={'center'}> | |
| 224 | + <Flex alignItems='center'> | |
| 225 | + <Text color={"secondaryGray.900"} | |
| 226 | + fontSize="22px" | |
| 227 | + fontWeight="700" | |
| 228 | + >{moduleName ? 'Custom Heading' : 'Select Module'}</Text> | |
| 229 | + {selectedHeadings.length > 0 && <Button color="red" ml='2' onClick={() => handleOpenDeleteModel('', 'many')} size='sm' ><DeleteIcon /></Button>} | |
| 230 | + </Flex> | |
| 231 | + <Box> | |
| 232 | + <Flex> | |
| 233 | + {!isLoading && ( | |
| 234 | + <Menu> | |
| 235 | + <MenuButton as={Button} mr={2} size='sm' rightIcon={<ChevronDownIcon />} variant="outline"> | |
| 236 | + {moduleName ? moduleName : 'Select Module'} | |
| 237 | + </MenuButton> | |
| 238 | + <MenuList minWidth={"10rem"} maxHeight={'15rem'} overflow={'auto'}> | |
| 239 | + <MenuItem onClick={() => { setModuleName(''); setData([]); setDataFilter([]); }}>Select Module</MenuItem> | |
| 240 | + {fields?.map((item, id) => ( | |
| 241 | + <MenuItem key={id} onClick={() => { setModuleName(item.moduleName); setModuleId(item._id); setHeadingId('') }}>{item.moduleName}</MenuItem> | |
| 242 | + ))} | |
| 243 | + </MenuList> | |
| 244 | + </Menu> | |
| 245 | + )} | |
| 246 | + | |
| 247 | + {data?.length > 0 && <Button ml={2} onClick={() => setAddHeadingModel(true)} leftIcon={<AddIcon />} variant="brand" size='sm'>Add Heading</Button>} | |
| 248 | + <Button onClick={() => navigate('/admin-setting')} variant="brand" size="sm" leftIcon={<IoIosArrowBack />} ml={2}>Back</Button> | |
| 249 | + </Flex> | |
| 250 | + </Box> | |
| 251 | + </Flex> | |
| 252 | + {!data?.length > 0 && !isLoading && <Text | |
| 253 | + textAlign={"center"} | |
| 254 | + width="100%" | |
| 255 | + color={'gray.500'} | |
| 256 | + fontSize="sm" | |
| 257 | + my='7' | |
| 258 | + fontWeight="700" | |
| 259 | + >-- Please Select Module --</Text>} | |
| 260 | + {isLoading ? <Flex justifyContent={'center'} alignItems={'center'} width="100%" color={textColor} fontSize="sm" fontWeight="700"> | |
| 261 | + <Spinner /> | |
| 262 | + </Flex> : <DragDropContext DragDropContext onDragEnd={handleHeadingsDragEnd} > | |
| 263 | + <Droppable droppableId="droppable"> | |
| 264 | + {(provided) => ( | |
| 265 | + <div ref={provided.innerRef} {...provided.droppableProps}> | |
| 266 | + { | |
| 267 | + data[0]?.headings?.length === 0 ? <Text | |
| 268 | + textAlign={"center"} | |
| 269 | + width="100%" | |
| 270 | + color={'gray.500'} | |
| 271 | + fontSize="sm" | |
| 272 | + my='7' | |
| 273 | + fontWeight="700" | |
| 274 | + >-- No Data Found --</Text> : <Grid templateColumns="repeat(12, 1fr)" gap={3} mt={5}> | |
| 275 | + {data[0]?.headings?.map((item, i) => ( | |
| 276 | + <GridItem colSpan={{ base: 12, md: 6 }} key={item._id}> | |
| 277 | + <Draggable draggableId={item._id} index={i}> | |
| 278 | + {(provided, snapshot) => ( | |
| 279 | + <div | |
| 280 | + ref={provided.innerRef} | |
| 281 | + {...provided.draggableProps} | |
| 282 | + > | |
| 283 | + <Flex | |
| 284 | + alignItems="center" | |
| 285 | + justifyContent="space-between" | |
| 286 | + className="CustomFieldName" | |
| 287 | + > | |
| 288 | + | |
| 289 | + <Text display='flex' alignItems='center' size='sm' colorScheme='gray' ms='4px' mt={4} fontSize='md' fontWeight='500' mb='8px' > | |
| 290 | + {!item.fixed && <div {...provided.dragHandleProps} style={{ marginRight: '10px', cursor: 'grab' }} size={18} > | |
| 291 | + <BiGridVertical size={18} /> | |
| 292 | + </div>} | |
| 293 | + {!item.fixed && <Checkbox colorScheme="brandScheme" value={selectedHeadings} isChecked={selectedHeadings.includes(item?._id)} onChange={(event) => handleHeadigsCheckboxChange(event, item?._id)} me="10px" />} | |
| 294 | + {item?.heading} | |
| 295 | + </Text> | |
| 296 | + <span className="EditDelete"> | |
| 297 | + {item?.editable ? | |
| 298 | + <Button size='sm' variant='outline' me={2} color={'green'} onClick={() => { setEditHeadingModal(true); setUpdateHeading(item) }}><EditIcon /></Button> : | |
| 299 | + <Button size='sm' variant='outline' me={2} color={'gray'} pointer="none" ><EditIcon /></Button>} | |
| 300 | + {item.fixed ? <Button size='sm' variant='outline' me={2} color={'gray'}><DeleteIcon /></Button> : <Button size='sm' variant='outline' me={2} color={'red'} onClick={() => { handleOpenDeleteModel(item?._id, 'one') }}><DeleteIcon /></Button>} | |
| 301 | + </span> | |
| 302 | + </Flex> | |
| 303 | + </div> | |
| 304 | + )} | |
| 305 | + </Draggable> | |
| 306 | + </GridItem> | |
| 307 | + ))} | |
| 308 | + </Grid> | |
| 309 | + } | |
| 310 | + | |
| 311 | + </div> | |
| 312 | + )} | |
| 313 | + </Droppable> | |
| 314 | + </DragDropContext>} | |
| 315 | + | |
| 316 | + </Card> | |
| 317 | + {data?.length > 0 && <Card mt='5'> | |
| 318 | + <Flex justifyContent={'space-between'} alignItems={'center'} mt='5'> | |
| 319 | + <Flex alignItems='center'> | |
| 320 | + <Text color={"secondaryGray.900"} | |
| 321 | + fontSize="22px" | |
| 322 | + fontWeight="700" | |
| 323 | + >{moduleName && 'Custom Field'}</Text> | |
| 324 | + {selectedValues.length > 0 && <Button color="red" ml='2' onClick={() => handleOpenDeleteMany('', 'many')} size='sm' ><DeleteIcon /></Button>} | |
| 325 | + </Flex> | |
| 326 | + | |
| 327 | + <Box> | |
| 328 | + {(!isLoading && data[0]?.headings.length > 0) && <Menu> | |
| 329 | + <MenuButton as={Button} size='sm' rightIcon={<ChevronDownIcon />} variant="outline"> | |
| 330 | + {heading ? heading : 'Select All Headings'} | |
| 331 | + </MenuButton> | |
| 332 | + <MenuList minWidth={"10rem"} maxHeight={'15rem'} overflow={'auto'}> | |
| 333 | + <MenuItem onClick={() => { setHeading(''); setHeadingId('') }}>Select All Headings</MenuItem> | |
| 334 | + {data[0]?.headings?.map((item, id) => ( | |
| 335 | + <MenuItem key={id} onClick={() => { setHeading(item.heading); setHeadingId(item._id) }}>{item.heading}</MenuItem> | |
| 336 | + ))} | |
| 337 | + </MenuList> | |
| 338 | + </Menu>} | |
| 339 | + {data?.length > 0 && <Button me={2} ml='2' onClick={() => setAddFieldModel(true)} variant="brand" leftIcon={<AddIcon />} size='sm'>Add Field</Button>} | |
| 340 | + </Box> | |
| 341 | + | |
| 342 | + </Flex> | |
| 343 | + {isLoading && moduleName ? <Flex justifyContent={'center'} alignItems={'center'} width="100%" color={textColor} fontSize="sm" fontWeight="700"> | |
| 344 | + <Spinner /> | |
| 345 | + </Flex> : | |
| 346 | + <> | |
| 347 | + <DragDropContext DragDropContext onDragEnd={handleDragEnd} > | |
| 348 | + <Droppable droppableId="droppable"> | |
| 349 | + {(provided) => ( | |
| 350 | + <div ref={provided.innerRef} {...provided.droppableProps}> | |
| 351 | + { | |
| 352 | + dataFilter.length === 0 ? | |
| 353 | + <Text | |
| 354 | + textAlign={"center"} | |
| 355 | + width="100%" | |
| 356 | + color={'gray.500'} | |
| 357 | + fontSize="sm" | |
| 358 | + my='7' | |
| 359 | + fontWeight="700" | |
| 360 | + >-- No Data Found --</Text> | |
| 361 | + : <Grid templateColumns="repeat(12, 1fr)" gap={3} mt={5}> | |
| 362 | + {dataFilter && dataFilter?.map((item, i) => ( | |
| 363 | + <GridItem colSpan={{ base: 12, md: 6 }} key={item._id}> | |
| 364 | + <Draggable draggableId={item._id} index={i}> | |
| 365 | + {(provided, snapshot) => ( | |
| 366 | + <div | |
| 367 | + ref={provided.innerRef} | |
| 368 | + {...provided.draggableProps} | |
| 369 | + > | |
| 370 | + <Flex | |
| 371 | + alignItems="center" | |
| 372 | + justifyContent="space-between" | |
| 373 | + className="CustomFieldName" | |
| 374 | + > | |
| 375 | + | |
| 376 | + <Text display='flex' alignItems='center' size='sm' colorScheme='gray' ms='4px' mt={4} fontSize='md' fontWeight='500' mb='8px' > | |
| 377 | + <div {...provided.dragHandleProps} style={{ marginRight: '10px', cursor: 'grab' }} size={18} > | |
| 378 | + <BiGridVertical size={18} /> | |
| 379 | + </div> | |
| 380 | + {!item.fixed && <Checkbox colorScheme="brandScheme" value={selectedValues} isChecked={selectedValues.includes(item?._id)} onChange={(event) => handleCheckboxChange(event, item?._id)} me="10px" />} | |
| 381 | + {item?.label} | |
| 382 | + </Text> | |
| 383 | + <span className="EditDelete"> | |
| 384 | + {item?.editable ? | |
| 385 | + <Button size='sm' variant='outline' me={2} color={'green'} onClick={() => { setEditModal(true); setUpdateField(item) }}><EditIcon /></Button> : | |
| 386 | + <Button size='sm' variant='outline' me={2} color={'gray'} ><EditIcon /></Button> | |
| 387 | + } | |
| 388 | + {item.fixed ? <Button size='sm' variant='outline' me={2} color={'gray'}><DeleteIcon /></Button> : <Button size='sm' variant='outline' me={2} color={'red'} onClick={() => { handleOpenDeleteMany(item?._id, "one"); }}><DeleteIcon /></Button>} | |
| 389 | + </span> | |
| 390 | + </Flex> | |
| 391 | + </div> | |
| 392 | + )} | |
| 393 | + </Draggable> | |
| 394 | + </GridItem> | |
| 395 | + ))} | |
| 396 | + </Grid> | |
| 397 | + } | |
| 398 | + | |
| 399 | + </div> | |
| 400 | + )} | |
| 401 | + </Droppable> | |
| 402 | + </DragDropContext> | |
| 403 | + | |
| 404 | + </> | |
| 405 | + } | |
| 406 | + </Card>} | |
| 407 | + <Addfield isOpen={addFieldModel} onClose={setAddFieldModel} moduleName={moduleName} field={data[0]?.fields} validations={validations} moduleId={data[0]?._id} fetchData={fetchData} headingsData={data?.[0]?.headings} /> | |
| 408 | + <EditField isOpen={editModal} onClose={setEditModal} field={data[0]?.fields} moduleId={data[0]?._id} validations={validations} fetchData={fetchData} updateFiled={updateField} headingsData={data?.[0]?.headings} /> | |
| 409 | + | |
| 410 | + <AddEditHeading isOpen={addHeadingModel || editHeadingModal} onClose={editHeadingModal ? setEditHeadingModal : setAddHeadingModel} moduleName={moduleName} moduleId={data[0]?._id} fetchData={fetchData} updateData={updateHeading} setUpdateData={setUpdateHeading} /> | |
| 411 | + | |
| 412 | + <CommonDeleteModel isOpen={deleteMany} onClose={() => setDeleteMany(false)} type={method === "one" ? 'Leads Filed' : 'Leads Fileds'} handleDeleteData={handleDeleteFiled} ids={selectedId} selectedValues={selectedValues} /> | |
| 413 | + <CommonDeleteModel isOpen={deleteManyHeadings} onClose={() => setDeleteManyHeadings(false)} type={method === "one" ? 'Leads Heading' : 'Leads Headings'} handleDeleteData={handleDeleteHeading} ids={selectedId} selectedValues={selectedHeadings} /> | |
| 414 | + | |
| 415 | + </> | |
| 416 | + ) | |
| 417 | +} | |
| 418 | + | |
| 419 | +export default CustomField |
+92-0Client/src/views/admin/default/components/PieCard.js
| @@ -0,0 +1,92 @@ | ||
| 1 | +// Chakra imports | |
| 2 | +import { Box, Flex, Text, Select, useColorModeValue } from "@chakra-ui/react"; | |
| 3 | +// Custom components | |
| 4 | +import Card from "components/card/Card.js"; | |
| 5 | +import PieChart from "components/charts/PieChart"; | |
| 6 | +import { pieChartData, pieChartOptions } from "variables/charts"; | |
| 7 | +import { VSeparator } from "components/separator/Separator"; | |
| 8 | +import React from "react"; | |
| 9 | + | |
| 10 | +export default function Conversion(props) { | |
| 11 | + const { ...rest } = props; | |
| 12 | + | |
| 13 | + // Chakra Color Mode | |
| 14 | + const textColor = useColorModeValue("secondaryGray.900", "white"); | |
| 15 | + const cardColor = useColorModeValue("white", "navy.700"); | |
| 16 | + const cardShadow = useColorModeValue( | |
| 17 | + "0px 18px 40px rgba(112, 144, 176, 0.12)", | |
| 18 | + "unset" | |
| 19 | + ); | |
| 20 | + return ( | |
| 21 | + <Card p='20px' align='center' direction='column' w='100%' {...rest}> | |
| 22 | + <Flex | |
| 23 | + px={{ base: "0px", "2xl": "10px" }} | |
| 24 | + justifyContent='space-between' | |
| 25 | + alignItems='center' | |
| 26 | + w='100%' | |
| 27 | + mb='8px'> | |
| 28 | + <Text color={textColor} fontSize='md' fontWeight='600' mt='4px'> | |
| 29 | + Your Pie Chart | |
| 30 | + </Text> | |
| 31 | + <Select | |
| 32 | + fontSize='sm' | |
| 33 | + variant='subtle' | |
| 34 | + defaultValue='monthly' | |
| 35 | + width='unset' | |
| 36 | + fontWeight='700'> | |
| 37 | + <option value='daily'>Daily</option> | |
| 38 | + <option value='monthly'>Monthly</option> | |
| 39 | + <option value='yearly'>Yearly</option> | |
| 40 | + </Select> | |
| 41 | + </Flex> | |
| 42 | + | |
| 43 | + <PieChart | |
| 44 | + h='100%' | |
| 45 | + w='100%' | |
| 46 | + chartData={pieChartData} | |
| 47 | + chartOptions={pieChartOptions} | |
| 48 | + /> | |
| 49 | + <Card | |
| 50 | + bg={cardColor} | |
| 51 | + flexDirection='row' | |
| 52 | + boxShadow={cardShadow} | |
| 53 | + w='100%' | |
| 54 | + p='15px' | |
| 55 | + px='20px' | |
| 56 | + mt='15px' | |
| 57 | + mx='auto'> | |
| 58 | + <Flex direction='column' py='5px'> | |
| 59 | + <Flex align='center'> | |
| 60 | + <Box h='8px' w='8px' bg='brand.500' borderRadius='50%' me='4px' /> | |
| 61 | + <Text | |
| 62 | + fontSize='xs' | |
| 63 | + color='secondaryGray.600' | |
| 64 | + fontWeight='700' | |
| 65 | + mb='5px'> | |
| 66 | + Your files | |
| 67 | + </Text> | |
| 68 | + </Flex> | |
| 69 | + <Text fontSize='lg' color={textColor} fontWeight='700'> | |
| 70 | + 63% | |
| 71 | + </Text> | |
| 72 | + </Flex> | |
| 73 | + <VSeparator mx={{ base: "60px", xl: "60px", "2xl": "60px" }} /> | |
| 74 | + <Flex direction='column' py='5px' me='10px'> | |
| 75 | + <Flex align='center'> | |
| 76 | + <Box h='8px' w='8px' bg='#6AD2FF' borderRadius='50%' me='4px' /> | |
| 77 | + <Text | |
| 78 | + fontSize='xs' | |
| 79 | + color='secondaryGray.600' | |
| 80 | + fontWeight='700' | |
| 81 | + mb='5px'> | |
| 82 | + System | |
| 83 | + </Text> | |
| 84 | + </Flex> | |
| 85 | + <Text fontSize='lg' color={textColor} fontWeight='700'> | |
| 86 | + 25% | |
| 87 | + </Text> | |
| 88 | + </Flex> | |
| 89 | + </Card> | |
| 90 | + </Card> | |
| 91 | + ); | |
| 92 | +} |
+355-0Client/src/views/admin/default/index.jsx
| @@ -0,0 +1,355 @@ | ||
| 1 | +// Chakra imports | |
| 2 | +import { | |
| 3 | + Flex, | |
| 4 | + Heading, | |
| 5 | + Icon, | |
| 6 | + IconButton, | |
| 7 | + SimpleGrid, | |
| 8 | + useColorModeValue, | |
| 9 | + Grid, | |
| 10 | + GridItem, | |
| 11 | + Progress, | |
| 12 | + Box, | |
| 13 | + Text, | |
| 14 | +} from "@chakra-ui/react"; | |
| 15 | +// Assets | |
| 16 | +// Custom components | |
| 17 | +import { ViewIcon } from "@chakra-ui/icons"; | |
| 18 | +import Card from "components/card/Card"; | |
| 19 | +import MiniStatistics from "components/card/MiniStatistics"; | |
| 20 | +import IconBox from "components/icons/IconBox"; | |
| 21 | +import { HSeparator } from "components/separator/Separator"; | |
| 22 | +import { useEffect, useState } from "react"; | |
| 23 | +import { LuBuilding2 } from "react-icons/lu"; | |
| 24 | +import { MdAddTask, MdContacts, MdLeaderboard } from "react-icons/md"; | |
| 25 | +import { useNavigate } from "react-router-dom"; | |
| 26 | +import { getApi } from "services/api"; | |
| 27 | +import ReportChart from "../reports/components/reportChart"; | |
| 28 | +import Chart from "components/charts/LineChart.js"; | |
| 29 | +import { HasAccess } from "../../../redux/accessUtils"; | |
| 30 | +import PieChart from "components/charts/PieChart"; | |
| 31 | +import CountUpComponent from "../../../../src/components/countUpComponent/countUpComponent"; | |
| 32 | +import Spinner from 'components/spinner/Spinner'; | |
| 33 | +import { useSelector } from "react-redux"; | |
| 34 | + | |
| 35 | +export default function UserReports() { | |
| 36 | + // Chakra Color Mode | |
| 37 | + const brandColor = useColorModeValue("brand.500", "white"); | |
| 38 | + const boxBg = useColorModeValue("secondaryGray.300", "whiteAlpha.100"); | |
| 39 | + const user = JSON.parse(localStorage.getItem("user")); | |
| 40 | + const [isLoding, setIsLoding] = useState(false); | |
| 41 | + | |
| 42 | + const [allData, setAllData] = useState([]); | |
| 43 | + const [data, setData] = useState([]); | |
| 44 | + const navigate = useNavigate(); | |
| 45 | + const modules = useSelector((state) => state?.modules?.data) | |
| 46 | + const [contactsView, taskView, leadView, proprtyView] = HasAccess(["Contacts", "Tasks", "Leads", "Properties"]); | |
| 47 | + | |
| 48 | + const fetchData = async () => { | |
| 49 | + let responseData = await getApi(user?.role === 'superAdmin' ? `api/status/` : `api/status/?createBy=${user?._id}`); | |
| 50 | + setAllData(responseData?.data?.data); | |
| 51 | + }; | |
| 52 | + | |
| 53 | + | |
| 54 | + const fetchProgressChart = async () => { | |
| 55 | + setIsLoding(true); | |
| 56 | + let result = await getApi(user?.role === 'superAdmin' ? 'api/reporting/line-chart' : `api/reporting/line-chart?createBy=${user?._id}`); | |
| 57 | + if (result && result?.status === 200) { | |
| 58 | + setData(result?.data) | |
| 59 | + } | |
| 60 | + setIsLoding(false); | |
| 61 | + } | |
| 62 | + useEffect(() => { | |
| 63 | + fetchProgressChart() | |
| 64 | + }, []) | |
| 65 | + | |
| 66 | + | |
| 67 | + const findModuleData = (title) => { | |
| 68 | + const filterData = data?.find(item => item?.name === title) | |
| 69 | + return filterData?.length || 0 | |
| 70 | + } | |
| 71 | + | |
| 72 | + const findLeadStatus = (title) => { | |
| 73 | + const filterData = allData?.leadData?.filter(item => item?.leadStatus === title) | |
| 74 | + return filterData?.length || 0 | |
| 75 | + } | |
| 76 | + const findTaskStatus = (title) => { | |
| 77 | + const filterData = allData?.taskData?.filter(item => item?.status === title) | |
| 78 | + return filterData?.length || 0 | |
| 79 | + } | |
| 80 | + | |
| 81 | + const leadModule = modules?.find(({ moduleName }) => moduleName === "Leads") | |
| 82 | + const contactModule = modules?.find(({ moduleName }) => moduleName === "Contacts") | |
| 83 | + const propertiesModule = modules?.find(({ moduleName }) => moduleName === "Properties") | |
| 84 | + const tasksModule = modules?.find(({ moduleName }) => moduleName === "Tasks") | |
| 85 | + const reportModule = modules?.find(({ moduleName }) => moduleName === "Reporting and Analytics") | |
| 86 | + const emailModule = modules?.find(({ moduleName }) => moduleName === "Emails") | |
| 87 | + const callModule = modules?.find(({ moduleName }) => moduleName === "Calls") | |
| 88 | + | |
| 89 | + const taskStatus = [ | |
| 90 | + { | |
| 91 | + name: "Completed", | |
| 92 | + status: 'completed', | |
| 93 | + length: findTaskStatus('completed'), | |
| 94 | + color: "#4d8f3a" | |
| 95 | + }, | |
| 96 | + { | |
| 97 | + name: "Pending", | |
| 98 | + status: 'pending', | |
| 99 | + length: findTaskStatus('pending'), | |
| 100 | + color: "#a37f08" | |
| 101 | + }, | |
| 102 | + { | |
| 103 | + name: "In Progress", | |
| 104 | + status: 'inProgress', | |
| 105 | + length: findTaskStatus('inProgress'), | |
| 106 | + color: "#7038db" | |
| 107 | + }, | |
| 108 | + { | |
| 109 | + name: "Todo", | |
| 110 | + status: 'todo', | |
| 111 | + length: findTaskStatus('todo'), | |
| 112 | + color: "#1f7eeb" | |
| 113 | + }, | |
| 114 | + { | |
| 115 | + name: "On Hold", | |
| 116 | + status: 'onHold', | |
| 117 | + length: findTaskStatus('onHold'), | |
| 118 | + color: "#DB5436" | |
| 119 | + }, | |
| 120 | + ] | |
| 121 | + const navigateTo = { | |
| 122 | + Lead: '/lead', | |
| 123 | + Contact: '/contacts', | |
| 124 | + Meeting: '/metting', | |
| 125 | + Call: '/phone-call', | |
| 126 | + Task: '/task', | |
| 127 | + Email: '/email', | |
| 128 | + Property: '/properties', | |
| 129 | + }; | |
| 130 | + | |
| 131 | + useEffect(() => { | |
| 132 | + fetchData(); | |
| 133 | + }, [user?._id]); | |
| 134 | + | |
| 135 | + return ( | |
| 136 | + <> | |
| 137 | + <SimpleGrid columns={{ base: 1, md: 2, lg: 4 }} gap="20px" mb="20px"> | |
| 138 | + {(taskView?.create || taskView?.update || taskView?.delete || taskView?.view) && (tasksModule?.isActive) && | |
| 139 | + <MiniStatistics | |
| 140 | + onClick={() => navigate("/task")} | |
| 141 | + startContent={ | |
| 142 | + <IconBox | |
| 143 | + w="56px" | |
| 144 | + h="56px" | |
| 145 | + bg="linear-gradient(90deg, #4481EB 0%, #04BEFE 100%)" | |
| 146 | + icon={<Icon w="28px" h="28px" as={MdAddTask} color="white" />} | |
| 147 | + /> | |
| 148 | + } | |
| 149 | + name="Tasks" | |
| 150 | + value={findModuleData("Tasks")} | |
| 151 | + />} | |
| 152 | + {(contactsView?.create || contactsView?.update || contactsView?.delete || contactsView?.view) && (contactModule?.isActive) && | |
| 153 | + < MiniStatistics | |
| 154 | + onClick={() => navigate("/contacts")} | |
| 155 | + startContent={ | |
| 156 | + <IconBox | |
| 157 | + w="56px" | |
| 158 | + h="56px" | |
| 159 | + bg={boxBg} | |
| 160 | + icon={ | |
| 161 | + <Icon w="32px" h="32px" as={MdContacts} color={brandColor} /> | |
| 162 | + } | |
| 163 | + /> | |
| 164 | + } | |
| 165 | + name="Contacts" | |
| 166 | + value={findModuleData("Contacts")} | |
| 167 | + />} | |
| 168 | + {(leadView?.create || leadView?.update || leadView?.delete || leadView?.view) && (leadModule?.isActive) && | |
| 169 | + <MiniStatistics | |
| 170 | + onClick={() => navigate("/lead")} | |
| 171 | + startContent={ | |
| 172 | + <IconBox | |
| 173 | + w="56px" | |
| 174 | + h="56px" | |
| 175 | + bg={boxBg} | |
| 176 | + icon={ | |
| 177 | + <Icon w="32px" h="32px" as={MdLeaderboard} color={brandColor} /> | |
| 178 | + } | |
| 179 | + /> | |
| 180 | + } | |
| 181 | + name="Leads" | |
| 182 | + value={findModuleData("Leads")} | |
| 183 | + />} | |
| 184 | + {(proprtyView?.create || proprtyView?.update || proprtyView?.delete || proprtyView?.view) && (propertiesModule?.isActive) && | |
| 185 | + <MiniStatistics | |
| 186 | + onClick={() => navigate("/properties")} | |
| 187 | + startContent={ | |
| 188 | + <IconBox | |
| 189 | + w="56px" | |
| 190 | + h="56px" | |
| 191 | + bg={boxBg} | |
| 192 | + icon={ | |
| 193 | + <Icon w="32px" h="32px" as={LuBuilding2} color={brandColor} /> | |
| 194 | + } | |
| 195 | + /> | |
| 196 | + } | |
| 197 | + name="Property" | |
| 198 | + value={findModuleData("Properties")} | |
| 199 | + />} | |
| 200 | + </SimpleGrid> | |
| 201 | + | |
| 202 | + <Grid Grid templateColumns="repeat(12, 1fr)" gap={3} > | |
| 203 | + { | |
| 204 | + (emailModule?.isActive || callModule?.isActive) && | |
| 205 | + <GridItem rowSpan={2} colSpan={{ base: 12, md: 6 }}> | |
| 206 | + <Card> | |
| 207 | + <Flex mb={3} alignItems={"center"} justifyContent={"space-between"}> | |
| 208 | + <Heading size="md">{(emailModule?.isActive && callModule?.isActive) ? "Email and Call" : emailModule?.isActive ? "Email" : callModule?.isActive ? "Call" : ""} Report</Heading> | |
| 209 | + { | |
| 210 | + reportModule?.isActive && | |
| 211 | + <IconButton | |
| 212 | + color={"green.500"} | |
| 213 | + onClick={() => navigate("/reporting-analytics")} | |
| 214 | + aria-label="Call Fred" | |
| 215 | + borderRadius="10px" | |
| 216 | + size="md" | |
| 217 | + icon={<ViewIcon />} | |
| 218 | + /> | |
| 219 | + } | |
| 220 | + </Flex> | |
| 221 | + <HSeparator /> | |
| 222 | + <ReportChart dashboard={"dashboard"} /> | |
| 223 | + </Card> | |
| 224 | + </GridItem> | |
| 225 | + } | |
| 226 | + <GridItem rowSpan={2} colSpan={{ base: 12, md: 6 }}> | |
| 227 | + <Card> | |
| 228 | + <Flex mb={5} alignItems={"center"} justifyContent={"space-between"}> | |
| 229 | + <Heading size="md">Module Data Report</Heading> | |
| 230 | + | |
| 231 | + </Flex> | |
| 232 | + <Box mb={3}> | |
| 233 | + <HSeparator /> | |
| 234 | + </Box> | |
| 235 | + <Chart dashboard={"dashboard"} data={data} /> | |
| 236 | + </Card> | |
| 237 | + </GridItem> | |
| 238 | + </Grid> | |
| 239 | + <SimpleGrid gap="20px" columns={{ | |
| 240 | + base: 1, md: leadView?.view && taskView?.view ? 2 : 2, lg: | |
| 241 | + leadView?.view && taskView?.view ? 3 : 2 | |
| 242 | + }} my="20px"> | |
| 243 | + | |
| 244 | + { | |
| 245 | + data && data.length > 0 && | |
| 246 | + <Card > | |
| 247 | + <Heading size="md" pb={3}>Statistics</Heading> | |
| 248 | + { | |
| 249 | + !isLoding ? | |
| 250 | + data && data.length > 0 && data?.map((item, i) => ( | |
| 251 | + <> | |
| 252 | + <Box border={"1px solid #e5e5e5"} p={2} m={1} cursor={'pointer'} key={i} onClick={() => navigate(navigateTo[item.name])}> | |
| 253 | + <Flex justifyContent={"space-between"}> | |
| 254 | + <Text fontSize="sm" fontWeight={600} pb={2}>{item?.name}</Text> | |
| 255 | + <Text fontSize="sm" fontWeight={600} pb={2}><CountUpComponent targetNumber={item?.length} /></Text> | |
| 256 | + </Flex> | |
| 257 | + <Progress | |
| 258 | + colorScheme={item?.color} | |
| 259 | + size='xs' value={item?.length} width={"100%"} /> | |
| 260 | + </Box> | |
| 261 | + </> | |
| 262 | + | |
| 263 | + )) : <div style={{ display: "flex", justifyContent: "center", alignItems: "center" }}><Spinner /></div> | |
| 264 | + } | |
| 265 | + </Card> | |
| 266 | + } | |
| 267 | + | |
| 268 | + {leadView?.view && (leadModule?.isActive) && <Card> | |
| 269 | + <Heading size="md" pb={2}>Lead Statistics</Heading> | |
| 270 | + {(leadView?.view) && | |
| 271 | + <Grid templateColumns="repeat(12, 1fr)" gap={2}> | |
| 272 | + <GridItem colSpan={{ base: 12, md: 6 }}> | |
| 273 | + <Box backgroundColor={"#ebf5ff"} | |
| 274 | + borderRadius={"10px"} | |
| 275 | + cursor={"pointer"} | |
| 276 | + onClick={() => navigate('/lead')} | |
| 277 | + p={2} m={1} textAlign={"center"}> | |
| 278 | + <Heading size="sm" pb={3} color={"#1f7eeb"}>Total Leads </Heading> | |
| 279 | + <Text fontWeight={600} color={"#1f7eeb"}><CountUpComponent targetNumber={allData?.leadData?.length || 0} /> </Text> | |
| 280 | + </Box> | |
| 281 | + </GridItem> | |
| 282 | + <GridItem colSpan={{ base: 12, md: 6 }}> | |
| 283 | + <Box backgroundColor={"#eaf9e6"} | |
| 284 | + borderRadius={"10px"} | |
| 285 | + cursor={"pointer"} | |
| 286 | + onClick={() => navigate('/lead', { state: 'active' })} | |
| 287 | + p={2} m={1} textAlign={"center"}> | |
| 288 | + <Heading size="sm" pb={3} color={"#43882f"} >Active Leads </Heading> | |
| 289 | + <Text fontWeight={600} color={"#43882f"}><CountUpComponent targetNumber={findLeadStatus("active")} /></Text> | |
| 290 | + </Box> | |
| 291 | + </GridItem> | |
| 292 | + <GridItem colSpan={{ base: 12, md: 6 }}> | |
| 293 | + <Box backgroundColor={"#fbf4dd"} | |
| 294 | + onClick={() => navigate('/lead', { state: 'pending' })} | |
| 295 | + borderRadius={"10px"} | |
| 296 | + cursor={"pointer"} | |
| 297 | + p={2} m={1} textAlign={"center"}> | |
| 298 | + <Heading size="sm" pb={3} color={"#a37f08"}>Pending Leads</Heading> | |
| 299 | + <Text fontWeight={600} color={"#a37f08"}><CountUpComponent targetNumber={findLeadStatus("pending")} /></Text> | |
| 300 | + </Box> | |
| 301 | + </GridItem> | |
| 302 | + | |
| 303 | + <GridItem colSpan={{ base: 12, md: 6 }}> | |
| 304 | + <Box backgroundColor={"#ffeeeb"} | |
| 305 | + borderRadius={"10px"} | |
| 306 | + cursor={"pointer"} | |
| 307 | + onClick={() => navigate('/lead', { state: 'sold' })} | |
| 308 | + p={2} m={1} textAlign={"center"}> | |
| 309 | + <Heading size="sm" pb={3} color={"#d6401d"}>Sold Leads </Heading> | |
| 310 | + <Text fontWeight={600} color={"#d6401d"}><CountUpComponent targetNumber={findLeadStatus("sold")} /></Text> | |
| 311 | + </Box> | |
| 312 | + </GridItem> | |
| 313 | + </Grid> | |
| 314 | + } | |
| 315 | + <Flex justifyContent={"center"} > | |
| 316 | + <PieChart leadData={allData?.leadData} /> | |
| 317 | + </Flex> | |
| 318 | + | |
| 319 | + </Card>} | |
| 320 | + | |
| 321 | + {taskView?.view && (tasksModule?.isActive) && <Card > | |
| 322 | + <Heading size="md" pb={3}>Task Statistics</Heading> | |
| 323 | + <Grid templateColumns="repeat(12, 1fr)" gap={2} mb={2}> | |
| 324 | + <GridItem colSpan={{ base: 12 }}> | |
| 325 | + <Box backgroundColor={"#ebf5ff"} | |
| 326 | + onClick={() => navigate('/task')} | |
| 327 | + borderRadius={"10px"} cursor={'pointer'} | |
| 328 | + p={2} m={1} textAlign={"center"}> | |
| 329 | + <Heading size="sm" pb={3} color={"#1f7eeb"}>Total Tasks </Heading> | |
| 330 | + <Text fontWeight={600} color={"#1f7eeb"}><CountUpComponent targetNumber={allData?.taskData?.length || 0} /></Text> | |
| 331 | + </Box> | |
| 332 | + </GridItem> | |
| 333 | + </Grid> | |
| 334 | + {taskStatus && taskStatus.length > 0 && taskStatus?.map((item, i) => ( | |
| 335 | + <Box my={1.5} key={i}> | |
| 336 | + {/* <Flex justifyContent={"space-between"} cursor={'pointer'} onClick={() => navigate('/task', { state: item.status })} alignItems={"center"} padding={4} backgroundColor={"#0b0b0b17"} borderRadius={"10px"}> */} | |
| 337 | + <Flex justifyContent={"space-between"} cursor={'pointer'} alignItems={"center"} padding={4} backgroundColor={"#0b0b0b17"} borderRadius={"10px"}> | |
| 338 | + <Flex alignItems={"center"}> | |
| 339 | + <Box height={"18px"} width={"18px"} lineHeight={"18px"} textAlign={"center"} border={`1px solid ${item.color}`} display={"flex"} justifyContent={"center"} alignItems={"center"} borderRadius={"50%"} margin={"0 auto"} > | |
| 340 | + <Box backgroundColor={`${item.color}`} height={"10px"} width={"10px"} borderRadius={"50%"}></Box> | |
| 341 | + </Box> | |
| 342 | + | |
| 343 | + <Text ps={2} fontWeight={"bold"} color={`${item.color}`}>{item.name}</Text> | |
| 344 | + | |
| 345 | + </Flex> | |
| 346 | + <Box fontWeight={"bold"} color={`${item.color}`}><CountUpComponent targetNumber={item?.length} /></Box> | |
| 347 | + </Flex> | |
| 348 | + </Box> | |
| 349 | + ))} | |
| 350 | + </Card>} | |
| 351 | + </SimpleGrid> | |
| 352 | + | |
| 353 | + </> | |
| 354 | + ); | |
| 355 | +} |
+46-0Client/src/views/admin/document/component/Delete.js
| @@ -0,0 +1,46 @@ | ||
| 1 | +import { Button, Modal, ModalBody, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalOverlay } from '@chakra-ui/react'; | |
| 2 | +import { deleteManyApi } from 'services/api'; | |
| 3 | + | |
| 4 | +const Delete = (props) => { | |
| 5 | + const handleDeleteClick = async () => { | |
| 6 | + if (props.method === 'one') { | |
| 7 | + props.deleteFile(props.id) | |
| 8 | + props.onClose(false) | |
| 9 | + } else if (props.method === 'many') { | |
| 10 | + try { | |
| 11 | + let response = await deleteManyApi(props.url, props.data) | |
| 12 | + if (response.status === 200) { | |
| 13 | + props.setSelectedValues([]) | |
| 14 | + props.onClose(false) | |
| 15 | + } | |
| 16 | + } catch (error) { | |
| 17 | + console.log(error) | |
| 18 | + } | |
| 19 | + } | |
| 20 | + }; | |
| 21 | + | |
| 22 | + const handleClose = () => { | |
| 23 | + props.onClose(false) | |
| 24 | + } | |
| 25 | + | |
| 26 | + return ( | |
| 27 | + <div> | |
| 28 | + <Modal onClose={props.onClose} isOpen={props.isOpen} isCentered> | |
| 29 | + <ModalOverlay /> | |
| 30 | + <ModalContent> | |
| 31 | + <ModalHeader>Delete Document{props.method === 'one' ? '' : 's'}</ModalHeader> | |
| 32 | + <ModalCloseButton /> | |
| 33 | + <ModalBody> | |
| 34 | + Are You Sure To Delete selected Document{props.method === 'one' ? '' : 's'} ? | |
| 35 | + </ModalBody> | |
| 36 | + <ModalFooter> | |
| 37 | + <Button size="sm" colorScheme="red" mr={2} onClick={handleDeleteClick}>Yes</Button> | |
| 38 | + <Button size="sm" variant="outline" onClick={handleClose}>No</Button> | |
| 39 | + </ModalFooter> | |
| 40 | + </ModalContent> | |
| 41 | + </Modal> | |
| 42 | + </div> | |
| 43 | + ) | |
| 44 | +} | |
| 45 | + | |
| 46 | +export default Delete |
+175-0Client/src/views/admin/document/component/LinkModel.js
| @@ -0,0 +1,175 @@ | ||
| 1 | +import { LinkIcon } from '@chakra-ui/icons'; | |
| 2 | +import { Button, Flex, FormLabel, Grid, GridItem, IconButton, Modal, ModalBody, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalOverlay, Radio, RadioGroup, Select, Stack, Text, useDisclosure } from '@chakra-ui/react'; | |
| 3 | +import { useFormik } from 'formik'; | |
| 4 | +import { useEffect, useState } from 'react'; | |
| 5 | +import { toast } from 'react-toastify'; | |
| 6 | +import { getApi, postApi } from 'services/api'; | |
| 7 | +import ContactModel from 'components/commonTableModel/ContactModel'; | |
| 8 | +import LeadModel from "components/commonTableModel/LeadModel"; | |
| 9 | +import { LiaMousePointerSolid } from 'react-icons/lia'; | |
| 10 | + | |
| 11 | +const Link = (props) => { | |
| 12 | + const { setLinkDocument } = props; | |
| 13 | + const [isLoding, setIsLoding] = useState(false) | |
| 14 | + const [data, setData] = useState([]) | |
| 15 | + const [contactModelOpen, setContactModel] = useState(false); | |
| 16 | + const [leadModelOpen, setLeadModel] = useState(false); | |
| 17 | + const user = JSON.parse(localStorage.getItem('user')) | |
| 18 | + | |
| 19 | + const initialValues = { | |
| 20 | + linkWith: '', | |
| 21 | + linkContact: '', | |
| 22 | + linkLead: '', | |
| 23 | + } | |
| 24 | + | |
| 25 | + const formik = useFormik({ | |
| 26 | + initialValues: initialValues, | |
| 27 | + onSubmit: (values, { resetForm }) => { | |
| 28 | + handleClick(); | |
| 29 | + resetForm(); | |
| 30 | + }, | |
| 31 | + }); | |
| 32 | + const { errors, touched, values, handleBlur, handleChange, handleSubmit, setFieldValue, resetForm } = formik | |
| 33 | + | |
| 34 | + const handleClick = async () => { | |
| 35 | + try { | |
| 36 | + setIsLoding(true) | |
| 37 | + let data = values.linkContact ? { linkContact: values.linkContact } : { linkLead: values.linkLead } | |
| 38 | + let response = await postApi(`api/document/link-document/${props.id}`, data) | |
| 39 | + if (response && response.status === 200) { | |
| 40 | + setLinkDocument((pre) => !pre) | |
| 41 | + props.onClose(); | |
| 42 | + } else { | |
| 43 | + toast.error(response.response.data?.message) | |
| 44 | + } | |
| 45 | + } catch (error) { | |
| 46 | + console.log(error) | |
| 47 | + } | |
| 48 | + finally { | |
| 49 | + setIsLoding(false) | |
| 50 | + } | |
| 51 | + } | |
| 52 | + | |
| 53 | + const handleClose = () => { | |
| 54 | + props.onClose(false) | |
| 55 | + } | |
| 56 | + | |
| 57 | + const fetchData = async () => { | |
| 58 | + if (values.linkWith === 'Contact') { | |
| 59 | + let result = await getApi(user.role === 'superAdmin' ? 'api/contact/' : `api/contact/?createBy=${user._id}`); | |
| 60 | + values.createBy = result?._id; | |
| 61 | + setData(prevData => [ | |
| 62 | + // ...prevData, | |
| 63 | + ...(result?.data?.map(item => ({ label: item.firstName + ' ' + item.lastName, value: item._id })) || []) | |
| 64 | + ]); | |
| 65 | + } else if (values.linkWith === 'lead') { | |
| 66 | + let result = await getApi(user.role === 'superAdmin' ? 'api/lead/' : `api/lead/?createBy=${user._id}`); | |
| 67 | + values.createBy = result?._id; | |
| 68 | + setData(prevData => [ | |
| 69 | + // ...prevData, | |
| 70 | + ...(result?.data?.map(item => ({ label: item.leadName, value: item._id })) || []) | |
| 71 | + ]); | |
| 72 | + } | |
| 73 | + } | |
| 74 | + | |
| 75 | + useEffect(() => { | |
| 76 | + fetchData() | |
| 77 | + }, [values.linkWith]); | |
| 78 | + | |
| 79 | + return ( | |
| 80 | + <> | |
| 81 | + | |
| 82 | + <Modal onClose={props.onClose} isOpen={props.isOpen} > | |
| 83 | + <ModalOverlay /> | |
| 84 | + <ModalContent> | |
| 85 | + <ModalHeader>Link With Document</ModalHeader> | |
| 86 | + <ModalCloseButton /> | |
| 87 | + <ModalBody> | |
| 88 | + | |
| 89 | + <Grid templateColumns="repeat(12, 1fr)" gap={3}> | |
| 90 | + | |
| 91 | + <GridItem colSpan={{ base: 12 }}> | |
| 92 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='8px'> | |
| 93 | + Link With | |
| 94 | + </FormLabel> | |
| 95 | + <RadioGroup onChange={(e) => { resetForm(); setFieldValue('linkWith', e) }} value={values.linkWith}> | |
| 96 | + <Stack direction='row'> | |
| 97 | + <Radio value='Contact'>Contact</Radio> | |
| 98 | + <Radio value='lead'>Lead</Radio> | |
| 99 | + </Stack> | |
| 100 | + </RadioGroup> | |
| 101 | + <Text mb='10px' color={'red'}> {errors.linkWith && touched.linkWith && errors.linkWith}</Text> | |
| 102 | + </GridItem> | |
| 103 | + | |
| 104 | + {values.linkWith === 'Contact' ? | |
| 105 | + <GridItem colSpan={{ base: 12 }}> | |
| 106 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='8px'> | |
| 107 | + Link Contact | |
| 108 | + </FormLabel> | |
| 109 | + | |
| 110 | + <Flex> | |
| 111 | + | |
| 112 | + <Select | |
| 113 | + value={values.linkContact} | |
| 114 | + name="linkContact" | |
| 115 | + onChange={handleChange} | |
| 116 | + mb={errors.linkContact && touched.linkContact ? undefined : '10px'} | |
| 117 | + fontWeight='500' | |
| 118 | + placeholder='Link Contact' | |
| 119 | + borderColor={errors.linkContact && touched.linkContact ? "red.300" : null} | |
| 120 | + > | |
| 121 | + {data?.map((item) => { | |
| 122 | + return <option value={item.value} key={item.value}>{values.linkWith === 'Contact' && `${item.label}`}</option> | |
| 123 | + })} | |
| 124 | + </Select> | |
| 125 | + <IconButton onClick={() => setContactModel(true)} ml={2} fontSize='25px' icon={<LiaMousePointerSolid />} /> | |
| 126 | + <Text mb='10px' color={'red'}> {errors.linkContact && touched.linkContact && errors.linkContact}</Text> | |
| 127 | + </Flex> | |
| 128 | + </GridItem> | |
| 129 | + : values.linkWith === 'lead' && | |
| 130 | + <GridItem colSpan={{ base: 12 }}> | |
| 131 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='8px'> | |
| 132 | + Link Lead | |
| 133 | + </FormLabel> | |
| 134 | + | |
| 135 | + <Flex> | |
| 136 | + | |
| 137 | + <Select | |
| 138 | + value={values.linkLead} | |
| 139 | + name="linkLead" | |
| 140 | + onChange={handleChange} | |
| 141 | + mb={errors.linkLead && touched.linkLead ? undefined : '10px'} | |
| 142 | + fontWeight='500' | |
| 143 | + placeholder='Link Lead' | |
| 144 | + borderColor={errors.linkLead && touched.linkLead ? "red.300" : null} | |
| 145 | + > | |
| 146 | + {data?.map((item) => { | |
| 147 | + return <option value={item.value} key={item.value}>{values.linkWith === 'lead' && `${item.label}`}</option> | |
| 148 | + })} | |
| 149 | + </Select> | |
| 150 | + <IconButton onClick={() => setContactModel(true)} ml={2} fontSize='25px' icon={<LiaMousePointerSolid />} /> | |
| 151 | + <Text mb='10px' color={'red'}> {errors.linkLead && touched.linkLead && errors.linkLead}</Text> | |
| 152 | + </Flex> | |
| 153 | + <Text mb='10px' color={'red'}> {errors.linkLead && touched.linkLead && errors.linkLead}</Text> | |
| 154 | + </GridItem> | |
| 155 | + | |
| 156 | + } | |
| 157 | + | |
| 158 | + </Grid> | |
| 159 | + | |
| 160 | + | |
| 161 | + </ModalBody> | |
| 162 | + <ModalFooter> | |
| 163 | + <Button size="sm" colorScheme='brand' rightIcon={<LinkIcon />} mr={2} onClick={handleClick}>Link</Button> | |
| 164 | + <Button size="sm" variant="outline" colorScheme='red' onClick={() => { handleClose(); resetForm() }}>Cancel</Button> | |
| 165 | + </ModalFooter> | |
| 166 | + </ModalContent> | |
| 167 | + </Modal > | |
| 168 | + <ContactModel data={data} isOpen={contactModelOpen} onClose={setContactModel} fieldName='linkContact' setFieldValue={setFieldValue} /> | |
| 169 | + {/* Lead Model */} | |
| 170 | + <LeadModel data={data} isOpen={leadModelOpen} onClose={setLeadModel} fieldName='linkLead' setFieldValue={setFieldValue} /> | |
| 171 | + </> | |
| 172 | + ) | |
| 173 | +} | |
| 174 | + | |
| 175 | +export default Link |
+69-0Client/src/views/admin/document/component/Upload.js
| @@ -0,0 +1,69 @@ | ||
| 1 | +// Chakra imports | |
| 2 | +import { | |
| 3 | + Box, Flex, | |
| 4 | + Grid, | |
| 5 | + GridItem, | |
| 6 | + Icon, | |
| 7 | + Text, | |
| 8 | + useColorModeValue | |
| 9 | +} from "@chakra-ui/react"; | |
| 10 | +// Assets | |
| 11 | +import Dropzone from "components/Dropzone"; | |
| 12 | +import { MdUpload } from "react-icons/md"; | |
| 13 | + | |
| 14 | +export default function Upload(props) { | |
| 15 | + const { used, total, count, ...rest } = props; | |
| 16 | + // Chakra Color Mode | |
| 17 | + const textColorPrimary = useColorModeValue("secondaryGray.900", "white"); | |
| 18 | + const brandColor = useColorModeValue("brand.500", "white"); | |
| 19 | + const textColorSecondary = "gray.400"; | |
| 20 | + return ( | |
| 21 | + <Grid templateColumns="repeat(12, 1fr)" mb={3} gap={2}> | |
| 22 | + <GridItem colSpan={{ base: 12, '2xl': 5 }} > | |
| 23 | + <Dropzone | |
| 24 | + w={{ base: "100%", "2xl": "240px" }} | |
| 25 | + me='36px' | |
| 26 | + minH={200} | |
| 27 | + height={'100%'} | |
| 28 | + onFileSelect={props.onFileSelect} | |
| 29 | + content={ | |
| 30 | + <Box> | |
| 31 | + <Icon as={MdUpload} w='70px' h='70px' color={brandColor} /> | |
| 32 | + <Flex justify='center' mx='auto' mb='12px'> | |
| 33 | + <Text fontSize='xl' fontWeight='700' color={brandColor}> | |
| 34 | + Upload Files | |
| 35 | + </Text> | |
| 36 | + </Flex> | |
| 37 | + <Text fontSize='sm' fontWeight='500' color='secondaryGray.500'> | |
| 38 | + {count > 0 && <Text fontSize='sm' fontWeight='500' color='secondaryGray.500'> | |
| 39 | + Selected Files : {count} | |
| 40 | + </Text>} | |
| 41 | + </Text> | |
| 42 | + </Box> | |
| 43 | + } | |
| 44 | + /> | |
| 45 | + </GridItem> | |
| 46 | + <GridItem colSpan={{ base: 12, '2xl': 7 }} > | |
| 47 | + <Flex direction='column' > | |
| 48 | + <Text | |
| 49 | + color={textColorPrimary} | |
| 50 | + fontWeight='bold' | |
| 51 | + textAlign='start' | |
| 52 | + fontSize='2xl' | |
| 53 | + mt={{ base: "20px", "2xl": "50px" }}> | |
| 54 | + Upload Files | |
| 55 | + </Text> | |
| 56 | + <Text | |
| 57 | + color={textColorSecondary} | |
| 58 | + fontSize='md' | |
| 59 | + my={{ base: "auto", "2xl": "10px" }} | |
| 60 | + mx='auto' | |
| 61 | + textAlign='start'> | |
| 62 | + Accepted File Types (Images, PDFs, Word docs, Powerpoint, Excel, ZIP, and video files - 15MB max) | |
| 63 | + </Text> | |
| 64 | + </Flex> | |
| 65 | + </GridItem> | |
| 66 | + </Grid> | |
| 67 | + ); | |
| 68 | +} | |
| 69 | + |
+189-0Client/src/views/admin/document/index.js
| @@ -0,0 +1,189 @@ | ||
| 1 | +import { Button, Flex, FormLabel, Grid, GridItem, Heading, Input, List, ListItem, Text, VStack, useDisclosure } from '@chakra-ui/react'; | |
| 2 | +import FolderTreeView from 'components/FolderTreeView/folderTreeView'; | |
| 3 | +import Card from 'components/card/Card'; | |
| 4 | +import { HSeparator } from 'components/separator/Separator'; | |
| 5 | +import Spinner from 'components/spinner/Spinner'; | |
| 6 | +import { constant } from 'constant'; | |
| 7 | +import { useFormik } from 'formik'; | |
| 8 | +import { useEffect, useState } from 'react'; | |
| 9 | +import { useNavigate } from 'react-router-dom'; | |
| 10 | +import { toast } from 'react-toastify'; | |
| 11 | +import { documentSchema } from 'schema'; | |
| 12 | +import { deleteApi, getApi } from 'services/api'; | |
| 13 | +import Upload from './component/Upload'; | |
| 14 | +import { postApi } from 'services/api'; | |
| 15 | + | |
| 16 | + | |
| 17 | +const Index = () => { | |
| 18 | + | |
| 19 | + const [data, setData] = useState([]) | |
| 20 | + const { isOpen, onOpen, onClose } = useDisclosure(); | |
| 21 | + const user = JSON.parse(localStorage.getItem("user")) | |
| 22 | + const [isLoding, setIsLoding] = useState(false) | |
| 23 | + const [linkDocument, setLinkDocument] = useState(false) | |
| 24 | + | |
| 25 | + const fetchData = async () => { | |
| 26 | + setIsLoding(true) | |
| 27 | + let result = await getApi(user.role === 'superAdmin' ? 'api/document' : `api/document?createBy=${user._id}`); | |
| 28 | + setData(result?.data); | |
| 29 | + setIsLoding(false) | |
| 30 | + } | |
| 31 | + | |
| 32 | + const initialValues = { | |
| 33 | + folderName: '', | |
| 34 | + files: '', | |
| 35 | + filename: '', | |
| 36 | + createBy: user._id | |
| 37 | + }; | |
| 38 | + | |
| 39 | + const formik = useFormik({ | |
| 40 | + initialValues: initialValues, | |
| 41 | + validationSchema: documentSchema, | |
| 42 | + onSubmit: (values, { resetForm }) => { | |
| 43 | + AddData(); | |
| 44 | + }, | |
| 45 | + }); | |
| 46 | + const { errors, touched, values, handleBlur, handleChange, handleSubmit, setFieldValue, resetForm } = formik | |
| 47 | + const navigate = useNavigate() | |
| 48 | + | |
| 49 | + const download = async (data) => { | |
| 50 | + if (data) { | |
| 51 | + let result = await getApi(`api/document/download/`, data) | |
| 52 | + if (result && result.status === 200) { | |
| 53 | + window.open(`${constant.baseUrl}api/document/download/${data}`) | |
| 54 | + toast.success('File Download successful') | |
| 55 | + } else if (result && result.response.status === 404) { | |
| 56 | + toast.error('File Not Found') | |
| 57 | + } | |
| 58 | + } | |
| 59 | + } | |
| 60 | + const deleteFile = async (data) => { | |
| 61 | + if (data) { | |
| 62 | + let result = await deleteApi(`api/document/delete/`, data) | |
| 63 | + if (result && result.status === 200) { | |
| 64 | + fetchData() | |
| 65 | + } | |
| 66 | + } | |
| 67 | + } | |
| 68 | + | |
| 69 | + const AddData = async () => { | |
| 70 | + try { | |
| 71 | + setIsLoding(true) | |
| 72 | + const formData = new FormData(); | |
| 73 | + formData?.append('folderName', values.folderName); | |
| 74 | + formData?.append('createBy', values.createBy); | |
| 75 | + formData?.append('filename', values.filename); | |
| 76 | + | |
| 77 | + // Append files to the formData | |
| 78 | + values.files.forEach((file) => { | |
| 79 | + formData?.append('files', file); | |
| 80 | + }); | |
| 81 | + | |
| 82 | + let response = await postApi('api/document/add', formData); | |
| 83 | + if (response && response.status === 200) { | |
| 84 | + fetchData(); | |
| 85 | + formik.resetForm(); | |
| 86 | + } | |
| 87 | + } catch (e) { | |
| 88 | + console.log(e); | |
| 89 | + } | |
| 90 | + finally { | |
| 91 | + setIsLoding(false) | |
| 92 | + } | |
| 93 | + }; | |
| 94 | + | |
| 95 | + useEffect(() => { | |
| 96 | + fetchData() | |
| 97 | + }, [linkDocument, handleSubmit]) | |
| 98 | + | |
| 99 | + return ( | |
| 100 | + <div> | |
| 101 | + <Grid templateColumns="repeat(12, 1fr)" mb={3} gap={2}> | |
| 102 | + <GridItem colSpan={{ base: 12, md: 7 }} > | |
| 103 | + <Card minH={'20em'}> | |
| 104 | + <Heading size="lg" mb={4} > | |
| 105 | + File Explorer | |
| 106 | + </Heading> | |
| 107 | + <HSeparator /> | |
| 108 | + <VStack mt={4} alignItems="flex-start"> | |
| 109 | + {isLoding ? | |
| 110 | + <Flex justifyContent={'center'} alignItems={'center'} width="100%" > | |
| 111 | + <Spinner /> | |
| 112 | + </Flex> | |
| 113 | + : data?.length === 0 ? ( | |
| 114 | + <Text textAlign={'center'} width="100%" fontSize="sm" fontWeight="700"> | |
| 115 | + -- No Document Found -- | |
| 116 | + </Text> | |
| 117 | + ) : data?.map((item) => ( | |
| 118 | + <FolderTreeView name={item.folderName} item={item}> | |
| 119 | + {item?.files?.map((file) => ( | |
| 120 | + <FolderTreeView download={download} setLinkDocument={setLinkDocument} deleteFile={deleteFile} data={file} name={file.fileName} isFile /> | |
| 121 | + ))} | |
| 122 | + </FolderTreeView> | |
| 123 | + ))} | |
| 124 | + </VStack> | |
| 125 | + </Card> | |
| 126 | + </GridItem> | |
| 127 | + <GridItem colSpan={{ base: 12, md: 5 }} colStart={{ base: 1, md: 8 }} > | |
| 128 | + <Card > | |
| 129 | + <GridItem colSpan={{ base: 12 }} > | |
| 130 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='8px'> | |
| 131 | + Folder Name<Text color={"red"}>*</Text> | |
| 132 | + </FormLabel> | |
| 133 | + <Input | |
| 134 | + onFocus={onOpen} | |
| 135 | + fontSize='sm' | |
| 136 | + onChange={handleChange} | |
| 137 | + onBlur={() => setTimeout(onClose, 200)} | |
| 138 | + value={values.folderName} | |
| 139 | + name="folderName" | |
| 140 | + placeholder='Enter Folder Name' | |
| 141 | + fontWeight='500' | |
| 142 | + borderColor={errors?.folderName && touched?.folderName ? "red.300" : null} | |
| 143 | + /> | |
| 144 | + {isOpen && values?.folderName && data?.filter((option) => option?.folderName?.toLowerCase()?.includes(values?.folderName.toLowerCase())).length > 0 && ( | |
| 145 | + <List position={'relative'} border={'1px solid'} bg={'gray.100'} width={'100%'} borderRadius={'0px 0px 20px 20px'} lineHeight={1} > | |
| 146 | + {data?.filter((option) => option?.folderName?.toLowerCase()?.includes(values?.folderName.toLowerCase())).map((option, index) => ( | |
| 147 | + <ListItem p={3} borderBottom={'2px solid #efefef'} sx={{ '&:last-child': { borderBottom: 'none' } }} key={option?._id} cursor={'pointer'} | |
| 148 | + onClick={() => { | |
| 149 | + setFieldValue('folderName', option?.folderName) | |
| 150 | + }} | |
| 151 | + > | |
| 152 | + {option?.folderName} | |
| 153 | + </ListItem> | |
| 154 | + ))} | |
| 155 | + </List> | |
| 156 | + )} | |
| 157 | + <Text mb='10px' color={'red'}> {errors.folderName && touched.folderName && errors.folderName}</Text> | |
| 158 | + </GridItem> | |
| 159 | + <GridItem colSpan={{ base: 12 }} > | |
| 160 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='8px'> | |
| 161 | + File Name | |
| 162 | + </FormLabel> | |
| 163 | + <Input | |
| 164 | + fontSize='sm' | |
| 165 | + onChange={handleChange} | |
| 166 | + onBlur={() => setTimeout(onClose, 200)} | |
| 167 | + value={values.filename} | |
| 168 | + name="filename" | |
| 169 | + placeholder='Enter File Name' | |
| 170 | + fontWeight='500' | |
| 171 | + borderColor={errors?.filename && touched?.filename ? "red.300" : null} | |
| 172 | + /> | |
| 173 | + <Text mb='10px' color={'red'}> {errors.filename && touched.filename && errors.filename}</Text> | |
| 174 | + </GridItem> | |
| 175 | + <Upload count={values.files.length} onFileSelect={(file) => setFieldValue('files', file)} /> | |
| 176 | + <Button size="sm" disabled={isLoding ? true : false} onClick={handleSubmit} variant='brand' fontWeight='500'> | |
| 177 | + {isLoding ? <Spinner /> : 'Publish now'} | |
| 178 | + </Button> | |
| 179 | + </Card> | |
| 180 | + </GridItem> | |
| 181 | + | |
| 182 | + </Grid> | |
| 183 | + | |
| 184 | + | |
| 185 | + </div> | |
| 186 | + ) | |
| 187 | +} | |
| 188 | + | |
| 189 | +export default Index |
+166-0Client/src/views/admin/dynamicPage/DynamicPageview.js
| @@ -0,0 +1,166 @@ | ||
| 1 | +import { AddIcon, ChevronDownIcon, DeleteIcon, EditIcon } from "@chakra-ui/icons"; | |
| 2 | +import { | |
| 3 | + Box, Button, Flex, Grid, GridItem, Menu, MenuButton, MenuDivider, MenuItem, MenuList, | |
| 4 | + | |
| 5 | + useColorModeValue, | |
| 6 | + useDisclosure, | |
| 7 | +} from "@chakra-ui/react"; | |
| 8 | +import Card from "components/card/Card"; | |
| 9 | +import Spinner from "components/spinner/Spinner"; | |
| 10 | +import { constant } from "constant"; | |
| 11 | +import { useEffect, useState } from "react"; | |
| 12 | +import { IoIosArrowBack } from "react-icons/io"; | |
| 13 | +import { Link, useNavigate, useParams } from "react-router-dom"; | |
| 14 | +import { toast } from "react-toastify"; | |
| 15 | +import { getApi } from "services/api"; | |
| 16 | +import { HasAccess } from "../../../redux/accessUtils"; | |
| 17 | +import CustomView from "utils/customView"; | |
| 18 | +import { useLocation } from 'react-router-dom'; | |
| 19 | +import { FaFilePdf } from "react-icons/fa"; | |
| 20 | +import html2pdf from "html2pdf.js"; | |
| 21 | +import moment from "moment"; | |
| 22 | + | |
| 23 | +const View = () => { | |
| 24 | + | |
| 25 | + const param = useParams() | |
| 26 | + | |
| 27 | + const user = JSON.parse(localStorage.getItem("user")); | |
| 28 | + | |
| 29 | + const buttonbg = useColorModeValue("gray.200", "white"); | |
| 30 | + const textColor = useColorModeValue("gray.500", "white"); | |
| 31 | + | |
| 32 | + const [data, setData] = useState() | |
| 33 | + const { isOpen, onOpen, onClose } = useDisclosure() | |
| 34 | + const [edit, setEdit] = useState(false); | |
| 35 | + const [deleteModel, setDelete] = useState(false); | |
| 36 | + const [isLoding, setIsLoding] = useState(false) | |
| 37 | + const [action, setAction] = useState(false) | |
| 38 | + const [leadData, setLeadData] = useState([]) | |
| 39 | + const location = useLocation() | |
| 40 | + const navigate = useNavigate() | |
| 41 | + const module = location.state.module; | |
| 42 | + const size = "lg"; | |
| 43 | + | |
| 44 | + const pathName = (name) => { | |
| 45 | + return `/${name.toLowerCase().replace(/ /g, '-')}`; | |
| 46 | + } | |
| 47 | + | |
| 48 | + const [permission, taskPermission, meetingPermission, callAccess, emailAccess, taskAccess, meetingAccess] = HasAccess(['Leads', 'Tasks', 'Meetings', 'Calls', 'Emails', 'Tasks', 'Meetings']); | |
| 49 | + | |
| 50 | + const download = async (data) => { | |
| 51 | + if (data) { | |
| 52 | + let result = await getApi(`api/document/download/`, data) | |
| 53 | + if (result && result.status === 200) { | |
| 54 | + window.open(`${constant.baseUrl}api/document/download/${data}`) | |
| 55 | + toast.success('file Download successful') | |
| 56 | + } else if (result && result.response.status === 404) { | |
| 57 | + toast.error('file Not Found') | |
| 58 | + } | |
| 59 | + } | |
| 60 | + } | |
| 61 | + | |
| 62 | + const fetchData = async () => { | |
| 63 | + if (param.id) { | |
| 64 | + try { | |
| 65 | + setIsLoding(true) | |
| 66 | + let response = await getApi(`api/form/view/${param.id}?moduleId=${module._id}`) | |
| 67 | + setData(response?.data?.data); | |
| 68 | + } catch (e) { | |
| 69 | + console.error(e) | |
| 70 | + } finally { | |
| 71 | + setIsLoding(false) | |
| 72 | + } | |
| 73 | + } | |
| 74 | + } | |
| 75 | + useEffect(() => { | |
| 76 | + fetchData() | |
| 77 | + }, [action]) | |
| 78 | + // }, [edit, addEmailHistory, addPhoneCall]) | |
| 79 | + | |
| 80 | + function toCamelCase(text) { | |
| 81 | + return text?.replace(/([a-z])([A-Z])/g, '$1 $2'); | |
| 82 | + } | |
| 83 | + const generatePDF = () => { | |
| 84 | + const element = document.getElementById("reports"); | |
| 85 | + if (element) { | |
| 86 | + element.style.display = 'block'; | |
| 87 | + element.style.width = '100%'; // Adjust width for mobile | |
| 88 | + element.style.height = 'auto'; | |
| 89 | + html2pdf() | |
| 90 | + .from(element) | |
| 91 | + .set({ | |
| 92 | + margin: [0, 0, 0, 0], | |
| 93 | + filename: `${module.moduleName}_Details_${moment().format("DD-MM-YYYY")}.pdf`, | |
| 94 | + image: { type: "jpeg", quality: 0.98 }, | |
| 95 | + html2canvas: { scale: 2, useCORS: true, allowTaint: true }, | |
| 96 | + jsPDF: { unit: "in", format: "letter", orientation: "portrait" }, | |
| 97 | + }) | |
| 98 | + .save().then(() => { | |
| 99 | + element.style.display = ''; | |
| 100 | + }) | |
| 101 | + // }, 500); | |
| 102 | + } else { | |
| 103 | + console.error("Element with ID 'reports' not found."); | |
| 104 | + } | |
| 105 | + }; | |
| 106 | + const fetchCustomData = async () => { | |
| 107 | + const response = await getApi('api/custom-field?moduleName=Leads') | |
| 108 | + setLeadData(response.data) | |
| 109 | + } | |
| 110 | + | |
| 111 | + useEffect(() => { | |
| 112 | + if (fetchCustomData) fetchCustomData() | |
| 113 | + }, [action]) | |
| 114 | + | |
| 115 | + return ( | |
| 116 | + <> | |
| 117 | + {isLoding ? | |
| 118 | + <Flex justifyContent={'center'} alignItems={'center'} width="100%" > | |
| 119 | + <Spinner /> | |
| 120 | + </Flex> : | |
| 121 | + <> | |
| 122 | + <GridItem colSpan={{ base: 12, md: 6 }} mt={{ sm: "3px", md: "5px" }} > | |
| 123 | + <Flex justifyContent={"right"}> | |
| 124 | + <Menu> | |
| 125 | + <MenuButton size="sm" variant="outline" colorScheme='blackAlpha' mr={2.5} as={Button} rightIcon={<ChevronDownIcon />}> | |
| 126 | + Actions | |
| 127 | + </MenuButton> | |
| 128 | + <MenuDivider /> | |
| 129 | + <MenuList minWidth={2}> | |
| 130 | + <MenuItem color={'blue'} onClick={() => onOpen()} alignItems={"start"} icon={<AddIcon />}>Add</MenuItem> | |
| 131 | + <MenuItem onClick={() => setEdit(true)} alignItems={"start"} icon={<EditIcon />}>Edit</MenuItem> | |
| 132 | + <MenuItem onClick={generatePDF} alignItems={"start"} icon={<FaFilePdf />} display={"flex"} style={{ alignItems: "center" }}>Print as PDF</MenuItem > | |
| 133 | + <> | |
| 134 | + <MenuDivider /> | |
| 135 | + <MenuItem alignItems={"start"} color={'red'} onClick={() => setDelete(true)} icon={<DeleteIcon />}>Delete</MenuItem> | |
| 136 | + </> | |
| 137 | + </MenuList> | |
| 138 | + </Menu> | |
| 139 | + <Link to={pathName(module.moduleName)}> | |
| 140 | + <Button leftIcon={<IoIosArrowBack />} size='sm' variant="brand"> | |
| 141 | + Back | |
| 142 | + </Button> | |
| 143 | + </Link> | |
| 144 | + </Flex> | |
| 145 | + </GridItem> | |
| 146 | + <Box style={{ margin: "10px 0" }}> | |
| 147 | + <CustomView data={module} fieldData={data} id="reports" /> | |
| 148 | + </Box> | |
| 149 | + <Card mt={3}> | |
| 150 | + <Grid templateColumns="repeat(6, 1fr)" gap={1}> | |
| 151 | + <GridItem colStart={6} > | |
| 152 | + <Flex justifyContent={"right"}> | |
| 153 | + <Button size='sm' onClick={() => setEdit(true)} leftIcon={<EditIcon />} mr={2.5} variant="outline" colorScheme="green">Edit</Button> | |
| 154 | + <Button size='sm' style={{ background: 'red.800' }} onClick={() => setDelete(true)} leftIcon={<DeleteIcon />} colorScheme="red" >Delete</Button> | |
| 155 | + </Flex> | |
| 156 | + </GridItem> | |
| 157 | + </Grid> | |
| 158 | + </Card> | |
| 159 | + | |
| 160 | + </> | |
| 161 | + } | |
| 162 | + </> | |
| 163 | + ); | |
| 164 | +}; | |
| 165 | + | |
| 166 | +export default View; |
+126-0Client/src/views/admin/dynamicPage/Edit.js
| @@ -0,0 +1,126 @@ | ||
| 1 | +import { CloseIcon } from '@chakra-ui/icons'; | |
| 2 | +import { Button, Drawer, DrawerBody, DrawerContent, DrawerFooter, DrawerHeader, DrawerOverlay, Flex, IconButton} from '@chakra-ui/react'; | |
| 3 | +import Spinner from 'components/spinner/Spinner'; | |
| 4 | +import { useFormik } from 'formik'; | |
| 5 | +import { useEffect, useState } from 'react'; | |
| 6 | +import { useParams } from 'react-router-dom'; | |
| 7 | +import { putApi } from 'services/api'; | |
| 8 | +import { getApi } from 'services/api'; | |
| 9 | +import { generateValidationSchema } from '../../../utils'; | |
| 10 | +import CustomForm from '../../../utils/customForm'; | |
| 11 | +import * as yup from 'yup' | |
| 12 | + | |
| 13 | +const Edit = (props) => { | |
| 14 | + const [isLoding, setIsLoding] = useState(false); | |
| 15 | + const initialFieldValues = Object.fromEntries( | |
| 16 | + (props?.moduleData?.fields || []).map(field => [field?.name, '']) | |
| 17 | + ); | |
| 18 | + | |
| 19 | + const [initialValues, setInitialValues] = useState({ | |
| 20 | + ...initialFieldValues, | |
| 21 | + createBy: JSON.parse(localStorage.getItem('user'))._id | |
| 22 | + }) | |
| 23 | + const param = useParams() | |
| 24 | + const formik = useFormik({ | |
| 25 | + initialValues: initialValues, | |
| 26 | + enableReinitialize: true, | |
| 27 | + validationSchema: yup.object().shape(generateValidationSchema(props?.moduleData?.fields)), | |
| 28 | + onSubmit: (values, { resetForm }) => { | |
| 29 | + EditData(); | |
| 30 | + }, | |
| 31 | + }); | |
| 32 | + | |
| 33 | + const { errors, touched, values, handleBlur, handleChange, handleSubmit, setFieldValue, } = formik | |
| 34 | + | |
| 35 | + const EditData = async () => { | |
| 36 | + try { | |
| 37 | + setIsLoding(true) | |
| 38 | + let response = await putApi(`api/form/edit/${props?.selectedId || param.id}`, { ...values, moduleId: props.moduleId }) | |
| 39 | + if (response.status === 200) { | |
| 40 | + props.onClose(); | |
| 41 | + props.setAction((pre) => !pre) | |
| 42 | + } | |
| 43 | + } catch (e) { | |
| 44 | + console.log(e); | |
| 45 | + } | |
| 46 | + finally { | |
| 47 | + setIsLoding(false) | |
| 48 | + } | |
| 49 | + }; | |
| 50 | + | |
| 51 | + const handleClose = () => { | |
| 52 | + props.onClose(false) | |
| 53 | + props.setSelectedId && props?.setSelectedId() | |
| 54 | + formik.resetForm(); | |
| 55 | + } | |
| 56 | + let response; | |
| 57 | + const fetchData = async () => { | |
| 58 | + if (props?.selectedId || param.id) { | |
| 59 | + try { | |
| 60 | + setIsLoding(true) | |
| 61 | + response = await getApi(`api/form/view/${props?.selectedId || param.id}?moduleId=${props.moduleId}`) | |
| 62 | + let editData = response?.data?.data; | |
| 63 | + setInitialValues((prev) => ({ ...prev, ...editData })); | |
| 64 | + | |
| 65 | + } catch (e) { | |
| 66 | + console.error(e) | |
| 67 | + } finally { | |
| 68 | + setIsLoding(false) | |
| 69 | + } | |
| 70 | + } | |
| 71 | + } | |
| 72 | + | |
| 73 | + useEffect(() => { | |
| 74 | + fetchData() | |
| 75 | + }, [props?.selectedId]) | |
| 76 | + | |
| 77 | + return ( | |
| 78 | + <div> | |
| 79 | + <Drawer isOpen={props.isOpen} size={props.size}> | |
| 80 | + <DrawerOverlay /> | |
| 81 | + <DrawerContent> | |
| 82 | + <DrawerHeader alignItems={"center"} justifyContent='space-between' display='flex' > | |
| 83 | + Edit {props.title} | |
| 84 | + <IconButton onClick={handleClose} icon={<CloseIcon />} /> | |
| 85 | + </DrawerHeader> | |
| 86 | + <DrawerBody> | |
| 87 | + | |
| 88 | + {isLoding ? | |
| 89 | + <Flex justifyContent={'center'} alignItems={'center'} width="100%" > | |
| 90 | + <Spinner /> | |
| 91 | + </Flex> | |
| 92 | + : | |
| 93 | + <CustomForm moduleData={props.moduleData} values={values} setFieldValue={setFieldValue} handleChange={handleChange} handleBlur={handleBlur} errors={errors} touched={touched} /> | |
| 94 | + | |
| 95 | + } | |
| 96 | + </DrawerBody> | |
| 97 | + <DrawerFooter> | |
| 98 | + <Button | |
| 99 | + sx={{ textTransform: "capitalize" }} | |
| 100 | + variant="brand" size="sm" | |
| 101 | + type="submit" | |
| 102 | + disabled={isLoding ? true : false} | |
| 103 | + onClick={handleSubmit} | |
| 104 | + > | |
| 105 | + {isLoding ? <Spinner /> : 'Update'} | |
| 106 | + </Button> | |
| 107 | + <Button | |
| 108 | + variant="outline" | |
| 109 | + colorScheme='red' size="sm" | |
| 110 | + sx={{ | |
| 111 | + marginLeft: 2, | |
| 112 | + textTransform: "capitalize", | |
| 113 | + }} | |
| 114 | + onClick={handleClose} | |
| 115 | + > | |
| 116 | + Close | |
| 117 | + </Button> | |
| 118 | + </DrawerFooter> | |
| 119 | + | |
| 120 | + </DrawerContent> | |
| 121 | + </Drawer> | |
| 122 | + </div> | |
| 123 | + ) | |
| 124 | +} | |
| 125 | + | |
| 126 | +export default Edit |
+89-0Client/src/views/admin/dynamicPage/add.js
| @@ -0,0 +1,89 @@ | ||
| 1 | +import { CloseIcon } from '@chakra-ui/icons'; | |
| 2 | +import { Button, Drawer, DrawerBody, DrawerContent, DrawerFooter, DrawerHeader, DrawerOverlay, IconButton } from '@chakra-ui/react'; | |
| 3 | +import Spinner from 'components/spinner/Spinner'; | |
| 4 | +import { useFormik } from 'formik'; | |
| 5 | +import { useState } from 'react'; | |
| 6 | +import { postApi } from 'services/api'; | |
| 7 | +import { generateValidationSchema } from 'utils'; | |
| 8 | +import CustomForm from 'utils/customForm'; | |
| 9 | +import * as yup from 'yup' | |
| 10 | + | |
| 11 | +const Add = (props) => { | |
| 12 | + const [isLoding, setIsLoding] = useState(false) | |
| 13 | + | |
| 14 | + const initialFieldValues = Object.fromEntries( | |
| 15 | + (props?.moduleData?.fields || []).map(field => [field?.name, '']) | |
| 16 | + ); | |
| 17 | + | |
| 18 | + const initialValues = { | |
| 19 | + ...initialFieldValues, | |
| 20 | + createBy: JSON.parse(localStorage.getItem('user'))._id | |
| 21 | + }; | |
| 22 | + | |
| 23 | + const formik = useFormik({ | |
| 24 | + initialValues: initialValues, | |
| 25 | + validationSchema: yup.object().shape(generateValidationSchema(props?.moduleData?.fields)), | |
| 26 | + onSubmit: (values, { resetForm }) => { | |
| 27 | + AddData(); | |
| 28 | + }, | |
| 29 | + }); | |
| 30 | + | |
| 31 | + const { errors, touched, values, handleBlur, handleChange, handleSubmit, setFieldValue, } = formik | |
| 32 | + | |
| 33 | + const AddData = async () => { | |
| 34 | + try { | |
| 35 | + setIsLoding(true) | |
| 36 | + let response = await postApi('api/form/add', { ...values, moduleId: props?.moduleData?._id }) | |
| 37 | + if (response.status === 200) { | |
| 38 | + props.onClose(); | |
| 39 | + formik.resetForm(); | |
| 40 | + props.setAction((pre) => !pre) | |
| 41 | + } | |
| 42 | + } catch (e) { | |
| 43 | + console.log(e); | |
| 44 | + } | |
| 45 | + finally { | |
| 46 | + setIsLoding(false) | |
| 47 | + } | |
| 48 | + }; | |
| 49 | + | |
| 50 | + const handleCancel = () => { | |
| 51 | + formik.resetForm(); | |
| 52 | + props.onClose() | |
| 53 | + } | |
| 54 | + | |
| 55 | + return ( | |
| 56 | + <div> | |
| 57 | + <Drawer isOpen={props.isOpen} size={props.size}> | |
| 58 | + <DrawerOverlay /> | |
| 59 | + <DrawerContent> | |
| 60 | + <DrawerHeader alignItems={"center"} justifyContent='space-between' display='flex' > | |
| 61 | + Add {props.title} | |
| 62 | + <IconButton onClick={props.onClose} icon={<CloseIcon />} /> | |
| 63 | + </DrawerHeader> | |
| 64 | + <DrawerBody> | |
| 65 | + <CustomForm moduleData={props.moduleData} values={values} setFieldValue={setFieldValue} handleChange={handleChange} handleBlur={handleBlur} errors={errors} touched={touched} /> | |
| 66 | + </DrawerBody> | |
| 67 | + <DrawerFooter> | |
| 68 | + <Button sx={{ textTransform: "capitalize" }} size="sm" disabled={isLoding ? true : false} variant="brand" type="submit" onClick={handleSubmit} > | |
| 69 | + {isLoding ? <Spinner /> : 'Save'} | |
| 70 | + </Button> | |
| 71 | + <Button | |
| 72 | + variant="outline" | |
| 73 | + colorScheme='red' size="sm" | |
| 74 | + sx={{ | |
| 75 | + marginLeft: 2, | |
| 76 | + textTransform: "capitalize", | |
| 77 | + }} | |
| 78 | + onClick={handleCancel} | |
| 79 | + > | |
| 80 | + Close | |
| 81 | + </Button> | |
| 82 | + </DrawerFooter> | |
| 83 | + </DrawerContent> | |
| 84 | + </Drawer> | |
| 85 | + </div> | |
| 86 | + ) | |
| 87 | +} | |
| 88 | + | |
| 89 | +export default Add |
+175-0Client/src/views/admin/dynamicPage/index.js
| @@ -0,0 +1,175 @@ | ||
| 1 | +import { useEffect, useState } from 'react'; | |
| 2 | +import { useNavigate } from "react-router-dom"; | |
| 3 | +import { HasAccess } from "../../../redux/accessUtils"; | |
| 4 | +import { Grid, GridItem, Text, Menu, MenuButton, MenuItem, MenuList, useDisclosure, Flex } from '@chakra-ui/react'; | |
| 5 | +import { DeleteIcon, ViewIcon, EditIcon } from "@chakra-ui/icons"; | |
| 6 | +import { CiMenuKebab } from "react-icons/ci"; | |
| 7 | +import { getApi } from "services/api"; | |
| 8 | +import CommonCheckTable from '../../../components/reactTable/checktable'; | |
| 9 | +import Add from './add'; | |
| 10 | +import Edit from './Edit'; | |
| 11 | +import Spinner from 'components/spinner/Spinner'; | |
| 12 | +import CommonDeleteModel from 'components/commonDeleteModel'; | |
| 13 | +import { deleteManyApi } from 'services/api'; | |
| 14 | + | |
| 15 | + | |
| 16 | +const Index = () => { | |
| 17 | + const pathName = window.location.pathname.split('/') | |
| 18 | + const title = pathName[1]; | |
| 19 | + const size = "lg"; | |
| 20 | + const user = JSON.parse(localStorage.getItem("user")); | |
| 21 | + const navigate = useNavigate(); | |
| 22 | + const [permission] = HasAccess([title]); | |
| 23 | + const [isLoding, setIsLoding] = useState(false); | |
| 24 | + const [data, setData] = useState([]); | |
| 25 | + const [tableColumns, setTableColumns] = useState([]); | |
| 26 | + const [columns, setColumns] = useState([]); | |
| 27 | + // const [dataColumn, setDataColumn] = useState([]); | |
| 28 | + const [selectedColumns, setSelectedColumns] = useState([]); | |
| 29 | + const [action, setAction] = useState(false); | |
| 30 | + const { isOpen, onOpen, onClose } = useDisclosure(); | |
| 31 | + const [moduleData, setModuleData] = useState({}); | |
| 32 | + const [edit, setEdit] = useState(false); | |
| 33 | + const [deleteModel, setDelete] = useState(false); | |
| 34 | + const [selectedId, setSelectedId] = useState(); | |
| 35 | + const [selectedValues, setSelectedValues] = useState([]); | |
| 36 | + const [isImport, setIsImport] = useState(false); | |
| 37 | + | |
| 38 | + const path = (name) => { | |
| 39 | + return `${name.toLowerCase().replace(/ /g, '-')}`; | |
| 40 | + } | |
| 41 | + | |
| 42 | + const fetchData = async (id) => { | |
| 43 | + setIsLoding(true); | |
| 44 | + let result = await getApi(`api/form?moduleId=${id}`); | |
| 45 | + if (result.status === 200) { | |
| 46 | + setData(result?.data?.data); | |
| 47 | + setIsLoding(false); | |
| 48 | + } | |
| 49 | + }; | |
| 50 | + | |
| 51 | + const fetchCustomDataFields = async () => { | |
| 52 | + setIsLoding(true); | |
| 53 | + const result = await getApi(`api/custom-field`); | |
| 54 | + const singaleData = result?.data?.find((item) => path(item?.moduleName) === title) | |
| 55 | + setModuleData(singaleData); | |
| 56 | + fetchData(singaleData?._id); | |
| 57 | + | |
| 58 | + const actionHeader = { | |
| 59 | + Header: "Action", isSortable: false, center: true, | |
| 60 | + cell: ({ row }) => ( | |
| 61 | + <Text fontSize="md" fontWeight="900" textAlign={"center"} > | |
| 62 | + <Menu isLazy > | |
| 63 | + <MenuButton><CiMenuKebab /></MenuButton> | |
| 64 | + <MenuList minW={'fit-content'} transform={"translate(1520px, 173px);"}> | |
| 65 | + {permission?.update && | |
| 66 | + <MenuItem py={2.5} icon={<EditIcon fontSize={15} mb={1} />} onClick={() => { setEdit(true); setSelectedId(row?.values?._id); }}>Edit</MenuItem>} | |
| 67 | + {permission?.view && | |
| 68 | + <MenuItem py={2.5} color={'green'} icon={<ViewIcon mb={1} fontSize={15} />} onClick={() => { navigate(`/${title}/${row?.values?._id}`, { state: { module: singaleData } }) }}>View</MenuItem>} | |
| 69 | + {permission?.delete && | |
| 70 | + <MenuItem py={2.5} color={'red'} icon={<DeleteIcon fontSize={15} mb={1} />} onClick={() => { setDelete(true); setSelectedValues([row?.values?._id]); }}>Delete</MenuItem>} | |
| 71 | + </MenuList> | |
| 72 | + </Menu> | |
| 73 | + </Text> | |
| 74 | + ) | |
| 75 | + }; | |
| 76 | + | |
| 77 | + const tempTableColumns = [ | |
| 78 | + { Header: "#", accessor: "_id", isSortable: false, width: 10 }, | |
| 79 | + ...(singaleData?.fields?.filter((field) => field?.isTableField === true && field?.isView)?.map((field) => ({ | |
| 80 | + Header: field?.label, | |
| 81 | + accessor: field?.name, | |
| 82 | + cell: (cell) => ( | |
| 83 | + <div className="selectOpt"> | |
| 84 | + <Text | |
| 85 | + onClick={() => { | |
| 86 | + navigate(`/${title}/${cell?.row?.original?._id}`, { state: { module: singaleData } }); | |
| 87 | + }} | |
| 88 | + me="10px" | |
| 89 | + sx={{ '&:hover': { color: 'blue.500', textDecoration: 'underline' }, cursor: 'pointer' }} | |
| 90 | + color='brand.600' | |
| 91 | + fontSize="sm" | |
| 92 | + fontWeight="700" | |
| 93 | + > | |
| 94 | + {cell?.value || "-"} | |
| 95 | + </Text> | |
| 96 | + </div> | |
| 97 | + ), | |
| 98 | + })) || []), | |
| 99 | + ...(singaleData?.fields?.filter((field) => field?.isTableField === true && !field?.isView)?.map((field) => ({ Header: field?.label, accessor: field?.name })) || []), | |
| 100 | + ...(permission?.update || permission?.view || permission?.delete ? [actionHeader] : []), | |
| 101 | + ]; | |
| 102 | + | |
| 103 | + setColumns(tempTableColumns); | |
| 104 | + setIsLoding(false); | |
| 105 | + } | |
| 106 | + const handleDelete = async (id, moduleId) => { | |
| 107 | + try { | |
| 108 | + setIsLoding(true) | |
| 109 | + const payload = { | |
| 110 | + moduleId: moduleId, | |
| 111 | + ids: id | |
| 112 | + } | |
| 113 | + let response = await deleteManyApi('api/form/deleteMany', payload) | |
| 114 | + if (response.status === 200) { | |
| 115 | + setSelectedValues([]) | |
| 116 | + setDelete(false) | |
| 117 | + setAction((pre) => !pre) | |
| 118 | + } | |
| 119 | + } catch (error) { | |
| 120 | + console.log(error) | |
| 121 | + } | |
| 122 | + finally { | |
| 123 | + setIsLoding(false) | |
| 124 | + } | |
| 125 | + } | |
| 126 | + | |
| 127 | + useEffect(() => { | |
| 128 | + fetchCustomDataFields(); | |
| 129 | + }, [action, title]) | |
| 130 | + | |
| 131 | + // useEffect(() => { | |
| 132 | + // setDataColumn(tableColumns?.filter(item => selectedColumns?.find(colum => colum?.Header === item.Header))); | |
| 133 | + // }, [tableColumns, selectedColumns]) | |
| 134 | + | |
| 135 | + return ( | |
| 136 | + <div> | |
| 137 | + {isLoding ? <Flex justifyContent={'center'} alignItems={'center'} width="100%" color={'black'} fontSize="sm" fontWeight="700"> | |
| 138 | + <Spinner /> | |
| 139 | + </Flex> : | |
| 140 | + <Grid templateColumns="repeat(6, 1fr)" mb={3} gap={4}> | |
| 141 | + <GridItem colSpan={6}> | |
| 142 | + <CommonCheckTable | |
| 143 | + title={moduleData?.moduleName} | |
| 144 | + isLoding={isLoding} | |
| 145 | + columnData={columns ?? []} | |
| 146 | + // dataColumn={dataColumn ?? []} | |
| 147 | + allData={data ?? []} | |
| 148 | + tableData={data} | |
| 149 | + tableCustomFields={moduleData?.fields?.filter((field) => field?.isTableField === true) || []} | |
| 150 | + access={permission} | |
| 151 | + // action={action} | |
| 152 | + // setAction={setAction} | |
| 153 | + // selectedColumns={selectedColumns} | |
| 154 | + // setSelectedColumns={setSelectedColumns} | |
| 155 | + // isOpen={isOpen} | |
| 156 | + // onClose={onclose} | |
| 157 | + onOpen={onOpen} | |
| 158 | + selectedValues={selectedValues} | |
| 159 | + setSelectedValues={setSelectedValues} | |
| 160 | + setDelete={setDelete} | |
| 161 | + setIsImport={setIsImport} | |
| 162 | + /> | |
| 163 | + </GridItem> | |
| 164 | + </Grid> | |
| 165 | + } | |
| 166 | + {isOpen && <Add isOpen={isOpen} title={title} size={size} moduleData={moduleData} onClose={onClose} setAction={setAction} action={action} />} | |
| 167 | + {deleteModel && <CommonDeleteModel isOpen={deleteModel} onClose={() => setDelete(false)} type={title} handleDeleteData={handleDelete} ids={selectedValues} selectedValues={moduleData?._id} />} | |
| 168 | + | |
| 169 | + {edit && <Edit isOpen={edit} title={title} size={size} moduleData={moduleData} selectedId={selectedId} setSelectedId={setSelectedId} onClose={setEdit} setAction={setAction} moduleId={moduleData?._id} />} | |
| 170 | + | |
| 171 | + </div> | |
| 172 | + ) | |
| 173 | +} | |
| 174 | + | |
| 175 | +export default Index |
+160-0Client/src/views/admin/emailHistory/View.js
| @@ -0,0 +1,160 @@ | ||
| 1 | +import { Box, Button, Flex, Grid, GridItem, Heading, Text, useDisclosure } from "@chakra-ui/react"; | |
| 2 | +import Card from "components/card/Card"; | |
| 3 | +import { HSeparator } from "components/separator/Separator"; | |
| 4 | +import Spinner from "components/spinner/Spinner"; | |
| 5 | +import moment from "moment"; | |
| 6 | +import { useEffect, useState } from "react"; | |
| 7 | +import { IoIosArrowBack } from "react-icons/io"; | |
| 8 | +import { Link, useParams, useNavigate } from "react-router-dom"; | |
| 9 | +import { HasAccess } from "../../../redux/accessUtils"; | |
| 10 | +import { getApi } from "services/api"; | |
| 11 | +import { FaFilePdf } from "react-icons/fa"; | |
| 12 | +import html2pdf from "html2pdf.js"; | |
| 13 | +const View = () => { | |
| 14 | + | |
| 15 | + const param = useParams() | |
| 16 | + | |
| 17 | + const [data, setData] = useState() | |
| 18 | + const { isOpen, onOpen, onClose } = useDisclosure() | |
| 19 | + const [edit, setEdit] = useState(false); | |
| 20 | + const [deleteModel, setDelete] = useState(false); | |
| 21 | + const user = JSON.parse(localStorage.getItem("user")) | |
| 22 | + const [isLoding, setIsLoding] = useState(false) | |
| 23 | + const navigate = useNavigate() | |
| 24 | + const [loading, setLoading] = useState(false) | |
| 25 | + | |
| 26 | + const size = "lg"; | |
| 27 | + | |
| 28 | + const fetchData = async () => { | |
| 29 | + setIsLoding(true) | |
| 30 | + let response = await getApi('api/email/view/', param.id) | |
| 31 | + setData(response?.data); | |
| 32 | + setIsLoding(false) | |
| 33 | + } | |
| 34 | + | |
| 35 | + const generatePDF = () => { | |
| 36 | + setLoading(true) | |
| 37 | + const element = document.getElementById("reports"); | |
| 38 | + const hideBtn = document.getElementById("hide-btn"); | |
| 39 | + | |
| 40 | + if (element) { | |
| 41 | + hideBtn.style.display = 'none'; | |
| 42 | + html2pdf() | |
| 43 | + .from(element) | |
| 44 | + .set({ | |
| 45 | + margin: [0, 0, 0, 0], | |
| 46 | + filename: `Email_Details_${moment().format("DD-MM-YYYY")}.pdf`, | |
| 47 | + image: { type: "jpeg", quality: 0.98 }, | |
| 48 | + html2canvas: { scale: 2, useCORS: true, allowTaint: true }, | |
| 49 | + jsPDF: { unit: "in", format: "letter", orientation: "portrait" }, | |
| 50 | + }) | |
| 51 | + .save().then(() => { | |
| 52 | + setLoading(false) | |
| 53 | + hideBtn.style.display = ''; | |
| 54 | + }) | |
| 55 | + // }, 500); | |
| 56 | + } else { | |
| 57 | + console.error("Element with ID 'reports' not found."); | |
| 58 | + setLoading(false) | |
| 59 | + } | |
| 60 | + }; | |
| 61 | + | |
| 62 | + useEffect(() => { | |
| 63 | + fetchData() | |
| 64 | + }, []) | |
| 65 | + | |
| 66 | + const [contactAccess, leadAccess] = HasAccess(['Contacts', 'Leads']) | |
| 67 | + | |
| 68 | + return ( | |
| 69 | + <> | |
| 70 | + {isLoding ? | |
| 71 | + <Flex justifyContent={'center'} alignItems={'center'} width="100%" > | |
| 72 | + <Spinner /> | |
| 73 | + </Flex> : <> | |
| 74 | + <Grid templateColumns="repeat(4, 1fr)" gap={3} id="reports"> | |
| 75 | + <GridItem colSpan={{ base: 4 }}> | |
| 76 | + <Heading size="lg" m={3}> | |
| 77 | + {data?.senderEmail} | |
| 78 | + </Heading> | |
| 79 | + </GridItem> | |
| 80 | + <GridItem colSpan={{ base: 4 }}> | |
| 81 | + <Card > | |
| 82 | + <Grid gap={4}> | |
| 83 | + <GridItem colSpan={2}> | |
| 84 | + <Box> | |
| 85 | + <Box display={"flex"} justifyContent={"space-between"}> | |
| 86 | + <Heading size="md" mb={3}> | |
| 87 | + Email Details | |
| 88 | + </Heading> | |
| 89 | + <Box id="hide-btn"> | |
| 90 | + <Button leftIcon={<FaFilePdf />} size='sm' variant="brand" onClick={generatePDF} disabled={loading}> | |
| 91 | + {loading ? "Please Wait..." : "Print as PDF"} | |
| 92 | + </Button> | |
| 93 | + <Button leftIcon={<IoIosArrowBack />} size='sm' variant="brand" onClick={() => navigate(-1)} style={{ marginLeft: 10 }}> | |
| 94 | + Back | |
| 95 | + </Button> | |
| 96 | + </Box> | |
| 97 | + </Box> | |
| 98 | + <HSeparator /> | |
| 99 | + </Box> | |
| 100 | + </GridItem> | |
| 101 | + <GridItem colSpan={{ base: 2, md: 1 }}> | |
| 102 | + <Text fontSize="sm" fontWeight="bold" color={'blackAlpha.900'}> Sender </Text> | |
| 103 | + <Text>{data?.senderEmail ? data?.senderEmail : ' - '}</Text> | |
| 104 | + </GridItem> | |
| 105 | + <GridItem colSpan={{ base: 2, md: 1 }}> | |
| 106 | + <Text fontSize="sm" fontWeight="bold" color={'blackAlpha.900'}> Recipient </Text> | |
| 107 | + <Text>{data?.recipient ? data?.recipient : ' - '}</Text> | |
| 108 | + </GridItem> | |
| 109 | + <GridItem colSpan={{ base: 2, md: 1 }}> | |
| 110 | + <Text fontSize="sm" fontWeight="bold" color={'blackAlpha.900'}> Create From </Text> | |
| 111 | + {data?.createBy ? | |
| 112 | + <Link to={`/contactView/${data?.createBy}`}> | |
| 113 | + <Text color={contactAccess?.view ? 'brand.600' : 'blackAlpha.900'} sx={{ '&:hover': { color: contactAccess?.view ? 'blue.500' : 'blackAlpha.900', textDecoration: contactAccess?.view ? 'underline' : 'none' } }}>{data?.createByName ? data?.createByName : ' - '}</Text> | |
| 114 | + </Link> : <Link to={`/leadView/${data?.createByLead}`}> | |
| 115 | + <Text color={leadAccess?.view ? 'brand.600' : 'blackAlpha.900'} sx={{ '&:hover': { color: leadAccess?.view ? 'blue.500' : 'blackAlpha.900', textDecoration: leadAccess?.view ? 'underline' : 'none' } }}>{data?.createByName ? data?.createByName : ' - '}</Text> | |
| 116 | + </Link> | |
| 117 | + } | |
| 118 | + </GridItem> | |
| 119 | + <GridItem colSpan={{ base: 2, md: 1 }}> | |
| 120 | + <Text fontSize="sm" fontWeight="bold" color={'blackAlpha.900'}> Realeted To </Text> | |
| 121 | + <Text>{data?.createBy ? "Contact" : data?.createByLead && "Lead"}</Text> | |
| 122 | + </GridItem> | |
| 123 | + | |
| 124 | + <GridItem colSpan={{ base: 2, md: 1 }}> | |
| 125 | + <Text fontSize="sm" fontWeight="bold" color={'blackAlpha.900'}> Start Date </Text> | |
| 126 | + <Text> {data?.startDate ? moment(data?.startDate).format('lll ') : ' - '} </Text> | |
| 127 | + </GridItem> | |
| 128 | + <GridItem colSpan={{ base: 2, md: 1 }}> | |
| 129 | + <Text fontSize="sm" fontWeight="bold" color={'blackAlpha.900'}>End Date </Text> | |
| 130 | + <Text> {data?.endDate ? moment(data?.endDate).format('lll ') : ' - '} </Text> | |
| 131 | + </GridItem> | |
| 132 | + <GridItem colSpan={{ base: 2, md: 1 }}> | |
| 133 | + <Text fontSize="sm" fontWeight="bold" color={'blackAlpha.900'}> Time stamp </Text> | |
| 134 | + <Text> {data?.timestamp ? moment(data?.timestamp).format('lll ') : ' - '} [{data?.timestamp ? moment(data?.timestamp).toNow() : ' - '}]</Text> | |
| 135 | + </GridItem> | |
| 136 | + <GridItem colSpan={{ base: 2 }}> | |
| 137 | + <Text fontSize="sm" fontWeight="bold" color={'blackAlpha.900'}> Subject </Text> | |
| 138 | + <Text>{data?.subject ? data?.subject : ' - '}</Text> | |
| 139 | + </GridItem> | |
| 140 | + <GridItem colSpan={{ base: 2 }} > | |
| 141 | + <Text fontSize="sm" fontWeight="bold" color={'blackAlpha.900'}> Message </Text> | |
| 142 | + { | |
| 143 | + data?.type === "message" ? | |
| 144 | + <Text>{data?.message ? data?.message : '-'}</Text> | |
| 145 | + : | |
| 146 | + <div dangerouslySetInnerHTML={{ __html: data?.html }} /> | |
| 147 | + } | |
| 148 | + </GridItem> | |
| 149 | + </Grid> | |
| 150 | + </Card> | |
| 151 | + </GridItem> | |
| 152 | + | |
| 153 | + </Grid> | |
| 154 | + | |
| 155 | + </>} | |
| 156 | + </> | |
| 157 | + ); | |
| 158 | +}; | |
| 159 | + | |
| 160 | +export default View; |
+309-0Client/src/views/admin/emailHistory/add.js
| @@ -0,0 +1,309 @@ | ||
| 1 | +import { Button, Flex, FormLabel, Grid, GridItem, IconButton, Input, Modal, ModalBody, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalOverlay, Radio, RadioGroup, Select, Stack, Text, Textarea } from '@chakra-ui/react'; | |
| 2 | +import Spinner from 'components/spinner/Spinner'; | |
| 3 | +import ContactModel from "components/commonTableModel/ContactModel"; | |
| 4 | +import LeadModel from "components/commonTableModel/LeadModel"; | |
| 5 | +import { useFormik } from 'formik'; | |
| 6 | +import { useEffect, useState } from 'react'; | |
| 7 | +import { LiaMousePointerSolid } from 'react-icons/lia'; | |
| 8 | +import { emailSchema } from 'schema'; | |
| 9 | +import { getApi, postApi } from 'services/api'; | |
| 10 | +import dayjs from 'dayjs'; | |
| 11 | +import { fetchEmailTempData } from '../../../redux/slices/emailTempSlice'; | |
| 12 | +import { useDispatch } from 'react-redux'; | |
| 13 | +import { toast } from 'react-toastify'; | |
| 14 | + | |
| 15 | +const AddEmailHistory = (props) => { | |
| 16 | + const { onClose, isOpen } = props | |
| 17 | + const [isLoding, setIsLoding] = useState(false) | |
| 18 | + const [assignToLeadData, setAssignToLeadData] = useState([]); | |
| 19 | + const [assignToContactData, setAssignToContactData] = useState([]); | |
| 20 | + const [contactModelOpen, setContactModel] = useState(false); | |
| 21 | + const [leadModelOpen, setLeadModel] = useState(false); | |
| 22 | + const [data, setData] = useState([]); | |
| 23 | + const dispatch = useDispatch(); | |
| 24 | + | |
| 25 | + const user = JSON.parse(localStorage.getItem('user')) | |
| 26 | + const todayTime = new Date().toISOString().split('.')[0]; | |
| 27 | + | |
| 28 | + const initialValues = { | |
| 29 | + sender: user?._id, | |
| 30 | + recipient: '', | |
| 31 | + subject: '', | |
| 32 | + callNotes: '', | |
| 33 | + createByContact: '', | |
| 34 | + createByLead: '', | |
| 35 | + startDate: '', | |
| 36 | + type: 'message', | |
| 37 | + html: '', | |
| 38 | + category: 'contact', | |
| 39 | + // assignTo: '', | |
| 40 | + // assignToLead: '', | |
| 41 | + createBy: user?._id, | |
| 42 | + } | |
| 43 | + const formik = useFormik({ | |
| 44 | + initialValues: initialValues, | |
| 45 | + validationSchema: emailSchema, | |
| 46 | + onSubmit: (values, { resetForm }) => { | |
| 47 | + AddData(); | |
| 48 | + resetForm(); | |
| 49 | + }, | |
| 50 | + }); | |
| 51 | + const { errors, touched, values, handleBlur, handleChange, handleSubmit, setFieldValue } = formik | |
| 52 | + | |
| 53 | + const AddData = async () => { | |
| 54 | + try { | |
| 55 | + setIsLoding(true) | |
| 56 | + let response = await postApi('api/email/add', values) | |
| 57 | + if (response.status === 200) { | |
| 58 | + props.onClose(); | |
| 59 | + props.setAction((pre) => !pre) | |
| 60 | + } | |
| 61 | + } catch (e) { | |
| 62 | + console.log(e); | |
| 63 | + } | |
| 64 | + finally { | |
| 65 | + setIsLoding(false) | |
| 66 | + } | |
| 67 | + }; | |
| 68 | + useEffect(async () => { | |
| 69 | + values.start = props?.date | |
| 70 | + try { | |
| 71 | + let result | |
| 72 | + if (values.category === "Contact" && assignToContactData.length <= 0) { | |
| 73 | + result = await getApi(user.role === 'superAdmin' ? 'api/contact/' : `api/contact/?createBy=${user._id}`) | |
| 74 | + setAssignToContactData(result?.data) | |
| 75 | + | |
| 76 | + } else if (values.category === "Lead" && assignToLeadData <= 0) { | |
| 77 | + result = await getApi(user.role === 'superAdmin' ? 'api/lead/' : `api/lead/?createBy=${user._id}`); | |
| 78 | + setAssignToLeadData(result?.data) | |
| 79 | + } | |
| 80 | + } | |
| 81 | + catch (e) { | |
| 82 | + console.log(e); | |
| 83 | + } | |
| 84 | + }, [props, values.category]) | |
| 85 | + | |
| 86 | + const fetchRecipientData = async () => { | |
| 87 | + if (values.createByContact) { | |
| 88 | + let findEmail = assignToContactData.find((item) => item._id === values.createByContact); | |
| 89 | + if (findEmail) { | |
| 90 | + setFieldValue('recipient', findEmail.email); | |
| 91 | + } | |
| 92 | + } else if (values.createByLead) { | |
| 93 | + let findEmail = assignToLeadData.find((item) => item._id === values.createByLead); | |
| 94 | + if (findEmail) { | |
| 95 | + setFieldValue('recipient', findEmail.leadEmail); | |
| 96 | + } | |
| 97 | + } else { | |
| 98 | + setFieldValue('recipient', ""); | |
| 99 | + | |
| 100 | + } | |
| 101 | + } | |
| 102 | + | |
| 103 | + const fetchData = async () => { | |
| 104 | + setIsLoding(true) | |
| 105 | + const result = await dispatch(fetchEmailTempData()) | |
| 106 | + if (result.payload.status === 200) { | |
| 107 | + setData(result?.payload?.data); | |
| 108 | + } else { | |
| 109 | + toast.error("Failed to fetch data", "error"); | |
| 110 | + } | |
| 111 | + setIsLoding(false) | |
| 112 | + } | |
| 113 | + | |
| 114 | + useEffect(() => { | |
| 115 | + if (values?.type === "template") fetchData() | |
| 116 | + }, [values?.type]) | |
| 117 | + | |
| 118 | + useEffect(() => { | |
| 119 | + fetchRecipientData() | |
| 120 | + }, [values.createByContact, values.createByLead]) | |
| 121 | + | |
| 122 | + return ( | |
| 123 | + <Modal onClose={onClose} isOpen={isOpen} isCentered> | |
| 124 | + <ModalOverlay /> | |
| 125 | + <ModalContent height={"580px"}> | |
| 126 | + <ModalHeader>Add Email </ModalHeader> | |
| 127 | + <ModalCloseButton /> | |
| 128 | + <ModalBody overflowY={"auto"} height={"400px"}> | |
| 129 | + {/* Contact Model */} | |
| 130 | + <ContactModel isOpen={contactModelOpen} data={assignToContactData} onClose={setContactModel} fieldName='createByContact' setFieldValue={setFieldValue} /> | |
| 131 | + {/* Lead Model */} | |
| 132 | + <LeadModel isOpen={leadModelOpen} data={assignToLeadData} onClose={setLeadModel} fieldName='createByLead' setFieldValue={setFieldValue} /> | |
| 133 | + | |
| 134 | + <Grid templateColumns="repeat(12, 1fr)" gap={3}> | |
| 135 | + <GridItem colSpan={{ base: 12, md: 6 }} > | |
| 136 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='8px'> | |
| 137 | + Related | |
| 138 | + </FormLabel> | |
| 139 | + <RadioGroup onChange={(e) => { setFieldValue('category', e); setFieldValue('createByContact', ''); setFieldValue('createByLead', ''); }} value={values.category}> | |
| 140 | + <Stack direction='row'> | |
| 141 | + <Radio value='Contact'>Contact</Radio> | |
| 142 | + <Radio value='Lead'>Lead</Radio> | |
| 143 | + </Stack> | |
| 144 | + </RadioGroup> | |
| 145 | + </GridItem> | |
| 146 | + <GridItem colSpan={{ base: 12 }}> | |
| 147 | + {values.category === "Contact" ? | |
| 148 | + <> | |
| 149 | + <GridItem colSpan={{ base: 12, md: 6 }} > | |
| 150 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='8px'> | |
| 151 | + Recipient (Contact) | |
| 152 | + </FormLabel> | |
| 153 | + <Flex justifyContent={'space-between'}> | |
| 154 | + <Select | |
| 155 | + value={values.createByContact} | |
| 156 | + name="createByContact" | |
| 157 | + onChange={handleChange} | |
| 158 | + mb={errors.createByContact && touched.createByContact ? undefined : '10px'} | |
| 159 | + fontWeight='500' | |
| 160 | + placeholder={'Assign To'} | |
| 161 | + borderColor={errors.createByContact && touched.createByContact ? "red.300" : null} | |
| 162 | + > | |
| 163 | + {assignToContactData?.map((item) => { | |
| 164 | + return <option value={item._id} key={item._id}>{values.category === 'Contact' ? `${item.firstName} ${item.lastName}` : item.leadName}</option> | |
| 165 | + })} | |
| 166 | + </Select> | |
| 167 | + <IconButton onClick={() => setContactModel(true)} ml={2} fontSize='25px' icon={<LiaMousePointerSolid />} /> | |
| 168 | + </Flex> | |
| 169 | + </GridItem> | |
| 170 | + </> | |
| 171 | + : values.category === "Lead" ? | |
| 172 | + <> | |
| 173 | + <GridItem colSpan={{ base: 12, md: 6 }} > | |
| 174 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='8px'> | |
| 175 | + Recipient (Lead) | |
| 176 | + </FormLabel> | |
| 177 | + <Flex justifyContent={'space-between'}> | |
| 178 | + <Select | |
| 179 | + value={values.createByLead} | |
| 180 | + name="createByLead" | |
| 181 | + onChange={handleChange} | |
| 182 | + mb={errors.createByLead && touched.createByLead ? undefined : '10px'} | |
| 183 | + fontWeight='500' | |
| 184 | + placeholder={'Assign To'} | |
| 185 | + borderColor={errors.createByLead && touched.createByLead ? "red.300" : null} | |
| 186 | + > | |
| 187 | + {assignToLeadData?.map((item) => { | |
| 188 | + return <option value={item._id} key={item._id}>{values.category === 'Contact' ? `${item.firstName} ${item.lastName}` : item.leadName}</option> | |
| 189 | + })} | |
| 190 | + </Select> | |
| 191 | + <IconButton onClick={() => setLeadModel(true)} ml={2} fontSize='25px' icon={<LiaMousePointerSolid />} /> | |
| 192 | + </Flex> | |
| 193 | + </GridItem> | |
| 194 | + </> | |
| 195 | + : '' | |
| 196 | + } | |
| 197 | + </GridItem> | |
| 198 | + <GridItem colSpan={{ base: 12 }}> | |
| 199 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='8px'> | |
| 200 | + Recipient<Text color={"red"}>*</Text> | |
| 201 | + </FormLabel> | |
| 202 | + <Input | |
| 203 | + fontSize='sm' | |
| 204 | + disabled | |
| 205 | + value={values.recipient} | |
| 206 | + name="recipient" | |
| 207 | + placeholder='Recipient' | |
| 208 | + fontWeight='500' | |
| 209 | + borderColor={errors.recipient && touched.recipient ? "red.300" : null} | |
| 210 | + /> | |
| 211 | + <Text mb='10px' fontSize='sm' color={'red'}> {errors.recipient && touched.recipient && errors.recipient}</Text> | |
| 212 | + </GridItem> | |
| 213 | + <GridItem colSpan={{ base: 12 }} > | |
| 214 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='8px'> | |
| 215 | + Start Date<Text color={"red"}>*</Text> | |
| 216 | + </FormLabel> | |
| 217 | + <Input | |
| 218 | + type="datetime-local" | |
| 219 | + fontSize='sm' | |
| 220 | + onChange={handleChange} | |
| 221 | + onBlur={handleBlur} | |
| 222 | + min={dayjs(todayTime).format('YYYY-MM-DD HH:mm')} | |
| 223 | + value={values.startDate} | |
| 224 | + name="startDate" | |
| 225 | + fontWeight='500' | |
| 226 | + borderColor={errors?.startDate && touched?.startDate ? "red.300" : null} | |
| 227 | + /> | |
| 228 | + <Text fontSize='sm' mb='10px' color={'red'}> {errors.startDate && touched.startDate && errors.startDate}</Text> | |
| 229 | + </GridItem> | |
| 230 | + | |
| 231 | + <GridItem colSpan={{ base: 12 }}> | |
| 232 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='8px'> | |
| 233 | + Subject<Text color={"red"}>*</Text> | |
| 234 | + </FormLabel> | |
| 235 | + <Input | |
| 236 | + fontSize='sm' | |
| 237 | + onChange={handleChange} onBlur={handleBlur} | |
| 238 | + value={values.subject} | |
| 239 | + name="subject" | |
| 240 | + placeholder='subject' | |
| 241 | + fontWeight='500' | |
| 242 | + borderColor={errors.subject && touched.subject ? "red.300" : null} | |
| 243 | + /> | |
| 244 | + <Text fontSize='sm' mb='10px' color={'red'}> {errors.subject && touched.subject && errors.subject}</Text> | |
| 245 | + </GridItem> | |
| 246 | + <GridItem colSpan={{ base: 12 }}> | |
| 247 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='8px'> | |
| 248 | + Message | |
| 249 | + </FormLabel> | |
| 250 | + <RadioGroup onChange={(e) => { setFieldValue('type', e) }} value={values.type}> | |
| 251 | + <Stack direction='row'> | |
| 252 | + <Radio value='message'>Message</Radio> | |
| 253 | + <Radio value='template'>Template</Radio> | |
| 254 | + </Stack> | |
| 255 | + </RadioGroup> | |
| 256 | + </GridItem> | |
| 257 | + <GridItem colSpan={{ base: 12 }}> | |
| 258 | + { | |
| 259 | + values?.type === "message" ? | |
| 260 | + <> | |
| 261 | + <Textarea | |
| 262 | + resize={'none'} | |
| 263 | + fontSize='sm' | |
| 264 | + placeholder='Enter Message' | |
| 265 | + onChange={handleChange} onBlur={handleBlur} | |
| 266 | + value={values.message} | |
| 267 | + name="message" | |
| 268 | + fontWeight='500' | |
| 269 | + borderColor={errors.message && touched.message ? "red.300" : null} | |
| 270 | + /> | |
| 271 | + <Text fontSize='sm' mb='10px' color={'red'}> {errors.message && touched.message && errors.message}</Text> | |
| 272 | + </> | |
| 273 | + : | |
| 274 | + <Select | |
| 275 | + // value={values.assignTo} | |
| 276 | + name="html" | |
| 277 | + onChange={handleChange} onBlur={handleBlur} | |
| 278 | + value={values.html} | |
| 279 | + fontWeight='500' | |
| 280 | + placeholder={'Select Template'} | |
| 281 | + // borderColor={errors.assignTo && touched.assignTo ? "red.300" : null} | |
| 282 | + > | |
| 283 | + {data?.map((item) => { | |
| 284 | + return <option value={item?.html} key={item._id}>{item?.templateName}</option> | |
| 285 | + })} | |
| 286 | + </Select> | |
| 287 | + } | |
| 288 | + </GridItem> | |
| 289 | + </Grid> | |
| 290 | + | |
| 291 | + | |
| 292 | + </ModalBody> | |
| 293 | + <ModalFooter> | |
| 294 | + <Button variant='brand' size="sm" disabled={isLoding ? true : false} onClick={handleSubmit}>{isLoding ? <Spinner /> : 'Save'}</Button> | |
| 295 | + <Button sx={{ | |
| 296 | + marginLeft: 2, | |
| 297 | + textTransform: "capitalize", | |
| 298 | + }} variant="outline" | |
| 299 | + colorScheme="red" onClick={() => { | |
| 300 | + formik.resetForm() | |
| 301 | + onClose() | |
| 302 | + }} size="sm">Close</Button> | |
| 303 | + </ModalFooter> | |
| 304 | + </ModalContent> | |
| 305 | + </Modal> | |
| 306 | + ) | |
| 307 | +} | |
| 308 | + | |
| 309 | +export default AddEmailHistory |
+205-0Client/src/views/admin/emailHistory/components/AddEmail.js
| @@ -0,0 +1,205 @@ | ||
| 1 | +import { Button, FormLabel, Grid, GridItem, Input, Modal, ModalBody, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalOverlay, Radio, RadioGroup, Select, Stack, Text, Textarea } from '@chakra-ui/react'; | |
| 2 | +import Spinner from 'components/spinner/Spinner'; | |
| 3 | +import dayjs from 'dayjs'; | |
| 4 | +import { useFormik } from 'formik'; | |
| 5 | +import { useEffect, useState } from 'react'; | |
| 6 | +import { useDispatch } from 'react-redux'; | |
| 7 | +import { toast } from 'react-toastify'; | |
| 8 | +import { emailSchema } from 'schema'; | |
| 9 | +import { postApi } from 'services/api'; | |
| 10 | +import { fetchEmailTempData } from '../../../../redux/slices/emailTempSlice'; | |
| 11 | + | |
| 12 | + | |
| 13 | +const AddEmailHistory = (props) => { | |
| 14 | + const { onClose, isOpen, fetchData, setAction } = props | |
| 15 | + const user = JSON.parse(localStorage.getItem('user')) | |
| 16 | + const [isLoding, setIsLoding] = useState(false) | |
| 17 | + const todayTime = new Date().toISOString().split('.')[0]; | |
| 18 | + const [data, setData] = useState([]); | |
| 19 | + const dispatch = useDispatch(); | |
| 20 | + | |
| 21 | + const initialValues = { | |
| 22 | + sender: user?._id, | |
| 23 | + recipient: props.lead !== true ? props?.contactEmail : props?.leadEmail, | |
| 24 | + subject: '', | |
| 25 | + message: '', | |
| 26 | + createByContact: props?.id && props?.lead !== true ? props?.id : '', | |
| 27 | + createByLead: props?.id && props?.lead === true ? props?.id : '', | |
| 28 | + startDate: '', | |
| 29 | + type: 'message', | |
| 30 | + html: '', | |
| 31 | + createBy: user?._id, | |
| 32 | + } | |
| 33 | + | |
| 34 | + const formik = useFormik({ | |
| 35 | + initialValues: initialValues, | |
| 36 | + validationSchema: emailSchema, | |
| 37 | + enableReinitialize: true, | |
| 38 | + onSubmit: (values, { resetForm }) => { | |
| 39 | + AddData(); | |
| 40 | + resetForm(); | |
| 41 | + }, | |
| 42 | + }); | |
| 43 | + const { errors, touched, values, handleBlur, handleChange, handleSubmit, setFieldValue, } = formik | |
| 44 | + | |
| 45 | + const AddData = async () => { | |
| 46 | + try { | |
| 47 | + setIsLoding(true) | |
| 48 | + let response = await postApi('api/email/add', values) | |
| 49 | + if (response.status === 200) { | |
| 50 | + props.onClose(); | |
| 51 | + fetchData(1) | |
| 52 | + // setAction((pre) => !pre) | |
| 53 | + } | |
| 54 | + } catch (e) { | |
| 55 | + console.log(e); | |
| 56 | + } | |
| 57 | + finally { | |
| 58 | + setIsLoding(false) | |
| 59 | + } | |
| 60 | + }; | |
| 61 | + const fetchEmailTemp = async () => { | |
| 62 | + setIsLoding(true) | |
| 63 | + const result = await dispatch(fetchEmailTempData()) | |
| 64 | + if (result.payload.status === 200) { | |
| 65 | + setData(result?.payload?.data); | |
| 66 | + } else { | |
| 67 | + toast.error("Failed to fetch data", "error"); | |
| 68 | + } | |
| 69 | + setIsLoding(false) | |
| 70 | + } | |
| 71 | + useEffect(() => { | |
| 72 | + if (values?.type === "template") fetchEmailTemp() | |
| 73 | + }, [values?.type]) | |
| 74 | + // useEffect(() => { | |
| 75 | + // if (props.id && props.lead !== true) { | |
| 76 | + // setFieldValue('createByContact', props.id); | |
| 77 | + // } else if (props.id && props.lead === true) { | |
| 78 | + // setFieldValue('createByLead', props.id); | |
| 79 | + // } | |
| 80 | + // }, [props.id, props.lead]) | |
| 81 | + | |
| 82 | + return ( | |
| 83 | + <Modal onClose={onClose} isOpen={isOpen} isCentered> | |
| 84 | + <ModalOverlay /> | |
| 85 | + <ModalContent> | |
| 86 | + <ModalHeader>Send Email </ModalHeader> | |
| 87 | + <ModalCloseButton /> | |
| 88 | + <ModalBody> | |
| 89 | + | |
| 90 | + <Grid templateColumns="repeat(12, 1fr)" gap={3}> | |
| 91 | + | |
| 92 | + <GridItem colSpan={{ base: 12 }}> | |
| 93 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='8px'> | |
| 94 | + Recipient<Text color={"red"}>*</Text> | |
| 95 | + </FormLabel> | |
| 96 | + <Input | |
| 97 | + fontSize='sm' | |
| 98 | + onChange={handleChange} onBlur={handleBlur} | |
| 99 | + value={values.recipient} | |
| 100 | + name="recipient" | |
| 101 | + disabled | |
| 102 | + placeholder='Recipient' | |
| 103 | + fontWeight='500' | |
| 104 | + borderColor={errors.recipient && touched.recipient ? "red.300" : null} | |
| 105 | + /> | |
| 106 | + <Text fontSize='sm' mb='10px' color={'red'}> {errors.recipient && touched.recipient && errors.recipient}</Text> | |
| 107 | + </GridItem> | |
| 108 | + <GridItem colSpan={{ base: 12 }}> | |
| 109 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='8px'> | |
| 110 | + Subject<Text color={"red"}>*</Text> | |
| 111 | + </FormLabel> | |
| 112 | + <Input | |
| 113 | + fontSize='sm' | |
| 114 | + placeholder='Enter subject' | |
| 115 | + onChange={handleChange} onBlur={handleBlur} | |
| 116 | + value={values.subject} | |
| 117 | + name="subject" | |
| 118 | + fontWeight='500' | |
| 119 | + borderColor={errors.subject && touched.subject ? "red.300" : null} | |
| 120 | + /> | |
| 121 | + <Text fontSize='sm' mb='10px' color={'red'}> {errors.subject && touched.subject && errors.subject}</Text> | |
| 122 | + </GridItem> | |
| 123 | + <GridItem colSpan={{ base: 12 }} > | |
| 124 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='8px'> | |
| 125 | + Start Date<Text color={"red"}>*</Text> | |
| 126 | + </FormLabel> | |
| 127 | + <Input | |
| 128 | + type="datetime-local" | |
| 129 | + fontSize='sm' | |
| 130 | + onChange={handleChange} | |
| 131 | + onBlur={handleBlur} | |
| 132 | + min={dayjs(todayTime).format('YYYY-MM-DD HH:mm')} | |
| 133 | + value={values.startDate} | |
| 134 | + name="startDate" | |
| 135 | + fontWeight='500' | |
| 136 | + borderColor={errors?.startDate && touched?.startDate ? "red.300" : null} | |
| 137 | + /> | |
| 138 | + <Text fontSize='sm' mb='10px' color={'red'}> {errors.startDate && touched.startDate && errors.startDate}</Text> | |
| 139 | + </GridItem> | |
| 140 | + | |
| 141 | + <GridItem colSpan={{ base: 12 }}> | |
| 142 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='8px'> | |
| 143 | + Message | |
| 144 | + </FormLabel> | |
| 145 | + <RadioGroup onChange={(e) => { setFieldValue('type', e) }} value={values.type}> | |
| 146 | + <Stack direction='row'> | |
| 147 | + <Radio value='message'>Message</Radio> | |
| 148 | + <Radio value='template'>Template</Radio> | |
| 149 | + </Stack> | |
| 150 | + </RadioGroup> | |
| 151 | + </GridItem> | |
| 152 | + <GridItem colSpan={{ base: 12 }}> | |
| 153 | + { | |
| 154 | + values?.type === "message" ? | |
| 155 | + <> | |
| 156 | + <Textarea | |
| 157 | + resize={'none'} | |
| 158 | + fontSize='sm' | |
| 159 | + placeholder='Enter Message' | |
| 160 | + onChange={handleChange} onBlur={handleBlur} | |
| 161 | + value={values.message} | |
| 162 | + name="message" | |
| 163 | + fontWeight='500' | |
| 164 | + borderColor={errors.message && touched.message ? "red.300" : null} | |
| 165 | + /> | |
| 166 | + <Text fontSize='sm' mb='10px' color={'red'}> {errors.message && touched.message && errors.message}</Text> | |
| 167 | + </> | |
| 168 | + : | |
| 169 | + <Select | |
| 170 | + // value={values.assignTo} | |
| 171 | + name="html" | |
| 172 | + onChange={handleChange} onBlur={handleBlur} | |
| 173 | + value={values.html} | |
| 174 | + fontWeight='500' | |
| 175 | + placeholder={'Select Template'} | |
| 176 | + // borderColor={errors.assignTo && touched.assignTo ? "red.300" : null} | |
| 177 | + > | |
| 178 | + {data?.map((item) => { | |
| 179 | + return <option value={item?.html} key={item._id}>{item?.templateName}</option> | |
| 180 | + })} | |
| 181 | + </Select> | |
| 182 | + } | |
| 183 | + </GridItem> | |
| 184 | + | |
| 185 | + </Grid> | |
| 186 | + | |
| 187 | + | |
| 188 | + </ModalBody> | |
| 189 | + <ModalFooter> | |
| 190 | + <Button size="sm" variant='brand' onClick={handleSubmit} disabled={isLoding ? true : false} >{isLoding ? <Spinner /> : 'Save'}</Button> | |
| 191 | + <Button sx={{ | |
| 192 | + marginLeft: 2, | |
| 193 | + textTransform: "capitalize", | |
| 194 | + }} variant="outline" | |
| 195 | + colorScheme="red" size="sm" onClick={() => { | |
| 196 | + formik.resetForm() | |
| 197 | + onClose() | |
| 198 | + }}>Close</Button> | |
| 199 | + </ModalFooter> | |
| 200 | + </ModalContent> | |
| 201 | + </Modal> | |
| 202 | + ) | |
| 203 | +} | |
| 204 | + | |
| 205 | +export default AddEmailHistory |
+131-0Client/src/views/admin/emailHistory/components/EmailAdvanceSearch.js
| @@ -0,0 +1,131 @@ | ||
| 1 | +import React from 'react'; | |
| 2 | +import { useFormik } from "formik"; | |
| 3 | +import * as yup from "yup"; | |
| 4 | +import { Modal, ModalBody, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalOverlay, Grid, GridItem, Input, FormLabel, Select, Text, Button, } from '@chakra-ui/react'; | |
| 5 | +import Spinner from 'components/spinner/Spinner'; | |
| 6 | +import { setSearchValue, getSearchData, setGetTagValues } from '../../../../redux/slices/advanceSearchSlice'; | |
| 7 | +import { useDispatch } from 'react-redux'; | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | +const EmailAdvanceSearch = (props) => { | |
| 12 | + const { state, allData, advanceSearch, setAdvanceSearch, isLoding, setSearchedData, setDisplaySearchData, setSearchClear, setSearchbox } = props; | |
| 13 | + | |
| 14 | + const dispatch = useDispatch(); | |
| 15 | + const initialValues = { | |
| 16 | + senderName: '', | |
| 17 | + realetedTo: '', | |
| 18 | + createByName: '', | |
| 19 | + } | |
| 20 | + const validationSchema = yup.object({ | |
| 21 | + senderName: yup.string(), | |
| 22 | + realetedTo: yup.string(), | |
| 23 | + createByName: yup.string() | |
| 24 | + }); | |
| 25 | + const formik = useFormik({ | |
| 26 | + initialValues: initialValues, | |
| 27 | + validationSchema: validationSchema, | |
| 28 | + onSubmit: (values, { resetForm }) => { | |
| 29 | + dispatch(setSearchValue(values)) | |
| 30 | + dispatch(getSearchData({ values: values, allData: allData, type: 'Email' })) | |
| 31 | + // const searchResult = allData?.filter( | |
| 32 | + // (item) => | |
| 33 | + // (!values?.senderName || (item?.senderName && item?.senderName.toLowerCase().includes(values?.senderName?.toLowerCase()))) && | |
| 34 | + // (!values?.realetedTo || (values.realetedTo === "contact" ? item.createBy : item.createByLead)) && | |
| 35 | + // (!values?.createByName || (item?.createByName && item?.createByName.toLowerCase().includes(values?.createByName?.toLowerCase()))) | |
| 36 | + // ) | |
| 37 | + // let getValue = [values.senderName, values?.realetedTo, values?.createByName].filter(value => value); | |
| 38 | + const getValue = [ | |
| 39 | + { | |
| 40 | + name: ["senderName"], | |
| 41 | + value: values.senderName | |
| 42 | + }, | |
| 43 | + { | |
| 44 | + name: ["realetedTo"], | |
| 45 | + value: values.realetedTo | |
| 46 | + }, | |
| 47 | + { | |
| 48 | + name: ["createByName"], | |
| 49 | + value: values.createByName | |
| 50 | + }, | |
| 51 | + ] | |
| 52 | + dispatch(setGetTagValues(getValue.filter(item => item.value))) | |
| 53 | + setDisplaySearchData(true) | |
| 54 | + setAdvanceSearch(false) | |
| 55 | + resetForm(); | |
| 56 | + setSearchbox(''); | |
| 57 | + } | |
| 58 | + }) | |
| 59 | + | |
| 60 | + const { errors, touched, values, handleBlur, handleChange, handleSubmit, setFieldValue, resetForm, dirty } = formik; | |
| 61 | + | |
| 62 | + return ( | |
| 63 | + <> | |
| 64 | + <Modal onClose={() => { setAdvanceSearch(false); resetForm() }} isOpen={advanceSearch} isCentered> | |
| 65 | + <ModalOverlay /> | |
| 66 | + <ModalContent> | |
| 67 | + <ModalHeader>Advance Search</ModalHeader> | |
| 68 | + <ModalCloseButton onClick={() => { setAdvanceSearch(false); resetForm() }} /> | |
| 69 | + <ModalBody> | |
| 70 | + <Grid templateColumns="repeat(12, 1fr)" mb={3} gap={2}> | |
| 71 | + <GridItem colSpan={{ base: 12 }}> | |
| 72 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='600' color={"#000"} mb="0" mt={2}> | |
| 73 | + Sender Name | |
| 74 | + </FormLabel> | |
| 75 | + <Input | |
| 76 | + fontSize='sm' | |
| 77 | + onChange={handleChange} onBlur={handleBlur} | |
| 78 | + value={values?.senderName} | |
| 79 | + name="senderName" | |
| 80 | + placeholder='Enter Sender Name' | |
| 81 | + fontWeight='500' | |
| 82 | + /> | |
| 83 | + <Text mb='10px' color={'red'}> {errors.senderName && touched.senderName && errors.senderName}</Text> | |
| 84 | + | |
| 85 | + </GridItem> | |
| 86 | + <GridItem colSpan={{ base: 12 }}> | |
| 87 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='600' color={"#000"} mb="0" mt={2}> | |
| 88 | + Realeted To | |
| 89 | + </FormLabel> | |
| 90 | + <Select | |
| 91 | + value={values?.realetedTo} | |
| 92 | + fontSize='sm' | |
| 93 | + name="realetedTo" | |
| 94 | + onChange={handleChange} | |
| 95 | + fontWeight='500' | |
| 96 | + placeholder={'Select Realeted To'} | |
| 97 | + > | |
| 98 | + <option value='contact'>Contact</option> | |
| 99 | + <option value='lead'>Lead</option> | |
| 100 | + </Select> | |
| 101 | + <Text mb='10px' color={'red'}> {errors.realetedTo && touched.realetedTo && errors.realetedTo}</Text> | |
| 102 | + | |
| 103 | + </GridItem> | |
| 104 | + | |
| 105 | + <GridItem colSpan={{ base: 12 }}> | |
| 106 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='600' color={"#000"} mb="0" mt={2} > | |
| 107 | + CreateBy Name | |
| 108 | + </FormLabel> | |
| 109 | + <Input | |
| 110 | + fontSize='sm' | |
| 111 | + onChange={handleChange} onBlur={handleBlur} | |
| 112 | + value={values?.createByName} | |
| 113 | + name="createByName" | |
| 114 | + placeholder='Enter CreateBy Name' | |
| 115 | + fontWeight='500' | |
| 116 | + /> | |
| 117 | + <Text mb='10px' color={'red'}> {errors.createByName && touched.createByName && errors.createByName}</Text> | |
| 118 | + </GridItem> | |
| 119 | + </Grid> | |
| 120 | + </ModalBody> | |
| 121 | + <ModalFooter> | |
| 122 | + <Button size="sm" variant="brand" mr={2} onClick={handleSubmit} disabled={isLoding || !dirty ? true : false} >{isLoding ? <Spinner /> : 'Search'}</Button> | |
| 123 | + <Button size="sm" variant="outline" colorScheme="red" onClick={() => resetForm()}>Clear</Button> | |
| 124 | + </ModalFooter> | |
| 125 | + </ModalContent> | |
| 126 | + </Modal> | |
| 127 | + </> | |
| 128 | + ) | |
| 129 | +} | |
| 130 | + | |
| 131 | +export default EmailAdvanceSearch |
+93-0Client/src/views/admin/emailHistory/emailView.js
| @@ -0,0 +1,93 @@ | ||
| 1 | +import { CloseIcon, DeleteIcon, EditIcon, ViewIcon } from '@chakra-ui/icons' | |
| 2 | +import { DrawerFooter, Flex, Grid, GridItem, IconButton, Modal, ModalBody, ModalContent, ModalHeader, ModalOverlay, Text } from '@chakra-ui/react' | |
| 3 | +import Spinner from "components/spinner/Spinner" | |
| 4 | +import moment from 'moment' | |
| 5 | +import { useEffect, useState } from 'react' | |
| 6 | +import { getApi } from 'services/api' | |
| 7 | +import { useNavigate } from 'react-router-dom'; | |
| 8 | + | |
| 9 | +const EmailView = (props) => { | |
| 10 | + const { onClose, isOpen, info, fetchData, setAction, action, access, contactAccess, leadAccess } = props | |
| 11 | + const [data, setData] = useState(); | |
| 12 | + const [edit, setEdit] = useState(false); | |
| 13 | + const [deleteModel, setDelete] = useState(false); | |
| 14 | + const user = JSON.parse(localStorage.getItem("user")) | |
| 15 | + const [isLoding, setIsLoding] = useState(false) | |
| 16 | + const navigate = useNavigate() | |
| 17 | + | |
| 18 | + const fetchViewData = async () => { | |
| 19 | + if (info) { | |
| 20 | + setIsLoding(true) | |
| 21 | + let result = await getApi('api/email/view/', info?.event ? info?.event?.id : info); | |
| 22 | + setData(result?.data); | |
| 23 | + setIsLoding(false) | |
| 24 | + } | |
| 25 | + } | |
| 26 | + useEffect(() => { | |
| 27 | + fetchViewData() | |
| 28 | + }, [action, info]) | |
| 29 | + | |
| 30 | + const handleViewOpen = () => { | |
| 31 | + if (info?.event) { | |
| 32 | + navigate(`/view/${info?.event?.id}`) | |
| 33 | + } | |
| 34 | + else { | |
| 35 | + navigate(`/view/${info}`) | |
| 36 | + } | |
| 37 | + } | |
| 38 | + | |
| 39 | + return ( | |
| 40 | + <Modal isOpen={isOpen} size={'md'} isCentered> | |
| 41 | + <ModalOverlay /> | |
| 42 | + <ModalContent> | |
| 43 | + <ModalHeader justifyContent='space-between' display='flex' > | |
| 44 | ||
| 45 | + <IconButton onClick={() => onClose(false)} icon={<CloseIcon />} /> | |
| 46 | + </ModalHeader> | |
| 47 | + {isLoding ? | |
| 48 | + <Flex justifyContent={'center'} alignItems={'center'} mb={30} width="100%" > | |
| 49 | + <Spinner /> | |
| 50 | + </Flex> : <> | |
| 51 | + | |
| 52 | + <ModalBody> | |
| 53 | + <Grid templateColumns="repeat(12, 1fr)" gap={3} > | |
| 54 | + | |
| 55 | + <GridItem colSpan={{ base: 12, md: 6 }} > | |
| 56 | + <Text fontSize="sm" fontWeight="bold" color={'blackAlpha.900'}> Recipient </Text> | |
| 57 | + <Text>{data?.recipient ? data?.recipient : ' - '}</Text> | |
| 58 | + </GridItem> | |
| 59 | + <GridItem colSpan={{ base: 12, md: 6 }} > | |
| 60 | + <Text fontSize="sm" fontWeight="bold" color={'blackAlpha.900'}> Sender Email </Text> | |
| 61 | + <Text>{data?.senderEmail ? data?.senderEmail : '-'}</Text> | |
| 62 | + </GridItem> | |
| 63 | + <GridItem colSpan={{ base: 12, md: 6 }} > | |
| 64 | + <Text fontSize="sm" fontWeight="bold" color={'blackAlpha.900'}> Start Date </Text> | |
| 65 | + <Text>{data?.startDate ? moment(data?.startDate).format('lll ') : ' - '}</Text> | |
| 66 | + </GridItem> | |
| 67 | + <GridItem colSpan={{ base: 12, md: 6 }} > | |
| 68 | + <Text fontSize="sm" fontWeight="bold" color={'blackAlpha.900'}> End Date </Text> | |
| 69 | + <Text>{data?.endDate ? moment(data?.endDate).format('lll ') : ' - '}</Text> | |
| 70 | + </GridItem> | |
| 71 | + <GridItem colSpan={{ base: 12, md: 6 }} > | |
| 72 | + <Text fontSize="sm" fontWeight="bold" color={'blackAlpha.900'}> Subject </Text> | |
| 73 | + <Text>{data?.subject ? data?.subject : '-'}</Text> | |
| 74 | + </GridItem> | |
| 75 | + <GridItem colSpan={{ base: 12, md: 6 }} > | |
| 76 | + <Text fontSize="sm" fontWeight="bold" color={'blackAlpha.900'}> Message </Text> | |
| 77 | + <Text>{data?.message ? data?.message : '-'}</Text> | |
| 78 | + </GridItem> | |
| 79 | + </Grid> | |
| 80 | + | |
| 81 | + </ModalBody> | |
| 82 | + <DrawerFooter> | |
| 83 | + {access?.view && <IconButton variant='outline' colorScheme={'green'} onClick={() => handleViewOpen()} borderRadius="10px" size="md" icon={<ViewIcon />} />} | |
| 84 | + {access?.update && <IconButton variant='outline' onClick={() => setEdit(true)} ml={3} borderRadius="10px" size="md" icon={<EditIcon />} />} | |
| 85 | + {access?.delete && <IconButton colorScheme='red' onClick={() => setDelete(true)} ml={3} borderRadius="10px" size="md" icon={<DeleteIcon />} />} | |
| 86 | + </DrawerFooter> | |
| 87 | + </>} | |
| 88 | + </ModalContent> | |
| 89 | + </Modal> | |
| 90 | + ) | |
| 91 | +} | |
| 92 | + | |
| 93 | +export default EmailView |
+220-0Client/src/views/admin/emailHistory/index.js
| @@ -0,0 +1,220 @@ | ||
| 1 | + | |
| 2 | +import { useEffect, useState } from 'react'; | |
| 3 | +import { Link, useNavigate } from "react-router-dom"; | |
| 4 | +import { ViewIcon } from '@chakra-ui/icons'; | |
| 5 | +import { Button, Menu, MenuButton, MenuItem, MenuList, Text, useDisclosure } from '@chakra-ui/react'; | |
| 6 | +import { getApi } from 'services/api'; | |
| 7 | +import { HasAccess } from '../../../redux/accessUtils'; | |
| 8 | +import CommonCheckTable from '../../../components/reactTable/checktable'; | |
| 9 | +import { SearchIcon } from "@chakra-ui/icons"; | |
| 10 | +import { CiMenuKebab } from 'react-icons/ci'; | |
| 11 | +import EmailAdvanceSearch from './components/EmailAdvanceSearch'; | |
| 12 | +import moment from 'moment'; | |
| 13 | +import { MdLeaderboard } from 'react-icons/md'; | |
| 14 | +import { IoIosContact } from 'react-icons/io'; | |
| 15 | +import AddEmailHistory from './add'; | |
| 16 | +import { useDispatch } from 'react-redux'; | |
| 17 | +import { fetchEmailsData } from '../../../redux/slices/emailsSlice'; | |
| 18 | +import { toast } from 'react-toastify'; | |
| 19 | + | |
| 20 | +const Index = (props) => { | |
| 21 | + const title = "Email"; | |
| 22 | + const [action, setAction] = useState(false); | |
| 23 | + const { isOpen, onOpen, onClose } = useDisclosure(); | |
| 24 | + const [selectedId, setDelete] = useState(false); | |
| 25 | + const [selectedValues, setSelectedValues] = useState([]); | |
| 26 | + const [advanceSearch, setAdvanceSearch] = useState(false); | |
| 27 | + const [getTagValuesOutSide, setGetTagValuesOutside] = useState([]); | |
| 28 | + const [searchboxOutside, setSearchboxOutside] = useState(''); | |
| 29 | + const user = JSON.parse(localStorage.getItem("user")); | |
| 30 | + const navigate = useNavigate(); | |
| 31 | + const dispatch = useDispatch(); | |
| 32 | + const [isLoding, setIsLoding] = useState(false); | |
| 33 | + const [data, setData] = useState([]); | |
| 34 | + const [displaySearchData, setDisplaySearchData] = useState(false); | |
| 35 | + const [searchedData, setSearchedData] = useState([]); | |
| 36 | + const [permission, leadAccess, contactAccess] = HasAccess(["Emails", 'Leads', 'Contacts']); | |
| 37 | + const actionHeader = { | |
| 38 | + Header: "Action", accessor: 'action', isSortable: false, center: true, | |
| 39 | + cell: ({ row }) => ( | |
| 40 | + <Text fontSize="md" fontWeight="900" textAlign={"center"}> | |
| 41 | + <Menu isLazy > | |
| 42 | + <MenuButton><CiMenuKebab /></MenuButton> | |
| 43 | + <MenuList minW={'fit-content'} transform={"translate(1520px, 173px);"}> | |
| 44 | + {permission?.view && <MenuItem py={2.5} color={'green'} onClick={() => navigate(`/Email/${row?.values._id}`)} icon={<ViewIcon mb={'2px'} fontSize={15} />}>View</MenuItem>} | |
| 45 | + {row?.original?.createByContact && contactAccess?.view ? | |
| 46 | + <MenuItem width={"165px"} py={2.5} color={'black'} onClick={() => navigate(row?.original?.createByContact && `/contactView/${row?.original.createByContact}`)} icon={row?.original.createByContact && <IoIosContact fontSize={15} />}> {(row?.original.createByContact && contactAccess?.view) && "contact"} | |
| 47 | + </MenuItem> | |
| 48 | + : ''} | |
| 49 | + {row?.original.createByLead && leadAccess?.view ? <MenuItem width={"165px"} py={2.5} color={'black'} onClick={() => navigate(`/leadView/${row?.original.createByLead}`)} icon={row?.original.createByLead && leadAccess?.view && <MdLeaderboard style={{ marginBottom: '4px' }} fontSize={15} />}>{row?.original.createByLead && leadAccess?.view && 'lead'}</MenuItem> : ''} | |
| 50 | + </MenuList> | |
| 51 | + </Menu> | |
| 52 | + </Text> | |
| 53 | + ) | |
| 54 | + }; | |
| 55 | + const tableColumns = [ | |
| 56 | + { Header: "#", accessor: "_id", isSortable: false, width: 10 }, | |
| 57 | + { | |
| 58 | + Header: "recipient", accessor: "createByName", cell: (cell) => ( | |
| 59 | + <Link to={`/Email/${cell?.row?.values._id}`}> | |
| 60 | + <Text | |
| 61 | + me="10px" | |
| 62 | + sx={{ '&:hover': { color: 'blue.500', textDecoration: 'underline' } }} | |
| 63 | + color='brand.600' | |
| 64 | + fontSize="sm" | |
| 65 | + fontWeight="700" | |
| 66 | + > | |
| 67 | + {cell?.value || '-'} | |
| 68 | + </Text> | |
| 69 | + </Link>) | |
| 70 | + }, | |
| 71 | + { Header: 'sender Name', accessor: 'senderName' }, | |
| 72 | + { | |
| 73 | + Header: "Realeted To", accessor: 'realeted', cell: ({ row }) => ( | |
| 74 | + <Text > | |
| 75 | + {row?.original.createByContact && contactAccess?.view ? <Link to={`/contactView/${row?.original.createByContact}`}> | |
| 76 | + <Text | |
| 77 | + me="10px" | |
| 78 | + sx={{ '&:hover': { color: 'blue.500', textDecoration: 'underline' } }} | |
| 79 | + color={'brand.600'} | |
| 80 | + fontSize="sm" | |
| 81 | + fontWeight="700" | |
| 82 | + > | |
| 83 | + {row?.original.createByContact && "Contact"} | |
| 84 | + </Text> | |
| 85 | + </Link> : | |
| 86 | + <Text | |
| 87 | + me="10px" | |
| 88 | + fontSize="sm" | |
| 89 | + fontWeight="700" | |
| 90 | + > | |
| 91 | + {row?.original.createByContact && "Contact"} | |
| 92 | + </Text>} | |
| 93 | + | |
| 94 | + {leadAccess?.view && row?.original.createByLead ? <Link to={`/leadView/${row?.original.createByLead}`}> | |
| 95 | + <Text | |
| 96 | + me="10px" | |
| 97 | + sx={{ '&:hover': { color: 'blue.500', textDecoration: 'underline' } }} | |
| 98 | + color={'brand.600'} | |
| 99 | + fontSize="sm" | |
| 100 | + fontWeight="700" | |
| 101 | + > | |
| 102 | + {row?.original.createByLead && "Lead"} | |
| 103 | + </Text> | |
| 104 | + </Link> : <Text | |
| 105 | + me="10px" | |
| 106 | + fontSize="sm" | |
| 107 | + fontWeight="700" | |
| 108 | + > | |
| 109 | + {row?.original.createByLead && "Lead"} | |
| 110 | + </Text>} | |
| 111 | + </Text> | |
| 112 | + ) | |
| 113 | + }, | |
| 114 | + { Header: "timestamp", accessor: "timestamp", }, | |
| 115 | + { | |
| 116 | + Header: "Created", accessor: 'created', cell: ({ row }) => ( | |
| 117 | + <Text fontSize="sm" fontWeight="700"> | |
| 118 | + {moment(row?.values.timestamp).format('(DD/MM) h:mma')} | |
| 119 | + </Text> | |
| 120 | + ) | |
| 121 | + }, | |
| 122 | + ...(permission?.update || permission?.view || permission?.delete ? [actionHeader] : []) | |
| 123 | + | |
| 124 | + ]; | |
| 125 | + | |
| 126 | + const fetchData = async () => { | |
| 127 | + setIsLoding(true) | |
| 128 | + const result = await dispatch(fetchEmailsData()) | |
| 129 | + let response = [...result?.payload?.data] | |
| 130 | + | |
| 131 | + response && response?.length > 0 && response?.forEach(element => { | |
| 132 | + | |
| 133 | + if (Object.isExtensible(element)) { | |
| 134 | + if (element.createByLead) { | |
| 135 | + element.realeted = 'Lead'; | |
| 136 | + } | |
| 137 | + if (element.createBy) { | |
| 138 | + element.realeted = 'Contact'; | |
| 139 | + } | |
| 140 | + } else { | |
| 141 | + const modifiedElement = { ...element }; | |
| 142 | + if (element.createByLead) { | |
| 143 | + modifiedElement.realeted = 'Lead'; | |
| 144 | + } | |
| 145 | + if (element.createBy) { | |
| 146 | + modifiedElement.realeted = 'Contact'; | |
| 147 | + } | |
| 148 | + element = modifiedElement; | |
| 149 | + } | |
| 150 | + }); | |
| 151 | + if (result.payload.status === 200) { | |
| 152 | + setData(response); | |
| 153 | + } else { | |
| 154 | + toast.error("Failed to fetch data", "error"); | |
| 155 | + } | |
| 156 | + setIsLoding(false) | |
| 157 | + } | |
| 158 | + | |
| 159 | + // const [columns, setColumns] = useState([...tableColumns]); | |
| 160 | + // const [selectedColumns, setSelectedColumns] = useState([...tableColumns]); | |
| 161 | + // const dataColumn = tableColumns?.filter(item => selectedColumns?.find(colum => colum?.Header === item.Header)) | |
| 162 | + | |
| 163 | + | |
| 164 | + useEffect(() => { | |
| 165 | + fetchData(); | |
| 166 | + }, [action]) | |
| 167 | + | |
| 168 | + return ( | |
| 169 | + <div> | |
| 170 | + <CommonCheckTable | |
| 171 | + title={title} | |
| 172 | + isLoding={isLoding} | |
| 173 | + columnData={tableColumns ?? []} | |
| 174 | + // dataColumn={dataColumn ?? []} | |
| 175 | + allData={data ?? []} | |
| 176 | + tableData={data} | |
| 177 | + searchDisplay={displaySearchData} | |
| 178 | + setSearchDisplay={setDisplaySearchData} | |
| 179 | + searchedDataOut={searchedData} | |
| 180 | + setSearchedDataOut={setSearchedData} | |
| 181 | + tableCustomFields={[]} | |
| 182 | + access={permission} | |
| 183 | + // action={action} | |
| 184 | + // setAction={setAction} | |
| 185 | + // selectedColumns={selectedColumns} | |
| 186 | + // setSelectedColumns={setSelectedColumns} | |
| 187 | + // isOpen={isOpen} | |
| 188 | + // onClose={onclose} | |
| 189 | + onOpen={onOpen} | |
| 190 | + selectedValues={selectedValues} | |
| 191 | + setSelectedValues={setSelectedValues} | |
| 192 | + setDelete={setDelete} | |
| 193 | + deleteMany={true} | |
| 194 | + AdvanceSearch={ | |
| 195 | + <Button variant="outline" colorScheme='brand' leftIcon={<SearchIcon />} mt={{ sm: "5px", md: "0" }} size="sm" onClick={() => setAdvanceSearch(true)}>Advance Search</Button> | |
| 196 | + } | |
| 197 | + getTagValuesOutSide={getTagValuesOutSide} | |
| 198 | + searchboxOutside={searchboxOutside} | |
| 199 | + setGetTagValuesOutside={setGetTagValuesOutside} | |
| 200 | + setSearchboxOutside={setSearchboxOutside} | |
| 201 | + handleSearchType="EmailSearch" | |
| 202 | + /> | |
| 203 | + | |
| 204 | + <EmailAdvanceSearch | |
| 205 | + advanceSearch={advanceSearch} | |
| 206 | + setAdvanceSearch={setAdvanceSearch} | |
| 207 | + setSearchedData={setSearchedData} | |
| 208 | + setDisplaySearchData={setDisplaySearchData} | |
| 209 | + allData={data ?? []} | |
| 210 | + setAction={setAction} | |
| 211 | + setGetTagValues={setGetTagValuesOutside} | |
| 212 | + setSearchbox={setSearchboxOutside} | |
| 213 | + /> | |
| 214 | + | |
| 215 | + <AddEmailHistory isOpen={isOpen} size={"sm"} onClose={onClose} setAction={setAction} /> | |
| 216 | + </div> | |
| 217 | + ) | |
| 218 | +} | |
| 219 | + | |
| 220 | +export default Index |
+168-0Client/src/views/admin/emailTemplate/AddEdit.js
| @@ -0,0 +1,168 @@ | ||
| 1 | +import { Button, Grid, GridItem, Flex, IconButton, Text, Menu, MenuButton, MenuDivider, MenuItem, MenuList, useDisclosure, Input, FormLabel } from '@chakra-ui/react' | |
| 2 | +import { AddIcon, ChevronDownIcon, DeleteIcon, EditIcon } from "@chakra-ui/icons"; | |
| 3 | +import React from 'react' | |
| 4 | +import moment from 'moment' | |
| 5 | +import { Link, useLocation, useNavigate, useParams } from 'react-router-dom' | |
| 6 | +import { BiLink } from 'react-icons/bi' | |
| 7 | +import { useEffect, useRef } from 'react' | |
| 8 | +import { useState } from 'react' | |
| 9 | +import { getApi } from 'services/api' | |
| 10 | +import Card from 'components/card/Card' | |
| 11 | +import { IoIosArrowBack } from "react-icons/io"; | |
| 12 | +import { HasAccess } from '../../../redux/accessUtils'; | |
| 13 | +import { HSeparator } from 'components/separator/Separator'; | |
| 14 | +import CommonDeleteModel from 'components/commonDeleteModel'; | |
| 15 | +import { deleteManyApi, } from 'services/api'; | |
| 16 | +import { EmailEditor } from 'react-email-editor'; | |
| 17 | +import { toast } from 'react-toastify'; | |
| 18 | +import { postApi } from 'services/api'; | |
| 19 | +import { putApi } from 'services/api'; | |
| 20 | + | |
| 21 | +const AddEdit = () => { | |
| 22 | + const user = JSON.parse(localStorage.getItem("user")) | |
| 23 | + const navigate = useNavigate() | |
| 24 | + const loction = useLocation() | |
| 25 | + const { type, id } = loction?.state || {} | |
| 26 | + const [permission] = HasAccess(['Email Template']) | |
| 27 | + const emailEditorRef = useRef(null); | |
| 28 | + const [preview, setPreview] = useState(false); | |
| 29 | + const [name, setName] = useState(''); | |
| 30 | + const [description, setDescription] = useState(''); | |
| 31 | + | |
| 32 | + const togglePreview = () => { | |
| 33 | + if (preview) { | |
| 34 | + emailEditorRef.current?.editor?.hidePreview(); | |
| 35 | + setPreview(false); | |
| 36 | + } else { | |
| 37 | + emailEditorRef.current?.editor?.showPreview('desktop'); | |
| 38 | + setPreview(true); | |
| 39 | + } | |
| 40 | + }; | |
| 41 | + | |
| 42 | + const fetchData = async () => { | |
| 43 | + const result = await getApi(`api/email-temp/view/${id}`) | |
| 44 | + if (result && result.status === 200) { | |
| 45 | + setName(result?.data?.templateName) | |
| 46 | + setDescription(result?.data?.description) | |
| 47 | + emailEditorRef?.current?.editor?.loadDesign(result?.data?.design); | |
| 48 | + } | |
| 49 | + } | |
| 50 | + | |
| 51 | + const saveDesign = () => { | |
| 52 | + if (name !== "") { | |
| 53 | + emailEditorRef.current?.editor?.exportHtml(async (allData) => { | |
| 54 | + const { html } = allData | |
| 55 | + const { design } = allData | |
| 56 | + | |
| 57 | + const data = { | |
| 58 | + html: html, | |
| 59 | + design: design, | |
| 60 | + templateName: name, | |
| 61 | + description: description, | |
| 62 | + createBy: user?._id | |
| 63 | + } | |
| 64 | + const result = await postApi('api/email-temp/add', data) | |
| 65 | + if (result && result.status === 200) { | |
| 66 | + toast.success(result.data.message) | |
| 67 | + setName('') | |
| 68 | + } | |
| 69 | + navigate('/email-template') | |
| 70 | + }); | |
| 71 | + } else { | |
| 72 | + toast.error("Template Name is required") | |
| 73 | + } | |
| 74 | + | |
| 75 | + }; | |
| 76 | + | |
| 77 | + const editDesign = () => { | |
| 78 | + if (name !== "") { | |
| 79 | + emailEditorRef.current?.editor?.exportHtml(async (allData) => { | |
| 80 | + const { html } = allData | |
| 81 | + const { design } = allData | |
| 82 | + | |
| 83 | + const data = { | |
| 84 | + html: html, | |
| 85 | + design: design, | |
| 86 | + templateName: name, | |
| 87 | + description: description, | |
| 88 | + createBy: user?._id | |
| 89 | + } | |
| 90 | + const result = await putApi(`api/email-temp/edit/${id}`, data) | |
| 91 | + if (result && result.status === 200) { | |
| 92 | + toast.success(result.data.message) | |
| 93 | + navigate('/email-template') | |
| 94 | + } | |
| 95 | + }); | |
| 96 | + } else { | |
| 97 | + toast.error("Template Name is required") | |
| 98 | + } | |
| 99 | + | |
| 100 | + }; | |
| 101 | + | |
| 102 | + const handleSave = () => { | |
| 103 | + type === "add" ? saveDesign() : editDesign() | |
| 104 | + } | |
| 105 | + | |
| 106 | + | |
| 107 | + useEffect(() => { | |
| 108 | + if (type === "edit") fetchData() | |
| 109 | + }, [type]) | |
| 110 | + | |
| 111 | + return ( | |
| 112 | + <div> | |
| 113 | + <Card> | |
| 114 | + <Grid templateColumns="repeat(12, 1fr)" mb={3} gap={1} justifyContent={"space-between"} alignItem={"center"}> | |
| 115 | + <GridItem colSpan={{ base: 12, md: 6 }}> | |
| 116 | + <Text fontSize="xl" fontWeight="bold" color={'blackAlpha.900'}>{type === "add" ? "Create" : "Edit"} Template </Text> | |
| 117 | + </GridItem> | |
| 118 | + | |
| 119 | + <GridItem colSpan={{ base: 12, md: 6 }}> | |
| 120 | + <Flex justifyContent={"right"}> | |
| 121 | + <Button size="sm" variant="brand" onClick={togglePreview}> | |
| 122 | + {preview ? "Hide Preview" : "Show Preview"} | |
| 123 | + </Button> | |
| 124 | + <Button size="sm" variant="brand" style={{ marginLeft: "10px" }} onClick={handleSave}> | |
| 125 | + Save | |
| 126 | + </Button> | |
| 127 | + <Link to="/email-template" style={{ marginLeft: "10px" }}> | |
| 128 | + <Button size="sm" leftIcon={<IoIosArrowBack />} variant="brand"> | |
| 129 | + Back | |
| 130 | + </Button> | |
| 131 | + </Link> | |
| 132 | + </Flex> | |
| 133 | + </GridItem> | |
| 134 | + </Grid> | |
| 135 | + <HSeparator /> | |
| 136 | + <div> | |
| 137 | + <Grid templateColumns="repeat(12, 1fr)" gap={3} mt={2}> | |
| 138 | + <GridItem colSpan={{ base: 12, md: 6 }} > | |
| 139 | + <Input | |
| 140 | + fontSize='sm' | |
| 141 | + name="templateName" | |
| 142 | + placeholder='Template Name' | |
| 143 | + fontWeight='500' | |
| 144 | + value={name} | |
| 145 | + onChange={(e) => setName(e.target.value)} | |
| 146 | + /> | |
| 147 | + </GridItem> | |
| 148 | + <GridItem colSpan={{ base: 12, md: 6 }} > | |
| 149 | + <Input | |
| 150 | + fontSize='sm' | |
| 151 | + name="description" | |
| 152 | + placeholder='Description' | |
| 153 | + fontWeight='500' | |
| 154 | + value={description} | |
| 155 | + onChange={(e) => setDescription(e.target.value)} | |
| 156 | + /> | |
| 157 | + </GridItem> | |
| 158 | + <GridItem colSpan={{ base: 12, md: 12 }} mt={2}> | |
| 159 | + <EmailEditor ref={emailEditorRef} /> | |
| 160 | + </GridItem> | |
| 161 | + </Grid> | |
| 162 | + </div> | |
| 163 | + </Card> | |
| 164 | + </div> | |
| 165 | + ) | |
| 166 | +} | |
| 167 | + | |
| 168 | +export default AddEdit |
+170-0Client/src/views/admin/emailTemplate/index.js
| @@ -0,0 +1,170 @@ | ||
| 1 | +import { useEffect, useState } from 'react'; | |
| 2 | +import { DeleteIcon, EditIcon, ViewIcon } from '@chakra-ui/icons'; | |
| 3 | +import { Button, Menu, MenuButton, MenuItem, MenuList, Select, Text, useDisclosure } from '@chakra-ui/react'; | |
| 4 | +import { getApi } from 'services/api'; | |
| 5 | +import { HasAccess } from '../../../redux/accessUtils'; | |
| 6 | +import CommonCheckTable from '../../../components/reactTable/checktable'; | |
| 7 | +import { CiMenuKebab } from 'react-icons/ci'; | |
| 8 | +import { putApi } from 'services/api'; | |
| 9 | +import { useLocation } from 'react-router-dom'; | |
| 10 | +import { deleteManyApi } from 'services/api'; | |
| 11 | +import { useNavigate } from 'react-router-dom'; | |
| 12 | +import { useDispatch } from 'react-redux'; | |
| 13 | +import { toast } from 'react-toastify'; | |
| 14 | +import CommonDeleteModel from 'components/commonDeleteModel'; | |
| 15 | +import { fetchEmailTempData } from '../../../redux/slices/emailTempSlice'; | |
| 16 | + | |
| 17 | +const Index = () => { | |
| 18 | + const [action, setAction] = useState(false); | |
| 19 | + const { isOpen, onOpen, onClose } = useDisclosure(); | |
| 20 | + const [edit, setEdit] = useState(false); | |
| 21 | + const [eventView, setEventView] = useState(false) | |
| 22 | + const [id, setId] = useState('') | |
| 23 | + const [selectedId, setSelectedId] = useState(); | |
| 24 | + const [selectedValues, setSelectedValues] = useState([]); | |
| 25 | + // const [advanceSearch, setAdvanceSearch] = useState(false); | |
| 26 | + const [getTagValuesOutSide, setGetTagValuesOutside] = useState([]); | |
| 27 | + const [searchboxOutside, setSearchboxOutside] = useState(''); | |
| 28 | + const user = JSON.parse(localStorage.getItem("user")); | |
| 29 | + const [deleteMany, setDeleteMany] = useState(false); | |
| 30 | + const [isImportLead, setIsImportLead] = useState(false); | |
| 31 | + const [isLoding, setIsLoding] = useState(false); | |
| 32 | + const [data, setData] = useState([]); | |
| 33 | + const [displaySearchData, setDisplaySearchData] = useState(false); | |
| 34 | + const [searchedData, setSearchedData] = useState([]); | |
| 35 | + const [userAction, setUserAction] = useState(""); | |
| 36 | + const [permission] = HasAccess(["Email Template"]); | |
| 37 | + const location = useLocation(); | |
| 38 | + // const state = location.state; | |
| 39 | + const navigate = useNavigate() | |
| 40 | + const dispatch = useDispatch(); | |
| 41 | + | |
| 42 | + const handleEditOpen = (row) => { | |
| 43 | + onOpen(); | |
| 44 | + setUserAction("edit") | |
| 45 | + navigate(`/email-template/email-template-addEdit`, { state: { type: 'edit', id: row?.values?._id } }) | |
| 46 | + setSelectedId(row?.values?._id); | |
| 47 | + } | |
| 48 | + | |
| 49 | + const actionHeader = { | |
| 50 | + Header: "Action", isSortable: false, center: true, | |
| 51 | + cell: ({ row }) => ( | |
| 52 | + <Text fontSize="md" fontWeight="900" textAlign={"center"}> | |
| 53 | + <Menu isLazy > | |
| 54 | + <MenuButton ><CiMenuKebab /></MenuButton> | |
| 55 | + <MenuList minW={'fit-content'} transform={"translate(1520px, 173px);"}> | |
| 56 | + {permission?.update && | |
| 57 | + <MenuItem py={2.5} icon={<EditIcon fontSize={15} mb={1} />} onClick={() => handleEditOpen(row)}>Edit</MenuItem>} | |
| 58 | + {permission?.view && | |
| 59 | + <MenuItem py={2.5} color={"green"} icon={<ViewIcon fontSize={15} mb={1} />} onClick={() => navigate(`/email-template/${row?.values?._id}`)}>View</MenuItem>} | |
| 60 | + {permission?.delete && | |
| 61 | + <MenuItem py={2.5} color={'red'} icon={<DeleteIcon fontSize={15} mb={1} />} onClick={() => { setDeleteMany(true); setSelectedValues([row?.values?._id]); }}>Delete</MenuItem>} | |
| 62 | + </MenuList> | |
| 63 | + </Menu> | |
| 64 | + </Text> | |
| 65 | + ) | |
| 66 | + } | |
| 67 | + const tableColumns = [ | |
| 68 | + { | |
| 69 | + Header: "#", | |
| 70 | + accessor: "_id", | |
| 71 | + isSortable: false, | |
| 72 | + width: 5 | |
| 73 | + }, | |
| 74 | + { | |
| 75 | + Header: 'Template Name', accessor: 'templateName', | |
| 76 | + cell: (cell) => ( | |
| 77 | + <div className="selectOpt"> | |
| 78 | + <Text | |
| 79 | + onClick={() => navigate(`/email-template/${cell?.row?.original._id}`)} | |
| 80 | + me="10px" | |
| 81 | + sx={{ '&:hover': { color: 'blue.500', textDecoration: 'underline' }, cursor: 'pointer' }} | |
| 82 | + color='brand.600' | |
| 83 | + fontSize="sm" | |
| 84 | + fontWeight="700" | |
| 85 | + > | |
| 86 | + {cell?.value} | |
| 87 | + </Text> | |
| 88 | + </div> | |
| 89 | + ) | |
| 90 | + }, | |
| 91 | + { Header: "Description", accessor: "description" }, | |
| 92 | + ...(permission?.update || permission?.delete ? [actionHeader] : []), | |
| 93 | + ]; | |
| 94 | + | |
| 95 | + const fetchData = async () => { | |
| 96 | + setIsLoding(true) | |
| 97 | + const result = await dispatch(fetchEmailTempData()) | |
| 98 | + if (result.payload.status === 200) { | |
| 99 | + setData(result?.payload?.data); | |
| 100 | + } else { | |
| 101 | + toast.error("Failed to fetch data", "error"); | |
| 102 | + } | |
| 103 | + setIsLoding(false) | |
| 104 | + } | |
| 105 | + | |
| 106 | + const handleDeleteTask = async (ids) => { | |
| 107 | + try { | |
| 108 | + setIsLoding(true) | |
| 109 | + let response = await deleteManyApi('api/email-temp/deleteMany', ids) | |
| 110 | + if (response.status === 200) { | |
| 111 | + setSelectedValues([]) | |
| 112 | + setDeleteMany(false) | |
| 113 | + setAction((pre) => !pre) | |
| 114 | + } | |
| 115 | + } catch (error) { | |
| 116 | + console.log(error) | |
| 117 | + } | |
| 118 | + finally { | |
| 119 | + setIsLoding(false) | |
| 120 | + } | |
| 121 | + } | |
| 122 | + | |
| 123 | + | |
| 124 | + // const [selectedColumns, setSelectedColumns] = useState([...tableColumns]); | |
| 125 | + // const dataColumn = tableColumns?.filter(item => selectedColumns?.find(colum => colum?.Header === item.Header)) | |
| 126 | + | |
| 127 | + const addBtn = () => { | |
| 128 | + navigate(`/email-template/email-template-addEdit`, { state: { type: 'add' } }) | |
| 129 | + setUserAction("add"); | |
| 130 | + } | |
| 131 | + | |
| 132 | + useEffect(() => { | |
| 133 | + fetchData(); | |
| 134 | + }, [action]) | |
| 135 | + | |
| 136 | + return ( | |
| 137 | + <div> | |
| 138 | + <CommonCheckTable | |
| 139 | + title={"Email Template"} | |
| 140 | + isLoding={isLoding} | |
| 141 | + columnData={tableColumns ?? []} | |
| 142 | + // dataColumn={dataColumn ?? []} | |
| 143 | + allData={data ?? []} | |
| 144 | + searchDisplay={displaySearchData} | |
| 145 | + setSearchDisplay={setDisplaySearchData} | |
| 146 | + searchedDataOut={searchedData} | |
| 147 | + setSearchedDataOut={setSearchedData} | |
| 148 | + tableCustomFields={[]} | |
| 149 | + access={permission} | |
| 150 | + // selectedColumns={selectedColumns} | |
| 151 | + // setSelectedColumns={setSelectedColumns} | |
| 152 | + // state={state} | |
| 153 | + onOpen={addBtn} | |
| 154 | + selectedValues={selectedValues} | |
| 155 | + setSelectedValues={setSelectedValues} | |
| 156 | + setDelete={setDeleteMany} | |
| 157 | + AdvanceSearch={false} | |
| 158 | + getTagValuesOutSide={getTagValuesOutSide} | |
| 159 | + searchboxOutside={searchboxOutside} | |
| 160 | + setGetTagValuesOutside={setGetTagValuesOutside} | |
| 161 | + setSearchboxOutside={setSearchboxOutside} | |
| 162 | + handleSearchType="template" | |
| 163 | + /> | |
| 164 | + <CommonDeleteModel isOpen={deleteMany} onClose={() => setDeleteMany(false)} type='Email Template' handleDeleteData={handleDeleteTask} ids={selectedValues} /> | |
| 165 | + </div> | |
| 166 | + ) | |
| 167 | +} | |
| 168 | + | |
| 169 | +export default Index | |
| 170 | + |
+91-0Client/src/views/admin/emailTemplate/view.js
| @@ -0,0 +1,91 @@ | ||
| 1 | +import { Button, Grid, GridItem, Heading, Flex, IconButton, Text, Menu, MenuButton, MenuDivider, MenuItem, MenuList, useDisclosure, Input, FormLabel } from '@chakra-ui/react' | |
| 2 | +import { AddIcon, ChevronDownIcon, DeleteIcon, EditIcon } from "@chakra-ui/icons"; | |
| 3 | +import React from 'react' | |
| 4 | +import moment from 'moment' | |
| 5 | +import { Link, useLocation, useNavigate, useParams } from 'react-router-dom' | |
| 6 | +import { BiLink } from 'react-icons/bi' | |
| 7 | +import { useEffect, useRef } from 'react' | |
| 8 | +import { useState } from 'react' | |
| 9 | +import { getApi } from 'services/api' | |
| 10 | +import Card from 'components/card/Card' | |
| 11 | +import { IoIosArrowBack } from "react-icons/io"; | |
| 12 | +import { HasAccess } from '../../../redux/accessUtils'; | |
| 13 | +import { HSeparator } from 'components/separator/Separator'; | |
| 14 | +import CommonDeleteModel from 'components/commonDeleteModel'; | |
| 15 | +import { deleteManyApi, } from 'services/api'; | |
| 16 | +import { EmailEditor } from 'react-email-editor'; | |
| 17 | +import { toast } from 'react-toastify'; | |
| 18 | +import { postApi } from 'services/api'; | |
| 19 | +import { putApi } from 'services/api'; | |
| 20 | + | |
| 21 | +const View = () => { | |
| 22 | + const user = JSON.parse(localStorage.getItem("user")) | |
| 23 | + const navigate = useNavigate() | |
| 24 | + const { id } = useParams() | |
| 25 | + const [permission, contactAccess, leadAccess] = HasAccess(['Email Template', 'Contacts', 'Leads']) | |
| 26 | + const emailEditorRef = useRef(null); | |
| 27 | + const [preview, setPreview] = useState(true); | |
| 28 | + const [name, setName] = useState(''); | |
| 29 | + const [description, setDescription] = useState(''); | |
| 30 | + | |
| 31 | + | |
| 32 | + const fetchData = async () => { | |
| 33 | + const result = await getApi(`api/email-temp/view/${id}`) | |
| 34 | + if (result && result.status === 200) { | |
| 35 | + setName(result?.data?.templateName) | |
| 36 | + setDescription(result?.data?.description) | |
| 37 | + emailEditorRef?.current?.editor?.loadDesign(result?.data?.design); | |
| 38 | + emailEditorRef.current?.editor?.showPreview('desktop'); | |
| 39 | + } | |
| 40 | + } | |
| 41 | + | |
| 42 | + | |
| 43 | + useEffect(() => { | |
| 44 | + fetchData() | |
| 45 | + }, []) | |
| 46 | + | |
| 47 | + return ( | |
| 48 | + <div> | |
| 49 | + <GridItem colSpan={{ base: 4 }}> | |
| 50 | + <Heading size="lg" m={3}> | |
| 51 | + {name || ""} | |
| 52 | + </Heading> | |
| 53 | + </GridItem> | |
| 54 | + <Card> | |
| 55 | + <Grid templateColumns="repeat(12, 1fr)" mb={3} gap={1} justifyContent={"space-between"} alignItem={"center"}> | |
| 56 | + <GridItem colSpan={{ base: 12, md: 6 }}> | |
| 57 | + <Text fontSize="xl" fontWeight="bold" color={'blackAlpha.900'}>View Template </Text> | |
| 58 | + </GridItem> | |
| 59 | + | |
| 60 | + <GridItem colSpan={{ base: 12, md: 6 }}> | |
| 61 | + <Flex justifyContent={"right"}> | |
| 62 | + <Link to="/email-template" style={{ marginLeft: "10px" }}> | |
| 63 | + <Button size="sm" leftIcon={<IoIosArrowBack />} variant="brand"> | |
| 64 | + Back | |
| 65 | + </Button> | |
| 66 | + </Link> | |
| 67 | + </Flex> | |
| 68 | + </GridItem> | |
| 69 | + </Grid> | |
| 70 | + <HSeparator /> | |
| 71 | + <div> | |
| 72 | + <Grid templateColumns="repeat(12, 1fr)" gap={3} mt={2}> | |
| 73 | + <GridItem colSpan={{ base: 12, md: 6 }} > | |
| 74 | + <Text fontSize="sm" fontWeight="bold" color={'blackAlpha.900'}> Template Name </Text> | |
| 75 | + <Text >{name ? name : ' - '}</Text> | |
| 76 | + </GridItem> | |
| 77 | + <GridItem colSpan={{ base: 12, md: 6 }} > | |
| 78 | + <Text fontSize="sm" fontWeight="bold" color={'blackAlpha.900'}> Description </Text> | |
| 79 | + <Text >{description ? description : ' - '}</Text> | |
| 80 | + </GridItem> | |
| 81 | + <GridItem colSpan={{ base: 12, md: 12 }} mt={2}> | |
| 82 | + <EmailEditor ref={emailEditorRef} /> | |
| 83 | + </GridItem> | |
| 84 | + </Grid> | |
| 85 | + </div> | |
| 86 | + </Card> | |
| 87 | + </div> | |
| 88 | + ) | |
| 89 | +} | |
| 90 | + | |
| 91 | +export default View |
+103-0Client/src/views/admin/image/addImage.js
| @@ -0,0 +1,103 @@ | ||
| 1 | +import { Button, Grid, GridItem, Modal, ModalBody, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalOverlay, useBreakpointValue } from '@chakra-ui/react' | |
| 2 | +import Spinner from 'components/spinner/Spinner' | |
| 3 | +import { useFormik } from 'formik' | |
| 4 | +import { useState } from 'react' | |
| 5 | +import { postApi } from 'services/api' | |
| 6 | +import UploadImg from './components/Upload'; | |
| 7 | +import { toast } from 'react-toastify' | |
| 8 | +import { fetchImage } from '../../../redux/slices/imageSlice' | |
| 9 | +import { useDispatch } from 'react-redux' | |
| 10 | + | |
| 11 | +const AddImage = (props) => { | |
| 12 | + const { imageModal, setImageModal } = props | |
| 13 | + const [isLoding, setIsLoding] = useState(false) | |
| 14 | + | |
| 15 | + const dispatch = useDispatch() | |
| 16 | + | |
| 17 | + const initialValues = { | |
| 18 | + authImg: '', | |
| 19 | + logoSmImg: '', | |
| 20 | + logoLgImg: '' | |
| 21 | + } | |
| 22 | + | |
| 23 | + const formik = useFormik({ | |
| 24 | + initialValues: initialValues, | |
| 25 | + onSubmit: (values, { resetForm }) => { | |
| 26 | + AddData() | |
| 27 | + resetForm(); | |
| 28 | + }, | |
| 29 | + }); | |
| 30 | + const { errors, touched, values, handleBlur, handleChange, handleSubmit, setFieldValue, resetForm, dirty } = formik | |
| 31 | + | |
| 32 | + const AddData = async () => { | |
| 33 | + try { | |
| 34 | + setIsLoding(true); | |
| 35 | + const formData = new FormData(); | |
| 36 | + | |
| 37 | + if (values?.authImg) { | |
| 38 | + formData?.append('authImg', values?.authImg?.[0]); | |
| 39 | + } | |
| 40 | + | |
| 41 | + if (values?.logoSmImg) { | |
| 42 | + formData?.append('logoSmImg', values?.logoSmImg?.[0]); | |
| 43 | + } | |
| 44 | + | |
| 45 | + if (values?.logoLgImg) { | |
| 46 | + formData?.append('logoLgImg', values?.logoLgImg?.[0]); | |
| 47 | + } | |
| 48 | + | |
| 49 | + if (values?.authImg || values?.logoSmImg || values?.logoLgImg) { | |
| 50 | + const response = await postApi('api/images/add-auth-logo-img', formData); | |
| 51 | + | |
| 52 | + if (response.status === 200) { | |
| 53 | + setImageModal(false); | |
| 54 | + resetForm(); | |
| 55 | + dispatch(fetchImage()); | |
| 56 | + toast.success(response?.data?.message); | |
| 57 | + } else { | |
| 58 | + toast.error(response?.response?.data?.message); | |
| 59 | + } | |
| 60 | + } | |
| 61 | + | |
| 62 | + } catch (e) { | |
| 63 | + console.log(e); | |
| 64 | + } | |
| 65 | + finally { | |
| 66 | + setIsLoding(false) | |
| 67 | + } | |
| 68 | + }; | |
| 69 | + | |
| 70 | + return ( | |
| 71 | + <Modal onClose={() => setImageModal(false)} size={"xl"} isOpen={imageModal} isCentered={useBreakpointValue({ base: false, md: true })}> | |
| 72 | + <ModalOverlay /> | |
| 73 | + <ModalContent> | |
| 74 | + <ModalHeader>Add Image</ModalHeader> | |
| 75 | + <ModalCloseButton /> | |
| 76 | + <ModalBody> | |
| 77 | + <Grid templateColumns="repeat(12, 1fr)" gap={3}> | |
| 78 | + <GridItem colSpan={{ base: 6, md: 12, sm: 12, lg: 6 }}> | |
| 79 | + <UploadImg id='logoLgImg' count={values?.logoLgImg?.length} onFileSelect={(file) => setFieldValue('logoLgImg', file)} text="Large Logo " /> | |
| 80 | + </GridItem> | |
| 81 | + <GridItem colSpan={{ base: 6, md: 12, sm: 12, lg: 6 }}> | |
| 82 | + <UploadImg id='logoSmImg' count={values?.logoSmImg?.length} onFileSelect={(file) => setFieldValue('logoSmImg', file)} text="Small Logo " /> | |
| 83 | + </GridItem> | |
| 84 | + <GridItem display={{ lg: 'flex' }} justifyContent={{ lg: 'center' }} colSpan={{ base: 12, md: 12, sm: 12, lg: 12 }}> | |
| 85 | + <UploadImg id='authImg' count={values?.authImg?.length} onFileSelect={(file) => setFieldValue('authImg', file)} text="Login Page Image" /> | |
| 86 | + </GridItem> | |
| 87 | + </Grid> | |
| 88 | + </ModalBody> | |
| 89 | + <ModalFooter> | |
| 90 | + <Button me={2} variant='brand' onClick={handleSubmit} disabled={isLoding || !(values?.authImg && values?.logoSmImg && values?.logoLgImg) ? true : false} size='sm' >{isLoding ? <Spinner /> : 'Save'}</Button> | |
| 91 | + <Button variant="outline" | |
| 92 | + size='sm' | |
| 93 | + colorScheme="red" onClick={() => { | |
| 94 | + setImageModal(false) | |
| 95 | + formik.resetForm() | |
| 96 | + }}>Close</Button> | |
| 97 | + </ModalFooter> | |
| 98 | + </ModalContent> | |
| 99 | + </Modal> | |
| 100 | + ) | |
| 101 | +} | |
| 102 | + | |
| 103 | +export default AddImage |
+69-0Client/src/views/admin/image/components/Upload.js
| @@ -0,0 +1,69 @@ | ||
| 1 | +// Chakra imports | |
| 2 | +import { | |
| 3 | + Box, Flex, | |
| 4 | + Grid, | |
| 5 | + GridItem, | |
| 6 | + Icon, | |
| 7 | + Text, | |
| 8 | + useColorModeValue | |
| 9 | +} from "@chakra-ui/react"; | |
| 10 | +// Custom components | |
| 11 | +// Assets | |
| 12 | +import Dropzone from "components/Dropzone"; | |
| 13 | +import { MdUpload } from "react-icons/md"; | |
| 14 | + | |
| 15 | +export default function Upload(props) { | |
| 16 | + const { used, total, count, ...rest } = props; | |
| 17 | + // Chakra Color Mode | |
| 18 | + const textColorPrimary = useColorModeValue("secondaryGray.900", "white"); | |
| 19 | + const brandColor = useColorModeValue("brand.500", "white"); | |
| 20 | + const textColorSecondary = "gray.400"; | |
| 21 | + return ( | |
| 22 | + <Grid templateColumns="repeat(12, 1fr)" gap={2}> | |
| 23 | + <GridItem colSpan={{ base: 12 }} > | |
| 24 | + <Dropzone | |
| 25 | + w={{ base: "100%" }} | |
| 26 | + me='36px' | |
| 27 | + minH={100} | |
| 28 | + img={props.text === 'Property Photos' ? 'img' : ''} | |
| 29 | + isMultipleAllow={false} | |
| 30 | + height={'100%'} | |
| 31 | + onFileSelect={props.onFileSelect} | |
| 32 | + content={ | |
| 33 | + <Box> | |
| 34 | + <Icon as={MdUpload} w='50px' h='50px' color={brandColor} /> | |
| 35 | + <Flex justify='center' mx='auto' mb='12px'> | |
| 36 | + <Text fontSize='sm' fontWeight='700' color={brandColor}> | |
| 37 | + Upload File | |
| 38 | + </Text> | |
| 39 | + </Flex> | |
| 40 | + {count > 0 && <Text fontSize='sm' fontWeight='500' color='secondaryGray.500'> | |
| 41 | + Selected Files : {count} | |
| 42 | + </Text>} | |
| 43 | + </Box> | |
| 44 | + } | |
| 45 | + /> | |
| 46 | + </GridItem> | |
| 47 | + <GridItem colSpan={{ base: 12 }} > | |
| 48 | + <Flex direction='column' > | |
| 49 | + <Text | |
| 50 | + color={textColorPrimary} | |
| 51 | + fontWeight='bold' | |
| 52 | + textAlign='start' | |
| 53 | + fontSize='xl' | |
| 54 | + my={{ base: "5px" }}> | |
| 55 | + Upload {props.text} | |
| 56 | + </Text> | |
| 57 | + <Text | |
| 58 | + color={textColorSecondary} | |
| 59 | + fontSize='md' | |
| 60 | + my={{ base: "auto" }} | |
| 61 | + mx='auto' | |
| 62 | + textAlign='start'> | |
| 63 | + Accepted File Types (Images files - 15MB max) only 1 file | |
| 64 | + </Text> | |
| 65 | + </Flex> | |
| 66 | + </GridItem> | |
| 67 | + </Grid> | |
| 68 | + ); | |
| 69 | +} |
+134-0Client/src/views/admin/image/imageView.js
| @@ -0,0 +1,134 @@ | ||
| 1 | +import { Button, Grid, GridItem, Image, Modal, ModalBody, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalOverlay, Text } from '@chakra-ui/react'; | |
| 2 | +import Spinner from 'components/spinner/Spinner'; | |
| 3 | +import React from 'react'; | |
| 4 | +import { useState } from 'react'; | |
| 5 | +import { putApi } from 'services/api'; | |
| 6 | +import Dropzone from "components/Dropzone"; | |
| 7 | +import { toast } from 'react-toastify'; | |
| 8 | +import { useDispatch } from 'react-redux'; | |
| 9 | +import { fetchImage } from '../../../redux/slices/imageSlice' | |
| 10 | + | |
| 11 | +const ImageView = (props) => { | |
| 12 | + const { data, fetchData, setImageData, handleDeleteOpen, imageviewData } = props; | |
| 13 | + const [isLoding, setIsLoding] = useState(false) | |
| 14 | + const dispatch = useDispatch() | |
| 15 | + | |
| 16 | + const authimg = 'authimage'; | |
| 17 | + const logoimg = 'logoimg' | |
| 18 | + const largelogoimg = 'largelogoimg' | |
| 19 | + | |
| 20 | + const changeImage = async (file, type) => { | |
| 21 | + try { | |
| 22 | + setIsLoding(true); | |
| 23 | + const formData = new FormData(); | |
| 24 | + | |
| 25 | + if (type === authimg) { | |
| 26 | + formData?.append('authImg', file?.[0]); | |
| 27 | + } | |
| 28 | + | |
| 29 | + if (type === logoimg) { | |
| 30 | + formData?.append('logoSmImg', file?.[0]); | |
| 31 | + } | |
| 32 | + | |
| 33 | + if (type === largelogoimg) { | |
| 34 | + formData?.append('logoLgImg', file?.[0]); | |
| 35 | + } | |
| 36 | + if (type === authimg || type === logoimg || type === largelogoimg) { | |
| 37 | + const response = await putApi(`api/images/change-auth-logo-img/${data?._id}`, formData); | |
| 38 | + if (response.status === 200) { | |
| 39 | + fetchData(data?._id); | |
| 40 | + dispatch(fetchImage()); | |
| 41 | + toast.success(response?.data?.message); | |
| 42 | + } else { | |
| 43 | + toast.error(response?.response?.data?.message); | |
| 44 | + } | |
| 45 | + } | |
| 46 | + } catch (e) { | |
| 47 | + console.log(e); | |
| 48 | + } | |
| 49 | + finally { | |
| 50 | + setIsLoding(false) | |
| 51 | + } | |
| 52 | + }; | |
| 53 | + | |
| 54 | + return ( | |
| 55 | + <div> | |
| 56 | + <Modal onClose={props.onClose} isOpen={props.isOpen} isCentered size='xl'> | |
| 57 | + <ModalOverlay /> | |
| 58 | + <ModalContent> | |
| 59 | + <ModalHeader>View Images</ModalHeader> | |
| 60 | + <ModalCloseButton /> | |
| 61 | + <ModalBody> | |
| 62 | + | |
| 63 | + <Grid templateColumns={'repeat(12, 1fr)'} gap={5}> | |
| 64 | + <GridItem colSpan={{ base: 12, md: 6 }} display={"flex"} justifyContent={"center"}> | |
| 65 | + <div> | |
| 66 | + <Text fontWeight={"600"} ps={3}>Navbar Small Logo Image</Text> | |
| 67 | + <div className="imageCard" style={{ margin: "10px" }}> | |
| 68 | + <Image src={data?.logoSmImg} height={"100px"} width={"100%"} /> | |
| 69 | + <div className='imageContent'> | |
| 70 | + <Dropzone | |
| 71 | + borderRadius="0" | |
| 72 | + isMultipleAllow={false} | |
| 73 | + onFileSelect={(file) => changeImage(file, logoimg)} | |
| 74 | + content={ | |
| 75 | + <Button size='sm' variant="brand">Change</Button> | |
| 76 | + } | |
| 77 | + /> | |
| 78 | + </div> | |
| 79 | + </div> | |
| 80 | + </div> | |
| 81 | + </GridItem> | |
| 82 | + <GridItem colSpan={{ base: 12, md: 6 }} display={"flex"} justifyContent={"center"}> | |
| 83 | + <div> | |
| 84 | + <Text fontWeight={"600"} ps={3}>Navbar Large Logo Image</Text> | |
| 85 | + <div className="imageCard" style={{ margin: "10px" }}> | |
| 86 | + <Image src={data?.logoLgImg} height={"100px"} width={"100%"} /> | |
| 87 | + <div className='imageContent'> | |
| 88 | + <Dropzone | |
| 89 | + borderRadius="0" | |
| 90 | + isMultipleAllow={false} | |
| 91 | + onFileSelect={(file) => changeImage(file, largelogoimg)} | |
| 92 | + content={ | |
| 93 | + <Button size='sm' variant="brand">Change</Button> | |
| 94 | + } | |
| 95 | + /> | |
| 96 | + </div> | |
| 97 | + </div> | |
| 98 | + </div> | |
| 99 | + </GridItem> | |
| 100 | + <GridItem colSpan={{ base: 12 }} display={"flex"} justifyContent={"center"}> | |
| 101 | + <div> | |
| 102 | + <Text fontWeight={"600"} ps={3}>Login page Image</Text> | |
| 103 | + <div className="imageCard" style={{ margin: "10px" }}> | |
| 104 | + <Image src={data?.authImg} height={"100px"} width={"100%"} /> | |
| 105 | + <div className='imageContent'> | |
| 106 | + <Dropzone | |
| 107 | + borderRadius="0" | |
| 108 | + isMultipleAllow={false} | |
| 109 | + onFileSelect={(file) => changeImage(file, authimg)} | |
| 110 | + content={ | |
| 111 | + <Button size='sm' variant="brand" >Change</Button> | |
| 112 | + } | |
| 113 | + /> | |
| 114 | + </div> | |
| 115 | + </div> | |
| 116 | + </div> | |
| 117 | + </GridItem> | |
| 118 | + </Grid> | |
| 119 | + </ModalBody> | |
| 120 | + <ModalFooter> | |
| 121 | + <Button colorScheme="brand" mr={2} size='sm' onClick={() => setImageData(data)} disabled={isLoding ? true : false}>{isLoding ? <Spinner /> : 'Set Image'}</Button> | |
| 122 | + <Button colorScheme="red" | |
| 123 | + size='sm' | |
| 124 | + disabled={data?.isActive === true ? true : false} | |
| 125 | + onClick={() => { handleDeleteOpen(imageviewData); }} | |
| 126 | + >Delete</Button> | |
| 127 | + </ModalFooter> | |
| 128 | + </ModalContent> | |
| 129 | + </Modal> | |
| 130 | + </div> | |
| 131 | + ) | |
| 132 | +} | |
| 133 | + | |
| 134 | +export default ImageView |
+153-0Client/src/views/admin/image/index.js
| @@ -0,0 +1,153 @@ | ||
| 1 | +import { Box, Button, Flex, Grid, GridItem, Image, Modal, ModalBody, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalOverlay, Text } from '@chakra-ui/react' | |
| 2 | +import Card from 'components/card/Card' | |
| 3 | +import React, { useEffect, useState } from 'react' | |
| 4 | +import { fetchImage } from "../../../redux/slices/imageSlice"; | |
| 5 | +import { useDispatch, useSelector } from 'react-redux' | |
| 6 | +import ImageView from './imageView'; | |
| 7 | +import AddImage from './addImage'; | |
| 8 | +import { getApi } from 'services/api'; | |
| 9 | +import { IoIosArrowBack } from 'react-icons/io'; | |
| 10 | +import { useNavigate } from 'react-router-dom'; | |
| 11 | +import { putApi } from 'services/api'; | |
| 12 | +import { AddIcon } from '@chakra-ui/icons'; | |
| 13 | +import DataNotFound from 'components/notFoundData'; | |
| 14 | +import { deleteApi } from 'services/api'; | |
| 15 | +import Spinner from 'components/spinner/Spinner'; | |
| 16 | + | |
| 17 | +const ChangeImage = () => { | |
| 18 | + const [imageModal, setImageModal] = useState(false) | |
| 19 | + const dispatch = useDispatch(); | |
| 20 | + const [imageview, setImageView] = useState(false) | |
| 21 | + const [imageviewData, setImageViewData] = useState({}) | |
| 22 | + const [deleteOpen, setDelete] = useState(false) | |
| 23 | + const [data, setData] = useState(false) | |
| 24 | + const [selectedId, setSelectedId] = useState(false) | |
| 25 | + const [isLoding, setIsLoding] = useState(false) | |
| 26 | + const navigate = useNavigate() | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + const fetchData = async (selectedId) => { | |
| 31 | + setIsLoding(true) | |
| 32 | + let result = await getApi(`api/images/view/${selectedId}`); | |
| 33 | + setData(result.data) | |
| 34 | + setIsLoding(false) | |
| 35 | + } | |
| 36 | + const image = useSelector((state) => state?.images?.images); | |
| 37 | + const load = useSelector((state) => state?.images); | |
| 38 | + const handleViewOpen = (item) => { | |
| 39 | + fetchData(item._id) | |
| 40 | + setImageView(!imageview) | |
| 41 | + } | |
| 42 | + const handleViewClose = () => { | |
| 43 | + setImageView(false) | |
| 44 | + } | |
| 45 | + const handleDeleteClose = () => { setDelete(false) } | |
| 46 | + const handleDeleteOpen = (item) => { | |
| 47 | + setDelete(!deleteOpen); | |
| 48 | + setSelectedId(item._id) | |
| 49 | + } | |
| 50 | + | |
| 51 | + const handleDelete = async () => { | |
| 52 | + try { | |
| 53 | + setIsLoding(true) | |
| 54 | + let response = await deleteApi(`api/images/delete/`, selectedId); | |
| 55 | + if (response.status === 200) { | |
| 56 | + dispatch(fetchImage()); | |
| 57 | + setDelete(false); | |
| 58 | + setImageView(false) | |
| 59 | + } | |
| 60 | + } catch (e) { | |
| 61 | + console.log(e); | |
| 62 | + } | |
| 63 | + finally { | |
| 64 | + setIsLoding(false) | |
| 65 | + } | |
| 66 | + } | |
| 67 | + | |
| 68 | + const setImageData = async (item) => { | |
| 69 | + try { | |
| 70 | + setIsLoding(true) | |
| 71 | + let response = await putApi(`api/images/isActive/${item?._id}`, { isActive: true }); | |
| 72 | + if (response.status === 200) { | |
| 73 | + handleViewClose(); | |
| 74 | + dispatch(fetchImage()); | |
| 75 | + } | |
| 76 | + } catch (e) { | |
| 77 | + console.log(e); | |
| 78 | + } | |
| 79 | + finally { | |
| 80 | + setIsLoding(false) | |
| 81 | + } | |
| 82 | + } | |
| 83 | + useEffect(() => { | |
| 84 | + dispatch(fetchImage()); | |
| 85 | + }, [dispatch]); | |
| 86 | + | |
| 87 | + useEffect(() => { | |
| 88 | + if (image?.length === 0) { | |
| 89 | + localStorage.removeItem('persist:image') | |
| 90 | + } | |
| 91 | + }, [image]) | |
| 92 | + | |
| 93 | + return ( | |
| 94 | + <> | |
| 95 | + <Card> | |
| 96 | + <Flex justifyContent={'end'}> | |
| 97 | + <Button variant='brand' size='sm' onClick={() => setImageModal(true)} leftIcon={<AddIcon />}>Add New</Button> | |
| 98 | + <Button onClick={() => navigate('/admin-setting')} variant="brand" size="sm" leftIcon={<IoIosArrowBack />} ml={2}>Back</Button> | |
| 99 | + </Flex> | |
| 100 | + {!(load.status === 'succeeded') ? <Flex justifyContent={'center'} alignItems={'center'} width="100%" color={'black'} fontSize="sm" fontWeight="700"> | |
| 101 | + <Spinner /> | |
| 102 | + </Flex> : <Card> | |
| 103 | + <Grid templateColumns={'repeat(12, 1fr)'} gap={5}> | |
| 104 | + {image?.length > 0 && image?.map((item, i) => ( | |
| 105 | + <GridItem colSpan={{ base: 12, md: 4, lg: 3 }}> | |
| 106 | + <div className="imageCard"> | |
| 107 | + <Image src={item?.authImg} height={"200px"} width={"100%"} /> | |
| 108 | + {item?.isActive === true ? <Box backgroundColor={"#422afb"} color={"#fff"} height={"20px"} width={"140px"} position={"absolute"} top={"18px"} right={"-40px"} transform={"rotate(45deg)"} fontSize={"16px"} display={"flex"} justifyContent={"center"} alignItems={"center"}> | |
| 109 | + Active | |
| 110 | + </Box> : ""} | |
| 111 | + <div className='imageContent'> | |
| 112 | + <Button size='sm' variant="brand" onClick={() => setImageData(item)}>Set Image</Button> | |
| 113 | + <Button size='sm' variant="brand" mx={1} onClick={() => { handleViewOpen(item); setImageViewData(item) }}>View</Button> | |
| 114 | + <Button size="sm" colorScheme="red" disabled={item?.isActive === true ? true : false} onClick={() => { handleDeleteOpen(item) }}>Delete</Button> | |
| 115 | + </div> | |
| 116 | + </div> | |
| 117 | + </GridItem> | |
| 118 | + ))} | |
| 119 | + </Grid> | |
| 120 | + {!image?.length > 0 && <Text textAlign={'center'} width="100%" color={'gray.500'} fontSize="sm" fontWeight="700"><DataNotFound /></Text>} | |
| 121 | + </Card>} | |
| 122 | + </Card> | |
| 123 | + | |
| 124 | + <ImageView isOpen={imageview} | |
| 125 | + onClose={handleViewClose} | |
| 126 | + image={image} | |
| 127 | + handleDeleteOpen={handleDeleteOpen} | |
| 128 | + imageviewData={imageviewData} | |
| 129 | + fetchData={fetchData} | |
| 130 | + data={data} | |
| 131 | + setImageData={setImageData} | |
| 132 | + /> | |
| 133 | + <AddImage imageModal={imageModal} setImageModal={setImageModal} fetchData={fetchImage} /> | |
| 134 | + {/* Delete modal */} | |
| 135 | + <Modal onClose={handleDeleteClose} isOpen={deleteOpen} isCentered> | |
| 136 | + <ModalOverlay /> | |
| 137 | + <ModalContent> | |
| 138 | + <ModalHeader>Delete Images</ModalHeader> | |
| 139 | + <ModalCloseButton /> | |
| 140 | + <ModalBody> | |
| 141 | + Are You Sure To Delete selected Images ? | |
| 142 | + </ModalBody> | |
| 143 | + <ModalFooter> | |
| 144 | + <Button colorScheme="red" size="sm" mr={2} onClick={handleDelete} disabled={isLoding ? true : false} >{isLoding ? <Spinner /> : 'Yes'}</Button> | |
| 145 | + <Button variant="outline" size="sm" onClick={handleDeleteClose}>No</Button> | |
| 146 | + </ModalFooter> | |
| 147 | + </ModalContent> | |
| 148 | + </Modal> | |
| 149 | + </> | |
| 150 | + ) | |
| 151 | +} | |
| 152 | + | |
| 153 | +export default ChangeImage |
+1112-0Client/src/views/admin/invoice/AddEdit.js
| @@ -0,0 +1,1112 @@ | ||
| 1 | +import { AddIcon, CloseIcon } from '@chakra-ui/icons'; | |
| 2 | +import { Box, Button, Checkbox, Drawer, DrawerBody, DrawerContent, DrawerFooter, DrawerHeader, DrawerOverlay, Flex, FormControl, FormLabel, Grid, GridItem, Heading, IconButton, Image, Input, Select, Table, Tbody, Td, Text, Textarea, Th, Thead, Tr } from '@chakra-ui/react'; | |
| 3 | +import Spinner from 'components/spinner/Spinner'; | |
| 4 | +import dayjs from 'dayjs'; | |
| 5 | +import { ErrorMessage, Field, FieldArray, useFormik, Form } from 'formik'; | |
| 6 | +import { useState, useEffect, useCallback } from 'react'; | |
| 7 | +import { LiaMousePointerSolid } from 'react-icons/lia'; | |
| 8 | +import { postApi, getApi, putApi } from 'services/api'; | |
| 9 | +import { generateValidationSchema } from 'utils'; | |
| 10 | +import CustomForm from 'utils/customForm'; | |
| 11 | +import * as yup from 'yup' | |
| 12 | +import { invoicesSchema } from '../../../schema/invoicesSchema'; | |
| 13 | +import UserModel from 'components/commonTableModel/UserModel'; | |
| 14 | +import OpprtunityModel from 'components/commonTableModel/OpprtunityModel'; | |
| 15 | +import { toast } from 'react-toastify'; | |
| 16 | +import { useDispatch, useSelector } from 'react-redux'; | |
| 17 | +import { fetchOpportunityData } from '../../../redux/slices/opportunitySlice'; | |
| 18 | +import AccountModel from 'components/commonTableModel/AccountModel'; | |
| 19 | +import { fetchAccountData } from '../../../redux/slices/accountSlice'; | |
| 20 | +import ContactModel from 'components/commonTableModel/ContactModel'; | |
| 21 | +import { HasAccess } from '../../../redux/accessUtils'; | |
| 22 | +import { fetchInvoicesData } from '../../../redux/slices/invoicesSlice'; | |
| 23 | +import moment from 'moment'; | |
| 24 | + | |
| 25 | +const AddEdit = (props) => { | |
| 26 | + const { isOpen, size, onClose, type, setAction, selectedId, contactId, action } = props; | |
| 27 | + const [isLoding, setIsLoding] = useState(false) | |
| 28 | + const [userModel, setUserModel] = useState(false) | |
| 29 | + const [opprtunityModel, setOpprtunityModel] = useState(false) | |
| 30 | + const [userData, setUserData] = useState([]); | |
| 31 | + const [accountModel, setAccountModel] = useState(false) | |
| 32 | + const [contactModel, setContactModel] = useState(false) | |
| 33 | + const [invoiceDetails, setInvoiceDetails] = useState({}); | |
| 34 | + const [items, setItems] = useState([{ id: 1, productName: "", qty: 0, rate: 0, amount: 0 }]); | |
| 35 | + const [total, setTotal] = useState(0); | |
| 36 | + const [discounts, setDiscount] = useState(0); | |
| 37 | + const [netAmount, setNetAmount] = useState(0); | |
| 38 | + const dispatch = useDispatch(); | |
| 39 | + const user = JSON.parse(localStorage.getItem("user")) | |
| 40 | + const [accountAccess, contactAccess] = HasAccess(['Account', 'Contacts']) | |
| 41 | + const [isOpenPreview, setIsOpenPreview] = useState(false) | |
| 42 | + | |
| 43 | + const opportunityList = useSelector((state) => state?.opportunityData?.data?.data) | |
| 44 | + const accountList = useSelector((state) => state?.accountData?.data?.data) | |
| 45 | + const contactList = useSelector((state) => state?.contactData?.data) | |
| 46 | + const largeLogo = useSelector((state) => state?.images?.images?.filter(item => item?.isActive === true)); | |
| 47 | + | |
| 48 | + const initialValues = { | |
| 49 | + title: type === "edit" ? invoiceDetails?.title : "", | |
| 50 | + quoteNumber: type === "edit" ? invoiceDetails?.quoteNumber : null, | |
| 51 | + quoteDate: type === "edit" ? invoiceDetails?.quoteDate : "", | |
| 52 | + dueDate: type === "edit" ? invoiceDetails?.dueDate : "", | |
| 53 | + invoiceDate: type === "edit" ? invoiceDetails?.invoiceDate : "", | |
| 54 | + status: type === "edit" ? invoiceDetails?.status : "Paid", | |
| 55 | + assignedTo: type === "edit" ? invoiceDetails?.assignedTo : null, | |
| 56 | + description: type === "edit" ? invoiceDetails?.description : "", | |
| 57 | + account: type === "edit" ? invoiceDetails?.account : null, | |
| 58 | + contact: type === "edit" ? invoiceDetails?.contact : contactId ? contactId : null, | |
| 59 | + billingStreet: type === "edit" ? invoiceDetails?.billingStreet : "", | |
| 60 | + shippingStreet: type === "edit" ? invoiceDetails?.shippingStreet : "", | |
| 61 | + billingCity: type === "edit" ? invoiceDetails?.billingCity : "", | |
| 62 | + shippingCity: type === "edit" ? invoiceDetails?.shippingCity : "", | |
| 63 | + billingState: type === "edit" ? invoiceDetails?.billingState : "", | |
| 64 | + shippingState: type === "edit" ? invoiceDetails?.shippingState : "", | |
| 65 | + billingPostalCode: type === "edit" ? invoiceDetails?.billingPostalCode : "", | |
| 66 | + shippingPostalCode: type === "edit" ? invoiceDetails?.shippingPostalCode : "", | |
| 67 | + billingCountry: type === "edit" ? invoiceDetails?.billingCountry : "", | |
| 68 | + shippingCountry: type === "edit" ? invoiceDetails?.shippingCountry : "", | |
| 69 | + isCheck: type === "edit" ? invoiceDetails?.isCheck : false, | |
| 70 | + currency: type === "edit" ? invoiceDetails?.currency : "$", | |
| 71 | + total: type === "edit" ? invoiceDetails?.total : "0", | |
| 72 | + discount: type === "edit" ? invoiceDetails?.discount : "", | |
| 73 | + subtotal: type === "edit" ? invoiceDetails?.subtotal : "0", | |
| 74 | + shipping: type === "edit" ? invoiceDetails?.shipping : "0", | |
| 75 | + shippingTax: type === "edit" ? invoiceDetails?.shippingTax : "", | |
| 76 | + ptax: type === "edit" ? invoiceDetails?.ptax : "0", | |
| 77 | + tax: type === "edit" ? invoiceDetails?.tax : "0", | |
| 78 | + grandTotal: type === "edit" ? invoiceDetails?.grandTotal : "0", | |
| 79 | + discountType: type === "edit" ? invoiceDetails?.discountType : "none", | |
| 80 | + items: type === "edit" ? invoiceDetails?.items : [], | |
| 81 | + createBy: JSON.parse(localStorage.getItem('user'))._id, | |
| 82 | + modifiedBy: JSON.parse(localStorage.getItem('user'))._id | |
| 83 | + }; | |
| 84 | + | |
| 85 | + | |
| 86 | + const addData = async (values) => { | |
| 87 | + try { | |
| 88 | + setIsLoding(true) | |
| 89 | + let response = await postApi('api/invoices/add', values) | |
| 90 | + if (response.status === 200) { | |
| 91 | + onClose(); | |
| 92 | + toast.success(`Invoice Save successfully`) | |
| 93 | + formik.resetForm(); | |
| 94 | + setAction((pre) => !pre) | |
| 95 | + } | |
| 96 | + } catch (e) { | |
| 97 | + console.log(e); | |
| 98 | + toast.error(`server error`) | |
| 99 | + } | |
| 100 | + finally { | |
| 101 | + setIsLoding(false) | |
| 102 | + } | |
| 103 | + }; | |
| 104 | + const editData = async (values) => { | |
| 105 | + try { | |
| 106 | + setIsLoding(true) | |
| 107 | + let response = await putApi(`api/invoices/edit/${selectedId}`, values) | |
| 108 | + if (response.status === 200) { | |
| 109 | + onClose(); | |
| 110 | + toast.success(`Invoice Update successfully`) | |
| 111 | + formik.resetForm(); | |
| 112 | + setAction((pre) => !pre) | |
| 113 | + } | |
| 114 | + } catch (e) { | |
| 115 | + console.log(e); | |
| 116 | + toast.error(`server error`) | |
| 117 | + } | |
| 118 | + finally { | |
| 119 | + setIsLoding(false) | |
| 120 | + } | |
| 121 | + }; | |
| 122 | + | |
| 123 | + const handleCancel = () => { | |
| 124 | + formik.resetForm(); | |
| 125 | + onClose() | |
| 126 | + setIsOpenPreview(false) | |
| 127 | + } | |
| 128 | + | |
| 129 | + const formik = useFormik({ | |
| 130 | + initialValues: initialValues, | |
| 131 | + validationSchema: invoicesSchema, | |
| 132 | + enableReinitialize: true, | |
| 133 | + onSubmit: (values, { resetForm }) => { | |
| 134 | + if (type === "add") { | |
| 135 | + addData(values) | |
| 136 | + } else { | |
| 137 | + const payload = { ...values, modifiedDate: new Date() } | |
| 138 | + editData(payload) | |
| 139 | + } | |
| 140 | + }, | |
| 141 | + }); | |
| 142 | + const { errors, touched, values, handleBlur, handleChange, handleSubmit, setFieldValue, } = formik | |
| 143 | + | |
| 144 | + const handleCheck = (e) => { | |
| 145 | + setFieldValue("isCheck", e.target.checked) | |
| 146 | + if (e.target.checked) { | |
| 147 | + setFieldValue("shippingStreet", values?.billingStreet) | |
| 148 | + setFieldValue("shippingCity", values?.billingCity) | |
| 149 | + setFieldValue("shippingState", values?.billingState) | |
| 150 | + setFieldValue("shippingPostalCode", values?.billingPostalCode) | |
| 151 | + setFieldValue("shippingCountry", values?.billingCountry) | |
| 152 | + } else { | |
| 153 | + setFieldValue("shippingStreet", "") | |
| 154 | + setFieldValue("shippingCity", "") | |
| 155 | + setFieldValue("shippingState", "") | |
| 156 | + setFieldValue("shippingPostalCode", "") | |
| 157 | + setFieldValue("shippingCountry", "") | |
| 158 | + } | |
| 159 | + } | |
| 160 | + | |
| 161 | + const fetchData = async () => { | |
| 162 | + setIsLoding(true) | |
| 163 | + let result = await getApi('api/user/'); | |
| 164 | + setUserData(result?.data?.user); | |
| 165 | + setIsLoding(false) | |
| 166 | + } | |
| 167 | + | |
| 168 | + const fetchInvoiceDetails = async () => { | |
| 169 | + if (type === "edit") { | |
| 170 | + try { | |
| 171 | + setIsLoding(true) | |
| 172 | + let result = await getApi('api/invoices/view/', selectedId) | |
| 173 | + if (result?.status === 200) { | |
| 174 | + setInvoiceDetails(result?.data?.result) | |
| 175 | + } | |
| 176 | + | |
| 177 | + } | |
| 178 | + catch (e) { | |
| 179 | + console.log(e); | |
| 180 | + } | |
| 181 | + finally { | |
| 182 | + setIsLoding(false) | |
| 183 | + } | |
| 184 | + } | |
| 185 | + } | |
| 186 | + const calculateAmounts = (items) => { | |
| 187 | + const totalAmount = items.reduce((sum, item) => sum + item.amount, 0); | |
| 188 | + const totalDiscount = items.reduce((sum, item) => sum + item.totalDiscount, 0); | |
| 189 | + const netAmount = Number(totalAmount) - Number(totalDiscount); | |
| 190 | + return { totalAmount, netAmount, discount: totalDiscount }; | |
| 191 | + }; | |
| 192 | + | |
| 193 | + const handleAddItem = () => { | |
| 194 | + setFieldValue("items", [ | |
| 195 | + ...values.items, | |
| 196 | + { id: values.items.length + 1, productName: "", qty: 0, rate: 0, discount: 0, discountType: "none", totalDiscount: 0, amount: 0 } | |
| 197 | + ]); | |
| 198 | + }; | |
| 199 | + | |
| 200 | + const handleRemoveItem = (index) => { | |
| 201 | + const newItems = values.items.filter((_, idx) => idx !== index); | |
| 202 | + setFieldValue("items", newItems); | |
| 203 | + const { totalAmount, netAmount, discount } = calculateAmounts(newItems); | |
| 204 | + setFieldValue("total", totalAmount); | |
| 205 | + setFieldValue("grandTotal", netAmount); | |
| 206 | + setFieldValue("discount", discount); | |
| 207 | + }; | |
| 208 | + | |
| 209 | + const handleChangeCalculation = (index, field, value) => { | |
| 210 | + const newItems = values.items.map((item, idx) => { | |
| 211 | + if (idx === index) { | |
| 212 | + const updatedItem = { ...item, [field]: value }; | |
| 213 | + if (['qty', 'rate', 'discount', 'discountType'].includes(field)) { | |
| 214 | + const discountValue = updatedItem.discountType === 'percent' | |
| 215 | + ? (updatedItem.rate * updatedItem.qty * updatedItem.discount / 100) | |
| 216 | + : updatedItem.discountType === 'none' | |
| 217 | + ? 0 | |
| 218 | + : updatedItem.discount; | |
| 219 | + updatedItem.amount = updatedItem.rate * updatedItem.qty - discountValue; | |
| 220 | + updatedItem.totalDiscount = discountValue; | |
| 221 | + } | |
| 222 | + return updatedItem; | |
| 223 | + } | |
| 224 | + return item; | |
| 225 | + }); | |
| 226 | + | |
| 227 | + setFieldValue("items", newItems); | |
| 228 | + const { totalAmount, netAmount, discount } = calculateAmounts(newItems); | |
| 229 | + setFieldValue("discount", discount); | |
| 230 | + setFieldValue("total", totalAmount); | |
| 231 | + setFieldValue("subtotal", totalAmount); | |
| 232 | + setFieldValue("grandTotal", netAmount); | |
| 233 | + }; | |
| 234 | + | |
| 235 | + const calculateValues = useCallback(() => { | |
| 236 | + const subtotal = values.subtotal; | |
| 237 | + const shipping = Number(values.shipping); | |
| 238 | + const ptax = Number(values.ptax); | |
| 239 | + const shippingTax = Number(subtotal) + Number(shipping); | |
| 240 | + const tax = shippingTax * ptax / 100; | |
| 241 | + const grandTotal = Number(shippingTax) + Number(tax); | |
| 242 | + | |
| 243 | + setFieldValue('shippingTax', shippingTax?.toFixed(2)); | |
| 244 | + setFieldValue('tax', tax?.toFixed(2)); | |
| 245 | + setFieldValue('grandTotal', grandTotal?.toFixed(2)); | |
| 246 | + }, [values.subtotal, values.shipping, values.ptax]); | |
| 247 | + | |
| 248 | + useEffect(() => { | |
| 249 | + calculateValues(); | |
| 250 | + }, [calculateValues]); | |
| 251 | + | |
| 252 | + useEffect(() => { | |
| 253 | + if (type === "edit") fetchInvoiceDetails(); | |
| 254 | + if (user.role === 'superAdmin') fetchData(); | |
| 255 | + }, [type, selectedId, action]) | |
| 256 | + | |
| 257 | + useEffect(() => { | |
| 258 | + if (opportunityList?.length === 0 || opportunityList === undefined) { dispatch(fetchOpportunityData()) } | |
| 259 | + if (accountList?.length === 0 || accountList === undefined) { dispatch(fetchAccountData()) } | |
| 260 | + }, []) | |
| 261 | + | |
| 262 | + return ( | |
| 263 | + <div> | |
| 264 | + {userModel && <UserModel onClose={() => setUserModel(false)} isOpen={userModel} fieldName={"assignedTo"} setFieldValue={setFieldValue} data={userData} isLoding={isLoding} setIsLoding={setIsLoding} />} | |
| 265 | + {/* {opprtunityModel && <OpprtunityModel onClose={() => setOpprtunityModel(false)} isOpen={opprtunityModel} fieldName={"oppotunity"} setFieldValue={setFieldValue} data={opportunityList} isLoding={isLoding} setIsLoding={setIsLoding} type="quotes" billingState="billingState" billingCity="billingCity" billingPostalCode="billingPostalCode" billingCountry="billingCountry" billingStreet="billingStreet" />} */} | |
| 266 | + {accountModel && <AccountModel onClose={() => setAccountModel(false)} isOpen={accountModel} fieldName={"account"} setFieldValue={setFieldValue} data={accountList} />} | |
| 267 | + {contactModel && <ContactModel isOpen={contactModel} data={contactList} onClose={setContactModel} values={values} fieldName='contact' setFieldValue={setFieldValue} />} | |
| 268 | + | |
| 269 | + <Drawer isOpen={isOpen} size={size}> | |
| 270 | + <DrawerOverlay /> | |
| 271 | + <DrawerContent> | |
| 272 | + <DrawerHeader alignItems={"center"} justifyContent='space-between' display='flex' > | |
| 273 | + {type === "add" ? !isOpenPreview ? "Add" : "Preview" : !isOpenPreview ? "Edit" : "Preview"} Invoice | |
| 274 | + <IconButton onClick={() => handleCancel()} icon={<CloseIcon />} /> | |
| 275 | + </DrawerHeader> | |
| 276 | + { | |
| 277 | + !isOpenPreview ? | |
| 278 | + <DrawerBody> | |
| 279 | + <Grid templateColumns="repeat(12, 1fr)" gap={3}> | |
| 280 | + <GridItem colSpan={{ base: 12 }}> | |
| 281 | + <Heading as="h1" size="md" mt='10px'> | |
| 282 | + Overview | |
| 283 | + </Heading> | |
| 284 | + </GridItem> | |
| 285 | + <GridItem colSpan={{ base: 12, md: 6 }}> | |
| 286 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='8px'> | |
| 287 | + Title<Text color={"red"}>*</Text> | |
| 288 | + </FormLabel> | |
| 289 | + <Input | |
| 290 | + fontSize='sm' | |
| 291 | + value={values.title} | |
| 292 | + name="title" | |
| 293 | + onBlur={handleBlur} | |
| 294 | + onChange={handleChange} | |
| 295 | + placeholder='Title' | |
| 296 | + fontWeight='500' | |
| 297 | + borderColor={errors.title && touched.title ? "red.300" : null} | |
| 298 | + /> | |
| 299 | + <Text mb='10px' fontSize='sm' color={'red'}> {errors.title && touched.title && errors.title}</Text> | |
| 300 | + </GridItem> | |
| 301 | + <GridItem colSpan={{ base: 12, md: 6 }}> | |
| 302 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='8px'> | |
| 303 | + Quote Number | |
| 304 | + </FormLabel> | |
| 305 | + <Input | |
| 306 | + fontSize='sm' | |
| 307 | + value={values.quoteNumber} | |
| 308 | + name="quoteNumber" | |
| 309 | + onBlur={handleBlur} | |
| 310 | + onChange={handleChange} | |
| 311 | + placeholder='Quote Number' | |
| 312 | + fontWeight='500' | |
| 313 | + borderColor={errors.quoteNumber && touched.quoteNumber ? "red.300" : null} | |
| 314 | + /> | |
| 315 | + <Text mb='10px' fontSize='sm' color={'red'}> {errors.quoteNumber && touched.quoteNumber && errors.quoteNumber}</Text> | |
| 316 | + </GridItem> | |
| 317 | + <GridItem colSpan={{ base: 12, md: 6 }}> | |
| 318 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='8px'> | |
| 319 | + Quote Date | |
| 320 | + </FormLabel> | |
| 321 | + <Input | |
| 322 | + fontSize='sm' | |
| 323 | + value={values.quoteDate && dayjs(values.quoteDate).format("YYYY-MM-DD")} | |
| 324 | + name="quoteDate" | |
| 325 | + onBlur={handleBlur} | |
| 326 | + onChange={handleChange} | |
| 327 | + type='date' | |
| 328 | + fontWeight='500' | |
| 329 | + borderColor={errors.quoteDate && touched.quoteDate ? "red.300" : null} | |
| 330 | + /> | |
| 331 | + <Text mb='10px' fontSize='sm' color={'red'}> {errors.quoteDate && touched.quoteDate && errors.quoteDate}</Text> | |
| 332 | + </GridItem> | |
| 333 | + <GridItem colSpan={{ base: 12, md: 6 }}> | |
| 334 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='8px'> | |
| 335 | + Due Date | |
| 336 | + </FormLabel> | |
| 337 | + <Input | |
| 338 | + fontSize='sm' | |
| 339 | + value={values.dueDate && dayjs(values.dueDate).format("YYYY-MM-DD")} | |
| 340 | + name="dueDate" | |
| 341 | + onBlur={handleBlur} | |
| 342 | + onChange={handleChange} | |
| 343 | + type='date' | |
| 344 | + fontWeight='500' | |
| 345 | + borderColor={errors.dueDate && touched.dueDate ? "red.300" : null} | |
| 346 | + /> | |
| 347 | + <Text mb='10px' fontSize='sm' color={'red'}> {errors.dueDate && touched.dueDate && errors.dueDate}</Text> | |
| 348 | + </GridItem> | |
| 349 | + <GridItem colSpan={{ base: 12, md: 6 }}> | |
| 350 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='8px'> | |
| 351 | + Invoice Date | |
| 352 | + </FormLabel> | |
| 353 | + <Input | |
| 354 | + fontSize='sm' | |
| 355 | + value={values.invoiceDate && dayjs(values.invoiceDate).format("YYYY-MM-DD")} | |
| 356 | + name="invoiceDate" | |
| 357 | + onBlur={handleBlur} | |
| 358 | + onChange={handleChange} | |
| 359 | + type='date' | |
| 360 | + fontWeight='500' | |
| 361 | + borderColor={errors.invoiceDate && touched.invoiceDate ? "red.300" : null} | |
| 362 | + /> | |
| 363 | + <Text mb='10px' fontSize='sm' color={'red'}> {errors.invoiceDate && touched.invoiceDate && errors.invoiceDate}</Text> | |
| 364 | + </GridItem> | |
| 365 | + { | |
| 366 | + user.role === 'superAdmin' && | |
| 367 | + <GridItem colSpan={{ base: 12, md: 6 }} > | |
| 368 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='8px'> | |
| 369 | + Assigned To | |
| 370 | + </FormLabel> | |
| 371 | + <Flex justifyContent={'space-between'}> | |
| 372 | + <Select | |
| 373 | + value={values.assignedTo} | |
| 374 | + name="assignedTo" | |
| 375 | + onBlur={handleBlur} | |
| 376 | + onChange={handleChange} | |
| 377 | + mb={errors.assignedTo && touched.assignedTo ? undefined : '10px'} | |
| 378 | + fontWeight='500' | |
| 379 | + placeholder={'Assigned To'} | |
| 380 | + borderColor={errors.assignedTo && touched.assignedTo ? "red.300" : null} | |
| 381 | + > | |
| 382 | + {userData?.map((item) => { | |
| 383 | + return <option value={item._id} key={item._id}>{`${item?.firstName} ${item?.lastName}`}</option> | |
| 384 | + })} | |
| 385 | + </Select> | |
| 386 | + <IconButton onClick={() => setUserModel(true)} ml={2} fontSize='25px' icon={<LiaMousePointerSolid />} /> | |
| 387 | + </Flex> | |
| 388 | + </GridItem> | |
| 389 | + } | |
| 390 | + <GridItem colSpan={{ base: 12, md: 6 }}> | |
| 391 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='8px'> | |
| 392 | + Status | |
| 393 | + </FormLabel> | |
| 394 | + <Select | |
| 395 | + value={values.status} | |
| 396 | + name="status" | |
| 397 | + onBlur={handleBlur} | |
| 398 | + onChange={handleChange} | |
| 399 | + mb={errors.status && touched.status ? undefined : '10px'} | |
| 400 | + fontWeight='500' | |
| 401 | + placeholder={'Status'} | |
| 402 | + borderColor={errors.status && touched.status ? "red.300" : null} | |
| 403 | + > | |
| 404 | + <option value="Paid">Paid</option> | |
| 405 | + <option value="Unpaid">Unpaid</option> | |
| 406 | + <option value="Cancelled">Cancelled</option> | |
| 407 | + </Select> | |
| 408 | + <Text mb='10px' fontSize='sm' color={'red'}> {errors.status && touched.status && errors.status}</Text> | |
| 409 | + </GridItem> | |
| 410 | + <GridItem colSpan={{ base: 12 }}> | |
| 411 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='8px'> | |
| 412 | + Description | |
| 413 | + </FormLabel> | |
| 414 | + <Textarea | |
| 415 | + fontSize='sm' | |
| 416 | + value={values.description} | |
| 417 | + name="description" | |
| 418 | + resize={"none"} | |
| 419 | + onBlur={handleBlur} | |
| 420 | + onChange={handleChange} | |
| 421 | + placeholder='Description' | |
| 422 | + fontWeight='500' | |
| 423 | + borderColor={errors.description && touched.description ? "red.300" : null} | |
| 424 | + /> | |
| 425 | + <Text mb='10px' fontSize='sm' color={'red'}> {errors.description && touched.description && errors.description}</Text> | |
| 426 | + </GridItem> | |
| 427 | + <GridItem colSpan={{ base: 12 }}> | |
| 428 | + <Heading as="h1" size="md" mt='10px'> | |
| 429 | + Address Information | |
| 430 | + </Heading> | |
| 431 | + </GridItem> | |
| 432 | + { | |
| 433 | + (user.role === 'superAdmin' || accountAccess?.view) && | |
| 434 | + <GridItem colSpan={{ base: 12, md: 6 }}> | |
| 435 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='8px'> | |
| 436 | + Account | |
| 437 | + </FormLabel> | |
| 438 | + <Flex justifyContent={'space-between'}> | |
| 439 | + <Select | |
| 440 | + value={values.account} | |
| 441 | + name="account" | |
| 442 | + onBlur={handleBlur} | |
| 443 | + onChange={handleChange} | |
| 444 | + mb={errors.account && touched.account ? undefined : '10px'} | |
| 445 | + fontWeight='500' | |
| 446 | + placeholder={'Account'} | |
| 447 | + borderColor={errors.account && touched.account ? "red.300" : null} | |
| 448 | + > | |
| 449 | + {accountList?.length > 0 && accountList?.map((item) => { | |
| 450 | + return <option value={item._id} key={item._id}>{`${item?.name}`}</option> | |
| 451 | + })} | |
| 452 | + </Select> | |
| 453 | + <IconButton onClick={() => setAccountModel(true)} ml={2} fontSize='25px' icon={<LiaMousePointerSolid />} /> | |
| 454 | + </Flex> | |
| 455 | + <Text mb='10px' fontSize='sm' color={'red'}> {errors.account && touched.account && errors.account}</Text> | |
| 456 | + </GridItem> | |
| 457 | + } | |
| 458 | + { | |
| 459 | + (user.role === 'superAdmin' || contactAccess?.view) && | |
| 460 | + <GridItem colSpan={{ base: 12, md: 6 }}> | |
| 461 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='8px'> | |
| 462 | + Contact | |
| 463 | + </FormLabel> | |
| 464 | + <Flex justifyContent={'space-between'}> | |
| 465 | + <Select | |
| 466 | + value={values.contact} | |
| 467 | + name="contact" | |
| 468 | + onBlur={handleBlur} | |
| 469 | + onChange={handleChange} | |
| 470 | + mb={errors.contact && touched.contact ? undefined : '10px'} | |
| 471 | + fontWeight='500' | |
| 472 | + placeholder={'Contact'} | |
| 473 | + borderColor={errors.contact && touched.contact ? "red.300" : null} | |
| 474 | + > | |
| 475 | + {contactList?.length > 0 && contactList?.map((item) => { | |
| 476 | + return <option value={item._id} key={item._id}>{`${item.firstName} ${item.lastName}`}</option> | |
| 477 | + })} | |
| 478 | + </Select> | |
| 479 | + <IconButton onClick={() => setContactModel(true)} ml={2} fontSize='25px' icon={<LiaMousePointerSolid />} /> | |
| 480 | + </Flex> | |
| 481 | + <Text mb='10px' fontSize='sm' color={'red'}> {errors.contact && touched.contact && errors.contact}</Text> | |
| 482 | + </GridItem> | |
| 483 | + } | |
| 484 | + | |
| 485 | + <GridItem colSpan={{ base: 12, md: 6 }}> | |
| 486 | + <Heading as="h1" size="md" mt='10px'> | |
| 487 | + Billing Address | |
| 488 | + </Heading> | |
| 489 | + </GridItem> | |
| 490 | + <GridItem colSpan={{ base: 12, md: 6 }}> | |
| 491 | + <Heading as="h1" size="md" mt='10px'> | |
| 492 | + Shipping Address | |
| 493 | + </Heading> | |
| 494 | + </GridItem> | |
| 495 | + | |
| 496 | + <GridItem colSpan={{ base: 12, md: 6 }}> | |
| 497 | + <Checkbox isChecked={values?.isCheck} onChange={(e) => handleCheck(e)} visibility={"hidden"}> | |
| 498 | + Copy address from left | |
| 499 | + </Checkbox> | |
| 500 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='8px' mt={3}> | |
| 501 | + Billing Street | |
| 502 | + </FormLabel> | |
| 503 | + <Input | |
| 504 | + fontSize='sm' | |
| 505 | + value={values.billingStreet} | |
| 506 | + name="billingStreet" | |
| 507 | + onBlur={handleBlur} | |
| 508 | + onChange={handleChange} | |
| 509 | + placeholder='Billing Street' | |
| 510 | + fontWeight='500' | |
| 511 | + borderColor={errors.billingStreet && touched.billingStreet ? "red.300" : null} | |
| 512 | + /> | |
| 513 | + <Text mb='10px' fontSize='sm' color={'red'}> {errors.billingStreet && touched.billingStreet && errors.billingStreet}</Text> | |
| 514 | + </GridItem> | |
| 515 | + | |
| 516 | + <GridItem colSpan={{ base: 12, md: 6 }}> | |
| 517 | + <Checkbox isChecked={values?.isCheck} onChange={(e) => handleCheck(e)}> | |
| 518 | + Copy address from left | |
| 519 | + </Checkbox> | |
| 520 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='8px' mt={3}> | |
| 521 | + Shipping Street | |
| 522 | + </FormLabel> | |
| 523 | + <Input | |
| 524 | + fontSize='sm' | |
| 525 | + value={values.shippingStreet} | |
| 526 | + name="shippingStreet" | |
| 527 | + disabled={values?.isCheck} | |
| 528 | + onBlur={handleBlur} | |
| 529 | + onChange={handleChange} | |
| 530 | + placeholder='Shipping Street' | |
| 531 | + fontWeight='500' | |
| 532 | + borderColor={errors.shippingStreet && touched.shippingStreet ? "red.300" : null} | |
| 533 | + /> | |
| 534 | + <Text mb='10px' fontSize='sm' color={'red'}> {errors.shippingStreet && touched.shippingStreet && errors.shippingStreet}</Text> | |
| 535 | + </GridItem> | |
| 536 | + <GridItem colSpan={{ base: 12, md: 6 }}> | |
| 537 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='8px'> | |
| 538 | + Billing City | |
| 539 | + </FormLabel> | |
| 540 | + <Input | |
| 541 | + fontSize='sm' | |
| 542 | + value={values.billingCity} | |
| 543 | + name="billingCity" | |
| 544 | + onBlur={handleBlur} | |
| 545 | + onChange={handleChange} | |
| 546 | + placeholder='Billing City' | |
| 547 | + fontWeight='500' | |
| 548 | + borderColor={errors.billingCity && touched.billingCity ? "red.300" : null} | |
| 549 | + /> | |
| 550 | + <Text mb='10px' fontSize='sm' color={'red'}> {errors.billingCity && touched.billingCity && errors.billingCity}</Text> | |
| 551 | + </GridItem> | |
| 552 | + <GridItem colSpan={{ base: 12, md: 6 }}> | |
| 553 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='8px'> | |
| 554 | + Shipping City | |
| 555 | + </FormLabel> | |
| 556 | + <Input | |
| 557 | + fontSize='sm' | |
| 558 | + value={values.shippingCity} | |
| 559 | + name="shippingCity" | |
| 560 | + disabled={values?.isCheck} | |
| 561 | + onBlur={handleBlur} | |
| 562 | + onChange={handleChange} | |
| 563 | + placeholder='Shipping City' | |
| 564 | + fontWeight='500' | |
| 565 | + borderColor={errors.shippingCity && touched.shippingCity ? "red.300" : null} | |
| 566 | + /> | |
| 567 | + <Text mb='10px' fontSize='sm' color={'red'}> {errors.shippingCity && touched.shippingCity && errors.shippingCity}</Text> | |
| 568 | + </GridItem> | |
| 569 | + <GridItem colSpan={{ base: 12, md: 6 }}> | |
| 570 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='8px'> | |
| 571 | + Billing State | |
| 572 | + </FormLabel> | |
| 573 | + <Input | |
| 574 | + fontSize='sm' | |
| 575 | + value={values.billingState} | |
| 576 | + name="billingState" | |
| 577 | + onBlur={handleBlur} | |
| 578 | + onChange={handleChange} | |
| 579 | + placeholder='Billing State' | |
| 580 | + fontWeight='500' | |
| 581 | + borderColor={errors.billingState && touched.billingState ? "red.300" : null} | |
| 582 | + /> | |
| 583 | + <Text mb='10px' fontSize='sm' color={'red'}> {errors.billingState && touched.billingState && errors.billingState}</Text> | |
| 584 | + </GridItem> | |
| 585 | + <GridItem colSpan={{ base: 12, md: 6 }}> | |
| 586 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='8px'> | |
| 587 | + Shipping State | |
| 588 | + </FormLabel> | |
| 589 | + <Input | |
| 590 | + fontSize='sm' | |
| 591 | + value={values.shippingState} | |
| 592 | + disabled={values?.isCheck} | |
| 593 | + name="shippingState" | |
| 594 | + onBlur={handleBlur} | |
| 595 | + onChange={handleChange} | |
| 596 | + placeholder='Shipping State' | |
| 597 | + fontWeight='500' | |
| 598 | + borderColor={errors.shippingState && touched.shippingState ? "red.300" : null} | |
| 599 | + /> | |
| 600 | + <Text mb='10px' fontSize='sm' color={'red'}> {errors.shippingState && touched.shippingState && errors.shippingState}</Text> | |
| 601 | + </GridItem> | |
| 602 | + <GridItem colSpan={{ base: 12, md: 6 }}> | |
| 603 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='8px'> | |
| 604 | + Billing Postal Code | |
| 605 | + </FormLabel> | |
| 606 | + <Input | |
| 607 | + fontSize='sm' | |
| 608 | + value={values.billingPostalCode} | |
| 609 | + name="billingPostalCode" | |
| 610 | + onBlur={handleBlur} | |
| 611 | + onChange={handleChange} | |
| 612 | + type='number' | |
| 613 | + placeholder='Billing Postal Code' | |
| 614 | + fontWeight='500' | |
| 615 | + borderColor={errors.billingPostalCode && touched.billingPostalCode ? "red.300" : null} | |
| 616 | + /> | |
| 617 | + <Text mb='10px' fontSize='sm' color={'red'}> {errors.billingPostalCode && touched.billingPostalCode && errors.billingPostalCode}</Text> | |
| 618 | + </GridItem> | |
| 619 | + <GridItem colSpan={{ base: 12, md: 6 }}> | |
| 620 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='8px'> | |
| 621 | + Shipping Postal Code | |
| 622 | + </FormLabel> | |
| 623 | + <Input | |
| 624 | + fontSize='sm' | |
| 625 | + value={values.shippingPostalCode} | |
| 626 | + disabled={values?.isCheck} | |
| 627 | + name="shippingPostalCode" | |
| 628 | + type='number' | |
| 629 | + onBlur={handleBlur} | |
| 630 | + onChange={handleChange} | |
| 631 | + placeholder='Shipping Postal Code' | |
| 632 | + fontWeight='500' | |
| 633 | + borderColor={errors.shippingPostalCode && touched.shippingPostalCode ? "red.300" : null} | |
| 634 | + /> | |
| 635 | + <Text mb='10px' fontSize='sm' color={'red'}> {errors.shippingPostalCode && touched.shippingPostalCode && errors.shippingPostalCode}</Text> | |
| 636 | + </GridItem> | |
| 637 | + <GridItem colSpan={{ base: 12, md: 6 }}> | |
| 638 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='8px'> | |
| 639 | + Billing Country | |
| 640 | + </FormLabel> | |
| 641 | + <Input | |
| 642 | + fontSize='sm' | |
| 643 | + value={values.billingCountry} | |
| 644 | + name="billingCountry" | |
| 645 | + onBlur={handleBlur} | |
| 646 | + onChange={handleChange} | |
| 647 | + placeholder='Billing Country' | |
| 648 | + fontWeight='500' | |
| 649 | + borderColor={errors.billingCountry && touched.billingCountry ? "red.300" : null} | |
| 650 | + /> | |
| 651 | + <Text mb='10px' fontSize='sm' color={'red'}> {errors.billingCountry && touched.billingCountry && errors.billingCountry}</Text> | |
| 652 | + </GridItem> | |
| 653 | + <GridItem colSpan={{ base: 12, md: 6 }}> | |
| 654 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='8px'> | |
| 655 | + Shipping Country | |
| 656 | + </FormLabel> | |
| 657 | + <Input | |
| 658 | + fontSize='sm' | |
| 659 | + value={values.shippingCountry} | |
| 660 | + name="shippingCountry" | |
| 661 | + onBlur={handleBlur} | |
| 662 | + onChange={handleChange} | |
| 663 | + disabled={values?.isCheck} | |
| 664 | + placeholder='Shipping Country' | |
| 665 | + fontWeight='500' | |
| 666 | + borderColor={errors.shippingCountry && touched.shippingCountry ? "red.300" : null} | |
| 667 | + /> | |
| 668 | + <Text mb='10px' fontSize='sm' color={'red'}> {errors.shippingCountry && touched.shippingCountry && errors.shippingCountry}</Text> | |
| 669 | + </GridItem> | |
| 670 | + | |
| 671 | + <GridItem colSpan={{ base: 12 }}> | |
| 672 | + <Heading as="h1" size="md" mt='10px'> | |
| 673 | + Line Items | |
| 674 | + </Heading> | |
| 675 | + </GridItem> | |
| 676 | + <GridItem colSpan={{ base: 12 }}> | |
| 677 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='8px'> | |
| 678 | + Currency | |
| 679 | + </FormLabel> | |
| 680 | + <Select | |
| 681 | + value={values.currency} | |
| 682 | + name="currency" | |
| 683 | + onBlur={handleBlur} | |
| 684 | + onChange={handleChange} | |
| 685 | + mb={errors.currency && touched.currency ? undefined : '10px'} | |
| 686 | + fontWeight='500' | |
| 687 | + placeholder={'Select Currency'} | |
| 688 | + borderColor={errors.currency && touched.currency ? "red.300" : null} | |
| 689 | + > | |
| 690 | + <option value="$" selected>USD</option> | |
| 691 | + </Select> | |
| 692 | + <Text mb='10px' fontSize='sm' color={'red'}> {errors.currency && touched.currency && errors.currency}</Text> | |
| 693 | + </GridItem> | |
| 694 | + <GridItem colSpan={{ base: 12 }}> | |
| 695 | + <Box> | |
| 696 | + <Table variant="simple" size="sm" mt={5} backgroundColor="#f9f9f9"> | |
| 697 | + <Thead> | |
| 698 | + <Tr> | |
| 699 | + <Th></Th> | |
| 700 | + <Th>#</Th> | |
| 701 | + <Th>Item</Th> | |
| 702 | + <Th>Qty</Th> | |
| 703 | + <Th>Rate</Th> | |
| 704 | + <Th>Discount</Th> | |
| 705 | + <Th>Amount</Th> | |
| 706 | + </Tr> | |
| 707 | + </Thead> | |
| 708 | + <Tbody> | |
| 709 | + {values?.items?.length > 0 && values?.items?.map((item, index) => ( | |
| 710 | + <Tr key={item.id}> | |
| 711 | + <Td className="text-center"> | |
| 712 | + <IconButton | |
| 713 | + icon={<CloseIcon />} | |
| 714 | + onClick={() => handleRemoveItem(index)} | |
| 715 | + colorScheme="red" | |
| 716 | + variant="outline" | |
| 717 | + size="sm" | |
| 718 | + style={{ fontSize: "10px" }} | |
| 719 | + /> | |
| 720 | + </Td> | |
| 721 | + <Td>{index + 1}</Td> | |
| 722 | + <Td> | |
| 723 | + <FormControl> | |
| 724 | + <Input | |
| 725 | + type="text" | |
| 726 | + value={item?.productName} | |
| 727 | + onChange={(e) => handleChangeCalculation(index, 'productName', e.target.value)} | |
| 728 | + size="sm" | |
| 729 | + /> | |
| 730 | + </FormControl> | |
| 731 | + </Td> | |
| 732 | + <Td> | |
| 733 | + <FormControl> | |
| 734 | + <Input | |
| 735 | + type="number" | |
| 736 | + value={item?.qty} | |
| 737 | + onChange={(e) => handleChangeCalculation(index, 'qty', Number(e.target.value))} | |
| 738 | + size="sm" | |
| 739 | + /> | |
| 740 | + </FormControl> | |
| 741 | + </Td> | |
| 742 | + <Td> | |
| 743 | + <FormControl> | |
| 744 | + <Input | |
| 745 | + type="number" | |
| 746 | + value={item?.rate} | |
| 747 | + onChange={(e) => handleChangeCalculation(index, 'rate', Number(e.target.value))} | |
| 748 | + size="sm" | |
| 749 | + /> | |
| 750 | + </FormControl> | |
| 751 | + </Td> | |
| 752 | + <Td style={{ display: "flex" }}> | |
| 753 | + <FormControl> | |
| 754 | + <Input | |
| 755 | + type="number" | |
| 756 | + value={item?.discount} | |
| 757 | + onChange={(e) => handleChangeCalculation(index, 'discount', Number(e.target.value))} | |
| 758 | + size="sm" | |
| 759 | + disabled={item?.discountType === "none"} | |
| 760 | + /> | |
| 761 | + <Select | |
| 762 | + value={item?.discountType} | |
| 763 | + onChange={(e) => { handleChangeCalculation(index, 'discountType', e.target.value); }} | |
| 764 | + size="sm" | |
| 765 | + > | |
| 766 | + <option value="none">none</option> | |
| 767 | + <option value="percent">%</option> | |
| 768 | + <option value="flatAmount">{values?.currency}</option> | |
| 769 | + </Select> | |
| 770 | + </FormControl> | |
| 771 | + </Td> | |
| 772 | + <Td> | |
| 773 | + <FormControl> | |
| 774 | + <Input | |
| 775 | + type="number" | |
| 776 | + value={item?.amount} | |
| 777 | + readOnly | |
| 778 | + size="sm" | |
| 779 | + /> | |
| 780 | + </FormControl> | |
| 781 | + </Td> | |
| 782 | + </Tr> | |
| 783 | + ))} | |
| 784 | + <Tr> | |
| 785 | + <Td colSpan={4} display="flex" justifyContent={"end"}> | |
| 786 | + <IconButton | |
| 787 | + icon={<AddIcon />} | |
| 788 | + onClick={handleAddItem} | |
| 789 | + colorScheme="green" | |
| 790 | + variant="outline" | |
| 791 | + size="sm" | |
| 792 | + style={{ fontSize: "10px" }} | |
| 793 | + /> | |
| 794 | + </Td> | |
| 795 | + <Td></Td> | |
| 796 | + <Td></Td> | |
| 797 | + <Td></Td> | |
| 798 | + </Tr> | |
| 799 | + </Tbody> | |
| 800 | + </Table> | |
| 801 | + | |
| 802 | + </Box> | |
| 803 | + </GridItem> | |
| 804 | + <GridItem colSpan={{ base: 12 }}> | |
| 805 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='8px'> | |
| 806 | + Total | |
| 807 | + </FormLabel> | |
| 808 | + <Input | |
| 809 | + fontSize='sm' | |
| 810 | + value={values.total} | |
| 811 | + name="total" | |
| 812 | + onBlur={handleBlur} | |
| 813 | + onChange={handleChange} | |
| 814 | + placeholder='Total' | |
| 815 | + fontWeight='500' | |
| 816 | + type='number' | |
| 817 | + borderColor={errors.total && touched.total ? "red.300" : null} | |
| 818 | + /> | |
| 819 | + <Text mb='10px' fontSize='sm' color={'red'}> {errors.total && touched.total && errors.total}</Text> | |
| 820 | + </GridItem> | |
| 821 | + <GridItem colSpan={{ base: 12 }}> | |
| 822 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='8px'> | |
| 823 | + Total Discount | |
| 824 | + </FormLabel> | |
| 825 | + <Input | |
| 826 | + fontSize='sm' | |
| 827 | + value={values.discount} | |
| 828 | + name="discount" | |
| 829 | + onBlur={handleBlur} | |
| 830 | + type='number' | |
| 831 | + onChange={handleChange} | |
| 832 | + fontWeight='500' | |
| 833 | + borderColor={errors.discount && touched.discount ? "red.300" : null} | |
| 834 | + /> | |
| 835 | + <Text mb='10px' fontSize='sm' color={'red'}> {errors.discount && touched.discount && errors.discount}</Text> | |
| 836 | + </GridItem> | |
| 837 | + <GridItem colSpan={{ base: 12 }}> | |
| 838 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='8px'> | |
| 839 | + Subtotal | |
| 840 | + </FormLabel> | |
| 841 | + <Input | |
| 842 | + fontSize='sm' | |
| 843 | + value={values.subtotal} | |
| 844 | + name="subtotal" | |
| 845 | + type='number' | |
| 846 | + onBlur={handleBlur} | |
| 847 | + onChange={handleChange} | |
| 848 | + placeholder='Subtotal' | |
| 849 | + fontWeight='500' | |
| 850 | + borderColor={errors.subtotal && touched.subtotal ? "red.300" : null} | |
| 851 | + /> | |
| 852 | + <Text mb='10px' fontSize='sm' color={'red'}> {errors.subtotal && touched.subtotal && errors.subtotal}</Text> | |
| 853 | + </GridItem> | |
| 854 | + <GridItem colSpan={{ base: 12 }}> | |
| 855 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='8px'> | |
| 856 | + Shipping | |
| 857 | + </FormLabel> | |
| 858 | + <Input | |
| 859 | + fontSize='sm' | |
| 860 | + value={values.shipping} | |
| 861 | + name="shipping" | |
| 862 | + type='number' | |
| 863 | + onBlur={handleBlur} | |
| 864 | + onChange={handleChange} | |
| 865 | + placeholder='Shipping' | |
| 866 | + fontWeight='500' | |
| 867 | + borderColor={errors.shipping && touched.shipping ? "red.300" : null} | |
| 868 | + /> | |
| 869 | + <Text mb='10px' fontSize='sm' color={'red'}> {errors.shipping && touched.shipping && errors.shipping}</Text> | |
| 870 | + </GridItem> | |
| 871 | + <GridItem colSpan={{ base: 12, md: 10 }}> | |
| 872 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='8px'> | |
| 873 | + Shipping Tax | |
| 874 | + </FormLabel> | |
| 875 | + <Input | |
| 876 | + fontSize='sm' | |
| 877 | + value={values.shippingTax} | |
| 878 | + name="shippingTax" | |
| 879 | + onBlur={handleBlur} | |
| 880 | + onChange={handleChange} | |
| 881 | + placeholder='Shipping Tax' | |
| 882 | + type='number' | |
| 883 | + fontWeight='500' | |
| 884 | + borderColor={errors.shippingTax && touched.shippingTax ? "red.300" : null} | |
| 885 | + /> | |
| 886 | + <Text mb='10px' fontSize='sm' color={'red'}> {errors.shippingTax && touched.shippingTax && errors.shippingTax}</Text> | |
| 887 | + </GridItem> | |
| 888 | + <GridItem colSpan={{ base: 12, md: 2 }}> | |
| 889 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='8px' visibility={"hidden"}> | |
| 890 | + - | |
| 891 | + </FormLabel> | |
| 892 | + <Select | |
| 893 | + value={values.ptax} | |
| 894 | + name="ptax" | |
| 895 | + onBlur={handleBlur} | |
| 896 | + onChange={handleChange} | |
| 897 | + mb={errors.ptax && touched.ptax ? undefined : '10px'} | |
| 898 | + fontWeight='500' | |
| 899 | + borderColor={errors.ptax && touched.ptax ? "red.300" : null} | |
| 900 | + > | |
| 901 | + <option value="0">0%</option> | |
| 902 | + <option value="10">10%</option> | |
| 903 | + <option value="18">18%</option> | |
| 904 | + </Select> | |
| 905 | + <Text mb='10px' fontSize='sm' color={'red'}> {errors.ptax && touched.ptax && errors.ptax}</Text> | |
| 906 | + </GridItem> | |
| 907 | + <GridItem colSpan={{ base: 12 }}> | |
| 908 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='8px' > | |
| 909 | + Tax | |
| 910 | + </FormLabel> | |
| 911 | + <Input | |
| 912 | + fontSize='sm' | |
| 913 | + value={values.tax} | |
| 914 | + name="tax" | |
| 915 | + onBlur={handleBlur} | |
| 916 | + onChange={handleChange} | |
| 917 | + fontWeight='500' | |
| 918 | + type="number" | |
| 919 | + placeholder='Tax' | |
| 920 | + borderColor={errors.tax && touched.tax ? "red.300" : null} | |
| 921 | + /> | |
| 922 | + <Text mb='10px' fontSize='sm' color={'red'}> {errors.tax && touched.tax && errors.tax}</Text> | |
| 923 | + </GridItem> | |
| 924 | + <GridItem colSpan={{ base: 12 }}> | |
| 925 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='8px' > | |
| 926 | + Grand Total | |
| 927 | + </FormLabel> | |
| 928 | + <Input | |
| 929 | + fontSize='sm' | |
| 930 | + value={values.grandTotal} | |
| 931 | + name="grandTotal" | |
| 932 | + onBlur={handleBlur} | |
| 933 | + onChange={handleChange} | |
| 934 | + fontWeight='500' | |
| 935 | + type="number" | |
| 936 | + placeholder='Grand Total' | |
| 937 | + borderColor={errors.grandTotal && touched.grandTotal ? "red.300" : null} | |
| 938 | + /> | |
| 939 | + <Text mb='10px' fontSize='sm' color={'red'}> {errors.grandTotal && touched.grandTotal && errors.grandTotal}</Text> | |
| 940 | + </GridItem> | |
| 941 | + </Grid> | |
| 942 | + </DrawerBody> | |
| 943 | + : | |
| 944 | + <DrawerBody> | |
| 945 | + <div className="invoice-container" > | |
| 946 | + <div className="invoice-header"> | |
| 947 | + <div className=""> | |
| 948 | + <Image | |
| 949 | + style={{ width: "100%", height: '52px' }} | |
| 950 | + src={largeLogo[0]?.logoLgImg} | |
| 951 | + alt="Logo" | |
| 952 | + cursor="pointer" | |
| 953 | + userSelect="none" | |
| 954 | + my={2} | |
| 955 | + /> | |
| 956 | + </div> | |
| 957 | + <div className="invoice-details"> | |
| 958 | + <table> | |
| 959 | + <tr> | |
| 960 | + <th style={{ textAlign: "start" }}>Invoice No.</th> | |
| 961 | + <td>:</td> | |
| 962 | + <td style={{ textAlign: "start" }}>{type === "edit" ? invoiceDetails?.invoiceNumber : ""}</td> | |
| 963 | + </tr> | |
| 964 | + <tr> | |
| 965 | + <th style={{ textAlign: "start" }}>Invoice Date</th> | |
| 966 | + <td>:</td> | |
| 967 | + <td style={{ textAlign: "start" }}>{values?.invoiceDate && moment(values?.invoiceDate).format("DD-MM-YYYY")}</td> | |
| 968 | + </tr> | |
| 969 | + <tr> | |
| 970 | + <th style={{ textAlign: "start" }}>Name</th> | |
| 971 | + <td>:</td> | |
| 972 | + <td style={{ textAlign: "start" }}>{values?.title}</td> | |
| 973 | + </tr> | |
| 974 | + <tr> | |
| 975 | + <th style={{ textAlign: "start" }}>Status</th> | |
| 976 | + <td>:</td> | |
| 977 | + <td style={{ textAlign: "start" }}>{values?.status}</td> | |
| 978 | + </tr> | |
| 979 | + </table> | |
| 980 | + </div> | |
| 981 | + </div> | |
| 982 | + <div className="address-section"> | |
| 983 | + <div className="address"> | |
| 984 | + <strong>Billing Address</strong><br /> | |
| 985 | + <p style={{ width: "250px", wordBreak: "break-all" }}> | |
| 986 | + {values?.billingStreet ? `${values?.billingStreet},${values?.billingCity},` : ""} | |
| 987 | + </p> | |
| 988 | + <p style={{ width: "250px", wordBreak: "break-all" }}> | |
| 989 | + {values?.billingState ? `${values?.billingState},` : ""} | |
| 990 | + </p> | |
| 991 | + <p style={{ width: "250px", wordBreak: "break-all" }}> | |
| 992 | + {`${values?.billingCountry} - ${values?.billingPostalCode}`} | |
| 993 | + </p> | |
| 994 | + </div> | |
| 995 | + <div className="address"> | |
| 996 | + <strong>Shipping Address</strong><br /> | |
| 997 | + <p style={{ width: "250px", wordBreak: "break-all" }}> | |
| 998 | + {values?.shippingStreet ? `${values?.shippingStreet},${values?.shippingCity},` : ""} | |
| 999 | + </p> | |
| 1000 | + <p style={{ width: "250px", wordBreak: "break-all" }}> | |
| 1001 | + {values?.shippingState ? `${values?.shippingState},` : ""} | |
| 1002 | + </p> | |
| 1003 | + <p style={{ width: "250px", wordBreak: "break-all" }}> | |
| 1004 | + {`${values?.shippingCountry} - ${values?.shippingPostalCode}`} | |
| 1005 | + </p> | |
| 1006 | + </div> | |
| 1007 | + </div> | |
| 1008 | + <table className="invoice-table"> | |
| 1009 | + <thead> | |
| 1010 | + <tr> | |
| 1011 | + <th style={{ width: "50px" }}>S No</th> | |
| 1012 | + <th style={{ width: "100px" }}>ITEM</th> | |
| 1013 | + <th style={{ width: "50px" }}>QTY</th> | |
| 1014 | + <th style={{ width: "50px" }}>RATE</th> | |
| 1015 | + <th style={{ width: "50px" }}>DISCOUNT</th> | |
| 1016 | + <th style={{ width: "50px" }}>AMOUNT</th> | |
| 1017 | + </tr> | |
| 1018 | + </thead> | |
| 1019 | + <tbody> | |
| 1020 | + { | |
| 1021 | + values?.items?.length > 0 && values?.items?.map((item, index) => ( | |
| 1022 | + <tr key={index}> | |
| 1023 | + <td>{item?.id}</td> | |
| 1024 | + <td>{item?.productName}</td> | |
| 1025 | + <td>{item?.qty}</td> | |
| 1026 | + <td>{item?.rate}</td> | |
| 1027 | + <td>{`${item?.discountType === "percent" ? `${item?.discount}%` : item?.discountType === "flatAmount" ? `${values?.currency}${item?.discount}` : item?.discountType === "none" ? 0 : ""}`}</td> | |
| 1028 | + <td>{item?.amount}</td> | |
| 1029 | + </tr> | |
| 1030 | + )) | |
| 1031 | + } | |
| 1032 | + </tbody> | |
| 1033 | + </table> | |
| 1034 | + <div className="totals"> | |
| 1035 | + <table> | |
| 1036 | + <tr> | |
| 1037 | + <th style={{ textAlign: "start" }}>Total</th> | |
| 1038 | + <td>:</td> | |
| 1039 | + <td style={{ textAlign: "start" }}>{`${values?.currency} ${values?.total || 0}`}</td> | |
| 1040 | + </tr> | |
| 1041 | + <tr> | |
| 1042 | + <th style={{ textAlign: "start" }}>Discount</th> | |
| 1043 | + <td>:</td> | |
| 1044 | + <td style={{ textAlign: "start" }}>{`${values?.currency} ${values?.discount || 0}`}</td> | |
| 1045 | + </tr> | |
| 1046 | + <tr> | |
| 1047 | + <th style={{ textAlign: "start" }}>Subtotal</th> | |
| 1048 | + <td>:</td> | |
| 1049 | + <td style={{ textAlign: "start" }}>{`${values?.currency} ${values?.subtotal || 0}`}</td> | |
| 1050 | + </tr> | |
| 1051 | + <tr> | |
| 1052 | + <th style={{ textAlign: "start" }}>Shipping </th> | |
| 1053 | + <td>:</td> | |
| 1054 | + <td style={{ textAlign: "start" }}>{`${values?.currency} ${values?.shipping || 0}`}</td> | |
| 1055 | + </tr> | |
| 1056 | + <tr> | |
| 1057 | + <th style={{ textAlign: "start" }}>Shipping Tax</th> | |
| 1058 | + <td>:</td> | |
| 1059 | + <td style={{ textAlign: "start" }}>{`${values?.currency} ${values?.shippingTax || 0}`}</td> | |
| 1060 | + </tr> | |
| 1061 | + <tr> | |
| 1062 | + <th style={{ textAlign: "start" }}>Tax ({values?.ptax}%)</th> | |
| 1063 | + <td>:</td> | |
| 1064 | + <td style={{ textAlign: "start" }}>{`${values?.currency} ${values?.tax || 0}`}</td> | |
| 1065 | + </tr> | |
| 1066 | + <tr> | |
| 1067 | + <th style={{ textAlign: "start" }}>Grand Total</th> | |
| 1068 | + <td>:</td> | |
| 1069 | + <td style={{ textAlign: "start" }}>{`${values?.currency} ${values?.grandTotal || 0}`}</td> | |
| 1070 | + | |
| 1071 | + </tr> | |
| 1072 | + </table> | |
| 1073 | + | |
| 1074 | + </div> | |
| 1075 | + <div className="footer"> | |
| 1076 | + <div>{values?.description}</div> | |
| 1077 | + </div> | |
| 1078 | + </div> | |
| 1079 | + </DrawerBody> | |
| 1080 | + } | |
| 1081 | + <DrawerFooter> | |
| 1082 | + <Button size="sm" sx={{ | |
| 1083 | + marginRight: 2, | |
| 1084 | + textTransform: "capitalize", | |
| 1085 | + }} variant='brand' onClick={() => setIsOpenPreview(!isOpenPreview)}>{isOpenPreview ? "Hide" : "Preview"}</Button> | |
| 1086 | + { | |
| 1087 | + !isOpenPreview && | |
| 1088 | + <> | |
| 1089 | + <Button sx={{ textTransform: "capitalize" }} size="sm" disabled={isLoding ? true : false} variant="brand" type="submit" onClick={handleSubmit} > | |
| 1090 | + {isLoding ? <Spinner /> : 'Save'} | |
| 1091 | + </Button> | |
| 1092 | + <Button | |
| 1093 | + variant="outline" | |
| 1094 | + colorScheme='red' size="sm" | |
| 1095 | + sx={{ | |
| 1096 | + marginLeft: 2, | |
| 1097 | + textTransform: "capitalize", | |
| 1098 | + }} | |
| 1099 | + onClick={handleCancel} | |
| 1100 | + > | |
| 1101 | + Close | |
| 1102 | + </Button> | |
| 1103 | + </> | |
| 1104 | + } | |
| 1105 | + </DrawerFooter> | |
| 1106 | + </DrawerContent> | |
| 1107 | + </Drawer> | |
| 1108 | + </div > | |
| 1109 | + ) | |
| 1110 | +} | |
| 1111 | + | |
| 1112 | +export default AddEdit |
+728-0Client/src/views/admin/invoice/View.js
| @@ -0,0 +1,728 @@ | ||
| 1 | +import { Button, Grid, GridItem, Flex, IconButton, Text, Menu, MenuButton, MenuDivider, MenuItem, MenuList, useDisclosure, Box, Heading, Input, Select, Textarea } from '@chakra-ui/react' | |
| 2 | +import { AddIcon, ChevronDownIcon, DeleteIcon, EditIcon } from "@chakra-ui/icons"; | |
| 3 | +import React from 'react' | |
| 4 | +import moment from 'moment' | |
| 5 | +import { Link, useNavigate, useParams } from 'react-router-dom' | |
| 6 | +import { BiLink } from 'react-icons/bi' | |
| 7 | +import { useEffect } from 'react' | |
| 8 | +import { useState } from 'react' | |
| 9 | +import Card from 'components/card/Card' | |
| 10 | +import { IoIosArrowBack } from "react-icons/io"; | |
| 11 | +import { HasAccess } from '../../../redux/accessUtils'; | |
| 12 | +import { HSeparator } from 'components/separator/Separator'; | |
| 13 | +import AddEdit from './AddEdit'; | |
| 14 | +import CommonDeleteModel from 'components/commonDeleteModel'; | |
| 15 | +import { deleteManyApi, putApi, getApi } from '../../../services/api'; | |
| 16 | +import { FaFilePdf } from "react-icons/fa"; | |
| 17 | +import html2pdf from "html2pdf.js"; | |
| 18 | +import { opprtunitiesSchema } from '../../../schema/opprtunitiesSchema'; | |
| 19 | +import { useFormik } from 'formik'; | |
| 20 | +import dayjs from 'dayjs'; | |
| 21 | +import { toast } from 'react-toastify'; | |
| 22 | +import { invoicesSchema } from '../../../schema/invoicesSchema'; | |
| 23 | +import CommonCheckTable from "components/reactTable/checktable"; | |
| 24 | + | |
| 25 | +const View = (props) => { | |
| 26 | + const params = useParams() | |
| 27 | + const { id } = params | |
| 28 | + const user = JSON.parse(localStorage.getItem("user")) | |
| 29 | + | |
| 30 | + const [invoiceAccess, accountAccess, contactAccess, quotesAccess] = HasAccess(['Invoices', 'Account', 'Contacts', 'Quotes']) | |
| 31 | + | |
| 32 | + const [data, setData] = useState() | |
| 33 | + const [quotesData, setQuotesData] = useState([]) | |
| 34 | + const { onOpen, onClose } = useDisclosure() | |
| 35 | + const [edit, setEdit] = useState(false); | |
| 36 | + const [deleteModel, setDelete] = useState(false); | |
| 37 | + const [deleteManyModel, setDeleteManyModel] = useState(false); | |
| 38 | + const [loading, setLoading] = useState(false) | |
| 39 | + const [isLoding, setIsLoding] = useState(false) | |
| 40 | + const navigate = useNavigate() | |
| 41 | + const [type, setType] = useState("") | |
| 42 | + const [editableField, setEditableField] = useState(null); | |
| 43 | + const [editableFieldName, setEditableFieldName] = useState(null); | |
| 44 | + const today = new Date().toISOString().split('T')[0]; | |
| 45 | + const todayTime = new Date().toISOString().split('.')[0]; | |
| 46 | + | |
| 47 | + const quotesColumns = [ | |
| 48 | + { Header: "Quote Number", accessor: "quoteNumber", isSortable: false, width: 10 }, | |
| 49 | + { | |
| 50 | + Header: 'Title', accessor: 'title', cell: (cell) => ( | |
| 51 | + <div className="selectOpt"> | |
| 52 | + <Text | |
| 53 | + onClick={() => navigate(`/quotesView/${cell?.row?.original._id}`)} | |
| 54 | + me="10px" | |
| 55 | + sx={{ '&:hover': { color: 'blue.500', textDecoration: 'underline' }, cursor: 'pointer' }} | |
| 56 | + color='brand.600' | |
| 57 | + fontSize="sm" | |
| 58 | + fontWeight="700" | |
| 59 | + > | |
| 60 | + {cell?.value} | |
| 61 | + </Text> | |
| 62 | + </div> | |
| 63 | + ) | |
| 64 | + }, | |
| 65 | + { Header: 'Quote Stage', accessor: 'quoteStage' }, | |
| 66 | + { | |
| 67 | + Header: 'Contact', accessor: 'contact', | |
| 68 | + cell: (cell) => ( | |
| 69 | + (user.role === 'superAdmin' || contactAccess?.view) ? | |
| 70 | + <div className="selectOpt"> | |
| 71 | + <Text | |
| 72 | + onClick={() => navigate(cell?.row?.original.contact !== null && `/contactView/${cell?.row?.original.contact}`)} | |
| 73 | + me="10px" | |
| 74 | + sx={{ '&:hover': { color: 'blue.500', textDecoration: 'underline' }, cursor: 'pointer' }} | |
| 75 | + color='brand.600' | |
| 76 | + fontSize="sm" | |
| 77 | + fontWeight="700" | |
| 78 | + > | |
| 79 | + {cell?.row?.original?.contactName ? cell?.row?.original?.contactName : "-"} | |
| 80 | + </Text> | |
| 81 | + </div> | |
| 82 | + : | |
| 83 | + <Text | |
| 84 | + > | |
| 85 | + {cell?.row?.original?.contactName ? cell?.row?.original?.contactName : "-"} | |
| 86 | + </Text> | |
| 87 | + ) | |
| 88 | + }, | |
| 89 | + { | |
| 90 | + Header: 'Account', accessor: 'account', | |
| 91 | + cell: (cell) => ( | |
| 92 | + (user.role === 'superAdmin' || accountAccess?.view) ? | |
| 93 | + <div className="selectOpt"> | |
| 94 | + <Text | |
| 95 | + onClick={() => navigate(cell?.row?.original.account !== null && `/accountView/${cell?.row?.original.account}`)} | |
| 96 | + me="10px" | |
| 97 | + sx={{ '&:hover': { color: 'blue.500', textDecoration: 'underline' }, cursor: 'pointer' }} | |
| 98 | + color='brand.600' | |
| 99 | + fontSize="sm" | |
| 100 | + fontWeight="700" | |
| 101 | + > | |
| 102 | + {cell?.row?.original?.accountName ? cell?.row?.original?.accountName : "-"} | |
| 103 | + </Text> | |
| 104 | + </div> | |
| 105 | + : | |
| 106 | + <Text | |
| 107 | + > | |
| 108 | + {cell?.row?.original?.accountName ? cell?.row?.original?.accountName : "-"} | |
| 109 | + </Text> | |
| 110 | + ) | |
| 111 | + }, | |
| 112 | + { | |
| 113 | + Header: "Grand Total", | |
| 114 | + accessor: "grandTotal", | |
| 115 | + cell: (cell) => ( | |
| 116 | + <div className="selectOpt"> | |
| 117 | + <Text | |
| 118 | + > | |
| 119 | + {cell?.row?.original?.grandTotal ? `$${cell?.row?.original?.grandTotal}` : '-'} | |
| 120 | + </Text> | |
| 121 | + </div> | |
| 122 | + ) | |
| 123 | + }, | |
| 124 | + { Header: "valid Until", accessor: "validUntil" }, | |
| 125 | + ]; | |
| 126 | + const fetchViewData = async () => { | |
| 127 | + if (id) { | |
| 128 | + let result = await getApi('api/invoices/view/', id); | |
| 129 | + setData(result?.data?.result); | |
| 130 | + setQuotesData(result?.data?.quotesDetails) | |
| 131 | + } | |
| 132 | + } | |
| 133 | + const generatePDF = () => { | |
| 134 | + setLoading(true) | |
| 135 | + const element = document.getElementById("reports"); | |
| 136 | + const hideBtn = document.getElementById("hide-btn"); | |
| 137 | + | |
| 138 | + if (element) { | |
| 139 | + hideBtn.style.display = 'none'; | |
| 140 | + html2pdf() | |
| 141 | + .from(element) | |
| 142 | + .set({ | |
| 143 | + margin: [0, 0, 0, 0], | |
| 144 | + filename: `Invoice_Details_${moment().format("DD-MM-YYYY")}.pdf`, | |
| 145 | + image: { type: "jpeg", quality: 0.98 }, | |
| 146 | + html2canvas: { scale: 2, useCORS: true, allowTaint: true }, | |
| 147 | + jsPDF: { unit: "in", format: "letter", orientation: "portrait" }, | |
| 148 | + }) | |
| 149 | + .save().then(() => { | |
| 150 | + setLoading(false) | |
| 151 | + hideBtn.style.display = ''; | |
| 152 | + }) | |
| 153 | + // }, 500); | |
| 154 | + } else { | |
| 155 | + console.error("Element with ID 'reports' not found."); | |
| 156 | + setLoading(false) | |
| 157 | + } | |
| 158 | + }; | |
| 159 | + const handleDeleteAccount = async (ids) => { | |
| 160 | + try { | |
| 161 | + let response = await deleteManyApi('api/invoices/deleteMany', ids) | |
| 162 | + if (response.status === 200) { | |
| 163 | + navigate('/invoices') | |
| 164 | + toast.success(`Invoices Delete successfully`) | |
| 165 | + setDeleteManyModel(false) | |
| 166 | + } | |
| 167 | + } catch (error) { | |
| 168 | + console.log(error) | |
| 169 | + toast.error(`server error`) | |
| 170 | + | |
| 171 | + } | |
| 172 | + | |
| 173 | + } | |
| 174 | + | |
| 175 | + const initialValues = { | |
| 176 | + title: data?.title, | |
| 177 | + quoteNumber: data?.quoteNumber, | |
| 178 | + quoteDate: data?.quoteDate, | |
| 179 | + dueDate: data?.dueDate, | |
| 180 | + invoiceDate: data?.invoiceDate, | |
| 181 | + status: data?.status, | |
| 182 | + assignedTo: data?.assignedTo, | |
| 183 | + description: data?.description, | |
| 184 | + account: data?.account, | |
| 185 | + contact: data?.contact, | |
| 186 | + billingStreet: data?.billingStreet, | |
| 187 | + shippingStreet: data?.shippingStreet, | |
| 188 | + billingCity: data?.billingCity, | |
| 189 | + shippingCity: data?.shippingCity, | |
| 190 | + billingState: data?.billingState, | |
| 191 | + shippingState: data?.shippingState, | |
| 192 | + billingPostalCode: data?.billingPostalCode, | |
| 193 | + shippingPostalCode: data?.shippingPostalCode, | |
| 194 | + billingCountry: data?.billingCountry, | |
| 195 | + shippingCountry: data?.shippingCountry, | |
| 196 | + isCheck: data?.isCheck, | |
| 197 | + currency: data?.currency, | |
| 198 | + total: data?.total, | |
| 199 | + discount: data?.discount, | |
| 200 | + subtotal: data?.subtotal, | |
| 201 | + shipping: data?.shipping, | |
| 202 | + shippingTax: data?.shippingTax, | |
| 203 | + ptax: data?.ptax, | |
| 204 | + tax: data?.tax, | |
| 205 | + grandTotal: data?.grandTotal, | |
| 206 | + grandTotal: data?.grandTotal, | |
| 207 | + modifiedBy: JSON.parse(localStorage.getItem('user'))._id | |
| 208 | + } | |
| 209 | + | |
| 210 | + const formik = useFormik({ | |
| 211 | + initialValues: initialValues, | |
| 212 | + validationSchema: invoicesSchema, | |
| 213 | + enableReinitialize: true, | |
| 214 | + onSubmit: async (values, { resetForm }) => { | |
| 215 | + const payload = { | |
| 216 | + ...values, | |
| 217 | + modifiedDate: new Date() | |
| 218 | + } | |
| 219 | + let response = await putApi(`api/invoices/edit/${id}`, payload) | |
| 220 | + if (response.status === 200) { | |
| 221 | + setEditableField(null); | |
| 222 | + fetchViewData() | |
| 223 | + toast.success(`${editableFieldName} Update successfully`) | |
| 224 | + } else { | |
| 225 | + toast.error(`${editableFieldName} not Update`) | |
| 226 | + } | |
| 227 | + }, | |
| 228 | + }); | |
| 229 | + const handleDoubleClick = (fieldName, value, name) => { | |
| 230 | + formik.setFieldValue(fieldName, value); | |
| 231 | + setEditableField(fieldName) | |
| 232 | + setEditableFieldName(name) | |
| 233 | + }; | |
| 234 | + | |
| 235 | + const handleBlur = (e) => { | |
| 236 | + formik.handleSubmit(); | |
| 237 | + }; | |
| 238 | + useEffect(() => { | |
| 239 | + fetchViewData() | |
| 240 | + }, [id, edit]) | |
| 241 | + | |
| 242 | + return ( | |
| 243 | + <div> | |
| 244 | + <Grid templateColumns="repeat(4, 1fr)" gap={3} id="reports"> | |
| 245 | + <GridItem colSpan={{ base: 4 }}> | |
| 246 | + <Heading size="lg" m={3}> | |
| 247 | + {data?.title || ""} | |
| 248 | + </Heading> | |
| 249 | + </GridItem> | |
| 250 | + <GridItem colSpan={{ base: 4 }}> | |
| 251 | + <Card > | |
| 252 | + <Grid gap={4}> | |
| 253 | + <GridItem colSpan={2}> | |
| 254 | + <Box> | |
| 255 | + <Box display={"flex"} justifyContent={"space-between"} > | |
| 256 | + <Heading size="md" mb={3}> | |
| 257 | + Invoice Details | |
| 258 | + </Heading> | |
| 259 | + <Flex id="hide-btn"> | |
| 260 | + <Menu> | |
| 261 | + {(user.role === 'superAdmin' || invoiceAccess?.create || invoiceAccess?.update || invoiceAccess?.delete) && <MenuButton variant="outline" colorScheme='blackAlpha' size="sm" va mr={2.5} as={Button} rightIcon={<ChevronDownIcon />}> | |
| 262 | + Actions | |
| 263 | + </MenuButton>} | |
| 264 | + <MenuDivider /> | |
| 265 | + <MenuList minWidth={2}> | |
| 266 | + {(user.role === 'superAdmin' || invoiceAccess?.create) && <MenuItem onClick={() => { setEdit(true); setType("add"); formik.resetForm() } | |
| 267 | + } alignItems={'start'} color={'blue'} icon={<AddIcon />}>Add</MenuItem>} | |
| 268 | + {(user.role === 'superAdmin' || invoiceAccess?.update) && <MenuItem onClick={() => { setEdit(true); setType("edit") }} alignItems={'start'} icon={<EditIcon />}>Edit</MenuItem>} | |
| 269 | + <MenuItem onClick={generatePDF} alignItems={"start"} icon={<FaFilePdf />} display={"flex"} style={{ alignItems: "center" }}>Print as PDF</MenuItem > | |
| 270 | + | |
| 271 | + {(user.role === 'superAdmin' || invoiceAccess?.delete) && <> | |
| 272 | + <MenuDivider /> | |
| 273 | + <MenuItem alignItems={'start'} onClick={() => setDeleteManyModel(true)} color={'red'} icon={<DeleteIcon />}>Delete</MenuItem> | |
| 274 | + </>} | |
| 275 | + </MenuList> | |
| 276 | + </Menu> | |
| 277 | + <Button leftIcon={<IoIosArrowBack />} size='sm' variant="brand" onClick={() => navigate(-1)} > | |
| 278 | + Back | |
| 279 | + </Button> | |
| 280 | + </Flex> | |
| 281 | + </Box> | |
| 282 | + <HSeparator /> | |
| 283 | + </Box> | |
| 284 | + </GridItem> | |
| 285 | + | |
| 286 | + <GridItem colSpan={{ base: 2, md: 1 }} > | |
| 287 | + <Text fontSize="sm" fontWeight="bold" color={'blackAlpha.900'}> Invoice Number </Text> | |
| 288 | + <Text>{data?.invoiceNumber ? data?.invoiceNumber : ' - '}</Text> | |
| 289 | + </GridItem> | |
| 290 | + <GridItem colSpan={{ base: 2, md: 1 }} > | |
| 291 | + <Text fontSize="sm" fontWeight="bold" color={'blackAlpha.900'}> Title </Text> | |
| 292 | + { | |
| 293 | + editableField === "title" ? | |
| 294 | + <> | |
| 295 | + <Input | |
| 296 | + id="text" | |
| 297 | + name="title" | |
| 298 | + type="text" | |
| 299 | + onChange={formik.handleChange} | |
| 300 | + onBlur={handleBlur} | |
| 301 | + value={formik.values.title} | |
| 302 | + borderColor={formik?.errors?.title && formik?.touched?.title ? "red.300" : null} | |
| 303 | + autoFocus | |
| 304 | + /> | |
| 305 | + <Text mb='10px' color={'red'}> {formik?.errors.title && formik?.touched.title && formik?.errors.title}</Text> | |
| 306 | + </> | |
| 307 | + : | |
| 308 | + <Text onDoubleClick={() => handleDoubleClick("title", data?.title, "Title")}>{data?.title ? data?.title : ' - '}</Text> | |
| 309 | + } | |
| 310 | + </GridItem> | |
| 311 | + | |
| 312 | + <GridItem colSpan={{ base: 2, md: 1 }} > | |
| 313 | + <Text fontSize="sm" fontWeight="bold" color={'blackAlpha.900'}> Quote Number</Text> | |
| 314 | + <Text >{data?.quoteNumber ? data?.quoteNumber : ' - '}</Text> | |
| 315 | + </GridItem> | |
| 316 | + | |
| 317 | + <GridItem colSpan={{ base: 2, md: 1 }} > | |
| 318 | + <Text fontSize="sm" fontWeight="bold" color={'blackAlpha.900'}> Quote Date </Text> | |
| 319 | + { | |
| 320 | + editableField === "quoteDate" ? | |
| 321 | + <> | |
| 322 | + <Input | |
| 323 | + id="text" | |
| 324 | + name="quoteDate" | |
| 325 | + type="date" | |
| 326 | + onChange={formik.handleChange} | |
| 327 | + onBlur={handleBlur} | |
| 328 | + value={dayjs(formik.values.quoteDate).format("YYYY-MM-DD")} | |
| 329 | + borderColor={formik?.errors?.quoteDate && formik?.touched?.quoteDate ? "red.300" : null} | |
| 330 | + autoFocus | |
| 331 | + /> | |
| 332 | + <Text mb='10px' color={'red'}> {formik?.errors.quoteDate && formik?.touched.quoteDate && formik?.errors.quoteDate}</Text> | |
| 333 | + </> | |
| 334 | + : | |
| 335 | + <Text onDoubleClick={() => handleDoubleClick("quoteDate", data?.quoteDate, "Quote Date")}>{data?.quoteDate ? dayjs(data?.quoteDate).format("YYYY-MM-DD") : ' - '}</Text> | |
| 336 | + } | |
| 337 | + </GridItem> | |
| 338 | + <GridItem colSpan={{ base: 2, md: 1 }} > | |
| 339 | + <Text fontSize="sm" fontWeight="bold" color={'blackAlpha.900'}> Due Date</Text> | |
| 340 | + { | |
| 341 | + editableField === "dueDate" ? | |
| 342 | + <> | |
| 343 | + <Input | |
| 344 | + id="text" | |
| 345 | + name="dueDate" | |
| 346 | + type="date" | |
| 347 | + onChange={formik.handleChange} | |
| 348 | + onBlur={handleBlur} | |
| 349 | + value={dayjs(formik.values.dueDate).format("YYYY-MM-DD")} | |
| 350 | + borderColor={formik?.errors?.dueDate && formik?.touched?.dueDate ? "red.300" : null} | |
| 351 | + autoFocus | |
| 352 | + /> | |
| 353 | + <Text mb='10px' color={'red'}> {formik?.errors.dueDate && formik?.touched.dueDate && formik?.errors.dueDate}</Text> | |
| 354 | + </> | |
| 355 | + : | |
| 356 | + <Text onDoubleClick={() => handleDoubleClick("dueDate", data?.dueDate, "Due Date")}>{data?.dueDate ? dayjs(data?.dueDate).format("YYYY-MM-DD") : ' - '}</Text> | |
| 357 | + } | |
| 358 | + </GridItem> | |
| 359 | + <GridItem colSpan={{ base: 2, md: 1 }} > | |
| 360 | + <Text fontSize="sm" fontWeight="bold" color={'blackAlpha.900'}> Invoice Date</Text> | |
| 361 | + { | |
| 362 | + editableField === "invoiceDate" ? | |
| 363 | + <> | |
| 364 | + <Input | |
| 365 | + id="text" | |
| 366 | + name="invoiceDate" | |
| 367 | + type="date" | |
| 368 | + onChange={formik.handleChange} | |
| 369 | + onBlur={handleBlur} | |
| 370 | + value={dayjs(formik.values.invoiceDate).format("YYYY-MM-DD")} | |
| 371 | + borderColor={formik?.errors?.invoiceDate && formik?.touched?.invoiceDate ? "red.300" : null} | |
| 372 | + autoFocus | |
| 373 | + /> | |
| 374 | + <Text mb='10px' color={'red'}> {formik?.errors.invoiceDate && formik?.touched.invoiceDate && formik?.errors.invoiceDate}</Text> | |
| 375 | + </> | |
| 376 | + : | |
| 377 | + <Text onDoubleClick={() => handleDoubleClick("invoiceDate", data?.invoiceDate, "Invoice Date")}>{data?.invoiceDate ? dayjs(data?.invoiceDate).format("YYYY-MM-DD") : ' - '}</Text> | |
| 378 | + } | |
| 379 | + </GridItem> | |
| 380 | + | |
| 381 | + | |
| 382 | + <GridItem colSpan={{ base: 2, md: 1 }} > | |
| 383 | + <Text fontSize="sm" fontWeight="bold" color={'blackAlpha.900'}>Status </Text> | |
| 384 | + { | |
| 385 | + editableField === "status" ? | |
| 386 | + <> | |
| 387 | + <Select | |
| 388 | + value={formik?.values.status} | |
| 389 | + name="status" | |
| 390 | + onChange={formik?.handleChange} | |
| 391 | + onBlur={handleBlur} | |
| 392 | + mb={formik?.errors.status && formik?.touched.status ? undefined : '10px'} | |
| 393 | + fontWeight='500' | |
| 394 | + borderColor={formik?.errors.status && formik?.touched.status ? "red.300" : null} | |
| 395 | + > | |
| 396 | + <option value="Paid">Paid</option> | |
| 397 | + <option value="Unpaid">Unpaid</option> | |
| 398 | + <option value="Cancelled">Cancelled</option> | |
| 399 | + </Select> | |
| 400 | + <Text mb='10px' color={'red'}> {formik?.errors.status && formik?.touched.status && formik?.errors.status}</Text> | |
| 401 | + </> | |
| 402 | + : | |
| 403 | + <Text onDoubleClick={() => handleDoubleClick("status", data?.status, "Status")}>{data?.status ? data?.status : ' - '}</Text> | |
| 404 | + } | |
| 405 | + </GridItem> | |
| 406 | + | |
| 407 | + | |
| 408 | + | |
| 409 | + <GridItem colSpan={{ base: 2, md: 1 }} > | |
| 410 | + <Text fontSize="sm" fontWeight="bold" color={'blackAlpha.900'}>Description</Text> | |
| 411 | + { | |
| 412 | + editableField === "description" ? | |
| 413 | + <> | |
| 414 | + <Textarea | |
| 415 | + fontSize='sm' | |
| 416 | + value={formik?.values.description} | |
| 417 | + name="description" | |
| 418 | + resize={"none"} | |
| 419 | + onBlur={handleBlur} | |
| 420 | + onChange={formik?.handleChange} | |
| 421 | + placeholder='Description' | |
| 422 | + fontWeight='500' | |
| 423 | + borderColor={formik?.errors.description && formik?.touched.description ? "red.300" : null} | |
| 424 | + /> | |
| 425 | + <Text mb='10px' color={'red'}> {formik?.errors.description && formik?.touched.description && formik?.errors.description}</Text> | |
| 426 | + </> | |
| 427 | + : | |
| 428 | + <Text onDoubleClick={() => handleDoubleClick("description", data?.description, "Description")} style={{ width: "300px" }}>{data?.description ? data?.description : ' - '}</Text> | |
| 429 | + } | |
| 430 | + </GridItem> | |
| 431 | + <GridItem colSpan={{ base: 2, md: 1 }} > | |
| 432 | + <Text fontSize="sm" fontWeight="bold" color={'blackAlpha.900'}>Account</Text> | |
| 433 | + { | |
| 434 | + data?.account ? | |
| 435 | + <Link to={accountAccess?.view && `/accountView/${data?.account}`}> | |
| 436 | + <Text color={accountAccess?.view ? 'blue.500' : 'blackAlpha.900'} sx={{ '&:hover': { color: accountAccess?.view ? 'blue.500' : 'blackAlpha.900', textDecoration: accountAccess?.view ? 'underline' : 'none' } }} style={{ cursor: "pointer" }}>{data?.accountName ? data?.accountName : ' - '}</Text> | |
| 437 | + </Link> | |
| 438 | + : | |
| 439 | + <Text color={accountAccess?.view ? 'blue.500' : 'blackAlpha.900'} sx={{ '&:hover': { color: accountAccess?.view ? 'blue.500' : 'blackAlpha.900', textDecoration: accountAccess?.view ? 'underline' : 'none' } }}>{data?.accountName ? data?.accountName : ' - '}</Text> | |
| 440 | + | |
| 441 | + } | |
| 442 | + </GridItem> | |
| 443 | + <GridItem colSpan={{ base: 2, md: 1 }} > | |
| 444 | + <Text fontSize="sm" fontWeight="bold" color={'blackAlpha.900'}>Contact</Text> | |
| 445 | + { | |
| 446 | + data?.contact ? | |
| 447 | + <Link to={contactAccess?.view && `/contactView/${data?.contact}`}> | |
| 448 | + <Text color={contactAccess?.view ? 'blue.500' : 'blackAlpha.900'} sx={{ '&:hover': { color: contactAccess?.view ? 'blue.500' : 'blackAlpha.900', textDecoration: contactAccess?.view ? 'underline' : 'none' } }} style={{ cursor: "pointer" }}>{data?.contactName ? data?.contactName : ' - '}</Text> | |
| 449 | + </Link> | |
| 450 | + : | |
| 451 | + <Text color={contactAccess?.view ? 'blue.500' : 'blackAlpha.900'} sx={{ '&:hover': { color: contactAccess?.view ? 'blue.500' : 'blackAlpha.900', textDecoration: contactAccess?.view ? 'underline' : 'none' } }}>{data?.contactName ? data?.contactName : ' - '}</Text> | |
| 452 | + | |
| 453 | + } | |
| 454 | + </GridItem> | |
| 455 | + <GridItem colSpan={{ base: 2, md: 1 }} > | |
| 456 | + <Text fontSize="sm" fontWeight="bold" color={'blackAlpha.900'}> Assigned To</Text> | |
| 457 | + { | |
| 458 | + data?.assignedTo ? | |
| 459 | + <Link to={user.role === 'superAdmin' && `/userView/${data?.assignedTo}`}> | |
| 460 | + <Text color={user.role === 'superAdmin' ? 'blue.500' : 'blackAlpha.900'} sx={{ '&:hover': { color: user.role === 'superAdmin' ? 'blue.500' : 'blackAlpha.900', textDecoration: user.role === 'superAdmin' ? 'underline' : 'none' } }} style={{ cursor: "pointer" }}>{data?.assignUserName ? data?.assignUserName : ' - '}</Text> | |
| 461 | + </Link> | |
| 462 | + : | |
| 463 | + <Text color={user.role === 'superAdmin' ? 'blue.500' : 'blackAlpha.900'} sx={{ '&:hover': { color: user.role === 'superAdmin' ? 'blue.500' : 'blackAlpha.900', textDecoration: user.role === 'superAdmin' ? 'underline' : 'none' } }}>{data?.assignUserName ? data?.assignUserName : ' - '}</Text> | |
| 464 | + | |
| 465 | + } | |
| 466 | + </GridItem> | |
| 467 | + <GridItem colSpan={{ base: 2, md: 1 }} > | |
| 468 | + <Text fontSize="sm" fontWeight="bold" color={'blackAlpha.900'}>Billing Street</Text> | |
| 469 | + { | |
| 470 | + editableField === "billingStreet" ? | |
| 471 | + <> | |
| 472 | + <Input | |
| 473 | + name="billingStreet" | |
| 474 | + onChange={formik.handleChange} | |
| 475 | + onBlur={handleBlur} | |
| 476 | + value={formik.values.billingStreet} | |
| 477 | + borderColor={formik?.errors?.billingStreet && formik?.touched?.billingStreet ? "red.300" : null} | |
| 478 | + autoFocus | |
| 479 | + /> | |
| 480 | + <Text mb='10px' color={'red'}> {formik?.errors.billingStreet && formik?.touched.billingStreet && formik?.errors.billingStreet}</Text> | |
| 481 | + </> | |
| 482 | + : | |
| 483 | + <Text onDoubleClick={() => handleDoubleClick("billingStreet", data?.billingStreet, "Billing Street")}>{data?.billingStreet ? data?.billingStreet : ' - '}</Text> | |
| 484 | + } | |
| 485 | + </GridItem> | |
| 486 | + <GridItem colSpan={{ base: 2, md: 1 }} > | |
| 487 | + <Text fontSize="sm" fontWeight="bold" color={'blackAlpha.900'}>Shipping Street</Text> | |
| 488 | + { | |
| 489 | + editableField === "shippingStreet" ? | |
| 490 | + <> | |
| 491 | + <Input | |
| 492 | + name="shippingStreet" | |
| 493 | + onChange={formik.handleChange} | |
| 494 | + onBlur={handleBlur} | |
| 495 | + value={formik.values.shippingStreet} | |
| 496 | + borderColor={formik?.errors?.shippingStreet && formik?.touched?.shippingStreet ? "red.300" : null} | |
| 497 | + autoFocus | |
| 498 | + /> | |
| 499 | + <Text mb='10px' color={'red'}> {formik?.errors.shippingStreet && formik?.touched.shippingStreet && formik?.errors.shippingStreet}</Text> | |
| 500 | + </> | |
| 501 | + : | |
| 502 | + <Text onDoubleClick={() => handleDoubleClick("shippingStreet", data?.shippingStreet, "Shipping Street")}>{data?.shippingStreet ? data?.shippingStreet : ' - '}</Text> | |
| 503 | + } | |
| 504 | + </GridItem> | |
| 505 | + | |
| 506 | + <GridItem colSpan={{ base: 2, md: 1 }} > | |
| 507 | + <Text fontSize="sm" fontWeight="bold" color={'blackAlpha.900'}>Billing City | |
| 508 | + </Text> | |
| 509 | + { | |
| 510 | + editableField === "billingCity" ? | |
| 511 | + <> | |
| 512 | + <Input | |
| 513 | + name="billingCity" | |
| 514 | + onChange={formik.handleChange} | |
| 515 | + onBlur={handleBlur} | |
| 516 | + value={formik.values.billingCity} | |
| 517 | + borderColor={formik?.errors?.billingCity && formik?.touched?.billingCity ? "red.300" : null} | |
| 518 | + autoFocus | |
| 519 | + /> | |
| 520 | + <Text mb='10px' color={'red'}> {formik?.errors.billingCity && formik?.touched.billingCity && formik?.errors.billingCity}</Text> | |
| 521 | + </> | |
| 522 | + : | |
| 523 | + <Text onDoubleClick={() => handleDoubleClick("billingCity", data?.billingCity, "Billing City")}>{data?.billingCity ? data?.billingCity : ' - '}</Text> | |
| 524 | + } | |
| 525 | + </GridItem> | |
| 526 | + <GridItem colSpan={{ base: 2, md: 1 }} > | |
| 527 | + <Text fontSize="sm" fontWeight="bold" color={'blackAlpha.900'}>Shipping City | |
| 528 | + </Text> | |
| 529 | + { | |
| 530 | + editableField === "shippingCity" ? | |
| 531 | + <> | |
| 532 | + <Input | |
| 533 | + name="shippingCity" | |
| 534 | + onChange={formik.handleChange} | |
| 535 | + onBlur={handleBlur} | |
| 536 | + value={formik.values.shippingCity} | |
| 537 | + borderColor={formik?.errors?.shippingCity && formik?.touched?.shippingCity ? "red.300" : null} | |
| 538 | + autoFocus | |
| 539 | + /> | |
| 540 | + <Text mb='10px' color={'red'}> {formik?.errors.shippingCity && formik?.touched.shippingCity && formik?.errors.shippingCity}</Text> | |
| 541 | + </> | |
| 542 | + : | |
| 543 | + <Text onDoubleClick={() => handleDoubleClick("shippingCity", data?.shippingCity, "Shipping City")}>{data?.shippingCity ? data?.shippingCity : ' - '}</Text> | |
| 544 | + } | |
| 545 | + </GridItem> | |
| 546 | + <GridItem colSpan={{ base: 2, md: 1 }} > | |
| 547 | + <Text fontSize="sm" fontWeight="bold" color={'blackAlpha.900'}>Billing State</Text> | |
| 548 | + { | |
| 549 | + editableField === "billingState" ? | |
| 550 | + <> | |
| 551 | + <Input | |
| 552 | + name="billingState" | |
| 553 | + onChange={formik.handleChange} | |
| 554 | + onBlur={handleBlur} | |
| 555 | + value={formik.values.billingState} | |
| 556 | + borderColor={formik?.errors?.billingState && formik?.touched?.billingState ? "red.300" : null} | |
| 557 | + autoFocus | |
| 558 | + /> | |
| 559 | + <Text mb='10px' color={'red'}> {formik?.errors.billingState && formik?.touched.billingState && formik?.errors.billingState}</Text> | |
| 560 | + </> | |
| 561 | + : | |
| 562 | + <Text onDoubleClick={() => handleDoubleClick("billingState", data?.billingState, "Billing State")}>{data?.billingState ? data?.billingState : ' - '}</Text> | |
| 563 | + } | |
| 564 | + </GridItem> | |
| 565 | + <GridItem colSpan={{ base: 2, md: 1 }} > | |
| 566 | + <Text fontSize="sm" fontWeight="bold" color={'blackAlpha.900'}>Shipping State</Text> | |
| 567 | + { | |
| 568 | + editableField === "shippingState" ? | |
| 569 | + <> | |
| 570 | + <Input | |
| 571 | + name="shippingState" | |
| 572 | + onChange={formik.handleChange} | |
| 573 | + onBlur={handleBlur} | |
| 574 | + value={formik.values.shippingState} | |
| 575 | + borderColor={formik?.errors?.shippingState && formik?.touched?.shippingState ? "red.300" : null} | |
| 576 | + autoFocus | |
| 577 | + /> | |
| 578 | + <Text mb='10px' color={'red'}> {formik?.errors.shippingState && formik?.touched.shippingState && formik?.errors.shippingState}</Text> | |
| 579 | + </> | |
| 580 | + : | |
| 581 | + <Text onDoubleClick={() => handleDoubleClick("shippingState", data?.shippingState, "Shipping State")}>{data?.shippingState ? data?.shippingState : ' - '}</Text> | |
| 582 | + } | |
| 583 | + </GridItem> | |
| 584 | + <GridItem colSpan={{ base: 2, md: 1 }} > | |
| 585 | + <Text fontSize="sm" fontWeight="bold" color={'blackAlpha.900'}>Billing Postal Code</Text> | |
| 586 | + { | |
| 587 | + editableField === "billingPostalCode" ? | |
| 588 | + <> | |
| 589 | + <Input | |
| 590 | + name="billingPostalCode" | |
| 591 | + onChange={formik.handleChange} | |
| 592 | + onBlur={handleBlur} | |
| 593 | + value={formik.values.billingPostalCode} | |
| 594 | + borderColor={formik?.errors?.billingPostalCode && formik?.touched?.billingPostalCode ? "red.300" : null} | |
| 595 | + autoFocus | |
| 596 | + /> | |
| 597 | + <Text mb='10px' color={'red'}> {formik?.errors.billingPostalCode && formik?.touched.billingPostalCode && formik?.errors.billingPostalCode}</Text> | |
| 598 | + </> | |
| 599 | + : | |
| 600 | + <Text onDoubleClick={() => handleDoubleClick("billingPostalCode", data?.billingPostalCode, "Billing Postal Code")}>{data?.billingPostalCode ? data?.billingPostalCode : ' - '}</Text> | |
| 601 | + } | |
| 602 | + </GridItem> | |
| 603 | + <GridItem colSpan={{ base: 2, md: 1 }} > | |
| 604 | + <Text fontSize="sm" fontWeight="bold" color={'blackAlpha.900'}>Shipping Postal Code</Text> | |
| 605 | + { | |
| 606 | + editableField === "shippingPostalCode" ? | |
| 607 | + <> | |
| 608 | + <Input | |
| 609 | + name="shippingPostalCode" | |
| 610 | + onChange={formik.handleChange} | |
| 611 | + onBlur={handleBlur} | |
| 612 | + value={formik.values.shippingPostalCode} | |
| 613 | + borderColor={formik?.errors?.shippingPostalCode && formik?.touched?.shippingPostalCode ? "red.300" : null} | |
| 614 | + autoFocus | |
| 615 | + /> | |
| 616 | + <Text mb='10px' color={'red'}> {formik?.errors.shippingPostalCode && formik?.touched.shippingPostalCode && formik?.errors.shippingPostalCode}</Text> | |
| 617 | + </> | |
| 618 | + : | |
| 619 | + <Text onDoubleClick={() => handleDoubleClick("shippingPostalCode", data?.shippingPostalCode, "Shipping Postal Code")}>{data?.shippingPostalCode ? data?.shippingPostalCode : ' - '}</Text> | |
| 620 | + } | |
| 621 | + </GridItem> | |
| 622 | + <GridItem colSpan={{ base: 2, md: 1 }} > | |
| 623 | + <Text fontSize="sm" fontWeight="bold" color={'blackAlpha.900'}>Billing Country</Text> | |
| 624 | + { | |
| 625 | + editableField === "billingCountry" ? | |
| 626 | + <> | |
| 627 | + <Input | |
| 628 | + name="billingCountry" | |
| 629 | + onChange={formik.handleChange} | |
| 630 | + onBlur={handleBlur} | |
| 631 | + value={formik.values.billingCountry} | |
| 632 | + borderColor={formik?.errors?.billingCountry && formik?.touched?.billingCountry ? "red.300" : null} | |
| 633 | + autoFocus | |
| 634 | + /> | |
| 635 | + <Text mb='10px' color={'red'}> {formik?.errors.billingCountry && formik?.touched.billingCountry && formik?.errors.billingCountry}</Text> | |
| 636 | + </> | |
| 637 | + : | |
| 638 | + <Text onDoubleClick={() => handleDoubleClick("billingCountry", data?.billingCountry, "Billing Country")}>{data?.billingCountry ? data?.billingCountry : ' - '}</Text> | |
| 639 | + } | |
| 640 | + </GridItem> | |
| 641 | + <GridItem colSpan={{ base: 2, md: 1 }} > | |
| 642 | + <Text fontSize="sm" fontWeight="bold" color={'blackAlpha.900'}>Shipping Country</Text> | |
| 643 | + { | |
| 644 | + editableField === "shippingCountry" ? | |
| 645 | + <> | |
| 646 | + <Input | |
| 647 | + name="shippingCountry" | |
| 648 | + onChange={formik.handleChange} | |
| 649 | + onBlur={handleBlur} | |
| 650 | + value={formik.values.shippingCountry} | |
| 651 | + borderColor={formik?.errors?.shippingCountry && formik?.touched?.shippingCountry ? "red.300" : null} | |
| 652 | + autoFocus | |
| 653 | + /> | |
| 654 | + <Text mb='10px' color={'red'}> {formik?.errors.shippingCountry && formik?.touched.shippingCountry && formik?.errors.shippingCountry}</Text> | |
| 655 | + </> | |
| 656 | + : | |
| 657 | + <Text onDoubleClick={() => handleDoubleClick("shippingCountry", data?.shippingCountry, "Shipping Country")}>{data?.shippingCountry ? data?.shippingCountry : ' - '}</Text> | |
| 658 | + } | |
| 659 | + </GridItem> | |
| 660 | + <GridItem colSpan={{ base: 2, md: 1 }} > | |
| 661 | + <Text fontSize="sm" fontWeight="bold" color={'blackAlpha.900'}>Total</Text> | |
| 662 | + <Text>{`${data?.currency}${data?.total ? data?.total : '0'}`}</Text> | |
| 663 | + </GridItem> | |
| 664 | + <GridItem colSpan={{ base: 2, md: 1 }} > | |
| 665 | + <Text fontSize="sm" fontWeight="bold" color={'blackAlpha.900'}>Discount</Text> | |
| 666 | + <Text >{`${data?.currency}${data?.discount || "0"}`}</Text> | |
| 667 | + </GridItem> | |
| 668 | + <GridItem colSpan={{ base: 2, md: 1 }} > | |
| 669 | + <Text fontSize="sm" fontWeight="bold" color={'blackAlpha.900'}>Subtotal</Text> | |
| 670 | + <Text>{`${data?.currency}${data?.subtotal ? data?.subtotal : '0'}`}</Text> | |
| 671 | + </GridItem> | |
| 672 | + <GridItem colSpan={{ base: 2, md: 1 }} > | |
| 673 | + <Text fontSize="sm" fontWeight="bold" color={'blackAlpha.900'}>Shipping</Text> | |
| 674 | + <>{`${data?.currency}${data?.shipping ? data?.shipping : '0'}`}</> | |
| 675 | + </GridItem> | |
| 676 | + <GridItem colSpan={{ base: 2, md: 1 }} > | |
| 677 | + <Text fontSize="sm" fontWeight="bold" color={'blackAlpha.900'}>Shipping Tax</Text> | |
| 678 | + <Text >{`${data?.currency}${data?.shippingTax ? data?.shippingTax : '0'}`}</Text> | |
| 679 | + </GridItem> | |
| 680 | + <GridItem colSpan={{ base: 2, md: 1 }} > | |
| 681 | + <Text fontSize="sm" fontWeight="bold" color={'blackAlpha.900'}>Tax</Text> | |
| 682 | + <Text >{`${data?.currency}${data?.tax ? data?.tax : '0'}`}</Text> | |
| 683 | + </GridItem> | |
| 684 | + <GridItem colSpan={{ base: 2, md: 1 }} > | |
| 685 | + <Text fontSize="sm" fontWeight="bold" color={'blackAlpha.900'}>Grand Total</Text> | |
| 686 | + <Text>{`${data?.currency}${data?.grandTotal ? data?.grandTotal : '0'}`}</Text> | |
| 687 | + </GridItem> | |
| 688 | + </Grid> | |
| 689 | + </Card> | |
| 690 | + </GridItem> | |
| 691 | + | |
| 692 | + </Grid> | |
| 693 | + {quotesAccess?.view && <GridItem colSpan={{ base: 12, md: 6 }}> | |
| 694 | + <Card overflow={'scroll'}> | |
| 695 | + <CommonCheckTable | |
| 696 | + title={"Quotes"} | |
| 697 | + isLoding={isLoding} | |
| 698 | + columnData={quotesColumns ?? []} | |
| 699 | + allData={quotesData ?? []} | |
| 700 | + tableData={quotesData ?? []} | |
| 701 | + AdvanceSearch={false} | |
| 702 | + tableCustomFields={[]} | |
| 703 | + checkBox={false} | |
| 704 | + deleteMany={true} | |
| 705 | + ManageGrid={false} | |
| 706 | + access={false} | |
| 707 | + /> | |
| 708 | + </Card> | |
| 709 | + </GridItem>} | |
| 710 | + { | |
| 711 | + (invoiceAccess?.update || invoiceAccess?.delete || user?.role === 'superAdmin') && <Card mt={3}> | |
| 712 | + <Grid templateColumns="repeat(6, 1fr)" gap={1}> | |
| 713 | + <GridItem colStart={6} > | |
| 714 | + <Flex justifyContent={"right"}> | |
| 715 | + {(invoiceAccess?.update || user?.role === 'superAdmin') && <Button size="sm" onClick={() => { setEdit(true); setType("edit") }} leftIcon={<EditIcon />} mr={2.5} variant="outline" colorScheme="green">Edit</Button>} | |
| 716 | + {(invoiceAccess?.delete || user?.role === 'superAdmin') && <Button size="sm" style={{ background: 'red.800' }} onClick={() => setDeleteManyModel(true)} leftIcon={<DeleteIcon />} colorScheme="red" >Delete</Button>} | |
| 717 | + </Flex> | |
| 718 | + </GridItem> | |
| 719 | + </Grid> | |
| 720 | + </Card> | |
| 721 | + } | |
| 722 | + <AddEdit isOpen={edit} size="lg" onClose={() => setEdit(false)} viewClose={onClose} selectedId={id?.event ? id?.event?._def?.extendedProps?._id : id} type={type} /> | |
| 723 | + <CommonDeleteModel isOpen={deleteManyModel} onClose={() => setDeleteManyModel(false)} type='Account' handleDeleteData={handleDeleteAccount} ids={[id]} /> | |
| 724 | + </div > | |
| 725 | + ) | |
| 726 | +} | |
| 727 | + | |
| 728 | +export default View |
+74-0Client/src/views/admin/invoice/components/ImportModel.js
| @@ -0,0 +1,74 @@ | ||
| 1 | +import { Button, Grid, GridItem, Modal, ModalBody, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalOverlay, Text } from '@chakra-ui/react' | |
| 2 | +import Spinner from 'components/spinner/Spinner' | |
| 3 | +import { useFormik } from 'formik' | |
| 4 | +import { useState } from 'react' | |
| 5 | +import { useNavigate } from 'react-router-dom' | |
| 6 | +import CommonFileUpload from 'components/commonFileUpload' | |
| 7 | + | |
| 8 | +const ImportModal = (props) => { | |
| 9 | + const { onClose, isOpen, text, customFields } = props | |
| 10 | + const [isLoding, setIsLoding] = useState(false) | |
| 11 | + const navigate = useNavigate(); | |
| 12 | + | |
| 13 | + const initialValues = { | |
| 14 | + invoices: '' | |
| 15 | + } | |
| 16 | + | |
| 17 | + const formik = useFormik({ | |
| 18 | + initialValues: initialValues, | |
| 19 | + onSubmit: (values, { resetForm }) => { | |
| 20 | + AddData() | |
| 21 | + resetForm(); | |
| 22 | + }, | |
| 23 | + }); | |
| 24 | + const { errors, touched, values, handleBlur, handleChange, handleSubmit, setFieldValue, resetForm } = formik | |
| 25 | + | |
| 26 | + const AddData = async () => { | |
| 27 | + try { | |
| 28 | + setIsLoding(true) | |
| 29 | + resetForm() | |
| 30 | + | |
| 31 | + if (values.invoices) { | |
| 32 | + onClose(); | |
| 33 | + navigate('/invoicesImport', { state: { fileData: values.invoices, customFields: customFields } }); | |
| 34 | + } | |
| 35 | + | |
| 36 | + } catch (e) { | |
| 37 | + console.log(e); | |
| 38 | + } | |
| 39 | + finally { | |
| 40 | + setIsLoding(false) | |
| 41 | + } | |
| 42 | + }; | |
| 43 | + | |
| 44 | + return ( | |
| 45 | + <Modal onClose={onClose} isOpen={isOpen} isCentered> | |
| 46 | + <ModalOverlay /> | |
| 47 | + <ModalContent> | |
| 48 | + <ModalHeader>Import Invoices</ModalHeader> | |
| 49 | + <ModalCloseButton /> | |
| 50 | + <ModalBody> | |
| 51 | + <Grid templateColumns="repeat(12, 1fr)" gap={3}> | |
| 52 | + <GridItem colSpan={{ base: 12 }}> | |
| 53 | + <CommonFileUpload count={values.invoices.length} onFileSelect={(file) => setFieldValue('invoices', file)} text={text} /> | |
| 54 | + <Text mb='10px' color={'red'}> {errors.invoices && touched.invoices && <>Please Select {text}</>}</Text> | |
| 55 | + </GridItem> | |
| 56 | + </Grid> | |
| 57 | + </ModalBody> | |
| 58 | + <ModalFooter> | |
| 59 | + <Button size="sm" variant='brand' onClick={handleSubmit} disabled={isLoding ? true : false} >{isLoding ? <Spinner /> : 'Save'}</Button> | |
| 60 | + <Button sx={{ | |
| 61 | + marginLeft: 2, | |
| 62 | + textTransform: "capitalize", | |
| 63 | + }} variant="outline" | |
| 64 | + colorScheme="red" size="sm" onClick={() => { | |
| 65 | + onClose() | |
| 66 | + formik.resetForm() | |
| 67 | + }}>Close</Button> | |
| 68 | + </ModalFooter> | |
| 69 | + </ModalContent> | |
| 70 | + </Modal> | |
| 71 | + ) | |
| 72 | +} | |
| 73 | + | |
| 74 | +export default ImportModal |
+185-0Client/src/views/admin/invoice/components/InvoiceAdvanceSearch.js
| @@ -0,0 +1,185 @@ | ||
| 1 | +import React from 'react'; | |
| 2 | +import { useFormik } from "formik"; | |
| 3 | +import * as yup from "yup"; | |
| 4 | +import { Modal, ModalBody, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalOverlay, Grid, GridItem, Input, FormLabel, Select, Text, Button, } from '@chakra-ui/react'; | |
| 5 | +import Spinner from 'components/spinner/Spinner'; | |
| 6 | +import { setSearchValue, getSearchData, setGetTagValues } from '../../../../redux/slices/advanceSearchSlice'; | |
| 7 | +import { useDispatch } from 'react-redux'; | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | +const InvoiceAdvanceSearch = (props) => { | |
| 12 | + const { state, allData, advanceSearch, setAdvanceSearch, isLoding, setSearchedData, setDisplaySearchData, setSearchClear, setSearchbox } = props; | |
| 13 | + | |
| 14 | + const dispatch = useDispatch(); | |
| 15 | + const initialValues = { | |
| 16 | + invoiceNumber: '', | |
| 17 | + title: '', | |
| 18 | + status: '', | |
| 19 | + contactName: '', | |
| 20 | + accountName: '', | |
| 21 | + grandTotal: '', | |
| 22 | + } | |
| 23 | + const validationSchema = yup.object({ | |
| 24 | + invoiceNumber: yup.string(), | |
| 25 | + title: yup.string(), | |
| 26 | + status: yup.string(), | |
| 27 | + contactName: yup.string(), | |
| 28 | + accountName: yup.string(), | |
| 29 | + grandTotal: yup.string(), | |
| 30 | + }); | |
| 31 | + const formik = useFormik({ | |
| 32 | + initialValues: initialValues, | |
| 33 | + validationSchema: validationSchema, | |
| 34 | + onSubmit: (values, { resetForm }) => { | |
| 35 | + dispatch(setSearchValue(values)) | |
| 36 | + dispatch(getSearchData({ values: values, allData: allData, type: 'invoice' })) | |
| 37 | + | |
| 38 | + const getValue = [ | |
| 39 | + { | |
| 40 | + name: ["invoiceNumber"], | |
| 41 | + value: values.invoiceNumber | |
| 42 | + }, | |
| 43 | + { | |
| 44 | + name: ["title"], | |
| 45 | + value: values.title | |
| 46 | + }, | |
| 47 | + { | |
| 48 | + name: ["status"], | |
| 49 | + value: values.status | |
| 50 | + }, | |
| 51 | + { | |
| 52 | + name: ["contactName"], | |
| 53 | + value: values.contactName | |
| 54 | + }, | |
| 55 | + { | |
| 56 | + name: ["accountName"], | |
| 57 | + value: values.accountName | |
| 58 | + }, | |
| 59 | + { | |
| 60 | + name: ["grandTotal"], | |
| 61 | + value: values.grandTotal | |
| 62 | + }, | |
| 63 | + ] | |
| 64 | + dispatch(setGetTagValues(getValue.filter(item => item.value))) | |
| 65 | + setDisplaySearchData(true) | |
| 66 | + setAdvanceSearch(false) | |
| 67 | + resetForm(); | |
| 68 | + setSearchbox(''); | |
| 69 | + } | |
| 70 | + }) | |
| 71 | + | |
| 72 | + const { errors, touched, values, handleBlur, handleChange, handleSubmit, setFieldValue, resetForm, dirty } = formik; | |
| 73 | + | |
| 74 | + return ( | |
| 75 | + <> | |
| 76 | + <Modal onClose={() => { setAdvanceSearch(false); resetForm() }} isOpen={advanceSearch} isCentered> | |
| 77 | + <ModalOverlay /> | |
| 78 | + <ModalContent> | |
| 79 | + <ModalHeader>Advance Search</ModalHeader> | |
| 80 | + <ModalCloseButton onClick={() => { setAdvanceSearch(false); resetForm() }} /> | |
| 81 | + <ModalBody> | |
| 82 | + <Grid templateColumns="repeat(12, 1fr)" mb={3} gap={2}> | |
| 83 | + <GridItem colSpan={{ base: 12, md: 6 }}> | |
| 84 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='600' color={"#000"} mb="0" mt={2}> | |
| 85 | + Invoice Number | |
| 86 | + </FormLabel> | |
| 87 | + <Input | |
| 88 | + fontSize='sm' | |
| 89 | + onChange={handleChange} onBlur={handleBlur} | |
| 90 | + value={values?.invoiceNumber} | |
| 91 | + name="invoiceNumber" | |
| 92 | + type='number' | |
| 93 | + placeholder='Enter Invoice Number' | |
| 94 | + fontWeight='500' | |
| 95 | + /> | |
| 96 | + <Text mb='10px' color={'red'}> {errors.invoiceNumber && touched.invoiceNumber && errors.invoiceNumber}</Text> | |
| 97 | + </GridItem> | |
| 98 | + <GridItem colSpan={{ base: 12, md: 6 }}> | |
| 99 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='600' color={"#000"} mb="0" mt={2}> | |
| 100 | + Title | |
| 101 | + </FormLabel> | |
| 102 | + <Input | |
| 103 | + fontSize='sm' | |
| 104 | + onChange={handleChange} onBlur={handleBlur} | |
| 105 | + value={values?.title} | |
| 106 | + name="title" | |
| 107 | + placeholder='Enter Title' | |
| 108 | + fontWeight='500' | |
| 109 | + /> | |
| 110 | + <Text mb='10px' color={'red'}> {errors.title && touched.title && errors.title}</Text> | |
| 111 | + </GridItem> | |
| 112 | + <GridItem colSpan={{ base: 12, md: 6 }}> | |
| 113 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='600' color={"#000"} mb="0" mt={2}> | |
| 114 | + Status | |
| 115 | + </FormLabel> | |
| 116 | + <Select | |
| 117 | + value={values.status} | |
| 118 | + name="status" | |
| 119 | + onChange={handleChange} | |
| 120 | + mb={errors.status && touched.status ? undefined : '10px'} | |
| 121 | + fontWeight='500' | |
| 122 | + placeholder={'Status'} | |
| 123 | + borderColor={errors.status && touched.status ? "red.300" : null} | |
| 124 | + > | |
| 125 | + <option value="Paid">Paid</option> | |
| 126 | + <option value="Unpaid">Unpaid</option> | |
| 127 | + <option value="Cancelled">Cancelled</option> | |
| 128 | + </Select> | |
| 129 | + <Text mb='10px' color={'red'}> {errors.status && touched.status && errors.status}</Text> | |
| 130 | + </GridItem> | |
| 131 | + <GridItem colSpan={{ base: 12, md: 6 }}> | |
| 132 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='600' color={"#000"} mb="0" mt={2}> | |
| 133 | + Contact Name | |
| 134 | + </FormLabel> | |
| 135 | + <Input | |
| 136 | + fontSize='sm' | |
| 137 | + onChange={handleChange} onBlur={handleBlur} | |
| 138 | + value={values?.contactName} | |
| 139 | + name="contactName" | |
| 140 | + placeholder='Enter Contact Name' | |
| 141 | + fontWeight='500' | |
| 142 | + /> | |
| 143 | + <Text mb='10px' color={'red'}> {errors.contactName && touched.contactName && errors.contactName}</Text> | |
| 144 | + </GridItem> | |
| 145 | + <GridItem colSpan={{ base: 12, md: 6 }}> | |
| 146 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='600' color={"#000"} mb="0" mt={2}> | |
| 147 | + Account Name | |
| 148 | + </FormLabel> | |
| 149 | + <Input | |
| 150 | + fontSize='sm' | |
| 151 | + onChange={handleChange} onBlur={handleBlur} | |
| 152 | + value={values?.accountName} | |
| 153 | + name="accountName" | |
| 154 | + placeholder='Enter Account Name' | |
| 155 | + fontWeight='500' | |
| 156 | + /> | |
| 157 | + <Text mb='10px' color={'red'}> {errors.accountName && touched.accountName && errors.accountName}</Text> | |
| 158 | + </GridItem> | |
| 159 | + <GridItem colSpan={{ base: 12, md: 6 }}> | |
| 160 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='600' color={"#000"} mb="0" mt={2}> | |
| 161 | + Grand Total | |
| 162 | + </FormLabel> | |
| 163 | + <Input | |
| 164 | + fontSize='sm' | |
| 165 | + onChange={handleChange} onBlur={handleBlur} | |
| 166 | + value={values?.grandTotal} | |
| 167 | + name="grandTotal" | |
| 168 | + placeholder='Enter Grand Total' | |
| 169 | + fontWeight='500' | |
| 170 | + /> | |
| 171 | + <Text mb='10px' color={'red'}> {errors.grandTotal && touched.grandTotal && errors.grandTotal}</Text> | |
| 172 | + </GridItem> | |
| 173 | + </Grid> | |
| 174 | + </ModalBody> | |
| 175 | + <ModalFooter> | |
| 176 | + <Button size="sm" variant="brand" mr={2} onClick={handleSubmit} disabled={isLoding || !dirty ? true : false} >{isLoding ? <Spinner /> : 'Search'}</Button> | |
| 177 | + <Button size="sm" variant="outline" colorScheme="red" onClick={() => resetForm()}>Clear</Button> | |
| 178 | + </ModalFooter> | |
| 179 | + </ModalContent> | |
| 180 | + </Modal> | |
| 181 | + </> | |
| 182 | + ) | |
| 183 | +} | |
| 184 | + | |
| 185 | +export default InvoiceAdvanceSearch |
+236-0Client/src/views/admin/invoice/components/InvoiceImport.js
| @@ -0,0 +1,236 @@ | ||
| 1 | +import React, { useState, useEffect } from 'react' | |
| 2 | +import { useLocation, useNavigate } from 'react-router-dom'; | |
| 3 | +import Papa from 'papaparse'; | |
| 4 | +import { | |
| 5 | + Flex, | |
| 6 | + useColorModeValue, | |
| 7 | + Select, | |
| 8 | + Button, | |
| 9 | + Grid, | |
| 10 | + GridItem, | |
| 11 | + Text | |
| 12 | +} from "@chakra-ui/react"; | |
| 13 | +import { useFormik } from "formik"; | |
| 14 | +import { postApi } from 'services/api'; | |
| 15 | +import { toast } from 'react-toastify'; | |
| 16 | +import moment from 'moment'; | |
| 17 | +import ExcelJS from 'exceljs'; | |
| 18 | +import Card from 'components/card/Card'; | |
| 19 | + | |
| 20 | +function InvoiceImport() { | |
| 21 | + | |
| 22 | + const location = useLocation(); | |
| 23 | + const { fileData, customFields } = location.state || {}; | |
| 24 | + const [importedFileFields, setImportedFileFields] = useState([]); | |
| 25 | + const [importedFileData, setImportedFileData] = useState([]); | |
| 26 | + const [isLoding, setIsLoding] = useState(false); | |
| 27 | + const navigate = useNavigate(); | |
| 28 | + const userId = JSON.parse(localStorage.getItem('user'))._id; | |
| 29 | + const [filterContact, setFilterContact] = useState([]); | |
| 30 | + | |
| 31 | + const columns = [ | |
| 32 | + { Header: 'Fields In Crm', accessor: 'crmFields' }, | |
| 33 | + { Header: 'Fields In File', accessor: 'fileFields' }, | |
| 34 | + ]; | |
| 35 | + | |
| 36 | + const initialFieldValues = Object.fromEntries( | |
| 37 | + (customFields || []).map(field => [field?.name, '']) | |
| 38 | + ); | |
| 39 | + const initialValues = { | |
| 40 | + ...initialFieldValues | |
| 41 | + }; | |
| 42 | + | |
| 43 | + const fieldsInCrm = [ | |
| 44 | + ...customFields?.map((field) => ({ Header: field?.label, accessor: field?.name, type: field?.type, formikType: field?.validations?.find(obj => obj.hasOwnProperty('formikType')) })) | |
| 45 | + ]; | |
| 46 | + | |
| 47 | + const formik = useFormik({ | |
| 48 | + initialValues: initialValues, | |
| 49 | + onSubmit: (values, { resetForm }) => { | |
| 50 | + | |
| 51 | + const invoiceData = importedFileData?.map((item, ind) => { | |
| 52 | + const invoices = { | |
| 53 | + createdDate: new Date(), | |
| 54 | + deleted: item[values.deleted || "deleted"] || false, | |
| 55 | + createBy: JSON.parse(localStorage.getItem('user'))._id, | |
| 56 | + modifiedBy: JSON.parse(localStorage.getItem('user'))._id, | |
| 57 | + }; | |
| 58 | + | |
| 59 | + fieldsInCrm?.forEach(field => { | |
| 60 | + const selectedField = values[field?.accessor]; | |
| 61 | + const fieldValue = item[selectedField] || ''; | |
| 62 | + | |
| 63 | + if (field?.type?.toLowerCase() === "date") { | |
| 64 | + invoices[field?.accessor] = moment(fieldValue).isValid() ? fieldValue : ''; | |
| 65 | + } else if (field?.type?.toLowerCase() === "number" && ['positive', 'negative'].includes(field?.formikType?.toLowerCase())) { | |
| 66 | + invoices[field?.accessor] = parseFloat(fieldValue) || ''; | |
| 67 | + } else if (field?.type?.toLowerCase() === "number") { | |
| 68 | + invoices[field?.accessor] = parseInt(fieldValue, 10) || ''; | |
| 69 | + } else { | |
| 70 | + invoices[field?.accessor] = fieldValue; | |
| 71 | + } | |
| 72 | + }); | |
| 73 | + | |
| 74 | + return invoices; | |
| 75 | + }); | |
| 76 | + | |
| 77 | + AddData(invoiceData); | |
| 78 | + } | |
| 79 | + }); | |
| 80 | + | |
| 81 | + const { errors, touched, values, handleBlur, handleChange, handleSubmit, setFieldValue, resetForm } = formik | |
| 82 | + | |
| 83 | + const AddData = async (invoices) => { | |
| 84 | + try { | |
| 85 | + setIsLoding(true); | |
| 86 | + let response = await postApi('api/invoices/addMany', invoices) | |
| 87 | + if (response.status === 200) { | |
| 88 | + toast.success(`Invoices imported successfully`) | |
| 89 | + resetForm(); | |
| 90 | + navigate('/invoices'); | |
| 91 | + } | |
| 92 | + } catch (e) { | |
| 93 | + console.error(e); | |
| 94 | + toast.error(`Invoices import failed`) | |
| 95 | + resetForm(); | |
| 96 | + navigate('/invoices'); | |
| 97 | + } | |
| 98 | + finally { | |
| 99 | + setIsLoding(false) | |
| 100 | + } | |
| 101 | + }; | |
| 102 | + | |
| 103 | + const parseFileData = async (file) => { | |
| 104 | + const reader = new FileReader(); | |
| 105 | + const extension = file.name.split('.').pop().toLowerCase(); | |
| 106 | + | |
| 107 | + reader.onload = async ({ target }) => { | |
| 108 | + | |
| 109 | + if (extension === 'csv') { | |
| 110 | + const csv = Papa.parse(target.result, { | |
| 111 | + header: true, | |
| 112 | + }); | |
| 113 | + const parsedData = csv?.data; | |
| 114 | + | |
| 115 | + if (parsedData && parsedData.length > 0) { | |
| 116 | + setImportedFileData(parsedData); | |
| 117 | + const fileHeadingFields = Object.keys(parsedData[0]); | |
| 118 | + setImportedFileFields(fileHeadingFields); | |
| 119 | + } else { | |
| 120 | + toast.error("Empty or invalid CSV file"); | |
| 121 | + navigate("/invoices"); | |
| 122 | + } | |
| 123 | + | |
| 124 | + } else if (extension === 'xlsx') { | |
| 125 | + const data = new Uint8Array(target.result); | |
| 126 | + const workbook = new ExcelJS.Workbook(); | |
| 127 | + | |
| 128 | + await workbook.xlsx.load(data); | |
| 129 | + | |
| 130 | + const worksheet = workbook.getWorksheet(1); | |
| 131 | + const jsonData = []; | |
| 132 | + | |
| 133 | + // Iterate over rows and cells | |
| 134 | + worksheet.eachRow({ includeEmpty: true }, (row, rowNumber) => { | |
| 135 | + const rowData = {}; | |
| 136 | + row.eachCell({ includeEmpty: true }, (cell, colNumber) => { | |
| 137 | + rowData[worksheet.getCell(1, colNumber).value] = cell.value; | |
| 138 | + }); | |
| 139 | + jsonData.push(rowData); | |
| 140 | + }); | |
| 141 | + jsonData?.splice(0, 1); | |
| 142 | + setImportedFileData(jsonData); | |
| 143 | + | |
| 144 | + if (jsonData && jsonData.length > 0) { | |
| 145 | + const fileHeadingFields = Object.keys(jsonData[0]); | |
| 146 | + setImportedFileFields(fileHeadingFields); | |
| 147 | + } else { | |
| 148 | + toast.error("Empty or invalid XLSX file"); | |
| 149 | + navigate("/invoices"); | |
| 150 | + } | |
| 151 | + } | |
| 152 | + }; | |
| 153 | + | |
| 154 | + if (extension === 'csv') { | |
| 155 | + reader.readAsText(file); | |
| 156 | + } else if (extension === 'xlsx') { | |
| 157 | + const blob = new Blob([file]); | |
| 158 | + reader.readAsArrayBuffer(blob); | |
| 159 | + } | |
| 160 | + }; | |
| 161 | + | |
| 162 | + useEffect(() => { | |
| 163 | + if (fileData && fileData.length > 0) { | |
| 164 | + const firstFile = fileData[0]; | |
| 165 | + parseFileData(firstFile); | |
| 166 | + } | |
| 167 | + }, [fileData]); | |
| 168 | + | |
| 169 | + | |
| 170 | + useEffect(() => { | |
| 171 | + const filterContactData = importedFileFields?.filter(field => { | |
| 172 | + const result = fieldsInCrm?.find(data => field === data?.accessor || field === data?.Header); | |
| 173 | + if (result) { | |
| 174 | + setFieldValue(result?.accessor, field); | |
| 175 | + return true; | |
| 176 | + } | |
| 177 | + return false; | |
| 178 | + }); | |
| 179 | + setFilterContact(filterContactData); | |
| 180 | + }, [importedFileFields]); | |
| 181 | + | |
| 182 | + return ( | |
| 183 | + <> | |
| 184 | + <Card overflowY={"auto"} className="importTable"> | |
| 185 | + <Text color={"secondaryGray.900"} | |
| 186 | + fontSize="22px" | |
| 187 | + fontWeight="700" | |
| 188 | + mb='20px' | |
| 189 | + >Import Quotes</Text> | |
| 190 | + <Grid templateColumns="repeat(12, 1fr)" mb={3} pb={2} gap={1} borderBottom={'1px solid #e2e8f0'}> | |
| 191 | + { | |
| 192 | + columns.map((column, index) => ( | |
| 193 | + <GridItem key={index} colSpan={{ base: 6 }} fontWeight={'600'} fontSize={{ sm: "14px", lg: "14px" }} color="secondaryGray.900" style={{ textTransform: "uppercase" }}> | |
| 194 | + {column.Header} | |
| 195 | + </GridItem> | |
| 196 | + )) | |
| 197 | + } | |
| 198 | + </Grid> | |
| 199 | + <Grid templateColumns="repeat(12, 1fr)" mb={3} gap={1} overflowY={'auto'}> | |
| 200 | + { | |
| 201 | + fieldsInCrm?.map((item, index) => ( | |
| 202 | + <> | |
| 203 | + <GridItem colSpan={{ base: 6 }} key={item.id} mt='10px'> | |
| 204 | + {item.Header} | |
| 205 | + </GridItem> | |
| 206 | + <GridItem colSpan={{ base: 4 }}> | |
| 207 | + <Select | |
| 208 | + variant="flushed" | |
| 209 | + fontWeight='500' | |
| 210 | + isSearchable | |
| 211 | + value={values[item.accessor]} | |
| 212 | + name={item.accessor} | |
| 213 | + onChange={handleChange} | |
| 214 | + > | |
| 215 | + <option value=''> {filterContact ? filterContact.find((data) => (item.Header === data || item.accessor === data) && data) ? filterContact.find((data) => (item.Header === data || item.accessor === data) && data) : 'Select Field In File' : 'Select Field In File'}</option> | |
| 216 | + { | |
| 217 | + importedFileFields?.map(field => ( | |
| 218 | + <option value={field} key={field}>{field}</option> | |
| 219 | + )) | |
| 220 | + } | |
| 221 | + </Select> | |
| 222 | + </GridItem> | |
| 223 | + </> | |
| 224 | + )) | |
| 225 | + } | |
| 226 | + </Grid> | |
| 227 | + | |
| 228 | + <Flex Flex justifyContent={'end'} mt='5' > | |
| 229 | + <Button size="sm" onClick={() => handleSubmit()} variant="brand">Save</Button> | |
| 230 | + </Flex> | |
| 231 | + </Card> | |
| 232 | + </> | |
| 233 | + ) | |
| 234 | +} | |
| 235 | + | |
| 236 | +export default InvoiceImport |
+664-0Client/src/views/admin/invoice/index.js
| @@ -0,0 +1,664 @@ | ||
| 1 | +import { DeleteIcon, EditIcon, SearchIcon, ViewIcon } from '@chakra-ui/icons'; | |
| 2 | +import { Button, Menu, MenuButton, MenuItem, MenuList, Text, useDisclosure } from '@chakra-ui/react'; | |
| 3 | +import html2pdf from "html2pdf.js"; | |
| 4 | +import moment from 'moment'; | |
| 5 | +import { useEffect, useState } from 'react'; | |
| 6 | +import { CiMenuKebab } from 'react-icons/ci'; | |
| 7 | +import { useDispatch } from 'react-redux'; | |
| 8 | +import { useNavigate } from "react-router-dom"; | |
| 9 | +import { toast } from 'react-toastify'; | |
| 10 | +import { deleteManyApi } from 'services/api'; | |
| 11 | +import CommonDeleteModel from '../../../components/commonDeleteModel'; | |
| 12 | +import CommonCheckTable from '../../../components/reactTable/checktable'; | |
| 13 | +import { HasAccess } from '../../../redux/accessUtils'; | |
| 14 | +import { fetchInvoicesData } from '../../../redux/slices/invoicesSlice'; | |
| 15 | +import AddEdit from './AddEdit'; | |
| 16 | +import ImportModal from './components/ImportModel'; | |
| 17 | +import InvoiceAdvanceSearch from './components/InvoiceAdvanceSearch'; | |
| 18 | +import Preview from './preview'; | |
| 19 | +import { TbFileInvoice } from 'react-icons/tb'; | |
| 20 | + | |
| 21 | +const Index = (props) => { | |
| 22 | + const [action, setAction] = useState(false); | |
| 23 | + const { isOpen, onOpen, onClose } = useDisclosure(); | |
| 24 | + const [selectedValues, setSelectedValues] = useState([]); | |
| 25 | + const [advanceSearch, setAdvanceSearch] = useState(false); | |
| 26 | + const [getTagValuesOutSide, setGetTagValuesOutside] = useState([]); | |
| 27 | + const [searchboxOutside, setSearchboxOutside] = useState(''); | |
| 28 | + const user = JSON.parse(localStorage.getItem("user")); | |
| 29 | + const navigate = useNavigate(); | |
| 30 | + const dispatch = useDispatch(); | |
| 31 | + const [isLoding, setIsLoding] = useState(false); | |
| 32 | + const [loading, setLoading] = useState(false); | |
| 33 | + const [data, setData] = useState([]); | |
| 34 | + const [displaySearchData, setDisplaySearchData] = useState(false); | |
| 35 | + const [searchedData, setSearchedData] = useState([]); | |
| 36 | + const [selectedId, setSelectedId] = useState(); | |
| 37 | + const [selectedPreviewId, setSelectedPreviewId] = useState(); | |
| 38 | + const [deleteModel, setDelete] = useState(false); | |
| 39 | + const [edit, setEdit] = useState(false); | |
| 40 | + const [type, setType] = useState("") | |
| 41 | + const [isImport, setIsImport] = useState(false); | |
| 42 | + const [isOpenPreview, setIsOpenPreview] = useState(false) | |
| 43 | + | |
| 44 | + const [permission, accountAccess, contactAccess] = HasAccess(["Invoices", "Account", "Contacts"]); | |
| 45 | + | |
| 46 | + const actionHeader = { | |
| 47 | + Header: "Action", | |
| 48 | + accessor: "action", | |
| 49 | + isSortable: false, | |
| 50 | + center: true, | |
| 51 | + cell: ({ row, i }) => ( | |
| 52 | + <Text fontSize="md" fontWeight="900" textAlign={"center"}> | |
| 53 | + <Menu isLazy> | |
| 54 | + <MenuButton> | |
| 55 | + <CiMenuKebab /> | |
| 56 | + </MenuButton> | |
| 57 | + <MenuList | |
| 58 | + minW={"fit-content"} | |
| 59 | + transform={"translate(1520px, 173px);"} | |
| 60 | + > | |
| 61 | + {permission?.update && ( | |
| 62 | + <MenuItem | |
| 63 | + py={2.5} | |
| 64 | + icon={<EditIcon fontSize={15} mb={1} />} | |
| 65 | + onClick={() => { | |
| 66 | + setType("edit"); | |
| 67 | + onOpen(); | |
| 68 | + setSelectedId(row?.values?._id); | |
| 69 | + }} | |
| 70 | + > | |
| 71 | + Edit | |
| 72 | + </MenuItem> | |
| 73 | + )} | |
| 74 | + {permission?.view && ( | |
| 75 | + <MenuItem | |
| 76 | + py={2.5} | |
| 77 | + color={"green"} | |
| 78 | + icon={<ViewIcon mb={1} fontSize={15} />} | |
| 79 | + onClick={() => { | |
| 80 | + navigate(`/invoicesView/${row?.values?._id}`); | |
| 81 | + }} | |
| 82 | + > | |
| 83 | + View | |
| 84 | + </MenuItem> | |
| 85 | + )} | |
| 86 | + <MenuItem | |
| 87 | + py={2.5} | |
| 88 | + color={"black"} | |
| 89 | + icon={<TbFileInvoice mb={1} fontSize={15} />} | |
| 90 | + onClick={() => { | |
| 91 | + setSelectedId(row?.values?._id) | |
| 92 | + setIsOpenPreview(true) | |
| 93 | + }} | |
| 94 | + > | |
| 95 | + Invoice | |
| 96 | + </MenuItem> | |
| 97 | + {permission?.delete && ( | |
| 98 | + <MenuItem | |
| 99 | + py={2.5} | |
| 100 | + color={"red"} | |
| 101 | + icon={<DeleteIcon fontSize={15} mb={1} />} | |
| 102 | + onClick={() => { | |
| 103 | + setDelete(true); | |
| 104 | + setSelectedValues([row?.values?._id]); | |
| 105 | + }} | |
| 106 | + > | |
| 107 | + Delete | |
| 108 | + </MenuItem> | |
| 109 | + )} | |
| 110 | + </MenuList> | |
| 111 | + </Menu> | |
| 112 | + </Text> | |
| 113 | + ), | |
| 114 | + }; | |
| 115 | + const tableColumns = [ | |
| 116 | + { Header: "#", accessor: "_id", isSortable: false, width: 10 }, | |
| 117 | + { Header: "Invoice Number", accessor: "invoiceNumber", isSortable: false, width: 10 }, | |
| 118 | + { | |
| 119 | + Header: 'Title', accessor: 'title', cell: (cell) => ( | |
| 120 | + <div className="selectOpt"> | |
| 121 | + <Text | |
| 122 | + onClick={() => handleViewOpen(cell?.row?.original._id)} | |
| 123 | + me="10px" | |
| 124 | + sx={{ '&:hover': { color: 'blue.500', textDecoration: 'underline' }, cursor: 'pointer' }} | |
| 125 | + color='brand.600' | |
| 126 | + fontSize="sm" | |
| 127 | + fontWeight="700" | |
| 128 | + > | |
| 129 | + {cell?.value} | |
| 130 | + </Text> | |
| 131 | + </div> | |
| 132 | + ) | |
| 133 | + }, | |
| 134 | + { | |
| 135 | + Header: 'Status', accessor: 'status', | |
| 136 | + }, | |
| 137 | + { | |
| 138 | + Header: 'Contact', | |
| 139 | + accessor: 'contact', | |
| 140 | + cell: (cell) => ( | |
| 141 | + (user.role === 'superAdmin' || contactAccess?.view) ? | |
| 142 | + <div className="selectOpt"> | |
| 143 | + <Text | |
| 144 | + onClick={() => navigate(cell?.row?.original.contact !== null && `/contactView/${cell?.row?.original.contact}`)} | |
| 145 | + me="10px" | |
| 146 | + sx={{ '&:hover': { color: 'blue.500', textDecoration: 'underline' }, cursor: 'pointer' }} | |
| 147 | + color='brand.600' | |
| 148 | + fontSize="sm" | |
| 149 | + fontWeight="700" | |
| 150 | + > | |
| 151 | + {cell?.row?.original?.contactName ? cell?.row?.original?.contactName : "-"} | |
| 152 | + </Text> | |
| 153 | + </div> | |
| 154 | + : | |
| 155 | + <Text | |
| 156 | + > | |
| 157 | + {cell?.row?.original?.contactName ? cell?.row?.original?.contactName : "-"} | |
| 158 | + </Text> | |
| 159 | + ) | |
| 160 | + }, | |
| 161 | + { | |
| 162 | + Header: 'Account', | |
| 163 | + accessor: 'account', | |
| 164 | + cell: (cell) => ( | |
| 165 | + (user.role === 'superAdmin' || accountAccess?.view) ? | |
| 166 | + <div className="selectOpt"> | |
| 167 | + <Text | |
| 168 | + onClick={() => navigate(cell?.row?.original.account !== null && `/accountView/${cell?.row?.original.account}`)} | |
| 169 | + me="10px" | |
| 170 | + sx={{ '&:hover': { color: 'blue.500', textDecoration: 'underline' }, cursor: 'pointer' }} | |
| 171 | + color='brand.600' | |
| 172 | + fontSize="sm" | |
| 173 | + fontWeight="700" | |
| 174 | + > | |
| 175 | + {cell?.row?.original?.accountName ? cell?.row?.original?.accountName : "-"} | |
| 176 | + </Text> | |
| 177 | + </div> | |
| 178 | + : | |
| 179 | + <Text | |
| 180 | + > | |
| 181 | + {cell?.row?.original?.accountName ? cell?.row?.original?.accountName : "-"} | |
| 182 | + </Text> | |
| 183 | + ) | |
| 184 | + }, | |
| 185 | + { | |
| 186 | + Header: "Grand Total", | |
| 187 | + accessor: "grandTotal", | |
| 188 | + cell: (cell) => ( | |
| 189 | + <div className="selectOpt"> | |
| 190 | + <Text> | |
| 191 | + {cell?.row?.original?.grandTotal ? `${cell?.row?.original?.currency} ${cell?.row?.original?.grandTotal}` : '-'} | |
| 192 | + </Text> | |
| 193 | + </div> | |
| 194 | + ) | |
| 195 | + }, | |
| 196 | + | |
| 197 | + ...(permission?.update || permission?.view || permission?.delete ? [actionHeader] : []) | |
| 198 | + | |
| 199 | + ]; | |
| 200 | + const handleViewOpen = (id) => { | |
| 201 | + navigate(`/invoicesView/${id}`) | |
| 202 | + } | |
| 203 | + const customFields = [ | |
| 204 | + { | |
| 205 | + "name": "invoiceNumber", | |
| 206 | + "label": "Invoice Number", | |
| 207 | + "type": "number", | |
| 208 | + "fixed": false, | |
| 209 | + "isDefault": false, | |
| 210 | + "delete": false, | |
| 211 | + "belongsTo": null, | |
| 212 | + "backendType": "Mixed", | |
| 213 | + "isTableField": false, | |
| 214 | + "isView": false, | |
| 215 | + "options": [ | |
| 216 | + { | |
| 217 | + "name": "", | |
| 218 | + "value": "", | |
| 219 | + "_id": "669508bc20a9be3594c8652f" | |
| 220 | + }, | |
| 221 | + { | |
| 222 | + "name": "", | |
| 223 | + "value": "", | |
| 224 | + "_id": "669508bc20a9be3594c86530" | |
| 225 | + } | |
| 226 | + ], | |
| 227 | + "validation": [ | |
| 228 | + { | |
| 229 | + "require": true, | |
| 230 | + "message": "Quote Number is required", | |
| 231 | + "_id": "669508bc20a9be3594c86531" | |
| 232 | + }, | |
| 233 | + { | |
| 234 | + "min": false, | |
| 235 | + "value": "", | |
| 236 | + "message": "", | |
| 237 | + "_id": "669508bc20a9be3594c86532" | |
| 238 | + }, | |
| 239 | + { | |
| 240 | + "max": false, | |
| 241 | + "value": "", | |
| 242 | + "message": "", | |
| 243 | + "_id": "669508bc20a9be3594c86533" | |
| 244 | + }, | |
| 245 | + { | |
| 246 | + "value": "", | |
| 247 | + "message": "", | |
| 248 | + "match": false, | |
| 249 | + "_id": "669508bc20a9be3594c86534" | |
| 250 | + }, | |
| 251 | + { | |
| 252 | + "message": "", | |
| 253 | + "formikType": "", | |
| 254 | + "_id": "669508bc20a9be3594c86535" | |
| 255 | + } | |
| 256 | + ], | |
| 257 | + "_id": "669508bc20a9be3594c8652e" | |
| 258 | + }, | |
| 259 | + { | |
| 260 | + "name": "title", | |
| 261 | + "label": "Title", | |
| 262 | + "type": "text", | |
| 263 | + "fixed": false, | |
| 264 | + "isDefault": false, | |
| 265 | + "delete": false, | |
| 266 | + "belongsTo": null, | |
| 267 | + "backendType": "Mixed", | |
| 268 | + "isTableField": false, | |
| 269 | + "isView": false, | |
| 270 | + "options": [ | |
| 271 | + { | |
| 272 | + "name": "", | |
| 273 | + "value": "", | |
| 274 | + "_id": "669508f820a9be3594c8686c" | |
| 275 | + }, | |
| 276 | + { | |
| 277 | + "name": "", | |
| 278 | + "value": "", | |
| 279 | + "_id": "669508f820a9be3594c8686d" | |
| 280 | + } | |
| 281 | + ], | |
| 282 | + "validation": [ | |
| 283 | + { | |
| 284 | + "require": true, | |
| 285 | + "message": "Title is required ", | |
| 286 | + "_id": "669508f820a9be3594c8686e" | |
| 287 | + }, | |
| 288 | + { | |
| 289 | + "min": false, | |
| 290 | + "value": "", | |
| 291 | + "message": "", | |
| 292 | + "_id": "669508f820a9be3594c8686f" | |
| 293 | + }, | |
| 294 | + { | |
| 295 | + "max": false, | |
| 296 | + "value": "", | |
| 297 | + "message": "", | |
| 298 | + "_id": "669508f820a9be3594c86870" | |
| 299 | + }, | |
| 300 | + { | |
| 301 | + "value": "", | |
| 302 | + "message": "", | |
| 303 | + "match": false, | |
| 304 | + "_id": "669508f820a9be3594c86871" | |
| 305 | + }, | |
| 306 | + { | |
| 307 | + "message": "", | |
| 308 | + "formikType": "", | |
| 309 | + "_id": "669508f820a9be3594c86872" | |
| 310 | + } | |
| 311 | + ], | |
| 312 | + "_id": "669508f820a9be3594c8686b" | |
| 313 | + }, | |
| 314 | + { | |
| 315 | + "name": "status", | |
| 316 | + "label": "Status", | |
| 317 | + "type": "select", | |
| 318 | + "fixed": false, | |
| 319 | + "isDefault": false, | |
| 320 | + "delete": false, | |
| 321 | + "belongsTo": null, | |
| 322 | + "backendType": "Mixed", | |
| 323 | + "isTableField": false, | |
| 324 | + "isView": false, | |
| 325 | + "options": [ | |
| 326 | + ], | |
| 327 | + "validation": [ | |
| 328 | + { | |
| 329 | + "require": false, | |
| 330 | + "message": "", | |
| 331 | + "_id": "6695095120a9be3594c86bc9" | |
| 332 | + }, | |
| 333 | + { | |
| 334 | + "min": false, | |
| 335 | + "value": "", | |
| 336 | + "message": "", | |
| 337 | + "_id": "6695095120a9be3594c86bca" | |
| 338 | + }, | |
| 339 | + { | |
| 340 | + "max": false, | |
| 341 | + "value": "", | |
| 342 | + "message": "", | |
| 343 | + "_id": "6695095120a9be3594c86bcb" | |
| 344 | + }, | |
| 345 | + { | |
| 346 | + "value": "", | |
| 347 | + "message": "", | |
| 348 | + "match": false, | |
| 349 | + "_id": "6695095120a9be3594c86bcc" | |
| 350 | + }, | |
| 351 | + { | |
| 352 | + "message": "", | |
| 353 | + "formikType": "", | |
| 354 | + "_id": "6695095120a9be3594c86bcd" | |
| 355 | + } | |
| 356 | + ], | |
| 357 | + "_id": "6695095120a9be3594c86bc0" | |
| 358 | + }, | |
| 359 | + { | |
| 360 | + "name": "contact", | |
| 361 | + "label": "Contact", | |
| 362 | + "type": "text", | |
| 363 | + "fixed": false, | |
| 364 | + "isDefault": false, | |
| 365 | + "delete": false, | |
| 366 | + "belongsTo": null, | |
| 367 | + "backendType": "Mixed", | |
| 368 | + "isTableField": false, | |
| 369 | + "isView": false, | |
| 370 | + "options": [ | |
| 371 | + { | |
| 372 | + "name": "", | |
| 373 | + "value": "", | |
| 374 | + "_id": "6695099520a9be3594c86f46" | |
| 375 | + }, | |
| 376 | + { | |
| 377 | + "name": "", | |
| 378 | + "value": "", | |
| 379 | + "_id": "6695099520a9be3594c86f47" | |
| 380 | + } | |
| 381 | + ], | |
| 382 | + "validation": [ | |
| 383 | + { | |
| 384 | + "require": false, | |
| 385 | + "message": "", | |
| 386 | + "_id": "6695099520a9be3594c86f48" | |
| 387 | + }, | |
| 388 | + { | |
| 389 | + "min": false, | |
| 390 | + "value": "", | |
| 391 | + "message": "", | |
| 392 | + "_id": "6695099520a9be3594c86f49" | |
| 393 | + }, | |
| 394 | + { | |
| 395 | + "max": false, | |
| 396 | + "value": "", | |
| 397 | + "message": "", | |
| 398 | + "_id": "6695099520a9be3594c86f4a" | |
| 399 | + }, | |
| 400 | + { | |
| 401 | + "value": "", | |
| 402 | + "message": "", | |
| 403 | + "match": false, | |
| 404 | + "_id": "6695099520a9be3594c86f4b" | |
| 405 | + }, | |
| 406 | + { | |
| 407 | + "message": "", | |
| 408 | + "formikType": "", | |
| 409 | + "_id": "6695099520a9be3594c86f4c" | |
| 410 | + } | |
| 411 | + ], | |
| 412 | + "_id": "6695099520a9be3594c86f45" | |
| 413 | + }, | |
| 414 | + { | |
| 415 | + "name": "account", | |
| 416 | + "label": "Account", | |
| 417 | + "type": "text", | |
| 418 | + "fixed": false, | |
| 419 | + "isDefault": false, | |
| 420 | + "delete": false, | |
| 421 | + "belongsTo": null, | |
| 422 | + "backendType": "Mixed", | |
| 423 | + "isTableField": false, | |
| 424 | + "isView": false, | |
| 425 | + "options": [ | |
| 426 | + { | |
| 427 | + "name": "", | |
| 428 | + "value": "", | |
| 429 | + "_id": "669509a320a9be3594c872dd" | |
| 430 | + }, | |
| 431 | + { | |
| 432 | + "name": "", | |
| 433 | + "value": "", | |
| 434 | + "_id": "669509a320a9be3594c872de" | |
| 435 | + } | |
| 436 | + ], | |
| 437 | + "validation": [ | |
| 438 | + { | |
| 439 | + "require": false, | |
| 440 | + "message": "", | |
| 441 | + "_id": "669509a320a9be3594c872df" | |
| 442 | + }, | |
| 443 | + { | |
| 444 | + "min": false, | |
| 445 | + "value": "", | |
| 446 | + "message": "", | |
| 447 | + "_id": "669509a320a9be3594c872e0" | |
| 448 | + }, | |
| 449 | + { | |
| 450 | + "max": false, | |
| 451 | + "value": "", | |
| 452 | + "message": "", | |
| 453 | + "_id": "669509a320a9be3594c872e1" | |
| 454 | + }, | |
| 455 | + { | |
| 456 | + "value": "", | |
| 457 | + "message": "", | |
| 458 | + "match": false, | |
| 459 | + "_id": "669509a320a9be3594c872e2" | |
| 460 | + }, | |
| 461 | + { | |
| 462 | + "message": "", | |
| 463 | + "formikType": "", | |
| 464 | + "_id": "669509a320a9be3594c872e3" | |
| 465 | + } | |
| 466 | + ], | |
| 467 | + "_id": "669509a320a9be3594c872dc" | |
| 468 | + }, | |
| 469 | + { | |
| 470 | + "name": "grandTotal", | |
| 471 | + "label": "Grand Total", | |
| 472 | + "type": "number", | |
| 473 | + "fixed": false, | |
| 474 | + "isDefault": false, | |
| 475 | + "delete": false, | |
| 476 | + "belongsTo": null, | |
| 477 | + "backendType": "Mixed", | |
| 478 | + "isTableField": false, | |
| 479 | + "isView": false, | |
| 480 | + "options": [ | |
| 481 | + { | |
| 482 | + "name": "", | |
| 483 | + "value": "", | |
| 484 | + "_id": "669509b120a9be3594c8768c" | |
| 485 | + }, | |
| 486 | + { | |
| 487 | + "name": "", | |
| 488 | + "value": "", | |
| 489 | + "_id": "669509b120a9be3594c8768d" | |
| 490 | + } | |
| 491 | + ], | |
| 492 | + "validation": [ | |
| 493 | + { | |
| 494 | + "require": false, | |
| 495 | + "message": "", | |
| 496 | + "_id": "669509b120a9be3594c8768e" | |
| 497 | + }, | |
| 498 | + { | |
| 499 | + "min": false, | |
| 500 | + "value": "", | |
| 501 | + "message": "", | |
| 502 | + "_id": "669509b120a9be3594c8768f" | |
| 503 | + }, | |
| 504 | + { | |
| 505 | + "max": false, | |
| 506 | + "value": "", | |
| 507 | + "message": "", | |
| 508 | + "_id": "669509b120a9be3594c87690" | |
| 509 | + }, | |
| 510 | + { | |
| 511 | + "value": "", | |
| 512 | + "message": "", | |
| 513 | + "match": false, | |
| 514 | + "_id": "669509b120a9be3594c87691" | |
| 515 | + }, | |
| 516 | + { | |
| 517 | + "message": "", | |
| 518 | + "formikType": "", | |
| 519 | + "_id": "669509b120a9be3594c87692" | |
| 520 | + } | |
| 521 | + ], | |
| 522 | + "_id": "669509b120a9be3594c8768b" | |
| 523 | + }, | |
| 524 | + ] | |
| 525 | + | |
| 526 | + const handleOpenAdd = () => { | |
| 527 | + onOpen(); | |
| 528 | + setType("add") | |
| 529 | + } | |
| 530 | + | |
| 531 | + const handleDelete = async (ids) => { | |
| 532 | + try { | |
| 533 | + setIsLoding(true); | |
| 534 | + let response = await deleteManyApi("api/invoices/deleteMany", ids); | |
| 535 | + if (response.status === 200) { | |
| 536 | + toast.success(`Invoices Delete successfully`) | |
| 537 | + setSelectedValues([]); | |
| 538 | + setDelete(false); | |
| 539 | + setAction((pre) => !pre); | |
| 540 | + } | |
| 541 | + } catch (error) { | |
| 542 | + console.log(error); | |
| 543 | + toast.error(`server error`) | |
| 544 | + | |
| 545 | + } finally { | |
| 546 | + setIsLoding(false); | |
| 547 | + } | |
| 548 | + }; | |
| 549 | + | |
| 550 | + const fetchData = async () => { | |
| 551 | + setIsLoding(true) | |
| 552 | + const result = await dispatch(fetchInvoicesData()) | |
| 553 | + | |
| 554 | + if (result.payload.status === 200) { | |
| 555 | + setData(result?.payload?.data); | |
| 556 | + } else { | |
| 557 | + toast.error("Failed to fetch data", "error"); | |
| 558 | + } | |
| 559 | + setIsLoding(false) | |
| 560 | + } | |
| 561 | + | |
| 562 | + const [columns, setColumns] = useState([...tableColumns]); | |
| 563 | + const [selectedColumns, setSelectedColumns] = useState([...tableColumns]); | |
| 564 | + const dataColumn = tableColumns?.filter(item => selectedColumns?.find(colum => colum?.Header === item.Header)) | |
| 565 | + | |
| 566 | + const generatePDF = () => { | |
| 567 | + setLoading(true) | |
| 568 | + const element = document.getElementById("reports"); | |
| 569 | + | |
| 570 | + if (element) { | |
| 571 | + html2pdf() | |
| 572 | + .from(element) | |
| 573 | + .set({ | |
| 574 | + margin: [0, 0, 0, 0], | |
| 575 | + filename: `Invoice_${moment().format("DD-MM-YYYY")}.pdf`, | |
| 576 | + image: { type: "jpeg", quality: 0.98 }, | |
| 577 | + html2canvas: { scale: 2, useCORS: true, allowTaint: true }, | |
| 578 | + jsPDF: { unit: "in", format: "letter", orientation: "portrait" }, | |
| 579 | + }) | |
| 580 | + .save().then(() => { | |
| 581 | + setLoading(false) | |
| 582 | + setIsOpenPreview(false) | |
| 583 | + }) | |
| 584 | + // }, 500); | |
| 585 | + } else { | |
| 586 | + console.error("Element with ID 'reports' not found."); | |
| 587 | + setLoading(false) | |
| 588 | + } | |
| 589 | + }; | |
| 590 | + | |
| 591 | + useEffect(() => { | |
| 592 | + fetchData(); | |
| 593 | + }, [action]) | |
| 594 | + | |
| 595 | + return ( | |
| 596 | + <div> | |
| 597 | + <CommonCheckTable | |
| 598 | + title={"Invoices"} | |
| 599 | + isLoding={isLoding} | |
| 600 | + columnData={columns ?? []} | |
| 601 | + // dataColumn={dataColumn ?? []} | |
| 602 | + allData={data ?? []} | |
| 603 | + tableData={data} | |
| 604 | + searchDisplay={displaySearchData} | |
| 605 | + setSearchDisplay={setDisplaySearchData} | |
| 606 | + searchedDataOut={searchedData} | |
| 607 | + setSearchedDataOut={setSearchedData} | |
| 608 | + tableCustomFields={[]} | |
| 609 | + access={permission} | |
| 610 | + // action={action} | |
| 611 | + // setAction={setAction} | |
| 612 | + // selectedColumns={selectedColumns} | |
| 613 | + // setSelectedColumns={setSelectedColumns} | |
| 614 | + // isOpen={isOpen} | |
| 615 | + // onClose={onclose} | |
| 616 | + setIsImport={setIsImport} | |
| 617 | + onOpen={handleOpenAdd} | |
| 618 | + selectedValues={selectedValues} | |
| 619 | + setSelectedValues={setSelectedValues} | |
| 620 | + setDelete={setDelete} | |
| 621 | + deleteMany={false} | |
| 622 | + AdvanceSearch={ | |
| 623 | + <Button variant="outline" colorScheme='brand' leftIcon={<SearchIcon />} mt={{ sm: "5px", md: "0" }} size="sm" onClick={() => setAdvanceSearch(true)}>Advance Search</Button> | |
| 624 | + } | |
| 625 | + getTagValuesOutSide={getTagValuesOutSide} | |
| 626 | + searchboxOutside={searchboxOutside} | |
| 627 | + setGetTagValuesOutside={setGetTagValuesOutside} | |
| 628 | + setSearchboxOutside={setSearchboxOutside} | |
| 629 | + handleSearchType="InvoiceSearch" | |
| 630 | + /> | |
| 631 | + | |
| 632 | + <InvoiceAdvanceSearch | |
| 633 | + advanceSearch={advanceSearch} | |
| 634 | + setAdvanceSearch={setAdvanceSearch} | |
| 635 | + setSearchedData={setSearchedData} | |
| 636 | + setDisplaySearchData={setDisplaySearchData} | |
| 637 | + allData={data ?? []} | |
| 638 | + setAction={setAction} | |
| 639 | + setGetTagValues={setGetTagValuesOutside} | |
| 640 | + setSearchbox={setSearchboxOutside} | |
| 641 | + /> | |
| 642 | + | |
| 643 | + <Preview isOpen={isOpenPreview} onClose={setIsOpenPreview} generatePDF={generatePDF} id="reports" selectedId={selectedId} isLoading={loading} /> | |
| 644 | + | |
| 645 | + <AddEdit isOpen={isOpen} size={"lg"} onClose={onClose} setAction={setAction} type={type} selectedId={selectedId} action={action} /> | |
| 646 | + <CommonDeleteModel | |
| 647 | + isOpen={deleteModel} | |
| 648 | + onClose={() => setDelete(false)} | |
| 649 | + type="Invoices" | |
| 650 | + handleDeleteData={handleDelete} | |
| 651 | + ids={selectedValues} | |
| 652 | + /> | |
| 653 | + | |
| 654 | + <ImportModal | |
| 655 | + text="Invoices file" | |
| 656 | + isOpen={isImport} | |
| 657 | + onClose={setIsImport} | |
| 658 | + customFields={customFields} | |
| 659 | + /> | |
| 660 | + </div> | |
| 661 | + ) | |
| 662 | +} | |
| 663 | + | |
| 664 | +export default Index |
+197-0Client/src/views/admin/invoice/preview.js
| @@ -0,0 +1,197 @@ | ||
| 1 | +import { CloseIcon } from '@chakra-ui/icons'; | |
| 2 | +import { Button, Drawer, DrawerBody, DrawerContent, DrawerFooter, DrawerHeader, IconButton, Image, Modal, ModalBody, ModalContent, ModalFooter, ModalHeader, Spinner } from '@chakra-ui/react'; | |
| 3 | +import moment from 'moment'; | |
| 4 | +import { useEffect, useState } from 'react'; | |
| 5 | +import { useSelector } from 'react-redux'; | |
| 6 | +import { getApi } from 'services/api'; | |
| 7 | + | |
| 8 | +const Preview = (props) => { | |
| 9 | + const { onClose, isOpen, id, generatePDF, selectedId, isLoading } = props | |
| 10 | + const [invoiceDetails, setInvoiceDetails] = useState({}); | |
| 11 | + const [isLoding, setIsLoding] = useState(false) | |
| 12 | + const largeLogo = useSelector((state) => state?.images?.images?.filter(item => item?.isActive === true)); | |
| 13 | + | |
| 14 | + const fetchInvoiceDetails = async () => { | |
| 15 | + try { | |
| 16 | + setIsLoding(true) | |
| 17 | + let result = await getApi('api/invoices/view/', selectedId) | |
| 18 | + if (result?.status === 200) { | |
| 19 | + setInvoiceDetails(result?.data?.result) | |
| 20 | + } | |
| 21 | + } | |
| 22 | + catch (e) { | |
| 23 | + console.log(e); | |
| 24 | + } | |
| 25 | + finally { | |
| 26 | + setIsLoding(false) | |
| 27 | + } | |
| 28 | + } | |
| 29 | + | |
| 30 | + useEffect(() => { | |
| 31 | + if (selectedId) fetchInvoiceDetails() | |
| 32 | + }, [selectedId]) | |
| 33 | + | |
| 34 | + return ( | |
| 35 | + <Drawer isOpen={isOpen} size={'lg'}> | |
| 36 | + <DrawerContent> | |
| 37 | + <DrawerHeader alignItems={"center"} justifyContent='space-between' display='flex' > | |
| 38 | + Invoice | |
| 39 | + <IconButton onClick={() => onClose(false)} icon={<CloseIcon />} /> | |
| 40 | + </DrawerHeader> | |
| 41 | + <DrawerBody> | |
| 42 | + { | |
| 43 | + !isLoding ? | |
| 44 | + <div id={id} style={{ padding: "5px" }}> | |
| 45 | + <div className="invoice-container" > | |
| 46 | + <div style={{ marginBottom: 10 }}><h1 style={{ fontSize: 30, textAlign: "center" }}>Invoice</h1></div> | |
| 47 | + <div className="invoice-header"> | |
| 48 | + <div> | |
| 49 | + <Image | |
| 50 | + style={{ width: "100%", height: '52px' }} | |
| 51 | + src={largeLogo[0]?.logoLgImg} | |
| 52 | + alt="Logo" | |
| 53 | + cursor="pointer" | |
| 54 | + userSelect="none" | |
| 55 | + my={2} | |
| 56 | + /> | |
| 57 | + </div> | |
| 58 | + <div className="invoice-details"> | |
| 59 | + <table> | |
| 60 | + <tr> | |
| 61 | + <th style={{ textAlign: "start" }}>Invoice No.</th> | |
| 62 | + <td>:</td> | |
| 63 | + <td style={{ textAlign: "start" }}>{invoiceDetails?.invoiceNumber}</td> | |
| 64 | + </tr> | |
| 65 | + <tr> | |
| 66 | + <th style={{ textAlign: "start" }}>Invoice Date</th> | |
| 67 | + <td>:</td> | |
| 68 | + <td style={{ textAlign: "start" }}>{invoiceDetails?.invoiceDate && moment(invoiceDetails?.invoiceDate).format("DD-MM-YYYY")}</td> | |
| 69 | + </tr> | |
| 70 | + <tr> | |
| 71 | + <th style={{ textAlign: "start" }}>Name</th> | |
| 72 | + <td>:</td> | |
| 73 | + <td style={{ textAlign: "start" }}>{invoiceDetails?.title}</td> | |
| 74 | + </tr> | |
| 75 | + <tr> | |
| 76 | + <th style={{ textAlign: "start" }}>Status</th> | |
| 77 | + <td>:</td> | |
| 78 | + <td style={{ textAlign: "start" }}>{invoiceDetails?.status}</td> | |
| 79 | + </tr> | |
| 80 | + </table> | |
| 81 | + </div> | |
| 82 | + </div> | |
| 83 | + <div className="address-section"> | |
| 84 | + <div className="address"> | |
| 85 | + <strong>Billing Address</strong><br /> | |
| 86 | + <p style={{ width: "250px", wordBreak: "break-all" }}> | |
| 87 | + {invoiceDetails?.billingStreet ? `${invoiceDetails?.billingStreet},${invoiceDetails?.billingCity},` : ""} | |
| 88 | + </p> | |
| 89 | + <p style={{ width: "250px", wordBreak: "break-all" }}> | |
| 90 | + {invoiceDetails?.billingState ? `${invoiceDetails?.billingState},` : ""} | |
| 91 | + </p> | |
| 92 | + <p style={{ width: "250px", wordBreak: "break-all" }}> | |
| 93 | + {`${invoiceDetails?.billingCountry} - ${invoiceDetails?.billingPostalCode}`} | |
| 94 | + </p> | |
| 95 | + </div> | |
| 96 | + <div className="address"> | |
| 97 | + <strong>Shipping Address</strong><br /> | |
| 98 | + <p style={{ width: "250px", wordBreak: "break-all" }}> | |
| 99 | + {invoiceDetails?.shippingStreet ? `${invoiceDetails?.shippingStreet},${invoiceDetails?.shippingCity},` : ""} | |
| 100 | + </p> | |
| 101 | + <p style={{ width: "250px", wordBreak: "break-all" }}> | |
| 102 | + {invoiceDetails?.shippingState ? `${invoiceDetails?.shippingState},` : ""} | |
| 103 | + </p> | |
| 104 | + <p style={{ width: "250px", wordBreak: "break-all" }}> | |
| 105 | + {`${invoiceDetails?.shippingCountry} - ${invoiceDetails?.shippingPostalCode}`} | |
| 106 | + </p> | |
| 107 | + </div> | |
| 108 | + </div> | |
| 109 | + <table className="invoice-table"> | |
| 110 | + <thead> | |
| 111 | + <tr> | |
| 112 | + <th style={{ width: "50px" }}>S No</th> | |
| 113 | + <th style={{ width: "100px" }}>ITEM</th> | |
| 114 | + <th style={{ width: "50px" }}>QTY</th> | |
| 115 | + <th style={{ width: "50px" }}>RATE</th> | |
| 116 | + <th style={{ width: "50px" }}>DISCOUNT</th> | |
| 117 | + <th style={{ width: "50px" }}>AMOUNT</th> | |
| 118 | + </tr> | |
| 119 | + </thead> | |
| 120 | + <tbody> | |
| 121 | + { | |
| 122 | + invoiceDetails?.items?.length > 0 && invoiceDetails?.items?.map((item, index) => ( | |
| 123 | + <tr key={index}> | |
| 124 | + <td>{item?.id}</td> | |
| 125 | + <td>{item?.productName}</td> | |
| 126 | + <td>{item?.qty}</td> | |
| 127 | + <td>{item?.rate}</td> | |
| 128 | + <td>{`${item?.discountType === "percent" ? `${item?.discount}%` : item?.discountType === "flatAmount" ? `${invoiceDetails?.currency}${item?.discount}` : item?.discountType === "none" ? 0 : ""}`}</td> | |
| 129 | + <td>{item?.amount}</td> | |
| 130 | + </tr> | |
| 131 | + )) | |
| 132 | + } | |
| 133 | + </tbody> | |
| 134 | + </table> | |
| 135 | + <div className="totals"> | |
| 136 | + <table> | |
| 137 | + <tr> | |
| 138 | + <th style={{ textAlign: "start" }}>Total</th> | |
| 139 | + <td>:</td> | |
| 140 | + <td style={{ textAlign: "start" }}>{`${invoiceDetails?.currency} ${invoiceDetails?.total || 0}`}</td> | |
| 141 | + </tr> | |
| 142 | + <tr> | |
| 143 | + <th style={{ textAlign: "start" }}>Discount </th> | |
| 144 | + <td>:</td> | |
| 145 | + <td style={{ textAlign: "start" }}>{`${invoiceDetails?.currency} ${invoiceDetails?.discount || 0}`}</td> | |
| 146 | + </tr> | |
| 147 | + <tr> | |
| 148 | + <th style={{ textAlign: "start" }}>Subtotal</th> | |
| 149 | + <td>:</td> | |
| 150 | + <td style={{ textAlign: "start" }}>{`${invoiceDetails?.currency} ${invoiceDetails?.subtotal || 0}`}</td> | |
| 151 | + </tr> | |
| 152 | + <tr> | |
| 153 | + <th style={{ textAlign: "start" }}>Shipping </th> | |
| 154 | + <td>:</td> | |
| 155 | + <td style={{ textAlign: "start" }}>{`${invoiceDetails?.currency} ${invoiceDetails?.shipping || 0}`}</td> | |
| 156 | + </tr> | |
| 157 | + <tr> | |
| 158 | + <th style={{ textAlign: "start" }}>Shipping Tax</th> | |
| 159 | + <td>:</td> | |
| 160 | + <td style={{ textAlign: "start" }}>{`${invoiceDetails?.currency} ${invoiceDetails?.shippingTax || 0}`}</td> | |
| 161 | + </tr> | |
| 162 | + <tr> | |
| 163 | + <th style={{ textAlign: "start" }}>Tax ({invoiceDetails?.ptax}%)</th> | |
| 164 | + <td>:</td> | |
| 165 | + <td style={{ textAlign: "start" }}>{`${invoiceDetails?.currency} ${invoiceDetails?.tax || 0}`}</td> | |
| 166 | + </tr> | |
| 167 | + <tr> | |
| 168 | + <th style={{ textAlign: "start" }}>Grand Total</th> | |
| 169 | + <td>:</td> | |
| 170 | + <td style={{ textAlign: "start" }}>{`${invoiceDetails?.currency} ${invoiceDetails?.grandTotal || 0}`}</td> | |
| 171 | + | |
| 172 | + </tr> | |
| 173 | + </table> | |
| 174 | + | |
| 175 | + </div> | |
| 176 | + <div className="footer"> | |
| 177 | + <div>{invoiceDetails?.description}</div> | |
| 178 | + </div> | |
| 179 | + </div> | |
| 180 | + </div> | |
| 181 | + : | |
| 182 | + <div style={{ display: "flex", justifyContent: "center" }}> | |
| 183 | + <Spinner /> | |
| 184 | + </div> | |
| 185 | + } | |
| 186 | + </DrawerBody> | |
| 187 | + <DrawerFooter> | |
| 188 | + <Button sx={{ textTransform: "capitalize" }} size="sm" disabled={isLoading} variant="brand" type="submit" onClick={generatePDF}> | |
| 189 | + {isLoading ? <Spinner /> : 'Download'} | |
| 190 | + </Button> | |
| 191 | + </DrawerFooter> | |
| 192 | + </DrawerContent> | |
| 193 | + </Drawer> | |
| 194 | + ) | |
| 195 | +} | |
| 196 | + | |
| 197 | +export default Preview |
+89-0Client/src/views/admin/lead/Add.js
| @@ -0,0 +1,89 @@ | ||
| 1 | +import { CloseIcon } from '@chakra-ui/icons'; | |
| 2 | +import { Button, Drawer, DrawerBody, DrawerContent, DrawerFooter, DrawerHeader, DrawerOverlay, IconButton } from '@chakra-ui/react'; | |
| 3 | +import Spinner from 'components/spinner/Spinner'; | |
| 4 | +import { useFormik } from 'formik'; | |
| 5 | +import { useState } from 'react'; | |
| 6 | +import { postApi } from 'services/api'; | |
| 7 | +import { generateValidationSchema } from 'utils'; | |
| 8 | +import CustomForm from 'utils/customForm'; | |
| 9 | +import * as yup from 'yup' | |
| 10 | + | |
| 11 | +const Add = (props) => { | |
| 12 | + const [isLoding, setIsLoding] = useState(false) | |
| 13 | + | |
| 14 | + | |
| 15 | + const initialFieldValues = Object.fromEntries( | |
| 16 | + (props?.leadData?.fields || []).map(field => [field?.name, '']) | |
| 17 | + ); | |
| 18 | + const initialValues = { | |
| 19 | + ...initialFieldValues, | |
| 20 | + createBy: JSON.parse(localStorage.getItem('user'))._id | |
| 21 | + }; | |
| 22 | + | |
| 23 | + const formik = useFormik({ | |
| 24 | + initialValues: initialValues, | |
| 25 | + validationSchema: yup.object().shape(generateValidationSchema(props?.leadData?.fields)), | |
| 26 | + onSubmit: (values, { resetForm }) => { | |
| 27 | + AddData(); | |
| 28 | + }, | |
| 29 | + }); | |
| 30 | + | |
| 31 | + const { errors, touched, values, handleBlur, handleChange, handleSubmit, setFieldValue, } = formik | |
| 32 | + | |
| 33 | + const AddData = async () => { | |
| 34 | + try { | |
| 35 | + setIsLoding(true) | |
| 36 | + let response = await postApi('api/form/add', { ...values, moduleId: props?.leadData?._id }) | |
| 37 | + if (response.status === 200) { | |
| 38 | + props.onClose(); | |
| 39 | + formik.resetForm(); | |
| 40 | + props.setAction((pre) => !pre) | |
| 41 | + } | |
| 42 | + } catch (e) { | |
| 43 | + console.log(e); | |
| 44 | + } | |
| 45 | + finally { | |
| 46 | + setIsLoding(false) | |
| 47 | + } | |
| 48 | + }; | |
| 49 | + | |
| 50 | + const handleCancel = () => { | |
| 51 | + formik.resetForm(); | |
| 52 | + props.onClose() | |
| 53 | + } | |
| 54 | + | |
| 55 | + return ( | |
| 56 | + <div> | |
| 57 | + <Drawer isOpen={props.isOpen} size={props.size}> | |
| 58 | + <DrawerOverlay /> | |
| 59 | + <DrawerContent> | |
| 60 | + <DrawerHeader alignItems={"center"} justifyContent='space-between' display='flex' > | |
| 61 | + Add Lead | |
| 62 | + <IconButton onClick={props.onClose} icon={<CloseIcon />} /> | |
| 63 | + </DrawerHeader> | |
| 64 | + <DrawerBody> | |
| 65 | + <CustomForm moduleData={props.leadData} values={values} setFieldValue={setFieldValue} handleChange={handleChange} handleBlur={handleBlur} errors={errors} touched={touched} /> | |
| 66 | + </DrawerBody> | |
| 67 | + <DrawerFooter> | |
| 68 | + <Button sx={{ textTransform: "capitalize" }} size="sm" disabled={isLoding ? true : false} variant="brand" type="submit" onClick={handleSubmit} > | |
| 69 | + {isLoding ? <Spinner /> : 'Save'} | |
| 70 | + </Button> | |
| 71 | + <Button | |
| 72 | + variant="outline" | |
| 73 | + colorScheme='red' size="sm" | |
| 74 | + sx={{ | |
| 75 | + marginLeft: 2, | |
| 76 | + textTransform: "capitalize", | |
| 77 | + }} | |
| 78 | + onClick={handleCancel} | |
| 79 | + > | |
| 80 | + Close | |
| 81 | + </Button> | |
| 82 | + </DrawerFooter> | |
| 83 | + </DrawerContent> | |
| 84 | + </Drawer> | |
| 85 | + </div> | |
| 86 | + ) | |
| 87 | +} | |
| 88 | + | |
| 89 | +export default Add |
+132-0Client/src/views/admin/lead/Edit.js
| @@ -0,0 +1,132 @@ | ||
| 1 | +import { CloseIcon } from '@chakra-ui/icons'; | |
| 2 | +import { Button, Drawer, DrawerBody, DrawerContent, DrawerFooter, DrawerHeader, DrawerOverlay, Flex, IconButton } from '@chakra-ui/react'; | |
| 3 | +import Spinner from 'components/spinner/Spinner'; | |
| 4 | +import { useFormik } from 'formik'; | |
| 5 | +import { useEffect, useState } from 'react'; | |
| 6 | +import { useParams } from 'react-router-dom'; | |
| 7 | +import { putApi } from 'services/api'; | |
| 8 | +import { getApi } from 'services/api'; | |
| 9 | +import { generateValidationSchema } from '../../../utils'; | |
| 10 | +import CustomForm from '../../../utils/customForm'; | |
| 11 | +import * as yup from 'yup' | |
| 12 | + | |
| 13 | +const Edit = (props) => { | |
| 14 | + const { data } = props; | |
| 15 | + const [isLoding, setIsLoding] = useState(false); | |
| 16 | + const initialFieldValues = Object.fromEntries( | |
| 17 | + (props?.leadData?.fields || []).map(field => [field?.name, '']) | |
| 18 | + ); | |
| 19 | + | |
| 20 | + const [initialValues, setInitialValues] = useState({ | |
| 21 | + ...initialFieldValues, | |
| 22 | + createBy: JSON.parse(localStorage.getItem('user'))._id | |
| 23 | + }) | |
| 24 | + const param = useParams() | |
| 25 | + | |
| 26 | + const formik = useFormik({ | |
| 27 | + initialValues: initialValues, | |
| 28 | + enableReinitialize: true, | |
| 29 | + validationSchema: yup.object().shape(generateValidationSchema(props?.leadData?.fields)), | |
| 30 | + onSubmit: (values, { resetForm }) => { | |
| 31 | + EditData(); | |
| 32 | + }, | |
| 33 | + }); | |
| 34 | + | |
| 35 | + const { errors, touched, values, handleBlur, handleChange, handleSubmit, setFieldValue, } = formik | |
| 36 | + | |
| 37 | + const EditData = async () => { | |
| 38 | + try { | |
| 39 | + setIsLoding(true) | |
| 40 | + let response = await putApi(`api/form/edit/${props?.selectedId || param.id}`, { ...values, moduleId: props.moduleId }) | |
| 41 | + if (response.status === 200) { | |
| 42 | + props.onClose(); | |
| 43 | + props.setAction((pre) => !pre) | |
| 44 | + } | |
| 45 | + } catch (e) { | |
| 46 | + console.log(e); | |
| 47 | + } | |
| 48 | + finally { | |
| 49 | + setIsLoding(false) | |
| 50 | + } | |
| 51 | + }; | |
| 52 | + | |
| 53 | + const handleClose = () => { | |
| 54 | + props.onClose(false) | |
| 55 | + props.setSelectedId && props?.setSelectedId() | |
| 56 | + formik.resetForm(); | |
| 57 | + } | |
| 58 | + | |
| 59 | + let response | |
| 60 | + const fetchData = async () => { | |
| 61 | + if (data) { | |
| 62 | + setInitialValues((prev) => ({ ...prev, ...data })) | |
| 63 | + } else if (props?.selectedId) { | |
| 64 | + // } else if (props?.selectedId || param.id) { | |
| 65 | + try { | |
| 66 | + setIsLoding(true) | |
| 67 | + response = await getApi('api/lead/view/', props?.selectedId) | |
| 68 | + let editData = response?.data?.lead; | |
| 69 | + setInitialValues((prev) => ({ ...prev, ...editData })); | |
| 70 | + } catch (e) { | |
| 71 | + console.error(e) | |
| 72 | + } finally { | |
| 73 | + setIsLoding(false) | |
| 74 | + } | |
| 75 | + } | |
| 76 | + } | |
| 77 | + | |
| 78 | + useEffect(() => { | |
| 79 | + fetchData() | |
| 80 | + }, [props?.selectedId, data]) | |
| 81 | + | |
| 82 | + return ( | |
| 83 | + <div> | |
| 84 | + <Drawer isOpen={props.isOpen} size={props.size}> | |
| 85 | + <DrawerOverlay /> | |
| 86 | + <DrawerContent> | |
| 87 | + <DrawerHeader alignItems={"center"} justifyContent='space-between' display='flex' > | |
| 88 | + Edit Lead | |
| 89 | + <IconButton onClick={handleClose} icon={<CloseIcon />} /> | |
| 90 | + </DrawerHeader> | |
| 91 | + <DrawerBody> | |
| 92 | + | |
| 93 | + {isLoding ? | |
| 94 | + <Flex justifyContent={'center'} alignItems={'center'} width="100%" > | |
| 95 | + <Spinner /> | |
| 96 | + </Flex> | |
| 97 | + : | |
| 98 | + <CustomForm moduleData={props.leadData} values={values} setFieldValue={setFieldValue} handleChange={handleChange} handleBlur={handleBlur} errors={errors} touched={touched} /> | |
| 99 | + | |
| 100 | + } | |
| 101 | + </DrawerBody> | |
| 102 | + | |
| 103 | + <DrawerFooter> | |
| 104 | + <Button | |
| 105 | + sx={{ textTransform: "capitalize" }} | |
| 106 | + variant="brand" size="sm" | |
| 107 | + type="submit" | |
| 108 | + disabled={isLoding ? true : false} | |
| 109 | + onClick={handleSubmit} | |
| 110 | + > | |
| 111 | + {isLoding ? <Spinner /> : 'Update'} | |
| 112 | + </Button> | |
| 113 | + <Button | |
| 114 | + variant="outline" | |
| 115 | + colorScheme='red' size="sm" | |
| 116 | + sx={{ | |
| 117 | + marginLeft: 2, | |
| 118 | + textTransform: "capitalize", | |
| 119 | + }} | |
| 120 | + onClick={handleClose} | |
| 121 | + > | |
| 122 | + Close | |
| 123 | + </Button> | |
| 124 | + </DrawerFooter> | |
| 125 | + | |
| 126 | + </DrawerContent> | |
| 127 | + </Drawer> | |
| 128 | + </div> | |
| 129 | + ) | |
| 130 | +} | |
| 131 | + | |
| 132 | +export default Edit |
+496-0Client/src/views/admin/lead/View.js
| @@ -0,0 +1,496 @@ | ||
| 1 | +import { AddIcon, ChevronDownIcon, DeleteIcon, EditIcon } from "@chakra-ui/icons"; | |
| 2 | +import { | |
| 3 | + Button, Flex, Grid, GridItem, Heading, Menu, MenuButton, MenuDivider, MenuItem, MenuList, | |
| 4 | + Tab, | |
| 5 | + TabList, | |
| 6 | + TabPanel, | |
| 7 | + TabPanels, | |
| 8 | + Tabs, | |
| 9 | + Text, | |
| 10 | + VStack, | |
| 11 | + useColorModeValue, | |
| 12 | + useDisclosure, | |
| 13 | +} from "@chakra-ui/react"; | |
| 14 | +import FolderTreeView from 'components/FolderTreeView/folderTreeView'; | |
| 15 | +import Card from "components/card/Card"; | |
| 16 | +import { HSeparator } from "components/separator/Separator"; | |
| 17 | +import Spinner from "components/spinner/Spinner"; | |
| 18 | +import { constant } from "constant"; | |
| 19 | +import { useEffect, useState } from "react"; | |
| 20 | +import { IoIosArrowBack } from "react-icons/io"; | |
| 21 | +import { Link, useNavigate, useParams, useLocation } from "react-router-dom"; | |
| 22 | +import { toast } from "react-toastify"; | |
| 23 | +import { getApi } from "services/api"; | |
| 24 | +import PhoneCall from "../contact/components/phonCall"; | |
| 25 | +import AddEmailHistory from "../emailHistory/components/AddEmail"; | |
| 26 | +import AddMeeting from "../meeting/components/Addmeeting"; | |
| 27 | +import AddPhoneCall from "../phoneCall/components/AddPhoneCall"; | |
| 28 | +import Add from "./Add"; | |
| 29 | +import Edit from "./Edit"; | |
| 30 | +import { HasAccess } from "../../../redux/accessUtils"; | |
| 31 | +import DataNotFound from "components/notFoundData"; | |
| 32 | +import CustomView from "utils/customView"; | |
| 33 | +import AddDocumentModal from "utils/addDocumentModal"; | |
| 34 | +import CommonDeleteModel from "components/commonDeleteModel"; | |
| 35 | +import { deleteApi } from "services/api"; | |
| 36 | +import CommonCheckTable from "components/reactTable/checktable"; | |
| 37 | +import moment from 'moment'; | |
| 38 | +import AddEdit from '../task/components/AddEdit' | |
| 39 | +import { useDispatch, useSelector } from "react-redux"; | |
| 40 | +import { fetchLeadCustomFiled } from "../../../redux/slices/leadCustomFiledSlice"; | |
| 41 | +import { FaFilePdf } from "react-icons/fa"; | |
| 42 | +import html2pdf from "html2pdf.js"; | |
| 43 | + | |
| 44 | +const View = () => { | |
| 45 | + const param = useParams() | |
| 46 | + const user = JSON.parse(localStorage.getItem("user")); | |
| 47 | + | |
| 48 | + const textColor = useColorModeValue("gray.500", "white"); | |
| 49 | + | |
| 50 | + const [data, setData] = useState() | |
| 51 | + const [allData, setAllData] = useState([]) | |
| 52 | + const { isOpen, onOpen, onClose } = useDisclosure() | |
| 53 | + const [edit, setEdit] = useState(false); | |
| 54 | + const [deleteModel, setDelete] = useState(false); | |
| 55 | + const [isLoding, setIsLoding] = useState(false) | |
| 56 | + const [addMeeting, setMeeting] = useState(false); | |
| 57 | + const [showEmail, setShowEmail] = useState(false); | |
| 58 | + const [showCall, setShowCall] = useState(false); | |
| 59 | + const [showTasks, setShowTasks] = useState(false); | |
| 60 | + const [showMeetings, setShowMeetings] = useState(false); | |
| 61 | + const [addDocument, setAddDocument] = useState(false); | |
| 62 | + const [action, setAction] = useState(false) | |
| 63 | + // const [leadData, setLeadData] = useState([]) | |
| 64 | + const [selectedTab, setSelectedTab] = useState(0); | |
| 65 | + const [taskModel, setTaskModel] = useState(false); | |
| 66 | + | |
| 67 | + const navigate = useNavigate(); | |
| 68 | + const dispatch = useDispatch(); | |
| 69 | + const size = "lg"; | |
| 70 | + | |
| 71 | + const [addEmailHistory, setAddEmailHistory] = useState(false); | |
| 72 | + const [addPhoneCall, setAddPhoneCall] = useState(false); | |
| 73 | + | |
| 74 | + const leadData = useSelector((state) => state?.leadCustomFiled?.data.data) | |
| 75 | + | |
| 76 | + const [permission, taskPermission, meetingPermission, callAccess, emailAccess, taskAccess, meetingAccess] = HasAccess(['Leads', 'Tasks', 'Meetings', 'Calls', 'Emails', 'Tasks', 'Meetings']); | |
| 77 | + | |
| 78 | + | |
| 79 | + const columnsDataColumns = [ | |
| 80 | + { Header: "sender", accessor: "senderName", }, | |
| 81 | + { | |
| 82 | + Header: "recipient", accessor: "createByName", cell: (cell) => ( | |
| 83 | + <Link to={`/Email/${cell?.row?.original?._id}`}> | |
| 84 | + <Text | |
| 85 | + me="10px" | |
| 86 | + sx={{ '&:hover': { color: 'blue.500', textDecoration: 'underline' } }} | |
| 87 | + color='brand.600' | |
| 88 | + fontSize="sm" | |
| 89 | + fontWeight="700" | |
| 90 | + > | |
| 91 | + {cell?.value || ' - '} | |
| 92 | + </Text></Link>) | |
| 93 | + }, | |
| 94 | + { | |
| 95 | + Header: "time stamp", accessor: "timestamp", | |
| 96 | + cell: (cell) => ( | |
| 97 | + <div className="selectOpt"> | |
| 98 | + <Text color={textColor} fontSize='sm' fontWeight='700'> | |
| 99 | + {moment(cell?.value).fromNow()} | |
| 100 | + </Text> | |
| 101 | + </div> | |
| 102 | + ) | |
| 103 | + }, | |
| 104 | + { | |
| 105 | + Header: "Created", accessor: "createBy", | |
| 106 | + cell: (cell) => ( | |
| 107 | + <div className="selectOpt"> | |
| 108 | + <Text color={textColor} fontSize='sm' fontWeight='700'> | |
| 109 | + {moment(cell?.row?.values.timestamp).format('h:mma (DD/MM)')} | |
| 110 | + </Text> | |
| 111 | + </div> | |
| 112 | + ) | |
| 113 | + }, | |
| 114 | + ]; | |
| 115 | + const callColumns = [ | |
| 116 | + { Header: "sender", accessor: "senderName", }, | |
| 117 | + { | |
| 118 | + Header: "recipient", accessor: "createByName", cell: (cell) => ( | |
| 119 | + <Link to={`/phone-call/${cell?.row?.original?._id}`}> | |
| 120 | + <Text | |
| 121 | + me="10px" | |
| 122 | + sx={{ '&:hover': { color: 'blue.500', textDecoration: 'underline' } }} | |
| 123 | + color='brand.600' | |
| 124 | + fontSize="sm" | |
| 125 | + fontWeight="700" | |
| 126 | + > | |
| 127 | + {cell?.value || ' - '} | |
| 128 | + </Text></Link>) | |
| 129 | + }, | |
| 130 | + { | |
| 131 | + Header: "time stamp", accessor: "timestamp", | |
| 132 | + cell: (cell) => ( | |
| 133 | + <div className="selectOpt"> | |
| 134 | + <Text color={textColor} fontSize='sm' fontWeight='700'> | |
| 135 | + {moment(cell?.value).fromNow()} | |
| 136 | + </Text> | |
| 137 | + </div> | |
| 138 | + ) | |
| 139 | + }, | |
| 140 | + { | |
| 141 | + Header: "Created", accessor: "createBy", | |
| 142 | + cell: (cell) => ( | |
| 143 | + <div className="selectOpt"> | |
| 144 | + <Text color={textColor} fontSize='sm' fontWeight='700'> | |
| 145 | + {moment(cell?.row?.values.timestamp).format('h:mma (DD/MM)')} | |
| 146 | + </Text> | |
| 147 | + </div> | |
| 148 | + ) | |
| 149 | + }, | |
| 150 | + ]; | |
| 151 | + const MeetingColumns = [ | |
| 152 | + { | |
| 153 | + Header: 'Agenda', accessor: 'agenda', cell: (cell) => ( | |
| 154 | + <Link to={`/metting/${cell?.row?.original?._id}`}> | |
| 155 | + <Text | |
| 156 | + me="10px" | |
| 157 | + sx={{ '&:hover': { color: 'blue.500', textDecoration: 'underline' } }} | |
| 158 | + color='brand.600' | |
| 159 | + fontSize="sm" | |
| 160 | + fontWeight="700" | |
| 161 | + > | |
| 162 | + {cell?.value || ' - '} | |
| 163 | + </Text></Link>) | |
| 164 | + }, | |
| 165 | + { Header: "date Time", accessor: "dateTime", }, | |
| 166 | + { | |
| 167 | + Header: "times tamp", accessor: "timestamp", | |
| 168 | + cell: (cell) => ( | |
| 169 | + <div className="selectOpt"> | |
| 170 | + <Text color={textColor} fontSize='sm' fontWeight='700'> | |
| 171 | + {moment(cell?.value).fromNow()} | |
| 172 | + </Text> | |
| 173 | + </div> | |
| 174 | + ) | |
| 175 | + }, | |
| 176 | + { Header: "create By", accessor: "createdByName", }, | |
| 177 | + ]; | |
| 178 | + const taskColumns = [ | |
| 179 | + { | |
| 180 | + Header: 'Title', accessor: 'title', type: 'text', formikType: '', cell: (cell) => ( | |
| 181 | + <div className="selectOpt"> | |
| 182 | + <Text | |
| 183 | + onClick={() => navigate(`/view/${cell?.row?.original._id}`)} | |
| 184 | + me="10px" | |
| 185 | + sx={{ '&:hover': { color: 'blue.500', textDecoration: 'underline' }, cursor: 'pointer' }} | |
| 186 | + color='brand.600' | |
| 187 | + fontSize="sm" | |
| 188 | + fontWeight="700" | |
| 189 | + > | |
| 190 | + {cell?.value} | |
| 191 | + </Text> | |
| 192 | + </div> | |
| 193 | + ) | |
| 194 | + }, | |
| 195 | + { Header: "Category", accessor: "category", }, | |
| 196 | + { Header: "Assign To", accessor: "assignToName", }, | |
| 197 | + { Header: "Start Date", accessor: "start", }, | |
| 198 | + { Header: "End Date", accessor: "end", }, | |
| 199 | + ]; | |
| 200 | + | |
| 201 | + | |
| 202 | + const handleTabChange = (index) => { | |
| 203 | + setSelectedTab(index); | |
| 204 | + }; | |
| 205 | + const generatePDF = () => { | |
| 206 | + const element = document.getElementById("reports"); | |
| 207 | + if (element) { | |
| 208 | + element.style.display = 'block'; | |
| 209 | + element.style.width = '100%'; // Adjust width for mobile | |
| 210 | + element.style.height = 'auto'; | |
| 211 | + // setTimeout(() => { | |
| 212 | + html2pdf() | |
| 213 | + .from(element) | |
| 214 | + .set({ | |
| 215 | + margin: [0, 0, 0, 0], | |
| 216 | + filename: `Lead_Details_${moment().format("DD-MM-YYYY")}.pdf`, | |
| 217 | + image: { type: "jpeg", quality: 0.98 }, | |
| 218 | + html2canvas: { scale: 2, useCORS: true, allowTaint: true }, | |
| 219 | + jsPDF: { unit: "in", format: "letter", orientation: "portrait" }, | |
| 220 | + }) | |
| 221 | + .save().then(() => { | |
| 222 | + element.style.display = ''; | |
| 223 | + }) | |
| 224 | + // }, 500); | |
| 225 | + } else { | |
| 226 | + console.error("Element with ID 'reports' not found."); | |
| 227 | + } | |
| 228 | + }; | |
| 229 | + | |
| 230 | + const download = async (data) => { | |
| 231 | + if (data) { | |
| 232 | + let result = await getApi(`api/document/download/`, data) | |
| 233 | + if (result && result.status === 200) { | |
| 234 | + window.open(`${constant.baseUrl}api/document/download/${data}`) | |
| 235 | + toast.success('file Download successful') | |
| 236 | + } else if (result && result.response.status === 404) { | |
| 237 | + toast.error('file Not Found') | |
| 238 | + } | |
| 239 | + } | |
| 240 | + } | |
| 241 | + | |
| 242 | + const fetchData = async (i) => { | |
| 243 | + setIsLoding(true) | |
| 244 | + let response = await getApi('api/lead/view/', param.id) | |
| 245 | + setData(response.data?.lead); | |
| 246 | + setAllData(response.data); | |
| 247 | + setIsLoding(false) | |
| 248 | + setSelectedTab(i) | |
| 249 | + } | |
| 250 | + | |
| 251 | + const handleDeleteLead = async (id) => { | |
| 252 | + try { | |
| 253 | + setIsLoding(true) | |
| 254 | + let response = await deleteApi('api/lead/delete/', id) | |
| 255 | + if (response.status === 200) { | |
| 256 | + setDelete(false) | |
| 257 | + setAction((pre) => !pre) | |
| 258 | + navigate('/lead') | |
| 259 | + } | |
| 260 | + } catch (error) { | |
| 261 | + console.log(error) | |
| 262 | + } | |
| 263 | + finally { | |
| 264 | + setIsLoding(false) | |
| 265 | + } | |
| 266 | + } | |
| 267 | + | |
| 268 | + useEffect(() => { | |
| 269 | + fetchData() | |
| 270 | + dispatch(fetchLeadCustomFiled()); | |
| 271 | + }, [action]) | |
| 272 | + | |
| 273 | + | |
| 274 | + function toCamelCase(text) { | |
| 275 | + return text?.replace(/([a-z])([A-Z])/g, '$1 $2'); | |
| 276 | + } | |
| 277 | + | |
| 278 | + const firstValue = Object?.values(param)[0]; | |
| 279 | + const splitValue = firstValue?.split('/') | |
| 280 | + | |
| 281 | + return ( | |
| 282 | + <> | |
| 283 | + | |
| 284 | + {isLoding ? | |
| 285 | + <Flex justifyContent={'center'} alignItems={'center'} width="100%" > | |
| 286 | + <Spinner /> | |
| 287 | + </Flex> : | |
| 288 | + <> | |
| 289 | + <Heading size="lg" mt={0} m={3}> | |
| 290 | + {data?.leadName || ""} | |
| 291 | + </Heading> | |
| 292 | + <Tabs onChange={handleTabChange} index={selectedTab}> | |
| 293 | + <Grid templateColumns={'repeat(12, 1fr)'} mb={3} gap={1}> | |
| 294 | + <GridItem colSpan={{ base: 12, md: 6 }}> | |
| 295 | + <TabList sx={{ | |
| 296 | + border: "none", | |
| 297 | + '& button:focus': { boxShadow: 'none', }, | |
| 298 | + '& button': { | |
| 299 | + margin: { sm: "0 3px", md: "0 5px" }, padding: { sm: "5px", md: "8px" }, border: '2px solid #8080803d', borderTopLeftRadius: "10px", borderTopRightRadius: "10px", borderBottom: 0, fontSize: { sm: "12px", md: "16px" } | |
| 300 | + }, | |
| 301 | + '& button[aria-selected="true"]': { | |
| 302 | + border: "2px solid brand.200", borderBottom: 0, zIndex: '0' | |
| 303 | + }, | |
| 304 | + }} > | |
| 305 | + <Tab>Information</Tab> | |
| 306 | + {(emailAccess?.view || callAccess?.view || taskAccess?.view || meetingAccess?.view) && <Tab> Communication</Tab>} | |
| 307 | + <Tab>Document</Tab> | |
| 308 | + </TabList> | |
| 309 | + | |
| 310 | + </GridItem> | |
| 311 | + <GridItem colSpan={{ base: 12, md: 6 }} mt={{ sm: "3px", md: "5px" }} > | |
| 312 | + <Flex justifyContent={"right"}> | |
| 313 | + <Menu> | |
| 314 | + {(user.role === 'superAdmin' || (permission?.create || permission?.update || permission?.delete)) && <MenuButton size="sm" variant="outline" colorScheme='blackAlpha' mr={2.5} as={Button} rightIcon={<ChevronDownIcon />}> | |
| 315 | + Actions | |
| 316 | + </MenuButton>} | |
| 317 | + <MenuDivider /> | |
| 318 | + <MenuList minWidth={2}> | |
| 319 | + {(user.role === 'superAdmin' || permission?.create) && <MenuItem color={'blue'} onClick={() => onOpen()} alignItems={"start"} icon={<AddIcon />} >Add</MenuItem>} | |
| 320 | + {(user.role === 'superAdmin' || permission?.update) && <MenuItem onClick={() => setEdit(true)} alignItems={"start"} icon={<EditIcon />}>Edit</MenuItem>} | |
| 321 | + <MenuItem onClick={generatePDF} alignItems={"start"} icon={<FaFilePdf />} display={"flex"} style={{ alignItems: "center" }}>Print as PDF</MenuItem > | |
| 322 | + {(user.role === 'superAdmin' || permission?.delete) && <> | |
| 323 | + <MenuDivider /> | |
| 324 | + <MenuItem alignItems={"start"} color={'red'} onClick={() => setDelete(true)} icon={<DeleteIcon />}>Delete</MenuItem> | |
| 325 | + </>} | |
| 326 | + </MenuList> | |
| 327 | + </Menu> | |
| 328 | + <Link to="/lead"> | |
| 329 | + <Button leftIcon={<IoIosArrowBack />} size='sm' variant="brand"> | |
| 330 | + Back | |
| 331 | + </Button> | |
| 332 | + </Link> | |
| 333 | + </Flex> | |
| 334 | + </GridItem> | |
| 335 | + </Grid> | |
| 336 | + | |
| 337 | + <TabPanels> | |
| 338 | + <TabPanel pt={4} p={0}> | |
| 339 | + <CustomView data={leadData?.[0]} fieldData={data} toCamelCase={toCamelCase} moduleId={leadData?.[0]?._id} fetchData={fetchData} id="reports" /> | |
| 340 | + </TabPanel> | |
| 341 | + <TabPanel pt={4} p={0}> | |
| 342 | + <GridItem colSpan={{ base: 4 }} > | |
| 343 | + <Grid overflow={'hidden'} templateColumns={{ base: "1fr" }} gap={4}> | |
| 344 | + <Grid templateColumns={'repeat(12, 1fr)'} gap={4}> | |
| 345 | + {emailAccess?.view && <GridItem colSpan={{ base: 12, md: 6 }}> | |
| 346 | + <Card > | |
| 347 | + <CommonCheckTable | |
| 348 | + title={"Email"} | |
| 349 | + isLoding={isLoding} | |
| 350 | + columnData={columnsDataColumns ?? []} | |
| 351 | + // dataColumn={columnsDataColumns ?? []} | |
| 352 | + allData={showEmail ? allData.Email : allData?.Email?.length > 0 ? [allData.Email[0]] : []} | |
| 353 | + tableData={showEmail ? allData.Email : allData?.Email?.length > 0 ? [allData.Email[0]] : []} | |
| 354 | + AdvanceSearch={false} | |
| 355 | + dataLength={allData?.Email?.length} | |
| 356 | + tableCustomFields={[]} | |
| 357 | + checkBox={false} | |
| 358 | + deleteMany={true} | |
| 359 | + ManageGrid={false} | |
| 360 | + onOpen={() => setAddEmailHistory(true)} | |
| 361 | + access={emailAccess} | |
| 362 | + /> | |
| 363 | + {allData?.Email?.length > 1 && | |
| 364 | + <div style={{ display: "flex", justifyContent: "end" }}> | |
| 365 | + <Button size="sm" colorScheme="brand" variant="outline" display="flex" justifyContant="end" onClick={() => showEmail ? setShowEmail(false) : setShowEmail(true)}>{showEmail ? "Show less" : "Show more"}</Button> | |
| 366 | + </div>} | |
| 367 | + </Card> | |
| 368 | + </GridItem>} | |
| 369 | + {callAccess?.view && <GridItem colSpan={{ base: 12, md: 6 }}> | |
| 370 | + <Card> | |
| 371 | + <CommonCheckTable | |
| 372 | + title={"Call"} | |
| 373 | + isLoding={isLoding} | |
| 374 | + columnData={callColumns ?? []} | |
| 375 | + // dataColumn={callColumns ?? []} | |
| 376 | + allData={showCall ? allData?.phoneCall : allData?.phoneCall?.length > 0 ? [allData?.phoneCall[0]] : []} | |
| 377 | + tableData={showCall ? allData?.phoneCall : allData?.phoneCall?.length > 0 ? [allData?.phoneCall[0]] : []} | |
| 378 | + AdvanceSearch={false} | |
| 379 | + dataLength={allData?.phoneCall?.length} | |
| 380 | + tableCustomFields={[]} | |
| 381 | + checkBox={false} | |
| 382 | + deleteMany={true} | |
| 383 | + ManageGrid={false} | |
| 384 | + onOpen={() => setAddPhoneCall(true)} | |
| 385 | + access={callAccess} | |
| 386 | + /> | |
| 387 | + {allData?.phoneCall?.length > 1 && <div style={{ display: "flex", justifyContent: "end" }}> | |
| 388 | + <Button size="sm" colorScheme="brand" variant="outline" display="flex" justifyContant="end" onClick={() => showCall ? setShowCall(false) : setShowCall(true)}>{showCall ? "Show less" : "Show more"}</Button> | |
| 389 | + </div>} | |
| 390 | + </Card> | |
| 391 | + </GridItem>} | |
| 392 | + {taskAccess?.view && <GridItem colSpan={{ base: 12, md: 6 }}> | |
| 393 | + <Card > | |
| 394 | + <CommonCheckTable | |
| 395 | + title={"Task"} | |
| 396 | + isLoding={isLoding} | |
| 397 | + columnData={taskColumns ?? []} | |
| 398 | + // dataColumn={taskColumns ?? []} | |
| 399 | + allData={showTasks ? allData?.task : allData?.task?.length > 0 ? [allData?.task[0]] : []} | |
| 400 | + tableData={showTasks ? allData?.task : allData?.task?.length > 0 ? [allData?.task[0]] : []} | |
| 401 | + dataLength={allData?.task?.length} | |
| 402 | + AdvanceSearch={false} | |
| 403 | + tableCustomFields={[]} | |
| 404 | + checkBox={false} | |
| 405 | + deleteMany={true} | |
| 406 | + ManageGrid={false} | |
| 407 | + onOpen={() => setTaskModel(true)} | |
| 408 | + access={taskAccess} | |
| 409 | + /> | |
| 410 | + { | |
| 411 | + allData?.task?.length > 1 && <div style={{ display: "flex", justifyContent: "end" }}> | |
| 412 | + <Button size="sm" colorScheme="brand" variant="outline" display="flex" justifyContant="end" onClick={() => showTasks ? setShowTasks(false) : setShowTasks(true)}>{showTasks ? "Show less" : "Show more"}</Button> | |
| 413 | + </div>} | |
| 414 | + </Card> | |
| 415 | + </GridItem>} | |
| 416 | + | |
| 417 | + {meetingAccess?.view && <GridItem colSpan={{ base: 12, md: 6 }}> | |
| 418 | + <Card overflow={'scroll'}> | |
| 419 | + <CommonCheckTable | |
| 420 | + title={"Meeting"} | |
| 421 | + isLoding={isLoding} | |
| 422 | + columnData={MeetingColumns ?? []} | |
| 423 | + // dataColumn={MeetingColumns ?? []} | |
| 424 | + allData={showMeetings ? allData?.meeting : allData?.meeting?.length > 0 ? [allData?.meeting[0]] : []} | |
| 425 | + tableData={showMeetings ? allData?.meeting : allData?.meeting?.length > 0 ? [allData?.meeting[0]] : []} | |
| 426 | + AdvanceSearch={false} | |
| 427 | + dataLength={allData?.meeting?.length} tableCustomFields={[]} | |
| 428 | + checkBox={false} | |
| 429 | + deleteMany={true} | |
| 430 | + ManageGrid={false} | |
| 431 | + onOpen={() => setMeeting(true)} | |
| 432 | + access={meetingAccess} | |
| 433 | + /> | |
| 434 | + | |
| 435 | + {allData?.meeting?.length > 1 && <div style={{ display: "flex", justifyContent: "end" }}> | |
| 436 | + <Button colorScheme="brand" size='sm' variant="outline" display="flex" justifyContant="end" onClick={() => showMeetings ? setShowMeetings(false) : setShowMeetings(true)}>{showMeetings ? "Show less" : "Show more"}</Button> | |
| 437 | + </div>} | |
| 438 | + </Card> | |
| 439 | + </GridItem>} | |
| 440 | + </Grid> | |
| 441 | + </Grid> | |
| 442 | + </GridItem> | |
| 443 | + </TabPanel> | |
| 444 | + <TabPanel pt={4} p={0}> | |
| 445 | + <GridItem colSpan={{ base: 4 }} > | |
| 446 | + <Card minH={'50vh'} > | |
| 447 | + <Flex alignItems={'center'} justifyContent={'space-between'} mb='2'> | |
| 448 | + <Heading size="md" mb={3}> | |
| 449 | + Documents | |
| 450 | + </Heading> | |
| 451 | + <Button leftIcon={<AddIcon />} size='sm' variant='brand' onClick={() => setAddDocument(true)}>Add Document</Button> | |
| 452 | + </Flex> | |
| 453 | + <HSeparator /> | |
| 454 | + <VStack mt={4} alignItems="flex-start"> | |
| 455 | + {allData?.Document?.length > 0 ? allData?.Document?.map((item) => ( | |
| 456 | + <FolderTreeView name={item.folderName} item={item}> | |
| 457 | + {item?.files?.map((file) => ( | |
| 458 | + <FolderTreeView download={download} data={file} name={file.fileName} isFile from="lead" /> | |
| 459 | + ))} | |
| 460 | + </FolderTreeView> | |
| 461 | + )) : <Text textAlign={'center'} width="100%" color={textColor} fontSize="sm" fontWeight="700"> | |
| 462 | + <DataNotFound /> | |
| 463 | + </Text>} | |
| 464 | + </VStack> | |
| 465 | + </Card> | |
| 466 | + </GridItem> | |
| 467 | + </TabPanel> | |
| 468 | + | |
| 469 | + </TabPanels> | |
| 470 | + </Tabs> | |
| 471 | + {(user.role === 'superAdmin' || (permission?.update || permission?.delete)) && <Card mt={3}> | |
| 472 | + <Grid templateColumns="repeat(6, 1fr)" gap={1}> | |
| 473 | + <GridItem colStart={6} > | |
| 474 | + <Flex justifyContent={"right"}> | |
| 475 | + {(user.role === 'superAdmin' || permission?.update) ? <Button size='sm' onClick={() => setEdit(true)} leftIcon={<EditIcon />} mr={2.5} variant="outline" colorScheme="green">Edit</Button> : ''} | |
| 476 | + {(user.role === 'superAdmin' || permission?.delete) ? <Button size='sm' style={{ background: 'red.800' }} onClick={() => setDelete(true)} leftIcon={<DeleteIcon />} colorScheme="red" >Delete</Button> : ''} | |
| 477 | + </Flex> | |
| 478 | + </GridItem> | |
| 479 | + </Grid> | |
| 480 | + </Card> | |
| 481 | + } | |
| 482 | + </> | |
| 483 | + } | |
| 484 | + {isOpen && <Add isOpen={isOpen} size={size} onClose={onClose} leadData={leadData?.[0]} setAction={setAction} />} | |
| 485 | + <Edit isOpen={edit} size={size} onClose={setEdit} leadData={leadData?.[0]} setAction={setAction} moduleId={leadData?.[0]?._id} data={data} /> | |
| 486 | + <AddMeeting fetchData={fetchData} isOpen={addMeeting} leadContect={splitValue?.[0]} onClose={setMeeting} from="contact" id={param.id} setAction={setAction} view={true} /> | |
| 487 | + <AddEdit isOpen={taskModel} fetchData={fetchData} leadContect={splitValue?.[0]} onClose={setTaskModel} id={param.id} userAction={'add'} view={true} /> | |
| 488 | + <AddPhoneCall viewData={allData} fetchData={fetchData} isOpen={addPhoneCall} onClose={setAddPhoneCall} setAction={setAction} data={data?.contact} id={param.id} lead={true} LData={data} /> | |
| 489 | + <AddEmailHistory lead={true} leadEmail={allData?.lead?.leadEmail} fetchData={fetchData} isOpen={addEmailHistory} onClose={setAddEmailHistory} id={param.id} /> | |
| 490 | + <AddDocumentModal addDocument={addDocument} setAddDocument={setAddDocument} linkId={param.id} from="lead" setAction={setAction} fetchData={fetchData} /> | |
| 491 | + <CommonDeleteModel isOpen={deleteModel} onClose={() => setDelete(false)} type='Lead' handleDeleteData={handleDeleteLead} ids={param.id} /> | |
| 492 | + </> | |
| 493 | + ); | |
| 494 | +}; | |
| 495 | + | |
| 496 | +export default View; |
+73-0Client/src/views/admin/lead/components/ImportModal.js
| @@ -0,0 +1,73 @@ | ||
| 1 | +import { Button, Grid, GridItem, Modal, ModalBody, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalOverlay, Text } from '@chakra-ui/react' | |
| 2 | +import Spinner from 'components/spinner/Spinner' | |
| 3 | +import { useFormik } from 'formik' | |
| 4 | +import { useState } from 'react' | |
| 5 | +import { useNavigate } from 'react-router-dom' | |
| 6 | +import CommonFileUpload from 'components/commonFileUpload' | |
| 7 | + | |
| 8 | +const ImportModal = (props) => { | |
| 9 | + const { onClose, isOpen, text, customFields } = props | |
| 10 | + const [isLoding, setIsLoding] = useState(false) | |
| 11 | + const navigate = useNavigate(); | |
| 12 | + | |
| 13 | + const initialValues = { | |
| 14 | + lead: '' | |
| 15 | + } | |
| 16 | + | |
| 17 | + const formik = useFormik({ | |
| 18 | + initialValues: initialValues, | |
| 19 | + onSubmit: (values, { resetForm }) => { | |
| 20 | + AddData() | |
| 21 | + resetForm(); | |
| 22 | + }, | |
| 23 | + }); | |
| 24 | + const { errors, touched, values, handleBlur, handleChange, handleSubmit, setFieldValue, resetForm } = formik | |
| 25 | + | |
| 26 | + const AddData = async () => { | |
| 27 | + try { | |
| 28 | + setIsLoding(true) | |
| 29 | + resetForm() | |
| 30 | + | |
| 31 | + if (values.lead) { | |
| 32 | + onClose(); | |
| 33 | + navigate('/leadImport', { state: { fileData: values.lead, customFields: customFields } }); | |
| 34 | + } | |
| 35 | + } catch (e) { | |
| 36 | + console.log(e); | |
| 37 | + } | |
| 38 | + finally { | |
| 39 | + setIsLoding(false) | |
| 40 | + } | |
| 41 | + }; | |
| 42 | + | |
| 43 | + return ( | |
| 44 | + <Modal onClose={onClose} isOpen={isOpen} isCentered> | |
| 45 | + <ModalOverlay /> | |
| 46 | + <ModalContent> | |
| 47 | + <ModalHeader>Import Leads</ModalHeader> | |
| 48 | + <ModalCloseButton /> | |
| 49 | + <ModalBody> | |
| 50 | + <Grid templateColumns="repeat(12, 1fr)" gap={3}> | |
| 51 | + <GridItem colSpan={{ base: 12 }}> | |
| 52 | + <CommonFileUpload count={values.lead.length} onFileSelect={(file) => setFieldValue('lead', file)} text={text} /> | |
| 53 | + <Text mb='10px' color={'red'}> {errors.lead && touched.lead && <>Please Select {text}</>}</Text> | |
| 54 | + </GridItem> | |
| 55 | + </Grid> | |
| 56 | + </ModalBody> | |
| 57 | + <ModalFooter> | |
| 58 | + <Button variant='brand' size="sm" onClick={handleSubmit} disabled={isLoding ? true : false} >{isLoding ? <Spinner /> : 'Save'}</Button> | |
| 59 | + <Button variant="outline" | |
| 60 | + colorScheme="red" sx={{ | |
| 61 | + marginLeft: 2, | |
| 62 | + textTransform: "capitalize", | |
| 63 | + }} size="sm" onClick={() => { | |
| 64 | + onClose() | |
| 65 | + formik.resetForm() | |
| 66 | + }}>Close</Button> | |
| 67 | + </ModalFooter> | |
| 68 | + </ModalContent> | |
| 69 | + </Modal> | |
| 70 | + ) | |
| 71 | +} | |
| 72 | + | |
| 73 | +export default ImportModal |
+232-0Client/src/views/admin/lead/components/LeadImport.js
| @@ -0,0 +1,232 @@ | ||
| 1 | +import React, { useState, useEffect } from 'react' | |
| 2 | +import { useLocation, useNavigate } from 'react-router-dom'; | |
| 3 | +import Papa from 'papaparse'; | |
| 4 | +import { | |
| 5 | + Flex, | |
| 6 | + useColorModeValue, | |
| 7 | + Select, | |
| 8 | + Button, | |
| 9 | + Text, | |
| 10 | + Grid, | |
| 11 | + GridItem | |
| 12 | +} from "@chakra-ui/react"; | |
| 13 | +import { useFormik } from "formik"; | |
| 14 | +import { postApi } from 'services/api'; | |
| 15 | +import { toast } from 'react-toastify'; | |
| 16 | +import moment from 'moment'; | |
| 17 | +import ExcelJS from 'exceljs'; | |
| 18 | +import Card from 'components/card/Card'; | |
| 19 | + | |
| 20 | +function LeadImport() { | |
| 21 | + | |
| 22 | + const location = useLocation(); | |
| 23 | + const { fileData, customFields } = location.state || {}; | |
| 24 | + const [importedFileFields, setImportedFileFields] = useState([]); | |
| 25 | + const [importedFileData, setImportedFileData] = useState([]); | |
| 26 | + const [isLoding, setIsLoding] = useState(false); | |
| 27 | + const navigate = useNavigate(); | |
| 28 | + const [filterLead, setFilterLead] = useState([]); | |
| 29 | + | |
| 30 | + const columns = [ | |
| 31 | + { Header: 'Fields In Crm', accessor: 'crmFields' }, | |
| 32 | + { Header: 'Fields In File', accessor: 'fileFields' }, | |
| 33 | + ]; | |
| 34 | + | |
| 35 | + const initialFieldValues = Object.fromEntries( | |
| 36 | + (customFields || []).map(field => [field?.name, '']) | |
| 37 | + ); | |
| 38 | + const initialValues = { | |
| 39 | + ...initialFieldValues | |
| 40 | + }; | |
| 41 | + | |
| 42 | + const fieldsInCrm = [ | |
| 43 | + ...customFields?.map((field) => ({ Header: field?.label, accessor: field?.name, type: field?.type, formikType: field?.validations?.find(obj => obj.hasOwnProperty('formikType')) })) | |
| 44 | + ]; | |
| 45 | + | |
| 46 | + const formik = useFormik({ | |
| 47 | + initialValues: initialValues, | |
| 48 | + onSubmit: (values, { resetForm }) => { | |
| 49 | + | |
| 50 | + const leadsData = importedFileData?.map((item, ind) => { | |
| 51 | + const lead = { | |
| 52 | + createdDate: new Date(), | |
| 53 | + deleted: item[values.deleted || "deleted"] || false, | |
| 54 | + createBy: JSON.parse(localStorage.getItem('user'))._id, | |
| 55 | + }; | |
| 56 | + | |
| 57 | + fieldsInCrm?.forEach(field => { | |
| 58 | + const selectedField = values[field?.accessor]; | |
| 59 | + const fieldValue = item[selectedField] || ''; | |
| 60 | + | |
| 61 | + if (field?.type?.toLowerCase() === "date") { | |
| 62 | + lead[field?.accessor] = moment(fieldValue).isValid() ? fieldValue : ''; | |
| 63 | + } else if (field?.type?.toLowerCase() === "number" && ['positive', 'negative'].includes(field?.formikType?.toLowerCase())) { | |
| 64 | + lead[field?.accessor] = parseFloat(fieldValue) || ''; | |
| 65 | + } else if (field?.type?.toLowerCase() === "number") { | |
| 66 | + lead[field?.accessor] = parseInt(fieldValue, 10) || ''; | |
| 67 | + } else { | |
| 68 | + lead[field?.accessor] = fieldValue; | |
| 69 | + } | |
| 70 | + }); | |
| 71 | + | |
| 72 | + return lead; | |
| 73 | + }); | |
| 74 | + | |
| 75 | + AddData(leadsData); | |
| 76 | + } | |
| 77 | + }) | |
| 78 | + | |
| 79 | + const { errors, touched, values, handleBlur, handleChange, handleSubmit, setFieldValue, resetForm } = formik | |
| 80 | + | |
| 81 | + const AddData = async (leads) => { | |
| 82 | + try { | |
| 83 | + setIsLoding(true); | |
| 84 | + let response = await postApi('api/lead/addMany', leads) | |
| 85 | + if (response.status === 200) { | |
| 86 | + toast.success(`Leads imported successfully`) | |
| 87 | + resetForm(); | |
| 88 | + navigate('/lead'); | |
| 89 | + } | |
| 90 | + } catch (e) { | |
| 91 | + console.error(e); | |
| 92 | + toast.error(`Leads import failed`) | |
| 93 | + resetForm(); | |
| 94 | + navigate('/lead'); | |
| 95 | + } | |
| 96 | + finally { | |
| 97 | + setIsLoding(false) | |
| 98 | + } | |
| 99 | + }; | |
| 100 | + | |
| 101 | + const parseFileData = async (file) => { | |
| 102 | + const reader = new FileReader(); | |
| 103 | + const extension = file.name.split('.').pop().toLowerCase(); | |
| 104 | + | |
| 105 | + reader.onload = async ({ target }) => { | |
| 106 | + | |
| 107 | + if (extension === 'csv') { | |
| 108 | + const csv = Papa.parse(target.result, { | |
| 109 | + header: true, | |
| 110 | + }); | |
| 111 | + const parsedData = csv?.data; | |
| 112 | + | |
| 113 | + if (parsedData && parsedData.length > 0) { | |
| 114 | + setImportedFileData(parsedData); | |
| 115 | + const fileHeadingFields = Object.keys(parsedData[0]); | |
| 116 | + setImportedFileFields(fileHeadingFields); | |
| 117 | + } else { | |
| 118 | + toast.error("Empty or invalid CSV file"); | |
| 119 | + navigate("/lead"); | |
| 120 | + } | |
| 121 | + | |
| 122 | + } else if (extension === 'xlsx') { | |
| 123 | + const data = new Uint8Array(target.result); | |
| 124 | + const workbook = new ExcelJS.Workbook(); | |
| 125 | + | |
| 126 | + await workbook.xlsx.load(data); | |
| 127 | + | |
| 128 | + const worksheet = workbook.getWorksheet(1); | |
| 129 | + const jsonData = []; | |
| 130 | + | |
| 131 | + // Iterate over rows and cells | |
| 132 | + worksheet.eachRow({ includeEmpty: true }, (row, rowNumber) => { | |
| 133 | + const rowData = {}; | |
| 134 | + row.eachCell({ includeEmpty: true }, (cell, colNumber) => { | |
| 135 | + rowData[worksheet.getCell(1, colNumber).value] = cell.value; | |
| 136 | + }); | |
| 137 | + jsonData.push(rowData); | |
| 138 | + }); | |
| 139 | + jsonData?.splice(0, 1); | |
| 140 | + setImportedFileData(jsonData); | |
| 141 | + | |
| 142 | + if (jsonData && jsonData.length > 0) { | |
| 143 | + const fileHeadingFields = Object.keys(jsonData[0]); | |
| 144 | + setImportedFileFields(fileHeadingFields); | |
| 145 | + } else { | |
| 146 | + toast.error("Empty or invalid XLSX file"); | |
| 147 | + navigate("/lead"); | |
| 148 | + } | |
| 149 | + } | |
| 150 | + }; | |
| 151 | + | |
| 152 | + if (extension === 'csv') { | |
| 153 | + reader.readAsText(file); | |
| 154 | + } else if (extension === 'xlsx') { | |
| 155 | + const blob = new Blob([file]); | |
| 156 | + reader.readAsArrayBuffer(blob); | |
| 157 | + } | |
| 158 | + }; | |
| 159 | + | |
| 160 | + useEffect(() => { | |
| 161 | + if (fileData && fileData.length > 0) { | |
| 162 | + const firstFile = fileData[0]; | |
| 163 | + parseFileData(firstFile); | |
| 164 | + } | |
| 165 | + }, [fileData]); | |
| 166 | + | |
| 167 | + useEffect(() => { | |
| 168 | + const filterLeadData = importedFileFields?.filter(field => { | |
| 169 | + const result = fieldsInCrm?.find(data => field === data?.accessor || field === data?.Header); | |
| 170 | + if (result) { | |
| 171 | + setFieldValue(result?.accessor, field); | |
| 172 | + return true; | |
| 173 | + } | |
| 174 | + return false; | |
| 175 | + }); | |
| 176 | + setFilterLead(filterLeadData); | |
| 177 | + }, [importedFileFields]); | |
| 178 | + | |
| 179 | + return ( | |
| 180 | + <> | |
| 181 | + <Card overflowY={"auto"} className="importTable"> | |
| 182 | + <Text color={"secondaryGray.900"} | |
| 183 | + fontSize="22px" | |
| 184 | + fontWeight="700" | |
| 185 | + mb='20px' | |
| 186 | + >Import Leads</Text> | |
| 187 | + <Grid templateColumns="repeat(12, 1fr)" mb={3} pb={2} gap={1} borderBottom={'1px solid #e2e8f0'}> | |
| 188 | + { | |
| 189 | + columns.map((column, index) => ( | |
| 190 | + <GridItem key={index} colSpan={{ base: 6 }} fontWeight={'600'} fontSize={{ sm: "14px", lg: "14px" }} color="secondaryGray.900" style={{ textTransform: "uppercase" }}> | |
| 191 | + {column.Header} | |
| 192 | + </GridItem> | |
| 193 | + )) | |
| 194 | + } | |
| 195 | + </Grid> | |
| 196 | + <Grid templateColumns="repeat(12, 1fr)" mb={3} gap={1} overflowY={'auto'}> | |
| 197 | + { | |
| 198 | + fieldsInCrm?.map((item, index) => ( | |
| 199 | + <> | |
| 200 | + <GridItem colSpan={{ base: 6 }} key={item.id} mt='10px'> | |
| 201 | + {item.Header} | |
| 202 | + </GridItem> | |
| 203 | + <GridItem colSpan={{ base: 4 }}> | |
| 204 | + <Select | |
| 205 | + variant="flushed" | |
| 206 | + fontWeight='500' | |
| 207 | + isSearchable | |
| 208 | + value={values[item.accessor]} | |
| 209 | + name={item.accessor} | |
| 210 | + onChange={handleChange} | |
| 211 | + > | |
| 212 | + <option value=''> {filterLead ? filterLead.find((data) => (item.Header === data || item.accessor === data) && data) ? filterLead.find((data) => (item.Header === data || item.accessor === data) && data) : 'Select Field In File' : 'Select Field In File'}</option> | |
| 213 | + { | |
| 214 | + importedFileFields?.map(field => ( | |
| 215 | + <option value={field} key={field}>{field}</option> | |
| 216 | + )) | |
| 217 | + } | |
| 218 | + </Select> | |
| 219 | + </GridItem> | |
| 220 | + </> | |
| 221 | + )) | |
| 222 | + } | |
| 223 | + </Grid> | |
| 224 | + <Flex Flex justifyContent={'end'} mt='5' > | |
| 225 | + <Button size="sm" onClick={() => handleSubmit()} variant="brand">Save</Button> | |
| 226 | + </Flex> | |
| 227 | + </Card> | |
| 228 | + </> | |
| 229 | + ) | |
| 230 | +} | |
| 231 | + | |
| 232 | +export default LeadImport |
+448-0Client/src/views/admin/lead/index.js
| @@ -0,0 +1,448 @@ | ||
| 1 | +import { useEffect, useState } from "react"; | |
| 2 | +import { useNavigate, useLocation } from "react-router-dom"; | |
| 3 | +import { HasAccess } from "../../../redux/accessUtils"; | |
| 4 | +import { | |
| 5 | + Grid, | |
| 6 | + GridItem, | |
| 7 | + Text, | |
| 8 | + Menu, | |
| 9 | + MenuButton, | |
| 10 | + MenuItem, | |
| 11 | + MenuList, | |
| 12 | + useDisclosure, | |
| 13 | + Select, | |
| 14 | +} from "@chakra-ui/react"; | |
| 15 | +import { | |
| 16 | + DeleteIcon, | |
| 17 | + ViewIcon, | |
| 18 | + EditIcon, | |
| 19 | + EmailIcon, | |
| 20 | + PhoneIcon, | |
| 21 | +} from "@chakra-ui/icons"; | |
| 22 | +import { CiMenuKebab } from "react-icons/ci"; | |
| 23 | +import { getApi } from "services/api"; | |
| 24 | +import CommonCheckTable from "../../../components/reactTable/checktable"; | |
| 25 | +import Add from "./Add"; | |
| 26 | +import Edit from "./Edit"; | |
| 27 | +import AddEmailHistory from "views/admin/emailHistory/components/AddEmail"; | |
| 28 | +import AddPhoneCall from "views/admin/phoneCall/components/AddPhoneCall"; | |
| 29 | +import ImportModal from "./components/ImportModal"; | |
| 30 | +import { putApi } from "services/api"; | |
| 31 | +import CommonDeleteModel from "components/commonDeleteModel"; | |
| 32 | +import { deleteManyApi } from "services/api"; | |
| 33 | +import { | |
| 34 | + getSearchData, | |
| 35 | + setGetTagValues, | |
| 36 | +} from "../../../redux/slices/advanceSearchSlice"; | |
| 37 | +import { useDispatch, useSelector } from "react-redux"; | |
| 38 | +import { fetchLeadData } from "../../../redux/slices/leadSlice"; | |
| 39 | +import { fetchLeadCustomFiled } from "../../../redux/slices/leadCustomFiledSlice"; | |
| 40 | +import { toast } from "react-toastify"; | |
| 41 | + | |
| 42 | +const Index = () => { | |
| 43 | + const title = "Leads"; | |
| 44 | + const size = "lg"; | |
| 45 | + const user = JSON.parse(localStorage.getItem("user")); | |
| 46 | + const navigate = useNavigate(); | |
| 47 | + const location = useLocation(); | |
| 48 | + const dispatch = useDispatch(); | |
| 49 | + | |
| 50 | + const [permission, emailAccess, callAccess] = HasAccess([ | |
| 51 | + "Leads", | |
| 52 | + "Emails", | |
| 53 | + "Calls", | |
| 54 | + ]); | |
| 55 | + const [isLoding, setIsLoding] = useState(false); | |
| 56 | + const [searchDisplay, setSearchDisplay] = useState(false); | |
| 57 | + // const [data, setData] = useState([]); | |
| 58 | + const [tableColumns, setTableColumns] = useState([]); | |
| 59 | + const [columns, setColumns] = useState([]); | |
| 60 | + const [dataColumn, setDataColumn] = useState([]); | |
| 61 | + const [selectedColumns, setSelectedColumns] = useState([]); | |
| 62 | + const [action, setAction] = useState(false); | |
| 63 | + const { isOpen, onOpen, onClose } = useDisclosure(); | |
| 64 | + const [leadData, setLeadData] = useState([]); | |
| 65 | + const [edit, setEdit] = useState(false); | |
| 66 | + const [deleteModel, setDelete] = useState(false); | |
| 67 | + const [addPhoneCall, setAddPhoneCall] = useState(false); | |
| 68 | + const [callSelectedId, setCallSelectedId] = useState(); | |
| 69 | + const [addEmailHistory, setAddEmailHistory] = useState(false); | |
| 70 | + const [selectedId, setSelectedId] = useState(); | |
| 71 | + const [selectedValues, setSelectedValues] = useState([]); | |
| 72 | + const [isImport, setIsImport] = useState(false); | |
| 73 | + const [emailRec, setEmailRec] = useState(""); | |
| 74 | + const [phoneRec, setPhoneRec] = useState({}); | |
| 75 | + | |
| 76 | + const data = useSelector((state) => state?.leadData?.data); | |
| 77 | + const searchedDataOut = useSelector( | |
| 78 | + (state) => state?.advanceSearchData?.searchResult | |
| 79 | + ); | |
| 80 | + const payload = { | |
| 81 | + leadStatus: location?.state, | |
| 82 | + }; | |
| 83 | + | |
| 84 | + const fetchData = async () => { | |
| 85 | + setIsLoding(true); | |
| 86 | + let result = await getApi( | |
| 87 | + user.role === "superAdmin" | |
| 88 | + ? "api/lead/" | |
| 89 | + : `api/lead/?createBy=${user._id}` | |
| 90 | + ); | |
| 91 | + // setData(result?.data); | |
| 92 | + setIsLoding(false); | |
| 93 | + }; | |
| 94 | + | |
| 95 | + const handleOpenEmail = (id, dataLead) => { | |
| 96 | + if (id) { | |
| 97 | + setEmailRec(dataLead?.leadEmail); | |
| 98 | + setAddEmailHistory(true); | |
| 99 | + } | |
| 100 | + }; | |
| 101 | + | |
| 102 | + const setStatusData = async (cell, e) => { | |
| 103 | + try { | |
| 104 | + setIsLoding(true); | |
| 105 | + let response = await putApi( | |
| 106 | + `api/lead/changeStatus/${cell.original._id}`, | |
| 107 | + { leadStatus: e.target.value } | |
| 108 | + ); | |
| 109 | + if (response.status === 200) { | |
| 110 | + setAction((pre) => !pre); | |
| 111 | + } | |
| 112 | + } catch (e) { | |
| 113 | + console.log(e); | |
| 114 | + } finally { | |
| 115 | + setIsLoding(false); | |
| 116 | + } | |
| 117 | + }; | |
| 118 | + | |
| 119 | + const changeStatus = (cell) => { | |
| 120 | + switch (cell.original.leadStatus) { | |
| 121 | + case "pending": | |
| 122 | + return "pending"; | |
| 123 | + case "active": | |
| 124 | + return "completed"; | |
| 125 | + case "sold": | |
| 126 | + return "onHold"; | |
| 127 | + default: | |
| 128 | + return "completed"; | |
| 129 | + } | |
| 130 | + }; | |
| 131 | + | |
| 132 | + const fetchCustomDataFields = async () => { | |
| 133 | + setIsLoding(true); | |
| 134 | + | |
| 135 | + try { | |
| 136 | + const result = await dispatch(fetchLeadCustomFiled()); | |
| 137 | + if (result.payload.status === 200) { | |
| 138 | + setLeadData(result?.payload?.data); | |
| 139 | + } else { | |
| 140 | + toast.error("Failed to fetch data", "error"); | |
| 141 | + } | |
| 142 | + | |
| 143 | + const actionHeader = { | |
| 144 | + Header: "Action", | |
| 145 | + accessor: "action", | |
| 146 | + isSortable: false, | |
| 147 | + center: true, | |
| 148 | + cell: ({ row, i }) => ( | |
| 149 | + <Text fontSize="md" fontWeight="900" textAlign={"center"}> | |
| 150 | + <Menu isLazy> | |
| 151 | + <MenuButton> | |
| 152 | + <CiMenuKebab /> | |
| 153 | + </MenuButton> | |
| 154 | + <MenuList | |
| 155 | + minW={"fit-content"} | |
| 156 | + transform={"translate(1520px, 173px);"} | |
| 157 | + > | |
| 158 | + {permission?.update && ( | |
| 159 | + <MenuItem | |
| 160 | + py={2.5} | |
| 161 | + icon={<EditIcon fontSize={15} mb={1} />} | |
| 162 | + onClick={() => { | |
| 163 | + setEdit(true); | |
| 164 | + setSelectedId(row?.values?._id); | |
| 165 | + }} | |
| 166 | + > | |
| 167 | + Edit | |
| 168 | + </MenuItem> | |
| 169 | + )} | |
| 170 | + {callAccess?.create && ( | |
| 171 | + <MenuItem | |
| 172 | + py={2.5} | |
| 173 | + width={"165px"} | |
| 174 | + onClick={() => { | |
| 175 | + setPhoneRec(row?.original); | |
| 176 | + setAddPhoneCall(true); | |
| 177 | + setCallSelectedId(row?.values?._id); | |
| 178 | + }} | |
| 179 | + icon={<PhoneIcon fontSize={15} mb={1} />} | |
| 180 | + > | |
| 181 | + Create Call | |
| 182 | + </MenuItem> | |
| 183 | + )} | |
| 184 | + {emailAccess?.create && ( | |
| 185 | + <MenuItem | |
| 186 | + py={2.5} | |
| 187 | + width={"165px"} | |
| 188 | + onClick={() => { | |
| 189 | + handleOpenEmail(row?.values?._id, row?.original); | |
| 190 | + setSelectedId(row?.values?._id); | |
| 191 | + }} | |
| 192 | + icon={<EmailIcon fontSize={15} mb={1} />} | |
| 193 | + > | |
| 194 | + EmailSend{" "} | |
| 195 | + </MenuItem> | |
| 196 | + )} | |
| 197 | + {permission?.view && ( | |
| 198 | + <MenuItem | |
| 199 | + py={2.5} | |
| 200 | + color={"green"} | |
| 201 | + icon={<ViewIcon mb={1} fontSize={15} />} | |
| 202 | + onClick={() => { | |
| 203 | + navigate(`/leadView/${row?.values?._id}`, { | |
| 204 | + state: { leadList: data }, | |
| 205 | + }); | |
| 206 | + }} | |
| 207 | + > | |
| 208 | + View | |
| 209 | + </MenuItem> | |
| 210 | + )} | |
| 211 | + {permission?.delete && ( | |
| 212 | + <MenuItem | |
| 213 | + py={2.5} | |
| 214 | + color={"red"} | |
| 215 | + icon={<DeleteIcon fontSize={15} mb={1} />} | |
| 216 | + onClick={() => { | |
| 217 | + setDelete(true); | |
| 218 | + setSelectedValues([row?.values?._id]); | |
| 219 | + }} | |
| 220 | + > | |
| 221 | + Delete | |
| 222 | + </MenuItem> | |
| 223 | + )} | |
| 224 | + </MenuList> | |
| 225 | + </Menu> | |
| 226 | + </Text> | |
| 227 | + ), | |
| 228 | + }; | |
| 229 | + const tempTableColumns = [ | |
| 230 | + { Header: "#", accessor: "_id", isSortable: false, width: 10 }, | |
| 231 | + { | |
| 232 | + Header: "Status", | |
| 233 | + accessor: "leadStatus", | |
| 234 | + isSortable: true, | |
| 235 | + center: true, | |
| 236 | + cell: ({ row }) => ( | |
| 237 | + <div className="selectOpt"> | |
| 238 | + <Select | |
| 239 | + defaultValue={"active"} | |
| 240 | + className={changeStatus(row)} | |
| 241 | + onChange={(e) => setStatusData(row, e)} | |
| 242 | + height={7} | |
| 243 | + width={130} | |
| 244 | + value={row.original.leadStatus} | |
| 245 | + style={{ fontSize: "14px" }} | |
| 246 | + > | |
| 247 | + <option value="active">Active</option> | |
| 248 | + <option value="sold">Sold</option> | |
| 249 | + <option value="pending">Pending</option> | |
| 250 | + </Select> | |
| 251 | + </div> | |
| 252 | + ), | |
| 253 | + }, | |
| 254 | + ...(result?.payload?.data && result.payload.data.length > 0 | |
| 255 | + ? result.payload.data[0]?.fields | |
| 256 | + ?.filter((field) => field?.isTableField === true && field?.isView) | |
| 257 | + ?.map( | |
| 258 | + (field) => | |
| 259 | + field?.name !== "leadStatus" && { | |
| 260 | + Header: field?.label, | |
| 261 | + accessor: field?.name, | |
| 262 | + cell: (cell) => ( | |
| 263 | + <div className="selectOpt"> | |
| 264 | + <Text | |
| 265 | + onClick={() => { | |
| 266 | + navigate(`/leadView/${cell?.row?.original?._id}`); | |
| 267 | + }} | |
| 268 | + me="10px" | |
| 269 | + sx={{ '&:hover': { color: 'blue.500', textDecoration: 'underline' }, cursor: 'pointer' }} | |
| 270 | + color='brand.600' | |
| 271 | + fontSize="sm" | |
| 272 | + fontWeight="700" | |
| 273 | + > | |
| 274 | + {cell?.value || "-"} | |
| 275 | + </Text> | |
| 276 | + </div> | |
| 277 | + ), | |
| 278 | + } | |
| 279 | + ) || [] | |
| 280 | + : []), | |
| 281 | + ...(result?.payload?.data && result.payload.data.length > 0 | |
| 282 | + ? result.payload.data[0]?.fields?.filter((field) => field?.isTableField === true && !field?.isView && field?.name !== "leadStatus") | |
| 283 | + ?.map((field) => ({ | |
| 284 | + Header: field?.label, | |
| 285 | + accessor: field?.name, | |
| 286 | + }) | |
| 287 | + ) || [] | |
| 288 | + : []), | |
| 289 | + ...(permission?.update || permission?.view || permission?.delete | |
| 290 | + ? [actionHeader] | |
| 291 | + : []), | |
| 292 | + ]; | |
| 293 | + | |
| 294 | + setColumns(tempTableColumns); | |
| 295 | + setIsLoding(false); | |
| 296 | + } catch (error) { | |
| 297 | + console.error("Error fetching custom data fields:", error); | |
| 298 | + toast.error("Failed to fetch data ", "error"); | |
| 299 | + } | |
| 300 | + }; | |
| 301 | + | |
| 302 | + const handleDeleteLead = async (ids) => { | |
| 303 | + try { | |
| 304 | + setIsLoding(true); | |
| 305 | + let response = await deleteManyApi("api/lead/deleteMany", ids); | |
| 306 | + if (response.status === 200) { | |
| 307 | + setSelectedValues([]); | |
| 308 | + setDelete(false); | |
| 309 | + setAction((pre) => !pre); | |
| 310 | + } | |
| 311 | + } catch (error) { | |
| 312 | + console.log(error); | |
| 313 | + } finally { | |
| 314 | + setIsLoding(false); | |
| 315 | + } | |
| 316 | + }; | |
| 317 | + | |
| 318 | + useEffect(() => { | |
| 319 | + if (window.location.pathname === "/lead") { | |
| 320 | + dispatch(fetchLeadData()); | |
| 321 | + } | |
| 322 | + fetchCustomDataFields(); | |
| 323 | + }, [action]); | |
| 324 | + | |
| 325 | + useEffect(() => { | |
| 326 | + setDataColumn( | |
| 327 | + tableColumns?.filter((item) => | |
| 328 | + selectedColumns?.find((colum) => colum?.Header === item.Header) | |
| 329 | + ) | |
| 330 | + ); | |
| 331 | + }, [tableColumns, selectedColumns]); | |
| 332 | + | |
| 333 | + useEffect(() => { | |
| 334 | + if (location?.state) { | |
| 335 | + setSearchDisplay(true); | |
| 336 | + dispatch( | |
| 337 | + getSearchData({ values: payload, allData: data, type: "Leads" }) | |
| 338 | + ); | |
| 339 | + const getValue = [ | |
| 340 | + { | |
| 341 | + name: ["leadStatus"], | |
| 342 | + value: location?.state, | |
| 343 | + }, | |
| 344 | + ]; | |
| 345 | + dispatch(setGetTagValues(getValue.filter((item) => item.value))); | |
| 346 | + } | |
| 347 | + }, [data, location?.state]); | |
| 348 | + | |
| 349 | + return ( | |
| 350 | + <div> | |
| 351 | + <Grid templateColumns="repeat(6, 1fr)" mb={3} gap={4}> | |
| 352 | + {!isLoding && ( | |
| 353 | + <GridItem colSpan={6}> | |
| 354 | + <CommonCheckTable | |
| 355 | + title={title} | |
| 356 | + isLoding={isLoding} | |
| 357 | + searchDisplay={searchDisplay} | |
| 358 | + setSearchDisplay={setSearchDisplay} | |
| 359 | + columnData={columns ?? []} | |
| 360 | + // dataColumn={dataColumn ?? []} | |
| 361 | + allData={data ?? []} | |
| 362 | + tableData={searchDisplay ? searchedDataOut : data} | |
| 363 | + tableCustomFields={ | |
| 364 | + leadData?.[0]?.fields?.filter( | |
| 365 | + (field) => field?.isTableField === true | |
| 366 | + ) || [] | |
| 367 | + } | |
| 368 | + access={permission} | |
| 369 | + action={action} | |
| 370 | + setAction={setAction} | |
| 371 | + // selectedColumns={selectedColumns} | |
| 372 | + // setSelectedColumns={setSelectedColumns} | |
| 373 | + isOpen={isOpen} | |
| 374 | + onClose={onclose} | |
| 375 | + onOpen={onOpen} | |
| 376 | + selectedValues={selectedValues} | |
| 377 | + setSelectedValues={setSelectedValues} | |
| 378 | + setDelete={setDelete} | |
| 379 | + setIsImport={setIsImport} | |
| 380 | + /> | |
| 381 | + </GridItem> | |
| 382 | + )} | |
| 383 | + </Grid> | |
| 384 | + | |
| 385 | + {isOpen && ( | |
| 386 | + <Add | |
| 387 | + isOpen={isOpen} | |
| 388 | + size={size} | |
| 389 | + leadData={leadData[0]} | |
| 390 | + onClose={onClose} | |
| 391 | + setAction={setAction} | |
| 392 | + action={action} | |
| 393 | + /> | |
| 394 | + )} | |
| 395 | + {edit && ( | |
| 396 | + <Edit | |
| 397 | + isOpen={edit} | |
| 398 | + size={size} | |
| 399 | + leadData={leadData[0]} | |
| 400 | + selectedId={selectedId} | |
| 401 | + setSelectedId={setSelectedId} | |
| 402 | + onClose={setEdit} | |
| 403 | + setAction={setAction} | |
| 404 | + moduleId={leadData?.[0]?._id} | |
| 405 | + /> | |
| 406 | + )} | |
| 407 | + {deleteModel && ( | |
| 408 | + <CommonDeleteModel | |
| 409 | + isOpen={deleteModel} | |
| 410 | + onClose={() => setDelete(false)} | |
| 411 | + type="Leads" | |
| 412 | + handleDeleteData={handleDeleteLead} | |
| 413 | + ids={selectedValues} | |
| 414 | + /> | |
| 415 | + )} | |
| 416 | + {addEmailHistory && ( | |
| 417 | + <AddEmailHistory | |
| 418 | + fetchData={fetchData} | |
| 419 | + isOpen={addEmailHistory} | |
| 420 | + onClose={setAddEmailHistory} | |
| 421 | + lead={true} | |
| 422 | + id={selectedId} | |
| 423 | + leadEmail={emailRec} | |
| 424 | + /> | |
| 425 | + )} | |
| 426 | + {addPhoneCall && ( | |
| 427 | + <AddPhoneCall | |
| 428 | + fetchData={fetchData} | |
| 429 | + isOpen={addPhoneCall} | |
| 430 | + onClose={setAddPhoneCall} | |
| 431 | + lead={true} | |
| 432 | + id={callSelectedId} | |
| 433 | + LData={phoneRec} | |
| 434 | + /> | |
| 435 | + )} | |
| 436 | + {isImport && ( | |
| 437 | + <ImportModal | |
| 438 | + text="Lead file" | |
| 439 | + isOpen={isImport} | |
| 440 | + onClose={setIsImport} | |
| 441 | + customFields={leadData?.[0]?.fields || []} | |
| 442 | + /> | |
| 443 | + )} | |
| 444 | + </div> | |
| 445 | + ); | |
| 446 | +}; | |
| 447 | + | |
| 448 | +export default Index; |
+193-0Client/src/views/admin/meeting/View.js
| @@ -0,0 +1,193 @@ | ||
| 1 | +import { Box, Button, Flex, Grid, GridItem, Heading, Text } from "@chakra-ui/react"; | |
| 2 | +import Card from "components/card/Card"; | |
| 3 | +import { HSeparator } from "components/separator/Separator"; | |
| 4 | +import Spinner from "components/spinner/Spinner"; | |
| 5 | +import moment from "moment"; | |
| 6 | +import { useEffect, useState } from "react"; | |
| 7 | +import { IoIosArrowBack } from "react-icons/io"; | |
| 8 | +import { Link, useNavigate, useParams } from "react-router-dom"; | |
| 9 | +import { HasAccess } from "../../../redux/accessUtils"; | |
| 10 | +import { getApi } from "services/api"; | |
| 11 | +import { DeleteIcon } from "@chakra-ui/icons"; | |
| 12 | +import { deleteApi } from "services/api"; | |
| 13 | +import CommonDeleteModel from "components/commonDeleteModel"; | |
| 14 | +import { FaFilePdf } from "react-icons/fa"; | |
| 15 | +import html2pdf from "html2pdf.js"; | |
| 16 | +const View = () => { | |
| 17 | + | |
| 18 | + const param = useParams() | |
| 19 | + | |
| 20 | + const [data, setData] = useState() | |
| 21 | + const [deleteMany, setDeleteMany] = useState(false); | |
| 22 | + const user = JSON.parse(localStorage.getItem("user")) | |
| 23 | + const [isLoding, setIsLoding] = useState(false) | |
| 24 | + const [loading, setLoading] = useState(false) | |
| 25 | + const navigate = useNavigate() | |
| 26 | + const params = useParams(); | |
| 27 | + | |
| 28 | + | |
| 29 | + const fetchData = async () => { | |
| 30 | + setIsLoding(true) | |
| 31 | + let response = await getApi('api/meeting/view/', param.id) | |
| 32 | + setData(response?.data); | |
| 33 | + setIsLoding(false) | |
| 34 | + } | |
| 35 | + | |
| 36 | + useEffect(() => { | |
| 37 | + fetchData() | |
| 38 | + }, []) | |
| 39 | + | |
| 40 | + const generatePDF = () => { | |
| 41 | + setLoading(true) | |
| 42 | + const element = document.getElementById("reports"); | |
| 43 | + const hideBtn = document.getElementById("hide-btn"); | |
| 44 | + if (element) { | |
| 45 | + hideBtn.style.display = 'none'; | |
| 46 | + html2pdf() | |
| 47 | + .from(element) | |
| 48 | + .set({ | |
| 49 | + margin: [0, 0, 0, 0], | |
| 50 | + filename: `Meeting_Details_${moment().format("DD-MM-YYYY")}.pdf`, | |
| 51 | + image: { type: "jpeg", quality: 0.98 }, | |
| 52 | + html2canvas: { scale: 2, useCORS: true, allowTaint: true }, | |
| 53 | + jsPDF: { unit: "in", format: "letter", orientation: "portrait" }, | |
| 54 | + }) | |
| 55 | + .save().then(() => { | |
| 56 | + setLoading(false) | |
| 57 | + hideBtn.style.display = ''; | |
| 58 | + }) | |
| 59 | + // }, 500); | |
| 60 | + } else { | |
| 61 | + console.error("Element with ID 'reports' not found."); | |
| 62 | + setLoading(false) | |
| 63 | + } | |
| 64 | + }; | |
| 65 | + | |
| 66 | + const handleDeleteMeeting = async (ids) => { | |
| 67 | + try { | |
| 68 | + setIsLoding(true) | |
| 69 | + let response = await deleteApi('api/meeting/delete/', params.id) | |
| 70 | + if (response.status === 200) { | |
| 71 | + setDeleteMany(false) | |
| 72 | + navigate(-1) | |
| 73 | + } | |
| 74 | + } catch (error) { | |
| 75 | + console.log(error) | |
| 76 | + } | |
| 77 | + finally { | |
| 78 | + setIsLoding(false) | |
| 79 | + } | |
| 80 | + } | |
| 81 | + | |
| 82 | + const [permission, contactAccess, leadAccess] = HasAccess(['Meetings', 'Contacts', 'Leads']) | |
| 83 | + | |
| 84 | + return ( | |
| 85 | + <> | |
| 86 | + {isLoding ? | |
| 87 | + <Flex justifyContent={'center'} alignItems={'center'} width="100%" > | |
| 88 | + <Spinner /> | |
| 89 | + </Flex> : <> | |
| 90 | + | |
| 91 | + <Grid templateColumns="repeat(4, 1fr)" gap={3} id="reports"> | |
| 92 | + <GridItem colSpan={{ base: 4 }}> | |
| 93 | + <Heading size="lg" m={3}> | |
| 94 | + {data?.agenda || ""} | |
| 95 | + </Heading> | |
| 96 | + </GridItem> | |
| 97 | + <GridItem colSpan={{ base: 4 }}> | |
| 98 | + <Card> | |
| 99 | + <Grid gap={4}> | |
| 100 | + <GridItem colSpan={2}> | |
| 101 | + <Box> | |
| 102 | + <Flex justifyContent={"space-between"}> | |
| 103 | + <Heading size="md" mb={3}> | |
| 104 | + Meeting Details | |
| 105 | + </Heading> | |
| 106 | + <Box id="hide-btn"> | |
| 107 | + <Button leftIcon={<FaFilePdf />} size='sm' variant="brand" onClick={generatePDF} disabled={loading}> | |
| 108 | + {loading ? "Please Wait..." : "Print as PDF"} | |
| 109 | + </Button> | |
| 110 | + <Button leftIcon={<IoIosArrowBack />} size='sm' variant="brand" onClick={() => navigate(-1)} style={{ marginLeft: 10 }}> | |
| 111 | + Back | |
| 112 | + </Button> | |
| 113 | + </Box> | |
| 114 | + </Flex> | |
| 115 | + <HSeparator /> | |
| 116 | + </Box> | |
| 117 | + </GridItem> | |
| 118 | + <GridItem colSpan={{ base: 2, md: 1 }}> | |
| 119 | + <Text fontSize="sm" fontWeight="bold" color={'blackAlpha.900'}> Agenda </Text> | |
| 120 | + <Text>{data?.agenda ? data?.agenda : ' - '}</Text> | |
| 121 | + </GridItem> | |
| 122 | + <GridItem colSpan={{ base: 2, md: 1 }}> | |
| 123 | + <Text fontSize="sm" fontWeight="bold" color={'blackAlpha.900'}> Created By </Text> | |
| 124 | + <Text>{data?.createdByName ? data?.createdByName : ' - '}</Text> | |
| 125 | + </GridItem> | |
| 126 | + | |
| 127 | + <GridItem colSpan={{ base: 2, md: 1 }}> | |
| 128 | + <Text fontSize="sm" fontWeight="bold" color={'blackAlpha.900'}> DateTime </Text> | |
| 129 | + <Text> {data?.dateTime ? moment(data?.dateTime).format('DD-MM-YYYY h:mma ') : ' - '} [{data?.dateTime ? moment(data?.dateTime).toNow() : ' - '}]</Text> | |
| 130 | + </GridItem> | |
| 131 | + <GridItem colSpan={{ base: 2, md: 1 }}> | |
| 132 | + <Text fontSize="sm" fontWeight="bold" color={'blackAlpha.900'}> Timestamp </Text> | |
| 133 | + <Text> {data?.timestamp ? moment(data?.timestamp).format('DD-MM-YYYY h:mma ') : ' - '} [{data?.timestamp ? moment(data?.timestamp).toNow() : ' - '}]</Text> | |
| 134 | + </GridItem> | |
| 135 | + <GridItem colSpan={{ base: 2, md: 1 }}> | |
| 136 | + <Text fontSize="sm" fontWeight="bold" color={'blackAlpha.900'}> Location </Text> | |
| 137 | + <Text>{data?.location ? data?.location : ' - '}</Text> | |
| 138 | + </GridItem> | |
| 139 | + <GridItem colSpan={{ base: 2, md: 1 }}> | |
| 140 | + <Text fontSize="sm" fontWeight="bold" color={'blackAlpha.900'}> Notes </Text> | |
| 141 | + <Text>{data?.notes ? data?.notes : ' - '}</Text> | |
| 142 | + </GridItem> | |
| 143 | + <GridItem colSpan={{ base: 2, md: 1 }}> | |
| 144 | + <Text fontSize="sm" fontWeight="bold" color={'blackAlpha.900'}> Attendes </Text> | |
| 145 | + {data?.related === 'Contact' && contactAccess?.view ? data?.attendes && data?.attendes.map((item) => { | |
| 146 | + return ( | |
| 147 | + <Link to={`/contactView/${item._id}`}> | |
| 148 | + <Text color='brand.600' sx={{ '&:hover': { color: 'blue.500', textDecoration: 'underline' } }}>{item.firstName + ' ' + item.lastName}</Text> | |
| 149 | + </Link> | |
| 150 | + ) | |
| 151 | + }) : data?.related === 'Lead' && leadAccess?.view ? data?.attendesLead && data?.attendesLead.map((item) => { | |
| 152 | + return ( | |
| 153 | + <Link to={`/leadView/${item._id}`}> | |
| 154 | + <Text color='brand.600' sx={{ '&:hover': { color: 'blue.500', textDecoration: 'underline' } }}>{item.leadName}</Text> | |
| 155 | + </Link> | |
| 156 | + ) | |
| 157 | + }) : data?.related === 'contact' ? data?.attendes && data?.attendes.map((item) => { | |
| 158 | + return ( | |
| 159 | + <Text color='blackAlpha.900' >{item.firstName + ' ' + item.lastName}</Text> | |
| 160 | + ) | |
| 161 | + }) : data?.related === 'lead' ? data?.attendesLead && data?.attendesLead.map((item) => { | |
| 162 | + return ( | |
| 163 | + <Text color='blackAlpha.900' >{item.leadName}</Text> | |
| 164 | + ) | |
| 165 | + }) : '-'} | |
| 166 | + </GridItem> | |
| 167 | + {/* <Grid templateColumns={'repeat(2, 1fr)'} gap={4} id="reports"> | |
| 168 | + | |
| 169 | + </Grid> */} | |
| 170 | + </Grid> | |
| 171 | + </Card> | |
| 172 | + </GridItem> | |
| 173 | + | |
| 174 | + </Grid> | |
| 175 | + {(user.role === 'superAdmin' || (permission?.update || permission?.delete)) && <Card mt={3}> | |
| 176 | + <Grid templateColumns="repeat(6, 1fr)" gap={1}> | |
| 177 | + <GridItem colStart={6} > | |
| 178 | + <Flex justifyContent={"right"}> | |
| 179 | + {(user.role === 'superAdmin' || permission?.delete) ? <Button size='sm' style={{ background: 'red.800' }} onClick={() => setDeleteMany(true)} leftIcon={<DeleteIcon />} colorScheme="red" >Delete</Button> : ''} | |
| 180 | + </Flex> | |
| 181 | + </GridItem> | |
| 182 | + </Grid> | |
| 183 | + </Card> | |
| 184 | + } | |
| 185 | + | |
| 186 | + </>} | |
| 187 | + {/* Delete model */} | |
| 188 | + <CommonDeleteModel isOpen={deleteMany} onClose={() => setDeleteMany(false)} type='Meetings' handleDeleteData={handleDeleteMeeting} ids={params.id} /> | |
| 189 | + </> | |
| 190 | + ); | |
| 191 | +}; | |
| 192 | + | |
| 193 | +export default View; |
+206-0Client/src/views/admin/meeting/components/Addmeeting.js
| @@ -0,0 +1,206 @@ | ||
| 1 | +import { Button, Flex, FormLabel, Grid, GridItem, IconButton, Input, Modal, ModalBody, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalOverlay, Radio, RadioGroup, Stack, Text, Textarea } from '@chakra-ui/react'; | |
| 2 | +import { CUIAutoComplete } from 'chakra-ui-autocomplete'; | |
| 3 | +import MultiContactModel from 'components/commonTableModel/MultiContactModel'; | |
| 4 | +import MultiLeadModel from 'components/commonTableModel/MultiLeadModel'; | |
| 5 | +import Spinner from 'components/spinner/Spinner'; | |
| 6 | +import dayjs from 'dayjs'; | |
| 7 | +import { useFormik } from 'formik'; | |
| 8 | +import { useEffect, useState } from 'react'; | |
| 9 | +import { LiaMousePointerSolid } from 'react-icons/lia'; | |
| 10 | +import { useSelector } from 'react-redux'; | |
| 11 | +import { toast } from 'react-toastify'; | |
| 12 | +import { MeetingSchema } from 'schema'; | |
| 13 | +import { getApi, postApi } from 'services/api'; | |
| 14 | + | |
| 15 | +const AddMeeting = (props) => { | |
| 16 | + const { onClose, isOpen, setAction, from, fetchData, view } = props | |
| 17 | + const [leaddata, setLeadData] = useState([]) | |
| 18 | + const [contactdata, setContactData] = useState([]) | |
| 19 | + const [isLoding, setIsLoding] = useState(false) | |
| 20 | + const [contactModelOpen, setContactModel] = useState(false); | |
| 21 | + const [leadModelOpen, setLeadModel] = useState(false); | |
| 22 | + const todayTime = new Date().toISOString().split('.')[0]; | |
| 23 | + const leadData = useSelector((state) => state?.leadData?.data); | |
| 24 | + | |
| 25 | + | |
| 26 | + const user = JSON.parse(localStorage.getItem('user')) | |
| 27 | + | |
| 28 | + const contactList = useSelector((state) => state?.contactData?.data) | |
| 29 | + | |
| 30 | + | |
| 31 | + const initialValues = { | |
| 32 | + agenda: '', | |
| 33 | + attendes: props.leadContect === 'contactView' && props.id ? [props.id] : [], | |
| 34 | + attendesLead: props.leadContect === 'leadView' && props.id ? [props.id] : [], | |
| 35 | + location: '', | |
| 36 | + related: props.leadContect === 'contactView' ? 'Contact' : props.leadContect === 'leadView' ? 'Lead' : 'None', | |
| 37 | + dateTime: '', | |
| 38 | + notes: '', | |
| 39 | + createBy: user?._id, | |
| 40 | + } | |
| 41 | + | |
| 42 | + const formik = useFormik({ | |
| 43 | + initialValues: initialValues, | |
| 44 | + validationSchema: MeetingSchema, | |
| 45 | + onSubmit: (values, { resetForm }) => { | |
| 46 | + | |
| 47 | + }, | |
| 48 | + }); | |
| 49 | + const { errors, touched, values, handleBlur, handleChange, handleSubmit, setFieldValue } = formik | |
| 50 | + | |
| 51 | + const AddData = async () => { | |
| 52 | + | |
| 53 | + }; | |
| 54 | + | |
| 55 | + const fetchAllData = async () => { | |
| 56 | + | |
| 57 | + } | |
| 58 | + | |
| 59 | + useEffect(() => { | |
| 60 | + | |
| 61 | + }, [props.id, values.related]) | |
| 62 | + | |
| 63 | + const extractLabels = (selectedItems) => { | |
| 64 | + return selectedItems.map((item) => item._id); | |
| 65 | + }; | |
| 66 | + | |
| 67 | + const countriesWithEmailAsLabel = (values.related === "Contact" ? contactdata : leaddata)?.map((item) => ({ | |
| 68 | + ...item, | |
| 69 | + value: item._id, | |
| 70 | + label: values.related === "Contact" ? `${item.firstName} ${item.lastName}` : item.leadName, | |
| 71 | + })); | |
| 72 | + | |
| 73 | + return ( | |
| 74 | + <Modal onClose={onClose} isOpen={isOpen} isCentered> | |
| 75 | + <ModalOverlay /> | |
| 76 | + <ModalContent height={"580px"}> | |
| 77 | + <ModalHeader>Add Meeting </ModalHeader> | |
| 78 | + <ModalCloseButton /> | |
| 79 | + <ModalBody overflowY={"auto"} height={"400px"}> | |
| 80 | + {/* Contact Model */} | |
| 81 | + <MultiContactModel data={contactdata} isOpen={contactModelOpen} onClose={setContactModel} fieldName='attendes' setFieldValue={setFieldValue} /> | |
| 82 | + {/* Lead Model */} | |
| 83 | + <MultiLeadModel data={leaddata} isOpen={leadModelOpen} onClose={setLeadModel} fieldName='attendesLead' setFieldValue={setFieldValue} /> | |
| 84 | + | |
| 85 | + <Grid templateColumns="repeat(12, 1fr)" gap={3}> | |
| 86 | + <GridItem colSpan={{ base: 12 }}> | |
| 87 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='8px'> | |
| 88 | + Agenda<Text color={"red"}>*</Text> | |
| 89 | + </FormLabel> | |
| 90 | + <Input | |
| 91 | + fontSize='sm' | |
| 92 | + onChange={handleChange} onBlur={handleBlur} | |
| 93 | + value={values.agenda} | |
| 94 | + name="agenda" | |
| 95 | + placeholder='Agenda' | |
| 96 | + fontWeight='500' | |
| 97 | + borderColor={errors.agenda && touched.agenda ? "red.300" : null} | |
| 98 | + /> | |
| 99 | + <Text fontSize='sm' mb='10px' color={'red'}> {errors.agenda && touched.agenda && errors.agenda}</Text> | |
| 100 | + </GridItem> | |
| 101 | + <GridItem colSpan={{ base: 12 }} > | |
| 102 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='8px'> | |
| 103 | + Related To<Text color={"red"}>*</Text> | |
| 104 | + </FormLabel> | |
| 105 | + <RadioGroup onChange={(e) => setFieldValue('related', e)} value={values.related}> | |
| 106 | + <Stack direction='row'> | |
| 107 | + {props.leadContect === 'contactView' && <Radio value='Contact'>Contact</Radio>} | |
| 108 | + {props.leadContect === 'leadView' && <Radio value='Lead'>Lead</Radio>} | |
| 109 | + {!props.leadContect && <> <Radio value='Contact'>Contact</Radio><Radio value='Lead'>Lead</Radio></>} | |
| 110 | + </Stack> | |
| 111 | + </RadioGroup> | |
| 112 | + <Text mb='10px' color={'red'} fontSize='sm'> {errors.related && touched.related && errors.related}</Text> | |
| 113 | + </GridItem> | |
| 114 | + {(values.related === "Contact" ? (contactdata?.length ?? 0) > 0 : (leaddata?.length ?? 0) > 0) && values.related && | |
| 115 | + | |
| 116 | + <GridItem colSpan={{ base: 12 }}> | |
| 117 | + <Flex alignItems={'end'} justifyContent={'space-between'} > | |
| 118 | + <Text w={'100%'} > | |
| 119 | + <CUIAutoComplete | |
| 120 | + label={`Choose Preferred Attendes ${values.related === "Contact" ? "Contact" : values.related === "Lead" && "Lead"}`} | |
| 121 | + placeholder="Type a Name" | |
| 122 | + name="attendes" | |
| 123 | + items={countriesWithEmailAsLabel} | |
| 124 | + className='custom-autoComplete' | |
| 125 | + selectedItems={countriesWithEmailAsLabel?.filter((item) => values.related === "Contact" ? values?.attendes.includes(item._id) : values.related === "Lead" && values?.attendesLead.includes(item._id))} | |
| 126 | + onSelectedItemsChange={(changes) => { | |
| 127 | + const selectedLabels = extractLabels(changes.selectedItems); | |
| 128 | + values.related === "Contact" ? setFieldValue('attendes', selectedLabels) : values.related === "Lead" && setFieldValue('attendesLead', selectedLabels) | |
| 129 | + }} | |
| 130 | + /> | |
| 131 | + </Text> | |
| 132 | + <IconButton mb={6} onClick={() => values.related === "Contact" ? setContactModel(true) : values.related === "Lead" && setLeadModel(true)} fontSize='25px' icon={<LiaMousePointerSolid />} /> | |
| 133 | + </Flex> | |
| 134 | + <Text color={'red'}> {errors.attendes && touched.attendes && errors.attendes}</Text> | |
| 135 | + </GridItem> | |
| 136 | + } | |
| 137 | + <GridItem colSpan={{ base: 12 }}> | |
| 138 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='8px'> | |
| 139 | + Location | |
| 140 | + </FormLabel> | |
| 141 | + <Input | |
| 142 | + fontSize='sm' | |
| 143 | + onChange={handleChange} onBlur={handleBlur} | |
| 144 | + value={values.location} | |
| 145 | + name="location" | |
| 146 | + placeholder='Location' | |
| 147 | + fontWeight='500' | |
| 148 | + borderColor={errors.location && touched.location ? "red.300" : null} | |
| 149 | + /> | |
| 150 | + <Text mb='10px' color={'red'} fontSize='sm'> {errors.location && touched.location && errors.location}</Text> | |
| 151 | + </GridItem> | |
| 152 | + <GridItem colSpan={{ base: 12 }}> | |
| 153 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='8px'> | |
| 154 | + Date Time<Text color={"red"}>*</Text> | |
| 155 | + </FormLabel> | |
| 156 | + <Input | |
| 157 | + fontSize='sm' | |
| 158 | + type='datetime-local' | |
| 159 | + onChange={handleChange} onBlur={handleBlur} | |
| 160 | + min={dayjs(todayTime).format('YYYY-MM-DD HH:mm')} | |
| 161 | + value={values.dateTime} | |
| 162 | + name="dateTime" | |
| 163 | + placeholder='Date Time' | |
| 164 | + fontWeight='500' | |
| 165 | + borderColor={errors.dateTime && touched.dateTime ? "red.300" : null} | |
| 166 | + /> | |
| 167 | + <Text fontSize='sm' mb='10px' color={'red'}> {errors.dateTime && touched.dateTime && errors.dateTime}</Text> | |
| 168 | + </GridItem> | |
| 169 | + <GridItem colSpan={{ base: 12 }}> | |
| 170 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='8px'> | |
| 171 | + Notes | |
| 172 | + </FormLabel> | |
| 173 | + <Textarea | |
| 174 | + resize={'none'} | |
| 175 | + fontSize='sm' | |
| 176 | + placeholder='Notes' | |
| 177 | + onChange={handleChange} onBlur={handleBlur} | |
| 178 | + value={values.notes} | |
| 179 | + name="notes" | |
| 180 | + fontWeight='500' | |
| 181 | + borderColor={errors.notes && touched.notes ? "red.300" : null} | |
| 182 | + /> | |
| 183 | + <Text mb='10px' color={'red'}> {errors.notes && touched.notes && errors.notes}</Text> | |
| 184 | + </GridItem> | |
| 185 | + | |
| 186 | + </Grid> | |
| 187 | + | |
| 188 | + | |
| 189 | + </ModalBody> | |
| 190 | + <ModalFooter> | |
| 191 | + <Button size="sm" variant='brand' me={2} disabled={isLoding ? true : false} onClick={handleSubmit}>{isLoding ? <Spinner /> : 'Save'}</Button> | |
| 192 | + <Button sx={{ | |
| 193 | + textTransform: "capitalize", | |
| 194 | + }} variant="outline" | |
| 195 | + colorScheme="red" size="sm" onClick={() => { | |
| 196 | + formik.resetForm() | |
| 197 | + onClose() | |
| 198 | + }}>Close</Button> | |
| 199 | + </ModalFooter> | |
| 200 | + </ModalContent> | |
| 201 | + </Modal> | |
| 202 | + ) | |
| 203 | +} | |
| 204 | + | |
| 205 | +export default AddMeeting | |
| 206 | + |
+202-0Client/src/views/admin/meeting/components/MeetingAdvanceSearch.js
| @@ -0,0 +1,202 @@ | ||
| 1 | +import React from 'react'; | |
| 2 | +import { useFormik } from "formik"; | |
| 3 | +import * as yup from "yup"; | |
| 4 | +import { Modal, ModalBody, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalOverlay, Grid, GridItem, Input, FormLabel, Text, Button, } from '@chakra-ui/react'; | |
| 5 | +import Spinner from 'components/spinner/Spinner'; | |
| 6 | +import moment from 'moment'; | |
| 7 | +import { getSearchData, setGetTagValues, setSearchValue } from '../../../../redux/slices/advanceSearchSlice'; | |
| 8 | +import { useDispatch } from 'react-redux'; | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | +const MeetingAdvanceSearch = (props) => { | |
| 13 | + const { allData, advanceSearch, setAdvanceSearch, isLoding, setSearchedData, setDisplaySearchData, setSearchbox } = props; | |
| 14 | + | |
| 15 | + const dispatch = useDispatch(); | |
| 16 | + const initialValues = { | |
| 17 | + agenda: '', | |
| 18 | + createBy: '', | |
| 19 | + startDate: '', | |
| 20 | + endDate: '', | |
| 21 | + timeStartDate: '', | |
| 22 | + timeEndDate: '' | |
| 23 | + } | |
| 24 | + const validationSchema = yup.object({ | |
| 25 | + agenda: yup.string(), | |
| 26 | + createBy: yup.string().email('Invalid email format'), | |
| 27 | + }); | |
| 28 | + const formik = useFormik({ | |
| 29 | + initialValues: initialValues, | |
| 30 | + validationSchema: validationSchema, | |
| 31 | + onSubmit: (values, { resetForm }) => { | |
| 32 | + dispatch(setSearchValue(values)) | |
| 33 | + dispatch(getSearchData({ values: values, allData: allData, type: 'Meeting' })) | |
| 34 | + // const searchResult = allData?.filter( | |
| 35 | + // (item) => { | |
| 36 | + // const itemDate = new Date(item.dateTime); | |
| 37 | + // const momentDate = moment(itemDate).format('YYYY-MM-DD'); | |
| 38 | + // const timeItemDate = new Date(item.timestamp); | |
| 39 | + // const timeMomentDate = moment(timeItemDate).format('YYYY-MM-DD'); | |
| 40 | + // return ( | |
| 41 | + // (!values?.agenda || (item?.agenda && item?.agenda.toLowerCase().includes(values?.agenda?.toLowerCase()))) && | |
| 42 | + // (!values?.createBy || (item?.createBy && item?.createBy.toLowerCase().includes(values?.createBy?.toLowerCase()))) && | |
| 43 | + // (!values?.startDate || (momentDate >= values.startDate)) && | |
| 44 | + // (!values?.endDate || (momentDate <= values.endDate)) && | |
| 45 | + // (!values.timeStartDate || (timeMomentDate >= values.timeStartDate)) && | |
| 46 | + // (!values.timeEndDate || (timeMomentDate <= values.timeEndDate))) | |
| 47 | + // } | |
| 48 | + // ) | |
| 49 | + | |
| 50 | + const dateFrom = `${values?.startDate && `From: ${values?.startDate}`}${values?.endDate && ` To: ${values?.endDate}`}`; | |
| 51 | + const timeDateFrom = `${values?.timeStartDate && `From: ${values?.timeStartDate}`}${values?.timeEndDate && ` To: ${values?.timeEndDate}`}` | |
| 52 | + // let getValue = [values.agenda, values?.createBy, (values?.startDate || values?.endDate) && dateFrom, (values?.timeStartDate || values?.timeEndDate) && timeDateFrom].filter(value => value); | |
| 53 | + | |
| 54 | + const getValue = [ | |
| 55 | + { | |
| 56 | + name: ["agenda"], | |
| 57 | + value: values.agenda | |
| 58 | + }, | |
| 59 | + { | |
| 60 | + name: ["createBy"], | |
| 61 | + value: values.createBy | |
| 62 | + }, | |
| 63 | + { | |
| 64 | + name: ["startDate", "endDate"], | |
| 65 | + value: dateFrom | |
| 66 | + }, | |
| 67 | + | |
| 68 | + { | |
| 69 | + name: ["timeStartDate", "timeEndDate"], | |
| 70 | + value: timeDateFrom | |
| 71 | + } | |
| 72 | + ] | |
| 73 | + dispatch(setGetTagValues(getValue.filter(item => item.value))) | |
| 74 | + // setSearchedData(searchResult); | |
| 75 | + setDisplaySearchData(true) | |
| 76 | + setAdvanceSearch(false) | |
| 77 | + resetForm(); | |
| 78 | + setSearchbox(''); | |
| 79 | + } | |
| 80 | + }) | |
| 81 | + | |
| 82 | + const { errors, touched, values, handleBlur, handleChange, handleSubmit, setFieldValue, resetForm, dirty } = formik; | |
| 83 | + | |
| 84 | + return ( | |
| 85 | + <> | |
| 86 | + <Modal onClose={() => { setAdvanceSearch(false); resetForm() }} isOpen={advanceSearch} isCentered> | |
| 87 | + <ModalOverlay /> | |
| 88 | + <ModalContent> | |
| 89 | + <ModalHeader>Advance Search</ModalHeader> | |
| 90 | + <ModalCloseButton onClick={() => { setAdvanceSearch(false); resetForm() }} /> | |
| 91 | + <ModalBody> | |
| 92 | + <Grid templateColumns="repeat(12, 1fr)" mb={3} gap={2}> | |
| 93 | + <GridItem colSpan={{ base: 12, md: 6 }}> | |
| 94 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='600' color={"#000"} mb="0" mt={2}> | |
| 95 | + Agenda | |
| 96 | + </FormLabel> | |
| 97 | + <Input | |
| 98 | + fontSize='sm' | |
| 99 | + onChange={handleChange} onBlur={handleBlur} | |
| 100 | + value={values?.agenda} | |
| 101 | + name="agenda" | |
| 102 | + placeholder='Enter Lead Name' | |
| 103 | + fontWeight='500' | |
| 104 | + /> | |
| 105 | + <Text mb='10px' color={'red'}> {errors.agenda && touched.agenda && errors.agenda}</Text> | |
| 106 | + | |
| 107 | + </GridItem> | |
| 108 | + | |
| 109 | + <GridItem colSpan={{ base: 12, md: 6 }}> | |
| 110 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='600' color={"#000"} mb="0" mt={2} > | |
| 111 | + Create By | |
| 112 | + </FormLabel> | |
| 113 | + <Input | |
| 114 | + fontSize='sm' | |
| 115 | + onChange={handleChange} onBlur={handleBlur} | |
| 116 | + value={values?.createBy} | |
| 117 | + name="createBy" | |
| 118 | + placeholder='Enter Lead Email' | |
| 119 | + fontWeight='500' | |
| 120 | + /> | |
| 121 | + <Text mb='10px' color={'red'}> {errors.createBy && touched.createBy && errors.createBy}</Text> | |
| 122 | + | |
| 123 | + </GridItem> | |
| 124 | + <GridItem colSpan={{ base: 12 }}> | |
| 125 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='600' color={"#000"} mb="0" mt={2}> | |
| 126 | + Date & time | |
| 127 | + </FormLabel> | |
| 128 | + </GridItem> | |
| 129 | + <GridItem colSpan={{ base: 12, md: 6 }}> | |
| 130 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='600' color={"#000"} mb="0" mt={2}> | |
| 131 | + From | |
| 132 | + </FormLabel> | |
| 133 | + <Input | |
| 134 | + fontSize='sm' | |
| 135 | + onChange={handleChange} onBlur={handleBlur} | |
| 136 | + value={values.startDate} | |
| 137 | + type="date" | |
| 138 | + name='startDate' | |
| 139 | + fontWeight='500' | |
| 140 | + /> | |
| 141 | + </GridItem> | |
| 142 | + <GridItem colSpan={{ base: 12, md: 6 }}> | |
| 143 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='600' color={"#000"} mb="0" mt={2}> | |
| 144 | + To | |
| 145 | + </FormLabel> | |
| 146 | + <Input | |
| 147 | + fontSize='sm' | |
| 148 | + onChange={handleChange} onBlur={handleBlur} | |
| 149 | + value={values.endDate} | |
| 150 | + min={values.startDate} | |
| 151 | + type="date" | |
| 152 | + name='endDate' | |
| 153 | + fontWeight='500' | |
| 154 | + /> | |
| 155 | + </GridItem> | |
| 156 | + | |
| 157 | + <GridItem colSpan={{ base: 12 }}> | |
| 158 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='600' color={"#000"} mb="0" mt={2}> | |
| 159 | + Time Stamp | |
| 160 | + </FormLabel> | |
| 161 | + </GridItem> | |
| 162 | + <GridItem colSpan={{ base: 12, md: 6 }}> | |
| 163 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='600' color={"#000"} mb="0" mt={2}> | |
| 164 | + From | |
| 165 | + </FormLabel> | |
| 166 | + <Input | |
| 167 | + fontSize='sm' | |
| 168 | + onChange={handleChange} onBlur={handleBlur} | |
| 169 | + value={values.timeStartDate} | |
| 170 | + type="date" | |
| 171 | + name='timeStartDate' | |
| 172 | + fontWeight='500' | |
| 173 | + /> | |
| 174 | + </GridItem> | |
| 175 | + <GridItem colSpan={{ base: 12, md: 6 }}> | |
| 176 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='600' color={"#000"} mb="0" mt={2}> | |
| 177 | + To | |
| 178 | + </FormLabel> | |
| 179 | + <Input | |
| 180 | + fontSize='sm' | |
| 181 | + onChange={handleChange} onBlur={handleBlur} | |
| 182 | + value={values.timeEndDate} | |
| 183 | + min={values.timeStartDate} | |
| 184 | + type="date" | |
| 185 | + name='timeEndDate' | |
| 186 | + fontWeight='500' | |
| 187 | + /> | |
| 188 | + </GridItem> | |
| 189 | + | |
| 190 | + </Grid> | |
| 191 | + </ModalBody> | |
| 192 | + <ModalFooter> | |
| 193 | + <Button size="sm" variant="brand" mr={2} onClick={handleSubmit} disabled={isLoding || !dirty ? true : false} >{isLoding ? <Spinner /> : 'Search'}</Button> | |
| 194 | + <Button size="sm" variant="outline" colorScheme="red" onClick={() => resetForm()}>Clear</Button> | |
| 195 | + </ModalFooter> | |
| 196 | + </ModalContent> | |
| 197 | + </Modal> | |
| 198 | + </> | |
| 199 | + ) | |
| 200 | +} | |
| 201 | + | |
| 202 | +export default MeetingAdvanceSearch |
+169-0Client/src/views/admin/meeting/index.js
| @@ -0,0 +1,169 @@ | ||
| 1 | +import { useEffect, useState } from 'react'; | |
| 2 | +import { DeleteIcon, ViewIcon } from '@chakra-ui/icons'; | |
| 3 | +import { Button, Menu, MenuButton, MenuItem, MenuList, Text, useDisclosure } from '@chakra-ui/react'; | |
| 4 | +import { getApi } from 'services/api'; | |
| 5 | +import { HasAccess } from '../../../redux/accessUtils'; | |
| 6 | +import CommonCheckTable from '../../../components/reactTable/checktable'; | |
| 7 | +import { SearchIcon } from "@chakra-ui/icons"; | |
| 8 | +import { CiMenuKebab } from 'react-icons/ci'; | |
| 9 | +import { Link, useNavigate } from 'react-router-dom'; | |
| 10 | +import MeetingAdvanceSearch from './components/MeetingAdvanceSearch'; | |
| 11 | +import AddMeeting from './components/Addmeeting'; | |
| 12 | +import CommonDeleteModel from 'components/commonDeleteModel'; | |
| 13 | +import { deleteManyApi } from 'services/api'; | |
| 14 | +import { toast } from 'react-toastify'; | |
| 15 | +import { fetchMeetingData } from '../../../redux/slices/meetingSlice'; | |
| 16 | +import { useDispatch } from 'react-redux'; | |
| 17 | + | |
| 18 | +const Index = () => { | |
| 19 | + const title = "Meeting"; | |
| 20 | + const navigate = useNavigate() | |
| 21 | + const [action, setAction] = useState(false); | |
| 22 | + const { isOpen, onOpen, onClose } = useDisclosure(); | |
| 23 | + const [selectedValues, setSelectedValues] = useState([]); | |
| 24 | + const [advanceSearch, setAdvanceSearch] = useState(false); | |
| 25 | + const [getTagValuesOutSide, setGetTagValuesOutside] = useState([]); | |
| 26 | + const [searchboxOutside, setSearchboxOutside] = useState(''); | |
| 27 | + const user = JSON.parse(localStorage.getItem("user")); | |
| 28 | + const [deleteMany, setDeleteMany] = useState(false); | |
| 29 | + const [isLoding, setIsLoding] = useState(false); | |
| 30 | + const [data, setData] = useState([]); | |
| 31 | + const [displaySearchData, setDisplaySearchData] = useState(false); | |
| 32 | + const [searchedData, setSearchedData] = useState([]); | |
| 33 | + const [permission] = HasAccess(['Meetings']) | |
| 34 | + const dispatch = useDispatch() | |
| 35 | + | |
| 36 | + | |
| 37 | + const actionHeader = { | |
| 38 | + Header: "Action", isSortable: false, center: true, | |
| 39 | + cell: ({ row }) => ( | |
| 40 | + <Text fontSize="md" fontWeight="900" textAlign={"center"}> | |
| 41 | + <Menu isLazy > | |
| 42 | + <MenuButton><CiMenuKebab /></MenuButton> | |
| 43 | + <MenuList minW={'fit-content'} transform={"translate(1520px, 173px);"}> | |
| 44 | + | |
| 45 | + {permission?.view && <MenuItem py={2.5} color={'green'} | |
| 46 | + onClick={() => navigate(`/metting/${row?.values._id}`)} | |
| 47 | + icon={<ViewIcon fontSize={15} />}>View</MenuItem>} | |
| 48 | + {permission?.delete && <MenuItem py={2.5} color={'red'} onClick={() => { setDeleteMany(true); setSelectedValues([row?.values?._id]); }} icon={<DeleteIcon fontSize={15} />}>Delete</MenuItem>} | |
| 49 | + </MenuList> | |
| 50 | + </Menu> | |
| 51 | + </Text> | |
| 52 | + ) | |
| 53 | + } | |
| 54 | + const tableColumns = [ | |
| 55 | + { | |
| 56 | + Header: "#", | |
| 57 | + accessor: "_id", | |
| 58 | + isSortable: false, | |
| 59 | + width: 10 | |
| 60 | + }, | |
| 61 | + { | |
| 62 | + Header: 'Agenda', accessor: 'agenda', cell: (cell) => ( | |
| 63 | + <Link to={`/metting/${cell?.row?.values._id}`}> <Text | |
| 64 | + me="10px" | |
| 65 | + sx={{ '&:hover': { color: 'blue.500', textDecoration: 'underline' } }} | |
| 66 | + color='brand.600' | |
| 67 | + fontSize="sm" | |
| 68 | + fontWeight="700" | |
| 69 | + > | |
| 70 | + {cell?.value || ' - '} | |
| 71 | + </Text></Link>) | |
| 72 | + }, | |
| 73 | + { Header: "Date & Time", accessor: "dateTime", }, | |
| 74 | + { Header: "Time Stamp", accessor: "timestamp", }, | |
| 75 | + { Header: "Create By", accessor: "createdByName", }, | |
| 76 | + ...(permission?.update || permission?.view || permission?.delete ? [actionHeader] : []) | |
| 77 | + | |
| 78 | + ]; | |
| 79 | + | |
| 80 | + const fetchData = async () => { | |
| 81 | + setIsLoding(true) | |
| 82 | + const result = await dispatch(fetchMeetingData()) | |
| 83 | + if (result.payload.status === 200) { | |
| 84 | + setData(result?.payload?.data); | |
| 85 | + } else { | |
| 86 | + toast.error("Failed to fetch data", "error"); | |
| 87 | + } | |
| 88 | + setIsLoding(false) | |
| 89 | + } | |
| 90 | + | |
| 91 | + const handleDeleteMeeting = async (ids) => { | |
| 92 | + try { | |
| 93 | + setIsLoding(true) | |
| 94 | + let response = await deleteManyApi('api/meeting/deleteMany', ids) | |
| 95 | + if (response.status === 200) { | |
| 96 | + setSelectedValues([]) | |
| 97 | + setDeleteMany(false) | |
| 98 | + setAction((pre) => !pre) | |
| 99 | + } | |
| 100 | + } catch (error) { | |
| 101 | + console.log(error) | |
| 102 | + } | |
| 103 | + finally { | |
| 104 | + setIsLoding(false) | |
| 105 | + } | |
| 106 | + } | |
| 107 | + | |
| 108 | + // const [selectedColumns, setSelectedColumns] = useState([...tableColumns]); | |
| 109 | + // const dataColumn = tableColumns?.filter(item => selectedColumns?.find(colum => colum?.Header === item.Header)) | |
| 110 | + | |
| 111 | + | |
| 112 | + useEffect(() => { | |
| 113 | + fetchData(); | |
| 114 | + }, [action]) | |
| 115 | + | |
| 116 | + return ( | |
| 117 | + <div> | |
| 118 | + <CommonCheckTable | |
| 119 | + title={title} | |
| 120 | + isLoding={isLoding} | |
| 121 | + columnData={tableColumns ?? []} | |
| 122 | + // dataColumn={dataColumn ?? []} | |
| 123 | + allData={data ?? []} | |
| 124 | + tableData={data} | |
| 125 | + searchDisplay={displaySearchData} | |
| 126 | + setSearchDisplay={setDisplaySearchData} | |
| 127 | + searchedDataOut={searchedData} | |
| 128 | + setSearchedDataOut={setSearchedData} | |
| 129 | + tableCustomFields={[]} | |
| 130 | + access={permission} | |
| 131 | + // action={action} | |
| 132 | + // setAction={setAction} | |
| 133 | + // selectedColumns={selectedColumns} | |
| 134 | + // setSelectedColumns={setSelectedColumns} | |
| 135 | + // isOpen={isOpen} | |
| 136 | + // onClose={onClose} | |
| 137 | + onOpen={onOpen} | |
| 138 | + selectedValues={selectedValues} | |
| 139 | + setSelectedValues={setSelectedValues} | |
| 140 | + setDelete={setDeleteMany} | |
| 141 | + AdvanceSearch={ | |
| 142 | + <Button variant="outline" colorScheme='brand' leftIcon={<SearchIcon />} mt={{ sm: "5px", md: "0" }} size="sm" onClick={() => setAdvanceSearch(true)}>Advance Search</Button> | |
| 143 | + } | |
| 144 | + getTagValuesOutSide={getTagValuesOutSide} | |
| 145 | + searchboxOutside={searchboxOutside} | |
| 146 | + setGetTagValuesOutside={setGetTagValuesOutside} | |
| 147 | + setSearchboxOutside={setSearchboxOutside} | |
| 148 | + handleSearchType="MeetingSearch" | |
| 149 | + /> | |
| 150 | + | |
| 151 | + <MeetingAdvanceSearch | |
| 152 | + advanceSearch={advanceSearch} | |
| 153 | + setAdvanceSearch={setAdvanceSearch} | |
| 154 | + setSearchedData={setSearchedData} | |
| 155 | + setDisplaySearchData={setDisplaySearchData} | |
| 156 | + allData={data ?? []} | |
| 157 | + setAction={setAction} | |
| 158 | + setGetTagValues={setGetTagValuesOutside} | |
| 159 | + setSearchbox={setSearchboxOutside} | |
| 160 | + /> | |
| 161 | + <AddMeeting setAction={setAction} isOpen={isOpen} onClose={onClose} /> | |
| 162 | + | |
| 163 | + {/* Delete model */} | |
| 164 | + <CommonDeleteModel isOpen={deleteMany} onClose={() => setDeleteMany(false)} type='Meetings' handleDeleteData={handleDeleteMeeting} ids={selectedValues} /> | |
| 165 | + </div> | |
| 166 | + ) | |
| 167 | +} | |
| 168 | + | |
| 169 | +export default Index |
+126-0Client/src/views/admin/meeting/meetingView.js
| @@ -0,0 +1,126 @@ | ||
| 1 | +import { CloseIcon, DeleteIcon, EditIcon, ViewIcon } from '@chakra-ui/icons' | |
| 2 | +import { DrawerFooter, Flex, Grid, GridItem, IconButton, Modal, ModalBody, ModalContent, ModalHeader, ModalOverlay, Text } from '@chakra-ui/react' | |
| 3 | +import Spinner from "components/spinner/Spinner" | |
| 4 | +import moment from 'moment' | |
| 5 | +import { useEffect, useState } from 'react' | |
| 6 | +import { BiLink } from 'react-icons/bi' | |
| 7 | +import { Link } from 'react-router-dom' | |
| 8 | +import { getApi } from 'services/api' | |
| 9 | +// import DeleteTask from './components/deleteTask' | |
| 10 | +import { useNavigate } from 'react-router-dom'; | |
| 11 | +import { HasAccess } from "../../../redux/accessUtils"; | |
| 12 | + | |
| 13 | +const MeetingView = (props) => { | |
| 14 | + const { onClose, isOpen, info, fetchData, setAction, action, access } = props | |
| 15 | + const [data, setData] = useState(); | |
| 16 | + const [edit, setEdit] = useState(false); | |
| 17 | + const [deleteModel, setDelete] = useState(false); | |
| 18 | + const user = JSON.parse(localStorage.getItem("user")) | |
| 19 | + const [isLoding, setIsLoding] = useState(false) | |
| 20 | + const navigate = useNavigate() | |
| 21 | + | |
| 22 | + const fetchViewData = async () => { | |
| 23 | + if (info) { | |
| 24 | + setIsLoding(true) | |
| 25 | + let result = await getApi('api/meeting/view/', info?.event ? info?.event?.id : info); | |
| 26 | + setData(result?.data); | |
| 27 | + setIsLoding(false) | |
| 28 | + } | |
| 29 | + } | |
| 30 | + | |
| 31 | + useEffect(() => { | |
| 32 | + fetchViewData() | |
| 33 | + }, [action, info]) | |
| 34 | + | |
| 35 | + const [contactAccess, leadAccess] = HasAccess(['Contacts', 'Leads']) | |
| 36 | + | |
| 37 | + const handleViewOpen = () => { | |
| 38 | + if (info?.event) { | |
| 39 | + navigate(`/view/${info?.event?.id}`) | |
| 40 | + } | |
| 41 | + else { | |
| 42 | + navigate(`/view/${info}`) | |
| 43 | + } | |
| 44 | + } | |
| 45 | + return ( | |
| 46 | + <Modal isOpen={isOpen} size={'md'} isCentered> | |
| 47 | + <ModalOverlay /> | |
| 48 | + <ModalContent height={"70%"}> | |
| 49 | + <ModalHeader justifyContent='space-between' display='flex' > | |
| 50 | + Meeting | |
| 51 | + <IconButton onClick={() => onClose(false)} icon={<CloseIcon />} /> | |
| 52 | + </ModalHeader> | |
| 53 | + {isLoding ? | |
| 54 | + <Flex justifyContent={'center'} alignItems={'center'} mb={30} width="100%" > | |
| 55 | + <Spinner /> | |
| 56 | + </Flex> : <> | |
| 57 | + | |
| 58 | + <ModalBody overflowY={"auto"}> | |
| 59 | + <Grid templateColumns="repeat(12, 1fr)" gap={3} > | |
| 60 | + <GridItem colSpan={{ base: 12, md: 6 }} > | |
| 61 | + <Text fontSize="sm" fontWeight="bold" color={'blackAlpha.900'}> Agenda </Text> | |
| 62 | + <Text>{data?.agenda ? data?.agenda : ' - '}</Text> | |
| 63 | + </GridItem> | |
| 64 | + <GridItem colSpan={{ base: 12, md: 6 }} > | |
| 65 | + <Text fontSize="sm" fontWeight="bold" color={'blackAlpha.900'}> Date&Time </Text> | |
| 66 | + <Text>{data?.dateTime ? moment(data?.dateTime).format('lll ') : ' - '}</Text> | |
| 67 | + </GridItem> | |
| 68 | + <GridItem colSpan={{ base: 12, md: 6 }} > | |
| 69 | + <Text fontSize="sm" fontWeight="bold" color={'blackAlpha.900'}> Created By </Text> | |
| 70 | + <Text>{data?.createdByName ? data?.createdByName : '-'}</Text> | |
| 71 | + </GridItem> | |
| 72 | + <GridItem colSpan={{ base: 12, md: 6 }} > | |
| 73 | + <Text fontSize="sm" fontWeight="bold" color={'blackAlpha.900'}> Related </Text> | |
| 74 | + <Text>{data?.related ? data?.related : '-'}</Text> | |
| 75 | + </GridItem> | |
| 76 | + <GridItem colSpan={{ base: 12, md: 6 }} > | |
| 77 | + <Text fontSize="sm" fontWeight="bold" color={'blackAlpha.900'}> Location </Text> | |
| 78 | + <Text>{data?.location ? data?.location : '-'}</Text> | |
| 79 | + </GridItem> | |
| 80 | + <GridItem colSpan={{ base: 12, md: 6 }} > | |
| 81 | + <Text fontSize="sm" fontWeight="bold" color={'blackAlpha.900'}> Notes </Text> | |
| 82 | + <Text>{data?.notes ? data?.notes : '-'}</Text> | |
| 83 | + </GridItem> | |
| 84 | + <GridItem colSpan={{ base: 12, md: 6 }} > | |
| 85 | + <Text fontSize="sm" fontWeight="bold" color={'blackAlpha.900'}> attendes </Text> | |
| 86 | + {data?.related === 'Contact' && contactAccess?.view ? data?.attendes && data?.attendes.map((item) => { | |
| 87 | + return ( | |
| 88 | + <Link to={`/contactView/${item._id}`}> | |
| 89 | + <Text color='brand.600' sx={{ '&:hover': { color: 'blue.500', textDecoration: 'underline' } }}>{item.firstName + ' ' + item.lastName}</Text> | |
| 90 | + </Link> | |
| 91 | + ) | |
| 92 | + }) : data?.related === 'Lead' && leadAccess?.view ? data?.attendesLead && data?.attendesLead.map((item) => { | |
| 93 | + return ( | |
| 94 | + <Link to={`/leadView/${item._id}`}> | |
| 95 | + <Text color='brand.600' sx={{ '&:hover': { color: 'blue.500', textDecoration: 'underline' } }}>{item.leadName}</Text> | |
| 96 | + </Link> | |
| 97 | + ) | |
| 98 | + }) : data?.related === 'Contact' ? data?.attendes && data?.attendes.map((item) => { | |
| 99 | + return ( | |
| 100 | + <Text color='blackAlpha.900' >{item.firstName + ' ' + item.lastName}</Text> | |
| 101 | + ) | |
| 102 | + }) : data?.related === 'Lead' ? data?.attendesLead && data?.attendesLead.map((item) => { | |
| 103 | + return ( | |
| 104 | + <Text color='blackAlpha.900' >{item.leadName}</Text> | |
| 105 | + ) | |
| 106 | + }) : '-'} | |
| 107 | + </GridItem> | |
| 108 | + | |
| 109 | + </Grid> | |
| 110 | + | |
| 111 | + </ModalBody> | |
| 112 | + <DrawerFooter> | |
| 113 | + {access?.view && <IconButton variant='outline' colorScheme={'green'} onClick={() => handleViewOpen()} borderRadius="10px" size="md" icon={<ViewIcon />} />} | |
| 114 | + {access?.update && <IconButton variant='outline' onClick={() => setEdit(true)} ml={3} borderRadius="10px" size="md" icon={<EditIcon />} />} | |
| 115 | + {access?.delete && <IconButton colorScheme='red' onClick={() => setDelete(true)} ml={3} borderRadius="10px" size="md" icon={<DeleteIcon />} />} | |
| 116 | + | |
| 117 | + {/* | |
| 118 | + <DeleteTask fetchData={props.fetchData} isOpen={deleteModel} onClose={setDelete} viewClose={onClose} url='api/task/delete/' method='one' id={info?.event ? info?.event?.id : info} /> */} | |
| 119 | + </DrawerFooter> | |
| 120 | + </>} | |
| 121 | + </ModalContent> | |
| 122 | + </Modal> | |
| 123 | + ) | |
| 124 | +} | |
| 125 | + | |
| 126 | +export default MeetingView |
+127-0Client/src/views/admin/moduleName/Edit.js
| @@ -0,0 +1,127 @@ | ||
| 1 | +import {Button, FormLabel, Grid, GridItem, Image, Input, Modal, ModalBody, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalOverlay, Text, useColorModeValue } from '@chakra-ui/react' | |
| 2 | +import Spinner from 'components/spinner/Spinner' | |
| 3 | +import React, {useState } from 'react' | |
| 4 | +import { useFormik } from 'formik' | |
| 5 | +import { HSeparator } from 'components/separator/Separator' | |
| 6 | +import { putApi } from 'services/api' | |
| 7 | +import { moduleAddSchema } from 'schema/moduleAddSchema' | |
| 8 | +import Dropzone from "components/Dropzone"; | |
| 9 | +import { toast } from 'react-toastify' | |
| 10 | +import { fetchRouteData } from '../../../redux/slices/routeSlice' | |
| 11 | +import { useDispatch } from 'react-redux' | |
| 12 | + | |
| 13 | +const Edit = (props) => { | |
| 14 | + const { onClose, isOpen, fetchData, selectedId, editdata, setAction } = props; | |
| 15 | + const [isLoding, setIsLoding] = useState(false) | |
| 16 | + const dispatch = useDispatch(); | |
| 17 | + | |
| 18 | + const initialValues = { | |
| 19 | + moduleName: editdata?.moduleName ? editdata?.moduleName : "", | |
| 20 | + icon: '' | |
| 21 | + }; | |
| 22 | + | |
| 23 | + const formik = useFormik({ | |
| 24 | + initialValues: initialValues, | |
| 25 | + validationSchema: moduleAddSchema, | |
| 26 | + enableReinitialize: true, | |
| 27 | + onSubmit: (values, { resetForm }) => { | |
| 28 | + EditData() | |
| 29 | + resetForm() | |
| 30 | + }, | |
| 31 | + }); | |
| 32 | + | |
| 33 | + const EditData = async () => { | |
| 34 | + let response = await putApi(`api/custom-field/change-module-name/${selectedId}`, values); | |
| 35 | + if (response.status === 200) { | |
| 36 | + await dispatch(fetchRouteData()) | |
| 37 | + onClose() | |
| 38 | + fetchData() | |
| 39 | + setAction((pre) => !pre) | |
| 40 | + toast.success(response?.data?.message); | |
| 41 | + } else { | |
| 42 | + toast.error(response?.response?.data?.message); | |
| 43 | + } | |
| 44 | + } | |
| 45 | + | |
| 46 | + const changeImage = async (file) => { | |
| 47 | + try { | |
| 48 | + setIsLoding(true); | |
| 49 | + const formData = new FormData(); | |
| 50 | + | |
| 51 | + formData.append("icon", file?.[0]) | |
| 52 | + const response = await putApi(`api/custom-field/change-icon/${editdata?._id}`, formData); | |
| 53 | + if (response.status === 200) { | |
| 54 | + await dispatch(fetchRouteData()); | |
| 55 | + onClose(); | |
| 56 | + setAction((pre) => !pre) | |
| 57 | + toast.success(response?.data?.message); | |
| 58 | + } else { | |
| 59 | + toast.error(response?.response?.data?.message); | |
| 60 | + } | |
| 61 | + } catch (e) { | |
| 62 | + console.log(e); | |
| 63 | + } | |
| 64 | + finally { | |
| 65 | + setIsLoding(false) | |
| 66 | + } | |
| 67 | + }; | |
| 68 | + | |
| 69 | + const { errors, touched, values, handleBlur, handleChange, handleSubmit, setFieldValue } = formik | |
| 70 | + return ( | |
| 71 | + <div> | |
| 72 | + <Modal onClose={onClose} isOpen={isOpen} isCentered size='2xl'> | |
| 73 | + <ModalOverlay /> | |
| 74 | + <ModalContent> | |
| 75 | + <ModalHeader>Edit Module Name </ModalHeader> | |
| 76 | + <ModalCloseButton /> | |
| 77 | + <HSeparator /> | |
| 78 | + <ModalBody> | |
| 79 | + <> | |
| 80 | + <Grid templateColumns="repeat(12, 1fr)" gap={3}> | |
| 81 | + <GridItem colSpan={{ base: 12 }}> | |
| 82 | + <div className="imageCard" style={{ margin: "10px" }}> | |
| 83 | + <Image src={editdata?.icon} height={"20%"} width={"20%"} /> | |
| 84 | + <div className='imageContent'> | |
| 85 | + <Dropzone | |
| 86 | + borderRadius="0" | |
| 87 | + isMultipleAllow={false} | |
| 88 | + onFileSelect={(file) => changeImage(file)} | |
| 89 | + content={ | |
| 90 | + <Button size='sm' variant="brand">Change</Button> | |
| 91 | + } | |
| 92 | + /> | |
| 93 | + </div> | |
| 94 | + </div> | |
| 95 | + </GridItem> | |
| 96 | + <GridItem colSpan={{ base: 12 }}> | |
| 97 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='2px'> | |
| 98 | + Name | |
| 99 | + </FormLabel> | |
| 100 | + <Input | |
| 101 | + fontSize='sm' | |
| 102 | + onChange={handleChange} onBlur={handleBlur} | |
| 103 | + value={values.moduleName} | |
| 104 | + name="moduleName" | |
| 105 | + placeholder='Enter Name' | |
| 106 | + fontWeight='500' | |
| 107 | + borderColor={errors.moduleName && touched.moduleName ? "red.300" : null} | |
| 108 | + /> | |
| 109 | + <Text mb='10px' color={'red'}> {errors.moduleName && touched.moduleName && errors.moduleName}</Text> | |
| 110 | + </GridItem> | |
| 111 | + </Grid> | |
| 112 | + </> | |
| 113 | + </ModalBody> | |
| 114 | + <ModalFooter> | |
| 115 | + <Button colorScheme="brand" size='sm' mr={2} disabled={isLoding ? true : false} onClick={handleSubmit}>{isLoding ? <Spinner /> : 'Update'}</Button> | |
| 116 | + <Button sx={{ | |
| 117 | + textTransform: "capitalize", | |
| 118 | + }} variant="outline" | |
| 119 | + colorScheme="red" size="sm" onClick={onClose}>Close</Button> | |
| 120 | + </ModalFooter> | |
| 121 | + </ModalContent> | |
| 122 | + </Modal> | |
| 123 | + </div> | |
| 124 | + ) | |
| 125 | +} | |
| 126 | + | |
| 127 | +export default Edit |
+137-0Client/src/views/admin/moduleName/add.js
| @@ -0,0 +1,137 @@ | ||
| 1 | +import { Box, Button, Flex, FormLabel, Grid, GridItem, Icon, Input, Modal, ModalBody, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalOverlay, Text, useColorModeValue } from '@chakra-ui/react' | |
| 2 | +import Spinner from 'components/spinner/Spinner' | |
| 3 | +import React, { useState } from 'react' | |
| 4 | +import { useFormik } from 'formik' | |
| 5 | +import { postApi } from 'services/api' | |
| 6 | +import { moduleAddSchema } from 'schema/moduleAddSchema' | |
| 7 | +import { toast } from 'react-toastify' | |
| 8 | +import Dropzone from "components/Dropzone"; | |
| 9 | +import { MdUpload } from "react-icons/md"; | |
| 10 | +import { fetchRouteData } from '../../../redux/slices/routeSlice' | |
| 11 | +import { useDispatch } from 'react-redux' | |
| 12 | +import { fetchModules } from '../../../redux/slices/moduleSlice' | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | +const Add = (props) => { | |
| 17 | + const { onClose, isOpen, fetchData, setAction } = props; | |
| 18 | + const [isLoding, setIsLoding] = useState(false) | |
| 19 | + const brandColor = useColorModeValue("brand.500", "white"); | |
| 20 | + const dispatch = useDispatch(); | |
| 21 | + const initialValues = { | |
| 22 | + moduleName: "", | |
| 23 | + icon: "" | |
| 24 | + }; | |
| 25 | + | |
| 26 | + const formik = useFormik({ | |
| 27 | + initialValues: initialValues, | |
| 28 | + // validationSchema: validationsAddSchema, | |
| 29 | + validationSchema: moduleAddSchema, | |
| 30 | + onSubmit: (values, { resetForm }) => { | |
| 31 | + AddData() | |
| 32 | + }, | |
| 33 | + }); | |
| 34 | + | |
| 35 | + const { errors, touched, values, handleBlur, handleChange, handleSubmit, setFieldValue, resetForm } = formik | |
| 36 | + | |
| 37 | + const AddData = async () => { | |
| 38 | + try { | |
| 39 | + setIsLoding(true) | |
| 40 | + const formData = new FormData(); | |
| 41 | + | |
| 42 | + formData.append("moduleName", values?.moduleName) | |
| 43 | + formData.append("icon", values?.icon?.[0]) | |
| 44 | + | |
| 45 | + | |
| 46 | + let response = await postApi('api/custom-field/add-module', formData); | |
| 47 | + if (response.status === 200) { | |
| 48 | + fetchData() | |
| 49 | + await dispatch(fetchRouteData()); | |
| 50 | + await dispatch(fetchModules()) | |
| 51 | + onClose() | |
| 52 | + resetForm() | |
| 53 | + setAction((pre) => !pre) | |
| 54 | + } else { | |
| 55 | + toast.error(response.response.data.message); | |
| 56 | + } | |
| 57 | + } | |
| 58 | + catch (error) { | |
| 59 | + if (error.response && error.response.data) { | |
| 60 | + toast.error(error.response.data.message); | |
| 61 | + } else { | |
| 62 | + toast.error("An error occurred while processing your request."); | |
| 63 | + } | |
| 64 | + } | |
| 65 | + finally { | |
| 66 | + setIsLoding(false) | |
| 67 | + } | |
| 68 | + } | |
| 69 | + | |
| 70 | + | |
| 71 | + return ( | |
| 72 | + <div> | |
| 73 | + <Modal onClose={onClose} isOpen={isOpen} isCentered size='2xl'> | |
| 74 | + <ModalOverlay /> | |
| 75 | + <ModalContent> | |
| 76 | + <ModalHeader>Add Module</ModalHeader> | |
| 77 | + <ModalCloseButton /> | |
| 78 | + <ModalBody> | |
| 79 | + <> | |
| 80 | + <Grid templateColumns="repeat(12, 1fr)" gap={3}> | |
| 81 | + | |
| 82 | + <GridItem colSpan={{ base: 12 }}> | |
| 83 | + <Dropzone | |
| 84 | + w={{ base: "100%" }} | |
| 85 | + me='36px' | |
| 86 | + minH={100} | |
| 87 | + img={props.text === 'Property Photos' ? 'img' : ''} | |
| 88 | + isMultipleAllow={false} | |
| 89 | + height={'100%'} | |
| 90 | + onFileSelect={(file) => setFieldValue('icon', file)} | |
| 91 | + content={ | |
| 92 | + <Box> | |
| 93 | + <Icon as={MdUpload} w='50px' h='50px' color={brandColor} /> | |
| 94 | + <Flex justify='center' mx='auto' mb='12px'> | |
| 95 | + <Text fontSize='sm' fontWeight='700' color={brandColor}> | |
| 96 | + Upload File | |
| 97 | + </Text> | |
| 98 | + </Flex> | |
| 99 | + {values?.icon.length > 0 && <Text fontSize='sm' fontWeight='500' color='secondaryGray.500'> | |
| 100 | + Selected Files : {values?.icon.length} | |
| 101 | + </Text>} | |
| 102 | + </Box> | |
| 103 | + } | |
| 104 | + /> | |
| 105 | + </GridItem> | |
| 106 | + <GridItem colSpan={{ base: 12 }}> | |
| 107 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='2px'> | |
| 108 | + Name<Text color={"red"}>*</Text> | |
| 109 | + </FormLabel> | |
| 110 | + <Input | |
| 111 | + fontSize='sm' | |
| 112 | + onChange={handleChange} onBlur={handleBlur} | |
| 113 | + value={values.moduleName} | |
| 114 | + name="moduleName" | |
| 115 | + placeholder='Enter Name' | |
| 116 | + fontWeight='500' | |
| 117 | + borderColor={errors.moduleName && touched.moduleName ? "red.300" : null} | |
| 118 | + /> | |
| 119 | + <Text mb='10px' fontSize='sm' color={'red'}> {errors.moduleName && touched.moduleName && errors.moduleName}</Text> | |
| 120 | + </GridItem> | |
| 121 | + </Grid> | |
| 122 | + </> | |
| 123 | + </ModalBody> | |
| 124 | + <ModalFooter> | |
| 125 | + <Button colorScheme="brand" size='sm' mr={2} disabled={isLoding ? true : false} onClick={handleSubmit}>{isLoding ? <Spinner /> : 'Save'}</Button> | |
| 126 | + <Button sx={{ | |
| 127 | + textTransform: "capitalize", | |
| 128 | + }} variant="outline" | |
| 129 | + colorScheme="red" size="sm" onClick={() => { onClose(); resetForm() }}>Close</Button> | |
| 130 | + </ModalFooter> | |
| 131 | + </ModalContent> | |
| 132 | + </Modal> | |
| 133 | + </div> | |
| 134 | + ) | |
| 135 | +} | |
| 136 | + | |
| 137 | +export default Add |
+183-0Client/src/views/admin/moduleName/index.js
| @@ -0,0 +1,183 @@ | ||
| 1 | +import { AddIcon, DeleteIcon, EditIcon } from '@chakra-ui/icons' | |
| 2 | +import { Button, Checkbox, Flex, Grid, GridItem, Heading, Text, Tooltip, useColorModeValue } from '@chakra-ui/react' | |
| 3 | +import Card from 'components/card/Card' | |
| 4 | +import React, { useEffect, useState } from 'react' | |
| 5 | +import { getApi } from 'services/api' | |
| 6 | +import Edit from './Edit' | |
| 7 | +import View from './view' | |
| 8 | +import { useNavigate } from 'react-router-dom' | |
| 9 | +import Add from './add' | |
| 10 | +import { IoIosArrowBack } from 'react-icons/io' | |
| 11 | +import DataNotFound from 'components/notFoundData' | |
| 12 | +import Spinner from '../../../components/spinner/Spinner' | |
| 13 | +import { deleteManyApi } from 'services/api' | |
| 14 | +import { fetchRouteData } from '../../../redux/slices/routeSlice'; | |
| 15 | +import CommonDeleteModel from 'components/commonDeleteModel'; | |
| 16 | +import { useDispatch } from 'react-redux' | |
| 17 | +import { deleteApi } from 'services/api' | |
| 18 | +import { fetchModules } from '../../../redux/slices/moduleSlice' | |
| 19 | + | |
| 20 | +const Index = () => { | |
| 21 | + const navigate = useNavigate() | |
| 22 | + const [editModal, setEdit] = useState(false) | |
| 23 | + const [viewModal, setViewModal] = useState(false) | |
| 24 | + const [addModal, setAddModal] = useState(false) | |
| 25 | + const [deleteModal, setDeleteModal] = useState(false) | |
| 26 | + const [action, setAction] = useState(false) | |
| 27 | + const [selectedId, setselectedId] = useState() | |
| 28 | + const [editdata, setEditData] = useState([]) | |
| 29 | + const [moduleData, setValidateData] = useState([]) | |
| 30 | + const [selectedValues, setSelectedValues] = useState([]); | |
| 31 | + const [isLoding, setIsLoding] = useState(false) | |
| 32 | + const textColor = useColorModeValue("gray.500", "white"); | |
| 33 | + const [method, setMethod] = useState('') | |
| 34 | + | |
| 35 | + const dispatch = useDispatch(); | |
| 36 | + | |
| 37 | + | |
| 38 | + const fetchData = async () => { | |
| 39 | + setIsLoding(true) | |
| 40 | + try { | |
| 41 | + let response = await getApi(`api/custom-field`); | |
| 42 | + setValidateData(response?.data); | |
| 43 | + setIsLoding(false); | |
| 44 | + } catch (error) { | |
| 45 | + | |
| 46 | + } | |
| 47 | + } | |
| 48 | + useEffect(() => { | |
| 49 | + fetchData() | |
| 50 | + }, [action]) | |
| 51 | + | |
| 52 | + const handleEditClose = () => { | |
| 53 | + setEdit(false) | |
| 54 | + } | |
| 55 | + | |
| 56 | + const handleEditOpen = (item) => { | |
| 57 | + setselectedId(item?._id) | |
| 58 | + setEditData(item) | |
| 59 | + setEdit(!editModal) | |
| 60 | + } | |
| 61 | + | |
| 62 | + const handleViewClose = () => { | |
| 63 | + setViewModal(false) | |
| 64 | + } | |
| 65 | + const handleAddOpen = (item) => { | |
| 66 | + setAddModal(!viewModal) | |
| 67 | + } | |
| 68 | + const handleAddClose = () => { | |
| 69 | + setAddModal(false) | |
| 70 | + } | |
| 71 | + const handleDeleteOpen = (item, type) => { | |
| 72 | + setMethod(type) | |
| 73 | + setselectedId(item?._id) | |
| 74 | + setDeleteModal(!deleteModal) | |
| 75 | + } | |
| 76 | + | |
| 77 | + const handleCheckboxChange = (event, value) => { | |
| 78 | + if (event.target.checked) { | |
| 79 | + setSelectedValues((prevSelectedValues) => [...prevSelectedValues, value]); | |
| 80 | + } else { | |
| 81 | + setSelectedValues((prevSelectedValues) => | |
| 82 | + prevSelectedValues.filter((selectedValue) => selectedValue !== value) | |
| 83 | + ); | |
| 84 | + } | |
| 85 | + }; | |
| 86 | + | |
| 87 | + const handleDeleteModule = async (ids, selectedIds) => { | |
| 88 | + if (method === 'one') { | |
| 89 | + try { | |
| 90 | + if (ids) { | |
| 91 | + setIsLoding(true) | |
| 92 | + const response = await deleteApi('api/custom-field/module/', ids) | |
| 93 | + if (response.status === 200) { | |
| 94 | + await dispatch(fetchRouteData()); | |
| 95 | + setDeleteModal(false) | |
| 96 | + fetchData() | |
| 97 | + await dispatch(fetchModules()) | |
| 98 | + } | |
| 99 | + } | |
| 100 | + } catch (error) { | |
| 101 | + console.log(error) | |
| 102 | + } | |
| 103 | + finally { | |
| 104 | + setIsLoding(false) | |
| 105 | + } | |
| 106 | + } else if (method === 'many') { | |
| 107 | + try { | |
| 108 | + setIsLoding(true) | |
| 109 | + let response = await deleteManyApi('api/custom-field/deleteMany-Module', selectedIds) | |
| 110 | + if (response.status === 200) { | |
| 111 | + await dispatch(fetchRouteData()); | |
| 112 | + setSelectedValues([]) | |
| 113 | + setDeleteModal(false) | |
| 114 | + fetchData() | |
| 115 | + await dispatch(fetchModules()) | |
| 116 | + } | |
| 117 | + } catch (error) { | |
| 118 | + console.log(error) | |
| 119 | + } | |
| 120 | + finally { | |
| 121 | + setIsLoding(false) | |
| 122 | + } | |
| 123 | + } | |
| 124 | + | |
| 125 | + }; | |
| 126 | + | |
| 127 | + return ( | |
| 128 | + <div> | |
| 129 | + <Flex justifyContent={"end"} mb={3}> | |
| 130 | + {selectedValues.length > 0 && <Button variant='outline' colorScheme='brand' color={"red"} mr={2} leftIcon={<DeleteIcon />} onClick={() => { handleDeleteOpen('', 'many') }} size='sm' >Delete</Button>} | |
| 131 | + <Button size='sm' variant='brand' me={1} onClick={() => handleAddOpen()} leftIcon={<AddIcon />}>Add New</Button> | |
| 132 | + <Button size='sm' variant='brand' onClick={() => navigate(-1)} leftIcon={<IoIosArrowBack />}> Back</Button> | |
| 133 | + </Flex> | |
| 134 | + {isLoding ? ( | |
| 135 | + <Flex justifyContent={'center'} alignItems={'center'} width="100%" color={textColor} fontSize="sm" fontWeight="700"> | |
| 136 | + <Spinner /> | |
| 137 | + </Flex> | |
| 138 | + ) : ( | |
| 139 | + moduleData && moduleData.length > 0 ? ( | |
| 140 | + <Grid templateColumns="repeat(12, 1fr)" gap={3}> | |
| 141 | + {moduleData && moduleData?.map((item, i) => ( | |
| 142 | + | |
| 143 | + <GridItem rowSpan={2} colSpan={{ base: 12, md: 6, lg: 3 }} key={i}> | |
| 144 | + <Card> | |
| 145 | + <Flex alignItems={"center"} justifyContent={"space-between"}> | |
| 146 | + <Flex> | |
| 147 | + <Checkbox disabled={item.moduleName === 'Properties' || item.moduleName === 'Contacts' || item.moduleName === 'Leads'} colorScheme="brandScheme" value={selectedValues} isChecked={selectedValues.includes(item?._id)} onChange={(event) => handleCheckboxChange(event, item?._id)} me="10px" /> | |
| 148 | + <Tooltip hasArrow label={item?.moduleName} bg='gray.200' color='gray' textTransform={"capitalize"} fontSize='sm'> | |
| 149 | + <Heading size="md" fontWeight={"500"} sx={{ | |
| 150 | + textOverflow: 'ellipsis', whiteSpace: 'nowrap', maxWidth: '6rem', | |
| 151 | + overflow: 'hidden', textTransform: 'capitalize' | |
| 152 | + }} | |
| 153 | + > | |
| 154 | + {item?.moduleName} | |
| 155 | + </Heading> | |
| 156 | + | |
| 157 | + </Tooltip> | |
| 158 | + </Flex> | |
| 159 | + <Flex> | |
| 160 | + <Button size='sm' disabled={item.moduleName === 'Properties' || item.moduleName === 'Contacts' || item.moduleName === 'Leads'} variant='outline' me={2} color={'green'} onClick={() => handleEditOpen(item)}><EditIcon /></Button> | |
| 161 | + <Button size='sm' disabled={item.moduleName === 'Properties' || item.moduleName === 'Contacts' || item.moduleName === 'Leads'} variant='outline' me={2} color={'red'} onClick={() => handleDeleteOpen(item, 'one')}><DeleteIcon /></Button> | |
| 162 | + </Flex> | |
| 163 | + </Flex> | |
| 164 | + </Card> | |
| 165 | + </GridItem> | |
| 166 | + ))} | |
| 167 | + </Grid>) : ( | |
| 168 | + <Card mt='5'> | |
| 169 | + <Text textAlign={'center'} width="100%" color={'gray.500'} fontSize="sm" fontWeight="700"> | |
| 170 | + <DataNotFound /> | |
| 171 | + </Text> | |
| 172 | + </Card> | |
| 173 | + ) | |
| 174 | + )} | |
| 175 | + <Add isOpen={addModal} onClose={handleAddClose} fetchData={fetchData} setAction={setAction} /> | |
| 176 | + <Edit isOpen={editModal} onClose={handleEditClose} selectedId={selectedId} editdata={editdata} setAction={setAction} fetchData={fetchData} /> | |
| 177 | + <CommonDeleteModel isOpen={deleteModal} onClose={() => setDeleteModal(false)} type={'Module'} handleDeleteData={handleDeleteModule} ids={selectedId} selectedValues={selectedValues} /> | |
| 178 | + <View isOpen={viewModal} onClose={handleViewClose} selectedId={selectedId} setAction={setAction} fetchData={fetchData} /> | |
| 179 | + </div> | |
| 180 | + ) | |
| 181 | +} | |
| 182 | + | |
| 183 | +export default Index |
+49-0Client/src/views/admin/moduleName/view.js
| @@ -0,0 +1,49 @@ | ||
| 1 | +import { DeleteIcon, EditIcon } from '@chakra-ui/icons'; | |
| 2 | +import { Box, Button, Flex, Grid, GridItem, Modal, ModalBody, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalOverlay, Text } from '@chakra-ui/react' | |
| 3 | +import Spinner from 'components/spinner/Spinner' | |
| 4 | +import React, { useState } from 'react' | |
| 5 | +import Edit from './Edit'; | |
| 6 | + | |
| 7 | +const View = (props) => { | |
| 8 | + const { onClose, isOpen, selectedId, fetchData, setAction } = props; | |
| 9 | + const [isLoding, setIsLoding] = useState(false) | |
| 10 | + const [data, setData] = useState(false) | |
| 11 | + const [editModal, setEditModal] = useState(false) | |
| 12 | + | |
| 13 | + const handleEditClose = () => { | |
| 14 | + setEditModal(false) | |
| 15 | + } | |
| 16 | + | |
| 17 | + return ( | |
| 18 | + <div> | |
| 19 | + <Modal onClose={onClose} isOpen={isOpen} isCentered size="lg"> | |
| 20 | + <ModalOverlay /> | |
| 21 | + <ModalContent> | |
| 22 | + <ModalHeader textTransform={"capitalize"}>{data?.name} </ModalHeader> | |
| 23 | + <ModalCloseButton /> | |
| 24 | + <ModalBody> | |
| 25 | + <Box> | |
| 26 | + <Grid templateColumns="repeat(12, 1fr)" gap={3}> | |
| 27 | + <GridItem colSpan={{ base: 12 }}> | |
| 28 | + <Flex> | |
| 29 | + <Text fontWeight={"bold"} pr={2} textTransform={"capitalize"}>require :</Text> | |
| 30 | + <Text > | |
| 31 | + {data?.validations && data?.validations?.length > 0 && data?.validations[0]?.require === true ? "True" : "False"} | |
| 32 | + </Text> | |
| 33 | + </Flex> | |
| 34 | + </GridItem> | |
| 35 | + </Grid> | |
| 36 | + </Box> | |
| 37 | + </ModalBody> | |
| 38 | + <ModalFooter> | |
| 39 | + <Button variant="outline" colorScheme='green' size='sm' me={2} onClick={() => { onClose(); setEditModal(true) }} leftIcon={<EditIcon />}>Edit</Button> | |
| 40 | + <Button colorScheme="red" size='sm' mr={2} disabled={isLoding ? true : false} leftIcon={<DeleteIcon />} >{isLoding ? <Spinner /> : 'Delete'}</Button> | |
| 41 | + </ModalFooter> | |
| 42 | + </ModalContent> | |
| 43 | + </Modal> | |
| 44 | + <Edit isOpen={editModal} onClose={handleEditClose} selectedId={props.selectedId} editdata={data} setAction={setAction} fetchData={fetchData} /> | |
| 45 | + </div> | |
| 46 | + ) | |
| 47 | +} | |
| 48 | + | |
| 49 | +export default View |
+445-0Client/src/views/admin/opportunities/AddEdit.js
| @@ -0,0 +1,445 @@ | ||
| 1 | +import { CloseIcon } from '@chakra-ui/icons'; | |
| 2 | +import { Button, Drawer, DrawerBody, DrawerContent, DrawerFooter, DrawerHeader, DrawerOverlay, Flex, FormLabel, Grid, GridItem, IconButton, Input, Select, Text, Textarea } from '@chakra-ui/react'; | |
| 3 | +import Spinner from 'components/spinner/Spinner'; | |
| 4 | +import dayjs from 'dayjs'; | |
| 5 | +import { useFormik } from 'formik'; | |
| 6 | +import { useState, useEffect } from 'react'; | |
| 7 | +import { LiaMousePointerSolid } from 'react-icons/lia'; | |
| 8 | +import { postApi, getApi, putApi } from 'services/api'; | |
| 9 | +import { generateValidationSchema } from 'utils'; | |
| 10 | +import CustomForm from 'utils/customForm'; | |
| 11 | +import * as yup from 'yup' | |
| 12 | +import { opprtunitiesSchema } from '../../../schema/opprtunitiesSchema'; | |
| 13 | +import UserModel from '../../../components/commonTableModel/UserModel'; | |
| 14 | +import { toast } from 'react-toastify'; | |
| 15 | +import { useSelector } from 'react-redux'; | |
| 16 | +import AccountModel from '../../../components/commonTableModel/AccountModel'; | |
| 17 | +import { HasAccess } from '../../../redux/accessUtils'; | |
| 18 | + | |
| 19 | +const AddEdit = (props) => { | |
| 20 | + const { isOpen, size, onClose, type, setAction, selectedId } = props; | |
| 21 | + const [isLoding, setIsLoding] = useState(false) | |
| 22 | + const [userModel, setUserModel] = useState(false) | |
| 23 | + const [accountModel, setAccountModel] = useState(false) | |
| 24 | + const [userData, setUserData] = useState([]); | |
| 25 | + const [opprtunityDetails, setOpportunityDetails] = useState({}); | |
| 26 | + const accountList = useSelector((state) => state?.accountData?.data?.data) | |
| 27 | + const user = JSON.parse(localStorage.getItem("user")) | |
| 28 | + const [accountAccess] = HasAccess(['Account']) | |
| 29 | + | |
| 30 | + const initialValues = { | |
| 31 | + opportunityName: type === "edit" ? opprtunityDetails?.opportunityName : "", | |
| 32 | + accountName: type === "edit" ? opprtunityDetails?.accountName : null, | |
| 33 | + assignUser: type === "edit" ? opprtunityDetails?.assignUser : null, | |
| 34 | + type: type === "edit" ? opprtunityDetails?.type : "", | |
| 35 | + leadSource: type === "edit" ? opprtunityDetails?.leadSource : "", | |
| 36 | + currency: type === "edit" ? opprtunityDetails?.currency : "", | |
| 37 | + opportunityAmount: type === "edit" ? opprtunityDetails?.opportunityAmount : "", | |
| 38 | + amount: type === "edit" ? opprtunityDetails?.amount : "", | |
| 39 | + expectedCloseDate: type === "edit" ? opprtunityDetails?.expectedCloseDate : "", | |
| 40 | + nextStep: type === "edit" ? opprtunityDetails?.nextStep : "", | |
| 41 | + salesStage: type === "edit" ? opprtunityDetails?.salesStage : "", | |
| 42 | + probability: type === "edit" ? opprtunityDetails?.probability : "", | |
| 43 | + description: type === "edit" ? opprtunityDetails?.description : "", | |
| 44 | + createBy: JSON.parse(localStorage.getItem('user'))._id, | |
| 45 | + modifiedBy: JSON.parse(localStorage.getItem('user'))._id | |
| 46 | + }; | |
| 47 | + | |
| 48 | + | |
| 49 | + const addData = async (values) => { | |
| 50 | + try { | |
| 51 | + setIsLoding(true) | |
| 52 | + let response = await postApi('api/opportunity/add', values) | |
| 53 | + if (response.status === 200) { | |
| 54 | + onClose(); | |
| 55 | + toast.success(`Opprtunities Save successfully`) | |
| 56 | + formik.resetForm(); | |
| 57 | + setAction((pre) => !pre) | |
| 58 | + } | |
| 59 | + } catch (e) { | |
| 60 | + console.log(e); | |
| 61 | + toast.error(`server error`) | |
| 62 | + } | |
| 63 | + finally { | |
| 64 | + setIsLoding(false) | |
| 65 | + } | |
| 66 | + }; | |
| 67 | + const editData = async (values) => { | |
| 68 | + try { | |
| 69 | + setIsLoding(true) | |
| 70 | + let response = await putApi(`api/opportunity/edit/${selectedId}`, values) | |
| 71 | + if (response.status === 200) { | |
| 72 | + onClose(); | |
| 73 | + toast.success(`Opprtunities Update successfully`) | |
| 74 | + formik.resetForm(); | |
| 75 | + // setAction((pre) => !pre) | |
| 76 | + } | |
| 77 | + } catch (e) { | |
| 78 | + console.log(e); | |
| 79 | + toast.error(`server error`) | |
| 80 | + } | |
| 81 | + finally { | |
| 82 | + setIsLoding(false) | |
| 83 | + } | |
| 84 | + }; | |
| 85 | + | |
| 86 | + const handleCancel = () => { | |
| 87 | + formik.resetForm(); | |
| 88 | + onClose() | |
| 89 | + } | |
| 90 | + | |
| 91 | + const formik = useFormik({ | |
| 92 | + initialValues: initialValues, | |
| 93 | + validationSchema: opprtunitiesSchema, | |
| 94 | + enableReinitialize: true, | |
| 95 | + onSubmit: (values, { resetForm }) => { | |
| 96 | + if (type === "add") { | |
| 97 | + addData(values) | |
| 98 | + } else { | |
| 99 | + const payload = { ...values, modifiedDate: new Date() } | |
| 100 | + editData(payload) | |
| 101 | + } | |
| 102 | + }, | |
| 103 | + }); | |
| 104 | + | |
| 105 | + const { errors, touched, values, handleBlur, handleChange, handleSubmit, setFieldValue, } = formik | |
| 106 | + console.log(errors) | |
| 107 | + const fetchData = async () => { | |
| 108 | + setIsLoding(true) | |
| 109 | + let result = await getApi('api/user/'); | |
| 110 | + setUserData(result?.data?.user); | |
| 111 | + setIsLoding(false) | |
| 112 | + } | |
| 113 | + | |
| 114 | + | |
| 115 | + const fetchTaskData = async () => { | |
| 116 | + if (type === "edit") { | |
| 117 | + try { | |
| 118 | + setIsLoding(true) | |
| 119 | + let result = await getApi('api/opportunity/view/', selectedId) | |
| 120 | + if (result?.status === 200) { | |
| 121 | + setOpportunityDetails(result?.data) | |
| 122 | + } | |
| 123 | + | |
| 124 | + } | |
| 125 | + catch (e) { | |
| 126 | + console.log(e); | |
| 127 | + } | |
| 128 | + finally { | |
| 129 | + setIsLoding(false) | |
| 130 | + } | |
| 131 | + } | |
| 132 | + } | |
| 133 | + | |
| 134 | + useEffect(() => { | |
| 135 | + if (user.role === 'superAdmin') fetchData(); | |
| 136 | + }, []) | |
| 137 | + | |
| 138 | + | |
| 139 | + useEffect(() => { | |
| 140 | + if (type === "edit") fetchTaskData(); | |
| 141 | + }, [type, selectedId]) | |
| 142 | + | |
| 143 | + | |
| 144 | + return ( | |
| 145 | + <div> | |
| 146 | + {userModel && <UserModel onClose={() => setUserModel(false)} isOpen={userModel} fieldName={"assignUser"} setFieldValue={setFieldValue} data={userData} isLoding={isLoding} setIsLoding={setIsLoding} />} | |
| 147 | + {accountModel && <AccountModel onClose={() => setAccountModel(false)} isOpen={accountModel} fieldName={"accountName"} setFieldValue={setFieldValue} data={accountList} />} | |
| 148 | + | |
| 149 | + <Drawer isOpen={isOpen} size={size}> | |
| 150 | + <DrawerOverlay /> | |
| 151 | + <DrawerContent> | |
| 152 | + <DrawerHeader alignItems={"center"} justifyContent='space-between' display='flex' > | |
| 153 | + {type === "add" ? "Add" : "Edit"} Opportunities | |
| 154 | + <IconButton onClick={() => handleCancel()} icon={<CloseIcon />} /> | |
| 155 | + </DrawerHeader> | |
| 156 | + <DrawerBody> | |
| 157 | + <Grid templateColumns="repeat(12, 1fr)" gap={3}> | |
| 158 | + <GridItem colSpan={{ base: 12, md: 6 }}> | |
| 159 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='8px'> | |
| 160 | + Opportunity Name<Text color={"red"}>*</Text> | |
| 161 | + </FormLabel> | |
| 162 | + <Input | |
| 163 | + fontSize='sm' | |
| 164 | + value={values.opportunityName} | |
| 165 | + name="opportunityName" | |
| 166 | + onBlur={handleBlur} | |
| 167 | + onChange={handleChange} | |
| 168 | + placeholder='Opportunity Name' | |
| 169 | + fontWeight='500' | |
| 170 | + borderColor={errors.opportunityName && touched.opportunityName ? "red.300" : null} | |
| 171 | + /> | |
| 172 | + <Text mb='10px' fontSize='sm' color={'red'}> {errors.opportunityName && touched.opportunityName && errors.opportunityName}</Text> | |
| 173 | + </GridItem> | |
| 174 | + { | |
| 175 | + (user.role === 'superAdmin' || accountAccess?.view) && | |
| 176 | + <GridItem colSpan={{ base: 12, md: 6 }}> | |
| 177 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='8px'> | |
| 178 | + Account Name | |
| 179 | + </FormLabel> | |
| 180 | + <Flex justifyContent={'space-between'}> | |
| 181 | + <Select | |
| 182 | + value={values.accountName} | |
| 183 | + name="accountName" | |
| 184 | + onBlur={handleBlur} | |
| 185 | + onChange={handleChange} | |
| 186 | + mb={errors.accountName && touched.accountName ? undefined : '10px'} | |
| 187 | + fontWeight='500' | |
| 188 | + placeholder={'Account Name'} | |
| 189 | + borderColor={errors.accountName && touched.accountName ? "red.300" : null} | |
| 190 | + > | |
| 191 | + {accountList?.length > 0 && accountList?.map((item) => { | |
| 192 | + return <option value={item._id} key={item._id}>{`${item?.name}`}</option> | |
| 193 | + })} | |
| 194 | + </Select> | |
| 195 | + <IconButton onClick={() => setAccountModel(true)} ml={2} fontSize='25px' icon={<LiaMousePointerSolid />} /> | |
| 196 | + </Flex> | |
| 197 | + <Text mb='10px' fontSize='sm' color={'red'}> {errors.accountName && touched.accountName && errors.accountName}</Text> | |
| 198 | + </GridItem> | |
| 199 | + } | |
| 200 | + { | |
| 201 | + user.role === 'superAdmin' && | |
| 202 | + <GridItem colSpan={{ base: 12, md: 6 }} > | |
| 203 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='8px'> | |
| 204 | + Assigned User | |
| 205 | + </FormLabel> | |
| 206 | + <Flex justifyContent={'space-between'}> | |
| 207 | + <Select | |
| 208 | + value={values.assignUser} | |
| 209 | + name="assignUser" | |
| 210 | + onBlur={handleBlur} | |
| 211 | + onChange={handleChange} | |
| 212 | + mb={errors.assignUser && touched.assignUser ? undefined : '10px'} | |
| 213 | + fontWeight='500' | |
| 214 | + placeholder={'Assign To'} | |
| 215 | + borderColor={errors.assignUser && touched.assignUser ? "red.300" : null} | |
| 216 | + > | |
| 217 | + {userData?.map((item) => { | |
| 218 | + return <option value={item._id} key={item._id}>{`${item?.firstName} ${item?.lastName}`}</option> | |
| 219 | + })} | |
| 220 | + </Select> | |
| 221 | + <IconButton onClick={() => setUserModel(true)} ml={2} fontSize='25px' icon={<LiaMousePointerSolid />} /> | |
| 222 | + </Flex> | |
| 223 | + </GridItem> | |
| 224 | + } | |
| 225 | + <GridItem colSpan={{ base: 12, md: 6 }}> | |
| 226 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='8px'> | |
| 227 | + Type | |
| 228 | + </FormLabel> | |
| 229 | + <Select | |
| 230 | + value={values.type} | |
| 231 | + name="type" | |
| 232 | + onBlur={handleBlur} | |
| 233 | + onChange={handleChange} | |
| 234 | + mb={errors.type && touched.type ? undefined : '10px'} | |
| 235 | + fontWeight='500' | |
| 236 | + placeholder={'Select Type'} | |
| 237 | + borderColor={errors.type && touched.type ? "red.300" : null} | |
| 238 | + > | |
| 239 | + <option value={"Existing Bussiness"} >Existing Bussiness</option> | |
| 240 | + <option value={"New Bussiness"} >New Bussiness</option> | |
| 241 | + </Select> | |
| 242 | + <Text mb='10px' fontSize='sm' color={'red'}> {errors.type && touched.type && errors.type}</Text> | |
| 243 | + </GridItem> | |
| 244 | + <GridItem colSpan={{ base: 12, md: 6 }}> | |
| 245 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='8px'> | |
| 246 | + Lead Source | |
| 247 | + </FormLabel> | |
| 248 | + <Select | |
| 249 | + value={values.leadSource} | |
| 250 | + name="leadSource" | |
| 251 | + onBlur={handleBlur} | |
| 252 | + onChange={handleChange} | |
| 253 | + mb={errors.leadSource && touched.leadSource ? undefined : '10px'} | |
| 254 | + fontWeight='500' | |
| 255 | + placeholder={'Select Lead Source'} | |
| 256 | + borderColor={errors.leadSource && touched.leadSource ? "red.300" : null} | |
| 257 | + > | |
| 258 | + <option value={"Cold Call"}>Cold Call</option> | |
| 259 | + <option value={"Existing Customer"}>Existing Customer</option> | |
| 260 | + <option value={"Self Generated"}>Self Generated</option> | |
| 261 | + <option value={"Employee"}>Employee</option> | |
| 262 | + <option value={"Partner"}>Partner</option> | |
| 263 | + <option value={"Public Relation"}>Public Relation</option> | |
| 264 | + <option value={"Direct Mail"}>Direct Mail</option> | |
| 265 | + <option value={"Conference"}>Conference</option> | |
| 266 | + <option value={"Trade Show"}>Trade Show</option> | |
| 267 | + <option value={"Web Site"}>Web Site</option> | |
| 268 | + <option value={"Word Of Mouth"}>Word Of Mouth</option> | |
| 269 | + <option value={"Email"}>Email</option> | |
| 270 | + <option value={"Other"}>Other</option> | |
| 271 | + </Select> | |
| 272 | + <Text mb='10px' fontSize='sm' color={'red'}> {errors.leadSource && touched.leadSource && errors.leadSource}</Text> | |
| 273 | + </GridItem> | |
| 274 | + <GridItem colSpan={{ base: 12, md: 6 }}> | |
| 275 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='8px'> | |
| 276 | + Currency | |
| 277 | + </FormLabel> | |
| 278 | + <Select | |
| 279 | + value={values.currency} | |
| 280 | + name="currency" | |
| 281 | + onBlur={handleBlur} | |
| 282 | + onChange={handleChange} | |
| 283 | + mb={errors.currency && touched.currency ? undefined : '10px'} | |
| 284 | + fontWeight='500' | |
| 285 | + placeholder={'Select Currency'} | |
| 286 | + borderColor={errors.currency && touched.currency ? "red.300" : null} | |
| 287 | + > | |
| 288 | + <option value={"$"}>USD</option> | |
| 289 | + </Select> | |
| 290 | + <Text mb='10px' fontSize='sm' color={'red'}> {errors.currency && touched.currency && errors.currency}</Text> | |
| 291 | + </GridItem> | |
| 292 | + <GridItem colSpan={{ base: 12, md: 6 }}> | |
| 293 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='8px'> | |
| 294 | + Opportunity Amount<Text color={"red"}>*</Text> | |
| 295 | + </FormLabel> | |
| 296 | + <Input | |
| 297 | + fontSize='sm' | |
| 298 | + value={values.opportunityAmount} | |
| 299 | + name="opportunityAmount" | |
| 300 | + type='number' | |
| 301 | + onBlur={handleBlur} | |
| 302 | + onChange={handleChange} | |
| 303 | + placeholder='Opportunity Amount' | |
| 304 | + fontWeight='500' | |
| 305 | + borderColor={errors.opportunityAmount && touched.opportunityAmount ? "red.300" : null} | |
| 306 | + /> | |
| 307 | + <Text mb='10px' fontSize='sm' color={'red'}> {errors.opportunityAmount && touched.opportunityAmount && errors.opportunityAmount}</Text> | |
| 308 | + </GridItem> | |
| 309 | + | |
| 310 | + <GridItem colSpan={{ base: 12, md: 6 }}> | |
| 311 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='8px'> | |
| 312 | + Amount | |
| 313 | + </FormLabel> | |
| 314 | + <Input | |
| 315 | + fontSize='sm' | |
| 316 | + value={values.amount} | |
| 317 | + name="amount" | |
| 318 | + onBlur={handleBlur} | |
| 319 | + onChange={handleChange} | |
| 320 | + type='number' | |
| 321 | + placeholder='Amount' | |
| 322 | + fontWeight='500' | |
| 323 | + borderColor={errors.amount && touched.amount ? "red.300" : null} | |
| 324 | + /> | |
| 325 | + <Text mb='10px' fontSize='sm' color={'red'}> {errors.amount && touched.amount && errors.amount}</Text> | |
| 326 | + </GridItem> | |
| 327 | + <GridItem colSpan={{ base: 12, md: 6 }} > | |
| 328 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='8px'> | |
| 329 | + Expected Close Date<Text color={"red"}>*</Text> | |
| 330 | + </FormLabel> | |
| 331 | + <Input | |
| 332 | + type="date" | |
| 333 | + fontSize='sm' | |
| 334 | + onChange={handleChange} | |
| 335 | + onBlur={handleBlur} | |
| 336 | + value={dayjs(values.expectedCloseDate).format("YYYY-MM-DD")} | |
| 337 | + name="expectedCloseDate" | |
| 338 | + fontWeight='500' | |
| 339 | + borderColor={errors?.expectedCloseDate && touched?.expectedCloseDate ? "red.300" : null} | |
| 340 | + /> | |
| 341 | + <Text fontSize='sm' mb='10px' color={'red'}> {errors.expectedCloseDate && touched.expectedCloseDate && errors.expectedCloseDate}</Text> | |
| 342 | + </GridItem> | |
| 343 | + | |
| 344 | + <GridItem colSpan={{ base: 12, md: 6 }}> | |
| 345 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='8px'> | |
| 346 | + Next Step | |
| 347 | + </FormLabel> | |
| 348 | + <Input | |
| 349 | + fontSize='sm' | |
| 350 | + onChange={handleChange} | |
| 351 | + onBlur={handleBlur} | |
| 352 | + value={values.nextStep} | |
| 353 | + name="nextStep" | |
| 354 | + placeholder='Next Step' | |
| 355 | + fontWeight='500' | |
| 356 | + borderColor={errors.nextStep && touched.nextStep ? "red.300" : null} | |
| 357 | + /> | |
| 358 | + <Text fontSize='sm' mb='10px' color={'red'}> {errors.nextStep && touched.nextStep && errors.nextStep}</Text> | |
| 359 | + </GridItem> | |
| 360 | + <GridItem colSpan={{ base: 12, md: 6 }}> | |
| 361 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='8px'> | |
| 362 | + Sales Stage<Text color={"red"}>*</Text> | |
| 363 | + </FormLabel> | |
| 364 | + <Select | |
| 365 | + value={values.salesStage} | |
| 366 | + name="salesStage" | |
| 367 | + onBlur={handleBlur} | |
| 368 | + onChange={handleChange} | |
| 369 | + mb={errors.salesStage && touched.salesStage ? undefined : '10px'} | |
| 370 | + fontWeight='500' | |
| 371 | + placeholder={'Select Sales Stage'} | |
| 372 | + borderColor={errors.salesStage && touched.salesStage ? "red.300" : null} | |
| 373 | + > | |
| 374 | + <option value={"Prospecting"}>Prospecting</option> | |
| 375 | + <option value={"Qualification"}>Qualification</option> | |
| 376 | + <option value={"Needs Analysis"}>Needs Analysis</option> | |
| 377 | + <option value={"Value Propositon"}>Value Propositon</option> | |
| 378 | + <option value={"Identifying Decision Makers"}>Identifying Decision Makers</option> | |
| 379 | + <option value={"Perception Analysis"}>Perception Analysis</option> | |
| 380 | + <option value={"Proposal/Price Quote"}>Proposal/Price Quote</option> | |
| 381 | + <option value={"Negotiation/Review"}>Negotiation/Review</option> | |
| 382 | + <option value={"Closed/Won"}>Closed/Won</option> | |
| 383 | + <option value={"Closed/Lost"}>Closed/Lost</option> | |
| 384 | + </Select> | |
| 385 | + <Text fontSize='sm' mb='10px' color={'red'}> {errors.salesStage && touched.salesStage && errors.salesStage}</Text> | |
| 386 | + </GridItem> | |
| 387 | + <GridItem colSpan={{ base: 12, md: 6 }}> | |
| 388 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='8px'> | |
| 389 | + Probability | |
| 390 | + </FormLabel> | |
| 391 | + <Input | |
| 392 | + fontSize='sm' | |
| 393 | + onChange={handleChange} | |
| 394 | + onBlur={handleBlur} | |
| 395 | + value={values.probability} | |
| 396 | + name="probability" | |
| 397 | + placeholder='Probability' | |
| 398 | + type='number' | |
| 399 | + fontWeight='500' | |
| 400 | + borderColor={errors.probability && touched.probability ? "red.300" : null} | |
| 401 | + /> | |
| 402 | + <Text fontSize='sm' mb='10px' color={'red'}> {errors.probability && touched.probability && errors.probability}</Text> | |
| 403 | + </GridItem> | |
| 404 | + <GridItem colSpan={{ base: 12, md: 6 }}> | |
| 405 | + <FormLabel display='flex' ms='4px' fontSize='sm' fontWeight='500' mb='8px'> | |
| 406 | + Description | |
| 407 | + </FormLabel> | |
| 408 | + <Textarea | |
| 409 | + fontSize='sm' | |
| 410 | + value={values.description} | |
| 411 | + name="description" | |
| 412 | + resize={"none"} | |
| 413 | + onBlur={handleBlur} | |
| 414 | + onChange={handleChange} | |
| 415 | + placeholder='Description' | |
| 416 | + fontWeight='500' | |
| 417 | + borderColor={errors.description && touched.description ? "red.300" : null} | |
| 418 | + /> | |
| 419 | + <Text mb='10px' fontSize='sm' color={'red'}> {errors.description && touched.description && errors.description}</Text> | |
| 420 | + </GridItem> | |
| 421 | + </Grid> | |
| 422 | + </DrawerBody> | |
| 423 | + <DrawerFooter> | |
| 424 | + <Button sx={{ textTransform: "capitalize" }} size="sm" disabled={isLoding ? true : false} variant="brand" type="submit" onClick={handleSubmit} > | |
| 425 | + {isLoding ? <Spinner /> : 'Save'} | |
| 426 | + </Button> | |
| 427 | + <Button | |
| 428 | + variant="outline" | |
| 429 | + colorScheme='red' size="sm" | |
| 430 | + sx={{ | |
| 431 | + marginLeft: 2, | |
| 432 | + textTransform: "capitalize", | |
| 433 | + }} | |
| 434 | + onClick={handleCancel} | |
| 435 | + > | |
| 436 | + Close | |
| 437 | + </Button> | |
| 438 | + </DrawerFooter> | |
| 439 | + </DrawerContent> | |
| 440 | + </Drawer> | |
| 441 | + </div> | |
| 442 | + ) | |
| 443 | +} | |
| 444 | + | |
| 445 | +export default AddEdit |
+477-0Client/src/views/admin/opportunities/View.js
| @@ -0,0 +1,553 @@ | ||
| 1 | +import { Button, Grid, GridItem, Flex, IconButton, Text, Menu, MenuButton, MenuDivider, MenuItem, MenuList, useDisclosure, Box, Heading, Input, Select } from '@chakra-ui/react' | |
| 2 | +import { AddIcon, ChevronDownIcon, DeleteIcon, EditIcon } from "@chakra-ui/icons"; | |
| 3 | +import React from 'react' | |
| 4 | +import moment from 'moment' | |
| 5 | +import { Link, useNavigate, useParams } from 'react-router-dom' | |
| 6 | +import { BiLink } from 'react-icons/bi' | |
| 7 | +import { useEffect } from 'react' | |
| 8 | +import { useState } from 'react' | |
| 9 | +import Card from 'components/card/Card' | |
| 10 | +import { IoIosArrowBack } from "react-icons/io"; | |
| 11 | +import { HasAccess } from '../../../redux/accessUtils'; | |
| 12 | +import { HSeparator } from 'components/separator/Separator'; | |
| 13 | +import AddEdit from './AddEdit'; | |
| 14 | +import CommonDeleteModel from 'components/commonDeleteModel'; | |
| 15 | +import { deleteManyApi, putApi, getApi } from '../../../services/api'; | |
| 16 | +import { FaFilePdf } from "react-icons/fa"; | |
| 17 | +import html2pdf from "html2pdf.js"; | |
| 18 | +import { opprtunitiesSchema } from '../../../schema/opprtunitiesSchema'; | |
| 19 | +import { useFormik } from 'formik'; | |
| 20 | +import dayjs from 'dayjs'; | |
| 21 | +import { toast } from 'react-toastify'; | |
| 22 | + | |
| 23 | +const View = (props) => { | |
| 24 | + const params = useParams() | |
| 25 | + const { id } = params | |
| 26 | + const user = JSON.parse(localStorage.getItem("user")) | |
| 27 | + | |
| 28 | + const [opportunityAccess, accountAccess] = HasAccess(['Opportunities', 'Account']) | |
| 29 | + | |
| 30 | + const [data, setData] = useState() | |
| 31 | + const { onOpen, onClose } = useDisclosure() | |
| 32 | + const [edit, setEdit] = useState(false); | |
| 33 | + const [deleteModel, setDelete] = useState(false); | |
| 34 | + const [deleteManyModel, setDeleteManyModel] = useState(false); | |
| 35 | + const [loading, setLoading] = useState(false) | |
| 36 | + const navigate = useNavigate() | |
| 37 | + const [type, setType] = useState("") | |
| 38 | + const [editableField, setEditableField] = useState(null); | |
| 39 | + const [editableFieldName, setEditableFieldName] = useState(null); | |
| 40 | + const today = new Date().toISOString().split('T')[0]; | |
| 41 | + const todayTime = new Date().toISOString().split('.')[0]; | |
| 42 | + const fetchViewData = async () => { | |
| 43 | + if (id) { | |
| 44 | + let result = await getApi('api/opportunity/view/', id); | |
| 45 | + setData(result?.data); | |
| 46 | + } | |
| 47 | + } | |
| 48 | + const generatePDF = () => { | |
| 49 | + setLoading(true) | |
| 50 | + const element = document.getElementById("reports"); | |
| 51 | + const hideBtn = document.getElementById("hide-btn"); | |
| 52 | + | |
| 53 | + if (element) { | |
| 54 | + hideBtn.style.display = 'none'; | |
| 55 | + html2pdf() | |
| 56 | + .from(element) | |
| 57 | + .set({ | |
| 58 | + margin: [0, 0, 0, 0], | |
| 59 | + filename: `Opportunities_Details_${moment().format("DD-MM-YYYY")}.pdf`, | |
| 60 | + image: { type: "jpeg", quality: 0.98 }, | |
| 61 | + html2canvas: { scale: 2, useCORS: true, allowTaint: true }, | |
| 62 | + jsPDF: { unit: "in", format: "letter", orientation: "portrait" }, | |
| 63 | + }) | |
| 64 | + .save().then(() => { | |
| 65 | + setLoading(false) | |
| 66 | + hideBtn.style.display = ''; | |
| 67 | + }) | |
| 68 | + // }, 500); | |
| 69 | + } else { | |
| 70 | + console.error("Element with ID 'reports' not found."); | |
| 71 | + setLoading(false) | |
| 72 | + } | |
| 73 | + }; | |
| 74 | + const handleDeleteOpportunities = async (ids) => { | |
| 75 | + try { | |
| 76 | + let response = await deleteManyApi('api/opportunities/deleteMany', ids) | |
| 77 | + if (response.status === 200) { | |
| 78 | + navigate('/opportunities') | |
| 79 | + toast.success(`Opprtunities Delete successfully`) | |
| 80 | + setDeleteManyModel(false) | |
| 81 | + } | |
| 82 | + } catch (error) { | |
| 83 | + console.log(error) | |
| 84 | + toast.error(`server error`) | |
| 85 | + | |
| 86 | + } | |
| 87 | + | |
| 88 | + } | |
| 89 | + | |
| 90 | + const initialValues = { | |
| 91 | + opportunityName: data?.opportunityName, | |
| 92 | + accountName: data?.accountName, | |
| 93 | + assignUser: data?.assignUser, | |
| 94 | + type: data?.type, | |
| 95 | + leadSource: data?.leadSource, | |
| 96 | + currency: data?.currency, | |
| 97 | + opportunityAmount: data?.opportunityAmount, | |
| 98 | + amount: data?.amount, | |
| 99 | + expectedCloseDate: data?.expectedCloseDate, | |
| 100 | + nextStep: data?.nextStep, | |
| 101 | + salesStage: data?.salesStage, | |
| 102 | + probability: data?.probability, | |
| 103 | + description: data?.description, | |
| 104 | + modifiedBy: JSON.parse(localStorage.getItem('user'))._id | |
| 105 | + } | |
| 106 | + | |
| 107 | + const formik = useFormik({ | |
| 108 | + initialValues: initialValues, | |
| 109 | + validationSchema: opprtunitiesSchema, | |
| 110 | + enableReinitialize: true, | |
| 111 | + onSubmit: async (values, { resetForm }) => { | |
| 112 | + setEditableField(null); | |
| 113 | + const payload = { | |
| 114 | + ...values, | |
| 115 | + modifiedDate: new Date() | |
| 116 | + } | |
| 117 | + let response = await putApi(`api/opportunity/edit/${id}`, payload) | |
| 118 | + if (response.status === 200) { | |
| 119 | + fetchViewData() | |
| 120 | + toast.success(`${editableFieldName} Update successfully`) | |
| 121 | + } else { | |
| 122 | + toast.error(`${editableFieldName} not Update`) | |
| 123 | + } | |
| 124 | + }, | |
| 125 | + }); | |
| 126 | + const handleDoubleClick = (fieldName, value, name) => { | |
| 127 | + formik.setFieldValue(fieldName, value); | |
| 128 | + setEditableField(fieldName) | |
| 129 | + setEditableFieldName(name) | |
| 130 | + }; | |
| 131 | + | |
| 132 | + const handleBlur = (e) => { | |
| 133 | + formik.handleSubmit(); | |
| 134 | + }; | |
| 135 | + useEffect(() => { | |
| 136 | + fetchViewData() | |
| 137 | + }, [id, edit]) | |
| 138 | + | |
| 139 | + return ( | |
| 140 | + <div> | |
| 141 | + <Grid templateColumns="repeat(4, 1fr)" gap={3} id="reports"> | |
| 142 | + <GridItem colSpan={{ base: 4 }}> | |
| 143 | + <Heading size="lg" m={3}> | |
| 144 | + {data?.opportunityName || ""} | |
| 145 | + </Heading> | |
| 146 | + | |
| 147 | + </GridItem> | |
| 148 | + <GridItem colSpan={{ base: 4 }}> | |
| 149 | + | |
| 150 | + <Card > | |
| 151 | + <Grid gap={4}> | |
| 152 | + <GridItem colSpan={2}> | |
| 153 | + <Box> | |
| 154 | + <Box display={"flex"} justifyContent={"space-between"} > | |
| 155 | + <Heading size="md" mb={3}> | |
| 156 | + Opportunities Details | |
| 157 | + </Heading> | |
| 158 | + <Flex id="hide-btn" > | |
| 159 | + <Menu> | |
| 160 | + {(user.role === 'superAdmin' || opportunityAccess?.create || opportunityAccess?.update || opportunityAccess?.delete) && <MenuButton variant="outline" colorScheme='blackAlpha' size="sm" va mr={2.5} as={Button} rightIcon={<ChevronDownIcon />}> | |
| 161 | + Actions | |
| 162 | + </MenuButton>} | |
| 163 | + <MenuDivider /> | |
| 164 | + <MenuList minWidth={2}> | |
| 165 | + {(user.role === 'superAdmin' || opportunityAccess?.create) && <MenuItem onClick={() => { setEdit(true); setType("add"); formik.resetForm() } | |
| 166 | + } alignItems={'start'} color={'blue'} icon={<AddIcon />}>Add</MenuItem>} | |
| 167 | + {(user.role === 'superAdmin' || opportunityAccess?.update) && <MenuItem onClick={() => { setEdit(true); setType("edit") }} alignItems={'start'} icon={<EditIcon />}>Edit</MenuItem>} | |
| 168 | + <MenuItem onClick={generatePDF} alignItems={"start"} icon={<FaFilePdf />} display={"flex"} style={{ alignItems: "center" }}>Print as PDF</MenuItem > | |
| 169 | + | |
| 170 | + {(user.role === 'superAdmin' || opportunityAccess?.delete) && <> | |
| 171 | + <MenuDivider /> | |
| 172 | + <MenuItem alignItems={'start'} onClick={() => setDeleteManyModel(true)} color={'red'} icon={<DeleteIcon />}>Delete</MenuItem> | |
| 173 | + </>} | |
| 174 | + </MenuList> | |
| 175 | + </Menu> | |
| 176 | + <Button leftIcon={<IoIosArrowBack />} size='sm' variant="brand" onClick={() => navigate(-1)} > | |
| 177 | + Back | |
| 178 | + </Button> | |
| 179 | + </Flex> | |
| 180 | + </Box> | |
| 181 | + <HSeparator /> | |
| 182 | + </Box> | |
| 183 | + </GridItem> | |
| 184 | + <GridItem colSpan={{ base: 2, md: 1 }} > | |
| 185 | + <Text fontSize="sm" fontWeight="bold" color={'blackAlpha.900'}> Opportunity Name </Text> | |
| 186 | + { | |
| 187 | + editableField === "opportunityName" ? | |
| 188 | + <> | |
| 189 | + <Input | |
| 190 | + id="text" | |
| 191 | + name="opportunityName" | |
| 192 | + type="text" | |
| 193 | + onChange={formik.handleChange} | |
| 194 | + onBlur={handleBlur} | |
| 195 | + value={formik.values.opportunityName} | |
| 196 | + borderColor={formik?.errors?.opportunityName && formik?.touched?.opportunityName ? "red.300" : null} | |
| 197 | + autoFocus | |
| 198 | + /> | |
| 199 | + <Text mb='10px' color={'red'}> {formik?.errors.opportunityName && formik?.touched.opportunityName && formik?.errors.opportunityName}</Text> | |
| 200 | + </> | |
| 201 | + : | |
| 202 | + <Text onDoubleClick={() => handleDoubleClick("opportunityName", data?.opportunityName, "Opportunity Name")}>{data?.opportunityName ? data?.opportunityName : ' - '}</Text> | |
| 203 | + } | |
| 204 | + </GridItem> | |
| 205 | + <GridItem colSpan={{ base: 2, md: 1 }} > | |
| 206 | + <Text fontSize="sm" fontWeight="bold" color={'blackAlpha.900'}> Account Name </Text> | |
| 207 | + { | |
| 208 | + data?.accountName ? | |
| 209 | + <Link to={accountAccess?.view && `/accountView/${data?.accountName}`}> | |
| 210 | + <Text color={accountAccess?.view ? 'blue.500' : 'blackAlpha.900'} sx={{ '&:hover': { color: accountAccess?.view ? 'blue.500' : 'blackAlpha.900', textDecoration: accountAccess?.view ? 'underline' : 'none' } }} style={{ cursor: "pointer" }}>{data?.accountName2 ? data?.accountName2 : ' - '}</Text> | |
| 211 | + </Link> | |
| 212 | + : | |
| 213 | + <Text color={accountAccess?.view ? 'blue.500' : 'blackAlpha.900'} sx={{ '&:hover': { color: accountAccess?.view ? 'blue.500' : 'blackAlpha.900', textDecoration: accountAccess?.view ? 'underline' : 'none' } }}>{data?.accountName2 ? data?.accountName2 : ' - '}</Text> | |
| 214 | + | |
| 215 | + } | |
| 216 | + </GridItem> | |
| 217 | + <GridItem colSpan={{ base: 2, md: 1 }} > | |
| 218 | + <Text fontSize="sm" fontWeight="bold" color={'blackAlpha.900'}> Assigned User </Text> | |
| 219 | + { | |
| 220 | + data?.assignUser ? | |
| 221 | + <Link to={user.role === 'superAdmin' && `/userView/${data?.assignUser}`}> | |
| 222 | + <Text color={user.role === 'superAdmin' ? 'blue.500' : 'blackAlpha.900'} sx={{ '&:hover': { color: user.role === 'superAdmin' ? 'blue.500' : 'blackAlpha.900', textDecoration: user.role === 'superAdmin' ? 'underline' : 'none' } }} style={{ cursor: "pointer" }}>{data?.assignUserName ? data?.assignUserName : ' - '}</Text> | |
| 223 | + </Link> | |
| 224 | + : | |
| 225 | + <Text color={user.role === 'superAdmin' ? 'blue.500' : 'blackAlpha.900'} sx={{ '&:hover': { color: user.role === 'superAdmin' ? 'blue.500' : 'blackAlpha.900', textDecoration: user.role === 'superAdmin' ? 'underline' : 'none' } }}>{data?.assignUserName ? data?.assignUserName : ' - '}</Text> | |
| 226 | + | |
| 227 | + } | |
| 228 | + </GridItem> | |
| 229 | + <GridItem colSpan={{ base: 2, md: 1 }} > | |
| 230 | + <Text fontSize="sm" fontWeight="bold" color={'blackAlpha.900'}>Type</Text> | |
| 231 | + { | |
| 232 | + editableField === "type" ? | |
| 233 | + <> | |
| 234 | + <Select | |
| 235 | + value={formik?.values.type} | |
| 236 | + name="type" | |
| 237 | + onChange={formik.handleChange} | |
| 238 | + onBlur={handleBlur} | |
| 239 | + mb={formik?.errors.type && formik?.touched.type ? undefined : '10px'} | |
| 240 | + fontWeight='500' | |
| 241 | + placeholder={'Type'} | |
| 242 | + borderColor={formik?.errors.type && formik?.touched.type ? "red.300" : null} | |
| 243 | + > | |
| 244 | + <option value={"Existing Bussiness"} >Existing Bussiness</option> | |
| 245 | + <option value={"New Bussiness"} >New Bussiness</option> | |
| 246 | + </Select> | |
| 247 | + <Text mb='10px' color={'red'}> {formik?.errors.type && formik?.touched.type && formik?.errors.type}</Text> | |
| 248 | + | |
| 249 | + </> | |
| 250 | + : | |
| 251 | + <Text onDoubleClick={() => handleDoubleClick("type", data?.type, "Type")}>{data?.type ? data?.type : ' - '}</Text> | |
| 252 | + } | |
| 253 | + </GridItem> | |
| 254 | + <GridItem colSpan={{ base: 2, md: 1 }} > | |
| 255 | + <Text fontSize="sm" fontWeight="bold" color={'blackAlpha.900'}> Lead Source</Text> | |
| 256 | + { | |
| 257 | + editableField === "leadSource" ? | |
| 258 | + <> | |
| 259 | + <Select | |
| 260 | + value={formik?.values.leadSource} | |
| 261 | + name="leadSource" | |
| 262 | + onChange={formik?.handleChange} | |
| 263 | + onBlur={handleBlur} | |
| 264 | + mb={formik?.errors.leadSource && formik?.touched.leadSource ? undefined : '10px'} | |
| 265 | + fontWeight='500' | |
| 266 | + placeholder={'Lead Source'} | |
| 267 | + borderColor={formik?.errors.leadSource && formik?.touched.leadSource ? "red.300" : null} | |
| 268 | + > | |
| 269 | + <option value={"Cold Call"}>Cold Call</option> | |
| 270 | + <option value={"Existing Customer"}>Existing Customer</option> | |
| 271 | + <option value={"Self Generated"}>Self Generated</option> | |
| 272 | + <option value={"Employee"}>Employee</option> | |
| 273 | + <option value={"Partner"}>Partner</option> | |
| 274 | + <option value={"Public Relation"}>Public Relation</option> | |
| 275 | + <option value={"Direct Mail"}>Direct Mail</option> | |
| 276 | + <option value={"Conference"}>Conference</option> | |
| 277 | + <option value={"Trade Show"}>Trade Show</option> | |
| 278 | + <option value={"Web Site"}>Web Site</option> | |
| 279 | + <option value={"Word Of Mouth"}>Word Of Mouth</option> | |
| 280 | + <option value={"Email"}>Email</option> | |
| 281 | + <option value={"Other"}>Other</option> | |
| 282 | + </Select> | |
| 283 | + <Text mb='10px' color={'red'}> {formik.errors.leadSource && formik.touched.leadSource && formik.errors.leadSource}</Text> | |
| 284 | + </> | |
| 285 | + | |
| 286 | + : | |
| 287 | + <Text onDoubleClick={() => handleDoubleClick("leadSource", data?.leadSource, "Lead Source")}>{data?.leadSource ? data?.leadSource : ' - '}</Text> | |
| 288 | + } | |
| 289 | + </GridItem> | |
| 290 | + <GridItem colSpan={{ base: 2, md: 1 }} > | |
| 291 | + <Text fontSize="sm" fontWeight="bold" color={'blackAlpha.900'}>Currency</Text> | |
| 292 | + { | |
| 293 | + editableField === "currency" ? | |
| 294 | + <> | |
| 295 | + <Select | |
| 296 | + value={formik?.values.currency} | |
| 297 | + name="currency" | |
| 298 | + onChange={formik?.handleChange} | |
| 299 | + onBlur={handleBlur} | |
| 300 | + mb={formik?.errors.currency && formik?.touched.currency ? undefined : '10px'} | |
| 301 | + fontWeight='500' | |
| 302 | + placeholder={'Select Currency'} | |
| 303 | + borderColor={formik?.errors.currency && formik?.touched.currency ? "red.300" : null} | |
| 304 | + > | |
| 305 | + <option value={"$"}>USD</option> | |
| 306 | + </Select> | |
| 307 | + <Text mb='10px' color={'red'}> {formik?.errors.currency && formik?.touched.currency && formik?.errors.currency}</Text> | |
| 308 | + </> | |
| 309 | + : | |
| 310 | + <Text onDoubleClick={() => handleDoubleClick("currency", data?.currency, "Currency")}>{data?.currency ? data?.currency : ' - '}</Text> | |
| 311 | + } | |
| 312 | + </GridItem> | |
| 313 | + <GridItem colSpan={{ base: 2, md: 1 }} > | |
| 314 | + <Text fontSize="sm" fontWeight="bold" color={'blackAlpha.900'}> Opportunity Amount</Text> | |
| 315 | + { | |
| 316 | + editableField === "opportunityAmount" ? | |
| 317 | + <> | |
| 318 | + <Input | |
| 319 | + id="text" | |
| 320 | + name="opportunityAmount" | |
| 321 | + type="number" | |
| 322 | + onChange={formik.handleChange} | |
| 323 | + onBlur={handleBlur} | |
| 324 | + value={formik.values.opportunityAmount} | |
| 325 | + autoFocus | |
| 326 | + borderColor={formik?.errors?.opportunityAmount && formik?.touched?.opportunityAmount ? "red.300" : null} | |
| 327 | + /> | |
| 328 | + <Text mb='10px' color={'red'}> {formik?.errors.opportunityAmount && formik?.touched.opportunityAmount && formik?.errors.opportunityAmount}</Text> | |
| 329 | + </> | |
| 330 | + : | |
| 331 | + <Text onDoubleClick={() => handleDoubleClick("opportunityAmount", data?.opportunityAmount, "Opportunity Amount")}>{data?.opportunityAmount ? data?.opportunityAmount : ' - '}</Text> | |
| 332 | + } | |
| 333 | + </GridItem> | |
| 334 | + <GridItem colSpan={{ base: 2, md: 1 }} > | |
| 335 | + <Text fontSize="sm" fontWeight="bold" color={'blackAlpha.900'}> Amount</Text> | |
| 336 | + { | |
| 337 | + editableField === "amount" ? | |
| 338 | + <> | |
| 339 | + <Input | |
| 340 | + id="text" | |
| 341 | + name="amount" | |
| 342 | + type="number" | |
| 343 | + onChange={formik.handleChange} | |
| 344 | + onBlur={handleBlur} | |
| 345 | + value={formik.values.amount} | |
| 346 | + autoFocus | |
| 347 | + borderColor={formik?.errors?.amount && formik?.touched?.amount ? "red.300" : null} | |
| 348 | + /> | |
| 349 | + <Text mb='10px' color={'red'}> {formik?.errors.amount && formik?.touched.amount && formik?.errors.amount}</Text> | |
| 350 | + </> | |
| 351 | + : | |
| 352 | + <Text onDoubleClick={() => handleDoubleClick("amount", data?.amount, "Amount")}>{data?.amount ? data?.amount : ' - '}</Text> | |
| 353 | + } | |
| 354 | + </GridItem> | |
| 355 | + <GridItem colSpan={{ base: 2, md: 1 }} > | |
| 356 | + <Text fontSize="sm" fontWeight="bold" color={'blackAlpha.900'}> Expected Close Date</Text> | |
| 357 | + { | |
| 358 | + editableField === "expectedCloseDate" ? | |
| 359 | + <> | |
| 360 | + <Input | |
| 361 | + id="text" | |
| 362 | + name="expectedCloseDate" | |
| 363 | + type="date" | |
| 364 | + onChange={formik.handleChange} | |
| 365 | + onBlur={handleBlur} | |
| 366 | + value={dayjs(formik.values.expectedCloseDate).format("YYYY-MM-DD")} | |
| 367 | + autoFocus | |
| 368 | + borderColor={formik?.errors?.expectedCloseDate && formik?.touched?.expectedCloseDate ? "red.300" : null} | |
| 369 | + /> | |
| 370 | + <Text mb='10px' color={'red'}> {formik?.errors.expectedCloseDate && formik?.touched.expectedCloseDate && formik?.errors.expectedCloseDate}</Text> | |
| 371 | + </> | |
| 372 | + : | |
| 373 | + <Text onDoubleClick={() => handleDoubleClick("expectedCloseDate", data?.expectedCloseDate, "Expected Close Date")}>{data?.expectedCloseDate ? dayjs(data?.expectedCloseDate).format("YYYY-MM-DD") : ' - '}</Text> | |
| 374 | + } | |
| 375 | + </GridItem> | |
| 376 | + <GridItem colSpan={{ base: 2, md: 1 }} > | |
| 377 | + <Text fontSize="sm" fontWeight="bold" color={'blackAlpha.900'}> Next Step</Text> | |
| 378 | + { | |
| 379 | + editableField === "nextStep" ? | |
| 380 | + <> | |
| 381 | + <Input | |
| 382 | + id="text" | |
| 383 | + name="nextStep" | |
| 384 | + type="text" | |
| 385 | + onChange={formik.handleChange} | |
| 386 | + onBlur={handleBlur} | |
| 387 | + value={formik.values.nextStep} | |
| 388 | + autoFocus | |
| 389 | + borderColor={formik?.errors?.nextStep && formik?.touched?.nextStep ? "red.300" : null} | |
| 390 | + /> | |
| 391 | + <Text mb='10px' color={'red'}> {formik?.errors.nextStep && formik?.touched.nextStep && formik?.errors.nextStep}</Text> | |
| 392 | + </> | |
| 393 | + : | |
| 394 | + <Text onDoubleClick={() => handleDoubleClick("nextStep", data?.nextStep, "Next Step")}>{data?.nextStep ? data?.nextStep : ' - '}</Text> | |
| 395 | + } | |
| 396 | + </GridItem> | |
| 397 | + <GridItem colSpan={{ base: 2, md: 1 }} > | |
| 398 | + <Text fontSize="sm" fontWeight="bold" color={'blackAlpha.900'}> Sales Stage</Text> | |
| 399 | + { | |
| 400 | + editableField === "salesStage" ? | |
| 401 | + <> | |
| 402 | + <Select | |
| 403 | + value={formik?.values.salesStage} | |
| 404 | + name="salesStage" | |
| 405 | + onChange={formik?.handleChange} | |
| 406 | + onBlur={handleBlur} | |
| 407 | + mb={formik?.errors.salesStage && formik?.touched.salesStage ? undefined : '10px'} | |
| 408 | + fontWeight='500' | |
| 409 | + placeholder={'Sales Stage'} | |
| 410 | + borderColor={formik?.errors.salesStage && formik?.touched.salesStage ? "red.300" : null} | |
| 411 | + > | |
| 412 | + <option value={"Prospecting"}>Prospecting</option> | |
| 413 | + <option value={"Qualification"}>Qualification</option> | |
| 414 | + <option value={"Needs Analysis"}>Needs Analysis</option> | |
| 415 | + <option value={"Value Propositon"}>Value Propositon</option> | |
| 416 | + <option value={"Identifying Decision Makers"}>Identifying Decision Makers</option> | |
| 417 | + <option value={"Perception Analysis"}>Perception Analysis</option> | |
| 418 | + <option value={"Proposal/Price Quote"}>Proposal/Price Quote</option> | |
| 419 | + <option value={"Negotiation/Review"}>Negotiation/Review</option> | |
| 420 | + <option value={"Closed/Won"}>Closed/Won</option> | |
| 421 | + <option value={"Closed/Lost"}>Closed/Lost</option> | |
| 422 | + </Select> | |
| 423 | + <Text mb='10px' color={'red'}> {formik?.errors.salesStage && formik?.touched.salesStage && formik?.errors.salesStage}</Text> | |
| 424 | + </> | |
| 425 | + : | |
| 426 | + <Text onDoubleClick={() => handleDoubleClick("salesStage", data?.salesStage, "Sales Stage")}>{data?.salesStage ? data?.salesStage : ' - '}</Text> | |
| 427 | + } | |
| 428 | + </GridItem> | |
| 429 | + <GridItem colSpan={{ base: 2, md: 1 }} > | |
| 430 | + <Text fontSize="sm" fontWeight="bold" color={'blackAlpha.900'}> Probability</Text> | |
| 431 | + { | |
| 432 | + editableField === "probability" ? | |
| 433 | + <> | |
| 434 | + <Input | |
| 435 | + id="text" | |
| 436 | + name="probability" | |
| 437 | + type="number" | |
| 438 | + onChange={formik.handleChange} | |
| 439 | + onBlur={handleBlur} | |
| 440 | + value={formik.values.probability} | |
| 441 | + autoFocus | |
| 442 | + borderColor={formik?.errors?.probability && formik?.touched?.probability ? "red.300" : null} | |
| 443 | + /> | |
| 444 | + <Text mb='10px' color={'red'}> {formik?.errors.probability && formik?.touched.probability && formik?.errors.probability}</Text> | |
| 445 | + </> | |
| 446 | + : | |
| 447 | + <Text onDoubleClick={() => handleDoubleClick("probability", data?.probability, "Probability")}>{data?.probability ? data?.probability : ' - '}</Text> | |
| 448 | + } | |
| 449 | + </GridItem> | |
| 450 | + <GridItem colSpan={{ base: 2, md: 1 }} > | |
| 451 | + <Text fontSize="sm" fontWeight="bold" color={'blackAlpha.900'}> Description</Text> | |
| 452 | + { | |
| 453 | + editableField === "description" ? | |
| 454 | + <> | |
| 455 | + <Input | |
| 456 | + id="text" | |
| 457 | + name="description" | |
| 458 | + type="text" | |
| 459 | + onChange={formik.handleChange} | |
| 460 | + onBlur={handleBlur} | |
| 461 | + value={formik.values.description} | |
| 462 | + autoFocus | |
| 463 | + borderColor={formik?.errors?.description && formik?.touched?.description ? "red.300" : null} | |
| 464 | + /> | |
| 465 | + <Text mb='10px' color={'red'}> {formik?.errors.description && formik?.touched.description && formik?.errors.description}</Text> | |
| 466 | + </> | |
| 467 | + : | |
| 468 | + <Text onDoubleClick={() => handleDoubleClick("description", data?.description, "Description")}>{data?.description ? data?.description : ' - '}</Text> | |
| 469 | + } | |
| 470 | + </GridItem> | |
| 471 | + | |
| 472 | + | |
| 473 | + {/* <GridItem colSpan={{ base: 2, md: 1 }} > | |
| 474 | + <Text fontSize="sm" fontWeight="bold" color={'blackAlpha.900'}> Task reminder </Text> | |
| 475 | + <Text>{data?.reminder ? data?.reminder : ' - '}</Text> | |
| 476 | + </GridItem> | |
| 477 | + <GridItem colSpan={{ base: 2, m |