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/vendor/libs/perfect-scrollbar/perfect-scrollbar.css" />
|
||||
<link rel="stylesheet" href="/assets/vendor/libs/apex-charts/apex-charts.css" />
|
||||
|
||||
<!-- Icons -->
|
||||
<link rel="stylesheet" href="/assets/vendor/fonts/boxicons.css" />
|
||||
@ -46,9 +45,11 @@
|
||||
<!-- Helpers -->
|
||||
<script src="/assets/vendor/js/helpers.js"></script>
|
||||
<script src="/assets/js/config.js"></script>
|
||||
|
||||
|
||||
<!-- Timer Picker -->
|
||||
<!-- Flatpickr CSS -->
|
||||
<link rel="stylesheet" href="/assets/vendor/libs/flatpickr/flatpickr.css" />
|
||||
<link rel="stylesheet" href="./src/assets/vendor/libs/jquery-timepicker/jquery-timepicker.css" />
|
||||
|
||||
|
||||
|
||||
@ -70,21 +71,20 @@
|
||||
<script src="/assets/vendor/libs/select2/select2.js"></script>
|
||||
<script src="/assets/vendor/js/menu.js"></script>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<!-- endbuild -->
|
||||
|
||||
|
||||
|
||||
|
||||
<!-- Vendors JS -->
|
||||
<script src="/assets/vendor/libs/bs-stepper/bs-stepper.js"></script>
|
||||
<script src="/assets/vendor/libs/bootstrap-select/bootstrap-select.js"></script>
|
||||
<script src="/assets/vendor/libs/select2/select2.js"></script>
|
||||
<script src="/assets/vendor/libs/apex-charts/apexcharts.js"></script>
|
||||
<script src="/assets/vendor/libs/jquery-timepicker/jquery-timepicker.js" ></script>
|
||||
|
||||
<!-- <script src="/assets/vendor/libs/jquery-timepicker/jquery-timepicker.js" ></script> -->
|
||||
<script src="/assets/vendor/libs/flatpickr/flatpickr.js"></script>
|
||||
<!-- Main JS -->
|
||||
<script src="/assets/js/main.js"></script>
|
||||
|
||||
@ -93,26 +93,15 @@
|
||||
<script src="/assets/js/dashboards-analytics.js"></script>
|
||||
|
||||
<!-- component -->
|
||||
<script src="./public/js/timppick.js"></script>
|
||||
|
||||
<script src="/assets/vendor/libs/sweetalert2/sweetalert2.js" ></script>
|
||||
<!-- <script src="./public/js/timppick.js"></script> -->
|
||||
|
||||
<script src="/assets/vendor/libs/sweetalert2/sweetalert2.js"></script>
|
||||
|
||||
<!-- <script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.10.2/dist/umd/popper.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/js/bootstrap.min.js"></script> -->
|
||||
|
||||
<!-- Flatpickr JS -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/flatpickr"></script>
|
||||
<!-- Flatpickr JS -->
|
||||
|
||||
<script>
|
||||
// Initialize flatpickr for 12-hour time format with AM/PM
|
||||
flatpickr("#timePicker", {
|
||||
enableTime: true,
|
||||
noCalendar: true,
|
||||
time_24hr: false, // Disable 24-hour format
|
||||
dateFormat: "h:i K", // 12-hour format with AM/PM
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
102
package-lock.json
generated
102
package-lock.json
generated
@ -10,7 +10,9 @@
|
||||
"dependencies": {
|
||||
"@hookform/resolvers": "^3.10.0",
|
||||
"@reduxjs/toolkit": "^2.5.0",
|
||||
"@types/web": "^0.0.216",
|
||||
"@vitejs/plugin-react": "^4.3.4",
|
||||
"apexcharts": "^4.5.0",
|
||||
"axios": "^1.7.9",
|
||||
"axios-retry": "^4.5.0",
|
||||
"dotenv": "^16.4.7",
|
||||
@ -21,6 +23,7 @@
|
||||
"moment": "^2.30.1",
|
||||
"perfect-scrollbar": "^1.5.5",
|
||||
"react": "^18.2.0",
|
||||
"react-apexcharts": "^1.7.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-hook-form": "^7.54.2",
|
||||
"react-redux": "^9.2.0",
|
||||
@ -1115,6 +1118,62 @@
|
||||
"win32"
|
||||
]
|
||||
},
|
||||
"node_modules/@svgdotjs/svg.draggable.js": {
|
||||
"version": "3.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@svgdotjs/svg.draggable.js/-/svg.draggable.js-3.0.6.tgz",
|
||||
"integrity": "sha512-7iJFm9lL3C40HQcqzEfezK2l+dW2CpoVY3b77KQGqc8GXWa6LhhmX5Ckv7alQfUXBuZbjpICZ+Dvq1czlGx7gA==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@svgdotjs/svg.js": "^3.2.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@svgdotjs/svg.filter.js": {
|
||||
"version": "3.0.9",
|
||||
"resolved": "https://registry.npmjs.org/@svgdotjs/svg.filter.js/-/svg.filter.js-3.0.9.tgz",
|
||||
"integrity": "sha512-/69XMRCDoam2HgC4ldHIaDgeQf1ViHIsa0Ld4uWgiXtZ+E24DWHe/9Ib6kbNiZ7WRIdlVokUDR1Fg0kjIpkfbw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@svgdotjs/svg.js": "^3.2.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@svgdotjs/svg.js": {
|
||||
"version": "3.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@svgdotjs/svg.js/-/svg.js-3.2.4.tgz",
|
||||
"integrity": "sha512-BjJ/7vWNowlX3Z8O4ywT58DqbNRyYlkk6Yz/D13aB7hGmfQTvGX4Tkgtm/ApYlu9M7lCQi15xUEidqMUmdMYwg==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/Fuzzyma"
|
||||
}
|
||||
},
|
||||
"node_modules/@svgdotjs/svg.resize.js": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@svgdotjs/svg.resize.js/-/svg.resize.js-2.0.5.tgz",
|
||||
"integrity": "sha512-4heRW4B1QrJeENfi7326lUPYBCevj78FJs8kfeDxn5st0IYPIRXoTtOSYvTzFWgaWWXd3YCDE6ao4fmv91RthA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 14.18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@svgdotjs/svg.js": "^3.2.4",
|
||||
"@svgdotjs/svg.select.js": "^4.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@svgdotjs/svg.select.js": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@svgdotjs/svg.select.js/-/svg.select.js-4.0.2.tgz",
|
||||
"integrity": "sha512-5gWdrvoQX3keo03SCmgaBbD+kFftq0F/f2bzCbNnpkkvW6tk4rl4MakORzFuNjvXPWwB4az9GwuvVxQVnjaK2g==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 14.18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@svgdotjs/svg.js": "^3.2.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@swc/core": {
|
||||
"version": "1.10.1",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core/-/core-1.10.1.tgz",
|
||||
@ -1439,6 +1498,11 @@
|
||||
"resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz",
|
||||
"integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg=="
|
||||
},
|
||||
"node_modules/@types/web": {
|
||||
"version": "0.0.216",
|
||||
"resolved": "https://registry.npmjs.org/@types/web/-/web-0.0.216.tgz",
|
||||
"integrity": "sha512-HLaPWQKq1oh6aQv1JLRsiH0vW4VsO+L/zTOeOUmoGBnLVR2wCj4w4oWfa/0O5JFMqZXWC6VpipqQU6B1v2M/qg=="
|
||||
},
|
||||
"node_modules/@ungap/structured-clone": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.1.tgz",
|
||||
@ -1650,6 +1714,12 @@
|
||||
"license": "Apache-2.0",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@yr/monotone-cubic-spline": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@yr/monotone-cubic-spline/-/monotone-cubic-spline-1.0.3.tgz",
|
||||
"integrity": "sha512-FQXkOta0XBSUPHndIKON2Y9JeQz5ZeMqLYZVVK93FliNBFm7LNMIZmY6FrMEB9XPcDbE2bekMbZD6kzDkxwYjA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/acorn": {
|
||||
"version": "8.14.0",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz",
|
||||
@ -1752,6 +1822,20 @@
|
||||
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/apexcharts": {
|
||||
"version": "4.5.0",
|
||||
"resolved": "https://registry.npmjs.org/apexcharts/-/apexcharts-4.5.0.tgz",
|
||||
"integrity": "sha512-E7ZkrVqPNBUWy/Rmg8DEIqHNBmElzICE/oxOX5Ekvs2ICQUOK/VkEkMH09JGJu+O/EA0NL31hxlmF+wrwrSLaQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@svgdotjs/svg.draggable.js": "^3.0.4",
|
||||
"@svgdotjs/svg.filter.js": "^3.0.8",
|
||||
"@svgdotjs/svg.js": "^3.2.4",
|
||||
"@svgdotjs/svg.resize.js": "^2.0.2",
|
||||
"@svgdotjs/svg.select.js": "^4.0.1",
|
||||
"@yr/monotone-cubic-spline": "^1.0.3"
|
||||
}
|
||||
},
|
||||
"node_modules/argparse": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
|
||||
@ -3994,7 +4078,6 @@
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
@ -4255,7 +4338,6 @@
|
||||
"version": "15.8.1",
|
||||
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
|
||||
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.4.0",
|
||||
"object-assign": "^4.1.1",
|
||||
@ -4317,6 +4399,19 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-apexcharts": {
|
||||
"version": "1.7.0",
|
||||
"resolved": "https://registry.npmjs.org/react-apexcharts/-/react-apexcharts-1.7.0.tgz",
|
||||
"integrity": "sha512-03oScKJyNLRf0Oe+ihJxFZliBQM9vW3UWwomVn4YVRTN1jsIR58dLWt0v1sb8RwJVHDMbeHiKQueM0KGpn7nOA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"prop-types": "^15.8.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"apexcharts": ">=4.0.0",
|
||||
"react": ">=0.13"
|
||||
}
|
||||
},
|
||||
"node_modules/react-dom": {
|
||||
"version": "18.3.1",
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
|
||||
@ -4348,8 +4443,7 @@
|
||||
"node_modules/react-is": {
|
||||
"version": "16.13.1",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
|
||||
"dev": true
|
||||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
|
||||
},
|
||||
"node_modules/react-redux": {
|
||||
"version": "9.2.0",
|
||||
|
||||
@ -3,7 +3,6 @@
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"json-server": "json-server --watch ./src/data/demo.json --port 5000",
|
||||
@ -14,7 +13,9 @@
|
||||
"dependencies": {
|
||||
"@hookform/resolvers": "^3.10.0",
|
||||
"@reduxjs/toolkit": "^2.5.0",
|
||||
"@types/web": "^0.0.216",
|
||||
"@vitejs/plugin-react": "^4.3.4",
|
||||
"apexcharts": "^4.5.0",
|
||||
"axios": "^1.7.9",
|
||||
"axios-retry": "^4.5.0",
|
||||
"dotenv": "^16.4.7",
|
||||
@ -25,6 +26,7 @@
|
||||
"moment": "^2.30.1",
|
||||
"perfect-scrollbar": "^1.5.5",
|
||||
"react": "^18.2.0",
|
||||
"react-apexcharts": "^1.7.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-hook-form": "^7.54.2",
|
||||
"react-redux": "^9.2.0",
|
||||
|
||||
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-y: 0.5rem;
|
||||
--bs-dropdown-spacer: 0.125rem;
|
||||
--bs-dropdown-font-size: 0.9375rem;
|
||||
--bs-dropdown-font-size: 0.8125rem;
|
||||
--bs-dropdown-color: var(--bs-body-color);
|
||||
--bs-dropdown-bg: #fff;
|
||||
--bs-dropdown-border-color: #e4e6e8;
|
||||
@ -35469,7 +35469,7 @@ html:not([dir="rtl"]) .menu-toggle::after {
|
||||
}
|
||||
|
||||
.menu-vertical .menu-item .menu-link {
|
||||
font-size: 0.8375rem;
|
||||
font-size: 0.7375rem;
|
||||
min-height: 1.825rem;
|
||||
}
|
||||
|
||||
|
||||
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 {
|
||||
max-height: 10rem;
|
||||
overflow-y: auto;
|
||||
margin: 0.125rem 0;
|
||||
.flatpickr-calendar {
|
||||
background: transparent;
|
||||
opacity: 0;
|
||||
display: none;
|
||||
text-align: center;
|
||||
visibility: hidden;
|
||||
padding: 0;
|
||||
-webkit-animation: none;
|
||||
animation: none;
|
||||
direction: ltr;
|
||||
border: 0;
|
||||
font-size: 14px;
|
||||
line-height: 24px;
|
||||
border-radius: 5px;
|
||||
position: absolute;
|
||||
width: 307.875px;
|
||||
-webkit-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
-ms-touch-action: manipulation;
|
||||
touch-action: manipulation;
|
||||
background: #fff;
|
||||
background-clip: padding-box;
|
||||
outline: none;
|
||||
padding: 0.5rem;
|
||||
-webkit-box-shadow: 1px 0 0 #e6e6e6, -1px 0 0 #e6e6e6, 0 1px 0 #e6e6e6,
|
||||
0 -1px 0 #e6e6e6, 0 3px 13px rgba(0, 0, 0, 0.08);
|
||||
box-shadow: 1px 0 0 #e6e6e6, -1px 0 0 #e6e6e6, 0 1px 0 #e6e6e6,
|
||||
0 -1px 0 #e6e6e6, 0 3px 13px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.ui-timepicker-list {
|
||||
list-style: none;
|
||||
padding: 0.125rem 0;
|
||||
margin: 0;
|
||||
.flatpickr-calendar.open,
|
||||
.flatpickr-calendar.inline {
|
||||
opacity: 1;
|
||||
max-height: 640px;
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.ui-timepicker-duration {
|
||||
margin-left: 0.25rem;
|
||||
.flatpickr-calendar.open {
|
||||
display: inline-block;
|
||||
z-index: 99999;
|
||||
}
|
||||
[dir=rtl] .ui-timepicker-duration {
|
||||
margin-left: 0;
|
||||
margin-right: 0.25rem;
|
||||
.flatpickr-calendar.animate.open {
|
||||
-webkit-animation: fpFadeInDown 300ms cubic-bezier(0.23, 1, 0.32, 1);
|
||||
animation: fpFadeInDown 300ms cubic-bezier(0.23, 1, 0.32, 1);
|
||||
}
|
||||
|
||||
.ui-timepicker-list li {
|
||||
padding: 0.25rem 0.75rem;
|
||||
margin: 0.125rem 0.5625rem;
|
||||
white-space: nowrap;
|
||||
.flatpickr-calendar.inline {
|
||||
display: block;
|
||||
position: relative;
|
||||
top: 2px;
|
||||
}
|
||||
.flatpickr-calendar.static {
|
||||
position: absolute;
|
||||
top: calc(100% + 2px);
|
||||
}
|
||||
.flatpickr-calendar.static.open {
|
||||
z-index: 999;
|
||||
display: block;
|
||||
}
|
||||
.flatpickr-calendar.multiMonth
|
||||
.flatpickr-days
|
||||
.dayContainer:nth-child(n + 1)
|
||||
.flatpickr-day.inRange:nth-child(7n + 7) {
|
||||
-webkit-box-shadow: none !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
.flatpickr-calendar.multiMonth
|
||||
.flatpickr-days
|
||||
.dayContainer:nth-child(n + 2)
|
||||
.flatpickr-day.inRange:nth-child(7n + 1) {
|
||||
-webkit-box-shadow: -2px 0 0 #e6e6e6, 5px 0 0 #e6e6e6;
|
||||
box-shadow: -2px 0 0 #e6e6e6, 5px 0 0 #e6e6e6;
|
||||
}
|
||||
.flatpickr-calendar .hasWeeks .dayContainer,
|
||||
.flatpickr-calendar .hasTime .dayContainer {
|
||||
border-bottom: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
}
|
||||
.flatpickr-calendar .hasWeeks .dayContainer {
|
||||
border-left: 0;
|
||||
}
|
||||
.flatpickr-calendar.hasTime .flatpickr-time {
|
||||
height: 40px;
|
||||
border-top: 1px solid #e6e6e6;
|
||||
}
|
||||
.flatpickr-calendar.noCalendar.hasTime .flatpickr-time {
|
||||
height: auto;
|
||||
}
|
||||
.flatpickr-calendar:before,
|
||||
.flatpickr-calendar:after {
|
||||
position: absolute;
|
||||
display: block;
|
||||
pointer-events: none;
|
||||
border: solid transparent;
|
||||
content: "";
|
||||
height: 0;
|
||||
width: 0;
|
||||
left: 22px;
|
||||
}
|
||||
.flatpickr-calendar.rightMost:before,
|
||||
.flatpickr-calendar.arrowRight:before,
|
||||
.flatpickr-calendar.rightMost:after,
|
||||
.flatpickr-calendar.arrowRight:after {
|
||||
left: auto;
|
||||
right: 22px;
|
||||
}
|
||||
.flatpickr-calendar.arrowCenter:before,
|
||||
.flatpickr-calendar.arrowCenter:after {
|
||||
left: 50%;
|
||||
right: 50%;
|
||||
}
|
||||
.flatpickr-calendar:before {
|
||||
border-width: 5px;
|
||||
margin: 0 -5px;
|
||||
}
|
||||
.flatpickr-calendar:after {
|
||||
border-width: 4px;
|
||||
margin: 0 -4px;
|
||||
}
|
||||
.flatpickr-calendar.arrowTop:before,
|
||||
.flatpickr-calendar.arrowTop:after {
|
||||
bottom: 100%;
|
||||
}
|
||||
.flatpickr-calendar.arrowTop:before {
|
||||
border-bottom-color: #e6e6e6;
|
||||
}
|
||||
.flatpickr-calendar.arrowTop:after {
|
||||
border-bottom-color: #fff;
|
||||
}
|
||||
.flatpickr-calendar.arrowBottom:before,
|
||||
.flatpickr-calendar.arrowBottom:after {
|
||||
top: 100%;
|
||||
}
|
||||
.flatpickr-calendar.arrowBottom:before {
|
||||
border-top-color: #e6e6e6;
|
||||
}
|
||||
.flatpickr-calendar.arrowBottom:after {
|
||||
border-top-color: #fff;
|
||||
}
|
||||
.flatpickr-calendar:focus {
|
||||
outline: 0;
|
||||
}
|
||||
.flatpickr-wrapper {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
}
|
||||
.flatpickr-months {
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
}
|
||||
.flatpickr-months .flatpickr-month {
|
||||
background: transparent;
|
||||
color: rgba(0, 0, 0, 0.9);
|
||||
fill: rgba(0, 0, 0, 0.9);
|
||||
height: 34px;
|
||||
line-height: 1;
|
||||
text-align: center;
|
||||
position: relative;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
overflow: hidden;
|
||||
-webkit-box-flex: 1;
|
||||
-webkit-flex: 1;
|
||||
-ms-flex: 1;
|
||||
flex: 1;
|
||||
}
|
||||
.flatpickr-months .flatpickr-prev-month,
|
||||
.flatpickr-months .flatpickr-next-month {
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
list-style: none;
|
||||
border-radius: var(--bs-border-radius);
|
||||
position: absolute;
|
||||
top: 0;
|
||||
height: 34px;
|
||||
padding: 10px;
|
||||
z-index: 3;
|
||||
color: rgba(0, 0, 0, 0.9);
|
||||
fill: rgba(0, 0, 0, 0.9);
|
||||
}
|
||||
.ui-timepicker-list li.ui-timepicker-disabled, .ui-timepicker-list li.ui-timepicker-selected.ui-timepicker-disabled {
|
||||
background: #fff !important;
|
||||
cursor: default !important;
|
||||
.flatpickr-months .flatpickr-prev-month.flatpickr-disabled,
|
||||
.flatpickr-months .flatpickr-next-month.flatpickr-disabled {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.light-style .ui-timepicker-wrapper {
|
||||
z-index: 1091;
|
||||
background: #fff;
|
||||
box-shadow: 0 0.25rem 0.75rem 0 rgba(34, 48, 62, 0.14);
|
||||
border: 0 solid #e4e6e8;
|
||||
border-radius: 0.375rem;
|
||||
.flatpickr-months .flatpickr-prev-month i,
|
||||
.flatpickr-months .flatpickr-next-month i {
|
||||
position: relative;
|
||||
}
|
||||
.light-style .ui-timepicker-list li {
|
||||
color: #384551;
|
||||
.flatpickr-months .flatpickr-prev-month.flatpickr-prev-month,
|
||||
.flatpickr-months .flatpickr-next-month.flatpickr-prev-month {
|
||||
/*
|
||||
/*rtl:begin:ignore*/
|
||||
left: 0; /*
|
||||
/*rtl:end:ignore*/
|
||||
} /*
|
||||
/*rtl:begin:ignore*/
|
||||
/*
|
||||
/*rtl:end:ignore*/
|
||||
.flatpickr-months .flatpickr-prev-month.flatpickr-next-month,
|
||||
.flatpickr-months .flatpickr-next-month.flatpickr-next-month {
|
||||
/*
|
||||
/*rtl:begin:ignore*/
|
||||
right: 0; /*
|
||||
/*rtl:end:ignore*/
|
||||
} /*
|
||||
/*rtl:begin:ignore*/
|
||||
/*
|
||||
/*rtl:end:ignore*/
|
||||
.flatpickr-months .flatpickr-prev-month:hover,
|
||||
.flatpickr-months .flatpickr-next-month:hover {
|
||||
color: #959ea9;
|
||||
}
|
||||
.light-style .ui-timepicker-list li:hover {
|
||||
background: rgba(34, 48, 62, 0.06);
|
||||
.flatpickr-months .flatpickr-prev-month:hover svg,
|
||||
.flatpickr-months .flatpickr-next-month:hover svg {
|
||||
fill: #f64747;
|
||||
}
|
||||
.light-style .ui-timepicker-list li:not(.ui-timepicker-selected) .ui-timepicker-duration {
|
||||
color: #a7acb2;
|
||||
.flatpickr-months .flatpickr-prev-month svg,
|
||||
.flatpickr-months .flatpickr-next-month svg {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
}
|
||||
.ui-timepicker-list:hover .light-style .ui-timepicker-list li:not(.ui-timepicker-selected) .ui-timepicker-duration {
|
||||
color: #a7acb2;
|
||||
.flatpickr-months .flatpickr-prev-month svg path,
|
||||
.flatpickr-months .flatpickr-next-month svg path {
|
||||
-webkit-transition: fill 0.1s;
|
||||
transition: fill 0.1s;
|
||||
fill: inherit;
|
||||
}
|
||||
.light-style .ui-timepicker-list li.ui-timepicker-disabled,
|
||||
.light-style .ui-timepicker-list li.ui-timepicker-selected.ui-timepicker-disabled {
|
||||
background: #fff !important;
|
||||
color: #a7acb2 !important;
|
||||
.numInputWrapper {
|
||||
position: relative;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.dark-style .ui-timepicker-wrapper {
|
||||
border: 0 solid #4e4f6c;
|
||||
z-index: 1091;
|
||||
background: #2b2c40;
|
||||
box-shadow: 0 0.25rem 0.75rem 0 rgba(20, 20, 29, 0.24);
|
||||
border-radius: 0.375rem;
|
||||
.numInputWrapper input,
|
||||
.numInputWrapper span {
|
||||
display: inline-block;
|
||||
}
|
||||
.dark-style .ui-timepicker-list li {
|
||||
color: #d5d5e2;
|
||||
.numInputWrapper input {
|
||||
width: 100%;
|
||||
}
|
||||
.dark-style .ui-timepicker-list li:hover {
|
||||
background: rgba(230, 230, 241, 0.06);
|
||||
.numInputWrapper input::-ms-clear {
|
||||
display: none;
|
||||
}
|
||||
.dark-style .ui-timepicker-list li:not(.ui-timepicker-selected) .ui-timepicker-duration {
|
||||
color: #7e7f96;
|
||||
.numInputWrapper input::-webkit-outer-spin-button,
|
||||
.numInputWrapper input::-webkit-inner-spin-button {
|
||||
margin: 0;
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
.ui-timepicker-list:hover .dark-style .ui-timepicker-list li:not(.ui-timepicker-selected) .ui-timepicker-duration {
|
||||
color: #7e7f96;
|
||||
.numInputWrapper span {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
width: 14px;
|
||||
padding: 0 4px 0 2px;
|
||||
height: 50%;
|
||||
line-height: 50%;
|
||||
opacity: 0;
|
||||
cursor: pointer;
|
||||
border: 1px solid rgba(57, 57, 57, 0.15);
|
||||
-webkit-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.dark-style .ui-timepicker-list li.ui-timepicker-disabled,
|
||||
.dark-style .ui-timepicker-list li.ui-timepicker-selected.ui-timepicker-disabled {
|
||||
color: #7e7f96 !important;
|
||||
background: #2b2c40 !important;
|
||||
.numInputWrapper span:hover {
|
||||
background: rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
.numInputWrapper span:active {
|
||||
background: rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
.numInputWrapper span:after {
|
||||
display: block;
|
||||
content: "";
|
||||
position: absolute;
|
||||
}
|
||||
.numInputWrapper span.arrowUp {
|
||||
top: 0;
|
||||
border-bottom: 0;
|
||||
}
|
||||
.numInputWrapper span.arrowUp:after {
|
||||
border-left: 4px solid transparent;
|
||||
border-right: 4px solid transparent;
|
||||
border-bottom: 4px solid rgba(57, 57, 57, 0.6);
|
||||
top: 26%;
|
||||
}
|
||||
.numInputWrapper span.arrowDown {
|
||||
top: 50%;
|
||||
}
|
||||
.numInputWrapper span.arrowDown:after {
|
||||
border-left: 4px solid transparent;
|
||||
border-right: 4px solid transparent;
|
||||
border-top: 4px solid rgba(57, 57, 57, 0.6);
|
||||
top: 40%;
|
||||
}
|
||||
.numInputWrapper span svg {
|
||||
width: inherit;
|
||||
height: auto;
|
||||
}
|
||||
.numInputWrapper span svg path {
|
||||
fill: rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
.numInputWrapper:hover {
|
||||
background: rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
.numInputWrapper:hover span {
|
||||
opacity: 1;
|
||||
}
|
||||
.flatpickr-current-month {
|
||||
font-size: 135%;
|
||||
line-height: inherit;
|
||||
font-weight: 300;
|
||||
color: inherit;
|
||||
position: absolute;
|
||||
width: 75%;
|
||||
left: 12.5%;
|
||||
padding: 7.48px 0 0 0;
|
||||
line-height: 1;
|
||||
height: 34px;
|
||||
display: inline-block;
|
||||
text-align: center;
|
||||
-webkit-transform: translate3d(0, 0, 0);
|
||||
transform: translate3d(0, 0, 0);
|
||||
}
|
||||
.flatpickr-current-month span.cur-month {
|
||||
font-family: inherit;
|
||||
font-weight: 700;
|
||||
color: inherit;
|
||||
display: inline-block;
|
||||
margin-left: 0.5ch;
|
||||
padding: 0;
|
||||
}
|
||||
.flatpickr-current-month span.cur-month:hover {
|
||||
background: rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
.flatpickr-current-month .numInputWrapper {
|
||||
width: 6ch;
|
||||
width: 7ch\0;
|
||||
display: inline-block;
|
||||
}
|
||||
.flatpickr-current-month .numInputWrapper span.arrowUp:after {
|
||||
border-bottom-color: rgba(0, 0, 0, 0.9);
|
||||
}
|
||||
.flatpickr-current-month .numInputWrapper span.arrowDown:after {
|
||||
border-top-color: rgba(0, 0, 0, 0.9);
|
||||
}
|
||||
.flatpickr-current-month input.cur-year {
|
||||
background: transparent;
|
||||
-webkit-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
color: inherit;
|
||||
cursor: text;
|
||||
padding: 0 0 0 0.5ch;
|
||||
margin: 0;
|
||||
display: inline-block;
|
||||
font-size: inherit;
|
||||
font-family: inherit;
|
||||
font-weight: 300;
|
||||
line-height: inherit;
|
||||
height: auto;
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
vertical-align: initial;
|
||||
-webkit-appearance: textfield;
|
||||
-moz-appearance: textfield;
|
||||
appearance: textfield;
|
||||
}
|
||||
.flatpickr-current-month input.cur-year:focus {
|
||||
outline: 0;
|
||||
}
|
||||
.flatpickr-current-month input.cur-year[disabled],
|
||||
.flatpickr-current-month input.cur-year[disabled]:hover {
|
||||
font-size: 100%;
|
||||
color: rgba(0, 0, 0, 0.5);
|
||||
background: transparent;
|
||||
pointer-events: none;
|
||||
}
|
||||
.flatpickr-current-month .flatpickr-monthDropdown-months {
|
||||
appearance: menulist;
|
||||
background: transparent;
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
box-sizing: border-box;
|
||||
color: inherit;
|
||||
cursor: pointer;
|
||||
font-size: inherit;
|
||||
font-family: inherit;
|
||||
font-weight: 300;
|
||||
height: auto;
|
||||
line-height: inherit;
|
||||
margin: -1px 0 0 0;
|
||||
outline: none;
|
||||
padding: 0 0 0 0.5ch;
|
||||
position: relative;
|
||||
vertical-align: initial;
|
||||
-webkit-box-sizing: border-box;
|
||||
-webkit-appearance: menulist;
|
||||
-moz-appearance: menulist;
|
||||
width: auto;
|
||||
}
|
||||
.flatpickr-current-month .flatpickr-monthDropdown-months:focus,
|
||||
.flatpickr-current-month .flatpickr-monthDropdown-months:active {
|
||||
outline: none;
|
||||
}
|
||||
.flatpickr-current-month .flatpickr-monthDropdown-months:hover {
|
||||
background: rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
.flatpickr-current-month
|
||||
.flatpickr-monthDropdown-months
|
||||
.flatpickr-monthDropdown-month {
|
||||
background-color: transparent;
|
||||
outline: none;
|
||||
padding: 0;
|
||||
}
|
||||
.flatpickr-weekdays {
|
||||
background: transparent;
|
||||
text-align: center;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-box-align: center;
|
||||
-webkit-align-items: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
height: 28px;
|
||||
}
|
||||
.flatpickr-weekdays .flatpickr-weekdaycontainer {
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-box-flex: 1;
|
||||
-webkit-flex: 1;
|
||||
-ms-flex: 1;
|
||||
flex: 1;
|
||||
}
|
||||
span.flatpickr-weekday {
|
||||
cursor: default;
|
||||
font-size: 90%;
|
||||
background: transparent;
|
||||
color: rgba(0, 0, 0, 0.54);
|
||||
line-height: 1;
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
display: block;
|
||||
-webkit-box-flex: 1;
|
||||
-webkit-flex: 1;
|
||||
-ms-flex: 1;
|
||||
flex: 1;
|
||||
font-weight: bolder;
|
||||
}
|
||||
.dayContainer,
|
||||
.flatpickr-weeks {
|
||||
padding: 1px 0 0 0;
|
||||
}
|
||||
.flatpickr-days {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-box-align: start;
|
||||
-webkit-align-items: flex-start;
|
||||
-ms-flex-align: start;
|
||||
align-items: flex-start;
|
||||
width: 307.875px;
|
||||
}
|
||||
.flatpickr-days:focus {
|
||||
outline: 0;
|
||||
}
|
||||
.dayContainer {
|
||||
padding: 0;
|
||||
outline: 0;
|
||||
text-align: left;
|
||||
width: 307.875px;
|
||||
min-width: 307.875px;
|
||||
max-width: 307.875px;
|
||||
-webkit-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
display: inline-block;
|
||||
display: -ms-flexbox;
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: flex;
|
||||
-webkit-flex-wrap: wrap;
|
||||
flex-wrap: wrap;
|
||||
-ms-flex-wrap: wrap;
|
||||
-ms-flex-pack: justify;
|
||||
-webkit-justify-content: space-around;
|
||||
justify-content: space-around;
|
||||
-webkit-transform: translate3d(0, 0, 0);
|
||||
transform: translate3d(0, 0, 0);
|
||||
opacity: 1;
|
||||
}
|
||||
.dayContainer + .dayContainer {
|
||||
-webkit-box-shadow: -1px 0 0 #e6e6e6;
|
||||
box-shadow: -1px 0 0 #e6e6e6;
|
||||
}
|
||||
.flatpickr-day {
|
||||
background: none;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 150px;
|
||||
-webkit-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
color: #393939;
|
||||
cursor: pointer;
|
||||
font-weight: 400;
|
||||
width: 14.2857143%;
|
||||
-webkit-flex-basis: 14.2857143%;
|
||||
-ms-flex-preferred-size: 14.2857143%;
|
||||
flex-basis: 14.2857143%;
|
||||
max-width: 39px;
|
||||
height: 39px;
|
||||
line-height: 39px;
|
||||
margin: 0;
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
-webkit-box-pack: center;
|
||||
-webkit-justify-content: center;
|
||||
-ms-flex-pack: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
}
|
||||
.flatpickr-day.inRange,
|
||||
.flatpickr-day.prevMonthDay.inRange,
|
||||
.flatpickr-day.nextMonthDay.inRange,
|
||||
.flatpickr-day.today.inRange,
|
||||
.flatpickr-day.prevMonthDay.today.inRange,
|
||||
.flatpickr-day.nextMonthDay.today.inRange,
|
||||
.flatpickr-day:hover,
|
||||
.flatpickr-day.prevMonthDay:hover,
|
||||
.flatpickr-day.nextMonthDay:hover,
|
||||
.flatpickr-day:focus,
|
||||
.flatpickr-day.prevMonthDay:focus,
|
||||
.flatpickr-day.nextMonthDay:focus {
|
||||
cursor: pointer;
|
||||
outline: 0;
|
||||
background: #e6e6e6;
|
||||
border-color: #e6e6e6;
|
||||
}
|
||||
.flatpickr-day.today {
|
||||
border-color: #959ea9;
|
||||
}
|
||||
.flatpickr-day.today:hover,
|
||||
.flatpickr-day.today:focus {
|
||||
border-color: #959ea9;
|
||||
background: #959ea9;
|
||||
color: #fff;
|
||||
}
|
||||
.flatpickr-day.selected,
|
||||
.flatpickr-day.startRange,
|
||||
.flatpickr-day.endRange,
|
||||
.flatpickr-day.selected.inRange,
|
||||
.flatpickr-day.startRange.inRange,
|
||||
.flatpickr-day.endRange.inRange,
|
||||
.flatpickr-day.selected:focus,
|
||||
.flatpickr-day.startRange:focus,
|
||||
.flatpickr-day.endRange:focus,
|
||||
.flatpickr-day.selected:hover,
|
||||
.flatpickr-day.startRange:hover,
|
||||
.flatpickr-day.endRange:hover,
|
||||
.flatpickr-day.selected.prevMonthDay,
|
||||
.flatpickr-day.startRange.prevMonthDay,
|
||||
.flatpickr-day.endRange.prevMonthDay,
|
||||
.flatpickr-day.selected.nextMonthDay,
|
||||
.flatpickr-day.startRange.nextMonthDay,
|
||||
.flatpickr-day.endRange.nextMonthDay {
|
||||
background: #569ff7;
|
||||
-webkit-box-shadow: none;
|
||||
box-shadow: none;
|
||||
color: #fff;
|
||||
border-color: #569ff7;
|
||||
}
|
||||
.flatpickr-day.selected.startRange,
|
||||
.flatpickr-day.startRange.startRange,
|
||||
.flatpickr-day.endRange.startRange {
|
||||
border-radius: 50px 0 0 50px;
|
||||
}
|
||||
.flatpickr-day.selected.endRange,
|
||||
.flatpickr-day.startRange.endRange,
|
||||
.flatpickr-day.endRange.endRange {
|
||||
border-radius: 0 50px 50px 0;
|
||||
}
|
||||
.flatpickr-day.selected.startRange + .endRange:not(:nth-child(7n + 1)),
|
||||
.flatpickr-day.startRange.startRange + .endRange:not(:nth-child(7n + 1)),
|
||||
.flatpickr-day.endRange.startRange + .endRange:not(:nth-child(7n + 1)) {
|
||||
-webkit-box-shadow: -10px 0 0 #569ff7;
|
||||
box-shadow: -10px 0 0 #569ff7;
|
||||
}
|
||||
.flatpickr-day.selected.startRange.endRange,
|
||||
.flatpickr-day.startRange.startRange.endRange,
|
||||
.flatpickr-day.endRange.startRange.endRange {
|
||||
border-radius: 50px;
|
||||
}
|
||||
.flatpickr-day.inRange {
|
||||
border-radius: 0;
|
||||
-webkit-box-shadow: -5px 0 0 #e6e6e6, 5px 0 0 #e6e6e6;
|
||||
box-shadow: -5px 0 0 #e6e6e6, 5px 0 0 #e6e6e6;
|
||||
}
|
||||
.flatpickr-day.flatpickr-disabled,
|
||||
.flatpickr-day.flatpickr-disabled:hover,
|
||||
.flatpickr-day.prevMonthDay,
|
||||
.flatpickr-day.nextMonthDay,
|
||||
.flatpickr-day.notAllowed,
|
||||
.flatpickr-day.notAllowed.prevMonthDay,
|
||||
.flatpickr-day.notAllowed.nextMonthDay {
|
||||
color: rgba(57, 57, 57, 0.3);
|
||||
background: transparent;
|
||||
border-color: transparent;
|
||||
cursor: default;
|
||||
}
|
||||
.flatpickr-day.flatpickr-disabled,
|
||||
.flatpickr-day.flatpickr-disabled:hover {
|
||||
cursor: not-allowed;
|
||||
color: rgba(57, 57, 57, 0.1);
|
||||
}
|
||||
.flatpickr-day.week.selected {
|
||||
border-radius: 0;
|
||||
-webkit-box-shadow: -5px 0 0 #569ff7, 5px 0 0 #569ff7;
|
||||
box-shadow: -5px 0 0 #569ff7, 5px 0 0 #569ff7;
|
||||
}
|
||||
.flatpickr-day.hidden {
|
||||
visibility: hidden;
|
||||
}
|
||||
.rangeMode .flatpickr-day {
|
||||
margin-top: 1px;
|
||||
}
|
||||
.flatpickr-weekwrapper {
|
||||
float: left;
|
||||
}
|
||||
.flatpickr-weekwrapper .flatpickr-weeks {
|
||||
padding: 0 12px;
|
||||
-webkit-box-shadow: 1px 0 0 #e6e6e6;
|
||||
box-shadow: 1px 0 0 #e6e6e6;
|
||||
}
|
||||
.flatpickr-weekwrapper .flatpickr-weekday {
|
||||
float: none;
|
||||
width: 100%;
|
||||
line-height: 28px;
|
||||
}
|
||||
.flatpickr-weekwrapper span.flatpickr-day,
|
||||
.flatpickr-weekwrapper span.flatpickr-day:hover {
|
||||
display: block;
|
||||
width: 100%;
|
||||
max-width: none;
|
||||
color: rgba(57, 57, 57, 0.3);
|
||||
background: transparent;
|
||||
cursor: default;
|
||||
border: none;
|
||||
}
|
||||
.flatpickr-innerContainer {
|
||||
display: block;
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
}
|
||||
.flatpickr-rContainer {
|
||||
display: inline-block;
|
||||
padding: 0;
|
||||
-webkit-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.flatpickr-time {
|
||||
text-align: center;
|
||||
outline: 0;
|
||||
display: block;
|
||||
height: 0;
|
||||
line-height: 40px;
|
||||
max-height: 40px;
|
||||
-webkit-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
}
|
||||
.flatpickr-time:after {
|
||||
content: "";
|
||||
display: table;
|
||||
clear: both;
|
||||
}
|
||||
.flatpickr-time .numInputWrapper {
|
||||
-webkit-box-flex: 1;
|
||||
-webkit-flex: 1;
|
||||
-ms-flex: 1;
|
||||
flex: 1;
|
||||
width: 40%;
|
||||
height: 40px;
|
||||
float: left;
|
||||
}
|
||||
.flatpickr-time .numInputWrapper span.arrowUp:after {
|
||||
border-bottom-color: #393939;
|
||||
}
|
||||
.flatpickr-time .numInputWrapper span.arrowDown:after {
|
||||
border-top-color: #393939;
|
||||
}
|
||||
.flatpickr-time.hasSeconds .numInputWrapper {
|
||||
width: 26%;
|
||||
}
|
||||
.flatpickr-time.time24hr .numInputWrapper {
|
||||
width: 49%;
|
||||
}
|
||||
.flatpickr-time input {
|
||||
background: transparent;
|
||||
-webkit-box-shadow: none;
|
||||
box-shadow: none;
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
text-align: center;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
height: inherit;
|
||||
line-height: inherit;
|
||||
color: #393939;
|
||||
font-size: 14px;
|
||||
position: relative;
|
||||
-webkit-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
-webkit-appearance: textfield;
|
||||
-moz-appearance: textfield;
|
||||
appearance: textfield;
|
||||
}
|
||||
.flatpickr-time input.flatpickr-hour {
|
||||
font-weight: bold;
|
||||
}
|
||||
.flatpickr-time input.flatpickr-minute,
|
||||
.flatpickr-time input.flatpickr-second {
|
||||
font-weight: 400;
|
||||
}
|
||||
.flatpickr-time input:focus {
|
||||
outline: 0;
|
||||
border: 0;
|
||||
}
|
||||
.flatpickr-time .flatpickr-time-separator,
|
||||
.flatpickr-time .flatpickr-am-pm {
|
||||
height: inherit;
|
||||
float: left;
|
||||
line-height: inherit;
|
||||
color: #393939;
|
||||
font-weight: bold;
|
||||
width: 2%;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
-webkit-align-self: center;
|
||||
-ms-flex-item-align: center;
|
||||
align-self: center;
|
||||
}
|
||||
.flatpickr-time .flatpickr-am-pm {
|
||||
outline: 0;
|
||||
width: 18%;
|
||||
cursor: pointer;
|
||||
text-align: center;
|
||||
font-weight: 400;
|
||||
}
|
||||
.flatpickr-time input:hover,
|
||||
.flatpickr-time .flatpickr-am-pm:hover,
|
||||
.flatpickr-time input:focus,
|
||||
.flatpickr-time .flatpickr-am-pm:focus {
|
||||
background: #eee;
|
||||
}
|
||||
.flatpickr-input[readonly] {
|
||||
cursor: pointer;
|
||||
}
|
||||
@-webkit-keyframes fpFadeInDown {
|
||||
from {
|
||||
opacity: 0;
|
||||
-webkit-transform: translate3d(0, -20px, 0);
|
||||
transform: translate3d(0, -20px, 0);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
-webkit-transform: translate3d(0, 0, 0);
|
||||
transform: translate3d(0, 0, 0);
|
||||
}
|
||||
}
|
||||
@keyframes fpFadeInDown {
|
||||
from {
|
||||
opacity: 0;
|
||||
-webkit-transform: translate3d(0, -20px, 0);
|
||||
transform: translate3d(0, -20px, 0);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
-webkit-transform: translate3d(0, 0, 0);
|
||||
transform: translate3d(0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@ -29,7 +29,7 @@ const AttendLogs = ({ Id }) => {
|
||||
<tbody>
|
||||
{logs
|
||||
.slice()
|
||||
.sort((a, b) => new Date(b.activityTime) - new Date(a.activityTime))
|
||||
.sort((a, b) => b.id - a.id)
|
||||
.map((log, index) => (
|
||||
<tr key={index}>
|
||||
<td>{convertShortTime(log.activityTime)}</td>
|
||||
|
||||
@ -38,7 +38,7 @@ const Attendance = ( {attendance, getRole, handleModalData} ) =>
|
||||
return checkInB - checkInA; // Sort in descending order of checkInTime
|
||||
})
|
||||
.map( ( item ) => (
|
||||
<tr key={item.id}>
|
||||
<tr key={item.employeeId}>
|
||||
<td colSpan={2}>
|
||||
<div className="d-flex justify-content-start align-items-center">
|
||||
<Avatar
|
||||
@ -47,7 +47,6 @@ const Attendance = ( {attendance, getRole, handleModalData} ) =>
|
||||
></Avatar>
|
||||
<div className="d-flex flex-column">
|
||||
<a
|
||||
// href="#"
|
||||
onClick={(e) =>navigate(`/employee/${item.employeeId}?for=attendance`)}
|
||||
className="text-heading text-truncate cursor-pointer"
|
||||
>
|
||||
@ -60,14 +59,13 @@ const Attendance = ( {attendance, getRole, handleModalData} ) =>
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<span className="badge bg-label-primary me-1">
|
||||
{getRole(item.roleID)}
|
||||
</span>
|
||||
{item.jobRoleName}
|
||||
|
||||
</td>
|
||||
<td>{item.checkInTime ? convertShortTime(item.checkInTime):"--"}</td>
|
||||
<td>{item.checkOutTime ? convertShortTime(item.checkOutTime):"--"}</td>
|
||||
|
||||
<td className="mx-24" >
|
||||
<td className="text-center" >
|
||||
<RenderAttendanceStatus attendanceData={item} handleModalData={handleModalData} Tab={1} currentDate={null}/>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@ -4,7 +4,7 @@ import AttendLogs from './AttendLogs'
|
||||
import Confirmation from './Confirmation'
|
||||
|
||||
const AttendanceModel = ({modelConfig,closeModal,handleSubmitForm}) => {
|
||||
console.log(modelConfig)
|
||||
|
||||
return (
|
||||
<div className={`modal-dialog modal-md modal-simple ${modelConfig.type === "view" ? "modal-lg":"modal-md"}`} >
|
||||
<div className="modal-content">
|
||||
|
||||
@ -45,12 +45,13 @@ const AttendanceLog = ({ attendance, handleModalData, projectId }) => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="table-responsive">
|
||||
<div className="table-responsive text-nowrap">
|
||||
{attendance && attendance.length > 0 ? (
|
||||
<table className="table mb-0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th className="border-top-1" colSpan={2}>Name</th>
|
||||
<th className="border-top-1" >Role</th>
|
||||
<th>
|
||||
<i className="bx bxs-down-arrow-alt text-success"></i> Check-In
|
||||
</th>
|
||||
@ -69,7 +70,7 @@ const AttendanceLog = ({ attendance, handleModalData, projectId }) => {
|
||||
|
||||
{renderAttendanceData?.map((attendance, index) => (
|
||||
<tr key={index}>
|
||||
<td>
|
||||
<td colSpan={2}>
|
||||
<div className="d-flex justify-content-start align-items-center">
|
||||
<Avatar firstName={attendance.firstName} lastName={attendance.lastName} />
|
||||
<div className="d-flex flex-column">
|
||||
@ -81,6 +82,7 @@ const AttendanceLog = ({ attendance, handleModalData, projectId }) => {
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td>{ attendance.jobRoleName}</td>
|
||||
<td>{convertShortTime(attendance.checkInTime)}</td>
|
||||
<td>{attendance.checkOutTime ? convertShortTime(attendance.checkOutTime) : '--'}</td>
|
||||
<td className="text-center">
|
||||
|
||||
@ -11,7 +11,7 @@ import {checkIfCurrentDate} from "../../utils/dateUtils";
|
||||
|
||||
|
||||
const schema = z.object({
|
||||
time: z.string().nonempty({message:"Time is required"}),
|
||||
markTime: z.string().nonempty({message:"Time is required"}),
|
||||
description:z.string().optional()
|
||||
});
|
||||
|
||||
@ -35,18 +35,18 @@ const CheckCheckOutmodel = ({modeldata,closeModal,handleSubmitForm,}) => {
|
||||
const onSubmit = ( data ) =>
|
||||
{
|
||||
console.log(data)
|
||||
let record = {...data, date: new Date().toLocaleDateString(),latitude:coords.latitude,longitude:coords.longitude,employeeId:modeldata.employeeId,action:modeldata.action}
|
||||
let record = {...data, date: new Date().toLocaleDateString(),latitude:coords.latitude,longitude:coords.longitude,employeeId:modeldata.employeeId,action:modeldata.action,id:modeldata?.id || null}
|
||||
if(modeldata.forWhichTab === 1){
|
||||
handleSubmitForm(record)
|
||||
} else
|
||||
{
|
||||
console.log("is Date" ,checkIfCurrentDate(modeldata?.currentDate))
|
||||
|
||||
if ( modeldata?.currentDate && checkIfCurrentDate(modeldata?.currentDate) )
|
||||
{
|
||||
handleSubmitForm(record)
|
||||
} else
|
||||
{
|
||||
let formData = {...data, date: new Date().toLocaleDateString(),latitude:coords.latitude,longitude:coords.longitude,employeeId:modeldata.employeeId,projectId:projectId,action:modeldata.action}
|
||||
let formData = {...data, date: new Date().toLocaleDateString(),latitude:coords.latitude,longitude:coords.longitude,employeeId:modeldata.employeeId,projectId:projectId,action:modeldata.action,id:modeldata?.id || null}
|
||||
dispatch(markAttendance(formData))
|
||||
.unwrap()
|
||||
.then(() => {
|
||||
@ -67,14 +67,12 @@ const CheckCheckOutmodel = ({modeldata,closeModal,handleSubmitForm,}) => {
|
||||
<form className="row g-2" onSubmit={handleSubmit(onSubmit)}>
|
||||
|
||||
<div className="col-12 col-md-12">
|
||||
{/* <TimePicker label="Time" onChange={(e) => setValue("time", e)} /> */}
|
||||
|
||||
<TimePicker
|
||||
label="Choose a time"
|
||||
onChange={(e) => setValue("time", e)}
|
||||
onChange={(e) => setValue("markTime", e)}
|
||||
interval={10}
|
||||
/>
|
||||
{errors.time && <p className="text-danger">{errors.time.message}</p>}
|
||||
{errors. markTime && <p className="text-danger">{errors.markTime.message}</p>}
|
||||
</div>
|
||||
|
||||
|
||||
@ -123,7 +121,7 @@ const schemaReg = z.object({
|
||||
export const Regularization = ({modeldata,closeModal,handleSubmitForm})=>{
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const coords = usePositionTracker();
|
||||
console.log(modeldata)
|
||||
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
@ -138,10 +136,12 @@ export const Regularization = ({modeldata,closeModal,handleSubmitForm})=>{
|
||||
};
|
||||
|
||||
|
||||
const onSubmit = (data) => {
|
||||
let record = {...data, date: new Date().toLocaleDateString(),latitude:coords.latitude,longitude:coords.longitude}
|
||||
const onSubmit = ( data ) =>
|
||||
{
|
||||
|
||||
|
||||
let record = {...data, date: new Date().toLocaleDateString(),latitude:coords.latitude,longitude:coords.longitude, }
|
||||
|
||||
console.log(record)
|
||||
handleSubmitForm(record)
|
||||
closeModal()
|
||||
};
|
||||
|
||||
@ -1,567 +1,114 @@
|
||||
|
||||
import React, { useState, useEffect } from "react";
|
||||
import "../../components/Project/ProjectInfra.css";
|
||||
import BuildingModel from "../../components/Project/BuildingModel";
|
||||
import FloorModel from "../../components/Project/FloorModel";
|
||||
import BuildingModel from "../Project/Infrastructure/BuildingModel";
|
||||
import FloorModel from "../Project/Infrastructure/FloorModel";
|
||||
import showToast from "../../services/toastService";
|
||||
import WorkAreaModel from "../../components/Project/WorkAreaModel";
|
||||
import TaskModel from "../../components/Project/TaskModel";
|
||||
import WorkAreaModel from "../Project/Infrastructure/WorkAreaModel";
|
||||
import TaskModel from "../Project/Infrastructure/TaskModel";
|
||||
import ProjectRepository from "../../repositories/ProjectRepository";
|
||||
import Breadcrumb from "../../components/common/Breadcrumb";
|
||||
import {useProjectDetails, useProjects} from "../../hooks/useProjects";
|
||||
import {useHasUserPermission} from "../../hooks/useHasUserPermission";
|
||||
import {MANAGE_PROJECT_INFRA} from "../../utils/constants";
|
||||
import {useDispatch, useSelector} from "react-redux";
|
||||
import {useProfile} from "../../hooks/useProfile";
|
||||
import {setProjectId} from "../../slices/localVariablesSlice";
|
||||
import InfraTable from "../Project/Infrastructure/InfraTable";
|
||||
|
||||
|
||||
const InfraPlanning = ({ data, activityMaster, onDataChange }) => {
|
||||
const [expandedBuildings, setExpandedBuildings] = useState([]);
|
||||
const [project, setProject] = useState(data);
|
||||
const [buildings, setBuildings] = useState(data?.buildings);
|
||||
const InfraPlanning = () =>
|
||||
{
|
||||
const {profile: LoggedUser} = useProfile()
|
||||
const dispatch = useDispatch()
|
||||
const {projects,loading:project_listLoader,error:projects_error} = useProjects()
|
||||
|
||||
const [isBuildingModalOpen, setIsBuildingModalOpen] = useState(false);
|
||||
const [isFloorModalOpen, setIsFloorModalOpen] = useState(false);
|
||||
const [isWorkAreaModelOpen, setIsWorkAreaModalOpen] = useState(false);
|
||||
const [isTaskModelOpen, setIsTaskModalOpen] = useState(false);
|
||||
|
||||
const [isCreateModalOpen, setIsCreateModalOpen] = useState(false);
|
||||
const [clearFormTrigger, setClearFormTrigger] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
setProject(data);
|
||||
setBuildings(data?.buildings);
|
||||
}, [data]);
|
||||
|
||||
const openModal = (projectData) => {
|
||||
setIsCreateModalOpen(true);
|
||||
};
|
||||
|
||||
const openFloorModel = (projectData) => {
|
||||
setIsFloorModalOpen(true);
|
||||
};
|
||||
const closeFloorModel = () => {
|
||||
setIsFloorModalOpen(false);
|
||||
// const modalBackdrop = document.querySelector(".modal-backdrop");
|
||||
// if (modalBackdrop) modalBackdrop.remove();
|
||||
};
|
||||
|
||||
const handleFloorModelFormSubmit = (updatedFloor) => {
|
||||
if (updatedFloor.id == "") delete updatedFloor.id;
|
||||
|
||||
submitData([
|
||||
{
|
||||
building: null,
|
||||
floor: updatedFloor,
|
||||
workArea: null,
|
||||
},
|
||||
]);
|
||||
};
|
||||
|
||||
const submitData = (infraObject) => {
|
||||
|
||||
ProjectRepository.manageProjectInfra(infraObject)
|
||||
.then((response) => {
|
||||
fetchData();
|
||||
onDataChange("building-change");
|
||||
showToast("Details updated successfully.", "success");
|
||||
setClearFormTrigger(true); // Set trigger to true
|
||||
})
|
||||
.catch((error) => {
|
||||
showToast(error.message, "error");
|
||||
});
|
||||
|
||||
// api
|
||||
// .post("/api/project/manage-infra", infraObject)
|
||||
// .then((data) => {
|
||||
// fetchData();
|
||||
// onDataChange("building-change");
|
||||
// showToast("Details updated successfully.", "success");
|
||||
// setClearFormTrigger(true); // Set trigger to true
|
||||
// })
|
||||
// .catch((error) => {
|
||||
// showToast(error.message, "error");
|
||||
// });
|
||||
};
|
||||
|
||||
const openBuildingModel = (projectData) => {
|
||||
setIsBuildingModalOpen(true);
|
||||
};
|
||||
|
||||
const closeBuildingModel = () => {
|
||||
setIsBuildingModalOpen(false);
|
||||
|
||||
};
|
||||
const handleBuildingModelFormSubmit = (buildingmodel) => {
|
||||
if (buildingmodel.id == "" || buildingmodel.id == 0)
|
||||
delete buildingmodel.id;
|
||||
|
||||
let data = [
|
||||
{
|
||||
building: buildingmodel,
|
||||
floor: null,
|
||||
workArea: null,
|
||||
},
|
||||
];
|
||||
|
||||
submitData(data);
|
||||
};
|
||||
|
||||
const openWorkAreaModel = (projectData) => {
|
||||
setIsWorkAreaModalOpen(true);
|
||||
};
|
||||
|
||||
const closeWorkAreaModel = () => {
|
||||
setIsWorkAreaModalOpen(false);
|
||||
|
||||
};
|
||||
const handleWorkAreaModelFormSubmit = (updatedModel) => {
|
||||
if (updatedModel.id == "") delete updatedModel.id;
|
||||
|
||||
submitData([
|
||||
{
|
||||
building: null,
|
||||
floor: null,
|
||||
workArea: updatedModel,
|
||||
},
|
||||
]);
|
||||
};
|
||||
|
||||
const openTaskModel = (projectData) => {
|
||||
setIsTaskModalOpen(true);
|
||||
};
|
||||
|
||||
const closeTaskModel = () => {
|
||||
setIsTaskModalOpen(false);
|
||||
// const modalBackdrop = document.querySelector(".modal-backdrop");
|
||||
// if (modalBackdrop) modalBackdrop.remove();
|
||||
};
|
||||
const handleTaskModelFormSubmit = (updatedModel) => {
|
||||
if (updatedModel.id == "") updatedModel.id = 0;
|
||||
|
||||
//console.log("Form submitted:", updatedModel); // Replace this with an API call or state update
|
||||
ProjectRepository.manageProjectTasks([updatedModel])
|
||||
.then((response) => {
|
||||
onDataChange("task-change");
|
||||
showToast("Details updated successfully.", "success");
|
||||
setClearFormTrigger(true); // Set trigger to true
|
||||
})
|
||||
.catch((error) => {
|
||||
showToast(error.message, "error");
|
||||
});
|
||||
|
||||
// api
|
||||
// .post("/api/project/task", [updatedModel])
|
||||
// .then((data) => {
|
||||
// onDataChange("task-change");
|
||||
// showToast("Details updated successfully.", "success");
|
||||
// setClearFormTrigger(true); // Set trigger to true
|
||||
// })
|
||||
// .catch((error) => {
|
||||
// showToast(error.message, "error");
|
||||
// });
|
||||
};
|
||||
|
||||
const toggleBuilding = (id) => {
|
||||
setExpandedBuildings((prev) =>
|
||||
prev.includes(id) ? prev.filter((bid) => bid !== id) : [...prev, id]
|
||||
);
|
||||
};
|
||||
|
||||
const getContent = (building) => {
|
||||
let hasFloors =
|
||||
building.floors && building.floors.length > 0 ? true : false;
|
||||
return (
|
||||
<>
|
||||
{(() => {
|
||||
if (hasFloors) {
|
||||
return building.floors.map((floor) => (
|
||||
<React.Fragment key={floor.id}>
|
||||
{floor.workAreas.length > 0 ? (
|
||||
floor.workAreas.map((workArea) => (
|
||||
<React.Fragment key={workArea.id}>
|
||||
<tr>
|
||||
<td colSpan="4" className="text-start table-cell">
|
||||
<div className="row ps-2">
|
||||
<div className="col-6">
|
||||
{" "}
|
||||
<h6>
|
||||
<span>
|
||||
{" "}
|
||||
{floor.floorName} - {workArea.areaName} {" "}
|
||||
</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 + "%";
|
||||
};
|
||||
const selectedProject = useSelector((store)=>store.localVariables.projectId)
|
||||
const ManageInfra = useHasUserPermission( MANAGE_PROJECT_INFRA )
|
||||
const {projects_Details, loading: project_deatilsLoader, error: project_error} = useProjectDetails(selectedProject)
|
||||
useEffect( () =>
|
||||
{
|
||||
dispatch(setProjectId(projects[0]?.id))
|
||||
},[projects])
|
||||
|
||||
return (
|
||||
<>
|
||||
{isBuildingModalOpen && (
|
||||
<div
|
||||
className={`modal fade `}
|
||||
id="building-model"
|
||||
tabIndex="-1"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<BuildingModel
|
||||
project={data}
|
||||
onClose={closeBuildingModel}
|
||||
onSubmit={handleBuildingModelFormSubmit}
|
||||
clearTrigger={clearFormTrigger}
|
||||
onClearComplete={() => setClearFormTrigger(false)}
|
||||
></BuildingModel>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{isFloorModalOpen && (
|
||||
<div
|
||||
className={`modal fade `}
|
||||
id="floor-model"
|
||||
tabIndex="-1"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<FloorModel
|
||||
project={data}
|
||||
// building={null}
|
||||
onClose={closeFloorModel}
|
||||
onSubmit={handleFloorModelFormSubmit}
|
||||
clearTrigger={clearFormTrigger}
|
||||
onClearComplete={() => setClearFormTrigger(false)}
|
||||
></FloorModel>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{isWorkAreaModelOpen && (
|
||||
<div
|
||||
className={`modal fade `}
|
||||
id="work-area-model"
|
||||
tabIndex="-1"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<WorkAreaModel
|
||||
project={data}
|
||||
// building={null}
|
||||
onClose={closeWorkAreaModel}
|
||||
onSubmit={handleWorkAreaModelFormSubmit}
|
||||
clearTrigger={clearFormTrigger}
|
||||
onClearComplete={() => setClearFormTrigger(false)}
|
||||
></WorkAreaModel>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{isTaskModelOpen && (
|
||||
<div
|
||||
className={`modal fade `}
|
||||
id="task-model"
|
||||
tabIndex="-1"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<TaskModel
|
||||
project={data}
|
||||
activities={activityMaster}
|
||||
onClose={closeTaskModel}
|
||||
onSubmit={handleTaskModelFormSubmit}
|
||||
clearTrigger={clearFormTrigger}
|
||||
onClearComplete={() => setClearFormTrigger(false)}
|
||||
></TaskModel>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
<div className="col-md-12 col-lg-12 col-xl-12 order-0 mb-4">
|
||||
<div className="card">
|
||||
{/* <div className="card-header pb-4"></div> */}
|
||||
|
||||
<div className="card-body" style={{ padding: "0.5rem" }}>
|
||||
<div className="align-items-center">
|
||||
<div className="row">
|
||||
<div className="col-12 text-end mb-1">
|
||||
<button
|
||||
type="button"
|
||||
className="link-button link-button-sm m-1"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#building-model"
|
||||
onClick={() => openBuildingModel()}
|
||||
>
|
||||
<i className="bx bx-plus-circle me-2"></i>
|
||||
Manage Building
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
data-bs-toggle="modal"
|
||||
className="link-button m-1"
|
||||
data-bs-target="#floor-model"
|
||||
onClick={() => openFloorModel()}
|
||||
>
|
||||
<i className="bx bx-plus-circle me-2"></i>
|
||||
Manage Floors
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
data-bs-toggle="modal"
|
||||
className="link-button m-1"
|
||||
data-bs-target="#work-area-model"
|
||||
onClick={() => openWorkAreaModel()}
|
||||
>
|
||||
<i className="bx bx-plus-circle me-2"></i>
|
||||
Manage Work Areas
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
data-bs-toggle="modal"
|
||||
className="link-button m-1"
|
||||
data-bs-target="#task-model"
|
||||
onClick={() => openTaskModel()}
|
||||
>
|
||||
<i className="bx bx-plus-circle me-2"></i>
|
||||
Manage Tasks
|
||||
</button>
|
||||
</div>
|
||||
<div className="col-md-12 col-lg-12 col-xl-12 order-0 mb-4">
|
||||
<div className="card">
|
||||
<div className="card-body" style={{ padding: "0.5rem" }}>
|
||||
<div className="align-items-center">
|
||||
<div className="row ">
|
||||
<div className="col-sm-3 col-8 text-start mb-1">
|
||||
<select name="DataTables_Table_0_length"
|
||||
aria-controls="DataTables_Table_0"
|
||||
className="form-select form-select-sm"
|
||||
value={selectedProject}
|
||||
onChange={(e)=>dispatch(setProjectId(e.target.value))}
|
||||
aria-label=""
|
||||
>
|
||||
{(project_listLoader || projects.length < 0) && <option value="Loading..." disabled>Loading...</option> }
|
||||
|
||||
{!project_listLoader && projects?.filter(project =>
|
||||
LoggedUser?.projects?.map(Number).includes(project.id)).map((project)=>(
|
||||
<option key={project.id} value={project.id}>{project.name}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
<div className="row">
|
||||
<div className="col-12">
|
||||
{buildings && buildings.length > 0 && (
|
||||
<table className="table table-bordered">
|
||||
<tbody>
|
||||
{buildings.map((building) => (
|
||||
building.floors && building.floors.length > 0 && (
|
||||
<React.Fragment key={building.id}>
|
||||
<tr>
|
||||
<td
|
||||
colSpan="4"
|
||||
className="text-start"
|
||||
style={{
|
||||
background: "#f0f0f0",
|
||||
cursor: "pointer",
|
||||
}}
|
||||
onClick={() => toggleBuilding(building.id)}
|
||||
>
|
||||
<div className="row">
|
||||
<h5 style={{ marginBottom: "0px" }}>
|
||||
|
||||
{ building.name}
|
||||
{expandedBuildings.includes(building.id) ? (
|
||||
<i className="bx bx-chevron-down"></i>
|
||||
) : (
|
||||
<i className="bx bx-chevron-right"></i>
|
||||
)}
|
||||
</h5>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
{expandedBuildings.includes(building.id) && getContent(building)}
|
||||
|
||||
</React.Fragment>
|
||||
)
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* <div className={`col-12 text-end mb-1 ${!ManageInfra && 'd-none'} `} >
|
||||
<button
|
||||
type="button"
|
||||
className="link-button link-button-sm m-1 "
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#building-model"
|
||||
// onClick={() => openBuildingModel()}
|
||||
>
|
||||
<i className="bx bx-plus-circle me-2"></i>
|
||||
Manage Building
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
data-bs-toggle="modal"
|
||||
className="link-button m-1"
|
||||
data-bs-target="#floor-model"
|
||||
// onClick={() => openFloorModel()}
|
||||
>
|
||||
<i className="bx bx-plus-circle me-2"></i>
|
||||
Manage Floors
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
data-bs-toggle="modal"
|
||||
className="link-button m-1"
|
||||
data-bs-target="#work-area-model"
|
||||
// onClick={() => openWorkAreaModel()}
|
||||
>
|
||||
<i className="bx bx-plus-circle me-2"></i>
|
||||
Manage Work Areas
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
data-bs-toggle="modal"
|
||||
className="link-button m-1"
|
||||
data-bs-target="#task-model"
|
||||
// onClick={() => openTaskModel()}
|
||||
>
|
||||
<i className="bx bx-plus-circle me-2"></i>
|
||||
Manage Tasks
|
||||
</button>
|
||||
</div> */}
|
||||
</div>
|
||||
<div className="row ">
|
||||
{project_deatilsLoader && ( <p>Loading...</p> )}
|
||||
{( !project_deatilsLoader && projects_Details?.buildings.length === 0 ) && ( <p>No Result Found</p> )}
|
||||
|
||||
|
||||
|
||||
{(!project_deatilsLoader && projects_Details?.buildings?.length > 0) && (<InfraTable buildings={projects_Details?.buildings}/>)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@ -2,35 +2,43 @@ import React, { useEffect, useState } from 'react';
|
||||
import Avatar from '../common/Avatar';
|
||||
import { convertShortTime } from '../../utils/dateUtils';
|
||||
import RegularizationActions from './RegularizationActions';
|
||||
import {useSelector} from 'react-redux';
|
||||
import {useRegularizationRequests} from '../../hooks/useAttendance';
|
||||
import moment from 'moment';
|
||||
|
||||
|
||||
const Regularization = ({attendance,handleRequest}) => {
|
||||
const[attendances,setAttendances] = useState(attendance)
|
||||
const regularize_Requests = attendances.filter((att)=>att.activity === 2)
|
||||
const Regularization = ( { handleRequest} ) =>
|
||||
{
|
||||
var selectedProject = useSelector((store) => store.localVariables.projectId);
|
||||
const [ regularizesList, setregularizedList ] = useState( [] )
|
||||
const { regularizes, loading,error,refetch} = useRegularizationRequests(selectedProject)
|
||||
|
||||
|
||||
|
||||
useEffect(()=>{
|
||||
setAttendances(attendance)
|
||||
},[attendance])
|
||||
|
||||
setregularizedList(regularizes)
|
||||
},[regularizes])
|
||||
|
||||
return (
|
||||
<div className="table-responsive">
|
||||
<div className="table-responsive text-nowrap">
|
||||
<table className="table mb-0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th colSpan={2}>Name</th>
|
||||
<th>Date</th>
|
||||
<th><i className='bx bxs-down-arrow-alt text-success' ></i>Check-In</th>
|
||||
<th><i className='bx bxs-up-arrow-alt text-danger' ></i>Check-Out</th>
|
||||
<th>Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tbody>
|
||||
|
||||
{loading &&<td colSpan={5}>Loading...</td>}
|
||||
{
|
||||
regularize_Requests.length > 0 ? (
|
||||
regularize_Requests?.map((att, index) => (
|
||||
regularizes?.length > 0 ? (
|
||||
regularizes?.map((att, index) => (
|
||||
<tr key={index}>
|
||||
<td>
|
||||
<td colSpan={2}>
|
||||
<div className="d-flex justify-content-start align-items-center">
|
||||
<Avatar
|
||||
firstName={att.firstName}
|
||||
@ -48,18 +56,18 @@ const Regularization = ({attendance,handleRequest}) => {
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td>{att.date}</td>
|
||||
<td>{moment(att.checkOutTime).format("DD-MMM-YYYY")}</td>
|
||||
<td>{convertShortTime(att.checkInTime)}</td>
|
||||
<td>{att.checkOutTime ? convertShortTime(att.checkOutTime):"--"}</td>
|
||||
<td className='text-center ' >
|
||||
{/* <div className='d-flex justify-content-center align-items-center gap-3'> */}
|
||||
<RegularizationActions attendanceData={att} handleRequest={handleRequest} />
|
||||
<RegularizationActions attendanceData={att} handleRequest={handleRequest} refresh={refetch } />
|
||||
{/* </div> */}
|
||||
</td>
|
||||
</tr>
|
||||
))
|
||||
):(
|
||||
<td colSpan={5}>No Result Found</td>
|
||||
<td colSpan={5}>No Record Found</td>
|
||||
)
|
||||
}
|
||||
</tbody>
|
||||
|
||||
@ -1,21 +1,24 @@
|
||||
import React, { act, useEffect, useState } from 'react'
|
||||
import useAttendanceStatus, { ACTIONS } from '../../hooks/useAttendanceStatus';
|
||||
// import AttendanceRepository from '../../repositories/AttendanceRepository';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { usePositionTracker } from '../../hooks/usePositionTracker';
|
||||
import {markCurrentAttendance} from '../../slices/apiSlice/attendanceAllSlice';
|
||||
import {cacheData, getCachedData} from '../../slices/apiDataManager';
|
||||
import showToast from '../../services/toastService';
|
||||
|
||||
|
||||
const RegularizationActions = ({attendanceData,handleRequest}) => {
|
||||
const RegularizationActions = ({attendanceData,handleRequest,refresh}) => {
|
||||
const [status,setStatus] = useState()
|
||||
const [loadingApprove,setLoadingForApprove] = useState(false)
|
||||
const [loadingReject,setLoadingForReject] = useState(false)
|
||||
|
||||
const projectId = useSelector((store)=>store.localVariables.projectId)
|
||||
const {latitude,longitude} = usePositionTracker();
|
||||
|
||||
const dispatch = useDispatch()
|
||||
|
||||
const handleRegularization =(request_attendance,IsReqularize)=>{
|
||||
|
||||
|
||||
if(IsReqularize){
|
||||
setLoadingForApprove(true)
|
||||
}else{
|
||||
@ -34,8 +37,22 @@ const {latitude,longitude} = usePositionTracker();
|
||||
action:IsReqularize ? 4 : 5,
|
||||
image:null
|
||||
}
|
||||
console.log(req_Data)
|
||||
handleRequest(req_Data)
|
||||
|
||||
dispatch( markCurrentAttendance( req_Data ) ).then( ( action ) =>
|
||||
{
|
||||
|
||||
const regularizedList = getCachedData("regularizedList")
|
||||
|
||||
const updatedata = regularizedList?.data?.filter( item => item.id !== action.payload.id );
|
||||
|
||||
cacheData("regularizedList",{data:updatedata,projectId:projectId})
|
||||
setLoadingForApprove( false )
|
||||
setLoadingForReject( false )
|
||||
refresh()
|
||||
}).catch( ( error ) =>
|
||||
{
|
||||
showToast(error.message,"error")
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
@ -55,7 +72,7 @@ const {latitude,longitude} = usePositionTracker();
|
||||
|
||||
|
||||
return (
|
||||
<div className="w-auto d-flex gap-2 align-items-center justify-content-between">
|
||||
<div className="w-auto d-flex gap-2 align-items-center justify-content-end">
|
||||
{attendanceData.activity == 2 && (
|
||||
<>
|
||||
<button
|
||||
|
||||
@ -9,7 +9,7 @@ const RenderAttendanceStatus = ({ attendanceData, handleModalData,Tab,currentDat
|
||||
const handleButtonClick = (key) => {
|
||||
|
||||
if(key === 6){
|
||||
handleModalData({action:6,id :attendanceData?.id})
|
||||
handleModalData({action:6,id:attendanceData?.id})
|
||||
}else{
|
||||
|
||||
handleModalData({
|
||||
@ -22,8 +22,9 @@ const RenderAttendanceStatus = ({ attendanceData, handleModalData,Tab,currentDat
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<div className="w-auto d-flex gap-2 align-items-center justify-content-between">
|
||||
<div className="d-flex justify-content-end">
|
||||
<button
|
||||
type="button"
|
||||
className={`btn btn-xs ${color}`}
|
||||
@ -39,7 +40,7 @@ const RenderAttendanceStatus = ({ attendanceData, handleModalData,Tab,currentDat
|
||||
{attendanceData?.checkInTime && (
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-xs btn-success"
|
||||
className="btn btn-xs btn-success ms-2"
|
||||
tabIndex="0"
|
||||
aria-controls="DataTables_Table_0"
|
||||
data-bs-toggle="modal"
|
||||
|
||||
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 = () => {
|
||||
return (
|
||||
<div class="content-wrapper">
|
||||
<div className="content-wrapper">
|
||||
<div className="container-xxl flex-grow-1 container-p-y">
|
||||
<div className="card">
|
||||
<div className="card-datatable table-responsive">
|
||||
<table className="datatables-basic table border-top">
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th></th>
|
||||
<th>id</th>
|
||||
<th>Name</th>
|
||||
<th>Email</th>
|
||||
<th>Date</th>
|
||||
<th>Salary</th>
|
||||
<th>Status</th>
|
||||
<th>Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div className="offcanvas offcanvas-end" id="add-new-record">
|
||||
<div className="offcanvas-header border-bottom">
|
||||
<h5 className="offcanvas-title" id="exampleModalLabel">
|
||||
New Record
|
||||
</h5>
|
||||
<button
|
||||
type="button"
|
||||
className="btn-close text-reset"
|
||||
data-bs-dismiss="offcanvas"
|
||||
aria-label="Close"
|
||||
></button>
|
||||
</div>
|
||||
<div className="offcanvas-body flex-grow-1">
|
||||
<form
|
||||
className="add-new-record pt-0 row g-2"
|
||||
id="form-add-new-record"
|
||||
onsubmit="return false"
|
||||
>
|
||||
<div className="col-sm-12">
|
||||
<label className="form-label" for="basicFullname">
|
||||
Full Name
|
||||
</label>
|
||||
<div className="input-group input-group-merge">
|
||||
<span id="basicFullname2" className="input-group-text">
|
||||
<i className="bx bx-user"></i>
|
||||
</span>
|
||||
<input
|
||||
type="text"
|
||||
id="basicFullname"
|
||||
className="form-control dt-full-name"
|
||||
name="basicFullname"
|
||||
placeholder="John Doe"
|
||||
aria-label="John Doe"
|
||||
aria-describedby="basicFullname2"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-sm-12">
|
||||
<label className="form-label" for="basicPost">
|
||||
Post
|
||||
</label>
|
||||
<div className="input-group input-group-merge">
|
||||
<span id="basicPost2" className="input-group-text">
|
||||
<i className="bx bxs-briefcase"></i>
|
||||
</span>
|
||||
<input
|
||||
type="text"
|
||||
id="basicPost"
|
||||
name="basicPost"
|
||||
className="form-control dt-post"
|
||||
placeholder="Web Developer"
|
||||
aria-label="Web Developer"
|
||||
aria-describedby="basicPost2"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-sm-12">
|
||||
<label className="form-label" for="basicEmail">
|
||||
Email
|
||||
</label>
|
||||
<div className="input-group input-group-merge">
|
||||
<span className="input-group-text">
|
||||
<i className="bx bx-envelope"></i>
|
||||
</span>
|
||||
<input
|
||||
type="text"
|
||||
id="basicEmail"
|
||||
name="basicEmail"
|
||||
className="form-control dt-email"
|
||||
placeholder="john.doe@example.com"
|
||||
aria-label="john.doe@example.com"
|
||||
/>
|
||||
</div>
|
||||
<div className="form-text">
|
||||
You can use letters, numbers & periods
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-sm-12">
|
||||
<label className="form-label" for="basicDate">
|
||||
Joining Date
|
||||
</label>
|
||||
<div className="input-group input-group-merge">
|
||||
<span id="basicDate2" className="input-group-text">
|
||||
<i className="bx bx-calendar"></i>
|
||||
</span>
|
||||
<input
|
||||
type="text"
|
||||
className="form-control dt-date"
|
||||
id="basicDate"
|
||||
name="basicDate"
|
||||
aria-describedby="basicDate2"
|
||||
placeholder="MM/DD/YYYY"
|
||||
aria-label="MM/DD/YYYY"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-sm-12">
|
||||
<label className="form-label" for="basicSalary">
|
||||
Salary
|
||||
</label>
|
||||
<div className="input-group input-group-merge">
|
||||
<span id="basicSalary2" className="input-group-text">
|
||||
<i className="bx bx-dollar"></i>
|
||||
</span>
|
||||
<input
|
||||
type="number"
|
||||
id="basicSalary"
|
||||
name="basicSalary"
|
||||
className="form-control dt-salary"
|
||||
placeholder="12000"
|
||||
aria-label="12000"
|
||||
aria-describedby="basicSalary2"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-sm-12">
|
||||
<button
|
||||
type="submit"
|
||||
className="btn btn-primary data-submit me-sm-4 me-1"
|
||||
>
|
||||
Submit
|
||||
</button>
|
||||
<button
|
||||
type="reset"
|
||||
className="btn btn-outline-secondary"
|
||||
data-bs-dismiss="offcanvas"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container-xxl flex-grow-1 container-p-y">
|
||||
<div class="card">
|
||||
<div class="card-datatable table-responsive">
|
||||
<table class="datatables-basic table border-top">
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th></th>
|
||||
<th>id</th>
|
||||
<th>Name</th>
|
||||
<th>Email</th>
|
||||
<th>Date</th>
|
||||
<th>Salary</th>
|
||||
<th>Status</th>
|
||||
<th>Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="offcanvas offcanvas-end" id="add-new-record">
|
||||
<div class="offcanvas-header border-bottom">
|
||||
<h5 class="offcanvas-title" id="exampleModalLabel">New Record</h5>
|
||||
<button
|
||||
type="button"
|
||||
class="btn-close text-reset"
|
||||
data-bs-dismiss="offcanvas"
|
||||
aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="offcanvas-body flex-grow-1">
|
||||
<form class="add-new-record pt-0 row g-2" id="form-add-new-record" onsubmit="return false">
|
||||
<div class="col-sm-12">
|
||||
<label class="form-label" for="basicFullname">Full Name</label>
|
||||
<div class="input-group input-group-merge">
|
||||
<span id="basicFullname2" class="input-group-text"><i class="bx bx-user"></i></span>
|
||||
<input
|
||||
type="text"
|
||||
id="basicFullname"
|
||||
class="form-control dt-full-name"
|
||||
name="basicFullname"
|
||||
placeholder="John Doe"
|
||||
aria-label="John Doe"
|
||||
aria-describedby="basicFullname2" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-12">
|
||||
<label class="form-label" for="basicPost">Post</label>
|
||||
<div class="input-group input-group-merge">
|
||||
<span id="basicPost2" class="input-group-text"><i class="bx bxs-briefcase"></i></span>
|
||||
<input
|
||||
type="text"
|
||||
id="basicPost"
|
||||
name="basicPost"
|
||||
class="form-control dt-post"
|
||||
placeholder="Web Developer"
|
||||
aria-label="Web Developer"
|
||||
aria-describedby="basicPost2" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-12">
|
||||
<label class="form-label" for="basicEmail">Email</label>
|
||||
<div class="input-group input-group-merge">
|
||||
<span class="input-group-text"><i class="bx bx-envelope"></i></span>
|
||||
<input
|
||||
type="text"
|
||||
id="basicEmail"
|
||||
name="basicEmail"
|
||||
class="form-control dt-email"
|
||||
placeholder="john.doe@example.com"
|
||||
aria-label="john.doe@example.com" />
|
||||
</div>
|
||||
<div class="form-text">You can use letters, numbers & periods</div>
|
||||
</div>
|
||||
<div class="col-sm-12">
|
||||
<label class="form-label" for="basicDate">Joining Date</label>
|
||||
<div class="input-group input-group-merge">
|
||||
<span id="basicDate2" class="input-group-text"><i class="bx bx-calendar"></i></span>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control dt-date"
|
||||
id="basicDate"
|
||||
name="basicDate"
|
||||
aria-describedby="basicDate2"
|
||||
placeholder="MM/DD/YYYY"
|
||||
aria-label="MM/DD/YYYY" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-12">
|
||||
<label class="form-label" for="basicSalary">Salary</label>
|
||||
<div class="input-group input-group-merge">
|
||||
<span id="basicSalary2" class="input-group-text"><i class="bx bx-dollar"></i></span>
|
||||
<input
|
||||
type="number"
|
||||
id="basicSalary"
|
||||
name="basicSalary"
|
||||
class="form-control dt-salary"
|
||||
placeholder="12000"
|
||||
aria-label="12000"
|
||||
aria-describedby="basicSalary2" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-12">
|
||||
<button type="submit" class="btn btn-primary data-submit me-sm-4 me-1">Submit</button>
|
||||
<button type="reset" class="btn btn-outline-secondary" data-bs-dismiss="offcanvas">Cancel</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<hr className="my-12" />
|
||||
|
||||
<hr className="my-12" />
|
||||
|
||||
<hr className="my-12" />
|
||||
</div>
|
||||
|
||||
<hr class="my-12" />
|
||||
<div className="content-backdrop fade"></div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
|
||||
<hr class="my-12" />
|
||||
|
||||
|
||||
|
||||
<hr class="my-12" />
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<div class="content-backdrop fade"></div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default DemoTable
|
||||
export default DemoTable;
|
||||
|
||||
@ -11,7 +11,7 @@ import { Link, useNavigate, useParams } from "react-router-dom";
|
||||
import { formatDate } from "../../utils/dateUtils";
|
||||
import { useEmployeeProfile } from "../../hooks/useEmployees";
|
||||
import { clearCacheKey, getCachedData } from "../../slices/apiDataManager";
|
||||
import {clearApiCacheKey} from "../../slices/apiCacheSlice";
|
||||
import { clearApiCacheKey } from "../../slices/apiCacheSlice";
|
||||
|
||||
const mobileNumberRegex = /^[7-9]\d{9}$/;
|
||||
|
||||
@ -24,66 +24,101 @@ const ManageEmployee = () => {
|
||||
error,
|
||||
loading: empLoading,
|
||||
} = useEmployeeProfile(employeeId);
|
||||
dispatch( changeMaster( "Job Role" ) );
|
||||
const [disabledEmail,setDisabledEmail] = useState(false)
|
||||
dispatch(changeMaster("Job Role"));
|
||||
const [disabledEmail, setDisabledEmail] = useState(false);
|
||||
const { data: job_role, loading } = useMaster();
|
||||
const [isloading, setLoading] = useState(false);
|
||||
const navigation = useNavigate();
|
||||
const [currentEmployee, setCurrentEmployee] = useState();
|
||||
|
||||
|
||||
const userSchema = z
|
||||
.object({
|
||||
...(employeeId ? { Id: z.number().optional() } : {}),
|
||||
FirstName: z.string().min(1, { message: "First Name is required" }),
|
||||
MiddleName: z.string().optional(),
|
||||
LastName: z.string().min(1, { message: "Last Name is required" }),
|
||||
Email: z.string().optional(),
|
||||
CurrentAddress: z
|
||||
.string()
|
||||
.min(1, { message: "Current Address is required" }).max(150, { message: "Address cannot exceed 150 characters" }),
|
||||
BirthDate: z.string().min(1, { message: "Birth Date is required" }).refine((date, ctx) => {
|
||||
return new Date(date) <= new Date();
|
||||
}, {
|
||||
message: "Birth date cannot be in the future",
|
||||
}),
|
||||
JoiningDate: z.string().min(1, { message: "Joining Date is required" }).refine((date, ctx) => {
|
||||
return new Date(date) <= new Date();
|
||||
}, {
|
||||
message: "Joining date cannot be in the future",
|
||||
}),
|
||||
EmergencyPhoneNumber: z
|
||||
.string()
|
||||
.min(1, { message: "Phone Number is required" })
|
||||
.regex(mobileNumberRegex, { message: "Invalid phone number " }),
|
||||
EmergencyContactPerson: z
|
||||
.string()
|
||||
.min(1, { message: "Emergency Contact Person is required" }),
|
||||
AadharNumber: z.string()
|
||||
.regex(/^\d{12}$/, "Aadhar card must be exactly 12 digits long")
|
||||
const userSchema = z.object({
|
||||
...(employeeId ? { Id: z.number().optional() } : {}),
|
||||
FirstName: z.string().min(1, { message: "First Name is required" }),
|
||||
MiddleName: z.string().optional(),
|
||||
LastName: z.string().min(1, { message: "Last Name is required" }),
|
||||
Email: z
|
||||
.string()
|
||||
.optional()
|
||||
.refine(
|
||||
(val) =>
|
||||
!val || /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(val),
|
||||
{
|
||||
message: "Invalid email format",
|
||||
}
|
||||
)
|
||||
.refine(
|
||||
(val) => {
|
||||
if (!val) return true;
|
||||
const [local, domain] = val.split("@");
|
||||
return (
|
||||
val.length <= 320 &&
|
||||
local?.length <= 64 &&
|
||||
domain?.length <= 255
|
||||
);
|
||||
},
|
||||
{
|
||||
message: "Email local or domain part is too long",
|
||||
}
|
||||
),
|
||||
CurrentAddress: z
|
||||
.string()
|
||||
.min(1, { message: "Current Address is required" })
|
||||
.max(500, { message: "Address cannot exceed 250 characters" }),
|
||||
BirthDate: z
|
||||
.string()
|
||||
.min(1, { message: "Birth Date is required" })
|
||||
.refine(
|
||||
(date, ctx) => {
|
||||
return new Date(date) <= new Date();
|
||||
},
|
||||
{
|
||||
message: "Birth date cannot be in the future",
|
||||
}
|
||||
),
|
||||
JoiningDate: z
|
||||
.string()
|
||||
.min(1, { message: "Joining Date is required" })
|
||||
.refine(
|
||||
(date, ctx) => {
|
||||
return new Date(date) <= new Date();
|
||||
},
|
||||
{
|
||||
message: "Joining date cannot be in the future",
|
||||
}
|
||||
),
|
||||
EmergencyPhoneNumber: z
|
||||
.string()
|
||||
.min(1, { message: "Phone Number is required" })
|
||||
.regex(mobileNumberRegex, { message: "Invalid phone number " }),
|
||||
EmergencyContactPerson: z
|
||||
.string()
|
||||
.min(1, { message: "Emergency Contact Person is required" }),
|
||||
AadharNumber: z
|
||||
.string()
|
||||
.regex(/^\d{12}$/, "Aadhar card must be exactly 12 digits long")
|
||||
.nonempty("Aadhar card is required"),
|
||||
Gender: z
|
||||
.string()
|
||||
.min(1, { message: "Gender is required" })
|
||||
.refine((val) => val !== "Select Gender", {
|
||||
message: "Please select a gender",
|
||||
}),
|
||||
PanNumber: z
|
||||
Gender: z
|
||||
.string()
|
||||
.min(1, { message: "Gender is required" })
|
||||
.refine((val) => val !== "Select Gender", {
|
||||
message: "Please select a gender",
|
||||
}),
|
||||
PanNumber: z
|
||||
.string()
|
||||
.optional()
|
||||
.refine((val) => !val || /^[A-Z]{5}[0-9]{4}[A-Z]{1}$/.test(val), {
|
||||
message: "Invalid PAN number",
|
||||
}),
|
||||
PeramnentAddress: z
|
||||
.string()
|
||||
.min(1, { message: "Permanent Address is required" }).max(150, { message: "Address cannot exceed 150 characters" }),
|
||||
PhoneNumber: z
|
||||
.string()
|
||||
.min(1, { message: "Phone Number is required" })
|
||||
.regex(mobileNumberRegex, { message: "Invalid phone number " }),
|
||||
JobRoleId: z.string().min(1, { message: "Role is required" }),
|
||||
})
|
||||
|
||||
PermanentAddress: z
|
||||
.string()
|
||||
.min(1, { message: "Permanent Address is required" })
|
||||
.max(500, { message: "Address cannot exceed 250 characters" }),
|
||||
PhoneNumber: z
|
||||
.string()
|
||||
.min(1, { message: "Phone Number is required" })
|
||||
.regex(mobileNumberRegex, { message: "Invalid phone number " }),
|
||||
JobRoleId: z.string().min(1, { message: "Role is required" }),
|
||||
});
|
||||
|
||||
const {
|
||||
register,
|
||||
@ -108,7 +143,7 @@ const ManageEmployee = () => {
|
||||
AadharNumber: currentEmployee?.aadharNumber || "",
|
||||
Gender: currentEmployee?.gender || "",
|
||||
PanNumber: currentEmployee?.panNumber || "",
|
||||
PeramnentAddress: currentEmployee?.peramnentAddress || "",
|
||||
PermanentAddress: currentEmployee?.permanentAddress || "",
|
||||
PhoneNumber: currentEmployee?.phoneNumber || "",
|
||||
JobRoleId: currentEmployee?.jobRoleId || "",
|
||||
},
|
||||
@ -116,7 +151,7 @@ const ManageEmployee = () => {
|
||||
|
||||
const onSubmit = (data) => {
|
||||
setLoading(true);
|
||||
|
||||
|
||||
const formData = getValues();
|
||||
|
||||
const formDataToSend = new FormData();
|
||||
@ -140,11 +175,10 @@ const ManageEmployee = () => {
|
||||
}
|
||||
|
||||
EmployeeRepository.manageEmployee(formDataToSend)
|
||||
.then( ( response ) =>
|
||||
{
|
||||
showToast("Employee details updated successfully.", "success" );
|
||||
clearCacheKey("employeeListByProject")
|
||||
clearCacheKey( "allEmployeeList" )
|
||||
.then((response) => {
|
||||
showToast("Employee details updated successfully.", "success");
|
||||
clearCacheKey("employeeListByProject");
|
||||
clearCacheKey("allEmployeeList");
|
||||
setLoading(false);
|
||||
navigation("/employees");
|
||||
})
|
||||
@ -180,7 +214,7 @@ const ManageEmployee = () => {
|
||||
AadharNumber: currentEmployee.aadharNumber || "",
|
||||
Gender: currentEmployee.gender || "",
|
||||
PanNumber: currentEmployee.panNumber || "",
|
||||
PeramnentAddress: currentEmployee.peramnentAddress || "",
|
||||
PermanentAddress: currentEmployee.permanentAddress || "",
|
||||
PhoneNumber: currentEmployee.phoneNumber || "",
|
||||
JobRoleId: currentEmployee.jobRoleId?.toString() || "",
|
||||
}
|
||||
@ -197,11 +231,19 @@ const ManageEmployee = () => {
|
||||
<h6 className="mb-0">
|
||||
{employee ? "Update Employee" : "Create Employee"}
|
||||
</h6>
|
||||
|
||||
<span className="cursor-pointer fs-6" data-htm="true" data-bs-toggle="tooltip"
|
||||
data-bs-offset="0,6"
|
||||
data-bs-placement="top"
|
||||
data-bs-html="true" title="Move Back" onClick={()=>navigation("/employees")}><i class='bx bxs-chevron-left'></i> Back</span>
|
||||
|
||||
<span
|
||||
className="cursor-pointer fs-6"
|
||||
data-htm="true"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-offset="0,6"
|
||||
data-bs-placement="top"
|
||||
data-bs-html="true"
|
||||
title="Move Back"
|
||||
onClick={() => navigation("/employees")}
|
||||
>
|
||||
<i className="bx bxs-chevron-left"></i> Back
|
||||
</span>
|
||||
</div>
|
||||
<div className="card-body">
|
||||
{!currentEmployee && empLoading && (
|
||||
@ -289,8 +331,6 @@ const ManageEmployee = () => {
|
||||
{errors.Email.message}
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
</div>
|
||||
<div className="col-sm-6">
|
||||
<div className="form-text text-start">Phone Number</div>
|
||||
@ -404,22 +444,22 @@ const ManageEmployee = () => {
|
||||
)}
|
||||
</div>
|
||||
<div className="col-sm-6">
|
||||
<div className="form-text text-start">Permnant Address</div>
|
||||
<div className="form-text text-start">Permanent Address</div>
|
||||
|
||||
<textarea
|
||||
id="PeramnentAddress"
|
||||
id="PermanentAddress"
|
||||
className="form-control form-control-sm"
|
||||
placeholder="Permnant Address"
|
||||
aria-label="Permnant Address"
|
||||
placeholder="Permanent Address"
|
||||
aria-label="Permanent Address"
|
||||
aria-describedby="basic-icon-default-message2"
|
||||
{...register("PeramnentAddress")}
|
||||
{...register("PermanentAddress")}
|
||||
></textarea>
|
||||
{errors.PeramnentAddress && (
|
||||
{errors.PermanentAddress && (
|
||||
<div
|
||||
className="danger-text text-start"
|
||||
style={{ fontSize: "12px" }}
|
||||
>
|
||||
{errors.PeramnentAddress.message}
|
||||
{errors.PermanentAddress.message}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@ -444,7 +484,9 @@ const ManageEmployee = () => {
|
||||
Select Role
|
||||
</option>
|
||||
{job_role?.map((item) => (
|
||||
<option value={item?.id} key={item.id}>{item?.name} </option>
|
||||
<option value={item?.id} key={item.id}>
|
||||
{item?.name}{" "}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
@ -525,7 +567,14 @@ const ManageEmployee = () => {
|
||||
id="PanNumber"
|
||||
placeholder="PAN Number"
|
||||
/>
|
||||
{errors.PanNumber && <div className="danger-text text-start" style={{fontSize:"12px"}}>{errors.PanNumber.message}</div>}
|
||||
{errors.PanNumber && (
|
||||
<div
|
||||
className="danger-text text-start"
|
||||
style={{ fontSize: "12px" }}
|
||||
>
|
||||
{errors.PanNumber.message}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@ -69,7 +69,7 @@ const Header = () =>
|
||||
<a
|
||||
aria-label="toggle for sidebar"
|
||||
className="nav-item nav-link px-0 me-xl-4"
|
||||
href="#"
|
||||
|
||||
>
|
||||
<i className="bx bx-menu bx-sm"></i>
|
||||
</a>
|
||||
|
||||
@ -4,17 +4,17 @@ import menuData from "../../data/menuData.json";
|
||||
import {getCachedProfileData} from "../../slices/apiDataManager";
|
||||
|
||||
const Sidebar = () => {
|
||||
const logineUser = getCachedProfileData()
|
||||
// const logineUser = getCachedProfileData()
|
||||
const navigate = useNavigate()
|
||||
const handleLogout = (e) => {
|
||||
e.preventDefault();
|
||||
// logout();
|
||||
};
|
||||
// const handleLogout = (e) => {
|
||||
// e.preventDefault();
|
||||
// // logout();
|
||||
// };
|
||||
|
||||
const handleProfilePage = ()=>{
|
||||
console.log(profile?.employeeInfo?.id)
|
||||
navigate(`/employee/${profile?.employeeInfo?.id}?for=account`)
|
||||
}
|
||||
// const handleProfilePage = ()=>{
|
||||
// console.log(profile?.employeeInfo?.id)
|
||||
// navigate(`/employee/${profile?.employeeInfo?.id}?for=account`)
|
||||
// }
|
||||
return (
|
||||
<aside
|
||||
id="layout-menu"
|
||||
@ -58,7 +58,7 @@ const Sidebar = () => {
|
||||
))}
|
||||
</ul>
|
||||
|
||||
<div className="dropdown py-sm-4 mt-sm-auto ms-auto ms-sm-0 flex-shrink-1 ps-5">
|
||||
{/* <div className="dropdown py-sm-4 mt-sm-auto ms-auto ms-sm-0 flex-shrink-1 ps-5">
|
||||
<a
|
||||
href="#"
|
||||
className="d-flex align-items-center text-decoration-none dropdown-toggle"
|
||||
@ -137,7 +137,7 @@ const Sidebar = () => {
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div> */}
|
||||
</aside>
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,10 +1,9 @@
|
||||
import React,{useState} from "react";
|
||||
import React, { useState } from "react";
|
||||
import moment from "moment";
|
||||
import { ProjectStatus } from "../../utils/projectStatus";
|
||||
const AboutProject = ( {data} ) =>
|
||||
{
|
||||
const [CurrentProject,setCurrentProject] = useState(data)
|
||||
|
||||
const AboutProject = ({ data }) => {
|
||||
const [CurrentProject, setCurrentProject] = useState(data);
|
||||
|
||||
return (
|
||||
<>
|
||||
{data && (
|
||||
@ -24,7 +23,7 @@ const AboutProject = ( {data} ) =>
|
||||
</span>
|
||||
</li>
|
||||
<li className="d-flex align-items-center mb-4">
|
||||
<i class="bx bx-stop-circle"></i>{" "}
|
||||
<i className="bx bx-stop-circle"></i>{" "}
|
||||
<span className="fw-medium mx-2">End Date:</span>{" "}
|
||||
<span>
|
||||
{data.endDate
|
||||
@ -33,33 +32,34 @@ const AboutProject = ( {data} ) =>
|
||||
</span>
|
||||
</li>
|
||||
<li className="d-flex align-items-center mb-2">
|
||||
<i class="bx bx-trophy"></i>
|
||||
<i className="bx bx-trophy"></i>
|
||||
<span className="fw-medium mx-2">Status:</span>{" "}
|
||||
<span>{ProjectStatus(data.projectStatusId)}</span>
|
||||
</li>
|
||||
</ul>
|
||||
<small className="card-text text-uppercase text-muted small">
|
||||
Contacts
|
||||
</small>
|
||||
<ul className="list-unstyled my-3 py-1">
|
||||
<li className="d-flex align-items-center mb-4">
|
||||
<i className="bx bx-user"></i>
|
||||
<span className="fw-medium mx-2">Contact:</span>{" "}
|
||||
<span>{data.contactPerson}</span>
|
||||
</li>
|
||||
<li className="d-flex align-items-center mb-4">
|
||||
<i className="bx bx-phone"></i>
|
||||
<span className="fw-medium mx-2">Contact Number:</span>{" "}
|
||||
<span>NA</span>
|
||||
</li>
|
||||
<li className="d-flex align-items-center mb-4">
|
||||
<i className="bx bx-envelope"></i>
|
||||
<span className="fw-medium mx-2">Email:</span> <span>NA</span>
|
||||
</li>
|
||||
<li className="d-flex align-items-center mb-4">
|
||||
<i className="bx bx-flag"></i>
|
||||
<span className="fw-medium mx-2">Address:</span>{" "}
|
||||
</li>
|
||||
</ul>
|
||||
{/* <small className="card-text text-uppercase text-muted small">
|
||||
Contacts
|
||||
</small> */}
|
||||
<ul className="list-unstyled my-3 py-1">
|
||||
|
||||
{/* <li className="d-flex align-items-center mb-4">
|
||||
<i className="bx bx-phone"></i>
|
||||
<span className="fw-medium mx-2">Contact Number:</span>{" "}
|
||||
<span>NA</span>
|
||||
</li> */}
|
||||
{/* <li className="d-flex align-items-center mb-4">
|
||||
<i className="bx bx-envelope"></i>
|
||||
<span className="fw-medium mx-2">Email:</span> <span>NA</span>
|
||||
</li> */}
|
||||
<li className="d-flex align-items-start test-start mb-4">
|
||||
<span>{data.projectAddress}</span>
|
||||
</li>
|
||||
|
||||
@ -1,137 +1,139 @@
|
||||
import React from "react";
|
||||
import { ComingSoonPage } from "../../pages/Misc/ComingSoonPage";
|
||||
|
||||
const ActivityTimeline = () => {
|
||||
return (
|
||||
<div className="card card-action mb-6">
|
||||
<div className="card-header align-items-center">
|
||||
<h5 className="card-action-title mb-0">
|
||||
<i className="bx bx-bar-chart-alt-2 bx-lg text-body me-4"></i>
|
||||
Activity Timeline
|
||||
</h5>
|
||||
</div>
|
||||
<div className="card-body pt-3">
|
||||
<ul className="timeline mb-0">
|
||||
<li className="timeline-item timeline-item-transparent">
|
||||
<span className="timeline-point timeline-point-primary"></span>
|
||||
<div className="timeline-event">
|
||||
<div className="timeline-header mb-3">
|
||||
<h6 className="mb-0">12 Invoices have been paid</h6>
|
||||
<small className="text-muted">12 min ago</small>
|
||||
</div>
|
||||
<p className="mb-2">Invoices have been paid to the company</p>
|
||||
<div className="d-flex align-items-center mb-2">
|
||||
<div className="badge bg-lighter rounded d-flex align-items-center">
|
||||
<img
|
||||
src="../../assets//img/icons/misc/pdf.png"
|
||||
alt="img"
|
||||
width="15"
|
||||
className="me-2"
|
||||
></img>
|
||||
<span className="h6 mb-0 text-body">invoices.pdf</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<li className="timeline-item timeline-item-transparent">
|
||||
<span className="timeline-point timeline-point-success"></span>
|
||||
<div className="timeline-event">
|
||||
<div className="timeline-header mb-3">
|
||||
<h6 className="mb-0">Client Meeting</h6>
|
||||
<small className="text-muted">45 min ago</small>
|
||||
</div>
|
||||
<p className="mb-2">Project meeting with john @10:15am</p>
|
||||
<div className="d-flex justify-content-between flex-wrap gap-2 mb-2">
|
||||
<div className="d-flex flex-wrap align-items-center mb-50">
|
||||
<div className="avatar avatar-sm me-3">
|
||||
<img
|
||||
src="../../assets/img/avatars/1.png"
|
||||
alt="Avatar"
|
||||
className="rounded-circle"
|
||||
></img>
|
||||
</div>
|
||||
<div>
|
||||
<p className="mb-0 small fw-medium">
|
||||
Lester McCarthy (Client)
|
||||
</p>
|
||||
<small>CEO of ThemeSelection</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<li className="timeline-item timeline-item-transparent">
|
||||
<span className="timeline-point timeline-point-info"></span>
|
||||
<div className="timeline-event">
|
||||
<div className="timeline-header mb-3">
|
||||
<h6 className="mb-0">Create a new project for client</h6>
|
||||
<small className="text-muted">2 Day Ago</small>
|
||||
</div>
|
||||
<p className="mb-2">6 team members in a project</p>
|
||||
<ul className="list-group list-group-flush">
|
||||
<li className="list-group-item d-flex justify-content-between align-items-center flex-wrap border-top-0 p-0">
|
||||
<div className="d-flex flex-wrap align-items-center">
|
||||
<ul className="list-unstyled users-list d-flex align-items-center avatar-group m-0 me-2">
|
||||
<li
|
||||
data-bs-toggle="tooltip"
|
||||
data-popup="tooltip-custom"
|
||||
data-bs-placement="top"
|
||||
className="avatar pull-up"
|
||||
aria-label="Vinnie Mostowy"
|
||||
data-bs-original-title="Vinnie Mostowy"
|
||||
>
|
||||
<img
|
||||
className="rounded-circle"
|
||||
src="../../assets/img/avatars/1.png"
|
||||
alt="Avatar"
|
||||
></img>
|
||||
</li>
|
||||
<li
|
||||
data-bs-toggle="tooltip"
|
||||
data-popup="tooltip-custom"
|
||||
data-bs-placement="top"
|
||||
className="avatar pull-up"
|
||||
aria-label="Allen Rieske"
|
||||
data-bs-original-title="Allen Rieske"
|
||||
>
|
||||
<img
|
||||
className="rounded-circle"
|
||||
src="../../assets/img/avatars/4.png"
|
||||
alt="Avatar"
|
||||
></img>
|
||||
</li>
|
||||
<li
|
||||
data-bs-toggle="tooltip"
|
||||
data-popup="tooltip-custom"
|
||||
data-bs-placement="top"
|
||||
className="avatar pull-up"
|
||||
aria-label="Julee Rossignol"
|
||||
data-bs-original-title="Julee Rossignol"
|
||||
>
|
||||
<img
|
||||
className="rounded-circle"
|
||||
src="../../assets/img/avatars/2.png"
|
||||
alt="Avatar"
|
||||
></img>
|
||||
</li>
|
||||
<li className="avatar">
|
||||
<span
|
||||
className="avatar-initial rounded-circle pull-up text-heading"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-placement="bottom"
|
||||
data-bs-original-title="3 more"
|
||||
>
|
||||
+3
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
// <div className="card card-action mb-6">
|
||||
// <div className="card-header align-items-center">
|
||||
// <h5 className="card-action-title mb-0">
|
||||
// <i className="bx bx-bar-chart-alt-2 bx-lg text-body me-4"></i>
|
||||
// Activity Timeline
|
||||
// </h5>
|
||||
// </div>
|
||||
// <div className="card-body pt-3">
|
||||
// <ul className="timeline mb-0">
|
||||
// <li className="timeline-item timeline-item-transparent">
|
||||
// <span className="timeline-point timeline-point-primary"></span>
|
||||
// <div className="timeline-event">
|
||||
// <div className="timeline-header mb-3">
|
||||
// <h6 className="mb-0">12 Invoices have been paid</h6>
|
||||
// <small className="text-muted">12 min ago</small>
|
||||
// </div>
|
||||
// <p className="mb-2">Invoices have been paid to the company</p>
|
||||
// <div className="d-flex align-items-center mb-2">
|
||||
// <div className="badge bg-lighter rounded d-flex align-items-center">
|
||||
// <img
|
||||
// src="../../assets//img/icons/misc/pdf.png"
|
||||
// alt="img"
|
||||
// width="15"
|
||||
// className="me-2"
|
||||
// ></img>
|
||||
// <span className="h6 mb-0 text-body">invoices.pdf</span>
|
||||
// </div>
|
||||
// </div>
|
||||
// </div>
|
||||
// </li>
|
||||
// <li className="timeline-item timeline-item-transparent">
|
||||
// <span className="timeline-point timeline-point-success"></span>
|
||||
// <div className="timeline-event">
|
||||
// <div className="timeline-header mb-3">
|
||||
// <h6 className="mb-0">Client Meeting</h6>
|
||||
// <small className="text-muted">45 min ago</small>
|
||||
// </div>
|
||||
// <p className="mb-2">Project meeting with john @10:15am</p>
|
||||
// <div className="d-flex justify-content-between flex-wrap gap-2 mb-2">
|
||||
// <div className="d-flex flex-wrap align-items-center mb-50">
|
||||
// <div className="avatar avatar-sm me-3">
|
||||
// <img
|
||||
// src="../../assets/img/avatars/1.png"
|
||||
// alt="Avatar"
|
||||
// className="rounded-circle"
|
||||
// ></img>
|
||||
// </div>
|
||||
// <div>
|
||||
// <p className="mb-0 small fw-medium">
|
||||
// Lester McCarthy (Client)
|
||||
// </p>
|
||||
// <small>CEO of ThemeSelection</small>
|
||||
// </div>
|
||||
// </div>
|
||||
// </div>
|
||||
// </div>
|
||||
// </li>
|
||||
// <li className="timeline-item timeline-item-transparent">
|
||||
// <span className="timeline-point timeline-point-info"></span>
|
||||
// <div className="timeline-event">
|
||||
// <div className="timeline-header mb-3">
|
||||
// <h6 className="mb-0">Create a new project for client</h6>
|
||||
// <small className="text-muted">2 Day Ago</small>
|
||||
// </div>
|
||||
// <p className="mb-2">6 team members in a project</p>
|
||||
// <ul className="list-group list-group-flush">
|
||||
// <li className="list-group-item d-flex justify-content-between align-items-center flex-wrap border-top-0 p-0">
|
||||
// <div className="d-flex flex-wrap align-items-center">
|
||||
// <ul className="list-unstyled users-list d-flex align-items-center avatar-group m-0 me-2">
|
||||
// <li
|
||||
// data-bs-toggle="tooltip"
|
||||
// data-popup="tooltip-custom"
|
||||
// data-bs-placement="top"
|
||||
// className="avatar pull-up"
|
||||
// aria-label="Vinnie Mostowy"
|
||||
// data-bs-original-title="Vinnie Mostowy"
|
||||
// >
|
||||
// <img
|
||||
// className="rounded-circle"
|
||||
// src="../../assets/img/avatars/1.png"
|
||||
// alt="Avatar"
|
||||
// ></img>
|
||||
// </li>
|
||||
// <li
|
||||
// data-bs-toggle="tooltip"
|
||||
// data-popup="tooltip-custom"
|
||||
// data-bs-placement="top"
|
||||
// className="avatar pull-up"
|
||||
// aria-label="Allen Rieske"
|
||||
// data-bs-original-title="Allen Rieske"
|
||||
// >
|
||||
// <img
|
||||
// className="rounded-circle"
|
||||
// src="../../assets/img/avatars/4.png"
|
||||
// alt="Avatar"
|
||||
// ></img>
|
||||
// </li>
|
||||
// <li
|
||||
// data-bs-toggle="tooltip"
|
||||
// data-popup="tooltip-custom"
|
||||
// data-bs-placement="top"
|
||||
// className="avatar pull-up"
|
||||
// aria-label="Julee Rossignol"
|
||||
// data-bs-original-title="Julee Rossignol"
|
||||
// >
|
||||
// <img
|
||||
// className="rounded-circle"
|
||||
// src="../../assets/img/avatars/2.png"
|
||||
// alt="Avatar"
|
||||
// ></img>
|
||||
// </li>
|
||||
// <li className="avatar">
|
||||
// <span
|
||||
// className="avatar-initial rounded-circle pull-up text-heading"
|
||||
// data-bs-toggle="tooltip"
|
||||
// data-bs-placement="bottom"
|
||||
// data-bs-original-title="3 more"
|
||||
// >
|
||||
// +3
|
||||
// </span>
|
||||
// </li>
|
||||
// </ul>
|
||||
// </div>
|
||||
// </li>
|
||||
// </ul>
|
||||
// </div>
|
||||
// </li>
|
||||
// </ul>
|
||||
// </div>
|
||||
// </div>
|
||||
<ComingSoonPage></ComingSoonPage>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@ -1,32 +1,44 @@
|
||||
import React, { useState,useEffect } from "react";
|
||||
import { useDispatch } from "react-redux";
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { changeMaster } from "../../slices/localVariablesSlice";
|
||||
import useMaster from "../../hooks/masterHook/useMaster";
|
||||
import { employee } from "../../data/masters";
|
||||
import { useForm, Controller } from "react-hook-form";
|
||||
import { z } from "zod";
|
||||
import { getCachedData } from "../../slices/apiDataManager";
|
||||
|
||||
import { useModal } from "../../ModalContext";
|
||||
import { useProjects } from "../../hooks/useProjects";
|
||||
import { useEmployeesAllOrByProjectId } from "../../hooks/useEmployees";
|
||||
import { TasksRepository } from "../../repositories/ProjectRepository";
|
||||
import showToast from "../../services/toastService";
|
||||
|
||||
const schema = z.object({
|
||||
selectedEmployees: z.array(z.number()).min(1, {message:"At least one employee must be selected"}),
|
||||
})
|
||||
selectedEmployees: z
|
||||
.array(z.number())
|
||||
.min(1, { message: "At least one employee must be selected" }),
|
||||
description: z.string().min(1, { message: "description required" }),
|
||||
});
|
||||
|
||||
const AssignRoleModel = ({ assignData, onClose }) => {
|
||||
const [plannedTask, setPlannedTask] = useState();
|
||||
const { openModal, closeModal } = useModal();
|
||||
const selectedProject = useSelector(
|
||||
(store) => store.localVariables.projectId
|
||||
);
|
||||
const { employees } = useEmployeesAllOrByProjectId(selectedProject);
|
||||
|
||||
const dispatch = useDispatch();
|
||||
const { data, loading } = useMaster();
|
||||
const jobRoleData = getCachedData("Job Role");
|
||||
|
||||
const [selectedRole, setSelectedRole] = useState("all");
|
||||
const [selectedEmployees, setSelectedEmployees] = useState([]);
|
||||
|
||||
|
||||
const AssignRoleModel = ( {assignData,onClose}) => {
|
||||
const[target,setTraget] = useState("")
|
||||
const dispatch = useDispatch()
|
||||
const {data,loading} = useMaster()
|
||||
const jobRoleData = getCachedData("Job Role")
|
||||
|
||||
|
||||
const [selectedRole, setSelectedRole] = useState("all");
|
||||
const [selectedEmployees, setSelectedEmployees] = useState([]);
|
||||
|
||||
|
||||
const { handleSubmit, control, setValue, watch, formState: { errors } } = useForm({
|
||||
const { handleSubmit, control, setValue, watch, formState: { errors },reset } = useForm({
|
||||
defaultValues: {
|
||||
selectedEmployees: []
|
||||
selectedEmployees: [],
|
||||
description:""
|
||||
},
|
||||
resolver: (data) => {
|
||||
const validation = schema.safeParse(data);
|
||||
@ -35,217 +47,281 @@ const { handleSubmit, control, setValue, watch, formState: { errors } } = useFor
|
||||
},
|
||||
});
|
||||
|
||||
const handleRoleChange = (event) => {
|
||||
setSelectedRole(event.target.value);
|
||||
const handleRoleChange = (event) => {
|
||||
setSelectedRole(event.plannedTask.value);
|
||||
};
|
||||
|
||||
const filteredEmployees =
|
||||
selectedRole === "all"
|
||||
? employees
|
||||
: employees.filter((emp) => emp.JobRoleId.toString() === selectedRole);
|
||||
|
||||
// not need currently for this fun
|
||||
const handleEmployeeSelection = (employeeId, field) => {
|
||||
setSelectedEmployees((prevSelected) => {
|
||||
let updatedSelection;
|
||||
if (!prevSelected.includes(employeeId)) {
|
||||
updatedSelection = [...prevSelected, employeeId];
|
||||
} else {
|
||||
updatedSelection = prevSelected.filter((id) => id !== employeeId);
|
||||
}
|
||||
field.onChange(updatedSelection);
|
||||
return updatedSelection;
|
||||
});
|
||||
};
|
||||
|
||||
const removeEmployee = (employeeId) => {
|
||||
setSelectedEmployees((prevSelected) => {
|
||||
const updatedSelection = prevSelected.filter((id) => id !== employeeId);
|
||||
setValue("selectedEmployees", updatedSelection); // Ensure form state is updated
|
||||
return updatedSelection;
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
const onSubmit = async(data) => {
|
||||
const formattedData = {
|
||||
taskTeam: data.selectedEmployees,
|
||||
plannedTask: parseInt( plannedTask, 10 ),
|
||||
description: data.description,
|
||||
assignmentDate: new Date().toISOString(),
|
||||
workItemId:assignData?.workItem?.workItem.id
|
||||
};
|
||||
try
|
||||
{
|
||||
let response = await TasksRepository.assignTask( formattedData );
|
||||
showToast( "Task Successfully Assigend", "success" )
|
||||
reset()
|
||||
onClose()
|
||||
} catch ( error )
|
||||
{
|
||||
showToast("something wrong","error")
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
const filteredEmployees = selectedRole === "all"
|
||||
? employee
|
||||
: employee.filter((emp) => emp.JobRoleId.toString() === selectedRole);
|
||||
|
||||
|
||||
// not need currently for this fun
|
||||
const handleEmployeeSelection = (employeeId,field) => {
|
||||
|
||||
|
||||
setSelectedEmployees((prevSelected) => {
|
||||
let updatedSelection;
|
||||
if (!prevSelected.includes(employeeId)) {
|
||||
updatedSelection = [...prevSelected, employeeId];
|
||||
} else {
|
||||
updatedSelection = prevSelected.filter((id) => id !== employeeId);
|
||||
}
|
||||
field.onChange(updatedSelection); // Update form state with new selection
|
||||
return updatedSelection;
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
const removeEmployee = (employeeId) => {
|
||||
setSelectedEmployees((prevSelected) => {
|
||||
const updatedSelection = prevSelected.filter((id) => id !== employeeId);
|
||||
setValue("selectedEmployees", updatedSelection); // Ensure form state is updated
|
||||
return updatedSelection;
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
const onSubmit = (data) => {
|
||||
console.log( {...data.selectedEmployees,target});
|
||||
onClose()
|
||||
};
|
||||
|
||||
useEffect(()=>{
|
||||
dispatch(changeMaster("Job Role"))
|
||||
return ()=> setSelectedRole("all")
|
||||
},[dispatch])
|
||||
|
||||
|
||||
return (<>
|
||||
|
||||
|
||||
<div className="container-fluid my-1">
|
||||
<div className="mb-2">
|
||||
<div className="bs-stepper wizard-numbered d-flex justify-content-center align-items-center flex-wrap">
|
||||
{[
|
||||
assignData?.building?.name,
|
||||
assignData?.floor?.floorName,
|
||||
assignData?.workArea?.areaName,
|
||||
assignData?.workItem?.workItem?.activityMaster?.activityName
|
||||
].map((item, index, array) => (
|
||||
<div key={index} className="col d-flex justify-content-center align-items-center">
|
||||
<div className="bs-stepper-header p-1 text-center">
|
||||
<span className="fs-5">{item}</span>
|
||||
|
||||
{/* Arrow between items */}
|
||||
{index < array.length - 1 && (
|
||||
<div className="line">
|
||||
<i className="icon-base bx bx-chevron-right scaleX-n1-rtl"></i>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
<div className="row mb-1">
|
||||
<div className="col-sm-4">
|
||||
<div className="form-text text-start">Select Role</div>
|
||||
<div className="input-group input-group-merge">
|
||||
<select
|
||||
className="form-select form-select-sm"
|
||||
id="Role"
|
||||
value={selectedRole}
|
||||
onChange={handleRoleChange}
|
||||
aria-label=""
|
||||
return (
|
||||
<>
|
||||
<div className="container my-1">
|
||||
<div className="mb-2">
|
||||
<div className="bs-stepper wizard-numbered d-flex justify-content-center align-items-center flex-wrap">
|
||||
{[
|
||||
assignData?.building?.name,
|
||||
assignData?.floor?.floorName,
|
||||
assignData?.workArea?.areaName,
|
||||
assignData?.workItem?.workItem?.activityMaster?.activityName,
|
||||
].map((item, index, array) => (
|
||||
<div
|
||||
key={index}
|
||||
className="col d-flex justify-content-center align-items-center"
|
||||
>
|
||||
{loading && data ? "Loading..." : (
|
||||
<>
|
||||
<option value="all">All</option>
|
||||
{ jobRoleData?.map((item) => (
|
||||
<option key={item.id} value={item.id}>
|
||||
{item.name}
|
||||
</option>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="divider text-start">
|
||||
<div class="divider-text">Employee</div>
|
||||
</div>
|
||||
{selectedRole !== "" && (
|
||||
<div className="row mb-2">
|
||||
<div className="col-sm-12">
|
||||
<div className="row">
|
||||
{filteredEmployees.map((emp) => {
|
||||
const jobRole = jobRoleData?.find((role) => role.id === emp.JobRoleId);
|
||||
<div className="bs-stepper-header p-1 text-center">
|
||||
<span className="fs-5">{item}</span>
|
||||
|
||||
return (
|
||||
<div key={emp.id} className="col-6 col-sm-4 col-md-4 col-lg-3 mb-1">
|
||||
<div className="form-check text-start p-0">
|
||||
<div className="li-wrapper d-flex justify-content-start align-items-start">
|
||||
<Controller
|
||||
name="selectedEmployees"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<input
|
||||
{...field}
|
||||
className="form-check-input mx-2"
|
||||
type="checkbox"
|
||||
id={`employee-${emp.id}`}
|
||||
value={emp.id}
|
||||
checked={field.value.includes(emp.id)} // Ensure the checkbox reflects the current form state
|
||||
onChange={() => {
|
||||
// Directly update the form value
|
||||
handleEmployeeSelection(emp.id,field)
|
||||
// const updatedSelection = field.value.includes(emp.id)
|
||||
// ? field.value.filter((id) => id !== emp.id)
|
||||
// : [...field.value, emp.id];
|
||||
// field.onChange(updatedSelection); // Directly update form value
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<div className="list-content">
|
||||
<h6 className="mb-0">{emp.FirtsName} {emp.LastName}</h6>
|
||||
<small className="text-muted">
|
||||
{loading && (<p className="skeleton para" style={{height:"7px"}}></p>)}
|
||||
{data && !loading && (jobRole ? jobRole.name : 'Unknown Role')}
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* Arrow between items */}
|
||||
{index < array.length - 1 && (
|
||||
<div className="line">
|
||||
<i className="icon-base bx bx-chevron-right scaleX-n1-rtl"></i>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
<div className="row mb-1">
|
||||
<div className="col-sm-4">
|
||||
<div className="form-text text-start">Select Role</div>
|
||||
<div className="input-group input-group-merge">
|
||||
<select
|
||||
className="form-select form-select-sm"
|
||||
id="Role"
|
||||
value={selectedRole}
|
||||
onChange={handleRoleChange}
|
||||
aria-label=""
|
||||
>
|
||||
{loading && data ? (
|
||||
"Loading..."
|
||||
) : (
|
||||
<>
|
||||
<option value="all">All</option>
|
||||
{jobRoleData?.map((item) => (
|
||||
<option key={item.id} value={item.id}>
|
||||
{item.name}
|
||||
</option>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
{selectedEmployees.length > 0 && (
|
||||
<div className="mt-1">
|
||||
<div className="text-start px-2">
|
||||
{selectedEmployees.map((empId) => {
|
||||
const emp = employee.find((emp) => emp.id === empId);
|
||||
return (
|
||||
<span key={empId} className="badge bg-label-primary d-inline-flex align-items-center gap-2 me-1 p-2 mb-2">
|
||||
{emp.FirtsName} {emp.LastName}
|
||||
<p
|
||||
type="button"
|
||||
className=" btn-close-white p-0 m-0"
|
||||
aria-label="Close"
|
||||
onClick={() => {
|
||||
removeEmployee(empId);
|
||||
setValue("selectedEmployees", selectedEmployees.filter(id => id !== empId));
|
||||
}}
|
||||
><i className="icon-base bx bx-x icon-md "></i></p>
|
||||
</span>
|
||||
);
|
||||
})}
|
||||
<div className="divider text-start">
|
||||
<div className="divider-text">Employee</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{selectedRole !== "" && (
|
||||
<div className="row mb-2">
|
||||
<div className="col-sm-12">
|
||||
<div className="row">
|
||||
{filteredEmployees.map((emp) => {
|
||||
const jobRole = jobRoleData?.find(
|
||||
(role) => role.id === emp.jobRoleId
|
||||
);
|
||||
|
||||
<div class="col-md text-start mx-0 px-0">
|
||||
<div class="form-check form-check-inline mt-4 px-1">
|
||||
<label className="form-text fs-6" for="inlineCheckbox1">Pending Work</label>
|
||||
<label className="form-check-label ms-2" for="inlineCheckbox1">{ assignData?.workItem?.workItem?.plannedWork - assignData?.workItem?.workItem?.completedWork}</label>
|
||||
</div>
|
||||
<div className="form-check form-check-inline col-sm-2 col">
|
||||
<label for="defaultFormControlInput" className="form-label">Target</label>
|
||||
<input type="text" className="form-control form-control-sm " value={target} onChange={(e)=>setTraget(e.target.value)} id="defaultFormControlInput" aria-describedby="defaultFormControlHelp" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
return (
|
||||
<div
|
||||
key={emp.id}
|
||||
className="col-6 col-sm-4 col-md-4 col-lg-3 mb-1"
|
||||
>
|
||||
<div className="form-check text-start p-0">
|
||||
<div className="li-wrapper d-flex justify-content-start align-items-start">
|
||||
<Controller
|
||||
name="selectedEmployees"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<input
|
||||
{...field}
|
||||
className="form-check-input mx-2"
|
||||
type="checkbox"
|
||||
id={`employee-${emp.id}`}
|
||||
value={emp.id}
|
||||
checked={field.value.includes(emp.id)} // Ensure the checkbox reflects the current form state
|
||||
onChange={() => {
|
||||
handleEmployeeSelection(emp.id, field);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<div className="list-content">
|
||||
<h6 className="mb-0">
|
||||
{emp.firstName} {emp.lastName}
|
||||
</h6>
|
||||
<small className="text-muted">
|
||||
{loading && (
|
||||
<p
|
||||
className="skeleton para"
|
||||
style={{ height: "7px" }}
|
||||
></p>
|
||||
)}
|
||||
{data &&
|
||||
!loading &&
|
||||
(jobRole ? jobRole.name : "Unknown Role")}
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
{errors.selectedEmployees && (
|
||||
<div className="danger-text mt-2">
|
||||
<p>{errors.selectedEmployees[0]}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{selectedEmployees.length > 0 && (
|
||||
<div className="mt-1">
|
||||
<div className="text-start px-2">
|
||||
{selectedEmployees.map((empId) => {
|
||||
const emp = employees.find((emp) => emp.id === empId);
|
||||
return (
|
||||
<span
|
||||
key={empId}
|
||||
className="badge bg-label-primary d-inline-flex align-items-center gap-2 me-1 p-2 mb-2"
|
||||
>
|
||||
{emp.firstName} {emp.lastName}
|
||||
<p
|
||||
type="button"
|
||||
className=" btn-close-white p-0 m-0"
|
||||
aria-label="Close"
|
||||
onClick={() => {
|
||||
removeEmployee(empId);
|
||||
setValue(
|
||||
"selectedEmployees",
|
||||
selectedEmployees.filter((id) => id !== empId)
|
||||
);
|
||||
}}
|
||||
>
|
||||
<i className="icon-base bx bx-x icon-md "></i>
|
||||
</p>
|
||||
</span>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="col-12 d-flex justify-content-center align-items-center gap-sm-6 gap-8 text-center">
|
||||
<button type="submit" className="btn btn-sm btn-primary ">Submit</button>
|
||||
<button type="reset" className="btn btn-sm btn-label-secondary" data-bs-dismiss="modal" aria-label="Close">
|
||||
Cancel
|
||||
</button>
|
||||
<div className="col-md text-start mx-0 px-0">
|
||||
<div className="form-check form-check-inline mt-4 px-1">
|
||||
<label className="form-text fs-6" for="inlineCheckbox1">
|
||||
Pending Work
|
||||
</label>
|
||||
<label className="form-check-label ms-2" for="inlineCheckbox1">
|
||||
{assignData?.workItem?.workItem?.plannedWork -
|
||||
assignData?.workItem?.workItem?.completedWork}
|
||||
</label>
|
||||
</div>
|
||||
<div className="form-check form-check-inline col-sm-2 col">
|
||||
<label for="defaultFormControlInput" className="form-label">
|
||||
Target
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
className="form-control form-control-sm "
|
||||
value={plannedTask}
|
||||
onChange={(e) => setPlannedTask(e.target.value)}
|
||||
id="defaultFormControlInput"
|
||||
aria-describedby="defaultFormControlHelp"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{errors.selectedEmployees && (
|
||||
<div className="danger-text mt-1">
|
||||
<p>{errors.selectedEmployees[0]}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<label for="exampleFormControlTextarea1" className="form-label">
|
||||
Description
|
||||
</label>
|
||||
<Controller
|
||||
name="description"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<textarea
|
||||
{...field}
|
||||
className="form-control"
|
||||
id="exampleFormControlTextarea1"
|
||||
rows="3"
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
{errors.description && <div>{errors.description.message}</div>}
|
||||
|
||||
<div className="col-12 d-flex justify-content-center align-items-center gap-sm-6 gap-8 text-center mt-1">
|
||||
<button type="submit" className="btn btn-sm btn-primary ">
|
||||
Submit
|
||||
</button>
|
||||
<button
|
||||
type="reset"
|
||||
className="btn btn-sm btn-label-secondary"
|
||||
data-bs-dismiss="modal"
|
||||
aria-label="Close"
|
||||
onClick={closeModal}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
</>
|
||||
</>
|
||||
);
|
||||
};
|
||||
export default AssignRoleModel
|
||||
export default AssignRoleModel;
|
||||
|
||||
@ -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 { useForm } from "react-hook-form";
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { z } from "zod";
|
||||
import {useDispatch} from "react-redux";
|
||||
import {changeMaster} from "../../../slices/localVariablesSlice";
|
||||
import useMaster from "../../../hooks/masterHook/useMaster";
|
||||
|
||||
// Define Zod validation schema
|
||||
const taskSchema = z.object({
|
||||
buildingID: z.string().min(1, "Building is required"),
|
||||
floorId: z.string().min(1, "Floor is required"),
|
||||
workAreaId: z.number().min(1, "Work Area is required"),
|
||||
activityID: z.number().min(1, "Activity is required"),
|
||||
plannedWork: z.number().min(1, "Planned Work must be greater than 0"),
|
||||
completedWork: z.number().optional(),
|
||||
});
|
||||
|
||||
const defaultModel = {
|
||||
id: "0",
|
||||
workAreaId: 0,
|
||||
activityId: 0,
|
||||
activityID: 0,
|
||||
plannedWork: 0,
|
||||
completedWork: 0,
|
||||
};
|
||||
|
||||
const TaskModel = ({
|
||||
project,
|
||||
activities,
|
||||
onSubmit,
|
||||
clearTrigger,
|
||||
onClearComplete,
|
||||
}) => {
|
||||
onClearComplete,onClose
|
||||
} )=>{
|
||||
const dispatch = useDispatch()
|
||||
const {data:activities,loading} = useMaster()
|
||||
const [formData, setFormData] = useState(defaultModel);
|
||||
|
||||
const [selectedBuilding, setSelectedBuilding] = useState(null);
|
||||
const [selectedFloor, setSelectedFloor] = useState(null);
|
||||
const [selectedWorkArea, setSelectedWorkArea] = useState(null);
|
||||
const [selectedActivity, setSelectedActivity] = useState(null);
|
||||
|
||||
//if (floor && floor.id) setFormData(floor);
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedBuilding) {
|
||||
let building = project.buildings.find(
|
||||
(b) => b.id === selectedBuilding.id
|
||||
);
|
||||
setSelectedBuilding(building);
|
||||
}
|
||||
|
||||
if (selectedFloor) {
|
||||
let floor = selectedBuilding.floors.find(
|
||||
(b) => b.id === Number(selectedFloor.id)
|
||||
);
|
||||
setSelectedFloor(floor);
|
||||
}
|
||||
if (selectedWorkArea) {
|
||||
formData.workAreaId = selectedWorkArea.id;
|
||||
}
|
||||
}, [project]);
|
||||
|
||||
const { register, handleSubmit, formState: { errors },reset } = useForm({
|
||||
resolver: zodResolver(taskSchema),
|
||||
defaultValues: formData,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (clearTrigger) {
|
||||
let model = defaultModel;
|
||||
model.floorId = selectedFloor.id;
|
||||
setFormData(defaultModel); // Clear form
|
||||
onClearComplete(); // Notify parent that clearing is done
|
||||
setFormData(defaultModel);
|
||||
onClearComplete();
|
||||
}
|
||||
}, [clearTrigger, onClearComplete]);
|
||||
|
||||
// Handle input change
|
||||
// Handle input changes
|
||||
const handleChange = (e) => {
|
||||
const { name, value } = e.target;
|
||||
const activity = activities.find((b) => b.id === Number(value));
|
||||
@ -63,29 +62,15 @@ const TaskModel = ({
|
||||
const { value } = e.target;
|
||||
const activity = activities.find((b) => b.id === Number(value));
|
||||
|
||||
setFormData({ ...formData, ["activityId"]: value });
|
||||
setFormData({ ...formData, ["activityID"]: value });
|
||||
setSelectedActivity(activity);
|
||||
};
|
||||
|
||||
const handleWorkAreaChange = (e) => {
|
||||
const { value } = e.target;
|
||||
const workArea = selectedFloor.workAreas.find(
|
||||
(b) => b.id === Number(value)
|
||||
);
|
||||
const workArea = selectedFloor.workAreas.find((b) => b.id === Number(value));
|
||||
setSelectedWorkArea(workArea);
|
||||
setSelectedActivity(null);
|
||||
setFormData({ ...formData, ["workAreaId"]: value });
|
||||
// if (workArea) {
|
||||
// setFormData({
|
||||
// id: workArea.id,
|
||||
// floorId: workArea.floorId,
|
||||
// areaName: workArea.areaName,
|
||||
// });
|
||||
// } else
|
||||
// setFormData({
|
||||
// id: "0",
|
||||
// floorId: selectedFloor.id,
|
||||
// areaName: "",
|
||||
// });
|
||||
};
|
||||
|
||||
const handleFloorChange = (e) => {
|
||||
@ -96,7 +81,8 @@ const TaskModel = ({
|
||||
setSelectedActivity(null);
|
||||
setFormData(defaultModel);
|
||||
};
|
||||
const handleBuildigChange = (e) => {
|
||||
|
||||
const handleBuildingChange = (e) => {
|
||||
const { value } = e.target;
|
||||
const building = project.buildings.find((b) => b.id === Number(value));
|
||||
setSelectedBuilding(building);
|
||||
@ -106,39 +92,59 @@ const TaskModel = ({
|
||||
setFormData(defaultModel);
|
||||
};
|
||||
|
||||
// Handle form submission
|
||||
const handleSubmit = (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
onSubmit(formData); // Pass the updated data to the parent
|
||||
const onSubmitForm = ( data ) =>
|
||||
{
|
||||
onSubmit( data );
|
||||
setSelectedActivity(null),
|
||||
setSelectedWorkArea(null)
|
||||
reset( {
|
||||
plannedWork: 0,
|
||||
completedWork:0
|
||||
|
||||
})
|
||||
};
|
||||
|
||||
useEffect( () =>
|
||||
{
|
||||
|
||||
dispatch(changeMaster("Activity")),
|
||||
() =>{
|
||||
resetVlaue ()
|
||||
}
|
||||
},[])
|
||||
const resetVlaue = () =>
|
||||
{
|
||||
setSelectedBuilding( null )
|
||||
setSelectedFloor( null )
|
||||
setSelectedWorkArea( null )
|
||||
setSelectedActivity(null)
|
||||
reset( {
|
||||
plannedWork: 0,
|
||||
completedWork:0
|
||||
})
|
||||
}
|
||||
return (
|
||||
<div className="modal-dialog modal-lg modal-simple modal-edit-user">
|
||||
<div className="modal-content">
|
||||
<div className="modal-body">
|
||||
<div className="row">
|
||||
<button
|
||||
type="button"
|
||||
className="btn-close"
|
||||
data-bs-dismiss="modal"
|
||||
aria-label="Close"
|
||||
></button>
|
||||
<button type="button" className="btn-close" aria-label="Close" onClick={onClose}/>
|
||||
<div className="text-center mb-1">
|
||||
<h5 className="mb-1">Manage Task</h5>
|
||||
</div>
|
||||
<form className="row g-2" onSubmit={handleSubmit}>
|
||||
<form className="row g-2" onSubmit={handleSubmit(onSubmitForm)}>
|
||||
{/* Select Building */}
|
||||
<div className="col-6 col-md-6">
|
||||
<label className="form-label" htmlFor="name">
|
||||
Select Building
|
||||
</label>
|
||||
<select
|
||||
id="buildingId"
|
||||
name="buildingId"
|
||||
id="buildingID"
|
||||
name="buildingID"
|
||||
className="select2 form-select form-select-sm"
|
||||
aria-label="Default select example"
|
||||
onChange={handleBuildigChange}
|
||||
value={formData.buildingId}
|
||||
{...register("buildingID")}
|
||||
onChange={(e) => handleBuildingChange(e)}
|
||||
>
|
||||
<option value="0">Select Building</option>
|
||||
{project.buildings.map((building) => (
|
||||
@ -147,9 +153,11 @@ const TaskModel = ({
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
{errors.buildingID && <p className="danger-text">{errors.buildingID.message}</p>}
|
||||
</div>
|
||||
|
||||
{selectedBuilding && selectedBuilding.id != "0" && (
|
||||
{/* Select Floor */}
|
||||
{selectedBuilding && selectedBuilding.id !== "0" && (
|
||||
<div className="col-6 col-md-6">
|
||||
<label className="form-label" htmlFor="floorId">
|
||||
Select Floor
|
||||
@ -159,19 +167,21 @@ const TaskModel = ({
|
||||
name="floorId"
|
||||
className="select2 form-select form-select-sm"
|
||||
aria-label="Default select example"
|
||||
onChange={handleFloorChange}
|
||||
value={formData.floorId}
|
||||
{...register("floorId")}
|
||||
onChange={(e) => handleFloorChange(e)}
|
||||
>
|
||||
<option value="0">Select Floor</option>
|
||||
{selectedBuilding.floors.map((floor) => (
|
||||
<option key={floor.id} value={floor.id}>
|
||||
{floor.floorName} - ({floor.workAreas.length} Work
|
||||
Areas)
|
||||
{floor.floorName} - ({floor.workAreas.length} Work Areas)
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
{errors.floorId && <p className="danger-text">{errors.floorId.message}</p>}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Select Work Area */}
|
||||
{selectedFloor && (
|
||||
<div className="col-12 col-md-12">
|
||||
<label className="form-label" htmlFor="workAreaId">
|
||||
@ -182,8 +192,8 @@ const TaskModel = ({
|
||||
name="workAreaId"
|
||||
className="select2 form-select form-select-sm"
|
||||
aria-label="Default select example"
|
||||
onChange={handleWorkAreaChange}
|
||||
value={formData.workAreaId}
|
||||
{...register("workAreaId",{valueAsNumber:true})}
|
||||
onChange={(e) => handleWorkAreaChange(e)}
|
||||
>
|
||||
<option value="0">Select Work Area</option>
|
||||
{selectedFloor.workAreas.map((workArea) => (
|
||||
@ -192,20 +202,23 @@ const TaskModel = ({
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
{errors.workAreaId && <p className="danger-text">{errors.workAreaId.message}</p>}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Select Activity */}
|
||||
{selectedWorkArea && (
|
||||
<div className="col-12 col-md-12">
|
||||
<label className="form-label" htmlFor="activityId">
|
||||
<label className="form-label" htmlFor="activityID">
|
||||
Select Activity
|
||||
</label>
|
||||
<select
|
||||
id="activityId"
|
||||
name="activityId"
|
||||
id="activityID"
|
||||
name="activityID"
|
||||
className="select2 form-select form-select-sm"
|
||||
aria-label="Default select example"
|
||||
onChange={handleActivityChange}
|
||||
value={formData.activityId}
|
||||
{...register("activityID",{valueAsNumber:true})}
|
||||
onChange={(e) => handleActivityChange(e)}
|
||||
>
|
||||
<option value="0">Select Activity</option>
|
||||
{activities.map((activity) => (
|
||||
@ -214,83 +227,75 @@ const TaskModel = ({
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
)}
|
||||
{selectedActivity && (
|
||||
<div className="col-5 col-md-5">
|
||||
{" "}
|
||||
<label className="form-label" htmlFor="plannedWork">
|
||||
{formData.id != "0" ? "Modify " : "Enter "} Planned Work
|
||||
</label>
|
||||
<div className="input-group">
|
||||
<input
|
||||
type="text"
|
||||
id="plannedWork"
|
||||
name="plannedWork"
|
||||
className="form-control form-control-sm me-2"
|
||||
placeholder="Task"
|
||||
onChange={handleChange}
|
||||
value={formData.plannedWork}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{selectedActivity && (
|
||||
<div className="col-5 col-md-5">
|
||||
{" "}
|
||||
<label className="form-label" htmlFor="completedWork">
|
||||
{formData.id != "0" ? "Modify " : "Enter "} Completed Work
|
||||
</label>
|
||||
<div className="input-group">
|
||||
<input
|
||||
type="text"
|
||||
id="completedWork"
|
||||
name="completedWork"
|
||||
className="form-control form-control-sm me-2"
|
||||
placeholder="Completed Work"
|
||||
onChange={handleChange}
|
||||
value={formData.completedWork}
|
||||
/>
|
||||
</div>
|
||||
{errors.activityID && <p className="danger-text">{errors.activityID.message}</p>}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Planned Work */}
|
||||
{selectedActivity && (
|
||||
<div className="col-5 col-md-5">
|
||||
<label className="form-label" htmlFor="plannedWork">
|
||||
{formData.id !== "0" ? "Modify " : "Enter "} Planned Work
|
||||
</label>
|
||||
<input
|
||||
{...register("plannedWork", { valueAsNumber: true })}
|
||||
type="number"
|
||||
id="plannedWork"
|
||||
name="plannedWork"
|
||||
className="form-control form-control-sm me-2"
|
||||
placeholder="Planned Work"
|
||||
/>
|
||||
{errors.plannedWork && <p className="danger-text">{errors.plannedWork.message}</p>}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Completed Work */}
|
||||
{selectedActivity && (
|
||||
<div className="col-5 col-md-5">
|
||||
<label className="form-label" htmlFor="completedWork">
|
||||
{formData.id !== "0" ? "Modify " : "Enter "} Completed Work
|
||||
</label>
|
||||
<input
|
||||
{...register("completedWork", { valueAsNumber: true })}
|
||||
type="number"
|
||||
id="completedWork"
|
||||
name="completedWork"
|
||||
className="form-control form-control-sm me-2"
|
||||
placeholder="Completed Work"
|
||||
/>
|
||||
{errors.completedWork && <p className="danger-text">{errors.completedWork.message}</p>}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Unit */}
|
||||
{selectedActivity && (
|
||||
<div className="col-2 col-md-2">
|
||||
{" "}
|
||||
<label className="form-label" htmlFor="unit">
|
||||
Unit
|
||||
</label>
|
||||
<div className="input-group">
|
||||
<input
|
||||
type="text"
|
||||
id="unit"
|
||||
disabled
|
||||
name="unit"
|
||||
className="form-control form-control-sm me-2"
|
||||
placeholder="Unit"
|
||||
onChange={handleChange}
|
||||
value={selectedActivity.unitOfMeasurement}
|
||||
/>
|
||||
</div>
|
||||
<input
|
||||
type="text"
|
||||
disabled
|
||||
id="unit"
|
||||
name="unit"
|
||||
className="form-control form-control-sm me-2"
|
||||
value={selectedActivity?.unitOfMeasurement || ""}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="col-12 text-center">
|
||||
{selectedActivity && (
|
||||
<button type="submit" className="btn btn-primary me-3">
|
||||
{formData.id != "0" && formData.id != ""
|
||||
? "Edit Task"
|
||||
: "Add Task"}
|
||||
</button>
|
||||
)}
|
||||
<button type="submit" className="btn btn-sm btn-primary me-3">
|
||||
{formData.id !== "0" && formData.id !== "" ? "Edit Task" : "Add Task"}
|
||||
</button>
|
||||
<button
|
||||
type="reset"
|
||||
className="btn btn-label-secondary"
|
||||
data-bs-dismiss="modal"
|
||||
aria-label="Close"
|
||||
type="button"
|
||||
className="btn btn-sm btn-label-secondary"
|
||||
onClick={onClose}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
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() } : {}),
|
||||
name: z.string().min( 1, {message: "Project Name is required"} ),
|
||||
contactPerson: z.string().min( 1, {message: "Contact Person Name is required"} ),
|
||||
projectAddress: z.string().min( 1, {message: "Address is required"} ).max(150, 'Address must not exceed 150 characters'),
|
||||
projectAddress: z.string().min( 1, {message: "Address is required"} ).max(500, 'Address must not exceed 150 characters'),
|
||||
startDate: z.string().min( 1, {message: "Start Date is required"} ).default(currentDate),
|
||||
endDate: z.string().min( 1, {message: "End Date is required"} ).default(currentDate),
|
||||
projectStatusId: z
|
||||
@ -41,7 +41,14 @@ const ManageProjectInfo = ( {project,handleSubmitForm, onClose} ) =>
|
||||
return num;
|
||||
}),
|
||||
|
||||
} )
|
||||
} ) .refine((data) => {
|
||||
const start = new Date(data.startDate);
|
||||
const end = new Date(data.endDate);
|
||||
return end > start;
|
||||
}, {
|
||||
path: ['endDate'], // attaches the error to the endDate field
|
||||
message: 'End Date must be greater than Start Date',
|
||||
});
|
||||
|
||||
|
||||
const {register, control, handleSubmit, formState: {errors}, reset, getValues} = useForm( {
|
||||
@ -82,13 +89,15 @@ const ManageProjectInfo = ( {project,handleSubmitForm, onClose} ) =>
|
||||
handleSubmitForm( updatedProject )
|
||||
};
|
||||
|
||||
|
||||
useEffect( () =>
|
||||
{
|
||||
return ()=>setLoading(false)
|
||||
},[])
|
||||
|
||||
|
||||
return (
|
||||
<div className="modal-dialog modal-lg modal-simple edit-project-modal" role="document">
|
||||
<div className="modal-dialog modal-lg modal-simple mx-sm-auto mx-1 edit-project-modal" role="document">
|
||||
<div className="modal-content">
|
||||
<div className="modal-body">
|
||||
<div className="modal-body p-sm-4 p-0">
|
||||
<button
|
||||
type="button"
|
||||
className="btn-close"
|
||||
@ -181,10 +190,10 @@ const ManageProjectInfo = ( {project,handleSubmitForm, onClose} ) =>
|
||||
<option value="1">Active</option>
|
||||
<option value="2">On Hold</option>
|
||||
|
||||
<option value="3">Suspended</option>
|
||||
<option value="4">Inactive</option>
|
||||
{/* <option value="3">Suspended</option> */}
|
||||
<option value="3">Inactive</option>
|
||||
|
||||
<option value="5">Completed</option>
|
||||
<option value="4">Completed</option>
|
||||
</select>
|
||||
{errors.projectStatusId && <div className="danger-text text-start" style={{fontSize:"12px"}}>{errors.projectStatusId.message}</div>}
|
||||
|
||||
|
||||
@ -5,12 +5,14 @@ import ProjectRepository from "../../repositories/ProjectRepository";
|
||||
import { cacheData,getCachedData } from "../../slices/apiDataManager";
|
||||
import {hasUserPermission} from "../../utils/authUtils";
|
||||
import moment from "moment";
|
||||
import {useHasUserPermission} from "../../hooks/useHasUserPermission";
|
||||
import {MANAGE_PROJECT} from "../../utils/constants";
|
||||
|
||||
|
||||
const ProjectBanner = ( {project_data} ) =>
|
||||
{
|
||||
const [showModal, setShowModal] = useState(false);
|
||||
|
||||
const manageProject = useHasUserPermission(MANAGE_PROJECT)
|
||||
const [ CurrentProject, setCurrentProject ] = useState( project_data )
|
||||
if (project_data == null) {
|
||||
return <span>incomplete project information</span>;
|
||||
@ -74,59 +76,33 @@ const ProjectBanner = ( {project_data} ) =>
|
||||
onClose={handleClose}
|
||||
></ManageProjectInfo>
|
||||
</div>
|
||||
{/* -------------------- */}
|
||||
{/* Project Banner */}
|
||||
<div className="col-12">
|
||||
<div className="card mb-6 pb-0">
|
||||
<div className="user-profile-header d-flex flex-column flex-lg-row text-sm-start text-center mb-2 ">
|
||||
<div className="flex-shrink-0 mt-1 mx-sm-0 px-2 mx-auto">
|
||||
|
||||
<div className="d-flex align-items-center justify-content-between p-4 flex-wrap">
|
||||
{/* Left: Icon + Name */}
|
||||
<div className="d-flex align-items-center gap-3">
|
||||
<img
|
||||
src="../../assets/icons/civil-engineering.svg"
|
||||
alt="user image"
|
||||
className="d-block h-auto ms-0 ms-sm-6 rounded-3 user-profile-img project-banner-icon"
|
||||
></img>
|
||||
className="rounded-3"
|
||||
style={{ width: "60px", height: "60px" }}
|
||||
/>
|
||||
<h4 className="mb-0">
|
||||
{CurrentProject.name ? CurrentProject.name : "N/A"}
|
||||
</h4>
|
||||
</div>
|
||||
<div className="flex-grow-1 mt-1 mt-lg-1">
|
||||
<div className="d-flex align-items-md-end align-items-sm-start align-items-center justify-content-md-between justify-content-start mx-5 flex-md-row flex-column gap-4">
|
||||
<div className="user-profile-info">
|
||||
<h4 className="mb-2 mt-lg-1">
|
||||
{CurrentProject.name ? CurrentProject.name : "N/A"}
|
||||
</h4>
|
||||
<h6 className="mb-1 mt-lg-1">
|
||||
Address:{" "}
|
||||
{CurrentProject.projectAddress ? CurrentProject.projectAddress : "N/A"}
|
||||
</h6>
|
||||
<h6 className="mb-1 mt-lg-1">
|
||||
Contact:{" "}
|
||||
{CurrentProject.contactPerson ? CurrentProject.contactPerson : "N/A"}
|
||||
</h6>
|
||||
<h6 className="mb-1 mt-lg-1">
|
||||
<span>
|
||||
{" "}
|
||||
Start Date:{" "}
|
||||
{CurrentProject.startDate
|
||||
? moment(CurrentProject.startDate).format("DD-MMM-YYYY")
|
||||
: "N/A"}
|
||||
</span>
|
||||
<span className="ms-5">
|
||||
End Date:{" "}
|
||||
{CurrentProject.endDate
|
||||
? moment(CurrentProject.endDate).format("DD-MMM-YYYY")
|
||||
: "N/A"}
|
||||
</span>
|
||||
</h6>
|
||||
</div>
|
||||
<button
|
||||
{manageProject && (
|
||||
<button
|
||||
type="button"
|
||||
className={`btn btn-sm btn-primary ${hasUserPermission("53176ebf-c75d-42e5-839f-4508ffac3def") ? "":"d-none"}`}
|
||||
className={`btn btn-sm btn-primary ${!manageProject && 'd-none'}`}
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#edit-project-modal"
|
||||
onClick={handleShow}
|
||||
>
|
||||
Modify
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
Modify
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -1,271 +1,285 @@
|
||||
import React,{useState,useEffect} from "react";
|
||||
import React, { useState } from "react";
|
||||
import moment from "moment";
|
||||
import { getDateDifferenceInDays } from "../../utils/dateUtils";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import {useProjectDetails} from "../../hooks/useProjects";
|
||||
import { useProjectDetails } from "../../hooks/useProjects";
|
||||
import ManageProjectInfo from "./ManageProjectInfo";
|
||||
import ProjectRepository from "../../repositories/ProjectRepository";
|
||||
import {cacheData, getCachedData} from "../../slices/apiDataManager";
|
||||
import { cacheData, getCachedData } from "../../slices/apiDataManager";
|
||||
import showToast from "../../services/toastService";
|
||||
import {useHasUserPermission} from "../../hooks/useHasUserPermission";
|
||||
import {MANAGE_PROJECT} from "../../utils/constants";
|
||||
import { useHasUserPermission } from "../../hooks/useHasUserPermission";
|
||||
import { MANAGE_PROJECT } from "../../utils/constants";
|
||||
|
||||
|
||||
|
||||
const ProjectCard = ( {projectData} ) =>
|
||||
{
|
||||
|
||||
const[projectInfo,setProjectInfo] = useState(projectData)
|
||||
const navigate = useNavigate()
|
||||
const {projects_Details, loading} = useProjectDetails( projectData.id )
|
||||
const [ showModal, setShowModal ] = useState( false );
|
||||
const ManageProject = useHasUserPermission(MANAGE_PROJECT)
|
||||
|
||||
const handleShow = () => setShowModal(true);
|
||||
const handleClose = () => setShowModal( false );
|
||||
|
||||
const getProjectStatusName = (statusId) => {
|
||||
switch (statusId) {
|
||||
case 1:
|
||||
return "Active";
|
||||
case 2:
|
||||
return "On Hold";
|
||||
case 3:
|
||||
return "Suspended";
|
||||
case 4:
|
||||
return "Inactive";
|
||||
case 5:
|
||||
return "Completed";
|
||||
}
|
||||
};
|
||||
const ProjectCard = ({ projectData }) => {
|
||||
const [projectInfo, setProjectInfo] = useState(projectData);
|
||||
const [projectDetails, setProjectDetails] = useState(null);
|
||||
const [showModal, setShowModal] = useState(false);
|
||||
const navigate = useNavigate();
|
||||
const ManageProject = useHasUserPermission(MANAGE_PROJECT);
|
||||
|
||||
const getProjectStatusColor = (statusId) => {
|
||||
switch (statusId) {
|
||||
case 1:
|
||||
return "bg-label-success";
|
||||
case 2:
|
||||
return "bg-label-warning";
|
||||
case 3:
|
||||
return "bg-label-info";
|
||||
case 4:
|
||||
return "bg-label-secondary";
|
||||
case 5:
|
||||
return "bg-label-dark";
|
||||
}
|
||||
};
|
||||
const handleShow = async () => {
|
||||
try {
|
||||
const response = await ProjectRepository.getProjectByprojectId(projectInfo.id);
|
||||
setProjectDetails(response.data);
|
||||
setShowModal(true);
|
||||
} catch (error) {
|
||||
showToast("Failed to load project details", "error");
|
||||
}
|
||||
};
|
||||
|
||||
const handleViewProject = (e) => {
|
||||
navigate(`/projects/${projectData.id}`)
|
||||
};
|
||||
const getProgress = (planned, completed) => {
|
||||
return (completed * 100) / planned + "%";
|
||||
};
|
||||
const getProgressInNumber = (planned, completed) => {
|
||||
return (completed * 100) / planned;
|
||||
};
|
||||
|
||||
const handleClose = () => setShowModal(false);
|
||||
|
||||
|
||||
const handleFormSubmit = ( updatedProject ) =>
|
||||
{
|
||||
if ( projectInfo?.id )
|
||||
{
|
||||
ProjectRepository.updateProject(projectInfo.id,updatedProject).then( ( response ) =>
|
||||
{
|
||||
const updatedProjectData = {
|
||||
...projectInfo,
|
||||
...response.data,
|
||||
building:projects_Details.building,
|
||||
};
|
||||
setProjectInfo( updatedProject )
|
||||
if ( getCachedData( `projectinfo-${ projectInfo.id }` ) )
|
||||
{
|
||||
cacheData( `projectinfo-${ projectInfo.id }`, updatedProjectData );
|
||||
|
||||
}
|
||||
const projects_list = getCachedData( "projectslist" );
|
||||
if ( projects_list )
|
||||
{
|
||||
const updatedProjectsList = projects_list.map(project =>
|
||||
project.id == projectInfo.id ? {
|
||||
...project,
|
||||
...response.data,
|
||||
tenant:project.tenant
|
||||
} : project
|
||||
);
|
||||
|
||||
cacheData("projectslist",updatedProjectsList)
|
||||
}
|
||||
|
||||
showToast( "Project updated successfully.", "success" );
|
||||
|
||||
setShowModal(false)
|
||||
})
|
||||
.catch((error) => {
|
||||
showToast( error.message, "error" );
|
||||
|
||||
});
|
||||
}
|
||||
const getProjectStatusName = (statusId) => {
|
||||
switch (statusId) {
|
||||
case 1:
|
||||
return "Active";
|
||||
case 2:
|
||||
return "On Hold";
|
||||
// case 3:
|
||||
// return "Suspended";
|
||||
case 3:
|
||||
return "Inactive";
|
||||
case 4:
|
||||
return "Completed";
|
||||
}
|
||||
};
|
||||
|
||||
const getProjectStatusColor = (statusId) => {
|
||||
switch (statusId) {
|
||||
case 1:
|
||||
return "bg-label-success";
|
||||
case 2:
|
||||
return "bg-label-warning";
|
||||
case 3:
|
||||
return "bg-label-info";
|
||||
case 4:
|
||||
return "bg-label-secondary";
|
||||
case 5:
|
||||
return "bg-label-dark";
|
||||
}
|
||||
};
|
||||
|
||||
const handleViewProject = () => {
|
||||
navigate(`/projects/${projectData.id}`);
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
<div
|
||||
className={`modal fade ${showModal ? 'show' : ''}`}
|
||||
tabIndex="-1"
|
||||
role="dialog"
|
||||
style={{ display: showModal ? 'block' : 'none' }}
|
||||
aria-hidden={!showModal}
|
||||
>
|
||||
<ManageProjectInfo
|
||||
project={projects_Details}
|
||||
handleSubmitForm={handleFormSubmit}
|
||||
onClose={handleClose}
|
||||
></ManageProjectInfo>
|
||||
</div>
|
||||
const handleFormSubmit = (updatedProject) => {
|
||||
if (projectInfo?.id) {
|
||||
ProjectRepository.updateProject(projectInfo.id, updatedProject)
|
||||
.then((response) => {
|
||||
const updatedProjectData = {
|
||||
...projectInfo,
|
||||
...response.data,
|
||||
building: projectDetails?.building,
|
||||
};
|
||||
|
||||
<div className="col-md-6 col-lg-4 col-xl-4 order-0 mb-4">
|
||||
<div className="card cursor-pointer">
|
||||
<div className="card-header pb-4">
|
||||
<div className="d-flex align-items-start">
|
||||
<div className="d-flex align-items-center">
|
||||
<div className="avatar me-4">
|
||||
<i
|
||||
className="rounded-circle bx bx-building-house"
|
||||
style={{ fontSize: "xx-large" }}
|
||||
></i>
|
||||
</div>
|
||||
<div className="me-2">
|
||||
<h5 className="mb-0">
|
||||
<a
|
||||
className="stretched-link text-heading"
|
||||
|
||||
onClick={handleViewProject}
|
||||
>
|
||||
{projectInfo.name}
|
||||
</a>
|
||||
</h5>
|
||||
<div className="client-info text-body">
|
||||
<span className="fw-medium">Client: </span>
|
||||
<span>{projectInfo.contactPerson}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className={`ms-auto ${!ManageProject && 'd-none'}`}>
|
||||
<div className="dropdown z-2">
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-icon btn-text-secondary rounded-pill dropdown-toggle hide-arrow p-0"
|
||||
data-bs-toggle="dropdown"
|
||||
aria-expanded="false"
|
||||
setProjectInfo(updatedProject);
|
||||
|
||||
if (getCachedData(`projectinfo-${projectInfo.id}`)) {
|
||||
cacheData(`projectinfo-${projectInfo.id}`, updatedProjectData);
|
||||
}
|
||||
|
||||
const projects_list = getCachedData("projectslist");
|
||||
if (projects_list) {
|
||||
const updatedProjectsList = projects_list.map((project) =>
|
||||
project.id === projectInfo.id
|
||||
? { ...project, ...response.data, tenant: project.tenant }
|
||||
: project
|
||||
);
|
||||
cacheData("projectslist", updatedProjectsList);
|
||||
}
|
||||
|
||||
showToast("Project updated successfully.", "success");
|
||||
setShowModal(false);
|
||||
})
|
||||
.catch((error) => {
|
||||
showToast(error.message, "error");
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{showModal && projectDetails && (
|
||||
<div
|
||||
className="modal fade show"
|
||||
tabIndex="-1"
|
||||
role="dialog"
|
||||
style={{ display: "block" }}
|
||||
aria-hidden="false"
|
||||
>
|
||||
<i className="bx bx-dots-vertical-rounded bx-sm text-muted"></i>
|
||||
</button>
|
||||
<ul className="dropdown-menu dropdown-menu-end">
|
||||
<li>
|
||||
<a
|
||||
aria-label="click to View details"
|
||||
className="dropdown-item"
|
||||
onClick={handleViewProject}
|
||||
>
|
||||
<i className="bx bx-detail me-2"></i>
|
||||
<span className="align-left">View details</span>
|
||||
</a>
|
||||
</li>
|
||||
<li data-bs-toggle="modal"
|
||||
data-bs-target="#edit-project-modal"
|
||||
onClick={handleShow}>
|
||||
<a
|
||||
className="dropdown-item"
|
||||
>
|
||||
<i className="bx bx-pencil me-2"></i>
|
||||
<span className="align-left"
|
||||
>Modify</span>
|
||||
</a>
|
||||
</li>
|
||||
<ManageProjectInfo
|
||||
project={projectDetails}
|
||||
handleSubmitForm={handleFormSubmit}
|
||||
onClose={handleClose}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<li>
|
||||
<a className="dropdown-item" >
|
||||
<i className="bx bx-task me-2"></i>
|
||||
<span className="align-left">Activities</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="card-body pb-1">
|
||||
<div className="d-flex align-items-center flex-wrap">
|
||||
<div className="text-start mb-4">
|
||||
<p className="mb-1">
|
||||
<span className="text-heading fw-medium">Start Date: </span>
|
||||
{projectInfo.startDate
|
||||
? moment(projectInfo.startDate).format("DD-MMM-YYYY")
|
||||
: "NA"}
|
||||
</p>
|
||||
<p className="mb-1">
|
||||
<span className="text-heading fw-medium">Deadline: </span>
|
||||
<div className="col-md-6 col-lg-4 col-xl-4 order-0 mb-4">
|
||||
<div className="card cursor-pointer">
|
||||
<div className="card-header pb-4">
|
||||
<div className="d-flex align-items-start">
|
||||
<div className="d-flex align-items-center">
|
||||
<div className="avatar me-4">
|
||||
<i
|
||||
className="rounded-circle bx bx-building-house"
|
||||
style={{ fontSize: "xx-large" }}
|
||||
></i>
|
||||
</div>
|
||||
<div className="me-2">
|
||||
<h5 className="mb-0">
|
||||
<a
|
||||
className="stretched-link text-heading"
|
||||
onClick={handleViewProject}
|
||||
>
|
||||
{projectInfo.name}
|
||||
</a>
|
||||
</h5>
|
||||
<div className="client-info text-body">
|
||||
<span className="fw-medium">Client: </span>
|
||||
<span>{projectInfo.contactPerson}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className={`ms-auto ${!ManageProject && "d-none"}`}>
|
||||
<div className="dropdown z-2">
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-icon btn-text-secondary rounded-pill dropdown-toggle hide-arrow p-0"
|
||||
data-bs-toggle="dropdown"
|
||||
aria-expanded="false"
|
||||
>
|
||||
<i className="bx bx-dots-vertical-rounded bx-sm text-muted" data-bs-toggle="tooltip" data-bs-offset="0,8" data-bs-placement="top" data-bs-custom-class="tooltip-dark" title="More Action"></i>
|
||||
</button>
|
||||
<ul className="dropdown-menu dropdown-menu-end">
|
||||
<li>
|
||||
<a
|
||||
aria-label="click to View details"
|
||||
className="dropdown-item"
|
||||
|
||||
onClick={handleViewProject}
|
||||
>
|
||||
<i className="bx bx-detail me-2"></i>
|
||||
<span className="align-left">View details</span>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
{projectInfo.endDate
|
||||
? moment(projectInfo.endDate).format("DD-MMM-YYYY")
|
||||
: "NA"}
|
||||
</p>
|
||||
<p className="mb-0">{projectInfo.projectAddress}</p>
|
||||
<li onClick={handleShow}>
|
||||
<a className="dropdown-item">
|
||||
<i className="bx bx-pencil me-2"></i>
|
||||
<span className="align-left">Modify</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a className="dropdown-item">
|
||||
<i className="bx bx-task me-2"></i>
|
||||
<span className="align-left">Activities</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="card-body pb-1">
|
||||
<div className="d-flex align-items-center flex-wrap">
|
||||
<div className="text-start mb-4">
|
||||
<p className="mb-1">
|
||||
<span className="text-heading fw-medium">Start Date: </span>
|
||||
{projectInfo.startDate
|
||||
? moment(projectInfo.startDate).format("DD-MMM-YYYY")
|
||||
: "NA"}
|
||||
</p>
|
||||
<p className="mb-1">
|
||||
<span className="text-heading fw-medium">Deadline: </span>
|
||||
|
||||
{projectInfo.endDate
|
||||
? moment(projectInfo.endDate).format("DD-MMM-YYYY")
|
||||
: "NA"}
|
||||
</p>
|
||||
<p className="mb-0">{projectInfo.projectAddress}</p>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div className="card-body border-top">
|
||||
<div className="d-flex align-items-center mb-4">
|
||||
<p className="mb-0">
|
||||
<span
|
||||
className={
|
||||
`badge rounded-pill ` +
|
||||
getProjectStatusColor(projectInfo.projectStatusId)
|
||||
}
|
||||
>
|
||||
{getProjectStatusName(projectInfo.projectStatusId)}
|
||||
</span>
|
||||
</p>{" "}
|
||||
{getDateDifferenceInDays(projectInfo.endDate, Date()) >= 0 &&
|
||||
( <span className="badge bg-label-success ms-auto">
|
||||
{projectInfo.endDate &&
|
||||
getDateDifferenceInDays(projectInfo.endDate, Date())}{" "}
|
||||
Days left
|
||||
</span>) }
|
||||
{getDateDifferenceInDays(projectInfo.endDate, Date()) < 0 &&
|
||||
( <span className="badge bg-label-danger ms-auto">
|
||||
{projectInfo.endDate &&
|
||||
getDateDifferenceInDays(projectInfo.endDate, Date())}{" "}
|
||||
Days overdue
|
||||
</span>)}
|
||||
|
||||
</div>
|
||||
<div className="d-flex justify-content-between align-items-center mb-2">
|
||||
<small className="text-body">Task: {projectInfo.completedWork} / {projectInfo.plannedWork}</small>
|
||||
<small className="text-body">{Math.floor(getProgressInNumber(projectInfo.plannedWork, projectInfo.completedWork)) || 0} % Completed</small>
|
||||
</div>
|
||||
<div className="progress mb-4 rounded" style={{ height: "8px" }}>
|
||||
<div
|
||||
className="progress-bar rounded"
|
||||
role="progressbar"
|
||||
style={{ width: getProgress(projectInfo.plannedWork, projectInfo.completedWork) }}
|
||||
aria-valuenow={projectInfo.completedWork}
|
||||
aria-valuemin="0"
|
||||
aria-valuemax={projectInfo.plannedWork}
|
||||
></div>
|
||||
</div>
|
||||
<div className="d-flex align-items-center justify-content-between">
|
||||
{/* <div className="d-flex align-items-center ">
|
||||
</div> */}
|
||||
<div >
|
||||
<a
|
||||
|
||||
className="text-muted d-flex " alt="Active team size"
|
||||
>
|
||||
<i className="bx bx-group bx-sm me-1_5"></i>{projectInfo?.teamSize} Members
|
||||
</a>
|
||||
|
||||
</div>
|
||||
<div >
|
||||
<a
|
||||
className="text-muted d-flex align-items-center"
|
||||
>
|
||||
<i className="bx bx-chat me-1 "></i> <span className="text-decoration-line-through">15</span>
|
||||
</a>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div className="card-body border-top">
|
||||
<div className="d-flex align-items-center mb-4">
|
||||
<p className="mb-0">
|
||||
<span
|
||||
className={
|
||||
`badge rounded-pill ` +
|
||||
getProjectStatusColor(projectInfo.projectStatusId)
|
||||
}
|
||||
>
|
||||
{getProjectStatusName(projectInfo.projectStatusId)}
|
||||
</span>
|
||||
</p>{" "}
|
||||
<span className="badge bg-label-success ms-auto">
|
||||
{projectInfo.startDate &&
|
||||
projectInfo.endDate &&
|
||||
getDateDifferenceInDays(
|
||||
projectInfo.startDate,
|
||||
projectInfo.endDate
|
||||
)}{" "}
|
||||
Days left
|
||||
</span>
|
||||
</div>
|
||||
<div className="d-flex justify-content-between align-items-center mb-2">
|
||||
<small className="text-body">Task: 290/344</small>
|
||||
<small className="text-body">95% Completed</small>
|
||||
</div>
|
||||
<div className="progress mb-4 rounded" style={{ height: "8px" }}>
|
||||
<div
|
||||
className="progress-bar rounded"
|
||||
role="progressbar"
|
||||
style={{ width: "95%" }}
|
||||
aria-valuenow="95"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100"
|
||||
></div>
|
||||
</div>
|
||||
<div className="d-flex align-items-center">
|
||||
<div className="d-flex align-items-center">
|
||||
</div>
|
||||
<div className="ms-auto">
|
||||
<a
|
||||
|
||||
className="text-muted d-flex align-items-center"
|
||||
>
|
||||
<i className="bx bx-chat me-1"></i> 15
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ProjectCard;
|
||||
|
||||
@ -1,58 +1,79 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import "./ProjectInfra.css";
|
||||
import BuildingModel from "./BuildingModel";
|
||||
import FloorModel from "./FloorModel";
|
||||
import BuildingModel from "./Infrastructure/BuildingModel";
|
||||
import FloorModel from "./Infrastructure/FloorModel";
|
||||
import showToast from "../../services/toastService";
|
||||
import WorkAreaModel from "./WorkAreaModel";
|
||||
import TaskModel from "./TaskModel";
|
||||
import ProjectRepository from "../../repositories/ProjectRepository";
|
||||
import WorkAreaModel from "./Infrastructure/WorkAreaModel";
|
||||
import TaskModel from "./Infrastructure/TaskModel";
|
||||
import ProjectRepository, {
|
||||
TasksRepository,
|
||||
} from "../../repositories/ProjectRepository";
|
||||
import ProjectModal from "./ProjectModal";
|
||||
import {useHasUserPermission} from "../../hooks/useHasUserPermission";
|
||||
import {MANAGE_PROJECT_INFRA} from "../../utils/constants";
|
||||
// import AssignRoleModel from "./AssignRoleModel";
|
||||
import { useHasUserPermission } from "../../hooks/useHasUserPermission";
|
||||
import { MANAGE_PROJECT_INFRA } from "../../utils/constants";
|
||||
import InfraTable from "./Infrastructure/InfraTable";
|
||||
import { cacheData, getCachedData } from "../../slices/apiDataManager";
|
||||
import { useProjectDetails } from "../../hooks/useProjects";
|
||||
|
||||
const ProjectInfra = ({ data, activityMaster, onDataChange,eachSiteEngineer }) => {
|
||||
|
||||
const ProjectInfra = ({
|
||||
data,
|
||||
onDataChange,
|
||||
eachSiteEngineer,
|
||||
}) => {
|
||||
const [expandedBuildings, setExpandedBuildings] = useState([]);
|
||||
const [project, setProject] = useState(data);
|
||||
const[modalConfig,setModalConfig] = useState({type:null,data:null});
|
||||
const [ isModalOpen, setIsModalOpen ] = useState( false )
|
||||
const ManageInfra = useHasUserPermission(MANAGE_PROJECT_INFRA)
|
||||
const { projects_Details, loading } = useProjectDetails(data.id);
|
||||
const [project, setProject] = useState(projects_Details);
|
||||
const [modalConfig, setModalConfig] = useState({ type: null, data: null });
|
||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||
const ManageInfra = useHasUserPermission(MANAGE_PROJECT_INFRA);
|
||||
|
||||
const [buildings, setBuildings] = useState(data.buildings);
|
||||
const [isBuildingModalOpen, setIsBuildingModalOpen] = useState(false);
|
||||
const [isFloorModalOpen, setIsFloorModalOpen] = useState(false);
|
||||
const [isWorkAreaModelOpen, setIsWorkAreaModalOpen] = useState(false);
|
||||
const [isTaskModelOpen, setIsTaskModalOpen] = useState(false);
|
||||
const [isAssignRoleModal,setIsAssingRoleModal] = useState(false)
|
||||
const [isAssignRoleModal, setIsAssingRoleModal] = useState(false);
|
||||
const [isCreateModalOpen, setIsCreateModalOpen] = useState(false);
|
||||
const [clearFormTrigger, setClearFormTrigger] = useState(false);
|
||||
const [CurrentBuilding,setCurrentBuilding] = useState("")
|
||||
const [CurrentBuilding, setCurrentBuilding] = useState("");
|
||||
const [showModal, setShowModal] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
setProject(data);
|
||||
setBuildings(data.buildings);
|
||||
}, [data]);
|
||||
|
||||
|
||||
setProject(projects_Details);
|
||||
}, [data, projects_Details]);
|
||||
|
||||
const openFloorModel = (projectData) => {
|
||||
setIsFloorModalOpen(true);
|
||||
};
|
||||
const closeFloorModel = () => {
|
||||
setIsFloorModalOpen(false);
|
||||
|
||||
};
|
||||
|
||||
const openAssignModel=(assignData)=>{
|
||||
|
||||
setCurrentBuilding(assignData)
|
||||
setIsAssingRoleModal(true)
|
||||
|
||||
}
|
||||
|
||||
const openAssignModel = (assignData) => {
|
||||
setCurrentBuilding(assignData);
|
||||
setIsAssingRoleModal(true);
|
||||
};
|
||||
const openBuildingModel = (projectData) => {
|
||||
setIsBuildingModalOpen(true);
|
||||
};
|
||||
|
||||
const closeBuildingModel = () => {
|
||||
setIsBuildingModalOpen(false);
|
||||
};
|
||||
|
||||
const handleBuildingModelFormSubmit = (buildingmodel) => {
|
||||
if (buildingmodel.id == "" || buildingmodel.id == 0)
|
||||
delete buildingmodel.id;
|
||||
let data = [
|
||||
{
|
||||
building: buildingmodel,
|
||||
floor: null,
|
||||
workArea: null,
|
||||
},
|
||||
];
|
||||
submitData(data);
|
||||
};
|
||||
const handleFloorModelFormSubmit = (updatedFloor) => {
|
||||
if (updatedFloor.id == "") delete updatedFloor.id;
|
||||
|
||||
submitData([
|
||||
{
|
||||
building: null,
|
||||
@ -62,53 +83,15 @@ const ProjectInfra = ({ data, activityMaster, onDataChange,eachSiteEngineer }) =
|
||||
]);
|
||||
};
|
||||
|
||||
const submitData = (infraObject) => {
|
||||
ProjectRepository.manageProjectInfra(infraObject)
|
||||
.then((response) => {
|
||||
fetchData();
|
||||
onDataChange("building-change");
|
||||
showToast("Details updated successfully.", "success");
|
||||
setClearFormTrigger(true); // Set trigger to true
|
||||
})
|
||||
.catch((error) => {
|
||||
showToast(error.message, "error");
|
||||
});
|
||||
};
|
||||
|
||||
const openBuildingModel = (projectData) => {
|
||||
setIsBuildingModalOpen(true);
|
||||
};
|
||||
|
||||
const closeBuildingModel = () => {
|
||||
setIsBuildingModalOpen(false);
|
||||
};
|
||||
const handleBuildingModelFormSubmit = (buildingmodel) => {
|
||||
if (buildingmodel.id == "" || buildingmodel.id == 0)
|
||||
delete buildingmodel.id;
|
||||
|
||||
let data = [
|
||||
{
|
||||
building: buildingmodel,
|
||||
floor: null,
|
||||
workArea: null,
|
||||
},
|
||||
];
|
||||
|
||||
submitData(data);
|
||||
};
|
||||
|
||||
const openWorkAreaModel = (projectData) => {
|
||||
setIsWorkAreaModalOpen(true);
|
||||
};
|
||||
|
||||
const closeWorkAreaModel = () => {
|
||||
setIsWorkAreaModalOpen(false);
|
||||
// const modalBackdrop = document.querySelector(".modal-backdrop");
|
||||
// if (modalBackdrop) modalBackdrop.remove();
|
||||
};
|
||||
const handleWorkAreaModelFormSubmit = (updatedModel) => {
|
||||
if (updatedModel.id == "") delete updatedModel.id;
|
||||
|
||||
submitData([
|
||||
{
|
||||
building: null,
|
||||
@ -124,428 +107,349 @@ const ProjectInfra = ({ data, activityMaster, onDataChange,eachSiteEngineer }) =
|
||||
|
||||
const closeTaskModel = () => {
|
||||
setIsTaskModalOpen(false);
|
||||
// const modalBackdrop = document.querySelector(".modal-backdrop");
|
||||
// if (modalBackdrop) modalBackdrop.remove();
|
||||
};
|
||||
|
||||
const handleTaskModelFormSubmit = (updatedModel) => {
|
||||
// debugger
|
||||
if (updatedModel.id == "") updatedModel.id = 0;
|
||||
const updatedProject = { ...project };
|
||||
|
||||
//console.log("Form submitted:", updatedModel); // Replace this with an API call or state update
|
||||
ProjectRepository.manageProjectTasks([updatedModel])
|
||||
.then((response) => {
|
||||
onDataChange("task-change");
|
||||
showToast("Details updated successfully.", "success");
|
||||
setClearFormTrigger(true); // Set trigger to true
|
||||
// setClearFormTrigger( true );
|
||||
|
||||
if (response?.data[0]) {
|
||||
const { workItemId, workItem } = response.data[0];
|
||||
|
||||
const updatedBuildings = updatedProject.buildings.map((building) =>
|
||||
building.id == updatedModel.buildingID
|
||||
? {
|
||||
...building,
|
||||
floors: building.floors.map((floor) =>
|
||||
floor.id == updatedModel.floorId
|
||||
? {
|
||||
...floor,
|
||||
workAreas: floor.workAreas.map((workArea) =>
|
||||
workArea.id === workItem?.workAreaId
|
||||
? {
|
||||
...workArea,
|
||||
workItems: workArea.workItems.some(
|
||||
(existingItem) =>
|
||||
existingItem.workItemId ===
|
||||
workItem.workItemId
|
||||
)
|
||||
? workArea.workItems // If the workItemId already exists, keep the current workItems
|
||||
: [...workArea.workItems, workItem],
|
||||
}
|
||||
: workArea
|
||||
),
|
||||
}
|
||||
: floor
|
||||
),
|
||||
}
|
||||
: building
|
||||
);
|
||||
|
||||
updatedProject.buildings = updatedBuildings;
|
||||
|
||||
setProject(updatedProject);
|
||||
cacheData("projectInfo", {
|
||||
projectId: updatedProject.id,
|
||||
data: updatedProject,
|
||||
});
|
||||
console.log(project);
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
showToast(error.message, "error");
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
const submitData = async (infraObject) => {
|
||||
try
|
||||
{
|
||||
debugger
|
||||
|
||||
let response = await ProjectRepository.manageProjectInfra(infraObject);
|
||||
const entity = response.data;
|
||||
|
||||
const updatedProject = { ...project };
|
||||
// Handle the building data
|
||||
if (entity.building) {
|
||||
const { id, name, description } = entity.building;
|
||||
const updatedBuildings = updatedProject?.buildings?.map((building) =>
|
||||
building.id === id ? { ...building, name, description } : building
|
||||
);
|
||||
|
||||
// Add building if it doesn't exist
|
||||
if (!updatedProject.buildings.some((building) => building.id === id)) {
|
||||
updatedBuildings.push({
|
||||
id: id,
|
||||
name,
|
||||
description,
|
||||
floors: [],
|
||||
});
|
||||
}
|
||||
|
||||
updatedProject.buildings = updatedBuildings;
|
||||
|
||||
// Update the cache for buildings
|
||||
cacheData("projectInfo", {
|
||||
projectId: updatedProject.id,
|
||||
data: updatedProject,
|
||||
});
|
||||
setProject((prevProject) => ({
|
||||
...prevProject,
|
||||
buildings: updatedBuildings,
|
||||
} ) );
|
||||
closeBuildingModel()
|
||||
}
|
||||
// Handle the floor data
|
||||
else if (entity.floor) {
|
||||
const { buildingId, id, floorName } = entity.floor;
|
||||
const updatedBuildings = updatedProject?.buildings?.map((building) =>
|
||||
building.id == buildingId
|
||||
? {
|
||||
...building,
|
||||
floors: building.floors
|
||||
.map((floor) =>
|
||||
floor.id === id
|
||||
? {
|
||||
...floor,
|
||||
floorName, // Update the floor name only
|
||||
// Keep other properties as they are (including workArea)
|
||||
}
|
||||
: floor
|
||||
)
|
||||
// Add the new floor if it doesn't already exist
|
||||
.concat(
|
||||
!building.floors.some((floor) => floor.id === id)
|
||||
? [{ id: id, floorName, workAreas: [] }] // New floor added with workArea set to null
|
||||
: []
|
||||
),
|
||||
}
|
||||
: building
|
||||
);
|
||||
|
||||
updatedProject.buildings = updatedBuildings;
|
||||
|
||||
// Cache the updated project
|
||||
cacheData("projectInfo", {
|
||||
projectId: updatedProject.id,
|
||||
data: updatedProject,
|
||||
});
|
||||
setProject( updatedProject );
|
||||
closeFloorModel()
|
||||
}
|
||||
// Handle the work area data
|
||||
else if ( entity.workArea )
|
||||
{
|
||||
let buildingId = infraObject[0].workArea.buildingId;
|
||||
|
||||
const { floorId, areaName, id } = entity.workArea;
|
||||
// Check if the workArea exists, otherwise create a new one
|
||||
const updatedBuildings = updatedProject.buildings.map((building) =>
|
||||
building.id == buildingId
|
||||
? {
|
||||
...building,
|
||||
floors: building.floors.map((floor) =>
|
||||
floor.id == floorId
|
||||
? {
|
||||
...floor,
|
||||
workAreas: floor.workAreas.some(
|
||||
(workArea) => workArea.id === id
|
||||
)
|
||||
? floor.workAreas.map((workArea) =>
|
||||
workArea.id === id
|
||||
? { ...workArea, areaName }
|
||||
: workArea
|
||||
)
|
||||
: [
|
||||
...floor.workAreas,
|
||||
{ id, areaName, workItems: [] },
|
||||
],
|
||||
}
|
||||
: floor
|
||||
),
|
||||
}
|
||||
: building
|
||||
);
|
||||
|
||||
updatedProject.buildings = updatedBuildings;
|
||||
|
||||
// Update the cache for work areas
|
||||
cacheData("projectInfo", {
|
||||
projectId: updatedProject.id,
|
||||
data: updatedProject,
|
||||
});
|
||||
setProject( updatedProject );
|
||||
closeWorkAreaModel()
|
||||
}
|
||||
// Handle the task (workItem) data
|
||||
else {
|
||||
console.error("Unsupported data type for submitData", entity);
|
||||
}
|
||||
} catch (Err) {
|
||||
console.log(Err);
|
||||
showToast("Somthing wrong", "error");
|
||||
}
|
||||
handleClose();
|
||||
};
|
||||
|
||||
|
||||
const toggleBuilding = (id) => {
|
||||
setExpandedBuildings((prev) =>
|
||||
prev.includes(id) ? prev.filter((bid) => bid !== id) : [...prev, id]
|
||||
);
|
||||
};
|
||||
|
||||
const getContent = (building) => {
|
||||
let hasFloors =
|
||||
building.floors && building.floors.length > 0 ? true : false;
|
||||
|
||||
return (
|
||||
<>
|
||||
{(() => {
|
||||
if (hasFloors) {
|
||||
return building.floors.map((floor) => (
|
||||
<React.Fragment key={floor.id}>
|
||||
|
||||
{floor.workAreas.length > 0 ? (
|
||||
floor.workAreas.map((workArea) => (
|
||||
<React.Fragment key={workArea.id}>
|
||||
<tr>
|
||||
<td colSpan="4" className="text-start table-cell">
|
||||
<div className="row ps-2">
|
||||
<div className="col-6">
|
||||
{" "}
|
||||
<h6>
|
||||
<span>
|
||||
{" "}
|
||||
{floor.floorName} - {workArea.areaName} {" "}
|
||||
</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 handleModalData = (type, modaldata) => {
|
||||
setModalConfig({ type: type, data: modaldata });
|
||||
};
|
||||
|
||||
const getProgress = (planned, completed) => {
|
||||
return (completed * 100) / planned + "%";
|
||||
};
|
||||
|
||||
|
||||
// common modal
|
||||
|
||||
const handleModalData = (type,modaldata)=>{
|
||||
setModalConfig({type:type,data:modaldata})
|
||||
}
|
||||
const openModal = () => {
|
||||
setIsModalOpen(true);
|
||||
const modalElement = document.getElementById("building-model");
|
||||
const modal = new Modal(modalElement, {
|
||||
backdrop: false,
|
||||
keyboard: true,
|
||||
focus: true,
|
||||
});
|
||||
modal.show();
|
||||
};
|
||||
const closeModal = () => {
|
||||
setIsModalOpen(false);
|
||||
setModalConfig(null)
|
||||
|
||||
|
||||
const modalElement = document.getElementById('project-modal');
|
||||
if (modalElement) {
|
||||
modalElement.classList.remove('show');
|
||||
modalElement.style.display = 'none'; // Hide modal visually
|
||||
document.body.classList.remove('modal-open'); // Unlock body scroll
|
||||
|
||||
|
||||
const backdropElement = document.querySelector('.modal-backdrop');
|
||||
if (backdropElement) {
|
||||
backdropElement.classList.remove('modal-backdrop'); // Remove backdrop class
|
||||
backdropElement.style.display = 'none'; // Hide the backdrop element
|
||||
}
|
||||
}
|
||||
const modalBackdropElement = document.querySelector('.modal-backdrop');
|
||||
if (modalBackdropElement) {
|
||||
modalBackdropElement.remove();
|
||||
}
|
||||
document.body.style.overflow = 'auto';
|
||||
|
||||
};
|
||||
useEffect(() => {
|
||||
if (modalConfig !== null) {
|
||||
openModal();
|
||||
}
|
||||
|
||||
}, [modalConfig,isModalOpen]);
|
||||
setIsModalOpen(false);
|
||||
setModalConfig(null);
|
||||
|
||||
const modalElement = document.getElementById("building-model");
|
||||
if (modalElement) {
|
||||
modalElement.classList.remove("show"); // Remove modal visibility class
|
||||
modalElement.style.display = "none"; // Hide the modal element
|
||||
}
|
||||
|
||||
document.body.classList.remove("modal-open"); // Remove modal-open class from body
|
||||
|
||||
// Remove the modal backdrop
|
||||
const backdropElement = document.querySelector(".modal-backdrop");
|
||||
if (backdropElement) {
|
||||
backdropElement.classList.remove("modal-backdrop"); // Remove backdrop class
|
||||
backdropElement.style.display = "none"; // Hide the backdrop element
|
||||
}
|
||||
document.body.style.overflow = "auto";
|
||||
};
|
||||
|
||||
const handleShow = () => setShowModal(true);
|
||||
const handleClose = () => setShowModal(false);
|
||||
|
||||
return (
|
||||
<>
|
||||
{isBuildingModalOpen && (
|
||||
<div
|
||||
className={`modal fade `}
|
||||
id="building-model"
|
||||
tabIndex="-1"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<BuildingModel
|
||||
project={data}
|
||||
onClose={closeBuildingModel}
|
||||
onSubmit={handleBuildingModelFormSubmit}
|
||||
clearTrigger={clearFormTrigger}
|
||||
onClearComplete={() => setClearFormTrigger(false)}
|
||||
></BuildingModel>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div
|
||||
className={`modal fade ${showModal ? "show" : ""}`}
|
||||
tabIndex="-1"
|
||||
role="dialog"
|
||||
style={{ display: showModal ? "block" : "none" }}
|
||||
aria-hidden={!showModal}
|
||||
>
|
||||
<BuildingModel
|
||||
project={project}
|
||||
onClose={handleClose}
|
||||
onSubmit={handleBuildingModelFormSubmit}
|
||||
clearTrigger={clearFormTrigger}
|
||||
onClearComplete={() => setClearFormTrigger(false)}
|
||||
></BuildingModel>
|
||||
</div>
|
||||
{isFloorModalOpen && (
|
||||
<div
|
||||
className={`modal fade `}
|
||||
className="modal fade show"
|
||||
id="floor-model"
|
||||
tabIndex="-1"
|
||||
aria-hidden="true"
|
||||
role="dialog"
|
||||
style={{ display: "block" }}
|
||||
aria-hidden="false"
|
||||
>
|
||||
<FloorModel
|
||||
project={data}
|
||||
// building={null}
|
||||
project={project}
|
||||
onClose={closeFloorModel}
|
||||
onSubmit={handleFloorModelFormSubmit}
|
||||
clearTrigger={clearFormTrigger}
|
||||
onClearComplete={() => setClearFormTrigger(false)}
|
||||
></FloorModel>
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{isWorkAreaModelOpen && (
|
||||
<div
|
||||
className={`modal fade `}
|
||||
className="modal fade show"
|
||||
id="work-area-model"
|
||||
tabIndex="-1"
|
||||
aria-hidden="true"
|
||||
role="dialog"
|
||||
style={{ display: "block" }}
|
||||
aria-hidden="false"
|
||||
>
|
||||
<WorkAreaModel
|
||||
project={data}
|
||||
// building={null}
|
||||
project={project}
|
||||
onClose={closeWorkAreaModel}
|
||||
onSubmit={handleWorkAreaModelFormSubmit}
|
||||
clearTrigger={clearFormTrigger}
|
||||
onClearComplete={() => setClearFormTrigger(false)}
|
||||
></WorkAreaModel>
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{isTaskModelOpen && (
|
||||
<div
|
||||
className={`modal fade `}
|
||||
className="modal fade show"
|
||||
id="task-model"
|
||||
tabIndex="-1"
|
||||
aria-hidden="true"
|
||||
role="dialog"
|
||||
style={{ display: "block" }}
|
||||
aria-hidden="false"
|
||||
>
|
||||
<TaskModel
|
||||
project={data}
|
||||
activities={activityMaster}
|
||||
project={project}
|
||||
|
||||
onClose={closeTaskModel}
|
||||
onSubmit={handleTaskModelFormSubmit}
|
||||
clearTrigger={clearFormTrigger}
|
||||
onClearComplete={() => setClearFormTrigger(false)}
|
||||
></TaskModel>
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
|
||||
{/* common Modal */}
|
||||
{isModalOpen && (
|
||||
<ProjectModal modalConfig={modalConfig} closeModal={closeModal} />
|
||||
<ProjectModal modalConfig={modalConfig} closeModal={closeModal} />
|
||||
)}
|
||||
|
||||
<div className="col-md-12 col-lg-12 col-xl-12 order-0 mb-4">
|
||||
<div className="card">
|
||||
{/* <div className="card-header pb-4"></div> */}
|
||||
|
||||
<div className="card-body" style={{ padding: "0.5rem" }}>
|
||||
<div className="align-items-center">
|
||||
<div className="row ">
|
||||
<div className={`col-12 text-end mb-1 ${!ManageInfra && 'd-none'} `} >
|
||||
<div
|
||||
className={`col-12 text-end mb-1 ${
|
||||
!ManageInfra && "d-none"
|
||||
} `}
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
className="link-button link-button-sm m-1 "
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#building-model"
|
||||
onClick={() => openBuildingModel()}
|
||||
onClick={handleShow}
|
||||
>
|
||||
<i className="bx bx-plus-circle me-2"></i>
|
||||
Manage Building
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
data-bs-toggle="modal"
|
||||
className="link-button m-1"
|
||||
data-bs-target="#floor-model"
|
||||
onClick={() => openFloorModel()}
|
||||
>
|
||||
<i className="bx bx-plus-circle me-2"></i>
|
||||
Manage Floors
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
data-bs-toggle="modal"
|
||||
className="link-button m-1"
|
||||
data-bs-target="#work-area-model"
|
||||
onClick={() => openWorkAreaModel()}
|
||||
>
|
||||
<i className="bx bx-plus-circle me-2"></i>
|
||||
@ -553,59 +457,23 @@ const ProjectInfra = ({ data, activityMaster, onDataChange,eachSiteEngineer }) =
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
data-bs-toggle="modal"
|
||||
className="link-button m-1"
|
||||
data-bs-target="#task-model"
|
||||
onClick={() => openTaskModel()}
|
||||
>
|
||||
<i className="bx bx-plus-circle me-2"></i>
|
||||
Manage Tasks
|
||||
Create Tasks
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="row ">
|
||||
<div className="col-12 overflow-auto">
|
||||
{buildings && buildings.length > 0 && (
|
||||
<table className="table table-bordered ">
|
||||
<tbody>
|
||||
{buildings.map((building) => (
|
||||
|
||||
building.floors && building.floors.length > 0 && (
|
||||
<React.Fragment key={building.id}>
|
||||
<tr className="overflow-auto">
|
||||
<td
|
||||
colSpan="4"
|
||||
className="text-start "
|
||||
style={{
|
||||
background: "#f0f0f0",
|
||||
cursor: "pointer",
|
||||
}}
|
||||
onClick={() => toggleBuilding(building.id)}
|
||||
>
|
||||
<div className="row table-responsive">
|
||||
<h5 style={{ marginBottom: "0px" }}>
|
||||
|
||||
{ building.name}
|
||||
{expandedBuildings.includes(building.id) ? (
|
||||
<i className="bx bx-chevron-down"></i>
|
||||
) : (
|
||||
<i className="bx bx-chevron-right"></i>
|
||||
)}
|
||||
</h5>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
{expandedBuildings.includes(building.id) && getContent(building)}
|
||||
|
||||
</React.Fragment>
|
||||
)
|
||||
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
)}
|
||||
</div>
|
||||
{loading && <p>Loading....</p>}
|
||||
{project && project.buildings?.length > 0 && (
|
||||
<InfraTable
|
||||
buildings={project?.buildings}
|
||||
project={project}
|
||||
handleFloor={submitData}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -25,7 +25,8 @@ const ProjectModal = ({modalConfig,closeModal}) => {
|
||||
|
||||
{/* Modal Component */}
|
||||
|
||||
{modalConfig?.type === "assignRole" && <AssignRole assignData={modalConfig?.data} onClose={closeModal} />}
|
||||
{modalConfig?.type === "assignRole" && <AssignRole assignData={modalConfig?.data} onClose={closeModal} />}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -35,7 +35,7 @@ const ProjectNav = ( {onPillClick, activePill} ) =>
|
||||
<i className="bx bx-group bx-sm me-1_5"></i> Teams
|
||||
</a>
|
||||
</li>
|
||||
<li className={`nav-item ${HasViewInfraStructure ? "":"d-none"} `}>
|
||||
<li className={`nav-item ${!HasViewInfraStructure && 'd-none'} `}>
|
||||
<a
|
||||
className={`nav-link ${activePill === "infra" ? "active" : ""}`}
|
||||
href="#"
|
||||
@ -47,7 +47,7 @@ const ProjectNav = ( {onPillClick, activePill} ) =>
|
||||
<i className="bx bx-grid-alt bx-sm me-1_5"></i> Infrastructure
|
||||
</a>
|
||||
</li>
|
||||
<li className="nav-item">
|
||||
{/* <li className="nav-item">
|
||||
<a
|
||||
className={`nav-link ${
|
||||
activePill === "workplan" ? "active" : ""
|
||||
@ -60,7 +60,7 @@ const ProjectNav = ( {onPillClick, activePill} ) =>
|
||||
>
|
||||
<i className="bx bx-link bx-sm me-1_5"></i> Work Plan
|
||||
</a>
|
||||
</li>
|
||||
</li> */}
|
||||
<li className="nav-item">
|
||||
<a
|
||||
className={`nav-link ${
|
||||
@ -78,15 +78,15 @@ const ProjectNav = ( {onPillClick, activePill} ) =>
|
||||
<li className="nav-item">
|
||||
<a
|
||||
className={`nav-link ${
|
||||
activePill === "activities" ? "active" : ""
|
||||
activePill === "directory" ? "active" : ""
|
||||
}`}
|
||||
href="#"
|
||||
onClick={(e) => {
|
||||
e.preventDefault(); // Prevent page reload
|
||||
onPillClick("activities");
|
||||
onPillClick("directory");
|
||||
}}
|
||||
>
|
||||
<i className="bx bx-link bx-sm me-1_5"></i> Activities
|
||||
<i className="bx bx-link bx-sm me-1_5"></i> Directory
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
@ -1,11 +1,10 @@
|
||||
import React from "react";
|
||||
import {useEmployeesByProjectAllocated} from "../../hooks/useProjects";
|
||||
import {useEmployeesByProjectAllocated, useProjects} from "../../hooks/useProjects";
|
||||
|
||||
const ProjectOverview = ({project}) =>
|
||||
{
|
||||
const {projectEmployees} = useEmployeesByProjectAllocated( project.id );
|
||||
let teamSize = projectEmployees.filter( ( emp ) => emp.isActive )
|
||||
|
||||
const {projects} = useProjects()
|
||||
const teamSize = projects.find((pro)=>pro.id == project)
|
||||
return (
|
||||
<div className="card mb-6">
|
||||
<div className="card-body">
|
||||
@ -26,7 +25,7 @@ const ProjectOverview = ({project}) =>
|
||||
<li className="d-flex align-items-center">
|
||||
<i className="bx bx-user"></i>
|
||||
<span className="fw-medium mx-2">Current team Size:</span>{" "}
|
||||
<span>{ teamSize?.length}</span>
|
||||
<span>{teamSize?.teamSize}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@ -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 { ComingSoonPage } from "../../pages/Misc/ComingSoonPage";
|
||||
|
||||
const WorkPlan = () => {
|
||||
return <div>Work plan calender will go here</div>;
|
||||
return <ComingSoonPage></ComingSoonPage>;
|
||||
};
|
||||
|
||||
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 = () => {
|
||||
return (
|
||||
<div class="demo-inline-spacing">
|
||||
<div class="spinner-grow" role="status">
|
||||
<span class="visually-hidden">Loading...</span>
|
||||
<div className="demo-inline-spacing">
|
||||
<div className="spinner-grow" role="status">
|
||||
<span className="visually-hidden">Loading...</span>
|
||||
</div>
|
||||
<div class="spinner-grow text-primary" role="status">
|
||||
<span class="visually-hidden">Loading...</span>
|
||||
<div className="spinner-grow text-primary" role="status">
|
||||
<span className="visually-hidden">Loading...</span>
|
||||
</div>
|
||||
<div class="spinner-grow text-secondary" role="status">
|
||||
<span class="visually-hidden">Loading...</span>
|
||||
<div className="spinner-grow text-secondary" role="status">
|
||||
<span className="visually-hidden">Loading...</span>
|
||||
</div>
|
||||
<div class="spinner-grow text-success" role="status">
|
||||
<span class="visually-hidden">Loading...</span>
|
||||
<div className="spinner-grow text-success" role="status">
|
||||
<span className="visually-hidden">Loading...</span>
|
||||
</div>
|
||||
<div class="spinner-grow text-danger" role="status">
|
||||
<span class="visually-hidden">Loading...</span>
|
||||
<div className="spinner-grow text-danger" role="status">
|
||||
<span className="visually-hidden">Loading...</span>
|
||||
</div>
|
||||
<div class="spinner-grow text-warning" role="status">
|
||||
<span class="visually-hidden">Loading...</span>
|
||||
<div className="spinner-grow text-warning" role="status">
|
||||
<span className="visually-hidden">Loading...</span>
|
||||
</div>
|
||||
<div class="spinner-grow text-info" role="status">
|
||||
<span class="visually-hidden">Loading...</span>
|
||||
<div className="spinner-grow text-info" role="status">
|
||||
<span className="visually-hidden">Loading...</span>
|
||||
</div>
|
||||
<div class="spinner-grow text-light" role="status">
|
||||
<span class="visually-hidden">Loading...</span>
|
||||
<div className="spinner-grow text-light" role="status">
|
||||
<span className="visually-hidden">Loading...</span>
|
||||
</div>
|
||||
<div class="spinner-grow text-dark" role="status">
|
||||
<span class="visually-hidden">Loading...</span>
|
||||
<div className="spinner-grow text-dark" role="status">
|
||||
<span className="visually-hidden">Loading...</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
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)=>{
|
||||
console.log(resp)
|
||||
setIsLoading(false)
|
||||
|
||||
const cachedData = getCachedData( "Role" );
|
||||
console.log(cachedData)
|
||||
const updatedData = [...cachedData, resp];
|
||||
const updatedData = [...cachedData, resp.data];
|
||||
|
||||
cacheData("Role", updatedData);
|
||||
showToast("Role Added successfully.", "success");
|
||||
cacheData("Application Role", updatedData);
|
||||
showToast("Application Role Added successfully.", "success");
|
||||
onClose()
|
||||
} ).catch( ( err ) =>
|
||||
{
|
||||
|
||||
@ -3,18 +3,17 @@ import axios from "axios";
|
||||
|
||||
const API_URL = "http://localhost:5000/mastersdata";
|
||||
|
||||
|
||||
const DeleteMaster = ({ master,onClose}) => {
|
||||
const DeleteMaster = ({ master, onClose }) => {
|
||||
const [loader, setLoader] = useState(false);
|
||||
|
||||
|
||||
const handleDelete = () => {
|
||||
const index = mastersdata[master?.masterType]?.findIndex(item => String(item?.id) === String(master?.item?.id));
|
||||
console.log(index)
|
||||
const handleDelete = () => {
|
||||
const index = mastersdata[master?.masterType]?.findIndex(
|
||||
(item) => String(item?.id) === String(master?.item?.id)
|
||||
);
|
||||
if (index !== -1) {
|
||||
mastersdata[master?.masterType].splice(index, 1);
|
||||
mastersdata[master?.masterType].splice(index, 1);
|
||||
}
|
||||
onClose()
|
||||
onClose();
|
||||
};
|
||||
|
||||
return (
|
||||
@ -27,8 +26,8 @@ const DeleteMaster = ({ master,onClose}) => {
|
||||
onClick={handleDelete}
|
||||
>
|
||||
{loader ? (
|
||||
<div class="spinner-border text-primary" role="status">
|
||||
<span class="sr-only">Loading...</span>
|
||||
<div className="spinner-border text-primary" role="status">
|
||||
<span className="sr-only">Loading...</span>
|
||||
</div>
|
||||
) : (
|
||||
"Delete"
|
||||
|
||||
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 )
|
||||
|
||||
|
||||
const cachedData = getCachedData("Role");
|
||||
const cachedData = getCachedData("Application Role");
|
||||
|
||||
if (cachedData) {
|
||||
|
||||
@ -109,9 +109,9 @@ const EditMaster=({master,onClose})=> {
|
||||
role.id === resp.data?.id ? { ...role, ...resp.data } : role
|
||||
);
|
||||
|
||||
cacheData("Role", updatedData);
|
||||
cacheData("Application Role", updatedData);
|
||||
}
|
||||
showToast( "Role Update successfully.", "success" );
|
||||
showToast( "Application Role Updated successfully.", "success" );
|
||||
setIsLoading(false)
|
||||
onClose()
|
||||
}).catch((Err)=>{
|
||||
|
||||
@ -5,53 +5,62 @@ import DeleteMaster from "./DeleteMaster";
|
||||
import EditRole from "./EditRole";
|
||||
import CreateJobRole from "./CreateJobRole";
|
||||
import EditJobRole from "./EditJobRole";
|
||||
import CreateActivity from "./CreateActivity";
|
||||
import EditActivity from "./EditActivity";
|
||||
|
||||
const MasterModal = ({ modaldata ,closeModal}) => {
|
||||
|
||||
const MasterModal = ({ modaldata, closeModal }) => {
|
||||
return (
|
||||
|
||||
<div
|
||||
className="modal fade"
|
||||
id="master-modal"
|
||||
tabIndex="-1"
|
||||
aria-hidden="true"
|
||||
role="dialog"
|
||||
<div
|
||||
className="modal fade"
|
||||
id="master-modal"
|
||||
tabIndex="-1"
|
||||
aria-hidden="true"
|
||||
role="dialog"
|
||||
aria-labelledby="modalToggleLabel"
|
||||
>
|
||||
<div
|
||||
className={`modal-dialog mx-sm-auto mx-1 ${
|
||||
modaldata?.modalType === "delete" ? "modal-md" : "modal-lg"
|
||||
} modal-simple ` }
|
||||
>
|
||||
<div className="modal-content">
|
||||
<div className="modal-body p-sm-4 p-0">
|
||||
<button
|
||||
type="button"
|
||||
className="btn-close"
|
||||
data-bs-dismiss="modal"
|
||||
aria-label="Close"
|
||||
onClick={closeModal}
|
||||
></button>
|
||||
<div className="text-center mb-2"></div>
|
||||
{modaldata?.modalType === "Role" &&
|
||||
<CreateRole masmodalType={modaldata.masterType} onClose={closeModal} />}
|
||||
{modaldata?.modalType === "Edit-Role" && (
|
||||
<EditRole master={modaldata} onClose={closeModal} />
|
||||
)}
|
||||
{modaldata?.modalType === "delete" && (
|
||||
<DeleteMaster master={modaldata} onClose={closeModal}/>
|
||||
)}
|
||||
{modaldata?.modalType === "Job Role" && (
|
||||
<CreateJobRole onClose={closeModal} />
|
||||
)}
|
||||
{modaldata?.modalType === "Edit-Job Role" && (
|
||||
<EditJobRole data ={modaldata.item} onClose={closeModal} />
|
||||
)}
|
||||
|
||||
<div
|
||||
className={`modal-dialog mx-sm-auto mx-1 ${
|
||||
modaldata?.modalType === "delete" || `Ativity` ? "modal-md" : "modal-lg"
|
||||
} modal-simple `}
|
||||
>
|
||||
<div className="modal-content">
|
||||
<div className="modal-body p-sm-4 p-0">
|
||||
<button
|
||||
type="button"
|
||||
className="btn-close"
|
||||
data-bs-dismiss="modal"
|
||||
aria-label="Close"
|
||||
onClick={closeModal}
|
||||
></button>
|
||||
<div className="text-center mb-2"></div>
|
||||
{modaldata?.modalType === "Application Role" && (
|
||||
<CreateRole
|
||||
masmodalType={modaldata.masterType}
|
||||
onClose={closeModal}
|
||||
/>
|
||||
)}
|
||||
{modaldata?.modalType === "Edit-Application Role" && (
|
||||
<EditRole master={modaldata} onClose={closeModal} />
|
||||
)}
|
||||
{modaldata?.modalType === "delete" && (
|
||||
<DeleteMaster master={modaldata} onClose={closeModal} />
|
||||
)}
|
||||
{modaldata?.modalType === "Job Role" && (
|
||||
<CreateJobRole onClose={closeModal} />
|
||||
)}
|
||||
{modaldata?.modalType === "Edit-Job Role" && (
|
||||
<EditJobRole data={modaldata.item} onClose={closeModal} />
|
||||
)}
|
||||
{modaldata?.modalType === "Activity" && (
|
||||
<CreateActivity onClose={closeModal} /> )
|
||||
}
|
||||
{modaldata?.modalType === 'Edit-Activity' && (
|
||||
<EditActivity activityData={modaldata.item} onClose={closeModal} />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@ -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 = [
|
||||
{
|
||||
|
||||
@ -28,17 +28,13 @@
|
||||
"text": "Project Status",
|
||||
"available": true,
|
||||
"link": "#"
|
||||
},
|
||||
{
|
||||
"text": "Inventory",
|
||||
"available": true,
|
||||
"link": "/inventory"
|
||||
}
|
||||
|
||||
]
|
||||
},
|
||||
{
|
||||
"text": "Activities",
|
||||
"icon": "bx bx-store",
|
||||
"icon": "bx bx-list-ul",
|
||||
"available": true,
|
||||
"link": "",
|
||||
"submenu": [
|
||||
@ -70,25 +66,20 @@
|
||||
{
|
||||
"text": "Daily Expenses",
|
||||
"available": true,
|
||||
"link": "/dashboard"
|
||||
"link": "/dashboard/"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"text": "User Management",
|
||||
"text": "Administration",
|
||||
"icon": "bx bx-box",
|
||||
"available": true,
|
||||
"link": "",
|
||||
"submenu": [
|
||||
{
|
||||
"text": "Application Users",
|
||||
"text": "Users",
|
||||
"available": true,
|
||||
"link": "/employees"
|
||||
},
|
||||
{
|
||||
"text": "Employees",
|
||||
"available": true,
|
||||
"link": "/employees"
|
||||
"link": "/employees/"
|
||||
},
|
||||
{
|
||||
"text": "Masters",
|
||||
@ -96,11 +87,17 @@
|
||||
"link": "/masters"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"text": "Inventory",
|
||||
"icon": "bx bx-store",
|
||||
"available": true,
|
||||
"link": "/inventory"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"header": "Misc",
|
||||
"header": "",
|
||||
"items": [
|
||||
{
|
||||
"text": "Support",
|
||||
|
||||
@ -35,15 +35,17 @@ const useMaster = () => {
|
||||
} else {
|
||||
let response;
|
||||
switch (selectedMaster) {
|
||||
case "Role":
|
||||
case "Application Role":
|
||||
response = await MasterRespository.getRoles();
|
||||
response = response.data;
|
||||
break;
|
||||
case "Job Role":
|
||||
response = await MasterRespository.getJobRole();
|
||||
response = response.data
|
||||
break;
|
||||
case "Module":
|
||||
response = [{description: null,module:"Module 1",featurePermission: null,id: "08dd4761-363c-49ed-8851-3d2489a3e98d"},{description: null,module:"Module 2",featurePermission: null,id: "08dy9761-363c-49ed-8851-3d2489a3e98d"},{description: null,module:"Module 3",featurePermission: null,id: "08dy7761-263c-49ed-8851-3d2489a3e98d"}];
|
||||
case "Activity":
|
||||
response = await MasterRespository.getActivites();
|
||||
response = response.data
|
||||
break;
|
||||
case "Status":
|
||||
response = [{description: null,featurePermission: null,id: "02dd4761-363c-49ed-8851-3d2489a3e98d",status:"status 1"},{description: null,featurePermission: null,id: "03dy9761-363c-49ed-8851-3d2489a3e98d",status:"status 2"},{description: null,featurePermission: null,id: "03dy7761-263c-49ed-8851-3d2489a3e98d",status:"Status 3"}];
|
||||
|
||||
@ -30,7 +30,10 @@ export const useAttendace =(projectId)=>{
|
||||
|
||||
|
||||
useEffect(()=>{
|
||||
fetchData(projectId);
|
||||
if ( projectId )
|
||||
{
|
||||
fetchData(projectId);
|
||||
}
|
||||
},[projectId])
|
||||
|
||||
return {attendance,loading,error}
|
||||
@ -40,7 +43,7 @@ export const useAttendace =(projectId)=>{
|
||||
|
||||
|
||||
export const useEmployeeAttendacesLog = (id) => {
|
||||
const [logs, setLogs] = useState();
|
||||
const [logs, setLogs] = useState([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState(null);
|
||||
|
||||
@ -71,4 +74,41 @@ export const useEmployeeAttendacesLog = (id) => {
|
||||
}, [id]);
|
||||
|
||||
return { logs, loading, error };
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
|
||||
export const useRegularizationRequests = ( projectId ) =>
|
||||
{
|
||||
const [regularizes, setregularizes] = useState([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState(null);
|
||||
|
||||
|
||||
const fetchData = () => {
|
||||
const regularizedList_cache = getCachedData("regularizedList");
|
||||
if(!regularizedList_cache || regularizedList_cache.projectId !== projectId ){
|
||||
setLoading(true)
|
||||
AttendanceRepository.getRegularizeList(projectId).then((response)=>{
|
||||
setregularizes( response.data )
|
||||
console.log(response.data)
|
||||
cacheData("regularizedList", { data: response.data, projectId })
|
||||
setLoading(false)
|
||||
}).catch((error)=>{
|
||||
setError("Failed to fetch data.");
|
||||
setLoading(false);
|
||||
})
|
||||
}else{
|
||||
|
||||
setregularizes(regularizedList_cache.data);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (projectId) {
|
||||
fetchData();
|
||||
}
|
||||
|
||||
}, [ projectId ] );
|
||||
return {regularizes,loading,error,refetch:fetchData}
|
||||
}
|
||||
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 Employee_cache = getCachedData("employeeProfile");
|
||||
if(!Employee_cache || Employee_cache.employeeId !== employeeId){
|
||||
|
||||
|
||||
|
||||
EmployeeRepository.getEmployeeProfile(employeeId)
|
||||
.then((response) => {
|
||||
setEmployees(response.data);
|
||||
@ -260,7 +259,9 @@ export const useEmployeeProfile =(employeeId)=>{
|
||||
};
|
||||
|
||||
useEffect(()=>{
|
||||
fetchData(employeeId);
|
||||
if(employeeId){
|
||||
fetchData(employeeId);
|
||||
}
|
||||
},[employeeId])
|
||||
|
||||
return {employee,loading,error}
|
||||
|
||||
@ -16,9 +16,9 @@ export const useMasterRole =()=>{
|
||||
if (!features_cache) {
|
||||
MasterRespository.getRoles()
|
||||
.then((response) => {
|
||||
setMasterRole(response);
|
||||
setMasterRole(response.data);
|
||||
|
||||
cacheData("masterRole", response);
|
||||
cacheData("masterRole", response.data);
|
||||
setLoading(false)
|
||||
})
|
||||
.catch((error) => {
|
||||
@ -55,9 +55,9 @@ export const useFeatures =()=> {
|
||||
if (!features_cache) {
|
||||
MasterRespository.getFeatures()
|
||||
.then((response) => {
|
||||
setMasterFeatures(response);
|
||||
setMasterFeatures(response.data);
|
||||
|
||||
cacheData("features", response);
|
||||
cacheData("features", response.data);
|
||||
setLoading(false)
|
||||
})
|
||||
.catch((error) => {
|
||||
|
||||
@ -18,8 +18,8 @@ export const useProfile = () =>
|
||||
{
|
||||
setLoading( true )
|
||||
let response = await AuthRepository.profile();
|
||||
setProfile( response )
|
||||
cacheProfileData( response )
|
||||
setProfile( response.data )
|
||||
cacheProfileData( response.data )
|
||||
setLoading( false );
|
||||
|
||||
} catch ( error )
|
||||
|
||||
@ -1,120 +1,126 @@
|
||||
import { useEffect,useState } from "react"
|
||||
import {
|
||||
cacheData,
|
||||
getCachedData,
|
||||
} from "../slices/apiDataManager"
|
||||
import { useEffect, useState } from "react";
|
||||
import { cacheData, getCachedData } from "../slices/apiDataManager";
|
||||
import ProjectRepository from "../repositories/ProjectRepository";
|
||||
import { useProfile } from "./useProfile";
|
||||
|
||||
|
||||
export const useProjects =()=>{
|
||||
|
||||
export const useProjects = () => {
|
||||
const [projects, setProjects] = useState([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState("");
|
||||
|
||||
const fetchData = async () => {
|
||||
const projects_cache = getCachedData("projectslist");
|
||||
|
||||
if (!projects_cache) {
|
||||
setLoading(true)
|
||||
ProjectRepository.getProjectList()
|
||||
const projects_cache = getCachedData("projectslist");
|
||||
|
||||
if (!projects_cache) {
|
||||
setLoading(true);
|
||||
ProjectRepository.getProjectList()
|
||||
.then((response) => {
|
||||
setProjects(response);
|
||||
|
||||
cacheData("projectslist", response);
|
||||
let projects = response.data;
|
||||
const sortedProject = [...projects].sort((a, b) =>
|
||||
a.name.localeCompare(b.name)
|
||||
);
|
||||
|
||||
setProjects(sortedProject);
|
||||
|
||||
cacheData("projectslist", sortedProject);
|
||||
setLoading(false);
|
||||
})
|
||||
.catch((error) => {
|
||||
setLoading(false)
|
||||
console.error(error);
|
||||
setLoading(false);
|
||||
setError("Failed to fetch data.");
|
||||
});
|
||||
|
||||
} else {
|
||||
if (!projects.length) setProjects(projects_cache);
|
||||
} else {
|
||||
if (!projects.length) {
|
||||
let projects = projects_cache;
|
||||
const sortedProject = [...projects].sort((a, b) =>
|
||||
a.name.localeCompare(b.name)
|
||||
);
|
||||
setProjects(sortedProject);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(()=>{
|
||||
fetchData()
|
||||
},[])
|
||||
|
||||
return { projects,loading,error,refetch:fetchData}
|
||||
|
||||
}
|
||||
useEffect(() => {
|
||||
fetchData();
|
||||
}, []);
|
||||
|
||||
export const useEmployeesByProjectAllocated = ( selectedProject ) =>
|
||||
{
|
||||
const [projectEmployees, setEmployeeList] = useState([]);
|
||||
const[loading,setLoading] = useState(true)
|
||||
const [projects, setProjects] = useState([]);
|
||||
|
||||
const fetchData = async (projectid) => {
|
||||
try {
|
||||
let EmployeeByProject_Cache = getCachedData("empListByProjectAllocated")
|
||||
if(!EmployeeByProject_Cache || !EmployeeByProject_Cache.projectId === projectid) {
|
||||
|
||||
let response = await ProjectRepository.getProjectAllocation(projectid)
|
||||
setEmployeeList(response.data);
|
||||
cacheData("empListByProjectAllocated",{data:response.data,projectId:projectid});
|
||||
setLoading(false)
|
||||
}else{
|
||||
setEmployeeList(EmployeeByProject_Cache.data)
|
||||
setLoading(false)
|
||||
}
|
||||
|
||||
|
||||
} catch (err) {
|
||||
setError("Failed to fetch data.");
|
||||
setLoading(false)
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
useEffect(()=>{
|
||||
if(selectedProject){
|
||||
return { projects, loading, error, refetch: fetchData };
|
||||
};
|
||||
|
||||
export const useEmployeesByProjectAllocated = (selectedProject) => {
|
||||
const [projectEmployees, setEmployeeList] = useState([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [projects, setProjects] = useState([]);
|
||||
|
||||
const fetchData = async (projectid) => {
|
||||
try {
|
||||
let EmployeeByProject_Cache = getCachedData("empListByProjectAllocated");
|
||||
if (
|
||||
!EmployeeByProject_Cache ||
|
||||
!EmployeeByProject_Cache.projectId === projectid
|
||||
) {
|
||||
let response = await ProjectRepository.getProjectAllocation(projectid);
|
||||
setEmployeeList(response.data);
|
||||
cacheData("empListByProjectAllocated", {
|
||||
data: response.data,
|
||||
projectId: projectid,
|
||||
});
|
||||
setLoading(false);
|
||||
} else {
|
||||
setEmployeeList(EmployeeByProject_Cache.data);
|
||||
setLoading(false);
|
||||
}
|
||||
} catch (err) {
|
||||
setError("Failed to fetch data.");
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedProject) {
|
||||
fetchData(selectedProject);
|
||||
}
|
||||
},[selectedProject])
|
||||
|
||||
|
||||
return {projectEmployees,loading,projects}
|
||||
}
|
||||
|
||||
export const useProjectDetails =(projectId)=>{
|
||||
|
||||
}
|
||||
}, [selectedProject]);
|
||||
|
||||
return { projectEmployees, loading, projects };
|
||||
};
|
||||
|
||||
export const useProjectDetails = (projectId) => {
|
||||
const { profile } = useProfile();
|
||||
const [projects_Details, setProject_Details] = useState(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState("");
|
||||
|
||||
const fetchData = async () => {
|
||||
setLoading(true)
|
||||
const project_cache = getCachedData(`projectinfo-${projectId}`);
|
||||
if (!project_cache) {
|
||||
setLoading(true);
|
||||
|
||||
const project_cache = getCachedData("projectInfo");
|
||||
if (!project_cache || project_cache?.projectId != projectId) {
|
||||
ProjectRepository.getProjectByprojectId(projectId)
|
||||
.then( ( response ) =>
|
||||
{
|
||||
setProject_Details(response);
|
||||
cacheData( `projectinfo-${ projectId }`, response );
|
||||
setLoading(false)
|
||||
.then((response) => {
|
||||
setProject_Details(response.data);
|
||||
cacheData("projectInfo", {
|
||||
projectId: projectId,
|
||||
data: response.data,
|
||||
});
|
||||
setLoading(false);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(error);
|
||||
setError( "Failed to fetch data." );
|
||||
setLoading(false)
|
||||
setError("Failed to fetch data.");
|
||||
setLoading(false);
|
||||
});
|
||||
} else {
|
||||
setProject_Details( project_cache );
|
||||
|
||||
setLoading(false)
|
||||
}
|
||||
|
||||
};;
|
||||
|
||||
useEffect(()=>{
|
||||
fetchData()
|
||||
},[projectId])
|
||||
setProject_Details(project_cache.data);
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return { projects_Details,loading,error,refetch:fetchData}
|
||||
|
||||
}
|
||||
useEffect(() => {
|
||||
if (profile && projectId != undefined) {
|
||||
fetchData();
|
||||
}
|
||||
}, [projectId, profile]);
|
||||
|
||||
return { projects_Details, loading, error, refetch: fetchData };
|
||||
};
|
||||
|
||||
43
src/hooks/useTasks.js
Normal file
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 { store } from './store/store';
|
||||
import { ModalProvider } from './ModalContext.jsx';
|
||||
|
||||
|
||||
|
||||
createRoot(document.getElementById('root')!).render(
|
||||
// <StrictMode>
|
||||
// <MasterDataProvider>
|
||||
<Provider store={store}>
|
||||
<App />
|
||||
<Provider store={ store }>
|
||||
<ModalProvider>
|
||||
<App />
|
||||
</ModalProvider>
|
||||
</Provider>
|
||||
// </MasterDataProvider>
|
||||
|
||||
|
||||
@ -1,5 +1,9 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { cacheData, getCachedData, getCachedProfileData } from "../../slices/apiDataManager";
|
||||
import {
|
||||
cacheData,
|
||||
getCachedData,
|
||||
getCachedProfileData,
|
||||
} from "../../slices/apiDataManager";
|
||||
import Breadcrumb from "../../components/common/Breadcrumb";
|
||||
import AttendanceLog from "../../components/Activities/AttendcesLogs";
|
||||
import Attendance from "../../components/Activities/Attendance";
|
||||
@ -10,26 +14,25 @@ import Regularization from "../../components/Activities/Regularization";
|
||||
import { useAttendace } from "../../hooks/useAttendance";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { setProjectId } from "../../slices/localVariablesSlice";
|
||||
import {markCurrentAttendance} from "../../slices/apiSlice/attendanceAllSlice";
|
||||
import { markCurrentAttendance } from "../../slices/apiSlice/attendanceAllSlice";
|
||||
import { hasUserPermission } from "../../utils/authUtils";
|
||||
import {useHasUserPermission} from "../../hooks/useHasUserPermission";
|
||||
import {REGULARIZE_ATTENDANCE} from "../../utils/constants";
|
||||
import { useHasUserPermission } from "../../hooks/useHasUserPermission";
|
||||
import { REGULARIZE_ATTENDANCE } from "../../utils/constants";
|
||||
|
||||
const AttendancePage = () =>
|
||||
{
|
||||
const loginUser = getCachedProfileData()
|
||||
var selectedProject = useSelector( ( store ) => store.localVariables.projectId )
|
||||
const {projects,loading:projectLoading} = useProjects()
|
||||
const {attendance,loading:attLoading} = useAttendace(selectedProject)
|
||||
const[attendances,setAttendances] = useState()
|
||||
const AttendancePage = () => {
|
||||
const loginUser = getCachedProfileData();
|
||||
var selectedProject = useSelector((store) => store.localVariables.projectId);
|
||||
const { projects, loading: projectLoading } = useProjects();
|
||||
const {attendance, loading: attLoading} = useAttendace( selectedProject );
|
||||
const [ attendances, setAttendances ] = useState();
|
||||
const [empRoles, setEmpRoles] = useState(null);
|
||||
const [isCreateModalOpen, setIsCreateModalOpen] = useState(false);
|
||||
const [ modelConfig, setModelConfig ] = useState();
|
||||
const DoRegularized = useHasUserPermission(REGULARIZE_ATTENDANCE)
|
||||
const dispatch = useDispatch()
|
||||
|
||||
const [modelConfig, setModelConfig] = useState();
|
||||
const DoRegularized = useHasUserPermission(REGULARIZE_ATTENDANCE);
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const [formData, setFormData] = useState({
|
||||
time: "",
|
||||
markTime: "",
|
||||
description: "",
|
||||
date: new Date().toLocaleDateString(),
|
||||
});
|
||||
@ -46,25 +49,28 @@ const AttendancePage = () =>
|
||||
setIsCreateModalOpen(true);
|
||||
};
|
||||
|
||||
|
||||
const handleModalData =(employee)=>{
|
||||
const handleModalData = ( employee ) =>
|
||||
{
|
||||
|
||||
setModelConfig(employee);
|
||||
}
|
||||
};
|
||||
|
||||
const closeModal = () => {
|
||||
setModelConfig(null);
|
||||
setIsCreateModalOpen(false);
|
||||
const modalElement = document.getElementById("check-Out-modal");
|
||||
if (modalElement) {
|
||||
modalElement.classList.remove('show');
|
||||
modalElement.style.display = 'none';
|
||||
document.body.classList.remove('modal-open');
|
||||
document.querySelector('.modal-backdrop').remove();
|
||||
}
|
||||
modalElement.classList.remove("show");
|
||||
modalElement.style.display = "none";
|
||||
document.body.classList.remove("modal-open");
|
||||
document.querySelector(".modal-backdrop").remove();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const handleSubmit = ( formData ) =>{
|
||||
const handleSubmit = ( formData ) =>
|
||||
{
|
||||
|
||||
dispatch( markCurrentAttendance( formData ) ).then( ( action ) =>
|
||||
{
|
||||
const updatedAttendance = attendances.map(item =>
|
||||
@ -82,33 +88,40 @@ const AttendancePage = () =>
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
if (modelConfig !== null) {
|
||||
openModel();
|
||||
}
|
||||
}, [modelConfig,isCreateModalOpen]);
|
||||
useEffect(()=>{
|
||||
setAttendances(attendance)
|
||||
},[attendance])
|
||||
}, [modelConfig, isCreateModalOpen]);
|
||||
useEffect(() => {
|
||||
setAttendances( attendance );
|
||||
}, [attendance]);
|
||||
useEffect(() => {
|
||||
dispatch(setProjectId(projects[0]?.id));
|
||||
}, [projects]);
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
{isCreateModalOpen && modelConfig && (
|
||||
<div
|
||||
className="modal fade show"
|
||||
style={{ display: "block" }}
|
||||
id="check-Out-modal"
|
||||
tabindex="-1"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<AttendanceModel modelConfig={modelConfig} closeModal={closeModal} handleSubmitForm={handleSubmit}/>
|
||||
{isCreateModalOpen && modelConfig && (
|
||||
<div
|
||||
className="modal fade show"
|
||||
style={{ display: "block" }}
|
||||
id="check-Out-modal"
|
||||
tabindex="-1"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<AttendanceModel
|
||||
modelConfig={modelConfig}
|
||||
closeModal={closeModal}
|
||||
handleSubmitForm={handleSubmit}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="container-xxl flex-grow-1 container-p-y">
|
||||
<Breadcrumb
|
||||
)}
|
||||
|
||||
<div className="container-xxl flex-grow-1 container-p-y">
|
||||
<Breadcrumb
|
||||
data={[
|
||||
{ label: "Home", link: "/dashboard" },
|
||||
{ label: "Attendance", link: null },
|
||||
@ -121,7 +134,7 @@ const AttendancePage = () =>
|
||||
id="DataTables_Table_0_length"
|
||||
>
|
||||
{
|
||||
((loginUser && loginUser?.projects.length > 1) ) && (<label>
|
||||
((loginUser && loginUser?.projects?.length > 1) ) && (<label>
|
||||
<select
|
||||
name="DataTables_Table_0_length"
|
||||
aria-controls="DataTables_Table_0"
|
||||
@ -132,7 +145,7 @@ const AttendancePage = () =>
|
||||
>
|
||||
{!projectLoading && projects?.filter(project =>
|
||||
loginUser?.projects?.map(Number).includes(project.id)).map((project)=>(
|
||||
<option value={project.id}>{project.name}</option>
|
||||
<option value={project.id} key={project.id}>{project.name}</option>
|
||||
))}
|
||||
{projectLoading && <option value="Loading..." disabled>Loading...</option> }
|
||||
</select>
|
||||
@ -165,7 +178,6 @@ const AttendancePage = () =>
|
||||
Logs
|
||||
</button>
|
||||
</li>
|
||||
|
||||
<li className={`nav-item ${!DoRegularized && 'd-none'}`}>
|
||||
<button
|
||||
type="button"
|
||||
@ -174,43 +186,55 @@ const AttendancePage = () =>
|
||||
data-bs-toggle="tab"
|
||||
data-bs-target="#navs-top-messages"
|
||||
aria-controls="navs-top-messages"
|
||||
aria-selected="false">
|
||||
aria-selected="false"
|
||||
>
|
||||
Regularization
|
||||
</button>
|
||||
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
<div class="tab-content attedanceTabs py-2">
|
||||
{projectLoading && (<span>Loading..</span>)}
|
||||
{(!projectLoading && !attendances) && <span>Not Found</span>}
|
||||
{ (projects && projects.length > 0 ) && (
|
||||
<>
|
||||
<div className="tab-pane fade show active py-0" id="navs-top-home" role="tabpanel" key={projects.id}>
|
||||
|
||||
<Attendance attendance={attendances} handleModalData={handleModalData} getRole={getRole} />
|
||||
</div>
|
||||
<div class="tab-pane fade" id="navs-top-profile" role="tabpanel">
|
||||
|
||||
<AttendanceLog
|
||||
attendance={attendances}
|
||||
handleModalData={handleModalData}
|
||||
projectId={selectedProject}
|
||||
/>
|
||||
</div>
|
||||
<div className="tab-pane fade" id="navs-top-messages" role="tabpanel">
|
||||
<Regularization
|
||||
attendance={attendances}
|
||||
handleRequest ={handleSubmit}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</ul>
|
||||
<div className="tab-content attedanceTabs py-2">
|
||||
{projectLoading && <span>Loading..</span>}
|
||||
{!projectLoading && !attendances && <span>Not Found</span>}
|
||||
{projects && projects.length > 0 && (
|
||||
<>
|
||||
<div
|
||||
className="tab-pane fade show active py-0"
|
||||
id="navs-top-home"
|
||||
role="tabpanel"
|
||||
key={projects.id}
|
||||
>
|
||||
<Attendance
|
||||
attendance={attendances}
|
||||
handleModalData={handleModalData}
|
||||
getRole={getRole}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className="tab-pane fade"
|
||||
id="navs-top-profile"
|
||||
role="tabpanel"
|
||||
>
|
||||
<AttendanceLog
|
||||
attendance={attendances}
|
||||
handleModalData={handleModalData}
|
||||
projectId={selectedProject}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className="tab-pane fade"
|
||||
id="navs-top-messages"
|
||||
role="tabpanel"
|
||||
>
|
||||
<Regularization
|
||||
|
||||
handleRequest={handleSubmit}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,86 +1,273 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import Breadcrumb from "../../components/common/Breadcrumb";
|
||||
import { dailyTask } from "../../data/masters";
|
||||
import { useTaskList } from "../../hooks/useTasks";
|
||||
import { useProjects } from "../../hooks/useProjects";
|
||||
import { setProjectId } from "../../slices/localVariablesSlice";
|
||||
import { useProfile } from "../../hooks/useProfile";
|
||||
import { formatDate } from "../../utils/dateUtils";
|
||||
import GlobalModel from "../../components/common/GlobalModel";
|
||||
import AssignRoleModel from "../../components/Project/AssignRole";
|
||||
import { ReportTask } from "../../components/Activities/ReportTask";
|
||||
import ReportTaskComments from "../../components/Activities/ReportTaskComments";
|
||||
import DateRangePicker from "../../components/common/DateRangePicker";
|
||||
import DatePicker from "../../components/common/DatePicker";
|
||||
|
||||
import Breadcrumb from "../../components/common/Breadcrumb"
|
||||
import { dailyTask } from "../../data/masters"
|
||||
const DailyTask =()=>{
|
||||
return (
|
||||
<>
|
||||
<div className="container-xxl flex-grow-1 container-p-y">
|
||||
const DailyTask = () => {
|
||||
const { profile: LoggedUser } = useProfile();
|
||||
|
||||
const {
|
||||
projects,
|
||||
loading: project_lodaing,
|
||||
error: projects_Error,
|
||||
} = useProjects();
|
||||
const selectedProject = useSelector(
|
||||
(store) => store.localVariables.projectId
|
||||
);
|
||||
const dispatch = useDispatch(selectedProject);
|
||||
const [dateRange, setDateRange] = useState({ startDate: "", endDate: "" });
|
||||
|
||||
const {
|
||||
TaskList,
|
||||
loading: task_loading,
|
||||
error: task_error,
|
||||
refetch,
|
||||
} = useTaskList(selectedProject, dateRange.startDate, dateRange.endDate);
|
||||
|
||||
const [TaskLists, setTaskLists] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
setTaskLists(TaskList);
|
||||
}, [TaskList, selectedProject]);
|
||||
|
||||
const [selectedTask, selectTask] = useState(null);
|
||||
const [comments, setComment] = useState(null);
|
||||
|
||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||
const [isModalOpenComment, setIsModalOpenComment] = useState(false);
|
||||
|
||||
const openModal = () => setIsModalOpen(true);
|
||||
const closeModal = () => setIsModalOpen(false);
|
||||
|
||||
const openComment = () => setIsModalOpenComment(true);
|
||||
const closeCommentModal = () => setIsModalOpenComment(false);
|
||||
const handletask = (task) => {
|
||||
selectTask(task);
|
||||
openModal();
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className={`modal fade ${isModalOpen ? "show" : ""}`}
|
||||
tabIndex="-1"
|
||||
role="dialog"
|
||||
style={{ display: isModalOpen ? "block" : "none" }}
|
||||
aria-hidden={!isModalOpen}
|
||||
>
|
||||
<ReportTask
|
||||
report={selectedTask}
|
||||
closeModal={closeModal}
|
||||
refetch={refetch}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className={`modal fade ${isModalOpenComment ? "show" : ""}`}
|
||||
tabIndex="-1"
|
||||
role="dialog"
|
||||
style={{ display: isModalOpenComment ? "block" : "none" }}
|
||||
aria-hidden={!isModalOpenComment}
|
||||
>
|
||||
<ReportTaskComments
|
||||
commentsData={comments}
|
||||
closeModal={closeCommentModal}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="container-xxl flex-grow-1 container-p-y">
|
||||
<Breadcrumb
|
||||
data={[
|
||||
{ label: "Home", link: "/dashboard" },
|
||||
{ label: "Attendance", link: null },
|
||||
{ label: "Daily Task", link: null },
|
||||
]}
|
||||
></Breadcrumb>
|
||||
<div className="card card-action mb-6">
|
||||
<div className="card-body">
|
||||
<div className="row">
|
||||
{/* <div className="col-12 text-end mb-1">
|
||||
<button
|
||||
type="button"
|
||||
className="link-button link-button-sm m-1"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#user-model"
|
||||
|
||||
<div className="card-body p-1 p-sm-2">
|
||||
<div className="row d-flex justify-content-between">
|
||||
<div className="col-6 text-start">
|
||||
<DateRangePicker onRangeChange={setDateRange} />
|
||||
</div>
|
||||
<div className="col-sm-3 col-6 text-end mb-1">
|
||||
<select
|
||||
name="DataTables_Table_0_length"
|
||||
aria-controls="DataTables_Table_0"
|
||||
className="form-select form-select-sm"
|
||||
value={selectedProject}
|
||||
onChange={(e) => dispatch(setProjectId(e.target.value))}
|
||||
aria-label=""
|
||||
>
|
||||
<i className="bx bx-plus-circle me-2"></i>
|
||||
Assign Employee
|
||||
</button>
|
||||
</div> */}
|
||||
{(project_lodaing || projects.length < 0) && (
|
||||
<option value="Loading..." disabled>
|
||||
Loading...
|
||||
</option>
|
||||
)}
|
||||
{!project_lodaing &&
|
||||
projects
|
||||
?.filter((project) =>
|
||||
LoggedUser?.projects?.map(Number).includes(project.id)
|
||||
)
|
||||
.map((project) => (
|
||||
<option value={project.id} key={project.id}>
|
||||
{project.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div className="table-responsive text-nowrap">
|
||||
{/* {employees && employees.length > 0 ? ( */}
|
||||
<table className="table ">
|
||||
<thead>
|
||||
<table className="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th>Activity</th>
|
||||
<th>Planned </th>
|
||||
<th>Compeleted</th>
|
||||
<th>Assign On</th>
|
||||
<th>Team</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="table-border-bottom-0">
|
||||
{TaskLists?.length === 0 && !task_loading && (
|
||||
<tr>
|
||||
<th>Sr</th>
|
||||
<th>Project Name</th>
|
||||
<th>Target</th>
|
||||
<th>Employees</th>
|
||||
|
||||
|
||||
<th>Actions</th>
|
||||
<td colSpan={7} className="text-center">
|
||||
No Data Found
|
||||
</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="table-border-bottom-0">
|
||||
|
||||
<tr >
|
||||
)}
|
||||
|
||||
{task_loading && (
|
||||
<tr>
|
||||
<td colSpan={7} className="text-center">
|
||||
<p>Loading..</p>
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
|
||||
{TaskLists.map((task, index) => {
|
||||
const accordionId = `accordion-${index}`;
|
||||
return (
|
||||
<React.Fragment key={index}>
|
||||
{/* Main Row */}
|
||||
<tr>
|
||||
<td>
|
||||
<div className="d-flex justify-content-center align-items-center">
|
||||
|
||||
<div
|
||||
className="d-flex justify-content-center align-items-center"
|
||||
data-bs-toggle="collapse"
|
||||
data-bs-target={`#${accordionId}`}
|
||||
aria-expanded="false"
|
||||
aria-controls={accordionId}
|
||||
>
|
||||
<div className="d-flex flex-column">
|
||||
<a href="#" className="text-heading text-truncate">
|
||||
<span className="fw-medium ">
|
||||
1
|
||||
</span>
|
||||
<a
|
||||
href="#"
|
||||
className="text-heading text-truncate"
|
||||
>
|
||||
<i className="bx bx-chevron-right"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td className="flex-wrap">
|
||||
project Name project Name project Name
|
||||
{task.workItem.activityMaster.activityName ||
|
||||
"No Activity Name"}
|
||||
</td>
|
||||
|
||||
<td>
|
||||
80
|
||||
<td>{task.plannedTask || "NA"}</td>
|
||||
<td>{task.completedTask}</td>
|
||||
<td>{formatDate(task.assignmentDate)}</td>
|
||||
<td className="text-center">
|
||||
<div className="d-flex align-items-center avatar-group justify-content-center">
|
||||
{task.teamMembers.slice(0, 3).map((member) => (
|
||||
<div
|
||||
key={member.id}
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-html="true"
|
||||
data-popup="tooltip-custom"
|
||||
data-bs-placement="top"
|
||||
title={`${member.firstName} ${member.lastName}`}
|
||||
className="avatar avatar-xs"
|
||||
>
|
||||
{/* <img src="..." alt="Avatar" className="rounded-circle pull-up" /> */}
|
||||
<span className="avatar-initial rounded-circle bg-label-primary">
|
||||
{member?.firstName.slice(0, 1)}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
{task.teamMembers.length > 3 && (
|
||||
<div
|
||||
className="avatar avatar-xs"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-placement="bottom"
|
||||
title={`${task.teamMembers.length - 3} more`}
|
||||
>
|
||||
<span className="avatar-initial rounded-circle bg-label-secondary pull-up">
|
||||
+{task.teamMembers.length - 3}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</td>
|
||||
|
||||
<td> NA</td>
|
||||
|
||||
<td>
|
||||
|
||||
<button type="button" className="btn btn-xs btn-primary">Report</button>
|
||||
|
||||
<td className="text-center">
|
||||
<div className="d-flex justify-content-center">
|
||||
<button
|
||||
type="button"
|
||||
className={`btn btn-xs btn-primary ${
|
||||
task.completedTask > 0 ? "d-none" : ""
|
||||
}`}
|
||||
onClick={() => {
|
||||
selectTask(task);
|
||||
openModal();
|
||||
}}
|
||||
>
|
||||
Report
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-xs btn-primary ms-2"
|
||||
onClick={() => {
|
||||
setComment(task);
|
||||
openComment();
|
||||
}}
|
||||
>
|
||||
Comment
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{/* ))} */}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
|
||||
{/* Accordion Content */}
|
||||
<tr
|
||||
id={accordionId}
|
||||
className="accordion-collapse collapse"
|
||||
>
|
||||
<td colSpan={5}>
|
||||
<div className="row">
|
||||
<p>{task.subdata?.name}</p>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</React.Fragment>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
export default DailyTask
|
||||
</>
|
||||
);
|
||||
};
|
||||
export default DailyTask;
|
||||
|
||||
@ -6,10 +6,19 @@ import ProjectRepository from "../../repositories/ProjectRepository";
|
||||
import Breadcrumb from "../../components/common/Breadcrumb";
|
||||
import InfraPlanning from "../../components/Activities/InfraPlanning";
|
||||
import { cacheData, getCachedData } from "../../slices/apiDataManager";
|
||||
import { useProfile } from "../../hooks/useProfile";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { useProjectDetails, useProjects } from "../../hooks/useProjects";
|
||||
import { setProjectId } from "../../slices/localVariablesSlice";
|
||||
|
||||
var projectId;
|
||||
const TaskPlannng = () => {
|
||||
|
||||
const {profile} = useProfile();
|
||||
const {projects,loading:project_listLoader,error:projects_error} = useProjects();
|
||||
const dispatch = useDispatch();
|
||||
const selectedProject = useSelector((store)=>store.localVariables.projectId);
|
||||
|
||||
|
||||
const [project, setProject] = useState(null);
|
||||
const [projectDetails, setProjectDetails] = useState(null);
|
||||
@ -17,6 +26,10 @@ const TaskPlannng = () => {
|
||||
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState("");
|
||||
useEffect( () =>
|
||||
{
|
||||
dispatch(setProjectId(projects[0]?.id))
|
||||
},[projects])
|
||||
|
||||
const fetchActivities = async () => {
|
||||
try {
|
||||
@ -25,22 +38,12 @@ const TaskPlannng = () => {
|
||||
if (!activities_cache) {
|
||||
ActivityeRepository.getActivities()
|
||||
.then((response) => {
|
||||
setActivities(response);
|
||||
cacheData("activitiesMaster", response);
|
||||
setActivities(response.data);
|
||||
cacheData("activitiesMaster", response.data);
|
||||
})
|
||||
.catch((error) => {
|
||||
setError("Failed to fetch data.");
|
||||
});
|
||||
// api
|
||||
// .get("/api/task/activities")
|
||||
// .then((data) => {
|
||||
// setActivities(data);
|
||||
// dispatch(cacheApiResponse({ key: "activitiesMaster", data: data }));
|
||||
// })
|
||||
// .catch((error) => {
|
||||
// console.error(error);
|
||||
// setError("Failed to fetch data.");
|
||||
// });
|
||||
} else {
|
||||
setActivities(activities_cache);
|
||||
}
|
||||
@ -53,9 +56,9 @@ const TaskPlannng = () => {
|
||||
|
||||
const fetchData = async () => {
|
||||
try {
|
||||
const project_cache = getCachedData(`projectinfo-${1}`);
|
||||
const project_cache = getCachedData(`projectinfo-${selectedProject}`);
|
||||
if (!project_cache) {
|
||||
ProjectRepository.getProjectByprojectId(1)
|
||||
ProjectRepository.getProjectByprojectId(selectedProject)
|
||||
.then((response) => {
|
||||
setProjectDetails(response);
|
||||
setProject(response);
|
||||
@ -68,21 +71,6 @@ const TaskPlannng = () => {
|
||||
} else {
|
||||
setProjectDetails(project_cache);
|
||||
}
|
||||
|
||||
// api
|
||||
// .get(`/api/project/details/${projectId}`)
|
||||
// .then((data) => {
|
||||
// setProjectDetails(data);
|
||||
// setProject(data);
|
||||
// dispatch(
|
||||
// cacheApiResponse({ key: `projectinfo-${projectId}`, data: data })
|
||||
// );
|
||||
// setLoading(false);
|
||||
// })
|
||||
// .catch((error) => {
|
||||
// console.error(error);
|
||||
// setError("Failed to fetch data.");
|
||||
// });
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
setError("Failed to fetch data.");
|
||||
@ -98,14 +86,16 @@ const TaskPlannng = () => {
|
||||
};
|
||||
|
||||
const handleDataChange = (data) => {
|
||||
console.log("datachange")
|
||||
fetchData();
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
projectId =1
|
||||
fetchData();
|
||||
if((projects.length != 0)){
|
||||
fetchData();
|
||||
fetchActivities();
|
||||
}, []);
|
||||
}
|
||||
}, [selectedProject]);
|
||||
|
||||
|
||||
return (
|
||||
@ -121,7 +111,7 @@ const TaskPlannng = () => {
|
||||
<InfraPlanning
|
||||
data={projectDetails}
|
||||
activityMaster={activities}
|
||||
onDataChange={handleDataChange}
|
||||
onDataChange={handleDataChange}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
import React from "react";
|
||||
import { ComingSoonPage } from "../Misc/ComingSoonPage";
|
||||
|
||||
const ImageGallary = () => {
|
||||
return <div>Image Gallery</div>;
|
||||
return <ComingSoonPage></ComingSoonPage>;
|
||||
};
|
||||
|
||||
export default ImageGallary;
|
||||
|
||||
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"
|
||||
>
|
||||
<span className="app-brand-logo demo">
|
||||
<img src="/public/img/brand/marco.png" alt="sneat-logo" />
|
||||
<img src="/img/brand/marco.png" alt="sneat-logo" />
|
||||
</span>
|
||||
{/* <span className="app-brand-text demo text-body fw-bold">
|
||||
Sneat
|
||||
|
||||
@ -4,45 +4,54 @@ import { AuthWrapper } from "./AuthWrapper"
|
||||
import "./page-auth.css";
|
||||
import AuthRepository from "../../repositories/AuthRepository";
|
||||
import showToast from "../../services/toastService";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import {z} from "zod";
|
||||
|
||||
|
||||
const forgotPassSceham = z.object( {
|
||||
email: z.string().email(),
|
||||
} )
|
||||
|
||||
const ForgotPasswordPage = () => {
|
||||
const [ email, setEmail ] = useState( "" );
|
||||
|
||||
const[loding,setLoading] = useState(false)
|
||||
|
||||
const handleChange = (e) => {
|
||||
setEmail(e.target.value);
|
||||
};
|
||||
const {register,
|
||||
handleSubmit,
|
||||
formState: { errors },
|
||||
reset,
|
||||
getValues } = useForm( {
|
||||
resolver: zodResolver( forgotPassSceham ),
|
||||
defaultValues: {
|
||||
email:""
|
||||
}
|
||||
})
|
||||
|
||||
const handleSubmit = async ( e ) =>
|
||||
const onSubmit = async (data) =>
|
||||
{
|
||||
setLoading(true)
|
||||
e.preventDefault();
|
||||
try
|
||||
{
|
||||
const response = await AuthRepository.forgotPassword({email})
|
||||
setLoading(true)
|
||||
const response = await AuthRepository.forgotPassword(data)
|
||||
if ( response.data && response.success )
|
||||
{
|
||||
showToast( response.message, "success" )
|
||||
} else
|
||||
{
|
||||
showToast( response.message, "warning" )
|
||||
}
|
||||
reset()
|
||||
setLoading( false )
|
||||
setEmail("")
|
||||
} catch ( error )
|
||||
} catch ( err )
|
||||
{
|
||||
showToast( "User Not Found", "error" )
|
||||
|
||||
showToast( err.message, "error" )
|
||||
setLoading(false)
|
||||
}
|
||||
};
|
||||
}
|
||||
return (
|
||||
<AuthWrapper>
|
||||
<h4 className="mb-2">Forgot Password? 🔒</h4>
|
||||
<p className="mb-4">
|
||||
Enter your email and we'll send you instructions to reset your password
|
||||
</p>
|
||||
<form id="formAuthentication" className="mb-3" onSubmit={handleSubmit}>
|
||||
<form id="formAuthentication" className="mb-3" onSubmit={handleSubmit(onSubmit)}>
|
||||
<div className="mb-3">
|
||||
<label htmlFor="email" className="form-label">
|
||||
Email
|
||||
@ -52,11 +61,18 @@ const ForgotPasswordPage = () => {
|
||||
className="form-control"
|
||||
id="email"
|
||||
name="email"
|
||||
value={email}
|
||||
onChange={handleChange}
|
||||
{...register("email")}
|
||||
placeholder="Enter your email"
|
||||
autoFocus
|
||||
/>
|
||||
{errors.email && (
|
||||
<div
|
||||
className="danger-text text-start"
|
||||
style={{ fontSize: "12px" }}
|
||||
>
|
||||
{errors.email.message}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<button aria-label="Click me" className="btn btn-primary d-grid w-100">
|
||||
{loding ? "Please Wait...":"Send Reset Link"}
|
||||
|
||||
@ -5,49 +5,50 @@ import { useNavigate } from "react-router-dom";
|
||||
import "./page-auth.css";
|
||||
import AuthRepository from "../../repositories/AuthRepository";
|
||||
import showToast from "../../services/toastService";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { z } from "zod";
|
||||
|
||||
const loginScheam = z.object({
|
||||
username: z.string().email(),
|
||||
password: z.string().min(1, { message: "Password required" }),
|
||||
rememberMe: z.boolean(),
|
||||
});
|
||||
|
||||
const LoginPage = () => {
|
||||
const navigate = useNavigate();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [hidepass, setHidepass] = useState(true);
|
||||
|
||||
const [formData, setFormData] = useState({
|
||||
password: "",
|
||||
username: "",
|
||||
rememberMe: false,
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
formState: { errors },
|
||||
reset,
|
||||
getValues,
|
||||
} = useForm({
|
||||
resolver: zodResolver(loginScheam),
|
||||
});
|
||||
|
||||
const handleChange = (e) => {
|
||||
const { name, value, type, checked } = e.target;
|
||||
|
||||
setFormData((prevData) => ({
|
||||
...prevData,
|
||||
[name]: type === "checkbox" ? checked : value,
|
||||
}));
|
||||
};
|
||||
|
||||
const handleSubmit = async ( e ) =>
|
||||
{
|
||||
|
||||
e.preventDefault();
|
||||
const onSubmit = async (data) => {
|
||||
setLoading(true);
|
||||
|
||||
try {
|
||||
let data = {
|
||||
username: formData.username,
|
||||
password: formData.password,
|
||||
let userCredential = {
|
||||
username: data.username,
|
||||
password: data.password,
|
||||
};
|
||||
|
||||
const response = await AuthRepository.login(data);
|
||||
const response = await AuthRepository.login(userCredential);
|
||||
localStorage.setItem("jwtToken", response.data.token);
|
||||
localStorage.setItem( "refreshToken", response.data.refreshToken );
|
||||
localStorage.setItem("refreshToken", response.data.refreshToken);
|
||||
setLoading(false);
|
||||
navigate("/dashboard");
|
||||
} catch (err) {
|
||||
console.log("Unable to proceed. Please try again.");
|
||||
setLoading(false);
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<AuthWrapper>
|
||||
<h4 className="mb-2">Welcome to PMS!</h4>
|
||||
@ -55,8 +56,12 @@ const LoginPage = () => {
|
||||
Please sign-in to your account and start the adventure
|
||||
</p>
|
||||
|
||||
<form id="formAuthentication" className="mb-3" onSubmit={handleSubmit}>
|
||||
<div className="mb-3">
|
||||
<form
|
||||
id="formAuthentication"
|
||||
className="mb-3"
|
||||
onSubmit={handleSubmit(onSubmit)}
|
||||
>
|
||||
<div className="mb-2">
|
||||
<label htmlFor="username" className="form-label">
|
||||
Email or Username
|
||||
</label>
|
||||
@ -64,55 +69,77 @@ const LoginPage = () => {
|
||||
type="text"
|
||||
className="form-control"
|
||||
id="username"
|
||||
value={formData.name}
|
||||
onChange={handleChange}
|
||||
{...register("username")}
|
||||
name="username"
|
||||
placeholder="Enter your email or username"
|
||||
autoFocus
|
||||
/>
|
||||
{errors.username && (
|
||||
<div
|
||||
className="danger-text text-start"
|
||||
style={{ fontSize: "12px" }}
|
||||
>
|
||||
{errors.username.message}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="mb-3 form-password-toggle">
|
||||
<div className="d-flex justify-content-between">
|
||||
<div className="d-flex justify-content-center">
|
||||
<label className="form-label" htmlFor="password">
|
||||
Password
|
||||
</label>
|
||||
<Link
|
||||
aria-label="Go to Forgot Password Page"
|
||||
to="/auth/forgot-password"
|
||||
>
|
||||
<small>Forgot Password?</small>
|
||||
</Link>
|
||||
|
||||
</div>
|
||||
<div className="input-group input-group-merge">
|
||||
<input
|
||||
type="password"
|
||||
type={hidepass ? "password" : "text"}
|
||||
autoComplete="true"
|
||||
id="password"
|
||||
value={formData.password}
|
||||
onChange={handleChange}
|
||||
{...register("password")}
|
||||
className="form-control"
|
||||
name="password"
|
||||
placeholder="············"
|
||||
aria-describedby="password"
|
||||
/>
|
||||
<span className="input-group-text cursor-pointer"></span>
|
||||
<span
|
||||
className="input-group-text cursor-pointer"
|
||||
onClick={() => setHidepass(!hidepass)}
|
||||
>
|
||||
{hidepass ? (
|
||||
<i className="bx bx-hide"></i>
|
||||
) : (
|
||||
<i className="bx bx-show"></i>
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
{errors.password && (
|
||||
<div
|
||||
className="danger-text text-start"
|
||||
style={{ fontSize: "12px" }}
|
||||
>
|
||||
{errors.password.message}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="mb-3">
|
||||
<div className="form-check">
|
||||
<div className="mb-3 d-flex justify-content-between">
|
||||
<div className="form-check d-flex">
|
||||
<input
|
||||
className="form-check-input"
|
||||
type="checkbox"
|
||||
id="remember-me"
|
||||
name="rememberMe"
|
||||
checked={formData.rememberMe}
|
||||
onChange={handleChange}
|
||||
{...register("rememberMe")}
|
||||
/>
|
||||
<label className="form-check-label" htmlFor="remember-me">
|
||||
{" "}
|
||||
Remember Me{" "}
|
||||
<label className="form-check-label ms-2" >
|
||||
Remember Me
|
||||
</label>
|
||||
</div>
|
||||
<Link
|
||||
aria-label="Go to Forgot Password Page"
|
||||
to="/auth/forgot-password"
|
||||
>
|
||||
<span>Forgot Password?</span>
|
||||
</Link>
|
||||
</div>
|
||||
<div className="mb-3">
|
||||
<button
|
||||
|
||||
@ -4,68 +4,80 @@ import "./page-auth.css";
|
||||
import { AuthWrapper } from "./AuthWrapper";
|
||||
import showToast from "../../services/toastService";
|
||||
import AuthRepository from "../../repositories/AuthRepository";
|
||||
import { z } from 'zod';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { z } from "zod";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import {clearAllCache} from "../../slices/apiDataManager";
|
||||
import { clearAllCache } from "../../slices/apiDataManager";
|
||||
|
||||
const resetPasswordSchema = z.object( {
|
||||
email:z.string().email(),
|
||||
password: z
|
||||
.string()
|
||||
.min(8, 'Password must be at least 8 characters')
|
||||
.regex(/[A-Z]/, 'Password must contain at least one uppercase letter')
|
||||
.regex(/[a-z]/, 'Password must contain at least one lowercase letter')
|
||||
.regex(/\d/, 'Password must contain at least one number')
|
||||
.regex(/[!@#$%^&*()_+{}\[\]:;<>,.?~\\/-]/, 'Password must contain at least one special character'),
|
||||
|
||||
confirmPassword: z.string().min(8, 'Password must be at least 8 characters'),
|
||||
})
|
||||
.refine((data) => data.password === data.confirmPassword, {
|
||||
message: 'Passwords do not match',
|
||||
path: ['confirmPassword'],
|
||||
} );
|
||||
|
||||
const ResetPasswordPage = () =>
|
||||
{
|
||||
const [ searchParams ] = useSearchParams();
|
||||
const [loading,setLoading] = useState(false)
|
||||
const resetPasswordSchema = z
|
||||
.object({
|
||||
email: z.string().email(),
|
||||
password: z
|
||||
.string()
|
||||
.min(8, "Password must be at least 8 characters")
|
||||
.regex(/[A-Z]/, "Password must contain at least one uppercase letter")
|
||||
.regex(/[a-z]/, "Password must contain at least one lowercase letter")
|
||||
.regex(/\d/, "Password must contain at least one number")
|
||||
.regex(
|
||||
/[!@#$%^&*()_+{}\[\]:;<>,.?~\\/-]/,
|
||||
"Password must contain at least one special character"
|
||||
),
|
||||
|
||||
const token = searchParams.get('token');
|
||||
const navigate = useNavigate()
|
||||
|
||||
const {register,handleSubmit,formState: { errors }} = useForm({
|
||||
resolver:zodResolver(resetPasswordSchema)
|
||||
confirmPassword: z
|
||||
.string()
|
||||
.min(8, "Password must be at least 8 characters"),
|
||||
})
|
||||
.refine((data) => data.password === data.confirmPassword, {
|
||||
message: "Passwords do not match",
|
||||
path: ["confirmPassword"],
|
||||
});
|
||||
|
||||
const ResetPasswordPage = () => {
|
||||
const [searchParams] = useSearchParams();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [hidepass, setHidepass] = useState(true);
|
||||
|
||||
const token = searchParams.get("token");
|
||||
const navigate = useNavigate();
|
||||
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
formState: { errors },
|
||||
} = useForm({
|
||||
resolver: zodResolver(resetPasswordSchema),
|
||||
});
|
||||
|
||||
const onSubmitResetPassword = async (data) => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const { email, password, confirmPassword } = data;
|
||||
|
||||
const onSubmitResetPassword = async(data) => {
|
||||
try
|
||||
{
|
||||
setLoading(true)
|
||||
const {email, password, confirmPassword} = data;
|
||||
|
||||
let reqObject = {
|
||||
email,
|
||||
token: token,
|
||||
newPassword: password
|
||||
}
|
||||
let response = await AuthRepository.resetPassword( reqObject );
|
||||
showToast( "Password Reseted", "success" )
|
||||
clearAllCache()
|
||||
setLoading(false)
|
||||
navigate("/auth/login",{replace:true})
|
||||
} catch ( error )
|
||||
{
|
||||
setLoading(false)
|
||||
showToast("Token is expries or Invalid ","error")
|
||||
email,
|
||||
token: token,
|
||||
newPassword: password,
|
||||
};
|
||||
let response = await AuthRepository.resetPassword(reqObject);
|
||||
showToast("Password Reseted", "success");
|
||||
clearAllCache();
|
||||
setLoading(false);
|
||||
navigate("/auth/login", { replace: true });
|
||||
} catch (error) {
|
||||
setLoading(false);
|
||||
showToast("Token is expries or Invalid ", "error");
|
||||
}
|
||||
};
|
||||
return (
|
||||
<AuthWrapper>
|
||||
<h4 className="mb-2">Reset Password? 🔒</h4>
|
||||
<p className="mb-4">Enter your email and new password to update.</p>
|
||||
<form id="formAuthentication" className="mb-3" onSubmit={handleSubmit(onSubmitResetPassword)}>
|
||||
<form
|
||||
id="formAuthentication"
|
||||
className="mb-3"
|
||||
onSubmit={handleSubmit(onSubmitResetPassword)}
|
||||
>
|
||||
<div className="mb-3">
|
||||
<label htmlFor="email" className="form-label">
|
||||
Email
|
||||
@ -78,48 +90,106 @@ const ResetPasswordPage = () =>
|
||||
placeholder="Enter your email"
|
||||
autoFocus
|
||||
/>
|
||||
{errors.email && <div className="danger-text text-start" style={{fontSize:"12px"}}>{errors.email.message}</div>}
|
||||
{errors.email && (
|
||||
<div
|
||||
className="danger-text text-start"
|
||||
style={{ fontSize: "12px" }}
|
||||
>
|
||||
{errors.email.message}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="mb-3">
|
||||
<label htmlFor="email" className="form-label">
|
||||
New Password
|
||||
</label>
|
||||
<input
|
||||
type="password"
|
||||
autoComplete="true"
|
||||
id="password"
|
||||
className="form-control"
|
||||
name="password"
|
||||
{...register('password')}
|
||||
placeholder="············"
|
||||
aria-describedby="password"
|
||||
/>
|
||||
{errors.password && <div className="danger-text text-start" style={{fontSize:"12px"}}>{errors.password.message}</div>}
|
||||
</div>
|
||||
<div className=" mb-3">
|
||||
<label htmlFor="email" className="form-label">
|
||||
Repeat New Password
|
||||
</label>
|
||||
<input
|
||||
type="password"
|
||||
autoComplete="true"
|
||||
id="password"
|
||||
className="form-control"
|
||||
name="confirmPassword"
|
||||
{...register('confirmPassword')}
|
||||
placeholder="············"
|
||||
aria-describedby="password"
|
||||
/>
|
||||
{errors.confirmPassword && <div className="danger-text text-start" style={{fontSize:"12px"}}>{errors.confirmPassword.message}</div>}
|
||||
|
||||
<div className="mb-2 form-password-toggle">
|
||||
<div className="mt-2">
|
||||
<label htmlFor="email" className="form-label list-group-item">
|
||||
New Password
|
||||
</label>
|
||||
</div>
|
||||
<div className=" input-group input-group-merge">
|
||||
<input
|
||||
type={hidepass ? "password" : "text"}
|
||||
autoComplete="true"
|
||||
id="password"
|
||||
className="form-control"
|
||||
name="password"
|
||||
{...register("password")}
|
||||
placeholder="············"
|
||||
aria-describedby="password"
|
||||
/>
|
||||
<span
|
||||
className="input-group-text lcursor-pointer"
|
||||
onClick={() => setHidepass(!hidepass)}
|
||||
>
|
||||
{hidepass ? (
|
||||
<i className="bx bx-hide"></i>
|
||||
) : (
|
||||
<i className="bx bx-show"></i>
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
{errors.password && (
|
||||
<div
|
||||
className="danger-text text-start"
|
||||
style={{ fontSize: "12px" }}
|
||||
>
|
||||
{errors.password.message}
|
||||
</div>
|
||||
)}
|
||||
<div className="mt-2">
|
||||
{" "}
|
||||
<label htmlFor="email" className="form-label">
|
||||
Repeat New Password
|
||||
</label>{" "}
|
||||
</div>
|
||||
<div className=" input-group input-group-merge">
|
||||
<input
|
||||
type={hidepass ? "password" : "text"}
|
||||
autoComplete="true"
|
||||
id="password"
|
||||
className="form-control"
|
||||
name="confirmPassword"
|
||||
{...register("confirmPassword")}
|
||||
placeholder="············"
|
||||
aria-describedby="password"
|
||||
/>
|
||||
<span
|
||||
className="input-group-text cursor-pointer"
|
||||
onClick={() => setHidepass(!hidepass)}
|
||||
>
|
||||
{hidepass ? (
|
||||
<i className="bx bx-hide"></i>
|
||||
) : (
|
||||
<i className="bx bx-show"></i>
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
{errors.confirmPassword && (
|
||||
<div
|
||||
className="danger-text text-start"
|
||||
style={{ fontSize: "12px" }}
|
||||
>
|
||||
{errors.confirmPassword.message}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="mb-3 text-start ">
|
||||
<p className="p-0 m-0"style={{fontSize:'9px'}}>Password must be at least 8 characters</p>
|
||||
<p className="p-0 m-0" style={{fontSize:'9px'}}>Password must contain at least one uppercase letter</p>
|
||||
<p className="p-0 m-0" style={{fontSize:'9px'}}>Password must contain at least one number</p>
|
||||
<p className="p-0 m-0" style={{fontSize:'9px'}}>Password must contain at least one special character</p>
|
||||
<p className="p-0 m-0" style={{ fontSize: "9px" }}>
|
||||
Password must be at least 8 characters
|
||||
</p>
|
||||
<p className="p-0 m-0" style={{ fontSize: "9px" }}>
|
||||
Password must contain at least one uppercase letter
|
||||
</p>
|
||||
<p className="p-0 m-0" style={{ fontSize: "9px" }}>
|
||||
Password must contain at least one number
|
||||
</p>
|
||||
<p className="p-0 m-0" style={{ fontSize: "9px" }}>
|
||||
Password must contain at least one special character
|
||||
</p>
|
||||
</div>
|
||||
<button aria-label="Click me" className="btn btn-primary d-grid w-100">
|
||||
{loading ? "Please Wait...":"Update Password"}
|
||||
{loading ? "Please Wait..." : "Update Password"}
|
||||
</button>
|
||||
</form>
|
||||
<div className="text-center">
|
||||
|
||||
@ -4,100 +4,95 @@ import { Link, NavLink, useNavigate } from "react-router-dom";
|
||||
import Avatar from "../../components/common/Avatar";
|
||||
import Breadcrumb from "../../components/common/Breadcrumb";
|
||||
import ManageEmp from "../../components/Employee/ManageRole";
|
||||
import {useEmployeesAllOrByProjectId} from "../../hooks/useEmployees";
|
||||
import { useProjects } from "../../hooks/useProjects";
|
||||
import { useEmployeesAllOrByProjectId } from "../../hooks/useEmployees";
|
||||
import { useProjects } from "../../hooks/useProjects";
|
||||
import { useProfile } from "../../hooks/useProfile";
|
||||
import {hasUserPermission} from "../../utils/authUtils";
|
||||
import {MANAGE_EMPLOYEES} from "../../utils/constants";
|
||||
import {useHasUserPermission} from "../../hooks/useHasUserPermission";
|
||||
import { hasUserPermission } from "../../utils/authUtils";
|
||||
import { MANAGE_EMPLOYEES } from "../../utils/constants";
|
||||
import { useHasUserPermission } from "../../hooks/useHasUserPermission";
|
||||
|
||||
const EmployeeList = () =>
|
||||
{
|
||||
const {profile:loginUser}= useProfile()
|
||||
const [selectedProject, setSelectedProject] = useState("");
|
||||
const {projects, loading: projectLoading} = useProjects()
|
||||
const ManageEmployee = useHasUserPermission(MANAGE_EMPLOYEES)
|
||||
|
||||
const { profile: loginUser } = useProfile();
|
||||
const [selectedProject, setSelectedProject] = useState("");
|
||||
const { projects, loading: projectLoading } = useProjects();
|
||||
const ManageEmployee = useHasUserPermission(MANAGE_EMPLOYEES);
|
||||
|
||||
const {employees, loading,setLoading, error} = useEmployeesAllOrByProjectId( selectedProject );
|
||||
const [ projectsList, setProjectsList ] = useState(projects || [] );
|
||||
|
||||
const [employeeList,setEmployeeList] = useState([])
|
||||
const { employees, loading, setLoading, error } =
|
||||
useEmployeesAllOrByProjectId(selectedProject);
|
||||
const [projectsList, setProjectsList] = useState(projects || []);
|
||||
|
||||
const [employeeList, setEmployeeList] = useState([]);
|
||||
const [modelConfig, setModelConfig] = useState();
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [itemsPerPage] = useState(5);
|
||||
const [isCreateModalOpen, setIsCreateModalOpen ] = useState( false );
|
||||
const [searchText,setSearchText] = useState("")
|
||||
const [isCreateModalOpen, setIsCreateModalOpen] = useState(false);
|
||||
const [searchText, setSearchText] = useState("");
|
||||
const [filteredData, setFilteredData] = useState([]);
|
||||
const navigate = useNavigate()
|
||||
|
||||
const navigate = useNavigate();
|
||||
|
||||
const handleSearch = (e) => {
|
||||
const value = e.target.value.toLowerCase();
|
||||
setSearchText(value);
|
||||
|
||||
|
||||
if (!employeeList.length) return;
|
||||
|
||||
|
||||
const results = employeeList.filter((item) =>
|
||||
Object.values(item).some((field) =>
|
||||
field && field.toString().toLowerCase().includes(value)
|
||||
Object.values(item).some(
|
||||
(field) => field && field.toString().toLowerCase().includes(value)
|
||||
)
|
||||
);
|
||||
|
||||
|
||||
setFilteredData(results);
|
||||
};
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
setCurrentPage( 1 )
|
||||
|
||||
setCurrentPage(1);
|
||||
|
||||
if (!loading && Array.isArray(employees)) {
|
||||
setEmployeeList(employees);
|
||||
setFilteredData( employees );
|
||||
setFilteredData(employees);
|
||||
}
|
||||
|
||||
}, [loading, employees, selectedProject]);
|
||||
|
||||
|
||||
const displayData = searchText ? filteredData : employeeList
|
||||
const displayData = searchText ? filteredData : employeeList;
|
||||
const indexOfLastItem = currentPage * itemsPerPage;
|
||||
const indexOfFirstItem = indexOfLastItem - itemsPerPage;
|
||||
const currentItems = Array.isArray(displayData)
|
||||
? displayData.slice(indexOfFirstItem, indexOfLastItem)
|
||||
const currentItems = Array.isArray(displayData)
|
||||
? displayData.slice(indexOfFirstItem, indexOfLastItem)
|
||||
: [];
|
||||
|
||||
|
||||
const paginate = (pageNumber) => setCurrentPage(pageNumber);
|
||||
const totalPages = Array.isArray(displayData)
|
||||
? Math.ceil(displayData.length / itemsPerPage)
|
||||
: 0;
|
||||
|
||||
|
||||
|
||||
const openModal = () => {
|
||||
setIsCreateModalOpen(true);
|
||||
};
|
||||
const openModal = () => {
|
||||
setIsCreateModalOpen(true);
|
||||
};
|
||||
|
||||
const closeModal = () => {
|
||||
setIsCreateModalOpen(false);
|
||||
|
||||
const modalElement = document.getElementById("managerole-modal");
|
||||
if (modalElement) {
|
||||
modalElement.classList.remove("show");
|
||||
modalElement.style.display = "none";
|
||||
document.body.classList.remove("modal-open");
|
||||
document.querySelector(".modal-backdrop").remove();
|
||||
}
|
||||
};
|
||||
|
||||
const handleConfigData = (config) => {
|
||||
setModelConfig(config);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (modelConfig !== null) {
|
||||
openModal();
|
||||
}
|
||||
}, [ modelConfig, isCreateModalOpen ] );
|
||||
|
||||
const closeModal = () => {
|
||||
setIsCreateModalOpen(false);
|
||||
|
||||
const modalElement = document.getElementById("managerole-modal");
|
||||
if (modalElement) {
|
||||
modalElement.classList.remove("show");
|
||||
modalElement.style.display = "none";
|
||||
document.body.classList.remove("modal-open");
|
||||
document.querySelector(".modal-backdrop").remove();
|
||||
}
|
||||
};
|
||||
|
||||
const handleConfigData = (config) => {
|
||||
setModelConfig(config);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (modelConfig !== null) {
|
||||
openModal();
|
||||
}
|
||||
}, [modelConfig, isCreateModalOpen]);
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -126,32 +121,30 @@ const EmployeeList = () =>
|
||||
className="dataTables_length text-start"
|
||||
id="DataTables_Table_0_length"
|
||||
>
|
||||
<label>
|
||||
<label>
|
||||
<select
|
||||
id="project-select"
|
||||
onChange={(e)=>setSelectedProject(e.target.value)}
|
||||
name="DataTables_Table_0_length"
|
||||
aria-controls="DataTables_Table_0"
|
||||
className="form-select form-select-sm"
|
||||
value={selectedProject || ""}
|
||||
>
|
||||
{projectLoading ? (
|
||||
<option value="Loading" >
|
||||
Loading...
|
||||
</option>
|
||||
) : (
|
||||
<>
|
||||
id="project-select"
|
||||
onChange={(e) => setSelectedProject(e.target.value)}
|
||||
name="DataTables_Table_0_length"
|
||||
aria-controls="DataTables_Table_0"
|
||||
className="form-select form-select-sm"
|
||||
value={selectedProject || ""}
|
||||
>
|
||||
{projectLoading ? (
|
||||
<option value="Loading">Loading...</option>
|
||||
) : (
|
||||
<>
|
||||
<option value="">All Employees</option>
|
||||
{Array.isArray(projects) && projects.map((item) => (
|
||||
<option key={item.id} value={item.id}>
|
||||
{item.name}
|
||||
</option>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
{Array.isArray(projects) &&
|
||||
projects.map((item) => (
|
||||
<option key={item.id} value={item.id}>
|
||||
{item.name}
|
||||
</option>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</select>
|
||||
</label>
|
||||
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -224,8 +217,9 @@ const EmployeeList = () =>
|
||||
</li>
|
||||
</ul>
|
||||
<button
|
||||
|
||||
className={`btn btn-sm add-new btn-primary ${!ManageEmployee && 'd-none'}`}
|
||||
className={`btn btn-sm add-new btn-primary ${
|
||||
!ManageEmployee && "d-none"
|
||||
}`}
|
||||
tabIndex="0"
|
||||
type="button"
|
||||
>
|
||||
@ -265,6 +259,17 @@ const EmployeeList = () =>
|
||||
>
|
||||
Name
|
||||
</th>
|
||||
<th
|
||||
className="sorting sorting_desc d-none d-sm-table-cell"
|
||||
tabIndex="0"
|
||||
aria-controls="DataTables_Table_0"
|
||||
rowSpan="1"
|
||||
colSpan="1"
|
||||
aria-label="User: activate to sort column ascending"
|
||||
aria-sort="descending"
|
||||
>
|
||||
Email
|
||||
</th>
|
||||
<th
|
||||
className="sorting sorting_desc d-none d-sm-table-cell"
|
||||
tabIndex="0"
|
||||
@ -287,7 +292,7 @@ const EmployeeList = () =>
|
||||
>
|
||||
Role
|
||||
</th>
|
||||
|
||||
|
||||
<th
|
||||
className="sorting d-none d-md-table-cell"
|
||||
tabIndex="0"
|
||||
@ -309,7 +314,9 @@ const EmployeeList = () =>
|
||||
Status
|
||||
</th>
|
||||
<th
|
||||
className={`sorting_disabled ${!ManageEmployee && 'd-none'}`}
|
||||
className={`sorting_disabled ${
|
||||
!ManageEmployee && "d-none"
|
||||
}`}
|
||||
rowSpan="1"
|
||||
colSpan="1"
|
||||
style={{ width: "50px" }}
|
||||
@ -319,105 +326,140 @@ const EmployeeList = () =>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{loading && <tr>
|
||||
<tbody>
|
||||
{loading && (
|
||||
<tr>
|
||||
<td colSpan={8}>
|
||||
<p>Loading...</p>
|
||||
</td>
|
||||
</tr>}
|
||||
{( !loading && employeeList?.length === 0 ) && <td colSpan={8}>Not Data Found </td>}
|
||||
{( !loading && employeeList && currentItems.length === 0 && employeeList.length !==0 ) && <td colSpan={8}><small className="muted">'{searchText}' employee not found</small> </td>}
|
||||
|
||||
{(currentItems && !loading) && currentItems.sort((a, b) => b.id - a.id).map((item) => (
|
||||
<tr className="odd" key={item.id}>
|
||||
<td className="sorting_1" colSpan={2}>
|
||||
<div className="d-flex justify-content-start align-items-center user-name">
|
||||
<Avatar
|
||||
firstName={item.firstName}
|
||||
lastName={item.lastName}
|
||||
></Avatar>
|
||||
<div className="d-flex flex-column">
|
||||
<a
|
||||
|
||||
onClick={()=>navigate(`/employee/${item.id}?for=account`)}
|
||||
className="text-heading text-truncate cursor-pointer"
|
||||
>
|
||||
<span className="fw-medium">
|
||||
{item.firstName} {item.lastName}
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td className="text-start d-none d-sm-table-cell">
|
||||
<span className="text-truncate">
|
||||
<i className="bx bxs-phone-call text-primary me-2"></i>
|
||||
{item.phoneNumber}
|
||||
</span>
|
||||
</td>
|
||||
<td className=" d-none d-sm-table-cell text-start">
|
||||
<span className="text-truncate">
|
||||
<i className="bx bxs-wrench text-success me-2"></i>{item.jobRole || "Not Assign Yet"}
|
||||
</span>
|
||||
</td>
|
||||
|
||||
<td className=" d-none d-md-table-cell">
|
||||
{moment(item.joiningDate).format("DD-MMM-YYYY")}
|
||||
</td>
|
||||
<td>
|
||||
<span
|
||||
className="badge bg-label-success"
|
||||
text-capitalized=""
|
||||
>
|
||||
Active
|
||||
</span>
|
||||
</td>
|
||||
<td className={`d-flex justify-content-end justify-content-sm-center ${!ManageEmployee && 'd-none'} `}>
|
||||
<div className="d-flex align-items-center ">
|
||||
<a
|
||||
className={`btn btn-icon dropdown-toggle hide-arrow`}
|
||||
data-bs-toggle="dropdown"
|
||||
>
|
||||
<i className="bx bx-dots-vertical-rounded bx-md"></i>
|
||||
</a>
|
||||
|
||||
<div className="dropdown-menu dropdown-menu-end m-0">
|
||||
{" "}
|
||||
<a
|
||||
|
||||
onClick={()=> navigate(`/employee/${item.id}`)}
|
||||
className="dropdown-item"
|
||||
>
|
||||
View
|
||||
</a>
|
||||
|
||||
<Link
|
||||
className={`dropdown-item `}
|
||||
to={`/employee/manage/${item.id}`}
|
||||
>
|
||||
Edit
|
||||
</Link>
|
||||
|
||||
<a className="dropdown-item">
|
||||
Suspend
|
||||
</a>
|
||||
<a
|
||||
className="dropdown-item"
|
||||
type="button"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#managerole-modal"
|
||||
onClick={() => {
|
||||
handleConfigData(item.id);
|
||||
}}
|
||||
>
|
||||
Manage Role
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
) )}
|
||||
|
||||
)}
|
||||
{!loading && employeeList?.length === 0 && (
|
||||
<td
|
||||
colSpan={8}
|
||||
style={{ paddingTop: "20px", textAlign: "center" }}
|
||||
>
|
||||
No Data Found
|
||||
</td>
|
||||
)}
|
||||
{!loading &&
|
||||
employeeList &&
|
||||
currentItems.length === 0 &&
|
||||
employeeList.length !== 0 && (
|
||||
<td colSpan={8}>
|
||||
<small className="muted">
|
||||
'{searchText}' employee not found
|
||||
</small>{" "}
|
||||
</td>
|
||||
)}
|
||||
|
||||
{currentItems &&
|
||||
!loading &&
|
||||
currentItems
|
||||
.sort((a, b) => b.id - a.id)
|
||||
.map((item) => (
|
||||
<tr className="odd" key={item.id}>
|
||||
<td className="sorting_1" colSpan={2}>
|
||||
<div className="d-flex justify-content-start align-items-center user-name">
|
||||
<Avatar
|
||||
firstName={item.firstName}
|
||||
lastName={item.lastName}
|
||||
></Avatar>
|
||||
<div className="d-flex flex-column">
|
||||
<a
|
||||
onClick={() =>
|
||||
navigate(
|
||||
`/employee/${item.id}?for=account`
|
||||
)
|
||||
}
|
||||
className="text-heading text-truncate cursor-pointer"
|
||||
>
|
||||
<span className="fw-medium">
|
||||
{item.firstName} {item.lastName}
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td className="text-start d-none d-sm-table-cell">
|
||||
{item.email ? (
|
||||
<span className="text-truncate">
|
||||
<i className="bx bxs-envelope text-primary me-2"></i>
|
||||
|
||||
{item.email}
|
||||
</span>
|
||||
) : (
|
||||
<span className="text-truncate text-italic">
|
||||
NA
|
||||
</span>
|
||||
)}
|
||||
</td>
|
||||
<td className="text-start d-none d-sm-table-cell">
|
||||
<span className="text-truncate">
|
||||
<i className="bx bxs-phone-call text-primary me-2"></i>
|
||||
{item.phoneNumber}
|
||||
</span>
|
||||
</td>
|
||||
<td className=" d-none d-sm-table-cell text-start">
|
||||
<span className="text-truncate">
|
||||
<i className="bx bxs-wrench text-success me-2"></i>
|
||||
{item.jobRole || "Not Assign Yet"}
|
||||
</span>
|
||||
</td>
|
||||
|
||||
<td className=" d-none d-md-table-cell">
|
||||
{moment(item.joiningDate).format("DD-MMM-YYYY")}
|
||||
</td>
|
||||
<td>
|
||||
<span
|
||||
className="badge bg-label-success"
|
||||
text-capitalized=""
|
||||
>
|
||||
Active
|
||||
</span>
|
||||
</td>
|
||||
{ManageEmployee && (
|
||||
<td className="text-end">
|
||||
<div className="dropdown">
|
||||
<button
|
||||
className="btn btn-icon dropdown-toggle hide-arrow"
|
||||
data-bs-toggle="dropdown"
|
||||
>
|
||||
<i className="bx bx-dots-vertical-rounded bx-md"></i>
|
||||
</button>
|
||||
<div className="dropdown-menu dropdown-menu-end">
|
||||
<button
|
||||
onClick={() =>
|
||||
navigate(`/employee/${item.id}`)
|
||||
}
|
||||
className="dropdown-item"
|
||||
>
|
||||
View
|
||||
</button>
|
||||
<Link
|
||||
to={`/employee/manage/${item.id}`}
|
||||
className="dropdown-item"
|
||||
>
|
||||
Edit
|
||||
</Link>
|
||||
<button className="dropdown-item">
|
||||
Suspend
|
||||
</button>
|
||||
<button
|
||||
className="dropdown-item"
|
||||
type="button"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#managerole-modal"
|
||||
onClick={() => handleConfigData(item.id)}
|
||||
>
|
||||
Manage Role
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
)}
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
|
||||
@ -8,7 +8,7 @@ import { getCachedData } from "../../slices/apiDataManager";
|
||||
import { useEmployeeProfile, useEmployees, useEmployeesByProject } from "../../hooks/useEmployees";
|
||||
import { useSelector } from "react-redux";
|
||||
import EmployeeRepository from "../../repositories/EmployeeRepository";
|
||||
|
||||
import { ComingSoonPage } from "../Misc/ComingSoonPage";
|
||||
|
||||
const EmployeeProfile = () => {
|
||||
|
||||
@ -56,35 +56,32 @@ const EmployeeProfile = () => {
|
||||
switch (activePill) {
|
||||
case "account": {
|
||||
return (
|
||||
<div className="row">
|
||||
{/* <div className="col-xl-4 col-lg-5 col-md-5"> */}
|
||||
<p>Account</p>
|
||||
{/* </div> */}
|
||||
|
||||
</div>
|
||||
<>
|
||||
<ComingSoonPage/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
case "attendance": {
|
||||
return (
|
||||
<div className="row">
|
||||
<div className="col-lg-12 col-xl-12">
|
||||
{/* Teams */}
|
||||
<p>attendance component</p>
|
||||
{/* Teams */}
|
||||
</div>
|
||||
</div>
|
||||
<>
|
||||
<ComingSoonPage/>
|
||||
</>
|
||||
);
|
||||
break;
|
||||
}
|
||||
case "activities": {
|
||||
return (
|
||||
<p>activites components</p>
|
||||
<>
|
||||
<ComingSoonPage/>
|
||||
</>
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
return <div>Select a pill to display content here.</div>;
|
||||
return <>
|
||||
<ComingSoonPage/>
|
||||
</>;
|
||||
}
|
||||
};
|
||||
|
||||
@ -160,7 +157,7 @@ const EmployeeProfile = () => {
|
||||
</li>
|
||||
<li className="d-flex align-items-start test-start mb-2">
|
||||
<span className={`${currentEmployee?.permanentAddress ? "" : "ms-4"}`}>
|
||||
{currentEmployee?.peramnentAddress}
|
||||
{currentEmployee?.permanentAddress}
|
||||
</span>
|
||||
</li>
|
||||
|
||||
|
||||
@ -53,10 +53,8 @@ const MasterPage = () => {
|
||||
|
||||
};
|
||||
|
||||
|
||||
const {data:masterData, loading,error , RecallApi} = useMaster();
|
||||
|
||||
|
||||
|
||||
const handleSearch = (e) => {
|
||||
const value = e.target.value.toLowerCase();
|
||||
|
||||
@ -7,7 +7,7 @@ const MasterTable = ( {data, columns, loading, handleModalData} ) =>
|
||||
{
|
||||
const hasMasterPermission = useHasUserPermission(MANAGE_MASTER)
|
||||
const selectedMaster = useSelector((store)=>store.localVariables.selectedMaster)
|
||||
const hiddenColumns = ["id", "featurePermission","tenant","tenantId"];
|
||||
const hiddenColumns = ["id", "featurePermission","tenant","tenantId","checkLists"];
|
||||
|
||||
const safeData = Array.isArray(data) ? data : [];
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
@ -28,7 +28,7 @@ const MasterTable = ( {data, columns, loading, handleModalData} ) =>
|
||||
.map((col) => ({
|
||||
...col,
|
||||
label:
|
||||
col.key === "role" || col.key === "module" || col.key === "status" ? "Name" : col.label,
|
||||
col.key === "role" || col.key === "activityName" || col.key === "status" ? "Name" : col.label,
|
||||
}));
|
||||
|
||||
return (
|
||||
@ -45,8 +45,8 @@ const MasterTable = ( {data, columns, loading, handleModalData} ) =>
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th>{ selectedMaster} Name</th>
|
||||
<th>{selectedMaster } Description</th>
|
||||
<th> Name</th>
|
||||
<th>{selectedMaster} {selectedMaster === "Activity" ? "Unit":"Description" }</th>
|
||||
<th className={` ${!hasMasterPermission && 'd-none'}`}>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
@ -1,11 +1,8 @@
|
||||
import React from "react";
|
||||
import { ComingSoonPage } from "../Misc/ComingSoonPage";
|
||||
|
||||
const Inventory = () => {
|
||||
return <div>Inventory
|
||||
<div>
|
||||
<p>ddj</p>
|
||||
</div>
|
||||
</div>;
|
||||
return <ComingSoonPage></ComingSoonPage>;
|
||||
};
|
||||
|
||||
export default Inventory;
|
||||
|
||||
@ -14,50 +14,34 @@ import Breadcrumb from "../../components/common/Breadcrumb";
|
||||
import { cacheData, getCachedData } from "../../slices/apiDataManager";
|
||||
import ProjectRepository from "../../repositories/ProjectRepository";
|
||||
import { ActivityeRepository } from "../../repositories/MastersRepository";
|
||||
|
||||
import "./ProjectDetails.css";
|
||||
import {useEmployeesByProjectAllocated} from "../../hooks/useProjects";
|
||||
import {useEmployeesByProjectAllocated, useProjectDetails} from "../../hooks/useProjects";
|
||||
import {useDispatch} from "react-redux";
|
||||
import {setProjectId} from "../../slices/localVariablesSlice";
|
||||
import { ComingSoonPage } from "../Misc/ComingSoonPage";
|
||||
|
||||
|
||||
const ProjectDetails = () => {
|
||||
let { projectId } = useParams();
|
||||
|
||||
let {projectId} = useParams();
|
||||
const {projects_Details,loading:projectLoading,error:ProjectError} = useProjectDetails(projectId)
|
||||
const dispatch = useDispatch()
|
||||
const [project, setProject] = useState(null);
|
||||
const [ projectDetails, setProjectDetails ] = useState( null );
|
||||
const [activities, setActivities] = useState(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState("");
|
||||
|
||||
const fetchActivities = async () => {
|
||||
const [ error, setError ] = useState( "" );
|
||||
|
||||
const activities_cache = getCachedData("activitiesMaster");
|
||||
|
||||
if (!activities_cache) {
|
||||
ActivityeRepository.getActivities()
|
||||
.then((response) => {
|
||||
setActivities(response);
|
||||
cacheData("activitiesMaster", response);
|
||||
})
|
||||
.catch((error) => {
|
||||
setError("Failed to fetch data.");
|
||||
});
|
||||
|
||||
} else {
|
||||
setActivities(activities_cache);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
const fetchData = async () => {
|
||||
|
||||
const project_cache = getCachedData(`projectinfo-${projectId}`);
|
||||
if (!project_cache) {
|
||||
const project_cache = getCachedData("projectInfo");
|
||||
if (!project_cache || project_cache?.projectId !== projectId) {
|
||||
ProjectRepository.getProjectByprojectId(projectId)
|
||||
.then( ( response ) =>
|
||||
{
|
||||
setProjectDetails(response);
|
||||
setProject(response);
|
||||
cacheData( `projectinfo-${ projectId }`, response );
|
||||
setProjectDetails( response.data );
|
||||
setProject( response.data );
|
||||
cacheData("projectInfo", {projectId,data: response.data} );
|
||||
setLoading(false)
|
||||
})
|
||||
.catch((error) => {
|
||||
@ -66,13 +50,14 @@ const ProjectDetails = () => {
|
||||
setLoading(false)
|
||||
});
|
||||
} else {
|
||||
setProjectDetails( project_cache );
|
||||
setProject( project_cache );
|
||||
setProjectDetails( project_cache.data );
|
||||
setProject( project_cache.data );
|
||||
setLoading(false)
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
const [activePill, setActivePill] = useState("profile");
|
||||
|
||||
|
||||
@ -86,7 +71,7 @@ const ProjectDetails = () => {
|
||||
|
||||
|
||||
const renderContent = () => {
|
||||
if (loading) return <Loader></Loader>;
|
||||
if (projectLoading) return <Loader></Loader>;
|
||||
switch (activePill) {
|
||||
case "profile": {
|
||||
return (
|
||||
@ -98,7 +83,7 @@ const ProjectDetails = () => {
|
||||
</div>
|
||||
<div className="col-xl-4 col-lg-5 col-md-5">
|
||||
{/* Profile Overview */}
|
||||
<ProjectOverview project={ project} />
|
||||
<ProjectOverview project={projectId} />
|
||||
{/* Profile Overview */}
|
||||
</div>
|
||||
</div>
|
||||
@ -120,7 +105,6 @@ const ProjectDetails = () => {
|
||||
return (
|
||||
<ProjectInfra
|
||||
data={projectDetails}
|
||||
activityMaster={activities}
|
||||
onDataChange={handleDataChange}
|
||||
></ProjectInfra>
|
||||
);
|
||||
@ -130,7 +114,6 @@ const ProjectDetails = () => {
|
||||
return (
|
||||
<WorkPlan
|
||||
data={projectDetails}
|
||||
activityMaster={activities}
|
||||
onDataChange={handleDataChange}
|
||||
></WorkPlan>
|
||||
);
|
||||
@ -147,18 +130,19 @@ const ProjectDetails = () => {
|
||||
}
|
||||
|
||||
default:
|
||||
return <div>Select a pill to display content here.</div>;
|
||||
return <ComingSoonPage></ComingSoonPage>;
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchData();
|
||||
fetchActivities();
|
||||
}, []);
|
||||
dispatch(setProjectId(projectId))
|
||||
setProject( projects_Details )
|
||||
setProjectDetails(projects_Details)
|
||||
}, [projects_Details,projectId]);
|
||||
|
||||
return (
|
||||
|
||||
<>
|
||||
{}
|
||||
<div className="container-xxl flex-grow-1 container-p-y">
|
||||
<Breadcrumb
|
||||
data={[
|
||||
@ -169,8 +153,8 @@ const ProjectDetails = () => {
|
||||
></Breadcrumb>
|
||||
|
||||
<div className="row">
|
||||
{loading && <p>Loading....</p>}
|
||||
{!loading && <ProjectBanner project_data={project} ></ProjectBanner>}
|
||||
{projectLoading && <p>Loading....</p>}
|
||||
{(!projectLoading && project) && <ProjectBanner project_data={project} ></ProjectBanner>}
|
||||
</div>
|
||||
|
||||
<div className="row">
|
||||
|
||||
@ -14,6 +14,7 @@ import {MANAGE_PROJECT} from "../../utils/constants";
|
||||
const ProjectList = () =>
|
||||
{
|
||||
|
||||
|
||||
const {profile: loginUser} = useProfile();
|
||||
const [showModal, setShowModal] = useState(false);
|
||||
const {projects, loading, error, refetch} = useProjects();
|
||||
@ -23,7 +24,7 @@ const ProjectList = () =>
|
||||
const[HasManageProject,setHasManageProject] = useState(HasManageProjectPermission)
|
||||
const dispatch = useDispatch();
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [itemsPerPage] = useState(5);
|
||||
const [itemsPerPage] = useState(6);
|
||||
|
||||
|
||||
const handleShow = () => setShowModal(true);
|
||||
@ -84,7 +85,20 @@ const ProjectList = () =>
|
||||
? Math.ceil(projectList.length / itemsPerPage)
|
||||
: 0;
|
||||
|
||||
const statusMap = {
|
||||
1: { label: 'active', priority: 1 },
|
||||
2: { label: 'hold', priority: 2 },
|
||||
3: { label: 'inactive', priority: 3 },
|
||||
4: { label: 'complete', priority: 4 }
|
||||
};
|
||||
|
||||
|
||||
const sortedProjects = [...currentItems].sort((a, b) => {
|
||||
const aPriority = statusMap[+a.projectStatusId]?.priority ?? 99;
|
||||
const bPriority = statusMap[+b.projectStatusId]?.priority ?? 99;
|
||||
return aPriority - bPriority;
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
@ -118,11 +132,7 @@ const ProjectList = () =>
|
||||
{" "}
|
||||
<button
|
||||
type="button"
|
||||
className={`btn btn-sm btn-primary ${
|
||||
HasManageProject
|
||||
? ""
|
||||
: "d-none"
|
||||
}`}
|
||||
className={`btn btn-xs btn-primary ${!HasManageProject && 'd-none' }`}
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#create-project-model"
|
||||
onClick={handleShow}
|
||||
@ -154,8 +164,10 @@ const ProjectList = () =>
|
||||
|
||||
<div className="row">
|
||||
{loading && <p className="text-center">Loading...</p>}
|
||||
|
||||
|
||||
{currentItems &&
|
||||
currentItems.sort((a, b) => b.id - a.id).map((item) => (
|
||||
sortedProjects.map((item) => (
|
||||
<ProjectCard projectData={item} key={item.id}></ProjectCard>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
import React from "react";
|
||||
import { ComingSoonPage } from "../Misc/ComingSoonPage";
|
||||
|
||||
const Reports = () => {
|
||||
return <div>Reports</div>;
|
||||
return <ComingSoonPage></ComingSoonPage>;
|
||||
};
|
||||
|
||||
export default Reports;
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
import React from "react";
|
||||
import { ComingSoonPage } from "../Misc/ComingSoonPage";
|
||||
|
||||
const Connect = () => {
|
||||
return <div>Connect</div>;
|
||||
return <ComingSoonPage></ComingSoonPage>;
|
||||
};
|
||||
|
||||
export default Connect;
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
import React from "react";
|
||||
import { ComingSoonPage } from "../Misc/ComingSoonPage";
|
||||
|
||||
const Documentation = () => {
|
||||
return <div>Documentation</div>;
|
||||
return <ComingSoonPage></ComingSoonPage>;
|
||||
};
|
||||
|
||||
export default Documentation;
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
import React from "react";
|
||||
import { ComingSoonPage } from "../Misc/ComingSoonPage";
|
||||
|
||||
const Support = () => {
|
||||
return <div>Support</div>;
|
||||
return <ComingSoonPage></ComingSoonPage>;
|
||||
};
|
||||
|
||||
export default Support;
|
||||
|
||||
@ -5,7 +5,8 @@ const AttendanceRepository = {
|
||||
markAttendance:(data)=>api.post("/api/attendance/record",data),
|
||||
getAttendance:(id)=>api.get(`api/attendance/project/team?projectId=${id}`),
|
||||
getAttendanceFilteredByDate:(id,date)=>api.get(`api/attendance/project/team?projectId=${id+"&date="+date}`),
|
||||
getAttendanceLogs:(id)=>api.get(`api/attendance/log/attendance/${id}`)
|
||||
getAttendanceLogs: ( id ) => api.get( `api/attendance/log/attendance/${ id }` ),
|
||||
getRegularizeList: (id)=> api.get(`api/attendance/regularize?projectId=${id}`)
|
||||
|
||||
}
|
||||
|
||||
|
||||
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),
|
||||
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),
|
||||
// updateProject: (data) => api.post("/api/project/update", data),
|
||||
|
||||
manageProjectAllocation: (data) => api.post("/api/project/allocation", data),
|
||||
manageProjectAllocation: ( data ) => api.post( "/api/project/allocation", data ),
|
||||
|
||||
manageProjectInfra: (data) => api.post("/api/project/manage-infra", data),
|
||||
manageProjectTasks: (data) => api.post("/api/project/manage-infra", data),
|
||||
manageProjectTasks: (data) => api.post("/api/project/task", data),
|
||||
|
||||
updateProject: (id, data) => api.put(`/api/project/update/${id}`, data),
|
||||
deleteProject: (id) => api.delete(`/projects/${id}`),
|
||||
};
|
||||
|
||||
export const TasksRepository = {
|
||||
assignTask: ( data ) => api.post( "/api/task/assign", data ),
|
||||
reportTak:(data)=>api.post("/api/task/report",data)
|
||||
}
|
||||
export default ProjectRepository;
|
||||
|
||||
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 AttendanceRepository from '../../repositories/AttendanceRepository';
|
||||
import {clearCacheKey} from '../apiDataManager';
|
||||
|
||||
// Fetch attendance data
|
||||
export const fetchAttendanceData = createAsyncThunk(
|
||||
@ -17,10 +18,11 @@ export const fetchAttendanceData = createAsyncThunk(
|
||||
// This method for marking attendance if a date filter is applied
|
||||
export const markAttendance = createAsyncThunk(
|
||||
'attendanceLogs/markAttendance', // Updated action type prefix
|
||||
async (formData, thunkAPI) => {
|
||||
async ( formData, thunkAPI ) =>
|
||||
{
|
||||
try {
|
||||
let newRecordAttendance = {
|
||||
id: formData.id || null,
|
||||
Id: formData.id || null,
|
||||
comment: formData.description,
|
||||
employeeID: formData.employeeId,
|
||||
projectID: formData.projectId,
|
||||
@ -32,7 +34,8 @@ export const markAttendance = createAsyncThunk(
|
||||
image: null,
|
||||
};
|
||||
|
||||
const response = await AttendanceRepository.markAttendance(newRecordAttendance);
|
||||
const response = await AttendanceRepository.markAttendance( newRecordAttendance );
|
||||
clearCacheKey("AttendanceLogs")
|
||||
return response.data; // Return the newly created or updated record
|
||||
} catch (error) {
|
||||
return thunkAPI.rejectWithValue(error.message);
|
||||
|
||||
@ -1,19 +1,24 @@
|
||||
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
|
||||
import AttendanceRepository from '../../repositories/AttendanceRepository';
|
||||
import {clearCacheKey} from '../apiDataManager';
|
||||
|
||||
export const markCurrentAttendance = createAsyncThunk(
|
||||
'attendanceCurrentDate/markAttendance',
|
||||
async (formData, { getState, dispatch, rejectWithValue }) => {
|
||||
async ( formData, {getState, dispatch, rejectWithValue} ) =>
|
||||
{
|
||||
|
||||
const { projectId } = getState().localVariables
|
||||
try {
|
||||
try
|
||||
{
|
||||
|
||||
// Create the new attendance record
|
||||
const newRecordAttendance = {
|
||||
id: null,
|
||||
Id: formData.id || null,
|
||||
comment: formData.description,
|
||||
employeeID: formData.employeeId,
|
||||
projectId: projectId,
|
||||
date: new Date().toISOString(),
|
||||
markTime: formData.time,
|
||||
markTime: formData.markTime,
|
||||
latitude: formData.latitude.toString(),
|
||||
longitude: formData.longitude.toString(),
|
||||
action: formData.action,
|
||||
@ -22,6 +27,7 @@ export const markCurrentAttendance = createAsyncThunk(
|
||||
|
||||
const response = await AttendanceRepository.markAttendance(newRecordAttendance);
|
||||
const markedAttendance = response.data
|
||||
clearCacheKey("AttendanceLogs")
|
||||
return markedAttendance;
|
||||
|
||||
} catch (error) {
|
||||
|
||||
@ -3,9 +3,9 @@ import { createSlice } from "@reduxjs/toolkit";
|
||||
const localVariablesSlice = createSlice({
|
||||
name: "localVariables",
|
||||
initialState: {
|
||||
selectedMaster:"Role",
|
||||
selectedMaster:"Application Role",
|
||||
regularizationCount:0,
|
||||
projectId:5,
|
||||
projectId:1,
|
||||
|
||||
},
|
||||
reducers: {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user