Compare commits
	
		
			477 Commits
		
	
	
		
			bab312b9ee
			...
			500006c1ef
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 500006c1ef | |||
| 93d807cca8 | |||
| a77f7cecbb | |||
| 9b40317eb2 | |||
| a480c3b111 | |||
| c2c9fd01fc | |||
| d3524c40f3 | |||
| 7eff9d8c3a | |||
| ad1ad667b1 | |||
| 435fa730b6 | |||
| ff8fca72fa | |||
| c3a52c46eb | |||
| 
						 | 
					9573daa8b0 | ||
| 
						 | 
					117ac31d12 | ||
| 
						 | 
					7dea047536 | ||
| 
						 | 
					3256a68d1c | ||
| 
						 | 
					d4f0662f57 | ||
| 
						 | 
					93c1fb1844 | ||
| 
						 | 
					0886990f9d | ||
| 
						 | 
					c7b93006d8 | ||
| 
						 | 
					b71ca1c7fb | ||
| 
						 | 
					1d43470783 | ||
| 
						 | 
					33f5bb3517 | ||
| 
						 | 
					e3731cab5f | ||
| 
						 | 
					71631fa68d | ||
| 
						 | 
					0b99238fa8 | ||
| 
						 | 
					376ead7967 | ||
| 
						 | 
					51c02c7156 | ||
| 
						 | 
					977397d31f | ||
| 
						 | 
					b77389284c | ||
| 
						 | 
					29ef1e4016 | ||
| 
						 | 
					8d966fc5c0 | ||
| 
						 | 
					901f633177 | ||
| 
						 | 
					d23a090cd5 | ||
| 
						 | 
					456fa6d385 | ||
| 
						 | 
					7bf3d69174 | ||
| 
						 | 
					d5d5ecafa9 | ||
| 
						 | 
					49dfc084c0 | ||
| 
						 | 
					0026de943b | ||
| 
						 | 
					3e61a9959e | ||
| 
						 | 
					bc657eebca | ||
| c4288fe35a | |||
| f6634eb527 | |||
| 
						 | 
					763d81e3b1 | ||
| 
						 | 
					c78c136467 | ||
| 
						 | 
					360f3b2352 | ||
| 
						 | 
					0eaea6a232 | ||
| 
						 | 
					08ace9b5ba | ||
| 
						 | 
					7de4de9b32 | ||
| 
						 | 
					cd7422c991 | ||
| 
						 | 
					e739a380f7 | ||
| 
						 | 
					2eee4a1f6a | ||
| 
						 | 
					b0e3b767d3 | ||
| 
						 | 
					49a3bec527 | ||
| 
						 | 
					289f4063f9 | ||
| 
						 | 
					ffd23dc11c | ||
| 
						 | 
					941e74fcbb | ||
| 
						 | 
					94daa0d4c2 | ||
| 
						 | 
					755ea1b0ee | ||
| 
						 | 
					0082a60e02 | ||
| 
						 | 
					7d3a7adf95 | ||
| 
						 | 
					6d02511847 | ||
| 
						 | 
					1f8ba16447 | ||
| 
						 | 
					bae8b130de | ||
| 
						 | 
					a35e8b645b | ||
| 
						 | 
					875ba214dd | ||
| 
						 | 
					bc220180c4 | ||
| 
						 | 
					8a57d3e978 | ||
| 
						 | 
					c9960a1273 | ||
| 
						 | 
					ddaba97c8f | ||
| 
						 | 
					14698e92fe | ||
| 
						 | 
					ec32d9160c | ||
| 
						 | 
					6346f132bd | ||
| 
						 | 
					3dd901a81f | ||
| 
						 | 
					f9ad47ef54 | ||
| 
						 | 
					62a044261a | ||
| 
						 | 
					3c75222dd7 | ||
| 
						 | 
					77e3082000 | ||
| 
						 | 
					db75cec8e7 | ||
| 
						 | 
					a9f3b27cb3 | ||
| 
						 | 
					813032ead2 | ||
| 
						 | 
					eb6d4e413d | ||
| 
						 | 
					16389bf102 | ||
| 
						 | 
					a87d2c9143 | ||
| 
						 | 
					2d1d32ee16 | ||
| 
						 | 
					c77dc76079 | ||
| 
						 | 
					04d64e57c9 | ||
| 
						 | 
					39ef265b56 | ||
| 
						 | 
					cd487dee49 | ||
| 
						 | 
					fba983f41d | ||
| 
						 | 
					5ef8bed2b8 | ||
| 
						 | 
					201a8881a5 | ||
| 
						 | 
					ed489842c6 | ||
| 
						 | 
					24ffb82616 | ||
| 
						 | 
					0695fb6737 | ||
| 
						 | 
					45a300e540 | ||
| 
						 | 
					b4157354f7 | ||
| 
						 | 
					8f9bfcae6b | ||
| 
						 | 
					47ecbcdafd | ||
| 
						 | 
					41d9db6fcc | ||
| 
						 | 
					330ac0ff62 | ||
| 
						 | 
					1b399c1ebf | ||
| 
						 | 
					360bfc5650 | ||
| 
						 | 
					1d1f82feb6 | ||
| 
						 | 
					195adbeb72 | ||
| 
						 | 
					d32b954d94 | ||
| 
						 | 
					c7e308586a | ||
| 
						 | 
					df1838dbba | ||
| 
						 | 
					276c97a734 | ||
| 
						 | 
					449debc0cf | ||
| 
						 | 
					560ee8fe27 | ||
| 
						 | 
					dd0ec9d018 | ||
| 
						 | 
					30b27ed2bb | ||
| 
						 | 
					ee9c80749f | ||
| 
						 | 
					2fc4db1f87 | ||
| 
						 | 
					7a8bbaf561 | ||
| 
						 | 
					3ff8066803 | ||
| 
						 | 
					a965116aa5 | ||
| 
						 | 
					5a701ca9d7 | ||
| 
						 | 
					ded8ab7752 | ||
| 
						 | 
					6a8332b606 | ||
| 
						 | 
					51e48e632a | ||
| 
						 | 
					8999d7bb47 | ||
| 
						 | 
					5e3c833ca6 | ||
| 
						 | 
					3f5b79dc5c | ||
| 
						 | 
					f748eecf14 | ||
| 
						 | 
					df5c2e4397 | ||
| 
						 | 
					0d09bfda04 | ||
| 
						 | 
					1b575a7d5f | ||
| 
						 | 
					17cceeb97c | ||
| 
						 | 
					eb8140ebac | ||
| 
						 | 
					c089ee47cf | ||
| 
						 | 
					6010e630e8 | ||
| 
						 | 
					0d249c7cdb | ||
| 
						 | 
					5fd6f26653 | ||
| 
						 | 
					2643bbbe59 | ||
| 
						 | 
					d40d4c59f2 | ||
| 
						 | 
					bfba4133f4 | ||
| 
						 | 
					31ca02fcd8 | ||
| 
						 | 
					105b5c6dcc | ||
| 
						 | 
					2607f852fb | ||
| 
						 | 
					54de8df956 | ||
| 
						 | 
					4e19c2a547 | ||
| 
						 | 
					43b1b1ffe2 | ||
| 
						 | 
					c784d18427 | ||
| 
						 | 
					0e38709435 | ||
| 
						 | 
					4f70d8cb17 | ||
| 
						 | 
					24d8688069 | ||
| 
						 | 
					5a3dbe466f | ||
| 
						 | 
					76bbfee4fc | ||
| 
						 | 
					3e251b3d47 | ||
| 
						 | 
					dcc17dea0c | ||
| 
						 | 
					37066904e3 | ||
| 
						 | 
					0c44ae76f8 | ||
| 
						 | 
					2688c95f47 | ||
| 
						 | 
					8f6c4b0814 | ||
| 
						 | 
					7d489d177d | ||
| 
						 | 
					d9053837a6 | ||
| 
						 | 
					7feaac1a94 | ||
| 
						 | 
					e3cfc1c073 | ||
| 
						 | 
					1a7636ad82 | ||
| 
						 | 
					96d1fd648f | ||
| 
						 | 
					f9c3c136b3 | ||
| 
						 | 
					93c0961751 | ||
| 
						 | 
					0fc460b38f | ||
| 
						 | 
					7514e73d0c | ||
| 
						 | 
					038ff416e1 | ||
| 
						 | 
					69abe1bd42 | ||
| 
						 | 
					3a6be65a48 | ||
| 
						 | 
					597fa279ea | ||
| 
						 | 
					61f0703096 | ||
| 
						 | 
					a9a5206062 | ||
| 
						 | 
					75897a2ab4 | ||
| 
						 | 
					1300ea33b6 | ||
| 
						 | 
					6cad6ac6e8 | ||
| 
						 | 
					2d84b489f5 | ||
| 
						 | 
					7b2a94b5a4 | ||
| 
						 | 
					a6a4435a77 | ||
| 
						 | 
					bc50e58150 | ||
| 
						 | 
					f8f43b62c6 | ||
| 
						 | 
					dbc1ae2c63 | ||
| 
						 | 
					76ce07d4d3 | ||
| 
						 | 
					65319cac7d | ||
| 
						 | 
					6fec5b68e3 | ||
| 
						 | 
					41494a0816 | ||
| 
						 | 
					4753117d7b | ||
| 
						 | 
					bb6aaeee18 | ||
| 
						 | 
					bef8a28db2 | ||
| 
						 | 
					f11f358f49 | ||
| 
						 | 
					65f1d32bb8 | ||
| 
						 | 
					7c67af41c3 | ||
| 
						 | 
					b082aa370b | ||
| 
						 | 
					15967ce2fc | ||
| 
						 | 
					c83960c040 | ||
| 
						 | 
					d10cbdd4bd | ||
| 
						 | 
					42b769c1b5 | ||
| 
						 | 
					ada8856b3b | ||
| 
						 | 
					86e9854071 | ||
| 
						 | 
					ba04e041b8 | ||
| 
						 | 
					3d43facdae | ||
| 
						 | 
					3e1f93d4f1 | ||
| 
						 | 
					09b1664ac7 | ||
| 
						 | 
					f62129e3ac | ||
| 
						 | 
					cdec17735f | ||
| 
						 | 
					8ece56da52 | ||
| 
						 | 
					22dc355f7d | ||
| 
						 | 
					f7c9c7c7ff | ||
| 
						 | 
					02492b0405 | ||
| 
						 | 
					283e13985d | ||
| 
						 | 
					a319171674 | ||
| 
						 | 
					07a402d28d | ||
| 
						 | 
					354c653240 | ||
| 
						 | 
					33f6220622 | ||
| 
						 | 
					e8504c6627 | ||
| 
						 | 
					5b3b6904bf | ||
| 
						 | 
					1e3a1f0365 | ||
| 
						 | 
					8d7fcc62a7 | ||
| 
						 | 
					5836a24aed | ||
| 
						 | 
					8c0ab1102b | ||
| 
						 | 
					b4081e73bf | ||
| 
						 | 
					a3428fed85 | ||
| 
						 | 
					bf0af60c47 | ||
| 
						 | 
					cecff08ba2 | ||
| 
						 | 
					8c15cacc04 | ||
| 
						 | 
					3393eaf48d | ||
| 
						 | 
					c672e44c76 | ||
| 
						 | 
					a1e75bf7dd | ||
| 
						 | 
					379d9ecb8b | ||
| 
						 | 
					662d464271 | ||
| 
						 | 
					99863442a6 | ||
| 2386bd0e74 | |||
| 
						 | 
					b1253bed88 | ||
| 
						 | 
					f129645386 | ||
| 
						 | 
					0b46dc0f1f | ||
| 
						 | 
					fe87e0dbd4 | ||
| 
						 | 
					74a5c05481 | ||
| 
						 | 
					7baffac11a | ||
| 
						 | 
					a3dcd9bee3 | ||
| 
						 | 
					d19652a98e | ||
| 2b015947ef | |||
| fed6711ece | |||
| 
						 | 
					6b5aa5fb1a | ||
| 1ca50afc74 | |||
| 
						 | 
					e83eafddd2 | ||
| 
						 | 
					1bd050f919 | ||
| 
						 | 
					1298994f58 | ||
| 
						 | 
					f92ef4e52a | ||
| 
						 | 
					34c04f31ec | ||
| 
						 | 
					0d649fbc78 | ||
| 6217941b0a | |||
| 
						 | 
					0843123505 | ||
| 
						 | 
					9c46bffc4b | ||
| da6b56ac8c | |||
| 7f45dbab2a | |||
| 
						 | 
					426a885629 | ||
| 
						 | 
					e03d1b4e99 | ||
| 1c8461e72a | |||
| 
						 | 
					6819bfbb52 | ||
| 
						 | 
					dbf7178db0 | ||
| 
						 | 
					d826acbb45 | ||
| 
						 | 
					4799e8e91c | ||
| 
						 | 
					07809d6635 | ||
| 99d8be1e4b | |||
| 30ce981a24 | |||
| f20a7cf4bc | |||
| 9c3bcaea9e | |||
| 252bf0904e | |||
| 4b64e7450e | |||
| 5572b80f4a | |||
| 0674fcf5bb | |||
| 
						 | 
					05f4a9da4d | ||
| 
						 | 
					e5bcf7111b | ||
| 
						 | 
					6774957558 | ||
| 
						 | 
					3594b78253 | ||
| 
						 | 
					e93062bdf4 | ||
| 
						 | 
					445a5e0064 | ||
| 
						 | 
					5666616b6b | ||
| 
						 | 
					15d313baf1 | ||
| d74854b847 | |||
| 
						 | 
					eb678585fa | ||
| 
						 | 
					9ff79b5854 | ||
| 
						 | 
					616df5e869 | ||
| 
						 | 
					a88a8d3df9 | ||
| 
						 | 
					36e7755b7d | ||
| 
						 | 
					73f57bb59f | ||
| 
						 | 
					41cbf69550 | ||
| 
						 | 
					8b5f7e69ab | ||
| 
						 | 
					85a957c204 | ||
| 
						 | 
					6fe62e7c0e | ||
| 
						 | 
					b5d3b0fd08 | ||
| 
						 | 
					fd4d4f0386 | ||
| 
						 | 
					acbe3f44b5 | ||
| 
						 | 
					b268763283 | ||
| 
						 | 
					767d8abd19 | ||
| 
						 | 
					5531d408f9 | ||
| 
						 | 
					3330565dcb | ||
| 
						 | 
					4628dea561 | ||
| 225f22153d | |||
| 
						 | 
					8c66a9a083 | ||
| 
						 | 
					2a5e075db5 | ||
| 
						 | 
					80a213595d | ||
| 
						 | 
					0f323a7594 | ||
| 8c43d07488 | |||
| 
						 | 
					6b5bc8c729 | ||
| 
						 | 
					ba5b07b608 | ||
| 
						 | 
					875656dbf5 | ||
| 
						 | 
					e22ab1ae05 | ||
| 
						 | 
					416f8e6621 | ||
| 
						 | 
					51f7e473f4 | ||
| 
						 | 
					e6cdff1ed6 | ||
| 
						 | 
					cb0800b103 | ||
| 
						 | 
					b02c599130 | ||
| 16301def0c | |||
| 
						 | 
					61e8c78245 | ||
| 
						 | 
					a0930197d9 | ||
| 
						 | 
					c6e9d3fcbe | ||
| 
						 | 
					b478e7e4a7 | ||
| 
						 | 
					054a7780db | ||
| 
						 | 
					1f864ac08c | ||
| 
						 | 
					cea094cd89 | ||
| 
						 | 
					cae2f2fffd | ||
| 
						 | 
					fc30db8532 | ||
| 
						 | 
					0ec3d0c3e3 | ||
| 
						 | 
					a553ac2fe2 | ||
| 
						 | 
					e316858c0a | ||
| 
						 | 
					ed72f29f01 | ||
| 
						 | 
					f8c3a6e767 | ||
| 
						 | 
					fc82989577 | ||
| 
						 | 
					6605293c47 | ||
| 
						 | 
					c382d9238d | ||
| 
						 | 
					45e88c07a9 | ||
| 
						 | 
					eb0e424a18 | ||
| 
						 | 
					3f7456e5e7 | ||
| 
						 | 
					478aedf2ae | ||
| 
						 | 
					866049e385 | ||
| 
						 | 
					8786f01b40 | ||
| 
						 | 
					d3f7c44922 | ||
| 
						 | 
					466f11382a | ||
| 
						 | 
					3072759ba8 | ||
| 
						 | 
					a17a32a7cf | ||
| 
						 | 
					991a833dbf | ||
| 
						 | 
					5287b40cd0 | ||
| 
						 | 
					dd35de14a7 | ||
| 
						 | 
					8fba237173 | ||
| 
						 | 
					c3643683c8 | ||
| 
						 | 
					ad145db3c7 | ||
| 2224b10842 | |||
| 
						 | 
					8abb8a3aa5 | ||
| 
						 | 
					51c3857675 | ||
| 
						 | 
					8f94cf6e2d | ||
| 
						 | 
					199cf4867f | ||
| 
						 | 
					06c0c5ebb3 | ||
| 16f40f7566 | |||
| d11174984d | |||
| ff56245119 | |||
| 
						 | 
					1cdaefbec4 | ||
| 
						 | 
					36b09747b0 | ||
| 
						 | 
					695369cbf8 | ||
| 
						 | 
					dcaa9ae7ec | ||
| 
						 | 
					79e38f6cd5 | ||
| 
						 | 
					3593e7d46c | ||
| 
						 | 
					144817aef6 | ||
| 889b5ab069 | |||
| 
						 | 
					bcc09851d3 | ||
| 
						 | 
					45cd17c808 | ||
| 
						 | 
					d9ce90e20e | ||
| 
						 | 
					385f08e752 | ||
| 
						 | 
					52ec9408d2 | ||
| 
						 | 
					64973029cc | ||
| 
						 | 
					7e4f4c8973 | ||
| 
						 | 
					6943f4e9ca | ||
| 
						 | 
					af996196a1 | ||
| b4d3e6453f | |||
| 
						 | 
					ab47a44c60 | ||
| 
						 | 
					10e38353aa | ||
| 
						 | 
					e372405ac0 | ||
| 
						 | 
					0cefb98ac1 | ||
| 
						 | 
					c78f94b17f | ||
| 
						 | 
					55f17daf0f | ||
| 
						 | 
					a69dd50425 | ||
| 
						 | 
					3b82653f23 | ||
| 
						 | 
					7147116d8e | ||
| 2643f1b2d5 | |||
| 
						 | 
					228c178acf | ||
| 
						 | 
					cb5cacaee0 | ||
| 1d90c631ef | |||
| 
						 | 
					ad0be79943 | ||
| 
						 | 
					a076f1810f | ||
| 
						 | 
					c60409e108 | ||
| 
						 | 
					b4df17b7e0 | ||
| 
						 | 
					eb41a66a79 | ||
| 
						 | 
					83ec0ec5dd | ||
| 
						 | 
					e331208b3e | ||
| 
						 | 
					5b155a3329 | ||
| 
						 | 
					89221dec38 | ||
| 
						 | 
					7f8784bf6a | ||
| 
						 | 
					7942f37dd6 | ||
| 
						 | 
					c59daf0ffe | ||
| 
						 | 
					8afef0d81a | ||
| 
						 | 
					af294a56d9 | ||
| 
						 | 
					75ab8f677c | ||
| 
						 | 
					7a30c74594 | ||
| 
						 | 
					9c9a69bc52 | ||
| 
						 | 
					c4de655fdf | ||
| 
						 | 
					6e8bd7eef8 | ||
| 
						 | 
					3738730ecf | ||
| 
						 | 
					3572361c67 | ||
| 
						 | 
					c454b3e884 | ||
| 
						 | 
					ea2a587fad | ||
| 
						 | 
					39ea50115f | ||
| 
						 | 
					8a53e2d8de | ||
| 
						 | 
					35b036398f | ||
| b71dd7f0f5 | |||
| 
						 | 
					99e0cd94d4 | ||
| 
						 | 
					27fafb4f05 | ||
| 
						 | 
					978165d3de | ||
| 
						 | 
					b9a7d4977c | ||
| 
						 | 
					65aa8990a3 | ||
| 
						 | 
					9509b0b8da | ||
| 
						 | 
					7e16701b0d | ||
| 
						 | 
					1e079a89eb | ||
| 
						 | 
					aba02f64c0 | ||
| 
						 | 
					c43c668fb8 | ||
| 
						 | 
					353238fd60 | ||
| 
						 | 
					b2e1fbcf95 | ||
| 
						 | 
					b296dec542 | ||
| 
						 | 
					3efeea55e0 | ||
| 
						 | 
					126a84b5cc | ||
| 
						 | 
					7613f36624 | ||
| 
						 | 
					1cac05ddcb | ||
| 
						 | 
					49d38f553a | ||
| 
						 | 
					b65be72809 | ||
| 
						 | 
					e9903ee2f6 | ||
| b489c094b3 | |||
| 
						 | 
					28e409ff99 | ||
| 
						 | 
					b6a0fb54ac | ||
| 
						 | 
					61ce554e05 | ||
| 
						 | 
					d956bfd3cc | ||
| 
						 | 
					71b9a3602b | ||
| 
						 | 
					68999401e5 | ||
| 
						 | 
					a837b16a7d | ||
| 
						 | 
					d716a5ffac | ||
| 
						 | 
					5f33826e61 | ||
| 
						 | 
					8e06991bd3 | ||
| 
						 | 
					79ad15d572 | ||
| 
						 | 
					4d6171e1ed | ||
| 
						 | 
					28a4f63d10 | ||
| 
						 | 
					c2ead3cd0f | ||
| 
						 | 
					b33fad1b17 | ||
| 
						 | 
					5ccdf33c35 | ||
| 431a541a89 | |||
| 
						 | 
					745673b3f3 | ||
| 
						 | 
					cfa8571079 | ||
| 
						 | 
					c7b5a0ba7d | ||
| 
						 | 
					a1ee9dac38 | ||
| 
						 | 
					085f45210e | ||
| 
						 | 
					7cfe04de2e | ||
| 
						 | 
					1c1a1da8a0 | ||
| 9a0482071a | |||
| 
						 | 
					2922cca22d | ||
| 
						 | 
					a3d21ba098 | ||
| cdc52ae947 | |||
| 
						 | 
					04297346fa | ||
| 70cfa36ba8 | |||
| 
						 | 
					d8df891370 | ||
| 
						 | 
					dcc611b768 | ||
| 
						 | 
					413dba4f5c | ||
| 9a4c5df14f | |||
| 
						 | 
					cd21bc14ff | ||
| 
						 | 
					40df12de86 | ||
| 
						 | 
					74ed8d3c4f | ||
| 
						 | 
					784eebafea | ||
| 88d23fc765 | |||
| 
						 | 
					50ef045479 | ||
| 
						 | 
					cc98f34b44 | ||
| 
						 | 
					5415210d70 | ||
| 
						 | 
					3670409977 | 
							
								
								
									
										166
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										166
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							@ -27,6 +27,7 @@
 | 
				
			|||||||
        "react-apexcharts": "^1.7.0",
 | 
					        "react-apexcharts": "^1.7.0",
 | 
				
			||||||
        "react-dom": "^18.2.0",
 | 
					        "react-dom": "^18.2.0",
 | 
				
			||||||
        "react-hook-form": "^7.54.2",
 | 
					        "react-hook-form": "^7.54.2",
 | 
				
			||||||
 | 
					        "react-quill": "^2.0.0",
 | 
				
			||||||
        "react-redux": "^9.2.0",
 | 
					        "react-redux": "^9.2.0",
 | 
				
			||||||
        "react-router-dom": "^6.20.1",
 | 
					        "react-router-dom": "^6.20.1",
 | 
				
			||||||
        "react-toastify": "^11.0.2",
 | 
					        "react-toastify": "^11.0.2",
 | 
				
			||||||
@ -1494,6 +1495,15 @@
 | 
				
			|||||||
      "integrity": "sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==",
 | 
					      "integrity": "sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==",
 | 
				
			||||||
      "devOptional": true
 | 
					      "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": {
 | 
					    "node_modules/@types/react": {
 | 
				
			||||||
      "version": "18.3.16",
 | 
					      "version": "18.3.16",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.16.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.16.tgz",
 | 
				
			||||||
@ -2100,7 +2110,6 @@
 | 
				
			|||||||
      "version": "1.0.8",
 | 
					      "version": "1.0.8",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz",
 | 
				
			||||||
      "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==",
 | 
					      "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==",
 | 
				
			||||||
      "dev": true,
 | 
					 | 
				
			||||||
      "dependencies": {
 | 
					      "dependencies": {
 | 
				
			||||||
        "call-bind-apply-helpers": "^1.0.0",
 | 
					        "call-bind-apply-helpers": "^1.0.0",
 | 
				
			||||||
        "es-define-property": "^1.0.0",
 | 
					        "es-define-property": "^1.0.0",
 | 
				
			||||||
@ -2118,7 +2127,6 @@
 | 
				
			|||||||
      "version": "1.0.1",
 | 
					      "version": "1.0.1",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.1.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.1.tgz",
 | 
				
			||||||
      "integrity": "sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g==",
 | 
					      "integrity": "sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g==",
 | 
				
			||||||
      "dev": true,
 | 
					 | 
				
			||||||
      "dependencies": {
 | 
					      "dependencies": {
 | 
				
			||||||
        "es-errors": "^1.3.0",
 | 
					        "es-errors": "^1.3.0",
 | 
				
			||||||
        "function-bind": "^1.1.2"
 | 
					        "function-bind": "^1.1.2"
 | 
				
			||||||
@ -2131,7 +2139,6 @@
 | 
				
			|||||||
      "version": "1.0.2",
 | 
					      "version": "1.0.2",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.2.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.2.tgz",
 | 
				
			||||||
      "integrity": "sha512-0lk0PHFe/uz0vl527fG9CgdE9WdafjDbCXvBbs+LUv000TVt2Jjhqbs4Jwm8gz070w8xXyEAxrPOMullsxXeGg==",
 | 
					      "integrity": "sha512-0lk0PHFe/uz0vl527fG9CgdE9WdafjDbCXvBbs+LUv000TVt2Jjhqbs4Jwm8gz070w8xXyEAxrPOMullsxXeGg==",
 | 
				
			||||||
      "dev": true,
 | 
					 | 
				
			||||||
      "dependencies": {
 | 
					      "dependencies": {
 | 
				
			||||||
        "call-bind": "^1.0.8",
 | 
					        "call-bind": "^1.0.8",
 | 
				
			||||||
        "get-intrinsic": "^1.2.5"
 | 
					        "get-intrinsic": "^1.2.5"
 | 
				
			||||||
@ -2210,6 +2217,15 @@
 | 
				
			|||||||
        "node": ">=6.0"
 | 
					        "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": {
 | 
					    "node_modules/clsx": {
 | 
				
			||||||
      "version": "2.1.1",
 | 
					      "version": "2.1.1",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
 | 
					      "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": {
 | 
					    "node_modules/deep-is": {
 | 
				
			||||||
      "version": "0.1.4",
 | 
					      "version": "0.1.4",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
 | 
				
			||||||
@ -2383,7 +2419,6 @@
 | 
				
			|||||||
      "version": "1.1.4",
 | 
					      "version": "1.1.4",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz",
 | 
				
			||||||
      "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==",
 | 
					      "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==",
 | 
				
			||||||
      "dev": true,
 | 
					 | 
				
			||||||
      "dependencies": {
 | 
					      "dependencies": {
 | 
				
			||||||
        "es-define-property": "^1.0.0",
 | 
					        "es-define-property": "^1.0.0",
 | 
				
			||||||
        "es-errors": "^1.3.0",
 | 
					        "es-errors": "^1.3.0",
 | 
				
			||||||
@ -2400,7 +2435,6 @@
 | 
				
			|||||||
      "version": "1.2.1",
 | 
					      "version": "1.2.1",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz",
 | 
				
			||||||
      "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==",
 | 
					      "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==",
 | 
				
			||||||
      "dev": true,
 | 
					 | 
				
			||||||
      "dependencies": {
 | 
					      "dependencies": {
 | 
				
			||||||
        "define-data-property": "^1.0.1",
 | 
					        "define-data-property": "^1.0.1",
 | 
				
			||||||
        "has-property-descriptors": "^1.0.0",
 | 
					        "has-property-descriptors": "^1.0.0",
 | 
				
			||||||
@ -2482,7 +2516,6 @@
 | 
				
			|||||||
      "version": "1.0.0",
 | 
					      "version": "1.0.0",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.0.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.0.tgz",
 | 
				
			||||||
      "integrity": "sha512-9+Sj30DIu+4KvHqMfLUGLFYL2PkURSYMVXJyXe92nFRvlYq5hBjLEhblKB+vkd/WVlUYMWigiY07T91Fkk0+4A==",
 | 
					      "integrity": "sha512-9+Sj30DIu+4KvHqMfLUGLFYL2PkURSYMVXJyXe92nFRvlYq5hBjLEhblKB+vkd/WVlUYMWigiY07T91Fkk0+4A==",
 | 
				
			||||||
      "dev": true,
 | 
					 | 
				
			||||||
      "dependencies": {
 | 
					      "dependencies": {
 | 
				
			||||||
        "call-bind-apply-helpers": "^1.0.0",
 | 
					        "call-bind-apply-helpers": "^1.0.0",
 | 
				
			||||||
        "es-errors": "^1.3.0",
 | 
					        "es-errors": "^1.3.0",
 | 
				
			||||||
@ -2575,7 +2608,6 @@
 | 
				
			|||||||
      "version": "1.0.1",
 | 
					      "version": "1.0.1",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
 | 
				
			||||||
      "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
 | 
					      "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
 | 
				
			||||||
      "dev": true,
 | 
					 | 
				
			||||||
      "engines": {
 | 
					      "engines": {
 | 
				
			||||||
        "node": ">= 0.4"
 | 
					        "node": ">= 0.4"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
@ -2584,7 +2616,6 @@
 | 
				
			|||||||
      "version": "1.3.0",
 | 
					      "version": "1.3.0",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
 | 
				
			||||||
      "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
 | 
					      "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
 | 
				
			||||||
      "dev": true,
 | 
					 | 
				
			||||||
      "engines": {
 | 
					      "engines": {
 | 
				
			||||||
        "node": ">= 0.4"
 | 
					        "node": ">= 0.4"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
@ -2949,6 +2980,12 @@
 | 
				
			|||||||
        "node": ">=0.10.0"
 | 
					        "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": {
 | 
					    "node_modules/events": {
 | 
				
			||||||
      "version": "3.3.0",
 | 
					      "version": "3.3.0",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz",
 | 
				
			||||||
@ -2959,11 +2996,23 @@
 | 
				
			|||||||
        "node": ">=0.8.x"
 | 
					        "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": {
 | 
					    "node_modules/fast-deep-equal": {
 | 
				
			||||||
      "version": "3.1.3",
 | 
					      "version": "3.1.3",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
 | 
				
			||||||
      "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
 | 
					      "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": {
 | 
					    "node_modules/fast-json-stable-stringify": {
 | 
				
			||||||
      "version": "2.1.0",
 | 
					      "version": "2.1.0",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
 | 
					      "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",
 | 
					      "version": "1.1.2",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
 | 
				
			||||||
      "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
 | 
					      "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
 | 
				
			||||||
      "dev": true,
 | 
					 | 
				
			||||||
      "funding": {
 | 
					      "funding": {
 | 
				
			||||||
        "url": "https://github.com/sponsors/ljharb"
 | 
					        "url": "https://github.com/sponsors/ljharb"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
@ -3150,7 +3198,6 @@
 | 
				
			|||||||
      "version": "1.2.3",
 | 
					      "version": "1.2.3",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz",
 | 
				
			||||||
      "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==",
 | 
					      "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==",
 | 
				
			||||||
      "dev": true,
 | 
					 | 
				
			||||||
      "funding": {
 | 
					      "funding": {
 | 
				
			||||||
        "url": "https://github.com/sponsors/ljharb"
 | 
					        "url": "https://github.com/sponsors/ljharb"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
@ -3167,7 +3214,6 @@
 | 
				
			|||||||
      "version": "1.2.5",
 | 
					      "version": "1.2.5",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.5.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.5.tgz",
 | 
				
			||||||
      "integrity": "sha512-Y4+pKa7XeRUPWFNvOOYHkRYrfzW07oraURSvjDmRVOJ748OrVmeXtpE4+GCEHncjCjkTxPNRt8kEbxDhsn6VTg==",
 | 
					      "integrity": "sha512-Y4+pKa7XeRUPWFNvOOYHkRYrfzW07oraURSvjDmRVOJ748OrVmeXtpE4+GCEHncjCjkTxPNRt8kEbxDhsn6VTg==",
 | 
				
			||||||
      "dev": true,
 | 
					 | 
				
			||||||
      "dependencies": {
 | 
					      "dependencies": {
 | 
				
			||||||
        "call-bind-apply-helpers": "^1.0.0",
 | 
					        "call-bind-apply-helpers": "^1.0.0",
 | 
				
			||||||
        "dunder-proto": "^1.0.0",
 | 
					        "dunder-proto": "^1.0.0",
 | 
				
			||||||
@ -3277,7 +3323,6 @@
 | 
				
			|||||||
      "version": "1.2.0",
 | 
					      "version": "1.2.0",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
 | 
				
			||||||
      "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
 | 
					      "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
 | 
				
			||||||
      "dev": true,
 | 
					 | 
				
			||||||
      "engines": {
 | 
					      "engines": {
 | 
				
			||||||
        "node": ">= 0.4"
 | 
					        "node": ">= 0.4"
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
@ -3319,7 +3364,6 @@
 | 
				
			|||||||
      "version": "1.0.2",
 | 
					      "version": "1.0.2",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz",
 | 
				
			||||||
      "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==",
 | 
					      "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==",
 | 
				
			||||||
      "dev": true,
 | 
					 | 
				
			||||||
      "dependencies": {
 | 
					      "dependencies": {
 | 
				
			||||||
        "es-define-property": "^1.0.0"
 | 
					        "es-define-property": "^1.0.0"
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
@ -3346,7 +3390,6 @@
 | 
				
			|||||||
      "version": "1.1.0",
 | 
					      "version": "1.1.0",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
 | 
				
			||||||
      "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
 | 
					      "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
 | 
				
			||||||
      "dev": true,
 | 
					 | 
				
			||||||
      "engines": {
 | 
					      "engines": {
 | 
				
			||||||
        "node": ">= 0.4"
 | 
					        "node": ">= 0.4"
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
@ -3358,7 +3401,6 @@
 | 
				
			|||||||
      "version": "1.0.2",
 | 
					      "version": "1.0.2",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
 | 
				
			||||||
      "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
 | 
					      "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
 | 
				
			||||||
      "dev": true,
 | 
					 | 
				
			||||||
      "dependencies": {
 | 
					      "dependencies": {
 | 
				
			||||||
        "has-symbols": "^1.0.3"
 | 
					        "has-symbols": "^1.0.3"
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
@ -3373,7 +3415,6 @@
 | 
				
			|||||||
      "version": "2.0.2",
 | 
					      "version": "2.0.2",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
 | 
				
			||||||
      "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
 | 
					      "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
 | 
				
			||||||
      "dev": true,
 | 
					 | 
				
			||||||
      "dependencies": {
 | 
					      "dependencies": {
 | 
				
			||||||
        "function-bind": "^1.1.2"
 | 
					        "function-bind": "^1.1.2"
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
@ -3460,6 +3501,22 @@
 | 
				
			|||||||
        "node": ">= 0.4"
 | 
					        "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": {
 | 
					    "node_modules/is-array-buffer": {
 | 
				
			||||||
      "version": "3.0.4",
 | 
					      "version": "3.0.4",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz",
 | 
				
			||||||
@ -3568,7 +3625,6 @@
 | 
				
			|||||||
      "version": "1.0.5",
 | 
					      "version": "1.0.5",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz",
 | 
				
			||||||
      "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==",
 | 
					      "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==",
 | 
				
			||||||
      "dev": true,
 | 
					 | 
				
			||||||
      "dependencies": {
 | 
					      "dependencies": {
 | 
				
			||||||
        "has-tostringtag": "^1.0.0"
 | 
					        "has-tostringtag": "^1.0.0"
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
@ -3683,7 +3739,6 @@
 | 
				
			|||||||
      "version": "1.2.0",
 | 
					      "version": "1.2.0",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.0.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.0.tgz",
 | 
				
			||||||
      "integrity": "sha512-B6ohK4ZmoftlUe+uvenXSbPJFo6U37BH7oO1B3nQH8f/7h27N56s85MhUtbFJAziz5dcmuR3i8ovUl35zp8pFA==",
 | 
					      "integrity": "sha512-B6ohK4ZmoftlUe+uvenXSbPJFo6U37BH7oO1B3nQH8f/7h27N56s85MhUtbFJAziz5dcmuR3i8ovUl35zp8pFA==",
 | 
				
			||||||
      "dev": true,
 | 
					 | 
				
			||||||
      "dependencies": {
 | 
					      "dependencies": {
 | 
				
			||||||
        "call-bind": "^1.0.7",
 | 
					        "call-bind": "^1.0.7",
 | 
				
			||||||
        "gopd": "^1.1.0",
 | 
					        "gopd": "^1.1.0",
 | 
				
			||||||
@ -4026,6 +4081,12 @@
 | 
				
			|||||||
        "url": "https://github.com/sponsors/sindresorhus"
 | 
					        "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": {
 | 
					    "node_modules/lodash.merge": {
 | 
				
			||||||
      "version": "4.6.2",
 | 
					      "version": "4.6.2",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
 | 
				
			||||||
@ -4166,11 +4227,26 @@
 | 
				
			|||||||
        "url": "https://github.com/sponsors/ljharb"
 | 
					        "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": {
 | 
					    "node_modules/object-keys": {
 | 
				
			||||||
      "version": "1.1.1",
 | 
					      "version": "1.1.1",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
 | 
				
			||||||
      "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==",
 | 
					      "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==",
 | 
				
			||||||
      "dev": true,
 | 
					 | 
				
			||||||
      "engines": {
 | 
					      "engines": {
 | 
				
			||||||
        "node": ">= 0.4"
 | 
					        "node": ">= 0.4"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
@ -4312,6 +4388,12 @@
 | 
				
			|||||||
      "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==",
 | 
					      "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==",
 | 
				
			||||||
      "license": "(MIT AND Zlib)"
 | 
					      "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": {
 | 
					    "node_modules/parent-module": {
 | 
				
			||||||
      "version": "1.0.1",
 | 
					      "version": "1.0.1",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
 | 
					      "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": {
 | 
					    "node_modules/randombytes": {
 | 
				
			||||||
      "version": "2.1.0",
 | 
					      "version": "2.1.0",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
 | 
					      "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",
 | 
					      "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
 | 
				
			||||||
      "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
 | 
					      "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": {
 | 
					    "node_modules/react-redux": {
 | 
				
			||||||
      "version": "9.2.0",
 | 
					      "version": "9.2.0",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz",
 | 
				
			||||||
@ -4651,7 +4776,6 @@
 | 
				
			|||||||
      "version": "1.5.3",
 | 
					      "version": "1.5.3",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.3.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.3.tgz",
 | 
				
			||||||
      "integrity": "sha512-vqlC04+RQoFalODCbCumG2xIOvapzVMHwsyIGM/SIE8fRhFFsXeH8/QQ+s0T0kDAhKc4k30s73/0ydkHQz6HlQ==",
 | 
					      "integrity": "sha512-vqlC04+RQoFalODCbCumG2xIOvapzVMHwsyIGM/SIE8fRhFFsXeH8/QQ+s0T0kDAhKc4k30s73/0ydkHQz6HlQ==",
 | 
				
			||||||
      "dev": true,
 | 
					 | 
				
			||||||
      "dependencies": {
 | 
					      "dependencies": {
 | 
				
			||||||
        "call-bind": "^1.0.7",
 | 
					        "call-bind": "^1.0.7",
 | 
				
			||||||
        "define-properties": "^1.2.1",
 | 
					        "define-properties": "^1.2.1",
 | 
				
			||||||
@ -4946,7 +5070,6 @@
 | 
				
			|||||||
      "version": "1.2.2",
 | 
					      "version": "1.2.2",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
 | 
				
			||||||
      "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==",
 | 
					      "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==",
 | 
				
			||||||
      "dev": true,
 | 
					 | 
				
			||||||
      "dependencies": {
 | 
					      "dependencies": {
 | 
				
			||||||
        "define-data-property": "^1.1.4",
 | 
					        "define-data-property": "^1.1.4",
 | 
				
			||||||
        "es-errors": "^1.3.0",
 | 
					        "es-errors": "^1.3.0",
 | 
				
			||||||
@ -4963,7 +5086,6 @@
 | 
				
			|||||||
      "version": "2.0.2",
 | 
					      "version": "2.0.2",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz",
 | 
				
			||||||
      "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==",
 | 
					      "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==",
 | 
				
			||||||
      "dev": true,
 | 
					 | 
				
			||||||
      "dependencies": {
 | 
					      "dependencies": {
 | 
				
			||||||
        "define-data-property": "^1.1.4",
 | 
					        "define-data-property": "^1.1.4",
 | 
				
			||||||
        "es-errors": "^1.3.0",
 | 
					        "es-errors": "^1.3.0",
 | 
				
			||||||
 | 
				
			|||||||
@ -30,6 +30,7 @@
 | 
				
			|||||||
    "react-apexcharts": "^1.7.0",
 | 
					    "react-apexcharts": "^1.7.0",
 | 
				
			||||||
    "react-dom": "^18.2.0",
 | 
					    "react-dom": "^18.2.0",
 | 
				
			||||||
    "react-hook-form": "^7.54.2",
 | 
					    "react-hook-form": "^7.54.2",
 | 
				
			||||||
 | 
					    "react-quill": "^2.0.0",
 | 
				
			||||||
    "react-redux": "^9.2.0",
 | 
					    "react-redux": "^9.2.0",
 | 
				
			||||||
    "react-router-dom": "^6.20.1",
 | 
					    "react-router-dom": "^6.20.1",
 | 
				
			||||||
    "react-toastify": "^11.0.2",
 | 
					    "react-toastify": "^11.0.2",
 | 
				
			||||||
 | 
				
			|||||||
@ -190,6 +190,12 @@
 | 
				
			|||||||
  padding-left: 50px;
 | 
					  padding-left: 50px;
 | 
				
			||||||
} */
 | 
					} */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.small-text{
 | 
					.small-text {
 | 
				
			||||||
  font-size: 12px;
 | 
					  font-size: 12px;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.layout-wrapper:not(.layout-horizontal)
 | 
				
			||||||
 | 
					  .layout-navbar
 | 
				
			||||||
 | 
					  .dropdown-menu[data-bs-popper] {
 | 
				
			||||||
 | 
					  inset-block-start: 100%;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										14
									
								
								public/assets/vendor/css/core.css
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										14
									
								
								public/assets/vendor/css/core.css
									
									
									
									
										vendored
									
									
								
							@ -5069,6 +5069,9 @@ fieldset:disabled .btn {
 | 
				
			|||||||
.card-group > .card {
 | 
					.card-group > .card {
 | 
				
			||||||
  margin-bottom: var(--bs-card-group-margin);
 | 
					  margin-bottom: var(--bs-card-group-margin);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					.card-minHeight{
 | 
				
			||||||
 | 
					  min-height: 430px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@media (min-width: 576px) {
 | 
					@media (min-width: 576px) {
 | 
				
			||||||
  .card-group {
 | 
					  .card-group {
 | 
				
			||||||
    display: flex;
 | 
					    display: flex;
 | 
				
			||||||
@ -16889,7 +16892,8 @@ li:not(:first-child) .dropdown-item,
 | 
				
			|||||||
  box-shadow: var(--bs-box-shadow-xs);
 | 
					  box-shadow: var(--bs-box-shadow-xs);
 | 
				
			||||||
  filter: none;
 | 
					  filter: none;
 | 
				
			||||||
  opacity: 1;
 | 
					  opacity: 1;
 | 
				
			||||||
  transform: translate(23px, -25px);
 | 
					  transform: translate(6px, -9px);
 | 
				
			||||||
 | 
					  z-index: 1056;
 | 
				
			||||||
  border-radius: 0.25rem;
 | 
					  border-radius: 0.25rem;
 | 
				
			||||||
  transition: all 0.23s ease 0.1s;
 | 
					  transition: all 0.23s ease 0.1s;
 | 
				
			||||||
  /* For hover effect of close btn */
 | 
					  /* For hover effect of close btn */
 | 
				
			||||||
@ -16899,18 +16903,18 @@ li:not(:first-child) .dropdown-item,
 | 
				
			|||||||
    transition: none;
 | 
					    transition: none;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
.modal .btn-close:hover,
 | 
					/* .modal .btn-close:hover,
 | 
				
			||||||
.modal .btn-close:focus,
 | 
					.modal .btn-close:focus,
 | 
				
			||||||
.modal .btn-close:active {
 | 
					.modal .btn-close:active {
 | 
				
			||||||
  opacity: 1;
 | 
					  opacity: 1;
 | 
				
			||||||
  outline: 0;
 | 
					  outline: 0;
 | 
				
			||||||
  transform: translate(20px, -20px);
 | 
					  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:focus,
 | 
				
			||||||
:dir(rtl) .modal .btn-close:active {
 | 
					:dir(rtl) .modal .btn-close:active {
 | 
				
			||||||
  transform: translate(26px, -20px);
 | 
					  transform: translate(26px, -20px);
 | 
				
			||||||
}
 | 
					} */
 | 
				
			||||||
.modal .btn-close::before {
 | 
					.modal .btn-close::before {
 | 
				
			||||||
  display: block;
 | 
					  display: block;
 | 
				
			||||||
  background-color: var(--bs-secondary-color);
 | 
					  background-color: var(--bs-secondary-color);
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										10
									
								
								src/App.tsx
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								src/App.tsx
									
									
									
									
									
								
							@ -1,3 +1,4 @@
 | 
				
			|||||||
 | 
					import { DireProvider } from "./Context/DireContext";
 | 
				
			||||||
import AppRoutes from "./router/AppRoutes";
 | 
					import AppRoutes from "./router/AppRoutes";
 | 
				
			||||||
import { ToastContainer } from "react-toastify";
 | 
					import { ToastContainer } from "react-toastify";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -5,8 +6,13 @@ import { ToastContainer } from "react-toastify";
 | 
				
			|||||||
const App = () => {
 | 
					const App = () => {
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <div className="app">
 | 
					    <div className="app">
 | 
				
			||||||
      <AppRoutes />
 | 
					      <DireProvider>
 | 
				
			||||||
    <ToastContainer></ToastContainer>
 | 
					        <AppRoutes />
 | 
				
			||||||
 | 
					      </DireProvider>
 | 
				
			||||||
 | 
					      
 | 
				
			||||||
 | 
					      <ToastContainer>
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					    </ToastContainer>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										21
									
								
								src/Context/DireContext.jsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								src/Context/DireContext.jsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,21 @@
 | 
				
			|||||||
 | 
					import React, { createContext, useContext, useState } from "react";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const DireContext = createContext(undefined);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const DireProvider = ({ children }) => {
 | 
				
			||||||
 | 
					  const [dirActions, setDirActions] = useState([]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <DireContext.Provider value={{ dirActions, setDirActions }}>
 | 
				
			||||||
 | 
					      {children}
 | 
				
			||||||
 | 
					    </DireContext.Provider>
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const useDir = () => {
 | 
				
			||||||
 | 
					  const context = useContext(DireContext);
 | 
				
			||||||
 | 
					  if (!context) {
 | 
				
			||||||
 | 
					    throw new Error("useDir must be used within a <DireProvider>");
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  return context;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
							
								
								
									
										15
									
								
								src/Context/FabContext.jsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								src/Context/FabContext.jsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,15 @@
 | 
				
			|||||||
 | 
					import React, { createContext, useContext, useState } from "react";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const FabContext = createContext();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const FabProvider = ({ children }) => {
 | 
				
			||||||
 | 
					  const [actions, setActions] = useState([]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <FabContext.Provider value={{ actions, setActions }}>
 | 
				
			||||||
 | 
					      {children}
 | 
				
			||||||
 | 
					    </FabContext.Provider>
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const useFab = () => useContext(FabContext);
 | 
				
			||||||
@ -5,6 +5,7 @@ import { convertShortTime } from "../../utils/dateUtils";
 | 
				
			|||||||
import RenderAttendanceStatus from "./RenderAttendanceStatus";
 | 
					import RenderAttendanceStatus from "./RenderAttendanceStatus";
 | 
				
			||||||
import usePagination from "../../hooks/usePagination";
 | 
					import usePagination from "../../hooks/usePagination";
 | 
				
			||||||
import { useNavigate } from "react-router-dom";
 | 
					import { useNavigate } from "react-router-dom";
 | 
				
			||||||
 | 
					import {ITEMS_PER_PAGE} from "../../utils/constants";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const Attendance = ({ attendance, getRole, handleModalData }) => {
 | 
					const Attendance = ({ attendance, getRole, handleModalData }) => {
 | 
				
			||||||
  const [loading, setLoading] = useState(false);
 | 
					  const [loading, setLoading] = useState(false);
 | 
				
			||||||
@ -33,7 +34,7 @@ const Attendance = ({ attendance, getRole, handleModalData }) => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  const { currentPage, totalPages, currentItems, paginate } = usePagination(
 | 
					  const { currentPage, totalPages, currentItems, paginate } = usePagination(
 | 
				
			||||||
    filteredData,
 | 
					    filteredData,
 | 
				
			||||||
    20
 | 
					    ITEMS_PER_PAGE
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <>
 | 
					    <>
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										192
									
								
								src/components/Directory/CardViewDirectory.jsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										192
									
								
								src/components/Directory/CardViewDirectory.jsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,192 @@
 | 
				
			|||||||
 | 
					import React from "react";
 | 
				
			||||||
 | 
					import Avatar from "../common/Avatar";
 | 
				
			||||||
 | 
					import { getBucketNameById } from "./DirectoryUtils";
 | 
				
			||||||
 | 
					import { useBuckets } from "../../hooks/useDirectory";
 | 
				
			||||||
 | 
					import { getPhoneIcon } from "./DirectoryUtils";
 | 
				
			||||||
 | 
					import { useDir } from "../../Context/DireContext";
 | 
				
			||||||
 | 
					const CardViewDirectory = ({
 | 
				
			||||||
 | 
					  IsActive,
 | 
				
			||||||
 | 
					  contact,
 | 
				
			||||||
 | 
					  setSelectedContact,
 | 
				
			||||||
 | 
					  setIsOpenModal,
 | 
				
			||||||
 | 
					  setOpen_contact,
 | 
				
			||||||
 | 
					  setIsOpenModalNote,
 | 
				
			||||||
 | 
					  IsDeleted,
 | 
				
			||||||
 | 
					  restore,
 | 
				
			||||||
 | 
					}) => {
 | 
				
			||||||
 | 
					  const { buckets } = useBuckets();
 | 
				
			||||||
 | 
					  const { dirActions, setDirActions } = useDir();
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <div
 | 
				
			||||||
 | 
					      className="card text-start border-1"
 | 
				
			||||||
 | 
					      style={{ background: `${!IsActive ? "#f8f6f6" : ""}` }}
 | 
				
			||||||
 | 
					    >
 | 
				
			||||||
 | 
					      <div className="card-body  px-1 py-2 pb-0">
 | 
				
			||||||
 | 
					        <div className="d-flex justify-content-between">
 | 
				
			||||||
 | 
					          <div
 | 
				
			||||||
 | 
					            className={`d-flex align-items-center ${
 | 
				
			||||||
 | 
					              IsActive && "cursor-pointer"
 | 
				
			||||||
 | 
					            }`}
 | 
				
			||||||
 | 
					            onClick={() => {
 | 
				
			||||||
 | 
					              if (IsActive) {
 | 
				
			||||||
 | 
					                setIsOpenModalNote(true);
 | 
				
			||||||
 | 
					                setOpen_contact(contact);
 | 
				
			||||||
 | 
					              }
 | 
				
			||||||
 | 
					            }}
 | 
				
			||||||
 | 
					          >
 | 
				
			||||||
 | 
					            <Avatar
 | 
				
			||||||
 | 
					              size="xs"
 | 
				
			||||||
 | 
					              firstName={
 | 
				
			||||||
 | 
					                (contact?.name || "").trim().split(" ")[0]?.charAt(0) || ""
 | 
				
			||||||
 | 
					              }
 | 
				
			||||||
 | 
					              lastName={
 | 
				
			||||||
 | 
					                (contact?.name || "").trim().split(" ")[1]?.charAt(0) || ""
 | 
				
			||||||
 | 
					              }
 | 
				
			||||||
 | 
					            />{" "}
 | 
				
			||||||
 | 
					            <span className="text-heading fs-6"> {contact?.name}</span>
 | 
				
			||||||
 | 
					          </div>
 | 
				
			||||||
 | 
					          <div>
 | 
				
			||||||
 | 
					            {IsActive && (
 | 
				
			||||||
 | 
					              <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 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 cursor-pointer py-1">
 | 
				
			||||||
 | 
					                      <i className="bx bx-edit bx-xs text-primary  me-2"></i>
 | 
				
			||||||
 | 
					                      <span className="align-left ">Modify</span>
 | 
				
			||||||
 | 
					                    </a>
 | 
				
			||||||
 | 
					                  </li>
 | 
				
			||||||
 | 
					                  <li>
 | 
				
			||||||
 | 
					                    <a
 | 
				
			||||||
 | 
					                      className="dropdown-item  px-2 cursor-pointer py-1"
 | 
				
			||||||
 | 
					                      onClick={() => IsDeleted(contact?.id)}
 | 
				
			||||||
 | 
					                    >
 | 
				
			||||||
 | 
					                      <i className="bx bx-trash text-danger  bx-xs me-2"></i>
 | 
				
			||||||
 | 
					                      <span className="align-left">Delete</span>
 | 
				
			||||||
 | 
					                    </a>
 | 
				
			||||||
 | 
					                  </li>
 | 
				
			||||||
 | 
					                </ul>
 | 
				
			||||||
 | 
					              </div>
 | 
				
			||||||
 | 
					            )}
 | 
				
			||||||
 | 
					            {!IsActive && (
 | 
				
			||||||
 | 
					              <i
 | 
				
			||||||
 | 
					                className={`bx  ${
 | 
				
			||||||
 | 
					                  dirActions.action && dirActions.id === contact.id
 | 
				
			||||||
 | 
					                    ? "bx-loader-alt bx-spin"
 | 
				
			||||||
 | 
					                    : "bx-recycle"
 | 
				
			||||||
 | 
					                } me-1 text-primary cursor-pointer`}
 | 
				
			||||||
 | 
					                title="Restore"
 | 
				
			||||||
 | 
					                onClick={() => {
 | 
				
			||||||
 | 
					                  setDirActions({ action: false, id: contact.id });
 | 
				
			||||||
 | 
					                  restore(contact.id);
 | 
				
			||||||
 | 
					                }}
 | 
				
			||||||
 | 
					              ></i>
 | 
				
			||||||
 | 
					            )}
 | 
				
			||||||
 | 
					          </div>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <ul className="list-inline m-0 ps-4 d-flex align-items-start">
 | 
				
			||||||
 | 
					          {/* <li className="list-inline-item me-1 small">
 | 
				
			||||||
 | 
					            <i className="fa-solid fa-briefcase me-2"></i>
 | 
				
			||||||
 | 
					          </li> */}
 | 
				
			||||||
 | 
					          <li className="list-inline-item text-break small ms-5">
 | 
				
			||||||
 | 
					            {contact.organization}
 | 
				
			||||||
 | 
					          </li>
 | 
				
			||||||
 | 
					        </ul>
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					      <div
 | 
				
			||||||
 | 
					        className={`card-footer text-start px-1 py-1 ${
 | 
				
			||||||
 | 
					          IsActive && "cursor-pointer"
 | 
				
			||||||
 | 
					        }`}
 | 
				
			||||||
 | 
					        onClick={() => {
 | 
				
			||||||
 | 
					          if (IsActive) {
 | 
				
			||||||
 | 
					            setIsOpenModalNote(true);
 | 
				
			||||||
 | 
					            setOpen_contact(contact);
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }}
 | 
				
			||||||
 | 
					      >
 | 
				
			||||||
 | 
					        <hr className="my-0" />
 | 
				
			||||||
 | 
					        {contact.contactEmails[0] && (
 | 
				
			||||||
 | 
					          <ul className="list-unstyled my-1 d-flex align-items-start   ms-2">
 | 
				
			||||||
 | 
					            <li className="me-2">
 | 
				
			||||||
 | 
					              <i className="bx bx-envelope bx-xs mt-1"></i>
 | 
				
			||||||
 | 
					            </li>
 | 
				
			||||||
 | 
					            <li className="flex-grow-1 text-break small">
 | 
				
			||||||
 | 
					              {contact.contactEmails[0].emailAddress}
 | 
				
			||||||
 | 
					            </li>
 | 
				
			||||||
 | 
					          </ul>
 | 
				
			||||||
 | 
					        )}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        {contact.contactPhones[0] && (
 | 
				
			||||||
 | 
					          <ul className="list-inline m-0  ms-2">
 | 
				
			||||||
 | 
					            <li className="list-inline-item me-1">
 | 
				
			||||||
 | 
					              <i
 | 
				
			||||||
 | 
					                className={` ${getPhoneIcon(
 | 
				
			||||||
 | 
					                  contact.contactPhones[0].label
 | 
				
			||||||
 | 
					                )} bx-xs`}
 | 
				
			||||||
 | 
					              ></i>
 | 
				
			||||||
 | 
					            </li>
 | 
				
			||||||
 | 
					            <li className="list-inline-item text-small">
 | 
				
			||||||
 | 
					              {contact.contactPhones[0]?.phoneNumber}
 | 
				
			||||||
 | 
					            </li>
 | 
				
			||||||
 | 
					          </ul>
 | 
				
			||||||
 | 
					        )}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        {contact?.contactCategory?.name ? (
 | 
				
			||||||
 | 
					          <ul className="list-inline m-0   ms-2">
 | 
				
			||||||
 | 
					            <li className="list-inline-item me-2 my-1">
 | 
				
			||||||
 | 
					              <i className="fa-solid fa-tag fs-6 ms-1"></i>
 | 
				
			||||||
 | 
					            </li>
 | 
				
			||||||
 | 
					            <li className="list-inline-item text-small active">
 | 
				
			||||||
 | 
					              {contact?.contactCategory?.name}
 | 
				
			||||||
 | 
					            </li>
 | 
				
			||||||
 | 
					          </ul>
 | 
				
			||||||
 | 
					        ) : (
 | 
				
			||||||
 | 
					          <ul className="list-inline m-0   ms-2">
 | 
				
			||||||
 | 
					            <li className="list-inline-item me-2 my-1">
 | 
				
			||||||
 | 
					              <i className="fa-solid fa-tag fs-6 ms-1"></i>
 | 
				
			||||||
 | 
					            </li>
 | 
				
			||||||
 | 
					            <li className="list-inline-item text-small active">Other</li>
 | 
				
			||||||
 | 
					          </ul>
 | 
				
			||||||
 | 
					        )}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <ul className="list-inline m-0   ms-2">
 | 
				
			||||||
 | 
					          {contact?.bucketIds?.map((bucketId) => (
 | 
				
			||||||
 | 
					            <li key={bucketId} className="list-inline-item me-1">
 | 
				
			||||||
 | 
					              <span
 | 
				
			||||||
 | 
					                className="badge bg-label-primary rounded-pill d-flex align-items-center gap-1"
 | 
				
			||||||
 | 
					                style={{ padding: "0.1rem 0.3rem" }}
 | 
				
			||||||
 | 
					              >
 | 
				
			||||||
 | 
					                <i className="bx bx-pin bx-xs"></i>
 | 
				
			||||||
 | 
					                <span className="small-text">
 | 
				
			||||||
 | 
					                  {getBucketNameById(buckets, bucketId)}
 | 
				
			||||||
 | 
					                </span>
 | 
				
			||||||
 | 
					              </span>
 | 
				
			||||||
 | 
					            </li>
 | 
				
			||||||
 | 
					          ))}
 | 
				
			||||||
 | 
					        </ul>
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default CardViewDirectory;
 | 
				
			||||||
							
								
								
									
										65
									
								
								src/components/Directory/DirectorySchema.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										65
									
								
								src/components/Directory/DirectorySchema.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,65 @@
 | 
				
			|||||||
 | 
					import { z } from "zod";
 | 
				
			||||||
 | 
					export const ContactSchema = z
 | 
				
			||||||
 | 
					  .object({
 | 
				
			||||||
 | 
					    name: z.string().min(1, "Name is required"),
 | 
				
			||||||
 | 
					    organization: z.string().min(1, "Organization name is required"),
 | 
				
			||||||
 | 
					    contactCategoryId: z.string().nullable().optional(),
 | 
				
			||||||
 | 
					    address: z.string().optional(),
 | 
				
			||||||
 | 
					    description: z.string().min(1, { message: "Description is required" }),
 | 
				
			||||||
 | 
					    projectIds: z.array(z.string()).nullable().optional(), // min(1, "Project is required")
 | 
				
			||||||
 | 
					    contactEmails: z
 | 
				
			||||||
 | 
					      .array(
 | 
				
			||||||
 | 
					        z.object({
 | 
				
			||||||
 | 
					          label: z.string(),
 | 
				
			||||||
 | 
					           emailAddress: z.string().email("Invalid email").or(z.literal("")),
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					      )
 | 
				
			||||||
 | 
					      .optional()
 | 
				
			||||||
 | 
					      .default([]),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    contactPhones: z
 | 
				
			||||||
 | 
					      .array(
 | 
				
			||||||
 | 
					        z.object({
 | 
				
			||||||
 | 
					          label: z.string(),
 | 
				
			||||||
 | 
					          phoneNumber: z
 | 
				
			||||||
 | 
					            .string()
 | 
				
			||||||
 | 
					            .min(6, "Invalid Number")
 | 
				
			||||||
 | 
					            .max(13, "Invalid Number")
 | 
				
			||||||
 | 
					            .regex(/^[\d\s+()-]+$/, "Invalid phone number format").or(z.literal("")),
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					      )
 | 
				
			||||||
 | 
					      .optional()
 | 
				
			||||||
 | 
					      .default([]),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    tags: z
 | 
				
			||||||
 | 
					      .array(
 | 
				
			||||||
 | 
					        z.object({
 | 
				
			||||||
 | 
					          id: z.string().nullable(),
 | 
				
			||||||
 | 
					          name: z.string(),
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					      )
 | 
				
			||||||
 | 
					      .min(1, { message: "At least one tag is required" }),
 | 
				
			||||||
 | 
					bucketIds: z.array(z.string()).nonempty({ message: "At least one label is required" })
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//    .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"],
 | 
				
			||||||
 | 
					// });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Buckets
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const bucketScheam = z.object( {
 | 
				
			||||||
 | 
					  name: z.string().min( 1, {message: "Name is required"} ),
 | 
				
			||||||
 | 
					  description:z.string().min(1,{message:"Description is required"})
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
							
								
								
									
										26
									
								
								src/components/Directory/DirectoryUtils.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								src/components/Directory/DirectoryUtils.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,26 @@
 | 
				
			|||||||
 | 
					import {useBuckets} from "../../hooks/useDirectory";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const getEmailIcon = (type) => {
 | 
				
			||||||
 | 
					  switch (type) {
 | 
				
			||||||
 | 
					    case 'Work': return "bx bx-briefcase me-1 " ;
 | 
				
			||||||
 | 
					    case 'Personal': return "bx bx-user me-1";
 | 
				
			||||||
 | 
					    case 'support': return "bx headphone-mic me-1";
 | 
				
			||||||
 | 
					    case 'billing': return "bx bx-receipt me-1";
 | 
				
			||||||
 | 
					    default: return "bx bx-envelope me-1";
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const getPhoneIcon = (type) => {
 | 
				
			||||||
 | 
					  switch (type) {
 | 
				
			||||||
 | 
					    case 'Business': return "bx bx-phone me-1 ";
 | 
				
			||||||
 | 
					    case 'Personal': return "bx bx-mobile me-1 ";
 | 
				
			||||||
 | 
					    case 'Office': return "bx bx-phone me-1 ";
 | 
				
			||||||
 | 
					    default: return "bx bx-phone me-1";
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const getBucketNameById = (buckets, id) => {
 | 
				
			||||||
 | 
					  const bucket = buckets.find(b => b.id === id);
 | 
				
			||||||
 | 
					  return bucket ? bucket.name : 'Unknown';
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
							
								
								
									
										158
									
								
								src/components/Directory/EmployeeList.jsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										158
									
								
								src/components/Directory/EmployeeList.jsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,158 @@
 | 
				
			|||||||
 | 
					import React, { useState, useEffect } from "react";
 | 
				
			||||||
 | 
					import { useSortableData } from "../../hooks/useSortableData";
 | 
				
			||||||
 | 
					import Avatar from "../common/Avatar";
 | 
				
			||||||
 | 
					const EmployeeList = ({ employees, onChange, bucket }) => {
 | 
				
			||||||
 | 
					  const [employeefiltered, setEmployeeFilter] = useState([]);
 | 
				
			||||||
 | 
					  const [employeeStatusList, setEmployeeStatusList] = useState([]);
 | 
				
			||||||
 | 
					  const [searchTerm, setSearchTerm] = useState("");
 | 
				
			||||||
 | 
					  useEffect(() => {
 | 
				
			||||||
 | 
					    setEmployeeFilter(employees?.filter((emp) => emp.email != null) || []);
 | 
				
			||||||
 | 
					  }, [employees]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // Initialize checked employees based on assignedEmployee prop
 | 
				
			||||||
 | 
					  useEffect(() => {
 | 
				
			||||||
 | 
					    if (Array.isArray(bucket?.employeeIds)) {
 | 
				
			||||||
 | 
					      const initialStatus = bucket?.employeeIds?.map((id) => ({
 | 
				
			||||||
 | 
					        employeeId: id,
 | 
				
			||||||
 | 
					        isActive: true,
 | 
				
			||||||
 | 
					      }));
 | 
				
			||||||
 | 
					      setEmployeeStatusList(initialStatus);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }, [bucket]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // Send updated list to parent
 | 
				
			||||||
 | 
					  useEffect(() => {
 | 
				
			||||||
 | 
					    if (onChange) {
 | 
				
			||||||
 | 
					      onChange(employeeStatusList);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }, [employeeStatusList]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const handleCheckboxChange = (id) => {
 | 
				
			||||||
 | 
					    setEmployeeStatusList((prev) => {
 | 
				
			||||||
 | 
					      const exists = prev.find((emp) => emp.employeeId === id);
 | 
				
			||||||
 | 
					      if (exists) {
 | 
				
			||||||
 | 
					        return prev.map((emp) =>
 | 
				
			||||||
 | 
					          emp.employeeId === id ? { ...emp, isActive: !emp.isActive } : emp
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        return [...prev, { employeeId: id, isActive: true }];
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const isChecked = (id) => {
 | 
				
			||||||
 | 
					    const found = employeeStatusList.find((emp) => emp.employeeId === id);
 | 
				
			||||||
 | 
					    return found?.isActive || false;
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // Sorting
 | 
				
			||||||
 | 
					  const {
 | 
				
			||||||
 | 
					    items: sortedEmployees,
 | 
				
			||||||
 | 
					    requestSort,
 | 
				
			||||||
 | 
					    sortConfig,
 | 
				
			||||||
 | 
					  } = useSortableData(employeefiltered, {
 | 
				
			||||||
 | 
					    key: (e) => `${e?.firstName} ${e?.lastName}`,
 | 
				
			||||||
 | 
					    direction: "asc",
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const getSortIcon = () => {
 | 
				
			||||||
 | 
					    if (!sortConfig) return null;
 | 
				
			||||||
 | 
					    return sortConfig.direction === "asc" ? (
 | 
				
			||||||
 | 
					      <i className="bx bx-caret-up text-secondary"></i>
 | 
				
			||||||
 | 
					    ) : (
 | 
				
			||||||
 | 
					      <i className="bx bx-caret-down text-secondary"></i>
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const filteredEmployees = sortedEmployees?.filter((employee) => {
 | 
				
			||||||
 | 
					    const fullName =
 | 
				
			||||||
 | 
					      `${employee?.firstName} ${employee?.lastName}`?.toLowerCase();
 | 
				
			||||||
 | 
					    return fullName.includes(searchTerm.toLowerCase());
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <>
 | 
				
			||||||
 | 
					      <div className="d-flex justify-content-between align-items-center mt-2">
 | 
				
			||||||
 | 
					        <p className="m-0  fw-normal">Add Employee</p>
 | 
				
			||||||
 | 
					        <div className="px-1">
 | 
				
			||||||
 | 
					          <input
 | 
				
			||||||
 | 
					            type="search"
 | 
				
			||||||
 | 
					            className="form-control form-control-sm"
 | 
				
			||||||
 | 
					            placeholder="Search Employee..."
 | 
				
			||||||
 | 
					            value={searchTerm}
 | 
				
			||||||
 | 
					            onChange={(e) => setSearchTerm(e.target.value)}
 | 
				
			||||||
 | 
					          />
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      <div className="table-responsive px-1 my-1 px-sm-0">
 | 
				
			||||||
 | 
					        <table className="table align-middle mb-0">
 | 
				
			||||||
 | 
					          <thead className="table-light">
 | 
				
			||||||
 | 
					            <tr>
 | 
				
			||||||
 | 
					              <th
 | 
				
			||||||
 | 
					                onClick={() =>
 | 
				
			||||||
 | 
					                  requestSort((e) => `${e.firstName} ${e.lastName}`)
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                className="text-start cursor-pointer"
 | 
				
			||||||
 | 
					              >
 | 
				
			||||||
 | 
					                <span className="ps-2">Name {getSortIcon()}</span>
 | 
				
			||||||
 | 
					              </th>
 | 
				
			||||||
 | 
					              <th className="text-start">Role</th>
 | 
				
			||||||
 | 
					            </tr>
 | 
				
			||||||
 | 
					          </thead>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          <tbody>
 | 
				
			||||||
 | 
					            {employees.length === 0 ? (
 | 
				
			||||||
 | 
					              <tr>
 | 
				
			||||||
 | 
					                <td colSpan={4}>
 | 
				
			||||||
 | 
					                  <div className="d-flex justify-content-center align-items-center py-5">
 | 
				
			||||||
 | 
					                    No Employee Available
 | 
				
			||||||
 | 
					                  </div>
 | 
				
			||||||
 | 
					                </td>
 | 
				
			||||||
 | 
					              </tr>
 | 
				
			||||||
 | 
					            ) : filteredEmployees.length === 0 ? (
 | 
				
			||||||
 | 
					              <tr className="my-4">
 | 
				
			||||||
 | 
					                <td colSpan={4}>
 | 
				
			||||||
 | 
					                  <div className="d-flex justify-content-center align-items-center py-5">
 | 
				
			||||||
 | 
					                    No Matching Employee Found.
 | 
				
			||||||
 | 
					                  </div>
 | 
				
			||||||
 | 
					                </td>
 | 
				
			||||||
 | 
					              </tr>
 | 
				
			||||||
 | 
					            ) : (
 | 
				
			||||||
 | 
					              filteredEmployees?.map((employee) => (
 | 
				
			||||||
 | 
					                <tr key={employee.id}>
 | 
				
			||||||
 | 
					                  <td>
 | 
				
			||||||
 | 
					                    <div className="d-flex align-items-center text-start">
 | 
				
			||||||
 | 
					                      <input
 | 
				
			||||||
 | 
					                        className="form-check-input me-3 mt-1"
 | 
				
			||||||
 | 
					                        type="checkbox"
 | 
				
			||||||
 | 
					                        checked={isChecked(employee.id)}
 | 
				
			||||||
 | 
					                        onChange={() => handleCheckboxChange(employee?.id)}
 | 
				
			||||||
 | 
					                        disabled={bucket?.createdBy?.id === employee?.id}
 | 
				
			||||||
 | 
					                      />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                      <Avatar
 | 
				
			||||||
 | 
					                        size="xs"
 | 
				
			||||||
 | 
					                        classAvatar="m-0"
 | 
				
			||||||
 | 
					                        firstName={employee.firstName}
 | 
				
			||||||
 | 
					                        lastName={employee.lastName}
 | 
				
			||||||
 | 
					                      />
 | 
				
			||||||
 | 
					                      <span
 | 
				
			||||||
 | 
					                        className="text-truncate mx-0"
 | 
				
			||||||
 | 
					                        style={{ maxWidth: "150px" }}
 | 
				
			||||||
 | 
					                      >{`${employee.firstName} ${employee.lastName}`}</span>
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					                  </td>
 | 
				
			||||||
 | 
					                  <td className="text-start">
 | 
				
			||||||
 | 
					                    <small className="text-muted">{employee.jobRole}</small>
 | 
				
			||||||
 | 
					                  </td>
 | 
				
			||||||
 | 
					                </tr>
 | 
				
			||||||
 | 
					              ))
 | 
				
			||||||
 | 
					            )}
 | 
				
			||||||
 | 
					          </tbody>
 | 
				
			||||||
 | 
					        </table>
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					    </>
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default EmployeeList;
 | 
				
			||||||
							
								
								
									
										131
									
								
								src/components/Directory/ListViewDirectory.jsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										131
									
								
								src/components/Directory/ListViewDirectory.jsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,131 @@
 | 
				
			|||||||
 | 
					import React, { useEffect } from "react";
 | 
				
			||||||
 | 
					import Avatar from "../common/Avatar";
 | 
				
			||||||
 | 
					import { getEmailIcon, getPhoneIcon } from "./DirectoryUtils";
 | 
				
			||||||
 | 
					import { useDir } from "../../Context/DireContext";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const ListViewDirectory = ({
 | 
				
			||||||
 | 
					  IsActive,
 | 
				
			||||||
 | 
					  contact,
 | 
				
			||||||
 | 
					  setSelectedContact,
 | 
				
			||||||
 | 
					  setIsOpenModal,
 | 
				
			||||||
 | 
					  setOpen_contact,
 | 
				
			||||||
 | 
					  setIsOpenModalNote,
 | 
				
			||||||
 | 
					  IsDeleted,
 | 
				
			||||||
 | 
					  restore,
 | 
				
			||||||
 | 
					}) => {
 | 
				
			||||||
 | 
					  const { dirActions, setDirActions } = useDir();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <tr className={!IsActive ? "bg-light" : ""}>
 | 
				
			||||||
 | 
					      <td
 | 
				
			||||||
 | 
					        className="text-start cursor-pointer"
 | 
				
			||||||
 | 
					        style={{ width: "18%" }}
 | 
				
			||||||
 | 
					        colSpan={2}
 | 
				
			||||||
 | 
					        onClick={() => {
 | 
				
			||||||
 | 
					          if (IsActive) {
 | 
				
			||||||
 | 
					            setIsOpenModalNote(true);
 | 
				
			||||||
 | 
					            setOpen_contact(contact);
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }}
 | 
				
			||||||
 | 
					      >
 | 
				
			||||||
 | 
					        <div className="d-flex align-items-center">
 | 
				
			||||||
 | 
					          <Avatar
 | 
				
			||||||
 | 
					            size="xs"
 | 
				
			||||||
 | 
					            classAvatar="m-0"
 | 
				
			||||||
 | 
					            firstName={
 | 
				
			||||||
 | 
					              (contact?.name || "").trim().split(" ")[0]?.charAt(0) || ""
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            lastName={
 | 
				
			||||||
 | 
					              (contact?.name || "").trim().split(" ")[1]?.charAt(0) || ""
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					          />
 | 
				
			||||||
 | 
					          <span className="text-truncate mx-0" style={{ maxWidth: "150px" }}>
 | 
				
			||||||
 | 
					            {contact?.name || ""}
 | 
				
			||||||
 | 
					          </span>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					      </td>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      <td className="px-2" style={{ width: "20%" }}>
 | 
				
			||||||
 | 
					        <div className="d-flex flex-column align-items-start text-truncate">
 | 
				
			||||||
 | 
					         {contact.contactEmails.length > 0 ? (contact.contactEmails?.map((email, index) => (
 | 
				
			||||||
 | 
					            <span key={email.id} className="text-truncate">
 | 
				
			||||||
 | 
					              <i
 | 
				
			||||||
 | 
					                className={getEmailIcon(email.label)}
 | 
				
			||||||
 | 
					                style={{ fontSize: "12px" }}
 | 
				
			||||||
 | 
					              ></i>
 | 
				
			||||||
 | 
					              <a
 | 
				
			||||||
 | 
					                href={`mailto:${email.emailAddress}`}
 | 
				
			||||||
 | 
					                className="text-decoration-none ms-1"
 | 
				
			||||||
 | 
					              >
 | 
				
			||||||
 | 
					                {email.emailAddress}
 | 
				
			||||||
 | 
					              </a>
 | 
				
			||||||
 | 
					            </span>
 | 
				
			||||||
 | 
					          ))):(<span className="small-text m-0 px-2">NA</span>)}
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					      </td>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      <td className="px-2" style={{ width: "20%" }}>
 | 
				
			||||||
 | 
					        <div className="d-flex flex-column align-items-start text-truncate">
 | 
				
			||||||
 | 
					           {contact.contactPhones?.length > 0 ? (
 | 
				
			||||||
 | 
					            contact.contactPhones?.map((phone, index) => (
 | 
				
			||||||
 | 
					            <span key={phone.id}>
 | 
				
			||||||
 | 
					              <i
 | 
				
			||||||
 | 
					                className={getPhoneIcon(phone.label)}
 | 
				
			||||||
 | 
					                style={{ fontSize: "12px" }}
 | 
				
			||||||
 | 
					              ></i>
 | 
				
			||||||
 | 
					              <span className="ms-1">{phone.phoneNumber}</span>
 | 
				
			||||||
 | 
					            </span>
 | 
				
			||||||
 | 
					          ))
 | 
				
			||||||
 | 
					          ):(<span className="text-small m-0 px-2">NA</span>)}
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					      </td>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      <td
 | 
				
			||||||
 | 
					        colSpan={2}
 | 
				
			||||||
 | 
					        className="text-start text-truncate px-2"
 | 
				
			||||||
 | 
					        style={{ width: "20%", maxWidth: "200px" }}
 | 
				
			||||||
 | 
					      >
 | 
				
			||||||
 | 
					        {contact.organization}
 | 
				
			||||||
 | 
					      </td>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      <td className="px-2" style={{ width: "10%" }}>
 | 
				
			||||||
 | 
					        <span className="badge badge-outline-secondary">
 | 
				
			||||||
 | 
					          {contact?.contactCategory?.name || "Other"}
 | 
				
			||||||
 | 
					        </span>
 | 
				
			||||||
 | 
					      </td>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      <td className="align-middle text-center" style={{ width: "12%" }}>
 | 
				
			||||||
 | 
					        {IsActive && (
 | 
				
			||||||
 | 
					          <>
 | 
				
			||||||
 | 
					            <i
 | 
				
			||||||
 | 
					              className="bx bx-edit bx-sm text-primary cursor-pointer me-2"
 | 
				
			||||||
 | 
					              onClick={() => {
 | 
				
			||||||
 | 
					                setSelectedContact(contact);
 | 
				
			||||||
 | 
					                setIsOpenModal(true);
 | 
				
			||||||
 | 
					              }}
 | 
				
			||||||
 | 
					            ></i>
 | 
				
			||||||
 | 
					            <i
 | 
				
			||||||
 | 
					              className="bx bx-trash bx-sm text-danger cursor-pointer"
 | 
				
			||||||
 | 
					              onClick={() => IsDeleted(contact.id)}
 | 
				
			||||||
 | 
					            ></i>
 | 
				
			||||||
 | 
					          </>
 | 
				
			||||||
 | 
					        )}
 | 
				
			||||||
 | 
					        {!IsActive && (
 | 
				
			||||||
 | 
					          <i
 | 
				
			||||||
 | 
					            className={`bx  ${
 | 
				
			||||||
 | 
					              dirActions.action && dirActions.id === contact.id ?  "bx-loader-alt bx-spin"
 | 
				
			||||||
 | 
					                    : "bx-recycle"
 | 
				
			||||||
 | 
					            } me-1 text-primary cursor-pointer`}
 | 
				
			||||||
 | 
					            title="Restore"
 | 
				
			||||||
 | 
					            onClick={() => {
 | 
				
			||||||
 | 
					              setDirActions({ action: false, id: contact.id });
 | 
				
			||||||
 | 
					              restore(contact.id);
 | 
				
			||||||
 | 
					            }}
 | 
				
			||||||
 | 
					          ></i>
 | 
				
			||||||
 | 
					        )}
 | 
				
			||||||
 | 
					      </td>
 | 
				
			||||||
 | 
					    </tr>
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default ListViewDirectory;
 | 
				
			||||||
							
								
								
									
										429
									
								
								src/components/Directory/ManageBucket.jsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										429
									
								
								src/components/Directory/ManageBucket.jsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,429 @@
 | 
				
			|||||||
 | 
					import React, { useEffect, useState } from "react";
 | 
				
			||||||
 | 
					import IconButton from "../common/IconButton";
 | 
				
			||||||
 | 
					import { useForm } from "react-hook-form";
 | 
				
			||||||
 | 
					import { zodResolver } from "@hookform/resolvers/zod";
 | 
				
			||||||
 | 
					import { bucketScheam } from "./DirectorySchema";
 | 
				
			||||||
 | 
					import showToast from "../../services/toastService";
 | 
				
			||||||
 | 
					import Directory from "../../pages/Directory/Directory";
 | 
				
			||||||
 | 
					import { DirectoryRepository } from "../../repositories/DirectoryRepository";
 | 
				
			||||||
 | 
					import { cacheData, getCachedData } from "../../slices/apiDataManager";
 | 
				
			||||||
 | 
					import { useBuckets } from "../../hooks/useDirectory";
 | 
				
			||||||
 | 
					import EmployeeList from "./EmployeeList";
 | 
				
			||||||
 | 
					import { useAllEmployees, useEmployees } from "../../hooks/useEmployees";
 | 
				
			||||||
 | 
					import { useSortableData } from "../../hooks/useSortableData";
 | 
				
			||||||
 | 
					import ConfirmModal from "../common/ConfirmModal";
 | 
				
			||||||
 | 
					import { useHasUserPermission } from "../../hooks/useHasUserPermission";
 | 
				
			||||||
 | 
					import { DIRECTORY_ADMIN, DIRECTORY_MANAGER } from "../../utils/constants";
 | 
				
			||||||
 | 
					import { useProfile } from "../../hooks/useProfile";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const ManageBucket = () => {
 | 
				
			||||||
 | 
					  const { profile } = useProfile();
 | 
				
			||||||
 | 
					  const [bucketList, setBucketList] = useState([]);
 | 
				
			||||||
 | 
					  const { employeesList } = useAllEmployees(false);
 | 
				
			||||||
 | 
					  const [selectedEmployee, setSelectEmployee] = useState([]);
 | 
				
			||||||
 | 
					  const { buckets, loading, refetch } = useBuckets();
 | 
				
			||||||
 | 
					  const [action_bucket, setAction_bucket] = useState(false);
 | 
				
			||||||
 | 
					  const [isSubmitting, setSubmitting] = useState(false);
 | 
				
			||||||
 | 
					  const [selected_bucket, select_bucket] = useState(null);
 | 
				
			||||||
 | 
					  const [deleteBucket, setDeleteBucket] = useState(null);
 | 
				
			||||||
 | 
					  const [searchTerm, setSearchTerm] = useState("");
 | 
				
			||||||
 | 
					  const DirManager = useHasUserPermission(DIRECTORY_MANAGER);
 | 
				
			||||||
 | 
					  const DirAdmin = useHasUserPermission(DIRECTORY_ADMIN);
 | 
				
			||||||
 | 
					  const {
 | 
				
			||||||
 | 
					    items: sortedBuckteList,
 | 
				
			||||||
 | 
					    requestSort,
 | 
				
			||||||
 | 
					    sortConfig,
 | 
				
			||||||
 | 
					  } = useSortableData(bucketList, {
 | 
				
			||||||
 | 
					    key: (e) => `${e.name}`,
 | 
				
			||||||
 | 
					    direction: "asc",
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					  const getSortIcon = () => {
 | 
				
			||||||
 | 
					    if (!sortConfig) return null;
 | 
				
			||||||
 | 
					    return sortConfig.direction === "asc" ? (
 | 
				
			||||||
 | 
					      <i className="bx bx-caret-up text-secondary"></i>
 | 
				
			||||||
 | 
					    ) : (
 | 
				
			||||||
 | 
					      <i className="bx bx-caret-down text-secondary"></i>
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const {
 | 
				
			||||||
 | 
					    register,
 | 
				
			||||||
 | 
					    handleSubmit,
 | 
				
			||||||
 | 
					    reset,
 | 
				
			||||||
 | 
					    formState: { errors },
 | 
				
			||||||
 | 
					  } = useForm({
 | 
				
			||||||
 | 
					    resolver: zodResolver(bucketScheam),
 | 
				
			||||||
 | 
					    defaultValues: {
 | 
				
			||||||
 | 
					      name: "",
 | 
				
			||||||
 | 
					      description: "",
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const onSubmit = async (data) => {
 | 
				
			||||||
 | 
					    setSubmitting(true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      const cache_buckets = getCachedData("buckets") || [];
 | 
				
			||||||
 | 
					      let response;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      // Utility: Compare arrays regardless of order
 | 
				
			||||||
 | 
					      const arraysAreEqual = (a, b) => {
 | 
				
			||||||
 | 
					        if (a.length !== b.length) return false;
 | 
				
			||||||
 | 
					        const setA = new Set(a);
 | 
				
			||||||
 | 
					        const setB = new Set(b);
 | 
				
			||||||
 | 
					        return [...setA].every((id) => setB.has(id));
 | 
				
			||||||
 | 
					      };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      // UPDATE existing bucket
 | 
				
			||||||
 | 
					      if (selected_bucket) {
 | 
				
			||||||
 | 
					        const payload = { ...data, id: selected_bucket.id };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // 1. Update bucket details
 | 
				
			||||||
 | 
					        response = await DirectoryRepository.UpdateBuckets(
 | 
				
			||||||
 | 
					          selected_bucket.id,
 | 
				
			||||||
 | 
					          payload
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const updatedBuckets = cache_buckets.map((bucket) =>
 | 
				
			||||||
 | 
					          bucket.id === selected_bucket.id ? response?.data : bucket
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        cacheData("buckets", updatedBuckets);
 | 
				
			||||||
 | 
					        setBucketList(updatedBuckets);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // 2. Update employee assignments if they changed
 | 
				
			||||||
 | 
					        const existingEmployeeIds = selected_bucket?.employeeIds || [];
 | 
				
			||||||
 | 
					        const employeesToUpdate = selectedEmployee.filter((emp) => {
 | 
				
			||||||
 | 
					          const isExisting = existingEmployeeIds.includes(emp.employeeId);
 | 
				
			||||||
 | 
					          return (!isExisting && emp.isActive) || (isExisting && !emp.isActive);
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Create a filtered list of active employee IDs to compare
 | 
				
			||||||
 | 
					        const newActiveEmployeeIds = selectedEmployee
 | 
				
			||||||
 | 
					          .filter((emp) => {
 | 
				
			||||||
 | 
					            const isExisting = existingEmployeeIds.includes(emp.employeeId);
 | 
				
			||||||
 | 
					            return (
 | 
				
			||||||
 | 
					              (!isExisting && emp.isActive) || (isExisting && !emp.isActive)
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					          })
 | 
				
			||||||
 | 
					          .map((emp) => emp.employeeId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (
 | 
				
			||||||
 | 
					          !arraysAreEqual(newActiveEmployeeIds, existingEmployeeIds) &&
 | 
				
			||||||
 | 
					          employeesToUpdate.length != 0
 | 
				
			||||||
 | 
					        ) {
 | 
				
			||||||
 | 
					          try {
 | 
				
			||||||
 | 
					            response = await DirectoryRepository.AssignedBuckets(
 | 
				
			||||||
 | 
					              selected_bucket.id,
 | 
				
			||||||
 | 
					              employeesToUpdate
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					          } catch (assignError) {
 | 
				
			||||||
 | 
					            const assignMessage =
 | 
				
			||||||
 | 
					              assignError?.response?.data?.message ||
 | 
				
			||||||
 | 
					              assignError?.message ||
 | 
				
			||||||
 | 
					              "Error assigning employees.";
 | 
				
			||||||
 | 
					            showToast(assignMessage, "error");
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        const updatedData = cache_buckets?.map((bucket) =>
 | 
				
			||||||
 | 
					          bucket.id === response?.data?.id ? response.data : bucket
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        cacheData("buckets", updatedData);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        setBucketList(updatedData);
 | 
				
			||||||
 | 
					        showToast("Bucket Updated Successfully", "success");
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      // CREATE new bucket
 | 
				
			||||||
 | 
					      else {
 | 
				
			||||||
 | 
					        response = await DirectoryRepository.CreateBuckets(data);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const updatedBuckets = [...cache_buckets, response?.data];
 | 
				
			||||||
 | 
					        cacheData("buckets", updatedBuckets);
 | 
				
			||||||
 | 
					        setBucketList(updatedBuckets);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        showToast("Bucket Created Successfully", "success");
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      handleBack();
 | 
				
			||||||
 | 
					    } catch (error) {
 | 
				
			||||||
 | 
					      const message =
 | 
				
			||||||
 | 
					        error?.response?.data?.message ||
 | 
				
			||||||
 | 
					        error?.message ||
 | 
				
			||||||
 | 
					        "Error occurred during API call";
 | 
				
			||||||
 | 
					      showToast(message, "error");
 | 
				
			||||||
 | 
					    } finally {
 | 
				
			||||||
 | 
					      setSubmitting(false);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const handleDeleteContact = async () => {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      const resp = await DirectoryRepository.DeleteBucket(deleteBucket);
 | 
				
			||||||
 | 
					      const cache_buckets = getCachedData("buckets") || [];
 | 
				
			||||||
 | 
					      const updatedBuckets = cache_buckets.filter(
 | 
				
			||||||
 | 
					        (bucket) => bucket.id != deleteBucket
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					      cacheData("buckets", updatedBuckets);
 | 
				
			||||||
 | 
					      setBucketList(updatedBuckets);
 | 
				
			||||||
 | 
					      showToast("Bucket deleted successfully", "success");
 | 
				
			||||||
 | 
					      setDeleteBucket(null);
 | 
				
			||||||
 | 
					    } catch (error) {
 | 
				
			||||||
 | 
					      const message =
 | 
				
			||||||
 | 
					        error?.response?.data?.message ||
 | 
				
			||||||
 | 
					        error?.message ||
 | 
				
			||||||
 | 
					        "Error occurred during API call.";
 | 
				
			||||||
 | 
					      showToast(message, "error");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  useEffect(() => {
 | 
				
			||||||
 | 
					    reset({
 | 
				
			||||||
 | 
					      name: selected_bucket?.name || "",
 | 
				
			||||||
 | 
					      description: selected_bucket?.description || "",
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  }, [selected_bucket]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  useEffect(() => {
 | 
				
			||||||
 | 
					    setBucketList(buckets);
 | 
				
			||||||
 | 
					  }, [buckets]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const handleBack = () => {
 | 
				
			||||||
 | 
					    select_bucket(null);
 | 
				
			||||||
 | 
					    setAction_bucket(false);
 | 
				
			||||||
 | 
					    setSubmitting(false);
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const sortedBucktesList = sortedBuckteList?.filter((bucket) => {
 | 
				
			||||||
 | 
					    const term = searchTerm?.toLowerCase();
 | 
				
			||||||
 | 
					    const name = bucket.name?.toLowerCase();
 | 
				
			||||||
 | 
					    return name?.includes(term);
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <>
 | 
				
			||||||
 | 
					      {deleteBucket && (
 | 
				
			||||||
 | 
					        <div
 | 
				
			||||||
 | 
					          className={`modal fade  ${deleteBucket ? "show" : ""}`}
 | 
				
			||||||
 | 
					          tabIndex="-1"
 | 
				
			||||||
 | 
					          role="dialog"
 | 
				
			||||||
 | 
					          style={{
 | 
				
			||||||
 | 
					            display: deleteBucket ? "block" : "none",
 | 
				
			||||||
 | 
					            backgroundColor: deleteBucket ? "rgba(0,0,0,0.5)" : "transparent",
 | 
				
			||||||
 | 
					          }}
 | 
				
			||||||
 | 
					        >
 | 
				
			||||||
 | 
					          <ConfirmModal
 | 
				
			||||||
 | 
					            type={"delete"}
 | 
				
			||||||
 | 
					            header={"Delete Bucket"}
 | 
				
			||||||
 | 
					            message={"Are you sure you want delete?"}
 | 
				
			||||||
 | 
					            onSubmit={handleDeleteContact}
 | 
				
			||||||
 | 
					            onClose={() => setDeleteBucket(null)}
 | 
				
			||||||
 | 
					            // loading={IsDeleting}
 | 
				
			||||||
 | 
					          />
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					      )}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      <div className="container m-0 p-0" style={{ minHeight: "200px" }}>
 | 
				
			||||||
 | 
					        <div className="d-flex justify-content-center">
 | 
				
			||||||
 | 
					          <p className="fs-6 fw-semibold m-0">Manage Buckets</p>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					        <div className="d-flex justify-content-between px-2 px-sm-0 mt-5 mt-3 align-items-center ">
 | 
				
			||||||
 | 
					          {action_bucket ? (
 | 
				
			||||||
 | 
					            <i
 | 
				
			||||||
 | 
					              className={`fa-solid fa-arrow-left fs-5 cursor-pointer`}
 | 
				
			||||||
 | 
					              onClick={handleBack}
 | 
				
			||||||
 | 
					            ></i>
 | 
				
			||||||
 | 
					          ) : (
 | 
				
			||||||
 | 
					            <div className="d-flex align-items-center gap-2">
 | 
				
			||||||
 | 
					              <input
 | 
				
			||||||
 | 
					                type="search"
 | 
				
			||||||
 | 
					                className="form-control form-control-sm"
 | 
				
			||||||
 | 
					                placeholder="Search Bucket ..."
 | 
				
			||||||
 | 
					                value={searchTerm}
 | 
				
			||||||
 | 
					                onChange={(e) => setSearchTerm(e.target.value)}
 | 
				
			||||||
 | 
					              />
 | 
				
			||||||
 | 
					              <i
 | 
				
			||||||
 | 
					                className={`bx bx-refresh cursor-pointer fs-4 ${
 | 
				
			||||||
 | 
					                  loading ? "spin" : ""
 | 
				
			||||||
 | 
					                }`}
 | 
				
			||||||
 | 
					                title="Refresh"
 | 
				
			||||||
 | 
					                onClick={() => refetch()}
 | 
				
			||||||
 | 
					              />
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					          )}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          <button
 | 
				
			||||||
 | 
					            type="button"
 | 
				
			||||||
 | 
					            className={`btn  btn-sm btn-primary ms-auto ${
 | 
				
			||||||
 | 
					              action_bucket ? "d-none" : ""
 | 
				
			||||||
 | 
					            }`}
 | 
				
			||||||
 | 
					            onClick={() => setAction_bucket(true)}
 | 
				
			||||||
 | 
					          >
 | 
				
			||||||
 | 
					            <i className="bx bx-plus-circle me-2"></i>
 | 
				
			||||||
 | 
					            Add Bucket
 | 
				
			||||||
 | 
					          </button>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					        <div>
 | 
				
			||||||
 | 
					          {!action_bucket ? (
 | 
				
			||||||
 | 
					            <div className="table-responsive text-nowrap pt-1 px-2 px-sm-0 mt-3">
 | 
				
			||||||
 | 
					              <table className="table  px-2">
 | 
				
			||||||
 | 
					                <thead className="p-0">
 | 
				
			||||||
 | 
					                  <tr className="p-0">
 | 
				
			||||||
 | 
					                    <th
 | 
				
			||||||
 | 
					                      colSpan={2}
 | 
				
			||||||
 | 
					                      className="cursor-pointer"
 | 
				
			||||||
 | 
					                      onClick={() => requestSort((e) => `${e.name} `)}
 | 
				
			||||||
 | 
					                    >
 | 
				
			||||||
 | 
					                      <div className="d-flex justify-content-start align-items-center gap-1 mx-2">
 | 
				
			||||||
 | 
					                        <span>Name {getSortIcon()}</span>
 | 
				
			||||||
 | 
					                      </div>
 | 
				
			||||||
 | 
					                    </th>
 | 
				
			||||||
 | 
					                    <th className="text-start d-none d-sm-table-cell">
 | 
				
			||||||
 | 
					                      <div className="d-flex align-items-center justify-content-center gap-1">
 | 
				
			||||||
 | 
					                        <span>Description</span>
 | 
				
			||||||
 | 
					                      </div>
 | 
				
			||||||
 | 
					                    </th>
 | 
				
			||||||
 | 
					                    <th>Contacts</th>
 | 
				
			||||||
 | 
					                    <th>
 | 
				
			||||||
 | 
					                      <div className="d-flex align-items-center justify-content-center gap-1">
 | 
				
			||||||
 | 
					                        <span>Action</span>
 | 
				
			||||||
 | 
					                      </div>
 | 
				
			||||||
 | 
					                    </th>
 | 
				
			||||||
 | 
					                  </tr>
 | 
				
			||||||
 | 
					                </thead>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                <tbody className="table-border-bottom-0 overflow-auto">
 | 
				
			||||||
 | 
					                  {loading && (
 | 
				
			||||||
 | 
					                    <tr className="mt-10">
 | 
				
			||||||
 | 
					                      <td colSpan={5}>
 | 
				
			||||||
 | 
					                        {" "}
 | 
				
			||||||
 | 
					                        <div className="d-flex justify-content-center align-items-center py-5">
 | 
				
			||||||
 | 
					                          Loading...
 | 
				
			||||||
 | 
					                        </div>
 | 
				
			||||||
 | 
					                      </td>
 | 
				
			||||||
 | 
					                    </tr>
 | 
				
			||||||
 | 
					                  )}
 | 
				
			||||||
 | 
					                  {!loading && buckets.length == 0 && (
 | 
				
			||||||
 | 
					                    <tr>
 | 
				
			||||||
 | 
					                      <td colSpan={5}>
 | 
				
			||||||
 | 
					                        <div className="d-flex justify-content-center align-items-center py-5">
 | 
				
			||||||
 | 
					                          Bucket Not Available.
 | 
				
			||||||
 | 
					                        </div>
 | 
				
			||||||
 | 
					                      </td>
 | 
				
			||||||
 | 
					                    </tr>
 | 
				
			||||||
 | 
					                  )}
 | 
				
			||||||
 | 
					                  {!loading && sortedBucktesList.length == 0 && (
 | 
				
			||||||
 | 
					                    <tr>
 | 
				
			||||||
 | 
					                      <td className="text-center py-4 h-25" colSpan={5}>
 | 
				
			||||||
 | 
					                        <div className="d-flex justify-content-center align-items-center py-5">
 | 
				
			||||||
 | 
					                          No Matching Bucket Found.
 | 
				
			||||||
 | 
					                        </div>
 | 
				
			||||||
 | 
					                      </td>
 | 
				
			||||||
 | 
					                    </tr>
 | 
				
			||||||
 | 
					                  )}
 | 
				
			||||||
 | 
					                  {!loading &&
 | 
				
			||||||
 | 
					                    sortedBucktesList.map((bucket) => (
 | 
				
			||||||
 | 
					                      <tr key={bucket.id}>
 | 
				
			||||||
 | 
					                        <td colSpan={2} className="text-start text-wrap">
 | 
				
			||||||
 | 
					                          <i className="bx bx-right-arrow-alt me-1"></i>{" "}
 | 
				
			||||||
 | 
					                          {bucket.name}
 | 
				
			||||||
 | 
					                        </td>
 | 
				
			||||||
 | 
					                        <td
 | 
				
			||||||
 | 
					                          className="text-start d-none d-sm-table-cell text-truncate"
 | 
				
			||||||
 | 
					                          style={{
 | 
				
			||||||
 | 
					                            maxWidth: "300px",
 | 
				
			||||||
 | 
					                            whiteSpace: "wrap",
 | 
				
			||||||
 | 
					                            overflow: "hidden",
 | 
				
			||||||
 | 
					                            textOverflow: "ellipsis",
 | 
				
			||||||
 | 
					                          }}
 | 
				
			||||||
 | 
					                          title={bucket.description}
 | 
				
			||||||
 | 
					                        >
 | 
				
			||||||
 | 
					                          {bucket.description}
 | 
				
			||||||
 | 
					                        </td>
 | 
				
			||||||
 | 
					                        <td>{bucket.numberOfContacts}</td>
 | 
				
			||||||
 | 
					                        <td className="justify-content-center">
 | 
				
			||||||
 | 
					                          {(DirManager ||
 | 
				
			||||||
 | 
					                            DirAdmin ||
 | 
				
			||||||
 | 
					                            bucket?.createdBy?.id ===
 | 
				
			||||||
 | 
					                              profile?.employeeInfo?.id) && (
 | 
				
			||||||
 | 
					                            <div className="d-flex justify-content-center align-items-center gap-2">
 | 
				
			||||||
 | 
					                              <i
 | 
				
			||||||
 | 
					                                className="bx bx-edit bx-sm text-primary cursor-pointer "
 | 
				
			||||||
 | 
					                                onClick={() => {
 | 
				
			||||||
 | 
					                                  select_bucket(bucket);
 | 
				
			||||||
 | 
					                                  setAction_bucket(true);
 | 
				
			||||||
 | 
					                                }}
 | 
				
			||||||
 | 
					                              ></i>
 | 
				
			||||||
 | 
					                              <i
 | 
				
			||||||
 | 
					                                className="bx bx-trash bx-sm text-danger cursor-pointer"
 | 
				
			||||||
 | 
					                                onClick={() => setDeleteBucket(bucket?.id)}
 | 
				
			||||||
 | 
					                              ></i>
 | 
				
			||||||
 | 
					                            </div>
 | 
				
			||||||
 | 
					                          )}
 | 
				
			||||||
 | 
					                        </td>
 | 
				
			||||||
 | 
					                      </tr>
 | 
				
			||||||
 | 
					                    ))}
 | 
				
			||||||
 | 
					                </tbody>
 | 
				
			||||||
 | 
					              </table>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					          ) : (
 | 
				
			||||||
 | 
					            <>
 | 
				
			||||||
 | 
					              <form onSubmit={handleSubmit(onSubmit)} className="px-2 px-sm-0">
 | 
				
			||||||
 | 
					                <div className="">
 | 
				
			||||||
 | 
					                  <label className="form-label">Bucket Name</label>
 | 
				
			||||||
 | 
					                  <input
 | 
				
			||||||
 | 
					                    className="form-control form-control-sm"
 | 
				
			||||||
 | 
					                    {...register("name")}
 | 
				
			||||||
 | 
					                  />
 | 
				
			||||||
 | 
					                  {errors.name && (
 | 
				
			||||||
 | 
					                    <small className="danger-text">{errors.name.message}</small>
 | 
				
			||||||
 | 
					                  )}
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					                <div className="">
 | 
				
			||||||
 | 
					                  <label className="form-label">Bucket Discription</label>
 | 
				
			||||||
 | 
					                  <textarea
 | 
				
			||||||
 | 
					                    className="form-control form-control-sm"
 | 
				
			||||||
 | 
					                    rows="3"
 | 
				
			||||||
 | 
					                    {...register("description")}
 | 
				
			||||||
 | 
					                  />
 | 
				
			||||||
 | 
					                  {errors.description && (
 | 
				
			||||||
 | 
					                    <small className="danger-text">
 | 
				
			||||||
 | 
					                      {errors.description.message}
 | 
				
			||||||
 | 
					                    </small>
 | 
				
			||||||
 | 
					                  )}
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                {selected_bucket && (
 | 
				
			||||||
 | 
					                  <EmployeeList
 | 
				
			||||||
 | 
					                    employees={employeesList}
 | 
				
			||||||
 | 
					                    onChange={(data) => setSelectEmployee(data)}
 | 
				
			||||||
 | 
					                    bucket={selected_bucket}
 | 
				
			||||||
 | 
					                  />
 | 
				
			||||||
 | 
					                )}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                <div className="mt-2 d-flex justify-content-center gap-3">
 | 
				
			||||||
 | 
					                  <button
 | 
				
			||||||
 | 
					                    onClick={() => handleBack()}
 | 
				
			||||||
 | 
					                    className="btn btn-sm btn-secondary"
 | 
				
			||||||
 | 
					                    disabled={isSubmitting}
 | 
				
			||||||
 | 
					                  >
 | 
				
			||||||
 | 
					                    Cancel
 | 
				
			||||||
 | 
					                  </button>
 | 
				
			||||||
 | 
					                  <button
 | 
				
			||||||
 | 
					                    type="submit"
 | 
				
			||||||
 | 
					                    className="btn btn-sm btn-primary"
 | 
				
			||||||
 | 
					                    disabled={isSubmitting}
 | 
				
			||||||
 | 
					                  >
 | 
				
			||||||
 | 
					                    {isSubmitting ? "Please wait..." : "Submit"}
 | 
				
			||||||
 | 
					                  </button>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					              </form>
 | 
				
			||||||
 | 
					            </>
 | 
				
			||||||
 | 
					          )}
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					    </>
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default ManageBucket;
 | 
				
			||||||
@ -1,40 +1,55 @@
 | 
				
			|||||||
import React, { useEffect } from "react";
 | 
					import React, { useEffect, useState } from "react";
 | 
				
			||||||
import { useForm, useFieldArray, FormProvider } from "react-hook-form";
 | 
					import {
 | 
				
			||||||
 | 
					  useForm,
 | 
				
			||||||
 | 
					  useFieldArray,
 | 
				
			||||||
 | 
					  FormProvider,
 | 
				
			||||||
 | 
					  useFormContext,
 | 
				
			||||||
 | 
					} from "react-hook-form";
 | 
				
			||||||
import { zodResolver } from "@hookform/resolvers/zod";
 | 
					import { zodResolver } from "@hookform/resolvers/zod";
 | 
				
			||||||
import TagInput from "../common/TagInput";
 | 
					import TagInput from "../common/TagInput";
 | 
				
			||||||
import { z } from "zod";
 | 
					import IconButton from "../common/IconButton";
 | 
				
			||||||
 | 
					import useMaster, {
 | 
				
			||||||
 | 
					  useContactCategory,
 | 
				
			||||||
 | 
					  useContactTags,
 | 
				
			||||||
 | 
					} from "../../hooks/masterHook/useMaster";
 | 
				
			||||||
 | 
					import { useDispatch, useSelector } from "react-redux";
 | 
				
			||||||
 | 
					import { changeMaster } from "../../slices/localVariablesSlice";
 | 
				
			||||||
 | 
					import { useBuckets, useOrganization } from "../../hooks/useDirectory";
 | 
				
			||||||
 | 
					import { useProjects } from "../../hooks/useProjects";
 | 
				
			||||||
 | 
					import SelectMultiple from "../common/SelectMultiple";
 | 
				
			||||||
 | 
					import { ContactSchema } from "./DirectorySchema";
 | 
				
			||||||
 | 
					import InputSuggestions from "../common/InputSuggestion";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const directorySchema = z.object({
 | 
					const ManageDirectory = ({ submitContact, onCLosed }) => {
 | 
				
			||||||
  firstName: z.string().min(1, "First Name is required"),
 | 
					  const selectedMaster = useSelector(
 | 
				
			||||||
  lastName: z.string().min(1, "Last Name is required"),
 | 
					    (store) => store.localVariables.selectedMaster
 | 
				
			||||||
  organization: z.string().min(1, "Organization name is required"),
 | 
					  );
 | 
				
			||||||
  type: z.string().min(1, "Type is required"),
 | 
					  const [categoryData, setCategoryData] = useState([]);
 | 
				
			||||||
  address: z.string().optional(),
 | 
					 | 
				
			||||||
  description: z.string().min(1, { message: "Description is required" }),
 | 
					 | 
				
			||||||
  email: z
 | 
					 | 
				
			||||||
    .array(z.string().email("Invalid email"))
 | 
					 | 
				
			||||||
    .nonempty("At least one email required"),
 | 
					 | 
				
			||||||
  phone: z
 | 
					 | 
				
			||||||
    .array(z.string().regex(/^\d{10}$/, "Phone must be 10 digits"))
 | 
					 | 
				
			||||||
    .nonempty("At least one phone number is required"),
 | 
					 | 
				
			||||||
  tags: z.array(z.string()).optional(),
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const [TagsData, setTagsData] = useState([]);
 | 
				
			||||||
 | 
					  const { data, loading } = useMaster();
 | 
				
			||||||
 | 
					  const { buckets, loading: bucketsLoaging } = useBuckets();
 | 
				
			||||||
 | 
					  const { projects, loading: projectLoading } = useProjects();
 | 
				
			||||||
 | 
					  const { contactCategory, loading: contactCategoryLoading } =
 | 
				
			||||||
 | 
					    useContactCategory();
 | 
				
			||||||
 | 
					  const { organizationList, loading: orgLoading } = useOrganization();
 | 
				
			||||||
 | 
					  const { contactTags, loading: Tagloading } = useContactTags();
 | 
				
			||||||
 | 
					  const [IsSubmitting, setSubmitting] = useState(false);
 | 
				
			||||||
 | 
					  const dispatch = useDispatch();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
const ManageDirectory = () => {
 | 
					 | 
				
			||||||
  const methods = useForm({
 | 
					  const methods = useForm({
 | 
				
			||||||
    resolver: zodResolver(directorySchema),
 | 
					    resolver: zodResolver(ContactSchema),
 | 
				
			||||||
    defaultValues: {
 | 
					    defaultValues: {
 | 
				
			||||||
      firstName: "",
 | 
					      name: "",
 | 
				
			||||||
      lastName: "",
 | 
					 | 
				
			||||||
      organization: "",
 | 
					      organization: "",
 | 
				
			||||||
      type: "",
 | 
					      contactCategoryId: null,
 | 
				
			||||||
      address: "",
 | 
					      address: "",
 | 
				
			||||||
      description: "",
 | 
					      description: "",
 | 
				
			||||||
      email: [""],
 | 
					      projectIds: [],
 | 
				
			||||||
      phone: [""],
 | 
					      contactEmails: [],
 | 
				
			||||||
 | 
					      contactPhones: [],
 | 
				
			||||||
      tags: [],
 | 
					      tags: [],
 | 
				
			||||||
 | 
					      bucketIds: [],
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -44,6 +59,9 @@ const ManageDirectory = () => {
 | 
				
			|||||||
    control,
 | 
					    control,
 | 
				
			||||||
    getValues,
 | 
					    getValues,
 | 
				
			||||||
    trigger,
 | 
					    trigger,
 | 
				
			||||||
 | 
					    setValue,
 | 
				
			||||||
 | 
					    watch,
 | 
				
			||||||
 | 
					    reset,
 | 
				
			||||||
    formState: { errors },
 | 
					    formState: { errors },
 | 
				
			||||||
  } = methods;
 | 
					  } = methods;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -51,160 +69,359 @@ const ManageDirectory = () => {
 | 
				
			|||||||
    fields: emailFields,
 | 
					    fields: emailFields,
 | 
				
			||||||
    append: appendEmail,
 | 
					    append: appendEmail,
 | 
				
			||||||
    remove: removeEmail,
 | 
					    remove: removeEmail,
 | 
				
			||||||
  } = useFieldArray({ control, name: "email" });
 | 
					  } = useFieldArray({ control, name: "contactEmails" });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const {
 | 
					  const {
 | 
				
			||||||
    fields: phoneFields,
 | 
					    fields: phoneFields,
 | 
				
			||||||
    append: appendPhone,
 | 
					    append: appendPhone,
 | 
				
			||||||
    remove: removePhone,
 | 
					    remove: removePhone,
 | 
				
			||||||
  } = useFieldArray({ control, name: "phone" });
 | 
					  } = useFieldArray({ control, name: "contactPhones" });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  useEffect(() => {
 | 
					  useEffect(() => {
 | 
				
			||||||
    if (emailFields.length === 0) appendEmail(""); 
 | 
					    if (emailFields.length === 0) {
 | 
				
			||||||
    if (phoneFields.length === 0) appendPhone(""); 
 | 
					      appendEmail({ label: "Work", emailAddress: "" });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (phoneFields.length === 0) {
 | 
				
			||||||
 | 
					      appendPhone({ label: "Office", phoneNumber: "" });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
  }, [emailFields.length, phoneFields.length]);
 | 
					  }, [emailFields.length, phoneFields.length]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const onSubmit = (data) => {
 | 
					 | 
				
			||||||
    // console.log("Submitted:\n" + JSON.stringify(data, null, 2));
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const handleAddEmail = async () => {
 | 
					  const handleAddEmail = async () => {
 | 
				
			||||||
    const emails = getValues("email");
 | 
					    const emails = getValues("contactEmails");
 | 
				
			||||||
    const lastIndex = emails.length - 1;
 | 
					    const lastIndex = emails.length - 1;
 | 
				
			||||||
    const valid = await trigger(`email.${lastIndex}`);
 | 
					    const valid = await trigger(`contactEmails.${lastIndex}.emailAddress`);
 | 
				
			||||||
    if (valid) appendEmail(""); 
 | 
					    if (valid) {
 | 
				
			||||||
 | 
					      appendEmail({ label: "Work", emailAddress: "" });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const handleAddPhone = async () => {
 | 
					  const handleAddPhone = async () => {
 | 
				
			||||||
    const phones = getValues("phone");
 | 
					    const phones = getValues("contactPhones");
 | 
				
			||||||
    const lastIndex = phones.length - 1;
 | 
					    const lastIndex = phones.length - 1;
 | 
				
			||||||
    const valid = await trigger(`phone.${lastIndex}`);
 | 
					    const valid = await trigger(`contactPhones.${lastIndex}.phoneNumber`);
 | 
				
			||||||
    if (valid) appendPhone(""); 
 | 
					    if (valid) {
 | 
				
			||||||
 | 
					      appendPhone({ label: "Office", phoneNumber: "" });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const watchBucketIds = watch("bucketIds");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const toggleBucketId = (id) => {
 | 
				
			||||||
 | 
					    const updated = watchBucketIds?.includes(id)
 | 
				
			||||||
 | 
					      ? watchBucketIds.filter((val) => val !== id)
 | 
				
			||||||
 | 
					      : [...watchBucketIds, id];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    setValue("bucketIds", updated, { shouldValidate: true });
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					  const handleCheckboxChange = (id) => {
 | 
				
			||||||
 | 
					    const updated = watchBucketIds.includes(id)
 | 
				
			||||||
 | 
					      ? watchBucketIds.filter((i) => i !== id)
 | 
				
			||||||
 | 
					      : [...watchBucketIds, id];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    setValue("bucketIds", updated, { shouldValidate: true });
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const onSubmit = (data) => {
 | 
				
			||||||
 | 
					    const cleaned = {
 | 
				
			||||||
 | 
					      ...data,
 | 
				
			||||||
 | 
					      contactEmails: (data.contactEmails || []).filter(
 | 
				
			||||||
 | 
					        (e) => e.emailAddress?.trim() !== ""
 | 
				
			||||||
 | 
					      ),
 | 
				
			||||||
 | 
					      contactPhones: (data.contactPhones || []).filter(
 | 
				
			||||||
 | 
					        (p) => p.phoneNumber?.trim() !== ""
 | 
				
			||||||
 | 
					      ),
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    setSubmitting(true);
 | 
				
			||||||
 | 
					    submitContact(cleaned, reset, setSubmitting);
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					  const orgValue = watch("organization");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const handleClosed = () => {
 | 
				
			||||||
 | 
					    onCLosed();
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <FormProvider {...methods}>
 | 
					    <FormProvider {...methods}>
 | 
				
			||||||
      <form className="p-2 p-sm-0" onSubmit={handleSubmit(onSubmit)}>
 | 
					      <form className="p-2 p-sm-0" onSubmit={handleSubmit(onSubmit)}>
 | 
				
			||||||
 | 
					        <div className="d-flex justify-content-center align-items-center">
 | 
				
			||||||
 | 
					          <h6 className="m-0 fw-18"> Create New Contact</h6>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
        <div className="row">
 | 
					        <div className="row">
 | 
				
			||||||
          <div className="col-md-6">
 | 
					          <div className="col-md-6  text-start">
 | 
				
			||||||
            <label className="form-label">First Name</label>
 | 
					            <label className="form-label">Name</label>
 | 
				
			||||||
            <input className="form-control form-control-sm" {...register("firstName")} />
 | 
					            <input
 | 
				
			||||||
            {errors.firstName && <small className="danger-text">{errors.firstName.message}</small>}
 | 
					              className="form-control form-control-sm"
 | 
				
			||||||
 | 
					              {...register("name")}
 | 
				
			||||||
 | 
					            />
 | 
				
			||||||
 | 
					            {errors.name && (
 | 
				
			||||||
 | 
					              <small className="danger-text">{errors.name.message}</small>
 | 
				
			||||||
 | 
					            )}
 | 
				
			||||||
          </div>
 | 
					          </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          <div className="col-md-6">
 | 
					          <div className="col-md-6  text-start">
 | 
				
			||||||
            <label className="form-label">Last Name</label>
 | 
					            <label className="form-label">Organization</label>
 | 
				
			||||||
            <input className="form-control form-control-sm" {...register("lastName")} />
 | 
					            <InputSuggestions
 | 
				
			||||||
            {errors.lastName && <small className="danger-text">{errors.lastName.message}</small>}
 | 
					              organizationList={organizationList}
 | 
				
			||||||
 | 
					              value={getValues("organization") || ""}
 | 
				
			||||||
 | 
					              onChange={(val) => setValue("organization", val)}
 | 
				
			||||||
 | 
					              error={errors.organization?.message}
 | 
				
			||||||
 | 
					            />
 | 
				
			||||||
          </div>
 | 
					          </div>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
 | 
					        <div className="row mt-1">
 | 
				
			||||||
        <div className="col-12">
 | 
					 | 
				
			||||||
          <label className="form-label">Organization</label>
 | 
					 | 
				
			||||||
          <input className="form-control form-control-sm" {...register("organization")} />
 | 
					 | 
				
			||||||
          {errors.organization && <small className="danger-text">{errors.organization.message}</small>}
 | 
					 | 
				
			||||||
        </div>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        <div className="row">
 | 
					 | 
				
			||||||
          <div className="col-md-6">
 | 
					          <div className="col-md-6">
 | 
				
			||||||
            <label className="form-label">Email</label>
 | 
					            {emailFields.map((field, index) => (
 | 
				
			||||||
            {emailFields.map((field, index) => (<>
 | 
					              <div
 | 
				
			||||||
              <div key={field.id} className="d-flex align-items-center mb-1">
 | 
					                key={field.id}
 | 
				
			||||||
                <input
 | 
					                className="row d-flex align-items-center mb-1"
 | 
				
			||||||
                  type="email"
 | 
					              >
 | 
				
			||||||
                  className="form-control form-control-sm"
 | 
					                <div className="col-5  text-start">
 | 
				
			||||||
                  {...register(`email.${index}`)}
 | 
					                  <label className="form-label">Label</label>
 | 
				
			||||||
                  placeholder="email@example.com"
 | 
					                  <select
 | 
				
			||||||
                />
 | 
					                    className="form-select form-select-sm"
 | 
				
			||||||
                {index === emailFields.length - 1 ? (
 | 
					                    {...register(`contactEmails.${index}.label`)}
 | 
				
			||||||
                  <button
 | 
					 | 
				
			||||||
                    type="button"
 | 
					 | 
				
			||||||
                    className="btn btn-xs btn-primary ms-1"
 | 
					 | 
				
			||||||
                    onClick={handleAddEmail}
 | 
					 | 
				
			||||||
                  >
 | 
					                  >
 | 
				
			||||||
                    <i className="bx bx-plus bx-xs" />
 | 
					                    <option value="Work">Work</option>
 | 
				
			||||||
                  </button>
 | 
					                    <option value="Personal">Personal</option>
 | 
				
			||||||
                ) : (
 | 
					                    <option value="Other">Other</option>
 | 
				
			||||||
                  <button
 | 
					                  </select>
 | 
				
			||||||
                    type="button"
 | 
					                  {errors.contactEmails?.[index]?.label && (
 | 
				
			||||||
                    className="btn btn-xs btn-danger ms-1"
 | 
					                    <small className="danger-text">
 | 
				
			||||||
                    onClick={() => removeEmail(index)} 
 | 
					                      {errors.contactEmails[index].label.message}
 | 
				
			||||||
                  >
 | 
					                    </small>
 | 
				
			||||||
                    <i className="bx bx-x bx-xs" />
 | 
					                  )}
 | 
				
			||||||
                  </button>
 | 
					                </div>
 | 
				
			||||||
                )}
 | 
					                <div className="col-7  text-start">
 | 
				
			||||||
 | 
					                  <label className="form-label">Email</label>
 | 
				
			||||||
 | 
					                  <div className="d-flex align-items-center">
 | 
				
			||||||
 | 
					                    <input
 | 
				
			||||||
 | 
					                      type="email"
 | 
				
			||||||
 | 
					                      className="form-control form-control-sm"
 | 
				
			||||||
 | 
					                      {...register(`contactEmails.${index}.emailAddress`)}
 | 
				
			||||||
 | 
					                      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 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 ms-1 cursor-pointer text-primary"
 | 
				
			||||||
 | 
					                        onClick={() => removeEmail(index)}
 | 
				
			||||||
 | 
					                      />
 | 
				
			||||||
 | 
					                    )}
 | 
				
			||||||
 | 
					                  </div>
 | 
				
			||||||
 | 
					                  {errors.contactEmails?.[index]?.emailAddress && (
 | 
				
			||||||
 | 
					                    <small className="danger-text">
 | 
				
			||||||
 | 
					                      {errors.contactEmails[index].emailAddress.message}
 | 
				
			||||||
 | 
					                    </small>
 | 
				
			||||||
 | 
					                  )}
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
              </div>
 | 
					              </div>
 | 
				
			||||||
              {errors.email?.[index] && (
 | 
					 | 
				
			||||||
        <small className="danger-text ms-2">
 | 
					 | 
				
			||||||
          {errors.email[index]?.message}
 | 
					 | 
				
			||||||
        </small>
 | 
					 | 
				
			||||||
      )}
 | 
					 | 
				
			||||||
              </>
 | 
					 | 
				
			||||||
            ))}
 | 
					            ))}
 | 
				
			||||||
                  
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
          </div>
 | 
					          </div>
 | 
				
			||||||
 | 
					 | 
				
			||||||
          <div className="col-md-6">
 | 
					          <div className="col-md-6">
 | 
				
			||||||
            <label className="form-label">Phone</label>
 | 
					            {phoneFields.map((field, index) => (
 | 
				
			||||||
            {phoneFields.map((field, index) => (<>
 | 
					              <div
 | 
				
			||||||
              <div key={field.id} className="d-flex align-items-center mb-1">
 | 
					                key={field.id}
 | 
				
			||||||
                <input
 | 
					                className="row d-flex align-items-center mb-2"
 | 
				
			||||||
                  type="text"
 | 
					              >
 | 
				
			||||||
                  className="form-control form-control-sm"
 | 
					                <div className="col-5  text-start">
 | 
				
			||||||
                  {...register(`phone.${index}`)}
 | 
					                  <label className="form-label">Label</label>
 | 
				
			||||||
                  placeholder="9876543210"
 | 
					                  <select
 | 
				
			||||||
                />
 | 
					                    className="form-select form-select-sm"
 | 
				
			||||||
                {index === phoneFields.length - 1 ? (
 | 
					                    {...register(`contactPhones.${index}.label`)}
 | 
				
			||||||
                  <button
 | 
					 | 
				
			||||||
                    type="button"
 | 
					 | 
				
			||||||
                    className="btn btn-xs btn-primary ms-1"
 | 
					 | 
				
			||||||
                    onClick={handleAddPhone}
 | 
					 | 
				
			||||||
                  >
 | 
					                  >
 | 
				
			||||||
                    <i className="bx bx-plus bx-xs" />
 | 
					                    <option value="Office">Office</option>
 | 
				
			||||||
                  </button>
 | 
					                    <option value="Personal">Personal</option>
 | 
				
			||||||
                ) : (
 | 
					                    <option value="Business">Business</option>
 | 
				
			||||||
                  <button
 | 
					                  </select>
 | 
				
			||||||
                    type="button"
 | 
					                  {errors.phone?.[index]?.label && (
 | 
				
			||||||
                    className="btn btn-xs btn-danger ms-1"
 | 
					                    <small className="danger-text">
 | 
				
			||||||
                    onClick={() => removePhone(index)} // Remove the phone field
 | 
					                      {errors.ContactPhones[index].label.message}
 | 
				
			||||||
                  >
 | 
					                    </small>
 | 
				
			||||||
                    <i className="bx bx-x bx-xs" />
 | 
					                  )}
 | 
				
			||||||
                  </button>
 | 
					                </div>
 | 
				
			||||||
                )}
 | 
					                <div className="col-7  text-start">
 | 
				
			||||||
 | 
					                  <label className="form-label">Phone</label>
 | 
				
			||||||
 | 
					                  <div className="d-flex align-items-center">
 | 
				
			||||||
 | 
					                    <input
 | 
				
			||||||
 | 
					                      type="text"
 | 
				
			||||||
 | 
					                      className="form-control form-control-sm"
 | 
				
			||||||
 | 
					                      {...register(`contactPhones.${index}.phoneNumber`)}
 | 
				
			||||||
 | 
					                      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 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 ms-1 cursor-pointer text-danager"
 | 
				
			||||||
 | 
					                        onClick={() => removePhone(index)}
 | 
				
			||||||
 | 
					                      />
 | 
				
			||||||
 | 
					                    )}
 | 
				
			||||||
 | 
					                  </div>
 | 
				
			||||||
 | 
					                  {errors.contactPhones?.[index]?.phoneNumber && (
 | 
				
			||||||
 | 
					                    <small className="danger-text">
 | 
				
			||||||
 | 
					                      {errors.contactPhones[index].phoneNumber.message}
 | 
				
			||||||
 | 
					                    </small>
 | 
				
			||||||
 | 
					                  )}
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
              </div>
 | 
					              </div>
 | 
				
			||||||
              {errors.phone?.[ index ] && <small className="danger-text ms-2">{errors.phone[ index ]?.message}</small>}
 | 
					 | 
				
			||||||
              </>
 | 
					 | 
				
			||||||
            ))}
 | 
					            ))}
 | 
				
			||||||
         
 | 
					 | 
				
			||||||
          </div>
 | 
					          </div>
 | 
				
			||||||
 | 
					          {errors.contactPhone?.message && (
 | 
				
			||||||
 | 
					            <div className="danger-text">{errors.contactPhone.message}</div>
 | 
				
			||||||
 | 
					          )}
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        <div className="row my-1">
 | 
					        <div className="row my-1">
 | 
				
			||||||
          <div className="col-md-6">
 | 
					          <div className="col-md-6  text-start">
 | 
				
			||||||
            <label className="form-label">Type</label>
 | 
					            <label className="form-label">Category</label>
 | 
				
			||||||
            <input className="form-control form-control-sm" {...register("type")} />
 | 
					            <select
 | 
				
			||||||
            {errors.type && <small className="danger-text">{errors.type.message}</small>}
 | 
					              className="form-select form-select-sm"
 | 
				
			||||||
 | 
					              {...register("contactCategoryId")}
 | 
				
			||||||
 | 
					            >
 | 
				
			||||||
 | 
					              {contactCategoryLoading && !contactCategory ? (
 | 
				
			||||||
 | 
					                <option disabled value="">
 | 
				
			||||||
 | 
					                  Loading...
 | 
				
			||||||
 | 
					                </option>
 | 
				
			||||||
 | 
					              ) : (
 | 
				
			||||||
 | 
					                <>
 | 
				
			||||||
 | 
					                  <option disabled value="">
 | 
				
			||||||
 | 
					                    Select Category
 | 
				
			||||||
 | 
					                  </option>
 | 
				
			||||||
 | 
					                  {contactCategory?.map((cate) => (
 | 
				
			||||||
 | 
					                    <option key={cate.id} value={cate.id}>
 | 
				
			||||||
 | 
					                      {cate.name}
 | 
				
			||||||
 | 
					                    </option>
 | 
				
			||||||
 | 
					                  ))}
 | 
				
			||||||
 | 
					                </>
 | 
				
			||||||
 | 
					              )}
 | 
				
			||||||
 | 
					            </select>
 | 
				
			||||||
 | 
					            {errors.contactCategoryId && (
 | 
				
			||||||
 | 
					              <small className="danger-text">
 | 
				
			||||||
 | 
					                {errors.contactCategoryId.message}
 | 
				
			||||||
 | 
					              </small>
 | 
				
			||||||
 | 
					            )}
 | 
				
			||||||
          </div>
 | 
					          </div>
 | 
				
			||||||
          <div className="col-md-6">
 | 
					          <div className="col-12 col-md-6 text-start">
 | 
				
			||||||
            <TagInput name="tags" label="Tags" />
 | 
					            <SelectMultiple
 | 
				
			||||||
 | 
					              name="projectIds"
 | 
				
			||||||
 | 
					              label="Select Projects"
 | 
				
			||||||
 | 
					              options={projects}
 | 
				
			||||||
 | 
					              labelKey="name"
 | 
				
			||||||
 | 
					              valueKey="id"
 | 
				
			||||||
 | 
					              IsLoading={projectLoading}
 | 
				
			||||||
 | 
					            />
 | 
				
			||||||
 | 
					            {errors.projectIds && (
 | 
				
			||||||
 | 
					              <small className="danger-text">{errors.projectIds.message}</small>
 | 
				
			||||||
 | 
					            )}
 | 
				
			||||||
          </div>
 | 
					          </div>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        <div className="col-12">
 | 
					        <div className="col-12 text-start">
 | 
				
			||||||
 | 
					          <TagInput name="tags" label="Tags" options={contactTags} />
 | 
				
			||||||
 | 
					          {errors.tags && (
 | 
				
			||||||
 | 
					            <small className="danger-text">{errors.tags.message}</small>
 | 
				
			||||||
 | 
					          )}
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					        <div className="row">
 | 
				
			||||||
 | 
					          <div className="col-md-12 mt-1 text-start">
 | 
				
			||||||
 | 
					            <label className="form-label ">Select Bucket</label>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <ul className="d-flex flex-wrap px-1 list-unstyled  mb-0">
 | 
				
			||||||
 | 
					              {bucketsLoaging && <p>Loading...</p>}
 | 
				
			||||||
 | 
					              {buckets?.map((item) => (
 | 
				
			||||||
 | 
					                <li
 | 
				
			||||||
 | 
					                  key={item.id}
 | 
				
			||||||
 | 
					                  className="list-inline-item flex-shrink-0 me-6 mb-2"
 | 
				
			||||||
 | 
					                >
 | 
				
			||||||
 | 
					                  <div className="form-check ">
 | 
				
			||||||
 | 
					                    <input
 | 
				
			||||||
 | 
					                      type="checkbox"
 | 
				
			||||||
 | 
					                      className="form-check-input"
 | 
				
			||||||
 | 
					                      id={`item-${item.id}`}
 | 
				
			||||||
 | 
					                      checked={watchBucketIds.includes(item.id)}
 | 
				
			||||||
 | 
					                      onChange={() => handleCheckboxChange(item.id)}
 | 
				
			||||||
 | 
					                    />
 | 
				
			||||||
 | 
					                    <label
 | 
				
			||||||
 | 
					                      className="form-check-label"
 | 
				
			||||||
 | 
					                      htmlFor={`item-${item.id}`}
 | 
				
			||||||
 | 
					                    >
 | 
				
			||||||
 | 
					                      {item.name}
 | 
				
			||||||
 | 
					                    </label>
 | 
				
			||||||
 | 
					                  </div>
 | 
				
			||||||
 | 
					                </li>
 | 
				
			||||||
 | 
					              ))}
 | 
				
			||||||
 | 
					              {errors.bucketIds && (
 | 
				
			||||||
 | 
					                <small className="danger-text mt-0">
 | 
				
			||||||
 | 
					                  {errors.bucketIds.message}
 | 
				
			||||||
 | 
					                </small>
 | 
				
			||||||
 | 
					              )}
 | 
				
			||||||
 | 
					            </ul>
 | 
				
			||||||
 | 
					          </div>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <div className="col-12 text-start">
 | 
				
			||||||
          <label className="form-label">Address</label>
 | 
					          <label className="form-label">Address</label>
 | 
				
			||||||
          <textarea className="form-control form-control-sm" rows="2" {...register("address")} />
 | 
					          <textarea
 | 
				
			||||||
 | 
					            className="form-control form-control-sm"
 | 
				
			||||||
 | 
					            rows="2"
 | 
				
			||||||
 | 
					            {...register("address")}
 | 
				
			||||||
 | 
					          />
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        <div className="col-12">
 | 
					        <div className="col-12 text-start">
 | 
				
			||||||
          <label className="form-label">Description</label>
 | 
					          <label className="form-label">Description</label>
 | 
				
			||||||
          <textarea className="form-control form-control-sm" rows="2" {...register("description")} />
 | 
					          <textarea
 | 
				
			||||||
          {errors.description && <small className="danger-text">{errors.description.message}</small>}
 | 
					            className="form-control form-control-sm"
 | 
				
			||||||
 | 
					            rows="2"
 | 
				
			||||||
 | 
					            {...register("description")}
 | 
				
			||||||
 | 
					          />
 | 
				
			||||||
 | 
					          {errors.description && (
 | 
				
			||||||
 | 
					            <small className="danger-text">{errors.description.message}</small>
 | 
				
			||||||
 | 
					          )}
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        <div className="d-flex justify-content-evenly py-2">
 | 
					        <div className="d-flex justify-content-center gap-1 py-2">
 | 
				
			||||||
          <button className="btn btn-sm btn-primary" type="submit">Submit</button>
 | 
					          <button className="btn btn-sm btn-primary" type="submit">
 | 
				
			||||||
          <button className="btn btn-sm btn-secondary" type="button">Cancel</button>
 | 
					            {IsSubmitting ? "Please Wait..." : "Submit"}
 | 
				
			||||||
 | 
					          </button>
 | 
				
			||||||
 | 
					          <button
 | 
				
			||||||
 | 
					            className="btn btn-sm btn-secondary"
 | 
				
			||||||
 | 
					            type="button"
 | 
				
			||||||
 | 
					            onClick={handleClosed}
 | 
				
			||||||
 | 
					          >
 | 
				
			||||||
 | 
					            Cancel
 | 
				
			||||||
 | 
					          </button>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
      </form>
 | 
					      </form>
 | 
				
			||||||
    </FormProvider>
 | 
					    </FormProvider>
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										220
									
								
								src/components/Directory/NoteCardDirectory.jsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										220
									
								
								src/components/Directory/NoteCardDirectory.jsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,220 @@
 | 
				
			|||||||
 | 
					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 = ({
 | 
				
			||||||
 | 
					  refetchProfile,
 | 
				
			||||||
 | 
					  refetchNotes,
 | 
				
			||||||
 | 
					  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 [isActivProcess, setActiveProcessing] = 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 (activeStatue) => {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      activeStatue ? setActiveProcessing(true) : setIsDeleting(true);
 | 
				
			||||||
 | 
					      const resp = await DirectoryRepository.DeleteNote(
 | 
				
			||||||
 | 
					        noteItem.id,
 | 
				
			||||||
 | 
					        activeStatue
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					      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);
 | 
				
			||||||
 | 
					      setActiveProcessing(false);
 | 
				
			||||||
 | 
					      refetchNotes(contactId, false);
 | 
				
			||||||
 | 
					      refetchProfile(contactId);
 | 
				
			||||||
 | 
					      showToast(
 | 
				
			||||||
 | 
					        `Note ${activeStatue ? "Restored" : "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-5 conntactNote rounded"
 | 
				
			||||||
 | 
					      style={{
 | 
				
			||||||
 | 
					        width: "100%",
 | 
				
			||||||
 | 
					        minWidth: "300px",
 | 
				
			||||||
 | 
					        borderRadius: "0px",
 | 
				
			||||||
 | 
					        background: `${noteItem.isActive ? "" : "#f8f6f6"}`,
 | 
				
			||||||
 | 
					      }}
 | 
				
			||||||
 | 
					      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
 | 
				
			||||||
 | 
					                .utc(noteItem.createdAt)
 | 
				
			||||||
 | 
					                .add(5, "hours")
 | 
				
			||||||
 | 
					                .add(30, "minutes")
 | 
				
			||||||
 | 
					                .format("MMMM DD, YYYY [at] hh:mm A")}
 | 
				
			||||||
 | 
					            </span>
 | 
				
			||||||
 | 
					          </div>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					        <div>
 | 
				
			||||||
 | 
					          {noteItem.isActive ? (
 | 
				
			||||||
 | 
					            <>
 | 
				
			||||||
 | 
					              <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(!noteItem.isActive)}
 | 
				
			||||||
 | 
					                ></i>
 | 
				
			||||||
 | 
					              ) : (
 | 
				
			||||||
 | 
					                <div
 | 
				
			||||||
 | 
					                  className="spinner-border spinner-border-sm text-secondary"
 | 
				
			||||||
 | 
					                  role="status"
 | 
				
			||||||
 | 
					                >
 | 
				
			||||||
 | 
					                  <span className="visually-hidden">Loading...</span>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					              )}
 | 
				
			||||||
 | 
					            </>
 | 
				
			||||||
 | 
					          ) : isActivProcess ? (
 | 
				
			||||||
 | 
					            <i className="bx  bx-loader-alt bx-spin text-primary"></i>
 | 
				
			||||||
 | 
					          ) : (
 | 
				
			||||||
 | 
					            <i
 | 
				
			||||||
 | 
					              className="bx bx-recycle  me-1 text-primary cursor-pointer"
 | 
				
			||||||
 | 
					              onClick={() => handleDeleteNote(!noteItem.isActive)}
 | 
				
			||||||
 | 
					              title="Restore"
 | 
				
			||||||
 | 
					            ></i>
 | 
				
			||||||
 | 
					          )}
 | 
				
			||||||
 | 
					        </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;
 | 
				
			||||||
							
								
								
									
										188
									
								
								src/components/Directory/NotesDirectory.jsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										188
									
								
								src/components/Directory/NotesDirectory.jsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,188 @@
 | 
				
			|||||||
 | 
					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";
 | 
				
			||||||
 | 
					import { useContactNotes } from "../../hooks/useDirectory";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const schema = z.object({
 | 
				
			||||||
 | 
					  note: z.string().min(1, { message: "Note is required" }),
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const NotesDirectory = ({
 | 
				
			||||||
 | 
					  refetchProfile,
 | 
				
			||||||
 | 
					  isLoading,
 | 
				
			||||||
 | 
					  contactProfile,
 | 
				
			||||||
 | 
					  setProfileContact,
 | 
				
			||||||
 | 
					}) => {
 | 
				
			||||||
 | 
					  const [IsActive, setIsActive] = useState(true);
 | 
				
			||||||
 | 
					  const { contactNotes, refetch } = useContactNotes(contactProfile?.id, true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const [NotesData, setNotesData] = useState();
 | 
				
			||||||
 | 
					  const [IsSubmitting, setIsSubmitting] = useState(false);
 | 
				
			||||||
 | 
					  const [addNote, setAddNote] = useState(true);
 | 
				
			||||||
 | 
					  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(true);
 | 
				
			||||||
 | 
					      setIsActive(true);
 | 
				
			||||||
 | 
					    } catch (error) {
 | 
				
			||||||
 | 
					      setIsSubmitting(false);
 | 
				
			||||||
 | 
					      const msg =
 | 
				
			||||||
 | 
					        error.response.data.message ||
 | 
				
			||||||
 | 
					        error.message ||
 | 
				
			||||||
 | 
					        "Error occured during API calling";
 | 
				
			||||||
 | 
					      showToast(msg, "error");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const onCancel = () => {
 | 
				
			||||||
 | 
					    setValue("note", "");
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					  const handleSwitch = () => {
 | 
				
			||||||
 | 
					    setIsActive(!IsActive);
 | 
				
			||||||
 | 
					    if (IsActive) {
 | 
				
			||||||
 | 
					      refetch(contactProfile?.id, false);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <div className="text-start">
 | 
				
			||||||
 | 
					      <div className="d-flex align-items-center justify-content-between">
 | 
				
			||||||
 | 
					        <p className="fw-semibold m-0">Notes :</p>
 | 
				
			||||||
 | 
					        <div className="m-0 d-flex aligin-items-center">
 | 
				
			||||||
 | 
					          <label className="switch switch-primary">
 | 
				
			||||||
 | 
					            <input
 | 
				
			||||||
 | 
					              type="checkbox"
 | 
				
			||||||
 | 
					              className="switch-input"
 | 
				
			||||||
 | 
					              onChange={() => handleSwitch(!IsActive)}
 | 
				
			||||||
 | 
					              value={IsActive}
 | 
				
			||||||
 | 
					            />
 | 
				
			||||||
 | 
					            <span className="switch-toggle-slider">
 | 
				
			||||||
 | 
					              <span className="switch-on">
 | 
				
			||||||
 | 
					                {/* <i class="icon-base bx bx-check"></i> */}
 | 
				
			||||||
 | 
					              </span>
 | 
				
			||||||
 | 
					              <span className="switch-off">
 | 
				
			||||||
 | 
					                {/* <i class="icon-base bx bx-x"></i> */}
 | 
				
			||||||
 | 
					              </span>
 | 
				
			||||||
 | 
					            </span>
 | 
				
			||||||
 | 
					            <span className="switch-label ">Show Including Inactive Notes</span>
 | 
				
			||||||
 | 
					          </label>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					      </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="d-flex justify-content-end px-2">
 | 
				
			||||||
 | 
					        <span
 | 
				
			||||||
 | 
					          className={`btn btn-sm   ${addNote ? "btn-danger" : "btn-primary"}`}
 | 
				
			||||||
 | 
					          onClick={() => setAddNote(!addNote)}
 | 
				
			||||||
 | 
					        >
 | 
				
			||||||
 | 
					          {addNote ? "Hide Editor" : "Add Note"}
 | 
				
			||||||
 | 
					        </span>
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					      <div className=" justify-content-start  px-1 mt-1">
 | 
				
			||||||
 | 
					        {isLoading && (
 | 
				
			||||||
 | 
					          <div className="text-center">
 | 
				
			||||||
 | 
					            {" "}
 | 
				
			||||||
 | 
					            <p>Loading...</p>{" "}
 | 
				
			||||||
 | 
					          </div>
 | 
				
			||||||
 | 
					        )}
 | 
				
			||||||
 | 
					        {!isLoading &&
 | 
				
			||||||
 | 
					          [...(IsActive ? contactProfile?.notes || [] : contactNotes || [])]
 | 
				
			||||||
 | 
					            .reverse()
 | 
				
			||||||
 | 
					            .map((noteItem) => (
 | 
				
			||||||
 | 
					              <NoteCardDirectory
 | 
				
			||||||
 | 
					                refetchProfile={refetchProfile}
 | 
				
			||||||
 | 
					                refetchNotes={refetch}
 | 
				
			||||||
 | 
					                refetchContact={refetch}
 | 
				
			||||||
 | 
					                noteItem={noteItem}
 | 
				
			||||||
 | 
					                contactId={contactProfile?.id}
 | 
				
			||||||
 | 
					                setProfileContact={setProfileContact}
 | 
				
			||||||
 | 
					                key={noteItem.id}
 | 
				
			||||||
 | 
					              />
 | 
				
			||||||
 | 
					            ))}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        {IsActive && (
 | 
				
			||||||
 | 
					          <div>
 | 
				
			||||||
 | 
					            {!isLoading && contactProfile?.notes.length == 0 && !addNote && (
 | 
				
			||||||
 | 
					              <div className="text-center mt-5">No Notes Found</div>
 | 
				
			||||||
 | 
					            )}
 | 
				
			||||||
 | 
					          </div>
 | 
				
			||||||
 | 
					        )}
 | 
				
			||||||
 | 
					        {!IsActive && (
 | 
				
			||||||
 | 
					          <div>
 | 
				
			||||||
 | 
					            {!isLoading && contactNotes.length == 0 && !addNote && (
 | 
				
			||||||
 | 
					              <div className="text-center  mt-5">No Notes Found</div>
 | 
				
			||||||
 | 
					            )}
 | 
				
			||||||
 | 
					          </div>
 | 
				
			||||||
 | 
					        )}
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default NotesDirectory;
 | 
				
			||||||
							
								
								
									
										234
									
								
								src/components/Directory/ProfileContactDirectory.jsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										234
									
								
								src/components/Directory/ProfileContactDirectory.jsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,234 @@
 | 
				
			|||||||
 | 
					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 { contactProfile, loading, refetch } = useContactProfile(contact?.id);
 | 
				
			||||||
 | 
					  const [copiedIndex, setCopiedIndex] = useState(null);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const [profileContact, setProfileContact] = useState();
 | 
				
			||||||
 | 
					  const [expanded, setExpanded] = useState(false);
 | 
				
			||||||
 | 
					  const description = contactProfile?.description || "";
 | 
				
			||||||
 | 
					  const limit = 500;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const toggleReadMore = () => setExpanded(!expanded);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const isLong = description.length > limit;
 | 
				
			||||||
 | 
					  const displayText = expanded
 | 
				
			||||||
 | 
					    ? description
 | 
				
			||||||
 | 
					    : description.slice(0, limit) + (isLong ? "..." : "");
 | 
				
			||||||
 | 
					  useEffect(() => {
 | 
				
			||||||
 | 
					    setProfileContact(contactProfile);
 | 
				
			||||||
 | 
					  }, [contactProfile]);
 | 
				
			||||||
 | 
					  const handleCopy = (email, index) => {
 | 
				
			||||||
 | 
					    navigator.clipboard.writeText(email);
 | 
				
			||||||
 | 
					    setCopiedIndex(index);
 | 
				
			||||||
 | 
					    setTimeout(() => setCopiedIndex(null), 2000); // Reset after 2 seconds
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					  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"
 | 
				
			||||||
 | 
					            classAvatar="m-0"
 | 
				
			||||||
 | 
					            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-1">
 | 
				
			||||||
 | 
					            <span className="m-0 fw-semibold">{contact?.name}</span>
 | 
				
			||||||
 | 
					            <small className="text-secondary small-text">
 | 
				
			||||||
 | 
					              {contactProfile?.tags?.map((tag) => tag.name).join(" | ")}
 | 
				
			||||||
 | 
					            </small>
 | 
				
			||||||
 | 
					          </div>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					        <div className="row">
 | 
				
			||||||
 | 
					          <div className="col-12 col-md-6 d-flex flex-column text-start">
 | 
				
			||||||
 | 
					            {contactProfile?.contactEmails?.length > 0 && (
 | 
				
			||||||
 | 
					              <div className="d-flex mb-2">
 | 
				
			||||||
 | 
					                <div style={{ width: "100px", minWidth: "100px" }}>
 | 
				
			||||||
 | 
					                  <p className="m-0">Email:</p>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					                <div style={{ flex: 1 }}>
 | 
				
			||||||
 | 
					                  <ul className="list-unstyled mb-0">
 | 
				
			||||||
 | 
					                    {contactProfile.contactEmails.map((email, idx) => (
 | 
				
			||||||
 | 
					                      <li className="d-flex align-items-center mb-1" key={idx}>
 | 
				
			||||||
 | 
					                        <i className="bx bx-envelope bx-xs me-1 mt-1"></i>
 | 
				
			||||||
 | 
					                        <span className="me-1 flex-grow text-break overflow-wrap">
 | 
				
			||||||
 | 
					                          {email.emailAddress}
 | 
				
			||||||
 | 
					                        </span>
 | 
				
			||||||
 | 
					                        <i
 | 
				
			||||||
 | 
					                          className={`bx bx-copy-alt cursor-pointer bx-xs text-start ${
 | 
				
			||||||
 | 
					                            copiedIndex === idx
 | 
				
			||||||
 | 
					                              ? "text-secondary"
 | 
				
			||||||
 | 
					                              : "text-primary"
 | 
				
			||||||
 | 
					                          }`}
 | 
				
			||||||
 | 
					                          title={copiedIndex === idx ? "Copied!" : "Copy Email"}
 | 
				
			||||||
 | 
					                          style={{ flexShrink: 0 }}
 | 
				
			||||||
 | 
					                          onClick={() => handleCopy(email.emailAddress, idx)}
 | 
				
			||||||
 | 
					                        ></i>
 | 
				
			||||||
 | 
					                      </li>
 | 
				
			||||||
 | 
					                    ))}
 | 
				
			||||||
 | 
					                  </ul>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					              </div>
 | 
				
			||||||
 | 
					            )}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            {contactProfile?.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">
 | 
				
			||||||
 | 
					                    {contactProfile?.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>
 | 
				
			||||||
 | 
					            )}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            {contactProfile?.createdAt && (
 | 
				
			||||||
 | 
					              <div className="d-flex  mb-2">
 | 
				
			||||||
 | 
					                <div style={{ width: "100px", minWidth: "100px" }}>
 | 
				
			||||||
 | 
					                  <p className="m-0">Created : </p>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					                <div className="d-flex align-items-center">
 | 
				
			||||||
 | 
					                  <li className="list-inline-item">
 | 
				
			||||||
 | 
					                    <i className="bx bx-calendar-week bx-xs me-1"></i>
 | 
				
			||||||
 | 
					                    {moment(contactProfile.createdAt).format("MMMM, DD YYYY")}
 | 
				
			||||||
 | 
					                  </li>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					              </div>
 | 
				
			||||||
 | 
					            )}
 | 
				
			||||||
 | 
					            {contactProfile?.address && (
 | 
				
			||||||
 | 
					              <div className="d-flex mb-2">
 | 
				
			||||||
 | 
					                <div style={{ width: "100px", minWidth: "100px" }}>
 | 
				
			||||||
 | 
					                  <p className="m-0">Location:</p>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					                <div className="d-flex align-items-center">
 | 
				
			||||||
 | 
					                  <i className="bx bx-map bx-xs me-1 "></i>
 | 
				
			||||||
 | 
					                  <span className="text-break small">
 | 
				
			||||||
 | 
					                    {contactProfile.address}
 | 
				
			||||||
 | 
					                  </span>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					              </div>
 | 
				
			||||||
 | 
					            )}
 | 
				
			||||||
 | 
					          </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          <div className="col-12 col-md-6 d-flex flex-column text-start">
 | 
				
			||||||
 | 
					            {contactProfile?.organization && (
 | 
				
			||||||
 | 
					              <div className="d-flex mb-2">
 | 
				
			||||||
 | 
					                <div style={{ width: "100px", minWidth: "100px" }}>
 | 
				
			||||||
 | 
					                  <p className="m-0">Orgnization : </p>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					                <div className="d-flex align-items-center">
 | 
				
			||||||
 | 
					                  <i className="fa-solid fa-briefcase me-2"></i>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                  <span style={{ wordBreak: "break-word" }}>
 | 
				
			||||||
 | 
					                    {contactProfile.organization}
 | 
				
			||||||
 | 
					                  </span>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					              </div>
 | 
				
			||||||
 | 
					            )}
 | 
				
			||||||
 | 
					            {contactProfile?.contactCategory && (
 | 
				
			||||||
 | 
					              <div className="d-flex mb-2">
 | 
				
			||||||
 | 
					                <div style={{ width: "100px", minWidth: "100px" }}>
 | 
				
			||||||
 | 
					                  <p className="m-0">Category : </p>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					                <div>
 | 
				
			||||||
 | 
					                  <ul className="list-inline mb-0">
 | 
				
			||||||
 | 
					                    <li className="list-inline-item">
 | 
				
			||||||
 | 
					                      <i className="bx bx-user bx-xs me-1"></i>
 | 
				
			||||||
 | 
					                      {contactProfile.contactCategory.name}
 | 
				
			||||||
 | 
					                    </li>
 | 
				
			||||||
 | 
					                  </ul>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					              </div>
 | 
				
			||||||
 | 
					            )}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            {contactProfile?.buckets?.length > 0 && (
 | 
				
			||||||
 | 
					              <div className="d-flex ">
 | 
				
			||||||
 | 
					                {contactProfile?.contactEmails?.length > 0 && (
 | 
				
			||||||
 | 
					                  <div className="d-flex mb-2 align-items-center">
 | 
				
			||||||
 | 
					                    <div style={{ width: "100px", minWidth: "100px" }}>
 | 
				
			||||||
 | 
					                      <p className="m-0">Buckets : </p>
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					                    <div>
 | 
				
			||||||
 | 
					                      <ul className="list-inline mb-0">
 | 
				
			||||||
 | 
					                        {contactProfile.buckets.map((bucket) => (
 | 
				
			||||||
 | 
					                          <li className="list-inline-item me-2" key={bucket.id}>
 | 
				
			||||||
 | 
					                            <span className="badge bg-label-primary my-1">
 | 
				
			||||||
 | 
					                              {bucket.name}
 | 
				
			||||||
 | 
					                            </span>
 | 
				
			||||||
 | 
					                          </li>
 | 
				
			||||||
 | 
					                        ))}
 | 
				
			||||||
 | 
					                      </ul>
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					                  </div>
 | 
				
			||||||
 | 
					                )}
 | 
				
			||||||
 | 
					              </div>
 | 
				
			||||||
 | 
					            )}
 | 
				
			||||||
 | 
					          </div>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					        {contactProfile?.projects?.length > 0 && (
 | 
				
			||||||
 | 
					          <div className="d-flex mb-2 align-items-start">
 | 
				
			||||||
 | 
					            <div style={{ minWidth: "100px" }}>
 | 
				
			||||||
 | 
					              <p className="m-0 text-start">Projects :</p>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					            <div className="text-start">
 | 
				
			||||||
 | 
					              <ul className="list-inline mb-0">
 | 
				
			||||||
 | 
					                {contactProfile.projects.map((project, index) => (
 | 
				
			||||||
 | 
					                  <li className="list-inline-item me-2" key={project.id}>
 | 
				
			||||||
 | 
					                    {project.name}
 | 
				
			||||||
 | 
					                    {index < contactProfile.projects.length - 1 && ","}
 | 
				
			||||||
 | 
					                  </li>
 | 
				
			||||||
 | 
					                ))}
 | 
				
			||||||
 | 
					              </ul>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					          </div>
 | 
				
			||||||
 | 
					        )}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <div className="d-flex mb-2 align-items-start">
 | 
				
			||||||
 | 
					          <div style={{ minWidth: "100px" }}>
 | 
				
			||||||
 | 
					            <p className="m-0 text-start">Description :</p>
 | 
				
			||||||
 | 
					          </div>
 | 
				
			||||||
 | 
					          <div className="text-start">
 | 
				
			||||||
 | 
					            {displayText}
 | 
				
			||||||
 | 
					            {isLong && (
 | 
				
			||||||
 | 
					              <span
 | 
				
			||||||
 | 
					                onClick={toggleReadMore}
 | 
				
			||||||
 | 
					                className="text-primary mx-1 cursor-pointer"
 | 
				
			||||||
 | 
					              >
 | 
				
			||||||
 | 
					                {expanded ? "Read less" : "Read more"}
 | 
				
			||||||
 | 
					              </span>
 | 
				
			||||||
 | 
					            )}
 | 
				
			||||||
 | 
					          </div>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <hr className="my-1" />
 | 
				
			||||||
 | 
					        <NotesDirectory
 | 
				
			||||||
 | 
					          refetchProfile={refetch}
 | 
				
			||||||
 | 
					          isLoading={loading}
 | 
				
			||||||
 | 
					          contactProfile={profileContact}
 | 
				
			||||||
 | 
					          setProfileContact={setProfileContact}
 | 
				
			||||||
 | 
					        />
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default ProfileContactDirectory;
 | 
				
			||||||
							
								
								
									
										465
									
								
								src/components/Directory/UpdateContact.jsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										465
									
								
								src/components/Directory/UpdateContact.jsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,465 @@
 | 
				
			|||||||
 | 
					import React, { useEffect, useState } from "react";
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					  useForm,
 | 
				
			||||||
 | 
					  useFieldArray,
 | 
				
			||||||
 | 
					  FormProvider,
 | 
				
			||||||
 | 
					  useFormContext,
 | 
				
			||||||
 | 
					} from "react-hook-form";
 | 
				
			||||||
 | 
					import { zodResolver } from "@hookform/resolvers/zod";
 | 
				
			||||||
 | 
					import TagInput from "../common/TagInput";
 | 
				
			||||||
 | 
					import IconButton from "../common/IconButton";
 | 
				
			||||||
 | 
					import useMaster, {
 | 
				
			||||||
 | 
					  useContactCategory,
 | 
				
			||||||
 | 
					  useContactTags,
 | 
				
			||||||
 | 
					} from "../../hooks/masterHook/useMaster";
 | 
				
			||||||
 | 
					import { useDispatch, useSelector } from "react-redux";
 | 
				
			||||||
 | 
					import { changeMaster } from "../../slices/localVariablesSlice";
 | 
				
			||||||
 | 
					import { useBuckets, useOrganization } from "../../hooks/useDirectory";
 | 
				
			||||||
 | 
					import { useProjects } from "../../hooks/useProjects";
 | 
				
			||||||
 | 
					import SelectMultiple from "../common/SelectMultiple";
 | 
				
			||||||
 | 
					import { ContactSchema } from "./DirectorySchema";
 | 
				
			||||||
 | 
					import InputSuggestions from "../common/InputSuggestion";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const UpdateContact = ({ submitContact, existingContact, onCLosed }) => {
 | 
				
			||||||
 | 
					  const selectedMaster = useSelector(
 | 
				
			||||||
 | 
					    (store) => store.localVariables.selectedMaster
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					  const [categoryData, setCategoryData] = useState([]);
 | 
				
			||||||
 | 
					  const [TagsData, setTagsData] = useState([]);
 | 
				
			||||||
 | 
					  const { data, loading } = useMaster();
 | 
				
			||||||
 | 
					  const { buckets, loading: bucketsLoaging } = useBuckets();
 | 
				
			||||||
 | 
					  const { projects, loading: projectLoading } = useProjects();
 | 
				
			||||||
 | 
					  const { contactCategory, loading: contactCategoryLoading } =
 | 
				
			||||||
 | 
					    useContactCategory();
 | 
				
			||||||
 | 
					  const { contactTags, loading: Tagloading } = useContactTags();
 | 
				
			||||||
 | 
					  const [ IsSubmitting, setSubmitting ] = useState( false );
 | 
				
			||||||
 | 
					  const [isInitialized, setIsInitialized] = useState(false);
 | 
				
			||||||
 | 
					  const dispatch = useDispatch();
 | 
				
			||||||
 | 
					  const {organizationList} = useOrganization()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const methods = useForm({
 | 
				
			||||||
 | 
					    resolver: zodResolver(ContactSchema),
 | 
				
			||||||
 | 
					    defaultValues: {
 | 
				
			||||||
 | 
					      name: "",
 | 
				
			||||||
 | 
					      organization: "",
 | 
				
			||||||
 | 
					      contactCategoryId: null,
 | 
				
			||||||
 | 
					      address: "",
 | 
				
			||||||
 | 
					      description: "",
 | 
				
			||||||
 | 
					      projectIds: [],
 | 
				
			||||||
 | 
					      contactEmails: [],
 | 
				
			||||||
 | 
					      contactPhones: [],
 | 
				
			||||||
 | 
					      tags: [],
 | 
				
			||||||
 | 
					      bucketIds: [],
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const {
 | 
				
			||||||
 | 
					    register,
 | 
				
			||||||
 | 
					    handleSubmit,
 | 
				
			||||||
 | 
					    control,
 | 
				
			||||||
 | 
					    getValues,
 | 
				
			||||||
 | 
					    trigger,
 | 
				
			||||||
 | 
					    setValue,
 | 
				
			||||||
 | 
					    watch,
 | 
				
			||||||
 | 
					    reset,
 | 
				
			||||||
 | 
					    formState: { errors },
 | 
				
			||||||
 | 
					  } = methods;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const {
 | 
				
			||||||
 | 
					    fields: emailFields,
 | 
				
			||||||
 | 
					    append: appendEmail,
 | 
				
			||||||
 | 
					    remove: removeEmail,
 | 
				
			||||||
 | 
					  } = useFieldArray({ control, name: "contactEmails" });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const {
 | 
				
			||||||
 | 
					    fields: phoneFields,
 | 
				
			||||||
 | 
					    append: appendPhone,
 | 
				
			||||||
 | 
					    remove: removePhone,
 | 
				
			||||||
 | 
					  } = useFieldArray({ control, name: "contactPhones" });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const handleAddEmail = async () => {
 | 
				
			||||||
 | 
					    const emails = getValues("contactEmails");
 | 
				
			||||||
 | 
					    const lastIndex = emails.length - 1;
 | 
				
			||||||
 | 
					    const valid = await trigger(`contactEmails.${lastIndex}.emailAddress`);
 | 
				
			||||||
 | 
					    if (valid) {
 | 
				
			||||||
 | 
					      appendEmail({ label: "Work", emailAddress: "" });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const handleAddPhone = async () => {
 | 
				
			||||||
 | 
					    const phones = getValues("contactPhones");
 | 
				
			||||||
 | 
					    const lastIndex = phones.length - 1;
 | 
				
			||||||
 | 
					    const valid = await trigger(`contactPhones.${lastIndex}.phoneNumber`);
 | 
				
			||||||
 | 
					    if (valid) {
 | 
				
			||||||
 | 
					      appendPhone({ label: "Office", phoneNumber: "" });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const watchBucketIds = watch("bucketIds");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const toggleBucketId = (id) => {
 | 
				
			||||||
 | 
					    const updated = watchBucketIds?.includes(id)
 | 
				
			||||||
 | 
					      ? watchBucketIds.filter((val) => val !== id)
 | 
				
			||||||
 | 
					      : [...watchBucketIds, id];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    setValue("bucketIds", updated, { shouldValidate: true });
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					  const handleCheckboxChange = (id) => {
 | 
				
			||||||
 | 
					    const updated = watchBucketIds.includes(id)
 | 
				
			||||||
 | 
					      ? watchBucketIds.filter((i) => i !== id)
 | 
				
			||||||
 | 
					      : [...watchBucketIds, id];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    setValue("bucketIds", updated, { shouldValidate: true });
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const onSubmit = async (data) => {
 | 
				
			||||||
 | 
					const cleaned = {
 | 
				
			||||||
 | 
					  ...data,
 | 
				
			||||||
 | 
					  contactEmails: (data.contactEmails || [])
 | 
				
			||||||
 | 
					    .filter((e) => e.emailAddress?.trim() !== "")
 | 
				
			||||||
 | 
					    .map((email, index) => {
 | 
				
			||||||
 | 
					      const existingEmail = existingContact.contactEmails?.[index];
 | 
				
			||||||
 | 
					      return existingEmail
 | 
				
			||||||
 | 
					        ? { ...email, id: existingEmail.id } 
 | 
				
			||||||
 | 
					        : email;
 | 
				
			||||||
 | 
					    }),
 | 
				
			||||||
 | 
					  contactPhones: (data.contactPhones || [])
 | 
				
			||||||
 | 
					    .filter((p) => p.phoneNumber?.trim() !== "")
 | 
				
			||||||
 | 
					    .map((phone, index) => {
 | 
				
			||||||
 | 
					      const existingPhone = existingContact.contactPhones?.[index];
 | 
				
			||||||
 | 
					      return existingPhone
 | 
				
			||||||
 | 
					        ? { ...phone, id: existingPhone.id }
 | 
				
			||||||
 | 
					        : phone;
 | 
				
			||||||
 | 
					    }),
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					setSubmitting(true);
 | 
				
			||||||
 | 
					await submitContact({ ...cleaned, id: existingContact.id });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    setSubmitting(false);
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					 const orgValue = watch("organization")
 | 
				
			||||||
 | 
					  const handleClosed = () => {
 | 
				
			||||||
 | 
					    onCLosed();
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					  useEffect(() => {
 | 
				
			||||||
 | 
					    const isValidContact =
 | 
				
			||||||
 | 
					      existingContact &&
 | 
				
			||||||
 | 
					      typeof existingContact === "object" &&
 | 
				
			||||||
 | 
					      !Array.isArray(existingContact);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (!isInitialized &&isValidContact && TagsData) {
 | 
				
			||||||
 | 
					      reset({
 | 
				
			||||||
 | 
					        name: existingContact.name || "",
 | 
				
			||||||
 | 
					        organization: existingContact.organization || "",
 | 
				
			||||||
 | 
					        contactEmails: existingContact.contactEmails || [],
 | 
				
			||||||
 | 
					        contactPhones: existingContact.contactPhones || [],
 | 
				
			||||||
 | 
					        contactCategoryId: existingContact.contactCategory?.id || null,
 | 
				
			||||||
 | 
					        address: existingContact.address || "",
 | 
				
			||||||
 | 
					        description: existingContact.description || "",
 | 
				
			||||||
 | 
					        projectIds: existingContact.projectIds || null,
 | 
				
			||||||
 | 
					        tags: existingContact.tags || [],
 | 
				
			||||||
 | 
					        bucketIds: existingContact.bucketIds || [],
 | 
				
			||||||
 | 
					      } );
 | 
				
			||||||
 | 
					      
 | 
				
			||||||
 | 
					      if (!existingContact.contactPhones || existingContact.contactPhones.length === 0) {
 | 
				
			||||||
 | 
					      appendPhone({ label: "Office", phoneNumber: "" });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (!existingContact.contactEmails || existingContact.contactEmails.length === 0) {
 | 
				
			||||||
 | 
					      appendEmail({ label: "Work", emailAddress: "" });
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      setIsInitialized(true)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    // return()=> reset()
 | 
				
			||||||
 | 
					  }, [ existingContact, buckets, projects ] );
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <FormProvider {...methods}>
 | 
				
			||||||
 | 
					      <form className="p-2 p-sm-0" onSubmit={handleSubmit(onSubmit)}>
 | 
				
			||||||
 | 
					        <div className="d-flex justify-content-center align-items-center">
 | 
				
			||||||
 | 
					          <h6 className="m-0 fw-18"> Update Contact</h6>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					        <div className="row">
 | 
				
			||||||
 | 
					          <div className="col-md-6  text-start">
 | 
				
			||||||
 | 
					            <label className="form-label">Name</label>
 | 
				
			||||||
 | 
					            <input
 | 
				
			||||||
 | 
					              className="form-control form-control-sm"
 | 
				
			||||||
 | 
					              {...register("name")}
 | 
				
			||||||
 | 
					            />
 | 
				
			||||||
 | 
					            {errors.name && (
 | 
				
			||||||
 | 
					              <small className="danger-text">{errors.name.message}</small>
 | 
				
			||||||
 | 
					            )}
 | 
				
			||||||
 | 
					          </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					         
 | 
				
			||||||
 | 
					          <div className="col-md-6  text-start">
 | 
				
			||||||
 | 
					            <label className="form-label">Organization</label>
 | 
				
			||||||
 | 
					            <InputSuggestions
 | 
				
			||||||
 | 
					          organizationList={organizationList}
 | 
				
			||||||
 | 
					          value={getValues("organization") || ""}
 | 
				
			||||||
 | 
					          onChange={(val) => setValue("organization", val)}
 | 
				
			||||||
 | 
					          error={errors.organization?.message}
 | 
				
			||||||
 | 
					        />
 | 
				
			||||||
 | 
					            {errors.organization && (
 | 
				
			||||||
 | 
					              <small className="danger-text">
 | 
				
			||||||
 | 
					                {errors.organization.message}
 | 
				
			||||||
 | 
					              </small>
 | 
				
			||||||
 | 
					            )}
 | 
				
			||||||
 | 
					          </div>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					        <div className="row mt-1">
 | 
				
			||||||
 | 
					          <div className="col-md-6">
 | 
				
			||||||
 | 
					            {emailFields.map((field, index) => (
 | 
				
			||||||
 | 
					              <div
 | 
				
			||||||
 | 
					                key={field.id}
 | 
				
			||||||
 | 
					                className="row d-flex align-items-center mb-1"
 | 
				
			||||||
 | 
					              >
 | 
				
			||||||
 | 
					                <div className="col-5  text-start">
 | 
				
			||||||
 | 
					                  <label className="form-label">Label</label>
 | 
				
			||||||
 | 
					                  <select
 | 
				
			||||||
 | 
					                    className="form-select form-select-sm"
 | 
				
			||||||
 | 
					                    {...register(`contactEmails.${index}.label`)}
 | 
				
			||||||
 | 
					                  >
 | 
				
			||||||
 | 
					                    <option value="Work">Work</option>
 | 
				
			||||||
 | 
					                    <option value="Personal">Personal</option>
 | 
				
			||||||
 | 
					                    <option value="Other">Other</option>
 | 
				
			||||||
 | 
					                  </select>
 | 
				
			||||||
 | 
					                  {errors.contactEmails?.[index]?.label && (
 | 
				
			||||||
 | 
					                    <small className="danger-text">
 | 
				
			||||||
 | 
					                      {errors.contactEmails[index].label.message}
 | 
				
			||||||
 | 
					                    </small>
 | 
				
			||||||
 | 
					                  )}
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					                <div className="col-7  text-start">
 | 
				
			||||||
 | 
					                  <label className="form-label">Email</label>
 | 
				
			||||||
 | 
					                  <div className="d-flex align-items-center">
 | 
				
			||||||
 | 
					                    <input
 | 
				
			||||||
 | 
					                      type="email"
 | 
				
			||||||
 | 
					                      className="form-control form-control-sm"
 | 
				
			||||||
 | 
					                      {...register(`contactEmails.${index}.emailAddress`)}
 | 
				
			||||||
 | 
					                      placeholder="email@example.com"
 | 
				
			||||||
 | 
					                    />
 | 
				
			||||||
 | 
					                    {index === emailFields.length - 1 ? (
 | 
				
			||||||
 | 
					                      // <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 ms-1 cursor-pointer text-danger" onClick={() => removeEmail(index)}/>
 | 
				
			||||||
 | 
					              
 | 
				
			||||||
 | 
					                    )}
 | 
				
			||||||
 | 
					                  </div>
 | 
				
			||||||
 | 
					                  {errors.contactEmails?.[index]?.emailAddress && (
 | 
				
			||||||
 | 
					                    <small className="danger-text">
 | 
				
			||||||
 | 
					                      {errors.contactEmails[index].emailAddress.message}
 | 
				
			||||||
 | 
					                    </small>
 | 
				
			||||||
 | 
					                  )}
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					              </div>
 | 
				
			||||||
 | 
					            ))}
 | 
				
			||||||
 | 
					          </div>
 | 
				
			||||||
 | 
					          <div className="col-md-6">
 | 
				
			||||||
 | 
					            {phoneFields.map((field, index) => (
 | 
				
			||||||
 | 
					              <div
 | 
				
			||||||
 | 
					                key={field.id}
 | 
				
			||||||
 | 
					                className="row d-flex align-items-center mb-2"
 | 
				
			||||||
 | 
					              >
 | 
				
			||||||
 | 
					                <div className="col-5  text-start">
 | 
				
			||||||
 | 
					                  <label className="form-label">Label</label>
 | 
				
			||||||
 | 
					                  <select
 | 
				
			||||||
 | 
					                    className="form-select form-select-sm"
 | 
				
			||||||
 | 
					                    {...register(`contactPhones.${index}.label`)}
 | 
				
			||||||
 | 
					                  >
 | 
				
			||||||
 | 
					                    <option value="Office">Office</option>
 | 
				
			||||||
 | 
					                    <option value="Personal">Personal</option>
 | 
				
			||||||
 | 
					                    <option value="Business">Business</option>
 | 
				
			||||||
 | 
					                  </select>
 | 
				
			||||||
 | 
					                  {errors.phone?.[index]?.label && (
 | 
				
			||||||
 | 
					                    <small className="danger-text">
 | 
				
			||||||
 | 
					                      {errors.ContactPhones[index].label.message}
 | 
				
			||||||
 | 
					                    </small>
 | 
				
			||||||
 | 
					                  )}
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					                <div className="col-7  text-start">
 | 
				
			||||||
 | 
					                  <label className="form-label">Phone</label>
 | 
				
			||||||
 | 
					                  <div className="d-flex align-items-center">
 | 
				
			||||||
 | 
					                    <input
 | 
				
			||||||
 | 
					                      type="text"
 | 
				
			||||||
 | 
					                      className="form-control form-control-sm"
 | 
				
			||||||
 | 
					                      {...register(`contactPhones.${index}.phoneNumber`)}
 | 
				
			||||||
 | 
					                      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 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 ms-1 cursor-pointer text-danger" onClick={() => removePhone(index)} />
 | 
				
			||||||
 | 
					                    )}
 | 
				
			||||||
 | 
					                  </div>
 | 
				
			||||||
 | 
					                  {errors.contactPhones?.[index]?.phoneNumber && (
 | 
				
			||||||
 | 
					                    <small className="danger-text">
 | 
				
			||||||
 | 
					                      {errors.contactPhones[index].phoneNumber.message}
 | 
				
			||||||
 | 
					                    </small>
 | 
				
			||||||
 | 
					                  )}
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					              </div>
 | 
				
			||||||
 | 
					            ))}
 | 
				
			||||||
 | 
					          </div>
 | 
				
			||||||
 | 
					          {errors.contactPhone?.message && (
 | 
				
			||||||
 | 
					            <div className="danger-text">{errors.contactPhone.message}</div>
 | 
				
			||||||
 | 
					          )}
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <div className="row my-1">
 | 
				
			||||||
 | 
					          <div className="col-md-6  text-start">
 | 
				
			||||||
 | 
					            <label className="form-label">Category</label>
 | 
				
			||||||
 | 
					            <select
 | 
				
			||||||
 | 
					              className="form-select form-select-sm"
 | 
				
			||||||
 | 
					              {...register("contactCategoryId")}
 | 
				
			||||||
 | 
					            >
 | 
				
			||||||
 | 
					              {contactCategoryLoading && !contactCategory ? (
 | 
				
			||||||
 | 
					                <option disabled value="">
 | 
				
			||||||
 | 
					                  Loading...
 | 
				
			||||||
 | 
					                </option>
 | 
				
			||||||
 | 
					              ) : (
 | 
				
			||||||
 | 
					                <>
 | 
				
			||||||
 | 
					                  <option disabled  value="">
 | 
				
			||||||
 | 
					                    Select Category
 | 
				
			||||||
 | 
					                  </option>
 | 
				
			||||||
 | 
					                  {contactCategory?.map((cate) => (
 | 
				
			||||||
 | 
					                    <option key={cate.id} value={cate.id}>
 | 
				
			||||||
 | 
					                      {cate.name}
 | 
				
			||||||
 | 
					                    </option>
 | 
				
			||||||
 | 
					                  ))}
 | 
				
			||||||
 | 
					                </>
 | 
				
			||||||
 | 
					              )}
 | 
				
			||||||
 | 
					            </select>
 | 
				
			||||||
 | 
					            {errors.contactCategoryId && (
 | 
				
			||||||
 | 
					              <small className="danger-text">
 | 
				
			||||||
 | 
					                {errors.contactCategoryId.message}
 | 
				
			||||||
 | 
					              </small>
 | 
				
			||||||
 | 
					            )}
 | 
				
			||||||
 | 
					          </div>
 | 
				
			||||||
 | 
					          <div className="col-12 col-md-6 text-start">
 | 
				
			||||||
 | 
					            <SelectMultiple
 | 
				
			||||||
 | 
					              name="projectIds"
 | 
				
			||||||
 | 
					              label="Select Projects"
 | 
				
			||||||
 | 
					              options={projects}
 | 
				
			||||||
 | 
					              labelKey="name"
 | 
				
			||||||
 | 
					              valueKey="id"
 | 
				
			||||||
 | 
					              IsLoading={projectLoading}
 | 
				
			||||||
 | 
					            />
 | 
				
			||||||
 | 
					            {errors.projectIds && (
 | 
				
			||||||
 | 
					              <small className="danger-text">{errors.projectIds.message}</small>
 | 
				
			||||||
 | 
					            )}
 | 
				
			||||||
 | 
					          </div>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <div className="col-12 text-start">
 | 
				
			||||||
 | 
					          <TagInput name="tags" label="Tags" options={contactTags} />
 | 
				
			||||||
 | 
					          {errors.tags && (
 | 
				
			||||||
 | 
					            <small className="danger-text">{errors.tags.message}</small>
 | 
				
			||||||
 | 
					          )}
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					        <div className="row">
 | 
				
			||||||
 | 
					         <div className="col-md-12 mt-1 text-start">
 | 
				
			||||||
 | 
					            <label className="form-label ">Select Label</label>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <ul className="d-flex flex-wrap px-1 list-unstyled  mb-0">
 | 
				
			||||||
 | 
					              {bucketsLoaging && <p>Loading...</p>}
 | 
				
			||||||
 | 
					              {buckets?.map((item) => (
 | 
				
			||||||
 | 
					                <li
 | 
				
			||||||
 | 
					                  key={item.id}
 | 
				
			||||||
 | 
					                  className="list-inline-item flex-shrink-0 me-6 mb-2"
 | 
				
			||||||
 | 
					                >
 | 
				
			||||||
 | 
					                  <div className="form-check ">
 | 
				
			||||||
 | 
					                    <input
 | 
				
			||||||
 | 
					                      type="checkbox"
 | 
				
			||||||
 | 
					                      className="form-check-input"
 | 
				
			||||||
 | 
					                      id={`item-${item.id}`}
 | 
				
			||||||
 | 
					                      checked={watchBucketIds.includes(item.id)}
 | 
				
			||||||
 | 
					                      onChange={() => handleCheckboxChange(item.id)}
 | 
				
			||||||
 | 
					                    />
 | 
				
			||||||
 | 
					                    <label
 | 
				
			||||||
 | 
					                      className="form-check-label"
 | 
				
			||||||
 | 
					                      htmlFor={`item-${item.id}`}
 | 
				
			||||||
 | 
					                    >
 | 
				
			||||||
 | 
					                      {item.name}
 | 
				
			||||||
 | 
					                    </label>
 | 
				
			||||||
 | 
					                  </div>
 | 
				
			||||||
 | 
					                </li>
 | 
				
			||||||
 | 
					              ))}
 | 
				
			||||||
 | 
					              {errors.bucketIds && (
 | 
				
			||||||
 | 
					                <small className="danger-text mt-0">
 | 
				
			||||||
 | 
					                  {errors.bucketIds.message}
 | 
				
			||||||
 | 
					                </small>
 | 
				
			||||||
 | 
					              )}
 | 
				
			||||||
 | 
					            </ul>
 | 
				
			||||||
 | 
					          </div>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <div className="col-12 text-start">
 | 
				
			||||||
 | 
					          <label className="form-label">Address</label>
 | 
				
			||||||
 | 
					          <textarea
 | 
				
			||||||
 | 
					            className="form-control form-control-sm"
 | 
				
			||||||
 | 
					            rows="2"
 | 
				
			||||||
 | 
					            {...register("address")}
 | 
				
			||||||
 | 
					          />
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <div className="col-12 text-start">
 | 
				
			||||||
 | 
					          <label className="form-label">Description</label>
 | 
				
			||||||
 | 
					          <textarea
 | 
				
			||||||
 | 
					            className="form-control form-control-sm"
 | 
				
			||||||
 | 
					            rows="2"
 | 
				
			||||||
 | 
					            {...register("description")}
 | 
				
			||||||
 | 
					          />
 | 
				
			||||||
 | 
					          {errors.description && (
 | 
				
			||||||
 | 
					            <small className="danger-text">{errors.description.message}</small>
 | 
				
			||||||
 | 
					          )}
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <div className="d-flex justify-content-center gap-1 py-2">
 | 
				
			||||||
 | 
					          <button className="btn btn-sm btn-primary" type="submit" disabled={IsSubmitting}>
 | 
				
			||||||
 | 
					            {IsSubmitting ? "Please Wait..." : "Update"}
 | 
				
			||||||
 | 
					          </button>
 | 
				
			||||||
 | 
					          <button
 | 
				
			||||||
 | 
					            className="btn btn-sm btn-secondary"
 | 
				
			||||||
 | 
					            type="button"
 | 
				
			||||||
 | 
					            onClick={handleClosed}
 | 
				
			||||||
 | 
					            disabled={IsSubmitting}
 | 
				
			||||||
 | 
					          >
 | 
				
			||||||
 | 
					            Cancel
 | 
				
			||||||
 | 
					          </button>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					      </form>
 | 
				
			||||||
 | 
					    </FormProvider>
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default UpdateContact;
 | 
				
			||||||
@ -637,6 +637,7 @@ const ManageEmployee = ({ employeeId, onClosed }) => {
 | 
				
			|||||||
                      aria-label="manage employee"
 | 
					                      aria-label="manage employee"
 | 
				
			||||||
                      type="reset"
 | 
					                      type="reset"
 | 
				
			||||||
                      className="btn btn-sm btn-primary ms-2"
 | 
					                      className="btn btn-sm btn-primary ms-2"
 | 
				
			||||||
 | 
					                      disabled={isloading}
 | 
				
			||||||
                    >
 | 
					                    >
 | 
				
			||||||
                      Clear
 | 
					                      Clear
 | 
				
			||||||
                    </button>
 | 
					                    </button>
 | 
				
			||||||
 | 
				
			|||||||
@ -3,7 +3,7 @@ const Footer = () => {
 | 
				
			|||||||
      <>
 | 
					      <>
 | 
				
			||||||
                  <footer className="content-footer footer bg-footer-theme">
 | 
					                  <footer className="content-footer footer bg-footer-theme">
 | 
				
			||||||
                   <div className="container-xxl d-flex flex-wrap justify-content-between py-2 flex-md-row flex-column">
 | 
					                   <div className="container-xxl d-flex flex-wrap justify-content-between py-2 flex-md-row flex-column">
 | 
				
			||||||
                  <div className="mb-2 mb-md-0" style={{width: "100%", textAlign: "right"}}>
 | 
					                  <div className="mb-2 mb-md-0 small-text" style={{width: "100%", textAlign: "right"}}>
 | 
				
			||||||
                  © {new Date().getFullYear()}
 | 
					                  © {new Date().getFullYear()}
 | 
				
			||||||
                  , by <a href="https://marcosolutions.co.in/" target="_blank" className="font-weight-light footer-link">MARCO AIoT Technologies Pvt. Ltd.</a>
 | 
					                  , by <a href="https://marcosolutions.co.in/" target="_blank" className="font-weight-light footer-link">MARCO AIoT Technologies Pvt. Ltd.</a>
 | 
				
			||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
 | 
				
			|||||||
@ -602,7 +602,7 @@ const Header = () => {
 | 
				
			|||||||
                  <span className="align-middle">My Profile</span>
 | 
					                  <span className="align-middle">My Profile</span>
 | 
				
			||||||
                </a>
 | 
					                </a>
 | 
				
			||||||
              </li>
 | 
					              </li>
 | 
				
			||||||
              <li>
 | 
					              <li onClick={handleProfilePage}>
 | 
				
			||||||
                <a
 | 
					                <a
 | 
				
			||||||
                  aria-label="go to setting "
 | 
					                  aria-label="go to setting "
 | 
				
			||||||
                  className="dropdown-item cusor-pointer"
 | 
					                  className="dropdown-item cusor-pointer"
 | 
				
			||||||
@ -611,7 +611,7 @@ const Header = () => {
 | 
				
			|||||||
                  <span className="align-middle">Settings</span>
 | 
					                  <span className="align-middle">Settings</span>
 | 
				
			||||||
                </a>
 | 
					                </a>
 | 
				
			||||||
              </li>
 | 
					              </li>
 | 
				
			||||||
              <li>
 | 
					              {/* <li>
 | 
				
			||||||
                <a
 | 
					                <a
 | 
				
			||||||
                  aria-label="go to billing "
 | 
					                  aria-label="go to billing "
 | 
				
			||||||
                  className="dropdown-item cusor-pointer"
 | 
					                  className="dropdown-item cusor-pointer"
 | 
				
			||||||
@ -626,8 +626,10 @@ const Header = () => {
 | 
				
			|||||||
                    </span>
 | 
					                    </span>
 | 
				
			||||||
                  </span>
 | 
					                  </span>
 | 
				
			||||||
                </a>
 | 
					                </a>
 | 
				
			||||||
              </li>
 | 
					              </li> */}
 | 
				
			||||||
              <li onClick={openChangePassword}> {/* Use the function from the context */}
 | 
					              <li onClick={openChangePassword}>
 | 
				
			||||||
 | 
					                {" "}
 | 
				
			||||||
 | 
					                {/* Use the function from the context */}
 | 
				
			||||||
                <a
 | 
					                <a
 | 
				
			||||||
                  aria-label="go to profile"
 | 
					                  aria-label="go to profile"
 | 
				
			||||||
                  className="dropdown-item cusor-pointer"
 | 
					                  className="dropdown-item cusor-pointer"
 | 
				
			||||||
 | 
				
			|||||||
@ -26,6 +26,7 @@ const ManageProjectInfo = ({ project, handleSubmitForm, onClose }) => {
 | 
				
			|||||||
    .object({
 | 
					    .object({
 | 
				
			||||||
      ...(project?.id ? { id: z.string().optional() } : {}),
 | 
					      ...(project?.id ? { id: z.string().optional() } : {}),
 | 
				
			||||||
      name: z.string().min(1, { message: "Project Name is required" }),
 | 
					      name: z.string().min(1, { message: "Project Name is required" }),
 | 
				
			||||||
 | 
					      shortName: z.string().optional(),
 | 
				
			||||||
      contactPerson: z
 | 
					      contactPerson: z
 | 
				
			||||||
        .string()
 | 
					        .string()
 | 
				
			||||||
        .min( 1, {message: "Contact Person Name is required"} )
 | 
					        .min( 1, {message: "Contact Person Name is required"} )
 | 
				
			||||||
@ -72,6 +73,7 @@ const ManageProjectInfo = ({ project, handleSubmitForm, onClose }) => {
 | 
				
			|||||||
    defaultValues: {
 | 
					    defaultValues: {
 | 
				
			||||||
      id: project?.id || "",
 | 
					      id: project?.id || "",
 | 
				
			||||||
      name: project?.name || "",
 | 
					      name: project?.name || "",
 | 
				
			||||||
 | 
					      shortName: project?.shortName || "",
 | 
				
			||||||
      contactPerson: project?.contactPerson || "",
 | 
					      contactPerson: project?.contactPerson || "",
 | 
				
			||||||
      projectAddress: project?.projectAddress || "",
 | 
					      projectAddress: project?.projectAddress || "",
 | 
				
			||||||
      startDate: formatDate(project?.startDate) || currentDate,
 | 
					      startDate: formatDate(project?.startDate) || currentDate,
 | 
				
			||||||
@ -88,6 +90,7 @@ const ManageProjectInfo = ({ project, handleSubmitForm, onClose }) => {
 | 
				
			|||||||
        ? {
 | 
					        ? {
 | 
				
			||||||
            id: project?.id || "",
 | 
					            id: project?.id || "",
 | 
				
			||||||
            name: project?.name || "",
 | 
					            name: project?.name || "",
 | 
				
			||||||
 | 
					            shortName: project?.shortName || "",
 | 
				
			||||||
            contactPerson: project?.contactPerson || "",
 | 
					            contactPerson: project?.contactPerson || "",
 | 
				
			||||||
            projectAddress: project?.projectAddress || "",
 | 
					            projectAddress: project?.projectAddress || "",
 | 
				
			||||||
            startDate: formatDate(project?.startDate) || "",
 | 
					            startDate: formatDate(project?.startDate) || "",
 | 
				
			||||||
@ -108,6 +111,7 @@ const ManageProjectInfo = ({ project, handleSubmitForm, onClose }) => {
 | 
				
			|||||||
    reset({
 | 
					    reset({
 | 
				
			||||||
      id: project?.id || "",
 | 
					      id: project?.id || "",
 | 
				
			||||||
      name: project?.name || "",
 | 
					      name: project?.name || "",
 | 
				
			||||||
 | 
					      shortName: project?.shortName || "",
 | 
				
			||||||
      contactPerson: project?.contactPerson || "",
 | 
					      contactPerson: project?.contactPerson || "",
 | 
				
			||||||
      projectAddress: project?.projectAddress || "",
 | 
					      projectAddress: project?.projectAddress || "",
 | 
				
			||||||
      startDate: formatDate(project?.startDate) || currentDate,
 | 
					      startDate: formatDate(project?.startDate) || currentDate,
 | 
				
			||||||
@ -157,6 +161,27 @@ const ManageProjectInfo = ({ project, handleSubmitForm, onClose }) => {
 | 
				
			|||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
              )}
 | 
					              )}
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
 | 
					            <div className="col-12 col-md-12">
 | 
				
			||||||
 | 
					              <label className="form-label" htmlFor="shortName">
 | 
				
			||||||
 | 
					                Short Name
 | 
				
			||||||
 | 
					              </label>
 | 
				
			||||||
 | 
					              <input
 | 
				
			||||||
 | 
					                type="text"
 | 
				
			||||||
 | 
					                id="shortName"
 | 
				
			||||||
 | 
					                name="shortName"
 | 
				
			||||||
 | 
					                className="form-control"
 | 
				
			||||||
 | 
					                placeholder="Short Name"
 | 
				
			||||||
 | 
					                {...register("shortName")}
 | 
				
			||||||
 | 
					              />
 | 
				
			||||||
 | 
					              {errors.shortName && (
 | 
				
			||||||
 | 
					                <div
 | 
				
			||||||
 | 
					                  className="danger-text text-start"
 | 
				
			||||||
 | 
					                  style={{ fontSize: "12px" }}
 | 
				
			||||||
 | 
					                >
 | 
				
			||||||
 | 
					                  {errors.shortName.message}
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					              )}
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
            <div className="col-12 col-md-12">
 | 
					            <div className="col-12 col-md-12">
 | 
				
			||||||
              <label className="form-label" htmlFor="contactPerson">
 | 
					              <label className="form-label" htmlFor="contactPerson">
 | 
				
			||||||
                Contact Person
 | 
					                Contact Person
 | 
				
			||||||
 | 
				
			|||||||
@ -1,19 +1,17 @@
 | 
				
			|||||||
import React, { useState,useEffect } from "react";
 | 
					import React, { useState, useEffect } from "react";
 | 
				
			||||||
import ManageProjectInfo from "./ManageProjectInfo";
 | 
					import ManageProjectInfo from "./ManageProjectInfo";
 | 
				
			||||||
import showToast from "../../services/toastService";
 | 
					import showToast from "../../services/toastService";
 | 
				
			||||||
import ProjectRepository from "../../repositories/ProjectRepository";
 | 
					import ProjectRepository from "../../repositories/ProjectRepository";
 | 
				
			||||||
import { cacheData,getCachedData } from "../../slices/apiDataManager";
 | 
					import { cacheData, getCachedData } from "../../slices/apiDataManager";
 | 
				
			||||||
import {hasUserPermission} from "../../utils/authUtils";
 | 
					import { hasUserPermission } from "../../utils/authUtils";
 | 
				
			||||||
import moment from "moment";
 | 
					import moment from "moment";
 | 
				
			||||||
import {useHasUserPermission} from "../../hooks/useHasUserPermission";
 | 
					import { useHasUserPermission } from "../../hooks/useHasUserPermission";
 | 
				
			||||||
import {MANAGE_PROJECT} from "../../utils/constants";
 | 
					import { MANAGE_PROJECT } from "../../utils/constants";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const ProjectBanner = ({ project_data }) => {
 | 
				
			||||||
const ProjectBanner = ( {project_data} ) =>
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
  const [showModal, setShowModal] = useState(false);
 | 
					  const [showModal, setShowModal] = useState(false);
 | 
				
			||||||
  const manageProject =  useHasUserPermission(MANAGE_PROJECT)
 | 
					  const manageProject = useHasUserPermission(MANAGE_PROJECT);
 | 
				
			||||||
  const [ CurrentProject, setCurrentProject ] = useState( project_data )
 | 
					  const [CurrentProject, setCurrentProject] = useState(project_data);
 | 
				
			||||||
  if (project_data == null) {
 | 
					  if (project_data == null) {
 | 
				
			||||||
    return <span>incomplete project information</span>;
 | 
					    return <span>incomplete project information</span>;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
@ -21,60 +19,57 @@ const ProjectBanner = ( {project_data} ) =>
 | 
				
			|||||||
  const handleShow = () => setShowModal(true);
 | 
					  const handleShow = () => setShowModal(true);
 | 
				
			||||||
  const handleClose = () => setShowModal(false);
 | 
					  const handleClose = () => setShowModal(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const handleFormSubmit = ( updatedProject,setLoading ) =>
 | 
					  const handleFormSubmit = (updatedProject, setLoading) => {
 | 
				
			||||||
  {
 | 
					    if (CurrentProject?.id) {
 | 
				
			||||||
 | 
					      ProjectRepository.updateProject(CurrentProject.id, updatedProject)
 | 
				
			||||||
 | 
					        .then((response) => {
 | 
				
			||||||
 | 
					          const updatedProjectData = {
 | 
				
			||||||
 | 
					            ...CurrentProject,
 | 
				
			||||||
 | 
					            ...response.data,
 | 
				
			||||||
 | 
					            building: CurrentProject.building,
 | 
				
			||||||
 | 
					          };
 | 
				
			||||||
 | 
					          setCurrentProject(updatedProject);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
         if ( CurrentProject?.id )
 | 
					          cacheData(`projectinfo-${CurrentProject.id}`, updatedProjectData);
 | 
				
			||||||
            {
 | 
					          const projects_list = getCachedData("projectslist");
 | 
				
			||||||
                 ProjectRepository.updateProject(CurrentProject.id,updatedProject).then( ( response ) =>
 | 
					          if (projects_list) {
 | 
				
			||||||
                 { 
 | 
					            const updatedProjectsList = projects_list.map((project) =>
 | 
				
			||||||
                   const updatedProjectData = {
 | 
					              project.id === CurrentProject.id
 | 
				
			||||||
                     ...CurrentProject, 
 | 
					                ? {
 | 
				
			||||||
                     ...response.data, 
 | 
					                    ...project,
 | 
				
			||||||
                     building: CurrentProject.building, 
 | 
					                    ...response.data,
 | 
				
			||||||
                   };
 | 
					                    //  tenant:project.tenant
 | 
				
			||||||
                   setCurrentProject( updatedProject )
 | 
					                  }
 | 
				
			||||||
 | 
					                : project
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                   cacheData( `projectinfo-${ CurrentProject.id }`, updatedProjectData );
 | 
					            cacheData("projectslist", updatedProjectsList);
 | 
				
			||||||
                   const projects_list = getCachedData("projectslist");
 | 
					 | 
				
			||||||
                   if ( projects_list )
 | 
					 | 
				
			||||||
                   {
 | 
					 | 
				
			||||||
                    const updatedProjectsList = projects_list.map(project => 
 | 
					 | 
				
			||||||
                      project.id === CurrentProject.id ? {
 | 
					 | 
				
			||||||
                        ...project,       
 | 
					 | 
				
			||||||
                         ...response.data,
 | 
					 | 
				
			||||||
                        //  tenant:project.tenant
 | 
					 | 
				
			||||||
                      } : project
 | 
					 | 
				
			||||||
                     );
 | 
					 | 
				
			||||||
                     
 | 
					 | 
				
			||||||
                     cacheData("projectslist",updatedProjectsList)
 | 
					 | 
				
			||||||
                   }
 | 
					 | 
				
			||||||
                   
 | 
					 | 
				
			||||||
                     showToast( "Project updated successfully.", "success" );
 | 
					 | 
				
			||||||
                     setLoading(false)
 | 
					 | 
				
			||||||
                     setShowModal(false)
 | 
					 | 
				
			||||||
                   })
 | 
					 | 
				
			||||||
                   .catch((error) => {
 | 
					 | 
				
			||||||
                     showToast( error.message, "error" );
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                   });       
 | 
					 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          showToast("Project updated successfully.", "success");
 | 
				
			||||||
 | 
					          setLoading(false);
 | 
				
			||||||
 | 
					          setShowModal(false);
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					        .catch((error) => {
 | 
				
			||||||
 | 
					          showToast(error.message, "error");
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <>
 | 
					    <>
 | 
				
			||||||
      <div
 | 
					      <div
 | 
				
			||||||
        className={`modal fade ${showModal ? 'show' : ''}`}
 | 
					        className={`modal fade ${showModal ? "show" : ""}`}
 | 
				
			||||||
        tabIndex="-1"
 | 
					        tabIndex="-1"
 | 
				
			||||||
        role="dialog"
 | 
					        role="dialog"
 | 
				
			||||||
        style={{ display: showModal ? 'block' : 'none' }}
 | 
					        style={{ display: showModal ? "block" : "none" }}
 | 
				
			||||||
        aria-hidden={!showModal}
 | 
					        aria-hidden={!showModal}
 | 
				
			||||||
      >
 | 
					      >
 | 
				
			||||||
         <ManageProjectInfo
 | 
					        <ManageProjectInfo
 | 
				
			||||||
            project={CurrentProject} 
 | 
					          project={CurrentProject}
 | 
				
			||||||
            handleSubmitForm={handleFormSubmit}
 | 
					          handleSubmitForm={handleFormSubmit}
 | 
				
			||||||
            onClose={handleClose}
 | 
					          onClose={handleClose}
 | 
				
			||||||
          ></ManageProjectInfo>
 | 
					        ></ManageProjectInfo>
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
      {/* Project Banner */}
 | 
					      {/* Project Banner */}
 | 
				
			||||||
      <div className="col-12">
 | 
					      <div className="col-12">
 | 
				
			||||||
@ -89,17 +84,23 @@ const ProjectBanner = ( {project_data} ) =>
 | 
				
			|||||||
                style={{ width: "40px", height: "40px" }}
 | 
					                style={{ width: "40px", height: "40px" }}
 | 
				
			||||||
              />
 | 
					              />
 | 
				
			||||||
              <h5 className="mb-0">
 | 
					              <h5 className="mb-0">
 | 
				
			||||||
                {CurrentProject.name ? CurrentProject.name : "N/A"}
 | 
					                {CurrentProject.name
 | 
				
			||||||
 | 
					                  ? CurrentProject.shortName
 | 
				
			||||||
 | 
					                    ? `${CurrentProject.name} (${CurrentProject.shortName})`
 | 
				
			||||||
 | 
					                    : CurrentProject.name
 | 
				
			||||||
 | 
					                  : "N/A"}
 | 
				
			||||||
              </h5>
 | 
					              </h5>
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
            {manageProject && (
 | 
					            {manageProject && (
 | 
				
			||||||
              <button
 | 
					              <button
 | 
				
			||||||
                  type="button"
 | 
					                type="button"
 | 
				
			||||||
                  className={`btn btn-sm btn-primary ${!manageProject && 'd-none'}`}
 | 
					                className={`btn btn-sm btn-primary ${
 | 
				
			||||||
                  data-bs-toggle="modal"
 | 
					                  !manageProject && "d-none"
 | 
				
			||||||
                  data-bs-target="#edit-project-modal"
 | 
					                }`}
 | 
				
			||||||
                  onClick={handleShow}
 | 
					                data-bs-toggle="modal"
 | 
				
			||||||
                >
 | 
					                data-bs-target="#edit-project-modal"
 | 
				
			||||||
 | 
					                onClick={handleShow}
 | 
				
			||||||
 | 
					              >
 | 
				
			||||||
                Modify
 | 
					                Modify
 | 
				
			||||||
              </button>
 | 
					              </button>
 | 
				
			||||||
            )}
 | 
					            )}
 | 
				
			||||||
 | 
				
			|||||||
@ -14,7 +14,7 @@ import {
 | 
				
			|||||||
  getProjectStatusName,
 | 
					  getProjectStatusName,
 | 
				
			||||||
} from "../../utils/projectStatus";
 | 
					} from "../../utils/projectStatus";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const ProjectCard = ({ projectData }) => {
 | 
					const ProjectCard = ({ projectData, recall }) => {
 | 
				
			||||||
  const [projectInfo, setProjectInfo] = useState(projectData);
 | 
					  const [projectInfo, setProjectInfo] = useState(projectData);
 | 
				
			||||||
  const [projectDetails, setProjectDetails] = useState(null);
 | 
					  const [projectDetails, setProjectDetails] = useState(null);
 | 
				
			||||||
  const [showModal, setShowModal] = useState(false);
 | 
					  const [showModal, setShowModal] = useState(false);
 | 
				
			||||||
@ -78,7 +78,7 @@ const ProjectCard = ({ projectData }) => {
 | 
				
			|||||||
            );
 | 
					            );
 | 
				
			||||||
            cacheData("projectslist", updatedProjectsList);
 | 
					            cacheData("projectslist", updatedProjectsList);
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
 | 
					          recall(getCachedData("projectslist"));
 | 
				
			||||||
          showToast("Project updated successfully.", "success");
 | 
					          showToast("Project updated successfully.", "success");
 | 
				
			||||||
          setShowModal(false);
 | 
					          setShowModal(false);
 | 
				
			||||||
        })
 | 
					        })
 | 
				
			||||||
@ -88,7 +88,6 @@ const ProjectCard = ({ projectData }) => {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <>
 | 
					    <>
 | 
				
			||||||
      {showModal && projectDetails && (
 | 
					      {showModal && projectDetails && (
 | 
				
			||||||
@ -119,17 +118,16 @@ const ProjectCard = ({ projectData }) => {
 | 
				
			|||||||
                  ></i>
 | 
					                  ></i>
 | 
				
			||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
                <div className="me-2">
 | 
					                <div className="me-2">
 | 
				
			||||||
                  <h5 className="mb-0">
 | 
					                  <h5
 | 
				
			||||||
                    <a
 | 
					                    className="mb-0 stretched-link text-heading text-start"
 | 
				
			||||||
                      className="stretched-link text-heading"
 | 
					                    onClick={handleViewProject}
 | 
				
			||||||
                      onClick={handleViewProject}
 | 
					                  >
 | 
				
			||||||
                    >
 | 
					                    {projectInfo.shortName
 | 
				
			||||||
                      {projectInfo.name}
 | 
					                      ? projectInfo.shortName
 | 
				
			||||||
                    </a>
 | 
					                      : projectInfo.name}
 | 
				
			||||||
                  </h5>
 | 
					                  </h5>
 | 
				
			||||||
                  <div className="client-info text-body">
 | 
					                  <div className="client-info text-body">
 | 
				
			||||||
                    <span className="fw-medium">Client: </span>
 | 
					                    <span>{projectInfo.shortName ? projectInfo.name : ""}</span>
 | 
				
			||||||
                    <span>{projectInfo.contactPerson}</span>
 | 
					 | 
				
			||||||
                  </div>
 | 
					                  </div>
 | 
				
			||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
              </div>
 | 
					              </div>
 | 
				
			||||||
@ -141,9 +139,14 @@ const ProjectCard = ({ projectData }) => {
 | 
				
			|||||||
                    data-bs-toggle="dropdown"
 | 
					                    data-bs-toggle="dropdown"
 | 
				
			||||||
                    aria-expanded="false"
 | 
					                    aria-expanded="false"
 | 
				
			||||||
                  >
 | 
					                  >
 | 
				
			||||||
                    {modifyProjectLoading? <div class="spinner-border spinner-border-sm text-secondary" role="status">
 | 
					                    {modifyProjectLoading ? (
 | 
				
			||||||
                      <span class="visually-hidden">Loading...</span>
 | 
					                      <div
 | 
				
			||||||
                    </div> :
 | 
					                        className="spinner-border spinner-border-sm text-secondary"
 | 
				
			||||||
 | 
					                        role="status"
 | 
				
			||||||
 | 
					                      >
 | 
				
			||||||
 | 
					                        <span className="visually-hidden">Loading...</span>
 | 
				
			||||||
 | 
					                      </div>
 | 
				
			||||||
 | 
					                    ) : (
 | 
				
			||||||
                      <i
 | 
					                      <i
 | 
				
			||||||
                        className="bx bx-dots-vertical-rounded bx-sm text-muted"
 | 
					                        className="bx bx-dots-vertical-rounded bx-sm text-muted"
 | 
				
			||||||
                        data-bs-toggle="tooltip"
 | 
					                        data-bs-toggle="tooltip"
 | 
				
			||||||
@ -151,7 +154,8 @@ const ProjectCard = ({ projectData }) => {
 | 
				
			|||||||
                        data-bs-placement="top"
 | 
					                        data-bs-placement="top"
 | 
				
			||||||
                        data-bs-custom-class="tooltip-dark"
 | 
					                        data-bs-custom-class="tooltip-dark"
 | 
				
			||||||
                        title="More Action"
 | 
					                        title="More Action"
 | 
				
			||||||
                      ></i>}
 | 
					                      ></i>
 | 
				
			||||||
 | 
					                    )}
 | 
				
			||||||
                  </button>
 | 
					                  </button>
 | 
				
			||||||
                  <ul className="dropdown-menu dropdown-menu-end">
 | 
					                  <ul className="dropdown-menu dropdown-menu-end">
 | 
				
			||||||
                    <li>
 | 
					                    <li>
 | 
				
			||||||
@ -191,6 +195,12 @@ const ProjectCard = ({ projectData }) => {
 | 
				
			|||||||
          <div className="card-body pb-1">
 | 
					          <div className="card-body pb-1">
 | 
				
			||||||
            <div className="d-flex align-items-center flex-wrap">
 | 
					            <div className="d-flex align-items-center flex-wrap">
 | 
				
			||||||
              <div className="text-start mb-4">
 | 
					              <div className="text-start mb-4">
 | 
				
			||||||
 | 
					                <p className="mb-1">
 | 
				
			||||||
 | 
					                  <span className="text-heading fw-medium">
 | 
				
			||||||
 | 
					                    Contact Person:{" "}
 | 
				
			||||||
 | 
					                  </span>
 | 
				
			||||||
 | 
					                  {projectInfo.contactPerson ? projectInfo.contactPerson : "NA"}
 | 
				
			||||||
 | 
					                </p>
 | 
				
			||||||
                <p className="mb-1">
 | 
					                <p className="mb-1">
 | 
				
			||||||
                  <span className="text-heading fw-medium">Start Date: </span>
 | 
					                  <span className="text-heading fw-medium">Start Date: </span>
 | 
				
			||||||
                  {projectInfo.startDate
 | 
					                  {projectInfo.startDate
 | 
				
			||||||
 | 
				
			|||||||
@ -1,10 +1,13 @@
 | 
				
			|||||||
import React from "react";
 | 
					import React from "react";
 | 
				
			||||||
import { hasUserPermission } from "../../utils/authUtils";
 | 
					import { hasUserPermission } from "../../utils/authUtils";
 | 
				
			||||||
import { useHasUserPermission } from "../../hooks/useHasUserPermission";
 | 
					import { useHasUserPermission } from "../../hooks/useHasUserPermission";
 | 
				
			||||||
import { VIEW_PROJECT_INFRA } from "../../utils/constants";
 | 
					import { DIRECTORY_ADMIN, DIRECTORY_MANAGER, DIRECTORY_USER, VIEW_PROJECT_INFRA } from "../../utils/constants";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const ProjectNav = ({ onPillClick, activePill }) => {
 | 
					const ProjectNav = ({ onPillClick, activePill }) => {
 | 
				
			||||||
  const HasViewInfraStructure = useHasUserPermission(VIEW_PROJECT_INFRA);
 | 
					  const HasViewInfraStructure = useHasUserPermission( VIEW_PROJECT_INFRA );
 | 
				
			||||||
 | 
					  const DirAdmin = useHasUserPermission(DIRECTORY_ADMIN);
 | 
				
			||||||
 | 
					  const DireManager = useHasUserPermission(DIRECTORY_MANAGER)
 | 
				
			||||||
 | 
					  const DirUser = useHasUserPermission(DIRECTORY_USER)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <div className="nav-align-top">
 | 
					    <div className="nav-align-top">
 | 
				
			||||||
@ -73,7 +76,8 @@ const ProjectNav = ({ onPillClick, activePill }) => {
 | 
				
			|||||||
            <i className="bx bxs-file-image bx-sm me-1_5"></i> <span className="d-none d-md-inline">Image Gallary</span>
 | 
					            <i className="bx bxs-file-image bx-sm me-1_5"></i> <span className="d-none d-md-inline">Image Gallary</span>
 | 
				
			||||||
          </a>
 | 
					          </a>
 | 
				
			||||||
        </li>
 | 
					        </li>
 | 
				
			||||||
        <li className="nav-item">
 | 
					        {(DirAdmin || DireManager || DirUser) && (
 | 
				
			||||||
 | 
					            <li className="nav-item">
 | 
				
			||||||
          <a
 | 
					          <a
 | 
				
			||||||
            className={`nav-link ${activePill === "directory" ? "active" : ""}`}
 | 
					            className={`nav-link ${activePill === "directory" ? "active" : ""}`}
 | 
				
			||||||
            href="#"
 | 
					            href="#"
 | 
				
			||||||
@ -85,6 +89,8 @@ const ProjectNav = ({ onPillClick, activePill }) => {
 | 
				
			|||||||
           <i className='bx bxs-contact bx-sm me-1_5'></i> <span className="d-none d-md-inline">Directory</span>
 | 
					           <i className='bx bxs-contact bx-sm me-1_5'></i> <span className="d-none d-md-inline">Directory</span>
 | 
				
			||||||
          </a>
 | 
					          </a>
 | 
				
			||||||
        </li>
 | 
					        </li>
 | 
				
			||||||
 | 
					        )}
 | 
				
			||||||
 | 
					      
 | 
				
			||||||
      </ul>
 | 
					      </ul>
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
 | 
				
			|||||||
@ -10,7 +10,7 @@ function hashString(str) {
 | 
				
			|||||||
  return hash;
 | 
					  return hash;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const Avatar = ({ firstName, lastName, size = "sm" }) => {
 | 
					const Avatar = ({ firstName, lastName, size = "sm", classAvatar }) => {
 | 
				
			||||||
  // Combine firstName and lastName to create a unique string for hashing
 | 
					  // Combine firstName and lastName to create a unique string for hashing
 | 
				
			||||||
  const fullName = `${firstName} ${lastName}`;
 | 
					  const fullName = `${firstName} ${lastName}`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -51,7 +51,7 @@ const Avatar = ({ firstName, lastName, size = "sm" }) => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <div className="avatar-wrapper p-1">
 | 
					    <div className="avatar-wrapper p-1">
 | 
				
			||||||
      <div className={`avatar avatar-${size} me-2`}>
 | 
					     <div className={`avatar avatar-${size} me-2 ${classAvatar}`}>
 | 
				
			||||||
        <span className={`avatar-initial rounded-circle ${bgClass}`}>
 | 
					        <span className={`avatar-initial rounded-circle ${bgClass}`}>
 | 
				
			||||||
          {generateAvatarText(firstName, lastName)}
 | 
					          {generateAvatarText(firstName, lastName)}
 | 
				
			||||||
        </span>
 | 
					        </span>
 | 
				
			||||||
 | 
				
			|||||||
@ -3,30 +3,32 @@ import { useNavigate } from "react-router-dom";
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
const Breadcrumb = ({ data }) => {
 | 
					const Breadcrumb = ({ data }) => {
 | 
				
			||||||
  const navigate = useNavigate();
 | 
					  const navigate = useNavigate();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <nav aria-label="breadcrumb">
 | 
					    <nav aria-label="breadcrumb">
 | 
				
			||||||
      <ol className="breadcrumb breadcrumb-custom-icon">
 | 
					      <ol className="breadcrumb breadcrumb-custom-icon">
 | 
				
			||||||
        {data.map((item, index) =>
 | 
					        {data.map((item, index) => (
 | 
				
			||||||
          item.link ? (
 | 
					          item.link ? (
 | 
				
			||||||
            <li className="breadcrumb-item cursor-pointer" key={index}>
 | 
					            <li className="breadcrumb-item cursor-pointer" key={index}>
 | 
				
			||||||
              <a
 | 
					              <a
 | 
				
			||||||
                aria-label="breadcrumb link link-underline-primary "
 | 
					                role="button"
 | 
				
			||||||
 | 
					                tabIndex={0}
 | 
				
			||||||
 | 
					                aria-label="breadcrumb link"
 | 
				
			||||||
                onClick={() => navigate(item.link)}
 | 
					                onClick={() => navigate(item.link)}
 | 
				
			||||||
 | 
					                onKeyDown={(e) => {
 | 
				
			||||||
 | 
					                  if (e.key === 'Enter') navigate(item.link);
 | 
				
			||||||
 | 
					                }}
 | 
				
			||||||
              >
 | 
					              >
 | 
				
			||||||
                {item.label}
 | 
					                {item.label}
 | 
				
			||||||
              </a>
 | 
					              </a>
 | 
				
			||||||
              <i className="breadcrumb-icon icon-base bx bx-chevron-right align-middle"></i>
 | 
					              <i className="breadcrumb-icon icon-base bx bx-chevron-right align-middle"></i>
 | 
				
			||||||
            </li>
 | 
					            </li>
 | 
				
			||||||
          ) : (
 | 
					          ) : (
 | 
				
			||||||
            <li
 | 
					            <li className="breadcrumb-item active" key={index}>
 | 
				
			||||||
              className="breadcrumb-item active "
 | 
					 | 
				
			||||||
              key={new Date().getMilliseconds()}
 | 
					 | 
				
			||||||
            >
 | 
					 | 
				
			||||||
              {" "}
 | 
					 | 
				
			||||||
              {item.label}
 | 
					              {item.label}
 | 
				
			||||||
            </li>
 | 
					            </li>
 | 
				
			||||||
          )
 | 
					          )
 | 
				
			||||||
        )}
 | 
					        ))}
 | 
				
			||||||
      </ol>
 | 
					      </ol>
 | 
				
			||||||
    </nav>
 | 
					    </nav>
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
 | 
				
			|||||||
@ -1,90 +1,98 @@
 | 
				
			|||||||
import React, { useState, useEffect } from "react";
 | 
					import React, { useState, useEffect } from "react";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const FilterIcon = ({ taskListData, onApplyFilters, currentSelectedBuilding, currentSelectedFloors, currentSelectedActivities }) => {
 | 
					const FilterIcon = ({
 | 
				
			||||||
  // State for filters, now managed within FilterIcon
 | 
					  taskListData,
 | 
				
			||||||
  const [selectedBuilding, setSelectedBuilding] = useState(currentSelectedBuilding);
 | 
					  onApplyFilters,
 | 
				
			||||||
  const [selectedFloors, setSelectedFloors] = useState(currentSelectedFloors);
 | 
					  currentSelectedBuilding,
 | 
				
			||||||
  const [selectedActivities, setSelectedActivities] = useState(currentSelectedActivities);
 | 
					  currentSelectedFloors,
 | 
				
			||||||
 | 
					  currentSelectedActivities,
 | 
				
			||||||
 | 
					}) => {
 | 
				
			||||||
 | 
					  const [selectedBuilding, setSelectedBuilding] = useState(currentSelectedBuilding || "");
 | 
				
			||||||
 | 
					  const [selectedFloors, setSelectedFloors] = useState(currentSelectedFloors || []);
 | 
				
			||||||
 | 
					  const [selectedActivities, setSelectedActivities] = useState(currentSelectedActivities || []);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Update internal state when props change (e.g., project selection in DailyTask clears filters)
 | 
					 | 
				
			||||||
  useEffect(() => {
 | 
					  useEffect(() => {
 | 
				
			||||||
    setSelectedBuilding(currentSelectedBuilding);
 | 
					    setSelectedBuilding(currentSelectedBuilding || "");
 | 
				
			||||||
    setSelectedFloors(currentSelectedFloors);
 | 
					    setSelectedFloors(currentSelectedFloors || []);
 | 
				
			||||||
    setSelectedActivities(currentSelectedActivities);
 | 
					    setSelectedActivities(currentSelectedActivities || []);
 | 
				
			||||||
  }, [currentSelectedBuilding, currentSelectedFloors, currentSelectedActivities]);
 | 
					  }, [currentSelectedBuilding, currentSelectedFloors, currentSelectedActivities]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Helper to get unique values for filters based on current selections
 | 
					  const getUniqueFilterValues = (key, overrideBuilding, overrideFloors) => {
 | 
				
			||||||
  const getUniqueFilterValues = (key) => {
 | 
					 | 
				
			||||||
    if (!taskListData) return [];
 | 
					    if (!taskListData) return [];
 | 
				
			||||||
    let relevantTasks = taskListData;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Filter tasks based on selected building for floors and activities
 | 
					    let filteredTasks = [...taskListData];
 | 
				
			||||||
    if (selectedBuilding) {
 | 
					
 | 
				
			||||||
      relevantTasks = relevantTasks.filter(task =>
 | 
					    if (overrideBuilding) {
 | 
				
			||||||
        task?.workItem?.workArea?.floor?.building?.name === selectedBuilding
 | 
					      filteredTasks = filteredTasks.filter(
 | 
				
			||||||
 | 
					        (task) =>
 | 
				
			||||||
 | 
					          task?.workItem?.workArea?.floor?.building?.name === overrideBuilding
 | 
				
			||||||
      );
 | 
					      );
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Filter tasks based on selected floors for activities
 | 
					    if (overrideFloors?.length > 0) {
 | 
				
			||||||
    if (selectedFloors.length > 0) {
 | 
					      filteredTasks = filteredTasks.filter((task) =>
 | 
				
			||||||
      relevantTasks = relevantTasks.filter(task =>
 | 
					        overrideFloors.includes(task?.workItem?.workArea?.floor?.floorName)
 | 
				
			||||||
        selectedFloors.includes(task?.workItem?.workArea?.floor?.floorName)
 | 
					 | 
				
			||||||
      );
 | 
					      );
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const values = relevantTasks.map(task => {
 | 
					    const values = filteredTasks.map((task) => {
 | 
				
			||||||
      if (key === 'building') return task?.workItem?.workArea?.floor?.building?.name;
 | 
					      if (key === "building") return task?.workItem?.workArea?.floor?.building?.name;
 | 
				
			||||||
      if (key === 'floor') return task?.workItem?.workArea?.floor?.floorName;
 | 
					      if (key === "floor") return task?.workItem?.workArea?.floor?.floorName;
 | 
				
			||||||
      if (key === 'activity') return task?.workItem?.activityMaster?.activityName;
 | 
					      if (key === "activity") return task?.workItem?.activityMaster?.activityName;
 | 
				
			||||||
      return null;
 | 
					      return null;
 | 
				
			||||||
    }).filter(Boolean); // Remove null or undefined values
 | 
					    });
 | 
				
			||||||
    return [...new Set(values)].sort(); // Sort for consistent order
 | 
					
 | 
				
			||||||
 | 
					    return [...new Set(values.filter(Boolean))].sort();
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const uniqueBuildings = getUniqueFilterValues('building');
 | 
					  const uniqueBuildings = getUniqueFilterValues("building");
 | 
				
			||||||
  const uniqueFloors = getUniqueFilterValues('floor');
 | 
					  const uniqueFloors = getUniqueFilterValues("floor", selectedBuilding);
 | 
				
			||||||
  const uniqueActivities = getUniqueFilterValues('activity');
 | 
					  const uniqueActivities = getUniqueFilterValues("activity", selectedBuilding, selectedFloors);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Handle filter selection with dependency logic
 | 
					 | 
				
			||||||
  const handleFilterChange = (filterType, value) => {
 | 
					  const handleFilterChange = (filterType, value) => {
 | 
				
			||||||
    let newSelectedBuilding = selectedBuilding;
 | 
					    let updatedBuilding = selectedBuilding;
 | 
				
			||||||
    let newSelectedFloors = [...selectedFloors];
 | 
					    let updatedFloors = [...selectedFloors];
 | 
				
			||||||
    let newSelectedActivities = [...selectedActivities];
 | 
					    let updatedActivities = [...selectedActivities];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (filterType === 'building') {
 | 
					    if (filterType === "building") {
 | 
				
			||||||
      if (selectedBuilding !== value) {
 | 
					      updatedBuilding = value;
 | 
				
			||||||
        newSelectedFloors = [];
 | 
					      updatedFloors = [];
 | 
				
			||||||
        newSelectedActivities = [];
 | 
					      updatedActivities = [];
 | 
				
			||||||
 | 
					    } else if (filterType === "floor") {
 | 
				
			||||||
 | 
					      if (updatedFloors.includes(value)) {
 | 
				
			||||||
 | 
					        updatedFloors = updatedFloors.filter((floor) => floor !== value);
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        updatedFloors.push(value);
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      newSelectedBuilding = value;
 | 
					
 | 
				
			||||||
    } else if (filterType === 'floor') {
 | 
					      const validActivities = getUniqueFilterValues("activity", updatedBuilding, updatedFloors);
 | 
				
			||||||
      newSelectedFloors = selectedFloors.includes(value) ? selectedFloors.filter(item => item !== value) : [...selectedFloors, value];
 | 
					      updatedActivities = updatedActivities.filter((act) => validActivities.includes(act));
 | 
				
			||||||
      if (!newSelectedFloors.includes(value) && selectedFloors.includes(value)) {
 | 
					    } else if (filterType === "activity") {
 | 
				
			||||||
        newSelectedActivities = [];
 | 
					      if (updatedActivities.includes(value)) {
 | 
				
			||||||
 | 
					        updatedActivities = updatedActivities.filter((act) => act !== value);
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        updatedActivities.push(value);
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    } else if (filterType === 'activity') {
 | 
					 | 
				
			||||||
      newSelectedActivities = selectedActivities.includes(value) ? selectedActivities.filter(item => item !== value) : [...selectedActivities, value];
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    setSelectedBuilding(newSelectedBuilding);
 | 
					    setSelectedBuilding(updatedBuilding);
 | 
				
			||||||
    setSelectedFloors(newSelectedFloors);
 | 
					    setSelectedFloors(updatedFloors);
 | 
				
			||||||
    setSelectedActivities(newSelectedActivities);
 | 
					    setSelectedActivities(updatedActivities);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Communicate the updated filter states back to the parent
 | 
					 | 
				
			||||||
    onApplyFilters({
 | 
					    onApplyFilters({
 | 
				
			||||||
      selectedBuilding: newSelectedBuilding,
 | 
					      selectedBuilding: updatedBuilding,
 | 
				
			||||||
      selectedFloors: newSelectedFloors,
 | 
					      selectedFloors: updatedFloors,
 | 
				
			||||||
      selectedActivities: newSelectedActivities,
 | 
					      selectedActivities: updatedActivities,
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const clearAllFilters = () => {
 | 
					  const clearAllFilters = () => {
 | 
				
			||||||
    setSelectedBuilding('');
 | 
					    setSelectedBuilding("");
 | 
				
			||||||
    setSelectedFloors([]);
 | 
					    setSelectedFloors([]);
 | 
				
			||||||
    setSelectedActivities([]);
 | 
					    setSelectedActivities([]);
 | 
				
			||||||
    // Communicate cleared filters back to the parent
 | 
					
 | 
				
			||||||
    onApplyFilters({
 | 
					    onApplyFilters({
 | 
				
			||||||
      selectedBuilding: '',
 | 
					      selectedBuilding: "",
 | 
				
			||||||
      selectedFloors: [],
 | 
					      selectedFloors: [],
 | 
				
			||||||
      selectedActivities: [],
 | 
					      selectedActivities: [],
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
@ -98,20 +106,19 @@ const FilterIcon = ({ taskListData, onApplyFilters, currentSelectedBuilding, cur
 | 
				
			|||||||
        data-bs-toggle="dropdown"
 | 
					        data-bs-toggle="dropdown"
 | 
				
			||||||
        aria-expanded="false"
 | 
					        aria-expanded="false"
 | 
				
			||||||
      >
 | 
					      >
 | 
				
			||||||
        <i className="fa-solid fa-filter bx-sm "></i>
 | 
					        <i
 | 
				
			||||||
 | 
					          className="fa-solid fa-filter bx-sm"
 | 
				
			||||||
 | 
					          style={{ color: selectedBuilding || selectedFloors.length > 0 || selectedActivities.length > 0 ? "#7161EF" : "gray" }}
 | 
				
			||||||
 | 
					        ></i>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      </a>
 | 
					      </a>
 | 
				
			||||||
      <ul
 | 
					      <ul
 | 
				
			||||||
        className="dropdown-menu p-2"
 | 
					        className="dropdown-menu p-2"
 | 
				
			||||||
        aria-labelledby="filterDropdown"
 | 
					        aria-labelledby="filterDropdown"
 | 
				
			||||||
        style={{
 | 
					        style={{ minWidth: "360px", fontSize: "13px" }}
 | 
				
			||||||
          minWidth: "360px",
 | 
					 | 
				
			||||||
          fontSize: "13px",
 | 
					 | 
				
			||||||
        }}
 | 
					 | 
				
			||||||
        // Prevent dropdown from closing when clicking inside it
 | 
					 | 
				
			||||||
        onClick={(e) => e.stopPropagation()}
 | 
					        onClick={(e) => e.stopPropagation()}
 | 
				
			||||||
      >
 | 
					      >
 | 
				
			||||||
        {/* Building Filter - Now a Dropdown */}
 | 
					        {/* Building */}
 | 
				
			||||||
        <li>
 | 
					        <li>
 | 
				
			||||||
          <div className="fw-bold text-dark mb-1">Building</div>
 | 
					          <div className="fw-bold text-dark mb-1">Building</div>
 | 
				
			||||||
          <div className="row">
 | 
					          <div className="row">
 | 
				
			||||||
@ -122,33 +129,26 @@ const FilterIcon = ({ taskListData, onApplyFilters, currentSelectedBuilding, cur
 | 
				
			|||||||
                onChange={(e) => handleFilterChange("building", e.target.value)}
 | 
					                onChange={(e) => handleFilterChange("building", e.target.value)}
 | 
				
			||||||
              >
 | 
					              >
 | 
				
			||||||
                <option value="">Select Building</option>
 | 
					                <option value="">Select Building</option>
 | 
				
			||||||
                {uniqueBuildings.length > 0 ? (
 | 
					                {uniqueBuildings.map((building, idx) => (
 | 
				
			||||||
                  uniqueBuildings.map((building, idx) => (
 | 
					                  <option key={idx} value={building}>
 | 
				
			||||||
                    <option key={`building-option-${idx}`} value={building}>
 | 
					                    {building}
 | 
				
			||||||
                      {building}
 | 
					                  </option>
 | 
				
			||||||
                    </option>
 | 
					                ))}
 | 
				
			||||||
                  ))
 | 
					 | 
				
			||||||
                ) : (
 | 
					 | 
				
			||||||
                  <option value="" disabled>No buildings available</option>
 | 
					 | 
				
			||||||
                )}
 | 
					 | 
				
			||||||
              </select>
 | 
					              </select>
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
          </div>
 | 
					          </div>
 | 
				
			||||||
        </li>
 | 
					        </li>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        {/* Floor Filter - Visible only if a building is selected */}
 | 
					        {/* Floor */}
 | 
				
			||||||
        {selectedBuilding && (
 | 
					        {selectedBuilding && (
 | 
				
			||||||
          <>
 | 
					          <>
 | 
				
			||||||
 | 
					            <li><hr className="my-1" /></li>
 | 
				
			||||||
            <li>
 | 
					            <li>
 | 
				
			||||||
              <hr className="my-1" />
 | 
					              <div className="fw-bold text-dark mb-1">Floors</div>
 | 
				
			||||||
            </li>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            <li>
 | 
					 | 
				
			||||||
              <div className="fw-bold text-dark mb-1">Floor</div>
 | 
					 | 
				
			||||||
              <div className="row">
 | 
					              <div className="row">
 | 
				
			||||||
                {uniqueFloors.length > 0 ? (
 | 
					                {uniqueFloors.length > 0 ? (
 | 
				
			||||||
                  uniqueFloors.map((floor, idx) => (
 | 
					                  uniqueFloors.map((floor, idx) => (
 | 
				
			||||||
                    <div className="col-6" key={`floor-${idx}`}>
 | 
					                    <div className="col-6" key={idx}>
 | 
				
			||||||
                      <div className="form-check mb-1">
 | 
					                      <div className="form-check mb-1">
 | 
				
			||||||
                        <input
 | 
					                        <input
 | 
				
			||||||
                          className="form-check-input"
 | 
					                          className="form-check-input"
 | 
				
			||||||
@ -157,36 +157,30 @@ const FilterIcon = ({ taskListData, onApplyFilters, currentSelectedBuilding, cur
 | 
				
			|||||||
                          checked={selectedFloors.includes(floor)}
 | 
					                          checked={selectedFloors.includes(floor)}
 | 
				
			||||||
                          onChange={() => handleFilterChange("floor", floor)}
 | 
					                          onChange={() => handleFilterChange("floor", floor)}
 | 
				
			||||||
                        />
 | 
					                        />
 | 
				
			||||||
                        <label
 | 
					                        <label className="form-check-label" htmlFor={`floor-${floor}`}>
 | 
				
			||||||
                          className="form-check-label"
 | 
					 | 
				
			||||||
                          htmlFor={`floor-${floor}`}
 | 
					 | 
				
			||||||
                        >
 | 
					 | 
				
			||||||
                          {floor}
 | 
					                          {floor}
 | 
				
			||||||
                        </label>
 | 
					                        </label>
 | 
				
			||||||
                      </div>
 | 
					                      </div>
 | 
				
			||||||
                    </div>
 | 
					                    </div>
 | 
				
			||||||
                  ))
 | 
					                  ))
 | 
				
			||||||
                ) : (
 | 
					                ) : (
 | 
				
			||||||
                  <div className="col-12 text-muted">No floors for selected building.</div>
 | 
					                  <div className="col-12 text-muted">No floors found.</div>
 | 
				
			||||||
                )}
 | 
					                )}
 | 
				
			||||||
              </div>
 | 
					              </div>
 | 
				
			||||||
            </li>
 | 
					            </li>
 | 
				
			||||||
          </>
 | 
					          </>
 | 
				
			||||||
        )}
 | 
					        )}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        {/* Activity Filter - Visible only if a floor is selected */}
 | 
					        {/* Activity */}
 | 
				
			||||||
        {selectedFloors.length > 0 && (
 | 
					        {selectedFloors.length > 0 && (
 | 
				
			||||||
          <>
 | 
					          <>
 | 
				
			||||||
 | 
					            <li><hr className="my-1" /></li>
 | 
				
			||||||
            <li>
 | 
					            <li>
 | 
				
			||||||
              <hr className="my-1" />
 | 
					              <div className="fw-bold text-dark mb-1">Activities</div>
 | 
				
			||||||
            </li>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            <li>
 | 
					 | 
				
			||||||
              <div className="fw-bold text-dark mb-1">Activity</div>
 | 
					 | 
				
			||||||
              <div className="row">
 | 
					              <div className="row">
 | 
				
			||||||
                {uniqueActivities.length > 0 ? (
 | 
					                {uniqueActivities.length > 0 ? (
 | 
				
			||||||
                  uniqueActivities.map((activity, idx) => (
 | 
					                  uniqueActivities.map((activity, idx) => (
 | 
				
			||||||
                    <div className="col-6" key={`activity-${idx}`}>
 | 
					                    <div className="col-6" key={idx}>
 | 
				
			||||||
                      <div className="form-check mb-1">
 | 
					                      <div className="form-check mb-1">
 | 
				
			||||||
                        <input
 | 
					                        <input
 | 
				
			||||||
                          className="form-check-input"
 | 
					                          className="form-check-input"
 | 
				
			||||||
@ -195,43 +189,60 @@ const FilterIcon = ({ taskListData, onApplyFilters, currentSelectedBuilding, cur
 | 
				
			|||||||
                          checked={selectedActivities.includes(activity)}
 | 
					                          checked={selectedActivities.includes(activity)}
 | 
				
			||||||
                          onChange={() => handleFilterChange("activity", activity)}
 | 
					                          onChange={() => handleFilterChange("activity", activity)}
 | 
				
			||||||
                        />
 | 
					                        />
 | 
				
			||||||
                        <label
 | 
					                        <label className="form-check-label" htmlFor={`activity-${activity}`}>
 | 
				
			||||||
                          className="form-check-label"
 | 
					 | 
				
			||||||
                          htmlFor={`activity-${activity}`}
 | 
					 | 
				
			||||||
                        >
 | 
					 | 
				
			||||||
                          {activity}
 | 
					                          {activity}
 | 
				
			||||||
                        </label>
 | 
					                        </label>
 | 
				
			||||||
                      </div>
 | 
					                      </div>
 | 
				
			||||||
                    </div>
 | 
					                    </div>
 | 
				
			||||||
                  ))
 | 
					                  ))
 | 
				
			||||||
                ) : (
 | 
					                ) : (
 | 
				
			||||||
                  <div className="col-12 text-muted">No activities for selected floor(s).</div>
 | 
					                  <div className="col-12 text-muted">No activities found.</div>
 | 
				
			||||||
                )}
 | 
					                )}
 | 
				
			||||||
              </div>
 | 
					              </div>
 | 
				
			||||||
            </li>
 | 
					            </li>
 | 
				
			||||||
          </>
 | 
					          </>
 | 
				
			||||||
        )}
 | 
					        )}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        {/* Clear Filters */}
 | 
					        {/* Action Buttons */}
 | 
				
			||||||
        {(selectedBuilding ||
 | 
					        <li><hr className="my-1" /></li>
 | 
				
			||||||
          selectedFloors.length > 0 ||
 | 
					        {(selectedBuilding || selectedFloors.length > 0 || selectedActivities.length > 0) && (
 | 
				
			||||||
          selectedActivities.length > 0) && (
 | 
					          <li className="d-flex justify-content-end gap-2 px-2">
 | 
				
			||||||
            <>
 | 
					            <button
 | 
				
			||||||
              <li>
 | 
					              type="button"
 | 
				
			||||||
                <hr className="my-1" />
 | 
					              className="btn btn-sm"
 | 
				
			||||||
              </li>
 | 
					              style={{
 | 
				
			||||||
              <li>
 | 
					                backgroundColor: "#7161EF",
 | 
				
			||||||
                <button
 | 
					                color: "white",
 | 
				
			||||||
                  type="button"
 | 
					                fontSize: "13px",
 | 
				
			||||||
                  className="dropdown-item text-danger px-2 py-1"
 | 
					                padding: "4px 16px",
 | 
				
			||||||
                  style={{ fontSize: "13px" }}
 | 
					                borderRadius: "8px",
 | 
				
			||||||
                  onClick={clearAllFilters}
 | 
					                boxShadow: "0 1px 4px rgba(0,0,0,0.1)"
 | 
				
			||||||
                >
 | 
					              }}
 | 
				
			||||||
                  Clear All Filters
 | 
					              onClick={clearAllFilters}
 | 
				
			||||||
                </button>
 | 
					            >
 | 
				
			||||||
              </li>
 | 
					              Clear
 | 
				
			||||||
            </>
 | 
					            </button>
 | 
				
			||||||
          )}
 | 
					            <button
 | 
				
			||||||
 | 
					              type="button"
 | 
				
			||||||
 | 
					              className="btn btn-sm"
 | 
				
			||||||
 | 
					              style={{
 | 
				
			||||||
 | 
					                backgroundColor: "#7161EF",
 | 
				
			||||||
 | 
					                color: "white",
 | 
				
			||||||
 | 
					                fontSize: "13px",
 | 
				
			||||||
 | 
					                padding: "4px 16px",
 | 
				
			||||||
 | 
					                borderRadius: "8px",
 | 
				
			||||||
 | 
					                boxShadow: "0 1px 4px rgba(0,0,0,0.1)"
 | 
				
			||||||
 | 
					              }}
 | 
				
			||||||
 | 
					              onClick={() => {
 | 
				
			||||||
 | 
					                document.getElementById("filterDropdown").click();
 | 
				
			||||||
 | 
					              }}
 | 
				
			||||||
 | 
					            >
 | 
				
			||||||
 | 
					              Apply
 | 
				
			||||||
 | 
					            </button>
 | 
				
			||||||
 | 
					          </li>
 | 
				
			||||||
 | 
					        )}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      </ul>
 | 
					      </ul>
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										35
									
								
								src/components/common/FloatingMenu.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								src/components/common/FloatingMenu.css
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,35 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					.fab-container {
 | 
				
			||||||
 | 
					  position: fixed;
 | 
				
			||||||
 | 
					  bottom: 35px;
 | 
				
			||||||
 | 
					  right: 30px;
 | 
				
			||||||
 | 
					  z-index: 1050;
 | 
				
			||||||
 | 
					  display: flex;
 | 
				
			||||||
 | 
					  flex-direction: column;
 | 
				
			||||||
 | 
					  align-items: end;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.fab-main {
 | 
				
			||||||
 | 
					  /* width: 45px;
 | 
				
			||||||
 | 
					  height: 45px;
 | 
				
			||||||
 | 
					  border-radius: 100%;
 | 
				
			||||||
 | 
					  background-color: #0d6efd;
 | 
				
			||||||
 | 
					  color: white;
 | 
				
			||||||
 | 
					  border: none; */
 | 
				
			||||||
 | 
					  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
 | 
				
			||||||
 | 
					  /* font-size: 24px; */
 | 
				
			||||||
 | 
					  cursor: pointer;
 | 
				
			||||||
 | 
					  pointer-events: auto;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.fab-option {
 | 
				
			||||||
 | 
					  pointer-events: auto;
 | 
				
			||||||
 | 
					  margin-bottom: 10px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					@media (max-width: 768px) {
 | 
				
			||||||
 | 
					  .fab-container {
 | 
				
			||||||
 | 
					    right: 20px;
 | 
				
			||||||
 | 
					    left: 50%;
 | 
				
			||||||
 | 
					    bottom: 20px;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										33
									
								
								src/components/common/FloatingMenu.jsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								src/components/common/FloatingMenu.jsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,33 @@
 | 
				
			|||||||
 | 
					import React, { useState } from "react";
 | 
				
			||||||
 | 
					import {useFab} from "../../Context/FabContext";
 | 
				
			||||||
 | 
					import './FloatingMenu.css'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const FloatingMenu = () => {
 | 
				
			||||||
 | 
					  const { actions } = useFab(); 
 | 
				
			||||||
 | 
					  const [open, setOpen] = useState(false);
 | 
				
			||||||
 | 
					  if (actions.length === 0) return null; 
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <div className="fab-container">
 | 
				
			||||||
 | 
					     {open &&
 | 
				
			||||||
 | 
					  actions.map((action, index) => (
 | 
				
			||||||
 | 
					    <button
 | 
				
			||||||
 | 
					      key={index}
 | 
				
			||||||
 | 
					      className={`badge bg-${action.color} rounded-pill mb-2 d-inline-flex align-items-center gap-2 px-3 py-1 cursor-pointer fab-option`}
 | 
				
			||||||
 | 
					      onClick={action.onClick}
 | 
				
			||||||
 | 
					      title={action.label}
 | 
				
			||||||
 | 
					    >
 | 
				
			||||||
 | 
					      <i className={action.icon}></i>
 | 
				
			||||||
 | 
					      <span>{action.label}</span> 
 | 
				
			||||||
 | 
					    </button>
 | 
				
			||||||
 | 
					))}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          <button type="button" className="btn btn-lg btn-icon rounded-pill me-2 btn-primary fab-main " onClick={() => setOpen(!open)}>
 | 
				
			||||||
 | 
					        <span className={`bx ${open ? "bx-x" : "bx-plus"}`}></span>
 | 
				
			||||||
 | 
					      </button>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default FloatingMenu;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -8,32 +8,45 @@ const GlobalModel = ({
 | 
				
			|||||||
  dialogClass = '',  // For additional custom classes on modal dialog
 | 
					  dialogClass = '',  // For additional custom classes on modal dialog
 | 
				
			||||||
  role = 'dialog',   // Accessibility role for the modal
 | 
					  role = 'dialog',   // Accessibility role for the modal
 | 
				
			||||||
  size = '',         // Dynamically set the size (sm, lg, xl)
 | 
					  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
 | 
					  const modalRef = useRef(null);  // Reference to the modal element
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  useEffect(() => {
 | 
					useEffect(() => {
 | 
				
			||||||
    const modalElement = modalRef.current;
 | 
					  const modalElement = modalRef.current;
 | 
				
			||||||
    const modalInstance = new window.bootstrap.Modal(modalElement);
 | 
					  const modalInstance = new window.bootstrap.Modal(modalElement, {
 | 
				
			||||||
 | 
					    backdrop: false,
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Show modal if isOpen is true
 | 
					  if (isOpen) {
 | 
				
			||||||
    if (isOpen) {
 | 
					    modalInstance.show();
 | 
				
			||||||
      modalInstance.show();
 | 
					  } else {
 | 
				
			||||||
    } else {
 | 
					    modalInstance.hide();
 | 
				
			||||||
      modalInstance.hide();
 | 
					  }
 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Handle modal hide event to invoke the closeModal function
 | 
					  const handleHideModal = () => {
 | 
				
			||||||
    const handleHideModal = () => {
 | 
					    closeModal();
 | 
				
			||||||
      closeModal();  // Close the modal via React state
 | 
					
 | 
				
			||||||
    };
 | 
					    // ✅ FIX: Remove any lingering body classes/styles
 | 
				
			||||||
 | 
					    document.body.classList.remove('modal-open');
 | 
				
			||||||
 | 
					    document.body.style.overflow = '';
 | 
				
			||||||
 | 
					    document.body.style.paddingRight = '';
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  modalElement.addEventListener('hidden.bs.modal', handleHideModal);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return () => {
 | 
				
			||||||
 | 
					    modalElement.removeEventListener('hidden.bs.modal', handleHideModal);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Also clean up just in case component unmounts
 | 
				
			||||||
 | 
					    document.body.classList.remove('modal-open');
 | 
				
			||||||
 | 
					    document.body.style.overflow = '';
 | 
				
			||||||
 | 
					    document.body.style.paddingRight = '';
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					}, [isOpen, closeModal]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    modalElement.addEventListener('hidden.bs.modal', handleHideModal);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return () => {
 | 
					 | 
				
			||||||
      modalElement.removeEventListener('hidden.bs.modal', handleHideModal);
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
  }, [isOpen, closeModal]);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Dynamically set the modal size classes (modal-sm, modal-lg, modal-xl)
 | 
					  // Dynamically set the modal size classes (modal-sm, modal-lg, modal-xl)
 | 
				
			||||||
  const modalSizeClass = size ? `modal-${size}` : '';  // Default is empty if no size is specified
 | 
					  const modalSizeClass = size ? `modal-${size}` : '';  // Default is empty if no size is specified
 | 
				
			||||||
@ -41,7 +54,20 @@ const GlobalModel = ({
 | 
				
			|||||||
  // Dynamically generate data-bs attributes
 | 
					  // Dynamically generate data-bs attributes
 | 
				
			||||||
  const dataAttributesProps = Object.keys(dataAttributes).map(key => ({
 | 
					  const dataAttributesProps = Object.keys(dataAttributes).map(key => ({
 | 
				
			||||||
    [key]: dataAttributes[key],
 | 
					    [key]: dataAttributes[key],
 | 
				
			||||||
  }));
 | 
					  } ) );
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   // The gray background
 | 
				
			||||||
 | 
					    const backdropStyle = {
 | 
				
			||||||
 | 
					      position: 'fixed',
 | 
				
			||||||
 | 
					      top: 0,
 | 
				
			||||||
 | 
					      bottom: 0,
 | 
				
			||||||
 | 
					      left: 0,
 | 
				
			||||||
 | 
					      right: 0,
 | 
				
			||||||
 | 
					      backgroundColor: 'rgba(0,0,0,0.3)',
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <div
 | 
					    <div
 | 
				
			||||||
@ -52,18 +78,19 @@ const GlobalModel = ({
 | 
				
			|||||||
      aria-hidden="true"
 | 
					      aria-hidden="true"
 | 
				
			||||||
      ref={modalRef}  // Assign the ref to the modal element
 | 
					      ref={modalRef}  // Assign the ref to the modal element
 | 
				
			||||||
      {...dataAttributesProps}
 | 
					      {...dataAttributesProps}
 | 
				
			||||||
 | 
					      style={backdropStyle}
 | 
				
			||||||
    >
 | 
					    >
 | 
				
			||||||
      <div className={`modal-dialog ${dialogClass} ${modalSizeClass } mx-sm-auto mx-1`} role={role}>
 | 
					      <div className={`modal-dialog ${dialogClass} ${modalSizeClass } mx-sm-auto mx-1`} role={role} >
 | 
				
			||||||
        <div className="modal-content">
 | 
					        <div className="modal-content">
 | 
				
			||||||
          <div className="modal-header p-0">
 | 
					          <div className="modal-header p-0">
 | 
				
			||||||
            {/* Close button inside the modal header */}
 | 
					            {/* Close button inside the modal header */}
 | 
				
			||||||
            <button
 | 
					           {IsCloseBtn && <button
 | 
				
			||||||
              type="button"
 | 
					              type="button"
 | 
				
			||||||
              className="btn-close"
 | 
					              className="btn-close"
 | 
				
			||||||
              data-bs-dismiss="modal"
 | 
					              data-bs-dismiss="modal"
 | 
				
			||||||
              aria-label="Close"
 | 
					              aria-label="Close"
 | 
				
			||||||
              onClick={closeModal}  // Trigger the React closeModal function
 | 
					              onClick={closeModal}  // Trigger the React closeModal function
 | 
				
			||||||
            ></button>
 | 
					            ></button>}
 | 
				
			||||||
          </div>
 | 
					          </div>
 | 
				
			||||||
          <div className="modal-body  p-sm-4 p-0">
 | 
					          <div className="modal-body  p-sm-4 p-0">
 | 
				
			||||||
            {children}  {/* Render children here, which can be the ReportTask component */}
 | 
					            {children}  {/* Render children here, which can be the ReportTask component */}
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										79
									
								
								src/components/common/InputSuggestion.jsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								src/components/common/InputSuggestion.jsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,79 @@
 | 
				
			|||||||
 | 
					import React, { useState } from "react";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const InputSuggestions = ({
 | 
				
			||||||
 | 
					  organizationList = [],
 | 
				
			||||||
 | 
					  value,
 | 
				
			||||||
 | 
					  onChange,
 | 
				
			||||||
 | 
					  error,
 | 
				
			||||||
 | 
					}) => {
 | 
				
			||||||
 | 
					  const [filteredList, setFilteredList] = useState([]);
 | 
				
			||||||
 | 
					  const [showSuggestions, setShowSuggestions] = useState(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const handleInputChange = (e) => {
 | 
				
			||||||
 | 
					    const val = e.target.value;
 | 
				
			||||||
 | 
					    onChange(val);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const matches = organizationList.filter((org) =>
 | 
				
			||||||
 | 
					      org.toLowerCase().includes(val.toLowerCase())
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					    setFilteredList(matches);
 | 
				
			||||||
 | 
					    setShowSuggestions(true);
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const handleSelectSuggestion = (val) => {
 | 
				
			||||||
 | 
					    onChange(val);
 | 
				
			||||||
 | 
					    setShowSuggestions(false);
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <div className="position-relative">
 | 
				
			||||||
 | 
					      <input
 | 
				
			||||||
 | 
					        className="form-control form-control-sm"
 | 
				
			||||||
 | 
					        value={value}
 | 
				
			||||||
 | 
					        onChange={handleInputChange}
 | 
				
			||||||
 | 
					        onBlur={() => setTimeout(() => setShowSuggestions(false), 150)}
 | 
				
			||||||
 | 
					        onFocus={() => {
 | 
				
			||||||
 | 
					          if (value) setShowSuggestions(true);
 | 
				
			||||||
 | 
					        }}
 | 
				
			||||||
 | 
					      />
 | 
				
			||||||
 | 
					      {showSuggestions && filteredList.length > 0 && (
 | 
				
			||||||
 | 
					        <ul
 | 
				
			||||||
 | 
					          className="list-group shadow-sm position-absolute w-100 bg-white border  zindex-tooltip"
 | 
				
			||||||
 | 
					          style={{
 | 
				
			||||||
 | 
					            maxHeight: "180px",
 | 
				
			||||||
 | 
					            overflowY: "auto",
 | 
				
			||||||
 | 
					            marginTop: "2px",
 | 
				
			||||||
 | 
					            zIndex: 1000,
 | 
				
			||||||
 | 
					            borderRadius:"0px"
 | 
				
			||||||
 | 
					          }}
 | 
				
			||||||
 | 
					        >
 | 
				
			||||||
 | 
					          {filteredList.map((org) => (
 | 
				
			||||||
 | 
					            <li
 | 
				
			||||||
 | 
					              key={org}
 | 
				
			||||||
 | 
					              className="list-group-item list-group-item-action border-none "
 | 
				
			||||||
 | 
					              style={{
 | 
				
			||||||
 | 
					                cursor: "pointer",
 | 
				
			||||||
 | 
					                padding: "5px 12px",
 | 
				
			||||||
 | 
					                fontSize: "14px",
 | 
				
			||||||
 | 
					                transition: "background-color 0.2s",
 | 
				
			||||||
 | 
					              }}
 | 
				
			||||||
 | 
					              onMouseDown={() => handleSelectSuggestion(org)}
 | 
				
			||||||
 | 
					              onMouseEnter={(e) =>
 | 
				
			||||||
 | 
					                (e.currentTarget.style.backgroundColor = "#f8f9fa")
 | 
				
			||||||
 | 
					              }
 | 
				
			||||||
 | 
					              onMouseLeave={(e) =>
 | 
				
			||||||
 | 
					                (e.currentTarget.style.backgroundColor = "transparent")
 | 
				
			||||||
 | 
					              }
 | 
				
			||||||
 | 
					            >
 | 
				
			||||||
 | 
					              {org}
 | 
				
			||||||
 | 
					            </li>
 | 
				
			||||||
 | 
					          ))}
 | 
				
			||||||
 | 
					        </ul>
 | 
				
			||||||
 | 
					      )}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      {error && <small className="danger-text">{error}</small>}
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default InputSuggestions;
 | 
				
			||||||
							
								
								
									
										158
									
								
								src/components/common/MultiSelectDropdown.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										158
									
								
								src/components/common/MultiSelectDropdown.css
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,158 @@
 | 
				
			|||||||
 | 
					/* Container for the multi-select dropdown */
 | 
				
			||||||
 | 
					.multi-select-dropdown-container {
 | 
				
			||||||
 | 
					  position: relative;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* Header of the dropdown */
 | 
				
			||||||
 | 
					.multi-select-dropdown-header {
 | 
				
			||||||
 | 
					  display: flex;
 | 
				
			||||||
 | 
					  justify-content: space-between;
 | 
				
			||||||
 | 
					  align-items: center;
 | 
				
			||||||
 | 
					  padding: 5px;
 | 
				
			||||||
 | 
					  border: 1px solid #ddd;
 | 
				
			||||||
 | 
					  border-radius: 5px;
 | 
				
			||||||
 | 
					  cursor: pointer;
 | 
				
			||||||
 | 
					  transition: all 0.3s ease;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.multi-select-dropdown-header .placeholder-style {
 | 
				
			||||||
 | 
					  color: #6c757d;
 | 
				
			||||||
 | 
					  font-size: 14px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.multi-select-dropdown-header .placeholder-style-selected {
 | 
				
			||||||
 | 
					  /* color: #0d6efd; */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  font-size: 12px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* Arrow icon */
 | 
				
			||||||
 | 
					.multi-select-dropdown-arrow {
 | 
				
			||||||
 | 
					  width: 14px;
 | 
				
			||||||
 | 
					  height: 14px;
 | 
				
			||||||
 | 
					  margin-left: 10px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* Dropdown options */
 | 
				
			||||||
 | 
					.multi-select-dropdown-options {
 | 
				
			||||||
 | 
					  position: absolute;
 | 
				
			||||||
 | 
					  top: 100%;
 | 
				
			||||||
 | 
					  left: 0;
 | 
				
			||||||
 | 
					  right: 0;
 | 
				
			||||||
 | 
					  z-index: 10;
 | 
				
			||||||
 | 
					  border: 1px solid #ddd;
 | 
				
			||||||
 | 
					  border-radius: 3px;
 | 
				
			||||||
 | 
					  background-color: white;
 | 
				
			||||||
 | 
					  max-height: 250px;
 | 
				
			||||||
 | 
					  overflow-y: auto;
 | 
				
			||||||
 | 
					  box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.2);
 | 
				
			||||||
 | 
					  margin-top: 5px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* Search input */
 | 
				
			||||||
 | 
					.multi-select-dropdown-search {
 | 
				
			||||||
 | 
					  padding: 4px;
 | 
				
			||||||
 | 
					  border-bottom: 1px solid #f1f3f5;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.multi-select-dropdown-search-input {
 | 
				
			||||||
 | 
					  width: 100%;
 | 
				
			||||||
 | 
					  padding: 4px;
 | 
				
			||||||
 | 
					  border: none;
 | 
				
			||||||
 | 
					  outline: none;
 | 
				
			||||||
 | 
					  background-color: #f8f9fa;
 | 
				
			||||||
 | 
					  border-radius: 6px;
 | 
				
			||||||
 | 
					  font-size: 14px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* Select All checkbox */
 | 
				
			||||||
 | 
					.multi-select-dropdown-select-all {
 | 
				
			||||||
 | 
					  display: flex;
 | 
				
			||||||
 | 
					  align-items: center;
 | 
				
			||||||
 | 
					  padding: 4px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.multi-select-dropdown-select-all .custom-checkbox {
 | 
				
			||||||
 | 
					  margin-right: 8px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.multi-select-dropdown-select-all-label {
 | 
				
			||||||
 | 
					  font-size: 14px;
 | 
				
			||||||
 | 
					  color: #333;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* Options in dropdown */
 | 
				
			||||||
 | 
					.multi-select-dropdown-option {
 | 
				
			||||||
 | 
					  display: flex;
 | 
				
			||||||
 | 
					  align-items: center;
 | 
				
			||||||
 | 
					  padding: 4px;
 | 
				
			||||||
 | 
					  cursor: pointer;
 | 
				
			||||||
 | 
					  transition: background-color 0.3s ease;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.multi-select-dropdown-option:hover {
 | 
				
			||||||
 | 
					  background-color: #f1f3f5;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.multi-select-dropdown-option.selected {
 | 
				
			||||||
 | 
					  background-color: #dbe7ff;
 | 
				
			||||||
 | 
					  color: #0d6efd;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.multi-select-dropdown-option input[type="checkbox"] {
 | 
				
			||||||
 | 
					  margin-right: 10px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* Custom checkbox */
 | 
				
			||||||
 | 
					.custom-checkbox {
 | 
				
			||||||
 | 
					  width: 18px;
 | 
				
			||||||
 | 
					  height: 18px;
 | 
				
			||||||
 | 
					  border-radius: 4px;
 | 
				
			||||||
 | 
					  border: 1px solid #ddd;
 | 
				
			||||||
 | 
					  background-color: white;
 | 
				
			||||||
 | 
					  cursor: pointer;
 | 
				
			||||||
 | 
					  position: relative;
 | 
				
			||||||
 | 
					  margin-right: 10px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.custom-checkbox:checked {
 | 
				
			||||||
 | 
					  background-color: #696cff;
 | 
				
			||||||
 | 
					  border-color: #696cff;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.custom-checkbox:checked::after {
 | 
				
			||||||
 | 
					  content: '';
 | 
				
			||||||
 | 
					  position: absolute;
 | 
				
			||||||
 | 
					  top: 2px;
 | 
				
			||||||
 | 
					  left: 2px;
 | 
				
			||||||
 | 
					  width: 10px;
 | 
				
			||||||
 | 
					  height: 10px;
 | 
				
			||||||
 | 
					  border-radius: 2px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.multi-select-dropdown-Not-found{
 | 
				
			||||||
 | 
					 text-align: center;
 | 
				
			||||||
 | 
					 padding: 1px 3px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.multi-select-dropdown-Not-found:hover {
 | 
				
			||||||
 | 
					  background: #e2dfdf;
 | 
				
			||||||
 | 
					 
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* Responsive styles */
 | 
				
			||||||
 | 
					@media (max-width: 767px) {
 | 
				
			||||||
 | 
					  .multi-select-dropdown-container {
 | 
				
			||||||
 | 
					    width: 100%;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  .multi-select-dropdown-header {
 | 
				
			||||||
 | 
					    font-size: 12px;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  .multi-select-dropdown-options {
 | 
				
			||||||
 | 
					    width: 100%;
 | 
				
			||||||
 | 
					    max-height: 200px;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										131
									
								
								src/components/common/SelectMultiple.jsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										131
									
								
								src/components/common/SelectMultiple.jsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,131 @@
 | 
				
			|||||||
 | 
					import React, { useState, useEffect, useRef } from "react";
 | 
				
			||||||
 | 
					import { useFormContext } from "react-hook-form";
 | 
				
			||||||
 | 
					import "./MultiSelectDropdown.css";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const SelectMultiple = ({
 | 
				
			||||||
 | 
					  name,
 | 
				
			||||||
 | 
					  options = [],
 | 
				
			||||||
 | 
					  label = "Select options",
 | 
				
			||||||
 | 
					  labelKey = "name",
 | 
				
			||||||
 | 
					  valueKey = "id",
 | 
				
			||||||
 | 
					  placeholder = "Please select...",
 | 
				
			||||||
 | 
					  IsLoading = false,
 | 
				
			||||||
 | 
					}) => {
 | 
				
			||||||
 | 
					  const { setValue, watch } = useFormContext();
 | 
				
			||||||
 | 
					  const selectedValues = watch(name) || [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const [isOpen, setIsOpen] = useState(false);
 | 
				
			||||||
 | 
					  const [searchText, setSearchText] = useState("");
 | 
				
			||||||
 | 
					  const dropdownRef = useRef(null);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  useEffect(() => {
 | 
				
			||||||
 | 
					    const handleClickOutside = (e) => {
 | 
				
			||||||
 | 
					      if (dropdownRef.current && !dropdownRef.current.contains(e.target)) {
 | 
				
			||||||
 | 
					        setIsOpen(false);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    document.addEventListener("mousedown", handleClickOutside);
 | 
				
			||||||
 | 
					    return () => document.removeEventListener("mousedown", handleClickOutside);
 | 
				
			||||||
 | 
					  }, []);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const handleCheckboxChange = (value) => {
 | 
				
			||||||
 | 
					    const updated = selectedValues.includes(value)
 | 
				
			||||||
 | 
					      ? selectedValues.filter((v) => v !== value)
 | 
				
			||||||
 | 
					      : [...selectedValues, value];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    setValue(name, updated, { shouldValidate: true });
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const filteredOptions = options.filter((item) =>
 | 
				
			||||||
 | 
					    item[labelKey]?.toLowerCase().includes(searchText.toLowerCase())
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <div ref={dropdownRef} className="multi-select-dropdown-container">
 | 
				
			||||||
 | 
					      <label className="form-label mb-1">{label}</label>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      <div
 | 
				
			||||||
 | 
					        className="multi-select-dropdown-header"
 | 
				
			||||||
 | 
					        onClick={() => setIsOpen((prev) => !prev)}
 | 
				
			||||||
 | 
					      >
 | 
				
			||||||
 | 
					        <span
 | 
				
			||||||
 | 
					          className={
 | 
				
			||||||
 | 
					            selectedValues.length > 0
 | 
				
			||||||
 | 
					              ? "placeholder-style-selected"
 | 
				
			||||||
 | 
					              : "placeholder-style"
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        >
 | 
				
			||||||
 | 
					          <div className="selected-badges-container">
 | 
				
			||||||
 | 
					            {selectedValues.length > 0 ? (
 | 
				
			||||||
 | 
					              selectedValues.map((val) => {
 | 
				
			||||||
 | 
					                const found = options.find((opt) => opt[valueKey] === val);
 | 
				
			||||||
 | 
					                return (
 | 
				
			||||||
 | 
					                  <span
 | 
				
			||||||
 | 
					                    key={val}
 | 
				
			||||||
 | 
					                    className="badge badge-selected-item mx-1 mb-1"
 | 
				
			||||||
 | 
					                  >
 | 
				
			||||||
 | 
					                    {found ? found[labelKey] : ""}
 | 
				
			||||||
 | 
					                  </span>
 | 
				
			||||||
 | 
					                );
 | 
				
			||||||
 | 
					              })
 | 
				
			||||||
 | 
					            ) : (
 | 
				
			||||||
 | 
					              <span className="placeholder-text">{placeholder}</span>
 | 
				
			||||||
 | 
					            )}
 | 
				
			||||||
 | 
					          </div>
 | 
				
			||||||
 | 
					        </span>
 | 
				
			||||||
 | 
					        <i className="bx bx-chevron-down"></i>
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      {isOpen && (
 | 
				
			||||||
 | 
					        <div className="multi-select-dropdown-options">
 | 
				
			||||||
 | 
					          <div className="multi-select-dropdown-search">
 | 
				
			||||||
 | 
					            <input
 | 
				
			||||||
 | 
					              type="text"
 | 
				
			||||||
 | 
					              placeholder="Search..."
 | 
				
			||||||
 | 
					              value={searchText}
 | 
				
			||||||
 | 
					              onChange={(e) => setSearchText(e.target.value)}
 | 
				
			||||||
 | 
					              className="multi-select-dropdown-search-input"
 | 
				
			||||||
 | 
					            />
 | 
				
			||||||
 | 
					          </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          {filteredOptions.map((item) => {
 | 
				
			||||||
 | 
					            const labelVal = item[labelKey];
 | 
				
			||||||
 | 
					            const valueVal = item[valueKey];
 | 
				
			||||||
 | 
					            const isChecked = selectedValues.includes(valueVal);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return (
 | 
				
			||||||
 | 
					              <div
 | 
				
			||||||
 | 
					                key={valueVal}
 | 
				
			||||||
 | 
					                className={`multi-select-dropdown-option ${
 | 
				
			||||||
 | 
					                  isChecked ? "selected" : ""
 | 
				
			||||||
 | 
					                }`}
 | 
				
			||||||
 | 
					              >
 | 
				
			||||||
 | 
					                <input
 | 
				
			||||||
 | 
					                  type="checkbox"
 | 
				
			||||||
 | 
					                  className="custom-checkbox form-check-input"
 | 
				
			||||||
 | 
					                  checked={isChecked}
 | 
				
			||||||
 | 
					                  onChange={() => handleCheckboxChange(valueVal)}
 | 
				
			||||||
 | 
					                />
 | 
				
			||||||
 | 
					                <label className="text-secondary">{labelVal}</label>
 | 
				
			||||||
 | 
					              </div>
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					          })}
 | 
				
			||||||
 | 
					          {!IsLoading && filteredOptions.length === 0 && (
 | 
				
			||||||
 | 
					            <div className="multi-select-dropdown-Not-found">
 | 
				
			||||||
 | 
					              <label className="text-muted">
 | 
				
			||||||
 | 
					                Not Found {`'${searchText}'`}
 | 
				
			||||||
 | 
					              </label>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					          )}
 | 
				
			||||||
 | 
					          {IsLoading && filteredOptions.length === 0 && (
 | 
				
			||||||
 | 
					            <div className="multi-select-dropdown-Not-found">
 | 
				
			||||||
 | 
					              <label className="text-muted">Loading...</label>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					          )}
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					      )}
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default SelectMultiple;
 | 
				
			||||||
@ -1,32 +1,106 @@
 | 
				
			|||||||
import React, { useState, useEffect } from "react";
 | 
					import { useFormContext, useWatch } from "react-hook-form";
 | 
				
			||||||
import { useFormContext } from "react-hook-form";
 | 
					import React, { useEffect, useState } from "react";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const TagInput = ({
 | 
					const TagInput = ({
 | 
				
			||||||
  label = "Tags",
 | 
					  label = "Tags",
 | 
				
			||||||
  name = "tags",
 | 
					  name = "tags",
 | 
				
			||||||
  placeholder = "Enter ... ",
 | 
					  placeholder = "Start typing to add... like employee, manager",
 | 
				
			||||||
  color = "#e9ecef",
 | 
					  color = "#e9ecef",
 | 
				
			||||||
 | 
					  options = [],
 | 
				
			||||||
}) => {
 | 
					}) => {
 | 
				
			||||||
  const [tags, setTags] = useState([]);
 | 
					  const [tags, setTags] = useState([]);
 | 
				
			||||||
  const [input, setInput] = useState("");
 | 
					  const [input, setInput] = useState("");
 | 
				
			||||||
  const { setValue } = useFormContext();
 | 
					  const [suggestions, setSuggestions] = useState([]);
 | 
				
			||||||
 | 
					  const { setValue, trigger, control } = useFormContext();
 | 
				
			||||||
 | 
					  const watchedTags = useWatch({ control, name });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					useEffect(() => {
 | 
				
			||||||
 | 
					  if (
 | 
				
			||||||
 | 
					    Array.isArray(watchedTags) &&
 | 
				
			||||||
 | 
					    JSON.stringify(tags) !== JSON.stringify(watchedTags)
 | 
				
			||||||
 | 
					  ) {
 | 
				
			||||||
 | 
					    setTags(watchedTags);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}, [JSON.stringify(watchedTags)]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  useEffect(() => {
 | 
					  useEffect(() => {
 | 
				
			||||||
    setValue(name, tags); // sync to form when tags change
 | 
					    if (input.trim() === "") {
 | 
				
			||||||
  }, [tags, name, setValue]);
 | 
					      setSuggestions([]);
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      const filtered = options?.filter(
 | 
				
			||||||
 | 
					        (opt) =>
 | 
				
			||||||
 | 
					          opt?.name?.toLowerCase()?.includes(input.toLowerCase()) &&
 | 
				
			||||||
 | 
					          !tags?.some((tag) => tag.name === opt.name)
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					      setSuggestions(filtered);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }, [input, options, tags]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const addTag = (e) => {
 | 
					  const addTag = async (tagObj) => {
 | 
				
			||||||
    e.preventDefault();
 | 
					    if (!tags.some((tag) => tag.name === tagObj.name)) {
 | 
				
			||||||
    const trimmed = input.trim();
 | 
					      const cleanedTag = {
 | 
				
			||||||
    if (trimmed !== "" && !tags.includes(trimmed)) {
 | 
					        id: tagObj.id ?? null,
 | 
				
			||||||
      setTags([...tags, trimmed]);
 | 
					        name: tagObj.name,
 | 
				
			||||||
 | 
					      };
 | 
				
			||||||
 | 
					      const newTags = [...tags, cleanedTag];
 | 
				
			||||||
 | 
					      setTags(newTags);
 | 
				
			||||||
 | 
					      setValue(name, newTags, { shouldValidate: true });
 | 
				
			||||||
 | 
					      await trigger(name);
 | 
				
			||||||
      setInput("");
 | 
					      setInput("");
 | 
				
			||||||
 | 
					      setSuggestions([]);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const removeTag = (removeIndex) => {
 | 
					  const removeTag = (indexToRemove) => {
 | 
				
			||||||
    const updated = tags.filter((_, i) => i !== removeIndex);
 | 
					    const newTags = tags.filter((_, i) => i !== indexToRemove);
 | 
				
			||||||
    setTags(updated);
 | 
					    setTags(newTags);
 | 
				
			||||||
 | 
					    setValue(name, newTags, { shouldValidate: true });
 | 
				
			||||||
 | 
					    trigger(name);
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const handleInputKeyDown = (e) => {
 | 
				
			||||||
 | 
					    if ((e.key === "Enter" || e.key === " ")&& input.trim() !== "") {
 | 
				
			||||||
 | 
					      e.preventDefault();
 | 
				
			||||||
 | 
					      const existing = options.find(
 | 
				
			||||||
 | 
					        (opt) => opt.name.toLowerCase() === input.trim().toLowerCase()
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					      const newTag = existing
 | 
				
			||||||
 | 
					        ? existing
 | 
				
			||||||
 | 
					        : {
 | 
				
			||||||
 | 
					            id: null,
 | 
				
			||||||
 | 
					            name: input.trim(),
 | 
				
			||||||
 | 
					            description: input.trim(),
 | 
				
			||||||
 | 
					          };
 | 
				
			||||||
 | 
					      addTag(newTag);
 | 
				
			||||||
 | 
					    } else if (e.key === "Backspace" && input === "") {
 | 
				
			||||||
 | 
					      setTags((prev) => prev.slice(0, -1));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					  const handleInputKey = (e) => {
 | 
				
			||||||
 | 
					  const key = e.key?.toLowerCase();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if ((key === "enter" || key === " " || e.code === "Space") && input.trim() !== "") {
 | 
				
			||||||
 | 
					    e.preventDefault();
 | 
				
			||||||
 | 
					    const existing = options.find(
 | 
				
			||||||
 | 
					      (opt) => opt.name.toLowerCase() === input.trim().toLowerCase()
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					    const newTag = existing
 | 
				
			||||||
 | 
					      ? existing
 | 
				
			||||||
 | 
					      : {
 | 
				
			||||||
 | 
					          id: null,
 | 
				
			||||||
 | 
					          name: input.trim(),
 | 
				
			||||||
 | 
					          description: input.trim(),
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					    addTag(newTag);
 | 
				
			||||||
 | 
					  } else if ((key === "backspace" || e.code === "Backspace") && input === "") {
 | 
				
			||||||
 | 
					    setTags((prev) => prev.slice(0, -1));
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const handleSuggestionClick = (suggestion) => {
 | 
				
			||||||
 | 
					    addTag(suggestion);
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const backgroundColor = color || "#f8f9fa";
 | 
					  const backgroundColor = color || "#f8f9fa";
 | 
				
			||||||
@ -34,35 +108,77 @@ const TagInput = ({
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <>
 | 
					    <>
 | 
				
			||||||
      <label htmlFor={name} className="form-label">{label}</label>
 | 
					      <label htmlFor={name} className="form-label">
 | 
				
			||||||
      <div className="form-control form-control-sm d-flex justify-content-start flex-wrap gap-1" style={{ minHeight: "12px" }}>
 | 
					        {label}
 | 
				
			||||||
        {tags.map((tag, i) => (
 | 
					      </label>
 | 
				
			||||||
          <span
 | 
					
 | 
				
			||||||
            key={i}
 | 
					      <div
 | 
				
			||||||
            className="d-flex align-items-center"
 | 
					        className="form-control form-control-sm p-1"
 | 
				
			||||||
 | 
					        style={{ minHeight: "38px", position: "relative" }}
 | 
				
			||||||
 | 
					      >
 | 
				
			||||||
 | 
					        <div className="d-flex flex-wrap align-items-center gap-1">
 | 
				
			||||||
 | 
					          {tags.map((tag, index) => (
 | 
				
			||||||
 | 
					            <span
 | 
				
			||||||
 | 
					              key={index}
 | 
				
			||||||
 | 
					              className="d-flex align-items-center"
 | 
				
			||||||
 | 
					              style={{
 | 
				
			||||||
 | 
					                color: iconColor,
 | 
				
			||||||
 | 
					                backgroundColor,
 | 
				
			||||||
 | 
					                padding: "2px 6px",
 | 
				
			||||||
 | 
					                borderRadius: "2px",
 | 
				
			||||||
 | 
					                fontSize: "0.85rem",
 | 
				
			||||||
 | 
					              }}
 | 
				
			||||||
 | 
					            >
 | 
				
			||||||
 | 
					              {tag.name}
 | 
				
			||||||
 | 
					              <i
 | 
				
			||||||
 | 
					                className="bx bx-x bx-xs ms-1"
 | 
				
			||||||
 | 
					                onClick={() => removeTag(index)}
 | 
				
			||||||
 | 
					                style={{ cursor: "pointer" }}
 | 
				
			||||||
 | 
					              />
 | 
				
			||||||
 | 
					            </span>
 | 
				
			||||||
 | 
					          ))}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          <input
 | 
				
			||||||
 | 
					            type="text"
 | 
				
			||||||
 | 
					            value={input}
 | 
				
			||||||
 | 
					            onChange={(e) => setInput(e.target.value)}
 | 
				
			||||||
 | 
					            onKeyDown={handleInputKeyDown}
 | 
				
			||||||
 | 
					              onKeyUp={handleInputKey}
 | 
				
			||||||
 | 
					            placeholder={placeholder}
 | 
				
			||||||
            style={{
 | 
					            style={{
 | 
				
			||||||
              color: iconColor,
 | 
					              border: "none",
 | 
				
			||||||
              backgroundColor,
 | 
					              outline: "none",
 | 
				
			||||||
              padding: "2px 3px",
 | 
					              flex: 1,
 | 
				
			||||||
              borderRadius: "2px"
 | 
					              minWidth: "120px",
 | 
				
			||||||
 | 
					            }}
 | 
				
			||||||
 | 
					          />
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        {suggestions.length > 0 && (
 | 
				
			||||||
 | 
					          <ul
 | 
				
			||||||
 | 
					            className="list-group position-absolute mt-1 bg-white w-50 shadow-sm "
 | 
				
			||||||
 | 
					            style={{
 | 
				
			||||||
 | 
					              zIndex: 1000,
 | 
				
			||||||
 | 
					              maxHeight: "150px",
 | 
				
			||||||
 | 
					              overflowY: "auto",
 | 
				
			||||||
 | 
					              boxShadow:"0px 4px 10px rgba(0, 0, 0, 0.2)",borderRadius:"3px",border:"1px solid #ddd"
 | 
				
			||||||
            }}
 | 
					            }}
 | 
				
			||||||
          >
 | 
					          >
 | 
				
			||||||
            {tag}
 | 
					            {suggestions.map((sugg, i) => (
 | 
				
			||||||
            <i className="bx bx-x bx-xs ms-1" onClick={() => removeTag(i)}></i>
 | 
					              <li
 | 
				
			||||||
          </span>
 | 
					                key={i}
 | 
				
			||||||
        ))}
 | 
					                className="dropdown-item p-1 hoverBox"
 | 
				
			||||||
        <input
 | 
					                onClick={() => handleSuggestionClick(sugg)}
 | 
				
			||||||
          type="text"
 | 
					                style={{cursor: "pointer", fontSize: "0.875rem"}}
 | 
				
			||||||
          className="border-0 flex-grow-1"
 | 
					                
 | 
				
			||||||
          value={input}
 | 
					              >
 | 
				
			||||||
          onChange={(e) => setInput(e.target.value)}
 | 
					                {sugg.name}
 | 
				
			||||||
          onKeyDown={(e) => (e.key === "Enter" ? addTag(e) : null)}
 | 
					              </li>
 | 
				
			||||||
          placeholder={placeholder}
 | 
					            ))}
 | 
				
			||||||
          style={{ outline: "none", minWidth: "120px" }}
 | 
					          </ul>
 | 
				
			||||||
        />
 | 
					        )}
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
    </>
 | 
					    </>
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					 | 
				
			||||||
export default TagInput;
 | 
					export default TagInput;
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										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: 100%;
 | 
				
			||||||
 | 
					  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;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										96
									
								
								src/components/common/TextEditor/Editor.jsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										96
									
								
								src/components/common/TextEditor/Editor.jsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,96 @@
 | 
				
			|||||||
 | 
					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 m-5">
 | 
				
			||||||
 | 
					      <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" defaultValue="">
 | 
				
			||||||
 | 
					                <option value="1" />
 | 
				
			||||||
 | 
					                <option value="2" />
 | 
				
			||||||
 | 
					                <option value="" />
 | 
				
			||||||
 | 
					              </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;
 | 
				
			||||||
							
								
								
									
										113
									
								
								src/components/master/CreateContactCategory.jsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										113
									
								
								src/components/master/CreateContactCategory.jsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,113 @@
 | 
				
			|||||||
 | 
					import React, { useEffect,useState } from 'react'
 | 
				
			||||||
 | 
					import { useForm } from 'react-hook-form';
 | 
				
			||||||
 | 
					import { z } from 'zod';
 | 
				
			||||||
 | 
					import { zodResolver } from '@hookform/resolvers/zod';
 | 
				
			||||||
 | 
					import { MasterRespository } from '../../repositories/MastersRepository';
 | 
				
			||||||
 | 
					import { clearApiCacheKey } from '../../slices/apiCacheSlice';
 | 
				
			||||||
 | 
					import { getCachedData,cacheData } from '../../slices/apiDataManager';
 | 
				
			||||||
 | 
					import showToast from '../../services/toastService';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const schema = z.object({
 | 
				
			||||||
 | 
					  name: z.string().min(1, { message: "Category name is required" }),
 | 
				
			||||||
 | 
					  description: z.string().min(1, { message: "Description is required" })
 | 
				
			||||||
 | 
					  .max(255, { message: "Description cannot exceed 255 characters" }),
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const CreateContactCategory = ({onClose}) => {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const[isLoading,setIsLoading] = useState(false)
 | 
				
			||||||
 | 
					     const {
 | 
				
			||||||
 | 
					      register,
 | 
				
			||||||
 | 
					      handleSubmit,
 | 
				
			||||||
 | 
					      formState: { errors },reset
 | 
				
			||||||
 | 
					      
 | 
				
			||||||
 | 
					    } = useForm({
 | 
				
			||||||
 | 
					      resolver: zodResolver(schema),
 | 
				
			||||||
 | 
					      defaultValues: {
 | 
				
			||||||
 | 
					        name: "",
 | 
				
			||||||
 | 
					        description: "",
 | 
				
			||||||
 | 
					      
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    const onSubmit = (data) => {
 | 
				
			||||||
 | 
					      setIsLoading(true)    
 | 
				
			||||||
 | 
					      MasterRespository.createContactCategory(data).then((resp)=>{ 
 | 
				
			||||||
 | 
					        setIsLoading(false)
 | 
				
			||||||
 | 
					        resetForm()
 | 
				
			||||||
 | 
					        const cachedData = getCachedData("Contact Category"); 
 | 
				
			||||||
 | 
					        const updatedData = [...cachedData, resp?.data]; 
 | 
				
			||||||
 | 
					        cacheData("Contact Category", updatedData); 
 | 
				
			||||||
 | 
					        showToast("Contact Category Added successfully.", "success");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        onClose()
 | 
				
			||||||
 | 
					      }).catch((error)=>{
 | 
				
			||||||
 | 
					        showToast(error?.response?.data?.message, "error");
 | 
				
			||||||
 | 
					        setIsLoading(false)
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    const resetForm = () => {
 | 
				
			||||||
 | 
					      reset({
 | 
				
			||||||
 | 
					        name: "",
 | 
				
			||||||
 | 
					        description: ""
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					      setDescriptionLength(0);
 | 
				
			||||||
 | 
					    }    
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    useEffect(()=>{
 | 
				
			||||||
 | 
					        return ()=>resetForm()
 | 
				
			||||||
 | 
					    },[])
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    const [descriptionLength, setDescriptionLength] = useState(0);
 | 
				
			||||||
 | 
					    const maxDescriptionLength = 255;    
 | 
				
			||||||
 | 
					  return (<>
 | 
				
			||||||
 | 
					    <form  className="row g-2" onSubmit={handleSubmit(onSubmit)}>
 | 
				
			||||||
 | 
					        <div className="col-12 col-md-12">
 | 
				
			||||||
 | 
					          <label className="form-label">Category Name</label>
 | 
				
			||||||
 | 
					          <input type="text" 
 | 
				
			||||||
 | 
					           {...register("name")} 
 | 
				
			||||||
 | 
					             className={`form-control ${errors.name ? 'is-invalids' : ''}`}
 | 
				
			||||||
 | 
					          />
 | 
				
			||||||
 | 
					          {errors.name && <p className="text-danger">{errors.name.message}</p>}
 | 
				
			||||||
 | 
					        </div> 
 | 
				
			||||||
 | 
					        <div className="col-12 col-md-12">
 | 
				
			||||||
 | 
					          <label className="form-label" htmlFor="description">Description</label>
 | 
				
			||||||
 | 
					        <textarea
 | 
				
			||||||
 | 
					          rows="3"
 | 
				
			||||||
 | 
					          {...register("description")}
 | 
				
			||||||
 | 
					          className={`form-control ${errors.description ? 'is-invalids' : ''}`}
 | 
				
			||||||
 | 
					          onChange={(e) => {
 | 
				
			||||||
 | 
					            setDescriptionLength(e.target.value.length);
 | 
				
			||||||
 | 
					            register("description").onChange(e);
 | 
				
			||||||
 | 
					          }}
 | 
				
			||||||
 | 
					        ></textarea>
 | 
				
			||||||
 | 
					        <div className="text-end small text-muted">
 | 
				
			||||||
 | 
					          {maxDescriptionLength - descriptionLength} characters left
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					          {errors.description && (
 | 
				
			||||||
 | 
					            <p className="text-danger">{errors.description.message}</p>
 | 
				
			||||||
 | 
					          )}
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <div className="col-12 text-center">
 | 
				
			||||||
 | 
					        <button type="submit" className="btn btn-sm btn-primary me-3">           
 | 
				
			||||||
 | 
					         {isLoading? "Please Wait...":"Submit"}       
 | 
				
			||||||
 | 
					        </button>
 | 
				
			||||||
 | 
					        <button
 | 
				
			||||||
 | 
					          type="reset"
 | 
				
			||||||
 | 
					          className="btn btn-sm btn-label-secondary "
 | 
				
			||||||
 | 
					          data-bs-dismiss="modal"
 | 
				
			||||||
 | 
					          aria-label="Close"
 | 
				
			||||||
 | 
					        >
 | 
				
			||||||
 | 
					          Cancel
 | 
				
			||||||
 | 
					        </button>
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					     
 | 
				
			||||||
 | 
					    </form>
 | 
				
			||||||
 | 
					 
 | 
				
			||||||
 | 
					    </>
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default CreateContactCategory;
 | 
				
			||||||
							
								
								
									
										114
									
								
								src/components/master/CreateContactTag.jsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										114
									
								
								src/components/master/CreateContactTag.jsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,114 @@
 | 
				
			|||||||
 | 
					import React, { useEffect,useState } from 'react'
 | 
				
			||||||
 | 
					import { useForm } from 'react-hook-form';
 | 
				
			||||||
 | 
					import { z } from 'zod';
 | 
				
			||||||
 | 
					import { zodResolver } from '@hookform/resolvers/zod';
 | 
				
			||||||
 | 
					import { MasterRespository } from '../../repositories/MastersRepository';
 | 
				
			||||||
 | 
					import { clearApiCacheKey } from '../../slices/apiCacheSlice';
 | 
				
			||||||
 | 
					import { getCachedData,cacheData } from '../../slices/apiDataManager';
 | 
				
			||||||
 | 
					import showToast from '../../services/toastService';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const schema = z.object({
 | 
				
			||||||
 | 
					  name: z.string().min(1, { message: "Tag name is required" }),
 | 
				
			||||||
 | 
					  description: z.string().min(1, { message: "Description is required" })
 | 
				
			||||||
 | 
					  .max(255, { message: "Description cannot exceed 255 characters" }),
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const CreateContactTag = ({onClose}) => {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const[isLoading,setIsLoading] = useState(false)
 | 
				
			||||||
 | 
					     const {
 | 
				
			||||||
 | 
					      register,
 | 
				
			||||||
 | 
					      handleSubmit,
 | 
				
			||||||
 | 
					      formState: { errors },reset
 | 
				
			||||||
 | 
					      
 | 
				
			||||||
 | 
					    } = useForm({
 | 
				
			||||||
 | 
					      resolver: zodResolver(schema),
 | 
				
			||||||
 | 
					      defaultValues: {
 | 
				
			||||||
 | 
					        name: "",
 | 
				
			||||||
 | 
					        description: "",
 | 
				
			||||||
 | 
					      
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    const onSubmit = (data) => {
 | 
				
			||||||
 | 
					      setIsLoading(true)    
 | 
				
			||||||
 | 
					      MasterRespository.createContactTag(data).then((resp)=>{ 
 | 
				
			||||||
 | 
					        setIsLoading(false)
 | 
				
			||||||
 | 
					        resetForm()
 | 
				
			||||||
 | 
					        debugger
 | 
				
			||||||
 | 
					        const cachedData = getCachedData("Contact Tag"); 
 | 
				
			||||||
 | 
					        const updatedData = [...cachedData, resp?.data]; 
 | 
				
			||||||
 | 
					        cacheData("Contact Tag", updatedData); 
 | 
				
			||||||
 | 
					        showToast("Contact Tag Added successfully.", "success");
 | 
				
			||||||
 | 
					    console.log(getCachedData("Contact Tag"))
 | 
				
			||||||
 | 
					        onClose()
 | 
				
			||||||
 | 
					      }).catch((error)=>{
 | 
				
			||||||
 | 
					        showToast(error?.response?.data?.message, "error");
 | 
				
			||||||
 | 
					        setIsLoading(false)
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    const resetForm = () => {
 | 
				
			||||||
 | 
					      reset({
 | 
				
			||||||
 | 
					        name: "",
 | 
				
			||||||
 | 
					        description: ""
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					      setDescriptionLength(0);
 | 
				
			||||||
 | 
					    }    
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    useEffect(()=>{
 | 
				
			||||||
 | 
					        return ()=>resetForm()
 | 
				
			||||||
 | 
					    },[])
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    const [descriptionLength, setDescriptionLength] = useState(0);
 | 
				
			||||||
 | 
					    const maxDescriptionLength = 255;    
 | 
				
			||||||
 | 
					  return (<>
 | 
				
			||||||
 | 
					    <form  className="row g-2" onSubmit={handleSubmit(onSubmit)}>
 | 
				
			||||||
 | 
					        <div className="col-12 col-md-12">
 | 
				
			||||||
 | 
					          <label className="form-label">Tag Name</label>
 | 
				
			||||||
 | 
					          <input type="text" 
 | 
				
			||||||
 | 
					           {...register("name")} 
 | 
				
			||||||
 | 
					             className={`form-control ${errors.name ? 'is-invalids' : ''}`}
 | 
				
			||||||
 | 
					          />
 | 
				
			||||||
 | 
					          {errors.name && <p className="text-danger">{errors.name.message}</p>}
 | 
				
			||||||
 | 
					        </div> 
 | 
				
			||||||
 | 
					        <div className="col-12 col-md-12">
 | 
				
			||||||
 | 
					          <label className="form-label" htmlFor="description">Description</label>
 | 
				
			||||||
 | 
					        <textarea
 | 
				
			||||||
 | 
					          rows="3"
 | 
				
			||||||
 | 
					          {...register("description")}
 | 
				
			||||||
 | 
					          className={`form-control ${errors.description ? 'is-invalids' : ''}`}
 | 
				
			||||||
 | 
					          onChange={(e) => {
 | 
				
			||||||
 | 
					            setDescriptionLength(e.target.value.length);
 | 
				
			||||||
 | 
					            register("description").onChange(e);
 | 
				
			||||||
 | 
					          }}
 | 
				
			||||||
 | 
					        ></textarea>
 | 
				
			||||||
 | 
					        <div className="text-end small text-muted">
 | 
				
			||||||
 | 
					          {maxDescriptionLength - descriptionLength} characters left
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					          {errors.description && (
 | 
				
			||||||
 | 
					            <p className="text-danger">{errors.description.message}</p>
 | 
				
			||||||
 | 
					          )}
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <div className="col-12 text-center">
 | 
				
			||||||
 | 
					        <button type="submit" className="btn btn-sm btn-primary me-3">           
 | 
				
			||||||
 | 
					         {isLoading? "Please Wait...":"Submit"}       
 | 
				
			||||||
 | 
					        </button>
 | 
				
			||||||
 | 
					        <button
 | 
				
			||||||
 | 
					          type="reset"
 | 
				
			||||||
 | 
					          className="btn btn-sm btn-label-secondary "
 | 
				
			||||||
 | 
					          data-bs-dismiss="modal"
 | 
				
			||||||
 | 
					          aria-label="Close"
 | 
				
			||||||
 | 
					        >
 | 
				
			||||||
 | 
					          Cancel
 | 
				
			||||||
 | 
					        </button>
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					     
 | 
				
			||||||
 | 
					    </form>
 | 
				
			||||||
 | 
					 
 | 
				
			||||||
 | 
					    </>
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default CreateContactTag;
 | 
				
			||||||
							
								
								
									
										126
									
								
								src/components/master/EditContactCategory.jsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										126
									
								
								src/components/master/EditContactCategory.jsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,126 @@
 | 
				
			|||||||
 | 
					import React, { useEffect,useState } from 'react'
 | 
				
			||||||
 | 
					import { useForm } from 'react-hook-form';
 | 
				
			||||||
 | 
					import { z } from 'zod';
 | 
				
			||||||
 | 
					import { zodResolver } from '@hookform/resolvers/zod';
 | 
				
			||||||
 | 
					import { MasterRespository } from '../../repositories/MastersRepository';
 | 
				
			||||||
 | 
					import { clearApiCacheKey } from '../../slices/apiCacheSlice';
 | 
				
			||||||
 | 
					import { getCachedData,cacheData } from '../../slices/apiDataManager';
 | 
				
			||||||
 | 
					import showToast from '../../services/toastService';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const schema = z.object({
 | 
				
			||||||
 | 
					  name: z.string().min(1, { message: "Category name is required" }),
 | 
				
			||||||
 | 
					  description: z.string().min(1, { message: "Description is required" })
 | 
				
			||||||
 | 
					  .max(255, { message: "Description cannot exceed 255 characters" }),
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const EditContactCategory= ({data,onClose}) => {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const[isLoading,setIsLoading] = useState(false)
 | 
				
			||||||
 | 
					     const {
 | 
				
			||||||
 | 
					      register,
 | 
				
			||||||
 | 
					      handleSubmit,
 | 
				
			||||||
 | 
					      formState: { errors },reset
 | 
				
			||||||
 | 
					      
 | 
				
			||||||
 | 
					    } = useForm({
 | 
				
			||||||
 | 
					      resolver: zodResolver(schema),
 | 
				
			||||||
 | 
					      defaultValues: {
 | 
				
			||||||
 | 
					       name: data?.name || "",
 | 
				
			||||||
 | 
					        description:data?.description || "",
 | 
				
			||||||
 | 
					      
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					  const onSubmit = (formdata) => {
 | 
				
			||||||
 | 
					      setIsLoading(true)
 | 
				
			||||||
 | 
					      const result = {
 | 
				
			||||||
 | 
					        id:data?.id,
 | 
				
			||||||
 | 
					        name: formdata?.name,
 | 
				
			||||||
 | 
					        description: formdata.description,  
 | 
				
			||||||
 | 
					      };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					     
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					      MasterRespository.updateContactCategory(data?.id,result).then((resp)=>{
 | 
				
			||||||
 | 
					        setIsLoading(false) 
 | 
				
			||||||
 | 
					        showToast("Contact Category Updated successfully.", "success");
 | 
				
			||||||
 | 
					        const cachedData = getCachedData("Contact Category");
 | 
				
			||||||
 | 
					        if (cachedData) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          const updatedData = cachedData.map((category) =>
 | 
				
			||||||
 | 
					            category.id === data?.id ? { ...category, ...resp.data } : category
 | 
				
			||||||
 | 
					          );
 | 
				
			||||||
 | 
					          cacheData("Contact Category", updatedData);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        onClose()
 | 
				
			||||||
 | 
					      }).catch((error)=>{
 | 
				
			||||||
 | 
					         showToast(error?.response?.data?.message, "error")
 | 
				
			||||||
 | 
					        setIsLoading(false)
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					     
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    const resetForm = () => {
 | 
				
			||||||
 | 
					      reset({
 | 
				
			||||||
 | 
					        name: "",
 | 
				
			||||||
 | 
					        description: ""
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					      setDescriptionLength(0);
 | 
				
			||||||
 | 
					    }    
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    useEffect(()=>{
 | 
				
			||||||
 | 
					        return ()=>resetForm()
 | 
				
			||||||
 | 
					    },[])
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    const [descriptionLength, setDescriptionLength] = useState(0);
 | 
				
			||||||
 | 
					    const maxDescriptionLength = 255;    
 | 
				
			||||||
 | 
					  return (<>
 | 
				
			||||||
 | 
					    <form  className="row g-2" onSubmit={handleSubmit(onSubmit)}>
 | 
				
			||||||
 | 
					        <div className="col-12 col-md-12">
 | 
				
			||||||
 | 
					          <label className="form-label">Category Name</label>
 | 
				
			||||||
 | 
					          <input type="text" 
 | 
				
			||||||
 | 
					           {...register("name")} 
 | 
				
			||||||
 | 
					             className={`form-control ${errors.name ? 'is-invalids' : ''}`}
 | 
				
			||||||
 | 
					          />
 | 
				
			||||||
 | 
					          {errors.name && <p className="text-danger">{errors.name.message}</p>}
 | 
				
			||||||
 | 
					        </div> 
 | 
				
			||||||
 | 
					        <div className="col-12 col-md-12">
 | 
				
			||||||
 | 
					          <label className="form-label" htmlFor="description">Description</label>
 | 
				
			||||||
 | 
					        <textarea
 | 
				
			||||||
 | 
					          rows="3"
 | 
				
			||||||
 | 
					          {...register("description")}
 | 
				
			||||||
 | 
					          className={`form-control ${errors.description ? 'is-invalids' : ''}`}
 | 
				
			||||||
 | 
					          onChange={(e) => {
 | 
				
			||||||
 | 
					            setDescriptionLength(e.target.value.length);
 | 
				
			||||||
 | 
					            register("description").onChange(e);
 | 
				
			||||||
 | 
					          }}
 | 
				
			||||||
 | 
					        ></textarea>
 | 
				
			||||||
 | 
					        <div className="text-end small text-muted">
 | 
				
			||||||
 | 
					          {maxDescriptionLength - descriptionLength} characters left
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					          {errors.description && (
 | 
				
			||||||
 | 
					            <p className="text-danger">{errors.description.message}</p>
 | 
				
			||||||
 | 
					          )}
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <div className="col-12 text-center">
 | 
				
			||||||
 | 
					        <button type="submit" className="btn btn-sm btn-primary me-3">           
 | 
				
			||||||
 | 
					         {isLoading? "Please Wait...":"Submit"}       
 | 
				
			||||||
 | 
					        </button>
 | 
				
			||||||
 | 
					        <button
 | 
				
			||||||
 | 
					          type="reset"
 | 
				
			||||||
 | 
					          className="btn btn-sm btn-label-secondary "
 | 
				
			||||||
 | 
					          data-bs-dismiss="modal"
 | 
				
			||||||
 | 
					          aria-label="Close"
 | 
				
			||||||
 | 
					        >
 | 
				
			||||||
 | 
					          Cancel
 | 
				
			||||||
 | 
					        </button>
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					     
 | 
				
			||||||
 | 
					    </form>
 | 
				
			||||||
 | 
					 
 | 
				
			||||||
 | 
					    </>
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default EditContactCategory;
 | 
				
			||||||
							
								
								
									
										126
									
								
								src/components/master/EditContactTag.jsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										126
									
								
								src/components/master/EditContactTag.jsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,126 @@
 | 
				
			|||||||
 | 
					import React,{useState,useEffect} from 'react'
 | 
				
			||||||
 | 
					import {useForm} from 'react-hook-form';
 | 
				
			||||||
 | 
					import { z } from 'zod';
 | 
				
			||||||
 | 
					import { zodResolver } from '@hookform/resolvers/zod';
 | 
				
			||||||
 | 
					import { MasterRespository } from '../../repositories/MastersRepository';
 | 
				
			||||||
 | 
					import { clearApiCacheKey } from '../../slices/apiCacheSlice';
 | 
				
			||||||
 | 
					import { getCachedData,cacheData } from '../../slices/apiDataManager';
 | 
				
			||||||
 | 
					import showToast from '../../services/toastService';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const schema = z.object({
 | 
				
			||||||
 | 
					  name: z.string().min(1, { message: "Tag name is required" }),
 | 
				
			||||||
 | 
					  description: z.string().min(1, { message: "Description is required" })
 | 
				
			||||||
 | 
					  .max(255, { message: "Description cannot exceed 255 characters" }),
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const EditContactTag= ({data,onClose}) => {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const[isLoading,setIsLoading] = useState(false)
 | 
				
			||||||
 | 
					     const {
 | 
				
			||||||
 | 
					      register,
 | 
				
			||||||
 | 
					      handleSubmit,
 | 
				
			||||||
 | 
					      formState: { errors },reset
 | 
				
			||||||
 | 
					      
 | 
				
			||||||
 | 
					    } = useForm({
 | 
				
			||||||
 | 
					      resolver: zodResolver(schema),
 | 
				
			||||||
 | 
					      defaultValues: {
 | 
				
			||||||
 | 
					       name: data?.name || "",
 | 
				
			||||||
 | 
					        description:data?.description || "",
 | 
				
			||||||
 | 
					      
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					  const onSubmit = (formdata) => {
 | 
				
			||||||
 | 
					      setIsLoading(true)
 | 
				
			||||||
 | 
					      const result = {
 | 
				
			||||||
 | 
					        id:data?.id,
 | 
				
			||||||
 | 
					        name: formdata?.name,
 | 
				
			||||||
 | 
					        description: formdata.description,  
 | 
				
			||||||
 | 
					      };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					     
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					      MasterRespository.updateContactTag(data?.id,result).then((resp)=>{
 | 
				
			||||||
 | 
					        setIsLoading(false) 
 | 
				
			||||||
 | 
					        showToast("Contact Tag Updated successfully.", "success");
 | 
				
			||||||
 | 
					        const cachedData = getCachedData("Contact Tag");
 | 
				
			||||||
 | 
					        if (cachedData) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          const updatedData = cachedData.map((category) =>
 | 
				
			||||||
 | 
					            category.id === data?.id ? { ...category, ...resp.data } : category
 | 
				
			||||||
 | 
					          );
 | 
				
			||||||
 | 
					          cacheData("Contact Tag", updatedData);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        onClose()
 | 
				
			||||||
 | 
					      }).catch((error)=>{
 | 
				
			||||||
 | 
					         showToast(error?.response?.data?.message, "error")
 | 
				
			||||||
 | 
					        setIsLoading(false)
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					     
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    const resetForm = () => {
 | 
				
			||||||
 | 
					      reset({
 | 
				
			||||||
 | 
					        name: "",
 | 
				
			||||||
 | 
					        description: ""
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					      setDescriptionLength(0);
 | 
				
			||||||
 | 
					    }    
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    useEffect(()=>{
 | 
				
			||||||
 | 
					        return ()=>resetForm()
 | 
				
			||||||
 | 
					    },[])
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    const [descriptionLength, setDescriptionLength] = useState(0);
 | 
				
			||||||
 | 
					    const maxDescriptionLength = 255;    
 | 
				
			||||||
 | 
					  return (<>
 | 
				
			||||||
 | 
					    <form  className="row g-2" onSubmit={handleSubmit(onSubmit)}>
 | 
				
			||||||
 | 
					        <div className="col-12 col-md-12">
 | 
				
			||||||
 | 
					          <label className="form-label">Tag Name</label>
 | 
				
			||||||
 | 
					          <input type="text" 
 | 
				
			||||||
 | 
					           {...register("name")} 
 | 
				
			||||||
 | 
					             className={`form-control ${errors.name ? 'is-invalids' : ''}`}
 | 
				
			||||||
 | 
					          />
 | 
				
			||||||
 | 
					          {errors.name && <p className="text-danger">{errors.name.message}</p>}
 | 
				
			||||||
 | 
					        </div> 
 | 
				
			||||||
 | 
					        <div className="col-12 col-md-12">
 | 
				
			||||||
 | 
					          <label className="form-label" htmlFor="description">Description</label>
 | 
				
			||||||
 | 
					        <textarea
 | 
				
			||||||
 | 
					          rows="3"
 | 
				
			||||||
 | 
					          {...register("description")}
 | 
				
			||||||
 | 
					          className={`form-control ${errors.description ? 'is-invalids' : ''}`}
 | 
				
			||||||
 | 
					          onChange={(e) => {
 | 
				
			||||||
 | 
					            setDescriptionLength(e.target.value.length);
 | 
				
			||||||
 | 
					            register("description").onChange(e);
 | 
				
			||||||
 | 
					          }}
 | 
				
			||||||
 | 
					        ></textarea>
 | 
				
			||||||
 | 
					        <div className="text-end small text-muted">
 | 
				
			||||||
 | 
					          {maxDescriptionLength - descriptionLength} characters left
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					          {errors.description && (
 | 
				
			||||||
 | 
					            <p className="text-danger">{errors.description.message}</p>
 | 
				
			||||||
 | 
					          )}
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <div className="col-12 text-center">
 | 
				
			||||||
 | 
					        <button type="submit" className="btn btn-sm btn-primary me-3">           
 | 
				
			||||||
 | 
					         {isLoading? "Please Wait...":"Submit"}       
 | 
				
			||||||
 | 
					        </button>
 | 
				
			||||||
 | 
					        <button
 | 
				
			||||||
 | 
					          type="reset"
 | 
				
			||||||
 | 
					          className="btn btn-sm btn-label-secondary "
 | 
				
			||||||
 | 
					          data-bs-dismiss="modal"
 | 
				
			||||||
 | 
					          aria-label="Close"
 | 
				
			||||||
 | 
					        >
 | 
				
			||||||
 | 
					          Cancel
 | 
				
			||||||
 | 
					        </button>
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					     
 | 
				
			||||||
 | 
					    </form>
 | 
				
			||||||
 | 
					 
 | 
				
			||||||
 | 
					    </>
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default EditContactTag;
 | 
				
			||||||
@ -13,6 +13,10 @@ import {cacheData, getCachedData} from "../../slices/apiDataManager";
 | 
				
			|||||||
import showToast from "../../services/toastService";
 | 
					import showToast from "../../services/toastService";
 | 
				
			||||||
import CreateWorkCategory from "./CreateWorkCategory";
 | 
					import CreateWorkCategory from "./CreateWorkCategory";
 | 
				
			||||||
import EditWorkCategory from "./EditWorkCategory";
 | 
					import EditWorkCategory from "./EditWorkCategory";
 | 
				
			||||||
 | 
					import CreateCategory from "./CreateContactCategory";
 | 
				
			||||||
 | 
					import CreateContactTag from "./CreateContactTag";
 | 
				
			||||||
 | 
					import EditContactCategory from "./EditContactCategory";
 | 
				
			||||||
 | 
					import EditContactTag from "./EditContactTag";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const MasterModal = ({ modaldata, closeModal }) => {
 | 
					const MasterModal = ({ modaldata, closeModal }) => {
 | 
				
			||||||
@ -21,7 +25,6 @@ const MasterModal = ({ modaldata, closeModal }) => {
 | 
				
			|||||||
  const handleSelectedMasterDeleted = async () =>
 | 
					  const handleSelectedMasterDeleted = async () =>
 | 
				
			||||||
  { 
 | 
					  { 
 | 
				
			||||||
    const deleteFn = MasterRespository[modaldata.masterType];
 | 
					    const deleteFn = MasterRespository[modaldata.masterType];
 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (!deleteFn) {
 | 
					    if (!deleteFn) {
 | 
				
			||||||
      showToast(`No delete strategy defined for master type`,"error");
 | 
					      showToast(`No delete strategy defined for master type`,"error");
 | 
				
			||||||
      return false;
 | 
					      return false;
 | 
				
			||||||
@ -74,7 +77,6 @@ const MasterModal = ({ modaldata, closeModal }) => {
 | 
				
			|||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <div
 | 
					    <div
 | 
				
			||||||
      className="modal fade"
 | 
					      className="modal fade"
 | 
				
			||||||
@ -93,13 +95,16 @@ const MasterModal = ({ modaldata, closeModal }) => {
 | 
				
			|||||||
      >
 | 
					      >
 | 
				
			||||||
        <div className="modal-content">
 | 
					        <div className="modal-content">
 | 
				
			||||||
          <div className="modal-body p-sm-4 p-0">
 | 
					          <div className="modal-body p-sm-4 p-0">
 | 
				
			||||||
            <button
 | 
					            <div className="d-flex justify-content-between">
 | 
				
			||||||
 | 
					              <h6>{ `${modaldata?.modalType} `}</h6>
 | 
				
			||||||
 | 
					               <button
 | 
				
			||||||
              type="button"
 | 
					              type="button"
 | 
				
			||||||
              className="btn-close"
 | 
					              className="btn-close"
 | 
				
			||||||
              data-bs-dismiss="modal"
 | 
					              data-bs-dismiss="modal"
 | 
				
			||||||
              aria-label="Close"
 | 
					              aria-label="Close"
 | 
				
			||||||
              onClick={closeModal}
 | 
					              onClick={closeModal}
 | 
				
			||||||
            ></button>
 | 
					            ></button>
 | 
				
			||||||
 | 
					           </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            {modaldata.modalType === "Application Role" && (
 | 
					            {modaldata.modalType === "Application Role" && (
 | 
				
			||||||
              <CreateRole masmodalType={modaldata.masterType} onClose={closeModal} />
 | 
					              <CreateRole masmodalType={modaldata.masterType} onClose={closeModal} />
 | 
				
			||||||
@ -125,6 +130,18 @@ const MasterModal = ({ modaldata, closeModal }) => {
 | 
				
			|||||||
            {modaldata.modalType === "Edit-Work Category" && (
 | 
					            {modaldata.modalType === "Edit-Work Category" && (
 | 
				
			||||||
              <EditWorkCategory data={modaldata.item} onClose={closeModal} />
 | 
					              <EditWorkCategory data={modaldata.item} onClose={closeModal} />
 | 
				
			||||||
            )}
 | 
					            )}
 | 
				
			||||||
 | 
					            {modaldata.modalType === "Contact Category" && (
 | 
				
			||||||
 | 
					              <CreateCategory data={modaldata.item} onClose={closeModal} />
 | 
				
			||||||
 | 
					            )}
 | 
				
			||||||
 | 
					             {modaldata.modalType === "Edit-Contact Category" && (
 | 
				
			||||||
 | 
					              <EditContactCategory data={modaldata.item} onClose={closeModal} />
 | 
				
			||||||
 | 
					            )}
 | 
				
			||||||
 | 
					             {modaldata.modalType === "Contact Tag" && (
 | 
				
			||||||
 | 
					              <CreateContactTag data={modaldata.item} onClose={closeModal} />
 | 
				
			||||||
 | 
					            )}
 | 
				
			||||||
 | 
					            {modaldata.modalType === "Edit-Contact Tag" && (
 | 
				
			||||||
 | 
					              <EditContactTag data={modaldata.item} onClose={closeModal} />
 | 
				
			||||||
 | 
					            )}
 | 
				
			||||||
          </div>
 | 
					          </div>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
 | 
				
			|||||||
@ -1,5 +1,5 @@
 | 
				
			|||||||
// it important ------
 | 
					// it important ------
 | 
				
			||||||
export const mastersList = [ {id: 1, name: "Application Role"}, {id: 2, name: "Job Role"}, {id: 3, name: "Activity"},{id: 4, name:"Work Category"} ]
 | 
					export const mastersList = [ {id: 1, name: "Application Role"}, {id: 2, name: "Job Role"}, {id: 3, name: "Activity"},{id: 4, name:"Work Category"},{id:5,name:"Contact Category"},{id:6,name:"Contact Tag"}]
 | 
				
			||||||
// -------------------
 | 
					// -------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const dailyTask = [
 | 
					export const dailyTask = [
 | 
				
			||||||
 | 
				
			|||||||
@ -23,11 +23,6 @@
 | 
				
			|||||||
                        "text": "Employees",
 | 
					                        "text": "Employees",
 | 
				
			||||||
                        "available": true,
 | 
					                        "available": true,
 | 
				
			||||||
                        "link": "/employees"
 | 
					                        "link": "/employees"
 | 
				
			||||||
                    },
 | 
					 | 
				
			||||||
                    {
 | 
					 | 
				
			||||||
                        "text": "Directory",
 | 
					 | 
				
			||||||
                        "available": true,
 | 
					 | 
				
			||||||
                        "link": "/directory"
 | 
					 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                ]
 | 
					                ]
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
@ -68,6 +63,12 @@
 | 
				
			|||||||
                        "link": "/activities/gallary"
 | 
					                        "link": "/activities/gallary"
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                ]
 | 
					                ]
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					              {
 | 
				
			||||||
 | 
					                "text": "Directory",
 | 
				
			||||||
 | 
					                "icon": "bx bx-group",
 | 
				
			||||||
 | 
					                "available": true,
 | 
				
			||||||
 | 
					                "link": "/directory"
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                "text": "Administration",
 | 
					                "text": "Administration",
 | 
				
			||||||
 | 
				
			|||||||
@ -47,10 +47,18 @@ const useMaster = (isMa) => {
 | 
				
			|||||||
              response = await MasterRespository.getActivites();
 | 
					              response = await MasterRespository.getActivites();
 | 
				
			||||||
              response = response.data
 | 
					              response = response.data
 | 
				
			||||||
              break;
 | 
					              break;
 | 
				
			||||||
              case "Work Category":
 | 
					            case "Work Category":
 | 
				
			||||||
              response = await MasterRespository.getWorkCategory();
 | 
					              response = await MasterRespository.getWorkCategory();
 | 
				
			||||||
              response = response.data
 | 
					              response = response.data
 | 
				
			||||||
              break;
 | 
					              break;
 | 
				
			||||||
 | 
					            case "Contact Category":
 | 
				
			||||||
 | 
					              response = await MasterRespository.getContactCategory();
 | 
				
			||||||
 | 
					              response = response.data
 | 
				
			||||||
 | 
					              break;
 | 
				
			||||||
 | 
					            case "Contact Tag":
 | 
				
			||||||
 | 
					              response = await MasterRespository.getContactTag();
 | 
				
			||||||
 | 
					              response = response.data
 | 
				
			||||||
 | 
					              break;
 | 
				
			||||||
            case "Status":
 | 
					            case "Status":
 | 
				
			||||||
              response = [{description: null,featurePermission: null,id: "02dd4761-363c-49ed-8851-3d2489a3e98d",status:"status 1"},{description: null,featurePermission: null,id: "03dy9761-363c-49ed-8851-3d2489a3e98d",status:"status 2"},{description: null,featurePermission: null,id: "03dy7761-263c-49ed-8851-3d2489a3e98d",status:"Status 3"}];
 | 
					              response = [{description: null,featurePermission: null,id: "02dd4761-363c-49ed-8851-3d2489a3e98d",status:"status 1"},{description: null,featurePermission: null,id: "03dy9761-363c-49ed-8851-3d2489a3e98d",status:"status 2"},{description: null,featurePermission: null,id: "03dy7761-263c-49ed-8851-3d2489a3e98d",status:"Status 3"}];
 | 
				
			||||||
              break;
 | 
					              break;
 | 
				
			||||||
@ -149,4 +157,67 @@ export const useActivitiesMaster = () =>
 | 
				
			|||||||
      }, [] )
 | 
					      }, [] )
 | 
				
			||||||
      
 | 
					      
 | 
				
			||||||
      return {categories,categoryLoading,categoryError}
 | 
					      return {categories,categoryLoading,categoryError}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					export const useContactCategory = () =>
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  const [ contactCategory, setContactCategory ] = useState( [] )
 | 
				
			||||||
 | 
					  const [ loading, setLoading ] = useState( false )
 | 
				
			||||||
 | 
					  const [ Error, setError ] = useState()
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  const fetchConatctCategory = async() =>
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    const cache_Category = getCachedData( "Contact Category" );
 | 
				
			||||||
 | 
					    if ( !cache_Category )
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      try
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        let resp = await MasterRespository.getContactCategory();
 | 
				
			||||||
 | 
					        setContactCategory( resp.data );
 | 
				
			||||||
 | 
					        cacheData("Contact Category",resp.data)
 | 
				
			||||||
 | 
					      } catch ( error )
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        setError(error)
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    } else
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      setContactCategory(cache_Category)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  useEffect( () =>
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    fetchConatctCategory()
 | 
				
			||||||
 | 
					  }, [] )
 | 
				
			||||||
 | 
					  return { contactCategory,loading,Error}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					export const useContactTags = () => {
 | 
				
			||||||
 | 
					  const [contactTags, setContactTags] = useState([]);
 | 
				
			||||||
 | 
					  const [loading, setLoading] = useState(false);
 | 
				
			||||||
 | 
					  const [error, setError] = useState(null);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  useEffect(() => {
 | 
				
			||||||
 | 
					    const fetchContactTag = async () => {
 | 
				
			||||||
 | 
					      const cache_Tags = getCachedData("Contact Tag");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if (!cache_Tags) {
 | 
				
			||||||
 | 
					        setLoading(true);
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					          const resp = await MasterRespository.getContactTag();
 | 
				
			||||||
 | 
					          setContactTags(resp.data);
 | 
				
			||||||
 | 
					          cacheData("Contact Tag", resp.data);
 | 
				
			||||||
 | 
					        } catch (err) {
 | 
				
			||||||
 | 
					          setError(err);
 | 
				
			||||||
 | 
					        } finally {
 | 
				
			||||||
 | 
					          setLoading(false);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        setContactTags(cache_Tags);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fetchContactTag();
 | 
				
			||||||
 | 
					  }, []);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return { contactTags, loading, error };
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
							
								
								
									
										179
									
								
								src/hooks/useDirectory.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										179
									
								
								src/hooks/useDirectory.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,179 @@
 | 
				
			|||||||
 | 
					import { useEffect, useState } from "react";
 | 
				
			||||||
 | 
					import { DirectoryRepository } from "../repositories/DirectoryRepository";
 | 
				
			||||||
 | 
					import { cacheData, getCachedData } from "../slices/apiDataManager";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const useDirectory = (isActive,prefernceContacts) => {
 | 
				
			||||||
 | 
					  const [contacts, setContacts] = useState([]);
 | 
				
			||||||
 | 
					  const [loading, setLoading] = useState(false);
 | 
				
			||||||
 | 
					  const [error, setError] = useState(null);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const fetch = async (activeParam = isActive) => {
 | 
				
			||||||
 | 
					    setLoading(true);
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      const response = await DirectoryRepository.GetContacts(activeParam,prefernceContacts);
 | 
				
			||||||
 | 
					      setContacts(response.data);
 | 
				
			||||||
 | 
					      cacheData("contacts", { data: response.data, isActive: activeParam });
 | 
				
			||||||
 | 
					    } catch (error) {
 | 
				
			||||||
 | 
					      setError(error);
 | 
				
			||||||
 | 
					    } finally {
 | 
				
			||||||
 | 
					      setLoading(false);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  useEffect(() => {
 | 
				
			||||||
 | 
					    const cachedContacts = getCachedData("contacts");
 | 
				
			||||||
 | 
					    if (!cachedContacts?.data || cachedContacts.isActive !== isActive || prefernceContacts) {
 | 
				
			||||||
 | 
					      fetch(isActive,prefernceContacts);
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      setContacts(cachedContacts.data);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }, [isActive,prefernceContacts]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return {
 | 
				
			||||||
 | 
					    contacts,
 | 
				
			||||||
 | 
					    loading,
 | 
				
			||||||
 | 
					    error,
 | 
				
			||||||
 | 
					    refetch: fetch,
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const useBuckets = () => {
 | 
				
			||||||
 | 
					  const [buckets, setBuckets] = useState([]);
 | 
				
			||||||
 | 
					  const [loading, setLoading] = useState(false);
 | 
				
			||||||
 | 
					  const [error, setError] = useState("");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const fetchBuckets = async () => {
 | 
				
			||||||
 | 
					    setLoading(true);
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      const resp = await DirectoryRepository.GetBucktes();
 | 
				
			||||||
 | 
					      setBuckets(resp.data);
 | 
				
			||||||
 | 
					      cacheData("buckets", resp.data);
 | 
				
			||||||
 | 
					      setLoading(false);
 | 
				
			||||||
 | 
					    } catch (error) {
 | 
				
			||||||
 | 
					      const msg =
 | 
				
			||||||
 | 
					        error?.response?.data?.message ||
 | 
				
			||||||
 | 
					        error?.message ||
 | 
				
			||||||
 | 
					        "Something went wrong";
 | 
				
			||||||
 | 
					      setError( msg );
 | 
				
			||||||
 | 
					      setLoading(false);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  useEffect(() => {
 | 
				
			||||||
 | 
					    const cacheBuckets = getCachedData("buckets");
 | 
				
			||||||
 | 
					    if (!cacheBuckets) {
 | 
				
			||||||
 | 
					      fetchBuckets();
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      setBuckets(cacheBuckets);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }, []);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return { buckets, loading, error, refetch: fetchBuckets };
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const useContactProfile = (id) => {
 | 
				
			||||||
 | 
					  const [contactProfile, setContactProfile] = useState(null);
 | 
				
			||||||
 | 
					  const [loading, setLoading] = useState(false);
 | 
				
			||||||
 | 
					  const [Error, setError] = useState("");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const fetchContactProfile = async () => {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      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);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  useEffect( () =>
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    const cached = getCachedData("Contact Profile");
 | 
				
			||||||
 | 
					       if (!cached || cached.contactId !== id) {
 | 
				
			||||||
 | 
					      fetchContactProfile(id);
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      setContactProfile(cached.data);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }, [id]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return { contactProfile, loading, Error ,refetch:fetchContactProfile};
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const useContactNotes = (id, IsActive) => {
 | 
				
			||||||
 | 
					  const [contactNotes, setContactNotes] = useState([]);
 | 
				
			||||||
 | 
					  const [loading, setLoading] = useState(false);
 | 
				
			||||||
 | 
					  const [Error, setError] = useState("");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const fetchContactNotes = async (id,IsActive) => {
 | 
				
			||||||
 | 
					   
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      setLoading(true);
 | 
				
			||||||
 | 
					      try {
 | 
				
			||||||
 | 
					        const resp = await DirectoryRepository.GetNote(id, IsActive);
 | 
				
			||||||
 | 
					        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);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					   
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  useEffect(() => {
 | 
				
			||||||
 | 
					    const cached = getCachedData("Contact Notes");
 | 
				
			||||||
 | 
					    if (!cached || cached.contactId !== id) {
 | 
				
			||||||
 | 
					      id && fetchContactNotes(id,IsActive);
 | 
				
			||||||
 | 
					     } else {
 | 
				
			||||||
 | 
					      setContactNotes(cached.data);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }, [id,IsActive]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return { contactNotes, loading, Error,refetch:fetchContactNotes };
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const useOrganization = () => {
 | 
				
			||||||
 | 
					  const [organizationList, setOrganizationList] = useState([]);
 | 
				
			||||||
 | 
					  const [loading, setLoading] = useState(false);
 | 
				
			||||||
 | 
					  const [error, setError] = useState("");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const fetchOrg = async () => {
 | 
				
			||||||
 | 
					    const cacheOrg = getCachedData("organizations");
 | 
				
			||||||
 | 
					    if (cacheOrg?.length != 0) {
 | 
				
			||||||
 | 
					      setLoading(true);
 | 
				
			||||||
 | 
					      try {
 | 
				
			||||||
 | 
					        const resp = await DirectoryRepository.GetOrganizations();
 | 
				
			||||||
 | 
					        cacheData("organizations", resp.data);
 | 
				
			||||||
 | 
					        setOrganizationList(resp.data);
 | 
				
			||||||
 | 
					        setLoading(false);
 | 
				
			||||||
 | 
					      } catch (error) {
 | 
				
			||||||
 | 
					        const msg =
 | 
				
			||||||
 | 
					          error?.response?.data?.message ||
 | 
				
			||||||
 | 
					          error?.message ||
 | 
				
			||||||
 | 
					          "Something went wrong";
 | 
				
			||||||
 | 
					        setError(msg);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      setOrganizationList(cacheOrg);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  useEffect(() => {
 | 
				
			||||||
 | 
					    fetchOrg();
 | 
				
			||||||
 | 
					  }, []);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return { organizationList, loading, error };
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
@ -4,6 +4,7 @@ import ProjectRepository from "../repositories/ProjectRepository";
 | 
				
			|||||||
import { useProfile } from "./useProfile";
 | 
					import { useProfile } from "./useProfile";
 | 
				
			||||||
import { useDispatch, useSelector } from "react-redux";
 | 
					import { useDispatch, useSelector } from "react-redux";
 | 
				
			||||||
import { setProjectId } from "../slices/localVariablesSlice";
 | 
					import { setProjectId } from "../slices/localVariablesSlice";
 | 
				
			||||||
 | 
					import EmployeeList from "../components/Directory/EmployeeList";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const useProjects = () => {
 | 
					export const useProjects = () => {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -130,3 +131,48 @@ export const useProjectDetails = (projectId) => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  return { projects_Details, loading, error, refetch: fetchData }
 | 
					  return { projects_Details, loading, error, refetch: fetchData }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const useProjectsByEmployee = ( employeeId ) =>
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  const [projectList, setProjectList] = useState([]);
 | 
				
			||||||
 | 
					  const [loading, setLoading] = useState(false);
 | 
				
			||||||
 | 
					  const [error, setError] = useState('');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const fetchProjects = async (id) => {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      setLoading(true);
 | 
				
			||||||
 | 
					      setError(''); // clear previous error
 | 
				
			||||||
 | 
					      const res = await ProjectRepository.getProjectsByEmployee(id);
 | 
				
			||||||
 | 
					      setProjectList(res.data);
 | 
				
			||||||
 | 
					      cacheData( 'ProjectsByEmployee', {data: res.data, employeeId: id} );
 | 
				
			||||||
 | 
					      setLoading(false)
 | 
				
			||||||
 | 
					    } catch (err) {
 | 
				
			||||||
 | 
					      setError( err?.message || 'Failed to fetch projects' );
 | 
				
			||||||
 | 
					      setLoading(false)
 | 
				
			||||||
 | 
					    } 
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  useEffect(() => {
 | 
				
			||||||
 | 
					    if (!employeeId) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const cache_project = getCachedData('ProjectsByEmployee');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (
 | 
				
			||||||
 | 
					      !cache_project?.data ||
 | 
				
			||||||
 | 
					      cache_project?.employeeId !== employeeId
 | 
				
			||||||
 | 
					    ) {
 | 
				
			||||||
 | 
					      fetchProjects(employeeId);
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      setProjectList(cache_project.data);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }, [employeeId]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return {
 | 
				
			||||||
 | 
					    projectList,
 | 
				
			||||||
 | 
					    loading,
 | 
				
			||||||
 | 
					    error,
 | 
				
			||||||
 | 
					    refetch : fetchProjects
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										35
									
								
								src/hooks/useSortableData.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								src/hooks/useSortableData.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,35 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					import { useState, useMemo } from 'react';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const useSortableData = (items, config = null) => {
 | 
				
			||||||
 | 
					  const [sortConfig, setSortConfig] = useState(config);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const sortedItems = useMemo(() => {
 | 
				
			||||||
 | 
					    let sortableItems = [...items];
 | 
				
			||||||
 | 
					    if (sortConfig !== null) {
 | 
				
			||||||
 | 
					      sortableItems.sort((a, b) => {
 | 
				
			||||||
 | 
					        const aValue = sortConfig.key(a).toLowerCase();
 | 
				
			||||||
 | 
					        const bValue = sortConfig.key(b).toLowerCase();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (aValue < bValue) return sortConfig.direction === 'asc' ? -1 : 1;
 | 
				
			||||||
 | 
					        if (aValue > bValue) return sortConfig.direction === 'asc' ? 1 : -1;
 | 
				
			||||||
 | 
					        return 0;
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return sortableItems;
 | 
				
			||||||
 | 
					  }, [items, sortConfig]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const requestSort = (keyFn) => {
 | 
				
			||||||
 | 
					    let direction = 'asc';
 | 
				
			||||||
 | 
					    if (
 | 
				
			||||||
 | 
					      sortConfig &&
 | 
				
			||||||
 | 
					      sortConfig.key.toString() === keyFn.toString() &&
 | 
				
			||||||
 | 
					      sortConfig.direction === 'asc'
 | 
				
			||||||
 | 
					    ) {
 | 
				
			||||||
 | 
					      direction = 'desc';
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    setSortConfig({ key: keyFn, direction });
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return { items: sortedItems, requestSort, sortConfig };
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
@ -165,3 +165,6 @@ padding: 1px !important;
 | 
				
			|||||||
.accordion-button:not(.collapsed) .toggle-icon {
 | 
					.accordion-button:not(.collapsed) .toggle-icon {
 | 
				
			||||||
  content: "\f146"; /* minus-circle */
 | 
					  content: "\f146"; /* minus-circle */
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					.hoverBox:hover{
 | 
				
			||||||
 | 
					  background-color: #f1f3f5;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -4,25 +4,30 @@ import Header from "../components/Layout/Header";
 | 
				
			|||||||
import Sidebar from "../components/Layout/Sidebar";
 | 
					import Sidebar from "../components/Layout/Sidebar";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import Footer from "../components/Layout/Footer";
 | 
					import Footer from "../components/Layout/Footer";
 | 
				
			||||||
 | 
					import FloatingMenu from "../components/common/FloatingMenu";
 | 
				
			||||||
 | 
					import { FabProvider } from "../Context/FabContext";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const HomeLayout = () => {
 | 
					const HomeLayout = () => {
 | 
				
			||||||
  useEffect(() => {
 | 
					  useEffect(() => {
 | 
				
			||||||
    Main();
 | 
					    Main();
 | 
				
			||||||
  }, []);
 | 
					  }, []);
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <div className="layout-wrapper layout-content-navbar" >
 | 
					    <FabProvider>
 | 
				
			||||||
      <div className="layout-container" >
 | 
					      <div className="layout-wrapper layout-content-navbar">
 | 
				
			||||||
        <Sidebar />
 | 
					        <div className="layout-container">
 | 
				
			||||||
        <div className="layout-page ">
 | 
					          <Sidebar />
 | 
				
			||||||
          <Header />
 | 
					          <div className="layout-page ">
 | 
				
			||||||
          <div className="content-wrapper"  >
 | 
					            <Header />
 | 
				
			||||||
            <Outlet />
 | 
					            <div className="content-wrapper">
 | 
				
			||||||
            <Footer />
 | 
					              <Outlet />
 | 
				
			||||||
 | 
					              <Footer />
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
          </div>
 | 
					          </div>
 | 
				
			||||||
 | 
					          <FloatingMenu />
 | 
				
			||||||
 | 
					          <div className="layout-overlay layout-menu-toggle"></div>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
        <div className="layout-overlay layout-menu-toggle"></div>
 | 
					 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
    </div>
 | 
					    </FabProvider>
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -141,7 +141,7 @@ const AttendancePage = () => {
 | 
				
			|||||||
          ]}
 | 
					          ]}
 | 
				
			||||||
        ></Breadcrumb>
 | 
					        ></Breadcrumb>
 | 
				
			||||||
        <div className="nav-align-top nav-tabs-shadow">
 | 
					        <div className="nav-align-top nav-tabs-shadow">
 | 
				
			||||||
          <ul className="nav nav-tabs" role="tablist">
 | 
					          <ul className="nav nav-tabs align-items-center" role="tablist">
 | 
				
			||||||
            <div
 | 
					            <div
 | 
				
			||||||
              className="dataTables_length text-start py-2 px-2 d-flex "
 | 
					              className="dataTables_length text-start py-2 px-2 d-flex "
 | 
				
			||||||
              id="DataTables_Table_0_length"
 | 
					              id="DataTables_Table_0_length"
 | 
				
			||||||
@ -175,6 +175,25 @@ const AttendancePage = () => {
 | 
				
			|||||||
                </label>
 | 
					                </label>
 | 
				
			||||||
              )}
 | 
					              )}
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
 | 
					            <li
 | 
				
			||||||
 | 
					              className={`nav-item ms-auto ${
 | 
				
			||||||
 | 
					                activeTab === "regularization" ? "d-none" : ""
 | 
				
			||||||
 | 
					              }`}
 | 
				
			||||||
 | 
					            >
 | 
				
			||||||
 | 
					              <label className="switch switch-primary">
 | 
				
			||||||
 | 
					                <input
 | 
				
			||||||
 | 
					                  type="checkbox"
 | 
				
			||||||
 | 
					                  className="switch-input"
 | 
				
			||||||
 | 
					                  checked={showOnlyCheckout}
 | 
				
			||||||
 | 
					                  onChange={handleToggle}
 | 
				
			||||||
 | 
					                />
 | 
				
			||||||
 | 
					                <span className="switch-toggle-slider">
 | 
				
			||||||
 | 
					                  <span className="switch-on"></span>
 | 
				
			||||||
 | 
					                  <span className="switch-off"></span>
 | 
				
			||||||
 | 
					                </span>
 | 
				
			||||||
 | 
					                <span className="switch-label m-2">Pending Actions</span>
 | 
				
			||||||
 | 
					              </label>
 | 
				
			||||||
 | 
					            </li>
 | 
				
			||||||
          </ul>
 | 
					          </ul>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          <ul className="nav nav-tabs" role="tablist">
 | 
					          <ul className="nav nav-tabs" role="tablist">
 | 
				
			||||||
@ -214,27 +233,9 @@ const AttendancePage = () => {
 | 
				
			|||||||
              </button>
 | 
					              </button>
 | 
				
			||||||
            </li>
 | 
					            </li>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            <li
 | 
					            
 | 
				
			||||||
              className={`nav-item ms-auto ${
 | 
					 | 
				
			||||||
                activeTab === "regularization" ? "d-none" : ""
 | 
					 | 
				
			||||||
              }`}
 | 
					 | 
				
			||||||
            >
 | 
					 | 
				
			||||||
              <label className="switch switch-primary">
 | 
					 | 
				
			||||||
                <input
 | 
					 | 
				
			||||||
                  type="checkbox"
 | 
					 | 
				
			||||||
                  className="switch-input"
 | 
					 | 
				
			||||||
                  checked={showOnlyCheckout}
 | 
					 | 
				
			||||||
                  onChange={handleToggle}
 | 
					 | 
				
			||||||
                />
 | 
					 | 
				
			||||||
                <span className="switch-toggle-slider">
 | 
					 | 
				
			||||||
                  <span className="switch-on"></span>
 | 
					 | 
				
			||||||
                  <span className="switch-off"></span>
 | 
					 | 
				
			||||||
                </span>
 | 
					 | 
				
			||||||
                <span className="switch-label m-2">Pending Actions</span>
 | 
					 | 
				
			||||||
              </label>
 | 
					 | 
				
			||||||
            </li>
 | 
					 | 
				
			||||||
          </ul>
 | 
					          </ul>
 | 
				
			||||||
          <div className="tab-content attedanceTabs py-2">
 | 
					          <div className="tab-content attedanceTabs py-2 px-1 px-sm-3">
 | 
				
			||||||
            {projectLoading && <span>Loading..</span>}
 | 
					            {projectLoading && <span>Loading..</span>}
 | 
				
			||||||
            {!projectLoading && !attendances && <span>Not Found</span>}
 | 
					            {!projectLoading && !attendances && <span>Not Found</span>}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -26,20 +26,17 @@ const DailyTask = () => {
 | 
				
			|||||||
  const [initialized, setInitialized] = useState(false);
 | 
					  const [initialized, setInitialized] = useState(false);
 | 
				
			||||||
  const dispatch = useDispatch();
 | 
					  const dispatch = useDispatch();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // State for filters (moved to FilterIcon, but we need to receive them here)
 | 
					 | 
				
			||||||
  const [filters, setFilters] = useState({
 | 
					  const [filters, setFilters] = useState({
 | 
				
			||||||
    selectedBuilding: "",
 | 
					    selectedBuilding: "",
 | 
				
			||||||
    selectedFloors: [],
 | 
					    selectedFloors: [],
 | 
				
			||||||
    selectedActivities: [],
 | 
					    selectedActivities: [],
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Sync projectId (either from URL or pick first accessible one)
 | 
					 | 
				
			||||||
  useEffect(() => {
 | 
					  useEffect(() => {
 | 
				
			||||||
    if (!project_loading && projects.length > 0 && !initialized) {
 | 
					    if (!project_loading && projects.length > 0 && !initialized) {
 | 
				
			||||||
      if (projectIdFromUrl) {
 | 
					      if (projectIdFromUrl) {
 | 
				
			||||||
        dispatch(setProjectId(projectIdFromUrl));
 | 
					        dispatch(setProjectId(projectIdFromUrl));
 | 
				
			||||||
      } else if (selectedProject === 1 || selectedProject === undefined) {
 | 
					      } else if (selectedProject === 1 || selectedProject === undefined) {
 | 
				
			||||||
        // If no project from URL or default/undefined, pick the first project
 | 
					 | 
				
			||||||
        dispatch(setProjectId(projects[0].id));
 | 
					        dispatch(setProjectId(projects[0].id));
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      setInitialized(true);
 | 
					      setInitialized(true);
 | 
				
			||||||
@ -57,7 +54,7 @@ const DailyTask = () => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  const {
 | 
					  const {
 | 
				
			||||||
    TaskList,
 | 
					    TaskList,
 | 
				
			||||||
    loading: task_loading, // This `loading` state indicates if task data is being fetched
 | 
					    loading: task_loading, 
 | 
				
			||||||
    error: task_error,
 | 
					    error: task_error,
 | 
				
			||||||
    refetch,
 | 
					    refetch,
 | 
				
			||||||
  } = useTaskList(
 | 
					  } = useTaskList(
 | 
				
			||||||
@ -66,13 +63,11 @@ const DailyTask = () => {
 | 
				
			|||||||
    initialized ? dateRange.endDate : null
 | 
					    initialized ? dateRange.endDate : null
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const [TaskLists, setTaskLists] = useState([]); // This state holds the *filtered* tasks for display
 | 
					  const [TaskLists, setTaskLists] = useState([]); 
 | 
				
			||||||
  const [dates, setDates] = useState([]);
 | 
					  const [dates, setDates] = useState([]);
 | 
				
			||||||
  const popoverRefs = useRef([]);
 | 
					  const popoverRefs = useRef([]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Effect to apply filters to TaskList (from useTaskList) and update TaskLists (filtered display)
 | 
					 | 
				
			||||||
  useEffect(() => {
 | 
					  useEffect(() => {
 | 
				
			||||||
    // Only filter if TaskList is available (not null or undefined)
 | 
					 | 
				
			||||||
    if (TaskList) {
 | 
					    if (TaskList) {
 | 
				
			||||||
      let filteredTasks = TaskList;
 | 
					      let filteredTasks = TaskList;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -101,8 +96,6 @@ const DailyTask = () => {
 | 
				
			|||||||
      }
 | 
					      }
 | 
				
			||||||
      setTaskLists(filteredTasks);
 | 
					      setTaskLists(filteredTasks);
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
      // If TaskList is null (e.g., during initial load or project change before data arrives),
 | 
					 | 
				
			||||||
      // ensure TaskLists is also empty to avoid displaying stale data.
 | 
					 | 
				
			||||||
      setTaskLists([]);
 | 
					      setTaskLists([]);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }, [
 | 
					  }, [
 | 
				
			||||||
@ -137,19 +130,9 @@ const DailyTask = () => {
 | 
				
			|||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  useEffect(() => {
 | 
					  useEffect(() => {
 | 
				
			||||||
    // Ensure Bootstrap's Popover is initialized correctly
 | 
					 | 
				
			||||||
    popoverRefs.current.forEach((el) => {
 | 
					    popoverRefs.current.forEach((el) => {
 | 
				
			||||||
      if (
 | 
					      if (el) {
 | 
				
			||||||
        el &&
 | 
					        new bootstrap.Popover(el, {
 | 
				
			||||||
        window.bootstrap &&
 | 
					 | 
				
			||||||
        typeof window.bootstrap.Popover === "function"
 | 
					 | 
				
			||||||
      ) {
 | 
					 | 
				
			||||||
        // Dispose existing popovers to prevent duplicates if component re-renders
 | 
					 | 
				
			||||||
        const existingPopover = window.bootstrap.Popover.getInstance(el);
 | 
					 | 
				
			||||||
        if (existingPopover) {
 | 
					 | 
				
			||||||
          existingPopover.dispose();
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        new window.bootstrap.Popover(el, {
 | 
					 | 
				
			||||||
          trigger: "focus",
 | 
					          trigger: "focus",
 | 
				
			||||||
          placement: "left",
 | 
					          placement: "left",
 | 
				
			||||||
          html: true,
 | 
					          html: true,
 | 
				
			||||||
@ -157,32 +140,12 @@ const DailyTask = () => {
 | 
				
			|||||||
        });
 | 
					        });
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					  },[dates, TaskLists]);
 | 
				
			||||||
 
 | 
					 
 | 
				
			||||||
    // Cleanup function for popovers when component unmounts or dependencies change
 | 
					 | 
				
			||||||
    return () => {
 | 
					 | 
				
			||||||
      popoverRefs.current.forEach((el) => {
 | 
					 | 
				
			||||||
        if (
 | 
					 | 
				
			||||||
          el &&
 | 
					 | 
				
			||||||
          window.bootstrap &&
 | 
					 | 
				
			||||||
          typeof window.bootstrap.Popover === "function"
 | 
					 | 
				
			||||||
        ) {
 | 
					 | 
				
			||||||
          const existingPopover = window.bootstrap.Popover.getInstance(el);
 | 
					 | 
				
			||||||
          if (existingPopover) {
 | 
					 | 
				
			||||||
            existingPopover.dispose();
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
      popoverRefs.current = []; // Clear the refs array
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
  }, [dates, TaskLists]); // Re-initialize popovers when tasks or dates change
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // Handler for project selection
 | 
					 | 
				
			||||||
  const handleProjectChange = (e) => {
 | 
					  const handleProjectChange = (e) => {
 | 
				
			||||||
    const newProjectId = e.target.value;
 | 
					    const newProjectId = e.target.value;
 | 
				
			||||||
    dispatch(setProjectId(newProjectId));
 | 
					    dispatch(setProjectId(newProjectId));
 | 
				
			||||||
    // --- IMPORTANT: Clear old data immediately to show loading state ---
 | 
					    setTaskLists([]); 
 | 
				
			||||||
    setTaskLists([]); // This makes the table empty, allowing the spinner to show
 | 
					 | 
				
			||||||
    // Reset filters when project changes (communicate to FilterIcon to clear)
 | 
					 | 
				
			||||||
    setFilters({
 | 
					    setFilters({
 | 
				
			||||||
      selectedBuilding: "",
 | 
					      selectedBuilding: "",
 | 
				
			||||||
      selectedFloors: [],
 | 
					      selectedFloors: [],
 | 
				
			||||||
@ -192,7 +155,6 @@ const DailyTask = () => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <>
 | 
					    <>
 | 
				
			||||||
      {/* Report Task Modal */}
 | 
					 | 
				
			||||||
      <div
 | 
					      <div
 | 
				
			||||||
        className={`modal fade ${isModalOpen ? "show d-block" : ""}`}
 | 
					        className={`modal fade ${isModalOpen ? "show d-block" : ""}`}
 | 
				
			||||||
        tabIndex="-1"
 | 
					        tabIndex="-1"
 | 
				
			||||||
@ -206,10 +168,8 @@ const DailyTask = () => {
 | 
				
			|||||||
          refetch={refetch}
 | 
					          refetch={refetch}
 | 
				
			||||||
        />
 | 
					        />
 | 
				
			||||||
        {isModalOpen && <div className="modal-backdrop fade show"></div>}{" "}
 | 
					        {isModalOpen && <div className="modal-backdrop fade show"></div>}{" "}
 | 
				
			||||||
        {/* Add backdrop */}
 | 
					 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      {/* Report Task Comments Modal */}
 | 
					 | 
				
			||||||
      <div
 | 
					      <div
 | 
				
			||||||
        className={`modal fade ${isModalOpenComment ? "show d-block" : ""}`}
 | 
					        className={`modal fade ${isModalOpenComment ? "show d-block" : ""}`}
 | 
				
			||||||
        tabIndex="-1"
 | 
					        tabIndex="-1"
 | 
				
			||||||
@ -222,7 +182,6 @@ const DailyTask = () => {
 | 
				
			|||||||
          closeModal={closeCommentModal}
 | 
					          closeModal={closeCommentModal}
 | 
				
			||||||
        />
 | 
					        />
 | 
				
			||||||
        {isModalOpenComment && <div className="modal-backdrop fade show"></div>}{" "}
 | 
					        {isModalOpenComment && <div className="modal-backdrop fade show"></div>}{" "}
 | 
				
			||||||
        {/* Add backdrop */}
 | 
					 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      <div className="container-xxl flex-grow-1 container-p-y">
 | 
					      <div className="container-xxl flex-grow-1 container-p-y">
 | 
				
			||||||
@ -242,15 +201,12 @@ const DailyTask = () => {
 | 
				
			|||||||
                  DateDifference="6"
 | 
					                  DateDifference="6"
 | 
				
			||||||
                  dateFormat="DD-MM-YYYY"
 | 
					                  dateFormat="DD-MM-YYYY"
 | 
				
			||||||
                />
 | 
					                />
 | 
				
			||||||
                {/* FilterIcon component now manages its own filter states and logic */}
 | 
					 | 
				
			||||||
                <FilterIcon
 | 
					                <FilterIcon
 | 
				
			||||||
                  taskListData={TaskList} // Pass the raw TaskList to FilterIcon
 | 
					                  taskListData={TaskList} 
 | 
				
			||||||
                  onApplyFilters={setFilters} // Callback to receive the filtered states from FilterIcon
 | 
					                  onApplyFilters={setFilters}
 | 
				
			||||||
                  currentSelectedBuilding={filters.selectedBuilding}
 | 
					                  currentSelectedBuilding={filters.selectedBuilding}
 | 
				
			||||||
                  currentSelectedFloors={filters.selectedFloors}
 | 
					                  currentSelectedFloors={filters.selectedFloors}
 | 
				
			||||||
                  currentSelectedActivities={filters.selectedActivities}
 | 
					                  currentSelectedActivities={filters.selectedActivities}
 | 
				
			||||||
                  // You can pass the project_loading state here if you want to disable filter during project load
 | 
					 | 
				
			||||||
                  // isProjectLoading={project_loading}
 | 
					 | 
				
			||||||
                />
 | 
					                />
 | 
				
			||||||
              </div>
 | 
					              </div>
 | 
				
			||||||
              <div className="col-md-4 col-12 text-center mb-2 mb-md-0">
 | 
					              <div className="col-md-4 col-12 text-center mb-2 mb-md-0">
 | 
				
			||||||
@ -261,7 +217,7 @@ const DailyTask = () => {
 | 
				
			|||||||
                  value={selectedProject || ""}
 | 
					                  value={selectedProject || ""}
 | 
				
			||||||
                  onChange={handleProjectChange}
 | 
					                  onChange={handleProjectChange}
 | 
				
			||||||
                  aria-label="Select Project"
 | 
					                  aria-label="Select Project"
 | 
				
			||||||
                  disabled={project_loading} // Disable dropdown while projects are loading
 | 
					                  disabled={project_loading}
 | 
				
			||||||
                >
 | 
					                >
 | 
				
			||||||
                  {project_loading && (
 | 
					                  {project_loading && (
 | 
				
			||||||
                    <option value="" disabled>
 | 
					                    <option value="" disabled>
 | 
				
			||||||
@ -296,7 +252,6 @@ const DailyTask = () => {
 | 
				
			|||||||
                    <tr>
 | 
					                    <tr>
 | 
				
			||||||
                      <td colSpan={6} className="text-center">
 | 
					                      <td colSpan={6} className="text-center">
 | 
				
			||||||
                        {" "}
 | 
					                        {" "}
 | 
				
			||||||
                        {/* ColSpan set to 6 based on your table headers */}
 | 
					 | 
				
			||||||
                        <div className="mt-10 mb-10 pt-5  pb-10">
 | 
					                        <div className="mt-10 mb-10 pt-5  pb-10">
 | 
				
			||||||
                          <div
 | 
					                          <div
 | 
				
			||||||
                            className="spinner-border text-primary"
 | 
					                            className="spinner-border text-primary"
 | 
				
			||||||
@ -309,7 +264,6 @@ const DailyTask = () => {
 | 
				
			|||||||
                      </td>
 | 
					                      </td>
 | 
				
			||||||
                    </tr>
 | 
					                    </tr>
 | 
				
			||||||
                  )}
 | 
					                  )}
 | 
				
			||||||
                  {/* --- "No Reports Found" message only if not loading and no tasks --- */}
 | 
					 | 
				
			||||||
                  {!task_loading &&
 | 
					                  {!task_loading &&
 | 
				
			||||||
                    !project_loading &&
 | 
					                    !project_loading &&
 | 
				
			||||||
                    TaskLists.length === 0 && (
 | 
					                    TaskLists.length === 0 && (
 | 
				
			||||||
@ -317,20 +271,17 @@ const DailyTask = () => {
 | 
				
			|||||||
                        <td colSpan={6} className="text-center">
 | 
					                        <td colSpan={6} className="text-center">
 | 
				
			||||||
                          <div className="mt-10 mb-10 pt-10 pb-10">
 | 
					                          <div className="mt-10 mb-10 pt-10 pb-10">
 | 
				
			||||||
                            {" "}
 | 
					                            {" "}
 | 
				
			||||||
                            {/* ColSpan set to 6 */}
 | 
					 | 
				
			||||||
                            <p>No Reports Found</p>
 | 
					                            <p>No Reports Found</p>
 | 
				
			||||||
                          </div>
 | 
					                          </div>
 | 
				
			||||||
                        </td>
 | 
					                        </td>
 | 
				
			||||||
                      </tr>
 | 
					                      </tr>
 | 
				
			||||||
                    )}
 | 
					                    )}
 | 
				
			||||||
                  {/* --- Render tasks when not loading and tasks exist --- */}
 | 
					 | 
				
			||||||
                  {!task_loading &&
 | 
					                  {!task_loading &&
 | 
				
			||||||
                    TaskLists.length > 0 &&
 | 
					                    TaskLists.length > 0 &&
 | 
				
			||||||
                    dates.map((date, i) => {
 | 
					                    dates.map((date, i) => {
 | 
				
			||||||
                      const tasksForDate = TaskLists.filter((task) =>
 | 
					                      const tasksForDate = TaskLists.filter((task) =>
 | 
				
			||||||
                        task.assignmentDate.includes(date)
 | 
					                        task.assignmentDate.includes(date)
 | 
				
			||||||
                      );
 | 
					                      );
 | 
				
			||||||
                      // Only render the date header if there are tasks for that date after filtering
 | 
					 | 
				
			||||||
                      if (tasksForDate.length === 0) return null;
 | 
					                      if (tasksForDate.length === 0) return null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                      return (
 | 
					                      return (
 | 
				
			||||||
@ -338,14 +289,13 @@ const DailyTask = () => {
 | 
				
			|||||||
                          <tr className="table-row-header">
 | 
					                          <tr className="table-row-header">
 | 
				
			||||||
                            <td colSpan={6} className="text-start">
 | 
					                            <td colSpan={6} className="text-start">
 | 
				
			||||||
                              {" "}
 | 
					                              {" "}
 | 
				
			||||||
                              {/* ColSpan set to 6 */}
 | 
					 | 
				
			||||||
                              <strong>
 | 
					                              <strong>
 | 
				
			||||||
                                {moment(date).format("DD-MM-YYYY")}
 | 
					                                {moment(date).format("DD-MM-YYYY")}
 | 
				
			||||||
                              </strong>
 | 
					                              </strong>
 | 
				
			||||||
                            </td>
 | 
					                            </td>
 | 
				
			||||||
                          </tr>
 | 
					                          </tr>
 | 
				
			||||||
                          {tasksForDate.map((task, index) => {
 | 
					                          {tasksForDate.map((task, index) => {
 | 
				
			||||||
                            const refIndex = `${i}-${index}`;
 | 
					                            const refIndex = index * 10 + i;
 | 
				
			||||||
                            return (
 | 
					                            return (
 | 
				
			||||||
                              <React.Fragment key={refIndex}>
 | 
					                              <React.Fragment key={refIndex}>
 | 
				
			||||||
                                <tr>
 | 
					                                <tr>
 | 
				
			||||||
 | 
				
			|||||||
@ -1,137 +1,478 @@
 | 
				
			|||||||
import React, { useState } from "react";
 | 
					import React, { useEffect, useMemo, useState } from "react";
 | 
				
			||||||
import Breadcrumb from "../../components/common/Breadcrumb";
 | 
					import Breadcrumb from "../../components/common/Breadcrumb";
 | 
				
			||||||
import IconButton from "../../components/common/IconButton";
 | 
					import IconButton from "../../components/common/IconButton";
 | 
				
			||||||
import GlobalModel from "../../components/common/GlobalModel";
 | 
					import GlobalModel from "../../components/common/GlobalModel";
 | 
				
			||||||
import ManageDirectory from "../../components/Directory/ManageDirectory";
 | 
					import ManageDirectory from "../../components/Directory/ManageDirectory";
 | 
				
			||||||
 | 
					import ListViewDirectory from "../../components/Directory/ListViewDirectory";
 | 
				
			||||||
 | 
					import { useBuckets, useDirectory } from "../../hooks/useDirectory";
 | 
				
			||||||
 | 
					import { DirectoryRepository } from "../../repositories/DirectoryRepository";
 | 
				
			||||||
 | 
					import { cacheData, getCachedData } from "../../slices/apiDataManager";
 | 
				
			||||||
 | 
					import showToast from "../../services/toastService";
 | 
				
			||||||
 | 
					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 ProfileContactDirectory from "../../components/Directory/ProfileContactDirectory";
 | 
				
			||||||
 | 
					import ConfirmModal from "../../components/common/ConfirmModal";
 | 
				
			||||||
 | 
					import DirectoryListTableHeader from "./DirectoryListTableHeader";
 | 
				
			||||||
 | 
					import DirectoryPageHeader from "./DirectoryPageHeader";
 | 
				
			||||||
 | 
					import ManageBucket from "../../components/Directory/ManageBucket";
 | 
				
			||||||
 | 
					import { useFab } from "../../Context/FabContext";
 | 
				
			||||||
 | 
					import { DireProvider, useDir } from "../../Context/DireContext";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const Directory = () => {
 | 
					const Directory = ({ IsPage = true, prefernceContacts }) => {
 | 
				
			||||||
 | 
					  const [projectPrefernce, setPerfence] = useState(null);
 | 
				
			||||||
 | 
					  const [IsActive, setIsActive] = useState(true);
 | 
				
			||||||
  const [isOpenModal, setIsOpenModal] = useState(false);
 | 
					  const [isOpenModal, setIsOpenModal] = useState(false);
 | 
				
			||||||
  const closedModel = () => setIsOpenModal(false);
 | 
					  const [isOpenModalNote, setIsOpenModalNote] = useState(false);
 | 
				
			||||||
 | 
					  const [selectedContact, setSelectedContact] = useState(null);
 | 
				
			||||||
 | 
					  const [open_contact, setOpen_contact] = useState(null);
 | 
				
			||||||
 | 
					  const [ContactList, setContactList] = useState([]);
 | 
				
			||||||
 | 
					  const [contactCategories, setContactCategories] = useState([]);
 | 
				
			||||||
 | 
					  const [searchText, setSearchText] = useState("");
 | 
				
			||||||
 | 
					  const [listView, setListView] = useState(false);
 | 
				
			||||||
 | 
					  const [selectedBucketIds, setSelectedBucketIds] = useState([]);
 | 
				
			||||||
 | 
					  const [deleteContact, setDeleteContact] = useState(null);
 | 
				
			||||||
 | 
					  const [IsDeleting, setDeleting] = useState(false);
 | 
				
			||||||
 | 
					  const [openBucketModal, setOpenBucketModal] = useState(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const [tempSelectedBucketIds, setTempSelectedBucketIds] = useState([]);
 | 
				
			||||||
 | 
					  const [tempSelectedCategoryIds, setTempSelectedCategoryIds] = useState([]);
 | 
				
			||||||
 | 
					  const { setActions } = useFab();
 | 
				
			||||||
 | 
					  const { dirActions, setDirActions } = useDir();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const { contacts, loading, refetch } = useDirectory(
 | 
				
			||||||
 | 
					    IsActive,
 | 
				
			||||||
 | 
					    projectPrefernce
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					  const { contactCategory, loading: contactCategoryLoading } =
 | 
				
			||||||
 | 
					    useContactCategory();
 | 
				
			||||||
 | 
					  const { buckets, refetch: refetchBucket } = useBuckets();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const submitContact = async (data) => {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      let response;
 | 
				
			||||||
 | 
					      let updatedContacts;
 | 
				
			||||||
 | 
					      const contacts_cache = getCachedData("contacts")?.data || [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if (selectedContact) {
 | 
				
			||||||
 | 
					        response = await DirectoryRepository.UpdateContact(data.id, data);
 | 
				
			||||||
 | 
					        updatedContacts = contacts_cache.map((contact) =>
 | 
				
			||||||
 | 
					          contact.id === data.id ? response.data : contact
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					        showToast("Contact updated successfully", "success");
 | 
				
			||||||
 | 
					        setIsOpenModal(false);
 | 
				
			||||||
 | 
					        setSelectedContact(null);
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        response = await DirectoryRepository.CreateContact(data);
 | 
				
			||||||
 | 
					        updatedContacts = [...contacts_cache, response.data];
 | 
				
			||||||
 | 
					        showToast("Contact created successfully", "success");
 | 
				
			||||||
 | 
					        setIsOpenModal(false);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      // cacheData("Contacts", {data:updatedContacts,isActive:IsActive});
 | 
				
			||||||
 | 
					      // setContactList(updatedContacts);
 | 
				
			||||||
 | 
					      refetch(IsActive, prefernceContacts);
 | 
				
			||||||
 | 
					      refetchBucket();
 | 
				
			||||||
 | 
					    } catch (error) {
 | 
				
			||||||
 | 
					      const msg =
 | 
				
			||||||
 | 
					        error.response?.data?.message ||
 | 
				
			||||||
 | 
					        error.message ||
 | 
				
			||||||
 | 
					        "Error occurred during API call!";
 | 
				
			||||||
 | 
					      showToast(msg, "error");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const handleDeleteContact = async (overrideId = null) => {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      if (!IsActive) {
 | 
				
			||||||
 | 
					        setDirActions((prev) => ({ ...prev, action: true }));
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        setDeleting(true);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      const id = overrideId || (!IsActive ? dirActions.id : deleteContact);
 | 
				
			||||||
 | 
					      if (!id) {
 | 
				
			||||||
 | 
					        showToast("No contact selected for deletion", "error");
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      await DirectoryRepository.DeleteContact(id, !IsActive);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      const updatedContacts = ContactList.filter((c) => c.id !== id);
 | 
				
			||||||
 | 
					      setContactList(updatedContacts);
 | 
				
			||||||
 | 
					      cacheData("Contacts", { data: updatedContacts, isActive: IsActive });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      showToast(
 | 
				
			||||||
 | 
					        `Contact ${IsActive ? "Deleted" : "Restored"} successfully`,
 | 
				
			||||||
 | 
					        "success"
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      setDeleteContact(null);
 | 
				
			||||||
 | 
					      refetchBucket();
 | 
				
			||||||
 | 
					      setDirActions({ action: false, id: null });
 | 
				
			||||||
 | 
					      setDeleting(false);
 | 
				
			||||||
 | 
					    } catch (error) {
 | 
				
			||||||
 | 
					      const msg =
 | 
				
			||||||
 | 
					        error?.response?.data?.message ||
 | 
				
			||||||
 | 
					        error.message ||
 | 
				
			||||||
 | 
					        "Error occurred during API call";
 | 
				
			||||||
 | 
					      showToast(msg, "error");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      setDeleting(false);
 | 
				
			||||||
 | 
					      setDirActions({ action: false, id: null });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const closedModel = () => {
 | 
				
			||||||
 | 
					    setIsOpenModal(false);
 | 
				
			||||||
 | 
					    setSelectedContact(null);
 | 
				
			||||||
 | 
					    setOpen_contact(null);
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					  const [selectedCategoryIds, setSelectedCategoryIds] = useState(
 | 
				
			||||||
 | 
					    contactCategory.map((category) => category.id)
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  useEffect(() => {
 | 
				
			||||||
 | 
					    setContactList(contacts);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    setTempSelectedCategoryIds([]);
 | 
				
			||||||
 | 
					    setTempSelectedBucketIds([]);
 | 
				
			||||||
 | 
					  }, [contacts]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const usedCategoryIds = [
 | 
				
			||||||
 | 
					    ...new Set(contacts.map((c) => c.contactCategory?.id)),
 | 
				
			||||||
 | 
					  ];
 | 
				
			||||||
 | 
					  const filteredCategories = contactCategory.filter((category) =>
 | 
				
			||||||
 | 
					    usedCategoryIds.includes(category.id)
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					  const handleTempBucketChange = (id) => {
 | 
				
			||||||
 | 
					    setTempSelectedBucketIds((prev) =>
 | 
				
			||||||
 | 
					      prev.includes(id) ? prev.filter((bid) => bid !== id) : [...prev, id]
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const handleTempCategoryChange = (id) => {
 | 
				
			||||||
 | 
					    setTempSelectedCategoryIds((prev) =>
 | 
				
			||||||
 | 
					      prev.includes(id) ? prev.filter((cid) => cid !== id) : [...prev, id]
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const usedBucketIds = [
 | 
				
			||||||
 | 
					    ...new Set(contacts.flatMap((c) => c.bucketIds || [])),
 | 
				
			||||||
 | 
					  ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const filteredBuckets = buckets.filter((bucket) =>
 | 
				
			||||||
 | 
					    usedBucketIds.includes(bucket.id)
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const filteredContacts = useMemo(() => {
 | 
				
			||||||
 | 
					    return ContactList.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);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      const matchesBucket =
 | 
				
			||||||
 | 
					        selectedBucketIds.length === 0 ||
 | 
				
			||||||
 | 
					        (c.bucketIds || []).some((id) => selectedBucketIds.includes(id));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      return matchesSearch && matchesCategory && matchesBucket;
 | 
				
			||||||
 | 
					    }).sort((a, b) => a.name.localeCompare(b.name));
 | 
				
			||||||
 | 
					  }, [
 | 
				
			||||||
 | 
					    ContactList,
 | 
				
			||||||
 | 
					    searchText,
 | 
				
			||||||
 | 
					    selectedCategoryIds,
 | 
				
			||||||
 | 
					    selectedBucketIds,
 | 
				
			||||||
 | 
					    selectedContact,
 | 
				
			||||||
 | 
					  ]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const applyFilter = () => {
 | 
				
			||||||
 | 
					    setSelectedBucketIds(tempSelectedBucketIds);
 | 
				
			||||||
 | 
					    setSelectedCategoryIds(tempSelectedCategoryIds);
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const clearFilter = () => {
 | 
				
			||||||
 | 
					    setTempSelectedBucketIds([]);
 | 
				
			||||||
 | 
					    setTempSelectedCategoryIds([]);
 | 
				
			||||||
 | 
					    setSelectedBucketIds([]);
 | 
				
			||||||
 | 
					    setSelectedCategoryIds([]);
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  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} />
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  useEffect(() => {
 | 
				
			||||||
 | 
					    const actions = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (IsPage) {
 | 
				
			||||||
 | 
					      actions.push({
 | 
				
			||||||
 | 
					        label: "Manage Bucket",
 | 
				
			||||||
 | 
					        icon: "fa-solid fa-bucket fs-5",
 | 
				
			||||||
 | 
					        color: "primary",
 | 
				
			||||||
 | 
					        onClick: () => setOpenBucketModal(true),
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (buckets?.length > 0) {
 | 
				
			||||||
 | 
					      actions.push({
 | 
				
			||||||
 | 
					        label: "New Contact",
 | 
				
			||||||
 | 
					        icon: "bx bx-plus-circle",
 | 
				
			||||||
 | 
					        color: "warning",
 | 
				
			||||||
 | 
					        onClick: () => setIsOpenModal(true),
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    setActions(actions);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return () => setActions([]);
 | 
				
			||||||
 | 
					  }, [IsPage, buckets]);
 | 
				
			||||||
 | 
					  useEffect(() => {
 | 
				
			||||||
 | 
					    setPerfence(prefernceContacts);
 | 
				
			||||||
 | 
					  }, [prefernceContacts]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <div className="container-xxl flex-grow-1 container-p-y">
 | 
					    <div className="container-xxl flex-grow-1 container-p-y">
 | 
				
			||||||
      <Breadcrumb
 | 
					      {IsPage && (
 | 
				
			||||||
        data={[
 | 
					        <Breadcrumb
 | 
				
			||||||
          { label: "Home", link: "/dashboard" },
 | 
					          data={[
 | 
				
			||||||
          { label: "Directory (Comming Soon)", link: null },
 | 
					            { label: "Home", link: "/dashboard" },
 | 
				
			||||||
        ]}
 | 
					            { label: "Directory", link: null },
 | 
				
			||||||
      ></Breadcrumb>
 | 
					          ]}
 | 
				
			||||||
 | 
					        ></Breadcrumb>
 | 
				
			||||||
 | 
					      )}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      <GlobalModel isOpen={isOpenModal} closeModal={closedModel}>
 | 
					      {isOpenModal && (
 | 
				
			||||||
        <ManageDirectory />
 | 
					        <GlobalModel
 | 
				
			||||||
      </GlobalModel>
 | 
					          isOpen={isOpenModal}
 | 
				
			||||||
 | 
					          closeModal={() => {
 | 
				
			||||||
      <div className="row">
 | 
					            setSelectedContact(null);
 | 
				
			||||||
        <div className="row mx-0 px-0">
 | 
					            setIsOpenModal(false);
 | 
				
			||||||
          <div className="col-md-4 col-6 flex-grow-1 mb-2 px-1">
 | 
					          }}
 | 
				
			||||||
            <input
 | 
					          size="xl"
 | 
				
			||||||
              type="search"
 | 
					        >
 | 
				
			||||||
              className="form-control form-control-sm"
 | 
					          {renderModalContent()}
 | 
				
			||||||
              placeholder="Search projects..."
 | 
					        </GlobalModel>
 | 
				
			||||||
 | 
					      )}
 | 
				
			||||||
 | 
					      {isOpenModalNote && (
 | 
				
			||||||
 | 
					        <GlobalModel
 | 
				
			||||||
 | 
					          isOpen={isOpenModalNote}
 | 
				
			||||||
 | 
					          closeModal={() => {
 | 
				
			||||||
 | 
					            setOpen_contact(null);
 | 
				
			||||||
 | 
					            setIsOpenModalNote(false);
 | 
				
			||||||
 | 
					          }}
 | 
				
			||||||
 | 
					          size="xl"
 | 
				
			||||||
 | 
					        >
 | 
				
			||||||
 | 
					          {open_contact && (
 | 
				
			||||||
 | 
					            <ProfileContactDirectory
 | 
				
			||||||
 | 
					              contact={open_contact}
 | 
				
			||||||
 | 
					              setOpen_contact={setOpen_contact}
 | 
				
			||||||
 | 
					              closeModal={() => setIsOpenModalNote(false)}
 | 
				
			||||||
            />
 | 
					            />
 | 
				
			||||||
          </div>
 | 
					          )}
 | 
				
			||||||
          <div className="col-md-8 col-6 text-end flex-grow-1 mb-2 px-1">
 | 
					        </GlobalModel>
 | 
				
			||||||
            <button
 | 
					      )}
 | 
				
			||||||
              type="button"
 | 
					      {deleteContact && (
 | 
				
			||||||
              className={`btn btn-sm btn-primary `}
 | 
					        <div
 | 
				
			||||||
              onClick={() => setIsOpenModal(true)}
 | 
					          className={`modal fade  ${deleteContact ? "show" : ""}`}
 | 
				
			||||||
            >
 | 
					          tabIndex="-1"
 | 
				
			||||||
              <i className="bx bx-plus-circle me-2"></i>
 | 
					          role="dialog"
 | 
				
			||||||
              New Contact
 | 
					          style={{
 | 
				
			||||||
            </button>
 | 
					            display: deleteContact ? "block" : "none",
 | 
				
			||||||
          </div>
 | 
					            backgroundColor: deleteContact ? "rgba(0,0,0,0.5)" : "transparent",
 | 
				
			||||||
 | 
					          }}
 | 
				
			||||||
 | 
					          aria-hidden="false"
 | 
				
			||||||
 | 
					        >
 | 
				
			||||||
 | 
					          <ConfirmModal
 | 
				
			||||||
 | 
					            type={"delete"}
 | 
				
			||||||
 | 
					            header={"Delete Contact"}
 | 
				
			||||||
 | 
					            message={"Are you sure you want delete?"}
 | 
				
			||||||
 | 
					            onSubmit={handleDeleteContact}
 | 
				
			||||||
 | 
					            onClose={() => setDeleteContact(null)}
 | 
				
			||||||
 | 
					            loading={IsDeleting}
 | 
				
			||||||
 | 
					          />
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
        <div className="table-responsive text-nowrap py-2 ">
 | 
					      )}
 | 
				
			||||||
          <table className="table px-2">
 | 
					
 | 
				
			||||||
            <thead>
 | 
					      {openBucketModal && (
 | 
				
			||||||
 | 
					        <GlobalModel
 | 
				
			||||||
 | 
					          isOpen={openBucketModal}
 | 
				
			||||||
 | 
					          closeModal={() => setOpenBucketModal(false)}
 | 
				
			||||||
 | 
					          size="lg"
 | 
				
			||||||
 | 
					        >
 | 
				
			||||||
 | 
					          <ManageBucket buckets={buckets} />
 | 
				
			||||||
 | 
					        </GlobalModel>
 | 
				
			||||||
 | 
					      )}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      <div className="card p-2 card-minHeight">
 | 
				
			||||||
 | 
					        <DirectoryPageHeader
 | 
				
			||||||
 | 
					          searchText={searchText}
 | 
				
			||||||
 | 
					          setSearchText={setSearchText}
 | 
				
			||||||
 | 
					          setIsActive={setIsActive}
 | 
				
			||||||
 | 
					          listView={listView}
 | 
				
			||||||
 | 
					          setListView={setListView}
 | 
				
			||||||
 | 
					          filteredBuckets={filteredBuckets}
 | 
				
			||||||
 | 
					          tempSelectedBucketIds={tempSelectedBucketIds}
 | 
				
			||||||
 | 
					          handleTempBucketChange={handleTempBucketChange}
 | 
				
			||||||
 | 
					          filteredCategories={filteredCategories}
 | 
				
			||||||
 | 
					          tempSelectedCategoryIds={tempSelectedCategoryIds}
 | 
				
			||||||
 | 
					          handleTempCategoryChange={handleTempCategoryChange}
 | 
				
			||||||
 | 
					          clearFilter={clearFilter}
 | 
				
			||||||
 | 
					          applyFilter={applyFilter}
 | 
				
			||||||
 | 
					          loading={loading}
 | 
				
			||||||
 | 
					          IsActive={IsActive}
 | 
				
			||||||
 | 
					          setOpenBucketModal={setOpenBucketModal}
 | 
				
			||||||
 | 
					        />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        {/* Messages when listView is false */}
 | 
				
			||||||
 | 
					        {!listView && (
 | 
				
			||||||
 | 
					          <div className="d-flex flex-column justify-content-center align-items-center text-center ">
 | 
				
			||||||
 | 
					            {loading && <p className="mt-10">Loading...</p>}
 | 
				
			||||||
 | 
					            {!loading && contacts?.length === 0 && (
 | 
				
			||||||
 | 
					              <p className="mt-10">No contact found</p>
 | 
				
			||||||
 | 
					            )}
 | 
				
			||||||
 | 
					            {!loading && contacts?.length > 0 && currentItems.length === 0 && (
 | 
				
			||||||
 | 
					              <p className="mt-10">No matching contact found</p>
 | 
				
			||||||
 | 
					            )}
 | 
				
			||||||
 | 
					          </div>
 | 
				
			||||||
 | 
					        )}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        {/* Table view (listView === true) */}
 | 
				
			||||||
 | 
					        {listView ? (
 | 
				
			||||||
 | 
					          <DirectoryListTableHeader>
 | 
				
			||||||
 | 
					            {loading && (
 | 
				
			||||||
              <tr>
 | 
					              <tr>
 | 
				
			||||||
                <th className="text-start" colSpan="2">
 | 
					                <td colSpan={10}>
 | 
				
			||||||
                  Name
 | 
					                  {" "}
 | 
				
			||||||
                </th>
 | 
					                  <p className="mt-10">Loading...</p>{" "}
 | 
				
			||||||
                <th className="px-2">
 | 
					 | 
				
			||||||
                  <div className="d-flex align-items-center gap-1">
 | 
					 | 
				
			||||||
                    <IconButton
 | 
					 | 
				
			||||||
                      size={12}
 | 
					 | 
				
			||||||
                      iconClass="bx bx-envelope"
 | 
					 | 
				
			||||||
                      color="primary"
 | 
					 | 
				
			||||||
                      onClick={() => alert("User icon clicked")}
 | 
					 | 
				
			||||||
                    />
 | 
					 | 
				
			||||||
                    <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">
 | 
					 | 
				
			||||||
                  <div className="dropdown">
 | 
					 | 
				
			||||||
                    <a
 | 
					 | 
				
			||||||
                      className="dropdown-toggle hide-arrow cursor-pointer align-items-center"
 | 
					 | 
				
			||||||
                      data-bs-toggle="dropdown"
 | 
					 | 
				
			||||||
                      aria-expanded="false"
 | 
					 | 
				
			||||||
                    >
 | 
					 | 
				
			||||||
                      Type <i className="bx bx-filter bx-sm"></i>
 | 
					 | 
				
			||||||
                    </a>
 | 
					 | 
				
			||||||
                    {/* <ul className="dropdown-menu p-2 text-capitalize">
 | 
					 | 
				
			||||||
                          {[
 | 
					 | 
				
			||||||
                            { id: 1, label: "Active" },
 | 
					 | 
				
			||||||
                            { id: 2, label: "On Hold" },
 | 
					 | 
				
			||||||
                            { id: 3, label: "Inactive" },
 | 
					 | 
				
			||||||
                            { id: 4, label: "Completed" },
 | 
					 | 
				
			||||||
                          ].map(({ id, label }) => (
 | 
					 | 
				
			||||||
                            <li key={id}>
 | 
					 | 
				
			||||||
                              <div className="form-check">
 | 
					 | 
				
			||||||
                                <input
 | 
					 | 
				
			||||||
                                  className="form-check-input  "
 | 
					 | 
				
			||||||
                                  type="checkbox"
 | 
					 | 
				
			||||||
                                  checked={selectedStatuses.includes(id)}
 | 
					 | 
				
			||||||
                                  onChange={() => handleStatusChange(id)}
 | 
					 | 
				
			||||||
                                />
 | 
					 | 
				
			||||||
                                <label className="form-check-label">
 | 
					 | 
				
			||||||
                                  {label}
 | 
					 | 
				
			||||||
                                </label>
 | 
					 | 
				
			||||||
                              </div>
 | 
					 | 
				
			||||||
                            </li>
 | 
					 | 
				
			||||||
                          ))}
 | 
					 | 
				
			||||||
                        </ul> 
 | 
					 | 
				
			||||||
                    */}
 | 
					 | 
				
			||||||
                  </div>
 | 
					 | 
				
			||||||
                </th>
 | 
					 | 
				
			||||||
                <th
 | 
					 | 
				
			||||||
                //   className={`mx-2 ${
 | 
					 | 
				
			||||||
                //     HasManageProject ? "d-sm-table-cell" : "d-none"
 | 
					 | 
				
			||||||
                //   }`}
 | 
					 | 
				
			||||||
                >
 | 
					 | 
				
			||||||
                  Action
 | 
					 | 
				
			||||||
                </th>
 | 
					 | 
				
			||||||
              </tr>
 | 
					 | 
				
			||||||
            </thead>
 | 
					 | 
				
			||||||
            <tbody className="table-border-bottom-0 overflow-auto ">
 | 
					 | 
				
			||||||
              <tr>
 | 
					 | 
				
			||||||
                <td colSpan="12" className="text-center py-4">
 | 
					 | 
				
			||||||
                  comming soon....
 | 
					 | 
				
			||||||
                </td>
 | 
					                </td>
 | 
				
			||||||
              </tr>
 | 
					              </tr>
 | 
				
			||||||
            </tbody>
 | 
					            )}
 | 
				
			||||||
          </table>
 | 
					
 | 
				
			||||||
        </div>
 | 
					            {!loading && contacts?.length === 0 && (
 | 
				
			||||||
 | 
					              <tr>
 | 
				
			||||||
 | 
					                <td colSpan={10}>
 | 
				
			||||||
 | 
					                  <p className="mt-10">No contact found</p>
 | 
				
			||||||
 | 
					                </td>
 | 
				
			||||||
 | 
					              </tr>
 | 
				
			||||||
 | 
					            )}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            {!loading && currentItems.length === 0 && contacts?.length > 0 && (
 | 
				
			||||||
 | 
					              <tr>
 | 
				
			||||||
 | 
					                <td colSpan={10}>
 | 
				
			||||||
 | 
					                  <p className="mt-10">No matching contact found</p>
 | 
				
			||||||
 | 
					                </td>
 | 
				
			||||||
 | 
					              </tr>
 | 
				
			||||||
 | 
					            )}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            {!loading &&
 | 
				
			||||||
 | 
					              currentItems.map((contact) => (
 | 
				
			||||||
 | 
					                <ListViewDirectory
 | 
				
			||||||
 | 
					                  key={contact.id}
 | 
				
			||||||
 | 
					                  IsActive={IsActive}
 | 
				
			||||||
 | 
					                  contact={contact}
 | 
				
			||||||
 | 
					                  setSelectedContact={setSelectedContact}
 | 
				
			||||||
 | 
					                  setIsOpenModal={setIsOpenModal}
 | 
				
			||||||
 | 
					                  setOpen_contact={setOpen_contact}
 | 
				
			||||||
 | 
					                  setIsOpenModalNote={setIsOpenModalNote}
 | 
				
			||||||
 | 
					                  IsDeleted={setDeleteContact}
 | 
				
			||||||
 | 
					                  restore={handleDeleteContact}
 | 
				
			||||||
 | 
					                />
 | 
				
			||||||
 | 
					              ))}
 | 
				
			||||||
 | 
					          </DirectoryListTableHeader>
 | 
				
			||||||
 | 
					        ) : (
 | 
				
			||||||
 | 
					          <div className="row mt-5">
 | 
				
			||||||
 | 
					            {!loading &&
 | 
				
			||||||
 | 
					              currentItems.map((contact) => (
 | 
				
			||||||
 | 
					                <div
 | 
				
			||||||
 | 
					                  key={contact.id}
 | 
				
			||||||
 | 
					                  className="col-12 col-sm-6 col-md-4 col-lg-4 mb-4"
 | 
				
			||||||
 | 
					                >
 | 
				
			||||||
 | 
					                  <CardViewDirectory
 | 
				
			||||||
 | 
					                    IsActive={IsActive}
 | 
				
			||||||
 | 
					                    contact={contact}
 | 
				
			||||||
 | 
					                    setSelectedContact={setSelectedContact}
 | 
				
			||||||
 | 
					                    setIsOpenModal={setIsOpenModal}
 | 
				
			||||||
 | 
					                    setOpen_contact={setOpen_contact}
 | 
				
			||||||
 | 
					                    setIsOpenModalNote={setIsOpenModalNote}
 | 
				
			||||||
 | 
					                    IsDeleted={setDeleteContact}
 | 
				
			||||||
 | 
					                    restore={handleDeleteContact}
 | 
				
			||||||
 | 
					                  />
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					              ))}
 | 
				
			||||||
 | 
					          </div>
 | 
				
			||||||
 | 
					        )}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        {/* Pagination */}
 | 
				
			||||||
 | 
					        {!loading &&
 | 
				
			||||||
 | 
					          contacts?.length > 0 &&
 | 
				
			||||||
 | 
					          currentItems.length > ITEMS_PER_PAGE && (
 | 
				
			||||||
 | 
					            <nav aria-label="Page navigation">
 | 
				
			||||||
 | 
					              <ul className="pagination pagination-sm justify-content-end py-1">
 | 
				
			||||||
 | 
					                <li
 | 
				
			||||||
 | 
					                  className={`page-item ${currentPage === 1 ? "disabled" : ""}`}
 | 
				
			||||||
 | 
					                >
 | 
				
			||||||
 | 
					                  <button
 | 
				
			||||||
 | 
					                    className="page-link btn-xs"
 | 
				
			||||||
 | 
					                    onClick={() => paginate(currentPage - 1)}
 | 
				
			||||||
 | 
					                  >
 | 
				
			||||||
 | 
					                    «
 | 
				
			||||||
 | 
					                  </button>
 | 
				
			||||||
 | 
					                </li>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                {[...Array(totalPages)].map((_, index) => (
 | 
				
			||||||
 | 
					                  <li
 | 
				
			||||||
 | 
					                    key={index}
 | 
				
			||||||
 | 
					                    className={`page-item ${
 | 
				
			||||||
 | 
					                      currentPage === index + 1 ? "active" : ""
 | 
				
			||||||
 | 
					                    }`}
 | 
				
			||||||
 | 
					                  >
 | 
				
			||||||
 | 
					                    <button
 | 
				
			||||||
 | 
					                      className="page-link"
 | 
				
			||||||
 | 
					                      onClick={() => paginate(index + 1)}
 | 
				
			||||||
 | 
					                    >
 | 
				
			||||||
 | 
					                      {index + 1}
 | 
				
			||||||
 | 
					                    </button>
 | 
				
			||||||
 | 
					                  </li>
 | 
				
			||||||
 | 
					                ))}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                <li
 | 
				
			||||||
 | 
					                  className={`page-item ${
 | 
				
			||||||
 | 
					                    currentPage === totalPages ? "disabled" : ""
 | 
				
			||||||
 | 
					                  }`}
 | 
				
			||||||
 | 
					                >
 | 
				
			||||||
 | 
					                  <button
 | 
				
			||||||
 | 
					                    className="page-link"
 | 
				
			||||||
 | 
					                    onClick={() => paginate(currentPage + 1)}
 | 
				
			||||||
 | 
					                  >
 | 
				
			||||||
 | 
					                    »
 | 
				
			||||||
 | 
					                  </button>
 | 
				
			||||||
 | 
					                </li>
 | 
				
			||||||
 | 
					              </ul>
 | 
				
			||||||
 | 
					            </nav>
 | 
				
			||||||
 | 
					          )}
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										39
									
								
								src/pages/Directory/DirectoryListTableHeader.jsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								src/pages/Directory/DirectoryListTableHeader.jsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,39 @@
 | 
				
			|||||||
 | 
					import React from "react";
 | 
				
			||||||
 | 
					import IconButton from "../../components/common/IconButton";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const DirectoryListTableHeader = ({ children }) => {
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <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">
 | 
				
			||||||
 | 
					                <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">
 | 
				
			||||||
 | 
					                <span>Email</span>
 | 
				
			||||||
 | 
					              </div>
 | 
				
			||||||
 | 
					            </th>
 | 
				
			||||||
 | 
					            <th className="mx-2">
 | 
				
			||||||
 | 
					              <div className="d-flex align-items-center m-0 p-0 gap-1">
 | 
				
			||||||
 | 
					                <span>Phone</span>
 | 
				
			||||||
 | 
					              </div>
 | 
				
			||||||
 | 
					            </th>
 | 
				
			||||||
 | 
					            <th colSpan={2} className="mx-2 ps-20">
 | 
				
			||||||
 | 
					              Organization
 | 
				
			||||||
 | 
					            </th>
 | 
				
			||||||
 | 
					            <th className="mx-2">Category</th>
 | 
				
			||||||
 | 
					            <th>Action</th>
 | 
				
			||||||
 | 
					          </tr>
 | 
				
			||||||
 | 
					        </thead>
 | 
				
			||||||
 | 
					        <tbody className="table-border-bottom-0 overflow-auto">
 | 
				
			||||||
 | 
					          {children}
 | 
				
			||||||
 | 
					        </tbody>
 | 
				
			||||||
 | 
					      </table>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					export default DirectoryListTableHeader;
 | 
				
			||||||
							
								
								
									
										196
									
								
								src/pages/Directory/DirectoryPageHeader.jsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										196
									
								
								src/pages/Directory/DirectoryPageHeader.jsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,196 @@
 | 
				
			|||||||
 | 
					import React, { useEffect, useState } from "react";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const DirectoryPageHeader = ({
 | 
				
			||||||
 | 
					  searchText,
 | 
				
			||||||
 | 
					  setSearchText,
 | 
				
			||||||
 | 
					  setIsActive,
 | 
				
			||||||
 | 
					  listView,
 | 
				
			||||||
 | 
					  setListView,
 | 
				
			||||||
 | 
					  filteredBuckets,
 | 
				
			||||||
 | 
					  tempSelectedBucketIds,
 | 
				
			||||||
 | 
					  handleTempBucketChange,
 | 
				
			||||||
 | 
					  filteredCategories,
 | 
				
			||||||
 | 
					  tempSelectedCategoryIds,
 | 
				
			||||||
 | 
					  handleTempCategoryChange,
 | 
				
			||||||
 | 
					  clearFilter,
 | 
				
			||||||
 | 
					  applyFilter,
 | 
				
			||||||
 | 
					  loading,
 | 
				
			||||||
 | 
					  IsActive,
 | 
				
			||||||
 | 
					  setOpenBucketModal,
 | 
				
			||||||
 | 
					}) => {
 | 
				
			||||||
 | 
					  const [filtered, setFiltered] = useState();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  useEffect(() => {
 | 
				
			||||||
 | 
					    setFiltered(
 | 
				
			||||||
 | 
					      tempSelectedBucketIds?.length + tempSelectedCategoryIds?.length
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }, [tempSelectedBucketIds, tempSelectedCategoryIds]);
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <>
 | 
				
			||||||
 | 
					      {/* <div className="row">vikas</div> */}
 | 
				
			||||||
 | 
					      <div className="row mx-0 px-0 align-items-center mt-2">
 | 
				
			||||||
 | 
					        <div className="col-12 col-md-6 mb-2 px-1 d-flex align-items-center gap-4 ">
 | 
				
			||||||
 | 
					          <input
 | 
				
			||||||
 | 
					            type="search"
 | 
				
			||||||
 | 
					            className="form-control me-2"
 | 
				
			||||||
 | 
					            placeholder="Search Contact..."
 | 
				
			||||||
 | 
					            value={searchText}
 | 
				
			||||||
 | 
					            onChange={(e) => setSearchText(e.target.value)}
 | 
				
			||||||
 | 
					            style={{ width: "200px" }}
 | 
				
			||||||
 | 
					          />
 | 
				
			||||||
 | 
					          <div className="d-flex gap-2 ">
 | 
				
			||||||
 | 
					            <button
 | 
				
			||||||
 | 
					              type="button"
 | 
				
			||||||
 | 
					              className={`btn btn-xs ${
 | 
				
			||||||
 | 
					                !listView ? "btn-primary" : "btn-outline-primary"
 | 
				
			||||||
 | 
					              }`}
 | 
				
			||||||
 | 
					              onClick={() => setListView(false)}
 | 
				
			||||||
 | 
					              data-bs-toggle="tooltip"
 | 
				
			||||||
 | 
					              data-bs-offset="0,8"
 | 
				
			||||||
 | 
					              data-bs-placement="top"
 | 
				
			||||||
 | 
					              data-bs-custom-class="tooltip"
 | 
				
			||||||
 | 
					              title="Card View"
 | 
				
			||||||
 | 
					            >
 | 
				
			||||||
 | 
					              <i className="bx bx-grid-alt"></i>
 | 
				
			||||||
 | 
					            </button>
 | 
				
			||||||
 | 
					            <button
 | 
				
			||||||
 | 
					              type="button"
 | 
				
			||||||
 | 
					              className={`btn btn-xs ${
 | 
				
			||||||
 | 
					                listView ? "btn-primary" : "btn-outline-primary"
 | 
				
			||||||
 | 
					              }`}
 | 
				
			||||||
 | 
					              onClick={() => setListView(true)}
 | 
				
			||||||
 | 
					              data-bs-toggle="tooltip"
 | 
				
			||||||
 | 
					              data-bs-offset="0,8"
 | 
				
			||||||
 | 
					              data-bs-placement="top"
 | 
				
			||||||
 | 
					              data-bs-custom-class="tooltip"
 | 
				
			||||||
 | 
					              title="List View"
 | 
				
			||||||
 | 
					            >
 | 
				
			||||||
 | 
					              <i className="bx bx-list-ul "></i>
 | 
				
			||||||
 | 
					            </button>
 | 
				
			||||||
 | 
					          </div>
 | 
				
			||||||
 | 
					          <div className="dropdown" style={{ width: "fit-content" }}>
 | 
				
			||||||
 | 
					            <div className="dropdown" style={{ width: "fit-content" }}>
 | 
				
			||||||
 | 
					              <a
 | 
				
			||||||
 | 
					                className="dropdown-toggle hide-arrow cursor-pointer d-flex align-items-center position-relative"
 | 
				
			||||||
 | 
					                data-bs-toggle="dropdown"
 | 
				
			||||||
 | 
					                aria-expanded="false"
 | 
				
			||||||
 | 
					              >
 | 
				
			||||||
 | 
					                <i
 | 
				
			||||||
 | 
					                  className={`fa-solid fa-filter ms-1 fs-5 ${
 | 
				
			||||||
 | 
					                    filtered > 0 ? "text-primary" : "text-muted"
 | 
				
			||||||
 | 
					                  }`}
 | 
				
			||||||
 | 
					                ></i>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                {filtered > 0 && (
 | 
				
			||||||
 | 
					                  <span
 | 
				
			||||||
 | 
					                    className="position-absolute top-0 start-100 translate-middle badge rounded-pill bg-warning"
 | 
				
			||||||
 | 
					                    style={{ fontSize: "0.4rem" }}
 | 
				
			||||||
 | 
					                  >
 | 
				
			||||||
 | 
					                    {filtered}
 | 
				
			||||||
 | 
					                  </span>
 | 
				
			||||||
 | 
					                )}
 | 
				
			||||||
 | 
					              </a>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					              <ul className="dropdown-menu p-3" style={{ width: "320px" }}>
 | 
				
			||||||
 | 
					                <div>
 | 
				
			||||||
 | 
					                  <p className="text-muted m-0 h6 ">Filter by</p>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                  {/* Bucket Filter */}
 | 
				
			||||||
 | 
					                  <div className="mt-1">
 | 
				
			||||||
 | 
					                    <p className="text-small mb-1 ">Buckets</p>
 | 
				
			||||||
 | 
					                    <div className="d-flex flex-wrap">
 | 
				
			||||||
 | 
					                      {filteredBuckets.map(({ id, name }) => (
 | 
				
			||||||
 | 
					                        <div
 | 
				
			||||||
 | 
					                          className="form-check me-3 mb-1"
 | 
				
			||||||
 | 
					                          style={{ minWidth: "33.33%" }}
 | 
				
			||||||
 | 
					                          key={id}
 | 
				
			||||||
 | 
					                        >
 | 
				
			||||||
 | 
					                          <input
 | 
				
			||||||
 | 
					                            className="form-check-input"
 | 
				
			||||||
 | 
					                            type="checkbox"
 | 
				
			||||||
 | 
					                            id={`bucket-${id}`}
 | 
				
			||||||
 | 
					                            checked={tempSelectedBucketIds.includes(id)}
 | 
				
			||||||
 | 
					                            onChange={() => handleTempBucketChange(id)}
 | 
				
			||||||
 | 
					                          />
 | 
				
			||||||
 | 
					                          <label
 | 
				
			||||||
 | 
					                            className="form-check-label text-nowrap text-small "
 | 
				
			||||||
 | 
					                            htmlFor={`bucket-${id}`}
 | 
				
			||||||
 | 
					                          >
 | 
				
			||||||
 | 
					                            {name}
 | 
				
			||||||
 | 
					                          </label>
 | 
				
			||||||
 | 
					                        </div>
 | 
				
			||||||
 | 
					                      ))}
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					                  </div>
 | 
				
			||||||
 | 
					                  <hr className="m-0" />
 | 
				
			||||||
 | 
					                  {/* Category Filter */}
 | 
				
			||||||
 | 
					                  <div className="mt-1">
 | 
				
			||||||
 | 
					                    <p className="text-small mb-1 ">Categories</p>
 | 
				
			||||||
 | 
					                    <div className="d-flex flex-wrap">
 | 
				
			||||||
 | 
					                      {filteredCategories.map(({ id, name }) => (
 | 
				
			||||||
 | 
					                        <div
 | 
				
			||||||
 | 
					                          className="form-check me-3 mb-1"
 | 
				
			||||||
 | 
					                          style={{ minWidth: "33.33%" }}
 | 
				
			||||||
 | 
					                          key={id}
 | 
				
			||||||
 | 
					                        >
 | 
				
			||||||
 | 
					                          <input
 | 
				
			||||||
 | 
					                            className="form-check-input"
 | 
				
			||||||
 | 
					                            type="checkbox"
 | 
				
			||||||
 | 
					                            id={`cat-${id}`}
 | 
				
			||||||
 | 
					                            checked={tempSelectedCategoryIds.includes(id)}
 | 
				
			||||||
 | 
					                            onChange={() => handleTempCategoryChange(id)}
 | 
				
			||||||
 | 
					                          />
 | 
				
			||||||
 | 
					                          <label
 | 
				
			||||||
 | 
					                            className="form-check-label text-nowrap text-small"
 | 
				
			||||||
 | 
					                            htmlFor={`cat-${id}`}
 | 
				
			||||||
 | 
					                          >
 | 
				
			||||||
 | 
					                            {name}
 | 
				
			||||||
 | 
					                          </label>
 | 
				
			||||||
 | 
					                        </div>
 | 
				
			||||||
 | 
					                      ))}
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					                  </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                  <div className="d-flex justify-content-end gap-2 mt-1">
 | 
				
			||||||
 | 
					                    <button
 | 
				
			||||||
 | 
					                      className="btn btn-xs btn-secondary"
 | 
				
			||||||
 | 
					                      onClick={clearFilter}
 | 
				
			||||||
 | 
					                    >
 | 
				
			||||||
 | 
					                      Clear
 | 
				
			||||||
 | 
					                    </button>
 | 
				
			||||||
 | 
					                    <button
 | 
				
			||||||
 | 
					                      className="btn btn-xs btn-primary"
 | 
				
			||||||
 | 
					                      onClick={applyFilter}
 | 
				
			||||||
 | 
					                    >
 | 
				
			||||||
 | 
					                      Apply Filter
 | 
				
			||||||
 | 
					                    </button>
 | 
				
			||||||
 | 
					                  </div>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					              </ul>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					          </div>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					        <div className="col-12 col-md-6 mb-2 px-1 d-flex justify-content-end gap-2 align-items-center text-end">
 | 
				
			||||||
 | 
					          <label className="switch switch-primary align-self-start mb-2">
 | 
				
			||||||
 | 
					            <input
 | 
				
			||||||
 | 
					              type="checkbox"
 | 
				
			||||||
 | 
					              className="switch-input me-3"
 | 
				
			||||||
 | 
					              onChange={() => setIsActive(!IsActive)}
 | 
				
			||||||
 | 
					              value={IsActive}
 | 
				
			||||||
 | 
					              disabled={loading}
 | 
				
			||||||
 | 
					            />
 | 
				
			||||||
 | 
					            <span className="switch-toggle-slider">
 | 
				
			||||||
 | 
					              <span className="switch-on"></span>
 | 
				
			||||||
 | 
					              <span className="switch-off"></span>
 | 
				
			||||||
 | 
					            </span>
 | 
				
			||||||
 | 
					            <span className=" list-inline-item ms-12 ">
 | 
				
			||||||
 | 
					              Show Inactive Contacts
 | 
				
			||||||
 | 
					            </span>
 | 
				
			||||||
 | 
					          </label>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					    </>
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default DirectoryPageHeader;
 | 
				
			||||||
@ -212,15 +212,16 @@ const ChangePasswordPage = ({ onClose }) => {
 | 
				
			|||||||
              <p className="p-0 m-0">Password must be:</p>
 | 
					              <p className="p-0 m-0">Password must be:</p>
 | 
				
			||||||
              <p className="p-0 m-0">- at least 8 characters long</p>
 | 
					              <p className="p-0 m-0">- at least 8 characters long</p>
 | 
				
			||||||
              <p className="p-0 m-0">
 | 
					              <p className="p-0 m-0">
 | 
				
			||||||
                - must contain at least one uppercase letter
 | 
					                - must contain one uppercase, one lowercase letter, at least one
 | 
				
			||||||
 | 
					                number, at least one special character
 | 
				
			||||||
              </p>
 | 
					              </p>
 | 
				
			||||||
              <p className="p-0 m-0">
 | 
					              {/* <p className="p-0 m-0">
 | 
				
			||||||
                - must contain at least one lowercase letter
 | 
					                - must contain at least one lowercase letter
 | 
				
			||||||
              </p>
 | 
					              </p>
 | 
				
			||||||
              <p className="p-0 m-0">- must contain at least one number</p>
 | 
					              <p className="p-0 m-0">- must contain at least one number</p>
 | 
				
			||||||
              <p className="p-0 m-0">
 | 
					              <p className="p-0 m-0">
 | 
				
			||||||
                - must contain at least one special character
 | 
					                - must contain at least one special character
 | 
				
			||||||
              </p>
 | 
					              </p> */}
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
          </form>
 | 
					          </form>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										182
									
								
								src/pages/employee/AssignToProject.jsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										182
									
								
								src/pages/employee/AssignToProject.jsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,182 @@
 | 
				
			|||||||
 | 
					import React, { useState, useEffect } from "react";
 | 
				
			||||||
 | 
					import { useProjects, useProjectsByEmployee } from "../../hooks/useProjects";
 | 
				
			||||||
 | 
					import EmployeeList from "./EmployeeList";
 | 
				
			||||||
 | 
					import showToast from "../../services/toastService";
 | 
				
			||||||
 | 
					import ProjectRepository from "../../repositories/ProjectRepository";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const AssignToProject = ({ employee, onClose }) => {
 | 
				
			||||||
 | 
					  const { projects, loading } = useProjects();
 | 
				
			||||||
 | 
					  const { projectList,loading:selectedProjectLoding ,refetch} = useProjectsByEmployee(employee?.id);
 | 
				
			||||||
 | 
					  const [isSubmitting,setSubmitting] = useState(false)
 | 
				
			||||||
 | 
					  const [searchTerm, setSearchTerm] = useState("");
 | 
				
			||||||
 | 
					  const [checkedProjects, setCheckedProjects] = useState({});
 | 
				
			||||||
 | 
					  const [selectedEmployees, setSelectedEmployees] = useState([]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  useEffect(() => {
 | 
				
			||||||
 | 
					    if (projectList && projectList.length > 0) {
 | 
				
			||||||
 | 
					      const initialChecked = {};
 | 
				
			||||||
 | 
					      const initialSelected = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      projectList.forEach((project) => {
 | 
				
			||||||
 | 
					        initialChecked[project.id] = true;
 | 
				
			||||||
 | 
					        initialSelected.push({
 | 
				
			||||||
 | 
					          jobRoleId: employee.jobRoleId,
 | 
				
			||||||
 | 
					          projectId: project.id,
 | 
				
			||||||
 | 
					          status: true,
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      setCheckedProjects(initialChecked);
 | 
				
			||||||
 | 
					      setSelectedEmployees(initialSelected);
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      setCheckedProjects({});
 | 
				
			||||||
 | 
					      setSelectedEmployees([]);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }, [projectList, employee?.id]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const handleSearchChange = (e) => {
 | 
				
			||||||
 | 
					    setSearchTerm(e.target.value.toLowerCase());
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const handleCheckboxChange = (projectId) => {
 | 
				
			||||||
 | 
					  const isChecked = !checkedProjects[projectId];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  setCheckedProjects((prev) => ({
 | 
				
			||||||
 | 
					    ...prev,
 | 
				
			||||||
 | 
					    [projectId]: isChecked,
 | 
				
			||||||
 | 
					  }));
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const handleSubmit = async () => {
 | 
				
			||||||
 | 
					  const initiallyAssigned = new Set(projectList.map((p) => p.id.toString()));
 | 
				
			||||||
 | 
					  const changes = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Object.entries(checkedProjects).forEach(([projectId, isChecked]) => {
 | 
				
			||||||
 | 
					    const wasAssigned = initiallyAssigned.has(projectId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (wasAssigned && !isChecked) {
 | 
				
			||||||
 | 
					      changes.push({
 | 
				
			||||||
 | 
					        projectId: projectId,
 | 
				
			||||||
 | 
					        jobRoleId: employee.jobRoleId,
 | 
				
			||||||
 | 
					        status: false,
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (!wasAssigned && isChecked) {
 | 
				
			||||||
 | 
					      changes.push({
 | 
				
			||||||
 | 
					        projectId: projectId,
 | 
				
			||||||
 | 
					        jobRoleId: employee.jobRoleId,
 | 
				
			||||||
 | 
					        status: true,
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (changes.length === 0) {
 | 
				
			||||||
 | 
					    showToast("Make change before.", "info");
 | 
				
			||||||
 | 
					    return;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  try {
 | 
				
			||||||
 | 
					    setSubmitting(true)
 | 
				
			||||||
 | 
					    await ProjectRepository.updateProjectsByEmployee(employee.id, changes)
 | 
				
			||||||
 | 
					    showToast( "Project assignments updated.", "success" );
 | 
				
			||||||
 | 
					    setSubmitting(false)
 | 
				
			||||||
 | 
					    onClose();
 | 
				
			||||||
 | 
					    refetch(employee.id)
 | 
				
			||||||
 | 
					  } catch (error) {
 | 
				
			||||||
 | 
					    const msg = error.response?.data?.message || error.message || "Error during API call.";
 | 
				
			||||||
 | 
					    showToast( msg, "error" );
 | 
				
			||||||
 | 
					    setSubmitting(false)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const handleClosedModal = () => {
 | 
				
			||||||
 | 
					    onClose();
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const filteredProjects = projects.filter((project) =>
 | 
				
			||||||
 | 
					    project.name.toLowerCase().includes(searchTerm)
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <div className="p-2 p-md-0">
 | 
				
			||||||
 | 
					      <p className="fw-semibold fs-6 m-0">Assign to Project</p>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      <div className="row my-1">
 | 
				
			||||||
 | 
					        <div className="col-12 col-sm-6 col-md-6 mt-2">
 | 
				
			||||||
 | 
					          <input
 | 
				
			||||||
 | 
					            type="text"
 | 
				
			||||||
 | 
					            className="form-control form-control-sm"
 | 
				
			||||||
 | 
					            placeholder="Search projects..."
 | 
				
			||||||
 | 
					            value={searchTerm}
 | 
				
			||||||
 | 
					            onChange={handleSearchChange}
 | 
				
			||||||
 | 
					          />
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      {loading ? (
 | 
				
			||||||
 | 
					        <div className="text-center py-4">
 | 
				
			||||||
 | 
					          <div className="spinner-border text-primary" role="status" />
 | 
				
			||||||
 | 
					          <p className="mt-2">Loading projects...</p>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					      ) : (
 | 
				
			||||||
 | 
					        <>
 | 
				
			||||||
 | 
					          <table className="table mt-2 mb-2">
 | 
				
			||||||
 | 
					            <thead>
 | 
				
			||||||
 | 
					              <tr className="text-start">
 | 
				
			||||||
 | 
					                <th>Select Project</th>
 | 
				
			||||||
 | 
					              </tr>
 | 
				
			||||||
 | 
					            </thead>
 | 
				
			||||||
 | 
					            <tbody>
 | 
				
			||||||
 | 
					              {filteredProjects.length > 0 ? (
 | 
				
			||||||
 | 
					                filteredProjects.map((project) => (
 | 
				
			||||||
 | 
					                  <tr key={project.id}>
 | 
				
			||||||
 | 
					                    <td className="d-flex align-items-center">
 | 
				
			||||||
 | 
					                      <div className="form-check d-flex justify-content-start align-items-center">
 | 
				
			||||||
 | 
					                        <input
 | 
				
			||||||
 | 
					                          className="form-check-input"
 | 
				
			||||||
 | 
					                          type="checkbox"
 | 
				
			||||||
 | 
					                          id={`project-${project.id}`}
 | 
				
			||||||
 | 
					                          checked={checkedProjects[project.id] || false}
 | 
				
			||||||
 | 
					                          onChange={() => handleCheckboxChange( project.id )}
 | 
				
			||||||
 | 
					                          disabled={selectedProjectLoding}
 | 
				
			||||||
 | 
					                          
 | 
				
			||||||
 | 
					                        />
 | 
				
			||||||
 | 
					                        <label
 | 
				
			||||||
 | 
					                          className="form-check-label ms-2"
 | 
				
			||||||
 | 
					                          htmlFor={`project-${project.id}`}
 | 
				
			||||||
 | 
					                        >
 | 
				
			||||||
 | 
					                          {project.name}
 | 
				
			||||||
 | 
					                        </label>
 | 
				
			||||||
 | 
					                      </div>
 | 
				
			||||||
 | 
					                    </td>
 | 
				
			||||||
 | 
					                  </tr>
 | 
				
			||||||
 | 
					                ))
 | 
				
			||||||
 | 
					              ) : (
 | 
				
			||||||
 | 
					                <tr>
 | 
				
			||||||
 | 
					                  <td className="text-center text-muted py-3">No projects found.</td>
 | 
				
			||||||
 | 
					                </tr>
 | 
				
			||||||
 | 
					              )}
 | 
				
			||||||
 | 
					            </tbody>
 | 
				
			||||||
 | 
					          </table>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          <div className="d-flex justify-content-center gap-2 mt-2">
 | 
				
			||||||
 | 
					            <button onClick={handleSubmit} className="btn btn-primary btn-sm" disabled={selectedProjectLoding || loading || isSubmitting }>
 | 
				
			||||||
 | 
					              {isSubmitting ? "Please Wait...":"Submit"}
 | 
				
			||||||
 | 
					            </button>
 | 
				
			||||||
 | 
					            <button onClick={handleClosedModal} className="btn btn-secondary btn-sm" disabled={isSubmitting}>
 | 
				
			||||||
 | 
					              Cancel
 | 
				
			||||||
 | 
					            </button>
 | 
				
			||||||
 | 
					          </div>
 | 
				
			||||||
 | 
					        </>
 | 
				
			||||||
 | 
					      )}
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default AssignToProject;
 | 
				
			||||||
@ -22,6 +22,8 @@ import {
 | 
				
			|||||||
import EmployeeRepository from "../../repositories/EmployeeRepository";
 | 
					import EmployeeRepository from "../../repositories/EmployeeRepository";
 | 
				
			||||||
import ManageEmployee from "../../components/Employee/ManageEmployee";
 | 
					import ManageEmployee from "../../components/Employee/ManageEmployee";
 | 
				
			||||||
import ConfirmModal from "../../components/common/ConfirmModal";
 | 
					import ConfirmModal from "../../components/common/ConfirmModal";
 | 
				
			||||||
 | 
					import GlobalModel from "../../components/common/GlobalModel";
 | 
				
			||||||
 | 
					import AssignToProject from "./AssignToProject";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const EmployeeList = () => {
 | 
					const EmployeeList = () => {
 | 
				
			||||||
  const { profile: loginUser } = useProfile();
 | 
					  const { profile: loginUser } = useProfile();
 | 
				
			||||||
@ -47,7 +49,8 @@ const EmployeeList = () => {
 | 
				
			|||||||
  const [IsDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
 | 
					  const [IsDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
 | 
				
			||||||
  const [selectedEmpFordelete, setSelectedEmpFordelete] = useState(null);
 | 
					  const [selectedEmpFordelete, setSelectedEmpFordelete] = useState(null);
 | 
				
			||||||
  const [employeeLodaing, setemployeeLodaing] = useState(false);
 | 
					  const [employeeLodaing, setemployeeLodaing] = useState(false);
 | 
				
			||||||
 | 
					  const [ selectedEmployee, setSelectEmployee ] = useState( null )
 | 
				
			||||||
 | 
					  const [IsOpenAsssingModal,setOpenAssignModal] = useState(false)
 | 
				
			||||||
  const navigate = useNavigate();
 | 
					  const navigate = useNavigate();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const handleSearch = (e) => {
 | 
					  const handleSearch = (e) => {
 | 
				
			||||||
@ -189,7 +192,11 @@ const EmployeeList = () => {
 | 
				
			|||||||
    setSelectedEmpFordelete(employee);
 | 
					    setSelectedEmpFordelete(employee);
 | 
				
			||||||
    setIsDeleteModalOpen(true);
 | 
					    setIsDeleteModalOpen(true);
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					  const handleCloseAssignModal = () =>
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    setOpenAssignModal( false )
 | 
				
			||||||
 | 
					    setSelectEmployee(null)
 | 
				
			||||||
 | 
					 }
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <>
 | 
					    <>
 | 
				
			||||||
      {isCreateModalOpen && (
 | 
					      {isCreateModalOpen && (
 | 
				
			||||||
@ -213,7 +220,8 @@ const EmployeeList = () => {
 | 
				
			|||||||
            />
 | 
					            />
 | 
				
			||||||
          </div>
 | 
					          </div>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
      </div>)}
 | 
					      </div> )}
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      {IsDeleteModalOpen && (
 | 
					      {IsDeleteModalOpen && (
 | 
				
			||||||
        <div
 | 
					        <div
 | 
				
			||||||
@ -240,6 +248,11 @@ const EmployeeList = () => {
 | 
				
			|||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
      )}
 | 
					      )}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      {IsOpenAsssingModal && ( <GlobalModel isOpen={IsOpenAsssingModal} closeModal={()=>setOpenAssignModal(false)}>
 | 
				
			||||||
 | 
					        <AssignToProject employee={selectedEmployee} onClose={() => setOpenAssignModal( false )} />
 | 
				
			||||||
 | 
					      </GlobalModel>)}
 | 
				
			||||||
 | 
					     
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      <div className="container-xxl flex-grow-1 container-p-y">
 | 
					      <div className="container-xxl flex-grow-1 container-p-y">
 | 
				
			||||||
        <Breadcrumb
 | 
					        <Breadcrumb
 | 
				
			||||||
          data={[
 | 
					          data={[
 | 
				
			||||||
@ -642,6 +655,19 @@ const EmployeeList = () => {
 | 
				
			|||||||
                                        <i className="bx bx-cog bx-sm"></i>{" "}
 | 
					                                        <i className="bx bx-cog bx-sm"></i>{" "}
 | 
				
			||||||
                                        Manage Role
 | 
					                                        Manage Role
 | 
				
			||||||
                                      </button>
 | 
					                                      </button>
 | 
				
			||||||
 | 
					                                      <button
 | 
				
			||||||
 | 
					                                        className="dropdown-item py-1"
 | 
				
			||||||
 | 
					                                     
 | 
				
			||||||
 | 
					                                        onClick={() =>
 | 
				
			||||||
 | 
					                                        {
 | 
				
			||||||
 | 
					                                          setSelectEmployee( item ),
 | 
				
			||||||
 | 
					                                          setOpenAssignModal(true)
 | 
				
			||||||
 | 
					                                        }
 | 
				
			||||||
 | 
					                                        }
 | 
				
			||||||
 | 
					                                      >
 | 
				
			||||||
 | 
					                                        <i className='bx bx-select-multiple'></i>{" "}
 | 
				
			||||||
 | 
					                                       Assign Project
 | 
				
			||||||
 | 
					                                      </button>
 | 
				
			||||||
                                    </>
 | 
					                                    </>
 | 
				
			||||||
                                  )}
 | 
					                                  )}
 | 
				
			||||||
                                </div>
 | 
					                                </div>
 | 
				
			||||||
 | 
				
			|||||||
@ -10,6 +10,8 @@ import {
 | 
				
			|||||||
  useEmployees,
 | 
					  useEmployees,
 | 
				
			||||||
  useEmployeesByProject,
 | 
					  useEmployeesByProject,
 | 
				
			||||||
} from "../../hooks/useEmployees";
 | 
					} from "../../hooks/useEmployees";
 | 
				
			||||||
 | 
					import { useProfile } from "../../hooks/useProfile";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { useSelector } from "react-redux";
 | 
					import { useSelector } from "react-redux";
 | 
				
			||||||
import EmployeeRepository from "../../repositories/EmployeeRepository";
 | 
					import EmployeeRepository from "../../repositories/EmployeeRepository";
 | 
				
			||||||
import { ComingSoonPage } from "../Misc/ComingSoonPage";
 | 
					import { ComingSoonPage } from "../Misc/ComingSoonPage";
 | 
				
			||||||
@ -17,7 +19,11 @@ import { useNavigate } from "react-router-dom";
 | 
				
			|||||||
import Avatar from "../../components/common/Avatar";
 | 
					import Avatar from "../../components/common/Avatar";
 | 
				
			||||||
import AttendancesEmployeeRecords from "./AttendancesEmployeeRecords";
 | 
					import AttendancesEmployeeRecords from "./AttendancesEmployeeRecords";
 | 
				
			||||||
import ManageEmployee from "../../components/Employee/ManageEmployee";
 | 
					import ManageEmployee from "../../components/Employee/ManageEmployee";
 | 
				
			||||||
 | 
					import { useChangePassword } from "../../components/Context/ChangePasswordContext";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const EmployeeProfile = () => {
 | 
					const EmployeeProfile = () => {
 | 
				
			||||||
 | 
					  const { profile } = useProfile();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const projectID = useSelector((store) => store.localVariables.projectId);
 | 
					  const projectID = useSelector((store) => store.localVariables.projectId);
 | 
				
			||||||
  const { employeeId } = useParams();
 | 
					  const { employeeId } = useParams();
 | 
				
			||||||
  // const {employee,loading} = useEmployeeProfile(employeeId)
 | 
					  // const {employee,loading} = useEmployeeProfile(employeeId)
 | 
				
			||||||
@ -27,17 +33,17 @@ const EmployeeProfile = () => {
 | 
				
			|||||||
  const tab = SearchParams.get("for");
 | 
					  const tab = SearchParams.get("for");
 | 
				
			||||||
  const [activePill, setActivePill] = useState(tab);
 | 
					  const [activePill, setActivePill] = useState(tab);
 | 
				
			||||||
  const [currentEmployee, setCurrentEmployee] = useState();
 | 
					  const [currentEmployee, setCurrentEmployee] = useState();
 | 
				
			||||||
    const [showModal, setShowModal] = useState(false);
 | 
					  const [showModal, setShowModal] = useState(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const handlePillClick = (pillKey) => {
 | 
					  const handlePillClick = (pillKey) => {
 | 
				
			||||||
    setActivePill(pillKey);
 | 
					    setActivePill(pillKey);
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
   const closeModal = () => {
 | 
					  const closeModal = () => {
 | 
				
			||||||
      setShowModal(false);
 | 
					    setShowModal(false);
 | 
				
			||||||
      fetchEmployeeProfile(employeeId);
 | 
					    fetchEmployeeProfile(employeeId);
 | 
				
			||||||
    };
 | 
					  };
 | 
				
			||||||
    const handleShow = () => setShowModal(true);
 | 
					  const handleShow = () => setShowModal(true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const fetchEmployeeProfile = async (employeeID) => {
 | 
					  const fetchEmployeeProfile = async (employeeID) => {
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
@ -95,179 +101,190 @@ const EmployeeProfile = () => {
 | 
				
			|||||||
  if (loading) {
 | 
					  if (loading) {
 | 
				
			||||||
    return <div>Loading...</div>;
 | 
					    return <div>Loading...</div>;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					  const { openChangePassword } = useChangePassword();
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <>    {showModal && (<div
 | 
					    <>
 | 
				
			||||||
        className={`modal fade ${showModal ? "show" : ""} `}
 | 
					      {" "}
 | 
				
			||||||
        tabIndex="-1"
 | 
					      {showModal && (
 | 
				
			||||||
        role="dialog"
 | 
					        <div
 | 
				
			||||||
        style={{ display: showModal ? "block" : "none" }}
 | 
					          className={`modal fade ${showModal ? "show" : ""} `}
 | 
				
			||||||
        aria-hidden={!showModal}
 | 
					          tabIndex="-1"
 | 
				
			||||||
      >
 | 
					          role="dialog"
 | 
				
			||||||
        <div className="modal-dialog modal-xl modal-dialog-centered ">
 | 
					          style={{ display: showModal ? "block" : "none" }}
 | 
				
			||||||
          <div
 | 
					          aria-hidden={!showModal}
 | 
				
			||||||
            className="modal-content overflow-y-auto overflow-x-hidden"
 | 
					        >
 | 
				
			||||||
            style={{ maxHeight: "90vh" }}
 | 
					          <div className="modal-dialog modal-xl modal-dialog-centered ">
 | 
				
			||||||
          >
 | 
					            <div
 | 
				
			||||||
            <ManageEmployee
 | 
					              className="modal-content overflow-y-auto overflow-x-hidden"
 | 
				
			||||||
              employeeId={employeeId}
 | 
					              style={{ maxHeight: "90vh" }}
 | 
				
			||||||
              onClosed={closeModal}
 | 
					            >
 | 
				
			||||||
            />
 | 
					              <ManageEmployee employeeId={employeeId} onClosed={closeModal} />
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
          </div>
 | 
					          </div>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
      </div>)}
 | 
					      )}
 | 
				
			||||||
 | 
					      <div className="container-xxl flex-grow-1 container-p-y">
 | 
				
			||||||
 | 
					        <Breadcrumb
 | 
				
			||||||
 | 
					          data={[
 | 
				
			||||||
 | 
					            { label: "Home", link: "/dashboard" },
 | 
				
			||||||
 | 
					            { label: "Employees", link: "/employees" },
 | 
				
			||||||
 | 
					            { label: "Profile", link: null },
 | 
				
			||||||
 | 
					          ]}
 | 
				
			||||||
 | 
					        ></Breadcrumb>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <div className="container-xxl flex-grow-1 container-p-y">
 | 
					        <div className="row">
 | 
				
			||||||
      <Breadcrumb
 | 
					          <div className="col-12 col-md-8 col-lg-4 order-1 order-lg-1">
 | 
				
			||||||
        data={[
 | 
					            <div className="row">
 | 
				
			||||||
          { label: "Home", link: "/dashboard" },
 | 
					              <div className="col-12 mb-4">
 | 
				
			||||||
          { label: "Employees", link: "/employees" },
 | 
					                <div className="card">
 | 
				
			||||||
          { label: "Profile", link: null },
 | 
					                  <div className="card-body">
 | 
				
			||||||
        ]}
 | 
					                    <div className="d-flex flex-row flex-lg-column">
 | 
				
			||||||
      ></Breadcrumb>
 | 
					                      <div className="d-flex flex-column justify-content-center align-items-center text-center">
 | 
				
			||||||
 | 
					                        <Avatar
 | 
				
			||||||
      <div className="row">
 | 
					                          firstName={`${currentEmployee?.firstName}`}
 | 
				
			||||||
        <div className="col-12 col-md-8 col-lg-4 order-1 order-lg-1">
 | 
					                          lastName={`${currentEmployee?.lastName}`}
 | 
				
			||||||
          <div className="row">
 | 
					                          size={"lg"}
 | 
				
			||||||
            <div className="col-12 mb-4">
 | 
					                        />
 | 
				
			||||||
              <div className="card">
 | 
					                        <div className="py-2">
 | 
				
			||||||
                <div className="card-body">
 | 
					                          <p className="h6">{`${currentEmployee?.firstName} ${currentEmployee?.lastName}`}</p>
 | 
				
			||||||
                  <div className="d-flex flex-row flex-lg-column">
 | 
					                        </div>
 | 
				
			||||||
                    <div className="d-flex flex-column justify-content-center align-items-center text-center">
 | 
					 | 
				
			||||||
                      <Avatar
 | 
					 | 
				
			||||||
                        firstName={`${currentEmployee?.firstName}`}
 | 
					 | 
				
			||||||
                        lastName={`${currentEmployee?.lastName}`}
 | 
					 | 
				
			||||||
                        size={"lg"}
 | 
					 | 
				
			||||||
                      />
 | 
					 | 
				
			||||||
                      <div className="py-2">
 | 
					 | 
				
			||||||
                        <p className="h6">{`${currentEmployee?.firstName} ${currentEmployee?.lastName}`}</p>
 | 
					 | 
				
			||||||
                      </div>
 | 
					                      </div>
 | 
				
			||||||
                    </div>
 | 
					                      <div className="w-100 d-flex flex-column justify-content-start">
 | 
				
			||||||
                    <div className="w-100 d-flex flex-column justify-content-start">
 | 
					                        <div className="mt-3 w-100">
 | 
				
			||||||
                      <div className="mt-3 w-100">
 | 
					                          <h6 className="mb-2 text-muted text-start">
 | 
				
			||||||
                        <h6 className="mb-2 text-muted text-start">
 | 
					                            Employee Info
 | 
				
			||||||
                          Employee Info
 | 
					                          </h6>
 | 
				
			||||||
                        </h6>
 | 
					                          <table className="table table-borderless mb-3">
 | 
				
			||||||
                        <table className="table table-borderless mb-3">
 | 
					                            <tbody>
 | 
				
			||||||
                          <tbody>
 | 
					                              <tr>
 | 
				
			||||||
                            <tr>
 | 
					                                <td className="fw-medium text-start">Email:</td>
 | 
				
			||||||
                              <td className="fw-medium text-start">Email:</td>
 | 
					                                <td className="text-start">
 | 
				
			||||||
                              <td className="text-start">
 | 
					                                  {currentEmployee?.email || <em>NA</em>}
 | 
				
			||||||
                                {currentEmployee?.email || <em>NA</em>}
 | 
					                                </td>
 | 
				
			||||||
                              </td>
 | 
					                              </tr>
 | 
				
			||||||
                            </tr>
 | 
					                              <tr>
 | 
				
			||||||
                            <tr>
 | 
					                                <td className="fw-medium text-start">
 | 
				
			||||||
                              <td className="fw-medium text-start">
 | 
					                                  Phone Number:
 | 
				
			||||||
                                Phone Number:
 | 
					                                </td>
 | 
				
			||||||
                              </td>
 | 
					                                <td className="text-start">
 | 
				
			||||||
                              <td className="text-start">
 | 
					                                  {currentEmployee?.phoneNumber || <em>NA</em>}
 | 
				
			||||||
                                {currentEmployee?.phoneNumber || <em>NA</em>}
 | 
					                                </td>
 | 
				
			||||||
                              </td>
 | 
					                              </tr>
 | 
				
			||||||
                            </tr>
 | 
					                              <tr>
 | 
				
			||||||
                            <tr>
 | 
					                                <td className="fw-medium text-start">
 | 
				
			||||||
                              <td className="fw-medium text-start">
 | 
					                                  Emergency Contact Person:
 | 
				
			||||||
                                Emergency Contact Person:
 | 
					                                </td>
 | 
				
			||||||
                              </td>
 | 
					                                <td className="text-start">
 | 
				
			||||||
                              <td className="text-start">
 | 
					                                  {currentEmployee?.emergencyContactPerson || (
 | 
				
			||||||
                                {currentEmployee?.emergencyContactPerson || (
 | 
					                                    <em>NA</em>
 | 
				
			||||||
                                  <em>NA</em>
 | 
					                                  )}
 | 
				
			||||||
                                )}
 | 
					                                </td>
 | 
				
			||||||
                              </td>
 | 
					                              </tr>
 | 
				
			||||||
                            </tr>
 | 
					                              <tr>
 | 
				
			||||||
                            <tr>
 | 
					                                <td className="fw-medium text-start">
 | 
				
			||||||
                              <td className="fw-medium text-start">
 | 
					                                  Emergency Contact Number:
 | 
				
			||||||
                                Emergency Contact Number:
 | 
					                                </td>
 | 
				
			||||||
                              </td>
 | 
					                                <td className="text-start">
 | 
				
			||||||
                              <td className="text-start">
 | 
					                                  {currentEmployee?.emergencyPhoneNumber || (
 | 
				
			||||||
                                {currentEmployee?.emergencyPhoneNumber || (
 | 
					                                    <em>NA</em>
 | 
				
			||||||
                                  <em>NA</em>
 | 
					                                  )}
 | 
				
			||||||
                                )}
 | 
					                                </td>
 | 
				
			||||||
                              </td>
 | 
					                              </tr>
 | 
				
			||||||
                            </tr>
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
                            <tr>
 | 
					                              <tr>
 | 
				
			||||||
                              <td className="fw-medium text-start">Gender:</td>
 | 
					                                <td className="fw-medium text-start">
 | 
				
			||||||
                              <td className="text-start">
 | 
					                                  Gender:
 | 
				
			||||||
                                {currentEmployee?.gender || <em>NA</em>}
 | 
					                                </td>
 | 
				
			||||||
                              </td>
 | 
					                                <td className="text-start">
 | 
				
			||||||
                            </tr>
 | 
					                                  {currentEmployee?.gender || <em>NA</em>}
 | 
				
			||||||
                            <tr>
 | 
					                                </td>
 | 
				
			||||||
                              <td className="fw-medium text-start">
 | 
					                              </tr>
 | 
				
			||||||
                                Birth Date:
 | 
					                              <tr>
 | 
				
			||||||
                              </td>
 | 
					                                <td className="fw-medium text-start">
 | 
				
			||||||
                              <td className="text-start">
 | 
					                                  Birth Date:
 | 
				
			||||||
                                {currentEmployee?.birthDate ? (
 | 
					                                </td>
 | 
				
			||||||
                                  new Date(
 | 
					                                <td className="text-start">
 | 
				
			||||||
                                    currentEmployee.birthDate
 | 
					                                  {currentEmployee?.birthDate ? (
 | 
				
			||||||
                                  ).toLocaleDateString()
 | 
					                                    new Date(
 | 
				
			||||||
                                ) : (
 | 
					                                      currentEmployee.birthDate
 | 
				
			||||||
                                  <em>NA</em>
 | 
					                                    ).toLocaleDateString()
 | 
				
			||||||
                                )}
 | 
					                                  ) : (
 | 
				
			||||||
                              </td>
 | 
					                                    <em>NA</em>
 | 
				
			||||||
                            </tr>
 | 
					                                  )}
 | 
				
			||||||
 | 
					                                </td>
 | 
				
			||||||
 | 
					                              </tr>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                            <tr>
 | 
					                              <tr>
 | 
				
			||||||
                              <td className="fw-medium text-start">
 | 
					                                <td className="fw-medium text-start">
 | 
				
			||||||
                                Joining Date:
 | 
					                                  Joining Date:
 | 
				
			||||||
                              </td>
 | 
					                                </td>
 | 
				
			||||||
                              <td className="text-start">
 | 
					                                <td className="text-start">
 | 
				
			||||||
                                {currentEmployee?.joiningDate ? (
 | 
					                                  {currentEmployee?.joiningDate ? (
 | 
				
			||||||
                                  new Date(
 | 
					                                    new Date(
 | 
				
			||||||
                                    currentEmployee.joiningDate
 | 
					                                      currentEmployee.joiningDate
 | 
				
			||||||
                                  ).toLocaleDateString()
 | 
					                                    ).toLocaleDateString()
 | 
				
			||||||
                                ) : (
 | 
					                                  ) : (
 | 
				
			||||||
                                  <em>NA</em>
 | 
					                                    <em>NA</em>
 | 
				
			||||||
                                )}
 | 
					                                  )}
 | 
				
			||||||
                              </td>
 | 
					                                </td>
 | 
				
			||||||
                            </tr>
 | 
					                              </tr>
 | 
				
			||||||
                            <tr>
 | 
					                              <tr>
 | 
				
			||||||
                              <td className="fw-medium text-start">
 | 
					                                <td className="fw-medium text-start">
 | 
				
			||||||
                                Job Role:
 | 
					                                  Job Role:
 | 
				
			||||||
                              </td>
 | 
					                                </td>
 | 
				
			||||||
                              <td className="text-start">
 | 
					                                <td className="text-start">
 | 
				
			||||||
                                {currentEmployee?.jobRole || <em>NA</em>}
 | 
					                                  {currentEmployee?.jobRole || <em>NA</em>}
 | 
				
			||||||
                              </td>
 | 
					                                </td>
 | 
				
			||||||
                            </tr>
 | 
					                              </tr>
 | 
				
			||||||
                            <tr>
 | 
					                              <tr>
 | 
				
			||||||
                              <td className="fw-medium text-start">Address:</td>
 | 
					                                <td className="fw-medium text-start">
 | 
				
			||||||
                              <td className="text-start">
 | 
					                                  Address:
 | 
				
			||||||
                                {currentEmployee?.currentAddress || <em>NA</em>}
 | 
					                                </td>
 | 
				
			||||||
                              </td>
 | 
					                                <td className="text-start">
 | 
				
			||||||
                            </tr>
 | 
					                                  {currentEmployee?.currentAddress || (
 | 
				
			||||||
                          </tbody>
 | 
					                                    <em>NA</em>
 | 
				
			||||||
                        </table>
 | 
					                                  )}
 | 
				
			||||||
 | 
					                                </td>
 | 
				
			||||||
 | 
					                              </tr>
 | 
				
			||||||
 | 
					                            </tbody>
 | 
				
			||||||
 | 
					                          </table>
 | 
				
			||||||
 | 
					                        </div>
 | 
				
			||||||
 | 
					                        <button
 | 
				
			||||||
 | 
					                          className="btn btn-primary btn-block"
 | 
				
			||||||
 | 
					                          onClick={() => handleShow()}
 | 
				
			||||||
 | 
					                        >
 | 
				
			||||||
 | 
					                          Edit Profile
 | 
				
			||||||
 | 
					                        </button>
 | 
				
			||||||
 | 
					                        {currentEmployee?.id == profile?.employeeInfo?.id && (
 | 
				
			||||||
 | 
					                          <button
 | 
				
			||||||
 | 
					                            className="btn btn-outline-primary btn-block mt-2"
 | 
				
			||||||
 | 
					                            onClick={() => openChangePassword()}
 | 
				
			||||||
 | 
					                          >
 | 
				
			||||||
 | 
					                            Change Password
 | 
				
			||||||
 | 
					                          </button>
 | 
				
			||||||
 | 
					                        )}
 | 
				
			||||||
                      </div>
 | 
					                      </div>
 | 
				
			||||||
                      <button
 | 
					 | 
				
			||||||
                        className="btn btn-primary btn-block"
 | 
					 | 
				
			||||||
                        onClick={() =>
 | 
					 | 
				
			||||||
                          handleShow()
 | 
					 | 
				
			||||||
                        }
 | 
					 | 
				
			||||||
                      >
 | 
					 | 
				
			||||||
                        Edit Profile
 | 
					 | 
				
			||||||
                      </button>
 | 
					 | 
				
			||||||
                    </div>
 | 
					                    </div>
 | 
				
			||||||
                  </div>
 | 
					                  </div>
 | 
				
			||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
              </div>
 | 
					              </div>
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
          </div>
 | 
					          </div>
 | 
				
			||||||
        </div>
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        <div className="col-12 col-lg-8 order-2 order-lg-2 mb-4">
 | 
					          <div className="col-12 col-lg-8 order-2 order-lg-2 mb-4">
 | 
				
			||||||
          <div className="row">
 | 
					            <div className="row">
 | 
				
			||||||
            <EmployeeNav
 | 
					              <EmployeeNav
 | 
				
			||||||
              onPillClick={handlePillClick}
 | 
					                onPillClick={handlePillClick}
 | 
				
			||||||
              activePill={activePill}
 | 
					                activePill={activePill}
 | 
				
			||||||
            />
 | 
					              />
 | 
				
			||||||
          </div>
 | 
					            </div>
 | 
				
			||||||
          <div className="card">
 | 
					            <div className="card">
 | 
				
			||||||
            <div className="row row-bordered g-0">{renderContent()}</div>
 | 
					              <div className="row row-bordered g-0">{renderContent()}</div>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
          </div>
 | 
					          </div>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
    </div>
 | 
					 | 
				
			||||||
    </>
 | 
					    </>
 | 
				
			||||||
 | 
					 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -1,7 +1,7 @@
 | 
				
			|||||||
import React, { useEffect, useState } from "react";
 | 
					import React, { useEffect, useState } from "react";
 | 
				
			||||||
import { useSelector } from "react-redux";
 | 
					import { useSelector } from "react-redux";
 | 
				
			||||||
import { useHasUserPermission } from "../../hooks/useHasUserPermission";
 | 
					import { useHasUserPermission } from "../../hooks/useHasUserPermission";
 | 
				
			||||||
import { MANAGE_MASTER } from "../../utils/constants";
 | 
					import { ITEMS_PER_PAGE, MANAGE_MASTER } from "../../utils/constants";
 | 
				
			||||||
import showToast from "../../services/toastService";
 | 
					import showToast from "../../services/toastService";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const MasterTable = ({ data, columns, loading, handleModalData }) => {
 | 
					const MasterTable = ({ data, columns, loading, handleModalData }) => {
 | 
				
			||||||
@ -21,7 +21,7 @@ const MasterTable = ({ data, columns, loading, handleModalData }) => {
 | 
				
			|||||||
  const safeData = Array.isArray(data) ? data : [];
 | 
					  const safeData = Array.isArray(data) ? data : [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const [currentPage, setCurrentPage] = useState(1);
 | 
					  const [currentPage, setCurrentPage] = useState(1);
 | 
				
			||||||
  const [itemsPerPage] = useState(20);
 | 
					  const [itemsPerPage] = useState(ITEMS_PER_PAGE);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const sortKeys = {
 | 
					  const sortKeys = {
 | 
				
			||||||
    "Application Role": "role",
 | 
					    "Application Role": "role",
 | 
				
			||||||
 | 
				
			|||||||
@ -22,6 +22,7 @@ import {
 | 
				
			|||||||
import { useDispatch } from "react-redux";
 | 
					import { useDispatch } from "react-redux";
 | 
				
			||||||
import { setProjectId } from "../../slices/localVariablesSlice";
 | 
					import { setProjectId } from "../../slices/localVariablesSlice";
 | 
				
			||||||
import { ComingSoonPage } from "../Misc/ComingSoonPage";
 | 
					import { ComingSoonPage } from "../Misc/ComingSoonPage";
 | 
				
			||||||
 | 
					import Directory from "../Directory/Directory";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const ProjectDetails = () => {
 | 
					const ProjectDetails = () => {
 | 
				
			||||||
  let { projectId } = useParams();
 | 
					  let { projectId } = useParams();
 | 
				
			||||||
@ -117,12 +118,10 @@ const ProjectDetails = () => {
 | 
				
			|||||||
        );
 | 
					        );
 | 
				
			||||||
        break;
 | 
					        break;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      case "activities": {
 | 
					      case "directory": {
 | 
				
			||||||
        return (
 | 
					        return (
 | 
				
			||||||
          <div className="row">
 | 
					          <div className="row">
 | 
				
			||||||
            <div className="col-lg-12 col-xl-12">
 | 
					              <Directory IsPage={ false} prefernceContacts={projectDetails.id} />
 | 
				
			||||||
              <ActivityTimeline></ActivityTimeline>
 | 
					 | 
				
			||||||
            </div>
 | 
					 | 
				
			||||||
          </div>
 | 
					          </div>
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
				
			|||||||
@ -9,7 +9,7 @@ import showToast from "../../services/toastService";
 | 
				
			|||||||
import { getCachedData, cacheData } from "../../slices/apiDataManager";
 | 
					import { getCachedData, cacheData } from "../../slices/apiDataManager";
 | 
				
			||||||
import { useHasUserPermission } from "../../hooks/useHasUserPermission";
 | 
					import { useHasUserPermission } from "../../hooks/useHasUserPermission";
 | 
				
			||||||
import { useProfile } from "../../hooks/useProfile";
 | 
					import { useProfile } from "../../hooks/useProfile";
 | 
				
			||||||
import { MANAGE_PROJECT } from "../../utils/constants";
 | 
					import { ITEMS_PER_PAGE, MANAGE_PROJECT } from "../../utils/constants";
 | 
				
			||||||
import ProjectListView from "./ProjectListView";
 | 
					import ProjectListView from "./ProjectListView";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const ProjectList = () => {
 | 
					const ProjectList = () => {
 | 
				
			||||||
@ -25,7 +25,7 @@ const ProjectList = () => {
 | 
				
			|||||||
  const dispatch = useDispatch();
 | 
					  const dispatch = useDispatch();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const [currentPage, setCurrentPage] = useState(1);
 | 
					  const [currentPage, setCurrentPage] = useState(1);
 | 
				
			||||||
  const [itemsPerPage] = useState(10);
 | 
					  const [itemsPerPage] = useState(ITEMS_PER_PAGE);
 | 
				
			||||||
  const [searchTerm, setSearchTerm] = useState("");
 | 
					  const [searchTerm, setSearchTerm] = useState("");
 | 
				
			||||||
  const [selectedStatuses, setSelectedStatuses] = useState([
 | 
					  const [selectedStatuses, setSelectedStatuses] = useState([
 | 
				
			||||||
    "b74da4c2-d07e-46f2-9919-e75e49b12731",
 | 
					    "b74da4c2-d07e-46f2-9919-e75e49b12731",
 | 
				
			||||||
@ -37,7 +37,7 @@ const ProjectList = () => {
 | 
				
			|||||||
  const handleShow = () => setShowModal(true);
 | 
					  const handleShow = () => setShowModal(true);
 | 
				
			||||||
  const handleClose = () => setShowModal(false);
 | 
					  const handleClose = () => setShowModal(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  useEffect(() => {
 | 
					  const sortingProject = (projects) =>{
 | 
				
			||||||
    if (!loading && Array.isArray(projects)) {
 | 
					    if (!loading && Array.isArray(projects)) {
 | 
				
			||||||
      const grouped = {};
 | 
					      const grouped = {};
 | 
				
			||||||
      projects.forEach((project) => {
 | 
					      projects.forEach((project) => {
 | 
				
			||||||
@ -56,6 +56,10 @@ const ProjectList = () => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
      setProjectList(sortedGrouped);
 | 
					      setProjectList(sortedGrouped);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  useEffect(() => {
 | 
				
			||||||
 | 
					    sortingProject(projects)
 | 
				
			||||||
  }, [projects, loginUser?.projects, loading]);
 | 
					  }, [projects, loginUser?.projects, loading]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  useEffect(() => {
 | 
					  useEffect(() => {
 | 
				
			||||||
@ -75,6 +79,7 @@ const ProjectList = () => {
 | 
				
			|||||||
        setProjectList( ( prev ) => [ ...prev, response.data ] );
 | 
					        setProjectList( ( prev ) => [ ...prev, response.data ] );
 | 
				
			||||||
        setloading( false )
 | 
					        setloading( false )
 | 
				
			||||||
        reset()
 | 
					        reset()
 | 
				
			||||||
 | 
					        sortingProject(getCachedData("projectslist"))
 | 
				
			||||||
        showToast("Project Created successfully.", "success");
 | 
					        showToast("Project Created successfully.", "success");
 | 
				
			||||||
        setShowModal(false);
 | 
					        setShowModal(false);
 | 
				
			||||||
      })
 | 
					      })
 | 
				
			||||||
@ -268,7 +273,7 @@ const ProjectList = () => {
 | 
				
			|||||||
                    <th className="text-start" colSpan={5}>
 | 
					                    <th className="text-start" colSpan={5}>
 | 
				
			||||||
                      Project Name
 | 
					                      Project Name
 | 
				
			||||||
                    </th>
 | 
					                    </th>
 | 
				
			||||||
                    <th className="mx-2 text-start">Project Manger</th>
 | 
					                    <th className="mx-2 text-start">Contact Person</th>
 | 
				
			||||||
                    <th className="mx-2">START DATE</th>
 | 
					                    <th className="mx-2">START DATE</th>
 | 
				
			||||||
                    <th className="mx-2">DEADLINE</th>
 | 
					                    <th className="mx-2">DEADLINE</th>
 | 
				
			||||||
                    <th className="mx-2">Task</th>
 | 
					                    <th className="mx-2">Task</th>
 | 
				
			||||||
@ -336,7 +341,7 @@ const ProjectList = () => {
 | 
				
			|||||||
                    </tr>
 | 
					                    </tr>
 | 
				
			||||||
                  ) : (
 | 
					                  ) : (
 | 
				
			||||||
                    currentItems.map((project) => (
 | 
					                    currentItems.map((project) => (
 | 
				
			||||||
                      <ProjectListView key={project.id} projectData={project} />
 | 
					                      <ProjectListView key={project.id} projectData={project} recall={sortingProject} />
 | 
				
			||||||
                    ))
 | 
					                    ))
 | 
				
			||||||
                  )}
 | 
					                  )}
 | 
				
			||||||
                </tbody>
 | 
					                </tbody>
 | 
				
			||||||
@ -344,7 +349,7 @@ const ProjectList = () => {
 | 
				
			|||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
          ) : (
 | 
					          ) : (
 | 
				
			||||||
            currentItems.map((project) => (
 | 
					            currentItems.map((project) => (
 | 
				
			||||||
              <ProjectCard key={project.id} projectData={project} />
 | 
					              <ProjectCard key={project.id} projectData={project} recall={sortingProject} />
 | 
				
			||||||
            ))
 | 
					            ))
 | 
				
			||||||
          )}
 | 
					          )}
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
 | 
				
			|||||||
@ -15,7 +15,7 @@ import ManageProjectInfo from "../../components/Project/ManageProjectInfo";
 | 
				
			|||||||
import showToast from "../../services/toastService";
 | 
					import showToast from "../../services/toastService";
 | 
				
			||||||
import { getCachedData, cacheData } from "../../slices/apiDataManager";
 | 
					import { getCachedData, cacheData } from "../../slices/apiDataManager";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const ProjectListView = ({ projectData }) => {
 | 
					const ProjectListView = ({ projectData, recall }) => {
 | 
				
			||||||
  const [projectInfo, setProjectInfo] = useState(projectData);
 | 
					  const [projectInfo, setProjectInfo] = useState(projectData);
 | 
				
			||||||
  const [projectDetails, setProjectDetails] = useState(null);
 | 
					  const [projectDetails, setProjectDetails] = useState(null);
 | 
				
			||||||
  const [showModal, setShowModal] = useState(false);
 | 
					  const [showModal, setShowModal] = useState(false);
 | 
				
			||||||
@ -76,6 +76,7 @@ const ProjectListView = ({ projectData }) => {
 | 
				
			|||||||
            );
 | 
					            );
 | 
				
			||||||
            cacheData("projectslist", updatedProjectsList);
 | 
					            cacheData("projectslist", updatedProjectsList);
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
 | 
					          recall(getCachedData("projectslist"));
 | 
				
			||||||
          showToast("Project updated successfully.", "success");
 | 
					          showToast("Project updated successfully.", "success");
 | 
				
			||||||
          setShowModal(false);
 | 
					          setShowModal(false);
 | 
				
			||||||
        })
 | 
					        })
 | 
				
			||||||
@ -87,19 +88,21 @@ const ProjectListView = ({ projectData }) => {
 | 
				
			|||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <>
 | 
					    <>
 | 
				
			||||||
      {showModal && projectDetails && (
 | 
					      {showModal && projectDetails && (
 | 
				
			||||||
        <div
 | 
					        <tr>
 | 
				
			||||||
          className="modal fade show"
 | 
					          <td
 | 
				
			||||||
          tabIndex="-1"
 | 
					            className="modal fade show"
 | 
				
			||||||
          role="dialog"
 | 
					            tabIndex="-1"
 | 
				
			||||||
          style={{ display: "block" }}
 | 
					            role="dialog"
 | 
				
			||||||
          aria-hidden="false"
 | 
					            style={{ display: "block" }}
 | 
				
			||||||
        >
 | 
					            aria-hidden="false"
 | 
				
			||||||
          <ManageProjectInfo
 | 
					          >
 | 
				
			||||||
            project={projectDetails}
 | 
					            <ManageProjectInfo
 | 
				
			||||||
            handleSubmitForm={handleFormSubmit}
 | 
					              project={projectDetails}
 | 
				
			||||||
            onClose={handleClose}
 | 
					              handleSubmitForm={handleFormSubmit}
 | 
				
			||||||
          />
 | 
					              onClose={handleClose}
 | 
				
			||||||
        </div>
 | 
					            />
 | 
				
			||||||
 | 
					          </td>
 | 
				
			||||||
 | 
					        </tr>
 | 
				
			||||||
      )}
 | 
					      )}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      <tr className="py-8">
 | 
					      <tr className="py-8">
 | 
				
			||||||
@ -108,7 +111,9 @@ const ProjectListView = ({ projectData }) => {
 | 
				
			|||||||
            className="text-primary cursor-pointer"
 | 
					            className="text-primary cursor-pointer"
 | 
				
			||||||
            onClick={() => navigate(`/projects/${projectInfo.id}`)}
 | 
					            onClick={() => navigate(`/projects/${projectInfo.id}`)}
 | 
				
			||||||
          >
 | 
					          >
 | 
				
			||||||
            {projectInfo.name}
 | 
					            {projectInfo.shortName
 | 
				
			||||||
 | 
					              ? `${projectInfo.name} (${projectInfo.shortName})`
 | 
				
			||||||
 | 
					              : projectInfo.name}
 | 
				
			||||||
          </strong>
 | 
					          </strong>
 | 
				
			||||||
        </td>
 | 
					        </td>
 | 
				
			||||||
        <td className="text-start small">{projectInfo.contactPerson}</td>
 | 
					        <td className="text-start small">{projectInfo.contactPerson}</td>
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										35
									
								
								src/repositories/DirectoryRepository.jsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								src/repositories/DirectoryRepository.jsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,35 @@
 | 
				
			|||||||
 | 
					import { api } from "../utils/axiosClient";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const DirectoryRepository = {
 | 
				
			||||||
 | 
					  GetOrganizations: () => api.get("/api/directory/organization"),
 | 
				
			||||||
 | 
					  GetContacts: (isActive, projectId) => {
 | 
				
			||||||
 | 
					    const params = new URLSearchParams();
 | 
				
			||||||
 | 
					    params.append("active", isActive);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (projectId) {
 | 
				
			||||||
 | 
					      params.append("projectId", projectId);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return api.get(`/api/Directory?${params.toString()}`);
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  CreateContact: (data) => api.post("/api/directory", data),
 | 
				
			||||||
 | 
					  UpdateContact: (id, data) => api.put(`/api/directory/${id}`, data),
 | 
				
			||||||
 | 
					  DeleteContact: (id, isActive) =>
 | 
				
			||||||
 | 
					    api.delete(`/api/directory/${id}/?active=${isActive}`),
 | 
				
			||||||
 | 
					  AssignedBuckets: (id, data) =>
 | 
				
			||||||
 | 
					    api.post(`/api/directory/assign-bucket/${id}`, data),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  GetBucktes: () => api.get(`/api/directory/buckets`),
 | 
				
			||||||
 | 
					  CreateBuckets: (data) => api.post(`/api/Directory/bucket`, data),
 | 
				
			||||||
 | 
					  UpdateBuckets: (id, data) => api.put(`/api/Directory/bucket/${id}`, data),
 | 
				
			||||||
 | 
					  DeleteBucket: (id) => api.delete(`/api/directory/bucket/${id}`),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  GetContactProfile: (id) => api.get(`/api/directory/profile/${id}`),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  CreateNote: (data) => api.post("/api/directory/note", data),
 | 
				
			||||||
 | 
					  GetNote: (id, isActive) =>
 | 
				
			||||||
 | 
					    api.get(`/api/directory/notes/${id}?active=${isActive}`),
 | 
				
			||||||
 | 
					  UpdateNote: (id, data) => api.put(`/api/directory/note/${id}`, data),
 | 
				
			||||||
 | 
					  DeleteNote: (id, isActive) =>
 | 
				
			||||||
 | 
					    api.delete(`/api/directory/note/${id}?active=${isActive}`),
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
@ -40,10 +40,20 @@ export const MasterRespository = {
 | 
				
			|||||||
  "Job Role": ( id ) => api.delete( `/api/roles/jobrole/${ id }` ),
 | 
					  "Job Role": ( id ) => api.delete( `/api/roles/jobrole/${ id }` ),
 | 
				
			||||||
  "Activity": ( id ) => api.delete( `/api/master/activity/delete/${ id }` ),
 | 
					  "Activity": ( id ) => api.delete( `/api/master/activity/delete/${ id }` ),
 | 
				
			||||||
  "Application Role":(id)=>api.delete(`/api/roles/${id}`),
 | 
					  "Application Role":(id)=>api.delete(`/api/roles/${id}`),
 | 
				
			||||||
  "Work Category": (id) => api.delete(`api/master/work-category/${id}`),
 | 
					  "Work Category": ( id ) => api.delete( `api/master/work-category/${ id }` ),
 | 
				
			||||||
 | 
					  "Contact Category": ( id ) => api.delete( `/api/master/contact-category` ),
 | 
				
			||||||
 | 
					  "Contact Tag" :(id)=>api.delete(`/api/master/contact-tag/${id}`),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  getWorkCategory:() => api.get(`/api/master/work-categories`),
 | 
					  getWorkCategory:() => api.get(`/api/master/work-categories`),
 | 
				
			||||||
  createWorkCategory: (data) => api.post(`/api/master/work-category`,data),
 | 
					  createWorkCategory: (data) => api.post(`/api/master/work-category`,data),
 | 
				
			||||||
  updateWorkCategory: (id,data) => api.post(`/api/master/work-category/edit/${id}`,data),
 | 
					  updateWorkCategory: ( id, data ) => api.post( `/api/master/work-category/edit/${ id }`, data ),
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  getContactCategory: () => api.get( `/api/master/contact-categories` ),
 | 
				
			||||||
 | 
					  createContactCategory: (data ) => api.post( `/api/master/contact-category`, data ),
 | 
				
			||||||
 | 
					  updateContactCategory: ( id, data ) => api.post( `/api/master/contact-category/edit/${ id }`, data ),
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  getContactTag: () => api.get( `/api/master/contact-tags` ),
 | 
				
			||||||
 | 
					  createContactTag: (data ) => api.post( `/api/master/contact-tag`, data ),
 | 
				
			||||||
 | 
					  updateContactTag: ( id, data ) => api.post( `/api/master/contact-tag/edit/${ id }`, data )
 | 
				
			||||||
  
 | 
					  
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -20,7 +20,9 @@ const ProjectRepository = {
 | 
				
			|||||||
  deleteProjectTask:(id)=> api.delete(`/api/project/task/${id}`),
 | 
					  deleteProjectTask:(id)=> api.delete(`/api/project/task/${id}`),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  updateProject: (id, data) => api.put(`/api/project/update/${id}`, data),
 | 
					  updateProject: (id, data) => api.put(`/api/project/update/${id}`, data),
 | 
				
			||||||
  deleteProject: (id) => api.delete(`/projects/${id}`),
 | 
					  deleteProject: ( id ) => api.delete( `/projects/${ id }` ),
 | 
				
			||||||
 | 
					  getProjectsByEmployee: ( id ) => api.get( `/api/project/assigned-projects/${ id }` ),
 | 
				
			||||||
 | 
					  updateProjectsByEmployee:(id,data)=>api.post(`/api/project/assign-projects/${id}`,data)
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const TasksRepository = {
 | 
					export const TasksRepository = {
 | 
				
			||||||
 | 
				
			|||||||
@ -1,5 +1,6 @@
 | 
				
			|||||||
export const THRESH_HOLD = 48;  //  hours
 | 
					export const THRESH_HOLD = 48;  //  hours
 | 
				
			||||||
export const DURATION_TIME = 10; // minutes
 | 
					export const DURATION_TIME = 10; // minutes
 | 
				
			||||||
 | 
					export const ITEMS_PER_PAGE = 20;
 | 
				
			||||||
export const OTP_EXPIRY_SECONDS = 600  // OTP time
 | 
					export const OTP_EXPIRY_SECONDS = 600  // OTP time
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const MANAGE_MASTER = "588a8824-f924-4955-82d8-fc51956cf323";
 | 
					export const MANAGE_MASTER = "588a8824-f924-4955-82d8-fc51956cf323";
 | 
				
			||||||
@ -27,3 +28,8 @@ export const VIEW_TASK = "9fcc5f87-25e3-4846-90ac-67a71ab92e3c"
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
export const ASSIGN_REPORT_TASK = "6a32379b-8b3f-49a6-8c48-4b7ac1b55dc2"
 | 
					export const ASSIGN_REPORT_TASK = "6a32379b-8b3f-49a6-8c48-4b7ac1b55dc2"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const DIRECTORY_ADMIN = "4286a13b-bb40-4879-8c6d-18e9e393beda"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const DIRECTORY_MANAGER = "62668630-13ce-4f52-a0f0-db38af2230c5"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const DIRECTORY_USER = "0f919170-92d4-4337-abd3-49b66fc871bb"
 | 
				
			||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user