Compare commits

...

334 Commits

Author SHA1 Message Date
12b632f087 added new api for list of org 2025-10-11 18:06:51 +05:30
6ee4fb6d04 Updated TeamEmployeeList to fetch employees using useOrganizationEmployees during search. 2025-10-11 17:48:25 +05:30
67bb685d4b added new api for orgaization dropdown 2025-10-11 16:55:22 +05:30
8fd4e7f3f1 added space between first and last name 2025-10-11 16:30:27 +05:30
aca2decb00 clear fully cache after remove session 2025-10-11 16:26:04 +05:30
f7f4b68997 changed reimburse data during transaction to current date 2025-10-11 15:09:04 +05:30
f839613066 Merge pull request 'Fix: Ensure orgData can be cleared when opening Organization Modal' (#474) from HotChanges_11_10_25 into main
Reviewed-on: #474
Merged
2025-10-11 08:42:26 +00:00
b58bd33774 added requested b and requested at column inside rgularization 2025-10-11 14:08:27 +05:30
136bc94c5b removed dbugger and add new classes 2025-10-11 13:12:23 +05:30
281a956ac8 fixed directory header layout 2025-10-11 12:43:37 +05:30
31882c3d12 REMOVED UNNEEDED CARD CLASS 2025-10-11 11:13:38 +05:30
a64635cd37 Fix: Ensure orgData can be cleared when opening Organization Modal 2025-10-11 10:29:40 +05:30
9b8c8c34ab Merge pull request 'HotChanges_10_10_25' (#472) from HotChanges_10_10_25 into main
Reviewed-on: #472
Merged
2025-10-10 14:50:04 +00:00
704ba79289 prevent to select future date 2025-10-10 20:17:06 +05:30
3440467107 wapped header content into separated div 2025-10-10 18:25:19 +05:30
f4edcfd2f3 changed prod url 2025-10-10 18:18:20 +05:30
62e5c6899a profile icon should display always at end 2025-10-10 18:17:24 +05:30
00a23b3de9 changed class for table responsive 2025-10-10 18:11:43 +05:30
79161a8ede prevent to pick future date 2025-10-10 18:09:35 +05:30
860779d096 fixed service list show in Dialy Task 2025-10-10 18:08:21 +05:30
a2067e150d fixed service list show 2025-10-10 17:51:12 +05:30
edce5ef614 added newhook that return only organization employee for employee list (Active or Inactive) 2025-10-10 16:48:22 +05:30
dd944b3414 datepicker should not take future date 2025-10-10 16:46:34 +05:30
13d3572cf6 fixed datepicke ui 2025-10-10 16:45:43 +05:30
0dd7c19457 added navigation hook to replace path 2025-10-10 15:37:59 +05:30
3cc4f0b416 mismatch service api fixed 2025-10-10 14:15:30 +05:30
f8095ac9bf upgraded progress 2025-10-10 10:55:51 +05:30
6280abf95e added marging between fltes input box 2025-10-09 17:32:39 +05:30
1200937097 Merge pull request 'HotChanges_09_10_25 : Added Daily Task Filter' (#467) from HotChanges_09_10_25 into main
Reviewed-on: #467
Merged
2025-10-09 12:00:21 +00:00
5d1ccb9572 changed buttons size 2025-10-09 17:25:03 +05:30
91ffc5a0e0 added filter for DailyTask Report 2025-10-09 17:23:04 +05:30
1c4804fed2 added for serviceI dropdown list 2025-10-09 16:48:15 +05:30
0e5e716df2 removed debuggger 2025-10-09 16:20:43 +05:30
aecaee7116 addded project when project goin to update state 2025-10-09 15:38:59 +05:30
97dca1a10b removed unused code 2025-10-07 14:53:48 +05:30
c35eacca5a addded filter added inside api 2025-10-07 14:02:25 +05:30
5e27ed36fa Merge pull request 'HotChanges_06-10-25 : Image Gallery and Attendanc CheckIn check-Out' (#459) from HotChanges_06-10-25 into main
Reviewed-on: #459
Merged
2025-10-07 07:12:49 +00:00
8bcfcc5718 fixed attendance - check In -out and persisted date range from Redux store for attendance logs 2025-10-07 07:12:49 +00:00
ad1bef4f7b added gallery and gallery filter 2025-10-07 07:12:49 +00:00
9886fac03e employee attendance display sorted by date, 2025-10-06 17:20:36 +05:30
a28a7fb444 checked-Out Time should be greather than or equal to checked In time 2025-10-06 16:07:31 +05:30
470421b730 added two date and service-name column inside ProjectAssignedOrgslist and fixed inactive fn in project team 2025-10-06 10:58:07 +05:30
7505b790a7 added trim fn to avoid extra spacing 2025-10-06 10:06:31 +05:30
8fd13247c7 Merge pull request 'HotChanges_04_10_25' (#450) from HotChanges_04_10_25 into main
Reviewed-on: #450
Merged
2025-10-05 04:28:55 +00:00
0fec257354 make removeSession flexible to clear local, session, or both 2025-10-05 00:21:19 +05:30
638c033705 fixed employee name show whenever update expense - for paid by 2025-10-05 00:14:39 +05:30
7872e21477 changed date utils and added search employee inside manage expense 2025-10-04 19:50:26 +05:30
6928bbd309 Changes in ProjectRepository api for getProjectInfraByProject 2025-10-04 17:50:54 +05:30
3693af3d00 added missing projects at ManageContact (Edit&Crate) 2025-10-04 16:36:11 +05:30
d5df200ede Merge pull request 'Organization_Management : Organization Hierarchy' (#443) from Organization_Management into main
Reviewed-on: #443
Merged
2025-09-30 09:07:30 +00:00
9c6450496e added closed fun after create project and changed label name 2025-09-30 14:22:01 +05:30
cfd3986479 fixed small changed like heading, font change 2025-09-30 11:59:16 +05:30
764b145ad9 empty select activity, planne work fields after successfully created task 2025-09-29 18:31:56 +05:30
20c7cf7f37 added right path for organization info show 2025-09-29 18:00:48 +05:30
6d74940c0c added optional chain at view org details 2025-09-29 17:55:56 +05:30
02dcd8611f user could not create subtask completed equal to approve task and fixd error imported subTask component 2025-09-29 17:29:11 +05:30
02600308e8 rmoved unused and console 2025-09-29 16:53:24 +05:30
d1c72291a3 update date utility for localtoUtc fn 2025-09-29 15:20:54 +05:30
fdbd81c5e7 removed console 2025-09-29 15:13:36 +05:30
28d5ef653d added optional chain for manage project to prevent run time error 2025-09-29 15:10:49 +05:30
550b142d74 fixed run time date error 2025-09-29 15:03:52 +05:30
68335f0695 added temp. placeholder for image gallary 2025-09-29 14:38:23 +05:30
4ea20981fc Merge pull request 'Changes in Directory edit' (#407) from Kartik_Bug#1180 into Organization_Management
Reviewed-on: #407
Merged
2025-09-29 09:00:27 +00:00
965e1e4808 Correction in Edit Bucket in Directory. 2025-09-29 09:00:27 +00:00
1c376fe91f Changes in Directory edit 2025-09-29 09:00:27 +00:00
61835cb189 added clear filter 2025-09-29 12:55:31 +05:30
375c482b61 renamed Inactive to Includes Inactive 2025-09-29 12:04:33 +05:30
72424eee53 added updation for fllter 2025-09-29 11:52:11 +05:30
22514b1fa0 Merge pull request 'ProjectUpdationForOrganizaion : added Two fields inside Project create and update form - Promoter and PMC and added Remember me' (#442) from ProjectUpdationForOrganizaion into Organization_Management
Reviewed-on: #442
Merged
2025-09-28 18:44:30 +00:00
61b209a082 addded remember me functionality 2025-09-29 00:13:52 +05:30
198e31290c updated project files 2025-09-28 22:15:07 +05:30
482f8a9bcb added organization api in repo. and removed unuse files 2025-09-27 23:54:44 +05:30
2489095b0b Merge pull request 'Date picker in Document Filter shows pre-filled date instead of blank' (#422) from Kartik_Bug#1250 into Organization_Management
Reviewed-on: #422
Merged
2025-09-27 09:41:15 +00:00
eb8d269662 Date picker in Document Filter shows pre-filled date instead of blank 2025-09-27 09:41:15 +00:00
bbd8ed12f6 Merge pull request 'Date field not cleared in Tenant filter after clicking Clear button' (#432) from Kartik_Bug#1306 into Organization_Management
Reviewed-on: #432
Merged
2025-09-27 09:40:05 +00:00
1e7b4ba21e Date field not cleared in Tenant filter after clicking Clear button 2025-09-27 09:40:05 +00:00
4de3987a37 Merge pull request 'Add extra spacing in Infra Work details.' (#434) from Kartik_Bug#1359 into Organization_Management
Reviewed-on: #434
Merged
2025-09-27 09:38:34 +00:00
7455d8a221 Add extra spacing in Infra Work details. 2025-09-27 09:38:34 +00:00
3f4b7d08d4 Merge pull request 'Add an info (ℹ️) icon to the Daily Progress Report’s Total Pending Task' (#435) from Kartik_Task#1361 into Organization_Management
Reviewed-on: #435
Merged
2025-09-27 09:37:36 +00:00
7ac3268514 Add an info (ℹ️) icon to the Daily Progress Report’s Total Pending Task 2025-09-27 09:37:36 +00:00
f8740472de Merge pull request 'Scrollbar Behavior in "Choose Organization" (Projects → Organization Tab)' (#436) from Kartik_Task#1374 into Organization_Management
Reviewed-on: #436
Merged
2025-09-27 09:36:27 +00:00
be72ca9a58 Scrollbar Behavior in "Choose Organization" (Projects → Organization Tab) 2025-09-27 09:36:27 +00:00
c71c00c0f7 Merge pull request 'Selected Building and Floor Should Persist While Creating Work Area and Activity (Project Infrastructure)' (#438) from Kartik_Bug#1372 into Organization_Management
Reviewed-on: #438
Merged
2025-09-27 09:35:27 +00:00
112e0ff798 Selected Building and Floor Should Persist While Creating Work Area and Activity (Project Infrastructure) 2025-09-27 09:35:27 +00:00
96eb030457 Merge pull request 'Tenant Creation – Error banner for plan selection/currency should not be shown' (#439) from Kartik_Bug#1370 into Organization_Management
Reviewed-on: #439
Merged
2025-09-27 09:34:48 +00:00
a873ace109 Tenant Creation – Error banner for plan selection/currency should not be shown 2025-09-27 09:34:48 +00:00
fb164bd2f2 Merge pull request 'Switch Tenant & Goto workspace option label should be “Switch Workspace' (#440) from Kartik_Bug#1366 into Organization_Management
Reviewed-on: #440
Merged
2025-09-27 09:34:07 +00:00
0d6708619f Switch Tenant & Goto workspace option label should be “Switch Workspace 2025-09-27 09:34:07 +00:00
edba191a2e Merge pull request 'Tenant Creation – Logo removed when navigating back from second page' (#441) from Kartik_Bug#1369 into Organization_Management
Reviewed-on: #441
Merged
2025-09-27 09:33:46 +00:00
ddfe09b570 Tenant Creation – Logo removed when navigating back from second page 2025-09-27 09:33:46 +00:00
acb899dd2e Merge pull request 'Change the label in Assign Organization.' (#433) from Kartik_Bug#1353 into Organization_Management
Reviewed-on: #433
Merged
2025-09-27 09:31:52 +00:00
ae66cb3705 Change the label in Assign Organization. 2025-09-27 09:31:52 +00:00
d52fa00de0 Merge pull request 'Team Assign ToProject : With other Organization employees' (#437) from TeamAssignToProject into Organization_Management
Reviewed-on: #437
Merged
2025-09-27 09:29:59 +00:00
ca8a41bb63 added style classes for footer 2025-09-27 09:29:59 +00:00
265c74f079 added filter and sorted employee list - Team 2025-09-27 09:29:59 +00:00
2ef1fcfd1d successfullly assigned employe to project 2025-09-27 09:29:59 +00:00
3233043cf2 initial setup for assign emp to project 2025-09-27 09:29:59 +00:00
3fddb686d3 change get employee at assign task to employee 2025-09-27 14:58:39 +05:30
4fd6e5cc1a Merge branch 'Organization_Management' of https://git.marcoaiot.com/admin/marco.pms.web into Organization_Management 2025-09-27 10:33:40 +05:30
1fb8eb9ef1 Create Task popup will open at all the time while submitting. 2025-09-26 16:21:37 +05:30
1a3890e837 Removing Create and Activity from MasterModal.jsx file. 2025-09-26 15:34:08 +05:30
2dbf08e330 Removing create and Edit activity component. 2025-09-26 15:02:03 +05:30
b044b88c49 Adding border in Group and Activities popup and Removing mandatory field in Tenant. 2025-09-26 14:56:19 +05:30
db815ba038 Rmoving Refresh icon and Label in Tenant. 2025-09-26 11:51:55 +05:30
53fa013c39 split projectList component 2025-09-26 11:47:03 +05:30
22a1ad45e7 Change the name of Switch Tenant to Workspace 2025-09-26 11:37:31 +05:30
91dcd7c132 The images on the Forgot page and Request Demo page should be centered after the update. 2025-09-26 10:53:31 +05:30
7d18edfa9b added tenant selection path within otp sign in way 2025-09-26 10:01:09 +05:30
fb08e48edd added iniial state of daly progress filte panel 2025-09-25 19:30:55 +05:30
69c225ac72 Adding Services dropdown in Organization Creation dropdown. 2025-09-25 16:09:36 +05:30
182280e91d Merge pull request 'Correction of Delete Employee Popup Texts' (#424) from Kartik_Bug#1261 into Organization_Management
Reviewed-on: #424
Mereged
2025-09-25 09:21:54 +00:00
38bd8d36c0 Correction of Delete Employee Popup Texts 2025-09-25 09:21:54 +00:00
a4bccc9bf6 Merge pull request 'Verify mandatory indicator (*) for Reference field while creating a Tenant' (#427) from Kartik_Bug#1295 into Organization_Management
Reviewed-on: #427
Merged
2025-09-25 09:20:48 +00:00
0e6dd93260 Verify mandatory indicator (*) for Reference field while creating a Tenant 2025-09-25 09:20:48 +00:00
245219ad71 Merge pull request 'Inconsistent search bar height in Tenant module' (#429) from Kartik_Bug#1300 into Organization_Management
Reviewed-on: #429
Merged
2025-09-25 09:19:07 +00:00
978a497f28 Inconsistent search bar height in Tenant module 2025-09-25 09:19:07 +00:00
451d0a785f Merge pull request 'Change all create button UI.' (#430) from Kartik_Task#1345 into Organization_Management
Reviewed-on: #430
Merged
2025-09-25 09:17:56 +00:00
84f9cb2e29 Change all create button UI. 2025-09-25 09:17:56 +00:00
9d9ca28bad Merge pull request 'Incorrect button sequence in Tenant Edit form' (#428) from Kartik_Bug#1301 into Organization_Management
Reviewed-on: #428
Merged
2025-09-25 09:17:16 +00:00
1d218056ac Incorrect button sequence in Tenant Edit form 2025-09-25 09:17:16 +00:00
58837cef0c Merge pull request 'Adding Activity-Group in Create Task popup.' (#431) from Kartik_Task_InfraMask#1266 into Organization_Management
Reviewed-on: #431
merged
2025-09-25 09:00:46 +00:00
1cd3bf6c7f updated assigned services poject whenver assigned new service 2025-09-25 09:00:46 +00:00
d2b10495bd Added Services Column in Edit activity modal 2025-09-25 09:00:46 +00:00
ae9c4833b3 Added Activity Group, and implemented sorting for Service, Activity Group, and Activities. 2025-09-25 09:00:46 +00:00
e69efe61cb Adding Activity-Group in Create Task popup. 2025-09-25 09:00:46 +00:00
92b1531b75 daily task planning filtering according to service 2025-09-25 14:11:04 +05:30
57edd92dce fixed blocking of dailyprogress by using pagination 2025-09-25 12:36:55 +05:30
49eaf857ad In Choose Orgainzation popup Find Organization and Search box show in one line. 2025-09-25 11:46:12 +05:30
ccdfc193c6 Change the position of setting and Organization in Project nav. 2025-09-25 09:52:13 +05:30
817b31379e Switch Tenant button move to top in User Profile. 2025-09-24 17:33:55 +05:30
b36120f73d Merge pull request 'In ExpensePanel add new Toggle button.' (#421) from Kartik_Task#1303 into Organization_Management
Reviewed-on: #421
2025-09-24 11:14:01 +00:00
e2ae2e5fbd change labels as per guidance 2025-09-24 16:43:36 +05:30
521e6690cb uncommit important stuff 2025-09-24 15:09:34 +05:30
1f4a7e5e9c forgot uncomment important stuff- tenant 2025-09-24 15:08:42 +05:30
1d3fcff859 Merge pull request 'Activities redirection issue – Redirects to “Marco Secure Solution Pvt Ltd” project by default' (#423) from Kartik_Bug#1292 into Organization_Management
Reviewed-on: #423
Merged
2025-09-24 07:20:45 +00:00
1286184e1f Activities redirection issue – Redirects to “Marco Secure Solution Pvt Ltd” project by default 2025-09-24 07:20:45 +00:00
4e315aafcf added small ui changed like table b-padding, button size and fixd delete activity and group 2025-09-24 12:49:00 +05:30
52e12426af Merge branch 'Organization_Management' of https://git.marcoaiot.com/admin/marco.pms.web into Organization_Management 2025-09-23 17:49:01 +05:30
d975664023 integrated service group wise activity operation delete,edit and create 2025-09-23 17:48:56 +05:30
d87dae4799 In ExpensePanel add new Toggle button. 2025-09-23 16:35:48 +05:30
4683eff749 In the ProjectList view, when we select 'View Details', the project is automatically set to 'marcosecure' instead of the selected project. 2025-09-23 11:06:16 +05:30
772a3e2829 Merge branch 'Organization_Management' of https://git.marcoaiot.com/admin/marco.pms.web into Organization_Management 2025-09-22 18:44:13 +05:30
ca7b0cda13 initially setup of service management 2025-09-22 18:44:05 +05:30
a380a7ab29 Changes in Choose Organization1 popup increase the space. 2025-09-22 16:25:02 +05:30
c609387924 Changes in Organization popup 2025-09-22 16:00:24 +05:30
69cc3b9383 Merge branch 'Organization_Management' of https://git.marcoaiot.com/admin/marco.pms.web into Organization_Management 2025-09-22 13:13:22 +05:30
6ebbc853bc optimized logout fun and intergrated inside Tenant selection page 2025-09-22 13:13:13 +05:30
71dd35adc2 Changes in Services Dropdown. 2025-09-22 12:40:00 +05:30
533b40d1bf Merge branch 'Organization_Management' of https://git.marcoaiot.com/admin/marco.pms.web into Organization_Management 2025-09-22 10:31:06 +05:30
42a80bbd68 removed unused code 2025-09-22 10:30:57 +05:30
397020ccb4 Merge pull request 'Creating a Services in Master Compoenents.' (#408) from Master_Services into Organization_Management
Reviewed-on: #408
2025-09-22 05:00:06 +00:00
71932ea6dc Merge pull request 'Integrate the API for Work Area and fetch data filtered by serviceId.' (#419) from Kartik_Task_Infra#1265 into Organization_Management
Reviewed-on: #419
2025-09-22 04:07:58 +00:00
693cabf63d Merge pull request 'Calling api for Attendance component for Organization.' (#420) from Kartik_Task_Att#1236 into Organization_Management
Reviewed-on: #420
2025-09-22 04:07:09 +00:00
4afe43d116 handle one tenant have , directly move to dashboard once logged 2025-09-22 00:09:34 +05:30
b9b3788dda configured tenant level login 2025-09-21 18:59:34 +05:30
5e1ccc9b05 Calling api for Attendance component for Organization. 2025-09-21 18:17:47 +05:30
aee510f527 revert pramod changed - (tenant login mistake 2025-09-21 16:58:29 +05:30
7e6020e3db Merge branch 'organization_management' of https://git.marcoaiot.com/admin/marco.pms.web into Organization_Management 2025-09-21 16:42:26 +05:30
cf78d17cf5 Merge pull request 'Add an Organization column in the Attendance grid across all tabs.' (#414) from Kartik_Task_OrgCol_att#1238 into Organization_Management
Reviewed-on: #414
2025-09-21 11:11:21 +00:00
72dbdb0fe0 Changes in Attendance 2025-09-21 16:40:23 +05:30
afcd1934f9 Integrate the API for Work Area and fetch data filtered by serviceId. 2025-09-21 15:10:31 +05:30
Pramod
3e5afe0bc6 revertlogin initial steup in organization_management brnach 2025-09-21 10:13:12 +05:30
Pramod
070fa93fca initially setup login 2025-09-21 10:06:38 +05:30
53a9cbc30b Merge pull request 'Changes in the Teams Services dropdown will reflect the Services data.' (#418) from Kartik_Task_TeamGrid#1259 into Organization_Management
Reviewed-on: #418
2025-09-20 15:01:16 +00:00
70acf57266 Remove message from Services dropdwon. 2025-09-20 20:05:25 +05:30
b2d7349fc9 Adding search funcionality in Teams for Organization and Services. 2025-09-20 19:37:47 +05:30
a0f7e5c57b Merge branch 'Organization_Management' of https://git.marcoaiot.com/admin/marco.pms.web into Kartik_Task_TeamGrid#1259 2025-09-20 19:32:41 +05:30
fa7dc2860c Changes in the Teams Services dropdown will reflect the Services data. 2025-09-20 19:30:47 +05:30
a1a935b0d5 Merge pull request 'changed Uii for org. or project assigned' (#417) from organization_level_login into Organization_Management
Reviewed-on: #417
2025-09-20 13:27:30 +00:00
Pramod
e610cc08c1 changed Uii for org. or project assigned 2025-09-20 18:47:09 +05:30
9711144236 Adding a new Grid in Teams Grid. 2025-09-20 15:40:17 +05:30
1a88f5fec5 chenages msg 2025-09-20 14:02:57 +05:30
ce73cfad21 Merge branch 'Organization_Management' of https://git.marcoaiot.com/admin/marco.pms.web into Organization_Management 2025-09-20 13:59:24 +05:30
7dafd4a45f fetched assigned organozation to project 2025-09-20 13:59:20 +05:30
2e3e3aa6ca Merge pull request 'Adding Filter Icon in Attendance tab and add functionality in all Attendance component.' (#416) from Kartik_Task_AttFilter#1235 into Organization_Management
Reviewed-on: #416
2025-09-20 06:45:11 +00:00
158c934a9f Merge branch 'Organization_Management' of https://git.marcoaiot.com/admin/marco.pms.web into Organization_Management 2025-09-20 12:14:40 +05:30
f6d864d42e assign to project and Tenant flow is integrated with api 2025-09-20 12:14:34 +05:30
90b96864be Changes in Attendance filter 2025-09-20 12:13:49 +05:30
5a048f7066 Merge pull request 'Adding Dropdown and API call in Daily Progress Report.' (#413) from Kartik_Task_DailyProgress#1232 into Organization_Management
Reviewed-on: #413
2025-09-20 06:22:43 +00:00
731d2dbed7 Changes in Infrastructure. 2025-09-20 11:51:32 +05:30
25de45b31b Merge pull request 'Adding Dropdown in Daily Task Planning and call api.' (#412) from Kartik_Task_DailyTask#1231 into Organization_Management
Reviewed-on: #412
2025-09-20 06:09:58 +00:00
58b5da1793 Changes in Daily Task planning 2025-09-20 11:39:14 +05:30
0746e5c349 Merge pull request 'Adding Dropdown in Create Task Popup.' (#411) from Kartik_Task#1230 into Organization_Management
Reviewed-on: #411
2025-09-20 06:03:55 +00:00
83143dff0a Merge pull request 'Adding Services Dropdown in Infrastructure.' (#410) from Kartik_Task#1229 into Organization_Management
Reviewed-on: #410
2025-09-20 06:00:05 +00:00
994f22e8c0 Changes in Infrastructure service dropdown. 2025-09-20 11:29:29 +05:30
84a5be52f8 Merge pull request 'Adding Dropdown and Organization Column in Teams' (#409) from Kartik_Task#1227 into Organization_Management
Reviewed-on: #409
2025-09-20 05:52:30 +00:00
b39df5f665 Changes in Teams dropdown. 2025-09-20 11:21:56 +05:30
9bdcc74486 Adding Filter Icon in Attendance tab and add functionality in all Attendance component. 2025-09-20 10:28:18 +05:30
005fdb3490 added assigned org to project 2025-09-19 23:46:06 +05:30
9223f7a176 Adding Card in Daily Progress Report. 2025-09-19 20:03:36 +05:30
27b62c858d Change the position of Datepicker and Dropdown box. 2025-09-19 19:50:50 +05:30
00d6774e06 Change the position of Services in Create Task popup in Infrastructure. 2025-09-19 19:26:46 +05:30
1ef82ad0b2 Only 1 project or no project is assigned then dropdown will be hide. 2025-09-19 19:20:01 +05:30
1da587d010 Adding condition if single or no project assigned then dropdown is not shown 2025-09-19 19:13:02 +05:30
164b82e1c7 Add an Organization column in the Attendance grid across all tabs. 2025-09-19 17:00:02 +05:30
e9d8b6daea Adding Dropdown and API call in Daily Progress Report. 2025-09-19 16:43:44 +05:30
9b37288901 Adding Dropdown in Daily Task Planning and call api. 2025-09-19 16:32:34 +05:30
54e609883d Adding Dropdown in Create Task Popup. 2025-09-19 16:13:09 +05:30
7d17422681 Calling Api for Services dropdown. 2025-09-19 16:02:36 +05:30
d67121c150 Merge branch 'Organization_Management' of https://git.marcoaiot.com/admin/marco.pms.web into Kartik_Task#1227 2025-09-19 15:55:25 +05:30
e154bac64a Calling api for services dropdwon. 2025-09-19 15:54:29 +05:30
ea350db98b Merge branch 'Organization_Management' of https://git.marcoaiot.com/admin/marco.pms.web into Kartik_Task#1229 2025-09-19 15:27:16 +05:30
133024bc5c setup assigned org to project and integrated projectservice 2025-09-19 15:25:58 +05:30
ea219b7176 Adding Services Dropdown in Infrastructure. 2025-09-19 15:03:25 +05:30
af5519fd60 Adding Dropdown and Organization Column in Teams 2025-09-19 14:53:40 +05:30
08194dd8ef Creating a Services in Master Compoenents. 2025-09-19 14:18:56 +05:30
1452e77bc5 initially setup service provider form 2025-09-18 19:23:24 +05:30
7fa2ca9227 Merge branch 'Organization_Management' of https://git.marcoaiot.com/admin/marco.pms.web into Organization_Management 2025-09-18 15:25:02 +05:30
0d9ef7f248 integrated fetch organization list api and display with search filter 2025-09-18 15:23:48 +05:30
9a3488c92b convert inline style css class into class name 2025-09-18 15:23:48 +05:30
1b144aab8a organization created successfully 2025-09-18 15:23:48 +05:30
2e65007f26 fixed Modal provider component and setup organization creation 2025-09-18 15:23:48 +05:30
3c4c25b449 refactore SelectMult Tag for label display is required or optional 2025-09-18 15:23:23 +05:30
979293ad90 setup organization modal, it seprated form another becuase this modal can open anywhere at one hook 2025-09-18 15:21:23 +05:30
99eaf92e3f initially setup 2025-09-18 15:18:51 +05:30
28b0541894 organization created successfully 2025-09-18 15:18:04 +05:30
a48fc1d989 fixed Modal provider component and setup organization creation 2025-09-18 15:18:04 +05:30
78a0ecebf1 resolved conflict during came rebase 2025-09-18 15:18:04 +05:30
8eb8e27f89 refactore SelectMult Tag for label display is required or optional 2025-09-18 15:18:04 +05:30
012a89b3ea setup organization modal, it seprated form another becuase this modal can open anywhere at one hook 2025-09-18 15:18:04 +05:30
18698a67e3 removed unused files 2025-09-18 15:18:04 +05:30
701d1adc0b initially setup 2025-09-18 15:18:03 +05:30
b2c68824dd fixed refresh existen permission 2025-09-18 12:57:37 +05:30
b3b7297bc3 Merge branch 'main' of https://git.marcoaiot.com/admin/marco.pms.web 2025-09-18 12:40:06 +05:30
9311f41f56 fixed high level changes 2025-09-18 12:38:39 +05:30
9ba2ecfb1f Merge pull request 'Intefrating_API_Dashboard Changes in UI of Dashboard and Login form password field.' (#406) from Intefrating_API_Dashboard into main
Reviewed-on: #406
2025-09-18 07:05:38 +00:00
a27b8571b5 cosmatic changes 2025-09-18 12:35:04 +05:30
fd36298543 Changes in UI of Landing Page subscription and add skeleton 2025-09-18 12:07:46 +05:30
09bb58e50e fixed project level permission bug 2025-09-18 11:30:38 +05:30
01a5766074 organization created successfully 2025-09-18 10:01:39 +05:30
0348f6da8e fixed Modal provider component and setup organization creation 2025-09-17 19:45:42 +05:30
1c0e8655c4 Changes in Login-form hide/unhide button add secondary. 2025-09-17 17:09:56 +05:30
6211f52e3a Merge branch 'main' of https://git.marcoaiot.com/admin/marco.pms.web into Intefrating_API_Dashboard 2025-09-17 16:51:56 +05:30
df9107f0d8 Changes in UI of Subscription-plan. 2025-09-17 16:49:52 +05:30
eea7252b96 Changes in UI of Dashboard getplans. 2025-09-17 16:10:49 +05:30
442ecff926 Merge pull request 'Refactor_Directory And Project Level Permsssion' (#404) from Refactor_Directory into main
Reviewed-on: #404
Merged
2025-09-17 10:24:45 +00:00
0abd77dab7 Merge pull request 'Calling API for Dashboard paln show.' (#405) from Intefrating_API_Dashboard into Refactor_Directory
Reviewed-on: #405
Merged
2025-09-17 10:23:13 +00:00
4b0ea3a0db Merge branch 'Refactor_Directory' of https://git.marcoaiot.com/admin/marco.pms.web into Refactor_Directory 2025-09-17 15:50:49 +05:30
d3218eb77a handle project set null at employee details page 2025-09-17 15:50:44 +05:30
4ad87af7f4 Add FAQ Answers on landing page 2025-09-17 15:22:25 +05:30
7b15309dbf Calling API for Dashboard paln show. 2025-09-17 14:38:57 +05:30
834ce62e67 Merge branch 'Refactor_Directory' of https://git.marcoaiot.com/admin/marco.pms.web into Organization_Management 2025-09-17 14:26:35 +05:30
889b477dd0 removed subMenu of Project setting 2025-09-17 14:24:45 +05:30
19f8189fc3 resolved conflict during came rebase 2025-09-17 14:19:02 +05:30
970c195ca5 refactore SelectMult Tag for label display is required or optional 2025-09-17 14:15:29 +05:30
d944d3a389 setup organization modal, it seprated form another becuase this modal can open anywhere at one hook 2025-09-17 14:15:29 +05:30
e0c7eee1fd removed unused files 2025-09-17 14:14:56 +05:30
6baa2896c2 initially setup 2025-09-17 14:14:56 +05:30
a835e75f66 fixed view project 2025-09-17 13:15:00 +05:30
cc7ef47055 fixed validation msg set bottom of select box 2025-09-17 12:54:40 +05:30
95fbac4760 fixed prevent removed existen permission whenever add new one 2025-09-17 12:41:00 +05:30
a86c815ca2 Asthetic Changes
- Margin on all sides for grid
- Margin for card content
2025-09-16 18:33:48 +05:30
daa1a29e8a added option chain for handle error 2025-09-16 18:11:47 +05:30
794429821b Merge branch 'Refactor_Directory' of https://git.marcoaiot.com/admin/marco.pms.web into Refactor_Directory 2025-09-16 17:57:11 +05:30
7d94c17c71 modified ui of header directory 2025-09-16 17:57:06 +05:30
fb6a8255c9 Changes in DocumentManager popup change the sequence. 2025-09-16 17:08:36 +05:30
7ef10e3e5b Merge pull request 'Handled Global and Project level permissions' (#403) from hotchanges_projectPermission into Refactor_Directory
Reviewed-on: #403
Merged
2025-09-16 10:43:30 +00:00
0a489be675 added proper global and project level permission 2025-09-16 16:08:47 +05:30
c014d6c929 handle delete bucket 2025-09-16 12:07:42 +05:30
2c378745fa changed label class of directory filter 2025-09-16 10:59:25 +05:30
080d2307ca removed unused code 2025-09-16 10:49:44 +05:30
d2288ea967 Merge pull request 'Issues_Sep_1W_V2' (#402) from Issues_Sep_1W_V2 into Refactor_Directory
Reviewed-on: #402
Merged
2025-09-16 05:11:27 +00:00
42086d7f3a Merge branch 'Refactor_Directory' of https://git.marcoaiot.com/admin/marco.pms.web into Issues_Sep_1W_V2 2025-09-16 10:40:27 +05:30
020020056e Changes in Activities DateRangePicket. 2025-09-15 18:19:11 +05:30
9b3ffdb33d changed input of search input text- search at Contact 2025-09-15 17:32:31 +05:30
d70c8e5995 Merge pull request 'Filter dropdown in Assign Task popup does not close on outside click' (#396) from Kartik_Bug#1123 into Issues_Sep_1W_V2
Reviewed-on: #396
Merged
2025-09-15 11:52:15 +00:00
e6e90c7d4e Filter dropdown in Assign Task popup does not close on outside click 2025-09-15 11:52:15 +00:00
19241cc556 Merge pull request 'Remove Extra Line Below "No Contact Found" Message in Directory' (#400) from Kartik_Bug#1115 into Refactor_Directory
Reviewed-on: #400
Merged
2025-09-15 11:51:46 +00:00
7c2744058d Remove Extra Line Below "No Contact Found" Message in Directory 2025-09-15 11:51:46 +00:00
a963176c95 Merge pull request 'Message should be inside card' (#399) from Kartik_Bug#1080 into Refactor_Directory
Reviewed-on: #399
Merged
2025-09-15 11:48:52 +00:00
6bc8c65c81 Message should be inside card 2025-09-15 11:48:52 +00:00
45bd5a7f66 Merge pull request 'Directory - Contacts Exported to Excel does not format properly' (#397) from Kartik_Bug#1064 into Refactor_Directory
Reviewed-on: #397
Merged
2025-09-15 11:47:56 +00:00
e179a267aa Directory - Contacts Exported to Excel does not format properly 2025-09-15 11:47:56 +00:00
9c51378963 Merge pull request 'Employee Profile Page Not Updating without refresh page ,After Edit' (#398) from Kartik_Bug#909 into Issues_Sep_1W_V2
Reviewed-on: #398
Merged
2025-09-15 11:37:17 +00:00
4927680fe3 fixed going wrong request for Expense payload - Transaction 2025-09-15 17:00:36 +05:30
9af7a5ceb2 Employee Profile Page Not Updating without refresh page ,After Edit 2025-09-15 15:19:40 +05:30
94eb283b2d added permission for managr task 2025-09-13 19:01:08 +05:30
0e3a634205 fixed permission add and remove updation 2025-09-13 18:16:56 +05:30
2ef56e7f83 added correct msg for contact person 2025-09-13 16:10:17 +05:30
d80fa27906 Merge pull request 'Remove Required Indicator from Tags Field in Document Upload' (#392) from Kartik_Bug#1104 into Issues_Sep_1W_V2
Reviewed-on: #392
Merged
2025-09-13 10:13:18 +00:00
70110192f4 Remove Required Indicator from Tags Field in Document Upload 2025-09-13 10:13:18 +00:00
548597ed4f Merge pull request 'Display Only "Re-activate" Button for Inactive Employees in Action Column' (#394) from Kartik_Bug#1113 into Issues_Sep_1W_V2
Reviewed-on: #394
Merged
2025-09-13 10:11:25 +00:00
75ca3d1504 Display Only "Re-activate" Button for Inactive Employees in Action Column 2025-09-13 10:11:25 +00:00
f20ff7eb73 Merge pull request 'Created bucket card is visible in middle of Manage Buckets popup & after refresh page it displayed at left corner.' (#393) from Kartik_Bug#976 into Issues_Sep_1W_V2
Reviewed-on: #393
Merged
2025-09-13 10:10:32 +00:00
ce59869827 Created bucket card is visible in middle of Manage Buckets popup & after refresh page it displayed at left corner. 2025-09-13 10:10:32 +00:00
d77e3c5f03 Merge pull request 'Standardize All Buttons in Expenses Module to Match Website-Wide Button Style' (#391) from Kartik_Bug#830 into Issues_Sep_1W_V2
Reviewed-on: #391
Merged
2025-09-13 10:06:14 +00:00
2f2ddb0576 Standardize All Buttons in Expenses Module to Match Website-Wide Button Style 2025-09-13 10:06:14 +00:00
2e14fc862d Merge pull request 'Forgot Password – Reset link not sent, error shown for all valid emails' (#389) from Kartik_Bug#962 into Issues_Sep_1W_V2
Reviewed-on: #389
Merged
2025-09-13 10:05:34 +00:00
84500f6913 Forgot Password – Reset link not sent, error shown for all valid emails 2025-09-13 10:05:34 +00:00
2790f50275 Merge pull request 'Password Field UI Not Proper – White Background Not Covering Full Field' (#386) from Kartik_Bug#1015 into Issues_Sep_1W_V2
Reviewed-on: #386
Merged
2025-09-13 10:04:39 +00:00
8c48b83581 Password Field UI Not Proper – White Background Not Covering Full Field 2025-09-13 10:04:39 +00:00
f992dbeaf1 Merge pull request 'Username and Password Fields Should Auto-Trim Spaces on Login' (#385) from Kartik_Bug#1016 into Issues_Sep_1W_V2
Reviewed-on: #385
Merged
2025-09-13 10:03:31 +00:00
d1098e64a7 Username and Password Fields Should Auto-Trim Spaces on Login 2025-09-13 10:03:31 +00:00
212e969258 Merge pull request 'New Password Field on Reset Password Page Should Be Empty' (#384) from Kartik_Bug#1071 into Issues_Sep_1W_V2
Reviewed-on: #384
Merged
2025-09-13 10:02:45 +00:00
9de1613bd9 New Password Field on Reset Password Page Should Be Empty 2025-09-13 10:02:45 +00:00
9676d45710 When the clear button hit api for full response. 2025-09-12 17:16:34 +05:30
7e5b6952f5 Apply functionality for Both Notes and Contact filter 2025-09-12 17:09:48 +05:30
0484de498c Changes in UI of Directory Notes Tabs and all the popups. 2025-09-12 16:11:31 +05:30
a7ccaa2812 changed position of text- Active and In-Active 2025-09-12 13:08:07 +05:30
5fb7e89cb2 changed sequence of contact form buttons 2025-09-12 12:58:01 +05:30
2027bd3d17 all images path changed 2025-09-12 12:37:31 +05:30
ee9698f665 changed images path for landing page 2025-09-12 12:29:54 +05:30
25ec5a354c changed path of logo 2025-09-12 12:21:40 +05:30
8335c42935 added direct bucket permission 2025-09-12 12:04:24 +05:30
3963002a2d Merge branch 'landing_page_1' into Refactor_Directory
# Conflicts:
#	src/pages/Home/LandingPage.css
2025-09-11 18:14:31 +05:30
717264c9f9 fixed update and creation for contacts 2025-09-11 17:34:56 +05:30
1a04dd51fc Merge branch 'main' of https://git.marcoaiot.com/admin/marco.pms.web into Refactor_Directory 2025-09-11 16:25:49 +05:30
c83de16466 added missed outed confirm modal inside employee for suspend emp 2025-09-11 11:09:46 +05:30
b51b3db9ec added missing assset 2025-09-11 10:51:46 +05:30
79553cab4e Merge pull request 'Added new page - Landing Page' (#390) from landing_page_1 into Refactor_Directory
Reviewed-on: #390
Merged
2025-09-11 05:01:19 +00:00
7a74084841 rearrange images in folder 2025-09-11 05:01:19 +00:00
7638f8bdaa Add blog content 2025-09-11 05:01:19 +00:00
78f304a490 add blog container 2025-09-11 05:01:19 +00:00
0b38436dfe handle animations 2025-09-11 05:01:19 +00:00
15581f3f26 landing page modifications 2025-09-11 05:01:19 +00:00
8dc064340b removed unused component 2025-09-10 21:01:28 +05:30
285b853026 removed alreday declare component 2025-09-10 20:39:48 +05:30
81e5456e1d Merge branch 'main' of https://git.marcoaiot.com/admin/marco.pms.web into Refactor_Directory 2025-09-10 20:36:54 +05:30
4741025dcb set Directory page for project 2025-09-10 20:35:48 +05:30
aec6bd64ff Merge pull request 'Added Document Managment feature' (#388) from Document_Manag into main
Reviewed-on: #388
Merged
2025-09-10 14:34:35 +00:00
d6de8cbfc1 completely refactored directory module 2025-09-10 18:13:36 +05:30
44cc6c6e81 rearrange images in folder 2025-09-10 13:44:50 +05:30
6604a4db13 Add blog content 2025-09-10 12:49:30 +05:30
b66106c301 Merge branch 'Document_Manag' of https://git.marcoaiot.com/admin/marco.pms.web into Refactor_Directory 2025-09-10 12:46:38 +05:30
47d0e332f1 add blog container 2025-09-10 10:35:36 +05:30
e454917763 refactored notes list 2025-09-10 02:41:59 +05:30
b113675cc2 contact Active and InActive refactored 2025-09-10 00:07:34 +05:30
025b13ea64 reafctor contact list with filter 2025-09-09 20:04:48 +05:30
2642532b60 handle animations 2025-09-09 15:02:59 +05:30
b91487712d initially contactfilter added 2025-09-09 12:09:03 +05:30
6ca6ec31f7 landing page modifications 2025-09-09 12:06:10 +05:30
1abed1de3a managebucket component is split 2025-09-09 01:49:31 +05:30
957f790fce initially created ManageBucket 2025-09-08 11:40:03 +05:30
6a472b39ef initialli refactoring directory 2025-09-08 10:14:18 +05:30
287 changed files with 14253 additions and 10764 deletions

119
package-lock.json generated
View File

@ -809,9 +809,9 @@
} }
}, },
"node_modules/@jridgewell/source-map": { "node_modules/@jridgewell/source-map": {
"version": "0.3.6", "version": "0.3.11",
"resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz",
"integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==",
"license": "MIT", "license": "MIT",
"peer": true, "peer": true,
"dependencies": { "dependencies": {
@ -1552,13 +1552,13 @@
"peer": true "peer": true
}, },
"node_modules/@types/node": { "node_modules/@types/node": {
"version": "22.13.13", "version": "24.5.2",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.13.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-24.5.2.tgz",
"integrity": "sha512-ClsL5nMwKaBRwPcCvH8E7+nU4GxHVx1axNvMZTFHMEfNI7oahimt26P5zjVCRrjiIWj6YFXfE1v3dEp94wLcGQ==", "integrity": "sha512-FYxk1I7wPv3K2XBaoyH2cTnocQEu8AOZ60hPbsyukMPLv5/5qr7V1i8PLHdl6Zf87I+xZXFvPCXYjiTFq+YSDQ==",
"license": "MIT", "license": "MIT",
"peer": true, "peer": true,
"dependencies": { "dependencies": {
"undici-types": "~6.20.0" "undici-types": "~7.12.0"
} }
}, },
"node_modules/@types/prop-types": { "node_modules/@types/prop-types": {
@ -1835,9 +1835,10 @@
} }
}, },
"node_modules/acorn": { "node_modules/acorn": {
"version": "8.14.0", "version": "8.15.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
"integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"license": "MIT",
"bin": { "bin": {
"acorn": "bin/acorn" "acorn": "bin/acorn"
}, },
@ -1845,6 +1846,19 @@
"node": ">=0.4.0" "node": ">=0.4.0"
} }
}, },
"node_modules/acorn-import-phases": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/acorn-import-phases/-/acorn-import-phases-1.0.4.tgz",
"integrity": "sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=10.13.0"
},
"peerDependencies": {
"acorn": "^8.14.0"
}
},
"node_modules/acorn-jsx": { "node_modules/acorn-jsx": {
"version": "5.3.2", "version": "5.3.2",
"resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
@ -2625,9 +2639,9 @@
"integrity": "sha512-ZpSAUOZ2Izby7qnZluSrAlGgGQzucmFbN0n64dYzocYxnxV5ufurpj3VgEe4cUp7ir9LmeLxNYo8bVnlM8bQHw==" "integrity": "sha512-ZpSAUOZ2Izby7qnZluSrAlGgGQzucmFbN0n64dYzocYxnxV5ufurpj3VgEe4cUp7ir9LmeLxNYo8bVnlM8bQHw=="
}, },
"node_modules/enhanced-resolve": { "node_modules/enhanced-resolve": {
"version": "5.18.1", "version": "5.18.3",
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.1.tgz", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.3.tgz",
"integrity": "sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==", "integrity": "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==",
"license": "MIT", "license": "MIT",
"peer": true, "peer": true,
"dependencies": { "dependencies": {
@ -2741,9 +2755,9 @@
} }
}, },
"node_modules/es-module-lexer": { "node_modules/es-module-lexer": {
"version": "1.6.0", "version": "1.7.0",
"resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.6.0.tgz", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz",
"integrity": "sha512-qqnD1yMU6tk/jnaMosogGySTZP8YtUgAffA9nMN+E/rjxcfRQ6IEk7IiozUjgxKoFHBGjTLnrHB/YC45r/59EQ==", "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==",
"license": "MIT", "license": "MIT",
"peer": true "peer": true
}, },
@ -3138,9 +3152,9 @@
"dev": true "dev": true
}, },
"node_modules/fast-uri": { "node_modules/fast-uri": {
"version": "3.0.6", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.6.tgz", "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz",
"integrity": "sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==", "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==",
"funding": [ "funding": [
{ {
"type": "github", "type": "github",
@ -5163,9 +5177,9 @@
} }
}, },
"node_modules/schema-utils": { "node_modules/schema-utils": {
"version": "4.3.0", "version": "4.3.2",
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.0.tgz", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.2.tgz",
"integrity": "sha512-Gf9qqc58SpCA/xdziiHz35F4GNIWYWZrEshUc/G/r5BnLph6xpKuLeoJoQuj5WfBIx/eQLf+hmVPYHaxJu7V2g==", "integrity": "sha512-Gn/JaSk/Mt9gYubxTtSn/QCV4em9mpAPiR1rqy/Ocu19u/G9J5WWdNoUT4SiV6mFC3y6cxyFcFwdzPM3FgxGAQ==",
"license": "MIT", "license": "MIT",
"peer": true, "peer": true,
"dependencies": { "dependencies": {
@ -5567,24 +5581,28 @@
} }
}, },
"node_modules/tapable": { "node_modules/tapable": {
"version": "2.2.1", "version": "2.2.3",
"resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.3.tgz",
"integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", "integrity": "sha512-ZL6DDuAlRlLGghwcfmSn9sK3Hr6ArtyudlSAiCqQ6IfE+b+HHbydbYDIG15IfS5do+7XQQBdBiubF/cV2dnDzg==",
"license": "MIT", "license": "MIT",
"peer": true, "peer": true,
"engines": { "engines": {
"node": ">=6" "node": ">=6"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/webpack"
} }
}, },
"node_modules/terser": { "node_modules/terser": {
"version": "5.39.0", "version": "5.44.0",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.39.0.tgz", "resolved": "https://registry.npmjs.org/terser/-/terser-5.44.0.tgz",
"integrity": "sha512-LBAhFyLho16harJoWMg/nZsQYgTrg5jXOn2nCYjRUcZZEdE3qa2zb8QEDRUGVZBW4rlazf2fxkg8tztybTaqWw==", "integrity": "sha512-nIVck8DK+GM/0Frwd+nIhZ84pR/BX7rmXMfYwyg+Sri5oGVE99/E3KvXqpC2xHFxyqXyGHTKBSioxxplrO4I4w==",
"license": "BSD-2-Clause", "license": "BSD-2-Clause",
"peer": true, "peer": true,
"dependencies": { "dependencies": {
"@jridgewell/source-map": "^0.3.3", "@jridgewell/source-map": "^0.3.3",
"acorn": "^8.8.2", "acorn": "^8.15.0",
"commander": "^2.20.0", "commander": "^2.20.0",
"source-map-support": "~0.5.20" "source-map-support": "~0.5.20"
}, },
@ -5777,9 +5795,9 @@
} }
}, },
"node_modules/undici-types": { "node_modules/undici-types": {
"version": "6.20.0", "version": "7.12.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.12.0.tgz",
"integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", "integrity": "sha512-goOacqME2GYyOZZfb5Lgtu+1IDmAlAEu5xnD3+xTzS10hT0vzpf0SPjkXwAw9Jm+4n/mQGDP3LO8CPbYROeBfQ==",
"license": "MIT", "license": "MIT",
"peer": true "peer": true
}, },
@ -5907,9 +5925,9 @@
} }
}, },
"node_modules/watchpack": { "node_modules/watchpack": {
"version": "2.4.2", "version": "2.4.4",
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.2.tgz", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.4.tgz",
"integrity": "sha512-TnbFSbcOCcDgjZ4piURLCbJ3nJhznVh9kw6F6iokjiFPl8ONxe9A6nMDVXDiNbrSfLILs6vB07F7wLBrwPYzJw==", "integrity": "sha512-c5EGNOiyxxV5qmTtAB7rbiXxi1ooX1pQKMLX/MIabJjRA0SJBQOjKF+KSVfHkr9U1cADPon0mRiVe/riyaiDUA==",
"license": "MIT", "license": "MIT",
"peer": true, "peer": true,
"dependencies": { "dependencies": {
@ -5927,21 +5945,23 @@
"license": "BSD-2-Clause" "license": "BSD-2-Clause"
}, },
"node_modules/webpack": { "node_modules/webpack": {
"version": "5.98.0", "version": "5.101.3",
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.98.0.tgz", "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.101.3.tgz",
"integrity": "sha512-UFynvx+gM44Gv9qFgj0acCQK2VE1CtdfwFdimkapco3hlPCJ/zeq73n2yVKimVbtm+TnApIugGhLJnkU6gjYXA==", "integrity": "sha512-7b0dTKR3Ed//AD/6kkx/o7duS8H3f1a4w3BYpIriX4BzIhjkn4teo05cptsxvLesHFKK5KObnadmCHBwGc+51A==",
"license": "MIT", "license": "MIT",
"peer": true, "peer": true,
"dependencies": { "dependencies": {
"@types/eslint-scope": "^3.7.7", "@types/eslint-scope": "^3.7.7",
"@types/estree": "^1.0.6", "@types/estree": "^1.0.8",
"@types/json-schema": "^7.0.15",
"@webassemblyjs/ast": "^1.14.1", "@webassemblyjs/ast": "^1.14.1",
"@webassemblyjs/wasm-edit": "^1.14.1", "@webassemblyjs/wasm-edit": "^1.14.1",
"@webassemblyjs/wasm-parser": "^1.14.1", "@webassemblyjs/wasm-parser": "^1.14.1",
"acorn": "^8.14.0", "acorn": "^8.15.0",
"acorn-import-phases": "^1.0.3",
"browserslist": "^4.24.0", "browserslist": "^4.24.0",
"chrome-trace-event": "^1.0.2", "chrome-trace-event": "^1.0.2",
"enhanced-resolve": "^5.17.1", "enhanced-resolve": "^5.17.3",
"es-module-lexer": "^1.2.1", "es-module-lexer": "^1.2.1",
"eslint-scope": "5.1.1", "eslint-scope": "5.1.1",
"events": "^3.2.0", "events": "^3.2.0",
@ -5951,11 +5971,11 @@
"loader-runner": "^4.2.0", "loader-runner": "^4.2.0",
"mime-types": "^2.1.27", "mime-types": "^2.1.27",
"neo-async": "^2.6.2", "neo-async": "^2.6.2",
"schema-utils": "^4.3.0", "schema-utils": "^4.3.2",
"tapable": "^2.1.1", "tapable": "^2.1.1",
"terser-webpack-plugin": "^5.3.11", "terser-webpack-plugin": "^5.3.11",
"watchpack": "^2.4.1", "watchpack": "^2.4.1",
"webpack-sources": "^3.2.3" "webpack-sources": "^3.3.3"
}, },
"bin": { "bin": {
"webpack": "bin/webpack.js" "webpack": "bin/webpack.js"
@ -5974,15 +5994,22 @@
} }
}, },
"node_modules/webpack-sources": { "node_modules/webpack-sources": {
"version": "3.2.3", "version": "3.3.3",
"resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.3.3.tgz",
"integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", "integrity": "sha512-yd1RBzSGanHkitROoPFd6qsrxt+oFhg/129YzheDGqeustzX0vTZJZsSsQjVQC4yzBQ56K55XU8gaNCtIzOnTg==",
"license": "MIT", "license": "MIT",
"peer": true, "peer": true,
"engines": { "engines": {
"node": ">=10.13.0" "node": ">=10.13.0"
} }
}, },
"node_modules/webpack/node_modules/@types/estree": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
"integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
"license": "MIT",
"peer": true
},
"node_modules/webpack/node_modules/eslint-scope": { "node_modules/webpack/node_modules/eslint-scope": {
"version": "5.1.1", "version": "5.1.1",
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz",

View File

@ -1,8 +1,282 @@
:root, :root,
[data-bs-theme="light"] { [data-bs-theme="light"] {
--bs-nav-link-font-size: 0.7375rem; --bs-nav-link-font-size: 0.7375rem;
--bg-border-color :#f8f6f6
} }
.card-header { .card-header {
padding: 0.5rem var(--bs-card-cap-padding-x); padding: 0.5rem var(--bs-card-cap-padding-x);
} }
.table_header_border {
border-bottom:2px solid var(--bs-table-border-color) ;
}
.text-xxs { font-size: 0.55rem; } /* 8px */
.text-xs { font-size: 0.75rem; } /* 12px */
.text-sm { font-size: 0.875rem; } /* 14px */
.text-base { font-size: 1rem; } /* 16px */
.text-lg { font-size: 1.125rem; } /* 18px */
.text-xl { font-size: 1.25rem; } /* 20px */
.text-2xl { font-size: 1.5rem; } /* 24px */
.text-3xl { font-size: 1.875rem; } /* 30px */
.text-4xl { font-size: 2.25rem; } /* 36px */
.text-5xl { font-size: 3rem; } /* 48px */
.text-6xl { font-size: 3.75rem; } /* 60px */
.text-7xl { font-size: 4.5rem; } /* 72px */
.text-8xl { font-size: 6rem; } /* 96px */
.text-9xl { font-size: 8rem; } /* 128px */
/* */
.w-0 { width: 0px; }
.w-px { width: 1px; }
.w-1 { width: 0.25rem; } /* 4px */
.w-2 { width: 0.5rem; } /* 8px */
.w-3 { width: 0.75rem; } /* 12px */
.w-4 { width: 1rem; } /* 16px */
.w-5 { width: 1.25rem; } /* 20px */
.w-6 { width: 1.5rem; } /* 24px */
.w-8 { width: 2rem; } /* 32px */
.w-10 { width: 2.5rem; } /* 40px */
.w-12 { width: 3rem; } /* 48px */
.w-16 { width: 4rem; } /* 64px */
.w-20 { width: 5rem; } /* 80px */
.w-24 { width: 6rem; } /* 96px */
.w-32 { width: 8rem; } /* 128px */
.w-40 { width: 10rem; } /* 160px */
.w-48 { width: 12rem; } /* 192px */
.w-56 { width: 14rem; } /* 224px */
.w-64 { width: 16rem; } /* 256px */
.w-auto { width: auto; }
.w-full { width: 100%; }
.w-screen{ width: 100vw; }
.w-min { width: min-content; }
.w-max { width: max-content; }
.h-0 { height: 0px; }
.h-px { height: 1px; }
.h-1 { height: 0.25rem; } /* 4px */
.h-2 { height: 0.5rem; } /* 8px */
.h-3 { height: 0.75rem; } /* 12px */
.h-4 { height: 1rem; } /* 16px */
.h-5 { height: 1.25rem; } /* 20px */
.h-6 { height: 1.5rem; } /* 24px */
.h-8 { height: 2rem; } /* 32px */
.h-10 { height: 2.5rem; } /* 40px */
.h-12 { height: 3rem; } /* 48px */
.h-16 { height: 4rem; } /* 64px */
.h-20 { height: 5rem; } /* 80px */
.h-24 { height: 6rem; } /* 96px */
.h-32 { height: 8rem; } /* 128px */
.h-40 { height: 10rem; } /* 160px */
.h-48 { height: 12rem; } /* 192px */
.h-56 { height: 14rem; } /* 224px */
.h-64 { height: 16rem; } /* 256px */
.h-auto { height: auto; }
.h-full { height: 100%; }
.h-screen{ height: 100vh; }
.h-min { height: min-content; }
.h-max { height: max-content; }
/* ==========================
Base Font Sizes (mobile first)
========================== */
.text-xxs { font-size: 0.55rem; } /* 8px */
.text-xs { font-size: 0.75rem; } /* 12px */
.text-sm { font-size: 0.875rem; } /* 14px */
.text-base{ font-size: 1rem; } /* 16px */
.text-lg { font-size: 1.125rem; } /* 18px */
.text-xl { font-size: 1.25rem; } /* 20px */
.text-2xl { font-size: 1.5rem; } /* 24px */
.text-3xl { font-size: 1.875rem; } /* 30px */
.text-4xl { font-size: 2.25rem; } /* 36px */
.text-5xl { font-size: 3rem; } /* 48px */
.text-6xl { font-size: 3.75rem; } /* 60px */
.text-7xl { font-size: 4.5rem; } /* 72px */
.text-8xl { font-size: 6rem; } /* 96px */
.text-9xl { font-size: 8rem; } /* 128px */
/* ==========================
Base Heights
========================== */
.h-0 { height: 0; }
.h-px { height: 1px; }
.h-1 { height: 0.25rem; } /* 4px */
.h-2 { height: 0.5rem; } /* 8px */
.h-3 { height: 0.75rem; } /* 12px */
.h-4 { height: 1rem; } /* 16px */
.h-5 { height: 1.25rem; } /* 20px */
.h-6 { height: 1.5rem; } /* 24px */
.h-8 { height: 2rem; } /* 32px */
.h-10 { height: 2.5rem; } /* 40px */
.h-12 { height: 3rem; } /* 48px */
.h-16 { height: 4rem; } /* 64px */
.h-20 { height: 5rem; } /* 80px */
.h-24 { height: 6rem; } /* 96px */
.h-32 { height: 8rem; } /* 128px */
.h-40 { height: 10rem; } /* 160px */
.h-48 { height: 12rem; } /* 192px */
.h-56 { height: 14rem; } /* 224px */
.h-64 { height: 16rem; } /* 256px */
.h-full { height: 100%; }
.h-screen{ height: 100vh; }
/* ==========================
Base Widths
========================== */
.w-0 { width: 0; }
.w-px { width: 1px; }
.w-1 { width: 0.25rem; }
.w-2 { width: 0.5rem; }
.w-3 { width: 0.75rem; }
.w-4 { width: 1rem; }
.w-5 { width: 1.25rem; }
.w-6 { width: 1.5rem; }
.w-8 { width: 2rem; }
.w-10 { width: 2.5rem; }
.w-12 { width: 3rem; }
.w-16 { width: 4rem; }
.w-20 { width: 5rem; }
.w-24 { width: 6rem; }
.w-32 { width: 8rem; }
.w-40 { width: 10rem; }
.w-48 { width: 12rem; }
.w-56 { width: 14rem; }
.w-64 { width: 16rem; }
.w-full { width: 100%; }
.w-screen{ width: 100vw; }
/* ==========================
Responsive Variants
========================== */
@media (min-width: 576px) { /* sm */
/* Font */
.text-xxs-sm { font-size: 0.55rem; }
.text-xs-sm { font-size: 0.75rem; }
.text-sm-sm { font-size: 0.875rem; }
.text-base-sm{ font-size: 1rem; }
.text-lg-sm { font-size: 1.125rem; }
.text-xl-sm { font-size: 1.25rem; }
.text-2xl-sm{ font-size: 1.5rem; }
/* Height */
.h-1-sm{ height: 0.25rem; }
.h-2-sm{ height: 0.5rem; }
.h-3-sm{ height: 0.75rem; }
.h-4-sm{ height: 1rem; }
.h-5-sm{ height: 1.25rem; }
.h-6-sm{ height: 1.5rem; }
.h-8-sm{ height: 2rem; }
.h-10-sm{ height: 2.5rem; }
/* Width */
.w-1-sm{ width: 0.25rem; }
.w-2-sm{ width: 0.5rem; }
.w-3-sm{ width: 0.75rem; }
.w-4-sm{ width: 1rem; }
.w-5-sm{ width: 1.25rem; }
.w-6-sm{ width: 1.5rem; }
.w-8-sm{ width: 2rem; }
.w-10-sm{ width: 2.5rem; }
}
@media (min-width: 768px) { /* md */
/* Font */
.text-xxs-md { font-size: 0.55rem; }
.text-xs-md { font-size: 0.75rem; }
.text-sm-md { font-size: 0.875rem; }
.text-base-md{ font-size: 1rem; }
.text-lg-md { font-size: 1.125rem; }
.text-xl-md { font-size: 1.25rem; }
.text-2xl-md{ font-size: 1.5rem; }
/* Height */
.h-1-md{ height: 0.25rem; }
.h-2-md{ height: 0.5rem; }
.h-3-md{ height: 0.75rem; }
.h-4-md{ height: 1rem; }
.h-5-md{ height: 1.25rem; }
.h-6-md{ height: 1.5rem; }
.h-8-md{ height: 2rem; }
.h-10-md{ height: 2.5rem; }
/* Width */
.w-1-md{ width: 0.25rem; }
.w-2-md{ width: 0.5rem; }
.w-3-md{ width: 0.75rem; }
.w-4-md{ width: 1rem; }
.w-5-md{ width: 1.25rem; }
.w-6-md{ width: 1.5rem; }
.w-8-md{ width: 2rem; }
.w-10-md{ width: 2.5rem; }
}
@media (min-width: 992px) { /* lg */
/* Font */
.text-xxs-lg { font-size: 0.55rem; }
.text-xs-lg { font-size: 0.75rem; }
.text-sm-lg { font-size: 0.875rem; }
.text-base-lg{ font-size: 1rem; }
.text-lg-lg { font-size: 1.125rem; }
.text-xl-lg { font-size: 1.25rem; }
.text-2xl-lg{ font-size: 1.5rem; }
/* Height */
.h-1-lg{ height: 0.25rem; }
.h-2-lg{ height: 0.5rem; }
.h-3-lg{ height: 0.75rem; }
.h-4-lg{ height: 1rem; }
.h-5-lg{ height: 1.25rem; }
.h-6-lg{ height: 1.5rem; }
.h-8-lg{ height: 2rem; }
.h-10-lg{ height: 2.5rem; }
/* Width */
.w-1-lg{ width: 0.25rem; }
.w-2-lg{ width: 0.5rem; }
.w-3-lg{ width: 0.75rem; }
.w-4-lg{ width: 1rem; }
.w-5-lg{ width: 1.25rem; }
.w-6-lg{ width: 1.5rem; }
.w-8-lg{ width: 2rem; }
.w-10-lg{ width: 2.5rem; }
}
@media (min-width: 1200px) { /* xl */
/* Font */
.text-xxs-xl { font-size: 0.55rem; }
.text-xs-xl { font-size: 0.75rem; }
.text-sm-xl { font-size: 0.875rem; }
.text-base-xl{ font-size: 1rem; }
.text-lg-xl { font-size: 1.125rem; }
.text-xl-xl { font-size: 1.25rem; }
.text-2xl-xl{ font-size: 1.5rem; }
/* Height */
.h-1-xl{ height: 0.25rem; }
.h-2-xl{ height: 0.5rem; }
.h-3-xl{ height: 0.75rem; }
.h-4-xl{ height: 1rem; }
.h-5-xl{ height: 1.25rem; }
.h-6-xl{ height: 1.5rem; }
.h-8-xl{ height: 2rem; }
.h-10-xl{ height: 2.5rem; }
/* Width */
.w-1-xl{ width: 0.25rem; }
.w-2-xl{ width: 0.5rem; }
.w-3-xl{ width: 0.75rem; }
.w-4-xl{ width: 1rem; }
.w-5-xl{ width: 1.25rem; }
.w-6-xl{ width: 1.5rem; }
.w-8-xl{ width: 2rem; }
.w-10-xl{ width: 2.5rem; }
}

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 120 120" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="120" height="120" fill="#EFF1F3"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M33.2503 38.4816C33.2603 37.0472 34.4199 35.8864 35.8543 35.875H83.1463C84.5848 35.875 85.7503 37.0431 85.7503 38.4816V80.5184C85.7403 81.9528 84.5807 83.1136 83.1463 83.125H35.8543C34.4158 83.1236 33.2503 81.957 33.2503 80.5184V38.4816ZM80.5006 41.1251H38.5006V77.8751L62.8921 53.4783C63.9172 52.4536 65.5788 52.4536 66.6039 53.4783L80.5006 67.4013V41.1251ZM43.75 51.6249C43.75 54.5244 46.1005 56.8749 49 56.8749C51.8995 56.8749 54.25 54.5244 54.25 51.6249C54.25 48.7254 51.8995 46.3749 49 46.3749C46.1005 46.3749 43.75 48.7254 43.75 51.6249Z" fill="#687787"/>
</svg>

After

Width:  |  Height:  |  Size: 888 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

View File

@ -18613,6 +18613,10 @@ li:not(:first-child) .dropdown-item,
min-height: 70vh !important; min-height: 70vh !important;
} }
.modal-min-h{
min-height: 60vh !important;
}
.flex-fill { .flex-fill {
flex: 1 1 auto !important; flex: 1 1 auto !important;
} }

View File

Before

Width:  |  Height:  |  Size: 96 KiB

After

Width:  |  Height:  |  Size: 96 KiB

View File

Before

Width:  |  Height:  |  Size: 222 KiB

After

Width:  |  Height:  |  Size: 222 KiB

View File

Before

Width:  |  Height:  |  Size: 308 KiB

After

Width:  |  Height:  |  Size: 308 KiB

View File

Before

Width:  |  Height:  |  Size: 165 KiB

After

Width:  |  Height:  |  Size: 165 KiB

View File

Before

Width:  |  Height:  |  Size: 217 KiB

After

Width:  |  Height:  |  Size: 217 KiB

View File

Before

Width:  |  Height:  |  Size: 278 KiB

After

Width:  |  Height:  |  Size: 278 KiB

View File

Before

Width:  |  Height:  |  Size: 202 KiB

After

Width:  |  Height:  |  Size: 202 KiB

View File

Before

Width:  |  Height:  |  Size: 166 KiB

After

Width:  |  Height:  |  Size: 166 KiB

View File

Before

Width:  |  Height:  |  Size: 411 KiB

After

Width:  |  Height:  |  Size: 411 KiB

View File

Before

Width:  |  Height:  |  Size: 1.4 MiB

After

Width:  |  Height:  |  Size: 1.4 MiB

View File

Before

Width:  |  Height:  |  Size: 787 KiB

After

Width:  |  Height:  |  Size: 787 KiB

View File

Before

Width:  |  Height:  |  Size: 786 KiB

After

Width:  |  Height:  |  Size: 786 KiB

View File

Before

Width:  |  Height:  |  Size: 237 KiB

After

Width:  |  Height:  |  Size: 237 KiB

View File

Before

Width:  |  Height:  |  Size: 1.3 MiB

After

Width:  |  Height:  |  Size: 1.3 MiB

View File

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

Before

Width:  |  Height:  |  Size: 5.0 KiB

After

Width:  |  Height:  |  Size: 5.0 KiB

View File

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 4.8 KiB

View File

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

Before

Width:  |  Height:  |  Size: 5.8 KiB

After

Width:  |  Height:  |  Size: 5.8 KiB

View File

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

View File

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

Before

Width:  |  Height:  |  Size: 4.4 KiB

After

Width:  |  Height:  |  Size: 4.4 KiB

View File

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

Before

Width:  |  Height:  |  Size: 4.9 KiB

After

Width:  |  Height:  |  Size: 4.9 KiB

View File

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 192 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 232 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 148 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 184 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 295 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 115 KiB

View File

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

View File

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

View File

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

Before

Width:  |  Height:  |  Size: 401 B

After

Width:  |  Height:  |  Size: 401 B

View File

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@ -0,0 +1 @@
<svg idth="64" height="64" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><path opacity="0.2" fill-rule="evenodd" clip-rule="evenodd" d="M0 142.1L0 480c0 17.7 14.3 32 32 32s32-14.3 32-32l0-240c0-17.7 14.3-32 32-32l384 0c17.7 0 32 14.3 32 32l0 240c0 17.7 14.3 32 32 32s32-14.3 32-32l0-337.9c0-27.5-17.6-52-43.8-60.7L303.2 5.1c-9.9-3.3-20.5-3.3-30.4 0L43.8 81.4C17.6 90.1 0 114.6 0 142.1zM464 256l-352 0 0 64 352 0 0-64zM112 416l352 0 0-64-352 0 0 64zm352 32l-352 0 0 64 352 0 0-64z"/></svg>

After

Width:  |  Height:  |  Size: 500 B

View File

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

Before

Width:  |  Height:  |  Size: 860 B

After

Width:  |  Height:  |  Size: 860 B

View File

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 3.9 KiB

View File

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

Before

Width:  |  Height:  |  Size: 5.1 KiB

After

Width:  |  Height:  |  Size: 5.1 KiB

View File

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 3.9 KiB

View File

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

Before

Width:  |  Height:  |  Size: 4.6 KiB

After

Width:  |  Height:  |  Size: 4.6 KiB

View File

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

Before

Width:  |  Height:  |  Size: 224 KiB

After

Width:  |  Height:  |  Size: 224 KiB

View File

Before

Width:  |  Height:  |  Size: 860 KiB

After

Width:  |  Height:  |  Size: 860 KiB

View File

Before

Width:  |  Height:  |  Size: 58 KiB

After

Width:  |  Height:  |  Size: 58 KiB

View File

Before

Width:  |  Height:  |  Size: 59 KiB

After

Width:  |  Height:  |  Size: 59 KiB

View File

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 50 KiB

View File

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 54 KiB

View File

@ -4,6 +4,7 @@ import { ToastContainer } from "react-toastify";
import { QueryClientProvider } from '@tanstack/react-query'; import { QueryClientProvider } from '@tanstack/react-query';
import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
import { queryClient } from "./layouts/AuthLayout"; import { queryClient } from "./layouts/AuthLayout";
import ModalProvider from "./ModalProvider";
@ -11,6 +12,7 @@ const App = () => {
return ( return (
<div className="app"> <div className="app">
<QueryClientProvider client={queryClient}> <QueryClientProvider client={queryClient}>
<ModalProvider/>
<DireProvider> <DireProvider>
<AppRoutes /> <AppRoutes />
</DireProvider> </DireProvider>

View File

@ -1,112 +0,0 @@
import React, {
createContext,
useContext,
useState,
useEffect,
useRef,
} from "react";
const ModalContext = createContext();
export const useModal = () => useContext(ModalContext);
// ModalProvider to manage modal state and expose functionality to the rest of the app
export const ModalProvider = ({ children }) => {
const [isOpen, setIsOpen] = useState(false);
const [modalContent, setModalContent] = useState(null);
const [onSubmit, setOnSubmit] = useState(null);
const [modalSize, setModalSize] = useState("lg");
// Ref to track the modal content element
const modalRef = useRef(null);
const openModal = (content, onSubmitCallback, size = "lg") => {
setModalContent(content); // Set modal content dynamically
setOnSubmit(() => onSubmitCallback); // Set the submit handler dynamically
setIsOpen(true); // Open the modal
setModalSize(size); // Set the modal size
};
// Function to close the modal
const closeModal = () => {
setIsOpen(false); // Close the modal
setModalContent(null); // Clear modal content
setOnSubmit(null); // Clear the submit callback
setModalSize("lg"); // Reset modal size
};
useEffect(() => {
const handleEscape = (event) => {
if (event.key === "Escape") {
closeModal();
}
};
document.addEventListener("keydown", handleEscape);
return () => {
document.removeEventListener("keydown", handleEscape);
};
}, []);
useEffect(() => {
const handleClickOutside = (event) => {
if (modalRef.current && !modalRef.current.contains(event.target)) {
closeModal();
}
};
document.addEventListener("mousedown", handleClickOutside);
return () => {
document.removeEventListener("mousedown", handleClickOutside);
};
}, []);
return (
<ModalContext.Provider
value={{
isOpen,
openModal,
closeModal,
modalContent,
modalSize,
onSubmit,
}}
>
{children}
{isOpen && (
<div style={overlayStyles}>
<div
ref={modalRef}
style={{
...modalStyles,
maxWidth: modalSize === "sm" ? "400px" : "800px",
}}
>
<div>{modalContent}</div>
</div>
</div>
)}
</ModalContext.Provider>
);
};
const overlayStyles = {
position: "fixed",
top: 0,
left: 0,
right: 0,
bottom: 0,
backgroundColor: "rgba(0, 0, 0, 0.5)",
display: "flex",
justifyContent: "center",
alignItems: "center",
zIndex: 1050,
};
const modalStyles = {
backgroundColor: "white",
padding: "20px",
borderRadius: "5px",
boxShadow: "0 4px 6px rgba(0, 0, 0, 0.1)",
width: "90%",
maxWidth: "800px",
};

22
src/ModalProvider.jsx Normal file
View File

@ -0,0 +1,22 @@
import React, { useEffect } from "react";
import { useOrganizationModal } from "./hooks/useOrganization";
import OrganizationModal from "./components/Organization/OrganizationModal";
import { useAuthModal, useModal } from "./hooks/useAuth";
import SwitchTenant from "./pages/authentication/SwitchTenant";
import ChangePasswordPage from "./pages/authentication/ChangePassword";
const ModalProvider = () => {
const { isOpen, onClose } = useOrganizationModal();
const { isOpen: isAuthOpen } = useAuthModal();
const {isOpen:isChangePass} = useModal("ChangePassword")
return (
<>
{isOpen && <OrganizationModal />}
{isAuthOpen && <SwitchTenant />}
{isChangePass && <ChangePasswordPage /> }
</>
);
};
export default ModalProvider;

View File

@ -12,22 +12,19 @@ import { useQueryClient } from "@tanstack/react-query";
import eventBus from "../../services/eventBus"; import eventBus from "../../services/eventBus";
import { useSelectedProject } from "../../slices/apiDataManager"; import { useSelectedProject } from "../../slices/apiDataManager";
const Attendance = ({ getRole, handleModalData, searchTerm }) => { const Attendance = ({ getRole, handleModalData, searchTerm, projectId, organizationId, }) => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const navigate = useNavigate(); const navigate = useNavigate();
const [todayDate, setTodayDate] = useState(new Date()); const [todayDate, setTodayDate] = useState(new Date());
const [ShowPending, setShowPending] = useState(false); const [ShowPending, setShowPending] = useState(false);
// const selectedProject = useSelector(
// (store) => store.localVariables.projectId
// );
const selectedProject = useSelectedProject(); const selectedProject = useSelectedProject();
const { const {
attendance, attendance,
loading: attLoading, loading: attLoading,
recall: attrecall, recall: attrecall,
isFetching isFetching
} = useAttendance(selectedProject); } = useAttendance(selectedProject, organizationId);
const filteredAttendance = ShowPending const filteredAttendance = ShowPending
? attendance?.filter( ? attendance?.filter(
(att) => att?.checkInTime !== null && att?.checkOutTime === null (att) => att?.checkInTime !== null && att?.checkOutTime === null
@ -62,12 +59,11 @@ const Attendance = ({ getRole, handleModalData, searchTerm }) => {
const role = item.jobRoleName?.toLowerCase() || ""; const role = item.jobRoleName?.toLowerCase() || "";
return ( return (
fullName.includes(lowercasedSearchTerm) || fullName.includes(lowercasedSearchTerm) ||
role.includes(lowercasedSearchTerm) // also search by role role.includes(lowercasedSearchTerm) // also search by role
); );
}); });
}, [group1, group2, searchTerm]); }, [group1, group2, searchTerm]);
const { currentPage, totalPages, currentItems, paginate } = usePagination( const { currentPage, totalPages, currentItems, paginate } = usePagination(
finalFilteredData, finalFilteredData,
ITEMS_PER_PAGE ITEMS_PER_PAGE
@ -116,7 +112,7 @@ const Attendance = ({ getRole, handleModalData, searchTerm }) => {
<> <>
<div <div
className="table-responsive text-nowrap h-100" className="table-responsive text-nowrap h-100"
style={{ minHeight: "200px" }} // 🔹 Ensures fixed height style={{ minHeight: "200px" }} // Ensures fixed height
> >
<div className="d-flex text-start align-items-center py-2"> <div className="d-flex text-start align-items-center py-2">
<strong>Date : {formatUTCToLocalTime(todayDate)}</strong> <strong>Date : {formatUTCToLocalTime(todayDate)}</strong>
@ -142,6 +138,7 @@ const Attendance = ({ getRole, handleModalData, searchTerm }) => {
<tr className="border-top-1"> <tr className="border-top-1">
<th colSpan={2}>Name</th> <th colSpan={2}>Name</th>
<th>Role</th> <th>Role</th>
<th>Organization</th>
<th> <th>
<i className="bx bxs-down-arrow-alt text-success"></i> <i className="bx bxs-down-arrow-alt text-success"></i>
Check-In Check-In
@ -190,6 +187,8 @@ const Attendance = ({ getRole, handleModalData, searchTerm }) => {
</td> </td>
<td>{item.jobRoleName}</td> <td>{item.jobRoleName}</td>
<td>{item.organizationName || "--"}</td>
<td> <td>
{item.checkInTime {item.checkInTime
? convertShortTime(item.checkInTime) ? convertShortTime(item.checkInTime)
@ -213,7 +212,11 @@ const Attendance = ({ getRole, handleModalData, searchTerm }) => {
))} ))}
{!attendance && ( {!attendance && (
<tr> <tr>
<td colSpan={6} className="text-center text-secondary" style={{ height: "200px" }}> <td
colSpan={7}
className="text-center text-secondary"
style={{ height: "200px" }}
>
No employees assigned to the project! No employees assigned to the project!
</td> </td>
</tr> </tr>
@ -221,6 +224,7 @@ const Attendance = ({ getRole, handleModalData, searchTerm }) => {
</tbody> </tbody>
</table> </table>
{!loading && finalFilteredData.length > ITEMS_PER_PAGE && ( {!loading && finalFilteredData.length > ITEMS_PER_PAGE && (
<nav aria-label="Page "> <nav aria-label="Page ">
<ul className="pagination pagination-sm justify-content-end py-1"> <ul className="pagination pagination-sm justify-content-end py-1">

View File

@ -4,9 +4,12 @@ import Avatar from "../common/Avatar";
import { convertShortTime } from "../../utils/dateUtils"; import { convertShortTime } from "../../utils/dateUtils";
import RenderAttendanceStatus from "./RenderAttendanceStatus"; import RenderAttendanceStatus from "./RenderAttendanceStatus";
import { useSelector, useDispatch } from "react-redux"; import { useSelector, useDispatch } from "react-redux";
import { fetchAttendanceData } from "../../slices/apiSlice/attedanceLogsSlice";
import DateRangePicker from "../common/DateRangePicker"; import DateRangePicker from "../common/DateRangePicker";
import { clearCacheKey, getCachedData, useSelectedProject } from "../../slices/apiDataManager"; import {
clearCacheKey,
getCachedData,
useSelectedProject,
} from "../../slices/apiDataManager";
import eventBus from "../../services/eventBus"; import eventBus from "../../services/eventBus";
import AttendanceRepository from "../../repositories/AttendanceRepository"; import AttendanceRepository from "../../repositories/AttendanceRepository";
import { useAttendancesLogs } from "../../hooks/useAttendance"; import { useAttendancesLogs } from "../../hooks/useAttendance";
@ -34,195 +37,142 @@ const usePagination = (data, itemsPerPage) => {
}; };
}; };
const AttendanceLog = ({ handleModalData, searchTerm }) => { const AttendanceLog = ({ handleModalData, searchTerm, organizationId }) => {
// const selectedProject = useSelector( const selectedProject = useSelectedProject();
// (store) => store.localVariables.projectId const [dateRange, setDateRange] = useState({ startDate: "", endDate: "" });
// ); const dispatch = useDispatch();
const selectedProject = useSelectedProject(); const [loading, setLoading] = useState(false);
const [dateRange, setDateRange] = useState({ startDate: "", endDate: "" }); const [showPending, setShowPending] = useState(false);
const dispatch = useDispatch(); const [isRefreshing, setIsRefreshing] = useState(false);
const [loading, setLoading] = useState(false);
const [showPending, setShowPending] = useState(false)
const [isRefreshing, setIsRefreshing] = useState(false); const today = new Date();
const [processedData, setProcessedData] = useState([]); today.setHours(0, 0, 0, 0);
const today = new Date(); const yesterday = new Date();
today.setHours(0, 0, 0, 0); yesterday.setDate(yesterday.getDate() - 1);
const yesterday = new Date(); const isSameDay = (dateStr) => {
yesterday.setDate(yesterday.getDate() - 1); if (!dateStr) return false;
const d = new Date(dateStr);
d.setHours(0, 0, 0, 0);
return d.getTime() === today.getTime();
};
const isSameDay = (dateStr) => { const isBeforeToday = (dateStr) => {
if (!dateStr) return false; if (!dateStr) return false;
const d = new Date(dateStr); const d = new Date(dateStr);
d.setHours(0, 0, 0, 0); d.setHours(0, 0, 0, 0);
return d.getTime() === today.getTime(); return d.getTime() < today.getTime();
}; };
const isBeforeToday = (dateStr) => { const sortByName = (a, b) => {
if (!dateStr) return false; const nameA = (a.firstName + a.lastName).toLowerCase();
const d = new Date(dateStr); const nameB = (b.firstName + b.lastName).toLowerCase();
d.setHours(0, 0, 0, 0); return nameA.localeCompare(nameB);
return d.getTime() < today.getTime(); };
};
const sortByName = (a, b) => { const { data = [], isLoading, error, refetch, isFetching } = useAttendancesLogs(
const nameA = a.firstName.toLowerCase() + a.lastName.toLowerCase(); selectedProject,
const nameB = b.firstName.toLowerCase() + b.lastName.toLowerCase(); dateRange.startDate,
return nameA?.localeCompare(nameB); dateRange.endDate,
}; organizationId
);
const { const processedData = useMemo(() => {
data = [], const filteredData = showPending
isLoading, ? data.filter((item) => item.checkOutTime === null)
error, : data;
refetch,
isFetching,
} = useAttendancesLogs(
selectedProject,
dateRange.startDate,
dateRange.endDate
);
const filtering = (data) => {
const filteredData = showPending
? data.filter((item) => item.checkOutTime === null)
: data;
const group1 = filteredData const group1 = filteredData.filter((d) => d.activity === 1 && isSameDay(d.checkInTime)).sort(sortByName);
.filter((d) => d.activity === 1 && isSameDay(d.checkInTime)) const group2 = filteredData.filter((d) => d.activity === 4 && isSameDay(d.checkOutTime)).sort(sortByName);
.sort(sortByName); const group3 = filteredData.filter((d) => d.activity === 1 && isBeforeToday(d.checkInTime)).sort(sortByName);
const group2 = filteredData const group4 = filteredData.filter((d) => d.activity === 4 && isBeforeToday(d.checkOutTime));
.filter((d) => d.activity === 4 && isSameDay(d.checkOutTime)) const group5 = filteredData.filter((d) => d.activity === 2 && isBeforeToday(d.checkOutTime)).sort(sortByName);
.sort(sortByName); const group6 = filteredData.filter((d) => d.activity === 5).sort(sortByName);
const group3 = filteredData
.filter((d) => d.activity === 1 && isBeforeToday(d.checkInTime))
.sort(sortByName);
const group4 = filteredData.filter(
(d) => d.activity === 4 && isBeforeToday(d.checkOutTime)
);
const group5 = filteredData
.filter((d) => d.activity === 2 && isBeforeToday(d.checkOutTime))
.sort(sortByName);
const group6 = filteredData
.filter((d) => d.activity === 5)
.sort(sortByName);
const sortedList = [ const sortedList = [...group1, ...group2, ...group3, ...group4, ...group5, ...group6];
...group1,
...group2,
...group3,
...group4,
...group5,
...group6,
];
// Group by date const groupedByDate = sortedList.reduce((acc, item) => {
const groupedByDate = sortedList.reduce((acc, item) => { const date = (item.checkInTime || item.checkOutTime)?.split("T")[0];
const date = (item.checkInTime || item.checkOutTime)?.split("T")[0]; if (date) {
if (date) { acc[date] = acc[date] || [];
acc[date] = acc[date] || []; acc[date].push(item);
acc[date].push(item);
}
return acc;
}, {});
const sortedDates = Object.keys(groupedByDate).sort(
(a, b) => new Date(b) - new Date(a)
);
const finalData = sortedDates.flatMap((date) => groupedByDate[date]);
setProcessedData(finalData);
};
useEffect(() => {
filtering(data);
}, [data, showPending]);
// New useEffect to handle search filtering
const filteredSearchData = useMemo(() => {
if (!searchTerm) {
return processedData;
} }
const lowercasedSearchTerm = searchTerm.toLowerCase(); return acc;
return processedData.filter((item) => { }, {});
const fullName = `${item.firstName} ${item.lastName}`.toLowerCase();
return fullName.includes(lowercasedSearchTerm);
});
}, [processedData, searchTerm]);
const { const sortedDates = Object.keys(groupedByDate).sort((a, b) => new Date(b) - new Date(a));
currentPage, return sortedDates.flatMap((date) => groupedByDate[date]);
totalPages, }, [data, showPending]);
currentItems: paginatedAttendances,
paginate,
resetPage,
} = usePagination(filteredSearchData, 20);
useEffect(() => { const filteredSearchData = useMemo(() => {
resetPage(); if (!searchTerm) return processedData;
}, [filteredSearchData, resetPage]);
const handler = useCallback( const lowercased = searchTerm.toLowerCase();
(msg) => { return processedData.filter((item) =>
const { startDate, endDate } = dateRange; `${item.firstName} ${item.lastName}`.toLowerCase().includes(lowercased)
const checkIn = msg.response.checkInTime.substring(0, 10);
if (
selectedProject === msg.projectId &&
startDate <= checkIn &&
checkIn <= endDate
) {
queryClient.setQueriesData(["attendanceLogs"], (oldData) => {
if (!oldData) {
queryClient.invalidateQueries({ queryKey: ["attendanceLogs"] });
return;
}
const updatedAttendance = oldData.map((record) =>
record.id === msg.response.id
? { ...record, ...msg.response }
: record
);
filtering(updatedAttendance);
return updatedAttendance;
});
resetPage();
}
},
[selectedProject, dateRange, filtering, resetPage]
); );
}, [processedData, searchTerm]);
useEffect(() => { const {
eventBus.on("attendance_log", handler); currentPage,
return () => eventBus.off("attendance_log", handler); totalPages,
}, [handler]); currentItems: paginatedAttendances,
paginate,
resetPage,
} = usePagination(filteredSearchData, 20);
const employeeHandler = useCallback( useEffect(() => {
(msg) => { resetPage();
const { startDate, endDate } = dateRange; }, [filteredSearchData]);
if (data.some((item) => item.employeeId == msg.employeeId)) {
// dispatch( const handler = useCallback(
// fetchAttendanceData({ (msg) => {
// , const { startDate, endDate } = dateRange;
// fromDate: startDate, const checkIn = msg.response.checkInTime.substring(0, 10);
// toDate: endDate,
// }) if (selectedProject === msg.projectId && startDate <= checkIn && checkIn <= endDate) {
// ); queryClient.setQueriesData(["attendanceLogs"], (oldData) => {
if (!oldData) {
queryClient.invalidateQueries({ queryKey: ["attendanceLogs"] });
return;
}
return oldData.map((record) =>
record.id === msg.response.id ? { ...record, ...msg.response } : record
);
});
resetPage();
}
},
[selectedProject, dateRange, resetPage]
);
useEffect(() => {
eventBus.on("attendance_log", handler);
return () => eventBus.off("attendance_log", handler);
}, [handler]);
const employeeHandler = useCallback(
(msg) => {
const { startDate, endDate } = dateRange;
if (data.some((item) => item.employeeId == msg.employeeId)) {
refetch();
}
},
[data, refetch]
);
useEffect(() => {
eventBus.on("employee", employeeHandler);
return () => eventBus.off("employee", employeeHandler);
}, [employeeHandler]);
refetch()
}
},
[selectedProject, dateRange, data, refetch]
);
useEffect(() => {
eventBus.on("employee", employeeHandler);
return () => eventBus.off("employee", employeeHandler);
}, [employeeHandler]);
return ( return (
<> <>
<div <div
className="dataTables_length text-start py-2 d-flex justify-content-between" className="dataTables_length text-start py-2 d-flex justify-content-between "
id="DataTables_Table_0_length" id="DataTables_Table_0_length"
> >
<div className="d-flex align-items-center my-0 "> <div className="d-flex align-items-center my-0 ">
@ -230,7 +180,7 @@ const AttendanceLog = ({ handleModalData, searchTerm }) => {
onRangeChange={setDateRange} onRangeChange={setDateRange}
defaultStartDate={yesterday} defaultStartDate={yesterday}
/> />
<div className="form-check form-switch text-start m-0 ms-5"> <div className="form-check form-switch text-start ms-1 ms-md-2 align-items-center mb-0">
<input <input
type="checkbox" type="checkbox"
className="form-check-input" className="form-check-input"
@ -243,18 +193,16 @@ const AttendanceLog = ({ handleModalData, searchTerm }) => {
<label className="form-check-label ms-0">Show Pending</label> <label className="form-check-label ms-0">Show Pending</label>
</div> </div>
</div> </div>
<div className="col-md-2 m-0 text-end">
<i
className={`bx bx-refresh cursor-pointer fs-4 ${isFetching ? "spin" : ""
}`}
title="Refresh"
onClick={() => refetch()}
/>
</div>
</div> </div>
<div className="table-responsive text-nowrap" style={{ minHeight: "200px" }}> <div
className="table-responsive text-nowrap"
style={{ minHeight: "200px" }}
>
{isLoading ? ( {isLoading ? (
<div className="d-flex justify-content-center align-items-center" style={{ height: "200px" }}> <div
className="d-flex justify-content-center align-items-center"
style={{ height: "200px" }}
>
<p className="text-secondary">Loading...</p> <p className="text-secondary">Loading...</p>
</div> </div>
) : filteredSearchData?.length > 0 ? ( ) : filteredSearchData?.length > 0 ? (
@ -265,6 +213,7 @@ const AttendanceLog = ({ handleModalData, searchTerm }) => {
Name Name
</th> </th>
<th className="border-top-1">Date</th> <th className="border-top-1">Date</th>
<th>Organization</th>
<th> <th>
<i className="bx bxs-down-arrow-alt text-success"></i>{" "} <i className="bx bxs-down-arrow-alt text-success"></i>{" "}
Check-In Check-In
@ -283,9 +232,9 @@ const AttendanceLog = ({ handleModalData, searchTerm }) => {
const previousAttendance = arr[index - 1]; const previousAttendance = arr[index - 1];
const previousDate = previousAttendance const previousDate = previousAttendance
? moment( ? moment(
previousAttendance.checkInTime || previousAttendance.checkInTime ||
previousAttendance.checkOutTime previousAttendance.checkOutTime
).format("YYYY-MM-DD") ).format("YYYY-MM-DD")
: null; : null;
if (!previousDate || currentDate !== previousDate) { if (!previousDate || currentDate !== previousDate) {
@ -294,7 +243,7 @@ const AttendanceLog = ({ handleModalData, searchTerm }) => {
key={`header-${currentDate}`} key={`header-${currentDate}`}
className="table-row-header" className="table-row-header"
> >
<td colSpan={6} className="text-start"> <td colSpan={8} className="text-start">
<strong> <strong>
{moment(currentDate).format("DD-MM-YYYY")} {moment(currentDate).format("DD-MM-YYYY")}
</strong> </strong>
@ -324,6 +273,7 @@ const AttendanceLog = ({ handleModalData, searchTerm }) => {
attendance.checkInTime || attendance.checkOutTime attendance.checkInTime || attendance.checkOutTime
).format("DD-MMM-YYYY")} ).format("DD-MMM-YYYY")}
</td> </td>
<td>{attendance.organizationName || "--"}</td>
<td>{convertShortTime(attendance.checkInTime)}</td> <td>{convertShortTime(attendance.checkInTime)}</td>
<td> <td>
{attendance.checkOutTime {attendance.checkOutTime
@ -345,7 +295,12 @@ const AttendanceLog = ({ handleModalData, searchTerm }) => {
</tbody> </tbody>
</table> </table>
) : ( ) : (
<div className="my-4"><span className="text-secondary">No Record Available !</span></div> <div className="my-12">
<span className="text-secondary">
No data available for the selected date range. Please Select
another date.
</span>
</div>
)} )}
</div> </div>
{paginatedAttendances?.length == 0 && filteredSearchData?.length > 0 && ( {paginatedAttendances?.length == 0 && filteredSearchData?.length > 0 && (
@ -371,8 +326,9 @@ const AttendanceLog = ({ handleModalData, searchTerm }) => {
(pageNumber) => ( (pageNumber) => (
<li <li
key={pageNumber} key={pageNumber}
className={`page-item ${currentPage === pageNumber ? "active" : "" className={`page-item ${
}`} currentPage === pageNumber ? "active" : ""
}`}
> >
<button <button
className="page-link" className="page-link"
@ -384,8 +340,9 @@ const AttendanceLog = ({ handleModalData, searchTerm }) => {
) )
)} )}
<li <li
className={`page-item ${currentPage === totalPages ? "disabled" : "" className={`page-item ${
}`} currentPage === totalPages ? "disabled" : ""
}`}
> >
<button <button
className="page-link" className="page-link"

View File

@ -5,7 +5,6 @@ import { zodResolver } from "@hookform/resolvers/zod";
import TimePicker from "../common/TimePicker"; import TimePicker from "../common/TimePicker";
import { usePositionTracker } from "../../hooks/usePositionTracker"; import { usePositionTracker } from "../../hooks/usePositionTracker";
import { useDispatch, useSelector } from "react-redux"; import { useDispatch, useSelector } from "react-redux";
import { markAttendance } from "../../slices/apiSlice/attedanceLogsSlice";
import showToast from "../../services/toastService"; import showToast from "../../services/toastService";
import { checkIfCurrentDate } from "../../utils/dateUtils"; import { checkIfCurrentDate } from "../../utils/dateUtils";
import { useMarkAttendance } from "../../hooks/useAttendance"; import { useMarkAttendance } from "../../hooks/useAttendance";
@ -34,7 +33,7 @@ const createSchema = (modeldata) => {
const checkOut = new Date(checkIn); const checkOut = new Date(checkIn);
checkOut.setHours(hour, minute, 0, 0); checkOut.setHours(hour, minute, 0, 0);
return checkOut > checkIn; return checkOut >= checkIn;
} }
return true; return true;
}, { }, {
@ -97,12 +96,12 @@ const CheckInCheckOut = ({ modeldata, closeModal, handleSubmitForm }) => {
}; };
return ( return (
<form className="row g-2" onSubmit={handleSubmit(onSubmit)}> <form className="row p-2" onSubmit={handleSubmit(onSubmit)}>
<div className="col-12 d-flex justify-content-center"> <div className="col-12 d-flex justify-content-center mb-4">
<label className="fs-5 text-dark text-center"> <label className="fs-5 tex-semibold text-center">
{modeldata?.checkInTime && !modeldata?.checkOutTime {modeldata?.checkInTime && !modeldata?.checkOutTime
? "Check-out :" ? "Check-Out "
: "Check-in :"} : "Check-In "}
</label> </label>
</div> </div>
@ -121,7 +120,7 @@ const CheckInCheckOut = ({ modeldata, closeModal, handleSubmitForm }) => {
modeldata?.checkInTime && !modeldata?.checkOutTime modeldata?.checkInTime && !modeldata?.checkOutTime
? formatDate(modeldata?.checkInTime?.split("T")[0]) || "" ? formatDate(modeldata?.checkInTime?.split("T")[0]) || ""
: formatDate(today) : formatDate(today)
} }
disabled disabled
/> />
</div> </div>

View File

@ -1,4 +1,3 @@
import React, { useState, useEffect } from "react"; import React, { useState, useEffect } from "react";
import "../../components/Project/ProjectInfra.css"; import "../../components/Project/ProjectInfra.css";
import BuildingModel from "../Project/Infrastructure/BuildingModel"; import BuildingModel from "../Project/Infrastructure/BuildingModel";
@ -8,64 +7,75 @@ import WorkAreaModel from "../Project/Infrastructure/WorkAreaModel";
import TaskModel from "../Project/Infrastructure/TaskModel"; import TaskModel from "../Project/Infrastructure/TaskModel";
import ProjectRepository from "../../repositories/ProjectRepository"; import ProjectRepository from "../../repositories/ProjectRepository";
import Breadcrumb from "../../components/common/Breadcrumb"; import Breadcrumb from "../../components/common/Breadcrumb";
import {useProjectDetails, useProjectInfra, useProjects} from "../../hooks/useProjects"; import {
import {useHasUserPermission} from "../../hooks/useHasUserPermission"; useCurrentService,
import {APPROVE_TASK, ASSIGN_REPORT_TASK, MANAGE_PROJECT_INFRA} from "../../utils/constants"; useProjectDetails,
import {useDispatch, useSelector} from "react-redux"; useProjectInfra,
import {useProfile} from "../../hooks/useProfile"; useProjects,
import {refreshData, setProjectId} from "../../slices/localVariablesSlice"; } from "../../hooks/useProjects";
import { useHasUserPermission } from "../../hooks/useHasUserPermission";
import {
APPROVE_TASK,
ASSIGN_REPORT_TASK,
MANAGE_PROJECT_INFRA,
} from "../../utils/constants";
import { useDispatch, useSelector } from "react-redux";
import { useProfile } from "../../hooks/useProfile";
import { refreshData, setProjectId } from "../../slices/localVariablesSlice";
import InfraTable from "../Project/Infrastructure/InfraTable"; import InfraTable from "../Project/Infrastructure/InfraTable";
import { useSelectedProject } from "../../slices/apiDataManager"; import { useSelectedProject } from "../../slices/apiDataManager";
import Loader from "../common/Loader"; import Loader from "../common/Loader";
const InfraPlanning = () => {
const InfraPlanning = () => const { profile: LoggedUser, refetch: fetchData } = useProfile();
{ const dispatch = useDispatch();
const {profile: LoggedUser, refetch : fetchData} = useProfile()
const dispatch = useDispatch()
// const selectedProject = useSelector((store)=>store.localVariables.projectId)
const selectedProject = useSelectedProject(); const selectedProject = useSelectedProject();
const {projectInfra, isLoading, error} = useProjectInfra( selectedProject ) const selectedService = useCurrentService();
const { projectInfra, isLoading, isError, error, isFetched } =
useProjectInfra(selectedProject, selectedService || "" );
const canManageInfra = useHasUserPermission(MANAGE_PROJECT_INFRA);
const ManageInfra = useHasUserPermission( MANAGE_PROJECT_INFRA ) const canApproveTask = useHasUserPermission(APPROVE_TASK);
const ApprovedTaskRights = useHasUserPermission(APPROVE_TASK) const canReportTask = useHasUserPermission(ASSIGN_REPORT_TASK);
const ReportTaskRights = useHasUserPermission(ASSIGN_REPORT_TASK)
const reloadedData = useSelector( ( store ) => store.localVariables.reload )
const reloadedData = useSelector((store) => store.localVariables.reload);
// useEffect( () =>
// {
// if (reloadedData)
// {
// refetch()
// dispatch( refreshData( false ) )
// }
// },[reloadedData]) const hasAccess = canManageInfra || canApproveTask || canReportTask;
if (isError) {
return <div>{error?.response?.data?.message || error?.message}</div>;
}
if (!hasAccess && !isLoading) {
return (
<div className="text-center">
<i className="fa-solid fa-triangle-exclamation fs-5"></i>
<p>Access Denied: You don't have permission to perform this action.</p>
</div>
);
}
if (isLoading) {
return <Loader />;
}
if (isFetched && (!projectInfra || projectInfra.length === 0)) {
return (
<div className="text-center">
<p className="my-3">No Result Found</p>
</div>
);
}
return ( return (
<div className="col-md-12 col-lg-12 col-xl-12 order-0 mb-4"> <div className="col-md-12 col-lg-12 col-xl-12 order-0 mb-4">
<div className="card">
<div className="card-body" style={{ padding: "0.5rem" }}> <div className="card-body" style={{ padding: "0.5rem" }}>
{(ApprovedTaskRights || ReportTaskRights) ? ( <div className="row">
<div className="align-items-center"> <InfraTable buildings={projectInfra} projectId={selectedProject} />
<div className="row ">
{isLoading && (<Loader/> )}
{( !isLoading && projectInfra?.length === 0 ) && ( <p>No Result Found</p> )}
{(!isLoading && projectInfra?.length > 0) && (<InfraTable buildings={projectInfra} projectId={selectedProject}/>)}
</div>
</div> </div>
) : (
<div className="text-center">
<i className="fa-solid fa-triangle-exclamation fs-5"></i>
<p>Access Denied: You don't have permission to perform this action. !</p>
</div>
)}
</div> </div>
</div> </div>
</div>
); );
}; };

View File

@ -1,25 +1,43 @@
import React, { useCallback, useEffect, useState, useMemo } from "react"; import React, { useCallback, useEffect, useState, useMemo } from "react";
import Avatar from "../common/Avatar"; import Avatar from "../common/Avatar";
import { convertShortTime } from "../../utils/dateUtils"; import { convertShortTime, formatUTCToLocalTime } from "../../utils/dateUtils";
import RegularizationActions from "./RegularizationActions"; import RegularizationActions from "./RegularizationActions";
import { useSelector } from "react-redux"; import { useSelector } from "react-redux";
import { useRegularizationRequests } from "../../hooks/useAttendance"; import { useRegularizationRequests } from "../../hooks/useAttendance";
import moment from "moment"; import moment from "moment";
import usePagination from "../../hooks/usePagination"; import usePagination from "../../hooks/usePagination";
import eventBus from "../../services/eventBus"; import eventBus from "../../services/eventBus";
import { cacheData, clearCacheKey, useSelectedProject } from "../../slices/apiDataManager"; import {
cacheData,
clearCacheKey,
useSelectedProject,
} from "../../slices/apiDataManager";
import { useQueryClient } from "@tanstack/react-query"; import { useQueryClient } from "@tanstack/react-query";
import Pagination from "../../components/common/Pagination";
const Regularization = ({ handleRequest, searchTerm }) => { const Regularization = ({
handleRequest,
searchTerm,
projectId,
organizationId,
IncludeInActive,
}) => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
// var selectedProject = useSelector((store) => store.localVariables.projectId); // var selectedProject = useSelector((store) => store.localVariables.projectId);
const selectedProject = useSelectedProject(); const selectedProject = useSelectedProject();
const [regularizesList, setregularizedList] = useState([]); const [regularizesList, setregularizedList] = useState([]);
const { regularizes, loading, error, refetch } = const { regularizes, loading, error, refetch } = useRegularizationRequests(
useRegularizationRequests(selectedProject); selectedProject,
organizationId,
IncludeInActive
);
useEffect(() => { useEffect(() => {
if(!regularizes) return
if(regularizes?.length) {
setregularizedList(regularizes); setregularizedList(regularizes);
}
}, [regularizes]); }, [regularizes]);
const sortByName = (a, b) => { const sortByName = (a, b) => {
@ -54,18 +72,15 @@ const Regularization = ({ handleRequest, searchTerm }) => {
} }
const lowercasedSearchTerm = searchTerm.toLowerCase(); const lowercasedSearchTerm = searchTerm.toLowerCase();
return sortedList.filter((item) => { return sortedList.filter((item) => {
const fullName = `${item.firstName} ${item.lastName}`.toLowerCase(); const fullName = `${item?.firstName} ${item?.lastName}`.toLowerCase();
return fullName.includes(lowercasedSearchTerm); return fullName.includes(lowercasedSearchTerm);
}); });
}, [regularizesList, searchTerm]); }, [regularizesList, searchTerm]);
const { currentPage, totalPages, currentItems, paginate } = const { currentPage, totalPages, currentItems, paginate } = usePagination(
usePagination(filteredSearchData, 20); filteredSearchData,
20
// Reset pagination when the search term or data changes );
useEffect(() => {
}, [filteredSearchData]);
useEffect(() => { useEffect(() => {
eventBus.on("regularization", handler); eventBus.on("regularization", handler);
@ -87,9 +102,15 @@ const Regularization = ({ handleRequest, searchTerm }) => {
}, [employeeHandler]); }, [employeeHandler]);
return ( return (
<div className="table-responsive text-nowrap pb-4" style={{ minHeight: "200px" }}> <div
className="table-responsive text-nowrap pb-4"
style={{ minHeight: "200px" }}
>
{loading ? ( {loading ? (
<div className="d-flex justify-content-center align-items-center" style={{ height: "200px" }}> <div
className="d-flex justify-content-center align-items-center"
style={{ height: "200px" }}
>
<p className="text-secondary">Loading...</p> <p className="text-secondary">Loading...</p>
</div> </div>
) : currentItems?.length > 0 ? ( ) : currentItems?.length > 0 ? (
@ -98,24 +119,28 @@ const Regularization = ({ handleRequest, searchTerm }) => {
<tr> <tr>
<th colSpan={2}>Name</th> <th colSpan={2}>Name</th>
<th>Date</th> <th>Date</th>
<th>Organization</th>
<th> <th>
<i className="bx bxs-down-arrow-alt text-success"></i>Check-In <i className="bx bxs-down-arrow-alt text-success"></i>Check-In
</th> </th>
<th> <th>
<i className="bx bxs-up-arrow-alt text-danger"></i>Check-Out <i className="bx bxs-up-arrow-alt text-danger"></i>Check-Out
</th> </th>
<th colSpan={2}>
Requested By
</th>
<th >
Requested At
</th>
<th>Action</th> <th>Action</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{currentItems?.map((att, index) => ( {currentItems?.map((att, index) => (
<tr key={index}> <tr key={index}>
<td colSpan={2}> <td colSpan={2}>
<div className="d-flex justify-content-start align-items-center"> <div className="d-flex justify-content-start align-items-center">
<Avatar <Avatar firstName={att.firstName} lastName={att.lastName} />
firstName={att.firstName}
lastName={att.lastName}
></Avatar>
<div className="d-flex flex-column"> <div className="d-flex flex-column">
<a href="#" className="text-heading text-truncate"> <a href="#" className="text-heading text-truncate">
<span className="fw-normal"> <span className="fw-normal">
@ -126,9 +151,28 @@ const Regularization = ({ handleRequest, searchTerm }) => {
</div> </div>
</td> </td>
<td>{moment(att.checkOutTime).format("DD-MMM-YYYY")}</td> <td>{moment(att.checkOutTime).format("DD-MMM-YYYY")}</td>
<td>{att.organizationName || "--"}</td>
<td>{convertShortTime(att.checkInTime)}</td> <td>{convertShortTime(att.checkInTime)}</td>
<td> <td>
{att.checkOutTime ? convertShortTime(att.checkOutTime) : "--"} {att.requestedAt ? convertShortTime(att.checkOutTime) : "--"}
</td>
<td colSpan={2}>
{att.requestedBy ? ( <div className="d-flex justify-content-start align-items-center">
<Avatar firstName={att?.requestedBy?.firstName} lastName={att?.requestedBy?.lastName} />
<div className="d-flex flex-column">
<a href="#" className="text-heading text-truncate">
<span className="fw-normal">
{att?.requestedBy?.firstName} {att?.requestedBy?.lastName}
</span>
</a>
</div>
</div>):(<small>--</small>)}
</td>
<td>
{att?.requestedAt ? formatUTCToLocalTime(att.requestedAt,true) : "--"}
</td> </td>
<td className="text-center "> <td className="text-center ">
<RegularizationActions <RegularizationActions
@ -136,7 +180,6 @@ const Regularization = ({ handleRequest, searchTerm }) => {
handleRequest={handleRequest} handleRequest={handleRequest}
refresh={refetch} refresh={refetch}
/> />
{/* </div> */}
</td> </td>
</tr> </tr>
))} ))}
@ -154,7 +197,7 @@ const Regularization = ({ handleRequest, searchTerm }) => {
</span> </span>
</div> </div>
)} )}
{!loading && totalPages > 1 && ( {/* {!loading && totalPages > 1 && (
<nav aria-label="Page "> <nav aria-label="Page ">
<ul className="pagination pagination-sm justify-content-end py-1 mt-3"> <ul className="pagination pagination-sm justify-content-end py-1 mt-3">
<li className={`page-item ${currentPage === 1 ? "disabled" : ""}`}> <li className={`page-item ${currentPage === 1 ? "disabled" : ""}`}>
@ -192,6 +235,14 @@ const Regularization = ({ handleRequest, searchTerm }) => {
</li> </li>
</ul> </ul>
</nav> </nav>
)} */}
{totalPages > 0 && (
<Pagination
currentPage={currentPage}
totalPages={totalPages}
onPageChange={paginate}
/>
)} )}
</div> </div>
); );

View File

@ -1,9 +1,7 @@
import React, { act, useEffect, useState } from 'react' import React, { act, useEffect, useState } from 'react'
import useAttendanceStatus, { ACTIONS } from '../../hooks/useAttendanceStatus'; import useAttendanceStatus, { ACTIONS } from '../../hooks/useAttendanceStatus';
// import AttendanceRepository from '../../repositories/AttendanceRepository';
import { useDispatch, useSelector } from 'react-redux'; import { useDispatch, useSelector } from 'react-redux';
import { usePositionTracker } from '../../hooks/usePositionTracker'; import { usePositionTracker } from '../../hooks/usePositionTracker';
import {markCurrentAttendance} from '../../slices/apiSlice/attendanceAllSlice';
import {cacheData, getCachedData, useSelectedProject} from '../../slices/apiDataManager'; import {cacheData, getCachedData, useSelectedProject} from '../../slices/apiDataManager';
import showToast from '../../services/toastService'; import showToast from '../../services/toastService';
import { useMarkAttendance } from '../../hooks/useAttendance'; import { useMarkAttendance } from '../../hooks/useAttendance';

View File

@ -77,7 +77,7 @@ export const ReportTask = ({ report, closeModal }) => {
return ( return (
<div className="container m-0"> <div className="container m-0">
<div className="text-center"> <div className="text-center">
<p className="fs-6 fw-semibold">Report Task</p> <p className="fs-5 fw-semibold">Report Task</p>
</div> </div>
<div className="mb-1 row text-start"> <div className="mb-1 row text-start">
<label htmlFor="html5-text-input" className="col-md-4 col-form-label"> <label htmlFor="html5-text-input" className="col-md-4 col-form-label">
@ -101,16 +101,14 @@ export const ReportTask = ({ report, closeModal }) => {
<label htmlFor="html5-email-input" className="col-md-4 col-form-label"> <label htmlFor="html5-email-input" className="col-md-4 col-form-label">
Wrok Area : Wrok Area :
</label> </label>
<div className="col-md-8 text-start text-wrap"> <div className="col-md-8 text-start">
<label className=" col-form-label"> <div className="text-wrap">
{" "} {report?.workItem?.workArea?.floor?.building?.name} <i className="bx bx-chevron-right"></i>
{report?.workItem?.workArea?.floor?.building?.name}{" "} {report?.workItem?.workArea?.floor?.floorName} <i className="bx bx-chevron-right"></i>
<i className="bx bx-chevron-right"></i>{" "} {report?.workItem?.workArea?.areaName}
{report?.workItem?.workArea?.floor?.floorName}{" "} </div>
<i className="bx bx-chevron-right"> </i> </div>
{report?.workItem?.workArea?.areaName}
</label>
</div>
</div> </div>
<div className="mb-1 row text-start"> <div className="mb-1 row text-start">
<label htmlFor="html5-email-input" className="col-md-4 col-form-label"> <label htmlFor="html5-email-input" className="col-md-4 col-form-label">

View File

@ -110,6 +110,8 @@ const ReportTaskComments = ({
approvedTask: defaultCompletedTask || 0, approvedTask: defaultCompletedTask || 0,
}); });
}, [defaultCompletedTask]); }, [defaultCompletedTask]);
const completed_Task = watch("approvedTask")
return ( return (
<div className="p-2 p-sm-1"> <div className="p-2 p-sm-1">
<div className="modal-body p-sm-4 p-0"> <div className="modal-body p-sm-4 p-0">
@ -339,13 +341,13 @@ const ReportTaskComments = ({
<div <div
className={` ${ className={` ${
actionAllow && !commentsData.approvedBy actionAllow && !commentsData.approvedBy
? " d-flex justify-content-between" ? " d-flex justify-content-between align-items-center"
: "text-end" : "text-end"
} mt-2`} } mt-2`}
> >
<div <div
className={`form-check ${ className={`form-check ${
!(actionAllow && !commentsData.approvedBy) && "d-none" !(actionAllow && !commentsData.approvedBy && defaultCompletedTask > completed_Task ) && "d-none"
} `} } `}
> >
<input <input
@ -383,7 +385,7 @@ const ReportTaskComments = ({
: "Comment"} : "Comment"}
</button> </button>
</span> </span>
</div> </div>
</form> </form>
<ul <ul

View File

@ -4,6 +4,7 @@ import { zodResolver } from "@hookform/resolvers/zod";
import { string, z } from "zod"; import { string, z } from "zod";
import { import {
useActivitiesMaster, useActivitiesMaster,
useServices,
useWorkCategoriesMaster, useWorkCategoriesMaster,
} from "../../hooks/masterHook/useMaster"; } from "../../hooks/masterHook/useMaster";
import showToast from "../../services/toastService"; import showToast from "../../services/toastService";
@ -25,6 +26,8 @@ const SubTask = ({ activity, onClose }) => {
const { activities, loading } = useActivitiesMaster(); const { activities, loading } = useActivitiesMaster();
const { categories, categoryLoading } = useWorkCategoriesMaster(); const { categories, categoryLoading } = useWorkCategoriesMaster();
const { Task, loading: TaskLoading } = useTaskById(activity?.id); const { Task, loading: TaskLoading } = useTaskById(activity?.id);
const {data,isError,isLoading,error} = useServices();
const { const {
register, register,
handleSubmit, handleSubmit,
@ -97,8 +100,8 @@ const SubTask = ({ activity, onClose }) => {
}; };
return ( return (
<div className="container-xxl my-1"> <div className="container-xxl my-1">
<p className="fw-semibold">Create Sub Task</p> <p className="fw-semibold fs-5">Create Sub Task</p>
<form className="row g-2" onSubmit={handleSubmit(onSubmitForm)}> <form className="row g-2 text-start" onSubmit={handleSubmit(onSubmitForm)}>
<div className="col-6"> <div className="col-6">
<label className="form-label">Building</label> <label className="form-label">Building</label>
<input <input
@ -128,27 +131,15 @@ const SubTask = ({ activity, onClose }) => {
disabled disabled
/> />
</div> </div>
<div className="col-12">
<div className="col-12"> <label className="form-label">Service</label>
<label className="form-label">Work Category</label> <input
<select type="text"
className="form-select form-select-sm" className="form-control form-control-sm"
{...register("workCategoryId")} value={activity?.workItem?.activityMaster?.activityGroup?.service?.name || ""}
onChange={handleCategoryChange} disabled
> />
<option value=""> </div>
{categoryLoading ? "Loading..." : "-- Select Category --"}
</option>
{categoryData.map((category) => (
<option key={category.id} value={category.id}>
{category.name}
</option>
))}
</select>
{errors.workCategoryId && (
<div className="danger-text">{errors.workCategoryId.message}</div>
)}
</div>
<div className="col-12"> <div className="col-12">
<label className="form-label">Select Activity</label> <label className="form-label">Select Activity</label>
<select <select
@ -172,6 +163,27 @@ const SubTask = ({ activity, onClose }) => {
)} )}
</div> </div>
<div className="col-12">
<label className="form-label">Work Category</label>
<select
className="form-select form-select-sm"
{...register("workCategoryId")}
onChange={handleCategoryChange}
>
<option value="">
{categoryLoading ? "Loading..." : "-- Select Category --"}
</option>
{categoryData.map((category) => (
<option key={category.id} value={category.id}>
{category.name}
</option>
))}
</select>
{errors.workCategoryId && (
<div className="danger-text">{errors.workCategoryId.message}</div>
)}
</div>
<div className="col-4"> <div className="col-4">
<label className="form-label">Planned Work</label> <label className="form-label">Planned Work</label>
<input <input
@ -219,7 +231,15 @@ const SubTask = ({ activity, onClose }) => {
)} )}
</div> </div>
<div className="col-12 text-center"> <div className="d-flex flex-row gap-3 justify-content-end py-2">
<button
type="button"
className="btn btn-sm btn-label-secondary"
onClick={() => onClose()}
disabled={isPending}
>
Cancel
</button>
<button <button
type="submit" type="submit"
className="btn btn-sm btn-primary me-2" className="btn btn-sm btn-primary me-2"
@ -227,14 +247,7 @@ const SubTask = ({ activity, onClose }) => {
> >
{isPending ? "Please wait..." : "Submit"} {isPending ? "Please wait..." : "Submit"}
</button> </button>
<button
type="button"
className="btn btn-sm btn-secondary"
onClick={() => onClose()}
disabled={isPending}
>
Cancel
</button>
</div> </div>
</form> </form>
</div> </div>

View File

@ -1,42 +0,0 @@
import React, { createContext, useState, useContext } from "react";
import ChangePasswordPage from "../../pages/authentication/ChangePassword";
const ChangePasswordContext = createContext();
export const ChangePasswordProvider = ({ children }) => {
const [isChangePasswordOpen, setIsChangePasswordOpen] = useState(false);
const openChangePassword = () => setIsChangePasswordOpen(true);
const closeChangePassword = () => setIsChangePasswordOpen(false);
return (
<ChangePasswordContext.Provider
value={{ isChangePasswordOpen, openChangePassword, closeChangePassword }}
>
{children}
{isChangePasswordOpen && (
<>
{/* This is the main Bootstrap modal container */}
{/* It provides the fixed positioning and high z-index */}
<div
className="modal fade show" // 'fade' for animation, 'show' to make it visible
style={{ display: 'block' }} // Explicitly set display: block for immediate visibility
tabIndex="-1" // Makes the modal focusable
role="dialog" // ARIA role for accessibility
aria-labelledby="changePasswordModalLabel" // Link to a heading for accessibility
aria-modal="true" // Indicate it's a modal dialog
>
{/* The ChangePasswordPage component itself contains the modal-dialog and modal-content */}
<ChangePasswordPage onClose={closeChangePassword} />
</div>
{/* The modal backdrop */}
<div className="modal-backdrop fade show"></div>
</>
)}
</ChangePasswordContext.Provider>
);
};
export const useChangePassword = () => useContext(ChangePasswordContext);

View File

@ -0,0 +1,107 @@
import React, { useState } from "react";
import { useCurrentService } from "../../hooks/useProjects";
import { useSelectedProject } from "../../slices/apiDataManager";
import { FormProvider, useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import {
TaskReportDefaultValue,
TaskReportFilterSchema,
} from "./TaskRportScheam";
import { DateRangePicker1 } from "../common/DateRangePicker";
import SelectMultiple from "../common/SelectMultiple";
import { localToUtc } from "../../utils/appUtils";
import { useTaskFilter } from "../../hooks/useTasks";
const TaskReportFilterPanel = ({ handleFilter }) => {
const [resetKey, setResetKey] = useState(0);
const selectedProject = useSelectedProject();
const selectedService = useCurrentService();
const { data } = useTaskFilter(selectedProject);
const methods = useForm({
resolver: zodResolver(TaskReportFilterSchema),
defaultValues: TaskReportDefaultValue,
});
const {
register,
reset,
handleSubmit,
formState: { errors },
} = methods;
const onSubmit = (formData) => {
const filterPayload = {
...formData,
dateFrom: localToUtc(formData.dateFrom),
dateTo: localToUtc(formData.dateTo),
};
handleFilter(filterPayload);
};
const onClear = () => {
setResetKey((prev) => prev + 1);
handleFilter(TaskReportDefaultValue);
reset(TaskReportDefaultValue);
};
return (
<FormProvider {...methods}>
<form onSubmit={handleSubmit(onSubmit)} className="p-2 text-start">
<div className="mb-3 w-100">
<label className="fw-semibold">Choose Date Range:</label>
<DateRangePicker1
placeholder="DD-MM-YYYY To DD-MM-YYYY"
startField="dateFrom"
endField="dateTo"
resetSignal={resetKey}
defaultRange={false}
maxDate={new Date()}
/>
</div>
<div className="row mb-2">
<SelectMultiple
name="buildingIds"
label="Building"
options={data?.buildings}
labelKey="name"
valueKey="id"
/>
</div>
<div className="row mb-2">
<SelectMultiple
name="floorIds"
label="Floor"
options={data?.floors}
labelKey="name"
valueKey="id"
/>
</div>
<div className="row mb-2">
<SelectMultiple
name="activityIds"
label="Activities"
options={data?.activities}
labelKey="name"
valueKey="id"
/>
</div>
<div className="d-flex justify-content-end py-3 gap-2">
<button
type="button"
className="btn btn-label-secondary btn-sm"
onClick={onClear}
>
Clear
</button>
<button type="submit" className="btn btn-primary btn-sm">
Apply
</button>
</div>
</form>
</FormProvider>
);
};
export default TaskReportFilterPanel;

View File

@ -0,0 +1,301 @@
import React, { useState, useEffect, useMemo } from "react";
import { useTaskList } from "../../hooks/useTasks";
import { useSelectedProject } from "../../slices/apiDataManager";
import { useProjectName } from "../../hooks/useProjects";
import DailyProgrssReport, {
useDailyProgrssContext,
} from "../../pages/DailyProgressReport/DailyProgrssReport";
import { useDispatch } from "react-redux";
import { setProjectId } from "../../slices/localVariablesSlice";
import {
APPROVE_TASK,
ASSIGN_REPORT_TASK,
ITEMS_PER_PAGE,
} from "../../utils/constants";
import { formatNumber, formatUTCToLocalTime } from "../../utils/dateUtils";
import { useHasUserPermission } from "../../hooks/useHasUserPermission";
import Pagination from "../common/Pagination";
import { TaskReportListSkeleton } from "./TaskRepprtListSkeleton";
import HoverPopup from "../common/HoverPopup";
const TaskReportList = () => {
const [currentPage, setCurrentPage] = useState(1);
const [filters, setFilters] = useState({
selectedBuilding: "",
selectedFloors: [],
selectedActivities: [],
});
const dispatch = useDispatch();
const ApprovedTaskRights = useHasUserPermission(APPROVE_TASK);
const ReportTaskRights = useHasUserPermission(ASSIGN_REPORT_TASK);
const { service, openModal, closeModal,filter } = useDailyProgrssContext();
const selectedProject = useSelectedProject();
const { projectNames } = useProjectName();
const { data, isLoading, isError, error } = useTaskList(
selectedProject,
ITEMS_PER_PAGE,
currentPage,
service,filter
);
const ProgrssReportColumn = [
{
key: "activity",
label: "Activity",
getValue: (task) => task.workItem.activityMaster?.activityName || "N/A",
align: "text-start",
},
{
key: "assigned",
label: "Total Assigned",
getValue: (task) => task.plannedTask ?? "N/A",
align: "text-start",
},
{
key: "completed",
label: "Completed",
getValue: (task) => task.completedTask ?? "N/A",
align: "text-start",
},
{
key: "assignAt",
label: "Assign Date",
getValue: (task) =>
task.assignmentDate ? formatUTCToLocalTime(task.assignmentDate) : "N/A",
align: "text-start",
},
{
key: "team",
label: "Team",
getValue: (task) =>
task.teamMembers?.map((m) => `${m.firstName} ${m.lastName}`).join(", ") ||
"N/A",
align: "text-start",
},
];
const paginate = (page) => {
if (page >= 1 && page <= (data?.totalPages ?? 1)) {
setCurrentPage(page);
}
};
useEffect(() => {
if (!selectedProject && projectNames.length > 0) {
dispatch(setProjectId(projectNames[0].id));
}
}, [selectedProject, projectNames, dispatch]);
useEffect(() => {
setFilters({
selectedBuilding: "",
selectedFloors: [],
selectedActivities: [],
});
}, [selectedProject]);
// Filter and Group wise data
const filteredTasks = useMemo(() => {
if (!data?.data) return [];
return data?.data.filter((task) => {
const { selectedBuilding, selectedFloors, selectedActivities } = filters;
if (
selectedBuilding &&
task?.workItem?.workArea?.floor?.building?.name !== selectedBuilding
)
return false;
if (
selectedFloors.length > 0 &&
!selectedFloors.includes(task?.workItem?.workArea?.floor?.floorName)
)
return false;
if (
selectedActivities.length > 0 &&
!selectedActivities.includes(
task?.workItem?.activityMaster?.activityName
)
)
return false;
return true;
});
}, [data?.data, filters, currentPage]);
const groupedTasks = useMemo(() => {
const groups = {};
filteredTasks.forEach((task) => {
const date = task.assignmentDate.split("T")[0];
if (!groups[date]) groups[date] = [];
groups[date].push(task);
});
return Object.keys(groups)
.sort((a, b) => new Date(b) - new Date(a))
.map((date) => ({ date, tasks: groups[date] }));
}, [filteredTasks, paginate, currentPage, selectedProject]);
const renderTeamMembers = (task, refIndex) => (
<div
key={refIndex}
tabIndex="0"
className="d-flex align-items-center avatar-group justify-content-center"
data-bs-toggle="popover"
data-bs-trigger="focus"
data-bs-placement="left"
data-bs-html="true"
data-bs-content={`
<div class="border border-secondary rounded custom-popover p-2 px-3">
${task.teamMembers
.map(
(m) => `
<div class="d-flex align-items-center gap-2 mb-2">
<div class="avatar avatar-xs">
<span class="avatar-initial rounded-circle bg-label-primary">
${m?.firstName?.charAt(0) || ""}${m?.lastName?.charAt(0) || ""
}
</span>
</div>
<span>${m.firstName} ${m.lastName}</span>
</div>`
)
.join("")}
</div>
`}
>
{task.teamMembers.slice(0, 3).map((m) => (
<div
key={m.id}
className="avatar avatar-xs"
title={`${m.firstName} ${m.lastName}`}
>
<span className="avatar-initial rounded-circle bg-label-primary">
{m?.firstName.slice(0, 1)}
</span>
</div>
))}
{task.teamMembers.length > 3 && (
<div
className="avatar avatar-xs"
title={`${task.teamMembers.length - 3} more`}
>
<span className="avatar-initial rounded-circle bg-label-secondary">
+{task.teamMembers.length - 3}
</span>
</div>
)}
</div>
);
if (isLoading) return <TaskReportListSkeleton />;
if (isError) return <div>Loading....</div>;
return (
<div className="mt-2 table-responsive text-nowrap">
<table className="table">
<thead>
<tr>
<th className="text-start">Activity</th>
<th>
<span>
Total Pending{" "}
<HoverPopup
title="Total Pending Task"
content={<p>This shows the total pending tasks for each activity on that date.</p>}
>
<i className="bx bx-xs ms-1 bx-info-circle cursor-pointer"></i>
</HoverPopup>
</span>
</th>
<th>
<span>
Reported/Planned{" "}
<HoverPopup
title="Reported and Planned Task"
content={<p>This shows the reported versus planned tasks for each activity on that date.</p>}
>
<i className="bx bx-xs ms-1 bx-info-circle cursor-pointer"></i>
</HoverPopup>
</span>
</th>
<th>Assign Date</th>
<th>Team</th>
<th className="text-center">Actions</th>
</tr>
</thead>
<tbody>
{groupedTasks.length === 0 && (
<tr>
<td colSpan={6} className="text-center align-middle" style={{ height: "200px", borderBottom: "none" }}>
No reports available
</td>
</tr>
)}
{groupedTasks.map(({ date, tasks }) => (
<React.Fragment key={date}>
<tr className="table-row-header text-start">
<td colSpan={6}>
<strong>{formatUTCToLocalTime(date)}</strong>
</td>
</tr>
{tasks.map((task, idx) => (
<tr key={task.id || idx}>
<td className="flex-wrap text-start">
<div>
{task.workItem.activityMaster?.activityName || "No Activity Name"}
</div>
<div className="text-sm py-2">
{task.workItem.workArea?.floor?.building?.name} {" "}
{task.workItem.workArea?.floor?.floorName} {" "}
{task.workItem.workArea?.areaName}
</div>
</td>
<td>
{formatNumber(task.workItem.plannedWork)}
</td>
<td>{`${formatNumber(task.completedTask)} / ${formatNumber(task.plannedTask)}`}</td>
<td>{formatUTCToLocalTime(task.assignmentDate)}</td>
<td className="text-center">{renderTeamMembers(task, idx)}</td>
<td className="text-center">
<div className="d-flex justify-content-end gap-2">
{ReportTaskRights && !task.reportedDate && (
<button className="btn btn-xs btn-primary" onClick={() => openModal("report", task)}>
Report
</button>
)}
{ApprovedTaskRights && task.reportedDate && !task.approvedBy && (
<button
className="btn btn-xs btn-warning"
onClick={() => openModal("comments", { task, isActionAllow: true })}
>
QC
</button>
)}
<button
className="btn btn-xs btn-primary"
onClick={() => openModal("comments", { task, isActionAllow: false })}
>
Comment
</button>
</div>
</td>
</tr>
))}
</React.Fragment>
))}
</tbody>
</table>
{data?.data?.length > 0 && (
<Pagination
currentPage={currentPage}
totalPages={data.totalPages}
onPageChange={paginate}
/>
)}
</div>
);
};
export default TaskReportList;

View File

@ -0,0 +1,62 @@
const SkeletonLine = ({ height = 20, width = "100%", className = "" }) => (
<div
className={`skeleton mb-2 ${className}`}
style={{
height,
width,
}}
></div>
);
export const TaskReportListSkeleton = () => {
const skeletonRows = 8; // Number of placeholder rows
return (
<div>
<table className="table">
<thead>
<tr>
<th>Activity</th>
<th>Assigned</th>
<th>Completed</th>
<th>Assign On</th>
<th>Team</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{[...Array(skeletonRows)].map((_, idx) => (
<tr key={idx}>
<td>
<SkeletonLine height={16} width="70%" />
<SkeletonLine height={12} width="50%" />
</td>
<td>
<SkeletonLine height={16} width="60%" />
</td>
<td>
<SkeletonLine height={16} width="60%" />
</td>
<td>
<SkeletonLine height={16} width="80%" />
</td>
<td className="text-center">
<div className="d-flex justify-content-center gap-1">
{[...Array(3)].map((_, i) => (
<SkeletonLine key={i} height={24} width={24} className="rounded-circle" />
))}
</div>
</td>
<td>
<div className="d-flex justify-content-end gap-2">
<SkeletonLine height={24} width="60px" />
<SkeletonLine height={24} width="60px" />
</div>
</td>
</tr>
))}
</tbody>
</table>
</div>
);
};

View File

@ -0,0 +1,17 @@
import { z } from "zod";
export const TaskReportFilterSchema = z.object({
buildingIds: z.array(z.string()).optional(),
floorIds: z.array(z.string()).optional(),
activityIds: z.array(z.string()).optional(),
dateFrom: z.string().optional(),
dateTo: z.string().optional(),
});
export const TaskReportDefaultValue = {
buildingIds:[],
floorIds:[],
activityIds:[],
dateFrom:null,
dateTo:null
}

View File

@ -1,194 +1,194 @@
import React, { useState, useEffect } from "react"; // import React, { useState, useEffect } from "react";
import LineChart from "../Charts/LineChart"; // import LineChart from "../Charts/LineChart";
import { useProjects } from "../../hooks/useProjects"; // import { useProjects } from "../../hooks/useProjects";
import { useDashboard_ActivityData } from "../../hooks/useDashboard_Data"; // import { useDashboard_ActivityData } from "../../hooks/useDashboard_Data";
import ApexChart from "../Charts/Circlechart"; // import ApexChart from "../Charts/Circlechart";
const LOCAL_STORAGE_PROJECT_KEY = "selectedActivityProjectId"; // const LOCAL_STORAGE_PROJECT_KEY = "selectedActivityProjectId";
const Activity = () => { // const Activity = () => {
const { projects } = useProjects(); // const { projects } = useProjects();
const today = new Date().toISOString().split("T")[0]; // Format: YYYY-MM-DD // const today = new Date().toISOString().split("T")[0]; // Format: YYYY-MM-DD
const [selectedDate, setSelectedDate] = useState(today); // const [selectedDate, setSelectedDate] = useState(today);
const storedProjectId = localStorage.getItem(LOCAL_STORAGE_PROJECT_KEY); // const storedProjectId = localStorage.getItem(LOCAL_STORAGE_PROJECT_KEY);
const initialProjectId = storedProjectId || "all"; // const initialProjectId = storedProjectId || "all";
const [selectedProjectId, setSelectedProjectId] = useState(initialProjectId); // const [selectedProjectId, setSelectedProjectId] = useState(initialProjectId);
const [displayedProjectName, setDisplayedProjectName] = useState("Select Project"); // const [displayedProjectName, setDisplayedProjectName] = useState("Select Project");
const [activeTab, setActiveTab] = useState("all"); // const [activeTab, setActiveTab] = useState("all");
const { dashboard_Activitydata: ActivityData, isLoading, error: isError } = // const { dashboard_Activitydata: ActivityData, isLoading, error: isError } =
useDashboard_ActivityData(selectedDate, selectedProjectId); // useDashboard_ActivityData(selectedDate, selectedProjectId);
useEffect(() => { // useEffect(() => {
if (selectedProjectId === "all") { // if (selectedProjectId === "all") {
setDisplayedProjectName("All Projects"); // setDisplayedProjectName("All Projects");
} else if (projects) { // } else if (projects) {
const foundProject = projects.find((p) => p.id === selectedProjectId); // const foundProject = projects.find((p) => p.id === selectedProjectId);
setDisplayedProjectName(foundProject ? foundProject.name : "Select Project"); // setDisplayedProjectName(foundProject ? foundProject.name : "Select Project");
} else { // } else {
setDisplayedProjectName("Select Project"); // setDisplayedProjectName("Select Project");
} // }
localStorage.setItem(LOCAL_STORAGE_PROJECT_KEY, selectedProjectId); // localStorage.setItem(LOCAL_STORAGE_PROJECT_KEY, selectedProjectId);
}, [selectedProjectId, projects]); // }, [selectedProjectId, projects]);
const handleProjectSelect = (projectId) => { // const handleProjectSelect = (projectId) => {
setSelectedProjectId(projectId); // setSelectedProjectId(projectId);
}; // };
const handleDateChange = (e) => { // const handleDateChange = (e) => {
setSelectedDate(e.target.value); // setSelectedDate(e.target.value);
}; // };
return ( // return (
<div className="card h-100"> // <div className="card h-100">
<div className="card-header"> // <div className="card-header">
<div className="d-flex flex-wrap justify-content-between align-items-center mb-0"> // <div className="d-flex flex-wrap justify-content-between align-items-center mb-0">
<div className="card-title mb-0 text-start"> // <div className="card-title mb-0 text-start">
<h5 className="mb-1">Activity</h5> // <h5 className="mb-1">Activity</h5>
<p className="card-subtitle">Activity Progress Chart</p> // <p className="card-subtitle text-primary">Activity Progress Chart</p>
</div> // </div>
<div className="btn-group"> // <div className="btn-group">
<button // <button
className="btn btn-outline-primary btn-sm dropdown-toggle" // className="btn btn-outline-primary btn-sm dropdown-toggle"
type="button" // type="button"
data-bs-toggle="dropdown" // data-bs-toggle="dropdown"
aria-expanded="false" // aria-expanded="false"
> // >
{displayedProjectName} // {displayedProjectName}
</button> // </button>
<ul className="dropdown-menu"> // <ul className="dropdown-menu">
<li> // <li>
<button className="dropdown-item" onClick={() => handleProjectSelect("all")}> // <button className="dropdown-item" onClick={() => handleProjectSelect("all")}>
All Projects // All Projects
</button> // </button>
</li> // </li>
{projects?.map((project) => ( // {projects?.map((project) => (
<li key={project.id}> // <li key={project.id}>
<button // <button
className="dropdown-item" // className="dropdown-item"
onClick={() => handleProjectSelect(project.id)} // onClick={() => handleProjectSelect(project.id)}
> // >
{project.name} // {project.name}
</button> // </button>
</li> // </li>
))} // ))}
</ul> // </ul>
</div> // </div>
</div> // </div>
</div> // </div>
{/* ✅ Date Picker Aligned Left with Padding */} // {/* Date Picker Aligned Left with Padding */}
<div className="d-flex justify-content-start ps-3 mb-3"> // <div className="d-flex justify-content-start ps-3 mb-3">
<div style={{ width: "150px" }}> // <div style={{ width: "150px" }}>
<input // <input
type="date" // type="date"
className="form-control" // className="form-control"
value={selectedDate} // value={selectedDate}
onChange={handleDateChange} // onChange={handleDateChange}
/> // />
</div> // </div>
</div> // </div>
{/* Tabs */} // {/* Tabs */}
<ul className="nav nav-tabs " role="tablist"> // <ul className="nav nav-tabs " role="tablist">
<li className="nav-item"> // <li className="nav-item">
<button // <button
type="button" // type="button"
className={`nav-link ${activeTab === "all" ? "active" : ""}`} // className={`nav-link ${activeTab === "all" ? "active" : ""}`}
onClick={() => setActiveTab("all")} // onClick={() => setActiveTab("all")}
data-bs-toggle="tab" // data-bs-toggle="tab"
> // >
Summary // Summary
</button> // </button>
</li> // </li>
<li className="nav-item"> // <li className="nav-item">
<button // <button
type="button" // type="button"
className={`nav-link ${activeTab === "logs" ? "active" : ""}`} // className={`nav-link ${activeTab === "logs" ? "active" : ""}`}
onClick={() => setActiveTab("logs")} // onClick={() => setActiveTab("logs")}
data-bs-toggle="tab" // data-bs-toggle="tab"
> // >
Details // Details
</button> // </button>
</li> // </li>
</ul> // </ul>
<div className="card-body"> // <div className="card-body">
{activeTab === "all" && ( // {activeTab === "all" && (
<div className="row justify-content-between"> // <div className="row justify-content-between">
<div className="col-md-6 d-flex flex-column align-items-center text-center mb-4"> // <div className="col-md-6 d-flex flex-column align-items-center text-center mb-4">
{isLoading ? ( // {isLoading ? (
<p>Loading activity data...</p> // <p>Loading activity data...</p>
) : isError ? ( // ) : isError ? (
<p>No data available.</p> // <p>No data available.</p>
) : ( // ) : (
ActivityData && ( // ActivityData && (
<> // <>
<h5 className="fw-bold mb-0 text-start w-80"> // <h5 className="fw-bold mb-0 text-start w-80">
<i className="bx bx-task text-info"></i> Allocated Task // <i className="bx bx-task text-info"></i> Allocated Task
</h5> // </h5>
<h4 className="mb-0 fw-bold"> // <h4 className="mb-0 fw-bold">
{ActivityData.totalCompletedWork?.toLocaleString()}/ // {ActivityData.totalCompletedWork?.toLocaleString()}/
{ActivityData.totalPlannedWork?.toLocaleString()} // {ActivityData.totalPlannedWork?.toLocaleString()}
</h4> // </h4>
<small className="text-muted">Completed / Assigned</small> // <small className="text-muted">Completed / Assigned</small>
<div style={{ maxWidth: "180px" }}> // <div style={{ maxWidth: "180px" }}>
<ApexChart /> // <ApexChart />
</div> // </div>
</> // </>
) // )
)} // )}
</div> // </div>
<div className="col-md-6 d-flex flex-column align-items-center text-center mb-4"> // <div className="col-md-6 d-flex flex-column align-items-center text-center mb-4">
{!isLoading && !isError && ActivityData && ( // {!isLoading && !isError && ActivityData && (
<> // <>
<h5 className="fw-bold mb-0 text-start w-110"> // <h5 className="fw-bold mb-0 text-start w-110">
<i className="bx bx-task text-info"></i> Activities // <i className="bx bx-task text-info"></i> Activities
</h5> // </h5>
<h4 className="mb-0 fw-bold"> // <h4 className="mb-0 fw-bold">
{ActivityData.totalCompletedWork?.toLocaleString()}/ // {ActivityData.totalCompletedWork?.toLocaleString()}/
{ActivityData.totalPlannedWork?.toLocaleString()} // {ActivityData.totalPlannedWork?.toLocaleString()}
</h4> // </h4>
<small className="text-muted ">Pending / Assigned</small> // <small className="text-muted ">Pending / Assigned</small>
<div style={{ maxWidth: "180px" }}> // <div style={{ maxWidth: "180px" }}>
<ApexChart /> // <ApexChart />
</div> // </div>
</> // </>
)} // )}
</div> // </div>
</div> // </div>
)} // )}
{activeTab === "logs" && ( // {activeTab === "logs" && (
<div className="table-responsive"> // <div className="table-responsive">
<table className="table table-bordered table-hover"> // <table className="table table-bordered table-hover">
<thead> // <thead>
<tr> // <tr>
<th>Activity / Location</th> // <th>Activity / Location</th>
<th>Assigned / Completed</th> // <th>Assigned / Completed</th>
</tr> // </tr>
</thead> // </thead>
<tbody> // <tbody>
{[{ // {[{
activity: "Code Review / Remote", // activity: "Code Review / Remote",
assignedToday: 3, // assignedToday: 3,
completed: 2 // completed: 2
}].map((log, index) => ( // }].map((log, index) => (
<tr key={index}> // <tr key={index}>
<td>{log.activity}</td> // <td>{log.activity}</td>
<td>{log.assignedToday} / {log.completed}</td> // <td>{log.assignedToday} / {log.completed}</td>
</tr> // </tr>
))} // ))}
</tbody> // </tbody>
</table> // </table>
</div> // </div>
)} // )}
</div> // </div>
</div> // </div>
); // );
}; // };
export default Activity; // export default Activity;

View File

@ -1,21 +1,16 @@
import React, { useState, useEffect } from "react"; import React, { useState, useMemo } from "react";
import LineChart from "../Charts/LineChart"; import ApexChart from "../Charts/Circle";
import { useProjects } from "../../hooks/useProjects"; import { useProjects } from "../../hooks/useProjects";
import { useDashboard_AttendanceData } from "../../hooks/useDashboard_Data"; import { useDashboard_AttendanceData } from "../../hooks/useDashboard_Data";
import ApexChart from "../Charts/Circle"; import { useSelectedProject } from "../../hooks/useSelectedProject"; // your custom hook
const LOCAL_STORAGE_PROJECT_KEY = "selectedAttendanceProjectId";
const Attendance = () => { const Attendance = () => {
const { projects } = useProjects(); const { projects } = useProjects();
const today = new Date().toISOString().split("T")[0]; // Format: YYYY-MM-DD const today = new Date().toISOString().split("T")[0]; // YYYY-MM-DD
const [selectedDate, setSelectedDate] = useState(today); const [selectedDate, setSelectedDate] = useState(today);
const storedProjectId = localStorage.getItem(LOCAL_STORAGE_PROJECT_KEY);
const initialProjectId = storedProjectId || "all"; // central project selection hook
const [selectedProjectId, setSelectedProjectId] = useState(initialProjectId); const selectedProjectId = useSelectedProject()
const [displayedProjectName, setDisplayedProjectName] =
useState("Select Project");
const [activeTab, setActiveTab] = useState("Summary");
const { const {
dashboard_Attendancedata: AttendanceData, dashboard_Attendancedata: AttendanceData,
@ -23,38 +18,24 @@ const Attendance = () => {
error: isError, error: isError,
} = useDashboard_AttendanceData(selectedDate, selectedProjectId); } = useDashboard_AttendanceData(selectedDate, selectedProjectId);
useEffect(() => { // project name derived once
if (selectedProjectId === "all") { const displayedProjectName = useMemo(() => {
setDisplayedProjectName("All Projects"); if (selectedProjectId === "all") return "All Projects";
} else if (projects) { const found = projects?.find((p) => p.id === selectedProjectId);
const foundProject = projects.find((p) => p.id === selectedProjectId); return found?.name || "Select Project";
setDisplayedProjectName(
foundProject ? foundProject.name : "Select Project"
);
} else {
setDisplayedProjectName("Select Project");
}
localStorage.setItem(LOCAL_STORAGE_PROJECT_KEY, selectedProjectId);
}, [selectedProjectId, projects]); }, [selectedProjectId, projects]);
const handleProjectSelect = (projectId) => {
setSelectedProjectId(projectId);
};
const handleDateChange = (e) => {
setSelectedDate(e.target.value);
};
return ( return (
<div className="card h-100"> <div className="card h-100">
<div className="card-header mb-1 pb-0 "> {/* Header */}
<div className="d-flex flex-wrap justify-content-between align-items-center mb-0 pb-0 "> <div className="card-header mb-1 pb-0">
<div className="d-flex flex-wrap justify-content-between align-items-center">
<div className="card-title mb-0 text-start"> <div className="card-title mb-0 text-start">
<h5 className="mb-1">Attendance</h5> <h5 className="mb-1">Attendance</h5>
<p className="card-subtitle">Daily Attendance Data</p> <p className="card-subtitle">Daily Attendance Data</p>
</div> </div>
{/* Project Dropdown */}
<div className="btn-group"> <div className="btn-group">
<button <button
className="btn btn-outline-primary btn-sm dropdown-toggle" className="btn btn-outline-primary btn-sm dropdown-toggle"
@ -68,7 +49,7 @@ const Attendance = () => {
<li> <li>
<button <button
className="dropdown-item" className="dropdown-item"
onClick={() => handleProjectSelect("all")} onClick={() => setSelectedProjectId("all")}
> >
All Projects All Projects
</button> </button>
@ -77,7 +58,7 @@ const Attendance = () => {
<li key={project.id}> <li key={project.id}>
<button <button
className="dropdown-item" className="dropdown-item"
onClick={() => handleProjectSelect(project.id)} onClick={() => setSelectedProjectId(project.id)}
> >
{project.name} {project.name}
</button> </button>
@ -88,52 +69,43 @@ const Attendance = () => {
</div> </div>
</div> </div>
<div className="d-flex flex-wrap justify-content-between align-items-center mb-0 mt-0 me-5 ms-5"> {/* Tabs + Date Picker */}
{/* Tabs */} <div className="d-flex flex-wrap justify-content-between align-items-center me-5 ms-5">
<div> <ul className="nav nav-tabs">
<ul className="nav nav-tabs " role="tablist"> <li className="nav-item">
<li className="nav-item"> <button
<button type="button"
type="button" className={`nav-link ${AttendanceData?.activeTab === "Summary" ? "active" : ""}`}
className={`nav-link ${ onClick={() => (AttendanceData.activeTab = "Summary")}
activeTab === "Summary" ? "active" : "" >
}`} Summary
onClick={() => setActiveTab("Summary")} </button>
data-bs-toggle="tab" </li>
> <li className="nav-item">
Summary <button
</button> type="button"
</li> className={`nav-link ${AttendanceData?.activeTab === "Details" ? "active" : ""}`}
<li className="nav-item"> onClick={() => (AttendanceData.activeTab = "Details")}
<button >
type="button" Details
className={`nav-link ${ </button>
activeTab === "Details" ? "active" : "" </li>
}`} </ul>
onClick={() => setActiveTab("Details")} <div className="ps-6 mb-3">
data-bs-toggle="tab" <input
> type="date"
Details className="form-control p-1"
</button> style={{ width: "120px" }}
</li> value={selectedDate}
</ul> onChange={(e) => setSelectedDate(e.target.value)}
</div> />
{/* ✅ Date Picker Aligned Left with Padding */}
<div className="ps-6 mb-3 mt-0">
<div style={{ width: "120px" }}>
<input
type="date"
className="form-control p-1"
// style={{ fontSize: "1rem" }}
value={selectedDate}
onChange={handleDateChange}
/>
</div>
</div> </div>
</div> </div>
{/* Body */}
<div className="card-body"> <div className="card-body">
{activeTab === "Summary" && ( {/* Summary */}
{AttendanceData?.activeTab === "Summary" && (
<div className="row justify-content-center"> <div className="row justify-content-center">
<div className="col-12 col-md-6 d-flex flex-column align-items-center text-center mb-4"> <div className="col-12 col-md-6 d-flex flex-column align-items-center text-center mb-4">
{isLoading ? ( {isLoading ? (
@ -143,7 +115,7 @@ const Attendance = () => {
) : ( ) : (
AttendanceData && ( AttendanceData && (
<> <>
<h5 className="fw-bold mb-0 text-center w-100"> <h5 className="fw-bold mb-0">
<i className="bx bx-task text-info"></i> Attendance <i className="bx bx-task text-info"></i> Attendance
</h5> </h5>
<h4 className="mb-0 fw-bold"> <h4 className="mb-0 fw-bold">
@ -164,11 +136,9 @@ const Attendance = () => {
</div> </div>
)} )}
{activeTab === "Details" && ( {/* Details */}
<div {AttendanceData?.activeTab === "Details" && (
className="table-responsive" <div className="table-responsive" style={{ maxHeight: "300px" }}>
style={{ maxHeight: "300px", overflowY: "auto" }}
>
<table className="table table-hover mb-0 text-start"> <table className="table table-hover mb-0 text-start">
<thead> <thead>
<tr> <tr>
@ -178,32 +148,17 @@ const Attendance = () => {
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{AttendanceData?.attendanceTable && {AttendanceData?.attendanceTable?.length ? (
AttendanceData.attendanceTable.length > 0 ? ( AttendanceData.attendanceTable.map((r, i) => (
AttendanceData.attendanceTable.map((record, index) => ( <tr key={i}>
<tr key={index}> <td>{r.firstName} {r.lastName}</td>
<td> <td>{r.inTime ? new Date(r.inTime).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }) : "-"}</td>
{record.firstName} {record.lastName} <td>{r.outTime ? new Date(r.outTime).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }) : "-"}</td>
</td>
<td>
{new Date(record.inTime).toLocaleTimeString([], {
hour: "2-digit",
minute: "2-digit",
})}
</td>
<td>
{new Date(record.outTime).toLocaleTimeString([], {
hour: "2-digit",
minute: "2-digit",
})}
</td>
</tr> </tr>
)) ))
) : ( ) : (
<tr> <tr>
<td colSpan="3" className="text-center"> <td colSpan="3" className="text-center">No attendance data available</td>
No attendance data available
</td>
</tr> </tr>
)} )}
</tbody> </tbody>

View File

@ -99,9 +99,7 @@ const AttendanceOverview = () => {
}; };
return ( return (
<div <div className="bg-white p-4 rounded shadow d-flex flex-column">
className="bg-white p-4 rounded shadow d-flex flex-column"
>
{/* Header */} {/* Header */}
<div className="d-flex justify-content-between align-items-center mb-3"> <div className="d-flex justify-content-between align-items-center mb-3">
<div className="card-title mb-0 text-start"> <div className="card-title mb-0 text-start">
@ -119,18 +117,22 @@ const AttendanceOverview = () => {
<option value={30}>Last 30 Days</option> <option value={30}>Last 30 Days</option>
</select> </select>
<button <button
className={`btn btn-sm ${view === "chart" ? "btn-primary" : "btn-outline-primary"}`} className={`btn btn-sm p-1 ${
view === "chart" ? "btn-primary" : "btn-outline-primary"
}`}
onClick={() => setView("chart")} onClick={() => setView("chart")}
title="Chart View" title="Chart View"
> >
<i className="bx bx-bar-chart-alt-2"></i> <i className="bx bx-bar-chart-alt-2"></i>
</button> </button>
<button <button
className={`btn btn-sm ${view === "table" ? "btn-primary" : "btn-outline-primary"}`} className={`btn btn-sm p-1 ${
view === "table" ? "btn-primary" : "btn-outline-primary"
}`}
onClick={() => setView("table")} onClick={() => setView("table")}
title="Table View" title="Table View"
> >
<i className="bx bx-task text-success"></i> <i className="bx bx-list-ul fs-5"></i>
</button> </button>
</div> </div>
</div> </div>

View File

@ -1,33 +1,28 @@
import React, { useCallback, useEffect, useState } from "react"; import React, { useEffect } from "react";
import { useDashboardProjectsCardData } from "../../hooks/useDashboard_Data"; import { useDashboardProjectsCardData } from "../../hooks/useDashboard_Data";
import eventBus from "../../services/eventBus"; import eventBus from "../../services/eventBus";
import GlobalRepository from "../../repositories/GlobalRepository";
const Projects = () => { const Projects = () => {
const { projectsCardData } = useDashboardProjectsCardData(); const {
const [projectData, setProjectsData] = useState(projectsCardData); data: projectsCardData,
isLoading,
isError,
error,
refetch,
} = useDashboardProjectsCardData();
useEffect(() => { useEffect(() => {
setProjectsData(projectsCardData); // When "project" event happens, just refetch
}, [projectsCardData]); const handler = () => {
refetch();
};
const handler = useCallback(
async (msg) => {
try {
const response =
await GlobalRepository.getDashboardProjectsCardData();
setProjectsData(response.data);
} catch (err) {
console.error(err);
}
},
[GlobalRepository]
);
useEffect(() => {
eventBus.on("project", handler); eventBus.on("project", handler);
return () => eventBus.off("project", handler); return () => eventBus.off("project", handler);
}, [handler]); }, [refetch]);
const totalProjects = projectsCardData?.totalProjects ?? 0;
const ongoingProjects = projectsCardData?.ongoingProjects ?? 0;
return ( return (
<div className="card p-3 h-100 text-center d-flex justify-content-between"> <div className="card p-3 h-100 text-center d-flex justify-content-between">
@ -37,20 +32,29 @@ const Projects = () => {
Projects Projects
</h5> </h5>
</div> </div>
<div className="d-flex justify-content-around align-items-start mt-n2">
<div> {isLoading ? (
<h4 className="mb-0 fw-bold"> <div className="d-flex justify-content-center align-items-center flex-grow-1">
{projectData.totalProjects?.toLocaleString()} <div className="spinner-border text-primary" role="status">
</h4> <span className="visually-hidden">Loading...</span>
<small className="text-muted">Total</small> </div>
</div> </div>
<div> ) : isError ? (
<h4 className="mb-0 fw-bold"> <div className="text-danger flex-grow-1 d-flex justify-content-center align-items-center">
{projectData.ongoingProjects?.toLocaleString()} {error?.message || "Error loading data"}
</h4>
<small className="text-muted">Ongoing</small>
</div> </div>
</div> ) : (
<div className="d-flex justify-content-around align-items-start mt-n2">
<div>
<h4 className="mb-0 fw-bold">{totalProjects.toLocaleString()}</h4>
<small className="text-muted">Total</small>
</div>
<div>
<h4 className="mb-0 fw-bold">{ongoingProjects.toLocaleString()}</h4>
<small className="text-muted">Ongoing</small>
</div>
</div>
)}
</div> </div>
); );
}; };

View File

@ -1,10 +1,16 @@
import React from "react"; import React from "react";
import { useSelector } from "react-redux"; import { useSelectedProject } from "../../slices/apiDataManager";
import { useDashboardTasksCardData } from "../../hooks/useDashboard_Data"; import { useDashboardTasksCardData } from "../../hooks/useDashboard_Data";
const TasksCard = () => { const TasksCard = () => {
const projectId = useSelector((store) => store.localVariables?.projectId); const projectId = useSelectedProject();
const { tasksCardData, loading, error } = useDashboardTasksCardData(projectId);
const {
data: tasksCardData,
isLoading,
isError,
error,
} = useDashboardTasksCardData(projectId);
return ( return (
<div className="card p-3 h-100 text-center d-flex justify-content-between"> <div className="card p-3 h-100 text-center d-flex justify-content-between">
@ -14,28 +20,30 @@ const TasksCard = () => {
</h5> </h5>
</div> </div>
{loading ? ( {isLoading ? (
// Loader will be displayed when loading is true // Loader while fetching
<div className="d-flex justify-content-center align-items-center flex-grow-1"> <div className="d-flex justify-content-center align-items-center flex-grow-1">
<div className="spinner-border text-primary" role="status"> <div className="spinner-border text-primary" role="status">
<span className="visually-hidden">Loading...</span> <span className="visually-hidden">Loading...</span>
</div> </div>
</div> </div>
) : error ? ( ) : isError ? (
// Error message if there's an error // Show error
<div className="text-danger flex-grow-1 d-flex justify-content-center align-items-center">{error}</div> <div className="text-danger flex-grow-1 d-flex justify-content-center align-items-center">
{error?.message || "Error loading data"}
</div>
) : ( ) : (
// Actual data when loaded successfully // Show data
<div className="d-flex justify-content-around align-items-start mt-n2"> <div className="d-flex justify-content-around align-items-start mt-n2">
<div> <div>
<h4 className="mb-0 fw-bold"> <h4 className="mb-0 fw-bold">
{tasksCardData?.totalTasks?.toLocaleString()} {tasksCardData?.totalTasks?.toLocaleString() ?? 0}
</h4> </h4>
<small className="text-muted">Total</small> <small className="text-muted">Total</small>
</div> </div>
<div> <div>
<h4 className="mb-0 fw-bold"> <h4 className="mb-0 fw-bold">
{tasksCardData?.completedTasks?.toLocaleString()} {tasksCardData?.completedTasks?.toLocaleString() ?? 0}
</h4> </h4>
<small className="text-muted">Completed</small> <small className="text-muted">Completed</small>
</div> </div>
@ -45,4 +53,4 @@ const TasksCard = () => {
); );
}; };
export default TasksCard; export default TasksCard;

View File

@ -1,33 +1,45 @@
import React, { useCallback, useEffect, useState } from "react"; import React, { useCallback, useEffect } from "react";
import { useSelector } from "react-redux"; import { useSelector } from "react-redux";
import { useDashboardTeamsCardData } from "../../hooks/useDashboard_Data"; import { useDashboardTeamsCardData } from "../../hooks/useDashboard_Data";
import eventBus from "../../services/eventBus"; import eventBus from "../../services/eventBus";
import { useQueryClient } from "@tanstack/react-query";
import { useSelectedProject } from "../../slices/apiDataManager";
const Teams = () => { const Teams = () => {
const projectId = useSelector((store) => store.localVariables?.projectId); const queryClient = useQueryClient();
const { teamsCardData, loading, error } = useDashboardTeamsCardData(projectId); const projectId = useSelectedProject()
const [totalEmployees, setTotalEmployee] = useState(0); const {
const [inToday, setInToday] = useState(0); data: teamsCardData,
isLoading,
// Update state when API data arrives isError,
useEffect(() => { error,
setTotalEmployee(teamsCardData?.totalEmployees || 0); } = useDashboardTeamsCardData(projectId);
setInToday(teamsCardData?.inToday || 0);
}, [teamsCardData]);
// Handle real-time updates via eventBus // Handle real-time updates via eventBus
const handler = useCallback((msg) => { const handler = useCallback(
if (msg.activity === 1) { (msg) => {
setInToday((prev) => prev + 1); if (msg.activity === 1) {
} queryClient.setQueryData(["dashboardTeams", projectId], (old) => {
}, []); if (!old) return old;
return {
...old,
inToday: (old.inToday || 0) + 1,
};
});
}
},
[queryClient, projectId]
);
useEffect(() => { useEffect(() => {
eventBus.on("attendance", handler); eventBus.on("attendance", handler);
return () => eventBus.off("attendance", handler); return () => eventBus.off("attendance", handler);
}, [handler]); }, [handler]);
const inToday = teamsCardData?.inToday ?? 0;
const totalEmployees = teamsCardData?.totalEmployees ?? 0;
return ( return (
<div className="card p-3 h-100 text-center d-flex justify-content-between"> <div className="card p-3 h-100 text-center d-flex justify-content-between">
<div className="d-flex justify-content-start align-items-center mb-3"> <div className="d-flex justify-content-start align-items-center mb-3">
@ -36,18 +48,17 @@ const Teams = () => {
</h5> </h5>
</div> </div>
{loading ? ( {isLoading ? (
// Blue spinner loader
<div className="d-flex justify-content-center align-items-center flex-grow-1"> <div className="d-flex justify-content-center align-items-center flex-grow-1">
<div className="spinner-border text-primary" role="status"> <div className="spinner-border text-primary" role="status">
<span className="visually-hidden">Loading...</span> <span className="visually-hidden">Loading...</span>
</div> </div>
</div> </div>
) : error ? ( ) : isError ? (
// Error message if data fetching fails <div className="text-danger flex-grow-1 d-flex justify-content-center align-items-center">
<div className="text-danger flex-grow-1 d-flex justify-content-center align-items-center">{error}</div> {error?.message || "Error loading data"}
</div>
) : ( ) : (
// Display data once loaded
<div className="d-flex justify-content-around align-items-start mt-n2"> <div className="d-flex justify-content-around align-items-start mt-n2">
<div> <div>
<h4 className="mb-0 fw-bold">{totalEmployees.toLocaleString()}</h4> <h4 className="mb-0 fw-bold">{totalEmployees.toLocaleString()}</h4>
@ -63,4 +74,4 @@ const Teams = () => {
); );
}; };
export default Teams; export default Teams;

View File

@ -0,0 +1,68 @@
import { useState, useEffect } from "react";
import EmployeeList from "./EmployeeList";
import { useAllEmployees } from "../../hooks/useEmployees";
import showToast from "../../services/toastService";
import { DirectoryRepository } from "../../repositories/DirectoryRepository";
import { useAssignEmpToBucket } from "../../hooks/useDirectory";
const AssignedBucket = ({ selectedBucket, handleClose }) => {
const { employeesList } = useAllEmployees(false);
const [selectedEmployees, setSelectedEmployees] = useState([]);
useEffect(() => {
if (selectedBucket) {
const preselected = employeesList
.filter((emp) => selectedBucket?.employeeIds?.includes(emp.employeeId))
.map((emp) => ({ ...emp, isActive: true }));
setSelectedEmployees(preselected);
}
}, [selectedBucket, employeesList]);
const { mutate: AssignEmployee, isPending } = useAssignEmpToBucket(() =>
handleClose()
);
const handleSubmit = async (e) => {
e.preventDefault();
const existingEmployeeIds = selectedBucket?.employeeIds || [];
const employeesToUpdate = selectedEmployees.filter((emp) => {
const isExisting = existingEmployeeIds.includes(emp.employeeId);
return (!isExisting && emp.isActive) || (isExisting && !emp.isActive);
});
if (employeesToUpdate.length === 0) {
showToast("No changes to update", "info");
return;
}
AssignEmployee({
bucketId: selectedBucket.id,
EmployeePayload: employeesToUpdate.map((emp) => ({
employeeId: emp.employeeId,
isActive: emp.isActive,
})),
});
};
return (
<form onSubmit={handleSubmit}>
<EmployeeList
employees={employeesList}
bucket={selectedBucket}
selectedEmployees={selectedEmployees}
onChange={setSelectedEmployees}
/>
<div className="mt-3 d-flex justify-content-end gap-3">
<button type="submit" className="btn btn-sm btn-primary" disabled={isPending}>
{isPending ? "Please Wait...":"Assign"}
</button>
</div>
</form>
);
};
export default AssignedBucket;

View File

@ -0,0 +1,95 @@
import { useEffect } from "react";
import { useForm } from "react-hook-form";
import { bucketScheam } from "./DirectorySchema";
import { zodResolver } from "@hookform/resolvers/zod";
import Label from "../common/Label";
const BucketForm = ({ selectedBucket, mode, onSubmit, onCancel, isPending }) => {
const {
register,
handleSubmit,
reset,
formState: { errors },
} = useForm({
resolver: zodResolver(bucketScheam),
defaultValues: selectedBucket || { name: "", description: "" },
});
useEffect(() => {
reset(selectedBucket || { name: "", description: "" });
}, [selectedBucket, reset]);
const isEditMode = mode === "edit";
const isCreateMode = mode === "create";
return (
<div className="row">
<div className="d-flex justify-content-between align-items-center">
<i
className="bx bx-left-arrow-alt bx-md cursor-pointer"
onClick={onCancel}
></i>
{/* Show edit toggle only for existing bucket in edit mode */}
{/* {isEditMode && (
<i className="bx bx-edit bx-sm text-primary cursor-pointer"></i>
)} */}
</div>
{(isCreateMode || isEditMode) ? (
<form onSubmit={handleSubmit(onSubmit)}>
<div className="mb-3 mt-5">
<Label htmlFor="Name" className="text-start" required>
Name
</Label>
<input
className="form-control form-control-sm"
{...register("name")}
/>
{errors.name && (
<small className="danger-text">{errors.name.message}</small>
)}
</div>
<div className="mb-3">
<Label htmlFor="description" className="text-start" required>
Description
</Label>
<textarea
className="form-control form-control-sm"
{...register("description")}
rows="3"
/>
{errors.description && (
<small className="danger-text">{errors.description.message}</small>
)}
</div>
<div className="mt-4 mb-3 d-flex gap-3 justify-content-end">
<button
type="button"
className="btn btn-sm btn-label-secondary"
onClick={onCancel}
disabled={isPending}
>
Cancel
</button>
<button type="submit" className="btn btn-sm btn-primary" disabled={isPending}>
{isPending ? "Please Wait " : isEditMode ? "Update" : "Create"}
</button>
</div>
</form>
) : (
<dl className="row text-start my-2">
<dt className="col-sm-2">Name</dt>
<dd className="col-sm-10">{selectedBucket?.name || "-"}</dd>
<dt className="col-sm-2">Description</dt>
<dd className="col-sm-10">{selectedBucket?.description || "-"}</dd>
</dl>
)}
</div>
);
};
export default BucketForm;

View File

@ -0,0 +1,53 @@
import { useHasUserPermission } from "../../hooks/useHasUserPermission";
import { useProfile } from "../../hooks/useProfile";
import { DIRECTORY_ADMIN, DIRECTORY_MANAGER } from "../../utils/constants";
const BucketList = ({ buckets, loading, searchTerm, onEdit, onDelete }) => {
const { profile } = useProfile();
const IsDirecrory_Admin = useHasUserPermission(DIRECTORY_ADMIN);
const IsDirectory_Manager = useHasUserPermission(DIRECTORY_MANAGER);
const sorted = buckets.filter((bucket) =>
bucket.name.toLowerCase().includes(searchTerm.toLowerCase())
);
if (loading) return <div>Loading...</div>;
if (!loading && sorted.length === 0) return <div>No buckets found</div>;
return (
<div className="row row-cols-1 row-cols-md-2 row-cols-lg-3 g-3 pt-3 px-2 px-sm-0">
{sorted.map((bucket) => (
<div className="col" key={bucket.id}>
<div className="card h-100">
<div className="card-body p-4">
<h6 className="card-title d-flex justify-content-between">
<span>{bucket.name}</span>
{(IsDirecrory_Admin ||
IsDirectory_Manager ||
bucket?.createdBy?.id === profile?.employeeInfo?.id) && (
<div className="d-flex gap-2">
<i
className="bx bx-edit bx-sm text-primary cursor-pointer"
onClick={() => onEdit(bucket)}
/>
<i
className="bx bx-trash bx-sm text-danger cursor-pointer"
onClick={() => onDelete(bucket?.id)}
/>
</div>
)}
</h6>
<h6 className="card-subtitle mb-2 text-muted">
Contacts: {bucket.numberOfContacts || 0}
</h6>
<p className="card-text">
{bucket.description || "No description"}
</p>
</div>
</div>
</div>
))}
</div>
);
};
export default BucketList;

View File

@ -0,0 +1,217 @@
import React, { useState } from "react";
import Avatar from "../common/Avatar";
import { getBucketNameById } from "./DirectoryUtils";
import { useActiveInActiveContact, useBuckets } from "../../hooks/useDirectory";
import { getPhoneIcon } from "./DirectoryUtils";
import { useDir } from "../../Context/DireContext";
import { useDirectoryContext } from "../../pages/Directory/DirectoryPage";
import ConfirmModal from "../common/ConfirmModal";
const CardViewContact = ({
IsActive,
contact,
setSelectedContact,
setIsOpenModal,
setOpen_contact,
setIsOpenModalNote,
IsDeleted,
restore,
}) => {
const { data, setManageContact, setContactOpen } = useDirectoryContext();
const [IsDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
const { mutate: ActiveInActive, isPending } = useActiveInActiveContact();
const handleActiveInactive = (contactId) => {
ActiveInActive({ contactId, contactStatus: !IsActive });
};
return (
<>
<ConfirmModal
type="delete"
header="Delete Contact"
message="Are you sure you want delete?"
onSubmit={handleActiveInactive}
onClose={() => setIsDeleteModalOpen(false)}
loading={isPending}
paramData={contact.id}
isOpen={IsDeleteModalOpen}
/>
<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) {
setContactOpen({ contact: contact, Open: true });
}
}}
>
<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={() =>
setManageContact({
isOpen: true,
contactId: contact?.id,
})
}
>
<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={() => setIsDeleteModalOpen(true)}
>
<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 ${
isPending ? "bx-loader-alt bx-spin" : "bx-recycle"
} me-1 text-primary cursor-pointer`}
title="Restore"
onClick={() => handleActiveInactive(contact.id)}
></i>
)}
</div>
</div>
<ul className="list-inline m-0 ps-4 d-flex align-items-start">
<li className="list-inline-item text-break small px-1 ms-5">
{contact?.organization}
</li>
</ul>
</div>
<div
className={`card-footer text-start px-9 py-1 ${
IsActive && "cursor-pointer"
}`}
onClick={() => {
if (IsActive) {
setIsOpenModalNote(true);
setOpen_contact(contact);
}
}}
>
<hr className="my-0" />
{contact?.designation && (
<ul className="list-unstyled my-1 d-flex align-items-start ms-2">
<li className="me-2">
<i className="fa-solid fa-id-badge ms-1"></i>
</li>
<li className="flex-grow-1 text-break small">
{contact.designation}
</li>
</ul>
)}
{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?.tags?.length > 0 ? (
<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>
{contact.tags.map((tag, index) => (
<li key={index} className="list-inline-item text-small active">
{tag.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(data, bucketId)}
</span>
</span>
</li>
))}
</ul>
</div>
</div>
</>
);
};
export default CardViewContact;

Some files were not shown because too many files have changed in this diff Show More