pramod_Task-#251: A "Notes" section appears in the contact view modal, allowing users to add a note for the selected contact. #124
166
package-lock.json
generated
166
package-lock.json
generated
@ -27,6 +27,7 @@
|
||||
"react-apexcharts": "^1.7.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-hook-form": "^7.54.2",
|
||||
"react-quill": "^2.0.0",
|
||||
"react-redux": "^9.2.0",
|
||||
"react-router-dom": "^6.20.1",
|
||||
"react-toastify": "^11.0.2",
|
||||
@ -1494,6 +1495,15 @@
|
||||
"integrity": "sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==",
|
||||
"devOptional": true
|
||||
},
|
||||
"node_modules/@types/quill": {
|
||||
"version": "1.3.10",
|
||||
"resolved": "https://registry.npmjs.org/@types/quill/-/quill-1.3.10.tgz",
|
||||
"integrity": "sha512-IhW3fPW+bkt9MLNlycw8u8fWb7oO7W5URC9MfZYHBlA24rex9rs23D5DETChu1zvgVdc5ka64ICjJOgQMr6Shw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"parchment": "^1.1.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/react": {
|
||||
"version": "18.3.16",
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.16.tgz",
|
||||
@ -2100,7 +2110,6 @@
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz",
|
||||
"integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"call-bind-apply-helpers": "^1.0.0",
|
||||
"es-define-property": "^1.0.0",
|
||||
@ -2118,7 +2127,6 @@
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.1.tgz",
|
||||
"integrity": "sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0",
|
||||
"function-bind": "^1.1.2"
|
||||
@ -2131,7 +2139,6 @@
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.2.tgz",
|
||||
"integrity": "sha512-0lk0PHFe/uz0vl527fG9CgdE9WdafjDbCXvBbs+LUv000TVt2Jjhqbs4Jwm8gz070w8xXyEAxrPOMullsxXeGg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"call-bind": "^1.0.8",
|
||||
"get-intrinsic": "^1.2.5"
|
||||
@ -2210,6 +2217,15 @@
|
||||
"node": ">=6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/clone": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz",
|
||||
"integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/clsx": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
|
||||
@ -2373,6 +2389,26 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/deep-equal": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.2.tgz",
|
||||
"integrity": "sha512-5tdhKF6DbU7iIzrIOa1AOUt39ZRm13cmL1cGEh//aqR8x9+tNfbywRf0n5FD/18OKMdo7DNEtrX2t22ZAkI+eg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"is-arguments": "^1.1.1",
|
||||
"is-date-object": "^1.0.5",
|
||||
"is-regex": "^1.1.4",
|
||||
"object-is": "^1.1.5",
|
||||
"object-keys": "^1.1.1",
|
||||
"regexp.prototype.flags": "^1.5.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/deep-is": {
|
||||
"version": "0.1.4",
|
||||
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
|
||||
@ -2383,7 +2419,6 @@
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz",
|
||||
"integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"es-define-property": "^1.0.0",
|
||||
"es-errors": "^1.3.0",
|
||||
@ -2400,7 +2435,6 @@
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz",
|
||||
"integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"define-data-property": "^1.0.1",
|
||||
"has-property-descriptors": "^1.0.0",
|
||||
@ -2482,7 +2516,6 @@
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.0.tgz",
|
||||
"integrity": "sha512-9+Sj30DIu+4KvHqMfLUGLFYL2PkURSYMVXJyXe92nFRvlYq5hBjLEhblKB+vkd/WVlUYMWigiY07T91Fkk0+4A==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"call-bind-apply-helpers": "^1.0.0",
|
||||
"es-errors": "^1.3.0",
|
||||
@ -2575,7 +2608,6 @@
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
|
||||
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
@ -2584,7 +2616,6 @@
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
|
||||
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
@ -2949,6 +2980,12 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/eventemitter3": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-2.0.3.tgz",
|
||||
"integrity": "sha512-jLN68Dx5kyFHaePoXWPsCGW5qdyZQtLYHkxkg02/Mz6g0kYpDx4FyP6XfArhQdlOC4b8Mv+EMxPo/8La7Tzghg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/events": {
|
||||
"version": "3.3.0",
|
||||
"resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz",
|
||||
@ -2959,11 +2996,23 @@
|
||||
"node": ">=0.8.x"
|
||||
}
|
||||
},
|
||||
"node_modules/extend": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
|
||||
"integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/fast-deep-equal": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
||||
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
|
||||
},
|
||||
"node_modules/fast-diff": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.1.2.tgz",
|
||||
"integrity": "sha512-KaJUt+M9t1qaIteSvjc6P3RbMdXsNhK61GRftR6SNxqmhthcd9MGIi4T+o0jD8LUSpSnSKXE20nLtJ3fOHxQig==",
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/fast-json-stable-stringify": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
|
||||
@ -3123,7 +3172,6 @@
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
||||
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
|
||||
"dev": true,
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
@ -3150,7 +3198,6 @@
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz",
|
||||
"integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==",
|
||||
"dev": true,
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
@ -3167,7 +3214,6 @@
|
||||
"version": "1.2.5",
|
||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.5.tgz",
|
||||
"integrity": "sha512-Y4+pKa7XeRUPWFNvOOYHkRYrfzW07oraURSvjDmRVOJ748OrVmeXtpE4+GCEHncjCjkTxPNRt8kEbxDhsn6VTg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"call-bind-apply-helpers": "^1.0.0",
|
||||
"dunder-proto": "^1.0.0",
|
||||
@ -3277,7 +3323,6 @@
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
|
||||
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
@ -3319,7 +3364,6 @@
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz",
|
||||
"integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"es-define-property": "^1.0.0"
|
||||
},
|
||||
@ -3346,7 +3390,6 @@
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
|
||||
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
@ -3358,7 +3401,6 @@
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
|
||||
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"has-symbols": "^1.0.3"
|
||||
},
|
||||
@ -3373,7 +3415,6 @@
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
||||
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"function-bind": "^1.1.2"
|
||||
},
|
||||
@ -3460,6 +3501,22 @@
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/is-arguments": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.2.0.tgz",
|
||||
"integrity": "sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"call-bound": "^1.0.2",
|
||||
"has-tostringtag": "^1.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/is-array-buffer": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz",
|
||||
@ -3568,7 +3625,6 @@
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz",
|
||||
"integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"has-tostringtag": "^1.0.0"
|
||||
},
|
||||
@ -3683,7 +3739,6 @@
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.0.tgz",
|
||||
"integrity": "sha512-B6ohK4ZmoftlUe+uvenXSbPJFo6U37BH7oO1B3nQH8f/7h27N56s85MhUtbFJAziz5dcmuR3i8ovUl35zp8pFA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"call-bind": "^1.0.7",
|
||||
"gopd": "^1.1.0",
|
||||
@ -4026,6 +4081,12 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/lodash": {
|
||||
"version": "4.17.21",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lodash.merge": {
|
||||
"version": "4.6.2",
|
||||
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
|
||||
@ -4166,11 +4227,26 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/object-is": {
|
||||
"version": "1.1.6",
|
||||
"resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.6.tgz",
|
||||
"integrity": "sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"call-bind": "^1.0.7",
|
||||
"define-properties": "^1.2.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/object-keys": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
|
||||
"integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
@ -4312,6 +4388,12 @@
|
||||
"integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==",
|
||||
"license": "(MIT AND Zlib)"
|
||||
},
|
||||
"node_modules/parchment": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/parchment/-/parchment-1.1.4.tgz",
|
||||
"integrity": "sha512-J5FBQt/pM2inLzg4hEWmzQx/8h8D0CiDxaG3vyp9rKrQRSDgBlhjdP5jQGgosEajXPSQouXGHOmVdgo7QmJuOg==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/parent-module": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
|
||||
@ -4468,6 +4550,34 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"node_modules/quill": {
|
||||
"version": "1.3.7",
|
||||
"resolved": "https://registry.npmjs.org/quill/-/quill-1.3.7.tgz",
|
||||
"integrity": "sha512-hG/DVzh/TiknWtE6QmWAF/pxoZKYxfe3J/d/+ShUWkDvvkZQVTPeVmUJVu1uE6DDooC4fWTiCLh84ul89oNz5g==",
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"clone": "^2.1.1",
|
||||
"deep-equal": "^1.0.1",
|
||||
"eventemitter3": "^2.0.3",
|
||||
"extend": "^3.0.2",
|
||||
"parchment": "^1.1.4",
|
||||
"quill-delta": "^3.6.2"
|
||||
}
|
||||
},
|
||||
"node_modules/quill-delta": {
|
||||
"version": "3.6.3",
|
||||
"resolved": "https://registry.npmjs.org/quill-delta/-/quill-delta-3.6.3.tgz",
|
||||
"integrity": "sha512-wdIGBlcX13tCHOXGMVnnTVFtGRLoP0imqxM696fIPwIf5ODIYUHIvHbZcyvGlZFiFhK5XzDC2lpjbxRhnM05Tg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"deep-equal": "^1.0.1",
|
||||
"extend": "^3.0.2",
|
||||
"fast-diff": "1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/randombytes": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
|
||||
@ -4535,6 +4645,21 @@
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
|
||||
},
|
||||
"node_modules/react-quill": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/react-quill/-/react-quill-2.0.0.tgz",
|
||||
"integrity": "sha512-4qQtv1FtCfLgoD3PXAur5RyxuUbPXQGOHgTlFie3jtxp43mXDtzCKaOgQ3mLyZfi1PUlyjycfivKelFhy13QUg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/quill": "^1.3.10",
|
||||
"lodash": "^4.17.4",
|
||||
"quill": "^1.3.7"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16 || ^17 || ^18",
|
||||
"react-dom": "^16 || ^17 || ^18"
|
||||
}
|
||||
},
|
||||
"node_modules/react-redux": {
|
||||
"version": "9.2.0",
|
||||
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz",
|
||||
@ -4651,7 +4776,6 @@
|
||||
"version": "1.5.3",
|
||||
"resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.3.tgz",
|
||||
"integrity": "sha512-vqlC04+RQoFalODCbCumG2xIOvapzVMHwsyIGM/SIE8fRhFFsXeH8/QQ+s0T0kDAhKc4k30s73/0ydkHQz6HlQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"call-bind": "^1.0.7",
|
||||
"define-properties": "^1.2.1",
|
||||
@ -4946,7 +5070,6 @@
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
|
||||
"integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"define-data-property": "^1.1.4",
|
||||
"es-errors": "^1.3.0",
|
||||
@ -4963,7 +5086,6 @@
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz",
|
||||
"integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"define-data-property": "^1.1.4",
|
||||
"es-errors": "^1.3.0",
|
||||
|
@ -30,6 +30,7 @@
|
||||
"react-apexcharts": "^1.7.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-hook-form": "^7.54.2",
|
||||
"react-quill": "^2.0.0",
|
||||
"react-redux": "^9.2.0",
|
||||
"react-router-dom": "^6.20.1",
|
||||
"react-toastify": "^11.0.2",
|
||||
|
11
public/assets/vendor/css/core.css
vendored
11
public/assets/vendor/css/core.css
vendored
@ -16889,7 +16889,8 @@ li:not(:first-child) .dropdown-item,
|
||||
box-shadow: var(--bs-box-shadow-xs);
|
||||
filter: none;
|
||||
opacity: 1;
|
||||
transform: translate(23px, -25px);
|
||||
transform: translate(6px, -9px);
|
||||
z-index: 1056;
|
||||
border-radius: 0.25rem;
|
||||
transition: all 0.23s ease 0.1s;
|
||||
/* For hover effect of close btn */
|
||||
@ -16899,18 +16900,18 @@ li:not(:first-child) .dropdown-item,
|
||||
transition: none;
|
||||
}
|
||||
}
|
||||
.modal .btn-close:hover,
|
||||
/* .modal .btn-close:hover,
|
||||
.modal .btn-close:focus,
|
||||
.modal .btn-close:active {
|
||||
opacity: 1;
|
||||
outline: 0;
|
||||
transform: translate(20px, -20px);
|
||||
}
|
||||
:dir(rtl) .modal .btn-close:hover,
|
||||
} */
|
||||
/* :dir(rtl) .modal .btn-close:hover,
|
||||
:dir(rtl) .modal .btn-close:focus,
|
||||
:dir(rtl) .modal .btn-close:active {
|
||||
transform: translate(26px, -20px);
|
||||
}
|
||||
} */
|
||||
.modal .btn-close::before {
|
||||
display: block;
|
||||
background-color: var(--bs-secondary-color);
|
||||
|
@ -1,61 +1,77 @@
|
||||
import React from "react";
|
||||
import Avatar from "../common/Avatar";
|
||||
|
||||
const CardViewDirectory = ({ contact,setSelectedContact , setIsOpenModal}) => {
|
||||
const CardViewDirectory = ({ contact, setSelectedContact, setIsOpenModal,setOpen_contact,setIsOpenModalNote }) => {
|
||||
return (
|
||||
<div class="card text-start border-1">
|
||||
<div class="card-body d-flex justify-content-between px-1 py-2">
|
||||
<div className="d-flex align-items-center">
|
||||
<Avatar
|
||||
size="xs"
|
||||
firstName={
|
||||
(contact?.name || "").trim().split(" ")[0]?.charAt(0) || ""
|
||||
}
|
||||
lastName={
|
||||
(contact?.name || "").trim().split(" ")[1]?.charAt(0) || ""
|
||||
}
|
||||
/>{" "}
|
||||
<p className="m-0">{contact.name}</p>
|
||||
</div>
|
||||
<div>
|
||||
<div className="dropdown z-2 ">
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-xs btn-icon btn-text-secondary rounded-pill dropdown-toggle hide-arrow p-0 m-0"
|
||||
data-bs-toggle="dropdown"
|
||||
aria-expanded="false"
|
||||
>
|
||||
<i
|
||||
className="bx bx-dots-vertical-rounded bx-sm text-muted p-0"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-offset="0,8"
|
||||
data-bs-placement="top"
|
||||
data-bs-custom-class="tooltip-dark"
|
||||
title="More Action"
|
||||
></i>
|
||||
</button>
|
||||
<ul className="dropdown-menu dropdown-menu-end w-auto">
|
||||
<li onClick={() =>
|
||||
{
|
||||
setSelectedContact(contact)
|
||||
setIsOpenModal(true)
|
||||
}}>
|
||||
<a className="dropdown-item px-2 py-0">
|
||||
<i className="bx bx-pencil bx-xs me-2"></i>
|
||||
<span className="align-left small-text">Modify</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a className="dropdown-item px-2 py-0">
|
||||
<i className="bx bx-trash bx-xs me-2"></i>
|
||||
<span className="align-left small-text">Delete</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<div className="card text-start border-1">
|
||||
<div className="card-body px-1 py-2 pb-0">
|
||||
<div className="d-flex justify-content-between">
|
||||
<div className="d-flex align-items-center">
|
||||
<Avatar
|
||||
size="xs"
|
||||
firstName={
|
||||
(contact?.name || "").trim().split(" ")[0]?.charAt(0) || ""
|
||||
}
|
||||
lastName={
|
||||
(contact?.name || "").trim().split(" ")[1]?.charAt(0) || ""
|
||||
}
|
||||
/>{" "}
|
||||
<p className="m-0">{contact.name}</p>
|
||||
</div>
|
||||
<div>
|
||||
<div className="dropdown z-2 ">
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-xs btn-icon btn-text-secondary rounded-pill dropdown-toggle hide-arrow p-0 m-0"
|
||||
data-bs-toggle="dropdown"
|
||||
aria-expanded="false"
|
||||
>
|
||||
<i
|
||||
className="bx bx-dots-vertical-rounded bx-sm text-muted p-0"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-offset="0,8"
|
||||
data-bs-placement="top"
|
||||
data-bs-custom-class="tooltip-dark"
|
||||
title="More Action"
|
||||
></i>
|
||||
</button>
|
||||
<ul className="dropdown-menu dropdown-menu-end w-auto">
|
||||
<li
|
||||
onClick={() => {
|
||||
setSelectedContact(contact);
|
||||
setIsOpenModal(true);
|
||||
}}
|
||||
>
|
||||
<a className="dropdown-item px-2 py-0">
|
||||
<i className="bx bx-pencil bx-xs me-2"></i>
|
||||
<span className="align-left small-text">Modify</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a className="dropdown-item px-2 py-0">
|
||||
<i className="bx bx-trash bx-xs me-2"></i>
|
||||
<span className="align-left small-text">Delete</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ul className="list-inline m-0 ps-4">
|
||||
<li className="list-inline-item me-1" style={{fontSize:"10px"}}>
|
||||
<i className="bx bx-building bx-xs"></i>
|
||||
</li>
|
||||
<li className="list-inline-item" style={{fontSize:"10px"}}>
|
||||
{contact.organization}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="card-footer text-start px-1 py-1">
|
||||
<div className="card-footer text-start px-1 py-1" onClick={() =>
|
||||
{
|
||||
setIsOpenModalNote(true)
|
||||
setOpen_contact(contact)
|
||||
}}>
|
||||
<hr className="my-0" />
|
||||
{contact.contactEmails[0] && (
|
||||
<ul className="list-inline my-1 ">
|
||||
@ -80,12 +96,6 @@ const CardViewDirectory = ({ contact,setSelectedContact , setIsOpenModal}) => {
|
||||
)}
|
||||
|
||||
<ul className="list-inline m-0">
|
||||
<li className="list-inline-item me-2">
|
||||
<i className="bx bx-building bx-xs"></i>
|
||||
</li>
|
||||
<li className="list-inline-item small-text">
|
||||
{contact.organization}
|
||||
</li>
|
||||
<li className="list-inline-item me-2">
|
||||
<i className="bx bx-merge bx-xs"></i>
|
||||
</li>
|
||||
|
@ -42,16 +42,16 @@ export const ContactSchema = z
|
||||
bucketIds: z.array(z.string()).optional(),
|
||||
})
|
||||
|
||||
.refine((data) => {
|
||||
const hasValidEmail = (data.contactEmails || []).some(
|
||||
(e) => e.emailAddress?.trim() !== ""
|
||||
);
|
||||
const hasValidPhone = (data.contactPhones || []).some(
|
||||
(p) => p.phoneNumber?.trim() !== ""
|
||||
);
|
||||
// .refine((data) => {
|
||||
// const hasValidEmail = (data.contactEmails || []).some(
|
||||
// (e) => e.emailAddress?.trim() !== ""
|
||||
// );
|
||||
// const hasValidPhone = (data.contactPhones || []).some(
|
||||
// (p) => p.phoneNumber?.trim() !== ""
|
||||
// );
|
||||
|
||||
return hasValidEmail || hasValidPhone;
|
||||
}, {
|
||||
message: "At least one contact (email or phone) is required",
|
||||
path: ["contactPhone"],
|
||||
});
|
||||
// return hasValidEmail || hasValidPhone;
|
||||
// }, {
|
||||
// message: "At least one contact (email or phone) is required",
|
||||
// path: ["contactPhone"],
|
||||
// });
|
||||
|
@ -204,23 +204,22 @@ useEffect(() => {
|
||||
placeholder="email@example.com"
|
||||
/>
|
||||
{index === emailFields.length - 1 ? (
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-xs btn-primary ms-1"
|
||||
onClick={handleAddEmail}
|
||||
style={{ width: "24px", height: "24px" }}
|
||||
>
|
||||
<i className="bx bx-plus-circle bx-xs" />
|
||||
</button>
|
||||
// <button
|
||||
// type="button"
|
||||
// className="btn btn-xs btn-primary ms-1"
|
||||
// onClick={handleAddEmail}
|
||||
// style={{ width: "24px", height: "24px" }}
|
||||
// >
|
||||
<i className="bx bx-plus-circle bx-xs ms-1 cursor-pointer text-primary" onClick={handleAddEmail} />
|
||||
) : (
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-xs btn-danger ms-1 p-0"
|
||||
onClick={() => removeEmail(index)}
|
||||
style={{ width: "24px", height: "24px" }}
|
||||
>
|
||||
<i className="bx bx-minus-circle bx-xs" />
|
||||
</button>
|
||||
// <button
|
||||
// type="button"
|
||||
// className="btn btn-xs btn-danger ms-1 p-0"
|
||||
// onClick={() => removeEmail(index)}
|
||||
// style={{ width: "24px", height: "24px" }}
|
||||
// >
|
||||
<i className="bx bx-minus-circle bx-xs ms-1 cursor-pointer text-primary" onClick={() => removeEmail(index)} />
|
||||
|
||||
)}
|
||||
</div>
|
||||
{errors.contactEmails?.[index]?.emailAddress && (
|
||||
@ -264,23 +263,23 @@ useEffect(() => {
|
||||
placeholder="9876543210"
|
||||
/>
|
||||
{index === phoneFields.length - 1 ? (
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-xs btn-primary ms-1"
|
||||
onClick={handleAddPhone}
|
||||
style={{ width: "24px", height: "24px" }}
|
||||
>
|
||||
<i className="bx bx-plus-circle bx-xs" />
|
||||
</button>
|
||||
// <button
|
||||
// type="button"
|
||||
// className="btn btn-xs btn-primary ms-1"
|
||||
// onClick={handleAddPhone}
|
||||
// style={{ width: "24px", height: "24px" }}
|
||||
// >
|
||||
<i className="bx bx-plus-circle bx-xs ms-1 cursor-pointer text-primary" onClick={handleAddPhone} />
|
||||
|
||||
) : (
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-xs btn-danger ms-1"
|
||||
onClick={() => removePhone(index)}
|
||||
style={{ width: "24px", height: "24px" }}
|
||||
>
|
||||
<i className="bx bx-minus-circle bx-xs" />
|
||||
</button>
|
||||
// <button
|
||||
// type="button"
|
||||
// className="btn btn-xs btn-danger ms-1"
|
||||
// onClick={() => removePhone(index)}
|
||||
// style={{ width: "24px", height: "24px" }}
|
||||
// >
|
||||
<i className="bx bx-minus-circle bx-xs ms-1 cursor-pointer text-danager" onClick={() => removePhone(index)} />
|
||||
|
||||
)}
|
||||
</div>
|
||||
{errors.contactPhones?.[index]?.phoneNumber && (
|
||||
|
183
src/components/Directory/NoteCardDirectory.jsx
Normal file
183
src/components/Directory/NoteCardDirectory.jsx
Normal file
@ -0,0 +1,183 @@
|
||||
import React, { useState } from "react";
|
||||
import ReactQuill from "react-quill";
|
||||
import moment from "moment";
|
||||
import Avatar from "../common/Avatar";
|
||||
import { DirectoryRepository } from "../../repositories/DirectoryRepository";
|
||||
import showToast from "../../services/toastService";
|
||||
import { cacheData, getCachedData } from "../../slices/apiDataManager";
|
||||
import "../common/TextEditor/Editor.css";
|
||||
|
||||
const NoteCardDirectory = ({ noteItem, contactId, setProfileContact }) => {
|
||||
const [editing, setEditing] = useState(false);
|
||||
const [editorValue, setEditorValue] = useState(noteItem.note);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [isDeleting, setIsDeleting] = useState(false);
|
||||
const handleUpdateNote = async () => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
const payload = {
|
||||
id: noteItem.id,
|
||||
note: editorValue,
|
||||
contactId: contactId,
|
||||
};
|
||||
|
||||
const response = await DirectoryRepository.UpdateNote(
|
||||
noteItem.id,
|
||||
payload
|
||||
);
|
||||
setProfileContact((prev) => ({
|
||||
...prev,
|
||||
notes: prev.notes.map((note) =>
|
||||
note.id === noteItem.id ? response?.data : note
|
||||
),
|
||||
}));
|
||||
|
||||
const cached_contactProfile = getCachedData("Contact Profile");
|
||||
|
||||
if (
|
||||
cached_contactProfile &&
|
||||
cached_contactProfile.contactId === contactId
|
||||
) {
|
||||
const updatedProfile = {
|
||||
...cached_contactProfile,
|
||||
data: {
|
||||
...cached_contactProfile?.data,
|
||||
notes: cached_contactProfile?.data?.notes.map((note) =>
|
||||
note.id === noteItem.id ? response?.data : note
|
||||
),
|
||||
},
|
||||
};
|
||||
cacheData("Contact Profile", updatedProfile);
|
||||
}
|
||||
setEditing(false);
|
||||
setIsLoading(false);
|
||||
showToast("Note Updated successfully", "success");
|
||||
} catch (error) {
|
||||
setIsLoading(false);
|
||||
const msg =
|
||||
error.reponse.data.message ||
|
||||
error.message ||
|
||||
"Error occured during API calling.";
|
||||
showToast("Failed to update note", "error");
|
||||
}
|
||||
};
|
||||
|
||||
const handleDeleteNote = async () => {
|
||||
try {
|
||||
setIsDeleting(true);
|
||||
const resp = await DirectoryRepository.DeleteNote(noteItem.id);
|
||||
setProfileContact((prev) => ({
|
||||
...prev,
|
||||
notes: prev.notes.filter((note) => note.id !== noteItem.id),
|
||||
}));
|
||||
|
||||
const cachedContactProfile = getCachedData("Contact Profile");
|
||||
|
||||
if (
|
||||
cachedContactProfile &&
|
||||
cachedContactProfile.contactId === contactId
|
||||
) {
|
||||
const updatedCache = {
|
||||
...cachedContactProfile,
|
||||
data: {
|
||||
...cachedContactProfile?.data,
|
||||
notes: (cachedContactProfile?.data?.notes || []).filter(
|
||||
(note) => note.id !== noteItem.id
|
||||
),
|
||||
},
|
||||
};
|
||||
|
||||
cacheData("Contact Profile", updatedCache);
|
||||
}
|
||||
setIsDeleting(false);
|
||||
showToast("Note Deleted Successfully", "success");
|
||||
} catch (error) {
|
||||
setIsDeleting(false);
|
||||
const msg =
|
||||
error.response?.data?.message ||
|
||||
error.message ||
|
||||
"Error occured during API calling";
|
||||
showToast(msg, "error");
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className="card p-1 shadow-sm border-1 mb-2 conntactNote"
|
||||
style={{ width: "100%", minWidth: "300px", borderRadius: "0px" }}
|
||||
key={noteItem.id}
|
||||
>
|
||||
<div className="d-flex justify-content-between align-items-center mb-1">
|
||||
<div className="d-flex align-items-center">
|
||||
<Avatar
|
||||
size="xs"
|
||||
firstName={noteItem.createdBy.firstName}
|
||||
lastName={noteItem.createdBy.lastName}
|
||||
className="m-0"
|
||||
/>
|
||||
<div className="d-flex flex-column ms-2">
|
||||
<span className="fw-semibold small">
|
||||
{noteItem.createdBy.firstName} {noteItem.createdBy.lastName}
|
||||
</span>
|
||||
<span className="text-muted" style={{ fontSize: "10px" }}>
|
||||
{moment(noteItem.createdAt).format("MMMM DD, YYYY [at] hh:mm A")}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<i
|
||||
className="bx bxs-edit bx-sm me-1 text-primary cursor-pointer"
|
||||
onClick={() => setEditing(true)}
|
||||
></i>
|
||||
{!isDeleting && (
|
||||
<i
|
||||
className="bx bx-trash bx-sm me-1 text-secondary cursor-pointer"
|
||||
onClick={handleDeleteNote}
|
||||
></i>
|
||||
)}
|
||||
{isDeleting && (
|
||||
<div
|
||||
class="spinner-border spinner-border-sm text-secondary"
|
||||
role="status"
|
||||
>
|
||||
<span class="visually-hidden">Loading...</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr className="mt-0" />
|
||||
|
||||
{editing ? (
|
||||
<>
|
||||
<ReactQuill
|
||||
value={editorValue}
|
||||
onChange={setEditorValue}
|
||||
theme="snow"
|
||||
className="compact-editor"
|
||||
/>
|
||||
<div className="d-flex justify-content-end gap-2">
|
||||
<span
|
||||
className="text-secondary cursor-pointer"
|
||||
aria-disabled={isLoading}
|
||||
onClick={() => setEditing(false)}
|
||||
>
|
||||
Cancel
|
||||
</span>
|
||||
<span
|
||||
className="text-primary cursor-pointer"
|
||||
aria-disabled={isLoading}
|
||||
onClick={handleUpdateNote}
|
||||
>
|
||||
{isLoading ? "Please Wait..." : "Submit"}
|
||||
</span>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<div dangerouslySetInnerHTML={{ __html: noteItem.note }} />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default NoteCardDirectory;
|
148
src/components/Directory/NotesDirectory.jsx
Normal file
148
src/components/Directory/NotesDirectory.jsx
Normal file
@ -0,0 +1,148 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import Editor from "../common/TextEditor/Editor";
|
||||
import Avatar from "../common/Avatar";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { z } from "zod";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { showText } from "pdf-lib";
|
||||
import { DirectoryRepository } from "../../repositories/DirectoryRepository";
|
||||
import moment from "moment";
|
||||
import { cacheData, getCachedData } from "../../slices/apiDataManager";
|
||||
import NoteCardDirectory from "./NoteCardDirectory";
|
||||
import showToast from "../../services/toastService";
|
||||
|
||||
const schema = z.object({
|
||||
note: z.string().min(1, { message: "Note is required" }),
|
||||
});
|
||||
|
||||
const NotesDirectory = ({ isLoading, contactProfile, setProfileContact }) => {
|
||||
const [NotesData, setNotesData] = useState();
|
||||
const [IsSubmitting, setIsSubmitting] = useState(false);
|
||||
const [addNote, setAddNote] = useState(false);
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
setValue,
|
||||
watch,
|
||||
formState: { errors },
|
||||
} = useForm({
|
||||
resolver: zodResolver(schema),
|
||||
defaultValues: {
|
||||
note: "",
|
||||
},
|
||||
});
|
||||
|
||||
const noteValue = watch("note");
|
||||
|
||||
const handleEditorChange = (value) => {
|
||||
setValue("note", value, { shouldValidate: true });
|
||||
};
|
||||
|
||||
const onSubmit = async (data) => {
|
||||
const newNote = { ...data, contactId: contactProfile?.id };
|
||||
try {
|
||||
setIsSubmitting(true);
|
||||
const response = await DirectoryRepository.CreateNote(newNote);
|
||||
|
||||
const createdNote = response.data;
|
||||
|
||||
setProfileContact((prev) => ({
|
||||
...prev,
|
||||
notes: [...(prev.notes || []), createdNote],
|
||||
}));
|
||||
|
||||
const cached_contactProfile = getCachedData("Contact Profile");
|
||||
if (
|
||||
cached_contactProfile &&
|
||||
cached_contactProfile.contactId === contactProfile?.id
|
||||
) {
|
||||
const updatedProfile = {
|
||||
...cached_contactProfile.data,
|
||||
notes: [...(cached_contactProfile.notes || []), createdNote],
|
||||
};
|
||||
cacheData("Contact Profile", updatedProfile);
|
||||
}
|
||||
|
||||
setValue("note", "");
|
||||
setIsSubmitting(false);
|
||||
showToast("Note added successfully!", "success");
|
||||
setAddNote(false);
|
||||
} catch (error) {
|
||||
setIsSubmitting(false);
|
||||
const msg =
|
||||
error.response.data.message ||
|
||||
error.message ||
|
||||
"Error occured during API calling";
|
||||
showToast(msg, "error");
|
||||
}
|
||||
};
|
||||
|
||||
const onCancel = () => {
|
||||
setValue("note", "");
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="text-start">
|
||||
<div
|
||||
className={`${
|
||||
contactProfile?.notes?.length > 0
|
||||
? "d-flex justify-content-between"
|
||||
: "d-flex justify-content-end"
|
||||
}`}
|
||||
>
|
||||
{contactProfile?.notes.length > 0 && (
|
||||
<p className="fw-semibold m-0">Notes :</p>
|
||||
)}
|
||||
<a
|
||||
className="small-text m-0 cursor-pointer"
|
||||
onClick={() => setAddNote(!addNote)}
|
||||
>
|
||||
<u>
|
||||
{addNote ? "" : "Add Note"}
|
||||
<i
|
||||
className={`bx ${addNote ? "bx-x" : "bx-pencil"} bx-xs`}
|
||||
></i>{" "}
|
||||
</u>{" "}
|
||||
</a>
|
||||
</div>
|
||||
{addNote && (
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
<Editor
|
||||
value={noteValue}
|
||||
loading={IsSubmitting}
|
||||
onChange={handleEditorChange}
|
||||
onCancel={onCancel}
|
||||
onSubmit={handleSubmit(onSubmit)}
|
||||
/>
|
||||
{errors.notes && (
|
||||
<p className="text-danger small mt-1">{errors.note.message}</p>
|
||||
)}
|
||||
</form>
|
||||
)}
|
||||
<div
|
||||
className=" justify-content-start overflow-auto px-1"
|
||||
style={{ maxHeight: "300px" }}
|
||||
>
|
||||
{isLoading && (
|
||||
<div className="text-center">
|
||||
{" "}
|
||||
<p>Loading...</p>{" "}
|
||||
</div>
|
||||
)}
|
||||
{!isLoading &&
|
||||
[...(contactProfile?.notes || [])]
|
||||
.reverse()
|
||||
.map((noteItem) => (
|
||||
<NoteCardDirectory
|
||||
noteItem={noteItem}
|
||||
contactId={contactProfile?.id}
|
||||
setProfileContact={setProfileContact}
|
||||
key={noteItem.id}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default NotesDirectory;
|
103
src/components/Directory/ProfileContactDirectory.jsx
Normal file
103
src/components/Directory/ProfileContactDirectory.jsx
Normal file
@ -0,0 +1,103 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useContactProfile } from "../../hooks/useDirectory";
|
||||
import Avatar from "../common/Avatar";
|
||||
import moment from "moment";
|
||||
import NotesDirectory from "./NotesDirectory";
|
||||
|
||||
const ProfileContactDirectory = ({ contact, setOpen_contact, closeModal }) => {
|
||||
const { conatProfile, loading } = useContactProfile(contact?.id);
|
||||
const [activeTab, setActiveTab] = useState("profile");
|
||||
const [profileContact, setProfileContact] = useState();
|
||||
|
||||
useEffect(() => {
|
||||
setProfileContact(conatProfile);
|
||||
}, [conatProfile]);
|
||||
return (
|
||||
<div className="p-1">
|
||||
<div className="text-center m-0 p-0">
|
||||
<p className="fw-semibold fs-6 m-0">Contact Profile</p>
|
||||
</div>
|
||||
<div>
|
||||
<div className="d-flex align-items-center mb-2">
|
||||
<Avatar
|
||||
size="sm"
|
||||
firstName={
|
||||
(contact?.name || "").trim().split(" ")[0]?.charAt(0) || ""
|
||||
}
|
||||
lastName={
|
||||
(contact?.name || "").trim().split(" ")[1]?.charAt(0) || ""
|
||||
}
|
||||
/>
|
||||
<div className="d-flex flex-column text-start ms-2">
|
||||
<span className="m-0 fw-semibold">{contact?.name}</span>
|
||||
<span className="small">
|
||||
<i className="bx bx-building bx-xs"></i>{" "}
|
||||
{conatProfile?.organization}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="d-flex flex-column text-start">
|
||||
{conatProfile?.contactEmails?.length > 0 && (
|
||||
<div className="d-flex mb-2">
|
||||
<div style={{ width: "100px", minWidth: "100px" }}>
|
||||
<p className="m-0">Email</p>
|
||||
</div>
|
||||
<div>
|
||||
<ul className="list-inline mb-0">
|
||||
{conatProfile.contactEmails.map((email, idx) => (
|
||||
<li className="list-inline-item me-3" key={idx}>
|
||||
<i className="bx bx-envelope bx-xs me-1"></i>
|
||||
{email.emailAddress}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{conatProfile?.contactPhones?.length > 0 && (
|
||||
<div className="d-flex mb-2">
|
||||
<div style={{ width: "100px", minWidth: "100px" }}>
|
||||
<p className="m-0">Phone</p>
|
||||
</div>
|
||||
<div>
|
||||
<ul className="list-inline mb-0">
|
||||
{conatProfile.contactPhones.map((phone, idx) => (
|
||||
<li className="list-inline-item me-3" key={idx}>
|
||||
<i className="bx bx-phone bx-xs me-1"></i>
|
||||
{phone.phoneNumber}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{conatProfile?.createdAt && (
|
||||
<div className="d-flex mb-2">
|
||||
<div style={{ width: "100px", minWidth: "100px" }}>
|
||||
<p className="m-0">Created</p>
|
||||
</div>
|
||||
<div>
|
||||
<ul className="list-inline mb-0">
|
||||
<li className="list-inline-item">
|
||||
<i className="bx bx-calendar-week bx-xs me-1"></i>
|
||||
{moment(conatProfile.createdAt).format("MMMM, DD YYYY")}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<hr className="my-1" />
|
||||
<NotesDirectory
|
||||
isLoading={loading}
|
||||
contactProfile={profileContact}
|
||||
setProfileContact={setProfileContact}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ProfileContactDirectory;
|
@ -239,23 +239,23 @@ await submitContact({ ...cleaned, id: existingContact.id });
|
||||
placeholder="email@example.com"
|
||||
/>
|
||||
{index === emailFields.length - 1 ? (
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-xs btn-primary ms-1"
|
||||
onClick={handleAddEmail}
|
||||
style={{ width: "24px", height: "24px" }}
|
||||
>
|
||||
<i className="bx bx-plus-circle bx-xs" />
|
||||
</button>
|
||||
// <button
|
||||
// type="button"
|
||||
// className="btn btn-xs btn-primary ms-1"
|
||||
|
||||
// style={{ width: "24px", height: "24px" }}
|
||||
// >
|
||||
<i className="bx bx-plus-circle bx-xs ms-1 cursor-pointer text-primary" onClick={handleAddEmail}/>
|
||||
|
||||
) : (
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-xs btn-danger ms-1 p-0"
|
||||
onClick={() => removeEmail(index)}
|
||||
style={{ width: "24px", height: "24px" }}
|
||||
>
|
||||
<i className="bx bx-minus-circle bx-xs" />
|
||||
</button>
|
||||
// <button
|
||||
// type="button"
|
||||
// className="btn btn-xs btn-danger ms-1 p-0"
|
||||
// onClick={() => removeEmail(index)}
|
||||
// style={{ width: "24px", height: "24px" }}
|
||||
// >
|
||||
<i className="bx bx-minus-circle bx-xs ms-1 cursor-pointer text-danger" onClick={() => removeEmail(index)}/>
|
||||
|
||||
)}
|
||||
</div>
|
||||
{errors.contactEmails?.[index]?.emailAddress && (
|
||||
@ -299,23 +299,21 @@ await submitContact({ ...cleaned, id: existingContact.id });
|
||||
placeholder="9876543210"
|
||||
/>
|
||||
{index === phoneFields.length - 1 ? (
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-xs btn-primary ms-1"
|
||||
onClick={handleAddPhone}
|
||||
style={{ width: "24px", height: "24px" }}
|
||||
>
|
||||
<i className="bx bx-plus-circle bx-xs" />
|
||||
</button>
|
||||
// <button
|
||||
// type="button"
|
||||
// className="btn btn-xs btn-primary ms-1"
|
||||
// onClick={handleAddPhone}
|
||||
// style={{ width: "24px", height: "24px" }}
|
||||
// >
|
||||
<i className="bx bx-plus-circle bx-xs ms-1 cursor-pointer text-primary" onClick={handleAddPhone} />
|
||||
) : (
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-xs btn-danger ms-1"
|
||||
onClick={() => removePhone(index)}
|
||||
style={{ width: "24px", height: "24px" }}
|
||||
>
|
||||
<i className="bx bx-minus-circle bx-xs" />
|
||||
</button>
|
||||
// <button
|
||||
// type="button"
|
||||
// className="btn btn-xs btn-danger ms-1"
|
||||
// onClick={() => removePhone(index)}
|
||||
// style={{ width: "24px", height: "24px" }}
|
||||
// >
|
||||
<i className="bx bx-minus-circle bx-xs ms-1 cursor-pointer text-danger" onClick={() => removePhone(index)} />
|
||||
)}
|
||||
</div>
|
||||
{errors.contactPhones?.[index]?.phoneNumber && (
|
||||
|
@ -8,7 +8,8 @@ const GlobalModel = ({
|
||||
dialogClass = '', // For additional custom classes on modal dialog
|
||||
role = 'dialog', // Accessibility role for the modal
|
||||
size = '', // Dynamically set the size (sm, lg, xl)
|
||||
dataAttributes = {} // Additional dynamic data-bs-* attributes
|
||||
dataAttributes = {}, // Additional dynamic data-bs-* attributes
|
||||
IsCloseBtn=true
|
||||
}) => {
|
||||
const modalRef = useRef(null); // Reference to the modal element
|
||||
|
||||
@ -69,13 +70,13 @@ useEffect(() => {
|
||||
<div className="modal-content">
|
||||
<div className="modal-header p-0">
|
||||
{/* Close button inside the modal header */}
|
||||
<button
|
||||
{IsCloseBtn && <button
|
||||
type="button"
|
||||
className="btn-close"
|
||||
data-bs-dismiss="modal"
|
||||
aria-label="Close"
|
||||
onClick={closeModal} // Trigger the React closeModal function
|
||||
></button>
|
||||
></button>}
|
||||
</div>
|
||||
<div className="modal-body p-sm-4 p-0">
|
||||
{children} {/* Render children here, which can be the ReportTask component */}
|
||||
|
@ -118,8 +118,8 @@
|
||||
}
|
||||
|
||||
.custom-checkbox:checked {
|
||||
background-color: #0d6efd;
|
||||
border-color: #0d6efd;
|
||||
background-color: #696cff;
|
||||
border-color: #696cff;
|
||||
}
|
||||
|
||||
.custom-checkbox:checked::after {
|
||||
|
132
src/components/common/TextEditor/Editor.css
Normal file
132
src/components/common/TextEditor/Editor.css
Normal file
@ -0,0 +1,132 @@
|
||||
.editor-wrapper {
|
||||
max-width: 800px;
|
||||
margin: 1px auto;
|
||||
background: #fff;
|
||||
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.ql-container {
|
||||
border: 1px solid #ccc;
|
||||
border-bottom: none;
|
||||
min-height: 80px;
|
||||
}
|
||||
|
||||
.custom-toolbar {
|
||||
/* text-align: left; */
|
||||
background-color: transparent;
|
||||
border: 1px solid #ccc;
|
||||
border-top: none;
|
||||
}
|
||||
/* Target the dropdown in the toolbar */
|
||||
.ql-toolbar .ql-picker.ql-header {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* Open the dropdown upwards */
|
||||
.ql-toolbar .ql-picker.ql-header .ql-picker-options {
|
||||
bottom: 100%; /* Move it above the picker */
|
||||
top: auto; /* Cancel default dropdown positioning */
|
||||
margin-bottom: 5px; /* Optional spacing */
|
||||
}
|
||||
.ql-toolbar .ql-picker.ql-header {
|
||||
font-family: Arial, sans-serif;
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.ql-toolbar .ql-picker-label {
|
||||
background-color: #eee;
|
||||
/* padding: 6px 10px; */
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.ql-toolbar .ql-picker-options {
|
||||
background-color: white;
|
||||
border: 1px solid #ccc;
|
||||
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.ql-toolbar .ql-picker-options span {
|
||||
padding: 2px 1px;
|
||||
display: block;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.ql-toolbar .ql-picker-options span:hover {
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
|
||||
.ql-toolbar .ql-picker-item{
|
||||
padding: 0px;
|
||||
}
|
||||
.ql-snow.ql-toolbar button, .ql-snow .ql-toolbar button {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
float: left;
|
||||
font-size: 15px;
|
||||
padding: 2px 2px;
|
||||
width: 28px;
|
||||
}
|
||||
.ql-toolbar.ql-snow{
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.ql-toolbar.ql-snow .ql-formats {
|
||||
margin-right: 1px;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
.ql-snow.ql-toolbar button,
|
||||
.ql-snow .ql-toolbar button {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
height: 18px;
|
||||
padding: 2px 2px;
|
||||
width: 22px;
|
||||
font-size: 14px;
|
||||
/* REMOVE THIS to fix side-alignment */
|
||||
/* float: left; */
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.ql-snow .ql-picker-label {
|
||||
font-size: 10px; /* Smaller text */
|
||||
padding: 0 6px; /* Horizontal padding */
|
||||
height: 20px; /* Height of the label */
|
||||
line-height: 20px; /* Match height to vertically center single-line text */
|
||||
background-color: #eee;
|
||||
border-radius: 0px;
|
||||
cursor: pointer;
|
||||
color: #333;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
|
||||
display: flex; /* Enable flexbox */
|
||||
align-items: center; /* Vertical centering */
|
||||
justify-content: center; /* Horizontal centering */
|
||||
}
|
||||
|
||||
|
||||
/* Remove custom upward-opening styles */
|
||||
.ql-toolbar .ql-picker.ql-header .ql-picker-options {
|
||||
top: 100%; /* Position it below the label */
|
||||
bottom: auto; /* Cancel the upward positioning */
|
||||
margin-top: 5px; /* Optional spacing */
|
||||
}
|
||||
.ql-editor {
|
||||
padding: 4px 15px;
|
||||
}
|
94
src/components/common/TextEditor/Editor.jsx
Normal file
94
src/components/common/TextEditor/Editor.jsx
Normal file
@ -0,0 +1,94 @@
|
||||
import React, { useRef } from "react";
|
||||
import ReactQuill from "react-quill";
|
||||
import "quill/dist/quill.snow.css";
|
||||
import "./Editor.css";
|
||||
|
||||
const Editor = ({
|
||||
value,
|
||||
loading,
|
||||
onChange,
|
||||
onCancel,
|
||||
onSubmit,
|
||||
placeholder = "Start writing...",
|
||||
}) => {
|
||||
const modules = {
|
||||
toolbar: {
|
||||
container: "#custom-toolbar",
|
||||
},
|
||||
};
|
||||
|
||||
const formats = [
|
||||
"header",
|
||||
"bold",
|
||||
"italic",
|
||||
"underline",
|
||||
"strike",
|
||||
"list",
|
||||
"bullet",
|
||||
"blockquote",
|
||||
"code-block",
|
||||
"link",
|
||||
"align",
|
||||
"image"
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="editor-wrapper">
|
||||
<div id="custom-toolbar" className="ql-toolbar ql-snow custom-toolbar">
|
||||
<div className="d-flex justify-content-between align-items-center w-100">
|
||||
{/* Left: Quill Format Buttons */}
|
||||
<span className="d-flex">
|
||||
<span className="ql-formats">
|
||||
<select className="ql-header">
|
||||
<option value="1" />
|
||||
<option value="2" />
|
||||
<option selected />
|
||||
</select>
|
||||
<button className="ql-bold" />
|
||||
<button className="ql-italic" />
|
||||
<button className="ql-underline" />
|
||||
<button className="ql-strike" />
|
||||
</span>
|
||||
|
||||
<span className="ql-formats">
|
||||
<button className="ql-list" value="ordered" />
|
||||
<button className="ql-list" value="bullet" />
|
||||
{/* <button className="ql-image" value="file" /> */}
|
||||
</span>
|
||||
|
||||
<span className="ql-formats">
|
||||
<button className="ql-link" />
|
||||
</span>
|
||||
</span>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<ReactQuill
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
modules={modules}
|
||||
formats={formats}
|
||||
theme="snow"
|
||||
placeholder={placeholder}
|
||||
/>
|
||||
{/* Right: Submit + Cancel Buttons */}
|
||||
<div className="d-flex justify-content-end gap-2 p-1">
|
||||
<span className="btn btn-xs btn-secondary" aria-disabled={loading} onClick={onCancel}>
|
||||
Cancel
|
||||
</span>
|
||||
<span
|
||||
type="submit"
|
||||
className="btn btn-xs btn-primary"
|
||||
onClick={onSubmit}
|
||||
aria-disabled={loading}
|
||||
>
|
||||
{loading ? "Please Wait..." : "Submit"}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Editor;
|
@ -61,3 +61,80 @@ export const useBuckets = () => {
|
||||
|
||||
return { buckets, loading, error };
|
||||
};
|
||||
|
||||
export const useContactProfile = (id) =>
|
||||
{
|
||||
const [ conatProfile, setContactProfile ] = useState( null );
|
||||
const [ loading, setLoading ] = useState( false );
|
||||
const [ Error, setError ] = useState( "" );
|
||||
|
||||
|
||||
const fetchContactProfile = async () => {
|
||||
const cached = getCachedData("Contact Profile");
|
||||
|
||||
if (!cached || cached.contactId !== id) {
|
||||
setLoading(true);
|
||||
try {
|
||||
const resp = await DirectoryRepository.GetContactProfile(id);
|
||||
setContactProfile(resp.data);
|
||||
cacheData("Contact Profile", { data: resp.data, contactId: id });
|
||||
} catch (err) {
|
||||
const msg =
|
||||
err?.response?.data?.message || err?.message || "Something went wrong";
|
||||
setError(msg);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
} else {
|
||||
setContactProfile(cached.data);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if ( id )
|
||||
{
|
||||
fetchContactProfile(id);
|
||||
}
|
||||
}, [id]);
|
||||
|
||||
return { conatProfile, loading, Error };
|
||||
}
|
||||
|
||||
|
||||
export const useContactNotes = (id) =>
|
||||
{
|
||||
const [ conatNotes, setContactNotes ] = useState( [] );
|
||||
const [ loading, setLoading ] = useState( false );
|
||||
const [ Error, setError ] = useState( "" );
|
||||
|
||||
|
||||
const fetchContactNotes = async () => {
|
||||
const cached = getCachedData("Contact Notes");
|
||||
|
||||
if (!cached || cached.contactId !== id) {
|
||||
setLoading(true);
|
||||
try {
|
||||
const resp = await DirectoryRepository.GetNote(id);
|
||||
setContactNotes(resp.data);
|
||||
cacheData("Contact Notes", { data: resp.data, contactId: id });
|
||||
} catch (err) {
|
||||
const msg =
|
||||
err?.response?.data?.message || err?.message || "Something went wrong";
|
||||
setError(msg);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
} else {
|
||||
setContactNotes(cached.data);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if ( id )
|
||||
{
|
||||
fetchContactNotes(id);
|
||||
}
|
||||
}, [id]);
|
||||
|
||||
return { conatProfile, loading, Error };
|
||||
}
|
||||
|
@ -12,15 +12,18 @@ import UpdateContact from "../../components/Directory/UpdateContact";
|
||||
import CardViewDirectory from "../../components/Directory/CardViewDirectory";
|
||||
import { useContactCategory } from "../../hooks/masterHook/useMaster";
|
||||
import usePagination from "../../hooks/usePagination";
|
||||
import {ITEMS_PER_PAGE} from "../../utils/constants";
|
||||
import { ITEMS_PER_PAGE } from "../../utils/constants";
|
||||
import ProfileContactDirectory from "../../components/Directory/ProfileContactDirectory";
|
||||
|
||||
const Directory = () => {
|
||||
const [isOpenModal, setIsOpenModal] = useState(false);
|
||||
const [isOpenModalNote, setIsOpenModalNote] = useState(false);
|
||||
const [selectedContact, setSelectedContact] = useState(null);
|
||||
const [open_contact, setOpen_contact] = useState(null);
|
||||
const [ContatList, setContactList] = useState([]);
|
||||
const [contactCategories, setContactCategories] = useState([]);
|
||||
const [ searchText, setSearchText ] = useState( "" );
|
||||
const [listView, setListView] = useState(true);
|
||||
const [searchText, setSearchText] = useState("");
|
||||
const [listView, setListView] = useState(false);
|
||||
|
||||
const { contacts, loading } = useDirectory();
|
||||
const { contactCategory, loading: contactCategoryLoading } =
|
||||
@ -59,6 +62,7 @@ const Directory = () => {
|
||||
const closedModel = () => {
|
||||
setIsOpenModal(false);
|
||||
setSelectedContact(null);
|
||||
setOpen_contact(null);
|
||||
};
|
||||
useEffect(() => {
|
||||
setContactList(contacts);
|
||||
@ -80,23 +84,39 @@ const Directory = () => {
|
||||
);
|
||||
};
|
||||
const filteredContacts = useMemo(() => {
|
||||
return ContatList
|
||||
.filter((c) => {
|
||||
const matchesSearch =
|
||||
c.name.toLowerCase().includes(searchText.toLowerCase()) ||
|
||||
c.organization.toLowerCase().includes(searchText.toLowerCase());
|
||||
const matchesCategory =
|
||||
selectedCategoryIds.length === 0 ||
|
||||
selectedCategoryIds.includes(c.contactCategory?.id);
|
||||
return matchesSearch && matchesCategory;
|
||||
})
|
||||
.sort((a, b) => a.name.localeCompare(b.name));
|
||||
return ContatList.filter((c) => {
|
||||
const matchesSearch =
|
||||
c.name.toLowerCase().includes(searchText.toLowerCase()) ||
|
||||
c.organization.toLowerCase().includes(searchText.toLowerCase());
|
||||
const matchesCategory =
|
||||
selectedCategoryIds.length === 0 ||
|
||||
selectedCategoryIds.includes(c.contactCategory?.id);
|
||||
return matchesSearch && matchesCategory;
|
||||
}).sort((a, b) => a.name.localeCompare(b.name));
|
||||
}, [ContatList, searchText, selectedCategoryIds]);
|
||||
const { currentPage, totalPages, currentItems, paginate } = usePagination(
|
||||
filteredContacts,
|
||||
ITEMS_PER_PAGE
|
||||
);
|
||||
|
||||
const renderModalContent = () => {
|
||||
if (selectedContact) {
|
||||
return (
|
||||
<UpdateContact
|
||||
existingContact={selectedContact}
|
||||
submitContact={submitContact}
|
||||
onCLosed={closedModel}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (!open_contact) {
|
||||
return (
|
||||
<ManageDirectory submitContact={submitContact} onCLosed={closedModel} />
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="container-xxl flex-grow-1 container-p-y">
|
||||
<Breadcrumb
|
||||
@ -109,26 +129,32 @@ const Directory = () => {
|
||||
{isOpenModal && (
|
||||
<GlobalModel
|
||||
isOpen={isOpenModal}
|
||||
closeModal={() => setIsOpenModal(false)}
|
||||
closeModal={() =>
|
||||
{
|
||||
setSelectedContact(null)
|
||||
setIsOpenModal(false)
|
||||
}}
|
||||
size="lg"
|
||||
>
|
||||
{selectedContact ? (
|
||||
<UpdateContact
|
||||
existingContact={selectedContact}
|
||||
submitContact={submitContact}
|
||||
onCLosed={closedModel}
|
||||
/>
|
||||
) : (
|
||||
<ManageDirectory
|
||||
submitContact={submitContact}
|
||||
onCLosed={closedModel}
|
||||
/>
|
||||
)}
|
||||
{renderModalContent()}
|
||||
</GlobalModel>
|
||||
)}
|
||||
{isOpenModalNote && (
|
||||
<GlobalModel
|
||||
isOpen={isOpenModalNote}
|
||||
closeModal={() =>
|
||||
{
|
||||
setOpen_contact(null)
|
||||
setIsOpenModalNote(false)
|
||||
}}
|
||||
size="lg"
|
||||
>
|
||||
{open_contact && <ProfileContactDirectory contact={open_contact} setOpen_contact={setOpen_contact} closeModal={ () => setIsOpenModalNote(false)} />}
|
||||
</GlobalModel>
|
||||
)}
|
||||
<div className="card p-2">
|
||||
<div className="row mx-0 px-0 align-items-center">
|
||||
<div className="col-7 col-md-4 mb-2 px-1 d-flex align-items-center ">
|
||||
<div className="col-12 col-md-4 mb-2 px-1 d-flex align-items-center ">
|
||||
<input
|
||||
type="search"
|
||||
className="form-control form-control-sm me-2"
|
||||
@ -166,7 +192,7 @@ const Directory = () => {
|
||||
<i className="bx bx-list-ul bx-sm"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div className="dropdown">
|
||||
<div className="dropdown">
|
||||
<a
|
||||
className="dropdown-toggle hide-arrow cursor-pointer d-flex align-items-center"
|
||||
data-bs-toggle="dropdown"
|
||||
@ -194,117 +220,119 @@ const Directory = () => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="col-5 col-md-8 mb-2 px-1 text-md-end text-end">
|
||||
<div className="col-12 col-md-8 mb-2 px-1 text-md-end text-end">
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-xs btn-primary"
|
||||
onClick={() => setIsOpenModal(true)}
|
||||
>
|
||||
<i className="bx bx-plus-circle me-2"></i>
|
||||
<span className="d-sm-block d-none"> New Contact</span>
|
||||
New Contact
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{
|
||||
listView ? (
|
||||
<div className="table-responsive text-nowrap py-2 ">
|
||||
<table className="table px-2">
|
||||
<thead>
|
||||
<tr>
|
||||
<th colSpan={2}>
|
||||
<div className="d-flex align-items-center gap-1">
|
||||
<IconButton
|
||||
size={12}
|
||||
iconClass="bx bx-user"
|
||||
color="secondary"
|
||||
onClick={() => alert("User icon clicked")}
|
||||
/>
|
||||
<span>Name</span>
|
||||
</div>
|
||||
</th>
|
||||
<th className="px-2 text-start">
|
||||
<div className="d-flex text-center align-items-center gap-1 justify-content-start">
|
||||
<IconButton
|
||||
size={12}
|
||||
iconClass="bx bx-envelope"
|
||||
color="primary"
|
||||
/>
|
||||
<span>Email</span>
|
||||
</div>
|
||||
</th>
|
||||
|
||||
<th className="mx-2">
|
||||
<div className="d-flex align-items-center m-0 p-0 gap-1">
|
||||
<IconButton
|
||||
size={12}
|
||||
iconClass="bx bx-phone"
|
||||
color="warning"
|
||||
onClick={() => alert("User icon clicked")}
|
||||
/>
|
||||
<span>Phone</span>
|
||||
</div>
|
||||
</th>
|
||||
<th className="mx-2">
|
||||
<div className="d-flex align-items-center gap-1">
|
||||
<IconButton
|
||||
size={12}
|
||||
iconClass="bx bxs-grid-alt"
|
||||
color="info"
|
||||
/>
|
||||
<span>Organization</span>
|
||||
</div>
|
||||
</th>
|
||||
<th className="mx-2">Category</th>
|
||||
<th
|
||||
// className={`mx-2 ${
|
||||
// HasManageProject ? "d-sm-table-cell" : "d-none"
|
||||
// }`}
|
||||
>
|
||||
Action
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="table-border-bottom-0 overflow-auto ">
|
||||
{loading && ContatList.length === 0 && (
|
||||
{!listView && loading && <p>Loading...</p>}
|
||||
{listView ? (
|
||||
<div className="table-responsive text-nowrap py-2 ">
|
||||
<table className="table px-2">
|
||||
<thead>
|
||||
<tr>
|
||||
<td colSpan={10}>Loading...</td>
|
||||
</tr>
|
||||
)}
|
||||
{!loading && contacts.length == 0 && ContatList.length === 0 && (
|
||||
<tr>
|
||||
<td colSpan={10}>No Contact Found</td>
|
||||
</tr>
|
||||
)}
|
||||
{!loading &&
|
||||
currentItems.map((contact) => (
|
||||
<ListViewDirectory
|
||||
key={contact.id}
|
||||
contact={contact}
|
||||
setSelectedContact={setSelectedContact}
|
||||
setIsOpenModal={setIsOpenModal}
|
||||
/>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
) : (
|
||||
<div className="row">
|
||||
{currentItems.map((contact, index) => (
|
||||
<div key={contact.id} className="col-12 col-sm-6 col-md-4 col-lg-4 mb-4">
|
||||
<CardViewDirectory contact={contact}
|
||||
setSelectedContact={setSelectedContact}
|
||||
setIsOpenModal={setIsOpenModal}
|
||||
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
<th colSpan={2}>
|
||||
<div className="d-flex align-items-center gap-1">
|
||||
<IconButton
|
||||
size={12}
|
||||
iconClass="bx bx-user"
|
||||
color="secondary"
|
||||
onClick={() => alert("User icon clicked")}
|
||||
/>
|
||||
<span>Name</span>
|
||||
</div>
|
||||
</th>
|
||||
<th className="px-2 text-start">
|
||||
<div className="d-flex text-center align-items-center gap-1 justify-content-start">
|
||||
<IconButton
|
||||
size={12}
|
||||
iconClass="bx bx-envelope"
|
||||
color="primary"
|
||||
/>
|
||||
<span>Email</span>
|
||||
</div>
|
||||
</th>
|
||||
|
||||
|
||||
|
||||
|
||||
<th className="mx-2">
|
||||
<div className="d-flex align-items-center m-0 p-0 gap-1">
|
||||
<IconButton
|
||||
size={12}
|
||||
iconClass="bx bx-phone"
|
||||
color="warning"
|
||||
onClick={() => alert("User icon clicked")}
|
||||
/>
|
||||
<span>Phone</span>
|
||||
</div>
|
||||
</th>
|
||||
<th className="mx-2">
|
||||
<div className="d-flex align-items-center gap-1">
|
||||
<IconButton
|
||||
size={12}
|
||||
iconClass="bx bxs-grid-alt"
|
||||
color="info"
|
||||
/>
|
||||
<span>Organization</span>
|
||||
</div>
|
||||
</th>
|
||||
<th className="mx-2">Category</th>
|
||||
<th
|
||||
// className={`mx-2 ${
|
||||
// HasManageProject ? "d-sm-table-cell" : "d-none"
|
||||
// }`}
|
||||
>
|
||||
Action
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="table-border-bottom-0 overflow-auto ">
|
||||
{loading && ContatList.length === 0 && (
|
||||
<tr>
|
||||
<td colSpan={10}>Loading...</td>
|
||||
</tr>
|
||||
)}
|
||||
{!loading &&
|
||||
contacts.length == 0 &&
|
||||
ContatList.length === 0 && (
|
||||
<tr>
|
||||
<td colSpan={10}>No Contact Found</td>
|
||||
</tr>
|
||||
)}
|
||||
{!loading &&
|
||||
currentItems.map((contact) => (
|
||||
<ListViewDirectory
|
||||
key={contact.id}
|
||||
contact={contact}
|
||||
setSelectedContact={setSelectedContact}
|
||||
setIsOpenModal={setIsOpenModal}
|
||||
/>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
) : (
|
||||
<div className="row">
|
||||
{currentItems.map((contact, index) => (
|
||||
<div
|
||||
key={contact.id}
|
||||
className="col-12 col-sm-6 col-md-4 col-lg-4 mb-4"
|
||||
>
|
||||
<CardViewDirectory
|
||||
contact={contact}
|
||||
setSelectedContact={setSelectedContact}
|
||||
setIsOpenModal={setIsOpenModal}
|
||||
setOpen_contact={setOpen_contact}
|
||||
setIsOpenModalNote={setIsOpenModalNote}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!loading && (
|
||||
<nav aria-label="Page ">
|
||||
|
@ -5,5 +5,12 @@ export const DirectoryRepository = {
|
||||
CreateContact: ( data ) => api.post( '/api/directory', data ),
|
||||
UpdateContact:(id,data)=>api.put(`/api/directory/${id}`,data),
|
||||
|
||||
GetBucktes:()=>api.get(`/api/Directory/buckets`)
|
||||
GetBucktes: () => api.get( `/api/directory/buckets` ),
|
||||
|
||||
GetContactProfile: ( id ) => api.get( `/api/directory/profile/${ id }` ),
|
||||
|
||||
CreateNote: ( data ) => api.post( '/api/directory/note', data ),
|
||||
GetNote: ( id ) => api.get( `/api/directory/note/${ id }` ),
|
||||
UpdateNote: ( id, data ) => api.put( `/api/directory/note/${ id }`, data ),
|
||||
DeleteNote:(id)=> api.delete(`/api/directory/note/${ id }`)
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user