irongit

init

Graceauthored
commit b348ad4ebc2edbdd96510efd302767fb13644ee3Browse files

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+});
+41-0Client/src/components/menu/ItemContent.js
@@ -0,0 +1,41 @@
1+// chakra imports
2+import { Icon, Flex, Text, useColorModeValue } from "@chakra-ui/react";
3+import { MdUpgrade } from "react-icons/md";
4+import React from "react";
5+
6+export function ItemContent(props) {
7+ const textColor = useColorModeValue("navy.700", "white");
8+ return (
9+ <>
10+ <Flex
11+ justify='center'
12+ align='center'
13+ borderRadius='16px'
14+ minH={{ base: "60px", md: "70px" }}
15+ h={{ base: "60px", md: "70px" }}
16+ minW={{ base: "60px", md: "70px" }}
17+ w={{ base: "60px", md: "70px" }}
18+ me='14px'
19+ bg='linear-gradient(135deg, #868CFF 0%, #4318FF 100%)'>
20+ <Icon as={MdUpgrade} color='white' w={8} h={14} />
21+ </Flex>
22+ <Flex flexDirection='column'>
23+ <Text
24+ mb='5px'
25+ fontWeight='bold'
26+ color={textColor}
27+ fontSize={{ base: "md", md: "md" }}>
28+ New Update: {props.info}
29+ </Text>
30+ <Flex alignItems='center'>
31+ <Text
32+ fontSize={{ base: "sm", md: "sm" }}
33+ lineHeight='100%'
34+ color={textColor}>
35+ A new update for your downloaded item is available!
36+ </Text>
37+ </Flex>
38+ </Flex>
39+ </>
40+ );
41+}
+201-0Client/src/components/navbar/NavbarAdmin.js
@@ -0,0 +1,201 @@
1+// Chakra Imports
2+import { Box, Flex, Heading, Image, Link, Text, useColorModeValue } from '@chakra-ui/react';
3+import AdminNavbarLinks from 'components/navbar/NavbarLinksAdmin';
4+import PropTypes from 'prop-types';
5+import { useEffect, useState } from 'react';
6+
7+import { AiOutlineMenuUnfold } from "react-icons/ai";
8+import { AiOutlineMenuFold } from "react-icons/ai";
9+import { useDispatch, useSelector } from 'react-redux';
10+import { fetchImage } from '../../redux/slices/imageSlice';
11+
12+export default function AdminNavbar(props) {
13+ const [scrolled, setScrolled] = useState(false);
14+
15+ useEffect(() => {
16+ window.addEventListener('scroll', changeNavbar);
17+
18+ return () => {
19+ window.removeEventListener('scroll', changeNavbar);
20+ };
21+ });
22+
23+ const { secondary, message, brandText, under, setOpenSidebar, openSidebar, largeLogo, routes } = props;
24+ // Here are all the props that may change depending on navbar's type or state.(secondary, variant, scrolled)
25+ let mainText = useColorModeValue('navy.700', 'white');
26+ let secondaryText = useColorModeValue('gray.700', 'white');
27+ let navbarPosition = 'fixed';
28+ let navbarFilter = 'none';
29+ let navbarBackdrop = 'blur(20px)';
30+ let navbarShadow = 'none';
31+ let navbarBg = useColorModeValue('#fff', 'rgba(11,20,55,0.5)');
32+ let navbarBorder = 'transparent';
33+ let secondaryMargin = '-9px';
34+ let paddingX = '15px';
35+ let gap = '0px';
36+ let size = "sm";
37+ const changeNavbar = () => {
38+ if (window.scrollY > 1) {
39+ setScrolled(true);
40+ } else {
41+ setScrolled(false);
42+ }
43+ };
44+ return (
45+ <Box
46+ position={navbarPosition}
47+ boxShadow={navbarShadow}
48+ bg={navbarBg}
49+ borderColor={navbarBorder}
50+ filter={navbarFilter}
51+ backdropFilter={navbarBackdrop}
52+ backgroundPosition='center'
53+ backgroundSize='cover'
54+ // borderRadius='16px'
55+ borderWidth='1.5px'
56+ borderStyle='solid'
57+ zIndex={1}
58+ transitionDelay='0s, 0s, 0s, 0s'
59+ transitionDuration=' 0.25s, 0.25s, 0.25s, 0s'
60+ transition-property='box-shadow, background-color, filter, border'
61+ transitionTimingFunction='linear, linear, linear, linear'
62+ alignItems={{ xl: 'center' }}
63+ display={secondary ? 'block' : 'flex'}
64+ minH='75px'
65+ justifyContent={{ xl: 'center' }}
66+ lineHeight='25.6px'
67+ mx='auto'
68+ mt={secondaryMargin}
69+ pb='6px'
70+ right={{ base: '0px' }}
71+ // right={{ base: '12px', md: '30px', lg: '30px', xl: '30px' }}
72+ px={{
73+ sm: paddingX,
74+ md: '10px'
75+ }}
76+ ps={{
77+ xl: '12px'
78+ }}
79+ pt='8px'
80+ top={{ base: '0px' }}
81+ w={{
82+ base: '100vw'
83+ // base: 'calc(100vw - 0%)',
84+ // md: 'calc(100vw - 0%)',
85+ // lg: 'calc(100vw - 0%)',
86+ // xl: openSidebar === true ? 'calc(100vw - 286px)' : 'calc(100vw - 80px)',
87+ // '2xl': openSidebar === true ? 'calc(100vw - 286px)' : 'calc(100vw - 80px)'
88+ }}
89+ sx={{ boxShadow: '14px 17px 40px 4px rgba(112, 144, 176, 0.08)' }}
90+ >
91+ <Flex
92+ w='100%'
93+ flexDirection={{
94+ sm: 'column',
95+ md: 'row'
96+ }}
97+ alignItems={{ xl: 'center' }}
98+ mb={gap}>
99+ <Box
100+ // mb={{ sm: '8px', md: '10px' }}
101+ // pt="15px"
102+ display={"flex"} alignItems={"center"}>
103+ {/*
104+ <Breadcrumb>
105+ <BreadcrumbItem color={secondaryText} fontSize='sm' mb='5px'>
106+ <BreadcrumbLink as={rrd.Link} to='/admin/default' color={secondaryText}>
107+ <AiTwotoneHome />
108+ </BreadcrumbLink>
109+ </BreadcrumbItem>
110+
111+ {under?.under && <BreadcrumbItem color={secondaryText} fontSize='sm' mb='5px'>
112+ <BreadcrumbLink as={rrd.Link} to={`${under.both === true ? '' : under.layout + '/'}${under.under}`} color={secondaryText}>
113+ {under.parentName}
114+ </BreadcrumbLink>
115+ </BreadcrumbItem>}
116+
117+
118+ <BreadcrumbItem color={secondaryText} fontSize='sm' mb='5px'>
119+ <BreadcrumbLink as={rrd.Link} to='#' color={secondaryText}>
120+ {brandText}
121+ </BreadcrumbLink>
122+ </BreadcrumbItem>
123+
124+ </Breadcrumb>
125+ */}
126+ <Flex me={openSidebar ? "" : "5"} mx={openSidebar ? "14" : "1"} display={{ sm: "none", xl: "flex" }}>
127+ {largeLogo && largeLogo[0]?.logoLgImg || largeLogo && largeLogo[0]?.logoSmImg ? <Image
128+ style={{ width: openSidebar ? "165px" : "60px", height: '52px', objectFit: "contain" }}
129+ src={openSidebar === true ? largeLogo[0]?.logoLgImg : largeLogo[0]?.logoSmImg} // Set the source path of your image
130+ alt="Logo" // Set the alt text for accessibility
131+ cursor="pointer"
132+ onClick={() => !props.from && setOpenSidebar(!openSidebar)}
133+ userSelect="none"
134+ my={2}
135+ /> : <Heading my={4} style={{ width: openSidebar ? "165px" : "60px", height: '52px', objectFit: "contain" }}
136+ cursor={"pointer"} onClick={() => !props.from && setOpenSidebar(!openSidebar)} userSelect={"none"}>{openSidebar === true ? "Prolink" : "Pr"}</Heading>}
137+ </Flex>
138+ <Box display={{ sm: "none", xl: "flex" }} ms={openSidebar ? "" : "3"} onClick={() => setOpenSidebar(!openSidebar)} style={{ fontSize: "25px" }}>{openSidebar ? <AiOutlineMenuFold /> : <AiOutlineMenuUnfold />}</Box>
139+ <Link
140+ color={mainText}
141+ display={{ sm: "flex", xl: "none" }}
142+ >
143+ {largeLogo && largeLogo[0]?.logoLgImg ? <Image
144+ style={{ width: "100%", height: '52px' }}
145+ src={largeLogo[0]?.logoLgImg}
146+ alt="Logo"
147+ cursor="pointer"
148+ userSelect="none"
149+ my={2}
150+ /> : <Heading my={4}
151+ cursor={"pointer"} userSelect={"none"}>{openSidebar === true ? "Prolink" : "Pr"}</Heading>}
152+ </Link>
153+
154+ <Link
155+ color={mainText}
156+ href='#'
157+ pt="2px"
158+ bg='inherit'
159+ ps="30px"
160+ display={{ sm: "none", xl: "flex" }}
161+ borderRadius='inherit'
162+ fontWeight='bold'
163+ fontSize='34px'
164+ textTransform={"capitalize"}
165+ _hover={{ color: { mainText } }}
166+ _active={{
167+ bg: 'inherit',
168+ transform: 'none',
169+ borderColor: 'transparent'
170+ }}
171+ _focus={{
172+ boxShadow: 'none'
173+ }}>
174+ {brandText}
175+ </Link>
176+ </Box>
177+ <Box ms='auto' w={{ sm: '100%', md: 'unset' }}>
178+ <AdminNavbarLinks
179+ setOpenSidebar={setOpenSidebar}
180+ openSidebar={openSidebar}
181+ onOpen={props.onOpen}
182+ logoText={props.logoText}
183+ secondary={props.secondary}
184+ fixed={props.fixed}
185+ scrolled={scrolled}
186+ routes={routes}
187+ />
188+ </Box>
189+ </Flex>
190+ {secondary ? <Text color='white'>{message}</Text> : null}
191+ </Box>
192+ );
193+}
194+
195+AdminNavbar.propTypes = {
196+ brandText: PropTypes.string,
197+ variant: PropTypes.string,
198+ secondary: PropTypes.bool,
199+ fixed: PropTypes.bool,
200+ onOpen: PropTypes.func
201+};
+505-0Client/src/components/navbar/NavbarAuth.js
@@ -0,0 +1,505 @@
1+import PropTypes from "prop-types";
2+import React from "react";
3+import { NavLink } from "react-router-dom";
4+
5+// Chakra imports
6+import {
7+ Box,
8+ Button,
9+ Flex,
10+ Grid,
11+ HStack,
12+ Icon,
13+ Link,
14+ Menu,
15+ MenuList,
16+ Stack,
17+ Text,
18+ useColorModeValue,
19+ useColorMode,
20+ useDisclosure,
21+ SimpleGrid,
22+} from "@chakra-ui/react";
23+
24+// Custom components
25+import IconBox from "components/icons/IconBox";
26+import { HorizonLogo } from "components/icons/Icons";
27+import { SidebarResponsive } from "components/sidebar/Sidebar";
28+import { SidebarContext } from "contexts/SidebarContext";
29+
30+// Assets
31+import { GoChevronDown, GoChevronRight } from "react-icons/go";
32+import routes from "routes.js";
33+
34+export default function AuthNavbar(props) {
35+ const { logo, logoText, secondary, sidebarWidth, ...rest } = props;
36+ const { colorMode } = useColorMode();
37+ // Menu States
38+ const {
39+ isOpen: isOpenAuth,
40+ onOpen: onOpenAuth,
41+ onClose: onCloseAuth,
42+ } = useDisclosure();
43+ const {
44+ isOpen: isOpenDashboards,
45+ onOpen: onOpenDashboards,
46+ onClose: onCloseDashboards,
47+ } = useDisclosure();
48+ const {
49+ isOpen: isOpenMain,
50+ onOpen: onOpenMain,
51+ onClose: onCloseMain,
52+ } = useDisclosure();
53+ const {
54+ isOpen: isOpenNft,
55+ onOpen: onOpenNft,
56+ onClose: onCloseNft,
57+ } = useDisclosure();
58+ // Menus
59+ function getLinks(routeName) {
60+ let foundRoute = routes.filter(function (route) {
61+ return route.items && route.name === routeName;
62+ });
63+ return foundRoute[0].items;
64+ }
65+ function getLinksCollapse(routeName) {
66+ let foundRoute = routes.filter(function (route) {
67+ return route.items && route.name === routeName;
68+ });
69+
70+ let foundLinks = foundRoute[0].items.filter(function (link) {
71+ return link.collapse === true;
72+ });
73+
74+ return foundLinks;
75+ }
76+ let authObject = getLinksCollapse("Authentication");
77+ let mainObject = getLinksCollapse("Main Pages");
78+ let dashboardsObject = getLinks("Dashboards");
79+ let nftsObject = getLinks("NFTs");
80+ let logoColor = useColorModeValue("white", "white");
81+ // Chakra color mode
82+
83+ const textColor = useColorModeValue("navy.700", "white");
84+ let menuBg = useColorModeValue("white", "navy.900");
85+ let mainText = "#fff";
86+ let navbarBg = "none";
87+ let navbarShadow = "initial";
88+ let bgButton = "white";
89+ let colorButton = "brand.500";
90+ let navbarPosition = "absolute";
91+
92+ let brand = (
93+ <Link
94+ href={`${process.env.PUBLIC_URL}/#/`}
95+ target='_blank'
96+ display='flex'
97+ lineHeight='100%'
98+ fontWeight='bold'
99+ justifyContent='center'
100+ alignItems='center'
101+ color={mainText}>
102+ <Stack direction='row' spacing='12px' align='center' justify='center'>
103+ <HorizonLogo h='26px' w='175px' color={logoColor} />
104+ </Stack>
105+ <Text fontsize='sm' mt='3px'>
106+ {logoText}
107+ </Text>
108+ </Link>
109+ );
110+ if (props.secondary === true) {
111+ brand = (
112+ <Link
113+ minW='175px'
114+ href={`${process.env.PUBLIC_URL}/#/`}
115+ target='_blank'
116+ display='flex'
117+ lineHeight='100%'
118+ fontWeight='bold'
119+ justifyContent='center'
120+ alignItems='center'
121+ color={mainText}>
122+ <HorizonLogo h='26px' w='175px' my='32px' color={logoColor} />
123+ </Link>
124+ );
125+ // mainText = useColorModeValue("gray.700", "gray.200");
126+ // navbarBg = useColorModeValue("white", "navy.800");
127+ // navbarShadow = useColorModeValue(
128+ // "0px 7px 23px rgba(0, 0, 0, 0.05)",
129+ // "none"
130+ // );
131+ // bgButton = useColorModeValue("gray.700", "white");
132+ // colorButton = useColorModeValue("white", "gray.700");
133+ // navbarPosition = "fixed";
134+ }
135+ const createNftsLinks = (routes) => {
136+ return routes.map((link, key) => {
137+ return (
138+ <NavLink
139+ key={key}
140+ to={link.layout + link.path}
141+ style={{ maxWidth: "max-content", marginLeft: "40px" }}>
142+ <Text color='gray.400' fontSize='sm' fontWeight='normal'>
143+ {link.name}
144+ </Text>
145+ </NavLink>
146+ );
147+ });
148+ };
149+ const createDashboardsLinks = (routes) => {
150+ return routes.map((link, key) => {
151+ return (
152+ <NavLink
153+ key={key}
154+ to={link.layout + link.path}
155+ style={{ maxWidth: "max-content", marginLeft: "40px" }}>
156+ <Text color='gray.400' fontSize='sm' fontWeight='normal'>
157+ {link.name}
158+ </Text>
159+ </NavLink>
160+ );
161+ });
162+ };
163+ const createMainLinks = (routes) => {
164+ return routes.map((link, key) => {
165+ if (link.collapse === true) {
166+ return (
167+ <Stack key={key} direction='column' maxW='max-content'>
168+ <Stack
169+ direction='row'
170+ spacing='0px'
171+ align='center'
172+ cursor='default'>
173+ <IconBox bg='brand.500' h='30px' w='30px' me='10px'>
174+ {link.icon}
175+ </IconBox>
176+ <Text fontWeight='bold' fontSize='md' me='auto' color={textColor}>
177+ {link.name}
178+ </Text>
179+ <Icon
180+ as={GoChevronRight}
181+ color={mainText}
182+ w='14px'
183+ h='14px'
184+ fontWeight='2000'
185+ />
186+ </Stack>
187+ <Stack direction='column' bg={menuBg}>
188+ {createMainLinks(link.items)}
189+ </Stack>
190+ </Stack>
191+ );
192+ } else {
193+ return (
194+ <NavLink
195+ key={key}
196+ to={link.layout + link.path}
197+ style={{ maxWidth: "max-content", marginLeft: "40px" }}>
198+ <Text color='gray.400' fontSize='sm' fontWeight='normal'>
199+ {link.name}
200+ </Text>
201+ </NavLink>
202+ );
203+ }
204+ });
205+ };
206+ const createAuthLinks = (routes) => {
207+ return routes.map((link, key) => {
208+ if (link.collapse === true) {
209+ return (
210+ <Stack key={key} direction='column' my='auto' maxW='max-content'>
211+ <Stack
212+ direction='row'
213+ spacing='0px'
214+ align='center'
215+ cursor='default'
216+ w='max-content'>
217+ <IconBox bg='brand.500' h='30px' w='30px' me='10px'>
218+ {link.icon}
219+ </IconBox>
220+ <Text fontWeight='bold' fontSize='md' me='auto' color={textColor}>
221+ {link.name}
222+ </Text>
223+ <Icon
224+ as={GoChevronRight}
225+ color={mainText}
226+ w='14px'
227+ h='14px'
228+ fontWeight='2000'
229+ />
230+ </Stack>
231+ <Stack direction='column' bg={menuBg}>
232+ {createAuthLinks(link.items)}
233+ </Stack>
234+ </Stack>
235+ );
236+ } else {
237+ return (
238+ <NavLink
239+ key={key}
240+ to={link.layout + link.path}
241+ style={{ maxWidth: "max-content", marginLeft: "40px" }}>
242+ <Text color='gray.400' fontSize='sm' fontWeight='normal'>
243+ {link.name}
244+ </Text>
245+ </NavLink>
246+ );
247+ }
248+ });
249+ };
250+ const linksAuth = (
251+ <HStack display={{ sm: "none", lg: "flex" }} spacing='12px'>
252+ <Stack
253+ direction='row'
254+ spacing='4px'
255+ align='center'
256+ color='#fff'
257+ fontWeight='bold'
258+ onMouseEnter={onOpenDashboards}
259+ onMouseLeave={onCloseDashboards}
260+ cursor='pointer'
261+ position='relative'>
262+ <Text fontSize='sm' color={mainText}>
263+ Dashboards
264+ </Text>
265+ <Box>
266+ <Icon
267+ mt='8px'
268+ as={GoChevronDown}
269+ color={mainText}
270+ w='14px'
271+ h='14px'
272+ fontWeight='2000'
273+ />
274+ </Box>
275+ <Menu isOpen={isOpenDashboards}>
276+ <MenuList
277+ bg={menuBg}
278+ p='22px'
279+ cursor='default'
280+ borderRadius='15px'
281+ position='absolute'
282+ top='30px'
283+ left='-10px'>
284+ <Flex flexWrap='wrap' w='300px' gap='16px'>
285+ {createDashboardsLinks(dashboardsObject)}
286+ </Flex>
287+ </MenuList>
288+ </Menu>
289+ </Stack>
290+ <Stack
291+ direction='row'
292+ spacing='4px'
293+ align='center'
294+ color='#fff'
295+ fontWeight='bold'
296+ onMouseEnter={onOpenAuth}
297+ onMouseLeave={onCloseAuth}
298+ cursor='pointer'
299+ position='relative'>
300+ <Text fontSize='sm' color={mainText}>
301+ Authentications
302+ </Text>
303+ <Box>
304+ <Icon
305+ mt='8px'
306+ as={GoChevronDown}
307+ color={mainText}
308+ w='14px'
309+ h='14px'
310+ fontWeight='2000'
311+ />
312+ </Box>
313+ <Menu isOpen={isOpenAuth}>
314+ <MenuList
315+ bg={menuBg}
316+ p='22px'
317+ cursor='default'
318+ borderRadius='15px'
319+ position='absolute'
320+ top='30px'
321+ left='-10px'>
322+ <Flex>
323+ <SimpleGrid columns='3' gap='10px' minW='500px' me='20px'>
324+ {createAuthLinks(authObject)}
325+ </SimpleGrid>
326+ {/* <Flex
327+ bg='red'
328+ direction='column'
329+ justify='center'
330+ align='center'
331+ w='stretch'
332+ minH='230px'
333+ borderRadius='15px'>
334+ <IconBox
335+ bg='white'
336+ color='white'
337+ borderRadius='50%'
338+ h='50px'
339+ w='50px'
340+ mb='12px'>
341+ <Icon as={AiFillStar} w='25px' h='25px' color='blue.500' />
342+ </IconBox>
343+ <Text
344+ fontSize='xl'
345+ fontWeight='bold'
346+ color='#fff'
347+ maxW='80%'
348+ textAlign='center'>
349+ Explore our utilities pages
350+ </Text>
351+ </Flex> */}
352+ </Flex>
353+ </MenuList>
354+ </Menu>
355+ </Stack>
356+ <Stack
357+ direction='row'
358+ spacing='4px'
359+ align='center'
360+ color='#fff'
361+ fontWeight='bold'
362+ onMouseEnter={onOpenMain}
363+ onMouseLeave={onCloseMain}
364+ cursor='pointer'
365+ position='relative'>
366+ <Text fontSize='sm' color={mainText}>
367+ Main Pages
368+ </Text>
369+ <Box>
370+ <Icon
371+ mt='8px'
372+ as={GoChevronDown}
373+ color={mainText}
374+ w='14px'
375+ h='14px'
376+ fontWeight='2000'
377+ />
378+ </Box>
379+ <Menu isOpen={isOpenMain}>
380+ <MenuList
381+ bg={menuBg}
382+ p='22px'
383+ cursor='default'
384+ borderRadius='15px'
385+ position='absolute'
386+ top='30px'
387+ left='-10px'>
388+ <Flex flexWrap='wrap' align='start' w='500px' gap='16px'>
389+ {createMainLinks(mainObject)}
390+ </Flex>
391+ </MenuList>
392+ </Menu>
393+ </Stack>
394+ <Stack
395+ direction='row'
396+ spacing='4px'
397+ align='center'
398+ color='#fff'
399+ fontWeight='bold'
400+ onMouseEnter={onOpenNft}
401+ onMouseLeave={onCloseNft}
402+ cursor='pointer'
403+ position='relative'>
404+ <Text fontSize='sm' color={mainText}>
405+ NFTs
406+ </Text>
407+ <Box>
408+ <Icon
409+ mt='8px'
410+ as={GoChevronDown}
411+ color={mainText}
412+ w='14px'
413+ h='14px'
414+ fontWeight='2000'
415+ />
416+ </Box>
417+ <Menu isOpen={isOpenNft}>
418+ <MenuList
419+ bg={menuBg}
420+ p='22px'
421+ minW='350px'
422+ cursor='default'
423+ borderRadius='15px'
424+ position='absolute'
425+ top='30px'
426+ left='-10px'>
427+ <Grid templateColumns='repeat(2, 1fr)' gap='16px'>
428+ {createNftsLinks(nftsObject)}
429+ </Grid>
430+ </MenuList>
431+ </Menu>
432+ </Stack>
433+ </HStack>
434+ );
435+
436+ return (
437+ <SidebarContext.Provider value={{ sidebarWidth }}>
438+ <Flex
439+ position={navbarPosition}
440+ top='16px'
441+ left='50%'
442+ transform='translate(-50%, 0px)'
443+ background={navbarBg}
444+ boxShadow={navbarShadow}
445+ borderRadius='15px'
446+ px='16px'
447+ py='22px'
448+ mx='auto'
449+ width='1044px'
450+ maxW='90%'
451+ alignItems='center'
452+ zIndex='3'>
453+ <Flex w='100%' justifyContent={{ sm: "start", lg: "space-between" }}>
454+ {brand}
455+ <Box
456+ ms={{ base: "auto", lg: "0px" }}
457+ display={{ base: "flex", lg: "none" }}
458+ justifyContent='center'
459+ alignItems='center'>
460+ <SidebarResponsive
461+ logo={
462+ <Stack
463+ direction='row'
464+ spacing='12px'
465+ align='center'
466+ justify='center'>
467+ <Box
468+ w='1px'
469+ h='20px'
470+ bg={colorMode === "dark" ? "white" : "gray.700"}
471+ />
472+ </Stack>
473+ }
474+ logoText={props.logoText}
475+ secondary={props.secondary}
476+ routes={routes}
477+ {...rest}
478+ />
479+ </Box>
480+ {linksAuth}
481+ <Link href='https://www.horizon-ui.com/pro'>
482+ <Button
483+ bg={bgButton}
484+ color={colorButton}
485+ fontSize='xs'
486+ variant='no-effects'
487+ borderRadius='50px'
488+ px='45px'
489+ display={{
490+ sm: "none",
491+ lg: "flex",
492+ }}>
493+ Buy Now
494+ </Button>
495+ </Link>
496+ </Flex>
497+ </Flex>
498+ </SidebarContext.Provider>
499+ );
500+}
501+
502+AuthNavbar.propTypes = {
503+ color: PropTypes.oneOf(["primary", "info", "success", "warning", "danger"]),
504+ brandText: PropTypes.string,
505+};
+586-0Client/src/components/navbar/NavbarExample.js
@@ -0,0 +1,586 @@
1+// Chakra imports
2+import {
3+ Box,
4+ Button,
5+ Flex,
6+ Grid,
7+ HStack,
8+ Icon,
9+ Link,
10+ Menu,
11+ MenuItem,
12+ MenuList,
13+ MenuButton,
14+ Stack,
15+ Text,
16+ useColorModeValue,
17+ useColorMode,
18+ useDisclosure,
19+ SimpleGrid,
20+} from "@chakra-ui/react";
21+import IconBox from "components/icons/IconBox";
22+import { SidebarResponsive } from "components/sidebar/Sidebar";
23+import PropTypes from "prop-types";
24+import React from "react";
25+import { AiFillStar } from "react-icons/ai";
26+import { GoChevronDown, GoChevronRight } from "react-icons/go";
27+import { NavLink } from "react-router-dom";
28+import { SidebarContext } from "contexts/SidebarContext";
29+import routes from "routes.js";
30+
31+export default function AuthNavbar(props) {
32+ const { logo, logoText, secondary, sidebarWidth, ...rest } = props;
33+ const { colorMode } = useColorMode();
34+ // Menu States
35+ const {
36+ isOpen: isOpenPages,
37+ onOpen: onOpenPages,
38+ onClose: onClosePages,
39+ } = useDisclosure();
40+
41+ const {
42+ isOpen: isOpenAuth,
43+ onOpen: onOpenAuth,
44+ onClose: onCloseAuth,
45+ } = useDisclosure();
46+
47+ const {
48+ isOpen: isOpenApplication,
49+ onOpen: onOpenApplication,
50+ onClose: onCloseApplication,
51+ } = useDisclosure();
52+
53+ const {
54+ isOpen: isOpenEcommerce,
55+ onOpen: onOpenEcommerce,
56+ onClose: onCloseEcommerce,
57+ } = useDisclosure();
58+
59+ // Menus
60+ let authObject = {};
61+ routes.map((route) => {
62+ if (route.items) {
63+ authObject = route.items.find((link) => link.name === "Authentication");
64+ }
65+ });
66+
67+ let applicationsObject = {};
68+ routes.map((route) => {
69+ if (route.items) {
70+ applicationsObject = route.items.find(
71+ (link) => link.name === "Applications"
72+ );
73+ }
74+ });
75+
76+ let ecommerceObject = {};
77+ routes.map((route) => {
78+ if (route.items) {
79+ ecommerceObject = route.items.find((link) => link.name === "Ecommerce");
80+ }
81+ });
82+
83+ let extraArr = [];
84+ routes.map((route) => {
85+ route.items.map((item) => {
86+ if (item.items && item.name === "Pages") {
87+ extraArr = item.items.filter((link) => !link.collapse);
88+ }
89+ });
90+ });
91+
92+ // Chakra color mode
93+
94+ const textColor = useColorModeValue("gray.700", "#fff");
95+ let menuBg = useColorModeValue("white", "navy.900");
96+ let mainText = "#fff";
97+ let navbarBg = "none";
98+ let navbarShadow = "initial";
99+ let bgButton = useColorModeValue("white", "navy.900");
100+ let colorButton = useColorModeValue("gray.700", "white");
101+ let navbarPosition = "absolute";
102+
103+ let brand = (
104+ <Link
105+ href={`${process.env.PUBLIC_URL}/#/`}
106+ target='_blank'
107+ display='flex'
108+ lineHeight='100%'
109+ fontWeight='bold'
110+ justifyContent='center'
111+ alignItems='center'
112+ color={mainText}>
113+ <Text fontsize='sm' mt='3px'>
114+ {logoText}
115+ </Text>
116+ </Link>
117+ );
118+ if (props.secondary === true) {
119+ brand = (
120+ <Link
121+ href={`${process.env.PUBLIC_URL}/#/`}
122+ target='_blank'
123+ display='flex'
124+ lineHeight='100%'
125+ fontWeight='bold'
126+ justifyContent='center'
127+ alignItems='center'
128+ color={mainText}>
129+ <Stack direction='row' spacing='12px' align='center' justify='center'>
130+ <Box
131+ w='1px'
132+ h='20px'
133+ // bg={useColorModeValue("gray.700", "white")}
134+ />
135+ </Stack>
136+ <Text fontsize='sm' mt='3px'>
137+ {logoText}
138+ </Text>
139+ </Link>
140+ );
141+ // mainText = useColorModeValue("gray.700", "gray.200");
142+ // navbarBg = useColorModeValue("white", "navy.800");
143+ // navbarShadow = useColorModeValue(
144+ // "0px 7px 23px rgba(0, 0, 0, 0.05)",
145+ // "none"
146+ // );
147+ // bgButton = useColorModeValue("gray.700", "white");
148+ // colorButton = useColorModeValue("white", "gray.700");
149+ // navbarPosition = "fixed";
150+ }
151+
152+ const createPagesLinks = (routes) => {
153+ return routes.map((link) => {
154+ if (
155+ link.name === "Applications" ||
156+ link.name === "Ecommerce" ||
157+ link.name === "Authentication" ||
158+ link.name === "RTL" ||
159+ link.name === "Widgets" ||
160+ link.name === "Charts" ||
161+ link.name === "Alerts"
162+ ) {
163+ return;
164+ }
165+ if (link.name === "Pricing Page") {
166+ return (
167+ <Stack direction='column'>
168+ <Stack
169+ direction='row'
170+ spacing='6px'
171+ align='center'
172+ mb='6px'
173+ cursor='default'>
174+ <IconBox bg='blue.500' color='white' h='30px' w='30px'>
175+ {/* <RocketIcon color='inherit' /> */}
176+ </IconBox>
177+ <Text fontWeight='bold' fontSize='sm' color={textColor}>
178+ Extra
179+ </Text>
180+ </Stack>
181+ {createExtraLinks(extraArr)}
182+ </Stack>
183+ );
184+ }
185+ if (link.authIcon) {
186+ return (
187+ <Stack direction='column'>
188+ <Stack
189+ direction='row'
190+ spacing='6px'
191+ align='center'
192+ mb='6px'
193+ cursor='default'>
194+ <IconBox bg='blue.500' color='white' h='30px' w='30px'>
195+ {link.authIcon}
196+ </IconBox>
197+ <Text fontWeight='bold' fontSize='sm' color={textColor}>
198+ {link.name}
199+ </Text>
200+ </Stack>
201+ {createPagesLinks(link.items)}
202+ </Stack>
203+ );
204+ } else {
205+ if (link.component) {
206+ return (
207+ <NavLink to={link.layout + link.path}>
208+ <MenuItem
209+ ps='36px'
210+ py='0px'
211+ _hover={{ boxShadow: "none", bg: "none" }}
212+ borderRadius='12px'>
213+ <Text color='gray.400' fontSize='sm' fontWeight='normal'>
214+ {link.name}
215+ </Text>
216+ </MenuItem>
217+ </NavLink>
218+ );
219+ } else {
220+ return <>{createPagesLinks(link.items)}</>;
221+ }
222+ }
223+ });
224+ };
225+
226+ const createExtraLinks = (routes) => {
227+ return routes.map((link) => {
228+ return (
229+ <NavLink to={link.layout + link.path}>
230+ <MenuItem
231+ ps='36px'
232+ py='0px'
233+ _hover={{ boxShadow: "none", bg: "none" }}
234+ borderRadius='12px'>
235+ <Text color='gray.400' fontSize='sm' fontWeight='normal'>
236+ {link.name}
237+ </Text>
238+ </MenuItem>
239+ </NavLink>
240+ );
241+ });
242+ };
243+
244+ const createAuthLinks = (routes) => {
245+ return routes.map((link) => {
246+ if (link.authIcon && link.collapse === true) {
247+ return (
248+ <Stack direction='column' my='auto'>
249+ <Stack
250+ direction='row'
251+ spacing='0px'
252+ align='center'
253+ cursor='default'>
254+ <Text fontWeight='bold' fontSize='sm' me='auto' color={textColor}>
255+ {link.name}
256+ </Text>
257+ <Icon
258+ as={GoChevronRight}
259+ color={mainText}
260+ w='14px'
261+ h='14px'
262+ fontWeight='2000'
263+ />
264+ </Stack>
265+
266+ <Flex direction='column' bg={menuBg}>
267+ {createAuthLinks(link.items)}
268+ </Flex>
269+ </Stack>
270+ );
271+ } else {
272+ return (
273+ <NavLink to={link.layout + link.path}>
274+ <Text color='red' fontSize='sm' fontWeight='normal'>
275+ {link.name}
276+ </Text>
277+ </NavLink>
278+ );
279+ }
280+ });
281+ };
282+
283+ const createApplicationLinks = (routes) => {
284+ return routes.map((link) => {
285+ return (
286+ <NavLink to={link.layout + link.path}>
287+ <Stack direction='row' spacing='12px' align='center' cursor='pointer'>
288+ <IconBox bg='blue.500' color='white' h='30px' w='30px'>
289+ {link.authIcon}
290+ </IconBox>
291+ <Text fontWeight='bold' fontSize='sm' color={textColor}>
292+ {link.name}
293+ </Text>
294+ </Stack>
295+ </NavLink>
296+ );
297+ });
298+ };
299+
300+ const createEcommerceLinks = (routes) => {
301+ return routes.map((link) => {
302+ if (link.authIcon) {
303+ return (
304+ <Stack direction='column'>
305+ <Stack
306+ direction='row'
307+ spacing='6px'
308+ align='center'
309+ mb='6px'
310+ cursor='default'>
311+ <IconBox bg='blue.500' color='white' h='30px' w='30px'>
312+ {link.authIcon}
313+ </IconBox>
314+ <Text fontWeight='bold' fontSize='sm' color={textColor}>
315+ {link.name}
316+ </Text>
317+ </Stack>
318+ {createPagesLinks(link.items)}
319+ </Stack>
320+ );
321+ } else {
322+ if (link.component) {
323+ return (
324+ <NavLink to={link.layout + link.path}>
325+ <MenuItem
326+ ps='36px'
327+ py='0px'
328+ _hover={{ boxShadow: "none", bg: "none" }}
329+ borderRadius='12px'>
330+ <Text color='gray.400' fontSize='sm' fontWeight='normal'>
331+ {link.name}
332+ </Text>
333+ </MenuItem>
334+ </NavLink>
335+ );
336+ } else {
337+ return <>{createPagesLinks(link.items)}</>;
338+ }
339+ }
340+ });
341+ };
342+
343+ const linksAuth = (
344+ <HStack display={{ sm: "none", lg: "flex" }} spacing='12px'>
345+ <Stack
346+ direction='row'
347+ spacing='4px'
348+ align='center'
349+ color='#fff'
350+ fontWeight='bold'
351+ onMouseEnter={onOpenPages}
352+ onMouseLeave={onClosePages}
353+ cursor='pointer'
354+ position='relative'>
355+ <Text fontSize='sm' color={mainText}>
356+ Pages
357+ </Text>
358+ <Icon
359+ as={GoChevronDown}
360+ color={mainText}
361+ w='14px'
362+ h='14px'
363+ fontWeight='2000'
364+ />
365+ <Menu isOpen={isOpenPages}>
366+ <MenuList
367+ bg={menuBg}
368+ p='22px'
369+ minW='550px'
370+ cursor='default'
371+ borderRadius='15px'
372+ position='absolute'
373+ top='30px'
374+ left='-10px'>
375+ <Grid templateColumns='repeat(3, 1fr)' gap='16px'>
376+ {createPagesLinks(routes)}
377+ </Grid>
378+ </MenuList>
379+ </Menu>
380+ </Stack>
381+ <Stack
382+ direction='row'
383+ spacing='4px'
384+ align='center'
385+ color='#fff'
386+ fontWeight='bold'
387+ onMouseEnter={onOpenAuth}
388+ onMouseLeave={onCloseAuth}
389+ cursor='pointer'
390+ position='relative'>
391+ <Text fontSize='sm' color={mainText}></Text>
392+ <Icon
393+ as={GoChevronDown}
394+ color={mainText}
395+ w='14px'
396+ h='14px'
397+ fontWeight='2000'
398+ />
399+ <Menu isOpen={isOpenAuth}>
400+ <MenuList
401+ bg={menuBg}
402+ p='22px'
403+ minW='450px'
404+ cursor='default'
405+ borderRadius='15px'
406+ position='absolute'
407+ top='30px'
408+ left='-10px'>
409+ <Stack direction='row' spacing='24px'>
410+ <Flex
411+ direction='column'
412+ justify='center'
413+ align='center'
414+ // bgImage={bgCard}
415+ minW='200px'
416+ maxW='200px'
417+ minH='230px'
418+ borderRadius='15px'>
419+ <IconBox
420+ bg='white'
421+ color='white'
422+ borderRadius='50%'
423+ h='50px'
424+ w='50px'
425+ mb='12px'>
426+ <Icon as={AiFillStar} w='25px' h='25px' color='blue.500' />
427+ </IconBox>
428+ <Text
429+ fontSize='xl'
430+ fontWeight='bold'
431+ color='#fff'
432+ maxW='80%'
433+ textAlign='center'>
434+ Explore our utilities pages
435+ </Text>
436+ </Flex>
437+ <SimpleGrid templateColumns='1fr' width='100%'>
438+ {createAuthLinks(authObject.items)}
439+ </SimpleGrid>
440+ </Stack>
441+ </MenuList>
442+ </Menu>
443+ </Stack>
444+ <Stack
445+ direction='row'
446+ spacing='4px'
447+ align='center'
448+ color='#fff'
449+ fontWeight='bold'
450+ onMouseEnter={onOpenApplication}
451+ onMouseLeave={onCloseApplication}
452+ cursor='pointer'
453+ position='relative'>
454+ <Text fontSize='sm' color={mainText}>
455+ Application
456+ </Text>
457+ <Icon
458+ as={GoChevronDown}
459+ color={mainText}
460+ w='14px'
461+ h='14px'
462+ fontWeight='2000'
463+ />
464+ <Menu isOpen={isOpenApplication}>
465+ <MenuList
466+ bg={menuBg}
467+ p='22px'
468+ cursor='default'
469+ borderRadius='15px'
470+ position='absolute'
471+ top='30px'
472+ left='-10px'>
473+ <Grid templateColumns='1fr' gap='16px'>
474+ {createApplicationLinks(applicationsObject.items)}
475+ </Grid>
476+ </MenuList>
477+ </Menu>
478+ </Stack>
479+ <Stack
480+ direction='row'
481+ spacing='4px'
482+ align='center'
483+ color='#fff'
484+ fontWeight='bold'
485+ onMouseEnter={onOpenEcommerce}
486+ onMouseLeave={onCloseEcommerce}
487+ cursor='pointer'
488+ position='relative'>
489+ <Text fontSize='sm' color={mainText}>
490+ Ecommerce
491+ </Text>
492+ <Icon
493+ as={GoChevronDown}
494+ color={mainText}
495+ w='14px'
496+ h='14px'
497+ fontWeight='2000'
498+ />
499+ <Menu isOpen={isOpenEcommerce}>
500+ <MenuList
501+ bg={menuBg}
502+ p='22px'
503+ minW='350px'
504+ cursor='default'
505+ borderRadius='15px'
506+ position='absolute'
507+ top='30px'
508+ left='-10px'>
509+ <Grid templateColumns='repeat(2, 1fr)' gap='16px'>
510+ {createEcommerceLinks(ecommerceObject.items)}
511+ </Grid>
512+ </MenuList>
513+ </Menu>
514+ </Stack>
515+ </HStack>
516+ );
517+
518+ return (
519+ <SidebarContext.Provider value={{ sidebarWidth }}>
520+ <Flex
521+ position={navbarPosition}
522+ top='16px'
523+ left='50%'
524+ transform='translate(-50%, 0px)'
525+ background={navbarBg}
526+ boxShadow={navbarShadow}
527+ borderRadius='15px'
528+ px='16px'
529+ py='22px'
530+ mx='auto'
531+ width='1044px'
532+ maxW='90%'
533+ alignItems='center'
534+ zIndex='3'>
535+ <Flex w='100%' justifyContent={{ sm: "start", lg: "space-between" }}>
536+ {brand}
537+ <Box
538+ ms={{ base: "auto", lg: "0px" }}
539+ display={{ base: "flex", lg: "none" }}
540+ justifyContent='center'
541+ alignItems='center'>
542+ <SidebarResponsive
543+ logo={
544+ <Stack
545+ direction='row'
546+ spacing='12px'
547+ align='center'
548+ justify='center'>
549+ <Box
550+ w='1px'
551+ h='20px'
552+ bg={colorMode === "dark" ? "white" : "gray.700"}
553+ />
554+ </Stack>
555+ }
556+ logoText={props.logoText}
557+ secondary={props.secondary}
558+ routes={routes}
559+ {...rest}
560+ />
561+ </Box>
562+ {linksAuth}
563+ <Link href='https://www.horizon-ui.com/pro'>
564+ <Button
565+ bg={bgButton}
566+ color={colorButton}
567+ fontSize='xs'
568+ variant='no-effects'
569+ px='30px'
570+ display={{
571+ sm: "none",
572+ lg: "flex",
573+ }}>
574+ Buy Now
575+ </Button>
576+ </Link>
577+ </Flex>
578+ </Flex>
579+ </SidebarContext.Provider>
580+ );
581+}
582+
583+AuthNavbar.propTypes = {
584+ color: PropTypes.oneOf(["primary", "info", "success", "warning", "danger"]),
585+ brandText: PropTypes.string,
586+};
+327-0Client/src/components/navbar/NavbarLinksAdmin.js
@@ -0,0 +1,327 @@
1+// Chakra Imports
2+import {
3+ Avatar,
4+ Button,
5+ Flex,
6+ Icon,
7+ Image,
8+ Menu,
9+ MenuButton,
10+ MenuItem,
11+ MenuList,
12+ Text,
13+ useColorModeValue,
14+} from "@chakra-ui/react";
15+// Custom Components
16+import { ItemContent } from "components/menu/ItemContent";
17+import { SearchBar } from "components/navbar/searchBar/SearchBar";
18+import { SidebarResponsive } from "components/sidebar/Sidebar";
19+import PropTypes from "prop-types";
20+import { useEffect, useState } from "react";
21+// Assets
22+import { MdInfoOutline, MdNotificationsNone } from "react-icons/md";
23+import { FaEthereum } from "react-icons/fa";
24+import { Link, useNavigate } from "react-router-dom";
25+import { getApi } from "services/api";
26+import { toast } from "react-toastify";
27+import jwtDecode from "jwt-decode";
28+import { ThemeEditor } from "./ThemeEditor";
29+import FixedPlugin from "components/fixedPlugin/FixedPlugin";
30+import { useDispatch, useSelector } from "react-redux";
31+export default function HeaderLinks(props) {
32+ const { secondary, setOpenSidebar, openSidebar, routes } = props;
33+ // Chakra Color Mode
34+ const navbarIcon = useColorModeValue("gray.400", "white");
35+ let menuBg = useColorModeValue("white", "navy.800");
36+ const textColor = useColorModeValue("secondaryGray.900", "white");
37+ const textColorBrand = useColorModeValue("brand.700", "brand.400");
38+ const ethColor = useColorModeValue("gray.700", "white");
39+ const borderColor = useColorModeValue("#E6ECFA", "rgba(135, 140, 189, 0.3)");
40+ const ethBg = useColorModeValue("secondaryGray.300", "navy.900");
41+ const ethBox = useColorModeValue("white", "navy.800");
42+ const shadow = useColorModeValue(
43+ "14px 17px 40px 4px rgba(112, 144, 176, 0.18)",
44+ "14px 17px 40px 4px rgba(112, 144, 176, 0.06)"
45+ );
46+ // const borderButton = useColorModeValue('secondaryGray.500', 'whiteAlpha.200');
47+
48+ // const [loginUser, setLoginUser] = useState();
49+ const navigate = useNavigate();
50+ const dispatch = useDispatch();
51+ const userData = useSelector(state => state.user.user)
52+
53+ const data = typeof userData === 'string' ? JSON.parse(userData) : userData
54+ const user = data?.firstName + " " + data?.lastName;
55+ const userId = JSON.parse(localStorage.getItem("user"))?._id;
56+ const loginUser = useSelector((state) => state?.user?.user)
57+
58+
59+ const [isLogoutScheduled, setIsLogoutScheduled] = useState(false);
60+
61+ const logOut = (message) => {
62+ localStorage.clear();
63+ sessionStorage.clear();
64+ navigate("/auth");
65+ if (message) {
66+ toast.error(message);
67+ } else {
68+ toast.success("Log out Successfully");
69+ }
70+ setIsLogoutScheduled(true);
71+ };
72+
73+ useEffect(() => {
74+ const token =
75+ localStorage.getItem("token") || sessionStorage.getItem("token");
76+
77+ if (token) {
78+ try {
79+ const decodedToken = jwtDecode(token);
80+ const currentTime = Date.now() / 1000; // Convert milliseconds to seconds
81+ if (decodedToken.exp < currentTime) {
82+ if (!isLogoutScheduled) {
83+ logOut("Token has expired");
84+ }
85+ } else {
86+ // Schedule automatic logout when the token expires
87+ const timeToExpire = (decodedToken.exp - currentTime) * 1000; // Convert seconds to milliseconds
88+ setTimeout(() => {
89+ if (!isLogoutScheduled) {
90+ logOut("Token has expired");
91+ }
92+ }, timeToExpire);
93+ }
94+ } catch (error) {
95+ console.error("Error decoding token:", error);
96+ }
97+ }
98+ }, [isLogoutScheduled]);
99+
100+ return (
101+ <Flex
102+ w={{ sm: "100%", md: "auto" }}
103+ alignItems="center"
104+ justifyContent={"end"}
105+ flexDirection="row"
106+ bg={menuBg}
107+ flexWrap={secondary ? { base: "wrap", md: "nowrap" } : "unset"}
108+ p="6px"
109+ mt={2.5}
110+ borderRadius="30px"
111+ boxShadow={shadow}
112+ >
113+ {/* <SearchBar
114+ mb={secondary ? { base: "10px", md: "unset" } : "unset"}
115+ me="10px"
116+ borderRadius="30px"
117+ /> */}
118+
119+ <Flex
120+ bg={ethBg}
121+ display={secondary ? "flex" : "none"}
122+ borderRadius="30px"
123+ ms="auto"
124+ p="6px"
125+ align="center"
126+ me="6px"
127+ >
128+ <Flex
129+ align="center"
130+ justify="center"
131+ bg={ethBox}
132+ h="29px"
133+ w="29px"
134+ borderRadius="30px"
135+ me="7px"
136+ >
137+ <Icon color={ethColor} w="9px" h="14px" as={FaEthereum} />
138+ </Flex>
139+ <Text
140+ w="max-content"
141+ color={ethColor}
142+ fontSize="sm"
143+ fontWeight="700"
144+ me="6px"
145+ >
146+ 1,924
147+ <Text as="span" display={{ base: "none", md: "unset" }}>
148+ {" "}
149+ ETH
150+ </Text>
151+ </Text>
152+ </Flex>
153+
154+ <SidebarResponsive routes={routes} setOpenSidebar={setOpenSidebar} openSidebar={openSidebar} />
155+
156+ <Menu>
157+ <MenuButton p="0px">
158+ <Icon
159+ mt="6px"
160+ as={MdNotificationsNone}
161+ color={navbarIcon}
162+ w="18px"
163+ h="18px"
164+ me="10px"
165+ />
166+ </MenuButton>
167+ <MenuList
168+ boxShadow={shadow}
169+ p="20px"
170+ borderRadius="20px"
171+ bg={menuBg}
172+ border="none"
173+ mt="22px"
174+ me={{ base: "30px", md: "unset" }}
175+ minW={{ base: "unset", md: "400px", xl: "450px" }}
176+ maxW={{ base: "360px", md: "unset" }}
177+ >
178+ <Flex jusitfy="space-between" w="100%" mb="20px">
179+ <Text fontSize="md" fontWeight="600" color={textColor}>
180+ Notifications
181+ </Text>
182+ <Text
183+ fontSize="sm"
184+ fontWeight="500"
185+ color={textColorBrand}
186+ ms="auto"
187+ cursor="pointer"
188+ >
189+ Mark all read
190+ </Text>
191+ </Flex>
192+ <Flex flexDirection="column">
193+ <MenuItem
194+ _hover={{ bg: "none" }}
195+ _focus={{ bg: "none" }}
196+ px="0"
197+ borderRadius="8px"
198+ mb="10px"
199+ >
200+ <ItemContent info="Horizon UI Dashboard PRO" aName="Alicia" />
201+ </MenuItem>
202+ <MenuItem
203+ _hover={{ bg: "none" }}
204+ _focus={{ bg: "none" }}
205+ px="0"
206+ borderRadius="8px"
207+ mb="10px"
208+ >
209+ <ItemContent
210+ info="Horizon Design System Free"
211+ aName="Josh Henry"
212+ />
213+ </MenuItem>
214+ </Flex>
215+ </MenuList>
216+ </Menu>
217+ {/* <FixedPlugin /> */}
218+ {/* <ThemeEditor navbarIcon={navbarIcon} /> */}
219+
220+ <Menu style={{ zIndex: 1500 }}>
221+ <MenuButton p="0px">
222+ <Avatar
223+ _hover={{ cursor: "pointer" }}
224+ color="white"
225+ name={user || "Prolink Infotech"}
226+ bg="#11047A"
227+ size="sm"
228+ w="40px"
229+ h="40px"
230+ />
231+ </MenuButton>
232+ <MenuList
233+ boxShadow={shadow}
234+ p="0px"
235+ mt="10px"
236+ borderRadius="20px"
237+ bg={menuBg}
238+ border="none"
239+ >
240+ <Flex w="100%" mb="0px">
241+ <Text
242+ ps="20px"
243+ pt="16px"
244+ pb="10px"
245+ w="100%"
246+ borderBottom="1px solid"
247+ borderColor={borderColor}
248+ fontSize="sm"
249+ fontWeight="700"
250+ textTransform={"capitalize"}
251+ color={textColor}
252+ >
253+ 👋&nbsp; Hey, {user}
254+ </Text>
255+ </Flex>
256+
257+ <Flex flexDirection="column" p="10px">
258+ <MenuItem
259+ _hover={{ bg: "none" }}
260+ _focus={{ bg: "none" }}
261+ borderRadius="8px"
262+ px="14px"
263+ >
264+ <Text fontSize="sm" onClick={() => navigate(`/admin/`)}>
265+ Home
266+ </Text>
267+ </MenuItem>
268+
269+ {loginUser?.role === "superAdmin" &&
270+ <MenuItem
271+ _hover={{ bg: "none" }}
272+ _focus={{ bg: "none" }}
273+ borderRadius="8px"
274+ px="14px"
275+ >
276+ <Text
277+ fontSize="sm"
278+ onClick={() =>
279+ navigate('/admin-setting')
280+ }
281+ >
282+ Admin Settings
283+ </Text>
284+ </MenuItem>}
285+ <MenuItem
286+ _hover={{ bg: "none" }}
287+ _focus={{ bg: "none" }}
288+ borderRadius="8px"
289+ px="14px"
290+ >
291+ <Text
292+ fontSize="sm"
293+ onClick={() =>
294+ navigate(
295+ `/userView/${JSON.parse(localStorage.getItem("user"))?._id}`
296+ )
297+ }
298+ >
299+ Profile Settings
300+ </Text>
301+ </MenuItem>
302+ {/*<MenuItem _hover={{ bg: 'none' }} _focus={{ bg: 'none' }} borderRadius="8px" px="14px">
303+ <Text fontSize="sm">Newsletter Settings</Text>
304+ </MenuItem> */}
305+ <MenuItem
306+ _hover={{ bg: "none" }}
307+ onClick={logOut}
308+ _focus={{ bg: "none" }}
309+ color="red.400"
310+ borderRadius="8px"
311+ px="14px"
312+ >
313+ <Text fontSize="sm">Log out</Text>
314+ </MenuItem>
315+ </Flex>
316+ </MenuList>
317+ </Menu>
318+ </Flex>
319+ );
320+}
321+
322+HeaderLinks.propTypes = {
323+ variant: PropTypes.string,
324+ fixed: PropTypes.bool,
325+ secondary: PropTypes.bool,
326+ onOpen: PropTypes.func,
327+};
+157-0Client/src/components/navbar/NavbarRTL.js
@@ -0,0 +1,157 @@
1+// Chakra Imports
2+import {
3+ Box,
4+ Breadcrumb,
5+ BreadcrumbItem,
6+ BreadcrumbLink,
7+ Flex,
8+ Link,
9+ Text,
10+ useColorModeValue,
11+} from "@chakra-ui/react";
12+import PropTypes from "prop-types";
13+import React, { useState, useEffect } from "react";
14+import AdminNavbarLinks from "components/navbar/NavbarLinksAdmin";
15+
16+export default function AdminNavbar(props) {
17+ const [scrolled, setScrolled] = useState(false);
18+
19+ useEffect(() => {
20+ window.addEventListener("scroll", changeNavbar);
21+
22+ return () => {
23+ window.removeEventListener("scroll", changeNavbar);
24+ };
25+ });
26+
27+ const { secondary, message, brandText } = props;
28+
29+ // Here are all the props that may change depending on navbar's type or state.(secondary, variant, scrolled)
30+ let mainText = useColorModeValue("navy.700", "white");
31+ let secondaryText = useColorModeValue("gray.700", "white");
32+ let navbarPosition = "fixed";
33+ let navbarFilter = "none";
34+ let navbarBackdrop = "blur(20px)";
35+ let navbarShadow = "none";
36+ let navbarBg = useColorModeValue(
37+ "rgba(244, 247, 254, 0.2)",
38+ "rgba(11,20,55,0.5)"
39+ );
40+ let navbarBorder = "transparent";
41+ let secondaryMargin = "0px";
42+ let paddingX = "15px";
43+ let gap = "0px";
44+ const changeNavbar = () => {
45+ if (window.scrollY > 1) {
46+ setScrolled(true);
47+ } else {
48+ setScrolled(false);
49+ }
50+ };
51+
52+ return (
53+ <Box
54+ position={navbarPosition}
55+ boxShadow={navbarShadow}
56+ bg={navbarBg}
57+ borderColor={navbarBorder}
58+ filter={navbarFilter}
59+ backdropFilter={navbarBackdrop}
60+ backgroundPosition='center'
61+ backgroundSize='cover'
62+ borderRadius='16px'
63+ borderWidth='1.5px'
64+ borderStyle='solid'
65+ transitionDelay='0s, 0s, 0s, 0s'
66+ transitionDuration=' 0.25s, 0.25s, 0.25s, 0s'
67+ transition-property='box-shadow, background-color, filter, border'
68+ transitionTimingFunction='linear, linear, linear, linear'
69+ alignItems={{ xl: "center" }}
70+ display={secondary ? "block" : "flex"}
71+ minH='75px'
72+ justifyContent={{ xl: "center" }}
73+ lineHeight='25.6px'
74+ mx='auto'
75+ mt={secondaryMargin}
76+ pb='8px'
77+ left={{ base: "12px", md: "30px", lg: "30px", xl: "30px" }}
78+ px={{
79+ sm: paddingX,
80+ md: "10px",
81+ }}
82+ ps={{
83+ xl: "12px",
84+ }}
85+ pt='8px'
86+ top={{ base: "12px", md: "16px", xl: "18px" }}
87+ w={{
88+ base: "calc(100vw - 6%)",
89+ md: "calc(100vw - 8%)",
90+ lg: "calc(100vw - 6%)",
91+ xl: "calc(100vw - 350px)",
92+ "2xl": "calc(100vw - 365px)",
93+ }}>
94+ <Flex
95+ w='100%'
96+ flexDirection={{
97+ sm: "column",
98+ md: "row",
99+ }}
100+ alignItems={{ xl: "center" }}
101+ mb={gap}>
102+ <Box mb={{ sm: "8px", md: "0px" }}>
103+ <Breadcrumb>
104+ <BreadcrumbItem color={secondaryText} fontSize='sm' mb='5px'>
105+ <BreadcrumbLink href='#' color={secondaryText}>
106+ Pages
107+ </BreadcrumbLink>
108+ </BreadcrumbItem>
109+
110+ <BreadcrumbItem color={secondaryText} fontSize='sm'>
111+ <BreadcrumbLink href='#' color={secondaryText}>
112+ {brandText}
113+ </BreadcrumbLink>
114+ </BreadcrumbItem>
115+ </Breadcrumb>
116+ {/* Here we create navbar brand, based on route name */}
117+ <Link
118+ color={mainText}
119+ href='#'
120+ bg='inherit'
121+ borderRadius='inherit'
122+ fontWeight='bold'
123+ fontSize='34px'
124+ _hover={{ color: { mainText } }}
125+ _active={{
126+ bg: "inherit",
127+ transform: "none",
128+ borderColor: "transparent",
129+ }}
130+ _focus={{
131+ boxShadow: "none",
132+ }}>
133+ {brandText}
134+ </Link>
135+ </Box>
136+ <Box ms='auto' w={{ sm: "100%", md: "unset" }}>
137+ <AdminNavbarLinks
138+ onOpen={props.onOpen}
139+ logoText={props.logoText}
140+ secondary={props.secondary}
141+ fixed={props.fixed}
142+ scrolled={scrolled}
143+ />
144+ </Box>
145+ </Flex>
146+ {secondary ? <Text color='white'>{message}</Text> : null}
147+ </Box>
148+ );
149+}
150+
151+AdminNavbar.propTypes = {
152+ brandText: PropTypes.string,
153+ variant: PropTypes.string,
154+ secondary: PropTypes.bool,
155+ fixed: PropTypes.bool,
156+ onOpen: PropTypes.func,
157+};
+48-0Client/src/components/navbar/ThemeEditor.js
@@ -0,0 +1,48 @@
1+import React from 'react'
2+import {
3+ ThemeEditor as ThemeEditorContainer,
4+ ThemeEditorDrawer,
5+ ThemeEditorColors,
6+ ThemeEditorFontSizes
7+} from '@hypertheme-editor/chakra-ui'
8+import { Button, Icon } from '@chakra-ui/react'
9+import { CgColorPicker } from 'react-icons/cg'
10+import { ImFontSize } from 'react-icons/im'
11+import { MdPalette } from 'react-icons/md'
12+
13+export function ThemeEditor(props) {
14+ return (
15+ <ThemeEditorContainer>
16+ <ThemeEditorButton {...props} />
17+ <ThemeEditorDrawer hideUpgradeToPro>
18+ <ThemeEditorColors icon={CgColorPicker} title="Colors" />
19+ <ThemeEditorFontSizes icon={ImFontSize} title="Font Sizes" />
20+ </ThemeEditorDrawer>
21+ </ThemeEditorContainer>
22+ )
23+}
24+
25+function ThemeEditorButton({ onOpen, navbarIcon, ...rest }) {
26+ return (
27+ <Button
28+ variant='no-hover'
29+ bg='transparent'
30+ p='0px'
31+ minW='unset'
32+ minH='unset'
33+ h='18px'
34+ w='max-content'
35+ _focus={{ boxShadow: 'none' }}
36+ onClick={onOpen}
37+ {...rest}
38+ >
39+ <Icon
40+ me='10px'
41+ h='18px'
42+ w='18px'
43+ color={navbarIcon}
44+ as={MdPalette}
45+ />
46+ </Button>
47+ )
48+}
+51-0Client/src/components/navbar/searchBar/SearchBar.js
@@ -0,0 +1,51 @@
1+import React from "react";
2+import {
3+ IconButton,
4+ Input,
5+ InputGroup,
6+ InputLeftElement,
7+ useColorModeValue,
8+} from "@chakra-ui/react";
9+import { SearchIcon } from "@chakra-ui/icons";
10+export function SearchBar(props) {
11+ // Pass the computed styles into the `__css` prop
12+ const { variant, background, children, placeholder, borderRadius, ...rest } =
13+ props;
14+ // Chakra Color Mode
15+ const searchIconColor = useColorModeValue("gray.700", "white");
16+ const inputBg = useColorModeValue("secondaryGray.300", "navy.900");
17+ const inputText = useColorModeValue("gray.700", "gray.100");
18+ return (
19+ <InputGroup w={{ base: "100%", md: "200px" }} {...rest}>
20+ <InputLeftElement
21+ children={
22+ <IconButton
23+ bg='inherit'
24+ borderRadius='inherit'
25+ _hover='none'
26+ _active={{
27+ bg: "inherit",
28+ transform: "none",
29+ borderColor: "transparent",
30+ }}
31+ _focus={{
32+ boxShadow: "none",
33+ }}
34+ icon={
35+ <SearchIcon color={searchIconColor} w='15px' h='15px' />
36+ }></IconButton>
37+ }
38+ />
39+ <Input
40+ variant='search'
41+ fontSize='sm'
42+ bg={background ? background : inputBg}
43+ color={inputText}
44+ fontWeight='500'
45+ _placeholder={{ color: "gray.400", fontSize: "14px" }}
46+ borderRadius={borderRadius ? borderRadius : "30px"}
47+ placeholder={placeholder ? placeholder : "Search..."}
48+ />
49+ </InputGroup>
50+ );
51+}
+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+ Email
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