Compare commits

...

124 Commits

Author SHA1 Message Date
Pramod Mahajan
bcb273d663 Merge branch 'Feature_Task_Management' of https://git.marcoaiot.com/admin/marco.pms.web into Feature_Task_Management 2025-04-16 00:19:30 +05:30
Pramod Mahajan
b73a7fc5ab added validation for end Date, end date should be greater than start date 2025-04-16 00:16:53 +05:30
85cae0a786 Merge pull request 'Vaibhav_Project_Details_Task-#73' (#11) from Vaibhav_Project_Details_Task-#73 into Feature_Task_Management
Reviewed-on: #11
2025-04-15 12:51:52 +00:00
8b8a518b12 replased nav naming and removed duplicate data 2025-04-15 18:10:17 +05:30
47bbb38683 refactor: clean up AboutProject component by removing unused contact fields 2025-04-15 18:07:47 +05:30
82e52e1937 Merge pull request 'update icon for Activities and rename User Management to Administration; add Inventory menu item' (#10) from Vaibhav_Sidebar_Task-#70 into Feature_Task_Management
Reviewed-on: #10
2025-04-15 12:12:13 +00:00
Pramod Mahajan
7ebd8dcc00 after assigned employee and submit, model will close 2025-04-15 17:26:16 +05:30
Pramod Mahajan
ab1754aa49 project list display status wise 2025-04-15 17:23:33 +05:30
b526e64a65 remove: delete Inventory menu item from Projects submenu 2025-04-15 17:23:19 +05:30
89df1fd9c0 update icon for Activities and rename User Management to Administration; add Inventory menu item 2025-04-15 17:20:39 +05:30
af7001f563 Merge pull request 'Vaibhav_Dashboard_Enhancement' (#9) from Vaibhav_Dashboard_Enhancement into Feature_Task_Management
Reviewed-on: #9
2025-04-15 11:42:45 +00:00
4e654444a8 add: enable legend display with reduced font size in HorizontalBarChart 2025-04-15 16:49:05 +05:30
c05f3aa0a1 fix: update card subtitle to better reflect project completion status 2025-04-15 16:43:45 +05:30
dd697b47b5 add line chart date formatting based on sorted dashboard data 2025-04-15 16:38:26 +05:30
9a5bf23712 refactor: remove unnecessary class from time range buttons in Dashboard 2025-04-15 16:29:22 +05:30
b57b46440d add font size adjustment for chart labels in HorizontalBarChart 2025-04-15 16:29:11 +05:30
19df5a55ff dynamically adjust chart height and bar height based on data points in HorizontalBarChart 2025-04-15 15:36:52 +05:30
Pramod Mahajan
30a9908499 changed Task Report UI 2025-04-15 02:58:01 +05:30
Pramod Mahajan
d1c36dfd97 Changed 'Role' to 'Application Role' 2025-04-15 01:08:59 +05:30
Pramod Mahajan
57272eb894 chanaged activityName validation msg 2025-04-15 01:02:15 +05:30
Pramod Mahajan
5f094aa2e6 added CreateActivity and EditActivity components. 2025-04-15 00:36:20 +05:30
Pramod Mahajan
6df20de749 it will take default range date, initially whenever component mount 2025-04-14 22:53:38 +05:30
Pramod Mahajan
ee2762fb4a removed console 2025-04-14 22:51:58 +05:30
Pramod Mahajan
97606dfef0 added 'checkLists' array inside hiddenColumn 2025-04-14 20:15:39 +05:30
Pramod Mahajan
23223661f0 removed console 2025-04-14 20:14:25 +05:30
Pramod Mahajan
2d57ab23da removed console and unnecessary hook 2025-04-14 20:10:30 +05:30
Pramod Mahajan
409c80471a removed href-# 2025-04-14 20:07:59 +05:30
Pramod Mahajan
a100194508 Merge branch 'Feature_Task_Management' of https://git.marcoaiot.com/admin/marco.pms.web into Feature_Task_Management 2025-04-14 20:05:40 +05:30
Pramod Mahajan
d421355451 added to tooltip in projectCard view more button 2025-04-14 20:05:16 +05:30
a39002ccf3 Merge branch 'Feature_Task_Management' of https://git.marcoaiot.com/admin/marco.pms.web into Feature_Task_Management 2025-04-14 18:00:02 +05:30
87c110a6af Add UI changes for dashboard.
- Reduce dropdown font
- sort project list
2025-04-14 17:59:43 +05:30
d82a788350 Merge branch 'Feature_Task_Management' of https://git.marcoaiot.com/admin/marco.pms.web into Feature_Task_Management 2025-04-14 17:58:23 +05:30
4f67d58923 removed cache handling. 2025-04-14 17:57:00 +05:30
Pramod Mahajan
777b8d8d0b set up deafult dates 2025-04-14 17:30:29 +05:30
Pramod Mahajan
c83161d5ab Breadcrumb changed 2025-04-14 17:23:36 +05:30
Pramod Mahajan
dd40f55b0a report button wil hide after reported 2025-04-14 17:14:29 +05:30
Pramod Mahajan
2a873f5d30 Merge branch 'Feature_Task_Management' of https://git.marcoaiot.com/admin/marco.pms.web into Feature_Task_Management 2025-04-14 16:49:41 +05:30
Pramod Mahajan
e9d3a746aa updated address character length 150 to 500 2025-04-14 16:49:14 +05:30
ca592fe9d7 Merge pull request 'Vaibhav_Chart_Enhancement-#61' (#8) from Vaibhav_Chart_Enhancement-#61 into Feature_Task_Management
Reviewed-on: #8
2025-04-14 06:55:13 +00:00
9c47b05d64 update HorizontalBarChart styles: change data label position to top and add border radius 2025-04-14 11:59:51 +05:30
aa70c5d1da enhance LineChart options and improve Dashboard date handling 2025-04-14 11:59:36 +05:30
Pramod Mahajan
63b02db9b3 added FromData to endDate filter in Task list 2025-04-13 17:27:51 +05:30
Pramod Mahajan
4c2fbf7bc6 made sepearte dataPickers 2025-04-13 17:26:36 +05:30
Pramod Mahajan
c459ef678e updated css properites 2025-04-13 17:25:29 +05:30
Pramod Mahajan
a52b5ef1d2 added timpicker css file link 2025-04-13 17:24:01 +05:30
Pramod Mahajan
1151c81c99 Merge branch 'Feature_Task_Management' of https://git.marcoaiot.com/admin/marco.pms.web into Feature_Task_Management 2025-04-11 17:55:16 +05:30
Pramod Mahajan
40843489d3 changed model size 2025-04-11 17:54:46 +05:30
Pramod Mahajan
ff5a772067 api integrated 2025-04-11 17:39:02 +05:30
b4be5a38ab refactor(Dashboard): rename loading state variable for clarity in progression data hook 2025-04-11 17:23:06 +05:30
99a4cb0553 feat(Dashboard): integrate loading state for line chart and fix data mapping 2025-04-11 17:22:56 +05:30
a30350916e feat(LineChart): add loading state to display spinner while loading data 2025-04-11 17:22:51 +05:30
d8a861bb08 feat(HorizontalBarChart): add loading state and improve label positioning 2025-04-11 17:22:45 +05:30
2b24351316 Merge pull request 'vaibhav_dashboard_feature' (#7) from vaibhav_dashboard_feature into Feature_Task_Management
Reviewed-on: #7
2025-04-11 10:39:13 +00:00
895e3da219 refactor(Dashboard): enhance project data handling and improve chart integration 2025-04-11 15:45:08 +05:30
02b0c4ccab fix(LineChart): enable x-axis labels visibility and adjust styling 2025-04-11 15:45:02 +05:30
12aa5c1491 refactor HorizontalBarChart: streamline data handling and improve label visibility 2025-04-11 15:44:56 +05:30
51190b0d53 add dashboard data hooks and global repository for API interactions 2025-04-11 15:44:51 +05:30
Pramod Mahajan
f4a09c7f53 removed max validation for completedTask 2025-04-11 15:34:46 +05:30
Pramod Mahajan
ec80121621 increased length of address field - 250 2025-04-11 15:33:11 +05:30
Pramod Mahajan
c2b91b9737 uncomment api 2025-04-11 15:30:15 +05:30
Pramod Mahajan
a7421eb6dc removed fetchActivity function , activity was needed for projectInfra, that can pass their child component TaskModel. instead of getting activity from ProjectInfra, Now TaskModel can directly calling to useMaster-Activity 2025-04-11 15:26:50 +05:30
Pramod Mahajan
8608043fb2 added unmount function that can be false loading flag 2025-04-11 15:21:01 +05:30
Pramod Mahajan
55b9420b6c Activity master component made and is reday for api integration. 2025-04-11 15:13:08 +05:30
Pramod Mahajan
2428f15f35 integrated activity api and fetch in master seaction. 2025-04-10 03:03:38 +05:30
Pramod Mahajan
4a5d9c05e6 set checkin-out button side right 2025-04-10 02:50:33 +05:30
Pramod Mahajan
60232cf121 added refetch fun 2025-04-10 02:49:05 +05:30
Pramod Mahajan
9e32986969 remove debugger 2025-04-10 02:47:51 +05:30
Pramod Mahajan
aa6ddd7fe9 remove regularization request after approve or reject. 2025-04-10 02:46:54 +05:30
Pramod Mahajan
c45130b611 updated project-infra-each modelForm able to taking recently added item 2025-04-10 02:07:47 +05:30
67a3648227 Update Dashboard component: change label from "In Role" to "In Today" 2025-04-09 18:07:30 +05:30
cb3abe4831 Update chart components: adjust bar height in HorizontalBarChart and set stroke width in LineChart; enhance Dashboard card layout with icons and format numbers 2025-04-09 17:56:52 +05:30
c0477285e3 Refactor Dashboard component: replace old implementation with new charts and project overview cards 2025-04-09 17:37:40 +05:30
d0ab36799f Add HorizontalBarChart and LineChart components; update package dependencies 2025-04-09 17:37:27 +05:30
Pramod Mahajan
d4452ae19a fixed refresh-token error and changed forgot password text position. 2025-04-09 16:22:03 +05:30
29caa20250 Card changes to show days left for project 2025-04-09 14:44:57 +05:30
19abc42fc6 Merge branch 'Feature_Task_Management' of https://git.marcoaiot.com/admin/marco.pms.web into Feature_Task_Management 2025-04-09 14:33:39 +05:30
bc516b58b6 Changed coming soon font size 2025-04-09 14:33:32 +05:30
Pramod Mahajan
308ac5af48 updated a project status in numbers on projectCard. 2025-04-09 13:57:08 +05:30
Pramod Mahajan
05a3b13a0e Merge branch 'Feature_Task_Management' of https://git.marcoaiot.com/admin/marco.pms.web into Feature_Task_Management 2025-04-09 13:44:40 +05:30
Pramod Mahajan
85c2401514 fixed progress bar and team size on project Card 2025-04-09 13:43:34 +05:30
43673cfd37 Fixed project modify error in project card 2025-04-09 13:33:54 +05:30
b1fd691f37 Add email in employee list 2025-04-09 13:15:11 +05:30
1019e9a32f add ComingSoonPage component and integrate it into EmployeeProfile for placeholder content 2025-04-09 12:03:16 +05:30
Pramod Mahajan
0e7088b4d4 Merge branch 'Feature_Task_Management' of https://git.marcoaiot.com/admin/marco.pms.web into Feature_Task_Management 2025-04-09 12:01:07 +05:30
Pramod Mahajan
797ff9b5bc removed MISC and changed login page logo url 2025-04-09 12:00:50 +05:30
68442bdc7c comment out unused functions and dropdown in Sidebar component 2025-04-09 11:50:44 +05:30
95f4443338 refactor ProjectCard component to improve state management and error handling 2025-04-09 11:50:29 +05:30
Pramod Mahajan
6a6f0356bc remove regularization method and created new component 2025-04-09 11:36:49 +05:30
Pramod Mahajan
1483cab13f fixed attendance logs and role name 2025-04-09 11:35:53 +05:30
Pramod Mahajan
453c53940f Merge branch 'Feature_Task_Management' of https://git.marcoaiot.com/admin/marco.pms.web into Feature_Task_Management 2025-04-09 11:28:20 +05:30
Pramod Mahajan
faf68cac25 fixed attendance logs, attendanance rolename and removed import-userProfile. 2025-04-09 11:23:34 +05:30
1f5d4688bb Fixed error of api call before submiting create form 2025-04-09 11:08:39 +05:30
Pramod Mahajan
e8aa2ae718 added close fun for addFloor model 2025-04-08 18:32:28 +05:30
Pramod Mahajan
57cf750d62 Merge branch 'Feature_Task_Management' of https://git.marcoaiot.com/admin/marco.pms.web into Feature_Task_Management 2025-04-08 18:22:14 +05:30
Pramod Mahajan
d56ba89a4a fixed regularization bugs- past employee is not able to regularized beacuse, id not sending to server 2025-04-08 18:21:59 +05:30
999ebe1623 Merge pull request 'Enhance email validation and fix spelling errors in address fields' (#6) from vaibhav_Bug-#26 into Feature_Task_Management
Reviewed-on: #6
2025-04-08 12:33:30 +00:00
9d9569460a Enhance email validation and fix spelling errors in address fields 2025-04-08 12:33:30 +00:00
079dfb8861 Merge pull request 'Refactor project status options and remove "Suspended" status' (#5) from vaibhav_bug_Issue-#50 into Feature_Task_Management
Reviewed-on: #5
2025-04-08 12:32:56 +00:00
d9e1c91a6b Refactor project status options and remove "Suspended" status 2025-04-08 16:10:42 +05:30
Pramod Mahajan
9c0f98dbaf Merge branch 'Feature_Task_Management' of https://git.marcoaiot.com/admin/marco.pms.web into Feature_Task_Management 2025-04-08 16:00:11 +05:30
5559970a05 Merge pull request 'Ashutosh_Daily_Task_Planning_Issues' (#4) from Ashutosh_Daily_Task_Planning_Issues into Feature_Task_Management
Reviewed-on: #4
2025-04-08 09:54:02 +00:00
a78c4b90db Fixed the error "project not found" in daily task planning 2025-04-08 09:54:02 +00:00
f9c6d1b7f8 Fixed the error "project not found" in daily task planning 2025-04-08 09:54:02 +00:00
147342bfc1 Merge pull request 'Refactor employee action dropdown for better accessibility and code clarity' (#3) from vaibhav_bug_Issue-#49 into Feature_Task_Management
Reviewed-on: #3
2025-04-08 07:53:22 +00:00
c989f23106 Refactor employee action dropdown for better accessibility and code clarity 2025-04-08 07:53:22 +00:00
14ca1eb54c Merge pull request 'Vaibhav_Resolved_Issues' (#2) from Vaibhav_Resolved_Issues into Feature_Task_Management
Reviewed-on: #2
2025-04-08 07:53:03 +00:00
085176c593 update "Not Data Found" message to "No Data Found" with improved styling 2025-04-08 07:53:03 +00:00
cee8f23fd9 enhance modal handling by adding onClose prop to TaskModel and WorkAreaModel components 2025-04-08 07:53:03 +00:00
Pramod Mahajan
df989605b9 Merge branch 'Feature_Task_Management' of https://git.marcoaiot.com/admin/marco.pms.web into Feature_Task_Management 2025-04-08 13:03:12 +05:30
5995c74ae6 change class= to className= 2025-04-08 09:37:38 +05:30
984efe207b Reduce font size on menu and project infra 2025-04-07 22:11:38 +05:30
8888f9d2d8 Merge pull request 'Vaibhav_Resolved_Issues' (#1) from Vaibhav_Resolved_Issues into Feature_Task_Management
Reviewed-on: #1
2025-04-07 16:25:50 +00:00
66c4e44ded updated Connect, Documentation, and Support pages with "Coming Soon!" message and illustration 2025-04-07 16:25:12 +00:00
f2a77b0685 added "Coming Soon!" message and illustration to multiple components 2025-04-07 16:25:12 +00:00
c595930640 refactor: streamline modal handling and improve floor management functionality 2025-04-07 16:25:12 +00:00
6c0b92606a updated links for Daily Expenses and Application Users in menuData.json 2025-04-07 16:25:12 +00:00
Pramod Mahajan
9e17b74ba4 changed font size and removed console. 2025-04-07 18:44:19 +05:30
Pramod Mahajan
a28d290feb hide or visible password 2025-04-07 17:17:21 +05:30
Pramod Mahajan
8bce23d9b6 added hid and visible functionality for password field. 2025-04-07 17:16:18 +05:30
Pramod Mahajan
2081b13d64 removed space between check and view button 2025-04-07 17:15:17 +05:30
Pramod Mahajan
93c95e007a integrated create Task api and handling data caching updated for task. 2025-04-07 17:14:07 +05:30
Pramod Mahajan
10d6f96ea7 added taskreport, commentTask feature 2025-04-06 18:03:10 +05:30
Pramod Mahajan
ddfbed1020 updated project infra components and added assigntask 2025-04-05 13:10:24 +05:30
Pramod Mahajan
1451c1aff7 added login and forgot form validation and changed server response format 2025-04-03 17:51:59 +05:30
105 changed files with 7866 additions and 4115 deletions

View File

@ -27,7 +27,6 @@
<link rel="stylesheet" href="/assets/css/default.css" />
<link rel="stylesheet" href="/assets/vendor/libs/perfect-scrollbar/perfect-scrollbar.css" />
<link rel="stylesheet" href="/assets/vendor/libs/apex-charts/apex-charts.css" />
<!-- Icons -->
<link rel="stylesheet" href="/assets/vendor/fonts/boxicons.css" />
@ -46,9 +45,11 @@
<!-- Helpers -->
<script src="/assets/vendor/js/helpers.js"></script>
<script src="/assets/js/config.js"></script>
<!-- Timer Picker -->
<!-- Flatpickr CSS -->
<link rel="stylesheet" href="/assets/vendor/libs/flatpickr/flatpickr.css" />
<link rel="stylesheet" href="./src/assets/vendor/libs/jquery-timepicker/jquery-timepicker.css" />
@ -70,21 +71,20 @@
<script src="/assets/vendor/libs/select2/select2.js"></script>
<script src="/assets/vendor/js/menu.js"></script>
<!-- endbuild -->
<!-- Vendors JS -->
<script src="/assets/vendor/libs/bs-stepper/bs-stepper.js"></script>
<script src="/assets/vendor/libs/bootstrap-select/bootstrap-select.js"></script>
<script src="/assets/vendor/libs/select2/select2.js"></script>
<script src="/assets/vendor/libs/apex-charts/apexcharts.js"></script>
<script src="/assets/vendor/libs/jquery-timepicker/jquery-timepicker.js" ></script>
<!-- <script src="/assets/vendor/libs/jquery-timepicker/jquery-timepicker.js" ></script> -->
<script src="/assets/vendor/libs/flatpickr/flatpickr.js"></script>
<!-- Main JS -->
<script src="/assets/js/main.js"></script>
@ -93,26 +93,15 @@
<script src="/assets/js/dashboards-analytics.js"></script>
<!-- component -->
<script src="./public/js/timppick.js"></script>
<script src="/assets/vendor/libs/sweetalert2/sweetalert2.js" ></script>
<!-- <script src="./public/js/timppick.js"></script> -->
<script src="/assets/vendor/libs/sweetalert2/sweetalert2.js"></script>
<!-- <script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.10.2/dist/umd/popper.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/js/bootstrap.min.js"></script> -->
<!-- Flatpickr JS -->
<script src="https://cdn.jsdelivr.net/npm/flatpickr"></script>
<!-- Flatpickr JS -->
<script>
// Initialize flatpickr for 12-hour time format with AM/PM
flatpickr("#timePicker", {
enableTime: true,
noCalendar: true,
time_24hr: false, // Disable 24-hour format
dateFormat: "h:i K", // 12-hour format with AM/PM
});
</script>
</body>
</html>

102
package-lock.json generated
View File

@ -10,7 +10,9 @@
"dependencies": {
"@hookform/resolvers": "^3.10.0",
"@reduxjs/toolkit": "^2.5.0",
"@types/web": "^0.0.216",
"@vitejs/plugin-react": "^4.3.4",
"apexcharts": "^4.5.0",
"axios": "^1.7.9",
"axios-retry": "^4.5.0",
"dotenv": "^16.4.7",
@ -21,6 +23,7 @@
"moment": "^2.30.1",
"perfect-scrollbar": "^1.5.5",
"react": "^18.2.0",
"react-apexcharts": "^1.7.0",
"react-dom": "^18.2.0",
"react-hook-form": "^7.54.2",
"react-redux": "^9.2.0",
@ -1115,6 +1118,62 @@
"win32"
]
},
"node_modules/@svgdotjs/svg.draggable.js": {
"version": "3.0.6",
"resolved": "https://registry.npmjs.org/@svgdotjs/svg.draggable.js/-/svg.draggable.js-3.0.6.tgz",
"integrity": "sha512-7iJFm9lL3C40HQcqzEfezK2l+dW2CpoVY3b77KQGqc8GXWa6LhhmX5Ckv7alQfUXBuZbjpICZ+Dvq1czlGx7gA==",
"license": "MIT",
"peerDependencies": {
"@svgdotjs/svg.js": "^3.2.4"
}
},
"node_modules/@svgdotjs/svg.filter.js": {
"version": "3.0.9",
"resolved": "https://registry.npmjs.org/@svgdotjs/svg.filter.js/-/svg.filter.js-3.0.9.tgz",
"integrity": "sha512-/69XMRCDoam2HgC4ldHIaDgeQf1ViHIsa0Ld4uWgiXtZ+E24DWHe/9Ib6kbNiZ7WRIdlVokUDR1Fg0kjIpkfbw==",
"license": "MIT",
"dependencies": {
"@svgdotjs/svg.js": "^3.2.4"
},
"engines": {
"node": ">= 0.8.0"
}
},
"node_modules/@svgdotjs/svg.js": {
"version": "3.2.4",
"resolved": "https://registry.npmjs.org/@svgdotjs/svg.js/-/svg.js-3.2.4.tgz",
"integrity": "sha512-BjJ/7vWNowlX3Z8O4ywT58DqbNRyYlkk6Yz/D13aB7hGmfQTvGX4Tkgtm/ApYlu9M7lCQi15xUEidqMUmdMYwg==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/Fuzzyma"
}
},
"node_modules/@svgdotjs/svg.resize.js": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/@svgdotjs/svg.resize.js/-/svg.resize.js-2.0.5.tgz",
"integrity": "sha512-4heRW4B1QrJeENfi7326lUPYBCevj78FJs8kfeDxn5st0IYPIRXoTtOSYvTzFWgaWWXd3YCDE6ao4fmv91RthA==",
"license": "MIT",
"engines": {
"node": ">= 14.18"
},
"peerDependencies": {
"@svgdotjs/svg.js": "^3.2.4",
"@svgdotjs/svg.select.js": "^4.0.1"
}
},
"node_modules/@svgdotjs/svg.select.js": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/@svgdotjs/svg.select.js/-/svg.select.js-4.0.2.tgz",
"integrity": "sha512-5gWdrvoQX3keo03SCmgaBbD+kFftq0F/f2bzCbNnpkkvW6tk4rl4MakORzFuNjvXPWwB4az9GwuvVxQVnjaK2g==",
"license": "MIT",
"engines": {
"node": ">= 14.18"
},
"peerDependencies": {
"@svgdotjs/svg.js": "^3.2.4"
}
},
"node_modules/@swc/core": {
"version": "1.10.1",
"resolved": "https://registry.npmjs.org/@swc/core/-/core-1.10.1.tgz",
@ -1439,6 +1498,11 @@
"resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz",
"integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg=="
},
"node_modules/@types/web": {
"version": "0.0.216",
"resolved": "https://registry.npmjs.org/@types/web/-/web-0.0.216.tgz",
"integrity": "sha512-HLaPWQKq1oh6aQv1JLRsiH0vW4VsO+L/zTOeOUmoGBnLVR2wCj4w4oWfa/0O5JFMqZXWC6VpipqQU6B1v2M/qg=="
},
"node_modules/@ungap/structured-clone": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.1.tgz",
@ -1650,6 +1714,12 @@
"license": "Apache-2.0",
"peer": true
},
"node_modules/@yr/monotone-cubic-spline": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@yr/monotone-cubic-spline/-/monotone-cubic-spline-1.0.3.tgz",
"integrity": "sha512-FQXkOta0XBSUPHndIKON2Y9JeQz5ZeMqLYZVVK93FliNBFm7LNMIZmY6FrMEB9XPcDbE2bekMbZD6kzDkxwYjA==",
"license": "MIT"
},
"node_modules/acorn": {
"version": "8.14.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz",
@ -1752,6 +1822,20 @@
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/apexcharts": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/apexcharts/-/apexcharts-4.5.0.tgz",
"integrity": "sha512-E7ZkrVqPNBUWy/Rmg8DEIqHNBmElzICE/oxOX5Ekvs2ICQUOK/VkEkMH09JGJu+O/EA0NL31hxlmF+wrwrSLaQ==",
"license": "MIT",
"dependencies": {
"@svgdotjs/svg.draggable.js": "^3.0.4",
"@svgdotjs/svg.filter.js": "^3.0.8",
"@svgdotjs/svg.js": "^3.2.4",
"@svgdotjs/svg.resize.js": "^2.0.2",
"@svgdotjs/svg.select.js": "^4.0.1",
"@yr/monotone-cubic-spline": "^1.0.3"
}
},
"node_modules/argparse": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
@ -3994,7 +4078,6 @@
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
@ -4255,7 +4338,6 @@
"version": "15.8.1",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
"dev": true,
"dependencies": {
"loose-envify": "^1.4.0",
"object-assign": "^4.1.1",
@ -4317,6 +4399,19 @@
"node": ">=0.10.0"
}
},
"node_modules/react-apexcharts": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/react-apexcharts/-/react-apexcharts-1.7.0.tgz",
"integrity": "sha512-03oScKJyNLRf0Oe+ihJxFZliBQM9vW3UWwomVn4YVRTN1jsIR58dLWt0v1sb8RwJVHDMbeHiKQueM0KGpn7nOA==",
"license": "MIT",
"dependencies": {
"prop-types": "^15.8.1"
},
"peerDependencies": {
"apexcharts": ">=4.0.0",
"react": ">=0.13"
}
},
"node_modules/react-dom": {
"version": "18.3.1",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
@ -4348,8 +4443,7 @@
"node_modules/react-is": {
"version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
"dev": true
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
},
"node_modules/react-redux": {
"version": "9.2.0",

View File

@ -3,7 +3,6 @@
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"json-server": "json-server --watch ./src/data/demo.json --port 5000",
@ -14,7 +13,9 @@
"dependencies": {
"@hookform/resolvers": "^3.10.0",
"@reduxjs/toolkit": "^2.5.0",
"@types/web": "^0.0.216",
"@vitejs/plugin-react": "^4.3.4",
"apexcharts": "^4.5.0",
"axios": "^1.7.9",
"axios-retry": "^4.5.0",
"dotenv": "^16.4.7",
@ -25,6 +26,7 @@
"moment": "^2.30.1",
"perfect-scrollbar": "^1.5.5",
"react": "^18.2.0",
"react-apexcharts": "^1.7.0",
"react-dom": "^18.2.0",
"react-hook-form": "^7.54.2",
"react-redux": "^9.2.0",

View File

@ -3832,7 +3832,7 @@ fieldset:disabled .btn {
--bs-dropdown-padding-x: 0;
--bs-dropdown-padding-y: 0.5rem;
--bs-dropdown-spacer: 0.125rem;
--bs-dropdown-font-size: 0.9375rem;
--bs-dropdown-font-size: 0.8125rem;
--bs-dropdown-color: var(--bs-body-color);
--bs-dropdown-bg: #fff;
--bs-dropdown-border-color: #e4e6e8;
@ -35469,7 +35469,7 @@ html:not([dir="rtl"]) .menu-toggle::after {
}
.menu-vertical .menu-item .menu-link {
font-size: 0.8375rem;
font-size: 0.7375rem;
min-height: 1.825rem;
}

View File

@ -0,0 +1,906 @@
.flatpickr-calendar {
position: absolute;
visibility: hidden;
overflow: hidden;
box-sizing: border-box;
padding: 0;
padding-bottom: 2px;
max-height: 0;
border: 0;
text-align: center;
opacity: 0;
animation: none;
outline: 0;
touch-action: manipulation;
line-height: 1.375;
font-size: 0.9375rem;
border-radius: 0.5rem;
}
.flatpickr-calendar.open, .flatpickr-calendar.inline {
visibility: visible;
overflow: visible;
max-height: 640px;
opacity: 1;
}
.flatpickr-calendar.open {
display: inline-block;
}
.flatpickr-calendar.animate.open {
animation: fpFadeInDown 300ms cubic-bezier(0.23, 1, 0.32, 1);
}
.flatpickr-calendar:not(.inline):not(.open) {
display: none !important;
}
.flatpickr-calendar.inline {
position: relative;
top: 2px;
display: block;
}
.flatpickr-calendar.static {
position: absolute;
top: calc(100% + 2px);
}
.flatpickr-calendar.static.open {
z-index: 999;
display: block;
}
.flatpickr-calendar.hasWeeks {
width: auto;
}
html:not([dir=rtl]) .flatpickr-calendar.hasWeeks .flatpickr-days {
border-bottom-left-radius: 0 !important;
}
[dir=rtl] .flatpickr-calendar.hasWeeks .flatpickr-days {
border-bottom-right-radius: 0 !important;
}
.flatpickr-calendar.hasTime {
padding-bottom: 0;
}
.flatpickr-calendar.hasTime .flatpickr-time {
height: 40px;
}
.flatpickr-calendar.noCalendar.hasTime .flatpickr-time {
height: auto;
}
.flatpickr-calendar input[type=number] {
-moz-appearance: textfield;
}
.flatpickr-calendar input[type=number]::-webkit-inner-spin-button,
.flatpickr-calendar input[type=number]::-webkit-outer-spin-button {
-webkit-appearance: none;
margin: 0;
}
.flatpickr-wrapper {
position: relative;
display: inline-block;
}
.flatpickr-month {
position: relative;
overflow: hidden;
height: 3rem;
text-align: center;
line-height: 1;
user-select: none;
}
.flatpickr-prev-month,
.flatpickr-next-month {
position: absolute;
top: 0.75rem;
z-index: 3;
padding: 0 0.41rem;
height: 1.875rem;
width: 1.875rem;
text-decoration: none;
cursor: pointer;
border-radius: 0.375rem;
display: flex;
align-items: center;
justify-content: center;
}
.flatpickr-prev-month svg,
.flatpickr-next-month svg {
stroke-width: 2;
vertical-align: middle;
}
.flatpickr-prev-month i,
.flatpickr-next-month i {
position: relative;
}
.flatpickr-prev-month.flatpickr-prev-month {
left: 1rem;
}
[dir=rtl] .flatpickr-prev-month {
right: 1rem;
left: auto;
transform: scaleX(-1);
}
.flatpickr-next-month.flatpickr-prev-month {
right: 0;
left: 0;
}
.flatpickr-next-month.flatpickr-next-month {
right: 1rem;
}
[dir=rtl] .flatpickr-next-month {
right: auto;
left: 1rem;
transform: scaleX(-1);
}
.flatpickr-prev-month:hover,
.flatpickr-next-month:hover {
opacity: 1;
}
.flatpickr-prev-month svg,
.flatpickr-next-month svg {
width: 0.6rem;
}
.flatpickr-prev-month svg path,
.flatpickr-next-month svg path {
transition: fill 0.1s;
fill: inherit;
}
.numInputWrapper {
position: relative;
height: auto;
}
.numInputWrapper :hover {
background: transparent;
}
.numInputWrapper input,
.numInputWrapper span {
display: inline-block;
}
.numInputWrapper input {
width: 100%;
}
.numInputWrapper span {
position: absolute;
right: 0;
display: flex;
align-items: center;
justify-content: center;
box-sizing: border-box;
width: 14px;
height: 50%;
line-height: 1;
opacity: 0;
cursor: pointer;
}
[dir=rtl] .numInputWrapper span {
right: auto;
left: 0;
}
.numInputWrapper span:hover {
background: rgba(0, 0, 0, 0.1);
}
.numInputWrapper span:active {
background: rgba(0, 0, 0, 0.2);
}
.numInputWrapper span:after {
content: "";
display: block;
width: 0;
height: 0;
}
.numInputWrapper span.arrowUp {
top: 0;
}
.numInputWrapper span.arrowUp:after {
border-right: 4px solid transparent;
border-bottom: 4px solid rgba(72, 72, 72, 0.6);
border-left: 4px solid transparent;
}
.numInputWrapper span.arrowDown {
top: 50%;
}
.numInputWrapper span.arrowDown:after {
border-top: 4px solid rgba(72, 72, 72, 0.6);
border-right: 4px solid transparent;
border-left: 4px solid transparent;
}
.numInputWrapper span svg {
width: inherit;
height: auto;
}
.numInputWrapper span svg path {
fill: rgba(255, 255, 255, 0.5);
}
.numInputWrapper:hover {
background: rgba(0, 0, 0, 0.05);
}
.numInputWrapper:hover span {
opacity: 1;
}
.flatpickr-current-month {
position: absolute;
left: 12.5%;
display: flex;
align-items: center;
justify-content: center;
gap: 0.25rem;
width: 75%;
height: 2.5rem;
text-align: center;
font-weight: 300;
line-height: 1;
padding: 0.9rem 0 0 0;
transform: translate3d(0px, 0px, 0px);
}
.flatpickr-current-month .flatpickr-monthDropdown-months,
.flatpickr-current-month input.cur-year {
outline: none;
vertical-align: middle !important;
font-weight: 400;
font-size: inherit;
font-family: inherit;
line-height: inherit;
color: inherit;
display: inline-block;
box-sizing: border-box;
background: transparent;
border: 0;
border-radius: 0;
}
.flatpickr-current-month .flatpickr-monthDropdown-months:not(:first-child),
.flatpickr-current-month input.cur-year:not(:first-child) {
padding: 0 0 0 0.5ch;
}
.flatpickr-current-month .numInputWrapper {
display: inline-block;
width: 6ch;
}
.flatpickr-current-month .flatpickr-monthDropdown-months {
appearance: menulist;
cursor: pointer;
height: 2.25rem;
position: relative;
width: auto;
font-size: 0.9375rem;
}
.flatpickr-current-month input.cur-year {
margin: 0;
height: 1.2rem;
cursor: default;
}
[dir=rtl] .flatpickr-current-month input.cur-year {
padding-right: 0.5ch;
padding-left: 0;
}
.flatpickr-current-month input.cur-year:focus {
outline: 0;
}
.flatpickr-current-month input.cur-year[disabled], .flatpickr-current-month input.cur-year[disabled]:hover {
background: transparent;
pointer-events: none;
}
.flatpickr-current-month input.cur-year[disabled] {
opacity: 0.5;
}
.flatpickr-weekdaycontainer {
display: flex;
width: 100%;
padding: 0.25rem 0.6rem;
}
.flatpickr-weekdays {
display: flex;
overflow: hidden;
align-items: center;
max-width: 17.5rem;
width: 100%;
height: 2.25rem;
text-align: center;
margin-bottom: 0.125rem;
}
span.flatpickr-weekday {
display: block;
flex: 1;
margin: 0;
text-align: center;
line-height: 1;
cursor: default;
}
.dayContainer,
.flatpickr-weeks {
padding: 1px 0 0 0;
}
.flatpickr-days {
position: relative;
display: flex;
overflow: hidden;
width: auto !important;
}
.flatpickr-days:focus {
outline: 0;
}
.flatpickr-calendar.hasTime .flatpickr-days {
border-bottom: 0 !important;
border-bottom-right-radius: 0 !important;
border-bottom-left-radius: 0 !important;
}
.dayContainer {
display: inline-block;
display: flex;
flex-wrap: wrap;
justify-content: space-around;
box-sizing: border-box;
padding: 0;
min-width: 15.75rem;
max-width: 15.75rem;
width: 15.75rem;
outline: 0;
opacity: 1;
transform: translate3d(0px, 0px, 0px);
}
.flatpickr-day {
position: relative;
display: inline-block;
flex-basis: 14.2857143%;
justify-content: center;
box-sizing: border-box;
margin: 0;
max-width: 2.25rem;
width: 15.2857143%;
height: 2.25rem;
border: 1px solid transparent;
background: none;
text-align: center;
font-weight: 400;
line-height: calc(2.25rem - 2px);
cursor: pointer;
}
.flatpickr-day.inRange, .flatpickr-day.prevMonthDay.inRange, .flatpickr-day.nextMonthDay.inRange, .flatpickr-day.today.inRange, .flatpickr-day.prevMonthDay.today.inRange, .flatpickr-day.nextMonthDay.today.inRange, .flatpickr-day:hover, .flatpickr-day.prevMonthDay:hover, .flatpickr-day.nextMonthDay:hover, .flatpickr-day:focus, .flatpickr-day.prevMonthDay:focus, .flatpickr-day.nextMonthDay:focus {
outline: 0;
cursor: pointer;
}
.flatpickr-day.inRange:not(.startRange):not(.endRange) {
border-radius: 0 !important;
}
.flatpickr-day.disabled, .flatpickr-day.flatpickr-disabled, .flatpickr-day.flatpickr-disabled.today, .flatpickr-day.disabled:hover, .flatpickr-day.flatpickr-disabled:hover, .flatpickr-day.flatpickr-disabled.today:hover {
border-color: transparent;
background: transparent !important;
cursor: default;
pointer-events: none;
box-shadow: none;
}
.flatpickr-day.prevMonthDay, .flatpickr-day.nextMonthDay {
border-color: transparent;
background: transparent;
cursor: default;
}
.flatpickr-day.notAllowed, .flatpickr-day.notAllowed.prevMonthDay, .flatpickr-day.notAllowed.nextMonthDay {
border-color: transparent;
background: transparent;
cursor: default;
}
.flatpickr-day.week.selected {
border-radius: 0;
}
html:not([dir=rtl]) .flatpickr-day.selected.startRange, html:not([dir=rtl]) .flatpickr-day.startRange.startRange, html:not([dir=rtl]) .flatpickr-day.endRange.startRange {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
html:not([dir=rtl]) .flatpickr-day.selected.endRange, html:not([dir=rtl]) .flatpickr-day.startRange.endRange, html:not([dir=rtl]) .flatpickr-day.endRange.endRange {
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}
[dir=rtl] .flatpickr-day.selected.startRange, [dir=rtl] .flatpickr-day.startRange.startRange, [dir=rtl] .flatpickr-day.endRange.startRange {
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}
[dir=rtl] .flatpickr-day.selected.endRange, [dir=rtl] .flatpickr-day.startRange.endRange, [dir=rtl] .flatpickr-day.endRange.endRange {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
.flatpickr-weekwrapper {
display: inline-block;
float: left;
}
.flatpickr-weekwrapper .flatpickr-weeks {
background-clip: padding-box !important;
}
html:not([dir=rtl]) .flatpickr-weekwrapper .flatpickr-weeks .flatpickr-weeks {
border-bottom-right-radius: 0 !important;
}
[dir=rtl] .flatpickr-weekwrapper .flatpickr-weeks .flatpickr-weeks {
border-bottom-left-radius: 0 !important;
}
.flatpickr-weekwrapper .flatpickr-weeks .flatpickr-day {
font-size: 0.8125rem;
}
.flatpickr-weekwrapper .flatpickr-calendar.hasTime .flatpickr-weeks {
border-bottom: 0 !important;
border-bottom-right-radius: 0 !important;
border-bottom-left-radius: 0 !important;
}
.flatpickr-weekwrapper .flatpickr-weekday {
float: none;
width: 100%;
line-height: 2.25rem;
position: relative;
top: 1px;
margin-bottom: 0.4rem;
}
.flatpickr-weekwrapper span.flatpickr-day {
display: block;
max-width: none;
width: 2.25rem;
background: none !important;
}
.flatpickr-calendar.hasTime .flatpickr-weeks {
border-bottom: 0 !important;
border-bottom-left-radius: 0 !important;
border-bottom-right-radius: 0 !important;
}
.flatpickr-innerContainer {
display: block;
display: flex;
overflow: hidden;
box-sizing: border-box;
}
html:not([dir=rtl]) .flatpickr-innerContainer:has(.flatpickr-weeks) .flatpickr-weeks {
padding-left: 0.445rem;
}
[dir=rtl] .flatpickr-innerContainer:has(.flatpickr-weeks) .flatpickr-weeks {
padding-left: 0.445rem;
}
[dir=rtl] .flatpickr-innerContainer:has(.flatpickr-weeks) .flatpickr-weekdaycontainer {
padding-left: 0.625rem;
}
.flatpickr-innerContainer:has(.flatpickr-weeks) .flatpickr-weekwrapper .flatpickr-weekday {
padding-left: 0.445rem;
}
[dir=rtl] .flatpickr-innerContainer:has(.flatpickr-weeks) .flatpickr-weekwrapper {
padding-right: 0.5rem;
}
.flatpickr-rContainer {
display: inline-block;
box-sizing: border-box;
padding: 0;
}
.flatpickr-time {
display: block;
display: flex;
overflow: hidden;
box-sizing: border-box;
max-height: 40px;
height: 0;
outline: 0;
background-clip: padding-box !important;
text-align: center;
line-height: 40px;
}
.flatpickr-time:after {
content: "";
display: table;
clear: both;
}
.flatpickr-time .numInputWrapper {
float: left;
flex: 1;
width: 40%;
height: 40px;
}
.flatpickr-time.hasSeconds .numInputWrapper {
width: 26%;
}
.flatpickr-time.time24hr .numInputWrapper {
width: 49%;
}
.flatpickr-time input {
position: relative;
box-sizing: border-box;
margin: 0;
padding: 0;
height: inherit;
border: 0;
border-radius: 0;
background: transparent;
box-shadow: none;
text-align: center;
line-height: inherit;
cursor: pointer;
font-size: 0.9375rem;
}
.flatpickr-time input.flatpickr-hour, .flatpickr-time input.flatpickr-minute, .flatpickr-time input.flatpickr-second {
font-weight: 400;
}
.flatpickr-time input:focus {
outline: 0;
border: 0;
}
.flatpickr-time .flatpickr-time-separator,
.flatpickr-time .flatpickr-am-pm {
display: inline-block;
float: left;
align-self: center;
width: 2%;
height: inherit;
line-height: inherit;
user-select: none;
}
.flatpickr-time .flatpickr-am-pm {
width: 18%;
outline: 0;
text-align: center;
font-weight: normal;
cursor: pointer;
}
.flatpickr-time .flatpickr-am-pm:hover {
background: rgba(0, 0, 0, 0.05);
}
.flatpickr-calendar.noCalendar .flatpickr-time {
box-shadow: none !important;
}
.flatpickr-calendar:not(.noCalendar) .flatpickr-time {
border-top: 0;
border-top-left-radius: 0 !important;
border-top-right-radius: 0 !important;
}
.flatpickr-input[readonly] {
cursor: pointer;
}
@-webkit-keyframes fpFadeInDown {
from {
opacity: 0;
transform: translate3d(0, -20px, 0);
}
to {
opacity: 1;
transform: translate3d(0, 0, 0);
}
}
@-moz-keyframes fpFadeInDown {
from {
opacity: 0;
transform: translate3d(0, -20px, 0);
}
to {
opacity: 1;
transform: translate3d(0, 0, 0);
}
}
@keyframes fpFadeInDown {
from {
opacity: 0;
transform: translate3d(0, -20px, 0);
}
to {
opacity: 1;
transform: translate3d(0, 0, 0);
}
}
.light-style .flatpickr-calendar {
background: #fff;
}
.light-style .flatpickr-prev-month,
.light-style .flatpickr-next-month {
background-color: #edeef0;
}
.light-style .flatpickr-prev-month svg,
.light-style .flatpickr-next-month svg {
fill: #646e78;
stroke: #646e78;
}
.light-style .flatpickr-calendar,
.light-style .flatpickr-days {
width: calc(16.75rem + 0 * 2px) !important;
}
.light-style .flatpickr-calendar {
background-color: #fff;
border-radius: 0.375rem !important;
}
.light-style:not([dir=rtl]) .flatpickr-calendar.hasWeeks {
width: calc(19rem + 0 * 3px + 0.35rem) !important;
}
.light-style[dir=rtl] .flatpickr-calendar.hasWeeks {
width: calc(19rem + 0 * 3px + 1rem) !important;
}
.light-style .flatpickr-calendar.open {
z-index: 1091;
}
.light-style .flatpickr-input[readonly],
.light-style .flatpickr-input ~ .form-control[readonly] {
background: transparent;
}
.light-style .flatpickr-days {
background: #fff;
padding: 0.25rem 0.5rem 0.5rem;
border: 0 solid #e4e6e8;
border-top: 0;
background-clip: padding-box;
border-bottom-right-radius: 0.375rem;
border-bottom-left-radius: 0.375rem;
}
.light-style:not([dir=rtl]) .flatpickr-calendar.hasWeeks .flatpickr-days {
border-left: 0;
padding-left: calc(0.5rem + 0px);
box-shadow: 0 0 0 #e4e6e8 inset;
}
.light-style[dir=rtl] .flatpickr-calendar.hasWeeks .flatpickr-days {
border-right: 0;
padding-right: calc(0.5rem + 0px);
box-shadow: 0 0 0 #e4e6e8 inset;
}
.light-style .flatpickr-calendar {
line-height: 1.375;
font-size: 0.9375rem;
box-shadow: 0 0.25rem 0.75rem 0 rgba(34, 48, 62, 0.14);
border-radius: 0.375rem;
}
.light-style .flatpickr-calendar.hasTime:not(.noCalendar):not(.hasTime) .flatpickr-time {
display: none !important;
}
.light-style .flatpickr-calendar.hasTime .flatpickr-time {
box-shadow: 0 1px 0 #e4e6e8 inset;
}
.light-style .flatpickr-monthDropdown-months {
color: #384551;
}
.light-style .flatpickr-current-month {
font-size: 112%;
color: #384551;
}
.light-style .flatpickr-current-month .cur-month,
.light-style .flatpickr-current-month .cur-year {
font-size: 0.9375rem;
font-weight: 400;
color: #384551;
}
.light-style .flatpickr-month,
.light-style span.flatpickr-weekday,
.light-style .flatpickr-weekdays {
background: #fff;
}
.light-style .flatpickr-month {
border-top-left-radius: 0.375rem;
border-top-right-radius: 0.375rem;
}
.light-style .flatpickr-month option.flatpickr-monthDropdown-month {
color: #384551;
background: transparent;
}
.light-style span.flatpickr-weekday {
color: #384551;
font-size: 0.8125rem;
}
.light-style .flatpickr-day {
color: #384551;
border-radius: 0.375rem;
}
.light-style .flatpickr-day:hover, .light-style .flatpickr-day:focus, .light-style .flatpickr-day.prevMonthDay:hover, .light-style .flatpickr-day.nextMonthDay:hover, .light-style .flatpickr-day.today:hover, .light-style .flatpickr-day.prevMonthDay:focus, .light-style .flatpickr-day.nextMonthDay:focus, .light-style .flatpickr-day.today:focus {
color: #384551;
background: #f2f3f3;
}
.light-style .flatpickr-day:hover:not(.today), .light-style .flatpickr-day:focus:not(.today), .light-style .flatpickr-day.prevMonthDay:hover:not(.today), .light-style .flatpickr-day.nextMonthDay:hover:not(.today), .light-style .flatpickr-day.today:hover:not(.today), .light-style .flatpickr-day.prevMonthDay:focus:not(.today), .light-style .flatpickr-day.nextMonthDay:focus:not(.today), .light-style .flatpickr-day.today:focus:not(.today) {
border-color: transparent;
}
.light-style .flatpickr-day.prevMonthDay, .light-style .flatpickr-day.nextMonthDay, .light-style .flatpickr-day.flatpickr-disabled {
color: #a7acb2 !important;
}
.light-style .flatpickr-day.prevMonthDay.today, .light-style .flatpickr-day.nextMonthDay.today, .light-style .flatpickr-day.flatpickr-disabled.today {
border: none;
}
.light-style .flatpickr-day.disabled {
color: #a7acb2 !important;
}
.light-style .flatpickr-day.selected.startRange.endRange {
border-radius: 0.375rem !important;
}
.light-style .flatpickr-weeks {
border-bottom: 0 solid #e4e6e8;
border-left: 0 solid #e4e6e8;
background: #fff;
border-bottom-right-radius: 0.375rem;
border-bottom-left-radius: 0.375rem;
border-bottom-right-radius: 0;
}
.light-style .flatpickr-weeks .flatpickr-day {
color: #384551;
}
.light-style[dir=rtl] .flatpickr-weeks {
border-right: 0 solid #e4e6e8;
border-left: 0;
border-bottom-right-radius: 0.375rem;
border-bottom-left-radius: 0.375rem;
border-bottom-left-radius: 0;
}
.light-style .flatpickr-time {
border: 0 solid #e4e6e8;
background: #fff;
border-radius: 0.375rem;
}
.light-style .flatpickr-time input {
color: #646e78;
font-size: 0.9375rem;
}
.light-style .flatpickr-time .numInputWrapper span.arrowUp:after {
border-bottom-color: #a7acb2;
}
.light-style .flatpickr-time .numInputWrapper span.arrowDown:after {
border-top-color: #a7acb2;
}
.light-style .flatpickr-time .flatpickr-am-pm {
color: #646e78;
}
.light-style .flatpickr-time .flatpickr-time-separator {
color: #646e78;
font-weight: 500;
}
.dark-style .flatpickr-calendar {
background: #2b2c40;
}
.dark-style .flatpickr-prev-month,
.dark-style .flatpickr-next-month {
background-color: rgba(230, 230, 241, 0.08);
}
.dark-style .flatpickr-prev-month svg,
.dark-style .flatpickr-next-month svg {
fill: #b2b2c4;
stroke: #b2b2c4;
}
.dark-style .flatpickr-calendar,
.dark-style .flatpickr-days {
width: calc(16.75rem + 0 * 2px) !important;
}
.dark-style:not([dir=rtl]) .flatpickr-calendar.hasWeeks {
width: calc(19rem + 0 * 3px + 0.355rem) !important;
}
.dark-style[dir=rtl] .flatpickr-calendar.hasWeeks {
width: calc(19rem + 0 * 3px + 1rem) !important;
}
.dark-style .flatpickr-calendar.open {
z-index: 1091;
}
.dark-style .flatpickr-input:not(.is-invalid):not(.is-valid) ~ .form-control:disabled,
.dark-style .flatpickr-input:not(.is-invalid):not(.is-valid)[readonly],
.dark-style .flatpickr-input:not(.is-invalid):not(.is-valid) ~ .form-control[readonly] {
background-color: transparent;
}
.dark-style .flatpickr-days {
border: 0 solid #4e4f6c;
border-top: 0;
padding: 0.25rem 0.5rem;
padding-bottom: 0.5rem;
background: #2b2c40;
background-clip: padding-box;
border-bottom-right-radius: 0.375rem;
border-bottom-left-radius: 0.375rem;
}
.dark-style:not([dir=rtl]) .flatpickr-calendar.hasWeeks .flatpickr-days {
border-left: 0;
padding-left: calc(0.5rem + 0px);
box-shadow: 0 0 0 #4e4f6c inset;
}
.dark-style[dir=rtl] .flatpickr-calendar.hasWeeks .flatpickr-days {
border-right: 0;
padding-right: calc(0.5rem + 0px);
box-shadow: 0 0 0 #4e4f6c inset;
}
.dark-style .flatpickr-calendar {
line-height: 1.375;
font-size: 0.9375rem;
box-shadow: 0 0.25rem 0.75rem 0 rgba(20, 20, 29, 0.24);
background-color: #2b2c40;
border-radius: 0.375rem;
}
.dark-style .flatpickr-calendar.hasTime:not(.noCalendar):not(.hasTime) .flatpickr-time {
display: none !important;
}
.dark-style .flatpickr-calendar.hasTime .flatpickr-time {
box-shadow: 0 1px 0 #4e4f6c inset;
}
.dark-style .flatpickr-month,
.dark-style span.flatpickr-weekday,
.dark-style .flatpickr-weekdays {
background: #2b2c40;
}
.dark-style .flatpickr-month {
border-top-left-radius: 0.375rem;
border-top-right-radius: 0.375rem;
}
.dark-style .flatpickr-month option.flatpickr-monthDropdown-month {
color: #b2b2c4;
background: #2b2c40;
}
.dark-style .flatpickr-monthDropdown-months {
color: #d5d5e2;
}
.dark-style .flatpickr-current-month {
font-size: 112%;
color: #d5d5e2;
}
.dark-style .flatpickr-current-month .cur-month,
.dark-style .flatpickr-current-month .cur-year {
font-size: 0.9375rem;
font-weight: 400;
color: #d5d5e2;
}
.dark-style span.flatpickr-weekday {
font-size: 0.8125rem;
color: #d5d5e2;
}
.dark-style .flatpickr-day {
color: #d5d5e2;
font-weight: 500;
border-radius: 0.375rem;
}
.dark-style .flatpickr-day:hover, .dark-style .flatpickr-day:focus, .dark-style .flatpickr-day.nextMonthDay:hover, .dark-style .flatpickr-day.prevMonthDay:hover, .dark-style .flatpickr-day.today:hover, .dark-style .flatpickr-day.nextMonthDay:focus, .dark-style .flatpickr-day.prevMonthDay:focus, .dark-style .flatpickr-day.today:focus {
border-color: transparent;
color: #d5d5e2;
background: #434463;
}
.dark-style .flatpickr-day.nextMonthDay, .dark-style .flatpickr-day.prevMonthDay, .dark-style .flatpickr-day.flatpickr-disabled {
color: #7e7f96 !important;
}
.dark-style .flatpickr-day.nextMonthDay.today, .dark-style .flatpickr-day.prevMonthDay.today, .dark-style .flatpickr-day.flatpickr-disabled.today {
border: 0;
}
.dark-style .flatpickr-day.selected.startRange.endRange {
border-radius: 0.375rem !important;
}
.dark-style .flatpickr-day.disabled {
color: #7e7f96 !important;
}
.dark-style .flatpickr-weeks {
border-bottom: 0 solid #4e4f6c;
border-left: 0 solid #4e4f6c;
background: #2b2c40;
border-bottom-right-radius: 0.375rem;
border-bottom-left-radius: 0.375rem;
border-bottom-right-radius: 0;
}
.dark-style .flatpickr-weeks .flatpickr-day {
color: #d5d5e2;
}
.dark-style[dir=rtl] .flatpickr-weeks {
border-right: 0 solid #4e4f6c;
border-left: 0;
}
.dark-style .flatpickr-time {
border: 0 solid #4e4f6c;
background: #2b2c40;
border-radius: 0.375rem;
}
.dark-style .flatpickr-time input {
color: #b2b2c4;
}
.dark-style .flatpickr-time .numInputWrapper span.arrowUp:after {
border-bottom-color: #7e7f96;
}
.dark-style .flatpickr-time .numInputWrapper span.arrowDown:after {
border-top-color: #7e7f96;
}
.dark-style .flatpickr-time .flatpickr-am-pm {
color: #b2b2c4;
}
.dark-style .flatpickr-time .flatpickr-time-separator {
color: #b2b2c4;
font-weight: 500;
}

File diff suppressed because one or more lines are too long

81
src/ModalContext.jsx Normal file
View File

@ -0,0 +1,81 @@
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 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",
};

View File

@ -1,86 +1,793 @@
.ui-timepicker-wrapper {
max-height: 10rem;
overflow-y: auto;
margin: 0.125rem 0;
.flatpickr-calendar {
background: transparent;
opacity: 0;
display: none;
text-align: center;
visibility: hidden;
padding: 0;
-webkit-animation: none;
animation: none;
direction: ltr;
border: 0;
font-size: 14px;
line-height: 24px;
border-radius: 5px;
position: absolute;
width: 307.875px;
-webkit-box-sizing: border-box;
box-sizing: border-box;
-ms-touch-action: manipulation;
touch-action: manipulation;
background: #fff;
background-clip: padding-box;
outline: none;
padding: 0.5rem;
-webkit-box-shadow: 1px 0 0 #e6e6e6, -1px 0 0 #e6e6e6, 0 1px 0 #e6e6e6,
0 -1px 0 #e6e6e6, 0 3px 13px rgba(0, 0, 0, 0.08);
box-shadow: 1px 0 0 #e6e6e6, -1px 0 0 #e6e6e6, 0 1px 0 #e6e6e6,
0 -1px 0 #e6e6e6, 0 3px 13px rgba(0, 0, 0, 0.08);
}
.ui-timepicker-list {
list-style: none;
padding: 0.125rem 0;
margin: 0;
.flatpickr-calendar.open,
.flatpickr-calendar.inline {
opacity: 1;
max-height: 640px;
visibility: visible;
}
.ui-timepicker-duration {
margin-left: 0.25rem;
.flatpickr-calendar.open {
display: inline-block;
z-index: 99999;
}
[dir=rtl] .ui-timepicker-duration {
margin-left: 0;
margin-right: 0.25rem;
.flatpickr-calendar.animate.open {
-webkit-animation: fpFadeInDown 300ms cubic-bezier(0.23, 1, 0.32, 1);
animation: fpFadeInDown 300ms cubic-bezier(0.23, 1, 0.32, 1);
}
.ui-timepicker-list li {
padding: 0.25rem 0.75rem;
margin: 0.125rem 0.5625rem;
white-space: nowrap;
.flatpickr-calendar.inline {
display: block;
position: relative;
top: 2px;
}
.flatpickr-calendar.static {
position: absolute;
top: calc(100% + 2px);
}
.flatpickr-calendar.static.open {
z-index: 999;
display: block;
}
.flatpickr-calendar.multiMonth
.flatpickr-days
.dayContainer:nth-child(n + 1)
.flatpickr-day.inRange:nth-child(7n + 7) {
-webkit-box-shadow: none !important;
box-shadow: none !important;
}
.flatpickr-calendar.multiMonth
.flatpickr-days
.dayContainer:nth-child(n + 2)
.flatpickr-day.inRange:nth-child(7n + 1) {
-webkit-box-shadow: -2px 0 0 #e6e6e6, 5px 0 0 #e6e6e6;
box-shadow: -2px 0 0 #e6e6e6, 5px 0 0 #e6e6e6;
}
.flatpickr-calendar .hasWeeks .dayContainer,
.flatpickr-calendar .hasTime .dayContainer {
border-bottom: 0;
border-bottom-right-radius: 0;
border-bottom-left-radius: 0;
}
.flatpickr-calendar .hasWeeks .dayContainer {
border-left: 0;
}
.flatpickr-calendar.hasTime .flatpickr-time {
height: 40px;
border-top: 1px solid #e6e6e6;
}
.flatpickr-calendar.noCalendar.hasTime .flatpickr-time {
height: auto;
}
.flatpickr-calendar:before,
.flatpickr-calendar:after {
position: absolute;
display: block;
pointer-events: none;
border: solid transparent;
content: "";
height: 0;
width: 0;
left: 22px;
}
.flatpickr-calendar.rightMost:before,
.flatpickr-calendar.arrowRight:before,
.flatpickr-calendar.rightMost:after,
.flatpickr-calendar.arrowRight:after {
left: auto;
right: 22px;
}
.flatpickr-calendar.arrowCenter:before,
.flatpickr-calendar.arrowCenter:after {
left: 50%;
right: 50%;
}
.flatpickr-calendar:before {
border-width: 5px;
margin: 0 -5px;
}
.flatpickr-calendar:after {
border-width: 4px;
margin: 0 -4px;
}
.flatpickr-calendar.arrowTop:before,
.flatpickr-calendar.arrowTop:after {
bottom: 100%;
}
.flatpickr-calendar.arrowTop:before {
border-bottom-color: #e6e6e6;
}
.flatpickr-calendar.arrowTop:after {
border-bottom-color: #fff;
}
.flatpickr-calendar.arrowBottom:before,
.flatpickr-calendar.arrowBottom:after {
top: 100%;
}
.flatpickr-calendar.arrowBottom:before {
border-top-color: #e6e6e6;
}
.flatpickr-calendar.arrowBottom:after {
border-top-color: #fff;
}
.flatpickr-calendar:focus {
outline: 0;
}
.flatpickr-wrapper {
position: relative;
display: inline-block;
}
.flatpickr-months {
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
}
.flatpickr-months .flatpickr-month {
background: transparent;
color: rgba(0, 0, 0, 0.9);
fill: rgba(0, 0, 0, 0.9);
height: 34px;
line-height: 1;
text-align: center;
position: relative;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
overflow: hidden;
-webkit-box-flex: 1;
-webkit-flex: 1;
-ms-flex: 1;
flex: 1;
}
.flatpickr-months .flatpickr-prev-month,
.flatpickr-months .flatpickr-next-month {
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
text-decoration: none;
cursor: pointer;
list-style: none;
border-radius: var(--bs-border-radius);
position: absolute;
top: 0;
height: 34px;
padding: 10px;
z-index: 3;
color: rgba(0, 0, 0, 0.9);
fill: rgba(0, 0, 0, 0.9);
}
.ui-timepicker-list li.ui-timepicker-disabled, .ui-timepicker-list li.ui-timepicker-selected.ui-timepicker-disabled {
background: #fff !important;
cursor: default !important;
.flatpickr-months .flatpickr-prev-month.flatpickr-disabled,
.flatpickr-months .flatpickr-next-month.flatpickr-disabled {
display: none;
}
.light-style .ui-timepicker-wrapper {
z-index: 1091;
background: #fff;
box-shadow: 0 0.25rem 0.75rem 0 rgba(34, 48, 62, 0.14);
border: 0 solid #e4e6e8;
border-radius: 0.375rem;
.flatpickr-months .flatpickr-prev-month i,
.flatpickr-months .flatpickr-next-month i {
position: relative;
}
.light-style .ui-timepicker-list li {
color: #384551;
.flatpickr-months .flatpickr-prev-month.flatpickr-prev-month,
.flatpickr-months .flatpickr-next-month.flatpickr-prev-month {
/*
/*rtl:begin:ignore*/
left: 0; /*
/*rtl:end:ignore*/
} /*
/*rtl:begin:ignore*/
/*
/*rtl:end:ignore*/
.flatpickr-months .flatpickr-prev-month.flatpickr-next-month,
.flatpickr-months .flatpickr-next-month.flatpickr-next-month {
/*
/*rtl:begin:ignore*/
right: 0; /*
/*rtl:end:ignore*/
} /*
/*rtl:begin:ignore*/
/*
/*rtl:end:ignore*/
.flatpickr-months .flatpickr-prev-month:hover,
.flatpickr-months .flatpickr-next-month:hover {
color: #959ea9;
}
.light-style .ui-timepicker-list li:hover {
background: rgba(34, 48, 62, 0.06);
.flatpickr-months .flatpickr-prev-month:hover svg,
.flatpickr-months .flatpickr-next-month:hover svg {
fill: #f64747;
}
.light-style .ui-timepicker-list li:not(.ui-timepicker-selected) .ui-timepicker-duration {
color: #a7acb2;
.flatpickr-months .flatpickr-prev-month svg,
.flatpickr-months .flatpickr-next-month svg {
width: 14px;
height: 14px;
}
.ui-timepicker-list:hover .light-style .ui-timepicker-list li:not(.ui-timepicker-selected) .ui-timepicker-duration {
color: #a7acb2;
.flatpickr-months .flatpickr-prev-month svg path,
.flatpickr-months .flatpickr-next-month svg path {
-webkit-transition: fill 0.1s;
transition: fill 0.1s;
fill: inherit;
}
.light-style .ui-timepicker-list li.ui-timepicker-disabled,
.light-style .ui-timepicker-list li.ui-timepicker-selected.ui-timepicker-disabled {
background: #fff !important;
color: #a7acb2 !important;
.numInputWrapper {
position: relative;
height: auto;
}
.dark-style .ui-timepicker-wrapper {
border: 0 solid #4e4f6c;
z-index: 1091;
background: #2b2c40;
box-shadow: 0 0.25rem 0.75rem 0 rgba(20, 20, 29, 0.24);
border-radius: 0.375rem;
.numInputWrapper input,
.numInputWrapper span {
display: inline-block;
}
.dark-style .ui-timepicker-list li {
color: #d5d5e2;
.numInputWrapper input {
width: 100%;
}
.dark-style .ui-timepicker-list li:hover {
background: rgba(230, 230, 241, 0.06);
.numInputWrapper input::-ms-clear {
display: none;
}
.dark-style .ui-timepicker-list li:not(.ui-timepicker-selected) .ui-timepicker-duration {
color: #7e7f96;
.numInputWrapper input::-webkit-outer-spin-button,
.numInputWrapper input::-webkit-inner-spin-button {
margin: 0;
-webkit-appearance: none;
}
.ui-timepicker-list:hover .dark-style .ui-timepicker-list li:not(.ui-timepicker-selected) .ui-timepicker-duration {
color: #7e7f96;
.numInputWrapper span {
position: absolute;
right: 0;
width: 14px;
padding: 0 4px 0 2px;
height: 50%;
line-height: 50%;
opacity: 0;
cursor: pointer;
border: 1px solid rgba(57, 57, 57, 0.15);
-webkit-box-sizing: border-box;
box-sizing: border-box;
}
.dark-style .ui-timepicker-list li.ui-timepicker-disabled,
.dark-style .ui-timepicker-list li.ui-timepicker-selected.ui-timepicker-disabled {
color: #7e7f96 !important;
background: #2b2c40 !important;
.numInputWrapper span:hover {
background: rgba(0, 0, 0, 0.1);
}
.numInputWrapper span:active {
background: rgba(0, 0, 0, 0.2);
}
.numInputWrapper span:after {
display: block;
content: "";
position: absolute;
}
.numInputWrapper span.arrowUp {
top: 0;
border-bottom: 0;
}
.numInputWrapper span.arrowUp:after {
border-left: 4px solid transparent;
border-right: 4px solid transparent;
border-bottom: 4px solid rgba(57, 57, 57, 0.6);
top: 26%;
}
.numInputWrapper span.arrowDown {
top: 50%;
}
.numInputWrapper span.arrowDown:after {
border-left: 4px solid transparent;
border-right: 4px solid transparent;
border-top: 4px solid rgba(57, 57, 57, 0.6);
top: 40%;
}
.numInputWrapper span svg {
width: inherit;
height: auto;
}
.numInputWrapper span svg path {
fill: rgba(0, 0, 0, 0.5);
}
.numInputWrapper:hover {
background: rgba(0, 0, 0, 0.05);
}
.numInputWrapper:hover span {
opacity: 1;
}
.flatpickr-current-month {
font-size: 135%;
line-height: inherit;
font-weight: 300;
color: inherit;
position: absolute;
width: 75%;
left: 12.5%;
padding: 7.48px 0 0 0;
line-height: 1;
height: 34px;
display: inline-block;
text-align: center;
-webkit-transform: translate3d(0, 0, 0);
transform: translate3d(0, 0, 0);
}
.flatpickr-current-month span.cur-month {
font-family: inherit;
font-weight: 700;
color: inherit;
display: inline-block;
margin-left: 0.5ch;
padding: 0;
}
.flatpickr-current-month span.cur-month:hover {
background: rgba(0, 0, 0, 0.05);
}
.flatpickr-current-month .numInputWrapper {
width: 6ch;
width: 7ch\0;
display: inline-block;
}
.flatpickr-current-month .numInputWrapper span.arrowUp:after {
border-bottom-color: rgba(0, 0, 0, 0.9);
}
.flatpickr-current-month .numInputWrapper span.arrowDown:after {
border-top-color: rgba(0, 0, 0, 0.9);
}
.flatpickr-current-month input.cur-year {
background: transparent;
-webkit-box-sizing: border-box;
box-sizing: border-box;
color: inherit;
cursor: text;
padding: 0 0 0 0.5ch;
margin: 0;
display: inline-block;
font-size: inherit;
font-family: inherit;
font-weight: 300;
line-height: inherit;
height: auto;
border: 0;
border-radius: 0;
vertical-align: initial;
-webkit-appearance: textfield;
-moz-appearance: textfield;
appearance: textfield;
}
.flatpickr-current-month input.cur-year:focus {
outline: 0;
}
.flatpickr-current-month input.cur-year[disabled],
.flatpickr-current-month input.cur-year[disabled]:hover {
font-size: 100%;
color: rgba(0, 0, 0, 0.5);
background: transparent;
pointer-events: none;
}
.flatpickr-current-month .flatpickr-monthDropdown-months {
appearance: menulist;
background: transparent;
border: none;
border-radius: 0;
box-sizing: border-box;
color: inherit;
cursor: pointer;
font-size: inherit;
font-family: inherit;
font-weight: 300;
height: auto;
line-height: inherit;
margin: -1px 0 0 0;
outline: none;
padding: 0 0 0 0.5ch;
position: relative;
vertical-align: initial;
-webkit-box-sizing: border-box;
-webkit-appearance: menulist;
-moz-appearance: menulist;
width: auto;
}
.flatpickr-current-month .flatpickr-monthDropdown-months:focus,
.flatpickr-current-month .flatpickr-monthDropdown-months:active {
outline: none;
}
.flatpickr-current-month .flatpickr-monthDropdown-months:hover {
background: rgba(0, 0, 0, 0.05);
}
.flatpickr-current-month
.flatpickr-monthDropdown-months
.flatpickr-monthDropdown-month {
background-color: transparent;
outline: none;
padding: 0;
}
.flatpickr-weekdays {
background: transparent;
text-align: center;
overflow: hidden;
width: 100%;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-box-align: center;
-webkit-align-items: center;
-ms-flex-align: center;
align-items: center;
height: 28px;
}
.flatpickr-weekdays .flatpickr-weekdaycontainer {
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-box-flex: 1;
-webkit-flex: 1;
-ms-flex: 1;
flex: 1;
}
span.flatpickr-weekday {
cursor: default;
font-size: 90%;
background: transparent;
color: rgba(0, 0, 0, 0.54);
line-height: 1;
margin: 0;
text-align: center;
display: block;
-webkit-box-flex: 1;
-webkit-flex: 1;
-ms-flex: 1;
flex: 1;
font-weight: bolder;
}
.dayContainer,
.flatpickr-weeks {
padding: 1px 0 0 0;
}
.flatpickr-days {
position: relative;
overflow: hidden;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-box-align: start;
-webkit-align-items: flex-start;
-ms-flex-align: start;
align-items: flex-start;
width: 307.875px;
}
.flatpickr-days:focus {
outline: 0;
}
.dayContainer {
padding: 0;
outline: 0;
text-align: left;
width: 307.875px;
min-width: 307.875px;
max-width: 307.875px;
-webkit-box-sizing: border-box;
box-sizing: border-box;
display: inline-block;
display: -ms-flexbox;
display: -webkit-box;
display: -webkit-flex;
display: flex;
-webkit-flex-wrap: wrap;
flex-wrap: wrap;
-ms-flex-wrap: wrap;
-ms-flex-pack: justify;
-webkit-justify-content: space-around;
justify-content: space-around;
-webkit-transform: translate3d(0, 0, 0);
transform: translate3d(0, 0, 0);
opacity: 1;
}
.dayContainer + .dayContainer {
-webkit-box-shadow: -1px 0 0 #e6e6e6;
box-shadow: -1px 0 0 #e6e6e6;
}
.flatpickr-day {
background: none;
border: 1px solid transparent;
border-radius: 150px;
-webkit-box-sizing: border-box;
box-sizing: border-box;
color: #393939;
cursor: pointer;
font-weight: 400;
width: 14.2857143%;
-webkit-flex-basis: 14.2857143%;
-ms-flex-preferred-size: 14.2857143%;
flex-basis: 14.2857143%;
max-width: 39px;
height: 39px;
line-height: 39px;
margin: 0;
display: inline-block;
position: relative;
-webkit-box-pack: center;
-webkit-justify-content: center;
-ms-flex-pack: center;
justify-content: center;
text-align: center;
}
.flatpickr-day.inRange,
.flatpickr-day.prevMonthDay.inRange,
.flatpickr-day.nextMonthDay.inRange,
.flatpickr-day.today.inRange,
.flatpickr-day.prevMonthDay.today.inRange,
.flatpickr-day.nextMonthDay.today.inRange,
.flatpickr-day:hover,
.flatpickr-day.prevMonthDay:hover,
.flatpickr-day.nextMonthDay:hover,
.flatpickr-day:focus,
.flatpickr-day.prevMonthDay:focus,
.flatpickr-day.nextMonthDay:focus {
cursor: pointer;
outline: 0;
background: #e6e6e6;
border-color: #e6e6e6;
}
.flatpickr-day.today {
border-color: #959ea9;
}
.flatpickr-day.today:hover,
.flatpickr-day.today:focus {
border-color: #959ea9;
background: #959ea9;
color: #fff;
}
.flatpickr-day.selected,
.flatpickr-day.startRange,
.flatpickr-day.endRange,
.flatpickr-day.selected.inRange,
.flatpickr-day.startRange.inRange,
.flatpickr-day.endRange.inRange,
.flatpickr-day.selected:focus,
.flatpickr-day.startRange:focus,
.flatpickr-day.endRange:focus,
.flatpickr-day.selected:hover,
.flatpickr-day.startRange:hover,
.flatpickr-day.endRange:hover,
.flatpickr-day.selected.prevMonthDay,
.flatpickr-day.startRange.prevMonthDay,
.flatpickr-day.endRange.prevMonthDay,
.flatpickr-day.selected.nextMonthDay,
.flatpickr-day.startRange.nextMonthDay,
.flatpickr-day.endRange.nextMonthDay {
background: #569ff7;
-webkit-box-shadow: none;
box-shadow: none;
color: #fff;
border-color: #569ff7;
}
.flatpickr-day.selected.startRange,
.flatpickr-day.startRange.startRange,
.flatpickr-day.endRange.startRange {
border-radius: 50px 0 0 50px;
}
.flatpickr-day.selected.endRange,
.flatpickr-day.startRange.endRange,
.flatpickr-day.endRange.endRange {
border-radius: 0 50px 50px 0;
}
.flatpickr-day.selected.startRange + .endRange:not(:nth-child(7n + 1)),
.flatpickr-day.startRange.startRange + .endRange:not(:nth-child(7n + 1)),
.flatpickr-day.endRange.startRange + .endRange:not(:nth-child(7n + 1)) {
-webkit-box-shadow: -10px 0 0 #569ff7;
box-shadow: -10px 0 0 #569ff7;
}
.flatpickr-day.selected.startRange.endRange,
.flatpickr-day.startRange.startRange.endRange,
.flatpickr-day.endRange.startRange.endRange {
border-radius: 50px;
}
.flatpickr-day.inRange {
border-radius: 0;
-webkit-box-shadow: -5px 0 0 #e6e6e6, 5px 0 0 #e6e6e6;
box-shadow: -5px 0 0 #e6e6e6, 5px 0 0 #e6e6e6;
}
.flatpickr-day.flatpickr-disabled,
.flatpickr-day.flatpickr-disabled:hover,
.flatpickr-day.prevMonthDay,
.flatpickr-day.nextMonthDay,
.flatpickr-day.notAllowed,
.flatpickr-day.notAllowed.prevMonthDay,
.flatpickr-day.notAllowed.nextMonthDay {
color: rgba(57, 57, 57, 0.3);
background: transparent;
border-color: transparent;
cursor: default;
}
.flatpickr-day.flatpickr-disabled,
.flatpickr-day.flatpickr-disabled:hover {
cursor: not-allowed;
color: rgba(57, 57, 57, 0.1);
}
.flatpickr-day.week.selected {
border-radius: 0;
-webkit-box-shadow: -5px 0 0 #569ff7, 5px 0 0 #569ff7;
box-shadow: -5px 0 0 #569ff7, 5px 0 0 #569ff7;
}
.flatpickr-day.hidden {
visibility: hidden;
}
.rangeMode .flatpickr-day {
margin-top: 1px;
}
.flatpickr-weekwrapper {
float: left;
}
.flatpickr-weekwrapper .flatpickr-weeks {
padding: 0 12px;
-webkit-box-shadow: 1px 0 0 #e6e6e6;
box-shadow: 1px 0 0 #e6e6e6;
}
.flatpickr-weekwrapper .flatpickr-weekday {
float: none;
width: 100%;
line-height: 28px;
}
.flatpickr-weekwrapper span.flatpickr-day,
.flatpickr-weekwrapper span.flatpickr-day:hover {
display: block;
width: 100%;
max-width: none;
color: rgba(57, 57, 57, 0.3);
background: transparent;
cursor: default;
border: none;
}
.flatpickr-innerContainer {
display: block;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-box-sizing: border-box;
box-sizing: border-box;
overflow: hidden;
}
.flatpickr-rContainer {
display: inline-block;
padding: 0;
-webkit-box-sizing: border-box;
box-sizing: border-box;
}
.flatpickr-time {
text-align: center;
outline: 0;
display: block;
height: 0;
line-height: 40px;
max-height: 40px;
-webkit-box-sizing: border-box;
box-sizing: border-box;
overflow: hidden;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
}
.flatpickr-time:after {
content: "";
display: table;
clear: both;
}
.flatpickr-time .numInputWrapper {
-webkit-box-flex: 1;
-webkit-flex: 1;
-ms-flex: 1;
flex: 1;
width: 40%;
height: 40px;
float: left;
}
.flatpickr-time .numInputWrapper span.arrowUp:after {
border-bottom-color: #393939;
}
.flatpickr-time .numInputWrapper span.arrowDown:after {
border-top-color: #393939;
}
.flatpickr-time.hasSeconds .numInputWrapper {
width: 26%;
}
.flatpickr-time.time24hr .numInputWrapper {
width: 49%;
}
.flatpickr-time input {
background: transparent;
-webkit-box-shadow: none;
box-shadow: none;
border: 0;
border-radius: 0;
text-align: center;
margin: 0;
padding: 0;
height: inherit;
line-height: inherit;
color: #393939;
font-size: 14px;
position: relative;
-webkit-box-sizing: border-box;
box-sizing: border-box;
-webkit-appearance: textfield;
-moz-appearance: textfield;
appearance: textfield;
}
.flatpickr-time input.flatpickr-hour {
font-weight: bold;
}
.flatpickr-time input.flatpickr-minute,
.flatpickr-time input.flatpickr-second {
font-weight: 400;
}
.flatpickr-time input:focus {
outline: 0;
border: 0;
}
.flatpickr-time .flatpickr-time-separator,
.flatpickr-time .flatpickr-am-pm {
height: inherit;
float: left;
line-height: inherit;
color: #393939;
font-weight: bold;
width: 2%;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
-webkit-align-self: center;
-ms-flex-item-align: center;
align-self: center;
}
.flatpickr-time .flatpickr-am-pm {
outline: 0;
width: 18%;
cursor: pointer;
text-align: center;
font-weight: 400;
}
.flatpickr-time input:hover,
.flatpickr-time .flatpickr-am-pm:hover,
.flatpickr-time input:focus,
.flatpickr-time .flatpickr-am-pm:focus {
background: #eee;
}
.flatpickr-input[readonly] {
cursor: pointer;
}
@-webkit-keyframes fpFadeInDown {
from {
opacity: 0;
-webkit-transform: translate3d(0, -20px, 0);
transform: translate3d(0, -20px, 0);
}
to {
opacity: 1;
-webkit-transform: translate3d(0, 0, 0);
transform: translate3d(0, 0, 0);
}
}
@keyframes fpFadeInDown {
from {
opacity: 0;
-webkit-transform: translate3d(0, -20px, 0);
transform: translate3d(0, -20px, 0);
}
to {
opacity: 1;
-webkit-transform: translate3d(0, 0, 0);
transform: translate3d(0, 0, 0);
}
}

View File

@ -29,7 +29,7 @@ const AttendLogs = ({ Id }) => {
<tbody>
{logs
.slice()
.sort((a, b) => new Date(b.activityTime) - new Date(a.activityTime))
.sort((a, b) => b.id - a.id)
.map((log, index) => (
<tr key={index}>
<td>{convertShortTime(log.activityTime)}</td>

View File

@ -38,7 +38,7 @@ const Attendance = ( {attendance, getRole, handleModalData} ) =>
return checkInB - checkInA; // Sort in descending order of checkInTime
})
.map( ( item ) => (
<tr key={item.id}>
<tr key={item.employeeId}>
<td colSpan={2}>
<div className="d-flex justify-content-start align-items-center">
<Avatar
@ -47,7 +47,6 @@ const Attendance = ( {attendance, getRole, handleModalData} ) =>
></Avatar>
<div className="d-flex flex-column">
<a
// href="#"
onClick={(e) =>navigate(`/employee/${item.employeeId}?for=attendance`)}
className="text-heading text-truncate cursor-pointer"
>
@ -60,14 +59,13 @@ const Attendance = ( {attendance, getRole, handleModalData} ) =>
</td>
<td>
<span className="badge bg-label-primary me-1">
{getRole(item.roleID)}
</span>
{item.jobRoleName}
</td>
<td>{item.checkInTime ? convertShortTime(item.checkInTime):"--"}</td>
<td>{item.checkOutTime ? convertShortTime(item.checkOutTime):"--"}</td>
<td className="mx-24" >
<td className="text-center" >
<RenderAttendanceStatus attendanceData={item} handleModalData={handleModalData} Tab={1} currentDate={null}/>
</td>
</tr>

View File

@ -4,7 +4,7 @@ import AttendLogs from './AttendLogs'
import Confirmation from './Confirmation'
const AttendanceModel = ({modelConfig,closeModal,handleSubmitForm}) => {
console.log(modelConfig)
return (
<div className={`modal-dialog modal-md modal-simple ${modelConfig.type === "view" ? "modal-lg":"modal-md"}`} >
<div className="modal-content">

View File

@ -45,12 +45,13 @@ const AttendanceLog = ({ attendance, handleModalData, projectId }) => {
</div>
</div>
<div className="table-responsive">
<div className="table-responsive text-nowrap">
{attendance && attendance.length > 0 ? (
<table className="table mb-0">
<thead>
<tr>
<th>Name</th>
<th className="border-top-1" colSpan={2}>Name</th>
<th className="border-top-1" >Role</th>
<th>
<i className="bx bxs-down-arrow-alt text-success"></i> Check-In
</th>
@ -69,7 +70,7 @@ const AttendanceLog = ({ attendance, handleModalData, projectId }) => {
{renderAttendanceData?.map((attendance, index) => (
<tr key={index}>
<td>
<td colSpan={2}>
<div className="d-flex justify-content-start align-items-center">
<Avatar firstName={attendance.firstName} lastName={attendance.lastName} />
<div className="d-flex flex-column">
@ -81,6 +82,7 @@ const AttendanceLog = ({ attendance, handleModalData, projectId }) => {
</div>
</div>
</td>
<td>{ attendance.jobRoleName}</td>
<td>{convertShortTime(attendance.checkInTime)}</td>
<td>{attendance.checkOutTime ? convertShortTime(attendance.checkOutTime) : '--'}</td>
<td className="text-center">

View File

@ -11,7 +11,7 @@ import {checkIfCurrentDate} from "../../utils/dateUtils";
const schema = z.object({
time: z.string().nonempty({message:"Time is required"}),
markTime: z.string().nonempty({message:"Time is required"}),
description:z.string().optional()
});
@ -35,18 +35,18 @@ const CheckCheckOutmodel = ({modeldata,closeModal,handleSubmitForm,}) => {
const onSubmit = ( data ) =>
{
console.log(data)
let record = {...data, date: new Date().toLocaleDateString(),latitude:coords.latitude,longitude:coords.longitude,employeeId:modeldata.employeeId,action:modeldata.action}
let record = {...data, date: new Date().toLocaleDateString(),latitude:coords.latitude,longitude:coords.longitude,employeeId:modeldata.employeeId,action:modeldata.action,id:modeldata?.id || null}
if(modeldata.forWhichTab === 1){
handleSubmitForm(record)
} else
{
console.log("is Date" ,checkIfCurrentDate(modeldata?.currentDate))
if ( modeldata?.currentDate && checkIfCurrentDate(modeldata?.currentDate) )
{
handleSubmitForm(record)
} else
{
let formData = {...data, date: new Date().toLocaleDateString(),latitude:coords.latitude,longitude:coords.longitude,employeeId:modeldata.employeeId,projectId:projectId,action:modeldata.action}
let formData = {...data, date: new Date().toLocaleDateString(),latitude:coords.latitude,longitude:coords.longitude,employeeId:modeldata.employeeId,projectId:projectId,action:modeldata.action,id:modeldata?.id || null}
dispatch(markAttendance(formData))
.unwrap()
.then(() => {
@ -67,14 +67,12 @@ const CheckCheckOutmodel = ({modeldata,closeModal,handleSubmitForm,}) => {
<form className="row g-2" onSubmit={handleSubmit(onSubmit)}>
<div className="col-12 col-md-12">
{/* <TimePicker label="Time" onChange={(e) => setValue("time", e)} /> */}
<TimePicker
label="Choose a time"
onChange={(e) => setValue("time", e)}
onChange={(e) => setValue("markTime", e)}
interval={10}
/>
{errors.time && <p className="text-danger">{errors.time.message}</p>}
{errors. markTime && <p className="text-danger">{errors.markTime.message}</p>}
</div>
@ -123,7 +121,7 @@ const schemaReg = z.object({
export const Regularization = ({modeldata,closeModal,handleSubmitForm})=>{
const [isLoading, setIsLoading] = useState(false);
const coords = usePositionTracker();
console.log(modeldata)
const {
register,
handleSubmit,
@ -138,10 +136,12 @@ export const Regularization = ({modeldata,closeModal,handleSubmitForm})=>{
};
const onSubmit = (data) => {
let record = {...data, date: new Date().toLocaleDateString(),latitude:coords.latitude,longitude:coords.longitude}
const onSubmit = ( data ) =>
{
let record = {...data, date: new Date().toLocaleDateString(),latitude:coords.latitude,longitude:coords.longitude, }
console.log(record)
handleSubmitForm(record)
closeModal()
};

View File

@ -1,567 +1,114 @@
import React, { useState, useEffect } from "react";
import "../../components/Project/ProjectInfra.css";
import BuildingModel from "../../components/Project/BuildingModel";
import FloorModel from "../../components/Project/FloorModel";
import BuildingModel from "../Project/Infrastructure/BuildingModel";
import FloorModel from "../Project/Infrastructure/FloorModel";
import showToast from "../../services/toastService";
import WorkAreaModel from "../../components/Project/WorkAreaModel";
import TaskModel from "../../components/Project/TaskModel";
import WorkAreaModel from "../Project/Infrastructure/WorkAreaModel";
import TaskModel from "../Project/Infrastructure/TaskModel";
import ProjectRepository from "../../repositories/ProjectRepository";
import Breadcrumb from "../../components/common/Breadcrumb";
import {useProjectDetails, useProjects} from "../../hooks/useProjects";
import {useHasUserPermission} from "../../hooks/useHasUserPermission";
import {MANAGE_PROJECT_INFRA} from "../../utils/constants";
import {useDispatch, useSelector} from "react-redux";
import {useProfile} from "../../hooks/useProfile";
import {setProjectId} from "../../slices/localVariablesSlice";
import InfraTable from "../Project/Infrastructure/InfraTable";
const InfraPlanning = ({ data, activityMaster, onDataChange }) => {
const [expandedBuildings, setExpandedBuildings] = useState([]);
const [project, setProject] = useState(data);
const [buildings, setBuildings] = useState(data?.buildings);
const InfraPlanning = () =>
{
const {profile: LoggedUser} = useProfile()
const dispatch = useDispatch()
const {projects,loading:project_listLoader,error:projects_error} = useProjects()
const [isBuildingModalOpen, setIsBuildingModalOpen] = useState(false);
const [isFloorModalOpen, setIsFloorModalOpen] = useState(false);
const [isWorkAreaModelOpen, setIsWorkAreaModalOpen] = useState(false);
const [isTaskModelOpen, setIsTaskModalOpen] = useState(false);
const [isCreateModalOpen, setIsCreateModalOpen] = useState(false);
const [clearFormTrigger, setClearFormTrigger] = useState(false);
useEffect(() => {
setProject(data);
setBuildings(data?.buildings);
}, [data]);
const openModal = (projectData) => {
setIsCreateModalOpen(true);
};
const openFloorModel = (projectData) => {
setIsFloorModalOpen(true);
};
const closeFloorModel = () => {
setIsFloorModalOpen(false);
// const modalBackdrop = document.querySelector(".modal-backdrop");
// if (modalBackdrop) modalBackdrop.remove();
};
const handleFloorModelFormSubmit = (updatedFloor) => {
if (updatedFloor.id == "") delete updatedFloor.id;
submitData([
{
building: null,
floor: updatedFloor,
workArea: null,
},
]);
};
const submitData = (infraObject) => {
ProjectRepository.manageProjectInfra(infraObject)
.then((response) => {
fetchData();
onDataChange("building-change");
showToast("Details updated successfully.", "success");
setClearFormTrigger(true); // Set trigger to true
})
.catch((error) => {
showToast(error.message, "error");
});
// api
// .post("/api/project/manage-infra", infraObject)
// .then((data) => {
// fetchData();
// onDataChange("building-change");
// showToast("Details updated successfully.", "success");
// setClearFormTrigger(true); // Set trigger to true
// })
// .catch((error) => {
// showToast(error.message, "error");
// });
};
const openBuildingModel = (projectData) => {
setIsBuildingModalOpen(true);
};
const closeBuildingModel = () => {
setIsBuildingModalOpen(false);
};
const handleBuildingModelFormSubmit = (buildingmodel) => {
if (buildingmodel.id == "" || buildingmodel.id == 0)
delete buildingmodel.id;
let data = [
{
building: buildingmodel,
floor: null,
workArea: null,
},
];
submitData(data);
};
const openWorkAreaModel = (projectData) => {
setIsWorkAreaModalOpen(true);
};
const closeWorkAreaModel = () => {
setIsWorkAreaModalOpen(false);
};
const handleWorkAreaModelFormSubmit = (updatedModel) => {
if (updatedModel.id == "") delete updatedModel.id;
submitData([
{
building: null,
floor: null,
workArea: updatedModel,
},
]);
};
const openTaskModel = (projectData) => {
setIsTaskModalOpen(true);
};
const closeTaskModel = () => {
setIsTaskModalOpen(false);
// const modalBackdrop = document.querySelector(".modal-backdrop");
// if (modalBackdrop) modalBackdrop.remove();
};
const handleTaskModelFormSubmit = (updatedModel) => {
if (updatedModel.id == "") updatedModel.id = 0;
//console.log("Form submitted:", updatedModel); // Replace this with an API call or state update
ProjectRepository.manageProjectTasks([updatedModel])
.then((response) => {
onDataChange("task-change");
showToast("Details updated successfully.", "success");
setClearFormTrigger(true); // Set trigger to true
})
.catch((error) => {
showToast(error.message, "error");
});
// api
// .post("/api/project/task", [updatedModel])
// .then((data) => {
// onDataChange("task-change");
// showToast("Details updated successfully.", "success");
// setClearFormTrigger(true); // Set trigger to true
// })
// .catch((error) => {
// showToast(error.message, "error");
// });
};
const toggleBuilding = (id) => {
setExpandedBuildings((prev) =>
prev.includes(id) ? prev.filter((bid) => bid !== id) : [...prev, id]
);
};
const getContent = (building) => {
let hasFloors =
building.floors && building.floors.length > 0 ? true : false;
return (
<>
{(() => {
if (hasFloors) {
return building.floors.map((floor) => (
<React.Fragment key={floor.id}>
{floor.workAreas.length > 0 ? (
floor.workAreas.map((workArea) => (
<React.Fragment key={workArea.id}>
<tr>
<td colSpan="4" className="text-start table-cell">
<div className="row ps-2">
<div className="col-6">
{" "}
<h6>
<span>
{" "}
{floor.floorName} - {workArea.areaName} &nbsp;{" "}
</span>
</h6>
</div>
<div className="col-6 text-end">
{/* <a
type="button"
className="text-end me-2"
data-bs-toggle="modal"
data-bs-target="#floor-model"
onClick={() => openFloorModel()}
>
<i className="bx bx-edit-alt me-2"></i>
Edit Floor
</a> */}
{/* <a
type="button"
className="text-end"
data-bs-toggle="modal"
data-bs-target="#editproject"
onClick={() => openModal()}
>
<i className="bx bx-plus-circle me-2"></i>
Add Activities
</a> */}
</div>
</div>
</td>
</tr>
{workArea.workItems.length > 0 ? (
<tr>
{" "}
<td colSpan={4}>
<table
className="table ms-4"
style={{ width: "100%" }}
>
<thead>
<tr>
<th>Activity</th>
<th>Planned</th>
<th>Complated</th>
<th>Progress</th>
<th>Actions</th>
</tr>
</thead>
<tbody className="table-border-bottom-0">
{workArea.workItems.map((workItem) => (
<React.Fragment key={workItem.workItemId}>
<tr>
<td className="text-start table-cell-small">
<i className="bx bx-right-arrow-alt"></i>
<span className="fw-medium">
{workItem.workItem.activityMaster
? workItem.workItem.activityMaster
.activityName
: "NA"}
</span>
</td>
<td className="text-center">
{workItem.workItem
? workItem.workItem.plannedWork
: "NA"}
</td>
<td className="text-center">
{workItem.workItem
? workItem.workItem.completedWork
: "NA"}
</td>
<td
className="text-center"
style={{ width: "15%" }}
>
<div className="progress p-0">
<div
className="progress-bar"
role="progressbar"
style={{
width: getProgress(
workItem.workItem.plannedWork,
workItem.workItem.completedWork
),
height: "10px",
}}
aria-valuenow={
workItem.workItem
? workItem.workItem
.completedWork
: 0
}
aria-valuemin="0"
aria-valuemax={
workItem.workItem
? workItem.workItem.plannedWork
: 0
}
></div>
</div>
{/* <input type="range" id="customRange1" max="@workItem.PlannedWork" min="0" value="@workItem.CompletedWork"> */}
</td>
<td>
<div className="dropdown">
<button
aria-label="Modify"
type="button"
className="btn p-0 dropdown-toggle hide-arrow"
>
<i class="bx bxs-edit me-2 text-primary"></i>
</button>
<button
aria-label="Delete"
type="button"
className="btn p-0 dropdown-toggle hide-arrow"
>
<i className="bx bx-trash me-1 text-danger"></i>{" "}
</button>
</div>
</td>
</tr>{" "}
</React.Fragment>
))}
</tbody>
</table>{" "}
</td>
</tr>
) : (
<span></span>
)}
</React.Fragment>
))
) : (
<React.Fragment key={building.id + floor.id}>
<tr>
<td colSpan="4" className="text-start table-cell">
<div className="row ps-2">
<div className="col-6">
{" "}
<h6>
<span> {floor.floorName} &nbsp; </span>
</h6>
</div>
<div className="col-6 text-end">
{/* <a
type="button"
className="text-end me-2"
data-bs-toggle="modal"
data-bs-target="#floor-model"
onClick={() => openFloorModel()}
>
<i className="bx bx-edit-alt me-2"></i>
Edit Floor
</a> */}
{/* <a
type="button"
className="text-end"
data-bs-toggle="modal"
data-bs-target="#editproject"
onClick={() => openModal()}
>
<i className="bx bx-plus-circle me-2"></i>
Add Work Area
</a> */}
</div>
</div>
</td>
</tr>
</React.Fragment>
)}
</React.Fragment>
));
} else {
return (
<>
<tr>
<td>
<p>No Floors Added, Please add them</p>
{/* <p>
<button
type="button"
data-bs-toggle="modal"
className="btn btn-sm btn-danger m-1"
data-bs-target="#editproject"
onClick={() => openModal()}
>
<i className="bx bx-plus-circle me-2"></i>
Add Floors
</button>
</p> */}
</td>
</tr>
</>
);
}
})()}
</>
);
};
const getProgress = (planned, completed) => {
return (completed * 100) / planned + "%";
};
const selectedProject = useSelector((store)=>store.localVariables.projectId)
const ManageInfra = useHasUserPermission( MANAGE_PROJECT_INFRA )
const {projects_Details, loading: project_deatilsLoader, error: project_error} = useProjectDetails(selectedProject)
useEffect( () =>
{
dispatch(setProjectId(projects[0]?.id))
},[projects])
return (
<>
{isBuildingModalOpen && (
<div
className={`modal fade `}
id="building-model"
tabIndex="-1"
aria-hidden="true"
>
<BuildingModel
project={data}
onClose={closeBuildingModel}
onSubmit={handleBuildingModelFormSubmit}
clearTrigger={clearFormTrigger}
onClearComplete={() => setClearFormTrigger(false)}
></BuildingModel>
</div>
)}
{isFloorModalOpen && (
<div
className={`modal fade `}
id="floor-model"
tabIndex="-1"
aria-hidden="true"
>
<FloorModel
project={data}
// building={null}
onClose={closeFloorModel}
onSubmit={handleFloorModelFormSubmit}
clearTrigger={clearFormTrigger}
onClearComplete={() => setClearFormTrigger(false)}
></FloorModel>
</div>
)}
{isWorkAreaModelOpen && (
<div
className={`modal fade `}
id="work-area-model"
tabIndex="-1"
aria-hidden="true"
>
<WorkAreaModel
project={data}
// building={null}
onClose={closeWorkAreaModel}
onSubmit={handleWorkAreaModelFormSubmit}
clearTrigger={clearFormTrigger}
onClearComplete={() => setClearFormTrigger(false)}
></WorkAreaModel>
</div>
)}
{isTaskModelOpen && (
<div
className={`modal fade `}
id="task-model"
tabIndex="-1"
aria-hidden="true"
>
<TaskModel
project={data}
activities={activityMaster}
onClose={closeTaskModel}
onSubmit={handleTaskModelFormSubmit}
clearTrigger={clearFormTrigger}
onClearComplete={() => setClearFormTrigger(false)}
></TaskModel>
</div>
)}
<div className="col-md-12 col-lg-12 col-xl-12 order-0 mb-4">
<div className="card">
{/* <div className="card-header pb-4"></div> */}
<div className="card-body" style={{ padding: "0.5rem" }}>
<div className="align-items-center">
<div className="row">
<div className="col-12 text-end mb-1">
<button
type="button"
className="link-button link-button-sm m-1"
data-bs-toggle="modal"
data-bs-target="#building-model"
onClick={() => openBuildingModel()}
>
<i className="bx bx-plus-circle me-2"></i>
Manage Building
</button>
<button
type="button"
data-bs-toggle="modal"
className="link-button m-1"
data-bs-target="#floor-model"
onClick={() => openFloorModel()}
>
<i className="bx bx-plus-circle me-2"></i>
Manage Floors
</button>
<button
type="button"
data-bs-toggle="modal"
className="link-button m-1"
data-bs-target="#work-area-model"
onClick={() => openWorkAreaModel()}
>
<i className="bx bx-plus-circle me-2"></i>
Manage Work Areas
</button>
<button
type="button"
data-bs-toggle="modal"
className="link-button m-1"
data-bs-target="#task-model"
onClick={() => openTaskModel()}
>
<i className="bx bx-plus-circle me-2"></i>
Manage Tasks
</button>
</div>
<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="align-items-center">
<div className="row ">
<div className="col-sm-3 col-8 text-start mb-1">
<select name="DataTables_Table_0_length"
aria-controls="DataTables_Table_0"
className="form-select form-select-sm"
value={selectedProject}
onChange={(e)=>dispatch(setProjectId(e.target.value))}
aria-label=""
>
{(project_listLoader || projects.length < 0) && <option value="Loading..." disabled>Loading...</option> }
{!project_listLoader && projects?.filter(project =>
LoggedUser?.projects?.map(Number).includes(project.id)).map((project)=>(
<option key={project.id} value={project.id}>{project.name}</option>
))}
</select>
</div>
<div className="row">
<div className="col-12">
{buildings && buildings.length > 0 && (
<table className="table table-bordered">
<tbody>
{buildings.map((building) => (
building.floors && building.floors.length > 0 && (
<React.Fragment key={building.id}>
<tr>
<td
colSpan="4"
className="text-start"
style={{
background: "#f0f0f0",
cursor: "pointer",
}}
onClick={() => toggleBuilding(building.id)}
>
<div className="row">
<h5 style={{ marginBottom: "0px" }}>
{ building.name} &nbsp;
{expandedBuildings.includes(building.id) ? (
<i className="bx bx-chevron-down"></i>
) : (
<i className="bx bx-chevron-right"></i>
)}
</h5>
</div>
</td>
</tr>
{expandedBuildings.includes(building.id) && getContent(building)}
</React.Fragment>
)
))}
</tbody>
</table>
)}
</div>
</div>
</div>
{/* <div className={`col-12 text-end mb-1 ${!ManageInfra && 'd-none'} `} >
<button
type="button"
className="link-button link-button-sm m-1 "
data-bs-toggle="modal"
data-bs-target="#building-model"
// onClick={() => openBuildingModel()}
>
<i className="bx bx-plus-circle me-2"></i>
Manage Building
</button>
<button
type="button"
data-bs-toggle="modal"
className="link-button m-1"
data-bs-target="#floor-model"
// onClick={() => openFloorModel()}
>
<i className="bx bx-plus-circle me-2"></i>
Manage Floors
</button>
<button
type="button"
data-bs-toggle="modal"
className="link-button m-1"
data-bs-target="#work-area-model"
// onClick={() => openWorkAreaModel()}
>
<i className="bx bx-plus-circle me-2"></i>
Manage Work Areas
</button>
<button
type="button"
data-bs-toggle="modal"
className="link-button m-1"
data-bs-target="#task-model"
// onClick={() => openTaskModel()}
>
<i className="bx bx-plus-circle me-2"></i>
Manage Tasks
</button>
</div> */}
</div>
<div className="row ">
{project_deatilsLoader && ( <p>Loading...</p> )}
{( !project_deatilsLoader && projects_Details?.buildings.length === 0 ) && ( <p>No Result Found</p> )}
{(!project_deatilsLoader && projects_Details?.buildings?.length > 0) && (<InfraTable buildings={projects_Details?.buildings}/>)}
</div>
</div>
</div>
</div>
</>
</div>
);
};

View File

@ -2,35 +2,43 @@ import React, { useEffect, useState } from 'react';
import Avatar from '../common/Avatar';
import { convertShortTime } from '../../utils/dateUtils';
import RegularizationActions from './RegularizationActions';
import {useSelector} from 'react-redux';
import {useRegularizationRequests} from '../../hooks/useAttendance';
import moment from 'moment';
const Regularization = ({attendance,handleRequest}) => {
const[attendances,setAttendances] = useState(attendance)
const regularize_Requests = attendances.filter((att)=>att.activity === 2)
const Regularization = ( { handleRequest} ) =>
{
var selectedProject = useSelector((store) => store.localVariables.projectId);
const [ regularizesList, setregularizedList ] = useState( [] )
const { regularizes, loading,error,refetch} = useRegularizationRequests(selectedProject)
useEffect(()=>{
setAttendances(attendance)
},[attendance])
setregularizedList(regularizes)
},[regularizes])
return (
<div className="table-responsive">
<div className="table-responsive text-nowrap">
<table className="table mb-0">
<thead>
<tr>
<th>Name</th>
<th colSpan={2}>Name</th>
<th>Date</th>
<th><i className='bx bxs-down-arrow-alt text-success' ></i>Check-In</th>
<th><i className='bx bxs-up-arrow-alt text-danger' ></i>Check-Out</th>
<th>Action</th>
</tr>
</thead>
<tbody>
<tbody>
{loading &&<td colSpan={5}>Loading...</td>}
{
regularize_Requests.length > 0 ? (
regularize_Requests?.map((att, index) => (
regularizes?.length > 0 ? (
regularizes?.map((att, index) => (
<tr key={index}>
<td>
<td colSpan={2}>
<div className="d-flex justify-content-start align-items-center">
<Avatar
firstName={att.firstName}
@ -48,18 +56,18 @@ const Regularization = ({attendance,handleRequest}) => {
</div>
</div>
</td>
<td>{att.date}</td>
<td>{moment(att.checkOutTime).format("DD-MMM-YYYY")}</td>
<td>{convertShortTime(att.checkInTime)}</td>
<td>{att.checkOutTime ? convertShortTime(att.checkOutTime):"--"}</td>
<td className='text-center ' >
{/* <div className='d-flex justify-content-center align-items-center gap-3'> */}
<RegularizationActions attendanceData={att} handleRequest={handleRequest} />
<RegularizationActions attendanceData={att} handleRequest={handleRequest} refresh={refetch } />
{/* </div> */}
</td>
</tr>
))
):(
<td colSpan={5}>No Result Found</td>
<td colSpan={5}>No Record Found</td>
)
}
</tbody>

View File

@ -1,21 +1,24 @@
import React, { act, useEffect, useState } from 'react'
import useAttendanceStatus, { ACTIONS } from '../../hooks/useAttendanceStatus';
// import AttendanceRepository from '../../repositories/AttendanceRepository';
import { useSelector } from 'react-redux';
import { useDispatch, useSelector } from 'react-redux';
import { usePositionTracker } from '../../hooks/usePositionTracker';
import {markCurrentAttendance} from '../../slices/apiSlice/attendanceAllSlice';
import {cacheData, getCachedData} from '../../slices/apiDataManager';
import showToast from '../../services/toastService';
const RegularizationActions = ({attendanceData,handleRequest}) => {
const RegularizationActions = ({attendanceData,handleRequest,refresh}) => {
const [status,setStatus] = useState()
const [loadingApprove,setLoadingForApprove] = useState(false)
const [loadingReject,setLoadingForReject] = useState(false)
const projectId = useSelector((store)=>store.localVariables.projectId)
const {latitude,longitude} = usePositionTracker();
const dispatch = useDispatch()
const handleRegularization =(request_attendance,IsReqularize)=>{
if(IsReqularize){
setLoadingForApprove(true)
}else{
@ -34,8 +37,22 @@ const {latitude,longitude} = usePositionTracker();
action:IsReqularize ? 4 : 5,
image:null
}
console.log(req_Data)
handleRequest(req_Data)
dispatch( markCurrentAttendance( req_Data ) ).then( ( action ) =>
{
const regularizedList = getCachedData("regularizedList")
const updatedata = regularizedList?.data?.filter( item => item.id !== action.payload.id );
cacheData("regularizedList",{data:updatedata,projectId:projectId})
setLoadingForApprove( false )
setLoadingForReject( false )
refresh()
}).catch( ( error ) =>
{
showToast(error.message,"error")
});
}
@ -55,7 +72,7 @@ const {latitude,longitude} = usePositionTracker();
return (
<div className="w-auto d-flex gap-2 align-items-center justify-content-between">
<div className="w-auto d-flex gap-2 align-items-center justify-content-end">
{attendanceData.activity == 2 && (
<>
<button

View File

@ -9,7 +9,7 @@ const RenderAttendanceStatus = ({ attendanceData, handleModalData,Tab,currentDat
const handleButtonClick = (key) => {
if(key === 6){
handleModalData({action:6,id :attendanceData?.id})
handleModalData({action:6,id:attendanceData?.id})
}else{
handleModalData({
@ -22,8 +22,9 @@ const RenderAttendanceStatus = ({ attendanceData, handleModalData,Tab,currentDat
}
};
return (
<div className="w-auto d-flex gap-2 align-items-center justify-content-between">
<div className="d-flex justify-content-end">
<button
type="button"
className={`btn btn-xs ${color}`}
@ -39,7 +40,7 @@ const RenderAttendanceStatus = ({ attendanceData, handleModalData,Tab,currentDat
{attendanceData?.checkInTime && (
<button
type="button"
className="btn btn-xs btn-success"
className="btn btn-xs btn-success ms-2"
tabIndex="0"
aria-controls="DataTables_Table_0"
data-bs-toggle="modal"

View File

@ -0,0 +1,174 @@
import React, { useState } from "react";
import { formatDate } from "../../utils/dateUtils";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
import showToast from "../../services/toastService";
import { TasksRepository } from "../../repositories/TaskRepository";
export const ReportTask = ({ report, closeModal, refetch }) => {
const [loading, setloading] = useState(false);
const schema = z.object({
completedTask: z
.number()
.min(1, "Completed Work must be at least 1")
.int("Completed Work must be an integer")
.positive("Completed Work must be a positive number")
.optional(),
comment: z.string().min(1, "Comment cannot be empty"),
});
const {
register,
handleSubmit,
formState: { errors },
} = useForm({
resolver: zodResolver(schema),
});
const onSubmit = async (data) => {
try {
setloading(true);
const reportData = {
...data,
id: report?.id,
reportedDate: new Date().toISOString(),
};
let response = await TasksRepository.reportTsak(reportData);
showToast("succesfully", "success");
refetch();
closeModal();
} catch (error) {
showToast("Somthing wrog", "error");
}
};
const handleClose = () => {
closeModal();
};
return (
<div
className="modal-dialog modal-md modal-simple report-task-modal"
role="document"
>
<div className="modal-content">
<div className="modal-body px-1">
<button
type="button"
className="btn-close"
onClick={handleClose}
aria-label="Close"
></button>
<div className="container m-0">
<div class="mb-1 row text-start">
<label for="html5-text-input" class="col-md-4 col-form-label">
Assigned Date :{" "}
</label>
<div class="col-md-8 text-start">
<label class="col-md-2 col-form-label">
{formatDate(report?.assignmentDate)}
</label>
</div>
</div>
<div class="mb-1 row text-start">
<label for="html5-search-input" class="col-md-4 col-form-label">
Assigned By :{" "}
</label>
<div class="col-md-8 text-start">
<label class=" col-form-label">{` ${report?.assignedBy.firstName} ${report?.assignedBy.lastName}`}</label>
</div>
</div>
<div class="mb-1 row text-start">
<label for="html5-email-input" class="col-md-4 col-form-label">
Wrok Area :
</label>
<div class="col-md-8 text-start text-wrap">
<label class=" col-form-label">
{" "}
{report?.workItem?.workArea?.floor?.building?.name}{" "}
<i class="bx bx-chevron-right"></i>{" "}
{report?.workItem?.workArea?.floor?.floorName}{" "}
<i class="bx bx-chevron-right"> </i>
{report?.workItem?.workArea?.areaName}
</label>
</div>
</div>
<div class="mb-1 row text-start">
<label for="html5-email-input" class="col-md-4 col-form-label">
Activity :
</label>
<div class="col-md-8 text-start text-wrap">
<label class=" col-form-label">
{report?.workItem?.activityMaster.activityName}
</label>
</div>
</div>
<div class="mb-1 row text-start">
<label for="html5-email-input" class="col-md-4 col-form-label">
Team Size :
</label>
<div class="col-md-8 text-start text-wrap">
<label class=" col-form-label">
{report?.teamMembers?.length}
</label>
</div>
</div>
<form onSubmit={handleSubmit(onSubmit)}>
<div class="mb-1 row text-start">
<label for="html5-email-input" class="col-md-4 col-form-label">
Completed Work
</label>
<div class="col-md-8 text-start text-wrap">
<input
{...register("completedTask", { valueAsNumber: true })}
id="smallInput"
className="form-control form-control-sm"
type="number"
placeholder="Completed Work"
/>
{errors.completedTask && (
<div className="text-danger">
{errors.completedTask.message}
</div>
)}
</div>
</div>
<div class="mb-1 row text-start">
<label for="html5-email-input" class="col-md-4 col-form-label">
Comment
</label>
<div class="col-md-8 text-start text-wrap">
<textarea
{...register("comment")}
className="form-control"
id="exampleFormControlTextarea1"
rows="1"
placeholder="Enter comment"
/>
{errors.comment && (
<div className="danger-text">{errors.comment.message}</div>
)}
</div>
</div>
<div className="col-12 text-center my-2">
<button type="submit" className="btn btn-sm btn-primary me-3">
{loading ? "Please wait" : "Submit Report"}
</button>
<button
type="button"
className="btn btn-sm btn-label-secondary"
onClick={handleClose}
>
Cancel
</button>
</div>
</form>
</div>
</div>
</div>
</div>
);
};

View File

@ -0,0 +1,155 @@
import React, { useEffect, useState } from "react";
import { useProfile } from "../../hooks/useProfile";
import moment from "moment";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
import { TasksRepository } from "../../repositories/TaskRepository";
import showToast from "../../services/toastService";
const schema = z.object({
comment: z.string().min(1, "Comment cannot be empty"),
});
const ReportTaskComments = ({ commentsData, closeModal }) => {
const [loading, setloading] = useState(false);
const { profile } = useProfile();
const [comments, setComment] = useState([]);
const {
register,
handleSubmit,
formState: { errors },
reset,
} = useForm({
resolver: zodResolver(schema),
});
useEffect(() => {
setComment(commentsData?.comments);
}, [commentsData]);
const isLoggedUser = (usrId) => profile?.employeeInfo.id;
const onSubmit = async (data) => {
let sendComment = {
...data,
taskAllocationId: commentsData?.id,
commentDate: new Date().toISOString(),
};
try {
setloading(true);
// const resp = await TasksRepository.taskComments( sendComment );
// console.timeLog( resp )
reset();
setloading(false);
showToast("Successfully Sent", "success");
closeModal();
} catch (err) {
setloading(false);
showToast(error.response.data?.message || "Something wrong", "error");
}
};
return (
<div
className="modal-dialog modal-md modal-simple report-task-comments-modal"
role="document"
>
<div className="modal-content">
<div className="modal-body px-1">
<button
type="button"
className="btn-close"
onClick={closeModal}
aria-label="Close"
></button>
<div className="container ">
{
comments && comments.map( ( data ) =>
(
<div className="text-start" key={data.id}>
<div class={`li-wrapper d-flex justify-content-${isLoggedUser(data?.employee?.id) ? "end":"start"} align-items-start`}>
<div class="avatar avatar-xs me-1">
<span class="avatar-initial rounded-circle bg-label-success">
{data?.employee?.firstName.slice(0,1)}
</span>
</div>
<div class="text-start py-0">
<p class="mb-0">
<strong>{ `${data?.employee?.firstName} ${data?.employee?.lastName}`}</strong>
</p>
<small style={{fontSize: "10px"}}>{ moment(data?.commentDate).fromNow()}</small>
</div>
</div>
<p className={`ms-${ isLoggedUser( data?.employee?.id ) ? "0 text-end me-6" : "6 " } mt-1`}>{ data?.comment
}</p>
</div>
))}
{/* by other users */}
{/* <div className="text-start">
<div className="li-wrapper d-flex justify-content-start align-items-start">
<div className="avatar avatar-xs me-1">
<span className="avatar-initial rounded-circle bg-label-success">
M
</span>
</div>
<div className="text-start py-0">
<p className="mb-0">
<strong>Mahajan</strong>
</p>
<small style={{ fontSize: "10px" }}>2 hour ago</small>
</div>
</div>
<p className="ms-6 mt-1">Stylized implementation of HTMLs element for abbreviations and acronyms to show the expanded version on hover. Abbreviations have a default underline and gain a help cursor to provide additional context on hov </p>
</div> */}
{/* by login usrer */}
{/* <div className="text-start">
<div className="li-wrapper d-flex justify-content-end align-items-start">
<div className="avatar avatar-xs me-1">
<span className="avatar-initial rounded-circle bg-label-success">
M
</span>
</div>
<div className"text-start py-0">
<p className="mb-0">
<strong>Pramod Mahajan</strong>
</p>
<small style={{ fontSize: "10px" }}>2 hour ago</small>
</div>
</div>
<p className="ms-6 mt-1">Stylized implementation of HTMLs element for abbreviations and acronyms to show the expanded version on hover. Abbreviations have a default underline and gain a help cursor to provide additional context on hov </p>
</div> */}
<form onSubmit={handleSubmit(onSubmit)}>
<textarea
{...register("comment")}
className="form-control"
id="exampleFormControlTextarea1"
rows="1"
placeholder="Enter comment"
/>
{errors.comment && (
<div className="danger-text">{errors.comment.message}</div>
)}
<div className="text-end my-1">
<button
type="button"
className="btn btn-sm btn-secondary"
onClick={closeModal}
data-bs-dismiss="modal"
>
Close
</button>
<button type="submit" className="btn btn-sm btn-primary ms-2">
{loading ? "Sending..." : "Comment"}
</button>
</div>
</form>
</div>
</div>
</div>
</div>
);
};
export default ReportTaskComments;

View File

@ -0,0 +1,134 @@
import React from "react";
import ReactApexChart from "react-apexcharts";
import PropTypes from "prop-types";
const HorizontalBarChart = ({
seriesData = [],
categories = [],
colors = [
"#1E90FF", "#00BFFF", "#9370DB", "#6A0DAD", "#A9A9A9",
"#6A5ACD", "#FFA500", "#FF4500", "#20B2AA", "#708090",
],
loading = false,
}) => {
// Show loading state
if (loading) {
return (
<div className="w-full h-[380px] flex items-center justify-center bg-gray-100 rounded-xl">
<span className="text-gray-500 text-sm">Loading chart...</span>
{/* Replace this with a skeleton or spinner if you prefer */}
</div>
);
}
// Guard clause for invalid or incomplete data
const hasValidData =
Array.isArray(seriesData) &&
seriesData.length > 0 &&
Array.isArray(categories) &&
categories.length === seriesData.length;
if (!hasValidData) {
return <div className="text-center text-gray-500">No data to display</div>;
}
// Combine seriesData and categories, then sort in descending order
const combined = seriesData.map((value, index) => ({
value,
label: categories[index],
}));
const sorted = combined.sort((a, b) => b.value - a.value);
// Extract sorted values
const sortedSeriesData = sorted.map(item => item.value);
const sortedCategories = sorted.map(item => item.label);
// Replace 0 with 1 for visual purposes, but display "0%" in labels
const adjustedSeriesData = sortedSeriesData.map(val => (val === 0 ? 1 : val));
// Dynamically adjust chart height if only one data point
const chartHeight = seriesData.length === 1 ? 80 : 380;
const chartOptions = {
chart: {
type: "bar",
height: chartHeight,
toolbar: { show: false },
},
grid: { show: false },
plotOptions: {
bar: {
barHeight: seriesData.length === 1 ? "30%" : "60%",
distributed: true,
horizontal: true,
borderRadius: 3,
borderRadiusApplication: "end",
dataLabels: {
position: "top",
},
},
},
colors,
dataLabels: {
enabled: true,
textAnchor: "center",
style: {
colors: ["#000"], // Black labels
fontSize: "8px",
},
formatter: function (_, opt) {
const originalVal = sortedSeriesData[opt.dataPointIndex]; // Show real value
return `${originalVal}%`;
},
offsetX: 10,
dropShadow: { enabled: false },
},
stroke: {
width: 1,
colors: ["#fff"],
},
xaxis: {
categories: sortedCategories,
axisBorder: { show: false },
axisTicks: { show: false },
labels: { show: false },
},
yaxis: {
labels: { show: false },
axisBorder: { show: false },
axisTicks: { show: false },
},
legend: {
show: true,
},
tooltip: {
theme: "dark",
x: { show: true },
y: {
title: {
formatter: () => "",
},
},
},
};
return (
<div className="w-full">
<ReactApexChart
options={chartOptions}
series={[{ data: adjustedSeriesData }]}
type="bar"
height={chartHeight}
/>
</div>
);
};
HorizontalBarChart.propTypes = {
seriesData: PropTypes.arrayOf(PropTypes.number),
categories: PropTypes.arrayOf(PropTypes.string),
colors: PropTypes.arrayOf(PropTypes.string),
loading: PropTypes.bool,
};
export default HorizontalBarChart;

View File

@ -0,0 +1,144 @@
import React from "react";
import ReactApexChart from "react-apexcharts";
import PropTypes from "prop-types";
const LineChart = ({
seriesData = [],
categories = [],
colors = ["#1E90FF", "#FF6347"],
loading = false,
lineChartCategoriesDates = [],
}) => {
const hasValidData =
Array.isArray(seriesData) &&
seriesData.length > 0 &&
Array.isArray(categories) &&
categories.length > 0;
if (loading) {
return (
<div className="flex justify-center items-center h-[350px] text-gray-500">
<div className="animate-spin rounded-full h-8 w-8 border-t-2 border-b-2 border-blue-500 mr-2" />
Loading chart...
</div>
);
}
if (!hasValidData) {
return <div className="text-center text-gray-500">No data to display</div>;
}
const chartOptions = {
chart: {
type: "line",
height: 350,
zoom: {
enabled: true,
type: 'x',
autoScaleYaxis: true
},
toolbar: {
show: true,
tools: {
zoom: true,
zoomin: true,
zoomout: true,
pan: true,
reset: true
}
},
background: 'transparent'
},
colors,
dataLabels: {
enabled: false
},
stroke: {
curve: 'smooth',
width: 2
},
grid: {
show: false,
xaxis: {
lines: {
show: false
}
},
yaxis: {
lines: {
show: false
}
}
},
markers: {
size: 5,
strokeWidth: 0,
hover: {
size: 7
}
},
xaxis: {
type: 'category',
categories,
labels: {
show: true,
rotate: -45,
style: {
fontSize: '12px'
}
},
tooltip: { enabled: false }
},
yaxis: {
labels: { show: false },
axisBorder: { show: false },
axisTicks: { show: false },
min: 0
},
legend: {
show: true
},
tooltip: {
enabled: true,
x: {
formatter: (value, { dataPointIndex }) => {
if (
lineChartCategoriesDates &&
lineChartCategoriesDates.length > dataPointIndex
) {
return lineChartCategoriesDates[dataPointIndex];
}
return value;
}
}
}
};
return (
<div className="w-full overflow-x-auto">
<ReactApexChart
options={chartOptions}
series={seriesData}
type="line"
height={350}
/>
</div>
);
};
LineChart.propTypes = {
seriesData: PropTypes.arrayOf(
PropTypes.shape({
name: PropTypes.string.isRequired,
data: PropTypes.arrayOf(PropTypes.number).isRequired
})
),
categories: PropTypes.arrayOf(PropTypes.string),
colors: PropTypes.arrayOf(PropTypes.string),
title: PropTypes.string,
loading: PropTypes.bool
};
export default LineChart;

View File

@ -0,0 +1,271 @@
import React, { useState } from "react";
import HorizontalBarChart from "../Charts/HorizontalBarChart";
import LineChart from "../Charts/LineChart";
import { useProjects } from "../../hooks/useProjects";
import {
useDashboard_Data,
useDashboardProjectsCardData,
useDashboardTeamsCardData,
useDashboardTasksCardData,
} from "../../hooks/useDashboard_Data";
const Dashboard = () => {
const { projects, loading } = useProjects();
const [selectedProjectId, setSelectedProjectId] = useState("all");
const [range, setRange] = useState("1W");
const getDaysFromRange = (range) => {
switch (range) {
case "1D":
return 1;
case "1W":
return 7;
case "15D":
return 15;
case "1M":
return 30;
case "3M":
return 90;
case "1Y":
return 365;
case "5Y":
return 1825;
default:
return 7;
}
};
const days = getDaysFromRange(range);
const today = new Date();
const FromDate = today.toISOString().split("T")[0]; // Always today
const { projectsCardData } = useDashboardProjectsCardData();
const { teamsCardData } = useDashboardTeamsCardData();
const { tasksCardData } = useDashboardTasksCardData();
const { dashboard_data, loading: isLineChartLoading } = useDashboard_Data({
days,
FromDate,
projectId: selectedProjectId === "all" ? 0 : selectedProjectId,
});
// Sort dashboard_data by date ascending
const sortedDashboardData = [...dashboard_data].sort(
(a, b) => new Date(a.date) - new Date(b.date)
);
// Bar chart logic
const projectNames = projects?.map((p) => p.name) || [];
const projectProgress =
projects?.map((p) => {
const completed = p.completedWork || 0;
const planned = p.plannedWork || 1;
const percent = (completed / planned) * 100;
return Math.min(Math.round(percent), 100);
}) || [];
// Line chart data
const lineChartSeries = [
{
name: "Planned Work",
data: sortedDashboardData.map((d) => d.plannedTask || 0),
},
{
name: "Completed Work",
data: sortedDashboardData.map((d) => d.completedTask || 0),
},
];
const lineChartCategories = sortedDashboardData.map((d) =>
new Date(d.date).toLocaleDateString("en-US", {
month: "short",
day: "numeric",
})
);
const lineChartCategoriesDates = sortedDashboardData.map((d) =>
new Date(d.date).toLocaleDateString("en-US", {
month: "short",
day: "numeric",
year: "numeric",
})
);
return (
<div className="container-xxl flex-grow-1 container-p-y">
<div className="row gy-4">
{/* Projects Card */}
<div className="col-sm-6 col-lg-4">
<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">
<h5 className="fw-bold mb-0 ms-2">
<i className="rounded-circle bx bx-building-house text-primary"></i>{" "}
Projects
</h5>
</div>
<div className="d-flex justify-content-around align-items-start mt-n2">
<div>
<h4 className="mb-0 fw-bold">
{projectsCardData.totalProjects?.toLocaleString()}
</h4>
<small className="text-muted">Total</small>
</div>
<div>
<h4 className="mb-0 fw-bold">
{projectsCardData.ongoingProjects?.toLocaleString()}
</h4>
<small className="text-muted">Ongoing</small>
</div>
</div>
</div>
</div>
{/* Teams Card */}
<div className="col-sm-6 col-lg-4">
<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">
<h5 className="fw-bold mb-0 ms-2">
<i className="bx bx-group text-warning"></i> Teams
</h5>
</div>
<div className="d-flex justify-content-around align-items-start mt-n2">
<div>
<h4 className="mb-0 fw-bold">
{teamsCardData.totalEmployees?.toLocaleString()}
</h4>
<small className="text-muted">Total Employees</small>
</div>
<div>
<h4 className="mb-0 fw-bold">
{teamsCardData.inToday?.toLocaleString()}
</h4>
<small className="text-muted">In Today</small>
</div>
</div>
</div>
</div>
{/* Tasks Card */}
<div className="col-sm-6 col-lg-4">
<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">
<h5 className="fw-bold mb-0 ms-2">
<i className="bx bx-task text-success"></i> Tasks
</h5>
</div>
<div className="d-flex justify-content-around align-items-start mt-n2">
<div>
<h4 className="mb-0 fw-bold">
{tasksCardData.totalTasks?.toLocaleString()}
</h4>
<small className="text-muted">Total</small>
</div>
<div>
<h4 className="mb-0 fw-bold">
{tasksCardData.completedTasks?.toLocaleString()}
</h4>
<small className="text-muted">Completed</small>
</div>
</div>
</div>
</div>
{/* Bar Chart */}
<div className="col-xxl-6 col-lg-6">
<div className="card h-100">
<div className="card-header d-flex align-items-start justify-content-between">
<div className="card-title mb-0 text-start">
<h5 className="mb-1">Projects</h5>
<p className="card-subtitle">Projects Completion Status</p>
</div>
</div>
<div className="card-body">
<HorizontalBarChart
categories={projectNames}
seriesData={projectProgress}
loading={loading}
/>
</div>
</div>
</div>
{/* Line Chart */}
<div className="col-xxl-6 col-lg-6">
<div className="card h-100">
<div className="card-header">
{/* Row 1: Title + Project Selector */}
<div className="d-flex flex-wrap justify-content-between align-items-center mb-2">
<div className="card-title mb-0 text-start">
<h5 className="mb-1">Project Progress</h5>
<p className="card-subtitle">Progress Overview by Project</p>
</div>
<div className="btn-group">
<button
type="button"
className="btn btn-outline-primary btn-sm dropdown-toggle"
type="button"
data-bs-toggle="dropdown"
aria-expanded="false"
>
{selectedProjectId === "all"
? "All Projects"
: projects?.find((p) => p.id === selectedProjectId)
?.name || "Select Project"}
</button>
<ul className="dropdown-menu">
<li>
<button
className="dropdown-item"
onClick={() => setSelectedProjectId("all")}
>
All Projects
</button>
</li>
{projects?.map((project) => (
<li key={project.id}>
<button
className="dropdown-item"
onClick={() => setSelectedProjectId(project.id)}
>
{project.name}
</button>
</li>
))}
</ul>
</div>
</div>
{/* Row 2: Time Range Buttons */}
<div className="d-flex flex-wrap mt-2">
{["1D", "1W", "15D", "1M", "3M", "1Y", "5Y"].map((key) => (
<button
key={key}
className={`border-0 bg-transparent px-2 py-1 text-sm rounded ${
range === key
? " border-bottom border-primary text-primary"
: "text-muted"
}`}
style={{ cursor: "pointer", transition: "all 0.2s ease" }}
onClick={() => setRange(key)}
>
{key}
</button>
))}
</div>
</div>
<div className="card-body ">
<LineChart
seriesData={lineChartSeries}
categories={lineChartCategories}
loading={isLineChartLoading}
lineChartCategoriesDates={lineChartCategoriesDates}
/>
</div>
</div>
</div>
</div>
</div>
);
};
export default Dashboard;

View File

@ -1,549 +0,0 @@
import { useProfile } from "../../hooks/useProfile";
const Dashboard = () => {
const {profile,loading} = useProfile()
return (
<>
{/* Content */}
<div className="container-xxl flex-grow-1 container-p-y">
<div className="row">
<div className="col-lg-8 mb-4 order-0">
<div className="card">
<div className="d-flex align-items-end row">
<div className="col-sm-7">
<div className="card-body">
<h5 className="card-title text-primary">Congratulations {profile?.employeeInfo?.firstName }! 🎉</h5>
<p className="mb-4">
You have done <span className="fw-bold">72%</span> more sales today. Check your new badge in
your profile.
</p>
<a className="btn btn-sm btn-outline-primary">View Badges</a>
</div>
</div>
<div className="col-sm-5 text-center text-sm-left">
<div className="card-body pb-0 px-0 px-md-4">
<img
src="./img/illustrations/man-with-laptop-light.png"
height="140"
alt="View Badge User"
data-app-dark-img="illustrations/man-with-laptop-dark.png"
data-app-light-img="illustrations/man-with-laptop-light.png"
/>
</div>
</div>
</div>
</div>
</div>
<div className="col-lg-4 col-md-4 order-1">
<div className="row">
<div className="col-lg-6 col-md-12 col-6 mb-4">
<div className="card">
<div className="card-body">
<div className="card-title d-flex align-items-start justify-content-between">
<div className="avatar flex-shrink-0">
<img
src="./img/icons/unicons/chart-success.png"
alt="chart success"
className="rounded"
/>
</div>
<div className="dropdown">
<button
className="btn p-0"
type="button"
id="cardOpt3"
data-bs-toggle="dropdown"
aria-haspopup="true"
aria-expanded="false"
>
<i className="bx bx-dots-vertical-rounded"></i>
</button>
<div className="dropdown-menu dropdown-menu-end" aria-labelledby="cardOpt3">
<a className="dropdown-item" >View More</a>
<a className="dropdown-item" >Delete</a>
</div>
</div>
</div>
<span className="fw-semibold d-block mb-1">Projects</span>
<h3 className="card-title mb-2">25</h3>
<small className="text-success fw-semibold"><i className="bx bx-up-arrow-alt"></i> +20%</small>
</div>
</div>
</div>
<div className="col-lg-6 col-md-12 col-6 mb-4">
<div className="card">
<div className="card-body">
<div className="card-title d-flex align-items-start justify-content-between">
<div className="avatar flex-shrink-0">
<img
src="./img/icons/unicons/wallet-info.png"
alt="Credit Card"
className="rounded"
/>
</div>
<div className="dropdown">
<button
className="btn p-0"
type="button"
id="cardOpt6"
data-bs-toggle="dropdown"
aria-haspopup="true"
aria-expanded="false"
>
<i className="bx bx-dots-vertical-rounded"></i>
</button>
<div className="dropdown-menu dropdown-menu-end" aria-labelledby="cardOpt6">
<a className="dropdown-item" >View More</a>
<a className="dropdown-item" >Delete</a>
</div>
</div>
</div>
<span>Upcoming Projects</span>
<h3 className="card-title text-nowrap mb-1">10</h3>
<small className="text-success fw-semibold"><i className="bx bx-up-arrow-alt"></i> +28.42%</small>
</div>
</div>
</div>
</div>
</div>
{/* Total Revenue */}
<div className="col-12 col-lg-8 order-2 order-md-3 order-lg-2 mb-4">
<div className="card">
<div className="row row-bordered g-0">
<div className="col-md-8">
<h5 className="card-header m-0 me-2 pb-3">Project Status</h5>
<div id="totalRevenueChart" className="px-2"></div>
</div>
<div className="col-md-4">
<div className="card-body">
<div className="text-center">
<div className="dropdown">
<button
className="btn btn-sm btn-outline-primary dropdown-toggle"
type="button"
id="growthReportId"
data-bs-toggle="dropdown"
aria-haspopup="true"
aria-expanded="false"
>
2022
</button>
<div className="dropdown-menu dropdown-menu-end" aria-labelledby="growthReportId">
<a className="dropdown-item" >2021</a>
<a className="dropdown-item" >2020</a>
<a className="dropdown-item" >2019</a>
</div>
</div>
</div>
</div>
<div id="growthChart"></div>
<div className="text-center fw-semibold pt-3 mb-2">62% Company Growth</div>
<div className="d-flex px-xxl-4 px-lg-2 p-4 gap-xxl-3 gap-lg-1 gap-3 justify-content-between">
<div className="d-flex">
<div className="me-2">
<span className="badge bg-label-primary p-2"><i className="bx bx-rupee text-primary"></i></span>
</div>
<div className="d-flex flex-column">
<small>2022</small>
<h6 className="mb-0">32.5k</h6>
</div>
</div>
<div className="d-flex">
<div className="me-2">
<span className="badge bg-label-info p-2"><i className="bx bx-wallet text-info"></i></span>
</div>
<div className="d-flex flex-column">
<small>2021</small>
<h6 className="mb-0">41.2k</h6>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{/*/ Total Revenue */}
<div className="col-12 col-md-8 col-lg-4 order-3 order-md-2">
<div className="row">
{/* <div className="col-6 mb-4">
<div className="card">
<div className="card-body">
<div className="card-title d-flex align-items-start justify-content-between">
<div className="avatar flex-shrink-0">
<img src="./img/icons/unicons/paypal.png" alt="Credit Card" className="rounded" />
</div>
<div className="dropdown">
<button
className="btn p-0"
type="button"
id="cardOpt4"
data-bs-toggle="dropdown"
aria-haspopup="true"
aria-expanded="false"
>
<i className="bx bx-dots-vertical-rounded"></i>
</button>
<div className="dropdown-menu dropdown-menu-end" aria-labelledby="cardOpt4">
<a className="dropdown-item" >View More</a>
<a className="dropdown-item" >Delete</a>
</div>
</div>
</div>
<span className="d-block mb-1">Payments</span>
<h3 className="card-title text-nowrap mb-2">$2,456</h3>
<small className="text-danger fw-semibold"><i className="bx bx-down-arrow-alt"></i> -14.82%</small>
</div>
</div>
</div> */}
{/* <div className="col-6 mb-4">
<div className="card">
<div className="card-body">
<div className="card-title d-flex align-items-start justify-content-between">
<div className="avatar flex-shrink-0">
<img src="./img/icons/unicons/cc-primary.png" alt="Credit Card" className="rounded" />
</div>
<div className="dropdown">
<button
className="btn p-0"
type="button"
id="cardOpt1"
data-bs-toggle="dropdown"
aria-haspopup="true"
aria-expanded="false"
>
<i className="bx bx-dots-vertical-rounded"></i>
</button>
<div className="dropdown-menu" aria-labelledby="cardOpt1">
<a className="dropdown-item" >View More</a>
<a className="dropdown-item" >Delete</a>
</div>
</div>
</div>
<span className="fw-semibold d-block mb-1">Transactions</span>
<h3 className="card-title mb-2">$14,857</h3>
<small className="text-success fw-semibold"><i className="bx bx-up-arrow-alt"></i> +28.14%</small>
</div>
</div>
</div> */}
{/* </div>
<div className="row"> */}
<div className="col-12 mb-4">
<div className="card">
<div className="card-body">
<div className="d-flex justify-content-between flex-sm-row flex-column gap-3">
<div className="d-flex flex-sm-column flex-row align-items-start justify-content-between">
<div className="card-title">
<h5 className="text-nowrap mb-2">Profile Report</h5>
<span className="badge bg-label-warning rounded-pill">Year 2021</span>
</div>
<div className="mt-sm-auto">
<small className="text-success text-nowrap fw-semibold"
><i className="bx bx-chevron-up"></i> 68.2%</small
>
<h3 className="mb-0">$84,686k</h3>
</div>
</div>
<div id="profileReportChart"></div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div className="row">
{/* Order Statistics */}
<div className="col-md-6 col-lg-4 col-xl-4 order-0 mb-4">
<div className="card h-100">
<div className="card-header d-flex align-items-center justify-content-between pb-0">
<div className="card-title mb-0">
<h5 className="m-0 me-2">Order Statistics</h5>
<small className="text-muted">42.82k Total Sales</small>
</div>
<div className="dropdown">
<button
className="btn p-0"
type="button"
id="orederStatistics"
data-bs-toggle="dropdown"
aria-haspopup="true"
aria-expanded="false"
>
<i className="bx bx-dots-vertical-rounded"></i>
</button>
<div className="dropdown-menu dropdown-menu-end" aria-labelledby="orederStatistics">
<a className="dropdown-item" >Select All</a>
<a className="dropdown-item" >Refresh</a>
<a className="dropdown-item" >Share</a>
</div>
</div>
</div>
<div className="card-body">
<div className="d-flex justify-content-between align-items-center mb-3">
<div className="d-flex flex-column align-items-center gap-1">
<h2 className="mb-2">8,258</h2>
<span>Total Orders</span>
</div>
<div id="orderStatisticsChart"></div>
</div>
<ul className="p-0 m-0">
<li className="d-flex mb-4 pb-1">
<div className="avatar flex-shrink-0 me-3">
<span className="avatar-initial rounded bg-label-primary"
><i className="bx bx-mobile-alt"></i
></span>
</div>
<div className="d-flex w-100 flex-wrap align-items-center justify-content-between gap-2">
<div className="me-2">
<h6 className="mb-0">Electronic</h6>
<small className="text-muted">Mobile, Earbuds, TV</small>
</div>
<div className="user-progress">
<small className="fw-semibold">82.5k</small>
</div>
</div>
</li>
<li className="d-flex mb-4 pb-1">
<div className="avatar flex-shrink-0 me-3">
<span className="avatar-initial rounded bg-label-success"><i className="bx bx-closet"></i></span>
</div>
<div className="d-flex w-100 flex-wrap align-items-center justify-content-between gap-2">
<div className="me-2">
<h6 className="mb-0">Fashion</h6>
<small className="text-muted">T-shirt, Jeans, Shoes</small>
</div>
<div className="user-progress">
<small className="fw-semibold">23.8k</small>
</div>
</div>
</li>
<li className="d-flex mb-4 pb-1">
<div className="avatar flex-shrink-0 me-3">
<span className="avatar-initial rounded bg-label-info"><i className="bx bx-home-alt"></i></span>
</div>
<div className="d-flex w-100 flex-wrap align-items-center justify-content-between gap-2">
<div className="me-2">
<h6 className="mb-0">Decor</h6>
<small className="text-muted">Fine Art, Dining</small>
</div>
<div className="user-progress">
<small className="fw-semibold">849k</small>
</div>
</div>
</li>
<li className="d-flex">
<div className="avatar flex-shrink-0 me-3">
<span className="avatar-initial rounded bg-label-secondary"
><i className="bx bx-football"></i ></span>
</div>
<div className="d-flex w-100 flex-wrap align-items-center justify-content-between gap-2">
<div className="me-2">
<h6 className="mb-0">Sports</h6>
<small className="text-muted">Football, Cricket Kit</small>
</div>
<div className="user-progress">
<small className="fw-semibold">99</small>
</div>
</div>
</li>
</ul>
</div>
</div>
</div>
{/*/ Order Statistics */}
{/* Expense Overview */}
<div className="col-md-6 col-lg-4 order-1 mb-4">
<div className="card h-100">
<div className="card-header">
<ul className="nav nav-pills" role="tablist">
<li className="nav-item">
<button
type="button"
className="nav-link active"
role="tab"
data-bs-toggle="tab"
data-bs-target="#navs-tabs-line-card-income"
aria-controls="navs-tabs-line-card-income"
aria-selected="true"
>
Income
</button>
</li>
<li className="nav-item">
<button type="button" className="nav-link" role="tab">Expenses</button>
</li>
<li className="nav-item">
<button type="button" className="nav-link" role="tab">Profit</button>
</li>
</ul>
</div>
<div className="card-body px-0">
<div className="tab-content p-0">
<div className="tab-pane fade show active" id="navs-tabs-line-card-income" role="tabpanel">
<div className="d-flex p-4 pt-3">
<div className="avatar flex-shrink-0 me-3">
<img src="./img/icons/unicons/wallet.png" alt="User" />
</div>
<div>
<small className="text-muted d-block">Total Balance</small>
<div className="d-flex align-items-center">
<h6 className="mb-0 me-1">$459.10</h6>
<small className="text-success fw-semibold">
<i className="bx bx-chevron-up"></i>
42.9%
</small>
</div>
</div>
</div>
<div id="incomeChart"></div>
<div className="d-flex justify-content-center pt-4 gap-2">
<div className="flex-shrink-0">
<div id="expensesOfWeek"></div>
</div>
<div>
<p className="mb-n1 mt-1">Expenses This Week</p>
<small className="text-muted">$39 less than last week</small>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{/*/ Expense Overview */}
{/* Transactions */}
<div className="col-md-6 col-lg-4 order-2 mb-4">
<div className="card h-100">
<div className="card-header d-flex align-items-center justify-content-between">
<h5 className="card-title m-0 me-2">Transactions</h5>
<div className="dropdown">
<button
className="btn p-0"
type="button"
id="transactionID"
data-bs-toggle="dropdown"
aria-haspopup="true"
aria-expanded="false"
>
<i className="bx bx-dots-vertical-rounded"></i>
</button>
<div className="dropdown-menu dropdown-menu-end" aria-labelledby="transactionID">
<a className="dropdown-item" >Last 28 Days</a>
<a className="dropdown-item" >Last Month</a>
<a className="dropdown-item" >Last Year</a>
</div>
</div>
</div>
<div className="card-body">
<ul className="p-0 m-0">
<li className="d-flex mb-4 pb-1">
<div className="avatar flex-shrink-0 me-3">
<img src="./img/icons/unicons/paypal.png" alt="User" className="rounded" />
</div>
<div className="d-flex w-100 flex-wrap align-items-center justify-content-between gap-2">
<div className="me-2">
<small className="text-muted d-block mb-1">Paypal</small>
<h6 className="mb-0">Send money</h6>
</div>
<div className="user-progress d-flex align-items-center gap-1">
<h6 className="mb-0">+82.6</h6>
<span className="text-muted">USD</span>
</div>
</div>
</li>
<li className="d-flex mb-4 pb-1">
<div className="avatar flex-shrink-0 me-3">
<img src="./img/icons/unicons/wallet.png" alt="User" className="rounded" />
</div>
<div className="d-flex w-100 flex-wrap align-items-center justify-content-between gap-2">
<div className="me-2">
<small className="text-muted d-block mb-1">Wallet</small>
<h6 className="mb-0">Mac'D</h6>
</div>
<div className="user-progress d-flex align-items-center gap-1">
<h6 className="mb-0">+270.69</h6>
<span className="text-muted">USD</span>
</div>
</div>
</li>
<li className="d-flex mb-4 pb-1">
<div className="avatar flex-shrink-0 me-3">
<img src="./img/icons/unicons/chart.png" alt="User" className="rounded" />
</div>
<div className="d-flex w-100 flex-wrap align-items-center justify-content-between gap-2">
<div className="me-2">
<small className="text-muted d-block mb-1">Transfer</small>
<h6 className="mb-0">Refund</h6>
</div>
<div className="user-progress d-flex align-items-center gap-1">
<h6 className="mb-0">+637.91</h6>
<span className="text-muted">USD</span>
</div>
</div>
</li>
<li className="d-flex mb-4 pb-1">
<div className="avatar flex-shrink-0 me-3">
<img src="./img/icons/unicons/cc-success.png" alt="User" className="rounded" />
</div>
<div className="d-flex w-100 flex-wrap align-items-center justify-content-between gap-2">
<div className="me-2">
<small className="text-muted d-block mb-1">Credit Card</small>
<h6 className="mb-0">Ordered Food</h6>
</div>
<div className="user-progress d-flex align-items-center gap-1">
<h6 className="mb-0">-838.71</h6>
<span className="text-muted">USD</span>
</div>
</div>
</li>
<li className="d-flex mb-4 pb-1">
<div className="avatar flex-shrink-0 me-3">
<img src="./img/icons/unicons/wallet.png" alt="User" className="rounded" />
</div>
<div className="d-flex w-100 flex-wrap align-items-center justify-content-between gap-2">
<div className="me-2">
<small className="text-muted d-block mb-1">Wallet</small>
<h6 className="mb-0">Starbucks</h6>
</div>
<div className="user-progress d-flex align-items-center gap-1">
<h6 className="mb-0">+203.33</h6>
<span className="text-muted">USD</span>
</div>
</div>
</li>
<li className="d-flex">
<div className="avatar flex-shrink-0 me-3">
<img src="./img/icons/unicons/cc-warning.png" alt="User" className="rounded" />
</div>
<div className="d-flex w-100 flex-wrap align-items-center justify-content-between gap-2">
<div className="me-2">
<small className="text-muted d-block mb-1">Mastercard</small>
<h6 className="mb-0">Ordered Food</h6>
</div>
<div className="user-progress d-flex align-items-center gap-1">
<h6 className="mb-0">-92.45</h6>
<span className="text-muted">USD</span>
</div>
</div>
</li>
</ul>
</div>
</div>
</div>
{/*/ Transactions */}
</div>
</div>
{/* / Content */}
</>
);
};
export default Dashboard;

View File

@ -1,135 +1,172 @@
import React from 'react'
import React from "react";
const DemoTable = () => {
return (
<div class="content-wrapper">
<div className="content-wrapper">
<div className="container-xxl flex-grow-1 container-p-y">
<div className="card">
<div className="card-datatable table-responsive">
<table className="datatables-basic table border-top">
<thead>
<tr>
<th></th>
<th></th>
<th>id</th>
<th>Name</th>
<th>Email</th>
<th>Date</th>
<th>Salary</th>
<th>Status</th>
<th>Action</th>
</tr>
</thead>
</table>
</div>
</div>
<div className="offcanvas offcanvas-end" id="add-new-record">
<div className="offcanvas-header border-bottom">
<h5 className="offcanvas-title" id="exampleModalLabel">
New Record
</h5>
<button
type="button"
className="btn-close text-reset"
data-bs-dismiss="offcanvas"
aria-label="Close"
></button>
</div>
<div className="offcanvas-body flex-grow-1">
<form
className="add-new-record pt-0 row g-2"
id="form-add-new-record"
onsubmit="return false"
>
<div className="col-sm-12">
<label className="form-label" for="basicFullname">
Full Name
</label>
<div className="input-group input-group-merge">
<span id="basicFullname2" className="input-group-text">
<i className="bx bx-user"></i>
</span>
<input
type="text"
id="basicFullname"
className="form-control dt-full-name"
name="basicFullname"
placeholder="John Doe"
aria-label="John Doe"
aria-describedby="basicFullname2"
/>
</div>
</div>
<div className="col-sm-12">
<label className="form-label" for="basicPost">
Post
</label>
<div className="input-group input-group-merge">
<span id="basicPost2" className="input-group-text">
<i className="bx bxs-briefcase"></i>
</span>
<input
type="text"
id="basicPost"
name="basicPost"
className="form-control dt-post"
placeholder="Web Developer"
aria-label="Web Developer"
aria-describedby="basicPost2"
/>
</div>
</div>
<div className="col-sm-12">
<label className="form-label" for="basicEmail">
Email
</label>
<div className="input-group input-group-merge">
<span className="input-group-text">
<i className="bx bx-envelope"></i>
</span>
<input
type="text"
id="basicEmail"
name="basicEmail"
className="form-control dt-email"
placeholder="john.doe@example.com"
aria-label="john.doe@example.com"
/>
</div>
<div className="form-text">
You can use letters, numbers & periods
</div>
</div>
<div className="col-sm-12">
<label className="form-label" for="basicDate">
Joining Date
</label>
<div className="input-group input-group-merge">
<span id="basicDate2" className="input-group-text">
<i className="bx bx-calendar"></i>
</span>
<input
type="text"
className="form-control dt-date"
id="basicDate"
name="basicDate"
aria-describedby="basicDate2"
placeholder="MM/DD/YYYY"
aria-label="MM/DD/YYYY"
/>
</div>
</div>
<div className="col-sm-12">
<label className="form-label" for="basicSalary">
Salary
</label>
<div className="input-group input-group-merge">
<span id="basicSalary2" className="input-group-text">
<i className="bx bx-dollar"></i>
</span>
<input
type="number"
id="basicSalary"
name="basicSalary"
className="form-control dt-salary"
placeholder="12000"
aria-label="12000"
aria-describedby="basicSalary2"
/>
</div>
</div>
<div className="col-sm-12">
<button
type="submit"
className="btn btn-primary data-submit me-sm-4 me-1"
>
Submit
</button>
<button
type="reset"
className="btn btn-outline-secondary"
data-bs-dismiss="offcanvas"
>
Cancel
</button>
</div>
</form>
</div>
</div>
<div class="container-xxl flex-grow-1 container-p-y">
<div class="card">
<div class="card-datatable table-responsive">
<table class="datatables-basic table border-top">
<thead>
<tr>
<th></th>
<th></th>
<th>id</th>
<th>Name</th>
<th>Email</th>
<th>Date</th>
<th>Salary</th>
<th>Status</th>
<th>Action</th>
</tr>
</thead>
</table>
</div>
</div>
<div class="offcanvas offcanvas-end" id="add-new-record">
<div class="offcanvas-header border-bottom">
<h5 class="offcanvas-title" id="exampleModalLabel">New Record</h5>
<button
type="button"
class="btn-close text-reset"
data-bs-dismiss="offcanvas"
aria-label="Close"></button>
</div>
<div class="offcanvas-body flex-grow-1">
<form class="add-new-record pt-0 row g-2" id="form-add-new-record" onsubmit="return false">
<div class="col-sm-12">
<label class="form-label" for="basicFullname">Full Name</label>
<div class="input-group input-group-merge">
<span id="basicFullname2" class="input-group-text"><i class="bx bx-user"></i></span>
<input
type="text"
id="basicFullname"
class="form-control dt-full-name"
name="basicFullname"
placeholder="John Doe"
aria-label="John Doe"
aria-describedby="basicFullname2" />
</div>
</div>
<div class="col-sm-12">
<label class="form-label" for="basicPost">Post</label>
<div class="input-group input-group-merge">
<span id="basicPost2" class="input-group-text"><i class="bx bxs-briefcase"></i></span>
<input
type="text"
id="basicPost"
name="basicPost"
class="form-control dt-post"
placeholder="Web Developer"
aria-label="Web Developer"
aria-describedby="basicPost2" />
</div>
</div>
<div class="col-sm-12">
<label class="form-label" for="basicEmail">Email</label>
<div class="input-group input-group-merge">
<span class="input-group-text"><i class="bx bx-envelope"></i></span>
<input
type="text"
id="basicEmail"
name="basicEmail"
class="form-control dt-email"
placeholder="john.doe@example.com"
aria-label="john.doe@example.com" />
</div>
<div class="form-text">You can use letters, numbers & periods</div>
</div>
<div class="col-sm-12">
<label class="form-label" for="basicDate">Joining Date</label>
<div class="input-group input-group-merge">
<span id="basicDate2" class="input-group-text"><i class="bx bx-calendar"></i></span>
<input
type="text"
class="form-control dt-date"
id="basicDate"
name="basicDate"
aria-describedby="basicDate2"
placeholder="MM/DD/YYYY"
aria-label="MM/DD/YYYY" />
</div>
</div>
<div class="col-sm-12">
<label class="form-label" for="basicSalary">Salary</label>
<div class="input-group input-group-merge">
<span id="basicSalary2" class="input-group-text"><i class="bx bx-dollar"></i></span>
<input
type="number"
id="basicSalary"
name="basicSalary"
class="form-control dt-salary"
placeholder="12000"
aria-label="12000"
aria-describedby="basicSalary2" />
</div>
</div>
<div class="col-sm-12">
<button type="submit" class="btn btn-primary data-submit me-sm-4 me-1">Submit</button>
<button type="reset" class="btn btn-outline-secondary" data-bs-dismiss="offcanvas">Cancel</button>
</div>
</form>
</div>
<hr className="my-12" />
<hr className="my-12" />
<hr className="my-12" />
</div>
<hr class="my-12" />
<div className="content-backdrop fade"></div>
</div>
);
};
<hr class="my-12" />
<hr class="my-12" />
</div>
<div class="content-backdrop fade"></div>
</div>
)
}
export default DemoTable
export default DemoTable;

View File

@ -11,7 +11,7 @@ import { Link, useNavigate, useParams } from "react-router-dom";
import { formatDate } from "../../utils/dateUtils";
import { useEmployeeProfile } from "../../hooks/useEmployees";
import { clearCacheKey, getCachedData } from "../../slices/apiDataManager";
import {clearApiCacheKey} from "../../slices/apiCacheSlice";
import { clearApiCacheKey } from "../../slices/apiCacheSlice";
const mobileNumberRegex = /^[7-9]\d{9}$/;
@ -24,66 +24,101 @@ const ManageEmployee = () => {
error,
loading: empLoading,
} = useEmployeeProfile(employeeId);
dispatch( changeMaster( "Job Role" ) );
const [disabledEmail,setDisabledEmail] = useState(false)
dispatch(changeMaster("Job Role"));
const [disabledEmail, setDisabledEmail] = useState(false);
const { data: job_role, loading } = useMaster();
const [isloading, setLoading] = useState(false);
const navigation = useNavigate();
const [currentEmployee, setCurrentEmployee] = useState();
const userSchema = z
.object({
...(employeeId ? { Id: z.number().optional() } : {}),
FirstName: z.string().min(1, { message: "First Name is required" }),
MiddleName: z.string().optional(),
LastName: z.string().min(1, { message: "Last Name is required" }),
Email: z.string().optional(),
CurrentAddress: z
.string()
.min(1, { message: "Current Address is required" }).max(150, { message: "Address cannot exceed 150 characters" }),
BirthDate: z.string().min(1, { message: "Birth Date is required" }).refine((date, ctx) => {
return new Date(date) <= new Date();
}, {
message: "Birth date cannot be in the future",
}),
JoiningDate: z.string().min(1, { message: "Joining Date is required" }).refine((date, ctx) => {
return new Date(date) <= new Date();
}, {
message: "Joining date cannot be in the future",
}),
EmergencyPhoneNumber: z
.string()
.min(1, { message: "Phone Number is required" })
.regex(mobileNumberRegex, { message: "Invalid phone number " }),
EmergencyContactPerson: z
.string()
.min(1, { message: "Emergency Contact Person is required" }),
AadharNumber: z.string()
.regex(/^\d{12}$/, "Aadhar card must be exactly 12 digits long")
const userSchema = z.object({
...(employeeId ? { Id: z.number().optional() } : {}),
FirstName: z.string().min(1, { message: "First Name is required" }),
MiddleName: z.string().optional(),
LastName: z.string().min(1, { message: "Last Name is required" }),
Email: z
.string()
.optional()
.refine(
(val) =>
!val || /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(val),
{
message: "Invalid email format",
}
)
.refine(
(val) => {
if (!val) return true;
const [local, domain] = val.split("@");
return (
val.length <= 320 &&
local?.length <= 64 &&
domain?.length <= 255
);
},
{
message: "Email local or domain part is too long",
}
),
CurrentAddress: z
.string()
.min(1, { message: "Current Address is required" })
.max(500, { message: "Address cannot exceed 250 characters" }),
BirthDate: z
.string()
.min(1, { message: "Birth Date is required" })
.refine(
(date, ctx) => {
return new Date(date) <= new Date();
},
{
message: "Birth date cannot be in the future",
}
),
JoiningDate: z
.string()
.min(1, { message: "Joining Date is required" })
.refine(
(date, ctx) => {
return new Date(date) <= new Date();
},
{
message: "Joining date cannot be in the future",
}
),
EmergencyPhoneNumber: z
.string()
.min(1, { message: "Phone Number is required" })
.regex(mobileNumberRegex, { message: "Invalid phone number " }),
EmergencyContactPerson: z
.string()
.min(1, { message: "Emergency Contact Person is required" }),
AadharNumber: z
.string()
.regex(/^\d{12}$/, "Aadhar card must be exactly 12 digits long")
.nonempty("Aadhar card is required"),
Gender: z
.string()
.min(1, { message: "Gender is required" })
.refine((val) => val !== "Select Gender", {
message: "Please select a gender",
}),
PanNumber: z
Gender: z
.string()
.min(1, { message: "Gender is required" })
.refine((val) => val !== "Select Gender", {
message: "Please select a gender",
}),
PanNumber: z
.string()
.optional()
.refine((val) => !val || /^[A-Z]{5}[0-9]{4}[A-Z]{1}$/.test(val), {
message: "Invalid PAN number",
}),
PeramnentAddress: z
.string()
.min(1, { message: "Permanent Address is required" }).max(150, { message: "Address cannot exceed 150 characters" }),
PhoneNumber: z
.string()
.min(1, { message: "Phone Number is required" })
.regex(mobileNumberRegex, { message: "Invalid phone number " }),
JobRoleId: z.string().min(1, { message: "Role is required" }),
})
PermanentAddress: z
.string()
.min(1, { message: "Permanent Address is required" })
.max(500, { message: "Address cannot exceed 250 characters" }),
PhoneNumber: z
.string()
.min(1, { message: "Phone Number is required" })
.regex(mobileNumberRegex, { message: "Invalid phone number " }),
JobRoleId: z.string().min(1, { message: "Role is required" }),
});
const {
register,
@ -108,7 +143,7 @@ const ManageEmployee = () => {
AadharNumber: currentEmployee?.aadharNumber || "",
Gender: currentEmployee?.gender || "",
PanNumber: currentEmployee?.panNumber || "",
PeramnentAddress: currentEmployee?.peramnentAddress || "",
PermanentAddress: currentEmployee?.permanentAddress || "",
PhoneNumber: currentEmployee?.phoneNumber || "",
JobRoleId: currentEmployee?.jobRoleId || "",
},
@ -116,7 +151,7 @@ const ManageEmployee = () => {
const onSubmit = (data) => {
setLoading(true);
const formData = getValues();
const formDataToSend = new FormData();
@ -140,11 +175,10 @@ const ManageEmployee = () => {
}
EmployeeRepository.manageEmployee(formDataToSend)
.then( ( response ) =>
{
showToast("Employee details updated successfully.", "success" );
clearCacheKey("employeeListByProject")
clearCacheKey( "allEmployeeList" )
.then((response) => {
showToast("Employee details updated successfully.", "success");
clearCacheKey("employeeListByProject");
clearCacheKey("allEmployeeList");
setLoading(false);
navigation("/employees");
})
@ -180,7 +214,7 @@ const ManageEmployee = () => {
AadharNumber: currentEmployee.aadharNumber || "",
Gender: currentEmployee.gender || "",
PanNumber: currentEmployee.panNumber || "",
PeramnentAddress: currentEmployee.peramnentAddress || "",
PermanentAddress: currentEmployee.permanentAddress || "",
PhoneNumber: currentEmployee.phoneNumber || "",
JobRoleId: currentEmployee.jobRoleId?.toString() || "",
}
@ -197,11 +231,19 @@ const ManageEmployee = () => {
<h6 className="mb-0">
{employee ? "Update Employee" : "Create Employee"}
</h6>
<span className="cursor-pointer fs-6" data-htm="true" data-bs-toggle="tooltip"
data-bs-offset="0,6"
data-bs-placement="top"
data-bs-html="true" title="Move Back" onClick={()=>navigation("/employees")}><i class='bx bxs-chevron-left'></i> Back</span>
<span
className="cursor-pointer fs-6"
data-htm="true"
data-bs-toggle="tooltip"
data-bs-offset="0,6"
data-bs-placement="top"
data-bs-html="true"
title="Move Back"
onClick={() => navigation("/employees")}
>
<i className="bx bxs-chevron-left"></i> Back
</span>
</div>
<div className="card-body">
{!currentEmployee && empLoading && (
@ -289,8 +331,6 @@ const ManageEmployee = () => {
{errors.Email.message}
</div>
)}
</div>
<div className="col-sm-6">
<div className="form-text text-start">Phone Number</div>
@ -404,22 +444,22 @@ const ManageEmployee = () => {
)}
</div>
<div className="col-sm-6">
<div className="form-text text-start">Permnant Address</div>
<div className="form-text text-start">Permanent Address</div>
<textarea
id="PeramnentAddress"
id="PermanentAddress"
className="form-control form-control-sm"
placeholder="Permnant Address"
aria-label="Permnant Address"
placeholder="Permanent Address"
aria-label="Permanent Address"
aria-describedby="basic-icon-default-message2"
{...register("PeramnentAddress")}
{...register("PermanentAddress")}
></textarea>
{errors.PeramnentAddress && (
{errors.PermanentAddress && (
<div
className="danger-text text-start"
style={{ fontSize: "12px" }}
>
{errors.PeramnentAddress.message}
{errors.PermanentAddress.message}
</div>
)}
</div>
@ -444,7 +484,9 @@ const ManageEmployee = () => {
Select Role
</option>
{job_role?.map((item) => (
<option value={item?.id} key={item.id}>{item?.name} </option>
<option value={item?.id} key={item.id}>
{item?.name}{" "}
</option>
))}
</select>
</div>
@ -525,7 +567,14 @@ const ManageEmployee = () => {
id="PanNumber"
placeholder="PAN Number"
/>
{errors.PanNumber && <div className="danger-text text-start" style={{fontSize:"12px"}}>{errors.PanNumber.message}</div>}
{errors.PanNumber && (
<div
className="danger-text text-start"
style={{ fontSize: "12px" }}
>
{errors.PanNumber.message}
</div>
)}
</div>
</div>

View File

@ -69,7 +69,7 @@ const Header = () =>
<a
aria-label="toggle for sidebar"
className="nav-item nav-link px-0 me-xl-4"
href="#"
>
<i className="bx bx-menu bx-sm"></i>
</a>

View File

@ -4,17 +4,17 @@ import menuData from "../../data/menuData.json";
import {getCachedProfileData} from "../../slices/apiDataManager";
const Sidebar = () => {
const logineUser = getCachedProfileData()
// const logineUser = getCachedProfileData()
const navigate = useNavigate()
const handleLogout = (e) => {
e.preventDefault();
// logout();
};
// const handleLogout = (e) => {
// e.preventDefault();
// // logout();
// };
const handleProfilePage = ()=>{
console.log(profile?.employeeInfo?.id)
navigate(`/employee/${profile?.employeeInfo?.id}?for=account`)
}
// const handleProfilePage = ()=>{
// console.log(profile?.employeeInfo?.id)
// navigate(`/employee/${profile?.employeeInfo?.id}?for=account`)
// }
return (
<aside
id="layout-menu"
@ -58,7 +58,7 @@ const Sidebar = () => {
))}
</ul>
<div className="dropdown py-sm-4 mt-sm-auto ms-auto ms-sm-0 flex-shrink-1 ps-5">
{/* <div className="dropdown py-sm-4 mt-sm-auto ms-auto ms-sm-0 flex-shrink-1 ps-5">
<a
href="#"
className="d-flex align-items-center text-decoration-none dropdown-toggle"
@ -137,7 +137,7 @@ const Sidebar = () => {
</a>
</li>
</ul>
</div>
</div> */}
</aside>
);
};

View File

@ -1,10 +1,9 @@
import React,{useState} from "react";
import React, { useState } from "react";
import moment from "moment";
import { ProjectStatus } from "../../utils/projectStatus";
const AboutProject = ( {data} ) =>
{
const [CurrentProject,setCurrentProject] = useState(data)
const AboutProject = ({ data }) => {
const [CurrentProject, setCurrentProject] = useState(data);
return (
<>
{data && (
@ -24,7 +23,7 @@ const AboutProject = ( {data} ) =>
</span>
</li>
<li className="d-flex align-items-center mb-4">
<i class="bx bx-stop-circle"></i>{" "}
<i className="bx bx-stop-circle"></i>{" "}
<span className="fw-medium mx-2">End Date:</span>{" "}
<span>
{data.endDate
@ -33,33 +32,34 @@ const AboutProject = ( {data} ) =>
</span>
</li>
<li className="d-flex align-items-center mb-2">
<i class="bx bx-trophy"></i>
<i className="bx bx-trophy"></i>
<span className="fw-medium mx-2">Status:</span>{" "}
<span>{ProjectStatus(data.projectStatusId)}</span>
</li>
</ul>
<small className="card-text text-uppercase text-muted small">
Contacts
</small>
<ul className="list-unstyled my-3 py-1">
<li className="d-flex align-items-center mb-4">
<i className="bx bx-user"></i>
<span className="fw-medium mx-2">Contact:</span>{" "}
<span>{data.contactPerson}</span>
</li>
<li className="d-flex align-items-center mb-4">
<i className="bx bx-phone"></i>
<span className="fw-medium mx-2">Contact Number:</span>{" "}
<span>NA</span>
</li>
<li className="d-flex align-items-center mb-4">
<i className="bx bx-envelope"></i>
<span className="fw-medium mx-2">Email:</span> <span>NA</span>
</li>
<li className="d-flex align-items-center mb-4">
<i className="bx bx-flag"></i>
<span className="fw-medium mx-2">Address:</span>{" "}
</li>
</ul>
{/* <small className="card-text text-uppercase text-muted small">
Contacts
</small> */}
<ul className="list-unstyled my-3 py-1">
{/* <li className="d-flex align-items-center mb-4">
<i className="bx bx-phone"></i>
<span className="fw-medium mx-2">Contact Number:</span>{" "}
<span>NA</span>
</li> */}
{/* <li className="d-flex align-items-center mb-4">
<i className="bx bx-envelope"></i>
<span className="fw-medium mx-2">Email:</span> <span>NA</span>
</li> */}
<li className="d-flex align-items-start test-start mb-4">
<span>{data.projectAddress}</span>
</li>

View File

@ -1,137 +1,139 @@
import React from "react";
import { ComingSoonPage } from "../../pages/Misc/ComingSoonPage";
const ActivityTimeline = () => {
return (
<div className="card card-action mb-6">
<div className="card-header align-items-center">
<h5 className="card-action-title mb-0">
<i className="bx bx-bar-chart-alt-2 bx-lg text-body me-4"></i>
Activity Timeline
</h5>
</div>
<div className="card-body pt-3">
<ul className="timeline mb-0">
<li className="timeline-item timeline-item-transparent">
<span className="timeline-point timeline-point-primary"></span>
<div className="timeline-event">
<div className="timeline-header mb-3">
<h6 className="mb-0">12 Invoices have been paid</h6>
<small className="text-muted">12 min ago</small>
</div>
<p className="mb-2">Invoices have been paid to the company</p>
<div className="d-flex align-items-center mb-2">
<div className="badge bg-lighter rounded d-flex align-items-center">
<img
src="../../assets//img/icons/misc/pdf.png"
alt="img"
width="15"
className="me-2"
></img>
<span className="h6 mb-0 text-body">invoices.pdf</span>
</div>
</div>
</div>
</li>
<li className="timeline-item timeline-item-transparent">
<span className="timeline-point timeline-point-success"></span>
<div className="timeline-event">
<div className="timeline-header mb-3">
<h6 className="mb-0">Client Meeting</h6>
<small className="text-muted">45 min ago</small>
</div>
<p className="mb-2">Project meeting with john @10:15am</p>
<div className="d-flex justify-content-between flex-wrap gap-2 mb-2">
<div className="d-flex flex-wrap align-items-center mb-50">
<div className="avatar avatar-sm me-3">
<img
src="../../assets/img/avatars/1.png"
alt="Avatar"
className="rounded-circle"
></img>
</div>
<div>
<p className="mb-0 small fw-medium">
Lester McCarthy (Client)
</p>
<small>CEO of ThemeSelection</small>
</div>
</div>
</div>
</div>
</li>
<li className="timeline-item timeline-item-transparent">
<span className="timeline-point timeline-point-info"></span>
<div className="timeline-event">
<div className="timeline-header mb-3">
<h6 className="mb-0">Create a new project for client</h6>
<small className="text-muted">2 Day Ago</small>
</div>
<p className="mb-2">6 team members in a project</p>
<ul className="list-group list-group-flush">
<li className="list-group-item d-flex justify-content-between align-items-center flex-wrap border-top-0 p-0">
<div className="d-flex flex-wrap align-items-center">
<ul className="list-unstyled users-list d-flex align-items-center avatar-group m-0 me-2">
<li
data-bs-toggle="tooltip"
data-popup="tooltip-custom"
data-bs-placement="top"
className="avatar pull-up"
aria-label="Vinnie Mostowy"
data-bs-original-title="Vinnie Mostowy"
>
<img
className="rounded-circle"
src="../../assets/img/avatars/1.png"
alt="Avatar"
></img>
</li>
<li
data-bs-toggle="tooltip"
data-popup="tooltip-custom"
data-bs-placement="top"
className="avatar pull-up"
aria-label="Allen Rieske"
data-bs-original-title="Allen Rieske"
>
<img
className="rounded-circle"
src="../../assets/img/avatars/4.png"
alt="Avatar"
></img>
</li>
<li
data-bs-toggle="tooltip"
data-popup="tooltip-custom"
data-bs-placement="top"
className="avatar pull-up"
aria-label="Julee Rossignol"
data-bs-original-title="Julee Rossignol"
>
<img
className="rounded-circle"
src="../../assets/img/avatars/2.png"
alt="Avatar"
></img>
</li>
<li className="avatar">
<span
className="avatar-initial rounded-circle pull-up text-heading"
data-bs-toggle="tooltip"
data-bs-placement="bottom"
data-bs-original-title="3 more"
>
+3
</span>
</li>
</ul>
</div>
</li>
</ul>
</div>
</li>
</ul>
</div>
</div>
// <div className="card card-action mb-6">
// <div className="card-header align-items-center">
// <h5 className="card-action-title mb-0">
// <i className="bx bx-bar-chart-alt-2 bx-lg text-body me-4"></i>
// Activity Timeline
// </h5>
// </div>
// <div className="card-body pt-3">
// <ul className="timeline mb-0">
// <li className="timeline-item timeline-item-transparent">
// <span className="timeline-point timeline-point-primary"></span>
// <div className="timeline-event">
// <div className="timeline-header mb-3">
// <h6 className="mb-0">12 Invoices have been paid</h6>
// <small className="text-muted">12 min ago</small>
// </div>
// <p className="mb-2">Invoices have been paid to the company</p>
// <div className="d-flex align-items-center mb-2">
// <div className="badge bg-lighter rounded d-flex align-items-center">
// <img
// src="../../assets//img/icons/misc/pdf.png"
// alt="img"
// width="15"
// className="me-2"
// ></img>
// <span className="h6 mb-0 text-body">invoices.pdf</span>
// </div>
// </div>
// </div>
// </li>
// <li className="timeline-item timeline-item-transparent">
// <span className="timeline-point timeline-point-success"></span>
// <div className="timeline-event">
// <div className="timeline-header mb-3">
// <h6 className="mb-0">Client Meeting</h6>
// <small className="text-muted">45 min ago</small>
// </div>
// <p className="mb-2">Project meeting with john @10:15am</p>
// <div className="d-flex justify-content-between flex-wrap gap-2 mb-2">
// <div className="d-flex flex-wrap align-items-center mb-50">
// <div className="avatar avatar-sm me-3">
// <img
// src="../../assets/img/avatars/1.png"
// alt="Avatar"
// className="rounded-circle"
// ></img>
// </div>
// <div>
// <p className="mb-0 small fw-medium">
// Lester McCarthy (Client)
// </p>
// <small>CEO of ThemeSelection</small>
// </div>
// </div>
// </div>
// </div>
// </li>
// <li className="timeline-item timeline-item-transparent">
// <span className="timeline-point timeline-point-info"></span>
// <div className="timeline-event">
// <div className="timeline-header mb-3">
// <h6 className="mb-0">Create a new project for client</h6>
// <small className="text-muted">2 Day Ago</small>
// </div>
// <p className="mb-2">6 team members in a project</p>
// <ul className="list-group list-group-flush">
// <li className="list-group-item d-flex justify-content-between align-items-center flex-wrap border-top-0 p-0">
// <div className="d-flex flex-wrap align-items-center">
// <ul className="list-unstyled users-list d-flex align-items-center avatar-group m-0 me-2">
// <li
// data-bs-toggle="tooltip"
// data-popup="tooltip-custom"
// data-bs-placement="top"
// className="avatar pull-up"
// aria-label="Vinnie Mostowy"
// data-bs-original-title="Vinnie Mostowy"
// >
// <img
// className="rounded-circle"
// src="../../assets/img/avatars/1.png"
// alt="Avatar"
// ></img>
// </li>
// <li
// data-bs-toggle="tooltip"
// data-popup="tooltip-custom"
// data-bs-placement="top"
// className="avatar pull-up"
// aria-label="Allen Rieske"
// data-bs-original-title="Allen Rieske"
// >
// <img
// className="rounded-circle"
// src="../../assets/img/avatars/4.png"
// alt="Avatar"
// ></img>
// </li>
// <li
// data-bs-toggle="tooltip"
// data-popup="tooltip-custom"
// data-bs-placement="top"
// className="avatar pull-up"
// aria-label="Julee Rossignol"
// data-bs-original-title="Julee Rossignol"
// >
// <img
// className="rounded-circle"
// src="../../assets/img/avatars/2.png"
// alt="Avatar"
// ></img>
// </li>
// <li className="avatar">
// <span
// className="avatar-initial rounded-circle pull-up text-heading"
// data-bs-toggle="tooltip"
// data-bs-placement="bottom"
// data-bs-original-title="3 more"
// >
// +3
// </span>
// </li>
// </ul>
// </div>
// </li>
// </ul>
// </div>
// </li>
// </ul>
// </div>
// </div>
<ComingSoonPage></ComingSoonPage>
);
};

View File

@ -1,32 +1,44 @@
import React, { useState,useEffect } from "react";
import { useDispatch } from "react-redux";
import React, { useState, useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import { changeMaster } from "../../slices/localVariablesSlice";
import useMaster from "../../hooks/masterHook/useMaster";
import { employee } from "../../data/masters";
import { useForm, Controller } from "react-hook-form";
import { z } from "zod";
import { getCachedData } from "../../slices/apiDataManager";
import { useModal } from "../../ModalContext";
import { useProjects } from "../../hooks/useProjects";
import { useEmployeesAllOrByProjectId } from "../../hooks/useEmployees";
import { TasksRepository } from "../../repositories/ProjectRepository";
import showToast from "../../services/toastService";
const schema = z.object({
selectedEmployees: z.array(z.number()).min(1, {message:"At least one employee must be selected"}),
})
selectedEmployees: z
.array(z.number())
.min(1, { message: "At least one employee must be selected" }),
description: z.string().min(1, { message: "description required" }),
});
const AssignRoleModel = ({ assignData, onClose }) => {
const [plannedTask, setPlannedTask] = useState();
const { openModal, closeModal } = useModal();
const selectedProject = useSelector(
(store) => store.localVariables.projectId
);
const { employees } = useEmployeesAllOrByProjectId(selectedProject);
const dispatch = useDispatch();
const { data, loading } = useMaster();
const jobRoleData = getCachedData("Job Role");
const [selectedRole, setSelectedRole] = useState("all");
const [selectedEmployees, setSelectedEmployees] = useState([]);
const AssignRoleModel = ( {assignData,onClose}) => {
const[target,setTraget] = useState("")
const dispatch = useDispatch()
const {data,loading} = useMaster()
const jobRoleData = getCachedData("Job Role")
const [selectedRole, setSelectedRole] = useState("all");
const [selectedEmployees, setSelectedEmployees] = useState([]);
const { handleSubmit, control, setValue, watch, formState: { errors } } = useForm({
const { handleSubmit, control, setValue, watch, formState: { errors },reset } = useForm({
defaultValues: {
selectedEmployees: []
selectedEmployees: [],
description:""
},
resolver: (data) => {
const validation = schema.safeParse(data);
@ -35,217 +47,281 @@ const { handleSubmit, control, setValue, watch, formState: { errors } } = useFor
},
});
const handleRoleChange = (event) => {
setSelectedRole(event.target.value);
const handleRoleChange = (event) => {
setSelectedRole(event.plannedTask.value);
};
const filteredEmployees =
selectedRole === "all"
? employees
: employees.filter((emp) => emp.JobRoleId.toString() === selectedRole);
// not need currently for this fun
const handleEmployeeSelection = (employeeId, field) => {
setSelectedEmployees((prevSelected) => {
let updatedSelection;
if (!prevSelected.includes(employeeId)) {
updatedSelection = [...prevSelected, employeeId];
} else {
updatedSelection = prevSelected.filter((id) => id !== employeeId);
}
field.onChange(updatedSelection);
return updatedSelection;
});
};
const removeEmployee = (employeeId) => {
setSelectedEmployees((prevSelected) => {
const updatedSelection = prevSelected.filter((id) => id !== employeeId);
setValue("selectedEmployees", updatedSelection); // Ensure form state is updated
return updatedSelection;
});
};
const onSubmit = async(data) => {
const formattedData = {
taskTeam: data.selectedEmployees,
plannedTask: parseInt( plannedTask, 10 ),
description: data.description,
assignmentDate: new Date().toISOString(),
workItemId:assignData?.workItem?.workItem.id
};
try
{
let response = await TasksRepository.assignTask( formattedData );
showToast( "Task Successfully Assigend", "success" )
reset()
onClose()
} catch ( error )
{
showToast("something wrong","error")
}
};
const filteredEmployees = selectedRole === "all"
? employee
: employee.filter((emp) => emp.JobRoleId.toString() === selectedRole);
// not need currently for this fun
const handleEmployeeSelection = (employeeId,field) => {
setSelectedEmployees((prevSelected) => {
let updatedSelection;
if (!prevSelected.includes(employeeId)) {
updatedSelection = [...prevSelected, employeeId];
} else {
updatedSelection = prevSelected.filter((id) => id !== employeeId);
}
field.onChange(updatedSelection); // Update form state with new selection
return updatedSelection;
});
};
const removeEmployee = (employeeId) => {
setSelectedEmployees((prevSelected) => {
const updatedSelection = prevSelected.filter((id) => id !== employeeId);
setValue("selectedEmployees", updatedSelection); // Ensure form state is updated
return updatedSelection;
});
};
const onSubmit = (data) => {
console.log( {...data.selectedEmployees,target});
onClose()
};
useEffect(()=>{
dispatch(changeMaster("Job Role"))
return ()=> setSelectedRole("all")
},[dispatch])
return (<>
<div className="container-fluid my-1">
<div className="mb-2">
<div className="bs-stepper wizard-numbered d-flex justify-content-center align-items-center flex-wrap">
{[
assignData?.building?.name,
assignData?.floor?.floorName,
assignData?.workArea?.areaName,
assignData?.workItem?.workItem?.activityMaster?.activityName
].map((item, index, array) => (
<div key={index} className="col d-flex justify-content-center align-items-center">
<div className="bs-stepper-header p-1 text-center">
<span className="fs-5">{item}</span>
{/* Arrow between items */}
{index < array.length - 1 && (
<div className="line">
<i className="icon-base bx bx-chevron-right scaleX-n1-rtl"></i>
</div>
)}
</div>
</div>
))}
</div>
<form onSubmit={handleSubmit(onSubmit)}>
<div className="row mb-1">
<div className="col-sm-4">
<div className="form-text text-start">Select Role</div>
<div className="input-group input-group-merge">
<select
className="form-select form-select-sm"
id="Role"
value={selectedRole}
onChange={handleRoleChange}
aria-label=""
return (
<>
<div className="container my-1">
<div className="mb-2">
<div className="bs-stepper wizard-numbered d-flex justify-content-center align-items-center flex-wrap">
{[
assignData?.building?.name,
assignData?.floor?.floorName,
assignData?.workArea?.areaName,
assignData?.workItem?.workItem?.activityMaster?.activityName,
].map((item, index, array) => (
<div
key={index}
className="col d-flex justify-content-center align-items-center"
>
{loading && data ? "Loading..." : (
<>
<option value="all">All</option>
{ jobRoleData?.map((item) => (
<option key={item.id} value={item.id}>
{item.name}
</option>
))}
</>
)}
</select>
</div>
</div>
</div>
<div class="divider text-start">
<div class="divider-text">Employee</div>
</div>
{selectedRole !== "" && (
<div className="row mb-2">
<div className="col-sm-12">
<div className="row">
{filteredEmployees.map((emp) => {
const jobRole = jobRoleData?.find((role) => role.id === emp.JobRoleId);
<div className="bs-stepper-header p-1 text-center">
<span className="fs-5">{item}</span>
return (
<div key={emp.id} className="col-6 col-sm-4 col-md-4 col-lg-3 mb-1">
<div className="form-check text-start p-0">
<div className="li-wrapper d-flex justify-content-start align-items-start">
<Controller
name="selectedEmployees"
control={control}
render={({ field }) => (
<input
{...field}
className="form-check-input mx-2"
type="checkbox"
id={`employee-${emp.id}`}
value={emp.id}
checked={field.value.includes(emp.id)} // Ensure the checkbox reflects the current form state
onChange={() => {
// Directly update the form value
handleEmployeeSelection(emp.id,field)
// const updatedSelection = field.value.includes(emp.id)
// ? field.value.filter((id) => id !== emp.id)
// : [...field.value, emp.id];
// field.onChange(updatedSelection); // Directly update form value
}}
/>
)}
/>
<div className="list-content">
<h6 className="mb-0">{emp.FirtsName} {emp.LastName}</h6>
<small className="text-muted">
{loading && (<p className="skeleton para" style={{height:"7px"}}></p>)}
{data && !loading && (jobRole ? jobRole.name : 'Unknown Role')}
</small>
</div>
</div>
</div>
{/* Arrow between items */}
{index < array.length - 1 && (
<div className="line">
<i className="icon-base bx bx-chevron-right scaleX-n1-rtl"></i>
</div>
);
})}
)}
</div>
</div>
))}
</div>
<form onSubmit={handleSubmit(onSubmit)}>
<div className="row mb-1">
<div className="col-sm-4">
<div className="form-text text-start">Select Role</div>
<div className="input-group input-group-merge">
<select
className="form-select form-select-sm"
id="Role"
value={selectedRole}
onChange={handleRoleChange}
aria-label=""
>
{loading && data ? (
"Loading..."
) : (
<>
<option value="all">All</option>
{jobRoleData?.map((item) => (
<option key={item.id} value={item.id}>
{item.name}
</option>
))}
</>
)}
</select>
</div>
</div>
</div>
</div>
)}
{selectedEmployees.length > 0 && (
<div className="mt-1">
<div className="text-start px-2">
{selectedEmployees.map((empId) => {
const emp = employee.find((emp) => emp.id === empId);
return (
<span key={empId} className="badge bg-label-primary d-inline-flex align-items-center gap-2 me-1 p-2 mb-2">
{emp.FirtsName} {emp.LastName}
<p
type="button"
className=" btn-close-white p-0 m-0"
aria-label="Close"
onClick={() => {
removeEmployee(empId);
setValue("selectedEmployees", selectedEmployees.filter(id => id !== empId));
}}
><i className="icon-base bx bx-x icon-md "></i></p>
</span>
);
})}
<div className="divider text-start">
<div className="divider-text">Employee</div>
</div>
</div>
)}
{selectedRole !== "" && (
<div className="row mb-2">
<div className="col-sm-12">
<div className="row">
{filteredEmployees.map((emp) => {
const jobRole = jobRoleData?.find(
(role) => role.id === emp.jobRoleId
);
<div class="col-md text-start mx-0 px-0">
<div class="form-check form-check-inline mt-4 px-1">
<label className="form-text fs-6" for="inlineCheckbox1">Pending Work</label>
<label className="form-check-label ms-2" for="inlineCheckbox1">{ assignData?.workItem?.workItem?.plannedWork - assignData?.workItem?.workItem?.completedWork}</label>
</div>
<div className="form-check form-check-inline col-sm-2 col">
<label for="defaultFormControlInput" className="form-label">Target</label>
<input type="text" className="form-control form-control-sm " value={target} onChange={(e)=>setTraget(e.target.value)} id="defaultFormControlInput" aria-describedby="defaultFormControlHelp" />
</div>
</div>
return (
<div
key={emp.id}
className="col-6 col-sm-4 col-md-4 col-lg-3 mb-1"
>
<div className="form-check text-start p-0">
<div className="li-wrapper d-flex justify-content-start align-items-start">
<Controller
name="selectedEmployees"
control={control}
render={({ field }) => (
<input
{...field}
className="form-check-input mx-2"
type="checkbox"
id={`employee-${emp.id}`}
value={emp.id}
checked={field.value.includes(emp.id)} // Ensure the checkbox reflects the current form state
onChange={() => {
handleEmployeeSelection(emp.id, field);
}}
/>
)}
/>
<div className="list-content">
<h6 className="mb-0">
{emp.firstName} {emp.lastName}
</h6>
<small className="text-muted">
{loading && (
<p
className="skeleton para"
style={{ height: "7px" }}
></p>
)}
{data &&
!loading &&
(jobRole ? jobRole.name : "Unknown Role")}
</small>
</div>
</div>
</div>
</div>
);
})}
</div>
</div>
</div>
)}
{errors.selectedEmployees && (
<div className="danger-text mt-2">
<p>{errors.selectedEmployees[0]}</p>
</div>
)}
{selectedEmployees.length > 0 && (
<div className="mt-1">
<div className="text-start px-2">
{selectedEmployees.map((empId) => {
const emp = employees.find((emp) => emp.id === empId);
return (
<span
key={empId}
className="badge bg-label-primary d-inline-flex align-items-center gap-2 me-1 p-2 mb-2"
>
{emp.firstName} {emp.lastName}
<p
type="button"
className=" btn-close-white p-0 m-0"
aria-label="Close"
onClick={() => {
removeEmployee(empId);
setValue(
"selectedEmployees",
selectedEmployees.filter((id) => id !== empId)
);
}}
>
<i className="icon-base bx bx-x icon-md "></i>
</p>
</span>
);
})}
</div>
</div>
)}
<div className="col-12 d-flex justify-content-center align-items-center gap-sm-6 gap-8 text-center">
<button type="submit" className="btn btn-sm btn-primary ">Submit</button>
<button type="reset" className="btn btn-sm btn-label-secondary" data-bs-dismiss="modal" aria-label="Close">
Cancel
</button>
<div className="col-md text-start mx-0 px-0">
<div className="form-check form-check-inline mt-4 px-1">
<label className="form-text fs-6" for="inlineCheckbox1">
Pending Work
</label>
<label className="form-check-label ms-2" for="inlineCheckbox1">
{assignData?.workItem?.workItem?.plannedWork -
assignData?.workItem?.workItem?.completedWork}
</label>
</div>
<div className="form-check form-check-inline col-sm-2 col">
<label for="defaultFormControlInput" className="form-label">
Target
</label>
<input
type="text"
className="form-control form-control-sm "
value={plannedTask}
onChange={(e) => setPlannedTask(e.target.value)}
id="defaultFormControlInput"
aria-describedby="defaultFormControlHelp"
/>
</div>
</div>
{errors.selectedEmployees && (
<div className="danger-text mt-1">
<p>{errors.selectedEmployees[0]}</p>
</div>
)}
<label for="exampleFormControlTextarea1" className="form-label">
Description
</label>
<Controller
name="description"
control={control}
render={({ field }) => (
<textarea
{...field}
className="form-control"
id="exampleFormControlTextarea1"
rows="3"
/>
)}
/>
{errors.description && <div>{errors.description.message}</div>}
<div className="col-12 d-flex justify-content-center align-items-center gap-sm-6 gap-8 text-center mt-1">
<button type="submit" className="btn btn-sm btn-primary ">
Submit
</button>
<button
type="reset"
className="btn btn-sm btn-label-secondary"
data-bs-dismiss="modal"
aria-label="Close"
onClick={closeModal}
>
Cancel
</button>
</div>
</form>
</div>
</form>
</div>
</div>
</>
</>
);
};
export default AssignRoleModel
export default AssignRoleModel;

View File

@ -1,140 +0,0 @@
import React, { useState, useEffect } from "react";
const defaultModel = {
id: "",
name: "",
description: "",
projectId: "",
};
const BuildingModel = ({
project,
onClose,
onSubmit,
clearTrigger,
onClearComplete,
}) => {
const [formData, setFormData] = useState(defaultModel);
useEffect(() => {
if (clearTrigger) {
setFormData(defaultModel); // Clear form
onClearComplete(); // Notify parent that clearing is done
}
}, [clearTrigger, onClearComplete]);
// Handle input change
const handleChange = (e) => {
const { name, value } = e.target;
setFormData({ ...formData, [name]: value });
};
const handleBuildigChange = (e) => {
const { name, value } = e.target;
const building = project.buildings.find((b) => b.id === Number(value));
if (building) {
delete building.floors;
building.projectId = project.id;
setFormData(building);
} else
setFormData({
id: "",
name: "",
description: "",
projectId: project.id,
});
//setFormData({ ...formData, [name]: value });
};
// Handle form submission
const handleSubmit = (e) => {
e.preventDefault();
formData.projectId = project.id;
onSubmit(formData); // Pass the updated data to the parent
};
return (
<div className="modal-dialog modal-lg modal-simple modal-edit-user">
<div className="modal-content">
<div className="modal-body">
<button
type="button"
className="btn-close"
data-bs-dismiss="modal"
aria-label="Close"
></button>
<div className="text-center mb-2">
<h5 className="mb-2">Manage Buildings - {project.name}</h5>
</div>
<form className="row g-2" onSubmit={handleSubmit}>
<div className="col-12 col-md-12">
<label className="form-label" htmlFor="name">
Select Building
</label>
<select
id="buildingId"
name="buildingId"
className="select2 form-select form-select-sm"
aria-label="Default select example"
onChange={handleBuildigChange}
value={formData.buildingId}
>
<option value="0">Add New Building</option>
{project.buildings.map((building) => (
<option key={building.id} value={building.id}>
{building.name}
</option>
))}
</select>
</div>
<div className="col-12 col-md-12">
<label className="form-label" htmlFor="name">
Building Name
</label>
<input
type="text"
id="name"
name="name"
className="form-control form-control-sm"
placeholder="Building Name"
onChange={handleChange}
value={formData.name}
/>
</div>
<div className="col-12 col-md-12">
<label className="form-label" htmlFor="description">
Description
</label>
<textarea
type="text"
id="description"
rows="5"
name="description"
className="form-control form-control-sm"
placeholder="Description"
onChange={handleChange}
value={formData.description}
/>
</div>
<div className="col-12 text-center">
<button type="submit" className="btn btn-primary me-3">
{formData.id ? "Edit Building" : "Add Building"}
</button>
<button
type="reset"
className="btn btn-label-secondary"
data-bs-dismiss="modal"
aria-label="Close"
>
Cancel
</button>
</div>
</form>
</div>
</div>
</div>
);
};
export default BuildingModel;

View File

@ -1,181 +0,0 @@
import React, { useState, useEffect } from "react";
const defaultModel = {
id: "0",
floorName: "",
buildingId: "0",
projectId: "",
};
const FloorModel = ({
project,
onClose,
onSubmit,
clearTrigger,
onClearComplete,
}) => {
const [formData, setFormData] = useState(defaultModel);
const [selectedBuilding, setSelectedBuilding] = useState({});
//if (floor && floor.id) setFormData(floor);
useEffect(() => {
if (clearTrigger) {
setFormData(defaultModel); // Clear form
onClearComplete(); // Notify parent that clearing is done
}
}, [clearTrigger, onClearComplete]);
// Handle input change
const handleChange = (e) => {
const { name, value } = e.target;
setFormData({ ...formData, [name]: value });
};
const handleFloorChange = (e) => {
const { name, value } = e.target;
const floor = selectedBuilding.floors.find((b) => b.id === Number(value));
if (floor) {
setFormData({
id: floor.id,
floorName: floor.floorName,
buildingId: selectedBuilding.id,
projectId: project.id,
});
} else
setFormData({
id: "0",
floorName: "",
buildingId: selectedBuilding.id,
projectId: project.id,
});
};
const handleBuildigChange = (e) => {
const { name, value } = e.target;
const building = project.buildings.find((b) => b.id === Number(value));
if (building) {
setFormData({
id: "",
floorName: "",
buildingId: building.id,
projectId: project.id,
});
} else
setFormData({
id: "",
floorName: "",
buildingId: "0",
projectId: project.id,
});
setSelectedBuilding(building);
};
// Handle form submission
const handleSubmit = (e) => {
e.preventDefault();
formData.projectId = project.id;
onSubmit(formData); // Pass the updated data to the parent
};
return (
<div className="modal-dialog modal-lg modal-simple modal-edit-user">
<div className="modal-content">
<div className="modal-body">
<div className="row">
<button
type="button"
className="btn-close"
data-bs-dismiss="modal"
aria-label="Close"
></button>
<div className="text-center mb-1">
<h5 className="mb-1">Manage Floors - {project.name}</h5>
</div>
<form className="row g-2" onSubmit={handleSubmit}>
<div className="col-12 col-md-12">
<label className="form-label" htmlFor="name">
Select Building
</label>
<select
id="buildingId"
name="buildingId"
className="select2 form-select form-select-sm"
aria-label="Default select example"
onChange={handleBuildigChange}
value={formData.buildingId}
>
<option value="0">Select Building</option>
{project.buildings.map((building) => (
<option key={building.id} value={building.id}>
{building.name}
</option>
))}
</select>
</div>
{formData.buildingId != "0" && (
<div className="col-12 col-md-12">
<label className="form-label" htmlFor="floorId">
Select Floor
</label>
<select
id="floorId"
name="floorId"
className="select2 form-select form-select-sm"
aria-label="Default select example"
onChange={handleFloorChange}
value={formData.floorId}
>
<option value="0">Add New Floor</option>
{selectedBuilding.floors.map((floor) => (
<option key={floor.id} value={floor.id}>
{floor.floorName}
</option>
))}
</select>
</div>
)}
{formData.buildingId != "0" && (
<div className="col-12 col-md-12">
{" "}
<label className="form-label" htmlFor="name">
{formData.id != "0" ? "Modify " : "Enter "} Floor Name
</label>
<div className="input-group">
<input
type="text"
id="floorName"
name="floorName"
className="form-control form-control-sm me-2"
placeholder="Floor Name"
onChange={handleChange}
value={formData.floorName}
/>
</div>
</div>
)}
<div className="col-12 text-center">
<button type="submit" className="btn btn-primary me-3">
{formData.id != "0" && formData.id != ""
? "Edit Floor"
: "Add Floor"}
</button>
<button
type="reset"
className="btn btn-label-secondary"
data-bs-dismiss="modal"
aria-label="Close"
>
Cancel
</button>
</div>
</form>
</div>
</div>
</div>
</div>
);
};
export default FloorModel;

View File

@ -0,0 +1,31 @@
import React from "react";
const Building = ( {building, toggleBuilding, expandedBuildings, getContent} ) =>
{
return (
<React.Fragment key={building.id}>
<tr className="overflow-auto">
<td
colSpan="4"
className="text-start"
style={{ background: "#f0f0f0", cursor: "pointer" }}
onClick={() => toggleBuilding(building.id)}
>
<div className="row table-responsive">
<h6 style={{ marginBottom: "0px", fontSize:"14px" }}>
{building.name} &nbsp;
{expandedBuildings.includes(building.id) ? (
<i className="bx bx-chevron-down"></i>
) : (
<i className="bx bx-chevron-right"></i>
)}
</h6>
</div>
</td>
</tr>
{expandedBuildings.includes(building.id) && getContent(building)}
</React.Fragment>
);
};
export default Building

View File

@ -0,0 +1,184 @@
import React, { useState, useEffect } from "react";
import { useForm } from "react-hook-form";
import { z } from "zod";
import { zodResolver } from "@hookform/resolvers/zod";
import ProjectRepository from "../../../repositories/ProjectRepository";
import { useSelector } from "react-redux";
import { useProjectDetails } from "../../../hooks/useProjects";
import {getCachedData} from "../../../slices/apiDataManager";
// Zod validation schema
const buildingSchema = z.object({
Id: z.string().optional(),
name: z.string().min(1, "Building name is required"),
description: z
.string()
.min(1, "Description is required")
.max(160, "Description cannot exceed 160 characters"),
});
const BuildingModel = ({
project,
onClose,
onSubmit,
clearTrigger,
onClearComplete,
editingBuilding = null,
}) => {
const selectedProject = useSelector(
(store) => store.localVariables.projectId
);
const [buildings ,setBuildings] = useState([])
const projects_Details = getCachedData("projectInfo")
const [formData, setFormData] = useState({
id: "",
name: "",
description: "",
projectId: project?.id,
});
useEffect(() => {
if (clearTrigger) {
setFormData({ id: "", name: "", description: "", projectId: project.id });
onClearComplete();
} else if (editingBuilding) {
setFormData({ ...editingBuilding, projectId: project.id });
}
return () => {
setValue("name", null);
};
}, [clearTrigger, onClearComplete, editingBuilding, project?.id]);
const {
register,
handleSubmit,
formState: { errors },
setValue,
reset,
getValues,
} = useForm({
resolver: zodResolver(buildingSchema),
defaultValues: formData, // Set default values from formData state
});
const handleBuildingChange = (e) => {
const selectedBuilding = project.buildings.find(
(b) => b.id === +e.target.value
);
if (selectedBuilding) {
setFormData({ ...selectedBuilding, projectId: project.id });
setValue("name", selectedBuilding.name); // Update name field
setValue("description", selectedBuilding.description); // Update description field
} else {
setFormData({ id: "", name: "", description: "", projectId: project.id });
setValue("name", "");
setValue("description", "");
}
};
const onSubmitHandler = async (data) => {
onSubmit({ ...data, projectId: project.id });
reset({
name: null,
description: null,
});
};
useEffect( () =>
{
setBuildings(projects_Details.data?.buildings)
},[projects_Details])
return (
<div className="modal-dialog modal-lg modal-simple modal-edit-user">
<div className="modal-content">
<div className="modal-body">
<button
type="button"
className="btn-close"
data-bs-dismiss="modal"
aria-label="Close"
onClick={onClose}
></button>
<h5 className="text-center mb-2">
Manage Buildings - {project?.name}
</h5>
<form onSubmit={handleSubmit(onSubmitHandler)} className="row g-2">
<div className="col-12">
<label className="form-label">Select Building</label>
<select
{...register("Id")}
className="select2 form-select form-select-sm"
onChange={(e) => {
handleBuildingChange(e);
}}
>
<option value="0">Add New Building</option>
{project &&
project?.buildings?.length > 0 &&
project?.buildings.map((building) => (
<option key={building.id} value={building.id}>
{building.name}
</option>
))}
</select>
{errors.Id && (
<span className="danger-text">{errors.Id.message}</span>
)}
</div>
<div className="col-12">
<label className="form-label">
{formData.id ? "Rename Building Name" : "New Building Name"}
</label>
<input
{...register("name")}
type="text"
className="form-control form-control-sm"
/>
{errors.name && (
<span className="danger-text">{errors.name.message}</span>
)}
</div>
<div className="col-12">
<label className="form-label">Description</label>
<textarea
{...register("description")}
maxLength="160"
rows="5"
className="form-control form-control-sm"
/>
{errors.description && (
<span className="danger-text">
{errors.description.message}
</span>
)}
</div>
<div className="col-12 text-center">
<button type="submit" className="btn btn-sm btn-primary me-3">
{formData.id && getValues("name")
? "Edit Building"
: "Add Building"}
</button>
<button
type="reset"
className="btn btn-sm btn-label-secondary"
data-bs-dismiss="modal"
aria-label="Close"
onClick={onClose}
>
Cancel
</button>
</div>
</form>
</div>
</div>
</div>
);
};
export default BuildingModel;

View File

@ -0,0 +1,29 @@
import React from "react";
import WorkArea from "./WorkArea";
const Floor = ( {floor, workAreas,forBuilding} ) =>
{
return (
<React.Fragment key={floor.id}>
{workAreas && workAreas.length > 0 ? (
workAreas.map((workArea) => (
<WorkArea forBuilding={forBuilding} key={workArea.id} workArea={workArea} floor={floor} />
))
) : (
<tr>
<td colSpan="4" className="text-start">
<div className="row ps-2">
<div className="col-6">
<h6>
<span>{floor.floorName} &nbsp;</span>
</h6>
</div>
</div>
</td>
</tr>
)}
</React.Fragment>
);
};
export default Floor

View File

@ -0,0 +1,228 @@
import React, { useState, useEffect } from "react";
import { useForm } from "react-hook-form";
import { z } from "zod";
import { zodResolver } from "@hookform/resolvers/zod";
import {getCachedData} from "../../../slices/apiDataManager";
// Zod validation schema
const floorSchema = z.object({
buildingId: z.string().min(1, "Building is required"),
id: z.string().min(1, "Floor is required").optional(),
floorName: z.string().min(1, "Floor Name is required"),
});
// Default model
const defaultModel = {
id: "0",
floorName: "",
buildingId: "0",
};
const FloorModel = ({
project,
onClose,
onSubmit,
clearTrigger,
onClearComplete,
} ) =>
{
const [formData, setFormData] = useState(defaultModel);
const [ selectedBuilding, setSelectedBuilding ] = useState( {} );
const [buildings, setBuildings] = useState(project?.buildings || []);
// Initialize the form with React Hook Form
const {
register,
handleSubmit,
setValue,
getValues,
reset,
formState: { errors },
} = useForm({
resolver: zodResolver(floorSchema),
defaultValues: defaultModel,
});
useEffect( () =>
{
if (clearTrigger) {
reset(defaultModel);
onClearComplete();
}
}, [clearTrigger, onClearComplete, reset,]);
// Handle building selection change
const handleBuildigChange = (e) => {
const buildingId = e.target.value;
console.log(buildingId)
const building = buildings.find((b) => b.id === Number(buildingId));
if (building) {
setSelectedBuilding(building);
setFormData({
id: "",
floorName: "",
buildingId: building.id,
});
setValue("buildingId", building.id); // Set value for validation
setValue("id", "0"); // Reset floorId when changing building
} else {
setSelectedBuilding({});
setFormData({
id: "",
floorName: "",
buildingId: "0",
});
setValue("buildingId", "0");
}
};
// Handle floor selection change
const handleFloorChange = (e) => {
const id = e.target.value;
const floor = selectedBuilding.floors.find((b) => b.id === Number(id));
if (floor) {
setFormData({
id: floor.id,
floorName: floor.floorName,
buildingId: selectedBuilding.id,
});
setValue("floorName", floor.floorName); // Set floor name for form
} else {
setFormData({
id: "0",
floorName: "",
buildingId: selectedBuilding.id,
});
setValue("floorName", "");
}
};
// Handle form submission
const onFormSubmit = (data) => {
onSubmit(data);
};
return (
<div className="modal-dialog modal-lg modal-simple modal-edit-user">
<div className="modal-content">
<div className="modal-body">
<div className="row">
<button type="button" className="btn-close" aria-label="Close" onClick={onClose}/>
<div className="text-center mb-1">
<h5 className="mb-1">Manage Floors - {project.name}</h5>
</div>
<form
className="row g-2"
onSubmit={handleSubmit(onFormSubmit)}
>
<div className="col-12 col-md-12">
<label className="form-label" htmlFor="buildingId">
Select Building
</label>
<select
id="buildingId"
className="select2 form-select form-select-sm"
aria-label="Select Building"
{...register("buildingId")}
onChange={handleBuildigChange}
>
<option value="0">Select Building</option>
{buildings &&
buildings?.length > 0 &&
buildings.map((building) => (
<option key={building.id} value={building.id}>
{building.name}
</option>
))}
</select>
{errors.buildingId && (
<p className="text-danger">{errors.buildingId.message}</p>
)}
</div>
{formData.buildingId !== "0" && (
<div className="col-12 col-md-12">
<label className="form-label" >
Select Floor
</label>
<select
id="id"
className="select2 form-select form-select-sm"
aria-label="Select Floor"
{...register("id")}
onChange={handleFloorChange}
>
<option value="0">Add New Floor</option>
{/* {selectedBuilding?.floors?.map((floor) => (
<option key={floor.id} value={floor.id}>
{floor.floorName}
</option>
) )} */}
{selectedBuilding &&
selectedBuilding?.floors.length > 0 &&
selectedBuilding?.floors.map((floor) => (
<option key={floor.id} value={floor.id}>
{floor.floorName}
</option>
))}
</select>
{errors.id && (
<p className="text-danger">{errors.id.message}</p>
)}
</div>
)}
{formData.buildingId !== "0" && (
<div className="col-12 col-md-12">
<label className="form-label" htmlFor="floorName">
{formData.id !== "0" ? "Modify " : "Enter "} Floor Name
</label>
<input
type="text"
id="floorName"
className="form-control form-control-sm me-2"
placeholder="Floor Name"
{...register("floorName")}
/>
{errors.floorName && (
<p className="text-danger">{errors.floorName.message}</p>
)}
</div>
)}
<div className="col-12 text-center">
<button type="submit" className="btn btn-sm btn-primary me-3">
{formData.id !== "0" && formData.id !== ""
? "Edit Floor"
: "Add Floor"}
</button>
<button
type="button"
className="btn btn-sm btn-label-secondary"
data-bs-dismiss="modal"
aria-label="Close"
onClick={onClose}
>
Cancel
</button>
</div>
</form>
</div>
</div>
</div>
</div>
);
};
export default FloorModel;

View File

@ -0,0 +1,154 @@
import { useEffect, useState } from "react";
import Building from "./Building";
import Floor from "./Floor";
import FloorModel from "./FloorModel";
import showToast from "../../../services/toastService";
import ProjectRepository from "../../../repositories/ProjectRepository";
const InfraTable = ({ buildings }) => {
const [projectBuilding, setProjectBuilding] = useState([]);
const [expandedBuildings, setExpandedBuildings] = useState([]);
const [showFloorModal, setShowFloorModal] = useState(false);
const [selectedBuilding, setSelectedBuilding] = useState(null);
const [clearTrigger, setClearTrigger] = useState(false);
const toggleBuilding = (buildingId) => {
setExpandedBuildings((prevExpanded) =>
prevExpanded.includes(buildingId)
? prevExpanded.filter((id) => id !== buildingId)
: [...prevExpanded, buildingId]
);
};
const handleAddFloor = (building) => {
setSelectedBuilding(building);
setShowFloorModal(true);
};
const handleFloorSubmit = async (data) => {
try {
const payload = [
{
building: null,
floor: {
id: data.id || "0",
floorName: data.floorName,
buildingId: data.buildingId,
},
workArea: null,
},
];
const res = await ProjectRepository.manageProjectInfra(payload);
if (res?.success) {
showToast("Floor saved successfully!", "success");
// Find and update the correct building
const updatedBuildings = projectBuilding.map((b) => {
if (b.id === parseInt(data.buildingId)) {
const newFloor = {
id: res.data?.[0]?.floor?.id || Math.random(),
floorName: res.data?.[0]?.floor?.floorName || data.floorName,
workAreas: [],
};
return {
...b,
floors: [...(b.floors || []), newFloor],
};
}
return b;
});
setProjectBuilding(updatedBuildings);
setShowFloorModal(false);
setClearTrigger(true);
} else {
showToast("Failed to save floor", "error");
}
} catch (err) {
console.error("Error adding floor", err);
showToast("Error occurred while saving floor", "error");
}
};
const handleClearComplete = () => {
setClearTrigger(false);
};
const getContent = (building) => {
return building.floors?.length > 0 ? (
building.floors.map((floor) => (
<Floor
key={floor.id}
forBuilding={building}
floor={floor}
workAreas={floor.workAreas}
/>
))
) : (
<tr>
<td colSpan="4">
<div className="alert alert-warning text-center mb-0" role="alert">
<p>No floors have been added yet. Please add floors to start managing your building.</p>
<button
type="button"
className="btn btn-xs btn-primary"
onClick={() => handleAddFloor(building)}
>
<i className="bx bx-plus-circle me-2"></i>
Add Floors
</button>
</div>
</td>
</tr>
);
};
useEffect(() => {
setProjectBuilding(buildings);
}, [buildings]);
return (
<div >
{projectBuilding && projectBuilding.length > 0 && (
<table className="table table-bordered">
<tbody>
{projectBuilding.map((building) => (
<Building
key={building.id}
building={building}
toggleBuilding={toggleBuilding}
expandedBuildings={expandedBuildings}
getContent={getContent}
/>
))}
</tbody>
</table>
)}
{showFloorModal && selectedBuilding && (
<div
className="modal fade show"
id="floor-model"
tabIndex="-1"
style={{ display: "block" }}
aria-modal="true"
role="dialog"
>
<FloorModel
project={{
buildings: [selectedBuilding]
}}
onClose={() => setShowFloorModal(false)}
onSubmit={handleFloorSubmit}
onClearComplete={handleClearComplete}
/>
</div>
)}
</div>
);
};
export default InfraTable;

View File

@ -1,58 +1,57 @@
import React, { useState, useEffect } from "react";
import { useForm } from "react-hook-form";
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from "zod";
import {useDispatch} from "react-redux";
import {changeMaster} from "../../../slices/localVariablesSlice";
import useMaster from "../../../hooks/masterHook/useMaster";
// Define Zod validation schema
const taskSchema = z.object({
buildingID: z.string().min(1, "Building is required"),
floorId: z.string().min(1, "Floor is required"),
workAreaId: z.number().min(1, "Work Area is required"),
activityID: z.number().min(1, "Activity is required"),
plannedWork: z.number().min(1, "Planned Work must be greater than 0"),
completedWork: z.number().optional(),
});
const defaultModel = {
id: "0",
workAreaId: 0,
activityId: 0,
activityID: 0,
plannedWork: 0,
completedWork: 0,
};
const TaskModel = ({
project,
activities,
onSubmit,
clearTrigger,
onClearComplete,
}) => {
onClearComplete,onClose
} )=>{
const dispatch = useDispatch()
const {data:activities,loading} = useMaster()
const [formData, setFormData] = useState(defaultModel);
const [selectedBuilding, setSelectedBuilding] = useState(null);
const [selectedFloor, setSelectedFloor] = useState(null);
const [selectedWorkArea, setSelectedWorkArea] = useState(null);
const [selectedActivity, setSelectedActivity] = useState(null);
//if (floor && floor.id) setFormData(floor);
useEffect(() => {
if (selectedBuilding) {
let building = project.buildings.find(
(b) => b.id === selectedBuilding.id
);
setSelectedBuilding(building);
}
if (selectedFloor) {
let floor = selectedBuilding.floors.find(
(b) => b.id === Number(selectedFloor.id)
);
setSelectedFloor(floor);
}
if (selectedWorkArea) {
formData.workAreaId = selectedWorkArea.id;
}
}, [project]);
const { register, handleSubmit, formState: { errors },reset } = useForm({
resolver: zodResolver(taskSchema),
defaultValues: formData,
});
useEffect(() => {
if (clearTrigger) {
let model = defaultModel;
model.floorId = selectedFloor.id;
setFormData(defaultModel); // Clear form
onClearComplete(); // Notify parent that clearing is done
setFormData(defaultModel);
onClearComplete();
}
}, [clearTrigger, onClearComplete]);
// Handle input change
// Handle input changes
const handleChange = (e) => {
const { name, value } = e.target;
const activity = activities.find((b) => b.id === Number(value));
@ -63,29 +62,15 @@ const TaskModel = ({
const { value } = e.target;
const activity = activities.find((b) => b.id === Number(value));
setFormData({ ...formData, ["activityId"]: value });
setFormData({ ...formData, ["activityID"]: value });
setSelectedActivity(activity);
};
const handleWorkAreaChange = (e) => {
const { value } = e.target;
const workArea = selectedFloor.workAreas.find(
(b) => b.id === Number(value)
);
const workArea = selectedFloor.workAreas.find((b) => b.id === Number(value));
setSelectedWorkArea(workArea);
setSelectedActivity(null);
setFormData({ ...formData, ["workAreaId"]: value });
// if (workArea) {
// setFormData({
// id: workArea.id,
// floorId: workArea.floorId,
// areaName: workArea.areaName,
// });
// } else
// setFormData({
// id: "0",
// floorId: selectedFloor.id,
// areaName: "",
// });
};
const handleFloorChange = (e) => {
@ -96,7 +81,8 @@ const TaskModel = ({
setSelectedActivity(null);
setFormData(defaultModel);
};
const handleBuildigChange = (e) => {
const handleBuildingChange = (e) => {
const { value } = e.target;
const building = project.buildings.find((b) => b.id === Number(value));
setSelectedBuilding(building);
@ -106,39 +92,59 @@ const TaskModel = ({
setFormData(defaultModel);
};
// Handle form submission
const handleSubmit = (e) => {
e.preventDefault();
onSubmit(formData); // Pass the updated data to the parent
const onSubmitForm = ( data ) =>
{
onSubmit( data );
setSelectedActivity(null),
setSelectedWorkArea(null)
reset( {
plannedWork: 0,
completedWork:0
})
};
useEffect( () =>
{
dispatch(changeMaster("Activity")),
() =>{
resetVlaue ()
}
},[])
const resetVlaue = () =>
{
setSelectedBuilding( null )
setSelectedFloor( null )
setSelectedWorkArea( null )
setSelectedActivity(null)
reset( {
plannedWork: 0,
completedWork:0
})
}
return (
<div className="modal-dialog modal-lg modal-simple modal-edit-user">
<div className="modal-content">
<div className="modal-body">
<div className="row">
<button
type="button"
className="btn-close"
data-bs-dismiss="modal"
aria-label="Close"
></button>
<button type="button" className="btn-close" aria-label="Close" onClick={onClose}/>
<div className="text-center mb-1">
<h5 className="mb-1">Manage Task</h5>
</div>
<form className="row g-2" onSubmit={handleSubmit}>
<form className="row g-2" onSubmit={handleSubmit(onSubmitForm)}>
{/* Select Building */}
<div className="col-6 col-md-6">
<label className="form-label" htmlFor="name">
Select Building
</label>
<select
id="buildingId"
name="buildingId"
id="buildingID"
name="buildingID"
className="select2 form-select form-select-sm"
aria-label="Default select example"
onChange={handleBuildigChange}
value={formData.buildingId}
{...register("buildingID")}
onChange={(e) => handleBuildingChange(e)}
>
<option value="0">Select Building</option>
{project.buildings.map((building) => (
@ -147,9 +153,11 @@ const TaskModel = ({
</option>
))}
</select>
{errors.buildingID && <p className="danger-text">{errors.buildingID.message}</p>}
</div>
{selectedBuilding && selectedBuilding.id != "0" && (
{/* Select Floor */}
{selectedBuilding && selectedBuilding.id !== "0" && (
<div className="col-6 col-md-6">
<label className="form-label" htmlFor="floorId">
Select Floor
@ -159,19 +167,21 @@ const TaskModel = ({
name="floorId"
className="select2 form-select form-select-sm"
aria-label="Default select example"
onChange={handleFloorChange}
value={formData.floorId}
{...register("floorId")}
onChange={(e) => handleFloorChange(e)}
>
<option value="0">Select Floor</option>
{selectedBuilding.floors.map((floor) => (
<option key={floor.id} value={floor.id}>
{floor.floorName} - ({floor.workAreas.length} Work
Areas)
{floor.floorName} - ({floor.workAreas.length} Work Areas)
</option>
))}
</select>
{errors.floorId && <p className="danger-text">{errors.floorId.message}</p>}
</div>
)}
{/* Select Work Area */}
{selectedFloor && (
<div className="col-12 col-md-12">
<label className="form-label" htmlFor="workAreaId">
@ -182,8 +192,8 @@ const TaskModel = ({
name="workAreaId"
className="select2 form-select form-select-sm"
aria-label="Default select example"
onChange={handleWorkAreaChange}
value={formData.workAreaId}
{...register("workAreaId",{valueAsNumber:true})}
onChange={(e) => handleWorkAreaChange(e)}
>
<option value="0">Select Work Area</option>
{selectedFloor.workAreas.map((workArea) => (
@ -192,20 +202,23 @@ const TaskModel = ({
</option>
))}
</select>
{errors.workAreaId && <p className="danger-text">{errors.workAreaId.message}</p>}
</div>
)}
{/* Select Activity */}
{selectedWorkArea && (
<div className="col-12 col-md-12">
<label className="form-label" htmlFor="activityId">
<label className="form-label" htmlFor="activityID">
Select Activity
</label>
<select
id="activityId"
name="activityId"
id="activityID"
name="activityID"
className="select2 form-select form-select-sm"
aria-label="Default select example"
onChange={handleActivityChange}
value={formData.activityId}
{...register("activityID",{valueAsNumber:true})}
onChange={(e) => handleActivityChange(e)}
>
<option value="0">Select Activity</option>
{activities.map((activity) => (
@ -214,83 +227,75 @@ const TaskModel = ({
</option>
))}
</select>
</div>
)}
{selectedActivity && (
<div className="col-5 col-md-5">
{" "}
<label className="form-label" htmlFor="plannedWork">
{formData.id != "0" ? "Modify " : "Enter "} Planned Work
</label>
<div className="input-group">
<input
type="text"
id="plannedWork"
name="plannedWork"
className="form-control form-control-sm me-2"
placeholder="Task"
onChange={handleChange}
value={formData.plannedWork}
/>
</div>
</div>
)}
{selectedActivity && (
<div className="col-5 col-md-5">
{" "}
<label className="form-label" htmlFor="completedWork">
{formData.id != "0" ? "Modify " : "Enter "} Completed Work
</label>
<div className="input-group">
<input
type="text"
id="completedWork"
name="completedWork"
className="form-control form-control-sm me-2"
placeholder="Completed Work"
onChange={handleChange}
value={formData.completedWork}
/>
</div>
{errors.activityID && <p className="danger-text">{errors.activityID.message}</p>}
</div>
)}
{/* Planned Work */}
{selectedActivity && (
<div className="col-5 col-md-5">
<label className="form-label" htmlFor="plannedWork">
{formData.id !== "0" ? "Modify " : "Enter "} Planned Work
</label>
<input
{...register("plannedWork", { valueAsNumber: true })}
type="number"
id="plannedWork"
name="plannedWork"
className="form-control form-control-sm me-2"
placeholder="Planned Work"
/>
{errors.plannedWork && <p className="danger-text">{errors.plannedWork.message}</p>}
</div>
)}
{/* Completed Work */}
{selectedActivity && (
<div className="col-5 col-md-5">
<label className="form-label" htmlFor="completedWork">
{formData.id !== "0" ? "Modify " : "Enter "} Completed Work
</label>
<input
{...register("completedWork", { valueAsNumber: true })}
type="number"
id="completedWork"
name="completedWork"
className="form-control form-control-sm me-2"
placeholder="Completed Work"
/>
{errors.completedWork && <p className="danger-text">{errors.completedWork.message}</p>}
</div>
)}
{/* Unit */}
{selectedActivity && (
<div className="col-2 col-md-2">
{" "}
<label className="form-label" htmlFor="unit">
Unit
</label>
<div className="input-group">
<input
type="text"
id="unit"
disabled
name="unit"
className="form-control form-control-sm me-2"
placeholder="Unit"
onChange={handleChange}
value={selectedActivity.unitOfMeasurement}
/>
</div>
<input
type="text"
disabled
id="unit"
name="unit"
className="form-control form-control-sm me-2"
value={selectedActivity?.unitOfMeasurement || ""}
/>
</div>
)}
<div className="col-12 text-center">
{selectedActivity && (
<button type="submit" className="btn btn-primary me-3">
{formData.id != "0" && formData.id != ""
? "Edit Task"
: "Add Task"}
</button>
)}
<button type="submit" className="btn btn-sm btn-primary me-3">
{formData.id !== "0" && formData.id !== "" ? "Edit Task" : "Add Task"}
</button>
<button
type="reset"
className="btn btn-label-secondary"
data-bs-dismiss="modal"
aria-label="Close"
type="button"
className="btn btn-sm btn-label-secondary"
onClick={onClose}
>
Cancel
</button>
</div>
</form>
</div>

View File

@ -0,0 +1,62 @@
import React,{useEffect} from "react";
import WorkItem from "./WorkItem";
const WorkArea = ( {workArea, floor, forBuilding} ) =>
{
useEffect(() => {
}, [workArea]);
return (
<React.Fragment key={workArea.id}>
<tr>
<td colSpan="4" className="text-start table-cell">
<div className="row ps-2">
<div className="col-6">
<h6>
<span>
{floor.floorName} - {workArea.areaName} &nbsp;
</span>
</h6>
</div>
</div>
</td>
</tr>
{workArea?.workItems && workArea.workItems.length > 0 ? (
<tr className="overflow-auto">
<td colSpan={4}>
<table className="table mx-1">
<thead>
<tr>
<th>Activity</th>
{/* for mobile view */}
<th className="d-sm-none d-sm-table-cell">Status</th>
{/* for greather than mobile view ************* */}
<th className="d-none d-md-table-cell">Planned</th>
<th className="d-none d-md-table-cell">Completed</th>
{/* ************************** */}
<th>Progress</th>
<th>Actions</th>
</tr>
</thead>
<tbody className="table-border-bottom-0">
{workArea?.workItems && workArea.workItems.length > 0 ? (
workArea.workItems.map((workItem) => (
<WorkItem key={workItem.workItemId} workItem={workItem} forBuilding={forBuilding} forFloor={floor} forWorkArea={workArea} />
))
) : (
<tr>
<td colSpan="4" className="text-center">
No Data
</td>
</tr>
)}
</tbody>
</table>
</td>
</tr>
) : null}
</React.Fragment>
);
};
export default WorkArea;

View File

@ -0,0 +1,219 @@
import React, { useState, useEffect } from "react";
import { set, useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
// Zod schema for form validation
const workAreaSchema = z.object( {
id:z.string().nonempty("Floor is required"),
buildingId: z.string().nonempty("Building is required"),
floorId: z.string().nonempty("Floor is required"),
areaName: z.string().nonempty( "Work Area Name is required" ).min( 3, "Name must be at least 3 characters long" ),
});
// Default form data
const defaultModel = {
id: "0",
areaName: "",
floorId: "0",
};
const WorkAreaModel = ({ project, onSubmit, clearTrigger, onClearComplete, onClose }) => {
const [selectedBuilding, setSelectedBuilding] = useState(null);
const [ selectedFloor, setSelectedFloor ] = useState( null );
const [selectdWorkArea,setWorkArea] = useState()
const { register, handleSubmit, formState: { errors }, setValue, reset, watch } = useForm({
resolver: zodResolver(workAreaSchema), // Use Zod resolver for validation
defaultValues: defaultModel,
});
const floorId = watch( "floorId" ); // Watch the floorId for conditional rendering
useEffect(() => {
if (clearTrigger) {
reset(defaultModel); // Reset form to initial state
setSelectedBuilding(null);
setSelectedFloor(null);
onClearComplete();
}
}, [clearTrigger, onClearComplete, reset]);
const handleWorkAreaChange = ( e ) =>
{
const { value } = e.target;
if (value === "0") {
setValue("id", "0"); // Create New Work Area
setValue( "areaName", "" );
setWorkArea(String(0))
} else {
const workArea = selectedFloor?.workAreas.find((b) => b.id === Number(value));
if ( workArea )
{
setValue("id", String(workArea.id)); // Set id as a string
setValue("areaName", workArea.areaName);
setWorkArea(String(workArea.id))
}
}
};
const handleFloorChange = (e) => {
const { value } = e.target;
const floor = selectedBuilding?.floors.find((b) => b.id === Number(value));
if (floor) {
setSelectedFloor(floor);
setValue("floorId", floor.id); // Update floorId
setValue("id", "0"); // Reset Work Area ID for new area creation
setValue("areaName", ""); // Reset Work Area Name when changing floor
} else {
setSelectedFloor(null);
setValue("floorId", "0");
setValue("id", "0"); // Reset Work Area ID
setValue("areaName", ""); // Reset Work Area Name
}
};
const handleBuildingChange = (e) => {
const { value } = e.target;
const building = project?.buildings.find((b) => b.id === Number(value));
setSelectedBuilding(building);
setSelectedFloor(null); // Reset selected floor on building change
reset(defaultModel); // Reset the form when a new building is selected
};
const onSubmitForm = ( data ) =>
{
let WorkArea = {
id: data.id,
areaName: data.areaName,
floorId: data.floorId,
buildingId:data.buildingId
}
onSubmit(WorkArea); // Send the final data to the parent
};
const handleCancel = () => {
reset(defaultModel); // Reset the form to initial state
setSelectedFloor(null);
setSelectedBuilding( null );
onClose()
};
return (
<div className="modal-dialog modal-lg modal-simple modal-edit-user">
<div className="modal-content">
<div className="modal-body">
<div className="row">
<button type="button" className="btn-close" aria-label="Close" onClick={onClose}/>
<div className="text-center mb-1">
<h5 className="mb-1">Manage Work Area</h5>
</div>
<form className="row g-2" onSubmit={handleSubmit(onSubmitForm)}>
{/* Building Selection */}
<div className="col-6 col-md-6">
<label className="form-label" htmlFor="buildingId">Select Building</label>
<select
id="buildingId"
name="buildingId"
className="select2 form-select form-select-sm"
{...register("buildingId")}
onChange={handleBuildingChange}
>
<option value="0">Select Building</option>
{project?.buildings?.map((building) => (
<option key={building.id} value={building.id}>
{building.name}
</option>
))}
</select>
{errors.buildingId && <span>{errors.buildingId.message}</span>}
</div>
{/* Floor Selection */}
{selectedBuilding && selectedBuilding.buildingId !== "0" && (
<div className="col-6 col-md-6">
<label className="form-label" htmlFor="floorId">Select Floor</label>
<select
id="floorId"
name="floorId"
className="select2 form-select form-select-sm"
{...register("floorId")}
onChange={handleFloorChange}
>
<option value="0">Select Floor</option>
{selectedBuilding.floors.map((floor) => (
<option key={floor.id} value={floor.id}>
{floor.floorName}
</option>
))}
</select>
{errors.floorId && <span>{errors.floorId.message}</span>}
</div>
)}
{/* Work Area Selection or Creation */}
{floorId !== "0" && (
<>
<div className="col-12 col-md-12">
<label className="form-label" >Select Work Area</label>
<select
id="workAreaId"
name="workAreaId"
className="select2 form-select form-select-sm"
{...register("id")}
onChange={handleWorkAreaChange}
>
<option value="0">Create New Work Area</option>
{selectedFloor?.workAreas?.map((workArea) => (
<option key={workArea.id} value={workArea.id}>
{workArea.areaName}
</option>
))}
</select>
{errors.id && <span>{errors.id.message}</span>}
</div>
{/* Work Area Name Input */}
<div className="col-12 col-md-12">
<label className="form-label" htmlFor="areaName">
{watch("id") === "0" ? "Enter Work Area Name" : "Modify Work Area Name"}
</label>
<input
type="text"
id="areaName"
name="areaName"
className="form-control form-control-sm"
placeholder="Work Area"
{...register("areaName")}
/>
{errors.areaName && <span className="danger-text">{errors.areaName.message}</span>}
</div>
{/* Submit and Cancel Buttons */}
<div className="col-12 text-center">
<button type="submit" className="btn btn-primary me-3">
{watch("id") === "0" ? "Add Work Area" : "Edit Work Area"}
</button>
<button type="button" className="btn btn-label-secondary" onClick={handleCancel} data-bs-dismiss="modal" aria-label="Close">
Cancel
</button>
</div>
</>
)}
</form>
</div>
</div>
</div>
</div>
);
};
export default WorkAreaModel;

View File

@ -0,0 +1,170 @@
import React, { useState, useEffect } from "react";
import { useModal } from "../../../ModalContext";
import AssignRoleModel from "../AssignRole";
import { useParams } from "react-router-dom";
import GlobalModel from "../../common/GlobalModel";
const WorkItem = ({ workItem, forBuilding, forFloor, forWorkArea }) => {
const { projectId } = useParams();
const [itemName, setItemName] = useState("");
const [NewWorkItem, setNewWorkItem] = useState();
const [isModalOpen, setIsModalOpen] = useState(false);
const openModal = () => setIsModalOpen(true);
const closeModal = () => setIsModalOpen(false);
const getProgress = (planned, completed) => {
return (completed * 100) / planned + "%";
};
const handleAssignTask = () => {
setItemName("");
};
useEffect(() => {
setNewWorkItem(workItem);
}, [workItem]); // This hook will run whenever the workItem prop changes
let assigndata = {
building: forBuilding,
floor: forFloor,
workArea: forWorkArea,
workItem,
};
const hasWorkItem = NewWorkItem && NewWorkItem;
return (
<>
<GlobalModel
isOpen={isModalOpen}
closeModal={closeModal}
dialogClass="modal-dialog-centered"
role="document"
size="lg"
>
<AssignRoleModel assignData={assigndata} onClose={closeModal} />
</GlobalModel>
<tr>
<td className="text-start table-cell-small">
<i className="bx bx-right-arrow-alt"></i>
<span className="fw-medium">
{hasWorkItem
? NewWorkItem?.workItem?.activityMaster?.activityName ||
workItem.activityMaster?.activityName
: "NA"}
</span>
</td>
{/* for mobile view */}
<td className="text-center d-sm-none d-sm-table-cell">
{hasWorkItem
? NewWorkItem?.workItem?.completedWork || workItem?.completedWork
: "NA"}
/{" "}
{hasWorkItem
? NewWorkItem?.workItem?.plannedWork || workItem?.plannedWork
: "NA"}
</td>
{/* for greather than mobile view ************* */}
<td className="text-center d-none d-md-table-cell">
{hasWorkItem
? NewWorkItem?.workItem?.plannedWork || workItem?.plannedWork
: "NA"}
</td>
<td className="text-center d-none d-md-table-cell">
{hasWorkItem
? NewWorkItem?.workItem?.completedWork || workItem?.completedWork
: "NA"}
</td>
{/* ************************************************ */}
<td className="text-center" style={{ width: "15%" }}>
<div className="progress p-0">
<div
className="progress-bar"
role="progressbar"
style={{
width: getProgress(
NewWorkItem?.workItem?.plannedWork || workItem?.plannedWork,
NewWorkItem?.workItem?.completedWork ||
workItem?.completedWork
),
height: "10px",
}}
aria-valuenow={
hasWorkItem
? NewWorkItem?.workItem?.completedWork ||
workItem?.completedWork
: 0
}
aria-valuemin="0"
aria-valuemax={
hasWorkItem
? NewWorkItem?.workItem?.plannedWork || workItem?.plannedWork
: 0
}
></div>
</div>
</td>
{/* for greather than mobile view */}
<td className="d-none d-md-table-cell">
<div className="dropdown">
{!projectId && (
<button
aria-label="Modify"
type="button"
className="btn p-0"
data-bs-toggle="modal"
data-bs-target="#project-modal"
onClick={openModal}
>
<span className="badge badge-md bg-label-primary me-1">Assign</span>
</button>
)}
<button
aria-label="Modify"
type="button"
className="btn p-0 dropdown-toggle hide-arrow"
>
<i className="bx bxs-edit me-2 text-primary"></i>
</button>
<button
aria-label="Delete"
type="button"
className="btn p-0 dropdown-toggle hide-arrow"
>
<i className="bx bx-trash me-1 text-danger"></i>
</button>
</div>
</td>
{/* for mobile view */}
<td className="text-end d-sm-none d-sm-table-cell">
<div className="d-flex align-items-center justify-content-center ">
<a
className={`btn btn-icon dropdown-toggle hide-arrow`}
data-bs-toggle="dropdown"
>
<i className="bx bx-dots-vertical-rounded bx-md"></i>
</a>
<div className="dropdown-menu dropdown-menu-end m-0">
{" "}
<a className="dropdown-item">
{" "}
<i className="bx bxs-edit me-2 text-primary"></i>Edit
</a>
<a className="dropdown-item">
{" "}
<i className="bx bx-trash me-1 text-danger"></i>Delete
</a>
</div>
</div>
</td>
</tr>
</>
);
};
export default WorkItem;

View File

@ -27,7 +27,7 @@ const ManageProjectInfo = ( {project,handleSubmitForm, onClose} ) =>
...(project?.id ? { id: z.number().optional() } : {}),
name: z.string().min( 1, {message: "Project Name is required"} ),
contactPerson: z.string().min( 1, {message: "Contact Person Name is required"} ),
projectAddress: z.string().min( 1, {message: "Address is required"} ).max(150, 'Address must not exceed 150 characters'),
projectAddress: z.string().min( 1, {message: "Address is required"} ).max(500, 'Address must not exceed 150 characters'),
startDate: z.string().min( 1, {message: "Start Date is required"} ).default(currentDate),
endDate: z.string().min( 1, {message: "End Date is required"} ).default(currentDate),
projectStatusId: z
@ -41,7 +41,14 @@ const ManageProjectInfo = ( {project,handleSubmitForm, onClose} ) =>
return num;
}),
} )
} ) .refine((data) => {
const start = new Date(data.startDate);
const end = new Date(data.endDate);
return end > start;
}, {
path: ['endDate'], // attaches the error to the endDate field
message: 'End Date must be greater than Start Date',
});
const {register, control, handleSubmit, formState: {errors}, reset, getValues} = useForm( {
@ -82,13 +89,15 @@ const ManageProjectInfo = ( {project,handleSubmitForm, onClose} ) =>
handleSubmitForm( updatedProject )
};
useEffect( () =>
{
return ()=>setLoading(false)
},[])
return (
<div className="modal-dialog modal-lg modal-simple edit-project-modal" role="document">
<div className="modal-dialog modal-lg modal-simple mx-sm-auto mx-1 edit-project-modal" role="document">
<div className="modal-content">
<div className="modal-body">
<div className="modal-body p-sm-4 p-0">
<button
type="button"
className="btn-close"
@ -181,10 +190,10 @@ const ManageProjectInfo = ( {project,handleSubmitForm, onClose} ) =>
<option value="1">Active</option>
<option value="2">On Hold</option>
<option value="3">Suspended</option>
<option value="4">Inactive</option>
{/* <option value="3">Suspended</option> */}
<option value="3">Inactive</option>
<option value="5">Completed</option>
<option value="4">Completed</option>
</select>
{errors.projectStatusId && <div className="danger-text text-start" style={{fontSize:"12px"}}>{errors.projectStatusId.message}</div>}

View File

@ -5,12 +5,14 @@ import ProjectRepository from "../../repositories/ProjectRepository";
import { cacheData,getCachedData } from "../../slices/apiDataManager";
import {hasUserPermission} from "../../utils/authUtils";
import moment from "moment";
import {useHasUserPermission} from "../../hooks/useHasUserPermission";
import {MANAGE_PROJECT} from "../../utils/constants";
const ProjectBanner = ( {project_data} ) =>
{
const [showModal, setShowModal] = useState(false);
const manageProject = useHasUserPermission(MANAGE_PROJECT)
const [ CurrentProject, setCurrentProject ] = useState( project_data )
if (project_data == null) {
return <span>incomplete project information</span>;
@ -74,59 +76,33 @@ const ProjectBanner = ( {project_data} ) =>
onClose={handleClose}
></ManageProjectInfo>
</div>
{/* -------------------- */}
{/* Project Banner */}
<div className="col-12">
<div className="card mb-6 pb-0">
<div className="user-profile-header d-flex flex-column flex-lg-row text-sm-start text-center mb-2 ">
<div className="flex-shrink-0 mt-1 mx-sm-0 px-2 mx-auto">
<div className="d-flex align-items-center justify-content-between p-4 flex-wrap">
{/* Left: Icon + Name */}
<div className="d-flex align-items-center gap-3">
<img
src="../../assets/icons/civil-engineering.svg"
alt="user image"
className="d-block h-auto ms-0 ms-sm-6 rounded-3 user-profile-img project-banner-icon"
></img>
className="rounded-3"
style={{ width: "60px", height: "60px" }}
/>
<h4 className="mb-0">
{CurrentProject.name ? CurrentProject.name : "N/A"}
</h4>
</div>
<div className="flex-grow-1 mt-1 mt-lg-1">
<div className="d-flex align-items-md-end align-items-sm-start align-items-center justify-content-md-between justify-content-start mx-5 flex-md-row flex-column gap-4">
<div className="user-profile-info">
<h4 className="mb-2 mt-lg-1">
{CurrentProject.name ? CurrentProject.name : "N/A"}
</h4>
<h6 className="mb-1 mt-lg-1">
Address:{" "}
{CurrentProject.projectAddress ? CurrentProject.projectAddress : "N/A"}
</h6>
<h6 className="mb-1 mt-lg-1">
Contact:{" "}
{CurrentProject.contactPerson ? CurrentProject.contactPerson : "N/A"}
</h6>
<h6 className="mb-1 mt-lg-1">
<span>
{" "}
Start Date:{" "}
{CurrentProject.startDate
? moment(CurrentProject.startDate).format("DD-MMM-YYYY")
: "N/A"}
</span>
<span className="ms-5">
End Date:{" "}
{CurrentProject.endDate
? moment(CurrentProject.endDate).format("DD-MMM-YYYY")
: "N/A"}
</span>
</h6>
</div>
<button
{manageProject && (
<button
type="button"
className={`btn btn-sm btn-primary ${hasUserPermission("53176ebf-c75d-42e5-839f-4508ffac3def") ? "":"d-none"}`}
className={`btn btn-sm btn-primary ${!manageProject && 'd-none'}`}
data-bs-toggle="modal"
data-bs-target="#edit-project-modal"
onClick={handleShow}
>
Modify
</button>
</div>
</div>
Modify
</button>
)}
</div>
</div>
</div>

View File

@ -1,271 +1,285 @@
import React,{useState,useEffect} from "react";
import React, { useState } from "react";
import moment from "moment";
import { getDateDifferenceInDays } from "../../utils/dateUtils";
import { useNavigate } from "react-router-dom";
import {useProjectDetails} from "../../hooks/useProjects";
import { useProjectDetails } from "../../hooks/useProjects";
import ManageProjectInfo from "./ManageProjectInfo";
import ProjectRepository from "../../repositories/ProjectRepository";
import {cacheData, getCachedData} from "../../slices/apiDataManager";
import { cacheData, getCachedData } from "../../slices/apiDataManager";
import showToast from "../../services/toastService";
import {useHasUserPermission} from "../../hooks/useHasUserPermission";
import {MANAGE_PROJECT} from "../../utils/constants";
import { useHasUserPermission } from "../../hooks/useHasUserPermission";
import { MANAGE_PROJECT } from "../../utils/constants";
const ProjectCard = ( {projectData} ) =>
{
const[projectInfo,setProjectInfo] = useState(projectData)
const navigate = useNavigate()
const {projects_Details, loading} = useProjectDetails( projectData.id )
const [ showModal, setShowModal ] = useState( false );
const ManageProject = useHasUserPermission(MANAGE_PROJECT)
const handleShow = () => setShowModal(true);
const handleClose = () => setShowModal( false );
const getProjectStatusName = (statusId) => {
switch (statusId) {
case 1:
return "Active";
case 2:
return "On Hold";
case 3:
return "Suspended";
case 4:
return "Inactive";
case 5:
return "Completed";
}
};
const ProjectCard = ({ projectData }) => {
const [projectInfo, setProjectInfo] = useState(projectData);
const [projectDetails, setProjectDetails] = useState(null);
const [showModal, setShowModal] = useState(false);
const navigate = useNavigate();
const ManageProject = useHasUserPermission(MANAGE_PROJECT);
const getProjectStatusColor = (statusId) => {
switch (statusId) {
case 1:
return "bg-label-success";
case 2:
return "bg-label-warning";
case 3:
return "bg-label-info";
case 4:
return "bg-label-secondary";
case 5:
return "bg-label-dark";
}
};
const handleShow = async () => {
try {
const response = await ProjectRepository.getProjectByprojectId(projectInfo.id);
setProjectDetails(response.data);
setShowModal(true);
} catch (error) {
showToast("Failed to load project details", "error");
}
};
const handleViewProject = (e) => {
navigate(`/projects/${projectData.id}`)
};
const getProgress = (planned, completed) => {
return (completed * 100) / planned + "%";
};
const getProgressInNumber = (planned, completed) => {
return (completed * 100) / planned;
};
const handleClose = () => setShowModal(false);
const handleFormSubmit = ( updatedProject ) =>
{
if ( projectInfo?.id )
{
ProjectRepository.updateProject(projectInfo.id,updatedProject).then( ( response ) =>
{
const updatedProjectData = {
...projectInfo,
...response.data,
building:projects_Details.building,
};
setProjectInfo( updatedProject )
if ( getCachedData( `projectinfo-${ projectInfo.id }` ) )
{
cacheData( `projectinfo-${ projectInfo.id }`, updatedProjectData );
}
const projects_list = getCachedData( "projectslist" );
if ( projects_list )
{
const updatedProjectsList = projects_list.map(project =>
project.id == projectInfo.id ? {
...project,
...response.data,
tenant:project.tenant
} : project
);
cacheData("projectslist",updatedProjectsList)
}
showToast( "Project updated successfully.", "success" );
setShowModal(false)
})
.catch((error) => {
showToast( error.message, "error" );
});
}
const getProjectStatusName = (statusId) => {
switch (statusId) {
case 1:
return "Active";
case 2:
return "On Hold";
// case 3:
// return "Suspended";
case 3:
return "Inactive";
case 4:
return "Completed";
}
};
const getProjectStatusColor = (statusId) => {
switch (statusId) {
case 1:
return "bg-label-success";
case 2:
return "bg-label-warning";
case 3:
return "bg-label-info";
case 4:
return "bg-label-secondary";
case 5:
return "bg-label-dark";
}
};
const handleViewProject = () => {
navigate(`/projects/${projectData.id}`);
};
return (
<>
<div
className={`modal fade ${showModal ? 'show' : ''}`}
tabIndex="-1"
role="dialog"
style={{ display: showModal ? 'block' : 'none' }}
aria-hidden={!showModal}
>
<ManageProjectInfo
project={projects_Details}
handleSubmitForm={handleFormSubmit}
onClose={handleClose}
></ManageProjectInfo>
</div>
const handleFormSubmit = (updatedProject) => {
if (projectInfo?.id) {
ProjectRepository.updateProject(projectInfo.id, updatedProject)
.then((response) => {
const updatedProjectData = {
...projectInfo,
...response.data,
building: projectDetails?.building,
};
<div className="col-md-6 col-lg-4 col-xl-4 order-0 mb-4">
<div className="card cursor-pointer">
<div className="card-header pb-4">
<div className="d-flex align-items-start">
<div className="d-flex align-items-center">
<div className="avatar me-4">
<i
className="rounded-circle bx bx-building-house"
style={{ fontSize: "xx-large" }}
></i>
</div>
<div className="me-2">
<h5 className="mb-0">
<a
className="stretched-link text-heading"
onClick={handleViewProject}
>
{projectInfo.name}
</a>
</h5>
<div className="client-info text-body">
<span className="fw-medium">Client: </span>
<span>{projectInfo.contactPerson}</span>
</div>
</div>
</div>
<div className={`ms-auto ${!ManageProject && 'd-none'}`}>
<div className="dropdown z-2">
<button
type="button"
className="btn btn-icon btn-text-secondary rounded-pill dropdown-toggle hide-arrow p-0"
data-bs-toggle="dropdown"
aria-expanded="false"
setProjectInfo(updatedProject);
if (getCachedData(`projectinfo-${projectInfo.id}`)) {
cacheData(`projectinfo-${projectInfo.id}`, updatedProjectData);
}
const projects_list = getCachedData("projectslist");
if (projects_list) {
const updatedProjectsList = projects_list.map((project) =>
project.id === projectInfo.id
? { ...project, ...response.data, tenant: project.tenant }
: project
);
cacheData("projectslist", updatedProjectsList);
}
showToast("Project updated successfully.", "success");
setShowModal(false);
})
.catch((error) => {
showToast(error.message, "error");
});
}
};
return (
<>
{showModal && projectDetails && (
<div
className="modal fade show"
tabIndex="-1"
role="dialog"
style={{ display: "block" }}
aria-hidden="false"
>
<i className="bx bx-dots-vertical-rounded bx-sm text-muted"></i>
</button>
<ul className="dropdown-menu dropdown-menu-end">
<li>
<a
aria-label="click to View details"
className="dropdown-item"
onClick={handleViewProject}
>
<i className="bx bx-detail me-2"></i>
<span className="align-left">View details</span>
</a>
</li>
<li data-bs-toggle="modal"
data-bs-target="#edit-project-modal"
onClick={handleShow}>
<a
className="dropdown-item"
>
<i className="bx bx-pencil me-2"></i>
<span className="align-left"
>Modify</span>
</a>
</li>
<ManageProjectInfo
project={projectDetails}
handleSubmitForm={handleFormSubmit}
onClose={handleClose}
/>
</div>
)}
<li>
<a className="dropdown-item" >
<i className="bx bx-task me-2"></i>
<span className="align-left">Activities</span>
</a>
</li>
</ul>
</div>
</div>
</div>
</div>
<div className="card-body pb-1">
<div className="d-flex align-items-center flex-wrap">
<div className="text-start mb-4">
<p className="mb-1">
<span className="text-heading fw-medium">Start Date: </span>
{projectInfo.startDate
? moment(projectInfo.startDate).format("DD-MMM-YYYY")
: "NA"}
</p>
<p className="mb-1">
<span className="text-heading fw-medium">Deadline: </span>
<div className="col-md-6 col-lg-4 col-xl-4 order-0 mb-4">
<div className="card cursor-pointer">
<div className="card-header pb-4">
<div className="d-flex align-items-start">
<div className="d-flex align-items-center">
<div className="avatar me-4">
<i
className="rounded-circle bx bx-building-house"
style={{ fontSize: "xx-large" }}
></i>
</div>
<div className="me-2">
<h5 className="mb-0">
<a
className="stretched-link text-heading"
onClick={handleViewProject}
>
{projectInfo.name}
</a>
</h5>
<div className="client-info text-body">
<span className="fw-medium">Client: </span>
<span>{projectInfo.contactPerson}</span>
</div>
</div>
</div>
<div className={`ms-auto ${!ManageProject && "d-none"}`}>
<div className="dropdown z-2">
<button
type="button"
className="btn btn-icon btn-text-secondary rounded-pill dropdown-toggle hide-arrow p-0"
data-bs-toggle="dropdown"
aria-expanded="false"
>
<i className="bx bx-dots-vertical-rounded bx-sm text-muted" 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">
<li>
<a
aria-label="click to View details"
className="dropdown-item"
onClick={handleViewProject}
>
<i className="bx bx-detail me-2"></i>
<span className="align-left">View details</span>
</a>
</li>
{projectInfo.endDate
? moment(projectInfo.endDate).format("DD-MMM-YYYY")
: "NA"}
</p>
<p className="mb-0">{projectInfo.projectAddress}</p>
<li onClick={handleShow}>
<a className="dropdown-item">
<i className="bx bx-pencil me-2"></i>
<span className="align-left">Modify</span>
</a>
</li>
<li>
<a className="dropdown-item">
<i className="bx bx-task me-2"></i>
<span className="align-left">Activities</span>
</a>
</li>
</ul>
</div>
</div>
</div>
</div>
<div className="card-body pb-1">
<div className="d-flex align-items-center flex-wrap">
<div className="text-start mb-4">
<p className="mb-1">
<span className="text-heading fw-medium">Start Date: </span>
{projectInfo.startDate
? moment(projectInfo.startDate).format("DD-MMM-YYYY")
: "NA"}
</p>
<p className="mb-1">
<span className="text-heading fw-medium">Deadline: </span>
{projectInfo.endDate
? moment(projectInfo.endDate).format("DD-MMM-YYYY")
: "NA"}
</p>
<p className="mb-0">{projectInfo.projectAddress}</p>
</div>
</div>
</div>
<div className="card-body border-top">
<div className="d-flex align-items-center mb-4">
<p className="mb-0">
<span
className={
`badge rounded-pill ` +
getProjectStatusColor(projectInfo.projectStatusId)
}
>
{getProjectStatusName(projectInfo.projectStatusId)}
</span>
</p>{" "}
{getDateDifferenceInDays(projectInfo.endDate, Date()) >= 0 &&
( <span className="badge bg-label-success ms-auto">
{projectInfo.endDate &&
getDateDifferenceInDays(projectInfo.endDate, Date())}{" "}
Days left
</span>) }
{getDateDifferenceInDays(projectInfo.endDate, Date()) < 0 &&
( <span className="badge bg-label-danger ms-auto">
{projectInfo.endDate &&
getDateDifferenceInDays(projectInfo.endDate, Date())}{" "}
Days overdue
</span>)}
</div>
<div className="d-flex justify-content-between align-items-center mb-2">
<small className="text-body">Task: {projectInfo.completedWork} / {projectInfo.plannedWork}</small>
<small className="text-body">{Math.floor(getProgressInNumber(projectInfo.plannedWork, projectInfo.completedWork)) || 0} % Completed</small>
</div>
<div className="progress mb-4 rounded" style={{ height: "8px" }}>
<div
className="progress-bar rounded"
role="progressbar"
style={{ width: getProgress(projectInfo.plannedWork, projectInfo.completedWork) }}
aria-valuenow={projectInfo.completedWork}
aria-valuemin="0"
aria-valuemax={projectInfo.plannedWork}
></div>
</div>
<div className="d-flex align-items-center justify-content-between">
{/* <div className="d-flex align-items-center ">
</div> */}
<div >
<a
className="text-muted d-flex " alt="Active team size"
>
<i className="bx bx-group bx-sm me-1_5"></i>{projectInfo?.teamSize} Members
</a>
</div>
<div >
<a
className="text-muted d-flex align-items-center"
>
<i className="bx bx-chat me-1 "></i> <span className="text-decoration-line-through">15</span>
</a>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div className="card-body border-top">
<div className="d-flex align-items-center mb-4">
<p className="mb-0">
<span
className={
`badge rounded-pill ` +
getProjectStatusColor(projectInfo.projectStatusId)
}
>
{getProjectStatusName(projectInfo.projectStatusId)}
</span>
</p>{" "}
<span className="badge bg-label-success ms-auto">
{projectInfo.startDate &&
projectInfo.endDate &&
getDateDifferenceInDays(
projectInfo.startDate,
projectInfo.endDate
)}{" "}
Days left
</span>
</div>
<div className="d-flex justify-content-between align-items-center mb-2">
<small className="text-body">Task: 290/344</small>
<small className="text-body">95% Completed</small>
</div>
<div className="progress mb-4 rounded" style={{ height: "8px" }}>
<div
className="progress-bar rounded"
role="progressbar"
style={{ width: "95%" }}
aria-valuenow="95"
aria-valuemin="0"
aria-valuemax="100"
></div>
</div>
<div className="d-flex align-items-center">
<div className="d-flex align-items-center">
</div>
<div className="ms-auto">
<a
className="text-muted d-flex align-items-center"
>
<i className="bx bx-chat me-1"></i> 15
</a>
</div>
</div>
</div>
</div>
</div>
</>
);
</>
);
};
export default ProjectCard;

View File

@ -1,58 +1,79 @@
import React, { useState, useEffect } from "react";
import "./ProjectInfra.css";
import BuildingModel from "./BuildingModel";
import FloorModel from "./FloorModel";
import BuildingModel from "./Infrastructure/BuildingModel";
import FloorModel from "./Infrastructure/FloorModel";
import showToast from "../../services/toastService";
import WorkAreaModel from "./WorkAreaModel";
import TaskModel from "./TaskModel";
import ProjectRepository from "../../repositories/ProjectRepository";
import WorkAreaModel from "./Infrastructure/WorkAreaModel";
import TaskModel from "./Infrastructure/TaskModel";
import ProjectRepository, {
TasksRepository,
} from "../../repositories/ProjectRepository";
import ProjectModal from "./ProjectModal";
import {useHasUserPermission} from "../../hooks/useHasUserPermission";
import {MANAGE_PROJECT_INFRA} from "../../utils/constants";
// import AssignRoleModel from "./AssignRoleModel";
import { useHasUserPermission } from "../../hooks/useHasUserPermission";
import { MANAGE_PROJECT_INFRA } from "../../utils/constants";
import InfraTable from "./Infrastructure/InfraTable";
import { cacheData, getCachedData } from "../../slices/apiDataManager";
import { useProjectDetails } from "../../hooks/useProjects";
const ProjectInfra = ({ data, activityMaster, onDataChange,eachSiteEngineer }) => {
const ProjectInfra = ({
data,
onDataChange,
eachSiteEngineer,
}) => {
const [expandedBuildings, setExpandedBuildings] = useState([]);
const [project, setProject] = useState(data);
const[modalConfig,setModalConfig] = useState({type:null,data:null});
const [ isModalOpen, setIsModalOpen ] = useState( false )
const ManageInfra = useHasUserPermission(MANAGE_PROJECT_INFRA)
const { projects_Details, loading } = useProjectDetails(data.id);
const [project, setProject] = useState(projects_Details);
const [modalConfig, setModalConfig] = useState({ type: null, data: null });
const [isModalOpen, setIsModalOpen] = useState(false);
const ManageInfra = useHasUserPermission(MANAGE_PROJECT_INFRA);
const [buildings, setBuildings] = useState(data.buildings);
const [isBuildingModalOpen, setIsBuildingModalOpen] = useState(false);
const [isFloorModalOpen, setIsFloorModalOpen] = useState(false);
const [isWorkAreaModelOpen, setIsWorkAreaModalOpen] = useState(false);
const [isTaskModelOpen, setIsTaskModalOpen] = useState(false);
const [isAssignRoleModal,setIsAssingRoleModal] = useState(false)
const [isAssignRoleModal, setIsAssingRoleModal] = useState(false);
const [isCreateModalOpen, setIsCreateModalOpen] = useState(false);
const [clearFormTrigger, setClearFormTrigger] = useState(false);
const [CurrentBuilding,setCurrentBuilding] = useState("")
const [CurrentBuilding, setCurrentBuilding] = useState("");
const [showModal, setShowModal] = useState(false);
useEffect(() => {
setProject(data);
setBuildings(data.buildings);
}, [data]);
setProject(projects_Details);
}, [data, projects_Details]);
const openFloorModel = (projectData) => {
setIsFloorModalOpen(true);
};
const closeFloorModel = () => {
setIsFloorModalOpen(false);
};
const openAssignModel=(assignData)=>{
setCurrentBuilding(assignData)
setIsAssingRoleModal(true)
}
const openAssignModel = (assignData) => {
setCurrentBuilding(assignData);
setIsAssingRoleModal(true);
};
const openBuildingModel = (projectData) => {
setIsBuildingModalOpen(true);
};
const closeBuildingModel = () => {
setIsBuildingModalOpen(false);
};
const handleBuildingModelFormSubmit = (buildingmodel) => {
if (buildingmodel.id == "" || buildingmodel.id == 0)
delete buildingmodel.id;
let data = [
{
building: buildingmodel,
floor: null,
workArea: null,
},
];
submitData(data);
};
const handleFloorModelFormSubmit = (updatedFloor) => {
if (updatedFloor.id == "") delete updatedFloor.id;
submitData([
{
building: null,
@ -62,53 +83,15 @@ const ProjectInfra = ({ data, activityMaster, onDataChange,eachSiteEngineer }) =
]);
};
const submitData = (infraObject) => {
ProjectRepository.manageProjectInfra(infraObject)
.then((response) => {
fetchData();
onDataChange("building-change");
showToast("Details updated successfully.", "success");
setClearFormTrigger(true); // Set trigger to true
})
.catch((error) => {
showToast(error.message, "error");
});
};
const openBuildingModel = (projectData) => {
setIsBuildingModalOpen(true);
};
const closeBuildingModel = () => {
setIsBuildingModalOpen(false);
};
const handleBuildingModelFormSubmit = (buildingmodel) => {
if (buildingmodel.id == "" || buildingmodel.id == 0)
delete buildingmodel.id;
let data = [
{
building: buildingmodel,
floor: null,
workArea: null,
},
];
submitData(data);
};
const openWorkAreaModel = (projectData) => {
setIsWorkAreaModalOpen(true);
};
const closeWorkAreaModel = () => {
setIsWorkAreaModalOpen(false);
// const modalBackdrop = document.querySelector(".modal-backdrop");
// if (modalBackdrop) modalBackdrop.remove();
};
const handleWorkAreaModelFormSubmit = (updatedModel) => {
if (updatedModel.id == "") delete updatedModel.id;
submitData([
{
building: null,
@ -124,428 +107,349 @@ const ProjectInfra = ({ data, activityMaster, onDataChange,eachSiteEngineer }) =
const closeTaskModel = () => {
setIsTaskModalOpen(false);
// const modalBackdrop = document.querySelector(".modal-backdrop");
// if (modalBackdrop) modalBackdrop.remove();
};
const handleTaskModelFormSubmit = (updatedModel) => {
// debugger
if (updatedModel.id == "") updatedModel.id = 0;
const updatedProject = { ...project };
//console.log("Form submitted:", updatedModel); // Replace this with an API call or state update
ProjectRepository.manageProjectTasks([updatedModel])
.then((response) => {
onDataChange("task-change");
showToast("Details updated successfully.", "success");
setClearFormTrigger(true); // Set trigger to true
// setClearFormTrigger( true );
if (response?.data[0]) {
const { workItemId, workItem } = response.data[0];
const updatedBuildings = updatedProject.buildings.map((building) =>
building.id == updatedModel.buildingID
? {
...building,
floors: building.floors.map((floor) =>
floor.id == updatedModel.floorId
? {
...floor,
workAreas: floor.workAreas.map((workArea) =>
workArea.id === workItem?.workAreaId
? {
...workArea,
workItems: workArea.workItems.some(
(existingItem) =>
existingItem.workItemId ===
workItem.workItemId
)
? workArea.workItems // If the workItemId already exists, keep the current workItems
: [...workArea.workItems, workItem],
}
: workArea
),
}
: floor
),
}
: building
);
updatedProject.buildings = updatedBuildings;
setProject(updatedProject);
cacheData("projectInfo", {
projectId: updatedProject.id,
data: updatedProject,
});
console.log(project);
}
})
.catch((error) => {
showToast(error.message, "error");
});
};
const submitData = async (infraObject) => {
try
{
debugger
let response = await ProjectRepository.manageProjectInfra(infraObject);
const entity = response.data;
const updatedProject = { ...project };
// Handle the building data
if (entity.building) {
const { id, name, description } = entity.building;
const updatedBuildings = updatedProject?.buildings?.map((building) =>
building.id === id ? { ...building, name, description } : building
);
// Add building if it doesn't exist
if (!updatedProject.buildings.some((building) => building.id === id)) {
updatedBuildings.push({
id: id,
name,
description,
floors: [],
});
}
updatedProject.buildings = updatedBuildings;
// Update the cache for buildings
cacheData("projectInfo", {
projectId: updatedProject.id,
data: updatedProject,
});
setProject((prevProject) => ({
...prevProject,
buildings: updatedBuildings,
} ) );
closeBuildingModel()
}
// Handle the floor data
else if (entity.floor) {
const { buildingId, id, floorName } = entity.floor;
const updatedBuildings = updatedProject?.buildings?.map((building) =>
building.id == buildingId
? {
...building,
floors: building.floors
.map((floor) =>
floor.id === id
? {
...floor,
floorName, // Update the floor name only
// Keep other properties as they are (including workArea)
}
: floor
)
// Add the new floor if it doesn't already exist
.concat(
!building.floors.some((floor) => floor.id === id)
? [{ id: id, floorName, workAreas: [] }] // New floor added with workArea set to null
: []
),
}
: building
);
updatedProject.buildings = updatedBuildings;
// Cache the updated project
cacheData("projectInfo", {
projectId: updatedProject.id,
data: updatedProject,
});
setProject( updatedProject );
closeFloorModel()
}
// Handle the work area data
else if ( entity.workArea )
{
let buildingId = infraObject[0].workArea.buildingId;
const { floorId, areaName, id } = entity.workArea;
// Check if the workArea exists, otherwise create a new one
const updatedBuildings = updatedProject.buildings.map((building) =>
building.id == buildingId
? {
...building,
floors: building.floors.map((floor) =>
floor.id == floorId
? {
...floor,
workAreas: floor.workAreas.some(
(workArea) => workArea.id === id
)
? floor.workAreas.map((workArea) =>
workArea.id === id
? { ...workArea, areaName }
: workArea
)
: [
...floor.workAreas,
{ id, areaName, workItems: [] },
],
}
: floor
),
}
: building
);
updatedProject.buildings = updatedBuildings;
// Update the cache for work areas
cacheData("projectInfo", {
projectId: updatedProject.id,
data: updatedProject,
});
setProject( updatedProject );
closeWorkAreaModel()
}
// Handle the task (workItem) data
else {
console.error("Unsupported data type for submitData", entity);
}
} catch (Err) {
console.log(Err);
showToast("Somthing wrong", "error");
}
handleClose();
};
const toggleBuilding = (id) => {
setExpandedBuildings((prev) =>
prev.includes(id) ? prev.filter((bid) => bid !== id) : [...prev, id]
);
};
const getContent = (building) => {
let hasFloors =
building.floors && building.floors.length > 0 ? true : false;
return (
<>
{(() => {
if (hasFloors) {
return building.floors.map((floor) => (
<React.Fragment key={floor.id}>
{floor.workAreas.length > 0 ? (
floor.workAreas.map((workArea) => (
<React.Fragment key={workArea.id}>
<tr>
<td colSpan="4" className="text-start table-cell">
<div className="row ps-2">
<div className="col-6">
{" "}
<h6>
<span>
{" "}
{floor.floorName} - {workArea.areaName} &nbsp;{" "}
</span>
</h6>
</div>
<div className="col-6 text-end">
{/* <a
type="button"
className="text-end me-2"
data-bs-toggle="modal"
data-bs-target="#floor-model"
onClick={() => openFloorModel()}
>
<i className="bx bx-edit-alt me-2"></i>
Edit Floor
</a> */}
{/* <a
type="button"
className="text-end"
data-bs-toggle="modal"
data-bs-target="#editproject"
onClick={() => openModal()}
>
<i className="bx bx-plus-circle me-2"></i>
Add Activities
</a> */}
</div>
</div>
</td>
</tr>
{workArea.workItems.length > 0 ? (
<tr className="overflow-auto" >
{" "}
<td colSpan={4} >
<table
className="table mx-1"
>
<thead>
<tr>
<th>Activity</th>
<th>Planned</th>
<th>Complated</th>
<th>Progress</th>
<th>Actions</th>
</tr>
</thead>
<tbody className="table-border-bottom-0">
{workArea.workItems.map((workItem) => (
<React.Fragment key={workItem.workItemId}>
<tr>
<td className="text-start table-cell-small">
<i className="bx bx-right-arrow-alt"></i>
<span className="fw-medium">
{workItem.workItem.activityMaster
? workItem.workItem.activityMaster
.activityName
: "NA"}
</span>
</td>
<td className="text-center">
{workItem.workItem
? workItem.workItem.plannedWork
: "NA"}
</td>
<td className="text-center">
{workItem.workItem
? workItem.workItem.completedWork
: "NA"}
</td>
<td
className="text-center"
style={{ width: "15%" }}
>
<div className="progress p-0">
<div
className="progress-bar"
role="progressbar"
style={{
width: getProgress(
workItem.workItem.plannedWork,
workItem.workItem.completedWork
),
height: "10px",
}}
aria-valuenow={
workItem.workItem
? workItem.workItem
.completedWork
: 0
}
aria-valuemin="0"
aria-valuemax={
workItem.workItem
? workItem.workItem.plannedWork
: 0
}
></div>
</div>
</td>
<td>
<div className="dropdown">
<button
aria-label="Modify"
type="button"
className="btn p-0 "
data-bs-toggle="modal"
data-bs-target="#project-modal"
onClick={()=>{
// setModalConfig({type:"assignRole",data:{building,floor,workArea,workItem}})
handleModalData("assignRole",{building,floor,workArea,workItem})
// openAssignModel({building,floor,workArea,workItem})
}}
>
<span className="badge bg-label-primary me-1">Assign</span>
</button>
<button
aria-label="Modify"
type="button"
className="btn p-0 dropdown-toggle hide-arrow"
>
<i class="bx bxs-edit me-2 text-primary"></i>
</button>
<button
aria-label="Delete"
type="button"
className="btn p-0 dropdown-toggle hide-arrow"
>
<i className="bx bx-trash me-1 text-danger"></i>{" "}
</button>
</div>
</td>
</tr>{" "}
</React.Fragment>
))}
</tbody>
</table>{" "}
</td>
</tr>
) : (
<span></span>
)}
</React.Fragment>
))
) : (
<React.Fragment key={building.id + floor.id}>
<tr>
<td colSpan="4" className="text-start table-cell">
<div className="row ps-2">
<div className="col-6">
{" "}
<h6>
<span> {floor.floorName} &nbsp; </span>
</h6>
</div>
<div className="col-6 text-end">
{/* <a
type="button"
className="text-end me-2"
data-bs-toggle="modal"
data-bs-target="#floor-model"
onClick={() => openFloorModel()}
>
<i className="bx bx-edit-alt me-2"></i>
Edit Floor
</a> */}
{/* <a
type="button"
className="text-end"
data-bs-toggle="modal"
data-bs-target="#editproject"
onClick={() => openModal()}
>
<i className="bx bx-plus-circle me-2"></i>
Add Work Area
</a> */}
</div>
</div>
</td>
</tr>
</React.Fragment>
)}
</React.Fragment>
));
} else {
return (
<>
<tr>
<td>
<p>No Floors Added, Please add them</p>
{/* <p>
<button
type="button"
data-bs-toggle="modal"
className="btn btn-sm btn-danger m-1"
data-bs-target="#editproject"
onClick={() => openModal()}
>
<i className="bx bx-plus-circle me-2"></i>
Add Floors
</button>
</p> */}
</td>
</tr>
</>
);
}
})()}
</>
);
const handleModalData = (type, modaldata) => {
setModalConfig({ type: type, data: modaldata });
};
const getProgress = (planned, completed) => {
return (completed * 100) / planned + "%";
};
// common modal
const handleModalData = (type,modaldata)=>{
setModalConfig({type:type,data:modaldata})
}
const openModal = () => {
setIsModalOpen(true);
const modalElement = document.getElementById("building-model");
const modal = new Modal(modalElement, {
backdrop: false,
keyboard: true,
focus: true,
});
modal.show();
};
const closeModal = () => {
setIsModalOpen(false);
setModalConfig(null)
const modalElement = document.getElementById('project-modal');
if (modalElement) {
modalElement.classList.remove('show');
modalElement.style.display = 'none'; // Hide modal visually
document.body.classList.remove('modal-open'); // Unlock body scroll
const backdropElement = document.querySelector('.modal-backdrop');
if (backdropElement) {
backdropElement.classList.remove('modal-backdrop'); // Remove backdrop class
backdropElement.style.display = 'none'; // Hide the backdrop element
}
}
const modalBackdropElement = document.querySelector('.modal-backdrop');
if (modalBackdropElement) {
modalBackdropElement.remove();
}
document.body.style.overflow = 'auto';
};
useEffect(() => {
if (modalConfig !== null) {
openModal();
}
}, [modalConfig,isModalOpen]);
setIsModalOpen(false);
setModalConfig(null);
const modalElement = document.getElementById("building-model");
if (modalElement) {
modalElement.classList.remove("show"); // Remove modal visibility class
modalElement.style.display = "none"; // Hide the modal element
}
document.body.classList.remove("modal-open"); // Remove modal-open class from body
// Remove the modal backdrop
const backdropElement = document.querySelector(".modal-backdrop");
if (backdropElement) {
backdropElement.classList.remove("modal-backdrop"); // Remove backdrop class
backdropElement.style.display = "none"; // Hide the backdrop element
}
document.body.style.overflow = "auto";
};
const handleShow = () => setShowModal(true);
const handleClose = () => setShowModal(false);
return (
<>
{isBuildingModalOpen && (
<div
className={`modal fade `}
id="building-model"
tabIndex="-1"
aria-hidden="true"
>
<BuildingModel
project={data}
onClose={closeBuildingModel}
onSubmit={handleBuildingModelFormSubmit}
clearTrigger={clearFormTrigger}
onClearComplete={() => setClearFormTrigger(false)}
></BuildingModel>
</div>
)}
<div
className={`modal fade ${showModal ? "show" : ""}`}
tabIndex="-1"
role="dialog"
style={{ display: showModal ? "block" : "none" }}
aria-hidden={!showModal}
>
<BuildingModel
project={project}
onClose={handleClose}
onSubmit={handleBuildingModelFormSubmit}
clearTrigger={clearFormTrigger}
onClearComplete={() => setClearFormTrigger(false)}
></BuildingModel>
</div>
{isFloorModalOpen && (
<div
className={`modal fade `}
className="modal fade show"
id="floor-model"
tabIndex="-1"
aria-hidden="true"
role="dialog"
style={{ display: "block" }}
aria-hidden="false"
>
<FloorModel
project={data}
// building={null}
project={project}
onClose={closeFloorModel}
onSubmit={handleFloorModelFormSubmit}
clearTrigger={clearFormTrigger}
onClearComplete={() => setClearFormTrigger(false)}
></FloorModel>
/>
</div>
)}
{isWorkAreaModelOpen && (
<div
className={`modal fade `}
className="modal fade show"
id="work-area-model"
tabIndex="-1"
aria-hidden="true"
role="dialog"
style={{ display: "block" }}
aria-hidden="false"
>
<WorkAreaModel
project={data}
// building={null}
project={project}
onClose={closeWorkAreaModel}
onSubmit={handleWorkAreaModelFormSubmit}
clearTrigger={clearFormTrigger}
onClearComplete={() => setClearFormTrigger(false)}
></WorkAreaModel>
/>
</div>
)}
{isTaskModelOpen && (
<div
className={`modal fade `}
className="modal fade show"
id="task-model"
tabIndex="-1"
aria-hidden="true"
role="dialog"
style={{ display: "block" }}
aria-hidden="false"
>
<TaskModel
project={data}
activities={activityMaster}
project={project}
onClose={closeTaskModel}
onSubmit={handleTaskModelFormSubmit}
clearTrigger={clearFormTrigger}
onClearComplete={() => setClearFormTrigger(false)}
></TaskModel>
/>
</div>
)}
{/* common Modal */}
{isModalOpen && (
<ProjectModal modalConfig={modalConfig} closeModal={closeModal} />
<ProjectModal modalConfig={modalConfig} closeModal={closeModal} />
)}
<div className="col-md-12 col-lg-12 col-xl-12 order-0 mb-4">
<div className="card">
{/* <div className="card-header pb-4"></div> */}
<div className="card-body" style={{ padding: "0.5rem" }}>
<div className="align-items-center">
<div className="row ">
<div className={`col-12 text-end mb-1 ${!ManageInfra && 'd-none'} `} >
<div
className={`col-12 text-end mb-1 ${
!ManageInfra && "d-none"
} `}
>
<button
type="button"
className="link-button link-button-sm m-1 "
data-bs-toggle="modal"
data-bs-target="#building-model"
onClick={() => openBuildingModel()}
onClick={handleShow}
>
<i className="bx bx-plus-circle me-2"></i>
Manage Building
</button>
<button
type="button"
data-bs-toggle="modal"
className="link-button m-1"
data-bs-target="#floor-model"
onClick={() => openFloorModel()}
>
<i className="bx bx-plus-circle me-2"></i>
Manage Floors
</button>
<button
type="button"
data-bs-toggle="modal"
className="link-button m-1"
data-bs-target="#work-area-model"
onClick={() => openWorkAreaModel()}
>
<i className="bx bx-plus-circle me-2"></i>
@ -553,59 +457,23 @@ const ProjectInfra = ({ data, activityMaster, onDataChange,eachSiteEngineer }) =
</button>
<button
type="button"
data-bs-toggle="modal"
className="link-button m-1"
data-bs-target="#task-model"
onClick={() => openTaskModel()}
>
<i className="bx bx-plus-circle me-2"></i>
Manage Tasks
Create Tasks
</button>
</div>
</div>
<div className="row ">
<div className="col-12 overflow-auto">
{buildings && buildings.length > 0 && (
<table className="table table-bordered ">
<tbody>
{buildings.map((building) => (
building.floors && building.floors.length > 0 && (
<React.Fragment key={building.id}>
<tr className="overflow-auto">
<td
colSpan="4"
className="text-start "
style={{
background: "#f0f0f0",
cursor: "pointer",
}}
onClick={() => toggleBuilding(building.id)}
>
<div className="row table-responsive">
<h5 style={{ marginBottom: "0px" }}>
{ building.name} &nbsp;
{expandedBuildings.includes(building.id) ? (
<i className="bx bx-chevron-down"></i>
) : (
<i className="bx bx-chevron-right"></i>
)}
</h5>
</div>
</td>
</tr>
{expandedBuildings.includes(building.id) && getContent(building)}
</React.Fragment>
)
))}
</tbody>
</table>
)}
</div>
{loading && <p>Loading....</p>}
{project && project.buildings?.length > 0 && (
<InfraTable
buildings={project?.buildings}
project={project}
handleFloor={submitData}
/>
)}
</div>
</div>
</div>

View File

@ -25,7 +25,8 @@ const ProjectModal = ({modalConfig,closeModal}) => {
{/* Modal Component */}
{modalConfig?.type === "assignRole" && <AssignRole assignData={modalConfig?.data} onClose={closeModal} />}
{modalConfig?.type === "assignRole" && <AssignRole assignData={modalConfig?.data} onClose={closeModal} />}
</div>
</div>
</div>

View File

@ -35,7 +35,7 @@ const ProjectNav = ( {onPillClick, activePill} ) =>
<i className="bx bx-group bx-sm me-1_5"></i> Teams
</a>
</li>
<li className={`nav-item ${HasViewInfraStructure ? "":"d-none"} `}>
<li className={`nav-item ${!HasViewInfraStructure && 'd-none'} `}>
<a
className={`nav-link ${activePill === "infra" ? "active" : ""}`}
href="#"
@ -47,7 +47,7 @@ const ProjectNav = ( {onPillClick, activePill} ) =>
<i className="bx bx-grid-alt bx-sm me-1_5"></i> Infrastructure
</a>
</li>
<li className="nav-item">
{/* <li className="nav-item">
<a
className={`nav-link ${
activePill === "workplan" ? "active" : ""
@ -60,7 +60,7 @@ const ProjectNav = ( {onPillClick, activePill} ) =>
>
<i className="bx bx-link bx-sm me-1_5"></i> Work Plan
</a>
</li>
</li> */}
<li className="nav-item">
<a
className={`nav-link ${
@ -78,15 +78,15 @@ const ProjectNav = ( {onPillClick, activePill} ) =>
<li className="nav-item">
<a
className={`nav-link ${
activePill === "activities" ? "active" : ""
activePill === "directory" ? "active" : ""
}`}
href="#"
onClick={(e) => {
e.preventDefault(); // Prevent page reload
onPillClick("activities");
onPillClick("directory");
}}
>
<i className="bx bx-link bx-sm me-1_5"></i> Activities
<i className="bx bx-link bx-sm me-1_5"></i> Directory
</a>
</li>
</ul>

View File

@ -1,11 +1,10 @@
import React from "react";
import {useEmployeesByProjectAllocated} from "../../hooks/useProjects";
import {useEmployeesByProjectAllocated, useProjects} from "../../hooks/useProjects";
const ProjectOverview = ({project}) =>
{
const {projectEmployees} = useEmployeesByProjectAllocated( project.id );
let teamSize = projectEmployees.filter( ( emp ) => emp.isActive )
const {projects} = useProjects()
const teamSize = projects.find((pro)=>pro.id == project)
return (
<div className="card mb-6">
<div className="card-body">
@ -26,7 +25,7 @@ const ProjectOverview = ({project}) =>
<li className="d-flex align-items-center">
<i className="bx bx-user"></i>
<span className="fw-medium mx-2">Current team Size:</span>{" "}
<span>{ teamSize?.length}</span>
<span>{teamSize?.teamSize}</span>
</li>
</ul>
</div>

View File

@ -1,230 +0,0 @@
import React, { useState, useEffect } from "react";
const defaultModel = {
id: "0",
areaName: "",
floorId: "0",
};
const WorkAreaModel = ({
project,
onSubmit,
clearTrigger,
onClearComplete,
}) => {
const [formData, setFormData] = useState(defaultModel);
const [selectedBuilding, setSelectedBuilding] = useState(null);
const [selectedFloor, setSelectedFloor] = useState(null);
//if (floor && floor.id) setFormData(floor);
useEffect(() => {
if (selectedBuilding) {
let building = project.buildings.find(
(b) => b.id === selectedBuilding.id
);
setSelectedBuilding(building);
}
if (selectedFloor) {
let floor = selectedBuilding.floors.find(
(b) => b.id === Number(selectedFloor.id)
);
setSelectedFloor(floor);
}
}, [project]);
useEffect(() => {
if (clearTrigger) {
let model = defaultModel;
model.floorId = selectedFloor.id;
setFormData(defaultModel); // Clear form
onClearComplete(); // Notify parent that clearing is done
}
}, [clearTrigger, onClearComplete]);
// Handle input change
const handleChange = (e) => {
const { name, value } = e.target;
setFormData({ ...formData, [name]: value });
};
const handleWorkAreaChange = (e) => {
const { name, value } = e.target;
const workArea = selectedFloor.workAreas.find(
(b) => b.id === Number(value)
);
if (workArea) {
setFormData({
id: workArea.id,
floorId: workArea.floorId,
areaName: workArea.areaName,
});
} else
setFormData({
id: "0",
floorId: selectedFloor.id,
areaName: "",
});
};
const handleFloorChange = (e) => {
const { name, value } = e.target;
const floor = selectedBuilding.floors.find((b) => b.id === Number(value));
setSelectedFloor(floor);
if (floor) {
setFormData({
id: "0",
floorId: floor.id,
areaName: "",
});
} else {
setSelectedFloor(null);
setFormData({
id: "0",
floorId: "0",
areaName: "",
});
}
};
const handleBuildigChange = (e) => {
const { name, value } = e.target;
const building = project.buildings.find((b) => b.id === Number(value));
setSelectedBuilding(building);
};
// Handle form submission
const handleSubmit = (e) => {
e.preventDefault();
formData.projectId = project.id;
onSubmit(formData); // Pass the updated data to the parent
};
return (
<div className="modal-dialog modal-lg modal-simple modal-edit-user">
<div className="modal-content">
<div className="modal-body">
<div className="row">
<button
type="button"
className="btn-close"
data-bs-dismiss="modal"
aria-label="Close"
></button>
<div className="text-center mb-1">
<h5 className="mb-1">Manage Work Area</h5>
</div>
<form className="row g-2" onSubmit={handleSubmit}>
<div className="col-6 col-md-6">
<label className="form-label" htmlFor="name">
Select Building
</label>
<select
id="buildingId"
name="buildingId"
className="select2 form-select form-select-sm"
aria-label="Default select example"
onChange={handleBuildigChange}
value={formData.buildingId}
>
<option value="0">Select Building</option>
{project.buildings.map((building) => (
<option key={building.id} value={building.id}>
{building.name}
</option>
))}
</select>
</div>
{selectedBuilding && selectedBuilding.buildingId != "0" && (
<div className="col-6 col-md-6">
<label className="form-label" htmlFor="floorId">
Select Floor
</label>
<select
id="floorId"
name="floorId"
className="select2 form-select form-select-sm"
aria-label="Default select example"
onChange={handleFloorChange}
value={formData.floorId}
>
<option value="0">Select Floor</option>
{selectedBuilding.floors.map((floor) => (
<option key={floor.id} value={floor.id}>
{floor.floorName}
</option>
))}
</select>
</div>
)}
{formData.floorId != "0" && (
<div className="col-12 col-md-12">
<label className="form-label" htmlFor="floorId">
Select Work Area
</label>
<select
id="floorId"
name="floorId"
className="select2 form-select form-select-sm"
aria-label="Default select example"
onChange={handleWorkAreaChange}
value={formData.floorId}
>
<option value="0">Create New Work Area</option>
{selectedFloor.workAreas.map((workArea) => (
<option key={workArea.id} value={workArea.id}>
{workArea.areaName}
</option>
))}
</select>
</div>
)}
{formData.floorId != "0" && (
<div className="col-12 col-md-12">
{" "}
<label className="form-label" htmlFor="areaName">
{formData.id != "0" ? "Modify " : "Enter "} Work Area Name
</label>
<div className="input-group">
<input
type="text"
id="areaName"
name="areaName"
className="form-control form-control-sm me-2"
placeholder="Work Area"
onChange={handleChange}
value={formData.areaName}
/>
</div>
</div>
)}
<div className="col-12 text-center">
{formData.floorId != "0" && (
<button type="submit" className="btn btn-primary me-3">
{formData.id != "0" && formData.id != ""
? "Edit Work Area"
: "Add Work Area"}
</button>
)}
<button
type="reset"
className="btn btn-label-secondary"
data-bs-dismiss="modal"
aria-label="Close"
>
Cancel
</button>
</div>
</form>
</div>
</div>
</div>
</div>
);
};
export default WorkAreaModel;

View File

@ -1,7 +1,8 @@
import React from "react";
import { ComingSoonPage } from "../../pages/Misc/ComingSoonPage";
const WorkPlan = () => {
return <div>Work plan calender will go here</div>;
return <ComingSoonPage></ComingSoonPage>;
};
export default WorkPlan;

View File

@ -0,0 +1,42 @@
import React, { useEffect, useRef } from "react";
const DatePicker = ({ onDateChange }) => {
const inputRef = useRef(null);
useEffect(() => {
const fp = flatpickr(inputRef.current, {
dateFormat: "Y-m-d",
defaultDate: new Date(),
onChange: (selectedDates, dateStr) => {
console.log("Selected date:", dateStr);
if (onDateChange) {
onDateChange(dateStr); // Pass selected date to parent
}
}
});
return () => {
// Cleanup flatpickr instance
fp.destroy();
};
}, [onDateChange]);
return (
<div className="container mt-3">
<div className="mb-3">
<label htmlFor="flatpickr-single" className="form-label">
Select Date
</label>
<input
type="text"
id="flatpickr-single"
className="form-control"
placeholder="YYYY-MM-DD"
ref={inputRef}
/>
</div>
</div>
);
};
export default DatePicker;

View File

@ -0,0 +1,45 @@
import React, { useEffect, useRef } from "react";
const DateRangePicker = ({ onRangeChange }) => {
const inputRef = useRef(null);
useEffect(() => {
const today = new Date();
const fifteenDaysAgo = new Date();
fifteenDaysAgo.setDate(today.getDate() - 15);
const fp = flatpickr(inputRef.current, {
mode: "range",
dateFormat: "Y-m-d",
defaultDate: [fifteenDaysAgo, today],
static: true,
clickOpens: true,
onChange: (selectedDates, dateStr) => {
const [startDate, endDate] = dateStr.split(" to ");
onRangeChange?.({ startDate, endDate });
},
});
onRangeChange?.({
startDate: fifteenDaysAgo.toISOString().split("T")[0],
endDate: today.toISOString().split("T")[0],
});
return () => {
fp.destroy();
};
}, [onRangeChange]);
return (
<input
type="text"
className="form-control form-control-sm ms-1"
placeholder="From to End"
id="flatpickr-range"
ref={inputRef}
/>
);
};
export default DateRangePicker;

View File

@ -0,0 +1,77 @@
import React, { useEffect, useRef } from 'react';
const GlobalModel = ({
isOpen,
closeModal,
children,
modalType = '', // For custom positioning like modal-top, modal-bottom
dialogClass = '', // For additional custom classes on modal dialog
role = 'dialog', // Accessibility role for the modal
size = '', // Dynamically set the size (sm, lg, xl)
dataAttributes = {} // Additional dynamic data-bs-* attributes
}) => {
const modalRef = useRef(null); // Reference to the modal element
useEffect(() => {
const modalElement = modalRef.current;
const modalInstance = new window.bootstrap.Modal(modalElement);
// Show modal if isOpen is true
if (isOpen) {
modalInstance.show();
} else {
modalInstance.hide();
}
// Handle modal hide event to invoke the closeModal function
const handleHideModal = () => {
closeModal(); // Close the modal via React state
};
modalElement.addEventListener('hidden.bs.modal', handleHideModal);
return () => {
modalElement.removeEventListener('hidden.bs.modal', handleHideModal);
};
}, [isOpen, closeModal]);
// Dynamically set the modal size classes (modal-sm, modal-lg, modal-xl)
const modalSizeClass = size ? `modal-${size}` : ''; // Default is empty if no size is specified
// Dynamically generate data-bs attributes
const dataAttributesProps = Object.keys(dataAttributes).map(key => ({
[key]: dataAttributes[key],
}));
return (
<div
className={`modal fade ${modalType}`}
id="customModal"
tabIndex="-1"
aria-labelledby="exampleModalLabel"
aria-hidden="true"
ref={modalRef} // Assign the ref to the modal element
{...dataAttributesProps}
>
<div className={`modal-dialog ${dialogClass} ${modalSizeClass}`} role={role}>
<div className="modal-content">
<div className="modal-header">
{/* Close button inside the modal header */}
<button
type="button"
className="btn-close"
data-bs-dismiss="modal"
aria-label="Close"
onClick={closeModal} // Trigger the React closeModal function
></button>
</div>
<div className="modal-body">
{children} {/* Render children here, which can be the ReportTask component */}
</div>
</div>
</div>
</div>
);
};
export default GlobalModel;

View File

@ -2,33 +2,33 @@ import React from "react";
const Loader = () => {
return (
<div class="demo-inline-spacing">
<div class="spinner-grow" role="status">
<span class="visually-hidden">Loading...</span>
<div className="demo-inline-spacing">
<div className="spinner-grow" role="status">
<span className="visually-hidden">Loading...</span>
</div>
<div class="spinner-grow text-primary" role="status">
<span class="visually-hidden">Loading...</span>
<div className="spinner-grow text-primary" role="status">
<span className="visually-hidden">Loading...</span>
</div>
<div class="spinner-grow text-secondary" role="status">
<span class="visually-hidden">Loading...</span>
<div className="spinner-grow text-secondary" role="status">
<span className="visually-hidden">Loading...</span>
</div>
<div class="spinner-grow text-success" role="status">
<span class="visually-hidden">Loading...</span>
<div className="spinner-grow text-success" role="status">
<span className="visually-hidden">Loading...</span>
</div>
<div class="spinner-grow text-danger" role="status">
<span class="visually-hidden">Loading...</span>
<div className="spinner-grow text-danger" role="status">
<span className="visually-hidden">Loading...</span>
</div>
<div class="spinner-grow text-warning" role="status">
<span class="visually-hidden">Loading...</span>
<div className="spinner-grow text-warning" role="status">
<span className="visually-hidden">Loading...</span>
</div>
<div class="spinner-grow text-info" role="status">
<span class="visually-hidden">Loading...</span>
<div className="spinner-grow text-info" role="status">
<span className="visually-hidden">Loading...</span>
</div>
<div class="spinner-grow text-light" role="status">
<span class="visually-hidden">Loading...</span>
<div className="spinner-grow text-light" role="status">
<span className="visually-hidden">Loading...</span>
</div>
<div class="spinner-grow text-dark" role="status">
<span class="visually-hidden">Loading...</span>
<div className="spinner-grow text-dark" role="status">
<span className="visually-hidden">Loading...</span>
</div>
</div>
);

View File

@ -0,0 +1,30 @@
import React from "react";
const ProgressBar = ( {completeValue, totalValue} ) =>
{
const getProgress = (complete, total) => {
return (total * 100) / complete + "%";
};
return (
<div className="progress mb-4 rounded" style={{height: "8px"}}>
<div className="progress p-0">
<div
className="progress-bar"
role="progressbar"
style={{
width: `${getProgress( totalValue,completeValue)}`,
height: "10px",
}}
aria-valuenow={completeValue}
aria-valuemin="0"
aria-valuemax={totalValue}
></div>
</div>
</div>
);
};
export default ProgressBar;

View File

@ -0,0 +1,234 @@
import React, { useState, useEffect } from "react";
import { useFieldArray, useForm } from "react-hook-form";
import { z } from "zod";
import { zodResolver } from "@hookform/resolvers/zod";
import { MasterRespository } from "../../repositories/MastersRepository";
import { clearApiCacheKey } from "../../slices/apiCacheSlice";
import { getCachedData, cacheData } from "../../slices/apiDataManager";
import showToast from "../../services/toastService";
const schema = z.object({
activityName: z.string().min(1, { message: "Activity Name is required" }),
unitOfMeasurement: z.string().min(1, { message: "Unit of Measurement is required" }),
checkList: z
.array(
z.object({
check: z.string().min(1, { message: "Checklist item cannot be empty" }),
isMandatory: z.boolean().default(false),
id: z.any().default(0),
})
)
.optional(),
});
const CreateActivity = ({ onClose }) => {
const [isLoading, setIsLoading] = useState(false);
const {
register,
handleSubmit,
control,
setValue,
clearErrors,
setError,
getValues,
reset,
formState: { errors },
} = useForm({
resolver: zodResolver(schema),
defaultValues: {
activityName: "",
unitOfMeasurement: "",
checkList: [],
},
});
const {
fields: checkListItems,
append,
remove,
} = useFieldArray({
control,
name: "checkList",
});
// Form submission handler
const onSubmit = (data) => {
console.log(data);
setIsLoading(true);
MasterRespository.createActivity(data)
.then( ( resp ) =>
{
const cachedData = getCachedData("Activity");
const updatedData = [ ...cachedData, resp?.data ];
cacheData("Activity", updatedData);
showToast("Activity Successfully Added.", "success");
setIsLoading(false);
handleClose()
})
.catch((error) => {
showToast(error.message, "error");
setIsLoading(false);
});
};
const addChecklistItem = () => {
const values = getValues("checkList");
const lastIndex = checkListItems.length - 1;
if (
checkListItems.length > 0 &&
(!values?.[lastIndex] || values[lastIndex].check.trim() === "")
) {
setError(`checkList.${lastIndex}.check`, {
type: "manual",
message: "Please fill this checklist item before adding another.",
});
return;
}
clearErrors(`checkList.${lastIndex}.check`);
append({
id: 0,
check: "",
isMandatory: false,
});
};
const removeChecklistItem = (index) => {
remove(index);
};
const handleChecklistChange = (index, value) => {
setValue(`checkList.${index}`, value);
};
const handleClose = () => {
reset();
onClose();
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<h6>Create Activity</h6>
<div className="row">
<div className="col-6">
<label className="form-label">Activity</label>
<input
type="text"
{...register("activityName")}
className={`form-control form-control-sm ${
errors.activityName ? "is-invalid" : ""
}`}
/>
{errors.activityName && (
<p className="danger-text">{errors.activityName.message}</p>
)}
</div>
<div className="col-6">
<label className="form-label">Measurement</label>
<input
type="text"
{...register("unitOfMeasurement")}
className={`form-control form-control-sm ${
errors.unitOfMeasurement ? "is-invalid" : ""
}`}
/>
{errors.unitOfMeasurement && (
<p className="danger-text">{errors.unitOfMeasurement.message}</p>
)}
</div>
<div className="col-md-12 text-start mt-1">
<p className="py-1 my-0">{checkListItems.length > 0 ? "Check List" : "Add Check List" }</p>
{checkListItems.length > 0 && (
<table className="table mt-1 border-0">
<thead className="py-0 my-0 table-border-top-0">
<tr className="py-1">
<th colSpan={2} className="py-1">
<small>Name</small>
</th>
<th colSpan={2} className="py-1 text-center">
<small>Is Mandatory</small>
</th>
<th className="text-center py-1">Action</th>
</tr>
</thead>
<tbody className="table-border-bottom-0 ">
{checkListItems.map((item, index) => (
<tr key={index} className="border-top-0">
<td colSpan={2} className="border-top-0 border-0">
<input
className="d-none"
{...register(`checkList.${index}.id`)}
></input>
<input
{...register(`checkList.${index}.check`)}
className="form-control form-control-sm"
placeholder={`Checklist item ${index + 1}`}
onChange={(e) =>
handleChecklistChange(index, e.target.value)
}
/>
{errors.checkList?.[index]?.check && (
<small
style={{ fontSize: "10px" }}
className="danger-text"
>
{errors.checkList[index]?.check?.message}
</small>
)}
</td>
<td colSpan={2} className="text-center border-0">
<input
className="form-check-input"
type="checkbox"
{...register(`checkList.${index}.isMandatory`)}
defaultChecked={item.isMandatory}
/>
</td>
<td className="text-center border-0">
<button
type="button"
onClick={() => removeChecklistItem(index)}
className="btn btn-xs btn-icon btn-text-secondary"
>
<i class="bx bxs-minus-circle text-danger"></i>
</button>
</td>
</tr>
))}
</tbody>
</table>
)}
<button
type="button"
className="btn btn-xs btn-primary mt-2"
onClick={addChecklistItem}
>
<i class="bx bx-plus-circle"></i>
</button>
</div>
<div className="col-12 text-center mt-3">
<button type="submit" className="btn btn-sm btn-primary me-3">
{isLoading ? "Please Wait" : "Submit"}
</button>
<button
type="reset"
className="btn btn-sm btn-label-secondary"
onClick={handleClose}
>
Cancel
</button>
</div>
</div>
</form>
);
};
export default CreateActivity;

View File

@ -57,15 +57,12 @@ const onSubmit = (values) => {
};
MasterRespository.createRole(result).then((resp)=>{
console.log(resp)
setIsLoading(false)
const cachedData = getCachedData( "Role" );
console.log(cachedData)
const updatedData = [...cachedData, resp];
const updatedData = [...cachedData, resp.data];
cacheData("Role", updatedData);
showToast("Role Added successfully.", "success");
cacheData("Application Role", updatedData);
showToast("Application Role Added successfully.", "success");
onClose()
} ).catch( ( err ) =>
{

View File

@ -3,18 +3,17 @@ import axios from "axios";
const API_URL = "http://localhost:5000/mastersdata";
const DeleteMaster = ({ master,onClose}) => {
const DeleteMaster = ({ master, onClose }) => {
const [loader, setLoader] = useState(false);
const handleDelete = () => {
const index = mastersdata[master?.masterType]?.findIndex(item => String(item?.id) === String(master?.item?.id));
console.log(index)
const handleDelete = () => {
const index = mastersdata[master?.masterType]?.findIndex(
(item) => String(item?.id) === String(master?.item?.id)
);
if (index !== -1) {
mastersdata[master?.masterType].splice(index, 1);
mastersdata[master?.masterType].splice(index, 1);
}
onClose()
onClose();
};
return (
@ -27,8 +26,8 @@ const DeleteMaster = ({ master,onClose}) => {
onClick={handleDelete}
>
{loader ? (
<div class="spinner-border text-primary" role="status">
<span class="sr-only">Loading...</span>
<div className="spinner-border text-primary" role="status">
<span className="sr-only">Loading...</span>
</div>
) : (
"Delete"

View File

@ -0,0 +1,248 @@
import React, { useEffect, useState } from "react";
import { useForm, useFieldArray } from "react-hook-form";
import { z } from "zod";
import { zodResolver } from "@hookform/resolvers/zod";
import {MasterRespository} from "../../repositories/MastersRepository";
import showToast from "../../services/toastService";
import {getCachedData,cacheData} from "../../slices/apiDataManager";
const schema = z.object({
activityName: z.string().min(1, { message: "Activity name is required" }),
unitOfMeasurement: z.string().min(1, { message: "Measurement is required" }),
checkList: z
.array(
z.object({
id: z.any().default(0),
check: z.string().min(1, { message: "Checklist item cannot be empty" }),
isMandatory: z.boolean().default(false),
})
)
.optional(),
});
const UpdateActivity = ({ activityData, onClose }) => {
const [isLoading, setIsLoading] = useState(false);
const {
register,
handleSubmit,
control,
setValue,
reset,
setError,
clearErrors,
getValues,
formState: { errors },
} = useForm({
resolver: zodResolver(schema),
defaultValues: {
id:activityData.id,
activityName: activityData.activityName,
unitOfMeasurement: activityData.unitOfMeasurement,
checkLists: activityData.checkLists || [],
},
});
const { fields: checkListItems, append, remove } = useFieldArray({
control,
name: "checkList",
});
// Load initial data
useEffect(() => {
if (activityData) {
reset( {
id:activityData.id,
activityName: activityData.activityName,
unitOfMeasurement: activityData.unitOfMeasurement,
checkList: activityData.checkLists || [],
});
}
}, [activityData]);
const handleChecklistChange = (index, value) => {
setValue(`checkList.${index}`, value);
};
// Submit handler
const onSubmit = async(data) => {
setIsLoading(true);
const Activity = {...data, id:activityData.id}
try
{
const response = await MasterRespository.updateActivity( activityData?.id, Activity );
const updatedActivity = response.data;
const cachedData = getCachedData("Activity")
if (cachedData) {
const updatedActivities = cachedData.map((activity) =>
activity.id === updatedActivity.id ? { ...activity, ...updatedActivity } : activity
);
cacheData( "Activity", updatedActivities );
onClose()
}
setIsLoading( false )
showToast("Activity Successfully Updated", "success");
} catch ( err )
{
setIsLoading( false )
showToast("error.message", "error");
console.log(err)
}
};
// Add new checklist item
const addChecklistItem = () => {
const values = getValues("checkList");
const lastIndex = checkListItems.length - 1;
if (
checkListItems.length > 0 &&
(!values?.[lastIndex] || values[lastIndex].check.trim() === "")
) {
setError(`checkList.${lastIndex}.check`, {
type: "manual",
message: "Please fill this checklist item before adding another.",
});
return;
}
clearErrors(`checkList.${lastIndex}.check`);
append({ id: 0, check: "", isMandatory: false });
};
const removeChecklistItem = (index) => {
remove(index);
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<h6>Update Activity</h6>
<div className="row">
{/* Activity Name */}
<div className="col-md-6">
<label className="form-label">Activity Name</label>
<input
type="text"
{...register("activityName")}
className={`form-control form-control-sm ${errors.activityName ? "is-invalid" : ""}`}
/>
{errors.activityName && (
<div className="text-danger">{errors.activityName.message}</div>
)}
</div>
{/* Unit of Measurement */}
<div className="col-md-6">
<label className="form-label">Measurement</label>
<input
type="text"
{...register("unitOfMeasurement")}
className={`form-control form-control-sm ${errors.unitOfMeasurement ? "is-invalid" : ""}`}
/>
{errors.unitOfMeasurement && (
<div className="text-danger">{errors.unitOfMeasurement.message}</div>
)}
</div>
{/* Checklist */}
<div className="col-md-12 text-start mt-1">
<p className="py-1 my-0">{checkListItems.length > 0 ? "Check List" : "Add Check List"}</p>
{checkListItems.length > 0 && (
<table className="table mt-1 border-0">
<thead className="py-0 my-0 table-border-top-0">
<tr className="py-1">
<th colSpan={2} className="py-1">
<small>Name</small>
</th>
<th colSpan={2} className="py-1 text-center">
<small>Is Mandatory</small>
</th>
<th className="text-center py-1">Action</th>
</tr>
</thead>
<tbody>
{checkListItems.map((item, index) => (
<tr key={item.id} className="border-top-0">
<td colSpan={2} className=" border-0">
<input
className="d-none"
{...register(`checkList.${index}.id`)}
></input>
<input
{...register(`checkList.${index}.check`)}
className="form-control form-control-sm"
placeholder={`Checklist item ${index + 1}`}
onChange={(e) =>
handleChecklistChange(index, e.target.value)
}
/>
{errors.checkList?.[index]?.check && (
<small
style={{ fontSize: "10px" }}
className="danger-text"
>
{errors.checkList[index]?.check?.message}
</small>
)}
</td>
<td colSpan={2} className="text-center border-0">
<input
className="form-check-input"
type="checkbox"
{...register(`checkList.${index}.isMandatory`)}
defaultChecked={item.isMandatory}
/>
</td>
<td className="text-center border-0">
<button
type="button"
onClick={() => removeChecklistItem(index)}
className="btn btn-xs btn-icon btn-text-secondary"
>
<i class="bx bxs-minus-circle text-danger"></i>
</button>
</td>
</tr>
))}
</tbody>
</table>
)}
<button
type="button"
className="btn btn-xs btn-primary mt-2"
onClick={addChecklistItem}
>
<i class="bx bx-plus-circle"></i>
</button>
</div>
{/* Submit / Cancel */}
<div className="col-12 text-center mt-3">
<button type="submit" className="btn btn-sm btn-primary me-3">
{isLoading ? "Please Wait" : "Submit"}
</button>
<button
type="reset"
className="btn btn-sm btn-label-secondary"
onClick={onClose}
>
Cancel
</button>
</div>
</div>
</form>
);
};
export default UpdateActivity;

View File

@ -101,7 +101,7 @@ const EditMaster=({master,onClose})=> {
setIsLoading( false )
const cachedData = getCachedData("Role");
const cachedData = getCachedData("Application Role");
if (cachedData) {
@ -109,9 +109,9 @@ const EditMaster=({master,onClose})=> {
role.id === resp.data?.id ? { ...role, ...resp.data } : role
);
cacheData("Role", updatedData);
cacheData("Application Role", updatedData);
}
showToast( "Role Update successfully.", "success" );
showToast( "Application Role Updated successfully.", "success" );
setIsLoading(false)
onClose()
}).catch((Err)=>{

View File

@ -5,53 +5,62 @@ import DeleteMaster from "./DeleteMaster";
import EditRole from "./EditRole";
import CreateJobRole from "./CreateJobRole";
import EditJobRole from "./EditJobRole";
import CreateActivity from "./CreateActivity";
import EditActivity from "./EditActivity";
const MasterModal = ({ modaldata ,closeModal}) => {
const MasterModal = ({ modaldata, closeModal }) => {
return (
<div
className="modal fade"
id="master-modal"
tabIndex="-1"
aria-hidden="true"
role="dialog"
<div
className="modal fade"
id="master-modal"
tabIndex="-1"
aria-hidden="true"
role="dialog"
aria-labelledby="modalToggleLabel"
>
<div
className={`modal-dialog mx-sm-auto mx-1 ${
modaldata?.modalType === "delete" ? "modal-md" : "modal-lg"
} modal-simple ` }
>
<div className="modal-content">
<div className="modal-body p-sm-4 p-0">
<button
type="button"
className="btn-close"
data-bs-dismiss="modal"
aria-label="Close"
onClick={closeModal}
></button>
<div className="text-center mb-2"></div>
{modaldata?.modalType === "Role" &&
<CreateRole masmodalType={modaldata.masterType} onClose={closeModal} />}
{modaldata?.modalType === "Edit-Role" && (
<EditRole master={modaldata} onClose={closeModal} />
)}
{modaldata?.modalType === "delete" && (
<DeleteMaster master={modaldata} onClose={closeModal}/>
)}
{modaldata?.modalType === "Job Role" && (
<CreateJobRole onClose={closeModal} />
)}
{modaldata?.modalType === "Edit-Job Role" && (
<EditJobRole data ={modaldata.item} onClose={closeModal} />
)}
<div
className={`modal-dialog mx-sm-auto mx-1 ${
modaldata?.modalType === "delete" || `Ativity` ? "modal-md" : "modal-lg"
} modal-simple `}
>
<div className="modal-content">
<div className="modal-body p-sm-4 p-0">
<button
type="button"
className="btn-close"
data-bs-dismiss="modal"
aria-label="Close"
onClick={closeModal}
></button>
<div className="text-center mb-2"></div>
{modaldata?.modalType === "Application Role" && (
<CreateRole
masmodalType={modaldata.masterType}
onClose={closeModal}
/>
)}
{modaldata?.modalType === "Edit-Application Role" && (
<EditRole master={modaldata} onClose={closeModal} />
)}
{modaldata?.modalType === "delete" && (
<DeleteMaster master={modaldata} onClose={closeModal} />
)}
{modaldata?.modalType === "Job Role" && (
<CreateJobRole onClose={closeModal} />
)}
{modaldata?.modalType === "Edit-Job Role" && (
<EditJobRole data={modaldata.item} onClose={closeModal} />
)}
{modaldata?.modalType === "Activity" && (
<CreateActivity onClose={closeModal} /> )
}
{modaldata?.modalType === 'Edit-Activity' && (
<EditActivity activityData={modaldata.item} onClose={closeModal} />
)}
</div>
</div>
</div>
</div>
</div>
);
};

View File

@ -1,4 +1,6 @@
export const mastersList = [{id:1, name: "Role"},{id:2, name: "Job Role"}]
// it important ------
export const mastersList = [ {id: 1, name: "Application Role"}, {id: 2, name: "Job Role"}, {id: 3, name: "Activity"} ]
// -------------------
export const dailyTask = [
{

View File

@ -28,17 +28,13 @@
"text": "Project Status",
"available": true,
"link": "#"
},
{
"text": "Inventory",
"available": true,
"link": "/inventory"
}
]
},
{
"text": "Activities",
"icon": "bx bx-store",
"icon": "bx bx-list-ul",
"available": true,
"link": "",
"submenu": [
@ -70,25 +66,20 @@
{
"text": "Daily Expenses",
"available": true,
"link": "/dashboard"
"link": "/dashboard/"
}
]
},
{
"text": "User Management",
"text": "Administration",
"icon": "bx bx-box",
"available": true,
"link": "",
"submenu": [
{
"text": "Application Users",
"text": "Users",
"available": true,
"link": "/employees"
},
{
"text": "Employees",
"available": true,
"link": "/employees"
"link": "/employees/"
},
{
"text": "Masters",
@ -96,11 +87,17 @@
"link": "/masters"
}
]
},
{
"text": "Inventory",
"icon": "bx bx-store",
"available": true,
"link": "/inventory"
}
]
},
{
"header": "Misc",
"header": "",
"items": [
{
"text": "Support",

View File

@ -35,15 +35,17 @@ const useMaster = () => {
} else {
let response;
switch (selectedMaster) {
case "Role":
case "Application Role":
response = await MasterRespository.getRoles();
response = response.data;
break;
case "Job Role":
response = await MasterRespository.getJobRole();
response = response.data
break;
case "Module":
response = [{description: null,module:"Module 1",featurePermission: null,id: "08dd4761-363c-49ed-8851-3d2489a3e98d"},{description: null,module:"Module 2",featurePermission: null,id: "08dy9761-363c-49ed-8851-3d2489a3e98d"},{description: null,module:"Module 3",featurePermission: null,id: "08dy7761-263c-49ed-8851-3d2489a3e98d"}];
case "Activity":
response = await MasterRespository.getActivites();
response = response.data
break;
case "Status":
response = [{description: null,featurePermission: null,id: "02dd4761-363c-49ed-8851-3d2489a3e98d",status:"status 1"},{description: null,featurePermission: null,id: "03dy9761-363c-49ed-8851-3d2489a3e98d",status:"status 2"},{description: null,featurePermission: null,id: "03dy7761-263c-49ed-8851-3d2489a3e98d",status:"Status 3"}];

View File

@ -30,7 +30,10 @@ export const useAttendace =(projectId)=>{
useEffect(()=>{
fetchData(projectId);
if ( projectId )
{
fetchData(projectId);
}
},[projectId])
return {attendance,loading,error}
@ -40,7 +43,7 @@ export const useAttendace =(projectId)=>{
export const useEmployeeAttendacesLog = (id) => {
const [logs, setLogs] = useState();
const [logs, setLogs] = useState([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
@ -71,4 +74,41 @@ export const useEmployeeAttendacesLog = (id) => {
}, [id]);
return { logs, loading, error };
};
};
export const useRegularizationRequests = ( projectId ) =>
{
const [regularizes, setregularizes] = useState([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const fetchData = () => {
const regularizedList_cache = getCachedData("regularizedList");
if(!regularizedList_cache || regularizedList_cache.projectId !== projectId ){
setLoading(true)
AttendanceRepository.getRegularizeList(projectId).then((response)=>{
setregularizes( response.data )
console.log(response.data)
cacheData("regularizedList", { data: response.data, projectId })
setLoading(false)
}).catch((error)=>{
setError("Failed to fetch data.");
setLoading(false);
})
}else{
setregularizes(regularizedList_cache.data);
}
};
useEffect(() => {
if (projectId) {
fetchData();
}
}, [ projectId ] );
return {regularizes,loading,error,refetch:fetchData}
}

View File

@ -0,0 +1,122 @@
import { useState, useEffect } from "react";
import GlobalRepository from "../repositories/GlobalRepository";
// 🔹 Dashboard Progression Data Hook
export const useDashboard_Data = ({ days, FromDate, projectId }) => {
const [dashboard_data, setDashboard_Data] = useState([]);
const [isLineChartLoading, setLoading] = useState(false);
const [error, setError] = useState("");
useEffect(() => {
if (!days) return;
const fetchData = async () => {
setLoading(true);
setError("");
try {
const payload = {
days,
FromDate: FromDate || '',
projectId: projectId || 0,
};
const response = await GlobalRepository.getDashboardProgressionData(payload);
setDashboard_Data(response.data);
} catch (err) {
setError("Failed to fetch dashboard data.");
console.error(err);
} finally {
setLoading(false);
}
};
fetchData();
}, [days, FromDate, projectId]);
return { dashboard_data, loading: isLineChartLoading, error };
};
// 🔹 Dashboard Projects Card Data Hook
export const useDashboardProjectsCardData = () => {
const [projectsCardData, setProjectsData] = useState([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState("");
useEffect(() => {
const fetchProjectsData = async () => {
setLoading(true);
setError("");
try {
const response = await GlobalRepository.getDashboardProjectsCardData();
setProjectsData(response.data);
} catch (err) {
setError("Failed to fetch projects card data.");
console.error(err);
} finally {
setLoading(false);
}
};
fetchProjectsData();
}, []);
return { projectsCardData, loading, error };
};
// 🔹 Dashboard Teams Card Data Hook
export const useDashboardTeamsCardData = () => {
const [teamsCardData, setTeamsData] = useState([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState("");
useEffect(() => {
const fetchTeamsData = async () => {
setLoading(true);
setError("");
try {
const response = await GlobalRepository.getDashboardTeamsCardData();
setTeamsData(response.data);
} catch (err) {
setError("Failed to fetch teams card data.");
console.error(err);
} finally {
setLoading(false);
}
};
fetchTeamsData();
}, []);
return { teamsCardData, loading, error };
};
// 🔹 Dashboard Tasks Card Data Hook
export const useDashboardTasksCardData = () => {
const [tasksCardData, setTasksData] = useState([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState("");
useEffect(() => {
const fetchTasksData = async () => {
setLoading(true);
setError("");
try {
const response = await GlobalRepository.getDashboardTasksCardData();
setTasksData(response.data);
} catch (err) {
setError("Failed to fetch tasks card data.");
console.error(err);
} finally {
setLoading(false);
}
};
fetchTasksData();
}, []);
return { tasksCardData, loading, error };
};

View File

@ -240,8 +240,7 @@ export const useEmployeeProfile =(employeeId)=>{
const fetchData = async () => {
const Employee_cache = getCachedData("employeeProfile");
if(!Employee_cache || Employee_cache.employeeId !== employeeId){
EmployeeRepository.getEmployeeProfile(employeeId)
.then((response) => {
setEmployees(response.data);
@ -260,7 +259,9 @@ export const useEmployeeProfile =(employeeId)=>{
};
useEffect(()=>{
fetchData(employeeId);
if(employeeId){
fetchData(employeeId);
}
},[employeeId])
return {employee,loading,error}

View File

@ -16,9 +16,9 @@ export const useMasterRole =()=>{
if (!features_cache) {
MasterRespository.getRoles()
.then((response) => {
setMasterRole(response);
setMasterRole(response.data);
cacheData("masterRole", response);
cacheData("masterRole", response.data);
setLoading(false)
})
.catch((error) => {
@ -55,9 +55,9 @@ export const useFeatures =()=> {
if (!features_cache) {
MasterRespository.getFeatures()
.then((response) => {
setMasterFeatures(response);
setMasterFeatures(response.data);
cacheData("features", response);
cacheData("features", response.data);
setLoading(false)
})
.catch((error) => {

View File

@ -18,8 +18,8 @@ export const useProfile = () =>
{
setLoading( true )
let response = await AuthRepository.profile();
setProfile( response )
cacheProfileData( response )
setProfile( response.data )
cacheProfileData( response.data )
setLoading( false );
} catch ( error )

View File

@ -1,120 +1,126 @@
import { useEffect,useState } from "react"
import {
cacheData,
getCachedData,
} from "../slices/apiDataManager"
import { useEffect, useState } from "react";
import { cacheData, getCachedData } from "../slices/apiDataManager";
import ProjectRepository from "../repositories/ProjectRepository";
import { useProfile } from "./useProfile";
export const useProjects =()=>{
export const useProjects = () => {
const [projects, setProjects] = useState([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState("");
const fetchData = async () => {
const projects_cache = getCachedData("projectslist");
if (!projects_cache) {
setLoading(true)
ProjectRepository.getProjectList()
const projects_cache = getCachedData("projectslist");
if (!projects_cache) {
setLoading(true);
ProjectRepository.getProjectList()
.then((response) => {
setProjects(response);
cacheData("projectslist", response);
let projects = response.data;
const sortedProject = [...projects].sort((a, b) =>
a.name.localeCompare(b.name)
);
setProjects(sortedProject);
cacheData("projectslist", sortedProject);
setLoading(false);
})
.catch((error) => {
setLoading(false)
console.error(error);
setLoading(false);
setError("Failed to fetch data.");
});
} else {
if (!projects.length) setProjects(projects_cache);
} else {
if (!projects.length) {
let projects = projects_cache;
const sortedProject = [...projects].sort((a, b) =>
a.name.localeCompare(b.name)
);
setProjects(sortedProject);
}
}
};
useEffect(()=>{
fetchData()
},[])
return { projects,loading,error,refetch:fetchData}
}
useEffect(() => {
fetchData();
}, []);
export const useEmployeesByProjectAllocated = ( selectedProject ) =>
{
const [projectEmployees, setEmployeeList] = useState([]);
const[loading,setLoading] = useState(true)
const [projects, setProjects] = useState([]);
const fetchData = async (projectid) => {
try {
let EmployeeByProject_Cache = getCachedData("empListByProjectAllocated")
if(!EmployeeByProject_Cache || !EmployeeByProject_Cache.projectId === projectid) {
let response = await ProjectRepository.getProjectAllocation(projectid)
setEmployeeList(response.data);
cacheData("empListByProjectAllocated",{data:response.data,projectId:projectid});
setLoading(false)
}else{
setEmployeeList(EmployeeByProject_Cache.data)
setLoading(false)
}
} catch (err) {
setError("Failed to fetch data.");
setLoading(false)
}
};
useEffect(()=>{
if(selectedProject){
return { projects, loading, error, refetch: fetchData };
};
export const useEmployeesByProjectAllocated = (selectedProject) => {
const [projectEmployees, setEmployeeList] = useState([]);
const [loading, setLoading] = useState(true);
const [projects, setProjects] = useState([]);
const fetchData = async (projectid) => {
try {
let EmployeeByProject_Cache = getCachedData("empListByProjectAllocated");
if (
!EmployeeByProject_Cache ||
!EmployeeByProject_Cache.projectId === projectid
) {
let response = await ProjectRepository.getProjectAllocation(projectid);
setEmployeeList(response.data);
cacheData("empListByProjectAllocated", {
data: response.data,
projectId: projectid,
});
setLoading(false);
} else {
setEmployeeList(EmployeeByProject_Cache.data);
setLoading(false);
}
} catch (err) {
setError("Failed to fetch data.");
setLoading(false);
}
};
useEffect(() => {
if (selectedProject) {
fetchData(selectedProject);
}
},[selectedProject])
return {projectEmployees,loading,projects}
}
export const useProjectDetails =(projectId)=>{
}
}, [selectedProject]);
return { projectEmployees, loading, projects };
};
export const useProjectDetails = (projectId) => {
const { profile } = useProfile();
const [projects_Details, setProject_Details] = useState(null);
const [loading, setLoading] = useState(false);
const [loading, setLoading] = useState(true);
const [error, setError] = useState("");
const fetchData = async () => {
setLoading(true)
const project_cache = getCachedData(`projectinfo-${projectId}`);
if (!project_cache) {
setLoading(true);
const project_cache = getCachedData("projectInfo");
if (!project_cache || project_cache?.projectId != projectId) {
ProjectRepository.getProjectByprojectId(projectId)
.then( ( response ) =>
{
setProject_Details(response);
cacheData( `projectinfo-${ projectId }`, response );
setLoading(false)
.then((response) => {
setProject_Details(response.data);
cacheData("projectInfo", {
projectId: projectId,
data: response.data,
});
setLoading(false);
})
.catch((error) => {
console.error(error);
setError( "Failed to fetch data." );
setLoading(false)
setError("Failed to fetch data.");
setLoading(false);
});
} else {
setProject_Details( project_cache );
setLoading(false)
}
};;
useEffect(()=>{
fetchData()
},[projectId])
setProject_Details(project_cache.data);
setLoading(false);
}
};
return { projects_Details,loading,error,refetch:fetchData}
}
useEffect(() => {
if (profile && projectId != undefined) {
fetchData();
}
}, [projectId, profile]);
return { projects_Details, loading, error, refetch: fetchData };
};

43
src/hooks/useTasks.js Normal file
View File

@ -0,0 +1,43 @@
import { useEffect, useState } from "react";
import { TasksRepository } from "../repositories/TaskRepository";
import { cacheData, getCachedData } from "../slices/apiDataManager";
// import {formatDate} from "../utils/dateUtils";
export const useTaskList = (projectId, dateFrom, toDate) => {
const [TaskList, setTaskList] = useState([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const fetchList = async () => {
const taskList_cached = getCachedData("taskList");
// if (!taskList_cached || taskList_cached?.projectId !== projectId) {
try {
setLoading(true);
const resp = await TasksRepository.getTaskList(
projectId,
dateFrom,
toDate
);
setTaskList(resp.data);
cacheData("taskList", { projectId: projectId, data: resp.data });
setLoading(false);
} catch (err) {
setLoading(false);
console.log(err);
setError(err);
}
// } else {
// setTaskList(taskList_cached.data);
// }
};
useEffect( () =>
{
if (projectId && dateFrom && toDate) {
fetchList();
}
}, [projectId, dateFrom, toDate]);
return { TaskList, loading, error, refetch:fetchList};
};

View File

@ -6,14 +6,17 @@ import App from './App.tsx'
import { Provider } from 'react-redux';
import { store } from './store/store';
import { ModalProvider } from './ModalContext.jsx';
createRoot(document.getElementById('root')!).render(
// <StrictMode>
// <MasterDataProvider>
<Provider store={store}>
<App />
<Provider store={ store }>
<ModalProvider>
<App />
</ModalProvider>
</Provider>
// </MasterDataProvider>

View File

@ -1,5 +1,9 @@
import React, { useState, useEffect } from "react";
import { cacheData, getCachedData, getCachedProfileData } from "../../slices/apiDataManager";
import {
cacheData,
getCachedData,
getCachedProfileData,
} from "../../slices/apiDataManager";
import Breadcrumb from "../../components/common/Breadcrumb";
import AttendanceLog from "../../components/Activities/AttendcesLogs";
import Attendance from "../../components/Activities/Attendance";
@ -10,26 +14,25 @@ import Regularization from "../../components/Activities/Regularization";
import { useAttendace } from "../../hooks/useAttendance";
import { useDispatch, useSelector } from "react-redux";
import { setProjectId } from "../../slices/localVariablesSlice";
import {markCurrentAttendance} from "../../slices/apiSlice/attendanceAllSlice";
import { markCurrentAttendance } from "../../slices/apiSlice/attendanceAllSlice";
import { hasUserPermission } from "../../utils/authUtils";
import {useHasUserPermission} from "../../hooks/useHasUserPermission";
import {REGULARIZE_ATTENDANCE} from "../../utils/constants";
import { useHasUserPermission } from "../../hooks/useHasUserPermission";
import { REGULARIZE_ATTENDANCE } from "../../utils/constants";
const AttendancePage = () =>
{
const loginUser = getCachedProfileData()
var selectedProject = useSelector( ( store ) => store.localVariables.projectId )
const {projects,loading:projectLoading} = useProjects()
const {attendance,loading:attLoading} = useAttendace(selectedProject)
const[attendances,setAttendances] = useState()
const AttendancePage = () => {
const loginUser = getCachedProfileData();
var selectedProject = useSelector((store) => store.localVariables.projectId);
const { projects, loading: projectLoading } = useProjects();
const {attendance, loading: attLoading} = useAttendace( selectedProject );
const [ attendances, setAttendances ] = useState();
const [empRoles, setEmpRoles] = useState(null);
const [isCreateModalOpen, setIsCreateModalOpen] = useState(false);
const [ modelConfig, setModelConfig ] = useState();
const DoRegularized = useHasUserPermission(REGULARIZE_ATTENDANCE)
const dispatch = useDispatch()
const [modelConfig, setModelConfig] = useState();
const DoRegularized = useHasUserPermission(REGULARIZE_ATTENDANCE);
const dispatch = useDispatch();
const [formData, setFormData] = useState({
time: "",
markTime: "",
description: "",
date: new Date().toLocaleDateString(),
});
@ -46,25 +49,28 @@ const AttendancePage = () =>
setIsCreateModalOpen(true);
};
const handleModalData =(employee)=>{
const handleModalData = ( employee ) =>
{
setModelConfig(employee);
}
};
const closeModal = () => {
setModelConfig(null);
setIsCreateModalOpen(false);
const modalElement = document.getElementById("check-Out-modal");
if (modalElement) {
modalElement.classList.remove('show');
modalElement.style.display = 'none';
document.body.classList.remove('modal-open');
document.querySelector('.modal-backdrop').remove();
}
modalElement.classList.remove("show");
modalElement.style.display = "none";
document.body.classList.remove("modal-open");
document.querySelector(".modal-backdrop").remove();
}
};
const handleSubmit = ( formData ) =>{
const handleSubmit = ( formData ) =>
{
dispatch( markCurrentAttendance( formData ) ).then( ( action ) =>
{
const updatedAttendance = attendances.map(item =>
@ -82,33 +88,40 @@ const AttendancePage = () =>
});
};
useEffect(() => {
if (modelConfig !== null) {
openModel();
}
}, [modelConfig,isCreateModalOpen]);
useEffect(()=>{
setAttendances(attendance)
},[attendance])
}, [modelConfig, isCreateModalOpen]);
useEffect(() => {
setAttendances( attendance );
}, [attendance]);
useEffect(() => {
dispatch(setProjectId(projects[0]?.id));
}, [projects]);
return (
<>
{isCreateModalOpen && modelConfig && (
<div
className="modal fade show"
style={{ display: "block" }}
id="check-Out-modal"
tabindex="-1"
aria-hidden="true"
>
<AttendanceModel modelConfig={modelConfig} closeModal={closeModal} handleSubmitForm={handleSubmit}/>
{isCreateModalOpen && modelConfig && (
<div
className="modal fade show"
style={{ display: "block" }}
id="check-Out-modal"
tabindex="-1"
aria-hidden="true"
>
<AttendanceModel
modelConfig={modelConfig}
closeModal={closeModal}
handleSubmitForm={handleSubmit}
/>
</div>
)}
<div className="container-xxl flex-grow-1 container-p-y">
<Breadcrumb
)}
<div className="container-xxl flex-grow-1 container-p-y">
<Breadcrumb
data={[
{ label: "Home", link: "/dashboard" },
{ label: "Attendance", link: null },
@ -121,7 +134,7 @@ const AttendancePage = () =>
id="DataTables_Table_0_length"
>
{
((loginUser && loginUser?.projects.length > 1) ) && (<label>
((loginUser && loginUser?.projects?.length > 1) ) && (<label>
<select
name="DataTables_Table_0_length"
aria-controls="DataTables_Table_0"
@ -132,7 +145,7 @@ const AttendancePage = () =>
>
{!projectLoading && projects?.filter(project =>
loginUser?.projects?.map(Number).includes(project.id)).map((project)=>(
<option value={project.id}>{project.name}</option>
<option value={project.id} key={project.id}>{project.name}</option>
))}
{projectLoading && <option value="Loading..." disabled>Loading...</option> }
</select>
@ -165,7 +178,6 @@ const AttendancePage = () =>
Logs
</button>
</li>
<li className={`nav-item ${!DoRegularized && 'd-none'}`}>
<button
type="button"
@ -174,43 +186,55 @@ const AttendancePage = () =>
data-bs-toggle="tab"
data-bs-target="#navs-top-messages"
aria-controls="navs-top-messages"
aria-selected="false">
aria-selected="false"
>
Regularization
</button>
</li>
</ul>
<div class="tab-content attedanceTabs py-2">
{projectLoading && (<span>Loading..</span>)}
{(!projectLoading && !attendances) && <span>Not Found</span>}
{ (projects && projects.length > 0 ) && (
<>
<div className="tab-pane fade show active py-0" id="navs-top-home" role="tabpanel" key={projects.id}>
<Attendance attendance={attendances} handleModalData={handleModalData} getRole={getRole} />
</div>
<div class="tab-pane fade" id="navs-top-profile" role="tabpanel">
<AttendanceLog
attendance={attendances}
handleModalData={handleModalData}
projectId={selectedProject}
/>
</div>
<div className="tab-pane fade" id="navs-top-messages" role="tabpanel">
<Regularization
attendance={attendances}
handleRequest ={handleSubmit}
/>
</div>
</>
)}
</ul>
<div className="tab-content attedanceTabs py-2">
{projectLoading && <span>Loading..</span>}
{!projectLoading && !attendances && <span>Not Found</span>}
{projects && projects.length > 0 && (
<>
<div
className="tab-pane fade show active py-0"
id="navs-top-home"
role="tabpanel"
key={projects.id}
>
<Attendance
attendance={attendances}
handleModalData={handleModalData}
getRole={getRole}
/>
</div>
<div
className="tab-pane fade"
id="navs-top-profile"
role="tabpanel"
>
<AttendanceLog
attendance={attendances}
handleModalData={handleModalData}
projectId={selectedProject}
/>
</div>
<div
className="tab-pane fade"
id="navs-top-messages"
role="tabpanel"
>
<Regularization
handleRequest={handleSubmit}
/>
</div>
</>
)}
</div>
</div>
</div>
</div>
</>
);
};

View File

@ -1,86 +1,273 @@
import React, { useEffect, useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import Breadcrumb from "../../components/common/Breadcrumb";
import { dailyTask } from "../../data/masters";
import { useTaskList } from "../../hooks/useTasks";
import { useProjects } from "../../hooks/useProjects";
import { setProjectId } from "../../slices/localVariablesSlice";
import { useProfile } from "../../hooks/useProfile";
import { formatDate } from "../../utils/dateUtils";
import GlobalModel from "../../components/common/GlobalModel";
import AssignRoleModel from "../../components/Project/AssignRole";
import { ReportTask } from "../../components/Activities/ReportTask";
import ReportTaskComments from "../../components/Activities/ReportTaskComments";
import DateRangePicker from "../../components/common/DateRangePicker";
import DatePicker from "../../components/common/DatePicker";
import Breadcrumb from "../../components/common/Breadcrumb"
import { dailyTask } from "../../data/masters"
const DailyTask =()=>{
return (
<>
<div className="container-xxl flex-grow-1 container-p-y">
const DailyTask = () => {
const { profile: LoggedUser } = useProfile();
const {
projects,
loading: project_lodaing,
error: projects_Error,
} = useProjects();
const selectedProject = useSelector(
(store) => store.localVariables.projectId
);
const dispatch = useDispatch(selectedProject);
const [dateRange, setDateRange] = useState({ startDate: "", endDate: "" });
const {
TaskList,
loading: task_loading,
error: task_error,
refetch,
} = useTaskList(selectedProject, dateRange.startDate, dateRange.endDate);
const [TaskLists, setTaskLists] = useState([]);
useEffect(() => {
setTaskLists(TaskList);
}, [TaskList, selectedProject]);
const [selectedTask, selectTask] = useState(null);
const [comments, setComment] = useState(null);
const [isModalOpen, setIsModalOpen] = useState(false);
const [isModalOpenComment, setIsModalOpenComment] = useState(false);
const openModal = () => setIsModalOpen(true);
const closeModal = () => setIsModalOpen(false);
const openComment = () => setIsModalOpenComment(true);
const closeCommentModal = () => setIsModalOpenComment(false);
const handletask = (task) => {
selectTask(task);
openModal();
};
return (
<>
<div
className={`modal fade ${isModalOpen ? "show" : ""}`}
tabIndex="-1"
role="dialog"
style={{ display: isModalOpen ? "block" : "none" }}
aria-hidden={!isModalOpen}
>
<ReportTask
report={selectedTask}
closeModal={closeModal}
refetch={refetch}
/>
</div>
<div
className={`modal fade ${isModalOpenComment ? "show" : ""}`}
tabIndex="-1"
role="dialog"
style={{ display: isModalOpenComment ? "block" : "none" }}
aria-hidden={!isModalOpenComment}
>
<ReportTaskComments
commentsData={comments}
closeModal={closeCommentModal}
/>
</div>
<div className="container-xxl flex-grow-1 container-p-y">
<Breadcrumb
data={[
{ label: "Home", link: "/dashboard" },
{ label: "Attendance", link: null },
{ label: "Daily Task", link: null },
]}
></Breadcrumb>
<div className="card card-action mb-6">
<div className="card-body">
<div className="row">
{/* <div className="col-12 text-end mb-1">
<button
type="button"
className="link-button link-button-sm m-1"
data-bs-toggle="modal"
data-bs-target="#user-model"
<div className="card-body p-1 p-sm-2">
<div className="row d-flex justify-content-between">
<div className="col-6 text-start">
<DateRangePicker onRangeChange={setDateRange} />
</div>
<div className="col-sm-3 col-6 text-end mb-1">
<select
name="DataTables_Table_0_length"
aria-controls="DataTables_Table_0"
className="form-select form-select-sm"
value={selectedProject}
onChange={(e) => dispatch(setProjectId(e.target.value))}
aria-label=""
>
<i className="bx bx-plus-circle me-2"></i>
Assign Employee
</button>
</div> */}
{(project_lodaing || projects.length < 0) && (
<option value="Loading..." disabled>
Loading...
</option>
)}
{!project_lodaing &&
projects
?.filter((project) =>
LoggedUser?.projects?.map(Number).includes(project.id)
)
.map((project) => (
<option value={project.id} key={project.id}>
{project.name}
</option>
))}
</select>
</div>
</div>
<div className="table-responsive text-nowrap">
{/* {employees && employees.length > 0 ? ( */}
<table className="table ">
<thead>
<table className="table">
<thead>
<tr>
<th></th>
<th>Activity</th>
<th>Planned </th>
<th>Compeleted</th>
<th>Assign On</th>
<th>Team</th>
<th>Actions</th>
</tr>
</thead>
<tbody className="table-border-bottom-0">
{TaskLists?.length === 0 && !task_loading && (
<tr>
<th>Sr</th>
<th>Project Name</th>
<th>Target</th>
<th>Employees</th>
<th>Actions</th>
<td colSpan={7} className="text-center">
No Data Found
</td>
</tr>
</thead>
<tbody className="table-border-bottom-0">
<tr >
)}
{task_loading && (
<tr>
<td colSpan={7} className="text-center">
<p>Loading..</p>
</td>
</tr>
)}
{TaskLists.map((task, index) => {
const accordionId = `accordion-${index}`;
return (
<React.Fragment key={index}>
{/* Main Row */}
<tr>
<td>
<div className="d-flex justify-content-center align-items-center">
<div
className="d-flex justify-content-center align-items-center"
data-bs-toggle="collapse"
data-bs-target={`#${accordionId}`}
aria-expanded="false"
aria-controls={accordionId}
>
<div className="d-flex flex-column">
<a href="#" className="text-heading text-truncate">
<span className="fw-medium ">
1
</span>
<a
href="#"
className="text-heading text-truncate"
>
<i className="bx bx-chevron-right"></i>
</a>
</div>
</div>
</td>
<td className="flex-wrap">
project Name project Name project Name
{task.workItem.activityMaster.activityName ||
"No Activity Name"}
</td>
<td>
80
<td>{task.plannedTask || "NA"}</td>
<td>{task.completedTask}</td>
<td>{formatDate(task.assignmentDate)}</td>
<td className="text-center">
<div className="d-flex align-items-center avatar-group justify-content-center">
{task.teamMembers.slice(0, 3).map((member) => (
<div
key={member.id}
data-bs-toggle="tooltip"
data-bs-html="true"
data-popup="tooltip-custom"
data-bs-placement="top"
title={`${member.firstName} ${member.lastName}`}
className="avatar avatar-xs"
>
{/* <img src="..." alt="Avatar" className="rounded-circle pull-up" /> */}
<span className="avatar-initial rounded-circle bg-label-primary">
{member?.firstName.slice(0, 1)}
</span>
</div>
))}
{task.teamMembers.length > 3 && (
<div
className="avatar avatar-xs"
data-bs-toggle="tooltip"
data-bs-placement="bottom"
title={`${task.teamMembers.length - 3} more`}
>
<span className="avatar-initial rounded-circle bg-label-secondary pull-up">
+{task.teamMembers.length - 3}
</span>
</div>
)}
</div>
</td>
<td> NA</td>
<td>
<button type="button" className="btn btn-xs btn-primary">Report</button>
<td className="text-center">
<div className="d-flex justify-content-center">
<button
type="button"
className={`btn btn-xs btn-primary ${
task.completedTask > 0 ? "d-none" : ""
}`}
onClick={() => {
selectTask(task);
openModal();
}}
>
Report
</button>
<button
type="button"
className="btn btn-xs btn-primary ms-2"
onClick={() => {
setComment(task);
openComment();
}}
>
Comment
</button>
</div>
</td>
</tr>
{/* ))} */}
</tbody>
</table>
{/* Accordion Content */}
<tr
id={accordionId}
className="accordion-collapse collapse"
>
<td colSpan={5}>
<div className="row">
<p>{task.subdata?.name}</p>
</div>
</td>
</tr>
</React.Fragment>
);
})}
</tbody>
</table>
</div>
</div>
</div>
</div>
</>
)
}
export default DailyTask
</>
);
};
export default DailyTask;

View File

@ -6,10 +6,19 @@ import ProjectRepository from "../../repositories/ProjectRepository";
import Breadcrumb from "../../components/common/Breadcrumb";
import InfraPlanning from "../../components/Activities/InfraPlanning";
import { cacheData, getCachedData } from "../../slices/apiDataManager";
import { useProfile } from "../../hooks/useProfile";
import { useDispatch, useSelector } from "react-redux";
import { useProjectDetails, useProjects } from "../../hooks/useProjects";
import { setProjectId } from "../../slices/localVariablesSlice";
var projectId;
const TaskPlannng = () => {
const {profile} = useProfile();
const {projects,loading:project_listLoader,error:projects_error} = useProjects();
const dispatch = useDispatch();
const selectedProject = useSelector((store)=>store.localVariables.projectId);
const [project, setProject] = useState(null);
const [projectDetails, setProjectDetails] = useState(null);
@ -17,6 +26,10 @@ const TaskPlannng = () => {
const [loading, setLoading] = useState(true);
const [error, setError] = useState("");
useEffect( () =>
{
dispatch(setProjectId(projects[0]?.id))
},[projects])
const fetchActivities = async () => {
try {
@ -25,22 +38,12 @@ const TaskPlannng = () => {
if (!activities_cache) {
ActivityeRepository.getActivities()
.then((response) => {
setActivities(response);
cacheData("activitiesMaster", response);
setActivities(response.data);
cacheData("activitiesMaster", response.data);
})
.catch((error) => {
setError("Failed to fetch data.");
});
// api
// .get("/api/task/activities")
// .then((data) => {
// setActivities(data);
// dispatch(cacheApiResponse({ key: "activitiesMaster", data: data }));
// })
// .catch((error) => {
// console.error(error);
// setError("Failed to fetch data.");
// });
} else {
setActivities(activities_cache);
}
@ -53,9 +56,9 @@ const TaskPlannng = () => {
const fetchData = async () => {
try {
const project_cache = getCachedData(`projectinfo-${1}`);
const project_cache = getCachedData(`projectinfo-${selectedProject}`);
if (!project_cache) {
ProjectRepository.getProjectByprojectId(1)
ProjectRepository.getProjectByprojectId(selectedProject)
.then((response) => {
setProjectDetails(response);
setProject(response);
@ -68,21 +71,6 @@ const TaskPlannng = () => {
} else {
setProjectDetails(project_cache);
}
// api
// .get(`/api/project/details/${projectId}`)
// .then((data) => {
// setProjectDetails(data);
// setProject(data);
// dispatch(
// cacheApiResponse({ key: `projectinfo-${projectId}`, data: data })
// );
// setLoading(false);
// })
// .catch((error) => {
// console.error(error);
// setError("Failed to fetch data.");
// });
} catch (err) {
console.log(err)
setError("Failed to fetch data.");
@ -98,14 +86,16 @@ const TaskPlannng = () => {
};
const handleDataChange = (data) => {
console.log("datachange")
fetchData();
};
useEffect(() => {
projectId =1
fetchData();
if((projects.length != 0)){
fetchData();
fetchActivities();
}, []);
}
}, [selectedProject]);
return (
@ -121,7 +111,7 @@ const TaskPlannng = () => {
<InfraPlanning
data={projectDetails}
activityMaster={activities}
onDataChange={handleDataChange}
onDataChange={handleDataChange}
/>
</div>
</>

View File

@ -1,7 +1,8 @@
import React from "react";
import { ComingSoonPage } from "../Misc/ComingSoonPage";
const ImageGallary = () => {
return <div>Image Gallery</div>;
return <ComingSoonPage></ComingSoonPage>;
};
export default ImageGallary;

View File

@ -0,0 +1,24 @@
import React from 'react'
import { Link } from 'react-router-dom'
export const ComingSoonPage = () => {
return (
<>
<div className="misc-wrapper pt-10">
<h5 className="mb-2 mx-2">Coming Soon!</h5>
<p className="mb-4 mx-2">We're currently working on this feature and will have it ready shortly.
Thank you for your patience!</p>
<div className="mt-4">
<img
src="../assets/img/illustrations/girl-doing-yoga-light.png"
alt="girl-doing-yoga-light"
aria-label="Girl doing yoga light"
width="500"
className="img-fluid"
data-app-dark-img="illustrations/girl-doing-yoga-dark.png"
data-app-light-img="illustrations/girl-doing-yoga-light.png" />
</div>
</div>
</>
)
}

View File

@ -15,7 +15,7 @@ export const AuthWrapper = ({ children }) => {
className="app-brand-link gap-2"
>
<span className="app-brand-logo demo">
<img src="/public/img/brand/marco.png" alt="sneat-logo" />
<img src="/img/brand/marco.png" alt="sneat-logo" />
</span>
{/* <span className="app-brand-text demo text-body fw-bold">
Sneat

View File

@ -4,45 +4,54 @@ import { AuthWrapper } from "./AuthWrapper"
import "./page-auth.css";
import AuthRepository from "../../repositories/AuthRepository";
import showToast from "../../services/toastService";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import {z} from "zod";
const forgotPassSceham = z.object( {
email: z.string().email(),
} )
const ForgotPasswordPage = () => {
const [ email, setEmail ] = useState( "" );
const[loding,setLoading] = useState(false)
const handleChange = (e) => {
setEmail(e.target.value);
};
const {register,
handleSubmit,
formState: { errors },
reset,
getValues } = useForm( {
resolver: zodResolver( forgotPassSceham ),
defaultValues: {
email:""
}
})
const handleSubmit = async ( e ) =>
const onSubmit = async (data) =>
{
setLoading(true)
e.preventDefault();
try
{
const response = await AuthRepository.forgotPassword({email})
setLoading(true)
const response = await AuthRepository.forgotPassword(data)
if ( response.data && response.success )
{
showToast( response.message, "success" )
} else
{
showToast( response.message, "warning" )
}
reset()
setLoading( false )
setEmail("")
} catch ( error )
} catch ( err )
{
showToast( "User Not Found", "error" )
showToast( err.message, "error" )
setLoading(false)
}
};
}
return (
<AuthWrapper>
<h4 className="mb-2">Forgot Password? 🔒</h4>
<p className="mb-4">
Enter your email and we'll send you instructions to reset your password
</p>
<form id="formAuthentication" className="mb-3" onSubmit={handleSubmit}>
<form id="formAuthentication" className="mb-3" onSubmit={handleSubmit(onSubmit)}>
<div className="mb-3">
<label htmlFor="email" className="form-label">
Email
@ -52,11 +61,18 @@ const ForgotPasswordPage = () => {
className="form-control"
id="email"
name="email"
value={email}
onChange={handleChange}
{...register("email")}
placeholder="Enter your email"
autoFocus
/>
{errors.email && (
<div
className="danger-text text-start"
style={{ fontSize: "12px" }}
>
{errors.email.message}
</div>
)}
</div>
<button aria-label="Click me" className="btn btn-primary d-grid w-100">
{loding ? "Please Wait...":"Send Reset Link"}

View File

@ -5,49 +5,50 @@ import { useNavigate } from "react-router-dom";
import "./page-auth.css";
import AuthRepository from "../../repositories/AuthRepository";
import showToast from "../../services/toastService";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
const loginScheam = z.object({
username: z.string().email(),
password: z.string().min(1, { message: "Password required" }),
rememberMe: z.boolean(),
});
const LoginPage = () => {
const navigate = useNavigate();
const [loading, setLoading] = useState(false);
const [hidepass, setHidepass] = useState(true);
const [formData, setFormData] = useState({
password: "",
username: "",
rememberMe: false,
const {
register,
handleSubmit,
formState: { errors },
reset,
getValues,
} = useForm({
resolver: zodResolver(loginScheam),
});
const handleChange = (e) => {
const { name, value, type, checked } = e.target;
setFormData((prevData) => ({
...prevData,
[name]: type === "checkbox" ? checked : value,
}));
};
const handleSubmit = async ( e ) =>
{
e.preventDefault();
const onSubmit = async (data) => {
setLoading(true);
try {
let data = {
username: formData.username,
password: formData.password,
let userCredential = {
username: data.username,
password: data.password,
};
const response = await AuthRepository.login(data);
const response = await AuthRepository.login(userCredential);
localStorage.setItem("jwtToken", response.data.token);
localStorage.setItem( "refreshToken", response.data.refreshToken );
localStorage.setItem("refreshToken", response.data.refreshToken);
setLoading(false);
navigate("/dashboard");
} catch (err) {
console.log("Unable to proceed. Please try again.");
setLoading(false);
}
};
return (
<AuthWrapper>
<h4 className="mb-2">Welcome to PMS!</h4>
@ -55,8 +56,12 @@ const LoginPage = () => {
Please sign-in to your account and start the adventure
</p>
<form id="formAuthentication" className="mb-3" onSubmit={handleSubmit}>
<div className="mb-3">
<form
id="formAuthentication"
className="mb-3"
onSubmit={handleSubmit(onSubmit)}
>
<div className="mb-2">
<label htmlFor="username" className="form-label">
Email or Username
</label>
@ -64,55 +69,77 @@ const LoginPage = () => {
type="text"
className="form-control"
id="username"
value={formData.name}
onChange={handleChange}
{...register("username")}
name="username"
placeholder="Enter your email or username"
autoFocus
/>
{errors.username && (
<div
className="danger-text text-start"
style={{ fontSize: "12px" }}
>
{errors.username.message}
</div>
)}
</div>
<div className="mb-3 form-password-toggle">
<div className="d-flex justify-content-between">
<div className="d-flex justify-content-center">
<label className="form-label" htmlFor="password">
Password
</label>
<Link
aria-label="Go to Forgot Password Page"
to="/auth/forgot-password"
>
<small>Forgot Password?</small>
</Link>
</div>
<div className="input-group input-group-merge">
<input
type="password"
type={hidepass ? "password" : "text"}
autoComplete="true"
id="password"
value={formData.password}
onChange={handleChange}
{...register("password")}
className="form-control"
name="password"
placeholder="&#xb7;&#xb7;&#xb7;&#xb7;&#xb7;&#xb7;&#xb7;&#xb7;&#xb7;&#xb7;&#xb7;&#xb7;"
aria-describedby="password"
/>
<span className="input-group-text cursor-pointer"></span>
<span
className="input-group-text cursor-pointer"
onClick={() => setHidepass(!hidepass)}
>
{hidepass ? (
<i className="bx bx-hide"></i>
) : (
<i className="bx bx-show"></i>
)}
</span>
</div>
{errors.password && (
<div
className="danger-text text-start"
style={{ fontSize: "12px" }}
>
{errors.password.message}
</div>
)}
</div>
<div className="mb-3">
<div className="form-check">
<div className="mb-3 d-flex justify-content-between">
<div className="form-check d-flex">
<input
className="form-check-input"
type="checkbox"
id="remember-me"
name="rememberMe"
checked={formData.rememberMe}
onChange={handleChange}
{...register("rememberMe")}
/>
<label className="form-check-label" htmlFor="remember-me">
{" "}
Remember Me{" "}
<label className="form-check-label ms-2" >
Remember Me
</label>
</div>
<Link
aria-label="Go to Forgot Password Page"
to="/auth/forgot-password"
>
<span>Forgot Password?</span>
</Link>
</div>
<div className="mb-3">
<button

View File

@ -4,68 +4,80 @@ import "./page-auth.css";
import { AuthWrapper } from "./AuthWrapper";
import showToast from "../../services/toastService";
import AuthRepository from "../../repositories/AuthRepository";
import { z } from 'zod';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from "zod";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { useNavigate } from "react-router-dom";
import {clearAllCache} from "../../slices/apiDataManager";
import { clearAllCache } from "../../slices/apiDataManager";
const resetPasswordSchema = z.object( {
email:z.string().email(),
password: z
.string()
.min(8, 'Password must be at least 8 characters')
.regex(/[A-Z]/, 'Password must contain at least one uppercase letter')
.regex(/[a-z]/, 'Password must contain at least one lowercase letter')
.regex(/\d/, 'Password must contain at least one number')
.regex(/[!@#$%^&*()_+{}\[\]:;<>,.?~\\/-]/, 'Password must contain at least one special character'),
confirmPassword: z.string().min(8, 'Password must be at least 8 characters'),
})
.refine((data) => data.password === data.confirmPassword, {
message: 'Passwords do not match',
path: ['confirmPassword'],
} );
const ResetPasswordPage = () =>
{
const [ searchParams ] = useSearchParams();
const [loading,setLoading] = useState(false)
const resetPasswordSchema = z
.object({
email: z.string().email(),
password: z
.string()
.min(8, "Password must be at least 8 characters")
.regex(/[A-Z]/, "Password must contain at least one uppercase letter")
.regex(/[a-z]/, "Password must contain at least one lowercase letter")
.regex(/\d/, "Password must contain at least one number")
.regex(
/[!@#$%^&*()_+{}\[\]:;<>,.?~\\/-]/,
"Password must contain at least one special character"
),
const token = searchParams.get('token');
const navigate = useNavigate()
const {register,handleSubmit,formState: { errors }} = useForm({
resolver:zodResolver(resetPasswordSchema)
confirmPassword: z
.string()
.min(8, "Password must be at least 8 characters"),
})
.refine((data) => data.password === data.confirmPassword, {
message: "Passwords do not match",
path: ["confirmPassword"],
});
const ResetPasswordPage = () => {
const [searchParams] = useSearchParams();
const [loading, setLoading] = useState(false);
const [hidepass, setHidepass] = useState(true);
const token = searchParams.get("token");
const navigate = useNavigate();
const {
register,
handleSubmit,
formState: { errors },
} = useForm({
resolver: zodResolver(resetPasswordSchema),
});
const onSubmitResetPassword = async (data) => {
try {
setLoading(true);
const { email, password, confirmPassword } = data;
const onSubmitResetPassword = async(data) => {
try
{
setLoading(true)
const {email, password, confirmPassword} = data;
let reqObject = {
email,
token: token,
newPassword: password
}
let response = await AuthRepository.resetPassword( reqObject );
showToast( "Password Reseted", "success" )
clearAllCache()
setLoading(false)
navigate("/auth/login",{replace:true})
} catch ( error )
{
setLoading(false)
showToast("Token is expries or Invalid ","error")
email,
token: token,
newPassword: password,
};
let response = await AuthRepository.resetPassword(reqObject);
showToast("Password Reseted", "success");
clearAllCache();
setLoading(false);
navigate("/auth/login", { replace: true });
} catch (error) {
setLoading(false);
showToast("Token is expries or Invalid ", "error");
}
};
return (
<AuthWrapper>
<h4 className="mb-2">Reset Password? 🔒</h4>
<p className="mb-4">Enter your email and new password to update.</p>
<form id="formAuthentication" className="mb-3" onSubmit={handleSubmit(onSubmitResetPassword)}>
<form
id="formAuthentication"
className="mb-3"
onSubmit={handleSubmit(onSubmitResetPassword)}
>
<div className="mb-3">
<label htmlFor="email" className="form-label">
Email
@ -78,48 +90,106 @@ const ResetPasswordPage = () =>
placeholder="Enter your email"
autoFocus
/>
{errors.email && <div className="danger-text text-start" style={{fontSize:"12px"}}>{errors.email.message}</div>}
{errors.email && (
<div
className="danger-text text-start"
style={{ fontSize: "12px" }}
>
{errors.email.message}
</div>
)}
</div>
<div className="mb-3">
<label htmlFor="email" className="form-label">
New Password
</label>
<input
type="password"
autoComplete="true"
id="password"
className="form-control"
name="password"
{...register('password')}
placeholder="&#xb7;&#xb7;&#xb7;&#xb7;&#xb7;&#xb7;&#xb7;&#xb7;&#xb7;&#xb7;&#xb7;&#xb7;"
aria-describedby="password"
/>
{errors.password && <div className="danger-text text-start" style={{fontSize:"12px"}}>{errors.password.message}</div>}
</div>
<div className=" mb-3">
<label htmlFor="email" className="form-label">
Repeat New Password
</label>
<input
type="password"
autoComplete="true"
id="password"
className="form-control"
name="confirmPassword"
{...register('confirmPassword')}
placeholder="&#xb7;&#xb7;&#xb7;&#xb7;&#xb7;&#xb7;&#xb7;&#xb7;&#xb7;&#xb7;&#xb7;&#xb7;"
aria-describedby="password"
/>
{errors.confirmPassword && <div className="danger-text text-start" style={{fontSize:"12px"}}>{errors.confirmPassword.message}</div>}
<div className="mb-2 form-password-toggle">
<div className="mt-2">
<label htmlFor="email" className="form-label list-group-item">
New Password
</label>
</div>
<div className=" input-group input-group-merge">
<input
type={hidepass ? "password" : "text"}
autoComplete="true"
id="password"
className="form-control"
name="password"
{...register("password")}
placeholder="&#xb7;&#xb7;&#xb7;&#xb7;&#xb7;&#xb7;&#xb7;&#xb7;&#xb7;&#xb7;&#xb7;&#xb7;"
aria-describedby="password"
/>
<span
className="input-group-text lcursor-pointer"
onClick={() => setHidepass(!hidepass)}
>
{hidepass ? (
<i className="bx bx-hide"></i>
) : (
<i className="bx bx-show"></i>
)}
</span>
</div>
{errors.password && (
<div
className="danger-text text-start"
style={{ fontSize: "12px" }}
>
{errors.password.message}
</div>
)}
<div className="mt-2">
{" "}
<label htmlFor="email" className="form-label">
Repeat New Password
</label>{" "}
</div>
<div className=" input-group input-group-merge">
<input
type={hidepass ? "password" : "text"}
autoComplete="true"
id="password"
className="form-control"
name="confirmPassword"
{...register("confirmPassword")}
placeholder="&#xb7;&#xb7;&#xb7;&#xb7;&#xb7;&#xb7;&#xb7;&#xb7;&#xb7;&#xb7;&#xb7;&#xb7;"
aria-describedby="password"
/>
<span
className="input-group-text cursor-pointer"
onClick={() => setHidepass(!hidepass)}
>
{hidepass ? (
<i className="bx bx-hide"></i>
) : (
<i className="bx bx-show"></i>
)}
</span>
</div>
{errors.confirmPassword && (
<div
className="danger-text text-start"
style={{ fontSize: "12px" }}
>
{errors.confirmPassword.message}
</div>
)}
</div>
<div className="mb-3 text-start ">
<p className="p-0 m-0"style={{fontSize:'9px'}}>Password must be at least 8 characters</p>
<p className="p-0 m-0" style={{fontSize:'9px'}}>Password must contain at least one uppercase letter</p>
<p className="p-0 m-0" style={{fontSize:'9px'}}>Password must contain at least one number</p>
<p className="p-0 m-0" style={{fontSize:'9px'}}>Password must contain at least one special character</p>
<p className="p-0 m-0" style={{ fontSize: "9px" }}>
Password must be at least 8 characters
</p>
<p className="p-0 m-0" style={{ fontSize: "9px" }}>
Password must contain at least one uppercase letter
</p>
<p className="p-0 m-0" style={{ fontSize: "9px" }}>
Password must contain at least one number
</p>
<p className="p-0 m-0" style={{ fontSize: "9px" }}>
Password must contain at least one special character
</p>
</div>
<button aria-label="Click me" className="btn btn-primary d-grid w-100">
{loading ? "Please Wait...":"Update Password"}
{loading ? "Please Wait..." : "Update Password"}
</button>
</form>
<div className="text-center">

View File

@ -4,100 +4,95 @@ import { Link, NavLink, useNavigate } from "react-router-dom";
import Avatar from "../../components/common/Avatar";
import Breadcrumb from "../../components/common/Breadcrumb";
import ManageEmp from "../../components/Employee/ManageRole";
import {useEmployeesAllOrByProjectId} from "../../hooks/useEmployees";
import { useProjects } from "../../hooks/useProjects";
import { useEmployeesAllOrByProjectId } from "../../hooks/useEmployees";
import { useProjects } from "../../hooks/useProjects";
import { useProfile } from "../../hooks/useProfile";
import {hasUserPermission} from "../../utils/authUtils";
import {MANAGE_EMPLOYEES} from "../../utils/constants";
import {useHasUserPermission} from "../../hooks/useHasUserPermission";
import { hasUserPermission } from "../../utils/authUtils";
import { MANAGE_EMPLOYEES } from "../../utils/constants";
import { useHasUserPermission } from "../../hooks/useHasUserPermission";
const EmployeeList = () =>
{
const {profile:loginUser}= useProfile()
const [selectedProject, setSelectedProject] = useState("");
const {projects, loading: projectLoading} = useProjects()
const ManageEmployee = useHasUserPermission(MANAGE_EMPLOYEES)
const { profile: loginUser } = useProfile();
const [selectedProject, setSelectedProject] = useState("");
const { projects, loading: projectLoading } = useProjects();
const ManageEmployee = useHasUserPermission(MANAGE_EMPLOYEES);
const {employees, loading,setLoading, error} = useEmployeesAllOrByProjectId( selectedProject );
const [ projectsList, setProjectsList ] = useState(projects || [] );
const [employeeList,setEmployeeList] = useState([])
const { employees, loading, setLoading, error } =
useEmployeesAllOrByProjectId(selectedProject);
const [projectsList, setProjectsList] = useState(projects || []);
const [employeeList, setEmployeeList] = useState([]);
const [modelConfig, setModelConfig] = useState();
const [currentPage, setCurrentPage] = useState(1);
const [itemsPerPage] = useState(5);
const [isCreateModalOpen, setIsCreateModalOpen ] = useState( false );
const [searchText,setSearchText] = useState("")
const [isCreateModalOpen, setIsCreateModalOpen] = useState(false);
const [searchText, setSearchText] = useState("");
const [filteredData, setFilteredData] = useState([]);
const navigate = useNavigate()
const navigate = useNavigate();
const handleSearch = (e) => {
const value = e.target.value.toLowerCase();
setSearchText(value);
if (!employeeList.length) return;
const results = employeeList.filter((item) =>
Object.values(item).some((field) =>
field && field.toString().toLowerCase().includes(value)
Object.values(item).some(
(field) => field && field.toString().toLowerCase().includes(value)
)
);
setFilteredData(results);
};
useEffect(() => {
setCurrentPage( 1 )
setCurrentPage(1);
if (!loading && Array.isArray(employees)) {
setEmployeeList(employees);
setFilteredData( employees );
setFilteredData(employees);
}
}, [loading, employees, selectedProject]);
const displayData = searchText ? filteredData : employeeList
const displayData = searchText ? filteredData : employeeList;
const indexOfLastItem = currentPage * itemsPerPage;
const indexOfFirstItem = indexOfLastItem - itemsPerPage;
const currentItems = Array.isArray(displayData)
? displayData.slice(indexOfFirstItem, indexOfLastItem)
const currentItems = Array.isArray(displayData)
? displayData.slice(indexOfFirstItem, indexOfLastItem)
: [];
const paginate = (pageNumber) => setCurrentPage(pageNumber);
const totalPages = Array.isArray(displayData)
? Math.ceil(displayData.length / itemsPerPage)
: 0;
const openModal = () => {
setIsCreateModalOpen(true);
};
const openModal = () => {
setIsCreateModalOpen(true);
};
const closeModal = () => {
setIsCreateModalOpen(false);
const modalElement = document.getElementById("managerole-modal");
if (modalElement) {
modalElement.classList.remove("show");
modalElement.style.display = "none";
document.body.classList.remove("modal-open");
document.querySelector(".modal-backdrop").remove();
}
};
const handleConfigData = (config) => {
setModelConfig(config);
};
useEffect(() => {
if (modelConfig !== null) {
openModal();
}
}, [ modelConfig, isCreateModalOpen ] );
const closeModal = () => {
setIsCreateModalOpen(false);
const modalElement = document.getElementById("managerole-modal");
if (modalElement) {
modalElement.classList.remove("show");
modalElement.style.display = "none";
document.body.classList.remove("modal-open");
document.querySelector(".modal-backdrop").remove();
}
};
const handleConfigData = (config) => {
setModelConfig(config);
};
useEffect(() => {
if (modelConfig !== null) {
openModal();
}
}, [modelConfig, isCreateModalOpen]);
return (
<>
@ -126,32 +121,30 @@ const EmployeeList = () =>
className="dataTables_length text-start"
id="DataTables_Table_0_length"
>
<label>
<label>
<select
id="project-select"
onChange={(e)=>setSelectedProject(e.target.value)}
name="DataTables_Table_0_length"
aria-controls="DataTables_Table_0"
className="form-select form-select-sm"
value={selectedProject || ""}
>
{projectLoading ? (
<option value="Loading" >
Loading...
</option>
) : (
<>
id="project-select"
onChange={(e) => setSelectedProject(e.target.value)}
name="DataTables_Table_0_length"
aria-controls="DataTables_Table_0"
className="form-select form-select-sm"
value={selectedProject || ""}
>
{projectLoading ? (
<option value="Loading">Loading...</option>
) : (
<>
<option value="">All Employees</option>
{Array.isArray(projects) && projects.map((item) => (
<option key={item.id} value={item.id}>
{item.name}
</option>
))}
</>
)}
{Array.isArray(projects) &&
projects.map((item) => (
<option key={item.id} value={item.id}>
{item.name}
</option>
))}
</>
)}
</select>
</label>
</label>
</div>
</div>
</div>
@ -224,8 +217,9 @@ const EmployeeList = () =>
</li>
</ul>
<button
className={`btn btn-sm add-new btn-primary ${!ManageEmployee && 'd-none'}`}
className={`btn btn-sm add-new btn-primary ${
!ManageEmployee && "d-none"
}`}
tabIndex="0"
type="button"
>
@ -265,6 +259,17 @@ const EmployeeList = () =>
>
Name
</th>
<th
className="sorting sorting_desc d-none d-sm-table-cell"
tabIndex="0"
aria-controls="DataTables_Table_0"
rowSpan="1"
colSpan="1"
aria-label="User: activate to sort column ascending"
aria-sort="descending"
>
Email
</th>
<th
className="sorting sorting_desc d-none d-sm-table-cell"
tabIndex="0"
@ -287,7 +292,7 @@ const EmployeeList = () =>
>
Role
</th>
<th
className="sorting d-none d-md-table-cell"
tabIndex="0"
@ -309,7 +314,9 @@ const EmployeeList = () =>
Status
</th>
<th
className={`sorting_disabled ${!ManageEmployee && 'd-none'}`}
className={`sorting_disabled ${
!ManageEmployee && "d-none"
}`}
rowSpan="1"
colSpan="1"
style={{ width: "50px" }}
@ -319,105 +326,140 @@ const EmployeeList = () =>
</th>
</tr>
</thead>
<tbody>
{loading && <tr>
<tbody>
{loading && (
<tr>
<td colSpan={8}>
<p>Loading...</p>
</td>
</tr>}
{( !loading && employeeList?.length === 0 ) && <td colSpan={8}>Not Data Found </td>}
{( !loading && employeeList && currentItems.length === 0 && employeeList.length !==0 ) && <td colSpan={8}><small className="muted">'{searchText}' employee not found</small> </td>}
{(currentItems && !loading) && currentItems.sort((a, b) => b.id - a.id).map((item) => (
<tr className="odd" key={item.id}>
<td className="sorting_1" colSpan={2}>
<div className="d-flex justify-content-start align-items-center user-name">
<Avatar
firstName={item.firstName}
lastName={item.lastName}
></Avatar>
<div className="d-flex flex-column">
<a
onClick={()=>navigate(`/employee/${item.id}?for=account`)}
className="text-heading text-truncate cursor-pointer"
>
<span className="fw-medium">
{item.firstName} {item.lastName}
</span>
</a>
</div>
</div>
</td>
<td className="text-start d-none d-sm-table-cell">
<span className="text-truncate">
<i className="bx bxs-phone-call text-primary me-2"></i>
{item.phoneNumber}
</span>
</td>
<td className=" d-none d-sm-table-cell text-start">
<span className="text-truncate">
<i className="bx bxs-wrench text-success me-2"></i>{item.jobRole || "Not Assign Yet"}
</span>
</td>
<td className=" d-none d-md-table-cell">
{moment(item.joiningDate).format("DD-MMM-YYYY")}
</td>
<td>
<span
className="badge bg-label-success"
text-capitalized=""
>
Active
</span>
</td>
<td className={`d-flex justify-content-end justify-content-sm-center ${!ManageEmployee && 'd-none'} `}>
<div className="d-flex align-items-center ">
<a
className={`btn btn-icon dropdown-toggle hide-arrow`}
data-bs-toggle="dropdown"
>
<i className="bx bx-dots-vertical-rounded bx-md"></i>
</a>
<div className="dropdown-menu dropdown-menu-end m-0">
{" "}
<a
onClick={()=> navigate(`/employee/${item.id}`)}
className="dropdown-item"
>
View
</a>
<Link
className={`dropdown-item `}
to={`/employee/manage/${item.id}`}
>
Edit
</Link>
<a className="dropdown-item">
Suspend
</a>
<a
className="dropdown-item"
type="button"
data-bs-toggle="modal"
data-bs-target="#managerole-modal"
onClick={() => {
handleConfigData(item.id);
}}
>
Manage Role
</a>
</div>
</div>
</td>
</tr>
) )}
)}
{!loading && employeeList?.length === 0 && (
<td
colSpan={8}
style={{ paddingTop: "20px", textAlign: "center" }}
>
No Data Found
</td>
)}
{!loading &&
employeeList &&
currentItems.length === 0 &&
employeeList.length !== 0 && (
<td colSpan={8}>
<small className="muted">
'{searchText}' employee not found
</small>{" "}
</td>
)}
{currentItems &&
!loading &&
currentItems
.sort((a, b) => b.id - a.id)
.map((item) => (
<tr className="odd" key={item.id}>
<td className="sorting_1" colSpan={2}>
<div className="d-flex justify-content-start align-items-center user-name">
<Avatar
firstName={item.firstName}
lastName={item.lastName}
></Avatar>
<div className="d-flex flex-column">
<a
onClick={() =>
navigate(
`/employee/${item.id}?for=account`
)
}
className="text-heading text-truncate cursor-pointer"
>
<span className="fw-medium">
{item.firstName} {item.lastName}
</span>
</a>
</div>
</div>
</td>
<td className="text-start d-none d-sm-table-cell">
{item.email ? (
<span className="text-truncate">
<i className="bx bxs-envelope text-primary me-2"></i>
{item.email}
</span>
) : (
<span className="text-truncate text-italic">
NA
</span>
)}
</td>
<td className="text-start d-none d-sm-table-cell">
<span className="text-truncate">
<i className="bx bxs-phone-call text-primary me-2"></i>
{item.phoneNumber}
</span>
</td>
<td className=" d-none d-sm-table-cell text-start">
<span className="text-truncate">
<i className="bx bxs-wrench text-success me-2"></i>
{item.jobRole || "Not Assign Yet"}
</span>
</td>
<td className=" d-none d-md-table-cell">
{moment(item.joiningDate).format("DD-MMM-YYYY")}
</td>
<td>
<span
className="badge bg-label-success"
text-capitalized=""
>
Active
</span>
</td>
{ManageEmployee && (
<td className="text-end">
<div className="dropdown">
<button
className="btn btn-icon dropdown-toggle hide-arrow"
data-bs-toggle="dropdown"
>
<i className="bx bx-dots-vertical-rounded bx-md"></i>
</button>
<div className="dropdown-menu dropdown-menu-end">
<button
onClick={() =>
navigate(`/employee/${item.id}`)
}
className="dropdown-item"
>
View
</button>
<Link
to={`/employee/manage/${item.id}`}
className="dropdown-item"
>
Edit
</Link>
<button className="dropdown-item">
Suspend
</button>
<button
className="dropdown-item"
type="button"
data-bs-toggle="modal"
data-bs-target="#managerole-modal"
onClick={() => handleConfigData(item.id)}
>
Manage Role
</button>
</div>
</div>
</td>
)}
</tr>
))}
</tbody>
</table>

View File

@ -8,7 +8,7 @@ import { getCachedData } from "../../slices/apiDataManager";
import { useEmployeeProfile, useEmployees, useEmployeesByProject } from "../../hooks/useEmployees";
import { useSelector } from "react-redux";
import EmployeeRepository from "../../repositories/EmployeeRepository";
import { ComingSoonPage } from "../Misc/ComingSoonPage";
const EmployeeProfile = () => {
@ -56,35 +56,32 @@ const EmployeeProfile = () => {
switch (activePill) {
case "account": {
return (
<div className="row">
{/* <div className="col-xl-4 col-lg-5 col-md-5"> */}
<p>Account</p>
{/* </div> */}
</div>
<>
<ComingSoonPage/>
</>
);
}
case "attendance": {
return (
<div className="row">
<div className="col-lg-12 col-xl-12">
{/* Teams */}
<p>attendance component</p>
{/* Teams */}
</div>
</div>
<>
<ComingSoonPage/>
</>
);
break;
}
case "activities": {
return (
<p>activites components</p>
<>
<ComingSoonPage/>
</>
);
break;
}
default:
return <div>Select a pill to display content here.</div>;
return <>
<ComingSoonPage/>
</>;
}
};
@ -160,7 +157,7 @@ const EmployeeProfile = () => {
</li>
<li className="d-flex align-items-start test-start mb-2">
<span className={`${currentEmployee?.permanentAddress ? "" : "ms-4"}`}>
{currentEmployee?.peramnentAddress}
{currentEmployee?.permanentAddress}
</span>
</li>

View File

@ -53,10 +53,8 @@ const MasterPage = () => {
};
const {data:masterData, loading,error , RecallApi} = useMaster();
const handleSearch = (e) => {
const value = e.target.value.toLowerCase();

View File

@ -7,7 +7,7 @@ const MasterTable = ( {data, columns, loading, handleModalData} ) =>
{
const hasMasterPermission = useHasUserPermission(MANAGE_MASTER)
const selectedMaster = useSelector((store)=>store.localVariables.selectedMaster)
const hiddenColumns = ["id", "featurePermission","tenant","tenantId"];
const hiddenColumns = ["id", "featurePermission","tenant","tenantId","checkLists"];
const safeData = Array.isArray(data) ? data : [];
const [currentPage, setCurrentPage] = useState(1);
@ -28,7 +28,7 @@ const MasterTable = ( {data, columns, loading, handleModalData} ) =>
.map((col) => ({
...col,
label:
col.key === "role" || col.key === "module" || col.key === "status" ? "Name" : col.label,
col.key === "role" || col.key === "activityName" || col.key === "status" ? "Name" : col.label,
}));
return (
@ -45,8 +45,8 @@ const MasterTable = ( {data, columns, loading, handleModalData} ) =>
<thead>
<tr>
<th></th>
<th>{ selectedMaster} Name</th>
<th>{selectedMaster } Description</th>
<th> Name</th>
<th>{selectedMaster} {selectedMaster === "Activity" ? "Unit":"Description" }</th>
<th className={` ${!hasMasterPermission && 'd-none'}`}>Actions</th>
</tr>
</thead>

View File

@ -1,11 +1,8 @@
import React from "react";
import { ComingSoonPage } from "../Misc/ComingSoonPage";
const Inventory = () => {
return <div>Inventory
<div>
<p>ddj</p>
</div>
</div>;
return <ComingSoonPage></ComingSoonPage>;
};
export default Inventory;

View File

@ -14,50 +14,34 @@ import Breadcrumb from "../../components/common/Breadcrumb";
import { cacheData, getCachedData } from "../../slices/apiDataManager";
import ProjectRepository from "../../repositories/ProjectRepository";
import { ActivityeRepository } from "../../repositories/MastersRepository";
import "./ProjectDetails.css";
import {useEmployeesByProjectAllocated} from "../../hooks/useProjects";
import {useEmployeesByProjectAllocated, useProjectDetails} from "../../hooks/useProjects";
import {useDispatch} from "react-redux";
import {setProjectId} from "../../slices/localVariablesSlice";
import { ComingSoonPage } from "../Misc/ComingSoonPage";
const ProjectDetails = () => {
let { projectId } = useParams();
let {projectId} = useParams();
const {projects_Details,loading:projectLoading,error:ProjectError} = useProjectDetails(projectId)
const dispatch = useDispatch()
const [project, setProject] = useState(null);
const [ projectDetails, setProjectDetails ] = useState( null );
const [activities, setActivities] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState("");
const fetchActivities = async () => {
const [ error, setError ] = useState( "" );
const activities_cache = getCachedData("activitiesMaster");
if (!activities_cache) {
ActivityeRepository.getActivities()
.then((response) => {
setActivities(response);
cacheData("activitiesMaster", response);
})
.catch((error) => {
setError("Failed to fetch data.");
});
} else {
setActivities(activities_cache);
}
};
const fetchData = async () => {
const project_cache = getCachedData(`projectinfo-${projectId}`);
if (!project_cache) {
const project_cache = getCachedData("projectInfo");
if (!project_cache || project_cache?.projectId !== projectId) {
ProjectRepository.getProjectByprojectId(projectId)
.then( ( response ) =>
{
setProjectDetails(response);
setProject(response);
cacheData( `projectinfo-${ projectId }`, response );
setProjectDetails( response.data );
setProject( response.data );
cacheData("projectInfo", {projectId,data: response.data} );
setLoading(false)
})
.catch((error) => {
@ -66,13 +50,14 @@ const ProjectDetails = () => {
setLoading(false)
});
} else {
setProjectDetails( project_cache );
setProject( project_cache );
setProjectDetails( project_cache.data );
setProject( project_cache.data );
setLoading(false)
}
};
const [activePill, setActivePill] = useState("profile");
@ -86,7 +71,7 @@ const ProjectDetails = () => {
const renderContent = () => {
if (loading) return <Loader></Loader>;
if (projectLoading) return <Loader></Loader>;
switch (activePill) {
case "profile": {
return (
@ -98,7 +83,7 @@ const ProjectDetails = () => {
</div>
<div className="col-xl-4 col-lg-5 col-md-5">
{/* Profile Overview */}
<ProjectOverview project={ project} />
<ProjectOverview project={projectId} />
{/* Profile Overview */}
</div>
</div>
@ -120,7 +105,6 @@ const ProjectDetails = () => {
return (
<ProjectInfra
data={projectDetails}
activityMaster={activities}
onDataChange={handleDataChange}
></ProjectInfra>
);
@ -130,7 +114,6 @@ const ProjectDetails = () => {
return (
<WorkPlan
data={projectDetails}
activityMaster={activities}
onDataChange={handleDataChange}
></WorkPlan>
);
@ -147,18 +130,19 @@ const ProjectDetails = () => {
}
default:
return <div>Select a pill to display content here.</div>;
return <ComingSoonPage></ComingSoonPage>;
}
};
useEffect(() => {
fetchData();
fetchActivities();
}, []);
dispatch(setProjectId(projectId))
setProject( projects_Details )
setProjectDetails(projects_Details)
}, [projects_Details,projectId]);
return (
<>
{}
<div className="container-xxl flex-grow-1 container-p-y">
<Breadcrumb
data={[
@ -169,8 +153,8 @@ const ProjectDetails = () => {
></Breadcrumb>
<div className="row">
{loading && <p>Loading....</p>}
{!loading && <ProjectBanner project_data={project} ></ProjectBanner>}
{projectLoading && <p>Loading....</p>}
{(!projectLoading && project) && <ProjectBanner project_data={project} ></ProjectBanner>}
</div>
<div className="row">

View File

@ -14,6 +14,7 @@ import {MANAGE_PROJECT} from "../../utils/constants";
const ProjectList = () =>
{
const {profile: loginUser} = useProfile();
const [showModal, setShowModal] = useState(false);
const {projects, loading, error, refetch} = useProjects();
@ -23,7 +24,7 @@ const ProjectList = () =>
const[HasManageProject,setHasManageProject] = useState(HasManageProjectPermission)
const dispatch = useDispatch();
const [currentPage, setCurrentPage] = useState(1);
const [itemsPerPage] = useState(5);
const [itemsPerPage] = useState(6);
const handleShow = () => setShowModal(true);
@ -84,7 +85,20 @@ const ProjectList = () =>
? Math.ceil(projectList.length / itemsPerPage)
: 0;
const statusMap = {
1: { label: 'active', priority: 1 },
2: { label: 'hold', priority: 2 },
3: { label: 'inactive', priority: 3 },
4: { label: 'complete', priority: 4 }
};
const sortedProjects = [...currentItems].sort((a, b) => {
const aPriority = statusMap[+a.projectStatusId]?.priority ?? 99;
const bPriority = statusMap[+b.projectStatusId]?.priority ?? 99;
return aPriority - bPriority;
});
return (
<>
<div
@ -118,11 +132,7 @@ const ProjectList = () =>
{" "}
<button
type="button"
className={`btn btn-sm btn-primary ${
HasManageProject
? ""
: "d-none"
}`}
className={`btn btn-xs btn-primary ${!HasManageProject && 'd-none' }`}
data-bs-toggle="modal"
data-bs-target="#create-project-model"
onClick={handleShow}
@ -154,8 +164,10 @@ const ProjectList = () =>
<div className="row">
{loading && <p className="text-center">Loading...</p>}
{currentItems &&
currentItems.sort((a, b) => b.id - a.id).map((item) => (
sortedProjects.map((item) => (
<ProjectCard projectData={item} key={item.id}></ProjectCard>
))}
</div>

View File

@ -1,7 +1,8 @@
import React from "react";
import { ComingSoonPage } from "../Misc/ComingSoonPage";
const Reports = () => {
return <div>Reports</div>;
return <ComingSoonPage></ComingSoonPage>;
};
export default Reports;

View File

@ -1,7 +1,8 @@
import React from "react";
import { ComingSoonPage } from "../Misc/ComingSoonPage";
const Connect = () => {
return <div>Connect</div>;
return <ComingSoonPage></ComingSoonPage>;
};
export default Connect;

View File

@ -1,7 +1,8 @@
import React from "react";
import { ComingSoonPage } from "../Misc/ComingSoonPage";
const Documentation = () => {
return <div>Documentation</div>;
return <ComingSoonPage></ComingSoonPage>;
};
export default Documentation;

View File

@ -1,7 +1,8 @@
import React from "react";
import { ComingSoonPage } from "../Misc/ComingSoonPage";
const Support = () => {
return <div>Support</div>;
return <ComingSoonPage></ComingSoonPage>;
};
export default Support;

View File

@ -5,7 +5,8 @@ const AttendanceRepository = {
markAttendance:(data)=>api.post("/api/attendance/record",data),
getAttendance:(id)=>api.get(`api/attendance/project/team?projectId=${id}`),
getAttendanceFilteredByDate:(id,date)=>api.get(`api/attendance/project/team?projectId=${id+"&date="+date}`),
getAttendanceLogs:(id)=>api.get(`api/attendance/log/attendance/${id}`)
getAttendanceLogs: ( id ) => api.get( `api/attendance/log/attendance/${ id }` ),
getRegularizeList: (id)=> api.get(`api/attendance/regularize?projectId=${id}`)
}

View File

@ -0,0 +1,25 @@
import { api } from "../utils/axiosClient";
const GlobalRepository = {
getDashboardProgressionData: ({ days = '', FromDate = '', projectId = '' }) => {
const params = new URLSearchParams({
days: days.toString(),
FromDate,
projectId,
});
return api.get(`/api/Dashboard/Progression?${params.toString()}`);
},
getDashboardProjectsCardData: () => {
return api.get(`/api/Dashboard/projects`);
},
getDashboardTeamsCardData: () => {
return api.get(`/api/Dashboard/teams`);
},
getDashboardTasksCardData: () => {
return api.get(`/api/Dashboard/tasks`);
},
};
export default GlobalRepository;

View File

@ -27,7 +27,11 @@ export const MasterRespository = {
createJobRole:(data)=>api.post('api/roles/jobrole',data),
getJobRole :()=>api.get("/api/roles/jobrole"),
updateJobRole:(id,data)=>api.put(`/api/roles/jobrole/${id}`,data)
updateJobRole: ( id, data ) => api.put( `/api/roles/jobrole/${ id }`, data ),
getActivites: () => api.get( 'api/master/activities' ),
createActivity: (data) => api.post( 'api/master/activity',data ),
updateActivity:(id,data) =>api.post(`api/master/edit/${id}`,data),
}

View File

@ -13,12 +13,17 @@ const ProjectRepository = {
manageProject: (data) => api.post("/api/project", data),
// updateProject: (data) => api.post("/api/project/update", data),
manageProjectAllocation: (data) => api.post("/api/project/allocation", data),
manageProjectAllocation: ( data ) => api.post( "/api/project/allocation", data ),
manageProjectInfra: (data) => api.post("/api/project/manage-infra", data),
manageProjectTasks: (data) => api.post("/api/project/manage-infra", data),
manageProjectTasks: (data) => api.post("/api/project/task", data),
updateProject: (id, data) => api.put(`/api/project/update/${id}`, data),
deleteProject: (id) => api.delete(`/projects/${id}`),
};
export const TasksRepository = {
assignTask: ( data ) => api.post( "/api/task/assign", data ),
reportTak:(data)=>api.post("/api/task/report",data)
}
export default ProjectRepository;

View File

@ -0,0 +1,19 @@
import { api } from "../utils/axiosClient";
export const TasksRepository = {
getTaskList: (id, fromdate = null, todate = null) => {
let url = `api/task/list?projectId=${id}`;
if (fromdate) {
url += `&dateFrom=${fromdate}`;
}
if (todate) {
url += `&dateTo=${todate}`;
}
return api.get(url);
},
reportTsak: (data) => api.post("api/task/report", data),
taskComments: (data) => api.post("api/task/comment", data),
};

View File

@ -1,5 +1,6 @@
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
import AttendanceRepository from '../../repositories/AttendanceRepository';
import {clearCacheKey} from '../apiDataManager';
// Fetch attendance data
export const fetchAttendanceData = createAsyncThunk(
@ -17,10 +18,11 @@ export const fetchAttendanceData = createAsyncThunk(
// This method for marking attendance if a date filter is applied
export const markAttendance = createAsyncThunk(
'attendanceLogs/markAttendance', // Updated action type prefix
async (formData, thunkAPI) => {
async ( formData, thunkAPI ) =>
{
try {
let newRecordAttendance = {
id: formData.id || null,
Id: formData.id || null,
comment: formData.description,
employeeID: formData.employeeId,
projectID: formData.projectId,
@ -32,7 +34,8 @@ export const markAttendance = createAsyncThunk(
image: null,
};
const response = await AttendanceRepository.markAttendance(newRecordAttendance);
const response = await AttendanceRepository.markAttendance( newRecordAttendance );
clearCacheKey("AttendanceLogs")
return response.data; // Return the newly created or updated record
} catch (error) {
return thunkAPI.rejectWithValue(error.message);

View File

@ -1,19 +1,24 @@
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
import AttendanceRepository from '../../repositories/AttendanceRepository';
import {clearCacheKey} from '../apiDataManager';
export const markCurrentAttendance = createAsyncThunk(
'attendanceCurrentDate/markAttendance',
async (formData, { getState, dispatch, rejectWithValue }) => {
async ( formData, {getState, dispatch, rejectWithValue} ) =>
{
const { projectId } = getState().localVariables
try {
try
{
// Create the new attendance record
const newRecordAttendance = {
id: null,
Id: formData.id || null,
comment: formData.description,
employeeID: formData.employeeId,
projectId: projectId,
date: new Date().toISOString(),
markTime: formData.time,
markTime: formData.markTime,
latitude: formData.latitude.toString(),
longitude: formData.longitude.toString(),
action: formData.action,
@ -22,6 +27,7 @@ export const markCurrentAttendance = createAsyncThunk(
const response = await AttendanceRepository.markAttendance(newRecordAttendance);
const markedAttendance = response.data
clearCacheKey("AttendanceLogs")
return markedAttendance;
} catch (error) {

View File

@ -3,9 +3,9 @@ import { createSlice } from "@reduxjs/toolkit";
const localVariablesSlice = createSlice({
name: "localVariables",
initialState: {
selectedMaster:"Role",
selectedMaster:"Application Role",
regularizationCount:0,
projectId:5,
projectId:1,
},
reducers: {

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