Compare commits
No commits in common. "9dddba4e3016f3a2390759cb30a370a1e37df6c6" and "18390e9368eb8d4407d1ca6f058b4f1fe3a78faa" have entirely different histories.
9dddba4e30
...
18390e9368
@ -45,7 +45,6 @@
|
|||||||
|
|
||||||
<link rel="stylesheet" href="/assets/vendor/libs/animate-css/animate.css" />
|
<link rel="stylesheet" href="/assets/vendor/libs/animate-css/animate.css" />
|
||||||
<link rel="stylesheet" href="/assets/vendor/libs/sweetalert2/sweetalert2.css" />
|
<link rel="stylesheet" href="/assets/vendor/libs/sweetalert2/sweetalert2.css" />
|
||||||
<link rel="stylesheet" href="/assets/vendor/libs/spinkit/spinkit.css" />
|
|
||||||
|
|
||||||
<!-- Helpers -->
|
<!-- Helpers -->
|
||||||
<script src="/assets/vendor/js/helpers.js"></script>
|
<script src="/assets/vendor/js/helpers.js"></script>
|
||||||
|
|||||||
3
public/assets/vendor/css/core.css
vendored
3
public/assets/vendor/css/core.css
vendored
@ -18609,9 +18609,6 @@ li:not(:first-child) .dropdown-item,
|
|||||||
.min-vh-100 {
|
.min-vh-100 {
|
||||||
min-height: 100vh !important;
|
min-height: 100vh !important;
|
||||||
}
|
}
|
||||||
.page-min-h{
|
|
||||||
min-height: 70vh !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.flex-fill {
|
.flex-fill {
|
||||||
flex: 1 1 auto !important;
|
flex: 1 1 auto !important;
|
||||||
|
|||||||
837
public/assets/vendor/libs/spinkit/spinkit.css
vendored
837
public/assets/vendor/libs/spinkit/spinkit.css
vendored
@ -1,837 +0,0 @@
|
|||||||
/* Config */
|
|
||||||
:root {
|
|
||||||
--sk-size: 40px;
|
|
||||||
--sk-color: #ff3e1d;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Utility class for centering */
|
|
||||||
.sk-center {
|
|
||||||
margin: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Plane
|
|
||||||
|
|
||||||
<div class="sk-plane"></div>
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
.sk-plane {
|
|
||||||
width: var(--sk-size);
|
|
||||||
height: var(--sk-size);
|
|
||||||
background-color: var(--sk-color);
|
|
||||||
animation: sk-plane 1.2s infinite ease-in-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes sk-plane {
|
|
||||||
0% {
|
|
||||||
transform: perspective(120px) rotateX(0deg) rotateY(0deg);
|
|
||||||
}
|
|
||||||
50% {
|
|
||||||
transform: perspective(120px) rotateX(-180.1deg) rotateY(0deg);
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
transform: perspective(120px) rotateX(-180deg) rotateY(-179.9deg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/* Chase
|
|
||||||
|
|
||||||
<div class="sk-chase">
|
|
||||||
<div class="sk-chase-dot"></div>
|
|
||||||
<div class="sk-chase-dot"></div>
|
|
||||||
<div class="sk-chase-dot"></div>
|
|
||||||
<div class="sk-chase-dot"></div>
|
|
||||||
<div class="sk-chase-dot"></div>
|
|
||||||
<div class="sk-chase-dot"></div>
|
|
||||||
</div>
|
|
||||||
*/
|
|
||||||
.sk-chase {
|
|
||||||
width: var(--sk-size);
|
|
||||||
height: var(--sk-size);
|
|
||||||
position: relative;
|
|
||||||
animation: sk-chase 2.5s infinite linear both;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sk-chase-dot {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
position: absolute;
|
|
||||||
left: 0;
|
|
||||||
top: 0;
|
|
||||||
animation: sk-chase-dot 2s infinite ease-in-out both;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sk-chase-dot:before {
|
|
||||||
content: "";
|
|
||||||
display: block;
|
|
||||||
width: 25%;
|
|
||||||
height: 25%;
|
|
||||||
background-color: var(--sk-color);
|
|
||||||
border-radius: 100%;
|
|
||||||
animation: sk-chase-dot-before 2s infinite ease-in-out both;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sk-chase-dot:nth-child(1) {
|
|
||||||
animation-delay: -1.1s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sk-chase-dot:nth-child(2) {
|
|
||||||
animation-delay: -1s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sk-chase-dot:nth-child(3) {
|
|
||||||
animation-delay: -0.9s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sk-chase-dot:nth-child(4) {
|
|
||||||
animation-delay: -0.8s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sk-chase-dot:nth-child(5) {
|
|
||||||
animation-delay: -0.7s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sk-chase-dot:nth-child(6) {
|
|
||||||
animation-delay: -0.6s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sk-chase-dot:nth-child(1):before {
|
|
||||||
animation-delay: -1.1s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sk-chase-dot:nth-child(2):before {
|
|
||||||
animation-delay: -1s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sk-chase-dot:nth-child(3):before {
|
|
||||||
animation-delay: -0.9s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sk-chase-dot:nth-child(4):before {
|
|
||||||
animation-delay: -0.8s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sk-chase-dot:nth-child(5):before {
|
|
||||||
animation-delay: -0.7s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sk-chase-dot:nth-child(6):before {
|
|
||||||
animation-delay: -0.6s;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes sk-chase {
|
|
||||||
100% {
|
|
||||||
transform: rotate(360deg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@keyframes sk-chase-dot {
|
|
||||||
80%, 100% {
|
|
||||||
transform: rotate(360deg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@keyframes sk-chase-dot-before {
|
|
||||||
50% {
|
|
||||||
transform: scale(0.4);
|
|
||||||
}
|
|
||||||
100%, 0% {
|
|
||||||
transform: scale(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/* Bounce
|
|
||||||
|
|
||||||
<div class="sk-bounce">
|
|
||||||
<div class="sk-bounce-dot"></div>
|
|
||||||
<div class="sk-bounce-dot"></div>
|
|
||||||
</div>
|
|
||||||
*/
|
|
||||||
.sk-bounce {
|
|
||||||
width: var(--sk-size);
|
|
||||||
height: var(--sk-size);
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sk-bounce-dot {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
border-radius: 50%;
|
|
||||||
background-color: var(--sk-color);
|
|
||||||
opacity: 0.6;
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
animation: sk-bounce 2s infinite cubic-bezier(0.455, 0.03, 0.515, 0.955);
|
|
||||||
}
|
|
||||||
|
|
||||||
.sk-bounce-dot:nth-child(2) {
|
|
||||||
animation-delay: -1s;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes sk-bounce {
|
|
||||||
0%, 100% {
|
|
||||||
transform: scale(0);
|
|
||||||
}
|
|
||||||
45%, 55% {
|
|
||||||
transform: scale(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/* Wave
|
|
||||||
|
|
||||||
<div class="sk-wave">
|
|
||||||
<div class="sk-wave-rect"></div>
|
|
||||||
<div class="sk-wave-rect"></div>
|
|
||||||
<div class="sk-wave-rect"></div>
|
|
||||||
<div class="sk-wave-rect"></div>
|
|
||||||
<div class="sk-wave-rect"></div>
|
|
||||||
</div>
|
|
||||||
*/
|
|
||||||
.sk-wave {
|
|
||||||
width: var(--sk-size);
|
|
||||||
height: var(--sk-size);
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sk-wave-rect {
|
|
||||||
background-color: var(--sk-color);
|
|
||||||
height: 100%;
|
|
||||||
width: 15%;
|
|
||||||
animation: sk-wave 1.2s infinite ease-in-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sk-wave-rect:nth-child(1) {
|
|
||||||
animation-delay: -1.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sk-wave-rect:nth-child(2) {
|
|
||||||
animation-delay: -1.1s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sk-wave-rect:nth-child(3) {
|
|
||||||
animation-delay: -1s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sk-wave-rect:nth-child(4) {
|
|
||||||
animation-delay: -0.9s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sk-wave-rect:nth-child(5) {
|
|
||||||
animation-delay: -0.8s;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes sk-wave {
|
|
||||||
0%, 40%, 100% {
|
|
||||||
transform: scaleY(0.4);
|
|
||||||
}
|
|
||||||
20% {
|
|
||||||
transform: scaleY(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/* Pulse
|
|
||||||
|
|
||||||
<div class="sk-pulse"></div>
|
|
||||||
*/
|
|
||||||
.sk-pulse {
|
|
||||||
width: var(--sk-size);
|
|
||||||
height: var(--sk-size);
|
|
||||||
background-color: var(--sk-color);
|
|
||||||
border-radius: 100%;
|
|
||||||
animation: sk-pulse 1.2s infinite cubic-bezier(0.455, 0.03, 0.515, 0.955);
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes sk-pulse {
|
|
||||||
0% {
|
|
||||||
transform: scale(0);
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
transform: scale(1);
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/* Flow
|
|
||||||
|
|
||||||
<div class="sk-flow">
|
|
||||||
<div class="sk-flow-dot"></div>
|
|
||||||
<div class="sk-flow-dot"></div>
|
|
||||||
<div class="sk-flow-dot"></div>
|
|
||||||
</div>
|
|
||||||
*/
|
|
||||||
.sk-flow {
|
|
||||||
width: calc(var(--sk-size) * 1.3);
|
|
||||||
height: calc(var(--sk-size) * 1.3);
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sk-flow-dot {
|
|
||||||
width: 25%;
|
|
||||||
height: 25%;
|
|
||||||
background-color: var(--sk-color);
|
|
||||||
border-radius: 50%;
|
|
||||||
animation: sk-flow 1.4s cubic-bezier(0.455, 0.03, 0.515, 0.955) 0s infinite both;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sk-flow-dot:nth-child(1) {
|
|
||||||
animation-delay: -0.3s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sk-flow-dot:nth-child(2) {
|
|
||||||
animation-delay: -0.15s;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes sk-flow {
|
|
||||||
0%, 80%, 100% {
|
|
||||||
transform: scale(0.3);
|
|
||||||
}
|
|
||||||
40% {
|
|
||||||
transform: scale(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/* Swing
|
|
||||||
|
|
||||||
<div class="sk-swing">
|
|
||||||
<div class="sk-swing-dot"></div>
|
|
||||||
<div class="sk-swing-dot"></div>
|
|
||||||
</div>
|
|
||||||
*/
|
|
||||||
.sk-swing {
|
|
||||||
width: var(--sk-size);
|
|
||||||
height: var(--sk-size);
|
|
||||||
position: relative;
|
|
||||||
animation: sk-swing 1.8s infinite linear;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sk-swing-dot {
|
|
||||||
width: 45%;
|
|
||||||
height: 45%;
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
margin: auto;
|
|
||||||
background-color: var(--sk-color);
|
|
||||||
border-radius: 100%;
|
|
||||||
animation: sk-swing-dot 2s infinite ease-in-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sk-swing-dot:nth-child(2) {
|
|
||||||
top: auto;
|
|
||||||
bottom: 0;
|
|
||||||
animation-delay: -1s;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes sk-swing {
|
|
||||||
100% {
|
|
||||||
transform: rotate(360deg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@keyframes sk-swing-dot {
|
|
||||||
0%, 100% {
|
|
||||||
transform: scale(0.2);
|
|
||||||
}
|
|
||||||
50% {
|
|
||||||
transform: scale(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/* Circle
|
|
||||||
|
|
||||||
<div class="sk-circle">
|
|
||||||
<div class="sk-circle-dot"></div>
|
|
||||||
<div class="sk-circle-dot"></div>
|
|
||||||
<div class="sk-circle-dot"></div>
|
|
||||||
<div class="sk-circle-dot"></div>
|
|
||||||
<div class="sk-circle-dot"></div>
|
|
||||||
<div class="sk-circle-dot"></div>
|
|
||||||
<div class="sk-circle-dot"></div>
|
|
||||||
<div class="sk-circle-dot"></div>
|
|
||||||
<div class="sk-circle-dot"></div>
|
|
||||||
<div class="sk-circle-dot"></div>
|
|
||||||
<div class="sk-circle-dot"></div>
|
|
||||||
<div class="sk-circle-dot"></div>
|
|
||||||
</div>
|
|
||||||
*/
|
|
||||||
.sk-circle {
|
|
||||||
width: var(--sk-size);
|
|
||||||
height: var(--sk-size);
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sk-circle-dot {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
position: absolute;
|
|
||||||
left: 0;
|
|
||||||
top: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sk-circle-dot:before {
|
|
||||||
content: "";
|
|
||||||
display: block;
|
|
||||||
width: 15%;
|
|
||||||
height: 15%;
|
|
||||||
background-color: var(--sk-color);
|
|
||||||
border-radius: 100%;
|
|
||||||
animation: sk-circle 1.2s infinite ease-in-out both;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sk-circle-dot:nth-child(1) {
|
|
||||||
transform: rotate(30deg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.sk-circle-dot:nth-child(2) {
|
|
||||||
transform: rotate(60deg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.sk-circle-dot:nth-child(3) {
|
|
||||||
transform: rotate(90deg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.sk-circle-dot:nth-child(4) {
|
|
||||||
transform: rotate(120deg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.sk-circle-dot:nth-child(5) {
|
|
||||||
transform: rotate(150deg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.sk-circle-dot:nth-child(6) {
|
|
||||||
transform: rotate(180deg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.sk-circle-dot:nth-child(7) {
|
|
||||||
transform: rotate(210deg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.sk-circle-dot:nth-child(8) {
|
|
||||||
transform: rotate(240deg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.sk-circle-dot:nth-child(9) {
|
|
||||||
transform: rotate(270deg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.sk-circle-dot:nth-child(10) {
|
|
||||||
transform: rotate(300deg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.sk-circle-dot:nth-child(11) {
|
|
||||||
transform: rotate(330deg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.sk-circle-dot:nth-child(1):before {
|
|
||||||
animation-delay: -1.1s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sk-circle-dot:nth-child(2):before {
|
|
||||||
animation-delay: -1s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sk-circle-dot:nth-child(3):before {
|
|
||||||
animation-delay: -0.9s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sk-circle-dot:nth-child(4):before {
|
|
||||||
animation-delay: -0.8s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sk-circle-dot:nth-child(5):before {
|
|
||||||
animation-delay: -0.7s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sk-circle-dot:nth-child(6):before {
|
|
||||||
animation-delay: -0.6s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sk-circle-dot:nth-child(7):before {
|
|
||||||
animation-delay: -0.5s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sk-circle-dot:nth-child(8):before {
|
|
||||||
animation-delay: -0.4s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sk-circle-dot:nth-child(9):before {
|
|
||||||
animation-delay: -0.3s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sk-circle-dot:nth-child(10):before {
|
|
||||||
animation-delay: -0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sk-circle-dot:nth-child(11):before {
|
|
||||||
animation-delay: -0.1s;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes sk-circle {
|
|
||||||
0%, 80%, 100% {
|
|
||||||
transform: scale(0);
|
|
||||||
}
|
|
||||||
40% {
|
|
||||||
transform: scale(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/* Circle Fade
|
|
||||||
|
|
||||||
<div class="sk-circle-fade">
|
|
||||||
<div class="sk-circle-fade-dot"></div>
|
|
||||||
<div class="sk-circle-fade-dot"></div>
|
|
||||||
<div class="sk-circle-fade-dot"></div>
|
|
||||||
<div class="sk-circle-fade-dot"></div>
|
|
||||||
<div class="sk-circle-fade-dot"></div>
|
|
||||||
<div class="sk-circle-fade-dot"></div>
|
|
||||||
<div class="sk-circle-fade-dot"></div>
|
|
||||||
<div class="sk-circle-fade-dot"></div>
|
|
||||||
<div class="sk-circle-fade-dot"></div>
|
|
||||||
<div class="sk-circle-fade-dot"></div>
|
|
||||||
<div class="sk-circle-fade-dot"></div>
|
|
||||||
<div class="sk-circle-fade-dot"></div>
|
|
||||||
</div>
|
|
||||||
*/
|
|
||||||
.sk-circle-fade {
|
|
||||||
width: var(--sk-size);
|
|
||||||
height: var(--sk-size);
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sk-circle-fade-dot {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
position: absolute;
|
|
||||||
left: 0;
|
|
||||||
top: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sk-circle-fade-dot:before {
|
|
||||||
content: "";
|
|
||||||
display: block;
|
|
||||||
width: 15%;
|
|
||||||
height: 15%;
|
|
||||||
background-color: var(--sk-color);
|
|
||||||
border-radius: 100%;
|
|
||||||
animation: sk-circle-fade 1.2s infinite ease-in-out both;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sk-circle-fade-dot:nth-child(1) {
|
|
||||||
transform: rotate(30deg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.sk-circle-fade-dot:nth-child(2) {
|
|
||||||
transform: rotate(60deg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.sk-circle-fade-dot:nth-child(3) {
|
|
||||||
transform: rotate(90deg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.sk-circle-fade-dot:nth-child(4) {
|
|
||||||
transform: rotate(120deg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.sk-circle-fade-dot:nth-child(5) {
|
|
||||||
transform: rotate(150deg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.sk-circle-fade-dot:nth-child(6) {
|
|
||||||
transform: rotate(180deg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.sk-circle-fade-dot:nth-child(7) {
|
|
||||||
transform: rotate(210deg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.sk-circle-fade-dot:nth-child(8) {
|
|
||||||
transform: rotate(240deg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.sk-circle-fade-dot:nth-child(9) {
|
|
||||||
transform: rotate(270deg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.sk-circle-fade-dot:nth-child(10) {
|
|
||||||
transform: rotate(300deg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.sk-circle-fade-dot:nth-child(11) {
|
|
||||||
transform: rotate(330deg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.sk-circle-fade-dot:nth-child(1):before {
|
|
||||||
animation-delay: -1.1s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sk-circle-fade-dot:nth-child(2):before {
|
|
||||||
animation-delay: -1s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sk-circle-fade-dot:nth-child(3):before {
|
|
||||||
animation-delay: -0.9s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sk-circle-fade-dot:nth-child(4):before {
|
|
||||||
animation-delay: -0.8s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sk-circle-fade-dot:nth-child(5):before {
|
|
||||||
animation-delay: -0.7s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sk-circle-fade-dot:nth-child(6):before {
|
|
||||||
animation-delay: -0.6s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sk-circle-fade-dot:nth-child(7):before {
|
|
||||||
animation-delay: -0.5s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sk-circle-fade-dot:nth-child(8):before {
|
|
||||||
animation-delay: -0.4s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sk-circle-fade-dot:nth-child(9):before {
|
|
||||||
animation-delay: -0.3s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sk-circle-fade-dot:nth-child(10):before {
|
|
||||||
animation-delay: -0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sk-circle-fade-dot:nth-child(11):before {
|
|
||||||
animation-delay: -0.1s;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes sk-circle-fade {
|
|
||||||
0%, 39%, 100% {
|
|
||||||
opacity: 0;
|
|
||||||
transform: scale(0.6);
|
|
||||||
}
|
|
||||||
40% {
|
|
||||||
opacity: 1;
|
|
||||||
transform: scale(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/* Grid
|
|
||||||
|
|
||||||
<div class="sk-grid">
|
|
||||||
<div class="sk-grid-cube"></div>
|
|
||||||
<div class="sk-grid-cube"></div>
|
|
||||||
<div class="sk-grid-cube"></div>
|
|
||||||
<div class="sk-grid-cube"></div>
|
|
||||||
<div class="sk-grid-cube"></div>
|
|
||||||
<div class="sk-grid-cube"></div>
|
|
||||||
<div class="sk-grid-cube"></div>
|
|
||||||
<div class="sk-grid-cube"></div>
|
|
||||||
<div class="sk-grid-cube"></div>
|
|
||||||
</div>
|
|
||||||
*/
|
|
||||||
.sk-grid {
|
|
||||||
width: var(--sk-size);
|
|
||||||
height: var(--sk-size);
|
|
||||||
/* Cube positions
|
|
||||||
* 1 2 3
|
|
||||||
* 4 5 6
|
|
||||||
* 7 8 9
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
|
|
||||||
.sk-grid-cube {
|
|
||||||
width: 33.33%;
|
|
||||||
height: 33.33%;
|
|
||||||
background-color: var(--sk-color);
|
|
||||||
float: left;
|
|
||||||
animation: sk-grid 1.3s infinite ease-in-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sk-grid-cube:nth-child(1) {
|
|
||||||
animation-delay: 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sk-grid-cube:nth-child(2) {
|
|
||||||
animation-delay: 0.3s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sk-grid-cube:nth-child(3) {
|
|
||||||
animation-delay: 0.4s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sk-grid-cube:nth-child(4) {
|
|
||||||
animation-delay: 0.1s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sk-grid-cube:nth-child(5) {
|
|
||||||
animation-delay: 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sk-grid-cube:nth-child(6) {
|
|
||||||
animation-delay: 0.3s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sk-grid-cube:nth-child(7) {
|
|
||||||
animation-delay: 0s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sk-grid-cube:nth-child(8) {
|
|
||||||
animation-delay: 0.1s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sk-grid-cube:nth-child(9) {
|
|
||||||
animation-delay: 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes sk-grid {
|
|
||||||
0%, 70%, 100% {
|
|
||||||
transform: scale3D(1, 1, 1);
|
|
||||||
}
|
|
||||||
35% {
|
|
||||||
transform: scale3D(0, 0, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/* Fold
|
|
||||||
|
|
||||||
<div class="sk-fold">
|
|
||||||
<div class="sk-fold-cube"></div>
|
|
||||||
<div class="sk-fold-cube"></div>
|
|
||||||
<div class="sk-fold-cube"></div>
|
|
||||||
<div class="sk-fold-cube"></div>
|
|
||||||
</div>
|
|
||||||
*/
|
|
||||||
.sk-fold {
|
|
||||||
width: var(--sk-size);
|
|
||||||
height: var(--sk-size);
|
|
||||||
position: relative;
|
|
||||||
transform: rotateZ(45deg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.sk-fold-cube {
|
|
||||||
float: left;
|
|
||||||
width: 50%;
|
|
||||||
height: 50%;
|
|
||||||
position: relative;
|
|
||||||
transform: scale(1.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.sk-fold-cube:before {
|
|
||||||
content: "";
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
background-color: var(--sk-color);
|
|
||||||
animation: sk-fold 2.4s infinite linear both;
|
|
||||||
transform-origin: 100% 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sk-fold-cube:nth-child(2) {
|
|
||||||
transform: scale(1.1) rotateZ(90deg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.sk-fold-cube:nth-child(4) {
|
|
||||||
transform: scale(1.1) rotateZ(180deg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.sk-fold-cube:nth-child(3) {
|
|
||||||
transform: scale(1.1) rotateZ(270deg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.sk-fold-cube:nth-child(2):before {
|
|
||||||
animation-delay: 0.3s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sk-fold-cube:nth-child(4):before {
|
|
||||||
animation-delay: 0.6s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sk-fold-cube:nth-child(3):before {
|
|
||||||
animation-delay: 0.9s;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes sk-fold {
|
|
||||||
0%, 10% {
|
|
||||||
transform: perspective(140px) rotateX(-180deg);
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
25%, 75% {
|
|
||||||
transform: perspective(140px) rotateX(0deg);
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
90%, 100% {
|
|
||||||
transform: perspective(140px) rotateY(180deg);
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/* Wander
|
|
||||||
|
|
||||||
<div class="sk-wander">
|
|
||||||
<div class="sk-wander-cube"></div>
|
|
||||||
<div class="sk-wander-cube"></div>
|
|
||||||
<div class="sk-wander-cube"></div>
|
|
||||||
<div class="sk-wander-cube"></div>
|
|
||||||
</div>
|
|
||||||
*/
|
|
||||||
.sk-wander {
|
|
||||||
width: var(--sk-size);
|
|
||||||
height: var(--sk-size);
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sk-wander-cube {
|
|
||||||
background-color: var(--sk-color);
|
|
||||||
width: 20%;
|
|
||||||
height: 20%;
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
--sk-wander-distance: calc(var(--sk-size) * 0.75);
|
|
||||||
animation: sk-wander 2s ease-in-out -2s infinite both;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sk-wander-cube:nth-child(2) {
|
|
||||||
animation-delay: -0.5s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sk-wander-cube:nth-child(3) {
|
|
||||||
animation-delay: -1s;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes sk-wander {
|
|
||||||
0% {
|
|
||||||
transform: rotate(0deg);
|
|
||||||
}
|
|
||||||
25% {
|
|
||||||
transform: translateX(var(--sk-wander-distance)) rotate(-90deg) scale(0.6);
|
|
||||||
}
|
|
||||||
50% { /* Make FF rotate in the right direction */
|
|
||||||
transform: translateX(var(--sk-wander-distance)) translateY(var(--sk-wander-distance)) rotate(-179deg);
|
|
||||||
}
|
|
||||||
50.1% {
|
|
||||||
transform: translateX(var(--sk-wander-distance)) translateY(var(--sk-wander-distance)) rotate(-180deg);
|
|
||||||
}
|
|
||||||
75% {
|
|
||||||
transform: translateX(0) translateY(var(--sk-wander-distance)) rotate(-270deg) scale(0.6);
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
transform: rotate(-360deg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
:root {
|
|
||||||
--sk-size: 30px;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
.sk-wave {
|
|
||||||
width: 40px;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sk-fading-circle .sk-circle {
|
|
||||||
margin-top: 0;
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sk-wave {
|
|
||||||
width: 40px;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sk-fading-circle .sk-circle {
|
|
||||||
margin-top: 0;
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
@ -16,7 +16,6 @@ import {useProfile} from "../../hooks/useProfile";
|
|||||||
import {refreshData, setProjectId} from "../../slices/localVariablesSlice";
|
import {refreshData, setProjectId} from "../../slices/localVariablesSlice";
|
||||||
import InfraTable from "../Project/Infrastructure/InfraTable";
|
import InfraTable from "../Project/Infrastructure/InfraTable";
|
||||||
import { useSelectedproject } from "../../slices/apiDataManager";
|
import { useSelectedproject } from "../../slices/apiDataManager";
|
||||||
import Loader from "../common/Loader";
|
|
||||||
|
|
||||||
|
|
||||||
const InfraPlanning = () =>
|
const InfraPlanning = () =>
|
||||||
@ -52,7 +51,7 @@ const InfraPlanning = () =>
|
|||||||
{(ApprovedTaskRights || ReportTaskRights) ? (
|
{(ApprovedTaskRights || ReportTaskRights) ? (
|
||||||
<div className="align-items-center">
|
<div className="align-items-center">
|
||||||
<div className="row ">
|
<div className="row ">
|
||||||
{isLoading && (<Loader/> )}
|
{isLoading && ( <p>Loading...</p> )}
|
||||||
{( !isLoading && projectInfra?.length === 0 ) && ( <p>No Result Found</p> )}
|
{( !isLoading && projectInfra?.length === 0 ) && ( <p>No Result Found</p> )}
|
||||||
{(!isLoading && projectInfra?.length > 0) && (<InfraTable buildings={projectInfra} projectId={selectedProject}/>)}
|
{(!isLoading && projectInfra?.length > 0) && (<InfraTable buildings={projectInfra} projectId={selectedProject}/>)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -202,4 +202,3 @@ export const ReportTask = ({ report, closeModal }) => {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
export default ReportTask;
|
|
||||||
@ -22,7 +22,6 @@ const EmpAttendance = ({ employee }) => {
|
|||||||
data = [],
|
data = [],
|
||||||
isLoading: loading,
|
isLoading: loading,
|
||||||
isFetching,
|
isFetching,
|
||||||
isError,
|
|
||||||
error,
|
error,
|
||||||
refetch,
|
refetch,
|
||||||
} = useAttendanceByEmployee(employee, dateRange.startDate, dateRange.endDate);
|
} = useAttendanceByEmployee(employee, dateRange.startDate, dateRange.endDate);
|
||||||
@ -146,7 +145,7 @@ const EmpAttendance = ({ employee }) => {
|
|||||||
</div>
|
</div>
|
||||||
<div className="table-responsive text-nowrap">
|
<div className="table-responsive text-nowrap">
|
||||||
{!loading && data.length === 0 && <span>No employee logs</span>}
|
{!loading && data.length === 0 && <span>No employee logs</span>}
|
||||||
{isError && <div className="text-center">{error.message}</div>}
|
{error && <div className="text-center">{error}</div>}
|
||||||
{loading && !data && <div className="text-center">Loading...</div>}
|
{loading && !data && <div className="text-center">Loading...</div>}
|
||||||
{data && data.length > 0 && (
|
{data && data.length > 0 && (
|
||||||
<table className="table mb-0">
|
<table className="table mb-0">
|
||||||
|
|||||||
@ -34,6 +34,7 @@ const ManageExpense = ({ closeModal, expenseToEdit = null }) => {
|
|||||||
isLoading,
|
isLoading,
|
||||||
error: ExpenseErrorLoad,
|
error: ExpenseErrorLoad,
|
||||||
} = useExpense(expenseToEdit);
|
} = useExpense(expenseToEdit);
|
||||||
|
console.log(data)
|
||||||
const [ExpenseType, setExpenseType] = useState();
|
const [ExpenseType, setExpenseType] = useState();
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const {
|
const {
|
||||||
|
|||||||
@ -1,36 +0,0 @@
|
|||||||
|
|
||||||
const SkeletonLine = ({ height = 50, width = "100%", className = "" }) => (
|
|
||||||
<div
|
|
||||||
className={`skeleton mb-2 ${className}`}
|
|
||||||
style={{
|
|
||||||
height,
|
|
||||||
width,
|
|
||||||
}}
|
|
||||||
></div>
|
|
||||||
);
|
|
||||||
export const MenuItemSkeleton = ({ hasSubmenu = false, submenuCount = 3 }) => {
|
|
||||||
return (
|
|
||||||
<li className="menu-item">
|
|
||||||
<div className="menu-link d-flex align-items-center gap-2 ">
|
|
||||||
{/* icon placeholder */}
|
|
||||||
<SkeletonLine height={25} width="25px" className="rounded" />
|
|
||||||
{/* text placeholder */}
|
|
||||||
<SkeletonLine height={25} width="100%" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Submenu skeletons */}
|
|
||||||
{hasSubmenu && (
|
|
||||||
<ul className="menu-sub mt-1 ms-4">
|
|
||||||
{[...Array(submenuCount)].map((_, idx) => (
|
|
||||||
<li key={idx} className="menu-item">
|
|
||||||
<div className="menu-link d-flex align-items-center gap-2">
|
|
||||||
<SkeletonLine height={20} width="20px" className="rounded" />
|
|
||||||
<SkeletonLine height={24} width="100px" />
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
)}
|
|
||||||
</li>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@ -2,12 +2,10 @@ import React from "react";
|
|||||||
import { Link, NavLink, useLocation, useNavigate } from "react-router-dom";
|
import { Link, NavLink, useLocation, useNavigate } from "react-router-dom";
|
||||||
import menuData from "../../data/menuData.json";
|
import menuData from "../../data/menuData.json";
|
||||||
import { getCachedProfileData } from "../../slices/apiDataManager";
|
import { getCachedProfileData } from "../../slices/apiDataManager";
|
||||||
import { useSidBarMenu } from "../../hooks/useProfile";
|
|
||||||
import { MenuItemSkeleton } from "./MenuItemSkeleton";
|
|
||||||
|
|
||||||
const Sidebar = () => {
|
const Sidebar = () => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { data, isError, isLoading, isFetched, error } = useSidBarMenu();
|
|
||||||
return (
|
return (
|
||||||
<aside
|
<aside
|
||||||
id="layout-menu"
|
id="layout-menu"
|
||||||
@ -35,49 +33,33 @@ const Sidebar = () => {
|
|||||||
<div className="menu-inner-shadow"></div>
|
<div className="menu-inner-shadow"></div>
|
||||||
|
|
||||||
<ul className="menu-inner py-1">
|
<ul className="menu-inner py-1">
|
||||||
{isError && (
|
{menuData.map((section) => (
|
||||||
<div className="text-center text-small">{error.message}</div>
|
<React.Fragment key={(Math.random() + 1).toString(36)}>
|
||||||
)}
|
{section.header && (
|
||||||
{isLoading && (
|
|
||||||
<>
|
|
||||||
{[...Array(7)].map((_, idx) => (
|
|
||||||
<MenuItemSkeleton
|
|
||||||
key={idx}
|
|
||||||
hasSubmenu={idx % 2 === 1}
|
|
||||||
submenuCount={Math.floor(Math.random() * 3) + 2}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
{data &&
|
|
||||||
data?.data.map((section) => (
|
|
||||||
<React.Fragment
|
|
||||||
key={section.id || section.header || section.items[0]?.id}
|
|
||||||
>
|
|
||||||
{/* {section.header && (
|
|
||||||
<li className="menu-header small text-uppercase">
|
<li className="menu-header small text-uppercase">
|
||||||
<span className="menu-header-text">{section.header}</span>
|
<span className="menu-header-text">{section.header}</span>
|
||||||
</li>
|
</li>
|
||||||
)} */}
|
)}
|
||||||
{section.items.map((item) => (
|
{section.items.map(MenuItem)}
|
||||||
<MenuItem key={item.id || item.link} {...item} />
|
</React.Fragment>
|
||||||
))}
|
))}
|
||||||
</React.Fragment>
|
|
||||||
))}
|
|
||||||
</ul>
|
</ul>
|
||||||
</aside>
|
</aside>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const MenuItem = (item) => {
|
const MenuItem = (item) => {
|
||||||
|
item.id = Math.random();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const isActive = location.pathname === item.link;
|
const isActive = location.pathname === item.link;
|
||||||
const hasSubmenu = Array.isArray(item.submenu) && item.submenu.length > 0;
|
const hasSubmenu = item.submenu && item.submenu.length > 0;
|
||||||
const isSubmenuActive =
|
const isSubmenuActive =
|
||||||
hasSubmenu && item.submenu.some((sub) => location.pathname === sub.link);
|
hasSubmenu &&
|
||||||
|
item.submenu.some((subitem) => location.pathname === subitem.link);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<li
|
<li
|
||||||
|
key={(Math.random() + 1).toString(36)}
|
||||||
className={`menu-item ${isActive || isSubmenuActive ? "active" : ""} ${
|
className={`menu-item ${isActive || isSubmenuActive ? "active" : ""} ${
|
||||||
hasSubmenu && isSubmenuActive ? "open" : ""
|
hasSubmenu && isSubmenuActive ? "open" : ""
|
||||||
}`}
|
}`}
|
||||||
@ -85,24 +67,21 @@ const MenuItem = (item) => {
|
|||||||
<NavLink
|
<NavLink
|
||||||
aria-label={`Navigate to ${item.text} ${!item.available ? "Pro" : ""}`}
|
aria-label={`Navigate to ${item.text} ${!item.available ? "Pro" : ""}`}
|
||||||
to={item.link}
|
to={item.link}
|
||||||
className={`menu-link ${hasSubmenu ? "menu-toggle" : ""}`}
|
className={`menu-link ${item.submenu ? "menu-toggle" : ""}`}
|
||||||
target={item.link?.includes("http") ? "_blank" : undefined}
|
key={(Math.random() + 1).toString(36)}
|
||||||
|
target={item.link.includes("http") ? "_blank" : undefined}
|
||||||
>
|
>
|
||||||
<i className={`menu-icon tf-icons ${item.icon}`}></i>
|
<i className={`menu-icon tf-icons ${item.icon}`}></i>
|
||||||
<div>{item.name}</div>
|
<div>{item.text}</div>{" "}
|
||||||
{item.available === false && (
|
{item.available === false && (
|
||||||
<div className="badge bg-label-primary fs-tiny rounded-pill ms-auto">
|
<div className="badge bg-label-primary fs-tiny rounded-pill ms-auto">
|
||||||
Pro
|
Pro
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</NavLink>
|
</NavLink>
|
||||||
|
{item.submenu && (
|
||||||
{/* Only render submenu if exists */}
|
<ul className="menu-sub" key={(Math.random() + 1).toString(36)}>
|
||||||
{hasSubmenu && (
|
{item.submenu.map(MenuItem)}
|
||||||
<ul className="menu-sub">
|
|
||||||
{item.submenu.map((sub) => (
|
|
||||||
<MenuItem key={sub.id || sub.link} {...sub} />
|
|
||||||
))}
|
|
||||||
</ul>
|
</ul>
|
||||||
)}
|
)}
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@ -1,17 +0,0 @@
|
|||||||
import React from 'react'
|
|
||||||
import { Link, useNavigate } from 'react-router-dom'
|
|
||||||
|
|
||||||
const Congratulation = () => {
|
|
||||||
const navigate = useNavigate()
|
|
||||||
return (
|
|
||||||
<div className="text-center p-4">
|
|
||||||
<h2>🎉 Congratulations!</h2>
|
|
||||||
<p>Your tenant is successfully onboarded.</p>
|
|
||||||
<div className="d-flex justify-content-center gap-3">
|
|
||||||
<p className='btn btn-sm btn-primary' onClick={()=>navigate('/tenants')}>Go To Tenant list</p> <p className='btn btn-sm btn-secondary' >Preview Tenant</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Congratulation
|
|
||||||
@ -1,109 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
import Label from "../common/Label";
|
|
||||||
import { useFormContext } from "react-hook-form";
|
|
||||||
|
|
||||||
const ContactInfro = ({ onNext }) => {
|
|
||||||
const {
|
|
||||||
register,
|
|
||||||
control,
|
|
||||||
trigger,
|
|
||||||
formState: { errors },
|
|
||||||
} = useFormContext();
|
|
||||||
|
|
||||||
|
|
||||||
const handleNext = async () => {
|
|
||||||
const valid = await trigger([
|
|
||||||
"firstName",
|
|
||||||
"lastName",
|
|
||||||
"email",
|
|
||||||
"contactNumber",
|
|
||||||
"billingAddress",
|
|
||||||
]);
|
|
||||||
if (valid) {
|
|
||||||
onNext(); // go to next tab
|
|
||||||
}
|
|
||||||
};
|
|
||||||
return (
|
|
||||||
<div className="row g-6">
|
|
||||||
<div className="col-sm-6">
|
|
||||||
<Label htmlFor="firstName" required>
|
|
||||||
First Name
|
|
||||||
</Label>
|
|
||||||
<input
|
|
||||||
id="firstName"
|
|
||||||
type="text"
|
|
||||||
className={`form-control form-control-sm`}
|
|
||||||
{...register("firstName")}
|
|
||||||
/>
|
|
||||||
{errors.firstName && (
|
|
||||||
<div className="danger-text">{errors.firstName.message}</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div className="col-sm-6">
|
|
||||||
<Label htmlFor="lastName" required>
|
|
||||||
Last Name
|
|
||||||
</Label>
|
|
||||||
<input
|
|
||||||
id="lastName"
|
|
||||||
type="text"
|
|
||||||
className={`form-control form-control-sm `}
|
|
||||||
{...register("lastName")}
|
|
||||||
/>
|
|
||||||
{errors.lastName && (
|
|
||||||
<div className="danger-text">{errors.lastName.message}</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div className="col-sm-6">
|
|
||||||
<Label htmlFor="email" required>
|
|
||||||
Email
|
|
||||||
</Label>
|
|
||||||
<input
|
|
||||||
id="email"
|
|
||||||
type="email"
|
|
||||||
className={`form-control form-control-sm `}
|
|
||||||
{...register("email")}
|
|
||||||
/>
|
|
||||||
{errors.email && (
|
|
||||||
<div className="danger-text">{errors.email.message}</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div className="col-sm-6">
|
|
||||||
<Label htmlFor="contactNumber" required>
|
|
||||||
Contact Number
|
|
||||||
</Label>
|
|
||||||
<input
|
|
||||||
id="contactNumber"
|
|
||||||
type="text"
|
|
||||||
className={`form-control form-control-sm `}
|
|
||||||
{...register("contactNumber")}
|
|
||||||
inputMode="tel"
|
|
||||||
placeholder="+91 9876543210"
|
|
||||||
/>
|
|
||||||
{errors.contactNumber && (
|
|
||||||
<div className="danger-text">{errors.contactNumber.message}</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div className="col-12">
|
|
||||||
<Label htmlFor="billingAddress" required>
|
|
||||||
Billing Address
|
|
||||||
</Label>
|
|
||||||
<textarea
|
|
||||||
id="billingAddress"
|
|
||||||
className={`form-control `}
|
|
||||||
{...register("billingAddress")}
|
|
||||||
rows={3}
|
|
||||||
/>
|
|
||||||
{errors.billingAddress && (
|
|
||||||
<div className="danger-text">{errors.billingAddress.message}</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div className="d-flex justify-content-end mt-3">
|
|
||||||
<button type="button" className="btn btn-sm btn-primary" onClick={handleNext}>
|
|
||||||
Next
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ContactInfro;
|
|
||||||
@ -1,188 +0,0 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
|
||||||
import Label from '../common/Label';
|
|
||||||
import { useFormContext,useForm,FormProvider } from 'react-hook-form';
|
|
||||||
import { useIndustries, useTenantDetails, useUpdateTenantDetails } from '../../hooks/useTenant';
|
|
||||||
import { orgSize, reference } from '../../utils/constants';
|
|
||||||
import { LogoUpload } from './LogoUpload';
|
|
||||||
import showToast from '../../services/toastService';
|
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
|
||||||
import { EditTenant } from './TenantSchema';
|
|
||||||
|
|
||||||
const EditProfile = ({ TenantId,onClose }) => {
|
|
||||||
const { data, isLoading, isError, error } = useTenantDetails(TenantId);
|
|
||||||
const [logoPreview, setLogoPreview] = useState(null);
|
|
||||||
const [logoName, setLogoName] = useState("");
|
|
||||||
const { data: Industries, isLoading: industryLoading, isError: industryError } = useIndustries();
|
|
||||||
const {mutate:UpdateTenant,isPending,} = useUpdateTenantDetails(()=>{
|
|
||||||
showToast("Tenant Details Updated Successfully","success")
|
|
||||||
onClose()
|
|
||||||
|
|
||||||
})
|
|
||||||
const methods = useForm({
|
|
||||||
resolver:zodResolver(EditTenant),
|
|
||||||
defaultValues: {
|
|
||||||
firstName: "",
|
|
||||||
lastName: "",
|
|
||||||
email: "",
|
|
||||||
contactNumber: "",
|
|
||||||
description: "",
|
|
||||||
domainName: "",
|
|
||||||
billingAddress: "",
|
|
||||||
taxId: "",
|
|
||||||
logoImage: "",
|
|
||||||
officeNumber: "",
|
|
||||||
organizationSize: "",
|
|
||||||
industryId: "",
|
|
||||||
reference: "",
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const { register, reset, handleSubmit, formState: { errors } } = methods;
|
|
||||||
|
|
||||||
const onSubmit = (formData) => {
|
|
||||||
const tenantPayload = {...formData,contactName:`${formData.firstName} ${formData.lastName}`,id:data.id,}
|
|
||||||
UpdateTenant({id:data.id,tenantPayload})
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (data && Industries) {
|
|
||||||
const [first = "", last = ""] = (data.contactName ?? "").split(" ");
|
|
||||||
reset({
|
|
||||||
firstName: first,
|
|
||||||
lastName: last,
|
|
||||||
contactNumber: data.contactNumber ?? "",
|
|
||||||
description: data.description ?? "",
|
|
||||||
domainName: data.domainName ?? "",
|
|
||||||
billingAddress: data.billingAddress ?? "",
|
|
||||||
taxId: data.taxId ?? "",
|
|
||||||
logoImage: data.logoImage ?? "",
|
|
||||||
officeNumber: data.officeNumber ?? "",
|
|
||||||
organizationSize: data.organizationSize ?? "",
|
|
||||||
industryId: data.industry?.id ?? "",
|
|
||||||
reference: data.reference ?? "",
|
|
||||||
});
|
|
||||||
setLogoPreview(data.logoImage)
|
|
||||||
}
|
|
||||||
}, [data, Industries, reset]);
|
|
||||||
|
|
||||||
if (isLoading) return <div>Loading...</div>;
|
|
||||||
if (isError) return <div>{error?.message}</div>;
|
|
||||||
|
|
||||||
|
|
||||||
return (
|
|
||||||
<FormProvider {...methods}>
|
|
||||||
<form className="row g-6" onSubmit={handleSubmit(onSubmit)}>
|
|
||||||
<h6>Edit Tenant</h6>
|
|
||||||
|
|
||||||
<div className="col-sm-6 mt-1">
|
|
||||||
<Label htmlFor="firstName" required>First Name</Label>
|
|
||||||
<input id="firstName" type="text" className="form-control form-control-sm" {...register("firstName")} inputMode='text' />
|
|
||||||
{errors.firstName && <div className="danger-text">{errors.firstName.message}</div>}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="col-sm-6 mt-1">
|
|
||||||
<Label htmlFor="lastName" required>Last Name</Label>
|
|
||||||
<input id="lastName" type="text" className="form-control form-control-sm" {...register("lastName")} />
|
|
||||||
{errors.lastName && <div className="danger-text">{errors.lastName.message}</div>}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<div className="col-sm-6 mt-1">
|
|
||||||
<Label htmlFor="contactNumber" required>Contact Number</Label>
|
|
||||||
<input id="contactNumber" type="text" className="form-control form-control-sm" {...register("contactNumber")} inputMode="tel"
|
|
||||||
placeholder="+91 9876543210" />
|
|
||||||
{errors.contactNumber && <div className="danger-text">{errors.contactNumber.message}</div>}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="col-sm-6 mt-1">
|
|
||||||
<Label htmlFor="domainName" required>Domain Name</Label>
|
|
||||||
<input id="domainName" type="text" className="form-control form-control-sm" {...register("domainName")} />
|
|
||||||
{errors.domainName && <div className="danger-text">{errors.domainName.message}</div>}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="col-sm-6 mt-1">
|
|
||||||
<Label htmlFor="taxId" required>Tax ID</Label>
|
|
||||||
<input id="taxId" type="text" className="form-control form-control-sm" {...register("taxId")} />
|
|
||||||
{errors.taxId && <div className="danger-text">{errors.taxId.message}</div>}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="col-sm-6 mt-1">
|
|
||||||
<Label htmlFor="officeNumber" required>Office Number</Label>
|
|
||||||
<input id="officeNumber" type="text" className="form-control form-control-sm" {...register("officeNumber")} />
|
|
||||||
{errors.officeNumber && <div className="danger-text">{errors.officeNumber.message}</div>}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="col-sm-6 mt-1">
|
|
||||||
<Label htmlFor="industryId" required>Industry</Label>
|
|
||||||
<select className="form-select form-select-sm" {...register("industryId")}>
|
|
||||||
{industryLoading ? <option value="">Loading...</option> :
|
|
||||||
Industries?.map((indu) => (
|
|
||||||
<option key={indu.id} value={indu.id}>{indu.name}</option>
|
|
||||||
))
|
|
||||||
}
|
|
||||||
</select>
|
|
||||||
{errors.industryId && <div className="danger-text">{errors.industryId.message}</div>}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="col-sm-6 mt-1">
|
|
||||||
<Label htmlFor="reference">Reference</Label>
|
|
||||||
<select className="form-select form-select-sm" {...register("reference")}>
|
|
||||||
{reference.map((org) => (
|
|
||||||
<option key={org.val} value={org.val}>{org.name}</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
{errors.reference && <div className="danger-text">{errors.reference.message}</div>}
|
|
||||||
</div>
|
|
||||||
<div className="col-sm-6">
|
|
||||||
<Label htmlFor="organizationSize" required>
|
|
||||||
Organization Size
|
|
||||||
</Label>
|
|
||||||
|
|
||||||
<select
|
|
||||||
className="form-select form-select-sm"
|
|
||||||
{...register("organizationSize")}
|
|
||||||
>
|
|
||||||
{orgSize.map((org) => (
|
|
||||||
<option key={org.val} value={org.val}>
|
|
||||||
{org.name}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
{errors.organizationSize && (
|
|
||||||
<div className="danger-text">{errors.organizationSize.message}</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="col-12 mt-1">
|
|
||||||
<Label htmlFor="billingAddress" required>Billing Address</Label>
|
|
||||||
<textarea id="billingAddress" className="form-control" {...register("billingAddress")} rows={2} />
|
|
||||||
{errors.billingAddress && <div className="danger-text">{errors.billingAddress.message}</div>}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="col-12 mt-1">
|
|
||||||
<Label htmlFor="description">Description</Label>
|
|
||||||
<textarea id="description" className="form-control" {...register("description")} rows={2} />
|
|
||||||
{errors.description && <div className="danger-text">{errors.description.message}</div>}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="col-sm-12">
|
|
||||||
<Label htmlFor="logImage">Logo Image</Label>
|
|
||||||
<LogoUpload
|
|
||||||
preview={logoPreview}
|
|
||||||
setPreview={setLogoPreview}
|
|
||||||
fileName={logoName}
|
|
||||||
setFileName={setLogoName}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="d-flex justify-content-center gap-2 mt-3">
|
|
||||||
<button type="submit" disabled={isPending} className="btn btn-sm btn-primary">{isPending ? "Please Wait..." : "Submit"}</button>
|
|
||||||
<button type="button" disabled={isPending} className="btn btn-sm btn-secondary" onClick={onClose}>Cancel</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</FormProvider>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default EditProfile;
|
|
||||||
@ -1,85 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
import { useFormContext } from "react-hook-form";
|
|
||||||
|
|
||||||
const toBase64 = (file) =>
|
|
||||||
new Promise((resolve, reject) => {
|
|
||||||
const reader = new FileReader();
|
|
||||||
reader.onload = () => resolve(reader.result);
|
|
||||||
reader.onerror = reject;
|
|
||||||
reader.readAsDataURL(file);
|
|
||||||
});
|
|
||||||
|
|
||||||
export const LogoUpload = ({ preview, setPreview, fileName, setFileName }) => {
|
|
||||||
const {
|
|
||||||
register,
|
|
||||||
setValue,
|
|
||||||
formState: { errors },
|
|
||||||
} = useFormContext();
|
|
||||||
|
|
||||||
const handleUpload = async (e) => {
|
|
||||||
const file = e.target.files?.[0];
|
|
||||||
if (!file) return;
|
|
||||||
|
|
||||||
if (file.size > 5 * 1024 * 1024) {
|
|
||||||
alert("File exceeds 5MB limit");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const base64 = await toBase64(file);
|
|
||||||
setValue("logoImage", base64, { shouldValidate: true });
|
|
||||||
setFileName(file.name);
|
|
||||||
setPreview(URL.createObjectURL(file));
|
|
||||||
e.target.value = "";
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleClear = () => {
|
|
||||||
setValue("logoImage", "", { shouldValidate: true });
|
|
||||||
setPreview(null);
|
|
||||||
setFileName("");
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="col-sm-12 mb-3">
|
|
||||||
<div
|
|
||||||
className="border border-secondary border-dashed rounded p-2 text-center position-relative"
|
|
||||||
style={{ cursor: "pointer" }}
|
|
||||||
onClick={() => document.getElementById("logoImageInput")?.click()}
|
|
||||||
>
|
|
||||||
<i className="bx bx-cloud-upload d-block bx-lg mb-2"></i>
|
|
||||||
<span className="text-muted">Click or browse to upload</span>
|
|
||||||
|
|
||||||
<input
|
|
||||||
type="file"
|
|
||||||
id="logoImageInput"
|
|
||||||
accept="image/png, image/jpeg"
|
|
||||||
style={{ display: "none" }}
|
|
||||||
{...register("logoImage")}
|
|
||||||
onChange={handleUpload}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{errors.logoImage && (
|
|
||||||
<small className="danger-text">{errors.logoImage.message}</small>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{preview && (
|
|
||||||
<div className="mt-2 d-flex align-items-start gap-2">
|
|
||||||
<img
|
|
||||||
src={preview}
|
|
||||||
alt="Preview"
|
|
||||||
className="img-thumbnail rounded"
|
|
||||||
style={{ maxHeight: "35px" }}
|
|
||||||
/>
|
|
||||||
<div className="d-flex align-items-center gap-2 mt-1">
|
|
||||||
<span className="small text-muted">{fileName}</span>
|
|
||||||
<i
|
|
||||||
className="bx bx-trash bx-sm text-danger"
|
|
||||||
style={{ cursor: "pointer" }}
|
|
||||||
onClick={handleClear}
|
|
||||||
></i>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@ -1,17 +0,0 @@
|
|||||||
import React from 'react'
|
|
||||||
import { formatUTCToLocalTime } from '../../utils/dateUtils'
|
|
||||||
|
|
||||||
const Organization = ({data}) => {
|
|
||||||
return (
|
|
||||||
<div className='container-fluid'>
|
|
||||||
{/* <div className='col-12'>
|
|
||||||
<h4>{data?.name}</h4>
|
|
||||||
</div> */}
|
|
||||||
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Organization
|
|
||||||
|
|
||||||
|
|
||||||
@ -1,242 +0,0 @@
|
|||||||
import React, { useState } from "react";
|
|
||||||
import { useFormContext, Controller } from "react-hook-form";
|
|
||||||
import Label from "../common/Label";
|
|
||||||
import DatePicker from "../common/DatePicker";
|
|
||||||
import { useCreateTenant, useIndustries } from "../../hooks/useTenant";
|
|
||||||
import { LogoUpload } from "./LogoUpload";
|
|
||||||
import { orgSize, reference } from "../../utils/constants";
|
|
||||||
import moment from "moment";
|
|
||||||
|
|
||||||
const OrganizationInfo = ({ onNext, onPrev, onSubmitTenant }) => {
|
|
||||||
const { data, isError, isLoading: industryLoading } = useIndustries();
|
|
||||||
const [logoPreview, setLogoPreview] = useState(null);
|
|
||||||
const [logoName, setLogoName] = useState("");
|
|
||||||
const {
|
|
||||||
register,
|
|
||||||
control,
|
|
||||||
setValue,
|
|
||||||
getValues,
|
|
||||||
trigger,
|
|
||||||
formState: { errors },
|
|
||||||
} = useFormContext();
|
|
||||||
|
|
||||||
const {
|
|
||||||
mutate: CreateTenant,
|
|
||||||
isError: tenantError,
|
|
||||||
error,
|
|
||||||
isPending,
|
|
||||||
} = useCreateTenant(() => {
|
|
||||||
debugger
|
|
||||||
onNext()
|
|
||||||
});
|
|
||||||
|
|
||||||
const handleNext = async () => {
|
|
||||||
const valid = await trigger([
|
|
||||||
"organizationName",
|
|
||||||
"officeNumber",
|
|
||||||
"domainName",
|
|
||||||
"description",
|
|
||||||
"onBoardingDate",
|
|
||||||
"organizationSize",
|
|
||||||
"taxId",
|
|
||||||
"industryId",
|
|
||||||
"reference",
|
|
||||||
"logoImage",
|
|
||||||
]);
|
|
||||||
|
|
||||||
if (valid) {
|
|
||||||
const data = getValues();
|
|
||||||
// onSubmitTenant(data);
|
|
||||||
// onNext();
|
|
||||||
const tenantPayload = {...data,onBoardingDate: moment.utc(data.onBoardingDate, "DD-MM-YYYY").toISOString() }
|
|
||||||
CreateTenant(tenantPayload);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="row g-2">
|
|
||||||
<div className="col-sm-6">
|
|
||||||
<Label htmlFor="organizationName" required>
|
|
||||||
Organization Name
|
|
||||||
</Label>
|
|
||||||
|
|
||||||
<input
|
|
||||||
id="organizationName"
|
|
||||||
className={`form-control form-control-sm `}
|
|
||||||
{...register("organizationName")}
|
|
||||||
/>
|
|
||||||
{errors.organizationName && (
|
|
||||||
<div className="danger-text">{errors.organizationName.message}</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="col-sm-6">
|
|
||||||
<Label htmlFor="officeNumber" required>
|
|
||||||
Office Number
|
|
||||||
</Label>
|
|
||||||
<input
|
|
||||||
id="officeNumber"
|
|
||||||
className={`form-control form-control-sm `}
|
|
||||||
{...register("officeNumber")}
|
|
||||||
/>
|
|
||||||
{errors.officeNumber && (
|
|
||||||
<div className="danger-text">{errors.officeNumber.message}</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="col-sm-6">
|
|
||||||
<Label htmlFor="domainName" required>
|
|
||||||
Domain Name
|
|
||||||
</Label>
|
|
||||||
<input
|
|
||||||
id="domainName"
|
|
||||||
className={`form-control form-control-sm `}
|
|
||||||
{...register("domainName")}
|
|
||||||
/>
|
|
||||||
{errors.domainName && (
|
|
||||||
<div className="danger-text">{errors.domainName.message}</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="col-sm-6">
|
|
||||||
<Label htmlFor="taxId" required>
|
|
||||||
Tax ID
|
|
||||||
</Label>
|
|
||||||
<input
|
|
||||||
id="taxId"
|
|
||||||
className={`form-control form-control-sm `}
|
|
||||||
{...register("taxId")}
|
|
||||||
/>
|
|
||||||
{errors.taxId && (
|
|
||||||
<div className="danger-text">{errors.taxId.message}</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="col-sm-6">
|
|
||||||
<Label htmlFor="onBoardingDate" required>
|
|
||||||
Onboarding Date
|
|
||||||
</Label>
|
|
||||||
<DatePicker
|
|
||||||
name="onBoardingDate"
|
|
||||||
control={control}
|
|
||||||
placeholder="DD-MM-YYYY"
|
|
||||||
maxDate={new Date()}
|
|
||||||
className={errors.onBoardingDate ? "is-invalid" : ""}
|
|
||||||
/>
|
|
||||||
{errors.onBoardingDate && (
|
|
||||||
<div className="invalid-feedback">
|
|
||||||
{errors.onBoardingDate.message}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="col-sm-6">
|
|
||||||
<Label htmlFor="organizationSize" required>
|
|
||||||
Organization Size
|
|
||||||
</Label>
|
|
||||||
|
|
||||||
<select
|
|
||||||
className="form-select form-select-sm"
|
|
||||||
{...register("organizationSize")}
|
|
||||||
>
|
|
||||||
{orgSize.map((org) => (
|
|
||||||
<option key={org.val} value={org.val}>
|
|
||||||
{org.name}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
{errors.organizationSize && (
|
|
||||||
<div className="danger-text">{errors.organizationSize.message}</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="col-sm-6">
|
|
||||||
<Label htmlFor="industryId" required>
|
|
||||||
Industry
|
|
||||||
</Label>
|
|
||||||
<select
|
|
||||||
className="form-select form-select-sm"
|
|
||||||
{...register("industryId")}
|
|
||||||
>
|
|
||||||
{industryLoading ? (
|
|
||||||
<option value="">Loading...</option>
|
|
||||||
) : (
|
|
||||||
data?.map((indu) => (
|
|
||||||
<option key={indu.id} value={indu.id}>
|
|
||||||
{indu.name}
|
|
||||||
</option>
|
|
||||||
))
|
|
||||||
)}
|
|
||||||
</select>
|
|
||||||
{errors.industryId && (
|
|
||||||
<div className="danger-text">{errors.industryId.message}</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="col-sm-6">
|
|
||||||
<Label htmlFor="reference">Reference</Label>
|
|
||||||
|
|
||||||
<select
|
|
||||||
className="form-select form-select-sm"
|
|
||||||
{...register("reference")}
|
|
||||||
>
|
|
||||||
{reference.map((org) => (
|
|
||||||
<option key={org.val} value={org.val}>
|
|
||||||
{org.name}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
{errors.reference && (
|
|
||||||
<div className="danger-text">{errors.reference.message}</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="col-sm-12">
|
|
||||||
<Label htmlFor="description">Description</Label>
|
|
||||||
<textarea
|
|
||||||
id="description"
|
|
||||||
rows={3}
|
|
||||||
className={`form-control form-control-sm `}
|
|
||||||
{...register("description")}
|
|
||||||
/>
|
|
||||||
{errors.description && (
|
|
||||||
<div className="danger-text">{errors.description.message}</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="col-sm-12">
|
|
||||||
<Label htmlFor="logImage">Logo Image</Label>
|
|
||||||
|
|
||||||
<LogoUpload
|
|
||||||
preview={logoPreview}
|
|
||||||
setPreview={setLogoPreview}
|
|
||||||
fileName={logoName}
|
|
||||||
setFileName={setLogoName}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="d-flex justify-content-between mt-3">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className="btn btn-sm btn-secondary"
|
|
||||||
onClick={onPrev}
|
|
||||||
disabled={isPending}
|
|
||||||
>
|
|
||||||
Back
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className="btn btn-sm btn-primary"
|
|
||||||
onClick={handleNext}
|
|
||||||
disabled={isPending}
|
|
||||||
>
|
|
||||||
{isPending ? "Please Wait..." : "Submit and Next"}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default OrganizationInfo;
|
|
||||||
@ -1,190 +0,0 @@
|
|||||||
import React, { useState } from "react";
|
|
||||||
import { formatUTCToLocalTime } from "../../utils/dateUtils";
|
|
||||||
import EditProfile from "./EditProfile";
|
|
||||||
import GlobalModel from "../common/GlobalModel";
|
|
||||||
import { useTenantContext } from "../../pages/Tenant/TenantPage";
|
|
||||||
import { useTenantDetailsContext } from "../../pages/Tenant/TenantDetails";
|
|
||||||
import IconButton from "../common/IconButton";
|
|
||||||
import { hasUserPermission } from "../../utils/authUtils";
|
|
||||||
import { MANAGE_TENANTS } from "../../utils/constants";
|
|
||||||
|
|
||||||
const Profile = ({ data }) => {
|
|
||||||
const {setEditTenant} = useTenantDetailsContext()
|
|
||||||
const canUpdateTenant = hasUserPermission(MANAGE_TENANTS)
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div className="container-fuid">
|
|
||||||
|
|
||||||
<div className="row">
|
|
||||||
<div className="col-12 my-2">
|
|
||||||
<div className="d-flex flex-wrap align-items-start position-relative ">
|
|
||||||
<div className=" d-flex align-items-start gap-2">
|
|
||||||
{data.logoImage ? (<img
|
|
||||||
src={data.logoImage}
|
|
||||||
alt="Preview"
|
|
||||||
className="img-thumbnail rounded"
|
|
||||||
style={{ maxHeight: "35px" }}
|
|
||||||
/>):( <IconButton
|
|
||||||
iconClass="bx bx-sm bx-building"
|
|
||||||
color="warning"
|
|
||||||
size={8}
|
|
||||||
/>)}
|
|
||||||
</div>
|
|
||||||
<div className="ms-2 ">
|
|
||||||
<h4 className="m-0">{data.name}</h4>
|
|
||||||
<div className="block">
|
|
||||||
<i className="bx bx-globe text-primary bx-xs me-1"></i>
|
|
||||||
<span>{data?.domainName}</span>
|
|
||||||
</div>
|
|
||||||
{canUpdateTenant && ( <span
|
|
||||||
className="position-absolute top-0 end-0 cursor-auto"
|
|
||||||
onClick={() => setEditTenant(true)}
|
|
||||||
>
|
|
||||||
<i className="bx bx-edit bs-sm text-primary cursor-pointer"></i>
|
|
||||||
</span>)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{data?.description && (
|
|
||||||
<div className="col rounded-2 justify-content-start p-2">
|
|
||||||
<p className="m-0">{data?.description}</p>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<div className="divider text-start my-1">
|
|
||||||
<div className="divider-text">Personal</div>
|
|
||||||
</div>
|
|
||||||
<div className="row ">
|
|
||||||
<div className="col-12 col-md-6 d-flex align-items-center">
|
|
||||||
<i className="bx bx-sm bx-user me-1"></i>
|
|
||||||
<span className="fw-semibold">Contact Person:</span>
|
|
||||||
<span className="ms-2">{data.contactName}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="col-12 col-md-6 d-flex align-items-center my-4 m-0">
|
|
||||||
<i className="bx bx-sm bx-envelope me-1"></i>
|
|
||||||
<span className="fw-semibold">Email:</span>
|
|
||||||
<span className="ms-2">{data.email}</span>
|
|
||||||
</div>
|
|
||||||
<div className="col-12 col-md-6 d-flex align-items-center">
|
|
||||||
<i className="bx bx-sm bx-mobile me-1"></i>
|
|
||||||
<span className="fw-semibold">Contact Number:</span>
|
|
||||||
<span className="ms-2">{data.contactNumber}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{data.billingAddress && (
|
|
||||||
<div className="col-12 d-flex text-wrap align-items-start mt-4 m-0">
|
|
||||||
<i className='bx bxs-flag-alt bx-sm me-1'></i>
|
|
||||||
<span className="fw-semibold">Address:</span>
|
|
||||||
<span className="ms-2">{data.billingAddress}</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div className="divider text-start ">
|
|
||||||
<div className="divider-text">Organization</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="col-12 d-flex align-items-center mb-2">
|
|
||||||
<i className="bx bx-sm bxs-building me-1"></i>
|
|
||||||
<span className="fw-semibold">Industry:</span>
|
|
||||||
<span className="ms-2">{data?.industry?.name}</span>
|
|
||||||
</div>
|
|
||||||
<div className="row ">
|
|
||||||
{data?.taxId && (
|
|
||||||
<div className="col-12 col-md-6 d-flex align-items-center ">
|
|
||||||
<i className="bx bx-sm bx-id-card me-1"></i>
|
|
||||||
<span className="fw-semibold">Tax Id:</span>
|
|
||||||
<span className="ms-2">{data?.taxId}</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<div className="col-12 col-md-6 d-flex align-items-center mb-2 m-0">
|
|
||||||
<i className="bx bx-sm bx-group me-1"></i>
|
|
||||||
<span className="fw-semibold">Organization Size:</span>
|
|
||||||
<span className="ms-2">{data?.organizationSize}</span>
|
|
||||||
</div>
|
|
||||||
<div className="col-12 col-md-6 d-flex align-items-center my-2 m-0">
|
|
||||||
<i className="bx bx-sm bx-group me-1"></i>
|
|
||||||
<span className="fw-semibold">Seat Available:</span>
|
|
||||||
<span className="ms-2">{data?.seatsAvailable}</span>
|
|
||||||
</div>
|
|
||||||
<div className="col-12 col-md-6 d-flex align-items-center my-2 m-0">
|
|
||||||
<i className="bx bx-sm bx-group me-1"></i>
|
|
||||||
<span className="fw-semibold">Total Seat:</span>
|
|
||||||
<span className="ms-2">{data?.currentPlan?.maxUsers}</span>
|
|
||||||
</div>
|
|
||||||
<div className="col-12 col-md-6 d-flex align-items-center">
|
|
||||||
<i className="bx bx-sm bxs-calendar me-1"></i>
|
|
||||||
<span className="fw-semibold">On-Boarding Date:</span>
|
|
||||||
<span className="ms-2">
|
|
||||||
{formatUTCToLocalTime(data?.onBoardingDate)}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<table className="table table-bordered text-center text-nowrap table-responsive my-4">
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<td colSpan="1">
|
|
||||||
<strong>Status</strong>
|
|
||||||
</td>
|
|
||||||
<td colSpan="1">
|
|
||||||
<strong>Active</strong>
|
|
||||||
</td>
|
|
||||||
<td colSpan="1">
|
|
||||||
<strong>In-Progress</strong>
|
|
||||||
</td>
|
|
||||||
<td colSpan="1">
|
|
||||||
<strong>On Hold</strong>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<strong>In-Active</strong>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<strong>Completed</strong>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<strong>Projects</strong>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<strong>{data?.activeProjects}</strong>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<strong>{data?.inProgressProjects}</strong>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<strong>{data?.onHoldProjects}</strong>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<strong>{data?.inActiveProjects}</strong>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<strong>{data?.completedProjects}</strong>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
<div className="row">
|
|
||||||
<div className="col-12 col-md-6 d-flex align-items-center">
|
|
||||||
<i className="bx bx-sm bx-group me-1"></i>
|
|
||||||
<span className="fw-semibold">Activite Employees:</span>
|
|
||||||
<span className="ms-2">{data?.activeEmployees}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="col-12 col-md-6 d-flex align-items-center my-4 m-0">
|
|
||||||
<i className="bx bx-sm bx-group me-1"></i>
|
|
||||||
<span className="fw-semibold">In-Active Employee:</span>
|
|
||||||
<span className="ms-2">{data?.inActiveEmployees}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Profile;
|
|
||||||
|
|
||||||
@ -1,45 +0,0 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
|
||||||
|
|
||||||
const SegmentedControl = ({setFrequency,defultFequency}) => {
|
|
||||||
const [selected, setSelected] = useState(defultFequency);
|
|
||||||
useEffect(()=>{
|
|
||||||
setFrequency(selected)
|
|
||||||
},[selected])
|
|
||||||
return (
|
|
||||||
<div className='text-center mt-6'>
|
|
||||||
<div className="d-inline-flex border rounded-pill overflow-hidden shadow-none">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className={`btn px-4 py-2 rounded-0 ${selected === 0 ? 'active btn-secondary text-white' : ''}`}
|
|
||||||
onClick={() => setSelected(0)}
|
|
||||||
>
|
|
||||||
Monthly
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className={`btn px-4 py-2 rounded-0 ${selected === 1? 'active btn-secondary text-white' : ''}`}
|
|
||||||
onClick={() => setSelected(1)}
|
|
||||||
>
|
|
||||||
Quaterly
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className={`btn px-4 py-2 rounded-0 ${selected === 2 ? 'active btn-secondary text-white' : ''}`}
|
|
||||||
onClick={() => setSelected(2)}
|
|
||||||
>
|
|
||||||
Half-Yearly
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className={`btn px-4 py-2 rounded-0 ${selected === 3 ? 'active btn-secondary text-white' : ''}`}
|
|
||||||
onClick={() => setSelected(3)}
|
|
||||||
>
|
|
||||||
Yearly
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default SegmentedControl;
|
|
||||||
@ -1,252 +0,0 @@
|
|||||||
import React, { useState, useEffect } from "react";
|
|
||||||
import {
|
|
||||||
useAddSubscription,
|
|
||||||
useSubscriptionPlan,
|
|
||||||
useUpgradeSubscription,
|
|
||||||
} from "../../hooks/useTenant";
|
|
||||||
import SegmentedControl from "./SegmentedControl";
|
|
||||||
import { useFormContext } from "react-hook-form";
|
|
||||||
import { CONSTANT_TEXT } from "../../utils/constants";
|
|
||||||
import Label from "../common/Label";
|
|
||||||
import { useSelector } from "react-redux";
|
|
||||||
import { useNavigate } from "react-router-dom";
|
|
||||||
|
|
||||||
const SubScription = ({ onSubmitSubScription, onNext }) => {
|
|
||||||
const [frequency, setFrequency] = useState(3);
|
|
||||||
const [selectedPlanId, setSelectedPlanId] = useState(null);
|
|
||||||
const selectedTenant = useSelector(
|
|
||||||
(store) => store.globalVariables.currentTenant
|
|
||||||
);
|
|
||||||
const naviget = useNavigate();
|
|
||||||
const {
|
|
||||||
data: plans = [],
|
|
||||||
isError,
|
|
||||||
isLoading,
|
|
||||||
error: subscriptionGettingError,
|
|
||||||
} = useSubscriptionPlan(frequency);
|
|
||||||
|
|
||||||
const {
|
|
||||||
register,
|
|
||||||
setValue,
|
|
||||||
getValues,
|
|
||||||
trigger,
|
|
||||||
formState: { errors },
|
|
||||||
} = useFormContext();
|
|
||||||
|
|
||||||
const {
|
|
||||||
mutate: AddSubScription,
|
|
||||||
isPending,
|
|
||||||
error,
|
|
||||||
} = useAddSubscription(() => {
|
|
||||||
naviget("/tenants");
|
|
||||||
});
|
|
||||||
const { mutate: updgradeSubscription, isPending: upgrading } =
|
|
||||||
useUpgradeSubscription(() => {
|
|
||||||
naviget("/tenants");
|
|
||||||
});
|
|
||||||
const handleSubscriptionSubmit = async () => {
|
|
||||||
const isValid = await trigger([
|
|
||||||
"planId",
|
|
||||||
"currencyId",
|
|
||||||
"maxUsers",
|
|
||||||
"frequency",
|
|
||||||
"isTrial",
|
|
||||||
"autoRenew",
|
|
||||||
]);
|
|
||||||
|
|
||||||
if (isValid) {
|
|
||||||
const payload = getValues();
|
|
||||||
// onSubmitSubScription(payload);
|
|
||||||
let subscriptionPayload = null;
|
|
||||||
|
|
||||||
if (selectedTenant?.operationMode === 1) {
|
|
||||||
subscriptionPayload = {
|
|
||||||
planId: payload.planId,
|
|
||||||
currencyId: payload.currencyId,
|
|
||||||
maxUsers: payload.maxUsers,
|
|
||||||
tenantId: selectedTenant?.data?.id,
|
|
||||||
};
|
|
||||||
updgradeSubscription(subscriptionPayload);
|
|
||||||
} else {
|
|
||||||
subscriptionPayload = {
|
|
||||||
...payload,
|
|
||||||
tenantId: selectedTenant?.data?.id,
|
|
||||||
};
|
|
||||||
AddSubScription(subscriptionPayload);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handlePlanSelection = (plan) => {
|
|
||||||
setSelectedPlanId(plan.id);
|
|
||||||
setValue("planId", plan.id);
|
|
||||||
setValue("currencyId", plan.currency?.id);
|
|
||||||
setValue("frequency", frequency);
|
|
||||||
};
|
|
||||||
|
|
||||||
const selectedPlan = plans.find((p) => p.id === selectedPlanId);
|
|
||||||
if (isLoading) return <div className="text-center">Loading....</div>;
|
|
||||||
if (isError)
|
|
||||||
return (
|
|
||||||
<div className="text-center">{subscriptionGettingError?.message}</div>
|
|
||||||
);
|
|
||||||
return (
|
|
||||||
<div className="text-start">
|
|
||||||
<SegmentedControl
|
|
||||||
setFrequency={setFrequency}
|
|
||||||
defultFequency={frequency}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{!isLoading && !isError && plans.length > 0 && (
|
|
||||||
<div className="row g-4 my-6">
|
|
||||||
{plans.map((plan) => {
|
|
||||||
const isSelected = plan.id === selectedPlanId;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div key={plan.id} className="col-md-4">
|
|
||||||
<div
|
|
||||||
className={`card h-100 shadow-none border-1 cursor-pointer ${
|
|
||||||
isSelected ? "border-primary border-1 shadow-md" : ""
|
|
||||||
}`}
|
|
||||||
onClick={() => handlePlanSelection(plan)}
|
|
||||||
>
|
|
||||||
<div className="card-body d-flex flex-column p-3">
|
|
||||||
<div className="d-flex align-items-center gap-3 mb-3">
|
|
||||||
<i className="bx bxs-package text-primary fs-1"></i>
|
|
||||||
<div>
|
|
||||||
<p className="card-title fs-4 fw-bold mb-1">
|
|
||||||
{plan.planName}
|
|
||||||
</p>
|
|
||||||
<p className="text-muted mb-0">{plan.description}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h4 className="fw-semibold mt-auto mb-3">
|
|
||||||
{plan.currency?.symbol} {plan.price}
|
|
||||||
</h4>
|
|
||||||
|
|
||||||
<ul className="list-unstyled d-flex gap-4 flex-wrap mb-2">
|
|
||||||
<li className="d-flex align-items-center">
|
|
||||||
<i className="bx bx-server me-1"></i>
|
|
||||||
Storage {plan.maxStorage} MB
|
|
||||||
</li>
|
|
||||||
<li className="d-flex align-items-center">
|
|
||||||
<i className="bx bx-check-double text-success me-2"></i>
|
|
||||||
Trial Days {plan.trialDays}
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<div className="divider my-3">
|
|
||||||
<div className="divider-text card-text text-uppercase text-muted small">
|
|
||||||
Features
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{plan?.features &&
|
|
||||||
Object.entries(plan?.features?.modules || {})
|
|
||||||
.filter(([key]) => key !== "id")
|
|
||||||
.map(([key, mod]) => (
|
|
||||||
<div
|
|
||||||
key={key}
|
|
||||||
className="mb-2 d-flex align-items-center"
|
|
||||||
>
|
|
||||||
<i
|
|
||||||
className={`fa-regular ${
|
|
||||||
mod.enabled
|
|
||||||
? "fa-circle-check text-success"
|
|
||||||
: "fa-circle-xmark text-danger"
|
|
||||||
}`}
|
|
||||||
></i>
|
|
||||||
<small className="ms-1">{mod.name}</small>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button
|
|
||||||
className={`btn mt-3 ${
|
|
||||||
isSelected ? "btn-primary" : "btn-outline-primary"
|
|
||||||
}`}
|
|
||||||
onClick={() => handlePlanSelection(plan)}
|
|
||||||
>
|
|
||||||
{isSelected ? "Selected" : "Select Plan"}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
|
|
||||||
{/* Form Inputs */}
|
|
||||||
<div className="row g-2 mt-3">
|
|
||||||
<div className="col-sm-4">
|
|
||||||
<Label htmlFor="maxUsers" required>
|
|
||||||
{" "}
|
|
||||||
Team Size
|
|
||||||
</Label>
|
|
||||||
<input
|
|
||||||
type="number"
|
|
||||||
step={1}
|
|
||||||
className="form-control form-control-sm"
|
|
||||||
{...register("maxUsers", {
|
|
||||||
valueAsNumber: true,
|
|
||||||
})}
|
|
||||||
onKeyDown={(e) => {
|
|
||||||
if (["e", "E", "+", "-", "."].includes(e.key)) {
|
|
||||||
e.preventDefault();
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="col-12">
|
|
||||||
<div className="d-flex justify-content-start align-items-center gap-2">
|
|
||||||
<label className="form-label d-block">Enable auto renew</label>
|
|
||||||
<label className="switch switch-square switch-sm">
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
className="switch-input"
|
|
||||||
{...register("autoRenew")}
|
|
||||||
/>
|
|
||||||
<span className="switch-toggle-slider">
|
|
||||||
<span className="switch-on">
|
|
||||||
<i className="icon-base bx bx-check"></i>
|
|
||||||
</span>
|
|
||||||
<span className="switch-off">
|
|
||||||
<i className="icon-base bx bx-x"></i>
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<small className="text-secondary text-tiny">
|
|
||||||
{CONSTANT_TEXT.RenewsubscriptionLabel}
|
|
||||||
</small>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{Object.keys(errors).length > 0 && (
|
|
||||||
<div class="alert alert-danger" role="alert">
|
|
||||||
{Object.entries(errors).map(([key, error]) => (
|
|
||||||
<div key={key} className="danger-text">
|
|
||||||
{error?.message}
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div className="d-flex text-center mt-4">
|
|
||||||
<button
|
|
||||||
onClick={handleSubscriptionSubmit}
|
|
||||||
className="btn btn-sm btn-primary"
|
|
||||||
type="button"
|
|
||||||
disabled={isPending || upgrading}
|
|
||||||
>
|
|
||||||
{isPending || upgrading ? "Please Wait..." : "Submit"}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default SubScription;
|
|
||||||
@ -1,219 +0,0 @@
|
|||||||
import React, { useEffect } from "react";
|
|
||||||
import { useTenantDetails } from "../../hooks/useTenant";
|
|
||||||
import { useDispatch } from "react-redux";
|
|
||||||
import { setCurrentTenant } from "../../slices/globalVariablesSlice";
|
|
||||||
import { useNavigate } from "react-router-dom";
|
|
||||||
import { formatUTCToLocalTime } from "../../utils/dateUtils";
|
|
||||||
import { SUBSCRIPTION_PLAN_FREQUENCIES } from "../../utils/constants";
|
|
||||||
|
|
||||||
const SubScriptionHistory = ({ tenantId }) => {
|
|
||||||
const { data, isLoading, isError, error } = useTenantDetails(tenantId);
|
|
||||||
const dispatch = useDispatch();
|
|
||||||
const navigate = useNavigate();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (data) {
|
|
||||||
dispatch(setCurrentTenant({ operationMode: 1, data }));
|
|
||||||
} else {
|
|
||||||
dispatch(setCurrentTenant({ operationMode: 0, data: null }));
|
|
||||||
}
|
|
||||||
}, [data, dispatch]);
|
|
||||||
|
|
||||||
const handleUpgradePlan = () => {
|
|
||||||
navigate("/tenants/new-tenant");
|
|
||||||
};
|
|
||||||
|
|
||||||
if (isLoading) return <div>Loading...</div>;
|
|
||||||
if (isError) return <div>{error}</div>;
|
|
||||||
|
|
||||||
const plan = data?.currentPlan;
|
|
||||||
const features = data?.currentPlanFeatures;
|
|
||||||
const subscriptionHistory = data?.subscriptionHistery;
|
|
||||||
|
|
||||||
if (!plan) {
|
|
||||||
return (
|
|
||||||
<div className="text-center p-4">
|
|
||||||
<button className="btn btn-success" onClick={handleUpgradePlan}>
|
|
||||||
Add Subscription
|
|
||||||
</button>
|
|
||||||
<div className="mt-2 text-center small text-muted">
|
|
||||||
<i className="bx bx-info-circle bx-xs"></i> Add your new subscription
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Format dates
|
|
||||||
const end = plan?.endDate ? new Date(plan.endDate) : null;
|
|
||||||
const today = new Date();
|
|
||||||
|
|
||||||
const daysLeft = end
|
|
||||||
? Math.max(0, Math.ceil((end - today) / (1000 * 60 * 60 * 24)))
|
|
||||||
: 0;
|
|
||||||
|
|
||||||
// Render logic for subscription history table
|
|
||||||
const renderSubscriptionHistory = () => {
|
|
||||||
if (!subscriptionHistory || subscriptionHistory.length === 0) {
|
|
||||||
return (
|
|
||||||
<div className="text-center text-muted p-4">
|
|
||||||
No subscription history found
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const sortedHistory = subscriptionHistory
|
|
||||||
.slice()
|
|
||||||
.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt));
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{/* Table for larger screens */}
|
|
||||||
<div className=" d-md-block table-responsive">
|
|
||||||
<table className="table border-top dataTable text-nowrap align-middle">
|
|
||||||
<thead className="align-middle">
|
|
||||||
<tr>
|
|
||||||
<th>Date</th>
|
|
||||||
<th>Type</th>
|
|
||||||
<th>Amount</th>
|
|
||||||
<th>Plan Name</th>
|
|
||||||
<th className="text-center">Action</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody className="align-middle">
|
|
||||||
{sortedHistory.map((item) => (
|
|
||||||
<tr key={item.id}>
|
|
||||||
<td>{formatUTCToLocalTime(item?.createdAt)}</td>
|
|
||||||
<td>{SUBSCRIPTION_PLAN_FREQUENCIES[item.frequency] || "N/A"}</td>
|
|
||||||
<td>
|
|
||||||
{item.currency?.symbol || "₹"} {item.price}
|
|
||||||
</td>
|
|
||||||
<td>{item.planName}</td>
|
|
||||||
<td className="text-center">
|
|
||||||
<div className="dropdown">
|
|
||||||
<button
|
|
||||||
className="btn btn-icon btn-sm dropdown-toggle hide-arrow"
|
|
||||||
data-bs-toggle="dropdown"
|
|
||||||
>
|
|
||||||
<i className="bx bx-dots-vertical-rounded"></i>
|
|
||||||
</button>
|
|
||||||
<div className="dropdown-menu dropdown-menu-end">
|
|
||||||
<button
|
|
||||||
className="dropdown-item py-1"
|
|
||||||
>
|
|
||||||
<i className="bx bx-detail bx-sm"></i> View
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
className="dropdown-item py-1"
|
|
||||||
onClick={() =>
|
|
||||||
console.log("Download clicked for", item.id)
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<i className="bx bx-cloud-download bx-sm"></i> Download
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
))}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Card-based view for smaller screens */}
|
|
||||||
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="p-2 p-md-4">
|
|
||||||
<div className="row g-4">
|
|
||||||
{/* Left Card: Active Subscription */}
|
|
||||||
<div className="col-12 col-lg-6">
|
|
||||||
<div className="card shadow-sm border rounded p-3 h-100 text-start">
|
|
||||||
<div className="divider text-start mb-3">
|
|
||||||
<div className="divider-text">Active Subscription</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<p className="text-primary fw-bold m-0 fs-4">
|
|
||||||
{plan.planName || "N/A"}
|
|
||||||
</p>
|
|
||||||
{plan.description && (
|
|
||||||
<p className="m-0 text-muted small">{plan.description}</p>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div className="mt-2">
|
|
||||||
<h3 className="m-0">
|
|
||||||
{plan.currency?.symbol || "₹"} {plan.price}
|
|
||||||
</h3>
|
|
||||||
<small className="text-muted">
|
|
||||||
{SUBSCRIPTION_PLAN_FREQUENCIES[plan.frequency] || ""}
|
|
||||||
</small>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="mt-3 small text-muted">
|
|
||||||
<div>
|
|
||||||
Activated Since:{" "}
|
|
||||||
{plan.startDate ? formatUTCToLocalTime(plan.startDate) : "N/A"} (
|
|
||||||
{daysLeft} days left)
|
|
||||||
</div>
|
|
||||||
<div className="mt-1">
|
|
||||||
Ends on:{" "}
|
|
||||||
{plan.endDate ? formatUTCToLocalTime(plan.endDate) : "N/A"}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Features list */}
|
|
||||||
<div className="mt-4">
|
|
||||||
<h6 className="text-secondary">Features</h6>
|
|
||||||
<div className="row g-2">
|
|
||||||
{features?.modules &&
|
|
||||||
Object.entries(features.modules).map(([key, mod]) => {
|
|
||||||
if (!mod?.name) return null;
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
key={key}
|
|
||||||
className="col-12 col-sm-6 d-flex align-items-center"
|
|
||||||
>
|
|
||||||
<i
|
|
||||||
className={`fa-regular ${
|
|
||||||
mod.enabled
|
|
||||||
? "fa-circle-check text-success"
|
|
||||||
: "fa-circle-xmark text-danger"
|
|
||||||
}`}
|
|
||||||
></i>
|
|
||||||
<span className="ms-2">{mod.name}</span>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="mt-3 text-end">
|
|
||||||
<button
|
|
||||||
className="btn btn-sm btn-primary"
|
|
||||||
onClick={handleUpgradePlan}
|
|
||||||
>
|
|
||||||
Upgrade Plan
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Right Card: Subscription History */}
|
|
||||||
<div className="col-12 col-lg-6">
|
|
||||||
<div className="card shadow-sm border rounded p-3 h-100">
|
|
||||||
<div className="divider text-start mb-3">
|
|
||||||
<div className="divider-text">
|
|
||||||
<i className="bx bx-history"></i> <small>History</small>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{renderSubscriptionHistory()}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default SubScriptionHistory;
|
|
||||||
@ -1,97 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
|
|
||||||
const SkeletonCell = ({ width = "100%", height = 20, style = {} }) => (
|
|
||||||
<div
|
|
||||||
className="skeleton"
|
|
||||||
style={{
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
borderRadius: 4,
|
|
||||||
...style,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
export const TenantTableSkeleton = ({ columns, rows = 5 }) => {
|
|
||||||
return (
|
|
||||||
<div className="card p-2 mt-3">
|
|
||||||
<div className="card-datatable text-nowrap table-responsive">
|
|
||||||
<table className="table border-top dataTable text-nowrap">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
{columns.map((col) => (
|
|
||||||
<th key={col.key} className="sorting d-table-cell">
|
|
||||||
<div className={col.align}>{col.label}</div>
|
|
||||||
</th>
|
|
||||||
))}
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{[...Array(rows)].map((_, rowIdx) => (
|
|
||||||
<tr key={rowIdx}>
|
|
||||||
{columns.map((col, colIdx) => (
|
|
||||||
<td
|
|
||||||
key={col.key || colIdx}
|
|
||||||
className={`d-table-cell px-3 py-2 align-middle ${
|
|
||||||
col.align ?? ""
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
{/* Icon + text skeleton for first few columns */}
|
|
||||||
{col.key === "name" && (
|
|
||||||
<div className="d-flex align-items-center">
|
|
||||||
<div
|
|
||||||
className="me-2"
|
|
||||||
style={{
|
|
||||||
width: 24,
|
|
||||||
height: 24,
|
|
||||||
borderRadius: "5px",
|
|
||||||
background: "#ddd",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<SkeletonCell width="120px" />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{col.key === "domainName" && (
|
|
||||||
<div className="d-flex align-items-center">
|
|
||||||
<div
|
|
||||||
className="me-2"
|
|
||||||
style={{
|
|
||||||
width: 14,
|
|
||||||
height: 14,
|
|
||||||
borderRadius: "50%",
|
|
||||||
background: "#ddd",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<SkeletonCell width="140px" />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{col.key === "contactName" && (
|
|
||||||
<div className="d-flex align-items-center ">
|
|
||||||
<div
|
|
||||||
className="me-2"
|
|
||||||
style={{
|
|
||||||
width: 20,
|
|
||||||
height: 20,
|
|
||||||
borderRadius: "50%",
|
|
||||||
background: "#ddd",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<SkeletonCell width="100px" />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{col.key === "contactNumber" && (
|
|
||||||
<SkeletonCell width="100px"/>
|
|
||||||
)}
|
|
||||||
{col.key === "status" && (
|
|
||||||
<SkeletonCell width="60px" height={24} />
|
|
||||||
)}
|
|
||||||
</td>
|
|
||||||
))}
|
|
||||||
</tr>
|
|
||||||
))}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@ -1,116 +0,0 @@
|
|||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
|
||||||
import React, { useState,useCallback } from "react";
|
|
||||||
import { FormProvider, useForm, useFormContext } from "react-hook-form";
|
|
||||||
import { defaultFilterValues, filterSchema } from "./TenantSchema";
|
|
||||||
import Label from "../common/Label";
|
|
||||||
import SelectMultiple from "../common/SelectMultiple";
|
|
||||||
import { useIndustries } from "../../hooks/useTenant";
|
|
||||||
import { reference, TENANT_STATUS } from "../../utils/constants";
|
|
||||||
import { DateRangePicker1 } from "../common/DateRangePicker";
|
|
||||||
import moment from "moment";
|
|
||||||
|
|
||||||
const TenantFilterPanel = ({onApply}) => {
|
|
||||||
const [resetKey, setResetKey] = useState(0);
|
|
||||||
|
|
||||||
const methods = useForm({
|
|
||||||
resolver: zodResolver(filterSchema),
|
|
||||||
defaultValues: defaultFilterValues,
|
|
||||||
});
|
|
||||||
|
|
||||||
const { handleSubmit, reset } = methods;
|
|
||||||
const { data: industries = [], isLoading } = useIndustries();
|
|
||||||
|
|
||||||
const handleClosePanel = useCallback(() => {
|
|
||||||
document.querySelector(".offcanvas.show .btn-close")?.click();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const onSubmit = useCallback(
|
|
||||||
(formData) => {
|
|
||||||
onApply({
|
|
||||||
...formData,
|
|
||||||
startDate: moment.utc(formData.startDate, "DD-MM-YYYY").toISOString(),
|
|
||||||
endDate: moment.utc(formData.endDate, "DD-MM-YYYY").toISOString(),
|
|
||||||
});
|
|
||||||
handleClosePanel();
|
|
||||||
},
|
|
||||||
[onApply, handleClosePanel]
|
|
||||||
);
|
|
||||||
|
|
||||||
const onClear = useCallback(() => {
|
|
||||||
reset(defaultFilterValues);
|
|
||||||
setResetKey((prev) => prev + 1); // triggers DateRangePicker reset
|
|
||||||
onApply(defaultFilterValues);
|
|
||||||
}, [onApply, reset]);
|
|
||||||
|
|
||||||
if (isLoading) {
|
|
||||||
return <div className="text-center">Loading...</div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<FormProvider {...methods}>
|
|
||||||
<form onSubmit={handleSubmit(onSubmit)}>
|
|
||||||
<div className="text-start mb-1">
|
|
||||||
<div className="text-start my-2">
|
|
||||||
<DateRangePicker1
|
|
||||||
placeholder="DD-MM-YYYY To DD-MM-YYYY"
|
|
||||||
startField="startDate"
|
|
||||||
endField="endDate"
|
|
||||||
resetSignal={resetKey}
|
|
||||||
defaultRange={false}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="text-strat mb-2">
|
|
||||||
<SelectMultiple
|
|
||||||
name="industryIds"
|
|
||||||
label="Industries"
|
|
||||||
options={industries}
|
|
||||||
labelKey="name"
|
|
||||||
valueKey="id"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="text-start mb-2">
|
|
||||||
<SelectMultiple
|
|
||||||
name="references"
|
|
||||||
label="References"
|
|
||||||
options={reference}
|
|
||||||
labelKey="name"
|
|
||||||
valueKey="val"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="text-start">
|
|
||||||
<SelectMultiple
|
|
||||||
name="tenantStatusIds"
|
|
||||||
label="Tenant Status"
|
|
||||||
options={TENANT_STATUS}
|
|
||||||
labelKey="name"
|
|
||||||
valueKey="id"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{/* <SelectMultiple
|
|
||||||
name="references"
|
|
||||||
label="Industries :"
|
|
||||||
options={reference}
|
|
||||||
labelKey="name"
|
|
||||||
valueKey="val"
|
|
||||||
/> */}
|
|
||||||
</div>
|
|
||||||
<div className="d-flex justify-content-end py-3 gap-2">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className="btn btn-secondary btn-xs"
|
|
||||||
onClick={onClear}
|
|
||||||
|
|
||||||
>
|
|
||||||
Clear
|
|
||||||
</button>
|
|
||||||
<button type="submit" className="btn btn-primary btn-xs" >
|
|
||||||
Apply
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</FormProvider>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default TenantFilterPanel;
|
|
||||||
@ -1,212 +0,0 @@
|
|||||||
import React, { useState, useEffect } from "react";
|
|
||||||
import ContactInfro from "./ContactInfro";
|
|
||||||
import SubScription from "./SubScription";
|
|
||||||
import OrganizationInfo from "./OrganizationInfo";
|
|
||||||
import Congratulation from "./Congratulation";
|
|
||||||
import { useForm, FormProvider } from "react-hook-form";
|
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
|
||||||
import {
|
|
||||||
getStepFields,
|
|
||||||
getSubscriptionSchema,
|
|
||||||
newTenantSchema,
|
|
||||||
subscriptionDefaultValues,
|
|
||||||
tenantDefaultValues,
|
|
||||||
} from "./TenantSchema";
|
|
||||||
import { useSelector } from "react-redux";
|
|
||||||
|
|
||||||
const TenantForm = () => {
|
|
||||||
|
|
||||||
const HasSelectedCurrentTenant = useSelector(
|
|
||||||
(store) => store.globalVariables.currentTenant
|
|
||||||
);
|
|
||||||
const [activeTab, setActiveTab] = useState(0);
|
|
||||||
const [completedTabs, setCompletedTabs] = useState([]);
|
|
||||||
|
|
||||||
const PlanTextLabel =
|
|
||||||
HasSelectedCurrentTenant?.operationMode === 1
|
|
||||||
? "Upgrade Plan"
|
|
||||||
: "Select Plan";
|
|
||||||
|
|
||||||
// Jump to subscription if tenant already exists
|
|
||||||
useEffect(() => {
|
|
||||||
if (HasSelectedCurrentTenant) {
|
|
||||||
if (HasSelectedCurrentTenant.operationMode === 1) {
|
|
||||||
// Skip to subscription step
|
|
||||||
setActiveTab(2); // index for "SubScription"
|
|
||||||
setCompletedTabs([0, 1]); // mark previous steps as completed
|
|
||||||
} else if (HasSelectedCurrentTenant.operationMode === 0) {
|
|
||||||
setActiveTab(0); // start from beginning
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [HasSelectedCurrentTenant]);
|
|
||||||
|
|
||||||
const tenantForm = useForm({
|
|
||||||
resolver: zodResolver(newTenantSchema),
|
|
||||||
defaultValues: tenantDefaultValues,
|
|
||||||
});
|
|
||||||
|
|
||||||
const subscriptionForm = useForm({
|
|
||||||
resolver: zodResolver(getSubscriptionSchema(HasSelectedCurrentTenant?.data?.activeEmployees)),
|
|
||||||
defaultValues: subscriptionDefaultValues,
|
|
||||||
});
|
|
||||||
|
|
||||||
const getCurrentTrigger = () =>
|
|
||||||
activeTab === 2 ? subscriptionForm.trigger : tenantForm.trigger;
|
|
||||||
|
|
||||||
const handleNext = async () => {
|
|
||||||
const currentStepFields = getStepFields(activeTab);
|
|
||||||
const trigger = getCurrentTrigger();
|
|
||||||
const valid = await trigger(currentStepFields);
|
|
||||||
|
|
||||||
if (valid) {
|
|
||||||
setCompletedTabs((prev) => [...new Set([...prev, activeTab])]);
|
|
||||||
|
|
||||||
setActiveTab((prev) => {
|
|
||||||
let nextStep = Math.min(prev + 1, newTenantConfig.length - 1);
|
|
||||||
|
|
||||||
// Check tenant operationMode to decide navigation
|
|
||||||
if (
|
|
||||||
HasSelectedCurrentTenant &&
|
|
||||||
HasSelectedCurrentTenant.operationMode === 1 &&
|
|
||||||
nextStep === 2
|
|
||||||
) {
|
|
||||||
// If tenant already has subscription, show upgrade
|
|
||||||
nextStep = 2;
|
|
||||||
} else if (
|
|
||||||
HasSelectedCurrentTenant &&
|
|
||||||
[0, 2].includes(HasSelectedCurrentTenant.operationMode) &&
|
|
||||||
nextStep === 2
|
|
||||||
) {
|
|
||||||
// If tenant just created (0) OR exists without subscription (2)
|
|
||||||
// → stay on subscription tab
|
|
||||||
nextStep = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
return nextStep;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
const handlePrev = () => {
|
|
||||||
setActiveTab((prev) => Math.max(prev - 1, 0));
|
|
||||||
};
|
|
||||||
|
|
||||||
const onSubmitTenant = (data) => {
|
|
||||||
console.log("Tenant Data:", data);
|
|
||||||
};
|
|
||||||
|
|
||||||
const onSubmitSubScription = (data) => {
|
|
||||||
console.log("Subscription Data:", data);
|
|
||||||
};
|
|
||||||
|
|
||||||
const newTenantConfig = [
|
|
||||||
{
|
|
||||||
name: "Contact Info",
|
|
||||||
icon: "bx bx-user bx-md",
|
|
||||||
subtitle: "Provide Contact Details",
|
|
||||||
component: <ContactInfro onNext={handleNext} />,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Organization",
|
|
||||||
icon: "bx bx-buildings bx-md",
|
|
||||||
subtitle: "Organization Details",
|
|
||||||
component: (
|
|
||||||
<OrganizationInfo
|
|
||||||
onNext={handleNext}
|
|
||||||
onPrev={handlePrev}
|
|
||||||
onSubmitTenant={onSubmitTenant}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "SubScription",
|
|
||||||
icon: "bx bx-star bx-md",
|
|
||||||
subtitle: PlanTextLabel,
|
|
||||||
component: (
|
|
||||||
<SubScription
|
|
||||||
onSubmitSubScription={onSubmitSubScription}
|
|
||||||
onNext={handleNext}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Congratulation",
|
|
||||||
icon: "bx bx-check-circle bx-md",
|
|
||||||
subtitle: "Completed",
|
|
||||||
component: <Congratulation />,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const isSubscriptionTab = activeTab === 2;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
id="wizard-property-listing"
|
|
||||||
className="bs-stepper horizontically mt-2"
|
|
||||||
>
|
|
||||||
<div className="bs-stepper-header border-end text-start ">
|
|
||||||
{newTenantConfig
|
|
||||||
.filter((step) => step.name.toLowerCase() !== "congratulation")
|
|
||||||
.map((step, index) => {
|
|
||||||
const isActive = activeTab === index;
|
|
||||||
const isCompleted = completedTabs.includes(index);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<React.Fragment key={step.name}>
|
|
||||||
<div
|
|
||||||
className={`step ${isActive ? "active" : ""} ${
|
|
||||||
isCompleted ? "crossed" : ""
|
|
||||||
}`}
|
|
||||||
data-target={`#step-${index}`}
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className={`step-trigger ${isActive ? "active" : ""}`}
|
|
||||||
// onClick={() => setActiveTab(index)} // optional
|
|
||||||
>
|
|
||||||
<span className="bs-stepper-circle">
|
|
||||||
{isCompleted ? (
|
|
||||||
<i className="bx bx-check"></i>
|
|
||||||
) : (
|
|
||||||
<i className={step.icon}></i>
|
|
||||||
)}
|
|
||||||
</span>
|
|
||||||
<span className="bs-stepper-label">
|
|
||||||
<span className="bs-stepper-title">{step.name}</span>
|
|
||||||
<span className="bs-stepper-subtitle">
|
|
||||||
{step.subtitle}
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
{index < newTenantConfig.length - 1 && (
|
|
||||||
<div className="line text-primary"></div>
|
|
||||||
)}
|
|
||||||
</React.Fragment>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="bs-stepper-content py-2">
|
|
||||||
{isSubscriptionTab ? (
|
|
||||||
<FormProvider {...subscriptionForm}>
|
|
||||||
<form
|
|
||||||
onSubmit={subscriptionForm.handleSubmit(onSubmitSubScription)}
|
|
||||||
>
|
|
||||||
{newTenantConfig[activeTab].component}
|
|
||||||
</form>
|
|
||||||
</FormProvider>
|
|
||||||
) : (
|
|
||||||
<FormProvider {...tenantForm}>
|
|
||||||
<form onSubmit={tenantForm.handleSubmit(onSubmitTenant)}>
|
|
||||||
{newTenantConfig[activeTab].component}
|
|
||||||
</form>
|
|
||||||
</FormProvider>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default TenantForm;
|
|
||||||
@ -1,155 +0,0 @@
|
|||||||
import { z } from "zod";
|
|
||||||
|
|
||||||
export const newTenantSchema = z.object({
|
|
||||||
firstName: z
|
|
||||||
.string().trim()
|
|
||||||
.min(1, { message: "First Name is required!" })
|
|
||||||
.regex(/^[A-Za-z]+$/, { message: "First Name should contain only letters!" }),
|
|
||||||
lastName: z
|
|
||||||
.string().trim()
|
|
||||||
.min(1, { message: "Last Name is required!" })
|
|
||||||
.regex(/^[A-Za-z]+$/, { message: "Last Name should contain only letters!" }),
|
|
||||||
email: z.string().trim().email("Invalid email address"),
|
|
||||||
description: z.string().trim().optional(),
|
|
||||||
domainName: z.string().trim().nonempty("Domain name is required"),
|
|
||||||
billingAddress: z.string().trim().nonempty("Billing address is required"),
|
|
||||||
taxId: z.string().trim().nonempty("Tax ID is required"),
|
|
||||||
logoImage: z.string().trim().optional(),
|
|
||||||
organizationName: z.string().trim().nonempty("Organization name is required"),
|
|
||||||
officeNumber: z.string().trim().nonempty("Office number is required"),
|
|
||||||
contactNumber: z.string().trim()
|
|
||||||
.nonempty("Contact number is required")
|
|
||||||
.regex(/^\+?[1-9]\d{7,14}$/, "Enter a valid contact number"),
|
|
||||||
onBoardingDate: z.preprocess((val) => {
|
|
||||||
if (typeof val === "string" && val.includes("-")) {
|
|
||||||
const [day, month, year] = val.split("-");
|
|
||||||
return new Date(`${year}-${month}-${day}`);
|
|
||||||
}
|
|
||||||
return val;
|
|
||||||
}, z.date({
|
|
||||||
required_error: "Onboarding date is required",
|
|
||||||
invalid_type_error: "Invalid date format",
|
|
||||||
})),
|
|
||||||
organizationSize: z.string().nonempty("Organization size is required"),
|
|
||||||
industryId: z.string().uuid("Invalid industry ID"),
|
|
||||||
reference: z.string().nonempty("Reference is required"),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const tenantDefaultValues = {
|
|
||||||
firstName: "",
|
|
||||||
lastName: "",
|
|
||||||
email: "",
|
|
||||||
description: "",
|
|
||||||
domainName: "",
|
|
||||||
billingAddress: "",
|
|
||||||
taxId: "",
|
|
||||||
logoImage: "",
|
|
||||||
organizationName: "",
|
|
||||||
officeNumber: "",
|
|
||||||
contactNumber: "",
|
|
||||||
onBoardingDate: new Date(), // or `null` if you want it empty
|
|
||||||
organizationSize: "",
|
|
||||||
industryId: "", // should be a valid UUID if pre-filled
|
|
||||||
reference: "",
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export const getSubscriptionSchema = (minUsers) =>
|
|
||||||
z.object({
|
|
||||||
planId: z.string().min(1, { message: "Please select Plan" }),
|
|
||||||
currencyId: z.string().uuid("Invalid currency"),
|
|
||||||
maxUsers: z
|
|
||||||
.number({ invalid_type_error: "Must be a number" })
|
|
||||||
.min(minUsers, { message: `Team size must be greater than or equal to ${minUsers}` }),
|
|
||||||
frequency: z
|
|
||||||
.number({ invalid_type_error: "Frequency must be a number" })
|
|
||||||
.min(0, "Please select any one Frequency"),
|
|
||||||
isTrial: z.boolean(),
|
|
||||||
autoRenew: z.boolean(),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const subscriptionDefaultValues = {
|
|
||||||
// tenantId: "",
|
|
||||||
planId: "",
|
|
||||||
currencyId: "",
|
|
||||||
maxUsers: 1,
|
|
||||||
frequency: 1,
|
|
||||||
isTrial: false,
|
|
||||||
autoRenew: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
export const filterSchema = z.object({
|
|
||||||
industryIds: z.array(z.string()).optional(),
|
|
||||||
// createdByIds: z.array(z.string()).optional(),
|
|
||||||
tenantStatusIds: z.array(z.string()).optional(),
|
|
||||||
references: z.array(z.string()).optional(),
|
|
||||||
startDate: z.string().optional(),
|
|
||||||
endDate: z.string().optional(),
|
|
||||||
});
|
|
||||||
export const defaultFilterValues = {
|
|
||||||
industryIds: [],
|
|
||||||
// createdByIds: [],
|
|
||||||
tenantStatusIds: [],
|
|
||||||
references: [],
|
|
||||||
startDate:null,
|
|
||||||
endDate:null,
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getStepFields = (stepIndex) => {
|
|
||||||
const stepFieldMap = {
|
|
||||||
0: [
|
|
||||||
"firstName",
|
|
||||||
"lastName",
|
|
||||||
"email",
|
|
||||||
"contactNumber",
|
|
||||||
"billingAddress",
|
|
||||||
],
|
|
||||||
1: [
|
|
||||||
"organizationName",
|
|
||||||
"officeNumber",
|
|
||||||
"domainName",
|
|
||||||
"description",
|
|
||||||
"onBoardingDate",
|
|
||||||
"organizationSize",
|
|
||||||
"taxId",
|
|
||||||
"industryId",
|
|
||||||
"reference",
|
|
||||||
"logoImage",
|
|
||||||
],
|
|
||||||
2: [
|
|
||||||
"tenantId",
|
|
||||||
"planId",
|
|
||||||
"currencyId",
|
|
||||||
"maxUsers",
|
|
||||||
"frequency",
|
|
||||||
"isTrial",
|
|
||||||
"autoRenew",
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
return stepFieldMap[stepIndex] || [];
|
|
||||||
};
|
|
||||||
|
|
||||||
export const EditTenant = z.object({
|
|
||||||
firstName: z
|
|
||||||
.string().trim()
|
|
||||||
.min(1, { message: "First Name is required!" })
|
|
||||||
.regex(/^[A-Za-z]+$/, { message: "First Name should contain only letters!" }),
|
|
||||||
lastName: z
|
|
||||||
.string().trim()
|
|
||||||
.min(1, { message: "Last Name is required!" })
|
|
||||||
.regex(/^[A-Za-z]+$/, { message: "Last Name should contain only letters!" }),
|
|
||||||
description: z.string().trim().optional(),
|
|
||||||
domainName: z.string().trim().min(1, { message: "Domain Name is required!" }),
|
|
||||||
billingAddress: z.string().trim().min(1, { message: "Billing Address is required!" }),
|
|
||||||
taxId: z.string().trim().min(1, { message: "Tax ID is required!" }),
|
|
||||||
logoImage: z.string().optional(),
|
|
||||||
officeNumber: z.string().trim().min(1, { message: "Office Number is required!" }),
|
|
||||||
contactNumber: z.string().trim()
|
|
||||||
.nonempty("Contact number is required")
|
|
||||||
.regex(/^\+?[1-9]\d{7,14}$/, "Enter a valid contact number"),
|
|
||||||
organizationSize: z.string().min(1, { message: "Organization Size is required!" }),
|
|
||||||
industryId: z.string().min(1,{ message: "Invalid Industry ID!" }),
|
|
||||||
reference: z.string().optional(),
|
|
||||||
});
|
|
||||||
@ -1,194 +0,0 @@
|
|||||||
import React, { useEffect, useState } from "react";
|
|
||||||
import { useTenants } from "../../hooks/useTenant";
|
|
||||||
import { ITEMS_PER_PAGE } from "../../utils/constants";
|
|
||||||
import { getTenantStatus } from "../../utils/dateUtils";
|
|
||||||
import IconButton from "../common/IconButton";
|
|
||||||
import Pagination from "../common/Pagination";
|
|
||||||
import { TenantTableSkeleton } from "./TenanatSkeleton";
|
|
||||||
import { useTenantContext } from "../../pages/Tenant/TenantPage";
|
|
||||||
import { useNavigate } from "react-router-dom";
|
|
||||||
|
|
||||||
const TenantsList = ({
|
|
||||||
filters,
|
|
||||||
searchText,
|
|
||||||
setIsRefetching,
|
|
||||||
setRefetchFn,
|
|
||||||
}) => {
|
|
||||||
const [currentPage, setCurrentPage] = useState(1);
|
|
||||||
const navigate = useNavigate();
|
|
||||||
const {
|
|
||||||
data,
|
|
||||||
isLoading,
|
|
||||||
isError,
|
|
||||||
isInitialLoading,
|
|
||||||
error,
|
|
||||||
refetch,
|
|
||||||
isFetching,
|
|
||||||
} = useTenants(currentPage, filters, searchText);
|
|
||||||
|
|
||||||
const { setRefetching } = useTenantContext();
|
|
||||||
|
|
||||||
const paginate = (page) => {
|
|
||||||
if (page >= 1 && page <= (data?.totalPages ?? 1)) {
|
|
||||||
setCurrentPage(page);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Pass the refetch function to parent when component mounts
|
|
||||||
useEffect(() => {
|
|
||||||
setRefetchFn(() => refetch); // store in parent
|
|
||||||
}, [setRefetchFn, refetch]);
|
|
||||||
|
|
||||||
// Sync fetching status with parent
|
|
||||||
useEffect(() => {
|
|
||||||
setIsRefetching(isFetching);
|
|
||||||
}, [isFetching, setIsRefetching]);
|
|
||||||
|
|
||||||
const TenantColumns = [
|
|
||||||
{
|
|
||||||
key: "name",
|
|
||||||
label: "Organization",
|
|
||||||
getValue: (t) => (
|
|
||||||
<div
|
|
||||||
className="d-flex align-items-center py-1 cursor-pointer"
|
|
||||||
onClick={() => navigate(`/tenant/${t.id}`)}
|
|
||||||
>
|
|
||||||
{t.logoImage ? (
|
|
||||||
<img
|
|
||||||
src={t.logoImage}
|
|
||||||
alt={`${t.name} Logo`}
|
|
||||||
style={{
|
|
||||||
height: "25px",
|
|
||||||
width: "25px",
|
|
||||||
objectFit: "contain",
|
|
||||||
borderRadius: "4px",
|
|
||||||
}}
|
|
||||||
className="me-2"
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<IconButton
|
|
||||||
iconClass="bx bx-sm bx-building"
|
|
||||||
color="warning"
|
|
||||||
size={8}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{t.name || "N/A"}
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
align: "text-start",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "domainName",
|
|
||||||
label: "Domain",
|
|
||||||
getValue: (t) => (
|
|
||||||
<div style={{ width: "160px" }} className="text-truncate">
|
|
||||||
<a href={t.domainName} className="text-decoration-none">
|
|
||||||
<i className="bx bx-globe text-primary bx-xs me-2"></i>
|
|
||||||
{t.domainName || "N/A"}
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
align: "text-start",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "contactName",
|
|
||||||
label: "Contact Person",
|
|
||||||
getValue: (t) => (
|
|
||||||
<div className="d-flex align-items-center text-start">
|
|
||||||
<i className="bx bx-sm bx-user me-1" />
|
|
||||||
{t.contactName || "N/A"}
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
align: "text-start",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "contactNumber",
|
|
||||||
label: "Contact",
|
|
||||||
getValue: (t) => t.contactNumber || "N/A",
|
|
||||||
isAlwaysVisible: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "status",
|
|
||||||
label: "Status",
|
|
||||||
align: "text-center",
|
|
||||||
getValue: (t) => (
|
|
||||||
<span
|
|
||||||
className={`badge ${
|
|
||||||
getTenantStatus(t.tenantStatus?.id) || "secondary"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
{t.tenantStatus?.name || "Unknown"}
|
|
||||||
</span>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
if (isInitialLoading)
|
|
||||||
return <TenantTableSkeleton columns={TenantColumns} rows={13} />;
|
|
||||||
if (isError)
|
|
||||||
return (
|
|
||||||
<div className="">
|
|
||||||
<div className="card text-center my-4 p-2">
|
|
||||||
<i className="fa-solid fa-triangle-exclamation fs-5"></i>
|
|
||||||
<p>{error.message}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div className="card p-2 mt-3">
|
|
||||||
<div className="card-datatable text-nowrap table-responsive">
|
|
||||||
<table className="table border-top dataTable text-nowrap">
|
|
||||||
<thead>
|
|
||||||
<tr className="shadow-sm">
|
|
||||||
{TenantColumns.map((col) => (
|
|
||||||
<th key={col.key} className="sorting d-table-cell">
|
|
||||||
<div className={col.align}>{col.label}</div>
|
|
||||||
</th>
|
|
||||||
))}
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{data?.data.length > 0 ? (
|
|
||||||
data.data.map((tenant) => (
|
|
||||||
<tr key={tenant.id}>
|
|
||||||
{TenantColumns.map((col) => (
|
|
||||||
<td
|
|
||||||
key={col.key}
|
|
||||||
className={`d-table-cell px-3 py-2 align-middle ${
|
|
||||||
col.align ?? ""
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
{col.customRender
|
|
||||||
? col.customRender(tenant)
|
|
||||||
: col.getValue(tenant)}
|
|
||||||
</td>
|
|
||||||
))}
|
|
||||||
</tr>
|
|
||||||
))
|
|
||||||
) : (
|
|
||||||
<tr>
|
|
||||||
<td
|
|
||||||
colSpan={TenantColumns.length + 1}
|
|
||||||
className="text-center py-4 border-0"
|
|
||||||
>
|
|
||||||
No Tenants Found
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
)}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
{data?.data?.length > 0 && (
|
|
||||||
<Pagination
|
|
||||||
currentPage={currentPage}
|
|
||||||
totalPages={data.totalPages}
|
|
||||||
onPageChange={paginate}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default TenantsList;
|
|
||||||
@ -76,8 +76,7 @@ export const DateRangePicker1 = ({
|
|||||||
placeholder = "Select date range",
|
placeholder = "Select date range",
|
||||||
className = "",
|
className = "",
|
||||||
allowText = false,
|
allowText = false,
|
||||||
resetSignal,
|
resetSignal, // <- NEW prop
|
||||||
defaultRange = true,
|
|
||||||
...rest
|
...rest
|
||||||
}) => {
|
}) => {
|
||||||
const inputRef = useRef(null);
|
const inputRef = useRef(null);
|
||||||
@ -125,9 +124,10 @@ export const DateRangePicker1 = ({
|
|||||||
...rest,
|
...rest,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Apply default if empty
|
||||||
const currentStart = getValues(startField);
|
const currentStart = getValues(startField);
|
||||||
const currentEnd = getValues(endField);
|
const currentEnd = getValues(endField);
|
||||||
if (defaultRange && !currentStart && !currentEnd) {
|
if (!currentStart && !currentEnd) {
|
||||||
applyDefaultDates();
|
applyDefaultDates();
|
||||||
} else if (currentStart && currentEnd) {
|
} else if (currentStart && currentEnd) {
|
||||||
instance.setDate([
|
instance.setDate([
|
||||||
@ -139,11 +139,12 @@ export const DateRangePicker1 = ({
|
|||||||
return () => instance.destroy();
|
return () => instance.destroy();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
// Reapply default range on resetSignal change
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (defaultRange && resetSignal !== undefined) {
|
if (resetSignal !== undefined) {
|
||||||
applyDefaultDates();
|
applyDefaultDates();
|
||||||
}
|
}
|
||||||
}, [resetSignal, defaultRange]);
|
}, [resetSignal]);
|
||||||
|
|
||||||
const start = getValues(startField);
|
const start = getValues(startField);
|
||||||
const end = getValues(endField);
|
const end = getValues(endField);
|
||||||
@ -172,4 +173,3 @@ export const DateRangePicker1 = ({
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -15,7 +15,7 @@ const IconButton = ({
|
|||||||
iconClass, // icon class string like 'bx bx-user'
|
iconClass, // icon class string like 'bx bx-user'
|
||||||
color = "primary",
|
color = "primary",
|
||||||
onClick,
|
onClick,
|
||||||
size = 5,
|
size = 20,
|
||||||
radius=null,
|
radius=null,
|
||||||
style = {},
|
style = {},
|
||||||
...rest
|
...rest
|
||||||
@ -31,7 +31,7 @@ const IconButton = ({
|
|||||||
style={{
|
style={{
|
||||||
backgroundColor,
|
backgroundColor,
|
||||||
color: iconColor,
|
color: iconColor,
|
||||||
padding: "0.3rem",
|
padding: "0.4rem",
|
||||||
margin:'0rem 0.2rem',
|
margin:'0rem 0.2rem',
|
||||||
...style,
|
...style,
|
||||||
}}
|
}}
|
||||||
|
|||||||
@ -1,12 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
|
|
||||||
const Label = ({ htmlFor, children, required = false, className = "" }) => {
|
|
||||||
return (
|
|
||||||
<label htmlFor={htmlFor} className={`form-label d-block ${className}`}>
|
|
||||||
{children}
|
|
||||||
{required && <span className="text-danger ms-1">*</span>}
|
|
||||||
</label>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Label;
|
|
||||||
@ -2,20 +2,37 @@ import React from "react";
|
|||||||
|
|
||||||
const Loader = () => {
|
const Loader = () => {
|
||||||
return (
|
return (
|
||||||
<div
|
<div className="demo-inline-spacing">
|
||||||
className="d-flex justify-content-center align-items-center"
|
<div className="spinner-grow text-primary" role="status">
|
||||||
style={{ height: "50vh" }}
|
<span className="visually-hidden">Loading...</span>
|
||||||
>
|
|
||||||
<div className="sk-wave">
|
|
||||||
<div className="sk-wave-rect"></div>
|
|
||||||
<div className="sk-wave-rect"></div>
|
|
||||||
<div className="sk-wave-rect"></div>
|
|
||||||
<div className="sk-wave-rect"></div>
|
|
||||||
<div className="sk-wave-rect"></div>
|
|
||||||
</div>
|
</div>
|
||||||
|
{/* <div className="spinner-grow" role="status">
|
||||||
|
<span className="visually-hidden">Loading...</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="spinner-grow text-secondary" role="status">
|
||||||
|
<span className="visually-hidden">Loading...</span>
|
||||||
|
</div>
|
||||||
|
<div className="spinner-grow text-success" role="status">
|
||||||
|
<span className="visually-hidden">Loading...</span>
|
||||||
|
</div>
|
||||||
|
<div className="spinner-grow text-danger" role="status">
|
||||||
|
<span className="visually-hidden">Loading...</span>
|
||||||
|
</div>
|
||||||
|
<div className="spinner-grow text-warning" role="status">
|
||||||
|
<span className="visually-hidden">Loading...</span>
|
||||||
|
</div>
|
||||||
|
<div className="spinner-grow text-info" role="status">
|
||||||
|
<span className="visually-hidden">Loading...</span>
|
||||||
|
</div> */}
|
||||||
|
<div className="spinner-grow text-light" role="status">
|
||||||
|
<span className="visually-hidden">Loading...</span>
|
||||||
|
</div>
|
||||||
|
{/* <div className="spinner-grow text-dark" role="status">
|
||||||
|
<span className="visually-hidden">Loading...</span>
|
||||||
|
</div> */}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Loader;
|
export default Loader;
|
||||||
|
|
||||||
|
|||||||
@ -66,6 +66,7 @@
|
|||||||
"available": true,
|
"available": true,
|
||||||
"link": "/expenses"
|
"link": "/expenses"
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
"text": "Image Gallary",
|
"text": "Image Gallary",
|
||||||
"icon": "bx bx-images",
|
"icon": "bx bx-images",
|
||||||
@ -79,9 +80,9 @@
|
|||||||
"link": "",
|
"link": "",
|
||||||
"submenu": [
|
"submenu": [
|
||||||
{
|
{
|
||||||
"text": "Tenant",
|
"text": "Users",
|
||||||
"available": true,
|
"available": true,
|
||||||
"link": "/tenants"
|
"link": "/employees/"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"text": "Masters",
|
"text": "Masters",
|
||||||
|
|||||||
@ -11,15 +11,7 @@ import showToast from "../../services/toastService";
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
export const useMasterMenu = ()=>{
|
|
||||||
return useQuery({
|
|
||||||
queryKey:["MasterMenu"],
|
|
||||||
queryFn:async()=> {
|
|
||||||
const resp = await MasterRespository.getMasterMenus();
|
|
||||||
return resp.data
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useActivitiesMaster = () =>
|
export const useActivitiesMaster = () =>
|
||||||
{
|
{
|
||||||
|
|||||||
@ -99,13 +99,3 @@ export const useProfile = () => {
|
|||||||
refetch,
|
refetch,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
export const useSidBarMenu = ()=>{
|
|
||||||
const userLogged = useSelector((store)=>store.globalVariables.loginUser);
|
|
||||||
return useQuery({
|
|
||||||
queryKey:["AppMenu"],
|
|
||||||
queryFn:async()=> await AuthRepository.appmenu(),
|
|
||||||
enabled: !!userLogged
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@ -175,7 +175,6 @@ export const useProjectInfra = (projectId) => {
|
|||||||
} = useQuery({
|
} = useQuery({
|
||||||
queryKey: ["ProjectInfra", projectId],
|
queryKey: ["ProjectInfra", projectId],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
if(!projectId) return null;
|
|
||||||
const res = await ProjectRepository.getProjectInfraByproject(projectId);
|
const res = await ProjectRepository.getProjectInfraByproject(projectId);
|
||||||
return res.data;
|
return res.data;
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,177 +0,0 @@
|
|||||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
|
||||||
import { TenantRepository } from "../repositories/TenantRepository";
|
|
||||||
import { MarketRepository } from "../repositories/MarketRepository";
|
|
||||||
import showToast from "../services/toastService";
|
|
||||||
import { useDispatch } from "react-redux";
|
|
||||||
import { setCurrentTenant } from "../slices/globalVariablesSlice";
|
|
||||||
import { ITEMS_PER_PAGE } from "../utils/constants";
|
|
||||||
import moment from "moment";
|
|
||||||
|
|
||||||
const cleanFilter = (filter) => {
|
|
||||||
const cleaned = { ...filter };
|
|
||||||
|
|
||||||
["industryIds", "references"].forEach((key) => {
|
|
||||||
if (Array.isArray(cleaned[key]) && cleaned[key].length === 0) {
|
|
||||||
delete cleaned[key];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
return cleaned;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useTenants = (pageNumber, filter, searchString = "") => {
|
|
||||||
return useQuery({
|
|
||||||
queryKey: ["Tenants", pageNumber, filter, searchString],
|
|
||||||
queryFn: async () => {
|
|
||||||
const cleanedFilter = cleanFilter(filter);
|
|
||||||
const response = await TenantRepository.getTenantList(
|
|
||||||
ITEMS_PER_PAGE,
|
|
||||||
pageNumber,
|
|
||||||
cleanedFilter,
|
|
||||||
searchString
|
|
||||||
);
|
|
||||||
return response.data;
|
|
||||||
},
|
|
||||||
keepPreviousData: true,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useTenantDetails = (id) => {
|
|
||||||
return useQuery({
|
|
||||||
queryKey: ["Tenant", id],
|
|
||||||
queryFn: async () => {
|
|
||||||
const response = await TenantRepository.getTenantDetails(id);
|
|
||||||
return response.data;
|
|
||||||
},
|
|
||||||
enabled:!!id
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useIndustries = () => {
|
|
||||||
return useQuery({
|
|
||||||
queryKey: ["Industries"],
|
|
||||||
queryFn: async () => {
|
|
||||||
const res = await MarketRepository.getIndustries();
|
|
||||||
return res.data;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useSubscriptionPlan = (freq) => {
|
|
||||||
return useQuery({
|
|
||||||
queryKey: ["SubscriptionPlan", freq],
|
|
||||||
queryFn: async () => {
|
|
||||||
const res = await TenantRepository.getSubscriptionPlan(freq);
|
|
||||||
return res.data;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// ------------Mutation---------------------
|
|
||||||
|
|
||||||
export const useCreateTenant = (onSuccessCallback) => {
|
|
||||||
const dispatch = useDispatch();
|
|
||||||
return useMutation({
|
|
||||||
mutationFn: async (tenantPayload) => {
|
|
||||||
const res = await TenantRepository.createTenant(tenantPayload);
|
|
||||||
return res.data;
|
|
||||||
},
|
|
||||||
onSuccess: (data, variables) => {
|
|
||||||
showToast("Tenant Created SuccessFully", "success");
|
|
||||||
// dispatch(setCurrentTenant({operationMode:0,data:data}))
|
|
||||||
let operationMode = 0; // default = new tenant, needs subscription
|
|
||||||
if (data?.subscriptionHistery?.length > 0) {
|
|
||||||
operationMode = 1; // tenant already has a subscription
|
|
||||||
} else if (data && !data.subscriptionHistery) {
|
|
||||||
operationMode = 2; // tenant exists but subscription not added yet
|
|
||||||
}
|
|
||||||
|
|
||||||
dispatch(setCurrentTenant({ operationMode, data }));
|
|
||||||
|
|
||||||
if (onSuccessCallback) onSuccessCallback();
|
|
||||||
},
|
|
||||||
onError: (error) => {
|
|
||||||
showToast(
|
|
||||||
error.response.message ||
|
|
||||||
error?.response?.data?.errors ||
|
|
||||||
`Something went wrong`,
|
|
||||||
"error"
|
|
||||||
);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
export const useUpdateTenantDetails = (onSuccessCallback) => {
|
|
||||||
const queryClient = useQueryClient();
|
|
||||||
|
|
||||||
return useMutation({
|
|
||||||
mutationFn: async ({ id, tenantPayload }) =>
|
|
||||||
TenantRepository.updateTenantDetails(id, tenantPayload),
|
|
||||||
onSuccess: (_, variables) => {
|
|
||||||
const { id } = variables.tenantPayload;
|
|
||||||
queryClient.invalidateQueries({ queryKey: ["Tenant", id] });
|
|
||||||
queryClient.invalidateQueries({ queryKey: ["Tenants"] });
|
|
||||||
if (onSuccessCallback) onSuccessCallback();
|
|
||||||
},
|
|
||||||
onError: (error) => {
|
|
||||||
showToast(
|
|
||||||
error.response.message || error.message || `Something went wrong`,
|
|
||||||
"error"
|
|
||||||
);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useAddSubscription = (onSuccessCallback) => {
|
|
||||||
const queryClient = useQueryClient();
|
|
||||||
return useMutation({
|
|
||||||
mutationFn: async (subscriptionPayload) => {
|
|
||||||
const res = await TenantRepository.addSubscription(subscriptionPayload);
|
|
||||||
return res.data;
|
|
||||||
},
|
|
||||||
onSuccess: (data, variables) => {
|
|
||||||
const { tenantId } = variables;
|
|
||||||
showToast("Tenant Plan Added SuccessFully", "success");
|
|
||||||
queryClient.invalidateQueries({ queryKey: ["Tenant", tenantId] });
|
|
||||||
if (onSuccessCallback) onSuccessCallback();
|
|
||||||
},
|
|
||||||
onError: (error) => {
|
|
||||||
showToast(
|
|
||||||
error.response.message || error.message || `Something went wrong`,
|
|
||||||
"error"
|
|
||||||
);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useUpgradeSubscription = (onSuccessCallback) => {
|
|
||||||
const queryClient = useQueryClient();
|
|
||||||
|
|
||||||
return useMutation({
|
|
||||||
mutationFn: async (subscriptionPayload) => {
|
|
||||||
const res = await TenantRepository.upgradeSubscription(
|
|
||||||
subscriptionPayload
|
|
||||||
);
|
|
||||||
return res.data;
|
|
||||||
},
|
|
||||||
onSuccess: (data, variables) => {
|
|
||||||
const { tenantId } = variables;
|
|
||||||
showToast("Tenant Plan Upgraded Successfully", "success");
|
|
||||||
|
|
||||||
// Refetch tenant details
|
|
||||||
queryClient.invalidateQueries({ queryKey: ["Tenant", tenantId] });
|
|
||||||
queryClient.invalidateQueries({ queryKey: ["Tenants"] });
|
|
||||||
|
|
||||||
if (onSuccessCallback) onSuccessCallback();
|
|
||||||
},
|
|
||||||
onError: (error) => {
|
|
||||||
showToast(
|
|
||||||
error?.response?.message ||
|
|
||||||
error?.response?.data?.errors ||
|
|
||||||
error.message ||
|
|
||||||
"Something went wrong",
|
|
||||||
"error"
|
|
||||||
);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
@ -1,28 +1,31 @@
|
|||||||
import React, { useEffect, useMemo, useRef, useState } from "react";
|
import React, { useEffect, useState, useRef } from "react";
|
||||||
import { useDispatch } from "react-redux";
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
import { useTaskList } from "../../hooks/useTasks";
|
|
||||||
import { useProjectName } from "../../hooks/useProjects";
|
|
||||||
import { setProjectId } from "../../slices/localVariablesSlice";
|
|
||||||
import Breadcrumb from "../../components/common/Breadcrumb";
|
import Breadcrumb from "../../components/common/Breadcrumb";
|
||||||
import DateRangePicker from "../../components/common/DateRangePicker";
|
import { useTaskList } from "../../hooks/useTasks";
|
||||||
import FilterIcon from "../../components/common/FilterIcon";
|
import { useProjectName, useProjects } from "../../hooks/useProjects";
|
||||||
import GlobalModel from "../../components/common/GlobalModel";
|
import { setProjectId } from "../../slices/localVariablesSlice";
|
||||||
import ReportTask from "../../components/Activities/ReportTask";
|
import { ReportTask } from "../../components/Activities/ReportTask";
|
||||||
import ReportTaskComments from "../../components/Activities/ReportTaskComments";
|
import ReportTaskComments from "../../components/Activities/ReportTaskComments";
|
||||||
|
import DateRangePicker from "../../components/common/DateRangePicker";
|
||||||
|
import { useSearchParams } from "react-router-dom";
|
||||||
|
import moment from "moment";
|
||||||
|
import FilterIcon from "../../components/common/FilterIcon";
|
||||||
|
import GlobalModel from "../../components/common/GlobalModel";
|
||||||
|
import AssignTask from "../../components/Project/AssignTask";
|
||||||
import SubTask from "../../components/Activities/SubTask";
|
import SubTask from "../../components/Activities/SubTask";
|
||||||
import { formatNumber, formatUTCToLocalTime } from "../../utils/dateUtils";
|
import {formatNumber} from "../../utils/dateUtils";
|
||||||
import { useHasUserPermission } from "../../hooks/useHasUserPermission";
|
import { useHasUserPermission } from "../../hooks/useHasUserPermission";
|
||||||
import { APPROVE_TASK, ASSIGN_REPORT_TASK } from "../../utils/constants";
|
import { APPROVE_TASK, ASSIGN_REPORT_TASK } from "../../utils/constants";
|
||||||
import { useSelectedproject } from "../../slices/apiDataManager";
|
import { useSelectedproject } from "../../slices/apiDataManager";
|
||||||
import moment from "moment";
|
|
||||||
import Loader from "../../components/common/Loader";
|
|
||||||
|
|
||||||
const DailyTask = () => {
|
const DailyTask = () => {
|
||||||
const dispatch = useDispatch();
|
// const selectedProject = useSelector(
|
||||||
|
// (store) => store.localVariables.projectId
|
||||||
|
// );
|
||||||
const selectedProject = useSelectedproject();
|
const selectedProject = useSelectedproject();
|
||||||
const { projectNames } = useProjectName();
|
const dispatch = useDispatch()
|
||||||
const ApprovedTaskRights = useHasUserPermission(APPROVE_TASK);
|
const { projectNames, loading: projectLoading, fetchData } = useProjectName();
|
||||||
const ReportTaskRights = useHasUserPermission(ASSIGN_REPORT_TASK);
|
|
||||||
|
|
||||||
const [filters, setFilters] = useState({
|
const [filters, setFilters] = useState({
|
||||||
selectedBuilding: "",
|
selectedBuilding: "",
|
||||||
@ -30,201 +33,427 @@ const DailyTask = () => {
|
|||||||
selectedActivities: [],
|
selectedActivities: [],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const [dateRange, setDateRange] = useState({ startDate: "", endDate: "" });
|
const [dateRange, setDateRange] = useState({ startDate: "", endDate: "" });
|
||||||
|
const ApprovedTaskRights = useHasUserPermission(APPROVE_TASK)
|
||||||
|
const ReportTaskRights = useHasUserPermission(ASSIGN_REPORT_TASK)
|
||||||
|
|
||||||
const { TaskList, loading: taskLoading } = useTaskList(
|
const {
|
||||||
selectedProject || null,
|
TaskList,
|
||||||
dateRange?.startDate || null,
|
loading: task_loading,
|
||||||
|
error: task_error,
|
||||||
|
refetch,
|
||||||
|
} = useTaskList(
|
||||||
|
selectedProject || null,
|
||||||
|
dateRange?.startDate || null,
|
||||||
dateRange?.endDate || null
|
dateRange?.endDate || null
|
||||||
);
|
);
|
||||||
|
|
||||||
// Ensure project is set
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!selectedProject && projectNames.length > 0) {
|
if(selectedProject == null){
|
||||||
dispatch(setProjectId(projectNames[0].id));
|
dispatch(setProjectId(projectNames[0]?.id));
|
||||||
|
}
|
||||||
|
},[])
|
||||||
|
const [TaskLists, setTaskLists] = useState([]);
|
||||||
|
const [dates, setDates] = useState([]);
|
||||||
|
const popoverRefs = useRef([]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (TaskList) {
|
||||||
|
let filteredTasks = TaskList;
|
||||||
|
|
||||||
|
if (filters.selectedBuilding) {
|
||||||
|
filteredTasks = filteredTasks.filter(
|
||||||
|
(task) =>
|
||||||
|
task?.workItem?.workArea?.floor?.building?.name ===
|
||||||
|
filters.selectedBuilding
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filters.selectedFloors.length > 0) {
|
||||||
|
filteredTasks = filteredTasks?.filter((task) =>
|
||||||
|
filters.selectedFloors?.includes(
|
||||||
|
task?.workItem?.workArea?.floor?.floorName
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filters.selectedActivities.length > 0) {
|
||||||
|
filteredTasks = filteredTasks.filter((task) =>
|
||||||
|
filters.selectedActivities.includes(
|
||||||
|
task?.workItem?.activityMaster?.activityName
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
setTaskLists(filteredTasks);
|
||||||
|
} else {
|
||||||
|
setTaskLists([]);
|
||||||
}
|
}
|
||||||
}, [selectedProject, projectNames, dispatch]);
|
}, [
|
||||||
|
TaskList,
|
||||||
|
filters?.selectedBuilding,
|
||||||
|
filters?.selectedFloors,
|
||||||
|
filters?.selectedActivities,
|
||||||
|
]);
|
||||||
|
|
||||||
// Memoized filtering
|
useEffect(() => {
|
||||||
const filteredTasks = useMemo(() => {
|
const AssignmentDates = [
|
||||||
if (!TaskList) return [];
|
...new Set(TaskLists.map((task) => task.assignmentDate.split("T")[0])),
|
||||||
return TaskList.filter((task) => {
|
].sort((a, b) => new Date(b) - new Date(a));
|
||||||
const { selectedBuilding, selectedFloors, selectedActivities } = filters;
|
setDates(AssignmentDates);
|
||||||
|
}, [TaskLists]);
|
||||||
|
|
||||||
if (selectedBuilding && task?.workItem?.workArea?.floor?.building?.name !== selectedBuilding) return false;
|
const [selectedTask, selectTask] = useState(null);
|
||||||
if (selectedFloors.length > 0 && !selectedFloors.includes(task?.workItem?.workArea?.floor?.floorName)) return false;
|
const [comments, setComment] = useState({ task: null, isActionAllow: false });
|
||||||
if (selectedActivities.length > 0 && !selectedActivities.includes(task?.workItem?.activityMaster?.activityName)) return false;
|
|
||||||
|
|
||||||
return true;
|
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 [IsSubTaskNeeded, setIsSubTaskNeeded] = useState(false);
|
||||||
|
const [SubTaskData, setSubTaskData] = useState();
|
||||||
|
const handletask = (task) => {
|
||||||
|
selectTask(task);
|
||||||
|
openModal();
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
popoverRefs.current.forEach((el) => {
|
||||||
|
if (el) {
|
||||||
|
new bootstrap.Popover(el, {
|
||||||
|
trigger: "focus",
|
||||||
|
placement: "left",
|
||||||
|
html: true,
|
||||||
|
content: el.getAttribute("data-bs-content"),
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}, [TaskList, filters]);
|
}, [dates, TaskLists]);
|
||||||
|
|
||||||
// Memoized dates
|
|
||||||
const groupedTasks = useMemo(() => {
|
|
||||||
const groups = {};
|
|
||||||
filteredTasks.forEach((task) => {
|
|
||||||
const date = task.assignmentDate.split("T")[0];
|
|
||||||
if (!groups[date]) groups[date] = [];
|
|
||||||
groups[date].push(task);
|
|
||||||
});
|
|
||||||
return Object.keys(groups)
|
|
||||||
.sort((a, b) => new Date(b) - new Date(a))
|
|
||||||
.map((date) => ({ date, tasks: groups[date] }));
|
|
||||||
}, [filteredTasks]);
|
|
||||||
|
|
||||||
// --- Modal State
|
const handlecloseModal = () =>
|
||||||
const [modal, setModal] = useState({ type: null, data: null });
|
{
|
||||||
|
setIsModalOpen( false )
|
||||||
|
// refetch();
|
||||||
|
}
|
||||||
|
|
||||||
const openModal = (type, data = null) => setModal({ type, data });
|
const handleCloseAction = (IsSubTask) => {
|
||||||
const closeModal = () => setModal({ type: null, data: null });
|
if (IsSubTask) {
|
||||||
|
setIsSubTaskNeeded(true);
|
||||||
|
setIsModalOpenComment(false);
|
||||||
|
} else {
|
||||||
|
// refetch();
|
||||||
|
setIsModalOpenComment(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const hanleCloseSubTask = () => {
|
||||||
|
setIsSubTaskNeeded(false);
|
||||||
|
setComment( null );
|
||||||
|
// refetch();
|
||||||
|
};
|
||||||
|
|
||||||
// --- Render helpers
|
|
||||||
const renderTeamMembers = (task, refIndex) => (
|
|
||||||
<div
|
|
||||||
key={refIndex}
|
|
||||||
tabIndex="0"
|
|
||||||
className="d-flex align-items-center avatar-group justify-content-center"
|
|
||||||
data-bs-toggle="popover"
|
|
||||||
data-bs-trigger="focus"
|
|
||||||
data-bs-placement="left"
|
|
||||||
data-bs-html="true"
|
|
||||||
data-bs-content={`
|
|
||||||
<div class="border border-secondary rounded custom-popover p-2 px-3">
|
|
||||||
${task.teamMembers
|
|
||||||
.map(
|
|
||||||
(m) => `
|
|
||||||
<div class="d-flex align-items-center gap-2 mb-2">
|
|
||||||
<div class="avatar avatar-xs">
|
|
||||||
<span class="avatar-initial rounded-circle bg-label-primary">
|
|
||||||
${m?.firstName?.charAt(0) || ""}${m?.lastName?.charAt(0) || ""}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<span>${m.firstName} ${m.lastName}</span>
|
|
||||||
</div>`
|
|
||||||
)
|
|
||||||
.join("")}
|
|
||||||
</div>
|
|
||||||
`}
|
|
||||||
>
|
|
||||||
{task.teamMembers.slice(0, 3).map((m) => (
|
|
||||||
<div key={m.id} className="avatar avatar-xs" title={`${m.firstName} ${m.lastName}`}>
|
|
||||||
<span className="avatar-initial rounded-circle bg-label-primary">
|
|
||||||
{m?.firstName.slice(0, 1)}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
{task.teamMembers.length > 3 && (
|
|
||||||
<div className="avatar avatar-xs" title={`${task.teamMembers.length - 3} more`}>
|
|
||||||
<span className="avatar-initial rounded-circle bg-label-secondary">+{task.teamMembers.length - 3}</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{/* --- Modals --- */}
|
{isModalOpen && <GlobalModel isOpen={isModalOpen} size="md" closeModal={handlecloseModal} >
|
||||||
{modal.type === "report" && (
|
<ReportTask
|
||||||
<GlobalModel isOpen size="md" closeModal={closeModal}>
|
report={selectedTask}
|
||||||
<ReportTask report={modal.data} closeModal={closeModal} />
|
closeModal={handlecloseModal}
|
||||||
</GlobalModel>
|
// refetch={refetch}
|
||||||
)}
|
/>
|
||||||
{modal.type === "comments" && (
|
</GlobalModel>}
|
||||||
<GlobalModel isOpen size="lg" closeModal={closeModal}>
|
|
||||||
|
{isModalOpenComment && (
|
||||||
|
<GlobalModel
|
||||||
|
isOpen={isModalOpenComment}
|
||||||
|
size="lg"
|
||||||
|
closeModal={() => setIsModalOpenComment(false)}
|
||||||
|
>
|
||||||
<ReportTaskComments
|
<ReportTaskComments
|
||||||
commentsData={modal.data.task}
|
commentsData={comments.task}
|
||||||
actionAllow={modal.data.isActionAllow}
|
actionAllow={comments.isActionAllow}
|
||||||
handleCloseAction={(isSubTask) => {
|
handleCloseAction={handleCloseAction}
|
||||||
if (isSubTask) openModal("subtask", modal.data.task);
|
closeModal={closeCommentModal}
|
||||||
else closeModal();
|
|
||||||
}}
|
|
||||||
closeModal={closeModal}
|
|
||||||
/>
|
/>
|
||||||
</GlobalModel>
|
</GlobalModel>
|
||||||
)}
|
)}
|
||||||
{modal.type === "subtask" && (
|
|
||||||
<GlobalModel isOpen size="lg" closeModal={closeModal}>
|
{IsSubTaskNeeded && (
|
||||||
<SubTask activity={modal.data} onClose={closeModal} />
|
<GlobalModel
|
||||||
|
isOpen={IsSubTaskNeeded}
|
||||||
|
size="lg"
|
||||||
|
closeModal={hanleCloseSubTask}
|
||||||
|
>
|
||||||
|
<SubTask activity={comments.task} onClose={hanleCloseSubTask} />
|
||||||
</GlobalModel>
|
</GlobalModel>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="container-fluid">
|
<div className="container-fluid">
|
||||||
<Breadcrumb data={[{ label: "Home", link: "/dashboard" }, { label: "Daily Progress Report" }]} />
|
<Breadcrumb
|
||||||
|
data={[
|
||||||
<div className="card card-action mb-6">
|
{ label: "Home", link: "/dashboard" },
|
||||||
|
{ label: "Daily Progress Report", link: null },
|
||||||
|
]}
|
||||||
|
></Breadcrumb>
|
||||||
|
<div className="card card-action mb-6 ">
|
||||||
<div className="card-body p-1 p-sm-2">
|
<div className="card-body p-1 p-sm-2">
|
||||||
{!selectedProject && (<div className="text-center text-muted">Please Select Project</div>)}
|
<div className="row d-flex justify-content-between align-items-center">
|
||||||
{/* --- Filters --- */}
|
<div className="col-md-12 d-flex align-items-center col-12 text-start mb-2 mb-md-0">
|
||||||
<div className="d-flex align-items-center mb-2">
|
<DateRangePicker
|
||||||
<DateRangePicker onRangeChange={setDateRange} endDateMode="today" DateDifference="6" dateFormat="DD-MM-YYYY" />
|
onRangeChange={setDateRange}
|
||||||
<FilterIcon
|
endDateMode="today"
|
||||||
taskListData={TaskList}
|
DateDifference="6"
|
||||||
onApplyFilters={setFilters}
|
dateFormat="DD-MM-YYYY"
|
||||||
currentSelectedBuilding={filters.selectedBuilding}
|
/>
|
||||||
currentSelectedFloors={filters.selectedFloors}
|
<FilterIcon
|
||||||
currentSelectedActivities={filters.selectedActivities}
|
taskListData={TaskList}
|
||||||
/>
|
onApplyFilters={setFilters}
|
||||||
|
currentSelectedBuilding={filters.selectedBuilding}
|
||||||
|
currentSelectedFloors={filters.selectedFloors}
|
||||||
|
currentSelectedActivities={filters.selectedActivities}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* --- Table --- */}
|
|
||||||
<div className="table-responsive text-nowrap mt-3">
|
<div className="table-responsive text-nowrap mt-3">
|
||||||
<table className="table">
|
<table className="table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Activity</th>
|
<th>Activity</th>
|
||||||
<th>Assigned</th>
|
<th>Assigned </th>
|
||||||
<th>Completed</th>
|
<th>Completed</th>
|
||||||
<th>Assign On</th>
|
<th>Assign On</th>
|
||||||
<th>Team</th>
|
<th>Team</th>
|
||||||
<th>Actions</th>
|
<th>Actions</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody className="table-border-bottom-0">
|
||||||
{taskLoading && (
|
{/* --- Spinner when tasks are loading --- */}
|
||||||
|
{task_loading && (
|
||||||
<tr>
|
<tr>
|
||||||
<td colSpan={6} className="text-center">
|
<td colSpan={6} className="text-center">
|
||||||
<Loader/>
|
{" "}
|
||||||
|
<div className="mt-10 mb-10 pt-5 pb-10">
|
||||||
|
<div
|
||||||
|
className="spinner-border text-primary"
|
||||||
|
role="status"
|
||||||
|
>
|
||||||
|
<span className="visually-hidden">Loading...</span>
|
||||||
|
</div>
|
||||||
|
<p className="mt-2">Loading Tasks...</p>
|
||||||
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
)}
|
)}
|
||||||
{!taskLoading && groupedTasks.length === 0 && (
|
{!task_loading &&
|
||||||
<tr>
|
TaskLists.length === 0 && (
|
||||||
<td colSpan={6} className="text-center">No Reports Found</td>
|
<tr>
|
||||||
</tr>
|
<td colSpan={6} className="text-center">
|
||||||
)}
|
<div className="mt-10 mb-10 pt-10 pb-10">
|
||||||
{!taskLoading &&
|
{" "}
|
||||||
groupedTasks.map(({ date, tasks }) => (
|
<p>No Reports Found</p>
|
||||||
<React.Fragment key={date}>
|
</div>
|
||||||
<tr className="table-row-header text-start">
|
</td>
|
||||||
<td colSpan={6}><strong>{formatUTCToLocalTime(date)}</strong></td>
|
</tr>
|
||||||
</tr>
|
)}
|
||||||
{tasks.map((task, idx) => (
|
{!task_loading &&
|
||||||
<tr key={task.id || idx}>
|
TaskLists.length > 0 &&
|
||||||
<td className="flex-wrap text-start">
|
dates.map((date, i) => {
|
||||||
<div>{task.workItem.activityMaster?.activityName || "No Activity Name"}</div>
|
const tasksForDate = TaskLists.filter((task) =>
|
||||||
<div className="text-sm">
|
task.assignmentDate.includes(date)
|
||||||
{task.workItem.workArea?.floor?.building?.name} › {task.workItem.workArea?.floor?.floorName} › {task.workItem.workArea?.areaName}
|
);
|
||||||
</div>
|
if (tasksForDate.length === 0) return null;
|
||||||
</td>
|
|
||||||
<td>{formatNumber(task.plannedTask)} / {formatNumber(task.workItem.plannedWork - task.workItem.completedWork)}</td>
|
return (
|
||||||
<td>{task.completedTask}</td>
|
<React.Fragment key={i}>
|
||||||
<td>{formatUTCToLocalTime(task.assignmentDate)}</td>
|
<tr className="table-row-header">
|
||||||
<td className="text-center">{renderTeamMembers(task, idx)}</td>
|
<td colSpan={6} className="text-start">
|
||||||
<td className="text-center">
|
{" "}
|
||||||
<div className="d-flex justify-content-end gap-2">
|
<strong>
|
||||||
{ReportTaskRights && !task.reportedDate && (
|
{moment(date).format("DD-MM-YYYY")}
|
||||||
<button className="btn btn-xs btn-primary" onClick={() => openModal("report", task)}>Report</button>
|
</strong>
|
||||||
)}
|
|
||||||
{ApprovedTaskRights && task.reportedDate && !task.approvedBy && (
|
|
||||||
<button className="btn btn-xs btn-warning" onClick={() => openModal("comments", { task, isActionAllow: true })}>QC</button>
|
|
||||||
)}
|
|
||||||
<button className="btn btn-xs btn-primary" onClick={() => openModal("comments", { task, isActionAllow: false })}>Comment</button>
|
|
||||||
</div>
|
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
))}
|
{tasksForDate.map((task, index) => {
|
||||||
</React.Fragment>
|
const refIndex = index * 10 + i;
|
||||||
))}
|
return (
|
||||||
|
<React.Fragment key={refIndex}>
|
||||||
|
<tr>
|
||||||
|
<td className="flex-wrap text-start">
|
||||||
|
<div>
|
||||||
|
{task.workItem.activityMaster
|
||||||
|
.activityName || "No Activity Name"}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="col-form-label text-sm">
|
||||||
|
{" "}
|
||||||
|
{
|
||||||
|
task?.workItem?.workArea?.floor
|
||||||
|
?.building?.name
|
||||||
|
}{" "}
|
||||||
|
<i
|
||||||
|
className="bx bx-chevron-right text-sm"
|
||||||
|
style={{ fontSize: ".75rem" }}
|
||||||
|
></i>{" "}
|
||||||
|
{
|
||||||
|
task?.workItem?.workArea?.floor
|
||||||
|
?.floorName
|
||||||
|
}{" "}
|
||||||
|
<i
|
||||||
|
className="bx bx-chevron-right text-sm"
|
||||||
|
style={{ fontSize: ".75rem" }}
|
||||||
|
>
|
||||||
|
{" "}
|
||||||
|
</i>
|
||||||
|
{task?.workItem?.workArea?.areaName}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{formatNumber(task.plannedTask)} / {formatNumber(task.workItem.plannedWork - task.workItem.completedWork)}
|
||||||
|
</td>
|
||||||
|
<td>{task.completedTask}</td>
|
||||||
|
<td>
|
||||||
|
{moment(task.assignmentDate).format(
|
||||||
|
"DD-MM-YYYY"
|
||||||
|
)}
|
||||||
|
</td>
|
||||||
|
<td className="text-center">
|
||||||
|
<div
|
||||||
|
key={refIndex}
|
||||||
|
ref={(el) =>
|
||||||
|
(popoverRefs.current[refIndex] = el)
|
||||||
|
}
|
||||||
|
tabIndex="0"
|
||||||
|
className="d-flex align-items-center avatar-group justify-content-center"
|
||||||
|
data-bs-toggle="popover"
|
||||||
|
data-bs-trigger="focus"
|
||||||
|
data-bs-placement="left"
|
||||||
|
data-bs-html="true"
|
||||||
|
data-bs-content={`
|
||||||
|
<div class="border border-secondary rounded custom-popover p-2 px-3">
|
||||||
|
${task.teamMembers
|
||||||
|
.map(
|
||||||
|
(member) => `
|
||||||
|
<div class="d-flex align-items-center gap-2 mb-2">
|
||||||
|
<div class="avatar avatar-xs">
|
||||||
|
<span class="avatar-initial rounded-circle bg-label-primary">
|
||||||
|
${
|
||||||
|
member?.firstName?.charAt(
|
||||||
|
0
|
||||||
|
) || ""
|
||||||
|
}${
|
||||||
|
member?.lastName?.charAt(0) || ""
|
||||||
|
}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<span>${member.firstName} ${
|
||||||
|
member.lastName
|
||||||
|
}</span>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
)
|
||||||
|
.join("")}
|
||||||
|
</div>
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
{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"
|
||||||
|
>
|
||||||
|
<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 className="text-center">
|
||||||
|
<div className="d-flex justify-content-end">
|
||||||
|
{ ReportTaskRights &&
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className={`btn btn-xs btn-primary ${
|
||||||
|
task.reportedDate != null
|
||||||
|
? "d-none"
|
||||||
|
: ""
|
||||||
|
}`}
|
||||||
|
onClick={() => {
|
||||||
|
selectTask(task);
|
||||||
|
openModal();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Report
|
||||||
|
</button>
|
||||||
|
}
|
||||||
|
{(ApprovedTaskRights && task.reportedDate ) && (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className={`btn btn-xs btn-warning ${
|
||||||
|
task.reportedDate && task.approvedBy
|
||||||
|
? "d-none"
|
||||||
|
: ""
|
||||||
|
}`}
|
||||||
|
onClick={() => {
|
||||||
|
setComment({
|
||||||
|
task: task,
|
||||||
|
isActionAllow: true,
|
||||||
|
});
|
||||||
|
openComment();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
QC
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="btn btn-xs btn-primary ms-2"
|
||||||
|
onClick={() => {
|
||||||
|
setComment({
|
||||||
|
task: task,
|
||||||
|
isActionAllow: false,
|
||||||
|
});
|
||||||
|
openComment();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Comment
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import React,{useEffect,useRef} from "react";
|
import React,{useEffect} from "react";
|
||||||
import Breadcrumb from "../../components/common/Breadcrumb";
|
import Breadcrumb from "../../components/common/Breadcrumb";
|
||||||
import InfraPlanning from "../../components/Activities/InfraPlanning";
|
import InfraPlanning from "../../components/Activities/InfraPlanning";
|
||||||
import { useProjectName } from "../../hooks/useProjects";
|
import { useProjectName } from "../../hooks/useProjects";
|
||||||
@ -6,34 +6,33 @@ import { useDispatch, useSelector } from "react-redux";
|
|||||||
import { setProjectId } from "../../slices/localVariablesSlice";
|
import { setProjectId } from "../../slices/localVariablesSlice";
|
||||||
import { useSelectedproject } from "../../slices/apiDataManager";
|
import { useSelectedproject } from "../../slices/apiDataManager";
|
||||||
|
|
||||||
|
|
||||||
const TaskPlannng = () => {
|
const TaskPlannng = () => {
|
||||||
|
// const selectedProject = useSelector(
|
||||||
|
// (store) => store.localVariables.projectId
|
||||||
|
// );
|
||||||
const selectedProject = useSelectedproject();
|
const selectedProject = useSelectedproject();
|
||||||
const dispatch = useDispatch();
|
|
||||||
const { projectNames, loading: projectLoading } = useProjectName();
|
|
||||||
|
|
||||||
const initialized = useRef(false);
|
|
||||||
|
|
||||||
|
const dispatch = useDispatch()
|
||||||
|
const { projectNames, loading: projectLoading, fetchData } = useProjectName();
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!initialized.current && projectNames.length > 0 && !selectedProject?.id) {
|
if(selectedProject == null){
|
||||||
dispatch(setProjectId(projectNames[0].id));
|
dispatch(setProjectId(projectNames[0]?.id));
|
||||||
initialized.current = true;
|
}
|
||||||
}
|
},[])
|
||||||
}, [projectNames, selectedProject, dispatch]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="container-fluid">
|
<>
|
||||||
<Breadcrumb
|
<div className="container-fluid">
|
||||||
data={[
|
<Breadcrumb
|
||||||
{ label: "Home", link: "/dashboard" },
|
data={[
|
||||||
{ label: "Daily Task Planning" },
|
{ label: "Home", link: "/dashboard" },
|
||||||
]}
|
{ label: "Daily Task Planning" }
|
||||||
/>
|
]}
|
||||||
{selectedProject ? (
|
></Breadcrumb>
|
||||||
<InfraPlanning />
|
<InfraPlanning/>
|
||||||
) : (
|
</div>
|
||||||
<div className="text-center">Please Select Project</div>
|
</>
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -80,23 +80,21 @@ const ExpensePage = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (IsViewAll || IsViewSelf || IsCreatedAble) {
|
setShowTrigger(true);
|
||||||
setShowTrigger(true);
|
setOffcanvasContent(
|
||||||
setOffcanvasContent(
|
"Expense Filters",
|
||||||
"Expense Filters",
|
<ExpenseFilterPanel
|
||||||
<ExpenseFilterPanel
|
onApply={setFilter}
|
||||||
onApply={setFilter}
|
handleGroupBy={setGroupBy}
|
||||||
handleGroupBy={setGroupBy}
|
clearFilter={clearFilter}
|
||||||
clearFilter={clearFilter}
|
/>
|
||||||
/>
|
);
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
setShowTrigger(false);
|
setShowTrigger(false);
|
||||||
setOffcanvasContent("", null);
|
setOffcanvasContent("", null);
|
||||||
};
|
};
|
||||||
}, [IsViewAll, IsViewSelf, IsCreatedAble]);
|
}, []);
|
||||||
|
|
||||||
const contextValue = {
|
const contextValue = {
|
||||||
setViewExpense,
|
setViewExpense,
|
||||||
@ -107,17 +105,16 @@ const ExpensePage = () => {
|
|||||||
return (
|
return (
|
||||||
<ExpenseContext.Provider value={contextValue}>
|
<ExpenseContext.Provider value={contextValue}>
|
||||||
<div className="container-fluid">
|
<div className="container-fluid">
|
||||||
<Breadcrumb
|
<Breadcrumb data={[{ label: "Home", link: "/" }, { label: "Expense" }]} />
|
||||||
data={[{ label: "Home", link: "/" }, { label: "Expense" }]}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{IsViewAll || IsViewSelf || IsCreatedAble ? (
|
{(IsViewAll || IsViewSelf || IsCreatedAble) ? (
|
||||||
<>
|
<>
|
||||||
<div className="card my-3 px-sm-4 px-0">
|
<div className="card my-3 px-sm-4 px-0">
|
||||||
<div className="card-body py-2 px-3">
|
<div className="card-body py-2 px-3">
|
||||||
<div className="row align-items-center">
|
<div className="row align-items-center">
|
||||||
<div className="col-6 ">
|
<div className="col-6 ">
|
||||||
<div className="d-flex align-items-center">
|
<div className="d-flex align-items-center">
|
||||||
|
|
||||||
<input
|
<input
|
||||||
type="search"
|
type="search"
|
||||||
className="form-control form-control-sm w-auto"
|
className="form-control form-control-sm w-auto"
|
||||||
@ -135,12 +132,7 @@ const ExpensePage = () => {
|
|||||||
type="button"
|
type="button"
|
||||||
className="p-1 me-1 m-sm-0 bg-primary rounded-circle"
|
className="p-1 me-1 m-sm-0 bg-primary rounded-circle"
|
||||||
title="Add New Expense"
|
title="Add New Expense"
|
||||||
onClick={() =>
|
onClick={() => setManageExpenseModal({ IsOpen: true, expenseId: null })}
|
||||||
setManageExpenseModal({
|
|
||||||
IsOpen: true,
|
|
||||||
expenseId: null,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
<i className="bx bx-plus fs-4 text-white"></i>
|
<i className="bx bx-plus fs-4 text-white"></i>
|
||||||
</button>
|
</button>
|
||||||
@ -150,18 +142,12 @@ const ExpensePage = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ExpenseList
|
<ExpenseList filters={filters} groupBy={groupBy} searchText={searchText} />
|
||||||
filters={filters}
|
|
||||||
groupBy={groupBy}
|
|
||||||
searchText={searchText}
|
|
||||||
/>
|
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<div className="card text-center py-1">
|
<div className="card text-center py-1">
|
||||||
<i className="fa-solid fa-triangle-exclamation fs-5" />
|
<i className="fa-solid fa-triangle-exclamation fs-5" />
|
||||||
<p>
|
<p>Access Denied: You don't have permission to perform this action!</p>
|
||||||
Access Denied: You don't have permission to perform this action !
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@ -170,16 +156,12 @@ const ExpensePage = () => {
|
|||||||
<GlobalModel
|
<GlobalModel
|
||||||
isOpen
|
isOpen
|
||||||
size="lg"
|
size="lg"
|
||||||
closeModal={() =>
|
closeModal={() => setManageExpenseModal({ IsOpen: null, expenseId: null })}
|
||||||
setManageExpenseModal({ IsOpen: null, expenseId: null })
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
<ManageExpense
|
<ManageExpense
|
||||||
key={ManageExpenseModal.expenseId ?? "new"}
|
key={ManageExpenseModal.expenseId ?? "new"}
|
||||||
expenseToEdit={ManageExpenseModal.expenseId}
|
expenseToEdit={ManageExpenseModal.expenseId}
|
||||||
closeModal={() =>
|
closeModal={() => setManageExpenseModal({ IsOpen: null, expenseId: null })}
|
||||||
setManageExpenseModal({ IsOpen: null, expenseId: null })
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
</GlobalModel>
|
</GlobalModel>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -1,20 +0,0 @@
|
|||||||
import React from 'react'
|
|
||||||
import Breadcrumb from '../../components/common/Breadcrumb'
|
|
||||||
import TenantForm from '../../components/Tenant/TenantForm'
|
|
||||||
|
|
||||||
const CreateTenant = () => {
|
|
||||||
return (
|
|
||||||
<div className='container-fluid'>
|
|
||||||
<Breadcrumb
|
|
||||||
data={[
|
|
||||||
{ label: "Home", link: "/dashboard" },
|
|
||||||
{ label: "Tenant", link: '/tenants' },
|
|
||||||
{ label: "New Tenant", link: null },
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
<TenantForm/>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default CreateTenant
|
|
||||||
@ -1,36 +0,0 @@
|
|||||||
import React, { useEffect, useMemo } from "react";
|
|
||||||
import { useProfile } from "../../hooks/useProfile";
|
|
||||||
import TenantDetails from "./TenantDetails";
|
|
||||||
import { hasUserPermission } from "../../utils/authUtils";
|
|
||||||
import { VIEW_TENANTS } from "../../utils/constants";
|
|
||||||
import { useNavigate } from "react-router-dom";
|
|
||||||
import Loader from "../../components/common/Loader";
|
|
||||||
|
|
||||||
const SelfTenantDetails = () => {
|
|
||||||
const { profile, loading } = useProfile();
|
|
||||||
const tenantId = profile?.employeeInfo?.tenantId;
|
|
||||||
const navigate = useNavigate();
|
|
||||||
const isSelfTenantView = hasUserPermission(VIEW_TENANTS);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!isSelfTenantView) {
|
|
||||||
navigate("/tenants");
|
|
||||||
}
|
|
||||||
}, [isSelfTenantView, navigate]);
|
|
||||||
|
|
||||||
if (loading || !tenantId) {
|
|
||||||
return <Loader/>;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<TenantDetails
|
|
||||||
tenantId={tenantId}
|
|
||||||
wrapInContainer={true}
|
|
||||||
showBreadcrumb={true}
|
|
||||||
iTSelf={true}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default SelfTenantDetails;
|
|
||||||
|
|
||||||
@ -1,8 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
import TenantDetails from "./TenantDetails";
|
|
||||||
|
|
||||||
const SuperTenantDetails = () => {
|
|
||||||
return <TenantDetails wrapInContainer showBreadcrumb iTSelf={false} />;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default SuperTenantDetails;
|
|
||||||
@ -1,182 +0,0 @@
|
|||||||
import React, { createContext, useContext, useState, useMemo } from "react";
|
|
||||||
import { useParams } from "react-router-dom";
|
|
||||||
import Breadcrumb from "../../components/common/Breadcrumb";
|
|
||||||
import Profile from "../../components/Tenant/Profile";
|
|
||||||
import { useTenantDetails } from "../../hooks/useTenant";
|
|
||||||
import { ComingSoonPage } from "../Misc/ComingSoonPage";
|
|
||||||
import GlobalModel from "../../components/common/GlobalModel";
|
|
||||||
import EditProfile from "../../components/Tenant/EditProfile";
|
|
||||||
import SubScriptionHistory from "../../components/Tenant/SubScriptionHistory";
|
|
||||||
import Loader from "../../components/common/Loader";
|
|
||||||
import { useHasUserPermission } from "../../hooks/useHasUserPermission";
|
|
||||||
import { MANAGE_TENANTS, SUPPER_TENANT } from "../../utils/constants";
|
|
||||||
|
|
||||||
const TenantDetailsContext = createContext();
|
|
||||||
export const useTenantDetailsContext = () => useContext(TenantDetailsContext);
|
|
||||||
|
|
||||||
const TenantDetails = ({
|
|
||||||
tenantId: tenantIdProp,
|
|
||||||
wrapInContainer = true,
|
|
||||||
showBreadcrumb = true,
|
|
||||||
iTSelf = true,
|
|
||||||
}) => {
|
|
||||||
const { tenantId: tenantIdFromUrl } = useParams();
|
|
||||||
const activeTenantId = tenantIdFromUrl || tenantIdProp;
|
|
||||||
const { data, isLoading, isError, error } = useTenantDetails(activeTenantId);
|
|
||||||
const ManageTenant = useHasUserPermission(SUPPER_TENANT);
|
|
||||||
const ModifyTenant = useHasUserPermission(MANAGE_TENANTS);
|
|
||||||
const [editTenant, setEditTenant] = useState(false);
|
|
||||||
const contextValues = useMemo(
|
|
||||||
() => ({ editTenant, setEditTenant }),
|
|
||||||
[editTenant]
|
|
||||||
);
|
|
||||||
|
|
||||||
const tabs = useMemo(() => {
|
|
||||||
const allTabs = [
|
|
||||||
{
|
|
||||||
id: "navs-left-home",
|
|
||||||
label: "Profile",
|
|
||||||
icon: "bx bx-user-circle",
|
|
||||||
iconSize: "bx-sm",
|
|
||||||
content: <Profile data={data} />,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "navs-left-bill",
|
|
||||||
label: "Bills and Plan",
|
|
||||||
icon: "bx bx-receipt",
|
|
||||||
iconSize: "bx-sm",
|
|
||||||
content: (
|
|
||||||
<div className="text-center">
|
|
||||||
<SubScriptionHistory tenantId={activeTenantId} />
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "navs-left-messages",
|
|
||||||
label: "Messages",
|
|
||||||
icon: "bx bx-message-rounded",
|
|
||||||
iconSize: "bx-sm",
|
|
||||||
content: (
|
|
||||||
<div className="text-center">
|
|
||||||
<ComingSoonPage />
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
return ManageTenant
|
|
||||||
? allTabs
|
|
||||||
: [allTabs[0], allTabs[allTabs.length - 1]];
|
|
||||||
}, [data, activeTenantId, ManageTenant, ModifyTenant]);
|
|
||||||
if (!activeTenantId) return <div className="my-4">No tenant selected.</div>;
|
|
||||||
if (isLoading)
|
|
||||||
return (
|
|
||||||
<div className="my-4">
|
|
||||||
<Loader />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
if (isError)
|
|
||||||
return (
|
|
||||||
<div className="container-fluid">
|
|
||||||
{error.status === 403 ? (
|
|
||||||
<div className="card text-center my-4 p-2">
|
|
||||||
<i className="fa-solid fa-triangle-exclamation fs-5"></i>
|
|
||||||
<p>
|
|
||||||
Access Denied: You don't have permission to perform this action!
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div className="card text-center my-4 p-2">
|
|
||||||
<i className="fa-solid fa-triangle-exclamation fs-5"></i>
|
|
||||||
<p>{error.message}</p>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
const Shell = ({ children }) =>
|
|
||||||
wrapInContainer ? (
|
|
||||||
<div className="container-fluid py-0">{children}</div>
|
|
||||||
) : (
|
|
||||||
<>{children}</>
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<TenantDetailsContext.Provider value={contextValues}>
|
|
||||||
<Shell>
|
|
||||||
{showBreadcrumb && (
|
|
||||||
<Breadcrumb
|
|
||||||
data={
|
|
||||||
iTSelf
|
|
||||||
? [
|
|
||||||
{ label: "Home", link: "/dashboard" },
|
|
||||||
{ label: "Tenant Details", link: null },
|
|
||||||
]
|
|
||||||
: [
|
|
||||||
{ label: "Home", link: "/dashboard" },
|
|
||||||
{ label: "Tenant", link: "/tenants" },
|
|
||||||
{ label: "Tenant Details", link: null },
|
|
||||||
]
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div className="nav-align-left nav-tabs-shadow mb-6">
|
|
||||||
<ul className="nav nav-tabs py-2 page-min-h" role="tablist">
|
|
||||||
{tabs.map((tab, index) => (
|
|
||||||
<li key={tab.id} className="nav-item">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className={`nav-link d-flex align-items-center text-tiny gap-2 ${
|
|
||||||
index === 0 ? "active" : ""
|
|
||||||
}`}
|
|
||||||
role="tab"
|
|
||||||
data-bs-toggle="tab"
|
|
||||||
data-bs-target={`#${tab.id}`}
|
|
||||||
aria-controls={tab.id}
|
|
||||||
aria-selected={index === 0}
|
|
||||||
>
|
|
||||||
{tab.icon && (
|
|
||||||
<i className={`${tab.icon} ${tab.iconSize}`} />
|
|
||||||
)}
|
|
||||||
{tab.label}
|
|
||||||
</button>
|
|
||||||
</li>
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<div className="tab-content">
|
|
||||||
{tabs.map((tab, index) => (
|
|
||||||
<div
|
|
||||||
key={tab.id}
|
|
||||||
className={`tab-pane fade ${
|
|
||||||
index === 0 ? "show active" : ""
|
|
||||||
} text-start`}
|
|
||||||
id={tab.id}
|
|
||||||
>
|
|
||||||
{tab.content}
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Shell>
|
|
||||||
</TenantDetailsContext.Provider>
|
|
||||||
|
|
||||||
{editTenant && (
|
|
||||||
<GlobalModel
|
|
||||||
size="lg"
|
|
||||||
isOpen={editTenant}
|
|
||||||
closeModal={() => setEditTenant(false)}
|
|
||||||
>
|
|
||||||
<EditProfile
|
|
||||||
TenantId={activeTenantId}
|
|
||||||
onClose={() => setEditTenant(false)}
|
|
||||||
/>
|
|
||||||
</GlobalModel>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default TenantDetails;
|
|
||||||
@ -1,189 +0,0 @@
|
|||||||
import React, {
|
|
||||||
useState,
|
|
||||||
createContext,
|
|
||||||
useEffect,
|
|
||||||
useContext,
|
|
||||||
useCallback,
|
|
||||||
useMemo,
|
|
||||||
} from "react";
|
|
||||||
import { useForm } from "react-hook-form";
|
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
|
||||||
import { useNavigate } from "react-router-dom";
|
|
||||||
import { useDispatch } from "react-redux";
|
|
||||||
|
|
||||||
// ------ Components -------
|
|
||||||
import Breadcrumb from "../../components/common/Breadcrumb";
|
|
||||||
import TenantsList from "../../components/Tenant/TenantsList";
|
|
||||||
import TenantFilterPanel from "../../components/Tenant/TenantFilterPanel";
|
|
||||||
|
|
||||||
// ------ Context & Utils -------
|
|
||||||
import { useDebounce } from "../../utils/appUtils";
|
|
||||||
import { useFab } from "../../Context/FabContext";
|
|
||||||
import { setCurrentTenant } from "../../slices/globalVariablesSlice";
|
|
||||||
import { hasUserPermission } from "../../utils/authUtils";
|
|
||||||
|
|
||||||
// ------ Schema -------
|
|
||||||
import {
|
|
||||||
defaultFilterValues,
|
|
||||||
filterSchema,
|
|
||||||
} from "../../components/Tenant/TenantSchema";
|
|
||||||
|
|
||||||
// ------ Constants -------
|
|
||||||
import {
|
|
||||||
MANAGE_TENANTS,
|
|
||||||
SUPPER_TENANT,
|
|
||||||
VIEW_TENANTS,
|
|
||||||
} from "../../utils/constants";
|
|
||||||
import { useProfile } from "../../hooks/useProfile";
|
|
||||||
|
|
||||||
// ---------- Context ----------
|
|
||||||
export const TenantContext = createContext();
|
|
||||||
export const useTenantContext = () => {
|
|
||||||
const context = useContext(TenantContext);
|
|
||||||
if (!context) {
|
|
||||||
throw new Error(
|
|
||||||
"useTenantContext must be used within a TenantContext.Provider"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return context;
|
|
||||||
};
|
|
||||||
|
|
||||||
const TenantPage = () => {
|
|
||||||
const dispatch = useDispatch();
|
|
||||||
const navigate = useNavigate();
|
|
||||||
const { profile } = useProfile();
|
|
||||||
|
|
||||||
// ---------- State ----------
|
|
||||||
const [searchText, setSearchText] = useState("");
|
|
||||||
const [isRefetching, setIsRefetching] = useState(false);
|
|
||||||
const [refetchFn, setRefetchFn] = useState(null);
|
|
||||||
const [filters, setFilters] = useState();
|
|
||||||
|
|
||||||
// ---------- Hooks ----------
|
|
||||||
const debouncedSearch = useDebounce(searchText, 500);
|
|
||||||
const { setOffcanvasContent, setShowTrigger } = useFab();
|
|
||||||
|
|
||||||
const isSuperTenant = hasUserPermission(SUPPER_TENANT);
|
|
||||||
const canManageTenants = hasUserPermission(MANAGE_TENANTS);
|
|
||||||
const isSelfTenant = hasUserPermission(VIEW_TENANTS);
|
|
||||||
|
|
||||||
const methods = useForm({
|
|
||||||
resolver: zodResolver(filterSchema),
|
|
||||||
defaultValues: defaultFilterValues,
|
|
||||||
});
|
|
||||||
const { reset } = methods;
|
|
||||||
|
|
||||||
const handleApplyFilters = useCallback((values) => {
|
|
||||||
setFilters(values);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const filterPanelElement = useMemo(
|
|
||||||
() => <TenantFilterPanel onApply={handleApplyFilters} />,
|
|
||||||
[handleApplyFilters]
|
|
||||||
);
|
|
||||||
// ---------- Fab Filter Panel ----------
|
|
||||||
useEffect(() => {
|
|
||||||
if (!isSuperTenant) return;
|
|
||||||
setShowTrigger(true);
|
|
||||||
setOffcanvasContent("Tenant Filters", filterPanelElement);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
setShowTrigger(false);
|
|
||||||
setOffcanvasContent("", null);
|
|
||||||
};
|
|
||||||
}, [isSuperTenant, filterPanelElement, profile]);
|
|
||||||
|
|
||||||
// ---------- Redirect for Self Tenant ----------
|
|
||||||
useEffect(() => {
|
|
||||||
if (!isSuperTenant && isSelfTenant) {
|
|
||||||
// Delay navigation to next tick to avoid "update during render" warning
|
|
||||||
setTimeout(() => {
|
|
||||||
navigate("/tenant/self");
|
|
||||||
}, 0);
|
|
||||||
}
|
|
||||||
}, [isSuperTenant, isSelfTenant, navigate]);
|
|
||||||
|
|
||||||
// ---------- Handlers ----------
|
|
||||||
const handleNewTenant = () => {
|
|
||||||
dispatch(setCurrentTenant(null));
|
|
||||||
navigate("/tenants/new-tenant");
|
|
||||||
};
|
|
||||||
|
|
||||||
// ---------- Context Value ----------
|
|
||||||
const contextValue = {};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<TenantContext.Provider value={contextValue}>
|
|
||||||
<div className="container-fluid">
|
|
||||||
<Breadcrumb
|
|
||||||
data={[
|
|
||||||
{ label: "Home", link: "/dashboard" },
|
|
||||||
{ label: "Tenant", link: null },
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* Super Tenant Actions */}
|
|
||||||
{isSuperTenant && (
|
|
||||||
<div className="card d-flex p-2">
|
|
||||||
<div className="row align-items-center">
|
|
||||||
{/* Search */}
|
|
||||||
<div className="col-6 col-md-6 col-lg-3 mb-md-0">
|
|
||||||
<input
|
|
||||||
type="search"
|
|
||||||
value={searchText}
|
|
||||||
onChange={(e) => setSearchText(e.target.value)}
|
|
||||||
className="form-control form-control-sm"
|
|
||||||
placeholder="Search Tenant"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Actions */}
|
|
||||||
<div className="col-6 col-md-6 col-lg-9 text-end">
|
|
||||||
<span
|
|
||||||
className="text-tiny text-muted p-1 border-0 bg-none lead mx-3 cursor-pointer"
|
|
||||||
disabled={isRefetching}
|
|
||||||
onClick={() => refetchFn && refetchFn()}
|
|
||||||
>
|
|
||||||
Refresh{" "}
|
|
||||||
<i
|
|
||||||
className={`bx bx-refresh ms-1 ${
|
|
||||||
isRefetching ? "bx-spin" : ""
|
|
||||||
}`}
|
|
||||||
></i>
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
title="Add New Tenant"
|
|
||||||
className="p-1 bg-primary rounded-circle cursor-pointer"
|
|
||||||
onClick={handleNewTenant}
|
|
||||||
>
|
|
||||||
<i className="bx bx-plus fs-4 text-white"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Tenant List or Access Denied */}
|
|
||||||
{isSuperTenant ? (
|
|
||||||
<TenantsList
|
|
||||||
filters={filters}
|
|
||||||
searchText={debouncedSearch}
|
|
||||||
setIsRefetching={setIsRefetching}
|
|
||||||
setRefetchFn={setRefetchFn}
|
|
||||||
/>
|
|
||||||
) : !isSelfTenant ? (
|
|
||||||
<div className="card text-center my-4 p-2">
|
|
||||||
<i className="fa-solid fa-triangle-exclamation fs-5"></i>
|
|
||||||
<p>
|
|
||||||
Access Denied: You don't have permission to perform this action!
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
</div>
|
|
||||||
</TenantContext.Provider>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default TenantPage;
|
|
||||||
@ -4,7 +4,7 @@ import MasterModal from "../../components/master/MasterModal";
|
|||||||
import { mastersList } from "../../data/masters";
|
import { mastersList } from "../../data/masters";
|
||||||
import { useDispatch, useSelector } from "react-redux";
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
import { changeMaster } from "../../slices/localVariablesSlice";
|
import { changeMaster } from "../../slices/localVariablesSlice";
|
||||||
import useMaster, { useMasterMenu } from "../../hooks/masterHook/useMaster"
|
import useMaster from "../../hooks/masterHook/useMaster"
|
||||||
import MasterTable from "./MasterTable";
|
import MasterTable from "./MasterTable";
|
||||||
import { getCachedData } from "../../slices/apiDataManager";
|
import { getCachedData } from "../../slices/apiDataManager";
|
||||||
import { useHasUserPermission } from "../../hooks/useHasUserPermission";
|
import { useHasUserPermission } from "../../hooks/useHasUserPermission";
|
||||||
@ -13,7 +13,6 @@ import { useQueryClient } from "@tanstack/react-query";
|
|||||||
|
|
||||||
|
|
||||||
const MasterPage = () => {
|
const MasterPage = () => {
|
||||||
const {data,isLoading,isError,error:menuError} = useMasterMenu()
|
|
||||||
const [modalConfig, setModalConfig] = useState({ modalType: "", item: null, masterType: null });
|
const [modalConfig, setModalConfig] = useState({ modalType: "", item: null, masterType: null });
|
||||||
const [searchTerm, setSearchTerm] = useState('');
|
const [searchTerm, setSearchTerm] = useState('');
|
||||||
const [filteredResults, setFilteredResults] = useState([]);
|
const [filteredResults, setFilteredResults] = useState([]);
|
||||||
@ -24,7 +23,7 @@ const MasterPage = () => {
|
|||||||
const selectedMaster = useSelector((store) => store.localVariables.selectedMaster);
|
const selectedMaster = useSelector((store) => store.localVariables.selectedMaster);
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
const { data: masterData = [], loading, error, RecallApi,isError:isMasterError } = useMaster();
|
const { data: masterData = [], loading, error, RecallApi } = useMaster();
|
||||||
|
|
||||||
const openModal = () => setIsCreateModalOpen(true);
|
const openModal = () => setIsCreateModalOpen(true);
|
||||||
|
|
||||||
@ -84,10 +83,7 @@ const MasterPage = () => {
|
|||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
if(isError || isMasterError) return <div className="d-flex flex-column align-items-center justify-content-center py-5">
|
|
||||||
<h4 className=" mb-3"><i className="fa-solid fa-triangle-exclamation fs-5" /> Oops, an error occurred</h4>
|
|
||||||
<p className="text-muted">{error?.message || menuError?.message}</p>
|
|
||||||
</div>
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{isCreateModalOpen && (
|
{isCreateModalOpen && (
|
||||||
@ -125,8 +121,8 @@ const MasterPage = () => {
|
|||||||
className="form-select form-select-sm"
|
className="form-select form-select-sm"
|
||||||
value={selectedMaster}
|
value={selectedMaster}
|
||||||
>
|
>
|
||||||
{isLoading && (<option value={null}>Loading...</option>)}
|
|
||||||
{(!isLoading && data) && data?.map((item) => (
|
{mastersList.map((item) => (
|
||||||
|
|
||||||
<option key={item.id} value={item.name}>{item.name}</option>
|
<option key={item.id} value={item.name}>{item.name}</option>
|
||||||
))}
|
))}
|
||||||
@ -158,6 +154,7 @@ const MasterPage = () => {
|
|||||||
|
|
||||||
<button
|
<button
|
||||||
className={`btn btn-sm add-new btn-primary `}
|
className={`btn btn-sm add-new btn-primary `}
|
||||||
|
// ${hasUserPermission('660131a4-788c-4739-a082-cbbf7879cbf2') ? "":"d-none"}
|
||||||
tabIndex="0"
|
tabIndex="0"
|
||||||
aria-controls="DataTables_Table_0"
|
aria-controls="DataTables_Table_0"
|
||||||
type="button"
|
type="button"
|
||||||
@ -165,6 +162,7 @@ const MasterPage = () => {
|
|||||||
data-bs-target="#master-modal"
|
data-bs-target="#master-modal"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
handleModalData(selectedMaster, "null", selectedMaster)
|
handleModalData(selectedMaster, "null", selectedMaster)
|
||||||
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span>
|
<span>
|
||||||
|
|||||||
@ -137,6 +137,7 @@ const ProjectDetails = () => {
|
|||||||
<div className="row">
|
<div className="row">
|
||||||
<ProjectNav onPillClick={handlePillClick} activePill={activePill} />
|
<ProjectNav onPillClick={handlePillClick} activePill={activePill} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{renderContent()}
|
{renderContent()}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -15,8 +15,6 @@ const AuthRepository = {
|
|||||||
logout: (data) => api.post("/api/auth/logout", data),
|
logout: (data) => api.post("/api/auth/logout", data),
|
||||||
profile: () => api.get("/api/user/profile"),
|
profile: () => api.get("/api/user/profile"),
|
||||||
changepassword: (data) => api.post("/api/auth/change-password", data),
|
changepassword: (data) => api.post("/api/auth/change-password", data),
|
||||||
appmenu:()=>api.get('/api/appmenu/get/menu')
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default AuthRepository;
|
export default AuthRepository;
|
||||||
|
|||||||
@ -18,8 +18,6 @@ export const RolesRepository = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const MasterRespository = {
|
export const MasterRespository = {
|
||||||
getMasterMenus:()=>api.get("/api/AppMenu/get/master-list"),
|
|
||||||
|
|
||||||
getRoles: () => api.get("/api/roles"),
|
getRoles: () => api.get("/api/roles"),
|
||||||
createRole: (data) => api.post("/api/roles", data),
|
createRole: (data) => api.post("/api/roles", data),
|
||||||
updateRoles: (id, data) => api.put(`/api/roles/${id}`, data),
|
updateRoles: (id, data) => api.put(`/api/roles/${id}`, data),
|
||||||
|
|||||||
@ -1,20 +0,0 @@
|
|||||||
import { api } from "../utils/axiosClient";
|
|
||||||
|
|
||||||
export const TenantRepository = {
|
|
||||||
getTenantList: ( pageSize, pageNumber, filter,searchString) => {
|
|
||||||
const payloadJsonString = JSON.stringify(filter);
|
|
||||||
|
|
||||||
return api.get(`/api/Tenant/list?pageSize=${pageSize}&pageNumber=${pageNumber}&filter=${payloadJsonString}&searchString=${searchString}`);
|
|
||||||
},
|
|
||||||
|
|
||||||
getTenantDetails:(id)=>api.get(`/api/Tenant/details/${id}`),
|
|
||||||
|
|
||||||
getSubscriptionPlan: (freq) =>
|
|
||||||
api.get(`/api/Tenant/list/subscription-plan?frequency=${freq}`),
|
|
||||||
|
|
||||||
createTenant: (data) => api.post("/api/Tenant/create", data),
|
|
||||||
updateTenantDetails :(id,data)=> api.put(`/api/Tenant/edit/${id}`,data),
|
|
||||||
|
|
||||||
addSubscription: (data) => api.post("/api/Tenant/add-subscription", data),
|
|
||||||
upgradeSubscription :(data)=> api.put("/api/Tenant/update-subscription",data)
|
|
||||||
};
|
|
||||||
@ -38,24 +38,19 @@ import LegalInfoCard from "../pages/TermsAndConditions/LegalInfoCard";
|
|||||||
import ProtectedRoute from "./ProtectedRoute";
|
import ProtectedRoute from "./ProtectedRoute";
|
||||||
import Directory from "../pages/Directory/Directory";
|
import Directory from "../pages/Directory/Directory";
|
||||||
import LoginWithOtp from "../pages/authentication/LoginWithOtp";
|
import LoginWithOtp from "../pages/authentication/LoginWithOtp";
|
||||||
import TenantPage from "../pages/Tenant/TenantPage";
|
|
||||||
import CreateTenant from "../pages/Tenant/CreateTenant";
|
|
||||||
import ExpensePage from "../pages/Expense/ExpensePage";
|
import ExpensePage from "../pages/Expense/ExpensePage";
|
||||||
import TenantDetails from "../pages/Tenant/TenantDetails";
|
|
||||||
import SelfTenantDetails from "../pages/Tenant/SelfTenantDetails";
|
|
||||||
import SuperTenantDetails from "../pages/Tenant/SuperTenantDetails";
|
|
||||||
|
|
||||||
const router = createBrowserRouter(
|
const router = createBrowserRouter(
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
element: <AuthLayout />,
|
element: <AuthLayout />,
|
||||||
children: [
|
children: [
|
||||||
{ path: "/auth/login", element: <LoginPage /> },
|
{path: "/auth/login", element: <LoginPage />},
|
||||||
{ path: "/auth/login-otp", element: <LoginWithOtp /> },
|
{path: "/auth/login-otp", element: <LoginWithOtp />},
|
||||||
{ path: "/auth/reqest/demo", element: <RegisterPage /> },
|
{ path: "/auth/reqest/demo", element: <RegisterPage /> },
|
||||||
{ path: "/auth/forgot-password", element: <ForgotPasswordPage /> },
|
{ path: "/auth/forgot-password", element: <ForgotPasswordPage /> },
|
||||||
{ path: "/reset-password", element: <ResetPasswordPage /> },
|
{ path: "/reset-password", element: <ResetPasswordPage /> },
|
||||||
{ path: "/legal-info", element: <LegalInfoCard /> },
|
{ path: "/legal-info", element: <LegalInfoCard /> },
|
||||||
{ path: "/auth/changepassword", element: <ChangePasswordPage /> },
|
{ path: "/auth/changepassword", element: <ChangePasswordPage /> },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@ -84,10 +79,6 @@ const router = createBrowserRouter(
|
|||||||
{ path: "/gallary", element: <ImageGallary /> },
|
{ path: "/gallary", element: <ImageGallary /> },
|
||||||
{ path: "/expenses", element: <ExpensePage /> },
|
{ path: "/expenses", element: <ExpensePage /> },
|
||||||
{ path: "/masters", element: <MasterPage /> },
|
{ path: "/masters", element: <MasterPage /> },
|
||||||
{ path: "/tenants", element: <TenantPage /> },
|
|
||||||
{ path: "/tenants/new-tenant", element: <CreateTenant /> },
|
|
||||||
{ path: "/tenant/:tenantId", element: <SuperTenantDetails /> },
|
|
||||||
{ path: "/tenant/self", element: <SelfTenantDetails /> },
|
|
||||||
{ path: "/help/support", element: <Support /> },
|
{ path: "/help/support", element: <Support /> },
|
||||||
{ path: "/help/docs", element: <Documentation /> },
|
{ path: "/help/docs", element: <Documentation /> },
|
||||||
{ path: "/help/connect", element: <Connect /> },
|
{ path: "/help/connect", element: <Connect /> },
|
||||||
|
|||||||
@ -3,8 +3,7 @@ import { createSlice } from "@reduxjs/toolkit";
|
|||||||
const globalVariablesSlice = createSlice({
|
const globalVariablesSlice = createSlice({
|
||||||
name: "globalVariables",
|
name: "globalVariables",
|
||||||
initialState: {
|
initialState: {
|
||||||
loginUser:null,
|
loginUser:null
|
||||||
currentTenant:null
|
|
||||||
},
|
},
|
||||||
reducers: {
|
reducers: {
|
||||||
setGlobalVariable: (state, action) => {
|
setGlobalVariable: (state, action) => {
|
||||||
@ -14,12 +13,9 @@ const globalVariablesSlice = createSlice({
|
|||||||
setLoginUserPermmisions: ( state, action ) =>
|
setLoginUserPermmisions: ( state, action ) =>
|
||||||
{
|
{
|
||||||
state.loginUser = action.payload
|
state.loginUser = action.payload
|
||||||
},
|
|
||||||
setCurrentTenant:(state,action)=>{
|
|
||||||
state.currentTenant = action.payload
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const { setGlobalVariable,setLoginUserPermmisions,setCurrentTenant } = globalVariablesSlice.actions;
|
export const { setGlobalVariable,setLoginUserPermmisions } = globalVariablesSlice.actions;
|
||||||
export default globalVariablesSlice.reducer;
|
export default globalVariablesSlice.reducer;
|
||||||
|
|||||||
@ -5,8 +5,6 @@ export const OTP_EXPIRY_SECONDS = 600 // OTP time
|
|||||||
|
|
||||||
export const MANAGE_MASTER = "588a8824-f924-4955-82d8-fc51956cf323";
|
export const MANAGE_MASTER = "588a8824-f924-4955-82d8-fc51956cf323";
|
||||||
|
|
||||||
export const VIEW_MASTER = "5ffbafe0-7ab0-48b1-bb50-c1bf76b65f9d"
|
|
||||||
|
|
||||||
export const MANAGE_PROJECT = "172fc9b6-755b-4f62-ab26-55c34a330614"
|
export const MANAGE_PROJECT = "172fc9b6-755b-4f62-ab26-55c34a330614"
|
||||||
|
|
||||||
export const VIEW_PROJECTS = "6ea44136-987e-44ba-9e5d-1cf8f5837ebc"
|
export const VIEW_PROJECTS = "6ea44136-987e-44ba-9e5d-1cf8f5837ebc"
|
||||||
@ -23,8 +21,6 @@ export const MANAGE_PROJECT_INFRA = "cf2825ad-453b-46aa-91d9-27c124d63373"
|
|||||||
export const VIEW_PROJECT_INFRA = "8d7cc6e3-9147-41f7-aaa7-fa507e450bd4"
|
export const VIEW_PROJECT_INFRA = "8d7cc6e3-9147-41f7-aaa7-fa507e450bd4"
|
||||||
|
|
||||||
export const REGULARIZE_ATTENDANCE ="57802c4a-00aa-4a1f-a048-fd2f70dd44b6"
|
export const REGULARIZE_ATTENDANCE ="57802c4a-00aa-4a1f-a048-fd2f70dd44b6"
|
||||||
export const TEAM_ATTENDANCE = "915e6bff-65f6-4e3f-aea8-3fd217d3ea9e"
|
|
||||||
export const SELF_ATTENDANCE = "ccb0589f-712b-43de-92ed-5b6088e7dc4e"
|
|
||||||
|
|
||||||
|
|
||||||
export const ASSIGN_TO_PROJECT = "b94802ce-0689-4643-9e1d-11c86950c35b";
|
export const ASSIGN_TO_PROJECT = "b94802ce-0689-4643-9e1d-11c86950c35b";
|
||||||
@ -63,45 +59,10 @@ export const EXPENSE_MANAGE = "ea5a1529-4ee8-4828-80ea-0e23c9d4dd11"
|
|||||||
export const EXPENSE_REJECTEDBY = ["d1ee5eec-24b6-4364-8673-a8f859c60729","965eda62-7907-4963-b4a1-657fb0b2724b"]
|
export const EXPENSE_REJECTEDBY = ["d1ee5eec-24b6-4364-8673-a8f859c60729","965eda62-7907-4963-b4a1-657fb0b2724b"]
|
||||||
|
|
||||||
export const EXPENSE_DRAFT = "297e0d8f-f668-41b5-bfea-e03b354251c8"
|
export const EXPENSE_DRAFT = "297e0d8f-f668-41b5-bfea-e03b354251c8"
|
||||||
|
|
||||||
export const SUPPER_TENANT = "d032cb1a-3f30-462c-bef0-7ace73a71c0b"
|
|
||||||
export const MANAGE_TENANTS = "00e20637-ce8d-4417-bec4-9b31b5e65092"
|
|
||||||
export const VIEW_TENANTS = "647145c6-2108-4c98-aab4-178602236e55"
|
|
||||||
export const ActiveTenant = "297e0d8f-f668-41b5-bfea-e03b354251c8"
|
|
||||||
// -------------------Application Role------------------------------
|
// -------------------Application Role------------------------------
|
||||||
|
|
||||||
// 1 - Expense Manage
|
// 1 - Expense Manage
|
||||||
export const EXPENSE_MANAGEMENT = "a4e25142-449b-4334-a6e5-22f70e4732d7"
|
export const EXPENSE_MANAGEMENT = "a4e25142-449b-4334-a6e5-22f70e4732d7"
|
||||||
|
|
||||||
export const TENANT_STATUS = [
|
|
||||||
{id:"62b05792-5115-4f99-8ff5-e8374859b191",name:"Active"},
|
|
||||||
{id:"c0b5def8-087e-4235-b3a4-8e2f0ed91b94",name:"In Active"},
|
|
||||||
{id:"35d7840a-164a-448b-95e6-efb2ec84a751",name:"Supspended"}
|
|
||||||
]
|
|
||||||
|
|
||||||
export const CONSTANT_TEXT = {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export const SUBSCRIPTION_PLAN_FREQUENCIES = {
|
|
||||||
0: "Monthly",
|
|
||||||
1:"Quarterly",
|
|
||||||
2:"Half-Yearly",
|
|
||||||
3:"Yearly"
|
|
||||||
}
|
|
||||||
export const reference = [
|
|
||||||
{ val: "google", name: "Google" },
|
|
||||||
{ val: "frineds", name: "Friends" },
|
|
||||||
{ val: "advertisement", name: "Advertisement" },
|
|
||||||
{ val: "root tenant", name: "Root Tenant" },
|
|
||||||
];
|
|
||||||
export const orgSize = [
|
|
||||||
{ val: "1-50", name: "1-50" },
|
|
||||||
{ val: "51-100", name: "51-100" },
|
|
||||||
{ val: "101-500", name: "101-500" },
|
|
||||||
{ val: "500+", name: "500+" },
|
|
||||||
];
|
|
||||||
|
|
||||||
export const BASE_URL = process.env.VITE_BASE_URL;
|
export const BASE_URL = process.env.VITE_BASE_URL;
|
||||||
|
|
||||||
// export const BASE_URL = "https://api.marcoaiot.com";
|
// export const BASE_URL = "https://api.marcoaiot.com";
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
import { ActiveTenant } from "./constants";
|
|
||||||
|
|
||||||
export const getDateDifferenceInDays = (startDate, endDate) => {
|
export const getDateDifferenceInDays = (startDate, endDate) => {
|
||||||
if (!startDate || !endDate) {
|
if (!startDate || !endDate) {
|
||||||
@ -83,8 +82,4 @@ export const getCompletionPercentage = (completedWork, plannedWork)=> {
|
|||||||
const clamped = Math.min(Math.max(percentage, 0), 100);
|
const clamped = Math.min(Math.max(percentage, 0), 100);
|
||||||
|
|
||||||
return clamped.toFixed(2);
|
return clamped.toFixed(2);
|
||||||
}
|
|
||||||
|
|
||||||
export const getTenantStatus =(statusId)=>{
|
|
||||||
return ActiveTenant === statusId ? " bg-label-success":"bg-label-secondary"
|
|
||||||
}
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user