Compare commits
124 Commits
e6a687c8e2
...
bcb273d663
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bcb273d663 | ||
|
|
b73a7fc5ab | ||
| 85cae0a786 | |||
| 8b8a518b12 | |||
| 47bbb38683 | |||
| 82e52e1937 | |||
|
|
7ebd8dcc00 | ||
|
|
ab1754aa49 | ||
| b526e64a65 | |||
| 89df1fd9c0 | |||
| af7001f563 | |||
| 4e654444a8 | |||
| c05f3aa0a1 | |||
| dd697b47b5 | |||
| 9a5bf23712 | |||
| b57b46440d | |||
| 19df5a55ff | |||
|
|
30a9908499 | ||
|
|
d1c36dfd97 | ||
|
|
57272eb894 | ||
|
|
5f094aa2e6 | ||
|
|
6df20de749 | ||
|
|
ee2762fb4a | ||
|
|
97606dfef0 | ||
|
|
23223661f0 | ||
|
|
2d57ab23da | ||
|
|
409c80471a | ||
|
|
a100194508 | ||
|
|
d421355451 | ||
| a39002ccf3 | |||
| 87c110a6af | |||
| d82a788350 | |||
| 4f67d58923 | |||
|
|
777b8d8d0b | ||
|
|
c83161d5ab | ||
|
|
dd40f55b0a | ||
|
|
2a873f5d30 | ||
|
|
e9d3a746aa | ||
| ca592fe9d7 | |||
| 9c47b05d64 | |||
| aa70c5d1da | |||
|
|
63b02db9b3 | ||
|
|
4c2fbf7bc6 | ||
|
|
c459ef678e | ||
|
|
a52b5ef1d2 | ||
|
|
1151c81c99 | ||
|
|
40843489d3 | ||
|
|
ff5a772067 | ||
| b4be5a38ab | |||
| 99a4cb0553 | |||
| a30350916e | |||
| d8a861bb08 | |||
| 2b24351316 | |||
| 895e3da219 | |||
| 02b0c4ccab | |||
| 12aa5c1491 | |||
| 51190b0d53 | |||
|
|
f4a09c7f53 | ||
|
|
ec80121621 | ||
|
|
c2b91b9737 | ||
|
|
a7421eb6dc | ||
|
|
8608043fb2 | ||
|
|
55b9420b6c | ||
|
|
2428f15f35 | ||
|
|
4a5d9c05e6 | ||
|
|
60232cf121 | ||
|
|
9e32986969 | ||
|
|
aa6ddd7fe9 | ||
|
|
c45130b611 | ||
| 67a3648227 | |||
| cb3abe4831 | |||
| c0477285e3 | |||
| d0ab36799f | |||
|
|
d4452ae19a | ||
| 29caa20250 | |||
| 19abc42fc6 | |||
| bc516b58b6 | |||
|
|
308ac5af48 | ||
|
|
05a3b13a0e | ||
|
|
85c2401514 | ||
| 43673cfd37 | |||
| b1fd691f37 | |||
| 1019e9a32f | |||
|
|
0e7088b4d4 | ||
|
|
797ff9b5bc | ||
| 68442bdc7c | |||
| 95f4443338 | |||
|
|
6a6f0356bc | ||
|
|
1483cab13f | ||
|
|
453c53940f | ||
|
|
faf68cac25 | ||
| 1f5d4688bb | |||
|
|
e8aa2ae718 | ||
|
|
57cf750d62 | ||
|
|
d56ba89a4a | ||
| 999ebe1623 | |||
| 9d9569460a | |||
| 079dfb8861 | |||
| d9e1c91a6b | |||
|
|
9c0f98dbaf | ||
| 5559970a05 | |||
| a78c4b90db | |||
| f9c6d1b7f8 | |||
| 147342bfc1 | |||
| c989f23106 | |||
| 14ca1eb54c | |||
| 085176c593 | |||
| cee8f23fd9 | |||
|
|
df989605b9 | ||
| 5995c74ae6 | |||
| 984efe207b | |||
| 8888f9d2d8 | |||
| 66c4e44ded | |||
| f2a77b0685 | |||
| c595930640 | |||
| 6c0b92606a | |||
|
|
9e17b74ba4 | ||
|
|
a28d290feb | ||
|
|
8bce23d9b6 | ||
|
|
2081b13d64 | ||
|
|
93c95e007a | ||
|
|
10d6f96ea7 | ||
|
|
ddfbed1020 | ||
|
|
1451c1aff7 |
31
index.html
31
index.html
@ -27,7 +27,6 @@
|
|||||||
<link rel="stylesheet" href="/assets/css/default.css" />
|
<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/perfect-scrollbar/perfect-scrollbar.css" />
|
||||||
<link rel="stylesheet" href="/assets/vendor/libs/apex-charts/apex-charts.css" />
|
|
||||||
|
|
||||||
<!-- Icons -->
|
<!-- Icons -->
|
||||||
<link rel="stylesheet" href="/assets/vendor/fonts/boxicons.css" />
|
<link rel="stylesheet" href="/assets/vendor/fonts/boxicons.css" />
|
||||||
@ -46,9 +45,11 @@
|
|||||||
<!-- Helpers -->
|
<!-- Helpers -->
|
||||||
<script src="/assets/vendor/js/helpers.js"></script>
|
<script src="/assets/vendor/js/helpers.js"></script>
|
||||||
<script src="/assets/js/config.js"></script>
|
<script src="/assets/js/config.js"></script>
|
||||||
|
|
||||||
<!-- Timer Picker -->
|
<!-- Timer Picker -->
|
||||||
<!-- Flatpickr CSS -->
|
<!-- 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/libs/select2/select2.js"></script>
|
||||||
<script src="/assets/vendor/js/menu.js"></script>
|
<script src="/assets/vendor/js/menu.js"></script>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<!-- endbuild -->
|
<!-- endbuild -->
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<!-- Vendors JS -->
|
<!-- Vendors JS -->
|
||||||
<script src="/assets/vendor/libs/bs-stepper/bs-stepper.js"></script>
|
<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/bootstrap-select/bootstrap-select.js"></script>
|
||||||
<script src="/assets/vendor/libs/select2/select2.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 -->
|
<!-- Main JS -->
|
||||||
<script src="/assets/js/main.js"></script>
|
<script src="/assets/js/main.js"></script>
|
||||||
|
|
||||||
@ -93,26 +93,15 @@
|
|||||||
<script src="/assets/js/dashboards-analytics.js"></script>
|
<script src="/assets/js/dashboards-analytics.js"></script>
|
||||||
|
|
||||||
<!-- component -->
|
<!-- component -->
|
||||||
<script src="./public/js/timppick.js"></script>
|
<!-- <script src="./public/js/timppick.js"></script> -->
|
||||||
|
|
||||||
<script src="/assets/vendor/libs/sweetalert2/sweetalert2.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/@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> -->
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/js/bootstrap.min.js"></script> -->
|
||||||
|
|
||||||
<!-- Flatpickr JS -->
|
<!-- Flatpickr JS -->
|
||||||
<script src="https://cdn.jsdelivr.net/npm/flatpickr"></script>
|
|
||||||
|
|
||||||
<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>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
102
package-lock.json
generated
102
package-lock.json
generated
@ -10,7 +10,9 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@hookform/resolvers": "^3.10.0",
|
"@hookform/resolvers": "^3.10.0",
|
||||||
"@reduxjs/toolkit": "^2.5.0",
|
"@reduxjs/toolkit": "^2.5.0",
|
||||||
|
"@types/web": "^0.0.216",
|
||||||
"@vitejs/plugin-react": "^4.3.4",
|
"@vitejs/plugin-react": "^4.3.4",
|
||||||
|
"apexcharts": "^4.5.0",
|
||||||
"axios": "^1.7.9",
|
"axios": "^1.7.9",
|
||||||
"axios-retry": "^4.5.0",
|
"axios-retry": "^4.5.0",
|
||||||
"dotenv": "^16.4.7",
|
"dotenv": "^16.4.7",
|
||||||
@ -21,6 +23,7 @@
|
|||||||
"moment": "^2.30.1",
|
"moment": "^2.30.1",
|
||||||
"perfect-scrollbar": "^1.5.5",
|
"perfect-scrollbar": "^1.5.5",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
|
"react-apexcharts": "^1.7.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-hook-form": "^7.54.2",
|
"react-hook-form": "^7.54.2",
|
||||||
"react-redux": "^9.2.0",
|
"react-redux": "^9.2.0",
|
||||||
@ -1115,6 +1118,62 @@
|
|||||||
"win32"
|
"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": {
|
"node_modules/@swc/core": {
|
||||||
"version": "1.10.1",
|
"version": "1.10.1",
|
||||||
"resolved": "https://registry.npmjs.org/@swc/core/-/core-1.10.1.tgz",
|
"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",
|
"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=="
|
"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": {
|
"node_modules/@ungap/structured-clone": {
|
||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.1.tgz",
|
||||||
@ -1650,6 +1714,12 @@
|
|||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"peer": true
|
"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": {
|
"node_modules/acorn": {
|
||||||
"version": "8.14.0",
|
"version": "8.14.0",
|
||||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz",
|
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz",
|
||||||
@ -1752,6 +1822,20 @@
|
|||||||
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
"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": {
|
"node_modules/argparse": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
|
||||||
@ -3994,7 +4078,6 @@
|
|||||||
"version": "4.1.1",
|
"version": "4.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||||
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
|
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
|
||||||
"dev": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
@ -4255,7 +4338,6 @@
|
|||||||
"version": "15.8.1",
|
"version": "15.8.1",
|
||||||
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
|
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
|
||||||
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
|
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"loose-envify": "^1.4.0",
|
"loose-envify": "^1.4.0",
|
||||||
"object-assign": "^4.1.1",
|
"object-assign": "^4.1.1",
|
||||||
@ -4317,6 +4399,19 @@
|
|||||||
"node": ">=0.10.0"
|
"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": {
|
"node_modules/react-dom": {
|
||||||
"version": "18.3.1",
|
"version": "18.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
|
||||||
@ -4348,8 +4443,7 @@
|
|||||||
"node_modules/react-is": {
|
"node_modules/react-is": {
|
||||||
"version": "16.13.1",
|
"version": "16.13.1",
|
||||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
|
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"node_modules/react-redux": {
|
"node_modules/react-redux": {
|
||||||
"version": "9.2.0",
|
"version": "9.2.0",
|
||||||
|
|||||||
@ -3,7 +3,6 @@
|
|||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"json-server": "json-server --watch ./src/data/demo.json --port 5000",
|
"json-server": "json-server --watch ./src/data/demo.json --port 5000",
|
||||||
@ -14,7 +13,9 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@hookform/resolvers": "^3.10.0",
|
"@hookform/resolvers": "^3.10.0",
|
||||||
"@reduxjs/toolkit": "^2.5.0",
|
"@reduxjs/toolkit": "^2.5.0",
|
||||||
|
"@types/web": "^0.0.216",
|
||||||
"@vitejs/plugin-react": "^4.3.4",
|
"@vitejs/plugin-react": "^4.3.4",
|
||||||
|
"apexcharts": "^4.5.0",
|
||||||
"axios": "^1.7.9",
|
"axios": "^1.7.9",
|
||||||
"axios-retry": "^4.5.0",
|
"axios-retry": "^4.5.0",
|
||||||
"dotenv": "^16.4.7",
|
"dotenv": "^16.4.7",
|
||||||
@ -25,6 +26,7 @@
|
|||||||
"moment": "^2.30.1",
|
"moment": "^2.30.1",
|
||||||
"perfect-scrollbar": "^1.5.5",
|
"perfect-scrollbar": "^1.5.5",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
|
"react-apexcharts": "^1.7.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-hook-form": "^7.54.2",
|
"react-hook-form": "^7.54.2",
|
||||||
"react-redux": "^9.2.0",
|
"react-redux": "^9.2.0",
|
||||||
|
|||||||
4
public/assets/vendor/css/core.css
vendored
4
public/assets/vendor/css/core.css
vendored
@ -3832,7 +3832,7 @@ fieldset:disabled .btn {
|
|||||||
--bs-dropdown-padding-x: 0;
|
--bs-dropdown-padding-x: 0;
|
||||||
--bs-dropdown-padding-y: 0.5rem;
|
--bs-dropdown-padding-y: 0.5rem;
|
||||||
--bs-dropdown-spacer: 0.125rem;
|
--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-color: var(--bs-body-color);
|
||||||
--bs-dropdown-bg: #fff;
|
--bs-dropdown-bg: #fff;
|
||||||
--bs-dropdown-border-color: #e4e6e8;
|
--bs-dropdown-border-color: #e4e6e8;
|
||||||
@ -35469,7 +35469,7 @@ html:not([dir="rtl"]) .menu-toggle::after {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.menu-vertical .menu-item .menu-link {
|
.menu-vertical .menu-item .menu-link {
|
||||||
font-size: 0.8375rem;
|
font-size: 0.7375rem;
|
||||||
min-height: 1.825rem;
|
min-height: 1.825rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
906
public/assets/vendor/libs/flatpickr/flatpickr.css
vendored
Normal file
906
public/assets/vendor/libs/flatpickr/flatpickr.css
vendored
Normal 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;
|
||||||
|
}
|
||||||
|
|
||||||
144
public/assets/vendor/libs/flatpickr/flatpickr.js
vendored
Normal file
144
public/assets/vendor/libs/flatpickr/flatpickr.js
vendored
Normal file
File diff suppressed because one or more lines are too long
81
src/ModalContext.jsx
Normal file
81
src/ModalContext.jsx
Normal 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",
|
||||||
|
};
|
||||||
@ -1,86 +1,793 @@
|
|||||||
.ui-timepicker-wrapper {
|
.flatpickr-calendar {
|
||||||
max-height: 10rem;
|
background: transparent;
|
||||||
overflow-y: auto;
|
opacity: 0;
|
||||||
margin: 0.125rem 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: #fff;
|
||||||
background-clip: padding-box;
|
-webkit-box-shadow: 1px 0 0 #e6e6e6, -1px 0 0 #e6e6e6, 0 1px 0 #e6e6e6,
|
||||||
outline: none;
|
0 -1px 0 #e6e6e6, 0 3px 13px rgba(0, 0, 0, 0.08);
|
||||||
padding: 0.5rem;
|
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);
|
||||||
}
|
}
|
||||||
|
.flatpickr-calendar.open,
|
||||||
.ui-timepicker-list {
|
.flatpickr-calendar.inline {
|
||||||
list-style: none;
|
opacity: 1;
|
||||||
padding: 0.125rem 0;
|
max-height: 640px;
|
||||||
margin: 0;
|
visibility: visible;
|
||||||
}
|
}
|
||||||
|
.flatpickr-calendar.open {
|
||||||
.ui-timepicker-duration {
|
display: inline-block;
|
||||||
margin-left: 0.25rem;
|
z-index: 99999;
|
||||||
}
|
}
|
||||||
[dir=rtl] .ui-timepicker-duration {
|
.flatpickr-calendar.animate.open {
|
||||||
margin-left: 0;
|
-webkit-animation: fpFadeInDown 300ms cubic-bezier(0.23, 1, 0.32, 1);
|
||||||
margin-right: 0.25rem;
|
animation: fpFadeInDown 300ms cubic-bezier(0.23, 1, 0.32, 1);
|
||||||
}
|
}
|
||||||
|
.flatpickr-calendar.inline {
|
||||||
.ui-timepicker-list li {
|
display: block;
|
||||||
padding: 0.25rem 0.75rem;
|
position: relative;
|
||||||
margin: 0.125rem 0.5625rem;
|
top: 2px;
|
||||||
white-space: nowrap;
|
}
|
||||||
|
.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;
|
cursor: pointer;
|
||||||
list-style: none;
|
position: absolute;
|
||||||
border-radius: var(--bs-border-radius);
|
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 {
|
.flatpickr-months .flatpickr-prev-month.flatpickr-disabled,
|
||||||
background: #fff !important;
|
.flatpickr-months .flatpickr-next-month.flatpickr-disabled {
|
||||||
cursor: default !important;
|
display: none;
|
||||||
}
|
}
|
||||||
|
.flatpickr-months .flatpickr-prev-month i,
|
||||||
.light-style .ui-timepicker-wrapper {
|
.flatpickr-months .flatpickr-next-month i {
|
||||||
z-index: 1091;
|
position: relative;
|
||||||
background: #fff;
|
|
||||||
box-shadow: 0 0.25rem 0.75rem 0 rgba(34, 48, 62, 0.14);
|
|
||||||
border: 0 solid #e4e6e8;
|
|
||||||
border-radius: 0.375rem;
|
|
||||||
}
|
}
|
||||||
.light-style .ui-timepicker-list li {
|
.flatpickr-months .flatpickr-prev-month.flatpickr-prev-month,
|
||||||
color: #384551;
|
.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 {
|
.flatpickr-months .flatpickr-prev-month:hover svg,
|
||||||
background: rgba(34, 48, 62, 0.06);
|
.flatpickr-months .flatpickr-next-month:hover svg {
|
||||||
|
fill: #f64747;
|
||||||
}
|
}
|
||||||
.light-style .ui-timepicker-list li:not(.ui-timepicker-selected) .ui-timepicker-duration {
|
.flatpickr-months .flatpickr-prev-month svg,
|
||||||
color: #a7acb2;
|
.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 {
|
.flatpickr-months .flatpickr-prev-month svg path,
|
||||||
color: #a7acb2;
|
.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,
|
.numInputWrapper {
|
||||||
.light-style .ui-timepicker-list li.ui-timepicker-selected.ui-timepicker-disabled {
|
position: relative;
|
||||||
background: #fff !important;
|
height: auto;
|
||||||
color: #a7acb2 !important;
|
|
||||||
}
|
}
|
||||||
|
.numInputWrapper input,
|
||||||
.dark-style .ui-timepicker-wrapper {
|
.numInputWrapper span {
|
||||||
border: 0 solid #4e4f6c;
|
display: inline-block;
|
||||||
z-index: 1091;
|
|
||||||
background: #2b2c40;
|
|
||||||
box-shadow: 0 0.25rem 0.75rem 0 rgba(20, 20, 29, 0.24);
|
|
||||||
border-radius: 0.375rem;
|
|
||||||
}
|
}
|
||||||
.dark-style .ui-timepicker-list li {
|
.numInputWrapper input {
|
||||||
color: #d5d5e2;
|
width: 100%;
|
||||||
}
|
}
|
||||||
.dark-style .ui-timepicker-list li:hover {
|
.numInputWrapper input::-ms-clear {
|
||||||
background: rgba(230, 230, 241, 0.06);
|
display: none;
|
||||||
}
|
}
|
||||||
.dark-style .ui-timepicker-list li:not(.ui-timepicker-selected) .ui-timepicker-duration {
|
.numInputWrapper input::-webkit-outer-spin-button,
|
||||||
color: #7e7f96;
|
.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 {
|
.numInputWrapper span {
|
||||||
color: #7e7f96;
|
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,
|
.numInputWrapper span:hover {
|
||||||
.dark-style .ui-timepicker-list li.ui-timepicker-selected.ui-timepicker-disabled {
|
background: rgba(0, 0, 0, 0.1);
|
||||||
color: #7e7f96 !important;
|
}
|
||||||
background: #2b2c40 !important;
|
.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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -29,7 +29,7 @@ const AttendLogs = ({ Id }) => {
|
|||||||
<tbody>
|
<tbody>
|
||||||
{logs
|
{logs
|
||||||
.slice()
|
.slice()
|
||||||
.sort((a, b) => new Date(b.activityTime) - new Date(a.activityTime))
|
.sort((a, b) => b.id - a.id)
|
||||||
.map((log, index) => (
|
.map((log, index) => (
|
||||||
<tr key={index}>
|
<tr key={index}>
|
||||||
<td>{convertShortTime(log.activityTime)}</td>
|
<td>{convertShortTime(log.activityTime)}</td>
|
||||||
|
|||||||
@ -38,7 +38,7 @@ const Attendance = ( {attendance, getRole, handleModalData} ) =>
|
|||||||
return checkInB - checkInA; // Sort in descending order of checkInTime
|
return checkInB - checkInA; // Sort in descending order of checkInTime
|
||||||
})
|
})
|
||||||
.map( ( item ) => (
|
.map( ( item ) => (
|
||||||
<tr key={item.id}>
|
<tr key={item.employeeId}>
|
||||||
<td colSpan={2}>
|
<td colSpan={2}>
|
||||||
<div className="d-flex justify-content-start align-items-center">
|
<div className="d-flex justify-content-start align-items-center">
|
||||||
<Avatar
|
<Avatar
|
||||||
@ -47,7 +47,6 @@ const Attendance = ( {attendance, getRole, handleModalData} ) =>
|
|||||||
></Avatar>
|
></Avatar>
|
||||||
<div className="d-flex flex-column">
|
<div className="d-flex flex-column">
|
||||||
<a
|
<a
|
||||||
// href="#"
|
|
||||||
onClick={(e) =>navigate(`/employee/${item.employeeId}?for=attendance`)}
|
onClick={(e) =>navigate(`/employee/${item.employeeId}?for=attendance`)}
|
||||||
className="text-heading text-truncate cursor-pointer"
|
className="text-heading text-truncate cursor-pointer"
|
||||||
>
|
>
|
||||||
@ -60,14 +59,13 @@ const Attendance = ( {attendance, getRole, handleModalData} ) =>
|
|||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td>
|
<td>
|
||||||
<span className="badge bg-label-primary me-1">
|
{item.jobRoleName}
|
||||||
{getRole(item.roleID)}
|
|
||||||
</span>
|
|
||||||
</td>
|
</td>
|
||||||
<td>{item.checkInTime ? convertShortTime(item.checkInTime):"--"}</td>
|
<td>{item.checkInTime ? convertShortTime(item.checkInTime):"--"}</td>
|
||||||
<td>{item.checkOutTime ? convertShortTime(item.checkOutTime):"--"}</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}/>
|
<RenderAttendanceStatus attendanceData={item} handleModalData={handleModalData} Tab={1} currentDate={null}/>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import AttendLogs from './AttendLogs'
|
|||||||
import Confirmation from './Confirmation'
|
import Confirmation from './Confirmation'
|
||||||
|
|
||||||
const AttendanceModel = ({modelConfig,closeModal,handleSubmitForm}) => {
|
const AttendanceModel = ({modelConfig,closeModal,handleSubmitForm}) => {
|
||||||
console.log(modelConfig)
|
|
||||||
return (
|
return (
|
||||||
<div className={`modal-dialog modal-md modal-simple ${modelConfig.type === "view" ? "modal-lg":"modal-md"}`} >
|
<div className={`modal-dialog modal-md modal-simple ${modelConfig.type === "view" ? "modal-lg":"modal-md"}`} >
|
||||||
<div className="modal-content">
|
<div className="modal-content">
|
||||||
|
|||||||
@ -45,12 +45,13 @@ const AttendanceLog = ({ attendance, handleModalData, projectId }) => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="table-responsive">
|
<div className="table-responsive text-nowrap">
|
||||||
{attendance && attendance.length > 0 ? (
|
{attendance && attendance.length > 0 ? (
|
||||||
<table className="table mb-0">
|
<table className="table mb-0">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Name</th>
|
<th className="border-top-1" colSpan={2}>Name</th>
|
||||||
|
<th className="border-top-1" >Role</th>
|
||||||
<th>
|
<th>
|
||||||
<i className="bx bxs-down-arrow-alt text-success"></i> Check-In
|
<i className="bx bxs-down-arrow-alt text-success"></i> Check-In
|
||||||
</th>
|
</th>
|
||||||
@ -69,7 +70,7 @@ const AttendanceLog = ({ attendance, handleModalData, projectId }) => {
|
|||||||
|
|
||||||
{renderAttendanceData?.map((attendance, index) => (
|
{renderAttendanceData?.map((attendance, index) => (
|
||||||
<tr key={index}>
|
<tr key={index}>
|
||||||
<td>
|
<td colSpan={2}>
|
||||||
<div className="d-flex justify-content-start align-items-center">
|
<div className="d-flex justify-content-start align-items-center">
|
||||||
<Avatar firstName={attendance.firstName} lastName={attendance.lastName} />
|
<Avatar firstName={attendance.firstName} lastName={attendance.lastName} />
|
||||||
<div className="d-flex flex-column">
|
<div className="d-flex flex-column">
|
||||||
@ -81,6 +82,7 @@ const AttendanceLog = ({ attendance, handleModalData, projectId }) => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
|
<td>{ attendance.jobRoleName}</td>
|
||||||
<td>{convertShortTime(attendance.checkInTime)}</td>
|
<td>{convertShortTime(attendance.checkInTime)}</td>
|
||||||
<td>{attendance.checkOutTime ? convertShortTime(attendance.checkOutTime) : '--'}</td>
|
<td>{attendance.checkOutTime ? convertShortTime(attendance.checkOutTime) : '--'}</td>
|
||||||
<td className="text-center">
|
<td className="text-center">
|
||||||
|
|||||||
@ -11,7 +11,7 @@ import {checkIfCurrentDate} from "../../utils/dateUtils";
|
|||||||
|
|
||||||
|
|
||||||
const schema = z.object({
|
const schema = z.object({
|
||||||
time: z.string().nonempty({message:"Time is required"}),
|
markTime: z.string().nonempty({message:"Time is required"}),
|
||||||
description:z.string().optional()
|
description:z.string().optional()
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -35,18 +35,18 @@ const CheckCheckOutmodel = ({modeldata,closeModal,handleSubmitForm,}) => {
|
|||||||
const onSubmit = ( data ) =>
|
const onSubmit = ( data ) =>
|
||||||
{
|
{
|
||||||
console.log(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){
|
if(modeldata.forWhichTab === 1){
|
||||||
handleSubmitForm(record)
|
handleSubmitForm(record)
|
||||||
} else
|
} else
|
||||||
{
|
{
|
||||||
console.log("is Date" ,checkIfCurrentDate(modeldata?.currentDate))
|
|
||||||
if ( modeldata?.currentDate && checkIfCurrentDate(modeldata?.currentDate) )
|
if ( modeldata?.currentDate && checkIfCurrentDate(modeldata?.currentDate) )
|
||||||
{
|
{
|
||||||
handleSubmitForm(record)
|
handleSubmitForm(record)
|
||||||
} else
|
} 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))
|
dispatch(markAttendance(formData))
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.then(() => {
|
.then(() => {
|
||||||
@ -67,14 +67,12 @@ const CheckCheckOutmodel = ({modeldata,closeModal,handleSubmitForm,}) => {
|
|||||||
<form className="row g-2" onSubmit={handleSubmit(onSubmit)}>
|
<form className="row g-2" onSubmit={handleSubmit(onSubmit)}>
|
||||||
|
|
||||||
<div className="col-12 col-md-12">
|
<div className="col-12 col-md-12">
|
||||||
{/* <TimePicker label="Time" onChange={(e) => setValue("time", e)} /> */}
|
|
||||||
|
|
||||||
<TimePicker
|
<TimePicker
|
||||||
label="Choose a time"
|
label="Choose a time"
|
||||||
onChange={(e) => setValue("time", e)}
|
onChange={(e) => setValue("markTime", e)}
|
||||||
interval={10}
|
interval={10}
|
||||||
/>
|
/>
|
||||||
{errors.time && <p className="text-danger">{errors.time.message}</p>}
|
{errors. markTime && <p className="text-danger">{errors.markTime.message}</p>}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
@ -123,7 +121,7 @@ const schemaReg = z.object({
|
|||||||
export const Regularization = ({modeldata,closeModal,handleSubmitForm})=>{
|
export const Regularization = ({modeldata,closeModal,handleSubmitForm})=>{
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const coords = usePositionTracker();
|
const coords = usePositionTracker();
|
||||||
console.log(modeldata)
|
|
||||||
const {
|
const {
|
||||||
register,
|
register,
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
@ -138,10 +136,12 @@ export const Regularization = ({modeldata,closeModal,handleSubmitForm})=>{
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
const onSubmit = (data) => {
|
const onSubmit = ( data ) =>
|
||||||
let record = {...data, date: new Date().toLocaleDateString(),latitude:coords.latitude,longitude:coords.longitude}
|
{
|
||||||
|
|
||||||
|
let record = {...data, date: new Date().toLocaleDateString(),latitude:coords.latitude,longitude:coords.longitude, }
|
||||||
|
|
||||||
|
console.log(record)
|
||||||
handleSubmitForm(record)
|
handleSubmitForm(record)
|
||||||
closeModal()
|
closeModal()
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,567 +1,114 @@
|
|||||||
|
|
||||||
import React, { useState, useEffect } from "react";
|
import React, { useState, useEffect } from "react";
|
||||||
import "../../components/Project/ProjectInfra.css";
|
import "../../components/Project/ProjectInfra.css";
|
||||||
import BuildingModel from "../../components/Project/BuildingModel";
|
import BuildingModel from "../Project/Infrastructure/BuildingModel";
|
||||||
import FloorModel from "../../components/Project/FloorModel";
|
import FloorModel from "../Project/Infrastructure/FloorModel";
|
||||||
import showToast from "../../services/toastService";
|
import showToast from "../../services/toastService";
|
||||||
import WorkAreaModel from "../../components/Project/WorkAreaModel";
|
import WorkAreaModel from "../Project/Infrastructure/WorkAreaModel";
|
||||||
import TaskModel from "../../components/Project/TaskModel";
|
import TaskModel from "../Project/Infrastructure/TaskModel";
|
||||||
import ProjectRepository from "../../repositories/ProjectRepository";
|
import ProjectRepository from "../../repositories/ProjectRepository";
|
||||||
import Breadcrumb from "../../components/common/Breadcrumb";
|
import Breadcrumb from "../../components/common/Breadcrumb";
|
||||||
|
import {useProjectDetails, 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 InfraPlanning = () =>
|
||||||
const [expandedBuildings, setExpandedBuildings] = useState([]);
|
{
|
||||||
const [project, setProject] = useState(data);
|
const {profile: LoggedUser} = useProfile()
|
||||||
const [buildings, setBuildings] = useState(data?.buildings);
|
const dispatch = useDispatch()
|
||||||
|
const {projects,loading:project_listLoader,error:projects_error} = useProjects()
|
||||||
|
|
||||||
const [isBuildingModalOpen, setIsBuildingModalOpen] = useState(false);
|
const selectedProject = useSelector((store)=>store.localVariables.projectId)
|
||||||
const [isFloorModalOpen, setIsFloorModalOpen] = useState(false);
|
const ManageInfra = useHasUserPermission( MANAGE_PROJECT_INFRA )
|
||||||
const [isWorkAreaModelOpen, setIsWorkAreaModalOpen] = useState(false);
|
const {projects_Details, loading: project_deatilsLoader, error: project_error} = useProjectDetails(selectedProject)
|
||||||
const [isTaskModelOpen, setIsTaskModalOpen] = useState(false);
|
useEffect( () =>
|
||||||
|
{
|
||||||
const [isCreateModalOpen, setIsCreateModalOpen] = useState(false);
|
dispatch(setProjectId(projects[0]?.id))
|
||||||
const [clearFormTrigger, setClearFormTrigger] = useState(false);
|
},[projects])
|
||||||
|
|
||||||
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} {" "}
|
|
||||||
</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} </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 + "%";
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<div className="col-md-12 col-lg-12 col-xl-12 order-0 mb-4">
|
||||||
{isBuildingModalOpen && (
|
<div className="card">
|
||||||
<div
|
<div className="card-body" style={{ padding: "0.5rem" }}>
|
||||||
className={`modal fade `}
|
<div className="align-items-center">
|
||||||
id="building-model"
|
<div className="row ">
|
||||||
tabIndex="-1"
|
<div className="col-sm-3 col-8 text-start mb-1">
|
||||||
aria-hidden="true"
|
<select name="DataTables_Table_0_length"
|
||||||
>
|
aria-controls="DataTables_Table_0"
|
||||||
<BuildingModel
|
className="form-select form-select-sm"
|
||||||
project={data}
|
value={selectedProject}
|
||||||
onClose={closeBuildingModel}
|
onChange={(e)=>dispatch(setProjectId(e.target.value))}
|
||||||
onSubmit={handleBuildingModelFormSubmit}
|
aria-label=""
|
||||||
clearTrigger={clearFormTrigger}
|
>
|
||||||
onClearComplete={() => setClearFormTrigger(false)}
|
{(project_listLoader || projects.length < 0) && <option value="Loading..." disabled>Loading...</option> }
|
||||||
></BuildingModel>
|
|
||||||
</div>
|
{!project_listLoader && projects?.filter(project =>
|
||||||
)}
|
LoggedUser?.projects?.map(Number).includes(project.id)).map((project)=>(
|
||||||
|
<option key={project.id} value={project.id}>{project.name}</option>
|
||||||
{isFloorModalOpen && (
|
))}
|
||||||
<div
|
</select>
|
||||||
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>
|
</div>
|
||||||
<div className="row">
|
{/* <div className={`col-12 text-end mb-1 ${!ManageInfra && 'd-none'} `} >
|
||||||
<div className="col-12">
|
<button
|
||||||
{buildings && buildings.length > 0 && (
|
type="button"
|
||||||
<table className="table table-bordered">
|
className="link-button link-button-sm m-1 "
|
||||||
<tbody>
|
data-bs-toggle="modal"
|
||||||
{buildings.map((building) => (
|
data-bs-target="#building-model"
|
||||||
building.floors && building.floors.length > 0 && (
|
// onClick={() => openBuildingModel()}
|
||||||
<React.Fragment key={building.id}>
|
>
|
||||||
<tr>
|
<i className="bx bx-plus-circle me-2"></i>
|
||||||
<td
|
Manage Building
|
||||||
colSpan="4"
|
</button>
|
||||||
className="text-start"
|
<button
|
||||||
style={{
|
type="button"
|
||||||
background: "#f0f0f0",
|
data-bs-toggle="modal"
|
||||||
cursor: "pointer",
|
className="link-button m-1"
|
||||||
}}
|
data-bs-target="#floor-model"
|
||||||
onClick={() => toggleBuilding(building.id)}
|
// onClick={() => openFloorModel()}
|
||||||
>
|
>
|
||||||
<div className="row">
|
<i className="bx bx-plus-circle me-2"></i>
|
||||||
<h5 style={{ marginBottom: "0px" }}>
|
Manage Floors
|
||||||
|
</button>
|
||||||
{ building.name}
|
|
||||||
{expandedBuildings.includes(building.id) ? (
|
<button
|
||||||
<i className="bx bx-chevron-down"></i>
|
type="button"
|
||||||
) : (
|
data-bs-toggle="modal"
|
||||||
<i className="bx bx-chevron-right"></i>
|
className="link-button m-1"
|
||||||
)}
|
data-bs-target="#work-area-model"
|
||||||
</h5>
|
// onClick={() => openWorkAreaModel()}
|
||||||
</div>
|
>
|
||||||
</td>
|
<i className="bx bx-plus-circle me-2"></i>
|
||||||
</tr>
|
Manage Work Areas
|
||||||
|
</button>
|
||||||
{expandedBuildings.includes(building.id) && getContent(building)}
|
<button
|
||||||
|
type="button"
|
||||||
</React.Fragment>
|
data-bs-toggle="modal"
|
||||||
)
|
className="link-button m-1"
|
||||||
))}
|
data-bs-target="#task-model"
|
||||||
</tbody>
|
// onClick={() => openTaskModel()}
|
||||||
</table>
|
>
|
||||||
)}
|
<i className="bx bx-plus-circle me-2"></i>
|
||||||
</div>
|
Manage Tasks
|
||||||
</div>
|
</button>
|
||||||
</div>
|
</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>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -2,35 +2,43 @@ import React, { useEffect, useState } from 'react';
|
|||||||
import Avatar from '../common/Avatar';
|
import Avatar from '../common/Avatar';
|
||||||
import { convertShortTime } from '../../utils/dateUtils';
|
import { convertShortTime } from '../../utils/dateUtils';
|
||||||
import RegularizationActions from './RegularizationActions';
|
import RegularizationActions from './RegularizationActions';
|
||||||
|
import {useSelector} from 'react-redux';
|
||||||
|
import {useRegularizationRequests} from '../../hooks/useAttendance';
|
||||||
|
import moment from 'moment';
|
||||||
|
|
||||||
|
|
||||||
const Regularization = ({attendance,handleRequest}) => {
|
const Regularization = ( { handleRequest} ) =>
|
||||||
const[attendances,setAttendances] = useState(attendance)
|
{
|
||||||
const regularize_Requests = attendances.filter((att)=>att.activity === 2)
|
var selectedProject = useSelector((store) => store.localVariables.projectId);
|
||||||
|
const [ regularizesList, setregularizedList ] = useState( [] )
|
||||||
|
const { regularizes, loading,error,refetch} = useRegularizationRequests(selectedProject)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
useEffect(()=>{
|
useEffect(()=>{
|
||||||
setAttendances(attendance)
|
setregularizedList(regularizes)
|
||||||
},[attendance])
|
},[regularizes])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="table-responsive">
|
<div className="table-responsive text-nowrap">
|
||||||
<table className="table mb-0">
|
<table className="table mb-0">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Name</th>
|
<th colSpan={2}>Name</th>
|
||||||
<th>Date</th>
|
<th>Date</th>
|
||||||
<th><i className='bx bxs-down-arrow-alt text-success' ></i>Check-In</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><i className='bx bxs-up-arrow-alt text-danger' ></i>Check-Out</th>
|
||||||
<th>Action</th>
|
<th>Action</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
|
|
||||||
|
{loading &&<td colSpan={5}>Loading...</td>}
|
||||||
{
|
{
|
||||||
regularize_Requests.length > 0 ? (
|
regularizes?.length > 0 ? (
|
||||||
regularize_Requests?.map((att, index) => (
|
regularizes?.map((att, index) => (
|
||||||
<tr key={index}>
|
<tr key={index}>
|
||||||
<td>
|
<td colSpan={2}>
|
||||||
<div className="d-flex justify-content-start align-items-center">
|
<div className="d-flex justify-content-start align-items-center">
|
||||||
<Avatar
|
<Avatar
|
||||||
firstName={att.firstName}
|
firstName={att.firstName}
|
||||||
@ -48,18 +56,18 @@ const Regularization = ({attendance,handleRequest}) => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td>{att.date}</td>
|
<td>{moment(att.checkOutTime).format("DD-MMM-YYYY")}</td>
|
||||||
<td>{convertShortTime(att.checkInTime)}</td>
|
<td>{convertShortTime(att.checkInTime)}</td>
|
||||||
<td>{att.checkOutTime ? convertShortTime(att.checkOutTime):"--"}</td>
|
<td>{att.checkOutTime ? convertShortTime(att.checkOutTime):"--"}</td>
|
||||||
<td className='text-center ' >
|
<td className='text-center ' >
|
||||||
{/* <div className='d-flex justify-content-center align-items-center gap-3'> */}
|
{/* <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> */}
|
{/* </div> */}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
))
|
))
|
||||||
):(
|
):(
|
||||||
<td colSpan={5}>No Result Found</td>
|
<td colSpan={5}>No Record Found</td>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|||||||
@ -1,21 +1,24 @@
|
|||||||
import React, { act, useEffect, useState } from 'react'
|
import React, { act, useEffect, useState } from 'react'
|
||||||
import useAttendanceStatus, { ACTIONS } from '../../hooks/useAttendanceStatus';
|
import useAttendanceStatus, { ACTIONS } from '../../hooks/useAttendanceStatus';
|
||||||
// import AttendanceRepository from '../../repositories/AttendanceRepository';
|
// import AttendanceRepository from '../../repositories/AttendanceRepository';
|
||||||
import { useSelector } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
import { usePositionTracker } from '../../hooks/usePositionTracker';
|
import { usePositionTracker } from '../../hooks/usePositionTracker';
|
||||||
|
import {markCurrentAttendance} from '../../slices/apiSlice/attendanceAllSlice';
|
||||||
|
import {cacheData, getCachedData} from '../../slices/apiDataManager';
|
||||||
|
import showToast from '../../services/toastService';
|
||||||
|
|
||||||
|
|
||||||
const RegularizationActions = ({attendanceData,handleRequest}) => {
|
const RegularizationActions = ({attendanceData,handleRequest,refresh}) => {
|
||||||
const [status,setStatus] = useState()
|
const [status,setStatus] = useState()
|
||||||
const [loadingApprove,setLoadingForApprove] = useState(false)
|
const [loadingApprove,setLoadingForApprove] = useState(false)
|
||||||
const [loadingReject,setLoadingForReject] = useState(false)
|
const [loadingReject,setLoadingForReject] = useState(false)
|
||||||
|
|
||||||
const projectId = useSelector((store)=>store.localVariables.projectId)
|
const projectId = useSelector((store)=>store.localVariables.projectId)
|
||||||
const {latitude,longitude} = usePositionTracker();
|
const {latitude,longitude} = usePositionTracker();
|
||||||
|
const dispatch = useDispatch()
|
||||||
|
|
||||||
const handleRegularization =(request_attendance,IsReqularize)=>{
|
const handleRegularization =(request_attendance,IsReqularize)=>{
|
||||||
|
|
||||||
if(IsReqularize){
|
if(IsReqularize){
|
||||||
setLoadingForApprove(true)
|
setLoadingForApprove(true)
|
||||||
}else{
|
}else{
|
||||||
@ -34,8 +37,22 @@ const {latitude,longitude} = usePositionTracker();
|
|||||||
action:IsReqularize ? 4 : 5,
|
action:IsReqularize ? 4 : 5,
|
||||||
image:null
|
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 (
|
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 && (
|
{attendanceData.activity == 2 && (
|
||||||
<>
|
<>
|
||||||
<button
|
<button
|
||||||
|
|||||||
@ -9,7 +9,7 @@ const RenderAttendanceStatus = ({ attendanceData, handleModalData,Tab,currentDat
|
|||||||
const handleButtonClick = (key) => {
|
const handleButtonClick = (key) => {
|
||||||
|
|
||||||
if(key === 6){
|
if(key === 6){
|
||||||
handleModalData({action:6,id :attendanceData?.id})
|
handleModalData({action:6,id:attendanceData?.id})
|
||||||
}else{
|
}else{
|
||||||
|
|
||||||
handleModalData({
|
handleModalData({
|
||||||
@ -22,8 +22,9 @@ const RenderAttendanceStatus = ({ attendanceData, handleModalData,Tab,currentDat
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-auto d-flex gap-2 align-items-center justify-content-between">
|
<div className="d-flex justify-content-end">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className={`btn btn-xs ${color}`}
|
className={`btn btn-xs ${color}`}
|
||||||
@ -39,7 +40,7 @@ const RenderAttendanceStatus = ({ attendanceData, handleModalData,Tab,currentDat
|
|||||||
{attendanceData?.checkInTime && (
|
{attendanceData?.checkInTime && (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="btn btn-xs btn-success"
|
className="btn btn-xs btn-success ms-2"
|
||||||
tabIndex="0"
|
tabIndex="0"
|
||||||
aria-controls="DataTables_Table_0"
|
aria-controls="DataTables_Table_0"
|
||||||
data-bs-toggle="modal"
|
data-bs-toggle="modal"
|
||||||
|
|||||||
174
src/components/Activities/ReportTask.jsx
Normal file
174
src/components/Activities/ReportTask.jsx
Normal 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>
|
||||||
|
);
|
||||||
|
};
|
||||||
155
src/components/Activities/ReportTaskComments.jsx
Normal file
155
src/components/Activities/ReportTaskComments.jsx
Normal 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 HTML’s 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 HTML’s 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;
|
||||||
134
src/components/Charts/HorizontalBarChart.jsx
Normal file
134
src/components/Charts/HorizontalBarChart.jsx
Normal 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;
|
||||||
144
src/components/Charts/LineChart.jsx
Normal file
144
src/components/Charts/LineChart.jsx
Normal 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;
|
||||||
271
src/components/Dashboard/Dashboard.jsx
Normal file
271
src/components/Dashboard/Dashboard.jsx
Normal 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;
|
||||||
@ -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;
|
|
||||||
@ -1,135 +1,172 @@
|
|||||||
import React from 'react'
|
import React from "react";
|
||||||
|
|
||||||
const DemoTable = () => {
|
const DemoTable = () => {
|
||||||
return (
|
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">
|
<hr className="my-12" />
|
||||||
<div class="card">
|
|
||||||
<div class="card-datatable table-responsive">
|
<hr className="my-12" />
|
||||||
<table class="datatables-basic table border-top">
|
|
||||||
<thead>
|
<hr className="my-12" />
|
||||||
<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>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<hr class="my-12" />
|
<div className="content-backdrop fade"></div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DemoTable;
|
||||||
|
|
||||||
<hr class="my-12" />
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<hr class="my-12" />
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="content-backdrop fade"></div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default DemoTable
|
|
||||||
|
|||||||
@ -11,7 +11,7 @@ import { Link, useNavigate, useParams } from "react-router-dom";
|
|||||||
import { formatDate } from "../../utils/dateUtils";
|
import { formatDate } from "../../utils/dateUtils";
|
||||||
import { useEmployeeProfile } from "../../hooks/useEmployees";
|
import { useEmployeeProfile } from "../../hooks/useEmployees";
|
||||||
import { clearCacheKey, getCachedData } from "../../slices/apiDataManager";
|
import { clearCacheKey, getCachedData } from "../../slices/apiDataManager";
|
||||||
import {clearApiCacheKey} from "../../slices/apiCacheSlice";
|
import { clearApiCacheKey } from "../../slices/apiCacheSlice";
|
||||||
|
|
||||||
const mobileNumberRegex = /^[7-9]\d{9}$/;
|
const mobileNumberRegex = /^[7-9]\d{9}$/;
|
||||||
|
|
||||||
@ -24,66 +24,101 @@ const ManageEmployee = () => {
|
|||||||
error,
|
error,
|
||||||
loading: empLoading,
|
loading: empLoading,
|
||||||
} = useEmployeeProfile(employeeId);
|
} = useEmployeeProfile(employeeId);
|
||||||
dispatch( changeMaster( "Job Role" ) );
|
dispatch(changeMaster("Job Role"));
|
||||||
const [disabledEmail,setDisabledEmail] = useState(false)
|
const [disabledEmail, setDisabledEmail] = useState(false);
|
||||||
const { data: job_role, loading } = useMaster();
|
const { data: job_role, loading } = useMaster();
|
||||||
const [isloading, setLoading] = useState(false);
|
const [isloading, setLoading] = useState(false);
|
||||||
const navigation = useNavigate();
|
const navigation = useNavigate();
|
||||||
const [currentEmployee, setCurrentEmployee] = useState();
|
const [currentEmployee, setCurrentEmployee] = useState();
|
||||||
|
|
||||||
|
const userSchema = z.object({
|
||||||
const userSchema = z
|
...(employeeId ? { Id: z.number().optional() } : {}),
|
||||||
.object({
|
FirstName: z.string().min(1, { message: "First Name is required" }),
|
||||||
...(employeeId ? { Id: z.number().optional() } : {}),
|
MiddleName: z.string().optional(),
|
||||||
FirstName: z.string().min(1, { message: "First Name is required" }),
|
LastName: z.string().min(1, { message: "Last Name is required" }),
|
||||||
MiddleName: z.string().optional(),
|
Email: z
|
||||||
LastName: z.string().min(1, { message: "Last Name is required" }),
|
.string()
|
||||||
Email: z.string().optional(),
|
.optional()
|
||||||
CurrentAddress: z
|
.refine(
|
||||||
.string()
|
(val) =>
|
||||||
.min(1, { message: "Current Address is required" }).max(150, { message: "Address cannot exceed 150 characters" }),
|
!val || /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(val),
|
||||||
BirthDate: z.string().min(1, { message: "Birth Date is required" }).refine((date, ctx) => {
|
{
|
||||||
return new Date(date) <= new Date();
|
message: "Invalid email format",
|
||||||
}, {
|
}
|
||||||
message: "Birth date cannot be in the future",
|
)
|
||||||
}),
|
.refine(
|
||||||
JoiningDate: z.string().min(1, { message: "Joining Date is required" }).refine((date, ctx) => {
|
(val) => {
|
||||||
return new Date(date) <= new Date();
|
if (!val) return true;
|
||||||
}, {
|
const [local, domain] = val.split("@");
|
||||||
message: "Joining date cannot be in the future",
|
return (
|
||||||
}),
|
val.length <= 320 &&
|
||||||
EmergencyPhoneNumber: z
|
local?.length <= 64 &&
|
||||||
.string()
|
domain?.length <= 255
|
||||||
.min(1, { message: "Phone Number is required" })
|
);
|
||||||
.regex(mobileNumberRegex, { message: "Invalid phone number " }),
|
},
|
||||||
EmergencyContactPerson: z
|
{
|
||||||
.string()
|
message: "Email local or domain part is too long",
|
||||||
.min(1, { message: "Emergency Contact Person is required" }),
|
}
|
||||||
AadharNumber: z.string()
|
),
|
||||||
.regex(/^\d{12}$/, "Aadhar card must be exactly 12 digits 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"),
|
.nonempty("Aadhar card is required"),
|
||||||
Gender: z
|
Gender: z
|
||||||
.string()
|
.string()
|
||||||
.min(1, { message: "Gender is required" })
|
.min(1, { message: "Gender is required" })
|
||||||
.refine((val) => val !== "Select Gender", {
|
.refine((val) => val !== "Select Gender", {
|
||||||
message: "Please select a gender",
|
message: "Please select a gender",
|
||||||
}),
|
}),
|
||||||
PanNumber: z
|
PanNumber: z
|
||||||
.string()
|
.string()
|
||||||
.optional()
|
.optional()
|
||||||
.refine((val) => !val || /^[A-Z]{5}[0-9]{4}[A-Z]{1}$/.test(val), {
|
.refine((val) => !val || /^[A-Z]{5}[0-9]{4}[A-Z]{1}$/.test(val), {
|
||||||
message: "Invalid PAN number",
|
message: "Invalid PAN number",
|
||||||
}),
|
}),
|
||||||
PeramnentAddress: z
|
PermanentAddress: z
|
||||||
.string()
|
.string()
|
||||||
.min(1, { message: "Permanent Address is required" }).max(150, { message: "Address cannot exceed 150 characters" }),
|
.min(1, { message: "Permanent Address is required" })
|
||||||
PhoneNumber: z
|
.max(500, { message: "Address cannot exceed 250 characters" }),
|
||||||
.string()
|
PhoneNumber: z
|
||||||
.min(1, { message: "Phone Number is required" })
|
.string()
|
||||||
.regex(mobileNumberRegex, { message: "Invalid phone number " }),
|
.min(1, { message: "Phone Number is required" })
|
||||||
JobRoleId: z.string().min(1, { message: "Role is required" }),
|
.regex(mobileNumberRegex, { message: "Invalid phone number " }),
|
||||||
})
|
JobRoleId: z.string().min(1, { message: "Role is required" }),
|
||||||
|
});
|
||||||
|
|
||||||
const {
|
const {
|
||||||
register,
|
register,
|
||||||
@ -108,7 +143,7 @@ const ManageEmployee = () => {
|
|||||||
AadharNumber: currentEmployee?.aadharNumber || "",
|
AadharNumber: currentEmployee?.aadharNumber || "",
|
||||||
Gender: currentEmployee?.gender || "",
|
Gender: currentEmployee?.gender || "",
|
||||||
PanNumber: currentEmployee?.panNumber || "",
|
PanNumber: currentEmployee?.panNumber || "",
|
||||||
PeramnentAddress: currentEmployee?.peramnentAddress || "",
|
PermanentAddress: currentEmployee?.permanentAddress || "",
|
||||||
PhoneNumber: currentEmployee?.phoneNumber || "",
|
PhoneNumber: currentEmployee?.phoneNumber || "",
|
||||||
JobRoleId: currentEmployee?.jobRoleId || "",
|
JobRoleId: currentEmployee?.jobRoleId || "",
|
||||||
},
|
},
|
||||||
@ -116,7 +151,7 @@ const ManageEmployee = () => {
|
|||||||
|
|
||||||
const onSubmit = (data) => {
|
const onSubmit = (data) => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
|
||||||
const formData = getValues();
|
const formData = getValues();
|
||||||
|
|
||||||
const formDataToSend = new FormData();
|
const formDataToSend = new FormData();
|
||||||
@ -140,11 +175,10 @@ const ManageEmployee = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
EmployeeRepository.manageEmployee(formDataToSend)
|
EmployeeRepository.manageEmployee(formDataToSend)
|
||||||
.then( ( response ) =>
|
.then((response) => {
|
||||||
{
|
showToast("Employee details updated successfully.", "success");
|
||||||
showToast("Employee details updated successfully.", "success" );
|
clearCacheKey("employeeListByProject");
|
||||||
clearCacheKey("employeeListByProject")
|
clearCacheKey("allEmployeeList");
|
||||||
clearCacheKey( "allEmployeeList" )
|
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
navigation("/employees");
|
navigation("/employees");
|
||||||
})
|
})
|
||||||
@ -180,7 +214,7 @@ const ManageEmployee = () => {
|
|||||||
AadharNumber: currentEmployee.aadharNumber || "",
|
AadharNumber: currentEmployee.aadharNumber || "",
|
||||||
Gender: currentEmployee.gender || "",
|
Gender: currentEmployee.gender || "",
|
||||||
PanNumber: currentEmployee.panNumber || "",
|
PanNumber: currentEmployee.panNumber || "",
|
||||||
PeramnentAddress: currentEmployee.peramnentAddress || "",
|
PermanentAddress: currentEmployee.permanentAddress || "",
|
||||||
PhoneNumber: currentEmployee.phoneNumber || "",
|
PhoneNumber: currentEmployee.phoneNumber || "",
|
||||||
JobRoleId: currentEmployee.jobRoleId?.toString() || "",
|
JobRoleId: currentEmployee.jobRoleId?.toString() || "",
|
||||||
}
|
}
|
||||||
@ -197,11 +231,19 @@ const ManageEmployee = () => {
|
|||||||
<h6 className="mb-0">
|
<h6 className="mb-0">
|
||||||
{employee ? "Update Employee" : "Create Employee"}
|
{employee ? "Update Employee" : "Create Employee"}
|
||||||
</h6>
|
</h6>
|
||||||
|
|
||||||
<span className="cursor-pointer fs-6" data-htm="true" data-bs-toggle="tooltip"
|
<span
|
||||||
data-bs-offset="0,6"
|
className="cursor-pointer fs-6"
|
||||||
data-bs-placement="top"
|
data-htm="true"
|
||||||
data-bs-html="true" title="Move Back" onClick={()=>navigation("/employees")}><i class='bx bxs-chevron-left'></i> Back</span>
|
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>
|
||||||
<div className="card-body">
|
<div className="card-body">
|
||||||
{!currentEmployee && empLoading && (
|
{!currentEmployee && empLoading && (
|
||||||
@ -289,8 +331,6 @@ const ManageEmployee = () => {
|
|||||||
{errors.Email.message}
|
{errors.Email.message}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div className="col-sm-6">
|
<div className="col-sm-6">
|
||||||
<div className="form-text text-start">Phone Number</div>
|
<div className="form-text text-start">Phone Number</div>
|
||||||
@ -404,22 +444,22 @@ const ManageEmployee = () => {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="col-sm-6">
|
<div className="col-sm-6">
|
||||||
<div className="form-text text-start">Permnant Address</div>
|
<div className="form-text text-start">Permanent Address</div>
|
||||||
|
|
||||||
<textarea
|
<textarea
|
||||||
id="PeramnentAddress"
|
id="PermanentAddress"
|
||||||
className="form-control form-control-sm"
|
className="form-control form-control-sm"
|
||||||
placeholder="Permnant Address"
|
placeholder="Permanent Address"
|
||||||
aria-label="Permnant Address"
|
aria-label="Permanent Address"
|
||||||
aria-describedby="basic-icon-default-message2"
|
aria-describedby="basic-icon-default-message2"
|
||||||
{...register("PeramnentAddress")}
|
{...register("PermanentAddress")}
|
||||||
></textarea>
|
></textarea>
|
||||||
{errors.PeramnentAddress && (
|
{errors.PermanentAddress && (
|
||||||
<div
|
<div
|
||||||
className="danger-text text-start"
|
className="danger-text text-start"
|
||||||
style={{ fontSize: "12px" }}
|
style={{ fontSize: "12px" }}
|
||||||
>
|
>
|
||||||
{errors.PeramnentAddress.message}
|
{errors.PermanentAddress.message}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@ -444,7 +484,9 @@ const ManageEmployee = () => {
|
|||||||
Select Role
|
Select Role
|
||||||
</option>
|
</option>
|
||||||
{job_role?.map((item) => (
|
{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>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
@ -525,7 +567,14 @@ const ManageEmployee = () => {
|
|||||||
id="PanNumber"
|
id="PanNumber"
|
||||||
placeholder="PAN Number"
|
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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@ -69,7 +69,7 @@ const Header = () =>
|
|||||||
<a
|
<a
|
||||||
aria-label="toggle for sidebar"
|
aria-label="toggle for sidebar"
|
||||||
className="nav-item nav-link px-0 me-xl-4"
|
className="nav-item nav-link px-0 me-xl-4"
|
||||||
href="#"
|
|
||||||
>
|
>
|
||||||
<i className="bx bx-menu bx-sm"></i>
|
<i className="bx bx-menu bx-sm"></i>
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@ -4,17 +4,17 @@ import menuData from "../../data/menuData.json";
|
|||||||
import {getCachedProfileData} from "../../slices/apiDataManager";
|
import {getCachedProfileData} from "../../slices/apiDataManager";
|
||||||
|
|
||||||
const Sidebar = () => {
|
const Sidebar = () => {
|
||||||
const logineUser = getCachedProfileData()
|
// const logineUser = getCachedProfileData()
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const handleLogout = (e) => {
|
// const handleLogout = (e) => {
|
||||||
e.preventDefault();
|
// e.preventDefault();
|
||||||
// logout();
|
// // logout();
|
||||||
};
|
// };
|
||||||
|
|
||||||
const handleProfilePage = ()=>{
|
// const handleProfilePage = ()=>{
|
||||||
console.log(profile?.employeeInfo?.id)
|
// console.log(profile?.employeeInfo?.id)
|
||||||
navigate(`/employee/${profile?.employeeInfo?.id}?for=account`)
|
// navigate(`/employee/${profile?.employeeInfo?.id}?for=account`)
|
||||||
}
|
// }
|
||||||
return (
|
return (
|
||||||
<aside
|
<aside
|
||||||
id="layout-menu"
|
id="layout-menu"
|
||||||
@ -58,7 +58,7 @@ const Sidebar = () => {
|
|||||||
))}
|
))}
|
||||||
</ul>
|
</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
|
<a
|
||||||
href="#"
|
href="#"
|
||||||
className="d-flex align-items-center text-decoration-none dropdown-toggle"
|
className="d-flex align-items-center text-decoration-none dropdown-toggle"
|
||||||
@ -137,7 +137,7 @@ const Sidebar = () => {
|
|||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div> */}
|
||||||
</aside>
|
</aside>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,10 +1,9 @@
|
|||||||
import React,{useState} from "react";
|
import React, { useState } from "react";
|
||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
import { ProjectStatus } from "../../utils/projectStatus";
|
import { ProjectStatus } from "../../utils/projectStatus";
|
||||||
const AboutProject = ( {data} ) =>
|
const AboutProject = ({ data }) => {
|
||||||
{
|
const [CurrentProject, setCurrentProject] = useState(data);
|
||||||
const [CurrentProject,setCurrentProject] = useState(data)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{data && (
|
{data && (
|
||||||
@ -24,7 +23,7 @@ const AboutProject = ( {data} ) =>
|
|||||||
</span>
|
</span>
|
||||||
</li>
|
</li>
|
||||||
<li className="d-flex align-items-center mb-4">
|
<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 className="fw-medium mx-2">End Date:</span>{" "}
|
||||||
<span>
|
<span>
|
||||||
{data.endDate
|
{data.endDate
|
||||||
@ -33,33 +32,34 @@ const AboutProject = ( {data} ) =>
|
|||||||
</span>
|
</span>
|
||||||
</li>
|
</li>
|
||||||
<li className="d-flex align-items-center mb-2">
|
<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 className="fw-medium mx-2">Status:</span>{" "}
|
||||||
<span>{ProjectStatus(data.projectStatusId)}</span>
|
<span>{ProjectStatus(data.projectStatusId)}</span>
|
||||||
</li>
|
</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">
|
<li className="d-flex align-items-center mb-4">
|
||||||
<i className="bx bx-user"></i>
|
<i className="bx bx-user"></i>
|
||||||
<span className="fw-medium mx-2">Contact:</span>{" "}
|
<span className="fw-medium mx-2">Contact:</span>{" "}
|
||||||
<span>{data.contactPerson}</span>
|
<span>{data.contactPerson}</span>
|
||||||
</li>
|
</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">
|
<li className="d-flex align-items-center mb-4">
|
||||||
<i className="bx bx-flag"></i>
|
<i className="bx bx-flag"></i>
|
||||||
<span className="fw-medium mx-2">Address:</span>{" "}
|
<span className="fw-medium mx-2">Address:</span>{" "}
|
||||||
</li>
|
</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">
|
<li className="d-flex align-items-start test-start mb-4">
|
||||||
<span>{data.projectAddress}</span>
|
<span>{data.projectAddress}</span>
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@ -1,137 +1,139 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
|
import { ComingSoonPage } from "../../pages/Misc/ComingSoonPage";
|
||||||
|
|
||||||
const ActivityTimeline = () => {
|
const ActivityTimeline = () => {
|
||||||
return (
|
return (
|
||||||
<div className="card card-action mb-6">
|
// <div className="card card-action mb-6">
|
||||||
<div className="card-header align-items-center">
|
// <div className="card-header align-items-center">
|
||||||
<h5 className="card-action-title mb-0">
|
// <h5 className="card-action-title mb-0">
|
||||||
<i className="bx bx-bar-chart-alt-2 bx-lg text-body me-4"></i>
|
// <i className="bx bx-bar-chart-alt-2 bx-lg text-body me-4"></i>
|
||||||
Activity Timeline
|
// Activity Timeline
|
||||||
</h5>
|
// </h5>
|
||||||
</div>
|
// </div>
|
||||||
<div className="card-body pt-3">
|
// <div className="card-body pt-3">
|
||||||
<ul className="timeline mb-0">
|
// <ul className="timeline mb-0">
|
||||||
<li className="timeline-item timeline-item-transparent">
|
// <li className="timeline-item timeline-item-transparent">
|
||||||
<span className="timeline-point timeline-point-primary"></span>
|
// <span className="timeline-point timeline-point-primary"></span>
|
||||||
<div className="timeline-event">
|
// <div className="timeline-event">
|
||||||
<div className="timeline-header mb-3">
|
// <div className="timeline-header mb-3">
|
||||||
<h6 className="mb-0">12 Invoices have been paid</h6>
|
// <h6 className="mb-0">12 Invoices have been paid</h6>
|
||||||
<small className="text-muted">12 min ago</small>
|
// <small className="text-muted">12 min ago</small>
|
||||||
</div>
|
// </div>
|
||||||
<p className="mb-2">Invoices have been paid to the company</p>
|
// <p className="mb-2">Invoices have been paid to the company</p>
|
||||||
<div className="d-flex align-items-center mb-2">
|
// <div className="d-flex align-items-center mb-2">
|
||||||
<div className="badge bg-lighter rounded d-flex align-items-center">
|
// <div className="badge bg-lighter rounded d-flex align-items-center">
|
||||||
<img
|
// <img
|
||||||
src="../../assets//img/icons/misc/pdf.png"
|
// src="../../assets//img/icons/misc/pdf.png"
|
||||||
alt="img"
|
// alt="img"
|
||||||
width="15"
|
// width="15"
|
||||||
className="me-2"
|
// className="me-2"
|
||||||
></img>
|
// ></img>
|
||||||
<span className="h6 mb-0 text-body">invoices.pdf</span>
|
// <span className="h6 mb-0 text-body">invoices.pdf</span>
|
||||||
</div>
|
// </div>
|
||||||
</div>
|
// </div>
|
||||||
</div>
|
// </div>
|
||||||
</li>
|
// </li>
|
||||||
<li className="timeline-item timeline-item-transparent">
|
// <li className="timeline-item timeline-item-transparent">
|
||||||
<span className="timeline-point timeline-point-success"></span>
|
// <span className="timeline-point timeline-point-success"></span>
|
||||||
<div className="timeline-event">
|
// <div className="timeline-event">
|
||||||
<div className="timeline-header mb-3">
|
// <div className="timeline-header mb-3">
|
||||||
<h6 className="mb-0">Client Meeting</h6>
|
// <h6 className="mb-0">Client Meeting</h6>
|
||||||
<small className="text-muted">45 min ago</small>
|
// <small className="text-muted">45 min ago</small>
|
||||||
</div>
|
// </div>
|
||||||
<p className="mb-2">Project meeting with john @10:15am</p>
|
// <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 justify-content-between flex-wrap gap-2 mb-2">
|
||||||
<div className="d-flex flex-wrap align-items-center mb-50">
|
// <div className="d-flex flex-wrap align-items-center mb-50">
|
||||||
<div className="avatar avatar-sm me-3">
|
// <div className="avatar avatar-sm me-3">
|
||||||
<img
|
// <img
|
||||||
src="../../assets/img/avatars/1.png"
|
// src="../../assets/img/avatars/1.png"
|
||||||
alt="Avatar"
|
// alt="Avatar"
|
||||||
className="rounded-circle"
|
// className="rounded-circle"
|
||||||
></img>
|
// ></img>
|
||||||
</div>
|
// </div>
|
||||||
<div>
|
// <div>
|
||||||
<p className="mb-0 small fw-medium">
|
// <p className="mb-0 small fw-medium">
|
||||||
Lester McCarthy (Client)
|
// Lester McCarthy (Client)
|
||||||
</p>
|
// </p>
|
||||||
<small>CEO of ThemeSelection</small>
|
// <small>CEO of ThemeSelection</small>
|
||||||
</div>
|
// </div>
|
||||||
</div>
|
// </div>
|
||||||
</div>
|
// </div>
|
||||||
</div>
|
// </div>
|
||||||
</li>
|
// </li>
|
||||||
<li className="timeline-item timeline-item-transparent">
|
// <li className="timeline-item timeline-item-transparent">
|
||||||
<span className="timeline-point timeline-point-info"></span>
|
// <span className="timeline-point timeline-point-info"></span>
|
||||||
<div className="timeline-event">
|
// <div className="timeline-event">
|
||||||
<div className="timeline-header mb-3">
|
// <div className="timeline-header mb-3">
|
||||||
<h6 className="mb-0">Create a new project for client</h6>
|
// <h6 className="mb-0">Create a new project for client</h6>
|
||||||
<small className="text-muted">2 Day Ago</small>
|
// <small className="text-muted">2 Day Ago</small>
|
||||||
</div>
|
// </div>
|
||||||
<p className="mb-2">6 team members in a project</p>
|
// <p className="mb-2">6 team members in a project</p>
|
||||||
<ul className="list-group list-group-flush">
|
// <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">
|
// <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">
|
// <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">
|
// <ul className="list-unstyled users-list d-flex align-items-center avatar-group m-0 me-2">
|
||||||
<li
|
// <li
|
||||||
data-bs-toggle="tooltip"
|
// data-bs-toggle="tooltip"
|
||||||
data-popup="tooltip-custom"
|
// data-popup="tooltip-custom"
|
||||||
data-bs-placement="top"
|
// data-bs-placement="top"
|
||||||
className="avatar pull-up"
|
// className="avatar pull-up"
|
||||||
aria-label="Vinnie Mostowy"
|
// aria-label="Vinnie Mostowy"
|
||||||
data-bs-original-title="Vinnie Mostowy"
|
// data-bs-original-title="Vinnie Mostowy"
|
||||||
>
|
// >
|
||||||
<img
|
// <img
|
||||||
className="rounded-circle"
|
// className="rounded-circle"
|
||||||
src="../../assets/img/avatars/1.png"
|
// src="../../assets/img/avatars/1.png"
|
||||||
alt="Avatar"
|
// alt="Avatar"
|
||||||
></img>
|
// ></img>
|
||||||
</li>
|
// </li>
|
||||||
<li
|
// <li
|
||||||
data-bs-toggle="tooltip"
|
// data-bs-toggle="tooltip"
|
||||||
data-popup="tooltip-custom"
|
// data-popup="tooltip-custom"
|
||||||
data-bs-placement="top"
|
// data-bs-placement="top"
|
||||||
className="avatar pull-up"
|
// className="avatar pull-up"
|
||||||
aria-label="Allen Rieske"
|
// aria-label="Allen Rieske"
|
||||||
data-bs-original-title="Allen Rieske"
|
// data-bs-original-title="Allen Rieske"
|
||||||
>
|
// >
|
||||||
<img
|
// <img
|
||||||
className="rounded-circle"
|
// className="rounded-circle"
|
||||||
src="../../assets/img/avatars/4.png"
|
// src="../../assets/img/avatars/4.png"
|
||||||
alt="Avatar"
|
// alt="Avatar"
|
||||||
></img>
|
// ></img>
|
||||||
</li>
|
// </li>
|
||||||
<li
|
// <li
|
||||||
data-bs-toggle="tooltip"
|
// data-bs-toggle="tooltip"
|
||||||
data-popup="tooltip-custom"
|
// data-popup="tooltip-custom"
|
||||||
data-bs-placement="top"
|
// data-bs-placement="top"
|
||||||
className="avatar pull-up"
|
// className="avatar pull-up"
|
||||||
aria-label="Julee Rossignol"
|
// aria-label="Julee Rossignol"
|
||||||
data-bs-original-title="Julee Rossignol"
|
// data-bs-original-title="Julee Rossignol"
|
||||||
>
|
// >
|
||||||
<img
|
// <img
|
||||||
className="rounded-circle"
|
// className="rounded-circle"
|
||||||
src="../../assets/img/avatars/2.png"
|
// src="../../assets/img/avatars/2.png"
|
||||||
alt="Avatar"
|
// alt="Avatar"
|
||||||
></img>
|
// ></img>
|
||||||
</li>
|
// </li>
|
||||||
<li className="avatar">
|
// <li className="avatar">
|
||||||
<span
|
// <span
|
||||||
className="avatar-initial rounded-circle pull-up text-heading"
|
// className="avatar-initial rounded-circle pull-up text-heading"
|
||||||
data-bs-toggle="tooltip"
|
// data-bs-toggle="tooltip"
|
||||||
data-bs-placement="bottom"
|
// data-bs-placement="bottom"
|
||||||
data-bs-original-title="3 more"
|
// data-bs-original-title="3 more"
|
||||||
>
|
// >
|
||||||
+3
|
// +3
|
||||||
</span>
|
// </span>
|
||||||
</li>
|
// </li>
|
||||||
</ul>
|
// </ul>
|
||||||
</div>
|
// </div>
|
||||||
</li>
|
// </li>
|
||||||
</ul>
|
// </ul>
|
||||||
</div>
|
// </div>
|
||||||
</li>
|
// </li>
|
||||||
</ul>
|
// </ul>
|
||||||
</div>
|
// </div>
|
||||||
</div>
|
// </div>
|
||||||
|
<ComingSoonPage></ComingSoonPage>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -1,32 +1,44 @@
|
|||||||
import React, { useState,useEffect } from "react";
|
import React, { useState, useEffect } from "react";
|
||||||
import { useDispatch } from "react-redux";
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
import { changeMaster } from "../../slices/localVariablesSlice";
|
import { changeMaster } from "../../slices/localVariablesSlice";
|
||||||
import useMaster from "../../hooks/masterHook/useMaster";
|
import useMaster from "../../hooks/masterHook/useMaster";
|
||||||
import { employee } from "../../data/masters";
|
import { employee } from "../../data/masters";
|
||||||
import { useForm, Controller } from "react-hook-form";
|
import { useForm, Controller } from "react-hook-form";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { getCachedData } from "../../slices/apiDataManager";
|
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({
|
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 { handleSubmit, control, setValue, watch, formState: { errors },reset } = useForm({
|
||||||
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({
|
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
selectedEmployees: []
|
selectedEmployees: [],
|
||||||
|
description:""
|
||||||
},
|
},
|
||||||
resolver: (data) => {
|
resolver: (data) => {
|
||||||
const validation = schema.safeParse(data);
|
const validation = schema.safeParse(data);
|
||||||
@ -35,217 +47,281 @@ const { handleSubmit, control, setValue, watch, formState: { errors } } = useFor
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleRoleChange = (event) => {
|
const handleRoleChange = (event) => {
|
||||||
setSelectedRole(event.target.value);
|
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(()=>{
|
useEffect(()=>{
|
||||||
dispatch(changeMaster("Job Role"))
|
dispatch(changeMaster("Job Role"))
|
||||||
return ()=> setSelectedRole("all")
|
return ()=> setSelectedRole("all")
|
||||||
},[dispatch])
|
},[dispatch])
|
||||||
|
|
||||||
|
|
||||||
return (<>
|
return (
|
||||||
|
<>
|
||||||
|
<div className="container my-1">
|
||||||
<div className="container-fluid my-1">
|
<div className="mb-2">
|
||||||
<div className="mb-2">
|
<div className="bs-stepper wizard-numbered d-flex justify-content-center align-items-center flex-wrap">
|
||||||
<div className="bs-stepper wizard-numbered d-flex justify-content-center align-items-center flex-wrap">
|
{[
|
||||||
{[
|
assignData?.building?.name,
|
||||||
assignData?.building?.name,
|
assignData?.floor?.floorName,
|
||||||
assignData?.floor?.floorName,
|
assignData?.workArea?.areaName,
|
||||||
assignData?.workArea?.areaName,
|
assignData?.workItem?.workItem?.activityMaster?.activityName,
|
||||||
assignData?.workItem?.workItem?.activityMaster?.activityName
|
].map((item, index, array) => (
|
||||||
].map((item, index, array) => (
|
<div
|
||||||
<div key={index} className="col d-flex justify-content-center align-items-center">
|
key={index}
|
||||||
<div className="bs-stepper-header p-1 text-center">
|
className="col d-flex justify-content-center align-items-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=""
|
|
||||||
>
|
>
|
||||||
{loading && data ? "Loading..." : (
|
<div className="bs-stepper-header p-1 text-center">
|
||||||
<>
|
<span className="fs-5">{item}</span>
|
||||||
<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);
|
|
||||||
|
|
||||||
return (
|
{/* Arrow between items */}
|
||||||
<div key={emp.id} className="col-6 col-sm-4 col-md-4 col-lg-3 mb-1">
|
{index < array.length - 1 && (
|
||||||
<div className="form-check text-start p-0">
|
<div className="line">
|
||||||
<div className="li-wrapper d-flex justify-content-start align-items-start">
|
<i className="icon-base bx bx-chevron-right scaleX-n1-rtl"></i>
|
||||||
<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>
|
|
||||||
</div>
|
</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>
|
</div>
|
||||||
</div>
|
<div className="divider text-start">
|
||||||
)}
|
<div className="divider-text">Employee</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>
|
</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">
|
return (
|
||||||
<div class="form-check form-check-inline mt-4 px-1">
|
<div
|
||||||
<label className="form-text fs-6" for="inlineCheckbox1">Pending Work</label>
|
key={emp.id}
|
||||||
<label className="form-check-label ms-2" for="inlineCheckbox1">{ assignData?.workItem?.workItem?.plannedWork - assignData?.workItem?.workItem?.completedWork}</label>
|
className="col-6 col-sm-4 col-md-4 col-lg-3 mb-1"
|
||||||
</div>
|
>
|
||||||
<div className="form-check form-check-inline col-sm-2 col">
|
<div className="form-check text-start p-0">
|
||||||
<label for="defaultFormControlInput" className="form-label">Target</label>
|
<div className="li-wrapper d-flex justify-content-start align-items-start">
|
||||||
<input type="text" className="form-control form-control-sm " value={target} onChange={(e)=>setTraget(e.target.value)} id="defaultFormControlInput" aria-describedby="defaultFormControlHelp" />
|
<Controller
|
||||||
</div>
|
name="selectedEmployees"
|
||||||
</div>
|
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>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{selectedEmployees.length > 0 && (
|
||||||
{errors.selectedEmployees && (
|
<div className="mt-1">
|
||||||
<div className="danger-text mt-2">
|
<div className="text-start px-2">
|
||||||
<p>{errors.selectedEmployees[0]}</p>
|
{selectedEmployees.map((empId) => {
|
||||||
</div>
|
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">
|
<div className="col-md text-start mx-0 px-0">
|
||||||
<button type="submit" className="btn btn-sm btn-primary ">Submit</button>
|
<div className="form-check form-check-inline mt-4 px-1">
|
||||||
<button type="reset" className="btn btn-sm btn-label-secondary" data-bs-dismiss="modal" aria-label="Close">
|
<label className="form-text fs-6" for="inlineCheckbox1">
|
||||||
Cancel
|
Pending Work
|
||||||
</button>
|
</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>
|
</div>
|
||||||
</form>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
export default AssignRoleModel
|
export default AssignRoleModel;
|
||||||
|
|||||||
@ -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;
|
|
||||||
@ -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;
|
|
||||||
31
src/components/Project/Infrastructure/Building.jsx
Normal file
31
src/components/Project/Infrastructure/Building.jsx
Normal 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}
|
||||||
|
{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
|
||||||
184
src/components/Project/Infrastructure/BuildingModel.jsx
Normal file
184
src/components/Project/Infrastructure/BuildingModel.jsx
Normal 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;
|
||||||
29
src/components/Project/Infrastructure/Floor.jsx
Normal file
29
src/components/Project/Infrastructure/Floor.jsx
Normal 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} </span>
|
||||||
|
</h6>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
)}
|
||||||
|
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default Floor
|
||||||
228
src/components/Project/Infrastructure/FloorModel.jsx
Normal file
228
src/components/Project/Infrastructure/FloorModel.jsx
Normal 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;
|
||||||
154
src/components/Project/Infrastructure/InfraTable.jsx
Normal file
154
src/components/Project/Infrastructure/InfraTable.jsx
Normal 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;
|
||||||
@ -1,58 +1,57 @@
|
|||||||
import React, { useState, useEffect } from "react";
|
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 = {
|
const defaultModel = {
|
||||||
id: "0",
|
id: "0",
|
||||||
workAreaId: 0,
|
workAreaId: 0,
|
||||||
activityId: 0,
|
activityID: 0,
|
||||||
plannedWork: 0,
|
plannedWork: 0,
|
||||||
completedWork: 0,
|
completedWork: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
const TaskModel = ({
|
const TaskModel = ({
|
||||||
project,
|
project,
|
||||||
activities,
|
|
||||||
onSubmit,
|
onSubmit,
|
||||||
clearTrigger,
|
clearTrigger,
|
||||||
onClearComplete,
|
onClearComplete,onClose
|
||||||
}) => {
|
} )=>{
|
||||||
|
const dispatch = useDispatch()
|
||||||
|
const {data:activities,loading} = useMaster()
|
||||||
const [formData, setFormData] = useState(defaultModel);
|
const [formData, setFormData] = useState(defaultModel);
|
||||||
|
|
||||||
const [selectedBuilding, setSelectedBuilding] = useState(null);
|
const [selectedBuilding, setSelectedBuilding] = useState(null);
|
||||||
const [selectedFloor, setSelectedFloor] = useState(null);
|
const [selectedFloor, setSelectedFloor] = useState(null);
|
||||||
const [selectedWorkArea, setSelectedWorkArea] = useState(null);
|
const [selectedWorkArea, setSelectedWorkArea] = useState(null);
|
||||||
const [selectedActivity, setSelectedActivity] = useState(null);
|
const [selectedActivity, setSelectedActivity] = useState(null);
|
||||||
|
|
||||||
//if (floor && floor.id) setFormData(floor);
|
|
||||||
|
const { register, handleSubmit, formState: { errors },reset } = useForm({
|
||||||
useEffect(() => {
|
resolver: zodResolver(taskSchema),
|
||||||
if (selectedBuilding) {
|
defaultValues: formData,
|
||||||
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]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (clearTrigger) {
|
if (clearTrigger) {
|
||||||
let model = defaultModel;
|
setFormData(defaultModel);
|
||||||
model.floorId = selectedFloor.id;
|
onClearComplete();
|
||||||
setFormData(defaultModel); // Clear form
|
|
||||||
onClearComplete(); // Notify parent that clearing is done
|
|
||||||
}
|
}
|
||||||
}, [clearTrigger, onClearComplete]);
|
}, [clearTrigger, onClearComplete]);
|
||||||
|
|
||||||
// Handle input change
|
// Handle input changes
|
||||||
const handleChange = (e) => {
|
const handleChange = (e) => {
|
||||||
const { name, value } = e.target;
|
const { name, value } = e.target;
|
||||||
const activity = activities.find((b) => b.id === Number(value));
|
const activity = activities.find((b) => b.id === Number(value));
|
||||||
@ -63,29 +62,15 @@ const TaskModel = ({
|
|||||||
const { value } = e.target;
|
const { value } = e.target;
|
||||||
const activity = activities.find((b) => b.id === Number(value));
|
const activity = activities.find((b) => b.id === Number(value));
|
||||||
|
|
||||||
setFormData({ ...formData, ["activityId"]: value });
|
setFormData({ ...formData, ["activityID"]: value });
|
||||||
setSelectedActivity(activity);
|
setSelectedActivity(activity);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleWorkAreaChange = (e) => {
|
const handleWorkAreaChange = (e) => {
|
||||||
const { value } = e.target;
|
const { value } = e.target;
|
||||||
const workArea = selectedFloor.workAreas.find(
|
const workArea = selectedFloor.workAreas.find((b) => b.id === Number(value));
|
||||||
(b) => b.id === Number(value)
|
|
||||||
);
|
|
||||||
setSelectedWorkArea(workArea);
|
setSelectedWorkArea(workArea);
|
||||||
setSelectedActivity(null);
|
|
||||||
setFormData({ ...formData, ["workAreaId"]: value });
|
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) => {
|
const handleFloorChange = (e) => {
|
||||||
@ -96,7 +81,8 @@ const TaskModel = ({
|
|||||||
setSelectedActivity(null);
|
setSelectedActivity(null);
|
||||||
setFormData(defaultModel);
|
setFormData(defaultModel);
|
||||||
};
|
};
|
||||||
const handleBuildigChange = (e) => {
|
|
||||||
|
const handleBuildingChange = (e) => {
|
||||||
const { value } = e.target;
|
const { value } = e.target;
|
||||||
const building = project.buildings.find((b) => b.id === Number(value));
|
const building = project.buildings.find((b) => b.id === Number(value));
|
||||||
setSelectedBuilding(building);
|
setSelectedBuilding(building);
|
||||||
@ -106,39 +92,59 @@ const TaskModel = ({
|
|||||||
setFormData(defaultModel);
|
setFormData(defaultModel);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle form submission
|
const onSubmitForm = ( data ) =>
|
||||||
const handleSubmit = (e) => {
|
{
|
||||||
e.preventDefault();
|
onSubmit( data );
|
||||||
|
setSelectedActivity(null),
|
||||||
onSubmit(formData); // Pass the updated data to the parent
|
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 (
|
return (
|
||||||
<div className="modal-dialog modal-lg modal-simple modal-edit-user">
|
<div className="modal-dialog modal-lg modal-simple modal-edit-user">
|
||||||
<div className="modal-content">
|
<div className="modal-content">
|
||||||
<div className="modal-body">
|
<div className="modal-body">
|
||||||
<div className="row">
|
<div className="row">
|
||||||
<button
|
<button type="button" className="btn-close" aria-label="Close" onClick={onClose}/>
|
||||||
type="button"
|
|
||||||
className="btn-close"
|
|
||||||
data-bs-dismiss="modal"
|
|
||||||
aria-label="Close"
|
|
||||||
></button>
|
|
||||||
<div className="text-center mb-1">
|
<div className="text-center mb-1">
|
||||||
<h5 className="mb-1">Manage Task</h5>
|
<h5 className="mb-1">Manage Task</h5>
|
||||||
</div>
|
</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">
|
<div className="col-6 col-md-6">
|
||||||
<label className="form-label" htmlFor="name">
|
<label className="form-label" htmlFor="name">
|
||||||
Select Building
|
Select Building
|
||||||
</label>
|
</label>
|
||||||
<select
|
<select
|
||||||
id="buildingId"
|
id="buildingID"
|
||||||
name="buildingId"
|
name="buildingID"
|
||||||
className="select2 form-select form-select-sm"
|
className="select2 form-select form-select-sm"
|
||||||
aria-label="Default select example"
|
aria-label="Default select example"
|
||||||
onChange={handleBuildigChange}
|
{...register("buildingID")}
|
||||||
value={formData.buildingId}
|
onChange={(e) => handleBuildingChange(e)}
|
||||||
>
|
>
|
||||||
<option value="0">Select Building</option>
|
<option value="0">Select Building</option>
|
||||||
{project.buildings.map((building) => (
|
{project.buildings.map((building) => (
|
||||||
@ -147,9 +153,11 @@ const TaskModel = ({
|
|||||||
</option>
|
</option>
|
||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
|
{errors.buildingID && <p className="danger-text">{errors.buildingID.message}</p>}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{selectedBuilding && selectedBuilding.id != "0" && (
|
{/* Select Floor */}
|
||||||
|
{selectedBuilding && selectedBuilding.id !== "0" && (
|
||||||
<div className="col-6 col-md-6">
|
<div className="col-6 col-md-6">
|
||||||
<label className="form-label" htmlFor="floorId">
|
<label className="form-label" htmlFor="floorId">
|
||||||
Select Floor
|
Select Floor
|
||||||
@ -159,19 +167,21 @@ const TaskModel = ({
|
|||||||
name="floorId"
|
name="floorId"
|
||||||
className="select2 form-select form-select-sm"
|
className="select2 form-select form-select-sm"
|
||||||
aria-label="Default select example"
|
aria-label="Default select example"
|
||||||
onChange={handleFloorChange}
|
{...register("floorId")}
|
||||||
value={formData.floorId}
|
onChange={(e) => handleFloorChange(e)}
|
||||||
>
|
>
|
||||||
<option value="0">Select Floor</option>
|
<option value="0">Select Floor</option>
|
||||||
{selectedBuilding.floors.map((floor) => (
|
{selectedBuilding.floors.map((floor) => (
|
||||||
<option key={floor.id} value={floor.id}>
|
<option key={floor.id} value={floor.id}>
|
||||||
{floor.floorName} - ({floor.workAreas.length} Work
|
{floor.floorName} - ({floor.workAreas.length} Work Areas)
|
||||||
Areas)
|
|
||||||
</option>
|
</option>
|
||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
|
{errors.floorId && <p className="danger-text">{errors.floorId.message}</p>}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Select Work Area */}
|
||||||
{selectedFloor && (
|
{selectedFloor && (
|
||||||
<div className="col-12 col-md-12">
|
<div className="col-12 col-md-12">
|
||||||
<label className="form-label" htmlFor="workAreaId">
|
<label className="form-label" htmlFor="workAreaId">
|
||||||
@ -182,8 +192,8 @@ const TaskModel = ({
|
|||||||
name="workAreaId"
|
name="workAreaId"
|
||||||
className="select2 form-select form-select-sm"
|
className="select2 form-select form-select-sm"
|
||||||
aria-label="Default select example"
|
aria-label="Default select example"
|
||||||
onChange={handleWorkAreaChange}
|
{...register("workAreaId",{valueAsNumber:true})}
|
||||||
value={formData.workAreaId}
|
onChange={(e) => handleWorkAreaChange(e)}
|
||||||
>
|
>
|
||||||
<option value="0">Select Work Area</option>
|
<option value="0">Select Work Area</option>
|
||||||
{selectedFloor.workAreas.map((workArea) => (
|
{selectedFloor.workAreas.map((workArea) => (
|
||||||
@ -192,20 +202,23 @@ const TaskModel = ({
|
|||||||
</option>
|
</option>
|
||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
|
{errors.workAreaId && <p className="danger-text">{errors.workAreaId.message}</p>}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Select Activity */}
|
||||||
{selectedWorkArea && (
|
{selectedWorkArea && (
|
||||||
<div className="col-12 col-md-12">
|
<div className="col-12 col-md-12">
|
||||||
<label className="form-label" htmlFor="activityId">
|
<label className="form-label" htmlFor="activityID">
|
||||||
Select Activity
|
Select Activity
|
||||||
</label>
|
</label>
|
||||||
<select
|
<select
|
||||||
id="activityId"
|
id="activityID"
|
||||||
name="activityId"
|
name="activityID"
|
||||||
className="select2 form-select form-select-sm"
|
className="select2 form-select form-select-sm"
|
||||||
aria-label="Default select example"
|
aria-label="Default select example"
|
||||||
onChange={handleActivityChange}
|
{...register("activityID",{valueAsNumber:true})}
|
||||||
value={formData.activityId}
|
onChange={(e) => handleActivityChange(e)}
|
||||||
>
|
>
|
||||||
<option value="0">Select Activity</option>
|
<option value="0">Select Activity</option>
|
||||||
{activities.map((activity) => (
|
{activities.map((activity) => (
|
||||||
@ -214,83 +227,75 @@ const TaskModel = ({
|
|||||||
</option>
|
</option>
|
||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
{errors.activityID && <p className="danger-text">{errors.activityID.message}</p>}
|
||||||
)}
|
|
||||||
{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>
|
|
||||||
</div>
|
</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 && (
|
{selectedActivity && (
|
||||||
<div className="col-2 col-md-2">
|
<div className="col-2 col-md-2">
|
||||||
{" "}
|
|
||||||
<label className="form-label" htmlFor="unit">
|
<label className="form-label" htmlFor="unit">
|
||||||
Unit
|
Unit
|
||||||
</label>
|
</label>
|
||||||
<div className="input-group">
|
<input
|
||||||
<input
|
type="text"
|
||||||
type="text"
|
disabled
|
||||||
id="unit"
|
id="unit"
|
||||||
disabled
|
name="unit"
|
||||||
name="unit"
|
className="form-control form-control-sm me-2"
|
||||||
className="form-control form-control-sm me-2"
|
value={selectedActivity?.unitOfMeasurement || ""}
|
||||||
placeholder="Unit"
|
/>
|
||||||
onChange={handleChange}
|
|
||||||
value={selectedActivity.unitOfMeasurement}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="col-12 text-center">
|
<div className="col-12 text-center">
|
||||||
{selectedActivity && (
|
<button type="submit" className="btn btn-sm btn-primary me-3">
|
||||||
<button type="submit" className="btn btn-primary me-3">
|
{formData.id !== "0" && formData.id !== "" ? "Edit Task" : "Add Task"}
|
||||||
{formData.id != "0" && formData.id != ""
|
</button>
|
||||||
? "Edit Task"
|
|
||||||
: "Add Task"}
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
<button
|
<button
|
||||||
type="reset"
|
type="button"
|
||||||
className="btn btn-label-secondary"
|
className="btn btn-sm btn-label-secondary"
|
||||||
data-bs-dismiss="modal"
|
onClick={onClose}
|
||||||
aria-label="Close"
|
|
||||||
>
|
>
|
||||||
Cancel
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
62
src/components/Project/Infrastructure/WorkArea.jsx
Normal file
62
src/components/Project/Infrastructure/WorkArea.jsx
Normal 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}
|
||||||
|
</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;
|
||||||
219
src/components/Project/Infrastructure/WorkAreaModel.jsx
Normal file
219
src/components/Project/Infrastructure/WorkAreaModel.jsx
Normal 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;
|
||||||
170
src/components/Project/Infrastructure/WorkItem.jsx
Normal file
170
src/components/Project/Infrastructure/WorkItem.jsx
Normal 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;
|
||||||
@ -27,7 +27,7 @@ const ManageProjectInfo = ( {project,handleSubmitForm, onClose} ) =>
|
|||||||
...(project?.id ? { id: z.number().optional() } : {}),
|
...(project?.id ? { id: z.number().optional() } : {}),
|
||||||
name: z.string().min( 1, {message: "Project Name is required"} ),
|
name: z.string().min( 1, {message: "Project Name is required"} ),
|
||||||
contactPerson: z.string().min( 1, {message: "Contact Person 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),
|
startDate: z.string().min( 1, {message: "Start Date is required"} ).default(currentDate),
|
||||||
endDate: z.string().min( 1, {message: "End Date is required"} ).default(currentDate),
|
endDate: z.string().min( 1, {message: "End Date is required"} ).default(currentDate),
|
||||||
projectStatusId: z
|
projectStatusId: z
|
||||||
@ -41,7 +41,14 @@ const ManageProjectInfo = ( {project,handleSubmitForm, onClose} ) =>
|
|||||||
return num;
|
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( {
|
const {register, control, handleSubmit, formState: {errors}, reset, getValues} = useForm( {
|
||||||
@ -82,13 +89,15 @@ const ManageProjectInfo = ( {project,handleSubmitForm, onClose} ) =>
|
|||||||
handleSubmitForm( updatedProject )
|
handleSubmitForm( updatedProject )
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useEffect( () =>
|
||||||
|
{
|
||||||
|
return ()=>setLoading(false)
|
||||||
|
},[])
|
||||||
|
|
||||||
|
|
||||||
return (
|
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-content">
|
||||||
<div className="modal-body">
|
<div className="modal-body p-sm-4 p-0">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="btn-close"
|
className="btn-close"
|
||||||
@ -181,10 +190,10 @@ const ManageProjectInfo = ( {project,handleSubmitForm, onClose} ) =>
|
|||||||
<option value="1">Active</option>
|
<option value="1">Active</option>
|
||||||
<option value="2">On Hold</option>
|
<option value="2">On Hold</option>
|
||||||
|
|
||||||
<option value="3">Suspended</option>
|
{/* <option value="3">Suspended</option> */}
|
||||||
<option value="4">Inactive</option>
|
<option value="3">Inactive</option>
|
||||||
|
|
||||||
<option value="5">Completed</option>
|
<option value="4">Completed</option>
|
||||||
</select>
|
</select>
|
||||||
{errors.projectStatusId && <div className="danger-text text-start" style={{fontSize:"12px"}}>{errors.projectStatusId.message}</div>}
|
{errors.projectStatusId && <div className="danger-text text-start" style={{fontSize:"12px"}}>{errors.projectStatusId.message}</div>}
|
||||||
|
|
||||||
|
|||||||
@ -5,12 +5,14 @@ import ProjectRepository from "../../repositories/ProjectRepository";
|
|||||||
import { cacheData,getCachedData } from "../../slices/apiDataManager";
|
import { cacheData,getCachedData } from "../../slices/apiDataManager";
|
||||||
import {hasUserPermission} from "../../utils/authUtils";
|
import {hasUserPermission} from "../../utils/authUtils";
|
||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
|
import {useHasUserPermission} from "../../hooks/useHasUserPermission";
|
||||||
|
import {MANAGE_PROJECT} from "../../utils/constants";
|
||||||
|
|
||||||
|
|
||||||
const ProjectBanner = ( {project_data} ) =>
|
const ProjectBanner = ( {project_data} ) =>
|
||||||
{
|
{
|
||||||
const [showModal, setShowModal] = useState(false);
|
const [showModal, setShowModal] = useState(false);
|
||||||
|
const manageProject = useHasUserPermission(MANAGE_PROJECT)
|
||||||
const [ CurrentProject, setCurrentProject ] = useState( project_data )
|
const [ CurrentProject, setCurrentProject ] = useState( project_data )
|
||||||
if (project_data == null) {
|
if (project_data == null) {
|
||||||
return <span>incomplete project information</span>;
|
return <span>incomplete project information</span>;
|
||||||
@ -74,59 +76,33 @@ const ProjectBanner = ( {project_data} ) =>
|
|||||||
onClose={handleClose}
|
onClose={handleClose}
|
||||||
></ManageProjectInfo>
|
></ManageProjectInfo>
|
||||||
</div>
|
</div>
|
||||||
{/* -------------------- */}
|
{/* Project Banner */}
|
||||||
<div className="col-12">
|
<div className="col-12">
|
||||||
<div className="card mb-6 pb-0">
|
<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="d-flex align-items-center justify-content-between p-4 flex-wrap">
|
||||||
<div className="flex-shrink-0 mt-1 mx-sm-0 px-2 mx-auto">
|
{/* Left: Icon + Name */}
|
||||||
|
<div className="d-flex align-items-center gap-3">
|
||||||
<img
|
<img
|
||||||
src="../../assets/icons/civil-engineering.svg"
|
src="../../assets/icons/civil-engineering.svg"
|
||||||
alt="user image"
|
alt="user image"
|
||||||
className="d-block h-auto ms-0 ms-sm-6 rounded-3 user-profile-img project-banner-icon"
|
className="rounded-3"
|
||||||
></img>
|
style={{ width: "60px", height: "60px" }}
|
||||||
|
/>
|
||||||
|
<h4 className="mb-0">
|
||||||
|
{CurrentProject.name ? CurrentProject.name : "N/A"}
|
||||||
|
</h4>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-grow-1 mt-1 mt-lg-1">
|
{manageProject && (
|
||||||
<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">
|
<button
|
||||||
<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
|
|
||||||
type="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-toggle="modal"
|
||||||
data-bs-target="#edit-project-modal"
|
data-bs-target="#edit-project-modal"
|
||||||
onClick={handleShow}
|
onClick={handleShow}
|
||||||
>
|
>
|
||||||
Modify
|
Modify
|
||||||
</button>
|
</button>
|
||||||
</div>
|
)}
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,271 +1,285 @@
|
|||||||
import React,{useState,useEffect} from "react";
|
import React, { useState } from "react";
|
||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
import { getDateDifferenceInDays } from "../../utils/dateUtils";
|
import { getDateDifferenceInDays } from "../../utils/dateUtils";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import {useProjectDetails} from "../../hooks/useProjects";
|
import { useProjectDetails } from "../../hooks/useProjects";
|
||||||
import ManageProjectInfo from "./ManageProjectInfo";
|
import ManageProjectInfo from "./ManageProjectInfo";
|
||||||
import ProjectRepository from "../../repositories/ProjectRepository";
|
import ProjectRepository from "../../repositories/ProjectRepository";
|
||||||
import {cacheData, getCachedData} from "../../slices/apiDataManager";
|
import { cacheData, getCachedData } from "../../slices/apiDataManager";
|
||||||
import showToast from "../../services/toastService";
|
import showToast from "../../services/toastService";
|
||||||
import {useHasUserPermission} from "../../hooks/useHasUserPermission";
|
import { useHasUserPermission } from "../../hooks/useHasUserPermission";
|
||||||
import {MANAGE_PROJECT} from "../../utils/constants";
|
import { MANAGE_PROJECT } from "../../utils/constants";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const ProjectCard = ( {projectData} ) =>
|
const ProjectCard = ({ projectData }) => {
|
||||||
{
|
const [projectInfo, setProjectInfo] = useState(projectData);
|
||||||
|
const [projectDetails, setProjectDetails] = useState(null);
|
||||||
const[projectInfo,setProjectInfo] = useState(projectData)
|
const [showModal, setShowModal] = useState(false);
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate();
|
||||||
const {projects_Details, loading} = useProjectDetails( projectData.id )
|
const ManageProject = useHasUserPermission(MANAGE_PROJECT);
|
||||||
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 getProjectStatusColor = (statusId) => {
|
const handleShow = async () => {
|
||||||
switch (statusId) {
|
try {
|
||||||
case 1:
|
const response = await ProjectRepository.getProjectByprojectId(projectInfo.id);
|
||||||
return "bg-label-success";
|
setProjectDetails(response.data);
|
||||||
case 2:
|
setShowModal(true);
|
||||||
return "bg-label-warning";
|
} catch (error) {
|
||||||
case 3:
|
showToast("Failed to load project details", "error");
|
||||||
return "bg-label-info";
|
}
|
||||||
case 4:
|
};
|
||||||
return "bg-label-secondary";
|
|
||||||
case 5:
|
|
||||||
return "bg-label-dark";
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleViewProject = (e) => {
|
const getProgress = (planned, completed) => {
|
||||||
navigate(`/projects/${projectData.id}`)
|
return (completed * 100) / planned + "%";
|
||||||
};
|
};
|
||||||
|
const getProgressInNumber = (planned, completed) => {
|
||||||
|
return (completed * 100) / planned;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleClose = () => setShowModal(false);
|
||||||
|
|
||||||
|
const getProjectStatusName = (statusId) => {
|
||||||
const handleFormSubmit = ( updatedProject ) =>
|
switch (statusId) {
|
||||||
{
|
case 1:
|
||||||
if ( projectInfo?.id )
|
return "Active";
|
||||||
{
|
case 2:
|
||||||
ProjectRepository.updateProject(projectInfo.id,updatedProject).then( ( response ) =>
|
return "On Hold";
|
||||||
{
|
// case 3:
|
||||||
const updatedProjectData = {
|
// return "Suspended";
|
||||||
...projectInfo,
|
case 3:
|
||||||
...response.data,
|
return "Inactive";
|
||||||
building:projects_Details.building,
|
case 4:
|
||||||
};
|
return "Completed";
|
||||||
setProjectInfo( updatedProject )
|
}
|
||||||
if ( getCachedData( `projectinfo-${ projectInfo.id }` ) )
|
};
|
||||||
{
|
|
||||||
cacheData( `projectinfo-${ projectInfo.id }`, updatedProjectData );
|
const getProjectStatusColor = (statusId) => {
|
||||||
|
switch (statusId) {
|
||||||
}
|
case 1:
|
||||||
const projects_list = getCachedData( "projectslist" );
|
return "bg-label-success";
|
||||||
if ( projects_list )
|
case 2:
|
||||||
{
|
return "bg-label-warning";
|
||||||
const updatedProjectsList = projects_list.map(project =>
|
case 3:
|
||||||
project.id == projectInfo.id ? {
|
return "bg-label-info";
|
||||||
...project,
|
case 4:
|
||||||
...response.data,
|
return "bg-label-secondary";
|
||||||
tenant:project.tenant
|
case 5:
|
||||||
} : project
|
return "bg-label-dark";
|
||||||
);
|
}
|
||||||
|
};
|
||||||
cacheData("projectslist",updatedProjectsList)
|
|
||||||
}
|
const handleViewProject = () => {
|
||||||
|
navigate(`/projects/${projectData.id}`);
|
||||||
showToast( "Project updated successfully.", "success" );
|
|
||||||
|
|
||||||
setShowModal(false)
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
showToast( error.message, "error" );
|
|
||||||
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
return (
|
const handleFormSubmit = (updatedProject) => {
|
||||||
<>
|
if (projectInfo?.id) {
|
||||||
|
ProjectRepository.updateProject(projectInfo.id, updatedProject)
|
||||||
<div
|
.then((response) => {
|
||||||
className={`modal fade ${showModal ? 'show' : ''}`}
|
const updatedProjectData = {
|
||||||
tabIndex="-1"
|
...projectInfo,
|
||||||
role="dialog"
|
...response.data,
|
||||||
style={{ display: showModal ? 'block' : 'none' }}
|
building: projectDetails?.building,
|
||||||
aria-hidden={!showModal}
|
};
|
||||||
>
|
|
||||||
<ManageProjectInfo
|
|
||||||
project={projects_Details}
|
|
||||||
handleSubmitForm={handleFormSubmit}
|
|
||||||
onClose={handleClose}
|
|
||||||
></ManageProjectInfo>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="col-md-6 col-lg-4 col-xl-4 order-0 mb-4">
|
setProjectInfo(updatedProject);
|
||||||
<div className="card cursor-pointer">
|
|
||||||
<div className="card-header pb-4">
|
if (getCachedData(`projectinfo-${projectInfo.id}`)) {
|
||||||
<div className="d-flex align-items-start">
|
cacheData(`projectinfo-${projectInfo.id}`, updatedProjectData);
|
||||||
<div className="d-flex align-items-center">
|
}
|
||||||
<div className="avatar me-4">
|
|
||||||
<i
|
const projects_list = getCachedData("projectslist");
|
||||||
className="rounded-circle bx bx-building-house"
|
if (projects_list) {
|
||||||
style={{ fontSize: "xx-large" }}
|
const updatedProjectsList = projects_list.map((project) =>
|
||||||
></i>
|
project.id === projectInfo.id
|
||||||
</div>
|
? { ...project, ...response.data, tenant: project.tenant }
|
||||||
<div className="me-2">
|
: project
|
||||||
<h5 className="mb-0">
|
);
|
||||||
<a
|
cacheData("projectslist", updatedProjectsList);
|
||||||
className="stretched-link text-heading"
|
}
|
||||||
|
|
||||||
onClick={handleViewProject}
|
showToast("Project updated successfully.", "success");
|
||||||
>
|
setShowModal(false);
|
||||||
{projectInfo.name}
|
})
|
||||||
</a>
|
.catch((error) => {
|
||||||
</h5>
|
showToast(error.message, "error");
|
||||||
<div className="client-info text-body">
|
});
|
||||||
<span className="fw-medium">Client: </span>
|
}
|
||||||
<span>{projectInfo.contactPerson}</span>
|
};
|
||||||
</div>
|
|
||||||
</div>
|
return (
|
||||||
</div>
|
<>
|
||||||
<div className={`ms-auto ${!ManageProject && 'd-none'}`}>
|
{showModal && projectDetails && (
|
||||||
<div className="dropdown z-2">
|
<div
|
||||||
<button
|
className="modal fade show"
|
||||||
type="button"
|
tabIndex="-1"
|
||||||
className="btn btn-icon btn-text-secondary rounded-pill dropdown-toggle hide-arrow p-0"
|
role="dialog"
|
||||||
data-bs-toggle="dropdown"
|
style={{ display: "block" }}
|
||||||
aria-expanded="false"
|
aria-hidden="false"
|
||||||
>
|
>
|
||||||
<i className="bx bx-dots-vertical-rounded bx-sm text-muted"></i>
|
<ManageProjectInfo
|
||||||
</button>
|
project={projectDetails}
|
||||||
<ul className="dropdown-menu dropdown-menu-end">
|
handleSubmitForm={handleFormSubmit}
|
||||||
<li>
|
onClose={handleClose}
|
||||||
<a
|
/>
|
||||||
aria-label="click to View details"
|
</div>
|
||||||
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>
|
|
||||||
|
|
||||||
<li>
|
<div className="col-md-6 col-lg-4 col-xl-4 order-0 mb-4">
|
||||||
<a className="dropdown-item" >
|
<div className="card cursor-pointer">
|
||||||
<i className="bx bx-task me-2"></i>
|
<div className="card-header pb-4">
|
||||||
<span className="align-left">Activities</span>
|
<div className="d-flex align-items-start">
|
||||||
</a>
|
<div className="d-flex align-items-center">
|
||||||
</li>
|
<div className="avatar me-4">
|
||||||
</ul>
|
<i
|
||||||
</div>
|
className="rounded-circle bx bx-building-house"
|
||||||
</div>
|
style={{ fontSize: "xx-large" }}
|
||||||
</div>
|
></i>
|
||||||
</div>
|
</div>
|
||||||
<div className="card-body pb-1">
|
<div className="me-2">
|
||||||
<div className="d-flex align-items-center flex-wrap">
|
<h5 className="mb-0">
|
||||||
<div className="text-start mb-4">
|
<a
|
||||||
<p className="mb-1">
|
className="stretched-link text-heading"
|
||||||
<span className="text-heading fw-medium">Start Date: </span>
|
onClick={handleViewProject}
|
||||||
{projectInfo.startDate
|
>
|
||||||
? moment(projectInfo.startDate).format("DD-MMM-YYYY")
|
{projectInfo.name}
|
||||||
: "NA"}
|
</a>
|
||||||
</p>
|
</h5>
|
||||||
<p className="mb-1">
|
<div className="client-info text-body">
|
||||||
<span className="text-heading fw-medium">Deadline: </span>
|
<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
|
<li onClick={handleShow}>
|
||||||
? moment(projectInfo.endDate).format("DD-MMM-YYYY")
|
<a className="dropdown-item">
|
||||||
: "NA"}
|
<i className="bx bx-pencil me-2"></i>
|
||||||
</p>
|
<span className="align-left">Modify</span>
|
||||||
<p className="mb-0">{projectInfo.projectAddress}</p>
|
</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>
|
|
||||||
<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;
|
export default ProjectCard;
|
||||||
|
|||||||
@ -1,58 +1,79 @@
|
|||||||
import React, { useState, useEffect } from "react";
|
import React, { useState, useEffect } from "react";
|
||||||
import "./ProjectInfra.css";
|
import "./ProjectInfra.css";
|
||||||
import BuildingModel from "./BuildingModel";
|
import BuildingModel from "./Infrastructure/BuildingModel";
|
||||||
import FloorModel from "./FloorModel";
|
import FloorModel from "./Infrastructure/FloorModel";
|
||||||
import showToast from "../../services/toastService";
|
import showToast from "../../services/toastService";
|
||||||
import WorkAreaModel from "./WorkAreaModel";
|
import WorkAreaModel from "./Infrastructure/WorkAreaModel";
|
||||||
import TaskModel from "./TaskModel";
|
import TaskModel from "./Infrastructure/TaskModel";
|
||||||
import ProjectRepository from "../../repositories/ProjectRepository";
|
import ProjectRepository, {
|
||||||
|
TasksRepository,
|
||||||
|
} from "../../repositories/ProjectRepository";
|
||||||
import ProjectModal from "./ProjectModal";
|
import ProjectModal from "./ProjectModal";
|
||||||
import {useHasUserPermission} from "../../hooks/useHasUserPermission";
|
import { useHasUserPermission } from "../../hooks/useHasUserPermission";
|
||||||
import {MANAGE_PROJECT_INFRA} from "../../utils/constants";
|
import { MANAGE_PROJECT_INFRA } from "../../utils/constants";
|
||||||
// import AssignRoleModel from "./AssignRoleModel";
|
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 [expandedBuildings, setExpandedBuildings] = useState([]);
|
||||||
const [project, setProject] = useState(data);
|
const { projects_Details, loading } = useProjectDetails(data.id);
|
||||||
const[modalConfig,setModalConfig] = useState({type:null,data:null});
|
const [project, setProject] = useState(projects_Details);
|
||||||
const [ isModalOpen, setIsModalOpen ] = useState( false )
|
const [modalConfig, setModalConfig] = useState({ type: null, data: null });
|
||||||
const ManageInfra = useHasUserPermission(MANAGE_PROJECT_INFRA)
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||||
|
const ManageInfra = useHasUserPermission(MANAGE_PROJECT_INFRA);
|
||||||
|
|
||||||
const [buildings, setBuildings] = useState(data.buildings);
|
|
||||||
const [isBuildingModalOpen, setIsBuildingModalOpen] = useState(false);
|
const [isBuildingModalOpen, setIsBuildingModalOpen] = useState(false);
|
||||||
const [isFloorModalOpen, setIsFloorModalOpen] = useState(false);
|
const [isFloorModalOpen, setIsFloorModalOpen] = useState(false);
|
||||||
const [isWorkAreaModelOpen, setIsWorkAreaModalOpen] = useState(false);
|
const [isWorkAreaModelOpen, setIsWorkAreaModalOpen] = useState(false);
|
||||||
const [isTaskModelOpen, setIsTaskModalOpen] = useState(false);
|
const [isTaskModelOpen, setIsTaskModalOpen] = useState(false);
|
||||||
const [isAssignRoleModal,setIsAssingRoleModal] = useState(false)
|
const [isAssignRoleModal, setIsAssingRoleModal] = useState(false);
|
||||||
const [isCreateModalOpen, setIsCreateModalOpen] = useState(false);
|
const [isCreateModalOpen, setIsCreateModalOpen] = useState(false);
|
||||||
const [clearFormTrigger, setClearFormTrigger] = useState(false);
|
const [clearFormTrigger, setClearFormTrigger] = useState(false);
|
||||||
const [CurrentBuilding,setCurrentBuilding] = useState("")
|
const [CurrentBuilding, setCurrentBuilding] = useState("");
|
||||||
|
const [showModal, setShowModal] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setProject(data);
|
setProject(projects_Details);
|
||||||
setBuildings(data.buildings);
|
}, [data, projects_Details]);
|
||||||
}, [data]);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const openFloorModel = (projectData) => {
|
const openFloorModel = (projectData) => {
|
||||||
setIsFloorModalOpen(true);
|
setIsFloorModalOpen(true);
|
||||||
};
|
};
|
||||||
const closeFloorModel = () => {
|
const closeFloorModel = () => {
|
||||||
setIsFloorModalOpen(false);
|
setIsFloorModalOpen(false);
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const openAssignModel=(assignData)=>{
|
const openAssignModel = (assignData) => {
|
||||||
|
setCurrentBuilding(assignData);
|
||||||
setCurrentBuilding(assignData)
|
setIsAssingRoleModal(true);
|
||||||
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) => {
|
const handleFloorModelFormSubmit = (updatedFloor) => {
|
||||||
if (updatedFloor.id == "") delete updatedFloor.id;
|
if (updatedFloor.id == "") delete updatedFloor.id;
|
||||||
|
|
||||||
submitData([
|
submitData([
|
||||||
{
|
{
|
||||||
building: null,
|
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) => {
|
const openWorkAreaModel = (projectData) => {
|
||||||
setIsWorkAreaModalOpen(true);
|
setIsWorkAreaModalOpen(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const closeWorkAreaModel = () => {
|
const closeWorkAreaModel = () => {
|
||||||
setIsWorkAreaModalOpen(false);
|
setIsWorkAreaModalOpen(false);
|
||||||
// const modalBackdrop = document.querySelector(".modal-backdrop");
|
|
||||||
// if (modalBackdrop) modalBackdrop.remove();
|
|
||||||
};
|
};
|
||||||
const handleWorkAreaModelFormSubmit = (updatedModel) => {
|
const handleWorkAreaModelFormSubmit = (updatedModel) => {
|
||||||
if (updatedModel.id == "") delete updatedModel.id;
|
if (updatedModel.id == "") delete updatedModel.id;
|
||||||
|
|
||||||
submitData([
|
submitData([
|
||||||
{
|
{
|
||||||
building: null,
|
building: null,
|
||||||
@ -124,428 +107,349 @@ const ProjectInfra = ({ data, activityMaster, onDataChange,eachSiteEngineer }) =
|
|||||||
|
|
||||||
const closeTaskModel = () => {
|
const closeTaskModel = () => {
|
||||||
setIsTaskModalOpen(false);
|
setIsTaskModalOpen(false);
|
||||||
// const modalBackdrop = document.querySelector(".modal-backdrop");
|
|
||||||
// if (modalBackdrop) modalBackdrop.remove();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleTaskModelFormSubmit = (updatedModel) => {
|
const handleTaskModelFormSubmit = (updatedModel) => {
|
||||||
|
// debugger
|
||||||
if (updatedModel.id == "") updatedModel.id = 0;
|
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])
|
ProjectRepository.manageProjectTasks([updatedModel])
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
onDataChange("task-change");
|
onDataChange("task-change");
|
||||||
showToast("Details updated successfully.", "success");
|
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) => {
|
.catch((error) => {
|
||||||
showToast(error.message, "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) => {
|
const toggleBuilding = (id) => {
|
||||||
setExpandedBuildings((prev) =>
|
setExpandedBuildings((prev) =>
|
||||||
prev.includes(id) ? prev.filter((bid) => bid !== id) : [...prev, id]
|
prev.includes(id) ? prev.filter((bid) => bid !== id) : [...prev, id]
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getContent = (building) => {
|
const handleModalData = (type, modaldata) => {
|
||||||
let hasFloors =
|
setModalConfig({ type: type, data: modaldata });
|
||||||
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} {" "}
|
|
||||||
</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} </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 + "%";
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
// common modal
|
|
||||||
|
|
||||||
const handleModalData = (type,modaldata)=>{
|
|
||||||
setModalConfig({type:type,data:modaldata})
|
|
||||||
}
|
|
||||||
const openModal = () => {
|
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 = () => {
|
const closeModal = () => {
|
||||||
setIsModalOpen(false);
|
setIsModalOpen(false);
|
||||||
setModalConfig(null)
|
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]);
|
|
||||||
|
|
||||||
|
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 (
|
return (
|
||||||
<>
|
<>
|
||||||
{isBuildingModalOpen && (
|
<div
|
||||||
<div
|
className={`modal fade ${showModal ? "show" : ""}`}
|
||||||
className={`modal fade `}
|
tabIndex="-1"
|
||||||
id="building-model"
|
role="dialog"
|
||||||
tabIndex="-1"
|
style={{ display: showModal ? "block" : "none" }}
|
||||||
aria-hidden="true"
|
aria-hidden={!showModal}
|
||||||
>
|
>
|
||||||
<BuildingModel
|
<BuildingModel
|
||||||
project={data}
|
project={project}
|
||||||
onClose={closeBuildingModel}
|
onClose={handleClose}
|
||||||
onSubmit={handleBuildingModelFormSubmit}
|
onSubmit={handleBuildingModelFormSubmit}
|
||||||
clearTrigger={clearFormTrigger}
|
clearTrigger={clearFormTrigger}
|
||||||
onClearComplete={() => setClearFormTrigger(false)}
|
onClearComplete={() => setClearFormTrigger(false)}
|
||||||
></BuildingModel>
|
></BuildingModel>
|
||||||
</div>
|
</div>
|
||||||
)}
|
|
||||||
|
|
||||||
{isFloorModalOpen && (
|
{isFloorModalOpen && (
|
||||||
<div
|
<div
|
||||||
className={`modal fade `}
|
className="modal fade show"
|
||||||
id="floor-model"
|
id="floor-model"
|
||||||
tabIndex="-1"
|
tabIndex="-1"
|
||||||
aria-hidden="true"
|
role="dialog"
|
||||||
|
style={{ display: "block" }}
|
||||||
|
aria-hidden="false"
|
||||||
>
|
>
|
||||||
<FloorModel
|
<FloorModel
|
||||||
project={data}
|
project={project}
|
||||||
// building={null}
|
|
||||||
onClose={closeFloorModel}
|
onClose={closeFloorModel}
|
||||||
onSubmit={handleFloorModelFormSubmit}
|
onSubmit={handleFloorModelFormSubmit}
|
||||||
clearTrigger={clearFormTrigger}
|
clearTrigger={clearFormTrigger}
|
||||||
onClearComplete={() => setClearFormTrigger(false)}
|
onClearComplete={() => setClearFormTrigger(false)}
|
||||||
></FloorModel>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{isWorkAreaModelOpen && (
|
{isWorkAreaModelOpen && (
|
||||||
<div
|
<div
|
||||||
className={`modal fade `}
|
className="modal fade show"
|
||||||
id="work-area-model"
|
id="work-area-model"
|
||||||
tabIndex="-1"
|
tabIndex="-1"
|
||||||
aria-hidden="true"
|
role="dialog"
|
||||||
|
style={{ display: "block" }}
|
||||||
|
aria-hidden="false"
|
||||||
>
|
>
|
||||||
<WorkAreaModel
|
<WorkAreaModel
|
||||||
project={data}
|
project={project}
|
||||||
// building={null}
|
|
||||||
onClose={closeWorkAreaModel}
|
onClose={closeWorkAreaModel}
|
||||||
onSubmit={handleWorkAreaModelFormSubmit}
|
onSubmit={handleWorkAreaModelFormSubmit}
|
||||||
clearTrigger={clearFormTrigger}
|
clearTrigger={clearFormTrigger}
|
||||||
onClearComplete={() => setClearFormTrigger(false)}
|
onClearComplete={() => setClearFormTrigger(false)}
|
||||||
></WorkAreaModel>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{isTaskModelOpen && (
|
{isTaskModelOpen && (
|
||||||
<div
|
<div
|
||||||
className={`modal fade `}
|
className="modal fade show"
|
||||||
id="task-model"
|
id="task-model"
|
||||||
tabIndex="-1"
|
tabIndex="-1"
|
||||||
aria-hidden="true"
|
role="dialog"
|
||||||
|
style={{ display: "block" }}
|
||||||
|
aria-hidden="false"
|
||||||
>
|
>
|
||||||
<TaskModel
|
<TaskModel
|
||||||
project={data}
|
project={project}
|
||||||
activities={activityMaster}
|
|
||||||
onClose={closeTaskModel}
|
onClose={closeTaskModel}
|
||||||
onSubmit={handleTaskModelFormSubmit}
|
onSubmit={handleTaskModelFormSubmit}
|
||||||
clearTrigger={clearFormTrigger}
|
clearTrigger={clearFormTrigger}
|
||||||
onClearComplete={() => setClearFormTrigger(false)}
|
onClearComplete={() => setClearFormTrigger(false)}
|
||||||
></TaskModel>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
{/* common Modal */}
|
|
||||||
{isModalOpen && (
|
{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="col-md-12 col-lg-12 col-xl-12 order-0 mb-4">
|
||||||
<div className="card">
|
<div className="card">
|
||||||
{/* <div className="card-header pb-4"></div> */}
|
|
||||||
|
|
||||||
<div className="card-body" style={{ padding: "0.5rem" }}>
|
<div className="card-body" style={{ padding: "0.5rem" }}>
|
||||||
<div className="align-items-center">
|
<div className="align-items-center">
|
||||||
<div className="row ">
|
<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
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="link-button link-button-sm m-1 "
|
className="link-button link-button-sm m-1 "
|
||||||
data-bs-toggle="modal"
|
onClick={handleShow}
|
||||||
data-bs-target="#building-model"
|
|
||||||
onClick={() => openBuildingModel()}
|
|
||||||
>
|
>
|
||||||
<i className="bx bx-plus-circle me-2"></i>
|
<i className="bx bx-plus-circle me-2"></i>
|
||||||
Manage Building
|
Manage Building
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
data-bs-toggle="modal"
|
|
||||||
className="link-button m-1"
|
className="link-button m-1"
|
||||||
data-bs-target="#floor-model"
|
|
||||||
onClick={() => openFloorModel()}
|
onClick={() => openFloorModel()}
|
||||||
>
|
>
|
||||||
<i className="bx bx-plus-circle me-2"></i>
|
<i className="bx bx-plus-circle me-2"></i>
|
||||||
Manage Floors
|
Manage Floors
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
data-bs-toggle="modal"
|
|
||||||
className="link-button m-1"
|
className="link-button m-1"
|
||||||
data-bs-target="#work-area-model"
|
|
||||||
onClick={() => openWorkAreaModel()}
|
onClick={() => openWorkAreaModel()}
|
||||||
>
|
>
|
||||||
<i className="bx bx-plus-circle me-2"></i>
|
<i className="bx bx-plus-circle me-2"></i>
|
||||||
@ -553,59 +457,23 @@ const ProjectInfra = ({ data, activityMaster, onDataChange,eachSiteEngineer }) =
|
|||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
data-bs-toggle="modal"
|
|
||||||
className="link-button m-1"
|
className="link-button m-1"
|
||||||
data-bs-target="#task-model"
|
|
||||||
onClick={() => openTaskModel()}
|
onClick={() => openTaskModel()}
|
||||||
>
|
>
|
||||||
<i className="bx bx-plus-circle me-2"></i>
|
<i className="bx bx-plus-circle me-2"></i>
|
||||||
Manage Tasks
|
Create Tasks
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="row ">
|
<div className="row ">
|
||||||
<div className="col-12 overflow-auto">
|
{loading && <p>Loading....</p>}
|
||||||
{buildings && buildings.length > 0 && (
|
{project && project.buildings?.length > 0 && (
|
||||||
<table className="table table-bordered ">
|
<InfraTable
|
||||||
<tbody>
|
buildings={project?.buildings}
|
||||||
{buildings.map((building) => (
|
project={project}
|
||||||
|
handleFloor={submitData}
|
||||||
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}
|
|
||||||
{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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -25,7 +25,8 @@ const ProjectModal = ({modalConfig,closeModal}) => {
|
|||||||
|
|
||||||
{/* Modal Component */}
|
{/* Modal Component */}
|
||||||
|
|
||||||
{modalConfig?.type === "assignRole" && <AssignRole assignData={modalConfig?.data} onClose={closeModal} />}
|
{modalConfig?.type === "assignRole" && <AssignRole assignData={modalConfig?.data} onClose={closeModal} />}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -35,7 +35,7 @@ const ProjectNav = ( {onPillClick, activePill} ) =>
|
|||||||
<i className="bx bx-group bx-sm me-1_5"></i> Teams
|
<i className="bx bx-group bx-sm me-1_5"></i> Teams
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li className={`nav-item ${HasViewInfraStructure ? "":"d-none"} `}>
|
<li className={`nav-item ${!HasViewInfraStructure && 'd-none'} `}>
|
||||||
<a
|
<a
|
||||||
className={`nav-link ${activePill === "infra" ? "active" : ""}`}
|
className={`nav-link ${activePill === "infra" ? "active" : ""}`}
|
||||||
href="#"
|
href="#"
|
||||||
@ -47,7 +47,7 @@ const ProjectNav = ( {onPillClick, activePill} ) =>
|
|||||||
<i className="bx bx-grid-alt bx-sm me-1_5"></i> Infrastructure
|
<i className="bx bx-grid-alt bx-sm me-1_5"></i> Infrastructure
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li className="nav-item">
|
{/* <li className="nav-item">
|
||||||
<a
|
<a
|
||||||
className={`nav-link ${
|
className={`nav-link ${
|
||||||
activePill === "workplan" ? "active" : ""
|
activePill === "workplan" ? "active" : ""
|
||||||
@ -60,7 +60,7 @@ const ProjectNav = ( {onPillClick, activePill} ) =>
|
|||||||
>
|
>
|
||||||
<i className="bx bx-link bx-sm me-1_5"></i> Work Plan
|
<i className="bx bx-link bx-sm me-1_5"></i> Work Plan
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li> */}
|
||||||
<li className="nav-item">
|
<li className="nav-item">
|
||||||
<a
|
<a
|
||||||
className={`nav-link ${
|
className={`nav-link ${
|
||||||
@ -78,15 +78,15 @@ const ProjectNav = ( {onPillClick, activePill} ) =>
|
|||||||
<li className="nav-item">
|
<li className="nav-item">
|
||||||
<a
|
<a
|
||||||
className={`nav-link ${
|
className={`nav-link ${
|
||||||
activePill === "activities" ? "active" : ""
|
activePill === "directory" ? "active" : ""
|
||||||
}`}
|
}`}
|
||||||
href="#"
|
href="#"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.preventDefault(); // Prevent page reload
|
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>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
@ -1,11 +1,10 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import {useEmployeesByProjectAllocated} from "../../hooks/useProjects";
|
import {useEmployeesByProjectAllocated, useProjects} from "../../hooks/useProjects";
|
||||||
|
|
||||||
const ProjectOverview = ({project}) =>
|
const ProjectOverview = ({project}) =>
|
||||||
{
|
{
|
||||||
const {projectEmployees} = useEmployeesByProjectAllocated( project.id );
|
const {projects} = useProjects()
|
||||||
let teamSize = projectEmployees.filter( ( emp ) => emp.isActive )
|
const teamSize = projects.find((pro)=>pro.id == project)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="card mb-6">
|
<div className="card mb-6">
|
||||||
<div className="card-body">
|
<div className="card-body">
|
||||||
@ -26,7 +25,7 @@ const ProjectOverview = ({project}) =>
|
|||||||
<li className="d-flex align-items-center">
|
<li className="d-flex align-items-center">
|
||||||
<i className="bx bx-user"></i>
|
<i className="bx bx-user"></i>
|
||||||
<span className="fw-medium mx-2">Current team Size:</span>{" "}
|
<span className="fw-medium mx-2">Current team Size:</span>{" "}
|
||||||
<span>{ teamSize?.length}</span>
|
<span>{teamSize?.teamSize}</span>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -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;
|
|
||||||
@ -1,7 +1,8 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
|
import { ComingSoonPage } from "../../pages/Misc/ComingSoonPage";
|
||||||
|
|
||||||
const WorkPlan = () => {
|
const WorkPlan = () => {
|
||||||
return <div>Work plan calender will go here</div>;
|
return <ComingSoonPage></ComingSoonPage>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default WorkPlan;
|
export default WorkPlan;
|
||||||
|
|||||||
42
src/components/common/DatePicker.jsx
Normal file
42
src/components/common/DatePicker.jsx
Normal 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;
|
||||||
45
src/components/common/DateRangePicker.jsx
Normal file
45
src/components/common/DateRangePicker.jsx
Normal 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;
|
||||||
77
src/components/common/GlobalModel.jsx
Normal file
77
src/components/common/GlobalModel.jsx
Normal 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;
|
||||||
@ -2,33 +2,33 @@ import React from "react";
|
|||||||
|
|
||||||
const Loader = () => {
|
const Loader = () => {
|
||||||
return (
|
return (
|
||||||
<div class="demo-inline-spacing">
|
<div className="demo-inline-spacing">
|
||||||
<div class="spinner-grow" role="status">
|
<div className="spinner-grow" role="status">
|
||||||
<span class="visually-hidden">Loading...</span>
|
<span className="visually-hidden">Loading...</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="spinner-grow text-primary" role="status">
|
<div className="spinner-grow text-primary" role="status">
|
||||||
<span class="visually-hidden">Loading...</span>
|
<span className="visually-hidden">Loading...</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="spinner-grow text-secondary" role="status">
|
<div className="spinner-grow text-secondary" role="status">
|
||||||
<span class="visually-hidden">Loading...</span>
|
<span className="visually-hidden">Loading...</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="spinner-grow text-success" role="status">
|
<div className="spinner-grow text-success" role="status">
|
||||||
<span class="visually-hidden">Loading...</span>
|
<span className="visually-hidden">Loading...</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="spinner-grow text-danger" role="status">
|
<div className="spinner-grow text-danger" role="status">
|
||||||
<span class="visually-hidden">Loading...</span>
|
<span className="visually-hidden">Loading...</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="spinner-grow text-warning" role="status">
|
<div className="spinner-grow text-warning" role="status">
|
||||||
<span class="visually-hidden">Loading...</span>
|
<span className="visually-hidden">Loading...</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="spinner-grow text-info" role="status">
|
<div className="spinner-grow text-info" role="status">
|
||||||
<span class="visually-hidden">Loading...</span>
|
<span className="visually-hidden">Loading...</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="spinner-grow text-light" role="status">
|
<div className="spinner-grow text-light" role="status">
|
||||||
<span class="visually-hidden">Loading...</span>
|
<span className="visually-hidden">Loading...</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="spinner-grow text-dark" role="status">
|
<div className="spinner-grow text-dark" role="status">
|
||||||
<span class="visually-hidden">Loading...</span>
|
<span className="visually-hidden">Loading...</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
30
src/components/common/ProgressBar.jsx
Normal file
30
src/components/common/ProgressBar.jsx
Normal 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;
|
||||||
234
src/components/master/CreateActivity.jsx
Normal file
234
src/components/master/CreateActivity.jsx
Normal 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;
|
||||||
@ -57,15 +57,12 @@ const onSubmit = (values) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
MasterRespository.createRole(result).then((resp)=>{
|
MasterRespository.createRole(result).then((resp)=>{
|
||||||
console.log(resp)
|
|
||||||
setIsLoading(false)
|
setIsLoading(false)
|
||||||
|
|
||||||
const cachedData = getCachedData( "Role" );
|
const cachedData = getCachedData( "Role" );
|
||||||
console.log(cachedData)
|
const updatedData = [...cachedData, resp.data];
|
||||||
const updatedData = [...cachedData, resp];
|
|
||||||
|
|
||||||
cacheData("Role", updatedData);
|
cacheData("Application Role", updatedData);
|
||||||
showToast("Role Added successfully.", "success");
|
showToast("Application Role Added successfully.", "success");
|
||||||
onClose()
|
onClose()
|
||||||
} ).catch( ( err ) =>
|
} ).catch( ( err ) =>
|
||||||
{
|
{
|
||||||
|
|||||||
@ -3,18 +3,17 @@ import axios from "axios";
|
|||||||
|
|
||||||
const API_URL = "http://localhost:5000/mastersdata";
|
const API_URL = "http://localhost:5000/mastersdata";
|
||||||
|
|
||||||
|
const DeleteMaster = ({ master, onClose }) => {
|
||||||
const DeleteMaster = ({ master,onClose}) => {
|
|
||||||
const [loader, setLoader] = useState(false);
|
const [loader, setLoader] = useState(false);
|
||||||
|
|
||||||
|
const handleDelete = () => {
|
||||||
const handleDelete = () => {
|
const index = mastersdata[master?.masterType]?.findIndex(
|
||||||
const index = mastersdata[master?.masterType]?.findIndex(item => String(item?.id) === String(master?.item?.id));
|
(item) => String(item?.id) === String(master?.item?.id)
|
||||||
console.log(index)
|
);
|
||||||
if (index !== -1) {
|
if (index !== -1) {
|
||||||
mastersdata[master?.masterType].splice(index, 1);
|
mastersdata[master?.masterType].splice(index, 1);
|
||||||
}
|
}
|
||||||
onClose()
|
onClose();
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -27,8 +26,8 @@ const DeleteMaster = ({ master,onClose}) => {
|
|||||||
onClick={handleDelete}
|
onClick={handleDelete}
|
||||||
>
|
>
|
||||||
{loader ? (
|
{loader ? (
|
||||||
<div class="spinner-border text-primary" role="status">
|
<div className="spinner-border text-primary" role="status">
|
||||||
<span class="sr-only">Loading...</span>
|
<span className="sr-only">Loading...</span>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
"Delete"
|
"Delete"
|
||||||
|
|||||||
248
src/components/master/EditActivity.jsx
Normal file
248
src/components/master/EditActivity.jsx
Normal 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;
|
||||||
@ -101,7 +101,7 @@ const EditMaster=({master,onClose})=> {
|
|||||||
setIsLoading( false )
|
setIsLoading( false )
|
||||||
|
|
||||||
|
|
||||||
const cachedData = getCachedData("Role");
|
const cachedData = getCachedData("Application Role");
|
||||||
|
|
||||||
if (cachedData) {
|
if (cachedData) {
|
||||||
|
|
||||||
@ -109,9 +109,9 @@ const EditMaster=({master,onClose})=> {
|
|||||||
role.id === resp.data?.id ? { ...role, ...resp.data } : role
|
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)
|
setIsLoading(false)
|
||||||
onClose()
|
onClose()
|
||||||
}).catch((Err)=>{
|
}).catch((Err)=>{
|
||||||
|
|||||||
@ -5,53 +5,62 @@ import DeleteMaster from "./DeleteMaster";
|
|||||||
import EditRole from "./EditRole";
|
import EditRole from "./EditRole";
|
||||||
import CreateJobRole from "./CreateJobRole";
|
import CreateJobRole from "./CreateJobRole";
|
||||||
import EditJobRole from "./EditJobRole";
|
import EditJobRole from "./EditJobRole";
|
||||||
|
import CreateActivity from "./CreateActivity";
|
||||||
|
import EditActivity from "./EditActivity";
|
||||||
|
|
||||||
const MasterModal = ({ modaldata ,closeModal}) => {
|
const MasterModal = ({ modaldata, closeModal }) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<div
|
||||||
<div
|
className="modal fade"
|
||||||
className="modal fade"
|
id="master-modal"
|
||||||
id="master-modal"
|
tabIndex="-1"
|
||||||
tabIndex="-1"
|
aria-hidden="true"
|
||||||
aria-hidden="true"
|
role="dialog"
|
||||||
role="dialog"
|
|
||||||
aria-labelledby="modalToggleLabel"
|
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
|
||||||
<div className="modal-body p-sm-4 p-0">
|
className={`modal-dialog mx-sm-auto mx-1 ${
|
||||||
<button
|
modaldata?.modalType === "delete" || `Ativity` ? "modal-md" : "modal-lg"
|
||||||
type="button"
|
} modal-simple `}
|
||||||
className="btn-close"
|
>
|
||||||
data-bs-dismiss="modal"
|
<div className="modal-content">
|
||||||
aria-label="Close"
|
<div className="modal-body p-sm-4 p-0">
|
||||||
onClick={closeModal}
|
<button
|
||||||
></button>
|
type="button"
|
||||||
<div className="text-center mb-2"></div>
|
className="btn-close"
|
||||||
{modaldata?.modalType === "Role" &&
|
data-bs-dismiss="modal"
|
||||||
<CreateRole masmodalType={modaldata.masterType} onClose={closeModal} />}
|
aria-label="Close"
|
||||||
{modaldata?.modalType === "Edit-Role" && (
|
onClick={closeModal}
|
||||||
<EditRole master={modaldata} onClose={closeModal} />
|
></button>
|
||||||
)}
|
<div className="text-center mb-2"></div>
|
||||||
{modaldata?.modalType === "delete" && (
|
{modaldata?.modalType === "Application Role" && (
|
||||||
<DeleteMaster master={modaldata} onClose={closeModal}/>
|
<CreateRole
|
||||||
)}
|
masmodalType={modaldata.masterType}
|
||||||
{modaldata?.modalType === "Job Role" && (
|
onClose={closeModal}
|
||||||
<CreateJobRole onClose={closeModal} />
|
/>
|
||||||
)}
|
)}
|
||||||
{modaldata?.modalType === "Edit-Job Role" && (
|
{modaldata?.modalType === "Edit-Application Role" && (
|
||||||
<EditJobRole data ={modaldata.item} onClose={closeModal} />
|
<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>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -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 = [
|
export const dailyTask = [
|
||||||
{
|
{
|
||||||
|
|||||||
@ -28,17 +28,13 @@
|
|||||||
"text": "Project Status",
|
"text": "Project Status",
|
||||||
"available": true,
|
"available": true,
|
||||||
"link": "#"
|
"link": "#"
|
||||||
},
|
|
||||||
{
|
|
||||||
"text": "Inventory",
|
|
||||||
"available": true,
|
|
||||||
"link": "/inventory"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"text": "Activities",
|
"text": "Activities",
|
||||||
"icon": "bx bx-store",
|
"icon": "bx bx-list-ul",
|
||||||
"available": true,
|
"available": true,
|
||||||
"link": "",
|
"link": "",
|
||||||
"submenu": [
|
"submenu": [
|
||||||
@ -70,25 +66,20 @@
|
|||||||
{
|
{
|
||||||
"text": "Daily Expenses",
|
"text": "Daily Expenses",
|
||||||
"available": true,
|
"available": true,
|
||||||
"link": "/dashboard"
|
"link": "/dashboard/"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"text": "User Management",
|
"text": "Administration",
|
||||||
"icon": "bx bx-box",
|
"icon": "bx bx-box",
|
||||||
"available": true,
|
"available": true,
|
||||||
"link": "",
|
"link": "",
|
||||||
"submenu": [
|
"submenu": [
|
||||||
{
|
{
|
||||||
"text": "Application Users",
|
"text": "Users",
|
||||||
"available": true,
|
"available": true,
|
||||||
"link": "/employees"
|
"link": "/employees/"
|
||||||
},
|
|
||||||
{
|
|
||||||
"text": "Employees",
|
|
||||||
"available": true,
|
|
||||||
"link": "/employees"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"text": "Masters",
|
"text": "Masters",
|
||||||
@ -96,11 +87,17 @@
|
|||||||
"link": "/masters"
|
"link": "/masters"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "Inventory",
|
||||||
|
"icon": "bx bx-store",
|
||||||
|
"available": true,
|
||||||
|
"link": "/inventory"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"header": "Misc",
|
"header": "",
|
||||||
"items": [
|
"items": [
|
||||||
{
|
{
|
||||||
"text": "Support",
|
"text": "Support",
|
||||||
|
|||||||
@ -35,15 +35,17 @@ const useMaster = () => {
|
|||||||
} else {
|
} else {
|
||||||
let response;
|
let response;
|
||||||
switch (selectedMaster) {
|
switch (selectedMaster) {
|
||||||
case "Role":
|
case "Application Role":
|
||||||
response = await MasterRespository.getRoles();
|
response = await MasterRespository.getRoles();
|
||||||
response = response.data;
|
response = response.data;
|
||||||
break;
|
break;
|
||||||
case "Job Role":
|
case "Job Role":
|
||||||
response = await MasterRespository.getJobRole();
|
response = await MasterRespository.getJobRole();
|
||||||
|
response = response.data
|
||||||
break;
|
break;
|
||||||
case "Module":
|
case "Activity":
|
||||||
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"}];
|
response = await MasterRespository.getActivites();
|
||||||
|
response = response.data
|
||||||
break;
|
break;
|
||||||
case "Status":
|
case "Status":
|
||||||
response = [{description: null,featurePermission: null,id: "02dd4761-363c-49ed-8851-3d2489a3e98d",status:"status 1"},{description: null,featurePermission: null,id: "03dy9761-363c-49ed-8851-3d2489a3e98d",status:"status 2"},{description: null,featurePermission: null,id: "03dy7761-263c-49ed-8851-3d2489a3e98d",status:"Status 3"}];
|
response = [{description: null,featurePermission: null,id: "02dd4761-363c-49ed-8851-3d2489a3e98d",status:"status 1"},{description: null,featurePermission: null,id: "03dy9761-363c-49ed-8851-3d2489a3e98d",status:"status 2"},{description: null,featurePermission: null,id: "03dy7761-263c-49ed-8851-3d2489a3e98d",status:"Status 3"}];
|
||||||
|
|||||||
@ -30,7 +30,10 @@ export const useAttendace =(projectId)=>{
|
|||||||
|
|
||||||
|
|
||||||
useEffect(()=>{
|
useEffect(()=>{
|
||||||
fetchData(projectId);
|
if ( projectId )
|
||||||
|
{
|
||||||
|
fetchData(projectId);
|
||||||
|
}
|
||||||
},[projectId])
|
},[projectId])
|
||||||
|
|
||||||
return {attendance,loading,error}
|
return {attendance,loading,error}
|
||||||
@ -40,7 +43,7 @@ export const useAttendace =(projectId)=>{
|
|||||||
|
|
||||||
|
|
||||||
export const useEmployeeAttendacesLog = (id) => {
|
export const useEmployeeAttendacesLog = (id) => {
|
||||||
const [logs, setLogs] = useState();
|
const [logs, setLogs] = useState([]);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [error, setError] = useState(null);
|
const [error, setError] = useState(null);
|
||||||
|
|
||||||
@ -71,4 +74,41 @@ export const useEmployeeAttendacesLog = (id) => {
|
|||||||
}, [id]);
|
}, [id]);
|
||||||
|
|
||||||
return { logs, loading, error };
|
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}
|
||||||
|
}
|
||||||
122
src/hooks/useDashboard_Data.jsx
Normal file
122
src/hooks/useDashboard_Data.jsx
Normal 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 };
|
||||||
|
};
|
||||||
@ -240,8 +240,7 @@ export const useEmployeeProfile =(employeeId)=>{
|
|||||||
const fetchData = async () => {
|
const fetchData = async () => {
|
||||||
const Employee_cache = getCachedData("employeeProfile");
|
const Employee_cache = getCachedData("employeeProfile");
|
||||||
if(!Employee_cache || Employee_cache.employeeId !== employeeId){
|
if(!Employee_cache || Employee_cache.employeeId !== employeeId){
|
||||||
|
|
||||||
|
|
||||||
EmployeeRepository.getEmployeeProfile(employeeId)
|
EmployeeRepository.getEmployeeProfile(employeeId)
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
setEmployees(response.data);
|
setEmployees(response.data);
|
||||||
@ -260,7 +259,9 @@ export const useEmployeeProfile =(employeeId)=>{
|
|||||||
};
|
};
|
||||||
|
|
||||||
useEffect(()=>{
|
useEffect(()=>{
|
||||||
fetchData(employeeId);
|
if(employeeId){
|
||||||
|
fetchData(employeeId);
|
||||||
|
}
|
||||||
},[employeeId])
|
},[employeeId])
|
||||||
|
|
||||||
return {employee,loading,error}
|
return {employee,loading,error}
|
||||||
|
|||||||
@ -16,9 +16,9 @@ export const useMasterRole =()=>{
|
|||||||
if (!features_cache) {
|
if (!features_cache) {
|
||||||
MasterRespository.getRoles()
|
MasterRespository.getRoles()
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
setMasterRole(response);
|
setMasterRole(response.data);
|
||||||
|
|
||||||
cacheData("masterRole", response);
|
cacheData("masterRole", response.data);
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
@ -55,9 +55,9 @@ export const useFeatures =()=> {
|
|||||||
if (!features_cache) {
|
if (!features_cache) {
|
||||||
MasterRespository.getFeatures()
|
MasterRespository.getFeatures()
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
setMasterFeatures(response);
|
setMasterFeatures(response.data);
|
||||||
|
|
||||||
cacheData("features", response);
|
cacheData("features", response.data);
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
|
|||||||
@ -18,8 +18,8 @@ export const useProfile = () =>
|
|||||||
{
|
{
|
||||||
setLoading( true )
|
setLoading( true )
|
||||||
let response = await AuthRepository.profile();
|
let response = await AuthRepository.profile();
|
||||||
setProfile( response )
|
setProfile( response.data )
|
||||||
cacheProfileData( response )
|
cacheProfileData( response.data )
|
||||||
setLoading( false );
|
setLoading( false );
|
||||||
|
|
||||||
} catch ( error )
|
} catch ( error )
|
||||||
|
|||||||
@ -1,120 +1,126 @@
|
|||||||
import { useEffect,useState } from "react"
|
import { useEffect, useState } from "react";
|
||||||
import {
|
import { cacheData, getCachedData } from "../slices/apiDataManager";
|
||||||
cacheData,
|
|
||||||
getCachedData,
|
|
||||||
} from "../slices/apiDataManager"
|
|
||||||
import ProjectRepository from "../repositories/ProjectRepository";
|
import ProjectRepository from "../repositories/ProjectRepository";
|
||||||
|
import { useProfile } from "./useProfile";
|
||||||
|
|
||||||
|
export const useProjects = () => {
|
||||||
export const useProjects =()=>{
|
|
||||||
|
|
||||||
const [projects, setProjects] = useState([]);
|
const [projects, setProjects] = useState([]);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [error, setError] = useState("");
|
const [error, setError] = useState("");
|
||||||
|
|
||||||
const fetchData = async () => {
|
const fetchData = async () => {
|
||||||
const projects_cache = getCachedData("projectslist");
|
const projects_cache = getCachedData("projectslist");
|
||||||
|
|
||||||
if (!projects_cache) {
|
if (!projects_cache) {
|
||||||
setLoading(true)
|
setLoading(true);
|
||||||
ProjectRepository.getProjectList()
|
ProjectRepository.getProjectList()
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
setProjects(response);
|
let projects = response.data;
|
||||||
|
const sortedProject = [...projects].sort((a, b) =>
|
||||||
cacheData("projectslist", response);
|
a.name.localeCompare(b.name)
|
||||||
|
);
|
||||||
|
|
||||||
|
setProjects(sortedProject);
|
||||||
|
|
||||||
|
cacheData("projectslist", sortedProject);
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
setLoading(false)
|
setLoading(false);
|
||||||
console.error(error);
|
|
||||||
setError("Failed to fetch data.");
|
setError("Failed to fetch data.");
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
} else {
|
if (!projects.length) {
|
||||||
if (!projects.length) setProjects(projects_cache);
|
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 ) =>
|
return { projects, loading, error, refetch: fetchData };
|
||||||
{
|
};
|
||||||
const [projectEmployees, setEmployeeList] = useState([]);
|
|
||||||
const[loading,setLoading] = useState(true)
|
export const useEmployeesByProjectAllocated = (selectedProject) => {
|
||||||
const [projects, setProjects] = useState([]);
|
const [projectEmployees, setEmployeeList] = useState([]);
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
const fetchData = async (projectid) => {
|
const [projects, setProjects] = useState([]);
|
||||||
try {
|
|
||||||
let EmployeeByProject_Cache = getCachedData("empListByProjectAllocated")
|
const fetchData = async (projectid) => {
|
||||||
if(!EmployeeByProject_Cache || !EmployeeByProject_Cache.projectId === projectid) {
|
try {
|
||||||
|
let EmployeeByProject_Cache = getCachedData("empListByProjectAllocated");
|
||||||
let response = await ProjectRepository.getProjectAllocation(projectid)
|
if (
|
||||||
setEmployeeList(response.data);
|
!EmployeeByProject_Cache ||
|
||||||
cacheData("empListByProjectAllocated",{data:response.data,projectId:projectid});
|
!EmployeeByProject_Cache.projectId === projectid
|
||||||
setLoading(false)
|
) {
|
||||||
}else{
|
let response = await ProjectRepository.getProjectAllocation(projectid);
|
||||||
setEmployeeList(EmployeeByProject_Cache.data)
|
setEmployeeList(response.data);
|
||||||
setLoading(false)
|
cacheData("empListByProjectAllocated", {
|
||||||
}
|
data: response.data,
|
||||||
|
projectId: projectid,
|
||||||
|
});
|
||||||
} catch (err) {
|
setLoading(false);
|
||||||
setError("Failed to fetch data.");
|
} else {
|
||||||
setLoading(false)
|
setEmployeeList(EmployeeByProject_Cache.data);
|
||||||
}
|
setLoading(false);
|
||||||
};
|
}
|
||||||
|
} catch (err) {
|
||||||
|
setError("Failed to fetch data.");
|
||||||
useEffect(()=>{
|
setLoading(false);
|
||||||
if(selectedProject){
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (selectedProject) {
|
||||||
fetchData(selectedProject);
|
fetchData(selectedProject);
|
||||||
}
|
}
|
||||||
},[selectedProject])
|
}, [selectedProject]);
|
||||||
|
|
||||||
|
return { projectEmployees, loading, projects };
|
||||||
return {projectEmployees,loading,projects}
|
};
|
||||||
}
|
|
||||||
|
export const useProjectDetails = (projectId) => {
|
||||||
export const useProjectDetails =(projectId)=>{
|
const { profile } = useProfile();
|
||||||
|
|
||||||
const [projects_Details, setProject_Details] = useState(null);
|
const [projects_Details, setProject_Details] = useState(null);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(true);
|
||||||
const [error, setError] = useState("");
|
const [error, setError] = useState("");
|
||||||
|
|
||||||
const fetchData = async () => {
|
const fetchData = async () => {
|
||||||
setLoading(true)
|
setLoading(true);
|
||||||
const project_cache = getCachedData(`projectinfo-${projectId}`);
|
|
||||||
if (!project_cache) {
|
const project_cache = getCachedData("projectInfo");
|
||||||
|
if (!project_cache || project_cache?.projectId != projectId) {
|
||||||
ProjectRepository.getProjectByprojectId(projectId)
|
ProjectRepository.getProjectByprojectId(projectId)
|
||||||
.then( ( response ) =>
|
.then((response) => {
|
||||||
{
|
setProject_Details(response.data);
|
||||||
setProject_Details(response);
|
cacheData("projectInfo", {
|
||||||
cacheData( `projectinfo-${ projectId }`, response );
|
projectId: projectId,
|
||||||
setLoading(false)
|
data: response.data,
|
||||||
|
});
|
||||||
|
setLoading(false);
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
setError( "Failed to fetch data." );
|
setError("Failed to fetch data.");
|
||||||
setLoading(false)
|
setLoading(false);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
setProject_Details( project_cache );
|
setProject_Details(project_cache.data);
|
||||||
|
setLoading(false);
|
||||||
setLoading(false)
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
};;
|
|
||||||
|
|
||||||
useEffect(()=>{
|
|
||||||
fetchData()
|
|
||||||
},[projectId])
|
|
||||||
|
|
||||||
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
43
src/hooks/useTasks.js
Normal 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};
|
||||||
|
};
|
||||||
@ -6,14 +6,17 @@ import App from './App.tsx'
|
|||||||
|
|
||||||
import { Provider } from 'react-redux';
|
import { Provider } from 'react-redux';
|
||||||
import { store } from './store/store';
|
import { store } from './store/store';
|
||||||
|
import { ModalProvider } from './ModalContext.jsx';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
createRoot(document.getElementById('root')!).render(
|
createRoot(document.getElementById('root')!).render(
|
||||||
// <StrictMode>
|
// <StrictMode>
|
||||||
// <MasterDataProvider>
|
// <MasterDataProvider>
|
||||||
<Provider store={store}>
|
<Provider store={ store }>
|
||||||
<App />
|
<ModalProvider>
|
||||||
|
<App />
|
||||||
|
</ModalProvider>
|
||||||
</Provider>
|
</Provider>
|
||||||
// </MasterDataProvider>
|
// </MasterDataProvider>
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,9 @@
|
|||||||
import React, { useState, useEffect } from "react";
|
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 Breadcrumb from "../../components/common/Breadcrumb";
|
||||||
import AttendanceLog from "../../components/Activities/AttendcesLogs";
|
import AttendanceLog from "../../components/Activities/AttendcesLogs";
|
||||||
import Attendance from "../../components/Activities/Attendance";
|
import Attendance from "../../components/Activities/Attendance";
|
||||||
@ -10,26 +14,25 @@ import Regularization from "../../components/Activities/Regularization";
|
|||||||
import { useAttendace } from "../../hooks/useAttendance";
|
import { useAttendace } from "../../hooks/useAttendance";
|
||||||
import { useDispatch, useSelector } from "react-redux";
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
import { setProjectId } from "../../slices/localVariablesSlice";
|
import { setProjectId } from "../../slices/localVariablesSlice";
|
||||||
import {markCurrentAttendance} from "../../slices/apiSlice/attendanceAllSlice";
|
import { markCurrentAttendance } from "../../slices/apiSlice/attendanceAllSlice";
|
||||||
import { hasUserPermission } from "../../utils/authUtils";
|
import { hasUserPermission } from "../../utils/authUtils";
|
||||||
import {useHasUserPermission} from "../../hooks/useHasUserPermission";
|
import { useHasUserPermission } from "../../hooks/useHasUserPermission";
|
||||||
import {REGULARIZE_ATTENDANCE} from "../../utils/constants";
|
import { REGULARIZE_ATTENDANCE } from "../../utils/constants";
|
||||||
|
|
||||||
const AttendancePage = () =>
|
const AttendancePage = () => {
|
||||||
{
|
const loginUser = getCachedProfileData();
|
||||||
const loginUser = getCachedProfileData()
|
var selectedProject = useSelector((store) => store.localVariables.projectId);
|
||||||
var selectedProject = useSelector( ( store ) => store.localVariables.projectId )
|
const { projects, loading: projectLoading } = useProjects();
|
||||||
const {projects,loading:projectLoading} = useProjects()
|
const {attendance, loading: attLoading} = useAttendace( selectedProject );
|
||||||
const {attendance,loading:attLoading} = useAttendace(selectedProject)
|
const [ attendances, setAttendances ] = useState();
|
||||||
const[attendances,setAttendances] = useState()
|
|
||||||
const [empRoles, setEmpRoles] = useState(null);
|
const [empRoles, setEmpRoles] = useState(null);
|
||||||
const [isCreateModalOpen, setIsCreateModalOpen] = useState(false);
|
const [isCreateModalOpen, setIsCreateModalOpen] = useState(false);
|
||||||
const [ modelConfig, setModelConfig ] = useState();
|
const [modelConfig, setModelConfig] = useState();
|
||||||
const DoRegularized = useHasUserPermission(REGULARIZE_ATTENDANCE)
|
const DoRegularized = useHasUserPermission(REGULARIZE_ATTENDANCE);
|
||||||
const dispatch = useDispatch()
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
const [formData, setFormData] = useState({
|
const [formData, setFormData] = useState({
|
||||||
time: "",
|
markTime: "",
|
||||||
description: "",
|
description: "",
|
||||||
date: new Date().toLocaleDateString(),
|
date: new Date().toLocaleDateString(),
|
||||||
});
|
});
|
||||||
@ -46,25 +49,28 @@ const AttendancePage = () =>
|
|||||||
setIsCreateModalOpen(true);
|
setIsCreateModalOpen(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleModalData = ( employee ) =>
|
||||||
const handleModalData =(employee)=>{
|
{
|
||||||
|
|
||||||
setModelConfig(employee);
|
setModelConfig(employee);
|
||||||
}
|
};
|
||||||
|
|
||||||
const closeModal = () => {
|
const closeModal = () => {
|
||||||
setModelConfig(null);
|
setModelConfig(null);
|
||||||
setIsCreateModalOpen(false);
|
setIsCreateModalOpen(false);
|
||||||
const modalElement = document.getElementById("check-Out-modal");
|
const modalElement = document.getElementById("check-Out-modal");
|
||||||
if (modalElement) {
|
if (modalElement) {
|
||||||
modalElement.classList.remove('show');
|
modalElement.classList.remove("show");
|
||||||
modalElement.style.display = 'none';
|
modalElement.style.display = "none";
|
||||||
document.body.classList.remove('modal-open');
|
document.body.classList.remove("modal-open");
|
||||||
document.querySelector('.modal-backdrop').remove();
|
document.querySelector(".modal-backdrop").remove();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
const handleSubmit = ( formData ) =>{
|
const handleSubmit = ( formData ) =>
|
||||||
|
{
|
||||||
|
|
||||||
dispatch( markCurrentAttendance( formData ) ).then( ( action ) =>
|
dispatch( markCurrentAttendance( formData ) ).then( ( action ) =>
|
||||||
{
|
{
|
||||||
const updatedAttendance = attendances.map(item =>
|
const updatedAttendance = attendances.map(item =>
|
||||||
@ -82,33 +88,40 @@ const AttendancePage = () =>
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (modelConfig !== null) {
|
if (modelConfig !== null) {
|
||||||
openModel();
|
openModel();
|
||||||
}
|
}
|
||||||
}, [modelConfig,isCreateModalOpen]);
|
}, [modelConfig, isCreateModalOpen]);
|
||||||
useEffect(()=>{
|
useEffect(() => {
|
||||||
setAttendances(attendance)
|
setAttendances( attendance );
|
||||||
},[attendance])
|
}, [attendance]);
|
||||||
|
useEffect(() => {
|
||||||
|
dispatch(setProjectId(projects[0]?.id));
|
||||||
|
}, [projects]);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{isCreateModalOpen && modelConfig && (
|
{isCreateModalOpen && modelConfig && (
|
||||||
<div
|
<div
|
||||||
className="modal fade show"
|
className="modal fade show"
|
||||||
style={{ display: "block" }}
|
style={{ display: "block" }}
|
||||||
id="check-Out-modal"
|
id="check-Out-modal"
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
>
|
>
|
||||||
<AttendanceModel modelConfig={modelConfig} closeModal={closeModal} handleSubmitForm={handleSubmit}/>
|
<AttendanceModel
|
||||||
|
modelConfig={modelConfig}
|
||||||
|
closeModal={closeModal}
|
||||||
|
handleSubmitForm={handleSubmit}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="container-xxl flex-grow-1 container-p-y">
|
<div className="container-xxl flex-grow-1 container-p-y">
|
||||||
<Breadcrumb
|
<Breadcrumb
|
||||||
data={[
|
data={[
|
||||||
{ label: "Home", link: "/dashboard" },
|
{ label: "Home", link: "/dashboard" },
|
||||||
{ label: "Attendance", link: null },
|
{ label: "Attendance", link: null },
|
||||||
@ -121,7 +134,7 @@ const AttendancePage = () =>
|
|||||||
id="DataTables_Table_0_length"
|
id="DataTables_Table_0_length"
|
||||||
>
|
>
|
||||||
{
|
{
|
||||||
((loginUser && loginUser?.projects.length > 1) ) && (<label>
|
((loginUser && loginUser?.projects?.length > 1) ) && (<label>
|
||||||
<select
|
<select
|
||||||
name="DataTables_Table_0_length"
|
name="DataTables_Table_0_length"
|
||||||
aria-controls="DataTables_Table_0"
|
aria-controls="DataTables_Table_0"
|
||||||
@ -132,7 +145,7 @@ const AttendancePage = () =>
|
|||||||
>
|
>
|
||||||
{!projectLoading && projects?.filter(project =>
|
{!projectLoading && projects?.filter(project =>
|
||||||
loginUser?.projects?.map(Number).includes(project.id)).map((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> }
|
{projectLoading && <option value="Loading..." disabled>Loading...</option> }
|
||||||
</select>
|
</select>
|
||||||
@ -165,7 +178,6 @@ const AttendancePage = () =>
|
|||||||
Logs
|
Logs
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li className={`nav-item ${!DoRegularized && 'd-none'}`}>
|
<li className={`nav-item ${!DoRegularized && 'd-none'}`}>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@ -174,43 +186,55 @@ const AttendancePage = () =>
|
|||||||
data-bs-toggle="tab"
|
data-bs-toggle="tab"
|
||||||
data-bs-target="#navs-top-messages"
|
data-bs-target="#navs-top-messages"
|
||||||
aria-controls="navs-top-messages"
|
aria-controls="navs-top-messages"
|
||||||
aria-selected="false">
|
aria-selected="false"
|
||||||
|
>
|
||||||
Regularization
|
Regularization
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
</li>
|
</li>
|
||||||
|
</ul>
|
||||||
</ul>
|
<div className="tab-content attedanceTabs py-2">
|
||||||
<div class="tab-content attedanceTabs py-2">
|
{projectLoading && <span>Loading..</span>}
|
||||||
{projectLoading && (<span>Loading..</span>)}
|
{!projectLoading && !attendances && <span>Not Found</span>}
|
||||||
{(!projectLoading && !attendances) && <span>Not Found</span>}
|
{projects && projects.length > 0 && (
|
||||||
{ (projects && projects.length > 0 ) && (
|
<>
|
||||||
<>
|
<div
|
||||||
<div className="tab-pane fade show active py-0" id="navs-top-home" role="tabpanel" key={projects.id}>
|
className="tab-pane fade show active py-0"
|
||||||
|
id="navs-top-home"
|
||||||
<Attendance attendance={attendances} handleModalData={handleModalData} getRole={getRole} />
|
role="tabpanel"
|
||||||
</div>
|
key={projects.id}
|
||||||
<div class="tab-pane fade" id="navs-top-profile" role="tabpanel">
|
>
|
||||||
|
<Attendance
|
||||||
<AttendanceLog
|
attendance={attendances}
|
||||||
attendance={attendances}
|
handleModalData={handleModalData}
|
||||||
handleModalData={handleModalData}
|
getRole={getRole}
|
||||||
projectId={selectedProject}
|
/>
|
||||||
/>
|
</div>
|
||||||
</div>
|
<div
|
||||||
<div className="tab-pane fade" id="navs-top-messages" role="tabpanel">
|
className="tab-pane fade"
|
||||||
<Regularization
|
id="navs-top-profile"
|
||||||
attendance={attendances}
|
role="tabpanel"
|
||||||
handleRequest ={handleSubmit}
|
>
|
||||||
/>
|
<AttendanceLog
|
||||||
</div>
|
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>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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"
|
const DailyTask = () => {
|
||||||
import { dailyTask } from "../../data/masters"
|
const { profile: LoggedUser } = useProfile();
|
||||||
const DailyTask =()=>{
|
|
||||||
return (
|
const {
|
||||||
<>
|
projects,
|
||||||
<div className="container-xxl flex-grow-1 container-p-y">
|
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
|
<Breadcrumb
|
||||||
data={[
|
data={[
|
||||||
{ label: "Home", link: "/dashboard" },
|
{ label: "Home", link: "/dashboard" },
|
||||||
{ label: "Attendance", link: null },
|
{ label: "Daily Task", link: null },
|
||||||
]}
|
]}
|
||||||
></Breadcrumb>
|
></Breadcrumb>
|
||||||
<div className="card card-action mb-6">
|
<div className="card card-action mb-6">
|
||||||
<div className="card-body">
|
<div className="card-body p-1 p-sm-2">
|
||||||
<div className="row">
|
<div className="row d-flex justify-content-between">
|
||||||
{/* <div className="col-12 text-end mb-1">
|
<div className="col-6 text-start">
|
||||||
<button
|
<DateRangePicker onRangeChange={setDateRange} />
|
||||||
type="button"
|
</div>
|
||||||
className="link-button link-button-sm m-1"
|
<div className="col-sm-3 col-6 text-end mb-1">
|
||||||
data-bs-toggle="modal"
|
<select
|
||||||
data-bs-target="#user-model"
|
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>
|
{(project_lodaing || projects.length < 0) && (
|
||||||
Assign Employee
|
<option value="Loading..." disabled>
|
||||||
</button>
|
Loading...
|
||||||
</div> */}
|
</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>
|
||||||
<div className="table-responsive text-nowrap">
|
<div className="table-responsive text-nowrap">
|
||||||
{/* {employees && employees.length > 0 ? ( */}
|
<table className="table">
|
||||||
<table className="table ">
|
<thead>
|
||||||
<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>
|
<tr>
|
||||||
<th>Sr</th>
|
<td colSpan={7} className="text-center">
|
||||||
<th>Project Name</th>
|
No Data Found
|
||||||
<th>Target</th>
|
</td>
|
||||||
<th>Employees</th>
|
|
||||||
|
|
||||||
|
|
||||||
<th>Actions</th>
|
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
)}
|
||||||
<tbody className="table-border-bottom-0">
|
|
||||||
|
{task_loading && (
|
||||||
<tr >
|
<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>
|
<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">
|
<div className="d-flex flex-column">
|
||||||
<a href="#" className="text-heading text-truncate">
|
<a
|
||||||
<span className="fw-medium ">
|
href="#"
|
||||||
1
|
className="text-heading text-truncate"
|
||||||
</span>
|
>
|
||||||
|
<i className="bx bx-chevron-right"></i>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td className="flex-wrap">
|
<td className="flex-wrap">
|
||||||
project Name project Name project Name
|
{task.workItem.activityMaster.activityName ||
|
||||||
|
"No Activity Name"}
|
||||||
</td>
|
</td>
|
||||||
|
<td>{task.plannedTask || "NA"}</td>
|
||||||
<td>
|
<td>{task.completedTask}</td>
|
||||||
80
|
<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>
|
||||||
|
<td className="text-center">
|
||||||
<td> NA</td>
|
<div className="d-flex justify-content-center">
|
||||||
|
<button
|
||||||
<td>
|
type="button"
|
||||||
|
className={`btn btn-xs btn-primary ${
|
||||||
<button type="button" className="btn btn-xs btn-primary">Report</button>
|
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>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{/* ))} */}
|
|
||||||
</tbody>
|
{/* Accordion Content */}
|
||||||
</table>
|
<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>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)
|
);
|
||||||
}
|
};
|
||||||
export default DailyTask
|
export default DailyTask;
|
||||||
|
|||||||
@ -6,10 +6,19 @@ import ProjectRepository from "../../repositories/ProjectRepository";
|
|||||||
import Breadcrumb from "../../components/common/Breadcrumb";
|
import Breadcrumb from "../../components/common/Breadcrumb";
|
||||||
import InfraPlanning from "../../components/Activities/InfraPlanning";
|
import InfraPlanning from "../../components/Activities/InfraPlanning";
|
||||||
import { cacheData, getCachedData } from "../../slices/apiDataManager";
|
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;
|
var projectId;
|
||||||
const TaskPlannng = () => {
|
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 [project, setProject] = useState(null);
|
||||||
const [projectDetails, setProjectDetails] = useState(null);
|
const [projectDetails, setProjectDetails] = useState(null);
|
||||||
@ -17,6 +26,10 @@ const TaskPlannng = () => {
|
|||||||
|
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [error, setError] = useState("");
|
const [error, setError] = useState("");
|
||||||
|
useEffect( () =>
|
||||||
|
{
|
||||||
|
dispatch(setProjectId(projects[0]?.id))
|
||||||
|
},[projects])
|
||||||
|
|
||||||
const fetchActivities = async () => {
|
const fetchActivities = async () => {
|
||||||
try {
|
try {
|
||||||
@ -25,22 +38,12 @@ const TaskPlannng = () => {
|
|||||||
if (!activities_cache) {
|
if (!activities_cache) {
|
||||||
ActivityeRepository.getActivities()
|
ActivityeRepository.getActivities()
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
setActivities(response);
|
setActivities(response.data);
|
||||||
cacheData("activitiesMaster", response);
|
cacheData("activitiesMaster", response.data);
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
setError("Failed to fetch data.");
|
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 {
|
} else {
|
||||||
setActivities(activities_cache);
|
setActivities(activities_cache);
|
||||||
}
|
}
|
||||||
@ -53,9 +56,9 @@ const TaskPlannng = () => {
|
|||||||
|
|
||||||
const fetchData = async () => {
|
const fetchData = async () => {
|
||||||
try {
|
try {
|
||||||
const project_cache = getCachedData(`projectinfo-${1}`);
|
const project_cache = getCachedData(`projectinfo-${selectedProject}`);
|
||||||
if (!project_cache) {
|
if (!project_cache) {
|
||||||
ProjectRepository.getProjectByprojectId(1)
|
ProjectRepository.getProjectByprojectId(selectedProject)
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
setProjectDetails(response);
|
setProjectDetails(response);
|
||||||
setProject(response);
|
setProject(response);
|
||||||
@ -68,21 +71,6 @@ const TaskPlannng = () => {
|
|||||||
} else {
|
} else {
|
||||||
setProjectDetails(project_cache);
|
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) {
|
} catch (err) {
|
||||||
console.log(err)
|
console.log(err)
|
||||||
setError("Failed to fetch data.");
|
setError("Failed to fetch data.");
|
||||||
@ -98,14 +86,16 @@ const TaskPlannng = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleDataChange = (data) => {
|
const handleDataChange = (data) => {
|
||||||
|
console.log("datachange")
|
||||||
fetchData();
|
fetchData();
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
projectId =1
|
if((projects.length != 0)){
|
||||||
fetchData();
|
fetchData();
|
||||||
fetchActivities();
|
fetchActivities();
|
||||||
}, []);
|
}
|
||||||
|
}, [selectedProject]);
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -121,7 +111,7 @@ const TaskPlannng = () => {
|
|||||||
<InfraPlanning
|
<InfraPlanning
|
||||||
data={projectDetails}
|
data={projectDetails}
|
||||||
activityMaster={activities}
|
activityMaster={activities}
|
||||||
onDataChange={handleDataChange}
|
onDataChange={handleDataChange}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@ -1,7 +1,8 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
|
import { ComingSoonPage } from "../Misc/ComingSoonPage";
|
||||||
|
|
||||||
const ImageGallary = () => {
|
const ImageGallary = () => {
|
||||||
return <div>Image Gallery</div>;
|
return <ComingSoonPage></ComingSoonPage>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ImageGallary;
|
export default ImageGallary;
|
||||||
|
|||||||
24
src/pages/Misc/ComingSoonPage.jsx
Normal file
24
src/pages/Misc/ComingSoonPage.jsx
Normal 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>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
@ -15,7 +15,7 @@ export const AuthWrapper = ({ children }) => {
|
|||||||
className="app-brand-link gap-2"
|
className="app-brand-link gap-2"
|
||||||
>
|
>
|
||||||
<span className="app-brand-logo demo">
|
<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>
|
||||||
{/* <span className="app-brand-text demo text-body fw-bold">
|
{/* <span className="app-brand-text demo text-body fw-bold">
|
||||||
Sneat
|
Sneat
|
||||||
|
|||||||
@ -4,45 +4,54 @@ import { AuthWrapper } from "./AuthWrapper"
|
|||||||
import "./page-auth.css";
|
import "./page-auth.css";
|
||||||
import AuthRepository from "../../repositories/AuthRepository";
|
import AuthRepository from "../../repositories/AuthRepository";
|
||||||
import showToast from "../../services/toastService";
|
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 ForgotPasswordPage = () => {
|
||||||
const [ email, setEmail ] = useState( "" );
|
|
||||||
const[loding,setLoading] = useState(false)
|
const[loding,setLoading] = useState(false)
|
||||||
|
|
||||||
const handleChange = (e) => {
|
const {register,
|
||||||
setEmail(e.target.value);
|
handleSubmit,
|
||||||
};
|
formState: { errors },
|
||||||
|
reset,
|
||||||
|
getValues } = useForm( {
|
||||||
|
resolver: zodResolver( forgotPassSceham ),
|
||||||
|
defaultValues: {
|
||||||
|
email:""
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
const handleSubmit = async ( e ) =>
|
const onSubmit = async (data) =>
|
||||||
{
|
{
|
||||||
setLoading(true)
|
|
||||||
e.preventDefault();
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
const response = await AuthRepository.forgotPassword({email})
|
setLoading(true)
|
||||||
|
const response = await AuthRepository.forgotPassword(data)
|
||||||
if ( response.data && response.success )
|
if ( response.data && response.success )
|
||||||
{
|
|
||||||
showToast( response.message, "success" )
|
showToast( response.message, "success" )
|
||||||
} else
|
reset()
|
||||||
{
|
|
||||||
showToast( response.message, "warning" )
|
|
||||||
}
|
|
||||||
setLoading( false )
|
setLoading( false )
|
||||||
setEmail("")
|
} catch ( err )
|
||||||
} catch ( error )
|
|
||||||
{
|
{
|
||||||
showToast( "User Not Found", "error" )
|
|
||||||
|
showToast( err.message, "error" )
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
return (
|
return (
|
||||||
<AuthWrapper>
|
<AuthWrapper>
|
||||||
<h4 className="mb-2">Forgot Password? 🔒</h4>
|
<h4 className="mb-2">Forgot Password? 🔒</h4>
|
||||||
<p className="mb-4">
|
<p className="mb-4">
|
||||||
Enter your email and we'll send you instructions to reset your password
|
Enter your email and we'll send you instructions to reset your password
|
||||||
</p>
|
</p>
|
||||||
<form id="formAuthentication" className="mb-3" onSubmit={handleSubmit}>
|
<form id="formAuthentication" className="mb-3" onSubmit={handleSubmit(onSubmit)}>
|
||||||
<div className="mb-3">
|
<div className="mb-3">
|
||||||
<label htmlFor="email" className="form-label">
|
<label htmlFor="email" className="form-label">
|
||||||
Email
|
Email
|
||||||
@ -52,11 +61,18 @@ const ForgotPasswordPage = () => {
|
|||||||
className="form-control"
|
className="form-control"
|
||||||
id="email"
|
id="email"
|
||||||
name="email"
|
name="email"
|
||||||
value={email}
|
{...register("email")}
|
||||||
onChange={handleChange}
|
|
||||||
placeholder="Enter your email"
|
placeholder="Enter your email"
|
||||||
autoFocus
|
autoFocus
|
||||||
/>
|
/>
|
||||||
|
{errors.email && (
|
||||||
|
<div
|
||||||
|
className="danger-text text-start"
|
||||||
|
style={{ fontSize: "12px" }}
|
||||||
|
>
|
||||||
|
{errors.email.message}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<button aria-label="Click me" className="btn btn-primary d-grid w-100">
|
<button aria-label="Click me" className="btn btn-primary d-grid w-100">
|
||||||
{loding ? "Please Wait...":"Send Reset Link"}
|
{loding ? "Please Wait...":"Send Reset Link"}
|
||||||
|
|||||||
@ -5,49 +5,50 @@ import { useNavigate } from "react-router-dom";
|
|||||||
import "./page-auth.css";
|
import "./page-auth.css";
|
||||||
import AuthRepository from "../../repositories/AuthRepository";
|
import AuthRepository from "../../repositories/AuthRepository";
|
||||||
import showToast from "../../services/toastService";
|
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 LoginPage = () => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [hidepass, setHidepass] = useState(true);
|
||||||
|
|
||||||
const [formData, setFormData] = useState({
|
const {
|
||||||
password: "",
|
register,
|
||||||
username: "",
|
handleSubmit,
|
||||||
rememberMe: false,
|
formState: { errors },
|
||||||
|
reset,
|
||||||
|
getValues,
|
||||||
|
} = useForm({
|
||||||
|
resolver: zodResolver(loginScheam),
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleChange = (e) => {
|
const onSubmit = async (data) => {
|
||||||
const { name, value, type, checked } = e.target;
|
|
||||||
|
|
||||||
setFormData((prevData) => ({
|
|
||||||
...prevData,
|
|
||||||
[name]: type === "checkbox" ? checked : value,
|
|
||||||
}));
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSubmit = async ( e ) =>
|
|
||||||
{
|
|
||||||
|
|
||||||
e.preventDefault();
|
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let data = {
|
let userCredential = {
|
||||||
username: formData.username,
|
username: data.username,
|
||||||
password: formData.password,
|
password: data.password,
|
||||||
};
|
};
|
||||||
|
const response = await AuthRepository.login(userCredential);
|
||||||
const response = await AuthRepository.login(data);
|
|
||||||
localStorage.setItem("jwtToken", response.data.token);
|
localStorage.setItem("jwtToken", response.data.token);
|
||||||
localStorage.setItem( "refreshToken", response.data.refreshToken );
|
localStorage.setItem("refreshToken", response.data.refreshToken);
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
navigate("/dashboard");
|
navigate("/dashboard");
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log("Unable to proceed. Please try again.");
|
console.log("Unable to proceed. Please try again.");
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AuthWrapper>
|
<AuthWrapper>
|
||||||
<h4 className="mb-2">Welcome to PMS!</h4>
|
<h4 className="mb-2">Welcome to PMS!</h4>
|
||||||
@ -55,8 +56,12 @@ const LoginPage = () => {
|
|||||||
Please sign-in to your account and start the adventure
|
Please sign-in to your account and start the adventure
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<form id="formAuthentication" className="mb-3" onSubmit={handleSubmit}>
|
<form
|
||||||
<div className="mb-3">
|
id="formAuthentication"
|
||||||
|
className="mb-3"
|
||||||
|
onSubmit={handleSubmit(onSubmit)}
|
||||||
|
>
|
||||||
|
<div className="mb-2">
|
||||||
<label htmlFor="username" className="form-label">
|
<label htmlFor="username" className="form-label">
|
||||||
Email or Username
|
Email or Username
|
||||||
</label>
|
</label>
|
||||||
@ -64,55 +69,77 @@ const LoginPage = () => {
|
|||||||
type="text"
|
type="text"
|
||||||
className="form-control"
|
className="form-control"
|
||||||
id="username"
|
id="username"
|
||||||
value={formData.name}
|
{...register("username")}
|
||||||
onChange={handleChange}
|
|
||||||
name="username"
|
name="username"
|
||||||
placeholder="Enter your email or username"
|
placeholder="Enter your email or username"
|
||||||
autoFocus
|
autoFocus
|
||||||
/>
|
/>
|
||||||
|
{errors.username && (
|
||||||
|
<div
|
||||||
|
className="danger-text text-start"
|
||||||
|
style={{ fontSize: "12px" }}
|
||||||
|
>
|
||||||
|
{errors.username.message}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="mb-3 form-password-toggle">
|
<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">
|
<label className="form-label" htmlFor="password">
|
||||||
Password
|
Password
|
||||||
</label>
|
</label>
|
||||||
<Link
|
|
||||||
aria-label="Go to Forgot Password Page"
|
|
||||||
to="/auth/forgot-password"
|
|
||||||
>
|
|
||||||
<small>Forgot Password?</small>
|
|
||||||
</Link>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="input-group input-group-merge">
|
<div className="input-group input-group-merge">
|
||||||
<input
|
<input
|
||||||
type="password"
|
type={hidepass ? "password" : "text"}
|
||||||
autoComplete="true"
|
autoComplete="true"
|
||||||
id="password"
|
id="password"
|
||||||
value={formData.password}
|
{...register("password")}
|
||||||
onChange={handleChange}
|
|
||||||
className="form-control"
|
className="form-control"
|
||||||
name="password"
|
name="password"
|
||||||
placeholder="············"
|
placeholder="············"
|
||||||
aria-describedby="password"
|
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>
|
</div>
|
||||||
|
{errors.password && (
|
||||||
|
<div
|
||||||
|
className="danger-text text-start"
|
||||||
|
style={{ fontSize: "12px" }}
|
||||||
|
>
|
||||||
|
{errors.password.message}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="mb-3">
|
<div className="mb-3 d-flex justify-content-between">
|
||||||
<div className="form-check">
|
<div className="form-check d-flex">
|
||||||
<input
|
<input
|
||||||
className="form-check-input"
|
className="form-check-input"
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
id="remember-me"
|
id="remember-me"
|
||||||
name="rememberMe"
|
name="rememberMe"
|
||||||
checked={formData.rememberMe}
|
{...register("rememberMe")}
|
||||||
onChange={handleChange}
|
|
||||||
/>
|
/>
|
||||||
<label className="form-check-label" htmlFor="remember-me">
|
<label className="form-check-label ms-2" >
|
||||||
{" "}
|
Remember Me
|
||||||
Remember Me{" "}
|
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
<Link
|
||||||
|
aria-label="Go to Forgot Password Page"
|
||||||
|
to="/auth/forgot-password"
|
||||||
|
>
|
||||||
|
<span>Forgot Password?</span>
|
||||||
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
<div className="mb-3">
|
<div className="mb-3">
|
||||||
<button
|
<button
|
||||||
|
|||||||
@ -4,68 +4,80 @@ import "./page-auth.css";
|
|||||||
import { AuthWrapper } from "./AuthWrapper";
|
import { AuthWrapper } from "./AuthWrapper";
|
||||||
import showToast from "../../services/toastService";
|
import showToast from "../../services/toastService";
|
||||||
import AuthRepository from "../../repositories/AuthRepository";
|
import AuthRepository from "../../repositories/AuthRepository";
|
||||||
import { z } from 'zod';
|
import { z } from "zod";
|
||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from "react-hook-form";
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import {clearAllCache} from "../../slices/apiDataManager";
|
import { clearAllCache } from "../../slices/apiDataManager";
|
||||||
|
|
||||||
const resetPasswordSchema = z.object( {
|
const resetPasswordSchema = z
|
||||||
email:z.string().email(),
|
.object({
|
||||||
password: z
|
email: z.string().email(),
|
||||||
.string()
|
password: z
|
||||||
.min(8, 'Password must be at least 8 characters')
|
.string()
|
||||||
.regex(/[A-Z]/, 'Password must contain at least one uppercase letter')
|
.min(8, "Password must be at least 8 characters")
|
||||||
.regex(/[a-z]/, 'Password must contain at least one lowercase letter')
|
.regex(/[A-Z]/, "Password must contain at least one uppercase letter")
|
||||||
.regex(/\d/, 'Password must contain at least one number')
|
.regex(/[a-z]/, "Password must contain at least one lowercase letter")
|
||||||
.regex(/[!@#$%^&*()_+{}\[\]:;<>,.?~\\/-]/, 'Password must contain at least one special character'),
|
.regex(/\d/, "Password must contain at least one number")
|
||||||
|
.regex(
|
||||||
confirmPassword: z.string().min(8, 'Password must be at least 8 characters'),
|
/[!@#$%^&*()_+{}\[\]:;<>,.?~\\/-]/,
|
||||||
})
|
"Password must contain at least one special character"
|
||||||
.refine((data) => data.password === data.confirmPassword, {
|
),
|
||||||
message: 'Passwords do not match',
|
|
||||||
path: ['confirmPassword'],
|
|
||||||
} );
|
|
||||||
|
|
||||||
const ResetPasswordPage = () =>
|
|
||||||
{
|
|
||||||
const [ searchParams ] = useSearchParams();
|
|
||||||
const [loading,setLoading] = useState(false)
|
|
||||||
|
|
||||||
const token = searchParams.get('token');
|
confirmPassword: z
|
||||||
const navigate = useNavigate()
|
.string()
|
||||||
|
.min(8, "Password must be at least 8 characters"),
|
||||||
const {register,handleSubmit,formState: { errors }} = useForm({
|
|
||||||
resolver:zodResolver(resetPasswordSchema)
|
|
||||||
})
|
})
|
||||||
|
.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 = {
|
let reqObject = {
|
||||||
email,
|
email,
|
||||||
token: token,
|
token: token,
|
||||||
newPassword: password
|
newPassword: password,
|
||||||
}
|
};
|
||||||
let response = await AuthRepository.resetPassword( reqObject );
|
let response = await AuthRepository.resetPassword(reqObject);
|
||||||
showToast( "Password Reseted", "success" )
|
showToast("Password Reseted", "success");
|
||||||
clearAllCache()
|
clearAllCache();
|
||||||
setLoading(false)
|
setLoading(false);
|
||||||
navigate("/auth/login",{replace:true})
|
navigate("/auth/login", { replace: true });
|
||||||
} catch ( error )
|
} catch (error) {
|
||||||
{
|
setLoading(false);
|
||||||
setLoading(false)
|
showToast("Token is expries or Invalid ", "error");
|
||||||
showToast("Token is expries or Invalid ","error")
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<AuthWrapper>
|
<AuthWrapper>
|
||||||
<h4 className="mb-2">Reset Password? 🔒</h4>
|
<h4 className="mb-2">Reset Password? 🔒</h4>
|
||||||
<p className="mb-4">Enter your email and new password to update.</p>
|
<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">
|
<div className="mb-3">
|
||||||
<label htmlFor="email" className="form-label">
|
<label htmlFor="email" className="form-label">
|
||||||
Email
|
Email
|
||||||
@ -78,48 +90,106 @@ const ResetPasswordPage = () =>
|
|||||||
placeholder="Enter your email"
|
placeholder="Enter your email"
|
||||||
autoFocus
|
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>
|
||||||
<div className="mb-3">
|
|
||||||
<label htmlFor="email" className="form-label">
|
<div className="mb-2 form-password-toggle">
|
||||||
New Password
|
<div className="mt-2">
|
||||||
</label>
|
<label htmlFor="email" className="form-label list-group-item">
|
||||||
<input
|
New Password
|
||||||
type="password"
|
</label>
|
||||||
autoComplete="true"
|
</div>
|
||||||
id="password"
|
<div className=" input-group input-group-merge">
|
||||||
className="form-control"
|
<input
|
||||||
name="password"
|
type={hidepass ? "password" : "text"}
|
||||||
{...register('password')}
|
autoComplete="true"
|
||||||
placeholder="············"
|
id="password"
|
||||||
aria-describedby="password"
|
className="form-control"
|
||||||
/>
|
name="password"
|
||||||
{errors.password && <div className="danger-text text-start" style={{fontSize:"12px"}}>{errors.password.message}</div>}
|
{...register("password")}
|
||||||
</div>
|
placeholder="············"
|
||||||
<div className=" mb-3">
|
aria-describedby="password"
|
||||||
<label htmlFor="email" className="form-label">
|
/>
|
||||||
Repeat New Password
|
<span
|
||||||
</label>
|
className="input-group-text lcursor-pointer"
|
||||||
<input
|
onClick={() => setHidepass(!hidepass)}
|
||||||
type="password"
|
>
|
||||||
autoComplete="true"
|
{hidepass ? (
|
||||||
id="password"
|
<i className="bx bx-hide"></i>
|
||||||
className="form-control"
|
) : (
|
||||||
name="confirmPassword"
|
<i className="bx bx-show"></i>
|
||||||
{...register('confirmPassword')}
|
)}
|
||||||
placeholder="············"
|
</span>
|
||||||
aria-describedby="password"
|
</div>
|
||||||
/>
|
{errors.password && (
|
||||||
{errors.confirmPassword && <div className="danger-text text-start" style={{fontSize:"12px"}}>{errors.confirmPassword.message}</div>}
|
<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="············"
|
||||||
|
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>
|
||||||
|
|
||||||
<div className="mb-3 text-start ">
|
<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" }}>
|
||||||
<p className="p-0 m-0" style={{fontSize:'9px'}}>Password must contain at least one uppercase letter</p>
|
Password must be at least 8 characters
|
||||||
<p className="p-0 m-0" style={{fontSize:'9px'}}>Password must contain at least one number</p>
|
</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 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>
|
</div>
|
||||||
<button aria-label="Click me" className="btn btn-primary d-grid w-100">
|
<button aria-label="Click me" className="btn btn-primary d-grid w-100">
|
||||||
{loading ? "Please Wait...":"Update Password"}
|
{loading ? "Please Wait..." : "Update Password"}
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
|
|||||||
@ -4,100 +4,95 @@ import { Link, NavLink, useNavigate } from "react-router-dom";
|
|||||||
import Avatar from "../../components/common/Avatar";
|
import Avatar from "../../components/common/Avatar";
|
||||||
import Breadcrumb from "../../components/common/Breadcrumb";
|
import Breadcrumb from "../../components/common/Breadcrumb";
|
||||||
import ManageEmp from "../../components/Employee/ManageRole";
|
import ManageEmp from "../../components/Employee/ManageRole";
|
||||||
import {useEmployeesAllOrByProjectId} from "../../hooks/useEmployees";
|
import { useEmployeesAllOrByProjectId } from "../../hooks/useEmployees";
|
||||||
import { useProjects } from "../../hooks/useProjects";
|
import { useProjects } from "../../hooks/useProjects";
|
||||||
import { useProfile } from "../../hooks/useProfile";
|
import { useProfile } from "../../hooks/useProfile";
|
||||||
import {hasUserPermission} from "../../utils/authUtils";
|
import { hasUserPermission } from "../../utils/authUtils";
|
||||||
import {MANAGE_EMPLOYEES} from "../../utils/constants";
|
import { MANAGE_EMPLOYEES } from "../../utils/constants";
|
||||||
import {useHasUserPermission} from "../../hooks/useHasUserPermission";
|
import { useHasUserPermission } from "../../hooks/useHasUserPermission";
|
||||||
|
|
||||||
const EmployeeList = () =>
|
const EmployeeList = () =>
|
||||||
{
|
{
|
||||||
const {profile:loginUser}= useProfile()
|
|
||||||
const [selectedProject, setSelectedProject] = useState("");
|
const { profile: loginUser } = useProfile();
|
||||||
const {projects, loading: projectLoading} = useProjects()
|
const [selectedProject, setSelectedProject] = useState("");
|
||||||
const ManageEmployee = useHasUserPermission(MANAGE_EMPLOYEES)
|
const { projects, loading: projectLoading } = useProjects();
|
||||||
|
const ManageEmployee = useHasUserPermission(MANAGE_EMPLOYEES);
|
||||||
|
|
||||||
const {employees, loading,setLoading, error} = useEmployeesAllOrByProjectId( selectedProject );
|
const { employees, loading, setLoading, error } =
|
||||||
const [ projectsList, setProjectsList ] = useState(projects || [] );
|
useEmployeesAllOrByProjectId(selectedProject);
|
||||||
|
const [projectsList, setProjectsList] = useState(projects || []);
|
||||||
const [employeeList,setEmployeeList] = useState([])
|
|
||||||
|
const [employeeList, setEmployeeList] = useState([]);
|
||||||
const [modelConfig, setModelConfig] = useState();
|
const [modelConfig, setModelConfig] = useState();
|
||||||
const [currentPage, setCurrentPage] = useState(1);
|
const [currentPage, setCurrentPage] = useState(1);
|
||||||
const [itemsPerPage] = useState(5);
|
const [itemsPerPage] = useState(5);
|
||||||
const [isCreateModalOpen, setIsCreateModalOpen ] = useState( false );
|
const [isCreateModalOpen, setIsCreateModalOpen] = useState(false);
|
||||||
const [searchText,setSearchText] = useState("")
|
const [searchText, setSearchText] = useState("");
|
||||||
const [filteredData, setFilteredData] = useState([]);
|
const [filteredData, setFilteredData] = useState([]);
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
|
||||||
const handleSearch = (e) => {
|
const handleSearch = (e) => {
|
||||||
const value = e.target.value.toLowerCase();
|
const value = e.target.value.toLowerCase();
|
||||||
setSearchText(value);
|
setSearchText(value);
|
||||||
|
|
||||||
if (!employeeList.length) return;
|
if (!employeeList.length) return;
|
||||||
|
|
||||||
const results = employeeList.filter((item) =>
|
const results = employeeList.filter((item) =>
|
||||||
Object.values(item).some((field) =>
|
Object.values(item).some(
|
||||||
field && field.toString().toLowerCase().includes(value)
|
(field) => field && field.toString().toLowerCase().includes(value)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
setFilteredData(results);
|
setFilteredData(results);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setCurrentPage( 1 )
|
setCurrentPage(1);
|
||||||
|
|
||||||
if (!loading && Array.isArray(employees)) {
|
if (!loading && Array.isArray(employees)) {
|
||||||
setEmployeeList(employees);
|
setEmployeeList(employees);
|
||||||
setFilteredData( employees );
|
setFilteredData(employees);
|
||||||
}
|
}
|
||||||
|
|
||||||
}, [loading, employees, selectedProject]);
|
}, [loading, employees, selectedProject]);
|
||||||
|
|
||||||
|
|
||||||
const displayData = searchText ? filteredData : employeeList
|
const displayData = searchText ? filteredData : employeeList;
|
||||||
const indexOfLastItem = currentPage * itemsPerPage;
|
const indexOfLastItem = currentPage * itemsPerPage;
|
||||||
const indexOfFirstItem = indexOfLastItem - itemsPerPage;
|
const indexOfFirstItem = indexOfLastItem - itemsPerPage;
|
||||||
const currentItems = Array.isArray(displayData)
|
const currentItems = Array.isArray(displayData)
|
||||||
? displayData.slice(indexOfFirstItem, indexOfLastItem)
|
? displayData.slice(indexOfFirstItem, indexOfLastItem)
|
||||||
: [];
|
: [];
|
||||||
|
|
||||||
const paginate = (pageNumber) => setCurrentPage(pageNumber);
|
const paginate = (pageNumber) => setCurrentPage(pageNumber);
|
||||||
const totalPages = Array.isArray(displayData)
|
const totalPages = Array.isArray(displayData)
|
||||||
? Math.ceil(displayData.length / itemsPerPage)
|
? Math.ceil(displayData.length / itemsPerPage)
|
||||||
: 0;
|
: 0;
|
||||||
|
|
||||||
|
const openModal = () => {
|
||||||
|
setIsCreateModalOpen(true);
|
||||||
const openModal = () => {
|
};
|
||||||
setIsCreateModalOpen(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
const closeModal = () => {
|
const closeModal = () => {
|
||||||
setIsCreateModalOpen(false);
|
setIsCreateModalOpen(false);
|
||||||
|
|
||||||
const modalElement = document.getElementById("managerole-modal");
|
const modalElement = document.getElementById("managerole-modal");
|
||||||
if (modalElement) {
|
if (modalElement) {
|
||||||
modalElement.classList.remove("show");
|
modalElement.classList.remove("show");
|
||||||
modalElement.style.display = "none";
|
modalElement.style.display = "none";
|
||||||
document.body.classList.remove("modal-open");
|
document.body.classList.remove("modal-open");
|
||||||
document.querySelector(".modal-backdrop").remove();
|
document.querySelector(".modal-backdrop").remove();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleConfigData = (config) => {
|
const handleConfigData = (config) => {
|
||||||
setModelConfig(config);
|
setModelConfig(config);
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (modelConfig !== null) {
|
if (modelConfig !== null) {
|
||||||
openModal();
|
openModal();
|
||||||
}
|
}
|
||||||
}, [ modelConfig, isCreateModalOpen ] );
|
}, [modelConfig, isCreateModalOpen]);
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -126,32 +121,30 @@ const EmployeeList = () =>
|
|||||||
className="dataTables_length text-start"
|
className="dataTables_length text-start"
|
||||||
id="DataTables_Table_0_length"
|
id="DataTables_Table_0_length"
|
||||||
>
|
>
|
||||||
<label>
|
<label>
|
||||||
<select
|
<select
|
||||||
id="project-select"
|
id="project-select"
|
||||||
onChange={(e)=>setSelectedProject(e.target.value)}
|
onChange={(e) => setSelectedProject(e.target.value)}
|
||||||
name="DataTables_Table_0_length"
|
name="DataTables_Table_0_length"
|
||||||
aria-controls="DataTables_Table_0"
|
aria-controls="DataTables_Table_0"
|
||||||
className="form-select form-select-sm"
|
className="form-select form-select-sm"
|
||||||
value={selectedProject || ""}
|
value={selectedProject || ""}
|
||||||
>
|
>
|
||||||
{projectLoading ? (
|
{projectLoading ? (
|
||||||
<option value="Loading" >
|
<option value="Loading">Loading...</option>
|
||||||
Loading...
|
) : (
|
||||||
</option>
|
<>
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<option value="">All Employees</option>
|
<option value="">All Employees</option>
|
||||||
{Array.isArray(projects) && projects.map((item) => (
|
{Array.isArray(projects) &&
|
||||||
<option key={item.id} value={item.id}>
|
projects.map((item) => (
|
||||||
{item.name}
|
<option key={item.id} value={item.id}>
|
||||||
</option>
|
{item.name}
|
||||||
))}
|
</option>
|
||||||
</>
|
))}
|
||||||
)}
|
</>
|
||||||
|
)}
|
||||||
</select>
|
</select>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -224,8 +217,9 @@ const EmployeeList = () =>
|
|||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<button
|
<button
|
||||||
|
className={`btn btn-sm add-new btn-primary ${
|
||||||
className={`btn btn-sm add-new btn-primary ${!ManageEmployee && 'd-none'}`}
|
!ManageEmployee && "d-none"
|
||||||
|
}`}
|
||||||
tabIndex="0"
|
tabIndex="0"
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
@ -265,6 +259,17 @@ const EmployeeList = () =>
|
|||||||
>
|
>
|
||||||
Name
|
Name
|
||||||
</th>
|
</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
|
<th
|
||||||
className="sorting sorting_desc d-none d-sm-table-cell"
|
className="sorting sorting_desc d-none d-sm-table-cell"
|
||||||
tabIndex="0"
|
tabIndex="0"
|
||||||
@ -287,7 +292,7 @@ const EmployeeList = () =>
|
|||||||
>
|
>
|
||||||
Role
|
Role
|
||||||
</th>
|
</th>
|
||||||
|
|
||||||
<th
|
<th
|
||||||
className="sorting d-none d-md-table-cell"
|
className="sorting d-none d-md-table-cell"
|
||||||
tabIndex="0"
|
tabIndex="0"
|
||||||
@ -309,7 +314,9 @@ const EmployeeList = () =>
|
|||||||
Status
|
Status
|
||||||
</th>
|
</th>
|
||||||
<th
|
<th
|
||||||
className={`sorting_disabled ${!ManageEmployee && 'd-none'}`}
|
className={`sorting_disabled ${
|
||||||
|
!ManageEmployee && "d-none"
|
||||||
|
}`}
|
||||||
rowSpan="1"
|
rowSpan="1"
|
||||||
colSpan="1"
|
colSpan="1"
|
||||||
style={{ width: "50px" }}
|
style={{ width: "50px" }}
|
||||||
@ -319,105 +326,140 @@ const EmployeeList = () =>
|
|||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{loading && <tr>
|
{loading && (
|
||||||
|
<tr>
|
||||||
<td colSpan={8}>
|
<td colSpan={8}>
|
||||||
<p>Loading...</p>
|
<p>Loading...</p>
|
||||||
</td>
|
</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>
|
</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>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
|||||||
@ -8,7 +8,7 @@ import { getCachedData } from "../../slices/apiDataManager";
|
|||||||
import { useEmployeeProfile, useEmployees, useEmployeesByProject } from "../../hooks/useEmployees";
|
import { useEmployeeProfile, useEmployees, useEmployeesByProject } from "../../hooks/useEmployees";
|
||||||
import { useSelector } from "react-redux";
|
import { useSelector } from "react-redux";
|
||||||
import EmployeeRepository from "../../repositories/EmployeeRepository";
|
import EmployeeRepository from "../../repositories/EmployeeRepository";
|
||||||
|
import { ComingSoonPage } from "../Misc/ComingSoonPage";
|
||||||
|
|
||||||
const EmployeeProfile = () => {
|
const EmployeeProfile = () => {
|
||||||
|
|
||||||
@ -56,35 +56,32 @@ const EmployeeProfile = () => {
|
|||||||
switch (activePill) {
|
switch (activePill) {
|
||||||
case "account": {
|
case "account": {
|
||||||
return (
|
return (
|
||||||
<div className="row">
|
<>
|
||||||
{/* <div className="col-xl-4 col-lg-5 col-md-5"> */}
|
<ComingSoonPage/>
|
||||||
<p>Account</p>
|
</>
|
||||||
{/* </div> */}
|
|
||||||
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
case "attendance": {
|
case "attendance": {
|
||||||
return (
|
return (
|
||||||
<div className="row">
|
<>
|
||||||
<div className="col-lg-12 col-xl-12">
|
<ComingSoonPage/>
|
||||||
{/* Teams */}
|
</>
|
||||||
<p>attendance component</p>
|
|
||||||
{/* Teams */}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "activities": {
|
case "activities": {
|
||||||
return (
|
return (
|
||||||
<p>activites components</p>
|
<>
|
||||||
|
<ComingSoonPage/>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return <div>Select a pill to display content here.</div>;
|
return <>
|
||||||
|
<ComingSoonPage/>
|
||||||
|
</>;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -160,7 +157,7 @@ const EmployeeProfile = () => {
|
|||||||
</li>
|
</li>
|
||||||
<li className="d-flex align-items-start test-start mb-2">
|
<li className="d-flex align-items-start test-start mb-2">
|
||||||
<span className={`${currentEmployee?.permanentAddress ? "" : "ms-4"}`}>
|
<span className={`${currentEmployee?.permanentAddress ? "" : "ms-4"}`}>
|
||||||
{currentEmployee?.peramnentAddress}
|
{currentEmployee?.permanentAddress}
|
||||||
</span>
|
</span>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
|
|||||||
@ -53,10 +53,8 @@ const MasterPage = () => {
|
|||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
const {data:masterData, loading,error , RecallApi} = useMaster();
|
const {data:masterData, loading,error , RecallApi} = useMaster();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const handleSearch = (e) => {
|
const handleSearch = (e) => {
|
||||||
const value = e.target.value.toLowerCase();
|
const value = e.target.value.toLowerCase();
|
||||||
|
|||||||
@ -7,7 +7,7 @@ const MasterTable = ( {data, columns, loading, handleModalData} ) =>
|
|||||||
{
|
{
|
||||||
const hasMasterPermission = useHasUserPermission(MANAGE_MASTER)
|
const hasMasterPermission = useHasUserPermission(MANAGE_MASTER)
|
||||||
const selectedMaster = useSelector((store)=>store.localVariables.selectedMaster)
|
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 safeData = Array.isArray(data) ? data : [];
|
||||||
const [currentPage, setCurrentPage] = useState(1);
|
const [currentPage, setCurrentPage] = useState(1);
|
||||||
@ -28,7 +28,7 @@ const MasterTable = ( {data, columns, loading, handleModalData} ) =>
|
|||||||
.map((col) => ({
|
.map((col) => ({
|
||||||
...col,
|
...col,
|
||||||
label:
|
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 (
|
return (
|
||||||
@ -45,8 +45,8 @@ const MasterTable = ( {data, columns, loading, handleModalData} ) =>
|
|||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th></th>
|
<th></th>
|
||||||
<th>{ selectedMaster} Name</th>
|
<th> Name</th>
|
||||||
<th>{selectedMaster } Description</th>
|
<th>{selectedMaster} {selectedMaster === "Activity" ? "Unit":"Description" }</th>
|
||||||
<th className={` ${!hasMasterPermission && 'd-none'}`}>Actions</th>
|
<th className={` ${!hasMasterPermission && 'd-none'}`}>Actions</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
|
|||||||
@ -1,11 +1,8 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
|
import { ComingSoonPage } from "../Misc/ComingSoonPage";
|
||||||
|
|
||||||
const Inventory = () => {
|
const Inventory = () => {
|
||||||
return <div>Inventory
|
return <ComingSoonPage></ComingSoonPage>;
|
||||||
<div>
|
|
||||||
<p>ddj</p>
|
|
||||||
</div>
|
|
||||||
</div>;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Inventory;
|
export default Inventory;
|
||||||
|
|||||||
@ -14,50 +14,34 @@ import Breadcrumb from "../../components/common/Breadcrumb";
|
|||||||
import { cacheData, getCachedData } from "../../slices/apiDataManager";
|
import { cacheData, getCachedData } from "../../slices/apiDataManager";
|
||||||
import ProjectRepository from "../../repositories/ProjectRepository";
|
import ProjectRepository from "../../repositories/ProjectRepository";
|
||||||
import { ActivityeRepository } from "../../repositories/MastersRepository";
|
import { ActivityeRepository } from "../../repositories/MastersRepository";
|
||||||
|
|
||||||
import "./ProjectDetails.css";
|
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 = () => {
|
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 [project, setProject] = useState(null);
|
||||||
const [ projectDetails, setProjectDetails ] = useState( null );
|
const [ projectDetails, setProjectDetails ] = useState( null );
|
||||||
const [activities, setActivities] = useState(null);
|
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [error, setError] = useState("");
|
const [ error, setError ] = useState( "" );
|
||||||
|
|
||||||
const fetchActivities = async () => {
|
|
||||||
|
|
||||||
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 fetchData = async () => {
|
||||||
|
|
||||||
const project_cache = getCachedData(`projectinfo-${projectId}`);
|
const project_cache = getCachedData("projectInfo");
|
||||||
if (!project_cache) {
|
if (!project_cache || project_cache?.projectId !== projectId) {
|
||||||
ProjectRepository.getProjectByprojectId(projectId)
|
ProjectRepository.getProjectByprojectId(projectId)
|
||||||
.then( ( response ) =>
|
.then( ( response ) =>
|
||||||
{
|
{
|
||||||
setProjectDetails(response);
|
setProjectDetails( response.data );
|
||||||
setProject(response);
|
setProject( response.data );
|
||||||
cacheData( `projectinfo-${ projectId }`, response );
|
cacheData("projectInfo", {projectId,data: response.data} );
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
@ -66,13 +50,14 @@ const ProjectDetails = () => {
|
|||||||
setLoading(false)
|
setLoading(false)
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
setProjectDetails( project_cache );
|
setProjectDetails( project_cache.data );
|
||||||
setProject( project_cache );
|
setProject( project_cache.data );
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
const [activePill, setActivePill] = useState("profile");
|
const [activePill, setActivePill] = useState("profile");
|
||||||
|
|
||||||
|
|
||||||
@ -86,7 +71,7 @@ const ProjectDetails = () => {
|
|||||||
|
|
||||||
|
|
||||||
const renderContent = () => {
|
const renderContent = () => {
|
||||||
if (loading) return <Loader></Loader>;
|
if (projectLoading) return <Loader></Loader>;
|
||||||
switch (activePill) {
|
switch (activePill) {
|
||||||
case "profile": {
|
case "profile": {
|
||||||
return (
|
return (
|
||||||
@ -98,7 +83,7 @@ const ProjectDetails = () => {
|
|||||||
</div>
|
</div>
|
||||||
<div className="col-xl-4 col-lg-5 col-md-5">
|
<div className="col-xl-4 col-lg-5 col-md-5">
|
||||||
{/* Profile Overview */}
|
{/* Profile Overview */}
|
||||||
<ProjectOverview project={ project} />
|
<ProjectOverview project={projectId} />
|
||||||
{/* Profile Overview */}
|
{/* Profile Overview */}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -120,7 +105,6 @@ const ProjectDetails = () => {
|
|||||||
return (
|
return (
|
||||||
<ProjectInfra
|
<ProjectInfra
|
||||||
data={projectDetails}
|
data={projectDetails}
|
||||||
activityMaster={activities}
|
|
||||||
onDataChange={handleDataChange}
|
onDataChange={handleDataChange}
|
||||||
></ProjectInfra>
|
></ProjectInfra>
|
||||||
);
|
);
|
||||||
@ -130,7 +114,6 @@ const ProjectDetails = () => {
|
|||||||
return (
|
return (
|
||||||
<WorkPlan
|
<WorkPlan
|
||||||
data={projectDetails}
|
data={projectDetails}
|
||||||
activityMaster={activities}
|
|
||||||
onDataChange={handleDataChange}
|
onDataChange={handleDataChange}
|
||||||
></WorkPlan>
|
></WorkPlan>
|
||||||
);
|
);
|
||||||
@ -147,18 +130,19 @@ const ProjectDetails = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return <div>Select a pill to display content here.</div>;
|
return <ComingSoonPage></ComingSoonPage>;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchData();
|
dispatch(setProjectId(projectId))
|
||||||
fetchActivities();
|
setProject( projects_Details )
|
||||||
}, []);
|
setProjectDetails(projects_Details)
|
||||||
|
}, [projects_Details,projectId]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
||||||
<>
|
<>
|
||||||
|
{}
|
||||||
<div className="container-xxl flex-grow-1 container-p-y">
|
<div className="container-xxl flex-grow-1 container-p-y">
|
||||||
<Breadcrumb
|
<Breadcrumb
|
||||||
data={[
|
data={[
|
||||||
@ -169,8 +153,8 @@ const ProjectDetails = () => {
|
|||||||
></Breadcrumb>
|
></Breadcrumb>
|
||||||
|
|
||||||
<div className="row">
|
<div className="row">
|
||||||
{loading && <p>Loading....</p>}
|
{projectLoading && <p>Loading....</p>}
|
||||||
{!loading && <ProjectBanner project_data={project} ></ProjectBanner>}
|
{(!projectLoading && project) && <ProjectBanner project_data={project} ></ProjectBanner>}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="row">
|
<div className="row">
|
||||||
|
|||||||
@ -14,6 +14,7 @@ import {MANAGE_PROJECT} from "../../utils/constants";
|
|||||||
const ProjectList = () =>
|
const ProjectList = () =>
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|
||||||
const {profile: loginUser} = useProfile();
|
const {profile: loginUser} = useProfile();
|
||||||
const [showModal, setShowModal] = useState(false);
|
const [showModal, setShowModal] = useState(false);
|
||||||
const {projects, loading, error, refetch} = useProjects();
|
const {projects, loading, error, refetch} = useProjects();
|
||||||
@ -23,7 +24,7 @@ const ProjectList = () =>
|
|||||||
const[HasManageProject,setHasManageProject] = useState(HasManageProjectPermission)
|
const[HasManageProject,setHasManageProject] = useState(HasManageProjectPermission)
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const [currentPage, setCurrentPage] = useState(1);
|
const [currentPage, setCurrentPage] = useState(1);
|
||||||
const [itemsPerPage] = useState(5);
|
const [itemsPerPage] = useState(6);
|
||||||
|
|
||||||
|
|
||||||
const handleShow = () => setShowModal(true);
|
const handleShow = () => setShowModal(true);
|
||||||
@ -84,7 +85,20 @@ const ProjectList = () =>
|
|||||||
? Math.ceil(projectList.length / itemsPerPage)
|
? Math.ceil(projectList.length / itemsPerPage)
|
||||||
: 0;
|
: 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 (
|
return (
|
||||||
<>
|
<>
|
||||||
<div
|
<div
|
||||||
@ -118,11 +132,7 @@ const ProjectList = () =>
|
|||||||
{" "}
|
{" "}
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className={`btn btn-sm btn-primary ${
|
className={`btn btn-xs btn-primary ${!HasManageProject && 'd-none' }`}
|
||||||
HasManageProject
|
|
||||||
? ""
|
|
||||||
: "d-none"
|
|
||||||
}`}
|
|
||||||
data-bs-toggle="modal"
|
data-bs-toggle="modal"
|
||||||
data-bs-target="#create-project-model"
|
data-bs-target="#create-project-model"
|
||||||
onClick={handleShow}
|
onClick={handleShow}
|
||||||
@ -154,8 +164,10 @@ const ProjectList = () =>
|
|||||||
|
|
||||||
<div className="row">
|
<div className="row">
|
||||||
{loading && <p className="text-center">Loading...</p>}
|
{loading && <p className="text-center">Loading...</p>}
|
||||||
|
|
||||||
|
|
||||||
{currentItems &&
|
{currentItems &&
|
||||||
currentItems.sort((a, b) => b.id - a.id).map((item) => (
|
sortedProjects.map((item) => (
|
||||||
<ProjectCard projectData={item} key={item.id}></ProjectCard>
|
<ProjectCard projectData={item} key={item.id}></ProjectCard>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,7 +1,8 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
|
import { ComingSoonPage } from "../Misc/ComingSoonPage";
|
||||||
|
|
||||||
const Reports = () => {
|
const Reports = () => {
|
||||||
return <div>Reports</div>;
|
return <ComingSoonPage></ComingSoonPage>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Reports;
|
export default Reports;
|
||||||
|
|||||||
@ -1,7 +1,8 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
|
import { ComingSoonPage } from "../Misc/ComingSoonPage";
|
||||||
|
|
||||||
const Connect = () => {
|
const Connect = () => {
|
||||||
return <div>Connect</div>;
|
return <ComingSoonPage></ComingSoonPage>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Connect;
|
export default Connect;
|
||||||
|
|||||||
@ -1,7 +1,8 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
|
import { ComingSoonPage } from "../Misc/ComingSoonPage";
|
||||||
|
|
||||||
const Documentation = () => {
|
const Documentation = () => {
|
||||||
return <div>Documentation</div>;
|
return <ComingSoonPage></ComingSoonPage>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Documentation;
|
export default Documentation;
|
||||||
|
|||||||
@ -1,7 +1,8 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
|
import { ComingSoonPage } from "../Misc/ComingSoonPage";
|
||||||
|
|
||||||
const Support = () => {
|
const Support = () => {
|
||||||
return <div>Support</div>;
|
return <ComingSoonPage></ComingSoonPage>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Support;
|
export default Support;
|
||||||
|
|||||||
@ -5,7 +5,8 @@ const AttendanceRepository = {
|
|||||||
markAttendance:(data)=>api.post("/api/attendance/record",data),
|
markAttendance:(data)=>api.post("/api/attendance/record",data),
|
||||||
getAttendance:(id)=>api.get(`api/attendance/project/team?projectId=${id}`),
|
getAttendance:(id)=>api.get(`api/attendance/project/team?projectId=${id}`),
|
||||||
getAttendanceFilteredByDate:(id,date)=>api.get(`api/attendance/project/team?projectId=${id+"&date="+date}`),
|
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}`)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
25
src/repositories/GlobalRepository.jsx
Normal file
25
src/repositories/GlobalRepository.jsx
Normal 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;
|
||||||
@ -27,7 +27,11 @@ export const MasterRespository = {
|
|||||||
|
|
||||||
createJobRole:(data)=>api.post('api/roles/jobrole',data),
|
createJobRole:(data)=>api.post('api/roles/jobrole',data),
|
||||||
getJobRole :()=>api.get("/api/roles/jobrole"),
|
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),
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -13,12 +13,17 @@ const ProjectRepository = {
|
|||||||
manageProject: (data) => api.post("/api/project", data),
|
manageProject: (data) => api.post("/api/project", data),
|
||||||
// updateProject: (data) => api.post("/api/project/update", 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),
|
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),
|
updateProject: (id, data) => api.put(`/api/project/update/${id}`, data),
|
||||||
deleteProject: (id) => api.delete(`/projects/${id}`),
|
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;
|
export default ProjectRepository;
|
||||||
|
|||||||
19
src/repositories/TaskRepository.jsx
Normal file
19
src/repositories/TaskRepository.jsx
Normal 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),
|
||||||
|
};
|
||||||
@ -1,5 +1,6 @@
|
|||||||
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
|
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
|
||||||
import AttendanceRepository from '../../repositories/AttendanceRepository';
|
import AttendanceRepository from '../../repositories/AttendanceRepository';
|
||||||
|
import {clearCacheKey} from '../apiDataManager';
|
||||||
|
|
||||||
// Fetch attendance data
|
// Fetch attendance data
|
||||||
export const fetchAttendanceData = createAsyncThunk(
|
export const fetchAttendanceData = createAsyncThunk(
|
||||||
@ -17,10 +18,11 @@ export const fetchAttendanceData = createAsyncThunk(
|
|||||||
// This method for marking attendance if a date filter is applied
|
// This method for marking attendance if a date filter is applied
|
||||||
export const markAttendance = createAsyncThunk(
|
export const markAttendance = createAsyncThunk(
|
||||||
'attendanceLogs/markAttendance', // Updated action type prefix
|
'attendanceLogs/markAttendance', // Updated action type prefix
|
||||||
async (formData, thunkAPI) => {
|
async ( formData, thunkAPI ) =>
|
||||||
|
{
|
||||||
try {
|
try {
|
||||||
let newRecordAttendance = {
|
let newRecordAttendance = {
|
||||||
id: formData.id || null,
|
Id: formData.id || null,
|
||||||
comment: formData.description,
|
comment: formData.description,
|
||||||
employeeID: formData.employeeId,
|
employeeID: formData.employeeId,
|
||||||
projectID: formData.projectId,
|
projectID: formData.projectId,
|
||||||
@ -32,7 +34,8 @@ export const markAttendance = createAsyncThunk(
|
|||||||
image: null,
|
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
|
return response.data; // Return the newly created or updated record
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return thunkAPI.rejectWithValue(error.message);
|
return thunkAPI.rejectWithValue(error.message);
|
||||||
|
|||||||
@ -1,19 +1,24 @@
|
|||||||
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
|
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
|
||||||
import AttendanceRepository from '../../repositories/AttendanceRepository';
|
import AttendanceRepository from '../../repositories/AttendanceRepository';
|
||||||
|
import {clearCacheKey} from '../apiDataManager';
|
||||||
|
|
||||||
export const markCurrentAttendance = createAsyncThunk(
|
export const markCurrentAttendance = createAsyncThunk(
|
||||||
'attendanceCurrentDate/markAttendance',
|
'attendanceCurrentDate/markAttendance',
|
||||||
async (formData, { getState, dispatch, rejectWithValue }) => {
|
async ( formData, {getState, dispatch, rejectWithValue} ) =>
|
||||||
|
{
|
||||||
|
|
||||||
const { projectId } = getState().localVariables
|
const { projectId } = getState().localVariables
|
||||||
try {
|
try
|
||||||
|
{
|
||||||
|
|
||||||
// Create the new attendance record
|
// Create the new attendance record
|
||||||
const newRecordAttendance = {
|
const newRecordAttendance = {
|
||||||
id: null,
|
Id: formData.id || null,
|
||||||
comment: formData.description,
|
comment: formData.description,
|
||||||
employeeID: formData.employeeId,
|
employeeID: formData.employeeId,
|
||||||
projectId: projectId,
|
projectId: projectId,
|
||||||
date: new Date().toISOString(),
|
date: new Date().toISOString(),
|
||||||
markTime: formData.time,
|
markTime: formData.markTime,
|
||||||
latitude: formData.latitude.toString(),
|
latitude: formData.latitude.toString(),
|
||||||
longitude: formData.longitude.toString(),
|
longitude: formData.longitude.toString(),
|
||||||
action: formData.action,
|
action: formData.action,
|
||||||
@ -22,6 +27,7 @@ export const markCurrentAttendance = createAsyncThunk(
|
|||||||
|
|
||||||
const response = await AttendanceRepository.markAttendance(newRecordAttendance);
|
const response = await AttendanceRepository.markAttendance(newRecordAttendance);
|
||||||
const markedAttendance = response.data
|
const markedAttendance = response.data
|
||||||
|
clearCacheKey("AttendanceLogs")
|
||||||
return markedAttendance;
|
return markedAttendance;
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@ -3,9 +3,9 @@ import { createSlice } from "@reduxjs/toolkit";
|
|||||||
const localVariablesSlice = createSlice({
|
const localVariablesSlice = createSlice({
|
||||||
name: "localVariables",
|
name: "localVariables",
|
||||||
initialState: {
|
initialState: {
|
||||||
selectedMaster:"Role",
|
selectedMaster:"Application Role",
|
||||||
regularizationCount:0,
|
regularizationCount:0,
|
||||||
projectId:5,
|
projectId:1,
|
||||||
|
|
||||||
},
|
},
|
||||||
reducers: {
|
reducers: {
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user